@hsuite/smart-engines-cli 1.2.1 → 1.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 (109) hide show
  1. package/README.md +272 -58
  2. package/dist/_lib/attestation.d.ts +45 -0
  3. package/dist/_lib/attestation.d.ts.map +1 -0
  4. package/dist/_lib/attestation.js +76 -0
  5. package/dist/_lib/attestation.js.map +1 -0
  6. package/dist/_lib/baas.d.ts +147 -0
  7. package/dist/_lib/baas.d.ts.map +1 -0
  8. package/dist/_lib/baas.js +260 -0
  9. package/dist/_lib/baas.js.map +1 -0
  10. package/dist/_lib/cluster.d.ts +6 -6
  11. package/dist/_lib/cluster.js +6 -6
  12. package/dist/_lib/deploy-manifest.d.ts +69 -0
  13. package/dist/_lib/deploy-manifest.d.ts.map +1 -0
  14. package/dist/_lib/deploy-manifest.js +87 -0
  15. package/dist/_lib/deploy-manifest.js.map +1 -0
  16. package/dist/_lib/docker.d.ts +82 -0
  17. package/dist/_lib/docker.d.ts.map +1 -0
  18. package/dist/_lib/docker.js +164 -0
  19. package/dist/_lib/docker.js.map +1 -0
  20. package/dist/_lib/faucet-client.d.ts +64 -0
  21. package/dist/_lib/faucet-client.d.ts.map +1 -0
  22. package/dist/_lib/faucet-client.js +92 -0
  23. package/dist/_lib/faucet-client.js.map +1 -0
  24. package/dist/_lib/framework.d.ts +59 -0
  25. package/dist/_lib/framework.d.ts.map +1 -0
  26. package/dist/_lib/framework.js +149 -0
  27. package/dist/_lib/framework.js.map +1 -0
  28. package/dist/_lib/frontend-guard.d.ts +13 -0
  29. package/dist/_lib/frontend-guard.d.ts.map +1 -0
  30. package/dist/_lib/frontend-guard.js +41 -0
  31. package/dist/_lib/frontend-guard.js.map +1 -0
  32. package/dist/_lib/registry-fanout.d.ts +114 -0
  33. package/dist/_lib/registry-fanout.d.ts.map +1 -0
  34. package/dist/_lib/registry-fanout.js +435 -0
  35. package/dist/_lib/registry-fanout.js.map +1 -0
  36. package/dist/_lib/runtime-env.d.ts +126 -0
  37. package/dist/_lib/runtime-env.d.ts.map +1 -0
  38. package/dist/_lib/runtime-env.js +172 -0
  39. package/dist/_lib/runtime-env.js.map +1 -0
  40. package/dist/_lib/subscription-client.d.ts +129 -18
  41. package/dist/_lib/subscription-client.d.ts.map +1 -1
  42. package/dist/_lib/subscription-client.js +268 -40
  43. package/dist/_lib/subscription-client.js.map +1 -1
  44. package/dist/_lib/subscription-env.d.ts +35 -0
  45. package/dist/_lib/subscription-env.d.ts.map +1 -1
  46. package/dist/_lib/subscription-env.js +46 -2
  47. package/dist/_lib/subscription-env.js.map +1 -1
  48. package/dist/commands/app-lifecycle.d.ts +36 -0
  49. package/dist/commands/app-lifecycle.d.ts.map +1 -0
  50. package/dist/commands/app-lifecycle.js +145 -0
  51. package/dist/commands/app-lifecycle.js.map +1 -0
  52. package/dist/commands/ci.d.ts +45 -0
  53. package/dist/commands/ci.d.ts.map +1 -0
  54. package/dist/commands/ci.js +174 -0
  55. package/dist/commands/ci.js.map +1 -0
  56. package/dist/commands/deploy-manifest.d.ts +11 -0
  57. package/dist/commands/deploy-manifest.d.ts.map +1 -0
  58. package/dist/commands/deploy-manifest.js +17 -0
  59. package/dist/commands/deploy-manifest.js.map +1 -0
  60. package/dist/commands/deploy.d.ts +70 -11
  61. package/dist/commands/deploy.d.ts.map +1 -1
  62. package/dist/commands/deploy.js +263 -150
  63. package/dist/commands/deploy.js.map +1 -1
  64. package/dist/commands/downgrade.js +1 -1
  65. package/dist/commands/downgrade.js.map +1 -1
  66. package/dist/commands/faucet.d.ts +78 -0
  67. package/dist/commands/faucet.d.ts.map +1 -0
  68. package/dist/commands/faucet.js +184 -0
  69. package/dist/commands/faucet.js.map +1 -0
  70. package/dist/commands/governance.js +1 -1
  71. package/dist/commands/governance.js.map +1 -1
  72. package/dist/commands/hist-balance.d.ts +1 -1
  73. package/dist/commands/hist-balance.js +1 -1
  74. package/dist/commands/init.js +1 -1
  75. package/dist/commands/list.d.ts +22 -0
  76. package/dist/commands/list.d.ts.map +1 -0
  77. package/dist/commands/list.js +81 -0
  78. package/dist/commands/list.js.map +1 -0
  79. package/dist/commands/personhood.js +2 -2
  80. package/dist/commands/personhood.js.map +1 -1
  81. package/dist/commands/redeploy.d.ts +62 -0
  82. package/dist/commands/redeploy.d.ts.map +1 -0
  83. package/dist/commands/redeploy.js +310 -0
  84. package/dist/commands/redeploy.js.map +1 -0
  85. package/dist/commands/status.d.ts +51 -0
  86. package/dist/commands/status.d.ts.map +1 -0
  87. package/dist/commands/status.js +138 -0
  88. package/dist/commands/status.js.map +1 -0
  89. package/dist/commands/subscribe.d.ts +94 -17
  90. package/dist/commands/subscribe.d.ts.map +1 -1
  91. package/dist/commands/subscribe.js +450 -81
  92. package/dist/commands/subscribe.js.map +1 -1
  93. package/dist/commands/transactions.d.ts +60 -0
  94. package/dist/commands/transactions.d.ts.map +1 -0
  95. package/dist/commands/transactions.js +101 -0
  96. package/dist/commands/transactions.js.map +1 -0
  97. package/dist/commands/update.d.ts +40 -0
  98. package/dist/commands/update.d.ts.map +1 -0
  99. package/dist/commands/update.js +114 -0
  100. package/dist/commands/update.js.map +1 -0
  101. package/dist/commands/verify.d.ts +10 -19
  102. package/dist/commands/verify.d.ts.map +1 -1
  103. package/dist/commands/verify.js +14 -23
  104. package/dist/commands/verify.js.map +1 -1
  105. package/dist/index.d.ts +12 -3
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js +32 -3
  108. package/dist/index.js.map +1 -1
  109. package/package.json +11 -4
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @hsuite/smart-engines-cli
2
2
 
3
3
  `hsuite` — the developer bootstrap CLI for [Smart Engines V3](https://hsuite.finance).
4
- Three commands that take you from "empty directory" to "smart-app running
5
- against the live testnet cluster" in under a minute.
4
+ Takes you from "empty directory" to "smart-app running against the live testnet
5
+ cluster" in under a minute, then manages the deployment's lifecycle.
6
6
 
7
7
  ```bash
8
8
  npm install -g @hsuite/smart-engines-cli
@@ -10,8 +10,14 @@ npm install -g @hsuite/smart-engines-cli
10
10
 
11
11
  ```
12
12
  hsuite init # XRPL wallet + faucet + .env.local
13
- hsuite subscribe --env <file> # free_testnet smart-app registration on cluster
14
- hsuite deploy --manifest <f> # deploy a smart-app from a JSON manifest
13
+ hsuite subscribe --env <file> # paid smart-app subscription on cluster (tier builder|growth|scale|enterprise)
14
+
15
+ # deployment lifecycle (manifest-driven):
16
+ hsuite deploy --manifest <f> # THE canonical path — fresh deploy AND redeploy
17
+ hsuite redeploy --manifest <f> # thin convenience: distinct --app-id / --tag override
18
+ hsuite update --replicas 3 # patch runtime config (cpu/memory/degree/replicas/env)
19
+ hsuite suspend | resume | delete
20
+ hsuite status [--watch] | list
15
21
  ```
16
22
 
17
23
  XRPL-only. The CLI generates an XRPL keypair, funds it via the testnet
@@ -57,9 +63,8 @@ API_KEY_ENABLED=false
57
63
  CORS_ORIGINS=http://localhost:8101,http://localhost:8100,http://localhost:4200
58
64
  ...
59
65
 
60
- # Subscription — run `hsuite subscribe` to register a free_testnet smart-app
61
- SKIP_SUBSCRIPTION_CHECK=true
62
- SUBSCRIPTION_TIER=free_testnet
66
+ # Subscription — run `hsuite subscribe` to register a paid smart-app. Default tier is builder.
67
+ SUBSCRIPTION_TIER=builder
63
68
  # SUBSCRIPTION_APP_ID= # populated by `hsuite subscribe`
64
69
  ```
65
70
 
@@ -67,13 +72,15 @@ SUBSCRIPTION_TIER=free_testnet
67
72
 
68
73
  ## `hsuite subscribe`
69
74
 
70
- Register a `free_testnet` smart-app on the cluster. On testnet, the
71
- validator explicitly skips the deposit/NFT gate DKG runs, the smart-app
72
- goes ACTIVE immediately, and the cluster-issued `appId` becomes your
73
- smart-app's on-chain identity.
75
+ Register a paid smart-app subscription on the cluster. Every connection is
76
+ paid: pick a tier (`builder | growth | scale | enterprise`, default `builder`),
77
+ the cluster spins up a TSS deposit wallet and returns deposit instructions, and
78
+ the subscription NFT mints once the HSUITE deposit confirms on-chain. DKG runs
79
+ so the cluster-issued `appId` becomes your smart-app's on-chain identity.
74
80
 
75
81
  ```bash
76
82
  hsuite subscribe --env .env.local
83
+ hsuite subscribe --env .env.local --tier growth
77
84
  hsuite subscribe --env .env.local --name "My Smart App"
78
85
  hsuite subscribe --env .env.local --services database,storage,messaging
79
86
  hsuite subscribe --env .env.local --gateway https://my-cluster.example.com
@@ -85,52 +92,263 @@ Flow:
85
92
  2. Builds a signer using `ripple-keypairs.sign`
86
93
  3. Authenticates via `BaasClient.authenticate({chain: 'xrpl', ...})` against the cluster
87
94
  4. Calls `BaasClient.deployment.init({name, port, services})` — the new four-step deploy flow's step 1 (legacy `register()` removed in PR-A); the cluster runs the per-entity DKG ceremony and returns the DKG entityId as `appId`
88
- 5. Appends `SUBSCRIPTION_APP_ID=<appId>` and `APP_ID=<appId>` to your `.env.local`, flips `SKIP_SUBSCRIPTION_CHECK=false`
95
+ 5. Appends `SUBSCRIPTION_APP_ID=<appId>` and `APP_ID=<appId>` to your `.env.local`
89
96
 
90
97
  After this, your smart-app boots in cluster-mode automatically.
91
98
 
92
99
  ---
93
100
 
94
- ## `hsuite deploy`
101
+ ## Deployment commands
95
102
 
96
- Deploy a smart-app image from a JSON manifest via the SDK's four-step
97
- runtime-orchestration flow (`init` `docker push` optional
98
- `uploadFrontend` → `deploy`). Use this for CI pipelines that ship a
99
- container image to the cluster.
103
+ Eight commands take a smart-app from a manifest to a running, manageable
104
+ deployment on the cluster. They all share base flags:
100
105
 
101
- ```bash
102
- hsuite deploy --manifest ./my-app.json --env .env.local
103
- hsuite deploy --manifest ./my-app.json --env .env.local --non-interactive # for CI
104
- ```
106
+ | Flag | Default | Meaning |
107
+ |---|---|---|
108
+ | `--env <file>` | `.env.local` | Env file with the XRPL auth creds |
109
+ | `--gateway <url>` | `https://gateway.testnet.hsuite.network` | Public gateway/host URL |
110
+ | `--app-id <id>` | `DEPLOYED_APP_ID` → `SUBSCRIPTION_APP_ID` | App to act on |
111
+
112
+ Every command constructs exactly **one** `BaasClient`
113
+ (`{ hostUrl, appId, pathPrefix: '/host', allowInsecure, timeout }`) and
114
+ authenticates it with the env XRPL signer — connecting straight to the public
115
+ round-robin gateway (no cluster-discovery factory, whose per-cluster hostnames
116
+ aren't reachable from a dev machine).
117
+
118
+ ### The manifest (one canonical shape)
105
119
 
106
- `my-app.json`:
120
+ Source of truth: `packages/hsuite-cli/src/_lib/deploy-manifest.ts`.
107
121
 
108
- ```json
122
+ Every smart-app is a **NestJS backend**, optionally fronted by an **ionic SPA**.
123
+ The manifest has ONE shape: a required `backend` block plus an optional
124
+ `frontend` block.
125
+
126
+ ```jsonc
109
127
  {
110
128
  "name": "my-smart-app",
111
- "port": 3000,
112
- "tag": "v1",
113
- "replicas": 1,
114
- "services": ["database", "storage", "messaging", "functions"],
115
- "env": { "NODE_ENV": "production" },
116
- "frontend": { "bundlePath": "./dist/bundle.tar.gz" }
129
+ "services": ["database", "storage", "messaging", "functions"], // ('auth'|'database'|'storage'|'functions'|'messaging')[]
130
+ "degree": 3, // clusters to span (>=3; 3 is the floor)
131
+ "backend": { // REQUIRED — the NestJS image
132
+ "port": 3200, // backend listen port, 1..65535, NEVER 8080 (reserved for the edge sidecar)
133
+ "tag": "v1", // image tag (REQUIRED)
134
+ "replicas": 1, // pods per cluster (default 1)
135
+ "context": ".", // docker build context (default '.')
136
+ "dockerfile": "Dockerfile", // default 'Dockerfile'
137
+ "platform": "linux/amd64", // default 'linux/amd64' (cluster-node arch)
138
+ "build": "yarn build", // optional pre-build shell cmd, run in context
139
+ "env": { "NODE_ENV": "production" }, // NON-SECRET overrides only — merged over the auto-forwarded .env.local runtime env (manifest wins)
140
+ "limits": { "cpu": "500m", "memory": "512Mi" }
141
+ },
142
+ "frontend": { // OPTIONAL — ionic SPA, served by the EDGE sidecar (NOT the backend)
143
+ "build": "ng build", // optional pre-build command
144
+ "dir": "www", // dir to tar into the SPA bundle
145
+ "bundlePath": null // OR a pre-built tarball (wins over `dir`)
146
+ }
117
147
  }
118
148
  ```
119
149
 
120
- What the command does, in order:
150
+ #### Runtime env: auto-forwarded from `.env.local` (do NOT hand-copy secrets)
151
+
152
+ `hsuite deploy` **automatically forwards your `.env.local` runtime creds into the
153
+ deployed container** — so the deployed app runs with the **same identity + config
154
+ it runs with locally**. The deployed smart-app authenticates as **your own XRPL
155
+ wallet** (the `XRPL_SEED` in `.env.local`, whose address holds the subscription
156
+ NFT); there is no separate app identity. You therefore **never hand-copy
157
+ `XRPL_SEED` / `HEDERA_PRIVATE_KEY` / chain creds into a committed manifest** — a
158
+ manual step that, if forgotten, silently shipped a **credential-less, open-mode**
159
+ app (`/baas/status` → `{"mode":"memory","appId":null}`; BaaS routes 503). See
160
+ issue #1564.
161
+
162
+ **What gets forwarded:** the runtime-relevant keys from your `.env.local` — the
163
+ same set the smart-app's env schema reads (`XRPL_*`, `SUBSCRIPTION_APP_ID`,
164
+ `APP_*`, `VALIDATOR_URL`/`SMART_HOST_URL`, AI/Discord config, …). Purely
165
+ deploy-time / CLI-only knobs (e.g. `SUBSCRIPTION_NFT_SERIAL`, `VALIDATOR_API_KEY`)
166
+ are **not** forwarded. The list lives in
167
+ `packages/hsuite-cli/src/_lib/runtime-env.ts` (`RUNTIME_ENV_KEYS`), mirroring the
168
+ smart-app schema (`apps/showcase/smart-app/src/config/env-validation.ts` in the
169
+ hsuite-ecosystem repo).
170
+
171
+ **Precedence:** the forwarded `.env.local` values are the base;
172
+ **`manifest.backend.env` is overlaid last and WINS** for any key it sets. Use
173
+ `backend.env` for explicit **non-secret** per-deploy overrides (ports, feature
174
+ flags, Discord/AI endpoints). Secrets flow automatically from `.env.local` and do
175
+ not belong in a committed manifest. Everything lands in the app's **PQC-encrypted
176
+ (kyber-aes-v1) per-app customer-env Secret** — the pod's runtime env, `envFrom`-
177
+ mounted on the backend container. Encryption at rest is the containment boundary;
178
+ no secret is ever written into a committed file.
179
+
180
+ **Fail-loud:** if a deploy would ship the app **credential-less** (no `XRPL_SEED`
181
+ reaching the container) the CLI **errors** rather than silently booting open mode.
182
+ An intentional open-mode deploy stays valid via the explicit `--allow-open-mode`
183
+ flag, which deliberately **withholds** the seed from the container (open mode)
184
+ while still using it locally to sign the required build attestation.
185
+
186
+ > The same forwarding + fail-loud applies to `hsuite redeploy`.
187
+
188
+ Backend-only example (no `frontend` block — the edge proxies all paths to the
189
+ backend):
190
+
191
+ ```jsonc
192
+ {
193
+ "name": "my-api",
194
+ "services": ["database"],
195
+ "degree": 3,
196
+ "backend": { "port": 3200, "tag": "v1" }
197
+ }
198
+ ```
121
199
 
122
- 1. **`deployment.init`** cluster allocates the `appId` (per-entity DKG
123
- entityId) and returns ephemeral DOCR push credentials.
124
- 2. **(out-of-band)** the CLI prints the `docker login` + `docker push`
125
- commands and waits for Enter (skipped with `--non-interactive`,
126
- `CI=true`, or no TTY the caller is assumed to have completed the
127
- push).
128
- 3. **`deployment.uploadFrontend`** if `frontend.bundlePath` is set, the
129
- tarball is uploaded.
130
- 4. **`deployment.deploy`** the cluster reconciles the requested `tag`
131
- + `replicas` + `env` to k8s.
200
+ > Relative `frontend.dir` / `frontend.bundlePath` (and the frontend `build` hook)
201
+ > resolve against the backend's build `context` (the smart-app root) — NOT the
202
+ > directory you ran `hsuite` from. A bare `"public"` means
203
+ > `<smart-app>/public`. `degree` is validated client-side against the host floor
204
+ > (`>= 3`) so a bad value fails with a clear CLI error instead of a server 500.
205
+
206
+ ### Serving model: the public-vs-edge split
207
+
208
+ Every deployed customer pod has **two containers**:
209
+
210
+ - The customer **backend** listens on **loopback** at `backend.port` (e.g.
211
+ `3200`). It is NOT exposed at the pod boundary, and `8080` is **forbidden**
212
+ for the backend.
213
+ - A platform **edge sidecar** (Caddy) listens on port `8080` — the pod's
214
+ exposed/Service port. The edge reverse-proxies API requests to the backend on
215
+ loopback and serves the SPA.
216
+
217
+ The ionic SPA is served by the **edge sidecar, not the backend**. The optional
218
+ `frontend` block is tarred and uploaded (`uploadFrontend`) into the cluster BaaS
219
+ storage tier, content-addressed by sha256. At pod start a **bundle-fetcher init
220
+ container** fetches that bundle into a shared emptyDir, which the edge serves
221
+ read-only from `/srv/spa`, falling back to the SPA shell (`index.html`) for
222
+ client-side routing. For a **backend-only** app (no `frontend` block) the edge
223
+ proxies all paths to the backend.
224
+
225
+ > The SPA is also commonly baked into the backend image's own `public/` — but in
226
+ > the deployed pod it is the **edge-served uploaded bundle** that the public sees
227
+ > on port `8080`.
228
+ >
229
+ > (Refs: `apps/smart-deployer/src/reconciler/caddyfile-renderer.service.ts`,
230
+ > `apps/smart-deployer/src/reconciler/k8s-resource-builder.service.ts`.)
231
+
232
+ ### Multi-cluster image distribution
233
+
234
+ `harbor.testnet.hsuite.network` is **public-DNS round-robin** over the 3 cluster
235
+ ingress IPs, each fronting its own Harbor. A single `docker push` lands the image
236
+ on **only one** cluster. `hsuite deploy` therefore **fans the image out** to every
237
+ cluster Harbor itself, synchronously, BEFORE reconciling: it resolves the
238
+ registry host's A-records, copies the pushed image to each cluster at the
239
+ Registry-v2 level (TLS-SNI pinned to the federated hostname, 401/Bearer token
240
+ auth, blob copy + manifest PUT), then **re-verifies the exact pushed digest is
241
+ served by every cluster and ABORTS the deploy** if any cluster can't be made to
242
+ serve it. This replaces reliance on the (unreliable) Harbor cross-cluster
243
+ replication mesh for customer images — the mesh is no longer load-bearing. The
244
+ push-robot credential is BLS-deterministic and identical on all 3 cluster
245
+ Harbors, which is what lets one credential authenticate the fan-out against every
246
+ cluster. `--skip-distribute` opts out (single-cluster / dev registry).
247
+ (Implementation: `packages/hsuite-cli/src/_lib/registry-fanout.ts`.)
248
+
249
+ ### Deploy strategy (Recreate vs RollingUpdate)
250
+
251
+ The strategy is driven by replica count
252
+ (`apps/smart-deployer/src/reconciler/k8s-resource-builder.service.ts`
253
+ `buildDeployment`):
254
+
255
+ - **Single-replica apps (`replicas <= 1`) use `Recreate`.** A single-replica
256
+ tenant CANNOT surge — the per-tenant `ResourceQuota` only fits one pod — so
257
+ `RollingUpdate` would deadlock. Pods are stateless (no RWO PVC), so
258
+ Recreate-in-place is safe.
259
+ - **Multi-replica apps use zero-downtime `RollingUpdate{maxSurge:1,
260
+ maxUnavailable:0}`.**
261
+
262
+ The `strategy` field on the manifest/deploy request is **advisory**: a
263
+ `recreate` request is coerced to `RollingUpdate` for multi-replica apps (with a
264
+ warning).
265
+
266
+ ### `hsuite deploy` — THE canonical deploy path
267
+
268
+ `hsuite deploy --manifest <file>` is the **single, canonical path** to deploy a
269
+ customer smart-app (NestJS backend + optional ionic SPA) across the 3-cluster
270
+ testnet. It handles BOTH a fresh deploy AND a redeploy of an existing/active
271
+ app. There is no hand-rolled `deploy-*.cjs` script and no manual per-cluster
272
+ `docker push` — exactly one path: `hsuite deploy`.
273
+
274
+ **Mint-first / deploy-last:** the developer's `SUBSCRIPTION_APP_ID` IS the deploy
275
+ target (created + paid by `hsuite subscribe`; `SUBSCRIPTION_APP_ID ==
276
+ DEPLOYED_APP_ID == DKG entityId`). `deploy` **allocates nothing** — it
277
+ authenticates as that id and fetches push creds for it (`pushCredentials`,
278
+ idempotent).
279
+
280
+ Flow: resolve `SUBSCRIPTION_APP_ID` → `connectAndAuth` → `pushCredentials` →
281
+ docker build+push (backend) → **fan the image out to every cluster Harbor**
282
+ (verify + abort on any miss) → tar + `uploadFrontend` (frontend) → sign a build
283
+ attestation → `deploy()`. Writes `DEPLOYED_APP_ID` to the env file.
132
284
 
133
- The returned `appId` is appended to `.env.local` as `DEPLOYED_APP_ID`.
285
+ ```bash
286
+ hsuite deploy --manifest ./my-app.json --env .env.local
287
+ hsuite deploy --manifest ./my-app.json --strategy recreate
288
+ hsuite deploy --manifest ./my-app.json --docker-host unix:///path/docker.sock
289
+ hsuite deploy --manifest ./my-app.json --skip-build --skip-push # image already pushed
290
+ hsuite deploy --manifest ./my-app.json --skip-distribute # single-cluster / dev registry
291
+ hsuite deploy --manifest ./my-app.json --subscription-app-id sub_… # override the deploy target
292
+ ```
293
+
294
+ Flags: `--manifest <file>`, `--env <file>` (default `.env.local`), `--project
295
+ <dir>` (SDK/framework detection root), `--gateway <url>`, `--subscription-app-id
296
+ <id>`, `--strategy rolling|recreate`, `--docker-host <host>` (defaults to
297
+ `$DOCKER_HOST`), `--skip-build`, `--skip-push`, `--skip-distribute`,
298
+ `--allow-open-mode` (permit an intentional zero-cred / open-mode deploy; default
299
+ OFF — a subscribed-but-credential-less deploy fails loud), `--non-interactive`.
300
+
301
+ The docker login uses the init-issued one-time robot creds via
302
+ `execFileSync('docker', ['login', server, '-u', username, '--password-stdin'])`
303
+ — the robot username (`robot$project+name`) is a discrete argv element and the
304
+ password is fed on stdin, so the `$` is never shell-expanded.
305
+
306
+ ### `hsuite redeploy` — thin convenience
307
+
308
+ `redeploy` is NOT a separate required path — `hsuite deploy` already redeploys an
309
+ existing/active app. `redeploy` is a thin convenience for passing a **distinct
310
+ deployed `--app-id`** and/or a **`--tag` override**. It rebuilds + pushes the
311
+ backend image, fans it out to every cluster (same `--skip-distribute` opt-out),
312
+ re-uploads the SPA bundle when the manifest carries a `frontend` block, then
313
+ `deploy()`s.
314
+
315
+ ```bash
316
+ hsuite redeploy --manifest ./my-app.json --tag v2
317
+ hsuite redeploy --manifest ./my-app.json --app-id app_… --strategy recreate
318
+ hsuite redeploy --manifest ./my-app.json --skip-distribute
319
+ ```
320
+
321
+ ### `hsuite update`
322
+
323
+ Patch runtime config without a new image — at least one field required.
324
+
325
+ ```bash
326
+ hsuite update --cpu 500m --memory 512Mi
327
+ hsuite update --replicas 3 --degree 3
328
+ hsuite update --strategy recreate
329
+ hsuite update --env-set NODE_ENV=production --env-set LOG_LEVEL=info
330
+ ```
331
+
332
+ ### `hsuite suspend` / `resume` / `delete`
333
+
334
+ ```bash
335
+ hsuite suspend # scale the runtime to zero
336
+ hsuite resume # scale it back up
337
+ hsuite delete # tear down (confirms; --yes to skip, required when non-TTY)
338
+ hsuite delete --yes
339
+ ```
340
+
341
+ ### `hsuite status` / `list`
342
+
343
+ ```bash
344
+ hsuite status # state + runtime (runtimeState / replicas / lastError)
345
+ hsuite status --watch # poll until RUNNING / FAILED
346
+ hsuite status --json
347
+ hsuite list # table of {appId, name, status}
348
+ hsuite list --json
349
+ ```
350
+
351
+ The deployed `appId` is appended to `.env.local` as `DEPLOYED_APP_ID`.
134
352
 
135
353
  ---
136
354
 
@@ -141,7 +359,7 @@ mkdir my-smart-app && cd my-smart-app
141
359
  npm install -g @hsuite/smart-engines-cli
142
360
 
143
361
  hsuite init # → .env.local with funded XRPL wallet
144
- hsuite subscribe --env .env.local # → free_testnet appId on cluster
362
+ hsuite subscribe --env .env.local # → paid (builder) subscription appId on cluster
145
363
  cat .env.local | grep XRPL_ADDRESS # → your wallet address
146
364
  cat .env.local | grep SUBSCRIPTION_APP_ID # → your smart-app's DKG entity ID
147
365
  ```
@@ -156,30 +374,26 @@ That's it — you now have:
156
374
  ## What the CLI does NOT do
157
375
 
158
376
  - **Pay HCS fees.** The cluster's validator/host pods do that from their own pod-local Hedera operator accounts. The smart-app never sees Hedera credentials.
159
- - **Mint a subscription NFT for `free_testnet`.** The validator's smart-app service explicitly skips this gate on testnet the DKG entity ID IS the on-chain registration proof. Paid-tier NFT minting is a separate post-launch flow.
377
+ - **Confirm the HSUITE deposit for you.** `subscribe` creates the subscription and returns deposit instructions, but you fund the TSS deposit wallet yourself; the subscription NFT mints (and the smart-app goes ACTIVE) only once that deposit confirms on-chain.
160
378
  - **Configure your smart-app's business logic.** If your smart-app does Hedera-side or Solana-side or any other-chain ops as part of its app logic, that's separate from the dev wallet and you configure it in your app's own code/env.
161
379
 
162
380
  ---
163
381
 
164
382
  ## Use programmatically
165
383
 
166
- Each command is exported for scripting:
384
+ The `hsuite` package is a **CLI only** — its entry point (`dist/index.js`) is an executable that parses `process.argv` on load and exposes no importable API. There are no `initWallet` / `subscribeFreeTestnet` / `deploySmartApp` functions to import.
385
+
386
+ For programmatic access from your own code, use the SDK directly — see [Companion packages](#companion-packages):
167
387
 
168
388
  ```ts
169
- import { initWallet, subscribeFreeTestnet, deploySmartApp } from '@hsuite/smart-engines-cli';
170
-
171
- await initWallet({
172
- out: '.env.local',
173
- network: 'testnet',
174
- gateway: 'https://v3-testnet-gateway.hsuite.network',
175
- fund: true,
176
- });
177
-
178
- await subscribeFreeTestnet({
179
- env: '.env.local',
180
- gateway: 'https://v3-testnet-gateway.hsuite.network',
181
- services: 'database,storage,messaging,functions',
182
- });
389
+ import { /* ... */ } from '@hsuite/smart-engines-sdk';
390
+ ```
391
+
392
+ To automate the CLI itself, shell out to the `hsuite` binary from your build scripts or CI:
393
+
394
+ ```sh
395
+ hsuite init --out .env.local
396
+ hsuite subscribe --env .env.local --tier builder
183
397
  ```
184
398
 
185
399
  ---
@@ -0,0 +1,45 @@
1
+ import { type BuildAttestation } from '@hsuite/smart-engines-sdk';
2
+ import type { Framework } from './framework';
3
+ /** Everything needed to assemble an attestation, minus `version`/`builtAt`. */
4
+ export type AttestationInputs = {
5
+ appId: string;
6
+ developerWallet: string;
7
+ developerChain: BuildAttestation['developerChain'];
8
+ framework: Framework;
9
+ sdkVersion: string;
10
+ imageDigest: string;
11
+ /** '' for backend-only apps (no frontend bundle). */
12
+ bundleSha256: string;
13
+ cliVersion: string;
14
+ /** Injectable clock for deterministic tests; defaults to `Date.now()`. */
15
+ now?: () => number;
16
+ };
17
+ /** A signed attestation: the payload plus the developer's `<pub>.<sig>` string. */
18
+ export type SignedAttestation = {
19
+ payload: BuildAttestation;
20
+ signature: string;
21
+ };
22
+ /**
23
+ * Read this CLI's own version from its `package.json`, for `cliVersion`.
24
+ *
25
+ * `__dirname` at runtime is `dist/_lib`, so the package root is two levels up.
26
+ * Falls back to `'0.0.0'` if it can't be read (mirrors index.ts's getVersion).
27
+ */
28
+ export declare function readCliVersion(): string;
29
+ /**
30
+ * Assemble a {@link BuildAttestation} from resolved inputs. Pins `version: '1'`
31
+ * and stamps `builtAt` from the injectable clock (default `Date.now()`).
32
+ *
33
+ * Field order here is cosmetic — the SDK canonicalizer sorts keys before
34
+ * hashing — but mirrors the type for readability.
35
+ */
36
+ export declare function assembleAttestation(inputs: AttestationInputs): BuildAttestation;
37
+ /**
38
+ * Assemble + sign in one step with the developer's XRPL seed, returning the
39
+ * `{ payload, signature }` envelope the deploy request carries.
40
+ *
41
+ * Delegates the signature to the frozen SDK primitive so the bytes the server
42
+ * verifies are produced by the canonical code path.
43
+ */
44
+ export declare function assembleAndSign(inputs: AttestationInputs, xrplSeed: string): SignedAttestation;
45
+ //# sourceMappingURL=attestation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../../src/_lib/attestation.ts"],"names":[],"mappings":"AAmBA,OAAO,EACL,KAAK,gBAAgB,EAEtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,+EAA+E;AAC/E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;IACnD,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,CAAC;AAEF,mFAAmF;AACnF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAQvC;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,iBAAiB,GAAG,gBAAgB,CAc/E;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,iBAAiB,EACzB,QAAQ,EAAE,MAAM,GACf,iBAAiB,CAInB"}
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readCliVersion = readCliVersion;
4
+ exports.assembleAttestation = assembleAttestation;
5
+ exports.assembleAndSign = assembleAndSign;
6
+ /**
7
+ * CLI-side assembly of a {@link BuildAttestation}.
8
+ *
9
+ * The cryptographic primitive (`signAttestation`, the `BuildAttestation` type,
10
+ * the canonicalizer) lives in `@hsuite/smart-engines-sdk` and is FROZEN — this
11
+ * module only imports it. The SDK primitive is deliberately clock-free and
12
+ * pure; the one impure input, `builtAt`, is supplied HERE (the CLI is allowed
13
+ * to read the clock).
14
+ *
15
+ * `assembleAttestation` gathers every field from already-resolved inputs:
16
+ * - developerWallet / developerChain : the env XRPL identity (env.ts signer)
17
+ * - sdkVersion : installed/declared SDK (framework.ts)
18
+ * - cliVersion : this CLI's package.json version
19
+ * - imageDigest : the pushed image's manifest digest
20
+ * - bundleSha256 : uploadFrontend's server-computed hash
21
+ * - builtAt : Date.now() at call time
22
+ */
23
+ const fs_1 = require("fs");
24
+ const path_1 = require("path");
25
+ const smart_engines_sdk_1 = require("@hsuite/smart-engines-sdk");
26
+ /**
27
+ * Read this CLI's own version from its `package.json`, for `cliVersion`.
28
+ *
29
+ * `__dirname` at runtime is `dist/_lib`, so the package root is two levels up.
30
+ * Falls back to `'0.0.0'` if it can't be read (mirrors index.ts's getVersion).
31
+ */
32
+ function readCliVersion() {
33
+ try {
34
+ const pkgPath = (0, path_1.join)(__dirname, '..', '..', 'package.json');
35
+ const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf8'));
36
+ return pkg.version ?? '0.0.0';
37
+ }
38
+ catch {
39
+ return '0.0.0';
40
+ }
41
+ }
42
+ /**
43
+ * Assemble a {@link BuildAttestation} from resolved inputs. Pins `version: '1'`
44
+ * and stamps `builtAt` from the injectable clock (default `Date.now()`).
45
+ *
46
+ * Field order here is cosmetic — the SDK canonicalizer sorts keys before
47
+ * hashing — but mirrors the type for readability.
48
+ */
49
+ function assembleAttestation(inputs) {
50
+ const now = inputs.now ?? Date.now;
51
+ return {
52
+ version: '1',
53
+ appId: inputs.appId,
54
+ developerWallet: inputs.developerWallet,
55
+ developerChain: inputs.developerChain,
56
+ framework: inputs.framework,
57
+ sdkVersion: inputs.sdkVersion,
58
+ imageDigest: inputs.imageDigest,
59
+ bundleSha256: inputs.bundleSha256,
60
+ cliVersion: inputs.cliVersion,
61
+ builtAt: now(),
62
+ };
63
+ }
64
+ /**
65
+ * Assemble + sign in one step with the developer's XRPL seed, returning the
66
+ * `{ payload, signature }` envelope the deploy request carries.
67
+ *
68
+ * Delegates the signature to the frozen SDK primitive so the bytes the server
69
+ * verifies are produced by the canonical code path.
70
+ */
71
+ function assembleAndSign(inputs, xrplSeed) {
72
+ const payload = assembleAttestation(inputs);
73
+ const signature = (0, smart_engines_sdk_1.signAttestation)(payload, xrplSeed);
74
+ return { payload, signature };
75
+ }
76
+ //# sourceMappingURL=attestation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attestation.js","sourceRoot":"","sources":["../../src/_lib/attestation.ts"],"names":[],"mappings":";;AAoDA,wCAQC;AASD,kDAcC;AASD,0CAOC;AAnGD;;;;;;;;;;;;;;;;GAgBG;AACH,2BAAkC;AAClC,+BAA4B;AAC5B,iEAGmC;AAwBnC;;;;;GAKG;AACH,SAAgB,cAAc;IAC5B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAA,WAAI,EAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,iBAAY,EAAC,OAAO,EAAE,MAAM,CAAC,CAAyB,CAAC;QAC9E,OAAO,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,mBAAmB,CAAC,MAAyB;IAC3D,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACnC,OAAO;QACL,OAAO,EAAE,GAAG;QACZ,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,OAAO,EAAE,GAAG,EAAE;KACf,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,eAAe,CAC7B,MAAyB,EACzB,QAAgB;IAEhB,MAAM,OAAO,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,IAAA,mCAAkB,EAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACxD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AAChC,CAAC"}