@promptbook/cli 0.112.0-97 → 0.112.0-99
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/apps/agents-server/README.md +3 -3
- package/apps/agents-server/src/app/admin/cli-access/CliAccessClient.tsx +99 -0
- package/apps/agents-server/src/app/admin/cli-access/page.tsx +14 -0
- package/apps/agents-server/src/app/admin/code-runners/CodeRunnersClient.tsx +76 -325
- package/apps/agents-server/src/app/admin/database/page.tsx +1 -2
- package/apps/agents-server/src/app/agents/[agentName]/chat/CanonicalAgentChatSurface.tsx +24 -0
- package/apps/agents-server/src/app/api/admin/cli-access/route.ts +137 -0
- package/apps/agents-server/src/app/api/admin/code-runners/authentication/route.ts +7 -64
- package/apps/agents-server/src/app/api/admin/servers/[serverId]/route.ts +3 -3
- package/apps/agents-server/src/app/api/admin/servers/route.ts +4 -4
- package/apps/agents-server/src/app/api/chat/export/pdf/route.ts +63 -0
- package/apps/agents-server/src/components/AdminTerminal/AdminTerminalCard.tsx +279 -0
- package/apps/agents-server/src/components/AdminTerminal/useAdminTerminalSession.ts +336 -0
- package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +4 -0
- package/apps/agents-server/src/database/$provideClientSql.ts +17 -4
- package/apps/agents-server/src/database/$provideDatabaseAdminExecutor.ts +24 -3
- package/apps/agents-server/src/database/$provideSupabaseForServer.ts +1 -11
- package/apps/agents-server/src/database/agentsServerDatabaseMode.ts +1 -20
- package/apps/agents-server/src/languages/ServerTranslationKeys.ts +1 -0
- package/apps/agents-server/src/languages/translations/czech.yaml +1 -0
- package/apps/agents-server/src/languages/translations/english.yaml +1 -0
- package/apps/agents-server/src/tools/$provideServer.ts +2 -2
- package/apps/agents-server/src/tools/BrowserConnectionProvider.ts +1 -1
- package/apps/agents-server/src/utils/chatExport/downloadChatPdfFromServer.ts +59 -0
- package/apps/agents-server/src/utils/chatExport/renderHtmlToPdfOnServer.ts +37 -0
- package/apps/agents-server/src/utils/codeRunnerAuthentication.ts +77 -237
- package/apps/agents-server/src/utils/createInteractiveTerminalEventStream.ts +84 -0
- package/apps/agents-server/src/utils/interactiveTerminalSession.ts +442 -0
- package/apps/agents-server/src/utils/serverCliAccess.ts +221 -0
- package/apps/agents-server/src/utils/serverRegistry.ts +4 -4
- package/apps/agents-server/src/utils/vpsConfiguration.ts +2 -0
- package/esm/apps/agents-server/src/database/agentsServerDatabaseMode.d.ts +1 -9
- package/esm/index.es.js +2 -2
- package/esm/src/book-components/Chat/Chat/ChatActionsBar.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +6 -0
- package/esm/src/book-components/Chat/save/_common/ChatSaveFormatHandler.d.ts +35 -0
- package/esm/src/book-components/Chat/save/_common/createChatExportFilename.d.ts +11 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/book-components/Chat/Chat/Chat.tsx +2 -0
- package/src/book-components/Chat/Chat/ChatActionsBar.tsx +17 -9
- package/src/book-components/Chat/Chat/ChatProps.tsx +7 -0
- package/src/book-components/Chat/save/_common/ChatSaveFormatHandler.ts +40 -0
- package/src/book-components/Chat/save/_common/createChatExportFilename.ts +20 -0
- package/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.ts +1 -1
- package/src/other/templates/getTemplatesPipelineCollection.ts +706 -736
- package/src/version.ts +2 -2
- package/src/versions.txt +2 -0
- package/umd/apps/agents-server/src/database/agentsServerDatabaseMode.d.ts +1 -9
- package/umd/index.umd.js +2 -2
- package/umd/src/book-components/Chat/Chat/ChatActionsBar.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +6 -0
- package/umd/src/book-components/Chat/save/_common/ChatSaveFormatHandler.d.ts +35 -0
- package/umd/src/book-components/Chat/save/_common/createChatExportFilename.d.ts +11 -0
- package/umd/src/version.d.ts +1 -1
- package/apps/agents-server/src/database/$providePostgresPool.ts +0 -27
- package/apps/agents-server/src/database/postgres/$provideLocalPostgresSupabase.ts +0 -1261
- package/src/conversion/validation/_importPipeline.ts +0 -88
- /package/esm/src/conversion/validation/{_importPipeline.d.ts → _importPipeline.test.d.ts} +0 -0
- /package/umd/src/conversion/validation/{_importPipeline.d.ts → _importPipeline.test.d.ts} +0 -0
|
@@ -12,10 +12,10 @@ ptbk agents-server init
|
|
|
12
12
|
ptbk agents-server start --agent github-copilot --model gpt-5.4 --thinking-level xhigh
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
`ptbk agents-server init` adds missing placeholders to `.env` and local runtime exclusions to `.gitignore` without deleting existing configuration. Use `PTBK_AGENTS_SERVER_DATABASE=supabase` for a Supabase-backed server
|
|
15
|
+
`ptbk agents-server init` adds missing placeholders to `.env` and local runtime exclusions to `.gitignore` without deleting existing configuration. Use `PTBK_AGENTS_SERVER_DATABASE=supabase` for a Supabase-backed server or `PTBK_AGENTS_SERVER_DATABASE=sqlite` for a standalone local database in `.promptbook`. When using Supabase, fill the initialized values from your project before starting the server. The Supabase project URL and API keys are available in the [Supabase project API settings](https://supabase.com/docs/guides/api/api-keys), and the PostgreSQL connection string is available from the [Supabase database connection guide](https://supabase.com/docs/guides/database/connecting-to-postgres).
|
|
16
16
|
|
|
17
17
|
<a id="agents-server-env-ptbk-agents-server-database"></a>
|
|
18
|
-
- `PTBK_AGENTS_SERVER_DATABASE`: Database backend used by Agents Server. Use `supabase` for the current hosted setup
|
|
18
|
+
- `PTBK_AGENTS_SERVER_DATABASE`: Database backend used by Agents Server. Use `supabase` for the current hosted setup or `sqlite` for a standalone local database.
|
|
19
19
|
|
|
20
20
|
<a id="agents-server-env-ptbk-agents-server-sqlite-path"></a>
|
|
21
21
|
- `PTBK_AGENTS_SERVER_SQLITE_PATH`: SQLite database file used when `PTBK_AGENTS_SERVER_DATABASE=sqlite`. Defaults to `.promptbook/agents-server.sqlite` in the launch directory.
|
|
@@ -24,7 +24,7 @@ ptbk agents-server start --agent github-copilot --model gpt-5.4 --thinking-level
|
|
|
24
24
|
- `OPENAI_API_KEY`: OpenAI API key used for Agents Server chat and agent execution. Create one in the [OpenAI API key settings](https://platform.openai.com/api-keys).
|
|
25
25
|
|
|
26
26
|
<a id="agents-server-env-postgres-url"></a>
|
|
27
|
-
- `POSTGRES_URL`: PostgreSQL connection string used by Agents Server SQL access and migrations.
|
|
27
|
+
- `POSTGRES_URL`: PostgreSQL connection string used by Agents Server SQL access and migrations. Copy a connection string from your Supabase database connection settings.
|
|
28
28
|
|
|
29
29
|
<a id="agents-server-env-next-public-supabase-url"></a>
|
|
30
30
|
- `NEXT_PUBLIC_SUPABASE_URL`: Public Supabase project URL used by Agents Server clients. Copy the project URL from your Supabase project API settings.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { AlertTriangle } from 'lucide-react';
|
|
4
|
+
import { AdminTerminalCard } from '../../../components/AdminTerminal/AdminTerminalCard';
|
|
5
|
+
import { useAdminTerminalSession } from '../../../components/AdminTerminal/useAdminTerminalSession';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Browser-safe snapshot of one raw CLI access session.
|
|
9
|
+
*/
|
|
10
|
+
type CliAccessSession = {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly title: string;
|
|
13
|
+
readonly shell: string;
|
|
14
|
+
readonly workingDirectory: string;
|
|
15
|
+
readonly isRunning: boolean;
|
|
16
|
+
readonly output: string;
|
|
17
|
+
readonly startedAt: string;
|
|
18
|
+
readonly finishedAt: string | null;
|
|
19
|
+
readonly exitCode: number | null;
|
|
20
|
+
readonly signal: string | null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Super-admin UI for the unrestricted standalone VPS shell.
|
|
25
|
+
*/
|
|
26
|
+
export function CliAccessClient() {
|
|
27
|
+
const terminal = useAdminTerminalSession<CliAccessSession>({
|
|
28
|
+
basePath: '/api/admin/cli-access',
|
|
29
|
+
loadErrorMessage: 'Failed to load the CLI access session.',
|
|
30
|
+
startErrorMessage: 'Failed to start the CLI access session.',
|
|
31
|
+
sendErrorMessage: 'Failed to send CLI access input.',
|
|
32
|
+
stopErrorMessage: 'Failed to stop the CLI access session.',
|
|
33
|
+
startSuccessMessage: 'CLI access terminal started.',
|
|
34
|
+
finishSuccessMessage: 'CLI access session finished.',
|
|
35
|
+
finishErrorMessage: 'CLI access session ended with an error.',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="container mx-auto space-y-6 px-4 py-8">
|
|
40
|
+
<div className="mt-20">
|
|
41
|
+
<h1 className="text-3xl font-light text-gray-900">CLI Access</h1>
|
|
42
|
+
<p className="mt-1 max-w-4xl text-sm text-gray-500">
|
|
43
|
+
Open the live server shell directly in the Agents Server UI and run raw commands with the same
|
|
44
|
+
permissions as the running Agents Server process.
|
|
45
|
+
</p>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
{terminal.errorMessage ? (
|
|
49
|
+
<div className="rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700">
|
|
50
|
+
{terminal.errorMessage}
|
|
51
|
+
</div>
|
|
52
|
+
) : null}
|
|
53
|
+
{terminal.successMessage ? (
|
|
54
|
+
<div className="rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700">
|
|
55
|
+
{terminal.successMessage}
|
|
56
|
+
</div>
|
|
57
|
+
) : null}
|
|
58
|
+
|
|
59
|
+
<div className="rounded-2xl border border-amber-200 bg-amber-50 px-4 py-4 text-sm text-amber-900">
|
|
60
|
+
<div className="flex items-start gap-3">
|
|
61
|
+
<AlertTriangle className="mt-0.5 h-5 w-5 flex-none" />
|
|
62
|
+
<div className="space-y-1">
|
|
63
|
+
<p className="font-semibold">Super-admin only</p>
|
|
64
|
+
<p>
|
|
65
|
+
This terminal is intentionally unrestricted. Every command runs directly on the server as the
|
|
66
|
+
same operating-system user that runs the Agents Server.
|
|
67
|
+
</p>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<AdminTerminalCard
|
|
73
|
+
title="Raw server shell"
|
|
74
|
+
description="Start one shared shell session in the managed installation directory, run any command you need, and stop the session when you are done."
|
|
75
|
+
hint={`Shell: ${terminal.session?.shell || 'bash'}${terminal.session?.workingDirectory ? ` • Working directory: ${terminal.session.workingDirectory}` : ''}`}
|
|
76
|
+
session={terminal.session}
|
|
77
|
+
input={terminal.input}
|
|
78
|
+
onInputChange={terminal.setInput}
|
|
79
|
+
onStart={() => void terminal.startSession()}
|
|
80
|
+
onStop={() => void terminal.stopSession()}
|
|
81
|
+
onSend={(input) => void terminal.sendInput(input)}
|
|
82
|
+
isLoading={terminal.isLoadingSession}
|
|
83
|
+
isStarting={terminal.isStarting}
|
|
84
|
+
isSending={terminal.isSending}
|
|
85
|
+
isStopping={terminal.isStopping}
|
|
86
|
+
startLabel="Start CLI session"
|
|
87
|
+
runningLabel="CLI session running"
|
|
88
|
+
stopLabel="Stop session"
|
|
89
|
+
outputLabel="Live shell output"
|
|
90
|
+
outputEmptyState="No shell output yet. Start the CLI session to see the live server console here."
|
|
91
|
+
inputPlaceholder="Type any shell command, for example pm2 status, git pull, npm run build, or bash scripts"
|
|
92
|
+
quickActions={[
|
|
93
|
+
{ label: 'Send Enter', input: '\n' },
|
|
94
|
+
{ label: 'Send Ctrl+C', input: '\u0003' },
|
|
95
|
+
]}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ForbiddenPage } from '../../../components/ForbiddenPage/ForbiddenPage';
|
|
2
|
+
import { isUserGlobalAdmin } from '../../../utils/isUserGlobalAdmin';
|
|
3
|
+
import { CliAccessClient } from './CliAccessClient';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Super-admin page for raw browser-based access to the VPS shell.
|
|
7
|
+
*/
|
|
8
|
+
export default async function CliAccessPage() {
|
|
9
|
+
if (!(await isUserGlobalAdmin())) {
|
|
10
|
+
return <ForbiddenPage />;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return <CliAccessClient />;
|
|
14
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { Loader2,
|
|
4
|
-
import { useCallback, useEffect,
|
|
3
|
+
import { Loader2, Save, ServerCog } from 'lucide-react';
|
|
4
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
5
|
+
import { AdminTerminalCard } from '../../../components/AdminTerminal/AdminTerminalCard';
|
|
6
|
+
import { useAdminTerminalSession } from '../../../components/AdminTerminal/useAdminTerminalSession';
|
|
5
7
|
import { Card } from '../../../components/Homepage/Card';
|
|
6
8
|
|
|
7
9
|
/**
|
|
@@ -33,14 +35,6 @@ type CodeRunnerAuthenticationSession = {
|
|
|
33
35
|
readonly signal: string | null;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
|
-
/**
|
|
37
|
-
* Authentication session API response.
|
|
38
|
-
*/
|
|
39
|
-
type CodeRunnerAuthenticationResponse = {
|
|
40
|
-
readonly session: CodeRunnerAuthenticationSession | null;
|
|
41
|
-
readonly error?: string;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
38
|
/**
|
|
45
39
|
* Supported runner options shown by the standalone UI.
|
|
46
40
|
*/
|
|
@@ -84,29 +78,39 @@ export function CodeRunnersClient() {
|
|
|
84
78
|
const [status, setStatus] = useState('');
|
|
85
79
|
const [isLoading, setIsLoading] = useState(true);
|
|
86
80
|
const [isSaving, setIsSaving] = useState(false);
|
|
87
|
-
const [
|
|
88
|
-
const [
|
|
89
|
-
const [isStoppingAuthentication, setIsStoppingAuthentication] = useState(false);
|
|
90
|
-
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
91
|
-
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
|
81
|
+
const [configurationErrorMessage, setConfigurationErrorMessage] = useState<string | null>(null);
|
|
82
|
+
const [configurationSuccessMessage, setConfigurationSuccessMessage] = useState<string | null>(null);
|
|
92
83
|
const [applyOutput, setApplyOutput] = useState<string | null>(null);
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
84
|
+
const authenticationTerminal = useAdminTerminalSession<CodeRunnerAuthenticationSession>({
|
|
85
|
+
basePath: '/api/admin/code-runners/authentication',
|
|
86
|
+
loadErrorMessage: 'Failed to load the authentication session.',
|
|
87
|
+
startErrorMessage: 'Failed to start the authentication session.',
|
|
88
|
+
sendErrorMessage: 'Failed to send authentication input.',
|
|
89
|
+
stopErrorMessage: 'Failed to stop the authentication session.',
|
|
90
|
+
startSuccessMessage: 'Runner authentication terminal started.',
|
|
91
|
+
finishSuccessMessage: 'Runner authentication finished.',
|
|
92
|
+
finishErrorMessage: 'Runner authentication session ended with an error.',
|
|
93
|
+
});
|
|
94
|
+
const {
|
|
95
|
+
session: authenticationSession,
|
|
96
|
+
input: authenticationInput,
|
|
97
|
+
setInput: setAuthenticationInput,
|
|
98
|
+
isStarting: isStartingAuthentication,
|
|
99
|
+
isSending: isSendingAuthenticationInput,
|
|
100
|
+
isStopping: isStoppingAuthentication,
|
|
101
|
+
errorMessage: authenticationErrorMessage,
|
|
102
|
+
successMessage: authenticationSuccessMessage,
|
|
103
|
+
clearMessages: clearAuthenticationMessages,
|
|
104
|
+
loadSession: loadAuthenticationSession,
|
|
105
|
+
startSession: startAuthenticationSession,
|
|
106
|
+
sendInput: sendAuthenticationInput,
|
|
107
|
+
stopSession: stopAuthenticationSession,
|
|
108
|
+
} = authenticationTerminal;
|
|
109
|
+
const authenticationHint =
|
|
110
|
+
AUTHENTICATION_HINTS[agent] ||
|
|
111
|
+
'Start the terminal, complete the runner authentication flow there, and exit the CLI when the runner is ready.';
|
|
112
|
+
const errorMessage = configurationErrorMessage ?? authenticationErrorMessage;
|
|
113
|
+
const successMessage = configurationSuccessMessage ?? authenticationSuccessMessage;
|
|
110
114
|
|
|
111
115
|
/**
|
|
112
116
|
* Loads current code-runner settings.
|
|
@@ -114,7 +118,7 @@ export function CodeRunnersClient() {
|
|
|
114
118
|
const loadConfiguration = useCallback(async (): Promise<void> => {
|
|
115
119
|
try {
|
|
116
120
|
setIsLoading(true);
|
|
117
|
-
|
|
121
|
+
setConfigurationErrorMessage(null);
|
|
118
122
|
|
|
119
123
|
const response = await fetch('/api/admin/code-runners', { cache: 'no-store' });
|
|
120
124
|
const payload = (await response.json()) as CodeRunnersResponse;
|
|
@@ -130,7 +134,9 @@ export function CodeRunnersClient() {
|
|
|
130
134
|
|
|
131
135
|
await loadAuthenticationSession();
|
|
132
136
|
} catch (error) {
|
|
133
|
-
|
|
137
|
+
setConfigurationErrorMessage(
|
|
138
|
+
error instanceof Error ? error.message : 'Failed to load code-runner configuration.',
|
|
139
|
+
);
|
|
134
140
|
} finally {
|
|
135
141
|
setIsLoading(false);
|
|
136
142
|
}
|
|
@@ -141,72 +147,12 @@ export function CodeRunnersClient() {
|
|
|
141
147
|
}, [loadConfiguration]);
|
|
142
148
|
|
|
143
149
|
useEffect(() => {
|
|
144
|
-
|
|
145
|
-
if (!sessionId) {
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const eventSource = new EventSource(
|
|
150
|
-
`/api/admin/code-runners/authentication?sessionId=${encodeURIComponent(sessionId)}&stream=1`,
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
const handleSnapshot = (event: MessageEvent<string>) => {
|
|
154
|
-
const payload = JSON.parse(event.data) as CodeRunnerAuthenticationSession;
|
|
155
|
-
setAuthenticationSession(payload);
|
|
156
|
-
};
|
|
157
|
-
const handleOutput = (event: MessageEvent<string>) => {
|
|
158
|
-
const payload = JSON.parse(event.data) as { readonly chunk: string };
|
|
159
|
-
setAuthenticationSession((currentSession) => {
|
|
160
|
-
if (!currentSession || currentSession.id !== sessionId) {
|
|
161
|
-
return currentSession;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return {
|
|
165
|
-
...currentSession,
|
|
166
|
-
output: currentSession.output + payload.chunk,
|
|
167
|
-
};
|
|
168
|
-
});
|
|
169
|
-
};
|
|
170
|
-
const handleExit = (event: MessageEvent<string>) => {
|
|
171
|
-
const payload = JSON.parse(event.data) as CodeRunnerAuthenticationSession;
|
|
172
|
-
setAuthenticationSession(payload);
|
|
173
|
-
setIsStartingAuthentication(false);
|
|
174
|
-
setIsSendingAuthenticationInput(false);
|
|
175
|
-
setIsStoppingAuthentication(false);
|
|
176
|
-
|
|
177
|
-
if (payload.exitCode === 0) {
|
|
178
|
-
setSuccessMessage('Runner authentication finished.');
|
|
179
|
-
} else {
|
|
180
|
-
setErrorMessage('Runner authentication session ended with an error.');
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
eventSource.close();
|
|
184
|
-
void loadConfiguration();
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
eventSource.addEventListener('snapshot', handleSnapshot as EventListener);
|
|
188
|
-
eventSource.addEventListener('output', handleOutput as EventListener);
|
|
189
|
-
eventSource.addEventListener('exit', handleExit as EventListener);
|
|
190
|
-
eventSource.onerror = () => {
|
|
191
|
-
eventSource.close();
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
return () => {
|
|
195
|
-
eventSource.close();
|
|
196
|
-
};
|
|
197
|
-
}, [authenticationSession?.id, loadConfiguration]);
|
|
198
|
-
|
|
199
|
-
useEffect(() => {
|
|
200
|
-
if (!authenticationOutputReference.current) {
|
|
150
|
+
if (!authenticationSession?.finishedAt) {
|
|
201
151
|
return;
|
|
202
152
|
}
|
|
203
153
|
|
|
204
|
-
|
|
205
|
-
}, [authenticationSession?.
|
|
206
|
-
|
|
207
|
-
const authenticationHint =
|
|
208
|
-
AUTHENTICATION_HINTS[agent] ||
|
|
209
|
-
'Start the terminal, complete the runner authentication flow there, and exit the CLI when the runner is ready.';
|
|
154
|
+
void loadConfiguration();
|
|
155
|
+
}, [authenticationSession?.finishedAt, loadConfiguration]);
|
|
210
156
|
|
|
211
157
|
/**
|
|
212
158
|
* Saves code-runner settings into `.env`.
|
|
@@ -214,8 +160,9 @@ export function CodeRunnersClient() {
|
|
|
214
160
|
async function saveConfiguration(applyRuntimeConfiguration: boolean): Promise<void> {
|
|
215
161
|
try {
|
|
216
162
|
setIsSaving(true);
|
|
217
|
-
|
|
218
|
-
|
|
163
|
+
setConfigurationErrorMessage(null);
|
|
164
|
+
setConfigurationSuccessMessage(null);
|
|
165
|
+
clearAuthenticationMessages();
|
|
219
166
|
setApplyOutput(null);
|
|
220
167
|
|
|
221
168
|
const response = await fetch('/api/admin/code-runners', {
|
|
@@ -236,120 +183,20 @@ export function CodeRunnersClient() {
|
|
|
236
183
|
setThinkingLevel(payload.thinkingLevel || thinkingLevel);
|
|
237
184
|
setStatus(payload.status || '');
|
|
238
185
|
setApplyOutput(payload.applyResult?.output || null);
|
|
239
|
-
|
|
186
|
+
setConfigurationSuccessMessage(
|
|
240
187
|
applyRuntimeConfiguration
|
|
241
188
|
? 'Code-runner configuration was saved and applied.'
|
|
242
189
|
: 'Code-runner configuration was saved.',
|
|
243
190
|
);
|
|
244
191
|
} catch (error) {
|
|
245
|
-
|
|
192
|
+
setConfigurationErrorMessage(
|
|
193
|
+
error instanceof Error ? error.message : 'Failed to save code-runner configuration.',
|
|
194
|
+
);
|
|
246
195
|
} finally {
|
|
247
196
|
setIsSaving(false);
|
|
248
197
|
}
|
|
249
198
|
}
|
|
250
199
|
|
|
251
|
-
/**
|
|
252
|
-
* Starts or reconnects to the saved-runner authentication terminal.
|
|
253
|
-
*/
|
|
254
|
-
async function startAuthenticationSession(): Promise<void> {
|
|
255
|
-
try {
|
|
256
|
-
setIsStartingAuthentication(true);
|
|
257
|
-
setErrorMessage(null);
|
|
258
|
-
setSuccessMessage(null);
|
|
259
|
-
|
|
260
|
-
const response = await fetch('/api/admin/code-runners/authentication', {
|
|
261
|
-
method: 'POST',
|
|
262
|
-
});
|
|
263
|
-
const payload = (await response.json()) as CodeRunnerAuthenticationResponse;
|
|
264
|
-
|
|
265
|
-
if (!response.ok || !payload.session) {
|
|
266
|
-
throw new Error(payload.error || 'Failed to start the authentication session.');
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
setAuthenticationSession(payload.session);
|
|
270
|
-
setSuccessMessage('Runner authentication terminal started.');
|
|
271
|
-
} catch (error) {
|
|
272
|
-
setErrorMessage(error instanceof Error ? error.message : 'Failed to start the authentication session.');
|
|
273
|
-
} finally {
|
|
274
|
-
setIsStartingAuthentication(false);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Sends one line of text to the running authentication terminal.
|
|
280
|
-
*/
|
|
281
|
-
async function sendAuthenticationInput(input: string): Promise<void> {
|
|
282
|
-
if (!authenticationSession) {
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
try {
|
|
287
|
-
setIsSendingAuthenticationInput(true);
|
|
288
|
-
setErrorMessage(null);
|
|
289
|
-
|
|
290
|
-
const response = await fetch('/api/admin/code-runners/authentication', {
|
|
291
|
-
method: 'PATCH',
|
|
292
|
-
headers: {
|
|
293
|
-
'Content-Type': 'application/json',
|
|
294
|
-
},
|
|
295
|
-
body: JSON.stringify({
|
|
296
|
-
sessionId: authenticationSession.id,
|
|
297
|
-
input,
|
|
298
|
-
}),
|
|
299
|
-
});
|
|
300
|
-
const payload = (await response.json()) as CodeRunnerAuthenticationResponse;
|
|
301
|
-
|
|
302
|
-
if (!response.ok) {
|
|
303
|
-
throw new Error(payload.error || 'Failed to send authentication input.');
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (payload.session) {
|
|
307
|
-
setAuthenticationSession(payload.session);
|
|
308
|
-
}
|
|
309
|
-
} catch (error) {
|
|
310
|
-
setErrorMessage(error instanceof Error ? error.message : 'Failed to send authentication input.');
|
|
311
|
-
} finally {
|
|
312
|
-
setIsSendingAuthenticationInput(false);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Stops the active authentication terminal.
|
|
318
|
-
*/
|
|
319
|
-
async function stopAuthenticationSession(): Promise<void> {
|
|
320
|
-
if (!authenticationSession) {
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
try {
|
|
325
|
-
setIsStoppingAuthentication(true);
|
|
326
|
-
setErrorMessage(null);
|
|
327
|
-
|
|
328
|
-
const response = await fetch('/api/admin/code-runners/authentication', {
|
|
329
|
-
method: 'DELETE',
|
|
330
|
-
headers: {
|
|
331
|
-
'Content-Type': 'application/json',
|
|
332
|
-
},
|
|
333
|
-
body: JSON.stringify({
|
|
334
|
-
sessionId: authenticationSession.id,
|
|
335
|
-
}),
|
|
336
|
-
});
|
|
337
|
-
const payload = (await response.json()) as CodeRunnerAuthenticationResponse;
|
|
338
|
-
|
|
339
|
-
if (!response.ok) {
|
|
340
|
-
throw new Error(payload.error || 'Failed to stop the authentication session.');
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (payload.session) {
|
|
344
|
-
setAuthenticationSession(payload.session);
|
|
345
|
-
}
|
|
346
|
-
} catch (error) {
|
|
347
|
-
setErrorMessage(error instanceof Error ? error.message : 'Failed to stop the authentication session.');
|
|
348
|
-
} finally {
|
|
349
|
-
setIsStoppingAuthentication(false);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
200
|
return (
|
|
354
201
|
<div className="container mx-auto space-y-6 px-4 py-8">
|
|
355
202
|
<div className="mt-20">
|
|
@@ -437,128 +284,32 @@ export function CodeRunnersClient() {
|
|
|
437
284
|
</pre>
|
|
438
285
|
) : null}
|
|
439
286
|
|
|
440
|
-
<
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
type="button"
|
|
467
|
-
onClick={() => void stopAuthenticationSession()}
|
|
468
|
-
disabled={!authenticationSession?.isRunning || isStoppingAuthentication}
|
|
469
|
-
className="inline-flex items-center gap-2 rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-60"
|
|
470
|
-
>
|
|
471
|
-
{isStoppingAuthentication ? (
|
|
472
|
-
<Loader2 className="h-4 w-4 animate-spin" />
|
|
473
|
-
) : (
|
|
474
|
-
<SquareTerminal className="h-4 w-4" />
|
|
475
|
-
)}
|
|
476
|
-
Stop terminal
|
|
477
|
-
</button>
|
|
478
|
-
</div>
|
|
479
|
-
|
|
480
|
-
<pre className="max-h-64 overflow-auto rounded-xl border border-slate-200 bg-slate-950 p-4 text-xs text-slate-100">
|
|
481
|
-
{status || 'Runner status was not available.'}
|
|
482
|
-
</pre>
|
|
483
|
-
|
|
484
|
-
<div className="space-y-2">
|
|
485
|
-
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
486
|
-
<h3 className="text-sm font-semibold text-slate-700">Live authentication terminal</h3>
|
|
487
|
-
{authenticationSession ? (
|
|
488
|
-
<span className="text-xs text-slate-500">
|
|
489
|
-
{authenticationSession.isRunning
|
|
490
|
-
? 'Running'
|
|
491
|
-
: authenticationSession.exitCode === 0
|
|
492
|
-
? 'Finished successfully'
|
|
493
|
-
: 'Finished with an error'}
|
|
494
|
-
</span>
|
|
495
|
-
) : (
|
|
496
|
-
<span className="text-xs text-slate-500">No session started yet.</span>
|
|
497
|
-
)}
|
|
498
|
-
</div>
|
|
499
|
-
<pre
|
|
500
|
-
ref={authenticationOutputReference}
|
|
501
|
-
className="max-h-96 overflow-auto rounded-xl border border-slate-200 bg-slate-950 p-4 text-xs text-slate-100"
|
|
502
|
-
>
|
|
503
|
-
{authenticationSession?.output ||
|
|
504
|
-
'No authentication session output yet. Start the saved-runner terminal to see the live authentication log here.'}
|
|
505
|
-
</pre>
|
|
506
|
-
</div>
|
|
507
|
-
|
|
508
|
-
<form
|
|
509
|
-
className="flex flex-col gap-3 md:flex-row"
|
|
510
|
-
onSubmit={(event) => {
|
|
511
|
-
event.preventDefault();
|
|
512
|
-
|
|
513
|
-
if (!authenticationInput.trim()) {
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
const input = authenticationInput.endsWith('\n')
|
|
518
|
-
? authenticationInput
|
|
519
|
-
: `${authenticationInput}\n`;
|
|
520
|
-
|
|
521
|
-
void sendAuthenticationInput(input);
|
|
522
|
-
setAuthenticationInput('');
|
|
523
|
-
}}
|
|
524
|
-
>
|
|
525
|
-
<input
|
|
526
|
-
type="text"
|
|
527
|
-
value={authenticationInput}
|
|
528
|
-
onChange={(event) => setAuthenticationInput(event.target.value)}
|
|
529
|
-
disabled={!authenticationSession?.isRunning || isSendingAuthenticationInput}
|
|
530
|
-
placeholder="Type a terminal command such as /login and send it to the running runner CLI"
|
|
531
|
-
className={INPUT_CLASS_NAME}
|
|
532
|
-
/>
|
|
533
|
-
<div className="flex gap-3">
|
|
534
|
-
<button
|
|
535
|
-
type="submit"
|
|
536
|
-
disabled={
|
|
537
|
-
!authenticationSession?.isRunning ||
|
|
538
|
-
isSendingAuthenticationInput ||
|
|
539
|
-
authenticationInput.trim() === ''
|
|
540
|
-
}
|
|
541
|
-
className="inline-flex items-center justify-center gap-2 rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60"
|
|
542
|
-
>
|
|
543
|
-
{isSendingAuthenticationInput ? (
|
|
544
|
-
<Loader2 className="h-4 w-4 animate-spin" />
|
|
545
|
-
) : (
|
|
546
|
-
<Send className="h-4 w-4" />
|
|
547
|
-
)}
|
|
548
|
-
Send
|
|
549
|
-
</button>
|
|
550
|
-
<button
|
|
551
|
-
type="button"
|
|
552
|
-
onClick={() => void sendAuthenticationInput('\n')}
|
|
553
|
-
disabled={!authenticationSession?.isRunning || isSendingAuthenticationInput}
|
|
554
|
-
className="inline-flex items-center justify-center rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-60"
|
|
555
|
-
>
|
|
556
|
-
Send Enter
|
|
557
|
-
</button>
|
|
558
|
-
</div>
|
|
559
|
-
</form>
|
|
560
|
-
</div>
|
|
561
|
-
</Card>
|
|
287
|
+
<AdminTerminalCard
|
|
288
|
+
title="Authentication"
|
|
289
|
+
description="Save runner changes first if you want to authenticate a different CLI, then start the saved-runner terminal here instead of SSHing into the VPS."
|
|
290
|
+
hint={authenticationHint}
|
|
291
|
+
session={authenticationSession}
|
|
292
|
+
input={authenticationInput}
|
|
293
|
+
onInputChange={setAuthenticationInput}
|
|
294
|
+
onStart={() => void startAuthenticationSession()}
|
|
295
|
+
onStop={() => void stopAuthenticationSession()}
|
|
296
|
+
onSend={(input) => void sendAuthenticationInput(input)}
|
|
297
|
+
isLoading={isLoading}
|
|
298
|
+
isStarting={isStartingAuthentication}
|
|
299
|
+
isSending={isSendingAuthenticationInput}
|
|
300
|
+
isStopping={isStoppingAuthentication}
|
|
301
|
+
startLabel="Authenticate saved runner"
|
|
302
|
+
runningLabel="Authentication running"
|
|
303
|
+
stopLabel="Stop terminal"
|
|
304
|
+
outputLabel="Live authentication terminal"
|
|
305
|
+
outputEmptyState="No authentication session output yet. Start the saved-runner terminal to see the live authentication log here."
|
|
306
|
+
inputPlaceholder="Type a terminal command such as /login and send it to the running runner CLI"
|
|
307
|
+
quickActions={[{ label: 'Send Enter', input: '\n' }]}
|
|
308
|
+
>
|
|
309
|
+
<pre className="max-h-64 overflow-auto rounded-xl border border-slate-200 bg-slate-950 p-4 text-xs text-slate-100">
|
|
310
|
+
{status || 'Runner status was not available.'}
|
|
311
|
+
</pre>
|
|
312
|
+
</AdminTerminalCard>
|
|
562
313
|
</div>
|
|
563
314
|
);
|
|
564
315
|
}
|
|
@@ -12,8 +12,7 @@ export default async function DatabaseAdminPage() {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const databaseMode = resolveAgentsServerDatabaseMode();
|
|
15
|
-
const databaseModeLabel =
|
|
16
|
-
databaseMode === 'sqlite' ? 'SQLite' : databaseMode === 'postgres' ? 'PostgreSQL' : 'Supabase';
|
|
15
|
+
const databaseModeLabel = databaseMode === 'sqlite' ? 'SQLite' : 'Supabase';
|
|
17
16
|
|
|
18
17
|
return (
|
|
19
18
|
<div className="flex h-[calc(100vh-60px)] flex-col overflow-hidden bg-white text-gray-900 dark:bg-gray-950 dark:text-gray-100">
|