mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 02:11:28 +00:00
fix: harden droid runtime alias resolution
This commit is contained in:
@@ -194,24 +194,24 @@ ccs api export glm --out ./glm.ccs-profile.json # Export for cross-device trans
|
||||
ccs api import ./glm.ccs-profile.json # Import exported profile bundle
|
||||
```
|
||||
|
||||
### Runtime Aliases (`argv[0]` pattern)
|
||||
### Runtime Aliases (built-in bins / `argv[0]` pattern)
|
||||
|
||||
Built-in Droid runtime aliases:
|
||||
Built-in Droid runtime aliases are installed with the package:
|
||||
|
||||
```bash
|
||||
ln -s "$(command -v ccs)" /usr/local/bin/ccs-droid
|
||||
ln -s "$(command -v ccs)" /usr/local/bin/ccsd
|
||||
ccs-droid glm # explicit alias
|
||||
ccsd glm # legacy shortcut
|
||||
```
|
||||
|
||||
Need additional alias names? Use `CCS_TARGET_ALIASES` (preferred) or keep using
|
||||
legacy `CCS_DROID_ALIASES`:
|
||||
Need additional alias names? First create the matching symlink or another launcher that
|
||||
preserves the invoked basename, then map that name with `CCS_TARGET_ALIASES` (preferred) or legacy
|
||||
`CCS_DROID_ALIASES`:
|
||||
|
||||
```bash
|
||||
CCS_TARGET_ALIASES='droid=mydroid,team-droid'
|
||||
ln -s "$(command -v ccs)" /usr/local/bin/mydroid
|
||||
CCS_TARGET_ALIASES='droid=mydroid'
|
||||
# Legacy fallback still supported:
|
||||
CCS_DROID_ALIASES='mydroid,team-droid'
|
||||
CCS_DROID_ALIASES='mydroid'
|
||||
```
|
||||
|
||||
For Factory BYOK compatibility, CCS also stores a per-profile Droid provider hint
|
||||
|
||||
@@ -86,7 +86,7 @@ Spawn Target Process
|
||||
- Spawns: `droid -m custom:ccs-<profile> <args>`
|
||||
- Model config includes baseUrl, apiKey, provider
|
||||
|
||||
**Runtime alias pattern (argv[0]-style):**
|
||||
**Runtime alias pattern (built-in bins / argv[0]-style):**
|
||||
|
||||
```
|
||||
ccs → Target: claude (default)
|
||||
|
||||
@@ -355,27 +355,28 @@ ccs --target droid glm
|
||||
### Runtime Alias Pattern
|
||||
|
||||
```bash
|
||||
# Create aliases/symlinks to auto-select droid target
|
||||
ln -s /path/to/ccs /path/to/ccs-droid
|
||||
ln -s /path/to/ccs /path/to/ccsd
|
||||
|
||||
# Usage
|
||||
# Built-in package bin aliases
|
||||
ccs-droid glm
|
||||
→ Target: droid (detected from argv[0])
|
||||
→ Target: droid (forced by runtime alias)
|
||||
→ droid -m custom:ccs-glm "args..."
|
||||
|
||||
# Legacy shortcut still works
|
||||
ccsd glm
|
||||
→ Target: droid (detected from argv[0])
|
||||
→ Target: droid (forced by runtime alias)
|
||||
→ droid -m custom:ccs-glm "args..."
|
||||
```
|
||||
|
||||
On Windows, `ccs-droid.cmd`, `ccsd.cmd`, `ccsd.bat`, `ccsd.ps1`, and `ccsd.exe` wrappers are also recognized.
|
||||
|
||||
Additional alias names can be configured at runtime via `CCS_TARGET_ALIASES`
|
||||
(preferred, `target=alias1,alias2;...`) or legacy `CCS_DROID_ALIASES`
|
||||
(comma-separated). Example:
|
||||
`CCS_TARGET_ALIASES=droid=mydroid,team-droid`
|
||||
Additional alias names can be configured at runtime after you create a matching
|
||||
symlink or another launcher that preserves the invoked basename. Use `CCS_TARGET_ALIASES` (preferred,
|
||||
`target=alias1,alias2;...`) or legacy `CCS_DROID_ALIASES` (comma-separated).
|
||||
Example:
|
||||
|
||||
```bash
|
||||
ln -s /path/to/ccs /path/to/mydroid
|
||||
CCS_TARGET_ALIASES=droid=mydroid
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
+2
-2
@@ -27,8 +27,8 @@
|
||||
"types": "dist/ccs.d.ts",
|
||||
"bin": {
|
||||
"ccs": "dist/ccs.js",
|
||||
"ccs-droid": "dist/ccs.js",
|
||||
"ccsd": "dist/ccs.js"
|
||||
"ccs-droid": "dist/bin/droid-runtime.js",
|
||||
"ccsd": "dist/bin/droid-runtime.js"
|
||||
},
|
||||
"files": [
|
||||
"dist/",
|
||||
|
||||
+33
-20
@@ -4,36 +4,49 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function getExecutablePaths() {
|
||||
const packageJsonPath = path.join(__dirname, '../package.json');
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
const binEntries = packageJson.bin || {};
|
||||
const uniqueRelativePaths = [...new Set(Object.values(binEntries))];
|
||||
|
||||
return uniqueRelativePaths.map((relativePath) => path.join(__dirname, '..', relativePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add shebang to dist/ccs.js and make executable
|
||||
* Add shebangs to all published bin entrypoints and make them executable.
|
||||
* Run after: tsc
|
||||
*/
|
||||
function addShebang() {
|
||||
const ccsPath = path.join(__dirname, '../dist/ccs.js');
|
||||
|
||||
if (!fs.existsSync(ccsPath)) {
|
||||
console.error('[X] dist/ccs.js not found. Run tsc first.');
|
||||
const executablePaths = getExecutablePaths();
|
||||
const missingPaths = executablePaths.filter((executablePath) => !fs.existsSync(executablePath));
|
||||
if (missingPaths.length > 0) {
|
||||
console.error(`[X] Missing built executable(s): ${missingPaths.join(', ')}. Run tsc first.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let content = fs.readFileSync(ccsPath, 'utf8');
|
||||
for (const executablePath of executablePaths) {
|
||||
let content = fs.readFileSync(executablePath, 'utf8');
|
||||
|
||||
// Add shebang if missing
|
||||
if (!content.startsWith('#!/usr/bin/env node')) {
|
||||
content = '#!/usr/bin/env node\n' + content;
|
||||
fs.writeFileSync(ccsPath, content);
|
||||
console.log('[OK] Shebang added to dist/ccs.js');
|
||||
}
|
||||
if (!content.startsWith('#!/usr/bin/env node')) {
|
||||
content = '#!/usr/bin/env node\n' + content;
|
||||
fs.writeFileSync(executablePath, content);
|
||||
console.log(`[OK] Shebang added to ${path.relative(path.join(__dirname, '..'), executablePath)}`);
|
||||
}
|
||||
|
||||
// Make executable (Unix-like systems)
|
||||
if (process.platform !== 'win32') {
|
||||
try {
|
||||
fs.chmodSync(ccsPath, 0o755);
|
||||
console.log('[OK] dist/ccs.js is now executable');
|
||||
} catch (err) {
|
||||
console.warn('[!] Could not chmod dist/ccs.js:', err.message);
|
||||
if (process.platform !== 'win32') {
|
||||
try {
|
||||
fs.chmodSync(executablePath, 0o755);
|
||||
console.log(
|
||||
`[OK] ${path.relative(path.join(__dirname, '..'), executablePath)} is now executable`
|
||||
);
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`[!] Could not chmod ${path.relative(path.join(__dirname, '..'), executablePath)}: ${err.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addShebang();
|
||||
addShebang();
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
process.env.CCS_INTERNAL_ENTRY_TARGET = 'droid';
|
||||
require('../ccs');
|
||||
@@ -3,8 +3,8 @@
|
||||
*
|
||||
* Resolves which CLI target to use based on:
|
||||
* 1. --target flag (highest priority)
|
||||
* 2. Per-profile config
|
||||
* 3. argv[0] detection (busybox/symlink pattern)
|
||||
* 2. Runtime alias entrypoint / argv[0] detection
|
||||
* 3. Per-profile config
|
||||
* 4. Default: 'claude'
|
||||
*/
|
||||
|
||||
@@ -20,14 +20,20 @@ const BUILTIN_ARGV0_TARGET_MAP: Record<string, TargetType> = {
|
||||
ccsd: 'droid',
|
||||
};
|
||||
const ALIAS_NAME_REGEX = /^[a-z0-9._-]+$/;
|
||||
const INTERNAL_ENTRY_TARGET_ENV_VAR = 'CCS_INTERNAL_ENTRY_TARGET';
|
||||
const GENERIC_TARGET_ALIAS_ENV_VAR = 'CCS_TARGET_ALIASES';
|
||||
const LEGACY_TARGET_ALIAS_ENV_VARS: Partial<Record<TargetType, string>> = {
|
||||
droid: 'CCS_DROID_ALIASES',
|
||||
};
|
||||
const RESERVED_BIN_NAMES = new Set<string>(['ccs', ...Object.keys(BUILTIN_ARGV0_TARGET_MAP)]);
|
||||
|
||||
function addAliasToMap(map: Record<string, TargetType>, alias: string, target: TargetType): void {
|
||||
const normalizedAlias = alias.trim().toLowerCase();
|
||||
if (!normalizedAlias || !ALIAS_NAME_REGEX.test(normalizedAlias)) {
|
||||
if (
|
||||
!normalizedAlias ||
|
||||
!ALIAS_NAME_REGEX.test(normalizedAlias) ||
|
||||
RESERVED_BIN_NAMES.has(normalizedAlias)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -87,6 +93,16 @@ function buildArgv0TargetMap(): Record<string, TargetType> {
|
||||
return map;
|
||||
}
|
||||
|
||||
function resolveEntrypointTarget(): TargetType | null {
|
||||
const rawTarget = process.env[INTERNAL_ENTRY_TARGET_ENV_VAR];
|
||||
if (!rawTarget) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalizedTarget = rawTarget.trim().toLowerCase();
|
||||
return isValidTarget(normalizedTarget) ? normalizedTarget : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valid target types for --target flag validation.
|
||||
*/
|
||||
@@ -172,13 +188,12 @@ export function resolveTargetType(
|
||||
return parsed.targetOverride;
|
||||
}
|
||||
|
||||
// 2. Check per-profile config
|
||||
if (profileConfig?.target !== undefined) {
|
||||
return isValidTarget(profileConfig.target) ? profileConfig.target : 'claude';
|
||||
// 2. Check runtime alias entrypoint / argv[0]
|
||||
const entrypointTarget = resolveEntrypointTarget();
|
||||
if (entrypointTarget) {
|
||||
return entrypointTarget;
|
||||
}
|
||||
|
||||
// 3. Check argv[0] (busybox pattern)
|
||||
// Strip common wrapper extensions for Windows shims/wrappers
|
||||
const rawBin = path.basename(process.argv[1] || process.argv0 || '');
|
||||
const binName = rawBin.replace(/\.(cmd|bat|ps1|exe)$/i, '').toLowerCase();
|
||||
const argv0TargetMap = buildArgv0TargetMap();
|
||||
@@ -187,6 +202,11 @@ export function resolveTargetType(
|
||||
return argv0Target;
|
||||
}
|
||||
|
||||
// 3. Check per-profile config
|
||||
if (profileConfig?.target !== undefined) {
|
||||
return isValidTarget(profileConfig.target) ? profileConfig.target : 'claude';
|
||||
}
|
||||
|
||||
// 4. Default
|
||||
return 'claude';
|
||||
}
|
||||
|
||||
@@ -129,7 +129,23 @@ describe('cross-platform', () => {
|
||||
|
||||
assert(packageJson.bin, 'package.json should have bin field');
|
||||
assert(packageJson.bin.ccs, 'bin field should specify ccs command');
|
||||
assert(packageJson.bin['ccs-droid'], 'bin field should specify ccs-droid command');
|
||||
assert(packageJson.bin.ccsd, 'bin field should specify ccsd command');
|
||||
assert.notStrictEqual(
|
||||
packageJson.bin['ccs-droid'],
|
||||
packageJson.bin.ccs,
|
||||
'ccs-droid should use a dedicated runtime entrypoint'
|
||||
);
|
||||
assert.strictEqual(
|
||||
packageJson.bin['ccs-droid'],
|
||||
packageJson.bin.ccsd,
|
||||
'legacy ccsd alias should share the dedicated droid runtime entrypoint'
|
||||
);
|
||||
assert(
|
||||
fs.existsSync(path.join(__dirname, '..', '..', packageJson.bin['ccs-droid'])),
|
||||
'dedicated droid runtime entrypoint should exist'
|
||||
);
|
||||
assert(packageJson.scripts, 'package.json should have scripts field');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ describe('resolveTargetType', () => {
|
||||
const originalArgv = process.argv;
|
||||
const originalDroidAliases = process.env.CCS_DROID_ALIASES;
|
||||
const originalTargetAliases = process.env.CCS_TARGET_ALIASES;
|
||||
const originalInternalEntryTarget = process.env.CCS_INTERNAL_ENTRY_TARGET;
|
||||
|
||||
afterEach(() => {
|
||||
process.argv = originalArgv;
|
||||
@@ -22,6 +23,12 @@ describe('resolveTargetType', () => {
|
||||
} else {
|
||||
process.env.CCS_TARGET_ALIASES = originalTargetAliases;
|
||||
}
|
||||
|
||||
if (originalInternalEntryTarget === undefined) {
|
||||
delete process.env.CCS_INTERNAL_ENTRY_TARGET;
|
||||
} else {
|
||||
process.env.CCS_INTERNAL_ENTRY_TARGET = originalInternalEntryTarget;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return claude as default', () => {
|
||||
@@ -82,12 +89,29 @@ describe('resolveTargetType', () => {
|
||||
expect(resolveTargetType([])).toBe('droid');
|
||||
});
|
||||
|
||||
it('should merge CCS_TARGET_ALIASES and CCS_DROID_ALIASES', () => {
|
||||
process.env.CCS_TARGET_ALIASES = 'droid=team-droid';
|
||||
process.env.CCS_DROID_ALIASES = 'legacy-droid';
|
||||
|
||||
process.argv = ['node', 'team-droid'];
|
||||
expect(resolveTargetType([])).toBe('droid');
|
||||
|
||||
process.argv = ['node', 'legacy-droid'];
|
||||
expect(resolveTargetType([])).toBe('droid');
|
||||
});
|
||||
|
||||
it('should ignore invalid custom alias entries', () => {
|
||||
process.env.CCS_DROID_ALIASES = 'valid_alias,../bad,';
|
||||
process.argv = ['node', '../bad'];
|
||||
expect(resolveTargetType([])).toBe('claude');
|
||||
});
|
||||
|
||||
it('should detect internal entry target for dedicated package bin entrypoints', () => {
|
||||
process.env.CCS_INTERNAL_ENTRY_TARGET = 'droid';
|
||||
process.argv = ['node', 'ccs'];
|
||||
expect(resolveTargetType([])).toBe('droid');
|
||||
});
|
||||
|
||||
it('should normalize argv[0] and custom aliases case-insensitively', () => {
|
||||
process.env.CCS_DROID_ALIASES = 'DroidCaps';
|
||||
process.argv = ['node', 'DROIDCAPS'];
|
||||
@@ -134,9 +158,41 @@ describe('resolveTargetType', () => {
|
||||
expect(resolveTargetType(['--target', 'claude'])).toBe('claude');
|
||||
});
|
||||
|
||||
it('should prioritize profile config over argv[0]', () => {
|
||||
it('should prioritize --target over internal entry target', () => {
|
||||
process.env.CCS_INTERNAL_ENTRY_TARGET = 'droid';
|
||||
process.argv = ['node', 'ccs'];
|
||||
expect(resolveTargetType(['--target', 'claude'])).toBe('claude');
|
||||
});
|
||||
|
||||
it('should prioritize runtime alias over profile config', () => {
|
||||
process.argv = ['node', 'ccsd'];
|
||||
expect(resolveTargetType([], { target: 'claude' })).toBe('claude');
|
||||
expect(resolveTargetType([], { target: 'claude' })).toBe('droid');
|
||||
});
|
||||
|
||||
it('should prioritize internal entry target over profile config', () => {
|
||||
process.env.CCS_INTERNAL_ENTRY_TARGET = 'droid';
|
||||
process.argv = ['node', 'ccs'];
|
||||
expect(resolveTargetType([], { target: 'claude' })).toBe('droid');
|
||||
});
|
||||
|
||||
it('should keep reserved command names authoritative', () => {
|
||||
process.env.CCS_TARGET_ALIASES = 'claude=ccs,ccs-droid,ccsd;droid=mydroid';
|
||||
process.env.CCS_DROID_ALIASES = 'ccs,ccs-droid,ccsd,legacy-droid';
|
||||
|
||||
process.argv = ['node', 'ccs'];
|
||||
expect(resolveTargetType([])).toBe('claude');
|
||||
|
||||
process.argv = ['node', 'ccs-droid'];
|
||||
expect(resolveTargetType([])).toBe('droid');
|
||||
|
||||
process.argv = ['node', 'ccsd'];
|
||||
expect(resolveTargetType([])).toBe('droid');
|
||||
|
||||
process.argv = ['node', 'mydroid'];
|
||||
expect(resolveTargetType([])).toBe('droid');
|
||||
|
||||
process.argv = ['node', 'legacy-droid'];
|
||||
expect(resolveTargetType([])).toBe('droid');
|
||||
});
|
||||
|
||||
it('should throw for invalid --target value', () => {
|
||||
|
||||
@@ -93,8 +93,8 @@ export function ProviderInfoTab({
|
||||
command={`ccs-droid ${provider} "your prompt"`}
|
||||
/>
|
||||
<UsageCommand
|
||||
label={isDroidTarget ? 'Override to Claude' : 'Override target'}
|
||||
command={`ccs ${provider} --target claude "your prompt"`}
|
||||
label={isDroidTarget ? 'Override to Claude' : 'Run on Droid (--target)'}
|
||||
command={`ccs ${provider} --target ${isDroidTarget ? 'claude' : 'droid'} "your prompt"`}
|
||||
/>
|
||||
<UsageCommand label="Change model" command={`ccs ${provider} --config`} />
|
||||
<UsageCommand label="Add account" command={`ccs ${provider} --add`} />
|
||||
|
||||
@@ -103,14 +103,14 @@ export function InfoSection({ profileName, target, data }: InfoSectionProps) {
|
||||
<Label className="text-xs text-muted-foreground">
|
||||
{isDroidTarget
|
||||
? t('profileEditor.overrideToClaude')
|
||||
: t('profileEditor.overrideToClaudeExplicit')}
|
||||
: t('profileEditor.runOnDroidWithFlag')}
|
||||
</Label>
|
||||
<div className="mt-1 flex gap-2">
|
||||
<code className="flex-1 px-2 py-1.5 bg-muted rounded text-xs font-mono truncate">
|
||||
ccs {profileName} --target claude "prompt"
|
||||
ccs {profileName} --target {isDroidTarget ? 'claude' : 'droid'} "prompt"
|
||||
</code>
|
||||
<CopyButton
|
||||
value={`ccs ${profileName} --target claude "prompt"`}
|
||||
value={`ccs ${profileName} --target ${isDroidTarget ? 'claude' : 'droid'} "prompt"`}
|
||||
size="icon"
|
||||
className="h-6 w-6"
|
||||
/>
|
||||
|
||||
@@ -526,23 +526,23 @@ export function ProfileCreateDialog({
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{targetValue === 'droid' ? (
|
||||
<>
|
||||
Preferred explicit alias:{' '}
|
||||
{t('profileEditor.targetHintPreferredAlias')}{' '}
|
||||
<code className="bg-muted px-1 rounded text-[10px]">ccs-droid</code>.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Run with <code className="bg-muted px-1 rounded text-[10px]">ccs</code> by
|
||||
default.
|
||||
{t('profileEditor.targetHintClaudeDefault')}{' '}
|
||||
<code className="bg-muted px-1 rounded text-[10px]">ccs</code>.
|
||||
</>
|
||||
)}
|
||||
{targetValue === 'droid' ? (
|
||||
<>
|
||||
{' '}
|
||||
Legacy <code className="bg-muted px-1 rounded text-[10px]">ccsd</code> still
|
||||
works for Droid.
|
||||
{t('profileEditor.targetHintLegacyAlias')}{' '}
|
||||
<code className="bg-muted px-1 rounded text-[10px]">ccsd</code>.
|
||||
</>
|
||||
) : null}{' '}
|
||||
You can still override each run with{' '}
|
||||
{t('profileEditor.targetHintOverride')}{' '}
|
||||
<code className="bg-muted px-1 rounded text-[10px]">--target</code>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -619,9 +619,14 @@ const resources = {
|
||||
quickUsage: 'Quick Usage',
|
||||
runWithProfile: 'Run with profile',
|
||||
runOnDroid: 'Run on Droid',
|
||||
runOnDroidWithFlag: 'Run on Droid (--target)',
|
||||
droidAliasExplicit: 'Droid alias (explicit)',
|
||||
overrideToClaude: 'Override to Claude',
|
||||
overrideToClaudeExplicit: 'Override to Claude (explicit)',
|
||||
targetHintPreferredAlias: 'Preferred explicit alias:',
|
||||
targetHintClaudeDefault: 'Default command:',
|
||||
targetHintLegacyAlias: 'Legacy shortcut still works:',
|
||||
targetHintOverride: 'You can still override each run with',
|
||||
setAsDefault: 'Set as default',
|
||||
provider: 'Provider',
|
||||
custom: 'Custom',
|
||||
@@ -1790,9 +1795,14 @@ const resources = {
|
||||
quickUsage: '快速使用',
|
||||
runWithProfile: '使用配置运行',
|
||||
runOnDroid: '在 Droid 上运行',
|
||||
runOnDroidWithFlag: '通过 --target 在 Droid 上运行',
|
||||
droidAliasExplicit: 'Droid 别名(显式)',
|
||||
overrideToClaude: '切换为 Claude',
|
||||
overrideToClaudeExplicit: '切换为 Claude(显式)',
|
||||
targetHintPreferredAlias: '推荐显式别名:',
|
||||
targetHintClaudeDefault: '默认命令:',
|
||||
targetHintLegacyAlias: '旧快捷方式仍可用:',
|
||||
targetHintOverride: '你仍可在每次运行时用',
|
||||
setAsDefault: '设为默认',
|
||||
provider: '提供商',
|
||||
custom: '自定义',
|
||||
@@ -2986,9 +2996,14 @@ const resources = {
|
||||
quickUsage: 'Sử dụng nhanh',
|
||||
runWithProfile: 'Chạy với hồ sơ',
|
||||
runOnDroid: 'Chạy trên Droid',
|
||||
runOnDroidWithFlag: 'Chạy trên Droid (--target)',
|
||||
droidAliasExplicit: 'Bí danh Droid (rõ ràng)',
|
||||
overrideToClaude: 'Ghi đè lên Claude',
|
||||
overrideToClaudeExplicit: 'Ghi đè lên Claude (rõ ràng)',
|
||||
targetHintPreferredAlias: 'Bí danh rõ ràng nên dùng:',
|
||||
targetHintClaudeDefault: 'Lệnh mặc định:',
|
||||
targetHintLegacyAlias: 'Lối tắt cũ vẫn dùng được:',
|
||||
targetHintOverride: 'Bạn vẫn có thể ghi đè mỗi lần chạy bằng',
|
||||
setAsDefault: 'Đặt làm mặc định',
|
||||
provider: 'Nhà cung cấp',
|
||||
custom: 'Tùy chỉnh',
|
||||
@@ -4206,9 +4221,14 @@ const resources = {
|
||||
quickUsage: 'クイック実行',
|
||||
runWithProfile: 'このプロファイルで実行',
|
||||
runOnDroid: 'Droid で実行',
|
||||
runOnDroidWithFlag: '--target で Droid 実行',
|
||||
droidAliasExplicit: 'Droid エイリアス(明示)',
|
||||
overrideToClaude: 'Claude に切り替え',
|
||||
overrideToClaudeExplicit: 'Claude に切り替え(明示)',
|
||||
targetHintPreferredAlias: '推奨の明示エイリアス:',
|
||||
targetHintClaudeDefault: 'デフォルトコマンド:',
|
||||
targetHintLegacyAlias: '従来ショートカットも利用可能:',
|
||||
targetHintOverride: '実行ごとに次でも上書きできます',
|
||||
setAsDefault: 'デフォルトに設定',
|
||||
provider: 'プロバイダー',
|
||||
custom: 'カスタム',
|
||||
|
||||
Reference in New Issue
Block a user