@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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth-status.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-status.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/auth-status.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,290 @@
1
+ import { describe, expect, it, vi, afterEach } from 'vitest';
2
+ import { formatAuthStatus, readAccountsFile, runAuthStatus } from '../commands/auth-status.js';
3
+ import * as fs from 'node:fs';
4
+ import * as os from 'node:os';
5
+ import * as path from 'node:path';
6
+ // Strip ANSI escape codes for plain-text assertions
7
+ function stripAnsi(s) {
8
+ // eslint-disable-next-line no-control-regex
9
+ return s.replace(/\x1B\[[0-9;]*m/g, '');
10
+ }
11
+ const NOW = new Date('2026-05-02T12:00:00Z').getTime();
12
+ // ── T4: token redaction ──────────────────────────────────────────────────────
13
+ describe('T4 — token redaction', () => {
14
+ it('accessToken and refreshToken values NEVER appear in formatted output', () => {
15
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'olam-auth-status-'));
16
+ const filePath = path.join(tmpDir, 'accounts.json');
17
+ const fixture = [
18
+ {
19
+ id: 'primary',
20
+ accountLabel: 'primary',
21
+ provider: 'claude',
22
+ accessToken: 'secret-access-token-REDACT-ME',
23
+ refreshToken: 'secret-refresh-token-REDACT-ME',
24
+ expiresAt: NOW + 3600_000,
25
+ state: 'active',
26
+ rateLimited: false,
27
+ usage: { requestCount5h: 5, windowStartedAt: null, last429At: null, cumulativeTokens: 0 },
28
+ addedAt: new Date(NOW).toISOString(),
29
+ lastRefreshed: new Date(NOW).toISOString(),
30
+ },
31
+ ];
32
+ fs.writeFileSync(filePath, JSON.stringify(fixture));
33
+ const accounts = readAccountsFile(filePath);
34
+ const { output } = formatAuthStatus(accounts, NOW);
35
+ expect(output).not.toContain('secret-access-token-REDACT-ME');
36
+ expect(output).not.toContain('secret-refresh-token-REDACT-ME');
37
+ fs.rmSync(tmpDir, { recursive: true });
38
+ });
39
+ });
40
+ // ── Picked credential first ──────────────────────────────────────────────────
41
+ describe('pickCredential selection', () => {
42
+ it('credential with lower req5h is shown first and marked ← selected', () => {
43
+ const accounts = [
44
+ {
45
+ id: 'heavy',
46
+ accountLabel: 'heavy',
47
+ expiresAt: NOW + 3600_000,
48
+ state: 'active',
49
+ usage: { requestCount5h: 50, last429At: null },
50
+ },
51
+ {
52
+ id: 'light',
53
+ accountLabel: 'light',
54
+ expiresAt: NOW + 3600_000,
55
+ state: 'active',
56
+ usage: { requestCount5h: 3, last429At: null },
57
+ },
58
+ ];
59
+ const { output, exitCode } = formatAuthStatus(accounts, NOW);
60
+ const plain = stripAnsi(output);
61
+ const lines = plain.split('\n').filter((l) => l.trim());
62
+ // Skip header lines (first two are header + separator)
63
+ const dataLines = lines.slice(2);
64
+ expect(exitCode).toBe(0);
65
+ // First data row must be 'light' (lower req5h)
66
+ expect(dataLines[0]).toContain('light');
67
+ expect(dataLines[0]).toContain('← selected');
68
+ // Second row must be 'heavy'
69
+ expect(dataLines[1]).toContain('heavy');
70
+ expect(dataLines[1]).not.toContain('← selected');
71
+ });
72
+ it('active-but-not-picked credential shows req5h reason', () => {
73
+ const accounts = [
74
+ {
75
+ id: 'a',
76
+ accountLabel: 'a',
77
+ expiresAt: NOW + 3600_000,
78
+ state: 'active',
79
+ usage: { requestCount5h: 1, last429At: null },
80
+ },
81
+ {
82
+ id: 'b',
83
+ accountLabel: 'b',
84
+ expiresAt: NOW + 3600_000,
85
+ state: 'active',
86
+ usage: { requestCount5h: 20, last429At: null },
87
+ },
88
+ ];
89
+ const { output } = formatAuthStatus(accounts, NOW);
90
+ const plain = stripAnsi(output);
91
+ expect(plain).toContain('higher than candidate');
92
+ });
93
+ });
94
+ // ── Reason formatting ────────────────────────────────────────────────────────
95
+ describe('reason formatting', () => {
96
+ it('cooldown credential shows "cooldown until HH:MM"', () => {
97
+ const resetAt = new Date('2026-05-02T14:00:00Z').toISOString();
98
+ const accounts = [
99
+ {
100
+ id: 'on-cooldown',
101
+ accountLabel: 'on-cooldown',
102
+ expiresAt: NOW + 3600_000,
103
+ state: 'cooldown',
104
+ rateLimited: true,
105
+ rateLimitResetsAt: resetAt,
106
+ usage: { requestCount5h: 87, last429At: new Date('2026-05-02T13:00:00Z').toISOString() },
107
+ },
108
+ ];
109
+ const { output, exitCode } = formatAuthStatus(accounts, NOW);
110
+ const plain = stripAnsi(output);
111
+ expect(plain).toContain('cooldown until');
112
+ expect(exitCode).toBe(2);
113
+ });
114
+ it('expired credential shows "expired N days ago"', () => {
115
+ const expiredAt = NOW - 3 * 24 * 60 * 60 * 1000; // 3 days ago
116
+ const accounts = [
117
+ {
118
+ id: 'old-cred',
119
+ accountLabel: 'old-cred',
120
+ expiresAt: expiredAt,
121
+ state: 'expired',
122
+ usage: { requestCount5h: 0, last429At: null },
123
+ },
124
+ ];
125
+ const { output, exitCode } = formatAuthStatus(accounts, NOW);
126
+ const plain = stripAnsi(output);
127
+ expect(plain).toContain('expired 3 days ago');
128
+ expect(exitCode).toBe(2);
129
+ });
130
+ it('disabled credential shows "disabled" reason', () => {
131
+ const accounts = [
132
+ {
133
+ id: 'off',
134
+ accountLabel: 'off',
135
+ expiresAt: NOW + 3600_000,
136
+ state: 'disabled',
137
+ usage: { requestCount5h: 0, last429At: null },
138
+ },
139
+ ];
140
+ const { output, exitCode } = formatAuthStatus(accounts, NOW);
141
+ const plain = stripAnsi(output);
142
+ expect(plain).toContain('disabled');
143
+ expect(exitCode).toBe(2);
144
+ });
145
+ });
146
+ // ── Exit 2 when all credentials unavailable ──────────────────────────────────
147
+ describe('exit code 2 — all unavailable', () => {
148
+ it('returns exitCode 2 when all credentials are on cooldown', () => {
149
+ const resetAt = new Date('2026-05-02T15:00:00Z').toISOString();
150
+ const accounts = [
151
+ {
152
+ id: 'cred1',
153
+ expiresAt: NOW + 3600_000,
154
+ state: 'cooldown',
155
+ rateLimitResetsAt: resetAt,
156
+ usage: { requestCount5h: 30, last429At: null },
157
+ },
158
+ {
159
+ id: 'cred2',
160
+ expiresAt: NOW + 3600_000,
161
+ state: 'cooldown',
162
+ rateLimitResetsAt: resetAt,
163
+ usage: { requestCount5h: 45, last429At: null },
164
+ },
165
+ ];
166
+ const { output, exitCode } = formatAuthStatus(accounts, NOW);
167
+ const plain = stripAnsi(output);
168
+ expect(exitCode).toBe(2);
169
+ expect(plain).toContain('Next reset:');
170
+ });
171
+ it('shows "No reset scheduled" footer when all cooldown creds lack rateLimitResetsAt', () => {
172
+ const accounts = [
173
+ {
174
+ id: 'cred1',
175
+ expiresAt: NOW + 3600_000,
176
+ state: 'cooldown',
177
+ usage: { requestCount5h: 10, last429At: null },
178
+ },
179
+ ];
180
+ const { output, exitCode } = formatAuthStatus(accounts, NOW);
181
+ const plain = stripAnsi(output);
182
+ expect(exitCode).toBe(2);
183
+ expect(plain).toContain('No reset scheduled');
184
+ });
185
+ });
186
+ // ── P2: latency test ─────────────────────────────────────────────────────────
187
+ describe('P2 — latency', () => {
188
+ it('formats 10 credentials in < 500 ms', () => {
189
+ const accounts = Array.from({ length: 10 }, (_, i) => ({
190
+ id: `cred-${i}`,
191
+ accountLabel: `cred-${i}`,
192
+ expiresAt: i < 5 ? NOW + 3600_000 : NOW - 1000,
193
+ state: (i < 5 ? 'active' : 'expired'),
194
+ usage: { requestCount5h: i * 3, last429At: null },
195
+ }));
196
+ const start = Date.now();
197
+ const { output } = formatAuthStatus(accounts, NOW);
198
+ const elapsed = Date.now() - start;
199
+ expect(output.length).toBeGreaterThan(0);
200
+ expect(elapsed).toBeLessThan(500);
201
+ });
202
+ });
203
+ // ── runAuthStatus: single source of truth ────────────────────────────────────
204
+ function makeAccountSummary(overrides = {}) {
205
+ return {
206
+ id: 'test-cred',
207
+ accountLabel: 'test-cred',
208
+ email: 'test@example.com',
209
+ provider: 'claude',
210
+ state: 'active',
211
+ tokenValid: true,
212
+ expiresIn: '1.0 hours',
213
+ rateLimited: false,
214
+ lastRefreshed: new Date(NOW).toISOString(),
215
+ usage: { requestCount5h: 5, windowStartedAt: null, last429At: null, cumulativeTokens: 0 },
216
+ ...overrides,
217
+ };
218
+ }
219
+ describe('runAuthStatus — auth-service as single source of truth', () => {
220
+ afterEach(() => {
221
+ vi.restoreAllMocks();
222
+ process.exitCode = undefined;
223
+ });
224
+ it('renders accounts returned by auth-service, not local file', async () => {
225
+ const serverAccounts = [
226
+ makeAccountSummary({ id: 'atlas-team', accountLabel: 'atlas-team', state: 'active' }),
227
+ makeAccountSummary({ id: 'grain-team', accountLabel: 'grain-team', state: 'disabled' }),
228
+ ];
229
+ const getStatus = vi.fn().mockResolvedValue({ reachable: true, accounts: serverAccounts });
230
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
231
+ await runAuthStatus(getStatus);
232
+ const output = consoleSpy.mock.calls.map((args) => args.join(' ')).join('\n');
233
+ expect(output).toContain('atlas-team');
234
+ expect(output).toContain('grain-team');
235
+ expect(process.exitCode).toBeUndefined();
236
+ });
237
+ it('does NOT show phantom credentials from local file when auth-service is reachable', async () => {
238
+ // Simulate: auth-service has 2 real creds, local file might have "ernest-grain" (phantom)
239
+ const serverAccounts = [
240
+ makeAccountSummary({ id: 'atlas-team', accountLabel: 'atlas-team' }),
241
+ makeAccountSummary({ id: 'personal', accountLabel: 'personal' }),
242
+ ];
243
+ const getStatus = vi.fn().mockResolvedValue({ reachable: true, accounts: serverAccounts });
244
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
245
+ await runAuthStatus(getStatus);
246
+ const output = consoleSpy.mock.calls.map((args) => args.join(' ')).join('\n');
247
+ expect(output).not.toContain('ernest-grain');
248
+ expect(output).toContain('atlas-team');
249
+ expect(output).toContain('personal');
250
+ });
251
+ it('exits non-zero with error when auth-service is unreachable', async () => {
252
+ const getStatus = vi.fn().mockResolvedValue({ reachable: false, accounts: [] });
253
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
254
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
255
+ await runAuthStatus(getStatus);
256
+ // Must not silently succeed
257
+ expect(process.exitCode).toBe(1);
258
+ // Combined output should mention how to fix it
259
+ const allOutput = [
260
+ ...consoleSpy.mock.calls,
261
+ ...errorSpy.mock.calls,
262
+ ].flat().join(' ');
263
+ expect(allOutput).toMatch(/auth|reachable|up/i);
264
+ });
265
+ it('exits non-zero when getStatus throws (service crash)', async () => {
266
+ const getStatus = vi.fn().mockRejectedValue(new Error('ECONNREFUSED'));
267
+ vi.spyOn(console, 'log').mockImplementation(() => { });
268
+ vi.spyOn(console, 'error').mockImplementation(() => { });
269
+ await runAuthStatus(getStatus);
270
+ expect(process.exitCode).toBe(1);
271
+ });
272
+ it('returns same shape as auth list when both succeed', async () => {
273
+ // auth list uses AuthClient.status() → same endpoint as runAuthStatus
274
+ const sharedAccounts = [
275
+ makeAccountSummary({ id: 'cred-a', accountLabel: 'cred-a', state: 'active' }),
276
+ makeAccountSummary({ id: 'cred-b', accountLabel: 'cred-b', state: 'cooldown',
277
+ rateLimited: true,
278
+ rateLimitResetsAt: new Date(NOW + 3600_000).toISOString(),
279
+ }),
280
+ ];
281
+ const getStatus = vi.fn().mockResolvedValue({ reachable: true, accounts: sharedAccounts });
282
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
283
+ await runAuthStatus(getStatus);
284
+ const output = consoleSpy.mock.calls.map((args) => args.join(' ')).join('\n');
285
+ // Both credentials from auth-service must appear
286
+ expect(output).toContain('cred-a');
287
+ expect(output).toContain('cred-b');
288
+ });
289
+ });
290
+ //# sourceMappingURL=auth-status.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-status.test.js","sourceRoot":"","sources":["../../src/__tests__/auth-status.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Unit tests for auth-upgrade.ts pure helpers.
3
+ *
4
+ * Only tests the stateless, side-effect-free helpers. The full upgrade
5
+ * pipeline (docker build, stop, rm, start) is too side-effectful to
6
+ * integration-test inside the devbox — same philosophy as upgrade.test.ts.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=auth-upgrade.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-upgrade.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/auth-upgrade.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Unit tests for auth-upgrade.ts pure helpers.
3
+ *
4
+ * Only tests the stateless, side-effect-free helpers. The full upgrade
5
+ * pipeline (docker build, stop, rm, start) is too side-effectful to
6
+ * integration-test inside the devbox — same philosophy as upgrade.test.ts.
7
+ */
8
+ import { describe, it, expect, beforeEach, afterEach, vi } 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 { validateAuthRepoRoot, parseAuthUpgradeOpts, smokeTestCodexProvider } from '../commands/auth-upgrade.js';
13
+ // ── validateAuthRepoRoot ──────────────────────────────────────────
14
+ describe('validateAuthRepoRoot', () => {
15
+ let tmpDir;
16
+ beforeEach(() => {
17
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'olam-auth-upgrade-test-'));
18
+ });
19
+ afterEach(() => {
20
+ fs.rmSync(tmpDir, { recursive: true, force: true });
21
+ });
22
+ it('returns ok:false when the marker file is absent', () => {
23
+ const result = validateAuthRepoRoot(tmpDir);
24
+ expect(result.ok).toBe(false);
25
+ if (!result.ok) {
26
+ expect(result.error).toMatch(/packages\/auth-service\/Dockerfile/);
27
+ expect(result.error).toMatch(/olam repo root/i);
28
+ }
29
+ });
30
+ it('returns ok:true when packages/auth-service/Dockerfile exists', () => {
31
+ const authServiceDir = path.join(tmpDir, 'packages/auth-service');
32
+ fs.mkdirSync(authServiceDir, { recursive: true });
33
+ fs.writeFileSync(path.join(authServiceDir, 'Dockerfile'), 'FROM node:22-slim');
34
+ const result = validateAuthRepoRoot(tmpDir);
35
+ expect(result.ok).toBe(true);
36
+ });
37
+ it('error message includes the expected path', () => {
38
+ const result = validateAuthRepoRoot('/nonexistent-definitely-not-a-real-path');
39
+ expect(result.ok).toBe(false);
40
+ if (!result.ok) {
41
+ expect(result.error).toContain('Dockerfile');
42
+ }
43
+ });
44
+ it('error message includes a remediation hint', () => {
45
+ const result = validateAuthRepoRoot(tmpDir);
46
+ expect(result.ok).toBe(false);
47
+ if (!result.ok) {
48
+ expect(result.error).toContain('olam auth upgrade');
49
+ }
50
+ });
51
+ });
52
+ // ── parseAuthUpgradeOpts ──────────────────────────────────────────
53
+ describe('parseAuthUpgradeOpts', () => {
54
+ it('defaults all options to false when called with empty object', () => {
55
+ const opts = parseAuthUpgradeOpts({});
56
+ expect(opts.yes).toBe(false);
57
+ expect(opts.skipRecreate).toBe(false);
58
+ });
59
+ it('sets yes:true when raw.yes is true', () => {
60
+ expect(parseAuthUpgradeOpts({ yes: true }).yes).toBe(true);
61
+ });
62
+ it('sets yes:false when raw.yes is false', () => {
63
+ expect(parseAuthUpgradeOpts({ yes: false }).yes).toBe(false);
64
+ });
65
+ it('sets yes:false when raw.yes is undefined', () => {
66
+ expect(parseAuthUpgradeOpts({ yes: undefined }).yes).toBe(false);
67
+ });
68
+ it('sets skipRecreate:true when raw.skipRecreate is true', () => {
69
+ expect(parseAuthUpgradeOpts({ skipRecreate: true }).skipRecreate).toBe(true);
70
+ });
71
+ it('sets skipRecreate:false when raw.skipRecreate is undefined', () => {
72
+ expect(parseAuthUpgradeOpts({}).skipRecreate).toBe(false);
73
+ });
74
+ it('accepts both options at once', () => {
75
+ const opts = parseAuthUpgradeOpts({ yes: true, skipRecreate: true });
76
+ expect(opts.yes).toBe(true);
77
+ expect(opts.skipRecreate).toBe(true);
78
+ });
79
+ it('result is a plain object (no mutation of input)', () => {
80
+ const raw = { yes: true };
81
+ const opts = parseAuthUpgradeOpts(raw);
82
+ expect(opts).not.toBe(raw);
83
+ });
84
+ });
85
+ // ── smokeTestCodexProvider ────────────────────────────────────────
86
+ // These tests verify that the smoke gate correctly distinguishes a stale
87
+ // (pre-codex) image from a current one. A stale image returns a
88
+ // claude.com loginUrl for provider=codex; a current image returns an
89
+ // OpenAI verification URL.
90
+ describe('smokeTestCodexProvider', () => {
91
+ beforeEach(() => {
92
+ vi.stubGlobal('fetch', undefined);
93
+ });
94
+ afterEach(() => {
95
+ vi.unstubAllGlobals();
96
+ });
97
+ function makeResponse(body, status = 200) {
98
+ return new Response(JSON.stringify(body), {
99
+ status,
100
+ headers: { 'Content-Type': 'application/json' },
101
+ });
102
+ }
103
+ it('returns ok:true when codex response has no claude.com loginUrl', async () => {
104
+ vi.stubGlobal('fetch', async () => makeResponse({
105
+ verificationUriComplete: 'https://login.microsoftonline.com/common/oauth2/v2.0/deviceauth',
106
+ userCode: 'TEST-CODE',
107
+ state: 'abc123',
108
+ }));
109
+ const result = await smokeTestCodexProvider(null);
110
+ expect(result.ok).toBe(true);
111
+ });
112
+ it('returns ok:false when codex response contains a claude.com loginUrl (stale image)', async () => {
113
+ vi.stubGlobal('fetch', async () => makeResponse({
114
+ loginUrl: 'https://claude.com/oauth/authorize?...',
115
+ state: 'pkce-state',
116
+ }));
117
+ const result = await smokeTestCodexProvider(null);
118
+ expect(result.ok).toBe(false);
119
+ if (!result.ok) {
120
+ expect(result.error).toMatch(/claude\.com/);
121
+ expect(result.error).toMatch(/stale/i);
122
+ }
123
+ });
124
+ it('returns ok:false when codex response contains a claude.ai loginUrl', async () => {
125
+ vi.stubGlobal('fetch', async () => makeResponse({ loginUrl: 'https://claude.ai/oauth/authorize' }));
126
+ const result = await smokeTestCodexProvider(null);
127
+ expect(result.ok).toBe(false);
128
+ });
129
+ it('returns ok:true for 502 (OpenAI unreachable) — not a stale-image failure', async () => {
130
+ vi.stubGlobal('fetch', async () => makeResponse({ error: 'OpenAI device auth endpoint unavailable' }, 502));
131
+ const result = await smokeTestCodexProvider(null);
132
+ expect(result.ok).toBe(true);
133
+ });
134
+ it('returns ok:false when fetch throws (network error)', async () => {
135
+ vi.stubGlobal('fetch', async () => { throw new Error('ECONNREFUSED'); });
136
+ const result = await smokeTestCodexProvider(null);
137
+ expect(result.ok).toBe(false);
138
+ if (!result.ok) {
139
+ expect(result.error).toMatch(/ECONNREFUSED/);
140
+ }
141
+ });
142
+ it('sends X-Olam-Secret header when authSecret is provided', async () => {
143
+ let capturedHeaders;
144
+ vi.stubGlobal('fetch', async (_url, init) => {
145
+ capturedHeaders = init.headers;
146
+ return makeResponse({ userCode: 'X' });
147
+ });
148
+ await smokeTestCodexProvider('super-secret');
149
+ expect(capturedHeaders?.['X-Olam-Secret']).toBe('super-secret');
150
+ });
151
+ it('omits X-Olam-Secret header when authSecret is null', async () => {
152
+ let capturedHeaders;
153
+ vi.stubGlobal('fetch', async (_url, init) => {
154
+ capturedHeaders = init.headers;
155
+ return makeResponse({ userCode: 'X' });
156
+ });
157
+ await smokeTestCodexProvider(null);
158
+ expect(capturedHeaders?.['X-Olam-Secret']).toBeUndefined();
159
+ });
160
+ });
161
+ //# sourceMappingURL=auth-upgrade.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-upgrade.test.js","sourceRoot":"","sources":["../../src/__tests__/auth-upgrade.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=create-app-urls.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-app-urls.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/create-app-urls.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Focused tests for the app URLs output block added to `olam create`.
3
+ *
4
+ * Rather than exercising the full create flow (which involves workspace
5
+ * inference, docker provider, ora spinners, etc.), these tests verify the
6
+ * two invariants that matter most for the E4 feature:
7
+ *
8
+ * 1. When the created world has appPortUrls, they appear in the output.
9
+ * 2. When appPortUrls is absent or empty, the Apps block is not printed.
10
+ *
11
+ * The tests reach into the printInfo helper directly to avoid Commander
12
+ * action plumbing, keeping the surface narrow and the assertions tight.
13
+ */
14
+ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
15
+ // printInfo calls console.log under the hood; spy before importing.
16
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
17
+ const ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
18
+ function strip(s) {
19
+ return s.replace(ANSI_RE, '');
20
+ }
21
+ const SAMPLE_URLS = [
22
+ { repoName: 'atlas-core', internalPort: 3000, hostPort: 13000, url: 'http://localhost:13000' },
23
+ { repoName: 'diner-app', internalPort: 4000, hostPort: 14000, url: 'http://localhost:14000' },
24
+ ];
25
+ // The display logic extracted from create.ts for focused testing.
26
+ // Mirrors exactly what create.ts does in its output block:
27
+ //
28
+ // if (world.appPortUrls && world.appPortUrls.length > 0) {
29
+ // world.appPortUrls.forEach((ap, i) => {
30
+ // printInfo(i === 0 ? 'Apps' : '', `${ap.repoName} ${ap.url}`);
31
+ // });
32
+ // }
33
+ //
34
+ // We import printInfo and run that logic here to verify the format matches.
35
+ const { printInfo } = await import('../output.js');
36
+ function simulateCreateOutput(appPortUrls, dashboardUrl) {
37
+ logSpy.mockClear();
38
+ if (dashboardUrl) {
39
+ printInfo('Dashboard', dashboardUrl);
40
+ }
41
+ if (appPortUrls && appPortUrls.length > 0) {
42
+ appPortUrls.forEach((ap, i) => {
43
+ printInfo(i === 0 ? 'Apps' : '', `${ap.repoName} ${ap.url}`);
44
+ });
45
+ }
46
+ }
47
+ describe('olam create — appPortUrls display logic', () => {
48
+ afterEach(() => {
49
+ logSpy.mockClear();
50
+ });
51
+ it('prints Apps block when appPortUrls is present', () => {
52
+ simulateCreateOutput(SAMPLE_URLS, 'http://localhost:19080');
53
+ const output = logSpy.mock.calls.map((args) => strip(String(args[0]))).join('\n');
54
+ expect(output).toContain('Dashboard');
55
+ expect(output).toContain('http://localhost:19080');
56
+ expect(output).toContain('Apps');
57
+ expect(output).toContain('atlas-core');
58
+ expect(output).toContain('http://localhost:13000');
59
+ expect(output).toContain('diner-app');
60
+ expect(output).toContain('http://localhost:14000');
61
+ });
62
+ it('first app URL line carries "Apps" label', () => {
63
+ simulateCreateOutput(SAMPLE_URLS);
64
+ const lines = logSpy.mock.calls.map((args) => strip(String(args[0])));
65
+ const appsLine = lines.find((l) => l.includes('Apps'));
66
+ expect(appsLine).toBeDefined();
67
+ expect(appsLine).toContain('atlas-core');
68
+ expect(appsLine).toContain('http://localhost:13000');
69
+ });
70
+ it('subsequent app URL lines use empty label (visual indentation)', () => {
71
+ simulateCreateOutput(SAMPLE_URLS);
72
+ const lines = logSpy.mock.calls.map((args) => strip(String(args[0])));
73
+ const dinerLine = lines.find((l) => l.includes('diner-app'));
74
+ expect(dinerLine).toBeDefined();
75
+ // Empty label pads to 14 spaces — line should NOT contain the word "Apps"
76
+ expect(dinerLine).not.toMatch(/Apps/);
77
+ expect(dinerLine).toContain('http://localhost:14000');
78
+ });
79
+ it('does NOT print Apps block when appPortUrls is absent', () => {
80
+ simulateCreateOutput(undefined, 'http://localhost:19080');
81
+ const output = logSpy.mock.calls.map((args) => strip(String(args[0]))).join('\n');
82
+ expect(output).not.toContain('Apps');
83
+ expect(output).toContain('Dashboard'); // dashboard still present
84
+ });
85
+ it('does NOT print Apps block when appPortUrls is empty array', () => {
86
+ simulateCreateOutput([], 'http://localhost:19080');
87
+ const output = logSpy.mock.calls.map((args) => strip(String(args[0]))).join('\n');
88
+ expect(output).not.toContain('Apps');
89
+ });
90
+ it('handles single-repo world — one Apps line, no indented continuation', () => {
91
+ const singleUrl = [
92
+ { repoName: 'solo-app', internalPort: 3000, hostPort: 13000, url: 'http://localhost:13000' },
93
+ ];
94
+ simulateCreateOutput(singleUrl);
95
+ const lines = logSpy.mock.calls.map((args) => strip(String(args[0])));
96
+ const appsLines = lines.filter((l) => l.includes('Apps'));
97
+ expect(appsLines).toHaveLength(1);
98
+ expect(appsLines[0]).toContain('solo-app');
99
+ expect(appsLines[0]).toContain('http://localhost:13000');
100
+ });
101
+ });
102
+ //# sourceMappingURL=create-app-urls.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-app-urls.test.js","sourceRoot":"","sources":["../../src/__tests__/create-app-urls.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=enter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enter.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/enter.test.ts"],"names":[],"mappings":""}