feat(cli): friendly missing-project status and per-project daemon state (#87)

- Block project-aware commands when ktx.yaml is absent and render a
  friendly "run ktx setup" message (plain or JSON) instead of leaking
  ENOENT or "Project: ..." noise.
- Make ktx status project detect the missing config and emit the same
  message via a shared renderMissingProjectMessage helper.
- Move the managed Python daemon state, stdout, and stderr files out of
  the shared runtime root into {projectDir}/.ktx/runtime so multiple
  projects no longer share a single daemon record.
- Simplify the runtime install root to ~/.ktx/runtime on every platform
  and split the daemon-specific paths into managedPythonDaemonLayout,
  threading projectDir through start, stop, and stop-all paths.
This commit is contained in:
Andrey Avtomonov 2026-05-14 14:35:55 +02:00 committed by GitHub
parent 6d7d90571e
commit e28b10454a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 450 additions and 248 deletions

View file

@ -21,19 +21,20 @@ import {
export type KtxRuntimeArgs =
| { command: 'install'; cliVersion: string; feature: KtxRuntimeFeature; force: boolean }
| { command: 'start'; cliVersion: string; feature: KtxRuntimeFeature; force: boolean }
| { command: 'stop'; cliVersion: string; all: boolean }
| { command: 'start'; cliVersion: string; projectDir: string; feature: KtxRuntimeFeature; force: boolean }
| { command: 'stop'; cliVersion: string; projectDir: string; all: boolean }
| { command: 'status'; cliVersion: string; json: boolean };
export interface KtxRuntimeDeps {
installRuntime?: (options: ManagedPythonRuntimeInstallOptions) => Promise<ManagedPythonRuntimeInstallResult>;
startDaemon?: (options: {
cliVersion: string;
projectDir: string;
features: KtxRuntimeFeature[];
force?: boolean;
}) => Promise<ManagedPythonDaemonStartResult>;
stopDaemon?: (options: { cliVersion: string }) => Promise<ManagedPythonDaemonStopResult>;
stopAllDaemons?: (options: { cliVersion: string }) => Promise<ManagedPythonDaemonStopAllResult>;
stopDaemon?: (options: { cliVersion: string; projectDir: string }) => Promise<ManagedPythonDaemonStopResult>;
stopAllDaemons?: (options: { cliVersion: string; projectDir: string }) => Promise<ManagedPythonDaemonStopAllResult>;
readStatus?: (options: ManagedPythonRuntimeLayoutOptions) => Promise<ManagedPythonRuntimeStatus>;
doctorRuntime?: (options: ManagedPythonRuntimeLayoutOptions) => Promise<ManagedPythonRuntimeDoctorCheck[]>;
}
@ -174,6 +175,7 @@ export async function runKtxRuntime(
const startDaemon = deps.startDaemon ?? startManagedPythonDaemon;
const result = await startDaemon({
cliVersion: args.cliVersion,
projectDir: args.projectDir,
features: [args.feature],
force: args.force,
});
@ -183,11 +185,11 @@ export async function runKtxRuntime(
if (args.command === 'stop') {
if (args.all) {
const stopAllDaemons = deps.stopAllDaemons ?? stopAllManagedPythonDaemons;
const result = await stopAllDaemons({ cliVersion: args.cliVersion });
const result = await stopAllDaemons({ cliVersion: args.cliVersion, projectDir: args.projectDir });
return writeDaemonStopAll(io, result);
} else {
const stopDaemon = deps.stopDaemon ?? stopManagedPythonDaemon;
const result = await stopDaemon({ cliVersion: args.cliVersion });
const result = await stopDaemon({ cliVersion: args.cliVersion, projectDir: args.projectDir });
writeDaemonStop(io, result);
return 0;
}