yichael 3 месяцев назад
Родитель
Сommit
f66486872e
100 измененных файлов с 2385 добавлено и 827 удалено
  1. 12 2
      configs/config.js
  2. 1 0
      configs/get-python-env-path.js
  3. 3 3
      doc/工作流语法.md
  4. 23 28
      enviroment-check.ps1
  5. 12 47
      nodejs/adb/adb-screencap.js
  6. 9 1
      nodejs/ef-compiler/Func/chat/chat-history.js
  7. 1 1
      nodejs/ef-compiler/Func/chat/ocr-chat.js
  8. 1 1
      nodejs/ef-compiler/Func/chat/read-last-message.js
  9. 0 160
      nodejs/ef-compiler/Func/image-area-cropping.js
  10. 0 81
      nodejs/ef-compiler/Func/image-center-location.js
  11. 0 98
      nodejs/ef-compiler/Func/image-region-location.js
  12. 63 0
      nodejs/ef-compiler/Func/img-bounding-box-location.js
  13. 64 0
      nodejs/ef-compiler/Func/img-center-point-location.js
  14. 94 0
      nodejs/ef-compiler/Func/img-cropping.js
  15. 20 76
      nodejs/ef-compiler/Func/read-txt.js
  16. 24 74
      nodejs/ef-compiler/Func/save-txt.js
  17. 59 22
      nodejs/ef-compiler/ef-compiler.js
  18. 0 180
      nodejs/ef-compiler/node-api.js
  19. 0 1
      python/arm64/environment.txt
  20. 702 0
      python/arm64/py/LICENSE.txt
  21. BIN
      python/arm64/py/_asyncio.pyd
  22. BIN
      python/arm64/py/_bz2.pyd
  23. BIN
      python/arm64/py/_ctypes.pyd
  24. BIN
      python/arm64/py/_decimal.pyd
  25. BIN
      python/arm64/py/_elementtree.pyd
  26. BIN
      python/arm64/py/_hashlib.pyd
  27. BIN
      python/arm64/py/_lzma.pyd
  28. BIN
      python/arm64/py/_msi.pyd
  29. BIN
      python/arm64/py/_multiprocessing.pyd
  30. BIN
      python/arm64/py/_overlapped.pyd
  31. BIN
      python/arm64/py/_queue.pyd
  32. BIN
      python/arm64/py/_socket.pyd
  33. BIN
      python/arm64/py/_sqlite3.pyd
  34. BIN
      python/arm64/py/_ssl.pyd
  35. BIN
      python/arm64/py/_uuid.pyd
  36. BIN
      python/arm64/py/_wmi.pyd
  37. BIN
      python/arm64/py/_zoneinfo.pyd
  38. BIN
      python/arm64/py/libcrypto-3-arm64.dll
  39. BIN
      python/arm64/py/libffi-8.dll
  40. BIN
      python/arm64/py/libssl-3-arm64.dll
  41. BIN
      python/arm64/py/pyexpat.pyd
  42. BIN
      python/arm64/py/python.cat
  43. BIN
      python/arm64/py/python.exe
  44. BIN
      python/arm64/py/python3.dll
  45. 5 0
      python/arm64/py/python312._pth
  46. BIN
      python/arm64/py/python312.dll
  47. BIN
      python/arm64/py/python312.zip
  48. BIN
      python/arm64/py/pythonw.exe
  49. BIN
      python/arm64/py/select.pyd
  50. BIN
      python/arm64/py/sqlite3.dll
  51. BIN
      python/arm64/py/unicodedata.pyd
  52. BIN
      python/arm64/py/vcruntime140.dll
  53. BIN
      python/arm64/py/vcruntime140_1.dll
  54. BIN
      python/arm64/py/winsound.pyd
  55. 4 3
      python/arm64/python-enviroment-install.py
  56. 33 0
      python/arm64/update-enviroment-list.bat
  57. 6 2
      python/arm64/update-environment-list.py
  58. 34 0
      python/scripts/img-crop.py
  59. 0 16
      python/scripts/test.py
  60. 0 30
      python/update-enviroment-list.bat
  61. 17 0
      python/x64/environment.txt
  62. 702 0
      python/x64/py/LICENSE.txt
  63. BIN
      python/x64/py/_asyncio.pyd
  64. BIN
      python/x64/py/_bz2.pyd
  65. BIN
      python/x64/py/_ctypes.pyd
  66. BIN
      python/x64/py/_decimal.pyd
  67. BIN
      python/x64/py/_elementtree.pyd
  68. BIN
      python/x64/py/_hashlib.pyd
  69. BIN
      python/x64/py/_lzma.pyd
  70. BIN
      python/x64/py/_msi.pyd
  71. BIN
      python/x64/py/_multiprocessing.pyd
  72. BIN
      python/x64/py/_overlapped.pyd
  73. BIN
      python/x64/py/_queue.pyd
  74. BIN
      python/x64/py/_socket.pyd
  75. BIN
      python/x64/py/_sqlite3.pyd
  76. BIN
      python/x64/py/_ssl.pyd
  77. BIN
      python/x64/py/_uuid.pyd
  78. BIN
      python/x64/py/_wmi.pyd
  79. BIN
      python/x64/py/_zoneinfo.pyd
  80. BIN
      python/x64/py/libcrypto-3.dll
  81. BIN
      python/x64/py/libffi-8.dll
  82. BIN
      python/x64/py/libssl-3.dll
  83. BIN
      python/x64/py/pyexpat.pyd
  84. BIN
      python/x64/py/python.cat
  85. BIN
      python/x64/py/python.exe
  86. BIN
      python/x64/py/python3.dll
  87. 5 0
      python/x64/py/python312._pth
  88. BIN
      python/x64/py/python312.dll
  89. BIN
      python/x64/py/python312.zip
  90. BIN
      python/x64/py/pythonw.exe
  91. BIN
      python/x64/py/select.pyd
  92. BIN
      python/x64/py/sqlite3.dll
  93. BIN
      python/x64/py/unicodedata.pyd
  94. BIN
      python/x64/py/vcruntime140.dll
  95. BIN
      python/x64/py/vcruntime140_1.dll
  96. BIN
      python/x64/py/winsound.pyd
  97. 279 0
      python/x64/python-enviroment-install.py
  98. 33 0
      python/x64/update-enviroment-list.bat
  99. 178 0
      python/x64/update-environment-list.py
  100. 1 1
      static/process/RedNoteAIThumbsUp/process.json

+ 12 - 2
configs/config.js

@@ -1,4 +1,12 @@
 // Electron 应用配置
+const path = require('path')
+
+const projectRoot = path.resolve(__dirname, '..')
+const isArm64 = process.arch === 'arm64'
+// 按本机架构选择环境目录:python/arm64 或 python/x64(内含 py/python.exe 与 environment.txt)
+const pythonDir = isArm64 ? 'arm64' : 'x64'
+const pythonEnvPath = path.join(projectRoot, 'python', pythonDir)
+
 module.exports = {
   // 窗口配置
   window: {
@@ -18,11 +26,13 @@ module.exports = {
     host: 'localhost' // 服务器主机地址
   },
 
-  // Python 路径配置
+  // Python 环境路径(按系统架构:python/arm64 或 python/x64,内含 py/python.exe)
   pythonPath: {
-    path: 'D:\\Program\\Python312' // Python 安装路径
+    path: path.join(projectRoot, 'python', pythonDir)
   },
 
+  pythonEnvPath,
+
   // ADB 路径配置(相对于项目根目录)
   adbPath: {
     path: 'lib/scrcpy-adb/adb.exe' // ADB 可执行文件路径(相对路径)

+ 1 - 0
configs/get-python-env-path.js

@@ -0,0 +1 @@
+console.log(require('./config.js').pythonEnvPath)

+ 3 - 3
doc/工作流语法.md

@@ -221,14 +221,14 @@
 - `save-txt`: 保存字符串为文本文件
   - `inVars`: `[内容, 文件路径]`(路径相对于工作流目录)
   - `outVars`: `[]`
-- `image-center-location`: 图像中心点定位
+- `img-center-point-location`: 图像中心点定位
   - `inVars`: `[模板图片路径]`(相对于工作流目录的 resources 文件夹)
   - `outVars`: `[位置坐标变量]`(输出JSON字符串格式:`{"x":123,"y":456}`)
-- `image-region-location`: 图像区域定位(在完整截图中查找区域图片的位置)
+- `img-bounding-box-location`: 图像区域定位(在完整截图中查找区域图片的位置)
   - `inVars`: `[截图路径, 区域图片路径]`(都相对于工作流目录的 resources 文件夹,如 `"ScreenShot.jpg"` 和 `"ChatArea.png"`)
   - `outVars`: `[区域坐标变量]`(返回四个顶点坐标:`{topLeft: {x, y}, topRight: {x, y}, bottomLeft: {x, y}, bottomRight: {x, y}}`)
   - 注意:此标签**不主动截图**,使用 `resources/` 文件夹下已有的截图文件进行匹配
-- `image-area-cropping`: 图像区域裁剪(从当前屏幕截图裁剪指定区域)
+- `img-cropping`: 图像区域裁剪(从当前屏幕截图裁剪指定区域)
   - `inVars`: `[区域坐标, 保存路径]`(区域坐标:JSON字符串格式,保存路径相对于工作流目录的 history 文件夹)
   - `outVars`: `[]`
   - 功能:从主进程缓存获取最新截图,保存到 `history/ScreenShot.jpg`,然后根据区域坐标裁剪并保存到指定路径

+ 23 - 28
enviroment-check.ps1

@@ -96,38 +96,33 @@ if ($pipVersion) {
 }
 
 
-#check python virtual environment
-Write-Host "`nChecking python virtual environment..." -ForegroundColor Yellow
-$venvPath = "./python/env"
-if (Test-Path $venvPath) {
-    Write-Host "[OK] python virtual environment exists at: $venvPath" -ForegroundColor Green
-} else {
-    Write-Host "[X] python virtual environment is not installed" -ForegroundColor Yellow
-    Write-Host "Creating python virtual environment..." -ForegroundColor Yellow
-    python -m venv $venvPath
-    if ($LASTEXITCODE -ne 0) {
-        Write-Host "[X] python virtual environment creation failed" -ForegroundColor Red
-        Write-Host "[WARN] Continuing without virtual environment..." -ForegroundColor Yellow
-    } else {
-        Write-Host "[OK] python virtual environment created successfully" -ForegroundColor Green
+# check python 环境目录(python/arm64 或 python/x64)
+Write-Host "`nChecking python environment..." -ForegroundColor Yellow
+$projectRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
+$_p = node (Join-Path $projectRoot "configs\get-python-env-path.js") 2>$null
+$arch = if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { "arm64" } else { "x64" }
+$envBase = if ($_p) { $_p.Trim() } else { Join-Path $projectRoot "python\$arch" }
+$venvPath = Join-Path $envBase "env-$arch"
+if (-not (Test-Path (Join-Path $venvPath "Scripts\python.exe"))) {
+    if (-not (Test-Path (Join-Path $envBase "py\python.exe"))) {
+        Write-Host "Creating venv at $venvPath ..." -ForegroundColor Yellow
+        python -m venv $venvPath
     }
 }
+if (Test-Path (Join-Path $venvPath "Scripts\python.exe")) { Write-Host "[OK] python venv at: $venvPath" -ForegroundColor Green }
+elseif (Test-Path (Join-Path $envBase "py\python.exe")) { Write-Host "[OK] python at: $envBase\py" -ForegroundColor Green }
+else { Write-Host "[X] No python under $envBase" -ForegroundColor Yellow }
 
-# check python dependencies
+# 安装/检查依赖
 Write-Host "`nChecking python dependencies..." -ForegroundColor Yellow
-
-# 调用 python-enviroment-install.py 脚本进行依赖检查和安装(安装到虚拟环境)
-$pythonDependenciesScript = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) "python\python-enviroment-install.py"
-
-if (Test-Path $pythonDependenciesScript) {
-    python $pythonDependenciesScript
-    if ($LASTEXITCODE -ne 0) {
-        Write-Host "[X] Python dependencies check/installation failed" -ForegroundColor Red
-        exit 1
-    }
-} else {
-    Write-Host "[X] python-enviroment-install.py not found at: $pythonDependenciesScript" -ForegroundColor Red
-    Write-Host "[WARN] Continuing without Python dependency check..." -ForegroundColor Yellow
+$installScript = Join-Path $envBase "python-enviroment-install.py"
+$envTxt = Join-Path $envBase "environment.txt"
+if (Test-Path $installScript) {
+    $env:PYTHON_VENV_PATH = $venvPath; python $installScript
+    if ($LASTEXITCODE -ne 0) { Write-Host "[X] Python dependencies failed" -ForegroundColor Red; exit 1 }
+} elseif (Test-Path $envTxt) {
+    $pip = Join-Path $venvPath "Scripts\pip.exe"
+    if (Test-Path $pip) { & $pip install -r $envTxt }
 }
 
 Write-Host "`n================================" -ForegroundColor Cyan

+ 12 - 47
nodejs/adb/adb-screencap.js

@@ -1,59 +1,24 @@
 #!/usr/bin/env node
 /**
- * ADB 截图:将设备屏幕捕获到本地文件
- * 用法: node adb-screencap.js <deviceId> <outputPath>
- * 示例: node adb-screencap.js 192.168.2.5:5555 C:\temp\screen.png
+ * ADB 截图:根据 IP 截屏并保存到指定路径
+ * 用法: node adb-screencap.js <ip> <outputPath>
  */
 
 const { spawnSync } = require('child_process')
 const path = require('path')
 const fs = require('fs')
 
-const config = require(path.join(__dirname, '..', '..', 'configs', 'config.js'))
-const projectRoot = path.resolve(__dirname, '..', '..')
-const adbPath = config.adbPath?.path
-  ? path.resolve(projectRoot, config.adbPath.path)
-  : path.join(projectRoot, 'lib', 'scrcpy-adb', 'adb.exe')
+const adb = path.resolve(__dirname, '..', '..', 'lib', 'scrcpy-adb', 'adb.exe')
 
-const deviceId = process.argv[2]
-const outputPath = process.argv[3]
-
-if (!deviceId || !outputPath) {
-  process.stderr.write('Usage: node adb-screencap.js <deviceId> <outputPath>\n')
-  process.exit(1)
-}
-
-const deviceFlag = deviceId && deviceId.includes(':') ? ['-s', deviceId] : []
-const args = [...deviceFlag, 'exec-out', 'screencap', '-p']
-
-const result = spawnSync(adbPath, args, {
-  encoding: null,
-  timeout: 10000,
-  maxBuffer: 16 * 1024 * 1024
-})
-
-if (result.error) {
-  process.stderr.write(`screencap error: ${result.error.message}\n`)
-  process.exit(1)
+/** 截图并保存到 outputPath */
+function captureScreenshot(ip, outputPath) {
+  const r = spawnSync(adb, ['-s', ip, 'exec-out', 'screencap', '-p'], { encoding: null, timeout: 10000, maxBuffer: 16 * 1024 * 1024 })
+  fs.writeFileSync(outputPath, r.stdout)
+  return { success: true }
 }
 
-if (result.status !== 0) {
-  process.stderr.write((result.stderr || result.stdout || Buffer.from('')).toString('utf8'))
-  process.exit(1)
-}
-
-let data = result.stdout
-if (!data || data.length === 0) {
-  process.stderr.write('screencap returned empty data\n')
-  process.exit(1)
-}
-
-// 注意:不要对 PNG 做 \r\n 替换,会破坏 IDAT 压缩块导致 OpenCV/PIL 无法解析
-
-const dir = path.dirname(outputPath)
-if (!fs.existsSync(dir)) {
-  fs.mkdirSync(dir, { recursive: true })
+if (require.main === module) {
+  captureScreenshot(process.argv[2], process.argv[3])
+  process.stdout.write(process.argv[3])
 }
-fs.writeFileSync(outputPath, data)
-process.stdout.write(outputPath)
-process.exit(0)
+module.exports = { captureScreenshot }

+ 9 - 1
nodejs/ef-compiler/Func/chat/chat-history.js

@@ -3,7 +3,15 @@
  * 负责保存聊天记录、生成AI总结、读取历史总结、读取所有聊天记录
  */
 
-const electronAPI = require('../../node-api.js')
+const stub = (name) => ({ success: false, error: `${name} 需在主进程实现` })
+const electronAPI = {
+  saveChatHistory: () => stub('saveChatHistory'),
+  readChatHistory: () => stub('readChatHistory'),
+  readAllChatHistory: () => stub('readAllChatHistory'),
+  saveChatHistoryTxt: () => stub('saveChatHistoryTxt'),
+  saveChatHistorySummary: () => stub('saveChatHistorySummary'),
+  getChatHistorySummary: () => stub('getChatHistorySummary'),
+}
 
 /**
  * 保存聊天记录到 history 文件夹

+ 1 - 1
nodejs/ef-compiler/Func/chat/ocr-chat.js

@@ -7,7 +7,7 @@
  * 运行时真实执行逻辑由 ActionParser + electronAPI.extractChatHistory + main-js/func/ocr-chat.js 实现。
  */
 
-const electronAPI = require('../../node-api.js')
+const electronAPI = { extractChatHistory: () => ({ success: false, error: 'extractChatHistory 需在主进程实现' }) }
 
 const tagName = 'ocr-chat'
 

+ 1 - 1
nodejs/ef-compiler/Func/chat/read-last-message.js

@@ -5,7 +5,7 @@
  * 2. 从 history 文件夹读取(如果没有提供 inputData)- 从最新的聊天记录文件中读取
  */
 
-const electronAPI = require('../../node-api.js')
+const electronAPI = { readLastMessage: () => ({ success: false, error: 'readLastMessage 需在主进程实现' }) }
 
 const tagName = 'read-last-message'
 

+ 0 - 160
nodejs/ef-compiler/Func/image-area-cropping.js

@@ -1,160 +0,0 @@
-/**
- * Func 标签:image-area-cropping
- *
- * 约定:src/pages/processing/func/ 目录下每个文件名就是一个"可用标签/能力"。
- * 本文件用于声明该标签存在(供文档/提示词/后续动态加载使用)。
- *
- * 语义:根据区域坐标裁剪当前截图(ScreenShot.jpg)的指定区域,并保存到指定路径。
- */
-
-const electronAPI = require('../node-api.js')
-
-const tagName = 'image-area-cropping'
-
-const schema = {
-  description: '根据区域坐标裁剪当前截图(ScreenShot.jpg)的指定区域,并保存到指定路径。',
-  inputs: {
-    area: '区域坐标(JSON字符串格式,包含 topLeft 和 bottomRight,或包含 x, y, width, height)',
-    savePath: '保存路径(相对于工作流目录或绝对路径)',
-  },
-  outputs: {
-    result: '保存结果(成功返回 "1",失败返回 "0")',
-  },
-};
-
-/**
- * 执行 image-area-cropping 功能
- * 这个函数会被 ActionParser 调用
- * 
- * @param {Object} params - 参数对象
- * @param {string} params.area - 区域坐标(JSON字符串或对象,格式:{topLeft: {x, y}, bottomRight: {x, y}} 或 {x, y, width, height})
- * @param {string} params.savePath - 保存路径
- * @param {string} params.folderPath - 工作流文件夹路径
- * @param {string} params.device - 设备 ID/IP:Port(可选,用于获取最新截图)
- * @returns {Promise<{success: boolean, error?: string}>}
- */
-async function executeImageAreaCropping({ area, savePath, folderPath, device }) {
-  try {
-    // 解析区域坐标
-    let areaObj = area;
-    if (typeof area === 'string') {
-      try {
-        areaObj = JSON.parse(area);
-      } catch (e) {
-        return { 
-          success: false, 
-          error: `区域坐标格式错误,无法解析JSON: ${e.message}` 
-        };
-      }
-    }
-
-    if (!areaObj || typeof areaObj !== 'object') {
-      return { 
-        success: false, 
-        error: '区域坐标必须是对象格式' 
-      };
-    }
-
-    // 提取坐标信息(支持多种格式)
-    let x, y, width, height;
-    
-    if (areaObj.topLeft && areaObj.bottomRight) {
-      // 格式1:{topLeft: {x, y}, bottomRight: {x, y}}
-      x = parseInt(areaObj.topLeft.x);
-      y = parseInt(areaObj.topLeft.y);
-      width = parseInt(areaObj.bottomRight.x - areaObj.topLeft.x);
-      height = parseInt(areaObj.bottomRight.y - areaObj.topLeft.y);
-    } else if (areaObj.topLeft && areaObj.topRight && areaObj.bottomLeft && areaObj.bottomRight) {
-      // 格式1.5:{topLeft, topRight, bottomLeft, bottomRight} - 使用 topLeft 和 bottomRight
-      x = parseInt(areaObj.topLeft.x);
-      y = parseInt(areaObj.topLeft.y);
-      width = parseInt(areaObj.bottomRight.x - areaObj.topLeft.x);
-      height = parseInt(areaObj.bottomRight.y - areaObj.topLeft.y);
-    } else if (areaObj.x !== undefined && areaObj.y !== undefined && areaObj.width !== undefined && areaObj.height !== undefined) {
-      // 格式2:{x, y, width, height}
-      x = parseInt(areaObj.x);
-      y = parseInt(areaObj.y);
-      width = parseInt(areaObj.width);
-      height = parseInt(areaObj.height);
-    } else {
-      return { 
-        success: false, 
-        error: '区域坐标格式不正确,需要包含 topLeft/bottomRight 或 x/y/width/height' 
-      };
-    }
-
-    // 验证坐标有效性
-    if (isNaN(x) || isNaN(y) || isNaN(width) || isNaN(height) || width <= 0 || height <= 0) {
-      return { 
-        success: false, 
-        error: `区域坐标无效: x=${x}, y=${y}, width=${width}, height=${height}` 
-      };
-    }
-
-    // 先通过 ADB 截图当前手机屏幕并保存到 history 文件夹
-    // 参考 screenshot.js 的实现方式
-    let imageBase64 = null; // 声明 imageBase64 变量
-    let screenshotPath;
-    const fileExtension = 'jpg'
-    
-    if (folderPath.includes(':')) {
-      // 绝对路径
-      screenshotPath = `${folderPath}/history/ScreenShot.${fileExtension}`;
-    } else {
-      // 相对路径,构建相对于项目根目录的路径
-      screenshotPath = `${folderPath}/history/ScreenShot.${fileExtension}`;
-    }
-    
-    if (device && electronAPI.getCachedScreenshot) {
-      const screenshotResult = await electronAPI.getCachedScreenshot(device)
-      if (screenshotResult && screenshotResult.success && screenshotResult.data) {
-        imageBase64 = screenshotResult.data
-      }
-    }
-    if (!imageBase64 && electronAPI.captureScreenshot) {
-      const screenshotResult = await electronAPI.captureScreenshot(device)
-      if (screenshotResult && screenshotResult.success && screenshotResult.data) {
-        imageBase64 = screenshotResult.data
-      }
-    }
-    if (imageBase64 && electronAPI.saveBase64Image) {
-      await electronAPI.saveBase64Image(imageBase64, screenshotPath)
-    }
-
-    // 处理保存路径(如果是相对路径,相对于工作流目录)
-    let absoluteSavePath = savePath;
-    if (!savePath.includes(':')) {
-      // 相对路径,相对于工作流目录
-      if (folderPath.includes(':')) {
-        absoluteSavePath = `${folderPath}/${savePath}`;
-      } else {
-        absoluteSavePath = `${folderPath}/${savePath}`;
-      }
-    }
-
-    if (!imageBase64 && electronAPI.readImageFileAsBase64) {
-      const fileContent = await electronAPI.readImageFileAsBase64(screenshotPath)
-      if (!fileContent || !fileContent.success) {
-        return { success: false, error: `无法读取截图文件: ${fileContent?.error || '未知错误'}` }
-      }
-      imageBase64 = fileContent.data
-    }
-    
-    // 验证 base64 数据是否有效(至少应该是几百字节)
-    if (!imageBase64 || imageBase64.length < 100) {
-      return {
-        success: false,
-        error: `截图数据无效,base64长度: ${imageBase64?.length || 0},应该是至少几百字节。可能是截图失败或读取失败`
-      };
-    }
-
-    return { success: false, error: 'image-area-cropping 需使用 sharp/jimp 等 Node 图片库实现' }
-  } catch (error) {
-    return { 
-      success: false, 
-      error: error.message || '裁剪图片失败' 
-    };
-  }
-}
-
-module.exports = { tagName, schema, executeImageAreaCropping }

+ 0 - 81
nodejs/ef-compiler/Func/image-center-location.js

@@ -1,81 +0,0 @@
-/**
- * Func 标签:image-center-location
- * 
- * 图像匹配功能:识别模板图片是否在截图中的位置,返回中心点坐标
- * 注意:实际的 OpenCV 处理需要在 Node.js 主进程中实现
- */
-
-const electronAPI = require('../node-api.js')
-
-const tagName = 'image-center-location'
-
-const schema = {
-  description: '在屏幕截图中查找模板图片的位置并返回中心点坐标(可用于定位/点击)。',
-  inputs: {
-    template: '模板图片路径(相对于工作流目录)',
-    variable: '输出变量名(保存中心点坐标)',
-  },
-  outputs: {
-    variable: '中心点坐标(JSON 字符串格式,如:{"x":123,"y":456})',
-  },
-};
-
-/**
- * 执行 image-center-location 功能
- * 这个函数会被 ActionParser 调用
- * 
- * @param {Object} params - 参数对象
- * @param {string} params.device - 设备 ID/IP:Port(必需,用于获取截图)
- * @param {string} params.template - 模板图片路径
- * @param {string} params.folderPath - 工作流文件夹路径
- * @returns {Promise<{success: boolean, center?: Object, error?: string}>}
- */
-async function executeImageCenterLocation({ device, template, folderPath }) {
-  try {
-    if (!electronAPI.matchImageAndGetCoordinate) {
-      return { 
-        success: false, 
-        error: 'matchImageAndGetCoordinate API 不可用' 
-      };
-    }
-
-    if (!device) {
-      return { 
-        success: false, 
-        error: '缺少设备 ID,无法自动获取截图' 
-      };
-    }
-
-    // 构建模板图片完整路径(如果路径不是绝对路径,则相对于工作流目录的 resources 文件夹)
-    // resources 作为根目录
-    const templatePath = template.startsWith('/') || template.includes(':') 
-      ? template 
-      : `${folderPath}/resources/${template}`;
-
-    // 调用主进程的图像匹配函数(会自动获取设备截图)
-    const result = await electronAPI.matchImageAndGetCoordinate(
-      device,
-      templatePath
-    );
-
-    if (!result.success) {
-      return { success: false, error: result.error };
-    }
-
-    // 返回中心点坐标
-    const center = result.clickPosition || { x: result.coordinate.x + result.coordinate.width / 2, y: result.coordinate.y + result.coordinate.height / 2 };
-
-    return {
-      success: true,
-      center: center,
-      coordinate: result.coordinate // 同时返回完整坐标信息
-    };
-  } catch (error) {
-    return { 
-      success: false, 
-      error: error.message || '图像中心点定位失败' 
-    };
-  }
-}
-
-module.exports = { tagName, schema, executeImageCenterLocation }

+ 0 - 98
nodejs/ef-compiler/Func/image-region-location.js

@@ -1,98 +0,0 @@
-/**
- * Func 标签:image-region-location
- *
- * 约定:src/pages/processing/func/ 目录下每个文件名就是一个"可用标签/能力"。
- * 本文件用于声明该标签存在(供文档/提示词/后续动态加载使用)。
- *
- * 语义:通过图像匹配找到区域截图在完整截图中的位置,返回区域的四个顶点坐标。
- * 当前项目里对应能力主要由 electronAPI.matchImageRegionLocation + main-js/func/image-center-location.js 实现承载。
- */
-
-const electronAPI = require('../node-api.js')
-
-const tagName = 'image-region-location'
-
-const schema = {
-  description: '在完整截图中查找区域截图的位置,返回区域的四个顶点坐标(左上、右上、左下、右下)。',
-  inputs: {
-    screenshot: '完整截图路径(相对于工作流目录)',
-    region: '区域截图路径(相对于工作流目录)',
-    variable: '输出变量名(保存四个顶点坐标)',
-  },
-  outputs: {
-    variable: '四个顶点坐标对象 {topLeft: {x, y}, topRight: {x, y}, bottomLeft: {x, y}, bottomRight: {x, y}}',
-  },
-};
-
-/**
- * 执行 image-region-location 功能
- * 这个函数会被 ActionParser 调用
- * 
- * @param {Object} params - 参数对象
- * @param {string} params.device - 设备 ID/IP:Port(可选,用于获取分辨率)
- * @param {string} params.screenshot - 完整截图路径
- * @param {string} params.region - 区域截图路径
- * @param {string} params.folderPath - 工作流文件夹路径
- * @returns {Promise<{success: boolean, corners?: Object, error?: string}>}
- */
-async function executeImageRegionLocation({ device, screenshot, region, folderPath }) {
-  try {
-    if (!electronAPI.matchImageRegionLocation) {
-      return { 
-        success: false, 
-        error: 'matchImageRegionLocation API 不可用' 
-      };
-    }
-
-    // 如果 screenshot 为 null,使用特殊标记让主进程自动获取截图
-    let screenshotPath = screenshot;
-    if (screenshot === null) {
-      screenshotPath = '__AUTO_SCREENSHOT__';
-    } else if (screenshot) {
-      // 构建完整路径(如果路径不是绝对路径,则相对于工作流目录的 resources 文件夹)
-      // resources 作为根目录(使用已有的截图文件进行匹配,不主动截图)
-      screenshotPath = screenshot.startsWith('/') || screenshot.includes(':') 
-        ? screenshot 
-        : `${folderPath}/resources/${screenshot}`;
-      
-    }
-    
-    const regionPath = region.startsWith('/') || region.includes(':') 
-      ? region 
-      : `${folderPath}/resources/${region}`;
-
-    // 调用主进程的图像区域定位函数
-    // 如果 screenshotPath 是 '__AUTO_SCREENSHOT__',主进程会自动获取截图
-    const result = await electronAPI.matchImageRegionLocation(
-      screenshotPath,
-      regionPath,
-      device // 可选,用于获取设备分辨率进行缩放或自动获取截图
-    );
-
-    if (!result.success) {
-      return { success: false, error: result.error };
-    }
-
-    // 计算四个顶点坐标
-    const { x, y, width, height } = result;
-    const corners = {
-      topLeft: { x, y },
-      topRight: { x: x + width, y },
-      bottomLeft: { x, y: y + height },
-      bottomRight: { x: x + width, y: y + height }
-    };
-
-    return {
-      success: true,
-      corners: corners,
-      bounds: { x, y, width, height } // 同时返回边界框信息
-    };
-  } catch (error) {
-    return { 
-      success: false, 
-      error: error.message || '图像区域定位失败' 
-    };
-  }
-}
-
-module.exports = { tagName, schema, executeImageRegionLocation }

+ 63 - 0
nodejs/ef-compiler/Func/img-bounding-box-location.js

@@ -0,0 +1,63 @@
+/**
+ * Func 标签:img-bounding-box-location
+ *
+ * 在完整截图中查找区域截图的位置,返回区域包围框的四个顶点坐标。
+ */
+
+function matchImageRegionLocation() {
+  return { success: false, error: 'matchImageRegionLocation 需在主进程实现' }
+}
+
+const tagName = 'img-bounding-box-location'
+
+const schema = {
+  description: '在完整截图中查找区域截图的位置,返回区域的四个顶点坐标(左上、右上、左下、右下)。',
+  inputs: {
+    screenshot: '完整截图路径(相对于工作流目录)',
+    region: '区域截图路径(相对于工作流目录)',
+    variable: '输出变量名(保存四个顶点坐标)',
+  },
+  outputs: {
+    variable: '四个顶点坐标对象 {topLeft: {x, y}, topRight: {x, y}, bottomLeft: {x, y}, bottomRight: {x, y}}',
+  },
+};
+
+/**
+ * 执行 img-bounding-box-location 功能
+ */
+async function executeImgBoundingBoxLocation({ device, screenshot, region, folderPath }) {
+  let screenshotPath = screenshot;
+  if (screenshot === null) {
+    screenshotPath = '__AUTO_SCREENSHOT__';
+  } else if (screenshot) {
+    screenshotPath = screenshot.startsWith('/') || screenshot.includes(':')
+      ? screenshot
+      : `${folderPath}/resources/${screenshot}`;
+  }
+
+  const regionPath = region.startsWith('/') || region.includes(':')
+    ? region
+    : `${folderPath}/resources/${region}`;
+
+  const result = matchImageRegionLocation(screenshotPath, regionPath, device);
+
+  if (!result.success) {
+    return { success: false, error: result.error };
+  }
+
+  const { x, y, width, height } = result;
+  const corners = {
+    topLeft: { x, y },
+    topRight: { x: x + width, y },
+    bottomLeft: { x, y: y + height },
+    bottomRight: { x: x + width, y: y + height },
+  };
+
+  return {
+    success: true,
+    corners,
+    bounds: { x, y, width, height },
+  };
+}
+
+module.exports = { tagName, schema, executeImgBoundingBoxLocation };

+ 64 - 0
nodejs/ef-compiler/Func/img-center-point-location.js

@@ -0,0 +1,64 @@
+/**
+ * Func 标签:img-center-point-location
+ * 图像匹配:识别模板图片在截图中的位置,返回中心点坐标
+ */
+
+const path = require('path')
+const fs = require('fs')
+const os = require('os')
+const { spawnSync } = require('child_process')
+
+const projectRoot = path.resolve(__dirname, '..', '..', '..')
+const config = require(path.join(projectRoot, 'configs', 'config.js'))
+const imageMatchScriptPath = path.join(projectRoot, 'python', 'scripts', 'image-match.py')
+
+const tagName = 'img-center-point-location'
+
+const schema = {
+  description: '在屏幕截图中查找模板图片的位置并返回中心点坐标(可用于定位/点击)。',
+  inputs: { template: '模板图片路径(相对于工作流目录)', variable: '输出变量名(保存中心点坐标)' },
+  outputs: { variable: '中心点坐标(JSON 字符串格式,如:{"x":123,"y":456})' },
+}
+
+/** 在设备截图中匹配模板,返回坐标与中心点 */
+function matchImageAndGetCoordinate(device, imagePath) {
+  const templatePath = path.isAbsolute(imagePath) ? imagePath : path.resolve(projectRoot, imagePath)
+  const ts = Date.now()
+  const screenshotPath = path.join(os.tmpdir(), `ef-screenshot-${ts}.png`)
+  const templateCopyPath = path.join(os.tmpdir(), `ef-template-${ts}.png`)
+  fs.copyFileSync(templatePath, templateCopyPath)
+
+  const envVenv = path.join(config.pythonEnvPath, 'env', 'Scripts', 'python.exe')
+  const venvScripts = path.join(config.pythonEnvPath, 'Scripts', 'python.exe')
+  const pyEmbedded = path.join(config.pythonEnvPath, 'py', 'python.exe')
+  const pythonPath = fs.existsSync(envVenv) ? envVenv : (fs.existsSync(venvScripts) ? venvScripts : (fs.existsSync(pyEmbedded) ? pyEmbedded : (config.pythonPath?.path ? path.join(config.pythonPath.path, 'py', 'python.exe') : 'python')))
+  const adbPath = path.resolve(projectRoot, config.adbPath?.path || 'lib/scrcpy-adb/adb.exe')
+  const r = spawnSync(pythonPath, [imageMatchScriptPath, '--adb', adbPath, '--device', device, '--screenshot', screenshotPath.replace(/\\/g, '/'), '--template', templateCopyPath.replace(/\\/g, '/')], {
+    encoding: 'utf-8',
+    timeout: 20000,
+    env: { ...process.env, PYTHONIOENCODING: 'utf-8' },
+    cwd: projectRoot
+  })
+  try { fs.unlinkSync(screenshotPath) } catch (_) {}
+  try { fs.unlinkSync(templateCopyPath) } catch (_) {}
+
+  if (r.status !== 0) return { success: false, error: (r.stderr || r.stdout || '').trim() || '图像匹配失败' }
+  const out = JSON.parse(r.stdout.trim())
+  if (!out.success) return { success: false, error: out.error || '未找到匹配' }
+  return {
+    success: true,
+    coordinate: { x: out.x, y: out.y, width: out.width, height: out.height },
+    clickPosition: { x: out.center_x, y: out.center_y }
+  }
+}
+
+async function executeImgCenterPointLocation({ device, template, folderPath }) {
+  if (!device) return { success: false, error: '缺少设备 ID,无法自动获取截图' }
+  const templatePath = template.startsWith('/') || template.includes(':') ? template : `${folderPath}/resources/${template}`
+  const result = matchImageAndGetCoordinate(device, templatePath)
+  if (!result.success) return { success: false, error: result.error }
+  const center = result.clickPosition || { x: result.coordinate.x + result.coordinate.width / 2, y: result.coordinate.y + result.coordinate.height / 2 }
+  return { success: true, center, coordinate: result.coordinate }
+}
+
+module.exports = { tagName, schema, executeImgCenterPointLocation, matchImageAndGetCoordinate }

+ 94 - 0
nodejs/ef-compiler/Func/img-cropping.js

@@ -0,0 +1,94 @@
+/**
+ * Func 标签:img-cropping
+ * 根据区域坐标裁剪截图指定区域并保存
+ */
+
+const path = require('path')
+const fs = require('fs')
+const { spawnSync } = require('child_process')
+const { captureScreenshot } = require('../../adb/adb-screencap.js')
+
+const tagName = 'img-cropping'
+const projectRoot = path.resolve(__dirname, '..', '..', '..')
+const config = require(path.join(projectRoot, 'configs', 'config.js'))
+const imgCropScriptPath = path.join(projectRoot, 'python', 'scripts', 'img-crop.py')
+
+/** 解析 area 为 { x, y, width, height } */
+function parseAreaToRect(area) {
+  const obj = typeof area === 'string' ? JSON.parse(area) : area
+  if (obj.topLeft && obj.bottomRight) {
+    return {
+      x: parseInt(obj.topLeft.x, 10),
+      y: parseInt(obj.topLeft.y, 10),
+      width: parseInt(obj.bottomRight.x - obj.topLeft.x, 10),
+      height: parseInt(obj.bottomRight.y - obj.topLeft.y, 10),
+    }
+  }
+  if (obj.topLeft && obj.topRight && obj.bottomLeft && obj.bottomRight) {
+    return {
+      x: parseInt(obj.topLeft.x, 10),
+      y: parseInt(obj.topLeft.y, 10),
+      width: parseInt(obj.bottomRight.x - obj.topLeft.x, 10),
+      height: parseInt(obj.bottomRight.y - obj.topLeft.y, 10),
+    }
+  }
+  if (obj.x != null && obj.y != null && obj.width != null && obj.height != null) {
+    return {
+      x: parseInt(obj.x, 10),
+      y: parseInt(obj.y, 10),
+      width: parseInt(obj.width, 10),
+      height: parseInt(obj.height, 10),
+    }
+  }
+  return null
+}
+
+/** 构建截图路径 */
+function buildScreenshotPath(folderPath) {
+  return path.join(folderPath, 'history', 'ScreenShot.png')
+}
+
+/** 构建保存路径 */
+function buildSavePath(savePath, folderPath) {
+  return savePath.includes(':') ? savePath : path.join(folderPath, savePath)
+}
+
+/** 调用 Python 裁剪图片并保存 */
+function cropAndSaveImage(inputPath, outputPath, x, y, width, height) {
+  const envVenv = path.join(config.pythonEnvPath, 'env', 'Scripts', 'python.exe')
+  const venvScripts = path.join(config.pythonEnvPath, 'Scripts', 'python.exe')
+  const pyEmbedded = path.join(config.pythonEnvPath, 'py', 'python.exe')
+  const pythonExe = fs.existsSync(envVenv) ? envVenv : (fs.existsSync(venvScripts) ? venvScripts : pyEmbedded)
+  const r = spawnSync(pythonExe, [imgCropScriptPath, inputPath, outputPath, String(x), String(y), String(width), String(height)], {
+    encoding: 'utf-8',
+    timeout: 10000,
+    cwd: projectRoot
+  })
+  if (r.status !== 0) {
+    return { success: false, error: (r.stderr || r.stdout || '').trim() || '裁剪失败' }
+  }
+  return { success: true }
+}
+
+/** 执行 img-cropping */
+async function executeImgCropping({ area, savePath, folderPath, device }) {
+  const rect = parseAreaToRect(area)
+  if (!rect || rect.width <= 0 || rect.height <= 0) {
+    return { success: false, error: '区域坐标格式不正确' }
+  }
+
+  const screenshotPath = buildScreenshotPath(folderPath)
+  const outputPath = buildSavePath(savePath, folderPath)
+
+  if (device) {
+    const cap = captureScreenshot(device, screenshotPath)
+    if (!cap.success) return { success: false, error: cap.error }
+  }
+
+  const crop = cropAndSaveImage(screenshotPath, outputPath, rect.x, rect.y, rect.width, rect.height)
+  if (!crop.success) return { success: false, error: crop.error }
+
+  return { success: true }
+}
+
+module.exports = { tagName, executeImgCropping, cropAndSaveImage }

+ 20 - 76
nodejs/ef-compiler/Func/read-txt.js

@@ -1,86 +1,30 @@
 /**
  * 读取根目录下的文本文件
- * 支持从项目根目录读取文本文件内容
  */
 
-const electronAPI = require('../node-api.js')
+const path = require('path')
+const fs = require('fs')
 
-const tagName = 'read-txt'
+const projectRoot = path.resolve(__dirname, '..', '..', '..')
 
-const schema = {
-  description: '读取根目录下的文本文件内容。',
-  inputs: {
-    filePath: '文件路径(相对于项目根目录,如 "config.txt" 或 "data/input.txt")',
-    variable: '输出变量名(保存文件内容)',
-  },
-  outputs: {
-    variable: '文件内容(字符串)',
-  },
-};
-
-/**
- * 执行读取文本文件
- * @param {Object} params - 参数对象
- * @param {string} params.filePath - 文件路径(相对于项目根目录)
- * @param {string} params.folderPath - 工作流文件夹路径(用于构建绝对路径)
- * @returns {Promise<{success: boolean, error?: string, content?: string}>}
- */
-async function executeReadTxt({ filePath, folderPath }) {
-  try {
-    if (!filePath) {
-      return { success: false, error: 'read-txt 缺少 filePath 参数' };
-    }
-
-    if (!electronAPI.readTextFile) {
-      return { success: false, error: '读取文本文件 API 不可用' };
-    }
-
-    // 构建文件路径
-    // 如果 filePath 是绝对路径,直接使用
-    // 如果提供了 folderPath(工作流目录),相对于工作流目录
-    // 否则,相对于项目根目录
-    let absoluteFilePath = filePath;
-    
-    // 如果是相对路径(不以 / 开头且不包含 :)
-    if (!filePath.startsWith('/') && !filePath.includes(':')) {
-      // 如果提供了工作流目录,相对于工作流目录
-      if (folderPath) {
-        // folderPath 格式可能是:static/processing/微信聊天自动发送工作流
-        // 需要构建绝对路径
-        if (folderPath.startsWith('static/processing/')) {
-          const folderName = folderPath.replace('static/processing/', '');
-          // 构建工作流目录的绝对路径,然后拼接文件路径
-          // 这里需要调用主进程的 API 来解析路径,或者使用相对路径
-          // 由于主进程的 readTextFile 只支持相对于项目根目录的路径
-          // 我们需要构建相对于项目根目录的完整路径
-          absoluteFilePath = `static/processing/${folderName}/${filePath}`;
-        } else {
-          absoluteFilePath = `${folderPath}/${filePath}`;
-        }
-      } else {
-        // 没有工作流目录,相对于项目根目录
-        absoluteFilePath = filePath;
-      }
-    }
-
-    // 调用主进程的 readTextFile API
-    // 主进程会将相对路径解析为相对于项目根目录的绝对路径
-    // 如果文件不存在,主进程会返回空字符串
-    const result = electronAPI.readTextFile(absoluteFilePath);
+/** 构建绝对路径:绝对路径直接返回,否则相对于 folderPath 或项目根 */
+function buildFilePath(filePath, folderPath) {
+  if (filePath.startsWith('/') || filePath.includes(':')) return filePath
+  return folderPath ? path.join(folderPath, filePath) : filePath
+}
 
-    // 即使文件不存在,也返回成功(内容为空字符串)
-    if (!result.success) {
-      // 如果读取失败但不是文件不存在的情况,返回错误
-      return { success: false, error: `读取文件失败: ${result.error}` };
-    }
+function readTextFile(filePath) {
+  const fullPath = path.isAbsolute(filePath) ? filePath : path.resolve(projectRoot, filePath)
+  const content = fs.readFileSync(fullPath, 'utf8')
+  return { success: true, content }
+}
 
-    return {
-      success: true,
-      content: result.content || ''
-    };
-  } catch (error) {
-    return { success: false, error: error.message || '读取文本文件失败' };
-  }
+/** 执行读取文本文件 */
+async function executeReadTxt({ filePath, folderPath }) {
+  if (!filePath) return { success: false, error: 'read-txt 缺少 filePath 参数' }
+  const absoluteFilePath = buildFilePath(filePath, folderPath)
+  const result = readTextFile(absoluteFilePath)
+  return { success: true, content: result.content || '' }
 }
 
-module.exports = { tagName, schema, executeReadTxt }
+module.exports = { executeReadTxt, readTextFile }

+ 24 - 74
nodejs/ef-compiler/Func/save-txt.js

@@ -1,84 +1,34 @@
 /**
  * 保存字符串为文本文件
- * 支持将字符串内容保存到根目录下的文本文件
  */
 
-const electronAPI = require('../node-api.js')
+const path = require('path')
+const fs = require('fs')
 
-const tagName = 'save-txt'
+const projectRoot = path.resolve(__dirname, '..', '..', '..')
 
-const schema = {
-  description: '将字符串内容保存到根目录下的文本文件。',
-  inputs: {
-    filePath: '文件路径(相对于项目根目录,如 "output.txt" 或 "data/output.txt")',
-    content: '要保存的内容(字符串,可以是变量)',
-  },
-  outputs: {
-    success: '保存是否成功(boolean)',
-  },
-};
-
-/**
- * 执行保存文本文件
- * @param {Object} params - 参数对象
- * @param {string} params.filePath - 文件路径(相对于项目根目录)
- * @param {string} params.content - 要保存的内容(字符串)
- * @param {string} params.folderPath - 工作流文件夹路径(用于构建绝对路径)
- * @returns {Promise<{success: boolean, error?: string}>}
- */
-async function executeSaveTxt({ filePath, content, folderPath }) {
-  try {
-    if (!filePath) {
-      return { success: false, error: 'save-txt 缺少 filePath 参数' };
-    }
-
-    if (content === undefined || content === null) {
-      return { success: false, error: 'save-txt 缺少 content 参数' };
-    }
-
-    if (!electronAPI.writeTextFile) {
-      return { success: false, error: '写入文本文件 API 不可用' };
-    }
-
-    // 构建文件路径
-    // 如果 filePath 是绝对路径,直接使用
-    // 如果提供了 folderPath(工作流目录),相对于工作流目录
-    // 否则,相对于项目根目录
-    let absoluteFilePath = filePath;
-    
-    // 如果是相对路径(不以 / 开头且不包含 :)
-    if (!filePath.startsWith('/') && !filePath.includes(':')) {
-      // 如果提供了工作流目录,相对于工作流目录
-      if (folderPath) {
-        // folderPath 格式可能是:static/processing/微信聊天自动发送工作流
-        // 需要构建相对于项目根目录的完整路径
-        if (folderPath.startsWith('static/processing/')) {
-          const folderName = folderPath.replace('static/processing/', '');
-          absoluteFilePath = `static/processing/${folderName}/${filePath}`;
-        } else {
-          absoluteFilePath = `${folderPath}/${filePath}`;
-        }
-      } else {
-        // 没有工作流目录,相对于项目根目录
-        absoluteFilePath = filePath;
-      }
-    }
-
-    // 将内容转换为字符串
-    const contentString = typeof content === 'string' ? content : String(content);
-
-    // 调用主进程的 writeTextFile API
-    // 主进程会将相对路径解析为相对于项目根目录的绝对路径
-    const result = electronAPI.writeTextFile(absoluteFilePath, contentString);
+/** 构建绝对路径:绝对路径直接返回,否则相对于 folderPath 或项目根 */
+function buildFilePath(filePath, folderPath) {
+  if (filePath.startsWith('/') || filePath.includes(':')) return filePath
+  return folderPath ? path.join(folderPath, filePath) : filePath
+}
 
-    if (!result.success) {
-      return { success: false, error: `保存文件失败: ${result.error}` };
-    }
+function writeTextFile(filePath, content) {
+  const fullPath = path.isAbsolute(filePath) ? filePath : path.resolve(projectRoot, filePath)
+  const dir = path.dirname(fullPath)
+  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
+  fs.writeFileSync(fullPath, content)
+  return { success: true }
+}
 
-    return { success: true };
-  } catch (error) {
-    return { success: false, error: error.message || '保存文本文件失败' };
-  }
+/** 执行保存文本文件 */
+async function executeSaveTxt({ filePath, content, folderPath }) {
+  if (!filePath) return { success: false, error: 'save-txt 缺少 filePath 参数' }
+  if (content === undefined || content === null) return { success: false, error: 'save-txt 缺少 content 参数' }
+  const absoluteFilePath = buildFilePath(filePath, folderPath)
+  const contentStr = typeof content === 'string' ? content : String(content)
+  writeTextFile(absoluteFilePath, contentStr)
+  return { success: true }
 }
 
-module.exports = { tagName, schema, executeSaveTxt }
+module.exports = { executeSaveTxt, writeTextFile }

+ 59 - 22
nodejs/ef-compiler/ef-compiler.js

@@ -58,17 +58,54 @@ async function logOutVars(action, variableContext, folderPath = null) {
 }
 
 const path = require('path')
+const fs = require('fs')
+const { spawnSync } = require('child_process')
 const funcDir = path.join(__dirname, 'Func')
+const projectRoot = path.resolve(__dirname, '..', '..')
+const adbInteractPath = path.join(projectRoot, 'nodejs', 'adb', 'adb-interact.js')
 const { generateHistorySummary, getHistorySummary } = require(path.join(funcDir, 'chat', 'chat-history.js'))
 const { executeOcrChat } = require(path.join(funcDir, 'chat', 'ocr-chat.js'))
-const { executeImageRegionLocation } = require(path.join(funcDir, 'image-region-location.js'))
-const { executeImageCenterLocation } = require(path.join(funcDir, 'image-center-location.js'))
-const { executeImageAreaCropping } = require(path.join(funcDir, 'image-area-cropping.js'))
+const { executeImgBoundingBoxLocation } = require(path.join(funcDir, 'img-bounding-box-location.js'))
+const { executeImgCenterPointLocation, matchImageAndGetCoordinate } = require(path.join(funcDir, 'img-center-point-location.js'))
+const { executeImgCropping } = require(path.join(funcDir, 'img-cropping.js'))
 const { executeReadLastMessage } = require(path.join(funcDir, 'chat', 'read-last-message.js'))
-const { executeReadTxt } = require(path.join(funcDir, 'read-txt.js'))
-const { executeSaveTxt } = require(path.join(funcDir, 'save-txt.js'))
+const { executeReadTxt, readTextFile } = require(path.join(funcDir, 'read-txt.js'))
+const { executeSaveTxt, writeTextFile } = require(path.join(funcDir, 'save-txt.js'))
 const { executeSmartChatAppend } = require(path.join(funcDir, 'chat', 'smart-chat-append.js'))
-const electronAPI = require('./node-api.js')
+
+function runAdb(action, args = [], deviceId = '') {
+  const r = spawnSync('node', [adbInteractPath, action, ...args, deviceId], { encoding: 'utf-8', timeout: 10000 })
+  return { success: r.status === 0, error: r.stderr }
+}
+const sendTap = (device, x, y) => runAdb('tap', [String(x), String(y)], device)
+const sendSwipe = (device, x1, y1, x2, y2, duration) => runAdb('swipe-coords', [String(x1), String(y1), String(x2), String(y2), String(duration || 300)], device)
+const sendKeyEvent = (device, keyCode) => runAdb('keyevent', [String(keyCode)], device)
+const sendText = (device, text) => runAdb('text', [String(text)], device)
+function appendLog(folderPath, message) {
+  fs.appendFileSync(path.join(folderPath, 'log.txt'), message + '\n')
+  return Promise.resolve()
+}
+const _stub = (name) => ({ success: false, error: `${name} 需在主进程实现` })
+const electronAPI = {
+  sendTap, sendSwipe, sendKeyEvent, sendText,
+  matchImageAndGetCoordinate,
+  findTextAndGetCoordinate: () => _stub('findTextAndGetCoordinate'),
+  appendLog, readTextFile, writeTextFile,
+  saveChatHistory: () => _stub('saveChatHistory'),
+  readChatHistory: () => _stub('readChatHistory'),
+  readAllChatHistory: () => _stub('readAllChatHistory'),
+  saveChatHistorySummary: () => _stub('saveChatHistorySummary'),
+  getChatHistorySummary: () => _stub('getChatHistorySummary'),
+  saveChatHistoryTxt: () => _stub('saveChatHistoryTxt'),
+  extractChatHistory: () => _stub('extractChatHistory'),
+  readLastMessage: () => _stub('readLastMessage'),
+  ocrLastMessage: () => _stub('ocrLastMessage'),
+  getCachedScreenshot: () => _stub('getCachedScreenshot'),
+  captureScreenshot: () => _stub('captureScreenshot'),
+  sendScroll: () => _stub('sendScroll'),
+  sendSystemKey: () => _stub('sendSystemKey'),
+  matchImageRegionLocation: () => _stub('matchImageRegionLocation'),
+}
 
 /**
  * 解析时间字符串(格式:2026/1/13 02:09)
@@ -1068,7 +1105,7 @@ function parseNewFormatAction(action) {
         parsed.variable = extractVarName(action.outVars[0]);
       }
       break;
-    case 'image-region-location':
+    case 'img-bounding-box-location':
       // 支持新的 inVars/outVars 格式(都可以为空)
       if (action.inVars && Array.isArray(action.inVars)) {
         parsed.inVars = action.inVars.map(v => extractVarName(v));
@@ -1086,7 +1123,7 @@ function parseNewFormatAction(action) {
         parsed.variable = extractVarName(action.variable);
       }
       break;
-    case 'image-center-location':
+    case 'img-center-point-location':
       // 支持新的 inVars/outVars 格式(都可以为空)
       if (action.inVars && Array.isArray(action.inVars)) {
         parsed.inVars = action.inVars.map(v => extractVarName(v));
@@ -1102,7 +1139,7 @@ function parseNewFormatAction(action) {
         parsed.variable = extractVarName(action.variable);
       }
       break;
-    case 'image-area-cropping':
+    case 'img-cropping':
       // 支持新的 inVars/outVars 格式
       if (action.inVars && Array.isArray(action.inVars)) {
         parsed.inVars = action.inVars.map(v => extractVarName(v));
@@ -1114,7 +1151,7 @@ function parseNewFormatAction(action) {
         parsed.area = action.area;
         parsed.savePath = action.savePath;
       }
-      // image-area-cropping 通常不需要 outVars,但为了兼容性支持
+      // img-cropping 通常不需要 outVars,但为了兼容性支持
       if (action.outVars && Array.isArray(action.outVars) && action.outVars.length > 0) {
         parsed.variable = extractVarName(action.outVars[0]);
       }
@@ -1235,9 +1272,9 @@ function getActionName(action) {
     'ocr-chat-history': 'OCR提取消息记录',
     'extract-chat-history': '提取消息记录', // 向后兼容
     'generate-history-summary': '生成总结',
-    'image-region-location': '图像区域定位',
-    'image-center-location': '图像中心点定位',
-    'image-area-cropping': '裁剪图片区域',
+    'img-bounding-box-location': '图像区域定位',
+    'img-center-point-location': '图像中心点定位',
+    'img-cropping': '裁剪图片区域',
     'read-last-message': '读取最后一条消息',
     'read-txt': '读取文本文件',
     'read-text': '读取文本文件', // 向后兼容别名
@@ -2572,7 +2609,7 @@ async function executeAction(action, device, folderPath, resolution) {
         return { success: true };
       }
 
-      case 'image-region-location': {
+      case 'img-bounding-box-location': {
         // 图像区域定位
         // 支持新的 inVars/outVars 格式(都可以为空)
         let screenshotPath = action.screenshot;
@@ -2626,7 +2663,7 @@ async function executeAction(action, device, folderPath, resolution) {
         }
 
         // 调用 Func 目录下的执行函数
-        const result = await executeImageRegionLocation({
+        const result = await executeImgBoundingBoxLocation({
           device,
           screenshot: screenshotPath,
           region: regionPath,
@@ -2638,7 +2675,7 @@ async function executeAction(action, device, folderPath, resolution) {
         }
 
         // 保存结果到变量(支持 outVars 格式)
-        // image-region-location 返回四个顶点坐标
+        // img-bounding-box-location 返回四个顶点坐标
         let outputVarName = null;
         if (action.outVars && Array.isArray(action.outVars) && action.outVars.length > 0) {
           outputVarName = extractVarName(action.outVars[0]);
@@ -2660,7 +2697,7 @@ async function executeAction(action, device, folderPath, resolution) {
         return { success: true, result: result.corners };
       }
 
-      case 'image-center-location': {
+      case 'img-center-point-location': {
         // 图像中心点定位
         // 支持新的 inVars/outVars 格式(都可以为空)
         let templatePath = action.template;
@@ -2689,7 +2726,7 @@ async function executeAction(action, device, folderPath, resolution) {
         }
 
         // 调用 Func 目录下的执行函数
-        const result = await executeImageCenterLocation({
+        const result = await executeImgCenterPointLocation({
           device,
           template: templatePath,
           folderPath
@@ -2721,7 +2758,7 @@ async function executeAction(action, device, folderPath, resolution) {
         return { success: true, result: result.center };
       }
 
-      case 'image-area-cropping': {
+      case 'img-cropping': {
         // 裁剪图片区域
         // 支持新的 inVars 格式
         let area = action.area;
@@ -2751,14 +2788,14 @@ async function executeAction(action, device, folderPath, resolution) {
         }
 
         if (!area) {
-          return { success: false, error: 'image-area-cropping 缺少 area 参数' };
+          return { success: false, error: 'img-cropping 缺少 area 参数' };
         }
 
         if (!savePath) {
-          return { success: false, error: 'image-area-cropping 缺少 savePath 参数' };
+          return { success: false, error: 'img-cropping 缺少 savePath 参数' };
         }
 
-        const result = await executeImageAreaCropping({
+        const result = await executeImgCropping({
           area,
           savePath,
           folderPath,

+ 0 - 180
nodejs/ef-compiler/node-api.js

@@ -1,180 +0,0 @@
-/**
- * Node.js 环境下的 API 桥接,替代 window.electronAPI
- */
-
-const path = require('path')
-const fs = require('fs')
-const os = require('os')
-const { spawnSync } = require('child_process')
-
-const projectRoot = path.resolve(__dirname, '..', '..')
-const adbInteractPath = path.join(projectRoot, 'nodejs', 'adb', 'adb-interact.js')
-const imageMatchScriptPath = path.join(projectRoot, 'python', 'scripts', 'image-match.py')
-
-const config = require(path.join(projectRoot, 'configs', 'config.js'))
-
-function runAdb(action, args = [], deviceId = '') {
-  const result = spawnSync('node', [adbInteractPath, action, ...args, deviceId], {
-    encoding: 'utf-8',
-    timeout: 10000
-  })
-  return { success: result.status === 0, error: result.stderr }
-}
-
-async function sendTap(device, x, y) {
-  const r = runAdb('tap', [String(x), String(y)], device)
-  return r
-}
-
-async function sendSwipe(device, x1, y1, x2, y2, duration) {
-  const r = runAdb('swipe-coords', [String(x1), String(y1), String(x2), String(y2), String(duration || 300)], device)
-  return r
-}
-
-async function sendKeyEvent(device, keyCode) {
-  const r = runAdb('keyevent', [String(keyCode)], device)
-  return r
-}
-
-async function sendText(device, text) {
-  const r = runAdb('text', [String(text)], device)
-  return r
-}
-
-async function matchImageAndGetCoordinate(device, imagePath) {
-  const templatePath = path.isAbsolute(imagePath) ? imagePath : path.resolve(projectRoot, imagePath)
-  if (!fs.existsSync(templatePath)) {
-    return { success: false, error: `模板图片不存在: ${templatePath}` }
-  }
-
-  const ts = Date.now()
-  const screenshotPath = path.join(os.tmpdir(), `ef-screenshot-${ts}.png`)
-  const templateCopyPath = path.join(os.tmpdir(), `ef-template-${ts}.png`)
-
-  try {
-    fs.copyFileSync(templatePath, templateCopyPath)
-  } catch (e) {
-    return { success: false, error: `复制模板失败: ${e.message}` }
-  }
-
-  const venvPython = path.join(projectRoot, 'python', 'env', 'Scripts', 'python.exe')
-  const hasVenv = fs.existsSync(venvPython)
-  const pythonPath = hasVenv
-    ? venvPython
-    : (config.pythonPath?.path ? path.join(config.pythonPath.path, 'python.exe') : 'python')
-
-  const adbPathRel = config.adbPath?.path || 'lib/scrcpy-adb/adb.exe'
-  const adbPath = path.isAbsolute(adbPathRel) ? adbPathRel : path.join(projectRoot, adbPathRel)
-
-  const screenshotPathNorm = screenshotPath.replace(/\\/g, '/')
-  const templateCopyPathNorm = templateCopyPath.replace(/\\/g, '/')
-
-  const matchResult = spawnSync(pythonPath, [imageMatchScriptPath, '--adb', adbPath, '--device', device, '--screenshot', screenshotPathNorm, '--template', templateCopyPathNorm], {
-    encoding: 'utf-8',
-    timeout: 20000,
-    env: { ...process.env, PYTHONIOENCODING: 'utf-8' },
-    cwd: projectRoot
-  })
-
-  try { fs.unlinkSync(screenshotPath) } catch (_) {}
-  try { fs.unlinkSync(templateCopyPath) } catch (_) {}
-
-  if (matchResult.status !== 0) {
-    const errMsg = (matchResult.stderr || matchResult.stdout || '').trim()
-    return { success: false, error: errMsg || '图像匹配失败' }
-  }
-
-  let out
-  try {
-    out = JSON.parse(matchResult.stdout.trim())
-  } catch (e) {
-    return { success: false, error: `解析匹配结果失败: ${matchResult.stdout}` }
-  }
-
-  if (!out.success) {
-    return { success: false, error: out.error || '未找到匹配' }
-  }
-
-  return {
-    success: true,
-    coordinate: { x: out.x, y: out.y, width: out.width, height: out.height },
-    clickPosition: { x: out.center_x, y: out.center_y }
-  }
-}
-
-async function findTextAndGetCoordinate(device, targetText) {
-  return { success: false, error: 'findTextAndGetCoordinate 需在主进程实现' }
-}
-
-async function appendLog(folderPath, message) {
-  const logPath = path.join(folderPath, 'log.txt')
-  fs.appendFileSync(logPath, message + '\n')
-  return Promise.resolve()
-}
-
-function readTextFile(filePath) {
-  const fullPath = path.isAbsolute(filePath) ? filePath : path.resolve(projectRoot, filePath)
-  const content = fs.readFileSync(fullPath, 'utf8')
-  return { success: true, content }
-}
-
-function writeTextFile(filePath, content) {
-  const fullPath = path.isAbsolute(filePath) ? filePath : path.resolve(projectRoot, filePath)
-  const dir = path.dirname(fullPath)
-  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
-  fs.writeFileSync(fullPath, content)
-  return { success: true }
-}
-
-async function stub(name) {
-  return { success: false, error: `${name} 需在主进程实现` }
-}
-
-const nodeApi = {
-  sendTap,
-  sendSwipe,
-  sendKeyEvent,
-  sendText,
-  matchImageAndGetCoordinate,
-  findTextAndGetCoordinate,
-  appendLog,
-  readTextFile,
-  writeTextFile,
-  saveChatHistory: () => stub('saveChatHistory'),
-  readChatHistory: () => stub('readChatHistory'),
-  readAllChatHistory: () => stub('readAllChatHistory'),
-  saveChatHistorySummary: () => stub('saveChatHistorySummary'),
-  getChatHistorySummary: () => stub('getChatHistorySummary'),
-  saveChatHistoryTxt: () => stub('saveChatHistoryTxt'),
-  extractChatHistory: () => stub('extractChatHistory'),
-  readLastMessage: () => stub('readLastMessage'),
-  ocrLastMessage: () => stub('ocrLastMessage'),
-  getCachedScreenshot: () => stub('getCachedScreenshot'),
-  captureScreenshot: () => stub('captureScreenshot'),
-  async readImageFileAsBase64(filePath) {
-    try {
-      const fullPath = path.isAbsolute(filePath) ? filePath : path.resolve(projectRoot, filePath)
-      const buf = fs.readFileSync(fullPath)
-      const data = buf.toString('base64')
-      return { success: true, data }
-    } catch (e) {
-      return { success: false, error: e.message }
-    }
-  },
-  matchImageRegionLocation: () => stub('matchImageRegionLocation'),
-  cropAndSaveImage: () => stub('cropAndSaveImage'),
-  async saveBase64Image(base64, filePath) {
-    try {
-      const fullPath = path.isAbsolute(filePath) ? filePath : path.resolve(projectRoot, filePath)
-      const dir = path.dirname(fullPath)
-      if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
-      const buf = Buffer.from(base64, 'base64')
-      fs.writeFileSync(fullPath, buf)
-      return { success: true }
-    } catch (e) {
-      return { success: false, error: e.message }
-    }
-  }
-}
-
-module.exports = nodeApi

+ 0 - 1
python/environment.txt → python/arm64/environment.txt

@@ -1,4 +1,3 @@
-coloredlogs==15.0.1
 flatbuffers==25.12.19
 humanfriendly==10.0
 mpmath==1.3.0

+ 702 - 0
python/arm64/py/LICENSE.txt

@@ -0,0 +1,702 @@
+A. HISTORY OF THE SOFTWARE
+==========================
+
+Python was created in the early 1990s by Guido van Rossum at Stichting
+Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands
+as a successor of a language called ABC.  Guido remains Python's
+principal author, although it includes many contributions from others.
+
+In 1995, Guido continued his work on Python at the Corporation for
+National Research Initiatives (CNRI, see https://www.cnri.reston.va.us)
+in Reston, Virginia where he released several versions of the
+software.
+
+In May 2000, Guido and the Python core development team moved to
+BeOpen.com to form the BeOpen PythonLabs team.  In October of the same
+year, the PythonLabs team moved to Digital Creations, which became
+Zope Corporation.  In 2001, the Python Software Foundation (PSF, see
+https://www.python.org/psf/) was formed, a non-profit organization
+created specifically to own Python-related Intellectual Property.
+Zope Corporation was a sponsoring member of the PSF.
+
+All Python releases are Open Source (see https://opensource.org for
+the Open Source Definition).  Historically, most, but not all, Python
+releases have also been GPL-compatible; the table below summarizes
+the various releases.
+
+    Release         Derived     Year        Owner       GPL-
+                    from                                compatible? (1)
+
+    0.9.0 thru 1.2              1991-1995   CWI         yes
+    1.3 thru 1.5.2  1.2         1995-1999   CNRI        yes
+    1.6             1.5.2       2000        CNRI        no
+    2.0             1.6         2000        BeOpen.com  no
+    1.6.1           1.6         2001        CNRI        yes (2)
+    2.1             2.0+1.6.1   2001        PSF         no
+    2.0.1           2.0+1.6.1   2001        PSF         yes
+    2.1.1           2.1+2.0.1   2001        PSF         yes
+    2.1.2           2.1.1       2002        PSF         yes
+    2.1.3           2.1.2       2002        PSF         yes
+    2.2 and above   2.1.1       2001-now    PSF         yes
+
+Footnotes:
+
+(1) GPL-compatible doesn't mean that we're distributing Python under
+    the GPL.  All Python licenses, unlike the GPL, let you distribute
+    a modified version without making your changes open source.  The
+    GPL-compatible licenses make it possible to combine Python with
+    other software that is released under the GPL; the others don't.
+
+(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
+    because its license has a choice of law clause.  According to
+    CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
+    is "not incompatible" with the GPL.
+
+Thanks to the many outside volunteers who have worked under Guido's
+direction to make these releases possible.
+
+
+B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
+===============================================================
+
+Python software and documentation are licensed under the
+Python Software Foundation License Version 2.
+
+Starting with Python 3.8.6, examples, recipes, and other code in
+the documentation are dual licensed under the PSF License Version 2
+and the Zero-Clause BSD license.
+
+Some software incorporated into Python is under different licenses.
+The licenses are listed with code falling under that license.
+
+
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+--------------------------------------------
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using this software ("Python") in source or binary form and
+its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF hereby
+grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+analyze, test, perform and/or display publicly, prepare derivative works,
+distribute, and otherwise use Python alone or in any derivative version,
+provided, however, that PSF's License Agreement and PSF's notice of copyright,
+i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation;
+All Rights Reserved" are retained in Python alone or in any derivative version
+prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS"
+basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee.  This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
+-------------------------------------------
+
+BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
+
+1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
+office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
+Individual or Organization ("Licensee") accessing and otherwise using
+this software in source or binary form and its associated
+documentation ("the Software").
+
+2. Subject to the terms and conditions of this BeOpen Python License
+Agreement, BeOpen hereby grants Licensee a non-exclusive,
+royalty-free, world-wide license to reproduce, analyze, test, perform
+and/or display publicly, prepare derivative works, distribute, and
+otherwise use the Software alone or in any derivative version,
+provided, however, that the BeOpen Python License is retained in the
+Software, alone or in any derivative version prepared by Licensee.
+
+3. BeOpen is making the Software available to Licensee on an "AS IS"
+basis.  BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
+SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
+AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
+DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+5. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+6. This License Agreement shall be governed by and interpreted in all
+respects by the law of the State of California, excluding conflict of
+law provisions.  Nothing in this License Agreement shall be deemed to
+create any relationship of agency, partnership, or joint venture
+between BeOpen and Licensee.  This License Agreement does not grant
+permission to use BeOpen trademarks or trade names in a trademark
+sense to endorse or promote products or services of Licensee, or any
+third party.  As an exception, the "BeOpen Python" logos available at
+http://www.pythonlabs.com/logos.html may be used according to the
+permissions granted on that web page.
+
+7. By copying, installing or otherwise using the software, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
+---------------------------------------
+
+1. This LICENSE AGREEMENT is between the Corporation for National
+Research Initiatives, having an office at 1895 Preston White Drive,
+Reston, VA 20191 ("CNRI"), and the Individual or Organization
+("Licensee") accessing and otherwise using Python 1.6.1 software in
+source or binary form and its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, CNRI
+hereby grants Licensee a nonexclusive, royalty-free, world-wide
+license to reproduce, analyze, test, perform and/or display publicly,
+prepare derivative works, distribute, and otherwise use Python 1.6.1
+alone or in any derivative version, provided, however, that CNRI's
+License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
+1995-2001 Corporation for National Research Initiatives; All Rights
+Reserved" are retained in Python 1.6.1 alone or in any derivative
+version prepared by Licensee.  Alternately, in lieu of CNRI's License
+Agreement, Licensee may substitute the following text (omitting the
+quotes): "Python 1.6.1 is made available subject to the terms and
+conditions in CNRI's License Agreement.  This Agreement together with
+Python 1.6.1 may be located on the internet using the following
+unique, persistent identifier (known as a handle): 1895.22/1013.  This
+Agreement may also be obtained from a proxy server on the internet
+using the following URL: http://hdl.handle.net/1895.22/1013".
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python 1.6.1 or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python 1.6.1.
+
+4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
+basis.  CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. This License Agreement shall be governed by the federal
+intellectual property law of the United States, including without
+limitation the federal copyright law, and, to the extent such
+U.S. federal law does not apply, by the law of the Commonwealth of
+Virginia, excluding Virginia's conflict of law provisions.
+Notwithstanding the foregoing, with regard to derivative works based
+on Python 1.6.1 that incorporate non-separable material that was
+previously distributed under the GNU General Public License (GPL), the
+law of the Commonwealth of Virginia shall govern this License
+Agreement only as to issues arising under or with respect to
+Paragraphs 4, 5, and 7 of this License Agreement.  Nothing in this
+License Agreement shall be deemed to create any relationship of
+agency, partnership, or joint venture between CNRI and Licensee.  This
+License Agreement does not grant permission to use CNRI trademarks or
+trade name in a trademark sense to endorse or promote products or
+services of Licensee, or any third party.
+
+8. By clicking on the "ACCEPT" button where indicated, or by copying,
+installing or otherwise using Python 1.6.1, Licensee agrees to be
+bound by the terms and conditions of this License Agreement.
+
+        ACCEPT
+
+
+CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
+--------------------------------------------------
+
+Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
+The Netherlands.  All rights reserved.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the name of Stichting Mathematisch
+Centrum or CWI not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior
+permission.
+
+STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
+FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
+----------------------------------------------------------------------
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+
+
+
+Additional Conditions for this Windows binary build
+---------------------------------------------------
+
+This program is linked with and uses Microsoft Distributable Code,
+copyrighted by Microsoft Corporation. The Microsoft Distributable Code
+is embedded in each .exe, .dll and .pyd file as a result of running
+the code through a linker.
+
+If you further distribute programs that include the Microsoft
+Distributable Code, you must comply with the restrictions on
+distribution specified by Microsoft. In particular, you must require
+distributors and external end users to agree to terms that protect the
+Microsoft Distributable Code at least as much as Microsoft's own
+requirements for the Distributable Code. See Microsoft's documentation
+(included in its developer tools and on its website at microsoft.com)
+for specific details.
+
+Redistribution of the Windows binary build of the Python interpreter
+complies with this agreement, provided that you do not:
+
+- alter any copyright, trademark or patent notice in Microsoft's
+Distributable Code;
+
+- use Microsoft's trademarks in your programs' names or in a way that
+suggests your programs come from or are endorsed by Microsoft;
+
+- distribute Microsoft's Distributable Code to run on a platform other
+than Microsoft operating systems, run-time technologies or application
+platforms; or
+
+- include Microsoft Distributable Code in malicious, deceptive or
+unlawful programs.
+
+These restrictions apply only to the Microsoft Distributable Code as
+defined above, not to Python itself or any programs running on the
+Python interpreter. The redistribution of the Python interpreter and
+libraries is governed by the Python Software License included with this
+file, or by other licenses as marked.
+
+
+
+--------------------------------------------------------------------------
+
+This program, "bzip2", the associated library "libbzip2", and all
+documentation, are copyright (C) 1996-2019 Julian R Seward.  All
+rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. The origin of this software must not be misrepresented; you must 
+   not claim that you wrote the original software.  If you use this 
+   software in a product, an acknowledgment in the product 
+   documentation would be appreciated but is not required.
+
+3. Altered source versions must be plainly marked as such, and must
+   not be misrepresented as being the original software.
+
+4. The name of the author may not be used to endorse or promote 
+   products derived from this software without specific prior written 
+   permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Julian Seward, jseward@acm.org
+bzip2/libbzip2 version 1.0.8 of 13 July 2019
+
+--------------------------------------------------------------------------
+
+libffi - Copyright (c) 1996-2022  Anthony Green, Red Hat, Inc and others.
+See source files for details.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+``Software''), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        https://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+This software is copyrighted by the Regents of the University of
+California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
+Corporation and other parties.  The following terms apply to all files
+associated with the software unless explicitly disclaimed in
+individual files.
+
+The authors hereby grant permission to use, copy, modify, distribute,
+and license this software and its documentation for any purpose, provided
+that existing copyright notices are retained in all copies and that this
+notice is included verbatim in any distributions. No written agreement,
+license, or royalty fee is required for any of the authorized uses.
+Modifications to this software may be copyrighted by their authors
+and need not follow the licensing terms described here, provided that
+the new terms are clearly indicated on the first page of each file where
+they apply.
+
+IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
+IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+MODIFICATIONS.
+
+GOVERNMENT USE: If you are acquiring this software on behalf of the
+U.S. government, the Government shall have only "Restricted Rights"
+in the software and related documentation as defined in the Federal
+Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
+are acquiring the software on behalf of the Department of Defense, the
+software shall be classified as "Commercial Computer Software" and the
+Government shall have only "Restricted Rights" as defined in Clause
+252.227-7014 (b) (3) of DFARs.  Notwithstanding the foregoing, the
+authors grant the U.S. Government and others acting in its behalf
+permission to use and distribute the software in accordance with the
+terms specified in this license.
+
+This software is copyrighted by the Regents of the University of
+California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
+Corporation, Apple Inc. and other parties.  The following terms apply to
+all files associated with the software unless explicitly disclaimed in
+individual files.
+
+The authors hereby grant permission to use, copy, modify, distribute,
+and license this software and its documentation for any purpose, provided
+that existing copyright notices are retained in all copies and that this
+notice is included verbatim in any distributions. No written agreement,
+license, or royalty fee is required for any of the authorized uses.
+Modifications to this software may be copyrighted by their authors
+and need not follow the licensing terms described here, provided that
+the new terms are clearly indicated on the first page of each file where
+they apply.
+
+IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
+IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+MODIFICATIONS.
+
+GOVERNMENT USE: If you are acquiring this software on behalf of the
+U.S. government, the Government shall have only "Restricted Rights"
+in the software and related documentation as defined in the Federal
+Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
+are acquiring the software on behalf of the Department of Defense, the
+software shall be classified as "Commercial Computer Software" and the
+Government shall have only "Restricted Rights" as defined in Clause
+252.227-7013 (b) (3) of DFARs.  Notwithstanding the foregoing, the
+authors grant the U.S. Government and others acting in its behalf
+permission to use and distribute the software in accordance with the
+terms specified in this license.
+
+Copyright (c) 1993-1999 Ioi Kim Lam.
+Copyright (c) 2000-2001 Tix Project Group.
+Copyright (c) 2004 ActiveState
+
+This software is copyrighted by the above entities
+and other parties.  The following terms apply to all files associated
+with the software unless explicitly disclaimed in individual files.
+
+The authors hereby grant permission to use, copy, modify, distribute,
+and license this software and its documentation for any purpose, provided
+that existing copyright notices are retained in all copies and that this
+notice is included verbatim in any distributions. No written agreement,
+license, or royalty fee is required for any of the authorized uses.
+Modifications to this software may be copyrighted by their authors
+and need not follow the licensing terms described here, provided that
+the new terms are clearly indicated on the first page of each file where
+they apply.
+
+IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
+IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+MODIFICATIONS.
+
+GOVERNMENT USE: If you are acquiring this software on behalf of the
+U.S. government, the Government shall have only "Restricted Rights"
+in the software and related documentation as defined in the Federal 
+Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
+are acquiring the software on behalf of the Department of Defense, the
+software shall be classified as "Commercial Computer Software" and the
+Government shall have only "Restricted Rights" as defined in Clause
+252.227-7013 (c) (1) of DFARs.  Notwithstanding the foregoing, the
+authors grant the U.S. Government and others acting in its behalf
+permission to use and distribute the software in accordance with the
+terms specified in this license. 
+
+----------------------------------------------------------------------
+
+Parts of this software are based on the Tcl/Tk software copyrighted by
+the Regents of the University of California, Sun Microsystems, Inc.,
+and other parties. The original license terms of the Tcl/Tk software
+distribution is included in the file docs/license.tcltk.
+
+Parts of this software are based on the HTML Library software
+copyrighted by Sun Microsystems, Inc. The original license terms of
+the HTML Library software distribution is included in the file
+docs/license.html_lib.
+

BIN
python/arm64/py/_asyncio.pyd


BIN
python/arm64/py/_bz2.pyd


BIN
python/arm64/py/_ctypes.pyd


BIN
python/arm64/py/_decimal.pyd


BIN
python/arm64/py/_elementtree.pyd


BIN
python/arm64/py/_hashlib.pyd


BIN
python/arm64/py/_lzma.pyd


BIN
python/arm64/py/_msi.pyd


BIN
python/arm64/py/_multiprocessing.pyd


BIN
python/arm64/py/_overlapped.pyd


BIN
python/arm64/py/_queue.pyd


BIN
python/arm64/py/_socket.pyd


BIN
python/arm64/py/_sqlite3.pyd


BIN
python/arm64/py/_ssl.pyd


BIN
python/arm64/py/_uuid.pyd


BIN
python/arm64/py/_wmi.pyd


BIN
python/arm64/py/_zoneinfo.pyd


BIN
python/arm64/py/libcrypto-3-arm64.dll


BIN
python/arm64/py/libffi-8.dll


BIN
python/arm64/py/libssl-3-arm64.dll


BIN
python/arm64/py/pyexpat.pyd


BIN
python/arm64/py/python.cat


BIN
python/arm64/py/python.exe


BIN
python/arm64/py/python3.dll


+ 5 - 0
python/arm64/py/python312._pth

@@ -0,0 +1,5 @@
+python312.zip
+.
+
+# Uncomment to run site.main() automatically
+#import site

BIN
python/arm64/py/python312.dll


BIN
python/arm64/py/python312.zip


BIN
python/arm64/py/pythonw.exe


BIN
python/arm64/py/select.pyd


BIN
python/arm64/py/sqlite3.dll


BIN
python/arm64/py/unicodedata.pyd


BIN
python/arm64/py/vcruntime140.dll


BIN
python/arm64/py/vcruntime140_1.dll


BIN
python/arm64/py/winsound.pyd


+ 4 - 3
python/python-enviroment-install.py → python/arm64/python-enviroment-install.py

@@ -11,10 +11,11 @@ import subprocess
 import platform
 from pathlib import Path
 
-# 获取脚本所在目录和项目根目录
+# 脚本所在目录即本环境目录(python/arm64 或 python/x64),venv 固定为 env
 SCRIPT_DIR = Path(__file__).parent.absolute()
-PROJECT_ROOT = SCRIPT_DIR.parent.absolute()
-VENV_PATH = SCRIPT_DIR / "env"
+PROJECT_ROOT = SCRIPT_DIR.parent.parent.absolute()
+_env_path = os.environ.get("PYTHON_VENV_PATH", "").strip()
+VENV_PATH = Path(_env_path) if _env_path else SCRIPT_DIR / "env"
 ENVIRONMENT_FILE = SCRIPT_DIR / "environment.txt"
 REQUIREMENTS_FILE = PROJECT_ROOT / "requirements.txt"
 

+ 33 - 0
python/arm64/update-enviroment-list.bat

@@ -0,0 +1,33 @@
+@echo off
+chcp 65001 >nul
+title Update Python Environment List
+
+cd /d "%~dp0"
+
+REM 环境目录(python/arm64 或 python/x64),优先使用 env\Scripts\python.exe
+for /f "delims=" %%i in ('node "%~dp0..\..\configs\get-python-env-path.js" 2^>nul') do set "ENV_BASE=%%i"
+if not defined ENV_BASE set "ENV_BASE=%~dp0"
+set "PYTHON_EXE="
+if exist "%ENV_BASE%\env\Scripts\python.exe" set "PYTHON_EXE=%ENV_BASE%\env\Scripts\python.exe"
+if not defined PYTHON_EXE if exist "%ENV_BASE%\Scripts\python.exe" set "PYTHON_EXE=%ENV_BASE%\Scripts\python.exe"
+if not defined PYTHON_EXE if exist "%ENV_BASE%\py\python.exe" set "PYTHON_EXE=%ENV_BASE%\py\python.exe"
+if not defined PYTHON_EXE (
+    echo [ERROR] Python not found under %ENV_BASE%
+    pause
+    exit /b 1
+)
+
+"%PYTHON_EXE%" "%~dp0update-environment-list.py"
+
+if errorlevel 1 (
+    echo.
+    echo [ERROR] Update failed
+    echo.
+    pause
+    exit /b 1
+) else (
+    echo.
+    echo [OK] Update completed
+    echo.
+    pause
+)

+ 6 - 2
python/update-environment-list.py → python/arm64/update-environment-list.py

@@ -11,9 +11,13 @@ import subprocess
 import platform
 from pathlib import Path
 
-# 获取脚本所在目录
+# 脚本所在目录即本环境目录,venv 固定为 env
 SCRIPT_DIR = Path(__file__).parent.absolute()
-VENV_PATH = SCRIPT_DIR / "env"
+_exe = Path(sys.executable).resolve()
+if "Scripts" in _exe.parts or "bin" in _exe.parts:
+    VENV_PATH = _exe.parent.parent
+else:
+    VENV_PATH = SCRIPT_DIR / "env"
 ENVIRONMENT_FILE = SCRIPT_DIR / "environment.txt"
 
 # 根据操作系统确定虚拟环境的 pip 路径

+ 34 - 0
python/scripts/img-crop.py

@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+按区域坐标裁剪图片并保存
+用法: python img-crop.py <input_path> <output_path> <x> <y> <width> <height>
+"""
+
+import sys
+from pathlib import Path
+
+try:
+    from PIL import Image
+except ImportError:
+    print('{"success": false, "error": "请安装 Pillow: pip install pillow"}')
+    sys.exit(1)
+
+def main():
+    if len(sys.argv) != 7:
+        print('{"success": false, "error": "用法: img-crop.py <input> <output> <x> <y> <width> <height>"}')
+        sys.exit(1)
+
+    input_path = sys.argv[1]
+    output_path = sys.argv[2]
+    x, y, w, h = int(sys.argv[3]), int(sys.argv[4]), int(sys.argv[5]), int(sys.argv[6])
+
+    img = Image.open(input_path).convert('RGB')
+    cropped = img.crop((x, y, x + w, y + h))
+    Path(output_path).parent.mkdir(parents=True, exist_ok=True)
+    cropped.save(output_path)
+    print('{"success": true}')
+    sys.exit(0)
+
+if __name__ == "__main__":
+    main()

+ 0 - 16
python/scripts/test.py

@@ -1,16 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-import sys
-
-# 获取第一个参数(数字)
-if len(sys.argv) < 2:
-    print("错误:需要传入一个数字参数")
-    sys.exit(1)
-
-try:
-    num = int(sys.argv[1])
-    result = num + 1
-    print(result)
-except ValueError:
-    print("错误:参数必须是数字")
-    sys.exit(1)

+ 0 - 30
python/update-enviroment-list.bat

@@ -1,30 +0,0 @@
-@echo off
-chcp 65001 >nul
-title Update Python Environment List
-
-cd /d "%~dp0"
-
-REM Check if virtual environment exists
-if not exist "env\Scripts\python.exe" (
-    echo [ERROR] Virtual environment not found at: env
-    echo Please run python-enviroment-install.py first to create the virtual environment.
-    echo.
-    pause
-    exit /b 1
-)
-
-REM Run Python script to compare and update environment.txt
-python "%~dp0update-environment-list.py"
-
-if errorlevel 1 (
-    echo.
-    echo [ERROR] Update failed
-    echo.
-    pause
-    exit /b 1
-) else (
-    echo.
-    echo [OK] Update completed
-    echo.
-    pause
-)

+ 17 - 0
python/x64/environment.txt

@@ -0,0 +1,17 @@
+coloredlogs==15.0.1
+flatbuffers==25.12.19
+humanfriendly==10.0
+mpmath==1.3.0
+numpy==2.4.1
+opencv-contrib-python==4.13.0.90
+opencv-python==4.13.0.90
+opencv-python-headless==4.13.0.90
+packaging==26.0
+pillow==12.1.1
+protobuf==6.33.4
+pyclipper==1.4.0
+pyreadline3==3.5.4
+setuptools==80.10.2
+shapely==2.1.2
+sympy==1.14.0
+wheel==0.46.3

+ 702 - 0
python/x64/py/LICENSE.txt

@@ -0,0 +1,702 @@
+A. HISTORY OF THE SOFTWARE
+==========================
+
+Python was created in the early 1990s by Guido van Rossum at Stichting
+Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands
+as a successor of a language called ABC.  Guido remains Python's
+principal author, although it includes many contributions from others.
+
+In 1995, Guido continued his work on Python at the Corporation for
+National Research Initiatives (CNRI, see https://www.cnri.reston.va.us)
+in Reston, Virginia where he released several versions of the
+software.
+
+In May 2000, Guido and the Python core development team moved to
+BeOpen.com to form the BeOpen PythonLabs team.  In October of the same
+year, the PythonLabs team moved to Digital Creations, which became
+Zope Corporation.  In 2001, the Python Software Foundation (PSF, see
+https://www.python.org/psf/) was formed, a non-profit organization
+created specifically to own Python-related Intellectual Property.
+Zope Corporation was a sponsoring member of the PSF.
+
+All Python releases are Open Source (see https://opensource.org for
+the Open Source Definition).  Historically, most, but not all, Python
+releases have also been GPL-compatible; the table below summarizes
+the various releases.
+
+    Release         Derived     Year        Owner       GPL-
+                    from                                compatible? (1)
+
+    0.9.0 thru 1.2              1991-1995   CWI         yes
+    1.3 thru 1.5.2  1.2         1995-1999   CNRI        yes
+    1.6             1.5.2       2000        CNRI        no
+    2.0             1.6         2000        BeOpen.com  no
+    1.6.1           1.6         2001        CNRI        yes (2)
+    2.1             2.0+1.6.1   2001        PSF         no
+    2.0.1           2.0+1.6.1   2001        PSF         yes
+    2.1.1           2.1+2.0.1   2001        PSF         yes
+    2.1.2           2.1.1       2002        PSF         yes
+    2.1.3           2.1.2       2002        PSF         yes
+    2.2 and above   2.1.1       2001-now    PSF         yes
+
+Footnotes:
+
+(1) GPL-compatible doesn't mean that we're distributing Python under
+    the GPL.  All Python licenses, unlike the GPL, let you distribute
+    a modified version without making your changes open source.  The
+    GPL-compatible licenses make it possible to combine Python with
+    other software that is released under the GPL; the others don't.
+
+(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
+    because its license has a choice of law clause.  According to
+    CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
+    is "not incompatible" with the GPL.
+
+Thanks to the many outside volunteers who have worked under Guido's
+direction to make these releases possible.
+
+
+B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
+===============================================================
+
+Python software and documentation are licensed under the
+Python Software Foundation License Version 2.
+
+Starting with Python 3.8.6, examples, recipes, and other code in
+the documentation are dual licensed under the PSF License Version 2
+and the Zero-Clause BSD license.
+
+Some software incorporated into Python is under different licenses.
+The licenses are listed with code falling under that license.
+
+
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+--------------------------------------------
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using this software ("Python") in source or binary form and
+its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF hereby
+grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+analyze, test, perform and/or display publicly, prepare derivative works,
+distribute, and otherwise use Python alone or in any derivative version,
+provided, however, that PSF's License Agreement and PSF's notice of copyright,
+i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation;
+All Rights Reserved" are retained in Python alone or in any derivative version
+prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS"
+basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee.  This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
+-------------------------------------------
+
+BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
+
+1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
+office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
+Individual or Organization ("Licensee") accessing and otherwise using
+this software in source or binary form and its associated
+documentation ("the Software").
+
+2. Subject to the terms and conditions of this BeOpen Python License
+Agreement, BeOpen hereby grants Licensee a non-exclusive,
+royalty-free, world-wide license to reproduce, analyze, test, perform
+and/or display publicly, prepare derivative works, distribute, and
+otherwise use the Software alone or in any derivative version,
+provided, however, that the BeOpen Python License is retained in the
+Software, alone or in any derivative version prepared by Licensee.
+
+3. BeOpen is making the Software available to Licensee on an "AS IS"
+basis.  BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
+SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
+AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
+DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+5. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+6. This License Agreement shall be governed by and interpreted in all
+respects by the law of the State of California, excluding conflict of
+law provisions.  Nothing in this License Agreement shall be deemed to
+create any relationship of agency, partnership, or joint venture
+between BeOpen and Licensee.  This License Agreement does not grant
+permission to use BeOpen trademarks or trade names in a trademark
+sense to endorse or promote products or services of Licensee, or any
+third party.  As an exception, the "BeOpen Python" logos available at
+http://www.pythonlabs.com/logos.html may be used according to the
+permissions granted on that web page.
+
+7. By copying, installing or otherwise using the software, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
+---------------------------------------
+
+1. This LICENSE AGREEMENT is between the Corporation for National
+Research Initiatives, having an office at 1895 Preston White Drive,
+Reston, VA 20191 ("CNRI"), and the Individual or Organization
+("Licensee") accessing and otherwise using Python 1.6.1 software in
+source or binary form and its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, CNRI
+hereby grants Licensee a nonexclusive, royalty-free, world-wide
+license to reproduce, analyze, test, perform and/or display publicly,
+prepare derivative works, distribute, and otherwise use Python 1.6.1
+alone or in any derivative version, provided, however, that CNRI's
+License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
+1995-2001 Corporation for National Research Initiatives; All Rights
+Reserved" are retained in Python 1.6.1 alone or in any derivative
+version prepared by Licensee.  Alternately, in lieu of CNRI's License
+Agreement, Licensee may substitute the following text (omitting the
+quotes): "Python 1.6.1 is made available subject to the terms and
+conditions in CNRI's License Agreement.  This Agreement together with
+Python 1.6.1 may be located on the internet using the following
+unique, persistent identifier (known as a handle): 1895.22/1013.  This
+Agreement may also be obtained from a proxy server on the internet
+using the following URL: http://hdl.handle.net/1895.22/1013".
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python 1.6.1 or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python 1.6.1.
+
+4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
+basis.  CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. This License Agreement shall be governed by the federal
+intellectual property law of the United States, including without
+limitation the federal copyright law, and, to the extent such
+U.S. federal law does not apply, by the law of the Commonwealth of
+Virginia, excluding Virginia's conflict of law provisions.
+Notwithstanding the foregoing, with regard to derivative works based
+on Python 1.6.1 that incorporate non-separable material that was
+previously distributed under the GNU General Public License (GPL), the
+law of the Commonwealth of Virginia shall govern this License
+Agreement only as to issues arising under or with respect to
+Paragraphs 4, 5, and 7 of this License Agreement.  Nothing in this
+License Agreement shall be deemed to create any relationship of
+agency, partnership, or joint venture between CNRI and Licensee.  This
+License Agreement does not grant permission to use CNRI trademarks or
+trade name in a trademark sense to endorse or promote products or
+services of Licensee, or any third party.
+
+8. By clicking on the "ACCEPT" button where indicated, or by copying,
+installing or otherwise using Python 1.6.1, Licensee agrees to be
+bound by the terms and conditions of this License Agreement.
+
+        ACCEPT
+
+
+CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
+--------------------------------------------------
+
+Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
+The Netherlands.  All rights reserved.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the name of Stichting Mathematisch
+Centrum or CWI not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior
+permission.
+
+STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
+FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
+----------------------------------------------------------------------
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+
+
+
+Additional Conditions for this Windows binary build
+---------------------------------------------------
+
+This program is linked with and uses Microsoft Distributable Code,
+copyrighted by Microsoft Corporation. The Microsoft Distributable Code
+is embedded in each .exe, .dll and .pyd file as a result of running
+the code through a linker.
+
+If you further distribute programs that include the Microsoft
+Distributable Code, you must comply with the restrictions on
+distribution specified by Microsoft. In particular, you must require
+distributors and external end users to agree to terms that protect the
+Microsoft Distributable Code at least as much as Microsoft's own
+requirements for the Distributable Code. See Microsoft's documentation
+(included in its developer tools and on its website at microsoft.com)
+for specific details.
+
+Redistribution of the Windows binary build of the Python interpreter
+complies with this agreement, provided that you do not:
+
+- alter any copyright, trademark or patent notice in Microsoft's
+Distributable Code;
+
+- use Microsoft's trademarks in your programs' names or in a way that
+suggests your programs come from or are endorsed by Microsoft;
+
+- distribute Microsoft's Distributable Code to run on a platform other
+than Microsoft operating systems, run-time technologies or application
+platforms; or
+
+- include Microsoft Distributable Code in malicious, deceptive or
+unlawful programs.
+
+These restrictions apply only to the Microsoft Distributable Code as
+defined above, not to Python itself or any programs running on the
+Python interpreter. The redistribution of the Python interpreter and
+libraries is governed by the Python Software License included with this
+file, or by other licenses as marked.
+
+
+
+--------------------------------------------------------------------------
+
+This program, "bzip2", the associated library "libbzip2", and all
+documentation, are copyright (C) 1996-2019 Julian R Seward.  All
+rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. The origin of this software must not be misrepresented; you must 
+   not claim that you wrote the original software.  If you use this 
+   software in a product, an acknowledgment in the product 
+   documentation would be appreciated but is not required.
+
+3. Altered source versions must be plainly marked as such, and must
+   not be misrepresented as being the original software.
+
+4. The name of the author may not be used to endorse or promote 
+   products derived from this software without specific prior written 
+   permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Julian Seward, jseward@acm.org
+bzip2/libbzip2 version 1.0.8 of 13 July 2019
+
+--------------------------------------------------------------------------
+
+libffi - Copyright (c) 1996-2022  Anthony Green, Red Hat, Inc and others.
+See source files for details.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+``Software''), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        https://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+This software is copyrighted by the Regents of the University of
+California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
+Corporation and other parties.  The following terms apply to all files
+associated with the software unless explicitly disclaimed in
+individual files.
+
+The authors hereby grant permission to use, copy, modify, distribute,
+and license this software and its documentation for any purpose, provided
+that existing copyright notices are retained in all copies and that this
+notice is included verbatim in any distributions. No written agreement,
+license, or royalty fee is required for any of the authorized uses.
+Modifications to this software may be copyrighted by their authors
+and need not follow the licensing terms described here, provided that
+the new terms are clearly indicated on the first page of each file where
+they apply.
+
+IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
+IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+MODIFICATIONS.
+
+GOVERNMENT USE: If you are acquiring this software on behalf of the
+U.S. government, the Government shall have only "Restricted Rights"
+in the software and related documentation as defined in the Federal
+Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
+are acquiring the software on behalf of the Department of Defense, the
+software shall be classified as "Commercial Computer Software" and the
+Government shall have only "Restricted Rights" as defined in Clause
+252.227-7014 (b) (3) of DFARs.  Notwithstanding the foregoing, the
+authors grant the U.S. Government and others acting in its behalf
+permission to use and distribute the software in accordance with the
+terms specified in this license.
+
+This software is copyrighted by the Regents of the University of
+California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
+Corporation, Apple Inc. and other parties.  The following terms apply to
+all files associated with the software unless explicitly disclaimed in
+individual files.
+
+The authors hereby grant permission to use, copy, modify, distribute,
+and license this software and its documentation for any purpose, provided
+that existing copyright notices are retained in all copies and that this
+notice is included verbatim in any distributions. No written agreement,
+license, or royalty fee is required for any of the authorized uses.
+Modifications to this software may be copyrighted by their authors
+and need not follow the licensing terms described here, provided that
+the new terms are clearly indicated on the first page of each file where
+they apply.
+
+IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
+IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+MODIFICATIONS.
+
+GOVERNMENT USE: If you are acquiring this software on behalf of the
+U.S. government, the Government shall have only "Restricted Rights"
+in the software and related documentation as defined in the Federal
+Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
+are acquiring the software on behalf of the Department of Defense, the
+software shall be classified as "Commercial Computer Software" and the
+Government shall have only "Restricted Rights" as defined in Clause
+252.227-7013 (b) (3) of DFARs.  Notwithstanding the foregoing, the
+authors grant the U.S. Government and others acting in its behalf
+permission to use and distribute the software in accordance with the
+terms specified in this license.
+
+Copyright (c) 1993-1999 Ioi Kim Lam.
+Copyright (c) 2000-2001 Tix Project Group.
+Copyright (c) 2004 ActiveState
+
+This software is copyrighted by the above entities
+and other parties.  The following terms apply to all files associated
+with the software unless explicitly disclaimed in individual files.
+
+The authors hereby grant permission to use, copy, modify, distribute,
+and license this software and its documentation for any purpose, provided
+that existing copyright notices are retained in all copies and that this
+notice is included verbatim in any distributions. No written agreement,
+license, or royalty fee is required for any of the authorized uses.
+Modifications to this software may be copyrighted by their authors
+and need not follow the licensing terms described here, provided that
+the new terms are clearly indicated on the first page of each file where
+they apply.
+
+IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
+IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+MODIFICATIONS.
+
+GOVERNMENT USE: If you are acquiring this software on behalf of the
+U.S. government, the Government shall have only "Restricted Rights"
+in the software and related documentation as defined in the Federal 
+Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
+are acquiring the software on behalf of the Department of Defense, the
+software shall be classified as "Commercial Computer Software" and the
+Government shall have only "Restricted Rights" as defined in Clause
+252.227-7013 (c) (1) of DFARs.  Notwithstanding the foregoing, the
+authors grant the U.S. Government and others acting in its behalf
+permission to use and distribute the software in accordance with the
+terms specified in this license. 
+
+----------------------------------------------------------------------
+
+Parts of this software are based on the Tcl/Tk software copyrighted by
+the Regents of the University of California, Sun Microsystems, Inc.,
+and other parties. The original license terms of the Tcl/Tk software
+distribution is included in the file docs/license.tcltk.
+
+Parts of this software are based on the HTML Library software
+copyrighted by Sun Microsystems, Inc. The original license terms of
+the HTML Library software distribution is included in the file
+docs/license.html_lib.
+

BIN
python/x64/py/_asyncio.pyd


BIN
python/x64/py/_bz2.pyd


BIN
python/x64/py/_ctypes.pyd


BIN
python/x64/py/_decimal.pyd


BIN
python/x64/py/_elementtree.pyd


BIN
python/x64/py/_hashlib.pyd


BIN
python/x64/py/_lzma.pyd


BIN
python/x64/py/_msi.pyd


BIN
python/x64/py/_multiprocessing.pyd


BIN
python/x64/py/_overlapped.pyd


BIN
python/x64/py/_queue.pyd


BIN
python/x64/py/_socket.pyd


BIN
python/x64/py/_sqlite3.pyd


BIN
python/x64/py/_ssl.pyd


BIN
python/x64/py/_uuid.pyd


BIN
python/x64/py/_wmi.pyd


BIN
python/x64/py/_zoneinfo.pyd


BIN
python/x64/py/libcrypto-3.dll


BIN
python/x64/py/libffi-8.dll


BIN
python/x64/py/libssl-3.dll


BIN
python/x64/py/pyexpat.pyd


BIN
python/x64/py/python.cat


BIN
python/x64/py/python.exe


BIN
python/x64/py/python3.dll


+ 5 - 0
python/x64/py/python312._pth

@@ -0,0 +1,5 @@
+python312.zip
+.
+
+# Uncomment to run site.main() automatically
+#import site

BIN
python/x64/py/python312.dll


BIN
python/x64/py/python312.zip


BIN
python/x64/py/pythonw.exe


BIN
python/x64/py/select.pyd


BIN
python/x64/py/sqlite3.dll


BIN
python/x64/py/unicodedata.pyd


BIN
python/x64/py/vcruntime140.dll


BIN
python/x64/py/vcruntime140_1.dll


BIN
python/x64/py/winsound.pyd


+ 279 - 0
python/x64/python-enviroment-install.py

@@ -0,0 +1,279 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Python 依赖安装和同步脚本
+功能:检查、安装 Python 依赖到虚拟环境,然后同步所有已安装的包到 environment.txt
+"""
+
+import os
+import sys
+import subprocess
+import platform
+from pathlib import Path
+
+# 脚本所在目录即本环境目录(python/arm64 或 python/x64),venv 固定为 env
+SCRIPT_DIR = Path(__file__).parent.absolute()
+PROJECT_ROOT = SCRIPT_DIR.parent.parent.absolute()
+_env_path = os.environ.get("PYTHON_VENV_PATH", "").strip()
+VENV_PATH = Path(_env_path) if _env_path else SCRIPT_DIR / "env"
+ENVIRONMENT_FILE = SCRIPT_DIR / "environment.txt"
+REQUIREMENTS_FILE = PROJECT_ROOT / "requirements.txt"
+
+# 根据操作系统确定虚拟环境的 Python 和 pip 路径
+if platform.system() == "Windows":
+    VENV_PYTHON = VENV_PATH / "Scripts" / "python.exe"
+    VENV_PIP = VENV_PATH / "Scripts" / "pip.exe"
+else:
+    VENV_PYTHON = VENV_PATH / "bin" / "python"
+    VENV_PIP = VENV_PATH / "bin" / "pip"
+
+
+def run_command(cmd, check=True, capture_output=True):
+    """运行命令并返回结果"""
+    try:
+        result = subprocess.run(
+            cmd,
+            shell=True,
+            check=check,
+            capture_output=capture_output,
+            text=True,
+            encoding='utf-8'
+        )
+        return result.returncode == 0, result.stdout, result.stderr
+    except subprocess.CalledProcessError as e:
+        return False, e.stdout if hasattr(e, 'stdout') else "", str(e)
+
+
+def ensure_venv():
+    """确保虚拟环境存在"""
+    if not VENV_PATH.exists():
+        print("[WARN] Virtual environment not found, creating...")
+        success, _, error = run_command(f'python -m venv "{VENV_PATH}"', check=False)
+        if not success:
+            print(f"[X] Failed to create virtual environment: {error}")
+            sys.exit(1)
+        print("[OK] Virtual environment created successfully")
+    return True
+
+
+def get_venv_pip():
+    """获取虚拟环境的 pip 命令"""
+    if platform.system() == "Windows":
+        return str(VENV_PIP)
+    else:
+        return str(VENV_PIP)
+
+
+def read_dependencies(source_file):
+    """读取依赖列表"""
+    if not source_file.exists():
+        return []
+    
+    dependencies = []
+    with open(source_file, 'r', encoding='utf-8') as f:
+        for line in f:
+            line = line.strip()
+            # 跳过注释和空行
+            if line and not line.startswith('#'):
+                dependencies.append(line)
+    return dependencies
+
+
+def get_installed_packages_from_filesystem():
+    """直接从文件系统获取已安装的包列表(快速方法)"""
+    if platform.system() == "Windows":
+        site_packages = VENV_PATH / "Lib" / "site-packages"
+    else:
+        # Linux/Mac: 需要找到 site-packages 路径
+        import sysconfig
+        site_packages = Path(sysconfig.get_path('purelib', vars={'base': str(VENV_PATH)}))
+    
+    installed_packages = set()
+    
+    if site_packages.exists():
+        for item in site_packages.iterdir():
+            if item.is_dir():
+                pkg_name = item.name
+                
+                # 处理 .dist-info 和 .egg-info 文件夹(最准确的包名来源)
+                if pkg_name.endswith('.dist-info'):
+                    # 从 dist-info 文件夹名提取包名(格式:package-name-version.dist-info)
+                    parts = pkg_name.replace('.dist-info', '').rsplit('-', 1)
+                    if len(parts) >= 1:
+                        installed_packages.add(parts[0].lower().replace('_', '-'))
+                elif pkg_name.endswith('.egg-info'):
+                    # 从 egg-info 文件夹名提取包名
+                    parts = pkg_name.replace('.egg-info', '').rsplit('-', 1)
+                    if len(parts) >= 1:
+                        installed_packages.add(parts[0].lower().replace('_', '-'))
+                elif pkg_name not in ['__pycache__', 'dist-info', 'egg-info']:
+                    # 检查是否是 Python 包(有 __init__.py 或 .py 文件)
+                    if (item / "__init__.py").exists() or any(item.glob("*.py")):
+                        pkg_lower = pkg_name.lower()
+                        installed_packages.add(pkg_lower)
+                        # 添加下划线和连字符的变体
+                        installed_packages.add(pkg_lower.replace('_', '-'))
+                        installed_packages.add(pkg_lower.replace('-', '_'))
+        
+        # 特殊映射:opencv-python 安装后显示为 cv2
+        if 'cv2' in installed_packages:
+            installed_packages.add('opencv-python')
+            installed_packages.add('opencv-contrib-python')
+            installed_packages.add('opencv-python-headless')
+    
+    return installed_packages
+
+
+def check_package_installed(package_name, installed_packages_set=None):
+    """检查包是否已安装(使用文件系统快速检查)"""
+    # 提取包名(支持 ==, >=, <=, >, <, ~= 等版本操作符)
+    pkg_name = package_name.split('==')[0].split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].split('~=')[0].strip()
+    pkg_name_lower = pkg_name.lower()
+    
+    # 如果没有提供已安装包集合,则获取一次(避免重复调用)
+    if installed_packages_set is None:
+        installed_packages_set = get_installed_packages_from_filesystem()
+    
+    # 快速检查(使用已获取的集合)
+    return (
+        pkg_name_lower in installed_packages_set or
+        pkg_name_lower.replace('-', '_') in installed_packages_set or
+        pkg_name_lower.replace('_', '-') in installed_packages_set
+    )
+
+
+def install_packages(packages, source_file, venv_pip):
+    """安装包到虚拟环境"""
+    failed_packages = []
+    
+    if source_file == REQUIREMENTS_FILE and REQUIREMENTS_FILE.exists():
+        # 使用 requirements.txt 批量安装
+        cmd = f'"{venv_pip}" install -r "{source_file}"'
+        success, _, error = run_command(cmd, check=False)
+        if not success:
+            print(f"[X] Installation failed: {error}")
+            return False, failed_packages
+    else:
+        # 逐个安装
+        for package in packages:
+            cmd = f'"{venv_pip}" install {package}'
+            success, _, error = run_command(cmd, check=False)
+            if not success:
+                print(f"[X] Failed to install: {package}")
+                failed_packages.append(package)
+    
+    if failed_packages:
+        return False, failed_packages
+    return True, []
+
+
+def sync_environment_file(venv_pip, silent=False):
+    """同步所有已安装的包到 environment.txt"""
+    cmd = f'"{venv_pip}" freeze'
+    success, output, error = run_command(cmd, check=False)
+    
+    if not success:
+        if not silent:
+            print(f"[X] Failed to get installed packages list: {error}")
+        return False
+    
+    # 使用 UTF-8 无 BOM 编码写入文件
+    with open(ENVIRONMENT_FILE, 'w', encoding='utf-8', newline='\n') as f:
+        f.write(output)
+    
+    if not silent:
+        package_count = len([line for line in output.strip().split('\n') if line.strip()])
+        print(f"[OK] All installed packages synced to {ENVIRONMENT_FILE}")
+        print(f"      Total packages: {package_count}")
+    return True
+
+
+def main():
+    """主函数"""
+    # 确保虚拟环境存在
+    if not ensure_venv():
+        sys.exit(1)
+    
+    venv_pip = get_venv_pip()
+    
+    # 确定依赖源文件(优先使用 requirements.txt,如果没有则使用 environment.txt)
+    if REQUIREMENTS_FILE.exists():
+        source_file = REQUIREMENTS_FILE
+    elif ENVIRONMENT_FILE.exists():
+        source_file = ENVIRONMENT_FILE
+    else:
+        sync_environment_file(venv_pip)
+        sys.exit(0)
+    
+    # 读取依赖列表
+    required_packages = read_dependencies(source_file)
+    
+    if not required_packages:
+        print("[OK] No dependencies specified")
+        sys.exit(0)
+    
+    # 快速检查缺失的依赖(使用文件系统)
+    missing_packages = []
+    installed_count = 0
+    missing_count = 0
+    
+    # 一次性获取所有已安装的包(只检查一次文件系统)
+    installed_packages_set = get_installed_packages_from_filesystem()
+    
+    for package in required_packages:
+        package_line = package.strip()
+        if not package_line:
+            continue
+        
+        # 提取包名
+        package_name = package_line.split('==')[0].split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].split('~=')[0].strip()
+        pkg_name_lower = package_name.lower()
+        
+        # 快速检查(使用已获取的集合)
+        is_installed = (
+            pkg_name_lower in installed_packages_set or
+            pkg_name_lower.replace('-', '_') in installed_packages_set or
+            pkg_name_lower.replace('_', '-') in installed_packages_set
+        )
+        
+        if is_installed:
+            installed_count += 1
+        else:
+            missing_packages.append(package_line)
+            missing_count += 1
+    
+    # 如果有缺失的依赖,显示必要信息并安装
+    if missing_count > 0:
+        print(f"[X] Missing {missing_count} package(s) out of {len(required_packages)}")
+        
+        print("Missing packages:")
+        for missing in missing_packages:
+            print(f"  - {missing}")
+        
+        print("\nInstalling missing packages...")
+        
+        success, failed = install_packages(missing_packages, source_file, venv_pip)
+        if success:
+            print("[OK] All packages installed successfully")
+        else:
+            if failed:
+                print(f"[X] Failed to install {len(failed)} package(s):")
+                for pkg in failed:
+                    print(f"  - {pkg}")
+            else:
+                print("[X] Some packages installation failed")
+            # 即使有失败,也继续同步已安装的包
+            print("[WARN] Continuing to sync installed packages...")
+        
+        # 同步所有已安装的包到 environment.txt
+        sync_environment_file(venv_pip)
+    else:
+        # 所有依赖都齐全时,只显示一行信息(包含同步结果)
+        sync_environment_file(venv_pip, silent=True)
+        print(f"[OK] All dependencies are installed ({len(required_packages)} packages)")
+    
+    sys.exit(0)
+
+
+if __name__ == "__main__":
+    main()

+ 33 - 0
python/x64/update-enviroment-list.bat

@@ -0,0 +1,33 @@
+@echo off
+chcp 65001 >nul
+title Update Python Environment List
+
+cd /d "%~dp0"
+
+REM 环境目录(python/arm64 或 python/x64),优先使用 env\Scripts\python.exe
+for /f "delims=" %%i in ('node "%~dp0..\..\configs\get-python-env-path.js" 2^>nul') do set "ENV_BASE=%%i"
+if not defined ENV_BASE set "ENV_BASE=%~dp0"
+set "PYTHON_EXE="
+if exist "%ENV_BASE%\env\Scripts\python.exe" set "PYTHON_EXE=%ENV_BASE%\env\Scripts\python.exe"
+if not defined PYTHON_EXE if exist "%ENV_BASE%\Scripts\python.exe" set "PYTHON_EXE=%ENV_BASE%\Scripts\python.exe"
+if not defined PYTHON_EXE if exist "%ENV_BASE%\py\python.exe" set "PYTHON_EXE=%ENV_BASE%\py\python.exe"
+if not defined PYTHON_EXE (
+    echo [ERROR] Python not found under %ENV_BASE%
+    pause
+    exit /b 1
+)
+
+"%PYTHON_EXE%" "%~dp0update-environment-list.py"
+
+if errorlevel 1 (
+    echo.
+    echo [ERROR] Update failed
+    echo.
+    pause
+    exit /b 1
+) else (
+    echo.
+    echo [OK] Update completed
+    echo.
+    pause
+)

+ 178 - 0
python/x64/update-environment-list.py

@@ -0,0 +1,178 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Python 环境列表更新脚本
+功能:对比虚拟环境中已安装的包和 environment.txt,如果不一致则更新 environment.txt
+"""
+
+import os
+import sys
+import subprocess
+import platform
+from pathlib import Path
+
+# 脚本所在目录即本环境目录,venv 固定为 env
+SCRIPT_DIR = Path(__file__).parent.absolute()
+_exe = Path(sys.executable).resolve()
+if "Scripts" in _exe.parts or "bin" in _exe.parts:
+    VENV_PATH = _exe.parent.parent
+else:
+    VENV_PATH = SCRIPT_DIR / "env"
+ENVIRONMENT_FILE = SCRIPT_DIR / "environment.txt"
+
+# 根据操作系统确定虚拟环境的 pip 路径
+if platform.system() == "Windows":
+    VENV_PIP = VENV_PATH / "Scripts" / "pip.exe"
+else:
+    VENV_PIP = VENV_PATH / "bin" / "pip"
+
+
+def run_command(cmd, check=True, capture_output=True):
+    """运行命令并返回结果"""
+    try:
+        result = subprocess.run(
+            cmd,
+            shell=True,
+            check=check,
+            capture_output=capture_output,
+            text=True,
+            encoding='utf-8'
+        )
+        return result.returncode == 0, result.stdout, result.stderr
+    except subprocess.CalledProcessError as e:
+        return False, e.stdout if hasattr(e, 'stdout') else "", str(e)
+
+
+def get_installed_packages():
+    """获取虚拟环境中已安装的包列表(使用 pip freeze)"""
+    if not VENV_PATH.exists():
+        print("[ERROR] Virtual environment not found")
+        return None
+    
+    cmd = f'"{VENV_PIP}" freeze'
+    success, output, error = run_command(cmd, check=False)
+    
+    if not success:
+        print(f"[ERROR] Failed to get installed packages: {error}")
+        return None
+    
+    # 解析输出,提取包名和版本
+    packages = {}
+    for line in output.strip().split('\n'):
+        line = line.strip()
+        if line and not line.startswith('#'):
+            # 格式:package==version 或 package>=version 等
+            if '==' in line:
+                parts = line.split('==', 1)
+                packages[parts[0].strip().lower()] = line.strip()
+            else:
+                # 如果没有版本号,使用整行
+                pkg_name = line.split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].split('~=')[0].strip()
+                packages[pkg_name.lower()] = line.strip()
+    
+    return packages
+
+
+def read_environment_file():
+    """读取 environment.txt 中的包列表"""
+    if not ENVIRONMENT_FILE.exists():
+        print(f"[WARN] {ENVIRONMENT_FILE} not found, will create new one")
+        return {}
+    
+    packages = {}
+    with open(ENVIRONMENT_FILE, 'r', encoding='utf-8') as f:
+        for line in f:
+            line = line.strip()
+            if line and not line.startswith('#'):
+                # 提取包名(支持各种版本操作符)
+                if '==' in line:
+                    parts = line.split('==', 1)
+                    packages[parts[0].strip().lower()] = line.strip()
+                else:
+                    pkg_name = line.split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].split('~=')[0].strip()
+                    packages[pkg_name.lower()] = line.strip()
+    
+    return packages
+
+
+def compare_and_update():
+    """对比并更新 environment.txt"""
+    print("Comparing installed packages with environment.txt...")
+    print("=" * 60)
+    
+    # 获取已安装的包
+    installed_packages = get_installed_packages()
+    if installed_packages is None:
+        sys.exit(1)
+    
+    # 读取 environment.txt 中的包
+    file_packages = read_environment_file()
+    
+    # 对比差异
+    installed_set = set(installed_packages.keys())
+    file_set = set(file_packages.keys())
+    
+    added_packages = installed_set - file_set
+    removed_packages = file_set - installed_set
+    changed_packages = []
+    
+    # 检查版本变化
+    for pkg_name in installed_set & file_set:
+        if installed_packages[pkg_name] != file_packages[pkg_name]:
+            changed_packages.append(pkg_name)
+    
+    # 显示差异
+    if added_packages:
+        print(f"\n[+] Added packages ({len(added_packages)}):")
+        for pkg in sorted(added_packages):
+            print(f"  + {installed_packages[pkg]}")
+    
+    if removed_packages:
+        print(f"\n[-] Removed packages ({len(removed_packages)}):")
+        for pkg in sorted(removed_packages):
+            print(f"  - {file_packages[pkg]}")
+    
+    if changed_packages:
+        print(f"\n[~] Changed packages ({len(changed_packages)}):")
+        for pkg in sorted(changed_packages):
+            print(f"  ~ {file_packages[pkg]} -> {installed_packages[pkg]}")
+    
+    # 判断是否需要更新
+    if not added_packages and not removed_packages and not changed_packages:
+        print("\n[OK] environment.txt is up to date")
+        print(f"     Total packages: {len(installed_packages)}")
+        return True
+    
+    # 更新 environment.txt
+    print(f"\nUpdating {ENVIRONMENT_FILE}...")
+    
+    # 使用 pip freeze 获取完整列表(包含所有依赖)
+    cmd = f'"{VENV_PIP}" freeze'
+    success, output, error = run_command(cmd, check=False)
+    
+    if not success:
+        print(f"[ERROR] Failed to get installed packages: {error}")
+        sys.exit(1)
+    
+    # 写入文件(使用 UTF-8 无 BOM 编码)
+    with open(ENVIRONMENT_FILE, 'w', encoding='utf-8', newline='\n') as f:
+        f.write(output)
+    
+    # 统计包数量
+    package_count = len([line for line in output.strip().split('\n') if line.strip()])
+    
+    print(f"[OK] {ENVIRONMENT_FILE} updated successfully")
+    print(f"     Total packages: {package_count}")
+    
+    return True
+
+
+def main():
+    """主函数"""
+    if not compare_and_update():
+        sys.exit(1)
+    sys.exit(0)
+
+
+if __name__ == "__main__":
+    main()

+ 1 - 1
static/process/RedNoteAIThumbsUp/process.json

@@ -6,7 +6,7 @@
 	},
 	"execute": [
 		{
-			"type": "image-center-location",
+			"type": "img-center-point-location",
 			"inVars": ["点赞按钮_未点赞.png"],
 			"outVars": ["{sendBtnPos}"]
 		},

Некоторые файлы не были показаны из-за большого количества измененных файлов