@tenderprompt/cli 0.1.26 → 0.1.28

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.
Files changed (2) hide show
  1. package/dist/index.js +2966 -916
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -146,6 +146,16 @@ Commands:
146
146
  tender playbooks get <id> Fetch one remote playbook body on demand
147
147
  tender playbooks file <id> --path <path>
148
148
  Fetch one playbook reference file on demand
149
+ tender connectors list List account-level connector instances
150
+ tender connectors shopify create
151
+ Create a merchant-owned Shopify connector
152
+ tender connectors shopify credentials set
153
+ Store Shopify custom app credentials from stdin or env
154
+ tender connectors shopify app-automation-token set
155
+ Store Shopify CLI deployment token from stdin or env
156
+ tender connectors shopify verify
157
+ Exchange credentials for an Admin API access token
158
+ tender connectors bind Print an app.json binding for a connector instance
149
159
  tender app list List Tender App records visible to the current token
150
160
  tender app create --name <name>
151
161
  Create a new Tender App in the current account
@@ -217,6 +227,12 @@ Examples:
217
227
  tender playbooks list --json
218
228
  tender playbooks get recharge-bundle-builder --json
219
229
  tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json
230
+ tender connectors list --profile account --json
231
+ tender connectors shopify create --instance default --admin-api-version 2026-04 --shop demo.myshopify.com --profile account --json
232
+ printf "$SHOPIFY_CLIENT_SECRET" | tender connectors shopify credentials set conn_123 --shop demo.myshopify.com --client-id abc --client-secret-stdin --profile account --json
233
+ printf "$SHOPIFY_APP_AUTOMATION_TOKEN" | tender connectors shopify app-automation-token set conn_123 --token-stdin --expires-at 2026-08-22 --profile account --json
234
+ tender connectors shopify verify conn_123 --profile account --json
235
+ tender connectors bind SHOPIFY_CONNECTOR conn_123 --instance default --json
220
236
  tender app list --json
221
237
  tender app create --name "Exit Intent Widget" --json
222
238
  tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
@@ -380,6 +396,159 @@ This command does not write playbooks to disk.
380
396
  Examples:
381
397
  tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json`;
382
398
  }
399
+ function connectorsHelp() {
400
+ return `Usage: tender connectors <command> [options]
401
+
402
+ Commands:
403
+ tender connectors list
404
+ List account-level connector instances
405
+ tender connectors shopify create
406
+ Create a Shopify connector and setup URL
407
+ tender connectors shopify setup-url <connector-id>
408
+ Create a fresh credential setup URL
409
+ tender connectors shopify credentials set <connector-id>
410
+ Store custom app credentials from stdin or env
411
+ tender connectors shopify app-automation-token set <connector-id>
412
+ Store Shopify CLI deployment token from stdin or env
413
+ tender connectors shopify app-automation-token delete <connector-id>
414
+ Remove the stored Shopify CLI deployment token
415
+ tender connectors shopify verify <connector-id>
416
+ Verify credentials with Shopify
417
+ tender connectors shopify source init <connector-id>
418
+ Create/ensure internal Artifact Git Shopify app source
419
+ tender connectors shopify source checkout <connector-id>
420
+ Checkout the connector source artifact
421
+ tender connectors shopify source validate <connector-id>
422
+ Validate connector source with Shopify CLI
423
+ tender connectors shopify source deploy <connector-id>
424
+ Deploy connector source with Shopify CLI
425
+ tender connectors shopify delete <connector-id>
426
+ Disable and remove stored credentials
427
+ tender connectors bind <binding-name> <connector-id>
428
+ Print an app.json binding for the connector instance
429
+
430
+ Examples:
431
+ tender connectors list --profile account --json
432
+ tender connectors shopify create --instance default --admin-api-version 2026-04 --shop demo.myshopify.com --profile account --json
433
+ printf "$SHOPIFY_CLIENT_SECRET" | tender connectors shopify credentials set conn_123 --shop demo.myshopify.com --client-id abc --client-secret-stdin --profile account --json
434
+ printf "$SHOPIFY_APP_AUTOMATION_TOKEN" | tender connectors shopify app-automation-token set conn_123 --token-stdin --expires-at 2026-08-22 --profile account --json
435
+ tender connectors shopify verify conn_123 --profile account --json
436
+ tender connectors shopify source init conn_123 --dir ./shopify-app --client-id abc --tender-app-url https://example.tenderprompt.app/client.js --tender-element tender-widget --profile account --json
437
+ tender connectors shopify source validate conn_123 --dir ./shopify-app --execute --json
438
+ SHOPIFY_APP_AUTOMATION_TOKEN=shpat_... tender connectors shopify source deploy conn_123 --dir ./shopify-app --confirm deploy --json
439
+ tender connectors bind SHOPIFY_CONNECTOR conn_123 --instance default --json`;
440
+ }
441
+ function connectorsShopifyCreateHelp() {
442
+ return `Usage: tender connectors shopify create [--instance <id>] [--label <text>] [--app-name <text>] [--admin-api-version <version>] [--shop <myshopify-domain>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
443
+
444
+ Creates a merchant-owned Shopify connector record and returns a setup URL for
445
+ secure credential capture.
446
+
447
+ Examples:
448
+ tender connectors shopify create --instance default --admin-api-version 2026-04 --shop demo.myshopify.com --profile account --json`;
449
+ }
450
+ function connectorsShopifySetupUrlHelp() {
451
+ return `Usage: tender connectors shopify setup-url <connector-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
452
+
453
+ Creates a fresh one-time credential setup URL for an existing connector.
454
+
455
+ Examples:
456
+ tender connectors shopify setup-url conn_123 --profile account --json`;
457
+ }
458
+ function connectorsShopifyCredentialsSetHelp() {
459
+ return `Usage: tender connectors shopify credentials set <connector-id> --shop <myshopify-domain> --client-id <id> (--client-secret-stdin | --client-secret-env <name>) [--base-url <url>] [--token <token>] [--profile <name>] [--json]
460
+
461
+ Stores credentials without printing the client secret. Prefer stdin or an env
462
+ var so shell history does not capture the secret.
463
+
464
+ Examples:
465
+ printf "$SHOPIFY_CLIENT_SECRET" | tender connectors shopify credentials set conn_123 --shop demo.myshopify.com --client-id abc --client-secret-stdin --profile account --json
466
+ SHOPIFY_CLIENT_SECRET=shpss_123 tender connectors shopify credentials set conn_123 --shop demo.myshopify.com --client-id abc --client-secret-env SHOPIFY_CLIENT_SECRET --profile account --json`;
467
+ }
468
+ function connectorsShopifyAppAutomationTokenSetHelp() {
469
+ return `Usage: tender connectors shopify app-automation-token set <connector-id> (--token-stdin | --token-env <name>) [--expires-at <date-or-iso>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
470
+
471
+ Stores a Shopify App Automation Token for non-interactive Shopify CLI deploys.
472
+ This token is separate from the connector's Admin API runtime credentials.
473
+
474
+ Examples:
475
+ printf "$SHOPIFY_APP_AUTOMATION_TOKEN" | tender connectors shopify app-automation-token set conn_123 --token-stdin --expires-at 2026-08-22 --profile account --json
476
+ tender connectors shopify app-automation-token set conn_123 --token-env SHOPIFY_APP_AUTOMATION_TOKEN --expires-at 2026-08-22 --profile account --json`;
477
+ }
478
+ function connectorsShopifyAppAutomationTokenDeleteHelp() {
479
+ return `Usage: tender connectors shopify app-automation-token delete <connector-id> --confirm delete [--base-url <url>] [--token <token>] [--profile <name>] [--json]
480
+
481
+ Removes the stored Shopify App Automation Token metadata and secret.
482
+
483
+ Examples:
484
+ tender connectors shopify app-automation-token delete conn_123 --confirm delete --profile account --json`;
485
+ }
486
+ function connectorsShopifyVerifyHelp() {
487
+ return `Usage: tender connectors shopify verify <connector-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
488
+
489
+ Exchanges the stored client ID and secret through Shopify's client credentials
490
+ grant and stores the returned Admin API access token.
491
+
492
+ Examples:
493
+ tender connectors shopify verify conn_123 --profile account --json`;
494
+ }
495
+ function connectorsShopifySourceInitHelp() {
496
+ return `Usage: tender connectors shopify source init <connector-id> --dir <path> --client-id <shopify-client-id> --tender-app-url <client.js-url> --tender-element <custom-element> [--source-name <name>] [--artifact-id <id>] [--public-id <id>] [--theme-extension-handle <handle>] [--theme-extension-uid <uid>] [--block-name <name>] [--scopes <csv>] [--force] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
497
+
498
+ Creates or reuses an internal Tender source artifact for the merchant-owned
499
+ Shopify app, checks it out to --dir, writes non-secret Shopify app source,
500
+ commits it, pushes it to Artifact Git, and records sourceArtifactId on the
501
+ connector. The generated source includes shopify.app.toml, dog-profile app-owned
502
+ custom data definitions, and a Theme App Extension app-block template.
503
+
504
+ Examples:
505
+ tender connectors shopify source init conn_123 --dir ./shopify-app --client-id abc --tender-app-url https://example.tenderprompt.app/client.js --tender-element tender-widget --profile account --json`;
506
+ }
507
+ function connectorsShopifySourceCheckoutHelp() {
508
+ return `Usage: tender connectors shopify source checkout <connector-id> --dir <path> [--remote <name>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
509
+
510
+ Checks out the connector's internal Shopify source artifact and configures the
511
+ Tender Artifact Git remote.
512
+
513
+ Examples:
514
+ tender connectors shopify source checkout conn_123 --dir ./shopify-app --profile account --json`;
515
+ }
516
+ function connectorsShopifySourceValidateHelp() {
517
+ return `Usage: tender connectors shopify source validate <connector-id> [--dir <path>] [--client-id <shopify-client-id>] [--execute] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
518
+
519
+ Without --execute, prints the exact Shopify CLI validation command. With
520
+ --execute, runs npx --yes @shopify/cli@latest app config validate.
521
+
522
+ Examples:
523
+ tender connectors shopify source validate conn_123 --dir ./shopify-app --execute --json`;
524
+ }
525
+ function connectorsShopifySourceDeployHelp() {
526
+ return `Usage: tender connectors shopify source deploy <connector-id> [--dir <path>] [--client-id <shopify-client-id>] [--version <tag>] [--message <text>] [--source-control-url <url>] [--allow-deletes] [--confirm deploy] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
527
+
528
+ Without --confirm deploy, prints the exact Shopify CLI deployment command. With
529
+ --confirm deploy, runs npx --yes @shopify/cli@latest app deploy. Set
530
+ SHOPIFY_APP_AUTOMATION_TOKEN in the environment for non-interactive deploys.
531
+
532
+ Examples:
533
+ SHOPIFY_APP_AUTOMATION_TOKEN=shpat_... tender connectors shopify source deploy conn_123 --dir ./shopify-app --version dog-profile-smoke --confirm deploy --json`;
534
+ }
535
+ function connectorsShopifyDeleteHelp() {
536
+ return `Usage: tender connectors shopify delete <connector-id> --confirm delete [--base-url <url>] [--token <token>] [--profile <name>] [--json]
537
+
538
+ Disables a connector and removes its stored credentials.
539
+
540
+ Examples:
541
+ tender connectors shopify delete conn_123 --confirm delete --profile account --json`;
542
+ }
543
+ function connectorsBindHelp() {
544
+ return `Usage: tender connectors bind <binding-name> <connector-id> --instance <id> [--label <text>] [--json]
545
+
546
+ Prints the app.json binding snippet a Tender App can opt into. The app receives
547
+ the binding at runtime as env.<binding-name>.
548
+
549
+ Examples:
550
+ tender connectors bind SHOPIFY_CONNECTOR conn_123 --instance default --json`;
551
+ }
383
552
  function appHelp() {
384
553
  return `Usage: tender app <command> [options]
385
554
 
@@ -1307,6 +1476,47 @@ function tenderCliCapabilities() {
1307
1476
  persistence: 'session_only',
1308
1477
  writesToCheckout: false,
1309
1478
  },
1479
+ connectors: {
1480
+ mode: 'tenant_owned_platform_bindings',
1481
+ listCommand: 'tender connectors list --profile account --json',
1482
+ bindCommand: 'tender connectors bind <binding-name> <connector-id> --instance <instance-id> --json',
1483
+ shopify: {
1484
+ playbookId: 'shopify-connector-custom-app',
1485
+ playbookCommand: 'tender playbooks get shopify-connector-custom-app --json',
1486
+ customerMetadataPlaybookCommand: 'tender playbooks get shopify-connector-customer-metadata-widget --json',
1487
+ whatItIs: 'A merchant-owned Shopify custom app exposed to Tender apps through an explicit platform connector binding.',
1488
+ whenToUse: [
1489
+ 'Shopify Admin GraphQL reads or writes',
1490
+ 'customer metafields or customer-linked metaobjects',
1491
+ 'app-owned custom data definitions',
1492
+ 'Theme App Extension app blocks',
1493
+ 'privileged background Shopify work that browser code cannot safely perform',
1494
+ ],
1495
+ whenNotToUse: [
1496
+ 'pure frontend widgets',
1497
+ 'Tender analytics only',
1498
+ 'public Shopify data already present on the page',
1499
+ 'client-side event publishing without Admin API writes',
1500
+ ],
1501
+ binding: {
1502
+ name: 'SHOPIFY_CONNECTOR',
1503
+ source: 'platform:shopify_connector',
1504
+ interface: 'ShopifyConnector',
1505
+ callShape: 'await this.env.SHOPIFY_CONNECTOR.invoke("shopify.graphql", { query, variables })',
1506
+ },
1507
+ sourceProject: {
1508
+ durableIdentity: 'connector.sourceProject.sourceArtifactId',
1509
+ targetPackId: 'shopify-connector-source',
1510
+ visibility: 'internal_source_artifact_not_customer_facing_app',
1511
+ },
1512
+ guardrails: [
1513
+ 'Use the account profile because connectors are account-level resources.',
1514
+ 'Use minimum Shopify scopes for the requested job.',
1515
+ 'Never commit client secrets, Admin API tokens, app automation tokens, Tender tokens, or customer data exports.',
1516
+ 'Do not create bindings/<id>/ files for Shopify connectors; use the platform connector source in app.json.',
1517
+ ],
1518
+ },
1519
+ },
1310
1520
  analyticsAuthoring: {
1311
1521
  command: ANALYTICS_AUTHORING_COMMAND,
1312
1522
  playbookId: ANALYTICS_AUTHORING_PLAYBOOK_ID,
@@ -1333,6 +1543,8 @@ function tenderCliCapabilities() {
1333
1543
  'tender app db --help',
1334
1544
  'tender app bindings outbound --help',
1335
1545
  'tender app outbound-auth --help',
1546
+ 'tender connectors --help',
1547
+ 'tender connectors shopify source init --help',
1336
1548
  ],
1337
1549
  },
1338
1550
  workflows: [
@@ -1358,6 +1570,23 @@ function tenderCliCapabilities() {
1358
1570
  'Do not write fetched playbook contents into the checkout or install them as skills.',
1359
1571
  ],
1360
1572
  },
1573
+ {
1574
+ name: 'shopify_connector_discovery',
1575
+ when: 'When a Tender App needs Shopify Admin API access, customer metadata, app blocks, app-owned custom data, or a merchant-owned Shopify custom app.',
1576
+ commands: [
1577
+ 'tender capabilities --json',
1578
+ 'tender playbooks list --json',
1579
+ 'tender playbooks get shopify-connector-custom-app --json',
1580
+ 'tender connectors list --profile account --json',
1581
+ 'tender connectors bind SHOPIFY_CONNECTOR <connector-id> --instance default --json',
1582
+ ],
1583
+ notes: [
1584
+ 'Connectors are tenant-owned platform bindings, not generated binding workers.',
1585
+ 'The merchant owns the Shopify custom app; Tender stores connector credentials and app source metadata.',
1586
+ 'Use connector.sourceProject.sourceArtifactId as the durable Shopify app source identity.',
1587
+ 'Fetch shopify-connector-customer-metadata-widget when the requested job involves customer metafields, metaobjects, or profile writes.',
1588
+ ],
1589
+ },
1361
1590
  {
1362
1591
  name: 'create_new_app_with_preview',
1363
1592
  when: 'When the user wants a new Tender App that is immediately visible.',
@@ -3077,643 +3306,682 @@ function parseShareCommonArgs(args, startIndex) {
3077
3306
  }
3078
3307
  return { baseUrl, flagToken, profileName, json, remaining };
3079
3308
  }
3080
- function parseShareCreateArgs(args) {
3081
- if (args.includes('--help') || args.includes('-h')) {
3082
- return { command: 'help', topic: 'app share create' };
3083
- }
3084
- const artifactId = args[0];
3085
- if (!artifactId || artifactId.startsWith('-')) {
3086
- throw new TenderCliUsageError('app id is required. Example: tender app share create <app-id> --json');
3087
- }
3088
- const parsed = parseShareCommonArgs(args, 1);
3089
- let expires = '7d';
3090
- let claims = 1;
3091
- for (let index = 0; index < parsed.remaining.length; index += 1) {
3092
- const arg = parsed.remaining[index];
3093
- if (arg === '--expires') {
3094
- expires = parseRequiredFlagValue(parsed.remaining[index + 1], '--expires');
3309
+ function parseConnectorsCommonArgs(args, startIndex) {
3310
+ let baseUrl = null;
3311
+ let flagToken = null;
3312
+ let profileName = null;
3313
+ let json = false;
3314
+ const remaining = [];
3315
+ for (let index = startIndex; index < args.length; index += 1) {
3316
+ const arg = args[index];
3317
+ if (arg === '--json') {
3318
+ json = true;
3319
+ continue;
3320
+ }
3321
+ if (arg === '--base-url') {
3322
+ baseUrl = parseBaseUrlFlag(args, index);
3095
3323
  index += 1;
3096
3324
  continue;
3097
3325
  }
3098
- if (arg === '--claims') {
3099
- claims = parsePositiveIntegerFlag(parsed.remaining[index + 1], '--claims');
3326
+ if (arg === '--token') {
3327
+ flagToken = parseTokenFlag(args, index);
3100
3328
  index += 1;
3101
3329
  continue;
3102
3330
  }
3103
- throw new TenderCliUsageError(`Unknown app share create option: ${arg}`);
3331
+ if (arg === '--profile') {
3332
+ profileName = parseProfileName(args[index + 1], '--profile');
3333
+ index += 1;
3334
+ continue;
3335
+ }
3336
+ remaining.push(arg);
3104
3337
  }
3105
- return { command: 'app share create', artifactId, expires, claims, ...parsed };
3338
+ return { baseUrl, flagToken, profileName, json, remaining };
3106
3339
  }
3107
- function parseShareInspectArgs(args) {
3108
- if (args.includes('--help') || args.includes('-h')) {
3109
- return { command: 'help', topic: 'app share inspect' };
3340
+ function parseConnectorId(value, usage) {
3341
+ if (!value || value.startsWith('-')) {
3342
+ throw new TenderCliUsageError(`connector id is required. Example: ${usage}`);
3110
3343
  }
3111
- const token = args[0];
3112
- if (!token || token.startsWith('-')) {
3113
- throw new TenderCliUsageError('share token is required. Example: tender app share inspect <token> --json');
3344
+ return value;
3345
+ }
3346
+ function parseConnectorInstanceId(value) {
3347
+ const normalized = (value ?? 'default').trim().toLowerCase();
3348
+ if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
3349
+ throw new TenderCliUsageError('--instance must start with a letter or number and contain only lowercase letters, numbers, underscores, or dashes.');
3114
3350
  }
3115
- const parsed = parseShareCommonArgs(args, 1);
3116
- if (parsed.remaining.length > 0) {
3117
- throw new TenderCliUsageError(`Unknown app share inspect option: ${parsed.remaining[0]}`);
3351
+ return normalized;
3352
+ }
3353
+ function parseBindingName(value) {
3354
+ if (!value || value.startsWith('-')) {
3355
+ throw new TenderCliUsageError('binding name is required. Example: tender connectors bind SHOPIFY_CONNECTOR conn_123 --instance default --json');
3118
3356
  }
3119
- return { command: 'app share inspect', token, ...parsed };
3357
+ if (!/^[A-Z][A-Z0-9_]{0,79}$/.test(value)) {
3358
+ throw new TenderCliUsageError('binding name must look like SHOPIFY_CONNECTOR.');
3359
+ }
3360
+ return value;
3120
3361
  }
3121
- function parseShareRevokeArgs(args) {
3362
+ function parseConnectorsListArgs(args) {
3122
3363
  if (args.includes('--help') || args.includes('-h')) {
3123
- return { command: 'help', topic: 'app share revoke' };
3124
- }
3125
- const shareId = args[0];
3126
- if (!shareId || shareId.startsWith('-')) {
3127
- throw new TenderCliUsageError('share id is required. Example: tender app share revoke <share-id> --json');
3364
+ return { command: 'help', topic: 'connectors list' };
3128
3365
  }
3129
- const parsed = parseShareCommonArgs(args, 1);
3366
+ const parsed = parseConnectorsCommonArgs(args, 0);
3130
3367
  if (parsed.remaining.length > 0) {
3131
- throw new TenderCliUsageError(`Unknown app share revoke option: ${parsed.remaining[0]}`);
3368
+ throw new TenderCliUsageError(`Unknown connectors list option: ${parsed.remaining[0]}`);
3132
3369
  }
3133
- return { command: 'app share revoke', shareId, ...parsed };
3370
+ return { command: 'connectors list', ...parsed };
3134
3371
  }
3135
- function parseClaimArgs(args) {
3372
+ function parseConnectorsShopifyCreateArgs(args) {
3136
3373
  if (args.includes('--help') || args.includes('-h')) {
3137
- return { command: 'help', topic: 'app claim' };
3138
- }
3139
- const token = args[0];
3140
- if (!token || token.startsWith('-')) {
3141
- throw new TenderCliUsageError('share token is required. Example: tender app claim <token> --dir ./widget');
3374
+ return { command: 'help', topic: 'connectors shopify create' };
3142
3375
  }
3143
- const parsed = parseShareCommonArgs(args, 1);
3144
- let dir = null;
3145
- let name = null;
3146
- let confirm = null;
3376
+ const parsed = parseConnectorsCommonArgs(args, 0);
3377
+ let instanceId = 'default';
3378
+ let label = null;
3379
+ let appName = null;
3380
+ let adminApiVersion = null;
3381
+ let shopDomain = null;
3147
3382
  for (let index = 0; index < parsed.remaining.length; index += 1) {
3148
3383
  const arg = parsed.remaining[index];
3149
- if (arg === '--dir') {
3150
- dir = parseRequiredFlagValue(parsed.remaining[index + 1], '--dir');
3384
+ if (arg === '--instance') {
3385
+ instanceId = parseConnectorInstanceId(parsed.remaining[index + 1]);
3151
3386
  index += 1;
3152
3387
  continue;
3153
3388
  }
3154
- if (arg === '--name') {
3155
- name = parseRequiredFlagValue(parsed.remaining[index + 1], '--name');
3389
+ if (arg === '--label') {
3390
+ label = parseRequiredFlagValue(parsed.remaining[index + 1], '--label');
3156
3391
  index += 1;
3157
3392
  continue;
3158
3393
  }
3159
- if (arg === '--confirm') {
3160
- confirm = parseRequiredFlagValue(parsed.remaining[index + 1], '--confirm');
3394
+ if (arg === '--app-name') {
3395
+ appName = parseRequiredFlagValue(parsed.remaining[index + 1], '--app-name');
3161
3396
  index += 1;
3162
3397
  continue;
3163
3398
  }
3164
- throw new TenderCliUsageError(`Unknown app claim option: ${arg}`);
3165
- }
3166
- if (!dir) {
3167
- throw new TenderCliUsageError('--dir is required. Example: tender app claim tok_share_123 --dir ./widget');
3399
+ if (arg === '--admin-api-version') {
3400
+ adminApiVersion = parseRequiredFlagValue(parsed.remaining[index + 1], '--admin-api-version');
3401
+ index += 1;
3402
+ continue;
3403
+ }
3404
+ if (arg === '--shop') {
3405
+ shopDomain = parseRequiredFlagValue(parsed.remaining[index + 1], '--shop');
3406
+ index += 1;
3407
+ continue;
3408
+ }
3409
+ throw new TenderCliUsageError(`Unknown connectors shopify create option: ${arg}`);
3168
3410
  }
3169
- return { command: 'app claim', token, dir, name, confirm, ...parsed };
3411
+ return {
3412
+ command: 'connectors shopify create',
3413
+ instanceId,
3414
+ label,
3415
+ appName,
3416
+ adminApiVersion,
3417
+ shopDomain,
3418
+ ...parsed,
3419
+ };
3170
3420
  }
3171
- function parseArtifactsInitArgs(args) {
3421
+ function parseConnectorsShopifySetupUrlArgs(args) {
3172
3422
  if (args.includes('--help') || args.includes('-h')) {
3173
- return { command: 'help', topic: 'app init' };
3423
+ return { command: 'help', topic: 'connectors shopify setup-url' };
3174
3424
  }
3175
- const artifactId = args[0];
3176
- if (!artifactId || artifactId.startsWith('-')) {
3177
- throw new TenderCliUsageError('app id is required. Example: tender app init <app-id> --dir ./widget --json');
3425
+ const connectorId = parseConnectorId(args[0], 'tender connectors shopify setup-url <connector-id> --json');
3426
+ const parsed = parseConnectorsCommonArgs(args, 1);
3427
+ if (parsed.remaining.length > 0) {
3428
+ throw new TenderCliUsageError(`Unknown connectors shopify setup-url option: ${parsed.remaining[0]}`);
3178
3429
  }
3179
- let dir = '.';
3180
- let remoteName = 'tender';
3181
- let baseUrl = null;
3182
- let flagToken = null;
3183
- let profileName = null;
3184
- let ttl = '7d';
3185
- let scaffold = 'server-backed';
3186
- let preview = false;
3187
- let dryRun = false;
3188
- let force = false;
3189
- let json = false;
3190
- for (let index = 1; index < args.length; index += 1) {
3191
- const arg = args[index];
3192
- if (arg === '--json') {
3193
- json = true;
3194
- continue;
3195
- }
3196
- if (arg === '--dry-run') {
3197
- dryRun = true;
3198
- continue;
3199
- }
3200
- if (arg === '--force') {
3201
- force = true;
3202
- continue;
3203
- }
3204
- if (arg === '--preview') {
3205
- preview = true;
3206
- continue;
3207
- }
3208
- if (arg === '--no-scaffold') {
3209
- scaffold = 'none';
3210
- continue;
3211
- }
3212
- if (arg === '--dir') {
3213
- const value = args[index + 1];
3214
- if (!value || value.startsWith('-')) {
3215
- throw new TenderCliUsageError('--dir requires a path.');
3216
- }
3217
- dir = value;
3430
+ return { command: 'connectors shopify setup-url', connectorId, ...parsed };
3431
+ }
3432
+ function parseConnectorsShopifyCredentialsSetArgs(args) {
3433
+ if (args.includes('--help') || args.includes('-h')) {
3434
+ return { command: 'help', topic: 'connectors shopify credentials set' };
3435
+ }
3436
+ const connectorId = parseConnectorId(args[0], 'tender connectors shopify credentials set <connector-id> --shop <domain> --client-id <id> --client-secret-stdin --json');
3437
+ const parsed = parseConnectorsCommonArgs(args, 1);
3438
+ let shopDomain = null;
3439
+ let clientId = null;
3440
+ let clientSecretSource = null;
3441
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
3442
+ const arg = parsed.remaining[index];
3443
+ if (arg === '--shop') {
3444
+ shopDomain = parseRequiredFlagValue(parsed.remaining[index + 1], '--shop');
3218
3445
  index += 1;
3219
3446
  continue;
3220
3447
  }
3221
- if (arg === '--remote') {
3222
- remoteName = parseRemoteName(args[index + 1]);
3448
+ if (arg === '--client-id') {
3449
+ clientId = parseRequiredFlagValue(parsed.remaining[index + 1], '--client-id');
3223
3450
  index += 1;
3224
3451
  continue;
3225
3452
  }
3226
- if (arg === '--base-url') {
3227
- baseUrl = parseBaseUrlFlag(args, index);
3228
- index += 1;
3453
+ if (arg === '--client-secret-stdin') {
3454
+ clientSecretSource = { kind: 'stdin' };
3229
3455
  continue;
3230
3456
  }
3231
- if (arg === '--token') {
3232
- flagToken = parseTokenFlag(args, index);
3457
+ if (arg === '--client-secret-env') {
3458
+ clientSecretSource = {
3459
+ kind: 'env',
3460
+ name: parseRequiredFlagValue(parsed.remaining[index + 1], '--client-secret-env'),
3461
+ };
3233
3462
  index += 1;
3234
3463
  continue;
3235
3464
  }
3236
- if (arg === '--profile') {
3237
- profileName = parseProfileName(args[index + 1], '--profile');
3238
- index += 1;
3465
+ throw new TenderCliUsageError(`Unknown connectors shopify credentials set option: ${arg}`);
3466
+ }
3467
+ if (!shopDomain) {
3468
+ throw new TenderCliUsageError('--shop is required. Example: tender connectors shopify credentials set conn_123 --shop demo.myshopify.com --client-id abc --client-secret-stdin --json');
3469
+ }
3470
+ if (!clientId) {
3471
+ throw new TenderCliUsageError('--client-id is required. Example: tender connectors shopify credentials set conn_123 --shop demo.myshopify.com --client-id abc --client-secret-stdin --json');
3472
+ }
3473
+ if (!clientSecretSource) {
3474
+ throw new TenderCliUsageError('--client-secret-stdin or --client-secret-env is required.');
3475
+ }
3476
+ return {
3477
+ command: 'connectors shopify credentials set',
3478
+ connectorId,
3479
+ shopDomain,
3480
+ clientId,
3481
+ clientSecretSource,
3482
+ ...parsed,
3483
+ };
3484
+ }
3485
+ function parseConnectorsShopifyAppAutomationTokenSetArgs(args) {
3486
+ if (args.includes('--help') || args.includes('-h')) {
3487
+ return { command: 'help', topic: 'connectors shopify app-automation-token set' };
3488
+ }
3489
+ const connectorId = parseConnectorId(args[0], 'tender connectors shopify app-automation-token set <connector-id> --token-stdin --json');
3490
+ const parsed = parseConnectorsCommonArgs(args, 1);
3491
+ let tokenSource = null;
3492
+ let expiresAt = null;
3493
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
3494
+ const arg = parsed.remaining[index];
3495
+ if (arg === '--token-stdin') {
3496
+ tokenSource = { kind: 'stdin' };
3239
3497
  continue;
3240
3498
  }
3241
- if (arg === '--ttl') {
3242
- ttl = parseTtl(args[index + 1]);
3499
+ if (arg === '--token-env') {
3500
+ tokenSource = {
3501
+ kind: 'env',
3502
+ name: parseRequiredFlagValue(parsed.remaining[index + 1], '--token-env'),
3503
+ };
3243
3504
  index += 1;
3244
3505
  continue;
3245
3506
  }
3246
- if (arg === '--scaffold') {
3247
- scaffold = parseScaffoldTemplate(args[index + 1]);
3507
+ if (arg === '--expires-at') {
3508
+ expiresAt = parseRequiredFlagValue(parsed.remaining[index + 1], '--expires-at');
3248
3509
  index += 1;
3249
3510
  continue;
3250
3511
  }
3251
- throw new TenderCliUsageError(`Unknown app init option: ${arg}`);
3512
+ throw new TenderCliUsageError(`Unknown connectors shopify app-automation-token set option: ${arg}`);
3513
+ }
3514
+ if (!tokenSource) {
3515
+ throw new TenderCliUsageError('--token-stdin or --token-env is required.');
3252
3516
  }
3253
3517
  return {
3254
- command: 'app init',
3255
- artifactId,
3256
- dir,
3257
- remoteName,
3258
- baseUrl,
3259
- flagToken,
3260
- profileName,
3261
- ttl,
3262
- scaffold,
3263
- preview,
3264
- dryRun,
3265
- force,
3266
- json,
3518
+ command: 'connectors shopify app-automation-token set',
3519
+ connectorId,
3520
+ tokenSource,
3521
+ expiresAt,
3522
+ ...parsed,
3267
3523
  };
3268
3524
  }
3269
- function parseContextFetchArgs(args) {
3525
+ function parseConnectorsShopifyAppAutomationTokenDeleteArgs(args) {
3270
3526
  if (args.includes('--help') || args.includes('-h')) {
3271
- return { command: 'help', topic: 'app context fetch' };
3272
- }
3273
- const artifactId = args[0];
3274
- if (!artifactId || artifactId.startsWith('-')) {
3275
- throw new TenderCliUsageError('app id is required. Example: tender app context fetch <app-id> --dir ./widget --json');
3527
+ return { command: 'help', topic: 'connectors shopify app-automation-token delete' };
3276
3528
  }
3277
- let dir = '.';
3278
- let baseUrl = null;
3279
- let flagToken = null;
3280
- let profileName = null;
3281
- let dryRun = false;
3282
- let force = false;
3283
- let json = false;
3284
- for (let index = 1; index < args.length; index += 1) {
3285
- const arg = args[index];
3286
- if (arg === '--json') {
3287
- json = true;
3288
- continue;
3289
- }
3290
- if (arg === '--dry-run') {
3291
- dryRun = true;
3292
- continue;
3293
- }
3294
- if (arg === '--force') {
3295
- force = true;
3296
- continue;
3297
- }
3298
- if (arg === '--dir') {
3299
- const value = args[index + 1];
3300
- if (!value || value.startsWith('-')) {
3301
- throw new TenderCliUsageError('--dir requires a path.');
3302
- }
3303
- dir = value;
3304
- index += 1;
3305
- continue;
3306
- }
3307
- if (arg === '--base-url') {
3308
- baseUrl = parseBaseUrlFlag(args, index);
3309
- index += 1;
3310
- continue;
3311
- }
3312
- if (arg === '--token') {
3313
- flagToken = parseTokenFlag(args, index);
3314
- index += 1;
3315
- continue;
3316
- }
3317
- if (arg === '--profile') {
3318
- profileName = parseProfileName(args[index + 1], '--profile');
3529
+ const connectorId = parseConnectorId(args[0], 'tender connectors shopify app-automation-token delete <connector-id> --confirm delete --json');
3530
+ const parsed = parseConnectorsCommonArgs(args, 1);
3531
+ let confirm = null;
3532
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
3533
+ const arg = parsed.remaining[index];
3534
+ if (arg === '--confirm') {
3535
+ confirm = parseRequiredFlagValue(parsed.remaining[index + 1], '--confirm');
3319
3536
  index += 1;
3320
3537
  continue;
3321
3538
  }
3322
- throw new TenderCliUsageError(`Unknown context fetch option: ${arg}`);
3539
+ throw new TenderCliUsageError(`Unknown connectors shopify app-automation-token delete option: ${arg}`);
3323
3540
  }
3324
3541
  return {
3325
- command: 'app context fetch',
3326
- artifactId,
3327
- dir,
3328
- baseUrl,
3329
- flagToken,
3330
- profileName,
3331
- dryRun,
3332
- force,
3333
- json,
3542
+ command: 'connectors shopify app-automation-token delete',
3543
+ connectorId,
3544
+ confirm,
3545
+ ...parsed,
3334
3546
  };
3335
3547
  }
3336
- function parseContextStatusArgs(args) {
3548
+ function parseConnectorsShopifyVerifyArgs(args) {
3337
3549
  if (args.includes('--help') || args.includes('-h')) {
3338
- return { command: 'help', topic: 'app context status' };
3550
+ return { command: 'help', topic: 'connectors shopify verify' };
3339
3551
  }
3340
- const artifactId = args[0];
3341
- if (!artifactId || artifactId.startsWith('-')) {
3342
- throw new TenderCliUsageError('app id is required. Example: tender app context status <app-id> --dir ./widget --json');
3552
+ const connectorId = parseConnectorId(args[0], 'tender connectors shopify verify <connector-id> --json');
3553
+ const parsed = parseConnectorsCommonArgs(args, 1);
3554
+ if (parsed.remaining.length > 0) {
3555
+ throw new TenderCliUsageError(`Unknown connectors shopify verify option: ${parsed.remaining[0]}`);
3343
3556
  }
3344
- let dir = '.';
3345
- let baseUrl = null;
3346
- let flagToken = null;
3347
- let profileName = null;
3348
- let json = false;
3349
- for (let index = 1; index < args.length; index += 1) {
3350
- const arg = args[index];
3351
- if (arg === '--json') {
3352
- json = true;
3353
- continue;
3354
- }
3557
+ return { command: 'connectors shopify verify', connectorId, ...parsed };
3558
+ }
3559
+ function parseConnectorsShopifySourceInitArgs(args) {
3560
+ if (args.includes('--help') || args.includes('-h')) {
3561
+ return { command: 'help', topic: 'connectors shopify source init' };
3562
+ }
3563
+ const connectorId = parseConnectorId(args[0], 'tender connectors shopify source init <connector-id> --dir ./shopify-app --client-id abc --tender-app-url https://example.tenderprompt.app/client.js --tender-element tender-widget --json');
3564
+ const parsed = parseConnectorsCommonArgs(args, 1);
3565
+ let dir = null;
3566
+ let clientId = null;
3567
+ let sourceName = null;
3568
+ let tenderAppUrl = null;
3569
+ let tenderElement = null;
3570
+ let artifactId = null;
3571
+ let publicId = null;
3572
+ let themeExtensionHandle = 'tender-widget';
3573
+ let themeExtensionUid = null;
3574
+ let blockName = 'Tender Prompt Widget';
3575
+ let scopes = null;
3576
+ let force = false;
3577
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
3578
+ const arg = parsed.remaining[index];
3355
3579
  if (arg === '--dir') {
3356
- const value = args[index + 1];
3357
- if (!value || value.startsWith('-')) {
3358
- throw new TenderCliUsageError('--dir requires a path.');
3359
- }
3360
- dir = value;
3580
+ dir = parseRequiredFlagValue(parsed.remaining[index + 1], '--dir');
3361
3581
  index += 1;
3362
3582
  continue;
3363
3583
  }
3364
- if (arg === '--base-url') {
3365
- baseUrl = parseBaseUrlFlag(args, index);
3584
+ if (arg === '--client-id') {
3585
+ clientId = parseRequiredFlagValue(parsed.remaining[index + 1], '--client-id');
3366
3586
  index += 1;
3367
3587
  continue;
3368
3588
  }
3369
- if (arg === '--token') {
3370
- flagToken = parseTokenFlag(args, index);
3589
+ if (arg === '--source-name') {
3590
+ sourceName = parseRequiredFlagValue(parsed.remaining[index + 1], '--source-name');
3371
3591
  index += 1;
3372
3592
  continue;
3373
3593
  }
3374
- if (arg === '--profile') {
3375
- profileName = parseProfileName(args[index + 1], '--profile');
3594
+ if (arg === '--tender-app-url') {
3595
+ tenderAppUrl = parseRequiredFlagValue(parsed.remaining[index + 1], '--tender-app-url');
3376
3596
  index += 1;
3377
3597
  continue;
3378
3598
  }
3379
- throw new TenderCliUsageError(`Unknown context status option: ${arg}`);
3380
- }
3381
- return {
3382
- command: 'app context status',
3383
- artifactId,
3384
- dir,
3385
- baseUrl,
3386
- flagToken,
3387
- profileName,
3388
- json,
3389
- };
3390
- }
3391
- function parseContextRefreshArgs(args) {
3392
- if (args.includes('--help') || args.includes('-h')) {
3393
- return { command: 'help', topic: 'app context refresh' };
3394
- }
3395
- const artifactId = args[0];
3396
- if (!artifactId || artifactId.startsWith('-')) {
3397
- throw new TenderCliUsageError('app id is required. Example: tender app context refresh <app-id> --dir ./widget --dry-run --json');
3398
- }
3399
- let dir = '.';
3400
- let baseUrl = null;
3401
- let flagToken = null;
3402
- let profileName = null;
3403
- let dryRun = false;
3404
- let force = false;
3405
- let json = false;
3406
- for (let index = 1; index < args.length; index += 1) {
3407
- const arg = args[index];
3408
- if (arg === '--json') {
3409
- json = true;
3599
+ if (arg === '--tender-element') {
3600
+ tenderElement = parseRequiredFlagValue(parsed.remaining[index + 1], '--tender-element');
3601
+ index += 1;
3410
3602
  continue;
3411
3603
  }
3412
- if (arg === '--dry-run') {
3413
- dryRun = true;
3604
+ if (arg === '--artifact-id') {
3605
+ artifactId = parseRequiredFlagValue(parsed.remaining[index + 1], '--artifact-id');
3606
+ index += 1;
3414
3607
  continue;
3415
3608
  }
3416
- if (arg === '--force') {
3417
- force = true;
3609
+ if (arg === '--public-id') {
3610
+ publicId = parseRequiredFlagValue(parsed.remaining[index + 1], '--public-id');
3611
+ index += 1;
3418
3612
  continue;
3419
3613
  }
3420
- if (arg === '--dir') {
3421
- const value = args[index + 1];
3422
- if (!value || value.startsWith('-')) {
3423
- throw new TenderCliUsageError('--dir requires a path.');
3424
- }
3425
- dir = value;
3614
+ if (arg === '--theme-extension-handle') {
3615
+ themeExtensionHandle = parseRequiredFlagValue(parsed.remaining[index + 1], '--theme-extension-handle');
3426
3616
  index += 1;
3427
3617
  continue;
3428
3618
  }
3429
- if (arg === '--base-url') {
3430
- baseUrl = parseBaseUrlFlag(args, index);
3619
+ if (arg === '--theme-extension-uid') {
3620
+ themeExtensionUid = parseRequiredFlagValue(parsed.remaining[index + 1], '--theme-extension-uid');
3431
3621
  index += 1;
3432
3622
  continue;
3433
3623
  }
3434
- if (arg === '--token') {
3435
- flagToken = parseTokenFlag(args, index);
3624
+ if (arg === '--block-name') {
3625
+ blockName = parseRequiredFlagValue(parsed.remaining[index + 1], '--block-name');
3436
3626
  index += 1;
3437
3627
  continue;
3438
3628
  }
3439
- if (arg === '--profile') {
3440
- profileName = parseProfileName(args[index + 1], '--profile');
3629
+ if (arg === '--scopes') {
3630
+ scopes = parseRequiredFlagValue(parsed.remaining[index + 1], '--scopes');
3441
3631
  index += 1;
3442
3632
  continue;
3443
3633
  }
3444
- throw new TenderCliUsageError(`Unknown context refresh option: ${arg}`);
3634
+ if (arg === '--force') {
3635
+ force = true;
3636
+ continue;
3637
+ }
3638
+ throw new TenderCliUsageError(`Unknown connectors shopify source init option: ${arg}`);
3639
+ }
3640
+ if (!dir) {
3641
+ throw new TenderCliUsageError('--dir is required. Example: tender connectors shopify source init conn_123 --dir ./shopify-app --client-id abc --tender-app-url https://example.tenderprompt.app/client.js --tender-element tender-widget --json');
3642
+ }
3643
+ if (!clientId) {
3644
+ throw new TenderCliUsageError('--client-id is required because Tender stores only the non-secret client ID prefix.');
3645
+ }
3646
+ if (!tenderAppUrl) {
3647
+ throw new TenderCliUsageError('--tender-app-url is required. Pass the Tender app client.js URL to load in the app block.');
3648
+ }
3649
+ if (!tenderElement) {
3650
+ throw new TenderCliUsageError('--tender-element is required. Pass the Tender custom element tag to mount in Shopify.');
3445
3651
  }
3446
3652
  return {
3447
- command: 'app context refresh',
3448
- artifactId,
3653
+ command: 'connectors shopify source init',
3654
+ connectorId,
3449
3655
  dir,
3450
- baseUrl,
3451
- flagToken,
3452
- profileName,
3453
- dryRun,
3656
+ clientId,
3657
+ sourceName,
3658
+ template: 'dog-profile',
3659
+ tenderAppUrl,
3660
+ tenderElement,
3661
+ artifactId,
3662
+ publicId,
3663
+ themeExtensionHandle,
3664
+ themeExtensionUid,
3665
+ blockName,
3666
+ scopes,
3454
3667
  force,
3455
- json,
3668
+ ...parsed,
3456
3669
  };
3457
3670
  }
3458
- function parseMemoryCommonArgs(args) {
3459
- let dir = '.';
3460
- let artifactId = null;
3671
+ function parseConnectorsShopifySourceCheckoutArgs(args) {
3672
+ if (args.includes('--help') || args.includes('-h')) {
3673
+ return { command: 'help', topic: 'connectors shopify source checkout' };
3674
+ }
3675
+ const connectorId = parseConnectorId(args[0], 'tender connectors shopify source checkout <connector-id> --dir ./shopify-app --json');
3676
+ const parsed = parseConnectorsCommonArgs(args, 1);
3677
+ let dir = null;
3461
3678
  let remoteName = 'tender';
3462
- let json = false;
3463
- const remaining = [];
3464
- for (let index = 0; index < args.length; index += 1) {
3465
- const arg = args[index];
3466
- if (arg === '--json') {
3467
- json = true;
3468
- continue;
3469
- }
3679
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
3680
+ const arg = parsed.remaining[index];
3470
3681
  if (arg === '--dir') {
3471
- dir = parseRequiredFlagValue(args[index + 1], '--dir');
3472
- index += 1;
3473
- continue;
3474
- }
3475
- if (arg === '--artifact') {
3476
- artifactId = parseRequiredFlagValue(args[index + 1], '--artifact');
3682
+ dir = parseRequiredFlagValue(parsed.remaining[index + 1], '--dir');
3477
3683
  index += 1;
3478
3684
  continue;
3479
3685
  }
3480
3686
  if (arg === '--remote') {
3481
- remoteName = parseRemoteName(args[index + 1]);
3687
+ remoteName = parseRemoteName(parsed.remaining[index + 1]);
3482
3688
  index += 1;
3483
3689
  continue;
3484
3690
  }
3485
- remaining.push(arg);
3691
+ throw new TenderCliUsageError(`Unknown connectors shopify source checkout option: ${arg}`);
3486
3692
  }
3487
- return { dir, artifactId, remoteName, json, remaining };
3693
+ if (!dir) {
3694
+ throw new TenderCliUsageError('--dir is required. Example: tender connectors shopify source checkout conn_123 --dir ./shopify-app --json');
3695
+ }
3696
+ return { command: 'connectors shopify source checkout', connectorId, dir, remoteName, ...parsed };
3488
3697
  }
3489
- function parseMemoryStartArgs(args) {
3698
+ function parseConnectorsShopifySourceValidateArgs(args) {
3490
3699
  if (args.includes('--help') || args.includes('-h')) {
3491
- return { command: 'help', topic: 'memory start' };
3700
+ return { command: 'help', topic: 'connectors shopify source validate' };
3492
3701
  }
3493
- const parsed = parseMemoryCommonArgs(args);
3494
- let summary = '';
3495
- let body = null;
3702
+ const connectorId = parseConnectorId(args[0], 'tender connectors shopify source validate <connector-id> --dir ./shopify-app --execute --json');
3703
+ const parsed = parseConnectorsCommonArgs(args, 1);
3704
+ let dir = '.';
3705
+ let clientId = null;
3706
+ let execute = false;
3496
3707
  for (let index = 0; index < parsed.remaining.length; index += 1) {
3497
3708
  const arg = parsed.remaining[index];
3498
- if (arg === '--summary') {
3499
- summary = parseRequiredFlagValue(parsed.remaining[index + 1], '--summary');
3709
+ if (arg === '--execute') {
3710
+ execute = true;
3711
+ continue;
3712
+ }
3713
+ if (arg === '--dir') {
3714
+ dir = parseRequiredFlagValue(parsed.remaining[index + 1], '--dir');
3500
3715
  index += 1;
3501
3716
  continue;
3502
3717
  }
3503
- if (arg === '--body') {
3504
- body = parseRequiredFlagValue(parsed.remaining[index + 1], '--body');
3718
+ if (arg === '--client-id') {
3719
+ clientId = parseRequiredFlagValue(parsed.remaining[index + 1], '--client-id');
3505
3720
  index += 1;
3506
3721
  continue;
3507
3722
  }
3508
- throw new TenderCliUsageError(`Unknown memory start option: ${arg}`);
3509
- }
3510
- if (!summary.trim()) {
3511
- throw new TenderCliUsageError('memory start requires --summary.\nNext: tender memory start --summary "Current user goal"');
3723
+ throw new TenderCliUsageError(`Unknown connectors shopify source validate option: ${arg}`);
3512
3724
  }
3513
- return { command: 'memory start', summary, body, ...parsed };
3725
+ return { command: 'connectors shopify source validate', connectorId, dir, clientId, execute, ...parsed };
3514
3726
  }
3515
- function parseMemoryAddArgs(args) {
3727
+ function parseConnectorsShopifySourceDeployArgs(args) {
3516
3728
  if (args.includes('--help') || args.includes('-h')) {
3517
- return { command: 'help', topic: 'memory add' };
3729
+ return { command: 'help', topic: 'connectors shopify source deploy' };
3518
3730
  }
3519
- const parsed = parseMemoryCommonArgs(args);
3520
- let kind = 'note';
3521
- let summary = '';
3522
- let body = null;
3523
- const tags = [];
3731
+ const connectorId = parseConnectorId(args[0], 'tender connectors shopify source deploy <connector-id> --dir ./shopify-app --confirm deploy --json');
3732
+ const parsed = parseConnectorsCommonArgs(args, 1);
3733
+ let dir = '.';
3734
+ let clientId = null;
3735
+ let version = null;
3736
+ let message = null;
3737
+ let sourceControlUrl = null;
3738
+ let allowDeletes = false;
3739
+ let confirm = null;
3524
3740
  for (let index = 0; index < parsed.remaining.length; index += 1) {
3525
3741
  const arg = parsed.remaining[index];
3526
- if (arg === '--kind') {
3527
- kind = parseRequiredFlagValue(parsed.remaining[index + 1], '--kind');
3742
+ if (arg === '--allow-deletes') {
3743
+ allowDeletes = true;
3744
+ continue;
3745
+ }
3746
+ if (arg === '--dir') {
3747
+ dir = parseRequiredFlagValue(parsed.remaining[index + 1], '--dir');
3528
3748
  index += 1;
3529
3749
  continue;
3530
3750
  }
3531
- if (arg === '--summary') {
3532
- summary = parseRequiredFlagValue(parsed.remaining[index + 1], '--summary');
3751
+ if (arg === '--client-id') {
3752
+ clientId = parseRequiredFlagValue(parsed.remaining[index + 1], '--client-id');
3533
3753
  index += 1;
3534
3754
  continue;
3535
3755
  }
3536
- if (arg === '--body') {
3537
- body = parseRequiredFlagValue(parsed.remaining[index + 1], '--body');
3756
+ if (arg === '--version') {
3757
+ version = parseRequiredFlagValue(parsed.remaining[index + 1], '--version');
3538
3758
  index += 1;
3539
3759
  continue;
3540
3760
  }
3541
- if (arg === '--tag') {
3542
- tags.push(parseRequiredFlagValue(parsed.remaining[index + 1], '--tag'));
3761
+ if (arg === '--message') {
3762
+ message = parseRequiredFlagValue(parsed.remaining[index + 1], '--message');
3543
3763
  index += 1;
3544
3764
  continue;
3545
3765
  }
3546
- throw new TenderCliUsageError(`Unknown memory add option: ${arg}`);
3766
+ if (arg === '--source-control-url') {
3767
+ sourceControlUrl = parseRequiredFlagValue(parsed.remaining[index + 1], '--source-control-url');
3768
+ index += 1;
3769
+ continue;
3770
+ }
3771
+ if (arg === '--confirm') {
3772
+ confirm = parseRequiredFlagValue(parsed.remaining[index + 1], '--confirm');
3773
+ index += 1;
3774
+ continue;
3775
+ }
3776
+ throw new TenderCliUsageError(`Unknown connectors shopify source deploy option: ${arg}`);
3547
3777
  }
3548
- return { command: 'memory add', kind, summary, body, tags, ...parsed };
3778
+ return { command: 'connectors shopify source deploy', connectorId, dir, clientId, version, message, sourceControlUrl, allowDeletes, confirm, ...parsed };
3549
3779
  }
3550
- function parseMemoryHandoffArgs(args) {
3780
+ function parseConnectorsShopifyDeleteArgs(args) {
3551
3781
  if (args.includes('--help') || args.includes('-h')) {
3552
- return { command: 'help', topic: 'memory handoff' };
3782
+ return { command: 'help', topic: 'connectors shopify delete' };
3553
3783
  }
3554
- const parsed = parseMemoryCommonArgs(args);
3555
- let reason = null;
3556
- let summary = null;
3557
- let body = null;
3784
+ const connectorId = parseConnectorId(args[0], 'tender connectors shopify delete <connector-id> --confirm delete --json');
3785
+ const parsed = parseConnectorsCommonArgs(args, 1);
3786
+ let confirm = null;
3558
3787
  for (let index = 0; index < parsed.remaining.length; index += 1) {
3559
3788
  const arg = parsed.remaining[index];
3560
- if (arg === '--reason') {
3561
- reason = parseRequiredFlagValue(parsed.remaining[index + 1], '--reason');
3789
+ if (arg === '--confirm') {
3790
+ confirm = parseRequiredFlagValue(parsed.remaining[index + 1], '--confirm');
3562
3791
  index += 1;
3563
3792
  continue;
3564
3793
  }
3565
- if (arg === '--summary') {
3566
- summary = parseRequiredFlagValue(parsed.remaining[index + 1], '--summary');
3794
+ throw new TenderCliUsageError(`Unknown connectors shopify delete option: ${arg}`);
3795
+ }
3796
+ return { command: 'connectors shopify delete', connectorId, confirm, ...parsed };
3797
+ }
3798
+ function parseConnectorsBindArgs(args) {
3799
+ if (args.includes('--help') || args.includes('-h')) {
3800
+ return { command: 'help', topic: 'connectors bind' };
3801
+ }
3802
+ const bindingName = parseBindingName(args[0]);
3803
+ const connectorId = parseConnectorId(args[1], 'tender connectors bind SHOPIFY_CONNECTOR conn_123 --instance default --json');
3804
+ let instanceId = 'default';
3805
+ let label = null;
3806
+ let json = false;
3807
+ for (let index = 2; index < args.length; index += 1) {
3808
+ const arg = args[index];
3809
+ if (arg === '--json') {
3810
+ json = true;
3811
+ continue;
3812
+ }
3813
+ if (arg === '--instance') {
3814
+ instanceId = parseConnectorInstanceId(args[index + 1]);
3567
3815
  index += 1;
3568
3816
  continue;
3569
3817
  }
3570
- if (arg === '--body') {
3571
- body = parseRequiredFlagValue(parsed.remaining[index + 1], '--body');
3818
+ if (arg === '--label') {
3819
+ label = parseRequiredFlagValue(args[index + 1], '--label');
3572
3820
  index += 1;
3573
3821
  continue;
3574
3822
  }
3575
- throw new TenderCliUsageError(`Unknown memory handoff option: ${arg}`);
3823
+ throw new TenderCliUsageError(`Unknown connectors bind option: ${arg}`);
3576
3824
  }
3577
- return { command: 'memory handoff', reason, summary, body, ...parsed };
3825
+ return { command: 'connectors bind', bindingName, connectorId, instanceId, label, json };
3578
3826
  }
3579
- function parseMemoryShowArgs(args) {
3827
+ function parseShareCreateArgs(args) {
3580
3828
  if (args.includes('--help') || args.includes('-h')) {
3581
- return { command: 'help', topic: 'memory show' };
3829
+ return { command: 'help', topic: 'app share create' };
3582
3830
  }
3583
- const parsed = parseMemoryCommonArgs(args);
3584
- let commitSha = null;
3831
+ const artifactId = args[0];
3832
+ if (!artifactId || artifactId.startsWith('-')) {
3833
+ throw new TenderCliUsageError('app id is required. Example: tender app share create <app-id> --json');
3834
+ }
3835
+ const parsed = parseShareCommonArgs(args, 1);
3836
+ let expires = '7d';
3837
+ let claims = 1;
3585
3838
  for (let index = 0; index < parsed.remaining.length; index += 1) {
3586
3839
  const arg = parsed.remaining[index];
3587
- if (arg === '--commit') {
3588
- commitSha = parseRequiredFlagValue(parsed.remaining[index + 1], '--commit');
3840
+ if (arg === '--expires') {
3841
+ expires = parseRequiredFlagValue(parsed.remaining[index + 1], '--expires');
3589
3842
  index += 1;
3590
3843
  continue;
3591
3844
  }
3592
- throw new TenderCliUsageError(`Unknown memory show option: ${arg}`);
3845
+ if (arg === '--claims') {
3846
+ claims = parsePositiveIntegerFlag(parsed.remaining[index + 1], '--claims');
3847
+ index += 1;
3848
+ continue;
3849
+ }
3850
+ throw new TenderCliUsageError(`Unknown app share create option: ${arg}`);
3593
3851
  }
3594
- return { command: 'memory show', commitSha, ...parsed };
3852
+ return { command: 'app share create', artifactId, expires, claims, ...parsed };
3595
3853
  }
3596
- function parseMemoryBriefArgs(args) {
3854
+ function parseShareInspectArgs(args) {
3597
3855
  if (args.includes('--help') || args.includes('-h')) {
3598
- return { command: 'help', topic: 'memory brief' };
3856
+ return { command: 'help', topic: 'app share inspect' };
3599
3857
  }
3600
- const parsed = parseMemoryCommonArgs(args);
3601
- let limit = 12;
3602
- for (let index = 0; index < parsed.remaining.length; index += 1) {
3603
- const arg = parsed.remaining[index];
3604
- if (arg === '--limit') {
3605
- limit = parsePositiveIntegerFlag(parsed.remaining[index + 1], '--limit');
3606
- index += 1;
3607
- continue;
3608
- }
3609
- throw new TenderCliUsageError(`Unknown memory brief option: ${arg}`);
3858
+ const token = args[0];
3859
+ if (!token || token.startsWith('-')) {
3860
+ throw new TenderCliUsageError('share token is required. Example: tender app share inspect <token> --json');
3610
3861
  }
3611
- return { command: 'memory brief', limit, ...parsed };
3862
+ const parsed = parseShareCommonArgs(args, 1);
3863
+ if (parsed.remaining.length > 0) {
3864
+ throw new TenderCliUsageError(`Unknown app share inspect option: ${parsed.remaining[0]}`);
3865
+ }
3866
+ return { command: 'app share inspect', token, ...parsed };
3612
3867
  }
3613
- function parseMemoryFlushArgs(args) {
3868
+ function parseShareRevokeArgs(args) {
3614
3869
  if (args.includes('--help') || args.includes('-h')) {
3615
- return { command: 'help', topic: 'memory flush' };
3870
+ return { command: 'help', topic: 'app share revoke' };
3616
3871
  }
3617
- const parsed = parseMemoryCommonArgs(args);
3872
+ const shareId = args[0];
3873
+ if (!shareId || shareId.startsWith('-')) {
3874
+ throw new TenderCliUsageError('share id is required. Example: tender app share revoke <share-id> --json');
3875
+ }
3876
+ const parsed = parseShareCommonArgs(args, 1);
3618
3877
  if (parsed.remaining.length > 0) {
3619
- throw new TenderCliUsageError(`Unknown memory flush option: ${parsed.remaining[0]}`);
3878
+ throw new TenderCliUsageError(`Unknown app share revoke option: ${parsed.remaining[0]}`);
3620
3879
  }
3621
- return { command: 'memory flush', ...parsed };
3880
+ return { command: 'app share revoke', shareId, ...parsed };
3622
3881
  }
3623
- function parseMemoryExportArgs(args) {
3882
+ function parseClaimArgs(args) {
3624
3883
  if (args.includes('--help') || args.includes('-h')) {
3625
- return { command: 'help', topic: 'memory export' };
3884
+ return { command: 'help', topic: 'app claim' };
3626
3885
  }
3627
- const parsed = parseMemoryCommonArgs(args);
3628
- let format = null;
3629
- let commitSha = null;
3886
+ const token = args[0];
3887
+ if (!token || token.startsWith('-')) {
3888
+ throw new TenderCliUsageError('share token is required. Example: tender app claim <token> --dir ./widget');
3889
+ }
3890
+ const parsed = parseShareCommonArgs(args, 1);
3891
+ let dir = null;
3892
+ let name = null;
3893
+ let confirm = null;
3630
3894
  for (let index = 0; index < parsed.remaining.length; index += 1) {
3631
3895
  const arg = parsed.remaining[index];
3632
- if (arg === '--format') {
3633
- const value = parseRequiredFlagValue(parsed.remaining[index + 1], '--format');
3634
- if (value !== 'jsonl') {
3635
- throw new TenderCliUsageError(`Unsupported memory export format: ${value}.\nNext: tender memory export --format jsonl --dir ${parsed.dir}`);
3636
- }
3637
- format = value;
3896
+ if (arg === '--dir') {
3897
+ dir = parseRequiredFlagValue(parsed.remaining[index + 1], '--dir');
3638
3898
  index += 1;
3639
3899
  continue;
3640
3900
  }
3641
- if (arg === '--commit') {
3642
- commitSha = parseRequiredFlagValue(parsed.remaining[index + 1], '--commit');
3901
+ if (arg === '--name') {
3902
+ name = parseRequiredFlagValue(parsed.remaining[index + 1], '--name');
3643
3903
  index += 1;
3644
3904
  continue;
3645
3905
  }
3646
- throw new TenderCliUsageError(`Unknown memory export option: ${arg}`);
3647
- }
3648
- if (format === null) {
3649
- throw new TenderCliUsageError(`memory export requires --format jsonl.\nNext: tender memory export --format jsonl --dir ${parsed.dir}`);
3650
- }
3651
- return { command: 'memory export', format, commitSha, ...parsed };
3652
- }
3653
- function parseTokenProfile(value) {
3654
- if (value === 'edit-preview' || value === 'publish' || value === 'create' || value === 'db-read') {
3655
- return value;
3656
- }
3657
- throw new TenderCliUsageError('--profile must be one of edit-preview, publish, create, or db-read.');
3658
- }
3659
- function parsePositiveIntegerFlag(value, flag) {
3660
- const parsed = Number(value);
3661
- if (!Number.isInteger(parsed) || parsed <= 0) {
3662
- throw new TenderCliUsageError(`${flag} requires a positive integer.`);
3906
+ if (arg === '--confirm') {
3907
+ confirm = parseRequiredFlagValue(parsed.remaining[index + 1], '--confirm');
3908
+ index += 1;
3909
+ continue;
3910
+ }
3911
+ throw new TenderCliUsageError(`Unknown app claim option: ${arg}`);
3663
3912
  }
3664
- return parsed;
3665
- }
3666
- function parseNonNegativeIntegerFlag(value, flag) {
3667
- const parsed = Number(value);
3668
- if (!Number.isInteger(parsed) || parsed < 0) {
3669
- throw new TenderCliUsageError(`${flag} requires a non-negative integer.`);
3913
+ if (!dir) {
3914
+ throw new TenderCliUsageError('--dir is required. Example: tender app claim tok_share_123 --dir ./widget');
3670
3915
  }
3671
- return parsed;
3916
+ return { command: 'app claim', token, dir, name, confirm, ...parsed };
3672
3917
  }
3673
- function parseAuthLoginArgs(args) {
3918
+ function parseArtifactsInitArgs(args) {
3674
3919
  if (args.includes('--help') || args.includes('-h')) {
3675
- return { command: 'help', topic: 'auth login' };
3920
+ return { command: 'help', topic: 'app init' };
3676
3921
  }
3677
- let device = false;
3922
+ const artifactId = args[0];
3923
+ if (!artifactId || artifactId.startsWith('-')) {
3924
+ throw new TenderCliUsageError('app id is required. Example: tender app init <app-id> --dir ./widget --json');
3925
+ }
3926
+ let dir = '.';
3927
+ let remoteName = 'tender';
3678
3928
  let baseUrl = null;
3679
- let tokenProfile = 'edit-preview';
3680
- let saveProfile = DEFAULT_PROFILE_NAME;
3681
- let artifactId = null;
3929
+ let flagToken = null;
3930
+ let profileName = null;
3682
3931
  let ttl = '7d';
3932
+ let scaffold = 'server-backed';
3933
+ let preview = false;
3934
+ let dryRun = false;
3935
+ let force = false;
3683
3936
  let json = false;
3684
- let noPoll = false;
3685
- let pollTimeoutSeconds = 600;
3686
- for (let index = 0; index < args.length; index += 1) {
3937
+ for (let index = 1; index < args.length; index += 1) {
3687
3938
  const arg = args[index];
3688
- if (arg === '--device') {
3689
- device = true;
3690
- continue;
3691
- }
3692
3939
  if (arg === '--json') {
3693
3940
  json = true;
3694
3941
  continue;
3695
3942
  }
3696
- if (arg === '--no-poll') {
3697
- noPoll = true;
3943
+ if (arg === '--dry-run') {
3944
+ dryRun = true;
3698
3945
  continue;
3699
3946
  }
3700
- if (arg === '--base-url') {
3701
- baseUrl = parseBaseUrlFlag(args, index);
3947
+ if (arg === '--force') {
3948
+ force = true;
3949
+ continue;
3950
+ }
3951
+ if (arg === '--preview') {
3952
+ preview = true;
3953
+ continue;
3954
+ }
3955
+ if (arg === '--no-scaffold') {
3956
+ scaffold = 'none';
3957
+ continue;
3958
+ }
3959
+ if (arg === '--dir') {
3960
+ const value = args[index + 1];
3961
+ if (!value || value.startsWith('-')) {
3962
+ throw new TenderCliUsageError('--dir requires a path.');
3963
+ }
3964
+ dir = value;
3702
3965
  index += 1;
3703
3966
  continue;
3704
3967
  }
3705
- if (arg === '--profile') {
3706
- tokenProfile = parseTokenProfile(args[index + 1]);
3968
+ if (arg === '--remote') {
3969
+ remoteName = parseRemoteName(args[index + 1]);
3707
3970
  index += 1;
3708
3971
  continue;
3709
3972
  }
3710
- if (arg === '--save-profile') {
3711
- saveProfile = parseProfileName(args[index + 1], '--save-profile');
3973
+ if (arg === '--base-url') {
3974
+ baseUrl = parseBaseUrlFlag(args, index);
3712
3975
  index += 1;
3713
3976
  continue;
3714
3977
  }
3715
- if (arg === '--artifact') {
3716
- artifactId = parseProfileName(args[index + 1], '--artifact');
3978
+ if (arg === '--token') {
3979
+ flagToken = parseTokenFlag(args, index);
3980
+ index += 1;
3981
+ continue;
3982
+ }
3983
+ if (arg === '--profile') {
3984
+ profileName = parseProfileName(args[index + 1], '--profile');
3717
3985
  index += 1;
3718
3986
  continue;
3719
3987
  }
@@ -3722,72 +3990,124 @@ function parseAuthLoginArgs(args) {
3722
3990
  index += 1;
3723
3991
  continue;
3724
3992
  }
3725
- if (arg === '--poll-timeout') {
3726
- pollTimeoutSeconds = parsePositiveIntegerFlag(args[index + 1], '--poll-timeout');
3993
+ if (arg === '--scaffold') {
3994
+ scaffold = parseScaffoldTemplate(args[index + 1]);
3727
3995
  index += 1;
3728
3996
  continue;
3729
3997
  }
3730
- throw new TenderCliUsageError(`Unknown auth login option: ${arg}`);
3731
- }
3732
- if (!device) {
3733
- throw new TenderCliUsageError('auth login currently requires --device. Example: tender auth login --device --profile edit-preview --artifact artifact_123');
3734
- }
3735
- if (tokenProfile === 'create' && artifactId) {
3736
- throw new TenderCliUsageError('--profile create must be account-scoped; remove --artifact.');
3737
- }
3738
- if (tokenProfile === 'db-read' && !artifactId) {
3739
- throw new TenderCliUsageError('--profile db-read requires --artifact <artifact-id>.');
3998
+ throw new TenderCliUsageError(`Unknown app init option: ${arg}`);
3740
3999
  }
3741
4000
  return {
3742
- command: 'auth login',
3743
- baseUrl,
3744
- device,
3745
- tokenProfile,
3746
- saveProfile,
4001
+ command: 'app init',
3747
4002
  artifactId,
4003
+ dir,
4004
+ remoteName,
4005
+ baseUrl,
4006
+ flagToken,
4007
+ profileName,
3748
4008
  ttl,
4009
+ scaffold,
4010
+ preview,
4011
+ dryRun,
4012
+ force,
3749
4013
  json,
3750
- noPoll,
3751
- pollTimeoutSeconds,
3752
4014
  };
3753
4015
  }
3754
- function parseAuthStatusArgs(args) {
4016
+ function parseContextFetchArgs(args) {
3755
4017
  if (args.includes('--help') || args.includes('-h')) {
3756
- return { command: 'help', topic: 'auth status' };
4018
+ return { command: 'help', topic: 'app context fetch' };
4019
+ }
4020
+ const artifactId = args[0];
4021
+ if (!artifactId || artifactId.startsWith('-')) {
4022
+ throw new TenderCliUsageError('app id is required. Example: tender app context fetch <app-id> --dir ./widget --json');
3757
4023
  }
4024
+ let dir = '.';
4025
+ let baseUrl = null;
4026
+ let flagToken = null;
3758
4027
  let profileName = null;
4028
+ let dryRun = false;
4029
+ let force = false;
3759
4030
  let json = false;
3760
- for (let index = 0; index < args.length; index += 1) {
4031
+ for (let index = 1; index < args.length; index += 1) {
3761
4032
  const arg = args[index];
3762
4033
  if (arg === '--json') {
3763
4034
  json = true;
3764
4035
  continue;
3765
4036
  }
4037
+ if (arg === '--dry-run') {
4038
+ dryRun = true;
4039
+ continue;
4040
+ }
4041
+ if (arg === '--force') {
4042
+ force = true;
4043
+ continue;
4044
+ }
4045
+ if (arg === '--dir') {
4046
+ const value = args[index + 1];
4047
+ if (!value || value.startsWith('-')) {
4048
+ throw new TenderCliUsageError('--dir requires a path.');
4049
+ }
4050
+ dir = value;
4051
+ index += 1;
4052
+ continue;
4053
+ }
4054
+ if (arg === '--base-url') {
4055
+ baseUrl = parseBaseUrlFlag(args, index);
4056
+ index += 1;
4057
+ continue;
4058
+ }
4059
+ if (arg === '--token') {
4060
+ flagToken = parseTokenFlag(args, index);
4061
+ index += 1;
4062
+ continue;
4063
+ }
3766
4064
  if (arg === '--profile') {
3767
4065
  profileName = parseProfileName(args[index + 1], '--profile');
3768
4066
  index += 1;
3769
4067
  continue;
3770
4068
  }
3771
- throw new TenderCliUsageError(`Unknown auth status option: ${arg}`);
4069
+ throw new TenderCliUsageError(`Unknown context fetch option: ${arg}`);
3772
4070
  }
3773
4071
  return {
3774
- command: 'auth status',
4072
+ command: 'app context fetch',
4073
+ artifactId,
4074
+ dir,
4075
+ baseUrl,
4076
+ flagToken,
3775
4077
  profileName,
4078
+ dryRun,
4079
+ force,
3776
4080
  json,
3777
4081
  };
3778
4082
  }
3779
- function parsePlaybooksCommonArgs(args, startIndex) {
4083
+ function parseContextStatusArgs(args) {
4084
+ if (args.includes('--help') || args.includes('-h')) {
4085
+ return { command: 'help', topic: 'app context status' };
4086
+ }
4087
+ const artifactId = args[0];
4088
+ if (!artifactId || artifactId.startsWith('-')) {
4089
+ throw new TenderCliUsageError('app id is required. Example: tender app context status <app-id> --dir ./widget --json');
4090
+ }
4091
+ let dir = '.';
3780
4092
  let baseUrl = null;
3781
4093
  let flagToken = null;
3782
4094
  let profileName = null;
3783
4095
  let json = false;
3784
- const extras = [];
3785
- for (let index = startIndex; index < args.length; index += 1) {
4096
+ for (let index = 1; index < args.length; index += 1) {
3786
4097
  const arg = args[index];
3787
4098
  if (arg === '--json') {
3788
4099
  json = true;
3789
4100
  continue;
3790
4101
  }
4102
+ if (arg === '--dir') {
4103
+ const value = args[index + 1];
4104
+ if (!value || value.startsWith('-')) {
4105
+ throw new TenderCliUsageError('--dir requires a path.');
4106
+ }
4107
+ dir = value;
4108
+ index += 1;
4109
+ continue;
4110
+ }
3791
4111
  if (arg === '--base-url') {
3792
4112
  baseUrl = parseBaseUrlFlag(args, index);
3793
4113
  index += 1;
@@ -3803,93 +4123,91 @@ function parsePlaybooksCommonArgs(args, startIndex) {
3803
4123
  index += 1;
3804
4124
  continue;
3805
4125
  }
3806
- extras.push(arg);
3807
- }
3808
- return { baseUrl, flagToken, profileName, json, extras };
3809
- }
3810
- function parsePlaybooksListArgs(args) {
3811
- if (args.includes('--help') || args.includes('-h')) {
3812
- return { command: 'help', topic: 'playbooks list' };
3813
- }
3814
- const parsed = parsePlaybooksCommonArgs(args, 0);
3815
- if (parsed.extras.length > 0) {
3816
- throw new TenderCliUsageError(`Unknown playbooks list option: ${parsed.extras[0]}`);
3817
- }
3818
- return {
3819
- command: 'playbooks list',
3820
- baseUrl: parsed.baseUrl,
3821
- flagToken: parsed.flagToken,
3822
- profileName: parsed.profileName,
3823
- json: parsed.json,
3824
- };
3825
- }
3826
- function parsePlaybooksGetArgs(args) {
3827
- if (args.includes('--help') || args.includes('-h')) {
3828
- return { command: 'help', topic: 'playbooks get' };
3829
- }
3830
- const playbookId = args[0];
3831
- if (!playbookId || playbookId.startsWith('-')) {
3832
- throw new TenderCliUsageError('playbook id is required. Example: tender playbooks get recharge-bundle-builder --json');
3833
- }
3834
- const parsed = parsePlaybooksCommonArgs(args, 1);
3835
- if (parsed.extras.length > 0) {
3836
- throw new TenderCliUsageError(`Unknown playbooks get option: ${parsed.extras[0]}`);
4126
+ throw new TenderCliUsageError(`Unknown context status option: ${arg}`);
3837
4127
  }
3838
4128
  return {
3839
- command: 'playbooks get',
3840
- playbookId,
3841
- baseUrl: parsed.baseUrl,
3842
- flagToken: parsed.flagToken,
3843
- profileName: parsed.profileName,
3844
- json: parsed.json,
4129
+ command: 'app context status',
4130
+ artifactId,
4131
+ dir,
4132
+ baseUrl,
4133
+ flagToken,
4134
+ profileName,
4135
+ json,
3845
4136
  };
3846
4137
  }
3847
- function parsePlaybooksFileArgs(args) {
4138
+ function parseContextRefreshArgs(args) {
3848
4139
  if (args.includes('--help') || args.includes('-h')) {
3849
- return { command: 'help', topic: 'playbooks file' };
4140
+ return { command: 'help', topic: 'app context refresh' };
3850
4141
  }
3851
- const playbookId = args[0];
3852
- if (!playbookId || playbookId.startsWith('-')) {
3853
- throw new TenderCliUsageError('playbook id is required. Example: tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json');
4142
+ const artifactId = args[0];
4143
+ if (!artifactId || artifactId.startsWith('-')) {
4144
+ throw new TenderCliUsageError('app id is required. Example: tender app context refresh <app-id> --dir ./widget --dry-run --json');
3854
4145
  }
3855
- let filePath = null;
3856
- const rest = [];
4146
+ let dir = '.';
4147
+ let baseUrl = null;
4148
+ let flagToken = null;
4149
+ let profileName = null;
4150
+ let dryRun = false;
4151
+ let force = false;
4152
+ let json = false;
3857
4153
  for (let index = 1; index < args.length; index += 1) {
3858
4154
  const arg = args[index];
3859
- if (arg === '--path') {
4155
+ if (arg === '--json') {
4156
+ json = true;
4157
+ continue;
4158
+ }
4159
+ if (arg === '--dry-run') {
4160
+ dryRun = true;
4161
+ continue;
4162
+ }
4163
+ if (arg === '--force') {
4164
+ force = true;
4165
+ continue;
4166
+ }
4167
+ if (arg === '--dir') {
3860
4168
  const value = args[index + 1];
3861
4169
  if (!value || value.startsWith('-')) {
3862
- throw new TenderCliUsageError('--path requires a playbook reference path.');
4170
+ throw new TenderCliUsageError('--dir requires a path.');
3863
4171
  }
3864
- filePath = value;
4172
+ dir = value;
3865
4173
  index += 1;
3866
4174
  continue;
3867
4175
  }
3868
- rest.push(arg);
3869
- }
3870
- const parsed = parsePlaybooksCommonArgs(rest, 0);
3871
- if (parsed.extras.length > 0) {
3872
- throw new TenderCliUsageError(`Unknown playbooks file option: ${parsed.extras[0]}`);
3873
- }
3874
- if (!filePath) {
3875
- throw new TenderCliUsageError('--path is required. Example: tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json');
4176
+ if (arg === '--base-url') {
4177
+ baseUrl = parseBaseUrlFlag(args, index);
4178
+ index += 1;
4179
+ continue;
4180
+ }
4181
+ if (arg === '--token') {
4182
+ flagToken = parseTokenFlag(args, index);
4183
+ index += 1;
4184
+ continue;
4185
+ }
4186
+ if (arg === '--profile') {
4187
+ profileName = parseProfileName(args[index + 1], '--profile');
4188
+ index += 1;
4189
+ continue;
4190
+ }
4191
+ throw new TenderCliUsageError(`Unknown context refresh option: ${arg}`);
3876
4192
  }
3877
4193
  return {
3878
- command: 'playbooks file',
3879
- playbookId,
3880
- filePath,
3881
- baseUrl: parsed.baseUrl,
3882
- flagToken: parsed.flagToken,
3883
- profileName: parsed.profileName,
3884
- json: parsed.json,
4194
+ command: 'app context refresh',
4195
+ artifactId,
4196
+ dir,
4197
+ baseUrl,
4198
+ flagToken,
4199
+ profileName,
4200
+ dryRun,
4201
+ force,
4202
+ json,
3885
4203
  };
3886
4204
  }
3887
- function parseGenerateEnvTypesArgs(args) {
3888
- if (args.includes('--help') || args.includes('-h')) {
3889
- return { command: 'help', topic: 'app generate-env-types' };
3890
- }
4205
+ function parseMemoryCommonArgs(args) {
3891
4206
  let dir = '.';
4207
+ let artifactId = null;
4208
+ let remoteName = 'tender';
3892
4209
  let json = false;
4210
+ const remaining = [];
3893
4211
  for (let index = 0; index < args.length; index += 1) {
3894
4212
  const arg = args[index];
3895
4213
  if (arg === '--json') {
@@ -3897,187 +4215,320 @@ function parseGenerateEnvTypesArgs(args) {
3897
4215
  continue;
3898
4216
  }
3899
4217
  if (arg === '--dir') {
3900
- const value = args[index + 1];
3901
- if (!value) {
3902
- throw new TenderCliUsageError('--dir requires a path.');
3903
- }
3904
- dir = value;
4218
+ dir = parseRequiredFlagValue(args[index + 1], '--dir');
3905
4219
  index += 1;
3906
4220
  continue;
3907
4221
  }
3908
- throw new TenderCliUsageError(`Unknown generate-env-types option: ${arg}`);
4222
+ if (arg === '--artifact') {
4223
+ artifactId = parseRequiredFlagValue(args[index + 1], '--artifact');
4224
+ index += 1;
4225
+ continue;
4226
+ }
4227
+ if (arg === '--remote') {
4228
+ remoteName = parseRemoteName(args[index + 1]);
4229
+ index += 1;
4230
+ continue;
4231
+ }
4232
+ remaining.push(arg);
3909
4233
  }
3910
- return {
3911
- command: 'app generate-env-types',
3912
- dir,
3913
- json,
3914
- };
4234
+ return { dir, artifactId, remoteName, json, remaining };
3915
4235
  }
3916
- function parseAnalyticsRange(value) {
3917
- if (!value || value.startsWith('-')) {
3918
- throw new TenderCliUsageError('--range requires 7d, 30d, 90d, or YYYY-MM-DD..YYYY-MM-DD.');
4236
+ function parseMemoryStartArgs(args) {
4237
+ if (args.includes('--help') || args.includes('-h')) {
4238
+ return { command: 'help', topic: 'memory start' };
3919
4239
  }
3920
- if (value === '7d' || value === '30d' || value === '90d') {
3921
- return value;
4240
+ const parsed = parseMemoryCommonArgs(args);
4241
+ let summary = '';
4242
+ let body = null;
4243
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
4244
+ const arg = parsed.remaining[index];
4245
+ if (arg === '--summary') {
4246
+ summary = parseRequiredFlagValue(parsed.remaining[index + 1], '--summary');
4247
+ index += 1;
4248
+ continue;
4249
+ }
4250
+ if (arg === '--body') {
4251
+ body = parseRequiredFlagValue(parsed.remaining[index + 1], '--body');
4252
+ index += 1;
4253
+ continue;
4254
+ }
4255
+ throw new TenderCliUsageError(`Unknown memory start option: ${arg}`);
3922
4256
  }
3923
- if (/^\d{4}-\d{2}-\d{2}\.\.\d{4}-\d{2}-\d{2}$/.test(value)) {
3924
- return value;
4257
+ if (!summary.trim()) {
4258
+ throw new TenderCliUsageError('memory start requires --summary.\nNext: tender memory start --summary "Current user goal"');
3925
4259
  }
3926
- throw new TenderCliUsageError('--range must be 7d, 30d, 90d, or YYYY-MM-DD..YYYY-MM-DD.');
4260
+ return { command: 'memory start', summary, body, ...parsed };
3927
4261
  }
3928
- function parseSpecPath(value) {
3929
- if (!value || (value !== '-' && value.startsWith('-'))) {
3930
- throw new TenderCliUsageError('--spec requires a JSON file path or - for stdin.');
4262
+ function parseMemoryAddArgs(args) {
4263
+ if (args.includes('--help') || args.includes('-h')) {
4264
+ return { command: 'help', topic: 'memory add' };
3931
4265
  }
3932
- return value;
3933
- }
3934
- function parseRequiredFlagValue(value, flag) {
3935
- if (!value || value.startsWith('-')) {
3936
- throw new TenderCliUsageError(`${flag} requires a value.`);
4266
+ const parsed = parseMemoryCommonArgs(args);
4267
+ let kind = 'note';
4268
+ let summary = '';
4269
+ let body = null;
4270
+ const tags = [];
4271
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
4272
+ const arg = parsed.remaining[index];
4273
+ if (arg === '--kind') {
4274
+ kind = parseRequiredFlagValue(parsed.remaining[index + 1], '--kind');
4275
+ index += 1;
4276
+ continue;
4277
+ }
4278
+ if (arg === '--summary') {
4279
+ summary = parseRequiredFlagValue(parsed.remaining[index + 1], '--summary');
4280
+ index += 1;
4281
+ continue;
4282
+ }
4283
+ if (arg === '--body') {
4284
+ body = parseRequiredFlagValue(parsed.remaining[index + 1], '--body');
4285
+ index += 1;
4286
+ continue;
4287
+ }
4288
+ if (arg === '--tag') {
4289
+ tags.push(parseRequiredFlagValue(parsed.remaining[index + 1], '--tag'));
4290
+ index += 1;
4291
+ continue;
4292
+ }
4293
+ throw new TenderCliUsageError(`Unknown memory add option: ${arg}`);
3937
4294
  }
3938
- return value;
4295
+ return { command: 'memory add', kind, summary, body, tags, ...parsed };
3939
4296
  }
3940
- function parseCapabilitiesArgs(args) {
4297
+ function parseMemoryHandoffArgs(args) {
3941
4298
  if (args.includes('--help') || args.includes('-h')) {
3942
- return { command: 'help', topic: 'capabilities' };
4299
+ return { command: 'help', topic: 'memory handoff' };
3943
4300
  }
3944
- let json = false;
3945
- for (const arg of args) {
3946
- if (arg === '--json') {
3947
- json = true;
4301
+ const parsed = parseMemoryCommonArgs(args);
4302
+ let reason = null;
4303
+ let summary = null;
4304
+ let body = null;
4305
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
4306
+ const arg = parsed.remaining[index];
4307
+ if (arg === '--reason') {
4308
+ reason = parseRequiredFlagValue(parsed.remaining[index + 1], '--reason');
4309
+ index += 1;
3948
4310
  continue;
3949
4311
  }
3950
- throw new TenderCliUsageError(`Unknown capabilities option: ${arg}`);
4312
+ if (arg === '--summary') {
4313
+ summary = parseRequiredFlagValue(parsed.remaining[index + 1], '--summary');
4314
+ index += 1;
4315
+ continue;
4316
+ }
4317
+ if (arg === '--body') {
4318
+ body = parseRequiredFlagValue(parsed.remaining[index + 1], '--body');
4319
+ index += 1;
4320
+ continue;
4321
+ }
4322
+ throw new TenderCliUsageError(`Unknown memory handoff option: ${arg}`);
3951
4323
  }
3952
- return { command: 'capabilities', json };
4324
+ return { command: 'memory handoff', reason, summary, body, ...parsed };
3953
4325
  }
3954
- function parseVersionArgs(args) {
4326
+ function parseMemoryShowArgs(args) {
3955
4327
  if (args.includes('--help') || args.includes('-h')) {
3956
- return { command: 'help', topic: 'version' };
4328
+ return { command: 'help', topic: 'memory show' };
3957
4329
  }
3958
- let json = false;
3959
- for (const arg of args) {
3960
- if (arg === '--json') {
3961
- json = true;
4330
+ const parsed = parseMemoryCommonArgs(args);
4331
+ let commitSha = null;
4332
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
4333
+ const arg = parsed.remaining[index];
4334
+ if (arg === '--commit') {
4335
+ commitSha = parseRequiredFlagValue(parsed.remaining[index + 1], '--commit');
4336
+ index += 1;
3962
4337
  continue;
3963
4338
  }
3964
- throw new TenderCliUsageError(`Unknown version option: ${arg}`);
4339
+ throw new TenderCliUsageError(`Unknown memory show option: ${arg}`);
3965
4340
  }
3966
- return { command: 'version', json };
4341
+ return { command: 'memory show', commitSha, ...parsed };
3967
4342
  }
3968
- function parseBindingId(value) {
3969
- if (!value || value.startsWith('-')) {
3970
- throw new TenderCliUsageError('--id requires a binding id.');
4343
+ function parseMemoryBriefArgs(args) {
4344
+ if (args.includes('--help') || args.includes('-h')) {
4345
+ return { command: 'help', topic: 'memory brief' };
3971
4346
  }
3972
- if (!/^[a-z][a-z0-9-]*$/.test(value)) {
3973
- throw new TenderCliUsageError('--id must start with a lowercase letter and contain only lowercase letters, numbers, and dashes. Example: shopify-api');
4347
+ const parsed = parseMemoryCommonArgs(args);
4348
+ let limit = 12;
4349
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
4350
+ const arg = parsed.remaining[index];
4351
+ if (arg === '--limit') {
4352
+ limit = parsePositiveIntegerFlag(parsed.remaining[index + 1], '--limit');
4353
+ index += 1;
4354
+ continue;
4355
+ }
4356
+ throw new TenderCliUsageError(`Unknown memory brief option: ${arg}`);
3974
4357
  }
3975
- return value;
4358
+ return { command: 'memory brief', limit, ...parsed };
3976
4359
  }
3977
- function parseAllowedHost(value) {
3978
- if (!value || value.startsWith('-')) {
3979
- throw new TenderCliUsageError('--host requires a hostname.');
4360
+ function parseMemoryFlushArgs(args) {
4361
+ if (args.includes('--help') || args.includes('-h')) {
4362
+ return { command: 'help', topic: 'memory flush' };
3980
4363
  }
3981
- if (value.length === 0 ||
3982
- value.includes('/') ||
3983
- value.includes(':') ||
3984
- value.includes('?') ||
3985
- value.includes('#') ||
3986
- value.includes('*') ||
3987
- value !== value.toLowerCase()) {
3988
- throw new TenderCliUsageError('--host must be an explicit lowercase hostname without protocol, wildcard, path, query, or port. Example: api.example.com');
4364
+ const parsed = parseMemoryCommonArgs(args);
4365
+ if (parsed.remaining.length > 0) {
4366
+ throw new TenderCliUsageError(`Unknown memory flush option: ${parsed.remaining[0]}`);
3989
4367
  }
3990
- return value;
4368
+ return { command: 'memory flush', ...parsed };
3991
4369
  }
3992
- function parseEnvBindingName(value) {
3993
- if (!value || value.startsWith('-')) {
3994
- throw new TenderCliUsageError('--name requires an env binding name.');
4370
+ function parseMemoryExportArgs(args) {
4371
+ if (args.includes('--help') || args.includes('-h')) {
4372
+ return { command: 'help', topic: 'memory export' };
3995
4373
  }
3996
- if (!/^[A-Z][A-Z0-9_]*$/.test(value)) {
3997
- throw new TenderCliUsageError('--name must be UPPER_SNAKE_CASE. Example: SHOPIFY_API');
4374
+ const parsed = parseMemoryCommonArgs(args);
4375
+ let format = null;
4376
+ let commitSha = null;
4377
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
4378
+ const arg = parsed.remaining[index];
4379
+ if (arg === '--format') {
4380
+ const value = parseRequiredFlagValue(parsed.remaining[index + 1], '--format');
4381
+ if (value !== 'jsonl') {
4382
+ throw new TenderCliUsageError(`Unsupported memory export format: ${value}.\nNext: tender memory export --format jsonl --dir ${parsed.dir}`);
4383
+ }
4384
+ format = value;
4385
+ index += 1;
4386
+ continue;
4387
+ }
4388
+ if (arg === '--commit') {
4389
+ commitSha = parseRequiredFlagValue(parsed.remaining[index + 1], '--commit');
4390
+ index += 1;
4391
+ continue;
4392
+ }
4393
+ throw new TenderCliUsageError(`Unknown memory export option: ${arg}`);
3998
4394
  }
3999
- return value;
4395
+ if (format === null) {
4396
+ throw new TenderCliUsageError(`memory export requires --format jsonl.\nNext: tender memory export --format jsonl --dir ${parsed.dir}`);
4397
+ }
4398
+ return { command: 'memory export', format, commitSha, ...parsed };
4000
4399
  }
4001
- function parseTypeName(value, flag) {
4002
- if (!value || value.startsWith('-')) {
4003
- throw new TenderCliUsageError(`${flag} requires a TypeScript type name.`);
4400
+ function parseTokenProfile(value) {
4401
+ if (value === 'edit-preview' || value === 'publish' || value === 'create' || value === 'db-read') {
4402
+ return value;
4004
4403
  }
4005
- if (!/^[A-Z][A-Za-z0-9]*$/.test(value)) {
4006
- throw new TenderCliUsageError(`${flag} must be a PascalCase TypeScript type name. Example: ShopifyApiBinding`);
4404
+ throw new TenderCliUsageError('--profile must be one of edit-preview, publish, create, or db-read.');
4405
+ }
4406
+ function parsePositiveIntegerFlag(value, flag) {
4407
+ const parsed = Number(value);
4408
+ if (!Number.isInteger(parsed) || parsed <= 0) {
4409
+ throw new TenderCliUsageError(`${flag} requires a positive integer.`);
4007
4410
  }
4008
- return value;
4411
+ return parsed;
4009
4412
  }
4010
- function parseBindingsOutboundArgs(args) {
4413
+ function parseNonNegativeIntegerFlag(value, flag) {
4414
+ const parsed = Number(value);
4415
+ if (!Number.isInteger(parsed) || parsed < 0) {
4416
+ throw new TenderCliUsageError(`${flag} requires a non-negative integer.`);
4417
+ }
4418
+ return parsed;
4419
+ }
4420
+ function parseAuthLoginArgs(args) {
4011
4421
  if (args.includes('--help') || args.includes('-h')) {
4012
- return { command: 'help', topic: 'app bindings outbound' };
4422
+ return { command: 'help', topic: 'auth login' };
4013
4423
  }
4014
- let id = null;
4015
- let host = null;
4016
- let name = null;
4017
- let label = null;
4018
- let interfaceName = null;
4019
- let dryRun = false;
4424
+ let device = false;
4425
+ let baseUrl = null;
4426
+ let tokenProfile = 'edit-preview';
4427
+ let saveProfile = DEFAULT_PROFILE_NAME;
4428
+ let artifactId = null;
4429
+ let ttl = '7d';
4020
4430
  let json = false;
4431
+ let noPoll = false;
4432
+ let pollTimeoutSeconds = 600;
4021
4433
  for (let index = 0; index < args.length; index += 1) {
4022
4434
  const arg = args[index];
4435
+ if (arg === '--device') {
4436
+ device = true;
4437
+ continue;
4438
+ }
4023
4439
  if (arg === '--json') {
4024
4440
  json = true;
4025
4441
  continue;
4026
4442
  }
4027
- if (arg === '--dry-run') {
4028
- dryRun = true;
4443
+ if (arg === '--no-poll') {
4444
+ noPoll = true;
4029
4445
  continue;
4030
4446
  }
4031
- if (arg === '--id') {
4032
- id = parseBindingId(args[index + 1]);
4447
+ if (arg === '--base-url') {
4448
+ baseUrl = parseBaseUrlFlag(args, index);
4033
4449
  index += 1;
4034
4450
  continue;
4035
4451
  }
4036
- if (arg === '--host') {
4037
- host = parseAllowedHost(args[index + 1]);
4452
+ if (arg === '--profile') {
4453
+ tokenProfile = parseTokenProfile(args[index + 1]);
4038
4454
  index += 1;
4039
4455
  continue;
4040
4456
  }
4041
- if (arg === '--name') {
4042
- name = parseEnvBindingName(args[index + 1]);
4457
+ if (arg === '--save-profile') {
4458
+ saveProfile = parseProfileName(args[index + 1], '--save-profile');
4043
4459
  index += 1;
4044
4460
  continue;
4045
4461
  }
4046
- if (arg === '--label') {
4047
- label = parseRequiredFlagValue(args[index + 1], '--label');
4462
+ if (arg === '--artifact') {
4463
+ artifactId = parseProfileName(args[index + 1], '--artifact');
4048
4464
  index += 1;
4049
4465
  continue;
4050
4466
  }
4051
- if (arg === '--interface') {
4052
- interfaceName = parseTypeName(args[index + 1], '--interface');
4467
+ if (arg === '--ttl') {
4468
+ ttl = parseTtl(args[index + 1]);
4053
4469
  index += 1;
4054
4470
  continue;
4055
4471
  }
4056
- throw new TenderCliUsageError(`Unknown bindings outbound option: ${arg}`);
4472
+ if (arg === '--poll-timeout') {
4473
+ pollTimeoutSeconds = parsePositiveIntegerFlag(args[index + 1], '--poll-timeout');
4474
+ index += 1;
4475
+ continue;
4476
+ }
4477
+ throw new TenderCliUsageError(`Unknown auth login option: ${arg}`);
4057
4478
  }
4058
- if (!id || !host) {
4059
- throw new TenderCliUsageError('--id and --host are required. Example: tender app bindings outbound --id shopify --host admin.shopify.com --dry-run --json');
4479
+ if (!device) {
4480
+ throw new TenderCliUsageError('auth login currently requires --device. Example: tender auth login --device --profile edit-preview --artifact artifact_123');
4060
4481
  }
4061
- if (!dryRun) {
4062
- throw new TenderCliUsageError('bindings outbound currently requires --dry-run. Example: tender app bindings outbound --id shopify --host admin.shopify.com --dry-run --json');
4482
+ if (tokenProfile === 'create' && artifactId) {
4483
+ throw new TenderCliUsageError('--profile create must be account-scoped; remove --artifact.');
4484
+ }
4485
+ if (tokenProfile === 'db-read' && !artifactId) {
4486
+ throw new TenderCliUsageError('--profile db-read requires --artifact <artifact-id>.');
4063
4487
  }
4064
4488
  return {
4065
- command: 'app bindings outbound',
4066
- id,
4067
- host,
4068
- name,
4069
- label,
4070
- interfaceName,
4071
- dryRun,
4489
+ command: 'auth login',
4490
+ baseUrl,
4491
+ device,
4492
+ tokenProfile,
4493
+ saveProfile,
4494
+ artifactId,
4495
+ ttl,
4072
4496
  json,
4497
+ noPoll,
4498
+ pollTimeoutSeconds,
4073
4499
  };
4074
4500
  }
4075
- function parseAnalyticsCommonArgs(args, startIndex) {
4501
+ function parseAuthStatusArgs(args) {
4502
+ if (args.includes('--help') || args.includes('-h')) {
4503
+ return { command: 'help', topic: 'auth status' };
4504
+ }
4505
+ let profileName = null;
4506
+ let json = false;
4507
+ for (let index = 0; index < args.length; index += 1) {
4508
+ const arg = args[index];
4509
+ if (arg === '--json') {
4510
+ json = true;
4511
+ continue;
4512
+ }
4513
+ if (arg === '--profile') {
4514
+ profileName = parseProfileName(args[index + 1], '--profile');
4515
+ index += 1;
4516
+ continue;
4517
+ }
4518
+ throw new TenderCliUsageError(`Unknown auth status option: ${arg}`);
4519
+ }
4520
+ return {
4521
+ command: 'auth status',
4522
+ profileName,
4523
+ json,
4524
+ };
4525
+ }
4526
+ function parsePlaybooksCommonArgs(args, startIndex) {
4076
4527
  let baseUrl = null;
4077
4528
  let flagToken = null;
4078
4529
  let profileName = null;
4079
4530
  let json = false;
4080
- const remaining = [];
4531
+ const extras = [];
4081
4532
  for (let index = startIndex; index < args.length; index += 1) {
4082
4533
  const arg = args[index];
4083
4534
  if (arg === '--json') {
@@ -4099,90 +4550,143 @@ function parseAnalyticsCommonArgs(args, startIndex) {
4099
4550
  index += 1;
4100
4551
  continue;
4101
4552
  }
4102
- remaining.push(arg);
4553
+ extras.push(arg);
4103
4554
  }
4104
- return { baseUrl, flagToken, profileName, json, remaining };
4555
+ return { baseUrl, flagToken, profileName, json, extras };
4105
4556
  }
4106
- function parseAnalyticsSummaryArgs(args) {
4557
+ function parsePlaybooksListArgs(args) {
4107
4558
  if (args.includes('--help') || args.includes('-h')) {
4108
- return { command: 'help', topic: 'app analytics summary' };
4559
+ return { command: 'help', topic: 'playbooks list' };
4109
4560
  }
4110
- const artifactId = args[0];
4111
- if (!artifactId || artifactId.startsWith('-')) {
4112
- throw new TenderCliUsageError('app id is required. Example: tender app analytics summary <app-id> --range 30d --json');
4561
+ const parsed = parsePlaybooksCommonArgs(args, 0);
4562
+ if (parsed.extras.length > 0) {
4563
+ throw new TenderCliUsageError(`Unknown playbooks list option: ${parsed.extras[0]}`);
4113
4564
  }
4114
- const parsed = parseAnalyticsCommonArgs(args, 1);
4115
- let range = '7d';
4116
- for (let index = 0; index < parsed.remaining.length; index += 1) {
4117
- const arg = parsed.remaining[index];
4118
- if (arg === '--range') {
4119
- range = parseAnalyticsRange(parsed.remaining[index + 1]);
4120
- index += 1;
4121
- continue;
4122
- }
4123
- throw new TenderCliUsageError(`Unknown analytics summary option: ${arg}`);
4565
+ return {
4566
+ command: 'playbooks list',
4567
+ baseUrl: parsed.baseUrl,
4568
+ flagToken: parsed.flagToken,
4569
+ profileName: parsed.profileName,
4570
+ json: parsed.json,
4571
+ };
4572
+ }
4573
+ function parsePlaybooksGetArgs(args) {
4574
+ if (args.includes('--help') || args.includes('-h')) {
4575
+ return { command: 'help', topic: 'playbooks get' };
4124
4576
  }
4125
- return { command: 'app analytics summary', artifactId, range, ...parsed };
4577
+ const playbookId = args[0];
4578
+ if (!playbookId || playbookId.startsWith('-')) {
4579
+ throw new TenderCliUsageError('playbook id is required. Example: tender playbooks get recharge-bundle-builder --json');
4580
+ }
4581
+ const parsed = parsePlaybooksCommonArgs(args, 1);
4582
+ if (parsed.extras.length > 0) {
4583
+ throw new TenderCliUsageError(`Unknown playbooks get option: ${parsed.extras[0]}`);
4584
+ }
4585
+ return {
4586
+ command: 'playbooks get',
4587
+ playbookId,
4588
+ baseUrl: parsed.baseUrl,
4589
+ flagToken: parsed.flagToken,
4590
+ profileName: parsed.profileName,
4591
+ json: parsed.json,
4592
+ };
4126
4593
  }
4127
- function parseAnalyticsQueryArgs(args) {
4594
+ function parsePlaybooksFileArgs(args) {
4128
4595
  if (args.includes('--help') || args.includes('-h')) {
4129
- return { command: 'help', topic: 'app analytics query' };
4596
+ return { command: 'help', topic: 'playbooks file' };
4130
4597
  }
4131
- const artifactId = args[0];
4132
- if (!artifactId || artifactId.startsWith('-')) {
4133
- throw new TenderCliUsageError('app id is required. Example: tender app analytics query <app-id> --spec chart.json --json');
4598
+ const playbookId = args[0];
4599
+ if (!playbookId || playbookId.startsWith('-')) {
4600
+ throw new TenderCliUsageError('playbook id is required. Example: tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json');
4134
4601
  }
4135
- const parsed = parseAnalyticsCommonArgs(args, 1);
4136
- let specPath = null;
4137
- for (let index = 0; index < parsed.remaining.length; index += 1) {
4138
- const arg = parsed.remaining[index];
4139
- if (arg === '--spec') {
4140
- specPath = parseSpecPath(parsed.remaining[index + 1]);
4602
+ let filePath = null;
4603
+ const rest = [];
4604
+ for (let index = 1; index < args.length; index += 1) {
4605
+ const arg = args[index];
4606
+ if (arg === '--path') {
4607
+ const value = args[index + 1];
4608
+ if (!value || value.startsWith('-')) {
4609
+ throw new TenderCliUsageError('--path requires a playbook reference path.');
4610
+ }
4611
+ filePath = value;
4141
4612
  index += 1;
4142
4613
  continue;
4143
4614
  }
4144
- throw new TenderCliUsageError(`Unknown analytics query option: ${arg}`);
4615
+ rest.push(arg);
4145
4616
  }
4146
- if (!specPath) {
4147
- throw new TenderCliUsageError('--spec is required. Example: tender app analytics query artifact_123 --spec chart.json --json');
4617
+ const parsed = parsePlaybooksCommonArgs(rest, 0);
4618
+ if (parsed.extras.length > 0) {
4619
+ throw new TenderCliUsageError(`Unknown playbooks file option: ${parsed.extras[0]}`);
4148
4620
  }
4149
- return { command: 'app analytics query', artifactId, specPath, ...parsed };
4621
+ if (!filePath) {
4622
+ throw new TenderCliUsageError('--path is required. Example: tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json');
4623
+ }
4624
+ return {
4625
+ command: 'playbooks file',
4626
+ playbookId,
4627
+ filePath,
4628
+ baseUrl: parsed.baseUrl,
4629
+ flagToken: parsed.flagToken,
4630
+ profileName: parsed.profileName,
4631
+ json: parsed.json,
4632
+ };
4150
4633
  }
4151
- function parseAnalyticsCapabilitiesArgs(args) {
4634
+ function parseGenerateEnvTypesArgs(args) {
4152
4635
  if (args.includes('--help') || args.includes('-h')) {
4153
- return { command: 'help', topic: 'app analytics capabilities' };
4154
- }
4155
- const artifactId = args[0];
4156
- if (!artifactId || artifactId.startsWith('-')) {
4157
- throw new TenderCliUsageError('app id is required. Example: tender app analytics capabilities <app-id> --include-catalog --json');
4636
+ return { command: 'help', topic: 'app generate-env-types' };
4158
4637
  }
4159
- const parsed = parseAnalyticsCommonArgs(args, 1);
4160
- let range = '7d';
4161
- let includeCatalog = false;
4162
- for (let index = 0; index < parsed.remaining.length; index += 1) {
4163
- const arg = parsed.remaining[index];
4164
- if (arg === '--include-catalog') {
4165
- includeCatalog = true;
4638
+ let dir = '.';
4639
+ let json = false;
4640
+ for (let index = 0; index < args.length; index += 1) {
4641
+ const arg = args[index];
4642
+ if (arg === '--json') {
4643
+ json = true;
4166
4644
  continue;
4167
4645
  }
4168
- if (arg === '--range') {
4169
- range = parseAnalyticsRange(parsed.remaining[index + 1]);
4646
+ if (arg === '--dir') {
4647
+ const value = args[index + 1];
4648
+ if (!value) {
4649
+ throw new TenderCliUsageError('--dir requires a path.');
4650
+ }
4651
+ dir = value;
4170
4652
  index += 1;
4171
4653
  continue;
4172
4654
  }
4173
- throw new TenderCliUsageError(`Unknown analytics capabilities option: ${arg}`);
4655
+ throw new TenderCliUsageError(`Unknown generate-env-types option: ${arg}`);
4174
4656
  }
4175
4657
  return {
4176
- command: 'app analytics capabilities',
4177
- artifactId,
4178
- includeCatalog,
4179
- range,
4180
- ...parsed,
4658
+ command: 'app generate-env-types',
4659
+ dir,
4660
+ json,
4181
4661
  };
4182
4662
  }
4183
- function parseAnalyticsAuthoringArgs(args) {
4663
+ function parseAnalyticsRange(value) {
4664
+ if (!value || value.startsWith('-')) {
4665
+ throw new TenderCliUsageError('--range requires 7d, 30d, 90d, or YYYY-MM-DD..YYYY-MM-DD.');
4666
+ }
4667
+ if (value === '7d' || value === '30d' || value === '90d') {
4668
+ return value;
4669
+ }
4670
+ if (/^\d{4}-\d{2}-\d{2}\.\.\d{4}-\d{2}-\d{2}$/.test(value)) {
4671
+ return value;
4672
+ }
4673
+ throw new TenderCliUsageError('--range must be 7d, 30d, 90d, or YYYY-MM-DD..YYYY-MM-DD.');
4674
+ }
4675
+ function parseSpecPath(value) {
4676
+ if (!value || (value !== '-' && value.startsWith('-'))) {
4677
+ throw new TenderCliUsageError('--spec requires a JSON file path or - for stdin.');
4678
+ }
4679
+ return value;
4680
+ }
4681
+ function parseRequiredFlagValue(value, flag) {
4682
+ if (!value || value.startsWith('-')) {
4683
+ throw new TenderCliUsageError(`${flag} requires a value.`);
4684
+ }
4685
+ return value;
4686
+ }
4687
+ function parseCapabilitiesArgs(args) {
4184
4688
  if (args.includes('--help') || args.includes('-h')) {
4185
- return { command: 'help', topic: 'app analytics authoring' };
4689
+ return { command: 'help', topic: 'capabilities' };
4186
4690
  }
4187
4691
  let json = false;
4188
4692
  for (const arg of args) {
@@ -4190,49 +4694,292 @@ function parseAnalyticsAuthoringArgs(args) {
4190
4694
  json = true;
4191
4695
  continue;
4192
4696
  }
4193
- throw new TenderCliUsageError(`Unknown analytics authoring option: ${arg}`);
4697
+ throw new TenderCliUsageError(`Unknown capabilities option: ${arg}`);
4194
4698
  }
4195
- return { command: 'app analytics authoring', json };
4699
+ return { command: 'capabilities', json };
4196
4700
  }
4197
- function parseAnalyticsSuggestionsArgs(args) {
4701
+ function parseVersionArgs(args) {
4198
4702
  if (args.includes('--help') || args.includes('-h')) {
4199
- return { command: 'help', topic: 'app analytics suggestions' };
4200
- }
4201
- const artifactId = args[0];
4202
- if (!artifactId || artifactId.startsWith('-')) {
4203
- throw new TenderCliUsageError('app id is required. Example: tender app analytics suggestions <app-id> --range 30d --json');
4703
+ return { command: 'help', topic: 'version' };
4204
4704
  }
4205
- const parsed = parseAnalyticsCommonArgs(args, 1);
4206
- let range = '7d';
4207
- for (let index = 0; index < parsed.remaining.length; index += 1) {
4208
- const arg = parsed.remaining[index];
4209
- if (arg === '--range') {
4210
- range = parseAnalyticsRange(parsed.remaining[index + 1]);
4211
- index += 1;
4705
+ let json = false;
4706
+ for (const arg of args) {
4707
+ if (arg === '--json') {
4708
+ json = true;
4212
4709
  continue;
4213
4710
  }
4214
- throw new TenderCliUsageError(`Unknown analytics suggestions option: ${arg}`);
4711
+ throw new TenderCliUsageError(`Unknown version option: ${arg}`);
4215
4712
  }
4216
- return { command: 'app analytics suggestions', artifactId, range, ...parsed };
4713
+ return { command: 'version', json };
4217
4714
  }
4218
- function parseAnalyticsSteps(value) {
4219
- const raw = parseRequiredFlagValue(value, '--steps');
4220
- const steps = raw
4221
- .split(',')
4222
- .map((step) => step.trim())
4223
- .filter((step) => step.length > 0);
4224
- if (steps.length < 2 || steps.length > 25 || new Set(steps).size !== steps.length) {
4225
- throw new TenderCliUsageError('--steps must contain 2-25 unique comma-separated flow steps.');
4715
+ function parseBindingId(value) {
4716
+ if (!value || value.startsWith('-')) {
4717
+ throw new TenderCliUsageError('--id requires a binding id.');
4226
4718
  }
4227
- return steps;
4719
+ if (!/^[a-z][a-z0-9-]*$/.test(value)) {
4720
+ throw new TenderCliUsageError('--id must start with a lowercase letter and contain only lowercase letters, numbers, and dashes. Example: shopify-api');
4721
+ }
4722
+ return value;
4228
4723
  }
4229
- function parseAnalyticsExactFunnelArgs(args) {
4230
- if (args.includes('--help') || args.includes('-h')) {
4231
- return { command: 'help', topic: 'app analytics exact-funnel' };
4724
+ function parseAllowedHost(value) {
4725
+ if (!value || value.startsWith('-')) {
4726
+ throw new TenderCliUsageError('--host requires a hostname.');
4232
4727
  }
4233
- const artifactId = args[0];
4234
- if (!artifactId || artifactId.startsWith('-')) {
4235
- throw new TenderCliUsageError('app id is required. Example: tender app analytics exact-funnel <app-id> --flow checkout_flow --steps started,completed --range 30d --json');
4728
+ if (value.length === 0 ||
4729
+ value.includes('/') ||
4730
+ value.includes(':') ||
4731
+ value.includes('?') ||
4732
+ value.includes('#') ||
4733
+ value.includes('*') ||
4734
+ value !== value.toLowerCase()) {
4735
+ throw new TenderCliUsageError('--host must be an explicit lowercase hostname without protocol, wildcard, path, query, or port. Example: api.example.com');
4736
+ }
4737
+ return value;
4738
+ }
4739
+ function parseEnvBindingName(value) {
4740
+ if (!value || value.startsWith('-')) {
4741
+ throw new TenderCliUsageError('--name requires an env binding name.');
4742
+ }
4743
+ if (!/^[A-Z][A-Z0-9_]*$/.test(value)) {
4744
+ throw new TenderCliUsageError('--name must be UPPER_SNAKE_CASE. Example: SHOPIFY_API');
4745
+ }
4746
+ return value;
4747
+ }
4748
+ function parseTypeName(value, flag) {
4749
+ if (!value || value.startsWith('-')) {
4750
+ throw new TenderCliUsageError(`${flag} requires a TypeScript type name.`);
4751
+ }
4752
+ if (!/^[A-Z][A-Za-z0-9]*$/.test(value)) {
4753
+ throw new TenderCliUsageError(`${flag} must be a PascalCase TypeScript type name. Example: ShopifyApiBinding`);
4754
+ }
4755
+ return value;
4756
+ }
4757
+ function parseBindingsOutboundArgs(args) {
4758
+ if (args.includes('--help') || args.includes('-h')) {
4759
+ return { command: 'help', topic: 'app bindings outbound' };
4760
+ }
4761
+ let id = null;
4762
+ let host = null;
4763
+ let name = null;
4764
+ let label = null;
4765
+ let interfaceName = null;
4766
+ let dryRun = false;
4767
+ let json = false;
4768
+ for (let index = 0; index < args.length; index += 1) {
4769
+ const arg = args[index];
4770
+ if (arg === '--json') {
4771
+ json = true;
4772
+ continue;
4773
+ }
4774
+ if (arg === '--dry-run') {
4775
+ dryRun = true;
4776
+ continue;
4777
+ }
4778
+ if (arg === '--id') {
4779
+ id = parseBindingId(args[index + 1]);
4780
+ index += 1;
4781
+ continue;
4782
+ }
4783
+ if (arg === '--host') {
4784
+ host = parseAllowedHost(args[index + 1]);
4785
+ index += 1;
4786
+ continue;
4787
+ }
4788
+ if (arg === '--name') {
4789
+ name = parseEnvBindingName(args[index + 1]);
4790
+ index += 1;
4791
+ continue;
4792
+ }
4793
+ if (arg === '--label') {
4794
+ label = parseRequiredFlagValue(args[index + 1], '--label');
4795
+ index += 1;
4796
+ continue;
4797
+ }
4798
+ if (arg === '--interface') {
4799
+ interfaceName = parseTypeName(args[index + 1], '--interface');
4800
+ index += 1;
4801
+ continue;
4802
+ }
4803
+ throw new TenderCliUsageError(`Unknown bindings outbound option: ${arg}`);
4804
+ }
4805
+ if (!id || !host) {
4806
+ throw new TenderCliUsageError('--id and --host are required. Example: tender app bindings outbound --id shopify --host admin.shopify.com --dry-run --json');
4807
+ }
4808
+ if (!dryRun) {
4809
+ throw new TenderCliUsageError('bindings outbound currently requires --dry-run. Example: tender app bindings outbound --id shopify --host admin.shopify.com --dry-run --json');
4810
+ }
4811
+ return {
4812
+ command: 'app bindings outbound',
4813
+ id,
4814
+ host,
4815
+ name,
4816
+ label,
4817
+ interfaceName,
4818
+ dryRun,
4819
+ json,
4820
+ };
4821
+ }
4822
+ function parseAnalyticsCommonArgs(args, startIndex) {
4823
+ let baseUrl = null;
4824
+ let flagToken = null;
4825
+ let profileName = null;
4826
+ let json = false;
4827
+ const remaining = [];
4828
+ for (let index = startIndex; index < args.length; index += 1) {
4829
+ const arg = args[index];
4830
+ if (arg === '--json') {
4831
+ json = true;
4832
+ continue;
4833
+ }
4834
+ if (arg === '--base-url') {
4835
+ baseUrl = parseBaseUrlFlag(args, index);
4836
+ index += 1;
4837
+ continue;
4838
+ }
4839
+ if (arg === '--token') {
4840
+ flagToken = parseTokenFlag(args, index);
4841
+ index += 1;
4842
+ continue;
4843
+ }
4844
+ if (arg === '--profile') {
4845
+ profileName = parseProfileName(args[index + 1], '--profile');
4846
+ index += 1;
4847
+ continue;
4848
+ }
4849
+ remaining.push(arg);
4850
+ }
4851
+ return { baseUrl, flagToken, profileName, json, remaining };
4852
+ }
4853
+ function parseAnalyticsSummaryArgs(args) {
4854
+ if (args.includes('--help') || args.includes('-h')) {
4855
+ return { command: 'help', topic: 'app analytics summary' };
4856
+ }
4857
+ const artifactId = args[0];
4858
+ if (!artifactId || artifactId.startsWith('-')) {
4859
+ throw new TenderCliUsageError('app id is required. Example: tender app analytics summary <app-id> --range 30d --json');
4860
+ }
4861
+ const parsed = parseAnalyticsCommonArgs(args, 1);
4862
+ let range = '7d';
4863
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
4864
+ const arg = parsed.remaining[index];
4865
+ if (arg === '--range') {
4866
+ range = parseAnalyticsRange(parsed.remaining[index + 1]);
4867
+ index += 1;
4868
+ continue;
4869
+ }
4870
+ throw new TenderCliUsageError(`Unknown analytics summary option: ${arg}`);
4871
+ }
4872
+ return { command: 'app analytics summary', artifactId, range, ...parsed };
4873
+ }
4874
+ function parseAnalyticsQueryArgs(args) {
4875
+ if (args.includes('--help') || args.includes('-h')) {
4876
+ return { command: 'help', topic: 'app analytics query' };
4877
+ }
4878
+ const artifactId = args[0];
4879
+ if (!artifactId || artifactId.startsWith('-')) {
4880
+ throw new TenderCliUsageError('app id is required. Example: tender app analytics query <app-id> --spec chart.json --json');
4881
+ }
4882
+ const parsed = parseAnalyticsCommonArgs(args, 1);
4883
+ let specPath = null;
4884
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
4885
+ const arg = parsed.remaining[index];
4886
+ if (arg === '--spec') {
4887
+ specPath = parseSpecPath(parsed.remaining[index + 1]);
4888
+ index += 1;
4889
+ continue;
4890
+ }
4891
+ throw new TenderCliUsageError(`Unknown analytics query option: ${arg}`);
4892
+ }
4893
+ if (!specPath) {
4894
+ throw new TenderCliUsageError('--spec is required. Example: tender app analytics query artifact_123 --spec chart.json --json');
4895
+ }
4896
+ return { command: 'app analytics query', artifactId, specPath, ...parsed };
4897
+ }
4898
+ function parseAnalyticsCapabilitiesArgs(args) {
4899
+ if (args.includes('--help') || args.includes('-h')) {
4900
+ return { command: 'help', topic: 'app analytics capabilities' };
4901
+ }
4902
+ const artifactId = args[0];
4903
+ if (!artifactId || artifactId.startsWith('-')) {
4904
+ throw new TenderCliUsageError('app id is required. Example: tender app analytics capabilities <app-id> --include-catalog --json');
4905
+ }
4906
+ const parsed = parseAnalyticsCommonArgs(args, 1);
4907
+ let range = '7d';
4908
+ let includeCatalog = false;
4909
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
4910
+ const arg = parsed.remaining[index];
4911
+ if (arg === '--include-catalog') {
4912
+ includeCatalog = true;
4913
+ continue;
4914
+ }
4915
+ if (arg === '--range') {
4916
+ range = parseAnalyticsRange(parsed.remaining[index + 1]);
4917
+ index += 1;
4918
+ continue;
4919
+ }
4920
+ throw new TenderCliUsageError(`Unknown analytics capabilities option: ${arg}`);
4921
+ }
4922
+ return {
4923
+ command: 'app analytics capabilities',
4924
+ artifactId,
4925
+ includeCatalog,
4926
+ range,
4927
+ ...parsed,
4928
+ };
4929
+ }
4930
+ function parseAnalyticsAuthoringArgs(args) {
4931
+ if (args.includes('--help') || args.includes('-h')) {
4932
+ return { command: 'help', topic: 'app analytics authoring' };
4933
+ }
4934
+ let json = false;
4935
+ for (const arg of args) {
4936
+ if (arg === '--json') {
4937
+ json = true;
4938
+ continue;
4939
+ }
4940
+ throw new TenderCliUsageError(`Unknown analytics authoring option: ${arg}`);
4941
+ }
4942
+ return { command: 'app analytics authoring', json };
4943
+ }
4944
+ function parseAnalyticsSuggestionsArgs(args) {
4945
+ if (args.includes('--help') || args.includes('-h')) {
4946
+ return { command: 'help', topic: 'app analytics suggestions' };
4947
+ }
4948
+ const artifactId = args[0];
4949
+ if (!artifactId || artifactId.startsWith('-')) {
4950
+ throw new TenderCliUsageError('app id is required. Example: tender app analytics suggestions <app-id> --range 30d --json');
4951
+ }
4952
+ const parsed = parseAnalyticsCommonArgs(args, 1);
4953
+ let range = '7d';
4954
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
4955
+ const arg = parsed.remaining[index];
4956
+ if (arg === '--range') {
4957
+ range = parseAnalyticsRange(parsed.remaining[index + 1]);
4958
+ index += 1;
4959
+ continue;
4960
+ }
4961
+ throw new TenderCliUsageError(`Unknown analytics suggestions option: ${arg}`);
4962
+ }
4963
+ return { command: 'app analytics suggestions', artifactId, range, ...parsed };
4964
+ }
4965
+ function parseAnalyticsSteps(value) {
4966
+ const raw = parseRequiredFlagValue(value, '--steps');
4967
+ const steps = raw
4968
+ .split(',')
4969
+ .map((step) => step.trim())
4970
+ .filter((step) => step.length > 0);
4971
+ if (steps.length < 2 || steps.length > 25 || new Set(steps).size !== steps.length) {
4972
+ throw new TenderCliUsageError('--steps must contain 2-25 unique comma-separated flow steps.');
4973
+ }
4974
+ return steps;
4975
+ }
4976
+ function parseAnalyticsExactFunnelArgs(args) {
4977
+ if (args.includes('--help') || args.includes('-h')) {
4978
+ return { command: 'help', topic: 'app analytics exact-funnel' };
4979
+ }
4980
+ const artifactId = args[0];
4981
+ if (!artifactId || artifactId.startsWith('-')) {
4982
+ throw new TenderCliUsageError('app id is required. Example: tender app analytics exact-funnel <app-id> --flow checkout_flow --steps started,completed --range 30d --json');
4236
4983
  }
4237
4984
  const parsed = parseAnalyticsCommonArgs(args, 1);
4238
4985
  let range = '7d';
@@ -6022,7 +6769,67 @@ function parseTenderArgs(args) {
6022
6769
  }
6023
6770
  throw new TenderCliUsageError(`Unknown playbooks command: ${args[1]}`);
6024
6771
  }
6025
- if (args[0] === 'apps') {
6772
+ if (args[0] === 'connectors') {
6773
+ if (args[1] === undefined || args[1] === '--help' || args[1] === '-h') {
6774
+ return { command: 'help', topic: 'connectors' };
6775
+ }
6776
+ if (args[1] === 'list') {
6777
+ return parseConnectorsListArgs(args.slice(2));
6778
+ }
6779
+ if (args[1] === 'bind') {
6780
+ return parseConnectorsBindArgs(args.slice(2));
6781
+ }
6782
+ if (args[1] === 'shopify') {
6783
+ if (args[2] === undefined || args[2] === '--help' || args[2] === '-h') {
6784
+ return { command: 'help', topic: 'connectors shopify' };
6785
+ }
6786
+ if (args[2] === 'create') {
6787
+ return parseConnectorsShopifyCreateArgs(args.slice(3));
6788
+ }
6789
+ if (args[2] === 'setup-url') {
6790
+ return parseConnectorsShopifySetupUrlArgs(args.slice(3));
6791
+ }
6792
+ if (args[2] === 'credentials' && (args[3] === undefined || args[3] === '--help' || args[3] === '-h')) {
6793
+ return { command: 'help', topic: 'connectors shopify credentials set' };
6794
+ }
6795
+ if (args[2] === 'credentials' && args[3] === 'set') {
6796
+ return parseConnectorsShopifyCredentialsSetArgs(args.slice(4));
6797
+ }
6798
+ if (args[2] === 'app-automation-token' && (args[3] === undefined || args[3] === '--help' || args[3] === '-h')) {
6799
+ return { command: 'help', topic: 'connectors shopify app-automation-token set' };
6800
+ }
6801
+ if (args[2] === 'app-automation-token' && args[3] === 'set') {
6802
+ return parseConnectorsShopifyAppAutomationTokenSetArgs(args.slice(4));
6803
+ }
6804
+ if (args[2] === 'app-automation-token' && args[3] === 'delete') {
6805
+ return parseConnectorsShopifyAppAutomationTokenDeleteArgs(args.slice(4));
6806
+ }
6807
+ if (args[2] === 'verify') {
6808
+ return parseConnectorsShopifyVerifyArgs(args.slice(3));
6809
+ }
6810
+ if (args[2] === 'source' && (args[3] === undefined || args[3] === '--help' || args[3] === '-h')) {
6811
+ return { command: 'help', topic: 'connectors shopify source init' };
6812
+ }
6813
+ if (args[2] === 'source' && args[3] === 'init') {
6814
+ return parseConnectorsShopifySourceInitArgs(args.slice(4));
6815
+ }
6816
+ if (args[2] === 'source' && args[3] === 'checkout') {
6817
+ return parseConnectorsShopifySourceCheckoutArgs(args.slice(4));
6818
+ }
6819
+ if (args[2] === 'source' && args[3] === 'validate') {
6820
+ return parseConnectorsShopifySourceValidateArgs(args.slice(4));
6821
+ }
6822
+ if (args[2] === 'source' && args[3] === 'deploy') {
6823
+ return parseConnectorsShopifySourceDeployArgs(args.slice(4));
6824
+ }
6825
+ if (args[2] === 'delete') {
6826
+ return parseConnectorsShopifyDeleteArgs(args.slice(3));
6827
+ }
6828
+ throw new TenderCliUsageError(`Unknown connectors shopify command: ${args[2]}`);
6829
+ }
6830
+ throw new TenderCliUsageError(`Unknown connectors command: ${args[1]}`);
6831
+ }
6832
+ if (args[0] === 'apps') {
6026
6833
  throw new TenderCliUsageError('Unknown command: apps. Use `tender app ...` for Tender App workflows.');
6027
6834
  }
6028
6835
  if (args[0] !== 'app') {
@@ -6738,6 +7545,27 @@ async function requestPlaybooksApi(input) {
6738
7545
  }
6739
7546
  return payload;
6740
7547
  }
7548
+ async function requestConnectorsApi(input) {
7549
+ const suffix = input.connectorsPath ? `/${input.connectorsPath.replace(/^\/+/, '')}` : '';
7550
+ const response = await input.fetcher(`${input.baseUrl}/api/account/connectors${suffix}`, {
7551
+ method: input.method,
7552
+ headers: {
7553
+ authorization: `Bearer ${input.token}`,
7554
+ accept: 'application/json',
7555
+ ...(input.body === undefined ? {} : { 'content-type': 'application/json' }),
7556
+ },
7557
+ body: input.body === undefined ? undefined : JSON.stringify(input.body),
7558
+ });
7559
+ const payload = response.status === 204 ? { ok: true } : await parseJsonResponse(response);
7560
+ if (!response.ok || !payload?.ok) {
7561
+ const reasonCode = (typeof payload?.reasonCode === 'string' && payload.reasonCode) || `http_${response.status}`;
7562
+ const message = (typeof payload?.message === 'string' && payload.message) ||
7563
+ (typeof payload?.reasonCode === 'string' && payload.reasonCode) ||
7564
+ `Connector request failed with HTTP ${response.status}.`;
7565
+ throw new Error([message, `Reason: ${reasonCode}.`, 'Example: tender connectors list --profile account --json.'].join('\n'));
7566
+ }
7567
+ return payload;
7568
+ }
6741
7569
  function dbCorrectedExample(artifactId, dbPath) {
6742
7570
  if (dbPath === 'capabilities') {
6743
7571
  return `tender app db capabilities ${artifactId} --json`;
@@ -8159,9 +8987,16 @@ async function runGitCommand(input) {
8159
8987
  function shellQuote(value) {
8160
8988
  return `'${value.replaceAll("'", "'\\''")}'`;
8161
8989
  }
8990
+ function currentCliCredentialHelperCommandParts() {
8991
+ const scriptPath = process.argv[1];
8992
+ if (!scriptPath) {
8993
+ return ['tender'];
8994
+ }
8995
+ return [process.execPath, scriptPath];
8996
+ }
8162
8997
  function createCredentialHelperCommand(input) {
8163
8998
  const parts = [
8164
- 'tender',
8999
+ ...currentCliCredentialHelperCommandParts().map(shellQuote),
8165
9000
  'app',
8166
9001
  'git',
8167
9002
  'credential',
@@ -9866,228 +10701,1349 @@ async function runMemoryBrief(command, io, runtime) {
9866
10701
  repoPath,
9867
10702
  runner: commandRunnerForMemory(runtime),
9868
10703
  });
9869
- const latestSummary = [...entries].reverse().find((entry) => entry.kind === 'summary') ?? null;
9870
- const unresolved = entries
9871
- .filter((entry) => entry.status === 'open' || entry.kind === 'failure' || entry.kind === 'open_question' || entry.kind === 'support_handoff')
9872
- .slice(-command.limit);
9873
- const briefEntries = latestSummary ? [latestSummary, ...unresolved.filter((entry) => entry.entryId !== latestSummary.entryId)] : unresolved;
10704
+ const latestSummary = [...entries].reverse().find((entry) => entry.kind === 'summary') ?? null;
10705
+ const unresolved = entries
10706
+ .filter((entry) => entry.status === 'open' || entry.kind === 'failure' || entry.kind === 'open_question' || entry.kind === 'support_handoff')
10707
+ .slice(-command.limit);
10708
+ const briefEntries = latestSummary ? [latestSummary, ...unresolved.filter((entry) => entry.entryId !== latestSummary.entryId)] : unresolved;
10709
+ const result = {
10710
+ ok: true,
10711
+ command: command.command,
10712
+ ref: ARTIFACT_MEMORY_NOTES_REF,
10713
+ entries: briefEntries,
10714
+ };
10715
+ if (command.json) {
10716
+ io.stdout(JSON.stringify(result, null, 2));
10717
+ }
10718
+ else {
10719
+ io.stdout(briefEntries.length > 0 ? briefEntries.map(formatArtifactMemoryEntry).join('\n') : 'No artifact memory brief is available yet.');
10720
+ }
10721
+ return 0;
10722
+ }
10723
+ async function runMemoryFlush(command, io, runtime) {
10724
+ const sync = await pushArtifactMemoryNotes({
10725
+ repoPath: path.resolve(command.dir),
10726
+ remoteName: command.remoteName,
10727
+ runner: commandRunnerForMemory(runtime),
10728
+ });
10729
+ const next = sync.status === 'failed'
10730
+ ? `tender memory flush --dir ${command.dir} --remote ${command.remoteName}`
10731
+ : sync.status === 'not_configured'
10732
+ ? memoryRecoveryForArtifact(command.artifactId, command.dir)
10733
+ : null;
10734
+ const result = {
10735
+ ok: sync.status === 'pushed',
10736
+ command: command.command,
10737
+ memorySync: sync,
10738
+ next,
10739
+ };
10740
+ if (command.json) {
10741
+ io.stdout(JSON.stringify(result, null, 2));
10742
+ }
10743
+ else {
10744
+ io.stdout([
10745
+ `memory_sync: ${sync.status}`,
10746
+ sync.error ? `error: ${sync.error}` : null,
10747
+ result.next ? `next: ${result.next}` : null,
10748
+ ]
10749
+ .filter((line) => Boolean(line))
10750
+ .join('\n'));
10751
+ }
10752
+ return sync.status === 'pushed' ? 0 : 1;
10753
+ }
10754
+ async function runMemoryExport(command, io, runtime) {
10755
+ const entries = await readArtifactMemoryEntries({
10756
+ repoPath: path.resolve(command.dir),
10757
+ commitSha: command.commitSha,
10758
+ runner: commandRunnerForMemory(runtime),
10759
+ });
10760
+ if (command.format === 'jsonl') {
10761
+ io.stdout(entries.map((entry) => JSON.stringify(entry)).join('\n'));
10762
+ return 0;
10763
+ }
10764
+ throw new TenderCliUsageError(`Unsupported memory export format: ${command.format}.\nNext: tender memory export --format jsonl --dir ${command.dir}`);
10765
+ }
10766
+ async function readLocalChangedFiles(input) {
10767
+ const result = await runGitCommand({
10768
+ runtime: input.runtime,
10769
+ cwd: input.repoPath,
10770
+ args: ['status', '--porcelain'],
10771
+ allowFailure: true,
10772
+ });
10773
+ if (result.exitCode !== 0) {
10774
+ return [];
10775
+ }
10776
+ return result.stdout
10777
+ .split(/\r?\n/)
10778
+ .map((line) => line.slice(3).trim())
10779
+ .filter(Boolean)
10780
+ .slice(0, 50);
10781
+ }
10782
+ async function appendAutomaticMemory(input) {
10783
+ try {
10784
+ return await appendArtifactMemoryNote({
10785
+ repoPath: input.repoPath,
10786
+ artifactId: input.artifactId,
10787
+ kind: input.kind,
10788
+ summary: input.summary,
10789
+ body: input.body,
10790
+ tags: input.tags,
10791
+ severity: input.severity,
10792
+ status: input.status,
10793
+ related: input.related,
10794
+ source: {
10795
+ actor: 'cli',
10796
+ command: input.summary,
10797
+ },
10798
+ runner: commandRunnerForMemory(input.runtime),
10799
+ now: input.runtime.now,
10800
+ });
10801
+ }
10802
+ catch (error) {
10803
+ return {
10804
+ error: error instanceof Error ? error.message : String(error),
10805
+ next: `tender memory add --dir ${input.repoPath} --artifact ${input.artifactId} --kind ${input.kind} --summary ${JSON.stringify(input.summary)}`,
10806
+ };
10807
+ }
10808
+ }
10809
+ async function flushAutomaticMemory(input) {
10810
+ return pushArtifactMemoryNotes({
10811
+ repoPath: input.repoPath,
10812
+ remoteName: input.remoteName,
10813
+ runner: commandRunnerForMemory(input.runtime),
10814
+ });
10815
+ }
10816
+ async function resolveAnalyticsCredentials(command, runtime) {
10817
+ return await resolveApiCredentials({
10818
+ baseUrl: command.baseUrl,
10819
+ flagToken: command.flagToken,
10820
+ profileName: command.profileName,
10821
+ env: runtime.env ?? process.env,
10822
+ });
10823
+ }
10824
+ async function resolvePlaybooksCredentials(command, runtime) {
10825
+ try {
10826
+ return await resolveApiCredentials({
10827
+ baseUrl: command.baseUrl,
10828
+ flagToken: command.flagToken,
10829
+ profileName: command.profileName,
10830
+ env: runtime.env ?? process.env,
10831
+ });
10832
+ }
10833
+ catch (error) {
10834
+ if (error instanceof TenderCliUsageError) {
10835
+ throw new TenderCliUsageError([
10836
+ 'Playbooks require Tender authentication.',
10837
+ 'Run `tender auth login --device --profile edit-preview --save-profile agent --ttl 7d --json`, set TENDER_API_TOKEN, or pass --token <token>.',
10838
+ ].join('\n'));
10839
+ }
10840
+ throw error;
10841
+ }
10842
+ }
10843
+ async function resolveDbCredentials(command, runtime) {
10844
+ return await resolveApiCredentials({
10845
+ baseUrl: command.baseUrl,
10846
+ flagToken: command.flagToken,
10847
+ profileName: command.profileName,
10848
+ env: runtime.env ?? process.env,
10849
+ });
10850
+ }
10851
+ function validateDbSql(sql) {
10852
+ if (!sql.trim()) {
10853
+ throw new TenderCliUsageError('SQL statement is required.');
10854
+ }
10855
+ const byteLength = new TextEncoder().encode(sql).byteLength;
10856
+ if (byteLength > DB_SQL_STATEMENT_MAX_BYTES) {
10857
+ throw new TenderCliUsageError(`SQL statement is limited to ${DB_SQL_STATEMENT_MAX_BYTES} bytes.`);
10858
+ }
10859
+ }
10860
+ async function readDbSql(command, runtime) {
10861
+ const sql = command.sqlSource.kind === 'inline'
10862
+ ? command.sqlSource.value
10863
+ : command.sqlSource.value === '-'
10864
+ ? runtime.stdin
10865
+ : await readFile(path.resolve(command.sqlSource.value), 'utf8');
10866
+ if (!sql) {
10867
+ throw new TenderCliUsageError('--file - requires SQL on stdin.');
10868
+ }
10869
+ validateDbSql(sql);
10870
+ return sql;
10871
+ }
10872
+ function printDbPayload(command, io, payload, fallbackLines) {
10873
+ if (command.json) {
10874
+ io.stdout(JSON.stringify(payload, null, 2));
10875
+ }
10876
+ else {
10877
+ io.stdout(fallbackLines.join('\n'));
10878
+ }
10879
+ }
10880
+ function printPlaybooksPayload(command, io, payload, fallback) {
10881
+ if (command.json) {
10882
+ io.stdout(JSON.stringify(payload, null, 2));
10883
+ }
10884
+ else {
10885
+ io.stdout(fallback);
10886
+ }
10887
+ }
10888
+ async function runPlaybooksList(command, io, runtime) {
10889
+ const credentials = await resolvePlaybooksCredentials(command, runtime);
10890
+ const payload = await requestPlaybooksApi({
10891
+ playbooksPath: 'list',
10892
+ baseUrl: credentials.baseUrl,
10893
+ token: credentials.token,
10894
+ fetcher: runtime.fetcher ?? fetch,
10895
+ });
10896
+ const playbooks = Array.isArray(payload.playbooks) ? payload.playbooks : [];
10897
+ printPlaybooksPayload(command, io, payload, playbooks
10898
+ .map((playbook) => (isCliJsonRecord(playbook) ? `${String(playbook.id ?? '')}\t${String(playbook.description ?? '')}` : ''))
10899
+ .filter(Boolean)
10900
+ .join('\n') || 'No Tender Prompt playbooks found.');
10901
+ return 0;
10902
+ }
10903
+ async function runPlaybooksGet(command, io, runtime) {
10904
+ const credentials = await resolvePlaybooksCredentials(command, runtime);
10905
+ const payload = await requestPlaybooksApi({
10906
+ playbooksPath: 'get',
10907
+ playbookId: command.playbookId,
10908
+ baseUrl: credentials.baseUrl,
10909
+ token: credentials.token,
10910
+ fetcher: runtime.fetcher ?? fetch,
10911
+ });
10912
+ printPlaybooksPayload(command, io, payload, typeof payload.body === 'string' ? payload.body : JSON.stringify(payload, null, 2));
10913
+ return 0;
10914
+ }
10915
+ async function runPlaybooksFile(command, io, runtime) {
10916
+ const credentials = await resolvePlaybooksCredentials(command, runtime);
10917
+ const payload = await requestPlaybooksApi({
10918
+ playbooksPath: 'file',
10919
+ playbookId: command.playbookId,
10920
+ filePath: command.filePath,
10921
+ baseUrl: credentials.baseUrl,
10922
+ token: credentials.token,
10923
+ fetcher: runtime.fetcher ?? fetch,
10924
+ });
10925
+ printPlaybooksPayload(command, io, payload, typeof payload.content === 'string' ? payload.content : JSON.stringify(payload, null, 2));
10926
+ return 0;
10927
+ }
10928
+ async function resolveConnectorsCredentials(command, runtime) {
10929
+ try {
10930
+ return await resolveApiCredentials({
10931
+ baseUrl: command.baseUrl,
10932
+ flagToken: command.flagToken,
10933
+ profileName: command.profileName,
10934
+ env: runtime.env ?? process.env,
10935
+ });
10936
+ }
10937
+ catch (error) {
10938
+ if (error instanceof TenderCliUsageError) {
10939
+ throw new TenderCliUsageError([
10940
+ 'Connectors require Tender account authentication.',
10941
+ 'Run `tender auth login --device --profile create --ttl 7d --save-profile account --json`, set TENDER_API_TOKEN, or pass --token <token>.',
10942
+ ].join('\n'));
10943
+ }
10944
+ throw error;
10945
+ }
10946
+ }
10947
+ function printConnectorsPayload(command, io, payload, fallbackLines) {
10948
+ if (command.json) {
10949
+ io.stdout(JSON.stringify(payload, null, 2));
10950
+ }
10951
+ else {
10952
+ io.stdout(fallbackLines.join('\n'));
10953
+ }
10954
+ }
10955
+ function readConnectorArray(payload) {
10956
+ return Array.isArray(payload.connectors) ? payload.connectors.filter(isCliJsonRecord) : [];
10957
+ }
10958
+ async function runConnectorsList(command, io, runtime) {
10959
+ const credentials = await resolveConnectorsCredentials(command, runtime);
10960
+ const payload = await requestConnectorsApi({
10961
+ connectorsPath: '',
10962
+ method: 'GET',
10963
+ baseUrl: credentials.baseUrl,
10964
+ token: credentials.token,
10965
+ fetcher: runtime.fetcher ?? fetch,
10966
+ });
10967
+ const connectors = readConnectorArray(payload);
10968
+ printConnectorsPayload(command, io, payload, connectors.length > 0
10969
+ ? connectors.map((connector) => {
10970
+ return [
10971
+ String(connector.connectorId ?? ''),
10972
+ String(connector.instanceId ?? ''),
10973
+ String(connector.status ?? ''),
10974
+ String(connector.adminApiVersion ?? ''),
10975
+ String(connector.shopDomain ?? ''),
10976
+ connector.hasAppAutomationToken ? 'app_automation_token:stored' : 'app_automation_token:missing',
10977
+ ].join('\t');
10978
+ })
10979
+ : ['No connectors found.']);
10980
+ return 0;
10981
+ }
10982
+ async function runConnectorsShopifyCreate(command, io, runtime) {
10983
+ const credentials = await resolveConnectorsCredentials(command, runtime);
10984
+ const payload = await requestConnectorsApi({
10985
+ connectorsPath: '',
10986
+ method: 'POST',
10987
+ baseUrl: credentials.baseUrl,
10988
+ token: credentials.token,
10989
+ fetcher: runtime.fetcher ?? fetch,
10990
+ body: {
10991
+ type: 'shopify',
10992
+ instanceId: command.instanceId,
10993
+ ...(command.label ? { label: command.label } : {}),
10994
+ ...(command.appName ? { appName: command.appName } : {}),
10995
+ ...(command.adminApiVersion ? { adminApiVersion: command.adminApiVersion } : {}),
10996
+ ...(command.shopDomain ? { shopDomain: command.shopDomain } : {}),
10997
+ },
10998
+ });
10999
+ const connector = isCliJsonRecord(payload.connector) ? payload.connector : {};
11000
+ printConnectorsPayload(command, io, payload, [
11001
+ `connector_id: ${String(connector.connectorId ?? '')}`,
11002
+ `instance: ${String(connector.instanceId ?? command.instanceId)}`,
11003
+ `status: ${String(connector.status ?? '')}`,
11004
+ `admin_api_version: ${String(connector.adminApiVersion ?? command.adminApiVersion ?? '')}`,
11005
+ `setup_url: ${String(payload.setupUrl ?? '')}`,
11006
+ ]);
11007
+ return 0;
11008
+ }
11009
+ async function runConnectorsShopifySetupUrl(command, io, runtime) {
11010
+ const credentials = await resolveConnectorsCredentials(command, runtime);
11011
+ const payload = await requestConnectorsApi({
11012
+ connectorsPath: `${encodeURIComponent(command.connectorId)}/setup-sessions`,
11013
+ method: 'POST',
11014
+ baseUrl: credentials.baseUrl,
11015
+ token: credentials.token,
11016
+ fetcher: runtime.fetcher ?? fetch,
11017
+ });
11018
+ printConnectorsPayload(command, io, payload, [
11019
+ `connector_id: ${command.connectorId}`,
11020
+ `setup_url: ${String(payload.setupUrl ?? '')}`,
11021
+ ]);
11022
+ return 0;
11023
+ }
11024
+ function readClientSecret(command, runtime) {
11025
+ if (command.clientSecretSource.kind === 'stdin') {
11026
+ const secret = (runtime.stdin ?? '').trim();
11027
+ if (!secret) {
11028
+ throw new TenderCliUsageError('--client-secret-stdin requires a non-empty secret on stdin.');
11029
+ }
11030
+ return secret;
11031
+ }
11032
+ const secret = runtime.env?.[command.clientSecretSource.name] ?? process.env[command.clientSecretSource.name];
11033
+ if (!secret?.trim()) {
11034
+ throw new TenderCliUsageError(`Environment variable ${command.clientSecretSource.name} is empty or unset.`);
11035
+ }
11036
+ return secret.trim();
11037
+ }
11038
+ function readAppAutomationToken(command, runtime) {
11039
+ if (command.tokenSource.kind === 'stdin') {
11040
+ const token = (runtime.stdin ?? '').trim();
11041
+ if (!token) {
11042
+ throw new TenderCliUsageError('--token-stdin requires a non-empty token on stdin.');
11043
+ }
11044
+ return token;
11045
+ }
11046
+ const token = runtime.env?.[command.tokenSource.name] ?? process.env[command.tokenSource.name];
11047
+ if (!token?.trim()) {
11048
+ throw new TenderCliUsageError(`Environment variable ${command.tokenSource.name} is empty or unset.`);
11049
+ }
11050
+ return token.trim();
11051
+ }
11052
+ async function runConnectorsShopifyCredentialsSet(command, io, runtime) {
11053
+ const credentials = await resolveConnectorsCredentials(command, runtime);
11054
+ const clientSecret = readClientSecret(command, runtime);
11055
+ const payload = await requestConnectorsApi({
11056
+ connectorsPath: `${encodeURIComponent(command.connectorId)}/credentials`,
11057
+ method: 'POST',
11058
+ baseUrl: credentials.baseUrl,
11059
+ token: credentials.token,
11060
+ fetcher: runtime.fetcher ?? fetch,
11061
+ body: {
11062
+ shopDomain: command.shopDomain,
11063
+ clientId: command.clientId,
11064
+ clientSecret,
11065
+ },
11066
+ });
11067
+ const connector = isCliJsonRecord(payload.connector) ? payload.connector : {};
11068
+ printConnectorsPayload(command, io, payload, [
11069
+ `connector_id: ${String(connector.connectorId ?? command.connectorId)}`,
11070
+ `status: ${String(connector.status ?? '')}`,
11071
+ `shop: ${String(connector.shopDomain ?? command.shopDomain)}`,
11072
+ `client_id_prefix: ${String(connector.clientIdPrefix ?? '')}`,
11073
+ ]);
11074
+ return 0;
11075
+ }
11076
+ async function runConnectorsShopifyAppAutomationTokenSet(command, io, runtime) {
11077
+ const credentials = await resolveConnectorsCredentials(command, runtime);
11078
+ const appAutomationToken = readAppAutomationToken(command, runtime);
11079
+ const payload = await requestConnectorsApi({
11080
+ connectorsPath: `${encodeURIComponent(command.connectorId)}/app-automation-token`,
11081
+ method: 'POST',
11082
+ baseUrl: credentials.baseUrl,
11083
+ token: credentials.token,
11084
+ fetcher: runtime.fetcher ?? fetch,
11085
+ body: {
11086
+ appAutomationToken,
11087
+ ...(command.expiresAt ? { expiresAt: command.expiresAt } : {}),
11088
+ },
11089
+ });
11090
+ const connector = isCliJsonRecord(payload.connector) ? payload.connector : {};
11091
+ printConnectorsPayload(command, io, payload, [
11092
+ `connector_id: ${String(connector.connectorId ?? command.connectorId)}`,
11093
+ `app_automation_token: ${connector.hasAppAutomationToken ? 'stored' : 'missing'}`,
11094
+ `app_automation_token_expires_at: ${String(connector.appAutomationTokenExpiresAt ?? '')}`,
11095
+ ]);
11096
+ return 0;
11097
+ }
11098
+ async function runConnectorsShopifyAppAutomationTokenDelete(command, io, runtime) {
11099
+ if (command.confirm !== 'delete') {
11100
+ throw new TenderCliUsageError('--confirm delete is required. Example: tender connectors shopify app-automation-token delete conn_123 --confirm delete --json');
11101
+ }
11102
+ const credentials = await resolveConnectorsCredentials(command, runtime);
11103
+ const payload = await requestConnectorsApi({
11104
+ connectorsPath: `${encodeURIComponent(command.connectorId)}/app-automation-token`,
11105
+ method: 'DELETE',
11106
+ baseUrl: credentials.baseUrl,
11107
+ token: credentials.token,
11108
+ fetcher: runtime.fetcher ?? fetch,
11109
+ });
11110
+ const connector = isCliJsonRecord(payload.connector) ? payload.connector : {};
11111
+ printConnectorsPayload(command, io, payload, [
11112
+ `connector_id: ${String(connector.connectorId ?? command.connectorId)}`,
11113
+ 'app_automation_token: deleted',
11114
+ ]);
11115
+ return 0;
11116
+ }
11117
+ async function runConnectorsShopifyVerify(command, io, runtime) {
11118
+ const credentials = await resolveConnectorsCredentials(command, runtime);
11119
+ const payload = await requestConnectorsApi({
11120
+ connectorsPath: `${encodeURIComponent(command.connectorId)}/verify`,
11121
+ method: 'POST',
11122
+ baseUrl: credentials.baseUrl,
11123
+ token: credentials.token,
11124
+ fetcher: runtime.fetcher ?? fetch,
11125
+ });
11126
+ const connector = isCliJsonRecord(payload.connector) ? payload.connector : {};
11127
+ const scopes = Array.isArray(connector.scopes) ? connector.scopes.join(',') : '';
11128
+ printConnectorsPayload(command, io, payload, [
11129
+ `connector_id: ${String(connector.connectorId ?? command.connectorId)}`,
11130
+ `status: ${String(connector.status ?? '')}`,
11131
+ `shop: ${String(connector.shopDomain ?? '')}`,
11132
+ `scopes: ${scopes}`,
11133
+ ]);
11134
+ return 0;
11135
+ }
11136
+ function requireShopifySourceString(value, fallback = '') {
11137
+ return typeof value === 'string' && value.trim() ? value.trim() : fallback;
11138
+ }
11139
+ function normalizeTenderElementTag(value) {
11140
+ const normalized = value.trim().toLowerCase();
11141
+ if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)+$/.test(normalized)) {
11142
+ throw new TenderCliUsageError('--tender-element must be a valid custom element tag, for example tender-dog-profile.');
11143
+ }
11144
+ return normalized;
11145
+ }
11146
+ function normalizeThemeExtensionHandle(value) {
11147
+ const normalized = value.trim().toLowerCase();
11148
+ if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(normalized)) {
11149
+ throw new TenderCliUsageError('--theme-extension-handle must contain only lowercase letters, numbers, and dashes.');
11150
+ }
11151
+ return normalized;
11152
+ }
11153
+ function tomlString(value) {
11154
+ return JSON.stringify(value);
11155
+ }
11156
+ function uniqueSortedScopes(scopes) {
11157
+ return [...new Set(scopes.map((scope) => scope.trim()).filter(Boolean))].sort();
11158
+ }
11159
+ function parseScopeCsv(value) {
11160
+ return uniqueSortedScopes((value ?? '').split(/[,\s]+/));
11161
+ }
11162
+ function defaultDogProfileScopes(connectorScopes, override) {
11163
+ if (override) {
11164
+ return parseScopeCsv(override);
11165
+ }
11166
+ const fromConnector = Array.isArray(connectorScopes) ? connectorScopes.filter((scope) => typeof scope === 'string') : [];
11167
+ return uniqueSortedScopes([
11168
+ ...fromConnector,
11169
+ 'read_customers',
11170
+ 'write_customers',
11171
+ 'read_metaobject_definitions',
11172
+ 'write_metaobject_definitions',
11173
+ 'read_metaobjects',
11174
+ 'write_metaobjects',
11175
+ ]);
11176
+ }
11177
+ function tenderWidgetAssetJs() {
11178
+ return `(function () {
11179
+ const loader = (window.TenderPromptBlockLoader ||= {});
11180
+ loader.scripts ||= new Map();
11181
+ loader.stylesheets ||= new Map();
11182
+
11183
+ function loadScript(src) {
11184
+ if (!src) return Promise.reject(new Error("Missing Tender runtime URL"));
11185
+
11186
+ const existing = loader.scripts.get(src);
11187
+ if (existing) return existing.promise;
11188
+
11189
+ const promise = new Promise((resolve, reject) => {
11190
+ const script = document.createElement("script");
11191
+ script.type = "module";
11192
+ script.src = src;
11193
+ script.onload = resolve;
11194
+ script.onerror = () => reject(new Error(\`Failed to load \${src}\`));
11195
+ document.head.appendChild(script);
11196
+ });
11197
+
11198
+ loader.scripts.set(src, { status: "loading", promise });
11199
+ promise.then(
11200
+ () => loader.scripts.set(src, { status: "loaded", promise }),
11201
+ () => loader.scripts.delete(src),
11202
+ );
11203
+
11204
+ return promise;
11205
+ }
11206
+
11207
+ function preloadStylesheet(src) {
11208
+ if (!src) return Promise.resolve();
11209
+ const existing = loader.stylesheets.get(src);
11210
+ if (existing) return existing.promise;
11211
+
11212
+ const promise = new Promise((resolve) => {
11213
+ const link = document.createElement("link");
11214
+ link.rel = "preload";
11215
+ link.as = "style";
11216
+ link.href = src;
11217
+ link.onload = resolve;
11218
+ link.onerror = resolve;
11219
+ document.head.appendChild(link);
11220
+ });
11221
+
11222
+ loader.stylesheets.set(src, { status: "loading", promise });
11223
+ promise.then(() => loader.stylesheets.set(src, { status: "loaded", promise }));
11224
+ return promise;
11225
+ }
11226
+
11227
+ function afterFrame(callback) {
11228
+ requestAnimationFrame(() => requestAnimationFrame(callback));
11229
+ }
11230
+
11231
+ function stylesheetLoaded(href) {
11232
+ return performance.getEntriesByName(href).some((entry) => entry.responseEnd > 0);
11233
+ }
11234
+
11235
+ function waitForWidgetPaintReady(widget, stylesheetUrl) {
11236
+ const stylesheetHref = stylesheetUrl ? new URL(stylesheetUrl, document.baseURI).href : null;
11237
+
11238
+ return new Promise((resolve) => {
11239
+ let settled = false;
11240
+ let noShadowTimer = null;
11241
+ let rootWithoutStylesheetTimer = null;
11242
+ const finish = () => {
11243
+ if (settled) return;
11244
+ settled = true;
11245
+ clearInterval(interval);
11246
+ clearTimeout(timeout);
11247
+ clearTimeout(noShadowTimer);
11248
+ clearTimeout(rootWithoutStylesheetTimer);
11249
+ afterFrame(resolve);
11250
+ };
11251
+
11252
+ const check = () => {
11253
+ const root = widget.shadowRoot;
11254
+ if (!root) {
11255
+ noShadowTimer ||= setTimeout(finish, 350);
11256
+ return;
11257
+ }
11258
+
11259
+ clearTimeout(noShadowTimer);
11260
+ const links = Array.from(root.querySelectorAll('link[rel="stylesheet"]'));
11261
+ const stylesheet = stylesheetHref
11262
+ ? links.find((link) => link.href === stylesheetHref)
11263
+ : links[0];
11264
+
11265
+ if (!stylesheet) {
11266
+ rootWithoutStylesheetTimer ||= setTimeout(finish, 500);
11267
+ return;
11268
+ }
11269
+
11270
+ clearTimeout(rootWithoutStylesheetTimer);
11271
+ if (stylesheet.sheet || stylesheetLoaded(stylesheet.href)) {
11272
+ finish();
11273
+ return;
11274
+ }
11275
+
11276
+ stylesheet.addEventListener("load", finish, { once: true });
11277
+ stylesheet.addEventListener("error", finish, { once: true });
11278
+ };
11279
+
11280
+ const interval = setInterval(check, 25);
11281
+ const timeout = setTimeout(finish, 2200);
11282
+ check();
11283
+ });
11284
+ }
11285
+
11286
+ function revealWidget(mount, widget) {
11287
+ const placeholder = mount.querySelector("[data-tender-placeholder]");
11288
+ widget.style.visibility = "";
11289
+ widget.style.position = "";
11290
+ widget.style.inset = "";
11291
+ widget.style.pointerEvents = "";
11292
+ placeholder?.remove();
11293
+ mount.dataset.tenderReady = "true";
11294
+ mount.setAttribute("aria-busy", "false");
11295
+ }
11296
+
11297
+ class TenderPromptWidgetBlock extends HTMLElement {
11298
+ connectedCallback() {
11299
+ this.mount();
11300
+ }
11301
+
11302
+ async mount() {
11303
+ const mount = this.querySelector("[data-tender-mount]");
11304
+ const elementTag = this.dataset.tenderElementTag;
11305
+ if (!mount || !elementTag || mount.dataset.tenderMounted === "true") return;
11306
+
11307
+ mount.setAttribute("aria-busy", "true");
11308
+ preloadStylesheet(this.dataset.stylesheetUrl);
11309
+ await loadScript(this.dataset.tenderRuntimeSrc);
11310
+ await Promise.race([
11311
+ customElements.whenDefined(elementTag),
11312
+ new Promise((resolve) => setTimeout(resolve, 1000)),
11313
+ ]);
11314
+
11315
+ const widget = document.createElement(elementTag);
11316
+ for (const [key, value] of Object.entries(this.dataset)) {
11317
+ if (value == null || key === "tenderRuntimeSrc" || key === "tenderElementTag") continue;
11318
+ widget.dataset[key] = value;
11319
+ }
11320
+
11321
+ widget.style.visibility = "hidden";
11322
+ widget.style.position = "absolute";
11323
+ widget.style.inset = "0";
11324
+ widget.style.pointerEvents = "none";
11325
+ mount.appendChild(widget);
11326
+ mount.dataset.tenderMounted = "true";
11327
+ await waitForWidgetPaintReady(widget, this.dataset.stylesheetUrl);
11328
+ revealWidget(mount, widget);
11329
+ }
11330
+ }
11331
+
11332
+ if (!customElements.get("tender-prompt-widget-block")) {
11333
+ customElements.define("tender-prompt-widget-block", TenderPromptWidgetBlock);
11334
+ }
11335
+ })();
11336
+ `;
11337
+ }
11338
+ function buildShopifySourceFiles(input) {
11339
+ const appName = requireShopifySourceString(input.connector.appName, 'Tender Prompt Apps');
11340
+ const adminApiVersion = requireShopifySourceString(input.connector.adminApiVersion, '2026-04');
11341
+ const shopDomain = requireShopifySourceString(input.connector.shopDomain);
11342
+ const tenderElement = normalizeTenderElementTag(input.tenderElement);
11343
+ const extensionHandle = normalizeThemeExtensionHandle(input.themeExtensionHandle);
11344
+ const scopes = defaultDogProfileScopes(input.connector.scopes, input.scopes);
11345
+ const artifactId = input.artifactId ?? 'artifact_replace_me';
11346
+ const publicId = input.publicId ?? 'tender-widget';
11347
+ const stylesheetUrl = new URL('./styles.css', input.tenderAppUrl).href;
11348
+ const blockName = input.blockName.trim().slice(0, 120) || 'Tender Prompt Widget';
11349
+ const connectorManifest = {
11350
+ version: 1,
11351
+ kind: 'shopify_connector_source',
11352
+ connectorId: input.connectorId,
11353
+ instanceId: requireShopifySourceString(input.connector.instanceId, 'default'),
11354
+ template: input.template,
11355
+ shopDomain: shopDomain || null,
11356
+ adminApiVersion,
11357
+ clientId: input.clientId,
11358
+ themeExtensionHandle: extensionHandle,
11359
+ tender: {
11360
+ artifactId,
11361
+ publicId,
11362
+ elementTag: tenderElement,
11363
+ runtimeUrl: input.tenderAppUrl,
11364
+ stylesheetUrl,
11365
+ },
11366
+ secrets: {
11367
+ clientSecret: 'stored in Tender connector credentials, not in this source tree',
11368
+ appAutomationToken: 'store in SHOPIFY_APP_AUTOMATION_TOKEN or Tender connector secret store, not in git',
11369
+ },
11370
+ };
11371
+ const shopifyAppToml = `# Merchant-owned Shopify app source generated by Tender Prompt.
11372
+ # Do not commit client secrets or app automation tokens.
11373
+
11374
+ client_id = ${tomlString(input.clientId)}
11375
+ name = ${tomlString(appName)}
11376
+ application_url = "https://shopify.dev/apps/default-app-home"
11377
+ embedded = true
11378
+
11379
+ [access_scopes]
11380
+ scopes = ${tomlString(scopes.join(','))}
11381
+
11382
+ [auth]
11383
+ redirect_urls = [ "https://shopify.dev/apps/default-app-home/api/auth" ]
11384
+
11385
+ [webhooks]
11386
+ api_version = ${tomlString(adminApiVersion)}
11387
+
11388
+ [build]
11389
+ automatically_update_urls_on_dev = true
11390
+
11391
+ [metaobjects.app.dog_profile]
11392
+ name = "Dog profile"
11393
+ display_name_field = "dog_name"
11394
+ access.admin = "merchant_read_write"
11395
+
11396
+ [metaobjects.app.dog_profile.fields.dog_name]
11397
+ name = "Dog name"
11398
+ type = "single_line_text_field"
11399
+ required = true
11400
+
11401
+ [metaobjects.app.dog_profile.fields.preferences]
11402
+ name = "Preferences"
11403
+ type = "json"
11404
+
11405
+ [customer.metafields.app.dog_profile]
11406
+ name = "Dog profile"
11407
+ type = "metaobject_reference<$app:dog_profile>"
11408
+ access.admin = "merchant_read_write"
11409
+ `;
11410
+ const extensionToml = [
11411
+ `name = ${tomlString(extensionHandle)}`,
11412
+ 'type = "theme"',
11413
+ input.themeExtensionUid ? `uid = ${tomlString(input.themeExtensionUid)}` : null,
11414
+ '',
11415
+ ]
11416
+ .filter((line) => line !== null)
11417
+ .join('\n');
11418
+ const liquid = `{% doc %}
11419
+ Tender Prompt app block generated from connector ${input.connectorId}.
11420
+
11421
+ The block loads one Tender runtime script and mounts the configured Tender
11422
+ custom element. Keep merchant-editable copy and version pins in block settings.
11423
+ {% enddoc %}
11424
+
11425
+ {% liquid
11426
+ assign artifact_id = block.settings.artifact_id | default: ${tomlString(artifactId)}
11427
+ assign widget_version = block.settings.widget_version | default: 'latest'
11428
+
11429
+ case widget_version
11430
+ else
11431
+ assign runtime_src = ${tomlString(input.tenderAppUrl)}
11432
+ assign stylesheet_src = ${tomlString(stylesheetUrl)}
11433
+ endcase
11434
+ %}
11435
+
11436
+ <tender-prompt-widget-block
11437
+ {{ block.shopify_attributes }}
11438
+ class="tender-prompt-widget-block tender-prompt-widget-block--{{ block.settings.tone }}"
11439
+ data-tender-artifact-id="{{ artifact_id | escape }}"
11440
+ data-tender-public-id="{{ block.settings.public_id | escape }}"
11441
+ data-tender-version="{{ widget_version | escape }}"
11442
+ data-tender-runtime-src="{{ runtime_src | escape }}"
11443
+ data-stylesheet-url="{{ stylesheet_src | escape }}"
11444
+ data-tender-element-tag="{{ block.settings.element_tag | escape }}"
11445
+ data-tender-mode="shopify_app_block"
11446
+ data-surface="shopify_theme_app_extension"
11447
+ data-tender-block-id="{{ block.id | escape }}"
11448
+ data-tender-shop-domain="{{ shop.permanent_domain | escape }}"
11449
+ data-tender-heading="{{ block.settings.heading | escape }}"
11450
+ data-tender-intro="{{ block.settings.intro | escape }}"
11451
+ data-tender-tone="{{ block.settings.tone | escape }}"
11452
+ {% if customer %}
11453
+ data-tender-customer-id="{{ customer.id }}"
11454
+ {% endif %}
11455
+ {% if product %}
11456
+ data-tender-product-id="{{ product.id }}"
11457
+ data-tender-product-handle="{{ product.handle | escape }}"
11458
+ {% endif %}
11459
+ >
11460
+ <div data-tender-mount style="position: relative;">
11461
+ <div
11462
+ data-tender-placeholder
11463
+ style="box-sizing: border-box; display: grid; gap: 14px; min-height: 260px; padding: 28px; border: 1px solid rgba(0,0,0,.12); border-radius: 8px; background: rgba(255,255,255,.78); color: currentColor;"
11464
+ >
11465
+ <div style="display: grid; gap: 8px;">
11466
+ <p style="margin: 0; font: inherit; font-weight: 700;">{{ block.settings.heading }}</p>
11467
+ <p style="margin: 0; max-width: 42rem; opacity: .72;">{{ block.settings.intro }}</p>
11468
+ </div>
11469
+ <div style="display: grid; gap: 10px;" aria-hidden="true">
11470
+ <span style="display: block; width: 52%; height: 12px; border-radius: 999px; background: rgba(0,0,0,.10);"></span>
11471
+ <span style="display: block; width: 100%; height: 44px; border-radius: 8px; background: rgba(0,0,0,.07);"></span>
11472
+ <span style="display: block; width: 88%; height: 44px; border-radius: 8px; background: rgba(0,0,0,.07);"></span>
11473
+ </div>
11474
+ </div>
11475
+ </div>
11476
+ </tender-prompt-widget-block>
11477
+
11478
+ {% schema %}
11479
+ {
11480
+ "name": ${JSON.stringify(blockName)},
11481
+ "target": "section",
11482
+ "javascript": "tender-widget.js",
11483
+ "settings": [
11484
+ {
11485
+ "type": "text",
11486
+ "id": "element_tag",
11487
+ "label": "Tender custom element",
11488
+ "default": ${JSON.stringify(tenderElement)}
11489
+ },
11490
+ {
11491
+ "type": "text",
11492
+ "id": "artifact_id",
11493
+ "label": "Tender artifact ID",
11494
+ "default": ${JSON.stringify(artifactId)}
11495
+ },
11496
+ {
11497
+ "type": "text",
11498
+ "id": "public_id",
11499
+ "label": "Tender public ID",
11500
+ "default": ${JSON.stringify(publicId)}
11501
+ },
11502
+ {
11503
+ "type": "select",
11504
+ "id": "widget_version",
11505
+ "label": "Widget version",
11506
+ "options": [
11507
+ { "value": "latest", "label": "Latest published version" }
11508
+ ],
11509
+ "default": "latest"
11510
+ },
11511
+ {
11512
+ "type": "inline_richtext",
11513
+ "id": "heading",
11514
+ "label": "Heading",
11515
+ "default": "Tell us about your customer"
11516
+ },
11517
+ {
11518
+ "type": "text",
11519
+ "id": "intro",
11520
+ "label": "Intro",
11521
+ "default": "Answer a few quick questions and keep your profile up to date."
11522
+ },
11523
+ {
11524
+ "type": "select",
11525
+ "id": "tone",
11526
+ "label": "Tone",
11527
+ "options": [
11528
+ { "value": "default", "label": "Default" },
11529
+ { "value": "subtle", "label": "Subtle" },
11530
+ { "value": "contrast", "label": "Contrast" }
11531
+ ],
11532
+ "default": "default"
11533
+ }
11534
+ ]
11535
+ }
11536
+ {% endschema %}
11537
+ `;
11538
+ const readme = `# ${appName}
11539
+
11540
+ This is the merchant-owned Shopify app source generated by Tender Prompt for
11541
+ connector \`${input.connectorId}\`.
11542
+
11543
+ ## What lives here
11544
+
11545
+ - \`shopify.app.toml\`: Shopify app configuration, scopes, and app-owned dog-profile custom data definitions.
11546
+ - \`extensions/${extensionHandle}\`: Theme App Extension with a Tender app block.
11547
+ - \`connector.json\`: Non-secret Tender metadata that maps this source tree back to the connector.
11548
+
11549
+ ## Secrets
11550
+
11551
+ Do not commit the Shopify client secret, Admin API token, or App Automation
11552
+ Token. Runtime credentials stay in the Tender connector secret store. Deployment
11553
+ uses \`SHOPIFY_APP_AUTOMATION_TOKEN\` at command time.
11554
+
11555
+ ## Validate
11556
+
11557
+ \`\`\`sh
11558
+ npx --yes @shopify/cli@latest app config validate --path . --json
11559
+ \`\`\`
11560
+
11561
+ ## Deploy
11562
+
11563
+ \`\`\`sh
11564
+ SHOPIFY_APP_AUTOMATION_TOKEN=... npx --yes @shopify/cli@latest app deploy --path . --allow-updates --version tender-widget
11565
+ \`\`\`
11566
+ `;
11567
+ return {
11568
+ extensionHandle,
11569
+ manifest: connectorManifest,
11570
+ files: new Map([
11571
+ ['.gitignore', '.shopify/\nnode_modules/\n.env\n.env.*\n'],
11572
+ ['README.md', readme],
11573
+ ['connector.json', `${JSON.stringify(connectorManifest, null, 2)}\n`],
11574
+ ['shopify.app.toml', shopifyAppToml],
11575
+ [`extensions/${extensionHandle}/shopify.extension.toml`, extensionToml],
11576
+ [`extensions/${extensionHandle}/blocks/tender_widget.liquid`, liquid],
11577
+ [`extensions/${extensionHandle}/assets/tender-widget.js`, tenderWidgetAssetJs()],
11578
+ [`extensions/${extensionHandle}/locales/en.default.json`, `${JSON.stringify({ tender: { block: { name: blockName } } }, null, 2)}\n`],
11579
+ ]),
11580
+ };
11581
+ }
11582
+ async function fileExists(filePath) {
11583
+ try {
11584
+ await readFile(filePath, 'utf8');
11585
+ return true;
11586
+ }
11587
+ catch {
11588
+ return false;
11589
+ }
11590
+ }
11591
+ async function writeShopifySourceFiles(input) {
11592
+ const written = [];
11593
+ for (const [relativePath, content] of input.files.entries()) {
11594
+ const absolutePath = path.join(input.dir, relativePath);
11595
+ if (!input.force && (await fileExists(absolutePath))) {
11596
+ throw new TenderCliUsageError(`Refusing to overwrite ${relativePath}. Pass --force to regenerate.`);
11597
+ }
11598
+ await mkdir(path.dirname(absolutePath), { recursive: true });
11599
+ await writeFile(absolutePath, content, 'utf8');
11600
+ written.push(relativePath);
11601
+ }
11602
+ return written;
11603
+ }
11604
+ const SHOPIFY_CONNECTOR_SOURCE_TARGET_PACK_ID = 'shopify-connector-source';
11605
+ const SHOPIFY_CONNECTOR_SOURCE_TARGET_PACK_VERSION = '1';
11606
+ function readConnectorSourceArtifactId(connector) {
11607
+ const sourceProject = isCliJsonRecord(connector.sourceProject) ? connector.sourceProject : null;
11608
+ const sourceArtifactId = typeof sourceProject?.sourceArtifactId === 'string' ? sourceProject.sourceArtifactId.trim() : '';
11609
+ return sourceArtifactId || null;
11610
+ }
11611
+ function defaultShopifySourceName(connector) {
11612
+ const appName = typeof connector.appName === 'string' && connector.appName.trim() ? connector.appName.trim() : 'Tender Prompt Apps';
11613
+ return `${appName} Shopify Source`.slice(0, 120);
11614
+ }
11615
+ async function requestConnector(command, runtime) {
11616
+ const credentials = await resolveConnectorsCredentials(command, runtime);
11617
+ const payload = await requestConnectorsApi({
11618
+ connectorsPath: encodeURIComponent(command.connectorId),
11619
+ method: 'GET',
11620
+ baseUrl: credentials.baseUrl,
11621
+ token: credentials.token,
11622
+ fetcher: runtime.fetcher ?? fetch,
11623
+ });
11624
+ const connector = isCliJsonRecord(payload.connector) ? payload.connector : {};
11625
+ return { credentials, payload, connector };
11626
+ }
11627
+ async function createShopifySourceArtifact(input) {
11628
+ const response = await input.fetcher(`${input.credentials.baseUrl}/api/v1/artifacts`, {
11629
+ method: 'POST',
11630
+ headers: {
11631
+ authorization: `Bearer ${input.credentials.token}`,
11632
+ 'content-type': 'application/json',
11633
+ },
11634
+ body: JSON.stringify({
11635
+ name: input.name,
11636
+ targetPackId: SHOPIFY_CONNECTOR_SOURCE_TARGET_PACK_ID,
11637
+ targetPackVersion: SHOPIFY_CONNECTOR_SOURCE_TARGET_PACK_VERSION,
11638
+ }),
11639
+ });
11640
+ const payload = await parseJsonResponse(response);
11641
+ if (!response.ok || !payload?.ok || !payload.artifact?.artifactId) {
11642
+ throw new Error(payload?.message ?? payload?.reasonCode ?? `Connector source artifact create failed with HTTP ${response.status}.`);
11643
+ }
11644
+ return payload.artifact;
11645
+ }
11646
+ async function patchConnectorSourceProject(input) {
11647
+ const sourceProject = {
11648
+ version: 1,
11649
+ kind: 'shopify_app_source',
11650
+ sourceArtifactId: input.sourceArtifactId,
11651
+ template: input.template,
11652
+ updatedAt: input.updatedAt,
11653
+ };
11654
+ const payload = await requestConnectorsApi({
11655
+ connectorsPath: encodeURIComponent(input.connectorId),
11656
+ method: 'PATCH',
11657
+ baseUrl: input.credentials.baseUrl,
11658
+ token: input.credentials.token,
11659
+ fetcher: input.fetcher,
11660
+ body: { sourceProject },
11661
+ });
11662
+ return { payload, sourceProject };
11663
+ }
11664
+ async function ensureConnectorSourceArtifact(input) {
11665
+ const existingSourceArtifactId = readConnectorSourceArtifactId(input.connector);
11666
+ if (existingSourceArtifactId) {
11667
+ return {
11668
+ sourceArtifactId: existingSourceArtifactId,
11669
+ artifact: null,
11670
+ created: false,
11671
+ };
11672
+ }
11673
+ const artifact = await createShopifySourceArtifact({
11674
+ name: input.command.sourceName?.trim() || defaultShopifySourceName(input.connector),
11675
+ credentials: input.credentials,
11676
+ fetcher: input.fetcher,
11677
+ });
11678
+ return {
11679
+ sourceArtifactId: artifact.artifactId,
11680
+ artifact,
11681
+ created: true,
11682
+ };
11683
+ }
11684
+ async function setupSourceArtifactCheckout(input) {
11685
+ const root = path.resolve(input.dir);
11686
+ const git = await requestGitRemoteLease({
11687
+ artifactId: input.artifactId,
11688
+ ttl: '7d',
11689
+ baseUrl: input.credentials.baseUrl,
11690
+ token: input.credentials.token,
11691
+ fetcher: input.fetcher,
11692
+ });
11693
+ const helper = createCredentialHelperCommand({
11694
+ artifactId: input.artifactId,
11695
+ baseUrl: input.credentials.baseUrl,
11696
+ profileName: input.credentials.profileName,
11697
+ ttl: '7d',
11698
+ remoteUrl: git.remoteUrl,
11699
+ });
11700
+ await mkdir(root, { recursive: true });
11701
+ const gitState = await runGitCommand({
11702
+ runtime: input.runtime,
11703
+ cwd: root,
11704
+ args: ['rev-parse', '--is-inside-work-tree'],
11705
+ allowFailure: true,
11706
+ });
11707
+ if (gitState.exitCode !== 0) {
11708
+ await runGitCommand({
11709
+ runtime: input.runtime,
11710
+ cwd: root,
11711
+ args: ['init', '-b', git.defaultBranch],
11712
+ });
11713
+ }
11714
+ await applyGitSetup({
11715
+ runtime: input.runtime,
11716
+ root,
11717
+ remoteName: input.remoteName,
11718
+ remoteUrl: git.remoteUrl,
11719
+ credentialHelper: helper,
11720
+ });
11721
+ const sourceCheckout = await maybeCheckoutInitialArtifactSource({
11722
+ runtime: input.runtime,
11723
+ root,
11724
+ remoteName: input.remoteName,
11725
+ defaultBranch: git.defaultBranch,
11726
+ });
11727
+ return {
11728
+ root,
11729
+ git,
11730
+ sourceCheckout,
11731
+ remoteName: input.remoteName,
11732
+ credentialHelper: helper,
11733
+ };
11734
+ }
11735
+ async function commitAndPushSourceFiles(input) {
11736
+ const paths = [...new Set(input.paths)].sort();
11737
+ if (paths.length === 0) {
11738
+ return {
11739
+ attempted: false,
11740
+ committed: false,
11741
+ pushed: false,
11742
+ skippedReason: 'no_files',
11743
+ commitSha: null,
11744
+ };
11745
+ }
11746
+ const status = await runGitCommand({
11747
+ runtime: input.runtime,
11748
+ cwd: input.dir,
11749
+ args: ['status', '--porcelain', '--', ...paths],
11750
+ });
11751
+ if (!status.stdout.trim()) {
11752
+ if (input.pushCleanHead) {
11753
+ const commitShaResult = await runGitCommand({
11754
+ runtime: input.runtime,
11755
+ cwd: input.dir,
11756
+ args: ['rev-parse', 'HEAD'],
11757
+ });
11758
+ await runGitCommand({
11759
+ runtime: input.runtime,
11760
+ cwd: input.dir,
11761
+ args: ['push', input.remoteName, input.defaultBranch],
11762
+ });
11763
+ return {
11764
+ attempted: true,
11765
+ committed: false,
11766
+ pushed: true,
11767
+ skippedReason: 'clean_head_pushed',
11768
+ commitSha: commitShaResult.stdout.trim() || null,
11769
+ };
11770
+ }
11771
+ return {
11772
+ attempted: true,
11773
+ committed: false,
11774
+ pushed: false,
11775
+ skippedReason: 'no_changes',
11776
+ commitSha: null,
11777
+ };
11778
+ }
11779
+ await runGitCommand({
11780
+ runtime: input.runtime,
11781
+ cwd: input.dir,
11782
+ args: ['add', '--', ...paths],
11783
+ });
11784
+ await runGitCommand({
11785
+ runtime: input.runtime,
11786
+ cwd: input.dir,
11787
+ args: ['commit', '-m', 'Update Shopify connector source'],
11788
+ });
11789
+ const commitShaResult = await runGitCommand({
11790
+ runtime: input.runtime,
11791
+ cwd: input.dir,
11792
+ args: ['rev-parse', 'HEAD'],
11793
+ });
11794
+ await runGitCommand({
11795
+ runtime: input.runtime,
11796
+ cwd: input.dir,
11797
+ args: ['push', input.remoteName, input.defaultBranch],
11798
+ });
11799
+ return {
11800
+ attempted: true,
11801
+ committed: true,
11802
+ pushed: true,
11803
+ skippedReason: null,
11804
+ commitSha: commitShaResult.stdout.trim() || null,
11805
+ };
11806
+ }
11807
+ function shopifyValidateArgs(input) {
11808
+ return [
11809
+ '--yes',
11810
+ '@shopify/cli@latest',
11811
+ 'app',
11812
+ 'config',
11813
+ 'validate',
11814
+ '--path',
11815
+ input.dir,
11816
+ '--json',
11817
+ ...(input.clientId ? ['--client-id', input.clientId] : []),
11818
+ ];
11819
+ }
11820
+ function shopifyDeployArgs(input) {
11821
+ return [
11822
+ '--yes',
11823
+ '@shopify/cli@latest',
11824
+ 'app',
11825
+ 'deploy',
11826
+ '--path',
11827
+ input.dir,
11828
+ input.allowDeletes ? '--allow-deletes' : '--allow-updates',
11829
+ ...(input.clientId ? ['--client-id', input.clientId] : []),
11830
+ ...(input.version ? ['--version', input.version] : []),
11831
+ ...(input.message ? ['--message', input.message] : []),
11832
+ ...(input.sourceControlUrl ? ['--source-control-url', input.sourceControlUrl] : []),
11833
+ ];
11834
+ }
11835
+ function commandLine(command, args) {
11836
+ return [command, ...args.map(shellQuote)].join(' ');
11837
+ }
11838
+ async function runNpxCommand(input) {
11839
+ const runner = input.runtime.commandRunner ?? defaultCommandRunner;
11840
+ const result = await runner('npx', input.args, { cwd: input.cwd });
11841
+ if (result.exitCode !== 0) {
11842
+ throw new Error([`npx ${input.args.join(' ')} failed with exit code ${result.exitCode}.`, result.stderr.trim() || result.stdout.trim()].filter(Boolean).join('\n'));
11843
+ }
11844
+ return result;
11845
+ }
11846
+ async function runConnectorsShopifySourceInit(command, io, runtime) {
11847
+ const fetcher = runtime.fetcher ?? fetch;
11848
+ const { credentials, payload, connector } = await requestConnector(command, runtime);
11849
+ const sourceArtifact = await ensureConnectorSourceArtifact({
11850
+ command,
11851
+ connector,
11852
+ credentials,
11853
+ fetcher,
11854
+ });
11855
+ const checkout = await setupSourceArtifactCheckout({
11856
+ artifactId: sourceArtifact.sourceArtifactId,
11857
+ dir: command.dir,
11858
+ remoteName: 'tender',
11859
+ credentials,
11860
+ runtime,
11861
+ fetcher,
11862
+ });
11863
+ const dir = path.resolve(command.dir);
11864
+ const source = buildShopifySourceFiles({
11865
+ connectorId: command.connectorId,
11866
+ connector,
11867
+ clientId: command.clientId,
11868
+ template: command.template,
11869
+ tenderAppUrl: command.tenderAppUrl,
11870
+ tenderElement: command.tenderElement,
11871
+ artifactId: command.artifactId,
11872
+ publicId: command.publicId,
11873
+ themeExtensionHandle: command.themeExtensionHandle,
11874
+ themeExtensionUid: command.themeExtensionUid,
11875
+ blockName: command.blockName,
11876
+ scopes: command.scopes,
11877
+ });
11878
+ const files = await writeShopifySourceFiles({ dir, files: source.files, force: command.force });
11879
+ const gitCommit = await commitAndPushSourceFiles({
11880
+ dir,
11881
+ paths: files,
11882
+ remoteName: checkout.remoteName,
11883
+ defaultBranch: checkout.git.defaultBranch,
11884
+ pushCleanHead: sourceArtifact.created || command.force,
11885
+ runtime,
11886
+ });
11887
+ const patched = await patchConnectorSourceProject({
11888
+ connectorId: command.connectorId,
11889
+ sourceArtifactId: sourceArtifact.sourceArtifactId,
11890
+ template: command.template,
11891
+ credentials,
11892
+ fetcher,
11893
+ updatedAt: runtime.now ? runtime.now() : Date.now(),
11894
+ });
9874
11895
  const result = {
9875
11896
  ok: true,
9876
11897
  command: command.command,
9877
- ref: ARTIFACT_MEMORY_NOTES_REF,
9878
- entries: briefEntries,
11898
+ connectorId: command.connectorId,
11899
+ sourceArtifactId: sourceArtifact.sourceArtifactId,
11900
+ sourceArtifactCreated: sourceArtifact.created,
11901
+ sourceArtifact: sourceArtifact.artifact,
11902
+ dir,
11903
+ files,
11904
+ sourceProject: patched.sourceProject,
11905
+ connector: patched.payload.connector ?? payload.connector ?? null,
11906
+ git: {
11907
+ remoteName: checkout.remoteName,
11908
+ remoteUrl: checkout.git.remoteUrl,
11909
+ defaultBranch: checkout.git.defaultBranch,
11910
+ sourceCheckout: checkout.sourceCheckout,
11911
+ commit: gitCommit,
11912
+ },
11913
+ validateCommand: commandLine('npx', shopifyValidateArgs({ dir, clientId: command.clientId })),
11914
+ deployCommand: commandLine('npx', shopifyDeployArgs({ dir, clientId: command.clientId, version: 'tender-widget', message: null, sourceControlUrl: null, allowDeletes: false })),
9879
11915
  };
9880
- if (command.json) {
9881
- io.stdout(JSON.stringify(result, null, 2));
9882
- }
9883
- else {
9884
- io.stdout(briefEntries.length > 0 ? briefEntries.map(formatArtifactMemoryEntry).join('\n') : 'No artifact memory brief is available yet.');
9885
- }
11916
+ printConnectorsPayload(command, io, result, [
11917
+ `connector_id: ${command.connectorId}`,
11918
+ `source_artifact_id: ${sourceArtifact.sourceArtifactId}`,
11919
+ `source_dir: ${dir}`,
11920
+ `files: ${files.length}`,
11921
+ `git_pushed: ${gitCommit.pushed ? 'true' : 'false'}`,
11922
+ `next_validate: ${result.validateCommand}`,
11923
+ ]);
9886
11924
  return 0;
9887
11925
  }
9888
- async function runMemoryFlush(command, io, runtime) {
9889
- const sync = await pushArtifactMemoryNotes({
9890
- repoPath: path.resolve(command.dir),
11926
+ async function runConnectorsShopifySourceCheckout(command, io, runtime) {
11927
+ const fetcher = runtime.fetcher ?? fetch;
11928
+ const { credentials, connector } = await requestConnector(command, runtime);
11929
+ const sourceArtifactId = readConnectorSourceArtifactId(connector);
11930
+ if (!sourceArtifactId) {
11931
+ throw new TenderCliUsageError('Connector does not have a source artifact yet. Run tender connectors shopify source init <connector-id> --dir ./shopify-app first.');
11932
+ }
11933
+ const checkout = await setupSourceArtifactCheckout({
11934
+ artifactId: sourceArtifactId,
11935
+ dir: command.dir,
9891
11936
  remoteName: command.remoteName,
9892
- runner: commandRunnerForMemory(runtime),
11937
+ credentials,
11938
+ runtime,
11939
+ fetcher,
9893
11940
  });
9894
- const next = sync.status === 'failed'
9895
- ? `tender memory flush --dir ${command.dir} --remote ${command.remoteName}`
9896
- : sync.status === 'not_configured'
9897
- ? memoryRecoveryForArtifact(command.artifactId, command.dir)
9898
- : null;
9899
11941
  const result = {
9900
- ok: sync.status === 'pushed',
11942
+ ok: true,
9901
11943
  command: command.command,
9902
- memorySync: sync,
9903
- next,
11944
+ connectorId: command.connectorId,
11945
+ sourceArtifactId,
11946
+ dir: checkout.root,
11947
+ remoteName: checkout.remoteName,
11948
+ remoteUrl: checkout.git.remoteUrl,
11949
+ defaultBranch: checkout.git.defaultBranch,
11950
+ sourceCheckout: checkout.sourceCheckout,
9904
11951
  };
9905
- if (command.json) {
9906
- io.stdout(JSON.stringify(result, null, 2));
9907
- }
9908
- else {
9909
- io.stdout([
9910
- `memory_sync: ${sync.status}`,
9911
- sync.error ? `error: ${sync.error}` : null,
9912
- result.next ? `next: ${result.next}` : null,
9913
- ]
9914
- .filter((line) => Boolean(line))
9915
- .join('\n'));
9916
- }
9917
- return sync.status === 'pushed' ? 0 : 1;
9918
- }
9919
- async function runMemoryExport(command, io, runtime) {
9920
- const entries = await readArtifactMemoryEntries({
9921
- repoPath: path.resolve(command.dir),
9922
- commitSha: command.commitSha,
9923
- runner: commandRunnerForMemory(runtime),
9924
- });
9925
- if (command.format === 'jsonl') {
9926
- io.stdout(entries.map((entry) => JSON.stringify(entry)).join('\n'));
9927
- return 0;
9928
- }
9929
- throw new TenderCliUsageError(`Unsupported memory export format: ${command.format}.\nNext: tender memory export --format jsonl --dir ${command.dir}`);
9930
- }
9931
- async function readLocalChangedFiles(input) {
9932
- const result = await runGitCommand({
9933
- runtime: input.runtime,
9934
- cwd: input.repoPath,
9935
- args: ['status', '--porcelain'],
9936
- allowFailure: true,
9937
- });
9938
- if (result.exitCode !== 0) {
9939
- return [];
9940
- }
9941
- return result.stdout
9942
- .split(/\r?\n/)
9943
- .map((line) => line.slice(3).trim())
9944
- .filter(Boolean)
9945
- .slice(0, 50);
9946
- }
9947
- async function appendAutomaticMemory(input) {
9948
- try {
9949
- return await appendArtifactMemoryNote({
9950
- repoPath: input.repoPath,
9951
- artifactId: input.artifactId,
9952
- kind: input.kind,
9953
- summary: input.summary,
9954
- body: input.body,
9955
- tags: input.tags,
9956
- severity: input.severity,
9957
- status: input.status,
9958
- related: input.related,
9959
- source: {
9960
- actor: 'cli',
9961
- command: input.summary,
9962
- },
9963
- runner: commandRunnerForMemory(input.runtime),
9964
- now: input.runtime.now,
9965
- });
9966
- }
9967
- catch (error) {
9968
- return {
9969
- error: error instanceof Error ? error.message : String(error),
9970
- next: `tender memory add --dir ${input.repoPath} --artifact ${input.artifactId} --kind ${input.kind} --summary ${JSON.stringify(input.summary)}`,
9971
- };
9972
- }
9973
- }
9974
- async function flushAutomaticMemory(input) {
9975
- return pushArtifactMemoryNotes({
9976
- repoPath: input.repoPath,
9977
- remoteName: input.remoteName,
9978
- runner: commandRunnerForMemory(input.runtime),
9979
- });
9980
- }
9981
- async function resolveAnalyticsCredentials(command, runtime) {
9982
- return await resolveApiCredentials({
9983
- baseUrl: command.baseUrl,
9984
- flagToken: command.flagToken,
9985
- profileName: command.profileName,
9986
- env: runtime.env ?? process.env,
9987
- });
11952
+ printConnectorsPayload(command, io, result, [
11953
+ `connector_id: ${command.connectorId}`,
11954
+ `source_artifact_id: ${sourceArtifactId}`,
11955
+ `source_dir: ${checkout.root}`,
11956
+ `remote: ${checkout.remoteName}`,
11957
+ ]);
11958
+ return 0;
9988
11959
  }
9989
- async function resolvePlaybooksCredentials(command, runtime) {
9990
- try {
9991
- return await resolveApiCredentials({
9992
- baseUrl: command.baseUrl,
9993
- flagToken: command.flagToken,
9994
- profileName: command.profileName,
9995
- env: runtime.env ?? process.env,
9996
- });
11960
+ async function runConnectorsShopifySourceValidate(command, io, runtime) {
11961
+ const { connector } = await requestConnector(command, runtime);
11962
+ const sourceArtifactId = readConnectorSourceArtifactId(connector);
11963
+ if (!sourceArtifactId) {
11964
+ throw new TenderCliUsageError('Connector does not have a source artifact yet. Run tender connectors shopify source init <connector-id> --dir ./shopify-app first.');
9997
11965
  }
9998
- catch (error) {
9999
- if (error instanceof TenderCliUsageError) {
10000
- throw new TenderCliUsageError([
10001
- 'Playbooks require Tender authentication.',
10002
- 'Run `tender auth login --device --profile edit-preview --save-profile agent --ttl 7d --json`, set TENDER_API_TOKEN, or pass --token <token>.',
10003
- ].join('\n'));
11966
+ const dir = path.resolve(command.dir);
11967
+ const args = shopifyValidateArgs({ dir, clientId: command.clientId });
11968
+ const required = ['shopify.app.toml', 'connector.json'];
11969
+ for (const file of required) {
11970
+ if (!(await fileExists(path.join(dir, file)))) {
11971
+ throw new TenderCliUsageError(`Missing ${file}. Run tender connectors shopify source init first.`);
10004
11972
  }
10005
- throw error;
10006
- }
10007
- }
10008
- async function resolveDbCredentials(command, runtime) {
10009
- return await resolveApiCredentials({
10010
- baseUrl: command.baseUrl,
10011
- flagToken: command.flagToken,
10012
- profileName: command.profileName,
10013
- env: runtime.env ?? process.env,
10014
- });
10015
- }
10016
- function validateDbSql(sql) {
10017
- if (!sql.trim()) {
10018
- throw new TenderCliUsageError('SQL statement is required.');
10019
- }
10020
- const byteLength = new TextEncoder().encode(sql).byteLength;
10021
- if (byteLength > DB_SQL_STATEMENT_MAX_BYTES) {
10022
- throw new TenderCliUsageError(`SQL statement is limited to ${DB_SQL_STATEMENT_MAX_BYTES} bytes.`);
10023
- }
10024
- }
10025
- async function readDbSql(command, runtime) {
10026
- const sql = command.sqlSource.kind === 'inline'
10027
- ? command.sqlSource.value
10028
- : command.sqlSource.value === '-'
10029
- ? runtime.stdin
10030
- : await readFile(path.resolve(command.sqlSource.value), 'utf8');
10031
- if (!sql) {
10032
- throw new TenderCliUsageError('--file - requires SQL on stdin.');
10033
- }
10034
- validateDbSql(sql);
10035
- return sql;
10036
- }
10037
- function printDbPayload(command, io, payload, fallbackLines) {
10038
- if (command.json) {
10039
- io.stdout(JSON.stringify(payload, null, 2));
10040
11973
  }
10041
- else {
10042
- io.stdout(fallbackLines.join('\n'));
11974
+ if (!command.execute) {
11975
+ printConnectorsPayload(command, io, { ok: true, dryRun: true, command: command.command, connectorId: command.connectorId, sourceArtifactId, dir, shopifyCommand: commandLine('npx', args) }, [`connector_id: ${command.connectorId}`, `source_artifact_id: ${sourceArtifactId}`, `source_dir: ${dir}`, `shopify_command: ${commandLine('npx', args)}`]);
11976
+ return 0;
10043
11977
  }
11978
+ const result = await runNpxCommand({ args, cwd: dir, runtime });
11979
+ printConnectorsPayload(command, io, { ok: true, command: command.command, connectorId: command.connectorId, sourceArtifactId, dir, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr }, [`connector_id: ${command.connectorId}`, `source_artifact_id: ${sourceArtifactId}`, `source_dir: ${dir}`, 'validation: passed']);
11980
+ return 0;
10044
11981
  }
10045
- function printPlaybooksPayload(command, io, payload, fallback) {
10046
- if (command.json) {
10047
- io.stdout(JSON.stringify(payload, null, 2));
11982
+ async function runConnectorsShopifySourceDeploy(command, io, runtime) {
11983
+ const { connector } = await requestConnector(command, runtime);
11984
+ const sourceArtifactId = readConnectorSourceArtifactId(connector);
11985
+ if (!sourceArtifactId) {
11986
+ throw new TenderCliUsageError('Connector does not have a source artifact yet. Run tender connectors shopify source init <connector-id> --dir ./shopify-app first.');
10048
11987
  }
10049
- else {
10050
- io.stdout(fallback);
11988
+ const dir = path.resolve(command.dir);
11989
+ const args = shopifyDeployArgs({
11990
+ dir,
11991
+ clientId: command.clientId,
11992
+ version: command.version,
11993
+ message: command.message,
11994
+ sourceControlUrl: command.sourceControlUrl,
11995
+ allowDeletes: command.allowDeletes,
11996
+ });
11997
+ if (command.confirm !== 'deploy') {
11998
+ printConnectorsPayload(command, io, { ok: true, dryRun: true, command: command.command, connectorId: command.connectorId, sourceArtifactId, dir, shopifyCommand: commandLine('npx', args), confirmRequired: 'deploy' }, [`connector_id: ${command.connectorId}`, `source_artifact_id: ${sourceArtifactId}`, `source_dir: ${dir}`, `shopify_command: ${commandLine('npx', args)}`, 'confirm_required: --confirm deploy']);
11999
+ return 0;
10051
12000
  }
10052
- }
10053
- async function runPlaybooksList(command, io, runtime) {
10054
- const credentials = await resolvePlaybooksCredentials(command, runtime);
10055
- const payload = await requestPlaybooksApi({
10056
- playbooksPath: 'list',
10057
- baseUrl: credentials.baseUrl,
10058
- token: credentials.token,
10059
- fetcher: runtime.fetcher ?? fetch,
10060
- });
10061
- const playbooks = Array.isArray(payload.playbooks) ? payload.playbooks : [];
10062
- printPlaybooksPayload(command, io, payload, playbooks
10063
- .map((playbook) => (isCliJsonRecord(playbook) ? `${String(playbook.id ?? '')}\t${String(playbook.description ?? '')}` : ''))
10064
- .filter(Boolean)
10065
- .join('\n') || 'No Tender Prompt playbooks found.');
12001
+ const result = await runNpxCommand({ args, cwd: dir, runtime });
12002
+ printConnectorsPayload(command, io, { ok: true, command: command.command, connectorId: command.connectorId, sourceArtifactId, dir, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr }, [`connector_id: ${command.connectorId}`, `source_artifact_id: ${sourceArtifactId}`, `source_dir: ${dir}`, 'deploy: completed']);
10066
12003
  return 0;
10067
12004
  }
10068
- async function runPlaybooksGet(command, io, runtime) {
10069
- const credentials = await resolvePlaybooksCredentials(command, runtime);
10070
- const payload = await requestPlaybooksApi({
10071
- playbooksPath: 'get',
10072
- playbookId: command.playbookId,
12005
+ async function runConnectorsShopifyDelete(command, io, runtime) {
12006
+ if (command.confirm !== 'delete') {
12007
+ throw new TenderCliUsageError('--confirm delete is required. Example: tender connectors shopify delete conn_123 --confirm delete --json');
12008
+ }
12009
+ const credentials = await resolveConnectorsCredentials(command, runtime);
12010
+ const payload = await requestConnectorsApi({
12011
+ connectorsPath: encodeURIComponent(command.connectorId),
12012
+ method: 'DELETE',
10073
12013
  baseUrl: credentials.baseUrl,
10074
12014
  token: credentials.token,
10075
12015
  fetcher: runtime.fetcher ?? fetch,
10076
12016
  });
10077
- printPlaybooksPayload(command, io, payload, typeof payload.body === 'string' ? payload.body : JSON.stringify(payload, null, 2));
12017
+ printConnectorsPayload(command, io, { ...payload, connectorId: command.connectorId }, [`connector_id: ${command.connectorId}`, 'status: deleted']);
10078
12018
  return 0;
10079
12019
  }
10080
- async function runPlaybooksFile(command, io, runtime) {
10081
- const credentials = await resolvePlaybooksCredentials(command, runtime);
10082
- const payload = await requestPlaybooksApi({
10083
- playbooksPath: 'file',
10084
- playbookId: command.playbookId,
10085
- filePath: command.filePath,
10086
- baseUrl: credentials.baseUrl,
10087
- token: credentials.token,
10088
- fetcher: runtime.fetcher ?? fetch,
10089
- });
10090
- printPlaybooksPayload(command, io, payload, typeof payload.content === 'string' ? payload.content : JSON.stringify(payload, null, 2));
12020
+ function runConnectorsBind(command, io) {
12021
+ const binding = {
12022
+ id: `shopify-${command.instanceId}`,
12023
+ label: command.label ?? (command.instanceId === 'default' ? 'Shopify connector' : `Shopify connector (${command.instanceId})`),
12024
+ name: command.bindingName,
12025
+ source: `platform:shopify_connector/${command.instanceId}`,
12026
+ interface: 'ShopifyConnector',
12027
+ metadata: {
12028
+ connectorId: command.connectorId,
12029
+ },
12030
+ };
12031
+ const payload = {
12032
+ ok: true,
12033
+ command: command.command,
12034
+ connectorId: command.connectorId,
12035
+ instanceId: command.instanceId,
12036
+ binding,
12037
+ appJsonPatch: {
12038
+ bindingsAppend: binding,
12039
+ },
12040
+ };
12041
+ if (command.json) {
12042
+ io.stdout(JSON.stringify(payload, null, 2));
12043
+ }
12044
+ else {
12045
+ io.stdout(JSON.stringify(binding, null, 2));
12046
+ }
10091
12047
  return 0;
10092
12048
  }
10093
12049
  async function runDbCapabilities(command, io, runtime) {
@@ -11032,6 +12988,51 @@ export async function runTenderCli(args, io = {
11032
12988
  else if (command.topic === 'playbooks file') {
11033
12989
  io.stdout(playbooksFileHelp());
11034
12990
  }
12991
+ else if (command.topic === 'connectors') {
12992
+ io.stdout(connectorsHelp());
12993
+ }
12994
+ else if (command.topic === 'connectors list') {
12995
+ io.stdout(connectorsHelp());
12996
+ }
12997
+ else if (command.topic === 'connectors shopify') {
12998
+ io.stdout(connectorsHelp());
12999
+ }
13000
+ else if (command.topic === 'connectors shopify create') {
13001
+ io.stdout(connectorsShopifyCreateHelp());
13002
+ }
13003
+ else if (command.topic === 'connectors shopify setup-url') {
13004
+ io.stdout(connectorsShopifySetupUrlHelp());
13005
+ }
13006
+ else if (command.topic === 'connectors shopify credentials set') {
13007
+ io.stdout(connectorsShopifyCredentialsSetHelp());
13008
+ }
13009
+ else if (command.topic === 'connectors shopify app-automation-token set') {
13010
+ io.stdout(connectorsShopifyAppAutomationTokenSetHelp());
13011
+ }
13012
+ else if (command.topic === 'connectors shopify app-automation-token delete') {
13013
+ io.stdout(connectorsShopifyAppAutomationTokenDeleteHelp());
13014
+ }
13015
+ else if (command.topic === 'connectors shopify verify') {
13016
+ io.stdout(connectorsShopifyVerifyHelp());
13017
+ }
13018
+ else if (command.topic === 'connectors shopify source init') {
13019
+ io.stdout(connectorsShopifySourceInitHelp());
13020
+ }
13021
+ else if (command.topic === 'connectors shopify source checkout') {
13022
+ io.stdout(connectorsShopifySourceCheckoutHelp());
13023
+ }
13024
+ else if (command.topic === 'connectors shopify source validate') {
13025
+ io.stdout(connectorsShopifySourceValidateHelp());
13026
+ }
13027
+ else if (command.topic === 'connectors shopify source deploy') {
13028
+ io.stdout(connectorsShopifySourceDeployHelp());
13029
+ }
13030
+ else if (command.topic === 'connectors shopify delete') {
13031
+ io.stdout(connectorsShopifyDeleteHelp());
13032
+ }
13033
+ else if (command.topic === 'connectors bind') {
13034
+ io.stdout(connectorsBindHelp());
13035
+ }
11035
13036
  else if (command.topic === 'auth') {
11036
13037
  io.stdout(authHelp());
11037
13038
  }
@@ -11259,6 +13260,45 @@ export async function runTenderCli(args, io = {
11259
13260
  if (command.command === 'playbooks file') {
11260
13261
  return await runPlaybooksFile(command, io, runtime);
11261
13262
  }
13263
+ if (command.command === 'connectors list') {
13264
+ return await runConnectorsList(command, io, runtime);
13265
+ }
13266
+ if (command.command === 'connectors shopify create') {
13267
+ return await runConnectorsShopifyCreate(command, io, runtime);
13268
+ }
13269
+ if (command.command === 'connectors shopify setup-url') {
13270
+ return await runConnectorsShopifySetupUrl(command, io, runtime);
13271
+ }
13272
+ if (command.command === 'connectors shopify credentials set') {
13273
+ return await runConnectorsShopifyCredentialsSet(command, io, runtime);
13274
+ }
13275
+ if (command.command === 'connectors shopify app-automation-token set') {
13276
+ return await runConnectorsShopifyAppAutomationTokenSet(command, io, runtime);
13277
+ }
13278
+ if (command.command === 'connectors shopify app-automation-token delete') {
13279
+ return await runConnectorsShopifyAppAutomationTokenDelete(command, io, runtime);
13280
+ }
13281
+ if (command.command === 'connectors shopify verify') {
13282
+ return await runConnectorsShopifyVerify(command, io, runtime);
13283
+ }
13284
+ if (command.command === 'connectors shopify source init') {
13285
+ return await runConnectorsShopifySourceInit(command, io, runtime);
13286
+ }
13287
+ if (command.command === 'connectors shopify source checkout') {
13288
+ return await runConnectorsShopifySourceCheckout(command, io, runtime);
13289
+ }
13290
+ if (command.command === 'connectors shopify source validate') {
13291
+ return await runConnectorsShopifySourceValidate(command, io, runtime);
13292
+ }
13293
+ if (command.command === 'connectors shopify source deploy') {
13294
+ return await runConnectorsShopifySourceDeploy(command, io, runtime);
13295
+ }
13296
+ if (command.command === 'connectors shopify delete') {
13297
+ return await runConnectorsShopifyDelete(command, io, runtime);
13298
+ }
13299
+ if (command.command === 'connectors bind') {
13300
+ return runConnectorsBind(command, io);
13301
+ }
11262
13302
  if (command.command === 'app list') {
11263
13303
  return await runArtifactsList(command, io, runtime);
11264
13304
  }
@@ -11425,7 +13465,7 @@ export async function runTenderCli(args, io = {
11425
13465
  }
11426
13466
  catch (error) {
11427
13467
  if (args.includes('--json') &&
11428
- ((args[0] === 'app' && (args[1] === 'db' || (args[1] === 'agent' && args[2] === 'heartbeat'))) || args[0] === 'playbooks')) {
13468
+ ((args[0] === 'app' && (args[1] === 'db' || (args[1] === 'agent' && args[2] === 'heartbeat'))) || args[0] === 'playbooks' || args[0] === 'connectors')) {
11429
13469
  io.stdout(JSON.stringify(cliErrorPayload(error, args, command), null, 2));
11430
13470
  return error instanceof TenderCliUsageError ? 2 : 1;
11431
13471
  }
@@ -11439,13 +13479,23 @@ function hasFlagValue(args, flag, value) {
11439
13479
  return args.some((arg, index) => (arg === flag && args[index + 1] === value) || arg === `${flag}=${value}`);
11440
13480
  }
11441
13481
  export function shouldReadEntrypointStdin(args) {
11442
- return (args[0] === 'app' &&
11443
- ((args[1] === 'git' && args[2] === 'credential') ||
11444
- (args[1] === 'analytics' && hasFlagValue(args, '--spec', '-')) ||
11445
- (args[1] === 'agent' && args[2] === 'heartbeat' && hasFlagValue(args, '--input', '-')) ||
11446
- (args[1] === 'db' && (args[2] === 'resources' || args[2] === 'widgets') && hasFlagValue(args, '--spec', '-')) ||
11447
- (args[1] === 'db' && args[2] === 'resources' && hasFlagValue(args, '--view', '-')) ||
11448
- (args[1] === 'db' && args[2] === 'query' && hasFlagValue(args, '--file', '-'))));
13482
+ return ((args[0] === 'connectors' &&
13483
+ args[1] === 'shopify' &&
13484
+ args[2] === 'credentials' &&
13485
+ args[3] === 'set' &&
13486
+ args.includes('--client-secret-stdin')) ||
13487
+ (args[0] === 'connectors' &&
13488
+ args[1] === 'shopify' &&
13489
+ args[2] === 'app-automation-token' &&
13490
+ args[3] === 'set' &&
13491
+ args.includes('--token-stdin')) ||
13492
+ (args[0] === 'app' &&
13493
+ ((args[1] === 'git' && args[2] === 'credential') ||
13494
+ (args[1] === 'analytics' && hasFlagValue(args, '--spec', '-')) ||
13495
+ (args[1] === 'agent' && args[2] === 'heartbeat' && hasFlagValue(args, '--input', '-')) ||
13496
+ (args[1] === 'db' && (args[2] === 'resources' || args[2] === 'widgets') && hasFlagValue(args, '--spec', '-')) ||
13497
+ (args[1] === 'db' && args[2] === 'resources' && hasFlagValue(args, '--view', '-')) ||
13498
+ (args[1] === 'db' && args[2] === 'query' && hasFlagValue(args, '--file', '-')))));
11449
13499
  }
11450
13500
  async function readEntrypointStdin() {
11451
13501
  if (process.stdin.isTTY) {