create-theokit 1.23.7 โ†’ 1.23.8

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.
@@ -1,70 +1,437 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
5
- <title>TheoKit App</title>
6
- <style>
7
- :root{--bg:#0a0a0a;--card:#141414;--border:#2a2a2a;--text:#e0e0e0;--muted:#888;--accent:#6366f1;--green:#22c55e;--red:#ef4444;--yellow:#eab308}
8
- *{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:var(--bg);color:var(--text);min-height:100vh}
9
- #app{max-width:1200px;margin:0 auto;padding:20px}h1{font-size:1.6rem}.accent{color:var(--accent)}.subtitle{color:var(--muted);font-size:.85rem;margin-top:2px}
10
- .role-bar{margin:12px 0}.role-bar select{padding:6px 12px;background:#1a1a1a;border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.8rem}
11
- .grid{display:grid;grid-template-columns:1fr 1fr;gap:20px}@media(max-width:768px){.grid{grid-template-columns:1fr}}
12
- .card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:20px}
13
- h2{font-size:1rem;margin-bottom:14px;display:flex;align-items:center;gap:8px}.badge{font-size:.65rem;padding:2px 8px;border-radius:99px;background:#6366f122;color:var(--accent)}.badge-ai{background:#eab30822;color:var(--yellow)}
14
- table{width:100%;border-collapse:collapse;font-size:.85rem}th{text-align:left;padding:8px 4px;color:var(--muted);border-bottom:1px solid var(--border);font-weight:500}td{padding:8px 4px;border-bottom:1px solid var(--border)}
15
- tr.done td{opacity:.5;text-decoration:line-through}.prio{font-size:.7rem;padding:2px 8px;border-radius:99px}.prio-high{background:#ef444422;color:var(--red)}.prio-med{background:#eab30822;color:var(--yellow)}.prio-low{background:#22c55e22;color:var(--green)}
16
- .create-bar{display:flex;gap:8px;margin-top:14px}.create-bar input{flex:1;padding:8px 12px;background:#1a1a1a;border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.85rem}
17
- .create-bar select{padding:8px;background:#1a1a1a;border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.8rem}.create-bar button{padding:8px 16px;background:var(--accent);color:#fff;border:none;border-radius:6px;cursor:pointer;font-weight:600}
18
- .error{color:var(--red);font-size:.8rem;margin-top:4px}
19
- .chat-box{height:400px;overflow-y:auto;padding:12px;background:#0d0d0d;border-radius:8px;margin-bottom:10px;font-size:.85rem;line-height:1.6}
20
- .msg{margin-bottom:10px;padding:8px 12px;border-radius:8px}.msg.user{background:#6366f118;color:var(--accent)}.msg.agent{background:#1a1a1a}.msg.tool{background:#eab30810;color:var(--yellow);font-size:.78rem;font-family:monospace}.msg.system{color:var(--muted);font-size:.78rem;font-style:italic}.msg.error{color:var(--red)}
21
- .chat-bar{display:flex;gap:8px}.chat-bar input{flex:1;padding:10px 14px;background:#1a1a1a;border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:.9rem;outline:none}.chat-bar input:focus{border-color:var(--accent)}
22
- .chat-bar button{padding:10px 20px;background:var(--accent);color:#fff;border:none;border-radius:8px;cursor:pointer;font-weight:600}.chat-bar button:disabled{opacity:.4;cursor:not-allowed}
23
- .cost{color:var(--muted);font-size:.75rem;margin-top:6px;text-align:right}
24
- </style>
25
- </head>
26
- <body>
27
- <div id="app">
28
- <header>
29
- <h1><span class="accent">TheoKit</span> App</h1>
30
- <p class="subtitle">Controllers + AI Agent โ€” same pipeline</p>
31
- <div class="role-bar"><label>Role: </label><select id="role"><option value="">None</option><option value="user" selected>User</option><option value="admin">Admin</option></select></div>
32
- </header>
33
- <main class="grid">
34
- <section class="card">
35
- <h2>๐Ÿ“‹ Tasks <span class="badge">@Controller</span></h2>
36
- <table><thead><tr><th>Task</th><th>Priority</th><th>Status</th></tr></thead><tbody id="task-list"></tbody></table>
37
- <form id="create-form" class="create-bar"><input id="new-title" placeholder="New task..." required minlength="3"><select id="new-priority"><option value="medium">Medium</option><option value="high">High</option><option value="low">Low</option></select><button type="submit">Add</button></form>
38
- <p id="form-error" class="error"></p>
39
- </section>
40
- <section class="card">
41
- <h2>๐Ÿค– AI Assistant <span class="badge badge-ai">@Agent + SSE</span></h2>
42
- <div id="chat" class="chat-box"><div class="msg system">Ask me to list, create, or complete tasks...</div></div>
43
- <div class="chat-bar"><input id="chat-input" placeholder="Message the AI assistant..."><button id="chat-send">Send</button></div>
44
- <p id="chat-cost" class="cost"></p>
45
- </section>
46
- </main>
47
- </div>
48
- <script>
49
- const getRole=()=>document.getElementById('role').value
50
- const headers=()=>{const h={'Content-Type':'application/json'};const r=getRole();if(r)h['x-role']=r;return h}
51
- let sessionId='s-'+Date.now()
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
6
+ <title>TheoKit App</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0a0a0a;
10
+ --card: #141414;
11
+ --border: #2a2a2a;
12
+ --text: #e0e0e0;
13
+ --muted: #888;
14
+ --accent: #6366f1;
15
+ --green: #22c55e;
16
+ --red: #ef4444;
17
+ --yellow: #eab308;
18
+ }
19
+ * {
20
+ margin: 0;
21
+ padding: 0;
22
+ box-sizing: border-box;
23
+ }
24
+ body {
25
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
26
+ background: var(--bg);
27
+ color: var(--text);
28
+ min-height: 100vh;
29
+ }
30
+ #app {
31
+ max-width: 1200px;
32
+ margin: 0 auto;
33
+ padding: 20px;
34
+ }
35
+ h1 {
36
+ font-size: 1.6rem;
37
+ }
38
+ .accent {
39
+ color: var(--accent);
40
+ }
41
+ .subtitle {
42
+ color: var(--muted);
43
+ font-size: 0.85rem;
44
+ margin-top: 2px;
45
+ }
46
+ .role-bar {
47
+ margin: 12px 0;
48
+ }
49
+ .role-bar select {
50
+ padding: 6px 12px;
51
+ background: #1a1a1a;
52
+ border: 1px solid var(--border);
53
+ border-radius: 6px;
54
+ color: var(--text);
55
+ font-size: 0.8rem;
56
+ }
57
+ .grid {
58
+ display: grid;
59
+ grid-template-columns: 1fr 1fr;
60
+ gap: 20px;
61
+ }
62
+ @media (max-width: 768px) {
63
+ .grid {
64
+ grid-template-columns: 1fr;
65
+ }
66
+ }
67
+ .card {
68
+ background: var(--card);
69
+ border: 1px solid var(--border);
70
+ border-radius: 12px;
71
+ padding: 20px;
72
+ }
73
+ h2 {
74
+ font-size: 1rem;
75
+ margin-bottom: 14px;
76
+ display: flex;
77
+ align-items: center;
78
+ gap: 8px;
79
+ }
80
+ .badge {
81
+ font-size: 0.65rem;
82
+ padding: 2px 8px;
83
+ border-radius: 99px;
84
+ background: #6366f122;
85
+ color: var(--accent);
86
+ }
87
+ .badge-ai {
88
+ background: #eab30822;
89
+ color: var(--yellow);
90
+ }
91
+ table {
92
+ width: 100%;
93
+ border-collapse: collapse;
94
+ font-size: 0.85rem;
95
+ }
96
+ th {
97
+ text-align: left;
98
+ padding: 8px 4px;
99
+ color: var(--muted);
100
+ border-bottom: 1px solid var(--border);
101
+ font-weight: 500;
102
+ }
103
+ td {
104
+ padding: 8px 4px;
105
+ border-bottom: 1px solid var(--border);
106
+ }
107
+ tr.done td {
108
+ opacity: 0.5;
109
+ text-decoration: line-through;
110
+ }
111
+ .prio {
112
+ font-size: 0.7rem;
113
+ padding: 2px 8px;
114
+ border-radius: 99px;
115
+ }
116
+ .prio-high {
117
+ background: #ef444422;
118
+ color: var(--red);
119
+ }
120
+ .prio-med {
121
+ background: #eab30822;
122
+ color: var(--yellow);
123
+ }
124
+ .prio-low {
125
+ background: #22c55e22;
126
+ color: var(--green);
127
+ }
128
+ .create-bar {
129
+ display: flex;
130
+ gap: 8px;
131
+ margin-top: 14px;
132
+ }
133
+ .create-bar input {
134
+ flex: 1;
135
+ padding: 8px 12px;
136
+ background: #1a1a1a;
137
+ border: 1px solid var(--border);
138
+ border-radius: 6px;
139
+ color: var(--text);
140
+ font-size: 0.85rem;
141
+ }
142
+ .create-bar select {
143
+ padding: 8px;
144
+ background: #1a1a1a;
145
+ border: 1px solid var(--border);
146
+ border-radius: 6px;
147
+ color: var(--text);
148
+ font-size: 0.8rem;
149
+ }
150
+ .create-bar button {
151
+ padding: 8px 16px;
152
+ background: var(--accent);
153
+ color: #fff;
154
+ border: none;
155
+ border-radius: 6px;
156
+ cursor: pointer;
157
+ font-weight: 600;
158
+ }
159
+ .error {
160
+ color: var(--red);
161
+ font-size: 0.8rem;
162
+ margin-top: 4px;
163
+ }
164
+ .chat-box {
165
+ height: 400px;
166
+ overflow-y: auto;
167
+ padding: 12px;
168
+ background: #0d0d0d;
169
+ border-radius: 8px;
170
+ margin-bottom: 10px;
171
+ font-size: 0.85rem;
172
+ line-height: 1.6;
173
+ }
174
+ .msg {
175
+ margin-bottom: 10px;
176
+ padding: 8px 12px;
177
+ border-radius: 8px;
178
+ }
179
+ .msg.user {
180
+ background: #6366f118;
181
+ color: var(--accent);
182
+ }
183
+ .msg.agent {
184
+ background: #1a1a1a;
185
+ }
186
+ .msg.tool {
187
+ background: #eab30810;
188
+ color: var(--yellow);
189
+ font-size: 0.78rem;
190
+ font-family: monospace;
191
+ }
192
+ .msg.system {
193
+ color: var(--muted);
194
+ font-size: 0.78rem;
195
+ font-style: italic;
196
+ }
197
+ .msg.error {
198
+ color: var(--red);
199
+ }
200
+ .chat-bar {
201
+ display: flex;
202
+ gap: 8px;
203
+ }
204
+ .chat-bar input {
205
+ flex: 1;
206
+ padding: 10px 14px;
207
+ background: #1a1a1a;
208
+ border: 1px solid var(--border);
209
+ border-radius: 8px;
210
+ color: var(--text);
211
+ font-size: 0.9rem;
212
+ outline: none;
213
+ }
214
+ .chat-bar input:focus {
215
+ border-color: var(--accent);
216
+ }
217
+ .chat-bar button {
218
+ padding: 10px 20px;
219
+ background: var(--accent);
220
+ color: #fff;
221
+ border: none;
222
+ border-radius: 8px;
223
+ cursor: pointer;
224
+ font-weight: 600;
225
+ }
226
+ .chat-bar button:disabled {
227
+ opacity: 0.4;
228
+ cursor: not-allowed;
229
+ }
230
+ .cost {
231
+ color: var(--muted);
232
+ font-size: 0.75rem;
233
+ margin-top: 6px;
234
+ text-align: right;
235
+ }
236
+ </style>
237
+ </head>
238
+ <body>
239
+ <div id="app">
240
+ <header>
241
+ <h1><span class="accent">TheoKit</span> App</h1>
242
+ <p class="subtitle">Controllers + AI Agent โ€” same pipeline</p>
243
+ <div class="role-bar">
244
+ <label>Role: </label
245
+ ><select id="role">
246
+ <option value="">None</option>
247
+ <option value="user" selected>User</option>
248
+ <option value="admin">Admin</option>
249
+ </select>
250
+ </div>
251
+ </header>
252
+ <main class="grid">
253
+ <section class="card">
254
+ <h2>๐Ÿ“‹ Tasks <span class="badge">@Controller</span></h2>
255
+ <table>
256
+ <thead>
257
+ <tr>
258
+ <th>Task</th>
259
+ <th>Priority</th>
260
+ <th>Status</th>
261
+ </tr>
262
+ </thead>
263
+ <tbody id="task-list"></tbody>
264
+ </table>
265
+ <form id="create-form" class="create-bar">
266
+ <input id="new-title" placeholder="New task..." required minlength="3" /><select
267
+ id="new-priority"
268
+ >
269
+ <option value="medium">Medium</option>
270
+ <option value="high">High</option>
271
+ <option value="low">Low</option></select
272
+ ><button type="submit">Add</button>
273
+ </form>
274
+ <p id="form-error" class="error"></p>
275
+ </section>
276
+ <section class="card">
277
+ <h2>๐Ÿค– AI Assistant <span class="badge badge-ai">@Agent + SSE</span></h2>
278
+ <div id="chat" class="chat-box">
279
+ <div class="msg system">Ask me to list, create, or complete tasks...</div>
280
+ </div>
281
+ <div class="chat-bar">
282
+ <input id="chat-input" placeholder="Message the AI assistant..." /><button
283
+ id="chat-send"
284
+ >
285
+ Send
286
+ </button>
287
+ </div>
288
+ <p id="chat-cost" class="cost"></p>
289
+ </section>
290
+ </main>
291
+ </div>
292
+ <script>
293
+ const getRole = () => document.getElementById('role').value
294
+ const headers = () => {
295
+ const h = { 'Content-Type': 'application/json' }
296
+ const r = getRole()
297
+ if (r) h['x-role'] = r
298
+ return h
299
+ }
300
+ let sessionId = 's-' + Date.now()
52
301
 
53
- async function loadTasks(){const r=await fetch('/api/tasks');const t=await r.json();document.getElementById('task-list').innerHTML=t.map(t=>{const s=t.done?'done':'';const p=t.priority==='high'?'prio-high':t.priority==='low'?'prio-low':'prio-med';return'<tr class="'+s+'"><td>'+(t.done?'โœ… ':'โ—‹ ')+t.title+'</td><td><span class="prio '+p+'">'+t.priority+'</span></td><td>'+(t.done?'Done':'To do')+'</td></tr>'}).join('')}
302
+ async function loadTasks() {
303
+ const r = await fetch('/api/tasks')
304
+ const t = await r.json()
305
+ document.getElementById('task-list').innerHTML = t
306
+ .map((t) => {
307
+ const s = t.done ? 'done' : ''
308
+ const p =
309
+ t.priority === 'high' ? 'prio-high' : t.priority === 'low' ? 'prio-low' : 'prio-med'
310
+ return (
311
+ '<tr class="' +
312
+ s +
313
+ '"><td>' +
314
+ (t.done ? 'โœ… ' : 'โ—‹ ') +
315
+ t.title +
316
+ '</td><td><span class="prio ' +
317
+ p +
318
+ '">' +
319
+ t.priority +
320
+ '</span></td><td>' +
321
+ (t.done ? 'Done' : 'To do') +
322
+ '</td></tr>'
323
+ )
324
+ })
325
+ .join('')
326
+ }
54
327
 
55
- document.getElementById('create-form').addEventListener('submit',async e=>{e.preventDefault();const t=document.getElementById('new-title').value.trim();const p=document.getElementById('new-priority').value;const err=document.getElementById('form-error');err.textContent='';if(!t)return;const r=await fetch('/api/tasks',{method:'POST',headers:headers(),body:JSON.stringify({title:t,priority:p})});if(r.status===403){err.textContent='403 โ€” Need User role';return}if(r.status===422){const e=await r.json();err.textContent=e.error?.issues?.[0]?.message||'Validation error';return}if(!r.ok){err.textContent='Error '+r.status;return}document.getElementById('new-title').value='';loadTasks()})
328
+ document.getElementById('create-form').addEventListener('submit', async (e) => {
329
+ e.preventDefault()
330
+ const t = document.getElementById('new-title').value.trim()
331
+ const p = document.getElementById('new-priority').value
332
+ const err = document.getElementById('form-error')
333
+ err.textContent = ''
334
+ if (!t) return
335
+ const r = await fetch('/api/tasks', {
336
+ method: 'POST',
337
+ headers: headers(),
338
+ body: JSON.stringify({ title: t, priority: p }),
339
+ })
340
+ if (r.status === 403) {
341
+ err.textContent = '403 โ€” Need User role'
342
+ return
343
+ }
344
+ if (r.status === 422) {
345
+ const e = await r.json()
346
+ err.textContent = e.error?.issues?.[0]?.message || 'Validation error'
347
+ return
348
+ }
349
+ if (!r.ok) {
350
+ err.textContent = 'Error ' + r.status
351
+ return
352
+ }
353
+ document.getElementById('new-title').value = ''
354
+ loadTasks()
355
+ })
56
356
 
57
- document.getElementById('chat-send').addEventListener('click',sendChat)
58
- document.getElementById('chat-input').addEventListener('keydown',e=>{if(e.key==='Enter')sendChat()})
357
+ document.getElementById('chat-send').addEventListener('click', sendChat)
358
+ document.getElementById('chat-input').addEventListener('keydown', (e) => {
359
+ if (e.key === 'Enter') sendChat()
360
+ })
59
361
 
60
- async function sendChat(){const input=document.getElementById('chat-input');const msg=input.value.trim();if(!msg)return;input.value='';const chat=document.getElementById('chat');chat.innerHTML+='<div class="msg user">You: '+msg.replace(/</g,'&lt;')+'</div>';document.getElementById('chat-send').disabled=true
61
- try{const r=await fetch('/api/agents/assistant/chat',{method:'POST',headers:headers(),body:JSON.stringify({message:msg,sessionId})});if(r.status===403){chat.innerHTML+='<div class="msg system">403 โ€” Need User role</div>';document.getElementById('chat-send').disabled=false;return}
62
- const reader=r.body.getReader();const dec=new TextDecoder();let div=document.createElement('div');div.className='msg agent';chat.appendChild(div);let buf=''
63
- while(true){const{done,value}=await reader.read();if(done)break;buf+=dec.decode(value,{stream:true});const lines=buf.split('\n');buf=lines.pop()||''
64
- for(const line of lines){if(!line.startsWith('data: '))continue;try{const ev=JSON.parse(line.slice(6));if(ev.type==='text_delta')div.innerHTML+=ev.content.replace(/</g,'&lt;').replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>').replace(/\n/g,'<br>');else if(ev.type==='tool_call'){const t=document.createElement('div');t.className='msg tool';t.textContent='๐Ÿ”ง '+ev.toolName;chat.insertBefore(t,div)}else if(ev.type==='tool_result'){const t=document.createElement('div');t.className='msg tool';t.textContent='โœ… '+(ev.output||'').substring(0,80);chat.insertBefore(t,div)}else if(ev.type==='done'){document.getElementById('chat-cost').textContent=(ev.usage?.totalTokens||0)+' tokens ยท '+(ev.durationMs||0)+'ms'+(ev.cost?' ยท $'+ev.cost.toFixed(6):'')}else if(ev.type==='error'){chat.innerHTML+='<div class="msg error">'+ev.message+'</div>'}}catch{}}
65
- chat.scrollTop=chat.scrollHeight}loadTasks()}catch(e){chat.innerHTML+='<div class="msg error">'+e.message+'</div>'}document.getElementById('chat-send').disabled=false;chat.scrollTop=chat.scrollHeight}
362
+ async function sendChat() {
363
+ const input = document.getElementById('chat-input')
364
+ const msg = input.value.trim()
365
+ if (!msg) return
366
+ input.value = ''
367
+ const chat = document.getElementById('chat')
368
+ chat.innerHTML += '<div class="msg user">You: ' + msg.replace(/</g, '&lt;') + '</div>'
369
+ document.getElementById('chat-send').disabled = true
370
+ try {
371
+ const r = await fetch('/api/agents/assistant/chat', {
372
+ method: 'POST',
373
+ headers: headers(),
374
+ body: JSON.stringify({ message: msg, sessionId }),
375
+ })
376
+ if (r.status === 403) {
377
+ chat.innerHTML += '<div class="msg system">403 โ€” Need User role</div>'
378
+ document.getElementById('chat-send').disabled = false
379
+ return
380
+ }
381
+ const reader = r.body.getReader()
382
+ const dec = new TextDecoder()
383
+ let div = document.createElement('div')
384
+ div.className = 'msg agent'
385
+ chat.appendChild(div)
386
+ let buf = ''
387
+ while (true) {
388
+ const { done, value } = await reader.read()
389
+ if (done) break
390
+ buf += dec.decode(value, { stream: true })
391
+ const lines = buf.split('\n')
392
+ buf = lines.pop() || ''
393
+ for (const line of lines) {
394
+ if (!line.startsWith('data: ')) continue
395
+ try {
396
+ const ev = JSON.parse(line.slice(6))
397
+ if (ev.type === 'text_delta')
398
+ div.innerHTML += ev.content
399
+ .replace(/</g, '&lt;')
400
+ .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
401
+ .replace(/\n/g, '<br>')
402
+ else if (ev.type === 'tool_call') {
403
+ const t = document.createElement('div')
404
+ t.className = 'msg tool'
405
+ t.textContent = '๐Ÿ”ง ' + ev.toolName
406
+ chat.insertBefore(t, div)
407
+ } else if (ev.type === 'tool_result') {
408
+ const t = document.createElement('div')
409
+ t.className = 'msg tool'
410
+ t.textContent = 'โœ… ' + (ev.output || '').substring(0, 80)
411
+ chat.insertBefore(t, div)
412
+ } else if (ev.type === 'done') {
413
+ document.getElementById('chat-cost').textContent =
414
+ (ev.usage?.totalTokens || 0) +
415
+ ' tokens ยท ' +
416
+ (ev.durationMs || 0) +
417
+ 'ms' +
418
+ (ev.cost ? ' ยท $' + ev.cost.toFixed(6) : '')
419
+ } else if (ev.type === 'error') {
420
+ chat.innerHTML += '<div class="msg error">' + ev.message + '</div>'
421
+ }
422
+ } catch {}
423
+ }
424
+ chat.scrollTop = chat.scrollHeight
425
+ }
426
+ loadTasks()
427
+ } catch (e) {
428
+ chat.innerHTML += '<div class="msg error">' + e.message + '</div>'
429
+ }
430
+ document.getElementById('chat-send').disabled = false
431
+ chat.scrollTop = chat.scrollHeight
432
+ }
66
433
 
67
- loadTasks()
68
- </script>
69
- </body>
434
+ loadTasks()
435
+ </script>
436
+ </body>
70
437
  </html>
@@ -11,7 +11,6 @@
11
11
  * 2. Add `'<name>': { ...inputShape }` below
12
12
  * 3. Use `ctx.queue.enqueue('<name>', { ...input })` from any route handler
13
13
  *
14
- * See: docs/concepts/jobs.md
15
14
  */
16
15
  declare module 'theokit/server' {
17
16
  interface JobRegistry {
@@ -1,15 +1,19 @@
1
1
  import { render } from 'ink'
2
+ import { loadProjectEnv } from '@theokit/sdk'
2
3
 
3
4
  import { App } from './App.js'
4
5
 
5
- // Load .env if present (Node native โ€” no dependency). Provider key: OPENROUTER_API_KEY or ANTHROPIC_API_KEY.
6
- if (typeof process.loadEnvFile === 'function') {
7
- try {
8
- process.loadEnvFile()
9
- } catch {
10
- // no .env on disk โ€” rely on the ambient environment
11
- }
12
- }
6
+ // Load a project `.env` if present, WITHOUT letting it move the credential store.
7
+ //
8
+ // A repository's `.env` is untrusted input: it arrives with the clone, before any trust prompt, and
9
+ // locating the credential store is one of the first things a build does. An unguarded loader lets a
10
+ // cloned repo set `THEOKIT_AUTH_HOME=/tmp/attacker-store` and redirect where credentials are read
11
+ // and written โ€” silently, because nothing fails when it works.
12
+ //
13
+ // `loadProjectEnv` applies everything else and refuses the keys that decide WHERE the framework
14
+ // looks (`SOVEREIGN_ENV_KEYS`). Those stay the operator's, set in the shell, never in a file that
15
+ // ships with a repository.
16
+ loadProjectEnv()
13
17
 
14
18
  // `exitOnCtrlC: false` so the App owns Ctrl+C โ€” a single press cancels a running turn, a double press
15
19
  // quits (the Claude Code affordance). Without this, Ink would hard-exit on the first Ctrl+C.