@newsails/veil-cli 1.0.1

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 (199) hide show
  1. package/.veil/agents/analyst/AGENT.md +21 -0
  2. package/.veil/agents/analyst/agent.json +23 -0
  3. package/.veil/agents/assistant/AGENT.md +15 -0
  4. package/.veil/agents/assistant/agent.json +19 -0
  5. package/.veil/agents/coder/AGENT.md +18 -0
  6. package/.veil/agents/coder/agent.json +19 -0
  7. package/.veil/agents/hello/AGENT.md +5 -0
  8. package/.veil/agents/hello/agent.json +13 -0
  9. package/.veil/agents/writer/AGENT.md +12 -0
  10. package/.veil/agents/writer/agent.json +17 -0
  11. package/.veil/memory/MEMORY.md +343 -0
  12. package/.veil/memory/agents/analyst/MEMORY.md +55 -0
  13. package/.veil/memory/agents/hello/MEMORY.md +12 -0
  14. package/.veil/runtime.pid +1 -0
  15. package/.veil/settings.json +10 -0
  16. package/.veil-studio/studio.db +0 -0
  17. package/.veil-studio/studio.db-shm +0 -0
  18. package/.veil-studio/studio.db-wal +0 -0
  19. package/PLAN/01-vision.md +26 -0
  20. package/PLAN/02-tech-stack.md +94 -0
  21. package/PLAN/03-agents.md +232 -0
  22. package/PLAN/04-runtime.md +171 -0
  23. package/PLAN/05-tools.md +211 -0
  24. package/PLAN/06-communication.md +243 -0
  25. package/PLAN/07-storage.md +218 -0
  26. package/PLAN/08-api-cli.md +153 -0
  27. package/PLAN/09-permissions.md +108 -0
  28. package/PLAN/10-ably.md +105 -0
  29. package/PLAN/11-file-formats.md +442 -0
  30. package/PLAN/12-folder-structure.md +205 -0
  31. package/PLAN/13-operations.md +212 -0
  32. package/PLAN/README.md +23 -0
  33. package/README.md +128 -0
  34. package/REPORT.md +174 -0
  35. package/TODO.md +45 -0
  36. package/ai-tests/FRONTEND_PROMPT.md +220 -0
  37. package/ai-tests/Research & Planning.md +814 -0
  38. package/ai-tests/prompt-001-basic-api.md +230 -0
  39. package/ai-tests/prompt-002-basic-flows.md +230 -0
  40. package/ai-tests/prompt-003-agent-behaviors.md +220 -0
  41. package/api/middleware.js +60 -0
  42. package/api/routes/agents.js +193 -0
  43. package/api/routes/chat.js +93 -0
  44. package/api/routes/completions.js +122 -0
  45. package/api/routes/daemons.js +80 -0
  46. package/api/routes/memory.js +169 -0
  47. package/api/routes/models.js +40 -0
  48. package/api/routes/remote-methods.js +74 -0
  49. package/api/routes/sessions.js +208 -0
  50. package/api/routes/settings.js +108 -0
  51. package/api/routes/system.js +50 -0
  52. package/api/routes/tasks.js +270 -0
  53. package/api/server.js +120 -0
  54. package/cli/formatter.js +70 -0
  55. package/cli/index.js +443 -0
  56. package/cli/parser.js +113 -0
  57. package/config/config.json +10 -0
  58. package/config/models.json +6826 -0
  59. package/core/agent.js +329 -0
  60. package/core/cancel.js +38 -0
  61. package/core/compaction.js +176 -0
  62. package/core/events.js +13 -0
  63. package/core/loop.js +564 -0
  64. package/core/memory.js +51 -0
  65. package/core/prompt.js +185 -0
  66. package/core/queue.js +96 -0
  67. package/core/registry.js +291 -0
  68. package/core/remote-methods.js +124 -0
  69. package/core/router.js +386 -0
  70. package/core/running-sessions.js +18 -0
  71. package/docs/api/01-system.md +84 -0
  72. package/docs/api/02-agents.md +374 -0
  73. package/docs/api/03-chat.md +269 -0
  74. package/docs/api/04-tasks.md +470 -0
  75. package/docs/api/05-sessions.md +444 -0
  76. package/docs/api/06-daemons.md +142 -0
  77. package/docs/api/07-memory.md +186 -0
  78. package/docs/api/08-settings.md +133 -0
  79. package/docs/api/09-models.md +119 -0
  80. package/docs/api/09-websocket.md +350 -0
  81. package/docs/api/10-completions.md +134 -0
  82. package/docs/api/README.md +116 -0
  83. package/docs/guide/01-quickstart.md +220 -0
  84. package/docs/guide/02-folder-structure.md +185 -0
  85. package/docs/guide/03-configuration.md +252 -0
  86. package/docs/guide/04-agents.md +267 -0
  87. package/docs/guide/05-cli.md +290 -0
  88. package/docs/guide/06-tools.md +643 -0
  89. package/docs/guide/07-permissions.md +236 -0
  90. package/docs/guide/08-memory.md +139 -0
  91. package/docs/guide/09-multi-agent.md +271 -0
  92. package/docs/guide/10-daemons.md +226 -0
  93. package/docs/guide/README.md +53 -0
  94. package/docs/index.html +623 -0
  95. package/examples/README.md +151 -0
  96. package/examples/agents/assistant/AGENT.md +31 -0
  97. package/examples/agents/assistant/SOUL.md +9 -0
  98. package/examples/agents/assistant/agent.json +74 -0
  99. package/examples/agents/hello/AGENT.md +15 -0
  100. package/examples/agents/hello/agent.json +14 -0
  101. package/examples/agents/monitor/AGENT.md +51 -0
  102. package/examples/agents/monitor/agent.json +33 -0
  103. package/examples/agents/monitor/heartbeats/monitor.md +24 -0
  104. package/examples/agents/orchestrator/AGENT.md +70 -0
  105. package/examples/agents/orchestrator/agent.json +30 -0
  106. package/examples/agents/researcher/AGENT.md +52 -0
  107. package/examples/agents/researcher/agent.json +49 -0
  108. package/examples/agents/researcher/skills/web-research.md +28 -0
  109. package/examples/skills/code-review.md +72 -0
  110. package/examples/skills/summarise.md +59 -0
  111. package/examples/skills/web-research.md +42 -0
  112. package/examples/tools/word-count/index.js +27 -0
  113. package/examples/tools/word-count/tool.json +18 -0
  114. package/infrastructure/database.js +563 -0
  115. package/infrastructure/scheduler.js +122 -0
  116. package/llm/client.js +206 -0
  117. package/migrations/001-initial.sql +121 -0
  118. package/migrations/002-debuggability.sql +13 -0
  119. package/migrations/003-drop-orphaned-columns.sql +72 -0
  120. package/migrations/004-session-message-token-fields.sql +78 -0
  121. package/migrations/005-session-thinking.sql +5 -0
  122. package/package.json +30 -0
  123. package/schemas/agent.json +143 -0
  124. package/schemas/settings.json +111 -0
  125. package/scripts/fetch-models.js +93 -0
  126. package/session-debug-scenario.md +248 -0
  127. package/settings/fields.js +52 -0
  128. package/system-prompts/base-core.md +7 -0
  129. package/system-prompts/environment.md +13 -0
  130. package/system-prompts/reminders/anti-drift.md +6 -0
  131. package/system-prompts/reminders/stall-recovery.md +10 -0
  132. package/system-prompts/safety-rules.md +25 -0
  133. package/system-prompts/task-heuristics.md +27 -0
  134. package/test/client.js +71 -0
  135. package/test/integration/01-health.test.js +25 -0
  136. package/test/integration/02-agents.test.js +80 -0
  137. package/test/integration/03-chat-hello.test.js +48 -0
  138. package/test/integration/04-chat-multiturn.test.js +61 -0
  139. package/test/integration/05-chat-writer.test.js +48 -0
  140. package/test/integration/06-task-basic.test.js +68 -0
  141. package/test/integration/07-task-tools.test.js +74 -0
  142. package/test/integration/08-task-code-analysis.test.js +69 -0
  143. package/test/integration/09-memory-analyst.test.js +63 -0
  144. package/test/integration/10-task-advanced.test.js +85 -0
  145. package/test/integration/11-sessions-advanced.test.js +84 -0
  146. package/test/integration/12-assistant-chat-tools.test.js +75 -0
  147. package/test/integration/13-edge-cases.test.js +99 -0
  148. package/test/integration/14-cancel.test.js +62 -0
  149. package/test/integration/15-debug.test.js +106 -0
  150. package/test/integration/16-memory-api.test.js +83 -0
  151. package/test/integration/17-settings-api.test.js +41 -0
  152. package/test/integration/18-tool-search-activation.test.js +119 -0
  153. package/test/results/.gitkeep +0 -0
  154. package/test/runner.js +206 -0
  155. package/test/smoke.js +216 -0
  156. package/tools/agent_message.js +85 -0
  157. package/tools/agent_send.js +80 -0
  158. package/tools/agent_spawn.js +44 -0
  159. package/tools/bash.js +49 -0
  160. package/tools/edit_file.js +41 -0
  161. package/tools/glob.js +64 -0
  162. package/tools/grep.js +82 -0
  163. package/tools/list_dir.js +63 -0
  164. package/tools/log_write.js +31 -0
  165. package/tools/memory_read.js +38 -0
  166. package/tools/memory_search.js +65 -0
  167. package/tools/memory_write.js +42 -0
  168. package/tools/read_file.js +48 -0
  169. package/tools/sleep.js +22 -0
  170. package/tools/task_create.js +41 -0
  171. package/tools/task_respond.js +37 -0
  172. package/tools/task_spawn.js +64 -0
  173. package/tools/task_status.js +39 -0
  174. package/tools/task_subscribe.js +37 -0
  175. package/tools/todo_read.js +26 -0
  176. package/tools/todo_write.js +38 -0
  177. package/tools/tool_activate.js +24 -0
  178. package/tools/tool_search.js +24 -0
  179. package/tools/web_fetch.js +50 -0
  180. package/tools/web_search.js +52 -0
  181. package/tools/write_file.js +28 -0
  182. package/ui/api.js +190 -0
  183. package/ui/app.js +281 -0
  184. package/ui/index.html +382 -0
  185. package/ui/views/agents.js +377 -0
  186. package/ui/views/chat.js +610 -0
  187. package/ui/views/connection.js +96 -0
  188. package/ui/views/daemons.js +129 -0
  189. package/ui/views/feed.js +194 -0
  190. package/ui/views/memory.js +263 -0
  191. package/ui/views/models.js +146 -0
  192. package/ui/views/sessions.js +314 -0
  193. package/ui/views/settings.js +142 -0
  194. package/ui/views/tasks.js +415 -0
  195. package/utils/context.js +49 -0
  196. package/utils/id.js +16 -0
  197. package/utils/models.js +88 -0
  198. package/utils/paths.js +213 -0
  199. package/utils/settings.js +172 -0
@@ -0,0 +1,263 @@
1
+ 'use strict';
2
+ window.Veil = window.Veil || {};
3
+ window.Veil.views = window.Veil.views || {};
4
+
5
+ window.Veil.views.memory = {
6
+ _tab: 'agent',
7
+ _agent: null,
8
+ _file: null,
9
+ _content: '',
10
+
11
+ render() {
12
+ return `
13
+ <div style="display:flex;height:100%;">
14
+ <!-- Left: agent/file selector -->
15
+ <div style="width:260px;flex-shrink:0;border-right:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden;">
16
+ <!-- Tab strip -->
17
+ <div style="display:flex;border-bottom:1px solid var(--border);">
18
+ <button id="mem-tab-agent" class="btn btn-secondary" style="flex:1;border-radius:0;border:none;border-bottom:2px solid ${this._tab==='agent'?'var(--accent)':'transparent'};padding:9px;font-size:13px;">Agent</button>
19
+ <button id="mem-tab-global" class="btn btn-secondary" style="flex:1;border-radius:0;border:none;border-bottom:2px solid ${this._tab==='global'?'var(--accent)':'transparent'};padding:9px;font-size:13px;">Global</button>
20
+ </div>
21
+ <!-- Agent selector (agent tab only) -->
22
+ <div id="mem-agent-sel-wrap" style="${this._tab==='agent'?'':'display:none;'}padding:10px 12px;border-bottom:1px solid var(--border);">
23
+ <select id="mem-agent-sel" class="input" style="padding:5px 10px;font-size:13px;cursor:pointer;"></select>
24
+ </div>
25
+ <!-- File list -->
26
+ <div id="mem-file-list" style="flex:1;overflow-y:auto;">
27
+ <div style="padding:16px;text-align:center;color:var(--text3);font-size:13px;">${window.Veil.utils.spinner()}</div>
28
+ </div>
29
+ <!-- New file button -->
30
+ <div style="padding:10px 12px;border-top:1px solid var(--border);">
31
+ <button id="btn-mem-newfile" class="btn btn-secondary" style="width:100%;justify-content:center;">+ New File</button>
32
+ </div>
33
+ </div>
34
+ <!-- Right: editor -->
35
+ <div id="mem-editor-panel" style="flex:1;display:flex;flex-direction:column;overflow:hidden;">
36
+ <div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3);font-size:13.5px;">
37
+ Select a file to edit
38
+ </div>
39
+ </div>
40
+ </div>`;
41
+ },
42
+
43
+ async mount() {
44
+ await this._populateAgentSel();
45
+ if (this._tab === 'agent') await this._loadFiles();
46
+
47
+ document.getElementById('mem-tab-agent').addEventListener('click', async () => {
48
+ this._tab = 'agent'; this._file = null; this._content = '';
49
+ this._updateTabs();
50
+ document.getElementById('mem-agent-sel-wrap').style.display = '';
51
+ await this._loadFiles();
52
+ this._showEditorPlaceholder();
53
+ });
54
+ document.getElementById('mem-tab-global').addEventListener('click', async () => {
55
+ this._tab = 'global'; this._file = null; this._content = '';
56
+ this._updateTabs();
57
+ document.getElementById('mem-agent-sel-wrap').style.display = 'none';
58
+ await this._loadFiles();
59
+ this._showEditorPlaceholder();
60
+ });
61
+ document.getElementById('mem-agent-sel').addEventListener('change', async (e) => {
62
+ this._agent = e.target.value;
63
+ this._file = null;
64
+ await this._loadFiles();
65
+ this._showEditorPlaceholder();
66
+ });
67
+ document.getElementById('btn-mem-newfile').addEventListener('click', () => this._showNewFileModal());
68
+ },
69
+
70
+ unmount() {},
71
+
72
+ _updateTabs() {
73
+ const agentBtn = document.getElementById('mem-tab-agent');
74
+ const globalBtn = document.getElementById('mem-tab-global');
75
+ if (agentBtn) agentBtn.style.borderBottomColor = this._tab === 'agent' ? 'var(--accent)' : 'transparent';
76
+ if (globalBtn) globalBtn.style.borderBottomColor = this._tab === 'global' ? 'var(--accent)' : 'transparent';
77
+ },
78
+
79
+ async _populateAgentSel() {
80
+ const sel = document.getElementById('mem-agent-sel');
81
+ if (!sel) return;
82
+ try {
83
+ const data = await window.Veil.state.api.listAgents();
84
+ const preferred = window.Veil.state.currentAgent || this._agent;
85
+ sel.innerHTML = (data.agents || []).map(a =>
86
+ `<option value="${window.Veil.utils.esc(a.name)}" ${a.name === preferred ? 'selected' : ''}>${window.Veil.utils.esc(a.name)}</option>`
87
+ ).join('') || '<option value="">No agents</option>';
88
+ this._agent = sel.value;
89
+ } catch {}
90
+ },
91
+
92
+ async _loadFiles() {
93
+ const el = document.getElementById('mem-file-list');
94
+ if (!el) return;
95
+ el.innerHTML = `<div style="padding:14px;text-align:center;color:var(--text3);">${window.Veil.utils.spinner()}</div>`;
96
+ try {
97
+ let files = [];
98
+ if (this._tab === 'agent') {
99
+ if (!this._agent) { el.innerHTML = `<div style="padding:14px;color:var(--text3);font-size:13px;">Select an agent</div>`; return; }
100
+ const data = await window.Veil.state.api.getAgentMemory(this._agent);
101
+ files = data.files || [];
102
+ } else {
103
+ const data = await window.Veil.state.api.listMemory();
104
+ files = data.files || [];
105
+ }
106
+ if (files.length === 0) {
107
+ el.innerHTML = `<div style="padding:16px;text-align:center;color:var(--text3);font-size:13px;">No memory files.<br>Use <b>+ New File</b> to create one.</div>`;
108
+ return;
109
+ }
110
+ const U = window.Veil.utils;
111
+ el.innerHTML = files.map(f => `
112
+ <div class="list-item ${this._file === f.name ? 'selected' : ''}" data-mem-file="${U.esc(f.name)}">
113
+ <div style="font-size:13.5px;color:var(--text);font-weight:500;">${U.esc(f.name)}</div>
114
+ <div style="font-size:12px;color:var(--text3);margin-top:2px;">${Math.ceil((f.size || 0) / 1024 * 10) / 10} KB · ${U.relTime(f.modified)}</div>
115
+ </div>`).join('');
116
+ el.querySelectorAll('[data-mem-file]').forEach(row => {
117
+ row.addEventListener('click', () => {
118
+ const name = row.getAttribute('data-mem-file');
119
+ el.querySelectorAll('.list-item').forEach(r => r.classList.remove('selected'));
120
+ row.classList.add('selected');
121
+ this._file = name;
122
+ this._openFile(name);
123
+ });
124
+ });
125
+ } catch (err) {
126
+ if (el) el.innerHTML = `<div style="padding:14px;color:var(--red);font-size:13px;">${window.Veil.utils.esc(err.message)}</div>`;
127
+ }
128
+ },
129
+
130
+ async _openFile(name) {
131
+ const el = document.getElementById('mem-editor-panel');
132
+ if (!el) return;
133
+ el.innerHTML = `<div style="padding:20px;color:var(--text3);">${window.Veil.utils.spinner()} Loading…</div>`;
134
+ try {
135
+ let content = '';
136
+ if (this._tab === 'agent') {
137
+ const data = await window.Veil.state.api.getAgentMemoryFile(this._agent, name);
138
+ content = data.content || '';
139
+ } else {
140
+ const data = await window.Veil.state.api.getMemoryFile(name);
141
+ content = data.content || '';
142
+ }
143
+ this._content = content;
144
+ this._renderEditor(name, content);
145
+ } catch (err) {
146
+ if (el) el.innerHTML = `<div style="padding:14px;color:var(--red);font-size:13px;">${window.Veil.utils.esc(err.message)}</div>`;
147
+ }
148
+ },
149
+
150
+ _renderEditor(name, content) {
151
+ const el = document.getElementById('mem-editor-panel');
152
+ if (!el) return;
153
+ const U = window.Veil.utils;
154
+ el.innerHTML = `
155
+ <div style="padding:12px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px;">
156
+ <span style="font-weight:600;font-size:14px;color:var(--text);">${U.esc(name)}</span>
157
+ <span style="font-size:12px;color:var(--text3);">${this._tab === 'agent' ? U.esc(this._agent) : 'global'}</span>
158
+ <div style="margin-left:auto;display:flex;gap:8px;">
159
+ <button class="btn btn-success btn-sm" id="btn-mem-save">Save</button>
160
+ <button class="btn btn-danger btn-sm" id="btn-mem-delete">Delete</button>
161
+ </div>
162
+ </div>
163
+ <div style="flex:1;overflow:hidden;display:flex;flex-direction:column;">
164
+ <textarea id="mem-editor-area" class="input mono" style="flex:1;border:none;border-radius:0;resize:none;padding:14px 16px;font-size:13px;line-height:1.6;background:var(--bg);"></textarea>
165
+ </div>
166
+ <div style="padding:8px 16px;border-top:1px solid var(--border);font-size:12px;color:var(--text3);display:flex;justify-content:space-between;">
167
+ <span id="mem-save-status"></span>
168
+ <span>Markdown file · changes take effect on next session</span>
169
+ </div>`;
170
+
171
+ document.getElementById('btn-mem-save').addEventListener('click', async () => {
172
+ const textarea = document.getElementById('mem-editor-area');
173
+ if (!textarea) return;
174
+ const newContent = textarea.value;
175
+ const statusEl = document.getElementById('mem-save-status');
176
+ const btn = document.getElementById('btn-mem-save');
177
+ btn.disabled = true; btn.innerHTML = U.spinner() + ' Saving…';
178
+ try {
179
+ if (this._tab === 'agent') {
180
+ await window.Veil.state.api.putAgentMemoryFile(this._agent, name, newContent);
181
+ } else {
182
+ await window.Veil.state.api.putMemoryFile(name, newContent);
183
+ }
184
+ if (statusEl) { statusEl.textContent = 'Saved ✓'; statusEl.style.color = 'var(--green)'; }
185
+ window.Veil.utils.toast('Memory file saved', 'success');
186
+ this._content = newContent;
187
+ btn.disabled = false; btn.textContent = 'Save';
188
+ this._loadFiles();
189
+ } catch (err) {
190
+ btn.disabled = false; btn.textContent = 'Save';
191
+ if (statusEl) { statusEl.textContent = 'Error: ' + err.message; statusEl.style.color = 'var(--red)'; }
192
+ window.Veil.utils.toast('Save failed: ' + err.message, 'error');
193
+ }
194
+ });
195
+
196
+ document.getElementById('btn-mem-delete').addEventListener('click', async () => {
197
+ if (!confirm(`Delete "${name}"?`)) return;
198
+ try {
199
+ if (this._tab === 'agent') {
200
+ await window.Veil.state.api.deleteAgentMemoryFile(this._agent, name);
201
+ } else {
202
+ await window.Veil.state.api.deleteMemoryFile(name);
203
+ }
204
+ window.Veil.utils.toast('File deleted', 'success');
205
+ this._file = null;
206
+ this._showEditorPlaceholder();
207
+ await this._loadFiles();
208
+ } catch (err) { window.Veil.utils.toast('Delete failed: ' + err.message, 'error'); }
209
+ });
210
+ },
211
+
212
+ _showEditorPlaceholder() {
213
+ const el = document.getElementById('mem-editor-panel');
214
+ if (el) el.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3);font-size:13.5px;">Select a memory file to edit</div>`;
215
+ },
216
+
217
+ _showNewFileModal() {
218
+ window.Veil.modal.show(`
219
+ <div>
220
+ <h3 style="font-size:1rem;font-weight:700;color:var(--text);margin-bottom:16px;">New Memory File</h3>
221
+ <div id="nm-err" style="display:none;background:var(--red-dim);color:var(--red);border:1px solid var(--surface2);border-radius:6px;padding:8px 12px;margin-bottom:12px;font-size:13px;"></div>
222
+ <div style="margin-bottom:12px;">
223
+ <label style="display:block;font-size:12.5px;color:var(--text2);margin-bottom:5px;">Filename <span style="color:var(--text3);">(e.g. MEMORY.md)</span></label>
224
+ <input id="nm-filename" class="input" placeholder="MEMORY.md" />
225
+ </div>
226
+ <div style="margin-bottom:20px;">
227
+ <label style="display:block;font-size:12.5px;color:var(--text2);margin-bottom:5px;">Initial content</label>
228
+ <textarea id="nm-content" class="input mono" rows="6" placeholder="## Notes\n\n" style="resize:vertical;"></textarea>
229
+ </div>
230
+ <div style="display:flex;gap:10px;justify-content:flex-end;">
231
+ <button class="btn btn-secondary" onclick="window.Veil.modal.hide()">Cancel</button>
232
+ <button class="btn btn-primary" id="nm-submit">Create</button>
233
+ </div>
234
+ </div>`);
235
+
236
+ document.getElementById('nm-submit').addEventListener('click', async () => {
237
+ const filename = document.getElementById('nm-filename').value.trim();
238
+ const content = document.getElementById('nm-content').value;
239
+ const errEl = document.getElementById('nm-err');
240
+ if (!filename) { errEl.textContent = 'Filename is required'; errEl.style.display = 'block'; return; }
241
+ if (!filename.match(/^[a-zA-Z0-9_-]+\.md$/)) {
242
+ errEl.textContent = 'Filename must match [a-z0-9A-Z_-]+.md'; errEl.style.display = 'block'; return;
243
+ }
244
+ const btn = document.getElementById('nm-submit');
245
+ btn.disabled = true;
246
+ try {
247
+ if (this._tab === 'agent' && this._agent) {
248
+ await window.Veil.state.api.putAgentMemoryFile(this._agent, filename, content);
249
+ } else {
250
+ await window.Veil.state.api.putMemoryFile(filename, content);
251
+ }
252
+ window.Veil.modal.hide();
253
+ window.Veil.utils.toast('File created', 'success');
254
+ this._file = filename;
255
+ await this._loadFiles();
256
+ this._openFile(filename);
257
+ } catch (err) {
258
+ btn.disabled = false;
259
+ errEl.textContent = err.message; errEl.style.display = 'block';
260
+ }
261
+ });
262
+ },
263
+ };
@@ -0,0 +1,146 @@
1
+ 'use strict';
2
+ window.Veil = window.Veil || {};
3
+ window.Veil.views = window.Veil.views || {};
4
+
5
+ window.Veil.views.models = {
6
+ _models: [],
7
+ _filter: '',
8
+
9
+ render() {
10
+ return `
11
+ <div style="padding:20px;max-width:1100px;">
12
+ <div style="display:flex;align-items:center;gap:12px;margin-bottom:20px;flex-wrap:wrap;">
13
+ <h1 style="font-size:16px;font-weight:700;color:var(--text);flex:none;">Models</h1>
14
+ <input id="models-search" class="input" style="flex:1;min-width:200px;max-width:360px;padding:6px 12px;" placeholder="Search by name or provider…" value="${window.Veil.utils.esc(this._filter)}" />
15
+ <button id="btn-refresh-models" class="btn btn-secondary btn-sm" style="flex:none;">↻ Refresh from OpenRouter</button>
16
+ <span id="models-count" style="font-size:12.5px;color:var(--text3);flex:none;"></span>
17
+ </div>
18
+ <div id="models-table-wrap">
19
+ <div style="text-align:center;padding:40px;color:var(--text3);">${window.Veil.utils.spinner()}</div>
20
+ </div>
21
+ </div>`;
22
+ },
23
+
24
+ async mount() {
25
+ document.getElementById('models-search').addEventListener('input', (e) => {
26
+ this._filter = e.target.value;
27
+ this._renderTable();
28
+ });
29
+ document.getElementById('btn-refresh-models').addEventListener('click', () => this._refresh());
30
+ await this._load();
31
+ },
32
+
33
+ unmount() {},
34
+
35
+ async _load() {
36
+ const wrap = document.getElementById('models-table-wrap');
37
+ if (!wrap) return;
38
+ try {
39
+ const data = await window.Veil.state.api.listModels();
40
+ this._models = data.models || [];
41
+ this._renderTable();
42
+ const updated = data.updated_at ? `Updated: ${window.Veil.utils.relTime(data.updated_at)}` : '';
43
+ const cnt = document.getElementById('models-count');
44
+ if (cnt) cnt.textContent = `${this._models.length} models · ${updated}`;
45
+ } catch (err) {
46
+ if (wrap) wrap.innerHTML = `<div style="color:var(--red);font-size:13px;">${window.Veil.utils.esc(err.message)}</div>`;
47
+ }
48
+ },
49
+
50
+ _renderTable() {
51
+ const wrap = document.getElementById('models-table-wrap');
52
+ if (!wrap) return;
53
+ const U = window.Veil.utils;
54
+ const q = (this._filter || '').toLowerCase();
55
+ const models = q
56
+ ? this._models.filter(m => m.id.toLowerCase().includes(q) || (m.name||'').toLowerCase().includes(q))
57
+ : this._models;
58
+
59
+ if (models.length === 0) {
60
+ wrap.innerHTML = `<div style="text-align:center;padding:30px;color:var(--text3);font-size:13.5px;">${q ? 'No models match "' + U.esc(q) + '"' : 'No models loaded'}</div>`;
61
+ return;
62
+ }
63
+
64
+ const cnt = document.getElementById('models-count');
65
+ if (cnt && q) cnt.textContent = `${models.length} / ${this._models.length} models shown`;
66
+
67
+ wrap.innerHTML = `
68
+ <div style="overflow-x:auto;">
69
+ <table style="width:100%;border-collapse:collapse;font-size:13px;">
70
+ <thead>
71
+ <tr style="border-bottom:1px solid var(--border);">
72
+ <th style="text-align:left;padding:8px 12px;color:var(--text3);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:0.05em;">Model ID</th>
73
+ <th style="text-align:left;padding:8px 12px;color:var(--text3);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:0.05em;">Name</th>
74
+ <th style="text-align:right;padding:8px 12px;color:var(--text3);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:0.05em;">Context</th>
75
+ <th style="text-align:right;padding:8px 12px;color:var(--text3);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:0.05em;">Input $/M</th>
76
+ <th style="text-align:right;padding:8px 12px;color:var(--text3);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:0.05em;">Output $/M</th>
77
+ <th style="text-align:left;padding:8px 12px;color:var(--text3);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:0.05em;">Modalities</th>
78
+ </tr>
79
+ </thead>
80
+ <tbody>
81
+ ${models.map(m => `
82
+ <tr class="model-row" data-model-id="${U.esc(m.id)}" style="border-bottom:1px solid var(--border);cursor:pointer;transition:background 0.1s;">
83
+ <td style="padding:8px 12px;font-family:monospace;font-size:12.5px;color:var(--accent);">${U.esc(m.id)}</td>
84
+ <td style="padding:8px 12px;color:var(--text);max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${U.esc(m.name || '')}</td>
85
+ <td style="padding:8px 12px;text-align:right;color:var(--text2);">${m.context_length ? (m.context_length/1000).toFixed(0) + 'k' : '—'}</td>
86
+ <td style="padding:8px 12px;text-align:right;color:var(--text2);">${m.pricing?.prompt != null ? '$' + (m.pricing.prompt * 1e6).toFixed(2) : '—'}</td>
87
+ <td style="padding:8px 12px;text-align:right;color:var(--text2);">${m.pricing?.completion != null ? '$' + (m.pricing.completion * 1e6).toFixed(2) : '—'}</td>
88
+ <td style="padding:8px 12px;color:var(--text3);font-size:12px;">${(m.input_modalities||[]).join('+') || '—'}</td>
89
+ </tr>`).join('')}
90
+ </tbody>
91
+ </table>
92
+ </div>`;
93
+
94
+ wrap.querySelectorAll('.model-row').forEach(row => {
95
+ row.addEventListener('mouseenter', () => row.style.background = 'var(--surface2)');
96
+ row.addEventListener('mouseleave', () => row.style.background = '');
97
+ row.addEventListener('click', () => {
98
+ const id = row.getAttribute('data-model-id');
99
+ const m = this._models.find(x => x.id === id);
100
+ if (m) this._showDetail(m);
101
+ });
102
+ });
103
+ },
104
+
105
+ _showDetail(m) {
106
+ const U = window.Veil.utils;
107
+ window.Veil.modal.show(`
108
+ <div>
109
+ <h3 style="font-size:15px;font-weight:700;color:var(--text);margin-bottom:4px;">${U.esc(m.name || m.id)}</h3>
110
+ <div style="display:flex;align-items:center;gap:6px;">
111
+ <code style="font-size:12.5px;color:var(--accent);">${U.esc(m.id)}</code>
112
+ <button onclick="navigator.clipboard.writeText('${U.esc(m.id)}');this.textContent='✓';setTimeout(()=>this.textContent='⎘',1000)" style="background:none;border:none;color:white;cursor:pointer;font-size:14px;padding:2px 4px;" title="Copy model ID">⎘</button>
113
+ </div>
114
+ <p style="font-size:13px;color:var(--text2);margin:8px 0 12px 0;">${U.esc(m.description || '(no description)')}</p>
115
+ <hr class="divider" style="margin:16px 0;">
116
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:16px;">
117
+ <div><div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px;">Context Length</div><div style="color:var(--text);">${m.context_length ? m.context_length.toLocaleString() + ' tokens' : '—'}</div></div>
118
+ <div><div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px;">Max Completion</div><div style="color:var(--text);">${m.max_completion_tokens ? m.max_completion_tokens.toLocaleString() + ' tokens' : '—'}</div></div>
119
+ <div><div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px;">Input ($/M tokens)</div><div style="color:var(--text);">${m.pricing?.prompt != null ? '$' + (m.pricing.prompt * 1e6).toFixed(4) : '—'}</div></div>
120
+ <div><div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px;">Output ($/M tokens)</div><div style="color:var(--text);">${m.pricing?.completion != null ? '$' + (m.pricing.completion * 1e6).toFixed(4) : '—'}</div></div>
121
+ <div><div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px;">Cache Read ($/M)</div><div style="color:var(--text);">${m.pricing?.cache_read != null ? '$' + (m.pricing.cache_read * 1e6).toFixed(4) : '—'}</div></div>
122
+ <div><div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px;">Cache Write ($/M)</div><div style="color:var(--text);">${m.pricing?.cache_write != null ? '$' + (m.pricing.cache_write * 1e6).toFixed(4) : '—'}</div></div>
123
+ <div><div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px;">Input Modalities</div><div style="color:var(--text);">${(m.input_modalities||[]).join(', ') || '—'}</div></div>
124
+ <div><div style="font-size:11px;color:var(--text3);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:3px;">Output Modalities</div><div style="color:var(--text);">${(m.output_modalities||[]).join(', ') || '—'}</div></div>
125
+ </div>
126
+ <div style="text-align:right;"><button class="btn btn-secondary" onclick="window.Veil.modal.hide()">Close</button></div>
127
+ </div>`);
128
+ },
129
+
130
+ async _refresh() {
131
+ const btn = document.getElementById('btn-refresh-models');
132
+ if (btn) { btn.disabled = true; btn.innerHTML = window.Veil.utils.spinner() + ' Refreshing…'; }
133
+ try {
134
+ const data = await window.Veil.state.api.refreshModels();
135
+ this._models = data.models || [];
136
+ this._renderTable();
137
+ window.Veil.utils.toast(`Models refreshed: ${this._models.length} loaded`, 'success');
138
+ const cnt = document.getElementById('models-count');
139
+ if (cnt) cnt.textContent = `${this._models.length} models · Updated just now`;
140
+ } catch (err) {
141
+ window.Veil.utils.toast('Refresh failed: ' + err.message, 'error');
142
+ } finally {
143
+ if (btn) { btn.disabled = false; btn.textContent = '↻ Refresh from OpenRouter'; }
144
+ }
145
+ },
146
+ };