@inkeep/agents-core 0.49.0 → 0.50.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/auth-schema.d.ts +105 -105
- package/dist/auth/auth-validation-schemas.d.ts +148 -148
- package/dist/auth/auth.d.ts +22 -22
- package/dist/auth/permissions.d.ts +9 -9
- package/dist/client-exports.d.ts +2 -2
- package/dist/data-access/manage/agents.d.ts +11 -11
- package/dist/data-access/manage/agents.js +8 -6
- package/dist/data-access/manage/artifactComponents.d.ts +6 -6
- package/dist/data-access/manage/artifactComponents.js +3 -3
- package/dist/data-access/manage/contextConfigs.d.ts +8 -8
- package/dist/data-access/manage/dataComponents.d.ts +2 -2
- package/dist/data-access/manage/dataComponents.js +2 -2
- package/dist/data-access/manage/functionTools.d.ts +8 -8
- package/dist/data-access/manage/skills.d.ts +11 -11
- package/dist/data-access/manage/subAgentExternalAgentRelations.d.ts +12 -12
- package/dist/data-access/manage/subAgentRelations.d.ts +4 -4
- package/dist/data-access/manage/subAgentRelations.js +2 -2
- package/dist/data-access/manage/subAgentTeamAgentRelations.d.ts +12 -12
- package/dist/data-access/manage/subAgents.d.ts +6 -6
- package/dist/data-access/manage/tools.d.ts +12 -12
- package/dist/data-access/runtime/apiKeys.d.ts +16 -16
- package/dist/data-access/runtime/conversations.d.ts +31 -31
- package/dist/data-access/runtime/messages.d.ts +18 -18
- package/dist/data-access/runtime/scheduledTriggerInvocations.d.ts +3 -3
- package/dist/data-access/runtime/tasks.d.ts +9 -9
- package/dist/db/manage/manage-schema.d.ts +445 -445
- package/dist/db/runtime/runtime-schema.d.ts +298 -298
- package/dist/setup/index.d.ts +2 -0
- package/dist/setup/index.js +3 -0
- package/dist/setup/setup.d.ts +24 -0
- package/dist/setup/setup.js +506 -0
- package/dist/validation/dolt-schemas.d.ts +1 -1
- package/dist/validation/drizzle-schema-helpers.d.ts +3 -3
- package/dist/validation/schemas.d.ts +1792 -1792
- package/package.json +5 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/setup/setup.d.ts
|
|
2
|
+
interface SetupPushConfig {
|
|
3
|
+
projectPath: string;
|
|
4
|
+
configPath: string;
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
}
|
|
7
|
+
interface SetupConfig {
|
|
8
|
+
dockerComposeFile: string;
|
|
9
|
+
manageMigrateCommand: string;
|
|
10
|
+
runMigrateCommand: string;
|
|
11
|
+
authInitCommand: string;
|
|
12
|
+
pushProject?: SetupPushConfig;
|
|
13
|
+
devApiCommand?: string;
|
|
14
|
+
devUiCommand?: string;
|
|
15
|
+
apiHealthUrl?: string;
|
|
16
|
+
uiHealthUrl?: string;
|
|
17
|
+
isCloud?: boolean;
|
|
18
|
+
skipPush?: boolean;
|
|
19
|
+
/** If set, upgrades packages instead of just migrating on subsequent runs */
|
|
20
|
+
upgradeCommand?: string;
|
|
21
|
+
}
|
|
22
|
+
declare function runSetup(config: SetupConfig): Promise<void>;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { SetupConfig, SetupPushConfig, runSetup };
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import { loadEnvironmentFiles } from "../env.js";
|
|
2
|
+
import { copyFileSync, existsSync, writeFileSync } from "node:fs";
|
|
3
|
+
import dotenv from "dotenv";
|
|
4
|
+
import { generateKeyPairSync } from "node:crypto";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { exec, spawn } from "node:child_process";
|
|
7
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
8
|
+
|
|
9
|
+
//#region src/setup/setup.ts
|
|
10
|
+
const colors = {
|
|
11
|
+
reset: "\x1B[0m",
|
|
12
|
+
bright: "\x1B[1m",
|
|
13
|
+
dim: "\x1B[2m",
|
|
14
|
+
green: "\x1B[32m",
|
|
15
|
+
yellow: "\x1B[33m",
|
|
16
|
+
red: "\x1B[31m",
|
|
17
|
+
blue: "\x1B[34m",
|
|
18
|
+
cyan: "\x1B[36m"
|
|
19
|
+
};
|
|
20
|
+
function logStep(step, message) {
|
|
21
|
+
console.log(`${colors.bright}${colors.blue}[Step ${step}]${colors.reset} ${message}`);
|
|
22
|
+
}
|
|
23
|
+
function logSuccess(message) {
|
|
24
|
+
console.log(`${colors.green}✓${colors.reset} ${message}`);
|
|
25
|
+
}
|
|
26
|
+
function logError(message, error) {
|
|
27
|
+
console.error(`${colors.red}✗ ${message}${colors.reset}`);
|
|
28
|
+
if (error) {
|
|
29
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
30
|
+
console.error(`${colors.dim} Error details: ${msg}${colors.reset}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function logWarning(message) {
|
|
34
|
+
console.warn(`${colors.yellow}⚠${colors.reset} ${message}`);
|
|
35
|
+
}
|
|
36
|
+
function logInfo(message) {
|
|
37
|
+
console.log(`${colors.cyan}ℹ${colors.reset} ${message}`);
|
|
38
|
+
}
|
|
39
|
+
const execAsync = promisify(exec);
|
|
40
|
+
function isLocalDatabaseUrl(url) {
|
|
41
|
+
if (!url) return false;
|
|
42
|
+
return /[@/](localhost|127\.0\.0\.1)[:/]/.test(url);
|
|
43
|
+
}
|
|
44
|
+
function hasExternalDatabases() {
|
|
45
|
+
const manageUrl = process.env.INKEEP_AGENTS_MANAGE_DATABASE_URL;
|
|
46
|
+
const runUrl = process.env.INKEEP_AGENTS_RUN_DATABASE_URL;
|
|
47
|
+
if (!manageUrl || !runUrl) return false;
|
|
48
|
+
if (process.env.CI) return true;
|
|
49
|
+
return !isLocalDatabaseUrl(manageUrl) || !isLocalDatabaseUrl(runUrl);
|
|
50
|
+
}
|
|
51
|
+
const REQUIRED_PORTS = [
|
|
52
|
+
{
|
|
53
|
+
port: 5432,
|
|
54
|
+
service: "DoltgreSQL"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
port: 5433,
|
|
58
|
+
service: "PostgreSQL"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
port: 5434,
|
|
62
|
+
service: "SpiceDB PostgreSQL"
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
async function checkPort(port) {
|
|
66
|
+
try {
|
|
67
|
+
await execAsync(`lsof -i :${port} -sTCP:LISTEN -t`, { timeout: 5e3 });
|
|
68
|
+
return true;
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function getContainerOnPort(port) {
|
|
74
|
+
try {
|
|
75
|
+
const { stdout } = await execAsync(`docker ps --format '{{.Names}}' --filter "publish=${port}"`, { timeout: 5e3 });
|
|
76
|
+
const name = stdout.trim();
|
|
77
|
+
if (!name) return null;
|
|
78
|
+
let project = null;
|
|
79
|
+
try {
|
|
80
|
+
const { stdout: labelOut } = await execAsync(`docker inspect ${name} --format '{{index .Config.Labels "com.docker.compose.project"}}'`, { timeout: 5e3 });
|
|
81
|
+
project = labelOut.trim() || null;
|
|
82
|
+
} catch {}
|
|
83
|
+
return {
|
|
84
|
+
name,
|
|
85
|
+
project
|
|
86
|
+
};
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function runPreflightChecks(composeFile) {
|
|
92
|
+
const issues = [];
|
|
93
|
+
const fixes = [];
|
|
94
|
+
try {
|
|
95
|
+
await execAsync("docker info", { timeout: 1e4 });
|
|
96
|
+
} catch {
|
|
97
|
+
if (hasExternalDatabases()) {
|
|
98
|
+
logWarning("Docker is not available, but database URLs point to external hosts");
|
|
99
|
+
logInfo("Assuming databases are managed externally.");
|
|
100
|
+
return {
|
|
101
|
+
passed: true,
|
|
102
|
+
dockerAvailable: false,
|
|
103
|
+
portsAvailable: false,
|
|
104
|
+
composeCmd: ""
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
logError("Docker is not running or not installed.");
|
|
108
|
+
logInfo(" Start Docker Desktop, or install Docker: https://docs.docker.com/get-docker/");
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
let composeCmd = "docker-compose";
|
|
112
|
+
try {
|
|
113
|
+
await execAsync("docker-compose version", { timeout: 5e3 });
|
|
114
|
+
} catch {
|
|
115
|
+
try {
|
|
116
|
+
await execAsync("docker compose version", { timeout: 5e3 });
|
|
117
|
+
composeCmd = "docker compose";
|
|
118
|
+
} catch {
|
|
119
|
+
if (hasExternalDatabases()) {
|
|
120
|
+
logWarning("Docker Compose not available, but database URLs point to external hosts");
|
|
121
|
+
logInfo("Assuming databases are managed externally.");
|
|
122
|
+
return {
|
|
123
|
+
passed: true,
|
|
124
|
+
dockerAvailable: true,
|
|
125
|
+
portsAvailable: false,
|
|
126
|
+
composeCmd
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
logError("Docker Compose is not installed.");
|
|
130
|
+
logInfo(" Install it: https://docs.docker.com/compose/install/");
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
let ownProjectPrefix;
|
|
135
|
+
try {
|
|
136
|
+
const { stdout } = await execAsync(`${composeCmd} -f ${composeFile} config --format json`, { timeout: 1e4 });
|
|
137
|
+
ownProjectPrefix = (JSON.parse(stdout).name || "").replace(/[^a-z0-9]/g, "");
|
|
138
|
+
} catch {
|
|
139
|
+
ownProjectPrefix = (process.cwd().split("/").pop() || "").replace(/[^a-z0-9]/g, "").toLowerCase();
|
|
140
|
+
}
|
|
141
|
+
const occupiedPorts = [];
|
|
142
|
+
await Promise.all(REQUIRED_PORTS.map(async ({ port, service }) => {
|
|
143
|
+
if (!await checkPort(port)) return;
|
|
144
|
+
const info = await getContainerOnPort(port);
|
|
145
|
+
if (!info) return;
|
|
146
|
+
if (info.name.replace(/[^a-z0-9]/g, "").toLowerCase().startsWith(ownProjectPrefix)) return;
|
|
147
|
+
occupiedPorts.push({
|
|
148
|
+
port,
|
|
149
|
+
service,
|
|
150
|
+
container: info.name,
|
|
151
|
+
project: info.project
|
|
152
|
+
});
|
|
153
|
+
}));
|
|
154
|
+
if (occupiedPorts.length > 0) {
|
|
155
|
+
issues.push(`Port${occupiedPorts.length > 1 ? "s" : ""} occupied by another Docker project:`);
|
|
156
|
+
for (const { port, service, container } of occupiedPorts) issues.push(` :${port} (${service}) → ${container}`);
|
|
157
|
+
const projectNames = new Set(occupiedPorts.map(({ project }) => project).filter((p) => p !== null));
|
|
158
|
+
for (const project of projectNames) fixes.push(` docker compose -p ${project} down`);
|
|
159
|
+
const standaloneContainers = occupiedPorts.filter(({ project }) => project === null).map(({ container }) => container);
|
|
160
|
+
for (const name of standaloneContainers) fixes.push(` docker rm -f ${name}`);
|
|
161
|
+
}
|
|
162
|
+
if (issues.length > 0) {
|
|
163
|
+
logWarning("Pre-flight check: port conflicts detected");
|
|
164
|
+
for (const issue of issues) console.log(`${colors.yellow} ${issue}${colors.reset}`);
|
|
165
|
+
console.log(`${colors.cyan} Fix:${colors.reset}`);
|
|
166
|
+
for (const fix of fixes) console.log(`${colors.cyan} ${fix}${colors.reset}`);
|
|
167
|
+
console.log();
|
|
168
|
+
logInfo("Databases on those ports will be used as-is. Continuing with setup...");
|
|
169
|
+
return {
|
|
170
|
+
passed: true,
|
|
171
|
+
dockerAvailable: true,
|
|
172
|
+
portsAvailable: false,
|
|
173
|
+
composeCmd
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
passed: true,
|
|
178
|
+
dockerAvailable: true,
|
|
179
|
+
portsAvailable: true,
|
|
180
|
+
composeCmd
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const SETUP_COMPLETE_FILE = ".setup-complete";
|
|
184
|
+
async function ensureEnvFile() {
|
|
185
|
+
if (!existsSync(".env") && existsSync(".env.example")) {
|
|
186
|
+
copyFileSync(".env.example", ".env");
|
|
187
|
+
logSuccess("Created .env from .env.example");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function generateJwtKeys() {
|
|
191
|
+
const envContent = await readFile(".env", "utf-8").catch(() => "");
|
|
192
|
+
if (envContent.includes("INKEEP_AGENTS_TEMP_JWT_PRIVATE_KEY=") && !envContent.includes("# INKEEP_AGENTS_TEMP_JWT_PRIVATE_KEY=")) {
|
|
193
|
+
const match = envContent.match(/INKEEP_AGENTS_TEMP_JWT_PRIVATE_KEY=(.+)/);
|
|
194
|
+
if (match && match[1].trim().length > 0) {
|
|
195
|
+
logInfo("JWT keys already configured, skipping generation");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const { privateKey, publicKey } = generateKeyPairSync("rsa", {
|
|
201
|
+
modulusLength: 2048,
|
|
202
|
+
privateKeyEncoding: {
|
|
203
|
+
type: "pkcs8",
|
|
204
|
+
format: "pem"
|
|
205
|
+
},
|
|
206
|
+
publicKeyEncoding: {
|
|
207
|
+
type: "spki",
|
|
208
|
+
format: "pem"
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
const privateKeyBase64 = Buffer.from(privateKey).toString("base64");
|
|
212
|
+
const publicKeyBase64 = Buffer.from(publicKey).toString("base64");
|
|
213
|
+
const lines = envContent.split("\n");
|
|
214
|
+
let privateKeyFound = false;
|
|
215
|
+
let publicKeyFound = false;
|
|
216
|
+
for (let i = 0; i < lines.length; i++) {
|
|
217
|
+
if (lines[i].startsWith("# INKEEP_AGENTS_TEMP_JWT_PRIVATE_KEY=") || lines[i].startsWith("INKEEP_AGENTS_TEMP_JWT_PRIVATE_KEY=")) {
|
|
218
|
+
lines[i] = `INKEEP_AGENTS_TEMP_JWT_PRIVATE_KEY=${privateKeyBase64}`;
|
|
219
|
+
privateKeyFound = true;
|
|
220
|
+
}
|
|
221
|
+
if (lines[i].startsWith("# INKEEP_AGENTS_TEMP_JWT_PUBLIC_KEY=") || lines[i].startsWith("INKEEP_AGENTS_TEMP_JWT_PUBLIC_KEY=")) {
|
|
222
|
+
lines[i] = `INKEEP_AGENTS_TEMP_JWT_PUBLIC_KEY=${publicKeyBase64}`;
|
|
223
|
+
publicKeyFound = true;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (!privateKeyFound) lines.push(`INKEEP_AGENTS_TEMP_JWT_PRIVATE_KEY=${privateKeyBase64}`);
|
|
227
|
+
if (!publicKeyFound) lines.push(`INKEEP_AGENTS_TEMP_JWT_PUBLIC_KEY=${publicKeyBase64}`);
|
|
228
|
+
await writeFile(".env", lines.join("\n"));
|
|
229
|
+
logSuccess("JWT keys generated and added to .env");
|
|
230
|
+
} catch (error) {
|
|
231
|
+
logError("Failed to generate JWT keys - playground may not work", error);
|
|
232
|
+
logInfo("You can manually run: pnpm run generate-jwt-keys");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async function waitForDockerHealth(composeFile, serviceName, composeCmd, timeout = 3e4) {
|
|
236
|
+
const start = Date.now();
|
|
237
|
+
let lastError = null;
|
|
238
|
+
while (Date.now() - start < timeout) {
|
|
239
|
+
try {
|
|
240
|
+
const { stdout } = await execAsync(`docker inspect --format='{{.State.Health.Status}}' $(${composeCmd} -f ${composeFile} ps -q ${serviceName})`);
|
|
241
|
+
if (stdout.trim() === "healthy") return;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
lastError = error;
|
|
244
|
+
}
|
|
245
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
246
|
+
}
|
|
247
|
+
const errorMsg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
248
|
+
throw new Error(`${serviceName} not healthy after ${timeout}ms${lastError ? ` (last error: ${errorMsg})` : ""}`);
|
|
249
|
+
}
|
|
250
|
+
async function startDockerDatabases(composeFile, composeCmd) {
|
|
251
|
+
logInfo("Starting database containers...");
|
|
252
|
+
try {
|
|
253
|
+
await execAsync(`${composeCmd} -f ${composeFile} up -d`, { timeout: 12e4 });
|
|
254
|
+
logSuccess("Database containers started");
|
|
255
|
+
} catch (error) {
|
|
256
|
+
const combined = `${error instanceof Error ? error.message : String(error)}\n${error?.stderr || ""}`;
|
|
257
|
+
const isTimeout = error?.killed === true;
|
|
258
|
+
const isPortConflict = !isTimeout && (combined.includes("port is already allocated") || combined.includes("address already in use") || combined.includes("Bind for 0.0.0.0"));
|
|
259
|
+
if (isTimeout) {
|
|
260
|
+
logWarning("docker compose up timed out — containers may still be starting");
|
|
261
|
+
logInfo("Will check health status below...");
|
|
262
|
+
} else if (isPortConflict) {
|
|
263
|
+
logWarning("Database port conflict during startup (databases might already be running)");
|
|
264
|
+
logInfo("Continuing with setup...");
|
|
265
|
+
return;
|
|
266
|
+
} else {
|
|
267
|
+
logError("Failed to start database containers", error);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
logInfo("Polling Docker health status...");
|
|
272
|
+
const [doltgresResult, postgresResult, spicedbResult] = await Promise.allSettled([
|
|
273
|
+
waitForDockerHealth(composeFile, "doltgres-db", composeCmd, 6e4),
|
|
274
|
+
waitForDockerHealth(composeFile, "postgres-db", composeCmd, 3e4),
|
|
275
|
+
waitForDockerHealth(composeFile, "spicedb-postgres", composeCmd, 3e4)
|
|
276
|
+
]);
|
|
277
|
+
if (doltgresResult.status === "fulfilled") logSuccess("DoltgreSQL is healthy");
|
|
278
|
+
else logWarning(`DoltgreSQL health check timed out: ${doltgresResult.reason.message}`);
|
|
279
|
+
if (postgresResult.status === "fulfilled") logSuccess("PostgreSQL is healthy");
|
|
280
|
+
else logWarning(`PostgreSQL health check timed out: ${postgresResult.reason.message}`);
|
|
281
|
+
if (spicedbResult.status === "fulfilled") logSuccess("SpiceDB PostgreSQL is healthy");
|
|
282
|
+
else logWarning(`SpiceDB PostgreSQL health check timed out: ${spicedbResult.reason.message}`);
|
|
283
|
+
if (doltgresResult.status === "rejected" && postgresResult.status === "rejected") {
|
|
284
|
+
logError("Both primary databases failed health checks - cannot proceed");
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
logSuccess("Database health checks complete");
|
|
288
|
+
}
|
|
289
|
+
async function runMigrations(config) {
|
|
290
|
+
const isFirstRun = !existsSync(SETUP_COMPLETE_FILE);
|
|
291
|
+
if (config.upgradeCommand && !isFirstRun && process.env.SKIP_UPGRADE !== "true") {
|
|
292
|
+
logInfo("Running database migrations and upgrading packages");
|
|
293
|
+
try {
|
|
294
|
+
const { stdout } = await execAsync(config.upgradeCommand);
|
|
295
|
+
if (stdout) console.log(`${colors.dim} ${stdout.trim()}${colors.reset}`);
|
|
296
|
+
logSuccess("Upgrades completed");
|
|
297
|
+
} catch (error) {
|
|
298
|
+
logError("Failed to run upgrades", error);
|
|
299
|
+
logWarning("This may cause issues. Consider checking your database schema.");
|
|
300
|
+
}
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
logInfo(isFirstRun ? "Fresh install detected - running migrations only" : "Running migrations...");
|
|
304
|
+
const [manageResult, runResult] = await Promise.allSettled([execAsync(config.manageMigrateCommand), execAsync(config.runMigrateCommand)]);
|
|
305
|
+
if (manageResult.status === "fulfilled") logSuccess("Manage database migrations completed");
|
|
306
|
+
else logWarning(`Manage migrations failed: ${manageResult.reason.message}`);
|
|
307
|
+
if (runResult.status === "fulfilled") logSuccess("Runtime database migrations completed");
|
|
308
|
+
else logWarning(`Runtime migrations failed: ${runResult.reason.message}`);
|
|
309
|
+
if (manageResult.status === "rejected" && runResult.status === "rejected") {
|
|
310
|
+
logError("Both database migrations failed");
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
if (manageResult.status === "fulfilled" && runResult.status === "fulfilled") writeFileSync(SETUP_COMPLETE_FILE, (/* @__PURE__ */ new Date()).toISOString());
|
|
314
|
+
else logWarning(`Partial migration success — ${SETUP_COMPLETE_FILE} not written so next run retries`);
|
|
315
|
+
}
|
|
316
|
+
async function initAuth(authInitCommand) {
|
|
317
|
+
if (process.env.INKEEP_AGENTS_MANAGE_UI_USERNAME && process.env.INKEEP_AGENTS_MANAGE_UI_PASSWORD && process.env.BETTER_AUTH_SECRET) {
|
|
318
|
+
logInfo("Initializing default organization and admin user...");
|
|
319
|
+
try {
|
|
320
|
+
await execAsync(authInitCommand);
|
|
321
|
+
logSuccess("Auth initialization complete");
|
|
322
|
+
} catch (error) {
|
|
323
|
+
logWarning(`Auth initialization failed - you may need to run manually: ${authInitCommand}`);
|
|
324
|
+
logInfo(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
325
|
+
}
|
|
326
|
+
} else {
|
|
327
|
+
logWarning("Skipping auth initialization - credentials not configured");
|
|
328
|
+
logInfo("To create a default admin user, set in .env:");
|
|
329
|
+
logInfo(" INKEEP_AGENTS_MANAGE_UI_USERNAME=admin@example.com");
|
|
330
|
+
logInfo(" INKEEP_AGENTS_MANAGE_UI_PASSWORD=your-password");
|
|
331
|
+
logInfo(" BETTER_AUTH_SECRET=your-secret-key");
|
|
332
|
+
logInfo(`Then run: ${authInitCommand}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
async function checkServerRunning(url, timeout = 5e3) {
|
|
336
|
+
try {
|
|
337
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(timeout) });
|
|
338
|
+
return response.ok || response.status === 204;
|
|
339
|
+
} catch {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
async function waitForServerReady(url, timeout = 18e4) {
|
|
344
|
+
const start = Date.now();
|
|
345
|
+
let lastError = null;
|
|
346
|
+
while (Date.now() - start < timeout) {
|
|
347
|
+
try {
|
|
348
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(5e3) });
|
|
349
|
+
if (response.ok || response.status === 204) return;
|
|
350
|
+
lastError = `HTTP ${response.status}`;
|
|
351
|
+
} catch (error) {
|
|
352
|
+
if (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError")) lastError = "fetch timeout (>5s per attempt)";
|
|
353
|
+
else lastError = error instanceof Error ? error.message : String(error);
|
|
354
|
+
}
|
|
355
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
356
|
+
}
|
|
357
|
+
throw new Error(`Server not ready at ${url} after ${timeout}ms. Last error: ${lastError}`);
|
|
358
|
+
}
|
|
359
|
+
async function startServersIfNeeded(config) {
|
|
360
|
+
const result = {
|
|
361
|
+
startedApi: false,
|
|
362
|
+
startedUi: false,
|
|
363
|
+
apiPid: null,
|
|
364
|
+
uiPid: null
|
|
365
|
+
};
|
|
366
|
+
if (!config.apiHealthUrl) return result;
|
|
367
|
+
if (await checkServerRunning(config.apiHealthUrl)) logSuccess("API server already running");
|
|
368
|
+
else if (config.devApiCommand) {
|
|
369
|
+
logInfo("Starting API server temporarily...");
|
|
370
|
+
const proc = spawn("sh", ["-c", config.devApiCommand], {
|
|
371
|
+
stdio: "ignore",
|
|
372
|
+
detached: true,
|
|
373
|
+
cwd: process.cwd()
|
|
374
|
+
});
|
|
375
|
+
proc.unref();
|
|
376
|
+
result.startedApi = true;
|
|
377
|
+
result.apiPid = proc.pid ?? null;
|
|
378
|
+
logSuccess(`API server process started (PID: ${proc.pid})`);
|
|
379
|
+
}
|
|
380
|
+
if (config.uiHealthUrl) {
|
|
381
|
+
if (await checkServerRunning(config.uiHealthUrl)) logSuccess("Dashboard already running");
|
|
382
|
+
else if (config.devUiCommand) {
|
|
383
|
+
logInfo("Starting Dashboard temporarily...");
|
|
384
|
+
const proc = spawn("sh", ["-c", config.devUiCommand], {
|
|
385
|
+
stdio: "ignore",
|
|
386
|
+
detached: true,
|
|
387
|
+
cwd: process.cwd()
|
|
388
|
+
});
|
|
389
|
+
proc.unref();
|
|
390
|
+
result.startedUi = true;
|
|
391
|
+
result.uiPid = proc.pid ?? null;
|
|
392
|
+
logSuccess(`Dashboard process started (PID: ${proc.pid})`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const waitPromises = [];
|
|
396
|
+
if (result.startedApi && config.apiHealthUrl) {
|
|
397
|
+
logInfo(`Waiting for API at ${config.apiHealthUrl}...`);
|
|
398
|
+
waitPromises.push(waitForServerReady(config.apiHealthUrl));
|
|
399
|
+
}
|
|
400
|
+
if (result.startedUi && config.uiHealthUrl) {
|
|
401
|
+
logInfo(`Waiting for Dashboard at ${config.uiHealthUrl}...`);
|
|
402
|
+
waitPromises.push(waitForServerReady(config.uiHealthUrl));
|
|
403
|
+
}
|
|
404
|
+
if (waitPromises.length > 0) {
|
|
405
|
+
const results = await Promise.allSettled(waitPromises);
|
|
406
|
+
for (const r of results) if (r.status === "rejected") logWarning(`Server readiness check failed: ${r.reason.message}`);
|
|
407
|
+
}
|
|
408
|
+
return result;
|
|
409
|
+
}
|
|
410
|
+
function stopProcessGroup(pid, name) {
|
|
411
|
+
try {
|
|
412
|
+
process.kill(-pid, "SIGTERM");
|
|
413
|
+
} catch {}
|
|
414
|
+
try {
|
|
415
|
+
process.kill(pid, "SIGTERM");
|
|
416
|
+
} catch {}
|
|
417
|
+
setTimeout(() => {
|
|
418
|
+
try {
|
|
419
|
+
process.kill(-pid, "SIGKILL");
|
|
420
|
+
} catch {}
|
|
421
|
+
try {
|
|
422
|
+
process.kill(pid, "SIGKILL");
|
|
423
|
+
} catch {}
|
|
424
|
+
}, 2e3);
|
|
425
|
+
logSuccess(`${name} stopped`);
|
|
426
|
+
}
|
|
427
|
+
async function pushProject(pushConfig) {
|
|
428
|
+
logInfo(`Pushing project: ${pushConfig.projectPath}`);
|
|
429
|
+
const pushEnv = pushConfig.apiKey ? {
|
|
430
|
+
...process.env,
|
|
431
|
+
INKEEP_CI: "true",
|
|
432
|
+
INKEEP_API_KEY: pushConfig.apiKey,
|
|
433
|
+
INKEEP_TENANT_ID: process.env.INKEEP_TENANT_ID || "default"
|
|
434
|
+
} : { ...process.env };
|
|
435
|
+
try {
|
|
436
|
+
const { stdout } = await execAsync(`pnpm inkeep push --project ${pushConfig.projectPath} --config ${pushConfig.configPath}`, { env: pushEnv });
|
|
437
|
+
if (stdout) console.log(`${colors.dim}${stdout.trim()}${colors.reset}`);
|
|
438
|
+
logSuccess("Project pushed successfully");
|
|
439
|
+
return true;
|
|
440
|
+
} catch (error) {
|
|
441
|
+
logError("Project push failed", error);
|
|
442
|
+
logWarning("The project may not have been seeded. You can manually run:");
|
|
443
|
+
logInfo(` pnpm inkeep push --project ${pushConfig.projectPath} --config ${pushConfig.configPath}`);
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
async function runSetup(config) {
|
|
448
|
+
console.log(`\n${colors.bright}=== Project Setup ===${colors.reset}\n`);
|
|
449
|
+
logStep(1, "Checking environment configuration");
|
|
450
|
+
await ensureEnvFile();
|
|
451
|
+
loadEnvironmentFiles();
|
|
452
|
+
dotenv.config();
|
|
453
|
+
if (!config.isCloud) {
|
|
454
|
+
const missing = [];
|
|
455
|
+
if (!process.env.INKEEP_AGENTS_MANAGE_DATABASE_URL) missing.push("INKEEP_AGENTS_MANAGE_DATABASE_URL");
|
|
456
|
+
if (!process.env.INKEEP_AGENTS_RUN_DATABASE_URL) missing.push("INKEEP_AGENTS_RUN_DATABASE_URL");
|
|
457
|
+
if (missing.length > 0) {
|
|
458
|
+
logError("Missing required database environment variables:");
|
|
459
|
+
for (const v of missing) logInfo(` - ${v}`);
|
|
460
|
+
logInfo("Check your .env file and ensure these variables are set.");
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
logStep(2, "Checking JWT keys");
|
|
465
|
+
await generateJwtKeys();
|
|
466
|
+
if (config.isCloud) logStep(3, "Cloud setup: Skipping Docker database startup");
|
|
467
|
+
else {
|
|
468
|
+
logStep(3, "Starting databases with Docker");
|
|
469
|
+
const preflight = await runPreflightChecks(config.dockerComposeFile);
|
|
470
|
+
if (preflight.dockerAvailable && preflight.portsAvailable) await startDockerDatabases(config.dockerComposeFile, preflight.composeCmd);
|
|
471
|
+
else if (preflight.dockerAvailable && !preflight.portsAvailable) logInfo("Skipping Docker startup — using databases on existing ports.");
|
|
472
|
+
}
|
|
473
|
+
logStep(4, "Running database migrations");
|
|
474
|
+
await runMigrations(config);
|
|
475
|
+
logStep(5, "Initializing authentication");
|
|
476
|
+
await initAuth(config.authInitCommand);
|
|
477
|
+
if (config.pushProject && !config.skipPush) {
|
|
478
|
+
const resolvedPush = {
|
|
479
|
+
...config.pushProject,
|
|
480
|
+
apiKey: config.pushProject.apiKey || process.env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET
|
|
481
|
+
};
|
|
482
|
+
logStep(6, "Checking server availability");
|
|
483
|
+
const servers = await startServersIfNeeded(config);
|
|
484
|
+
let pushSuccess = false;
|
|
485
|
+
try {
|
|
486
|
+
logStep(7, "Pushing project to API");
|
|
487
|
+
pushSuccess = await pushProject(resolvedPush);
|
|
488
|
+
} finally {
|
|
489
|
+
if (servers.startedApi || servers.startedUi) {
|
|
490
|
+
logStep(8, "Cleaning up temporarily started servers");
|
|
491
|
+
if (servers.startedApi && servers.apiPid) stopProcessGroup(servers.apiPid, "API server");
|
|
492
|
+
if (servers.startedUi && servers.uiPid) stopProcessGroup(servers.uiPid, "Dashboard");
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
console.log(`\n${colors.bright}=== Setup Complete ===${colors.reset}\n`);
|
|
496
|
+
if (pushSuccess) logSuccess("All steps completed successfully!");
|
|
497
|
+
else logWarning("Setup completed with some warnings. See details above.");
|
|
498
|
+
} else {
|
|
499
|
+
console.log(`\n${colors.bright}=== Setup Complete ===${colors.reset}\n`);
|
|
500
|
+
logSuccess("Database setup completed!");
|
|
501
|
+
if (!config.pushProject) logInfo("No project push configured. Run \"pnpm dev\" to start development servers.");
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
//#endregion
|
|
506
|
+
export { runSetup };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { z } from "@hono/zod-openapi";
|
|
2
|
-
import * as
|
|
2
|
+
import * as drizzle_zod0 from "drizzle-zod";
|
|
3
3
|
import { AnySQLiteTable } from "drizzle-orm/sqlite-core";
|
|
4
4
|
|
|
5
5
|
//#region src/validation/drizzle-schema-helpers.d.ts
|
|
6
|
-
declare function createSelectSchemaWithModifiers<T extends AnySQLiteTable>(table: T, overrides?: Partial<Record<keyof T['_']['columns'], (schema: z.ZodTypeAny) => z.ZodTypeAny>>):
|
|
7
|
-
declare function createInsertSchemaWithModifiers<T extends AnySQLiteTable>(table: T, overrides?: Partial<Record<keyof T['_']['columns'], (schema: z.ZodTypeAny) => z.ZodTypeAny>>):
|
|
6
|
+
declare function createSelectSchemaWithModifiers<T extends AnySQLiteTable>(table: T, overrides?: Partial<Record<keyof T['_']['columns'], (schema: z.ZodTypeAny) => z.ZodTypeAny>>): drizzle_zod0.BuildSchema<"select", T["_"]["columns"], drizzle_zod0.BuildRefine<T["_"]["columns"], undefined>, undefined>;
|
|
7
|
+
declare function createInsertSchemaWithModifiers<T extends AnySQLiteTable>(table: T, overrides?: Partial<Record<keyof T['_']['columns'], (schema: z.ZodTypeAny) => z.ZodTypeAny>>): drizzle_zod0.BuildSchema<"insert", T["_"]["columns"], drizzle_zod0.BuildRefine<Pick<T["_"]["columns"], keyof T["$inferInsert"]>, undefined>, undefined>;
|
|
8
8
|
declare const createSelectSchema: typeof createSelectSchemaWithModifiers;
|
|
9
9
|
declare const createInsertSchema: typeof createInsertSchemaWithModifiers;
|
|
10
10
|
/**
|