diff --git a/CODEWHALE.md b/CODEWHALE.md index b809124..9bef495 100644 --- a/CODEWHALE.md +++ b/CODEWHALE.md @@ -1,6 +1,6 @@ # Codewhale 一键安装和 LMM 配置 -需要 Node.js 22+ 和 npm。Bash 入口单独下载时还需要 curl。脚本不代装 Node,不改 npm registry,不使用 sudo 或 `--force`。安装使用官方的 `npm install --global codewhale@latest`;二进制选择和校验仍由官方 npm 安装器负责。LMM 适配器安装固定 Git 提交 `8c78be0f936fb8f508badabc0195cdb21442a75d`,禁用其 npm 生命周期脚本;不依赖尚未发布的 npm 包。 +需要 Node.js 22+ 和 npm。Bash 入口单独下载时还需要 curl。脚本不代装 Node,不改 npm registry,不使用 sudo 或 `--force`。安装使用官方的 `npm install --global codewhale@latest`;二进制选择和校验仍由官方 npm 安装器负责。LMM 适配器安装固定 Git 提交 `adad64b7ac77ec997b5be4662c6b6ccf36998357`,禁用其 npm 生命周期脚本;不依赖尚未发布的 npm 包。 ## 安装并配置 @@ -68,3 +68,5 @@ Node 测试使用模拟 npm、适配器和原生程序;菜单测试实际分 新增 CI 在 Ubuntu、macOS、Windows 运行离线测试,并使用隔离的 npm prefix 做真实下载安装和无账号启动检查;另覆盖 Windows PowerShell 5.1。CI 结果以对应提交为准。以上都不代替生产账号 OAuth、真实推理和 Termux 真机验收。 接口依据:[官方安装文档](https://github.com/Hmbown/Codewhale/blob/1554ac11846bc3e1423b0ee66591eea2870f10b1/docs/INSTALL.md)、[官方 npm 原生程序解析](https://github.com/Hmbown/Codewhale/blob/1554ac11846bc3e1423b0ee66591eea2870f10b1/npm/codewhale/scripts/run.js)、[LMM 适配器](https://github.com/TokenNotIncluded/codewhale-lmm-provider)。 + +新版 Codewhale 的 `--profile` 不能透传覆盖 LMM 临时配置;原生 `-p`(提示词)和 `-c`(继续会话)保持可用。Bash 与 PowerShell 的独立下载入口固定到同一份助手提交,并校验相同 SHA-256。 diff --git a/codewhale.mjs b/codewhale.mjs index 165f079..a581a46 100755 --- a/codewhale.mjs +++ b/codewhale.mjs @@ -7,7 +7,7 @@ import { createRequire } from 'node:module'; import { createInterface } from 'node:readline/promises'; import { pathToFileURL } from 'node:url'; -export const PROVIDER_REV = '8c78be0f936fb8f508badabc0195cdb21442a75d'; +export const PROVIDER_REV = 'adad64b7ac77ec997b5be4662c6b6ccf36998357'; export const PROVIDER_URL = `https://github.com/TokenNotIncluded/codewhale-lmm-provider/archive/${PROVIDER_REV}.tar.gz`; const require = createRequire(import.meta.url); const actions = ['setup', 'install', 'login', 'run', 'models', 'status', 'balance', 'usage', 'logout', 'doctor', 'menu', 'help']; @@ -49,8 +49,8 @@ export function parseArguments(argv) { if (!actions.includes(result.action)) throw new Error('Unknown action. Use --help.'); if (result.noBrowser && !['login', 'setup'].includes(result.action)) throw new Error('--no-browser is only valid with login/setup.'); if ((result.model || result.forwarded.length) && result.action !== 'run') throw new Error('--model and forwarded arguments are only valid with run.'); - if (result.forwarded.some(arg => /^--(?:provider|model|base-url|api-key|config|config-path)(?:=|$)/.test(arg))) { - throw new Error('Do not override the LMM provider, model, configuration or credentials after --.'); + if (result.forwarded.some(arg => /^--(?:provider|model|base-url|api-key|config|config-path|profile)(?:=|$)/.test(arg))) { + throw new Error('Do not override the LMM provider, model, configuration, profile or credentials after --.'); } return result; } diff --git a/codewhale.ps1 b/codewhale.ps1 index 470d0f3..df13dc1 100644 --- a/codewhale.ps1 +++ b/codewhale.ps1 @@ -19,8 +19,8 @@ try { $work = Join-Path ([IO.Path]::GetTempPath()) ('lmm-codewhale-' + [guid]::NewGuid()) New-Item -ItemType Directory -Path $work | Out-Null $helper = Join-Path $work 'codewhale.mjs' - Invoke-WebRequest -UseBasicParsing 'https://raw.githubusercontent.com/TokenNotIncluded/lmm-scripts/354a0e7e8597957b33f5d4f4afcf9ac7ebfe8693/codewhale.mjs' -OutFile $helper - if ((Get-FileHash -LiteralPath $helper -Algorithm SHA256).Hash -ne 'd985a055e9f5a95ad8b358d3569f4a9d33a1eaca52dffa39c4b8aa2268a3bbf1') { + Invoke-WebRequest -UseBasicParsing 'https://raw.githubusercontent.com/TokenNotIncluded/lmm-scripts/319d9790ba026583ea226fb92b13b366b738c3ef/codewhale.mjs' -OutFile $helper + if ((Get-FileHash -LiteralPath $helper -Algorithm SHA256).Hash -ne '91dcff456f04abb0ef0ff7f95742ff3d332bfb2c93b4b067f4d778008556f69c') { throw 'Codewhale setup script checksum mismatch; nothing was executed.' } } diff --git a/codewhale.sh b/codewhale.sh index bac5ef3..e518b1d 100755 --- a/codewhale.sh +++ b/codewhale.sh @@ -10,6 +10,6 @@ temp_root=${TMPDIR:-/tmp} if [[ -n ${TERMUX_VERSION:-} || ${PREFIX:-} == */com.termux/files/usr ]]; then temp_root=${TMPDIR:-${PREFIX:-/data/data/com.termux/files/usr}/tmp}; fi work=$(mktemp -d "$temp_root/lmm-codewhale.XXXXXX") trap 'rm -rf -- "$work"' EXIT -curl --proto '=https' --proto-redir '=https' -fsSL 'https://raw.githubusercontent.com/TokenNotIncluded/lmm-scripts/354a0e7e8597957b33f5d4f4afcf9ac7ebfe8693/codewhale.mjs' -o "$work/codewhale.mjs" -node -e 'const fs=require("node:fs"),crypto=require("node:crypto");if(crypto.createHash("sha256").update(fs.readFileSync(process.argv[1])).digest("hex")!==process.argv[2]){console.error("Codewhale setup script checksum mismatch; nothing was executed.");process.exit(1)}' "$work/codewhale.mjs" 'd985a055e9f5a95ad8b358d3569f4a9d33a1eaca52dffa39c4b8aa2268a3bbf1' +curl --proto '=https' --proto-redir '=https' -fsSL 'https://raw.githubusercontent.com/TokenNotIncluded/lmm-scripts/319d9790ba026583ea226fb92b13b366b738c3ef/codewhale.mjs' -o "$work/codewhale.mjs" +node -e 'const fs=require("node:fs"),crypto=require("node:crypto");if(crypto.createHash("sha256").update(fs.readFileSync(process.argv[1])).digest("hex")!==process.argv[2]){console.error("Codewhale setup script checksum mismatch; nothing was executed.");process.exit(1)}' "$work/codewhale.mjs" '91dcff456f04abb0ef0ff7f95742ff3d332bfb2c93b4b067f4d778008556f69c' node "$work/codewhale.mjs" "$@" diff --git a/test-codewhale.mjs b/test-codewhale.mjs index c4b3ebf..b711293 100644 --- a/test-codewhale.mjs +++ b/test-codewhale.mjs @@ -65,7 +65,7 @@ test('unknown commands/options, missing values and cross-action flags fail close } }); test('provider, credential and config overrides cannot be forwarded', () => { - for (const flag of ['--api-key=secret', '--provider', '--config-path', '--model=other', '--base-url=x']) { + for (const flag of ['--api-key=secret', '--provider', '--config-path', '--model=other', '--base-url=x', '--profile', '--profile=personal']) { assert.throws(() => parseArguments(['run', '--', flag])); } }); @@ -240,7 +240,7 @@ process.exit(Number(process.env.TEST_CURL_EXIT||0));\n`; const result = f.runShell('--help'); assert.equal(result.status, 0, result.stderr); assert.match(result.stdout, /Codewhale \+ LMM/); - assert.ok(f.calls()[0][1].some(arg => /354a0e7e8597957b33f5d4f4afcf9ac7ebfe8693\/codewhale.mjs/.test(arg))); + assert.ok(f.calls()[0][1].some(arg => /319d9790ba026583ea226fb92b13b366b738c3ef\/codewhale.mjs/.test(arg))); }); test('truncated downloads fail before execution even when bytes were written', t => { const f = shellFixture(t); f.env.TEST_BAD_DOWNLOAD = '1'; f.env.TEST_CURL_EXIT = '18'; @@ -264,3 +264,7 @@ process.exit(Number(process.env.TEST_CURL_EXIT||0));\n`; assert.deepEqual(f.calls(), [['local', ['run', '--model', id, '--', 'exec', task]]]); }); } + +test('native prompt and continue short flags are not mistaken for route overrides', () => { + assert.deepEqual(parseArguments(['run', '--', '-p', 'hello', '-c']).forwarded, ['-p', 'hello', '-c']); +});