@tdk-landscape/tdk-cli-core 1.3.170 → 1.3.172
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/README.md +14 -0
- package/cli/dist/agent-runtimes/custom.js +333 -0
- package/cli/dist/agent-runtimes/registry.js +34 -0
- package/cli/dist/backend-frameworks/registry.js +16 -0
- package/cli/dist/cli.js +4 -0
- package/cli/dist/commands/agent.js +117 -0
- package/cli/dist/commands/completion.js +5 -1
- package/cli/dist/commands/help.js +7 -0
- package/cli/dist/commands/resource.js +54 -14
- package/cli/dist/commands/restart.js +79 -0
- package/cli/dist/mcp/tools.js +68 -13
- package/cli/dist/types/index.js +2 -0
- package/cli/dist/utils/constants.js +4 -0
- package/cli/dist/utils/doctor-report.js +2 -1
- package/cli/dist/utils/doctor-wiring.js +1 -1
- package/cli/dist/utils/json-output.js +2 -1
- package/cli/dist/utils/machine-output.js +2 -1
- package/cli/dist/utils/project-templates.js +4 -0
- package/cli/dist/utils/resource-kind.js +1 -1
- package/cli/dist/utils/service-manifest.js +1 -0
- package/cli/dist/utils/status-ports.js +3 -1
- package/cli/dist-bundle/{chunk-MZBDLTO2.js → chunk-JF2COU6K.js} +69 -69
- package/cli/dist-bundle/cli.js +602 -282
- package/cli/dist-bundle/{cold-preflight-PZKLJJGV.js → cold-preflight-GVGTFUDZ.js} +1 -1
- package/cli/package.json +1 -1
- package/engine/schemas/service-schema.json +26 -1
- package/engine/topologies/platform/docker/compose/compose.star +31 -3
- package/engine/topologies/platform/docker/layers/l4_runtime_layers.star +8 -0
- package/engine/topologies/tilt/manifest/loader.star +9 -0
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import { existsSync, mkdirSync } from "node:fs";
|
|
|
4
4
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import { Command } from "commander";
|
|
7
|
+
import { DEFAULT_AGENT_RUNTIME, resolveAgentDurability, resolveAgentRuntime, } from "../agent-runtimes/registry.js";
|
|
7
8
|
import { resolveBackendFramework } from "../backend-frameworks/registry.js";
|
|
8
9
|
import { getBackendIndexTemplate } from "../backend-languages/bun.js";
|
|
9
10
|
import { resolveBackendLanguage } from "../backend-languages/registry.js";
|
|
@@ -56,6 +57,10 @@ export const TYPE_SPECIFIC = {
|
|
|
56
57
|
mcp: {
|
|
57
58
|
healthCheckPath: "/health",
|
|
58
59
|
},
|
|
60
|
+
// /ready is the readiness route (checkpoint store reachable); /health is liveness only, so an idle agent stays healthy.
|
|
61
|
+
agent: {
|
|
62
|
+
healthCheckPath: "/health",
|
|
63
|
+
},
|
|
59
64
|
[BRING_YOUR_OWN_TYPE]: {
|
|
60
65
|
healthCheckPath: "/health",
|
|
61
66
|
},
|
|
@@ -84,7 +89,7 @@ export function resolveResourcePort(resourceType, value, assignedPort, resources
|
|
|
84
89
|
}
|
|
85
90
|
return port;
|
|
86
91
|
}
|
|
87
|
-
export function createServiceJson(name, type, stack, port, extraFeatures = [], frameworkId, languageId) {
|
|
92
|
+
export function createServiceJson(name, type, stack, port, extraFeatures = [], frameworkId, languageId, agent) {
|
|
88
93
|
const framework = resolveFrontendFramework(type, frameworkId);
|
|
89
94
|
// Only an explicit selection is persisted: a missing `language` has always meant Bun.
|
|
90
95
|
const language = languageId === undefined ? undefined : resolveBackendLanguage(type, languageId);
|
|
@@ -123,8 +128,11 @@ export function createServiceJson(name, type, stack, port, extraFeatures = [], f
|
|
|
123
128
|
stack,
|
|
124
129
|
port,
|
|
125
130
|
...(framework ? { framework: framework.id } : {}),
|
|
126
|
-
...(backendFramework && type !== "mcp"
|
|
131
|
+
...(backendFramework && type !== "mcp" && type !== "agent"
|
|
132
|
+
? { framework: backendFramework.id }
|
|
133
|
+
: {}),
|
|
127
134
|
...(language ? { language: language.id } : {}),
|
|
135
|
+
...(type === "agent" && agent ? { agent } : {}),
|
|
128
136
|
};
|
|
129
137
|
}
|
|
130
138
|
export function createByoServiceJson(name, stack, port, options) {
|
|
@@ -154,10 +162,11 @@ function requireGeneratedViteConfig(configPath) {
|
|
|
154
162
|
"Run tdk up <stack> once, or point this script at your own Vite config to build on the host.";
|
|
155
163
|
return `bun -e 'if(!require("fs").existsSync(${JSON.stringify(configPath)})){console.error(${JSON.stringify(message)});process.exit(1)}'`;
|
|
156
164
|
}
|
|
157
|
-
export function createPackageJson(name, type, frameworkId, prismaEnabled = false) {
|
|
165
|
+
export function createPackageJson(name, type, frameworkId, prismaEnabled = false, agentConfig) {
|
|
158
166
|
const isFrontend = type === "frontend";
|
|
159
167
|
const framework = resolveFrontendFramework(type, frameworkId);
|
|
160
168
|
const backendFramework = resolveBackendFramework(type, frameworkId);
|
|
169
|
+
const isPostgresAgent = type === "agent" && agentConfig?.durability === "postgres";
|
|
161
170
|
return {
|
|
162
171
|
name: `@project/${name}`,
|
|
163
172
|
version: "0.0.1",
|
|
@@ -184,11 +193,15 @@ export function createPackageJson(name, type, frameworkId, prismaEnabled = false
|
|
|
184
193
|
dependencies: {
|
|
185
194
|
...(isFrontend ? {} : (backendFramework?.dependencies ?? { hono: "^4.0.0" })),
|
|
186
195
|
...(framework?.dependencies ?? {}),
|
|
187
|
-
...(prismaEnabled
|
|
196
|
+
...(prismaEnabled || isPostgresAgent
|
|
188
197
|
? {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
198
|
+
...(prismaEnabled
|
|
199
|
+
? {
|
|
200
|
+
prisma: "^7.5.0",
|
|
201
|
+
"@prisma/client": "^7.5.0",
|
|
202
|
+
"@prisma/adapter-pg": "^7.5.0",
|
|
203
|
+
}
|
|
204
|
+
: {}),
|
|
192
205
|
pg: "^8.13.0",
|
|
193
206
|
}
|
|
194
207
|
: {}),
|
|
@@ -272,7 +285,7 @@ export function parseResourceType(type) {
|
|
|
272
285
|
export const resourceCommand = new Command("resource")
|
|
273
286
|
.description("Create a new resource (service) from scratch, or register an existing one")
|
|
274
287
|
.argument("[name]", "Resource name (kebab-case)")
|
|
275
|
-
.option("-t, --type <type>", "Resource type: backend, frontend, worker, mcp, bring-your-own, sdk", "backend")
|
|
288
|
+
.option("-t, --type <type>", "Resource type: backend, frontend, worker, mcp, agent, bring-your-own, sdk", "backend")
|
|
276
289
|
.option("--framework <id>", "Framework: frontend react (default), vue, svelte, preact, lit, solid, qwik, vanilla, tanstack-router (see --frameworks); backend hono (default), express, elysia, fastify, nestjs, koa, h3")
|
|
277
290
|
.option("--frameworks", "List registered frontend frameworks and exit")
|
|
278
291
|
.option("--language <id>", "Backend language: bun (default), python, go, rust")
|
|
@@ -289,6 +302,8 @@ export const resourceCommand = new Command("resource")
|
|
|
289
302
|
.option("--image <name>", "Docker image name instead of building from Dockerfile (for bring-your-own)")
|
|
290
303
|
.option("--port <port>", "Port for the resource, within its type's range (default: next free port in that range)")
|
|
291
304
|
.option("--feature <feature...>", "Enable a resource feature (for example: prisma)")
|
|
305
|
+
.option("--runtime <id>", "Agent runtime (for agent only): custom (default)")
|
|
306
|
+
.option("--durability <kind>", "Where agent sessions are checkpointed (for agent only): files (default, named volume) or postgres")
|
|
292
307
|
.action(async (name, options) => {
|
|
293
308
|
if (options.frameworks) {
|
|
294
309
|
for (const f of listFrontendFrameworks()) {
|
|
@@ -322,6 +337,21 @@ export const resourceCommand = new Command("resource")
|
|
|
322
337
|
assertValid(validateResourceName(resourceName));
|
|
323
338
|
}
|
|
324
339
|
const resourceType = parseResourceType(options.type);
|
|
340
|
+
if (resourceType !== "agent" &&
|
|
341
|
+
(options.runtime !== undefined || options.durability !== undefined)) {
|
|
342
|
+
throw new TdkError("--runtime and --durability can only be used with --type agent.", [
|
|
343
|
+
"Drop --runtime and --durability, or pass --type agent",
|
|
344
|
+
]);
|
|
345
|
+
}
|
|
346
|
+
// Validate the agent options before anything is written, so a bad flag leaves no half-scaffolded directory.
|
|
347
|
+
let agentConfig;
|
|
348
|
+
if (resourceType === "agent") {
|
|
349
|
+
resolveAgentRuntime(options.runtime);
|
|
350
|
+
agentConfig = {
|
|
351
|
+
runtime: options.runtime ?? DEFAULT_AGENT_RUNTIME,
|
|
352
|
+
durability: resolveAgentDurability(options.durability),
|
|
353
|
+
};
|
|
354
|
+
}
|
|
325
355
|
let frameworkOption = options.framework;
|
|
326
356
|
if (resourceType === "frontend" &&
|
|
327
357
|
frameworkOption === undefined &&
|
|
@@ -343,7 +373,9 @@ export const resourceCommand = new Command("resource")
|
|
|
343
373
|
const backendFramework = resolveBackendFramework(resourceType, frameworkOption, backendLanguage);
|
|
344
374
|
// The id passed on to the file generators. An mcp resource has one fixed scaffold and no `framework` setting, so it passes none
|
|
345
375
|
// (its provider is selected by the resource type itself).
|
|
346
|
-
const scaffoldFrameworkId = resourceType === "mcp"
|
|
376
|
+
const scaffoldFrameworkId = resourceType === "mcp" || resourceType === "agent"
|
|
377
|
+
? undefined
|
|
378
|
+
: (frontendFramework?.id ?? backendFramework?.id);
|
|
347
379
|
let dddEnabled = false;
|
|
348
380
|
if (options.ddd) {
|
|
349
381
|
if (resourceType !== "backend" && resourceType !== "worker") {
|
|
@@ -423,6 +455,7 @@ export const resourceCommand = new Command("resource")
|
|
|
423
455
|
frontend: `apps/${resourceName}`,
|
|
424
456
|
worker: `workers/${resourceName}`,
|
|
425
457
|
mcp: `services/${stackName}/${resourceName}`,
|
|
458
|
+
agent: `services/${stackName}/${resourceName}`,
|
|
426
459
|
[BRING_YOUR_OWN_TYPE]: `services/${stackName}/${resourceName}`,
|
|
427
460
|
sdk: `packages/${resourceName}`,
|
|
428
461
|
};
|
|
@@ -607,8 +640,8 @@ This file contains the resource configuration for TDK.
|
|
|
607
640
|
// Prepare file generation tasks
|
|
608
641
|
const requestedFeatures = Array.isArray(options.feature) ? options.feature : [];
|
|
609
642
|
const prismaEnabled = requestedFeatures.includes("prisma");
|
|
610
|
-
const serviceJson = createServiceJson(resourceName, resourceType, stackName, assignedPort, [...(dddEnabled ? ["ddd"] : []), ...requestedFeatures], scaffoldFrameworkId, options.language === undefined ? undefined : backendLanguage?.id);
|
|
611
|
-
const packageJson = createPackageJson(resourceName, resourceType, scaffoldFrameworkId, prismaEnabled);
|
|
643
|
+
const serviceJson = createServiceJson(resourceName, resourceType, stackName, assignedPort, [...(dddEnabled ? ["ddd"] : []), ...requestedFeatures], scaffoldFrameworkId, options.language === undefined ? undefined : backendLanguage?.id, agentConfig);
|
|
644
|
+
const packageJson = createPackageJson(resourceName, resourceType, scaffoldFrameworkId, prismaEnabled, agentConfig);
|
|
612
645
|
if (prismaEnabled && resourceType === "backend") {
|
|
613
646
|
serviceJson.dependsOn = [`${resourceName}-migrator`];
|
|
614
647
|
}
|
|
@@ -645,12 +678,19 @@ This file contains the resource configuration for TDK.
|
|
|
645
678
|
},
|
|
646
679
|
];
|
|
647
680
|
// Add source files based on resource type
|
|
648
|
-
if (resourceType === "backend" || resourceType === "mcp") {
|
|
681
|
+
if (resourceType === "backend" || resourceType === "mcp" || resourceType === "agent") {
|
|
682
|
+
const indexContent = resourceType === "agent"
|
|
683
|
+
? (backendFramework?.createIndex ?? getBackendIndexTemplate)(resourceName, agentConfig?.durability)
|
|
684
|
+
: (backendFramework?.createIndex ?? getBackendIndexTemplate)(resourceName);
|
|
649
685
|
tasks.push({
|
|
650
686
|
type: "text",
|
|
651
687
|
filename: "src/index.ts",
|
|
652
|
-
content:
|
|
653
|
-
description: resourceType === "mcp"
|
|
688
|
+
content: indexContent,
|
|
689
|
+
description: resourceType === "mcp"
|
|
690
|
+
? "Generating MCP server source"
|
|
691
|
+
: resourceType === "agent"
|
|
692
|
+
? "Generating agent source"
|
|
693
|
+
: "Generating backend source",
|
|
654
694
|
emoji: "💻",
|
|
655
695
|
});
|
|
656
696
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Copyright (c) 2026 TDK Landscape contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { STANDARD_PORTS } from "../utils/constants.js";
|
|
6
|
+
import { errorFactories, runCommand, TdkError } from "../utils/errors.js";
|
|
7
|
+
import { createMachineEnvelope, writeMachineError } from "../utils/machine-output.js";
|
|
8
|
+
import { findProjectRoot } from "../utils/paths.js";
|
|
9
|
+
import { isTiltAvailable, runTilt } from "../utils/tilt.js";
|
|
10
|
+
import { isTiltConnectionFailure } from "../utils/tilt-logs.js";
|
|
11
|
+
import { tiltUnreachableMessage } from "../utils/tilt-unreachable-message.js";
|
|
12
|
+
export const restartCommand = new Command("restart")
|
|
13
|
+
.description("Restart a running resource in the stack")
|
|
14
|
+
.argument("<resource>", "Name of the resource (service) to restart")
|
|
15
|
+
.option("--port <n>", "Tilt UI port (default: TILT_PORT or 10350)")
|
|
16
|
+
.option("--json", "Output one JSON object and exit", false)
|
|
17
|
+
.action(async (resourceName, options) => {
|
|
18
|
+
const action = async () => {
|
|
19
|
+
const root = findProjectRoot();
|
|
20
|
+
if (!root) {
|
|
21
|
+
const error = errorFactories.notInProject();
|
|
22
|
+
if (options.json)
|
|
23
|
+
writeMachineError(error);
|
|
24
|
+
error.exit();
|
|
25
|
+
}
|
|
26
|
+
const portText = options.port ?? process.env.TILT_PORT ?? String(STANDARD_PORTS.tiltUi);
|
|
27
|
+
let tiltAvailable = false;
|
|
28
|
+
try {
|
|
29
|
+
tiltAvailable = await isTiltAvailable();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
tiltAvailable = false;
|
|
33
|
+
}
|
|
34
|
+
if (!tiltAvailable) {
|
|
35
|
+
// isTiltAvailable collapses missing binary and nonzero version into false.
|
|
36
|
+
// Report TILT_NOT_RUNNING for both; a missing binary is rare and still surfaces via doctor.
|
|
37
|
+
const msg = tiltUnreachableMessage(portText);
|
|
38
|
+
if (options.json) {
|
|
39
|
+
console.log(JSON.stringify(createMachineEnvelope(null, [{ code: "TILT_NOT_RUNNING", message: msg }])));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
throw new TdkError(msg, ["Start the stack first with: tdk up"]);
|
|
43
|
+
}
|
|
44
|
+
const result = await runTilt("trigger", [resourceName, "--port", portText], {
|
|
45
|
+
inheritStdio: false,
|
|
46
|
+
});
|
|
47
|
+
if (result.exitCode !== 0) {
|
|
48
|
+
const detail = result.stderr.trim();
|
|
49
|
+
if (!detail || isTiltConnectionFailure(detail)) {
|
|
50
|
+
const msg = tiltUnreachableMessage(portText);
|
|
51
|
+
if (options.json) {
|
|
52
|
+
console.log(JSON.stringify(createMachineEnvelope(null, [{ code: "TILT_NOT_RUNNING", message: msg }])));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
throw new TdkError(msg, ["Start the stack first with: tdk up"]);
|
|
56
|
+
}
|
|
57
|
+
if (options.json) {
|
|
58
|
+
console.log(JSON.stringify(createMachineEnvelope(null, [{ code: "TILT_TRIGGER_FAILED", message: detail }])));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
throw new TdkError(`Failed to restart ${resourceName}: ${detail}`);
|
|
62
|
+
}
|
|
63
|
+
if (options.json) {
|
|
64
|
+
console.log(JSON.stringify(createMachineEnvelope({ resource: resourceName, restarted: true })));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
console.log(chalk.green(`✓ Triggered restart for ${resourceName}`));
|
|
68
|
+
};
|
|
69
|
+
if (options.json) {
|
|
70
|
+
try {
|
|
71
|
+
await action();
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
writeMachineError(err);
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
await runCommand(action);
|
|
79
|
+
});
|
package/cli/dist/mcp/tools.js
CHANGED
|
@@ -4,6 +4,7 @@ import { spawn } from "node:child_process";
|
|
|
4
4
|
import { closeSync, existsSync, openSync, readFileSync } from "node:fs";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { isAbsolute, join } from "node:path";
|
|
7
|
+
import { createMachineEnvelope } from "../utils/machine-output.js";
|
|
7
8
|
/** A service or stack name. A leading "-" is rejected so a value can never be read as a flag. */
|
|
8
9
|
const NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
9
10
|
const SINCE = /^(\d+(\.\d+)?(ns|us|µs|ms|s|m|h))+$/;
|
|
@@ -131,6 +132,8 @@ export function buildUpArgs(args) {
|
|
|
131
132
|
out.push(...only.map((name) => `--only=${name}`));
|
|
132
133
|
if (flag(args.force, "force"))
|
|
133
134
|
out.push("--force");
|
|
135
|
+
if (flag(args.dryRun, "dryRun"))
|
|
136
|
+
out.push("--dry-run");
|
|
134
137
|
out.push("--json");
|
|
135
138
|
return out;
|
|
136
139
|
}
|
|
@@ -168,17 +171,13 @@ export async function startUp(args, deps = defaultUpDeps) {
|
|
|
168
171
|
}
|
|
169
172
|
child.unref();
|
|
170
173
|
return {
|
|
171
|
-
data: {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
next: "Poll the status tool until data.tilt.readiness.ready is true. Call down to stop it.",
|
|
179
|
-
},
|
|
180
|
-
errors: [],
|
|
181
|
-
},
|
|
174
|
+
data: createMachineEnvelope({
|
|
175
|
+
ok: true,
|
|
176
|
+
started: true,
|
|
177
|
+
pid: child.pid ?? null,
|
|
178
|
+
logFile,
|
|
179
|
+
next: "Poll the status tool until data.tilt.readiness.ready is true. Call down to stop it.",
|
|
180
|
+
}),
|
|
182
181
|
isError: false,
|
|
183
182
|
};
|
|
184
183
|
}
|
|
@@ -245,6 +244,7 @@ export function createTdkTools(run = runTdkCli, up = defaultUpDeps) {
|
|
|
245
244
|
description: "Start only these services plus what they depend on and shared infrastructure",
|
|
246
245
|
},
|
|
247
246
|
force: { type: "boolean", description: "Replace a Tilt that is already running" },
|
|
247
|
+
dryRun: { type: "boolean", description: "Show what would be started without starting" },
|
|
248
248
|
waitSeconds: {
|
|
249
249
|
type: "number",
|
|
250
250
|
description: "How long to watch for an early failure before returning (default 15, max 60)",
|
|
@@ -255,8 +255,15 @@ export function createTdkTools(run = runTdkCli, up = defaultUpDeps) {
|
|
|
255
255
|
{
|
|
256
256
|
name: "down",
|
|
257
257
|
description: "Stop the running stack, optionally selecting its project with projectPath.",
|
|
258
|
-
inputSchema: OBJECT({
|
|
259
|
-
|
|
258
|
+
inputSchema: OBJECT({
|
|
259
|
+
projectPath: PROJECT_PATH,
|
|
260
|
+
dryRun: { type: "boolean", description: "Show what would be stopped without stopping" },
|
|
261
|
+
}),
|
|
262
|
+
handler: viaCli((args) => [
|
|
263
|
+
"down",
|
|
264
|
+
"--json",
|
|
265
|
+
...(flag(args.dryRun, "dryRun") ? ["--dry-run"] : []),
|
|
266
|
+
]),
|
|
260
267
|
},
|
|
261
268
|
{
|
|
262
269
|
name: "status",
|
|
@@ -290,5 +297,53 @@ export function createTdkTools(run = runTdkCli, up = defaultUpDeps) {
|
|
|
290
297
|
return ["resources", "--json", ...(stack ? [`--stack=${stack}`] : [])];
|
|
291
298
|
}),
|
|
292
299
|
},
|
|
300
|
+
{
|
|
301
|
+
name: "restart_resource",
|
|
302
|
+
description: "Restart a running resource in the stack (or trigger its rebuild). Set projectPath when the MCP server is not running from the project root.",
|
|
303
|
+
inputSchema: OBJECT({
|
|
304
|
+
projectPath: PROJECT_PATH,
|
|
305
|
+
resource: { type: "string", description: "Name of the resource to restart" },
|
|
306
|
+
}),
|
|
307
|
+
handler: viaCli((args) => {
|
|
308
|
+
const resource = optionalName(args.resource, "resource");
|
|
309
|
+
if (!resource)
|
|
310
|
+
throw new Error("resource is required");
|
|
311
|
+
return ["restart", resource, "--json"];
|
|
312
|
+
}),
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: "list_agents",
|
|
316
|
+
description: "List AI agent resources (appType: 'agent') in the project with their stack, runtime, durability and port. Set projectPath when the MCP server is not running from the project root.",
|
|
317
|
+
inputSchema: OBJECT({
|
|
318
|
+
projectPath: PROJECT_PATH,
|
|
319
|
+
stack: { type: "string", description: "Only this stack" },
|
|
320
|
+
}),
|
|
321
|
+
handler: viaCli((args) => {
|
|
322
|
+
const stack = optionalName(args.stack, "stack");
|
|
323
|
+
return ["resources", "--type=agent", "--json", ...(stack ? [`--stack=${stack}`] : [])];
|
|
324
|
+
}),
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: "get_agent_sessions",
|
|
328
|
+
description: "Inspect session checkpoints from a running AI agent resource. Set projectPath when the MCP server is not running from the project root.",
|
|
329
|
+
inputSchema: OBJECT({
|
|
330
|
+
projectPath: PROJECT_PATH,
|
|
331
|
+
resource: { type: "string", description: "Name of the agent resource" },
|
|
332
|
+
sessionId: { type: "string", description: "Optional session ID to fetch" },
|
|
333
|
+
}),
|
|
334
|
+
handler: viaCli((args) => {
|
|
335
|
+
const resource = optionalName(args.resource, "resource");
|
|
336
|
+
if (!resource)
|
|
337
|
+
throw new Error("resource is required");
|
|
338
|
+
const sessionId = optionalName(args.sessionId, "sessionId");
|
|
339
|
+
return [
|
|
340
|
+
"agent",
|
|
341
|
+
"sessions",
|
|
342
|
+
resource,
|
|
343
|
+
...(sessionId ? [`--session-id=${sessionId}`] : []),
|
|
344
|
+
"--json",
|
|
345
|
+
];
|
|
346
|
+
}),
|
|
347
|
+
},
|
|
293
348
|
];
|
|
294
349
|
}
|
package/cli/dist/types/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export const RESOURCE_CONFIG_APP_TYPES = [
|
|
|
9
9
|
"worker",
|
|
10
10
|
"migrator",
|
|
11
11
|
"mcp",
|
|
12
|
+
"agent",
|
|
12
13
|
"bring-your-own",
|
|
13
14
|
];
|
|
14
15
|
export const CREATABLE_RESOURCE_TYPES = [
|
|
@@ -16,6 +17,7 @@ export const CREATABLE_RESOURCE_TYPES = [
|
|
|
16
17
|
"frontend",
|
|
17
18
|
"worker",
|
|
18
19
|
"mcp",
|
|
20
|
+
"agent",
|
|
19
21
|
"bring-your-own",
|
|
20
22
|
];
|
|
21
23
|
export function isCreatableResourceType(value) {
|
|
@@ -45,6 +45,7 @@ export const VALID_RESOURCE_TYPES = [
|
|
|
45
45
|
"worker",
|
|
46
46
|
"migrator",
|
|
47
47
|
"mcp",
|
|
48
|
+
"agent",
|
|
48
49
|
BRING_YOUR_OWN_TYPE,
|
|
49
50
|
];
|
|
50
51
|
// Frontend/library/sdk services are served statically or consumed as
|
|
@@ -56,6 +57,7 @@ export const REQUIRED_PACKAGE_SCRIPTS = {
|
|
|
56
57
|
worker: ["dev", "build", "start"],
|
|
57
58
|
migrator: ["dev", "build", "start"],
|
|
58
59
|
mcp: ["dev", "build", "start"],
|
|
60
|
+
agent: ["dev", "build", "start"],
|
|
59
61
|
frontend: ["dev", "build"],
|
|
60
62
|
library: ["dev", "build"],
|
|
61
63
|
sdk: ["dev", "build"],
|
|
@@ -76,6 +78,8 @@ export const PORT_RANGES = {
|
|
|
76
78
|
backend: { base: 4000, min: 4000, max: 4999, range: "4000-4999" },
|
|
77
79
|
// An MCP server is an HTTP service, so it shares the backend-compatible range with bring-your-own.
|
|
78
80
|
mcp: { base: 4000, min: 4000, max: 5999, range: "4000-5999" },
|
|
81
|
+
// An agent serves /health and /ready over HTTP (see agent-runtimes/custom.ts), so it shares the same range.
|
|
82
|
+
agent: { base: 4000, min: 4000, max: 5999, range: "4000-5999" },
|
|
79
83
|
[BRING_YOUR_OWN_TYPE]: { base: 4000, min: 4000, max: 5999, range: "4000-5999" },
|
|
80
84
|
worker: { base: 6000, min: 6000, max: 6999, range: "6000-6999" },
|
|
81
85
|
health: { base: 5000, min: 5000, max: 5999, range: "5000-5999" },
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CONTAINER_RUNTIME_CHECK } from "./constants.js";
|
|
2
|
+
import { SCHEMA_VERSION } from "./machine-output.js";
|
|
2
3
|
/** Wait for concurrent probes to finish even if one throws, so they can release resources. */
|
|
3
4
|
export async function collectDoctorChecks(machineChecks, projectChecks) {
|
|
4
5
|
const checks = [];
|
|
@@ -28,7 +29,7 @@ function containerRuntimeReachable(checks) {
|
|
|
28
29
|
/** Produce the same readiness decision for human and machine consumers. */
|
|
29
30
|
export function createDoctorReport(checks, inProject, errors = [], portPlan, host) {
|
|
30
31
|
return {
|
|
31
|
-
schemaVersion:
|
|
32
|
+
schemaVersion: SCHEMA_VERSION,
|
|
32
33
|
data: {
|
|
33
34
|
ready: errors.length === 0 &&
|
|
34
35
|
checks.every((check) => check.didPass || check.isSkipped || check.isWarning),
|
|
@@ -232,7 +232,7 @@ export function checkDuplicateResourceNames(projectRoot = findProjectRoot() ?? p
|
|
|
232
232
|
fix: 'Give each resource its own "appName" in its service.json. The name becomes the Compose service and the Traefik router, so two resources with one name collide',
|
|
233
233
|
};
|
|
234
234
|
}
|
|
235
|
-
/** A resource that gets a Traefik route to its own `port`: a backend, an mcp server, or a bring-your-own resource that is not opted out. */
|
|
235
|
+
/** A resource that gets a Traefik route to its own `port`: a backend, an mcp server, an agent, or a bring-your-own resource that is not opted out. */
|
|
236
236
|
function isRoutedToPort(resource) {
|
|
237
237
|
const appType = resource.config?.appType;
|
|
238
238
|
if (isApiServiceType(appType))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Copyright (c) 2026 TDK Landscape contributors
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
import { writeSync } from "node:fs";
|
|
4
|
+
import { createMachineEnvelope, } from "./machine-output.js";
|
|
4
5
|
/**
|
|
5
6
|
* Returns an emitter that writes at most one `{schemaVersion, data, errors}` object to stdout. If the process
|
|
6
7
|
* exits before anything was emitted (a preflight `process.exit`, Tilt ending early), a failure object is written
|
|
@@ -12,7 +13,7 @@ export function createJsonEmitter(failureCode, command) {
|
|
|
12
13
|
if (emitted)
|
|
13
14
|
return;
|
|
14
15
|
emitted = true;
|
|
15
|
-
writeSync(1, `${JSON.stringify(
|
|
16
|
+
writeSync(1, `${JSON.stringify(createMachineEnvelope(data, errors))}\n`);
|
|
16
17
|
};
|
|
17
18
|
process.once("exit", (code) => {
|
|
18
19
|
emit({ ok: false }, [
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// Copyright (c) 2026 TDK Landscape contributors
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
import { TdkError } from "./errors.js";
|
|
4
|
+
export const SCHEMA_VERSION = 1;
|
|
4
5
|
export function createMachineEnvelope(data, errors = []) {
|
|
5
|
-
return { schemaVersion:
|
|
6
|
+
return { schemaVersion: SCHEMA_VERSION, data, errors };
|
|
6
7
|
}
|
|
7
8
|
export function toMachineError(error) {
|
|
8
9
|
if (error instanceof TdkError) {
|
|
@@ -25,4 +25,8 @@ export const PROJECT_TEMPLATES = {
|
|
|
25
25
|
bundledPath: "examples/tdk-example",
|
|
26
26
|
description: "Hono + Postgres + NATS worker + Vite example with a routed write path",
|
|
27
27
|
},
|
|
28
|
+
"agent-stack": {
|
|
29
|
+
repo: "https://github.com/tdk-landscape/tdk-agent-stack.git",
|
|
30
|
+
description: "AI agent stack: Bun agent + MCP tool server + Postgres + NATS",
|
|
31
|
+
},
|
|
28
32
|
};
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* Protocol endpoint, which the engine builds, routes and health-checks exactly like a backend).
|
|
6
6
|
*/
|
|
7
7
|
export function isApiServiceType(appType) {
|
|
8
|
-
return appType === "backend" || appType === "mcp";
|
|
8
|
+
return appType === "backend" || appType === "mcp" || appType === "agent";
|
|
9
9
|
}
|
|
@@ -9,7 +9,9 @@ export function buildServicePorts(resource, ingressHttp) {
|
|
|
9
9
|
const { appBase, apiBase } = resolveSubdomainBases(ingressHttp);
|
|
10
10
|
url = `${isApiServiceType(appType) ? apiBase : appBase}${resolveServicePath(resource)}`;
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
const containerPort = resource.port ?? resource.config?.port ?? null;
|
|
13
|
+
const directUrl = !routable && containerPort ? `http://localhost:${containerPort}` : null;
|
|
14
|
+
return { url, containerPort, hostPort: null, directUrl };
|
|
13
15
|
}
|
|
14
16
|
/** Ports a client should forward or open: ingress, Tilt UI, and the published datastore. Not one per service. */
|
|
15
17
|
export function buildStackPorts(plan, tiltPort) {
|