newmark-agent 0.4.4 → 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 +860 -688
- package/dist/core/agent.d.ts +19 -0
- package/dist/core/agent.js +72 -8
- 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/core/toolPolicy.js +28 -13
- package/dist/core/workspace.d.ts +6 -0
- package/dist/core/workspace.js +14 -0
- package/dist/main.js +49 -0
- package/dist/preload.js +5 -0
- package/dist/server.js +195 -3
- package/dist/tools/index.d.ts +14 -0
- package/dist/tools/index.js +143 -17
- 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 +860 -688
- package/package.json +15 -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/core/toolPolicy.js
CHANGED
|
@@ -181,21 +181,21 @@ function deletionVerbCount(text) {
|
|
|
181
181
|
const matches = text.match(new RegExp(DELETE_VERB_BOUNDARY.source, 'gi'));
|
|
182
182
|
return matches ? matches.length : 0;
|
|
183
183
|
}
|
|
184
|
-
/** 循环结构批量删除:foreach / for…in / for( / while( / bash do…done。 */
|
|
184
|
+
/** 循环结构批量删除:foreach / for…in / for( / CMD for…do del / while( / bash do…done。 */
|
|
185
185
|
function hasLoopDeletion(text) {
|
|
186
186
|
const lower = text.toLowerCase();
|
|
187
187
|
if (/\bforeach\b/.test(lower))
|
|
188
|
-
return true; // PowerShell foreach
|
|
188
|
+
return true; // PowerShell foreach / ForEach-Object
|
|
189
189
|
if (/\bfor\b\s*[$({]/.test(lower))
|
|
190
|
-
return true; // PowerShell/C for(...)
|
|
190
|
+
return true; // PowerShell/C/bash for(...)
|
|
191
191
|
if (/\bfor\b\s+\S+\s+in\b/.test(lower))
|
|
192
|
-
return true; // bash for f in ...
|
|
192
|
+
return true; // bash for f in ... / CMD for %f in (...)
|
|
193
|
+
if (/\bfor\b[^\n;&|]*\bdo\b[^\n;&|]*\b(?:del|rm|erase|remove-item|ri)\b/.test(lower))
|
|
194
|
+
return true; // CMD for ... do del
|
|
193
195
|
if (/\bwhile\b\s*[({]/.test(lower))
|
|
194
196
|
return true; // while(...)
|
|
195
197
|
if (/\bwhile\b\s+\S/.test(lower) && /\bdo\b/.test(lower))
|
|
196
198
|
return true; // bash while ... do
|
|
197
|
-
if (/\bdone\b/.test(lower))
|
|
198
|
-
return true; // bash 循环结束标记
|
|
199
199
|
return false;
|
|
200
200
|
}
|
|
201
201
|
/** find -delete / find -exec rm / xargs rm 批量删除。 */
|
|
@@ -206,6 +206,17 @@ function hasFindXargsDeletion(text) {
|
|
|
206
206
|
return true;
|
|
207
207
|
return false;
|
|
208
208
|
}
|
|
209
|
+
/** git clean(非 dry-run)删除未跟踪文件 = 批量删除;`-n` / `--dry-run` 只预览放行。 */
|
|
210
|
+
function hasGitCleanDeletion(text) {
|
|
211
|
+
const lower = text.toLowerCase();
|
|
212
|
+
if (!/\bgit\b\s+clean\b/.test(lower))
|
|
213
|
+
return false;
|
|
214
|
+
if (/(?:^|\s)-[a-z]*n[a-z]*(?:\s|$)/.test(lower))
|
|
215
|
+
return false;
|
|
216
|
+
if (/(?:^|\s)--dry-run(?:\s|$)/.test(lower))
|
|
217
|
+
return false;
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
209
220
|
/** 按 shell 语义切分参数:引号内的空格不拆分,返回去引号后的 token。 */
|
|
210
221
|
function splitCommandArgs(args) {
|
|
211
222
|
const tokens = [];
|
|
@@ -218,20 +229,20 @@ function splitCommandArgs(args) {
|
|
|
218
229
|
}
|
|
219
230
|
return tokens;
|
|
220
231
|
}
|
|
221
|
-
/**
|
|
232
|
+
/** 管道接收端删除:上游产出多项,删除动词作为接收端即批量删除。`||`(逻辑或)不是管道。 */
|
|
222
233
|
function hasPipeDeletion(text) {
|
|
223
|
-
return
|
|
234
|
+
return /(?<!\|)\|\s*(?:remove-item|rmdir|unlink|erase|del|rm|rd|ri)\b/i.test(text);
|
|
224
235
|
}
|
|
225
|
-
/** 递归删除标志:rm -r/-R/--recursive、Remove-Item -Recurse、rmdir/rd /s、del /s。 */
|
|
236
|
+
/** 递归删除标志:rm -r/-R/--recursive、Remove-Item/ri -Recurse、rmdir/rd /s、del/erase /s。 */
|
|
226
237
|
function hasRecursiveDeletionFlag(text) {
|
|
227
238
|
const lower = text.toLowerCase();
|
|
228
239
|
if (/\brm\b\s+(-[a-z]*r[a-z]*|--recursive)\b/.test(lower))
|
|
229
240
|
return true;
|
|
230
|
-
if (/\
|
|
241
|
+
if (/\b(?:remove-item|ri)\b[^\n;&|]*\s+-(?:recurse|r)\b/.test(lower))
|
|
231
242
|
return true;
|
|
232
243
|
if (/\b(?:rmdir|rd)\b\s+(-r\b|\/[s]\b)/.test(lower))
|
|
233
244
|
return true;
|
|
234
|
-
if (/\
|
|
245
|
+
if (/\b(?:del|erase)\b\s+\/[s]\b/.test(lower))
|
|
235
246
|
return true;
|
|
236
247
|
return false;
|
|
237
248
|
}
|
|
@@ -271,9 +282,11 @@ function evaluateDeletionGuard(command) {
|
|
|
271
282
|
const text = String(command || '');
|
|
272
283
|
if (!text.trim())
|
|
273
284
|
return { blocked: false };
|
|
274
|
-
// find -delete / find -exec rm 中,-delete
|
|
285
|
+
// find -delete / find -exec rm 中,-delete 不含标准删除动词;git clean 也不含删除动词,
|
|
286
|
+
// 均需在入口单独识别为批量删除意图。
|
|
275
287
|
const findXargs = hasFindXargsDeletion(text);
|
|
276
|
-
|
|
288
|
+
const gitClean = hasGitCleanDeletion(text);
|
|
289
|
+
if (!hasDeletionVerb(text) && !findXargs && !gitClean)
|
|
277
290
|
return { blocked: false };
|
|
278
291
|
const refuse = (kind) => ({
|
|
279
292
|
blocked: true,
|
|
@@ -283,6 +296,8 @@ function evaluateDeletionGuard(command) {
|
|
|
283
296
|
return refuse('Loop-based');
|
|
284
297
|
if (findXargs)
|
|
285
298
|
return refuse('find/xargs');
|
|
299
|
+
if (gitClean)
|
|
300
|
+
return refuse('git-clean');
|
|
286
301
|
if (hasPipeDeletion(text))
|
|
287
302
|
return refuse('Pipe-fed');
|
|
288
303
|
if (hasRecursiveDeletionFlag(text))
|
package/dist/core/workspace.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ export interface WorkspaceManagerOptions {
|
|
|
21
21
|
/** Runtime workers receive an explicit workspace target and must not rewrite the shared registry. */
|
|
22
22
|
detached?: boolean;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* 把 Windows 盘符路径(`C:\...` / `C:/...`)转换为 WSL/Linux 挂载路径
|
|
26
|
+
* (`/mnt/c/...`)。非 Windows 盘符路径返回空字符串,由调用方决定回退。
|
|
27
|
+
* 纯函数、无 I/O:WSL 运行时文件工具与 bash 都依赖它做跨环境归一。
|
|
28
|
+
*/
|
|
29
|
+
export declare function windowsDrivePathToPosix(input: string): string;
|
|
24
30
|
/** Normalize persisted Windows/WSL aliases and recover paths damaged by cross-host path.resolve calls. */
|
|
25
31
|
export declare function normalizeHostWorkspacePath(input: string, platform?: NodeJS.Platform): string;
|
|
26
32
|
/**
|
package/dist/core/workspace.js
CHANGED
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.WorkspaceManager = void 0;
|
|
37
|
+
exports.windowsDrivePathToPosix = windowsDrivePathToPosix;
|
|
37
38
|
exports.normalizeHostWorkspacePath = normalizeHostWorkspacePath;
|
|
38
39
|
exports.isProtectedInstallWorkspacePath = isProtectedInstallWorkspacePath;
|
|
39
40
|
const fs = __importStar(require("fs"));
|
|
@@ -46,6 +47,19 @@ function lastEmbeddedWindowsPath(input) {
|
|
|
46
47
|
lastIndex = match.index;
|
|
47
48
|
return lastIndex >= 0 ? input.slice(lastIndex) : '';
|
|
48
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* 把 Windows 盘符路径(`C:\...` / `C:/...`)转换为 WSL/Linux 挂载路径
|
|
52
|
+
* (`/mnt/c/...`)。非 Windows 盘符路径返回空字符串,由调用方决定回退。
|
|
53
|
+
* 纯函数、无 I/O:WSL 运行时文件工具与 bash 都依赖它做跨环境归一。
|
|
54
|
+
*/
|
|
55
|
+
function windowsDrivePathToPosix(input) {
|
|
56
|
+
const raw = String(input || '').trim();
|
|
57
|
+
const drive = /^([A-Za-z]):[\\/](.*)$/.exec(raw);
|
|
58
|
+
if (!drive)
|
|
59
|
+
return '';
|
|
60
|
+
const rest = drive[2].replace(/\\/g, '/').replace(/^\/+/, '');
|
|
61
|
+
return `/mnt/${drive[1].toLowerCase()}/${rest}`;
|
|
62
|
+
}
|
|
49
63
|
/** Normalize persisted Windows/WSL aliases and recover paths damaged by cross-host path.resolve calls. */
|
|
50
64
|
function normalizeHostWorkspacePath(input, platform = process.platform) {
|
|
51
65
|
const raw = String(input || '').trim();
|