mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
- triggersApi: 添加 assertSubscriptionBaseUrl 防止 OAuth token 泄露 - claude.ts: 修复流式响应 O(n^2) 字符串拼接,改用数组累积 - claude.ts: 移除未使用的 import,动态 import 改为静态 import - StatusLine: BuiltinStatusLine 仅在 statusLineEnabled 时显示,修复双行问题 - local-vault: 修复 --reveal 标志位置解析 bug - share: 修复 sk-proj-* OpenAI 密钥未脱敏问题 - store.ts: 临时文件改用同目录创建,避免跨文件系统 rename 失败 - store.ts: 添加空字符串 key 校验 - permissionValidation: 端口正则限制为有效 TCP 范围 0-65535 - 测试 mock 补全: schedule/vault/skill-store 测试文件 - 移除过期的 biome-ignore 注释 Co-Authored-By: glm-5-turbo <zai-org@claude-code-best.win>
121 lines
5.1 KiB
TypeScript
121 lines
5.1 KiB
TypeScript
/**
|
||
* Parse the args string for the /local-vault command.
|
||
*
|
||
* Supported sub-commands:
|
||
* list → { action: 'list' }
|
||
* set <key> <value> → { action: 'set', key, value }
|
||
* get <key> → { action: 'get', key, reveal: false }
|
||
* get <key> --reveal → { action: 'get', key, reveal: true }
|
||
* delete <key> → { action: 'delete', key }
|
||
* (empty) → { action: 'list' }
|
||
* anything else → { action: 'invalid', reason }
|
||
*/
|
||
|
||
export type LocalVaultArgs =
|
||
| { action: 'list' }
|
||
| { action: 'set'; key: string; value: string }
|
||
| { action: 'get'; key: string; reveal: boolean }
|
||
| { action: 'delete'; key: string }
|
||
| { action: 'invalid'; reason: string }
|
||
|
||
// Markdown renderer in REPL output treats `<key>` / `<value>` as HTML tags
|
||
// and strips them. Use uppercase placeholder names without angle brackets
|
||
// so the full usage line is visible to users.
|
||
const USAGE =
|
||
'Usage: /local-vault list | set KEY VALUE | get KEY [--reveal] | delete KEY'
|
||
|
||
// M1 fix (codecov-100 audit #4): defensively reject hyphen-like Unicode
|
||
// prefixes on key names. ASCII '-' is the obvious flag prefix, but a key
|
||
// stored as e.g. '−mykey' (U+2212 MINUS SIGN) would round-trip through
|
||
// /local-vault set and then be unretrievable via the CLI because the
|
||
// shell-style tokenizer here is consistent. Reject any key whose first
|
||
// character is in the Unicode hyphen / dash family. List drawn from
|
||
// Unicode general category Pd (Dash_Punctuation) plus the math minus.
|
||
// U+002D HYPHEN-MINUS -
|
||
// U+2010 HYPHEN ‐
|
||
// U+2011 NON-BREAKING HYPHEN ‑
|
||
// U+2012 FIGURE DASH ‒
|
||
// U+2013 EN DASH –
|
||
// U+2014 EM DASH —
|
||
// U+2015 HORIZONTAL BAR ―
|
||
// U+2212 MINUS SIGN −
|
||
// U+FE58 SMALL EM DASH ﹘
|
||
// U+FE63 SMALL HYPHEN-MINUS ﹣
|
||
// U+FF0D FULLWIDTH HYPHEN-MINUS -
|
||
const HYPHEN_LIKE_PREFIX_REGEX = /^[-‐-―−﹘﹣-]/
|
||
|
||
export function parseLocalVaultArgs(args: string): LocalVaultArgs {
|
||
const trimmed = args.trim()
|
||
|
||
if (trimmed === '' || trimmed === 'list') {
|
||
return { action: 'list' }
|
||
}
|
||
|
||
const tokens = trimmed.split(/\s+/)
|
||
const subCmd = tokens[0]
|
||
|
||
// ── list ──────────────────────────────────────────────────────────────────
|
||
if (subCmd === 'list') {
|
||
return { action: 'list' }
|
||
}
|
||
|
||
// ── set ───────────────────────────────────────────────────────────────────
|
||
if (subCmd === 'set') {
|
||
const key = tokens[1]
|
||
if (!key) {
|
||
return { action: 'invalid', reason: `set requires a key name. ${USAGE}` }
|
||
}
|
||
// D3 + M1: reject keys that start with '-' or any hyphen-like Unicode
|
||
// character. ASCII '-' would be mistaken for a flag; non-ASCII hyphen
|
||
// lookalikes (e.g. U+2212 MINUS SIGN) would silently store but then be
|
||
// unretrievable because the user typically can't reproduce the exact
|
||
// codepoint at the shell.
|
||
if (HYPHEN_LIKE_PREFIX_REGEX.test(key)) {
|
||
return {
|
||
action: 'invalid',
|
||
reason: `Key name must not start with "-" or a hyphen-like character (reserved for flags). ${USAGE}`,
|
||
}
|
||
}
|
||
// D4: value is tokens[2..] joined, not substring math (handles keys with repeated substrings)
|
||
const rest = tokens.slice(2).join(' ')
|
||
if (!rest) {
|
||
return {
|
||
action: 'invalid',
|
||
reason: `set requires a value. ${USAGE}`,
|
||
}
|
||
}
|
||
return { action: 'set', key, value: rest }
|
||
}
|
||
|
||
// ── get ───────────────────────────────────────────────────────────────────
|
||
if (subCmd === 'get') {
|
||
// Strip flags before extracting the key so that `get --reveal MY_KEY`
|
||
// correctly resolves MY_KEY as the key rather than --reveal.
|
||
const flags = ['--reveal']
|
||
const argsWithoutFlags = tokens.filter(t => !flags.includes(t))
|
||
const key = argsWithoutFlags[1] // argsWithoutFlags[0] is 'get'
|
||
if (!key) {
|
||
return { action: 'invalid', reason: `get requires a key name. ${USAGE}` }
|
||
}
|
||
const reveal = tokens.includes('--reveal')
|
||
return { action: 'get', key, reveal }
|
||
}
|
||
|
||
// ── delete ────────────────────────────────────────────────────────────────
|
||
if (subCmd === 'delete') {
|
||
const key = tokens[1]
|
||
if (!key) {
|
||
return {
|
||
action: 'invalid',
|
||
reason: `delete requires a key name. ${USAGE}`,
|
||
}
|
||
}
|
||
return { action: 'delete', key }
|
||
}
|
||
|
||
return {
|
||
action: 'invalid',
|
||
reason: `Unknown sub-command "${subCmd}". ${USAGE}`,
|
||
}
|
||
}
|