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,480 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Social commands
|
|
3
|
+
* chainlesschain social contact|friend|post|chat|stats
|
|
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
|
+
ensureSocialTables,
|
|
11
|
+
addContact,
|
|
12
|
+
listContacts,
|
|
13
|
+
deleteContact,
|
|
14
|
+
showContact,
|
|
15
|
+
addFriend,
|
|
16
|
+
listFriends,
|
|
17
|
+
removeFriend,
|
|
18
|
+
pendingRequests,
|
|
19
|
+
publishPost,
|
|
20
|
+
listPosts,
|
|
21
|
+
likePost,
|
|
22
|
+
sendChatMessage,
|
|
23
|
+
getChatMessages,
|
|
24
|
+
getChatThreads,
|
|
25
|
+
getSocialStats,
|
|
26
|
+
} from "../lib/social-manager.js";
|
|
27
|
+
|
|
28
|
+
export function registerSocialCommand(program) {
|
|
29
|
+
const social = program
|
|
30
|
+
.command("social")
|
|
31
|
+
.description("Social platform — contacts, friends, posts, chat");
|
|
32
|
+
|
|
33
|
+
// ── Contact subcommands ─────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
const contact = social.command("contact").description("Contact management");
|
|
36
|
+
|
|
37
|
+
contact
|
|
38
|
+
.command("add <name>")
|
|
39
|
+
.description("Add a contact")
|
|
40
|
+
.option("-d, --did <did>", "Contact DID")
|
|
41
|
+
.option("-e, --email <email>", "Contact email")
|
|
42
|
+
.option("-n, --notes <text>", "Notes")
|
|
43
|
+
.action(async (name, options) => {
|
|
44
|
+
try {
|
|
45
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
46
|
+
if (!ctx.db) {
|
|
47
|
+
logger.error("Database not available");
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
const db = ctx.db.getDatabase();
|
|
51
|
+
ensureSocialTables(db);
|
|
52
|
+
|
|
53
|
+
const c = addContact(
|
|
54
|
+
db,
|
|
55
|
+
name,
|
|
56
|
+
options.did,
|
|
57
|
+
options.email,
|
|
58
|
+
options.notes,
|
|
59
|
+
);
|
|
60
|
+
logger.success(`Contact ${chalk.cyan(c.name)} added`);
|
|
61
|
+
logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(c.id)}`);
|
|
62
|
+
await shutdown();
|
|
63
|
+
} catch (err) {
|
|
64
|
+
logger.error(`Failed: ${err.message}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
contact
|
|
70
|
+
.command("list")
|
|
71
|
+
.description("List contacts")
|
|
72
|
+
.option("--json", "Output as JSON")
|
|
73
|
+
.action(async (options) => {
|
|
74
|
+
try {
|
|
75
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
76
|
+
if (!ctx.db) {
|
|
77
|
+
logger.error("Database not available");
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
const db = ctx.db.getDatabase();
|
|
81
|
+
ensureSocialTables(db);
|
|
82
|
+
|
|
83
|
+
const contacts = listContacts();
|
|
84
|
+
if (options.json) {
|
|
85
|
+
console.log(JSON.stringify(contacts, null, 2));
|
|
86
|
+
} else if (contacts.length === 0) {
|
|
87
|
+
logger.info("No contacts.");
|
|
88
|
+
} else {
|
|
89
|
+
for (const c of contacts) {
|
|
90
|
+
logger.log(
|
|
91
|
+
` ${chalk.cyan(c.id.slice(0, 8))} ${c.name} ${c.email || ""} ${c.did || ""}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
await shutdown();
|
|
96
|
+
} catch (err) {
|
|
97
|
+
logger.error(`Failed: ${err.message}`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
contact
|
|
103
|
+
.command("delete <contact-id>")
|
|
104
|
+
.description("Delete a contact")
|
|
105
|
+
.action(async (contactId) => {
|
|
106
|
+
try {
|
|
107
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
108
|
+
if (!ctx.db) {
|
|
109
|
+
logger.error("Database not available");
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
const db = ctx.db.getDatabase();
|
|
113
|
+
ensureSocialTables(db);
|
|
114
|
+
|
|
115
|
+
deleteContact(db, contactId);
|
|
116
|
+
logger.success(`Contact deleted`);
|
|
117
|
+
await shutdown();
|
|
118
|
+
} catch (err) {
|
|
119
|
+
logger.error(`Failed: ${err.message}`);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
contact
|
|
125
|
+
.command("show <contact-id>")
|
|
126
|
+
.description("Show contact details")
|
|
127
|
+
.option("--json", "Output as JSON")
|
|
128
|
+
.action(async (contactId, options) => {
|
|
129
|
+
try {
|
|
130
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
131
|
+
if (!ctx.db) {
|
|
132
|
+
logger.error("Database not available");
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
const db = ctx.db.getDatabase();
|
|
136
|
+
ensureSocialTables(db);
|
|
137
|
+
|
|
138
|
+
const c = showContact(contactId);
|
|
139
|
+
if (options.json) {
|
|
140
|
+
console.log(JSON.stringify(c, null, 2));
|
|
141
|
+
} else {
|
|
142
|
+
logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(c.id)}`);
|
|
143
|
+
logger.log(` ${chalk.bold("Name:")} ${c.name}`);
|
|
144
|
+
logger.log(` ${chalk.bold("DID:")} ${c.did || "N/A"}`);
|
|
145
|
+
logger.log(` ${chalk.bold("Email:")} ${c.email || "N/A"}`);
|
|
146
|
+
logger.log(` ${chalk.bold("Notes:")} ${c.notes || "N/A"}`);
|
|
147
|
+
}
|
|
148
|
+
await shutdown();
|
|
149
|
+
} catch (err) {
|
|
150
|
+
logger.error(`Failed: ${err.message}`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// ── Friend subcommands ──────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
const friend = social.command("friend").description("Friend management");
|
|
158
|
+
|
|
159
|
+
friend
|
|
160
|
+
.command("add <contact-id>")
|
|
161
|
+
.description("Send a friend request")
|
|
162
|
+
.action(async (contactId) => {
|
|
163
|
+
try {
|
|
164
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
165
|
+
if (!ctx.db) {
|
|
166
|
+
logger.error("Database not available");
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
const db = ctx.db.getDatabase();
|
|
170
|
+
ensureSocialTables(db);
|
|
171
|
+
|
|
172
|
+
const f = addFriend(db, contactId);
|
|
173
|
+
logger.success(`Friend request sent (${f.status})`);
|
|
174
|
+
await shutdown();
|
|
175
|
+
} catch (err) {
|
|
176
|
+
logger.error(`Failed: ${err.message}`);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
friend
|
|
182
|
+
.command("list")
|
|
183
|
+
.description("List friends")
|
|
184
|
+
.option("--json", "Output as JSON")
|
|
185
|
+
.action(async (options) => {
|
|
186
|
+
try {
|
|
187
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
188
|
+
if (!ctx.db) {
|
|
189
|
+
logger.error("Database not available");
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
const db = ctx.db.getDatabase();
|
|
193
|
+
ensureSocialTables(db);
|
|
194
|
+
|
|
195
|
+
const friends = listFriends();
|
|
196
|
+
if (options.json) {
|
|
197
|
+
console.log(JSON.stringify(friends, null, 2));
|
|
198
|
+
} else if (friends.length === 0) {
|
|
199
|
+
logger.info("No friends.");
|
|
200
|
+
} else {
|
|
201
|
+
for (const f of friends) {
|
|
202
|
+
logger.log(
|
|
203
|
+
` ${chalk.cyan(f.contactId.slice(0, 8))} [${f.status}]`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
await shutdown();
|
|
208
|
+
} catch (err) {
|
|
209
|
+
logger.error(`Failed: ${err.message}`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
friend
|
|
215
|
+
.command("remove <contact-id>")
|
|
216
|
+
.description("Remove a friend")
|
|
217
|
+
.action(async (contactId) => {
|
|
218
|
+
try {
|
|
219
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
220
|
+
if (!ctx.db) {
|
|
221
|
+
logger.error("Database not available");
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
const db = ctx.db.getDatabase();
|
|
225
|
+
ensureSocialTables(db);
|
|
226
|
+
|
|
227
|
+
removeFriend(db, contactId);
|
|
228
|
+
logger.success("Friend removed");
|
|
229
|
+
await shutdown();
|
|
230
|
+
} catch (err) {
|
|
231
|
+
logger.error(`Failed: ${err.message}`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
friend
|
|
237
|
+
.command("pending")
|
|
238
|
+
.description("List pending friend requests")
|
|
239
|
+
.option("--json", "Output as JSON")
|
|
240
|
+
.action(async (options) => {
|
|
241
|
+
try {
|
|
242
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
243
|
+
if (!ctx.db) {
|
|
244
|
+
logger.error("Database not available");
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
const db = ctx.db.getDatabase();
|
|
248
|
+
ensureSocialTables(db);
|
|
249
|
+
|
|
250
|
+
const pending = pendingRequests();
|
|
251
|
+
if (options.json) {
|
|
252
|
+
console.log(JSON.stringify(pending, null, 2));
|
|
253
|
+
} else if (pending.length === 0) {
|
|
254
|
+
logger.info("No pending requests.");
|
|
255
|
+
} else {
|
|
256
|
+
for (const p of pending) {
|
|
257
|
+
logger.log(
|
|
258
|
+
` ${chalk.cyan(p.contactId.slice(0, 8))} [${p.status}]`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
await shutdown();
|
|
263
|
+
} catch (err) {
|
|
264
|
+
logger.error(`Failed: ${err.message}`);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// ── Post subcommands ────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
const post = social.command("post").description("Social posts");
|
|
272
|
+
|
|
273
|
+
post
|
|
274
|
+
.command("publish <content>")
|
|
275
|
+
.description("Publish a post")
|
|
276
|
+
.option("-a, --author <name>", "Author name", "cli-user")
|
|
277
|
+
.action(async (content, options) => {
|
|
278
|
+
try {
|
|
279
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
280
|
+
if (!ctx.db) {
|
|
281
|
+
logger.error("Database not available");
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
const db = ctx.db.getDatabase();
|
|
285
|
+
ensureSocialTables(db);
|
|
286
|
+
|
|
287
|
+
const p = publishPost(db, content, options.author);
|
|
288
|
+
logger.success("Post published");
|
|
289
|
+
logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(p.id)}`);
|
|
290
|
+
await shutdown();
|
|
291
|
+
} catch (err) {
|
|
292
|
+
logger.error(`Failed: ${err.message}`);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
post
|
|
298
|
+
.command("list")
|
|
299
|
+
.description("List posts")
|
|
300
|
+
.option("-a, --author <name>", "Filter by author")
|
|
301
|
+
.option("--json", "Output as JSON")
|
|
302
|
+
.action(async (options) => {
|
|
303
|
+
try {
|
|
304
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
305
|
+
if (!ctx.db) {
|
|
306
|
+
logger.error("Database not available");
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
const db = ctx.db.getDatabase();
|
|
310
|
+
ensureSocialTables(db);
|
|
311
|
+
|
|
312
|
+
const posts = listPosts({ author: options.author });
|
|
313
|
+
if (options.json) {
|
|
314
|
+
console.log(JSON.stringify(posts, null, 2));
|
|
315
|
+
} else if (posts.length === 0) {
|
|
316
|
+
logger.info("No posts.");
|
|
317
|
+
} else {
|
|
318
|
+
for (const p of posts) {
|
|
319
|
+
logger.log(
|
|
320
|
+
` ${chalk.cyan(p.id.slice(0, 8))} by ${p.author} — "${p.content.slice(0, 60)}" ♥ ${p.likes}`,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
await shutdown();
|
|
325
|
+
} catch (err) {
|
|
326
|
+
logger.error(`Failed: ${err.message}`);
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
post
|
|
332
|
+
.command("like <post-id>")
|
|
333
|
+
.description("Like a post")
|
|
334
|
+
.action(async (postId) => {
|
|
335
|
+
try {
|
|
336
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
337
|
+
if (!ctx.db) {
|
|
338
|
+
logger.error("Database not available");
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
const db = ctx.db.getDatabase();
|
|
342
|
+
ensureSocialTables(db);
|
|
343
|
+
|
|
344
|
+
const p = likePost(db, postId);
|
|
345
|
+
logger.success(`Post liked (${p.likes} total)`);
|
|
346
|
+
await shutdown();
|
|
347
|
+
} catch (err) {
|
|
348
|
+
logger.error(`Failed: ${err.message}`);
|
|
349
|
+
process.exit(1);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// ── Chat subcommands ────────────────────────────────────────
|
|
354
|
+
|
|
355
|
+
const chat = social.command("chat").description("Direct messaging");
|
|
356
|
+
|
|
357
|
+
chat
|
|
358
|
+
.command("send <recipient> <message>")
|
|
359
|
+
.description("Send a chat message")
|
|
360
|
+
.option("-s, --sender <name>", "Sender name", "cli-user")
|
|
361
|
+
.action(async (recipient, message, options) => {
|
|
362
|
+
try {
|
|
363
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
364
|
+
if (!ctx.db) {
|
|
365
|
+
logger.error("Database not available");
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
const db = ctx.db.getDatabase();
|
|
369
|
+
ensureSocialTables(db);
|
|
370
|
+
|
|
371
|
+
const msg = sendChatMessage(db, recipient, message, options.sender);
|
|
372
|
+
logger.success(`Message sent to ${chalk.cyan(recipient)}`);
|
|
373
|
+
logger.log(` ${chalk.bold("Thread:")} ${msg.threadId}`);
|
|
374
|
+
await shutdown();
|
|
375
|
+
} catch (err) {
|
|
376
|
+
logger.error(`Failed: ${err.message}`);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
chat
|
|
382
|
+
.command("messages <thread-id>")
|
|
383
|
+
.description("Get messages in a thread")
|
|
384
|
+
.option("-n, --limit <n>", "Max messages", "50")
|
|
385
|
+
.option("--json", "Output as JSON")
|
|
386
|
+
.action(async (threadId, options) => {
|
|
387
|
+
try {
|
|
388
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
389
|
+
if (!ctx.db) {
|
|
390
|
+
logger.error("Database not available");
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
const db = ctx.db.getDatabase();
|
|
394
|
+
ensureSocialTables(db);
|
|
395
|
+
|
|
396
|
+
const messages = getChatMessages(threadId, {
|
|
397
|
+
limit: parseInt(options.limit),
|
|
398
|
+
});
|
|
399
|
+
if (options.json) {
|
|
400
|
+
console.log(JSON.stringify(messages, null, 2));
|
|
401
|
+
} else if (messages.length === 0) {
|
|
402
|
+
logger.info("No messages in this thread.");
|
|
403
|
+
} else {
|
|
404
|
+
for (const m of messages) {
|
|
405
|
+
logger.log(` ${chalk.gray(m.sender)}: ${m.content}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
await shutdown();
|
|
409
|
+
} catch (err) {
|
|
410
|
+
logger.error(`Failed: ${err.message}`);
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
chat
|
|
416
|
+
.command("threads")
|
|
417
|
+
.description("List chat threads")
|
|
418
|
+
.option("--json", "Output as JSON")
|
|
419
|
+
.action(async (options) => {
|
|
420
|
+
try {
|
|
421
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
422
|
+
if (!ctx.db) {
|
|
423
|
+
logger.error("Database not available");
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
const db = ctx.db.getDatabase();
|
|
427
|
+
ensureSocialTables(db);
|
|
428
|
+
|
|
429
|
+
const threads = getChatThreads();
|
|
430
|
+
if (options.json) {
|
|
431
|
+
console.log(JSON.stringify(threads, null, 2));
|
|
432
|
+
} else if (threads.length === 0) {
|
|
433
|
+
logger.info("No chat threads.");
|
|
434
|
+
} else {
|
|
435
|
+
for (const t of threads) {
|
|
436
|
+
logger.log(
|
|
437
|
+
` ${chalk.cyan(t.threadId)} (${t.messageCount} messages)`,
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
await shutdown();
|
|
442
|
+
} catch (err) {
|
|
443
|
+
logger.error(`Failed: ${err.message}`);
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// ── Stats ───────────────────────────────────────────────────
|
|
449
|
+
|
|
450
|
+
social
|
|
451
|
+
.command("stats")
|
|
452
|
+
.description("Show social statistics")
|
|
453
|
+
.option("--json", "Output as JSON")
|
|
454
|
+
.action(async (options) => {
|
|
455
|
+
try {
|
|
456
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
457
|
+
if (!ctx.db) {
|
|
458
|
+
logger.error("Database not available");
|
|
459
|
+
process.exit(1);
|
|
460
|
+
}
|
|
461
|
+
const db = ctx.db.getDatabase();
|
|
462
|
+
ensureSocialTables(db);
|
|
463
|
+
|
|
464
|
+
const stats = getSocialStats();
|
|
465
|
+
if (options.json) {
|
|
466
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
467
|
+
} else {
|
|
468
|
+
logger.log(` ${chalk.bold("Contacts:")} ${stats.contacts}`);
|
|
469
|
+
logger.log(` ${chalk.bold("Friends:")} ${stats.friends}`);
|
|
470
|
+
logger.log(` ${chalk.bold("Posts:")} ${stats.posts}`);
|
|
471
|
+
logger.log(` ${chalk.bold("Messages:")} ${stats.messages}`);
|
|
472
|
+
logger.log(` ${chalk.bold("Pending:")} ${stats.pendingRequests}`);
|
|
473
|
+
}
|
|
474
|
+
await shutdown();
|
|
475
|
+
} catch (err) {
|
|
476
|
+
logger.error(`Failed: ${err.message}`);
|
|
477
|
+
process.exit(1);
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terraform commands
|
|
3
|
+
* chainlesschain terraform workspaces|create|plan|runs
|
|
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
|
+
ensureTerraformTables,
|
|
11
|
+
listWorkspaces,
|
|
12
|
+
createWorkspace,
|
|
13
|
+
planRun,
|
|
14
|
+
listRuns,
|
|
15
|
+
} from "../lib/terraform-manager.js";
|
|
16
|
+
|
|
17
|
+
export function registerTerraformCommand(program) {
|
|
18
|
+
const terraform = program
|
|
19
|
+
.command("terraform")
|
|
20
|
+
.description("Terraform IaC — workspace and run management");
|
|
21
|
+
|
|
22
|
+
terraform
|
|
23
|
+
.command("workspaces")
|
|
24
|
+
.description("List Terraform workspaces")
|
|
25
|
+
.option("--status <status>", "Filter by status")
|
|
26
|
+
.option("--json", "Output as JSON")
|
|
27
|
+
.action(async (options) => {
|
|
28
|
+
try {
|
|
29
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
30
|
+
if (!ctx.db) {
|
|
31
|
+
logger.error("Database not available");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const db = ctx.db.getDatabase();
|
|
35
|
+
ensureTerraformTables(db);
|
|
36
|
+
|
|
37
|
+
const workspaces = listWorkspaces({ status: options.status });
|
|
38
|
+
if (options.json) {
|
|
39
|
+
console.log(JSON.stringify(workspaces, null, 2));
|
|
40
|
+
} else if (workspaces.length === 0) {
|
|
41
|
+
logger.info("No workspaces. Use `terraform create` to add one.");
|
|
42
|
+
} else {
|
|
43
|
+
for (const w of workspaces) {
|
|
44
|
+
logger.log(
|
|
45
|
+
` ${chalk.cyan(w.id.slice(0, 8))} ${w.name} [${w.status}] tf=${w.terraformVersion} state=v${w.stateVersion}`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
await shutdown();
|
|
50
|
+
} catch (err) {
|
|
51
|
+
logger.error(`Failed: ${err.message}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
terraform
|
|
57
|
+
.command("create <name>")
|
|
58
|
+
.description("Create a Terraform workspace")
|
|
59
|
+
.option("-d, --description <text>", "Workspace description")
|
|
60
|
+
.option("--tf-version <version>", "Terraform version", "1.9.0")
|
|
61
|
+
.option("--auto-apply", "Enable auto-apply")
|
|
62
|
+
.action(async (name, options) => {
|
|
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
|
+
ensureTerraformTables(db);
|
|
71
|
+
|
|
72
|
+
const ws = createWorkspace(db, name, {
|
|
73
|
+
description: options.description,
|
|
74
|
+
terraformVersion: options.tfVersion,
|
|
75
|
+
autoApply: options.autoApply,
|
|
76
|
+
});
|
|
77
|
+
logger.success("Workspace created");
|
|
78
|
+
logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(ws.id)}`);
|
|
79
|
+
logger.log(` ${chalk.bold("Name:")} ${ws.name}`);
|
|
80
|
+
logger.log(` ${chalk.bold("Version:")} ${ws.terraformVersion}`);
|
|
81
|
+
await shutdown();
|
|
82
|
+
} catch (err) {
|
|
83
|
+
logger.error(`Failed: ${err.message}`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
terraform
|
|
89
|
+
.command("plan <workspace-id>")
|
|
90
|
+
.description("Run a Terraform plan")
|
|
91
|
+
.option("-t, --type <type>", "Run type: plan, apply, destroy", "plan")
|
|
92
|
+
.action(async (workspaceId, options) => {
|
|
93
|
+
try {
|
|
94
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
95
|
+
if (!ctx.db) {
|
|
96
|
+
logger.error("Database not available");
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
const db = ctx.db.getDatabase();
|
|
100
|
+
ensureTerraformTables(db);
|
|
101
|
+
|
|
102
|
+
const run = planRun(db, workspaceId, { runType: options.type });
|
|
103
|
+
logger.success(`Run completed: ${run.planOutput}`);
|
|
104
|
+
logger.log(` ${chalk.bold("Run ID:")} ${chalk.cyan(run.id)}`);
|
|
105
|
+
logger.log(` ${chalk.bold("Added:")} ${run.resourcesAdded}`);
|
|
106
|
+
logger.log(` ${chalk.bold("Changed:")} ${run.resourcesChanged}`);
|
|
107
|
+
logger.log(` ${chalk.bold("Destroyed:")} ${run.resourcesDestroyed}`);
|
|
108
|
+
await shutdown();
|
|
109
|
+
} catch (err) {
|
|
110
|
+
logger.error(`Failed: ${err.message}`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
terraform
|
|
116
|
+
.command("runs")
|
|
117
|
+
.description("List Terraform runs")
|
|
118
|
+
.option("-w, --workspace <id>", "Filter by workspace ID")
|
|
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
|
+
ensureTerraformTables(db);
|
|
129
|
+
|
|
130
|
+
const runs = listRuns({ workspaceId: options.workspace });
|
|
131
|
+
if (options.json) {
|
|
132
|
+
console.log(JSON.stringify(runs, null, 2));
|
|
133
|
+
} else if (runs.length === 0) {
|
|
134
|
+
logger.info("No runs found.");
|
|
135
|
+
} else {
|
|
136
|
+
for (const r of runs) {
|
|
137
|
+
logger.log(
|
|
138
|
+
` ${chalk.cyan(r.id.slice(0, 8))} ${r.runType} [${r.status}] +${r.resourcesAdded} ~${r.resourcesChanged} -${r.resourcesDestroyed}`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
await shutdown();
|
|
143
|
+
} catch (err) {
|
|
144
|
+
logger.error(`Failed: ${err.message}`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|