settld 0.2.3 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "settld",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Settld kernel CLI and local control-plane tooling",
5
5
  "private": false,
6
6
  "type": "module",
@@ -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,7 +206,9 @@ export async function runLogin({
190
206
  const rl = interactive ? createInterface({ input: stdin, output: stdout }) : null;
191
207
  try {
192
208
  if (interactive) {
193
- state.baseUrl = await promptLine(rl, "Settld base URL", { defaultValue: state.baseUrl || "https://api.settld.work" });
209
+ if (!args.baseUrlProvided) {
210
+ state.baseUrl = await promptLine(rl, "Settld base URL", { defaultValue: state.baseUrl || "https://api.settld.work" });
211
+ }
194
212
  state.tenantId = await promptLine(rl, "Tenant ID (optional, leave blank to create new)", {
195
213
  defaultValue: state.tenantId,
196
214
  required: false
@@ -205,7 +223,20 @@ export async function runLogin({
205
223
  if (!state.email) throw new Error("email is required");
206
224
  if (!state.tenantId && !state.company) throw new Error("company is required when tenant ID is omitted");
207
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
+
208
238
  let tenantId = state.tenantId;
239
+ let otpAlreadyIssued = false;
209
240
  if (!tenantId) {
210
241
  const signup = await requestJson(`${baseUrl}/v1/public/signup`, {
211
242
  method: "POST",
@@ -213,27 +244,24 @@ export async function runLogin({
213
244
  fetchImpl
214
245
  });
215
246
  if (!signup.res.ok) {
216
- const code = typeof signup.json?.code === "string" ? signup.json.code : "";
217
- const message = typeof signup.json?.message === "string" ? signup.json.message : signup.text;
247
+ const code = responseCode(signup);
248
+ const message = responseMessage(signup);
218
249
  if (code === "SIGNUP_DISABLED") {
219
250
  throw new Error("Public signup is disabled for this environment. Use an existing tenant ID or bootstrap key flow.");
220
251
  }
221
- throw new Error(`public signup failed (${signup.res.status}): ${message || "unknown error"}`);
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}`);
222
258
  }
223
259
  tenantId = String(signup.json?.tenantId ?? "").trim();
224
260
  if (!tenantId) throw new Error("public signup response missing tenantId");
261
+ otpAlreadyIssued = Boolean(signup.json?.otpIssued);
225
262
  if (interactive) stdout.write(`Created tenant: ${tenantId}\n`);
226
- } else {
227
- const otpRequest = await requestJson(`${baseUrl}/v1/tenants/${encodeURIComponent(tenantId)}/buyer/login/otp`, {
228
- method: "POST",
229
- body: { email: state.email },
230
- fetchImpl
231
- });
232
- if (!otpRequest.res.ok) {
233
- const message = typeof otpRequest.json?.message === "string" ? otpRequest.json.message : otpRequest.text;
234
- throw new Error(`otp request failed (${otpRequest.res.status}): ${message || "unknown error"}`);
235
- }
236
263
  }
264
+ if (!otpAlreadyIssued) await requestTenantOtp(tenantId);
237
265
 
238
266
  if (!state.otp && interactive) {
239
267
  state.otp = await promptLine(rl, "OTP code", { required: true });
@@ -246,8 +274,8 @@ export async function runLogin({
246
274
  fetchImpl
247
275
  });
248
276
  if (!login.res.ok) {
249
- const message = typeof login.json?.message === "string" ? login.json.message : login.text;
250
- throw new Error(`login failed (${login.res.status}): ${message || "unknown error"}`);
277
+ const message = responseMessage(login);
278
+ throw new Error(`login failed (${login.res.status}): ${message}`);
251
279
  }
252
280
  const setCookie = login.res.headers.get("set-cookie") ?? "";
253
281
  const cookie = cookieHeaderFromSetCookie(setCookie);
@@ -1361,21 +1361,26 @@ async function resolveRuntimeConfig({
1361
1361
  { defaultValue: canUseSavedSession ? "session" : "login", color }
1362
1362
  );
1363
1363
  if (keyMode === "login") {
1364
- await runLoginImpl({
1365
- argv: ["--base-url", out.baseUrl, "--session-file", out.sessionFile],
1366
- stdin,
1367
- stdout,
1368
- fetchImpl
1369
- });
1370
- const refreshedSession = await readSavedSessionImpl({ sessionPath: out.sessionFile });
1371
- if (!refreshedSession) throw new Error("login did not produce a saved session");
1372
- out.baseUrl = String(refreshedSession.baseUrl ?? out.baseUrl).trim() || out.baseUrl;
1373
- out.tenantId = String(refreshedSession.tenantId ?? out.tenantId).trim();
1374
- out.sessionCookie = String(refreshedSession.cookie ?? out.sessionCookie).trim();
1375
- if (savedSession) {
1376
- savedSession.baseUrl = refreshedSession.baseUrl;
1377
- savedSession.tenantId = refreshedSession.tenantId;
1378
- savedSession.cookie = refreshedSession.cookie;
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");
1379
1384
  }
1380
1385
  continue;
1381
1386
  }