@kontextmind/kxm 0.7.4 → 0.7.5

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 (36) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/docs/README.md +1 -0
  3. package/docs/adr/ADR-0002-browser-automation-steel-doks.md +103 -0
  4. package/docs/agent-skills.md +14 -0
  5. package/docs/browser-automation.md +116 -0
  6. package/docs/kb/how-credentials-retrieved-safely.md +31 -0
  7. package/docs/kb/how-to-capture-and-annotate-section.md +60 -0
  8. package/docs/kb/how-to-connect-playwright-to-steel.md +54 -0
  9. package/docs/kb/how-to-recover-expired-session-or-orphan.md +54 -0
  10. package/docs/kb/how-to-resume-after-mfa.md +28 -0
  11. package/docs/kb/how-to-take-over-session.md +32 -0
  12. package/docs/kb/why-authentication-disappeared.md +32 -0
  13. package/docs/kb/why-automation-opened-different-browser.md +32 -0
  14. package/docs/kb/why-session-viewer-cannot-control.md +31 -0
  15. package/docs/prompts/browser-annotate-feedback.md +41 -0
  16. package/docs/prompts/browser-diagnose-recover.md +38 -0
  17. package/docs/prompts/browser-explore.md +42 -0
  18. package/docs/prompts/browser-repro-fix.md +48 -0
  19. package/docs/prompts/browser-start.md +41 -0
  20. package/docs/prompts/browser-takeover.md +50 -0
  21. package/package.json +1 -1
  22. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  23. package/plugins/kxm/dist/mcp-server.js +1 -1
  24. package/plugins/kxm/dist/runtime.js +432 -0
  25. package/plugins/kxm/package.json +1 -1
  26. package/plugins/kxm/skills/hints.json +30 -0
  27. package/plugins/kxm/skills/kxm-browser-annotate/SKILL.md +90 -0
  28. package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +47 -0
  29. package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +48 -0
  30. package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +48 -0
  31. package/plugins/kxm/skills/kxm-browser-session/SKILL.md +94 -0
  32. package/plugins/kxm/skills/kxm-browser-takeover/SKILL.md +87 -0
  33. package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +71 -0
  34. package/plugins/kxm/src/browser.ts +603 -0
  35. package/plugins/kxm/src/mcp-server.ts +1 -1
  36. package/plugins/kxm/src/runtime.ts +1 -0
@@ -27750,6 +27750,429 @@ function evaluatePromotionPolicy(candidate, policy = "manual_pr", options) {
27750
27750
  reason: `Unknown promotion policy: ${String(policy)}`
27751
27751
  };
27752
27752
  }
27753
+
27754
+ // plugins/kxm/src/browser.ts
27755
+ import { execSync } from "node:child_process";
27756
+ var VIEWPORT_PRESETS = Object.freeze({
27757
+ "mobile-sm": { name: "Mobile Small (SE)", category: "mobile", width: 375, height: 667, isMobile: true, hasTouch: true },
27758
+ "mobile": { name: "Mobile (iPhone 16 / 15 Pro)", category: "mobile", width: 393, height: 852, isMobile: true, hasTouch: true },
27759
+ "mobile-lg": { name: "Mobile Large (Pro Max)", category: "mobile", width: 430, height: 932, isMobile: true, hasTouch: true },
27760
+ "pixel": { name: "Google Pixel 8/9", category: "mobile", width: 412, height: 924, isMobile: true, hasTouch: true },
27761
+ "galaxy": { name: "Samsung Galaxy S24", category: "mobile", width: 360, height: 780, isMobile: true, hasTouch: true },
27762
+ "tablet": { name: "Tablet (iPad Air / Mini)", category: "tablet", width: 820, height: 1180, isMobile: true, hasTouch: true },
27763
+ "tablet-lg": { name: "Tablet Large (iPad Pro 12.9)", category: "tablet", width: 1024, height: 1366, isMobile: true, hasTouch: true },
27764
+ "laptop": { name: "Standard Laptop", category: "laptop", width: 1366, height: 768 },
27765
+ "macbook-13": { name: "MacBook Air 13", category: "laptop", width: 1440, height: 900 },
27766
+ "macbook-16": { name: "MacBook Pro 16", category: "laptop", width: 1728, height: 1117 },
27767
+ "desktop": { name: "Desktop FHD (1080p)", category: "desktop", width: 1920, height: 1080 },
27768
+ "desktop-2k": { name: "Desktop QHD (1440p)", category: "desktop", width: 2560, height: 1440 },
27769
+ "desktop-4k": { name: "Desktop 4K UHD", category: "desktop", width: 3840, height: 2160 }
27770
+ });
27771
+ function resolveViewportDimensions(presetOrDims) {
27772
+ if (!presetOrDims) {
27773
+ return { width: 1920, height: 1080 };
27774
+ }
27775
+ if (typeof presetOrDims === "string") {
27776
+ const matched = VIEWPORT_PRESETS[presetOrDims.toLowerCase()];
27777
+ if (matched) {
27778
+ return { width: matched.width, height: matched.height };
27779
+ }
27780
+ return { width: 1920, height: 1080 };
27781
+ }
27782
+ return presetOrDims;
27783
+ }
27784
+ function resolvePassCliApiKey(execFn = (cmd) => execSync(cmd, { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], timeout: 5e3 })) {
27785
+ if (typeof process === "undefined" || process.env.USE_PASS_CLI === "false") {
27786
+ return void 0;
27787
+ }
27788
+ if (process.env.PASS_CLI_OUTPUT_MOCK) {
27789
+ try {
27790
+ const parsed = JSON.parse(process.env.PASS_CLI_OUTPUT_MOCK);
27791
+ const extraFields = parsed?.item?.content?.extra_fields || [];
27792
+ const customSections = parsed?.item?.content?.content?.Custom?.sections || [];
27793
+ const sectionFields = customSections.flatMap((s) => s.section_fields || []);
27794
+ const allFields = [...extraFields, ...sectionFields];
27795
+ return allFields.find((f) => f.name === "STEEL_API_KEY")?.content?.Hidden;
27796
+ } catch {
27797
+ return void 0;
27798
+ }
27799
+ }
27800
+ try {
27801
+ const output = execFn(
27802
+ 'pass-cli item view --vault-name "AI Provider Keys" --item-title "Steel Browser (KontextMind DOKS)" --output json'
27803
+ );
27804
+ const parsed = JSON.parse(output);
27805
+ const extraFields = parsed?.item?.content?.extra_fields || [];
27806
+ const customSections = parsed?.item?.content?.content?.Custom?.sections || [];
27807
+ const sectionFields = customSections.flatMap((s) => s.section_fields || []);
27808
+ const allFields = [...extraFields, ...sectionFields];
27809
+ return allFields.find((f) => f.name === "STEEL_API_KEY")?.content?.Hidden;
27810
+ } catch {
27811
+ return void 0;
27812
+ }
27813
+ }
27814
+ function resolveSteelConfig(overrides) {
27815
+ const apiUrl = overrides?.apiUrl || process.env.STEEL_API_URL || "https://steel.kontextmind.com";
27816
+ const apiKey = overrides?.apiKey || process.env.STEEL_API_KEY || resolvePassCliApiKey();
27817
+ const uiUrl = overrides?.uiUrl || (overrides?.apiUrl ? `${overrides.apiUrl.replace(/\/$/, "")}/ui` : void 0) || process.env.STEEL_UI_URL || `${apiUrl.replace(/\/$/, "")}/ui`;
27818
+ return {
27819
+ apiUrl: apiUrl.replace(/\/$/, ""),
27820
+ apiKey,
27821
+ uiUrl,
27822
+ timeoutMs: overrides?.timeoutMs || 3e5
27823
+ // 5 minutes default
27824
+ };
27825
+ }
27826
+ function formatCDPEndpoint(session, config) {
27827
+ const baseApi = config.apiUrl;
27828
+ const urlObj = new URL(baseApi);
27829
+ const isSecure = urlObj.protocol === "https:";
27830
+ const wsProtocol = isSecure ? "wss:" : "ws:";
27831
+ const host = urlObj.host;
27832
+ const searchParams = new URLSearchParams();
27833
+ searchParams.set("sessionId", session.id);
27834
+ if (config.apiKey) {
27835
+ searchParams.set("apiKey", config.apiKey);
27836
+ }
27837
+ return `${wsProtocol}//${host}/v1/devtools?${searchParams.toString()}`;
27838
+ }
27839
+ function sanitizeLogOutput(input) {
27840
+ if (typeof input === "string") {
27841
+ return input.replace(/apiKey=[^&]+/g, "apiKey=[REDACTED]").replace(/steel_[a-f0-9]+/g, "steel_[REDACTED]");
27842
+ }
27843
+ if (Array.isArray(input)) {
27844
+ return input.map(sanitizeLogOutput);
27845
+ }
27846
+ if (input !== null && typeof input === "object") {
27847
+ const copy = {};
27848
+ for (const [k, v] of Object.entries(input)) {
27849
+ if (/key|secret|token|auth|password/i.test(k) && typeof v === "string") {
27850
+ copy[k] = "[REDACTED]";
27851
+ } else {
27852
+ copy[k] = sanitizeLogOutput(v);
27853
+ }
27854
+ }
27855
+ return copy;
27856
+ }
27857
+ return input;
27858
+ }
27859
+ function createAnnotationFeedback(feedback) {
27860
+ return {
27861
+ ...feedback,
27862
+ capturedAt: feedback.capturedAt || (/* @__PURE__ */ new Date()).toISOString()
27863
+ };
27864
+ }
27865
+ function formatAnnotationFeedbackPrompt(feedback) {
27866
+ let output = `## Visual Feedback & Section Annotation
27867
+
27868
+ `;
27869
+ output += `- **Target URL**: ${feedback.url}
27870
+ `;
27871
+ if (feedback.sessionId) {
27872
+ output += `- **Session ID**: ${feedback.sessionId}
27873
+ `;
27874
+ }
27875
+ if (feedback.sectionSelector) {
27876
+ output += `- **Section Target Selector**: \`${feedback.sectionSelector}\`
27877
+ `;
27878
+ }
27879
+ if (feedback.screenshotPath) {
27880
+ output += `- **Screenshot Artifact**: \`${feedback.screenshotPath}\`
27881
+ `;
27882
+ }
27883
+ output += `- **Captured At**: ${feedback.capturedAt}
27884
+
27885
+ `;
27886
+ output += `### Summary
27887
+ ${feedback.overallSummary}
27888
+
27889
+ `;
27890
+ if (feedback.annotations.length > 0) {
27891
+ output += `### Annotated Elements & Notes
27892
+
27893
+ `;
27894
+ feedback.annotations.forEach((item, idx) => {
27895
+ output += `${idx + 1}. **${item.label}**`;
27896
+ if (item.severity) {
27897
+ output += ` [${item.severity.toUpperCase()}]`;
27898
+ }
27899
+ output += `
27900
+ - **Note**: ${item.note}
27901
+ `;
27902
+ if (item.selector) {
27903
+ output += ` - **Selector**: \`${item.selector}\`
27904
+ `;
27905
+ }
27906
+ if (item.boundingBox) {
27907
+ output += ` - **Region (Box)**: x=${item.boundingBox.x}, y=${item.boundingBox.y}, w=${item.boundingBox.width}, h=${item.boundingBox.height}
27908
+ `;
27909
+ }
27910
+ });
27911
+ output += `
27912
+ `;
27913
+ }
27914
+ if (feedback.requestedChanges.length > 0) {
27915
+ output += `### Actionable Change List
27916
+
27917
+ `;
27918
+ feedback.requestedChanges.forEach((change, idx) => {
27919
+ output += `- [ ] ${change}
27920
+ `;
27921
+ });
27922
+ }
27923
+ return output;
27924
+ }
27925
+ var SteelClient = class {
27926
+ config;
27927
+ activeSessions = /* @__PURE__ */ new Map();
27928
+ constructor(config) {
27929
+ this.config = resolveSteelConfig(config);
27930
+ }
27931
+ getConfig() {
27932
+ return { ...this.config };
27933
+ }
27934
+ headers() {
27935
+ const h = {
27936
+ "Content-Type": "application/json"
27937
+ };
27938
+ if (this.config.apiKey) {
27939
+ h["x-steel-api-key"] = this.config.apiKey;
27940
+ }
27941
+ return h;
27942
+ }
27943
+ /**
27944
+ * Launch a new Steel browser session on DOKS.
27945
+ */
27946
+ async createSession(options) {
27947
+ const timeoutMs = options?.timeoutMs ?? this.config.timeoutMs ?? 3e5;
27948
+ const body = {
27949
+ timeout: timeoutMs
27950
+ };
27951
+ const dimensions = options?.dimensions || (options?.viewportPreset ? resolveViewportDimensions(options.viewportPreset) : void 0);
27952
+ if (dimensions) {
27953
+ body.dimensions = dimensions;
27954
+ }
27955
+ if (options?.userAgent) {
27956
+ body.userAgent = options.userAgent;
27957
+ }
27958
+ if (options?.proxy) {
27959
+ body.proxy = options.proxy;
27960
+ }
27961
+ const res = await fetch(`${this.config.apiUrl}/v1/sessions`, {
27962
+ method: "POST",
27963
+ headers: this.headers(),
27964
+ body: JSON.stringify(body)
27965
+ });
27966
+ if (!res.ok) {
27967
+ const errText = await res.text();
27968
+ throw new Error(`Failed to create Steel session (${res.status}): ${errText}`);
27969
+ }
27970
+ const data = await res.json();
27971
+ const session = {
27972
+ id: data.id,
27973
+ createdAt: data.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
27974
+ status: "live",
27975
+ state: "AGENT_CONTROL",
27976
+ websocketUrl: data.websocketUrl || "",
27977
+ debugUrl: data.debugUrl || "",
27978
+ debuggerUrl: data.debuggerUrl || "",
27979
+ sessionViewerUrl: data.sessionViewerUrl || `${this.config.uiUrl}?sessionId=${data.id}`,
27980
+ timeoutMs,
27981
+ lastActiveAt: Date.now(),
27982
+ activeController: "agent",
27983
+ userAgent: data.userAgent
27984
+ };
27985
+ this.activeSessions.set(session.id, session);
27986
+ return session;
27987
+ }
27988
+ /**
27989
+ * Get details of an existing session.
27990
+ */
27991
+ async getSession(sessionId) {
27992
+ const res = await fetch(`${this.config.apiUrl}/v1/sessions/${encodeURIComponent(sessionId)}`, {
27993
+ method: "GET",
27994
+ headers: this.headers()
27995
+ });
27996
+ if (res.status === 404) {
27997
+ const cached = this.activeSessions.get(sessionId);
27998
+ if (cached) {
27999
+ cached.state = "EXPIRED";
28000
+ cached.status = "released";
28001
+ cached.activeController = "none";
28002
+ }
28003
+ return null;
28004
+ }
28005
+ if (!res.ok) {
28006
+ throw new Error(`Failed to fetch Steel session (${res.status})`);
28007
+ }
28008
+ const data = await res.json();
28009
+ const existing = this.activeSessions.get(sessionId);
28010
+ const session = {
28011
+ id: data.id,
28012
+ createdAt: data.createdAt,
28013
+ status: data.status,
28014
+ state: existing?.state ?? (data.status === "live" ? "AGENT_CONTROL" : "RELEASED"),
28015
+ websocketUrl: data.websocketUrl || existing?.websocketUrl || "",
28016
+ debugUrl: data.debugUrl || existing?.debugUrl || "",
28017
+ debuggerUrl: data.debuggerUrl || existing?.debuggerUrl || "",
28018
+ sessionViewerUrl: existing?.sessionViewerUrl || `${this.config.uiUrl}?sessionId=${data.id}`,
28019
+ timeoutMs: data.timeout || existing?.timeoutMs || 3e5,
28020
+ lastActiveAt: Date.now(),
28021
+ activeController: existing?.activeController ?? (data.status === "live" ? "agent" : "none"),
28022
+ userAgent: data.userAgent
28023
+ };
28024
+ this.activeSessions.set(session.id, session);
28025
+ return session;
28026
+ }
28027
+ /**
28028
+ * Request human takeover for MFA, login, or consent.
28029
+ * Pauses agent automation and sets state to HUMAN_CONTROL.
28030
+ */
28031
+ requestHumanTakeover(sessionId, reason) {
28032
+ const session = this.activeSessions.get(sessionId);
28033
+ if (!session) {
28034
+ throw new Error(`Session ${sessionId} not tracked or already released`);
28035
+ }
28036
+ if (session.state === "RELEASED" || session.state === "EXPIRED" || session.state === "FAILED") {
28037
+ throw new Error(`Cannot initiate takeover on session in state ${session.state}`);
28038
+ }
28039
+ session.state = "HUMAN_CONTROL";
28040
+ session.activeController = "human";
28041
+ session.takeoverReason = reason;
28042
+ session.lastActiveAt = Date.now();
28043
+ const takeoverUrl = `${this.config.uiUrl}?sessionId=${encodeURIComponent(sessionId)}`;
28044
+ const instructions = `[HUMAN TAKEOVER REQUIRED]
28045
+ Reason: ${reason}
28046
+ Session ID: ${session.id}
28047
+ Takeover URL: ${takeoverUrl}
28048
+
28049
+ Instructions for Operator:
28050
+ 1. Open the URL above to access the session UI.
28051
+ 2. Perform the required authentication / MFA / consent action.
28052
+ 3. Return to the terminal and signal completion. Automation is paused until you confirm.`;
28053
+ return {
28054
+ session,
28055
+ takeoverUrl,
28056
+ instructions
28057
+ };
28058
+ }
28059
+ /**
28060
+ * Signal that human takeover is complete.
28061
+ * Moves state to VERIFY_AUTHENTICATION before transitioning back to AGENT_CONTROL.
28062
+ */
28063
+ signalHumanComplete(sessionId) {
28064
+ const session = this.activeSessions.get(sessionId);
28065
+ if (!session) {
28066
+ throw new Error(`Session ${sessionId} not tracked or already released`);
28067
+ }
28068
+ if (session.state !== "HUMAN_CONTROL") {
28069
+ throw new Error(`Session ${sessionId} is not in HUMAN_CONTROL state (currently ${session.state})`);
28070
+ }
28071
+ session.state = "VERIFY_AUTHENTICATION";
28072
+ session.activeController = "agent";
28073
+ session.lastActiveAt = Date.now();
28074
+ return {
28075
+ session,
28076
+ state: session.state
28077
+ };
28078
+ }
28079
+ /**
28080
+ * Confirm authentication verification passed and restore AGENT_CONTROL.
28081
+ */
28082
+ confirmAuthenticationVerified(sessionId) {
28083
+ const session = this.activeSessions.get(sessionId);
28084
+ if (!session) {
28085
+ throw new Error(`Session ${sessionId} not tracked or already released`);
28086
+ }
28087
+ if (session.state !== "VERIFY_AUTHENTICATION") {
28088
+ throw new Error(`Session ${sessionId} is not in VERIFY_AUTHENTICATION state (currently ${session.state})`);
28089
+ }
28090
+ session.state = "AGENT_CONTROL";
28091
+ session.activeController = "agent";
28092
+ session.takeoverReason = void 0;
28093
+ session.lastActiveAt = Date.now();
28094
+ return session;
28095
+ }
28096
+ /**
28097
+ * Gracefully release a session.
28098
+ */
28099
+ async releaseSession(sessionId) {
28100
+ try {
28101
+ const res = await fetch(`${this.config.apiUrl}/v1/sessions/${encodeURIComponent(sessionId)}/release`, {
28102
+ method: "POST",
28103
+ headers: this.headers()
28104
+ });
28105
+ const session = this.activeSessions.get(sessionId);
28106
+ if (session) {
28107
+ session.status = "released";
28108
+ session.state = "RELEASED";
28109
+ session.activeController = "none";
28110
+ }
28111
+ this.activeSessions.delete(sessionId);
28112
+ return res.ok;
28113
+ } catch {
28114
+ this.activeSessions.delete(sessionId);
28115
+ return false;
28116
+ }
28117
+ }
28118
+ /**
28119
+ * Perform a direct stateless scrape without manual session management.
28120
+ */
28121
+ async scrape(url) {
28122
+ const res = await fetch(`${this.config.apiUrl}/v1/scrape`, {
28123
+ method: "POST",
28124
+ headers: this.headers(),
28125
+ body: JSON.stringify({ url })
28126
+ });
28127
+ if (!res.ok) {
28128
+ const err = await res.text();
28129
+ throw new Error(`Scrape failed (${res.status}): ${err}`);
28130
+ }
28131
+ return res.json();
28132
+ }
28133
+ /**
28134
+ * Perform a direct screenshot action.
28135
+ */
28136
+ async screenshot(url, fullPage = false) {
28137
+ const res = await fetch(`${this.config.apiUrl}/v1/screenshot`, {
28138
+ method: "POST",
28139
+ headers: this.headers(),
28140
+ body: JSON.stringify({ url, fullPage })
28141
+ });
28142
+ if (!res.ok) {
28143
+ const err = await res.text();
28144
+ throw new Error(`Screenshot failed (${res.status}): ${err}`);
28145
+ }
28146
+ return res.json();
28147
+ }
28148
+ /**
28149
+ * Detect and list orphaned or timed-out active sessions.
28150
+ */
28151
+ async checkOrphanedSessions(maxIdleMs = 6e5) {
28152
+ const res = await fetch(`${this.config.apiUrl}/v1/sessions`, {
28153
+ method: "GET",
28154
+ headers: this.headers()
28155
+ });
28156
+ if (!res.ok) {
28157
+ return [];
28158
+ }
28159
+ const data = await res.json();
28160
+ const remoteSessions = data.sessions || [];
28161
+ const now = Date.now();
28162
+ const orphaned = [];
28163
+ for (const rs of remoteSessions) {
28164
+ if (rs.status === "live" || rs.status === "idle") {
28165
+ const tracked = this.activeSessions.get(rs.id);
28166
+ if (!tracked && rs.duration > maxIdleMs) {
28167
+ orphaned.push(rs.id);
28168
+ } else if (tracked && now - tracked.lastActiveAt > maxIdleMs && tracked.state !== "HUMAN_CONTROL") {
28169
+ orphaned.push(rs.id);
28170
+ }
28171
+ }
28172
+ }
28173
+ return orphaned;
28174
+ }
28175
+ };
27753
28176
  export {
27754
28177
  BUILTIN_HARNESSES,
27755
28178
  BUILTIN_HARNESS_IDS,
@@ -27765,6 +28188,8 @@ export {
27765
28188
  PI_NATIVE_BRAKE_PROVIDERS,
27766
28189
  PiSession,
27767
28190
  SAFE_HARNESS_COMMAND_ID,
28191
+ SteelClient,
28192
+ VIEWPORT_PRESETS,
27768
28193
  VNEXT_ABSENT_MEMORY_REVISION,
27769
28194
  VNEXT_EVENT_STORE_SCHEMA_VERSION,
27770
28195
  VNEXT_REGISTRY_SCHEMA_VERSION,
@@ -27786,6 +28211,7 @@ export {
27786
28211
  closeVnextRuntimeContext,
27787
28212
  computeGateEvidenceOutcome,
27788
28213
  computeVnextMemoryRevision,
28214
+ createAnnotationFeedback,
27789
28215
  createBackup,
27790
28216
  createLogger,
27791
28217
  createVnextOneShotProducer,
@@ -27800,6 +28226,8 @@ export {
27800
28226
  fileSha256,
27801
28227
  findWinNpmInnerExe,
27802
28228
  foldStoredVnextRun,
28229
+ formatAnnotationFeedbackPrompt,
28230
+ formatCDPEndpoint,
27803
28231
  formatHarnessInventory,
27804
28232
  formatHarnessUpdate,
27805
28233
  formatImprovementReport,
@@ -27844,11 +28272,15 @@ export {
27844
28272
  redactLogValue,
27845
28273
  registerVnextRuntimeCloseHook,
27846
28274
  resolveDispatchStatus,
28275
+ resolvePassCliApiKey,
28276
+ resolveSteelConfig,
28277
+ resolveViewportDimensions,
27847
28278
  restoreBackup,
27848
28279
  restoreDatabaseFile,
27849
28280
  rotateLogFiles,
27850
28281
  runHarnessUpdate,
27851
28282
  runtimeError,
28283
+ sanitizeLogOutput,
27852
28284
  startVnextRuntimeSupervisor,
27853
28285
  tableColumns,
27854
28286
  userTables,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kxm-claude-plugin",
3
- "version": "0.7.4",
3
+ "version": "0.7.5",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -68,6 +68,36 @@
68
68
  "argumentHint": "[trailers|trust|gates|authz|webhooks]",
69
69
  "triggers": ["KM-Session", "trust mode", "secret gate", "RLS", "protocol"],
70
70
  "complete": ["trailers", "trust", "gates", "authz", "webhooks"]
71
+ },
72
+ {
73
+ "name": "kxm-browser-session",
74
+ "argumentHint": "[create|inspect|release|cdp]",
75
+ "triggers": ["browser session", "steel session", "remote browser", "launch browser", "cdp endpoint"],
76
+ "complete": ["create session", "inspect session", "release session", "format cdp"]
77
+ },
78
+ {
79
+ "name": "kxm-browser-takeover",
80
+ "argumentHint": "[request|signal|verify]",
81
+ "triggers": ["human takeover", "mfa login", "takeover url", "browser auth required", "resume browser"],
82
+ "complete": ["request takeover", "signal complete", "verify auth"]
83
+ },
84
+ {
85
+ "name": "kxm-browser-explore",
86
+ "argumentHint": "[open|snapshot|get|screenshot]",
87
+ "triggers": ["agent-browser", "explore web", "scrape page", "dom snapshot"],
88
+ "complete": ["agent-browser open", "agent-browser snapshot", "agent-browser get", "agent-browser screenshot"]
89
+ },
90
+ {
91
+ "name": "kxm-browser-verify",
92
+ "argumentHint": "[reproduce|test|assert|trace]",
93
+ "triggers": ["playwright test", "ui reproduction", "regression test", "e2e verify"],
94
+ "complete": ["playwright test", "reproduce issue", "save trace"]
95
+ },
96
+ {
97
+ "name": "kxm-browser-diagnostics",
98
+ "argumentHint": "[check|cleanup|recover]",
99
+ "triggers": ["browser diagnostics", "cdp failure", "session timeout", "orphaned browser"],
100
+ "complete": ["check health", "cleanup orphans", "recover session"]
71
101
  }
72
102
  ]
73
103
  }
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: kxm-browser-annotate
3
+ description: Capture a visual DOM section or element, attach structured annotations and change requests, and send them back to the agent.
4
+ ---
5
+
6
+ # KXM Browser Section Capture & Visual Annotation Feedback
7
+
8
+ Use this skill to capture visual screenshots of specific UI sections or elements from a Steel browser session, record structured design/code annotations, and feed actionable change requests directly back to an AI coding agent.
9
+
10
+ ## Purpose & Scope
11
+
12
+ - Enable human operators and critic agents to visually review web interfaces.
13
+ - Crop or capture specific DOM elements, cards, modals, or viewport bounding boxes.
14
+ - Attach structured notes (e.g. alignment issues, color contrast, missing data, layout bugs) with severity ratings.
15
+ - Provide a standardized Markdown/JSON feedback payload that an agent can parse and implement immediately.
16
+
17
+ ## Workflow
18
+
19
+ ```text
20
+ 1. CAPTURE SECTION
21
+ └─ Use Playwright element.screenshot() or agent-browser screenshot to isolate the target component.
22
+
23
+ 2. ATTACH ANNOTATIONS
24
+ └─ Record bounding box / element selector, defect note, and severity rating.
25
+
26
+ 3. ASSEMBLE FEEDBACK PACKAGE
27
+ └─ Package screenshot artifact, element selectors, notes, and concrete change list.
28
+
29
+ 4. HANDOFF TO AGENT
30
+ └─ Inject formatted visual feedback into agent context or KXM workflow run.
31
+
32
+ 5. AGENT IMPLEMENTS FIX
33
+ └─ Agent modifies code, re-captures the section, and verifies the change visually and with Playwright.
34
+ ```
35
+
36
+ ## Capturing a Specific Section with Playwright
37
+
38
+ ```typescript
39
+ import { chromium } from "playwright";
40
+ import { resolveSteelConfig, formatCDPEndpoint } from "@kontextmind/kxm/runtime";
41
+
42
+ async function captureSection(sessionId: string, selector: string, outputPath: string) {
43
+ const config = resolveSteelConfig();
44
+ const cdpUrl = formatCDPEndpoint({ id: sessionId, websocketUrl: "" }, config);
45
+
46
+ const browser = await chromium.connectOverCDP(cdpUrl);
47
+ const context = browser.contexts()[0] || await browser.newContext();
48
+ const page = context.pages()[0] || await context.newPage();
49
+
50
+ const element = page.locator(selector);
51
+ await element.screenshot({ path: outputPath });
52
+
53
+ await browser.close();
54
+ }
55
+ ```
56
+
57
+ ## Structured Feedback Schema
58
+
59
+ ```json
60
+ {
61
+ "sessionId": "sess_12345",
62
+ "url": "https://app.example.com/settings/billing",
63
+ "sectionSelector": "[data-testid='subscription-card']",
64
+ "screenshotPath": ".kxm/artifacts/browser/billing-card.png",
65
+ "overallSummary": "Pricing tier badge overflows card boundary on narrow screens",
66
+ "annotations": [
67
+ {
68
+ "label": "Badge Overflow",
69
+ "selector": ".badge-tier",
70
+ "note": "Text overflows container when tier name is 'Enterprise Plus'",
71
+ "severity": "fix"
72
+ },
73
+ {
74
+ "label": "Button Padding",
75
+ "selector": "button.upgrade-btn",
76
+ "note": "Increase vertical padding from 8px to 12px for touch target compliance",
77
+ "severity": "suggestion"
78
+ }
79
+ ],
80
+ "requestedChanges": [
81
+ "Add `overflow: hidden` or `flex-wrap: wrap` to the subscription header container",
82
+ "Update `.badge-tier` CSS to support dynamic text wrapping",
83
+ "Adjust `button.upgrade-btn` padding to `py-3 px-4`"
84
+ ]
85
+ }
86
+ ```
87
+
88
+ ## Formatting for the Agent
89
+
90
+ Use `formatAnnotationFeedbackPrompt()` from `@kontextmind/kxm/runtime` to render a clean, checklist-driven prompt that the agent executes step-by-step.
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: kxm-browser-auth
3
+ description: Retrieve application credentials and manage authenticated browser profiles safely via pass-cli without secret exposure.
4
+ ---
5
+
6
+ # KXM Browser Credentials & Authenticated Profiles
7
+
8
+ Use this skill to retrieve target application credentials and manage browser session state securely using `pass-cli` as the sole authoritative store.
9
+
10
+ ## Purpose & Scope
11
+
12
+ - Enforce `pass-cli` as the single source of truth for credentials and API keys.
13
+ - Prevent secrets from leaking into git repositories, logs, prompts, or model-visible tool outputs.
14
+ - Support safe storage and retrieval of session storage state and authenticated profiles.
15
+
16
+ ## Credential Retrieval Guidelines
17
+
18
+ ### 1. Authoritative Tool: pass-cli
19
+
20
+ Always retrieve credentials and API keys directly from `pass-cli`:
21
+
22
+ ```bash
23
+ # Retrieve target login password into an environment variable or piping mechanism
24
+ pass-cli item view --vault-name "<vault>" --item-title "<title>" --field password
25
+
26
+ # Retrieve Steel infrastructure API key
27
+ pass-cli item view --vault-name "AI Provider Keys" --item-title "Steel Browser (KontextMind DOKS)" --field STEEL_API_KEY
28
+ ```
29
+
30
+ ### 2. Secret Redaction Invariants
31
+
32
+ - **Never** write plain passwords, session tokens, or API keys into markdown docs, commit messages, or prompts.
33
+ - **Never** pass plain credentials as unredacted command line arguments in shared logs.
34
+ - Use environment variable injection (`pass-cli run`) or direct in-memory pipes.
35
+
36
+ ### 3. Profile & Storage State Management
37
+
38
+ When an authenticated session state (cookies, local storage) needs to be preserved for subsequent test runs:
39
+
40
+ 1. **Extract State**:
41
+ Extract storage state from Playwright via `context.storageState({ path: 'state.json' })` or from Steel via `GET /v1/sessions/:id/context`.
42
+ 2. **Encrypt / Store Privately**:
43
+ Store sensitive storage state in git-ignored, private locations (e.g. `.kxm/state/browser/` or as an encrypted secret).
44
+ 3. **Session Expiration**:
45
+ Treat cookies as transient. When expired, trigger the `kxm-browser-takeover` flow instead of failing silently.
46
+ 4. **Account & Profile Separation**:
47
+ Maintain separate storage states per environment (e.g., `dev`, `staging`, `prod`) and per user role (e.g., `admin`, `viewer`). Never mix profiles across concurrent test runs.