wattetheria 0.3.2 → 0.3.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.
package/.env.release CHANGED
@@ -1,5 +1,5 @@
1
1
  # Coordinated release image set
2
- RELEASE_TAG=1.0.3
2
+ RELEASE_TAG=1.0.4
3
3
 
4
4
  WATTETHERIA_KERNEL_IMAGE=ghcr.io/wattetheria/wattetheria-kernel:${RELEASE_TAG}
5
5
  WATTSWARM_KERNEL_IMAGE=ghcr.io/wattetheria/wattswarm-kernel:${RELEASE_TAG}
package/README.md CHANGED
@@ -556,9 +556,11 @@ Agent commands such as `identity`, `wallet`, and `servicenet` are forwarded to t
556
556
  native `wattetheria-client-cli`; installed release packages should not require Rust on the user's
557
557
  machine. The JS wrapper resolves `WATTETHERIA_CLI_BIN` first, then the matching optional native
558
558
  package such as `@wattetheria/cli-win32-x64`, then `bin/native/<platform>-<arch>/`, then `PATH`.
559
- If no host native CLI is available but the local Wattetheria node deployment is installed, the
560
- wrapper runs the same command inside the `kernel` container using the node's
561
- `/var/lib/wattetheria` data directory.
559
+ `identity` and `wallet` are lightweight local setup commands for ServiceNet publishing and wallet
560
+ binding when no local Wattetheria node identity or wallet exists. If a local node already has
561
+ identity or wallet state, the wrapper refuses to create a separate local identity or wallet through
562
+ those setup commands. ServiceNet commands can still run through an installed node when no host
563
+ native CLI is available.
562
564
 
563
565
  NPM publish flow:
564
566
 
@@ -583,14 +585,16 @@ CLI saves the provider/card context locally:
583
585
  ```bash
584
586
  npx wattetheria servicenet agent-card init
585
587
  # or write the template into a specific directory:
586
- npx wattetheria servicenet agent-card init --out ./agents/hermes
588
+ npx wattetheria servicenet agent-card init --out <output-directory>
587
589
 
588
- npx wattetheria servicenet provider register --card ./agent-card.json
590
+ npx wattetheria servicenet register
591
+ # If the card is not in the current directory:
592
+ npx wattetheria servicenet register --card <path-to-agent-card.json>
589
593
  ```
590
594
 
591
595
  Then publish through the ServiceNet business command with the returned agent id. The publish
592
- command reads the saved provider id, ServiceNet URL, endpoint URL, and card path from local
593
- context instead of asking for repeated flags:
596
+ command reads the saved provider id, endpoint URL, and card path from local context instead of
597
+ asking for repeated flags:
594
598
 
595
599
  ```bash
596
600
  npx wattetheria servicenet publish <agent-id>
package/lib/cli.js CHANGED
@@ -73,10 +73,25 @@ Commands:
73
73
  doctor Check local prerequisites
74
74
  help Show this help
75
75
 
76
- Agent subcommands (forwarded to the bundled native CLI when available):
77
- identity init | show | export-seed
78
- wallet manage wallet payment accounts
79
- servicenet agent-card init | provider register | publish <agent-id>
76
+ Agent subcommands:
77
+ identity
78
+ init Initialize a lightweight local identity for ServiceNet publishing or wallet binding
79
+ show Show the local identity public DID and public key
80
+ export-seed Export the local identity seed; treat it like a password
81
+
82
+ wallet
83
+ create-payment-account Create a local payment account for ServiceNet payment binding
84
+ import-payment-account Import a local payment account for ServiceNet payment binding
85
+ watch-payment-account Track a payment address without importing its private key
86
+ list-payment-accounts List local payment accounts
87
+ bind-payment-account Select the active local payment account
88
+ active-payment-account Show the active local payment account
89
+
90
+ servicenet
91
+ agent-card init Generate an editable agent-card.json template in the current directory
92
+ register --card <path-to-agent-card.json>
93
+ Register the agent card and local identity with ServiceNet; returns agent_id and provider_id
94
+ publish <agent-id> Publish a registered ServiceNet agent using the agent_id returned by register
80
95
 
81
96
  Options:
82
97
  --version, -v Alias for \`version\`
@@ -94,6 +109,15 @@ Options:
94
109
  `);
95
110
  }
96
111
 
112
+ function throwCommandSuggestion(argv) {
113
+ if (argv[0] === "provider" && argv[1] === "register") {
114
+ throw new Error("Unknown command: provider register. Use `wattetheria servicenet register`.");
115
+ }
116
+ if (argv[0] === "register") {
117
+ throw new Error("Unknown command: register. Use `wattetheria servicenet register`.");
118
+ }
119
+ }
120
+
97
121
  function parseArgs(argv) {
98
122
  let command = DEFAULT_COMMAND;
99
123
  let index = 0;
@@ -1291,11 +1315,25 @@ function bundledRustBinaryPath() {
1291
1315
 
1292
1316
  function missingNativeCliError(commandName) {
1293
1317
  const platformKey = nativePlatformKey() || `${process.platform}-${process.arch}`;
1318
+ if (commandName === "identity" || commandName === "wallet") {
1319
+ return new Error(
1320
+ [
1321
+ `Cannot run '${commandName}' because the Wattetheria native CLI for ${platformKey} was not found.`,
1322
+ "",
1323
+ "`wattetheria identity` and `wattetheria wallet` are lightweight local setup commands",
1324
+ "for ServiceNet publishing and wallet binding. They do not require a local Wattetheria",
1325
+ "node, but they do require the native CLI package for this system.",
1326
+ "",
1327
+ "Install the matching Wattetheria native CLI package, then retry."
1328
+ ].join("\n")
1329
+ );
1330
+ }
1331
+
1294
1332
  const deployment = deploymentState();
1295
1333
  if (deployment.installed) {
1296
1334
  return new Error(
1297
1335
  `Wattetheria node deployment was found at ${deployment.dir}, but this npm package does not include ` +
1298
- `the native publisher CLI for ${platformKey}. ` +
1336
+ `the native CLI for ${platformKey}. ` +
1299
1337
  `Run 'npx wattetheria status' to check the installed node, or update the local node image.`
1300
1338
  );
1301
1339
  }
@@ -1306,6 +1344,103 @@ function missingNativeCliError(commandName) {
1306
1344
  );
1307
1345
  }
1308
1346
 
1347
+ function forwardedSubcommand(rawArgv) {
1348
+ for (let index = 1; index < rawArgv.length; index += 1) {
1349
+ const arg = rawArgv[index];
1350
+ if (arg.startsWith("-")) {
1351
+ if (!arg.includes("=")) {
1352
+ index += 1;
1353
+ }
1354
+ continue;
1355
+ }
1356
+ return arg;
1357
+ }
1358
+ return "";
1359
+ }
1360
+
1361
+ function isIdentityWriteCommand(commandName, rawArgv) {
1362
+ return commandName === "identity" && forwardedSubcommand(rawArgv) === "init";
1363
+ }
1364
+
1365
+ function isWalletWriteCommand(commandName, rawArgv) {
1366
+ if (commandName !== "wallet") {
1367
+ return false;
1368
+ }
1369
+ return new Set([
1370
+ "create-payment-account",
1371
+ "import-payment-account",
1372
+ "watch-payment-account",
1373
+ "bind-payment-account"
1374
+ ]).has(forwardedSubcommand(rawArgv));
1375
+ }
1376
+
1377
+ function installedNodeArtifacts(deployment = deploymentState()) {
1378
+ return {
1379
+ identityPath: path.join(deployment.stateDir, "identity.json"),
1380
+ walletMetadataPath: path.join(deployment.stateDir, ".watt-wallet", "metadata.json"),
1381
+ walletKeystorePath: path.join(deployment.stateDir, ".watt-wallet", "keystore.json")
1382
+ };
1383
+ }
1384
+
1385
+ function explicitDataDir(rawArgv) {
1386
+ const option = parsePathOption(rawArgv, "--data-dir", 1);
1387
+ if (!option) {
1388
+ return "";
1389
+ }
1390
+ if (!option.value) {
1391
+ throw new Error("Missing value for --data-dir");
1392
+ }
1393
+ return path.resolve(option.value);
1394
+ }
1395
+
1396
+ function usesSeparateDataDir(rawArgv, deployment) {
1397
+ const dataDir = explicitDataDir(rawArgv);
1398
+ return Boolean(dataDir) && dataDir !== path.resolve(deployment.stateDir);
1399
+ }
1400
+
1401
+ function ensureLightweightCommandAllowed(commandName, rawArgv) {
1402
+ if (!(isIdentityWriteCommand(commandName, rawArgv) || isWalletWriteCommand(commandName, rawArgv))) {
1403
+ return;
1404
+ }
1405
+ const deployment = deploymentState();
1406
+ if (!deployment.installed || usesSeparateDataDir(rawArgv, deployment)) {
1407
+ return;
1408
+ }
1409
+ const artifacts = installedNodeArtifacts(deployment);
1410
+ if (isIdentityWriteCommand(commandName, rawArgv) && fs.existsSync(artifacts.identityPath)) {
1411
+ throw new Error(
1412
+ [
1413
+ "Refusing to initialize a separate local identity.",
1414
+ "",
1415
+ "A local Wattetheria node is already installed and has an identity at:",
1416
+ deployment.stateDir,
1417
+ "",
1418
+ "`wattetheria identity init` is only for lightweight local setup when no Wattetheria",
1419
+ "node identity exists. Use the installed node's identity instead, or choose a separate",
1420
+ "data directory only for isolated testing."
1421
+ ].join("\n")
1422
+ );
1423
+ }
1424
+ if (
1425
+ isWalletWriteCommand(commandName, rawArgv)
1426
+ && fs.existsSync(artifacts.walletMetadataPath)
1427
+ && fs.existsSync(artifacts.walletKeystorePath)
1428
+ ) {
1429
+ throw new Error(
1430
+ [
1431
+ "Refusing to modify a separate local wallet.",
1432
+ "",
1433
+ "A local Wattetheria node is already installed and has wallet state at:",
1434
+ deployment.stateDir,
1435
+ "",
1436
+ "`wattetheria wallet` setup commands are only for lightweight local setup when no",
1437
+ "Wattetheria node wallet exists. Use the installed node's wallet instead, or choose a",
1438
+ "separate data directory only for isolated testing."
1439
+ ].join("\n")
1440
+ );
1441
+ }
1442
+ }
1443
+
1309
1444
  function forwardedArgsForInstalledNode(commandName, rawArgv) {
1310
1445
  const args = [commandName];
1311
1446
  if (!rawArgv.some((arg) => arg === "--data-dir" || arg.startsWith("--data-dir="))) {
@@ -1315,6 +1450,158 @@ function forwardedArgsForInstalledNode(commandName, rawArgv) {
1315
1450
  return args;
1316
1451
  }
1317
1452
 
1453
+ function isServicenetAgentCardInit(commandName, rawArgv) {
1454
+ return commandName === "servicenet"
1455
+ && rawArgv[1] === "agent-card"
1456
+ && rawArgv[2] === "init";
1457
+ }
1458
+
1459
+ function isServicenetRegister(commandName, rawArgv) {
1460
+ return commandName === "servicenet"
1461
+ && rawArgv[1] === "register";
1462
+ }
1463
+
1464
+ function isServicenetPublish(commandName, rawArgv) {
1465
+ return commandName === "servicenet"
1466
+ && rawArgv[1] === "publish";
1467
+ }
1468
+
1469
+ function usesWorkspaceMount(commandName, rawArgv) {
1470
+ return isServicenetAgentCardInit(commandName, rawArgv)
1471
+ || isServicenetRegister(commandName, rawArgv)
1472
+ || isServicenetPublish(commandName, rawArgv);
1473
+ }
1474
+
1475
+ function findExistingAncestor(targetPath) {
1476
+ let cursor = path.resolve(targetPath);
1477
+ while (!fs.existsSync(cursor)) {
1478
+ const parent = path.dirname(cursor);
1479
+ if (parent === cursor) {
1480
+ throw new Error(`No existing parent directory found for ${targetPath}`);
1481
+ }
1482
+ cursor = parent;
1483
+ }
1484
+ return cursor;
1485
+ }
1486
+
1487
+ function toContainerPath(relativePath) {
1488
+ if (!relativePath || relativePath === ".") {
1489
+ return "/workspace";
1490
+ }
1491
+ return `/workspace/${relativePath.split(path.sep).join("/")}`;
1492
+ }
1493
+
1494
+ function parsePathOption(rawArgv, optionName, startIndex) {
1495
+ const equalsPrefix = `${optionName}=`;
1496
+ for (let index = startIndex; index < rawArgv.length; index += 1) {
1497
+ const arg = rawArgv[index];
1498
+ if (arg === optionName) {
1499
+ return { index, value: rawArgv[index + 1], style: "separate" };
1500
+ }
1501
+ if (arg.startsWith(equalsPrefix)) {
1502
+ return { index, value: arg.slice(equalsPrefix.length), style: "equals" };
1503
+ }
1504
+ }
1505
+ return null;
1506
+ }
1507
+
1508
+ function pathMountFor(targetPath) {
1509
+ const requestedPath = path.resolve(targetPath);
1510
+ const existingPath = fs.existsSync(requestedPath) ? requestedPath : "";
1511
+ const mountSource = existingPath
1512
+ ? (
1513
+ fs.statSync(existingPath).isDirectory()
1514
+ ? existingPath
1515
+ : path.dirname(existingPath)
1516
+ )
1517
+ : findExistingAncestor(requestedPath);
1518
+ return {
1519
+ mountSource,
1520
+ containerPath: toContainerPath(path.relative(mountSource, requestedPath))
1521
+ };
1522
+ }
1523
+
1524
+ function rewritePathOption(rawArgv, parsedOption, containerPath) {
1525
+ const rewrittenArgv = [...rawArgv];
1526
+ if (parsedOption.style === "separate") {
1527
+ rewrittenArgv[parsedOption.index + 1] = containerPath;
1528
+ } else {
1529
+ rewrittenArgv[parsedOption.index] = `${rewrittenArgv[parsedOption.index].split("=")[0]}=${containerPath}`;
1530
+ }
1531
+ return rewrittenArgv;
1532
+ }
1533
+
1534
+ function dockerWorkspaceInvocation(commandName, rawArgv) {
1535
+ if (isServicenetAgentCardInit(commandName, rawArgv)) {
1536
+ const outputArg = parsePathOption(rawArgv, "--out", 3);
1537
+ const outputPath = outputArg?.value || process.cwd();
1538
+ if (outputArg && !outputArg.value) {
1539
+ throw new Error("Missing value for --out");
1540
+ }
1541
+ const outputMount = pathMountFor(outputPath);
1542
+ const rewrittenArgv = outputArg
1543
+ ? rewritePathOption(rawArgv, outputArg, outputMount.containerPath)
1544
+ : rawArgv;
1545
+ return {
1546
+ args: outputArg
1547
+ ? forwardedArgsForInstalledNode(commandName, rewrittenArgv)
1548
+ : [...forwardedArgsForInstalledNode(commandName, rawArgv), "--out", outputMount.containerPath],
1549
+ mountSource: outputMount.mountSource
1550
+ };
1551
+ }
1552
+
1553
+ if (isServicenetRegister(commandName, rawArgv)) {
1554
+ const cardArg = parsePathOption(rawArgv, "--card", 2);
1555
+ if (!cardArg) {
1556
+ return {
1557
+ args: forwardedArgsForInstalledNode(commandName, rawArgv),
1558
+ mountSource: process.cwd()
1559
+ };
1560
+ }
1561
+ if (!cardArg.value) {
1562
+ throw new Error("Missing value for --card");
1563
+ }
1564
+ const cardMount = pathMountFor(cardArg.value);
1565
+ return {
1566
+ args: forwardedArgsForInstalledNode(
1567
+ commandName,
1568
+ rewritePathOption(rawArgv, cardArg, cardMount.containerPath)
1569
+ ),
1570
+ mountSource: cardMount.mountSource
1571
+ };
1572
+ }
1573
+
1574
+ return {
1575
+ args: forwardedArgsForInstalledNode(commandName, rawArgv),
1576
+ mountSource: process.cwd()
1577
+ };
1578
+ }
1579
+
1580
+ function hostPathFromWorkspace(containerPath, mountSource) {
1581
+ if (containerPath === "/workspace") {
1582
+ return mountSource;
1583
+ }
1584
+ if (!containerPath.startsWith("/workspace/")) {
1585
+ return containerPath;
1586
+ }
1587
+ return path.join(mountSource, containerPath.slice("/workspace/".length));
1588
+ }
1589
+
1590
+ function rewriteWorkspaceJsonStdout(stdout, mountSource) {
1591
+ if (!stdout.trim()) {
1592
+ return stdout;
1593
+ }
1594
+ try {
1595
+ const payload = JSON.parse(stdout);
1596
+ if (typeof payload.card === "string") {
1597
+ payload.card = hostPathFromWorkspace(payload.card, mountSource);
1598
+ }
1599
+ return `${JSON.stringify(payload, null, 2)}\n`;
1600
+ } catch (_error) {
1601
+ return stdout;
1602
+ }
1603
+ }
1604
+
1318
1605
  function runInstalledNodeCli(commandName, rawArgv, deployment) {
1319
1606
  const dockerCommand = resolveDockerCommand();
1320
1607
  if (!dockerCommand) {
@@ -1323,6 +1610,41 @@ function runInstalledNodeCli(commandName, rawArgv, deployment) {
1323
1610
  `to run '${commandName}' inside the installed node. Start Docker Desktop and retry.`
1324
1611
  );
1325
1612
  }
1613
+ if (usesWorkspaceMount(commandName, rawArgv)) {
1614
+ const invocation = dockerWorkspaceInvocation(commandName, rawArgv);
1615
+ const result = spawnSync(
1616
+ dockerCommand,
1617
+ [
1618
+ "compose",
1619
+ "--project-name",
1620
+ DEFAULT_PROJECT_NAME,
1621
+ "--env-file",
1622
+ deployment.envPath,
1623
+ "-f",
1624
+ deployment.composePath,
1625
+ "run",
1626
+ "--rm",
1627
+ "--no-deps",
1628
+ "-T",
1629
+ "--entrypoint",
1630
+ "/app/target/release/wattetheria-client-cli",
1631
+ "-v",
1632
+ `${invocation.mountSource}:/workspace`,
1633
+ "-w",
1634
+ "/workspace",
1635
+ "kernel",
1636
+ ...invocation.args
1637
+ ],
1638
+ { stdio: ["inherit", "pipe", "inherit"], encoding: "utf8" }
1639
+ );
1640
+ if (result.stdout) {
1641
+ process.stdout.write(rewriteWorkspaceJsonStdout(result.stdout, invocation.mountSource));
1642
+ }
1643
+ if (typeof result.status === "number") {
1644
+ process.exit(result.status);
1645
+ }
1646
+ throw result.error ?? new Error(`Failed to run '${commandName}' inside the installed node`);
1647
+ }
1326
1648
  const result = spawnSync(
1327
1649
  dockerCommand,
1328
1650
  [
@@ -1348,6 +1670,8 @@ function runInstalledNodeCli(commandName, rawArgv, deployment) {
1348
1670
  }
1349
1671
 
1350
1672
  function forwardToRustBinary(commandName, rawArgv) {
1673
+ ensureLightweightCommandAllowed(commandName, rawArgv);
1674
+
1351
1675
  // Only the Rust binary name is allowed here. The bare `wattetheria` name
1352
1676
  // resolves to this JS shim on most user PATHs (via the npm bin link), so
1353
1677
  // including it would create an infinite spawn loop.
@@ -1374,6 +1698,9 @@ function forwardToRustBinary(commandName, rawArgv) {
1374
1698
  }
1375
1699
 
1376
1700
  const deployment = deploymentState();
1701
+ if (commandName === "identity" || commandName === "wallet") {
1702
+ throw missingNativeCliError(commandName);
1703
+ }
1377
1704
  if (deployment.runnable) {
1378
1705
  runInstalledNodeCli(commandName, rawArgv, deployment);
1379
1706
  return;
@@ -1383,6 +1710,8 @@ function forwardToRustBinary(commandName, rawArgv) {
1383
1710
  }
1384
1711
 
1385
1712
  async function run(argv) {
1713
+ throwCommandSuggestion(argv);
1714
+
1386
1715
  if (argv[0] && FORWARDED_SUBCOMMANDS.has(argv[0])) {
1387
1716
  forwardToRustBinary(argv[0], argv);
1388
1717
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wattetheria",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Wattetheria deployment CLI",
5
5
  "license": "Apache-2.0",
6
6
  "type": "commonjs",