setup-cc-room 0.2.2 → 0.2.4

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.
@@ -0,0 +1,20 @@
1
+ // src/daemon-resolver.ts
2
+ import { existsSync } from "fs";
3
+ import { join } from "path";
4
+ function resolveDaemonSource(setupPackageRoot) {
5
+ const candidates = [
6
+ join(setupPackageRoot, "vendor", "daemon", "dist", "index.js"),
7
+ join(setupPackageRoot, "..", "daemon", "dist-bundle", "index.js"),
8
+ // モノレポ開発時のフォールバック(チャンク分割あり・単体コピーでは動かないことがある)
9
+ join(setupPackageRoot, "..", "daemon", "dist", "index.js"),
10
+ join(setupPackageRoot, "..", "..", "node_modules", "@cc-room", "daemon", "dist", "index.js")
11
+ ];
12
+ for (const c of candidates) {
13
+ if (existsSync(c)) return c;
14
+ }
15
+ return null;
16
+ }
17
+
18
+ export {
19
+ resolveDaemonSource
20
+ };
@@ -0,0 +1,151 @@
1
+ import {
2
+ t
3
+ } from "./chunk-Y4O4R4IQ.js";
4
+ import {
5
+ resolveCcRoomDir
6
+ } from "./chunk-WCEABGOA.js";
7
+
8
+ // src/settings-merger.ts
9
+ import { readFileSync, writeFileSync, existsSync, copyFileSync, mkdirSync } from "fs";
10
+ import { join } from "path";
11
+ import { homedir } from "os";
12
+ import { execFileSync } from "child_process";
13
+ var SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
14
+ function resolveDaemonCommand(env = process.env) {
15
+ const bundled = join(resolveCcRoomDir(env), "bin", "cc-room-daemon");
16
+ if (existsSync(bundled)) return bundled;
17
+ try {
18
+ const which = execFileSync("which", ["cc-room-daemon"], {
19
+ encoding: "utf-8",
20
+ stdio: "pipe"
21
+ }).trim();
22
+ if (which) return which;
23
+ } catch {
24
+ }
25
+ return bundled;
26
+ }
27
+ function isCcRoomHookCommand(command) {
28
+ if (!command) return false;
29
+ return command.includes("cc-room-daemon") && command.includes("hook");
30
+ }
31
+ function buildCcRoomHooks(daemonBin) {
32
+ return {
33
+ UserPromptSubmit: [
34
+ {
35
+ hooks: [
36
+ {
37
+ type: "command",
38
+ command: `${daemonBin} hook user-prompt-submit`
39
+ }
40
+ ]
41
+ }
42
+ ],
43
+ PostToolUse: [
44
+ {
45
+ matcher: "Write|Edit",
46
+ hooks: [
47
+ {
48
+ type: "command",
49
+ command: `${daemonBin} hook post-tool-use`
50
+ }
51
+ ]
52
+ }
53
+ ],
54
+ Stop: [
55
+ {
56
+ hooks: [
57
+ {
58
+ type: "command",
59
+ command: `${daemonBin} hook session-stop`
60
+ }
61
+ ]
62
+ }
63
+ ]
64
+ };
65
+ }
66
+ function buildCcRoomMcp(daemonBin) {
67
+ return {
68
+ "cc-room": {
69
+ type: "stdio",
70
+ command: daemonBin,
71
+ args: ["mcp"]
72
+ }
73
+ };
74
+ }
75
+ function mergeSettings() {
76
+ let settings = {};
77
+ mkdirSync(join(homedir(), ".claude"), { recursive: true });
78
+ if (existsSync(SETTINGS_PATH)) {
79
+ try {
80
+ settings = JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
81
+ } catch {
82
+ console.log(t("settings.parse_fail_backup"));
83
+ copyFileSync(SETTINGS_PATH, SETTINGS_PATH + ".backup");
84
+ settings = {};
85
+ }
86
+ }
87
+ const daemonBin = resolveDaemonCommand();
88
+ const ccRoomHooks = buildCcRoomHooks(daemonBin);
89
+ const ccRoomMcp = buildCcRoomMcp(daemonBin);
90
+ const existingHooks = settings.hooks ?? {};
91
+ const mergedHooks = { ...existingHooks };
92
+ for (const [event, newEntries] of Object.entries(ccRoomHooks)) {
93
+ const existing = mergedHooks[event] ?? [];
94
+ const filtered = existing.filter((e) => {
95
+ const entry = e;
96
+ return !entry.hooks?.some((h) => isCcRoomHookCommand(h.command));
97
+ });
98
+ mergedHooks[event] = [...filtered, ...newEntries];
99
+ }
100
+ const existingMcp = settings.mcpServers ?? {};
101
+ const mergedMcp = { ...existingMcp, ...ccRoomMcp };
102
+ settings.hooks = mergedHooks;
103
+ settings.mcpServers = mergedMcp;
104
+ writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", { mode: 420 });
105
+ console.log(t("settings.updated", { path: SETTINGS_PATH }));
106
+ }
107
+ function stripCcRoomFromSettings(settings) {
108
+ const next = { ...settings };
109
+ const existingHooks = settings.hooks ?? {};
110
+ const strippedHooks = {};
111
+ for (const [event, entries] of Object.entries(existingHooks)) {
112
+ strippedHooks[event] = (entries ?? []).filter((e) => {
113
+ const entry = e;
114
+ return !entry.hooks?.some((h) => isCcRoomHookCommand(h.command));
115
+ });
116
+ }
117
+ if (Object.keys(existingHooks).length > 0 || Object.keys(strippedHooks).length > 0) {
118
+ next.hooks = strippedHooks;
119
+ }
120
+ const existingMcp = settings.mcpServers ?? {};
121
+ if (Object.keys(existingMcp).length > 0) {
122
+ const { ["cc-room"]: _removed, ...rest } = existingMcp;
123
+ next.mcpServers = rest;
124
+ }
125
+ return next;
126
+ }
127
+ function unmergeSettings() {
128
+ if (!existsSync(SETTINGS_PATH)) {
129
+ console.log(t("settings.skip_missing", { path: SETTINGS_PATH }));
130
+ return;
131
+ }
132
+ let settings;
133
+ try {
134
+ settings = JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
135
+ } catch {
136
+ console.log(t("settings.parse_fail_skip"));
137
+ return;
138
+ }
139
+ const next = stripCcRoomFromSettings(settings);
140
+ writeFileSync(SETTINGS_PATH, JSON.stringify(next, null, 2) + "\n", { mode: 420 });
141
+ console.log(t("settings.stripped", { path: SETTINGS_PATH }));
142
+ }
143
+
144
+ export {
145
+ resolveDaemonCommand,
146
+ buildCcRoomHooks,
147
+ buildCcRoomMcp,
148
+ mergeSettings,
149
+ stripCcRoomFromSettings,
150
+ unmergeSettings
151
+ };
@@ -0,0 +1,151 @@
1
+ import {
2
+ t
3
+ } from "./chunk-OYW7MM4W.js";
4
+ import {
5
+ resolveCcRoomDir
6
+ } from "./chunk-WCEABGOA.js";
7
+
8
+ // src/settings-merger.ts
9
+ import { readFileSync, writeFileSync, existsSync, copyFileSync, mkdirSync } from "fs";
10
+ import { join } from "path";
11
+ import { homedir } from "os";
12
+ import { execFileSync } from "child_process";
13
+ var SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
14
+ function resolveDaemonCommand(env = process.env) {
15
+ const bundled = join(resolveCcRoomDir(env), "bin", "cc-room-daemon");
16
+ if (existsSync(bundled)) return bundled;
17
+ try {
18
+ const which = execFileSync("which", ["cc-room-daemon"], {
19
+ encoding: "utf-8",
20
+ stdio: "pipe"
21
+ }).trim();
22
+ if (which) return which;
23
+ } catch {
24
+ }
25
+ return bundled;
26
+ }
27
+ function isCcRoomHookCommand(command) {
28
+ if (!command) return false;
29
+ return command.includes("cc-room-daemon") && command.includes("hook");
30
+ }
31
+ function buildCcRoomHooks(daemonBin) {
32
+ return {
33
+ UserPromptSubmit: [
34
+ {
35
+ hooks: [
36
+ {
37
+ type: "command",
38
+ command: `${daemonBin} hook user-prompt-submit`
39
+ }
40
+ ]
41
+ }
42
+ ],
43
+ PostToolUse: [
44
+ {
45
+ matcher: "Write|Edit",
46
+ hooks: [
47
+ {
48
+ type: "command",
49
+ command: `${daemonBin} hook post-tool-use`
50
+ }
51
+ ]
52
+ }
53
+ ],
54
+ Stop: [
55
+ {
56
+ hooks: [
57
+ {
58
+ type: "command",
59
+ command: `${daemonBin} hook session-stop`
60
+ }
61
+ ]
62
+ }
63
+ ]
64
+ };
65
+ }
66
+ function buildCcRoomMcp(daemonBin) {
67
+ return {
68
+ "cc-room": {
69
+ type: "stdio",
70
+ command: daemonBin,
71
+ args: ["mcp"]
72
+ }
73
+ };
74
+ }
75
+ function mergeSettings() {
76
+ let settings = {};
77
+ mkdirSync(join(homedir(), ".claude"), { recursive: true });
78
+ if (existsSync(SETTINGS_PATH)) {
79
+ try {
80
+ settings = JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
81
+ } catch {
82
+ console.log(t("settings.parse_fail_backup"));
83
+ copyFileSync(SETTINGS_PATH, SETTINGS_PATH + ".backup");
84
+ settings = {};
85
+ }
86
+ }
87
+ const daemonBin = resolveDaemonCommand();
88
+ const ccRoomHooks = buildCcRoomHooks(daemonBin);
89
+ const ccRoomMcp = buildCcRoomMcp(daemonBin);
90
+ const existingHooks = settings.hooks ?? {};
91
+ const mergedHooks = { ...existingHooks };
92
+ for (const [event, newEntries] of Object.entries(ccRoomHooks)) {
93
+ const existing = mergedHooks[event] ?? [];
94
+ const filtered = existing.filter((e) => {
95
+ const entry = e;
96
+ return !entry.hooks?.some((h) => isCcRoomHookCommand(h.command));
97
+ });
98
+ mergedHooks[event] = [...filtered, ...newEntries];
99
+ }
100
+ const existingMcp = settings.mcpServers ?? {};
101
+ const mergedMcp = { ...existingMcp, ...ccRoomMcp };
102
+ settings.hooks = mergedHooks;
103
+ settings.mcpServers = mergedMcp;
104
+ writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", { mode: 420 });
105
+ console.log(t("settings.updated", { path: SETTINGS_PATH }));
106
+ }
107
+ function stripCcRoomFromSettings(settings) {
108
+ const next = { ...settings };
109
+ const existingHooks = settings.hooks ?? {};
110
+ const strippedHooks = {};
111
+ for (const [event, entries] of Object.entries(existingHooks)) {
112
+ strippedHooks[event] = (entries ?? []).filter((e) => {
113
+ const entry = e;
114
+ return !entry.hooks?.some((h) => isCcRoomHookCommand(h.command));
115
+ });
116
+ }
117
+ if (Object.keys(existingHooks).length > 0 || Object.keys(strippedHooks).length > 0) {
118
+ next.hooks = strippedHooks;
119
+ }
120
+ const existingMcp = settings.mcpServers ?? {};
121
+ if (Object.keys(existingMcp).length > 0) {
122
+ const { ["cc-room"]: _removed, ...rest } = existingMcp;
123
+ next.mcpServers = rest;
124
+ }
125
+ return next;
126
+ }
127
+ function unmergeSettings() {
128
+ if (!existsSync(SETTINGS_PATH)) {
129
+ console.log(t("settings.skip_missing", { path: SETTINGS_PATH }));
130
+ return;
131
+ }
132
+ let settings;
133
+ try {
134
+ settings = JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
135
+ } catch {
136
+ console.log(t("settings.parse_fail_skip"));
137
+ return;
138
+ }
139
+ const next = stripCcRoomFromSettings(settings);
140
+ writeFileSync(SETTINGS_PATH, JSON.stringify(next, null, 2) + "\n", { mode: 420 });
141
+ console.log(t("settings.stripped", { path: SETTINGS_PATH }));
142
+ }
143
+
144
+ export {
145
+ resolveDaemonCommand,
146
+ buildCcRoomHooks,
147
+ buildCcRoomMcp,
148
+ mergeSettings,
149
+ stripCcRoomFromSettings,
150
+ unmergeSettings
151
+ };
@@ -0,0 +1,131 @@
1
+ import {
2
+ resolveDaemonCommand
3
+ } from "./chunk-DXODI3WA.js";
4
+ import {
5
+ t
6
+ } from "./chunk-OYW7MM4W.js";
7
+ import {
8
+ resolveCcRoomDir
9
+ } from "./chunk-WCEABGOA.js";
10
+
11
+ // src/register-service.ts
12
+ import { writeFileSync, mkdirSync, existsSync } from "fs";
13
+ import { join } from "path";
14
+ import { homedir, platform } from "os";
15
+ import { execFileSync } from "child_process";
16
+ var CC_ROOM_DIR = resolveCcRoomDir();
17
+ function escapeXml(text) {
18
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
19
+ }
20
+ function ccRoomHomeEnvXml() {
21
+ if (!process.env.CC_ROOM_HOME) return "";
22
+ return `
23
+ <key>CC_ROOM_HOME</key>
24
+ <string>${escapeXml(process.env.CC_ROOM_HOME)}</string>`;
25
+ }
26
+ function registerLaunchd() {
27
+ const plistDir = join(homedir(), "Library", "LaunchAgents");
28
+ const plistPath = join(plistDir, "dev.ccroom.daemon.plist");
29
+ const daemonBin = resolveDaemonCommand();
30
+ const logPath = join(CC_ROOM_DIR, "logs", "daemon.log");
31
+ mkdirSync(join(CC_ROOM_DIR, "logs"), { recursive: true });
32
+ mkdirSync(plistDir, { recursive: true });
33
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
34
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
35
+ <plist version="1.0">
36
+ <dict>
37
+ <key>Label</key>
38
+ <string>dev.ccroom.daemon</string>
39
+ <key>ProgramArguments</key>
40
+ <array>
41
+ <string>${escapeXml(daemonBin)}</string>
42
+ </array>
43
+ <key>KeepAlive</key>
44
+ <true/>
45
+ <key>RunAtLoad</key>
46
+ <true/>
47
+ <key>StandardOutPath</key>
48
+ <string>${escapeXml(logPath)}</string>
49
+ <key>StandardErrorPath</key>
50
+ <string>${escapeXml(logPath)}</string>
51
+ <key>EnvironmentVariables</key>
52
+ <dict>
53
+ <key>PATH</key>
54
+ <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>${ccRoomHomeEnvXml()}
55
+ </dict>
56
+ </dict>
57
+ </plist>
58
+ `;
59
+ writeFileSync(plistPath, plist, { mode: 420 });
60
+ try {
61
+ execFileSync("launchctl", ["unload", plistPath], { stdio: "pipe" });
62
+ } catch {
63
+ }
64
+ try {
65
+ execFileSync("launchctl", ["load", plistPath], { stdio: "pipe" });
66
+ console.log(t("svc.launchd_ok", { path: plistPath }));
67
+ } catch (err) {
68
+ const msg = err instanceof Error ? err.message : String(err);
69
+ console.log(t("svc.launchd_fail", { msg }));
70
+ console.log(t("svc.launchd_manual", { path: plistPath }));
71
+ }
72
+ }
73
+ function registerSystemd() {
74
+ const serviceDir = join(homedir(), ".config", "systemd", "user");
75
+ const servicePath = join(serviceDir, "cc-room-daemon.service");
76
+ const daemonBin = resolveDaemonCommand();
77
+ const logPath = join(CC_ROOM_DIR, "logs", "daemon.log");
78
+ mkdirSync(join(CC_ROOM_DIR, "logs"), { recursive: true });
79
+ mkdirSync(serviceDir, { recursive: true });
80
+ const envLine = process.env.CC_ROOM_HOME ? `Environment="CC_ROOM_HOME=${process.env.CC_ROOM_HOME}"
81
+ ` : "";
82
+ const unit = `[Unit]
83
+ Description=cc-room daemon
84
+ After=network.target
85
+
86
+ [Service]
87
+ ExecStart=${daemonBin}
88
+ Restart=on-failure
89
+ RestartSec=5
90
+ ${envLine}StandardOutput=append:${logPath}
91
+ StandardError=append:${logPath}
92
+
93
+ [Install]
94
+ WantedBy=default.target
95
+ `;
96
+ writeFileSync(servicePath, unit, { mode: 420 });
97
+ try {
98
+ execFileSync("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
99
+ execFileSync("systemctl", ["--user", "enable", "--now", "cc-room-daemon"], { stdio: "pipe" });
100
+ console.log(t("svc.systemd_ok", { path: servicePath }));
101
+ } catch (err) {
102
+ const msg = err instanceof Error ? err.message : String(err);
103
+ console.log(t("svc.systemd_fail", { msg }));
104
+ console.log(t("svc.systemd_manual"));
105
+ }
106
+ }
107
+ async function registerService() {
108
+ const os = platform();
109
+ if (os === "darwin") {
110
+ registerLaunchd();
111
+ } else if (os === "linux") {
112
+ registerSystemd();
113
+ } else {
114
+ console.log(t("svc.unsupported", { os }));
115
+ }
116
+ const pidPath = join(CC_ROOM_DIR, "daemon.pid");
117
+ if (!existsSync(pidPath)) {
118
+ console.log(t("svc.waiting"));
119
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
120
+ }
121
+ if (existsSync(pidPath)) {
122
+ console.log(t("svc.started"));
123
+ } else {
124
+ console.log(t("svc.start_unconfirmed"));
125
+ }
126
+ }
127
+
128
+ export {
129
+ escapeXml,
130
+ registerService
131
+ };
@@ -0,0 +1,123 @@
1
+ import {
2
+ unmergeSettings
3
+ } from "./chunk-CVPLSBRZ.js";
4
+ import {
5
+ t
6
+ } from "./chunk-Y4O4R4IQ.js";
7
+ import {
8
+ resolveCcRoomDir
9
+ } from "./chunk-WCEABGOA.js";
10
+
11
+ // src/uninstaller.ts
12
+ import {
13
+ existsSync,
14
+ mkdirSync,
15
+ rmSync,
16
+ unlinkSync
17
+ } from "fs";
18
+ import { join } from "path";
19
+ import { homedir, platform } from "os";
20
+ import { execFileSync, spawnSync } from "child_process";
21
+ var COMMAND_FILES = ["room.md", "private.md", "show.md"];
22
+ function getLaunchdPlistPath(home = homedir()) {
23
+ return join(home, "Library", "LaunchAgents", "dev.ccroom.daemon.plist");
24
+ }
25
+ function getSystemdUnitPath(home = homedir()) {
26
+ return join(home, ".config", "systemd", "user", "cc-room-daemon.service");
27
+ }
28
+ function stopLaunchd(plistPath) {
29
+ if (!existsSync(plistPath)) {
30
+ console.log(t("un.launchd_none"));
31
+ return;
32
+ }
33
+ try {
34
+ execFileSync("launchctl", ["unload", plistPath], { stdio: "pipe" });
35
+ } catch {
36
+ }
37
+ try {
38
+ unlinkSync(plistPath);
39
+ console.log(t("un.launchd_ok", { path: plistPath }));
40
+ } catch (err) {
41
+ const msg = err instanceof Error ? err.message : String(err);
42
+ console.log(t("un.plist_fail", { msg }));
43
+ }
44
+ }
45
+ function stopSystemd(unitPath) {
46
+ try {
47
+ execFileSync("systemctl", ["--user", "disable", "--now", "cc-room-daemon"], { stdio: "pipe" });
48
+ } catch {
49
+ }
50
+ if (existsSync(unitPath)) {
51
+ try {
52
+ unlinkSync(unitPath);
53
+ console.log(t("un.systemd_ok", { path: unitPath }));
54
+ } catch (err) {
55
+ const msg = err instanceof Error ? err.message : String(err);
56
+ console.log(t("un.unit_fail", { msg }));
57
+ }
58
+ } else {
59
+ console.log(t("un.systemd_none"));
60
+ }
61
+ try {
62
+ execFileSync("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
63
+ } catch {
64
+ }
65
+ }
66
+ function removeCommandFiles(commandsDir) {
67
+ mkdirSync(commandsDir, { recursive: true });
68
+ let removed = 0;
69
+ for (const name of COMMAND_FILES) {
70
+ const path = join(commandsDir, name);
71
+ if (existsSync(path)) {
72
+ unlinkSync(path);
73
+ removed++;
74
+ }
75
+ }
76
+ console.log(t("un.commands_ok", { count: removed, dir: commandsDir }));
77
+ }
78
+ function removeCcRoomData(ccRoomDir) {
79
+ if (!existsSync(ccRoomDir)) {
80
+ console.log(t("un.data_skip", { dir: ccRoomDir }));
81
+ return;
82
+ }
83
+ rmSync(ccRoomDir, { recursive: true, force: true });
84
+ console.log(t("un.data_ok", { dir: ccRoomDir }));
85
+ }
86
+ function stopOrphanDaemon() {
87
+ if (platform() === "win32") {
88
+ spawnSync("taskkill", ["/F", "/IM", "cc-room-daemon.exe"], { stdio: "pipe" });
89
+ return;
90
+ }
91
+ spawnSync("pkill", ["-f", "cc-room-daemon"], { stdio: "pipe" });
92
+ }
93
+ async function uninstall() {
94
+ const ccRoomDir = resolveCcRoomDir();
95
+ const commandsDir = join(homedir(), ".claude", "commands");
96
+ const os = platform();
97
+ console.log(t("un.step1"));
98
+ if (os === "darwin") {
99
+ stopLaunchd(getLaunchdPlistPath());
100
+ } else if (os === "linux") {
101
+ stopSystemd(getSystemdUnitPath());
102
+ } else {
103
+ console.log(t("un.os_manual", { os }));
104
+ }
105
+ stopOrphanDaemon();
106
+ console.log(t("un.step2"));
107
+ removeCommandFiles(commandsDir);
108
+ console.log(t("un.step3"));
109
+ unmergeSettings();
110
+ console.log(t("un.step4"));
111
+ removeCcRoomData(ccRoomDir);
112
+ }
113
+
114
+ export {
115
+ getLaunchdPlistPath,
116
+ getSystemdUnitPath,
117
+ stopLaunchd,
118
+ stopSystemd,
119
+ removeCommandFiles,
120
+ removeCcRoomData,
121
+ stopOrphanDaemon,
122
+ uninstall
123
+ };