settld 0.2.0 → 0.2.2
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 +21 -0
- package/SETTLD_VERSION +1 -1
- package/bin/settld.js +18 -0
- package/docs/QUICKSTART_MCP_HOSTS.md +78 -4
- package/docs/gitbook/quickstart.md +47 -4
- package/docs/integrations/openclaw/PUBLIC_QUICKSTART.md +13 -0
- package/docs/ops/VERCEL_MONOREPO_DEPLOY.md +42 -0
- package/package.json +2 -1
- package/scripts/ci/run-mcp-host-smoke.mjs +6 -0
- package/scripts/ci/run-production-cutover-gate.mjs +6 -0
- package/scripts/demo/mcp-paid-exa.mjs +18 -1
- package/scripts/setup/login.mjs +299 -0
- package/scripts/setup/onboard.mjs +281 -26
- package/scripts/setup/session-store.mjs +65 -0
- package/scripts/vercel/build-mkdocs.sh +3 -3
- package/scripts/vercel/ignore-dashboard.sh +26 -0
- package/scripts/vercel/ignore-mkdocs.sh +2 -0
- package/scripts/vercel/install-mkdocs.sh +2 -3
- package/scripts/wallet/cli.mjs +871 -0
- package/src/core/wallet-funding-coinbase.js +197 -0
- package/src/core/wallet-funding-hosted.js +155 -0
- package/src/core/wallet-provider-bootstrap.js +95 -0
|
@@ -10,8 +10,9 @@ import { spawnSync } from "node:child_process";
|
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
|
|
12
12
|
import { bootstrapWalletProvider } from "../../src/core/wallet-provider-bootstrap.js";
|
|
13
|
-
import { loadHostConfigHelper, runWizard } from "./wizard.mjs";
|
|
13
|
+
import { extractBootstrapMcpEnv, loadHostConfigHelper, runWizard } from "./wizard.mjs";
|
|
14
14
|
import { SUPPORTED_HOSTS } from "./host-config.mjs";
|
|
15
|
+
import { defaultSessionPath, readSavedSession } from "./session-store.mjs";
|
|
15
16
|
|
|
16
17
|
const WALLET_MODES = new Set(["managed", "byo", "none"]);
|
|
17
18
|
const WALLET_PROVIDERS = new Set(["circle"]);
|
|
@@ -37,6 +38,12 @@ const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
|
37
38
|
const REPO_ROOT = path.resolve(SCRIPT_DIR, "..", "..");
|
|
38
39
|
const SETTLD_BIN = path.join(REPO_ROOT, "bin", "settld.js");
|
|
39
40
|
const PROFILE_FINGERPRINT_REGEX = /^[0-9a-f]{64}$/;
|
|
41
|
+
const ANSI_RESET = "\u001b[0m";
|
|
42
|
+
const ANSI_BOLD = "\u001b[1m";
|
|
43
|
+
const ANSI_DIM = "\u001b[2m";
|
|
44
|
+
const ANSI_CYAN = "\u001b[36m";
|
|
45
|
+
const ANSI_GREEN = "\u001b[32m";
|
|
46
|
+
const ANSI_MAGENTA = "\u001b[35m";
|
|
40
47
|
|
|
41
48
|
function usage() {
|
|
42
49
|
const text = [
|
|
@@ -51,6 +58,11 @@ function usage() {
|
|
|
51
58
|
" --base-url <url> Settld API base URL (or SETTLD_BASE_URL)",
|
|
52
59
|
" --tenant-id <id> Settld tenant ID (or SETTLD_TENANT_ID)",
|
|
53
60
|
" --settld-api-key <key> Settld tenant API key (or SETTLD_API_KEY)",
|
|
61
|
+
" --bootstrap-api-key <key> Onboarding bootstrap API key used to mint tenant API key",
|
|
62
|
+
" --magic-link-api-key <key> Alias for --bootstrap-api-key",
|
|
63
|
+
" --session-file <path> Saved login session path (default: ~/.settld/session.json)",
|
|
64
|
+
" --bootstrap-key-id <id> Optional API key ID hint for runtime bootstrap",
|
|
65
|
+
" --bootstrap-scopes <csv> Optional scopes for generated tenant API key",
|
|
54
66
|
" --wallet-mode <managed|byo|none> Wallet setup mode (default: managed)",
|
|
55
67
|
" --wallet-provider <name> Wallet provider (circle; default: circle)",
|
|
56
68
|
" --wallet-bootstrap <auto|local|remote> Managed wallet setup path (default: auto)",
|
|
@@ -101,6 +113,10 @@ function parseArgs(argv) {
|
|
|
101
113
|
baseUrl: null,
|
|
102
114
|
tenantId: null,
|
|
103
115
|
settldApiKey: null,
|
|
116
|
+
bootstrapApiKey: null,
|
|
117
|
+
sessionFile: defaultSessionPath(),
|
|
118
|
+
bootstrapKeyId: null,
|
|
119
|
+
bootstrapScopes: null,
|
|
104
120
|
walletMode: "managed",
|
|
105
121
|
walletProvider: "circle",
|
|
106
122
|
walletBootstrap: "auto",
|
|
@@ -186,6 +202,35 @@ function parseArgs(argv) {
|
|
|
186
202
|
i = parsed.nextIndex;
|
|
187
203
|
continue;
|
|
188
204
|
}
|
|
205
|
+
if (
|
|
206
|
+
arg === "--bootstrap-api-key" ||
|
|
207
|
+
arg === "--magic-link-api-key" ||
|
|
208
|
+
arg.startsWith("--bootstrap-api-key=") ||
|
|
209
|
+
arg.startsWith("--magic-link-api-key=")
|
|
210
|
+
) {
|
|
211
|
+
const parsed = readArgValue(argv, i, arg);
|
|
212
|
+
out.bootstrapApiKey = parsed.value;
|
|
213
|
+
i = parsed.nextIndex;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (arg === "--session-file" || arg.startsWith("--session-file=")) {
|
|
217
|
+
const parsed = readArgValue(argv, i, arg);
|
|
218
|
+
out.sessionFile = parsed.value;
|
|
219
|
+
i = parsed.nextIndex;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (arg === "--bootstrap-key-id" || arg.startsWith("--bootstrap-key-id=")) {
|
|
223
|
+
const parsed = readArgValue(argv, i, arg);
|
|
224
|
+
out.bootstrapKeyId = parsed.value;
|
|
225
|
+
i = parsed.nextIndex;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (arg === "--bootstrap-scopes" || arg.startsWith("--bootstrap-scopes=")) {
|
|
229
|
+
const parsed = readArgValue(argv, i, arg);
|
|
230
|
+
out.bootstrapScopes = parsed.value;
|
|
231
|
+
i = parsed.nextIndex;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
189
234
|
if (arg === "--wallet-mode" || arg.startsWith("--wallet-mode=")) {
|
|
190
235
|
const parsed = readArgValue(argv, i, arg);
|
|
191
236
|
out.walletMode = String(parsed.value ?? "").trim().toLowerCase();
|
|
@@ -272,6 +317,7 @@ function parseArgs(argv) {
|
|
|
272
317
|
if (out.preflightOnly && out.preflight === false) {
|
|
273
318
|
throw new Error("--preflight-only cannot be combined with --no-preflight");
|
|
274
319
|
}
|
|
320
|
+
out.sessionFile = path.resolve(process.cwd(), String(out.sessionFile ?? "").trim() || defaultSessionPath());
|
|
275
321
|
if (out.outEnv) out.outEnv = path.resolve(process.cwd(), out.outEnv);
|
|
276
322
|
if (out.reportPath) out.reportPath = path.resolve(process.cwd(), out.reportPath);
|
|
277
323
|
return out;
|
|
@@ -290,6 +336,19 @@ function normalizeHttpUrl(value) {
|
|
|
290
336
|
return parsed.toString().replace(/\/+$/, "");
|
|
291
337
|
}
|
|
292
338
|
|
|
339
|
+
function supportsColor(output = process.stdout, env = process.env) {
|
|
340
|
+
if (!output?.isTTY) return false;
|
|
341
|
+
if (String(env.NO_COLOR ?? "").trim()) return false;
|
|
342
|
+
if (String(env.FORCE_COLOR ?? "").trim() === "0") return false;
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function tint(enabled, code, value) {
|
|
347
|
+
const text = String(value ?? "");
|
|
348
|
+
if (!enabled) return text;
|
|
349
|
+
return `${code}${text}${ANSI_RESET}`;
|
|
350
|
+
}
|
|
351
|
+
|
|
293
352
|
function commandExists(command, { platform = process.platform } = {}) {
|
|
294
353
|
const lookupCmd = platform === "win32" ? "where" : "which";
|
|
295
354
|
const probe = spawnSync(lookupCmd, [command], { stdio: "ignore" });
|
|
@@ -582,7 +641,7 @@ async function promptSelect(
|
|
|
582
641
|
stdout,
|
|
583
642
|
label,
|
|
584
643
|
options,
|
|
585
|
-
{ defaultValue = null, hint = null } = {}
|
|
644
|
+
{ defaultValue = null, hint = null, color = false } = {}
|
|
586
645
|
) {
|
|
587
646
|
if (!Array.isArray(options) || options.length === 0) {
|
|
588
647
|
throw new Error(`${label} requires at least one option`);
|
|
@@ -614,14 +673,14 @@ async function promptSelect(
|
|
|
614
673
|
|
|
615
674
|
const render = () => {
|
|
616
675
|
const lines = [];
|
|
617
|
-
lines.push(`${label} (arrow keys + Enter)`);
|
|
676
|
+
lines.push(tint(color, ANSI_CYAN, `${label} (arrow keys + Enter)`));
|
|
618
677
|
for (let i = 0; i < normalizedOptions.length; i += 1) {
|
|
619
678
|
const option = normalizedOptions[i];
|
|
620
|
-
const prefix = i === index ? "
|
|
679
|
+
const prefix = i === index ? tint(color, ANSI_GREEN, "●") : tint(color, ANSI_DIM, "○");
|
|
621
680
|
const detail = option.hint ? ` - ${option.hint}` : "";
|
|
622
|
-
lines.push(` ${prefix} ${option.label}${detail}`);
|
|
681
|
+
lines.push(` ${prefix} ${i === index ? tint(color, ANSI_BOLD, option.label) : option.label}${tint(color, ANSI_DIM, detail)}`);
|
|
623
682
|
}
|
|
624
|
-
if (hint) lines.push(` ${hint}`);
|
|
683
|
+
if (hint) lines.push(` ${tint(color, ANSI_DIM, hint)}`);
|
|
625
684
|
if (renderedLines > 0) {
|
|
626
685
|
stdout.write(`\u001b[${renderedLines}A`);
|
|
627
686
|
}
|
|
@@ -648,7 +707,7 @@ async function promptSelect(
|
|
|
648
707
|
const resolveWithSelection = () => {
|
|
649
708
|
const selected = normalizedOptions[index];
|
|
650
709
|
cleanup();
|
|
651
|
-
stdout.write(`\u001b[2K\r${label}: ${selected.label}\n`);
|
|
710
|
+
stdout.write(`\u001b[2K\r${tint(color, ANSI_CYAN, label)}: ${tint(color, ANSI_GREEN, selected.label)}\n`);
|
|
652
711
|
resolve(selected.value);
|
|
653
712
|
};
|
|
654
713
|
|
|
@@ -679,7 +738,14 @@ async function promptSelect(
|
|
|
679
738
|
});
|
|
680
739
|
}
|
|
681
740
|
|
|
682
|
-
async function promptBooleanChoice(
|
|
741
|
+
async function promptBooleanChoice(
|
|
742
|
+
rl,
|
|
743
|
+
stdin,
|
|
744
|
+
stdout,
|
|
745
|
+
label,
|
|
746
|
+
defaultValue,
|
|
747
|
+
{ trueLabel = "Yes", falseLabel = "No", hint = null, color = false } = {}
|
|
748
|
+
) {
|
|
683
749
|
const selected = await promptSelect(
|
|
684
750
|
rl,
|
|
685
751
|
stdin,
|
|
@@ -689,7 +755,7 @@ async function promptBooleanChoice(rl, stdin, stdout, label, defaultValue, { tru
|
|
|
689
755
|
{ value: "yes", label: trueLabel },
|
|
690
756
|
{ value: "no", label: falseLabel }
|
|
691
757
|
],
|
|
692
|
-
{ defaultValue: defaultValue ? "yes" : "no", hint }
|
|
758
|
+
{ defaultValue: defaultValue ? "yes" : "no", hint, color }
|
|
693
759
|
);
|
|
694
760
|
return selected === "yes";
|
|
695
761
|
}
|
|
@@ -824,6 +890,81 @@ function resolveByoWalletEnv({ walletProvider, walletEnvRows, runtimeEnv }) {
|
|
|
824
890
|
return env;
|
|
825
891
|
}
|
|
826
892
|
|
|
893
|
+
function parseScopes(raw) {
|
|
894
|
+
if (!raw || !String(raw).trim()) return [];
|
|
895
|
+
const seen = new Set();
|
|
896
|
+
const out = [];
|
|
897
|
+
for (const part of String(raw).split(",")) {
|
|
898
|
+
const scope = String(part ?? "").trim();
|
|
899
|
+
if (!scope || seen.has(scope)) continue;
|
|
900
|
+
seen.add(scope);
|
|
901
|
+
out.push(scope);
|
|
902
|
+
}
|
|
903
|
+
return out;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
async function requestRuntimeBootstrapMcpEnv({
|
|
907
|
+
baseUrl,
|
|
908
|
+
tenantId,
|
|
909
|
+
bootstrapApiKey,
|
|
910
|
+
sessionCookie,
|
|
911
|
+
bootstrapKeyId = null,
|
|
912
|
+
bootstrapScopes = [],
|
|
913
|
+
idempotencyKey = null,
|
|
914
|
+
fetchImpl = fetch
|
|
915
|
+
} = {}) {
|
|
916
|
+
const normalizedBaseUrl = normalizeHttpUrl(baseUrl);
|
|
917
|
+
if (!normalizedBaseUrl) throw new Error(`invalid runtime bootstrap base URL: ${baseUrl}`);
|
|
918
|
+
const apiKey = String(bootstrapApiKey ?? "").trim();
|
|
919
|
+
const cookie = String(sessionCookie ?? "").trim();
|
|
920
|
+
if (!apiKey && !cookie) {
|
|
921
|
+
throw new Error("runtime bootstrap requires bootstrap API key or saved login session");
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const headers = {
|
|
925
|
+
"content-type": "application/json"
|
|
926
|
+
};
|
|
927
|
+
if (apiKey) headers["x-api-key"] = apiKey;
|
|
928
|
+
if (cookie) headers.cookie = cookie;
|
|
929
|
+
if (idempotencyKey) headers["x-idempotency-key"] = String(idempotencyKey);
|
|
930
|
+
|
|
931
|
+
const body = {
|
|
932
|
+
apiKey: {
|
|
933
|
+
create: true,
|
|
934
|
+
description: "settld setup runtime bootstrap"
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
if (bootstrapKeyId) body.apiKey.keyId = String(bootstrapKeyId);
|
|
938
|
+
if (Array.isArray(bootstrapScopes) && bootstrapScopes.length > 0) {
|
|
939
|
+
body.apiKey.scopes = bootstrapScopes;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const url = new URL(
|
|
943
|
+
`/v1/tenants/${encodeURIComponent(String(tenantId ?? ""))}/onboarding/runtime-bootstrap`,
|
|
944
|
+
normalizedBaseUrl
|
|
945
|
+
);
|
|
946
|
+
const res = await fetchImpl(url.toString(), {
|
|
947
|
+
method: "POST",
|
|
948
|
+
headers,
|
|
949
|
+
body: JSON.stringify(body)
|
|
950
|
+
});
|
|
951
|
+
const text = await res.text();
|
|
952
|
+
let json = null;
|
|
953
|
+
try {
|
|
954
|
+
json = text ? JSON.parse(text) : null;
|
|
955
|
+
} catch {
|
|
956
|
+
json = null;
|
|
957
|
+
}
|
|
958
|
+
if (!res.ok) {
|
|
959
|
+
const message =
|
|
960
|
+
json && typeof json === "object"
|
|
961
|
+
? json?.message ?? json?.error ?? `HTTP ${res.status}`
|
|
962
|
+
: text || `HTTP ${res.status}`;
|
|
963
|
+
throw new Error(`runtime bootstrap request failed (${res.status}): ${String(message)}`);
|
|
964
|
+
}
|
|
965
|
+
return extractBootstrapMcpEnv(json);
|
|
966
|
+
}
|
|
967
|
+
|
|
827
968
|
async function requestRemoteWalletBootstrap({
|
|
828
969
|
baseUrl,
|
|
829
970
|
tenantId,
|
|
@@ -889,6 +1030,8 @@ async function resolveRuntimeConfig({
|
|
|
889
1030
|
stdout = process.stdout,
|
|
890
1031
|
detectInstalledHostsImpl = detectInstalledHosts
|
|
891
1032
|
}) {
|
|
1033
|
+
const sessionFile = String(args.sessionFile ?? runtimeEnv.SETTLD_SESSION_FILE ?? defaultSessionPath()).trim();
|
|
1034
|
+
const savedSession = await readSavedSession({ sessionPath: sessionFile });
|
|
892
1035
|
const installedHosts = detectInstalledHostsImpl();
|
|
893
1036
|
const defaultHost = selectDefaultHost({
|
|
894
1037
|
explicitHost: args.host ? String(args.host).toLowerCase() : "",
|
|
@@ -900,6 +1043,13 @@ async function resolveRuntimeConfig({
|
|
|
900
1043
|
baseUrl: String(args.baseUrl ?? runtimeEnv.SETTLD_BASE_URL ?? "").trim(),
|
|
901
1044
|
tenantId: String(args.tenantId ?? runtimeEnv.SETTLD_TENANT_ID ?? "").trim(),
|
|
902
1045
|
settldApiKey: String(args.settldApiKey ?? runtimeEnv.SETTLD_API_KEY ?? "").trim(),
|
|
1046
|
+
bootstrapApiKey: String(
|
|
1047
|
+
args.bootstrapApiKey ?? runtimeEnv.SETTLD_BOOTSTRAP_API_KEY ?? runtimeEnv.MAGIC_LINK_API_KEY ?? ""
|
|
1048
|
+
).trim(),
|
|
1049
|
+
sessionFile,
|
|
1050
|
+
sessionCookie: String(runtimeEnv.SETTLD_SESSION_COOKIE ?? "").trim(),
|
|
1051
|
+
bootstrapKeyId: String(args.bootstrapKeyId ?? runtimeEnv.SETTLD_BOOTSTRAP_KEY_ID ?? "").trim(),
|
|
1052
|
+
bootstrapScopes: String(args.bootstrapScopes ?? runtimeEnv.SETTLD_BOOTSTRAP_SCOPES ?? "").trim(),
|
|
903
1053
|
walletProvider: args.walletProvider,
|
|
904
1054
|
walletBootstrap: args.walletBootstrap,
|
|
905
1055
|
circleApiKey: String(args.circleApiKey ?? runtimeEnv.CIRCLE_API_KEY ?? "").trim(),
|
|
@@ -914,12 +1064,19 @@ async function resolveRuntimeConfig({
|
|
|
914
1064
|
dryRun: Boolean(args.dryRun),
|
|
915
1065
|
installedHosts
|
|
916
1066
|
};
|
|
1067
|
+
if (savedSession) {
|
|
1068
|
+
if (!out.baseUrl) out.baseUrl = String(savedSession.baseUrl ?? "").trim();
|
|
1069
|
+
if (!out.tenantId) out.tenantId = String(savedSession.tenantId ?? "").trim();
|
|
1070
|
+
if (!out.sessionCookie) out.sessionCookie = String(savedSession.cookie ?? "").trim();
|
|
1071
|
+
}
|
|
917
1072
|
|
|
918
1073
|
if (args.nonInteractive) {
|
|
919
1074
|
if (!SUPPORTED_HOSTS.includes(out.host)) throw new Error(`--host must be one of: ${SUPPORTED_HOSTS.join(", ")}`);
|
|
920
1075
|
if (!out.baseUrl) throw new Error("--base-url is required");
|
|
921
1076
|
if (!out.tenantId) throw new Error("--tenant-id is required");
|
|
922
|
-
if (!out.settldApiKey
|
|
1077
|
+
if (!out.settldApiKey && !out.bootstrapApiKey && !out.sessionCookie) {
|
|
1078
|
+
throw new Error("--settld-api-key, --bootstrap-api-key, or saved login session is required");
|
|
1079
|
+
}
|
|
923
1080
|
if (out.walletMode === "managed" && out.walletBootstrap === "local" && !out.circleApiKey) {
|
|
924
1081
|
throw new Error("--circle-api-key is required for --wallet-mode managed --wallet-bootstrap local");
|
|
925
1082
|
}
|
|
@@ -929,15 +1086,22 @@ async function resolveRuntimeConfig({
|
|
|
929
1086
|
if (!stdin.isTTY || !stdout.isTTY) {
|
|
930
1087
|
throw new Error("interactive mode requires a TTY. Re-run with --non-interactive and explicit flags.");
|
|
931
1088
|
}
|
|
1089
|
+
const color = supportsColor(stdout, runtimeEnv);
|
|
932
1090
|
const mutableOutput = createMutableOutput(stdout);
|
|
933
1091
|
const rl = createInterface({ input: stdin, output: mutableOutput });
|
|
934
1092
|
try {
|
|
935
|
-
|
|
936
|
-
|
|
1093
|
+
const title = tint(color, ANSI_BOLD, "Settld guided setup");
|
|
1094
|
+
const subtitle = tint(color, ANSI_DIM, "Deterministic onboarding for trusted agent spend");
|
|
1095
|
+
stdout.write(`${title}\n`);
|
|
1096
|
+
stdout.write(`${tint(color, ANSI_MAGENTA, "===================")}\n`);
|
|
1097
|
+
stdout.write(`${subtitle}\n`);
|
|
937
1098
|
if (installedHosts.length > 0) {
|
|
938
|
-
stdout.write(
|
|
1099
|
+
stdout.write(`${tint(color, ANSI_CYAN, "Detected hosts")}: ${installedHosts.join(", ")}\n`);
|
|
939
1100
|
} else {
|
|
940
|
-
stdout.write("Detected hosts: none (will still write config files)\n
|
|
1101
|
+
stdout.write(`${tint(color, ANSI_CYAN, "Detected hosts")}: none (will still write config files)\n`);
|
|
1102
|
+
}
|
|
1103
|
+
if (savedSession?.tenantId) {
|
|
1104
|
+
stdout.write(`${tint(color, ANSI_GREEN, "Saved login session")}: tenant ${savedSession.tenantId}\n`);
|
|
941
1105
|
}
|
|
942
1106
|
stdout.write("\n");
|
|
943
1107
|
|
|
@@ -952,7 +1116,7 @@ async function resolveRuntimeConfig({
|
|
|
952
1116
|
stdout,
|
|
953
1117
|
"Select host",
|
|
954
1118
|
hostOptions,
|
|
955
|
-
{ defaultValue: hostPromptDefault, hint: "Up/Down arrows change selection" }
|
|
1119
|
+
{ defaultValue: hostPromptDefault, hint: "Up/Down arrows change selection", color }
|
|
956
1120
|
);
|
|
957
1121
|
|
|
958
1122
|
if (!out.walletMode) out.walletMode = "managed";
|
|
@@ -966,7 +1130,7 @@ async function resolveRuntimeConfig({
|
|
|
966
1130
|
{ value: "byo", label: "byo", hint: "Use your existing wallet IDs and secrets" },
|
|
967
1131
|
{ value: "none", label: "none", hint: "No payment rail wiring during setup" }
|
|
968
1132
|
],
|
|
969
|
-
{ defaultValue: out.walletMode }
|
|
1133
|
+
{ defaultValue: out.walletMode, color }
|
|
970
1134
|
);
|
|
971
1135
|
|
|
972
1136
|
if (!out.baseUrl) {
|
|
@@ -975,7 +1139,48 @@ async function resolveRuntimeConfig({
|
|
|
975
1139
|
if (!out.tenantId) {
|
|
976
1140
|
out.tenantId = await promptLine(rl, "Tenant ID", { defaultValue: "tenant_default" });
|
|
977
1141
|
}
|
|
978
|
-
if (!out.settldApiKey)
|
|
1142
|
+
if (!out.settldApiKey) {
|
|
1143
|
+
const canUseSavedSession =
|
|
1144
|
+
Boolean(out.sessionCookie) &&
|
|
1145
|
+
(!savedSession ||
|
|
1146
|
+
(normalizeHttpUrl(out.baseUrl) === normalizeHttpUrl(savedSession?.baseUrl) &&
|
|
1147
|
+
String(out.tenantId ?? "").trim() === String(savedSession?.tenantId ?? "").trim()));
|
|
1148
|
+
const keyOptions = [];
|
|
1149
|
+
if (canUseSavedSession) {
|
|
1150
|
+
keyOptions.push({
|
|
1151
|
+
value: "session",
|
|
1152
|
+
label: "Use saved login session",
|
|
1153
|
+
hint: `Reuse ${out.sessionFile} to mint runtime key`
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
keyOptions.push(
|
|
1157
|
+
{ value: "bootstrap", label: "Generate during setup", hint: "Use onboarding bootstrap API key" },
|
|
1158
|
+
{ value: "manual", label: "Paste existing key", hint: "Use an existing tenant API key" }
|
|
1159
|
+
);
|
|
1160
|
+
const keyMode = await promptSelect(
|
|
1161
|
+
rl,
|
|
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");
|
|
1171
|
+
}
|
|
1172
|
+
if (!out.bootstrapKeyId) {
|
|
1173
|
+
out.bootstrapKeyId = await promptLine(rl, "Generated key ID (optional)", { required: false });
|
|
1174
|
+
}
|
|
1175
|
+
if (!out.bootstrapScopes) {
|
|
1176
|
+
out.bootstrapScopes = await promptLine(rl, "Generated key scopes CSV (optional)", { required: false });
|
|
1177
|
+
}
|
|
1178
|
+
} else if (keyMode === "manual") {
|
|
1179
|
+
out.settldApiKey = await promptSecretLine(rl, mutableOutput, stdout, "Settld API key");
|
|
1180
|
+
} else {
|
|
1181
|
+
out.bootstrapApiKey = "";
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
979
1184
|
|
|
980
1185
|
if (out.walletMode === "managed") {
|
|
981
1186
|
out.walletBootstrap = await promptSelect(
|
|
@@ -988,7 +1193,7 @@ async function resolveRuntimeConfig({
|
|
|
988
1193
|
{ value: "local", label: "local", hint: "Always use local Circle API key flow" },
|
|
989
1194
|
{ value: "remote", label: "remote", hint: "Always use tenant onboarding endpoint" }
|
|
990
1195
|
],
|
|
991
|
-
{ defaultValue: out.walletBootstrap || "auto" }
|
|
1196
|
+
{ defaultValue: out.walletBootstrap || "auto", color }
|
|
992
1197
|
);
|
|
993
1198
|
if (out.walletBootstrap === "local" && !out.circleApiKey) {
|
|
994
1199
|
out.circleApiKey = await promptSecretLine(rl, mutableOutput, stdout, "Circle API key");
|
|
@@ -1024,7 +1229,8 @@ async function resolveRuntimeConfig({
|
|
|
1024
1229
|
out.preflight,
|
|
1025
1230
|
{
|
|
1026
1231
|
trueLabel: "Yes - validate API/auth/paths",
|
|
1027
|
-
falseLabel: "No - skip preflight"
|
|
1232
|
+
falseLabel: "No - skip preflight",
|
|
1233
|
+
color
|
|
1028
1234
|
}
|
|
1029
1235
|
);
|
|
1030
1236
|
out.smoke = await promptBooleanChoice(
|
|
@@ -1035,7 +1241,8 @@ async function resolveRuntimeConfig({
|
|
|
1035
1241
|
out.smoke,
|
|
1036
1242
|
{
|
|
1037
1243
|
trueLabel: "Yes - run settld.about probe",
|
|
1038
|
-
falseLabel: "No - skip smoke"
|
|
1244
|
+
falseLabel: "No - skip smoke",
|
|
1245
|
+
color
|
|
1039
1246
|
}
|
|
1040
1247
|
);
|
|
1041
1248
|
|
|
@@ -1047,7 +1254,8 @@ async function resolveRuntimeConfig({
|
|
|
1047
1254
|
!out.skipProfileApply,
|
|
1048
1255
|
{
|
|
1049
1256
|
trueLabel: "Yes - apply profile now",
|
|
1050
|
-
falseLabel: "No - skip profile apply"
|
|
1257
|
+
falseLabel: "No - skip profile apply",
|
|
1258
|
+
color
|
|
1051
1259
|
}
|
|
1052
1260
|
);
|
|
1053
1261
|
out.skipProfileApply = !applyProfile;
|
|
@@ -1065,7 +1273,8 @@ async function resolveRuntimeConfig({
|
|
|
1065
1273
|
out.dryRun,
|
|
1066
1274
|
{
|
|
1067
1275
|
trueLabel: "Yes - preview only",
|
|
1068
|
-
falseLabel: "No - write config"
|
|
1276
|
+
falseLabel: "No - write config",
|
|
1277
|
+
color
|
|
1069
1278
|
}
|
|
1070
1279
|
);
|
|
1071
1280
|
}
|
|
@@ -1084,6 +1293,7 @@ export async function runOnboard({
|
|
|
1084
1293
|
runWizardImpl = runWizard,
|
|
1085
1294
|
loadHostConfigHelperImpl = loadHostConfigHelper,
|
|
1086
1295
|
bootstrapWalletProviderImpl = bootstrapWalletProvider,
|
|
1296
|
+
requestRuntimeBootstrapMcpEnvImpl = requestRuntimeBootstrapMcpEnv,
|
|
1087
1297
|
requestRemoteWalletBootstrapImpl = requestRemoteWalletBootstrap,
|
|
1088
1298
|
runPreflightChecksImpl = runPreflightChecks,
|
|
1089
1299
|
detectInstalledHostsImpl = detectInstalledHosts
|
|
@@ -1110,7 +1320,28 @@ export async function runOnboard({
|
|
|
1110
1320
|
const normalizedBaseUrl = normalizeHttpUrl(mustString(config.baseUrl, "SETTLD_BASE_URL / --base-url"));
|
|
1111
1321
|
if (!normalizedBaseUrl) throw new Error(`invalid Settld base URL: ${config.baseUrl}`);
|
|
1112
1322
|
const tenantId = mustString(config.tenantId, "SETTLD_TENANT_ID / --tenant-id");
|
|
1113
|
-
|
|
1323
|
+
let settldApiKey = String(config.settldApiKey ?? "").trim();
|
|
1324
|
+
let runtimeBootstrapEnv = null;
|
|
1325
|
+
if (!settldApiKey) {
|
|
1326
|
+
if (showSteps) stdout.write("Generating tenant runtime API key via onboarding bootstrap/session...\n");
|
|
1327
|
+
runtimeBootstrapEnv = await requestRuntimeBootstrapMcpEnvImpl({
|
|
1328
|
+
baseUrl: normalizedBaseUrl,
|
|
1329
|
+
tenantId,
|
|
1330
|
+
bootstrapApiKey: config.bootstrapApiKey,
|
|
1331
|
+
sessionCookie: config.sessionCookie,
|
|
1332
|
+
bootstrapKeyId: config.bootstrapKeyId || null,
|
|
1333
|
+
bootstrapScopes: parseScopes(config.bootstrapScopes),
|
|
1334
|
+
fetchImpl
|
|
1335
|
+
});
|
|
1336
|
+
settldApiKey = mustString(runtimeBootstrapEnv?.SETTLD_API_KEY ?? "", "runtime bootstrap SETTLD_API_KEY");
|
|
1337
|
+
}
|
|
1338
|
+
const runtimeBootstrapOptionalEnv = {};
|
|
1339
|
+
if (runtimeBootstrapEnv?.SETTLD_PAID_TOOLS_BASE_URL) {
|
|
1340
|
+
runtimeBootstrapOptionalEnv.SETTLD_PAID_TOOLS_BASE_URL = String(runtimeBootstrapEnv.SETTLD_PAID_TOOLS_BASE_URL);
|
|
1341
|
+
}
|
|
1342
|
+
if (runtimeBootstrapEnv?.SETTLD_PAID_TOOLS_AGENT_PASSPORT) {
|
|
1343
|
+
runtimeBootstrapOptionalEnv.SETTLD_PAID_TOOLS_AGENT_PASSPORT = String(runtimeBootstrapEnv.SETTLD_PAID_TOOLS_AGENT_PASSPORT);
|
|
1344
|
+
}
|
|
1114
1345
|
|
|
1115
1346
|
if (showSteps) printStep(stdout, step, totalSteps, "Resolve wallet configuration");
|
|
1116
1347
|
let walletBootstrapMode = "none";
|
|
@@ -1198,13 +1429,19 @@ export async function runOnboard({
|
|
|
1198
1429
|
preflight,
|
|
1199
1430
|
hostInstallDetected: Array.isArray(config.installedHosts) && config.installedHosts.includes(config.host),
|
|
1200
1431
|
installedHosts: config.installedHosts,
|
|
1201
|
-
env:
|
|
1432
|
+
env: {
|
|
1433
|
+
SETTLD_BASE_URL: normalizedBaseUrl,
|
|
1434
|
+
SETTLD_TENANT_ID: tenantId,
|
|
1435
|
+
SETTLD_API_KEY: settldApiKey,
|
|
1436
|
+
...runtimeBootstrapOptionalEnv,
|
|
1437
|
+
...walletEnv
|
|
1438
|
+
},
|
|
1202
1439
|
outEnv: args.outEnv ?? null,
|
|
1203
1440
|
reportPath: args.reportPath ?? null
|
|
1204
1441
|
};
|
|
1205
1442
|
if (args.outEnv) {
|
|
1206
1443
|
await fs.mkdir(path.dirname(args.outEnv), { recursive: true });
|
|
1207
|
-
await fs.writeFile(args.outEnv, toEnvFileText(
|
|
1444
|
+
await fs.writeFile(args.outEnv, toEnvFileText(payload.env), "utf8");
|
|
1208
1445
|
}
|
|
1209
1446
|
await writeJsonReport(args.reportPath, payload);
|
|
1210
1447
|
if (args.format === "json") {
|
|
@@ -1216,6 +1453,12 @@ export async function runOnboard({
|
|
|
1216
1453
|
lines.push(`Settld: ${normalizedBaseUrl} (tenant=${tenantId})`);
|
|
1217
1454
|
lines.push(`Wallet mode: ${config.walletMode}`);
|
|
1218
1455
|
lines.push(`Wallet bootstrap mode: ${walletBootstrapMode}`);
|
|
1456
|
+
if (config.walletMode !== "none") {
|
|
1457
|
+
lines.push("Wallet next steps:");
|
|
1458
|
+
lines.push("- settld wallet status");
|
|
1459
|
+
lines.push("- settld wallet fund --method transfer");
|
|
1460
|
+
lines.push("- settld wallet balance --watch --min-usdc 1");
|
|
1461
|
+
}
|
|
1219
1462
|
if (args.outEnv) lines.push(`Wrote env file: ${args.outEnv}`);
|
|
1220
1463
|
if (args.reportPath) lines.push(`Wrote report: ${args.reportPath}`);
|
|
1221
1464
|
lines.push("");
|
|
@@ -1251,11 +1494,15 @@ export async function runOnboard({
|
|
|
1251
1494
|
argv: wizardArgv,
|
|
1252
1495
|
fetchImpl,
|
|
1253
1496
|
stdout,
|
|
1254
|
-
extraEnv:
|
|
1497
|
+
extraEnv: {
|
|
1498
|
+
...runtimeBootstrapOptionalEnv,
|
|
1499
|
+
...walletEnv
|
|
1500
|
+
}
|
|
1255
1501
|
});
|
|
1256
1502
|
step += 1;
|
|
1257
1503
|
|
|
1258
1504
|
const mergedEnv = {
|
|
1505
|
+
...runtimeBootstrapOptionalEnv,
|
|
1259
1506
|
...(walletEnv ?? {}),
|
|
1260
1507
|
...(wizardResult?.env && typeof wizardResult.env === "object" ? wizardResult.env : {})
|
|
1261
1508
|
};
|
|
@@ -1316,6 +1563,14 @@ export async function runOnboard({
|
|
|
1316
1563
|
lines.push(`${step}. ${row}`);
|
|
1317
1564
|
step += 1;
|
|
1318
1565
|
}
|
|
1566
|
+
if (config.walletMode !== "none") {
|
|
1567
|
+
lines.push(`${step}. Run \`settld wallet status\` to confirm wallet wiring.`);
|
|
1568
|
+
step += 1;
|
|
1569
|
+
lines.push(`${step}. Fund with \`settld wallet fund --method transfer\` (or \`--method card --open\` if hosted links are configured).`);
|
|
1570
|
+
step += 1;
|
|
1571
|
+
lines.push(`${step}. Confirm funds: \`settld wallet balance --watch --min-usdc 1\`.`);
|
|
1572
|
+
step += 1;
|
|
1573
|
+
}
|
|
1319
1574
|
lines.push(`${step}. Run \`npm run mcp:probe\` for an immediate health check.`);
|
|
1320
1575
|
stdout.write(`${lines.join("\n")}\n`);
|
|
1321
1576
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
const SESSION_SCHEMA_VERSION = "SettldCliSession.v1";
|
|
8
|
+
|
|
9
|
+
function normalizeCookieHeader(value) {
|
|
10
|
+
const raw = String(value ?? "").trim();
|
|
11
|
+
if (!raw) return null;
|
|
12
|
+
const firstSegment = raw.split(";")[0]?.trim() ?? "";
|
|
13
|
+
if (!firstSegment) return null;
|
|
14
|
+
const eq = firstSegment.indexOf("=");
|
|
15
|
+
if (eq <= 0) return null;
|
|
16
|
+
const name = firstSegment.slice(0, eq).trim();
|
|
17
|
+
const token = firstSegment.slice(eq + 1).trim();
|
|
18
|
+
if (!name || !token) return null;
|
|
19
|
+
return `${name}=${token}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function defaultSessionPath({ homeDir = os.homedir() } = {}) {
|
|
23
|
+
return path.join(homeDir, ".settld", "session.json");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function normalizeSession(input) {
|
|
27
|
+
const row = input && typeof input === "object" && !Array.isArray(input) ? input : {};
|
|
28
|
+
const baseUrl = typeof row.baseUrl === "string" ? row.baseUrl.trim().replace(/\/+$/, "") : "";
|
|
29
|
+
const tenantId = typeof row.tenantId === "string" ? row.tenantId.trim() : "";
|
|
30
|
+
const email = typeof row.email === "string" ? row.email.trim().toLowerCase() : "";
|
|
31
|
+
const cookie = normalizeCookieHeader(row.cookie);
|
|
32
|
+
if (!baseUrl || !tenantId || !cookie) return null;
|
|
33
|
+
const out = {
|
|
34
|
+
schemaVersion: SESSION_SCHEMA_VERSION,
|
|
35
|
+
savedAt: typeof row.savedAt === "string" && row.savedAt.trim() ? row.savedAt.trim() : new Date().toISOString(),
|
|
36
|
+
baseUrl,
|
|
37
|
+
tenantId,
|
|
38
|
+
cookie,
|
|
39
|
+
email: email || null
|
|
40
|
+
};
|
|
41
|
+
if (typeof row.expiresAt === "string" && row.expiresAt.trim()) out.expiresAt = row.expiresAt.trim();
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function readSavedSession({ sessionPath = defaultSessionPath() } = {}) {
|
|
46
|
+
try {
|
|
47
|
+
const raw = await fs.readFile(sessionPath, "utf8");
|
|
48
|
+
const parsed = JSON.parse(raw);
|
|
49
|
+
return normalizeSession(parsed);
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function writeSavedSession({ session, sessionPath = defaultSessionPath() } = {}) {
|
|
56
|
+
const normalized = normalizeSession(session);
|
|
57
|
+
if (!normalized) throw new Error("invalid session payload");
|
|
58
|
+
await fs.mkdir(path.dirname(sessionPath), { recursive: true });
|
|
59
|
+
await fs.writeFile(sessionPath, `${JSON.stringify(normalized, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
60
|
+
return normalized;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function cookieHeaderFromSetCookie(value) {
|
|
64
|
+
return normalizeCookieHeader(value);
|
|
65
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
-
if
|
|
5
|
-
echo "MkDocs
|
|
4
|
+
if ! python3 -m mkdocs --version >/dev/null 2>&1; then
|
|
5
|
+
echo "MkDocs not found; running install step first..."
|
|
6
6
|
bash scripts/vercel/install-mkdocs.sh
|
|
7
7
|
fi
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
python3 -m mkdocs build --strict --config-file mkdocs.yml
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Vercel "ignoreCommand" contract:
|
|
5
|
+
# - exit 0 => skip deployment
|
|
6
|
+
# - exit 1 => continue with deployment
|
|
7
|
+
|
|
8
|
+
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
|
9
|
+
cd "$REPO_ROOT"
|
|
10
|
+
|
|
11
|
+
if ! git rev-parse --verify HEAD^ >/dev/null 2>&1; then
|
|
12
|
+
# No parent commit context available; build to stay safe.
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if git diff --quiet HEAD^ HEAD -- \
|
|
17
|
+
dashboard/ \
|
|
18
|
+
scripts/vercel/ignore-dashboard.sh \
|
|
19
|
+
.github/workflows/release.yml \
|
|
20
|
+
.github/workflows/tests.yml; then
|
|
21
|
+
# No website changes; skip dashboard deployment.
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Relevant website/deploy files changed; run deployment.
|
|
26
|
+
exit 1
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
-
python3 -m
|
|
5
|
-
|
|
6
|
-
.vercel-venv/bin/pip install mkdocs mkdocs-material
|
|
4
|
+
python3 -m pip install --break-system-packages --upgrade pip setuptools wheel
|
|
5
|
+
python3 -m pip install --break-system-packages mkdocs mkdocs-material
|