@hellcoder/companion 0.96.0

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 (242) hide show
  1. package/bin/cli.ts +168 -0
  2. package/bin/ctl.ts +528 -0
  3. package/bin/generate-token.ts +28 -0
  4. package/dist/apple-touch-icon.png +0 -0
  5. package/dist/assets/AgentsPage-DCFhrJ28.js +13 -0
  6. package/dist/assets/CronManager-EGwLJONv.js +1 -0
  7. package/dist/assets/IntegrationsPage-CTMRnbQS.js +1 -0
  8. package/dist/assets/LinearOAuthSettingsPage-CgQFMIgr.js +1 -0
  9. package/dist/assets/LinearSettingsPage-C9nok1qi.js +1 -0
  10. package/dist/assets/Playground-BV3k0RbV.js +109 -0
  11. package/dist/assets/PromptsPage-CFojqNKP.js +4 -0
  12. package/dist/assets/RunsPage-DUJ1QUSa.js +1 -0
  13. package/dist/assets/SandboxManager-CrVQ-VU_.js +8 -0
  14. package/dist/assets/SettingsPage-D1fPCL19.js +1 -0
  15. package/dist/assets/TailscalePage-D06cyvyC.js +1 -0
  16. package/dist/assets/index-BhUa1e6X.css +1 -0
  17. package/dist/assets/index-DkqeP-R9.js +134 -0
  18. package/dist/assets/sw-register-BibwRdvC.js +1 -0
  19. package/dist/assets/workbox-window.prod.es5-BIl4cyR9.js +2 -0
  20. package/dist/favicon.svg +8 -0
  21. package/dist/fonts/MesloLGSNerdFontMono-Bold.woff2 +0 -0
  22. package/dist/fonts/MesloLGSNerdFontMono-Regular.woff2 +0 -0
  23. package/dist/icon-192.png +0 -0
  24. package/dist/icon-512.png +0 -0
  25. package/dist/index.html +20 -0
  26. package/dist/logo-codex.svg +14 -0
  27. package/dist/logo-docker.svg +4 -0
  28. package/dist/logo.svg +14 -0
  29. package/dist/manifest.json +24 -0
  30. package/dist/sw.js +2 -0
  31. package/package.json +104 -0
  32. package/server/agent-cron-migrator.test.ts +610 -0
  33. package/server/agent-cron-migrator.ts +85 -0
  34. package/server/agent-executor.test.ts +1108 -0
  35. package/server/agent-executor.ts +346 -0
  36. package/server/agent-store.test.ts +588 -0
  37. package/server/agent-store.ts +185 -0
  38. package/server/agent-types.ts +138 -0
  39. package/server/ai-validation-settings.test.ts +128 -0
  40. package/server/ai-validation-settings.ts +35 -0
  41. package/server/ai-validator.test.ts +387 -0
  42. package/server/ai-validator.ts +271 -0
  43. package/server/auth-manager.test.ts +83 -0
  44. package/server/auth-manager.ts +150 -0
  45. package/server/auto-namer.test.ts +252 -0
  46. package/server/auto-namer.ts +78 -0
  47. package/server/backend-adapter.test.ts +38 -0
  48. package/server/backend-adapter.ts +54 -0
  49. package/server/cache-headers.test.ts +98 -0
  50. package/server/cache-headers.ts +61 -0
  51. package/server/claude-adapter.test.ts +1363 -0
  52. package/server/claude-adapter.ts +889 -0
  53. package/server/claude-container-auth.test.ts +44 -0
  54. package/server/claude-container-auth.ts +30 -0
  55. package/server/claude-protocol-contract.test.ts +71 -0
  56. package/server/claude-protocol-drift.test.ts +78 -0
  57. package/server/claude-session-discovery.test.ts +132 -0
  58. package/server/claude-session-discovery.ts +157 -0
  59. package/server/claude-session-history.test.ts +158 -0
  60. package/server/claude-session-history.ts +410 -0
  61. package/server/cli-launcher.test.ts +1343 -0
  62. package/server/cli-launcher.ts +1298 -0
  63. package/server/cli.test.ts +16 -0
  64. package/server/codex-adapter.test.ts +5545 -0
  65. package/server/codex-adapter.ts +3062 -0
  66. package/server/codex-container-auth.test.ts +50 -0
  67. package/server/codex-container-auth.ts +24 -0
  68. package/server/codex-home.test.ts +61 -0
  69. package/server/codex-home.ts +26 -0
  70. package/server/codex-protocol-contract.test.ts +96 -0
  71. package/server/codex-protocol-drift.test.ts +123 -0
  72. package/server/codex-ws-proxy.cjs +226 -0
  73. package/server/commands-discovery.test.ts +179 -0
  74. package/server/commands-discovery.ts +81 -0
  75. package/server/constants.ts +7 -0
  76. package/server/container-manager.test.ts +1211 -0
  77. package/server/container-manager.ts +1053 -0
  78. package/server/cron-scheduler.test.ts +957 -0
  79. package/server/cron-scheduler.ts +243 -0
  80. package/server/cron-store.test.ts +422 -0
  81. package/server/cron-store.ts +148 -0
  82. package/server/cron-types.ts +63 -0
  83. package/server/env-manager.test.ts +268 -0
  84. package/server/env-manager.ts +161 -0
  85. package/server/event-bus-types.ts +64 -0
  86. package/server/event-bus.test.ts +244 -0
  87. package/server/event-bus.ts +124 -0
  88. package/server/execution-store.test.ts +307 -0
  89. package/server/execution-store.ts +170 -0
  90. package/server/fs-utils.ts +15 -0
  91. package/server/git-utils.test.ts +938 -0
  92. package/server/git-utils.ts +421 -0
  93. package/server/github-pr.test.ts +498 -0
  94. package/server/github-pr.ts +379 -0
  95. package/server/image-pull-manager.test.ts +303 -0
  96. package/server/image-pull-manager.ts +279 -0
  97. package/server/index.ts +396 -0
  98. package/server/linear-agent-bridge.test.ts +1157 -0
  99. package/server/linear-agent-bridge.ts +629 -0
  100. package/server/linear-agent.test.ts +473 -0
  101. package/server/linear-agent.ts +479 -0
  102. package/server/linear-cache.test.ts +136 -0
  103. package/server/linear-cache.ts +113 -0
  104. package/server/linear-connections.test.ts +350 -0
  105. package/server/linear-connections.ts +231 -0
  106. package/server/linear-credential-migration.test.ts +337 -0
  107. package/server/linear-credential-migration.ts +63 -0
  108. package/server/linear-oauth-connections-migration.test.ts +268 -0
  109. package/server/linear-oauth-connections.test.ts +365 -0
  110. package/server/linear-oauth-connections.ts +294 -0
  111. package/server/linear-project-manager.test.ts +162 -0
  112. package/server/linear-project-manager.ts +111 -0
  113. package/server/linear-prompt-builder.test.ts +74 -0
  114. package/server/linear-prompt-builder.ts +61 -0
  115. package/server/linear-staging.test.ts +276 -0
  116. package/server/linear-staging.ts +142 -0
  117. package/server/logger.test.ts +393 -0
  118. package/server/logger.ts +259 -0
  119. package/server/metrics-collector.test.ts +413 -0
  120. package/server/metrics-collector.ts +350 -0
  121. package/server/metrics-types.ts +108 -0
  122. package/server/middleware/managed-auth.test.ts +264 -0
  123. package/server/middleware/managed-auth.ts +195 -0
  124. package/server/novnc-proxy.test.ts +333 -0
  125. package/server/novnc-proxy.ts +99 -0
  126. package/server/path-resolver.test.ts +552 -0
  127. package/server/path-resolver.ts +186 -0
  128. package/server/paths.test.ts +31 -0
  129. package/server/paths.ts +11 -0
  130. package/server/pr-poller.test.ts +191 -0
  131. package/server/pr-poller.ts +162 -0
  132. package/server/prompt-manager.test.ts +211 -0
  133. package/server/prompt-manager.ts +211 -0
  134. package/server/protocol/claude-upstream/README.md +19 -0
  135. package/server/protocol/claude-upstream/sdk.d.ts.txt +1943 -0
  136. package/server/protocol/codex-upstream/ClientNotification.ts.txt +5 -0
  137. package/server/protocol/codex-upstream/ClientRequest.ts.txt +60 -0
  138. package/server/protocol/codex-upstream/README.md +18 -0
  139. package/server/protocol/codex-upstream/ServerNotification.ts.txt +41 -0
  140. package/server/protocol/codex-upstream/ServerRequest.ts.txt +16 -0
  141. package/server/protocol/codex-upstream/v2/DynamicToolCallParams.ts.txt +6 -0
  142. package/server/protocol/codex-upstream/v2/DynamicToolCallResponse.ts.txt +6 -0
  143. package/server/protocol-monitor.ts +50 -0
  144. package/server/recorder.test.ts +454 -0
  145. package/server/recorder.ts +374 -0
  146. package/server/recording-hub/compat-validator.test.ts +150 -0
  147. package/server/recording-hub/compat-validator.ts +284 -0
  148. package/server/recording-hub/diagnostics.test.ts +140 -0
  149. package/server/recording-hub/diagnostics.ts +299 -0
  150. package/server/recording-hub/hub-config.test.ts +44 -0
  151. package/server/recording-hub/hub-config.ts +19 -0
  152. package/server/recording-hub/hub-routes.test.ts +417 -0
  153. package/server/recording-hub/hub-routes.ts +236 -0
  154. package/server/recording-hub/hub-store.test.ts +262 -0
  155. package/server/recording-hub/hub-store.ts +265 -0
  156. package/server/recording-hub/replay-adapter.test.ts +294 -0
  157. package/server/recording-hub/replay-adapter.ts +207 -0
  158. package/server/relay-client.test.ts +337 -0
  159. package/server/relay-client.ts +320 -0
  160. package/server/replay.test.ts +200 -0
  161. package/server/replay.ts +78 -0
  162. package/server/routes/agent-routes.test.ts +1400 -0
  163. package/server/routes/agent-routes.ts +409 -0
  164. package/server/routes/cron-routes.test.ts +881 -0
  165. package/server/routes/cron-routes.ts +103 -0
  166. package/server/routes/env-routes.test.ts +383 -0
  167. package/server/routes/env-routes.ts +95 -0
  168. package/server/routes/fs-routes.test.ts +1198 -0
  169. package/server/routes/fs-routes.ts +605 -0
  170. package/server/routes/git-routes.test.ts +813 -0
  171. package/server/routes/git-routes.ts +97 -0
  172. package/server/routes/linear-agent-routes.test.ts +721 -0
  173. package/server/routes/linear-agent-routes.ts +304 -0
  174. package/server/routes/linear-connection-routes.test.ts +927 -0
  175. package/server/routes/linear-connection-routes.ts +244 -0
  176. package/server/routes/linear-oauth-connection-routes.test.ts +406 -0
  177. package/server/routes/linear-oauth-connection-routes.ts +129 -0
  178. package/server/routes/linear-routes.test.ts +1510 -0
  179. package/server/routes/linear-routes.ts +953 -0
  180. package/server/routes/metrics-routes.test.ts +103 -0
  181. package/server/routes/metrics-routes.ts +13 -0
  182. package/server/routes/prompt-routes.ts +67 -0
  183. package/server/routes/sandbox-routes.test.ts +513 -0
  184. package/server/routes/sandbox-routes.ts +127 -0
  185. package/server/routes/settings-routes.ts +270 -0
  186. package/server/routes/skills-routes.test.ts +690 -0
  187. package/server/routes/skills-routes.ts +100 -0
  188. package/server/routes/system-routes.test.ts +637 -0
  189. package/server/routes/system-routes.ts +228 -0
  190. package/server/routes/tailscale-routes.test.ts +176 -0
  191. package/server/routes/tailscale-routes.ts +22 -0
  192. package/server/routes.test.ts +4655 -0
  193. package/server/routes.ts +1277 -0
  194. package/server/sandbox-manager.test.ts +378 -0
  195. package/server/sandbox-manager.ts +168 -0
  196. package/server/service.test.ts +1419 -0
  197. package/server/service.ts +718 -0
  198. package/server/session-creation-service.test.ts +661 -0
  199. package/server/session-creation-service.ts +473 -0
  200. package/server/session-git-info.ts +104 -0
  201. package/server/session-linear-issues.test.ts +118 -0
  202. package/server/session-linear-issues.ts +88 -0
  203. package/server/session-names.test.ts +94 -0
  204. package/server/session-names.ts +67 -0
  205. package/server/session-orchestrator.test.ts +1784 -0
  206. package/server/session-orchestrator.ts +973 -0
  207. package/server/session-state-machine.test.ts +606 -0
  208. package/server/session-state-machine.ts +207 -0
  209. package/server/session-store.test.ts +290 -0
  210. package/server/session-store.ts +146 -0
  211. package/server/session-types.ts +509 -0
  212. package/server/settings-manager.test.ts +275 -0
  213. package/server/settings-manager.ts +173 -0
  214. package/server/tailscale-manager.test.ts +553 -0
  215. package/server/tailscale-manager.ts +451 -0
  216. package/server/terminal-manager.ts +240 -0
  217. package/server/update-checker.test.ts +306 -0
  218. package/server/update-checker.ts +197 -0
  219. package/server/usage-limits.test.ts +536 -0
  220. package/server/usage-limits.ts +225 -0
  221. package/server/worktree-tracker.test.ts +243 -0
  222. package/server/worktree-tracker.ts +84 -0
  223. package/server/ws-auth.test.ts +59 -0
  224. package/server/ws-auth.ts +41 -0
  225. package/server/ws-bridge-browser-ingest.test.ts +272 -0
  226. package/server/ws-bridge-browser-ingest.ts +72 -0
  227. package/server/ws-bridge-browser.ts +112 -0
  228. package/server/ws-bridge-cli-ingest.test.ts +302 -0
  229. package/server/ws-bridge-cli-ingest.ts +81 -0
  230. package/server/ws-bridge-codex.test.ts +1837 -0
  231. package/server/ws-bridge-codex.ts +266 -0
  232. package/server/ws-bridge-controls.test.ts +124 -0
  233. package/server/ws-bridge-controls.ts +20 -0
  234. package/server/ws-bridge-persist.test.ts +296 -0
  235. package/server/ws-bridge-persist.ts +66 -0
  236. package/server/ws-bridge-publish.test.ts +234 -0
  237. package/server/ws-bridge-publish.ts +79 -0
  238. package/server/ws-bridge-replay.test.ts +44 -0
  239. package/server/ws-bridge-replay.ts +61 -0
  240. package/server/ws-bridge-types.ts +106 -0
  241. package/server/ws-bridge.test.ts +4777 -0
  242. package/server/ws-bridge.ts +1279 -0
@@ -0,0 +1,179 @@
1
+ import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+
5
+ let tempDir: string;
6
+ let discoverFn: typeof import("./commands-discovery.js").discoverCommandsAndSkills;
7
+
8
+ const mockHomedir = vi.hoisted(() => {
9
+ let dir = "";
10
+ return {
11
+ get: () => dir,
12
+ set: (d: string) => {
13
+ dir = d;
14
+ },
15
+ };
16
+ });
17
+
18
+ vi.mock("node:os", async (importOriginal) => {
19
+ const actual = await importOriginal<typeof import("node:os")>();
20
+ return {
21
+ ...actual,
22
+ homedir: () => mockHomedir.get(),
23
+ };
24
+ });
25
+
26
+ beforeEach(async () => {
27
+ tempDir = mkdtempSync(join(tmpdir(), "cmd-discovery-test-"));
28
+ mockHomedir.set(tempDir);
29
+ vi.resetModules();
30
+ const mod = await import("./commands-discovery.js");
31
+ discoverFn = mod.discoverCommandsAndSkills;
32
+ });
33
+
34
+ afterEach(() => {
35
+ rmSync(tempDir, { recursive: true, force: true });
36
+ });
37
+
38
+ // ── Helper to create directory structure ──────────────────────────────────────
39
+
40
+ function mkdirp(path: string) {
41
+ mkdirSync(path, { recursive: true });
42
+ }
43
+
44
+ // ── Tests ────────────────────────────────────────────────────────────────────
45
+
46
+ describe("discoverCommandsAndSkills", () => {
47
+ it("returns empty arrays when no directories exist", async () => {
48
+ // When there are no .claude directories at all, the function should
49
+ // return empty arrays without throwing.
50
+ const result = await discoverFn("/nonexistent/path");
51
+ expect(result.slash_commands).toEqual([]);
52
+ expect(result.skills).toEqual([]);
53
+ });
54
+
55
+ it("discovers user-level commands from ~/.claude/commands/*.md", async () => {
56
+ // User commands are .md files in ~/.claude/commands/
57
+ const cmdDir = join(tempDir, ".claude", "commands");
58
+ mkdirp(cmdDir);
59
+ writeFileSync(join(cmdDir, "commit.md"), "# Commit command");
60
+ writeFileSync(join(cmdDir, "review-pr.md"), "# Review PR command");
61
+ // Non-md files should be ignored
62
+ writeFileSync(join(cmdDir, "notes.txt"), "not a command");
63
+
64
+ const result = await discoverFn();
65
+ expect(result.slash_commands).toEqual(["commit", "review-pr"]);
66
+ });
67
+
68
+ it("discovers project-level commands from {cwd}/.claude/commands/*.md", async () => {
69
+ // Project commands are .md files in {cwd}/.claude/commands/
70
+ const projectDir = join(tempDir, "my-project");
71
+ const cmdDir = join(projectDir, ".claude", "commands");
72
+ mkdirp(cmdDir);
73
+ writeFileSync(join(cmdDir, "deploy.md"), "# Deploy command");
74
+
75
+ const result = await discoverFn(projectDir);
76
+ expect(result.slash_commands).toContain("deploy");
77
+ });
78
+
79
+ it("deduplicates commands between user and project level", async () => {
80
+ // If the same command name exists at both user and project level,
81
+ // it should appear only once in the result.
82
+ const userCmdDir = join(tempDir, ".claude", "commands");
83
+ mkdirp(userCmdDir);
84
+ writeFileSync(join(userCmdDir, "commit.md"), "# User commit");
85
+
86
+ const projectDir = join(tempDir, "my-project");
87
+ const projectCmdDir = join(projectDir, ".claude", "commands");
88
+ mkdirp(projectCmdDir);
89
+ writeFileSync(join(projectCmdDir, "commit.md"), "# Project commit");
90
+ writeFileSync(join(projectCmdDir, "test.md"), "# Test command");
91
+
92
+ const result = await discoverFn(projectDir);
93
+ // "commit" should appear only once, "test" should also appear
94
+ expect(result.slash_commands).toEqual(["commit", "test"]);
95
+ });
96
+
97
+ it("returns sorted command names", async () => {
98
+ // Commands should be sorted alphabetically regardless of filesystem order.
99
+ const cmdDir = join(tempDir, ".claude", "commands");
100
+ mkdirp(cmdDir);
101
+ writeFileSync(join(cmdDir, "zebra.md"), "");
102
+ writeFileSync(join(cmdDir, "alpha.md"), "");
103
+ writeFileSync(join(cmdDir, "middle.md"), "");
104
+
105
+ const result = await discoverFn();
106
+ expect(result.slash_commands).toEqual(["alpha", "middle", "zebra"]);
107
+ });
108
+
109
+ it("discovers skills from ~/.claude/skills/*/SKILL.md", async () => {
110
+ // Skills are identified by directories under ~/.claude/skills/ that
111
+ // contain a SKILL.md file. The directory name is used as the slug.
112
+ const skillsDir = join(tempDir, ".claude", "skills");
113
+ mkdirp(join(skillsDir, "my-skill"));
114
+ writeFileSync(join(skillsDir, "my-skill", "SKILL.md"), "---\nname: My Skill\n---\n# Skill");
115
+ mkdirp(join(skillsDir, "another-skill"));
116
+ writeFileSync(join(skillsDir, "another-skill", "SKILL.md"), "# Another");
117
+
118
+ const result = await discoverFn();
119
+ expect(result.skills).toEqual(["another-skill", "my-skill"]);
120
+ });
121
+
122
+ it("ignores skill directories without SKILL.md", async () => {
123
+ // A directory under ~/.claude/skills/ without a SKILL.md should be ignored.
124
+ const skillsDir = join(tempDir, ".claude", "skills");
125
+ mkdirp(join(skillsDir, "valid-skill"));
126
+ writeFileSync(join(skillsDir, "valid-skill", "SKILL.md"), "# Valid");
127
+ mkdirp(join(skillsDir, "incomplete-skill"));
128
+ // No SKILL.md in incomplete-skill
129
+
130
+ const result = await discoverFn();
131
+ expect(result.skills).toEqual(["valid-skill"]);
132
+ });
133
+
134
+ it("returns sorted skill names", async () => {
135
+ // Skills should be sorted alphabetically by directory name.
136
+ const skillsDir = join(tempDir, ".claude", "skills");
137
+ mkdirp(join(skillsDir, "zulu"));
138
+ writeFileSync(join(skillsDir, "zulu", "SKILL.md"), "");
139
+ mkdirp(join(skillsDir, "alpha"));
140
+ writeFileSync(join(skillsDir, "alpha", "SKILL.md"), "");
141
+
142
+ const result = await discoverFn();
143
+ expect(result.skills).toEqual(["alpha", "zulu"]);
144
+ });
145
+
146
+ it("handles cwd being undefined", async () => {
147
+ // When no cwd is provided, only user-level items should be discovered.
148
+ const cmdDir = join(tempDir, ".claude", "commands");
149
+ mkdirp(cmdDir);
150
+ writeFileSync(join(cmdDir, "global-cmd.md"), "");
151
+
152
+ const result = await discoverFn(undefined);
153
+ expect(result.slash_commands).toEqual(["global-cmd"]);
154
+ });
155
+
156
+ it("ignores non-file entries in commands directory", async () => {
157
+ // Subdirectories inside commands/ should be ignored.
158
+ const cmdDir = join(tempDir, ".claude", "commands");
159
+ mkdirp(cmdDir);
160
+ writeFileSync(join(cmdDir, "valid.md"), "");
161
+ mkdirp(join(cmdDir, "subdirectory.md")); // directory named like .md
162
+
163
+ const result = await discoverFn();
164
+ // Only the file should be picked up, not the directory
165
+ expect(result.slash_commands).toEqual(["valid"]);
166
+ });
167
+
168
+ it("ignores non-directory entries in skills directory", async () => {
169
+ // Regular files directly under ~/.claude/skills/ should be ignored.
170
+ const skillsDir = join(tempDir, ".claude", "skills");
171
+ mkdirp(skillsDir);
172
+ writeFileSync(join(skillsDir, "stray-file.md"), "not a skill");
173
+ mkdirp(join(skillsDir, "real-skill"));
174
+ writeFileSync(join(skillsDir, "real-skill", "SKILL.md"), "");
175
+
176
+ const result = await discoverFn();
177
+ expect(result.skills).toEqual(["real-skill"]);
178
+ });
179
+ });
@@ -0,0 +1,81 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ /**
7
+ * Discover slash commands and skills from the filesystem.
8
+ * Used to pre-populate session state before system.init arrives from the CLI.
9
+ *
10
+ * Commands come from:
11
+ * - ~/.claude/commands/*.md (user-level)
12
+ * - {cwd}/.claude/commands/*.md (project-level)
13
+ *
14
+ * Skills come from:
15
+ * - ~/.claude/skills/`*`/SKILL.md (user-level, directory name = slug)
16
+ *
17
+ * Returns sorted, deduplicated arrays. Never throws.
18
+ */
19
+ export async function discoverCommandsAndSkills(cwd?: string): Promise<{
20
+ slash_commands: string[];
21
+ skills: string[];
22
+ }> {
23
+ const commandSet = new Set<string>();
24
+ const skills: string[] = [];
25
+
26
+ const home = homedir();
27
+
28
+ // User-level commands: ~/.claude/commands/*.md
29
+ try {
30
+ const userCommandsDir = join(home, ".claude", "commands");
31
+ if (existsSync(userCommandsDir)) {
32
+ const entries = await readdir(userCommandsDir, { withFileTypes: true });
33
+ for (const e of entries) {
34
+ if (e.isFile() && e.name.endsWith(".md")) {
35
+ commandSet.add(e.name.replace(/\.md$/, ""));
36
+ }
37
+ }
38
+ }
39
+ } catch {
40
+ /* gracefully ignore */
41
+ }
42
+
43
+ // Project-level commands: {cwd}/.claude/commands/*.md
44
+ if (cwd) {
45
+ try {
46
+ const projectCommandsDir = join(cwd, ".claude", "commands");
47
+ if (existsSync(projectCommandsDir)) {
48
+ const entries = await readdir(projectCommandsDir, { withFileTypes: true });
49
+ for (const e of entries) {
50
+ if (e.isFile() && e.name.endsWith(".md")) {
51
+ commandSet.add(e.name.replace(/\.md$/, ""));
52
+ }
53
+ }
54
+ }
55
+ } catch {
56
+ /* gracefully ignore */
57
+ }
58
+ }
59
+
60
+ // User-level skills: ~/.claude/skills/*/SKILL.md
61
+ try {
62
+ const skillsDir = join(home, ".claude", "skills");
63
+ if (existsSync(skillsDir)) {
64
+ const entries = await readdir(skillsDir, { withFileTypes: true });
65
+ for (const entry of entries) {
66
+ if (!entry.isDirectory()) continue;
67
+ const skillMdPath = join(skillsDir, entry.name, "SKILL.md");
68
+ if (existsSync(skillMdPath)) {
69
+ skills.push(entry.name);
70
+ }
71
+ }
72
+ }
73
+ } catch {
74
+ /* gracefully ignore */
75
+ }
76
+
77
+ const slash_commands = Array.from(commandSet).sort();
78
+ skills.sort();
79
+
80
+ return { slash_commands, skills };
81
+ }
@@ -0,0 +1,7 @@
1
+ export const DEFAULT_PORT_DEV = 3457;
2
+ export const DEFAULT_PORT_PROD = 3456;
3
+
4
+ // Container port constants — shared between routes.ts and session-creation-service.ts
5
+ export const VSCODE_EDITOR_CONTAINER_PORT = 13337;
6
+ export const CODEX_APP_SERVER_CONTAINER_PORT = Number(process.env.COMPANION_CODEX_CONTAINER_WS_PORT || "4502");
7
+ export const NOVNC_CONTAINER_PORT = 6080;