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 +146 -0
- package/cli/guard.cjs +144 -0
- package/desktop/dist/daemon.cjs +20696 -0
- package/desktop/dist/overlay.html +283 -0
- package/desktop/dist/preload.cjs +7 -0
- package/desktop/resources/.gitkeep +1 -0
- package/desktop/resources/capture_testpad.ps1 +125 -0
- package/desktop/resources/find_testpad.ps1 +37 -0
- package/desktop/resources/inject_dll.ps1 +43 -0
- package/desktop/resources/unprotect.dll +0 -0
- package/dist/cli/index.js +657 -0
- package/dist/shared/index.cjs +553 -0
- package/dist/shared/index.d.cts +47 -0
- package/dist/shared/index.d.ts +47 -0
- package/dist/shared/index.js +518 -0
- package/docs/deployment.md +27 -0
- package/package.json +80 -0
- package/shared/keys.json +6 -0
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// cli/index.ts
|
|
10
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, copyFileSync } from "fs";
|
|
11
|
+
import { dirname, join as join2, resolve as resolve2 } from "path";
|
|
12
|
+
import { homedir as homedir2 } from "os";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
import { spawn, execSync } from "child_process";
|
|
15
|
+
import { createRequire } from "module";
|
|
16
|
+
import chalk from "chalk";
|
|
17
|
+
import { Command } from "commander";
|
|
18
|
+
import dotenv from "dotenv";
|
|
19
|
+
|
|
20
|
+
// shared/storage.ts
|
|
21
|
+
import Conf from "conf";
|
|
22
|
+
|
|
23
|
+
// shared/crypto.ts
|
|
24
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes } from "crypto";
|
|
25
|
+
var ALGO = "aes-256-gcm";
|
|
26
|
+
function machineScopedSecret() {
|
|
27
|
+
const basis = `${process.platform}|${process.arch}|${process.env.USERNAME ?? process.env.USER ?? "user"}`;
|
|
28
|
+
return createHash("sha256").update(basis).digest();
|
|
29
|
+
}
|
|
30
|
+
function poolSecret() {
|
|
31
|
+
const parts = [112, 97, 103, 101, 97, 105, 45, 112, 111, 111, 108, 45, 118, 50];
|
|
32
|
+
return createHash("sha256").update(Buffer.from(parts)).digest();
|
|
33
|
+
}
|
|
34
|
+
function encryptValue(raw) {
|
|
35
|
+
const iv = randomBytes(12);
|
|
36
|
+
const key = machineScopedSecret();
|
|
37
|
+
const cipher = createCipheriv(ALGO, key, iv);
|
|
38
|
+
const encrypted = Buffer.concat([cipher.update(raw, "utf8"), cipher.final()]);
|
|
39
|
+
const tag = cipher.getAuthTag();
|
|
40
|
+
return Buffer.concat([iv, tag, encrypted]).toString("base64");
|
|
41
|
+
}
|
|
42
|
+
function decryptValue(payload) {
|
|
43
|
+
const data = Buffer.from(payload, "base64");
|
|
44
|
+
const iv = data.subarray(0, 12);
|
|
45
|
+
const tag = data.subarray(12, 28);
|
|
46
|
+
const encrypted = data.subarray(28);
|
|
47
|
+
const key = machineScopedSecret();
|
|
48
|
+
const decipher = createDecipheriv(ALGO, key, iv);
|
|
49
|
+
decipher.setAuthTag(tag);
|
|
50
|
+
const result = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
51
|
+
return result.toString("utf8");
|
|
52
|
+
}
|
|
53
|
+
function decryptPoolKey(payload) {
|
|
54
|
+
const data = Buffer.from(payload, "base64");
|
|
55
|
+
const iv = data.subarray(0, 12);
|
|
56
|
+
const tag = data.subarray(12, 28);
|
|
57
|
+
const encrypted = data.subarray(28);
|
|
58
|
+
const key = poolSecret();
|
|
59
|
+
const decipher = createDecipheriv(ALGO, key, iv);
|
|
60
|
+
decipher.setAuthTag(tag);
|
|
61
|
+
const result = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
62
|
+
return result.toString("utf8");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// shared/storage.ts
|
|
66
|
+
var conf;
|
|
67
|
+
function getConf() {
|
|
68
|
+
if (!conf) {
|
|
69
|
+
conf = new Conf({
|
|
70
|
+
projectName: "Idlidosa",
|
|
71
|
+
defaults: {
|
|
72
|
+
config: {
|
|
73
|
+
model: "llama-3.3-70b-versatile",
|
|
74
|
+
autoSummarizeOnLoad: false,
|
|
75
|
+
theme: "dark",
|
|
76
|
+
stealth: true
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return conf;
|
|
82
|
+
}
|
|
83
|
+
function saveUserConfig(config) {
|
|
84
|
+
const toSave = { ...config };
|
|
85
|
+
if (toSave.apiKey) {
|
|
86
|
+
toSave.apiKey = encryptValue(toSave.apiKey);
|
|
87
|
+
}
|
|
88
|
+
getConf().set("config", toSave);
|
|
89
|
+
}
|
|
90
|
+
function loadUserConfig() {
|
|
91
|
+
const config = getConf().get("config") || {};
|
|
92
|
+
if (config.model === "meta-llama/llama-4-scout-17b-16e-instruct") {
|
|
93
|
+
config.model = "llama-3.3-70b-versatile";
|
|
94
|
+
getConf().set("config", config);
|
|
95
|
+
}
|
|
96
|
+
if (config.apiKey) {
|
|
97
|
+
try {
|
|
98
|
+
return { ...config, apiKey: decryptValue(config.apiKey) };
|
|
99
|
+
} catch {
|
|
100
|
+
return { ...config, apiKey: void 0 };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return config;
|
|
104
|
+
}
|
|
105
|
+
function patchUserConfig(patch) {
|
|
106
|
+
const current = loadUserConfig();
|
|
107
|
+
const merged = { ...current, ...patch };
|
|
108
|
+
saveUserConfig(merged);
|
|
109
|
+
return merged;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// shared/keyPool.ts
|
|
113
|
+
import { createHash as createHash2 } from "crypto";
|
|
114
|
+
import { readFileSync, existsSync } from "fs";
|
|
115
|
+
import { resolve } from "path";
|
|
116
|
+
import nodeMachineId from "node-machine-id";
|
|
117
|
+
var machineIdSync = nodeMachineId.machineIdSync ?? nodeMachineId.default?.machineIdSync ?? (() => {
|
|
118
|
+
throw new Error("node-machine-id is unavailable");
|
|
119
|
+
});
|
|
120
|
+
var cachedPool = null;
|
|
121
|
+
function findKeysFile() {
|
|
122
|
+
const seeds = [];
|
|
123
|
+
if (typeof __dirname !== "undefined") {
|
|
124
|
+
seeds.push(__dirname);
|
|
125
|
+
}
|
|
126
|
+
seeds.push(process.cwd());
|
|
127
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
128
|
+
for (const seed of seeds) {
|
|
129
|
+
candidates.add(resolve(seed, "shared", "keys.json"));
|
|
130
|
+
candidates.add(resolve(seed, "..", "shared", "keys.json"));
|
|
131
|
+
candidates.add(resolve(seed, "..", "..", "shared", "keys.json"));
|
|
132
|
+
candidates.add(resolve(seed, "..", "..", "..", "shared", "keys.json"));
|
|
133
|
+
candidates.add(resolve(seed, "keys.json"));
|
|
134
|
+
}
|
|
135
|
+
for (const candidate of candidates) {
|
|
136
|
+
if (existsSync(candidate)) return candidate;
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
function loadPool() {
|
|
141
|
+
if (cachedPool) return cachedPool;
|
|
142
|
+
const path = findKeysFile();
|
|
143
|
+
if (!path) {
|
|
144
|
+
cachedPool = [];
|
|
145
|
+
return cachedPool;
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const raw = readFileSync(path, "utf8");
|
|
149
|
+
const parsed = JSON.parse(raw);
|
|
150
|
+
const isEncrypted = parsed.v === 2;
|
|
151
|
+
cachedPool = (parsed.keys ?? []).filter((k) => typeof k === "string" && k.length > 0).map((k) => {
|
|
152
|
+
if (isEncrypted) {
|
|
153
|
+
try {
|
|
154
|
+
return decryptPoolKey(k);
|
|
155
|
+
} catch {
|
|
156
|
+
return "";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return k;
|
|
160
|
+
}).filter((k) => k.length > 0 && !k.startsWith("REPLACE_WITH_"));
|
|
161
|
+
} catch {
|
|
162
|
+
cachedPool = [];
|
|
163
|
+
}
|
|
164
|
+
return cachedPool;
|
|
165
|
+
}
|
|
166
|
+
function machineSlot(poolSize) {
|
|
167
|
+
if (poolSize <= 0) return 0;
|
|
168
|
+
let id = "fallback";
|
|
169
|
+
try {
|
|
170
|
+
id = machineIdSync(true);
|
|
171
|
+
} catch {
|
|
172
|
+
id = `${process.platform}|${process.arch}|${process.env.USER ?? process.env.USERNAME ?? "user"}`;
|
|
173
|
+
}
|
|
174
|
+
const digest = createHash2("sha256").update(id).digest();
|
|
175
|
+
return digest.readUInt32BE(0) % poolSize;
|
|
176
|
+
}
|
|
177
|
+
function resolveCurrentKey() {
|
|
178
|
+
const envKey = process.env.GEMINI_API_KEY || process.env.GROQ_API_KEY || process.env.API_KEY;
|
|
179
|
+
if (envKey && envKey.trim().length > 0) {
|
|
180
|
+
return {
|
|
181
|
+
apiKey: envKey.trim(),
|
|
182
|
+
source: "env-variable",
|
|
183
|
+
poolSize: 0
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const config = loadUserConfig();
|
|
187
|
+
const pool = loadPool();
|
|
188
|
+
if (config.apiKey && config.apiKey.trim().length > 0) {
|
|
189
|
+
return {
|
|
190
|
+
apiKey: config.apiKey,
|
|
191
|
+
source: "user-override",
|
|
192
|
+
poolSize: pool.length
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (pool.length === 0) return null;
|
|
196
|
+
let slot = config.keyPoolSlot;
|
|
197
|
+
if (typeof slot !== "number" || slot < 0 || slot >= pool.length) {
|
|
198
|
+
slot = machineSlot(pool.length);
|
|
199
|
+
patchUserConfig({ keyPoolSlot: slot });
|
|
200
|
+
}
|
|
201
|
+
return { apiKey: pool[slot], source: "pool", slot, poolSize: pool.length };
|
|
202
|
+
}
|
|
203
|
+
function rotateKey() {
|
|
204
|
+
const pool = loadPool();
|
|
205
|
+
if (pool.length === 0) return null;
|
|
206
|
+
const config = loadUserConfig();
|
|
207
|
+
const current = typeof config.keyPoolSlot === "number" ? config.keyPoolSlot : machineSlot(pool.length);
|
|
208
|
+
const next = (current + 1) % pool.length;
|
|
209
|
+
patchUserConfig({ keyPoolSlot: next });
|
|
210
|
+
return { apiKey: pool[next], source: "pool", slot: next, poolSize: pool.length };
|
|
211
|
+
}
|
|
212
|
+
function maskKey(key) {
|
|
213
|
+
if (!key) return "(none)";
|
|
214
|
+
if (key.length <= 8) return "********";
|
|
215
|
+
return `${key.slice(0, 4)}\u2026${key.slice(-4)}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// desktop/src/pid.ts
|
|
219
|
+
import { mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync, existsSync as existsSync2 } from "fs";
|
|
220
|
+
import { homedir } from "os";
|
|
221
|
+
import { join } from "path";
|
|
222
|
+
function pidDir() {
|
|
223
|
+
if (process.platform === "win32") {
|
|
224
|
+
const base = process.env.APPDATA ?? join(homedir(), "AppData", "Roaming");
|
|
225
|
+
return join(base, "idlidosa");
|
|
226
|
+
}
|
|
227
|
+
return join(homedir(), ".config", "idlidosa");
|
|
228
|
+
}
|
|
229
|
+
function pidFilePath() {
|
|
230
|
+
return join(pidDir(), "Idlidosa.pid");
|
|
231
|
+
}
|
|
232
|
+
function guardPidPath() {
|
|
233
|
+
return join(pidDir(), "guard.pid");
|
|
234
|
+
}
|
|
235
|
+
function stopFlagPath() {
|
|
236
|
+
return join(pidDir(), "guard.stop");
|
|
237
|
+
}
|
|
238
|
+
function readPidFile() {
|
|
239
|
+
const path = pidFilePath();
|
|
240
|
+
if (!existsSync2(path)) return null;
|
|
241
|
+
try {
|
|
242
|
+
const raw = readFileSync2(path, "utf8").trim();
|
|
243
|
+
const pid = Number.parseInt(raw, 10);
|
|
244
|
+
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
245
|
+
return pid;
|
|
246
|
+
} catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function removePidFile() {
|
|
251
|
+
try {
|
|
252
|
+
rmSync(pidFilePath(), { force: true });
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function readGuardPid() {
|
|
257
|
+
const path = guardPidPath();
|
|
258
|
+
if (!existsSync2(path)) return null;
|
|
259
|
+
try {
|
|
260
|
+
const raw = readFileSync2(path, "utf8").trim();
|
|
261
|
+
const pid = Number.parseInt(raw, 10);
|
|
262
|
+
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
263
|
+
return pid;
|
|
264
|
+
} catch {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function writeStopFlag() {
|
|
269
|
+
mkdirSync(pidDir(), { recursive: true });
|
|
270
|
+
writeFileSync(stopFlagPath(), "stop", "utf8");
|
|
271
|
+
}
|
|
272
|
+
function isProcessAlive(pid) {
|
|
273
|
+
try {
|
|
274
|
+
process.kill(pid, 0);
|
|
275
|
+
return true;
|
|
276
|
+
} catch {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// cli/index.ts
|
|
282
|
+
try {
|
|
283
|
+
const seeds = [dirname(fileURLToPath(import.meta.url)), process.cwd()];
|
|
284
|
+
const candidates = new Set();
|
|
285
|
+
for (const seed of seeds) {
|
|
286
|
+
candidates.add(resolve2(seed, ".env"));
|
|
287
|
+
candidates.add(resolve2(seed, "..", ".env"));
|
|
288
|
+
candidates.add(resolve2(seed, "..", "..", ".env"));
|
|
289
|
+
candidates.add(resolve2(seed, "..", "..", "..", ".env"));
|
|
290
|
+
}
|
|
291
|
+
for (const cand of candidates) {
|
|
292
|
+
if (existsSync3(cand)) {
|
|
293
|
+
dotenv.config({ path: cand });
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
} catch (err) {
|
|
298
|
+
dotenv.config({ quiet: true });
|
|
299
|
+
}
|
|
300
|
+
var HOTKEYS_LINE = "Hotkeys: Left+Right screenshot | Up+Right ask | Alt+S toggle | Up+Left follow | Down+Right copy | Down+Left scroll | Ctrl+Down/Up scroll | Alt+A scan & ask | Alt+Shift+S scroll-capture | Alt hide | Alt+M quit";
|
|
301
|
+
var require_ = createRequire(import.meta.url);
|
|
302
|
+
function packageRoot() {
|
|
303
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
304
|
+
return resolve2(here, "..", "..");
|
|
305
|
+
}
|
|
306
|
+
function resolveDaemonPath() {
|
|
307
|
+
const root = packageRoot();
|
|
308
|
+
const candidates = [
|
|
309
|
+
join2(root, "desktop", "dist", "daemon.cjs"),
|
|
310
|
+
join2(root, "desktop", "dist", "daemon.js"),
|
|
311
|
+
join2(process.cwd(), "desktop", "dist", "daemon.cjs"),
|
|
312
|
+
join2(process.cwd(), "desktop", "dist", "daemon.js")
|
|
313
|
+
];
|
|
314
|
+
for (const candidate of candidates) {
|
|
315
|
+
if (existsSync3(candidate)) return candidate;
|
|
316
|
+
}
|
|
317
|
+
return candidates[0];
|
|
318
|
+
}
|
|
319
|
+
function resolveGuardScript() {
|
|
320
|
+
const root = packageRoot();
|
|
321
|
+
const candidates = [
|
|
322
|
+
join2(root, "dist", "cli", "guard.cjs"),
|
|
323
|
+
join2(root, "cli", "guard.cjs"),
|
|
324
|
+
join2(process.cwd(), "cli", "guard.cjs")
|
|
325
|
+
];
|
|
326
|
+
for (const candidate of candidates) {
|
|
327
|
+
if (existsSync3(candidate)) return candidate;
|
|
328
|
+
}
|
|
329
|
+
return candidates[0];
|
|
330
|
+
}
|
|
331
|
+
function resolveElectronBinary() {
|
|
332
|
+
try {
|
|
333
|
+
const electronPath = require_("electron");
|
|
334
|
+
if (typeof electronPath === "string" && existsSync3(electronPath)) return electronPath;
|
|
335
|
+
} catch {
|
|
336
|
+
}
|
|
337
|
+
const local = join2(packageRoot(), "node_modules", "electron", "cli.js");
|
|
338
|
+
if (existsSync3(local)) return process.execPath;
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
function disguiseElectron(originalPath) {
|
|
342
|
+
if (process.platform !== "win32") return originalPath;
|
|
343
|
+
if (!originalPath.toLowerCase().endsWith("electron.exe")) return originalPath;
|
|
344
|
+
const dir = dirname(originalPath);
|
|
345
|
+
const disguisedName = "msedgewebview2.exe";
|
|
346
|
+
const disguisedPath = join2(dir, disguisedName);
|
|
347
|
+
const markerPath = join2(dir, ".Idlidosa-disguised");
|
|
348
|
+
if (existsSync3(disguisedPath) && existsSync3(markerPath)) return disguisedPath;
|
|
349
|
+
try {
|
|
350
|
+
try {
|
|
351
|
+
if (existsSync3(disguisedPath)) __require("fs").unlinkSync(disguisedPath);
|
|
352
|
+
} catch {
|
|
353
|
+
}
|
|
354
|
+
copyFileSync(originalPath, disguisedPath);
|
|
355
|
+
const rceditCandidates = [
|
|
356
|
+
join2(packageRoot(), "node_modules", "rcedit", "bin", "rcedit-x64.exe"),
|
|
357
|
+
join2(packageRoot(), "node_modules", "rcedit", "bin", "rcedit.exe"),
|
|
358
|
+
join2(process.cwd(), "node_modules", "rcedit", "bin", "rcedit-x64.exe"),
|
|
359
|
+
join2(process.cwd(), "node_modules", "rcedit", "bin", "rcedit.exe")
|
|
360
|
+
];
|
|
361
|
+
let rceditBin = "";
|
|
362
|
+
for (const c of rceditCandidates) {
|
|
363
|
+
if (existsSync3(c)) {
|
|
364
|
+
rceditBin = c;
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (rceditBin) {
|
|
369
|
+
execSync([
|
|
370
|
+
`"${rceditBin}"`,
|
|
371
|
+
`"${disguisedPath}"`,
|
|
372
|
+
`--set-version-string "FileDescription" "Microsoft Edge WebView2"`,
|
|
373
|
+
`--set-version-string "ProductName" "Microsoft Edge WebView2 Runtime"`,
|
|
374
|
+
`--set-version-string "OriginalFilename" "msedgewebview2.exe"`,
|
|
375
|
+
`--set-version-string "InternalName" "msedgewebview2"`,
|
|
376
|
+
`--set-version-string "CompanyName" "Microsoft Corporation"`
|
|
377
|
+
].join(" "), { windowsHide: true, timeout: 15e3, stdio: "ignore" });
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
writeFileSync2(markerPath, "done", "utf8");
|
|
381
|
+
} catch {
|
|
382
|
+
}
|
|
383
|
+
return disguisedPath;
|
|
384
|
+
} catch {
|
|
385
|
+
if (existsSync3(disguisedPath)) return disguisedPath;
|
|
386
|
+
return originalPath;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function installResurrector(nodePath, guardScript, electronBin, daemonPath) {
|
|
390
|
+
if (process.platform !== "win32") return;
|
|
391
|
+
const configDir = join2(process.env.APPDATA || join2(homedir2(), "AppData", "Roaming"), "Idlidosa");
|
|
392
|
+
mkdirSync2(configDir, { recursive: true });
|
|
393
|
+
writeFileSync2(
|
|
394
|
+
join2(configDir, "launch.json"),
|
|
395
|
+
JSON.stringify({ nodePath, guardScript, electronBin, daemonPath }),
|
|
396
|
+
"utf8"
|
|
397
|
+
);
|
|
398
|
+
const launcherScript = join2(configDir, "resurrect.js");
|
|
399
|
+
writeFileSync2(launcherScript, [
|
|
400
|
+
`const fs = require("fs");`,
|
|
401
|
+
`const path = require("path");`,
|
|
402
|
+
`const { spawn } = require("child_process");`,
|
|
403
|
+
`const dir = path.join(process.env.APPDATA || "", "Idlidosa");`,
|
|
404
|
+
`const stopFile = path.join(dir, "guard.stop");`,
|
|
405
|
+
`if (fs.existsSync(stopFile)) process.exit(0);`,
|
|
406
|
+
`const pidFile = path.join(dir, "guard.pid");`,
|
|
407
|
+
`if (fs.existsSync(pidFile)) {`,
|
|
408
|
+
` try { const p = parseInt(fs.readFileSync(pidFile,"utf8")); process.kill(p,0); process.exit(0); } catch {}`,
|
|
409
|
+
`}`,
|
|
410
|
+
`const cfg = JSON.parse(fs.readFileSync(path.join(dir,"launch.json"),"utf8"));`,
|
|
411
|
+
`try { process.title = "Windows Audio Device Graph Isolation"; } catch {}`,
|
|
412
|
+
`spawn(cfg.nodePath, [cfg.guardScript, cfg.electronBin, cfg.daemonPath], {`,
|
|
413
|
+
` detached: true, stdio: "ignore", windowsHide: true`,
|
|
414
|
+
`}).unref();`
|
|
415
|
+
].join("\n"), "utf8");
|
|
416
|
+
try {
|
|
417
|
+
const cmd = `schtasks /create /tn "MicrosoftEdgeWebView2Update" /tr ""${nodePath}" "${launcherScript}"" /sc MINUTE /mo 1 /f`;
|
|
418
|
+
execSync(cmd, { windowsHide: true, timeout: 1e4, stdio: "ignore" });
|
|
419
|
+
} catch {
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function removeResurrector() {
|
|
423
|
+
if (process.platform !== "win32") return;
|
|
424
|
+
try {
|
|
425
|
+
execSync('schtasks /delete /tn "MicrosoftEdgeWebView2Update" /f', {
|
|
426
|
+
windowsHide: true,
|
|
427
|
+
timeout: 5e3,
|
|
428
|
+
stdio: "ignore"
|
|
429
|
+
});
|
|
430
|
+
} catch {
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function ensureSignalDir() {
|
|
434
|
+
const dir = dirname(pidFilePath());
|
|
435
|
+
mkdirSync2(dir, { recursive: true });
|
|
436
|
+
return dir;
|
|
437
|
+
}
|
|
438
|
+
function sendDaemonSignal(payload) {
|
|
439
|
+
const dir = ensureSignalDir();
|
|
440
|
+
writeFileSync2(join2(dir, "signal.json"), JSON.stringify(payload), "utf8");
|
|
441
|
+
}
|
|
442
|
+
async function startCommand(opts) {
|
|
443
|
+
const existing = readPidFile();
|
|
444
|
+
if (existing && isProcessAlive(existing)) {
|
|
445
|
+
console.log(chalk.yellow(`npxsks is already running (pid ${existing}). Run \`npxsks stop\` first.`));
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const daemonPath = resolveDaemonPath();
|
|
449
|
+
if (!existsSync3(daemonPath)) {
|
|
450
|
+
console.log(chalk.red(`Daemon build not found at ${daemonPath}.`));
|
|
451
|
+
console.log(chalk.dim("If you cloned the repo, run `npm run build` first."));
|
|
452
|
+
process.exitCode = 1;
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const electronBin = resolveElectronBinary();
|
|
456
|
+
if (!electronBin) {
|
|
457
|
+
console.log(chalk.red("Could not find Electron binary. Try `npm install` inside the package."));
|
|
458
|
+
process.exitCode = 1;
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const resolved = resolveCurrentKey();
|
|
462
|
+
if (!resolved) {
|
|
463
|
+
console.log(
|
|
464
|
+
chalk.yellow(
|
|
465
|
+
"Warning: no API key available. Populate shared/keys.json before publishing, or run `npxsks key <YOUR_KEY>`."
|
|
466
|
+
)
|
|
467
|
+
);
|
|
468
|
+
} else {
|
|
469
|
+
const where = resolved.source === "user-override" ? "user override" : (resolved.source === "env-variable" ? "environment variable" : `pool slot ${resolved.slot + 1}/${resolved.poolSize}`);
|
|
470
|
+
console.log(chalk.dim(`Using key: ${maskKey(resolved.apiKey)} (${where})`));
|
|
471
|
+
}
|
|
472
|
+
const useGuard = !opts.foreground && !opts.noguard;
|
|
473
|
+
if (useGuard) {
|
|
474
|
+
const guardScript = resolveGuardScript();
|
|
475
|
+
if (!existsSync3(guardScript)) {
|
|
476
|
+
console.log(chalk.yellow("Guard script not found, starting without auto-restart protection."));
|
|
477
|
+
} else {
|
|
478
|
+
const disguisedBin = disguiseElectron(electronBin);
|
|
479
|
+
if (disguisedBin !== electronBin) {
|
|
480
|
+
console.log(chalk.dim(`Binary disguised as ${disguisedBin.split(/[\\/]/).pop()}`));
|
|
481
|
+
}
|
|
482
|
+
const guardChild = spawn(process.execPath, [guardScript, disguisedBin, daemonPath], {
|
|
483
|
+
detached: true,
|
|
484
|
+
stdio: "ignore",
|
|
485
|
+
windowsHide: true
|
|
486
|
+
});
|
|
487
|
+
guardChild.unref();
|
|
488
|
+
installResurrector(process.execPath, guardScript, disguisedBin, daemonPath);
|
|
489
|
+
console.log(chalk.green(`npxsks started with guard (pid ${guardChild.pid}). Auto-restart ON. Stealth ON.`));
|
|
490
|
+
console.log(chalk.dim(HOTKEYS_LINE));
|
|
491
|
+
console.log(
|
|
492
|
+
chalk.dim(
|
|
493
|
+
"Guard mode: if the app is killed, it will restart automatically. Run `npxsks stop` to fully stop."
|
|
494
|
+
)
|
|
495
|
+
);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
const disguisedBinDirect = disguiseElectron(electronBin);
|
|
500
|
+
const stdio = opts.foreground ? "inherit" : "ignore";
|
|
501
|
+
const detached = !opts.foreground;
|
|
502
|
+
const childEnv = { ...process.env, IDLIDOSA_DAEMON: "1" };
|
|
503
|
+
delete childEnv.ELECTRON_RUN_AS_NODE;
|
|
504
|
+
delete childEnv.ELECTRON_NO_ATTACH_CONSOLE;
|
|
505
|
+
const child = spawn(disguisedBinDirect, [daemonPath], {
|
|
506
|
+
detached,
|
|
507
|
+
stdio,
|
|
508
|
+
env: childEnv,
|
|
509
|
+
windowsHide: true
|
|
510
|
+
});
|
|
511
|
+
if (detached) {
|
|
512
|
+
child.unref();
|
|
513
|
+
console.log(chalk.green(`npxsks started (pid ${child.pid}). Stealth ON (hidden from screen share).`));
|
|
514
|
+
console.log(chalk.dim(HOTKEYS_LINE));
|
|
515
|
+
console.log(
|
|
516
|
+
chalk.dim(
|
|
517
|
+
"You can close this terminal \u2014 npxsks keeps running in the background until you press Alt+Shift+K or run `npxsks stop`."
|
|
518
|
+
)
|
|
519
|
+
);
|
|
520
|
+
if (process.platform === "darwin") {
|
|
521
|
+
console.log(
|
|
522
|
+
chalk.dim(
|
|
523
|
+
"macOS: first run will request Accessibility permission. Grant it in System Settings, then `npxsks stop` and `npxsks start` again."
|
|
524
|
+
)
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
} else {
|
|
528
|
+
child.on("exit", (code) => {
|
|
529
|
+
process.exit(code ?? 0);
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
function stopCommand() {
|
|
534
|
+
removeResurrector();
|
|
535
|
+
writeStopFlag();
|
|
536
|
+
const guardPid = readGuardPid();
|
|
537
|
+
if (guardPid && isProcessAlive(guardPid)) {
|
|
538
|
+
try {
|
|
539
|
+
process.kill(guardPid, "SIGTERM");
|
|
540
|
+
console.log(chalk.dim(`Stopped guard (pid ${guardPid}).`));
|
|
541
|
+
} catch {
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const pid = readPidFile();
|
|
545
|
+
if (!pid) {
|
|
546
|
+
if (!guardPid) console.log(chalk.yellow("npxsks is not running (no pid file)."));
|
|
547
|
+
else console.log(chalk.green("Stopped."));
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (!isProcessAlive(pid)) {
|
|
551
|
+
removePidFile();
|
|
552
|
+
console.log(chalk.yellow("npxsks was not running. Cleaned up stale pid file."));
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
process.kill(pid, "SIGTERM");
|
|
557
|
+
console.log(chalk.green(`Sent SIGTERM to daemon (pid ${pid}).`));
|
|
558
|
+
} catch (err) {
|
|
559
|
+
console.log(chalk.red(`Failed to stop pid ${pid}: ${err.message}`));
|
|
560
|
+
process.exitCode = 1;
|
|
561
|
+
}
|
|
562
|
+
console.log(chalk.dim("Scheduled task removed. Full stop complete."));
|
|
563
|
+
}
|
|
564
|
+
function statusCommand() {
|
|
565
|
+
const pid = readPidFile();
|
|
566
|
+
const alive = pid ? isProcessAlive(pid) : false;
|
|
567
|
+
const config = loadUserConfig();
|
|
568
|
+
const resolved = resolveCurrentKey();
|
|
569
|
+
const pool = loadPool();
|
|
570
|
+
console.log("");
|
|
571
|
+
console.log(chalk.bold("npxsks status"));
|
|
572
|
+
console.log(` Daemon: ${alive ? chalk.green(`running (pid ${pid})`) : chalk.dim("stopped")}`);
|
|
573
|
+
console.log(` Stealth: ${config.stealth === false ? chalk.yellow("OFF (visible to screen share)") : chalk.green("ON (hidden from screen share)")}`);
|
|
574
|
+
if (resolved) {
|
|
575
|
+
if (resolved.source === "user-override") {
|
|
576
|
+
console.log(` Key: user-provided (${maskKey(resolved.apiKey)})`);
|
|
577
|
+
} else if (resolved.source === "env-variable") {
|
|
578
|
+
console.log(` Key: environment variable (${maskKey(resolved.apiKey)})`);
|
|
579
|
+
} else {
|
|
580
|
+
console.log(
|
|
581
|
+
` Key: pool slot ${resolved.slot + 1}/${resolved.poolSize} (${maskKey(resolved.apiKey)})`
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
console.log(` Key: ${chalk.red("none")} (pool size: ${pool.length})`);
|
|
586
|
+
}
|
|
587
|
+
console.log(` Model: ${config.model}`);
|
|
588
|
+
console.log(` Pid file: ${pidFilePath()}`);
|
|
589
|
+
console.log("");
|
|
590
|
+
console.log(chalk.dim(HOTKEYS_LINE));
|
|
591
|
+
}
|
|
592
|
+
function rotateCommand() {
|
|
593
|
+
const next = rotateKey();
|
|
594
|
+
if (!next) {
|
|
595
|
+
console.log(chalk.red("Key pool is empty. Nothing to rotate."));
|
|
596
|
+
process.exitCode = 1;
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
console.log(
|
|
600
|
+
chalk.green(
|
|
601
|
+
`Rotated to pool slot ${next.slot + 1}/${next.poolSize} (${maskKey(next.apiKey)}).`
|
|
602
|
+
)
|
|
603
|
+
);
|
|
604
|
+
sendDaemonSignal({ type: "key-rotated" });
|
|
605
|
+
}
|
|
606
|
+
function stealthCommand(value) {
|
|
607
|
+
const v = value.toLowerCase();
|
|
608
|
+
if (v !== "on" && v !== "off") {
|
|
609
|
+
console.log(chalk.red("Usage: npxsks stealth <on|off>"));
|
|
610
|
+
process.exitCode = 1;
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const stealth = v === "on";
|
|
614
|
+
patchUserConfig({ stealth });
|
|
615
|
+
sendDaemonSignal({ type: "stealth-changed" });
|
|
616
|
+
console.log(
|
|
617
|
+
stealth ? chalk.green("Stealth ON. Overlay will be hidden from screen sharing/recording.") : chalk.yellow("Stealth OFF. Overlay will be visible in screen sharing/recording.")
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
function keyCommand(apiKey, opts) {
|
|
621
|
+
if (!apiKey) {
|
|
622
|
+
console.log(chalk.red("Usage: npxsks key <API_KEY>"));
|
|
623
|
+
process.exitCode = 1;
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
const existing = loadUserConfig();
|
|
627
|
+
saveUserConfig({
|
|
628
|
+
apiKey,
|
|
629
|
+
model: opts.model ?? existing.model ?? "llama-3.3-70b-versatile",
|
|
630
|
+
autoSummarizeOnLoad: existing.autoSummarizeOnLoad ?? false,
|
|
631
|
+
theme: existing.theme ?? "dark",
|
|
632
|
+
stealth: existing.stealth ?? true,
|
|
633
|
+
keyPoolSlot: existing.keyPoolSlot
|
|
634
|
+
});
|
|
635
|
+
console.log(chalk.green(`Saved user-provided key (${maskKey(apiKey)}).`));
|
|
636
|
+
}
|
|
637
|
+
function clearKeyCommand() {
|
|
638
|
+
const existing = loadUserConfig();
|
|
639
|
+
saveUserConfig({
|
|
640
|
+
model: existing.model ?? "llama-3.3-70b-versatile",
|
|
641
|
+
autoSummarizeOnLoad: existing.autoSummarizeOnLoad ?? false,
|
|
642
|
+
theme: existing.theme ?? "dark",
|
|
643
|
+
stealth: existing.stealth ?? true,
|
|
644
|
+
keyPoolSlot: existing.keyPoolSlot
|
|
645
|
+
});
|
|
646
|
+
console.log(chalk.green("Cleared user-provided key. Falling back to bundled pool."));
|
|
647
|
+
}
|
|
648
|
+
var app = new Command();
|
|
649
|
+
app.name("npxsks").description("System-wide AI overlay (`npx npxsks start`). Hidden from screen sharing.").version("1.0.10");
|
|
650
|
+
app.command("start", { isDefault: true }).description("Start the overlay daemon with auto-restart guard").option("-f, --foreground", "Run in the foreground (don't detach)").option("--no-guard", "Disable auto-restart guard").action(startCommand);
|
|
651
|
+
app.command("stop").description("Stop the npxsks daemon").action(stopCommand);
|
|
652
|
+
app.command("status").description("Show daemon, stealth, and key status").action(statusCommand);
|
|
653
|
+
app.command("rotate").description("Switch to the next bundled API key (use if rate-limited)").action(rotateCommand);
|
|
654
|
+
app.command("stealth <onOrOff>").description("Toggle hide-from-screen-share mode (on|off)").action(stealthCommand);
|
|
655
|
+
app.command("key <apiKey>").description("Use your own Groq API key (overrides the bundled pool)").option("-m, --model <model>", "Model name", "llama-3.3-70b-versatile").action(keyCommand);
|
|
656
|
+
app.command("clear-key").description("Forget your override key and fall back to the bundled pool").action(clearKeyCommand);
|
|
657
|
+
app.parse();
|