Publish matching Windows CLI MCP package with document helper

This commit is contained in:
karl committed 2026-10-13 15:58:26 +02:00
1 parent a8249160ec
commit f1028e2689
4 files changed
+154 -7

No files matched your search

+69
View File
@@ -0,0 +1,69 @@
"""Package the optional Windows CLI/MCP client; reuse the installed desktop models."""
import argparse
import hashlib
import json
import os
from pathlib import Path
import tempfile
import zipfile
from package_windows import pe_machine
from stage_extraction_worker import verify
ROOT = Path(__file__).resolve().parents[1]
MAX_FILE = 64 * 1024 * 1024
def package(root, executable, resources, output):
root, executable, resources, output = map(Path, (root, executable, resources, output))
def read(path):
if path.is_symlink() or not path.is_file() or path.absolute() != path.resolve():
raise ValueError('Package input missing or linked')
with path.open('rb') as stream:
data = stream.read(MAX_FILE + 1)
if not data or len(data) > MAX_FILE:
raise ValueError('Package input empty or oversized')
return data
cli = read(executable)
architecture = {0x8664: 'x86_64', 0xAA64: 'aarch64'}.get(pe_machine(executable))
if architecture is None:
raise ValueError('Unsupported Windows CLI architecture')
expected = verify(resources, root, 'windows', architecture)
selected = {'searchi-cli.exe': cli, 'LICENSE.txt': read(root / 'LICENSE')}
for name, record in expected.items():
data = read(resources / name)
if len(data) != record['bytes'] or hashlib.sha256(data).hexdigest() != record['sha256']:
raise ValueError('Extraction resource changed while packaging')
selected[name] = data
# The captured CLI must still match the header used for architecture validation.
if read(executable) != cli:
raise ValueError('CLI changed while packaging')
records = [{'path': name, 'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()}
for name, data in selected.items()]
output.parent.mkdir(parents=True, exist_ok=True)
with tempfile.NamedTemporaryFile(dir=output.parent, suffix='.partial', delete=False) as temporary:
temporary_path = Path(temporary.name)
try:
with zipfile.ZipFile(temporary_path, 'w', zipfile.ZIP_DEFLATED) as archive:
for name, data in selected.items():
archive.writestr(name, data)
archive.writestr('PACKAGE.json', json.dumps({'platform': 'windows', 'architecture': architecture,
'status': 'optional unsigned CLI/MCP client; install desktop for model setup', 'files': records}, indent=2))
os.replace(temporary_path, output)
finally:
temporary_path.unlink(missing_ok=True)
return architecture
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--executable', type=Path, required=True)
parser.add_argument('--resources', type=Path, default=ROOT / 'apps/desktop/src-tauri/resources')
parser.add_argument('--output', type=Path, default=ROOT / '.searchi/packages/searchi-cli_0.1.0_x64-013.zip')
args = parser.parse_args()
print(f"Packaged Windows {package(ROOT, args.executable, args.resources, args.output)} CLI: {args.output}")
if __name__ == '__main__':
main()
+64
View File
@@ -0,0 +1,64 @@
import hashlib
import json
from pathlib import Path
import tempfile
import unittest
import zipfile
from package_windows_cli import package
from test_package_windows import executable
import stage_extraction_worker as extraction
class CliPackageTests(unittest.TestCase):
def fixture(self, directory):
root = Path(directory)
(root / 'Cargo.toml').write_text('workspace')
(root / 'Cargo.lock').write_text('lock')
for crate in ('searchi-core', 'searchi-media', 'searchi-extract-worker'):
base = root / 'crates' / crate
(base / 'src').mkdir(parents=True)
(base / 'Cargo.toml').write_text(crate)
(base / 'src' / ('main.rs' if crate == 'searchi-extract-worker' else 'lib.rs')).write_text('// fixture')
(root / 'LICENSE').write_text('license')
cli = root / 'cli.exe'
helper = root / 'helper.exe'
executable(cli)
executable(helper)
resources = root / 'resources'
extraction.stage(helper, extraction.fingerprint(root), resources, root)
return root, cli, resources, root / 'client.zip'
def test_integrity_and_minimal_contents(self):
with tempfile.TemporaryDirectory() as directory:
root, cli, resources, output = self.fixture(directory)
self.assertEqual(package(root, cli, resources, output), 'x86_64')
with zipfile.ZipFile(output) as archive:
self.assertEqual(set(archive.namelist()), {'searchi-cli.exe', 'searchi-extract-worker.exe',
'extraction-worker.json', 'LICENSE.txt', 'PACKAGE.json'})
manifest = json.loads(archive.read('PACKAGE.json'))
for record in manifest['files']:
data = archive.read(record['path'])
self.assertEqual(len(data), record['bytes'])
self.assertEqual(hashlib.sha256(data).hexdigest(), record['sha256'])
def test_rejected_helper_preserves_output(self):
for mutation in ('missing', 'tampered', 'architecture'):
with self.subTest(mutation=mutation), tempfile.TemporaryDirectory() as directory:
root, cli, resources, output = self.fixture(directory)
output.write_bytes(b'previous')
helper = resources / 'searchi-extract-worker.exe'
if mutation == 'missing':
helper.unlink()
elif mutation == 'tampered':
helper.write_bytes(helper.read_bytes() + b'changed')
else:
executable(cli, 0xAA64)
with self.assertRaises(ValueError):
package(root, cli, resources, output)
self.assertEqual(output.read_bytes(), b'previous')
self.assertEqual(list(root.glob('*.partial')), [])
if __name__ == '__main__':
unittest.main()