@xnetjs/cli 0.0.2 → 0.0.4

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.
Files changed (2) hide show
  1. package/dist/cli.js +1028 -160
  2. package/package.json +10 -3
package/dist/cli.js CHANGED
@@ -8,9 +8,793 @@ import {
8
8
  // src/cli.ts
9
9
  import { program } from "commander";
10
10
 
11
+ // src/commands/agent.ts
12
+ import { readFile, rename, mkdir } from "fs/promises";
13
+ import { dirname, join, resolve } from "path";
14
+ import {
15
+ ScriptSandbox,
16
+ XNET_AGENT_SKILL_MD,
17
+ createAgentScriptContext,
18
+ createAiSurfaceService,
19
+ createAiWorkspaceExporter,
20
+ createAiWorkspaceWatcher,
21
+ flattenRowForTsv,
22
+ toTsv
23
+ } from "@xnetjs/plugins/node";
24
+
25
+ // src/utils/agent-remote.ts
26
+ var RemoteApiError = class extends Error {
27
+ constructor(message, status) {
28
+ super(message);
29
+ this.status = status;
30
+ this.name = "RemoteApiError";
31
+ }
32
+ };
33
+ async function createRemoteAgentBackend(options = {}) {
34
+ const baseUrl = (options.apiUrl ?? process.env.XNET_API_URL ?? "http://127.0.0.1:31415").replace(
35
+ /\/$/,
36
+ ""
37
+ );
38
+ const token = options.token ?? process.env.XNET_API_TOKEN;
39
+ const request = async (method, path, body) => {
40
+ const response = await fetch(`${baseUrl}${path}`, {
41
+ method,
42
+ headers: {
43
+ "content-type": "application/json",
44
+ ...token ? { authorization: `Bearer ${token}` } : {}
45
+ },
46
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
47
+ });
48
+ const text2 = await response.text();
49
+ const parsed = text2 ? JSON.parse(text2) : null;
50
+ if (!response.ok) {
51
+ const message = isRecord(parsed) && typeof parsed.error === "string" ? parsed.error : `${method} ${path} failed with ${response.status}`;
52
+ throw new RemoteApiError(message, response.status);
53
+ }
54
+ return parsed;
55
+ };
56
+ const store = {
57
+ get: async (id) => {
58
+ try {
59
+ return await request("GET", `/api/v1/nodes/${encodeURIComponent(id)}`);
60
+ } catch (err) {
61
+ if (err instanceof RemoteApiError && err.status === 404) return null;
62
+ throw err;
63
+ }
64
+ },
65
+ list: async (listOptions) => {
66
+ const params = new URLSearchParams();
67
+ if (listOptions?.schemaId) params.set("schema", listOptions.schemaId);
68
+ if (listOptions?.limit !== void 0) params.set("limit", String(listOptions.limit));
69
+ if (listOptions?.offset !== void 0) params.set("offset", String(listOptions.offset));
70
+ const query = params.size > 0 ? `?${params.toString()}` : "";
71
+ const result = await request("GET", `/api/v1/nodes${query}`);
72
+ return isRecord(result) && Array.isArray(result.nodes) ? result.nodes : [];
73
+ },
74
+ create: async ({ schemaId, properties }) => await request("POST", "/api/v1/nodes", { schema: schemaId, properties }),
75
+ update: async (id, { properties }) => await request("PATCH", `/api/v1/nodes/${encodeURIComponent(id)}`, properties),
76
+ delete: async (id) => {
77
+ await request("DELETE", `/api/v1/nodes/${encodeURIComponent(id)}`);
78
+ },
79
+ subscribe: () => () => {
80
+ }
81
+ };
82
+ const schemaCache = /* @__PURE__ */ new Map();
83
+ const listed = await request("GET", "/api/v1/schemas");
84
+ if (isRecord(listed) && Array.isArray(listed.schemas)) {
85
+ for (const schema of listed.schemas) {
86
+ if (isRecord(schema) && typeof schema.iri === "string") {
87
+ schemaCache.set(schema.iri, schema);
88
+ }
89
+ }
90
+ }
91
+ const schemas = {
92
+ getAllIRIs: () => Array.from(schemaCache.keys()),
93
+ get: async (iri) => {
94
+ const cached = schemaCache.get(iri);
95
+ if (cached) return cached;
96
+ try {
97
+ const fetched = await request(
98
+ "GET",
99
+ `/api/v1/schemas/${encodeURIComponent(iri)}`
100
+ );
101
+ schemaCache.set(iri, fetched);
102
+ return fetched;
103
+ } catch (err) {
104
+ if (err instanceof RemoteApiError && err.status === 404) return null;
105
+ throw err;
106
+ }
107
+ }
108
+ };
109
+ return { store, schemas };
110
+ }
111
+ function isRecord(value) {
112
+ return typeof value === "object" && value !== null && !Array.isArray(value);
113
+ }
114
+
115
+ // src/commands/agent.ts
116
+ function createAgentServices(backend) {
117
+ const aiSurface = createAiSurfaceService({ store: backend.store, schemas: backend.schemas });
118
+ return {
119
+ store: backend.store,
120
+ schemas: backend.schemas,
121
+ aiSurface,
122
+ exporter: createAiWorkspaceExporter({ ...backend, aiSurface }),
123
+ watcher: createAiWorkspaceWatcher({ ...backend, aiSurface })
124
+ };
125
+ }
126
+ var defaultServicesFactory = async ({ apiUrl }) => createAgentServices(await createRemoteAgentBackend({ apiUrl }));
127
+ async function runCheckout(services, options) {
128
+ const result = await services.exporter.checkout({
129
+ rootDir: resolve(options.dir),
130
+ ...options.name ? { workspaceName: options.name } : {},
131
+ scope: {
132
+ ...options.query ? { query: options.query } : {},
133
+ ...options.schema?.length ? { schemaIds: options.schema } : {},
134
+ ...options.node?.length ? { nodeIds: options.node } : {},
135
+ ...options.kind?.length ? { kinds: options.kind } : {},
136
+ ...options.limit !== void 0 ? { limit: options.limit } : {}
137
+ }
138
+ });
139
+ const lines = result.manifestEntries.map((entry) => `${entry.path} ${entry.id}`);
140
+ return [
141
+ `checked out ${result.manifestEntries.length} file(s) into ${options.dir}`,
142
+ ...lines
143
+ ].join("\n");
144
+ }
145
+ async function runStatus(services, options) {
146
+ const scan = await services.watcher.scanChangedFiles({
147
+ rootDir: resolve(options.dir),
148
+ writePendingPlans: false,
149
+ writeConflicts: false,
150
+ writeReviewIndex: false
151
+ });
152
+ if (options.format === "json") {
153
+ return JSON.stringify({
154
+ pendingPlans: scan.pendingPlans.map((pending) => ({
155
+ path: pending.path,
156
+ planId: pending.plan.id,
157
+ intent: pending.plan.intent
158
+ })),
159
+ conflicts: scan.conflicts
160
+ });
161
+ }
162
+ const lines = [
163
+ ...scan.pendingPlans.map(
164
+ (pending) => `pending ${pending.path} ${pending.plan.id} ${pending.plan.intent}`
165
+ ),
166
+ ...scan.conflicts.map(
167
+ (conflict) => `conflict ${conflict.path} ${conflict.kind} ${conflict.message}`
168
+ )
169
+ ];
170
+ return lines.length > 0 ? lines.join("\n") : "clean";
171
+ }
172
+ async function runCommit(services, options) {
173
+ const rootDir = resolve(options.dir);
174
+ const scan = await services.watcher.scanChangedFiles({
175
+ rootDir,
176
+ actor: options.actor ?? "xnet-cli"
177
+ });
178
+ const lines = [];
179
+ for (const conflict of scan.conflicts) {
180
+ lines.push(`conflict ${conflict.path} ${conflict.kind} see ${conflict.notePath ?? ""}`);
181
+ }
182
+ if (!options.apply) {
183
+ for (const pending of scan.pendingPlans) {
184
+ lines.push(`planned ${pending.path} ${pending.plan.id}`);
185
+ }
186
+ if (scan.pendingPlans.length === 0 && scan.conflicts.length === 0) return "clean";
187
+ lines.push(`${scan.pendingPlans.length} plan(s) pending; re-run with --apply to apply`);
188
+ return lines.join("\n");
189
+ }
190
+ const appliedNodeIds = [];
191
+ for (const pending of scan.pendingPlans) {
192
+ const outcome = await applyPendingPlan(services, pending.plan);
193
+ lines.push(`${outcome.status} ${pending.path} ${pending.plan.id} ${outcome.detail}`);
194
+ if (outcome.status === "applied") {
195
+ appliedNodeIds.push(...pending.plan.changes.map((change) => change.targetId));
196
+ await archivePendingPlan(rootDir, pending.planPath);
197
+ }
198
+ }
199
+ if (appliedNodeIds.length > 0) {
200
+ await services.exporter.checkout({ rootDir, scope: { nodeIds: appliedNodeIds } });
201
+ }
202
+ return lines.length > 0 ? lines.join("\n") : "clean";
203
+ }
204
+ var APPLY_TOOL_BY_TARGET_KIND = {
205
+ page: "xnet_apply_page_markdown",
206
+ database: "xnet_apply_database_mutation",
207
+ databaseRows: "xnet_apply_database_mutation"
208
+ };
209
+ async function applyPendingPlan(services, plan) {
210
+ const targetKind = plan.changes[0]?.targetKind ?? "unknown";
211
+ const tool = APPLY_TOOL_BY_TARGET_KIND[targetKind];
212
+ if (!tool) {
213
+ return { status: "skipped", detail: `${targetKind} plans need review in the xNet app` };
214
+ }
215
+ try {
216
+ const result = await services.aiSurface.callTool(tool, { plan, confirmApply: true });
217
+ return result.applied ? { status: "applied", detail: `${targetKind} plan applied` } : { status: "failed", detail: result.validation?.errors?.join("; ") ?? "not applied" };
218
+ } catch (err) {
219
+ return { status: "failed", detail: err instanceof Error ? err.message : String(err) };
220
+ }
221
+ }
222
+ async function archivePendingPlan(rootDir, planPath) {
223
+ const appliedPath = planPath.replace(".xnet/pending/", ".xnet/applied/");
224
+ try {
225
+ await mkdir(dirname(join(rootDir, appliedPath)), { recursive: true });
226
+ await rename(join(rootDir, planPath), join(rootDir, appliedPath));
227
+ } catch {
228
+ }
229
+ }
230
+ async function runSearch(services, options) {
231
+ const result = await services.aiSurface.search({
232
+ query: options.text,
233
+ schemaId: options.schema,
234
+ limit: options.limit
235
+ });
236
+ const results = Array.isArray(result.results) ? result.results : [];
237
+ if (options.format === "json") return JSON.stringify(result);
238
+ if (options.format === "jsonl") return results.map((row) => JSON.stringify(row)).join("\n");
239
+ const compact = results.map((row) => ({
240
+ id: row.id,
241
+ schemaId: row.schemaId,
242
+ title: row.title,
243
+ snippet: row.snippet
244
+ }));
245
+ if (results.length === 0) return "no results";
246
+ if (options.format === "md") return toMarkdownTable(compact);
247
+ return toTsv(compact).trimEnd();
248
+ }
249
+ async function runQuery(services, options) {
250
+ const where = parseAssignments(options.where ?? []);
251
+ const result = await services.aiSurface.callTool("xnet_database_query", {
252
+ databaseId: options.databaseId,
253
+ ...Object.keys(where).length > 0 ? { where } : {},
254
+ ...options.limit !== void 0 ? { limit: options.limit } : {},
255
+ ...options.offset !== void 0 ? { offset: options.offset } : {}
256
+ });
257
+ const rows = Array.isArray(result.rows) ? result.rows : [];
258
+ if (options.format === "json") {
259
+ return JSON.stringify(
260
+ options.detailed ? result : {
261
+ databaseId: result.databaseId,
262
+ count: result.count,
263
+ totalCount: result.totalCount,
264
+ rows
265
+ }
266
+ );
267
+ }
268
+ if (options.format === "jsonl") return rows.map((row) => JSON.stringify(row)).join("\n");
269
+ if (rows.length === 0) return "no rows";
270
+ if (options.format === "md") {
271
+ return toMarkdownTable(rows.map((row) => flattenRowForTsv(row)));
272
+ }
273
+ return toTsv(rows).trimEnd();
274
+ }
275
+ function toMarkdownTable(rows) {
276
+ const columns = [];
277
+ for (const row of rows) {
278
+ for (const key of Object.keys(row)) {
279
+ if (!columns.includes(key)) columns.push(key);
280
+ }
281
+ }
282
+ const cell = (value) => {
283
+ if (value === null || value === void 0) return "";
284
+ const text2 = typeof value === "object" ? JSON.stringify(value) : String(value);
285
+ return text2.replace(/[|\n\r\t]+/g, " ");
286
+ };
287
+ return [
288
+ `| ${columns.join(" | ")} |`,
289
+ `| ${columns.map(() => "---").join(" | ")} |`,
290
+ ...rows.map((row) => `| ${columns.map((column) => cell(row[column])).join(" | ")} |`)
291
+ ].join("\n");
292
+ }
293
+ async function runDbGet(services, options) {
294
+ const node = await services.store.get(options.nodeId);
295
+ if (!node) throw new Error(`Node not found: ${options.nodeId}`);
296
+ if (options.detailed) return JSON.stringify(node);
297
+ return JSON.stringify({
298
+ id: node.id,
299
+ schemaId: node.schemaId,
300
+ properties: node.properties,
301
+ revision: `updatedAt:${node.updatedAt}`
302
+ });
303
+ }
304
+ async function runDbSet(services, options) {
305
+ const properties = parseAssignments(options.assignments);
306
+ if (Object.keys(properties).length === 0) {
307
+ throw new Error("db set requires at least one field=value assignment");
308
+ }
309
+ const plan = await services.aiSurface.callTool("xnet_plan_database_mutation", {
310
+ databaseId: options.databaseId,
311
+ actor: options.actor ?? "xnet-cli",
312
+ intent: `Update row ${options.rowId} via xnet db set`,
313
+ operations: [{ op: "updateRow", args: { rowId: options.rowId, properties } }]
314
+ });
315
+ if (!plan.validation.valid) {
316
+ throw new Error(`plan invalid: ${plan.validation.errors.join("; ")}`);
317
+ }
318
+ if (options.planOnly) return JSON.stringify(plan);
319
+ const result = await services.aiSurface.callTool("xnet_apply_database_mutation", {
320
+ plan,
321
+ confirmApply: true
322
+ });
323
+ if (!result.applied) {
324
+ throw new Error(`apply failed: ${result.validation?.errors?.join("; ") ?? "unknown error"}`);
325
+ }
326
+ return `applied ${options.rowId} ${plan.id}`;
327
+ }
328
+ async function runScript(services, options) {
329
+ const code = await readFile(resolve(options.file), "utf8");
330
+ const nodes = await services.store.list({
331
+ ...options.schema ? { schemaId: options.schema } : {},
332
+ limit: options.limit ?? 200,
333
+ offset: 0
334
+ });
335
+ const flatNodes = nodes.filter((node) => !node.deleted).map(toFlatNode);
336
+ const currentNode = options.node ? flatNodes.find((node) => node.id === options.node) : void 0;
337
+ const session = createAgentScriptContext({
338
+ nodes: flatNodes,
339
+ ...currentNode ? { node: currentNode } : {}
340
+ });
341
+ const sandbox = new ScriptSandbox({ timeoutMs: options.timeoutMs ?? 5e3 });
342
+ const result = await sandbox.execute(code, session.context);
343
+ const plan = session.toMutationPlan({ actor: options.actor ?? "xnet-cli-script" });
344
+ const output = { result };
345
+ if (plan) {
346
+ output.plan = { id: plan.id, changes: plan.changes.length, valid: plan.validation.valid };
347
+ if (options.dir) {
348
+ const planPath = `.xnet/pending/${plan.id}.plan.json`;
349
+ const fullPath = join(resolve(options.dir), planPath);
350
+ await mkdir(dirname(fullPath), { recursive: true });
351
+ await (await import("fs/promises")).writeFile(fullPath, `${JSON.stringify(plan, null, 2)}
352
+ `, "utf8");
353
+ output.planPath = planPath;
354
+ } else {
355
+ output.planDetail = plan;
356
+ }
357
+ }
358
+ return JSON.stringify(output);
359
+ }
360
+ function toFlatNode(node) {
361
+ return {
362
+ id: node.id,
363
+ schemaIRI: node.schemaId,
364
+ ...node.properties,
365
+ updatedAt: node.updatedAt
366
+ };
367
+ }
368
+ function startDaemon(services, options) {
369
+ const rootDir = resolve(options.dir);
370
+ const report = options.onScan ?? ((summary) => console.log(summary));
371
+ const handleScan = async (scan) => {
372
+ if (scan.pendingPlans.length === 0 && scan.conflicts.length === 0) return;
373
+ if (options.apply && scan.pendingPlans.length > 0) {
374
+ const summary = await runCommit(services, {
375
+ dir: rootDir,
376
+ apply: true,
377
+ actor: options.actor
378
+ });
379
+ report(summary);
380
+ return;
381
+ }
382
+ report(
383
+ [
384
+ ...scan.pendingPlans.map((pending) => `planned ${pending.path} ${pending.plan.id}`),
385
+ ...scan.conflicts.map((conflict) => `conflict ${conflict.path} ${conflict.kind}`)
386
+ ].join("\n")
387
+ );
388
+ };
389
+ const handle = services.watcher.watchWorkspace(
390
+ {
391
+ rootDir,
392
+ actor: options.actor ?? "xnet-daemon",
393
+ usePolling: options.poll,
394
+ ...options.pollIntervalMs !== void 0 ? { pollIntervalMs: options.pollIntervalMs } : {}
395
+ },
396
+ (scan) => void handleScan(scan)
397
+ );
398
+ return handle;
399
+ }
400
+ function parseAssignments(pairs) {
401
+ const record = {};
402
+ for (const pair of pairs) {
403
+ const separator = pair.indexOf("=");
404
+ if (separator <= 0) {
405
+ throw new Error(`Invalid assignment "${pair}"; expected field=value`);
406
+ }
407
+ const field = pair.slice(0, separator).trim();
408
+ const raw = pair.slice(separator + 1);
409
+ try {
410
+ record[field] = JSON.parse(raw);
411
+ } catch {
412
+ record[field] = raw;
413
+ }
414
+ }
415
+ return record;
416
+ }
417
+ function registerAgentCommands(program2, createServices = defaultServicesFactory) {
418
+ const services = (options) => createServices(options);
419
+ const print = (text2) => {
420
+ console.log(text2);
421
+ };
422
+ program2.command("checkout").description("Materialize a scoped slice of the workspace into a vault folder").option("-q, --query <text>", "Search query scope").option("-s, --schema <iri...>", "Schema IRI scope").option("-n, --node <id...>", "Node id scope").option("-k, --kind <kind...>", "Kind folder scope: page|database|canvas").option("-l, --limit <n>", "Max nodes to materialize", parseIntOption).option("-d, --dir <path>", "Checkout directory", ".").option("--name <name>", "Workspace display name").option("--api-url <url>", "xNet local API URL").action(async (options) => {
423
+ print(await runCheckout(await services(options), options));
424
+ });
425
+ program2.command("status").description("List pending plans and conflicts for a checkout").option("-d, --dir <path>", "Checkout directory", ".").option("--format <format>", "Output format: tsv|json", "tsv").option("--api-url <url>", "xNet local API URL").action(async (options) => {
426
+ print(await runStatus(await services(options), options));
427
+ });
428
+ program2.command("commit").description("Lift file edits into mutation plans; --apply applies them").option("-d, --dir <path>", "Checkout directory", ".").option("--apply", "Apply valid plans through the plan pipeline").option("--actor <actor>", "Actor recorded on plans", "xnet-cli").option("--api-url <url>", "xNet local API URL").action(async (options) => {
429
+ print(await runCommit(await services(options), options));
430
+ });
431
+ program2.command("search <text>").description("Ranked workspace search (TSV: id, schema, title, snippet)").option("-s, --schema <iri>", "Schema IRI filter").option("-l, --limit <n>", "Max results", parseIntOption).option("--format <format>", "Output format: tsv|jsonl|json", "tsv").option("--api-url <url>", "xNet local API URL").action(async (text2, options) => {
432
+ print(await runSearch(await services(options), { ...options, text: text2 }));
433
+ });
434
+ program2.command("query <databaseId>").description("Query database rows (TSV by default)").option("-w, --where <expr...>", "Filters as field=value").option("-l, --limit <n>", "Max rows", parseIntOption).option("-o, --offset <n>", "Row offset", parseIntOption).option("--format <format>", "Output format: tsv|jsonl|json", "tsv").option("--detailed", "Include descriptor and query plan in json output").option("--api-url <url>", "xNet local API URL").action(async (databaseId, options) => {
435
+ print(await runQuery(await services(options), { ...options, databaseId }));
436
+ });
437
+ const db = program2.command("db").description("Direct node and row access");
438
+ db.command("get <nodeId>").description("Read a node as compact JSON").option("--detailed", "Include full node record").option("--api-url <url>", "xNet local API URL").action(async (nodeId, options) => {
439
+ print(await runDbGet(await services(options), { ...options, nodeId }));
440
+ });
441
+ db.command("set <databaseId> <rowId> <assignments...>").description("Update row properties through the plan/apply pipeline").option("--plan-only", "Print the mutation plan without applying").option("--actor <actor>", "Actor recorded on the plan", "xnet-cli").option("--api-url <url>", "xNet local API URL").action(async (databaseId, rowId, assignments, options) => {
442
+ print(
443
+ await runDbSet(await services(options), {
444
+ databaseId,
445
+ rowId,
446
+ assignments,
447
+ actor: options.actor,
448
+ planOnly: options.planOnly
449
+ })
450
+ );
451
+ });
452
+ program2.command("run <file>").description("Run a sandboxed agent script with the @xnet/agent-api surface").option("-s, --schema <iri>", "Preload nodes of this schema").option("-l, --limit <n>", "Max nodes to preload", parseIntOption).option("-n, --node <id>", "Current node id").option("-d, --dir <path>", "Checkout directory for proposal plans").option("--actor <actor>", "Actor recorded on proposal plans", "xnet-cli-script").option("--api-url <url>", "xNet local API URL").action(async (file, options) => {
453
+ print(await runScript(await services(options), { ...options, file }));
454
+ });
455
+ program2.command("daemon").description("Watch a checkout and lift saves into mutation plans").option("-d, --dir <path>", "Checkout directory", ".").option("--poll", "Use interval polling instead of fs.watch").option("--apply", "Auto-apply valid plans (watcher autocommit)").option("--actor <actor>", "Actor recorded on plans", "xnet-daemon").option("--api-url <url>", "xNet local API URL").action(async (options) => {
456
+ const handle = startDaemon(await services(options), options);
457
+ console.log(`watching ${resolve(options.dir)} (ctrl-c to stop)`);
458
+ process.on("SIGINT", () => {
459
+ handle.close();
460
+ process.exit(0);
461
+ });
462
+ });
463
+ program2.command("skill").description("Print the cross-harness xNet SKILL.md").action(() => {
464
+ print(XNET_AGENT_SKILL_MD);
465
+ });
466
+ }
467
+ function parseIntOption(value) {
468
+ const parsed = Number.parseInt(value, 10);
469
+ if (!Number.isFinite(parsed)) throw new Error(`Invalid number: ${value}`);
470
+ return parsed;
471
+ }
472
+
473
+ // src/commands/bridge.ts
474
+ import { writeFileSync } from "fs";
475
+ import { tmpdir } from "os";
476
+ import { join as join2 } from "path";
477
+ import {
478
+ buildAgentArgs,
479
+ cliAgentRunner,
480
+ cliChatAgent,
481
+ createBridgeServer,
482
+ DEFAULT_BRIDGE_PORT,
483
+ defaultXnetGate,
484
+ Git,
485
+ handleBridgeRun,
486
+ mcpConfigFor,
487
+ NodeCommandRunner
488
+ } from "@xnetjs/devkit";
489
+ function buildBridgeServer(options, runner = new NodeCommandRunner()) {
490
+ const command = options.agent ?? "claude";
491
+ const cwd = options.cwd ?? process.cwd();
492
+ const args = buildAgentArgs(command, {
493
+ ...options.mcpConfigPath ? { mcpConfigPath: options.mcpConfigPath } : {}
494
+ });
495
+ const agent = cliChatAgent(runner, { command, cwd, args });
496
+ const run = options.code ? (request) => handleBridgeRun(
497
+ {
498
+ git: new Git(runner, cwd),
499
+ runner,
500
+ agent: cliAgentRunner(runner, { command }),
501
+ gate: defaultXnetGate(),
502
+ worktreeRoot: join2(cwd, ".xnet", "agent-worktrees")
503
+ },
504
+ request
505
+ ) : void 0;
506
+ return createBridgeServer({
507
+ agent,
508
+ agentName: command,
509
+ ...run ? { run } : {},
510
+ ...options.host ? { host: options.host } : {},
511
+ ...options.port !== void 0 ? { port: options.port } : {},
512
+ ...options.allowOrigin ? { allowedOrigins: options.allowOrigin } : {}
513
+ });
514
+ }
515
+ function registerBridgeCommand(program2) {
516
+ const bridge = program2.command("bridge").description("Run the local agent bridge for XNet's AI chat panel");
517
+ bridge.command("serve").description("Serve the agent bridge on loopback (default :31416), driving your own agent CLI").option("--agent <command>", "Agent CLI to drive (claude, codex, \u2026)", "claude").option("--host <host>", "Loopback host (default 127.0.0.1)").option("--port <n>", `Port (default ${DEFAULT_BRIDGE_PORT})`, parseIntOption2).option(
518
+ "--allow-origin <origin...>",
519
+ "Browser origins permitted (e.g. https://user.github.io for the web deployment)"
520
+ ).option("--cwd <dir>", "Working directory the agent runs in (default current dir)").option("--code", "Enable POST /run agentic code tasks (worktree \u2192 gate \u2192 checkpoint/PR)").option("--mcp", "Give the agent XNet's workspace tools via `xnet mcp serve`").option(
521
+ "--mcp-api-url <url>",
522
+ "xNet local API URL the MCP server talks to (default http://127.0.0.1:31415)"
523
+ ).action(async (options) => {
524
+ const resolved = { ...options };
525
+ if (options.mcp) {
526
+ const spec = {
527
+ command: process.execPath,
528
+ args: [
529
+ process.argv[1],
530
+ "mcp",
531
+ "serve",
532
+ "--api-url",
533
+ options.mcpApiUrl ?? "http://127.0.0.1:31415"
534
+ ]
535
+ };
536
+ const mcpConfigPath = join2(tmpdir(), `xnet-bridge-mcp-${process.pid}.json`);
537
+ writeFileSync(mcpConfigPath, JSON.stringify(mcpConfigFor(spec)));
538
+ resolved.mcpConfigPath = mcpConfigPath;
539
+ }
540
+ const handle = buildBridgeServer(resolved);
541
+ await handle.start();
542
+ console.error(
543
+ `xNet agent bridge listening on ${handle.url} (agent: ${options.agent ?? "claude"}${options.mcp ? ", workspace tools enabled" : ""})`
544
+ );
545
+ console.error('In XNet, open the AI panel and select "Local bridge".');
546
+ const shutdown = () => {
547
+ void handle.stop().then(() => process.exit(0));
548
+ };
549
+ process.on("SIGINT", shutdown);
550
+ process.on("SIGTERM", shutdown);
551
+ });
552
+ }
553
+ function parseIntOption2(value) {
554
+ const parsed = Number.parseInt(value, 10);
555
+ if (!Number.isFinite(parsed)) throw new Error(`Invalid number: ${value}`);
556
+ return parsed;
557
+ }
558
+
559
+ // src/commands/code.ts
560
+ import { join as join3, resolve as resolve2 } from "path";
561
+ import {
562
+ cliAgentRunner as cliAgentRunner2,
563
+ defaultXnetGate as defaultXnetGate2,
564
+ Git as Git2,
565
+ NodeCommandRunner as NodeCommandRunner2,
566
+ openPullRequest,
567
+ runAgentTask
568
+ } from "@xnetjs/devkit";
569
+ function slug(text2) {
570
+ return text2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "task";
571
+ }
572
+ function resolveCodeRunConfig(prompt, options, repoRoot, now = Date.now) {
573
+ const id = options.id ?? `code-${slug(prompt)}-${now().toString(36)}`;
574
+ const branch = options.branch ?? `agent/${id}`;
575
+ const base = options.base ?? "origin/main";
576
+ const worktreePath = options.worktree ? resolve2(options.worktree) : join3(repoRoot, ".xnet", "agent-worktrees", id);
577
+ return {
578
+ id,
579
+ branch,
580
+ base,
581
+ worktreePath,
582
+ gate: defaultXnetGate2({ changedSince: base }),
583
+ keepWorktree: Boolean(options.pr || options.keep)
584
+ };
585
+ }
586
+ function summarizeAgentTaskResult(result) {
587
+ if (!result.ok) {
588
+ if (result.rolledBack && result.gate.failedStep) {
589
+ return `\u2717 gate failed at "${result.gate.failedStep}" \u2014 edits rolled back on ${result.branch}.`;
590
+ }
591
+ return `\u2717 agent run failed on ${result.branch}.`;
592
+ }
593
+ if (result.noChanges) return `\u2022 agent made no changes on ${result.branch}.`;
594
+ return `\u2713 ${result.branch} passed the gate and was checkpointed.`;
595
+ }
596
+ function registerCodeCommand(program2) {
597
+ program2.command("code").argument("<intent>", "What the coding agent should do").description("Run a coding agent on this repo in an isolated worktree (gate \u2192 checkpoint/PR)").option("--agent <command>", "Coding agent CLI (claude, codex, aider, \u2026)", "claude").option("--id <id>", "Task id (also the default branch + worktree name)").option("--branch <name>", "Branch for the worktree (default agent/<id>)").option("--base <ref>", "Base ref for the gate + PR (default origin/main)").option("--worktree <dir>", "Worktree directory (default .xnet/agent-worktrees/<id>)").option("--pr", "Open a pull request when the gate passes").option("--keep", "Keep the worktree after a successful run").option("--repo <dir>", "Repo root (default current dir)").action(async (intent, options) => {
598
+ const runner = new NodeCommandRunner2();
599
+ const repoRoot = resolve2(options.repo ?? process.cwd());
600
+ const config = resolveCodeRunConfig(intent, options, repoRoot);
601
+ const result = await runAgentTask({
602
+ git: new Git2(runner, repoRoot),
603
+ runner,
604
+ agent: cliAgentRunner2(runner, { command: options.agent ?? "claude" }),
605
+ task: { id: config.id, prompt: intent },
606
+ worktreePath: config.worktreePath,
607
+ branch: config.branch,
608
+ gate: config.gate,
609
+ keepWorktree: config.keepWorktree
610
+ });
611
+ console.error(summarizeAgentTaskResult(result));
612
+ if (result.ok && !result.noChanges && options.pr) {
613
+ try {
614
+ const pr = await openPullRequest(runner, config.worktreePath, config.branch, {
615
+ base: config.base,
616
+ title: intent
617
+ });
618
+ console.error(`\u2192 PR: ${pr.url}`);
619
+ } catch (err) {
620
+ console.error(`\u2192 PR failed: ${err instanceof Error ? err.message : String(err)}`);
621
+ }
622
+ }
623
+ process.exitCode = result.ok ? 0 : 1;
624
+ });
625
+ }
626
+
627
+ // src/commands/connector.ts
628
+ import { resolve as resolve4 } from "path";
629
+ import { scaffoldPlugin as scaffoldPlugin2 } from "@xnetjs/plugins";
630
+
631
+ // src/commands/plugin.ts
632
+ import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
633
+ import { resolve as resolve3, dirname as dirname2, join as join4 } from "path";
634
+ import { scaffoldPlugin } from "@xnetjs/plugins";
635
+ var TEMPLATES = ["client", "two-sided", "ai-script"];
636
+ var nodeIO = {
637
+ mkdir: (path) => mkdirSync(path, { recursive: true }),
638
+ writeFile: (path, content) => writeFileSync2(path, content, "utf-8")
639
+ };
640
+ function writeScaffoldFiles(files, targetDir, io = nodeIO) {
641
+ const written = [];
642
+ for (const [rel, content] of Object.entries(files)) {
643
+ const full = join4(targetDir, rel);
644
+ io.mkdir(dirname2(full));
645
+ io.writeFile(full, content);
646
+ written.push(rel);
647
+ }
648
+ return written;
649
+ }
650
+ function resolveTemplate(value) {
651
+ const template = value ?? "client";
652
+ if (!TEMPLATES.includes(template)) {
653
+ throw new Error(`Unknown template "${value}". Use one of: ${TEMPLATES.join(", ")}`);
654
+ }
655
+ return template;
656
+ }
657
+ function scaffoldCommand(id, options) {
658
+ const template = resolveTemplate(options.template);
659
+ const { files } = scaffoldPlugin({
660
+ id,
661
+ name: options.name ?? id,
662
+ template,
663
+ author: options.author,
664
+ description: options.description
665
+ });
666
+ const targetDir = resolve3(process.cwd(), options.out ?? id);
667
+ const written = writeScaffoldFiles(files, targetDir);
668
+ console.log(`Scaffolded ${template} plugin "${id}" at ${targetDir}`);
669
+ for (const rel of written) console.log(` + ${rel}`);
670
+ console.log("\nNext: cd into the project, `npm install`, then `npm test`.");
671
+ }
672
+ function registerPluginCommand(program2) {
673
+ const plugin = program2.command("plugin").description("Author and manage xNet plugins");
674
+ plugin.command("scaffold <id>").description("Scaffold a new plugin project (id is reverse-domain, e.g. com.acme.kanban)").option("--name <name>", "Human-readable plugin name").option("--template <template>", `Template: ${TEMPLATES.join(" | ")}`, "client").option("--author <author>", "Author name").option("--description <text>", "Short description").option("--out <dir>", "Output directory (default: the plugin id)").action((id, opts) => {
675
+ try {
676
+ scaffoldCommand(id, opts);
677
+ } catch (error) {
678
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
679
+ process.exit(1);
680
+ }
681
+ });
682
+ }
683
+
684
+ // src/commands/connector.ts
685
+ function scaffoldConnector(id, options) {
686
+ const { files } = scaffoldPlugin2({
687
+ id,
688
+ name: options.name ?? id,
689
+ template: "connector",
690
+ author: options.author,
691
+ description: options.description
692
+ });
693
+ const targetDir = resolve4(process.cwd(), options.out ?? id);
694
+ const written = writeScaffoldFiles(files, targetDir);
695
+ console.log(`Scaffolded connector "${id}" at ${targetDir}`);
696
+ for (const rel of written) console.log(` + ${rel}`);
697
+ console.log(
698
+ "\nNext: cd in, `npm install`, `npm test`. Then fill in the sync `pull` and register the hub half with `connectorSyncFeature` (see @xnetjs/hub)."
699
+ );
700
+ }
701
+ function registerConnectorCommand(program2) {
702
+ const connector = program2.command("connector").description("Author agent-native Connectors (sync an external service into governed nodes)");
703
+ connector.command("scaffold <id>").description(
704
+ "Scaffold a Connector project (id is reverse-domain, e.g. dev.acme.connector.slack)"
705
+ ).option("--name <name>", "Human-readable connector name").option("--author <author>", "Author name").option("--description <text>", "Short description").option("--out <dir>", "Output directory (default: the connector id)").action((id, opts) => {
706
+ try {
707
+ scaffoldConnector(id, opts);
708
+ } catch (error) {
709
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
710
+ process.exit(1);
711
+ }
712
+ });
713
+ }
714
+
715
+ // src/commands/data.ts
716
+ import { generateSigningKeyPair, getSigningPublicKeyFromPrivate } from "@xnetjs/crypto";
717
+ import { defineSchema, SQLiteNodeStorageAdapter, text } from "@xnetjs/data";
718
+ import { createDID } from "@xnetjs/identity";
719
+ import { createXNetClient } from "@xnetjs/runtime";
720
+ import chalk from "chalk";
721
+ var NoteSchema = defineSchema({
722
+ name: "Note",
723
+ namespace: "xnet://cli/",
724
+ properties: {
725
+ title: text({ required: true }),
726
+ body: text({})
727
+ }
728
+ });
729
+ function hexToBytes(hex) {
730
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
731
+ return new Uint8Array(Buffer.from(clean, "hex"));
732
+ }
733
+ function resolveSigningKey(key) {
734
+ const provided = key ?? process.env.XNET_SIGNING_KEY;
735
+ if (provided) return { signingKey: hexToBytes(provided), ephemeral: false };
736
+ return { signingKey: generateSigningKeyPair().privateKey, ephemeral: true };
737
+ }
738
+ async function resolveStorage(db) {
739
+ if (db) {
740
+ const { createElectronSQLiteAdapter } = await import("@xnetjs/sqlite/electron");
741
+ const adapter2 = await createElectronSQLiteAdapter({
742
+ path: db,
743
+ busyTimeout: 5e3,
744
+ foreignKeys: true,
745
+ walMode: true
746
+ });
747
+ return new SQLiteNodeStorageAdapter(adapter2);
748
+ }
749
+ const { createMemorySQLiteAdapter } = await import("@xnetjs/sqlite/memory");
750
+ const adapter = await createMemorySQLiteAdapter();
751
+ return new SQLiteNodeStorageAdapter(adapter);
752
+ }
753
+ async function buildDataClient(options = {}) {
754
+ const { signingKey } = resolveSigningKey(options.key);
755
+ const authorDID = createDID(getSigningPublicKeyFromPrivate(signingKey));
756
+ const nodeStorage = await resolveStorage(options.db);
757
+ return createXNetClient({ nodeStorage, authorDID, signingKey });
758
+ }
759
+ async function runCreateNote(client, input) {
760
+ return client.mutate.create(NoteSchema, { title: input.title, body: input.body ?? "" });
761
+ }
762
+ async function runListNotes(client) {
763
+ return client.fetch(NoteSchema);
764
+ }
765
+ function registerDataCommand(program2) {
766
+ const data = program2.command("data").description("Read and write live nodes (runtime client)");
767
+ data.command("add").description("Create a Note node").requiredOption("--title <title>", "Note title").option("--body <body>", "Note body", "").option("--db <path>", "SQLite file path (default: in-memory)").option("--key <hex>", "Ed25519 signing key (hex); falls back to $XNET_SIGNING_KEY").action(async (opts) => {
768
+ const client = await buildDataClient({ db: opts.db, key: opts.key });
769
+ try {
770
+ const node = await runCreateNote(client, { title: opts.title, body: opts.body });
771
+ console.log(chalk.green(`\u2713 created ${node.id}`));
772
+ console.log(chalk.dim(` author: ${client.authorDID}`));
773
+ } finally {
774
+ await client.destroy();
775
+ }
776
+ });
777
+ data.command("list").description("List Note nodes").option("--db <path>", "SQLite file path (default: in-memory)").option("--key <hex>", "Ed25519 signing key (hex); falls back to $XNET_SIGNING_KEY").action(async (opts) => {
778
+ const client = await buildDataClient({ db: opts.db, key: opts.key });
779
+ try {
780
+ const notes = await runListNotes(client);
781
+ if (notes.length === 0) {
782
+ console.log(chalk.dim("(no notes)"));
783
+ return;
784
+ }
785
+ for (const note of notes) {
786
+ const title = String(note.properties.title ?? "");
787
+ console.log(`${chalk.cyan(note.id)} ${title}`);
788
+ }
789
+ } finally {
790
+ await client.destroy();
791
+ }
792
+ });
793
+ }
794
+
11
795
  // src/commands/doctor.ts
12
- import { writeFileSync, readFileSync, existsSync, readdirSync, statSync } from "fs";
13
- import { resolve, join } from "path";
796
+ import { writeFileSync as writeFileSync3, readFileSync, existsSync, readdirSync, statSync } from "fs";
797
+ import { resolve as resolve5, join as join5 } from "path";
14
798
  import {
15
799
  verifyIntegrity,
16
800
  quickIntegrityCheck,
@@ -22,8 +806,8 @@ import {
22
806
  } from "@xnetjs/sync";
23
807
  async function getChalk() {
24
808
  try {
25
- const chalk = await import("chalk");
26
- return chalk.default;
809
+ const chalk2 = await import("chalk");
810
+ return chalk2.default;
27
811
  } catch {
28
812
  const identity = (s) => s;
29
813
  return {
@@ -40,15 +824,15 @@ async function getChalk() {
40
824
  }
41
825
  function findDataDir(providedDir) {
42
826
  if (providedDir) {
43
- const resolved = resolve(process.cwd(), providedDir);
827
+ const resolved = resolve5(process.cwd(), providedDir);
44
828
  if (existsSync(resolved)) {
45
829
  return resolved;
46
830
  }
47
831
  throw new Error(`Data directory not found: ${resolved}`);
48
832
  }
49
- const commonPaths = [".xnet/data", "data", ".data", resolve(process.env.HOME ?? "", ".xnet/data")];
833
+ const commonPaths = [".xnet/data", "data", ".data", resolve5(process.env.HOME ?? "", ".xnet/data")];
50
834
  for (const path of commonPaths) {
51
- const resolved = resolve(process.cwd(), path);
835
+ const resolved = resolve5(process.cwd(), path);
52
836
  if (existsSync(resolved)) {
53
837
  return resolved;
54
838
  }
@@ -59,7 +843,7 @@ function loadChangesFromDir(dataDir) {
59
843
  const changes = [];
60
844
  const patterns = ["changes.json", "changes/*.json", "*.changes.json"];
61
845
  for (const pattern of patterns) {
62
- const changesFile = join(dataDir, pattern.split("/")[0]);
846
+ const changesFile = join5(dataDir, pattern.split("/")[0]);
63
847
  if (existsSync(changesFile) && statSync(changesFile).isFile()) {
64
848
  try {
65
849
  const content = readFileSync(changesFile, "utf-8");
@@ -73,12 +857,12 @@ function loadChangesFromDir(dataDir) {
73
857
  }
74
858
  }
75
859
  }
76
- const changesDir = join(dataDir, "changes");
860
+ const changesDir = join5(dataDir, "changes");
77
861
  if (existsSync(changesDir) && statSync(changesDir).isDirectory()) {
78
862
  const files = readdirSync(changesDir).filter((f) => f.endsWith(".json"));
79
863
  for (const file of files) {
80
864
  try {
81
- const content = readFileSync(join(changesDir, file), "utf-8");
865
+ const content = readFileSync(join5(changesDir, file), "utf-8");
82
866
  const change = JSON.parse(content);
83
867
  changes.push(change);
84
868
  } catch {
@@ -92,114 +876,114 @@ function formatDuration(ms) {
92
876
  return `${(ms / 1e3).toFixed(2)}s`;
93
877
  }
94
878
  async function doctorCommand(options) {
95
- const chalk = await getChalk();
96
- console.log(chalk.bold("\nxNet Health Check\n"));
97
- console.log(chalk.dim("\u2500".repeat(50)));
879
+ const chalk2 = await getChalk();
880
+ console.log(chalk2.bold("\nxNet Health Check\n"));
881
+ console.log(chalk2.dim("\u2500".repeat(50)));
98
882
  try {
99
883
  let dataDir;
100
884
  let changes = [];
101
885
  try {
102
886
  dataDir = findDataDir(options.dataDir);
103
- console.log(chalk.gray(`Data directory: ${dataDir}`));
887
+ console.log(chalk2.gray(`Data directory: ${dataDir}`));
104
888
  changes = loadChangesFromDir(dataDir);
105
- console.log(chalk.gray(`Found ${changes.length} changes`));
889
+ console.log(chalk2.gray(`Found ${changes.length} changes`));
106
890
  } catch {
107
- console.log(chalk.yellow("\nNote: No data directory found."));
108
- console.log(chalk.gray("Use --data-dir to specify a data directory."));
109
- console.log(chalk.gray("Running with demo data...\n"));
891
+ console.log(chalk2.yellow("\nNote: No data directory found."));
892
+ console.log(chalk2.gray("Use --data-dir to specify a data directory."));
893
+ console.log(chalk2.gray("Running with demo data...\n"));
110
894
  changes = generateDemoChanges();
111
895
  }
112
896
  console.log();
113
- console.log(chalk.bold("Checking data integrity..."));
897
+ console.log(chalk2.bold("Checking data integrity..."));
114
898
  const report = options.quick ? await quickIntegrityCheck(changes) : await verifyIntegrity(changes);
115
899
  if (options.json) {
116
900
  console.log(JSON.stringify(report, null, 2));
117
901
  return;
118
902
  }
119
- printIntegrityResults(report, chalk, options.verbose);
903
+ printIntegrityResults(report, chalk2, options.verbose);
120
904
  console.log();
121
- console.log(chalk.bold("Analyzing change chains..."));
905
+ console.log(chalk2.bold("Analyzing change chains..."));
122
906
  const orphans = findOrphans(changes);
123
907
  const roots = findRoots(changes);
124
908
  const heads = findHeads(changes);
125
909
  const depth = getChainDepth(changes);
126
- console.log(` ${chalk.green("\u2713")} Roots: ${roots.length}`);
127
- console.log(` ${chalk.green("\u2713")} Heads: ${heads.length}`);
128
- console.log(` ${chalk.green("\u2713")} Depth: ${depth}`);
910
+ console.log(` ${chalk2.green("\u2713")} Roots: ${roots.length}`);
911
+ console.log(` ${chalk2.green("\u2713")} Heads: ${heads.length}`);
912
+ console.log(` ${chalk2.green("\u2713")} Depth: ${depth}`);
129
913
  if (orphans.length > 0) {
130
- console.log(` ${chalk.yellow("\u26A0")} Orphans: ${orphans.length}`);
914
+ console.log(` ${chalk2.yellow("\u26A0")} Orphans: ${orphans.length}`);
131
915
  } else {
132
- console.log(` ${chalk.green("\u2713")} Orphans: 0`);
916
+ console.log(` ${chalk2.green("\u2713")} Orphans: 0`);
133
917
  }
134
918
  console.log();
135
- console.log(chalk.bold("Checking schema compatibility..."));
136
- console.log(chalk.gray(" (Schema analysis requires @xnetjs/data integration)"));
919
+ console.log(chalk2.bold("Checking schema compatibility..."));
920
+ console.log(chalk2.gray(" (Schema analysis requires @xnetjs/data integration)"));
137
921
  console.log();
138
- console.log(chalk.bold("Checking sync state..."));
139
- console.log(chalk.gray(" (Sync analysis requires active SyncProvider)"));
922
+ console.log(chalk2.bold("Checking sync state..."));
923
+ console.log(chalk2.gray(" (Sync analysis requires active SyncProvider)"));
140
924
  console.log();
141
- console.log(chalk.dim("\u2500".repeat(50)));
925
+ console.log(chalk2.dim("\u2500".repeat(50)));
142
926
  const hasErrors = report.summary.errors > 0;
143
927
  const hasWarnings = report.summary.warnings > 0 || orphans.length > 0;
144
928
  if (hasErrors) {
145
- console.log(chalk.red(chalk.bold("Status: UNHEALTHY")));
146
- console.log(chalk.red(" Data integrity issues detected. Run `xnet repair` to fix."));
929
+ console.log(chalk2.red(chalk2.bold("Status: UNHEALTHY")));
930
+ console.log(chalk2.red(" Data integrity issues detected. Run `xnet repair` to fix."));
147
931
  } else if (hasWarnings) {
148
- console.log(chalk.yellow(chalk.bold("Status: HEALTHY with warnings")));
149
- console.log(chalk.gray(" Some issues detected but data is intact."));
932
+ console.log(chalk2.yellow(chalk2.bold("Status: HEALTHY with warnings")));
933
+ console.log(chalk2.gray(" Some issues detected but data is intact."));
150
934
  } else {
151
- console.log(chalk.green(chalk.bold("Status: HEALTHY")));
152
- console.log(chalk.green(" All checks passed."));
935
+ console.log(chalk2.green(chalk2.bold("Status: HEALTHY")));
936
+ console.log(chalk2.green(" All checks passed."));
153
937
  }
154
938
  console.log();
155
939
  } catch (error) {
156
- console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
940
+ console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
157
941
  process.exit(1);
158
942
  }
159
943
  }
160
- function printIntegrityResults(report, chalk, verbose) {
944
+ function printIntegrityResults(report, chalk2, verbose) {
161
945
  const { checked, valid, issues, summary, durationMs } = report;
162
946
  const percentage = checked > 0 ? Math.round(valid / checked * 100) : 100;
163
947
  if (issues.length === 0) {
164
- console.log(` ${chalk.green("\u2713")} ${checked} changes verified (${percentage}% valid)`);
165
- console.log(` ${chalk.green("\u2713")} Hash chains intact`);
166
- console.log(` ${chalk.green("\u2713")} No issues detected`);
167
- console.log(chalk.gray(` Completed in ${formatDuration(durationMs)}`));
948
+ console.log(` ${chalk2.green("\u2713")} ${checked} changes verified (${percentage}% valid)`);
949
+ console.log(` ${chalk2.green("\u2713")} Hash chains intact`);
950
+ console.log(` ${chalk2.green("\u2713")} No issues detected`);
951
+ console.log(chalk2.gray(` Completed in ${formatDuration(durationMs)}`));
168
952
  } else {
169
953
  console.log(
170
- ` ${chalk.yellow("!")} ${checked} changes checked (${valid} valid, ${checked - valid} issues)`
954
+ ` ${chalk2.yellow("!")} ${checked} changes checked (${valid} valid, ${checked - valid} issues)`
171
955
  );
172
956
  if (summary.errors > 0) {
173
- console.log(` ${chalk.red("\u2717")} ${summary.errors} errors`);
957
+ console.log(` ${chalk2.red("\u2717")} ${summary.errors} errors`);
174
958
  }
175
959
  if (summary.warnings > 0) {
176
- console.log(` ${chalk.yellow("\u26A0")} ${summary.warnings} warnings`);
960
+ console.log(` ${chalk2.yellow("\u26A0")} ${summary.warnings} warnings`);
177
961
  }
178
- console.log(chalk.gray(` Completed in ${formatDuration(durationMs)}`));
962
+ console.log(chalk2.gray(` Completed in ${formatDuration(durationMs)}`));
179
963
  if (verbose) {
180
964
  console.log();
181
- console.log(chalk.bold("Issues by type:"));
965
+ console.log(chalk2.bold("Issues by type:"));
182
966
  for (const [type, count] of Object.entries(summary.byType)) {
183
967
  if (count > 0) {
184
968
  console.log(` - ${type}: ${count}`);
185
969
  }
186
970
  }
187
971
  console.log();
188
- console.log(chalk.bold("Details:"));
972
+ console.log(chalk2.bold("Details:"));
189
973
  for (const issue of issues.slice(0, 10)) {
190
- const icon = issue.severity === "error" ? chalk.red("\u2717") : chalk.yellow("\u26A0");
974
+ const icon = issue.severity === "error" ? chalk2.red("\u2717") : chalk2.yellow("\u26A0");
191
975
  console.log(` ${icon} [${issue.type}] ${issue.details}`);
192
976
  if (issue.repairAction) {
193
- console.log(chalk.gray(` \u2192 ${issue.repairAction.description}`));
977
+ console.log(chalk2.gray(` \u2192 ${issue.repairAction.description}`));
194
978
  }
195
979
  }
196
980
  if (issues.length > 10) {
197
- console.log(chalk.gray(` ... and ${issues.length - 10} more`));
981
+ console.log(chalk2.gray(` ... and ${issues.length - 10} more`));
198
982
  }
199
983
  }
200
984
  if (report.repairable) {
201
985
  console.log();
202
- console.log(chalk.cyan(` Run \`xnet repair\` to fix these issues.`));
986
+ console.log(chalk2.cyan(` Run \`xnet repair\` to fix these issues.`));
203
987
  }
204
988
  }
205
989
  }
@@ -219,7 +1003,7 @@ function generateDemoChanges() {
219
1003
  authorDID: demoAuthor,
220
1004
  signature: new Uint8Array([1, 2, 3, 4]),
221
1005
  wallTime: now - 1e4,
222
- lamport: { time: 1, author: demoAuthor }
1006
+ lamport: 1
223
1007
  },
224
1008
  {
225
1009
  id: "change-2",
@@ -231,13 +1015,13 @@ function generateDemoChanges() {
231
1015
  authorDID: demoAuthor,
232
1016
  signature: new Uint8Array([5, 6, 7, 8]),
233
1017
  wallTime: now - 5e3,
234
- lamport: { time: 2, author: demoAuthor }
1018
+ lamport: 2
235
1019
  }
236
1020
  ];
237
1021
  }
238
1022
  async function repairCommand(options) {
239
- const chalk = await getChalk();
240
- console.log(chalk.bold("\nxNet Data Repair\n"));
1023
+ const chalk2 = await getChalk();
1024
+ console.log(chalk2.bold("\nxNet Data Repair\n"));
241
1025
  try {
242
1026
  let dataDir;
243
1027
  let changes = [];
@@ -245,15 +1029,15 @@ async function repairCommand(options) {
245
1029
  dataDir = findDataDir(options.dataDir);
246
1030
  changes = loadChangesFromDir(dataDir);
247
1031
  } catch {
248
- console.log(chalk.yellow("Note: No data directory found."));
249
- console.log(chalk.gray("Using demo data for illustration.\n"));
1032
+ console.log(chalk2.yellow("Note: No data directory found."));
1033
+ console.log(chalk2.gray("Using demo data for illustration.\n"));
250
1034
  dataDir = "";
251
1035
  changes = generateDemoChanges();
252
1036
  }
253
- console.log(chalk.gray("Running integrity check..."));
1037
+ console.log(chalk2.gray("Running integrity check..."));
254
1038
  const report = await verifyIntegrity(changes);
255
1039
  if (report.issues.length === 0) {
256
- console.log(chalk.green("\n\u2713 No issues found. Nothing to repair."));
1040
+ console.log(chalk2.green("\n\u2713 No issues found. Nothing to repair."));
257
1041
  return;
258
1042
  }
259
1043
  console.log(`
@@ -262,7 +1046,7 @@ Found ${report.issues.length} issues:`);
262
1046
  console.log(` - ${report.summary.warnings} warnings`);
263
1047
  console.log();
264
1048
  if (options.dryRun) {
265
- console.log(chalk.yellow("DRY RUN - No changes will be made\n"));
1049
+ console.log(chalk2.yellow("DRY RUN - No changes will be made\n"));
266
1050
  }
267
1051
  const { remainingIssues, repairCount } = await attemptRepair(changes, report.issues);
268
1052
  if (options.json) {
@@ -280,33 +1064,33 @@ Found ${report.issues.length} issues:`);
280
1064
  );
281
1065
  return;
282
1066
  }
283
- console.log(chalk.bold("Repair results:"));
284
- console.log(` ${chalk.green("\u2713")} Repaired: ${repairCount} issues`);
285
- console.log(` ${chalk.yellow("!")} Remaining: ${remainingIssues.length} issues`);
1067
+ console.log(chalk2.bold("Repair results:"));
1068
+ console.log(` ${chalk2.green("\u2713")} Repaired: ${repairCount} issues`);
1069
+ console.log(` ${chalk2.yellow("!")} Remaining: ${remainingIssues.length} issues`);
286
1070
  if (remainingIssues.length > 0) {
287
1071
  console.log();
288
- console.log(chalk.yellow("Issues that require manual intervention:"));
1072
+ console.log(chalk2.yellow("Issues that require manual intervention:"));
289
1073
  for (const issue of remainingIssues.slice(0, 5)) {
290
1074
  console.log(` - [${issue.type}] ${issue.details}`);
291
1075
  }
292
1076
  if (remainingIssues.length > 5) {
293
- console.log(chalk.gray(` ... and ${remainingIssues.length - 5} more`));
1077
+ console.log(chalk2.gray(` ... and ${remainingIssues.length - 5} more`));
294
1078
  }
295
1079
  }
296
1080
  if (!options.dryRun && dataDir && repairCount > 0) {
297
1081
  console.log();
298
- console.log(chalk.gray("Note: Writing repaired changes requires NodeStore integration."));
299
- console.log(chalk.gray("Changes are prepared but not written to disk."));
1082
+ console.log(chalk2.gray("Note: Writing repaired changes requires NodeStore integration."));
1083
+ console.log(chalk2.gray("Changes are prepared but not written to disk."));
300
1084
  }
301
1085
  console.log();
302
1086
  } catch (error) {
303
- console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1087
+ console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
304
1088
  process.exit(1);
305
1089
  }
306
1090
  }
307
1091
  async function exportCommand(options) {
308
- const chalk = await getChalk();
309
- console.log(chalk.bold("\nxNet Data Export\n"));
1092
+ const chalk2 = await getChalk();
1093
+ console.log(chalk2.bold("\nxNet Data Export\n"));
310
1094
  try {
311
1095
  let dataDir;
312
1096
  let changes = [];
@@ -314,14 +1098,14 @@ async function exportCommand(options) {
314
1098
  dataDir = findDataDir(options.dataDir);
315
1099
  changes = loadChangesFromDir(dataDir);
316
1100
  } catch {
317
- console.log(chalk.yellow("Note: No data directory found."));
318
- console.log(chalk.gray("Using demo data for illustration.\n"));
1101
+ console.log(chalk2.yellow("Note: No data directory found."));
1102
+ console.log(chalk2.gray("Using demo data for illustration.\n"));
319
1103
  dataDir = "";
320
1104
  changes = generateDemoChanges();
321
1105
  }
322
- const outputPath = resolve(process.cwd(), options.output);
323
- console.log(chalk.gray(`Source: ${dataDir || "demo data"}`));
324
- console.log(chalk.gray(`Output: ${outputPath}`));
1106
+ const outputPath = resolve5(process.cwd(), options.output);
1107
+ console.log(chalk2.gray(`Source: ${dataDir || "demo data"}`));
1108
+ console.log(chalk2.gray(`Output: ${outputPath}`));
325
1109
  console.log();
326
1110
  const exportData = {
327
1111
  version: 1,
@@ -342,30 +1126,30 @@ async function exportCommand(options) {
342
1126
  const content = options.pretty ? JSON.stringify(exportData, null, 2) : JSON.stringify(exportData);
343
1127
  if (options.format === "jsonl") {
344
1128
  const lines = exportData.changes.map((c) => JSON.stringify(c)).join("\n");
345
- writeFileSync(outputPath, lines, "utf-8");
1129
+ writeFileSync3(outputPath, lines, "utf-8");
346
1130
  } else {
347
- writeFileSync(outputPath, content, "utf-8");
1131
+ writeFileSync3(outputPath, content, "utf-8");
348
1132
  }
349
- console.log(chalk.green(`\u2713 Exported ${changes.length} changes`));
350
- console.log(chalk.gray(` File size: ${Math.round(content.length / 1024)}KB`));
1133
+ console.log(chalk2.green(`\u2713 Exported ${changes.length} changes`));
1134
+ console.log(chalk2.gray(` File size: ${Math.round(content.length / 1024)}KB`));
351
1135
  console.log();
352
1136
  } catch (error) {
353
- console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1137
+ console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
354
1138
  process.exit(1);
355
1139
  }
356
1140
  }
357
1141
  async function importCommand(options) {
358
- const chalk = await getChalk();
359
- console.log(chalk.bold("\nxNet Data Import\n"));
1142
+ const chalk2 = await getChalk();
1143
+ console.log(chalk2.bold("\nxNet Data Import\n"));
360
1144
  try {
361
- const inputPath = resolve(process.cwd(), options.input);
1145
+ const inputPath = resolve5(process.cwd(), options.input);
362
1146
  if (!existsSync(inputPath)) {
363
- console.error(chalk.red(`Error: Input file not found: ${inputPath}`));
1147
+ console.error(chalk2.red(`Error: Input file not found: ${inputPath}`));
364
1148
  process.exit(1);
365
1149
  }
366
- console.log(chalk.gray(`Input: ${inputPath}`));
1150
+ console.log(chalk2.gray(`Input: ${inputPath}`));
367
1151
  if (options.dryRun) {
368
- console.log(chalk.yellow("\nDRY RUN - No changes will be applied\n"));
1152
+ console.log(chalk2.yellow("\nDRY RUN - No changes will be applied\n"));
369
1153
  }
370
1154
  const content = readFileSync(inputPath, "utf-8");
371
1155
  let importData;
@@ -380,43 +1164,43 @@ async function importCommand(options) {
380
1164
  const lines = content.split("\n").filter((l) => l.trim());
381
1165
  importData = { changes: lines.map((l) => JSON.parse(l)) };
382
1166
  }
383
- console.log(chalk.gray(`Found ${importData.changes.length} changes to import`));
1167
+ console.log(chalk2.gray(`Found ${importData.changes.length} changes to import`));
384
1168
  console.log();
385
1169
  const changes = importData.changes.map((c) => ({
386
1170
  ...c,
387
1171
  signature: typeof c.signature === "string" ? new Uint8Array(Buffer.from(c.signature, "base64")) : c.signature
388
1172
  }));
389
- console.log(chalk.bold("Verifying import data..."));
1173
+ console.log(chalk2.bold("Verifying import data..."));
390
1174
  const report = await quickIntegrityCheck(changes);
391
1175
  if (report.issues.length > 0) {
392
- console.log(chalk.yellow(` \u26A0 ${report.issues.length} integrity issues found`));
1176
+ console.log(chalk2.yellow(` \u26A0 ${report.issues.length} integrity issues found`));
393
1177
  if (report.summary.errors > 0) {
394
- console.log(chalk.red(` ${report.summary.errors} errors may prevent import`));
1178
+ console.log(chalk2.red(` ${report.summary.errors} errors may prevent import`));
395
1179
  }
396
1180
  } else {
397
- console.log(chalk.green(" \u2713 All changes verified"));
1181
+ console.log(chalk2.green(" \u2713 All changes verified"));
398
1182
  }
399
1183
  if (options.applyMigrations) {
400
1184
  console.log();
401
- console.log(chalk.bold("Checking for required migrations..."));
402
- console.log(chalk.gray(" (Migration detection requires schema registry)"));
1185
+ console.log(chalk2.bold("Checking for required migrations..."));
1186
+ console.log(chalk2.gray(" (Migration detection requires schema registry)"));
403
1187
  }
404
1188
  console.log();
405
- console.log(chalk.bold("Import summary:"));
1189
+ console.log(chalk2.bold("Import summary:"));
406
1190
  console.log(` Changes: ${changes.length}`);
407
1191
  console.log(` Roots: ${findRoots(changes).length}`);
408
1192
  console.log(` Heads: ${findHeads(changes).length}`);
409
1193
  console.log(` Chain depth: ${getChainDepth(changes)}`);
410
1194
  if (!options.dryRun) {
411
1195
  console.log();
412
- console.log(chalk.gray("Note: Writing imported changes requires NodeStore integration."));
413
- console.log(chalk.gray("Changes are validated but not written to storage."));
1196
+ console.log(chalk2.gray("Note: Writing imported changes requires NodeStore integration."));
1197
+ console.log(chalk2.gray("Changes are validated but not written to storage."));
414
1198
  }
415
1199
  console.log();
416
- console.log(chalk.green("\u2713 Import validation complete"));
1200
+ console.log(chalk2.green("\u2713 Import validation complete"));
417
1201
  console.log();
418
1202
  } catch (error) {
419
- console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1203
+ console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
420
1204
  process.exit(1);
421
1205
  }
422
1206
  }
@@ -435,13 +1219,90 @@ function registerDoctorCommand(program2) {
435
1219
  });
436
1220
  }
437
1221
 
1222
+ // src/commands/mcp.ts
1223
+ import {
1224
+ createMCPServer,
1225
+ createMcpHttpServer
1226
+ } from "@xnetjs/plugins/node";
1227
+ var defaultBackendFactory = (options) => createRemoteAgentBackend(options);
1228
+ function buildMcpServer(backend) {
1229
+ return createMCPServer({ store: backend.store, schemas: backend.schemas });
1230
+ }
1231
+ async function startMcpServe(backendFactory, options) {
1232
+ const backend = await backendFactory({
1233
+ ...options.apiUrl ? { apiUrl: options.apiUrl } : {}
1234
+ });
1235
+ const server = buildMcpServer(backend);
1236
+ if (options.http) {
1237
+ const http = createMcpHttpServer({
1238
+ server,
1239
+ ...options.pairingToken ? { pairingToken: options.pairingToken } : {},
1240
+ ...options.allowOrigin && options.allowOrigin.length > 0 ? { allowedOrigins: options.allowOrigin } : {},
1241
+ ...options.host ? { host: options.host } : {},
1242
+ ...options.port !== void 0 ? { port: options.port } : {}
1243
+ });
1244
+ await http.start();
1245
+ return { mode: "http", server, http, stop: () => http.stop() };
1246
+ }
1247
+ return {
1248
+ mode: "stdio",
1249
+ server,
1250
+ stop: async () => {
1251
+ server.stop();
1252
+ }
1253
+ };
1254
+ }
1255
+ function openClawHttpConfigSnippet(handle) {
1256
+ return JSON.stringify(
1257
+ {
1258
+ mcp: {
1259
+ servers: {
1260
+ xnet: {
1261
+ url: `${handle.url}${handle.path}`,
1262
+ transport: "streamable-http",
1263
+ headers: { "x-xnet-pairing": handle.pairingToken }
1264
+ }
1265
+ }
1266
+ }
1267
+ },
1268
+ null,
1269
+ 2
1270
+ );
1271
+ }
1272
+ function registerMcpCommand(program2, backendFactory = defaultBackendFactory) {
1273
+ const mcp = program2.command("mcp").description("Expose the workspace to MCP clients");
1274
+ mcp.command("serve").description("Start an MCP server (stdio by default; --http for browser/OpenClaw clients)").option("--http", "Serve over hardened loopback HTTP instead of stdio").option("--host <host>", "Loopback host for --http (default 127.0.0.1)").option("--port <n>", "Port for --http (default 31416)", parseIntOption3).option(
1275
+ "--allow-origin <origin...>",
1276
+ "Browser origins permitted for --http (e.g. https://user.github.io)"
1277
+ ).option("--pairing-token <token>", "Shared secret for --http (generated if omitted)").option("--api-url <url>", "xNet local API URL (default http://127.0.0.1:31415)").action(async (options) => {
1278
+ const handle = await startMcpServe(backendFactory, options);
1279
+ if (handle.mode === "http" && handle.http) {
1280
+ console.error(`xNet MCP server listening on ${handle.http.url}${handle.http.path}`);
1281
+ console.error(`pairing token: ${handle.http.pairingToken}`);
1282
+ console.error("OpenClaw config:\n" + openClawHttpConfigSnippet(handle.http));
1283
+ const shutdown = () => {
1284
+ void handle.stop().then(() => process.exit(0));
1285
+ };
1286
+ process.on("SIGINT", shutdown);
1287
+ process.on("SIGTERM", shutdown);
1288
+ return;
1289
+ }
1290
+ await handle.server.startStdio();
1291
+ });
1292
+ }
1293
+ function parseIntOption3(value) {
1294
+ const parsed = Number.parseInt(value, 10);
1295
+ if (!Number.isFinite(parsed)) throw new Error(`Invalid number: ${value}`);
1296
+ return parsed;
1297
+ }
1298
+
438
1299
  // src/commands/migrate.ts
439
- import { writeFileSync as writeFileSync2, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
440
- import { resolve as resolve2 } from "path";
1300
+ import { writeFileSync as writeFileSync4, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
1301
+ import { resolve as resolve6 } from "path";
441
1302
  async function getChalk2() {
442
1303
  try {
443
- const chalk = await import("chalk");
444
- return chalk.default;
1304
+ const chalk2 = await import("chalk");
1305
+ return chalk2.default;
445
1306
  } catch {
446
1307
  const identity = (s) => s;
447
1308
  return {
@@ -490,7 +1351,7 @@ function findSchema(schemaIRI, schemas) {
490
1351
  return null;
491
1352
  }
492
1353
  async function analyzeCommand(options) {
493
- const chalk = await getChalk2();
1354
+ const chalk2 = await getChalk2();
494
1355
  try {
495
1356
  const fromParsed = parseSchemaIRI(options.from);
496
1357
  const toParsed = parseSchemaIRI(options.to);
@@ -501,8 +1362,8 @@ async function analyzeCommand(options) {
501
1362
  const fromSchema = findSchema(options.from, schemas);
502
1363
  const toSchema = findSchema(options.to, schemas);
503
1364
  if (!fromSchema || !toSchema) {
504
- console.log(chalk.yellow("\nNote: Schema file not provided. Showing example output.\n"));
505
- console.log(chalk.gray("Use --schema-file to load actual schemas from a JSON file.\n"));
1365
+ console.log(chalk2.yellow("\nNote: Schema file not provided. Showing example output.\n"));
1366
+ console.log(chalk2.gray("Use --schema-file to load actual schemas from a JSON file.\n"));
506
1367
  const mockFromSchema = {
507
1368
  "@id": `xnet://xnet.fyi/${fromParsed.name}@${fromParsed.version}`,
508
1369
  "@type": "xnet://xnet.fyi/Schema",
@@ -531,72 +1392,72 @@ async function analyzeCommand(options) {
531
1392
  { "@id": `#priority`, name: "priority", type: "text", required: true }
532
1393
  ]
533
1394
  };
534
- printAnalysisResult(diffSchemas(mockFromSchema, mockToSchema), options.json, chalk);
1395
+ printAnalysisResult(diffSchemas(mockFromSchema, mockToSchema), options.json, chalk2);
535
1396
  return;
536
1397
  }
537
1398
  const diff = diffSchemas(fromSchema, toSchema);
538
- printAnalysisResult(diff, options.json, chalk);
1399
+ printAnalysisResult(diff, options.json, chalk2);
539
1400
  } catch (error) {
540
- console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1401
+ console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
541
1402
  process.exit(1);
542
1403
  }
543
1404
  }
544
- function printAnalysisResult(diff, json, chalk) {
1405
+ function printAnalysisResult(diff, json, chalk2) {
545
1406
  if (json) {
546
1407
  console.log(JSON.stringify(diff, null, 2));
547
1408
  return;
548
1409
  }
549
- console.log(chalk.bold(`
1410
+ console.log(chalk2.bold(`
550
1411
  Schema changes: ${diff.fromVersion} \u2192 ${diff.toVersion}
551
1412
  `));
552
1413
  if (diff.changes.length === 0) {
553
- console.log(chalk.green("No changes detected."));
1414
+ console.log(chalk2.green("No changes detected."));
554
1415
  return;
555
1416
  }
556
1417
  const breaking = diff.changes.filter((c) => c.risk === "breaking");
557
1418
  const caution = diff.changes.filter((c) => c.risk === "caution");
558
1419
  const safe = diff.changes.filter((c) => c.risk === "safe");
559
1420
  if (breaking.length > 0) {
560
- console.log(chalk.red(chalk.bold("BREAKING CHANGES:")));
1421
+ console.log(chalk2.red(chalk2.bold("BREAKING CHANGES:")));
561
1422
  for (const change of breaking) {
562
- console.log(chalk.red(` - ${change.type.toUpperCase()}: ${change.description}`));
1423
+ console.log(chalk2.red(` - ${change.type.toUpperCase()}: ${change.description}`));
563
1424
  }
564
1425
  console.log();
565
1426
  }
566
1427
  if (caution.length > 0) {
567
- console.log(chalk.yellow(chalk.bold("CAUTION:")));
1428
+ console.log(chalk2.yellow(chalk2.bold("CAUTION:")));
568
1429
  for (const change of caution) {
569
- console.log(chalk.yellow(` - ${change.type.toUpperCase()}: ${change.description}`));
1430
+ console.log(chalk2.yellow(` - ${change.type.toUpperCase()}: ${change.description}`));
570
1431
  }
571
1432
  console.log();
572
1433
  }
573
1434
  if (safe.length > 0) {
574
- console.log(chalk.green(chalk.bold("SAFE:")));
1435
+ console.log(chalk2.green(chalk2.bold("SAFE:")));
575
1436
  for (const change of safe) {
576
- console.log(chalk.green(` - ${change.type.toUpperCase()}: ${change.description}`));
1437
+ console.log(chalk2.green(` - ${change.type.toUpperCase()}: ${change.description}`));
577
1438
  }
578
1439
  console.log();
579
1440
  }
580
- console.log(chalk.bold("Summary:"));
1441
+ console.log(chalk2.bold("Summary:"));
581
1442
  console.log(
582
- ` ${chalk.red(`${diff.summary.breaking} breaking`)} | ${chalk.yellow(`${diff.summary.caution} caution`)} | ${chalk.green(`${diff.summary.safe} safe`)}`
1443
+ ` ${chalk2.red(`${diff.summary.breaking} breaking`)} | ${chalk2.yellow(`${diff.summary.caution} caution`)} | ${chalk2.green(`${diff.summary.safe} safe`)}`
583
1444
  );
584
1445
  console.log();
585
1446
  if (diff.changes.some((c) => c.suggestedLens)) {
586
- console.log(chalk.bold("Suggested lens:"));
587
- console.log(chalk.dim("\u2500".repeat(40)));
1447
+ console.log(chalk2.bold("Suggested lens:"));
1448
+ console.log(chalk2.dim("\u2500".repeat(40)));
588
1449
  console.log(generateLensSnippet(diff));
589
- console.log(chalk.dim("\u2500".repeat(40)));
1450
+ console.log(chalk2.dim("\u2500".repeat(40)));
590
1451
  console.log();
591
1452
  }
592
1453
  if (diff.autoMigratable) {
593
- console.log(chalk.green("\u2713 Automatic migration possible"));
1454
+ console.log(chalk2.green("\u2713 Automatic migration possible"));
594
1455
  } else {
595
- console.log(chalk.yellow("\u26A0 Manual intervention required for some changes"));
1456
+ console.log(chalk2.yellow("\u26A0 Manual intervention required for some changes"));
596
1457
  }
597
1458
  }
598
1459
  async function generateCommand(options) {
599
- const chalk = await getChalk2();
1460
+ const chalk2 = await getChalk2();
600
1461
  try {
601
1462
  const fromParsed = parseSchemaIRI(options.from);
602
1463
  const toParsed = parseSchemaIRI(options.to);
@@ -609,7 +1470,7 @@ async function generateCommand(options) {
609
1470
  const from = fromSchema ?? createMockSchema(fromParsed.name, fromParsed.version, "from");
610
1471
  const to = toSchema ?? createMockSchema(toParsed.name, toParsed.version, "to");
611
1472
  if (!fromSchema || !toSchema) {
612
- console.log(chalk.yellow("\nNote: Using mock schemas for demonstration.\n"));
1473
+ console.log(chalk2.yellow("\nNote: Using mock schemas for demonstration.\n"));
613
1474
  }
614
1475
  const diff = diffSchemas(from, to);
615
1476
  const sourceIRI = from["@id"];
@@ -622,26 +1483,26 @@ async function generateCommand(options) {
622
1483
  includeTodos: true
623
1484
  });
624
1485
  if (options.output) {
625
- const outputPath = resolve2(process.cwd(), options.output);
1486
+ const outputPath = resolve6(process.cwd(), options.output);
626
1487
  if (existsSync2(outputPath) && !options.force) {
627
- console.error(chalk.red(`Error: File already exists: ${outputPath}`));
628
- console.error(chalk.gray("Use --force to overwrite."));
1488
+ console.error(chalk2.red(`Error: File already exists: ${outputPath}`));
1489
+ console.error(chalk2.gray("Use --force to overwrite."));
629
1490
  process.exit(1);
630
1491
  }
631
- writeFileSync2(outputPath, generated.code, "utf-8");
632
- console.log(chalk.green(`
1492
+ writeFileSync4(outputPath, generated.code, "utf-8");
1493
+ console.log(chalk2.green(`
633
1494
  \u2713 Generated lens written to: ${outputPath}`));
634
1495
  } else {
635
1496
  console.log(generated.code);
636
1497
  }
637
1498
  if (!generated.isComplete) {
638
- console.log(chalk.yellow("\n\u26A0 Manual completion required:"));
1499
+ console.log(chalk2.yellow("\n\u26A0 Manual completion required:"));
639
1500
  for (const item of generated.manualItems) {
640
- console.log(chalk.yellow(` - ${item}`));
1501
+ console.log(chalk2.yellow(` - ${item}`));
641
1502
  }
642
1503
  }
643
1504
  } catch (error) {
644
- console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1505
+ console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
645
1506
  process.exit(1);
646
1507
  }
647
1508
  }
@@ -679,43 +1540,43 @@ function createMockSchema(name, version, type) {
679
1540
  }
680
1541
  }
681
1542
  async function runCommand(options) {
682
- const chalk = await getChalk2();
1543
+ const chalk2 = await getChalk2();
683
1544
  if (!options.dryRun && !options.apply) {
684
- console.error(chalk.red("Error: Must specify either --dry-run or --apply"));
1545
+ console.error(chalk2.red("Error: Must specify either --dry-run or --apply"));
685
1546
  process.exit(1);
686
1547
  }
687
1548
  if (options.dryRun && options.apply) {
688
- console.error(chalk.red("Error: Cannot specify both --dry-run and --apply"));
1549
+ console.error(chalk2.red("Error: Cannot specify both --dry-run and --apply"));
689
1550
  process.exit(1);
690
1551
  }
691
1552
  try {
692
- console.log(chalk.blue(`
1553
+ console.log(chalk2.blue(`
693
1554
  Migration: ${options.from} \u2192 ${options.to}
694
1555
  `));
695
1556
  if (options.dryRun) {
696
- console.log(chalk.yellow("DRY RUN - No changes will be applied\n"));
697
- console.log(chalk.gray("Would migrate nodes:"));
698
- console.log(chalk.gray(" - Scan data directory for nodes with schema " + options.from));
699
- console.log(chalk.gray(" - Apply migration lens to each node"));
700
- console.log(chalk.gray(" - Update schema version to " + options.to));
1557
+ console.log(chalk2.yellow("DRY RUN - No changes will be applied\n"));
1558
+ console.log(chalk2.gray("Would migrate nodes:"));
1559
+ console.log(chalk2.gray(" - Scan data directory for nodes with schema " + options.from));
1560
+ console.log(chalk2.gray(" - Apply migration lens to each node"));
1561
+ console.log(chalk2.gray(" - Update schema version to " + options.to));
701
1562
  console.log();
702
- console.log(chalk.dim("Note: Full migration requires --data-dir and --lens-file options"));
703
- console.log(chalk.dim(" or a configured NodeStore with registered lenses."));
1563
+ console.log(chalk2.dim("Note: Full migration requires --data-dir and --lens-file options"));
1564
+ console.log(chalk2.dim(" or a configured NodeStore with registered lenses."));
704
1565
  } else {
705
- console.log(chalk.yellow("APPLY MODE - Changes will be written\n"));
706
- console.log(chalk.gray("Would apply migration:"));
707
- console.log(chalk.gray(" - Load nodes with schema " + options.from));
708
- console.log(chalk.gray(" - Transform using migration lens"));
709
- console.log(chalk.gray(" - Write updated nodes with schema " + options.to));
1566
+ console.log(chalk2.yellow("APPLY MODE - Changes will be written\n"));
1567
+ console.log(chalk2.gray("Would apply migration:"));
1568
+ console.log(chalk2.gray(" - Load nodes with schema " + options.from));
1569
+ console.log(chalk2.gray(" - Transform using migration lens"));
1570
+ console.log(chalk2.gray(" - Write updated nodes with schema " + options.to));
710
1571
  console.log();
711
- console.log(chalk.dim("Note: Full migration requires --data-dir and --lens-file options"));
712
- console.log(chalk.dim(" or a configured NodeStore with registered lenses."));
1572
+ console.log(chalk2.dim("Note: Full migration requires --data-dir and --lens-file options"));
1573
+ console.log(chalk2.dim(" or a configured NodeStore with registered lenses."));
713
1574
  }
714
1575
  console.log();
715
- console.log(chalk.green("\u2713 Migration command parsed successfully"));
716
- console.log(chalk.gray(" Full implementation requires NodeStore integration."));
1576
+ console.log(chalk2.green("\u2713 Migration command parsed successfully"));
1577
+ console.log(chalk2.gray(" Full implementation requires NodeStore integration."));
717
1578
  } catch (error) {
718
- console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1579
+ console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
719
1580
  process.exit(1);
720
1581
  }
721
1582
  }
@@ -733,7 +1594,7 @@ function registerMigrateCommand(program2) {
733
1594
  }
734
1595
 
735
1596
  // src/commands/schema.ts
736
- import { writeFileSync as writeFileSync3, readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
1597
+ import { writeFileSync as writeFileSync5, readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
737
1598
  function extractSchemas() {
738
1599
  const schemaExportPath = process.env.XNET_SCHEMA_EXPORT || "./schemas-export.json";
739
1600
  if (existsSync3(schemaExportPath)) {
@@ -758,7 +1619,7 @@ function registerSchemaCommand(program2) {
758
1619
  schemas
759
1620
  };
760
1621
  const json = options.pretty ? JSON.stringify(output, null, 2) : JSON.stringify(output);
761
- writeFileSync3(options.output, json, "utf-8");
1622
+ writeFileSync5(options.output, json, "utf-8");
762
1623
  console.log(`Extracted ${schemas.length} schemas to ${options.output}`);
763
1624
  });
764
1625
  schema.command("diff").description("Compare two schema files and report changes").argument("<old-file>", "Path to the old schemas JSON file").argument("<new-file>", "Path to the new schemas JSON file").option("-o, --output <file>", "Output diff to JSON file").option("--fail-on-breaking", "Exit with code 1 if breaking changes found", false).option("--quiet", "Only output JSON, no console messages", false).action(
@@ -850,7 +1711,7 @@ function registerSchemaCommand(program2) {
850
1711
  }
851
1712
  };
852
1713
  if (options.output) {
853
- writeFileSync3(options.output, JSON.stringify(output, null, 2), "utf-8");
1714
+ writeFileSync5(options.output, JSON.stringify(output, null, 2), "utf-8");
854
1715
  }
855
1716
  if (!options.quiet) {
856
1717
  if (diffs.length === 0) {
@@ -887,4 +1748,11 @@ program.name("xnet").description("xNet CLI - Schema migrations, diagnostics, and
887
1748
  registerMigrateCommand(program);
888
1749
  registerSchemaCommand(program);
889
1750
  registerDoctorCommand(program);
1751
+ registerAgentCommands(program);
1752
+ registerMcpCommand(program);
1753
+ registerBridgeCommand(program);
1754
+ registerCodeCommand(program);
1755
+ registerDataCommand(program);
1756
+ registerPluginCommand(program);
1757
+ registerConnectorCommand(program);
890
1758
  program.parse();