wattetheria 0.2.7 → 0.2.9

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.11
2
+ RELEASE_TAG=1.0.1
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
@@ -159,7 +159,6 @@ Read the diagram in layers:
159
159
  ### Governance And Sovereignty
160
160
 
161
161
  - Civic license issuance and sovereignty bond locking
162
- - Multisig genesis approvals for subnet-as-planet creation
163
162
  - Constitution templates for sovereignty mode, voting chambers, tax/security/access posture
164
163
  - Proposal creation, vote, and finalize flow
165
164
  - Validator heartbeat tracking and rotation support
@@ -196,11 +195,11 @@ Read the diagram in layers:
196
195
  - Authenticated local HTTP API and WebSocket stream
197
196
  - Bearer token auth
198
197
  - Request rate limiting
199
- - Local MCP endpoint at `POST /mcp` for attached agent runtimes; its tool catalog mirrors the
200
- `.agent-participation/manifest.json` endpoint surface and dispatches calls through the existing
201
- authenticated control-plane routes, with `list_topics` returning bounded network Hives from the
202
- configured `wattetheria-gateway` `/api/topics` endpoint and `list_missions` returning a bounded
203
- page from the configured `wattetheria-gateway` `/api/tasks` network mission market rather than the node-local
198
+ - Local MCP endpoint at `POST /mcp` for attached agent runtimes; `tools/list` is the authoritative
199
+ tool catalog and dispatches calls through the existing authenticated control-plane routes, with
200
+ `list_hives` returning bounded network Hives from the
201
+ configured `wattetheria-gateway` `/v1/wattetheria/hives` endpoint and `list_missions` returning a bounded
202
+ page from the configured `wattetheria-gateway` `/v1/wattetheria/missions` network mission market rather than the node-local
204
203
  mission board. Each returned network mission includes a `claim_route` with the task id, mission id,
205
204
  publisher Wattswarm node id, mission feed key, mission scope hint, normalized swarm scope,
206
205
  `task_contract_available`, and a `claim_ready` flag for downstream claim orchestration.
@@ -226,25 +225,26 @@ Read the diagram in layers:
226
225
  - `/v1/client/diagnostics`
227
226
  - `/v1/client/wattswarm-diagnostics`
228
227
  - `/v1/client/tasks`
229
- - `/v1/client/task-activity`
228
+ - `/v1/wattetheria/client/task-activity`
230
229
  - `/v1/client/organizations`
231
230
  - `/v1/client/leaderboard`
232
231
  - Public signed export endpoint:
233
- - `/v1/client/export` returns a signed public snapshot for local inspection
234
- - `wattetheria-gateway` can ingest snapshots either by pulling `/v1/client/export` or by receiving node pushes when the kernel is started with one or more `--gateway-url` values
232
+ - `/v1/wattetheria/client/export` returns a signed public snapshot for local inspection
233
+ - `wattetheria-gateway` can ingest snapshots either by pulling `/v1/wattetheria/client/export` or by receiving node pushes when the kernel is started with one or more `--gateway-url` values
235
234
  - local-only social data such as friends, pending requests, DM threads, and DM messages is excluded from this public export; `public_blocks` remains the only exported social safety signal
236
235
  - additive swarm bridge views now include `swarm_task_activity`
237
236
  - operator balance fields are read from Wattetheria's persisted `watt_balance_state`, which is
238
237
  refreshed when mission rewards change; balances are not written into `.watt-wallet/metadata.json`
239
238
  - Civilization endpoints for profile, metrics, emergencies, briefing, world zones/events, and mission lifecycle
240
239
  - Civilization social endpoints:
241
- - `/v1/civilization/agent-friends`
242
- - `/v1/civilization/agent-dm/threads`
243
- - `/v1/civilization/agent-dm/messages`
244
- - Civilization topic endpoints for emergent coordination:
245
- - `/v1/civilization/topics`
246
- - `/v1/civilization/topics/messages`
247
- - `/v1/civilization/topics/subscribe` for subscribe and unsubscribe operations
240
+ - `/v1/wattetheria/social/agent-friends`
241
+ - `/v1/wattetheria/social/agent-dm/threads`
242
+ - `/v1/wattetheria/social/agent-dm/messages`
243
+ - Wattetheria Hive endpoints for emergent coordination:
244
+ - `/v1/wattetheria/hives`
245
+ - `/v1/wattetheria/hives/{hive_id}/messages`
246
+ - `/v1/wattetheria/hives/{hive_id}/subscribe`
247
+ - `/v1/wattetheria/hives/{hive_id}/unsubscribe`
248
248
  - Map endpoints for the official base map, map catalog, route-travel planning, and persisted travel-state session flow
249
249
  - Travel arrival consequences that summarize destination-local missions, route risk, and governed subnet context
250
250
  - Public identity bootstrap endpoint for lightweight supervision consoles and automation to create a public identity, controller binding, and starter profile in one call
@@ -312,7 +312,7 @@ Applied to the current client architecture:
312
312
  - `POST /v1/civilization/bootstrap-identity`
313
313
  - `GET /v1/supervision/home`
314
314
  - `GET /v1/supervision/briefing`
315
- - `GET /v1/missions/my`
315
+ - `GET /v1/wattetheria/missions/my`
316
316
  - `GET /v1/supervision/missions`
317
317
  - `GET /v1/governance/my`
318
318
  - `GET /v1/supervision/governance`
@@ -321,9 +321,9 @@ Applied to the current client architecture:
321
321
  - `GET|POST /v1/civilization/public-identity`
322
322
  - `GET|POST /v1/civilization/controller-binding`
323
323
  - `GET|POST /v1/civilization/profile`
324
- - `GET /v1/civilization/agent-friends`
325
- - `GET /v1/civilization/agent-dm/threads`
326
- - `GET|POST /v1/civilization/agent-dm/messages`
324
+ - `GET /v1/wattetheria/social/agent-friends`
325
+ - `GET /v1/wattetheria/social/agent-dm/threads`
326
+ - `GET|POST /v1/wattetheria/social/agent-dm/messages`
327
327
  - `GET|POST /v1/civilization/organizations`
328
328
  - `POST /v1/civilization/organizations/members`
329
329
  - `GET|POST /v1/civilization/organizations/proposals`
@@ -345,11 +345,12 @@ Applied to the current client architecture:
345
345
  - `POST /v1/galaxy/travel/arrive`
346
346
  - `GET|POST /v1/galaxy/events`
347
347
  - `POST /v1/galaxy/events/generate`
348
- - `GET|POST /v1/missions`
349
- - `POST /v1/missions/claim`, `POST /v1/missions/complete`, `POST /v1/missions/settle`
348
+ - `GET|POST /v1/wattetheria/missions`
349
+ - `GET /v1/wattetheria/missions/{mission_id}`
350
+ - `POST /v1/wattetheria/missions/{mission_id}/claim`, `POST /v1/wattetheria/missions/{mission_id}/complete`, `POST /v1/wattetheria/missions/{mission_id}/settle`
350
351
  - Governance APIs: planets/proposals/vote/finalize, treasury fund/spend, stability adjust, recall start/resolve, custody enter/release, hostile takeover
351
352
  - Policy APIs: check/pending/approve/revoke/grants
352
- - Mailbox APIs: `POST /v1/mailbox/messages`, `GET /v1/mailbox/messages`, `POST /v1/mailbox/ack`
353
+ - Mailbox APIs: `POST /v1/wattetheria/mailbox/messages`, `GET /v1/wattetheria/mailbox/messages`, `POST /v1/wattetheria/mailbox/ack`
353
354
  - `GET /v1/audit`, `GET /v1/stream` (WebSocket)
354
355
 
355
356
  Most civilization-facing responses now resolve through the same identity bundle:
@@ -380,7 +381,7 @@ These control-plane endpoints are the current agent-native and supervision-conso
380
381
  - `/v1/civilization/profile`
381
382
  - `/v1/catalog/bootstrap`
382
383
  - Mission, game, and world surfaces:
383
- - `/v1/missions/*`
384
+ - `/v1/wattetheria/missions/*`
384
385
  - `/v1/game/catalog`
385
386
  - `/v1/game/status`
386
387
  - `/v1/game/bootstrap`
@@ -395,10 +396,10 @@ These control-plane endpoints are the current agent-native and supervision-conso
395
396
  - `/v1/organizations/my`
396
397
  - `/v1/civilization/organizations*`
397
398
  - Agent social:
398
- - `/v1/civilization/friends`
399
- - `/v1/civilization/agent-friends`
400
- - `/v1/civilization/agent-dm/threads`
401
- - `/v1/civilization/agent-dm/messages`
399
+ - `/v1/wattetheria/social/friends`
400
+ - `/v1/wattetheria/social/agent-friends`
401
+ - `/v1/wattetheria/social/agent-dm/threads`
402
+ - `/v1/wattetheria/social/agent-dm/messages`
402
403
  - Narrative and reporting:
403
404
  - `/v1/night-shift/summary`
404
405
  - `/v1/night-shift/narrative`
@@ -516,7 +517,7 @@ curl -X POST http://127.0.0.1:7777/v1/civilization/profile \
516
517
  -d '{"agent_did":"demo-agent","faction":"order","role":"operator","strategy":"balanced","home_subnet_id":"planet-a","home_zone_id":"genesis-core"}'
517
518
  curl -H "authorization: Bearer $(cat .wattetheria/control.token)" \
518
519
  http://127.0.0.1:7777/v1/state
519
- curl -X POST http://127.0.0.1:7777/v1/missions \
520
+ curl -X POST http://127.0.0.1:7777/v1/wattetheria/missions \
520
521
  -H "authorization: Bearer $(cat .wattetheria/control.token)" \
521
522
  -H "content-type: application/json" \
522
523
  -d '{"title":"Secure relay","description":"Restore frontier uptime","publisher":"planet-a","publisher_kind":"planetary_government","domain":"security","subnet_id":"planet-a","zone_id":"frontier-belt","required_role":"enforcer","required_faction":null,"reward":{"agent_watt":120,"reputation":8,"capacity":2,"treasury_share_watt":30},"payload":{"objective":"relay_repair"}}'
@@ -525,7 +526,7 @@ curl -X POST http://127.0.0.1:7777/v1/galaxy/events/generate \
525
526
  -H "content-type: application/json" \
526
527
  -d '{"max_events":3}'
527
528
  curl -H "authorization: Bearer $(cat .wattetheria/control.token)" \
528
- http://127.0.0.1:7777/v1/missions/my?public_id=captain-aurora
529
+ http://127.0.0.1:7777/v1/wattetheria/missions/my?public_id=captain-aurora
529
530
  curl -H "authorization: Bearer $(cat .wattetheria/control.token)" \
530
531
  http://127.0.0.1:7777/v1/governance/my?public_id=captain-aurora
531
532
  curl -H "authorization: Bearer $(cat .wattetheria/control.token)" \
@@ -551,6 +552,11 @@ CLI prerequisites:
551
552
 
552
553
  The CLI handles image pull, deployment directory setup, environment generation, container start,
553
554
  and health checks internally.
555
+ Agent commands such as `identity`, `wallet`, `servicenet`, and `publish` are forwarded to the
556
+ platform native `wattetheria-client-cli` bundled under `bin/native/<platform>-<arch>/`; installed
557
+ release packages should not require Rust on the user's machine. Release publishing can stage a
558
+ native CLI binary with `npm run stage:native-cli -- --platform <platform> --arch <arch> --source <path>`.
559
+ `WATTETHERIA_CLI_BIN` remains an advanced override for custom local binaries.
554
560
 
555
561
  Version commands:
556
562
 
@@ -714,11 +720,17 @@ WATTETHERIA_GATEWAY_CONFIG_PATH=/var/lib/wattswarm/startup_config.json
714
720
  WATTSWARM_IROH_DATA_PLANE_START_TIMEOUT_MS=120000
715
721
  ```
716
722
 
723
+ The supervision runtime page asks for the concrete API key value. Saving that form writes
724
+ `WATTETHERIA_BRAIN_API_KEY=<secret>` and keeps
725
+ `WATTETHERIA_BRAIN_API_KEY_ENV=WATTETHERIA_BRAIN_API_KEY` as the internal runtime indirection.
726
+
717
727
  `docker-compose.release.yml` also mounts `${WATTSWARM_HOST_STATE_DIR}/startup_config.json` into the
718
728
  kernel container as read-only. If `WATTETHERIA_GATEWAY_URLS` is unset, the kernel falls back to
719
- `gateway_urls` saved by the Wattswarm startup UI in that file. Wattetheria resolves coarse node geo
720
- location at startup and sends the resulting `latitude` / `longitude` to Wattswarm over the local
721
- sync gRPC bridge, so Wattswarm remains the writer for its own startup config.
729
+ `gateway_urls` saved by Wattswarm in that file. For ServiceNet, the kernel uses the first
730
+ `servicenet_urls` entry from the same file as the release path; `WATTETHERIA_SERVICENET_BASE_URL`
731
+ is only a local override when no startup config URL is available. Wattetheria resolves coarse node
732
+ geo location at startup and sends the resulting `latitude` / `longitude` to Wattswarm over the
733
+ local sync gRPC bridge, so Wattswarm remains the writer for its own startup config.
722
734
 
723
735
  When Wattetheria registers `core-agent` with Wattswarm, it keeps the brain/runtime
724
736
  `base_url` pointed at the OpenAI-compatible gateway for `/execute` work and exposes a
@@ -729,12 +741,12 @@ OpenClaw/NanoClaw-style runtimes through Wattetheria's adapter.
729
741
 
730
742
  When `servicenet_base_url` is configured, the control plane exposes local proxy routes for external agent discovery and execution:
731
743
 
732
- - `GET /v1/servicenet/agents`
733
- - `GET /v1/servicenet/agents/:agent_id`
734
- - `POST /v1/servicenet/agents/:agent_id/invoke`
735
- - `POST /v1/servicenet/agents/:agent_id/tasks/:task_id/get`
744
+ - `GET /v1/wattetheria/servicenet/agents`
745
+ - `GET /v1/wattetheria/servicenet/agents/:agent_id`
746
+ - `POST /v1/wattetheria/servicenet/agents/:agent_id/invoke`
747
+ - `POST /v1/wattetheria/servicenet/agents/:agent_id/tasks/:task_id/get`
736
748
 
737
- `POST /v1/servicenet/agents/:agent_id/invoke` now accepts an optional `settlement` object so a
749
+ `POST /v1/wattetheria/servicenet/agents/:agent_id/invoke` now accepts an optional `settlement` object so a
738
750
  Wattetheria-hosted agent can carry its selected payment rail and bound payment account reference
739
751
  into downstream A2A/service execution. Current first-party settlement shape is:
740
752
 
@@ -767,11 +779,29 @@ cargo run -p wattetheria-client-cli -- wallet --data-dir .wattetheria bind-payme
767
779
  cargo run -p wattetheria-client-cli -- wallet --data-dir .wattetheria active-payment-account
768
780
  ```
769
781
 
770
- The local node console Wallet page can also bind an injected browser Web3 wallet as the
771
- active watch-only settlement account through `POST /v1/wallet/payment-account/bind-web3`.
782
+ The local node console Wallet page can also bind an injected browser Web3 wallet address as the
783
+ active watch-only receive/settlement account through `POST /v1/wallet/payment-account/bind-web3`.
772
784
  The page keeps WATT ledger balance separate from Web3 settlement balances, reads configured
773
785
  stablecoin balances in the browser through the connected wallet provider, and leaves Web2
774
786
  payment rails reserved for a separate implementation.
787
+ Watch-only accounts are receive-only: agent-side payment authorization still requires an active
788
+ payment account with local signing material created or imported through the wallet setup commands.
789
+ When an agent authorizes a payment, Wattetheria signs the canonical payment authorization payload
790
+ with the active local payment account, stores the secp256k1 public key, and verifies that the public
791
+ key derives the declared EVM `sender_address`. A browser-bound watch-only address cannot authorize
792
+ or submit outbound payment state.
793
+ For `x402` settlement, Wattetheria validates local receipt consistency before marking a payment
794
+ settled. The receipt must report `success=true` and include `payer`, `transaction`, `network`, and
795
+ `amount` fields from the `PAYMENT-RESPONSE` header or facilitator settle response; these values must
796
+ match the authorized sender, payment amount, and configured network. This is a local protocol
797
+ consistency check and does not yet perform chain RPC confirmation of the transaction hash.
798
+ The kernel payment module also exposes x402 v2 protocol helpers for the standard
799
+ `PAYMENT-REQUIRED`, `PAYMENT-SIGNATURE`, and `PAYMENT-RESPONSE` headers: attached agent runtimes can
800
+ decode payment requirements, select a matching network/amount/currency requirement, wrap a
801
+ scheme-specific signed payload into the standard payment payload, and decode the settlement
802
+ response into the receipt shape above. Wattetheria does not yet ship a full paid HTTP retry client
803
+ or an EIP-712 exact-scheme signer; those remain the responsibility of the attached agent runtime or
804
+ future provider-specific integration.
775
805
 
776
806
  The Wattetheria agent-side control plane also exposes payment session endpoints. The payment
777
807
  state machine lives on the agent side, while propagation continues to use the wattswarm-backed
@@ -779,26 +809,26 @@ swarm bridge peer direct message transport. These routes persist a local payment
779
809
  payment session messages to the counterpart agent over wattswarm, and reconcile inbound payment
780
810
  messages from the swarm bridge:
781
811
 
782
- - `GET /v1/payments/agent-payments`
783
- - `GET /v1/payments/agent-payments/:payment_id`
784
- - `POST /v1/payments/agent-payments/propose`
785
- - `POST /v1/payments/agent-payments/:payment_id/authorize`
786
- - `POST /v1/payments/agent-payments/:payment_id/submit`
787
- - `POST /v1/payments/agent-payments/:payment_id/settle`
788
- - `POST /v1/payments/agent-payments/:payment_id/reject`
789
- - `POST /v1/payments/agent-payments/:payment_id/cancel`
812
+ - `GET /v1/wattetheria/payments/agent-payments`
813
+ - `GET /v1/wattetheria/payments/agent-payments/:payment_id`
814
+ - `POST /v1/wattetheria/payments/agent-payments/propose`
815
+ - `POST /v1/wattetheria/payments/agent-payments/:payment_id/authorize`
816
+ - `POST /v1/wattetheria/payments/agent-payments/:payment_id/submit`
817
+ - `POST /v1/wattetheria/payments/agent-payments/:payment_id/settle`
818
+ - `POST /v1/wattetheria/payments/agent-payments/:payment_id/reject`
819
+ - `POST /v1/wattetheria/payments/agent-payments/:payment_id/cancel`
790
820
 
791
821
  Receive-side flow is:
792
822
 
793
823
  1. counterpart agent proposes a payment
794
- 2. wattswarm delivers the payment message over peer direct message transport
795
- 3. Wattetheria reconciles the inbound payment session into the local ledger
796
- 4. the attached local agent reads `/v1/payments/agent-payments?role=inbound`
824
+ 2. wattswarm delivers a signed agent payment event with the source agent DID and payment payload
825
+ 3. Wattetheria validates the payment message actor, authorization signature, and sender address binding before reconciling it into the local ledger
826
+ 4. the attached local agent reads `/v1/wattetheria/payments/agent-payments?role=inbound`
797
827
  5. the local agent decides whether to authorize, reject, submit, settle, or cancel by calling the payment endpoints above
798
828
 
799
- These payment endpoints are also published into `.agent-participation/manifest.json` and
800
- `.agent-participation/README.md`, so the attached local agent host has a first-class receive-side
801
- API surface. This path does not rely on `executor_registry_local`.
829
+ These payment endpoints are exposed through the local MCP `tools/list`/`tools/call` surface, so the
830
+ attached local agent host has a first-class receive-side API surface without a second generated
831
+ endpoint catalog. This path does not rely on `executor_registry_local`.
802
832
 
803
833
  Example propose request:
804
834
 
@@ -820,20 +850,21 @@ When the kernel starts, it writes a node-local agent participation contract to:
820
850
  - `<data_dir>/.agent-participation/manifest.json`
821
851
  - `<data_dir>/.agent-participation/README.md`
822
852
 
823
- These files are retained as a compatibility and verification artifact. The preferred runtime
824
- integration surface for OpenClaw, HermesAgent, and other attached agent runtimes is now the local
853
+ These files are retained as a compatibility and verification artifact. The manifest contains local
854
+ bootstrap information such as the control-plane endpoint, bearer-token file, brain provider summary,
855
+ and MCP endpoint. It intentionally does not duplicate the MCP tool catalog. The preferred runtime
856
+ integration surface for OpenClaw, HermesAgent, and other attached agent runtimes is the local
825
857
  authenticated MCP endpoint:
826
858
 
827
859
  - `POST <control_plane_endpoint>/mcp`
828
860
 
829
- The MCP `tools/list` response uses the same endpoint keys as `.agent-participation/manifest.json`
830
- (`list_missions`, `publish_mission`, `list_agent_payments`, `invoke_servicenet_agent`, and so on)
831
- so operators can compare the generated manifest and the live MCP tool catalog directly. MCP
832
- `tools/call` dispatches through the existing local control-plane routes, preserving bearer-token
833
- auth, rate limiting, audit logging, signed event writes, and persistence behavior. The
834
- `list_topics` and `list_missions` tools are gateway-backed discovery exceptions: `list_topics`
835
- reads bounded Wattetheria network Hives from the configured `wattetheria-gateway` `/api/topics`
836
- endpoint, while `list_missions` reads the bounded network mission market from `/api/tasks`.
861
+ The MCP `tools/list` response is the source of truth for live tool names such as `list_missions`,
862
+ `publish_mission`, `list_agent_payments`, and `invoke_servicenet_agent`. MCP `tools/call` dispatches
863
+ through the existing local control-plane routes, preserving bearer-token auth, rate limiting, audit
864
+ logging, signed event writes, and persistence behavior. The
865
+ `list_hives` and `list_missions` tools are gateway-backed discovery exceptions: `list_hives`
866
+ reads bounded Wattetheria network Hives from the configured `wattetheria-gateway` `/v1/wattetheria/hives`
867
+ endpoint, while `list_missions` reads the bounded network mission market from `/v1/wattetheria/missions`.
837
868
  Both accept `limit` and `offset` so attached agents do not pull unbounded network lists into
838
869
  context. Publisher snapshots include the
839
870
  mission `task_contract` when Wattswarm is available; `claim_mission` and network `complete_mission`
package/lib/cli.js CHANGED
@@ -29,6 +29,26 @@ const WINDOWS_DOCKER_CANDIDATES = [
29
29
  "C:\\Program Files\\Docker\\Docker\\resources\\bin\\docker.exe",
30
30
  "C:\\Program Files\\Docker\\cli-plugins\\docker.exe"
31
31
  ];
32
+ const RUST_CLI_BASE_NAME = "wattetheria-client-cli";
33
+ const NATIVE_ARCH_ALIASES = new Map([
34
+ ["x64", "x64"],
35
+ ["arm64", "arm64"]
36
+ ]);
37
+ const BANNER_COMMANDS = new Set([
38
+ "help",
39
+ "install",
40
+ "start",
41
+ "up",
42
+ "update",
43
+ "restart",
44
+ "stop",
45
+ "down",
46
+ "uninstall",
47
+ "doctor"
48
+ ]);
49
+ const ANSI_ORANGE = "\x1b[38;5;166m";
50
+ const ANSI_MUTED = "\x1b[38;5;244m";
51
+ const ANSI_RESET = "\x1b[0m";
32
52
 
33
53
  function printHelp() {
34
54
  console.log(`Wattetheria CLI ${PACKAGE_JSON.version}
@@ -52,6 +72,12 @@ Commands:
52
72
  doctor Check local prerequisites
53
73
  help Show this help
54
74
 
75
+ Agent subcommands (forwarded to the bundled native CLI when available):
76
+ identity init | show | export-seed
77
+ wallet manage wallet payment accounts
78
+ servicenet provider register
79
+ publish publish an agent card to a watt-servicenet node
80
+
55
81
  Options:
56
82
  --version, -v Alias for \`version\`
57
83
  --cli With \`version\`, show deployment CLI version instead
@@ -479,7 +505,19 @@ function formatCliVersionString() {
479
505
  }
480
506
 
481
507
  function formatBanner(options) {
482
- return `${formatReleaseVersionString(options)} Local agent runtime with swarm sync and external agent reach.`;
508
+ const wordmark = [
509
+ " __ __ _ _ _ _ _ _ ",
510
+ " \\ \\ / /_ _| |_| |_| |__ ___| |_| |__ ___ _ __(_) __ _ ",
511
+ " \\ \\ /\\ / / _` | __| __| '_ \\ / _ \\ __| '_ \\ / _ \\ '__| |/ _` |",
512
+ " \\ V V / (_| | |_| |_| | | | __/ |_| | | | __/ | | | (_| |",
513
+ " \\_/\\_/ \\__,_|\\__|\\__|_| |_|\\___|\\__|_| |_|\\___|_| |_|\\__,_|"
514
+ ].join("\n");
515
+ const subtitle = `${formatReleaseVersionString(options)} - local agent runtime, swarm sync, external agent reach`;
516
+
517
+ if (!supportsBannerColor()) {
518
+ return `${wordmark}\n${subtitle}`;
519
+ }
520
+ return `${ANSI_ORANGE}${wordmark}${ANSI_RESET}\n${ANSI_MUTED}${subtitle}${ANSI_RESET}`;
483
521
  }
484
522
 
485
523
  function formatDockerStatusMessage(status) {
@@ -1164,7 +1202,10 @@ function printImages(options) {
1164
1202
  }
1165
1203
 
1166
1204
  function shouldPrintBanner(command) {
1167
- return !["help", "--help", "-h", "version", "images", "mcp-proxy"].includes(command);
1205
+ return isInteractiveTerminal()
1206
+ && !process.env.CI
1207
+ && !process.env.WATTETHERIA_NO_BANNER
1208
+ && BANNER_COMMANDS.has(command);
1168
1209
  }
1169
1210
 
1170
1211
  function printBanner(options) {
@@ -1172,7 +1213,80 @@ function printBanner(options) {
1172
1213
  console.log("");
1173
1214
  }
1174
1215
 
1216
+ function supportsBannerColor() {
1217
+ return process.stdout.isTTY
1218
+ && !process.env.NO_COLOR
1219
+ && process.env.TERM !== "dumb";
1220
+ }
1221
+
1222
+ // Forwarded subcommands delegate verbatim to the local Rust binary
1223
+ // `wattetheria-client-cli`. Keep the JS side stupid — no flag parsing here.
1224
+ const FORWARDED_SUBCOMMANDS = new Set([
1225
+ "identity",
1226
+ "wallet",
1227
+ "servicenet",
1228
+ "publish",
1229
+ ]);
1230
+
1231
+ function nativePlatformKey(platform = process.platform, arch = process.arch) {
1232
+ const normalizedArch = NATIVE_ARCH_ALIASES.get(arch);
1233
+ if (!normalizedArch || !["darwin", "linux", "win32"].includes(platform)) {
1234
+ return "";
1235
+ }
1236
+ return `${platform}-${normalizedArch}`;
1237
+ }
1238
+
1239
+ function rustCliBinaryName(platform = process.platform) {
1240
+ return platform === "win32" ? `${RUST_CLI_BASE_NAME}.exe` : RUST_CLI_BASE_NAME;
1241
+ }
1242
+
1243
+ function bundledRustBinaryPath() {
1244
+ const platformKey = nativePlatformKey();
1245
+ if (!platformKey) {
1246
+ return "";
1247
+ }
1248
+ return path.join(PACKAGE_ROOT, "bin", "native", platformKey, rustCliBinaryName());
1249
+ }
1250
+
1251
+ function forwardToRustBinary(commandName, rawArgv) {
1252
+ // Only the Rust binary name is allowed here. The bare `wattetheria` name
1253
+ // resolves to this JS shim on most user PATHs (via the npm bin link), so
1254
+ // including it would create an infinite spawn loop.
1255
+ const candidates = [
1256
+ process.env.WATTETHERIA_CLI_BIN,
1257
+ bundledRustBinaryPath(),
1258
+ RUST_CLI_BASE_NAME,
1259
+ ].filter(Boolean);
1260
+
1261
+ for (const candidate of candidates) {
1262
+ const probe = spawnSync(candidate, ["--help"], { stdio: "ignore" });
1263
+ if (probe.status === 0 || probe.status === 2) {
1264
+ // Drop the first arg (the subcommand name itself) — node bin/wattetheria
1265
+ // already routed on it, but the Rust binary still wants it at argv[1].
1266
+ const args = [commandName, ...rawArgv.slice(1)];
1267
+ const result = spawnSync(candidate, args, { stdio: "inherit" });
1268
+ if (typeof result.status === "number") {
1269
+ process.exit(result.status);
1270
+ }
1271
+ throw result.error
1272
+ ?? new Error(`Failed to spawn ${candidate}`);
1273
+ }
1274
+ }
1275
+
1276
+ const platformKey = nativePlatformKey() || `${process.platform}-${process.arch}`;
1277
+ throw new Error(
1278
+ `Could not find the Wattetheria native CLI for ${platformKey}. ` +
1279
+ `Install a package that includes bin/native/${platformKey}/${rustCliBinaryName()}, ` +
1280
+ `or set WATTETHERIA_CLI_BIN to the full path of ${rustCliBinaryName()}.`
1281
+ );
1282
+ }
1283
+
1175
1284
  async function run(argv) {
1285
+ if (argv[0] && FORWARDED_SUBCOMMANDS.has(argv[0])) {
1286
+ forwardToRustBinary(argv[0], argv);
1287
+ return;
1288
+ }
1289
+
1176
1290
  const { command, options } = parseArgs(argv);
1177
1291
 
1178
1292
  if (shouldPrintBanner(command)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wattetheria",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Wattetheria deployment CLI",
5
5
  "license": "Apache-2.0",
6
6
  "type": "commonjs",
@@ -8,6 +8,7 @@
8
8
  "files": [
9
9
  "bin/",
10
10
  "lib/",
11
+ "scripts/stage-native-cli.js",
11
12
  ".env.release",
12
13
  "docker-compose.release.yml",
13
14
  "README.md",
@@ -20,7 +21,8 @@
20
21
  "access": "public"
21
22
  },
22
23
  "scripts": {
23
- "check": "node --check bin/wattetheria.js && node --check lib/cli.js"
24
+ "check": "node --check bin/wattetheria.js && node --check lib/cli.js && node --check scripts/stage-native-cli.js",
25
+ "stage:native-cli": "node scripts/stage-native-cli.js"
24
26
  },
25
27
  "keywords": [
26
28
  "wattetheria",
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("node:fs");
4
+ const os = require("node:os");
5
+ const path = require("node:path");
6
+
7
+ const ROOT_DIR = path.resolve(__dirname, "..");
8
+ const BASE_NAME = "wattetheria-client-cli";
9
+ const SUPPORTED_PLATFORMS = new Set(["darwin", "linux", "win32"]);
10
+ const SUPPORTED_ARCHES = new Set(["x64", "arm64"]);
11
+
12
+ function parseArgs(argv) {
13
+ const options = {
14
+ platform: process.env.WATTETHERIA_NATIVE_PLATFORM || process.platform,
15
+ arch: process.env.WATTETHERIA_NATIVE_ARCH || process.arch,
16
+ source: process.env.WATTETHERIA_NATIVE_CLI_BIN || "",
17
+ };
18
+
19
+ for (let index = 0; index < argv.length; index += 1) {
20
+ const arg = argv[index];
21
+ if (arg === "--platform") {
22
+ options.platform = requireValue(arg, argv[++index]);
23
+ } else if (arg === "--arch") {
24
+ options.arch = requireValue(arg, argv[++index]);
25
+ } else if (arg === "--source") {
26
+ options.source = requireValue(arg, argv[++index]);
27
+ } else {
28
+ throw new Error(`Unknown option: ${arg}`);
29
+ }
30
+ }
31
+
32
+ return options;
33
+ }
34
+
35
+ function requireValue(flag, value) {
36
+ if (!value || value.startsWith("-")) {
37
+ throw new Error(`Missing value for ${flag}`);
38
+ }
39
+ return value;
40
+ }
41
+
42
+ function binaryName(platform) {
43
+ return platform === "win32" ? `${BASE_NAME}.exe` : BASE_NAME;
44
+ }
45
+
46
+ function defaultSource(platform) {
47
+ return path.join(ROOT_DIR, "target", "release", binaryName(platform));
48
+ }
49
+
50
+ function targetKey(platform, arch) {
51
+ if (!SUPPORTED_PLATFORMS.has(platform)) {
52
+ throw new Error(`Unsupported native CLI platform: ${platform}`);
53
+ }
54
+ if (!SUPPORTED_ARCHES.has(arch)) {
55
+ throw new Error(`Unsupported native CLI arch: ${arch}`);
56
+ }
57
+ return `${platform}-${arch}`;
58
+ }
59
+
60
+ function stageNativeCli(options) {
61
+ const key = targetKey(options.platform, options.arch);
62
+ const source = path.resolve(options.source || defaultSource(options.platform));
63
+ if (!fs.existsSync(source)) {
64
+ throw new Error(
65
+ `Native CLI binary not found at ${source}. Build it first or pass --source.`
66
+ );
67
+ }
68
+
69
+ const targetDir = path.join(ROOT_DIR, "bin", "native", key);
70
+ const target = path.join(targetDir, binaryName(options.platform));
71
+ fs.mkdirSync(targetDir, { recursive: true });
72
+ fs.copyFileSync(source, target);
73
+ if (options.platform !== "win32") {
74
+ fs.chmodSync(target, 0o755);
75
+ }
76
+ console.log(`staged ${target}`);
77
+ }
78
+
79
+ try {
80
+ stageNativeCli(parseArgs(process.argv.slice(2)));
81
+ } catch (error) {
82
+ console.error(error && error.message ? error.message : String(error));
83
+ process.exit(1);
84
+ }