@agent-relay/dashboard 2.0.82 → 2.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 (228) hide show
  1. package/out/404.html +1 -1
  2. package/out/_next/static/chunks/1028-da5d75e35d1420f1.js +1 -0
  3. package/out/_next/static/chunks/1528-78b17000a7e10bc6.js +2 -0
  4. package/out/_next/static/chunks/1695-4a5d33ba715e09b4.js +1 -0
  5. package/out/_next/static/chunks/1705-36c2180d00a4a569.js +1 -0
  6. package/out/_next/static/chunks/1dd3208c-e1f87c7b3dc1a820.js +1 -0
  7. package/out/_next/static/chunks/3663-47290254b8f6f5dd.js +1 -0
  8. package/out/_next/static/chunks/3677-4b225baf4801d9b9.js +73 -0
  9. package/out/_next/static/chunks/5118-7e8ada2df38eef07.js +1 -0
  10. package/out/_next/static/chunks/5888-15cbe97c90ed5fae.js +1 -0
  11. package/out/_next/static/chunks/6773-a45343a98df3abb5.js +1 -0
  12. package/out/_next/static/chunks/6940-b824612b605e79b3.js +9 -0
  13. package/out/_next/static/chunks/7894-f4a15249082a680d.js +1 -0
  14. package/out/_next/static/chunks/9175-b3617c1e5cbfed0e.js +1 -0
  15. package/out/_next/static/chunks/9372-1a804b8d08c7a236.js +1 -0
  16. package/out/_next/static/chunks/{ab6c8a12-0a58072fbb505134.js → ab6c8a12-91438a812d94ecf0.js} +1 -1
  17. package/out/_next/static/chunks/app/_not-found/page-8e8842f82d204726.js +1 -0
  18. package/out/_next/static/chunks/app/about/page-b78577a7da8fa459.js +1 -0
  19. package/out/_next/static/chunks/app/app/[[...slug]]/page-3dffd65b6344f53e.js +1 -0
  20. package/out/_next/static/chunks/app/app/onboarding/page-b89be9aa6264a5e1.js +1 -0
  21. package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-fbd00893ef69e499.js +1 -0
  22. package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-de2ea13649d0b6d3.js +1 -0
  23. package/out/_next/static/chunks/app/blog/page-a08e263c57a156fa.js +1 -0
  24. package/out/_next/static/chunks/app/careers/page-02228e1d6969b232.js +1 -0
  25. package/out/_next/static/chunks/app/changelog/page-1b5c1d79efc6e53a.js +1 -0
  26. package/out/_next/static/chunks/app/cloud/link/page-99654edffffb3af2.js +1 -0
  27. package/out/_next/static/chunks/app/complete-profile/page-59d146e5ddeafc5c.js +1 -0
  28. package/out/_next/static/chunks/app/connect-repos/page-995e16a976a6632c.js +1 -0
  29. package/out/_next/static/chunks/app/contact/page-273396a5ad57bcee.js +1 -0
  30. package/out/_next/static/chunks/app/dev/cli-tools/page-a71b80dcb2d5fc8d.js +1 -0
  31. package/out/_next/static/chunks/app/dev/log-viewer/page-46a6151ae1be0796.js +1 -0
  32. package/out/_next/static/chunks/app/docs/page-7c7cb603b24b7c40.js +1 -0
  33. package/out/_next/static/chunks/app/history/page-0c5cab1dab4e8886.js +1 -0
  34. package/out/_next/static/chunks/app/layout-96d72ba8ef8a43a0.js +1 -0
  35. package/out/_next/static/chunks/app/login/page-0ccbab34213df842.js +1 -0
  36. package/out/_next/static/chunks/app/metrics/page-8616272aeab9c8b0.js +1 -0
  37. package/out/_next/static/chunks/app/page-09ce10603ad9a251.js +1 -0
  38. package/out/_next/static/chunks/app/pricing/page-91c975079120c941.js +1 -0
  39. package/out/_next/static/chunks/app/privacy/{page-c21d51ac2dee3a88.js → page-a49ab271cc686644.js} +1 -1
  40. package/out/_next/static/chunks/app/providers/{page-59114505f4353512.js → page-d775d6eb5bc29e96.js} +1 -1
  41. package/out/_next/static/chunks/app/providers/setup/[provider]/page-ec4ef3cd80de807e.js +1 -0
  42. package/out/_next/static/chunks/app/security/page-d9da9bd9191e8f95.js +1 -0
  43. package/out/_next/static/chunks/app/signup/page-930eca0bf5fd299d.js +1 -0
  44. package/out/_next/static/chunks/app/terms/page-3e4827620b98613c.js +1 -0
  45. package/out/_next/static/chunks/framework-648e1ae7da590300.js +1 -0
  46. package/out/_next/static/chunks/{main-acb1b24265295d6a.js → main-2b1990080c292d92.js} +1 -1
  47. package/out/_next/static/chunks/main-app-9f6b7ff9e754a8f5.js +1 -0
  48. package/out/_next/static/chunks/pages/_app-a077b72e02273ab1.js +1 -0
  49. package/out/_next/static/chunks/pages/_error-84001666436a04e4.js +1 -0
  50. package/out/_next/static/chunks/{webpack-dd93b81e2659669c.js → webpack-7586035f1585f2db.js} +1 -1
  51. package/out/_next/static/css/eb9fc69d1e3d2bed.css +1 -0
  52. package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_buildManifest.js +1 -1
  53. package/out/about.html +2 -2
  54. package/out/about.txt +2 -2
  55. package/out/app/onboarding.html +1 -1
  56. package/out/app/onboarding.txt +2 -2
  57. package/out/app.html +1 -1
  58. package/out/app.txt +2 -2
  59. package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +3 -3
  60. package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
  61. package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
  62. package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
  63. package/out/blog.html +2 -2
  64. package/out/blog.txt +1 -1
  65. package/out/careers.html +2 -2
  66. package/out/careers.txt +2 -2
  67. package/out/changelog.html +2 -2
  68. package/out/changelog.txt +2 -2
  69. package/out/cloud/link.html +1 -1
  70. package/out/cloud/link.txt +2 -2
  71. package/out/complete-profile.html +2 -2
  72. package/out/complete-profile.txt +2 -2
  73. package/out/connect-repos.html +1 -1
  74. package/out/connect-repos.txt +2 -2
  75. package/out/contact.html +2 -2
  76. package/out/contact.txt +2 -2
  77. package/out/dev/cli-tools.html +1 -0
  78. package/out/dev/cli-tools.txt +7 -0
  79. package/out/dev/log-viewer.html +23 -0
  80. package/out/dev/log-viewer.txt +7 -0
  81. package/out/docs.html +2 -2
  82. package/out/docs.txt +2 -2
  83. package/out/history.html +1 -1
  84. package/out/history.txt +2 -2
  85. package/out/index.html +1 -1
  86. package/out/index.txt +2 -2
  87. package/out/login.html +2 -2
  88. package/out/login.txt +2 -2
  89. package/out/metrics.html +1 -1
  90. package/out/metrics.txt +2 -2
  91. package/out/pricing.html +2 -2
  92. package/out/pricing.txt +2 -2
  93. package/out/privacy.html +2 -2
  94. package/out/privacy.txt +2 -2
  95. package/out/providers/setup/claude.html +1 -1
  96. package/out/providers/setup/claude.txt +2 -2
  97. package/out/providers/setup/codex.html +1 -1
  98. package/out/providers/setup/codex.txt +2 -2
  99. package/out/providers/setup/cursor.html +1 -1
  100. package/out/providers/setup/cursor.txt +2 -2
  101. package/out/providers.html +1 -1
  102. package/out/providers.txt +2 -2
  103. package/out/security.html +2 -2
  104. package/out/security.txt +2 -2
  105. package/out/signup.html +2 -2
  106. package/out/signup.txt +2 -2
  107. package/out/terms.html +2 -2
  108. package/out/terms.txt +2 -2
  109. package/package.json +5 -1
  110. package/src/adapters/DashboardConfigProvider.tsx +56 -0
  111. package/src/adapters/cloudFetchAdapter.ts +278 -0
  112. package/src/adapters/index.ts +3 -0
  113. package/src/adapters/types.ts +508 -0
  114. package/src/app/app/[[...slug]]/DashboardPageClient.tsx +67 -18
  115. package/src/app/app/onboarding/page.tsx +870 -170
  116. package/src/app/cloud/link/page.tsx +14 -6
  117. package/src/app/connect-repos/page.tsx +9 -3
  118. package/src/app/dev/cli-tools/page.tsx +130 -0
  119. package/src/app/dev/log-viewer/MockLogViewer.tsx +132 -0
  120. package/src/app/dev/log-viewer/fixtures.ts +110 -0
  121. package/src/app/dev/log-viewer/page.tsx +288 -0
  122. package/src/app/history/page.tsx +28 -12
  123. package/src/app/page.tsx +1 -1
  124. package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +209 -59
  125. package/src/components/AgentCard.tsx +4 -4
  126. package/src/components/AgentLogPreview.tsx +2 -38
  127. package/src/components/App.tsx +441 -2624
  128. package/src/components/CliToolHarness.test.tsx +83 -0
  129. package/src/components/CliToolHarness.tsx +292 -0
  130. package/src/components/CoordinatorPanel.tsx +13 -6
  131. package/src/components/LogViewer.tsx +2 -42
  132. package/src/components/ProviderAuthFlow.tsx +201 -81
  133. package/src/components/ProvisioningProgress.tsx +1 -1
  134. package/src/components/ReactionChips.tsx +2 -1
  135. package/src/components/SpawnModal.test.tsx +51 -18
  136. package/src/components/SpawnModal.tsx +175 -207
  137. package/src/components/TerminalProviderSetup.tsx +1 -1
  138. package/src/components/ThreadPanel.tsx +2 -0
  139. package/src/components/WorkspaceContext.tsx +7 -19
  140. package/src/components/XTermLogViewer.tsx +190 -27
  141. package/src/components/channels/ChannelMessageList.tsx +94 -4
  142. package/src/components/channels/ChannelViewV1.tsx +35 -11
  143. package/src/components/channels/api.ts +21 -20
  144. package/src/components/channels/types.ts +16 -0
  145. package/src/components/hooks/index.ts +0 -19
  146. package/src/components/hooks/useMessages.test.ts +80 -0
  147. package/src/components/hooks/useMessages.ts +13 -4
  148. package/src/components/hooks/useOrchestrator.ts +1 -1
  149. package/src/components/hooks/usePresence.ts +45 -6
  150. package/src/components/hooks/useThread.ts +83 -46
  151. package/src/components/hooks/useTrajectory.ts +62 -5
  152. package/src/components/hooks/useWebSocket.test.ts +358 -0
  153. package/src/components/hooks/useWebSocket.ts +243 -5
  154. package/src/components/index.ts +2 -14
  155. package/src/components/layout/Header.tsx +9 -15
  156. package/src/components/layout/Sidebar.tsx +1 -8
  157. package/src/components/settings/SettingsPage.tsx +108 -47
  158. package/src/components/settings/index.ts +0 -3
  159. package/src/landing/blogData.ts +1 -1
  160. package/src/lib/agent-merge.test.ts +2 -2
  161. package/src/lib/api.ts +8 -38
  162. package/src/lib/identity.test.ts +139 -0
  163. package/src/lib/identity.ts +48 -0
  164. package/src/lib/relaycastMessageAdapters.test.ts +182 -0
  165. package/src/lib/relaycastMessageAdapters.ts +105 -0
  166. package/src/lib/sanitize-logs.test.ts +227 -0
  167. package/src/lib/sanitize-logs.ts +202 -0
  168. package/src/providers/AgentProvider.tsx +799 -0
  169. package/src/providers/ChannelProvider.tsx +528 -0
  170. package/src/providers/CloudWorkspaceProvider.tsx +402 -0
  171. package/src/providers/MessageProvider.tsx +875 -0
  172. package/src/providers/RelayConfigProvider.tsx +94 -0
  173. package/src/providers/SendProvider.tsx +497 -0
  174. package/src/providers/SettingsProvider.tsx +247 -0
  175. package/src/providers/index.ts +26 -0
  176. package/src/types/index.ts +10 -10
  177. package/out/_next/static/chunks/11-9a2993a37266dcb3.js +0 -9
  178. package/out/_next/static/chunks/118-ae2b650136a5a5fc.js +0 -1
  179. package/out/_next/static/chunks/1dd3208c-40ab0fc0f60392b8.js +0 -1
  180. package/out/_next/static/chunks/202-fc0763dd7488e58f.js +0 -1
  181. package/out/_next/static/chunks/259-83b77fa1b91ba5aa.js +0 -1
  182. package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +0 -1
  183. package/out/_next/static/chunks/528-f5f676996d613c25.js +0 -2
  184. package/out/_next/static/chunks/663-ddb04081febc3678.js +0 -1
  185. package/out/_next/static/chunks/687-88b6b139a6bb0e2e.js +0 -1
  186. package/out/_next/static/chunks/695-51d25b1988644374.js +0 -1
  187. package/out/_next/static/chunks/773-54a2641043c81e55.js +0 -1
  188. package/out/_next/static/chunks/app/_not-found/page-6da9b72091e5b511.js +0 -1
  189. package/out/_next/static/chunks/app/about/page-fff7c6457683f243.js +0 -1
  190. package/out/_next/static/chunks/app/app/[[...slug]]/page-f7eca1b66fb4249b.js +0 -1
  191. package/out/_next/static/chunks/app/app/onboarding/page-129abc5da2e67971.js +0 -1
  192. package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-5d5f28fd126b692f.js +0 -1
  193. package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-b194f207fbd91862.js +0 -1
  194. package/out/_next/static/chunks/app/blog/page-b9bd9d8703fca76a.js +0 -1
  195. package/out/_next/static/chunks/app/careers/page-a4bd8d5f4de8f4eb.js +0 -1
  196. package/out/_next/static/chunks/app/changelog/page-9a1f6ad1743d63c5.js +0 -1
  197. package/out/_next/static/chunks/app/cloud/link/page-0844c5699b027c3b.js +0 -1
  198. package/out/_next/static/chunks/app/complete-profile/page-39ed5a67916beb87.js +0 -1
  199. package/out/_next/static/chunks/app/connect-repos/page-297eddee0c39f2a3.js +0 -1
  200. package/out/_next/static/chunks/app/contact/page-3c1dd8690217fade.js +0 -1
  201. package/out/_next/static/chunks/app/docs/page-1875e981f2c3fd13.js +0 -1
  202. package/out/_next/static/chunks/app/history/page-2d5c5695c9e8b40c.js +0 -1
  203. package/out/_next/static/chunks/app/layout-0a4b99656da25511.js +0 -1
  204. package/out/_next/static/chunks/app/login/page-f69c076f5a6fc520.js +0 -1
  205. package/out/_next/static/chunks/app/metrics/page-bebbee055669a17e.js +0 -1
  206. package/out/_next/static/chunks/app/page-0ee604f7070d14c0.js +0 -1
  207. package/out/_next/static/chunks/app/pricing/page-eeae7d594af333b6.js +0 -1
  208. package/out/_next/static/chunks/app/providers/setup/[provider]/page-daf9b3e05e77ae19.js +0 -1
  209. package/out/_next/static/chunks/app/security/page-cd562730fe84a0a2.js +0 -1
  210. package/out/_next/static/chunks/app/signup/page-c242ca08101a84ff.js +0 -1
  211. package/out/_next/static/chunks/app/terms/page-c7001720e7941dc6.js +0 -1
  212. package/out/_next/static/chunks/framework-3664cab31236a9fa.js +0 -1
  213. package/out/_next/static/chunks/main-app-7f73a939a312a228.js +0 -1
  214. package/out/_next/static/chunks/pages/_app-10a93ab5b7c32eb3.js +0 -1
  215. package/out/_next/static/chunks/pages/_error-2d792b2a41857be4.js +0 -1
  216. package/out/_next/static/css/8968d98ed4c4d33f.css +0 -1
  217. package/src/components/BillingResult.tsx +0 -447
  218. package/src/components/CloudSessionProvider.tsx +0 -130
  219. package/src/components/SessionExpiredModal.tsx +0 -128
  220. package/src/components/WorkspaceStatusIndicator.tsx +0 -396
  221. package/src/components/hooks/useSession.ts +0 -209
  222. package/src/components/hooks/useWorkspaceMembers.ts +0 -132
  223. package/src/components/hooks/useWorkspaceStatus.ts +0 -237
  224. package/src/components/settings/BillingSettingsPanel.tsx +0 -564
  225. package/src/components/settings/TeamSettingsPanel.tsx +0 -560
  226. package/src/components/settings/WorkspaceSettingsPanel.tsx +0 -1368
  227. package/src/lib/cloudApi.ts +0 -893
  228. /package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_ssgManifest.js +0 -0
@@ -7,7 +7,7 @@
7
7
  * 1. User runs `agent-relay cloud link` in terminal
8
8
  * 2. CLI opens this page with ?code=<temp>&machine=<id>&name=<name>
9
9
  * 3. User confirms machine details and clicks "Link Machine"
10
- * 4. Server generates API key via POST /api/daemons/link
10
+ * 4. Server generates API key via POST /api/brokers/link
11
11
  * 5. User copies API key back to terminal
12
12
  */
13
13
 
@@ -31,7 +31,7 @@ interface Workspace {
31
31
 
32
32
  interface LinkResult {
33
33
  apiKey: string;
34
- daemonId: string;
34
+ brokerId: string;
35
35
  workspaceId: string | null;
36
36
  }
37
37
 
@@ -108,7 +108,7 @@ function CloudLinkContent() {
108
108
  setError('');
109
109
 
110
110
  try {
111
- const result = await api.post<{ apiKey: string; daemonId: string; workspaceId: string | null }>('/api/daemons/link', {
111
+ const payload = {
112
112
  machineId: machineInfo.machineId,
113
113
  name: machineInfo.machineName,
114
114
  workspaceId: selectedWorkspaceId,
@@ -116,17 +116,25 @@ function CloudLinkContent() {
116
116
  linkedVia: 'cli',
117
117
  userAgent: navigator.userAgent,
118
118
  },
119
- });
119
+ };
120
+ const result = await api.post<{ apiKey: string; brokerId: string; workspaceId: string | null }>('/api/brokers/link', payload);
120
121
 
121
122
  setLinkResult({
122
123
  apiKey: result.apiKey,
123
- daemonId: result.daemonId,
124
+ brokerId: result.brokerId,
124
125
  workspaceId: result.workspaceId,
125
126
  });
126
127
  setState('success');
127
128
  } catch (err: any) {
128
129
  console.error('Link failed:', err);
129
- setError(err.message || 'Failed to link machine. Please try again.');
130
+ const message = err?.message || 'Failed to link machine. Please try again.';
131
+ if (typeof message === 'string' && message.includes('HTTP 404')) {
132
+ setError(
133
+ 'BREAKING CHANGE: daemon link endpoints were removed. Cloud API must expose POST /api/brokers/link.'
134
+ );
135
+ } else {
136
+ setError(message);
137
+ }
130
138
  setState('error');
131
139
  }
132
140
  };
@@ -161,10 +161,16 @@ export default function ConnectReposPage() {
161
161
  const error = err as Error & { type?: string };
162
162
  console.error('GitHub App auth error:', error);
163
163
 
164
- // Don't show error for user-cancelled auth
165
- if (error.type === 'user_cancelled' || error.message?.includes('closed')) {
164
+ // Don't show error for user-cancelled auth or stale OAuth state
165
+ const isUserCancelled = error.type === 'user_cancelled' || error.message?.includes('closed');
166
+ const isStaleState = error.message?.includes('invalid_oauth_state');
167
+
168
+ if (isUserCancelled || isStaleState) {
166
169
  setStatusMessage('');
167
- // Re-initialize for next attempt
170
+ if (isStaleState) {
171
+ setError('Session expired. Click Connect to try again.');
172
+ }
173
+ // Re-initialize Nango session for next attempt
168
174
  fetch('/api/auth/nango/repo-session', { credentials: 'include' })
169
175
  .then(res => res.json())
170
176
  .then(data => {
@@ -0,0 +1,130 @@
1
+ /**
2
+ * /dev/cli-tools
3
+ *
4
+ * Isolated, manually testable CLI tool harness page.
5
+ * Each card is independent and uses real `/api/spawn`, `/api/spawned/:name`,
6
+ * and websocket log streaming. No mocked fixtures are used.
7
+ */
8
+
9
+ 'use client';
10
+
11
+ import React, { Suspense } from 'react';
12
+ import { useSearchParams } from 'next/navigation';
13
+ import { CliToolHarness, type CliToolHarnessConfig } from '../../../components/CliToolHarness';
14
+
15
+ const CLI_TOOLS: CliToolHarnessConfig[] = [
16
+ {
17
+ id: 'claude',
18
+ name: 'Claude',
19
+ command: 'claude',
20
+ description: 'Spawns a real Claude CLI tool and streams its process logs.',
21
+ },
22
+ {
23
+ id: 'codex',
24
+ name: 'Codex',
25
+ command: 'codex',
26
+ description: 'Spawns a real Codex CLI tool and streams its process logs.',
27
+ },
28
+ {
29
+ id: 'gemini',
30
+ name: 'Gemini',
31
+ command: 'gemini',
32
+ description: 'Spawns a real Gemini CLI tool and streams its process logs.',
33
+ },
34
+ {
35
+ id: 'cursor',
36
+ name: 'Cursor',
37
+ command: 'cursor',
38
+ description: 'Spawns a real Cursor CLI tool and streams its process logs.',
39
+ },
40
+ {
41
+ id: 'opencode',
42
+ name: 'OpenCode',
43
+ command: 'opencode',
44
+ description: 'Spawns a real OpenCode CLI tool and streams its process logs.',
45
+ },
46
+ {
47
+ id: 'droid',
48
+ name: 'Droid',
49
+ command: 'droid',
50
+ description: 'Spawns a real Droid CLI tool and streams its process logs.',
51
+ },
52
+ ];
53
+
54
+ function CliToolsHarnessContent() {
55
+ const searchParams = useSearchParams();
56
+ const selectedTool = searchParams.get('tool') || searchParams.get('cli');
57
+ const normalizedTool = selectedTool?.trim().toLowerCase();
58
+ const selectedTools = normalizedTool
59
+ ? CLI_TOOLS.filter((tool) =>
60
+ tool.id === normalizedTool ||
61
+ tool.command.toLowerCase() === normalizedTool ||
62
+ tool.name.toLowerCase() === normalizedTool
63
+ )
64
+ : CLI_TOOLS;
65
+ const toolsToRender = selectedTools.length === 0 ? CLI_TOOLS : selectedTools;
66
+
67
+ return (
68
+ <div className="min-h-screen bg-[#0a0c10] text-[#c9d1d9]">
69
+ <div className="border-b border-[#21262d] bg-[#0d1117]">
70
+ <div className="mx-auto max-w-[1400px] px-6 py-5">
71
+ <div className="flex items-center justify-between gap-3">
72
+ <div>
73
+ <h1 className="text-lg font-semibold text-white flex items-center gap-2">
74
+ <span className="w-2.5 h-2.5 rounded-full bg-[#58a6ff]" />
75
+ CLI Tool Harness
76
+ </h1>
77
+ <p className="mt-1 text-sm text-[#8b949e]">
78
+ {selectedTool ? `Focused tool: ${selectedTool}` : 'Real CLI launch + live XTerm log stream per tool.'} No mocks.
79
+ </p>
80
+ </div>
81
+ <span className="rounded-md bg-[#3fb950]/20 px-2.5 py-1 text-xs font-medium text-[#3fb950]">
82
+ Manual + integration testing
83
+ </span>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <main className="mx-auto max-w-[1400px] px-6 py-6">
89
+ <div className="grid gap-4 md:grid-cols-2">
90
+ {selectedTools.length === 0 && (
91
+ <div className="rounded-lg border border-[#3fb950]/40 bg-[#1f2937] px-3 py-4 text-sm text-[#7c8594]">
92
+ No matching tool found for "{selectedTool}". Showing all available tools.
93
+ </div>
94
+ )}
95
+ {toolsToRender.map((tool) => (
96
+ <CliToolHarness key={tool.id} tool={tool} />
97
+ ))}
98
+ </div>
99
+ </main>
100
+ </div>
101
+ );
102
+ }
103
+
104
+ function CliToolsLoadingFallback() {
105
+ return (
106
+ <div className="min-h-screen bg-[#0a0c10] text-[#c9d1d9]">
107
+ <div className="border-b border-[#21262d] bg-[#0d1117]">
108
+ <div className="mx-auto max-w-[1400px] px-6 py-5">
109
+ <h1 className="text-lg font-semibold text-white flex items-center gap-2">
110
+ <span className="w-2.5 h-2.5 rounded-full bg-[#58a6ff]" />
111
+ CLI Tool Harness
112
+ </h1>
113
+ </div>
114
+ </div>
115
+ <main className="mx-auto max-w-[1400px] px-6 py-6">
116
+ <div className="rounded-lg border border-[#58a6ff]/30 bg-[#1f2937] px-3 py-4 text-sm text-[#7c8594]">
117
+ Initializing CLI tools…
118
+ </div>
119
+ </main>
120
+ </div>
121
+ );
122
+ }
123
+
124
+ export default function CliToolsHarnessPage() {
125
+ return (
126
+ <Suspense fallback={<CliToolsLoadingFallback />}>
127
+ <CliToolsHarnessContent />
128
+ </Suspense>
129
+ );
130
+ }
@@ -0,0 +1,132 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * InlineMockViewer — Standalone inline log viewer for testing.
5
+ *
6
+ * Renders sanitized log content (ANSI stripped) with direct data injection.
7
+ * Used on the /dev/log-viewer test page alongside the production XTermLogViewer
8
+ * (in mock mode) for side-by-side comparison.
9
+ */
10
+
11
+ import React, { useRef, useEffect, useState } from 'react';
12
+ import { sanitizeLogContent, isSpinnerFragment } from '../../../lib/sanitize-logs';
13
+ import type { LogFixtureLine, LogFixture } from './fixtures';
14
+
15
+ interface InlineMockViewerProps {
16
+ fixture: LogFixture;
17
+ streaming?: boolean;
18
+ }
19
+
20
+ export function InlineMockViewer({ fixture, streaming = false }: InlineMockViewerProps) {
21
+ const [lines, setLines] = useState<LogFixtureLine[]>([]);
22
+ const scrollRef = useRef<HTMLDivElement>(null);
23
+
24
+ useEffect(() => {
25
+ let cancelled = false;
26
+
27
+ if (!streaming) {
28
+ setLines(fixture.lines);
29
+ return;
30
+ }
31
+
32
+ // Streaming mode: feed lines with delays
33
+ setLines([]);
34
+ (async () => {
35
+ for (const line of fixture.lines) {
36
+ if (cancelled) break;
37
+ if (line.delay) {
38
+ await new Promise((r) => setTimeout(r, line.delay));
39
+ }
40
+ if (cancelled) break;
41
+ setLines((prev) => [...prev, line]);
42
+ }
43
+ })();
44
+
45
+ return () => {
46
+ cancelled = true;
47
+ };
48
+ }, [fixture, streaming]);
49
+
50
+ // Auto-scroll
51
+ useEffect(() => {
52
+ if (scrollRef.current) {
53
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
54
+ }
55
+ }, [lines]);
56
+
57
+ const filteredLines = lines.filter((line) => {
58
+ const stripped = sanitizeLogContent(line.content).trim();
59
+ if (stripped.length === 0) return false;
60
+ if (isSpinnerFragment(stripped)) return false;
61
+ return true;
62
+ });
63
+
64
+ return (
65
+ <div
66
+ className="rounded-lg overflow-hidden border border-[#2a2d35]"
67
+ style={{
68
+ background: 'linear-gradient(180deg, #0d0f14 0%, #12151c 100%)',
69
+ boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.02), 0 4px 12px rgba(0,0,0,0.3)',
70
+ }}
71
+ >
72
+ <div
73
+ className="flex items-center justify-between px-3 py-2 border-b border-[#2a2d35]"
74
+ style={{
75
+ background: 'linear-gradient(180deg, #161b22 0%, #0d1117 100%)',
76
+ }}
77
+ >
78
+ <div className="flex items-center gap-2">
79
+ <svg
80
+ width="16"
81
+ height="16"
82
+ viewBox="0 0 24 24"
83
+ fill="none"
84
+ stroke="currentColor"
85
+ strokeWidth="2"
86
+ strokeLinecap="round"
87
+ strokeLinejoin="round"
88
+ className="text-[#8b949e]"
89
+ >
90
+ <polyline points="4 17 10 11 4 5" />
91
+ <line x1="12" y1="19" x2="20" y2="19" />
92
+ </svg>
93
+ <span className="text-xs font-medium text-accent-cyan">
94
+ Inline Mode
95
+ </span>
96
+ <span className="px-1.5 py-0.5 rounded-full bg-[#238636]/20 text-[10px] text-[#3fb950] uppercase tracking-wider">
97
+ mock
98
+ </span>
99
+ </div>
100
+ <span className="text-[10px] text-[#6e7681] font-mono">
101
+ {filteredLines.length} lines
102
+ </span>
103
+ </div>
104
+ <div
105
+ ref={scrollRef}
106
+ className="font-mono text-xs leading-relaxed p-3 overflow-y-auto"
107
+ style={{ maxHeight: '400px' }}
108
+ >
109
+ {filteredLines.map((line, idx) => {
110
+ const sanitized = sanitizeLogContent(line.content);
111
+ const typeClass =
112
+ line.type === 'stderr'
113
+ ? 'text-[#f85149]'
114
+ : line.type === 'system'
115
+ ? 'text-[#58a6ff] italic'
116
+ : 'text-[#c9d1d9]';
117
+ return (
118
+ <div key={idx} className={`${typeClass} leading-5 whitespace-pre-wrap break-all`}>
119
+ {sanitized}
120
+ </div>
121
+ );
122
+ })}
123
+ {filteredLines.length === 0 && (
124
+ <div className="text-[#484f58] italic flex items-center gap-2">
125
+ <span className="w-1.5 h-1.5 rounded-full bg-[#484f58] animate-pulse" />
126
+ {streaming ? 'Streaming...' : 'No output'}
127
+ </div>
128
+ )}
129
+ </div>
130
+ </div>
131
+ );
132
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * CLI output fixtures for the isolated log viewer test page.
3
+ *
4
+ * Static fixtures for edge-case testing + dynamic loading of real log files
5
+ * from the dashboard server's /api/logs endpoint.
6
+ */
7
+
8
+ export interface LogFixtureLine {
9
+ content: string;
10
+ type: 'stdout' | 'stderr' | 'system';
11
+ delay?: number;
12
+ }
13
+
14
+ export interface LogFixture {
15
+ name: string;
16
+ description: string;
17
+ lines: LogFixtureLine[];
18
+ }
19
+
20
+ // ── Edge cases fixture (static) ─────────────────────────────────────
21
+
22
+ export const edgeCaseFixture: LogFixture = {
23
+ name: 'Edge Cases',
24
+ description: 'Malformed escapes, Unicode, long lines, stderr, spinners',
25
+ lines: [
26
+ // stderr output
27
+ { content: 'Warning: deprecated API usage\n', type: 'stderr' },
28
+ { content: 'Error: ENOENT: no such file or directory\n', type: 'stderr', delay: 100 },
29
+
30
+ // system messages
31
+ { content: '[Connected to agent log stream]\n', type: 'system' },
32
+
33
+ // Orphaned/degraded ANSI (lost ESC byte)
34
+ { content: '[38;5;216mThis had a color code[0m but ESC bytes were stripped\n', type: 'stdout', delay: 100 },
35
+ { content: '[?25hCursor show command leaked\n', type: 'stdout', delay: 100 },
36
+ { content: '[2KLine erase leaked\n', type: 'stdout', delay: 100 },
37
+
38
+ // Unicode and emoji
39
+ { content: '🚀 Deploying to production...\n', type: 'stdout', delay: 200 },
40
+ { content: '✅ Deploy complete! 🎉 (took 2.3s)\n', type: 'stdout', delay: 200 },
41
+ { content: '日本語テスト: 成功\n', type: 'stdout', delay: 100 },
42
+ { content: '中文测试: 通过\n', type: 'stdout', delay: 100 },
43
+
44
+ // Very long line
45
+ { content: 'Long line: ' + 'abcdefghij'.repeat(50) + '\n', type: 'stdout', delay: 100 },
46
+
47
+ // Rapid carriage returns (progress simulation)
48
+ { content: 'Downloading... 10%\r', type: 'stdout', delay: 50 },
49
+ { content: 'Downloading... 30%\r', type: 'stdout', delay: 50 },
50
+ { content: 'Downloading... 50%\r', type: 'stdout', delay: 50 },
51
+ { content: 'Downloading... 70%\r', type: 'stdout', delay: 50 },
52
+ { content: 'Downloading... 90%\r', type: 'stdout', delay: 50 },
53
+ { content: 'Downloading... 100%\n', type: 'stdout', delay: 50 },
54
+
55
+ // Spinner sequences
56
+ { content: '⠋', type: 'stdout', delay: 80 },
57
+ { content: '\r⠙', type: 'stdout', delay: 80 },
58
+ { content: '\r⠹', type: 'stdout', delay: 80 },
59
+ { content: '\r⠸', type: 'stdout', delay: 80 },
60
+ { content: '\r⠼', type: 'stdout', delay: 80 },
61
+ { content: '\r\x1b[2K', type: 'stdout' },
62
+ { content: 'Done!\n', type: 'stdout' },
63
+
64
+ // Backspace overwrites
65
+ { content: 'Typo\x08\x08\x08\x08Fixed text here\n', type: 'stdout', delay: 100 },
66
+
67
+ // Mixed ANSI with content
68
+ { content: '\x1b[1m\x1b[4mBold and underlined\x1b[0m normal \x1b[31mred\x1b[0m \x1b[42mgreen bg\x1b[0m\n', type: 'stdout' },
69
+
70
+ // Nested 256-color
71
+ { content: '\x1b[38;5;196m\x1b[48;5;232m Red on dark \x1b[0m \x1b[38;5;46m\x1b[48;5;17m Green on navy \x1b[0m\n', type: 'stdout' },
72
+ ],
73
+ };
74
+
75
+ // ── Streaming simulation fixture (static) ───────────────────────────
76
+
77
+ export const streamingFixture: LogFixture = {
78
+ name: 'Streaming',
79
+ description: 'Simulates real-time streaming with delays to test auto-scroll and buffering',
80
+ lines: Array.from({ length: 50 }, (_, i) => ({
81
+ content: `\x1b[2m${String(i + 1).padStart(3, '0')}\x1b[22m \x1b[${
82
+ i % 2 === 0 ? '36' : '33'
83
+ }m[${new Date(Date.now() + i * 200).toISOString().slice(11, 23)}]\x1b[0m Processing item ${i + 1}/50...\n`,
84
+ type: 'stdout' as const,
85
+ delay: 200,
86
+ })),
87
+ };
88
+
89
+ /** Static fixtures that don't need a server */
90
+ export const STATIC_FIXTURES: LogFixture[] = [
91
+ edgeCaseFixture,
92
+ streamingFixture,
93
+ ];
94
+
95
+ /**
96
+ * Convert raw log file content (single string) into LogFixtureLine array.
97
+ * Each line of the file becomes a separate fixture line.
98
+ */
99
+ export function rawLogToFixture(name: string, rawContent: string): LogFixture {
100
+ const lines = rawContent.split('\n').map((line) => ({
101
+ content: line + '\n',
102
+ type: 'stdout' as const,
103
+ }));
104
+
105
+ return {
106
+ name,
107
+ description: `Real log output from ${name} worker`,
108
+ lines,
109
+ };
110
+ }