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.
- package/LICENSE +2 -2
- package/README.md +68 -0
- package/dist/cli.js +72 -58
- package/dist/cli.js.map +1 -1
- package/package.json +10 -1
- package/templates/default/README.md.tmpl +87 -50
- package/templates/default/agents/chat.ts +1 -2
- package/templates/default/app/components/Nav.tsx +1 -2
- package/templates/default/app/page.tsx +1 -1
- package/templates/default/docs/ARCHITECTURE.md +6 -2
- package/templates/default/dot-claude/skills/theokit-agents/SKILL.md +1 -1
- package/templates/default/dot-claude/skills/theokit-config/SKILL.md +1 -1
- package/templates/default/dot-claude/skills/theokit-ui/SKILL.md +3 -3
- package/templates/default/eslint.config.mjs +7 -7
- package/templates/default/index.html +11 -11
- package/templates/default/package.json.tmpl +4 -4
- package/templates/default/public/index.html +430 -63
- package/templates/default/types/jobs.d.ts +0 -1
- package/templates/surfaces/tui/tui/main.tsx.tmpl +12 -8
|
@@ -1,70 +1,437 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8"
|
|
5
|
-
<
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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()
|
|
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
|
|
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=>{
|
|
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(){
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
chat
|
|
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, '<') + '</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, '<')
|
|
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>
|
|
@@ -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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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.
|