gaslighting-engine 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/.agents/prompts/gaslighting.md +2 -1
  2. package/.agents/skills/gaslighting/SKILL.md +4 -1
  3. package/.codex/prompts/gaslighting.md +2 -1
  4. package/.codex/skills/gaslighting/SKILL.md +4 -1
  5. package/README.md +36 -1
  6. package/dist/cli.js +38 -1
  7. package/dist/commands/cockpit.js +174 -0
  8. package/dist/commands/doctor.js +1 -0
  9. package/dist/core/cockpitHtml.js +596 -0
  10. package/dist/core/codexRuntime.js +74 -0
  11. package/dist/core/generateDocs.js +5 -3
  12. package/dist/core/generateOtherMarkdown.js +68 -4
  13. package/dist/index.js +1 -1
  14. package/dist/utils/logger.js +1 -1
  15. package/dist/version.js +1 -1
  16. package/docs/codex-usage.md +16 -4
  17. package/docs/examples.md +3 -0
  18. package/examples/ecommerce/.codex/prompts/gaslighting.md +2 -1
  19. package/examples/ecommerce/.codex/skills/gaslighting/SKILL.md +4 -1
  20. package/examples/ecommerce/.gaslighting/AGENTS.md +3 -1
  21. package/examples/ecommerce/.gaslighting/AGENT_RUNTIME.md +54 -0
  22. package/examples/ecommerce/.gaslighting/CODEX_PROMPT.md +2 -1
  23. package/examples/ecommerce/AGENTS.md +3 -2
  24. package/examples/hospital-homepage/.codex/prompts/gaslighting.md +2 -1
  25. package/examples/hospital-homepage/.codex/skills/gaslighting/SKILL.md +4 -1
  26. package/examples/hospital-homepage/.gaslighting/AGENTS.md +3 -1
  27. package/examples/hospital-homepage/.gaslighting/AGENT_RUNTIME.md +54 -0
  28. package/examples/hospital-homepage/.gaslighting/CODEX_PROMPT.md +2 -1
  29. package/examples/hospital-homepage/AGENTS.md +3 -2
  30. package/examples/landing-page/.codex/prompts/gaslighting.md +2 -1
  31. package/examples/landing-page/.codex/skills/gaslighting/SKILL.md +4 -1
  32. package/examples/landing-page/.gaslighting/AGENTS.md +3 -1
  33. package/examples/landing-page/.gaslighting/AGENT_RUNTIME.md +54 -0
  34. package/examples/landing-page/.gaslighting/CODEX_PROMPT.md +2 -1
  35. package/examples/landing-page/AGENTS.md +3 -2
  36. package/package.json +1 -1
@@ -0,0 +1,596 @@
1
+ export function renderCockpitHtml() {
2
+ return `<!doctype html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <title>Gaslighting Mission Control</title>
8
+ <style>
9
+ :root {
10
+ color-scheme: light;
11
+ --bg: #eef3f8;
12
+ --surface: #ffffff;
13
+ --surface-soft: #f6f7f9;
14
+ --line: #dfe4ea;
15
+ --text: #171a1f;
16
+ --muted: #65717f;
17
+ --green: #168a50;
18
+ --orange: #b76b00;
19
+ --red: #b42318;
20
+ --blue: #2563eb;
21
+ --shadow: 0 18px 50px rgba(19, 29, 45, 0.08);
22
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
23
+ }
24
+ * { box-sizing: border-box; }
25
+ body {
26
+ margin: 0;
27
+ min-height: 100vh;
28
+ background: var(--bg);
29
+ color: var(--text);
30
+ letter-spacing: 0;
31
+ overflow: hidden;
32
+ }
33
+ button, input, textarea { font: inherit; }
34
+ button {
35
+ border: 1px solid var(--line);
36
+ background: var(--surface);
37
+ color: var(--text);
38
+ border-radius: 8px;
39
+ min-height: 38px;
40
+ padding: 0 12px;
41
+ cursor: pointer;
42
+ }
43
+ button:hover { border-color: #b8c2cc; background: #f9fafb; }
44
+ button.primary { background: #111827; border-color: #111827; color: white; }
45
+ button.primary:hover { background: #242b38; }
46
+ button.ghost { background: transparent; }
47
+ input, textarea {
48
+ width: 100%;
49
+ border: 1px solid var(--line);
50
+ background: var(--surface);
51
+ color: var(--text);
52
+ border-radius: 8px;
53
+ padding: 10px 12px;
54
+ outline: none;
55
+ }
56
+ textarea { min-height: 92px; resize: vertical; line-height: 1.45; }
57
+ input:focus, textarea:focus { border-color: #7aa2ff; box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.12); }
58
+ .app {
59
+ display: grid;
60
+ grid-template-columns: 286px minmax(520px, 1fr) 330px;
61
+ height: 100vh;
62
+ }
63
+ .sidebar {
64
+ border-right: 1px solid var(--line);
65
+ padding: 18px 14px;
66
+ background: #e8eef5;
67
+ overflow: auto;
68
+ }
69
+ .brand {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 10px;
73
+ height: 42px;
74
+ padding: 0 10px;
75
+ font-weight: 700;
76
+ }
77
+ .mark {
78
+ width: 28px;
79
+ height: 28px;
80
+ display: grid;
81
+ place-items: center;
82
+ border: 1px solid #cbd5e1;
83
+ border-radius: 8px;
84
+ background: #111827;
85
+ color: white;
86
+ font-size: 13px;
87
+ }
88
+ .nav-title {
89
+ margin: 22px 10px 8px;
90
+ color: #8994a3;
91
+ font-size: 12px;
92
+ text-transform: uppercase;
93
+ letter-spacing: 0.08em;
94
+ }
95
+ .nav-item {
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: space-between;
99
+ gap: 10px;
100
+ min-height: 34px;
101
+ padding: 0 10px;
102
+ border-radius: 8px;
103
+ color: #343b46;
104
+ font-size: 14px;
105
+ }
106
+ .nav-item.active { background: #dde5ee; color: #111827; font-weight: 650; }
107
+ .nav-dot { width: 8px; height: 8px; border-radius: 99px; background: #94a3b8; }
108
+ .nav-dot.ok { background: var(--green); }
109
+ .nav-dot.warn { background: var(--orange); }
110
+ .main {
111
+ display: grid;
112
+ grid-template-rows: 64px 1fr 156px;
113
+ background: #fbfcfd;
114
+ min-width: 0;
115
+ }
116
+ .topbar {
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: space-between;
120
+ border-bottom: 1px solid var(--line);
121
+ padding: 0 22px;
122
+ background: var(--surface);
123
+ }
124
+ .title-block { min-width: 0; }
125
+ .title {
126
+ font-size: 15px;
127
+ font-weight: 700;
128
+ white-space: nowrap;
129
+ overflow: hidden;
130
+ text-overflow: ellipsis;
131
+ }
132
+ .subtitle { color: var(--muted); font-size: 12px; margin-top: 2px; }
133
+ .top-actions { display: flex; gap: 8px; align-items: center; }
134
+ .content {
135
+ overflow: auto;
136
+ padding: 24px 24px 30px;
137
+ }
138
+ .mission {
139
+ max-width: 920px;
140
+ margin: 0 auto;
141
+ }
142
+ .hero-panel {
143
+ background: var(--surface);
144
+ border: 1px solid var(--line);
145
+ border-radius: 8px;
146
+ box-shadow: var(--shadow);
147
+ padding: 18px;
148
+ margin-bottom: 16px;
149
+ }
150
+ .hero-grid {
151
+ display: grid;
152
+ grid-template-columns: minmax(0, 1fr) 210px;
153
+ gap: 18px;
154
+ align-items: start;
155
+ }
156
+ .label {
157
+ color: var(--muted);
158
+ font-size: 12px;
159
+ text-transform: uppercase;
160
+ letter-spacing: 0.08em;
161
+ margin-bottom: 8px;
162
+ }
163
+ .request { font-size: 20px; font-weight: 750; line-height: 1.35; margin-bottom: 12px; }
164
+ .meta-grid {
165
+ display: grid;
166
+ grid-template-columns: repeat(3, minmax(0, 1fr));
167
+ gap: 8px;
168
+ }
169
+ .meta {
170
+ background: var(--surface-soft);
171
+ border: 1px solid var(--line);
172
+ border-radius: 8px;
173
+ padding: 10px;
174
+ min-height: 64px;
175
+ }
176
+ .meta strong { display: block; font-size: 13px; margin-bottom: 4px; }
177
+ .meta span { color: var(--muted); font-size: 12px; overflow-wrap: anywhere; }
178
+ .ring {
179
+ height: 164px;
180
+ border: 1px solid var(--line);
181
+ border-radius: 8px;
182
+ display: grid;
183
+ place-items: center;
184
+ background: #f8fafc;
185
+ }
186
+ .score { text-align: center; }
187
+ .score b { font-size: 34px; display: block; }
188
+ .score span { color: var(--muted); font-size: 12px; }
189
+ .section {
190
+ background: var(--surface);
191
+ border: 1px solid var(--line);
192
+ border-radius: 8px;
193
+ padding: 16px;
194
+ margin-bottom: 14px;
195
+ }
196
+ .section-head {
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: space-between;
200
+ gap: 12px;
201
+ margin-bottom: 12px;
202
+ }
203
+ .section h2 { margin: 0; font-size: 15px; }
204
+ .pill {
205
+ display: inline-flex;
206
+ align-items: center;
207
+ min-height: 24px;
208
+ padding: 0 8px;
209
+ border-radius: 999px;
210
+ border: 1px solid var(--line);
211
+ color: var(--muted);
212
+ font-size: 12px;
213
+ background: #fbfcfd;
214
+ }
215
+ .list { display: grid; gap: 8px; }
216
+ .item {
217
+ display: grid;
218
+ grid-template-columns: 18px minmax(0, 1fr);
219
+ gap: 8px;
220
+ line-height: 1.38;
221
+ color: #2c3440;
222
+ font-size: 14px;
223
+ }
224
+ .checkmark {
225
+ width: 18px;
226
+ height: 18px;
227
+ border-radius: 5px;
228
+ display: grid;
229
+ place-items: center;
230
+ background: #eaf7ef;
231
+ color: var(--green);
232
+ font-size: 12px;
233
+ margin-top: 1px;
234
+ }
235
+ .checkmark.warn { background: #fff7e8; color: var(--orange); }
236
+ .doc-grid {
237
+ display: grid;
238
+ grid-template-columns: repeat(2, minmax(0, 1fr));
239
+ gap: 8px;
240
+ }
241
+ .doc-row {
242
+ min-height: 38px;
243
+ display: flex;
244
+ align-items: center;
245
+ justify-content: space-between;
246
+ gap: 10px;
247
+ border: 1px solid var(--line);
248
+ border-radius: 8px;
249
+ padding: 0 10px;
250
+ font-size: 13px;
251
+ background: #fbfcfd;
252
+ }
253
+ .bottom {
254
+ border-top: 1px solid var(--line);
255
+ background: var(--surface);
256
+ padding: 16px 22px;
257
+ overflow-x: auto;
258
+ }
259
+ .cards {
260
+ display: grid;
261
+ grid-template-columns: repeat(4, minmax(170px, 1fr));
262
+ gap: 12px;
263
+ max-width: 920px;
264
+ margin: 0 auto;
265
+ }
266
+ .action-card {
267
+ border: 1px solid var(--line);
268
+ background: var(--surface-soft);
269
+ border-radius: 8px;
270
+ padding: 12px;
271
+ min-height: 116px;
272
+ display: grid;
273
+ align-content: space-between;
274
+ gap: 10px;
275
+ }
276
+ .action-card strong { font-size: 14px; }
277
+ .action-card span { color: var(--muted); font-size: 12px; line-height: 1.35; }
278
+ .side {
279
+ border-left: 1px solid var(--line);
280
+ background: var(--surface);
281
+ padding: 18px;
282
+ overflow: auto;
283
+ }
284
+ .side-panel {
285
+ border: 1px solid var(--line);
286
+ border-radius: 8px;
287
+ padding: 14px;
288
+ margin-bottom: 14px;
289
+ background: #ffffff;
290
+ }
291
+ .side-panel h3 { margin: 0 0 12px; font-size: 14px; }
292
+ .status-row {
293
+ display: flex;
294
+ align-items: flex-start;
295
+ gap: 9px;
296
+ padding: 8px 0;
297
+ border-top: 1px solid #edf0f3;
298
+ font-size: 13px;
299
+ }
300
+ .status-row:first-of-type { border-top: 0; padding-top: 0; }
301
+ .status-row small { display: block; color: var(--muted); line-height: 1.35; margin-top: 2px; }
302
+ .badge {
303
+ width: 18px;
304
+ height: 18px;
305
+ border-radius: 99px;
306
+ display: grid;
307
+ place-items: center;
308
+ flex: 0 0 auto;
309
+ color: white;
310
+ font-size: 11px;
311
+ margin-top: 1px;
312
+ background: var(--green);
313
+ }
314
+ .badge.warn { background: var(--orange); }
315
+ .console {
316
+ background: #111827;
317
+ color: #e5e7eb;
318
+ border-radius: 8px;
319
+ padding: 12px;
320
+ min-height: 84px;
321
+ max-height: 160px;
322
+ overflow: auto;
323
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
324
+ font-size: 12px;
325
+ line-height: 1.45;
326
+ white-space: pre-wrap;
327
+ overflow-wrap: anywhere;
328
+ }
329
+ .split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px; }
330
+ .hidden { display: none; }
331
+ @media (max-width: 1180px) {
332
+ body { overflow: auto; }
333
+ .app { grid-template-columns: 240px minmax(0, 1fr); height: auto; min-height: 100vh; }
334
+ .side { grid-column: 1 / -1; border-left: 0; border-top: 1px solid var(--line); }
335
+ .main { min-height: 760px; }
336
+ }
337
+ @media (max-width: 820px) {
338
+ .app { display: block; }
339
+ .sidebar { border-right: 0; border-bottom: 1px solid var(--line); }
340
+ .main { display: block; }
341
+ .topbar, .bottom { position: static; }
342
+ .hero-grid, .meta-grid, .split, .cards, .doc-grid { grid-template-columns: 1fr; }
343
+ body { overflow: auto; }
344
+ }
345
+ </style>
346
+ </head>
347
+ <body>
348
+ <div class="app">
349
+ <aside class="sidebar">
350
+ <div class="brand"><div class="mark">GE</div><div>Gaslighting<br />Mission Control</div></div>
351
+ <div class="nav-title">Project</div>
352
+ <div class="nav-item active"><span id="navProject">Loading project</span><span class="nav-dot" id="navDot"></span></div>
353
+ <div class="nav-item"><span>Discipline Docs</span><span class="nav-dot ok"></span></div>
354
+ <div class="nav-item"><span>Codex Runtime</span><span class="nav-dot warn" id="runtimeDot"></span></div>
355
+ <div class="nav-title">Workflow</div>
356
+ <div class="nav-item"><span>1. Confirm mission</span></div>
357
+ <div class="nav-item"><span>2. Fix care risks</span></div>
358
+ <div class="nav-item"><span>3. Start Codex</span></div>
359
+ <div class="nav-title">Root</div>
360
+ <div class="nav-item"><span id="rootPath">.</span></div>
361
+ </aside>
362
+
363
+ <main class="main">
364
+ <header class="topbar">
365
+ <div class="title-block">
366
+ <div class="title" id="title">Mission Control</div>
367
+ <div class="subtitle" id="subtitle">Codex-first project discipline cockpit</div>
368
+ </div>
369
+ <div class="top-actions">
370
+ <button class="ghost" id="refreshBtn">Refresh</button>
371
+ <button class="primary" id="startBtn">Start Codex</button>
372
+ </div>
373
+ </header>
374
+
375
+ <section class="content">
376
+ <div class="mission">
377
+ <div class="hero-panel">
378
+ <div class="hero-grid">
379
+ <div>
380
+ <div class="label">Current Request</div>
381
+ <div class="request" id="requestText">Loading</div>
382
+ <div class="meta-grid">
383
+ <div class="meta"><strong>Type</strong><span id="projectType">-</span></div>
384
+ <div class="meta"><strong>Confidence</strong><span id="confidence">-</span></div>
385
+ <div class="meta"><strong>Stack</strong><span id="stackHints">-</span></div>
386
+ </div>
387
+ </div>
388
+ <div class="ring">
389
+ <div class="score"><b id="readinessScore">0%</b><span>readiness</span></div>
390
+ </div>
391
+ </div>
392
+ </div>
393
+
394
+ <div class="section">
395
+ <div class="section-head"><h2>Mission Purpose</h2><span class="pill">must not drift</span></div>
396
+ <div class="list" id="purposeList"></div>
397
+ </div>
398
+
399
+ <div class="split">
400
+ <div class="section">
401
+ <div class="section-head"><h2>Expected Pages</h2><span class="pill" id="pageCount">0</span></div>
402
+ <div class="list" id="pageList"></div>
403
+ </div>
404
+ <div class="section">
405
+ <div class="section-head"><h2>Missing Info</h2><span class="pill">non-blocking first</span></div>
406
+ <div class="list" id="missingList"></div>
407
+ </div>
408
+ </div>
409
+
410
+ <div class="section">
411
+ <div class="section-head"><h2>Generated Control Files</h2><span class="pill">Codex reads these</span></div>
412
+ <div class="doc-grid" id="docGrid"></div>
413
+ </div>
414
+
415
+ <div class="section">
416
+ <div class="section-head"><h2>Codex Launch Prompt</h2><span class="pill">editable before launch</span></div>
417
+ <textarea id="requestInput"></textarea>
418
+ </div>
419
+ </div>
420
+ </section>
421
+
422
+ <footer class="bottom">
423
+ <div class="cards">
424
+ <div class="action-card"><div><strong>Refresh Docs</strong><br /><span>Regenerate .gaslighting and root AGENTS.md from the current request.</span></div><button id="generateBtn">Generate</button></div>
425
+ <div class="action-card"><div><strong>Connect GitHub</strong><br /><span>Initialize Git if needed and connect origin without blocking implementation.</span></div><button id="githubBtn">Connect</button></div>
426
+ <div class="action-card"><div><strong>Doctor</strong><br /><span>Check discipline documents and operational care state.</span></div><button id="doctorBtn">Check</button></div>
427
+ <div class="action-card"><div><strong>Start Codex</strong><br /><span>Launch Codex with the Gaslighting contract already loaded.</span></div><button class="primary" id="startCardBtn">Start</button></div>
428
+ </div>
429
+ </footer>
430
+ </main>
431
+
432
+ <aside class="side">
433
+ <div class="side-panel">
434
+ <h3>Environment</h3>
435
+ <div id="careChecks"></div>
436
+ </div>
437
+ <div class="side-panel">
438
+ <h3>Runtime</h3>
439
+ <div class="status-row"><span class="badge">C</span><div><strong>Codex args</strong><small id="codexArgs">-</small></div></div>
440
+ <div class="status-row"><span class="badge warn">M</span><div><strong>Model strategy</strong><small>Codex-first now. Multi-model slots can be added later.</small></div></div>
441
+ </div>
442
+ <div class="side-panel">
443
+ <h3>GitHub Remote</h3>
444
+ <input id="githubInput" placeholder="https://github.com/user/repo.git" />
445
+ </div>
446
+ <div class="side-panel">
447
+ <h3>Console</h3>
448
+ <div class="console" id="consoleBox">Waiting for mission state...</div>
449
+ </div>
450
+ </aside>
451
+ </div>
452
+
453
+ <script>
454
+ var state = null;
455
+ var docs = [
456
+ 'AGENTS.md',
457
+ '.gaslighting/GASLIGHTING.md',
458
+ '.gaslighting/PRD.md',
459
+ '.gaslighting/MISSING_INFO.md',
460
+ '.gaslighting/ASSUMPTIONS.md',
461
+ '.gaslighting/DECISION_LOG.md',
462
+ '.gaslighting/MEMORY.md',
463
+ '.gaslighting/AGENT_RUNTIME.md',
464
+ '.gaslighting/PROJECT_CARE.md'
465
+ ];
466
+
467
+ function el(id) { return document.getElementById(id); }
468
+ function log(message) { el('consoleBox').textContent = message; }
469
+ function item(text, warn) {
470
+ var row = document.createElement('div');
471
+ row.className = 'item';
472
+ var mark = document.createElement('div');
473
+ mark.className = warn ? 'checkmark warn' : 'checkmark';
474
+ mark.textContent = warn ? '!' : 'OK';
475
+ var body = document.createElement('div');
476
+ body.textContent = text;
477
+ row.appendChild(mark);
478
+ row.appendChild(body);
479
+ return row;
480
+ }
481
+ function statusRow(check) {
482
+ var row = document.createElement('div');
483
+ row.className = 'status-row';
484
+ var badge = document.createElement('span');
485
+ badge.className = check.ok ? 'badge' : 'badge warn';
486
+ badge.textContent = check.ok ? 'OK' : '!';
487
+ var body = document.createElement('div');
488
+ var strong = document.createElement('strong');
489
+ strong.textContent = check.label;
490
+ var small = document.createElement('small');
491
+ small.textContent = check.message;
492
+ body.appendChild(strong);
493
+ body.appendChild(small);
494
+ row.appendChild(badge);
495
+ row.appendChild(body);
496
+ return row;
497
+ }
498
+ function render(next) {
499
+ state = next;
500
+ el('title').textContent = 'Gaslighting Mission Control';
501
+ el('subtitle').textContent = state.root;
502
+ el('rootPath').textContent = state.root;
503
+ el('navProject').textContent = state.analysis.projectType;
504
+ el('requestText').textContent = state.request;
505
+ el('requestInput').value = state.request;
506
+ el('projectType').textContent = state.analysis.projectType;
507
+ el('confidence').textContent = state.analysis.confidence;
508
+ el('stackHints').textContent = state.analysis.detectedStackHints.length ? state.analysis.detectedStackHints.join(', ') : 'default web stack';
509
+ el('codexArgs').textContent = state.codexArgs;
510
+
511
+ var warningCount = state.care.checks.filter(function (check) { return !check.ok; }).length;
512
+ var score = Math.max(0, Math.round(((state.care.checks.length - warningCount) / Math.max(1, state.care.checks.length)) * 100));
513
+ el('readinessScore').textContent = score + '%';
514
+ el('navDot').className = warningCount ? 'nav-dot warn' : 'nav-dot ok';
515
+ el('runtimeDot').className = state.codexAvailable ? 'nav-dot ok' : 'nav-dot warn';
516
+
517
+ el('purposeList').replaceChildren.apply(el('purposeList'), state.purpose.map(function (text) { return item(text, false); }));
518
+ el('pageCount').textContent = String(state.pages.length);
519
+ el('pageList').replaceChildren.apply(el('pageList'), state.pages.map(function (text) { return item(text, false); }));
520
+ el('missingList').replaceChildren.apply(el('missingList'), state.analysis.missingInfo.map(function (info) { return item(info.item + ': ' + info.temporaryAssumption, info.blocking !== 'non_blocking'); }));
521
+
522
+ var careNodes = state.care.checks.map(statusRow);
523
+ el('careChecks').replaceChildren.apply(el('careChecks'), careNodes);
524
+
525
+ var docNodes = docs.map(function (name) {
526
+ var row = document.createElement('div');
527
+ row.className = 'doc-row';
528
+ var left = document.createElement('span');
529
+ left.textContent = name;
530
+ var right = document.createElement('span');
531
+ right.className = 'pill';
532
+ right.textContent = state.docsPresent[name] ? 'ready' : 'missing';
533
+ row.appendChild(left);
534
+ row.appendChild(right);
535
+ return row;
536
+ });
537
+ el('docGrid').replaceChildren.apply(el('docGrid'), docNodes);
538
+ log('Mission loaded. Review the warnings, then start Codex.');
539
+ }
540
+ async function api(path, options) {
541
+ var response = await fetch(path, options);
542
+ var data = await response.json();
543
+ if (!response.ok) throw new Error(data.message || 'Request failed.');
544
+ return data;
545
+ }
546
+ async function refresh() {
547
+ render(await api('/api/state'));
548
+ }
549
+ async function generate() {
550
+ log('Generating control documents...');
551
+ var data = await api('/api/generate', {
552
+ method: 'POST',
553
+ headers: { 'content-type': 'application/json' },
554
+ body: JSON.stringify({ request: el('requestInput').value })
555
+ });
556
+ render(data.state);
557
+ log('Documents regenerated. Codex can now start from the updated mission.');
558
+ }
559
+ async function startCodex() {
560
+ log('Starting Codex...');
561
+ var data = await api('/api/start-codex', {
562
+ method: 'POST',
563
+ headers: { 'content-type': 'application/json' },
564
+ body: JSON.stringify({ request: el('requestInput').value })
565
+ });
566
+ log(data.message + '\\n\\n' + data.command);
567
+ }
568
+ async function connectGithub() {
569
+ var githubUrl = el('githubInput').value.trim();
570
+ if (!githubUrl) {
571
+ log('Paste a GitHub remote URL first.');
572
+ return;
573
+ }
574
+ var data = await api('/api/connect-github', {
575
+ method: 'POST',
576
+ headers: { 'content-type': 'application/json' },
577
+ body: JSON.stringify({ githubUrl: githubUrl })
578
+ });
579
+ render(data.state);
580
+ log(data.results.map(function (result) { return (result.ok ? 'PASS ' : 'WARN ') + result.step + ': ' + result.message; }).join('\\n'));
581
+ }
582
+ el('refreshBtn').addEventListener('click', refresh);
583
+ el('generateBtn').addEventListener('click', generate);
584
+ el('startBtn').addEventListener('click', startCodex);
585
+ el('startCardBtn').addEventListener('click', startCodex);
586
+ el('githubBtn').addEventListener('click', connectGithub);
587
+ el('doctorBtn').addEventListener('click', function () {
588
+ if (!state) return;
589
+ var warnings = state.care.checks.filter(function (check) { return !check.ok; });
590
+ log(warnings.length ? warnings.map(function (check) { return 'WARN ' + check.label + ': ' + check.action; }).join('\\n') : 'PASS care checks look stable.');
591
+ });
592
+ refresh().catch(function (error) { log(error.message); });
593
+ </script>
594
+ </body>
595
+ </html>`;
596
+ }
@@ -0,0 +1,74 @@
1
+ import { spawn } from "node:child_process";
2
+ import { platform } from "node:os";
3
+ const defaultCodexArgs = "--search --dangerously-bypass-approvals-and-sandbox";
4
+ export function getDefaultCodexArgs() {
5
+ return process.env.GASLIGHTING_CODEX_ARGS || defaultCodexArgs;
6
+ }
7
+ export function buildCodexPrompt(request) {
8
+ return [
9
+ "Use the gaslighting skill.",
10
+ "",
11
+ "Read these files before doing any implementation:",
12
+ "1. AGENTS.md",
13
+ "2. .gaslighting/GASLIGHTING.md",
14
+ "3. .gaslighting/PRD.md",
15
+ "4. .gaslighting/STACK_POLICY.md",
16
+ "5. .gaslighting/MISSING_INFO.md",
17
+ "6. .gaslighting/ASSUMPTIONS.md",
18
+ "7. .gaslighting/DECISION_LOG.md",
19
+ "8. .gaslighting/MEMORY.md",
20
+ "9. .gaslighting/AGENT_RUNTIME.md",
21
+ "10. .gaslighting/PROJECT_CARE.md",
22
+ "",
23
+ `Project request: ${request}`,
24
+ "",
25
+ "Implement the MVP. Preserve full scope. Do not use TODOs as fake implementation. Do not deliver representative examples when full coverage is required. If something is incomplete, declare it explicitly.",
26
+ ].join("\n");
27
+ }
28
+ export function buildCodexCommand(input) {
29
+ const args = splitCommandArgs(input.codexArgs || getDefaultCodexArgs());
30
+ return ["codex", ...args, "-C", quoteArg(input.root), quoteArg(buildCodexPrompt(input.request))].join(" ");
31
+ }
32
+ export function launchCodex(input) {
33
+ const args = splitCommandArgs(input.codexArgs || getDefaultCodexArgs());
34
+ const prompt = buildCodexPrompt(input.request);
35
+ const command = buildCodexCommand(input);
36
+ try {
37
+ if (platform() === "win32") {
38
+ const child = spawn("cmd.exe", ["/c", "start", "Gaslighting Codex", "/D", input.root, "codex", ...args, "-C", input.root, prompt], {
39
+ cwd: input.root,
40
+ detached: true,
41
+ stdio: "ignore",
42
+ });
43
+ child.unref();
44
+ return { ok: true, command, message: "Started Codex in a new terminal window." };
45
+ }
46
+ if (platform() === "darwin") {
47
+ const script = `tell application "Terminal" to do script ${JSON.stringify(`cd ${quoteShell(input.root)} && ${command}`)}`;
48
+ const child = spawn("osascript", ["-e", script], { detached: true, stdio: "ignore" });
49
+ child.unref();
50
+ return { ok: true, command, message: "Started Codex in Terminal." };
51
+ }
52
+ const child = spawn("sh", ["-lc", `cd ${quoteShell(input.root)} && ${command}`], { detached: true, stdio: "ignore" });
53
+ child.unref();
54
+ return { ok: true, command, message: "Started Codex in the background. If no terminal appears, copy the command and run it manually." };
55
+ }
56
+ catch (error) {
57
+ const message = error instanceof Error ? error.message : "Unknown Codex launch failure.";
58
+ return { ok: false, command, message };
59
+ }
60
+ }
61
+ export function splitCommandArgs(input) {
62
+ const args = [];
63
+ const pattern = /"([^"]*)"|'([^']*)'|(\S+)/g;
64
+ let match;
65
+ while ((match = pattern.exec(input)) !== null)
66
+ args.push(match[1] ?? match[2] ?? match[3] ?? "");
67
+ return args.filter(Boolean);
68
+ }
69
+ function quoteArg(value) {
70
+ return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\n", "\\n")}"`;
71
+ }
72
+ function quoteShell(value) {
73
+ return `'${value.replaceAll("'", "'\\''")}'`;
74
+ }