Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CODEWHALE.md
Original file line number Diff line number Diff line change
@@ -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 包。

## 安装并配置

Expand Down Expand Up @@ -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。
6 changes: 3 additions & 3 deletions codewhale.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand Down Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions codewhale.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
}
}
Expand Down
4 changes: 2 additions & 2 deletions codewhale.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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" "$@"
8 changes: 6 additions & 2 deletions test-codewhale.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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]));
}
});
Expand Down Expand Up @@ -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';
Expand All @@ -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']);
});
Loading