sparkecode-devtools 0.1.38 → 0.1.40

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
@@ -74,6 +74,27 @@ Optional overrides:
74
74
  </script>
75
75
  ```
76
76
 
77
+ ## Environment variable (Next.js)
78
+
79
+ You can set the Sparkecoder server URL via environment variable instead of passing it as a prop:
80
+
81
+ ```bash
82
+ # In your .env.local
83
+ NEXT_PUBLIC_SPARKECODER_DEV_TOOLS_SERVER_URL=http://192.168.1.100:3141
84
+ ```
85
+
86
+ This is useful when your Sparkecoder server is running on a different machine or port.
87
+
88
+ The component checks for:
89
+ 1. `NEXT_PUBLIC_SPARKECODER_DEV_TOOLS_SERVER_URL` (client-side accessible)
90
+ 2. `SPARKECODER_DEV_TOOLS_SERVER_URL` (server-side only, for SSR)
91
+
92
+ You can also pass `serverUrl` as a prop to override:
93
+
94
+ ```tsx
95
+ <SparkeCodeSelect serverUrl="http://192.168.1.100:3141" />
96
+ ```
97
+
77
98
  ## Webhook (custom)
78
99
 
79
100
  You can also send copy events to any endpoint:
@@ -92,3 +113,15 @@ You can also send copy events to any endpoint:
92
113
  - Drag the button to reposition
93
114
  - Press `Cmd/Ctrl+Shift+S` to toggle
94
115
  - Press `Esc` to exit selection mode
116
+
117
+ ## Auto-send to Agent
118
+
119
+ When connected to a running Sparkecoder instance, clicking a component will:
120
+
121
+ 1. Show a prompt input modal
122
+ 2. Let you type what you want to do with the component (e.g., "make this button red")
123
+ 3. Press Enter to send the request + component context directly to the agent
124
+ 4. The agent will start working immediately
125
+
126
+ If you leave the prompt empty and press Enter, it sends just the component context.
127
+ Press Esc or click outside to cancel.
package/next.d.ts CHANGED
@@ -19,6 +19,11 @@ export interface SparkeCodeSelectProps {
19
19
  config?: SparkeCodeSelectConfig;
20
20
  sparkecoderEnabled?: boolean;
21
21
  strategy?: 'afterInteractive' | 'lazyOnload' | 'beforeInteractive' | 'worker';
22
+ /**
23
+ * Override the Sparkecoder server URL.
24
+ * Defaults to NEXT_PUBLIC_SPARKECODER_DEV_TOOLS_SERVER_URL or SPARKECODER_DEV_TOOLS_SERVER_URL env variable.
25
+ */
26
+ serverUrl?: string;
22
27
  }
23
28
 
24
29
  export function SparkeCodeSelect(props: SparkeCodeSelectProps): JSX.Element | null;
package/next.js CHANGED
@@ -5,20 +5,29 @@ import { useEffect, useMemo } from 'react';
5
5
 
6
6
  const DEFAULT_SCRIPT_URL = 'https://unpkg.com/sparkecode-devtools/sparkecode-select.js';
7
7
 
8
+ // Check for env variable to override the default Sparkecoder server URL
9
+ // This needs to be prefixed with NEXT_PUBLIC_ to be available in the browser
10
+ const ENV_SERVER_URL = typeof process !== 'undefined'
11
+ ? (process.env.NEXT_PUBLIC_SPARKECODER_DEV_TOOLS_SERVER_URL || process.env.SPARKECODER_DEV_TOOLS_SERVER_URL)
12
+ : undefined;
13
+
8
14
  export function SparkeCodeSelect({
9
15
  enabled = process.env.NODE_ENV !== 'production',
10
16
  scriptUrl = DEFAULT_SCRIPT_URL,
11
17
  config,
12
18
  sparkecoderEnabled = true,
13
19
  strategy = 'afterInteractive',
20
+ serverUrl = ENV_SERVER_URL,
14
21
  }) {
15
22
  const mergedConfig = useMemo(() => ({
16
23
  ...(config || {}),
17
24
  sparkecoder: {
18
25
  enabled: sparkecoderEnabled,
26
+ // Use serverUrl prop, which defaults to env variable if set
27
+ ...(serverUrl ? { apiBase: serverUrl } : {}),
19
28
  ...(config && config.sparkecoder ? config.sparkecoder : {}),
20
29
  },
21
- }), [config, sparkecoderEnabled]);
30
+ }), [config, sparkecoderEnabled, serverUrl]);
22
31
 
23
32
  useEffect(() => {
24
33
  if (!enabled) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparkecode-devtools",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "description": "Sparkecode Dev Tools for selecting and sharing components.",
5
5
  "type": "module",
6
6
  "main": "sparkecode-select.js",
@@ -333,6 +333,27 @@
333
333
  }
334
334
  }
335
335
 
336
+ async function runAgentWithPrompt(promptText) {
337
+ const connection = await ensureSparkecoderConnection();
338
+ if (!connection) return false;
339
+
340
+ try {
341
+ // Post to agents run endpoint to actually run the agent
342
+ const res = await fetch(
343
+ `${connection.apiBase}/agents/${connection.sessionId}/run`,
344
+ {
345
+ method: 'POST',
346
+ headers: { 'Content-Type': 'application/json' },
347
+ body: JSON.stringify({ prompt: promptText }),
348
+ }
349
+ );
350
+ return res.ok;
351
+ } catch (e) {
352
+ console.warn('[SparkeCode] Failed to run agent:', e.message);
353
+ return false;
354
+ }
355
+ }
356
+
336
357
  // Heartbeat interval ID
337
358
  let heartbeatIntervalId = null;
338
359
  let lastHeartbeatPath = null;
@@ -691,6 +712,175 @@
691
712
  }, 1500);
692
713
  }
693
714
 
715
+ // ============================================
716
+ // Prompt Input (Spotlight-style)
717
+ // ============================================
718
+
719
+ let promptBar = null;
720
+ let promptBackdrop = null;
721
+ let pendingComponentContent = null;
722
+ let pendingComponentInfo = null;
723
+
724
+ function createPromptBar() {
725
+ if (promptBar) return;
726
+
727
+ // Backdrop
728
+ promptBackdrop = document.createElement('div');
729
+ promptBackdrop.id = 'sparkecode-backdrop';
730
+ promptBackdrop.style.cssText = `
731
+ position: fixed;
732
+ inset: 0;
733
+ background: rgba(0,0,0,0.5);
734
+ z-index: 1000002;
735
+ opacity: 0;
736
+ transition: opacity 0.15s ease;
737
+ display: none;
738
+ `;
739
+ promptBackdrop.addEventListener('click', hidePromptBar);
740
+ document.body.appendChild(promptBackdrop);
741
+
742
+ // Main bar
743
+ promptBar = document.createElement('div');
744
+ promptBar.id = 'sparkecode-prompt-modal';
745
+ promptBar.style.cssText = `
746
+ position: fixed;
747
+ top: 20%;
748
+ left: 50%;
749
+ transform: translateX(-50%) scale(0.95);
750
+ background: ${config.bgColor};
751
+ border-radius: 12px;
752
+ z-index: 1000003;
753
+ box-shadow: 0 16px 70px rgba(0,0,0,0.5);
754
+ border: 1px solid rgba(255,255,255,0.1);
755
+ display: none;
756
+ opacity: 0;
757
+ transition: all 0.15s ease;
758
+ width: 500px;
759
+ max-width: calc(100vw - 32px);
760
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
761
+ overflow: hidden;
762
+ `;
763
+
764
+ // Component tag (top bar)
765
+ const tag = document.createElement('div');
766
+ tag.id = 'sparkecode-prompt-tag';
767
+ tag.style.cssText = `
768
+ padding: 10px 16px;
769
+ background: rgba(139, 92, 246, 0.1);
770
+ border-bottom: 1px solid rgba(255,255,255,0.05);
771
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
772
+ font-size: 13px;
773
+ color: ${config.primaryColor};
774
+ display: flex;
775
+ align-items: center;
776
+ gap: 8px;
777
+ `;
778
+ promptBar.appendChild(tag);
779
+
780
+ // Input
781
+ const input = document.createElement('input');
782
+ input.id = 'sparkecode-prompt-input';
783
+ input.type = 'text';
784
+ input.placeholder = 'What do you want to do? (Enter to send, Esc to cancel)';
785
+ input.style.cssText = `
786
+ width: 100%;
787
+ background: transparent;
788
+ border: none;
789
+ padding: 16px;
790
+ color: ${config.textColor};
791
+ font-size: 16px;
792
+ outline: none;
793
+ `;
794
+ input.addEventListener('keydown', (e) => {
795
+ if (e.key === 'Enter') {
796
+ e.preventDefault();
797
+ submitPrompt();
798
+ } else if (e.key === 'Escape') {
799
+ e.preventDefault();
800
+ hidePromptBar();
801
+ }
802
+ });
803
+ promptBar.appendChild(input);
804
+
805
+ document.body.appendChild(promptBar);
806
+ }
807
+
808
+ function showPromptBar(componentContent, componentInfo) {
809
+ createPromptBar();
810
+
811
+ pendingComponentContent = componentContent;
812
+ pendingComponentInfo = componentInfo;
813
+
814
+ // Update tag
815
+ const tag = document.getElementById('sparkecode-prompt-tag');
816
+ if (tag && componentInfo) {
817
+ tag.innerHTML = `<span style="opacity: 0.6;">Selected:</span> &lt;${componentInfo.name}&gt;`;
818
+ }
819
+
820
+ const input = document.getElementById('sparkecode-prompt-input');
821
+ if (input) input.value = '';
822
+
823
+ promptBackdrop.style.display = 'block';
824
+ promptBar.style.display = 'block';
825
+
826
+ requestAnimationFrame(() => {
827
+ promptBackdrop.style.opacity = '1';
828
+ promptBar.style.opacity = '1';
829
+ promptBar.style.transform = 'translateX(-50%) scale(1)';
830
+ if (input) input.focus();
831
+ });
832
+ }
833
+
834
+ function hidePromptBar() {
835
+ if (!promptBar) return;
836
+
837
+ promptBackdrop.style.opacity = '0';
838
+ promptBar.style.opacity = '0';
839
+ promptBar.style.transform = 'translateX(-50%) scale(0.95)';
840
+
841
+ setTimeout(() => {
842
+ if (promptBackdrop) promptBackdrop.style.display = 'none';
843
+ if (promptBar) promptBar.style.display = 'none';
844
+ }, 150);
845
+
846
+ pendingComponentContent = null;
847
+ pendingComponentInfo = null;
848
+ }
849
+
850
+ async function submitPrompt() {
851
+ const input = document.getElementById('sparkecode-prompt-input');
852
+ const userMessage = input?.value?.trim() || '';
853
+
854
+ if (!pendingComponentContent) {
855
+ hidePromptBar();
856
+ return;
857
+ }
858
+
859
+ // Build prompt: user message first (if any), then component context
860
+ let fullPrompt = userMessage
861
+ ? `${userMessage}\n\n---\n\n${pendingComponentContent}`
862
+ : pendingComponentContent;
863
+
864
+ hidePromptBar();
865
+ showToast('Sending...');
866
+
867
+ const success = await runAgentWithPrompt(fullPrompt);
868
+
869
+ if (success) {
870
+ showToast('Sent ✓');
871
+ } else {
872
+ await copyToClipboard(fullPrompt);
873
+ showToast('Copied (no connection)');
874
+ }
875
+
876
+ sendWebhook('prompt-sent', {
877
+ component: pendingComponentInfo?.name,
878
+ fileName: pendingComponentInfo?.fileName,
879
+ hasUserMessage: !!userMessage,
880
+ success,
881
+ });
882
+ }
883
+
694
884
  // ============================================
695
885
  // Event Handlers
696
886
  // ============================================
@@ -698,7 +888,6 @@
698
888
  function isValidElement(element) {
699
889
  if (!element || !isInDOM(element)) return false;
700
890
  if (element.id?.startsWith('sparkecode-')) return false;
701
- if (element.closest('#sparkecode-button')) return false;
702
891
  if (element === document.body || element === document.documentElement) return false;
703
892
  try {
704
893
  const style = getComputedStyle(element);
@@ -729,7 +918,7 @@
729
918
 
730
919
  async function handleClick(e) {
731
920
  if (!isActive) return;
732
- if (e.target.closest('#sparkecode-button')) return;
921
+ if (e.target.id?.startsWith('sparkecode-') || e.target.closest('[id^="sparkecode-"]')) return;
733
922
 
734
923
  e.preventDefault();
735
924
  e.stopPropagation();
@@ -738,10 +927,7 @@
738
927
  if (!element) return;
739
928
 
740
929
  const content = generateContent(element);
741
- await copyToClipboard(content);
742
-
743
930
  const info = getComponentInfo(element);
744
- showToast(`Copied <${info?.name || 'Component'}>`);
745
931
 
746
932
  // Flash effect
747
933
  if (hoverBox) {
@@ -760,9 +946,19 @@
760
946
  page: getPagePath(),
761
947
  });
762
948
 
949
+ // If Sparkecoder is enabled and connected, show the prompt bar
763
950
  if (config.sparkecoder?.enabled) {
764
- sendToSparkecoder(content).catch(() => {});
951
+ const connection = await ensureSparkecoderConnection();
952
+ if (connection) {
953
+ deactivate();
954
+ showPromptBar(content, info);
955
+ return;
956
+ }
765
957
  }
958
+
959
+ // Fallback: just copy to clipboard
960
+ await copyToClipboard(content);
961
+ showToast(`Copied <${info?.name || 'Component'}>`);
766
962
  }
767
963
 
768
964
  function handleKeyDown(e) {
@@ -775,9 +971,13 @@
775
971
  return;
776
972
  }
777
973
 
778
- if (isActive && e.key === 'Escape') {
974
+ if (e.key === 'Escape') {
779
975
  e.preventDefault();
780
- deactivate();
976
+ if (promptBar && promptBar.style.display !== 'none') {
977
+ hidePromptBar();
978
+ } else if (isActive) {
979
+ deactivate();
980
+ }
781
981
  }
782
982
  }
783
983
 
@@ -891,7 +1091,7 @@
891
1091
  getComponentInfo,
892
1092
  getComponentStack,
893
1093
 
894
- version: '1.0.0',
1094
+ version: '1.1.0',
895
1095
  };
896
1096
 
897
1097
  if (typeof window !== 'undefined') {