newmark-agent 0.4.5 → 0.4.6
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/cli-commands.d.ts +1 -1
- package/dist/cli-commands.js +89 -7
- package/dist/cli-discovery.js +8 -0
- package/dist/conversation-utility-host.bundle.cjs +3 -0
- package/dist/core/config.js +3 -0
- package/dist/core/dshCompatibility.d.ts +23 -6
- package/dist/core/dshCompatibility.js +99 -1
- package/dist/core/installUpdate.d.ts +67 -0
- package/dist/core/installUpdate.js +265 -0
- package/dist/core/mobilePairing.d.ts +46 -0
- package/dist/core/mobilePairing.js +207 -0
- package/dist/main.js +49 -0
- package/dist/preload.js +5 -0
- package/dist/server.js +195 -3
- package/dist/tui/src/adapters/core-runtime-adapter.js +17 -1
- package/dist/tui/src/app.js +41 -0
- package/dist/tui/src/data.js +1 -0
- package/dist/tui/src/render.js +11 -0
- package/dist/tui/src/settings-schema.js +3 -1
- package/dist/tui/src/state.js +21 -1
- package/dist/ui/index.html +455 -13
- package/dist/ui/lucide-sprite.svg +10 -0
- package/dist/wsl-agent-host.bundle.cjs +3 -0
- package/package.json +12 -8
|
@@ -39,6 +39,15 @@ exports.normalizeReleaseVersion = normalizeReleaseVersion;
|
|
|
39
39
|
exports.compareSemver = compareSemver;
|
|
40
40
|
exports.checkGitHubUpdate = checkGitHubUpdate;
|
|
41
41
|
exports.applyGitHubUpdate = applyGitHubUpdate;
|
|
42
|
+
exports.listRunningNewmarkProcesses = listRunningNewmarkProcesses;
|
|
43
|
+
exports.stopNewmarkProcesses = stopNewmarkProcesses;
|
|
44
|
+
exports.listInstalledNewmarkProducts = listInstalledNewmarkProducts;
|
|
45
|
+
exports.uninstallNewmarkProduct = uninstallNewmarkProduct;
|
|
46
|
+
exports.installMsiPackage = installMsiPackage;
|
|
47
|
+
exports.findLegacyNewmarkExecutables = findLegacyNewmarkExecutables;
|
|
48
|
+
exports.removeLegacyNewmarkExecutables = removeLegacyNewmarkExecutables;
|
|
49
|
+
exports.planManagedMsiInstall = planManagedMsiInstall;
|
|
50
|
+
exports.executeManagedMsiInstall = executeManagedMsiInstall;
|
|
42
51
|
const fs = __importStar(require("fs"));
|
|
43
52
|
const path = __importStar(require("path"));
|
|
44
53
|
const os = __importStar(require("os"));
|
|
@@ -518,4 +527,260 @@ async function applyGitHubUpdate(options) {
|
|
|
518
527
|
};
|
|
519
528
|
}
|
|
520
529
|
}
|
|
530
|
+
const NEWMARK_PROCESS_NAMES = ['Newmark Agent.exe', 'Newmark.exe'];
|
|
531
|
+
const NEWMARK_LEGACY_EXECUTABLES = new Set(['newmark.exe', 'newmark agent.exe']);
|
|
532
|
+
function normalizeWindowsPathForCompare(value) {
|
|
533
|
+
return path.resolve(String(value)).toLowerCase();
|
|
534
|
+
}
|
|
535
|
+
function isWithinOrEqual(candidate, parent) {
|
|
536
|
+
const child = normalizeWindowsPathForCompare(candidate);
|
|
537
|
+
const rootPath = normalizeWindowsPathForCompare(parent);
|
|
538
|
+
return child === rootPath || child.startsWith(rootPath.endsWith(path.sep) ? rootPath : rootPath + path.sep);
|
|
539
|
+
}
|
|
540
|
+
function runPowerShellJson(script) {
|
|
541
|
+
const args = ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script];
|
|
542
|
+
const stdout = (0, child_process_1.execFileSync)('powershell.exe', args, { encoding: 'utf-8', windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
543
|
+
const trimmed = String(stdout || '').replace(/^\uFEFF/, '').trim();
|
|
544
|
+
if (!trimmed)
|
|
545
|
+
return [];
|
|
546
|
+
const parsed = JSON.parse(trimmed);
|
|
547
|
+
return Array.isArray(parsed) ? parsed.map(item => item) : [parsed];
|
|
548
|
+
}
|
|
549
|
+
function listRunningNewmarkProcesses() {
|
|
550
|
+
const rows = runPowerShellJson(`Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq 'Newmark Agent.exe' -or $_.Name -eq 'Newmark.exe' } | Select-Object ProcessId,Name,ExecutablePath | ConvertTo-Json -Compress`);
|
|
551
|
+
const skipPid = Number(process.env.NEWMARK_SKIP_PROCESS_PID || process.pid);
|
|
552
|
+
return rows
|
|
553
|
+
.map(row => ({
|
|
554
|
+
pid: Number(row.ProcessId || 0),
|
|
555
|
+
name: String(row.Name || ''),
|
|
556
|
+
executablePath: String(row.ExecutablePath || ''),
|
|
557
|
+
}))
|
|
558
|
+
.filter(proc => proc.pid > 0 && proc.pid !== skipPid);
|
|
559
|
+
}
|
|
560
|
+
function stopNewmarkProcesses(pids) {
|
|
561
|
+
const ids = Array.from(new Set((pids || []).map(Number).filter(pid => Number.isFinite(pid) && pid > 0)));
|
|
562
|
+
if (!ids.length)
|
|
563
|
+
return { stopped: [], errors: [] };
|
|
564
|
+
const script = `$ids = @(${ids.join(',')}); foreach ($id in $ids) { try { Stop-Process -Id $id -Force -ErrorAction Stop; Write-Output ('stopped:' + $id) } catch { Write-Output ('error:' + $id + ':' + $_.Exception.Message) } }`;
|
|
565
|
+
const args = ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script];
|
|
566
|
+
const stdout = String((0, child_process_1.execFileSync)('powershell.exe', args, { encoding: 'utf-8', windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] }) || '');
|
|
567
|
+
const stopped = [];
|
|
568
|
+
const errors = [];
|
|
569
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
570
|
+
const trimmed = line.trim();
|
|
571
|
+
if (!trimmed)
|
|
572
|
+
continue;
|
|
573
|
+
const stoppedMatch = trimmed.match(/^stopped:(\d+)$/);
|
|
574
|
+
const errorMatch = trimmed.match(/^error:(\d+):(.*)$/);
|
|
575
|
+
if (stoppedMatch)
|
|
576
|
+
stopped.push(Number(stoppedMatch[1]));
|
|
577
|
+
else if (errorMatch)
|
|
578
|
+
errors.push(`PID ${errorMatch[1]}: ${errorMatch[2]}`);
|
|
579
|
+
}
|
|
580
|
+
return { stopped, errors };
|
|
581
|
+
}
|
|
582
|
+
function listInstalledNewmarkProducts() {
|
|
583
|
+
const rows = runPowerShellJson(`$paths = @('HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*','HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'); Get-ItemProperty $paths -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like '*Newmark Agent*' } | Select-Object PSChildName,DisplayName,InstallLocation,UninstallString,QuietUninstallString | ConvertTo-Json -Compress`);
|
|
584
|
+
return rows
|
|
585
|
+
.map(row => ({
|
|
586
|
+
productCode: String(row.PSChildName || ''),
|
|
587
|
+
displayName: String(row.DisplayName || ''),
|
|
588
|
+
installLocation: String(row.InstallLocation || ''),
|
|
589
|
+
uninstallString: String(row.UninstallString || row.QuietUninstallString || ''),
|
|
590
|
+
}))
|
|
591
|
+
.filter(product => product.productCode);
|
|
592
|
+
}
|
|
593
|
+
function runMsiExec(args) {
|
|
594
|
+
const result = (0, child_process_1.spawnSync)('msiexec.exe', args, { encoding: 'utf-8', windowsHide: true });
|
|
595
|
+
return {
|
|
596
|
+
exitCode: result.status === null ? -1 : result.status,
|
|
597
|
+
stdout: String(result.stdout || ''),
|
|
598
|
+
stderr: String(result.stderr || ''),
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
function windowsCommandQuote(value) {
|
|
602
|
+
const text = String(value);
|
|
603
|
+
return /\s/.test(text) ? `"${text.replace(/"/g, '\\"')}"` : text;
|
|
604
|
+
}
|
|
605
|
+
function runElevatedMsiExec(args) {
|
|
606
|
+
const argumentString = args.map(arg => windowsCommandQuote(arg)).join(' ');
|
|
607
|
+
const script = [
|
|
608
|
+
`$argumentList = '${argumentString.replace(/'/g, "''")}'`,
|
|
609
|
+
'$process = Start-Process -FilePath "msiexec.exe" -ArgumentList $argumentList -Verb RunAs -Wait -PassThru',
|
|
610
|
+
'Write-Output ("EXITCODE:" + $process.ExitCode)',
|
|
611
|
+
].join('; ');
|
|
612
|
+
const result = (0, child_process_1.spawnSync)('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script], { encoding: 'utf-8', windowsHide: true });
|
|
613
|
+
const stdout = String(result.stdout || '');
|
|
614
|
+
const match = stdout.match(/EXITCODE:(\d+)/);
|
|
615
|
+
return {
|
|
616
|
+
exitCode: match ? Number(match[1]) : (result.status === null ? -1 : result.status),
|
|
617
|
+
stdout,
|
|
618
|
+
stderr: String(result.stderr || ''),
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
function uninstallNewmarkProduct(productCode, logPath) {
|
|
622
|
+
const args = ['/x', productCode, '/qn', '/norestart', '/l*v', logPath];
|
|
623
|
+
let result = runMsiExec(args);
|
|
624
|
+
if (result.exitCode !== 0)
|
|
625
|
+
result = runElevatedMsiExec(args);
|
|
626
|
+
return {
|
|
627
|
+
ok: result.exitCode === 0,
|
|
628
|
+
exitCode: result.exitCode,
|
|
629
|
+
logPath,
|
|
630
|
+
error: result.exitCode === 0 ? undefined : `msiexec uninstall exited ${result.exitCode}`,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
function installMsiPackage(msiPath, options = {}) {
|
|
634
|
+
const logPath = path.join(options.logDir || os.tmpdir(), `newmark-msi-install-${process.pid}-${Date.now()}.log`);
|
|
635
|
+
const args = ['/i', path.resolve(msiPath), '/qn', '/norestart', '/l*v', logPath];
|
|
636
|
+
let result = runMsiExec(args);
|
|
637
|
+
if (result.exitCode !== 0 && options.allowElevate !== false)
|
|
638
|
+
result = runElevatedMsiExec(args);
|
|
639
|
+
return {
|
|
640
|
+
ok: result.exitCode === 0,
|
|
641
|
+
exitCode: result.exitCode,
|
|
642
|
+
logPath,
|
|
643
|
+
error: result.exitCode === 0 ? undefined : `msiexec install exited ${result.exitCode}`,
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
function findLegacyNewmarkExecutables(excludeRoots = []) {
|
|
647
|
+
const found = new Map();
|
|
648
|
+
for (const name of NEWMARK_PROCESS_NAMES) {
|
|
649
|
+
try {
|
|
650
|
+
const stdout = String((0, child_process_1.execFileSync)('where.exe', [name], { encoding: 'utf-8', windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] }) || '');
|
|
651
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
652
|
+
const candidate = String(line || '').trim();
|
|
653
|
+
if (!candidate)
|
|
654
|
+
continue;
|
|
655
|
+
const key = normalizeWindowsPathForCompare(candidate);
|
|
656
|
+
if (NEWMARK_LEGACY_EXECUTABLES.has(path.basename(candidate).toLowerCase()) && !found.has(key))
|
|
657
|
+
found.set(key, candidate);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
catch {
|
|
661
|
+
// where.exe returns non-zero when an executable is not found on PATH.
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const exclude = (excludeRoots || []).filter(Boolean).map(root => normalizeWindowsPathForCompare(root));
|
|
665
|
+
return Array.from(found.values())
|
|
666
|
+
.filter(candidate => !exclude.some(root => isWithinOrEqual(candidate, root)))
|
|
667
|
+
.sort();
|
|
668
|
+
}
|
|
669
|
+
function removeLegacyNewmarkExecutables(paths) {
|
|
670
|
+
const removed = [];
|
|
671
|
+
const errors = [];
|
|
672
|
+
for (const candidate of Array.from(new Set(paths || []))) {
|
|
673
|
+
const full = path.resolve(candidate);
|
|
674
|
+
if (!NEWMARK_LEGACY_EXECUTABLES.has(path.basename(full).toLowerCase())) {
|
|
675
|
+
errors.push(`refusing non-Newmark executable: ${full}`);
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
try {
|
|
679
|
+
fs.unlinkSync(full);
|
|
680
|
+
removed.push(full);
|
|
681
|
+
}
|
|
682
|
+
catch (e) {
|
|
683
|
+
errors.push(`${full}: ${e instanceof Error ? e.message : String(e)}`);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return { removed, errors };
|
|
687
|
+
}
|
|
688
|
+
function planManagedMsiInstall(msiPath, options = {}) {
|
|
689
|
+
const fullPath = path.resolve(msiPath);
|
|
690
|
+
try {
|
|
691
|
+
if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile() || path.extname(fullPath).toLowerCase() !== '.msi') {
|
|
692
|
+
return {
|
|
693
|
+
ok: false,
|
|
694
|
+
msiPath: fullPath,
|
|
695
|
+
runningProcesses: [],
|
|
696
|
+
installedProducts: [],
|
|
697
|
+
legacyExecutables: [],
|
|
698
|
+
needsStopConfirmation: false,
|
|
699
|
+
needsLegacyRemovalConfirmation: false,
|
|
700
|
+
error: `MSI package does not exist or is not a .msi file: ${fullPath}`,
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
const runningProcesses = listRunningNewmarkProcesses();
|
|
704
|
+
const installedProducts = listInstalledNewmarkProducts();
|
|
705
|
+
const excludeRoots = [
|
|
706
|
+
...(options.excludeRoots || []),
|
|
707
|
+
process.cwd(),
|
|
708
|
+
path.dirname(process.execPath || ''),
|
|
709
|
+
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Newmark Agent'),
|
|
710
|
+
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Newmark Agent'),
|
|
711
|
+
...installedProducts.map(product => product.installLocation),
|
|
712
|
+
];
|
|
713
|
+
const legacyExecutables = findLegacyNewmarkExecutables(excludeRoots);
|
|
714
|
+
return {
|
|
715
|
+
ok: true,
|
|
716
|
+
msiPath: fullPath,
|
|
717
|
+
runningProcesses,
|
|
718
|
+
installedProducts,
|
|
719
|
+
legacyExecutables,
|
|
720
|
+
needsStopConfirmation: runningProcesses.length > 0 && options.stopConfirmed !== true,
|
|
721
|
+
needsLegacyRemovalConfirmation: legacyExecutables.length > 0 && options.removeLegacyConfirmed !== true,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
catch (e) {
|
|
725
|
+
return {
|
|
726
|
+
ok: false,
|
|
727
|
+
msiPath: fullPath,
|
|
728
|
+
runningProcesses: [],
|
|
729
|
+
installedProducts: [],
|
|
730
|
+
legacyExecutables: [],
|
|
731
|
+
needsStopConfirmation: false,
|
|
732
|
+
needsLegacyRemovalConfirmation: false,
|
|
733
|
+
error: e instanceof Error ? e.message : String(e),
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
function executeManagedMsiInstall(msiPath, options = {}) {
|
|
738
|
+
const plan = planManagedMsiInstall(msiPath, options);
|
|
739
|
+
if (!plan.ok) {
|
|
740
|
+
return { ok: false, plan, stopped: [], uninstalled: [], removedLegacy: [], error: plan.error };
|
|
741
|
+
}
|
|
742
|
+
if (plan.needsStopConfirmation) {
|
|
743
|
+
return { ok: false, plan, stopped: [], uninstalled: [], removedLegacy: [], error: 'Running Newmark processes require confirmation before they can be stopped.' };
|
|
744
|
+
}
|
|
745
|
+
if (plan.needsLegacyRemovalConfirmation) {
|
|
746
|
+
return { ok: false, plan, stopped: [], uninstalled: [], removedLegacy: [], error: 'Legacy Newmark executables require confirmation before they can be removed.' };
|
|
747
|
+
}
|
|
748
|
+
const stopResult = plan.runningProcesses.length
|
|
749
|
+
? stopNewmarkProcesses(plan.runningProcesses.map(process => process.pid))
|
|
750
|
+
: { stopped: [], errors: [] };
|
|
751
|
+
const uninstalled = [];
|
|
752
|
+
if (options.uninstallPrevious !== false) {
|
|
753
|
+
for (const product of plan.installedProducts) {
|
|
754
|
+
const logPath = path.join(options.logDir || os.tmpdir(), `newmark-msi-uninstall-${product.productCode}-${process.pid}-${Date.now()}.log`);
|
|
755
|
+
const uninstallResult = uninstallNewmarkProduct(product.productCode, logPath);
|
|
756
|
+
if (!uninstallResult.ok) {
|
|
757
|
+
return {
|
|
758
|
+
ok: false,
|
|
759
|
+
plan,
|
|
760
|
+
stopped: stopResult.stopped,
|
|
761
|
+
uninstalled,
|
|
762
|
+
removedLegacy: [],
|
|
763
|
+
exitCode: uninstallResult.exitCode,
|
|
764
|
+
logPath: uninstallResult.logPath,
|
|
765
|
+
error: `Failed to uninstall previous Newmark version ${product.productCode}: ${uninstallResult.error}`,
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
uninstalled.push(product.productCode);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
const removeResult = plan.legacyExecutables.length
|
|
772
|
+
? removeLegacyNewmarkExecutables(plan.legacyExecutables)
|
|
773
|
+
: { removed: [], errors: [] };
|
|
774
|
+
const installResult = installMsiPackage(plan.msiPath, { logDir: options.logDir, allowElevate: options.allowElevate });
|
|
775
|
+
return {
|
|
776
|
+
ok: installResult.ok,
|
|
777
|
+
plan,
|
|
778
|
+
stopped: stopResult.stopped,
|
|
779
|
+
uninstalled,
|
|
780
|
+
removedLegacy: removeResult.removed,
|
|
781
|
+
exitCode: installResult.exitCode,
|
|
782
|
+
logPath: installResult.logPath,
|
|
783
|
+
error: installResult.error,
|
|
784
|
+
};
|
|
785
|
+
}
|
|
521
786
|
//# sourceMappingURL=installUpdate.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export declare const MOBILE_TOKEN_FILENAME = ".newmark-mobile-token";
|
|
2
|
+
export declare const MOBILE_PAIRING_FILENAME = ".newmark-mobile-pairing.json";
|
|
3
|
+
export declare const MOBILE_PORT = 47890;
|
|
4
|
+
export declare const MOBILE_PAIRING_TTL_MS = 120000;
|
|
5
|
+
export interface PairingSession {
|
|
6
|
+
pairingId: string;
|
|
7
|
+
token: string;
|
|
8
|
+
host: string;
|
|
9
|
+
hostname: string;
|
|
10
|
+
port: number;
|
|
11
|
+
issuedAt: number;
|
|
12
|
+
expiresAt: number;
|
|
13
|
+
url: string;
|
|
14
|
+
confirmed?: boolean;
|
|
15
|
+
confirmedAt?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface PairingStatus {
|
|
18
|
+
pairingId: string;
|
|
19
|
+
issuedAt: number;
|
|
20
|
+
expiresAt: number;
|
|
21
|
+
confirmed: boolean;
|
|
22
|
+
confirmedAt: number;
|
|
23
|
+
active: boolean;
|
|
24
|
+
expired: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare function ensureMobileToken(root: string): string;
|
|
27
|
+
export declare function tailscaleIpv4(): string | null;
|
|
28
|
+
export declare function pairingHost(): string;
|
|
29
|
+
export declare function createPairingSession(root: string, ttlMs?: number): PairingSession;
|
|
30
|
+
export declare function pairingUrl(root: string): string;
|
|
31
|
+
export declare function pairingTokenPath(root: string): string;
|
|
32
|
+
export declare function pairingStatus(root: string): PairingStatus;
|
|
33
|
+
export declare function confirmPairing(root: string, pairingId: string, token: string): {
|
|
34
|
+
ok: boolean;
|
|
35
|
+
error?: string;
|
|
36
|
+
status: PairingStatus;
|
|
37
|
+
};
|
|
38
|
+
export declare function pairingQrDataUrl(root: string, ttlMs?: number): Promise<{
|
|
39
|
+
dataUrl: string;
|
|
40
|
+
session: PairingSession;
|
|
41
|
+
}>;
|
|
42
|
+
export declare function pairingQrAscii(root: string, ttlMs?: number): Promise<{
|
|
43
|
+
ascii: string;
|
|
44
|
+
session: PairingSession;
|
|
45
|
+
}>;
|
|
46
|
+
//# sourceMappingURL=mobilePairing.d.ts.map
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.MOBILE_PAIRING_TTL_MS = exports.MOBILE_PORT = exports.MOBILE_PAIRING_FILENAME = exports.MOBILE_TOKEN_FILENAME = void 0;
|
|
37
|
+
exports.ensureMobileToken = ensureMobileToken;
|
|
38
|
+
exports.tailscaleIpv4 = tailscaleIpv4;
|
|
39
|
+
exports.pairingHost = pairingHost;
|
|
40
|
+
exports.createPairingSession = createPairingSession;
|
|
41
|
+
exports.pairingUrl = pairingUrl;
|
|
42
|
+
exports.pairingTokenPath = pairingTokenPath;
|
|
43
|
+
exports.pairingStatus = pairingStatus;
|
|
44
|
+
exports.confirmPairing = confirmPairing;
|
|
45
|
+
exports.pairingQrDataUrl = pairingQrDataUrl;
|
|
46
|
+
exports.pairingQrAscii = pairingQrAscii;
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const os = __importStar(require("os"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const crypto_1 = require("crypto");
|
|
51
|
+
const child_process_1 = require("child_process");
|
|
52
|
+
const QRCode = __importStar(require("qrcode"));
|
|
53
|
+
exports.MOBILE_TOKEN_FILENAME = '.newmark-mobile-token';
|
|
54
|
+
exports.MOBILE_PAIRING_FILENAME = '.newmark-mobile-pairing.json';
|
|
55
|
+
exports.MOBILE_PORT = 47890;
|
|
56
|
+
exports.MOBILE_PAIRING_TTL_MS = 120_000;
|
|
57
|
+
function pairingStatePath(root) {
|
|
58
|
+
return path.join(root, exports.MOBILE_PAIRING_FILENAME);
|
|
59
|
+
}
|
|
60
|
+
function readPairingState(root) {
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse(fs.readFileSync(pairingStatePath(root), 'utf-8'));
|
|
63
|
+
if (!parsed || !parsed.pairingId || !parsed.token)
|
|
64
|
+
return null;
|
|
65
|
+
return parsed;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function writePairingState(root, state) {
|
|
72
|
+
fs.mkdirSync(root, { recursive: true });
|
|
73
|
+
fs.writeFileSync(pairingStatePath(root), JSON.stringify(state, null, 2), 'utf-8');
|
|
74
|
+
}
|
|
75
|
+
function ensureMobileToken(root) {
|
|
76
|
+
const tokenPath = path.join(root, exports.MOBILE_TOKEN_FILENAME);
|
|
77
|
+
try {
|
|
78
|
+
const existing = fs.readFileSync(tokenPath, 'utf-8').replace(/\s+/g, '').trim();
|
|
79
|
+
if (existing.length >= 32)
|
|
80
|
+
return existing;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// first start: no token file yet
|
|
84
|
+
}
|
|
85
|
+
const generated = (0, crypto_1.randomBytes)(24).toString('hex');
|
|
86
|
+
try {
|
|
87
|
+
fs.mkdirSync(root, { recursive: true });
|
|
88
|
+
fs.writeFileSync(tokenPath, generated, { encoding: 'utf-8', mode: 0o600 });
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// keep the generated token for this process even if persistence fails
|
|
92
|
+
}
|
|
93
|
+
return generated;
|
|
94
|
+
}
|
|
95
|
+
function tailscaleIpv4() {
|
|
96
|
+
const exe = process.platform === 'win32' ? 'tailscale.exe' : 'tailscale';
|
|
97
|
+
try {
|
|
98
|
+
const result = (0, child_process_1.spawnSync)(exe, ['ip', '-4'], { encoding: 'utf-8', windowsHide: true, timeout: 3000 });
|
|
99
|
+
if (result.error || result.status !== 0)
|
|
100
|
+
return null;
|
|
101
|
+
const lines = String(result.stdout || '').split(/\r?\n/).map(line => line.trim()).filter(Boolean);
|
|
102
|
+
return lines[0] || null;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function pairingHost() {
|
|
109
|
+
return tailscaleIpv4() || '127.0.0.1';
|
|
110
|
+
}
|
|
111
|
+
function buildPairingUrl(session) {
|
|
112
|
+
const query = new URLSearchParams({
|
|
113
|
+
token: session.token,
|
|
114
|
+
host: session.hostname,
|
|
115
|
+
port: String(session.port),
|
|
116
|
+
pairingId: session.pairingId,
|
|
117
|
+
issuedAt: String(session.issuedAt),
|
|
118
|
+
expiresAt: String(session.expiresAt),
|
|
119
|
+
});
|
|
120
|
+
return `newmark-pair://${session.host}:${session.port}?${query.toString()}`;
|
|
121
|
+
}
|
|
122
|
+
function createPairingSession(root, ttlMs = exports.MOBILE_PAIRING_TTL_MS) {
|
|
123
|
+
const token = ensureMobileToken(root);
|
|
124
|
+
const host = pairingHost();
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
const session = {
|
|
127
|
+
pairingId: (0, crypto_1.randomBytes)(12).toString('hex'),
|
|
128
|
+
token,
|
|
129
|
+
host,
|
|
130
|
+
hostname: os.hostname(),
|
|
131
|
+
port: exports.MOBILE_PORT,
|
|
132
|
+
issuedAt: now,
|
|
133
|
+
expiresAt: now + Math.max(1000, Number(ttlMs) || exports.MOBILE_PAIRING_TTL_MS),
|
|
134
|
+
url: '',
|
|
135
|
+
confirmed: false,
|
|
136
|
+
confirmedAt: 0,
|
|
137
|
+
};
|
|
138
|
+
session.url = buildPairingUrl(session);
|
|
139
|
+
writePairingState(root, session);
|
|
140
|
+
return session;
|
|
141
|
+
}
|
|
142
|
+
function pairingUrl(root) {
|
|
143
|
+
return createPairingSession(root).url;
|
|
144
|
+
}
|
|
145
|
+
function pairingTokenPath(root) {
|
|
146
|
+
return path.join(root, exports.MOBILE_TOKEN_FILENAME);
|
|
147
|
+
}
|
|
148
|
+
function pairingStatus(root) {
|
|
149
|
+
const state = readPairingState(root);
|
|
150
|
+
if (!state) {
|
|
151
|
+
return { pairingId: '', issuedAt: 0, expiresAt: 0, confirmed: false, confirmedAt: 0, active: false, expired: true };
|
|
152
|
+
}
|
|
153
|
+
const now = Date.now();
|
|
154
|
+
const confirmed = state.confirmed === true;
|
|
155
|
+
const expired = now >= Number(state.expiresAt || 0);
|
|
156
|
+
return {
|
|
157
|
+
pairingId: state.pairingId,
|
|
158
|
+
issuedAt: Number(state.issuedAt || 0),
|
|
159
|
+
expiresAt: Number(state.expiresAt || 0),
|
|
160
|
+
confirmed,
|
|
161
|
+
confirmedAt: Number(state.confirmedAt || 0),
|
|
162
|
+
active: !confirmed && !expired,
|
|
163
|
+
expired: !confirmed && expired,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function confirmPairing(root, pairingId, token) {
|
|
167
|
+
const state = readPairingState(root);
|
|
168
|
+
const status = pairingStatus(root);
|
|
169
|
+
if (!state || !state.pairingId) {
|
|
170
|
+
return { ok: false, error: 'No pairing window is active.', status };
|
|
171
|
+
}
|
|
172
|
+
if (state.token !== token) {
|
|
173
|
+
return { ok: false, error: 'Pairing token does not match.', status };
|
|
174
|
+
}
|
|
175
|
+
if (state.pairingId !== pairingId) {
|
|
176
|
+
return { ok: false, error: 'Pairing window is stale.', status };
|
|
177
|
+
}
|
|
178
|
+
if (status.expired) {
|
|
179
|
+
return { ok: false, error: 'Pairing window expired.', status };
|
|
180
|
+
}
|
|
181
|
+
if (status.confirmed) {
|
|
182
|
+
return { ok: true, status };
|
|
183
|
+
}
|
|
184
|
+
const updated = { ...state, confirmed: true, confirmedAt: Date.now() };
|
|
185
|
+
writePairingState(root, updated);
|
|
186
|
+
return { ok: true, status: pairingStatus(root) };
|
|
187
|
+
}
|
|
188
|
+
async function pairingQrDataUrl(root, ttlMs) {
|
|
189
|
+
const session = createPairingSession(root, ttlMs);
|
|
190
|
+
const dataUrl = await QRCode.toDataURL(session.url, {
|
|
191
|
+
width: 420,
|
|
192
|
+
margin: 1,
|
|
193
|
+
errorCorrectionLevel: 'M',
|
|
194
|
+
color: { dark: '#101828', light: '#FFFFFF' },
|
|
195
|
+
});
|
|
196
|
+
return { dataUrl, session };
|
|
197
|
+
}
|
|
198
|
+
async function pairingQrAscii(root, ttlMs) {
|
|
199
|
+
const session = createPairingSession(root, ttlMs);
|
|
200
|
+
const ascii = await QRCode.toString(session.url, {
|
|
201
|
+
type: 'terminal',
|
|
202
|
+
small: true,
|
|
203
|
+
errorCorrectionLevel: 'M',
|
|
204
|
+
});
|
|
205
|
+
return { ascii, session };
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=mobilePairing.js.map
|
package/dist/main.js
CHANGED
|
@@ -55,6 +55,7 @@ const cli_discovery_1 = require("./cli-discovery");
|
|
|
55
55
|
const config_1 = require("./core/config");
|
|
56
56
|
const memoryLab_1 = require("./core/memoryLab");
|
|
57
57
|
const installUpdate_1 = require("./core/installUpdate");
|
|
58
|
+
const mobilePairing_1 = require("./core/mobilePairing");
|
|
58
59
|
const terminalTakeover_1 = require("./tools/terminalTakeover");
|
|
59
60
|
const nativeBash_1 = require("./core/nativeBash");
|
|
60
61
|
const nativeTools_1 = require("./tools/nativeTools");
|
|
@@ -3472,6 +3473,7 @@ else {
|
|
|
3472
3473
|
},
|
|
3473
3474
|
configuredAgentBackend: agent.config.getBool('agent', 'run_in_wsl') ? 'wsl' : 'windows',
|
|
3474
3475
|
agentBackendRestartRequired: (agent.config.getBool('agent', 'run_in_wsl') ? 'wsl' : 'windows') !== activeAgentBackendMode,
|
|
3476
|
+
remoteTouchEnabled: agent.config.getBool('remote', 'touch_enabled'),
|
|
3475
3477
|
wslAvailable: wslDistros.length > 0,
|
|
3476
3478
|
wslDistros,
|
|
3477
3479
|
};
|
|
@@ -4784,6 +4786,15 @@ else {
|
|
|
4784
4786
|
return { servers: mcpManager.list(), discovered };
|
|
4785
4787
|
});
|
|
4786
4788
|
electron_1.ipcMain.handle('dsh:discover', async () => (0, dshCompatibility_1.discoverDshCompatibility)(root));
|
|
4789
|
+
electron_1.ipcMain.handle('dsh:installBundle', async (_event, manifestPath) => {
|
|
4790
|
+
return (0, dshCompatibility_1.installDshBundle)(root, String(manifestPath || ''));
|
|
4791
|
+
});
|
|
4792
|
+
electron_1.ipcMain.handle('dsh:uninstallBundle', async (_event, name) => {
|
|
4793
|
+
return (0, dshCompatibility_1.uninstallDshBundle)(root, String(name || ''));
|
|
4794
|
+
});
|
|
4795
|
+
electron_1.ipcMain.handle('dsh:setBundleEnabled', async (_event, name, enabled) => {
|
|
4796
|
+
return (0, dshCompatibility_1.setDshBundleEnabled)(root, String(name || ''), enabled === true);
|
|
4797
|
+
});
|
|
4787
4798
|
electron_1.ipcMain.handle('mcp:upsert', async (_event, input) => {
|
|
4788
4799
|
if (!mcpManager)
|
|
4789
4800
|
return { ok: false, error: 'MCP manager is unavailable.' };
|
|
@@ -4855,6 +4866,21 @@ else {
|
|
|
4855
4866
|
electron_1.ipcMain.handle('update:version', async () => {
|
|
4856
4867
|
return { ok: true, version: (0, installUpdate_1.currentAppVersion)(), root };
|
|
4857
4868
|
});
|
|
4869
|
+
electron_1.ipcMain.handle('mobile:pairingQr', async () => {
|
|
4870
|
+
const qr = await (0, mobilePairing_1.pairingQrDataUrl)(root);
|
|
4871
|
+
return {
|
|
4872
|
+
ok: true,
|
|
4873
|
+
url: qr.session.url,
|
|
4874
|
+
dataUrl: qr.dataUrl,
|
|
4875
|
+
pairingId: qr.session.pairingId,
|
|
4876
|
+
expiresAt: qr.session.expiresAt,
|
|
4877
|
+
tokenFile: (0, mobilePairing_1.pairingTokenPath)(root),
|
|
4878
|
+
tailscaleIpv4: (0, mobilePairing_1.tailscaleIpv4)(),
|
|
4879
|
+
};
|
|
4880
|
+
});
|
|
4881
|
+
electron_1.ipcMain.handle('mobile:pairingStatus', async () => {
|
|
4882
|
+
return { ok: true, status: (0, mobilePairing_1.pairingStatus)(root) };
|
|
4883
|
+
});
|
|
4858
4884
|
electron_1.ipcMain.handle('update:checkGithub', async (_event, input = {}) => {
|
|
4859
4885
|
return (0, installUpdate_1.checkGitHubUpdate)(String(input.repo || ''), String(input.tag || ''), String(input.asset || ''));
|
|
4860
4886
|
});
|
|
@@ -4884,6 +4910,29 @@ else {
|
|
|
4884
4910
|
setTimeout(() => electron_1.app.quit(), 150);
|
|
4885
4911
|
return result;
|
|
4886
4912
|
});
|
|
4913
|
+
electron_1.ipcMain.handle('update:planMsi', async (_event, input = {}) => {
|
|
4914
|
+
return (0, installUpdate_1.planManagedMsiInstall)(String(input.msiPath || ''), {
|
|
4915
|
+
stopConfirmed: input.stopConfirmed === true,
|
|
4916
|
+
removeLegacyConfirmed: input.removeLegacyConfirmed === true,
|
|
4917
|
+
uninstallPrevious: input.uninstallPrevious !== false,
|
|
4918
|
+
allowElevate: input.allowElevate !== false,
|
|
4919
|
+
excludeRoots: Array.isArray(input.excludeRoots) ? input.excludeRoots.map(String) : undefined,
|
|
4920
|
+
logDir: typeof input.logDir === 'string' ? input.logDir : undefined,
|
|
4921
|
+
});
|
|
4922
|
+
});
|
|
4923
|
+
electron_1.ipcMain.handle('update:executeMsi', async (_event, input = {}) => {
|
|
4924
|
+
const result = (0, installUpdate_1.executeManagedMsiInstall)(String(input.msiPath || ''), {
|
|
4925
|
+
stopConfirmed: input.stopConfirmed === true,
|
|
4926
|
+
removeLegacyConfirmed: input.removeLegacyConfirmed === true,
|
|
4927
|
+
uninstallPrevious: input.uninstallPrevious !== false,
|
|
4928
|
+
allowElevate: input.allowElevate !== false,
|
|
4929
|
+
excludeRoots: Array.isArray(input.excludeRoots) ? input.excludeRoots.map(String) : undefined,
|
|
4930
|
+
logDir: typeof input.logDir === 'string' ? input.logDir : undefined,
|
|
4931
|
+
});
|
|
4932
|
+
if (result.ok)
|
|
4933
|
+
setTimeout(() => electron_1.app.quit(), 150);
|
|
4934
|
+
return result;
|
|
4935
|
+
});
|
|
4887
4936
|
electron_1.ipcMain.handle('github:gh', async (_event, argv = []) => {
|
|
4888
4937
|
const safeArgs = Array.isArray(argv) ? argv.map(String).filter(a => a.length < 400) : [];
|
|
4889
4938
|
try {
|
package/dist/preload.js
CHANGED
|
@@ -124,6 +124,9 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
124
124
|
refreshSkills: () => ipcRenderer.invoke('skills:refresh'),
|
|
125
125
|
listMcpServers: () => ipcRenderer.invoke('mcp:list'),
|
|
126
126
|
discoverDshCompatibility: () => ipcRenderer.invoke('dsh:discover'),
|
|
127
|
+
installDshBundle: (manifestPath) => ipcRenderer.invoke('dsh:installBundle', manifestPath),
|
|
128
|
+
uninstallDshBundle: (name) => ipcRenderer.invoke('dsh:uninstallBundle', name),
|
|
129
|
+
setDshBundleEnabled: (name, enabled) => ipcRenderer.invoke('dsh:setBundleEnabled', name, enabled),
|
|
127
130
|
upsertMcpServer: (input) => ipcRenderer.invoke('mcp:upsert', input),
|
|
128
131
|
setMcpServerEnabled: (id, enabled) => ipcRenderer.invoke('mcp:setEnabled', id, enabled),
|
|
129
132
|
removeMcpServer: (id) => ipcRenderer.invoke('mcp:remove', id),
|
|
@@ -134,6 +137,8 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
134
137
|
memoryLabUpdate: (input) => ipcRenderer.invoke('memoryLab:update', input),
|
|
135
138
|
memoryLabReindex: () => ipcRenderer.invoke('memoryLab:reindex'),
|
|
136
139
|
updateVersion: () => ipcRenderer.invoke('update:version'),
|
|
140
|
+
mobilePairingQr: () => ipcRenderer.invoke('mobile:pairingQr'),
|
|
141
|
+
mobilePairingStatus: () => ipcRenderer.invoke('mobile:pairingStatus'),
|
|
137
142
|
updateCheckGithub: (input) => ipcRenderer.invoke('update:checkGithub', input),
|
|
138
143
|
updateApplyGithub: (input) => ipcRenderer.invoke('update:applyGithub', input),
|
|
139
144
|
updateInstallLocal: (input) => ipcRenderer.invoke('update:installLocal', input),
|