@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,36 @@
1
+ {
2
+ "name": "n8n-nodes-doppelganger",
3
+ "version": "0.2.1",
4
+ "description": "n8n node for running Doppelganger tasks",
5
+ "license": "Apache-2.0",
6
+ "keywords": [
7
+ "n8n-community-node-package",
8
+ "doppelganger",
9
+ "automation"
10
+ ],
11
+ "main": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc -p tsconfig.json && copyfiles -u 1 \"src/nodes/**/*.{png,svg}\" dist"
18
+ },
19
+ "dependencies": {
20
+ "n8n-workflow": "^1.0.0"
21
+ },
22
+ "devDependencies": {
23
+ "copyfiles": "^2.4.1",
24
+ "n8n-core": "^1.0.0",
25
+ "typescript": "^5.4.0"
26
+ },
27
+ "n8n": {
28
+ "n8nNodesApiVersion": 1,
29
+ "nodes": [
30
+ "dist/nodes/Doppelganger/Doppelganger.node.js"
31
+ ],
32
+ "credentials": [
33
+ "dist/credentials/DoppelgangerApi.credentials.js"
34
+ ]
35
+ }
36
+ }
@@ -0,0 +1,35 @@
1
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow';
2
+
3
+ export class DoppelgangerApi implements ICredentialType {
4
+ name = 'doppelgangerApi';
5
+ displayName = 'Doppelganger API';
6
+ documentationUrl = 'https://doppelgangerdev.com/docs/api-authentication-and-secure-access';
7
+ properties: INodeProperties[] = [
8
+ {
9
+ displayName: 'Base URL',
10
+ name: 'baseUrl',
11
+ type: 'string',
12
+ default: 'http://localhost:11345',
13
+ required: true,
14
+ description: 'Doppelganger server base URL',
15
+ },
16
+ {
17
+ displayName: 'API Key',
18
+ name: 'apiKey',
19
+ type: 'string',
20
+ typeOptions: { password: true },
21
+ default: '',
22
+ required: true,
23
+ description: 'API key from Doppelganger Settings',
24
+ },
25
+ ];
26
+
27
+ authenticate = {
28
+ type: 'generic',
29
+ properties: {
30
+ headers: {
31
+ 'x-api-key': '={{$credentials.apiKey}}',
32
+ },
33
+ },
34
+ } as const;
35
+ }
@@ -0,0 +1,4 @@
1
+ import { Doppelganger } from './nodes/Doppelganger/Doppelganger.node';
2
+ import { DoppelgangerApi } from './credentials/DoppelgangerApi.credentials';
3
+
4
+ export { Doppelganger, DoppelgangerApi };
@@ -0,0 +1,147 @@
1
+ import type {
2
+ IDataObject,
3
+ IExecuteFunctions,
4
+ IHttpRequestMethods,
5
+ INodeExecutionData,
6
+ INodeType,
7
+ INodeTypeDescription,
8
+ } from 'n8n-workflow';
9
+ import { NodeOperationError } from 'n8n-workflow';
10
+
11
+ export class Doppelganger implements INodeType {
12
+ description: INodeTypeDescription = {
13
+ displayName: 'Doppelganger',
14
+ name: 'doppelganger',
15
+ icon: 'file:icon.png',
16
+ group: ['transform'],
17
+ version: 1,
18
+ description: 'Run a Doppelganger task via the API',
19
+ defaults: {
20
+ name: 'Execute Task',
21
+ },
22
+ inputs: ['main'],
23
+ outputs: ['main'],
24
+ usableAsTool: true,
25
+ credentials: [
26
+ {
27
+ name: 'doppelgangerApi',
28
+ required: true,
29
+ },
30
+ ],
31
+ properties: [
32
+ {
33
+ displayName: 'Operation',
34
+ name: 'operation',
35
+ type: 'options',
36
+ noDataExpression: true,
37
+ options: [
38
+ {
39
+ name: 'Execute Task',
40
+ value: 'executeTask',
41
+ description: 'Run a Doppelganger task',
42
+ action: 'Execute a task',
43
+ },
44
+ ],
45
+ default: 'executeTask',
46
+ },
47
+ {
48
+ displayName: 'Task ID',
49
+ name: 'taskId',
50
+ type: 'string',
51
+ default: '',
52
+ required: true,
53
+ description: 'Task ID from the Doppelganger dashboard',
54
+ },
55
+ {
56
+ displayName: 'Variables',
57
+ name: 'variables',
58
+ type: 'fixedCollection',
59
+ default: {},
60
+ required: false,
61
+ description: 'Optional task variables to override',
62
+ typeOptions: {
63
+ multipleValues: true,
64
+ },
65
+ options: [
66
+ {
67
+ name: 'values',
68
+ displayName: 'Variable',
69
+ values: [
70
+ {
71
+ displayName: 'Name',
72
+ name: 'name',
73
+ type: 'string',
74
+ default: '',
75
+ required: true,
76
+ },
77
+ {
78
+ displayName: 'Value',
79
+ name: 'value',
80
+ type: 'string',
81
+ default: '',
82
+ },
83
+ ],
84
+ },
85
+ ],
86
+ },
87
+ ],
88
+ };
89
+
90
+ async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
91
+ const items = this.getInputData();
92
+ const returnData: INodeExecutionData[] = [];
93
+
94
+ for (let i = 0; i < items.length; i++) {
95
+ const operation = this.getNodeParameter('operation', i) as string;
96
+ if (operation !== 'executeTask') {
97
+ throw new NodeOperationError(this.getNode(), 'Unsupported operation.', {
98
+ itemIndex: i,
99
+ });
100
+ }
101
+
102
+ const taskId = this.getNodeParameter('taskId', i) as string;
103
+ const variablesRaw = this.getNodeParameter('variables', i) as {
104
+ values?: Array<{ name?: string; value?: string }>;
105
+ };
106
+
107
+ const variables: IDataObject = {};
108
+ if (variablesRaw?.values?.length) {
109
+ for (const entry of variablesRaw.values) {
110
+ const key = (entry.name || '').trim();
111
+ if (!key) {
112
+ continue;
113
+ }
114
+ variables[key] = entry.value ?? '';
115
+ }
116
+ }
117
+
118
+ const credentials = await this.getCredentials('doppelgangerApi');
119
+ const baseUrl = String(credentials.baseUrl || '').replace(/\/+$/, '');
120
+
121
+ if (!baseUrl) {
122
+ throw new NodeOperationError(this.getNode(), 'Base URL is required in credentials.', {
123
+ itemIndex: i,
124
+ });
125
+ }
126
+
127
+ const options = {
128
+ method: 'POST' as IHttpRequestMethods,
129
+ url: `${baseUrl}/tasks/${encodeURIComponent(taskId)}/api`,
130
+ body: {
131
+ variables,
132
+ },
133
+ json: true,
134
+ };
135
+
136
+ const response = await this.helpers.requestWithAuthentication.call(
137
+ this,
138
+ 'doppelgangerApi',
139
+ options,
140
+ );
141
+
142
+ returnData.push({ json: response as IDataObject });
143
+ }
144
+
145
+ return [returnData];
146
+ }
147
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2019",
4
+ "module": "commonjs",
5
+ "lib": ["ES2019"],
6
+ "declaration": true,
7
+ "outDir": "dist",
8
+ "rootDir": "src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true
12
+ },
13
+ "include": ["src/**/*.ts"]
14
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@doppelgangerdev/doppelganger",
3
+ "version": "0.2.2",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "server": "node server.js",
8
+ "start": "node server.js",
9
+ "build": "tsc && vite build",
10
+ "preview": "vite preview",
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "keywords": [],
14
+ "author": "",
15
+ "license": "ISC",
16
+ "description": "",
17
+ "dependencies": {
18
+ "bcryptjs": "^3.0.3",
19
+ "express": "^5.2.1",
20
+ "express-session": "^1.18.2",
21
+ "jsdom": "^22.1.0",
22
+ "lucide-react": "^0.562.0",
23
+ "pg": "^8.16.3",
24
+ "playwright": "^1.57.0",
25
+ "playwright-extra": "^4.3.6",
26
+ "puppeteer-extra-plugin-stealth": "^2.11.2",
27
+ "react": "^19.2.3",
28
+ "react-dom": "^19.2.3",
29
+ "react-router-dom": "^7.11.0",
30
+ "session-file-store": "^1.5.0"
31
+ },
32
+ "devDependencies": {
33
+ "@tailwindcss/postcss": "^4.1.18",
34
+ "@types/node": "^25.0.3",
35
+ "@types/react": "^19.2.7",
36
+ "@types/react-dom": "^19.2.3",
37
+ "@vitejs/plugin-react": "^5.1.2",
38
+ "autoprefixer": "^10.4.23",
39
+ "cross-env": "^10.1.0",
40
+ "postcss": "^8.5.6",
41
+ "tailwindcss": "^3.4.19",
42
+ "typescript": "^5.9.3",
43
+ "vite": "^7.3.0"
44
+ }
45
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
Binary file
@@ -0,0 +1,151 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta
6
+ name="viewport"
7
+ content="width=device-width, initial-scale=1, maximum-scale=1"
8
+ />
9
+ <title>Headful Browser</title>
10
+ <style>
11
+ :root {
12
+ color-scheme: dark;
13
+ }
14
+ html,
15
+ body {
16
+ width: 100%;
17
+ height: 100%;
18
+ margin: 0;
19
+ background: #050505;
20
+ overflow: hidden;
21
+ font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif;
22
+ }
23
+ #screen {
24
+ width: 100%;
25
+ height: 100%;
26
+ }
27
+ #status {
28
+ position: absolute;
29
+ top: 12px;
30
+ left: 12px;
31
+ background: rgba(0, 0, 0, 0.6);
32
+ border: 1px solid rgba(255, 255, 255, 0.1);
33
+ padding: 6px 10px;
34
+ border-radius: 10px;
35
+ font-size: 11px;
36
+ letter-spacing: 0.08em;
37
+ text-transform: uppercase;
38
+ color: #d1d5db;
39
+ pointer-events: none;
40
+ }
41
+ #status.hidden {
42
+ display: none;
43
+ }
44
+ #controls {
45
+ position: absolute;
46
+ top: 12px;
47
+ right: 12px;
48
+ display: flex;
49
+ gap: 6px;
50
+ z-index: 10;
51
+ }
52
+ #controls button {
53
+ background: rgba(0, 0, 0, 0.6);
54
+ border: 1px solid rgba(255, 255, 255, 0.1);
55
+ color: #d1d5db;
56
+ font-size: 10px;
57
+ letter-spacing: 0.08em;
58
+ text-transform: uppercase;
59
+ padding: 6px 10px;
60
+ border-radius: 10px;
61
+ cursor: pointer;
62
+ }
63
+ #controls button:hover {
64
+ border-color: rgba(255, 255, 255, 0.3);
65
+ }
66
+ </style>
67
+ </head>
68
+ <body>
69
+ <div id="status">Connecting...</div>
70
+ <div id="controls">
71
+ <button id="reset-keys" type="button">Reset Keys</button>
72
+ <button id="toggle-caps" type="button">Toggle Caps</button>
73
+ </div>
74
+ <div id="screen"></div>
75
+ <script type="module">
76
+ import RFB from '/novnc/core/rfb.js';
77
+
78
+ const params = new URLSearchParams(window.location.search);
79
+ const host = params.get('host') || window.location.hostname;
80
+ const port = params.get('port') || '54311';
81
+ const path = params.get('path') || 'websockify';
82
+ const proto = window.location.protocol === 'https:' ? 'wss' : 'ws';
83
+ const wsUrl = `${proto}://${host}:${port}/${path}`;
84
+
85
+ const statusEl = document.getElementById('status');
86
+ const screen = document.getElementById('screen');
87
+ const rfb = new RFB(screen, wsUrl);
88
+ rfb.scaleViewport = true;
89
+ rfb.resizeSession = true;
90
+ rfb.showDotCursor = false;
91
+ rfb.clipViewport = false;
92
+
93
+ const sendKey = (keysym, code, down) => {
94
+ if (typeof rfb.sendKey !== 'function') return;
95
+ try {
96
+ rfb.sendKey(keysym, code, down);
97
+ } catch (err) {
98
+ // ignore
99
+ }
100
+ };
101
+
102
+ const clearModifiers = () => {
103
+ const modifiers = [
104
+ [0xFFE1, 'ShiftLeft'],
105
+ [0xFFE2, 'ShiftRight'],
106
+ [0xFFE3, 'ControlLeft'],
107
+ [0xFFE4, 'ControlRight'],
108
+ [0xFFE9, 'AltLeft'],
109
+ [0xFFEA, 'AltRight'],
110
+ [0xFFE7, 'MetaLeft'],
111
+ [0xFFE8, 'MetaRight']
112
+ ];
113
+ modifiers.forEach(([keysym, code]) => sendKey(keysym, code, false));
114
+ };
115
+
116
+ const toggleCaps = () => {
117
+ sendKey(0xFFE5, 'CapsLock', true);
118
+ sendKey(0xFFE5, 'CapsLock', false);
119
+ };
120
+
121
+ const resetButton = document.getElementById('reset-keys');
122
+ const capsButton = document.getElementById('toggle-caps');
123
+ if (resetButton) {
124
+ resetButton.addEventListener('click', () => {
125
+ clearModifiers();
126
+ });
127
+ }
128
+ if (capsButton) {
129
+ capsButton.addEventListener('click', () => {
130
+ toggleCaps();
131
+ clearModifiers();
132
+ });
133
+ }
134
+
135
+ window.addEventListener('focus', () => {
136
+ clearModifiers();
137
+ });
138
+ window.addEventListener('blur', () => {
139
+ clearModifiers();
140
+ });
141
+
142
+ rfb.addEventListener('connect', () => {
143
+ statusEl.classList.add('hidden');
144
+ });
145
+ rfb.addEventListener('disconnect', () => {
146
+ statusEl.classList.remove('hidden');
147
+ statusEl.textContent = 'Disconnected';
148
+ });
149
+ </script>
150
+ </body>
151
+ </html>
@@ -0,0 +1,86 @@
1
+ /* Doppelgänger Shared Styles */
2
+
3
+ .glass {
4
+ background: rgba(10, 10, 10, 0.9);
5
+ backdrop-filter: blur(64px);
6
+ -webkit-backdrop-filter: blur(64px);
7
+ }
8
+
9
+ .glass-card {
10
+ background: rgba(255, 255, 255, 0.015);
11
+ backdrop-filter: blur(16px);
12
+ -webkit-backdrop-filter: blur(16px);
13
+ border: 1px solid rgba(255, 255, 255, 0.08);
14
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
15
+ }
16
+
17
+ .glass-card:hover {
18
+ background: rgba(255, 255, 255, 0.04);
19
+ border-color: rgba(255, 255, 255, 0.15);
20
+ }
21
+
22
+ .shine-effect {
23
+ position: relative;
24
+ overflow: hidden;
25
+ }
26
+
27
+ .shine-effect::after {
28
+ content: '';
29
+ position: absolute;
30
+ top: -50%;
31
+ left: -100%;
32
+ width: 33.333333%;
33
+ height: 200%;
34
+ background: rgba(255, 255, 255, 0.2);
35
+ transform: rotate(25deg);
36
+ pointer-events: none;
37
+ transition: none;
38
+ }
39
+
40
+ .shine-effect:hover::after {
41
+ left: 150%;
42
+ transition: left 0.7s ease-in-out;
43
+ }
44
+
45
+ .custom-scrollbar::-webkit-scrollbar {
46
+ width: 4px;
47
+ height: 4px;
48
+ }
49
+
50
+ .custom-scrollbar::-webkit-scrollbar-track {
51
+ background: transparent;
52
+ }
53
+
54
+ .custom-scrollbar::-webkit-scrollbar-thumb {
55
+ background: rgba(255, 255, 255, 0.1);
56
+ border-radius: 9999px;
57
+ }
58
+
59
+ .custom-scrollbar::-webkit-scrollbar-thumb:hover {
60
+ background: rgba(255, 255, 255, 0.2);
61
+ }
62
+
63
+ .custom-select {
64
+ appearance: none;
65
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='white' stroke-opacity='0.4'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
66
+ background-repeat: no-repeat;
67
+ background-position: right 1rem center;
68
+ background-size: 1em;
69
+ }
70
+
71
+ .custom-select option {
72
+ background: #0a0a0a;
73
+ color: white;
74
+ }
75
+
76
+ .action-card-item.dragging {
77
+ opacity: 0.5;
78
+ }
79
+
80
+ .action-card-item.drag-over-top {
81
+ border-top: 2px solid rgba(59, 130, 246, 0.5);
82
+ }
83
+
84
+ .action-card-item.drag-over-bottom {
85
+ border-bottom: 2px solid rgba(59, 130, 246, 0.5);
86
+ }