@flisk-sh/flisk 1.4.6 → 1.4.7
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 +2 -2
- package/cli/dist/agent-runtimes/custom.js +94 -13
- package/cli/dist/commands/agent.js +144 -123
- package/cli/dist/commands/doctor.js +12 -0
- package/cli/dist/commands/logs.js +71 -4
- package/cli/dist/commands/project.js +16 -0
- package/cli/dist/commands/resource.js +3 -1
- package/cli/dist/commands/status.js +43 -24
- package/cli/dist/commands/ui.js +41 -2
- package/cli/dist/commands/up.js +54 -11
- package/cli/dist/components/DetailPanel.js +23 -3
- package/cli/dist/mcp/tools.js +9 -6
- package/cli/dist/types/index.js +0 -2
- package/cli/dist/utils/agent-format.js +18 -0
- package/cli/dist/utils/agent-sessions.js +122 -0
- package/cli/dist/utils/agent-skills.js +64 -0
- package/cli/dist/utils/cold-preflight.js +3 -0
- package/cli/dist/utils/doctor-fix-first.js +93 -0
- package/cli/dist/utils/doctor-report.js +3 -0
- package/cli/dist/utils/native-runtime.js +3 -0
- package/cli/dist/utils/native-service.js +2 -2
- package/cli/dist/utils/service-manifest.js +8 -15
- package/cli/dist/utils/services.js +7 -10
- package/cli/dist/utils/startup-report.js +45 -3
- package/cli/dist/utils/status-groups.js +23 -0
- package/cli/dist/utils/up-hints.js +42 -0
- package/cli/dist/utils/up-progress.js +117 -0
- package/cli/dist/utils/up-readiness.js +5 -0
- package/cli/dist-bundle/{chunk-U5FCQF42.js → chunk-6OW2GVQV.js} +1 -1
- package/cli/dist-bundle/chunk-B2JOM3HN.js +35 -0
- package/cli/dist-bundle/{chunk-R7DWXISM.js → chunk-F2DLIM75.js} +1 -1
- package/cli/dist-bundle/{chunk-ETXZ5LUG.js → chunk-L6O7TT3K.js} +10 -10
- package/cli/dist-bundle/chunk-MFY7RZ76.js +130 -0
- package/cli/dist-bundle/{chunk-MUDE4DZD.js → chunk-MYAYJF4Q.js} +1 -1
- package/cli/dist-bundle/{chunk-DPQK5TIS.js → chunk-P7GZ6NGM.js} +1 -1
- package/cli/dist-bundle/{chunk-IDNES5PE.js → chunk-T44DEEL6.js} +1 -1
- package/cli/dist-bundle/{chunk-C4UFBXRD.js → chunk-TIPSERWO.js} +1 -1
- package/cli/dist-bundle/{chunk-BIVAAWBX.js → chunk-VYPB7MY5.js} +1 -1
- package/cli/dist-bundle/{chunk-WCGBVETR.js → chunk-XHENT4KO.js} +1 -1
- package/cli/dist-bundle/{chunk-CAEMWNPF.js → chunk-YTBTCAWY.js} +1 -1
- package/cli/dist-bundle/cli.js +462 -377
- package/cli/dist-bundle/{cold-preflight-KUWOL2Q2.js → cold-preflight-DQVJUGBB.js} +1 -1
- package/cli/dist-bundle/{demo-stage-QCC4IWXR.js → demo-stage-F4YA7UL2.js} +1 -1
- package/cli/dist-bundle/{open-AZNX4AC5.js → open-ZUSSVPD3.js} +1 -1
- package/cli/dist-bundle/project-JFCTPRP5.js +2 -0
- package/cli/dist-bundle/{services-EY5WKPHQ.js → services-DOHDHOCT.js} +1 -1
- package/cli/dist-bundle/{tilt-port-owner-UBU6JWOA.js → tilt-port-owner-XODQHXHX.js} +1 -1
- package/cli/package.json +1 -1
- package/cli/templates/agent-skills/flisk-agent-ops/SKILL.md +39 -0
- package/cli/templates/agent-skills/flisk-build/SKILL.md +85 -0
- package/cli/templates/agent-skills/flisk-doctor/SKILL.md +28 -0
- package/cli/templates/agent-skills/flisk-recipe-ai-agent/SKILL.md +45 -0
- package/cli/templates/agent-skills/flisk-recipe-blog/SKILL.md +36 -0
- package/cli/templates/agent-skills/flisk-recipe-saas/SKILL.md +39 -0
- package/cli/templates/agent-skills/flisk-recipe-shop/SKILL.md +41 -0
- package/cli/templates/agent-skills/flisk-recipe-starters/SKILL.md +46 -0
- package/cli/templates/agent-skills/flisk-resource/SKILL.md +27 -0
- package/cli/templates/agent-skills/flisk-status/SKILL.md +25 -0
- package/cli/templates/agent-skills/flisk-troubleshoot/SKILL.md +227 -0
- package/cli/templates/agent-skills/flisk-up/SKILL.md +37 -0
- package/package.json +1 -1
- package/runtime/engine/schemas/service-schema.json +3 -15
- package/runtime/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +7 -1
- package/cli/dist-bundle/chunk-CDKEHFW6.js +0 -35
- package/cli/dist-bundle/chunk-GT76VMUL.js +0 -130
- package/cli/dist-bundle/project-5SWIGCNP.js +0 -2
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { STANDARD_PORTS } from "../utils/constants.js";
|
|
5
|
+
import { getDeferredResourceNames } from "../utils/doctor-runtime.js";
|
|
5
6
|
import { errorFactories, FliskError, runCommand, showErrorAndExit } from "../utils/errors.js";
|
|
6
7
|
import { createMachineEnvelope } from "../utils/machine-output.js";
|
|
7
|
-
import { readNativeLogLines } from "../utils/native-runtime.js";
|
|
8
|
+
import { nativeServiceStates, readNativeLogLines } from "../utils/native-runtime.js";
|
|
8
9
|
import { isNativeProject } from "../utils/native-service.js";
|
|
9
10
|
import { findProjectRoot } from "../utils/paths.js";
|
|
10
11
|
import { EnvUnreadableError, envSecretValues, redactSecrets, redactValue, } from "../utils/secret-redaction.js";
|
|
12
|
+
import { deriveServiceStates } from "../utils/service-runtime-state.js";
|
|
11
13
|
import { discoverResourcesFromRoot } from "../utils/services.js";
|
|
14
|
+
import { unhealthyServiceNames } from "../utils/status-groups.js";
|
|
12
15
|
import { isTiltAvailable, runTilt } from "../utils/tilt.js";
|
|
13
16
|
import { DEFAULT_LOG_TAIL, isTiltConnectionFailure, isValidPort, isValidSince, isValidTail, MAX_LOG_TAIL, parseTiltLogLines, sinceMilliseconds, } from "../utils/tilt-logs.js";
|
|
14
17
|
import { tiltUnreachableMessage } from "../utils/tilt-unreachable-message.js";
|
|
@@ -28,6 +31,38 @@ async function tiltResourceNames(port) {
|
|
|
28
31
|
return null;
|
|
29
32
|
}
|
|
30
33
|
}
|
|
34
|
+
/** Raw `tilt get uiresources` JSON, or null when Tilt cannot be read. */
|
|
35
|
+
async function tiltResourcesJson(port) {
|
|
36
|
+
try {
|
|
37
|
+
const result = await runTilt("get", ["uiresources", "-o", "json", "--port", port], {
|
|
38
|
+
inheritStdio: false,
|
|
39
|
+
});
|
|
40
|
+
return result.exitCode === 0 ? result.stdout : null;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Failing or pending services among the project's, from the running Tilt; null when Tilt cannot be read. */
|
|
47
|
+
async function tiltUnhealthyNames(projectRoot, port) {
|
|
48
|
+
const text = await tiltResourcesJson(port);
|
|
49
|
+
if (!text)
|
|
50
|
+
return null;
|
|
51
|
+
let resources = [];
|
|
52
|
+
try {
|
|
53
|
+
resources = discoverResourcesFromRoot(projectRoot);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Without discovery there are no dependsOn edges; Tilt's own states still decide.
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const states = deriveServiceStates(text, Object.fromEntries(resources.map((r) => [r.name, r.config?.dependsOn ?? []])), getDeferredResourceNames(resources));
|
|
60
|
+
return unhealthyServiceNames(Object.keys(states).map((name) => ({ name })), states);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
31
66
|
/** The project's services when every one declares `command` (they run as local processes), else null. */
|
|
32
67
|
function nativeResources(projectRoot) {
|
|
33
68
|
try {
|
|
@@ -45,6 +80,7 @@ export const logsCommand = new Command("logs")
|
|
|
45
80
|
.option("-s, --service <names...>", "Only these services (Tilt resource names)")
|
|
46
81
|
.option("--tail <n>", "Number of most recent lines to return", String(DEFAULT_LOG_TAIL))
|
|
47
82
|
.option("--since <duration>", "Only logs newer than this duration, for example 30s, 5m, 1h")
|
|
83
|
+
.option("--unhealthy", "Only services that are failing or still starting", false)
|
|
48
84
|
.option("--port <n>", "Tilt UI port (default: TILT_PORT or 10350)")
|
|
49
85
|
.option("--json", "Output one JSON object and exit", false)
|
|
50
86
|
.action(async (options) => {
|
|
@@ -78,6 +114,19 @@ export const logsCommand = new Command("logs")
|
|
|
78
114
|
fail("USAGE", "--port must be a valid port number", 2);
|
|
79
115
|
const tail = Number(options.tail);
|
|
80
116
|
const services = options.service ?? [];
|
|
117
|
+
const reportAllHealthy = () => {
|
|
118
|
+
if (options.json) {
|
|
119
|
+
console.log(JSON.stringify(createMachineEnvelope({
|
|
120
|
+
services,
|
|
121
|
+
tail,
|
|
122
|
+
since: options.since ?? null,
|
|
123
|
+
lines: [],
|
|
124
|
+
unhealthy: [],
|
|
125
|
+
})));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
console.log("All services are healthy; nothing to show with --unhealthy.");
|
|
129
|
+
};
|
|
81
130
|
const action = async () => {
|
|
82
131
|
const projectRoot = findProjectRoot();
|
|
83
132
|
if (!projectRoot) {
|
|
@@ -104,7 +153,15 @@ export const logsCommand = new Command("logs")
|
|
|
104
153
|
const error = errorFactories.unknownServices(unknown, known);
|
|
105
154
|
fail("UNKNOWN_SERVICE", error.message, error.exitCode, error.suggestions);
|
|
106
155
|
}
|
|
107
|
-
|
|
156
|
+
let selected = services.length > 0 ? services : known;
|
|
157
|
+
if (options.unhealthy) {
|
|
158
|
+
const states = nativeServiceStates(projectRoot, known);
|
|
159
|
+
const bad = new Set(unhealthyServiceNames(resources, states));
|
|
160
|
+
selected = selected.filter((name) => bad.has(name));
|
|
161
|
+
if (selected.length === 0)
|
|
162
|
+
return reportAllHealthy();
|
|
163
|
+
}
|
|
164
|
+
const lines = readNativeLogLines(projectRoot, selected, {
|
|
108
165
|
tail,
|
|
109
166
|
sinceMs: options.since === undefined ? undefined : sinceMilliseconds(options.since),
|
|
110
167
|
});
|
|
@@ -130,14 +187,24 @@ export const logsCommand = new Command("logs")
|
|
|
130
187
|
fail("UNKNOWN_SERVICE", error.message, error.exitCode, suggestions);
|
|
131
188
|
}
|
|
132
189
|
}
|
|
190
|
+
let selectedServices = services;
|
|
191
|
+
if (options.unhealthy) {
|
|
192
|
+
const bad = await tiltUnhealthyNames(projectRoot, portText);
|
|
193
|
+
if (bad === null)
|
|
194
|
+
fail("TILT_NOT_RUNNING", tiltUnreachableMessage(portText));
|
|
195
|
+
selectedServices =
|
|
196
|
+
services.length > 0 ? services.filter((n) => bad?.includes(n)) : (bad ?? []);
|
|
197
|
+
if (selectedServices.length === 0)
|
|
198
|
+
return reportAllHealthy();
|
|
199
|
+
}
|
|
133
200
|
const args = ["--port", portText, "--tail", String(tail)];
|
|
134
201
|
if (options.since !== undefined)
|
|
135
202
|
args.push("--since", options.since);
|
|
136
203
|
if (options.json)
|
|
137
204
|
args.push("--json");
|
|
138
205
|
// `--` so a service name that starts with "-" is never read as a Tilt flag.
|
|
139
|
-
if (
|
|
140
|
-
args.push("--", ...
|
|
206
|
+
if (selectedServices.length > 0)
|
|
207
|
+
args.push("--", ...selectedServices);
|
|
141
208
|
const result = await runTilt("logs", args, { inheritStdio: false });
|
|
142
209
|
if (result.exitCode !== 0) {
|
|
143
210
|
const detail = result.stderr.trim();
|
|
@@ -8,6 +8,7 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
import { Command } from "commander";
|
|
10
10
|
import { generateMasterConfigs, readProjectConfig } from "../generator/template-engine.js";
|
|
11
|
+
import { installAgentSkills } from "../utils/agent-skills.js";
|
|
11
12
|
import { addYesOption } from "../utils/command-helpers.js";
|
|
12
13
|
import { MASTER_CONFIG_FILES, PROJECT_JSON } from "../utils/constants.js";
|
|
13
14
|
import { clearDiscoveryCache } from "../utils/discovery-context.js";
|
|
@@ -233,6 +234,7 @@ export const projectCommand = addYesOption(new Command("project"))
|
|
|
233
234
|
.option("--force", "Overwrite existing configuration (dangerous)")
|
|
234
235
|
.option("--config-file <path>", "Load project config from existing JSON file")
|
|
235
236
|
.option("-q, --quiet", "Only print what fails; used by `flisk demo`", false)
|
|
237
|
+
.option("--no-agent-skills", "Do not write the Flisk agent skills (.claude/skills, .agents/skills) and AGENTS.md")
|
|
236
238
|
.action(async (template, options) => {
|
|
237
239
|
const { assertMachineReadyOrExit, detectRuntimePrerequisites, formatRuntimeNextSteps } = await import("../utils/cold-preflight.js");
|
|
238
240
|
// Project setup only writes files: it must not need a running Docker daemon
|
|
@@ -493,6 +495,20 @@ export const projectCommand = addYesOption(new Command("project"))
|
|
|
493
495
|
if (ignored.length > 0 && !quiet) {
|
|
494
496
|
showSuccess(`Added to .gitignore: ${ignored.join(", ")}`);
|
|
495
497
|
}
|
|
498
|
+
// A coding agent opened in the project then knows to build and run the app with Flisk.
|
|
499
|
+
// The project works without them, so a failure here is a warning, not an exit.
|
|
500
|
+
if (options.agentSkills !== false) {
|
|
501
|
+
try {
|
|
502
|
+
const skillFiles = installAgentSkills(projectRoot);
|
|
503
|
+
if (skillFiles.length > 0 && !quiet) {
|
|
504
|
+
showSuccess("Added agent skills: .claude/skills, .agents/skills and AGENTS.md (start with flisk-build)");
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
catch (error) {
|
|
508
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
509
|
+
console.log(chalk.yellow(`⚠️ Could not add the agent skills: ${reason}`));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
496
512
|
if (!quiet) {
|
|
497
513
|
console.log(chalk.green("\n✅ Project configuration complete!"));
|
|
498
514
|
showDetail("\nGenerated files in .flisk/.flisk-out/:", 0);
|
|
@@ -231,7 +231,9 @@ export function createPackageJson(name, type, frameworkId, prismaEnabled = false
|
|
|
231
231
|
// vitest 5 peers on vite ^6.4.0 || ^7 || ^8; vite 5 makes `vitest run` fail at startup with
|
|
232
232
|
// ERR_PACKAGE_PATH_NOT_EXPORTED ('./module-runner'), in every resource once it is hoisted.
|
|
233
233
|
...(isFrontend && !framework?.scripts ? { vite: "^6.4.1" } : {}),
|
|
234
|
-
...(drizzleEnabled ? { "drizzle-kit": DRIZZLE_KIT_RANGE
|
|
234
|
+
...(drizzleEnabled ? { "drizzle-kit": DRIZZLE_KIT_RANGE } : {}),
|
|
235
|
+
// Both import 'pg' (src/db.ts, the agent's checkpoint store); without the types the image's `tsc` fails with TS7016.
|
|
236
|
+
...(drizzleEnabled || isPostgresAgent ? { "@types/pg": "^8.11.0" } : {}),
|
|
235
237
|
...(framework?.devDependencies ?? {}),
|
|
236
238
|
...(backendFramework?.devDependencies ?? {}),
|
|
237
239
|
},
|
|
@@ -13,6 +13,7 @@ import { isNativeProject } from "../utils/native-service.js";
|
|
|
13
13
|
import { findProjectRoot } from "../utils/paths.js";
|
|
14
14
|
import { deriveServiceStates } from "../utils/service-runtime-state.js";
|
|
15
15
|
import { groupSharedPostgres, readProjectName } from "../utils/shared-infra.js";
|
|
16
|
+
import { groupServicesByStack, unhealthyServiceNames } from "../utils/status-groups.js";
|
|
16
17
|
import { buildServicePorts, buildStackPorts } from "../utils/status-ports.js";
|
|
17
18
|
import { isTiltAvailable, runTilt } from "../utils/tilt.js";
|
|
18
19
|
import { getTiltPollingPort } from "../utils/tilt-startup.js";
|
|
@@ -24,6 +25,7 @@ export const statusCommand = new Command("status")
|
|
|
24
25
|
.option("--stacks", "Show stack information (default)", true)
|
|
25
26
|
.option("--resources", "Show all discovered resources", false)
|
|
26
27
|
.option("--tilt", "Show tilt resource status", false)
|
|
28
|
+
.option("--unhealthy", "Show only services that are failing or still starting", false)
|
|
27
29
|
.action(async (options) => {
|
|
28
30
|
const action = async () => {
|
|
29
31
|
// Discover first: outside a project this fails before any status line is printed.
|
|
@@ -155,9 +157,12 @@ export const statusCommand = new Command("status")
|
|
|
155
157
|
name: stack.name,
|
|
156
158
|
resourceCount: stack.resourceCount,
|
|
157
159
|
resources: stack.resources.map((resource) => resource.name),
|
|
160
|
+
healthyCount: stack.resources.filter((resource) => serviceStates?.[resource.name]?.status === "ready").length,
|
|
158
161
|
})),
|
|
159
162
|
unassignedResources: discovery.unassignedResources.map((resource) => resource.name),
|
|
160
163
|
sharedInfra: sharedInfraGroup,
|
|
164
|
+
// Names of the failing or pending services, so a caller need not filter `resources` itself.
|
|
165
|
+
unhealthy: unhealthyServiceNames(discovery.resources, serviceStates),
|
|
161
166
|
};
|
|
162
167
|
console.log(JSON.stringify(createMachineEnvelope(data, errors)));
|
|
163
168
|
if (errors.length > 0) {
|
|
@@ -168,27 +173,29 @@ export const statusCommand = new Command("status")
|
|
|
168
173
|
}
|
|
169
174
|
console.log(chalk.bold("Resources:"), `${discovery.resources.length} discovered`);
|
|
170
175
|
console.log(chalk.bold("Stacks:"), `${discovery.stacks.length} defined`);
|
|
171
|
-
|
|
176
|
+
const hasStates = serviceStates !== null && Object.keys(serviceStates).length > 0;
|
|
177
|
+
const unhealthyOnly = Boolean(options.unhealthy);
|
|
178
|
+
// With service states the grouped "Services:" section below replaces the plain per-stack counts.
|
|
179
|
+
if (!hasStates && !unhealthyOnly) {
|
|
172
180
|
for (const stack of discovery.stacks) {
|
|
173
|
-
|
|
174
|
-
showDetail(`${stack.name}: ${formatCount(resourcesInStack, "resource")}`);
|
|
181
|
+
showDetail(`${stack.name}: ${formatCount(stack.resources.length, "resource")}`);
|
|
175
182
|
if (options.verbose) {
|
|
176
183
|
for (const resource of stack.resources) {
|
|
177
184
|
showDetail(`${resource.name}`, 6);
|
|
178
185
|
}
|
|
179
186
|
}
|
|
180
187
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
if (discovery.unassignedResources.length > 0) {
|
|
189
|
+
console.log();
|
|
190
|
+
console.log(chalk.yellow(`${formatCount(discovery.unassignedResources.length, "resource")} not in any stack:`));
|
|
191
|
+
if (options.verbose) {
|
|
192
|
+
for (const resource of discovery.unassignedResources) {
|
|
193
|
+
showDetail(`${resource.name}`);
|
|
194
|
+
}
|
|
188
195
|
}
|
|
189
196
|
}
|
|
190
197
|
}
|
|
191
|
-
if (sharedInfraGroup) {
|
|
198
|
+
if (sharedInfraGroup && !unhealthyOnly) {
|
|
192
199
|
console.log();
|
|
193
200
|
console.log(chalk.bold("Shared infrastructure:"));
|
|
194
201
|
showDetail(`${sharedInfraGroup.name} (one instance, ${formatCount(sharedInfraGroup.databases.length, "database")})`);
|
|
@@ -196,21 +203,33 @@ export const statusCommand = new Command("status")
|
|
|
196
203
|
showDetail(` ${database.name} (stack ${database.stack}): ${database.services.join(", ")}`);
|
|
197
204
|
}
|
|
198
205
|
}
|
|
199
|
-
if (
|
|
206
|
+
if (hasStates && serviceStates) {
|
|
207
|
+
const states = serviceStates;
|
|
200
208
|
console.log();
|
|
201
|
-
console.log(chalk.bold("Services:"));
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
209
|
+
console.log(chalk.bold(unhealthyOnly ? "Unhealthy services:" : "Services:"));
|
|
210
|
+
const unhealthy = new Set(unhealthyServiceNames(discovery.resources, states));
|
|
211
|
+
if (unhealthyOnly && unhealthy.size === 0) {
|
|
212
|
+
showDetail(chalk.green("All services are healthy."));
|
|
213
|
+
}
|
|
214
|
+
for (const group of groupServicesByStack(discovery.stacks, discovery.unassignedResources, states)) {
|
|
215
|
+
const shown = group.resources.filter((resource) => states[resource.name] && (!unhealthyOnly || unhealthy.has(resource.name)));
|
|
216
|
+
if (unhealthyOnly && shown.length === 0)
|
|
205
217
|
continue;
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
218
|
+
const summary = `${group.healthy}/${group.total} healthy`;
|
|
219
|
+
showDetail(`${chalk.bold(group.name)}: ${group.healthy === group.total ? chalk.green(summary) : chalk.yellow(summary)}`);
|
|
220
|
+
for (const resource of shown) {
|
|
221
|
+
const state = states[resource.name];
|
|
222
|
+
if (!state)
|
|
223
|
+
continue;
|
|
224
|
+
const mark = state.status === "ready"
|
|
225
|
+
? chalk.green("✓")
|
|
226
|
+
: state.status === "error"
|
|
227
|
+
? chalk.red("✗")
|
|
228
|
+
: chalk.yellow("…");
|
|
229
|
+
const ports = buildServicePorts(resource, portPlan?.ingressHttp);
|
|
230
|
+
const urlPart = ports.url ? ` → ${ports.url}` : "";
|
|
231
|
+
showDetail(`${mark} ${resource.name}${urlPart} ${state.status}${state.reason ? ` (${state.reason})` : ""}`, 6);
|
|
232
|
+
}
|
|
214
233
|
}
|
|
215
234
|
}
|
|
216
235
|
else if (discovery.resources.length > 0) {
|
package/cli/dist/commands/ui.js
CHANGED
|
@@ -7,6 +7,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
7
7
|
import { DetailPanel, FileTree, ResourceSelectInput, ResourceTable, ServiceIssues, TabBar, TUIHeader, } from "../components/index.js";
|
|
8
8
|
import { TABS } from "../components/TabBar.js";
|
|
9
9
|
import { createTUITheme, sectionTitle, TUIThemeContext, useTUITheme, } from "../components/ui-theme.js";
|
|
10
|
+
import { fetchAgentOverview } from "../utils/agent-sessions.js";
|
|
10
11
|
import { errorFactories, requireProjectRoot } from "../utils/errors.js";
|
|
11
12
|
import { readSavedHostPortPlan } from "../utils/host-port-config.js";
|
|
12
13
|
import { findProjectRoot, getPackageVersion } from "../utils/paths.js";
|
|
@@ -16,6 +17,7 @@ import { buildAdvertisedTargets } from "../utils/service-urls.js";
|
|
|
16
17
|
import { clearMetadataCache, discoverResources, discoverStacks, getResourceMetadata, getStackMetadata, } from "../utils/services.js";
|
|
17
18
|
import { singleFlight } from "../utils/single-flight.js";
|
|
18
19
|
import { createStatusMessageController } from "../utils/status-message.js";
|
|
20
|
+
import { buildServicePorts } from "../utils/status-ports.js";
|
|
19
21
|
import { getListRowFromMouseY, getTerminalRuleWidth } from "../utils/terminal-layout.js";
|
|
20
22
|
import { isTiltAvailable } from "../utils/tilt.js";
|
|
21
23
|
import { loadTiltEvents, stripTerminalControls, TiltEventsLoadError, } from "../utils/tilt-events.js";
|
|
@@ -134,12 +136,45 @@ export const TUIApp = ({ animated = true }) => {
|
|
|
134
136
|
});
|
|
135
137
|
const poll = () => run(() => fetchServiceStates(services, tiltPort));
|
|
136
138
|
void poll();
|
|
137
|
-
|
|
139
|
+
// The agent panel rides on this same timer: the selected agent is read once per tick, never one loop per agent.
|
|
140
|
+
const timer = setInterval(() => {
|
|
141
|
+
void poll();
|
|
142
|
+
void agentPollRef.current();
|
|
143
|
+
}, 5000);
|
|
138
144
|
return () => {
|
|
139
145
|
cancelled = true;
|
|
140
146
|
clearInterval(timer);
|
|
141
147
|
};
|
|
142
148
|
}, [services]);
|
|
149
|
+
// Readiness and sessions of the selected agent resource, read from the agent itself through its ingress route.
|
|
150
|
+
const [agentOverview, setAgentOverview] = useState(null);
|
|
151
|
+
const agentRoute = useMemo(() => {
|
|
152
|
+
const resource = services.find((s) => s.name === selectedService);
|
|
153
|
+
if (!resource || (resource.config?.appType ?? resource.type) !== "agent")
|
|
154
|
+
return null;
|
|
155
|
+
const url = buildServicePorts(resource, ingressPort).url;
|
|
156
|
+
return url ? { name: resource.name, url } : null;
|
|
157
|
+
}, [services, selectedService, ingressPort]);
|
|
158
|
+
const agentPollRef = useRef(async () => { });
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (!agentRoute) {
|
|
161
|
+
agentPollRef.current = async () => { };
|
|
162
|
+
setAgentOverview(null);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
let cancelled = false;
|
|
166
|
+
const run = singleFlight((overview) => {
|
|
167
|
+
if (!cancelled)
|
|
168
|
+
setAgentOverview({ name: agentRoute.name, overview });
|
|
169
|
+
});
|
|
170
|
+
agentPollRef.current = () => run(() => fetchAgentOverview(agentRoute.url));
|
|
171
|
+
setAgentOverview(null);
|
|
172
|
+
void agentPollRef.current();
|
|
173
|
+
return () => {
|
|
174
|
+
cancelled = true;
|
|
175
|
+
agentPollRef.current = async () => { };
|
|
176
|
+
};
|
|
177
|
+
}, [agentRoute]);
|
|
143
178
|
// Re-read service.json files from disk. An empty project is not an error:
|
|
144
179
|
// it renders EmptyState. Only a failed discovery shows ErrorScreen.
|
|
145
180
|
const refresh = useCallback(() => {
|
|
@@ -648,7 +683,11 @@ export const TUIApp = ({ animated = true }) => {
|
|
|
648
683
|
? theme.info
|
|
649
684
|
: theme.success, children: [formatEventTime(event.occurredAt), " ", event.resourceName, " ", event.title] }), event.details && (_jsx(Text, { color: theme.muted, children: formatEventDetails(event.details, mainPanelWidth) }))] }, event.id))) })) : (_jsx(Text, { color: theme.muted, children: "No recent build events are available from Tilt yet." }))] }))] })), activeTab === "config" && (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: theme.muted, children: sectionTitle(theme, "Configuration") }) }), selectedServiceData ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: theme.accent, children: selectedServiceData.service.configPath }), _jsx(Box, { marginTop: 1, ...(theme.ascii
|
|
650
685
|
? {}
|
|
651
|
-
: { borderStyle: "single", borderColor: theme.border }), paddingX: 1, children: _jsx(Text, { color: theme.muted, wrap: "wrap", children: JSON.stringify(selectedServiceData.service.config, null, 2).slice(0, 1000) }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.muted, children: "Select a service to view configuration" }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex, isActive: !isSearching, maxVisibleItems: pageSize, width: mainPanelWidth, onLayout: handleListLayout }) })] }))] }))] }), showSidebar && (_jsx(Box, { marginLeft: 2, children: _jsx(DetailPanel, { stack: selectedStackData?.stack || null, service: selectedServiceData?.service || null, stackMetadata: selectedStackData?.metadata || null,
|
|
686
|
+
: { borderStyle: "single", borderColor: theme.border }), paddingX: 1, children: _jsx(Text, { color: theme.muted, wrap: "wrap", children: JSON.stringify(selectedServiceData.service.config, null, 2).slice(0, 1000) }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.muted, children: "Select a service to view configuration" }), _jsx(Box, { marginTop: 1, children: _jsx(ResourceSelectInput, { items: items, onSelect: handleSelect, highlightedIndex: highlightedIndex, isActive: !isSearching, maxVisibleItems: pageSize, width: mainPanelWidth, onLayout: handleListLayout }) })] }))] }))] }), showSidebar && (_jsx(Box, { marginLeft: 2, children: _jsx(DetailPanel, { stack: selectedStackData?.stack || null, service: selectedServiceData?.service || null, stackMetadata: selectedStackData?.metadata || null, agent: agentRoute && selectedServiceData
|
|
687
|
+
? agentOverview?.name === agentRoute.name
|
|
688
|
+
? agentOverview.overview
|
|
689
|
+
: null
|
|
690
|
+
: undefined, visible: !!selectedStack || !!selectedService }) }))] }), _jsxs(Box, { ...(theme.ascii ? {} : { borderStyle: "single", borderColor: theme.border }), paddingX: 1, height: 3, flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: theme.accent, bold: true, children: [theme.bannerStart, " ", activeTab] }), _jsxs(Text, { color: theme.success, children: [theme.ascii ? "[+]" : "\u25cf", " ", services.filter((s) => s.stack).length, " in stack"] }), _jsxs(Text, { color: theme.warning, children: [theme.ascii ? "-" : "\u25cb", " ", services.filter((s) => !s.stack).length, " no stack"] })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: theme.muted, children: ["Stacks: ", stacks.length] }), _jsxs(Text, { color: theme.muted, children: ["Services: ", services.length] }), _jsxs(Text, { color: theme.muted, children: [theme.ascii ? "Mouse" : "🖱️", " ", mouseEnabled ? "ON" : "OFF", theme.separator, theme.ascii ? "Info" : "ℹ️", " ", showTooltips ? "ON" : "OFF", theme.separator, _jsx(Text, { color: showEnabledOnly ? theme.success : theme.warning, children: showEnabledOnly
|
|
652
691
|
? theme.ascii
|
|
653
692
|
? "[+] enabled"
|
|
654
693
|
: "\u2713 enabled"
|
package/cli/dist/commands/up.js
CHANGED
|
@@ -12,7 +12,7 @@ import { STANDARD_PORTS } from "../utils/constants.js";
|
|
|
12
12
|
import { getDeferredResourceNames, summarizeTiltBuildError } from "../utils/doctor-runtime.js";
|
|
13
13
|
import { applyEnvFileSelection, assertEngineSupportsEnvFile, EnvFileError, envFileWarning, PROJECT_ENV_FILE_VAR, selectEnvFile, } from "../utils/env-selection.js";
|
|
14
14
|
import { completeEnvFile } from "../utils/env-validator.js";
|
|
15
|
-
import { errorFactories, FliskError, getErrorMessage,
|
|
15
|
+
import { errorFactories, FliskError, getErrorMessage, requireProjectRoot, runCommand, showError, showErrorAndExit, withTiltCheck, } from "../utils/errors.js";
|
|
16
16
|
import { ensureSmokeRecordsIgnored } from "../utils/file-helpers.js";
|
|
17
17
|
import { evaluateFliskVersionFloor } from "../utils/flisk-version.js";
|
|
18
18
|
import { formatCount } from "../utils/formatting.js";
|
|
@@ -34,13 +34,15 @@ import { enforceServiceConfigGate, warnSchemaVersions } from "../utils/service-c
|
|
|
34
34
|
import { appendHealthPath, resolveServiceUrl, resolveSubdomainBases, } from "../utils/service-urls.js";
|
|
35
35
|
import { discoverResourcesStrict, discoverResourcesWithProblems, discoverStacks, getResourcesForStack, stackExists, warnAboutSkippedFiles, } from "../utils/services.js";
|
|
36
36
|
import { buildSmokePlans, formatSmokeFailure, runSmokePlans, safeSmokeDeps, } from "../utils/smoke.js";
|
|
37
|
-
import { buildStartupReport, formatStartupReport, isStartupStalled, } from "../utils/startup-report.js";
|
|
37
|
+
import { buildStartupReport, formatStartupReport, isStartupStalled, machineProblemFixes, } from "../utils/startup-report.js";
|
|
38
38
|
import { buildTiltUpArgs, runTilt } from "../utils/tilt.js";
|
|
39
39
|
import { classifyTiltPorts } from "../utils/tilt-port-owner.js";
|
|
40
40
|
import { stopTiltOnPort } from "../utils/tilt-process.js";
|
|
41
41
|
import { parseTiltPort, resolveTiltPort, secondUpAction, stopTiltForUp, } from "../utils/tilt-startup.js";
|
|
42
|
+
import { upFailureHints } from "../utils/up-hints.js";
|
|
42
43
|
import { findUnknownServices, resolveOnlySelection } from "../utils/up-only.js";
|
|
43
|
-
import {
|
|
44
|
+
import { createUpProgressReporter, fetchWithProgress, shouldRedrawUpProgress, } from "../utils/up-progress.js";
|
|
45
|
+
import { tiltGetUiResources, tiltResourceLogs, waitForTiltResourcesReady, } from "../utils/up-readiness.js";
|
|
44
46
|
import { enableDiscoveredStacks, writeDefaultProjectConfig } from "./project.js";
|
|
45
47
|
export function formatUpSuccess(port, options = {}) {
|
|
46
48
|
const { appUrls = [], serviceHealth = [], showLogsHint = serviceHealth.length > 0 } = options;
|
|
@@ -318,8 +320,11 @@ export const upCommand = new Command("up")
|
|
|
318
320
|
options.quiet = true;
|
|
319
321
|
const portSetting = parseTiltPort(process.env.TILT_PORT);
|
|
320
322
|
if (!portSetting.ok) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
+
const suggestions = upFailureHints({ kind: "invalid-tilt-port" });
|
|
324
|
+
emit?.({ ok: false }, [
|
|
325
|
+
{ code: "INVALID_TILT_PORT", message: portSetting.message, suggestions },
|
|
326
|
+
]);
|
|
327
|
+
showErrorAndExit(portSetting.message, 1, suggestions);
|
|
323
328
|
}
|
|
324
329
|
const configuredTiltPort = portSetting.port;
|
|
325
330
|
// First, before the host/Docker checks below: an old CLI must stop with one clear line, not a half-working stack.
|
|
@@ -441,7 +446,7 @@ export const upCommand = new Command("up")
|
|
|
441
446
|
}
|
|
442
447
|
if (!options.dryRun) {
|
|
443
448
|
const { assertMachineReadyOrExit } = await import("../utils/cold-preflight.js");
|
|
444
|
-
await assertMachineReadyOrExit();
|
|
449
|
+
await assertMachineReadyOrExit({ hints: upFailureHints({ kind: "machine" }) });
|
|
445
450
|
}
|
|
446
451
|
const action = async () => {
|
|
447
452
|
const foundRoot = options.dryRun ? requireProjectRoot() : findProjectRoot();
|
|
@@ -716,12 +721,16 @@ export const upCommand = new Command("up")
|
|
|
716
721
|
let startedDependencies = dependencyNames;
|
|
717
722
|
if (uiReady && emit) {
|
|
718
723
|
const deferred = getDeferredResourceNames(discoveredResources);
|
|
724
|
+
const dependsOn = Object.fromEntries(discoveredResources.map((r) => [r.name, r.config?.dependsOn ?? []]));
|
|
719
725
|
const readiness = await waitForTiltResourcesReady(port, {
|
|
720
726
|
deferred,
|
|
721
727
|
// Only what the caller named must be enabled. Dependencies are the Tiltfile's call (it skips some on purpose).
|
|
722
728
|
expected: options.only
|
|
723
729
|
? options.only.filter((name) => !deferred.has(name))
|
|
724
730
|
: undefined,
|
|
731
|
+
// As for the human output: stop as soon as everything left is blocked by a failure (for example init-networks),
|
|
732
|
+
// instead of waiting out the whole timeout.
|
|
733
|
+
isStalled: (text) => isStartupStalled(text, dependsOn, deferred),
|
|
725
734
|
});
|
|
726
735
|
if (readiness.ready) {
|
|
727
736
|
jsonReady = true;
|
|
@@ -735,11 +744,21 @@ export const upCommand = new Command("up")
|
|
|
735
744
|
}
|
|
736
745
|
else {
|
|
737
746
|
startupFailed = true;
|
|
738
|
-
const
|
|
747
|
+
const fixes = await machineProblemFixes(readiness.failures, (name) => tiltResourceLogs(port, name));
|
|
748
|
+
const detail = readiness.failures
|
|
749
|
+
.map((f) => {
|
|
750
|
+
const fix = fixes.get(f.name);
|
|
751
|
+
return `${f.name}: ${f.message}${fix ? `. ${fix}` : ""}`;
|
|
752
|
+
})
|
|
753
|
+
.join("; ");
|
|
739
754
|
emit({ ok: false, tiltUrl: `http://localhost:${port}`, failures: readiness.failures }, [
|
|
740
755
|
{
|
|
741
756
|
code: readiness.timedOut ? "UP_TIMEOUT" : "UP_RESOURCE_FAILED",
|
|
742
757
|
message: `${readiness.timedOut ? "Timed out waiting for resources" : "Resources failed"}${detail ? ` (${detail})` : ""}. Tilt is still running; inspect it or run: flisk down`,
|
|
758
|
+
suggestions: upFailureHints({
|
|
759
|
+
kind: "services",
|
|
760
|
+
failed: readiness.failures.map((failure) => failure.name),
|
|
761
|
+
}),
|
|
743
762
|
},
|
|
744
763
|
]);
|
|
745
764
|
}
|
|
@@ -749,10 +768,22 @@ export const upCommand = new Command("up")
|
|
|
749
768
|
if (uiReady && !emit) {
|
|
750
769
|
const deferred = getDeferredResourceNames(discoveredResources);
|
|
751
770
|
const dependsOn = Object.fromEntries(discoveredResources.map((r) => [r.name, r.config?.dependsOn ?? []]));
|
|
771
|
+
const progress = createUpProgressReporter({
|
|
772
|
+
write: (text) => process.stdout.write(text),
|
|
773
|
+
redraw: shouldRedrawUpProgress({
|
|
774
|
+
isTTY: process.stdout.isTTY,
|
|
775
|
+
quiet: Boolean(options.quiet),
|
|
776
|
+
env: process.env,
|
|
777
|
+
}),
|
|
778
|
+
deferred,
|
|
779
|
+
columns: process.stdout.columns,
|
|
780
|
+
});
|
|
752
781
|
const readiness = await waitForTiltResourcesReady(port, {
|
|
753
782
|
deferred,
|
|
754
783
|
isStalled: (text) => isStartupStalled(text, dependsOn, deferred),
|
|
784
|
+
fetchJson: fetchWithProgress(tiltGetUiResources, progress),
|
|
755
785
|
});
|
|
786
|
+
progress.finish();
|
|
756
787
|
if (!readiness.ready) {
|
|
757
788
|
startupFailed = true;
|
|
758
789
|
const resourcesJson = await tiltGetUiResources(port);
|
|
@@ -766,9 +797,13 @@ export const upCommand = new Command("up")
|
|
|
766
797
|
blocked: [],
|
|
767
798
|
starting: [],
|
|
768
799
|
};
|
|
769
|
-
const
|
|
800
|
+
const machineFixes = await machineProblemFixes(report.failed, (name) => tiltResourceLogs(port, name));
|
|
801
|
+
const reportLines = formatStartupReport(report, readiness.timedOut, (port) => findPortOccupant(port), machineFixes);
|
|
770
802
|
if (reportLines.length > 0) {
|
|
771
|
-
showError(reportLines.join("\n")
|
|
803
|
+
showError(reportLines.join("\n"), undefined, upFailureHints({
|
|
804
|
+
kind: "services",
|
|
805
|
+
failed: report.failed.map((failure) => failure.name),
|
|
806
|
+
}));
|
|
772
807
|
}
|
|
773
808
|
}
|
|
774
809
|
}
|
|
@@ -814,6 +849,7 @@ export const upCommand = new Command("up")
|
|
|
814
849
|
envFiles: envFileSelection ? [envFileSelection.path] : [],
|
|
815
850
|
}),
|
|
816
851
|
});
|
|
852
|
+
const smokeFailures = [];
|
|
817
853
|
for (const smokeResult of results) {
|
|
818
854
|
if (smokeResult.ok) {
|
|
819
855
|
if (!options.quiet)
|
|
@@ -821,13 +857,20 @@ export const upCommand = new Command("up")
|
|
|
821
857
|
}
|
|
822
858
|
else {
|
|
823
859
|
smokeFailed = true;
|
|
860
|
+
smokeFailures.push(smokeResult.name);
|
|
824
861
|
showError(formatSmokeFailure(smokeResult));
|
|
825
862
|
}
|
|
826
863
|
}
|
|
827
864
|
if (smokeFailed) {
|
|
828
865
|
stopTiltOnPort(port);
|
|
866
|
+
const suggestions = upFailureHints({ kind: "smoke", failed: smokeFailures });
|
|
867
|
+
showError("A smoke check failed; Tilt was stopped", undefined, suggestions);
|
|
829
868
|
emit?.({ ok: false }, [
|
|
830
|
-
{
|
|
869
|
+
{
|
|
870
|
+
code: "UP_SMOKE_FAILED",
|
|
871
|
+
message: "A smoke check failed; Tilt was stopped",
|
|
872
|
+
suggestions,
|
|
873
|
+
},
|
|
831
874
|
]);
|
|
832
875
|
}
|
|
833
876
|
}
|
|
@@ -849,7 +892,7 @@ export const upCommand = new Command("up")
|
|
|
849
892
|
process.exit(1);
|
|
850
893
|
}
|
|
851
894
|
if (result.exitCode !== 0) {
|
|
852
|
-
|
|
895
|
+
showErrorAndExit(`tilt up failed with exit code ${result.exitCode}`, result.exitCode, upFailureHints({ kind: "tilt-exited" }));
|
|
853
896
|
}
|
|
854
897
|
if (startupFailed && result.exitCode === 0) {
|
|
855
898
|
process.exit(1);
|
|
@@ -1,10 +1,30 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// Copyright (c) 2026 Flisk Landscape contributors
|
|
3
3
|
// SPDX-License-Identifier: MIT
|
|
4
4
|
import { Box, Text } from "ink";
|
|
5
|
+
import { formatAge } from "../utils/agent-format.js";
|
|
5
6
|
import { formatDate, getStatusColor, getStatusIcon } from "../utils/formatting.js";
|
|
6
7
|
import { getTUIStatusColor, getTUIStatusIcon, useTUITheme } from "./ui-theme.js";
|
|
7
|
-
|
|
8
|
+
const MAX_LISTED_SESSIONS = 5;
|
|
9
|
+
/** Readiness, session count, last activity and the newest sessions of an agent; `null` while the first poll is pending. */
|
|
10
|
+
const AgentSection = ({ agent }) => {
|
|
11
|
+
const theme = useTUITheme();
|
|
12
|
+
const row = (label, value, color = theme.foreground) => (_jsxs(Box, { children: [_jsxs(Text, { color: theme.muted, children: [label, ": "] }), _jsx(Text, { color: color, children: value })] }));
|
|
13
|
+
let body;
|
|
14
|
+
if (agent === null) {
|
|
15
|
+
body = row("Agent", "checking...", theme.muted);
|
|
16
|
+
}
|
|
17
|
+
else if (agent.state === "not-running") {
|
|
18
|
+
body = row("Agent", "not running", theme.warning);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
const { sessions } = agent;
|
|
22
|
+
const newest = sessions?.sessions[0]?.lastActivity ?? null;
|
|
23
|
+
body = (_jsxs(_Fragment, { children: [row("Ready", agent.ready ? "yes" : "no", agent.ready ? theme.success : theme.warning), sessions ? (_jsxs(_Fragment, { children: [row("Sessions", String(sessions.total)), row("Last activity", formatAge(newest)), sessions.sessions.slice(0, MAX_LISTED_SESSIONS).map((s) => (_jsxs(Box, { children: [_jsx(Text, { color: theme.foreground, children: s.sessionId.slice(0, 18) }), _jsxs(Text, { color: theme.muted, children: [" ", formatAge(s.lastActivity), " ", s.status] })] }, s.sessionId))), sessions.total > MAX_LISTED_SESSIONS && (_jsxs(Text, { color: theme.muted, children: ["... ", sessions.total - MAX_LISTED_SESSIONS, " more"] }))] })) : (row("Sessions", "not available (agent has no /sessions)", theme.muted))] }));
|
|
24
|
+
}
|
|
25
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: theme.muted, underline: true, children: "Agent" }), body] }));
|
|
26
|
+
};
|
|
27
|
+
export const DetailPanel = ({ stack, service, stackMetadata, agent, visible, }) => {
|
|
8
28
|
const theme = useTUITheme();
|
|
9
29
|
if (!visible) {
|
|
10
30
|
return _jsx(Box, { width: 0 });
|
|
@@ -13,7 +33,7 @@ export const DetailPanel = ({ stack, service, stackMetadata, visible, }) => {
|
|
|
13
33
|
const title = theme.ascii
|
|
14
34
|
? `--- ${service.name.toUpperCase()} ---`
|
|
15
35
|
: `\u250C\u2500 ${service.name.toUpperCase()} \u2500\u2510`;
|
|
16
|
-
return (_jsxs(Box, { width: 40, flexDirection: "column", borderStyle: theme.ascii ? undefined : "single", borderColor: theme.border, paddingX: 1, paddingY: 1, children: [_jsx(Box, { marginBottom: 1, justifyContent: "center", children: _jsx(Text, { color: theme.accent, bold: true, children: title }) }), _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.muted, children: "Name: " }), _jsx(Text, { color: theme.foreground, children: service.name })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.muted, children: "Stack: " }), _jsx(Text, { color: theme.foreground, children: service.stack || "unknown" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.muted, children: "Type: " }), _jsx(Text, { color: theme.warning, bold: theme.highContrast, children: (service.stack || "") === "platform" ? "PLATFORM" : "PRODUCT" })] })] }), _jsx(Box, { marginTop: 2, children: _jsxs(Text, { color: theme.muted, dimColor: theme.dimMuted, children: ["Press ", _jsx(Text, { color: theme.accent, children: "[Esc]" }), " to close"] }) })] }));
|
|
36
|
+
return (_jsxs(Box, { width: 40, flexDirection: "column", borderStyle: theme.ascii ? undefined : "single", borderColor: theme.border, paddingX: 1, paddingY: 1, children: [_jsx(Box, { marginBottom: 1, justifyContent: "center", children: _jsx(Text, { color: theme.accent, bold: true, children: title }) }), _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.muted, children: "Name: " }), _jsx(Text, { color: theme.foreground, children: service.name })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.muted, children: "Stack: " }), _jsx(Text, { color: theme.foreground, children: service.stack || "unknown" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.muted, children: "Type: " }), _jsx(Text, { color: theme.warning, bold: theme.highContrast, children: (service.stack || "") === "platform" ? "PLATFORM" : "PRODUCT" })] })] }), agent !== undefined && _jsx(AgentSection, { agent: agent }), _jsx(Box, { marginTop: 2, children: _jsxs(Text, { color: theme.muted, dimColor: theme.dimMuted, children: ["Press ", _jsx(Text, { color: theme.accent, children: "[Esc]" }), " to close"] }) })] }));
|
|
17
37
|
}
|
|
18
38
|
if (stack && stackMetadata) {
|
|
19
39
|
const statusColor = getTUIStatusColor(theme, getStatusColor(stackMetadata.overallStatus));
|
package/cli/dist/mcp/tools.js
CHANGED
|
@@ -328,23 +328,26 @@ export function createFliskTools(run = runFliskCli, up = defaultUpDeps) {
|
|
|
328
328
|
},
|
|
329
329
|
{
|
|
330
330
|
name: "get_agent_sessions",
|
|
331
|
-
description: "
|
|
331
|
+
description: "List the sessions of a running AI agent resource (id, last activity, status, checkpoint size), or read one session's checkpoint when sessionId is set. Set projectPath when the MCP server is not running from the project root.",
|
|
332
332
|
inputSchema: {
|
|
333
333
|
...OBJECT({
|
|
334
334
|
projectPath: PROJECT_PATH,
|
|
335
335
|
resource: { type: "string", description: "Name of the agent resource" },
|
|
336
|
-
sessionId: {
|
|
336
|
+
sessionId: {
|
|
337
|
+
type: "string",
|
|
338
|
+
description: "Session ID to read; omit it to list all sessions",
|
|
339
|
+
},
|
|
337
340
|
}),
|
|
338
|
-
required: ["resource"
|
|
341
|
+
required: ["resource"],
|
|
339
342
|
},
|
|
340
343
|
handler: viaCli((args) => {
|
|
341
344
|
const resource = optionalName(args.resource, "resource");
|
|
342
345
|
if (!resource)
|
|
343
346
|
throw new Error("resource is required");
|
|
344
347
|
const sessionId = optionalName(args.sessionId, "sessionId");
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
+
return sessionId
|
|
349
|
+
? ["agent", "sessions", resource, `--session-id=${sessionId}`, "--json"]
|
|
350
|
+
: ["agent", "sessions", resource, "--json"];
|
|
348
351
|
}),
|
|
349
352
|
},
|
|
350
353
|
];
|