@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.
- package/out/404.html +1 -1
- package/out/_next/static/chunks/1028-da5d75e35d1420f1.js +1 -0
- package/out/_next/static/chunks/1528-78b17000a7e10bc6.js +2 -0
- package/out/_next/static/chunks/1695-4a5d33ba715e09b4.js +1 -0
- package/out/_next/static/chunks/1705-36c2180d00a4a569.js +1 -0
- package/out/_next/static/chunks/1dd3208c-e1f87c7b3dc1a820.js +1 -0
- package/out/_next/static/chunks/3663-47290254b8f6f5dd.js +1 -0
- package/out/_next/static/chunks/3677-4b225baf4801d9b9.js +73 -0
- package/out/_next/static/chunks/5118-7e8ada2df38eef07.js +1 -0
- package/out/_next/static/chunks/5888-15cbe97c90ed5fae.js +1 -0
- package/out/_next/static/chunks/6773-a45343a98df3abb5.js +1 -0
- package/out/_next/static/chunks/6940-b824612b605e79b3.js +9 -0
- package/out/_next/static/chunks/7894-f4a15249082a680d.js +1 -0
- package/out/_next/static/chunks/9175-b3617c1e5cbfed0e.js +1 -0
- package/out/_next/static/chunks/9372-1a804b8d08c7a236.js +1 -0
- package/out/_next/static/chunks/{ab6c8a12-0a58072fbb505134.js → ab6c8a12-91438a812d94ecf0.js} +1 -1
- package/out/_next/static/chunks/app/_not-found/page-8e8842f82d204726.js +1 -0
- package/out/_next/static/chunks/app/about/page-b78577a7da8fa459.js +1 -0
- package/out/_next/static/chunks/app/app/[[...slug]]/page-3dffd65b6344f53e.js +1 -0
- package/out/_next/static/chunks/app/app/onboarding/page-b89be9aa6264a5e1.js +1 -0
- package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-fbd00893ef69e499.js +1 -0
- package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-de2ea13649d0b6d3.js +1 -0
- package/out/_next/static/chunks/app/blog/page-a08e263c57a156fa.js +1 -0
- package/out/_next/static/chunks/app/careers/page-02228e1d6969b232.js +1 -0
- package/out/_next/static/chunks/app/changelog/page-1b5c1d79efc6e53a.js +1 -0
- package/out/_next/static/chunks/app/cloud/link/page-99654edffffb3af2.js +1 -0
- package/out/_next/static/chunks/app/complete-profile/page-59d146e5ddeafc5c.js +1 -0
- package/out/_next/static/chunks/app/connect-repos/page-995e16a976a6632c.js +1 -0
- package/out/_next/static/chunks/app/contact/page-273396a5ad57bcee.js +1 -0
- package/out/_next/static/chunks/app/dev/cli-tools/page-a71b80dcb2d5fc8d.js +1 -0
- package/out/_next/static/chunks/app/dev/log-viewer/page-46a6151ae1be0796.js +1 -0
- package/out/_next/static/chunks/app/docs/page-7c7cb603b24b7c40.js +1 -0
- package/out/_next/static/chunks/app/history/page-0c5cab1dab4e8886.js +1 -0
- package/out/_next/static/chunks/app/layout-96d72ba8ef8a43a0.js +1 -0
- package/out/_next/static/chunks/app/login/page-0ccbab34213df842.js +1 -0
- package/out/_next/static/chunks/app/metrics/page-8616272aeab9c8b0.js +1 -0
- package/out/_next/static/chunks/app/page-09ce10603ad9a251.js +1 -0
- package/out/_next/static/chunks/app/pricing/page-91c975079120c941.js +1 -0
- package/out/_next/static/chunks/app/privacy/{page-c21d51ac2dee3a88.js → page-a49ab271cc686644.js} +1 -1
- package/out/_next/static/chunks/app/providers/{page-59114505f4353512.js → page-d775d6eb5bc29e96.js} +1 -1
- package/out/_next/static/chunks/app/providers/setup/[provider]/page-ec4ef3cd80de807e.js +1 -0
- package/out/_next/static/chunks/app/security/page-d9da9bd9191e8f95.js +1 -0
- package/out/_next/static/chunks/app/signup/page-930eca0bf5fd299d.js +1 -0
- package/out/_next/static/chunks/app/terms/page-3e4827620b98613c.js +1 -0
- package/out/_next/static/chunks/framework-648e1ae7da590300.js +1 -0
- package/out/_next/static/chunks/{main-acb1b24265295d6a.js → main-2b1990080c292d92.js} +1 -1
- package/out/_next/static/chunks/main-app-9f6b7ff9e754a8f5.js +1 -0
- package/out/_next/static/chunks/pages/_app-a077b72e02273ab1.js +1 -0
- package/out/_next/static/chunks/pages/_error-84001666436a04e4.js +1 -0
- package/out/_next/static/chunks/{webpack-dd93b81e2659669c.js → webpack-7586035f1585f2db.js} +1 -1
- package/out/_next/static/css/eb9fc69d1e3d2bed.css +1 -0
- package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_buildManifest.js +1 -1
- package/out/about.html +2 -2
- package/out/about.txt +2 -2
- package/out/app/onboarding.html +1 -1
- package/out/app/onboarding.txt +2 -2
- package/out/app.html +1 -1
- package/out/app.txt +2 -2
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +3 -3
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
- package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
- package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
- package/out/blog.html +2 -2
- package/out/blog.txt +1 -1
- package/out/careers.html +2 -2
- package/out/careers.txt +2 -2
- package/out/changelog.html +2 -2
- package/out/changelog.txt +2 -2
- package/out/cloud/link.html +1 -1
- package/out/cloud/link.txt +2 -2
- package/out/complete-profile.html +2 -2
- package/out/complete-profile.txt +2 -2
- package/out/connect-repos.html +1 -1
- package/out/connect-repos.txt +2 -2
- package/out/contact.html +2 -2
- package/out/contact.txt +2 -2
- package/out/dev/cli-tools.html +1 -0
- package/out/dev/cli-tools.txt +7 -0
- package/out/dev/log-viewer.html +23 -0
- package/out/dev/log-viewer.txt +7 -0
- package/out/docs.html +2 -2
- package/out/docs.txt +2 -2
- package/out/history.html +1 -1
- package/out/history.txt +2 -2
- package/out/index.html +1 -1
- package/out/index.txt +2 -2
- package/out/login.html +2 -2
- package/out/login.txt +2 -2
- package/out/metrics.html +1 -1
- package/out/metrics.txt +2 -2
- package/out/pricing.html +2 -2
- package/out/pricing.txt +2 -2
- package/out/privacy.html +2 -2
- package/out/privacy.txt +2 -2
- package/out/providers/setup/claude.html +1 -1
- package/out/providers/setup/claude.txt +2 -2
- package/out/providers/setup/codex.html +1 -1
- package/out/providers/setup/codex.txt +2 -2
- package/out/providers/setup/cursor.html +1 -1
- package/out/providers/setup/cursor.txt +2 -2
- package/out/providers.html +1 -1
- package/out/providers.txt +2 -2
- package/out/security.html +2 -2
- package/out/security.txt +2 -2
- package/out/signup.html +2 -2
- package/out/signup.txt +2 -2
- package/out/terms.html +2 -2
- package/out/terms.txt +2 -2
- package/package.json +5 -1
- package/src/adapters/DashboardConfigProvider.tsx +56 -0
- package/src/adapters/cloudFetchAdapter.ts +278 -0
- package/src/adapters/index.ts +3 -0
- package/src/adapters/types.ts +508 -0
- package/src/app/app/[[...slug]]/DashboardPageClient.tsx +67 -18
- package/src/app/app/onboarding/page.tsx +870 -170
- package/src/app/cloud/link/page.tsx +14 -6
- package/src/app/connect-repos/page.tsx +9 -3
- package/src/app/dev/cli-tools/page.tsx +130 -0
- package/src/app/dev/log-viewer/MockLogViewer.tsx +132 -0
- package/src/app/dev/log-viewer/fixtures.ts +110 -0
- package/src/app/dev/log-viewer/page.tsx +288 -0
- package/src/app/history/page.tsx +28 -12
- package/src/app/page.tsx +1 -1
- package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +209 -59
- package/src/components/AgentCard.tsx +4 -4
- package/src/components/AgentLogPreview.tsx +2 -38
- package/src/components/App.tsx +441 -2624
- package/src/components/CliToolHarness.test.tsx +83 -0
- package/src/components/CliToolHarness.tsx +292 -0
- package/src/components/CoordinatorPanel.tsx +13 -6
- package/src/components/LogViewer.tsx +2 -42
- package/src/components/ProviderAuthFlow.tsx +201 -81
- package/src/components/ProvisioningProgress.tsx +1 -1
- package/src/components/ReactionChips.tsx +2 -1
- package/src/components/SpawnModal.test.tsx +51 -18
- package/src/components/SpawnModal.tsx +175 -207
- package/src/components/TerminalProviderSetup.tsx +1 -1
- package/src/components/ThreadPanel.tsx +2 -0
- package/src/components/WorkspaceContext.tsx +7 -19
- package/src/components/XTermLogViewer.tsx +190 -27
- package/src/components/channels/ChannelMessageList.tsx +94 -4
- package/src/components/channels/ChannelViewV1.tsx +35 -11
- package/src/components/channels/api.ts +21 -20
- package/src/components/channels/types.ts +16 -0
- package/src/components/hooks/index.ts +0 -19
- package/src/components/hooks/useMessages.test.ts +80 -0
- package/src/components/hooks/useMessages.ts +13 -4
- package/src/components/hooks/useOrchestrator.ts +1 -1
- package/src/components/hooks/usePresence.ts +45 -6
- package/src/components/hooks/useThread.ts +83 -46
- package/src/components/hooks/useTrajectory.ts +62 -5
- package/src/components/hooks/useWebSocket.test.ts +358 -0
- package/src/components/hooks/useWebSocket.ts +243 -5
- package/src/components/index.ts +2 -14
- package/src/components/layout/Header.tsx +9 -15
- package/src/components/layout/Sidebar.tsx +1 -8
- package/src/components/settings/SettingsPage.tsx +108 -47
- package/src/components/settings/index.ts +0 -3
- package/src/landing/blogData.ts +1 -1
- package/src/lib/agent-merge.test.ts +2 -2
- package/src/lib/api.ts +8 -38
- package/src/lib/identity.test.ts +139 -0
- package/src/lib/identity.ts +48 -0
- package/src/lib/relaycastMessageAdapters.test.ts +182 -0
- package/src/lib/relaycastMessageAdapters.ts +105 -0
- package/src/lib/sanitize-logs.test.ts +227 -0
- package/src/lib/sanitize-logs.ts +202 -0
- package/src/providers/AgentProvider.tsx +799 -0
- package/src/providers/ChannelProvider.tsx +528 -0
- package/src/providers/CloudWorkspaceProvider.tsx +402 -0
- package/src/providers/MessageProvider.tsx +875 -0
- package/src/providers/RelayConfigProvider.tsx +94 -0
- package/src/providers/SendProvider.tsx +497 -0
- package/src/providers/SettingsProvider.tsx +247 -0
- package/src/providers/index.ts +26 -0
- package/src/types/index.ts +10 -10
- package/out/_next/static/chunks/11-9a2993a37266dcb3.js +0 -9
- package/out/_next/static/chunks/118-ae2b650136a5a5fc.js +0 -1
- package/out/_next/static/chunks/1dd3208c-40ab0fc0f60392b8.js +0 -1
- package/out/_next/static/chunks/202-fc0763dd7488e58f.js +0 -1
- package/out/_next/static/chunks/259-83b77fa1b91ba5aa.js +0 -1
- package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +0 -1
- package/out/_next/static/chunks/528-f5f676996d613c25.js +0 -2
- package/out/_next/static/chunks/663-ddb04081febc3678.js +0 -1
- package/out/_next/static/chunks/687-88b6b139a6bb0e2e.js +0 -1
- package/out/_next/static/chunks/695-51d25b1988644374.js +0 -1
- package/out/_next/static/chunks/773-54a2641043c81e55.js +0 -1
- package/out/_next/static/chunks/app/_not-found/page-6da9b72091e5b511.js +0 -1
- package/out/_next/static/chunks/app/about/page-fff7c6457683f243.js +0 -1
- package/out/_next/static/chunks/app/app/[[...slug]]/page-f7eca1b66fb4249b.js +0 -1
- package/out/_next/static/chunks/app/app/onboarding/page-129abc5da2e67971.js +0 -1
- package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-5d5f28fd126b692f.js +0 -1
- package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-b194f207fbd91862.js +0 -1
- package/out/_next/static/chunks/app/blog/page-b9bd9d8703fca76a.js +0 -1
- package/out/_next/static/chunks/app/careers/page-a4bd8d5f4de8f4eb.js +0 -1
- package/out/_next/static/chunks/app/changelog/page-9a1f6ad1743d63c5.js +0 -1
- package/out/_next/static/chunks/app/cloud/link/page-0844c5699b027c3b.js +0 -1
- package/out/_next/static/chunks/app/complete-profile/page-39ed5a67916beb87.js +0 -1
- package/out/_next/static/chunks/app/connect-repos/page-297eddee0c39f2a3.js +0 -1
- package/out/_next/static/chunks/app/contact/page-3c1dd8690217fade.js +0 -1
- package/out/_next/static/chunks/app/docs/page-1875e981f2c3fd13.js +0 -1
- package/out/_next/static/chunks/app/history/page-2d5c5695c9e8b40c.js +0 -1
- package/out/_next/static/chunks/app/layout-0a4b99656da25511.js +0 -1
- package/out/_next/static/chunks/app/login/page-f69c076f5a6fc520.js +0 -1
- package/out/_next/static/chunks/app/metrics/page-bebbee055669a17e.js +0 -1
- package/out/_next/static/chunks/app/page-0ee604f7070d14c0.js +0 -1
- package/out/_next/static/chunks/app/pricing/page-eeae7d594af333b6.js +0 -1
- package/out/_next/static/chunks/app/providers/setup/[provider]/page-daf9b3e05e77ae19.js +0 -1
- package/out/_next/static/chunks/app/security/page-cd562730fe84a0a2.js +0 -1
- package/out/_next/static/chunks/app/signup/page-c242ca08101a84ff.js +0 -1
- package/out/_next/static/chunks/app/terms/page-c7001720e7941dc6.js +0 -1
- package/out/_next/static/chunks/framework-3664cab31236a9fa.js +0 -1
- package/out/_next/static/chunks/main-app-7f73a939a312a228.js +0 -1
- package/out/_next/static/chunks/pages/_app-10a93ab5b7c32eb3.js +0 -1
- package/out/_next/static/chunks/pages/_error-2d792b2a41857be4.js +0 -1
- package/out/_next/static/css/8968d98ed4c4d33f.css +0 -1
- package/src/components/BillingResult.tsx +0 -447
- package/src/components/CloudSessionProvider.tsx +0 -130
- package/src/components/SessionExpiredModal.tsx +0 -128
- package/src/components/WorkspaceStatusIndicator.tsx +0 -396
- package/src/components/hooks/useSession.ts +0 -209
- package/src/components/hooks/useWorkspaceMembers.ts +0 -132
- package/src/components/hooks/useWorkspaceStatus.ts +0 -237
- package/src/components/settings/BillingSettingsPanel.tsx +0 -564
- package/src/components/settings/TeamSettingsPanel.tsx +0 -560
- package/src/components/settings/WorkspaceSettingsPanel.tsx +0 -1368
- package/src/lib/cloudApi.ts +0 -893
- /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/
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|