@botim/mp-debug-sdk 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,10 +13,15 @@ Mini-programs run on user devices in environments you can't easily attach a debu
13
13
 
14
14
  ## Install
15
15
 
16
+ > **Heads-up — registry override required in BOTIM repos.**
17
+ > Most BOTIM mini-program repos pin the `@botim` scope to the internal Artifactory mirror (`@botim:registry=https://artifactory.corp.algento.com/artifactory/api/npm/bot-npm/`). This SDK is **published to public npm**, so a plain `npm install @botim/mp-debug-sdk` in those repos will hit Artifactory, not find the package, and 404. Use the public registry explicitly for this one package:
18
+
16
19
  ```bash
17
- npm install @botim/mp-debug-sdk
20
+ npm install @botim/mp-debug-sdk --registry=https://registry.npmjs.org/
18
21
  ```
19
22
 
23
+ If your repo doesn't have an `@botim` scope override (rare), the flag is harmless — npm uses `registry.npmjs.org` by default. **Don't** add `@botim:registry=https://registry.npmjs.org/` to your `.npmrc`: it would shadow the internal Artifactory registry that other `@botim/*` packages (mp-framework, etc.) need.
24
+
20
25
  ## 1. Add the env config files
21
26
 
22
27
  Each environment of your mini-program ships with one config file at the project root, in the standard BOTIM mini-program schema:
@@ -237,6 +242,16 @@ Once your build is wired and shipped, see **[`docs/live-debugging.md`](./docs/li
237
242
  | `RemoteDebugHandle.flush()` | Force-flush the in-memory buffer. |
238
243
  | `RemoteDebugHandle.stop()` | Uninstall and drain the queue. |
239
244
 
245
+ ## AI debug skill (Claude Code)
246
+
247
+ A Claude Code skill that teaches AI agents how to wire and consume this SDK lives at `.claude/skills/botim-debug-relay/SKILL.md`. Open Claude Code from this repo and the skill auto-loads — no setup needed. To use it from anywhere on your machine:
248
+
249
+ ```bash
250
+ cp -r .claude/skills/botim-debug-relay ~/.claude/skills/
251
+ ```
252
+
253
+ > **The file in this repo is a mirror.** The canonical copy lives in `botim-debug-relay/.claude/skills/botim-debug-relay/SKILL.md`. **Do not edit the SDK-side copy directly** — your changes will be overwritten by the next sync. Edit in the relay repo, then run `bash bin/sync-skill.sh` from that repo to propagate here.
254
+
240
255
  ## License
241
256
 
242
257
  [ISC](./LICENSE) © BOTIM
package/dist/index.cjs CHANGED
@@ -22,6 +22,214 @@ var BotimConsentError = class extends Error {
22
22
  }
23
23
  };
24
24
 
25
+ // src/consent-ui.ts
26
+ var STORAGE_PREFIX = "__botim_debug_consent_";
27
+ var CSS_PREFIX = "__botim-debug-consent";
28
+ var STYLE_ELEMENT_ID = `${CSS_PREFIX}-styles`;
29
+ var DEFAULT_COPY = {
30
+ title: "Enable BOTIM debug logging?",
31
+ body: [
32
+ "This shares your screen content, console logs, and network calls with the BOTIM team to help debug this mini-program.",
33
+ "No data leaves your device unless you agree. You can revoke this anytime by clearing site data or via developer tools."
34
+ ],
35
+ acceptLabel: "Allow",
36
+ declineLabel: "Skip"
37
+ };
38
+ function readConsentDecision(mpId) {
39
+ if (typeof localStorage === "undefined") return null;
40
+ try {
41
+ const raw = localStorage.getItem(STORAGE_PREFIX + mpId);
42
+ if (raw === "granted" || raw === "denied") return raw;
43
+ return null;
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+ function writeConsentDecision(mpId, decision) {
49
+ if (typeof localStorage === "undefined") return;
50
+ try {
51
+ localStorage.setItem(STORAGE_PREFIX + mpId, decision);
52
+ } catch {
53
+ }
54
+ }
55
+ function clearConsentDecision(mpId) {
56
+ if (typeof localStorage === "undefined") return;
57
+ try {
58
+ localStorage.removeItem(STORAGE_PREFIX + mpId);
59
+ } catch {
60
+ }
61
+ }
62
+ var inflightPrompt = null;
63
+ async function promptForConsent(mpId, copy = {}) {
64
+ if (inflightPrompt) return inflightPrompt;
65
+ if (typeof document === "undefined" || typeof window === "undefined") {
66
+ return "denied";
67
+ }
68
+ const merged = {
69
+ title: copy.title ?? DEFAULT_COPY.title,
70
+ body: copy.body ?? DEFAULT_COPY.body,
71
+ acceptLabel: copy.acceptLabel ?? DEFAULT_COPY.acceptLabel,
72
+ declineLabel: copy.declineLabel ?? DEFAULT_COPY.declineLabel
73
+ };
74
+ inflightPrompt = new Promise((resolve) => {
75
+ injectStylesheet();
76
+ const overlay = buildOverlay(merged, (decision) => {
77
+ writeConsentDecision(mpId, decision);
78
+ try {
79
+ overlay.remove();
80
+ } catch {
81
+ }
82
+ inflightPrompt = null;
83
+ resolve(decision);
84
+ });
85
+ document.body.appendChild(overlay);
86
+ });
87
+ return inflightPrompt;
88
+ }
89
+ function injectStylesheet() {
90
+ if (document.getElementById(STYLE_ELEMENT_ID)) return;
91
+ const style = document.createElement("style");
92
+ style.id = STYLE_ELEMENT_ID;
93
+ style.textContent = `
94
+ .${CSS_PREFIX}-overlay {
95
+ position: fixed !important;
96
+ inset: 0 !important;
97
+ z-index: 2147483647 !important;
98
+ display: flex !important;
99
+ align-items: flex-end !important;
100
+ justify-content: center !important;
101
+ background: rgba(0, 0, 0, 0.45) !important;
102
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
103
+ Roboto, "Helvetica Neue", sans-serif !important;
104
+ animation: ${CSS_PREFIX}-fade-in 160ms ease-out !important;
105
+ box-sizing: border-box !important;
106
+ padding: 16px !important;
107
+ }
108
+ @media (min-width: 480px) {
109
+ .${CSS_PREFIX}-overlay {
110
+ align-items: center !important;
111
+ }
112
+ }
113
+ @keyframes ${CSS_PREFIX}-fade-in {
114
+ from { opacity: 0; }
115
+ to { opacity: 1; }
116
+ }
117
+ .${CSS_PREFIX}-card {
118
+ background: #ffffff !important;
119
+ color: #0e1116 !important;
120
+ border-radius: 16px !important;
121
+ max-width: 420px !important;
122
+ width: 100% !important;
123
+ padding: 20px 20px 16px !important;
124
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18) !important;
125
+ box-sizing: border-box !important;
126
+ }
127
+ .${CSS_PREFIX}-title {
128
+ font-size: 17px !important;
129
+ font-weight: 600 !important;
130
+ margin: 0 0 12px !important;
131
+ line-height: 1.3 !important;
132
+ }
133
+ .${CSS_PREFIX}-body {
134
+ font-size: 14px !important;
135
+ line-height: 1.5 !important;
136
+ color: #4a5160 !important;
137
+ margin: 0 0 8px !important;
138
+ }
139
+ .${CSS_PREFIX}-actions {
140
+ display: flex !important;
141
+ justify-content: flex-end !important;
142
+ gap: 8px !important;
143
+ margin-top: 16px !important;
144
+ }
145
+ .${CSS_PREFIX}-btn {
146
+ font-family: inherit !important;
147
+ font-size: 14px !important;
148
+ font-weight: 500 !important;
149
+ padding: 9px 16px !important;
150
+ border-radius: 10px !important;
151
+ border: none !important;
152
+ cursor: pointer !important;
153
+ line-height: 1 !important;
154
+ min-height: 36px !important;
155
+ box-sizing: border-box !important;
156
+ }
157
+ .${CSS_PREFIX}-btn-decline {
158
+ background: transparent !important;
159
+ color: #4a5160 !important;
160
+ }
161
+ .${CSS_PREFIX}-btn-decline:hover {
162
+ background: #f3f4f6 !important;
163
+ }
164
+ .${CSS_PREFIX}-btn-accept {
165
+ background: #0066ff !important;
166
+ color: #ffffff !important;
167
+ }
168
+ .${CSS_PREFIX}-btn-accept:hover {
169
+ background: #0055d4 !important;
170
+ }
171
+ @media (prefers-color-scheme: dark) {
172
+ .${CSS_PREFIX}-card {
173
+ background: #1c1f24 !important;
174
+ color: #e8eaed !important;
175
+ }
176
+ .${CSS_PREFIX}-body {
177
+ color: #b9bec7 !important;
178
+ }
179
+ .${CSS_PREFIX}-btn-decline {
180
+ color: #b9bec7 !important;
181
+ }
182
+ .${CSS_PREFIX}-btn-decline:hover {
183
+ background: #2a2e35 !important;
184
+ }
185
+ }
186
+ `;
187
+ document.head.appendChild(style);
188
+ }
189
+ function buildOverlay(copy, onDecision) {
190
+ const overlay = document.createElement("div");
191
+ overlay.className = `${CSS_PREFIX}-overlay`;
192
+ overlay.setAttribute("role", "dialog");
193
+ overlay.setAttribute("aria-modal", "true");
194
+ overlay.setAttribute("aria-labelledby", `${CSS_PREFIX}-title`);
195
+ const card = document.createElement("div");
196
+ card.className = `${CSS_PREFIX}-card`;
197
+ const title = document.createElement("h2");
198
+ title.id = `${CSS_PREFIX}-title`;
199
+ title.className = `${CSS_PREFIX}-title`;
200
+ title.textContent = copy.title;
201
+ card.appendChild(title);
202
+ for (const para of copy.body) {
203
+ const p = document.createElement("p");
204
+ p.className = `${CSS_PREFIX}-body`;
205
+ p.textContent = para;
206
+ card.appendChild(p);
207
+ }
208
+ const actions = document.createElement("div");
209
+ actions.className = `${CSS_PREFIX}-actions`;
210
+ const declineBtn = document.createElement("button");
211
+ declineBtn.type = "button";
212
+ declineBtn.className = `${CSS_PREFIX}-btn ${CSS_PREFIX}-btn-decline`;
213
+ declineBtn.textContent = copy.declineLabel;
214
+ declineBtn.addEventListener("click", () => onDecision("denied"));
215
+ const acceptBtn = document.createElement("button");
216
+ acceptBtn.type = "button";
217
+ acceptBtn.className = `${CSS_PREFIX}-btn ${CSS_PREFIX}-btn-accept`;
218
+ acceptBtn.textContent = copy.acceptLabel;
219
+ acceptBtn.addEventListener("click", () => onDecision("granted"));
220
+ actions.appendChild(declineBtn);
221
+ actions.appendChild(acceptBtn);
222
+ card.appendChild(actions);
223
+ overlay.appendChild(card);
224
+ requestAnimationFrame(() => {
225
+ try {
226
+ acceptBtn.focus();
227
+ } catch {
228
+ }
229
+ });
230
+ return overlay;
231
+ }
232
+
25
233
  // src/buffer.ts
26
234
  var RingBuffer = class {
27
235
  constructor(opts) {
@@ -1408,6 +1616,21 @@ function assertConfig(config) {
1408
1616
  async function enableRemoteDebug(options) {
1409
1617
  if (options.enabled === false) return NOOP_HANDLE;
1410
1618
  assertConfig(options.config);
1619
+ if (options.consent?.promptUser) {
1620
+ const cached = readConsentDecision(options.config.miniProgramId);
1621
+ let decision = cached;
1622
+ if (decision === null) {
1623
+ decision = await promptForConsent(
1624
+ options.config.miniProgramId,
1625
+ options.consent.promptCopy
1626
+ );
1627
+ }
1628
+ if (decision === "denied") return NOOP_HANDLE;
1629
+ options = {
1630
+ ...options,
1631
+ consent: { ...options.consent, userOptIn: true }
1632
+ };
1633
+ }
1411
1634
  assertConsent(options.config, options.consent);
1412
1635
  const onError = options.onError ?? (() => {
1413
1636
  });
@@ -1610,7 +1833,11 @@ exports.DEFAULT_MAX_BATCH_SIZE = DEFAULT_MAX_BATCH_SIZE;
1610
1833
  exports.DEFAULT_MAX_BODY_BYTES = DEFAULT_MAX_BODY_BYTES;
1611
1834
  exports.DEFAULT_REDACT_HEADERS = DEFAULT_REDACT_HEADERS;
1612
1835
  exports.SCHEMA_VERSION = SCHEMA_VERSION;
1836
+ exports.clearConsentDecision = clearConsentDecision;
1613
1837
  exports.enableRemoteDebug = enableRemoteDebug;
1614
1838
  exports.getBOT = getBOT;
1839
+ exports.promptForConsent = promptForConsent;
1840
+ exports.readConsentDecision = readConsentDecision;
1841
+ exports.writeConsentDecision = writeConsentDecision;
1615
1842
  //# sourceMappingURL=index.cjs.map
1616
1843
  //# sourceMappingURL=index.cjs.map