shopstack 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/LICENSE +21 -0
- package/README.md +39 -13
- package/SKILL.md +24 -7
- package/bin/shopstack +3 -1
- package/package.json +29 -5
- package/src/cli.js +210 -37
- package/src/client.d.ts +70 -9
- package/src/client.js +356 -81
- package/src/config.d.ts +22 -3
- package/src/config.js +56 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shopstack
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# shopstack
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
JavaScript client and CLI for Shopstack's asynchronous checkout API.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -12,22 +12,25 @@ npm install -g shopstack
|
|
|
12
12
|
|
|
13
13
|
Node.js 18 or newer is required.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
`https://
|
|
15
|
+
The CLI and client default to Shopstack's production API at
|
|
16
|
+
`https://api.shopstack.ai/v1`. Set
|
|
17
17
|
`SHOPSTACK_API_URL` only when targeting a different Shopstack environment.
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Verified signup
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
shopstack signup
|
|
22
|
+
shopstack signup
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
28
30
|
`~/.config/shopstack/config.json` with file mode `0600`; it never prints the
|
|
29
|
-
key or polling capability.
|
|
30
|
-
the
|
|
31
|
+
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:
|
|
31
34
|
|
|
32
35
|
```bash
|
|
33
36
|
shopstack signup resume sup_...
|
|
@@ -36,7 +39,7 @@ shopstack signup resume sup_...
|
|
|
36
39
|
## Developer account and users
|
|
37
40
|
|
|
38
41
|
```bash
|
|
39
|
-
shopstack signup
|
|
42
|
+
shopstack signup
|
|
40
43
|
shopstack users create --external-id customer-123 --profile customer-123
|
|
41
44
|
```
|
|
42
45
|
|
|
@@ -65,7 +68,7 @@ phase. It deliberately has no card-input or payment-approval tool.
|
|
|
65
68
|
|
|
66
69
|
The canonical agent skill ships as `SKILL.md` in this package. After
|
|
67
70
|
publication it is available at
|
|
68
|
-
`https://unpkg.com/shopstack@0.2.
|
|
71
|
+
`https://unpkg.com/shopstack@0.2.2/SKILL.md` with the release-pinned package.
|
|
69
72
|
|
|
70
73
|
## Connections
|
|
71
74
|
|
|
@@ -110,6 +113,12 @@ Then run:
|
|
|
110
113
|
shopstack checkout run --file checkout.json
|
|
111
114
|
```
|
|
112
115
|
|
|
116
|
+
The CLI prints the private owner live-view URL as soon as checkout creation
|
|
117
|
+
succeeds. Do not redirect it into shared logs or expose it to a model. The page
|
|
118
|
+
is view-only unless the model explicitly requests human assistance; while that
|
|
119
|
+
request is active, one owner viewer can use **Take control** and **Return
|
|
120
|
+
control**. **Stop** is always available and never approves payment.
|
|
121
|
+
|
|
113
122
|
When no provider is selected, the checkout runs normally until the payment
|
|
114
123
|
form, then asks for card details through a no-echo terminal prompt. Card data is
|
|
115
124
|
sent only to the protected payment-details endpoint. The CLI separately shows
|
|
@@ -125,6 +134,14 @@ Card values are never accepted as command-line flags.
|
|
|
125
134
|
```js
|
|
126
135
|
import { ShopstackClient } from "shopstack";
|
|
127
136
|
|
|
137
|
+
const onboarding = new ShopstackClient();
|
|
138
|
+
const account = await onboarding.signup({
|
|
139
|
+
accountType: "developer",
|
|
140
|
+
email: "developer@example.com",
|
|
141
|
+
onProgress: ({ state }) => console.log(state),
|
|
142
|
+
persistence: secureSignupPersistence,
|
|
143
|
+
});
|
|
144
|
+
|
|
128
145
|
const shopstack = new ShopstackClient({
|
|
129
146
|
apiKey: process.env.SHOPSTACK_API_KEY,
|
|
130
147
|
});
|
|
@@ -136,9 +153,18 @@ const result = await shopstack.runCheckout(checkoutRequest, {
|
|
|
136
153
|
});
|
|
137
154
|
```
|
|
138
155
|
|
|
156
|
+
`signup` generates retry state internally and returns the completed account
|
|
157
|
+
identity only after the one-time credential is stored. It never returns the API
|
|
158
|
+
key, recovery key, or polling token. Pass a secure persistence adapter for
|
|
159
|
+
restart recovery and durable credential storage; never use browser storage.
|
|
160
|
+
|
|
139
161
|
`runCheckout` returns at a required input if its corresponding callback is
|
|
140
162
|
omitted. Final payment approval is never inferred from a message or from
|
|
141
|
-
supplying a card.
|
|
163
|
+
supplying a card. While the checkout is active, the CLI and client use a one-time
|
|
164
|
+
checkout-scoped WebSocket capability for change notification and always fetches
|
|
165
|
+
the canonical resource after a signal. It falls back to bounded revision-aware
|
|
166
|
+
HTTP waiting, then to fixed polling when either newer transport is unavailable.
|
|
167
|
+
Neither the API key nor WebSocket capability is placed in the socket URL.
|
|
142
168
|
|
|
143
169
|
Set `SHOPSTACK_API_URL` to target a different Shopstack API and
|
|
144
170
|
`SHOPSTACK_CONFIG_FILE` to relocate CLI profiles.
|
package/SKILL.md
CHANGED
|
@@ -41,14 +41,16 @@ Never ask the model to print or relay a Shopstack API key. The CLI and MCP serve
|
|
|
41
41
|
Start verified CLI signup:
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
shopstack signup
|
|
45
|
-
shopstack signup developer --email developer@example.com
|
|
44
|
+
shopstack signup
|
|
46
45
|
```
|
|
47
46
|
|
|
48
|
-
The
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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.
|
|
52
54
|
|
|
53
55
|
After developer signup, create an end user:
|
|
54
56
|
|
|
@@ -107,12 +109,27 @@ shopstack checkout run --file checkout.json
|
|
|
107
109
|
Or create with MCP, then call `poll_checkout`. Display these authoritative fields to the user when they change:
|
|
108
110
|
|
|
109
111
|
- `status`: one of exactly `queued`, `started`, `help_required`, `approval_required`, `submitting`, `complete`, `failed`, `cancelled`;
|
|
112
|
+
- `presentation_revision`: the monotonic revision for user-visible checkout state;
|
|
110
113
|
- `activity`: bounded present-tense display text;
|
|
111
114
|
- `intent.name`, `intent.phase`, and `intent.updated_at`: bounded mechanical action progress with no arguments or reasoning;
|
|
112
115
|
- `required_input`: a typed handoff such as protected payment-card input;
|
|
113
116
|
- `approval`: the exact amount-bound approval request when present.
|
|
114
117
|
|
|
115
|
-
|
|
118
|
+
The CLI and JavaScript client subscribe with a one-time checkout-scoped
|
|
119
|
+
WebSocket capability. Each valid notification means the presentation revision
|
|
120
|
+
changed; the client then fetches the canonical checkout before displaying or
|
|
121
|
+
acting on it. The notification is not checkout state truth and never grants
|
|
122
|
+
payment authority. The capability belongs in the WebSocket subprotocol, never
|
|
123
|
+
the URL, logs, model context, or messages. If WebSocket delivery is unavailable,
|
|
124
|
+
the client falls back to bounded HTTP waiting and then fixed polling.
|
|
125
|
+
|
|
126
|
+
MCP and manual integrations poll every two seconds for `queued`, `started`, and
|
|
127
|
+
`submitting`; every five seconds for `help_required` and `approval_required`;
|
|
128
|
+
stop at `complete`, `failed`, or `cancelled`.
|
|
129
|
+
|
|
130
|
+
The optional `live_view_url` is a private owner-viewer capability. Show it only
|
|
131
|
+
to the initiating user. Do not store it in browser storage, analytics, logs,
|
|
132
|
+
model context, or ordinary messages.
|
|
116
133
|
|
|
117
134
|
## Respond at typed boundaries
|
|
118
135
|
|
package/bin/shopstack
CHANGED
|
@@ -5,6 +5,8 @@ import { runCli } from "../src/cli.js";
|
|
|
5
5
|
try {
|
|
6
6
|
await runCli(process.argv.slice(2));
|
|
7
7
|
} catch (error) {
|
|
8
|
-
process.stderr.write(
|
|
8
|
+
process.stderr.write(
|
|
9
|
+
`${error instanceof Error ? error.message : String(error)}\n`,
|
|
10
|
+
);
|
|
9
11
|
process.exitCode = 1;
|
|
10
12
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shopstack",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Shopstack
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Production Shopstack SDK and CLI for agentic checkout.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -23,17 +23,41 @@
|
|
|
23
23
|
"shopstack": "bin/shopstack"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
|
+
"check": "npm run format:check && npm run test:coverage",
|
|
27
|
+
"format:check": "prettier --check .",
|
|
26
28
|
"shopstack": "node ./bin/shopstack",
|
|
27
|
-
"test": "node --test"
|
|
29
|
+
"test": "node --test test",
|
|
30
|
+
"test:coverage": "node --test --experimental-test-coverage test",
|
|
31
|
+
"prepublishOnly": "npm run check"
|
|
28
32
|
},
|
|
29
33
|
"engines": {
|
|
30
34
|
"node": ">=18"
|
|
31
35
|
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/jimbo132/shopstack-cli.git"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/jimbo132/shopstack-cli#readme",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/jimbo132/shopstack-cli/issues"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"sideEffects": false,
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"ws": "8.21.3"
|
|
50
|
+
},
|
|
32
51
|
"license": "MIT",
|
|
33
52
|
"keywords": [
|
|
34
53
|
"shopstack",
|
|
35
54
|
"cli",
|
|
36
55
|
"checkout",
|
|
37
|
-
"payments"
|
|
38
|
-
|
|
56
|
+
"payments",
|
|
57
|
+
"agentic-commerce",
|
|
58
|
+
"agents"
|
|
59
|
+
],
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"prettier": "3.6.2"
|
|
62
|
+
}
|
|
39
63
|
}
|
package/src/cli.js
CHANGED
|
@@ -4,25 +4,50 @@ import { createInterface } from "node:readline/promises";
|
|
|
4
4
|
import { ShopstackClient } from "./client.js";
|
|
5
5
|
import { ConfigStore } from "./config.js";
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const VERSION = "0.2.2";
|
|
8
|
+
|
|
9
|
+
const HELP = `Shopstack CLI ${VERSION}
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
shopstack <command> [options]
|
|
8
13
|
|
|
9
14
|
Account setup:
|
|
15
|
+
shopstack signup
|
|
16
|
+
# Create or resume an email-verified Personal or Developer profile.
|
|
10
17
|
shopstack signup user --email EMAIL
|
|
18
|
+
# Create a Personal profile without interactive account-type prompts.
|
|
11
19
|
shopstack signup developer --email EMAIL
|
|
20
|
+
# Create a Developer management profile without interactive prompts.
|
|
12
21
|
shopstack signup resume SIGNUP_ID
|
|
22
|
+
# Resume a known pending email-verification flow.
|
|
13
23
|
shopstack users create --external-id ID [--profile NAME]
|
|
24
|
+
# Create an independently scoped user from a Developer profile.
|
|
14
25
|
shopstack profiles list
|
|
26
|
+
# List local profiles without printing their credentials.
|
|
15
27
|
shopstack profiles use NAME
|
|
28
|
+
# Select the profile used by later commands.
|
|
16
29
|
|
|
17
30
|
Payment connection:
|
|
18
31
|
shopstack connect list
|
|
32
|
+
# Inspect payment connections for the active user profile.
|
|
19
33
|
shopstack connect link
|
|
34
|
+
# Connect Link Agentic Wallet in the trusted browser flow.
|
|
20
35
|
|
|
21
36
|
Checkout:
|
|
22
37
|
shopstack checkout create --file checkout.json
|
|
38
|
+
# Create a checkout and return control immediately.
|
|
23
39
|
shopstack checkout run --file checkout.json
|
|
40
|
+
# Create, monitor, and complete a checkout interactively.
|
|
24
41
|
shopstack checkout get CHECKOUT_ID
|
|
42
|
+
# Read the canonical current checkout state.
|
|
25
43
|
shopstack checkout cancel CHECKOUT_ID
|
|
44
|
+
# Cancel a checkout that has not reached a terminal state.
|
|
45
|
+
|
|
46
|
+
Options:
|
|
47
|
+
-h, --help
|
|
48
|
+
# Show this command reference.
|
|
49
|
+
-v, --version
|
|
50
|
+
# Print the installed Shopstack CLI version.
|
|
26
51
|
`;
|
|
27
52
|
|
|
28
53
|
function parseOptions(args, allowed) {
|
|
@@ -54,6 +79,11 @@ function required(options, name) {
|
|
|
54
79
|
return value;
|
|
55
80
|
}
|
|
56
81
|
|
|
82
|
+
function isAffirmative(value) {
|
|
83
|
+
const answer = String(value).trim().toLowerCase();
|
|
84
|
+
return answer === "y" || answer === "yes";
|
|
85
|
+
}
|
|
86
|
+
|
|
57
87
|
function writeJson(stream, value) {
|
|
58
88
|
stream.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
59
89
|
}
|
|
@@ -145,9 +175,72 @@ async function saveVerifiedSignup(result, profileName, configStore) {
|
|
|
145
175
|
);
|
|
146
176
|
}
|
|
147
177
|
|
|
178
|
+
function signupPersistence(configStore, profileName) {
|
|
179
|
+
return {
|
|
180
|
+
async loadPending(input) {
|
|
181
|
+
return typeof configStore.findPendingSignup === "function"
|
|
182
|
+
? configStore.findPendingSignup(input)
|
|
183
|
+
: undefined;
|
|
184
|
+
},
|
|
185
|
+
async savePending(state) {
|
|
186
|
+
await configStore.savePendingSignup({ ...state, profile: profileName });
|
|
187
|
+
},
|
|
188
|
+
async completePending(state, result) {
|
|
189
|
+
const pendingId = state.attemptId ?? state.signupId ?? state.id;
|
|
190
|
+
if (typeof configStore.completePendingSignup === "function") {
|
|
191
|
+
await configStore.completePendingSignup(pendingId, profileName, result);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
await saveVerifiedSignup(result, profileName, configStore);
|
|
195
|
+
await configStore.deletePendingSignup(pendingId);
|
|
196
|
+
},
|
|
197
|
+
async deletePending(state) {
|
|
198
|
+
await configStore.deletePendingSignup(
|
|
199
|
+
state.attemptId ?? state.signupId ?? state.id,
|
|
200
|
+
);
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function reportSignupProgress(progress, stream) {
|
|
206
|
+
switch (progress.state) {
|
|
207
|
+
case "email_sent":
|
|
208
|
+
stream.write("Verification email sent.\n");
|
|
209
|
+
return;
|
|
210
|
+
case "waiting":
|
|
211
|
+
stream.write("Waiting for verification...\n");
|
|
212
|
+
return;
|
|
213
|
+
case "verified":
|
|
214
|
+
stream.write("✓ Email verified\n");
|
|
215
|
+
return;
|
|
216
|
+
case "complete":
|
|
217
|
+
stream.write("✓ Shopstack account created\n");
|
|
218
|
+
stream.write("✓ Credentials saved securely\n");
|
|
219
|
+
return;
|
|
220
|
+
case "expired":
|
|
221
|
+
stream.write("Signup expired. Start again to receive a new email.\n");
|
|
222
|
+
return;
|
|
223
|
+
case "rate_limited":
|
|
224
|
+
stream.write(
|
|
225
|
+
"Signup is rate-limited. Retry after the indicated delay.\n",
|
|
226
|
+
);
|
|
227
|
+
return;
|
|
228
|
+
case "conflict":
|
|
229
|
+
stream.write("Signup retry state conflicted and was cleared.\n");
|
|
230
|
+
return;
|
|
231
|
+
case "retryable_failure":
|
|
232
|
+
stream.write(
|
|
233
|
+
"Signup paused after a retryable failure. Run signup again to resume.\n",
|
|
234
|
+
);
|
|
235
|
+
return;
|
|
236
|
+
default:
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
148
241
|
async function activeClient(dependencies, requiredKind = "user") {
|
|
149
242
|
const profile = await dependencies.configStore.activeProfile();
|
|
150
|
-
const environmentKey =
|
|
243
|
+
const environmentKey = dependencies.env.SHOPSTACK_API_KEY;
|
|
151
244
|
const apiKey = environmentKey || profile?.apiKey;
|
|
152
245
|
const keyType = environmentKey ? requiredKind : profile?.keyType;
|
|
153
246
|
if (!apiKey)
|
|
@@ -158,7 +251,7 @@ async function activeClient(dependencies, requiredKind = "user") {
|
|
|
158
251
|
return {
|
|
159
252
|
client: dependencies.clientFactory({
|
|
160
253
|
apiKey,
|
|
161
|
-
baseUrl:
|
|
254
|
+
baseUrl: dependencies.env.SHOPSTACK_API_URL,
|
|
162
255
|
}),
|
|
163
256
|
profile,
|
|
164
257
|
};
|
|
@@ -175,13 +268,23 @@ export async function runCli(args, supplied = {}) {
|
|
|
175
268
|
stderr: process.stderr,
|
|
176
269
|
stdin: process.stdin,
|
|
177
270
|
stdout: process.stdout,
|
|
271
|
+
env: process.env,
|
|
178
272
|
...supplied,
|
|
179
273
|
};
|
|
180
274
|
const [group, action, ...rest] = args;
|
|
181
|
-
if (
|
|
275
|
+
if (
|
|
276
|
+
group === undefined ||
|
|
277
|
+
group === "help" ||
|
|
278
|
+
group === "--help" ||
|
|
279
|
+
group === "-h"
|
|
280
|
+
) {
|
|
182
281
|
dependencies.stdout.write(HELP);
|
|
183
282
|
return;
|
|
184
283
|
}
|
|
284
|
+
if (group === "--version" || group === "-v") {
|
|
285
|
+
dependencies.stdout.write(`${VERSION}\n`);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
185
288
|
|
|
186
289
|
if (group === "signup") {
|
|
187
290
|
if (action === "resume") {
|
|
@@ -190,65 +293,130 @@ export async function runCli(args, supplied = {}) {
|
|
|
190
293
|
}
|
|
191
294
|
const pending = await dependencies.configStore.pendingSignup(rest[0]);
|
|
192
295
|
if (pending === undefined) {
|
|
193
|
-
throw new Error(
|
|
296
|
+
throw new Error(
|
|
297
|
+
"That signup is not present in the local profile store.",
|
|
298
|
+
);
|
|
194
299
|
}
|
|
195
300
|
const client = dependencies.clientFactory({
|
|
196
|
-
baseUrl:
|
|
301
|
+
baseUrl: dependencies.env.SHOPSTACK_API_URL,
|
|
302
|
+
});
|
|
303
|
+
const result = await client.signup({
|
|
304
|
+
accountType: pending.accountType,
|
|
305
|
+
email: pending.email,
|
|
306
|
+
onProgress: (progress) =>
|
|
307
|
+
reportSignupProgress(progress, dependencies.stderr),
|
|
308
|
+
persistence: signupPersistence(
|
|
309
|
+
dependencies.configStore,
|
|
310
|
+
pending.profile,
|
|
311
|
+
),
|
|
197
312
|
});
|
|
198
|
-
const result = await client.waitForAccountSignup(
|
|
199
|
-
pending.id,
|
|
200
|
-
pending.pollToken,
|
|
201
|
-
);
|
|
202
|
-
await saveVerifiedSignup(result, pending.profile, dependencies.configStore);
|
|
203
|
-
await dependencies.configStore.deletePendingSignup(pending.id);
|
|
204
313
|
writeJson(dependencies.stdout, {
|
|
205
314
|
...sanitizedAccountResult(result),
|
|
206
315
|
profile: pending.profile,
|
|
207
316
|
});
|
|
208
317
|
return;
|
|
209
318
|
}
|
|
210
|
-
if (action !== "user" && action !== "developer") {
|
|
319
|
+
if (action !== undefined && action !== "user" && action !== "developer") {
|
|
211
320
|
throw new Error(
|
|
212
321
|
"Use `shopstack signup user` or `shopstack signup developer`.",
|
|
213
322
|
);
|
|
214
323
|
}
|
|
324
|
+
const signupArgs = action === undefined ? [] : rest;
|
|
215
325
|
const { options, positional } = parseOptions(
|
|
216
|
-
|
|
326
|
+
signupArgs,
|
|
217
327
|
new Set(["email", "profile"]),
|
|
218
328
|
);
|
|
219
329
|
if (positional.length > 0) throw new Error("Unexpected signup argument.");
|
|
220
|
-
const
|
|
330
|
+
const pendingCandidates =
|
|
331
|
+
action === undefined &&
|
|
332
|
+
typeof dependencies.configStore.pendingSignups === "function"
|
|
333
|
+
? await dependencies.configStore.pendingSignups()
|
|
334
|
+
: [];
|
|
335
|
+
const resumable =
|
|
336
|
+
pendingCandidates.length === 1 ? pendingCandidates[0] : undefined;
|
|
337
|
+
let accountType;
|
|
338
|
+
let email;
|
|
339
|
+
if (resumable !== undefined) {
|
|
340
|
+
accountType = resumable.accountType;
|
|
341
|
+
email = resumable.email;
|
|
342
|
+
dependencies.stderr.write("Resuming pending signup.\n");
|
|
343
|
+
} else if (action === undefined) {
|
|
344
|
+
email = String(await visiblePrompt("Email: ", dependencies)).trim();
|
|
345
|
+
if (email.length === 0) throw new Error("Email is required.");
|
|
346
|
+
const selected = String(
|
|
347
|
+
await visiblePrompt(
|
|
348
|
+
"Account type (Personal / Developer): ",
|
|
349
|
+
dependencies,
|
|
350
|
+
),
|
|
351
|
+
)
|
|
352
|
+
.trim()
|
|
353
|
+
.toLowerCase();
|
|
354
|
+
if (selected === "personal" || selected === "user") {
|
|
355
|
+
accountType = "personal";
|
|
356
|
+
} else if (selected === "developer") {
|
|
357
|
+
accountType = "developer";
|
|
358
|
+
} else {
|
|
359
|
+
throw new Error("Account type must be Personal or Developer.");
|
|
360
|
+
}
|
|
361
|
+
} else {
|
|
362
|
+
accountType = action === "user" ? "personal" : "developer";
|
|
363
|
+
email = required(options, "email");
|
|
364
|
+
}
|
|
221
365
|
const client = dependencies.clientFactory({
|
|
222
|
-
baseUrl:
|
|
366
|
+
baseUrl: dependencies.env.SHOPSTACK_API_URL,
|
|
223
367
|
});
|
|
224
|
-
const accountType = action === "user" ? "personal" : "developer";
|
|
225
368
|
const profileName =
|
|
226
|
-
options.profile ??
|
|
227
|
-
|
|
369
|
+
options.profile ??
|
|
370
|
+
resumable?.profile ??
|
|
371
|
+
(accountType === "developer" ? "developer" : "default");
|
|
372
|
+
const result = await client.signup({
|
|
228
373
|
accountType,
|
|
229
374
|
email,
|
|
375
|
+
onProgress: (progress) =>
|
|
376
|
+
reportSignupProgress(progress, dependencies.stderr),
|
|
377
|
+
persistence: signupPersistence(dependencies.configStore, profileName),
|
|
230
378
|
});
|
|
231
|
-
await dependencies.configStore.savePendingSignup({
|
|
232
|
-
accountType: signup.account_type,
|
|
233
|
-
email: signup.email,
|
|
234
|
-
expiresAt: signup.expires_at,
|
|
235
|
-
id: signup.id,
|
|
236
|
-
pollToken: signup.poll_token,
|
|
237
|
-
profile: profileName,
|
|
238
|
-
});
|
|
239
|
-
dependencies.stderr.write(
|
|
240
|
-
`Verification email sent to ${signup.email}. Open the link before ${signup.expires_at}. If interrupted, resume signup ${signup.id}.\n`,
|
|
241
|
-
);
|
|
242
|
-
const result = await client.waitForAccountSignup(
|
|
243
|
-
signup.id,
|
|
244
|
-
signup.poll_token,
|
|
245
|
-
);
|
|
246
|
-
await saveVerifiedSignup(result, profileName, dependencies.configStore);
|
|
247
|
-
await dependencies.configStore.deletePendingSignup(signup.id);
|
|
248
379
|
writeJson(dependencies.stdout, {
|
|
249
380
|
...sanitizedAccountResult(result),
|
|
250
381
|
profile: profileName,
|
|
251
382
|
});
|
|
383
|
+
if (accountType === "developer") {
|
|
384
|
+
dependencies.stderr.write(
|
|
385
|
+
"The developer management credential cannot run user checkouts. Create a user profile with `shopstack users create --external-id ID`.\n",
|
|
386
|
+
);
|
|
387
|
+
if (
|
|
388
|
+
action === undefined &&
|
|
389
|
+
isAffirmative(
|
|
390
|
+
await visiblePrompt(
|
|
391
|
+
"Create the first developer-owned user now? (y/N): ",
|
|
392
|
+
dependencies,
|
|
393
|
+
),
|
|
394
|
+
)
|
|
395
|
+
) {
|
|
396
|
+
const externalId = String(
|
|
397
|
+
await visiblePrompt("User external ID: ", dependencies),
|
|
398
|
+
).trim();
|
|
399
|
+
if (externalId.length === 0) {
|
|
400
|
+
throw new Error("User external ID is required.");
|
|
401
|
+
}
|
|
402
|
+
const { client: developerClient, profile } = await activeClient(
|
|
403
|
+
dependencies,
|
|
404
|
+
"developer",
|
|
405
|
+
);
|
|
406
|
+
const user = await developerClient.createUser({ externalId });
|
|
407
|
+
await dependencies.configStore.saveProfile(
|
|
408
|
+
externalId,
|
|
409
|
+
{
|
|
410
|
+
accountId: profile.accountId,
|
|
411
|
+
apiKey: user.api_key,
|
|
412
|
+
keyType: "user",
|
|
413
|
+
userId: user.id,
|
|
414
|
+
},
|
|
415
|
+
{ activate: true },
|
|
416
|
+
);
|
|
417
|
+
writeJson(dependencies.stdout, sanitizedUserResult(user, externalId));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
252
420
|
return;
|
|
253
421
|
}
|
|
254
422
|
|
|
@@ -324,6 +492,11 @@ export async function runCli(args, supplied = {}) {
|
|
|
324
492
|
const { client } = await activeClient(dependencies, "user");
|
|
325
493
|
const request = await dependencies.readJsonFile(required(options, "file"));
|
|
326
494
|
const result = await client.runCheckout(request, {
|
|
495
|
+
onCreated: (checkout) => {
|
|
496
|
+
if (typeof checkout.live_view_url === "string") {
|
|
497
|
+
dependencies.stderr.write(`Live view: ${checkout.live_view_url}\n`);
|
|
498
|
+
}
|
|
499
|
+
},
|
|
327
500
|
onProgress: (checkout) => {
|
|
328
501
|
const intent =
|
|
329
502
|
checkout.intent?.name === undefined
|
|
@@ -383,4 +556,4 @@ export async function runCli(args, supplied = {}) {
|
|
|
383
556
|
throw new Error("Unknown command. Run `shopstack help`.");
|
|
384
557
|
}
|
|
385
558
|
|
|
386
|
-
export { HELP };
|
|
559
|
+
export { HELP, VERSION };
|