veryfront 0.0.82 → 0.0.84

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 (126) hide show
  1. package/README.md +18 -17
  2. package/esm/deno.js +1 -1
  3. package/esm/proxy/cache/index.d.ts +41 -0
  4. package/esm/proxy/cache/index.d.ts.map +1 -0
  5. package/esm/proxy/cache/index.js +75 -0
  6. package/esm/proxy/cache/memory-cache.d.ts +18 -0
  7. package/esm/proxy/cache/memory-cache.d.ts.map +1 -0
  8. package/esm/proxy/cache/memory-cache.js +100 -0
  9. package/esm/proxy/cache/redis-cache.d.ts +27 -0
  10. package/esm/proxy/cache/redis-cache.d.ts.map +1 -0
  11. package/esm/proxy/cache/redis-cache.js +183 -0
  12. package/esm/proxy/cache/resilient-cache.d.ts +44 -0
  13. package/esm/proxy/cache/resilient-cache.d.ts.map +1 -0
  14. package/esm/proxy/cache/resilient-cache.js +178 -0
  15. package/esm/proxy/cache/types.d.ts +65 -0
  16. package/esm/proxy/cache/types.d.ts.map +1 -0
  17. package/esm/proxy/cache/types.js +7 -0
  18. package/esm/proxy/handler.d.ts +81 -0
  19. package/esm/proxy/handler.d.ts.map +1 -0
  20. package/esm/proxy/handler.js +417 -0
  21. package/esm/proxy/logger.d.ts +29 -0
  22. package/esm/proxy/logger.d.ts.map +1 -0
  23. package/esm/proxy/logger.js +258 -0
  24. package/esm/proxy/oauth-client.d.ts +15 -0
  25. package/esm/proxy/oauth-client.d.ts.map +1 -0
  26. package/esm/proxy/oauth-client.js +52 -0
  27. package/esm/proxy/token-manager.d.ts +59 -0
  28. package/esm/proxy/token-manager.d.ts.map +1 -0
  29. package/esm/proxy/token-manager.js +125 -0
  30. package/esm/proxy/tracing.d.ts +39 -0
  31. package/esm/proxy/tracing.d.ts.map +1 -0
  32. package/esm/proxy/tracing.js +194 -0
  33. package/esm/src/cache/backend.d.ts +2 -0
  34. package/esm/src/cache/backend.d.ts.map +1 -1
  35. package/esm/src/cache/backend.js +2 -0
  36. package/esm/src/cache/cache-key-builder.d.ts +0 -4
  37. package/esm/src/cache/cache-key-builder.d.ts.map +1 -1
  38. package/esm/src/cache/cache-key-builder.js +0 -6
  39. package/esm/src/cache/multi-tier.d.ts +0 -29
  40. package/esm/src/cache/multi-tier.d.ts.map +1 -1
  41. package/esm/src/cache/multi-tier.js +0 -26
  42. package/esm/src/cli/app/actions.d.ts +26 -0
  43. package/esm/src/cli/app/actions.d.ts.map +1 -0
  44. package/esm/src/cli/app/actions.js +152 -0
  45. package/esm/src/cli/app/components/inline-input.d.ts +35 -0
  46. package/esm/src/cli/app/components/inline-input.d.ts.map +1 -0
  47. package/esm/src/cli/app/components/inline-input.js +220 -0
  48. package/esm/src/cli/app/components/list-select.d.ts +69 -0
  49. package/esm/src/cli/app/components/list-select.d.ts.map +1 -0
  50. package/esm/src/cli/app/components/list-select.js +137 -0
  51. package/esm/src/cli/app/index.d.ts +45 -0
  52. package/esm/src/cli/app/index.d.ts.map +1 -0
  53. package/esm/src/cli/app/index.js +1252 -0
  54. package/esm/src/cli/app/state.d.ts +122 -0
  55. package/esm/src/cli/app/state.d.ts.map +1 -0
  56. package/esm/src/cli/app/state.js +232 -0
  57. package/esm/src/cli/app/views/dashboard.d.ts +19 -0
  58. package/esm/src/cli/app/views/dashboard.d.ts.map +1 -0
  59. package/esm/src/cli/app/views/dashboard.js +178 -0
  60. package/esm/src/cli/commands/dev.js +2 -2
  61. package/esm/src/cli/commands/new.js +1 -1
  62. package/esm/src/cli/index/command-router.d.ts.map +1 -1
  63. package/esm/src/cli/index/command-router.js +9 -39
  64. package/esm/src/cli/index/start-handler.d.ts +3 -0
  65. package/esm/src/cli/index/start-handler.d.ts.map +1 -0
  66. package/esm/src/cli/index/start-handler.js +145 -0
  67. package/esm/src/cli/mcp/index.d.ts +11 -0
  68. package/esm/src/cli/mcp/index.d.ts.map +1 -0
  69. package/esm/src/cli/mcp/index.js +10 -0
  70. package/esm/src/cli/ui/tui.js +1 -1
  71. package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts +2 -0
  72. package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts.map +1 -1
  73. package/esm/src/middleware/builtin/security/redis-rate-limit.js +23 -9
  74. package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.d.ts +10 -0
  75. package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.d.ts.map +1 -1
  76. package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.js +30 -42
  77. package/esm/src/modules/react-loader/ssr-module-loader/loader.d.ts.map +1 -1
  78. package/esm/src/modules/react-loader/ssr-module-loader/loader.js +34 -13
  79. package/esm/src/platform/adapters/fs/cache/file-cache.d.ts.map +1 -1
  80. package/esm/src/platform/adapters/fs/cache/file-cache.js +9 -3
  81. package/esm/src/server/context/cache-invalidation.d.ts.map +1 -1
  82. package/esm/src/server/context/cache-invalidation.js +4 -0
  83. package/esm/src/server/handlers/dev/dashboard/api.js +4 -0
  84. package/esm/src/server/handlers/dev/projects/ui-handler.d.ts.map +1 -1
  85. package/esm/src/server/handlers/dev/projects/ui-handler.js +6 -0
  86. package/esm/src/transforms/esm/http-cache.d.ts.map +1 -1
  87. package/esm/src/transforms/esm/http-cache.js +139 -64
  88. package/esm/src/utils/index.d.ts +1 -1
  89. package/esm/src/utils/index.d.ts.map +1 -1
  90. package/esm/src/utils/index.js +1 -1
  91. package/package.json +2 -1
  92. package/src/deno.js +1 -1
  93. package/src/proxy/cache/index.ts +93 -0
  94. package/src/proxy/cache/memory-cache.ts +120 -0
  95. package/src/proxy/cache/redis-cache.ts +203 -0
  96. package/src/proxy/cache/resilient-cache.ts +205 -0
  97. package/src/proxy/cache/types.ts +72 -0
  98. package/src/proxy/handler.ts +593 -0
  99. package/src/proxy/logger.ts +329 -0
  100. package/src/proxy/oauth-client.ts +91 -0
  101. package/src/proxy/token-manager.ts +174 -0
  102. package/src/proxy/tracing.ts +237 -0
  103. package/src/src/cache/backend.ts +3 -0
  104. package/src/src/cache/cache-key-builder.ts +0 -9
  105. package/src/src/cache/multi-tier.ts +0 -41
  106. package/src/src/cli/app/actions.ts +190 -0
  107. package/src/src/cli/app/components/inline-input.ts +255 -0
  108. package/src/src/cli/app/components/list-select.ts +215 -0
  109. package/src/src/cli/app/index.ts +1471 -0
  110. package/src/src/cli/app/state.ts +385 -0
  111. package/src/src/cli/app/views/dashboard.ts +212 -0
  112. package/src/src/cli/commands/dev.ts +2 -2
  113. package/src/src/cli/commands/new.ts +1 -1
  114. package/src/src/cli/index/command-router.ts +9 -40
  115. package/src/src/cli/index/start-handler.ts +195 -0
  116. package/src/src/cli/mcp/index.ts +11 -0
  117. package/src/src/cli/ui/tui.ts +1 -1
  118. package/src/src/middleware/builtin/security/redis-rate-limit.ts +24 -11
  119. package/src/src/modules/react-loader/ssr-module-loader/cache/redis.ts +36 -50
  120. package/src/src/modules/react-loader/ssr-module-loader/loader.ts +38 -14
  121. package/src/src/platform/adapters/fs/cache/file-cache.ts +9 -3
  122. package/src/src/server/context/cache-invalidation.ts +4 -0
  123. package/src/src/server/handlers/dev/dashboard/api.ts +2 -0
  124. package/src/src/server/handlers/dev/projects/ui-handler.ts +6 -0
  125. package/src/src/transforms/esm/http-cache.ts +148 -73
  126. package/src/src/utils/index.ts +0 -1
@@ -0,0 +1,385 @@
1
+ import type { ListItem, ListSelectState } from "./components/list-select.js";
2
+ import { createListState } from "./components/list-select.js";
3
+ import { getRuntimeEnv, type RuntimeEnv } from "../../config/runtime-env.js";
4
+ import { cwd } from "../../platform/compat/process.js";
5
+
6
+ export type AppView =
7
+ | "dashboard"
8
+ | "project-detail"
9
+ | "new-project"
10
+ | "templates"
11
+ | "examples"
12
+ | "auth"
13
+ | "help";
14
+
15
+ export interface ProjectInfo {
16
+ slug: string;
17
+ path: string;
18
+ type: "local" | "example" | "template";
19
+ }
20
+
21
+ export interface ServerStatus {
22
+ running: boolean;
23
+ url: string;
24
+ port: number;
25
+ errors: number;
26
+ warnings: number;
27
+ }
28
+
29
+ export interface MCPStatus {
30
+ enabled: boolean;
31
+ transport: "stdio" | "http" | null;
32
+ connected: boolean;
33
+ clientName?: string;
34
+ httpPort?: number;
35
+ }
36
+
37
+ export interface RemoteState {
38
+ user: { email: string; name?: string } | null;
39
+ projects: Array<{ id: string; name: string; slug: string }>;
40
+ /** Currently focused index in remote projects list */
41
+ focusedIndex: number;
42
+ /** Scroll offset for remote projects list */
43
+ scrollOffset: number;
44
+ }
45
+
46
+ export interface InputState {
47
+ active: boolean;
48
+ prompt: string;
49
+ value: string;
50
+ cursorPos: number;
51
+ onSubmit: ((value: string) => void) | null;
52
+ onCancel: (() => void) | null;
53
+ }
54
+
55
+ export interface LogMeta {
56
+ method?: string;
57
+ path?: string;
58
+ status?: number;
59
+ durationMs?: number;
60
+ project?: string;
61
+ env?: string;
62
+ releaseId?: string;
63
+ }
64
+
65
+ export interface LogEntry {
66
+ time: Date;
67
+ level: "info" | "warn" | "error" | "debug";
68
+ message: string;
69
+ meta?: LogMeta;
70
+ }
71
+
72
+ export interface AppState {
73
+ view: AppView;
74
+ previousView: AppView | null;
75
+
76
+ server: ServerStatus;
77
+ mcp: MCPStatus;
78
+ remote: RemoteState;
79
+
80
+ projects: ListSelectState<ProjectInfo>;
81
+ examples: ListSelectState<ProjectInfo>;
82
+ templates: ListSelectState<ProjectInfo>;
83
+
84
+ activeList: "projects" | "examples" | "templates" | "remoteProjects";
85
+ selectedProject: ProjectInfo | null;
86
+
87
+ wizard: {
88
+ step: number;
89
+ startType: "scratch" | "template" | "example" | null;
90
+ selectedTemplate: string | null;
91
+ integrations: string[];
92
+ projectName: string;
93
+ };
94
+
95
+ input: InputState;
96
+
97
+ logs: LogEntry[];
98
+ maxLogs: number;
99
+ logsExpanded: boolean;
100
+ logScroll: number;
101
+
102
+ /** Auth provider selection index (0=Google, 1=GitHub, 2=Microsoft) */
103
+ authProviderIndex: number;
104
+ /** New project option index (0=template, 1=example, 2=scratch) */
105
+ newProjectIndex: number;
106
+ }
107
+
108
+ export function createInitialState(): AppState {
109
+ return {
110
+ view: "dashboard",
111
+ previousView: null,
112
+ server: {
113
+ running: false,
114
+ url: "http://veryfront.me:8080",
115
+ port: 8080,
116
+ errors: 0,
117
+ warnings: 0,
118
+ },
119
+ mcp: {
120
+ enabled: false,
121
+ transport: null,
122
+ connected: false,
123
+ },
124
+ remote: {
125
+ user: null,
126
+ projects: [],
127
+ focusedIndex: 0,
128
+ scrollOffset: 0,
129
+ },
130
+ projects: createListState([]),
131
+ examples: createListState([]),
132
+ templates: createListState([]),
133
+ activeList: "projects",
134
+ selectedProject: null,
135
+ wizard: {
136
+ step: 0,
137
+ startType: null,
138
+ selectedTemplate: null,
139
+ integrations: [],
140
+ projectName: "",
141
+ },
142
+ input: {
143
+ active: false,
144
+ prompt: "",
145
+ value: "",
146
+ cursorPos: 0,
147
+ onSubmit: null,
148
+ onCancel: null,
149
+ },
150
+ logs: [],
151
+ maxLogs: 100,
152
+ logsExpanded: false,
153
+ logScroll: 0,
154
+ authProviderIndex: 0,
155
+ newProjectIndex: 0,
156
+ };
157
+ }
158
+
159
+ export type StateUpdater = (state: AppState) => AppState;
160
+
161
+ export function setProjects(
162
+ projects: Array<{ slug: string; path: string }>,
163
+ ): StateUpdater {
164
+ return (state) => ({
165
+ ...state,
166
+ projects: createListState(
167
+ projects.map((p) => ({
168
+ id: p.slug,
169
+ label: p.slug,
170
+ meta: shortenPath(p.path),
171
+ data: { slug: p.slug, path: p.path, type: "local" },
172
+ })),
173
+ ),
174
+ });
175
+ }
176
+
177
+ export function setExamples(
178
+ examples: Array<{ slug: string; path: string; description?: string }>,
179
+ ): StateUpdater {
180
+ return (state) => ({
181
+ ...state,
182
+ examples: createListState(
183
+ examples.map((e) => ({
184
+ id: e.slug,
185
+ label: e.slug,
186
+ description: e.description,
187
+ data: { slug: e.slug, path: e.path, type: "example" },
188
+ })),
189
+ ),
190
+ });
191
+ }
192
+
193
+ export function setTemplates(
194
+ templates: Array<{ id: string; name: string; description: string }>,
195
+ ): StateUpdater {
196
+ return (state) => ({
197
+ ...state,
198
+ templates: createListState(
199
+ templates.map((t) => ({
200
+ id: t.id,
201
+ label: t.name,
202
+ description: t.description,
203
+ data: { slug: t.id, path: "", type: "template" },
204
+ })),
205
+ ),
206
+ });
207
+ }
208
+
209
+ export function updateServer(update: Partial<ServerStatus>): StateUpdater {
210
+ return (state) => ({ ...state, server: { ...state.server, ...update } });
211
+ }
212
+
213
+ export function updateMCP(update: Partial<MCPStatus>): StateUpdater {
214
+ return (state) => ({ ...state, mcp: { ...state.mcp, ...update } });
215
+ }
216
+
217
+ export function updateRemote(update: Partial<RemoteState>): StateUpdater {
218
+ return (state) => ({ ...state, remote: { ...state.remote, ...update } });
219
+ }
220
+
221
+ export function navigateTo(view: AppView): StateUpdater {
222
+ return (state) => ({ ...state, view, previousView: state.view });
223
+ }
224
+
225
+ export function goBack(): StateUpdater {
226
+ return (state) => ({
227
+ ...state,
228
+ view: state.previousView ?? "dashboard",
229
+ previousView: null,
230
+ });
231
+ }
232
+
233
+ export function setActiveList(
234
+ list: "projects" | "examples" | "templates" | "remoteProjects",
235
+ ): StateUpdater {
236
+ return (state) => ({ ...state, activeList: list });
237
+ }
238
+
239
+ export function updateActiveList(
240
+ updater: (list: ListSelectState<ProjectInfo>) => ListSelectState<ProjectInfo>,
241
+ ): StateUpdater {
242
+ return (state) => {
243
+ const key = state.activeList;
244
+ // remoteProjects is not a ListSelectState, skip update
245
+ if (key === "remoteProjects") return state;
246
+ return { ...state, [key]: updater(state[key]) };
247
+ };
248
+ }
249
+
250
+ export function selectProject(project: ProjectInfo | null): StateUpdater {
251
+ return (state) => {
252
+ if (!project) return { ...state, selectedProject: null };
253
+
254
+ return {
255
+ ...state,
256
+ selectedProject: project,
257
+ view: "project-detail",
258
+ previousView: state.view,
259
+ };
260
+ };
261
+ }
262
+
263
+ export function updateWizard(
264
+ update: Partial<AppState["wizard"]>,
265
+ ): StateUpdater {
266
+ return (state) => ({ ...state, wizard: { ...state.wizard, ...update } });
267
+ }
268
+
269
+ export function resetWizard(): StateUpdater {
270
+ return (state) => ({
271
+ ...state,
272
+ wizard: {
273
+ step: 0,
274
+ startType: null,
275
+ selectedTemplate: null,
276
+ integrations: [],
277
+ projectName: "",
278
+ },
279
+ });
280
+ }
281
+
282
+ export function startInput(
283
+ prompt: string,
284
+ onSubmit: (value: string) => void,
285
+ onCancel?: () => void,
286
+ initialValue?: string,
287
+ ): StateUpdater {
288
+ return (state) => ({
289
+ ...state,
290
+ input: {
291
+ active: true,
292
+ prompt,
293
+ value: initialValue ?? "",
294
+ cursorPos: initialValue?.length ?? 0,
295
+ onSubmit,
296
+ onCancel: onCancel ?? null,
297
+ },
298
+ });
299
+ }
300
+
301
+ export function updateInputValue(value: string, cursorPos: number): StateUpdater {
302
+ return (state) => ({
303
+ ...state,
304
+ input: { ...state.input, value, cursorPos },
305
+ });
306
+ }
307
+
308
+ export function endInput(): StateUpdater {
309
+ return (state) => ({
310
+ ...state,
311
+ input: {
312
+ active: false,
313
+ prompt: "",
314
+ value: "",
315
+ cursorPos: 0,
316
+ onSubmit: null,
317
+ onCancel: null,
318
+ },
319
+ });
320
+ }
321
+
322
+ export function addLog(level: LogEntry["level"], message: string, meta?: LogMeta): StateUpdater {
323
+ return (state) => {
324
+ const logs = [...state.logs, { time: new Date(), level, message, meta }];
325
+ if (logs.length > state.maxLogs) logs.shift();
326
+ return { ...state, logs };
327
+ };
328
+ }
329
+
330
+ export function clearLogs(): StateUpdater {
331
+ return (state) => ({ ...state, logs: [], logScroll: 0 });
332
+ }
333
+
334
+ export function toggleLogsExpanded(): StateUpdater {
335
+ return (state) => ({
336
+ ...state,
337
+ logsExpanded: !state.logsExpanded,
338
+ logScroll: 0,
339
+ });
340
+ }
341
+
342
+ export function scrollLogs(direction: "up" | "down"): StateUpdater {
343
+ return (state) => {
344
+ if (!state.logsExpanded) return state;
345
+
346
+ const maxScroll = Math.max(0, state.logs.length - 5);
347
+ let newScroll = state.logScroll;
348
+
349
+ if (direction === "up") {
350
+ newScroll = Math.min(maxScroll, state.logScroll + 1);
351
+ } else {
352
+ newScroll = Math.max(0, state.logScroll - 1);
353
+ }
354
+
355
+ return { ...state, logScroll: newScroll };
356
+ };
357
+ }
358
+
359
+ function shortenPath(path: string, env: RuntimeEnv = getRuntimeEnv()): string {
360
+ // Prefer relative path to cwd
361
+ const currentDir = cwd();
362
+ if (path.startsWith(currentDir + "/")) {
363
+ return "./" + path.slice(currentDir.length + 1);
364
+ }
365
+ if (path === currentDir) {
366
+ return "./";
367
+ }
368
+
369
+ // Fall back to ~ for home
370
+ const home = env.homeDir ?? "";
371
+ if (home && path.startsWith(home)) {
372
+ return `~${path.slice(home.length)}`;
373
+ }
374
+
375
+ return path;
376
+ }
377
+
378
+ export function getActiveSelection(
379
+ state: AppState,
380
+ ): ListItem<ProjectInfo> | undefined {
381
+ // remoteProjects is not a ListSelectState
382
+ if (state.activeList === "remoteProjects") return undefined;
383
+ const list = state[state.activeList];
384
+ return list.items[list.selectedIndex];
385
+ }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Dashboard View
3
+ *
4
+ * Main view showing server status, projects, and quick actions.
5
+ */
6
+
7
+ import { box } from "../../ui/box.js";
8
+ import { brand, dim, error, muted, success } from "../../ui/colors.js";
9
+ import { getTerminalWidth } from "../../ui/layout.js";
10
+ import { getAgentFaceWithText } from "../../ui/dot-matrix.js";
11
+ import { renderList } from "../components/list-select.js";
12
+ import type { AppState } from "../state.js";
13
+
14
+ /**
15
+ * Render the dashboard view
16
+ */
17
+ export function renderDashboard(state: AppState): string {
18
+ const termWidth = Math.min(getTerminalWidth() - 4, 80);
19
+ const maxListWidth = termWidth - 4;
20
+ const lines: string[] = [];
21
+
22
+ lines.push(renderBanner(state), "");
23
+
24
+ const hasProjects = state.projects.items.length > 0;
25
+ const hasExamples = state.examples.items.length > 0;
26
+ const hasRemoteProjects = state.remote.user && state.remote.projects.length > 0;
27
+
28
+ if (hasProjects) {
29
+ const isActive = state.activeList === "projects";
30
+ lines.push(renderSection("Local Projects", state.projects.items.length, isActive));
31
+ lines.push(
32
+ renderList(state.projects, {
33
+ maxWidth: maxListWidth,
34
+ visibleCount: 5,
35
+ showNumbers: true,
36
+ showSelection: isActive,
37
+ }),
38
+ "",
39
+ );
40
+ }
41
+
42
+ // Remote projects (when logged in)
43
+ if (hasRemoteProjects) {
44
+ const isRemoteActive = state.activeList === "remoteProjects";
45
+ const visibleCount = 5;
46
+ const start = state.remote.scrollOffset;
47
+ const end = Math.min(start + visibleCount, state.remote.projects.length);
48
+ const visibleProjects = state.remote.projects.slice(start, end);
49
+
50
+ lines.push(renderSection("Remote Projects", state.remote.projects.length, isRemoteActive));
51
+
52
+ if (start > 0) {
53
+ lines.push(` ${dim("↑ more above")}`);
54
+ }
55
+
56
+ visibleProjects.forEach((p, i) => {
57
+ const actualIndex = start + i;
58
+ const isFocused = isRemoteActive && actualIndex === state.remote.focusedIndex;
59
+ const cursor = isFocused ? brand("›") : " ";
60
+ // Show number 1-9 or letter a-z for 10+
61
+ const displayNum = actualIndex + 1;
62
+ const shortcut = displayNum <= 9
63
+ ? String(displayNum)
64
+ : String.fromCharCode(96 + displayNum - 9); // 10='a', 11='b', etc.
65
+ const num = isFocused ? brand(`[${shortcut}]`) : dim(`[${shortcut}]`);
66
+ const label = isFocused ? p.slug : dim(p.slug);
67
+ lines.push(`${cursor} ${num} ${label}`);
68
+ });
69
+
70
+ if (end < state.remote.projects.length) {
71
+ lines.push(` ${dim("↓ more below")}`);
72
+ }
73
+ lines.push("");
74
+ }
75
+
76
+ if (hasExamples) {
77
+ const isActive = state.activeList === "examples";
78
+ lines.push(renderSection("Examples", state.examples.items.length, isActive));
79
+ lines.push(
80
+ renderList(state.examples, {
81
+ maxWidth: maxListWidth,
82
+ visibleCount: 5,
83
+ showNumbers: true,
84
+ showSelection: isActive,
85
+ }),
86
+ "",
87
+ );
88
+ }
89
+
90
+ lines.push(renderHelpBar(state));
91
+
92
+ return lines.join("\n");
93
+ }
94
+
95
+ /**
96
+ * Render the banner with agent face and server info
97
+ */
98
+ function renderBanner(state: AppState): string {
99
+ const serverDot = state.server.running ? success("●") : error("●");
100
+ const mcpDot = state.mcp.enabled ? success("●") : dim("○");
101
+
102
+ const textLines: string[] = [];
103
+
104
+ textLines.push(`${serverDot} ${dim("Server running")}`);
105
+ textLines.push(` ${brand(state.server.url)}`);
106
+
107
+ if (state.mcp.enabled) {
108
+ textLines.push(`${mcpDot} ${dim("MCP")}`);
109
+ if (state.mcp.transport === "http") {
110
+ const port = state.mcp.httpPort ?? 9999;
111
+ textLines.push(` ${brand(`http://veryfront.me:${port}/mcp`)}`);
112
+ } else {
113
+ textLines.push(` ${dim("stdio")}`);
114
+ }
115
+ }
116
+
117
+ const { errors, warnings } = state.server;
118
+ if (errors > 0 || warnings > 0) {
119
+ const parts: string[] = [];
120
+ if (errors > 0) parts.push(error(`${errors} errors`));
121
+ if (warnings > 0) parts.push(muted(`${warnings} warnings`));
122
+ textLines.push(parts.join(" "));
123
+ }
124
+
125
+ return getAgentFaceWithText(textLines, {
126
+ litColor: "\x1b[38;2;252;143;93m", // Veryfront brand orange
127
+ });
128
+ }
129
+
130
+ /**
131
+ * Render a section header
132
+ */
133
+ function renderSection(title: string, count: number, isActive = true): string {
134
+ const indicator = isActive ? brand("›") : " ";
135
+ const titleText = isActive ? title : dim(title);
136
+ return ` ${indicator} ${titleText} ${dim(`(${count})`)}`;
137
+ }
138
+
139
+ /**
140
+ * Render the help bar at the bottom
141
+ */
142
+ function renderHelpBar(state: AppState): string {
143
+ const parts: string[] = [];
144
+
145
+ const hasProjects = state.projects.items.length > 0;
146
+ const hasExamples = state.examples.items.length > 0;
147
+ const hasRemoteProjects = state.remote.user && state.remote.projects.length > 0;
148
+
149
+ // Count sections for tab switching
150
+ const sectionCount = [hasProjects, hasExamples, hasRemoteProjects].filter(Boolean).length;
151
+
152
+ if (sectionCount > 1) {
153
+ parts.push(dim("tab switch"));
154
+ }
155
+
156
+ parts.push(dim("↑↓ nav"));
157
+
158
+ if (hasProjects || hasExamples || hasRemoteProjects) {
159
+ parts.push(dim("o open"), dim("s studio"), dim("i ide"));
160
+ }
161
+
162
+ if (!state.remote.user) {
163
+ parts.push(dim("a login"));
164
+ } else {
165
+ // Show context-aware actions based on active list
166
+ if (state.activeList === "projects") {
167
+ parts.push(dim("p pull"), dim("u push"));
168
+ } else if (state.activeList === "remoteProjects") {
169
+ parts.push(dim("p pull"));
170
+ }
171
+ parts.push(dim("n new"), dim("x logout"));
172
+ }
173
+
174
+ parts.push(dim("? help"), dim("q quit"));
175
+
176
+ return ` ${parts.join(" ")}`;
177
+ }
178
+
179
+ /**
180
+ * Render a boxed dashboard (alternative style)
181
+ */
182
+ export function renderDashboardBoxed(state: AppState): string {
183
+ const termWidth = Math.min(getTerminalWidth() - 4, 80);
184
+ const content = renderDashboard(state);
185
+
186
+ return box(content, {
187
+ style: "rounded",
188
+ title: "Veryfront Code",
189
+ titleColor: "\x1b[38;2;252;143;93m",
190
+ width: termWidth,
191
+ paddingX: 1,
192
+ paddingY: 0,
193
+ });
194
+ }
195
+
196
+ /**
197
+ * Render empty state when no projects found
198
+ */
199
+ export function renderEmptyState(): string {
200
+ return [
201
+ "",
202
+ ` ${dim("No projects found.")}`,
203
+ "",
204
+ ` ${dim("Get started:")}`,
205
+ ` ${brand("[n]")} Create a new project`,
206
+ ` ${brand("[t]")} Browse templates`,
207
+ "",
208
+ ` ${dim("Or run with a project directory:")}`,
209
+ ` ${muted("deno task start --project ./my-project")}`,
210
+ "",
211
+ ].join("\n");
212
+ }
@@ -195,8 +195,8 @@ export function devCommand(options: DevOptions): Promise<DevCommandResult> {
195
195
  console.log();
196
196
  console.log(
197
197
  banner({
198
- title: "Veryfront",
199
- subtitle: "is now running",
198
+ title: "Veryfront Code",
199
+ subtitle: "is running",
200
200
  info: {
201
201
  url: serverUrl,
202
202
  ...(projectSlug ? { project: projectSlug } : {}),
@@ -154,7 +154,7 @@ export async function newCommand(
154
154
  return;
155
155
  }
156
156
 
157
- const tui = createTui({ title: "Veryfront", showLogs: true });
157
+ const tui = createTui({ title: "Veryfront Code", showLogs: true });
158
158
  const restore = interceptConsole(tui);
159
159
 
160
160
  const localUrl = `http://${name}.veryfront.me:${port}`;
@@ -24,7 +24,7 @@ import { mergeCommand, parseMergeArgs } from "../commands/merge.js";
24
24
  import { deployCommand, parseDeployArgs } from "../commands/deploy.js";
25
25
  import { parseUpArgs, upCommand } from "../commands/up.js";
26
26
  import { newCommand, parseNewArgs } from "../commands/new.js";
27
- import { promptProjectName, showMainMenu } from "../commands/main.js";
27
+ import { promptProjectName } from "../commands/main.js";
28
28
  import { issuesCommand } from "../commands/issues.js";
29
29
  import { login, logout, whoami } from "../auth/index.js";
30
30
  import { COMMANDS } from "../help/command-definitions.js";
@@ -41,7 +41,7 @@ import { handleBuildCommand } from "./build-handler.js";
41
41
  import { handleDevCommand } from "./dev-handler.js";
42
42
  import { handleGenerateCommand } from "./generate-handler.js";
43
43
  import { handleStudioCommand } from "./studio-handler.js";
44
- import { createMCPServer } from "../mcp/server.js";
44
+ import { handleStartCommand } from "./start-handler.js";
45
45
  import type { ParsedArgs } from "./types.js";
46
46
  import type { InitTemplate } from "../commands/init/types.js";
47
47
  import type { IntegrationName } from "../templates/types.js";
@@ -437,11 +437,10 @@ export async function routeCommand(args: ParsedArgs): Promise<void> {
437
437
  }
438
438
 
439
439
  case "mcp": {
440
- const server = await createMCPServer({ stdio: true });
441
- await new Promise(() => {
442
- /* never resolve - stdio loop runs until stdin closes */
443
- });
444
- await server.stop();
440
+ const { createMCPServer } = await import("../mcp/server.js");
441
+ const mcpServer = await createMCPServer({ stdio: true });
442
+ await new Promise(() => {});
443
+ await mcpServer.stop();
445
444
  break;
446
445
  }
447
446
 
@@ -454,40 +453,10 @@ export async function routeCommand(args: ParsedArgs): Promise<void> {
454
453
  exitProcess(0);
455
454
  return;
456
455
 
457
- case undefined: {
458
- const action = await showMainMenu();
459
-
460
- switch (action) {
461
- case "new": {
462
- const name = await promptProjectName();
463
- if (name) {
464
- await newCommand(name, {});
465
- }
466
- break;
467
- }
468
- case "dev":
469
- await handleDevCommand(args);
470
- break;
471
- case "deploy": {
472
- const result = parseDeployArgs(args);
473
- if (result.success) {
474
- await deployCommand(result.data);
475
- }
476
- break;
477
- }
478
- case "login":
479
- await login();
480
- break;
481
- case "help":
482
- showHelp();
483
- break;
484
- case "exit":
485
- case null:
486
- exitProcess(0);
487
- break;
488
- }
456
+ case undefined:
457
+ // Default: run full TUI dashboard (like `deno task start`)
458
+ await handleStartCommand(args);
489
459
  break;
490
- }
491
460
 
492
461
  default:
493
462
  cliLogger.error(`Unknown command: ${command}\n`);