@xnetjs/cli 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +5 -0
  2. package/dist/cli.js +215 -142
  3. package/package.json +14 -12
package/README.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  CLI tools for schema migration workflows, schema diffing, and data integrity diagnostics.
4
4
 
5
+ > **Alpha software.** xNet is released but early: this package is on npm and
6
+ > usable today, but its API can change between releases, sometimes without a
7
+ > migration path. Pin your version. See the
8
+ > [project README](https://github.com/crs48/xNet#readme) for what alpha means here.
9
+
5
10
  ## Installation
6
11
 
7
12
  ```bash
package/dist/cli.js CHANGED
@@ -518,7 +518,7 @@ function buildBridgeServer(options, runner = new NodeCommandRunner()) {
518
518
  });
519
519
  }
520
520
  function registerBridgeCommand(program2) {
521
- const bridge = program2.command("bridge").description("Run the local agent bridge for XNet's AI chat panel");
521
+ const bridge = program2.command("bridge").description("Run the local agent bridge for xNet's AI chat panel");
522
522
  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(
523
523
  "--allow-origin <origin...>",
524
524
  "Browser origins permitted (e.g. https://app.xnet.fyi for the web deployment)"
@@ -528,7 +528,7 @@ function registerBridgeCommand(program2) {
528
528
  ).option("--cwd <dir>", "Working directory the agent runs in (default current dir)").option(
529
529
  "--upstream <url>",
530
530
  "Front a raw OpenAI-compatible server (e.g. http://localhost:11434 for Ollama) instead of a CLI"
531
- ).option("--upstream-model <id>", "Model id to request from --upstream (default llama3.2)").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(
531
+ ).option("--upstream-model <id>", "Model id to request from --upstream (default llama3.2)").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(
532
532
  "--mcp-api-url <url>",
533
533
  "xNet local API URL the MCP server talks to (default http://127.0.0.1:31415)"
534
534
  ).action(async (options) => {
@@ -555,7 +555,7 @@ function registerBridgeCommand(program2) {
555
555
  );
556
556
  console.error(`Pairing code: ${handle.pairingToken}`);
557
557
  console.error(
558
- 'In XNet, open the AI panel, select "Local bridge", and paste the pairing code.'
558
+ 'In xNet, open the AI panel, select "Local bridge", and paste the pairing code.'
559
559
  );
560
560
  const shutdown = () => {
561
561
  void handle.stop().then(() => process.exit(0));
@@ -1032,8 +1032,8 @@ import {
1032
1032
  } from "@xnetjs/sync";
1033
1033
  async function getChalk() {
1034
1034
  try {
1035
- const chalk2 = await import("chalk");
1036
- return chalk2.default;
1035
+ const chalk3 = await import("chalk");
1036
+ return chalk3.default;
1037
1037
  } catch {
1038
1038
  const identity = (s) => s;
1039
1039
  return {
@@ -1102,114 +1102,114 @@ function formatDuration(ms) {
1102
1102
  return `${(ms / 1e3).toFixed(2)}s`;
1103
1103
  }
1104
1104
  async function doctorCommand(options) {
1105
- const chalk2 = await getChalk();
1106
- console.log(chalk2.bold("\nxNet Health Check\n"));
1107
- console.log(chalk2.dim("\u2500".repeat(50)));
1105
+ const chalk3 = await getChalk();
1106
+ console.log(chalk3.bold("\nxNet Health Check\n"));
1107
+ console.log(chalk3.dim("\u2500".repeat(50)));
1108
1108
  try {
1109
1109
  let dataDir;
1110
1110
  let changes = [];
1111
1111
  try {
1112
1112
  dataDir = findDataDir(options.dataDir);
1113
- console.log(chalk2.gray(`Data directory: ${dataDir}`));
1113
+ console.log(chalk3.gray(`Data directory: ${dataDir}`));
1114
1114
  changes = loadChangesFromDir(dataDir);
1115
- console.log(chalk2.gray(`Found ${changes.length} changes`));
1115
+ console.log(chalk3.gray(`Found ${changes.length} changes`));
1116
1116
  } catch {
1117
- console.log(chalk2.yellow("\nNote: No data directory found."));
1118
- console.log(chalk2.gray("Use --data-dir to specify a data directory."));
1119
- console.log(chalk2.gray("Running with demo data...\n"));
1117
+ console.log(chalk3.yellow("\nNote: No data directory found."));
1118
+ console.log(chalk3.gray("Use --data-dir to specify a data directory."));
1119
+ console.log(chalk3.gray("Running with demo data...\n"));
1120
1120
  changes = generateDemoChanges();
1121
1121
  }
1122
1122
  console.log();
1123
- console.log(chalk2.bold("Checking data integrity..."));
1123
+ console.log(chalk3.bold("Checking data integrity..."));
1124
1124
  const report = options.quick ? await quickIntegrityCheck(changes) : await verifyIntegrity(changes);
1125
1125
  if (options.json) {
1126
1126
  console.log(JSON.stringify(report, null, 2));
1127
1127
  return;
1128
1128
  }
1129
- printIntegrityResults(report, chalk2, options.verbose);
1129
+ printIntegrityResults(report, chalk3, options.verbose);
1130
1130
  console.log();
1131
- console.log(chalk2.bold("Analyzing change chains..."));
1131
+ console.log(chalk3.bold("Analyzing change chains..."));
1132
1132
  const orphans = findOrphans(changes);
1133
1133
  const roots = findRoots(changes);
1134
1134
  const heads = findHeads(changes);
1135
1135
  const depth = getChainDepth(changes);
1136
- console.log(` ${chalk2.green("\u2713")} Roots: ${roots.length}`);
1137
- console.log(` ${chalk2.green("\u2713")} Heads: ${heads.length}`);
1138
- console.log(` ${chalk2.green("\u2713")} Depth: ${depth}`);
1136
+ console.log(` ${chalk3.green("\u2713")} Roots: ${roots.length}`);
1137
+ console.log(` ${chalk3.green("\u2713")} Heads: ${heads.length}`);
1138
+ console.log(` ${chalk3.green("\u2713")} Depth: ${depth}`);
1139
1139
  if (orphans.length > 0) {
1140
- console.log(` ${chalk2.yellow("\u26A0")} Orphans: ${orphans.length}`);
1140
+ console.log(` ${chalk3.yellow("\u26A0")} Orphans: ${orphans.length}`);
1141
1141
  } else {
1142
- console.log(` ${chalk2.green("\u2713")} Orphans: 0`);
1142
+ console.log(` ${chalk3.green("\u2713")} Orphans: 0`);
1143
1143
  }
1144
1144
  console.log();
1145
- console.log(chalk2.bold("Checking schema compatibility..."));
1146
- console.log(chalk2.gray(" (Schema analysis requires @xnetjs/data integration)"));
1145
+ console.log(chalk3.bold("Checking schema compatibility..."));
1146
+ console.log(chalk3.gray(" (Schema analysis requires @xnetjs/data integration)"));
1147
1147
  console.log();
1148
- console.log(chalk2.bold("Checking sync state..."));
1149
- console.log(chalk2.gray(" (Sync analysis requires active SyncProvider)"));
1148
+ console.log(chalk3.bold("Checking sync state..."));
1149
+ console.log(chalk3.gray(" (Sync analysis requires active SyncProvider)"));
1150
1150
  console.log();
1151
- console.log(chalk2.dim("\u2500".repeat(50)));
1151
+ console.log(chalk3.dim("\u2500".repeat(50)));
1152
1152
  const hasErrors = report.summary.errors > 0;
1153
1153
  const hasWarnings = report.summary.warnings > 0 || orphans.length > 0;
1154
1154
  if (hasErrors) {
1155
- console.log(chalk2.red(chalk2.bold("Status: UNHEALTHY")));
1156
- console.log(chalk2.red(" Data integrity issues detected. Run `xnet repair` to fix."));
1155
+ console.log(chalk3.red(chalk3.bold("Status: UNHEALTHY")));
1156
+ console.log(chalk3.red(" Data integrity issues detected. Run `xnet repair` to fix."));
1157
1157
  } else if (hasWarnings) {
1158
- console.log(chalk2.yellow(chalk2.bold("Status: HEALTHY with warnings")));
1159
- console.log(chalk2.gray(" Some issues detected but data is intact."));
1158
+ console.log(chalk3.yellow(chalk3.bold("Status: HEALTHY with warnings")));
1159
+ console.log(chalk3.gray(" Some issues detected but data is intact."));
1160
1160
  } else {
1161
- console.log(chalk2.green(chalk2.bold("Status: HEALTHY")));
1162
- console.log(chalk2.green(" All checks passed."));
1161
+ console.log(chalk3.green(chalk3.bold("Status: HEALTHY")));
1162
+ console.log(chalk3.green(" All checks passed."));
1163
1163
  }
1164
1164
  console.log();
1165
1165
  } catch (error) {
1166
- console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1166
+ console.error(chalk3.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1167
1167
  process.exit(1);
1168
1168
  }
1169
1169
  }
1170
- function printIntegrityResults(report, chalk2, verbose) {
1170
+ function printIntegrityResults(report, chalk3, verbose) {
1171
1171
  const { checked, valid, issues, summary, durationMs } = report;
1172
1172
  const percentage = checked > 0 ? Math.round(valid / checked * 100) : 100;
1173
1173
  if (issues.length === 0) {
1174
- console.log(` ${chalk2.green("\u2713")} ${checked} changes verified (${percentage}% valid)`);
1175
- console.log(` ${chalk2.green("\u2713")} Hash chains intact`);
1176
- console.log(` ${chalk2.green("\u2713")} No issues detected`);
1177
- console.log(chalk2.gray(` Completed in ${formatDuration(durationMs)}`));
1174
+ console.log(` ${chalk3.green("\u2713")} ${checked} changes verified (${percentage}% valid)`);
1175
+ console.log(` ${chalk3.green("\u2713")} Hash chains intact`);
1176
+ console.log(` ${chalk3.green("\u2713")} No issues detected`);
1177
+ console.log(chalk3.gray(` Completed in ${formatDuration(durationMs)}`));
1178
1178
  } else {
1179
1179
  console.log(
1180
- ` ${chalk2.yellow("!")} ${checked} changes checked (${valid} valid, ${checked - valid} issues)`
1180
+ ` ${chalk3.yellow("!")} ${checked} changes checked (${valid} valid, ${checked - valid} issues)`
1181
1181
  );
1182
1182
  if (summary.errors > 0) {
1183
- console.log(` ${chalk2.red("\u2717")} ${summary.errors} errors`);
1183
+ console.log(` ${chalk3.red("\u2717")} ${summary.errors} errors`);
1184
1184
  }
1185
1185
  if (summary.warnings > 0) {
1186
- console.log(` ${chalk2.yellow("\u26A0")} ${summary.warnings} warnings`);
1186
+ console.log(` ${chalk3.yellow("\u26A0")} ${summary.warnings} warnings`);
1187
1187
  }
1188
- console.log(chalk2.gray(` Completed in ${formatDuration(durationMs)}`));
1188
+ console.log(chalk3.gray(` Completed in ${formatDuration(durationMs)}`));
1189
1189
  if (verbose) {
1190
1190
  console.log();
1191
- console.log(chalk2.bold("Issues by type:"));
1191
+ console.log(chalk3.bold("Issues by type:"));
1192
1192
  for (const [type, count] of Object.entries(summary.byType)) {
1193
1193
  if (count > 0) {
1194
1194
  console.log(` - ${type}: ${count}`);
1195
1195
  }
1196
1196
  }
1197
1197
  console.log();
1198
- console.log(chalk2.bold("Details:"));
1198
+ console.log(chalk3.bold("Details:"));
1199
1199
  for (const issue of issues.slice(0, 10)) {
1200
- const icon = issue.severity === "error" ? chalk2.red("\u2717") : chalk2.yellow("\u26A0");
1200
+ const icon = issue.severity === "error" ? chalk3.red("\u2717") : chalk3.yellow("\u26A0");
1201
1201
  console.log(` ${icon} [${issue.type}] ${issue.details}`);
1202
1202
  if (issue.repairAction) {
1203
- console.log(chalk2.gray(` \u2192 ${issue.repairAction.description}`));
1203
+ console.log(chalk3.gray(` \u2192 ${issue.repairAction.description}`));
1204
1204
  }
1205
1205
  }
1206
1206
  if (issues.length > 10) {
1207
- console.log(chalk2.gray(` ... and ${issues.length - 10} more`));
1207
+ console.log(chalk3.gray(` ... and ${issues.length - 10} more`));
1208
1208
  }
1209
1209
  }
1210
1210
  if (report.repairable) {
1211
1211
  console.log();
1212
- console.log(chalk2.cyan(` Run \`xnet repair\` to fix these issues.`));
1212
+ console.log(chalk3.cyan(` Run \`xnet repair\` to fix these issues.`));
1213
1213
  }
1214
1214
  }
1215
1215
  }
@@ -1246,8 +1246,8 @@ function generateDemoChanges() {
1246
1246
  ];
1247
1247
  }
1248
1248
  async function repairCommand(options) {
1249
- const chalk2 = await getChalk();
1250
- console.log(chalk2.bold("\nxNet Data Repair\n"));
1249
+ const chalk3 = await getChalk();
1250
+ console.log(chalk3.bold("\nxNet Data Repair\n"));
1251
1251
  try {
1252
1252
  let dataDir;
1253
1253
  let changes = [];
@@ -1255,15 +1255,15 @@ async function repairCommand(options) {
1255
1255
  dataDir = findDataDir(options.dataDir);
1256
1256
  changes = loadChangesFromDir(dataDir);
1257
1257
  } catch {
1258
- console.log(chalk2.yellow("Note: No data directory found."));
1259
- console.log(chalk2.gray("Using demo data for illustration.\n"));
1258
+ console.log(chalk3.yellow("Note: No data directory found."));
1259
+ console.log(chalk3.gray("Using demo data for illustration.\n"));
1260
1260
  dataDir = "";
1261
1261
  changes = generateDemoChanges();
1262
1262
  }
1263
- console.log(chalk2.gray("Running integrity check..."));
1263
+ console.log(chalk3.gray("Running integrity check..."));
1264
1264
  const report = await verifyIntegrity(changes);
1265
1265
  if (report.issues.length === 0) {
1266
- console.log(chalk2.green("\n\u2713 No issues found. Nothing to repair."));
1266
+ console.log(chalk3.green("\n\u2713 No issues found. Nothing to repair."));
1267
1267
  return;
1268
1268
  }
1269
1269
  console.log(`
@@ -1272,7 +1272,7 @@ Found ${report.issues.length} issues:`);
1272
1272
  console.log(` - ${report.summary.warnings} warnings`);
1273
1273
  console.log();
1274
1274
  if (options.dryRun) {
1275
- console.log(chalk2.yellow("DRY RUN - No changes will be made\n"));
1275
+ console.log(chalk3.yellow("DRY RUN - No changes will be made\n"));
1276
1276
  }
1277
1277
  const { remainingIssues, repairCount } = await attemptRepair(changes, report.issues);
1278
1278
  if (options.json) {
@@ -1290,33 +1290,33 @@ Found ${report.issues.length} issues:`);
1290
1290
  );
1291
1291
  return;
1292
1292
  }
1293
- console.log(chalk2.bold("Repair results:"));
1294
- console.log(` ${chalk2.green("\u2713")} Repaired: ${repairCount} issues`);
1295
- console.log(` ${chalk2.yellow("!")} Remaining: ${remainingIssues.length} issues`);
1293
+ console.log(chalk3.bold("Repair results:"));
1294
+ console.log(` ${chalk3.green("\u2713")} Repaired: ${repairCount} issues`);
1295
+ console.log(` ${chalk3.yellow("!")} Remaining: ${remainingIssues.length} issues`);
1296
1296
  if (remainingIssues.length > 0) {
1297
1297
  console.log();
1298
- console.log(chalk2.yellow("Issues that require manual intervention:"));
1298
+ console.log(chalk3.yellow("Issues that require manual intervention:"));
1299
1299
  for (const issue of remainingIssues.slice(0, 5)) {
1300
1300
  console.log(` - [${issue.type}] ${issue.details}`);
1301
1301
  }
1302
1302
  if (remainingIssues.length > 5) {
1303
- console.log(chalk2.gray(` ... and ${remainingIssues.length - 5} more`));
1303
+ console.log(chalk3.gray(` ... and ${remainingIssues.length - 5} more`));
1304
1304
  }
1305
1305
  }
1306
1306
  if (!options.dryRun && dataDir && repairCount > 0) {
1307
1307
  console.log();
1308
- console.log(chalk2.gray("Note: Writing repaired changes requires NodeStore integration."));
1309
- console.log(chalk2.gray("Changes are prepared but not written to disk."));
1308
+ console.log(chalk3.gray("Note: Writing repaired changes requires NodeStore integration."));
1309
+ console.log(chalk3.gray("Changes are prepared but not written to disk."));
1310
1310
  }
1311
1311
  console.log();
1312
1312
  } catch (error) {
1313
- console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1313
+ console.error(chalk3.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1314
1314
  process.exit(1);
1315
1315
  }
1316
1316
  }
1317
1317
  async function exportCommand(options) {
1318
- const chalk2 = await getChalk();
1319
- console.log(chalk2.bold("\nxNet Data Export\n"));
1318
+ const chalk3 = await getChalk();
1319
+ console.log(chalk3.bold("\nxNet Data Export\n"));
1320
1320
  try {
1321
1321
  let dataDir;
1322
1322
  let changes = [];
@@ -1324,14 +1324,14 @@ async function exportCommand(options) {
1324
1324
  dataDir = findDataDir(options.dataDir);
1325
1325
  changes = loadChangesFromDir(dataDir);
1326
1326
  } catch {
1327
- console.log(chalk2.yellow("Note: No data directory found."));
1328
- console.log(chalk2.gray("Using demo data for illustration.\n"));
1327
+ console.log(chalk3.yellow("Note: No data directory found."));
1328
+ console.log(chalk3.gray("Using demo data for illustration.\n"));
1329
1329
  dataDir = "";
1330
1330
  changes = generateDemoChanges();
1331
1331
  }
1332
1332
  const outputPath = resolve6(process.cwd(), options.output);
1333
- console.log(chalk2.gray(`Source: ${dataDir || "demo data"}`));
1334
- console.log(chalk2.gray(`Output: ${outputPath}`));
1333
+ console.log(chalk3.gray(`Source: ${dataDir || "demo data"}`));
1334
+ console.log(chalk3.gray(`Output: ${outputPath}`));
1335
1335
  console.log();
1336
1336
  const exportData = {
1337
1337
  version: 1,
@@ -1356,26 +1356,26 @@ async function exportCommand(options) {
1356
1356
  } else {
1357
1357
  writeFileSync3(outputPath, content, "utf-8");
1358
1358
  }
1359
- console.log(chalk2.green(`\u2713 Exported ${changes.length} changes`));
1360
- console.log(chalk2.gray(` File size: ${Math.round(content.length / 1024)}KB`));
1359
+ console.log(chalk3.green(`\u2713 Exported ${changes.length} changes`));
1360
+ console.log(chalk3.gray(` File size: ${Math.round(content.length / 1024)}KB`));
1361
1361
  console.log();
1362
1362
  } catch (error) {
1363
- console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1363
+ console.error(chalk3.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1364
1364
  process.exit(1);
1365
1365
  }
1366
1366
  }
1367
1367
  async function importCommand(options) {
1368
- const chalk2 = await getChalk();
1369
- console.log(chalk2.bold("\nxNet Data Import\n"));
1368
+ const chalk3 = await getChalk();
1369
+ console.log(chalk3.bold("\nxNet Data Import\n"));
1370
1370
  try {
1371
1371
  const inputPath = resolve6(process.cwd(), options.input);
1372
1372
  if (!existsSync(inputPath)) {
1373
- console.error(chalk2.red(`Error: Input file not found: ${inputPath}`));
1373
+ console.error(chalk3.red(`Error: Input file not found: ${inputPath}`));
1374
1374
  process.exit(1);
1375
1375
  }
1376
- console.log(chalk2.gray(`Input: ${inputPath}`));
1376
+ console.log(chalk3.gray(`Input: ${inputPath}`));
1377
1377
  if (options.dryRun) {
1378
- console.log(chalk2.yellow("\nDRY RUN - No changes will be applied\n"));
1378
+ console.log(chalk3.yellow("\nDRY RUN - No changes will be applied\n"));
1379
1379
  }
1380
1380
  const content = readFileSync(inputPath, "utf-8");
1381
1381
  let importData;
@@ -1390,43 +1390,43 @@ async function importCommand(options) {
1390
1390
  const lines = content.split("\n").filter((l) => l.trim());
1391
1391
  importData = { changes: lines.map((l) => JSON.parse(l)) };
1392
1392
  }
1393
- console.log(chalk2.gray(`Found ${importData.changes.length} changes to import`));
1393
+ console.log(chalk3.gray(`Found ${importData.changes.length} changes to import`));
1394
1394
  console.log();
1395
1395
  const changes = importData.changes.map((c) => ({
1396
1396
  ...c,
1397
1397
  signature: typeof c.signature === "string" ? new Uint8Array(Buffer.from(c.signature, "base64")) : c.signature
1398
1398
  }));
1399
- console.log(chalk2.bold("Verifying import data..."));
1399
+ console.log(chalk3.bold("Verifying import data..."));
1400
1400
  const report = await quickIntegrityCheck(changes);
1401
1401
  if (report.issues.length > 0) {
1402
- console.log(chalk2.yellow(` \u26A0 ${report.issues.length} integrity issues found`));
1402
+ console.log(chalk3.yellow(` \u26A0 ${report.issues.length} integrity issues found`));
1403
1403
  if (report.summary.errors > 0) {
1404
- console.log(chalk2.red(` ${report.summary.errors} errors may prevent import`));
1404
+ console.log(chalk3.red(` ${report.summary.errors} errors may prevent import`));
1405
1405
  }
1406
1406
  } else {
1407
- console.log(chalk2.green(" \u2713 All changes verified"));
1407
+ console.log(chalk3.green(" \u2713 All changes verified"));
1408
1408
  }
1409
1409
  if (options.applyMigrations) {
1410
1410
  console.log();
1411
- console.log(chalk2.bold("Checking for required migrations..."));
1412
- console.log(chalk2.gray(" (Migration detection requires schema registry)"));
1411
+ console.log(chalk3.bold("Checking for required migrations..."));
1412
+ console.log(chalk3.gray(" (Migration detection requires schema registry)"));
1413
1413
  }
1414
1414
  console.log();
1415
- console.log(chalk2.bold("Import summary:"));
1415
+ console.log(chalk3.bold("Import summary:"));
1416
1416
  console.log(` Changes: ${changes.length}`);
1417
1417
  console.log(` Roots: ${findRoots(changes).length}`);
1418
1418
  console.log(` Heads: ${findHeads(changes).length}`);
1419
1419
  console.log(` Chain depth: ${getChainDepth(changes)}`);
1420
1420
  if (!options.dryRun) {
1421
1421
  console.log();
1422
- console.log(chalk2.gray("Note: Writing imported changes requires NodeStore integration."));
1423
- console.log(chalk2.gray("Changes are validated but not written to storage."));
1422
+ console.log(chalk3.gray("Note: Writing imported changes requires NodeStore integration."));
1423
+ console.log(chalk3.gray("Changes are validated but not written to storage."));
1424
1424
  }
1425
1425
  console.log();
1426
- console.log(chalk2.green("\u2713 Import validation complete"));
1426
+ console.log(chalk3.green("\u2713 Import validation complete"));
1427
1427
  console.log();
1428
1428
  } catch (error) {
1429
- console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1429
+ console.error(chalk3.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1430
1430
  process.exit(1);
1431
1431
  }
1432
1432
  }
@@ -1755,8 +1755,8 @@ import { writeFileSync as writeFileSync4, readFileSync as readFileSync2, existsS
1755
1755
  import { resolve as resolve7 } from "path";
1756
1756
  async function getChalk2() {
1757
1757
  try {
1758
- const chalk2 = await import("chalk");
1759
- return chalk2.default;
1758
+ const chalk3 = await import("chalk");
1759
+ return chalk3.default;
1760
1760
  } catch {
1761
1761
  const identity = (s) => s;
1762
1762
  return {
@@ -1805,7 +1805,7 @@ function findSchema(schemaIRI, schemas) {
1805
1805
  return null;
1806
1806
  }
1807
1807
  async function analyzeCommand(options) {
1808
- const chalk2 = await getChalk2();
1808
+ const chalk3 = await getChalk2();
1809
1809
  try {
1810
1810
  const fromParsed = parseSchemaIRI(options.from);
1811
1811
  const toParsed = parseSchemaIRI(options.to);
@@ -1816,8 +1816,8 @@ async function analyzeCommand(options) {
1816
1816
  const fromSchema = findSchema(options.from, schemas);
1817
1817
  const toSchema = findSchema(options.to, schemas);
1818
1818
  if (!fromSchema || !toSchema) {
1819
- console.log(chalk2.yellow("\nNote: Schema file not provided. Showing example output.\n"));
1820
- console.log(chalk2.gray("Use --schema-file to load actual schemas from a JSON file.\n"));
1819
+ console.log(chalk3.yellow("\nNote: Schema file not provided. Showing example output.\n"));
1820
+ console.log(chalk3.gray("Use --schema-file to load actual schemas from a JSON file.\n"));
1821
1821
  const mockFromSchema = {
1822
1822
  "@id": `xnet://xnet.fyi/${fromParsed.name}@${fromParsed.version}`,
1823
1823
  "@type": "xnet://xnet.fyi/Schema",
@@ -1846,72 +1846,72 @@ async function analyzeCommand(options) {
1846
1846
  { "@id": `#priority`, name: "priority", type: "text", required: true }
1847
1847
  ]
1848
1848
  };
1849
- printAnalysisResult(diffSchemas(mockFromSchema, mockToSchema), options.json, chalk2);
1849
+ printAnalysisResult(diffSchemas(mockFromSchema, mockToSchema), options.json, chalk3);
1850
1850
  return;
1851
1851
  }
1852
1852
  const diff = diffSchemas(fromSchema, toSchema);
1853
- printAnalysisResult(diff, options.json, chalk2);
1853
+ printAnalysisResult(diff, options.json, chalk3);
1854
1854
  } catch (error) {
1855
- console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1855
+ console.error(chalk3.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1856
1856
  process.exit(1);
1857
1857
  }
1858
1858
  }
1859
- function printAnalysisResult(diff, json, chalk2) {
1859
+ function printAnalysisResult(diff, json, chalk3) {
1860
1860
  if (json) {
1861
1861
  console.log(JSON.stringify(diff, null, 2));
1862
1862
  return;
1863
1863
  }
1864
- console.log(chalk2.bold(`
1864
+ console.log(chalk3.bold(`
1865
1865
  Schema changes: ${diff.fromVersion} \u2192 ${diff.toVersion}
1866
1866
  `));
1867
1867
  if (diff.changes.length === 0) {
1868
- console.log(chalk2.green("No changes detected."));
1868
+ console.log(chalk3.green("No changes detected."));
1869
1869
  return;
1870
1870
  }
1871
1871
  const breaking = diff.changes.filter((c) => c.risk === "breaking");
1872
1872
  const caution = diff.changes.filter((c) => c.risk === "caution");
1873
1873
  const safe = diff.changes.filter((c) => c.risk === "safe");
1874
1874
  if (breaking.length > 0) {
1875
- console.log(chalk2.red(chalk2.bold("BREAKING CHANGES:")));
1875
+ console.log(chalk3.red(chalk3.bold("BREAKING CHANGES:")));
1876
1876
  for (const change of breaking) {
1877
- console.log(chalk2.red(` - ${change.type.toUpperCase()}: ${change.description}`));
1877
+ console.log(chalk3.red(` - ${change.type.toUpperCase()}: ${change.description}`));
1878
1878
  }
1879
1879
  console.log();
1880
1880
  }
1881
1881
  if (caution.length > 0) {
1882
- console.log(chalk2.yellow(chalk2.bold("CAUTION:")));
1882
+ console.log(chalk3.yellow(chalk3.bold("CAUTION:")));
1883
1883
  for (const change of caution) {
1884
- console.log(chalk2.yellow(` - ${change.type.toUpperCase()}: ${change.description}`));
1884
+ console.log(chalk3.yellow(` - ${change.type.toUpperCase()}: ${change.description}`));
1885
1885
  }
1886
1886
  console.log();
1887
1887
  }
1888
1888
  if (safe.length > 0) {
1889
- console.log(chalk2.green(chalk2.bold("SAFE:")));
1889
+ console.log(chalk3.green(chalk3.bold("SAFE:")));
1890
1890
  for (const change of safe) {
1891
- console.log(chalk2.green(` - ${change.type.toUpperCase()}: ${change.description}`));
1891
+ console.log(chalk3.green(` - ${change.type.toUpperCase()}: ${change.description}`));
1892
1892
  }
1893
1893
  console.log();
1894
1894
  }
1895
- console.log(chalk2.bold("Summary:"));
1895
+ console.log(chalk3.bold("Summary:"));
1896
1896
  console.log(
1897
- ` ${chalk2.red(`${diff.summary.breaking} breaking`)} | ${chalk2.yellow(`${diff.summary.caution} caution`)} | ${chalk2.green(`${diff.summary.safe} safe`)}`
1897
+ ` ${chalk3.red(`${diff.summary.breaking} breaking`)} | ${chalk3.yellow(`${diff.summary.caution} caution`)} | ${chalk3.green(`${diff.summary.safe} safe`)}`
1898
1898
  );
1899
1899
  console.log();
1900
1900
  if (diff.changes.some((c) => c.suggestedLens)) {
1901
- console.log(chalk2.bold("Suggested lens:"));
1902
- console.log(chalk2.dim("\u2500".repeat(40)));
1901
+ console.log(chalk3.bold("Suggested lens:"));
1902
+ console.log(chalk3.dim("\u2500".repeat(40)));
1903
1903
  console.log(generateLensSnippet(diff));
1904
- console.log(chalk2.dim("\u2500".repeat(40)));
1904
+ console.log(chalk3.dim("\u2500".repeat(40)));
1905
1905
  console.log();
1906
1906
  }
1907
1907
  if (diff.autoMigratable) {
1908
- console.log(chalk2.green("\u2713 Automatic migration possible"));
1908
+ console.log(chalk3.green("\u2713 Automatic migration possible"));
1909
1909
  } else {
1910
- console.log(chalk2.yellow("\u26A0 Manual intervention required for some changes"));
1910
+ console.log(chalk3.yellow("\u26A0 Manual intervention required for some changes"));
1911
1911
  }
1912
1912
  }
1913
1913
  async function generateCommand(options) {
1914
- const chalk2 = await getChalk2();
1914
+ const chalk3 = await getChalk2();
1915
1915
  try {
1916
1916
  const fromParsed = parseSchemaIRI(options.from);
1917
1917
  const toParsed = parseSchemaIRI(options.to);
@@ -1924,7 +1924,7 @@ async function generateCommand(options) {
1924
1924
  const from = fromSchema ?? createMockSchema(fromParsed.name, fromParsed.version, "from");
1925
1925
  const to = toSchema ?? createMockSchema(toParsed.name, toParsed.version, "to");
1926
1926
  if (!fromSchema || !toSchema) {
1927
- console.log(chalk2.yellow("\nNote: Using mock schemas for demonstration.\n"));
1927
+ console.log(chalk3.yellow("\nNote: Using mock schemas for demonstration.\n"));
1928
1928
  }
1929
1929
  const diff = diffSchemas(from, to);
1930
1930
  const sourceIRI = from["@id"];
@@ -1939,24 +1939,24 @@ async function generateCommand(options) {
1939
1939
  if (options.output) {
1940
1940
  const outputPath = resolve7(process.cwd(), options.output);
1941
1941
  if (existsSync2(outputPath) && !options.force) {
1942
- console.error(chalk2.red(`Error: File already exists: ${outputPath}`));
1943
- console.error(chalk2.gray("Use --force to overwrite."));
1942
+ console.error(chalk3.red(`Error: File already exists: ${outputPath}`));
1943
+ console.error(chalk3.gray("Use --force to overwrite."));
1944
1944
  process.exit(1);
1945
1945
  }
1946
1946
  writeFileSync4(outputPath, generated.code, "utf-8");
1947
- console.log(chalk2.green(`
1947
+ console.log(chalk3.green(`
1948
1948
  \u2713 Generated lens written to: ${outputPath}`));
1949
1949
  } else {
1950
1950
  console.log(generated.code);
1951
1951
  }
1952
1952
  if (!generated.isComplete) {
1953
- console.log(chalk2.yellow("\n\u26A0 Manual completion required:"));
1953
+ console.log(chalk3.yellow("\n\u26A0 Manual completion required:"));
1954
1954
  for (const item of generated.manualItems) {
1955
- console.log(chalk2.yellow(` - ${item}`));
1955
+ console.log(chalk3.yellow(` - ${item}`));
1956
1956
  }
1957
1957
  }
1958
1958
  } catch (error) {
1959
- console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1959
+ console.error(chalk3.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1960
1960
  process.exit(1);
1961
1961
  }
1962
1962
  }
@@ -1994,43 +1994,43 @@ function createMockSchema(name, version, type) {
1994
1994
  }
1995
1995
  }
1996
1996
  async function runCommand(options) {
1997
- const chalk2 = await getChalk2();
1997
+ const chalk3 = await getChalk2();
1998
1998
  if (!options.dryRun && !options.apply) {
1999
- console.error(chalk2.red("Error: Must specify either --dry-run or --apply"));
1999
+ console.error(chalk3.red("Error: Must specify either --dry-run or --apply"));
2000
2000
  process.exit(1);
2001
2001
  }
2002
2002
  if (options.dryRun && options.apply) {
2003
- console.error(chalk2.red("Error: Cannot specify both --dry-run and --apply"));
2003
+ console.error(chalk3.red("Error: Cannot specify both --dry-run and --apply"));
2004
2004
  process.exit(1);
2005
2005
  }
2006
2006
  try {
2007
- console.log(chalk2.blue(`
2007
+ console.log(chalk3.blue(`
2008
2008
  Migration: ${options.from} \u2192 ${options.to}
2009
2009
  `));
2010
2010
  if (options.dryRun) {
2011
- console.log(chalk2.yellow("DRY RUN - No changes will be applied\n"));
2012
- console.log(chalk2.gray("Would migrate nodes:"));
2013
- console.log(chalk2.gray(" - Scan data directory for nodes with schema " + options.from));
2014
- console.log(chalk2.gray(" - Apply migration lens to each node"));
2015
- console.log(chalk2.gray(" - Update schema version to " + options.to));
2011
+ console.log(chalk3.yellow("DRY RUN - No changes will be applied\n"));
2012
+ console.log(chalk3.gray("Would migrate nodes:"));
2013
+ console.log(chalk3.gray(" - Scan data directory for nodes with schema " + options.from));
2014
+ console.log(chalk3.gray(" - Apply migration lens to each node"));
2015
+ console.log(chalk3.gray(" - Update schema version to " + options.to));
2016
2016
  console.log();
2017
- console.log(chalk2.dim("Note: Full migration requires --data-dir and --lens-file options"));
2018
- console.log(chalk2.dim(" or a configured NodeStore with registered lenses."));
2017
+ console.log(chalk3.dim("Note: Full migration requires --data-dir and --lens-file options"));
2018
+ console.log(chalk3.dim(" or a configured NodeStore with registered lenses."));
2019
2019
  } else {
2020
- console.log(chalk2.yellow("APPLY MODE - Changes will be written\n"));
2021
- console.log(chalk2.gray("Would apply migration:"));
2022
- console.log(chalk2.gray(" - Load nodes with schema " + options.from));
2023
- console.log(chalk2.gray(" - Transform using migration lens"));
2024
- console.log(chalk2.gray(" - Write updated nodes with schema " + options.to));
2020
+ console.log(chalk3.yellow("APPLY MODE - Changes will be written\n"));
2021
+ console.log(chalk3.gray("Would apply migration:"));
2022
+ console.log(chalk3.gray(" - Load nodes with schema " + options.from));
2023
+ console.log(chalk3.gray(" - Transform using migration lens"));
2024
+ console.log(chalk3.gray(" - Write updated nodes with schema " + options.to));
2025
2025
  console.log();
2026
- console.log(chalk2.dim("Note: Full migration requires --data-dir and --lens-file options"));
2027
- console.log(chalk2.dim(" or a configured NodeStore with registered lenses."));
2026
+ console.log(chalk3.dim("Note: Full migration requires --data-dir and --lens-file options"));
2027
+ console.log(chalk3.dim(" or a configured NodeStore with registered lenses."));
2028
2028
  }
2029
2029
  console.log();
2030
- console.log(chalk2.green("\u2713 Migration command parsed successfully"));
2031
- console.log(chalk2.gray(" Full implementation requires NodeStore integration."));
2030
+ console.log(chalk3.green("\u2713 Migration command parsed successfully"));
2031
+ console.log(chalk3.gray(" Full implementation requires NodeStore integration."));
2032
2032
  } catch (error) {
2033
- console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
2033
+ console.error(chalk3.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
2034
2034
  process.exit(1);
2035
2035
  }
2036
2036
  }
@@ -2047,6 +2047,78 @@ function registerMigrateCommand(program2) {
2047
2047
  });
2048
2048
  }
2049
2049
 
2050
+ // src/commands/publish.ts
2051
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
2052
+ import { dirname as dirname4, join as join8, resolve as resolve8 } from "path";
2053
+ import { buildStaticSite } from "@xnetjs/publish";
2054
+ import chalk2 from "chalk";
2055
+ function assertPublication(value) {
2056
+ if (typeof value !== "object" || value === null) {
2057
+ throw new Error("publication file must be a JSON object");
2058
+ }
2059
+ const file = value;
2060
+ if (!file.meta || typeof file.meta.siteUrl !== "string" || typeof file.meta.title !== "string") {
2061
+ throw new Error("publication file needs `meta.siteUrl` and `meta.title`");
2062
+ }
2063
+ if (!Array.isArray(file.posts)) throw new Error("publication file needs a `posts` array");
2064
+ for (const [i, post] of file.posts.entries()) {
2065
+ if (typeof post?.slug !== "string" || post.slug === "") {
2066
+ throw new Error(`posts[${i}] needs a non-empty \`slug\``);
2067
+ }
2068
+ if (typeof post.title !== "string") throw new Error(`posts[${i}] needs a \`title\``);
2069
+ if (typeof post.html !== "string") {
2070
+ throw new Error(`posts[${i}] needs rendered \`html\` (see renderPost() in @xnetjs/publish)`);
2071
+ }
2072
+ }
2073
+ }
2074
+ function registerPublishCommand(program2) {
2075
+ const publish = program2.command("publish").description("Render a publication to a static site (HTML, RSS, sitemap)");
2076
+ publish.command("static", { isDefault: true }).description("Write a self-contained static site to a directory").requiredOption("--input <path>", "Publication JSON (meta + posts with rendered html)").option("--out <dir>", "Output directory", "dist/publish").option("--dry-run", "List the files that would be written without writing them").action(async (opts) => {
2077
+ const inputPath = resolve8(opts.input);
2078
+ const outDir = resolve8(opts.out);
2079
+ let parsed;
2080
+ try {
2081
+ parsed = JSON.parse(await readFile4(inputPath, "utf8"));
2082
+ } catch (error) {
2083
+ console.error(chalk2.red(`Could not read ${inputPath}: ${error.message}`));
2084
+ process.exitCode = 1;
2085
+ return;
2086
+ }
2087
+ let file;
2088
+ try {
2089
+ assertPublication(parsed);
2090
+ file = parsed;
2091
+ } catch (error) {
2092
+ console.error(chalk2.red(error.message));
2093
+ process.exitCode = 1;
2094
+ return;
2095
+ }
2096
+ const site = buildStaticSite({
2097
+ meta: file.meta,
2098
+ posts: file.posts,
2099
+ css: file.css,
2100
+ head: file.head
2101
+ });
2102
+ const published = file.posts.filter((p) => Boolean(p.publishedAt)).length;
2103
+ const drafts = file.posts.length - published;
2104
+ if (opts.dryRun) {
2105
+ for (const path of [...site.keys()].sort()) console.log(join8(outDir, path));
2106
+ console.log(chalk2.dim(`${site.size} file(s), not written (--dry-run)`));
2107
+ return;
2108
+ }
2109
+ for (const [path, contents] of site) {
2110
+ const target = join8(outDir, path);
2111
+ await mkdir4(dirname4(target), { recursive: true });
2112
+ await writeFile3(target, contents, "utf8");
2113
+ }
2114
+ console.log(chalk2.green(`Published ${published} post(s) to ${outDir}`));
2115
+ if (drafts > 0) {
2116
+ console.log(chalk2.dim(`${drafts} draft(s) skipped (no publishedAt)`));
2117
+ }
2118
+ console.log(chalk2.dim("Serve this directory from any static host \u2014 no hub required."));
2119
+ });
2120
+ }
2121
+
2050
2122
  // src/commands/schema.ts
2051
2123
  import { writeFileSync as writeFileSync5, readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
2052
2124
  function extractSchemas() {
@@ -2210,4 +2282,5 @@ registerCodeCommand(program);
2210
2282
  registerDataCommand(program);
2211
2283
  registerPluginCommand(program);
2212
2284
  registerConnectorCommand(program);
2285
+ registerPublishCommand(program);
2213
2286
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xnetjs/cli",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "xNet CLI - Schema migrations, diagnostics, and development tools",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -15,8 +15,8 @@
15
15
  },
16
16
  "exports": {
17
17
  ".": {
18
- "import": "./dist/index.js",
19
- "types": "./dist/index.d.ts"
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
20
  }
21
21
  },
22
22
  "files": [
@@ -31,18 +31,20 @@
31
31
  "dependencies": {
32
32
  "chalk": "^5.3.0",
33
33
  "commander": "^12.0.0",
34
- "@xnetjs/core": "2.5.0",
35
- "@xnetjs/crypto": "2.5.0",
36
- "@xnetjs/data": "2.5.0",
37
- "@xnetjs/devkit": "1.0.0",
38
- "@xnetjs/identity": "2.5.0",
39
- "@xnetjs/plugins": "2.5.0",
40
- "@xnetjs/runtime": "0.5.5",
41
- "@xnetjs/sqlite": "2.5.0",
42
- "@xnetjs/sync": "2.5.0"
34
+ "@xnetjs/core": "3.0.0",
35
+ "@xnetjs/crypto": "3.0.0",
36
+ "@xnetjs/data": "3.0.0",
37
+ "@xnetjs/devkit": "1.0.1",
38
+ "@xnetjs/identity": "3.0.0",
39
+ "@xnetjs/plugins": "3.0.0",
40
+ "@xnetjs/publish": "0.1.0",
41
+ "@xnetjs/runtime": "0.6.0",
42
+ "@xnetjs/sqlite": "3.0.0",
43
+ "@xnetjs/sync": "3.0.0"
43
44
  },
44
45
  "devDependencies": {
45
46
  "tsup": "^8.0.0",
47
+ "yjs": "^13.6.0",
46
48
  "tsx": "^4.0.0",
47
49
  "typescript": "^5.4.0",
48
50
  "vitest": "^4.0.0"