svharness 0.14.17 → 0.14.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -11
- package/dist/commands/shell-integration.js +22 -28
- package/dist/index.js +4 -4
- package/dist/lib/agent-launcher.js +4 -22
- package/dist/lib/launch-script-path.js +24 -0
- package/dist/lib/ps-codechat-alias.js +183 -0
- package/docs/standalone-codechat-ps1.md +5 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +2 -2
- package/scripts/preuninstall.js +45 -0
- package/templates/codechat/Start-CodeChat.ps1 +498 -359
- package/dist/commands/references-apply-skills.js +0 -47
package/README.md
CHANGED
|
@@ -733,11 +733,13 @@ svharness start-agent --work-dir ~/projects/my-app
|
|
|
733
733
|
|
|
734
734
|
#### 平台说明
|
|
735
735
|
|
|
736
|
-
| 平台 | spawn 模式 | 右键菜单 |
|
|
737
|
-
|
|
736
|
+
| 平台 | spawn 模式 | 右键菜单 / PowerShell 别名 |
|
|
737
|
+
|------|------------|----------------------------|
|
|
738
738
|
| Windows | `shell: true` | 支持(`svharness shell`) |
|
|
739
739
|
| Linux / macOS | `shell: false` | 不支持 |
|
|
740
740
|
|
|
741
|
+
Windows 下首次 `start-agent` 会**尽力**写入 PowerShell `codechat` 函数(与 `shell install` 相同逻辑);若未全局安装 svharness,可手动执行 `svharness shell install`。
|
|
742
|
+
|
|
741
743
|
### 无需 svharness:独立 PowerShell 启动器
|
|
742
744
|
|
|
743
745
|
未安装 npm / svharness 时,可拷贝 [`templates/codechat/Start-CodeChat.ps1`](templates/codechat/Start-CodeChat.ps1) 到项目 `.claude\`,实现相同的 env 同步与 `run.bat` 解析(含 Android Studio / IDEA 插件路径):
|
|
@@ -745,7 +747,12 @@ svharness start-agent --work-dir ~/projects/my-app
|
|
|
745
747
|
```powershell
|
|
746
748
|
Copy-Item .\templates\codechat\Start-CodeChat.ps1 .\.claude\
|
|
747
749
|
.\.claude\Start-CodeChat.ps1 # 启动
|
|
748
|
-
.\.claude\Start-CodeChat.ps1 -Action
|
|
750
|
+
.\.claude\Start-CodeChat.ps1 -Action Install # 可选:独立右键 + 别名
|
|
751
|
+
.\.claude\Start-CodeChat.ps1 -Action InstallMenu # 仅右键
|
|
752
|
+
.\.claude\Start-CodeChat.ps1 -Action InstallAlias # 仅别名
|
|
753
|
+
.\.claude\Start-CodeChat.ps1 -Action UninstallMenu # 反注册独立右键
|
|
754
|
+
.\.claude\Start-CodeChat.ps1 -Action UninstallAlias # 反注册独立别名
|
|
755
|
+
.\.claude\Start-CodeChat.ps1 -Action Status
|
|
749
756
|
```
|
|
750
757
|
|
|
751
758
|
右键菜单使用注册表键 `CodeChatStandaloneAgent`,**不与** `svharness shell install` 冲突;可与 svharness 菜单并存。
|
|
@@ -762,24 +769,74 @@ launch_codechat_cli D:\projects\my-app # Windows
|
|
|
762
769
|
launch_codechat_cli ~/projects/my-app # Linux
|
|
763
770
|
```
|
|
764
771
|
|
|
765
|
-
### `shell` —— Windows
|
|
772
|
+
### `shell` —— Windows 右键菜单与 PowerShell 别名(v0.16+,仅 win32)
|
|
773
|
+
|
|
774
|
+
在资源管理器**文件夹空白处**与**文件夹图标**右键增加「在此启动 CodeChat Agent」,并在 PowerShell `$PROFILE` 写入 `codechat` 函数(等价于 `launch_codechat_cli [workdir]`)。**注册与反注册**对右键菜单、本地 stub、PowerShell 别名**一并**处理。
|
|
766
775
|
|
|
767
|
-
|
|
776
|
+
#### 注册
|
|
768
777
|
|
|
769
778
|
```bash
|
|
770
|
-
svharness shell install #
|
|
771
|
-
svharness shell
|
|
772
|
-
svharness shell status # 查看 stub / 注册表状态
|
|
773
|
-
svharness shell uninstall
|
|
774
|
-
npm uninstall -g svharness
|
|
779
|
+
svharness shell install # 注册右键 + 安装 codechat 别名(幂等)
|
|
780
|
+
svharness shell status # 查看是否已完整安装
|
|
775
781
|
```
|
|
776
782
|
|
|
777
|
-
|
|
783
|
+
注册时会写入:
|
|
784
|
+
|
|
785
|
+
| 项 | 路径 / 键 |
|
|
786
|
+
|----|-----------|
|
|
787
|
+
| 空白处右键 | `HKCU\Software\Classes\Directory\Background\shell\SvharnessLaunchCodeChatAgent` |
|
|
788
|
+
| 文件夹右键 | `HKCU\Software\Classes\Directory\shell\SvharnessLaunchCodeChatAgent` |
|
|
789
|
+
| 启动 stub | `%LOCALAPPDATA%\svharness\bin\launch_codechat_cli.ps1` |
|
|
790
|
+
| 菜单图标 | `%LOCALAPPDATA%\svharness\bin\codechat-agent.ico` |
|
|
791
|
+
| PowerShell 别名 | 当前用户 `$PROFILE` 内 `# >>> codechat >>>` … `# <<< codechat <<<` 块 |
|
|
792
|
+
|
|
793
|
+
`npm install -g svharness` 在 Windows 上 **默认自动** 执行上述注册。跳过:
|
|
778
794
|
|
|
779
795
|
```bash
|
|
780
796
|
SVHARNESS_SKIP_SHELL=1 npm install -g svharness
|
|
781
797
|
```
|
|
782
798
|
|
|
799
|
+
**PowerShell 别名用法**(注册后需**新开** PowerShell 窗口才生效):
|
|
800
|
+
|
|
801
|
+
```powershell
|
|
802
|
+
codechat # 当前目录启动
|
|
803
|
+
codechat D:\projects\my-app # 指定项目根
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
#### 反注册
|
|
807
|
+
|
|
808
|
+
```bash
|
|
809
|
+
svharness shell uninstall # 移除右键、stub、图标与 profile 中的 codechat 块
|
|
810
|
+
svharness shell status # 确认「右键菜单完整 / codechat 别名」均为否
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
反注册时会删除:
|
|
814
|
+
|
|
815
|
+
| 项 | 说明 |
|
|
816
|
+
|----|------|
|
|
817
|
+
| 上述两个 HKCU 注册表键 | 资源管理器右键菜单项消失(无需重启 Explorer) |
|
|
818
|
+
| `%LOCALAPPDATA%\svharness\bin\` | 删除 stub `.ps1` 与 `.ico` |
|
|
819
|
+
| `$PROFILE` 中的 codechat 块 | 移除 `# >>> codechat >>>` … `# <<< codechat <<<` 整段;**不**删除 profile 文件本身 |
|
|
820
|
+
|
|
821
|
+
全局卸载 npm 包时,`preuninstall` 会**尽力**自动反注册(与 `shell uninstall` 相同范围):
|
|
822
|
+
|
|
823
|
+
```bash
|
|
824
|
+
npm uninstall -g svharness
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
若自动反注册失败,可手动执行 `svharness shell uninstall`(包仍在时),或自行检查:
|
|
828
|
+
|
|
829
|
+
- 注册表:`reg query HKCU\Software\Classes\Directory\Background\shell\SvharnessLaunchCodeChatAgent` 应报错「找不到」
|
|
830
|
+
- 目录:`%LOCALAPPDATA%\svharness\bin\` 应不存在或为空
|
|
831
|
+
- profile:打开 `$PROFILE`,确认无 `# >>> codechat >>>` 标记块
|
|
832
|
+
|
|
833
|
+
**注意**:`shell uninstall` **仅**移除 svharness 注册的菜单(键名 `SvharnessLaunchCodeChatAgent`)。若曾用独立脚本 [`Start-CodeChat.ps1`](templates/codechat/Start-CodeChat.ps1) 注册过菜单(键名 `CodeChatStandaloneAgent`),需单独反注册:
|
|
834
|
+
|
|
835
|
+
```powershell
|
|
836
|
+
.\.claude\Start-CodeChat.ps1 -Action UninstallMenu # 独立右键
|
|
837
|
+
.\.claude\Start-CodeChat.ps1 -Action UninstallAlias # 独立别名(若曾 Install / InstallAlias)
|
|
838
|
+
```
|
|
839
|
+
|
|
783
840
|
---
|
|
784
841
|
|
|
785
842
|
## 架构模板
|
|
@@ -3,9 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveLaunchScriptPath = void 0;
|
|
6
7
|
exports.resolveShellMenuIconSourcePath = resolveShellMenuIconSourcePath;
|
|
7
8
|
exports.installShellMenuIcon = installShellMenuIcon;
|
|
8
|
-
exports.resolveLaunchScriptPath = resolveLaunchScriptPath;
|
|
9
9
|
exports.resolveWindowsTerminalPath = resolveWindowsTerminalPath;
|
|
10
10
|
exports.runShellInstall = runShellInstall;
|
|
11
11
|
exports.runShellUninstall = runShellUninstall;
|
|
@@ -18,6 +18,9 @@ const os_1 = __importDefault(require("os"));
|
|
|
18
18
|
const child_process_1 = require("child_process");
|
|
19
19
|
const logger_1 = require("../utils/logger");
|
|
20
20
|
const win_registry_1 = require("../lib/win-registry");
|
|
21
|
+
const ps_codechat_alias_1 = require("../lib/ps-codechat-alias");
|
|
22
|
+
const launch_script_path_1 = require("../lib/launch-script-path");
|
|
23
|
+
Object.defineProperty(exports, "resolveLaunchScriptPath", { enumerable: true, get: function () { return launch_script_path_1.resolveLaunchScriptPath; } });
|
|
21
24
|
const STUB_DIR = path_1.default.join(process.env.LOCALAPPDATA ?? path_1.default.join(os_1.default.homedir(), 'AppData', 'Local'), 'svharness', 'bin');
|
|
22
25
|
const STUB_PATH = path_1.default.join(STUB_DIR, 'launch_codechat_cli.ps1');
|
|
23
26
|
const LEGACY_STUB_PATH = path_1.default.join(STUB_DIR, 'launch_codechat_cli.cmd');
|
|
@@ -64,22 +67,6 @@ async function installShellMenuIcon() {
|
|
|
64
67
|
await fs_extra_1.default.copy(source, ICON_STUB_PATH, { overwrite: true });
|
|
65
68
|
return `${ICON_STUB_PATH},0`;
|
|
66
69
|
}
|
|
67
|
-
/**
|
|
68
|
-
* Resolve absolute path to bin/launch-codechat-cli.js in the installed package.
|
|
69
|
-
*/
|
|
70
|
-
function resolveLaunchScriptPath() {
|
|
71
|
-
// dist/commands/shell-integration.js → ../../bin/launch-codechat-cli.js
|
|
72
|
-
const fromDist = path_1.default.resolve(__dirname, '..', '..', 'bin', 'launch-codechat-cli.js');
|
|
73
|
-
if (fs_extra_1.default.existsSync(fromDist)) {
|
|
74
|
-
return fromDist;
|
|
75
|
-
}
|
|
76
|
-
// fallback: package root from scripts/postinstall.js context
|
|
77
|
-
const fromCwd = path_1.default.resolve(__dirname, '..', '..', '..', 'bin', 'launch-codechat-cli.js');
|
|
78
|
-
if (fs_extra_1.default.existsSync(fromCwd)) {
|
|
79
|
-
return fromCwd;
|
|
80
|
-
}
|
|
81
|
-
throw new Error(`未找到 launch-codechat-cli.js(预期:${fromDist})`);
|
|
82
|
-
}
|
|
83
70
|
function canAccessExecutable(filePath) {
|
|
84
71
|
try {
|
|
85
72
|
fs_extra_1.default.accessSync(filePath, fs_extra_1.default.constants.F_OK);
|
|
@@ -162,7 +149,7 @@ async function runShellInstall(opts = {}) {
|
|
|
162
149
|
// Avoid duplicate menu entries: overwrite standalone menu if present.
|
|
163
150
|
(0, win_registry_1.regDeleteKey)(STANDALONE_MENU_KEY_BG);
|
|
164
151
|
(0, win_registry_1.regDeleteKey)(STANDALONE_MENU_KEY_DIR);
|
|
165
|
-
const launchScriptPath = resolveLaunchScriptPath();
|
|
152
|
+
const launchScriptPath = (0, launch_script_path_1.resolveLaunchScriptPath)();
|
|
166
153
|
await fs_extra_1.default.ensureDir(STUB_DIR);
|
|
167
154
|
await writeStubFile(launchScriptPath);
|
|
168
155
|
if (await fs_extra_1.default.pathExists(LEGACY_STUB_PATH)) {
|
|
@@ -182,6 +169,7 @@ async function runShellInstall(opts = {}) {
|
|
|
182
169
|
if (wtPath) {
|
|
183
170
|
logInfo(` wt: ${wtPath}`, opts.silent);
|
|
184
171
|
}
|
|
172
|
+
await (0, ps_codechat_alias_1.installCodechatAlias)({ silent: opts.silent });
|
|
185
173
|
}
|
|
186
174
|
/**
|
|
187
175
|
* Remove HKCU context menus and stub file.
|
|
@@ -199,7 +187,8 @@ async function runShellUninstall(opts = {}) {
|
|
|
199
187
|
if (await fs_extra_1.default.pathExists(LEGACY_STUB_PATH)) {
|
|
200
188
|
await fs_extra_1.default.remove(LEGACY_STUB_PATH);
|
|
201
189
|
}
|
|
202
|
-
|
|
190
|
+
await (0, ps_codechat_alias_1.uninstallCodechatAlias)({ silent: opts.silent });
|
|
191
|
+
logSuccess('Windows 右键菜单与 PowerShell 别名已卸载', opts.silent);
|
|
203
192
|
}
|
|
204
193
|
/**
|
|
205
194
|
* Report shell integration installation state.
|
|
@@ -208,7 +197,7 @@ function getShellStatus() {
|
|
|
208
197
|
assertWin32();
|
|
209
198
|
let launchScriptPath = '';
|
|
210
199
|
try {
|
|
211
|
-
launchScriptPath = resolveLaunchScriptPath();
|
|
200
|
+
launchScriptPath = (0, launch_script_path_1.resolveLaunchScriptPath)();
|
|
212
201
|
}
|
|
213
202
|
catch {
|
|
214
203
|
launchScriptPath = '(未找到)';
|
|
@@ -217,6 +206,7 @@ function getShellStatus() {
|
|
|
217
206
|
const directoryMenu = (0, win_registry_1.regKeyExists)(win_registry_1.SHELL_MENU_KEY_DIR);
|
|
218
207
|
const stubExists = fs_extra_1.default.existsSync(STUB_PATH);
|
|
219
208
|
const wtPath = resolveWindowsTerminalPath();
|
|
209
|
+
const aliasStatus = (0, ps_codechat_alias_1.getCodechatAliasStatus)();
|
|
220
210
|
return {
|
|
221
211
|
installed: backgroundMenu && directoryMenu && stubExists,
|
|
222
212
|
stubPath: STUB_PATH,
|
|
@@ -224,24 +214,28 @@ function getShellStatus() {
|
|
|
224
214
|
directoryMenu,
|
|
225
215
|
launchScriptPath,
|
|
226
216
|
viaWindowsTerminal: wtPath !== null,
|
|
217
|
+
aliasInstalled: aliasStatus.installed,
|
|
218
|
+
profilePath: aliasStatus.profilePath,
|
|
227
219
|
};
|
|
228
220
|
}
|
|
229
221
|
function printShellStatus() {
|
|
230
222
|
const status = getShellStatus();
|
|
231
|
-
logger_1.logger.section('Windows
|
|
232
|
-
logger_1.logger.plain(`
|
|
233
|
-
logger_1.logger.plain(` stub 路径:
|
|
234
|
-
logger_1.logger.plain(` 空白处菜单:
|
|
235
|
-
logger_1.logger.plain(` 文件夹菜单:
|
|
236
|
-
logger_1.logger.plain(` 启动脚本:
|
|
237
|
-
logger_1.logger.plain(` 经 WT 启动:
|
|
223
|
+
logger_1.logger.section('Windows shell 集成状态');
|
|
224
|
+
logger_1.logger.plain(` 右键菜单完整: ${status.installed ? '是' : '否'}`);
|
|
225
|
+
logger_1.logger.plain(` stub 路径: ${status.stubPath}`);
|
|
226
|
+
logger_1.logger.plain(` 空白处菜单: ${status.backgroundMenu ? '已注册' : '未注册'}`);
|
|
227
|
+
logger_1.logger.plain(` 文件夹菜单: ${status.directoryMenu ? '已注册' : '未注册'}`);
|
|
228
|
+
logger_1.logger.plain(` 启动脚本: ${status.launchScriptPath}`);
|
|
229
|
+
logger_1.logger.plain(` 经 WT 启动: ${status.viaWindowsTerminal ? '是(install 时检测到 wt.exe)' : '否'}`);
|
|
230
|
+
logger_1.logger.plain(` codechat 别名: ${status.aliasInstalled ? '已安装' : '未安装'}`);
|
|
231
|
+
logger_1.logger.plain(` profile 路径: ${status.profilePath}`);
|
|
238
232
|
}
|
|
239
233
|
async function runShellInstallSafe(opts = {}) {
|
|
240
234
|
try {
|
|
241
235
|
await runShellInstall(opts);
|
|
242
236
|
}
|
|
243
237
|
catch (err) {
|
|
244
|
-
logWarn(`自动注册 Windows
|
|
238
|
+
logWarn(`自动注册 Windows shell 集成失败:${err.message}\n` +
|
|
245
239
|
` 可稍后手动执行:svharness shell install`);
|
|
246
240
|
}
|
|
247
241
|
}
|
package/dist/index.js
CHANGED
|
@@ -492,10 +492,10 @@ function main() {
|
|
|
492
492
|
});
|
|
493
493
|
const shellCmd = program
|
|
494
494
|
.command('shell')
|
|
495
|
-
.description('Windows
|
|
495
|
+
.description('Windows 右键菜单与 PowerShell codechat 别名(仅 win32)');
|
|
496
496
|
shellCmd
|
|
497
497
|
.command('install')
|
|
498
|
-
.description('注册 HKCU
|
|
498
|
+
.description('注册 HKCU 右键菜单,并在 $PROFILE 安装 codechat 函数(幂等)')
|
|
499
499
|
.action(async () => {
|
|
500
500
|
try {
|
|
501
501
|
await (0, shell_integration_1.runShellInstall)();
|
|
@@ -507,7 +507,7 @@ function main() {
|
|
|
507
507
|
});
|
|
508
508
|
shellCmd
|
|
509
509
|
.command('uninstall')
|
|
510
|
-
.description('
|
|
510
|
+
.description('移除右键菜单、本地 stub 与 PowerShell codechat 别名')
|
|
511
511
|
.action(async () => {
|
|
512
512
|
try {
|
|
513
513
|
await (0, shell_integration_1.runShellUninstall)();
|
|
@@ -519,7 +519,7 @@ function main() {
|
|
|
519
519
|
});
|
|
520
520
|
shellCmd
|
|
521
521
|
.command('status')
|
|
522
|
-
.description('
|
|
522
|
+
.description('查看右键菜单与 PowerShell codechat 别名状态')
|
|
523
523
|
.action(() => {
|
|
524
524
|
try {
|
|
525
525
|
(0, shell_integration_1.printShellStatus)();
|
|
@@ -14,6 +14,7 @@ const child_process_1 = require("child_process");
|
|
|
14
14
|
const logger_1 = require("../utils/logger");
|
|
15
15
|
const validate_args_1 = require("../utils/validate-args");
|
|
16
16
|
const codechat_runner_resolver_1 = require("./codechat-runner-resolver");
|
|
17
|
+
const ps_codechat_alias_1 = require("./ps-codechat-alias");
|
|
17
18
|
const SUPPORTED_START_AGENTS = ['codechat'];
|
|
18
19
|
const DEFAULT_ENV_TEMPLATE = 'default-claude.env';
|
|
19
20
|
function homePath(...segments) {
|
|
@@ -160,27 +161,6 @@ async function ensureUserEnv(workdir, config, syncEnv) {
|
|
|
160
161
|
await copyEnvToUserOnly(defaultTemplate, userEnv);
|
|
161
162
|
logger_1.logger.info(`已写入内置默认 env 到用户目录(未写入项目)→ ${userEnv}`);
|
|
162
163
|
}
|
|
163
|
-
function warnIfNotHarnessProject(workdir) {
|
|
164
|
-
const hints = [];
|
|
165
|
-
if (fs_extra_1.default.existsSync(path_1.default.join(workdir, 'CLAUDE.md')))
|
|
166
|
-
hints.push('CLAUDE.md');
|
|
167
|
-
if (fs_extra_1.default.existsSync(path_1.default.join(workdir, 'AGENTS.md')))
|
|
168
|
-
hints.push('AGENTS.md');
|
|
169
|
-
if (fs_extra_1.default.existsSync(path_1.default.join(workdir, '.he-harness')))
|
|
170
|
-
hints.push('.he-harness/');
|
|
171
|
-
try {
|
|
172
|
-
const entries = fs_extra_1.default.readdirSync(workdir);
|
|
173
|
-
if (entries.some((e) => e.endsWith('-harness') && fs_extra_1.default.statSync(path_1.default.join(workdir, e)).isDirectory())) {
|
|
174
|
-
hints.push('*-harness/');
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
catch {
|
|
178
|
-
// ignore
|
|
179
|
-
}
|
|
180
|
-
if (hints.length === 0) {
|
|
181
|
-
logger_1.logger.warn(`当前目录可能不是 harness 项目根(未找到 CLAUDE.md / AGENTS.md / *-harness/ / .he-harness/):\n ${workdir}`);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
164
|
function buildRunnerArgs(config, workdir, skipPermissions) {
|
|
185
165
|
const envFile = resolveUserEnvPath(config);
|
|
186
166
|
const args = [
|
|
@@ -205,6 +185,9 @@ async function runStartAgent(opts) {
|
|
|
205
185
|
workDir: opts.workDir,
|
|
206
186
|
positionalWorkdir: opts.positionalWorkdir,
|
|
207
187
|
});
|
|
188
|
+
if (process.platform === 'win32') {
|
|
189
|
+
await (0, ps_codechat_alias_1.ensureCodechatAliasSafe)({ silent: true });
|
|
190
|
+
}
|
|
208
191
|
const config = getCodechatEnvConfig();
|
|
209
192
|
const syncEnv = opts.syncEnv !== false;
|
|
210
193
|
const skipPermissions = opts.skipPermissions !== false;
|
|
@@ -216,7 +199,6 @@ async function runStartAgent(opts) {
|
|
|
216
199
|
`请提供 ${path_1.default.join(workdir, config.envSyncFromRel)},或确保内置模板 templates/default-claude.env 可访问。`);
|
|
217
200
|
}
|
|
218
201
|
await verifyUserEnvWritten(userEnvFile);
|
|
219
|
-
warnIfNotHarnessProject(workdir);
|
|
220
202
|
const args = buildRunnerArgs(config, workdir, skipPermissions);
|
|
221
203
|
logger_1.logger.info(`CodeChat CLI: ${runner} (${formatRunnerSourceLog(source, label)})`);
|
|
222
204
|
logger_1.logger.info(`启动 CodeChat Agent(workdir: ${workdir})`);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveLaunchScriptPath = resolveLaunchScriptPath;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
/**
|
|
10
|
+
* Resolve absolute path to bin/launch-codechat-cli.js in the installed package.
|
|
11
|
+
*/
|
|
12
|
+
function resolveLaunchScriptPath() {
|
|
13
|
+
// dist/lib/launch-script-path.js → ../../bin/launch-codechat-cli.js
|
|
14
|
+
const fromDist = path_1.default.resolve(__dirname, '..', '..', 'bin', 'launch-codechat-cli.js');
|
|
15
|
+
if (fs_extra_1.default.existsSync(fromDist)) {
|
|
16
|
+
return fromDist;
|
|
17
|
+
}
|
|
18
|
+
// fallback: package root from scripts/postinstall.js context
|
|
19
|
+
const fromCwd = path_1.default.resolve(__dirname, '..', '..', '..', 'bin', 'launch-codechat-cli.js');
|
|
20
|
+
if (fs_extra_1.default.existsSync(fromCwd)) {
|
|
21
|
+
return fromCwd;
|
|
22
|
+
}
|
|
23
|
+
throw new Error(`未找到 launch-codechat-cli.js(预期:${fromDist})`);
|
|
24
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getPowerShellProfilePath = getPowerShellProfilePath;
|
|
7
|
+
exports.installCodechatAlias = installCodechatAlias;
|
|
8
|
+
exports.uninstallCodechatAlias = uninstallCodechatAlias;
|
|
9
|
+
exports.getCodechatAliasStatus = getCodechatAliasStatus;
|
|
10
|
+
exports.printCodechatAliasStatus = printCodechatAliasStatus;
|
|
11
|
+
exports.ensureCodechatAliasSafe = ensureCodechatAliasSafe;
|
|
12
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const child_process_1 = require("child_process");
|
|
15
|
+
const logger_1 = require("../utils/logger");
|
|
16
|
+
const launch_script_path_1 = require("./launch-script-path");
|
|
17
|
+
const START_MARKER = /^#\s*>>>\s*codechat\s*>>>/;
|
|
18
|
+
const END_MARKER = /^#\s*<<<\s*codechat\s*<<</;
|
|
19
|
+
function assertWin32() {
|
|
20
|
+
if (process.platform !== 'win32') {
|
|
21
|
+
throw new Error('PowerShell codechat 别名仅支持 Windows');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the current user's PowerShell $PROFILE path.
|
|
26
|
+
*/
|
|
27
|
+
function getPowerShellProfilePath() {
|
|
28
|
+
assertWin32();
|
|
29
|
+
const out = (0, child_process_1.execFileSync)('powershell.exe', ['-NoProfile', '-Command', 'Write-Output $PROFILE'], { encoding: 'utf8', windowsHide: true }).trim();
|
|
30
|
+
if (!out) {
|
|
31
|
+
throw new Error('无法解析 PowerShell $PROFILE 路径');
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function normalizePathForCompare(value) {
|
|
36
|
+
return path_1.default.resolve(value).replace(/\\/g, '/').toLowerCase();
|
|
37
|
+
}
|
|
38
|
+
function stripCodechatBlock(content) {
|
|
39
|
+
const lines = content.split(/\r?\n/);
|
|
40
|
+
const clean = [];
|
|
41
|
+
let hadBlock = false;
|
|
42
|
+
let inBlock = false;
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
if (START_MARKER.test(line)) {
|
|
45
|
+
hadBlock = true;
|
|
46
|
+
inBlock = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (inBlock) {
|
|
50
|
+
if (END_MARKER.test(line)) {
|
|
51
|
+
inBlock = false;
|
|
52
|
+
}
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
clean.push(line);
|
|
56
|
+
}
|
|
57
|
+
const joined = clean.join('\n').replace(/\n+$/, '');
|
|
58
|
+
return { clean: joined ? `${joined}\n` : '', hadBlock };
|
|
59
|
+
}
|
|
60
|
+
function buildAliasSnippet(launchScriptPath) {
|
|
61
|
+
const escapedPath = launchScriptPath.replace(/'/g, "''");
|
|
62
|
+
return `
|
|
63
|
+
|
|
64
|
+
# >>> codechat >>>
|
|
65
|
+
function codechat {
|
|
66
|
+
param([string]$Path)
|
|
67
|
+
if (-not $Path) { $Path = (Get-Location).Path }
|
|
68
|
+
& node '${escapedPath}' $Path
|
|
69
|
+
}
|
|
70
|
+
# <<< codechat <<<
|
|
71
|
+
|
|
72
|
+
`;
|
|
73
|
+
}
|
|
74
|
+
function profileHasSvharnessAlias(content, launchScriptPath) {
|
|
75
|
+
if (!START_MARKER.test(content)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const needle = normalizePathForCompare(launchScriptPath);
|
|
79
|
+
return normalizePathForCompare(content).includes(needle);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Install or refresh the PowerShell `codechat` function in $PROFILE (idempotent).
|
|
83
|
+
* Ported from templates/codechat/Start-CodeChat.ps1 Install-CodeChatAlias.
|
|
84
|
+
*/
|
|
85
|
+
async function installCodechatAlias(opts = {}) {
|
|
86
|
+
assertWin32();
|
|
87
|
+
const profilePath = getPowerShellProfilePath();
|
|
88
|
+
const launchScriptPath = (0, launch_script_path_1.resolveLaunchScriptPath)();
|
|
89
|
+
const existing = (await fs_extra_1.default.pathExists(profilePath))
|
|
90
|
+
? await fs_extra_1.default.readFile(profilePath, 'utf8')
|
|
91
|
+
: '';
|
|
92
|
+
if (profileHasSvharnessAlias(existing, launchScriptPath)) {
|
|
93
|
+
if (!opts.silent) {
|
|
94
|
+
logger_1.logger.info(`PowerShell 别名 codechat 已存在:${profilePath}`);
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(profilePath));
|
|
99
|
+
if (!(await fs_extra_1.default.pathExists(profilePath))) {
|
|
100
|
+
await fs_extra_1.default.writeFile(profilePath, '', 'utf8');
|
|
101
|
+
}
|
|
102
|
+
const { clean, hadBlock } = stripCodechatBlock(existing);
|
|
103
|
+
const snippet = buildAliasSnippet(launchScriptPath);
|
|
104
|
+
await fs_extra_1.default.writeFile(profilePath, `${clean}${snippet}`, 'utf8');
|
|
105
|
+
if (!opts.silent) {
|
|
106
|
+
if (hadBlock) {
|
|
107
|
+
logger_1.logger.success(`已更新 PowerShell 别名 codechat → ${launchScriptPath}`);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
logger_1.logger.success(`已安装 PowerShell 别名 codechat → ${profilePath}`);
|
|
111
|
+
}
|
|
112
|
+
logger_1.logger.info(' 新开 PowerShell 窗口后可直接运行:codechat [项目路径]');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Remove the codechat block from $PROFILE.
|
|
117
|
+
*/
|
|
118
|
+
async function uninstallCodechatAlias(opts = {}) {
|
|
119
|
+
assertWin32();
|
|
120
|
+
const profilePath = getPowerShellProfilePath();
|
|
121
|
+
if (!(await fs_extra_1.default.pathExists(profilePath))) {
|
|
122
|
+
if (!opts.silent) {
|
|
123
|
+
logger_1.logger.info("PowerShell 别名 codechat 未安装(profile 不存在)");
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const existing = await fs_extra_1.default.readFile(profilePath, 'utf8');
|
|
128
|
+
const { clean, hadBlock } = stripCodechatBlock(existing);
|
|
129
|
+
if (!hadBlock) {
|
|
130
|
+
if (!opts.silent) {
|
|
131
|
+
logger_1.logger.info("PowerShell 别名 codechat 未安装");
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
await fs_extra_1.default.writeFile(profilePath, clean, 'utf8');
|
|
136
|
+
if (!opts.silent) {
|
|
137
|
+
logger_1.logger.success(`已从 ${profilePath} 移除 PowerShell 别名 codechat`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function getCodechatAliasStatus() {
|
|
141
|
+
assertWin32();
|
|
142
|
+
const profilePath = getPowerShellProfilePath();
|
|
143
|
+
let launchScriptPath = '';
|
|
144
|
+
try {
|
|
145
|
+
launchScriptPath = (0, launch_script_path_1.resolveLaunchScriptPath)();
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
launchScriptPath = '(未找到)';
|
|
149
|
+
}
|
|
150
|
+
let installed = false;
|
|
151
|
+
if (fs_extra_1.default.existsSync(profilePath) && launchScriptPath !== '(未找到)') {
|
|
152
|
+
try {
|
|
153
|
+
const content = fs_extra_1.default.readFileSync(profilePath, 'utf8');
|
|
154
|
+
installed = profileHasSvharnessAlias(content, launchScriptPath);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
installed = false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return { profilePath, installed, launchScriptPath };
|
|
161
|
+
}
|
|
162
|
+
function printCodechatAliasStatus() {
|
|
163
|
+
const status = getCodechatAliasStatus();
|
|
164
|
+
logger_1.logger.section('PowerShell codechat 别名状态');
|
|
165
|
+
logger_1.logger.plain(` 已安装: ${status.installed ? '是' : '否'}`);
|
|
166
|
+
logger_1.logger.plain(` profile: ${status.profilePath}`);
|
|
167
|
+
logger_1.logger.plain(` 启动脚本: ${status.launchScriptPath}`);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Best-effort alias install; never throws (warn only).
|
|
171
|
+
*/
|
|
172
|
+
async function ensureCodechatAliasSafe(opts = {}) {
|
|
173
|
+
if (process.platform !== 'win32') {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
await installCodechatAlias({ silent: opts.silent ?? true });
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
logger_1.logger.warn(`自动安装 PowerShell 别名 codechat 失败:${err.message}\n` +
|
|
181
|
+
` 可稍后手动执行:svharness shell install`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -23,9 +23,10 @@ cd D:\projects\my-app
|
|
|
23
23
|
# 3. 可选:注册 Explorer 右键(独立菜单项)
|
|
24
24
|
.\.claude\Start-CodeChat.ps1 -Action InstallMenu
|
|
25
25
|
|
|
26
|
-
# 4. 状态 /
|
|
26
|
+
# 4. 状态 / 反注册
|
|
27
27
|
.\.claude\Start-CodeChat.ps1 -Action Status
|
|
28
28
|
.\.claude\Start-CodeChat.ps1 -Action UninstallMenu
|
|
29
|
+
.\.claude\Start-CodeChat.ps1 -Action UninstallAlias
|
|
29
30
|
```
|
|
30
31
|
|
|
31
32
|
## 子命令 `-Action`
|
|
@@ -33,8 +34,11 @@ cd D:\projects\my-app
|
|
|
33
34
|
| Action | 说明 |
|
|
34
35
|
|--------|------|
|
|
35
36
|
| `Start`(默认) | 同步 env → 解析 `run.bat` → 启动 CodeChat |
|
|
37
|
+
| `Install` | 注册 Explorer 右键 **并** 安装 PowerShell `codechat` 别名 |
|
|
36
38
|
| `InstallMenu` | 复制脚本到 `%LOCALAPPDATA%\codechat-standalone\` 并注册 HKCU 右键 |
|
|
37
39
|
| `UninstallMenu` | 删除独立注册表键与 `%LOCALAPPDATA%\codechat-standalone\` |
|
|
40
|
+
| `InstallAlias` | 在 `$PROFILE` 安装 `codechat` 函数 |
|
|
41
|
+
| `UninstallAlias` | 从 `$PROFILE` 移除 `codechat` 函数块 |
|
|
38
42
|
| `Status` | 打印 runner / env / 菜单注册状态 |
|
|
39
43
|
|
|
40
44
|
## Start 参数
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* postinstall: global install on Windows registers Explorer context menu.
|
|
4
|
+
* postinstall: global install on Windows registers Explorer context menu + PowerShell codechat alias.
|
|
5
5
|
* Skip: SVHARNESS_SKIP_SHELL=1 npm install -g svharness
|
|
6
6
|
*/
|
|
7
7
|
if (process.platform !== 'win32') {
|
|
@@ -20,7 +20,7 @@ if (process.env.SVHARNESS_SKIP_SHELL === '1') {
|
|
|
20
20
|
await runShellInstallSafe({ silent: false });
|
|
21
21
|
} catch (err) {
|
|
22
22
|
console.warn(
|
|
23
|
-
'[svharness postinstall] 注册 Windows
|
|
23
|
+
'[svharness postinstall] 注册 Windows shell 集成失败:',
|
|
24
24
|
err && err.message ? err.message : err,
|
|
25
25
|
);
|
|
26
26
|
console.warn('[svharness postinstall] 可稍后手动执行:svharness shell install');
|
package/scripts/preuninstall.js
CHANGED
|
@@ -55,3 +55,48 @@ regDeleteKey('HKCU\\Software\\Classes\\Directory\\shell\\svharness_codechat');
|
|
|
55
55
|
|
|
56
56
|
// Stub files + icon under %LOCALAPPDATA%\svharness
|
|
57
57
|
removeDirRecursive(STUB_DIR);
|
|
58
|
+
|
|
59
|
+
function stripCodechatAliasFromProfile(profilePath) {
|
|
60
|
+
if (!profilePath || !fs.existsSync(profilePath)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const content = fs.readFileSync(profilePath, 'utf8');
|
|
65
|
+
const lines = content.split(/\r?\n/);
|
|
66
|
+
const clean = [];
|
|
67
|
+
let inBlock = false;
|
|
68
|
+
let hadBlock = false;
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
if (/^#\s*>>>\s*codechat\s*>>>/.test(line)) {
|
|
71
|
+
hadBlock = true;
|
|
72
|
+
inBlock = true;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (inBlock) {
|
|
76
|
+
if (/^#\s*<<<\s*codechat\s*<<</.test(line)) {
|
|
77
|
+
inBlock = false;
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
clean.push(line);
|
|
82
|
+
}
|
|
83
|
+
if (!hadBlock) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const joined = clean.join('\n').replace(/\n+$/, '');
|
|
87
|
+
fs.writeFileSync(profilePath, joined ? `${joined}\n` : '', 'utf8');
|
|
88
|
+
} catch {
|
|
89
|
+
// best effort
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const profilePath = execFileSync(
|
|
95
|
+
'powershell.exe',
|
|
96
|
+
['-NoProfile', '-Command', 'Write-Output $PROFILE'],
|
|
97
|
+
{ encoding: 'utf8', windowsHide: true },
|
|
98
|
+
).trim();
|
|
99
|
+
stripCodechatAliasFromProfile(profilePath);
|
|
100
|
+
} catch {
|
|
101
|
+
// best effort
|
|
102
|
+
}
|