ashtar 1.0.10

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 ADDED
@@ -0,0 +1,146 @@
1
+ # Idlidosa (npxsks)
2
+
3
+ System-wide AI overlay. Press the same arrow-key combos you'd use in a Chrome
4
+ extension — but **anywhere on your OS** (browser, IDE, PDF viewer, terminal,
5
+ games, anything). The overlay is **hidden from screen sharing and screen
6
+ recording** by default, so Zoom / Meet / Teams / Loom / OBS / QuickTime can't
7
+ see it.
8
+
9
+ Powered by Groq (Llama 4 Scout). The package ships with an encrypted key pool,
10
+ so on first install you don't even need to paste your own key. Keys auto-rotate
11
+ when rate limits are hit.
12
+
13
+ ## Install & run
14
+
15
+ ```bash
16
+ npx npxsks start
17
+ ```
18
+
19
+ That's it. A tray icon appears, the system-wide hotkey listener attaches, and
20
+ the overlay is ready. **Stealth is ON by default** — hidden from all screen
21
+ sharing software automatically.
22
+
23
+ To stop it:
24
+
25
+ ```bash
26
+ npx npxsks stop
27
+ ```
28
+
29
+ Or press **Alt + M** anywhere to instantly quit.
30
+
31
+ ## Hotkeys (work everywhere on your OS)
32
+
33
+ | Combo | Action |
34
+ |---|---|
35
+ | `Left + Right` | Capture full-screen screenshot |
36
+ | `Up + Right` | Ask AI about the current screen |
37
+ | `Alt + S` | Toggle the overlay |
38
+ | `Up + Left` | Toggle follow-cursor mode |
39
+ | `Down + Right` | Copy answer to clipboard |
40
+ | `Alt + A` | Full scan of foreground app + ask AI |
41
+ | `Alt` (alone) | Hide the overlay |
42
+ | `Alt + M` | Quit npxsks instantly |
43
+
44
+ ## Hidden from screen sharing
45
+
46
+ By default the overlay window has OS-level "content protection" turned on:
47
+
48
+ - **macOS**: `NSWindowSharingNone` — invisible to ScreenCaptureKit /
49
+ `CGWindowListCreateImage` / Zoom / Meet / Teams / Loom / OBS / QuickTime.
50
+ - **Windows**: `WDA_EXCLUDEFROMCAPTURE` (Win10 2004+) — hidden from Windows
51
+ Graphics Capture and Desktop Duplication APIs.
52
+ - **Linux/X11**: not supported by the OS — the overlay will appear in screen
53
+ share. (Wayland support is on the roadmap.)
54
+
55
+ To toggle:
56
+
57
+ ```bash
58
+ npxsks stealth on # default
59
+ npxsks stealth off
60
+ ```
61
+
62
+ Or click the tray icon → **Stealth (hide from screen share)** checkbox.
63
+
64
+ ## Commands
65
+
66
+ ```bash
67
+ npxsks start # launch the tray daemon (default command)
68
+ npxsks stop # kill the daemon
69
+ npxsks status # show daemon, stealth, and key state
70
+ npxsks stealth on|off # toggle hide-from-screen-share
71
+ npxsks rotate # pick the next bundled key
72
+ npxsks key <YOUR_KEY> # use your own Groq key (overrides the pool)
73
+ npxsks clear-key # forget your override and fall back to the pool
74
+ ```
75
+
76
+ ## Network compatibility
77
+
78
+ Idlidosa works on any network, including college/corporate networks with
79
+ firewalls or SSL inspection. SSL certificate validation is relaxed to ensure
80
+ connectivity through proxy servers.
81
+
82
+ ## Key management
83
+
84
+ The bundled key pool is AES-256-GCM encrypted in `shared/keys.json`. Keys are
85
+ decrypted at runtime only. This prevents automated key scanners from detecting
86
+ API keys in the npm tarball.
87
+
88
+ When a key hits a rate limit (HTTP 429), Idlidosa automatically rotates to the
89
+ next key in the pool. Each key gets a 60-second cooldown before being retried.
90
+ With 9 keys in the pool, rate limiting is effectively eliminated for normal use.
91
+
92
+ ## Permissions
93
+
94
+ - **macOS**: First run pops the OS Accessibility prompt (System Settings →
95
+ Privacy & Security → Accessibility). Enable Idlidosa, then `npxsks stop` and
96
+ `npxsks start` again. The tray menu has a quick-link.
97
+ - **Windows**: No prompt; works out of the box.
98
+ - **Linux**: Requires X11 (Wayland is unsupported by the underlying keyboard
99
+ hook).
100
+
101
+ ## Project layout
102
+
103
+ ```txt
104
+ npxsks/
105
+ ├── cli/ # `npxsks` CLI entry (start/stop/status/rotate/stealth/key)
106
+ ├── desktop/src/ # Electron daemon, hotkeys, capture, overlay window
107
+ ├── shared/ # types, storage (Conf), groq client, keyPool, crypto
108
+ ├── scripts/ # key encryption utility
109
+ ├── extension/ # legacy Chrome extension
110
+ ├── docs/ # release notes
111
+ ├── package.json
112
+ └── README.md
113
+ ```
114
+
115
+ ## Development
116
+
117
+ ```bash
118
+ npm install
119
+ npm run build
120
+ node dist/cli/index.js start # foreground-friendly: append --foreground
121
+ ```
122
+
123
+ ## Build & publish
124
+
125
+ ```bash
126
+ # 1. Encrypt your Groq keys
127
+ npm run encrypt-keys key1 key2 key3 ...
128
+
129
+ # 2. Build
130
+ npm run build
131
+
132
+ # 3. Sanity check
133
+ npm pack --dry-run
134
+
135
+ # 4. Publish
136
+ npm publish
137
+ ```
138
+
139
+ ## Publishing with a different name
140
+
141
+ To publish under a different npm name (e.g., `studymate`):
142
+
143
+ 1. Edit `package.json`: change `"name"` to your desired name
144
+ 2. Edit `package.json`: change `"bin"` key to match: `"studymate": "dist/cli/index.js"`
145
+ 3. Run `npm run build && npm publish`
146
+ 4. Users install with: `npx studymate start`
package/cli/guard.cjs ADDED
@@ -0,0 +1,144 @@
1
+ // Auto-restart watchdog for the overlay daemon.
2
+ // Runs as a plain node.exe process (which lockdown software rarely kills).
3
+ // If the Electron daemon is killed, this restarts it after a short delay.
4
+
5
+ const { spawn } = require("node:child_process");
6
+ const { existsSync, writeFileSync, readFileSync, rmSync, mkdirSync } = require("node:fs");
7
+ const { join } = require("node:path");
8
+ const { homedir } = require("node:os");
9
+
10
+ // --- Args ---
11
+ const electronPath = process.argv[2];
12
+ const daemonPath = process.argv[3];
13
+ if (!electronPath || !daemonPath) {
14
+ process.exit(1);
15
+ }
16
+
17
+ // --- Disguise this process ---
18
+ try { process.title = "Windows Audio Device Graph Isolation"; } catch {}
19
+
20
+ // --- PID management ---
21
+ function guardDir() {
22
+ const base = process.platform === "win32"
23
+ ? (process.env.APPDATA || join(homedir(), "AppData", "Roaming"))
24
+ : join(homedir(), ".config");
25
+ return join(base, "idlidosa");
26
+ }
27
+
28
+ function guardPidPath() {
29
+ return join(guardDir(), "guard.pid");
30
+ }
31
+
32
+ function stopFlagPath() {
33
+ return join(guardDir(), "guard.stop");
34
+ }
35
+
36
+ function writeGuardPid() {
37
+ mkdirSync(guardDir(), { recursive: true });
38
+ writeFileSync(guardPidPath(), String(process.pid), "utf8");
39
+ }
40
+
41
+ function removeGuardPid() {
42
+ try { rmSync(guardPidPath(), { force: true }); } catch {}
43
+ }
44
+
45
+ function shouldStop() {
46
+ return existsSync(stopFlagPath());
47
+ }
48
+
49
+ function clearStopFlag() {
50
+ try { rmSync(stopFlagPath(), { force: true }); } catch {}
51
+ }
52
+
53
+ // --- Daemon management ---
54
+ let child = null;
55
+ let stopping = false;
56
+ const RESTART_DELAY = 1500; // ms before restarting — fast enough to beat TestPad's 30s scan
57
+ const MAX_RAPID_RESTARTS = 40; // max restarts in rapid window
58
+ const RAPID_WINDOW = 60000; // 60 seconds
59
+ let restartTimes = [];
60
+
61
+ function startDaemon() {
62
+ if (stopping) return;
63
+ if (shouldStop()) {
64
+ cleanup();
65
+ process.exit(0);
66
+ return;
67
+ }
68
+
69
+ // Rapid restart protection
70
+ const now = Date.now();
71
+ restartTimes = restartTimes.filter(t => now - t < RAPID_WINDOW);
72
+ if (restartTimes.length >= MAX_RAPID_RESTARTS) {
73
+ // Too many restarts, wait longer
74
+ setTimeout(startDaemon, 30000);
75
+ return;
76
+ }
77
+ restartTimes.push(now);
78
+
79
+ const env = { ...process.env, IDLIDOSA_GUARDED: "1" };
80
+ delete env.ELECTRON_RUN_AS_NODE;
81
+ delete env.ELECTRON_NO_ATTACH_CONSOLE;
82
+
83
+ child = spawn(electronPath, [daemonPath], {
84
+ detached: false,
85
+ stdio: "ignore",
86
+ env,
87
+ windowsHide: true
88
+ });
89
+
90
+ child.on("exit", () => {
91
+ child = null;
92
+ if (!stopping && !shouldStop()) {
93
+ setTimeout(startDaemon, RESTART_DELAY);
94
+ } else {
95
+ cleanup();
96
+ process.exit(0);
97
+ }
98
+ });
99
+
100
+ child.on("error", () => {
101
+ child = null;
102
+ if (!stopping && !shouldStop()) {
103
+ setTimeout(startDaemon, RESTART_DELAY);
104
+ }
105
+ });
106
+ }
107
+
108
+ function cleanup() {
109
+ removeGuardPid();
110
+ clearStopFlag();
111
+ }
112
+
113
+ function shutdown() {
114
+ stopping = true;
115
+ if (child) {
116
+ try { child.kill("SIGTERM"); } catch {}
117
+ }
118
+ setTimeout(() => {
119
+ cleanup();
120
+ process.exit(0);
121
+ }, 1500);
122
+ }
123
+
124
+ // --- Signal handlers ---
125
+ process.on("SIGTERM", shutdown);
126
+ process.on("SIGINT", shutdown);
127
+ process.on("exit", cleanup);
128
+
129
+ // --- Heartbeat (so daemon can detect if guard is alive) ---
130
+ function heartbeatPath() {
131
+ return join(guardDir(), "guard.heartbeat");
132
+ }
133
+ function writeHeartbeat() {
134
+ try {
135
+ writeFileSync(heartbeatPath(), String(Date.now()), "utf8");
136
+ } catch {}
137
+ }
138
+ const heartbeatTimer = setInterval(writeHeartbeat, 5000);
139
+
140
+ // --- Start ---
141
+ clearStopFlag();
142
+ writeGuardPid();
143
+ writeHeartbeat();
144
+ startDaemon();