shopstack 0.2.5 → 0.2.6

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 CHANGED
@@ -16,30 +16,27 @@ The CLI and client default to Shopstack's production API at
16
16
  `https://api.shopstack.ai/v1`. Set
17
17
  `SHOPSTACK_API_URL` only when targeting a different Shopstack environment.
18
18
 
19
- ## Verified signup
19
+ ## Verified login
20
20
 
21
21
  ```bash
22
- shopstack signup
22
+ shopstack login [--email EMAIL] [--profile NAME] [--account-type personal|developer]
23
23
  ```
24
24
 
25
- The command asks for email and Personal or Developer account type. It starts a
26
- 15-minute signup and waits while you open the verification email. No account or
27
- API key exists until the link is consumed. The CLI generates and privately
28
- persists all retry and polling capabilities before the first request, then
29
- retrieves the first key after verification and stores it in
25
+ The command asks for an email when `--email` is absent. It uses the `default`
26
+ profile and Personal account preference unless flags select other values. An
27
+ existing account keeps its current account type. A new email creates the
28
+ requested account type. The command waits for up to 15 minutes while you open
29
+ the verification email. It does not issue a new API key until the link is
30
+ consumed. The CLI privately persists all retry and polling capabilities before
31
+ the first request, then retrieves a new key after verification and stores it in
30
32
  `~/.config/shopstack/config.json` with file mode `0600`; it never prints the
31
33
  key, recovery capability, or polling capability. If the command is interrupted,
32
- rerun `shopstack signup`; the matching pending attempt resumes automatically.
33
- The explicit helper also remains available:
34
-
35
- ```bash
36
- shopstack signup resume sup_...
37
- ```
34
+ rerun `shopstack login`; the matching pending attempt resumes automatically.
38
35
 
39
36
  ## Developer account and users
40
37
 
41
38
  ```bash
42
- shopstack signup
39
+ shopstack login
43
40
  shopstack users create --external-id customer-123 --profile customer-123
44
41
  ```
45
42
 
@@ -61,7 +58,7 @@ Configure any stdio MCP client to run:
61
58
  }
62
59
  ```
63
60
 
64
- The MCP server can start and poll verified signup, manage local profiles,
61
+ The MCP server has one verified login tool, manages local profiles,
65
62
  create developer-owned users, list/connect Link, and create/poll/message/cancel
66
63
  checkouts. Checkout creation returns the exact live-view URL as a clickable MCP
67
64
  resource; `get_live_view` replaces a lost one-time link for an active checkout.
@@ -147,11 +144,11 @@ Card values are never accepted as command-line flags.
147
144
  import { ShopstackClient } from "shopstack";
148
145
 
149
146
  const onboarding = new ShopstackClient();
150
- const account = await onboarding.signup({
147
+ const account = await onboarding.login({
151
148
  accountType: "developer",
152
149
  email: "developer@example.com",
153
150
  onProgress: ({ state }) => console.log(state),
154
- persistence: secureSignupPersistence,
151
+ persistence: secureLoginPersistence,
155
152
  });
156
153
 
157
154
  const shopstack = new ShopstackClient({
@@ -165,8 +162,8 @@ const result = await shopstack.runCheckout(checkoutRequest, {
165
162
  });
166
163
  ```
167
164
 
168
- `signup` generates retry state internally and returns the completed account
169
- identity only after the one-time credential is stored. It never returns the API
165
+ `login` generates retry state internally and returns the authenticated account
166
+ identity only after the new credential is stored. It never returns the API
170
167
  key, recovery key, or polling token. Pass a secure persistence adapter for
171
168
  restart recovery and durable credential storage; never use browser storage.
172
169
 
package/SKILL.md CHANGED
@@ -38,21 +38,22 @@ Never ask the model to print or relay a Shopstack API key. The CLI and MCP serve
38
38
  - A developer management key creates and manages users but cannot run a user's checkout.
39
39
  - Every developer-owned user receives an independent user API key and profile.
40
40
 
41
- Start verified CLI signup:
41
+ Start verified CLI login:
42
42
 
43
43
  ```bash
44
- shopstack signup
44
+ shopstack login [--email EMAIL] [--profile NAME] [--account-type personal|developer]
45
45
  ```
46
46
 
47
- The command prompts for email and Personal or Developer account type. The user
48
- must open the time-limited verification link. No account or API key exists
49
- before verification. The CLI generates and privately stores its retry and
50
- polling capabilities before the request, polls signup, saves the verified key
51
- with mode `0600`, and prints none of those credentials. Rerunning
52
- `shopstack signup` automatically resumes the matching pending attempt after an
53
- interruption; `shopstack signup resume SIGNUP_ID` remains an explicit helper.
47
+ The command prompts only for a missing email. Personal and `default` are the
48
+ default account preference and profile. An existing account keeps its current
49
+ type. A new email creates the requested account type. The user must open the
50
+ time-limited verification link. The CLI generates and privately stores its
51
+ retry and polling capabilities before the request, polls login, saves the new
52
+ key with mode `0600`, and prints none of those credentials. Rerunning
53
+ `shopstack login` automatically resumes the matching pending attempt after an
54
+ interruption.
54
55
 
55
- After developer signup, create an end user:
56
+ After developer login, create an end user:
56
57
 
57
58
  ```bash
58
59
  shopstack users create --external-id customer-123 --profile customer-123
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shopstack",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Production Shopstack SDK and CLI for agentic checkout.",
5
5
  "type": "module",
6
6
  "exports": {
package/src/cli.js CHANGED
@@ -17,14 +17,8 @@ Usage:
17
17
  shopstack <command> [options]
18
18
 
19
19
  Account setup:
20
- shopstack signup
21
- # Create or resume an email-verified Personal or Developer profile.
22
- shopstack signup user --email EMAIL
23
- # Create a Personal profile without interactive account-type prompts.
24
- shopstack signup developer --email EMAIL
25
- # Create a Developer management profile without interactive prompts.
26
- shopstack signup resume SIGNUP_ID
27
- # Resume a known pending email-verification flow.
20
+ shopstack login [--email EMAIL] [--profile NAME] [--account-type personal|developer]
21
+ # Log in, or create the requested account type when the email is new.
28
22
  shopstack users create --external-id ID [--profile NAME]
29
23
  # Create an independently scoped user from a Developer profile.
30
24
  shopstack profiles list
@@ -86,11 +80,6 @@ function required(options, name) {
86
80
  return value;
87
81
  }
88
82
 
89
- function isAffirmative(value) {
90
- const answer = String(value).trim().toLowerCase();
91
- return answer === "y" || answer === "yes";
92
- }
93
-
94
83
  function writeJson(stream, value) {
95
84
  stream.write(`${JSON.stringify(value, null, 2)}\n`);
96
85
  }
@@ -240,7 +229,7 @@ function sanitizedUserResult(result, profile) {
240
229
  return { api_key_saved: true, profile, user };
241
230
  }
242
231
 
243
- async function saveVerifiedSignup(result, profileName, configStore) {
232
+ async function saveVerifiedLogin(result, profileName, configStore) {
244
233
  await configStore.saveProfile(
245
234
  profileName,
246
235
  {
@@ -253,34 +242,34 @@ async function saveVerifiedSignup(result, profileName, configStore) {
253
242
  );
254
243
  }
255
244
 
256
- function signupPersistence(configStore, profileName) {
245
+ function loginPersistence(configStore, profileName) {
257
246
  return {
258
247
  async loadPending(input) {
259
- return typeof configStore.findPendingSignup === "function"
260
- ? configStore.findPendingSignup(input)
248
+ return typeof configStore.findPendingLogin === "function"
249
+ ? configStore.findPendingLogin(input)
261
250
  : undefined;
262
251
  },
263
252
  async savePending(state) {
264
- await configStore.savePendingSignup({ ...state, profile: profileName });
253
+ await configStore.savePendingLogin({ ...state, profile: profileName });
265
254
  },
266
255
  async completePending(state, result) {
267
- const pendingId = state.attemptId ?? state.signupId ?? state.id;
268
- if (typeof configStore.completePendingSignup === "function") {
269
- await configStore.completePendingSignup(pendingId, profileName, result);
256
+ const pendingId = state.attemptId ?? state.loginId ?? state.id;
257
+ if (typeof configStore.completePendingLogin === "function") {
258
+ await configStore.completePendingLogin(pendingId, profileName, result);
270
259
  return;
271
260
  }
272
- await saveVerifiedSignup(result, profileName, configStore);
273
- await configStore.deletePendingSignup(pendingId);
261
+ await saveVerifiedLogin(result, profileName, configStore);
262
+ await configStore.deletePendingLogin(pendingId);
274
263
  },
275
264
  async deletePending(state) {
276
- await configStore.deletePendingSignup(
277
- state.attemptId ?? state.signupId ?? state.id,
265
+ await configStore.deletePendingLogin(
266
+ state.attemptId ?? state.loginId ?? state.id,
278
267
  );
279
268
  },
280
269
  };
281
270
  }
282
271
 
283
- async function reportSignupProgress(progress, stream) {
272
+ async function reportLoginProgress(progress, stream) {
284
273
  switch (progress.state) {
285
274
  case "email_sent":
286
275
  stream.write("Verification email sent.\n");
@@ -292,23 +281,21 @@ async function reportSignupProgress(progress, stream) {
292
281
  stream.write("✓ Email verified\n");
293
282
  return;
294
283
  case "complete":
295
- stream.write("✓ Shopstack account created\n");
284
+ stream.write("✓ Shopstack login complete\n");
296
285
  stream.write("✓ Credentials saved securely\n");
297
286
  return;
298
287
  case "expired":
299
- stream.write("Signup expired. Start again to receive a new email.\n");
288
+ stream.write("Login expired. Start again to receive a new email.\n");
300
289
  return;
301
290
  case "rate_limited":
302
- stream.write(
303
- "Signup is rate-limited. Retry after the indicated delay.\n",
304
- );
291
+ stream.write("Login is rate-limited. Retry after the indicated delay.\n");
305
292
  return;
306
293
  case "conflict":
307
- stream.write("Signup retry state conflicted and was cleared.\n");
294
+ stream.write("Login retry state conflicted and was cleared.\n");
308
295
  return;
309
296
  case "retryable_failure":
310
297
  stream.write(
311
- "Signup paused after a retryable failure. Run signup again to resume.\n",
298
+ "Login paused after a retryable failure. Run login again to resume.\n",
312
299
  );
313
300
  return;
314
301
  default:
@@ -321,8 +308,7 @@ async function activeClient(dependencies, requiredKind = "user") {
321
308
  const environmentKey = dependencies.env.SHOPSTACK_API_KEY;
322
309
  const apiKey = environmentKey || profile?.apiKey;
323
310
  const keyType = environmentKey ? requiredKind : profile?.keyType;
324
- if (!apiKey)
325
- throw new Error("No active Shopstack API key. Run signup first.");
311
+ if (!apiKey) throw new Error("No active Shopstack API key. Run login first.");
326
312
  if (requiredKind && keyType !== requiredKind) {
327
313
  throw new Error(`This command requires an active ${requiredKind} profile.`);
328
314
  }
@@ -368,136 +354,62 @@ export async function runCli(args, supplied = {}) {
368
354
  return;
369
355
  }
370
356
 
371
- if (group === "signup") {
372
- if (action === "resume") {
373
- if (rest.length !== 1) {
374
- throw new Error("Use `shopstack signup resume SIGNUP_ID`.");
375
- }
376
- const pending = await dependencies.configStore.pendingSignup(rest[0]);
377
- if (pending === undefined) {
378
- throw new Error(
379
- "That signup is not present in the local profile store.",
380
- );
381
- }
382
- const client = dependencies.clientFactory({
383
- baseUrl: dependencies.env.SHOPSTACK_API_URL,
384
- });
385
- const result = await client.signup({
386
- accountType: pending.accountType,
387
- email: pending.email,
388
- onProgress: (progress) =>
389
- reportSignupProgress(progress, dependencies.stderr),
390
- persistence: signupPersistence(
391
- dependencies.configStore,
392
- pending.profile,
393
- ),
394
- });
395
- writeJson(dependencies.stdout, {
396
- ...sanitizedAccountResult(result),
397
- profile: pending.profile,
398
- });
399
- return;
400
- }
401
- if (action !== undefined && action !== "user" && action !== "developer") {
402
- throw new Error(
403
- "Use `shopstack signup user` or `shopstack signup developer`.",
404
- );
405
- }
406
- const signupArgs = action === undefined ? [] : rest;
357
+ if (group === "login") {
407
358
  const { options, positional } = parseOptions(
408
- signupArgs,
409
- new Set(["email", "profile"]),
359
+ args.slice(1),
360
+ new Set(["account-type", "email", "profile"]),
410
361
  );
411
- if (positional.length > 0) throw new Error("Unexpected signup argument.");
362
+ if (positional.length > 0) throw new Error("Unexpected login argument.");
363
+ const profileName = options.profile ?? "default";
364
+ const requestedAccountType = options["account-type"] ?? "personal";
365
+ if (
366
+ requestedAccountType !== "personal" &&
367
+ requestedAccountType !== "developer"
368
+ ) {
369
+ throw new Error("Account type must be personal or developer.");
370
+ }
412
371
  const pendingCandidates =
413
- action === undefined &&
414
- typeof dependencies.configStore.pendingSignups === "function"
415
- ? await dependencies.configStore.pendingSignups()
372
+ typeof dependencies.configStore.pendingLogins === "function"
373
+ ? await dependencies.configStore.pendingLogins()
416
374
  : [];
375
+ const matchingPending = pendingCandidates.filter(
376
+ (pending) =>
377
+ pending.profile === profileName &&
378
+ (options.email === undefined ||
379
+ pending.email.trim().toLowerCase() ===
380
+ options.email.trim().toLowerCase()) &&
381
+ (options["account-type"] === undefined ||
382
+ pending.accountType === requestedAccountType),
383
+ );
417
384
  const resumable =
418
- pendingCandidates.length === 1 ? pendingCandidates[0] : undefined;
419
- let accountType;
420
- let email;
385
+ matchingPending.length === 1 ? matchingPending[0] : undefined;
386
+ const accountType = resumable?.accountType ?? requestedAccountType;
387
+ let email = resumable?.email ?? options.email;
421
388
  if (resumable !== undefined) {
422
- accountType = resumable.accountType;
423
- email = resumable.email;
424
- dependencies.stderr.write("Resuming pending signup.\n");
425
- } else if (action === undefined) {
389
+ dependencies.stderr.write("Resuming pending login.\n");
390
+ }
391
+ if (email === undefined) {
426
392
  email = String(await visiblePrompt("Email: ", dependencies)).trim();
427
- if (email.length === 0) throw new Error("Email is required.");
428
- const selected = String(
429
- await visiblePrompt(
430
- "Account type (Personal / Developer): ",
431
- dependencies,
432
- ),
433
- )
434
- .trim()
435
- .toLowerCase();
436
- if (selected === "personal" || selected === "user") {
437
- accountType = "personal";
438
- } else if (selected === "developer") {
439
- accountType = "developer";
440
- } else {
441
- throw new Error("Account type must be Personal or Developer.");
442
- }
443
- } else {
444
- accountType = action === "user" ? "personal" : "developer";
445
- email = required(options, "email");
446
393
  }
394
+ if (email.length === 0) throw new Error("Email is required.");
447
395
  const client = dependencies.clientFactory({
448
396
  baseUrl: dependencies.env.SHOPSTACK_API_URL,
449
397
  });
450
- const profileName =
451
- options.profile ??
452
- resumable?.profile ??
453
- (accountType === "developer" ? "developer" : "default");
454
- const result = await client.signup({
398
+ const result = await client.login({
455
399
  accountType,
456
400
  email,
457
401
  onProgress: (progress) =>
458
- reportSignupProgress(progress, dependencies.stderr),
459
- persistence: signupPersistence(dependencies.configStore, profileName),
402
+ reportLoginProgress(progress, dependencies.stderr),
403
+ persistence: loginPersistence(dependencies.configStore, profileName),
460
404
  });
461
405
  writeJson(dependencies.stdout, {
462
406
  ...sanitizedAccountResult(result),
463
407
  profile: profileName,
464
408
  });
465
- if (accountType === "developer") {
409
+ if (result.key_type === "developer") {
466
410
  dependencies.stderr.write(
467
411
  "The developer management credential cannot run user checkouts. Create a user profile with `shopstack users create --external-id ID`.\n",
468
412
  );
469
- if (
470
- action === undefined &&
471
- isAffirmative(
472
- await visiblePrompt(
473
- "Create the first developer-owned user now? (y/N): ",
474
- dependencies,
475
- ),
476
- )
477
- ) {
478
- const externalId = String(
479
- await visiblePrompt("User external ID: ", dependencies),
480
- ).trim();
481
- if (externalId.length === 0) {
482
- throw new Error("User external ID is required.");
483
- }
484
- const { client: developerClient, profile } = await activeClient(
485
- dependencies,
486
- "developer",
487
- );
488
- const user = await developerClient.createUser({ externalId });
489
- await dependencies.configStore.saveProfile(
490
- externalId,
491
- {
492
- accountId: profile.accountId,
493
- apiKey: user.api_key,
494
- keyType: "user",
495
- userId: user.id,
496
- },
497
- { activate: true },
498
- );
499
- writeJson(dependencies.stdout, sanitizedUserResult(user, externalId));
500
- }
501
413
  }
502
414
  return;
503
415
  }
package/src/client.d.ts CHANGED
@@ -82,7 +82,7 @@ export interface LiveViewCapability {
82
82
  live_view_url: string;
83
83
  }
84
84
 
85
- export interface AccountSignupStarted {
85
+ export interface AccountLoginStarted {
86
86
  id: string;
87
87
  account_type: "personal" | "developer";
88
88
  email: string;
@@ -92,7 +92,7 @@ export interface AccountSignupStarted {
92
92
  poll_token: string;
93
93
  }
94
94
 
95
- export interface VerifiedAccountSignup {
95
+ export interface VerifiedAccountLogin {
96
96
  id: string;
97
97
  status: "verified";
98
98
  verified_at: string;
@@ -102,19 +102,19 @@ export interface VerifiedAccountSignup {
102
102
  user?: Record<string, unknown>;
103
103
  }
104
104
 
105
- export interface SignupOptions {
105
+ export interface LoginOptions {
106
106
  accountType: "personal" | "developer";
107
107
  email: string;
108
108
  pollIntervalMs?: number;
109
109
  timeoutMs?: number;
110
- persistence?: SignupPersistence;
111
- onProgress?(progress: SignupProgress): void | Promise<void>;
110
+ persistence?: LoginPersistence;
111
+ onProgress?(progress: LoginProgress): void | Promise<void>;
112
112
  onVerificationRequired?(
113
- signup: Omit<AccountSignupStarted, "poll_token">,
113
+ login: Omit<AccountLoginStarted, "poll_token">,
114
114
  ): void | Promise<void>;
115
115
  }
116
116
 
117
- export interface PendingSignupState {
117
+ export interface PendingLoginState {
118
118
  accountType: "personal" | "developer";
119
119
  attemptId: string;
120
120
  email: string;
@@ -122,23 +122,23 @@ export interface PendingSignupState {
122
122
  idempotencyKey: string;
123
123
  pollToken?: string;
124
124
  recoveryKey: string;
125
- signupId?: string;
125
+ loginId?: string;
126
126
  }
127
127
 
128
- export interface SignupPersistence {
128
+ export interface LoginPersistence {
129
129
  loadPending(input: {
130
130
  accountType: "personal" | "developer";
131
131
  email: string;
132
- }): Promise<PendingSignupState | undefined>;
133
- savePending(state: PendingSignupState): Promise<void>;
132
+ }): Promise<PendingLoginState | undefined>;
133
+ savePending(state: PendingLoginState): Promise<void>;
134
134
  completePending(
135
- state: PendingSignupState,
136
- result: VerifiedAccountSignup,
135
+ state: PendingLoginState,
136
+ result: VerifiedAccountLogin,
137
137
  ): Promise<void>;
138
- deletePending(state: PendingSignupState): Promise<void>;
138
+ deletePending(state: PendingLoginState): Promise<void>;
139
139
  }
140
140
 
141
- export interface SignupProgress {
141
+ export interface LoginProgress {
142
142
  state:
143
143
  | "pending"
144
144
  | "email_sent"
@@ -155,7 +155,7 @@ export interface SignupProgress {
155
155
  key_type?: "user" | "developer";
156
156
  }
157
157
 
158
- export type CompletedAccountSignup = Omit<VerifiedAccountSignup, "api_key">;
158
+ export type CompletedAccountLogin = Omit<VerifiedAccountLogin, "api_key">;
159
159
 
160
160
  export class ShopstackApiError extends Error {
161
161
  code: string;
@@ -165,20 +165,20 @@ export class ShopstackApiError extends Error {
165
165
 
166
166
  export class ShopstackClient {
167
167
  constructor(options?: ShopstackClientOptions);
168
- startAccountSignup(input: {
168
+ startAccountLogin(input: {
169
169
  accountType: "personal" | "developer";
170
170
  email: string;
171
- }): Promise<AccountSignupStarted>;
172
- pollAccountSignup(
173
- signupId: string,
171
+ }): Promise<AccountLoginStarted>;
172
+ pollAccountLogin(
173
+ loginId: string,
174
174
  pollToken: string,
175
- ): Promise<Omit<AccountSignupStarted, "poll_token"> | VerifiedAccountSignup>;
176
- waitForAccountSignup(
177
- signupId: string,
175
+ ): Promise<Omit<AccountLoginStarted, "poll_token"> | VerifiedAccountLogin>;
176
+ waitForAccountLogin(
177
+ loginId: string,
178
178
  pollToken: string,
179
179
  options?: { pollIntervalMs?: number; timeoutMs?: number },
180
- ): Promise<VerifiedAccountSignup>;
181
- signup(input: SignupOptions): Promise<CompletedAccountSignup>;
180
+ ): Promise<VerifiedAccountLogin>;
181
+ login(input: LoginOptions): Promise<CompletedAccountLogin>;
182
182
  createUser(input: {
183
183
  externalId: string;
184
184
  idempotencyKey?: string;
package/src/client.js CHANGED
@@ -17,11 +17,11 @@ function idempotencyKey(prefix) {
17
17
  return `${prefix}-${randomUUID()}`;
18
18
  }
19
19
 
20
- function signupRecoveryKey() {
21
- return `signup_recovery_${randomBytes(32).toString("base64url")}`;
20
+ function loginRecoveryKey() {
21
+ return `login_recovery_${randomBytes(32).toString("base64url")}`;
22
22
  }
23
23
 
24
- function signupAttemptKey(accountType, email) {
24
+ function loginAttemptKey(accountType, email) {
25
25
  return `${accountType}\u0000${email.trim().toLowerCase()}`;
26
26
  }
27
27
 
@@ -164,7 +164,7 @@ export class ShopstackClient {
164
164
  body,
165
165
  idempotencyKey: mutationKey,
166
166
  method = "GET",
167
- signupRecoveryKey: recoveryKey,
167
+ loginRecoveryKey: recoveryKey,
168
168
  } = {},
169
169
  ) {
170
170
  const headers = new Headers({ Accept: "application/json" });
@@ -174,7 +174,7 @@ export class ShopstackClient {
174
174
  }
175
175
  if (body !== undefined) headers.set("Content-Type", "application/json");
176
176
  if (mutationKey) headers.set("Idempotency-Key", mutationKey);
177
- if (recoveryKey) headers.set("Signup-Recovery-Key", recoveryKey);
177
+ if (recoveryKey) headers.set("Login-Recovery-Key", recoveryKey);
178
178
  const response = await this.fetch(`${this.baseUrl}${path}`, {
179
179
  body: body === undefined ? undefined : JSON.stringify(body),
180
180
  headers,
@@ -208,37 +208,32 @@ export class ShopstackClient {
208
208
  return payload;
209
209
  }
210
210
 
211
- _startAccountSignup({
212
- accountType,
213
- email,
214
- idempotencyKey: key,
215
- recoveryKey,
216
- }) {
217
- return this.request("/accounts", {
211
+ _startAccountLogin({ accountType, email, idempotencyKey: key, recoveryKey }) {
212
+ return this.request("/login", {
218
213
  body: { account_type: accountType, email },
219
214
  idempotencyKey: key,
220
215
  method: "POST",
221
- signupRecoveryKey: recoveryKey,
216
+ loginRecoveryKey: recoveryKey,
222
217
  });
223
218
  }
224
219
 
225
- startAccountSignup({ accountType, email } = {}) {
226
- return this._startAccountSignup({
220
+ startAccountLogin({ accountType, email } = {}) {
221
+ return this._startAccountLogin({
227
222
  accountType,
228
223
  email,
229
224
  idempotencyKey: idempotencyKey("account"),
230
- recoveryKey: signupRecoveryKey(),
225
+ recoveryKey: loginRecoveryKey(),
231
226
  });
232
227
  }
233
228
 
234
- pollAccountSignup(signupId, pollToken) {
235
- return this.request(`/accounts/${encodeURIComponent(signupId)}`, {
229
+ pollAccountLogin(loginId, pollToken) {
230
+ return this.request(`/login/${encodeURIComponent(loginId)}`, {
236
231
  bearerToken: pollToken,
237
232
  });
238
233
  }
239
234
 
240
- async waitForAccountSignup(
241
- signupId,
235
+ async waitForAccountLogin(
236
+ loginId,
242
237
  pollToken,
243
238
  { pollIntervalMs = 2_000, timeoutMs = 15 * 60 * 1_000 } = {},
244
239
  ) {
@@ -246,16 +241,16 @@ export class ShopstackClient {
246
241
  while (true) {
247
242
  if (Date.now() - startedAt >= timeoutMs) {
248
243
  throw new ShopstackApiError("Email verification timed out.", {
249
- code: "signup_timeout",
244
+ code: "login_timeout",
250
245
  });
251
246
  }
252
- const current = await this.pollAccountSignup(signupId, pollToken);
247
+ const current = await this.pollAccountLogin(loginId, pollToken);
253
248
  if (current.status === "verified") return current;
254
249
  await delay(pollIntervalMs);
255
250
  }
256
251
  }
257
252
 
258
- async signup({
253
+ async login({
259
254
  accountType,
260
255
  email,
261
256
  onProgress,
@@ -265,17 +260,17 @@ export class ShopstackClient {
265
260
  timeoutMs = 15 * 60 * 1_000,
266
261
  } = {}) {
267
262
  const normalizedEmail = email.trim().toLowerCase();
268
- const memoryKey = signupAttemptKey(accountType, normalizedEmail);
269
- this._pendingSignups ??= new Map();
263
+ const memoryKey = loginAttemptKey(accountType, normalizedEmail);
264
+ this._pendingLogins ??= new Map();
270
265
  const storage = persistence ?? {
271
266
  completePending: async (state, result) => {
272
267
  this.apiKey = result.api_key;
273
- this._pendingSignups.delete(memoryKey);
268
+ this._pendingLogins.delete(memoryKey);
274
269
  },
275
- deletePending: async () => this._pendingSignups.delete(memoryKey),
276
- loadPending: async () => this._pendingSignups.get(memoryKey),
270
+ deletePending: async () => this._pendingLogins.delete(memoryKey),
271
+ loadPending: async () => this._pendingLogins.get(memoryKey),
277
272
  savePending: async (state) => {
278
- this._pendingSignups.set(memoryKey, structuredClone(state));
273
+ this._pendingLogins.set(memoryKey, structuredClone(state));
279
274
  },
280
275
  };
281
276
  const publish = async (state, details = {}) => {
@@ -299,7 +294,7 @@ export class ShopstackClient {
299
294
  attemptId: `attempt-${randomUUID()}`,
300
295
  email: normalizedEmail,
301
296
  idempotencyKey: idempotencyKey("account"),
302
- recoveryKey: signupRecoveryKey(),
297
+ recoveryKey: loginRecoveryKey(),
303
298
  };
304
299
  await storage.savePending(pending);
305
300
  await publish("pending", {
@@ -307,8 +302,8 @@ export class ShopstackClient {
307
302
  email: normalizedEmail,
308
303
  });
309
304
  try {
310
- if (!pending.signupId || !pending.pollToken) {
311
- const started = await this._startAccountSignup({
305
+ if (!pending.loginId || !pending.pollToken) {
306
+ const started = await this._startAccountLogin({
312
307
  accountType,
313
308
  email: normalizedEmail,
314
309
  idempotencyKey: pending.idempotencyKey,
@@ -318,7 +313,7 @@ export class ShopstackClient {
318
313
  ...pending,
319
314
  expiresAt: started.expires_at,
320
315
  pollToken: started.poll_token,
321
- signupId: started.id,
316
+ loginId: started.id,
322
317
  };
323
318
  await storage.savePending(pending);
324
319
  if (typeof onVerificationRequired === "function") {
@@ -341,8 +336,8 @@ export class ShopstackClient {
341
336
  email: normalizedEmail,
342
337
  expires_at: pending.expiresAt,
343
338
  });
344
- const result = await this.waitForAccountSignup(
345
- pending.signupId,
339
+ const result = await this.waitForAccountLogin(
340
+ pending.loginId,
346
341
  pending.pollToken,
347
342
  { pollIntervalMs, timeoutMs },
348
343
  );
@@ -359,7 +354,7 @@ export class ShopstackClient {
359
354
  const { api_key: _credential, ...completed } = result;
360
355
  return completed;
361
356
  } catch (error) {
362
- if (error?.code === "signup_expired") {
357
+ if (error?.code === "login_expired") {
363
358
  await storage.deletePending(pending);
364
359
  await publish("expired", {
365
360
  account_type: accountType,
@@ -372,7 +367,7 @@ export class ShopstackClient {
372
367
  });
373
368
  } else if (
374
369
  error?.code === "idempotency_conflict" ||
375
- error?.code === "invalid_signup_recovery"
370
+ error?.code === "invalid_login_recovery"
376
371
  ) {
377
372
  await storage.deletePending(pending);
378
373
  await publish("conflict", {
package/src/config.d.ts CHANGED
@@ -5,7 +5,7 @@ export interface ShopstackProfile {
5
5
  userId?: string;
6
6
  }
7
7
 
8
- export interface PendingSignup {
8
+ export interface PendingLogin {
9
9
  accountType: "developer" | "personal";
10
10
  attemptId?: string;
11
11
  email: string;
@@ -15,7 +15,7 @@ export interface PendingSignup {
15
15
  pollToken?: string;
16
16
  profile: string;
17
17
  recoveryKey?: string;
18
- signupId?: string;
18
+ loginId?: string;
19
19
  }
20
20
 
21
21
  export class ConfigStore {
@@ -27,14 +27,14 @@ export class ConfigStore {
27
27
  options?: { activate?: boolean },
28
28
  ): Promise<ShopstackProfile>;
29
29
  useProfile(name: string): Promise<ShopstackProfile>;
30
- savePendingSignup(signup: PendingSignup): Promise<PendingSignup>;
31
- pendingSignup(signupId: string): Promise<PendingSignup | undefined>;
32
- pendingSignups(): Promise<PendingSignup[]>;
33
- findPendingSignup(input: {
30
+ savePendingLogin(login: PendingLogin): Promise<PendingLogin>;
31
+ pendingLogin(loginId: string): Promise<PendingLogin | undefined>;
32
+ pendingLogins(): Promise<PendingLogin[]>;
33
+ findPendingLogin(input: {
34
34
  accountType: "developer" | "personal";
35
35
  email: string;
36
- }): Promise<PendingSignup | undefined>;
37
- completePendingSignup(
36
+ }): Promise<PendingLogin | undefined>;
37
+ completePendingLogin(
38
38
  attemptId: string,
39
39
  profileName: string,
40
40
  result: {
@@ -44,10 +44,10 @@ export class ConfigStore {
44
44
  user?: { id?: string };
45
45
  },
46
46
  ): Promise<ShopstackProfile>;
47
- deletePendingSignup(signupId: string): Promise<void>;
47
+ deletePendingLogin(loginId: string): Promise<void>;
48
48
  load(): Promise<{
49
49
  active: string | null;
50
- pendingSignups: Record<string, PendingSignup>;
50
+ pendingLogins: Record<string, PendingLogin>;
51
51
  profiles: Record<string, ShopstackProfile>;
52
52
  }>;
53
53
  }
package/src/config.js CHANGED
@@ -26,18 +26,21 @@ export class ConfigStore {
26
26
  throw new Error("Shopstack configuration is invalid.");
27
27
  }
28
28
  if (
29
- parsed.pendingSignups !== undefined &&
30
- (typeof parsed.pendingSignups !== "object" ||
31
- parsed.pendingSignups === null ||
32
- Array.isArray(parsed.pendingSignups))
29
+ parsed.pendingLogins !== undefined &&
30
+ (typeof parsed.pendingLogins !== "object" ||
31
+ parsed.pendingLogins === null ||
32
+ Array.isArray(parsed.pendingLogins))
33
33
  ) {
34
34
  throw new Error("Shopstack configuration is invalid.");
35
35
  }
36
- parsed.pendingSignups ??= {};
37
- return parsed;
36
+ return {
37
+ active: parsed.active ?? null,
38
+ pendingLogins: parsed.pendingLogins ?? {},
39
+ profiles: parsed.profiles,
40
+ };
38
41
  } catch (error) {
39
42
  if (error?.code === "ENOENT") {
40
- return { active: null, pendingSignups: {}, profiles: {} };
43
+ return { active: null, pendingLogins: {}, profiles: {} };
41
44
  }
42
45
  throw error;
43
46
  }
@@ -82,50 +85,50 @@ export class ConfigStore {
82
85
  return typeof name === "string" ? config.profiles[name] : undefined;
83
86
  }
84
87
 
85
- async savePendingSignup(signup) {
88
+ async savePendingLogin(login) {
86
89
  const config = await this.load();
87
- const key = signup.attemptId ?? signup.id;
90
+ const key = login.attemptId ?? login.id;
88
91
  if (typeof key !== "string" || key.length === 0) {
89
- throw new Error("Pending signup identity is invalid.");
92
+ throw new Error("Pending login identity is invalid.");
90
93
  }
91
- config.pendingSignups[key] = { ...signup };
94
+ config.pendingLogins[key] = { ...login };
92
95
  await this.write(config);
93
- return config.pendingSignups[key];
96
+ return config.pendingLogins[key];
94
97
  }
95
98
 
96
- async pendingSignup(signupId) {
99
+ async pendingLogin(loginId) {
97
100
  const config = await this.load();
98
101
  return (
99
- config.pendingSignups[signupId] ??
100
- Object.values(config.pendingSignups).find(
101
- (pending) => pending.id === signupId || pending.signupId === signupId,
102
+ config.pendingLogins[loginId] ??
103
+ Object.values(config.pendingLogins).find(
104
+ (pending) => pending.id === loginId || pending.loginId === loginId,
102
105
  )
103
106
  );
104
107
  }
105
108
 
106
- async pendingSignups() {
107
- return Object.values((await this.load()).pendingSignups);
109
+ async pendingLogins() {
110
+ return Object.values((await this.load()).pendingLogins);
108
111
  }
109
112
 
110
- async findPendingSignup({ accountType, email }) {
113
+ async findPendingLogin({ accountType, email }) {
111
114
  const normalizedEmail = email.trim().toLowerCase();
112
- return Object.values((await this.load()).pendingSignups).find(
115
+ return Object.values((await this.load()).pendingLogins).find(
113
116
  (pending) =>
114
117
  pending.accountType === accountType &&
115
118
  pending.email.trim().toLowerCase() === normalizedEmail,
116
119
  );
117
120
  }
118
121
 
119
- async completePendingSignup(attemptId, profileName, result) {
122
+ async completePendingLogin(attemptId, profileName, result) {
120
123
  const config = await this.load();
121
- const entry = Object.entries(config.pendingSignups).find(
124
+ const entry = Object.entries(config.pendingLogins).find(
122
125
  ([key, pending]) =>
123
126
  key === attemptId ||
124
127
  pending.id === attemptId ||
125
- pending.signupId === attemptId,
128
+ pending.loginId === attemptId,
126
129
  );
127
130
  if (entry === undefined) {
128
- throw new Error("Pending signup is unavailable.");
131
+ throw new Error("Pending login is unavailable.");
129
132
  }
130
133
  config.profiles[profileName] = {
131
134
  accountId: result.account.id,
@@ -134,20 +137,20 @@ export class ConfigStore {
134
137
  ...(result.user?.id === undefined ? {} : { userId: result.user.id }),
135
138
  };
136
139
  config.active = profileName;
137
- delete config.pendingSignups[entry[0]];
140
+ delete config.pendingLogins[entry[0]];
138
141
  await this.write(config);
139
142
  return config.profiles[profileName];
140
143
  }
141
144
 
142
- async deletePendingSignup(signupId) {
145
+ async deletePendingLogin(loginId) {
143
146
  const config = await this.load();
144
- const entry = Object.entries(config.pendingSignups).find(
147
+ const entry = Object.entries(config.pendingLogins).find(
145
148
  ([key, pending]) =>
146
- key === signupId ||
147
- pending.id === signupId ||
148
- pending.signupId === signupId,
149
+ key === loginId ||
150
+ pending.id === loginId ||
151
+ pending.loginId === loginId,
149
152
  );
150
- if (entry !== undefined) delete config.pendingSignups[entry[0]];
153
+ if (entry !== undefined) delete config.pendingLogins[entry[0]];
151
154
  await this.write(config);
152
155
  }
153
156
  }