@my-life-buddies/cli 0.26.0-dev.0 → 0.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/web/app.js CHANGED
@@ -567,6 +567,7 @@
567
567
  }
568
568
 
569
569
  const widgets = window.createSimulatorWidgets?.({ api, avatar: () => messageAvatar("assistant") });
570
+ const products = window.createSimulatorProducts?.({ api, refresh: () => refreshSimulator() });
570
571
  function renderWidgets() { widgets?.update({ reloading: ["reloading", "reload_failed"].includes(state.dev.phase), active: state.dev.phase === "ready" && !state.resetting, conversationRevision: state.simulator.conversationRevision, devRevision: state.dev.revision }); }
571
572
 
572
573
  let messageSignature = "";
@@ -604,7 +605,8 @@
604
605
 
605
606
  function renderMessages() {
606
607
  renderWidgets();
607
- const signature = JSON.stringify([state.simulator.messages, state.simulator.activeRun?.runId]);
608
+ products?.update({ active: state.dev.phase === "ready" && !state.resetting, conversationRevision: state.simulator.conversationRevision, devRevision: state.dev.revision });
609
+ const signature = JSON.stringify([state.simulator.messages, state.simulator.activeRun?.runId, state.dev.phase, state.resetting]);
608
610
  if (signature === messageSignature) { renderLiveReply(); return; }
609
611
  messageSignature = signature;
610
612
  const previousTop = elements.chatMessages.scrollTop;
@@ -623,7 +625,8 @@
623
625
  }
624
626
  const row = node("div", { class: "message-row", "data-role": message.role });
625
627
  const wrap = node("div", { class: "message-content" });
626
- if (message.widget && widgets) wrap.append(widgets.card(message.widget, message.messageId));
628
+ if (message.product && products) wrap.append(products.card(message.product));
629
+ else if (message.widget && widgets) wrap.append(widgets.card(message.widget, message.messageId));
627
630
  else {
628
631
  const bubble = node("div", { class: "message-bubble" });
629
632
  renderMessageText(bubble, message.text);
@@ -866,7 +869,7 @@
866
869
  if (!window.confirm("清空当前对话并重新开始?\n这会结束当前开发版会话,重置聊天上下文、记忆和该会话的主动服务。旧记录仅在服务端留档;搭子资料和工程文件会保留。")) return;
867
870
  state.resetting = true;
868
871
  elements.errorBanner.hidden = true;
869
- widgets?.reset();
872
+ widgets?.reset(); products?.reset();
870
873
  state.simulatorRequest++;
871
874
  elements.resetConversation.textContent = "正在清空…";
872
875
  renderDev();
@@ -965,7 +968,7 @@
965
968
  connectionReady = false;
966
969
  state.dev = { phase: "idle", revision: -1 };
967
970
  state.simulatorRequest++;
968
- widgets?.reset();
971
+ widgets?.reset(); products?.reset();
969
972
  void togglePairing(false);
970
973
  renderDev(); syncPolling();
971
974
  $("#local-status").textContent = "本地工程";
@@ -981,9 +984,9 @@
981
984
  if (!response.ok) throw new Error(value.error || "连接状态读取失败");
982
985
  if (epoch !== connectionEpoch) return;
983
986
  connectionChecked = true; connectionFailure = "";
984
- $("#connect-command").textContent = value.command.replace(/^buddy-cli(?= )/, "buddy-cli-sit");
987
+ $("#connect-command").textContent = value.command.replace(/^buddy-cli(?= )/, "buddy-cli");
985
988
  $("#copy-connect-command").disabled = false;
986
- $("#stop-command").textContent = value.stopCommand?.replace(/^buddy-cli(?= )/, "buddy-cli-sit") || "buddy-cli-sit dev stop";
989
+ $("#stop-command").textContent = value.stopCommand?.replace(/^buddy-cli(?= )/, "buddy-cli") || "buddy-cli dev stop";
987
990
  if (value.projectRoot) $("#project-root").textContent = value.projectRoot;
988
991
  if (value.machineName) $("#machine-name").textContent = value.machineName;
989
992
  if (!value.connected) { connectionInstance = value.instanceId || ""; disconnected(); return; }
@@ -13,11 +13,13 @@
13
13
  <link rel="stylesheet" href="/assets/app.css">
14
14
  <script src="/assets/iphone-16-max.js" defer></script>
15
15
  <script src="/assets/widgets.js" defer></script>
16
+ <script src="/assets/products.js" defer></script>
16
17
  <script src="/assets/diagnostics.js" defer></script>
17
18
  <script src="/assets/tool-inventory.js" defer></script>
18
19
  <script src="/assets/chat-markdown.js" defer></script>
19
20
  <script src="/assets/app.js" defer></script>
20
21
  <link rel="stylesheet" href="/assets/widgets.css">
22
+ <link rel="stylesheet" href="/assets/products.css">
21
23
  </head>
22
24
  <body>
23
25
  <a class="skip-link" href="#debug-content">跳到调试内容</a>
@@ -0,0 +1,19 @@
1
+ /* 随手机画布缩放;详情在手机内独立滚动,购买按钮保持可见。 */
2
+ .product-card { display:flex; flex-direction:column; gap:calc(var(--widget-unit, 1px)*10); width:100%; min-width:0; padding:calc(var(--widget-unit, 1px)*18); border:1px solid #ddc9af; border-radius:calc(var(--widget-unit, 1px)*20); background:linear-gradient(145deg,#fffaf4,#eed8bf); color:#513516; text-align:left; white-space:normal; overflow-wrap:anywhere; font:inherit; }
3
+ .product-card strong { font-size:1.1em; }
4
+ .product-card-label { font-size:.75em; opacity:.75; }
5
+ .product-card-action { border-top:1px solid #b9976d55; padding-top:.6em; font-weight:600; }
6
+ .product-overlay[hidden] { display:none; }
7
+ .product-overlay { position:absolute; inset:0; z-index:30; display:flex; align-items:flex-end; background:#0005; font-size:calc(var(--widget-unit, 1px)*16); color:#513516; }
8
+ .product-sheet { width:100%; min-width:0; max-height:87%; display:flex; flex-direction:column; overflow:hidden; border-radius:calc(var(--widget-unit, 1px)*28) calc(var(--widget-unit, 1px)*28) 0 0; background:#fff8ef; box-sizing:border-box; }
9
+ .product-sheet-header { display:flex; align-items:center; justify-content:space-between; gap:.6em; padding:1em; border-bottom:1px solid #e7d9c7; }
10
+ .product-sheet-header h3 { font-size:1.1em; margin:0; min-width:0; overflow-wrap:anywhere; }
11
+ .product-sheet button { font:inherit; cursor:pointer; border:1px solid #d9c4a9; background:#fff; color:#513516; border-radius:.8em; padding:.7em; }
12
+ .product-sheet button:disabled { opacity:.55; cursor:wait; }
13
+ .product-sheet-content { overflow-y:auto; overscroll-behavior:contain; padding:0 1em 1em; min-height:0; overflow-wrap:anywhere; }
14
+ .product-sheet h4 { font-size:1em; margin:1em 0 .4em; }
15
+ .product-sheet p { white-space:pre-wrap; line-height:1.55; margin:.5em 0; }
16
+ .product-sheet .product-notice { font-size:.85em; padding:.75em; background:#f1e4d2; border-radius:.8em; }
17
+ .product-sheet-footer { flex:none; padding:.8em 1em 1.8em; border-top:1px solid #e7d9c7; background:#fff8ef; }
18
+ .product-sheet .product-primary { width:100%; background:#63451f; color:white; font-weight:600; }
19
+ .product-success { color:#28603c; }
@@ -0,0 +1,104 @@
1
+ /* 商品卡预览:只使用当前 DEV 会话的真实卡片和零扣费模拟购买接口。 */
2
+ (() => {
3
+ const el = (tag, className, text) => { const item = document.createElement(tag); item.className = className; if (text !== undefined) item.textContent = text; return item; };
4
+ const button = (text, action, className = '') => { const item = el('button', className, text); item.type = 'button'; item.addEventListener('click', action); return item; };
5
+ window.createSimulatorProducts = ({ api, refresh }) => {
6
+ const phone = document.querySelector('.phone-screen');
7
+ const overlay = el('section', 'product-overlay'); overlay.hidden = true; overlay.setAttribute('role', 'dialog'); overlay.setAttribute('aria-modal', 'true'); overlay.setAttribute('aria-label', '商品方案与模拟购买'); phone.append(overlay);
8
+ let active = false, scope, revision, generation = 0, selected, busy = false, previousFocus;
9
+ const purchaseKeys = new Map();
10
+ function close(force = false) {
11
+ if (busy && !force) return;
12
+ generation++; selected = undefined; busy = false; overlay.hidden = true; overlay.replaceChildren();
13
+ for (const child of phone.children) if (child !== overlay) child.inert = false;
14
+ previousFocus?.focus(); previousFocus = undefined;
15
+ }
16
+ function shell(title) {
17
+ overlay.replaceChildren();
18
+ const sheet = el('div', 'product-sheet'), header = el('header', 'product-sheet-header');
19
+ const dismiss = button('关闭', () => close()); dismiss.disabled = busy;
20
+ header.append(el('h3', '', title), dismiss); sheet.append(header); overlay.append(sheet); return sheet;
21
+ }
22
+ function section(container, title, text) { if (!text) return; container.append(el('h4', '', title), el('p', '', text)); }
23
+ function credentialCopy(credential) { return ({ pending: '购买已确认,等待搭子开通', active: '服务使用中', ended: '服务已结束', revoked: '模拟服务已解除' })[credential?.state] || '购买已确认'; }
24
+ async function open(card) {
25
+ if (!active) return;
26
+ selected = card; previousFocus ||= document.activeElement; overlay.hidden = false;
27
+ for (const child of phone.children) if (child !== overlay) child.inert = true;
28
+ await load();
29
+ }
30
+ async function load(note = '') {
31
+ const card = selected, mine = ++generation;
32
+ if (!card) return;
33
+ busy = false; shell('读取商品方案…').append(el('p', 'product-notice', '正在读取平台商品和购买状态'));
34
+ try {
35
+ const detail = await api(`/api/simulator/products/${encodeURIComponent(card.offerId)}`);
36
+ if (mine !== generation || !active) return;
37
+ const product = detail.product;
38
+ if (!product || !Number.isSafeInteger(detail.version) || detail.paymentChannel !== 'mock' || detail.chargedAmountMinor !== 0) throw new Error('当前方案不支持零扣费预览');
39
+ const sheet = shell(product.name), content = el('div', 'product-sheet-content');
40
+ content.append(el('p', 'product-notice', '开发预览 · 零扣费模拟购买 · 不影响正式搭子的购买权益'));
41
+ if (note) content.append(el('p', 'product-notice', note));
42
+ section(content, '服务介绍', product.description);
43
+ section(content, '本次关注', detail.personalization?.needSummary || detail.personalization?.cardSummary);
44
+ const benefits = el('div', 'product-benefits');
45
+ for (const benefit of product.benefits || []) section(benefits, benefit.title, benefit.description);
46
+ content.append(benefits);
47
+ const price = product.requestedPrice;
48
+ section(content, '方案价格', `${price.currency} ${(price.amountMinor / 100).toFixed(2)} / ${product.pricingUnitLabel}(本次实际扣费 0 元)`);
49
+ section(content, '开始条件', product.productTerms?.startCondition);
50
+ section(content, '结束条件', product.productTerms?.endCondition);
51
+ section(content, '服务边界', (product.limitations || []).join('\n'));
52
+ const footer = el('footer', 'product-sheet-footer');
53
+ if (detail.existingPurchase) {
54
+ footer.append(el('p', 'product-success', credentialCopy(detail.existingPurchase.credential)), button('返回对话', () => close(), 'product-primary'));
55
+ } else if (!detail.canPurchase) {
56
+ footer.append(el('p', 'product-notice', detail.unavailableReason || '暂不可购买'), button('重新加载', () => load()));
57
+ } else {
58
+ const confirm = button('确认模拟购买 · 0 元', async () => {
59
+ if (busy || mine !== generation || !active) return;
60
+ busy = true; confirm.disabled = true; confirm.textContent = '正在确认…';
61
+ const key = `${card.offerId}:${detail.version}`;
62
+ if (!purchaseKeys.has(key)) purchaseKeys.set(key, `preview-${crypto.randomUUID()}`);
63
+ try {
64
+ const result = await api(`/api/simulator/products/${encodeURIComponent(card.offerId)}/mock-purchase`, { method: 'POST', body: JSON.stringify({ expectedVersion: detail.version, idempotencyKey: purchaseKeys.get(key), conversationRevision: revision }) });
65
+ if (mine !== generation) return;
66
+ if (result.status !== 'simulated_success' || result.chargedAmountMinor !== 0) throw new Error('尚未确认购买结果,请重新读取状态');
67
+ busy = false; await load('模拟购买成功,实际扣费 0 元。回到对话后继续体验服务。'); await refresh();
68
+ } catch (error) {
69
+ if (mine !== generation) return;
70
+ busy = false; await load(error.code === 'PRODUCT_VERSION_CHANGED' ? '商品已更新,请重新查看方案并确认。' : `上次结果尚未确认:${error.message}。已重新读取状态,重试会复用同一购买请求。`);
71
+ }
72
+ }, 'product-primary');
73
+ footer.append(confirm);
74
+ }
75
+ sheet.append(content, footer); sheet.querySelector('button')?.focus();
76
+ } catch (error) {
77
+ if (mine !== generation) return;
78
+ const sheet = shell('商品暂时无法读取'); sheet.append(el('p', 'product-notice', error.message), button('重试', () => load()));
79
+ }
80
+ }
81
+ overlay.addEventListener('keydown', event => {
82
+ if (event.key === 'Escape') { event.preventDefault(); close(); }
83
+ if (event.key === 'Tab') {
84
+ const buttons = [...overlay.querySelectorAll('button:not(:disabled)')];
85
+ const first = buttons[0], last = buttons.at(-1);
86
+ if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last?.focus(); }
87
+ else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first?.focus(); }
88
+ }
89
+ });
90
+ return {
91
+ card(product) {
92
+ const card = button('', () => open(product), 'product-card'); card.disabled = !active;
93
+ card.setAttribute('aria-label', `${product.title},${product.buttonText}`);
94
+ card.append(el('span', 'product-card-label', '服务方案'), el('strong', '', product.title), el('span', 'product-card-summary', product.summary), el('span', 'product-card-action', `${product.buttonText} ›`)); return card;
95
+ },
96
+ update(next) {
97
+ const nextScope = `${next.conversationRevision}:${next.devRevision}`;
98
+ if (scope !== undefined && scope !== nextScope || !next.active) close(true);
99
+ active = next.active; scope = nextScope; revision = next.conversationRevision;
100
+ },
101
+ reset() { active = false; scope = undefined; close(true); purchaseKeys.clear(); },
102
+ };
103
+ };
104
+ })();
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@my-life-buddies/cli",
3
- "version": "0.26.0-dev.0",
3
+ "version": "0.26.1",
4
4
  "type": "module",
5
- "description": "搭搭 SIT 开发命令行工具",
5
+ "description": "搭搭 Developer Platform 命令行工具",
6
6
  "publishConfig": {
7
7
  "access": "public",
8
8
  "registry": "https://registry.npmjs.org/",
9
- "tag": "dev"
9
+ "tag": "latest"
10
10
  },
11
11
  "engines": {
12
12
  "node": ">=24"
@@ -15,7 +15,7 @@
15
15
  "darwin"
16
16
  ],
17
17
  "bin": {
18
- "buddy-cli-sit": "dist/bin/buddy.js"
18
+ "buddy-cli": "dist/bin/buddy.js"
19
19
  },
20
20
  "files": [
21
21
  "dist/bin",
@@ -42,8 +42,8 @@
42
42
  "acorn": "8.15.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@buddy/cli-core": "0.26.0-dev.0",
46
- "@buddy/preview": "0.26.0-dev.0"
45
+ "@buddy/cli-core": "0.26.1",
46
+ "@buddy/preview": "0.26.1"
47
47
  },
48
48
  "repository": {
49
49
  "type": "git",