@askexenow/exe-os 0.8.45 → 0.8.46
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/dist/bin/exe-boot.js +0 -10
- package/dist/bin/exe-cloud.js +29 -37
- package/dist/bin/exe-link.js +73 -268
- package/dist/bin/exe-settings.js +14 -22
- package/dist/hooks/summary-worker.js +0 -2
- package/dist/lib/cloud-sync.js +68 -261
- package/package.json +1 -1
package/dist/bin/exe-boot.js
CHANGED
|
@@ -2806,14 +2806,6 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
2806
2806
|
);
|
|
2807
2807
|
}
|
|
2808
2808
|
}
|
|
2809
|
-
async function assertFeature(feature) {
|
|
2810
|
-
const license = await checkLicense();
|
|
2811
|
-
if (!isFeatureAllowed(license, feature)) {
|
|
2812
|
-
throw new PlanLimitError(
|
|
2813
|
-
`Feature "${feature}" requires a paid plan. Current plan: ${license.plan}. Upgrade at https://askexe.com.`
|
|
2814
|
-
);
|
|
2815
|
-
}
|
|
2816
|
-
}
|
|
2817
2809
|
var PlanLimitError, CACHE_PATH2;
|
|
2818
2810
|
var init_plan_limits = __esm({
|
|
2819
2811
|
"src/lib/plan-limits.ts"() {
|
|
@@ -5594,7 +5586,6 @@ async function cloudPull(sinceVersion, config) {
|
|
|
5594
5586
|
}
|
|
5595
5587
|
}
|
|
5596
5588
|
async function cloudSync(config) {
|
|
5597
|
-
await assertFeature("cloud_sync");
|
|
5598
5589
|
let client;
|
|
5599
5590
|
try {
|
|
5600
5591
|
client = getClient();
|
|
@@ -6330,7 +6321,6 @@ var init_cloud_sync = __esm({
|
|
|
6330
6321
|
init_database();
|
|
6331
6322
|
init_crypto();
|
|
6332
6323
|
init_compress();
|
|
6333
|
-
init_plan_limits();
|
|
6334
6324
|
init_license();
|
|
6335
6325
|
init_config();
|
|
6336
6326
|
init_employees();
|
package/dist/bin/exe-cloud.js
CHANGED
|
@@ -246,9 +246,9 @@ __export(license_exports, {
|
|
|
246
246
|
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
247
247
|
validateLicense: () => validateLicense
|
|
248
248
|
});
|
|
249
|
-
import { readFileSync as
|
|
249
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3, mkdirSync } from "fs";
|
|
250
250
|
import { randomUUID } from "crypto";
|
|
251
|
-
import
|
|
251
|
+
import path3 from "path";
|
|
252
252
|
import { jwtVerify, importSPKI } from "jose";
|
|
253
253
|
async function fetchRetry(url, init) {
|
|
254
254
|
try {
|
|
@@ -259,17 +259,17 @@ async function fetchRetry(url, init) {
|
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
function loadDeviceId() {
|
|
262
|
-
const deviceJsonPath =
|
|
262
|
+
const deviceJsonPath = path3.join(EXE_AI_DIR, "device.json");
|
|
263
263
|
try {
|
|
264
|
-
if (
|
|
265
|
-
const data = JSON.parse(
|
|
264
|
+
if (existsSync3(deviceJsonPath)) {
|
|
265
|
+
const data = JSON.parse(readFileSync2(deviceJsonPath, "utf8"));
|
|
266
266
|
if (data.deviceId) return data.deviceId;
|
|
267
267
|
}
|
|
268
268
|
} catch {
|
|
269
269
|
}
|
|
270
270
|
try {
|
|
271
|
-
if (
|
|
272
|
-
const id2 =
|
|
271
|
+
if (existsSync3(DEVICE_ID_PATH)) {
|
|
272
|
+
const id2 = readFileSync2(DEVICE_ID_PATH, "utf8").trim();
|
|
273
273
|
if (id2) return id2;
|
|
274
274
|
}
|
|
275
275
|
} catch {
|
|
@@ -281,8 +281,8 @@ function loadDeviceId() {
|
|
|
281
281
|
}
|
|
282
282
|
function loadLicense() {
|
|
283
283
|
try {
|
|
284
|
-
if (!
|
|
285
|
-
return
|
|
284
|
+
if (!existsSync3(LICENSE_PATH)) return null;
|
|
285
|
+
return readFileSync2(LICENSE_PATH, "utf8").trim();
|
|
286
286
|
} catch {
|
|
287
287
|
return null;
|
|
288
288
|
}
|
|
@@ -315,8 +315,8 @@ async function verifyLicenseJwt(token) {
|
|
|
315
315
|
}
|
|
316
316
|
async function getCachedLicense() {
|
|
317
317
|
try {
|
|
318
|
-
if (!
|
|
319
|
-
const raw = JSON.parse(
|
|
318
|
+
if (!existsSync3(CACHE_PATH)) return null;
|
|
319
|
+
const raw = JSON.parse(readFileSync2(CACHE_PATH, "utf8"));
|
|
320
320
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
321
321
|
return await verifyLicenseJwt(raw.token);
|
|
322
322
|
} catch {
|
|
@@ -325,8 +325,8 @@ async function getCachedLicense() {
|
|
|
325
325
|
}
|
|
326
326
|
function readCachedToken() {
|
|
327
327
|
try {
|
|
328
|
-
if (!
|
|
329
|
-
const raw = JSON.parse(
|
|
328
|
+
if (!existsSync3(CACHE_PATH)) return null;
|
|
329
|
+
const raw = JSON.parse(readFileSync2(CACHE_PATH, "utf8"));
|
|
330
330
|
return typeof raw.token === "string" ? raw.token : null;
|
|
331
331
|
} catch {
|
|
332
332
|
return null;
|
|
@@ -392,9 +392,9 @@ async function checkLicense() {
|
|
|
392
392
|
let key = loadLicense();
|
|
393
393
|
if (!key) {
|
|
394
394
|
try {
|
|
395
|
-
const configPath =
|
|
396
|
-
if (
|
|
397
|
-
const raw = JSON.parse(
|
|
395
|
+
const configPath = path3.join(EXE_AI_DIR, "config.json");
|
|
396
|
+
if (existsSync3(configPath)) {
|
|
397
|
+
const raw = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
398
398
|
const cloud = raw.cloud;
|
|
399
399
|
if (cloud?.apiKey) {
|
|
400
400
|
key = cloud.apiKey;
|
|
@@ -553,9 +553,9 @@ var init_license = __esm({
|
|
|
553
553
|
"src/lib/license.ts"() {
|
|
554
554
|
"use strict";
|
|
555
555
|
init_config();
|
|
556
|
-
LICENSE_PATH =
|
|
557
|
-
CACHE_PATH =
|
|
558
|
-
DEVICE_ID_PATH =
|
|
556
|
+
LICENSE_PATH = path3.join(EXE_AI_DIR, "license.key");
|
|
557
|
+
CACHE_PATH = path3.join(EXE_AI_DIR, "license-cache.json");
|
|
558
|
+
DEVICE_ID_PATH = path3.join(EXE_AI_DIR, "device-id");
|
|
559
559
|
API_BASE = "https://askexe.com/cloud";
|
|
560
560
|
RETRY_DELAY_MS = 500;
|
|
561
561
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
@@ -741,9 +741,9 @@ function isMainModule(importMetaUrl) {
|
|
|
741
741
|
|
|
742
742
|
// src/lib/cloud-sync.ts
|
|
743
743
|
init_database();
|
|
744
|
-
import { readFileSync as
|
|
744
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync5, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync, openSync, closeSync } from "fs";
|
|
745
745
|
import crypto4 from "crypto";
|
|
746
|
-
import
|
|
746
|
+
import path5 from "path";
|
|
747
747
|
import { homedir } from "os";
|
|
748
748
|
|
|
749
749
|
// src/lib/crypto.ts
|
|
@@ -752,29 +752,21 @@ import crypto3 from "crypto";
|
|
|
752
752
|
// src/lib/compress.ts
|
|
753
753
|
import { brotliCompressSync, brotliDecompressSync, constants } from "zlib";
|
|
754
754
|
|
|
755
|
-
// src/lib/
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
import path5 from "path";
|
|
755
|
+
// src/lib/cloud-sync.ts
|
|
756
|
+
init_license();
|
|
757
|
+
init_config();
|
|
759
758
|
|
|
760
759
|
// src/lib/employees.ts
|
|
761
760
|
init_config();
|
|
762
761
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
763
|
-
import { existsSync as
|
|
762
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3 } from "fs";
|
|
764
763
|
import { execSync } from "child_process";
|
|
765
|
-
import
|
|
766
|
-
var EMPLOYEES_PATH =
|
|
767
|
-
|
|
768
|
-
// src/lib/plan-limits.ts
|
|
769
|
-
init_license();
|
|
770
|
-
init_config();
|
|
771
|
-
var CACHE_PATH2 = path5.join(EXE_AI_DIR, "license-cache.json");
|
|
764
|
+
import path4 from "path";
|
|
765
|
+
var EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
|
|
772
766
|
|
|
773
767
|
// src/lib/cloud-sync.ts
|
|
774
|
-
init_license();
|
|
775
|
-
init_config();
|
|
776
768
|
var LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
777
|
-
var ROSTER_LOCK_PATH =
|
|
769
|
+
var ROSTER_LOCK_PATH = path5.join(EXE_AI_DIR, "roster-merge.lock");
|
|
778
770
|
function assertSecureEndpoint(endpoint) {
|
|
779
771
|
if (endpoint.startsWith("https://")) return;
|
|
780
772
|
if (endpoint.startsWith("http://")) {
|
|
@@ -789,7 +781,7 @@ function assertSecureEndpoint(endpoint) {
|
|
|
789
781
|
);
|
|
790
782
|
}
|
|
791
783
|
}
|
|
792
|
-
var ROSTER_DELETIONS_PATH =
|
|
784
|
+
var ROSTER_DELETIONS_PATH = path5.join(EXE_AI_DIR, "roster-deletions.json");
|
|
793
785
|
|
|
794
786
|
// src/bin/exe-cloud.ts
|
|
795
787
|
var BAR = "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550";
|
package/dist/bin/exe-link.js
CHANGED
|
@@ -1271,13 +1271,50 @@ var init_compress = __esm({
|
|
|
1271
1271
|
}
|
|
1272
1272
|
});
|
|
1273
1273
|
|
|
1274
|
+
// src/lib/license.ts
|
|
1275
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3, mkdirSync } from "fs";
|
|
1276
|
+
import { randomUUID } from "crypto";
|
|
1277
|
+
import path3 from "path";
|
|
1278
|
+
import { jwtVerify, importSPKI } from "jose";
|
|
1279
|
+
function loadDeviceId() {
|
|
1280
|
+
const deviceJsonPath = path3.join(EXE_AI_DIR, "device.json");
|
|
1281
|
+
try {
|
|
1282
|
+
if (existsSync3(deviceJsonPath)) {
|
|
1283
|
+
const data = JSON.parse(readFileSync2(deviceJsonPath, "utf8"));
|
|
1284
|
+
if (data.deviceId) return data.deviceId;
|
|
1285
|
+
}
|
|
1286
|
+
} catch {
|
|
1287
|
+
}
|
|
1288
|
+
try {
|
|
1289
|
+
if (existsSync3(DEVICE_ID_PATH)) {
|
|
1290
|
+
const id2 = readFileSync2(DEVICE_ID_PATH, "utf8").trim();
|
|
1291
|
+
if (id2) return id2;
|
|
1292
|
+
}
|
|
1293
|
+
} catch {
|
|
1294
|
+
}
|
|
1295
|
+
const id = randomUUID();
|
|
1296
|
+
mkdirSync(EXE_AI_DIR, { recursive: true });
|
|
1297
|
+
writeFileSync(DEVICE_ID_PATH, id, "utf8");
|
|
1298
|
+
return id;
|
|
1299
|
+
}
|
|
1300
|
+
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
|
|
1301
|
+
var init_license = __esm({
|
|
1302
|
+
"src/lib/license.ts"() {
|
|
1303
|
+
"use strict";
|
|
1304
|
+
init_config();
|
|
1305
|
+
LICENSE_PATH = path3.join(EXE_AI_DIR, "license.key");
|
|
1306
|
+
CACHE_PATH = path3.join(EXE_AI_DIR, "license-cache.json");
|
|
1307
|
+
DEVICE_ID_PATH = path3.join(EXE_AI_DIR, "device-id");
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1274
1311
|
// src/lib/employees.ts
|
|
1275
1312
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
1276
|
-
import { existsSync as
|
|
1313
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3 } from "fs";
|
|
1277
1314
|
import { execSync } from "child_process";
|
|
1278
|
-
import
|
|
1315
|
+
import path4 from "path";
|
|
1279
1316
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
1280
|
-
if (!
|
|
1317
|
+
if (!existsSync4(employeesPath)) {
|
|
1281
1318
|
return [];
|
|
1282
1319
|
}
|
|
1283
1320
|
const raw = await readFile3(employeesPath, "utf-8");
|
|
@@ -1288,7 +1325,7 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
1288
1325
|
}
|
|
1289
1326
|
}
|
|
1290
1327
|
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
1291
|
-
await mkdir3(
|
|
1328
|
+
await mkdir3(path4.dirname(employeesPath), { recursive: true });
|
|
1292
1329
|
await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
1293
1330
|
}
|
|
1294
1331
|
function findExeBin() {
|
|
@@ -1307,7 +1344,7 @@ function registerBinSymlinks(name) {
|
|
|
1307
1344
|
errors.push("Could not find 'exe-os' in PATH");
|
|
1308
1345
|
return { created, skipped, errors };
|
|
1309
1346
|
}
|
|
1310
|
-
const binDir =
|
|
1347
|
+
const binDir = path4.dirname(exeBinPath);
|
|
1311
1348
|
let target;
|
|
1312
1349
|
try {
|
|
1313
1350
|
target = readlinkSync(exeBinPath);
|
|
@@ -1317,8 +1354,8 @@ function registerBinSymlinks(name) {
|
|
|
1317
1354
|
}
|
|
1318
1355
|
for (const suffix of ["", "-opencode"]) {
|
|
1319
1356
|
const linkName = `${name}${suffix}`;
|
|
1320
|
-
const linkPath =
|
|
1321
|
-
if (
|
|
1357
|
+
const linkPath = path4.join(binDir, linkName);
|
|
1358
|
+
if (existsSync4(linkPath)) {
|
|
1322
1359
|
skipped.push(linkName);
|
|
1323
1360
|
continue;
|
|
1324
1361
|
}
|
|
@@ -1336,237 +1373,7 @@ var init_employees = __esm({
|
|
|
1336
1373
|
"src/lib/employees.ts"() {
|
|
1337
1374
|
"use strict";
|
|
1338
1375
|
init_config();
|
|
1339
|
-
EMPLOYEES_PATH =
|
|
1340
|
-
}
|
|
1341
|
-
});
|
|
1342
|
-
|
|
1343
|
-
// src/lib/license.ts
|
|
1344
|
-
import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4, mkdirSync } from "fs";
|
|
1345
|
-
import { randomUUID } from "crypto";
|
|
1346
|
-
import path4 from "path";
|
|
1347
|
-
import { jwtVerify, importSPKI } from "jose";
|
|
1348
|
-
async function fetchRetry(url, init) {
|
|
1349
|
-
try {
|
|
1350
|
-
return await fetch(url, init);
|
|
1351
|
-
} catch {
|
|
1352
|
-
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
1353
|
-
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
function loadDeviceId() {
|
|
1357
|
-
const deviceJsonPath = path4.join(EXE_AI_DIR, "device.json");
|
|
1358
|
-
try {
|
|
1359
|
-
if (existsSync4(deviceJsonPath)) {
|
|
1360
|
-
const data = JSON.parse(readFileSync3(deviceJsonPath, "utf8"));
|
|
1361
|
-
if (data.deviceId) return data.deviceId;
|
|
1362
|
-
}
|
|
1363
|
-
} catch {
|
|
1364
|
-
}
|
|
1365
|
-
try {
|
|
1366
|
-
if (existsSync4(DEVICE_ID_PATH)) {
|
|
1367
|
-
const id2 = readFileSync3(DEVICE_ID_PATH, "utf8").trim();
|
|
1368
|
-
if (id2) return id2;
|
|
1369
|
-
}
|
|
1370
|
-
} catch {
|
|
1371
|
-
}
|
|
1372
|
-
const id = randomUUID();
|
|
1373
|
-
mkdirSync(EXE_AI_DIR, { recursive: true });
|
|
1374
|
-
writeFileSync(DEVICE_ID_PATH, id, "utf8");
|
|
1375
|
-
return id;
|
|
1376
|
-
}
|
|
1377
|
-
function loadLicense() {
|
|
1378
|
-
try {
|
|
1379
|
-
if (!existsSync4(LICENSE_PATH)) return null;
|
|
1380
|
-
return readFileSync3(LICENSE_PATH, "utf8").trim();
|
|
1381
|
-
} catch {
|
|
1382
|
-
return null;
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
function saveLicense(apiKey) {
|
|
1386
|
-
mkdirSync(EXE_AI_DIR, { recursive: true });
|
|
1387
|
-
writeFileSync(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
1388
|
-
}
|
|
1389
|
-
async function verifyLicenseJwt(token) {
|
|
1390
|
-
try {
|
|
1391
|
-
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
1392
|
-
const { payload } = await jwtVerify(token, key, {
|
|
1393
|
-
algorithms: [LICENSE_JWT_ALG]
|
|
1394
|
-
});
|
|
1395
|
-
const plan = payload.plan ?? "free";
|
|
1396
|
-
const email = payload.sub ?? "";
|
|
1397
|
-
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1398
|
-
return {
|
|
1399
|
-
valid: true,
|
|
1400
|
-
plan,
|
|
1401
|
-
email,
|
|
1402
|
-
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
1403
|
-
deviceLimit: limits.devices,
|
|
1404
|
-
employeeLimit: limits.employees,
|
|
1405
|
-
memoryLimit: limits.memories
|
|
1406
|
-
};
|
|
1407
|
-
} catch {
|
|
1408
|
-
return null;
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
async function getCachedLicense() {
|
|
1412
|
-
try {
|
|
1413
|
-
if (!existsSync4(CACHE_PATH)) return null;
|
|
1414
|
-
const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
|
|
1415
|
-
if (!raw.token || typeof raw.token !== "string") return null;
|
|
1416
|
-
return await verifyLicenseJwt(raw.token);
|
|
1417
|
-
} catch {
|
|
1418
|
-
return null;
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
function cacheResponse(token) {
|
|
1422
|
-
try {
|
|
1423
|
-
writeFileSync(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
1424
|
-
} catch {
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
async function validateLicense(apiKey, deviceId) {
|
|
1428
|
-
const did = deviceId ?? loadDeviceId();
|
|
1429
|
-
try {
|
|
1430
|
-
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
1431
|
-
method: "POST",
|
|
1432
|
-
headers: { "Content-Type": "application/json" },
|
|
1433
|
-
body: JSON.stringify({ apiKey, deviceId: did }),
|
|
1434
|
-
signal: AbortSignal.timeout(1e4)
|
|
1435
|
-
});
|
|
1436
|
-
if (res.ok) {
|
|
1437
|
-
const data = await res.json();
|
|
1438
|
-
if (data.error === "device_limit_exceeded") {
|
|
1439
|
-
const cached2 = await getCachedLicense();
|
|
1440
|
-
if (cached2) return cached2;
|
|
1441
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
1442
|
-
}
|
|
1443
|
-
if (data.token) {
|
|
1444
|
-
cacheResponse(data.token);
|
|
1445
|
-
const verified = await verifyLicenseJwt(data.token);
|
|
1446
|
-
if (verified) return verified;
|
|
1447
|
-
}
|
|
1448
|
-
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
1449
|
-
return {
|
|
1450
|
-
valid: data.valid,
|
|
1451
|
-
plan: data.plan,
|
|
1452
|
-
email: data.email,
|
|
1453
|
-
expiresAt: data.expiresAt,
|
|
1454
|
-
deviceLimit: limits.devices,
|
|
1455
|
-
employeeLimit: limits.employees,
|
|
1456
|
-
memoryLimit: limits.memories
|
|
1457
|
-
};
|
|
1458
|
-
}
|
|
1459
|
-
const cached = await getCachedLicense();
|
|
1460
|
-
if (cached) return cached;
|
|
1461
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
1462
|
-
} catch {
|
|
1463
|
-
const cached = await getCachedLicense();
|
|
1464
|
-
if (cached) return cached;
|
|
1465
|
-
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
|
-
function getCacheAgeMs() {
|
|
1469
|
-
try {
|
|
1470
|
-
const { statSync } = __require("fs");
|
|
1471
|
-
const s = statSync(CACHE_PATH);
|
|
1472
|
-
return Date.now() - s.mtimeMs;
|
|
1473
|
-
} catch {
|
|
1474
|
-
return Infinity;
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
async function checkLicense() {
|
|
1478
|
-
let key = loadLicense();
|
|
1479
|
-
if (!key) {
|
|
1480
|
-
try {
|
|
1481
|
-
const configPath = path4.join(EXE_AI_DIR, "config.json");
|
|
1482
|
-
if (existsSync4(configPath)) {
|
|
1483
|
-
const raw = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
1484
|
-
const cloud = raw.cloud;
|
|
1485
|
-
if (cloud?.apiKey) {
|
|
1486
|
-
key = cloud.apiKey;
|
|
1487
|
-
saveLicense(key);
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
} catch {
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
if (!key) return FREE_LICENSE;
|
|
1494
|
-
const cached = await getCachedLicense();
|
|
1495
|
-
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
1496
|
-
const deviceId = loadDeviceId();
|
|
1497
|
-
return validateLicense(key, deviceId);
|
|
1498
|
-
}
|
|
1499
|
-
function isFeatureAllowed(license, feature) {
|
|
1500
|
-
switch (feature) {
|
|
1501
|
-
case "cloud_sync":
|
|
1502
|
-
case "external_agents":
|
|
1503
|
-
case "wiki":
|
|
1504
|
-
return license.plan !== "free";
|
|
1505
|
-
case "unlimited_employees":
|
|
1506
|
-
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS;
|
|
1510
|
-
var init_license = __esm({
|
|
1511
|
-
"src/lib/license.ts"() {
|
|
1512
|
-
"use strict";
|
|
1513
|
-
init_config();
|
|
1514
|
-
LICENSE_PATH = path4.join(EXE_AI_DIR, "license.key");
|
|
1515
|
-
CACHE_PATH = path4.join(EXE_AI_DIR, "license-cache.json");
|
|
1516
|
-
DEVICE_ID_PATH = path4.join(EXE_AI_DIR, "device-id");
|
|
1517
|
-
API_BASE = "https://askexe.com/cloud";
|
|
1518
|
-
RETRY_DELAY_MS = 500;
|
|
1519
|
-
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
1520
|
-
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
1521
|
-
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
1522
|
-
-----END PUBLIC KEY-----`;
|
|
1523
|
-
LICENSE_JWT_ALG = "ES256";
|
|
1524
|
-
PLAN_LIMITS = {
|
|
1525
|
-
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1526
|
-
pro: { devices: 2, employees: 5, memories: 1e5 },
|
|
1527
|
-
team: { devices: 10, employees: 20, memories: 1e6 },
|
|
1528
|
-
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
1529
|
-
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
1530
|
-
};
|
|
1531
|
-
FREE_LICENSE = {
|
|
1532
|
-
valid: true,
|
|
1533
|
-
plan: "free",
|
|
1534
|
-
email: "",
|
|
1535
|
-
expiresAt: null,
|
|
1536
|
-
deviceLimit: 1,
|
|
1537
|
-
employeeLimit: 1,
|
|
1538
|
-
memoryLimit: 5e3
|
|
1539
|
-
};
|
|
1540
|
-
CACHE_MAX_AGE_MS = 36e5;
|
|
1541
|
-
}
|
|
1542
|
-
});
|
|
1543
|
-
|
|
1544
|
-
// src/lib/plan-limits.ts
|
|
1545
|
-
import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
|
|
1546
|
-
import path5 from "path";
|
|
1547
|
-
async function assertFeature(feature) {
|
|
1548
|
-
const license = await checkLicense();
|
|
1549
|
-
if (!isFeatureAllowed(license, feature)) {
|
|
1550
|
-
throw new PlanLimitError(
|
|
1551
|
-
`Feature "${feature}" requires a paid plan. Current plan: ${license.plan}. Upgrade at https://askexe.com.`
|
|
1552
|
-
);
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
var PlanLimitError, CACHE_PATH2;
|
|
1556
|
-
var init_plan_limits = __esm({
|
|
1557
|
-
"src/lib/plan-limits.ts"() {
|
|
1558
|
-
"use strict";
|
|
1559
|
-
init_database();
|
|
1560
|
-
init_employees();
|
|
1561
|
-
init_license();
|
|
1562
|
-
init_config();
|
|
1563
|
-
PlanLimitError = class extends Error {
|
|
1564
|
-
constructor(message) {
|
|
1565
|
-
super(message);
|
|
1566
|
-
this.name = "PlanLimitError";
|
|
1567
|
-
}
|
|
1568
|
-
};
|
|
1569
|
-
CACHE_PATH2 = path5.join(EXE_AI_DIR, "license-cache.json");
|
|
1376
|
+
EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
|
|
1570
1377
|
}
|
|
1571
1378
|
});
|
|
1572
1379
|
|
|
@@ -1596,13 +1403,13 @@ __export(cloud_sync_exports, {
|
|
|
1596
1403
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
1597
1404
|
recordRosterDeletion: () => recordRosterDeletion
|
|
1598
1405
|
});
|
|
1599
|
-
import { readFileSync as
|
|
1406
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync5, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync, openSync, closeSync } from "fs";
|
|
1600
1407
|
import crypto3 from "crypto";
|
|
1601
|
-
import
|
|
1408
|
+
import path5 from "path";
|
|
1602
1409
|
import { homedir } from "os";
|
|
1603
1410
|
function logError(msg) {
|
|
1604
1411
|
try {
|
|
1605
|
-
const logPath =
|
|
1412
|
+
const logPath = path5.join(homedir(), ".exe-os", "workers.log");
|
|
1606
1413
|
appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
1607
1414
|
`);
|
|
1608
1415
|
} catch {
|
|
@@ -1616,7 +1423,7 @@ async function withRosterLock(fn) {
|
|
|
1616
1423
|
} catch (err) {
|
|
1617
1424
|
if (err.code === "EEXIST") {
|
|
1618
1425
|
try {
|
|
1619
|
-
const ts = parseInt(
|
|
1426
|
+
const ts = parseInt(readFileSync4(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
1620
1427
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
1621
1428
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
1622
1429
|
}
|
|
@@ -1743,7 +1550,6 @@ async function cloudPull(sinceVersion, config) {
|
|
|
1743
1550
|
}
|
|
1744
1551
|
}
|
|
1745
1552
|
async function cloudSync(config) {
|
|
1746
|
-
await assertFeature("cloud_sync");
|
|
1747
1553
|
let client;
|
|
1748
1554
|
try {
|
|
1749
1555
|
client = getClient();
|
|
@@ -1924,8 +1730,8 @@ async function cloudSync(config) {
|
|
|
1924
1730
|
function recordRosterDeletion(name) {
|
|
1925
1731
|
let deletions = [];
|
|
1926
1732
|
try {
|
|
1927
|
-
if (
|
|
1928
|
-
deletions = JSON.parse(
|
|
1733
|
+
if (existsSync5(ROSTER_DELETIONS_PATH)) {
|
|
1734
|
+
deletions = JSON.parse(readFileSync4(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
1929
1735
|
}
|
|
1930
1736
|
} catch {
|
|
1931
1737
|
}
|
|
@@ -1934,8 +1740,8 @@ function recordRosterDeletion(name) {
|
|
|
1934
1740
|
}
|
|
1935
1741
|
function consumeRosterDeletions() {
|
|
1936
1742
|
try {
|
|
1937
|
-
if (!
|
|
1938
|
-
const deletions = JSON.parse(
|
|
1743
|
+
if (!existsSync5(ROSTER_DELETIONS_PATH)) return [];
|
|
1744
|
+
const deletions = JSON.parse(readFileSync4(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
1939
1745
|
writeFileSync2(ROSTER_DELETIONS_PATH, "[]");
|
|
1940
1746
|
return deletions;
|
|
1941
1747
|
} catch {
|
|
@@ -1943,29 +1749,29 @@ function consumeRosterDeletions() {
|
|
|
1943
1749
|
}
|
|
1944
1750
|
}
|
|
1945
1751
|
function buildRosterBlob(paths) {
|
|
1946
|
-
const rosterPath = paths?.rosterPath ??
|
|
1947
|
-
const identityDir = paths?.identityDir ??
|
|
1948
|
-
const configPath = paths?.configPath ??
|
|
1752
|
+
const rosterPath = paths?.rosterPath ?? path5.join(EXE_AI_DIR, "exe-employees.json");
|
|
1753
|
+
const identityDir = paths?.identityDir ?? path5.join(EXE_AI_DIR, "identity");
|
|
1754
|
+
const configPath = paths?.configPath ?? path5.join(EXE_AI_DIR, "config.json");
|
|
1949
1755
|
let roster = [];
|
|
1950
|
-
if (
|
|
1756
|
+
if (existsSync5(rosterPath)) {
|
|
1951
1757
|
try {
|
|
1952
|
-
roster = JSON.parse(
|
|
1758
|
+
roster = JSON.parse(readFileSync4(rosterPath, "utf-8"));
|
|
1953
1759
|
} catch {
|
|
1954
1760
|
}
|
|
1955
1761
|
}
|
|
1956
1762
|
const identities = {};
|
|
1957
|
-
if (
|
|
1763
|
+
if (existsSync5(identityDir)) {
|
|
1958
1764
|
for (const file of readdirSync(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
1959
1765
|
try {
|
|
1960
|
-
identities[file] =
|
|
1766
|
+
identities[file] = readFileSync4(path5.join(identityDir, file), "utf-8");
|
|
1961
1767
|
} catch {
|
|
1962
1768
|
}
|
|
1963
1769
|
}
|
|
1964
1770
|
}
|
|
1965
1771
|
let config;
|
|
1966
|
-
if (
|
|
1772
|
+
if (existsSync5(configPath)) {
|
|
1967
1773
|
try {
|
|
1968
|
-
config = JSON.parse(
|
|
1774
|
+
config = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
1969
1775
|
} catch {
|
|
1970
1776
|
}
|
|
1971
1777
|
}
|
|
@@ -2041,23 +1847,23 @@ async function cloudPullRoster(config) {
|
|
|
2041
1847
|
}
|
|
2042
1848
|
}
|
|
2043
1849
|
function mergeConfig(remoteConfig, configPath) {
|
|
2044
|
-
const cfgPath = configPath ??
|
|
1850
|
+
const cfgPath = configPath ?? path5.join(EXE_AI_DIR, "config.json");
|
|
2045
1851
|
let local = {};
|
|
2046
|
-
if (
|
|
1852
|
+
if (existsSync5(cfgPath)) {
|
|
2047
1853
|
try {
|
|
2048
|
-
local = JSON.parse(
|
|
1854
|
+
local = JSON.parse(readFileSync4(cfgPath, "utf-8"));
|
|
2049
1855
|
} catch {
|
|
2050
1856
|
}
|
|
2051
1857
|
}
|
|
2052
1858
|
const merged = { ...remoteConfig, ...local };
|
|
2053
|
-
const dir =
|
|
2054
|
-
if (!
|
|
1859
|
+
const dir = path5.dirname(cfgPath);
|
|
1860
|
+
if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
|
|
2055
1861
|
writeFileSync2(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
2056
1862
|
}
|
|
2057
1863
|
async function mergeRosterFromRemote(remote, paths) {
|
|
2058
1864
|
return withRosterLock(async () => {
|
|
2059
1865
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
2060
|
-
const identityDir = paths?.identityDir ??
|
|
1866
|
+
const identityDir = paths?.identityDir ?? path5.join(EXE_AI_DIR, "identity");
|
|
2061
1867
|
const localEmployees = await loadEmployees(rosterPath);
|
|
2062
1868
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
2063
1869
|
let added = 0;
|
|
@@ -2067,9 +1873,9 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
2067
1873
|
localNames.add(remoteEmp.name);
|
|
2068
1874
|
added++;
|
|
2069
1875
|
if (remote.identities[`${remoteEmp.name}.md`]) {
|
|
2070
|
-
if (!
|
|
2071
|
-
const idPath =
|
|
2072
|
-
if (!
|
|
1876
|
+
if (!existsSync5(identityDir)) mkdirSync2(identityDir, { recursive: true });
|
|
1877
|
+
const idPath = path5.join(identityDir, `${remoteEmp.name}.md`);
|
|
1878
|
+
if (!existsSync5(idPath)) {
|
|
2073
1879
|
writeFileSync2(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
|
|
2074
1880
|
}
|
|
2075
1881
|
}
|
|
@@ -2479,16 +2285,15 @@ var init_cloud_sync = __esm({
|
|
|
2479
2285
|
init_database();
|
|
2480
2286
|
init_crypto();
|
|
2481
2287
|
init_compress();
|
|
2482
|
-
init_plan_limits();
|
|
2483
2288
|
init_license();
|
|
2484
2289
|
init_config();
|
|
2485
2290
|
init_employees();
|
|
2486
2291
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
2487
2292
|
FETCH_TIMEOUT_MS = 3e4;
|
|
2488
2293
|
PUSH_BATCH_SIZE = 5e3;
|
|
2489
|
-
ROSTER_LOCK_PATH =
|
|
2294
|
+
ROSTER_LOCK_PATH = path5.join(EXE_AI_DIR, "roster-merge.lock");
|
|
2490
2295
|
LOCK_STALE_MS = 3e4;
|
|
2491
|
-
ROSTER_DELETIONS_PATH =
|
|
2296
|
+
ROSTER_DELETIONS_PATH = path5.join(EXE_AI_DIR, "roster-deletions.json");
|
|
2492
2297
|
}
|
|
2493
2298
|
});
|
|
2494
2299
|
|
package/dist/bin/exe-settings.js
CHANGED
|
@@ -230,9 +230,9 @@ function isMainModule(importMetaUrl) {
|
|
|
230
230
|
|
|
231
231
|
// src/lib/cloud-sync.ts
|
|
232
232
|
init_database();
|
|
233
|
-
import { readFileSync as
|
|
233
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync, openSync, closeSync } from "fs";
|
|
234
234
|
import crypto2 from "crypto";
|
|
235
|
-
import
|
|
235
|
+
import path4 from "path";
|
|
236
236
|
import { homedir } from "os";
|
|
237
237
|
|
|
238
238
|
// src/lib/crypto.ts
|
|
@@ -241,33 +241,25 @@ import crypto from "crypto";
|
|
|
241
241
|
// src/lib/compress.ts
|
|
242
242
|
import { brotliCompressSync, brotliDecompressSync, constants } from "zlib";
|
|
243
243
|
|
|
244
|
-
// src/lib/
|
|
245
|
-
|
|
246
|
-
import {
|
|
247
|
-
import
|
|
244
|
+
// src/lib/license.ts
|
|
245
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
|
|
246
|
+
import { randomUUID } from "crypto";
|
|
247
|
+
import path2 from "path";
|
|
248
|
+
import { jwtVerify, importSPKI } from "jose";
|
|
249
|
+
var LICENSE_PATH = path2.join(EXE_AI_DIR, "license.key");
|
|
250
|
+
var CACHE_PATH = path2.join(EXE_AI_DIR, "license-cache.json");
|
|
251
|
+
var DEVICE_ID_PATH = path2.join(EXE_AI_DIR, "device-id");
|
|
248
252
|
|
|
249
253
|
// src/lib/employees.ts
|
|
250
254
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
251
|
-
import { existsSync as
|
|
255
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3 } from "fs";
|
|
252
256
|
import { execSync } from "child_process";
|
|
253
|
-
import path2 from "path";
|
|
254
|
-
var EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
255
|
-
|
|
256
|
-
// src/lib/license.ts
|
|
257
|
-
import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync3, mkdirSync } from "fs";
|
|
258
|
-
import { randomUUID } from "crypto";
|
|
259
257
|
import path3 from "path";
|
|
260
|
-
|
|
261
|
-
var LICENSE_PATH = path3.join(EXE_AI_DIR, "license.key");
|
|
262
|
-
var CACHE_PATH = path3.join(EXE_AI_DIR, "license-cache.json");
|
|
263
|
-
var DEVICE_ID_PATH = path3.join(EXE_AI_DIR, "device-id");
|
|
264
|
-
|
|
265
|
-
// src/lib/plan-limits.ts
|
|
266
|
-
var CACHE_PATH2 = path4.join(EXE_AI_DIR, "license-cache.json");
|
|
258
|
+
var EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
267
259
|
|
|
268
260
|
// src/lib/cloud-sync.ts
|
|
269
261
|
var LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
270
|
-
var ROSTER_LOCK_PATH =
|
|
262
|
+
var ROSTER_LOCK_PATH = path4.join(EXE_AI_DIR, "roster-merge.lock");
|
|
271
263
|
function assertSecureEndpoint(endpoint) {
|
|
272
264
|
if (endpoint.startsWith("https://")) return;
|
|
273
265
|
if (endpoint.startsWith("http://")) {
|
|
@@ -282,7 +274,7 @@ function assertSecureEndpoint(endpoint) {
|
|
|
282
274
|
);
|
|
283
275
|
}
|
|
284
276
|
}
|
|
285
|
-
var ROSTER_DELETIONS_PATH =
|
|
277
|
+
var ROSTER_DELETIONS_PATH = path4.join(EXE_AI_DIR, "roster-deletions.json");
|
|
286
278
|
|
|
287
279
|
// src/bin/exe-settings.ts
|
|
288
280
|
function label(value) {
|
|
@@ -2709,7 +2709,6 @@ async function cloudPull(sinceVersion, config) {
|
|
|
2709
2709
|
}
|
|
2710
2710
|
}
|
|
2711
2711
|
async function cloudSync(config) {
|
|
2712
|
-
await assertFeature("cloud_sync");
|
|
2713
2712
|
let client;
|
|
2714
2713
|
try {
|
|
2715
2714
|
client = getClient();
|
|
@@ -3445,7 +3444,6 @@ var init_cloud_sync = __esm({
|
|
|
3445
3444
|
init_database();
|
|
3446
3445
|
init_crypto();
|
|
3447
3446
|
init_compress();
|
|
3448
|
-
init_plan_limits();
|
|
3449
3447
|
init_license();
|
|
3450
3448
|
init_config();
|
|
3451
3449
|
init_employees();
|
package/dist/lib/cloud-sync.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
-
}) : x)(function(x) {
|
|
6
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
-
});
|
|
9
3
|
var __esm = (fn, res) => function __init() {
|
|
10
4
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
5
|
};
|
|
@@ -945,9 +939,9 @@ var init_database = __esm({
|
|
|
945
939
|
|
|
946
940
|
// src/lib/cloud-sync.ts
|
|
947
941
|
init_database();
|
|
948
|
-
import { readFileSync as
|
|
942
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync, openSync, closeSync } from "fs";
|
|
949
943
|
import crypto2 from "crypto";
|
|
950
|
-
import
|
|
944
|
+
import path4 from "path";
|
|
951
945
|
import { homedir } from "os";
|
|
952
946
|
|
|
953
947
|
// src/lib/crypto.ts
|
|
@@ -999,16 +993,11 @@ function decompress(input) {
|
|
|
999
993
|
return brotliDecompressSync(input);
|
|
1000
994
|
}
|
|
1001
995
|
|
|
1002
|
-
// src/lib/
|
|
1003
|
-
|
|
1004
|
-
import {
|
|
1005
|
-
import path4 from "path";
|
|
1006
|
-
|
|
1007
|
-
// src/lib/employees.ts
|
|
1008
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1009
|
-
import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
|
|
1010
|
-
import { execSync } from "child_process";
|
|
996
|
+
// src/lib/license.ts
|
|
997
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
|
|
998
|
+
import { randomUUID } from "crypto";
|
|
1011
999
|
import path2 from "path";
|
|
1000
|
+
import { jwtVerify, importSPKI } from "jose";
|
|
1012
1001
|
|
|
1013
1002
|
// src/lib/config.ts
|
|
1014
1003
|
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
@@ -1101,10 +1090,40 @@ var DEFAULT_CONFIG = {
|
|
|
1101
1090
|
}
|
|
1102
1091
|
};
|
|
1103
1092
|
|
|
1093
|
+
// src/lib/license.ts
|
|
1094
|
+
var LICENSE_PATH = path2.join(EXE_AI_DIR, "license.key");
|
|
1095
|
+
var CACHE_PATH = path2.join(EXE_AI_DIR, "license-cache.json");
|
|
1096
|
+
var DEVICE_ID_PATH = path2.join(EXE_AI_DIR, "device-id");
|
|
1097
|
+
function loadDeviceId() {
|
|
1098
|
+
const deviceJsonPath = path2.join(EXE_AI_DIR, "device.json");
|
|
1099
|
+
try {
|
|
1100
|
+
if (existsSync2(deviceJsonPath)) {
|
|
1101
|
+
const data = JSON.parse(readFileSync2(deviceJsonPath, "utf8"));
|
|
1102
|
+
if (data.deviceId) return data.deviceId;
|
|
1103
|
+
}
|
|
1104
|
+
} catch {
|
|
1105
|
+
}
|
|
1106
|
+
try {
|
|
1107
|
+
if (existsSync2(DEVICE_ID_PATH)) {
|
|
1108
|
+
const id2 = readFileSync2(DEVICE_ID_PATH, "utf8").trim();
|
|
1109
|
+
if (id2) return id2;
|
|
1110
|
+
}
|
|
1111
|
+
} catch {
|
|
1112
|
+
}
|
|
1113
|
+
const id = randomUUID();
|
|
1114
|
+
mkdirSync(EXE_AI_DIR, { recursive: true });
|
|
1115
|
+
writeFileSync(DEVICE_ID_PATH, id, "utf8");
|
|
1116
|
+
return id;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1104
1119
|
// src/lib/employees.ts
|
|
1105
|
-
|
|
1120
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1121
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3 } from "fs";
|
|
1122
|
+
import { execSync } from "child_process";
|
|
1123
|
+
import path3 from "path";
|
|
1124
|
+
var EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
1106
1125
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
1107
|
-
if (!
|
|
1126
|
+
if (!existsSync3(employeesPath)) {
|
|
1108
1127
|
return [];
|
|
1109
1128
|
}
|
|
1110
1129
|
const raw = await readFile2(employeesPath, "utf-8");
|
|
@@ -1115,7 +1134,7 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
1115
1134
|
}
|
|
1116
1135
|
}
|
|
1117
1136
|
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
1118
|
-
await mkdir2(
|
|
1137
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
1119
1138
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
1120
1139
|
}
|
|
1121
1140
|
function findExeBin() {
|
|
@@ -1134,7 +1153,7 @@ function registerBinSymlinks(name) {
|
|
|
1134
1153
|
errors.push("Could not find 'exe-os' in PATH");
|
|
1135
1154
|
return { created, skipped, errors };
|
|
1136
1155
|
}
|
|
1137
|
-
const binDir =
|
|
1156
|
+
const binDir = path3.dirname(exeBinPath);
|
|
1138
1157
|
let target;
|
|
1139
1158
|
try {
|
|
1140
1159
|
target = readlinkSync(exeBinPath);
|
|
@@ -1144,8 +1163,8 @@ function registerBinSymlinks(name) {
|
|
|
1144
1163
|
}
|
|
1145
1164
|
for (const suffix of ["", "-opencode"]) {
|
|
1146
1165
|
const linkName = `${name}${suffix}`;
|
|
1147
|
-
const linkPath =
|
|
1148
|
-
if (
|
|
1166
|
+
const linkPath = path3.join(binDir, linkName);
|
|
1167
|
+
if (existsSync3(linkPath)) {
|
|
1149
1168
|
skipped.push(linkName);
|
|
1150
1169
|
continue;
|
|
1151
1170
|
}
|
|
@@ -1159,221 +1178,10 @@ function registerBinSymlinks(name) {
|
|
|
1159
1178
|
return { created, skipped, errors };
|
|
1160
1179
|
}
|
|
1161
1180
|
|
|
1162
|
-
// src/lib/license.ts
|
|
1163
|
-
import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync3, mkdirSync } from "fs";
|
|
1164
|
-
import { randomUUID } from "crypto";
|
|
1165
|
-
import path3 from "path";
|
|
1166
|
-
import { jwtVerify, importSPKI } from "jose";
|
|
1167
|
-
var LICENSE_PATH = path3.join(EXE_AI_DIR, "license.key");
|
|
1168
|
-
var CACHE_PATH = path3.join(EXE_AI_DIR, "license-cache.json");
|
|
1169
|
-
var DEVICE_ID_PATH = path3.join(EXE_AI_DIR, "device-id");
|
|
1170
|
-
var API_BASE = "https://askexe.com/cloud";
|
|
1171
|
-
var RETRY_DELAY_MS = 500;
|
|
1172
|
-
async function fetchRetry(url, init) {
|
|
1173
|
-
try {
|
|
1174
|
-
return await fetch(url, init);
|
|
1175
|
-
} catch {
|
|
1176
|
-
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
1177
|
-
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
var LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
1181
|
-
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
1182
|
-
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
1183
|
-
-----END PUBLIC KEY-----`;
|
|
1184
|
-
var LICENSE_JWT_ALG = "ES256";
|
|
1185
|
-
var PLAN_LIMITS = {
|
|
1186
|
-
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1187
|
-
pro: { devices: 2, employees: 5, memories: 1e5 },
|
|
1188
|
-
team: { devices: 10, employees: 20, memories: 1e6 },
|
|
1189
|
-
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
1190
|
-
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
1191
|
-
};
|
|
1192
|
-
var FREE_LICENSE = {
|
|
1193
|
-
valid: true,
|
|
1194
|
-
plan: "free",
|
|
1195
|
-
email: "",
|
|
1196
|
-
expiresAt: null,
|
|
1197
|
-
deviceLimit: 1,
|
|
1198
|
-
employeeLimit: 1,
|
|
1199
|
-
memoryLimit: 5e3
|
|
1200
|
-
};
|
|
1201
|
-
function loadDeviceId() {
|
|
1202
|
-
const deviceJsonPath = path3.join(EXE_AI_DIR, "device.json");
|
|
1203
|
-
try {
|
|
1204
|
-
if (existsSync3(deviceJsonPath)) {
|
|
1205
|
-
const data = JSON.parse(readFileSync3(deviceJsonPath, "utf8"));
|
|
1206
|
-
if (data.deviceId) return data.deviceId;
|
|
1207
|
-
}
|
|
1208
|
-
} catch {
|
|
1209
|
-
}
|
|
1210
|
-
try {
|
|
1211
|
-
if (existsSync3(DEVICE_ID_PATH)) {
|
|
1212
|
-
const id2 = readFileSync3(DEVICE_ID_PATH, "utf8").trim();
|
|
1213
|
-
if (id2) return id2;
|
|
1214
|
-
}
|
|
1215
|
-
} catch {
|
|
1216
|
-
}
|
|
1217
|
-
const id = randomUUID();
|
|
1218
|
-
mkdirSync(EXE_AI_DIR, { recursive: true });
|
|
1219
|
-
writeFileSync(DEVICE_ID_PATH, id, "utf8");
|
|
1220
|
-
return id;
|
|
1221
|
-
}
|
|
1222
|
-
function loadLicense() {
|
|
1223
|
-
try {
|
|
1224
|
-
if (!existsSync3(LICENSE_PATH)) return null;
|
|
1225
|
-
return readFileSync3(LICENSE_PATH, "utf8").trim();
|
|
1226
|
-
} catch {
|
|
1227
|
-
return null;
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
function saveLicense(apiKey) {
|
|
1231
|
-
mkdirSync(EXE_AI_DIR, { recursive: true });
|
|
1232
|
-
writeFileSync(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
1233
|
-
}
|
|
1234
|
-
async function verifyLicenseJwt(token) {
|
|
1235
|
-
try {
|
|
1236
|
-
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
1237
|
-
const { payload } = await jwtVerify(token, key, {
|
|
1238
|
-
algorithms: [LICENSE_JWT_ALG]
|
|
1239
|
-
});
|
|
1240
|
-
const plan = payload.plan ?? "free";
|
|
1241
|
-
const email = payload.sub ?? "";
|
|
1242
|
-
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1243
|
-
return {
|
|
1244
|
-
valid: true,
|
|
1245
|
-
plan,
|
|
1246
|
-
email,
|
|
1247
|
-
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
1248
|
-
deviceLimit: limits.devices,
|
|
1249
|
-
employeeLimit: limits.employees,
|
|
1250
|
-
memoryLimit: limits.memories
|
|
1251
|
-
};
|
|
1252
|
-
} catch {
|
|
1253
|
-
return null;
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
async function getCachedLicense() {
|
|
1257
|
-
try {
|
|
1258
|
-
if (!existsSync3(CACHE_PATH)) return null;
|
|
1259
|
-
const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
|
|
1260
|
-
if (!raw.token || typeof raw.token !== "string") return null;
|
|
1261
|
-
return await verifyLicenseJwt(raw.token);
|
|
1262
|
-
} catch {
|
|
1263
|
-
return null;
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
function cacheResponse(token) {
|
|
1267
|
-
try {
|
|
1268
|
-
writeFileSync(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
1269
|
-
} catch {
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
async function validateLicense(apiKey, deviceId) {
|
|
1273
|
-
const did = deviceId ?? loadDeviceId();
|
|
1274
|
-
try {
|
|
1275
|
-
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
1276
|
-
method: "POST",
|
|
1277
|
-
headers: { "Content-Type": "application/json" },
|
|
1278
|
-
body: JSON.stringify({ apiKey, deviceId: did }),
|
|
1279
|
-
signal: AbortSignal.timeout(1e4)
|
|
1280
|
-
});
|
|
1281
|
-
if (res.ok) {
|
|
1282
|
-
const data = await res.json();
|
|
1283
|
-
if (data.error === "device_limit_exceeded") {
|
|
1284
|
-
const cached2 = await getCachedLicense();
|
|
1285
|
-
if (cached2) return cached2;
|
|
1286
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
1287
|
-
}
|
|
1288
|
-
if (data.token) {
|
|
1289
|
-
cacheResponse(data.token);
|
|
1290
|
-
const verified = await verifyLicenseJwt(data.token);
|
|
1291
|
-
if (verified) return verified;
|
|
1292
|
-
}
|
|
1293
|
-
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
1294
|
-
return {
|
|
1295
|
-
valid: data.valid,
|
|
1296
|
-
plan: data.plan,
|
|
1297
|
-
email: data.email,
|
|
1298
|
-
expiresAt: data.expiresAt,
|
|
1299
|
-
deviceLimit: limits.devices,
|
|
1300
|
-
employeeLimit: limits.employees,
|
|
1301
|
-
memoryLimit: limits.memories
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
|
-
const cached = await getCachedLicense();
|
|
1305
|
-
if (cached) return cached;
|
|
1306
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
1307
|
-
} catch {
|
|
1308
|
-
const cached = await getCachedLicense();
|
|
1309
|
-
if (cached) return cached;
|
|
1310
|
-
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
var CACHE_MAX_AGE_MS = 36e5;
|
|
1314
|
-
function getCacheAgeMs() {
|
|
1315
|
-
try {
|
|
1316
|
-
const { statSync } = __require("fs");
|
|
1317
|
-
const s = statSync(CACHE_PATH);
|
|
1318
|
-
return Date.now() - s.mtimeMs;
|
|
1319
|
-
} catch {
|
|
1320
|
-
return Infinity;
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
async function checkLicense() {
|
|
1324
|
-
let key = loadLicense();
|
|
1325
|
-
if (!key) {
|
|
1326
|
-
try {
|
|
1327
|
-
const configPath = path3.join(EXE_AI_DIR, "config.json");
|
|
1328
|
-
if (existsSync3(configPath)) {
|
|
1329
|
-
const raw = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
1330
|
-
const cloud = raw.cloud;
|
|
1331
|
-
if (cloud?.apiKey) {
|
|
1332
|
-
key = cloud.apiKey;
|
|
1333
|
-
saveLicense(key);
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
} catch {
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
if (!key) return FREE_LICENSE;
|
|
1340
|
-
const cached = await getCachedLicense();
|
|
1341
|
-
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
1342
|
-
const deviceId = loadDeviceId();
|
|
1343
|
-
return validateLicense(key, deviceId);
|
|
1344
|
-
}
|
|
1345
|
-
function isFeatureAllowed(license, feature) {
|
|
1346
|
-
switch (feature) {
|
|
1347
|
-
case "cloud_sync":
|
|
1348
|
-
case "external_agents":
|
|
1349
|
-
case "wiki":
|
|
1350
|
-
return license.plan !== "free";
|
|
1351
|
-
case "unlimited_employees":
|
|
1352
|
-
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
// src/lib/plan-limits.ts
|
|
1357
|
-
var PlanLimitError = class extends Error {
|
|
1358
|
-
constructor(message) {
|
|
1359
|
-
super(message);
|
|
1360
|
-
this.name = "PlanLimitError";
|
|
1361
|
-
}
|
|
1362
|
-
};
|
|
1363
|
-
var CACHE_PATH2 = path4.join(EXE_AI_DIR, "license-cache.json");
|
|
1364
|
-
async function assertFeature(feature) {
|
|
1365
|
-
const license = await checkLicense();
|
|
1366
|
-
if (!isFeatureAllowed(license, feature)) {
|
|
1367
|
-
throw new PlanLimitError(
|
|
1368
|
-
`Feature "${feature}" requires a paid plan. Current plan: ${license.plan}. Upgrade at https://askexe.com.`
|
|
1369
|
-
);
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
1181
|
// src/lib/cloud-sync.ts
|
|
1374
1182
|
function logError(msg) {
|
|
1375
1183
|
try {
|
|
1376
|
-
const logPath =
|
|
1184
|
+
const logPath = path4.join(homedir(), ".exe-os", "workers.log");
|
|
1377
1185
|
appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
1378
1186
|
`);
|
|
1379
1187
|
} catch {
|
|
@@ -1382,7 +1190,7 @@ function logError(msg) {
|
|
|
1382
1190
|
var LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
1383
1191
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
1384
1192
|
var PUSH_BATCH_SIZE = 5e3;
|
|
1385
|
-
var ROSTER_LOCK_PATH =
|
|
1193
|
+
var ROSTER_LOCK_PATH = path4.join(EXE_AI_DIR, "roster-merge.lock");
|
|
1386
1194
|
var LOCK_STALE_MS = 3e4;
|
|
1387
1195
|
async function withRosterLock(fn) {
|
|
1388
1196
|
try {
|
|
@@ -1392,7 +1200,7 @@ async function withRosterLock(fn) {
|
|
|
1392
1200
|
} catch (err) {
|
|
1393
1201
|
if (err.code === "EEXIST") {
|
|
1394
1202
|
try {
|
|
1395
|
-
const ts = parseInt(
|
|
1203
|
+
const ts = parseInt(readFileSync4(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
1396
1204
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
1397
1205
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
1398
1206
|
}
|
|
@@ -1519,7 +1327,6 @@ async function cloudPull(sinceVersion, config) {
|
|
|
1519
1327
|
}
|
|
1520
1328
|
}
|
|
1521
1329
|
async function cloudSync(config) {
|
|
1522
|
-
await assertFeature("cloud_sync");
|
|
1523
1330
|
let client;
|
|
1524
1331
|
try {
|
|
1525
1332
|
client = getClient();
|
|
@@ -1697,12 +1504,12 @@ async function cloudSync(config) {
|
|
|
1697
1504
|
documents: documentsResult
|
|
1698
1505
|
};
|
|
1699
1506
|
}
|
|
1700
|
-
var ROSTER_DELETIONS_PATH =
|
|
1507
|
+
var ROSTER_DELETIONS_PATH = path4.join(EXE_AI_DIR, "roster-deletions.json");
|
|
1701
1508
|
function recordRosterDeletion(name) {
|
|
1702
1509
|
let deletions = [];
|
|
1703
1510
|
try {
|
|
1704
|
-
if (
|
|
1705
|
-
deletions = JSON.parse(
|
|
1511
|
+
if (existsSync4(ROSTER_DELETIONS_PATH)) {
|
|
1512
|
+
deletions = JSON.parse(readFileSync4(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
1706
1513
|
}
|
|
1707
1514
|
} catch {
|
|
1708
1515
|
}
|
|
@@ -1711,8 +1518,8 @@ function recordRosterDeletion(name) {
|
|
|
1711
1518
|
}
|
|
1712
1519
|
function consumeRosterDeletions() {
|
|
1713
1520
|
try {
|
|
1714
|
-
if (!
|
|
1715
|
-
const deletions = JSON.parse(
|
|
1521
|
+
if (!existsSync4(ROSTER_DELETIONS_PATH)) return [];
|
|
1522
|
+
const deletions = JSON.parse(readFileSync4(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
1716
1523
|
writeFileSync2(ROSTER_DELETIONS_PATH, "[]");
|
|
1717
1524
|
return deletions;
|
|
1718
1525
|
} catch {
|
|
@@ -1720,29 +1527,29 @@ function consumeRosterDeletions() {
|
|
|
1720
1527
|
}
|
|
1721
1528
|
}
|
|
1722
1529
|
function buildRosterBlob(paths) {
|
|
1723
|
-
const rosterPath = paths?.rosterPath ??
|
|
1724
|
-
const identityDir = paths?.identityDir ??
|
|
1725
|
-
const configPath = paths?.configPath ??
|
|
1530
|
+
const rosterPath = paths?.rosterPath ?? path4.join(EXE_AI_DIR, "exe-employees.json");
|
|
1531
|
+
const identityDir = paths?.identityDir ?? path4.join(EXE_AI_DIR, "identity");
|
|
1532
|
+
const configPath = paths?.configPath ?? path4.join(EXE_AI_DIR, "config.json");
|
|
1726
1533
|
let roster = [];
|
|
1727
|
-
if (
|
|
1534
|
+
if (existsSync4(rosterPath)) {
|
|
1728
1535
|
try {
|
|
1729
|
-
roster = JSON.parse(
|
|
1536
|
+
roster = JSON.parse(readFileSync4(rosterPath, "utf-8"));
|
|
1730
1537
|
} catch {
|
|
1731
1538
|
}
|
|
1732
1539
|
}
|
|
1733
1540
|
const identities = {};
|
|
1734
|
-
if (
|
|
1541
|
+
if (existsSync4(identityDir)) {
|
|
1735
1542
|
for (const file of readdirSync(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
1736
1543
|
try {
|
|
1737
|
-
identities[file] =
|
|
1544
|
+
identities[file] = readFileSync4(path4.join(identityDir, file), "utf-8");
|
|
1738
1545
|
} catch {
|
|
1739
1546
|
}
|
|
1740
1547
|
}
|
|
1741
1548
|
}
|
|
1742
1549
|
let config;
|
|
1743
|
-
if (
|
|
1550
|
+
if (existsSync4(configPath)) {
|
|
1744
1551
|
try {
|
|
1745
|
-
config = JSON.parse(
|
|
1552
|
+
config = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
1746
1553
|
} catch {
|
|
1747
1554
|
}
|
|
1748
1555
|
}
|
|
@@ -1818,23 +1625,23 @@ async function cloudPullRoster(config) {
|
|
|
1818
1625
|
}
|
|
1819
1626
|
}
|
|
1820
1627
|
function mergeConfig(remoteConfig, configPath) {
|
|
1821
|
-
const cfgPath = configPath ??
|
|
1628
|
+
const cfgPath = configPath ?? path4.join(EXE_AI_DIR, "config.json");
|
|
1822
1629
|
let local = {};
|
|
1823
|
-
if (
|
|
1630
|
+
if (existsSync4(cfgPath)) {
|
|
1824
1631
|
try {
|
|
1825
|
-
local = JSON.parse(
|
|
1632
|
+
local = JSON.parse(readFileSync4(cfgPath, "utf-8"));
|
|
1826
1633
|
} catch {
|
|
1827
1634
|
}
|
|
1828
1635
|
}
|
|
1829
1636
|
const merged = { ...remoteConfig, ...local };
|
|
1830
|
-
const dir =
|
|
1831
|
-
if (!
|
|
1637
|
+
const dir = path4.dirname(cfgPath);
|
|
1638
|
+
if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
|
|
1832
1639
|
writeFileSync2(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
1833
1640
|
}
|
|
1834
1641
|
async function mergeRosterFromRemote(remote, paths) {
|
|
1835
1642
|
return withRosterLock(async () => {
|
|
1836
1643
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
1837
|
-
const identityDir = paths?.identityDir ??
|
|
1644
|
+
const identityDir = paths?.identityDir ?? path4.join(EXE_AI_DIR, "identity");
|
|
1838
1645
|
const localEmployees = await loadEmployees(rosterPath);
|
|
1839
1646
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
1840
1647
|
let added = 0;
|
|
@@ -1844,9 +1651,9 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
1844
1651
|
localNames.add(remoteEmp.name);
|
|
1845
1652
|
added++;
|
|
1846
1653
|
if (remote.identities[`${remoteEmp.name}.md`]) {
|
|
1847
|
-
if (!
|
|
1848
|
-
const idPath =
|
|
1849
|
-
if (!
|
|
1654
|
+
if (!existsSync4(identityDir)) mkdirSync2(identityDir, { recursive: true });
|
|
1655
|
+
const idPath = path4.join(identityDir, `${remoteEmp.name}.md`);
|
|
1656
|
+
if (!existsSync4(idPath)) {
|
|
1850
1657
|
writeFileSync2(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
|
|
1851
1658
|
}
|
|
1852
1659
|
}
|
package/package.json
CHANGED