@tenderprompt/cli 0.1.29 → 0.1.31
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 +1 -0
- package/dist/index.js +146 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -60,6 +60,7 @@ tender --version
|
|
|
60
60
|
tender capabilities --json
|
|
61
61
|
|
|
62
62
|
tender auth login --device --profile edit-preview --artifact artifact_123 --ttl 7d
|
|
63
|
+
tender auth login --device --profile create --shopify-store tender-prompt --json
|
|
63
64
|
|
|
64
65
|
tender app init artifact_123 --dir ./widget --preview --json
|
|
65
66
|
|
package/dist/index.js
CHANGED
|
@@ -295,18 +295,21 @@ Commands:
|
|
|
295
295
|
Examples:
|
|
296
296
|
tender auth login --device --profile edit-preview --artifact artifact_123 --ttl 7d
|
|
297
297
|
tender auth login --device --profile publish --artifact artifact_123 --ttl 7d --save-profile publish
|
|
298
|
+
tender auth login --device --profile create --shopify-store tender-prompt --json
|
|
298
299
|
tender auth status --json`;
|
|
299
300
|
}
|
|
300
301
|
function authLoginHelp() {
|
|
301
|
-
return `Usage: tender auth login --device [--base-url <url>] [--profile <edit-preview|publish|create|db-read>] [--artifact <artifact-id>] [--ttl <7d|14d|30d>] [--save-profile <name>] [--no-poll] [--json]
|
|
302
|
+
return `Usage: tender auth login --device [--base-url <url>] [--shopify-store <store>] [--shopify-app-url <url>] [--profile <edit-preview|publish|create|db-read>] [--artifact <artifact-id>] [--ttl <7d|14d|30d>] [--save-profile <name>] [--no-poll] [--json]
|
|
302
303
|
|
|
303
304
|
Options:
|
|
304
305
|
--device Use the OAuth device-code flow.
|
|
305
306
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL or https://app.tenderprompt.com.
|
|
307
|
+
--shopify-store <store> Approve the device code in Shopify Admin for this store handle.
|
|
308
|
+
--shopify-app-url <url> Approve the device code through a Tender Prompt Shopify App Home URL.
|
|
306
309
|
--profile <value> Token permission profile: edit-preview, publish, create, or db-read. Defaults to edit-preview.
|
|
307
310
|
--artifact <artifact-id> Scope the token to one artifact. Required for normal edit-preview agent work.
|
|
308
311
|
--ttl <value> Token lifetime: 7d, 14d, or 30d. Defaults to 7d.
|
|
309
|
-
--save-profile <name> Local config profile name. Defaults to default.
|
|
312
|
+
--save-profile <name> Local config profile name. Defaults to default, or shopify-<store> with Shopify approval.
|
|
310
313
|
--no-poll Print the verification URL and exit without waiting for approval.
|
|
311
314
|
--json Emit stable JSON for coding agents.
|
|
312
315
|
|
|
@@ -314,6 +317,7 @@ Examples:
|
|
|
314
317
|
tender auth login --device --profile edit-preview --artifact artifact_123 --ttl 7d
|
|
315
318
|
tender auth login --device --profile publish --artifact artifact_123 --ttl 7d --save-profile publish --json
|
|
316
319
|
tender auth login --device --profile create --ttl 7d --save-profile account
|
|
320
|
+
tender auth login --device --profile create --shopify-store tender-prompt --json
|
|
317
321
|
tender auth login --device --profile db-read --artifact artifact_123 --ttl 7d --save-profile db`;
|
|
318
322
|
}
|
|
319
323
|
function authStatusHelp() {
|
|
@@ -2033,8 +2037,38 @@ function normalizeStoredProfile(value) {
|
|
|
2033
2037
|
permissions: Array.isArray(record.permissions)
|
|
2034
2038
|
? record.permissions.filter((permission) => typeof permission === 'string')
|
|
2035
2039
|
: undefined,
|
|
2040
|
+
...normalizeStoredShopifyContext(record),
|
|
2036
2041
|
};
|
|
2037
2042
|
}
|
|
2043
|
+
function normalizeStoredShopifyContext(record) {
|
|
2044
|
+
const shopifyAppUrl = typeof record.shopifyAppUrl === 'string' ? record.shopifyAppUrl : null;
|
|
2045
|
+
if (shopifyAppUrl) {
|
|
2046
|
+
try {
|
|
2047
|
+
const parsed = parseShopifyAppUrl(shopifyAppUrl, 'profile.shopifyAppUrl');
|
|
2048
|
+
return {
|
|
2049
|
+
shopifyAppUrl: parsed.appUrl,
|
|
2050
|
+
shopifyStore: parsed.store,
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
catch {
|
|
2054
|
+
return {};
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
const shopifyStore = typeof record.shopifyStore === 'string' ? record.shopifyStore : null;
|
|
2058
|
+
if (shopifyStore) {
|
|
2059
|
+
try {
|
|
2060
|
+
const store = parseShopifyStoreHandle(shopifyStore, 'profile.shopifyStore');
|
|
2061
|
+
return {
|
|
2062
|
+
shopifyAppUrl: shopifyAppUrlForStore(store),
|
|
2063
|
+
shopifyStore: store,
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
catch {
|
|
2067
|
+
return {};
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
return {};
|
|
2071
|
+
}
|
|
2038
2072
|
function normalizeConfig(value) {
|
|
2039
2073
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
2040
2074
|
return {};
|
|
@@ -2131,6 +2165,60 @@ function parseBaseUrlFlag(args, index) {
|
|
|
2131
2165
|
}
|
|
2132
2166
|
return normalizeBaseUrl(value);
|
|
2133
2167
|
}
|
|
2168
|
+
function parseShopifyStoreHandle(value, flag) {
|
|
2169
|
+
if (!value || value.startsWith('-')) {
|
|
2170
|
+
throw new TenderCliUsageError(`${flag} requires a Shopify Admin store handle.`);
|
|
2171
|
+
}
|
|
2172
|
+
const normalized = value.trim().toLowerCase();
|
|
2173
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(normalized)) {
|
|
2174
|
+
throw new TenderCliUsageError(`${flag} must be a Shopify Admin store handle, such as tender-prompt.`);
|
|
2175
|
+
}
|
|
2176
|
+
return normalized;
|
|
2177
|
+
}
|
|
2178
|
+
function shopifyAppUrlForStore(store) {
|
|
2179
|
+
return `https://admin.shopify.com/store/${store}/apps/tender-prompt/shopify`;
|
|
2180
|
+
}
|
|
2181
|
+
function parseShopifyAppUrl(value, flag) {
|
|
2182
|
+
if (!value || value.startsWith('-')) {
|
|
2183
|
+
throw new TenderCliUsageError(`${flag} requires a Shopify Admin App Home URL.`);
|
|
2184
|
+
}
|
|
2185
|
+
let url;
|
|
2186
|
+
try {
|
|
2187
|
+
url = new URL(value);
|
|
2188
|
+
}
|
|
2189
|
+
catch {
|
|
2190
|
+
throw new TenderCliUsageError(`${flag} requires a valid Shopify Admin App Home URL.`);
|
|
2191
|
+
}
|
|
2192
|
+
if (url.protocol !== 'https:' || url.hostname !== 'admin.shopify.com') {
|
|
2193
|
+
throw new TenderCliUsageError(`${flag} must start with https://admin.shopify.com/.`);
|
|
2194
|
+
}
|
|
2195
|
+
const parts = url.pathname.split('/').filter(Boolean);
|
|
2196
|
+
const deviceSuffix = parts[5] === 'device' ? parts[5] : null;
|
|
2197
|
+
if ((parts.length !== 5 && !(parts.length === 6 && deviceSuffix)) ||
|
|
2198
|
+
parts[0] !== 'store' ||
|
|
2199
|
+
parts[2] !== 'apps' ||
|
|
2200
|
+
parts[4] !== 'shopify') {
|
|
2201
|
+
throw new TenderCliUsageError(`${flag} must look like https://admin.shopify.com/store/<store>/apps/tender-prompt/shopify.`);
|
|
2202
|
+
}
|
|
2203
|
+
if (url.search || url.hash) {
|
|
2204
|
+
throw new TenderCliUsageError(`${flag} should not include a query string or hash.`);
|
|
2205
|
+
}
|
|
2206
|
+
const store = parseShopifyStoreHandle(parts[1], flag);
|
|
2207
|
+
const appHandle = parseShopifyStoreHandle(parts[3], flag);
|
|
2208
|
+
return {
|
|
2209
|
+
store,
|
|
2210
|
+
appUrl: `https://admin.shopify.com/store/${store}/apps/${appHandle}/shopify`,
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
function parseShopifyAppUrlFlag(args, index) {
|
|
2214
|
+
return parseShopifyAppUrl(args[index + 1], '--shopify-app-url');
|
|
2215
|
+
}
|
|
2216
|
+
function shopifyDeviceVerificationUri(shopifyAppUrl) {
|
|
2217
|
+
return `${shopifyAppUrl.replace(/\/+$/, '')}/device`;
|
|
2218
|
+
}
|
|
2219
|
+
function shopifyDeviceVerificationUrl(input) {
|
|
2220
|
+
return `${shopifyDeviceVerificationUri(input.shopifyAppUrl)}?user_code=${encodeURIComponent(input.userCode)}`;
|
|
2221
|
+
}
|
|
2134
2222
|
function parseTokenFlag(args, index) {
|
|
2135
2223
|
const value = args[index + 1];
|
|
2136
2224
|
if (!value || value.startsWith('-')) {
|
|
@@ -4397,8 +4485,11 @@ function parseAuthLoginArgs(args) {
|
|
|
4397
4485
|
}
|
|
4398
4486
|
let device = false;
|
|
4399
4487
|
let baseUrl = null;
|
|
4488
|
+
let shopifyAppUrl = null;
|
|
4489
|
+
let shopifyStore = null;
|
|
4400
4490
|
let tokenProfile = 'edit-preview';
|
|
4401
4491
|
let saveProfile = DEFAULT_PROFILE_NAME;
|
|
4492
|
+
let saveProfileExplicit = false;
|
|
4402
4493
|
let artifactId = null;
|
|
4403
4494
|
let ttl = '7d';
|
|
4404
4495
|
let json = false;
|
|
@@ -4423,6 +4514,25 @@ function parseAuthLoginArgs(args) {
|
|
|
4423
4514
|
index += 1;
|
|
4424
4515
|
continue;
|
|
4425
4516
|
}
|
|
4517
|
+
if (arg === '--shopify-store') {
|
|
4518
|
+
if (shopifyAppUrl) {
|
|
4519
|
+
throw new TenderCliUsageError('--shopify-store cannot be combined with --shopify-app-url.');
|
|
4520
|
+
}
|
|
4521
|
+
shopifyStore = parseShopifyStoreHandle(args[index + 1], '--shopify-store');
|
|
4522
|
+
shopifyAppUrl = shopifyAppUrlForStore(shopifyStore);
|
|
4523
|
+
index += 1;
|
|
4524
|
+
continue;
|
|
4525
|
+
}
|
|
4526
|
+
if (arg === '--shopify-app-url') {
|
|
4527
|
+
if (shopifyAppUrl) {
|
|
4528
|
+
throw new TenderCliUsageError('--shopify-app-url cannot be combined with --shopify-store.');
|
|
4529
|
+
}
|
|
4530
|
+
const parsed = parseShopifyAppUrlFlag(args, index);
|
|
4531
|
+
shopifyStore = parsed.store;
|
|
4532
|
+
shopifyAppUrl = parsed.appUrl;
|
|
4533
|
+
index += 1;
|
|
4534
|
+
continue;
|
|
4535
|
+
}
|
|
4426
4536
|
if (arg === '--profile') {
|
|
4427
4537
|
tokenProfile = parseTokenProfile(args[index + 1]);
|
|
4428
4538
|
index += 1;
|
|
@@ -4430,6 +4540,7 @@ function parseAuthLoginArgs(args) {
|
|
|
4430
4540
|
}
|
|
4431
4541
|
if (arg === '--save-profile') {
|
|
4432
4542
|
saveProfile = parseProfileName(args[index + 1], '--save-profile');
|
|
4543
|
+
saveProfileExplicit = true;
|
|
4433
4544
|
index += 1;
|
|
4434
4545
|
continue;
|
|
4435
4546
|
}
|
|
@@ -4459,9 +4570,14 @@ function parseAuthLoginArgs(args) {
|
|
|
4459
4570
|
if (tokenProfile === 'db-read' && !artifactId) {
|
|
4460
4571
|
throw new TenderCliUsageError('--profile db-read requires --artifact <artifact-id>.');
|
|
4461
4572
|
}
|
|
4573
|
+
if (shopifyStore && !saveProfileExplicit) {
|
|
4574
|
+
saveProfile = `shopify-${shopifyStore}`;
|
|
4575
|
+
}
|
|
4462
4576
|
return {
|
|
4463
4577
|
command: 'auth login',
|
|
4464
4578
|
baseUrl,
|
|
4579
|
+
shopifyAppUrl,
|
|
4580
|
+
shopifyStore,
|
|
4465
4581
|
device,
|
|
4466
4582
|
tokenProfile,
|
|
4467
4583
|
saveProfile,
|
|
@@ -8145,6 +8261,8 @@ async function runAuthStatus(command, io, runtime) {
|
|
|
8145
8261
|
artifactId: profile?.artifactId ?? null,
|
|
8146
8262
|
permissions: profile?.permissions ?? [],
|
|
8147
8263
|
expiresAt: formatExpiry(profile?.expiresAt),
|
|
8264
|
+
shopifyAppUrl: profile?.shopifyAppUrl ?? null,
|
|
8265
|
+
shopifyStore: profile?.shopifyStore ?? null,
|
|
8148
8266
|
};
|
|
8149
8267
|
if (command.json) {
|
|
8150
8268
|
io.stdout(JSON.stringify(result, null, 2));
|
|
@@ -8159,6 +8277,7 @@ async function runAuthStatus(command, io, runtime) {
|
|
|
8159
8277
|
`app_id: ${profile.artifactId ?? '(account)'}`,
|
|
8160
8278
|
`permissions: ${(profile.permissions ?? []).join(' ') || '(unknown)'}`,
|
|
8161
8279
|
`expires_at: ${formatExpiry(profile.expiresAt) ?? '(unknown)'}`,
|
|
8280
|
+
`shopify_app_url: ${profile.shopifyAppUrl ?? '(none)'}`,
|
|
8162
8281
|
].join('\n'));
|
|
8163
8282
|
}
|
|
8164
8283
|
else {
|
|
@@ -8203,6 +8322,8 @@ async function runAuthStatus(command, io, runtime) {
|
|
|
8203
8322
|
artifactId: profile?.artifactId ?? null,
|
|
8204
8323
|
permissions: profile?.permissions ?? [],
|
|
8205
8324
|
expiresAt: formatExpiry(profile?.expiresAt),
|
|
8325
|
+
shopifyAppUrl: profile?.shopifyAppUrl ?? null,
|
|
8326
|
+
shopifyStore: profile?.shopifyStore ?? null,
|
|
8206
8327
|
};
|
|
8207
8328
|
if (command.json) {
|
|
8208
8329
|
io.stdout(JSON.stringify(result, null, 2));
|
|
@@ -8217,6 +8338,7 @@ async function runAuthStatus(command, io, runtime) {
|
|
|
8217
8338
|
`app_id: ${profile.artifactId ?? '(account)'}`,
|
|
8218
8339
|
`permissions: ${(profile.permissions ?? []).join(' ') || '(unknown)'}`,
|
|
8219
8340
|
`expires_at: ${formatExpiry(profile.expiresAt) ?? '(unknown)'}`,
|
|
8341
|
+
`shopify_app_url: ${profile.shopifyAppUrl ?? '(none)'}`,
|
|
8220
8342
|
].join('\n'));
|
|
8221
8343
|
}
|
|
8222
8344
|
else {
|
|
@@ -8233,7 +8355,11 @@ async function runAuthLogin(command, io, runtime) {
|
|
|
8233
8355
|
const fetcher = runtime.fetcher ?? fetch;
|
|
8234
8356
|
const sleeper = runtime.sleeper ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
8235
8357
|
const now = runtime.now ?? Date.now;
|
|
8236
|
-
const
|
|
8358
|
+
const config = await readConfig(env);
|
|
8359
|
+
const existingProfile = config.profiles?.[command.saveProfile] ?? null;
|
|
8360
|
+
const baseUrl = command.baseUrl ?? existingProfile?.baseUrl ?? readDefaultBaseUrl(env);
|
|
8361
|
+
const shopifyAppUrl = command.shopifyAppUrl ?? existingProfile?.shopifyAppUrl ?? null;
|
|
8362
|
+
const shopifyStore = command.shopifyStore ?? existingProfile?.shopifyStore ?? null;
|
|
8237
8363
|
const deviceResponse = await fetcher(`${baseUrl}/oauth/device/code`, {
|
|
8238
8364
|
method: 'POST',
|
|
8239
8365
|
headers: {
|
|
@@ -8249,15 +8375,24 @@ async function runAuthLogin(command, io, runtime) {
|
|
|
8249
8375
|
if (!deviceResponse.ok || !devicePayload?.device_code || !devicePayload.user_code || !devicePayload.verification_uri_complete) {
|
|
8250
8376
|
throw new Error(devicePayload?.error_description ?? devicePayload?.error ?? `Device authorization failed with HTTP ${deviceResponse.status}.`);
|
|
8251
8377
|
}
|
|
8378
|
+
const verificationUriComplete = shopifyAppUrl
|
|
8379
|
+
? shopifyDeviceVerificationUrl({
|
|
8380
|
+
shopifyAppUrl,
|
|
8381
|
+
userCode: devicePayload.user_code,
|
|
8382
|
+
})
|
|
8383
|
+
: devicePayload.verification_uri_complete;
|
|
8384
|
+
const verificationUri = shopifyAppUrl ? shopifyDeviceVerificationUri(shopifyAppUrl) : devicePayload.verification_uri;
|
|
8252
8385
|
const pendingResult = {
|
|
8253
8386
|
ok: true,
|
|
8254
8387
|
command: command.command,
|
|
8255
8388
|
baseUrl,
|
|
8389
|
+
shopifyAppUrl,
|
|
8390
|
+
shopifyStore,
|
|
8256
8391
|
profile: command.tokenProfile,
|
|
8257
8392
|
saveProfile: command.saveProfile,
|
|
8258
8393
|
userCode: devicePayload.user_code,
|
|
8259
|
-
verificationUri
|
|
8260
|
-
verificationUriComplete
|
|
8394
|
+
verificationUri,
|
|
8395
|
+
verificationUriComplete,
|
|
8261
8396
|
expiresIn: devicePayload.expires_in ?? null,
|
|
8262
8397
|
interval: devicePayload.interval ?? null,
|
|
8263
8398
|
};
|
|
@@ -8266,13 +8401,13 @@ async function runAuthLogin(command, io, runtime) {
|
|
|
8266
8401
|
io.stdout(JSON.stringify(pendingResult, null, 2));
|
|
8267
8402
|
}
|
|
8268
8403
|
else {
|
|
8269
|
-
io.stdout([`verification_url: ${
|
|
8404
|
+
io.stdout([`verification_url: ${verificationUriComplete}`, `user_code: ${devicePayload.user_code}`].join('\n'));
|
|
8270
8405
|
}
|
|
8271
8406
|
return 0;
|
|
8272
8407
|
}
|
|
8273
8408
|
if (!command.json) {
|
|
8274
8409
|
io.stdout([
|
|
8275
|
-
`Open this URL to approve Tender CLI access: ${
|
|
8410
|
+
`Open this URL to approve Tender CLI access: ${verificationUriComplete}`,
|
|
8276
8411
|
`User code: ${devicePayload.user_code}`,
|
|
8277
8412
|
].join('\n'));
|
|
8278
8413
|
}
|
|
@@ -8293,7 +8428,6 @@ async function runAuthLogin(command, io, runtime) {
|
|
|
8293
8428
|
});
|
|
8294
8429
|
const tokenPayload = await parseJsonResponse(tokenResponse);
|
|
8295
8430
|
if (tokenResponse.ok && tokenPayload?.access_token) {
|
|
8296
|
-
const config = await readConfig(env);
|
|
8297
8431
|
const profiles = config.profiles ?? {};
|
|
8298
8432
|
profiles[command.saveProfile] = {
|
|
8299
8433
|
baseUrl,
|
|
@@ -8303,6 +8437,8 @@ async function runAuthLogin(command, io, runtime) {
|
|
|
8303
8437
|
artifactId: tokenPayload.artifact_id,
|
|
8304
8438
|
expiresAt: tokenPayload.expires_at,
|
|
8305
8439
|
permissions: splitScope(tokenPayload.scope),
|
|
8440
|
+
...(shopifyAppUrl ? { shopifyAppUrl } : {}),
|
|
8441
|
+
...(shopifyStore ? { shopifyStore } : {}),
|
|
8306
8442
|
};
|
|
8307
8443
|
await writeConfig(env, {
|
|
8308
8444
|
...config,
|
|
@@ -8320,6 +8456,8 @@ async function runAuthLogin(command, io, runtime) {
|
|
|
8320
8456
|
artifactId: tokenPayload.artifact_id ?? null,
|
|
8321
8457
|
permissions: splitScope(tokenPayload.scope),
|
|
8322
8458
|
expiresAt: formatExpiry(tokenPayload.expires_at),
|
|
8459
|
+
shopifyAppUrl,
|
|
8460
|
+
shopifyStore,
|
|
8323
8461
|
configPath: readConfigPath(env),
|
|
8324
8462
|
};
|
|
8325
8463
|
if (command.json) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenderprompt/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"tender": "bin/tender.js"
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"prepack": "yarn build"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@tenderprompt/tender-app-validator": "0.1.
|
|
26
|
+
"@tenderprompt/tender-app-validator": "0.1.5"
|
|
27
27
|
},
|
|
28
28
|
"engines": {
|
|
29
29
|
"node": ">=20"
|