@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,145 @@
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
+ 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 { readKeysFile, keysFilePath } from '../commands/keys.js';
13
+ let tmpHome;
14
+ let originalHome;
15
+ beforeEach(() => {
16
+ tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'olam-keys-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
+ // ── keysFilePath ──────────────────────────────────────────────────
28
+ describe('keysFilePath', () => {
29
+ it('returns path under OLAM_HOME', () => {
30
+ expect(keysFilePath()).toBe(path.join(tmpHome, 'keys.yaml'));
31
+ });
32
+ it('honors OLAM_HOME so tests do not stomp on the real ~/.olam', () => {
33
+ expect(keysFilePath()).not.toContain(os.homedir() + path.sep + '.olam');
34
+ });
35
+ });
36
+ // ── readKeysFile ──────────────────────────────────────────────────
37
+ describe('readKeysFile', () => {
38
+ it('returns null when ~/.olam/keys.yaml does not exist', () => {
39
+ expect(readKeysFile()).toBeNull();
40
+ });
41
+ it('returns null for an empty file', () => {
42
+ fs.mkdirSync(tmpHome, { recursive: true });
43
+ fs.writeFileSync(keysFilePath(), '');
44
+ expect(readKeysFile()).toBeNull();
45
+ });
46
+ it('returns null for a whitespace-only file', () => {
47
+ fs.mkdirSync(tmpHome, { recursive: true });
48
+ fs.writeFileSync(keysFilePath(), ' \n\n');
49
+ expect(readKeysFile()).toBeNull();
50
+ });
51
+ it('returns null for malformed YAML', () => {
52
+ fs.mkdirSync(tmpHome, { recursive: true });
53
+ fs.writeFileSync(keysFilePath(), ': bad: yaml: [}');
54
+ expect(readKeysFile()).toBeNull();
55
+ });
56
+ it('returns null when YAML is not an object (array)', () => {
57
+ fs.mkdirSync(tmpHome, { recursive: true });
58
+ fs.writeFileSync(keysFilePath(), '- item1\n- item2\n');
59
+ expect(readKeysFile()).toBeNull();
60
+ });
61
+ it('returns null when YAML is a scalar', () => {
62
+ fs.mkdirSync(tmpHome, { recursive: true });
63
+ fs.writeFileSync(keysFilePath(), 'just a string\n');
64
+ expect(readKeysFile()).toBeNull();
65
+ });
66
+ it('parses a valid single-key file', () => {
67
+ fs.mkdirSync(tmpHome, { recursive: true });
68
+ fs.writeFileSync(keysFilePath(), 'OPENAI: sk-proj-testvalue\n');
69
+ expect(readKeysFile()).toEqual({ OPENAI: 'sk-proj-testvalue' });
70
+ });
71
+ it('parses multiple keys', () => {
72
+ fs.mkdirSync(tmpHome, { recursive: true });
73
+ fs.writeFileSync(keysFilePath(), 'OPENAI: sk-proj-aaa\nGEMINI: AIzaBBB\n');
74
+ expect(readKeysFile()).toEqual({ OPENAI: 'sk-proj-aaa', GEMINI: 'AIzaBBB' });
75
+ });
76
+ it('skips non-string values silently', () => {
77
+ fs.mkdirSync(tmpHome, { recursive: true });
78
+ fs.writeFileSync(keysFilePath(), 'OPENAI: sk-valid\nnumeric: 12345\n');
79
+ // numeric is a YAML integer, not a string — should be skipped
80
+ const result = readKeysFile();
81
+ expect(result).not.toBeNull();
82
+ expect(result.OPENAI).toBe('sk-valid');
83
+ expect('numeric' in result).toBe(false);
84
+ });
85
+ it('returns null when all values are non-string', () => {
86
+ fs.mkdirSync(tmpHome, { recursive: true });
87
+ fs.writeFileSync(keysFilePath(), 'count: 42\nflag: true\n');
88
+ expect(readKeysFile()).toBeNull();
89
+ });
90
+ it('honors OLAM_HOME so tests do not stomp on the real ~/.olam', () => {
91
+ fs.mkdirSync(tmpHome, { recursive: true });
92
+ fs.writeFileSync(keysFilePath(), 'OPENAI: sentinel\n');
93
+ expect(readKeysFile()).toEqual({ OPENAI: 'sentinel' });
94
+ expect(keysFilePath()).toBe(path.join(tmpHome, 'keys.yaml'));
95
+ });
96
+ });
97
+ // ── env-merge precedence helper ───────────────────────────────────
98
+ //
99
+ // The manager.ts injection logic is: only set OLAM_LLM_<KEY> when it
100
+ // is NOT already present in worldEnv. Verify that contract here by
101
+ // simulating the merge logic used in manager.ts.
102
+ function mergeKeysIntoEnv(keys, existing) {
103
+ const env = { ...existing };
104
+ for (const [k, v] of Object.entries(keys)) {
105
+ const envKey = `OLAM_LLM_${k.toUpperCase()}`;
106
+ if (!(envKey in env)) {
107
+ env[envKey] = v;
108
+ }
109
+ }
110
+ return env;
111
+ }
112
+ describe('env-merge precedence', () => {
113
+ it('injects OLAM_LLM_<UPPER_KEY> for each key', () => {
114
+ const result = mergeKeysIntoEnv({ OPENAI: 'sk-abc', GEMINI: 'AIza-xyz' }, {});
115
+ expect(result).toEqual({
116
+ OLAM_LLM_OPENAI: 'sk-abc',
117
+ OLAM_LLM_GEMINI: 'AIza-xyz',
118
+ });
119
+ });
120
+ it('uppercases the key name regardless of file case', () => {
121
+ const result = mergeKeysIntoEnv({ openai: 'sk-lower' }, {});
122
+ expect(result.OLAM_LLM_OPENAI).toBe('sk-lower');
123
+ });
124
+ it('does NOT overwrite an existing OLAM_LLM_* entry', () => {
125
+ const result = mergeKeysIntoEnv({ OPENAI: 'from-file' }, { OLAM_LLM_OPENAI: 'already-set' });
126
+ expect(result.OLAM_LLM_OPENAI).toBe('already-set');
127
+ });
128
+ it('does NOT overwrite OLAM_TASK or other protected env keys', () => {
129
+ const result = mergeKeysIntoEnv({ TASK: 'injected' }, { OLAM_LLM_TASK: 'protected' });
130
+ expect(result.OLAM_LLM_TASK).toBe('protected');
131
+ });
132
+ it('does not inject anything when keys map is empty', () => {
133
+ const existing = { OLAM_TASK: 'build feature' };
134
+ const result = mergeKeysIntoEnv({}, existing);
135
+ expect(result).toEqual(existing);
136
+ });
137
+ it('preserves all existing env entries unchanged', () => {
138
+ const existing = { OLAM_TASK: 'task', OLAM_R2_BUCKET: 'bucket' };
139
+ const result = mergeKeysIntoEnv({ OPENAI: 'sk-new' }, existing);
140
+ expect(result.OLAM_TASK).toBe('task');
141
+ expect(result.OLAM_R2_BUCKET).toBe('bucket');
142
+ expect(result.OLAM_LLM_OPENAI).toBe('sk-new');
143
+ });
144
+ });
145
+ //# sourceMappingURL=keys.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.test.js","sourceRoot":"","sources":["../../src/__tests__/keys.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Unit tests for logs.ts pure helpers.
3
+ *
4
+ * The colorLine and formatLine functions are exported for testability.
5
+ * http.get streaming is exercised by manual `olam logs` invocation against
6
+ * a running world (see B1 Verification in the tracker).
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=logs.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/logs.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Unit tests for logs.ts pure helpers.
3
+ *
4
+ * The colorLine and formatLine functions are exported for testability.
5
+ * http.get streaming is exercised by manual `olam logs` invocation against
6
+ * a running world (see B1 Verification in the tracker).
7
+ */
8
+ import { describe, it, expect } from 'vitest';
9
+ import { colorLine, formatLine, parseSseEvent } from '../commands/logs.js';
10
+ // picocolors is applied in the tests; we test the string content rather than
11
+ // exact ANSI codes since ANSI output depends on the terminal environment.
12
+ // To make assertions deterministic, we strip ANSI codes via a simple regex.
13
+ const ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
14
+ function strip(s) {
15
+ return s.replace(ANSI_RE, '');
16
+ }
17
+ describe('parseSseEvent', () => {
18
+ it('returns null for non-data lines', () => {
19
+ expect(parseSseEvent('')).toBeNull();
20
+ expect(parseSseEvent(': ping')).toBeNull();
21
+ expect(parseSseEvent('event: heartbeat')).toBeNull();
22
+ expect(parseSseEvent(': connected')).toBeNull();
23
+ });
24
+ it('returns null for malformed JSON', () => {
25
+ expect(parseSseEvent('data: {bad json')).toBeNull();
26
+ expect(parseSseEvent('data: not-json')).toBeNull();
27
+ });
28
+ it('returns null for unknown event types', () => {
29
+ expect(parseSseEvent('data: {"type":"unknown","foo":"bar"}')).toBeNull();
30
+ expect(parseSseEvent('data: {"type":"error","message":"x"}')).toBeNull();
31
+ });
32
+ it('parses replay events correctly', () => {
33
+ const ev = parseSseEvent('data: {"type":"replay","service":"atlas-core","lines":["a","b"]}');
34
+ expect(ev).not.toBeNull();
35
+ expect(ev.type).toBe('replay');
36
+ if (ev !== null && ev.type === 'replay') {
37
+ expect(ev.service).toBe('atlas-core');
38
+ expect(ev.lines).toEqual(['a', 'b']);
39
+ }
40
+ });
41
+ it('parses replay events without service field', () => {
42
+ const ev = parseSseEvent('data: {"type":"replay","lines":["x"]}');
43
+ expect(ev).not.toBeNull();
44
+ expect(ev.type).toBe('replay');
45
+ });
46
+ it('parses line events correctly', () => {
47
+ const ev = parseSseEvent('data: {"type":"line","service":"diner-app","line":"server started","ts":1234567}');
48
+ expect(ev).not.toBeNull();
49
+ expect(ev.type).toBe('line');
50
+ if (ev !== null && ev.type === 'line') {
51
+ expect(ev.service).toBe('diner-app');
52
+ expect(ev.line).toBe('server started');
53
+ expect(ev.ts).toBe(1234567);
54
+ }
55
+ });
56
+ it('returns null for replay with non-array lines', () => {
57
+ expect(parseSseEvent('data: {"type":"replay","lines":"not-array"}')).toBeNull();
58
+ });
59
+ it('returns null for line event with non-string line', () => {
60
+ expect(parseSseEvent('data: {"type":"line","line":42,"ts":0}')).toBeNull();
61
+ });
62
+ });
63
+ describe('colorLine', () => {
64
+ it('returns plain text unchanged', () => {
65
+ expect(strip(colorLine('server started'))).toBe('server started');
66
+ });
67
+ it('colors ERROR lines (output contains the text)', () => {
68
+ const result = colorLine('ERROR: database unreachable');
69
+ expect(strip(result)).toBe('ERROR: database unreachable');
70
+ // Colored output should differ from plain (ANSI codes present or same in no-color mode).
71
+ // We verify the content is preserved, not the specific ANSI code.
72
+ });
73
+ it('colors WARN lines', () => {
74
+ const result = colorLine('WARN: slow query detected');
75
+ expect(strip(result)).toBe('WARN: slow query detected');
76
+ });
77
+ it('dims INFO lines', () => {
78
+ const result = colorLine('INFO: server listening on port 3000');
79
+ expect(strip(result)).toBe('INFO: server listening on port 3000');
80
+ });
81
+ it('does not false-positive on ERRORS (plural) — word boundary check', () => {
82
+ // "ERRORS" should still match \bERROR\b in some regex flavors; check it works
83
+ const result = colorLine('ERRORS found: 3');
84
+ // "ERRORS" contains "ERROR" — \bERROR\b does NOT match inside "ERRORS"
85
+ // because \b is a word boundary and S follows R. Actually \bERROR\b will NOT
86
+ // match in "ERRORS" because after the R comes S, not a non-word char. Good.
87
+ expect(strip(result)).toBe('ERRORS found: 3');
88
+ });
89
+ it('preserves multi-word lines', () => {
90
+ const line = '2024-01-15T10:00:00Z INFO [atlas-core] Request completed in 42ms';
91
+ expect(strip(colorLine(line))).toBe(line);
92
+ });
93
+ });
94
+ describe('formatLine', () => {
95
+ it('no prefix when showService=false', () => {
96
+ const result = formatLine('hello world', 'atlas-core', false);
97
+ expect(strip(result)).toBe('hello world');
98
+ });
99
+ it('no prefix when showService=true but service is undefined', () => {
100
+ const result = formatLine('hello world', undefined, true);
101
+ expect(strip(result)).toBe('hello world');
102
+ });
103
+ it('prepends [service] when showService=true and service provided', () => {
104
+ const result = formatLine('server started', 'atlas-core', true);
105
+ expect(strip(result)).toContain('[atlas-core]');
106
+ expect(strip(result)).toContain('server started');
107
+ // Service prefix comes first
108
+ const plain = strip(result);
109
+ expect(plain.indexOf('[atlas-core]')).toBeLessThan(plain.indexOf('server started'));
110
+ });
111
+ it('prepends [service] for multi-service stream with diner-app', () => {
112
+ const result = formatLine('compiled successfully', 'diner-app', true);
113
+ const plain = strip(result);
114
+ expect(plain).toContain('[diner-app]');
115
+ expect(plain).toContain('compiled successfully');
116
+ });
117
+ it('applies color AND prefix together — ERROR line with service', () => {
118
+ const result = formatLine('ERROR: crash', 'atlas-core', true);
119
+ const plain = strip(result);
120
+ expect(plain).toContain('[atlas-core]');
121
+ expect(plain).toContain('ERROR: crash');
122
+ });
123
+ });
124
+ //# sourceMappingURL=logs.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.test.js","sourceRoot":"","sources":["../../src/__tests__/logs.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ps.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ps.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/ps.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,172 @@
1
+ import { vi, describe, it, expect } from 'vitest';
2
+ import { parseDockerTop, sortRows } from '../commands/ps.js';
3
+ // context.js transitively imports @olam/adapters (not built in devbox);
4
+ // mock it so pure-function tests don't pull in the workspace dep graph.
5
+ vi.mock('../context.js', () => ({ loadContext: vi.fn() }));
6
+ // ── parseDockerTop — JSON format ──────────────────────────────────
7
+ describe('parseDockerTop — JSON format', () => {
8
+ const jsonFixture = JSON.stringify({
9
+ Titles: ['PID', 'USER', '%CPU', '%MEM', 'STIME', 'STAT', 'CMD'],
10
+ Processes: [
11
+ ['1234', 'root', '45.2', '12.3', '10:00', 'S', '/usr/bin/node', 'server.js'],
12
+ ['5678', 'olam', '0.5', '1.2', 'Feb11', 'R', '/bin/bash'],
13
+ ],
14
+ });
15
+ it('parses JSON format and returns correct row count', () => {
16
+ const rows = parseDockerTop(jsonFixture);
17
+ expect(rows).toHaveLength(2);
18
+ });
19
+ it('extracts pid correctly', () => {
20
+ const rows = parseDockerTop(jsonFixture);
21
+ expect(rows[0].pid).toBe('1234');
22
+ expect(rows[1].pid).toBe('5678');
23
+ });
24
+ it('extracts user correctly', () => {
25
+ const rows = parseDockerTop(jsonFixture);
26
+ expect(rows[0].user).toBe('root');
27
+ expect(rows[1].user).toBe('olam');
28
+ });
29
+ it('extracts cpu correctly', () => {
30
+ const rows = parseDockerTop(jsonFixture);
31
+ expect(rows[0].cpu).toBe('45.2');
32
+ });
33
+ it('extracts mem correctly', () => {
34
+ const rows = parseDockerTop(jsonFixture);
35
+ expect(rows[0].mem).toBe('12.3');
36
+ });
37
+ it('extracts started (stime) correctly', () => {
38
+ const rows = parseDockerTop(jsonFixture);
39
+ expect(rows[0].started).toBe('10:00');
40
+ expect(rows[1].started).toBe('Feb11');
41
+ });
42
+ it('extracts state correctly', () => {
43
+ const rows = parseDockerTop(jsonFixture);
44
+ expect(rows[0].state).toBe('S');
45
+ expect(rows[1].state).toBe('R');
46
+ });
47
+ it('joins all tokens from cmdIdx onward into a single command string', () => {
48
+ const rows = parseDockerTop(jsonFixture);
49
+ expect(rows[0].command).toBe('/usr/bin/node server.js');
50
+ expect(rows[1].command).toBe('/bin/bash');
51
+ });
52
+ it('returns [] for empty JSON Processes array', () => {
53
+ const empty = JSON.stringify({ Titles: ['PID', 'CMD'], Processes: [] });
54
+ expect(parseDockerTop(empty)).toHaveLength(0);
55
+ });
56
+ it('returns [] when JSON is missing Titles/Processes', () => {
57
+ expect(parseDockerTop(JSON.stringify({}))).toHaveLength(0);
58
+ });
59
+ it('returns [] when required pid column is absent', () => {
60
+ const noPid = JSON.stringify({
61
+ Titles: ['USER', 'CMD'],
62
+ Processes: [['root', '/bin/bash']],
63
+ });
64
+ expect(parseDockerTop(noPid)).toHaveLength(0);
65
+ });
66
+ it('returns [] when required cmd column is absent', () => {
67
+ const noCmd = JSON.stringify({
68
+ Titles: ['PID', 'USER'],
69
+ Processes: [['1', 'root']],
70
+ });
71
+ expect(parseDockerTop(noCmd)).toHaveLength(0);
72
+ });
73
+ it('filters out rows with empty pid', () => {
74
+ const fixture = JSON.stringify({
75
+ Titles: ['PID', 'CMD'],
76
+ Processes: [
77
+ ['', '/bin/bash'],
78
+ ['99', '/bin/sh'],
79
+ ],
80
+ });
81
+ const rows = parseDockerTop(fixture);
82
+ expect(rows).toHaveLength(1);
83
+ expect(rows[0].pid).toBe('99');
84
+ });
85
+ it('accepts lowercase title variants', () => {
86
+ const lower = JSON.stringify({
87
+ Titles: ['pid', 'user', '%cpu', '%mem', 'stime', 'stat', 'cmd'],
88
+ Processes: [['42', 'root', '1.0', '2.0', '10:00', 'S', 'sleep']],
89
+ });
90
+ const rows = parseDockerTop(lower);
91
+ expect(rows[0].pid).toBe('42');
92
+ expect(rows[0].command).toBe('sleep');
93
+ });
94
+ it('falls back cpu/mem to "0.0" when columns are missing', () => {
95
+ const noCpu = JSON.stringify({
96
+ Titles: ['PID', 'CMD'],
97
+ Processes: [['1', '/bin/sh']],
98
+ });
99
+ const rows = parseDockerTop(noCpu);
100
+ expect(rows[0].cpu).toBe('0.0');
101
+ expect(rows[0].mem).toBe('0.0');
102
+ });
103
+ });
104
+ // ── parseDockerTop — tabular format ──────────────────────────────
105
+ describe('parseDockerTop — tabular format', () => {
106
+ const tabularFixture = [
107
+ 'PID USER %CPU %MEM STIME STAT CMD',
108
+ '1234 root 45.2 12.3 10:00 S /usr/bin/node server.js',
109
+ '5678 olam 0.5 1.2 Feb11 R /bin/bash',
110
+ ].join('\n');
111
+ it('parses tabular format and returns correct row count', () => {
112
+ expect(parseDockerTop(tabularFixture)).toHaveLength(2);
113
+ });
114
+ it('extracts pid from tabular format', () => {
115
+ expect(parseDockerTop(tabularFixture)[0].pid).toBe('1234');
116
+ });
117
+ it('joins multi-word command from tabular format', () => {
118
+ expect(parseDockerTop(tabularFixture)[0].command).toBe('/usr/bin/node server.js');
119
+ });
120
+ it('returns [] for empty string', () => {
121
+ expect(parseDockerTop('')).toHaveLength(0);
122
+ });
123
+ it('returns [] when only a header line (no data rows)', () => {
124
+ expect(parseDockerTop('PID CMD')).toHaveLength(0);
125
+ });
126
+ it('returns [] for malformed JSON-like string', () => {
127
+ expect(parseDockerTop('{bad json')).toHaveLength(0);
128
+ });
129
+ });
130
+ // ── sortRows ──────────────────────────────────────────────────────
131
+ describe('sortRows', () => {
132
+ const rows = [
133
+ { pid: '100', user: 'root', cpu: '5.0', mem: '10.0', started: 'Feb01', state: 'S', command: 'node' },
134
+ { pid: '200', user: 'olam', cpu: '80.0', mem: '2.0', started: 'Feb02', state: 'R', command: 'build' },
135
+ { pid: '50', user: 'nobody', cpu: '1.0', mem: '50.0', started: 'Feb03', state: 'Z', command: 'zombie' },
136
+ ];
137
+ it('sorts by cpu descending', () => {
138
+ const sorted = sortRows(rows, 'cpu');
139
+ expect(sorted).toHaveLength(3);
140
+ expect(sorted[0].cpu).toBe('80.0');
141
+ expect(sorted[1].cpu).toBe('5.0');
142
+ expect(sorted[2].cpu).toBe('1.0');
143
+ });
144
+ it('sorts by mem descending', () => {
145
+ const sorted = sortRows(rows, 'mem');
146
+ expect(sorted).toHaveLength(3);
147
+ expect(sorted[0].mem).toBe('50.0');
148
+ expect(sorted[1].mem).toBe('10.0');
149
+ expect(sorted[2].mem).toBe('2.0');
150
+ });
151
+ it('sorts by pid descending', () => {
152
+ const sorted = sortRows(rows, 'pid');
153
+ expect(sorted).toHaveLength(3);
154
+ expect(sorted[0].pid).toBe('200');
155
+ expect(sorted[1].pid).toBe('100');
156
+ expect(sorted[2].pid).toBe('50');
157
+ });
158
+ it('does not mutate the input array', () => {
159
+ const original = [...rows];
160
+ sortRows(rows, 'cpu');
161
+ expect(rows).toEqual(original);
162
+ });
163
+ it('handles equal cpu values without throwing', () => {
164
+ const equal = [
165
+ { pid: '1', user: 'a', cpu: '5.0', mem: '1.0', started: 'Feb01', state: 'S', command: 'a' },
166
+ { pid: '2', user: 'b', cpu: '5.0', mem: '2.0', started: 'Feb02', state: 'R', command: 'b' },
167
+ ];
168
+ expect(() => sortRows(equal, 'cpu')).not.toThrow();
169
+ expect(sortRows(equal, 'cpu')).toHaveLength(2);
170
+ });
171
+ });
172
+ //# sourceMappingURL=ps.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ps.test.js","sourceRoot":"","sources":["../../src/__tests__/ps.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=status-app-urls.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-app-urls.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/status-app-urls.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,125 @@
1
+ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { Command } from 'commander';
3
+ // Mock loadContext before importing the command so vitest can intercept
4
+ vi.mock('../context.js', () => ({ loadContext: vi.fn() }));
5
+ // After mock declaration, import the mocked function and the command registrar
6
+ const { loadContext } = await import('../context.js');
7
+ const { registerStatus } = await import('../commands/status.js');
8
+ const ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
9
+ function strip(s) {
10
+ return s.replace(ANSI_RE, '');
11
+ }
12
+ const SAMPLE_URLS = [
13
+ { repoName: 'atlas-core', internalPort: 3000, hostPort: 13000, url: 'http://localhost:13000' },
14
+ { repoName: 'diner-app', internalPort: 4000, hostPort: 14000, url: 'http://localhost:14000' },
15
+ ];
16
+ function makeWorld(overrides) {
17
+ return {
18
+ id: 'test-world-1',
19
+ name: 'Test World',
20
+ status: 'running',
21
+ repos: ['olam'],
22
+ branch: 'main',
23
+ portOffset: 0,
24
+ workspacePath: '/home/olam/workspace/olam',
25
+ computeProvider: 'docker',
26
+ totalCostUsd: 0.0042,
27
+ thoughtCount: 7,
28
+ createdAt: '2026-05-01T10:00:00.000Z',
29
+ updatedAt: '2026-05-01T11:00:00.000Z',
30
+ ...overrides,
31
+ };
32
+ }
33
+ function makeCtx(world) {
34
+ return {
35
+ worldManager: {
36
+ getWorld: vi.fn().mockReturnValue(world),
37
+ },
38
+ costTracker: {
39
+ getWorldCost: vi.fn().mockReturnValue(0.0042),
40
+ },
41
+ };
42
+ }
43
+ describe('olam status — appPortUrls output', () => {
44
+ let logSpy;
45
+ beforeEach(() => {
46
+ logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
47
+ vi.mocked(loadContext).mockReset();
48
+ });
49
+ afterEach(() => {
50
+ logSpy.mockRestore();
51
+ vi.unstubAllEnvs();
52
+ });
53
+ async function runStatus(worldId = 'test-world-1', extraArgs = []) {
54
+ const program = new Command().exitOverride();
55
+ registerStatus(program);
56
+ await program.parseAsync(['node', 'cli', 'status', worldId, ...extraArgs]);
57
+ }
58
+ it('prints Dashboard and Apps block when appPortUrls is present', async () => {
59
+ const world = makeWorld({ appPortUrls: SAMPLE_URLS });
60
+ vi.mocked(loadContext).mockResolvedValue({ ctx: makeCtx(world) });
61
+ await runStatus();
62
+ const output = logSpy.mock.calls.map((args) => strip(String(args[0]))).join('\n');
63
+ expect(output).toContain('http://localhost:19080'); // dashboard
64
+ expect(output).toContain('atlas-core');
65
+ expect(output).toContain('http://localhost:13000');
66
+ expect(output).toContain('diner-app');
67
+ expect(output).toContain('http://localhost:14000');
68
+ });
69
+ it('prints first app URL with "Apps" label, subsequent with empty label', async () => {
70
+ const world = makeWorld({ appPortUrls: SAMPLE_URLS });
71
+ vi.mocked(loadContext).mockResolvedValue({ ctx: makeCtx(world) });
72
+ await runStatus();
73
+ const lines = logSpy.mock.calls.map((args) => strip(String(args[0])));
74
+ const appsLine = lines.find((l) => l.includes('Apps') && l.includes('atlas-core'));
75
+ expect(appsLine).toBeDefined();
76
+ // Second app URL line uses empty label (14 spaces of padding)
77
+ const dinerLine = lines.find((l) => l.includes('diner-app') && l.includes('http://localhost:14000'));
78
+ expect(dinerLine).toBeDefined();
79
+ expect(dinerLine).not.toMatch(/Apps/);
80
+ });
81
+ it('does NOT print Apps block when appPortUrls is absent', async () => {
82
+ const world = makeWorld(); // no appPortUrls
83
+ vi.mocked(loadContext).mockResolvedValue({ ctx: makeCtx(world) });
84
+ await runStatus();
85
+ const output = logSpy.mock.calls.map((args) => strip(String(args[0]))).join('\n');
86
+ expect(output).not.toContain('Apps');
87
+ expect(output).toContain('http://localhost:19080'); // dashboard still present
88
+ });
89
+ it('does NOT print Apps block when appPortUrls is empty array', async () => {
90
+ const world = makeWorld({ appPortUrls: [] });
91
+ vi.mocked(loadContext).mockResolvedValue({ ctx: makeCtx(world) });
92
+ await runStatus();
93
+ const output = logSpy.mock.calls.map((args) => strip(String(args[0]))).join('\n');
94
+ expect(output).not.toContain('Apps');
95
+ });
96
+ it('--json output includes appPortUrls array', async () => {
97
+ const world = makeWorld({ appPortUrls: SAMPLE_URLS });
98
+ vi.mocked(loadContext).mockResolvedValue({ ctx: makeCtx(world) });
99
+ await runStatus('test-world-1', ['--json']);
100
+ const jsonCall = logSpy.mock.calls.find((args) => String(args[0]).startsWith('{'));
101
+ expect(jsonCall).toBeDefined();
102
+ const parsed = JSON.parse(String(jsonCall[0]));
103
+ expect(parsed.appPortUrls).toEqual(SAMPLE_URLS);
104
+ expect(parsed.dashboardUrl).toBe('http://localhost:19080');
105
+ });
106
+ it('--json output without appPortUrls omits the field', async () => {
107
+ const world = makeWorld(); // no appPortUrls
108
+ vi.mocked(loadContext).mockResolvedValue({ ctx: makeCtx(world) });
109
+ await runStatus('test-world-1', ['--json']);
110
+ const jsonCall = logSpy.mock.calls.find((args) => String(args[0]).startsWith('{'));
111
+ expect(jsonCall).toBeDefined();
112
+ const parsed = JSON.parse(String(jsonCall[0]));
113
+ expect(parsed.appPortUrls).toBeUndefined();
114
+ expect(parsed.dashboardUrl).toBe('http://localhost:19080');
115
+ });
116
+ it('uses portOffset to compute correct dashboardUrl', async () => {
117
+ const world = makeWorld({ portOffset: 200, appPortUrls: SAMPLE_URLS });
118
+ vi.mocked(loadContext).mockResolvedValue({ ctx: makeCtx(world) });
119
+ await runStatus('test-world-1', ['--json']);
120
+ const jsonCall = logSpy.mock.calls.find((args) => String(args[0]).startsWith('{'));
121
+ const parsed = JSON.parse(String(jsonCall[0]));
122
+ expect(parsed.dashboardUrl).toBe('http://localhost:19280'); // 19080 + 200
123
+ });
124
+ });
125
+ //# sourceMappingURL=status-app-urls.test.js.map