@myclaw163/clawclaw-cli 0.6.69 → 0.6.71

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.
Files changed (39) hide show
  1. package/bin/clawclaw-cli.mjs +3 -3
  2. package/package.json +1 -1
  3. package/scripts/sync-bundled-skill.mjs +1 -1
  4. package/skills/clawclaw/references/STRATEGIES.md +5 -3
  5. package/src/commands/config.ts +30 -30
  6. package/src/commands/game.ts +8 -3
  7. package/src/commands/setup/hermes.test.ts +96 -96
  8. package/src/commands/setup/hermes.ts +76 -76
  9. package/src/commands/setup/index.ts +13 -13
  10. package/src/commands/setup/openclaw.test.ts +114 -114
  11. package/src/commands/setup/openclaw.ts +147 -147
  12. package/src/commands/strategy.test.ts +10 -0
  13. package/src/commands/strategy.ts +11 -10
  14. package/src/lib/host-config-patcher.test.ts +130 -130
  15. package/src/lib/host-config-patcher.ts +151 -151
  16. package/src/lib/hub-reminder.ts +19 -19
  17. package/src/strategies/avoid-lone.ts +1 -0
  18. package/src/strategies/avoid-players.ts +1 -0
  19. package/src/strategies/corpse-patrol.ts +1 -0
  20. package/src/strategies/crab-sabotage.ts +1 -0
  21. package/src/strategies/custom-module.test.ts +1 -0
  22. package/src/strategies/find-player.ts +1 -0
  23. package/src/strategies/hide.ts +1 -0
  24. package/src/strategies/kill-frenzy.ts +1 -0
  25. package/src/strategies/kill-lone.ts +1 -0
  26. package/src/strategies/kill-target.ts +1 -0
  27. package/src/strategies/loader.ts +9 -2
  28. package/src/strategies/lone-kill-task.ts +1 -0
  29. package/src/strategies/move-room.ts +1 -0
  30. package/src/strategies/paradise-fish.ts +1 -0
  31. package/src/strategies/patrol.ts +1 -0
  32. package/src/strategies/report-patrol.ts +1 -0
  33. package/src/strategies/shrimp-memory.ts +1 -0
  34. package/src/strategies/social-task.ts +1 -0
  35. package/src/strategies/task-kill-report.ts +1 -0
  36. package/src/strategies/task-only.ts +1 -0
  37. package/src/strategies/task-report.ts +1 -0
  38. package/src/strategies/types.ts +7 -0
  39. package/src/strategies/warrior-memory.ts +1 -0
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { register } from 'tsx/esm/api';
3
- register();
4
- await import('../src/cli.ts');
2
+ import { register } from 'tsx/esm/api';
3
+ register();
4
+ await import('../src/cli.ts');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myclaw163/clawclaw-cli",
3
- "version": "0.6.69",
3
+ "version": "0.6.71",
4
4
  "type": "module",
5
5
  "description": "ClawClaw social deduction game CLI",
6
6
  "bin": {
@@ -1,4 +1,4 @@
1
- import { spawnSync } from 'child_process';
1
+ import { spawnSync } from 'child_process';
2
2
  import { createHash } from 'crypto';
3
3
  import {
4
4
  cpSync,
@@ -8,10 +8,12 @@
8
8
  ccl strategy --list
9
9
  ```
10
10
 
11
- `--list` 返回当前实际加载到的策略 `id` 和 `description`,包括内置策略、Hub 策略和本地自定义策略;如果用户用同名策略覆盖官方策略,也以这里显示的结果为准。
11
+ `--list` 返回当前实际加载到的策略 `id`、`name` 和 `description`,包括内置策略、Hub 策略和本地自定义策略;如果用户用同名策略覆盖官方策略,也以这里显示的结果为准。
12
12
 
13
13
  `description` 是策略选择的第一入口。它应说明策略做什么、是否需要参数、参数怎么传,以及是否读取 `ccl knowledge`。
14
14
 
15
+ `name` 是策略的中文代词(如「守尸」「武士虾」),是玩家和你口头沟通时用的称呼。玩家说「切到守尸」时,你据此映射回对应 `id`。`id` 仍是规范选择器,但 `ccl strategy <name>`(中文名)与 `ccl strategy <id>` 等价,两者都能启动。
16
+
15
17
  ## 策略选择流程
16
18
 
17
19
  1. 先确认当前策略:从 `ccl game start` 短通知、`ccl events` 返回的 state / 事件,或最近一次 `ccl strategy <id>` 的启动结果里,看现在正在跑什么策略。
@@ -25,7 +27,7 @@ ccl strategy --info <id>
25
27
  ```
26
28
 
27
29
  6. 如需让当前策略对特定玩家或事实做出反应,使用 `ccl knowledge` 写入判断;如果是整体目标变化,则切换策略。
28
- 7. 启动策略:
30
+ 7. 启动策略(`<id>` 处也可填中文 `name`):
29
31
 
30
32
  ```bash
31
33
  ccl strategy <id> [args...]
@@ -54,4 +56,4 @@ ccl strategy --info <id>
54
56
 
55
57
  ## 能力边界说明
56
58
 
57
- > **策略是有限集合——不要暗示它们无所不能。** `ccl strategy --list` 是当前实际可用策略目录。**当用户要求的玩法超出这些策略能力时,直说——告诉用户当前策略不支持。** 不要拖延、不要假装某个策略能做到、不要沉默忽略请求。然后提供最接近的方案:让现有策略覆盖能做的部分,其余用 `ccl do`(发言/投票/思考)、手动 `ccl strategy` 切换或手动移动来处理——明确指出哪些是策略自动化的、哪些是你手动操作的、哪些确实无法实现。当差距真实存在——请求需要当前策略都不支持的自动化行为时——主动引导用户到 clawclawhub:`ccl hub search` 浏览社区策略,`ccl hub install <type>/<id>` 安装用户选中的。安装后用 `ccl strategy --list` 确认策略可用。如果那里也不适合,建议创建自定义策略:在 `<workspace>/strategies/` 下放 `.ts` / `.js` 文件,导出 `strategy`(id / description / create),从 `clawclaw-cli` 导入辅助函数。主动提出为他们编写——API 和示例见 `ccl strategy -h` 和 `docs/自定义策略.md`。
59
+ > **策略是有限集合——不要暗示它们无所不能。** `ccl strategy --list` 是当前实际可用策略目录。**当用户要求的玩法超出这些策略能力时,直说——告诉用户当前策略不支持。** 不要拖延、不要假装某个策略能做到、不要沉默忽略请求。然后提供最接近的方案:让现有策略覆盖能做的部分,其余用 `ccl do`(发言/投票/思考)、手动 `ccl strategy` 切换或手动移动来处理——明确指出哪些是策略自动化的、哪些是你手动操作的、哪些确实无法实现。当差距真实存在——请求需要当前策略都不支持的自动化行为时——主动引导用户到 clawclawhub:`ccl hub search` 浏览社区策略,`ccl hub install <type>/<id>` 安装用户选中的。安装后用 `ccl strategy --list` 确认策略可用。如果那里也不适合,建议创建自定义策略:在 `<workspace>/strategies/` 下放 `.ts` / `.js` 文件,导出 `strategy`(id / name / description / create),从 `clawclaw-cli` 导入辅助函数。主动提出为他们编写——API 和示例见 `ccl strategy -h` 和 `docs/自定义策略.md`。
@@ -1,30 +1,30 @@
1
- import { Command } from 'commander';
2
- import { getWorkspaceDir } from '../lib/init-command.js';
3
- import { AuthStore } from '../lib/auth.js';
4
-
5
- export function createWorkspaceSubcommand(): Command {
6
- return new Command('workspace')
7
- .description('Print the workspace directory path')
8
- .action(() => {
9
- console.log(getWorkspaceDir());
10
- });
11
- }
12
-
13
- export function createApikeySubcommand(): Command {
14
- return new Command('apikey')
15
- .description('Print the active account API key')
16
- .action(() => {
17
- const store = new AuthStore();
18
- const profile = store.getActive();
19
- if (!profile) throw new Error('Not logged in. Run: clawclaw-cli account register');
20
- console.log(profile.apiKey);
21
- });
22
- }
23
-
24
- export function createConfigCommand(): Command {
25
- const config = new Command('config');
26
- config.description('Query ClawClaw CLI configuration.');
27
- config.addCommand(createWorkspaceSubcommand());
28
- config.addCommand(createApikeySubcommand());
29
- return config;
30
- }
1
+ import { Command } from 'commander';
2
+ import { getWorkspaceDir } from '../lib/init-command.js';
3
+ import { AuthStore } from '../lib/auth.js';
4
+
5
+ export function createWorkspaceSubcommand(): Command {
6
+ return new Command('workspace')
7
+ .description('Print the workspace directory path')
8
+ .action(() => {
9
+ console.log(getWorkspaceDir());
10
+ });
11
+ }
12
+
13
+ export function createApikeySubcommand(): Command {
14
+ return new Command('apikey')
15
+ .description('Print the active account API key')
16
+ .action(() => {
17
+ const store = new AuthStore();
18
+ const profile = store.getActive();
19
+ if (!profile) throw new Error('Not logged in. Run: clawclaw-cli account register');
20
+ console.log(profile.apiKey);
21
+ });
22
+ }
23
+
24
+ export function createConfigCommand(): Command {
25
+ const config = new Command('config');
26
+ config.description('Query ClawClaw CLI configuration.');
27
+ config.addCommand(createWorkspaceSubcommand());
28
+ config.addCommand(createApikeySubcommand());
29
+ return config;
30
+ }
@@ -364,7 +364,7 @@ function autoStartStrategy(roleData: any, stateData?: any, gameId?: string): { s
364
364
  return { strategy: strategyId, pid: child.pid, child };
365
365
  }
366
366
 
367
- async function runGameStart(opts: { watch: boolean; force?: boolean }): Promise<void> {
367
+ async function runGameStart(opts: { watch: boolean; force?: boolean; channelUrl?: string }): Promise<void> {
368
368
  if (process.env.CLAWCLAW_REQUIRE_STREAM_TOOL === '1' && process.env.CLAWCLAW_STREAMED !== '1') {
369
369
  process.stderr.write(
370
370
  'In OpenClaw, start the game via the clawclaw_game_start tool - do not exec `ccl game start` directly.\n' +
@@ -434,7 +434,11 @@ async function runGameStart(opts: { watch: boolean; force?: boolean }): Promise<
434
434
  process.on('SIGINT', onOwnerSignal);
435
435
  process.on('SIGTERM', onOwnerSignal);
436
436
  const emit = (obj: Record<string, any>): void => {
437
- process.stdout.write(JSON.stringify(obj) + '\n');
437
+ const line = JSON.stringify(obj) + '\n';
438
+ process.stdout.write(line);
439
+ if (opts.channelUrl) {
440
+ fetch(opts.channelUrl, { method: 'POST', body: line }).catch(() => {});
441
+ }
438
442
  };
439
443
  const emitLifecycle = (
440
444
  reason: string,
@@ -500,7 +504,7 @@ async function runGameStart(opts: { watch: boolean; force?: boolean }): Promise<
500
504
  feedPath,
501
505
  sessionPath,
502
506
  getSessionPath: () => EventStore.latestSessionPath(),
503
- stdout: (s) => process.stdout.write(s),
507
+ stdout: (s) => { process.stdout.write(s); if (opts.channelUrl) { fetch(opts.channelUrl, { method: 'POST', body: s }).catch(() => {}); } },
504
508
  signal: ctrl.signal,
505
509
  skipFeedWait: true,
506
510
  readSummary: currentSummary,
@@ -819,6 +823,7 @@ export function createGameCommand(): Command {
819
823
  .description('Start or resume the owner game runtime, then stream events as NDJSON until game_over. Pass --no-watch to exit after allocation. Pass --force to replace an orphaned game-start runtime.')
820
824
  .option('--no-watch', 'exit after allocation instead of streaming events through game_over')
821
825
  .option('--force', 'force restart: kill any lingering game-start stream process and start fresh')
826
+ .option('--channel-url <url>', 'forward each NDJSON line via HTTP POST to this URL')
822
827
  .action(runGameStart);
823
828
 
824
829
  game
@@ -1,96 +1,96 @@
1
- import { describe, expect, it, vi, beforeEach } from 'vitest';
2
- import { runHermesSetup } from './hermes.js';
3
- import { spawnSync } from 'child_process';
4
-
5
- vi.mock('child_process', () => ({
6
- spawnSync: vi.fn(),
7
- }));
8
-
9
- const mockedSpawnSync = vi.mocked(spawnSync);
10
-
11
- function mockSpawn(overrides: Array<{ status: number; stdout?: string; stderr?: string }>) {
12
- mockedSpawnSync.mockImplementation((_cmd: any, _args?: any, _opts?: any): any => {
13
- const next = overrides.shift();
14
- if (!next) throw new Error('Unexpected spawnSync call');
15
- return {
16
- status: next.status,
17
- stdout: next.stdout ?? '',
18
- stderr: next.stderr ?? '',
19
- };
20
- });
21
- }
22
-
23
- beforeEach(() => {
24
- vi.clearAllMocks();
25
- });
26
-
27
- describe('runHermesSetup', () => {
28
- it('returns error when hermes CLI is not found', () => {
29
- mockSpawn([{ status: 1, stderr: 'command not found' }]);
30
- const r = runHermesSetup({});
31
- expect(r.exitCode).toBe(1);
32
- expect(r.output.some((l) => l.includes('not found'))).toBe(true);
33
- });
34
-
35
- it('reports already-enabled when clawclaw is in plugins list', () => {
36
- mockSpawn([
37
- { status: 0 }, // --version
38
- { status: 0, stdout: 'clawclaw\nother-plugin' }, // plugins list
39
- ]);
40
- const r = runHermesSetup({});
41
- expect(r.exitCode).toBe(0);
42
- expect(r.output.some((l) => l.includes('already enabled'))).toBe(true);
43
- });
44
-
45
- it('dry-run shows pending message when not enabled', () => {
46
- mockSpawn([
47
- { status: 0 }, // --version
48
- { status: 0, stdout: 'other-plugin' }, // plugins list
49
- ]);
50
- const r = runHermesSetup({});
51
- expect(r.exitCode).toBe(2);
52
- expect(r.output.some((l) => l.includes('Dry-run'))).toBe(true);
53
- });
54
-
55
- it('--print shows the command that would run', () => {
56
- mockSpawn([
57
- { status: 0 }, // --version
58
- { status: 0, stdout: 'other-plugin' }, // plugins list
59
- ]);
60
- const r = runHermesSetup({ print: true });
61
- expect(r.exitCode).toBe(0);
62
- expect(r.output.some((l) => l.includes('hermes plugins enable clawclaw'))).toBe(true);
63
- });
64
-
65
- it('-y runs hermes plugins enable and succeeds', () => {
66
- mockSpawn([
67
- { status: 0 }, // --version
68
- { status: 0, stdout: 'other-plugin' }, // plugins list
69
- { status: 0, stdout: 'plugin enabled' }, // plugins enable
70
- ]);
71
- const r = runHermesSetup({ yes: true });
72
- expect(r.exitCode).toBe(0);
73
- expect(r.output.some((l) => l.includes('enabled'))).toBe(true);
74
- });
75
-
76
- it('-y handles hermes plugins enable failure', () => {
77
- mockSpawn([
78
- { status: 0 }, // --version
79
- { status: 0, stdout: 'other-plugin' }, // plugins list
80
- { status: 1, stderr: 'permission denied' }, // plugins enable
81
- ]);
82
- const r = runHermesSetup({ yes: true });
83
- expect(r.exitCode).toBe(1);
84
- expect(r.output.some((l) => l.includes('Failed'))).toBe(true);
85
- });
86
-
87
- it('already-enabled short-circuits --print', () => {
88
- mockSpawn([
89
- { status: 0 }, // --version
90
- { status: 0, stdout: 'clawclaw' }, // plugins list
91
- ]);
92
- const r = runHermesSetup({ print: true });
93
- expect(r.exitCode).toBe(0);
94
- expect(r.output.some((l) => l.includes('already enabled'))).toBe(true);
95
- });
96
- });
1
+ import { describe, expect, it, vi, beforeEach } from 'vitest';
2
+ import { runHermesSetup } from './hermes.js';
3
+ import { spawnSync } from 'child_process';
4
+
5
+ vi.mock('child_process', () => ({
6
+ spawnSync: vi.fn(),
7
+ }));
8
+
9
+ const mockedSpawnSync = vi.mocked(spawnSync);
10
+
11
+ function mockSpawn(overrides: Array<{ status: number; stdout?: string; stderr?: string }>) {
12
+ mockedSpawnSync.mockImplementation((_cmd: any, _args?: any, _opts?: any): any => {
13
+ const next = overrides.shift();
14
+ if (!next) throw new Error('Unexpected spawnSync call');
15
+ return {
16
+ status: next.status,
17
+ stdout: next.stdout ?? '',
18
+ stderr: next.stderr ?? '',
19
+ };
20
+ });
21
+ }
22
+
23
+ beforeEach(() => {
24
+ vi.clearAllMocks();
25
+ });
26
+
27
+ describe('runHermesSetup', () => {
28
+ it('returns error when hermes CLI is not found', () => {
29
+ mockSpawn([{ status: 1, stderr: 'command not found' }]);
30
+ const r = runHermesSetup({});
31
+ expect(r.exitCode).toBe(1);
32
+ expect(r.output.some((l) => l.includes('not found'))).toBe(true);
33
+ });
34
+
35
+ it('reports already-enabled when clawclaw is in plugins list', () => {
36
+ mockSpawn([
37
+ { status: 0 }, // --version
38
+ { status: 0, stdout: 'clawclaw\nother-plugin' }, // plugins list
39
+ ]);
40
+ const r = runHermesSetup({});
41
+ expect(r.exitCode).toBe(0);
42
+ expect(r.output.some((l) => l.includes('already enabled'))).toBe(true);
43
+ });
44
+
45
+ it('dry-run shows pending message when not enabled', () => {
46
+ mockSpawn([
47
+ { status: 0 }, // --version
48
+ { status: 0, stdout: 'other-plugin' }, // plugins list
49
+ ]);
50
+ const r = runHermesSetup({});
51
+ expect(r.exitCode).toBe(2);
52
+ expect(r.output.some((l) => l.includes('Dry-run'))).toBe(true);
53
+ });
54
+
55
+ it('--print shows the command that would run', () => {
56
+ mockSpawn([
57
+ { status: 0 }, // --version
58
+ { status: 0, stdout: 'other-plugin' }, // plugins list
59
+ ]);
60
+ const r = runHermesSetup({ print: true });
61
+ expect(r.exitCode).toBe(0);
62
+ expect(r.output.some((l) => l.includes('hermes plugins enable clawclaw'))).toBe(true);
63
+ });
64
+
65
+ it('-y runs hermes plugins enable and succeeds', () => {
66
+ mockSpawn([
67
+ { status: 0 }, // --version
68
+ { status: 0, stdout: 'other-plugin' }, // plugins list
69
+ { status: 0, stdout: 'plugin enabled' }, // plugins enable
70
+ ]);
71
+ const r = runHermesSetup({ yes: true });
72
+ expect(r.exitCode).toBe(0);
73
+ expect(r.output.some((l) => l.includes('enabled'))).toBe(true);
74
+ });
75
+
76
+ it('-y handles hermes plugins enable failure', () => {
77
+ mockSpawn([
78
+ { status: 0 }, // --version
79
+ { status: 0, stdout: 'other-plugin' }, // plugins list
80
+ { status: 1, stderr: 'permission denied' }, // plugins enable
81
+ ]);
82
+ const r = runHermesSetup({ yes: true });
83
+ expect(r.exitCode).toBe(1);
84
+ expect(r.output.some((l) => l.includes('Failed'))).toBe(true);
85
+ });
86
+
87
+ it('already-enabled short-circuits --print', () => {
88
+ mockSpawn([
89
+ { status: 0 }, // --version
90
+ { status: 0, stdout: 'clawclaw' }, // plugins list
91
+ ]);
92
+ const r = runHermesSetup({ print: true });
93
+ expect(r.exitCode).toBe(0);
94
+ expect(r.output.some((l) => l.includes('already enabled'))).toBe(true);
95
+ });
96
+ });
@@ -1,76 +1,76 @@
1
- /**
2
- * `ccl setup hermes` — enable the clawclaw plugin in Hermes via `hermes plugins enable`.
3
- */
4
-
5
- import { Command } from 'commander';
6
- import { spawnSync } from 'child_process';
7
-
8
- export const PLUGIN_ID = 'clawclaw';
9
-
10
- export interface SetupHermesResult {
11
- exitCode: number;
12
- output: string[];
13
- }
14
-
15
- export function runHermesSetup(flags: { yes?: boolean; print?: boolean }): SetupHermesResult {
16
- const out: string[] = [];
17
-
18
- // Check hermes is available
19
- const which = spawnSync('hermes', ['--version'], { stdio: 'pipe', timeout: 5000 });
20
- if (which.status !== 0) {
21
- out.push('hermes CLI not found. Install it first, then re-run.');
22
- return { exitCode: 1, output: out };
23
- }
24
-
25
- // Check current plugin status
26
- const list = spawnSync('hermes', ['plugins', 'list'], {
27
- stdio: 'pipe',
28
- timeout: 5000,
29
- env: { ...process.env, HERMES_PLUGINS_DEBUG: '1' },
30
- });
31
- const listOutput = list.stdout?.toString() ?? '';
32
- const alreadyEnabled = listOutput.includes(PLUGIN_ID) && !listOutput.includes(`${PLUGIN_ID} (disabled)`);
33
-
34
- if (alreadyEnabled) {
35
- out.push(`Plugin "${PLUGIN_ID}" is already enabled in Hermes.`);
36
- return { exitCode: 0, output: out };
37
- }
38
-
39
- if (flags.print) {
40
- out.push(`# Will run: hermes plugins enable ${PLUGIN_ID}`);
41
- return { exitCode: 0, output: out };
42
- }
43
-
44
- if (!flags.yes) {
45
- out.push(`Pending: hermes plugins enable ${PLUGIN_ID}`);
46
- out.push(``);
47
- out.push(`Dry-run only. Re-run with -y to apply.`);
48
- return { exitCode: 2, output: out };
49
- }
50
-
51
- const result = spawnSync('hermes', ['plugins', 'enable', PLUGIN_ID], {
52
- stdio: 'pipe',
53
- timeout: 10000,
54
- });
55
-
56
- if (result.status !== 0) {
57
- out.push(`Failed to enable plugin: ${result.stderr?.toString() ?? result.stdout?.toString() ?? 'unknown error'}`);
58
- return { exitCode: 1, output: out };
59
- }
60
-
61
- out.push(`Plugin "${PLUGIN_ID}" enabled in Hermes.`);
62
- out.push(`Verify: HERMES_PLUGINS_DEBUG=1 hermes plugins list`);
63
- return { exitCode: 0, output: out };
64
- }
65
-
66
- export function createSetupHermesSubcommand(): Command {
67
- return new Command('hermes')
68
- .description('Enable the clawclaw plugin in Hermes via "hermes plugins enable".')
69
- .option('-y, --yes', 'Apply changes (default is dry-run)')
70
- .option('--print', 'Only print the command that would be run; do not execute')
71
- .action((opts: { yes?: boolean; print?: boolean }) => {
72
- const result = runHermesSetup({ yes: opts.yes, print: opts.print });
73
- for (const line of result.output) console.log(line);
74
- if (result.exitCode !== 0) process.exit(result.exitCode);
75
- });
76
- }
1
+ /**
2
+ * `ccl setup hermes` — enable the clawclaw plugin in Hermes via `hermes plugins enable`.
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import { spawnSync } from 'child_process';
7
+
8
+ export const PLUGIN_ID = 'clawclaw';
9
+
10
+ export interface SetupHermesResult {
11
+ exitCode: number;
12
+ output: string[];
13
+ }
14
+
15
+ export function runHermesSetup(flags: { yes?: boolean; print?: boolean }): SetupHermesResult {
16
+ const out: string[] = [];
17
+
18
+ // Check hermes is available
19
+ const which = spawnSync('hermes', ['--version'], { stdio: 'pipe', timeout: 5000 });
20
+ if (which.status !== 0) {
21
+ out.push('hermes CLI not found. Install it first, then re-run.');
22
+ return { exitCode: 1, output: out };
23
+ }
24
+
25
+ // Check current plugin status
26
+ const list = spawnSync('hermes', ['plugins', 'list'], {
27
+ stdio: 'pipe',
28
+ timeout: 5000,
29
+ env: { ...process.env, HERMES_PLUGINS_DEBUG: '1' },
30
+ });
31
+ const listOutput = list.stdout?.toString() ?? '';
32
+ const alreadyEnabled = listOutput.includes(PLUGIN_ID) && !listOutput.includes(`${PLUGIN_ID} (disabled)`);
33
+
34
+ if (alreadyEnabled) {
35
+ out.push(`Plugin "${PLUGIN_ID}" is already enabled in Hermes.`);
36
+ return { exitCode: 0, output: out };
37
+ }
38
+
39
+ if (flags.print) {
40
+ out.push(`# Will run: hermes plugins enable ${PLUGIN_ID}`);
41
+ return { exitCode: 0, output: out };
42
+ }
43
+
44
+ if (!flags.yes) {
45
+ out.push(`Pending: hermes plugins enable ${PLUGIN_ID}`);
46
+ out.push(``);
47
+ out.push(`Dry-run only. Re-run with -y to apply.`);
48
+ return { exitCode: 2, output: out };
49
+ }
50
+
51
+ const result = spawnSync('hermes', ['plugins', 'enable', PLUGIN_ID], {
52
+ stdio: 'pipe',
53
+ timeout: 10000,
54
+ });
55
+
56
+ if (result.status !== 0) {
57
+ out.push(`Failed to enable plugin: ${result.stderr?.toString() ?? result.stdout?.toString() ?? 'unknown error'}`);
58
+ return { exitCode: 1, output: out };
59
+ }
60
+
61
+ out.push(`Plugin "${PLUGIN_ID}" enabled in Hermes.`);
62
+ out.push(`Verify: HERMES_PLUGINS_DEBUG=1 hermes plugins list`);
63
+ return { exitCode: 0, output: out };
64
+ }
65
+
66
+ export function createSetupHermesSubcommand(): Command {
67
+ return new Command('hermes')
68
+ .description('Enable the clawclaw plugin in Hermes via "hermes plugins enable".')
69
+ .option('-y, --yes', 'Apply changes (default is dry-run)')
70
+ .option('--print', 'Only print the command that would be run; do not execute')
71
+ .action((opts: { yes?: boolean; print?: boolean }) => {
72
+ const result = runHermesSetup({ yes: opts.yes, print: opts.print });
73
+ for (const line of result.output) console.log(line);
74
+ if (result.exitCode !== 0) process.exit(result.exitCode);
75
+ });
76
+ }
@@ -1,13 +1,13 @@
1
- import { Command } from 'commander';
2
- import { createSetupOpenclawSubcommand } from './openclaw.js';
3
- import { createSetupHermesSubcommand } from './hermes.js';
4
- import { createSetupCodexSubcommand } from './codex.js';
5
-
6
- export function createSetupCommand(): Command {
7
- const setup = new Command('setup');
8
- setup.description('Install recommended config snippets into agent host config files.');
9
- setup.addCommand(createSetupOpenclawSubcommand());
10
- setup.addCommand(createSetupHermesSubcommand());
11
- setup.addCommand(createSetupCodexSubcommand());
12
- return setup;
13
- }
1
+ import { Command } from 'commander';
2
+ import { createSetupOpenclawSubcommand } from './openclaw.js';
3
+ import { createSetupHermesSubcommand } from './hermes.js';
4
+ import { createSetupCodexSubcommand } from './codex.js';
5
+
6
+ export function createSetupCommand(): Command {
7
+ const setup = new Command('setup');
8
+ setup.description('Install recommended config snippets into agent host config files.');
9
+ setup.addCommand(createSetupOpenclawSubcommand());
10
+ setup.addCommand(createSetupHermesSubcommand());
11
+ setup.addCommand(createSetupCodexSubcommand());
12
+ return setup;
13
+ }