settld 0.2.2 → 0.2.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/README.md +11 -0
- package/docs/QUICKSTART_MCP_HOSTS.md +29 -8
- package/docs/README.md +5 -4
- package/docs/gitbook/README.md +5 -1
- package/docs/gitbook/quickstart.md +11 -4
- package/docs/integrations/README.md +1 -1
- package/docs/integrations/openclaw/CLAWHUB_PUBLISH_CHECKLIST.md +1 -1
- package/docs/integrations/openclaw/PUBLIC_QUICKSTART.md +7 -5
- package/docs/integrations/openclaw/settld-mcp-skill/SKILL.md +19 -4
- package/docs/integrations/openclaw/settld-mcp-skill/mcp-server.example.json +3 -5
- package/docs/integrations/openclaw/settld-mcp-skill/skill.json +11 -0
- package/package.json +1 -1
- package/scripts/setup/login.mjs +49 -18
- package/scripts/setup/onboard.mjs +328 -58
package/README.md
CHANGED
|
@@ -73,6 +73,16 @@ Agent host onboarding (Codex / Claude / Cursor / OpenClaw), with guided wallet +
|
|
|
73
73
|
npx -y settld setup
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
Default interactive flow is now login-first:
|
|
77
|
+
|
|
78
|
+
1. pick host + wallet mode
|
|
79
|
+
2. choose `quick` setup (recommended)
|
|
80
|
+
3. login with OTP (creates tenant if needed)
|
|
81
|
+
4. setup mints runtime API key automatically
|
|
82
|
+
5. guided wallet fund + first paid call check runs
|
|
83
|
+
|
|
84
|
+
Advanced mode is still available in setup when you need explicit base URL/bootstrap/API-key control.
|
|
85
|
+
|
|
76
86
|
Preflight-only check (no host config write), with JSON report:
|
|
77
87
|
|
|
78
88
|
```sh
|
|
@@ -89,6 +99,7 @@ settld setup
|
|
|
89
99
|
Check wallet wiring and funding path:
|
|
90
100
|
|
|
91
101
|
```sh
|
|
102
|
+
settld login
|
|
92
103
|
settld wallet status
|
|
93
104
|
settld wallet fund --open
|
|
94
105
|
settld wallet fund --method transfer
|
|
@@ -13,16 +13,27 @@ For deeper tool-level examples, see `docs/QUICKSTART_MCP.md`.
|
|
|
13
13
|
|
|
14
14
|
## 1) Before you run `settld setup`
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Public default path (recommended):
|
|
17
17
|
|
|
18
|
-
-
|
|
19
|
-
-
|
|
18
|
+
- Node.js 20+
|
|
19
|
+
- no API keys required up front
|
|
20
|
+
- run `settld setup`, choose `quick`, then login with OTP
|
|
21
|
+
- setup creates tenant (if needed), mints runtime key, and wires MCP
|
|
22
|
+
|
|
23
|
+
Admin/operator path (advanced):
|
|
24
|
+
|
|
25
|
+
- explicit `SETTLD_BASE_URL`, `SETTLD_TENANT_ID`
|
|
20
26
|
- one of:
|
|
21
27
|
- `SETTLD_API_KEY` (`keyId.secret`), or
|
|
22
|
-
- `SETTLD_BOOTSTRAP_API_KEY` (
|
|
23
|
-
|
|
28
|
+
- `SETTLD_BOOTSTRAP_API_KEY` (bootstrap key that mints runtime key)
|
|
29
|
+
|
|
30
|
+
Recommended interactive pattern:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
settld setup
|
|
34
|
+
```
|
|
24
35
|
|
|
25
|
-
Recommended non-interactive pattern:
|
|
36
|
+
Recommended non-interactive pattern (automation/support):
|
|
26
37
|
|
|
27
38
|
```bash
|
|
28
39
|
settld setup --non-interactive \
|
|
@@ -37,7 +48,7 @@ settld setup --non-interactive \
|
|
|
37
48
|
--out-env ./.tmp/settld-openclaw.env
|
|
38
49
|
```
|
|
39
50
|
|
|
40
|
-
If you want setup to generate the tenant API key
|
|
51
|
+
If you want non-interactive setup to generate the tenant API key:
|
|
41
52
|
|
|
42
53
|
```bash
|
|
43
54
|
settld setup --non-interactive \
|
|
@@ -73,14 +84,18 @@ Unified setup command:
|
|
|
73
84
|
settld setup
|
|
74
85
|
```
|
|
75
86
|
|
|
76
|
-
|
|
87
|
+
`quick` mode (default) handles:
|
|
77
88
|
|
|
78
89
|
- host selection (`codex|claude|cursor|openclaw`)
|
|
79
90
|
- wallet mode selection (`managed|byo|none`)
|
|
91
|
+
- login/signup + OTP session flow (no manual key paste)
|
|
80
92
|
- preflight checks (API health, tenant auth, profile baseline, host config path)
|
|
81
93
|
- policy apply + optional smoke
|
|
94
|
+
- guided wallet funding and first paid MCP check
|
|
82
95
|
- interactive menus with arrow keys (Up/Down + Enter) for choice steps
|
|
83
96
|
|
|
97
|
+
`advanced` mode exposes explicit key/bootstrap/base-url prompts and fine-grained setup toggles.
|
|
98
|
+
|
|
84
99
|
Host-specific non-interactive examples:
|
|
85
100
|
|
|
86
101
|
```bash
|
|
@@ -177,6 +192,12 @@ Check wallet assignment after setup:
|
|
|
177
192
|
settld wallet status
|
|
178
193
|
```
|
|
179
194
|
|
|
195
|
+
If wallet commands return auth errors, run:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
settld login
|
|
199
|
+
```
|
|
200
|
+
|
|
180
201
|
Funding paths:
|
|
181
202
|
|
|
182
203
|
```bash
|
package/docs/README.md
CHANGED
|
@@ -17,10 +17,11 @@ For curated public docs, start here:
|
|
|
17
17
|
|
|
18
18
|
## Fastest onboarding path
|
|
19
19
|
|
|
20
|
-
1. Run `settld setup` (or `./bin/settld.js setup`)
|
|
21
|
-
2.
|
|
22
|
-
3.
|
|
23
|
-
4.
|
|
20
|
+
1. Run `settld setup` (or `./bin/settld.js setup`), choose `quick`, and complete OTP login.
|
|
21
|
+
2. Let guided wallet funding complete (or run `settld wallet fund` + `settld wallet balance --watch --min-usdc 1`).
|
|
22
|
+
3. Activate your host and run `npm run mcp:probe`.
|
|
23
|
+
4. Run `npm run demo:mcp-paid-exa`.
|
|
24
|
+
5. Verify the first receipt:
|
|
24
25
|
|
|
25
26
|
```bash
|
|
26
27
|
jq -c 'first' artifacts/mcp-paid-exa/*/x402-receipts.export.jsonl > /tmp/settld-first-receipt.json
|
package/docs/gitbook/README.md
CHANGED
|
@@ -19,9 +19,13 @@ Settld gives you a canonical economic loop:
|
|
|
19
19
|
## One-command onboarding
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
settld setup
|
|
22
|
+
settld setup
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
Recommended path: choose `quick`, complete OTP login, and let setup run guided funding + paid-call checks.
|
|
26
|
+
|
|
27
|
+
Advanced/scripted path is still supported with explicit non-interactive flags.
|
|
28
|
+
|
|
25
29
|
Then:
|
|
26
30
|
|
|
27
31
|
1. `npm run mcp:probe -- --call settld.about '{}'`
|
|
@@ -5,9 +5,8 @@ Get from zero to a verified paid agent action in minutes.
|
|
|
5
5
|
## Prerequisites
|
|
6
6
|
|
|
7
7
|
- Node.js 20+
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
- Tenant API key (`keyId.secret`)
|
|
8
|
+
- Public flow: no API key required up front (`settld setup` handles login/session bootstrap)
|
|
9
|
+
- Advanced flow: optional explicit `--base-url`, `--tenant-id`, and `--settld-api-key`
|
|
11
10
|
|
|
12
11
|
## 0) One-command setup
|
|
13
12
|
|
|
@@ -17,7 +16,14 @@ Run guided setup:
|
|
|
17
16
|
settld setup
|
|
18
17
|
```
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
Recommended interactive choices:
|
|
20
|
+
|
|
21
|
+
1. host
|
|
22
|
+
2. `quick` setup mode
|
|
23
|
+
3. wallet mode
|
|
24
|
+
4. OTP login (creates tenant if needed)
|
|
25
|
+
|
|
26
|
+
`quick` mode auto-runs preflight/smoke/profile apply, then starts guided wallet fund + first paid call checks.
|
|
21
27
|
|
|
22
28
|
Non-interactive example:
|
|
23
29
|
|
|
@@ -54,6 +60,7 @@ Then restart your host app (Codex/Claude/Cursor/OpenClaw) so it reloads MCP conf
|
|
|
54
60
|
## 2) Check wallet and fund it
|
|
55
61
|
|
|
56
62
|
```bash
|
|
63
|
+
settld login
|
|
57
64
|
settld wallet status
|
|
58
65
|
settld wallet fund --method transfer
|
|
59
66
|
settld wallet balance --watch --min-usdc 1
|
|
@@ -5,7 +5,7 @@ Copy/paste adoption templates and guardrails:
|
|
|
5
5
|
- `github-actions.md` — composite action usage and trust anchor wiring.
|
|
6
6
|
- `github-actions-verify.yml` — pasteable workflow template.
|
|
7
7
|
- `openclaw/PUBLIC_QUICKSTART.md` — public npm onboarding flow for OpenClaw (`npx settld@latest setup`).
|
|
8
|
-
- `openclaw/settld-mcp-skill/SKILL.md` — OpenClaw skill payload for Settld MCP.
|
|
8
|
+
- `openclaw/settld-mcp-skill/SKILL.md` + `openclaw/settld-mcp-skill/skill.json` — OpenClaw skill payload + manifest for Settld MCP.
|
|
9
9
|
- `openclaw/CLAWHUB_PUBLISH_CHECKLIST.md` — publish + validation checklist for ClawHub.
|
|
10
10
|
|
|
11
11
|
See also:
|
|
@@ -15,6 +15,7 @@ Confirm required files exist:
|
|
|
15
15
|
|
|
16
16
|
- `docs/integrations/openclaw/settld-mcp-skill/SKILL.md`
|
|
17
17
|
- `docs/integrations/openclaw/settld-mcp-skill/mcp-server.example.json`
|
|
18
|
+
- `docs/integrations/openclaw/settld-mcp-skill/skill.json`
|
|
18
19
|
|
|
19
20
|
## 2) Prepare Skill Metadata
|
|
20
21
|
|
|
@@ -62,4 +63,3 @@ Capture these fields each publish:
|
|
|
62
63
|
- Added/changed tools
|
|
63
64
|
- Known limitations
|
|
64
65
|
- Validation run timestamp
|
|
65
|
-
|
|
@@ -33,10 +33,10 @@ npx -y settld@latest setup
|
|
|
33
33
|
Choose:
|
|
34
34
|
|
|
35
35
|
1. `host`: `openclaw`
|
|
36
|
-
2.
|
|
37
|
-
3. wallet
|
|
38
|
-
4.
|
|
39
|
-
5.
|
|
36
|
+
2. setup mode: `quick`
|
|
37
|
+
3. wallet mode (`managed` recommended first)
|
|
38
|
+
4. login with OTP (new tenant is created if needed)
|
|
39
|
+
5. let setup run guided fund + first paid check
|
|
40
40
|
|
|
41
41
|
Non-interactive path (automation/support):
|
|
42
42
|
|
|
@@ -53,7 +53,7 @@ npx -y settld@latest setup \
|
|
|
53
53
|
--smoke
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
Advanced non-interactive key/bootstrap paths are still supported:
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
59
|
npx -y settld@latest setup \
|
|
@@ -102,7 +102,9 @@ npx -y settld@latest x402 receipt verify ./receipt.json --format json
|
|
|
102
102
|
## Notes for operators
|
|
103
103
|
|
|
104
104
|
- Public users do not need to clone the Settld repo.
|
|
105
|
+
- Public users should not need bootstrap/admin keys in the default setup path.
|
|
105
106
|
- Public path is valid only after publishing a package version that includes the current setup flow.
|
|
106
107
|
- For OpenClaw skill packaging and publish flow, see:
|
|
107
108
|
- `docs/integrations/openclaw/settld-mcp-skill/SKILL.md`
|
|
109
|
+
- `docs/integrations/openclaw/settld-mcp-skill/skill.json`
|
|
108
110
|
- `docs/integrations/openclaw/CLAWHUB_PUBLISH_CHECKLIST.md`
|
|
@@ -9,6 +9,14 @@ author: Settld
|
|
|
9
9
|
|
|
10
10
|
This skill teaches OpenClaw agents to use Settld for paid MCP tool calls.
|
|
11
11
|
|
|
12
|
+
It is designed for the public `quick` onboarding flow:
|
|
13
|
+
|
|
14
|
+
1. `settld setup`
|
|
15
|
+
2. pick `openclaw` + `quick`
|
|
16
|
+
3. login via OTP
|
|
17
|
+
4. fund wallet
|
|
18
|
+
5. run paid tool call with deterministic receipt evidence
|
|
19
|
+
|
|
12
20
|
## What This Skill Enables
|
|
13
21
|
|
|
14
22
|
- Discover Settld MCP tools (`settld.*`)
|
|
@@ -19,9 +27,7 @@ This skill teaches OpenClaw agents to use Settld for paid MCP tool calls.
|
|
|
19
27
|
## Prerequisites
|
|
20
28
|
|
|
21
29
|
- Node.js 20+
|
|
22
|
-
- Settld
|
|
23
|
-
- Settld API base URL (`SETTLD_BASE_URL`)
|
|
24
|
-
- Tenant id (`SETTLD_TENANT_ID`)
|
|
30
|
+
- Settld runtime env from setup (`SETTLD_API_KEY`, `SETTLD_BASE_URL`, `SETTLD_TENANT_ID`)
|
|
25
31
|
- Optional paid tools base URL (`SETTLD_PAID_TOOLS_BASE_URL`)
|
|
26
32
|
|
|
27
33
|
## MCP Server Registration
|
|
@@ -61,9 +67,18 @@ Optional env vars:
|
|
|
61
67
|
- "Call `settld.about` and return the result JSON."
|
|
62
68
|
- "Run `settld.weather_current_paid` for Chicago in fahrenheit and include the `x-settld-*` headers."
|
|
63
69
|
|
|
70
|
+
## Identity + Traceability
|
|
71
|
+
|
|
72
|
+
Every paid call should be explainable and auditable:
|
|
73
|
+
|
|
74
|
+
- tenant identity (who owns the runtime)
|
|
75
|
+
- actor/session identity (who approved/triggered)
|
|
76
|
+
- policy decision identity (`allow|challenge|deny|escalate` + reason codes)
|
|
77
|
+
- settlement identity (`settlementReceiptId`)
|
|
78
|
+
- evidence identity (hash-verifiable receipt/timeline artifacts)
|
|
79
|
+
|
|
64
80
|
## Safety Notes
|
|
65
81
|
|
|
66
82
|
- Treat `SETTLD_API_KEY` as secret input.
|
|
67
83
|
- Do not print full API keys in chat output.
|
|
68
84
|
- Keep paid tools scoped to trusted providers and tenant policy.
|
|
69
|
-
|
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
"command": "npx",
|
|
4
4
|
"args": ["-y", "settld-mcp"],
|
|
5
5
|
"env": {
|
|
6
|
-
"SETTLD_BASE_URL": "
|
|
7
|
-
"SETTLD_TENANT_ID": "
|
|
8
|
-
"SETTLD_API_KEY": "sk_live_xxx.yyy"
|
|
9
|
-
"SETTLD_PAID_TOOLS_BASE_URL": "http://127.0.0.1:8402"
|
|
6
|
+
"SETTLD_BASE_URL": "https://api.settld.work",
|
|
7
|
+
"SETTLD_TENANT_ID": "tenant_xxx",
|
|
8
|
+
"SETTLD_API_KEY": "sk_live_xxx.yyy"
|
|
10
9
|
}
|
|
11
10
|
}
|
|
12
|
-
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "settld-mcp-payments",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "OpenClaw skill for Settld paid MCP tools with policy decisions and verifiable receipts.",
|
|
5
|
+
"author": "Settld",
|
|
6
|
+
"entry": "SKILL.md",
|
|
7
|
+
"files": [
|
|
8
|
+
"SKILL.md",
|
|
9
|
+
"mcp-server.example.json"
|
|
10
|
+
]
|
|
11
|
+
}
|
package/package.json
CHANGED
package/scripts/setup/login.mjs
CHANGED
|
@@ -39,6 +39,7 @@ function readArgValue(argv, index, rawArg) {
|
|
|
39
39
|
function parseArgs(argv) {
|
|
40
40
|
const out = {
|
|
41
41
|
baseUrl: "https://api.settld.work",
|
|
42
|
+
baseUrlProvided: false,
|
|
42
43
|
tenantId: "",
|
|
43
44
|
email: "",
|
|
44
45
|
company: "",
|
|
@@ -64,6 +65,7 @@ function parseArgs(argv) {
|
|
|
64
65
|
if (arg === "--base-url" || arg.startsWith("--base-url=")) {
|
|
65
66
|
const parsed = readArgValue(argv, i, arg);
|
|
66
67
|
out.baseUrl = parsed.value;
|
|
68
|
+
out.baseUrlProvided = true;
|
|
67
69
|
i = parsed.nextIndex;
|
|
68
70
|
continue;
|
|
69
71
|
}
|
|
@@ -149,6 +151,20 @@ async function requestJson(url, { method, body, headers = {}, fetchImpl = fetch
|
|
|
149
151
|
return { res, text, json };
|
|
150
152
|
}
|
|
151
153
|
|
|
154
|
+
function responseCode({ json }) {
|
|
155
|
+
const direct = typeof json?.code === "string" ? json.code : "";
|
|
156
|
+
if (direct) return direct;
|
|
157
|
+
const error = typeof json?.error === "string" ? json.error : "";
|
|
158
|
+
return error ? error.toUpperCase() : "";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function responseMessage({ json, text }, fallback = "unknown error") {
|
|
162
|
+
if (typeof json?.message === "string" && json.message.trim()) return json.message.trim();
|
|
163
|
+
if (typeof json?.error === "string" && json.error.trim()) return json.error.trim();
|
|
164
|
+
const raw = String(text ?? "").trim();
|
|
165
|
+
return raw || fallback;
|
|
166
|
+
}
|
|
167
|
+
|
|
152
168
|
async function promptLine(rl, label, { defaultValue = "", required = true } = {}) {
|
|
153
169
|
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
|
154
170
|
const value = String(await rl.question(`${label}${suffix}: `) ?? "").trim() || String(defaultValue ?? "").trim();
|
|
@@ -190,11 +206,16 @@ export async function runLogin({
|
|
|
190
206
|
const rl = interactive ? createInterface({ input: stdin, output: stdout }) : null;
|
|
191
207
|
try {
|
|
192
208
|
if (interactive) {
|
|
193
|
-
|
|
194
|
-
|
|
209
|
+
if (!args.baseUrlProvided) {
|
|
210
|
+
state.baseUrl = await promptLine(rl, "Settld base URL", { defaultValue: state.baseUrl || "https://api.settld.work" });
|
|
211
|
+
}
|
|
212
|
+
state.tenantId = await promptLine(rl, "Tenant ID (optional, leave blank to create new)", {
|
|
213
|
+
defaultValue: state.tenantId,
|
|
214
|
+
required: false
|
|
215
|
+
});
|
|
195
216
|
state.email = (await promptLine(rl, "Email", { defaultValue: state.email })).toLowerCase();
|
|
196
217
|
if (!state.tenantId) {
|
|
197
|
-
state.company = await promptLine(rl, "
|
|
218
|
+
state.company = await promptLine(rl, "What should your tenant/company name be?", { defaultValue: state.company });
|
|
198
219
|
}
|
|
199
220
|
}
|
|
200
221
|
|
|
@@ -202,7 +223,20 @@ export async function runLogin({
|
|
|
202
223
|
if (!state.email) throw new Error("email is required");
|
|
203
224
|
if (!state.tenantId && !state.company) throw new Error("company is required when tenant ID is omitted");
|
|
204
225
|
|
|
226
|
+
const requestTenantOtp = async (tenantId) => {
|
|
227
|
+
const otpRequest = await requestJson(`${baseUrl}/v1/tenants/${encodeURIComponent(String(tenantId ?? ""))}/buyer/login/otp`, {
|
|
228
|
+
method: "POST",
|
|
229
|
+
body: { email: state.email },
|
|
230
|
+
fetchImpl
|
|
231
|
+
});
|
|
232
|
+
if (!otpRequest.res.ok) {
|
|
233
|
+
const message = responseMessage(otpRequest);
|
|
234
|
+
throw new Error(`otp request failed (${otpRequest.res.status}): ${message}`);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
205
238
|
let tenantId = state.tenantId;
|
|
239
|
+
let otpAlreadyIssued = false;
|
|
206
240
|
if (!tenantId) {
|
|
207
241
|
const signup = await requestJson(`${baseUrl}/v1/public/signup`, {
|
|
208
242
|
method: "POST",
|
|
@@ -210,27 +244,24 @@ export async function runLogin({
|
|
|
210
244
|
fetchImpl
|
|
211
245
|
});
|
|
212
246
|
if (!signup.res.ok) {
|
|
213
|
-
const code =
|
|
214
|
-
const message =
|
|
247
|
+
const code = responseCode(signup);
|
|
248
|
+
const message = responseMessage(signup);
|
|
215
249
|
if (code === "SIGNUP_DISABLED") {
|
|
216
250
|
throw new Error("Public signup is disabled for this environment. Use an existing tenant ID or bootstrap key flow.");
|
|
217
251
|
}
|
|
218
|
-
|
|
252
|
+
if (signup.res.status === 403 && code === "FORBIDDEN") {
|
|
253
|
+
throw new Error(
|
|
254
|
+
"Public signup is unavailable on this base URL. Retry with --tenant-id <existing_tenant>, or in `settld setup` choose `Generate during setup` and provide an onboarding bootstrap API key."
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
throw new Error(`public signup failed (${signup.res.status}): ${message}`);
|
|
219
258
|
}
|
|
220
259
|
tenantId = String(signup.json?.tenantId ?? "").trim();
|
|
221
260
|
if (!tenantId) throw new Error("public signup response missing tenantId");
|
|
261
|
+
otpAlreadyIssued = Boolean(signup.json?.otpIssued);
|
|
222
262
|
if (interactive) stdout.write(`Created tenant: ${tenantId}\n`);
|
|
223
|
-
} else {
|
|
224
|
-
const otpRequest = await requestJson(`${baseUrl}/v1/tenants/${encodeURIComponent(tenantId)}/buyer/login/otp`, {
|
|
225
|
-
method: "POST",
|
|
226
|
-
body: { email: state.email },
|
|
227
|
-
fetchImpl
|
|
228
|
-
});
|
|
229
|
-
if (!otpRequest.res.ok) {
|
|
230
|
-
const message = typeof otpRequest.json?.message === "string" ? otpRequest.json.message : otpRequest.text;
|
|
231
|
-
throw new Error(`otp request failed (${otpRequest.res.status}): ${message || "unknown error"}`);
|
|
232
|
-
}
|
|
233
263
|
}
|
|
264
|
+
if (!otpAlreadyIssued) await requestTenantOtp(tenantId);
|
|
234
265
|
|
|
235
266
|
if (!state.otp && interactive) {
|
|
236
267
|
state.otp = await promptLine(rl, "OTP code", { required: true });
|
|
@@ -243,8 +274,8 @@ export async function runLogin({
|
|
|
243
274
|
fetchImpl
|
|
244
275
|
});
|
|
245
276
|
if (!login.res.ok) {
|
|
246
|
-
const message =
|
|
247
|
-
throw new Error(`login failed (${login.res.status}): ${message
|
|
277
|
+
const message = responseMessage(login);
|
|
278
|
+
throw new Error(`login failed (${login.res.status}): ${message}`);
|
|
248
279
|
}
|
|
249
280
|
const setCookie = login.res.headers.get("set-cookie") ?? "";
|
|
250
281
|
const cookie = cookieHeaderFromSetCookie(setCookie);
|
|
@@ -13,10 +13,13 @@ import { bootstrapWalletProvider } from "../../src/core/wallet-provider-bootstra
|
|
|
13
13
|
import { extractBootstrapMcpEnv, loadHostConfigHelper, runWizard } from "./wizard.mjs";
|
|
14
14
|
import { SUPPORTED_HOSTS } from "./host-config.mjs";
|
|
15
15
|
import { defaultSessionPath, readSavedSession } from "./session-store.mjs";
|
|
16
|
+
import { runLogin } from "./login.mjs";
|
|
17
|
+
import { runWalletCli } from "../wallet/cli.mjs";
|
|
16
18
|
|
|
17
19
|
const WALLET_MODES = new Set(["managed", "byo", "none"]);
|
|
18
20
|
const WALLET_PROVIDERS = new Set(["circle"]);
|
|
19
21
|
const WALLET_BOOTSTRAP_MODES = new Set(["auto", "local", "remote"]);
|
|
22
|
+
const SETUP_MODES = new Set(["quick", "advanced"]);
|
|
20
23
|
const FORMAT_OPTIONS = new Set(["text", "json"]);
|
|
21
24
|
const HOST_BINARY_HINTS = Object.freeze({
|
|
22
25
|
codex: "codex",
|
|
@@ -38,6 +41,7 @@ const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
|
38
41
|
const REPO_ROOT = path.resolve(SCRIPT_DIR, "..", "..");
|
|
39
42
|
const SETTLD_BIN = path.join(REPO_ROOT, "bin", "settld.js");
|
|
40
43
|
const PROFILE_FINGERPRINT_REGEX = /^[0-9a-f]{64}$/;
|
|
44
|
+
const DEFAULT_PUBLIC_BASE_URL = "https://api.settld.work";
|
|
41
45
|
const ANSI_RESET = "\u001b[0m";
|
|
42
46
|
const ANSI_BOLD = "\u001b[1m";
|
|
43
47
|
const ANSI_DIM = "\u001b[2m";
|
|
@@ -54,6 +58,10 @@ function usage() {
|
|
|
54
58
|
"",
|
|
55
59
|
"flags:",
|
|
56
60
|
" --non-interactive Disable prompts; require explicit flags",
|
|
61
|
+
" --quick Quick setup mode (default for interactive)",
|
|
62
|
+
" --advanced Advanced setup mode (shows all prompts)",
|
|
63
|
+
" --guided-next Run guided fund + first paid check after setup (default in quick mode)",
|
|
64
|
+
" --skip-guided-next Skip guided post-setup actions",
|
|
57
65
|
` --host <${SUPPORTED_HOSTS.join("|")}> Host target (default: auto-detect, fallback openclaw)`,
|
|
58
66
|
" --base-url <url> Settld API base URL (or SETTLD_BASE_URL)",
|
|
59
67
|
" --tenant-id <id> Settld tenant ID (or SETTLD_TENANT_ID)",
|
|
@@ -109,6 +117,8 @@ function parseWalletEnvAssignment(raw) {
|
|
|
109
117
|
function parseArgs(argv) {
|
|
110
118
|
const out = {
|
|
111
119
|
nonInteractive: false,
|
|
120
|
+
setupMode: null,
|
|
121
|
+
guidedNext: null,
|
|
112
122
|
host: null,
|
|
113
123
|
baseUrl: null,
|
|
114
124
|
tenantId: null,
|
|
@@ -149,6 +159,22 @@ function parseArgs(argv) {
|
|
|
149
159
|
out.nonInteractive = true;
|
|
150
160
|
continue;
|
|
151
161
|
}
|
|
162
|
+
if (arg === "--quick") {
|
|
163
|
+
out.setupMode = "quick";
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (arg === "--advanced") {
|
|
167
|
+
out.setupMode = "advanced";
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (arg === "--guided-next") {
|
|
171
|
+
out.guidedNext = true;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (arg === "--skip-guided-next") {
|
|
175
|
+
out.guidedNext = false;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
152
178
|
if (arg === "--skip-profile-apply") {
|
|
153
179
|
out.skipProfileApply = true;
|
|
154
180
|
continue;
|
|
@@ -310,6 +336,9 @@ function parseArgs(argv) {
|
|
|
310
336
|
if (out.host && !SUPPORTED_HOSTS.includes(out.host)) {
|
|
311
337
|
throw new Error(`--host must be one of: ${SUPPORTED_HOSTS.join(", ")}`);
|
|
312
338
|
}
|
|
339
|
+
if (out.setupMode && !SETUP_MODES.has(out.setupMode)) {
|
|
340
|
+
throw new Error("--quick or --advanced are the supported setup modes");
|
|
341
|
+
}
|
|
313
342
|
if (!WALLET_MODES.has(out.walletMode)) throw new Error("--wallet-mode must be managed|byo|none");
|
|
314
343
|
if (!WALLET_PROVIDERS.has(out.walletProvider)) throw new Error(`--wallet-provider must be one of: ${[...WALLET_PROVIDERS].join(", ")}`);
|
|
315
344
|
if (!WALLET_BOOTSTRAP_MODES.has(out.walletBootstrap)) throw new Error("--wallet-bootstrap must be auto|local|remote");
|
|
@@ -865,6 +894,144 @@ function buildHostNextSteps({ host, installedHosts }) {
|
|
|
865
894
|
return steps;
|
|
866
895
|
}
|
|
867
896
|
|
|
897
|
+
function runMcpPaidCallProbe({ env, timeoutMs = 45_000 } = {}) {
|
|
898
|
+
const args = [
|
|
899
|
+
path.join("scripts", "mcp", "probe.mjs"),
|
|
900
|
+
"--call",
|
|
901
|
+
"settld.weather_current_paid",
|
|
902
|
+
JSON.stringify({ city: "Chicago", unit: "f" }),
|
|
903
|
+
"--timeout-ms",
|
|
904
|
+
String(timeoutMs)
|
|
905
|
+
];
|
|
906
|
+
const result = spawnSync(process.execPath, args, {
|
|
907
|
+
cwd: REPO_ROOT,
|
|
908
|
+
env: { ...process.env, ...(env ?? {}) },
|
|
909
|
+
encoding: "utf8",
|
|
910
|
+
timeout: timeoutMs + 5000,
|
|
911
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
912
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
913
|
+
});
|
|
914
|
+
const stdoutText = String(result.stdout ?? "");
|
|
915
|
+
const stderrText = String(result.stderr ?? "");
|
|
916
|
+
const sawToolError = /"isError"\s*:\s*true/.test(stdoutText);
|
|
917
|
+
const sawToolSuccess = /"isError"\s*:\s*false/.test(stdoutText);
|
|
918
|
+
const ok = Number(result.status) === 0 && sawToolSuccess && !sawToolError;
|
|
919
|
+
return {
|
|
920
|
+
ok,
|
|
921
|
+
exitCode: typeof result.status === "number" ? result.status : 1,
|
|
922
|
+
stdout: stdoutText,
|
|
923
|
+
stderr: stderrText
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
async function runGuidedQuickFlow({
|
|
928
|
+
enabled = false,
|
|
929
|
+
walletMode = "none",
|
|
930
|
+
normalizedBaseUrl,
|
|
931
|
+
tenantId,
|
|
932
|
+
sessionFile,
|
|
933
|
+
sessionCookie = "",
|
|
934
|
+
mergedEnv = {},
|
|
935
|
+
runtimeEnv = process.env,
|
|
936
|
+
stdin = process.stdin,
|
|
937
|
+
stdout = process.stdout,
|
|
938
|
+
runWalletCliImpl = runWalletCli
|
|
939
|
+
} = {}) {
|
|
940
|
+
const summary = {
|
|
941
|
+
enabled: Boolean(enabled),
|
|
942
|
+
ran: false,
|
|
943
|
+
walletFund: null,
|
|
944
|
+
walletBalanceWatch: null,
|
|
945
|
+
firstPaidCall: null,
|
|
946
|
+
warnings: []
|
|
947
|
+
};
|
|
948
|
+
if (!summary.enabled) return summary;
|
|
949
|
+
if (!stdin?.isTTY || !stdout?.isTTY) {
|
|
950
|
+
summary.warnings.push("guided quick flow skipped (non-interactive terminal)");
|
|
951
|
+
return summary;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
const actionEnv = {
|
|
955
|
+
...runtimeEnv,
|
|
956
|
+
...mergedEnv,
|
|
957
|
+
SETTLD_BASE_URL: normalizedBaseUrl,
|
|
958
|
+
SETTLD_TENANT_ID: tenantId,
|
|
959
|
+
...(sessionCookie ? { SETTLD_SESSION_COOKIE: sessionCookie } : {})
|
|
960
|
+
};
|
|
961
|
+
summary.ran = true;
|
|
962
|
+
|
|
963
|
+
if (walletMode !== "none") {
|
|
964
|
+
try {
|
|
965
|
+
const fundResult = await runWalletCliImpl({
|
|
966
|
+
argv: ["fund", "--open", "--base-url", normalizedBaseUrl, "--tenant-id", tenantId, "--session-file", sessionFile, "--format", "json"],
|
|
967
|
+
env: actionEnv,
|
|
968
|
+
stdin,
|
|
969
|
+
stdout
|
|
970
|
+
});
|
|
971
|
+
summary.walletFund = {
|
|
972
|
+
ok: true,
|
|
973
|
+
method: String(fundResult?.method ?? "").trim() || null
|
|
974
|
+
};
|
|
975
|
+
} catch (err) {
|
|
976
|
+
summary.walletFund = {
|
|
977
|
+
ok: false,
|
|
978
|
+
error: err?.message ?? String(err ?? "")
|
|
979
|
+
};
|
|
980
|
+
summary.warnings.push("wallet funding did not complete during guided flow");
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
try {
|
|
984
|
+
const watchResult = await runWalletCliImpl({
|
|
985
|
+
argv: [
|
|
986
|
+
"balance",
|
|
987
|
+
"--watch",
|
|
988
|
+
"--min-usdc",
|
|
989
|
+
"1",
|
|
990
|
+
"--timeout-seconds",
|
|
991
|
+
"300",
|
|
992
|
+
"--interval-seconds",
|
|
993
|
+
"5",
|
|
994
|
+
"--base-url",
|
|
995
|
+
normalizedBaseUrl,
|
|
996
|
+
"--tenant-id",
|
|
997
|
+
tenantId,
|
|
998
|
+
"--session-file",
|
|
999
|
+
sessionFile,
|
|
1000
|
+
"--format",
|
|
1001
|
+
"json"
|
|
1002
|
+
],
|
|
1003
|
+
env: actionEnv,
|
|
1004
|
+
stdin,
|
|
1005
|
+
stdout
|
|
1006
|
+
});
|
|
1007
|
+
summary.walletBalanceWatch = {
|
|
1008
|
+
ok: Boolean(watchResult?.ok),
|
|
1009
|
+
satisfied: Boolean(watchResult?.watch?.satisfied)
|
|
1010
|
+
};
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
summary.walletBalanceWatch = {
|
|
1013
|
+
ok: false,
|
|
1014
|
+
error: err?.message ?? String(err ?? "")
|
|
1015
|
+
};
|
|
1016
|
+
summary.warnings.push("wallet balance watch did not reach target within timeout");
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
const paidProbe = runMcpPaidCallProbe({ env: actionEnv });
|
|
1021
|
+
if (paidProbe.ok) {
|
|
1022
|
+
summary.firstPaidCall = { ok: true };
|
|
1023
|
+
} else {
|
|
1024
|
+
summary.firstPaidCall = {
|
|
1025
|
+
ok: false,
|
|
1026
|
+
exitCode: paidProbe.exitCode,
|
|
1027
|
+
error: paidProbe.stderr || "paid call returned tool error"
|
|
1028
|
+
};
|
|
1029
|
+
summary.warnings.push("first paid call probe failed");
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
return summary;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
868
1035
|
function resolveByoWalletEnv({ walletProvider, walletEnvRows, runtimeEnv }) {
|
|
869
1036
|
const env = {};
|
|
870
1037
|
for (const row of walletEnvRows ?? []) env[row.key] = row.value;
|
|
@@ -1028,16 +1195,21 @@ async function resolveRuntimeConfig({
|
|
|
1028
1195
|
runtimeEnv,
|
|
1029
1196
|
stdin = process.stdin,
|
|
1030
1197
|
stdout = process.stdout,
|
|
1031
|
-
detectInstalledHostsImpl = detectInstalledHosts
|
|
1198
|
+
detectInstalledHostsImpl = detectInstalledHosts,
|
|
1199
|
+
fetchImpl = fetch,
|
|
1200
|
+
runLoginImpl = runLogin,
|
|
1201
|
+
readSavedSessionImpl = readSavedSession
|
|
1032
1202
|
}) {
|
|
1033
1203
|
const sessionFile = String(args.sessionFile ?? runtimeEnv.SETTLD_SESSION_FILE ?? defaultSessionPath()).trim();
|
|
1034
|
-
const savedSession = await
|
|
1204
|
+
const savedSession = await readSavedSessionImpl({ sessionPath: sessionFile });
|
|
1035
1205
|
const installedHosts = detectInstalledHostsImpl();
|
|
1036
1206
|
const defaultHost = selectDefaultHost({
|
|
1037
1207
|
explicitHost: args.host ? String(args.host).toLowerCase() : "",
|
|
1038
1208
|
installedHosts
|
|
1039
1209
|
});
|
|
1040
1210
|
const out = {
|
|
1211
|
+
setupMode: args.setupMode ?? "quick",
|
|
1212
|
+
guidedNext: args.guidedNext,
|
|
1041
1213
|
host: args.host ?? defaultHost,
|
|
1042
1214
|
walletMode: args.walletMode,
|
|
1043
1215
|
baseUrl: String(args.baseUrl ?? runtimeEnv.SETTLD_BASE_URL ?? "").trim(),
|
|
@@ -1071,6 +1243,9 @@ async function resolveRuntimeConfig({
|
|
|
1071
1243
|
}
|
|
1072
1244
|
|
|
1073
1245
|
if (args.nonInteractive) {
|
|
1246
|
+
if (!out.setupMode) out.setupMode = "quick";
|
|
1247
|
+
if (!SETUP_MODES.has(out.setupMode)) throw new Error("--quick or --advanced are the supported setup modes");
|
|
1248
|
+
if (out.guidedNext === null || out.guidedNext === undefined) out.guidedNext = false;
|
|
1074
1249
|
if (!SUPPORTED_HOSTS.includes(out.host)) throw new Error(`--host must be one of: ${SUPPORTED_HOSTS.join(", ")}`);
|
|
1075
1250
|
if (!out.baseUrl) throw new Error("--base-url is required");
|
|
1076
1251
|
if (!out.tenantId) throw new Error("--tenant-id is required");
|
|
@@ -1105,6 +1280,24 @@ async function resolveRuntimeConfig({
|
|
|
1105
1280
|
}
|
|
1106
1281
|
stdout.write("\n");
|
|
1107
1282
|
|
|
1283
|
+
if (!out.setupMode || !SETUP_MODES.has(out.setupMode)) {
|
|
1284
|
+
out.setupMode = "quick";
|
|
1285
|
+
}
|
|
1286
|
+
out.setupMode = await promptSelect(
|
|
1287
|
+
rl,
|
|
1288
|
+
stdin,
|
|
1289
|
+
stdout,
|
|
1290
|
+
"Setup mode",
|
|
1291
|
+
[
|
|
1292
|
+
{ value: "quick", label: "quick", hint: "Recommended: minimal prompts and guided next steps" },
|
|
1293
|
+
{ value: "advanced", label: "advanced", hint: "Full control over all setup options" }
|
|
1294
|
+
],
|
|
1295
|
+
{ defaultValue: out.setupMode, color }
|
|
1296
|
+
);
|
|
1297
|
+
if (out.guidedNext === null || out.guidedNext === undefined) {
|
|
1298
|
+
out.guidedNext = out.setupMode === "quick";
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1108
1301
|
const hostPromptDefault = out.host && SUPPORTED_HOSTS.includes(out.host) ? out.host : defaultHost;
|
|
1109
1302
|
const hostOptions = SUPPORTED_HOSTS.map((host) => ({
|
|
1110
1303
|
value: host,
|
|
@@ -1133,68 +1326,105 @@ async function resolveRuntimeConfig({
|
|
|
1133
1326
|
{ defaultValue: out.walletMode, color }
|
|
1134
1327
|
);
|
|
1135
1328
|
|
|
1136
|
-
if (!out.baseUrl)
|
|
1137
|
-
out.baseUrl = await promptLine(rl, "Settld base URL", { defaultValue: "https://api.settld.work" });
|
|
1138
|
-
}
|
|
1139
|
-
if (!out.tenantId) {
|
|
1140
|
-
out.tenantId = await promptLine(rl, "Tenant ID", { defaultValue: "tenant_default" });
|
|
1141
|
-
}
|
|
1329
|
+
if (!out.baseUrl) out.baseUrl = DEFAULT_PUBLIC_BASE_URL;
|
|
1142
1330
|
if (!out.settldApiKey) {
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
(
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
stdin,
|
|
1163
|
-
stdout,
|
|
1164
|
-
"How should setup get your Settld API key?",
|
|
1165
|
-
keyOptions,
|
|
1166
|
-
{ defaultValue: canUseSavedSession ? "session" : "bootstrap", color }
|
|
1167
|
-
);
|
|
1168
|
-
if (keyMode === "bootstrap") {
|
|
1169
|
-
if (!out.bootstrapApiKey) {
|
|
1170
|
-
out.bootstrapApiKey = await promptSecretLine(rl, mutableOutput, stdout, "Onboarding bootstrap API key");
|
|
1331
|
+
while (!out.settldApiKey) {
|
|
1332
|
+
const canUseSavedSession =
|
|
1333
|
+
Boolean(out.sessionCookie) &&
|
|
1334
|
+
(!savedSession ||
|
|
1335
|
+
(normalizeHttpUrl(out.baseUrl) === normalizeHttpUrl(savedSession?.baseUrl) &&
|
|
1336
|
+
(!out.tenantId || String(out.tenantId ?? "").trim() === String(savedSession?.tenantId ?? "").trim())));
|
|
1337
|
+
const keyOptions = [];
|
|
1338
|
+
if (canUseSavedSession) {
|
|
1339
|
+
keyOptions.push({
|
|
1340
|
+
value: "session",
|
|
1341
|
+
label: "Use saved login session",
|
|
1342
|
+
hint: `Reuse ${out.sessionFile} to mint runtime key`
|
|
1343
|
+
});
|
|
1344
|
+
} else {
|
|
1345
|
+
keyOptions.push({
|
|
1346
|
+
value: "login",
|
|
1347
|
+
label: "Login / create tenant (recommended)",
|
|
1348
|
+
hint: "OTP sign-in; no API key required"
|
|
1349
|
+
});
|
|
1171
1350
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1351
|
+
keyOptions.push(
|
|
1352
|
+
{ value: "bootstrap", label: "Generate during setup", hint: "Use onboarding bootstrap API key" },
|
|
1353
|
+
{ value: "manual", label: "Paste existing key", hint: "Use an existing tenant API key" }
|
|
1354
|
+
);
|
|
1355
|
+
const keyMode = await promptSelect(
|
|
1356
|
+
rl,
|
|
1357
|
+
stdin,
|
|
1358
|
+
stdout,
|
|
1359
|
+
"How should setup get your Settld API key?",
|
|
1360
|
+
keyOptions,
|
|
1361
|
+
{ defaultValue: canUseSavedSession ? "session" : "login", color }
|
|
1362
|
+
);
|
|
1363
|
+
if (keyMode === "login") {
|
|
1364
|
+
try {
|
|
1365
|
+
await runLoginImpl({
|
|
1366
|
+
argv: ["--base-url", out.baseUrl, "--session-file", out.sessionFile],
|
|
1367
|
+
stdin,
|
|
1368
|
+
stdout,
|
|
1369
|
+
fetchImpl
|
|
1370
|
+
});
|
|
1371
|
+
const refreshedSession = await readSavedSessionImpl({ sessionPath: out.sessionFile });
|
|
1372
|
+
if (!refreshedSession) throw new Error("login did not produce a saved session");
|
|
1373
|
+
out.baseUrl = String(refreshedSession.baseUrl ?? out.baseUrl).trim() || out.baseUrl;
|
|
1374
|
+
out.tenantId = String(refreshedSession.tenantId ?? out.tenantId).trim();
|
|
1375
|
+
out.sessionCookie = String(refreshedSession.cookie ?? out.sessionCookie).trim();
|
|
1376
|
+
if (savedSession) {
|
|
1377
|
+
savedSession.baseUrl = refreshedSession.baseUrl;
|
|
1378
|
+
savedSession.tenantId = refreshedSession.tenantId;
|
|
1379
|
+
savedSession.cookie = refreshedSession.cookie;
|
|
1380
|
+
}
|
|
1381
|
+
} catch (err) {
|
|
1382
|
+
stdout.write(`Login failed: ${err?.message ?? "unknown error"}\n`);
|
|
1383
|
+
stdout.write("Choose `Generate during setup` if your deployment does not expose public signup/login.\n");
|
|
1384
|
+
}
|
|
1385
|
+
continue;
|
|
1174
1386
|
}
|
|
1175
|
-
if (
|
|
1176
|
-
out.
|
|
1387
|
+
if (keyMode === "bootstrap") {
|
|
1388
|
+
if (!out.bootstrapApiKey) {
|
|
1389
|
+
out.bootstrapApiKey = await promptSecretLine(rl, mutableOutput, stdout, "Onboarding bootstrap API key");
|
|
1390
|
+
}
|
|
1391
|
+
if (!out.bootstrapKeyId) {
|
|
1392
|
+
out.bootstrapKeyId = await promptLine(rl, "Generated key ID (optional)", { required: false });
|
|
1393
|
+
}
|
|
1394
|
+
if (!out.bootstrapScopes) {
|
|
1395
|
+
out.bootstrapScopes = await promptLine(rl, "Generated key scopes CSV (optional)", { required: false });
|
|
1396
|
+
}
|
|
1397
|
+
break;
|
|
1398
|
+
}
|
|
1399
|
+
if (keyMode === "manual") {
|
|
1400
|
+
out.settldApiKey = await promptSecretLine(rl, mutableOutput, stdout, "Settld API key");
|
|
1401
|
+
break;
|
|
1177
1402
|
}
|
|
1178
|
-
} else if (keyMode === "manual") {
|
|
1179
|
-
out.settldApiKey = await promptSecretLine(rl, mutableOutput, stdout, "Settld API key");
|
|
1180
|
-
} else {
|
|
1181
1403
|
out.bootstrapApiKey = "";
|
|
1404
|
+
break;
|
|
1182
1405
|
}
|
|
1183
1406
|
}
|
|
1407
|
+
if (!out.tenantId) {
|
|
1408
|
+
out.tenantId = await promptLine(rl, "Tenant ID", { defaultValue: "tenant_default" });
|
|
1409
|
+
}
|
|
1184
1410
|
|
|
1185
1411
|
if (out.walletMode === "managed") {
|
|
1186
|
-
out.
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1412
|
+
if (out.setupMode === "quick") {
|
|
1413
|
+
out.walletBootstrap = out.walletBootstrap || "auto";
|
|
1414
|
+
} else {
|
|
1415
|
+
out.walletBootstrap = await promptSelect(
|
|
1416
|
+
rl,
|
|
1417
|
+
stdin,
|
|
1418
|
+
stdout,
|
|
1419
|
+
"Managed wallet bootstrap",
|
|
1420
|
+
[
|
|
1421
|
+
{ value: "auto", label: "auto", hint: "Use local Circle key when present, else remote bootstrap" },
|
|
1422
|
+
{ value: "local", label: "local", hint: "Always use local Circle API key flow" },
|
|
1423
|
+
{ value: "remote", label: "remote", hint: "Always use tenant onboarding endpoint" }
|
|
1424
|
+
],
|
|
1425
|
+
{ defaultValue: out.walletBootstrap || "auto", color }
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1198
1428
|
if (out.walletBootstrap === "local" && !out.circleApiKey) {
|
|
1199
1429
|
out.circleApiKey = await promptSecretLine(rl, mutableOutput, stdout, "Circle API key");
|
|
1200
1430
|
}
|
|
@@ -1220,6 +1450,9 @@ async function resolveRuntimeConfig({
|
|
|
1220
1450
|
out.smoke = false;
|
|
1221
1451
|
out.skipProfileApply = true;
|
|
1222
1452
|
out.dryRun = true;
|
|
1453
|
+
out.guidedNext = false;
|
|
1454
|
+
} else if (out.setupMode === "quick") {
|
|
1455
|
+
out.profileId = out.profileId || "engineering-spend";
|
|
1223
1456
|
} else {
|
|
1224
1457
|
out.preflight = await promptBooleanChoice(
|
|
1225
1458
|
rl,
|
|
@@ -1296,7 +1529,10 @@ export async function runOnboard({
|
|
|
1296
1529
|
requestRuntimeBootstrapMcpEnvImpl = requestRuntimeBootstrapMcpEnv,
|
|
1297
1530
|
requestRemoteWalletBootstrapImpl = requestRemoteWalletBootstrap,
|
|
1298
1531
|
runPreflightChecksImpl = runPreflightChecks,
|
|
1299
|
-
detectInstalledHostsImpl = detectInstalledHosts
|
|
1532
|
+
detectInstalledHostsImpl = detectInstalledHosts,
|
|
1533
|
+
runLoginImpl = runLogin,
|
|
1534
|
+
readSavedSessionImpl = readSavedSession,
|
|
1535
|
+
runWalletCliImpl = runWalletCli
|
|
1300
1536
|
} = {}) {
|
|
1301
1537
|
const args = parseArgs(argv);
|
|
1302
1538
|
if (args.help) {
|
|
@@ -1305,7 +1541,7 @@ export async function runOnboard({
|
|
|
1305
1541
|
}
|
|
1306
1542
|
|
|
1307
1543
|
const showSteps = args.format !== "json";
|
|
1308
|
-
const totalSteps = args.preflightOnly ? 4 :
|
|
1544
|
+
const totalSteps = args.preflightOnly ? 4 : 6;
|
|
1309
1545
|
let step = 1;
|
|
1310
1546
|
|
|
1311
1547
|
if (showSteps) printStep(stdout, step, totalSteps, "Resolve setup configuration");
|
|
@@ -1314,7 +1550,10 @@ export async function runOnboard({
|
|
|
1314
1550
|
runtimeEnv,
|
|
1315
1551
|
stdin,
|
|
1316
1552
|
stdout,
|
|
1317
|
-
detectInstalledHostsImpl
|
|
1553
|
+
detectInstalledHostsImpl,
|
|
1554
|
+
fetchImpl,
|
|
1555
|
+
runLoginImpl,
|
|
1556
|
+
readSavedSessionImpl
|
|
1318
1557
|
});
|
|
1319
1558
|
step += 1;
|
|
1320
1559
|
const normalizedBaseUrl = normalizeHttpUrl(mustString(config.baseUrl, "SETTLD_BASE_URL / --base-url"));
|
|
@@ -1512,9 +1751,27 @@ export async function runOnboard({
|
|
|
1512
1751
|
await fs.writeFile(args.outEnv, toEnvFileText(mergedEnv), "utf8");
|
|
1513
1752
|
}
|
|
1514
1753
|
|
|
1754
|
+
if (showSteps) printStep(stdout, step, totalSteps, "Guided next steps");
|
|
1755
|
+
const guided = await runGuidedQuickFlow({
|
|
1756
|
+
enabled: Boolean(config.setupMode === "quick" && config.guidedNext),
|
|
1757
|
+
walletMode: config.walletMode,
|
|
1758
|
+
normalizedBaseUrl,
|
|
1759
|
+
tenantId,
|
|
1760
|
+
sessionFile: config.sessionFile,
|
|
1761
|
+
sessionCookie: config.sessionCookie,
|
|
1762
|
+
mergedEnv,
|
|
1763
|
+
runtimeEnv,
|
|
1764
|
+
stdin,
|
|
1765
|
+
stdout,
|
|
1766
|
+
runWalletCliImpl
|
|
1767
|
+
});
|
|
1768
|
+
step += 1;
|
|
1769
|
+
|
|
1515
1770
|
if (showSteps) printStep(stdout, step, totalSteps, "Finalize output");
|
|
1516
1771
|
const payload = {
|
|
1517
1772
|
ok: true,
|
|
1773
|
+
setupMode: config.setupMode,
|
|
1774
|
+
guided,
|
|
1518
1775
|
host: config.host,
|
|
1519
1776
|
wallet: {
|
|
1520
1777
|
mode: config.walletMode,
|
|
@@ -1546,6 +1803,7 @@ export async function runOnboard({
|
|
|
1546
1803
|
lines.push("Settld onboard complete.");
|
|
1547
1804
|
lines.push(`Host: ${config.host}`);
|
|
1548
1805
|
lines.push(`Settld: ${normalizedBaseUrl} (tenant=${tenantId})`);
|
|
1806
|
+
lines.push(`Setup mode: ${config.setupMode}`);
|
|
1549
1807
|
lines.push(`Preflight: ${config.preflight ? "passed" : "skipped"}`);
|
|
1550
1808
|
lines.push(`Wallet mode: ${config.walletMode}`);
|
|
1551
1809
|
lines.push(`Wallet bootstrap mode: ${walletBootstrapMode}`);
|
|
@@ -1553,6 +1811,18 @@ export async function runOnboard({
|
|
|
1553
1811
|
if (wallet?.wallets?.escrow?.walletId) lines.push(`Escrow wallet: ${wallet.wallets.escrow.walletId}`);
|
|
1554
1812
|
if (wallet?.tokenIdUsdc) lines.push(`USDC token id: ${wallet.tokenIdUsdc}`);
|
|
1555
1813
|
if (args.outEnv) lines.push(`Wrote env file: ${args.outEnv}`);
|
|
1814
|
+
if (guided?.enabled) {
|
|
1815
|
+
lines.push("");
|
|
1816
|
+
lines.push("Guided quick flow:");
|
|
1817
|
+
if (guided.ran) {
|
|
1818
|
+
lines.push(`- wallet fund: ${guided.walletFund?.ok ? "ok" : "not completed"}`);
|
|
1819
|
+
lines.push(`- wallet balance watch: ${guided.walletBalanceWatch?.ok ? "ok" : "not completed"}`);
|
|
1820
|
+
lines.push(`- first paid call: ${guided.firstPaidCall?.ok ? "ok" : "failed"}`);
|
|
1821
|
+
} else {
|
|
1822
|
+
lines.push("- skipped");
|
|
1823
|
+
}
|
|
1824
|
+
for (const warning of guided.warnings ?? []) lines.push(`- warning: ${warning}`);
|
|
1825
|
+
}
|
|
1556
1826
|
lines.push("");
|
|
1557
1827
|
lines.push("Combined exports:");
|
|
1558
1828
|
lines.push(toExportText(mergedEnv));
|