@tenderprompt/cli 0.1.27 → 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 +192 -13
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -434,8 +434,8 @@ Examples:
434
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
435
  tender connectors shopify verify conn_123 --profile account --json
436
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 --dir ./shopify-app --execute --json
438
- SHOPIFY_APP_AUTOMATION_TOKEN=shpat_... tender connectors shopify source deploy --dir ./shopify-app --confirm deploy --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
439
  tender connectors bind SHOPIFY_CONNECTOR conn_123 --instance default --json`;
440
440
  }
441
441
  function connectorsShopifyCreateHelp() {
@@ -1476,6 +1476,47 @@ function tenderCliCapabilities() {
1476
1476
  persistence: 'session_only',
1477
1477
  writesToCheckout: false,
1478
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
+ },
1479
1520
  analyticsAuthoring: {
1480
1521
  command: ANALYTICS_AUTHORING_COMMAND,
1481
1522
  playbookId: ANALYTICS_AUTHORING_PLAYBOOK_ID,
@@ -1502,6 +1543,8 @@ function tenderCliCapabilities() {
1502
1543
  'tender app db --help',
1503
1544
  'tender app bindings outbound --help',
1504
1545
  'tender app outbound-auth --help',
1546
+ 'tender connectors --help',
1547
+ 'tender connectors shopify source init --help',
1505
1548
  ],
1506
1549
  },
1507
1550
  workflows: [
@@ -1527,6 +1570,23 @@ function tenderCliCapabilities() {
1527
1570
  'Do not write fetched playbook contents into the checkout or install them as skills.',
1528
1571
  ],
1529
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
+ },
1530
1590
  {
1531
1591
  name: 'create_new_app_with_preview',
1532
1592
  when: 'When the user wants a new Tender App that is immediately visible.',
@@ -11116,7 +11176,9 @@ function defaultDogProfileScopes(connectorScopes, override) {
11116
11176
  }
11117
11177
  function tenderWidgetAssetJs() {
11118
11178
  return `(function () {
11119
- const loader = (window.TenderPromptBlockLoader ||= { scripts: new Map() });
11179
+ const loader = (window.TenderPromptBlockLoader ||= {});
11180
+ loader.scripts ||= new Map();
11181
+ loader.stylesheets ||= new Map();
11120
11182
 
11121
11183
  function loadScript(src) {
11122
11184
  if (!src) return Promise.reject(new Error("Missing Tender runtime URL"));
@@ -11142,6 +11204,96 @@ function tenderWidgetAssetJs() {
11142
11204
  return promise;
11143
11205
  }
11144
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
+
11145
11297
  class TenderPromptWidgetBlock extends HTMLElement {
11146
11298
  connectedCallback() {
11147
11299
  this.mount();
@@ -11152,7 +11304,13 @@ function tenderWidgetAssetJs() {
11152
11304
  const elementTag = this.dataset.tenderElementTag;
11153
11305
  if (!mount || !elementTag || mount.dataset.tenderMounted === "true") return;
11154
11306
 
11307
+ mount.setAttribute("aria-busy", "true");
11308
+ preloadStylesheet(this.dataset.stylesheetUrl);
11155
11309
  await loadScript(this.dataset.tenderRuntimeSrc);
11310
+ await Promise.race([
11311
+ customElements.whenDefined(elementTag),
11312
+ new Promise((resolve) => setTimeout(resolve, 1000)),
11313
+ ]);
11156
11314
 
11157
11315
  const widget = document.createElement(elementTag);
11158
11316
  for (const [key, value] of Object.entries(this.dataset)) {
@@ -11160,8 +11318,14 @@ function tenderWidgetAssetJs() {
11160
11318
  widget.dataset[key] = value;
11161
11319
  }
11162
11320
 
11163
- mount.replaceChildren(widget);
11321
+ widget.style.visibility = "hidden";
11322
+ widget.style.position = "absolute";
11323
+ widget.style.inset = "0";
11324
+ widget.style.pointerEvents = "none";
11325
+ mount.appendChild(widget);
11164
11326
  mount.dataset.tenderMounted = "true";
11327
+ await waitForWidgetPaintReady(widget, this.dataset.stylesheetUrl);
11328
+ revealWidget(mount, widget);
11165
11329
  }
11166
11330
  }
11167
11331
 
@@ -11180,6 +11344,7 @@ function buildShopifySourceFiles(input) {
11180
11344
  const scopes = defaultDogProfileScopes(input.connector.scopes, input.scopes);
11181
11345
  const artifactId = input.artifactId ?? 'artifact_replace_me';
11182
11346
  const publicId = input.publicId ?? 'tender-widget';
11347
+ const stylesheetUrl = new URL('./styles.css', input.tenderAppUrl).href;
11183
11348
  const blockName = input.blockName.trim().slice(0, 120) || 'Tender Prompt Widget';
11184
11349
  const connectorManifest = {
11185
11350
  version: 1,
@@ -11196,6 +11361,7 @@ function buildShopifySourceFiles(input) {
11196
11361
  publicId,
11197
11362
  elementTag: tenderElement,
11198
11363
  runtimeUrl: input.tenderAppUrl,
11364
+ stylesheetUrl,
11199
11365
  },
11200
11366
  secrets: {
11201
11367
  clientSecret: 'stored in Tender connector credentials, not in this source tree',
@@ -11258,8 +11424,13 @@ access.admin = "merchant_read_write"
11258
11424
 
11259
11425
  {% liquid
11260
11426
  assign artifact_id = block.settings.artifact_id | default: ${tomlString(artifactId)}
11261
- assign runtime_src = block.settings.runtime_src | default: ${tomlString(input.tenderAppUrl)}
11262
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
11263
11434
  %}
11264
11435
 
11265
11436
  <tender-prompt-widget-block
@@ -11269,6 +11440,7 @@ access.admin = "merchant_read_write"
11269
11440
  data-tender-public-id="{{ block.settings.public_id | escape }}"
11270
11441
  data-tender-version="{{ widget_version | escape }}"
11271
11442
  data-tender-runtime-src="{{ runtime_src | escape }}"
11443
+ data-stylesheet-url="{{ stylesheet_src | escape }}"
11272
11444
  data-tender-element-tag="{{ block.settings.element_tag | escape }}"
11273
11445
  data-tender-mode="shopify_app_block"
11274
11446
  data-surface="shopify_theme_app_extension"
@@ -11285,8 +11457,21 @@ access.admin = "merchant_read_write"
11285
11457
  data-tender-product-handle="{{ product.handle | escape }}"
11286
11458
  {% endif %}
11287
11459
  >
11288
- <div data-tender-mount>
11289
- <p>{{ block.settings.heading }}</p>
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>
11290
11475
  </div>
11291
11476
  </tender-prompt-widget-block>
11292
11477
 
@@ -11296,12 +11481,6 @@ access.admin = "merchant_read_write"
11296
11481
  "target": "section",
11297
11482
  "javascript": "tender-widget.js",
11298
11483
  "settings": [
11299
- {
11300
- "type": "text",
11301
- "id": "runtime_src",
11302
- "label": "Tender runtime URL",
11303
- "default": ${JSON.stringify(input.tenderAppUrl)}
11304
- },
11305
11484
  {
11306
11485
  "type": "text",
11307
11486
  "id": "element_tag",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenderprompt/cli",
3
- "version": "0.1.27",
3
+ "version": "0.1.28",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "tender": "bin/tender.js"