@doppelgangerdev/doppelganger 0.2.2

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 (63) hide show
  1. package/.dockerignore +9 -0
  2. package/.github/workflows/docker-publish.yml +59 -0
  3. package/CODE_OF_CONDUCT.md +28 -0
  4. package/CONTRIBUTING.md +42 -0
  5. package/Dockerfile +44 -0
  6. package/LICENSE +163 -0
  7. package/README.md +133 -0
  8. package/TERMS.md +16 -0
  9. package/THIRD_PARTY_LICENSES.md +3502 -0
  10. package/agent.js +1240 -0
  11. package/headful.js +171 -0
  12. package/index.html +21 -0
  13. package/n8n-nodes-doppelganger/LICENSE +201 -0
  14. package/n8n-nodes-doppelganger/README.md +42 -0
  15. package/n8n-nodes-doppelganger/package-lock.json +6128 -0
  16. package/n8n-nodes-doppelganger/package.json +36 -0
  17. package/n8n-nodes-doppelganger/src/credentials/DoppelgangerApi.credentials.ts +35 -0
  18. package/n8n-nodes-doppelganger/src/index.ts +4 -0
  19. package/n8n-nodes-doppelganger/src/nodes/Doppelganger/Doppelganger.node.ts +147 -0
  20. package/n8n-nodes-doppelganger/src/nodes/Doppelganger/icon.png +0 -0
  21. package/n8n-nodes-doppelganger/tsconfig.json +14 -0
  22. package/package.json +45 -0
  23. package/postcss.config.js +6 -0
  24. package/public/icon.png +0 -0
  25. package/public/novnc.html +151 -0
  26. package/public/styles.css +86 -0
  27. package/scrape.js +389 -0
  28. package/server.js +875 -0
  29. package/src/App.tsx +722 -0
  30. package/src/components/AuthScreen.tsx +95 -0
  31. package/src/components/CodeEditor.tsx +70 -0
  32. package/src/components/DashboardScreen.tsx +133 -0
  33. package/src/components/EditorScreen.tsx +1519 -0
  34. package/src/components/ExecutionDetailScreen.tsx +115 -0
  35. package/src/components/ExecutionsScreen.tsx +156 -0
  36. package/src/components/LoadingScreen.tsx +26 -0
  37. package/src/components/NotFoundScreen.tsx +34 -0
  38. package/src/components/RichInput.tsx +68 -0
  39. package/src/components/SettingsScreen.tsx +228 -0
  40. package/src/components/Sidebar.tsx +61 -0
  41. package/src/components/app/CenterAlert.tsx +44 -0
  42. package/src/components/app/CenterConfirm.tsx +33 -0
  43. package/src/components/app/EditorLoader.tsx +89 -0
  44. package/src/components/editor/ActionPalette.tsx +79 -0
  45. package/src/components/editor/JsonEditorPane.tsx +71 -0
  46. package/src/components/editor/ResultsPane.tsx +641 -0
  47. package/src/components/editor/actionCatalog.ts +23 -0
  48. package/src/components/settings/AgentAiPanel.tsx +105 -0
  49. package/src/components/settings/ApiKeyPanel.tsx +68 -0
  50. package/src/components/settings/CookiesPanel.tsx +154 -0
  51. package/src/components/settings/LayoutPanel.tsx +46 -0
  52. package/src/components/settings/ScreenshotsPanel.tsx +64 -0
  53. package/src/components/settings/SettingsHeader.tsx +28 -0
  54. package/src/components/settings/StoragePanel.tsx +35 -0
  55. package/src/index.css +287 -0
  56. package/src/main.tsx +13 -0
  57. package/src/types.ts +114 -0
  58. package/src/utils/syntaxHighlight.ts +140 -0
  59. package/start-vnc.sh +52 -0
  60. package/tailwind.config.js +22 -0
  61. package/tsconfig.json +39 -0
  62. package/tsconfig.node.json +12 -0
  63. package/vite.config.mts +27 -0
@@ -0,0 +1,95 @@
1
+ import { useState } from 'react';
2
+
3
+ interface AuthScreenProps {
4
+ status: 'login' | 'setup';
5
+ onSubmit: (email: string, pass: string, name?: string, passConfirm?: string) => Promise<void>;
6
+ error: string;
7
+ }
8
+
9
+ const AuthScreen: React.FC<AuthScreenProps> = ({ status, onSubmit, error }) => {
10
+ const [name, setName] = useState('');
11
+ const [email, setEmail] = useState('');
12
+ const [pass, setPass] = useState('');
13
+ const [passConfirm, setPassConfirm] = useState('');
14
+
15
+ const handleSubmit = () => {
16
+ onSubmit(email, pass, name, passConfirm);
17
+ };
18
+
19
+ return (
20
+ <div className="fixed inset-0 z-[100] bg-[#020202] flex items-center justify-center">
21
+ <div className="absolute inset-0 opacity-[0.03] pointer-events-none"
22
+ style={{ backgroundImage: 'radial-gradient(#fff 1px, transparent 1px)', backgroundSize: '40px 40px' }} />
23
+ <div className="w-[400px] glass-card p-10 rounded-[48px] space-y-8 relative">
24
+ <div className="text-center space-y-2">
25
+ <h1 className="text-2xl font-bold tracking-tighter text-white uppercase">Doppelganger</h1>
26
+ <p className="text-[10px] font-bold text-gray-500 uppercase tracking-[0.3em]">
27
+ {status === 'setup' ? 'Initializing System' : 'Access Restricted'}
28
+ </p>
29
+ </div>
30
+
31
+ <div className="space-y-4">
32
+ {status === 'setup' && (
33
+ <div className="space-y-2">
34
+ <label className="text-[9px] font-bold text-gray-400 uppercase tracking-[0.2em]">Name</label>
35
+ <input
36
+ type="text"
37
+ value={name}
38
+ onChange={(e) => setName(e.target.value)}
39
+ placeholder="Full Name"
40
+ className="w-full bg-white/[0.05] border border-white/10 rounded-2xl px-5 py-4 text-sm focus:outline-none focus:border-white/30 transition-all placeholder:text-gray-600"
41
+ />
42
+ </div>
43
+ )}
44
+ <div className="space-y-2">
45
+ <label className="text-[9px] font-bold text-gray-400 uppercase tracking-[0.2em]">Email</label>
46
+ <input
47
+ type="email"
48
+ value={email}
49
+ onChange={(e) => setEmail(e.target.value)}
50
+ placeholder="user@example.com"
51
+ className="w-full bg-white/[0.05] border border-white/10 rounded-2xl px-5 py-4 text-sm focus:outline-none focus:border-white/30 transition-all placeholder:text-gray-600"
52
+ />
53
+ </div>
54
+ <div className="space-y-2">
55
+ <label className="text-[9px] font-bold text-gray-400 uppercase tracking-[0.2em]">Password</label>
56
+ <input
57
+ type="password"
58
+ value={pass}
59
+ onChange={(e) => setPass(e.target.value)}
60
+ placeholder="••••••••"
61
+ className="w-full bg-white/[0.05] border border-white/10 rounded-2xl px-5 py-4 text-sm focus:outline-none focus:border-white/30 transition-all placeholder:text-gray-600"
62
+ />
63
+ </div>
64
+ {status === 'setup' && (
65
+ <div className="space-y-2">
66
+ <label className="text-[9px] font-bold text-gray-400 uppercase tracking-[0.2em]">Confirm Password</label>
67
+ <input
68
+ type="password"
69
+ value={passConfirm}
70
+ onChange={(e) => setPassConfirm(e.target.value)}
71
+ placeholder="••••••••"
72
+ className="w-full bg-white/[0.05] border border-white/10 rounded-2xl px-5 py-4 text-sm focus:outline-none focus:border-white/30 transition-all placeholder:text-gray-600"
73
+ />
74
+ </div>
75
+ )}
76
+ </div>
77
+
78
+ <button
79
+ onClick={handleSubmit}
80
+ className="shine-effect w-full bg-white text-black py-4 rounded-2xl font-bold text-[10px] tracking-[0.3em] uppercase hover:scale-[1.02] active:scale-[0.98] transition-all"
81
+ >
82
+ {status === 'setup' ? 'Create Account' : 'Authenticate'}
83
+ </button>
84
+
85
+ {error && (
86
+ <div className="text-[9px] font-bold text-red-500 text-center uppercase tracking-widest">
87
+ {error}
88
+ </div>
89
+ )}
90
+ </div>
91
+ </div>
92
+ );
93
+ };
94
+
95
+ export default AuthScreen;
@@ -0,0 +1,70 @@
1
+ import { useEffect, useMemo, useRef } from 'react';
2
+ import { highlightCode, SyntaxLanguage } from '../utils/syntaxHighlight';
3
+
4
+ interface CodeEditorProps {
5
+ value: string;
6
+ onChange?: (val: string) => void;
7
+ language: SyntaxLanguage;
8
+ placeholder?: string;
9
+ className?: string;
10
+ readOnly?: boolean;
11
+ variables?: Record<string, any>;
12
+ }
13
+
14
+ const CodeEditor: React.FC<CodeEditorProps> = ({ value, onChange, language, placeholder, className, readOnly, variables }) => {
15
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
16
+ const preRef = useRef<HTMLPreElement>(null);
17
+
18
+ const displayValue = value || placeholder || '';
19
+ const isPlaceholder = !value && !!placeholder;
20
+ const highlighted = useMemo(() => highlightCode(displayValue, language, variables), [displayValue, language, variables]);
21
+
22
+ useEffect(() => {
23
+ const textarea = textareaRef.current;
24
+ const pre = preRef.current;
25
+ if (!textarea || !pre) return;
26
+ const syncScroll = () => {
27
+ pre.scrollTop = textarea.scrollTop;
28
+ pre.scrollLeft = textarea.scrollLeft;
29
+ };
30
+ textarea.addEventListener('scroll', syncScroll);
31
+ return () => {
32
+ textarea.removeEventListener('scroll', syncScroll);
33
+ };
34
+ }, []);
35
+
36
+ return (
37
+ <div
38
+ className={`code-editor ${className || ''}`}
39
+ onWheel={(event) => {
40
+ const textarea = textareaRef.current;
41
+ if (!textarea) return;
42
+ if (textarea.scrollHeight <= textarea.clientHeight) return;
43
+ textarea.scrollTop += event.deltaY;
44
+ textarea.scrollLeft += event.deltaX;
45
+ textarea.focus();
46
+ event.preventDefault();
47
+ }}
48
+ >
49
+ <pre
50
+ ref={preRef}
51
+ className={`code-editor-pre ${isPlaceholder ? 'code-editor-placeholder' : ''}`}
52
+ aria-hidden
53
+ dangerouslySetInnerHTML={{ __html: highlighted }}
54
+ />
55
+ <textarea
56
+ ref={textareaRef}
57
+ value={value}
58
+ onChange={(e) => onChange?.(e.target.value)}
59
+ spellCheck={false}
60
+ wrap="off"
61
+ readOnly={readOnly}
62
+ className={`code-editor-textarea ${readOnly ? 'code-editor-textarea-readonly' : ''}`}
63
+ aria-label="Code editor"
64
+ tabIndex={readOnly ? -1 : 0}
65
+ />
66
+ </div>
67
+ );
68
+ };
69
+
70
+ export default CodeEditor;
@@ -0,0 +1,133 @@
1
+ import { X, Globe, Download, Upload } from 'lucide-react';
2
+ import { useRef } from 'react';
3
+ import { Task } from '../types';
4
+
5
+ interface DashboardScreenProps {
6
+ tasks: Task[];
7
+ onNewTask: () => void;
8
+ onEditTask: (task: Task) => void;
9
+ onDeleteTask: (id: string) => void;
10
+ onExportTasks: () => void;
11
+ onImportTasks: (file: File) => void;
12
+ }
13
+
14
+ const DashboardScreen: React.FC<DashboardScreenProps> = ({ tasks, onNewTask, onEditTask, onDeleteTask, onExportTasks, onImportTasks }) => {
15
+ const fileInputRef = useRef<HTMLInputElement | null>(null);
16
+ const getFavicon = (url: string) => {
17
+ try {
18
+ if (!url) return null;
19
+ const domain = new URL(url).hostname;
20
+ return `https://www.google.com/s2/favicons?domain=${domain}&sz=64`;
21
+ } catch (e) {
22
+ return null;
23
+ }
24
+ };
25
+
26
+ const handleImportClick = () => {
27
+ fileInputRef.current?.click();
28
+ };
29
+
30
+ const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
31
+ const file = event.target.files?.[0];
32
+ if (file) {
33
+ onImportTasks(file);
34
+ }
35
+ event.target.value = '';
36
+ };
37
+
38
+ return (
39
+ <div className="flex-1 overflow-hidden animate-in fade-in duration-500">
40
+ <div className="h-full flex flex-col px-12 py-12 max-w-7xl mx-auto space-y-12 w-full">
41
+ <div className="flex items-end justify-between">
42
+ <div className="space-y-2">
43
+ <p className="text-[10px] font-bold text-blue-500 uppercase tracking-[0.4em]">Status</p>
44
+ <h2 className="text-4xl font-bold tracking-tighter text-white">Dashboard</h2>
45
+ </div>
46
+ <div className="flex items-center gap-3">
47
+ <button
48
+ onClick={onExportTasks}
49
+ className="px-4 py-3 rounded-2xl border border-white/10 text-white text-[9px] font-bold uppercase tracking-[0.3em] hover:bg-white/5 transition-all"
50
+ >
51
+ <Download className="w-4 h-4 inline-block mr-2" />
52
+ Export
53
+ </button>
54
+ <button
55
+ onClick={handleImportClick}
56
+ className="px-4 py-3 rounded-2xl border border-white/10 text-white text-[9px] font-bold uppercase tracking-[0.3em] hover:bg-white/5 transition-all"
57
+ >
58
+ <Upload className="w-4 h-4 inline-block mr-2" />
59
+ Import
60
+ </button>
61
+ <button
62
+ onClick={onNewTask}
63
+ className="shine-effect bg-white text-black px-8 py-3 rounded-2xl font-bold text-[10px] tracking-[0.2em] uppercase transition-all hover:scale-105 active:scale-95"
64
+ >
65
+ + New Task
66
+ </button>
67
+ <input
68
+ ref={fileInputRef}
69
+ type="file"
70
+ accept="application/json"
71
+ className="hidden"
72
+ onChange={handleFileChange}
73
+ />
74
+ </div>
75
+ </div>
76
+
77
+ <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 overflow-y-auto custom-scrollbar pb-12 pr-4">
78
+ {tasks.map(task => {
79
+ const favicon = getFavicon(task.url);
80
+ return (
81
+ <div key={task.id} className="glass-card p-8 rounded-[40px] flex flex-col gap-6 group hover:-translate-y-1">
82
+ <div className="flex justify-between items-start">
83
+ <div className="w-12 h-12 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center overflow-hidden">
84
+ {favicon ? (
85
+ <img
86
+ src={favicon}
87
+ alt=""
88
+ className="w-6 h-6 object-contain"
89
+ onError={(e) => {
90
+ (e.target as HTMLImageElement).style.display = 'none';
91
+ }}
92
+ />
93
+ ) : (
94
+ <Globe className="w-5 h-5 text-gray-500" />
95
+ )}
96
+ </div>
97
+ <div className="px-3 py-1 rounded-full bg-white/5 text-[7px] font-bold uppercase tracking-widest text-gray-500">{task.mode}</div>
98
+ </div>
99
+ <div>
100
+ <h3 className="text-lg font-bold text-white truncate">{task.name || 'Untitled'}</h3>
101
+ <p className="text-[10px] text-gray-600 font-mono truncate mt-1">{task.url || 'Target undefined'}</p>
102
+ </div>
103
+ <div className="flex gap-3 pt-4 border-t border-white/5">
104
+ <button
105
+ onClick={() => onEditTask(task)}
106
+ className="flex-1 py-2 rounded-xl bg-white text-black text-[9px] font-bold uppercase tracking-widest hover:scale-105 transition-all"
107
+ >
108
+ Edit Task
109
+ </button>
110
+ <button
111
+ onClick={() => onDeleteTask(task.id!)}
112
+ className="w-10 h-10 rounded-xl bg-white/5 border border-white/10 flex items-center justify-center hover:bg-red-500/10 hover:text-red-500 transition-all"
113
+ >
114
+ <X className="w-4 h-4" />
115
+ </button>
116
+ </div>
117
+ </div>
118
+ );
119
+ })}
120
+ </div>
121
+
122
+ {tasks.length === 0 && (
123
+ <div className="flex-1 flex flex-col items-center justify-center space-y-6 opacity-20">
124
+ <div className="w-20 h-20 border-2 border-dashed border-white rounded-[40px] flex items-center justify-center text-3xl">🚀</div>
125
+ <p className="text-[10px] font-bold uppercase tracking-[0.3em]">No Tasks Found</p>
126
+ </div>
127
+ )}
128
+ </div>
129
+ </div>
130
+ );
131
+ };
132
+
133
+ export default DashboardScreen;