skopix 2.0.10 → 2.0.12
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 +126 -0
- package/package.json +1 -1
- package/skopix_backup/skopix-backup.command +5 -0
- package/skopix_backup/skopix-backup.js +85 -0
- package/skopix_backup/skopix-restore.command +5 -0
- package/skopix_backup/skopix-restore.js +152 -0
- package/skopix_backup.sql +3532 -0
- package/web/app/index.html +1 -0
- package/web/index.html +6 -3
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Skopix
|
|
2
|
+
|
|
3
|
+
Record tests by using your app. Replay them anywhere.
|
|
4
|
+
|
|
5
|
+
**[skopix.ayteelabs.com](https://skopix.ayteelabs.com)**
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g skopix
|
|
13
|
+
npx playwright install chromium
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### Solo mode
|
|
21
|
+
```bash
|
|
22
|
+
skopix dashboard
|
|
23
|
+
```
|
|
24
|
+
Open `http://localhost:9000` and start recording.
|
|
25
|
+
|
|
26
|
+
### Team mode (one command)
|
|
27
|
+
Add to `~/.skopix.env` once:
|
|
28
|
+
```
|
|
29
|
+
SKOPIX_SECRET_KEY=your-secret
|
|
30
|
+
SKOPIX_AGENT_EMAIL=your@email.com
|
|
31
|
+
SKOPIX_AGENT_PASSWORD=yourpassword
|
|
32
|
+
```
|
|
33
|
+
Then:
|
|
34
|
+
```bash
|
|
35
|
+
skopix start
|
|
36
|
+
```
|
|
37
|
+
Starts the dashboard + agent in one command. Teammates connect via `http://YOUR-IP:9000`.
|
|
38
|
+
|
|
39
|
+
### Teammates — connect as agent
|
|
40
|
+
```bash
|
|
41
|
+
skopix agent --server http://HOST-IP:9000 --key "your-secret"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Configure AI
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
skopix init
|
|
50
|
+
```
|
|
51
|
+
Choose Gemini, OpenAI, or Ollama (local — no API key needed).
|
|
52
|
+
|
|
53
|
+
Or set manually in `~/.skopix.env`:
|
|
54
|
+
```
|
|
55
|
+
SKOPIX_PROVIDER=gemini
|
|
56
|
+
GEMINI_API_KEY=your-key
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## All Commands
|
|
62
|
+
|
|
63
|
+
| Command | Description |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `skopix start` | Start dashboard + agent (team mode) |
|
|
66
|
+
| `skopix dashboard` | Start dashboard (solo mode) |
|
|
67
|
+
| `skopix dashboard --team --host 0.0.0.0` | Start dashboard in team mode |
|
|
68
|
+
| `skopix agent --server URL --key SECRET` | Connect as agent to a shared server |
|
|
69
|
+
| `skopix init` | Configure AI provider and API keys |
|
|
70
|
+
| `skopix config --set KEY=value` | Set a config value |
|
|
71
|
+
| `skopix config --list` | List all config values |
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Data & Backup
|
|
76
|
+
|
|
77
|
+
All data lives in `~/.skopix/` — tests, suites, sessions, credentials.
|
|
78
|
+
|
|
79
|
+
Backup:
|
|
80
|
+
```bash
|
|
81
|
+
node skopix-backup.js
|
|
82
|
+
```
|
|
83
|
+
Restore:
|
|
84
|
+
```bash
|
|
85
|
+
node skopix-restore.js
|
|
86
|
+
```
|
|
87
|
+
Download backup scripts from this repo.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Remote Access
|
|
92
|
+
|
|
93
|
+
Expose your dashboard to remote teammates using [Portix](https://portix.dev):
|
|
94
|
+
```bash
|
|
95
|
+
portix 9000 --name skopix
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Export to Playwright
|
|
101
|
+
|
|
102
|
+
Every recorded test generates a `.spec.js` / `.spec.ts` file. Download it from the test editor and run it anywhere with:
|
|
103
|
+
```bash
|
|
104
|
+
npx playwright test
|
|
105
|
+
```
|
|
106
|
+
No Skopix needed to run exported tests.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Environment Variables
|
|
111
|
+
|
|
112
|
+
| Variable | Description |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `SKOPIX_SECRET_KEY` | Required for team mode |
|
|
115
|
+
| `SKOPIX_PROVIDER` | AI provider: `gemini`, `openai`, `ollama` |
|
|
116
|
+
| `GEMINI_API_KEY` | Google Gemini API key |
|
|
117
|
+
| `OPENAI_API_KEY` | OpenAI API key |
|
|
118
|
+
| `OLLAMA_MODEL` | Ollama model name (e.g. `llama3.1`) |
|
|
119
|
+
| `SKOPIX_AGENT_EMAIL` | Auto-agent login email |
|
|
120
|
+
| `SKOPIX_AGENT_PASSWORD` | Auto-agent login password |
|
|
121
|
+
| `BASE_URL` | Override base URL for exported Playwright tests |
|
|
122
|
+
| `TEST_PASSWORD` | Password used in exported Playwright tests |
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
Built by [Aytee Labs](https://ayteelabs.com)
|
package/package.json
CHANGED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ─────────────────────────────────────────────────────────────────
|
|
3
|
+
// SKOPIX BACKUP
|
|
4
|
+
// Works on Mac, Windows, and Linux — no dependencies needed.
|
|
5
|
+
// Usage: node skopix-backup.js
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
|
|
12
|
+
const SKOPIX_DIR = path.join(os.homedir(), '.skopix');
|
|
13
|
+
const TODAY = new Date().toISOString().slice(0, 10);
|
|
14
|
+
const BACKUP_NAME = `skopix-backup-${TODAY}.zip`;
|
|
15
|
+
const DESKTOP = path.join(os.homedir(), 'Desktop');
|
|
16
|
+
const DEST = path.join(DESKTOP, BACKUP_NAME);
|
|
17
|
+
|
|
18
|
+
const c = {
|
|
19
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
20
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
21
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
22
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
23
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function countFiles(dir) {
|
|
27
|
+
let count = 0;
|
|
28
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
29
|
+
if (entry.isDirectory()) count += countFiles(path.join(dir, entry.name));
|
|
30
|
+
else count++;
|
|
31
|
+
}
|
|
32
|
+
return count;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatBytes(bytes) {
|
|
36
|
+
if (bytes < 1024) return bytes + ' B';
|
|
37
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
38
|
+
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function dirSize(dir) {
|
|
42
|
+
let total = 0;
|
|
43
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
44
|
+
const p = path.join(dir, entry.name);
|
|
45
|
+
if (entry.isDirectory()) total += dirSize(p);
|
|
46
|
+
else total += fs.statSync(p).size;
|
|
47
|
+
}
|
|
48
|
+
return total;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log('');
|
|
52
|
+
console.log(c.cyan(c.bold(' SKOPIX BACKUP')));
|
|
53
|
+
console.log(' ' + c.dim('─'.repeat(50)));
|
|
54
|
+
|
|
55
|
+
if (!fs.existsSync(SKOPIX_DIR)) {
|
|
56
|
+
console.log(' ' + c.red('✖') + ' ~/.skopix not found — nothing to back up');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const fileCount = countFiles(SKOPIX_DIR);
|
|
61
|
+
const size = formatBytes(dirSize(SKOPIX_DIR));
|
|
62
|
+
console.log(' Source : ' + c.cyan(SKOPIX_DIR));
|
|
63
|
+
console.log(' Files : ' + fileCount + ' files (' + size + ')');
|
|
64
|
+
console.log(' Saving : ' + c.cyan(DEST));
|
|
65
|
+
console.log('');
|
|
66
|
+
|
|
67
|
+
if (fs.existsSync(DEST)) fs.unlinkSync(DEST);
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const isWindows = process.platform === 'win32';
|
|
71
|
+
if (isWindows) {
|
|
72
|
+
const ps = `Compress-Archive -Path "${SKOPIX_DIR}\\*" -DestinationPath "${DEST}" -Force`;
|
|
73
|
+
execSync(`powershell -Command "${ps}"`, { stdio: 'pipe' });
|
|
74
|
+
} else {
|
|
75
|
+
execSync(`cd "${os.homedir()}" && zip -r "${DEST}" .skopix`, { stdio: 'pipe' });
|
|
76
|
+
}
|
|
77
|
+
console.log(' ' + c.green('✔') + ' Backup saved to:');
|
|
78
|
+
console.log(' ' + c.cyan(DEST));
|
|
79
|
+
console.log('');
|
|
80
|
+
console.log(' ' + c.dim('Restore with: node skopix-restore.js'));
|
|
81
|
+
console.log('');
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.log(' ' + c.red('✖') + ' Backup failed: ' + e.message);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ─────────────────────────────────────────────────────────────────
|
|
3
|
+
// SKOPIX RESTORE
|
|
4
|
+
// Works on Mac, Windows, and Linux — no dependencies needed.
|
|
5
|
+
// Usage: node skopix-restore.js
|
|
6
|
+
// or: node skopix-restore.js /path/to/skopix-backup-2026-05-26.zip
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import os from 'os';
|
|
11
|
+
import { execSync } from 'child_process';
|
|
12
|
+
import readline from 'readline';
|
|
13
|
+
|
|
14
|
+
const SKOPIX_DIR = path.join(os.homedir(), '.skopix');
|
|
15
|
+
|
|
16
|
+
const c = {
|
|
17
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
18
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
19
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
20
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
21
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
22
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function ask(question) {
|
|
26
|
+
return new Promise(resolve => {
|
|
27
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
28
|
+
rl.question(' ' + question, ans => { rl.close(); resolve(ans.trim()); });
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function copyDir(src, dest) {
|
|
33
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
34
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
35
|
+
const s = path.join(src, entry.name);
|
|
36
|
+
const d = path.join(dest, entry.name);
|
|
37
|
+
if (entry.isDirectory()) copyDir(s, d);
|
|
38
|
+
else fs.copyFileSync(s, d);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function countFiles(dir) {
|
|
43
|
+
let count = 0;
|
|
44
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
45
|
+
if (entry.isDirectory()) count += countFiles(path.join(dir, entry.name));
|
|
46
|
+
else count++;
|
|
47
|
+
}
|
|
48
|
+
return count;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function findBackups() {
|
|
52
|
+
const searchDirs = [
|
|
53
|
+
path.join(os.homedir(), 'Desktop'),
|
|
54
|
+
path.join(os.homedir(), 'Downloads'),
|
|
55
|
+
os.homedir(),
|
|
56
|
+
process.cwd(),
|
|
57
|
+
];
|
|
58
|
+
const found = [];
|
|
59
|
+
for (const dir of searchDirs) {
|
|
60
|
+
if (!fs.existsSync(dir)) continue;
|
|
61
|
+
for (const f of fs.readdirSync(dir)) {
|
|
62
|
+
if (f.startsWith('skopix-backup') && f.endsWith('.zip')) {
|
|
63
|
+
found.push(path.join(dir, f));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return found;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log('');
|
|
71
|
+
console.log(c.cyan(c.bold(' SKOPIX RESTORE')));
|
|
72
|
+
console.log(' ' + c.dim('─'.repeat(50)));
|
|
73
|
+
|
|
74
|
+
// Find backup file
|
|
75
|
+
let src = process.argv[2];
|
|
76
|
+
|
|
77
|
+
if (!src) {
|
|
78
|
+
const found = findBackups();
|
|
79
|
+
if (found.length === 0) {
|
|
80
|
+
console.log(' ' + c.red('✖') + ' No backup files found on Desktop or Downloads.');
|
|
81
|
+
console.log(' Run: node skopix-restore.js /path/to/backup.zip');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
} else if (found.length === 1) {
|
|
84
|
+
src = found[0];
|
|
85
|
+
console.log(' Found: ' + c.cyan(src));
|
|
86
|
+
} else {
|
|
87
|
+
console.log(' Found multiple backups:');
|
|
88
|
+
found.forEach((f, i) => console.log(' ' + (i + 1) + '. ' + f));
|
|
89
|
+
const choice = await ask('Which one to restore? (1-' + found.length + '): ');
|
|
90
|
+
const idx = parseInt(choice) - 1;
|
|
91
|
+
if (isNaN(idx) || idx < 0 || idx >= found.length) {
|
|
92
|
+
console.log(' ' + c.red('✖') + ' Invalid choice'); process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
src = found[idx];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!fs.existsSync(src)) {
|
|
99
|
+
console.log(' ' + c.red('✖') + ' File not found: ' + src);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log(' Backup : ' + c.cyan(src));
|
|
104
|
+
console.log('');
|
|
105
|
+
|
|
106
|
+
// Warn about overwrite
|
|
107
|
+
if (fs.existsSync(SKOPIX_DIR)) {
|
|
108
|
+
const existing = countFiles(SKOPIX_DIR);
|
|
109
|
+
console.log(' ' + c.yellow('⚠') + ' This will overwrite ~/.skopix (' + existing + ' files)');
|
|
110
|
+
const confirm = await ask('Continue? (y/N): ');
|
|
111
|
+
if (confirm.toLowerCase() !== 'y') {
|
|
112
|
+
console.log(' ' + c.yellow('⚠') + ' Restore cancelled.');
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Auto-backup existing data first
|
|
117
|
+
const autoBackup = path.join(os.tmpdir(), 'skopix-pre-restore-' + Date.now());
|
|
118
|
+
copyDir(SKOPIX_DIR, autoBackup);
|
|
119
|
+
console.log(' ' + c.dim('Auto-backed up existing data to: ' + autoBackup));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(' Restoring...');
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const tmpDir = path.join(os.tmpdir(), 'skopix-restore-' + Date.now());
|
|
126
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
127
|
+
|
|
128
|
+
const isWindows = process.platform === 'win32';
|
|
129
|
+
if (isWindows) {
|
|
130
|
+
const ps = `Expand-Archive -Path "${src}" -DestinationPath "${tmpDir}" -Force`;
|
|
131
|
+
execSync(`powershell -Command "${ps}"`, { stdio: 'pipe' });
|
|
132
|
+
} else {
|
|
133
|
+
execSync(`unzip -o "${src}" -d "${tmpDir}"`, { stdio: 'pipe' });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Find .skopix inside the extracted folder
|
|
137
|
+
const inner = path.join(tmpDir, '.skopix');
|
|
138
|
+
const actualSrc = fs.existsSync(inner) ? inner : tmpDir;
|
|
139
|
+
|
|
140
|
+
if (fs.existsSync(SKOPIX_DIR)) fs.rmSync(SKOPIX_DIR, { recursive: true, force: true });
|
|
141
|
+
copyDir(actualSrc, SKOPIX_DIR);
|
|
142
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
143
|
+
|
|
144
|
+
const restored = countFiles(SKOPIX_DIR);
|
|
145
|
+
console.log('');
|
|
146
|
+
console.log(' ' + c.green('✔') + ' Restored ' + restored + ' files to ' + c.cyan(SKOPIX_DIR));
|
|
147
|
+
console.log(' ' + c.green('✔') + ' Restart Skopix to see your data');
|
|
148
|
+
console.log('');
|
|
149
|
+
} catch (e) {
|
|
150
|
+
console.log(' ' + c.red('✖') + ' Restore failed: ' + e.message);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|