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

@ -45,9 +45,6 @@ function runtime(): ManagedPythonCommandRuntime {
assetManifestPath: '/assets/python/manifest.json',
pythonPath: '/runtime/0.2.0/.venv/bin/python',
daemonPath: '/runtime/0.2.0/.venv/bin/ktx-daemon',
daemonStatePath: '/runtime/0.2.0/daemon.json',
daemonStdoutPath: '/runtime/0.2.0/daemon.stdout.log',
daemonStderrPath: '/runtime/0.2.0/daemon.stderr.log',
},
manifest: {
schemaVersion: 1,
@ -77,7 +74,14 @@ function runtime(): ManagedPythonCommandRuntime {
function daemonResult(status: 'started' | 'reused' = 'reused'): ManagedPythonDaemonStartResult {
return {
status,
layout: runtime().layout,
layout: {
...runtime().layout,
projectDir: '/work/proj',
daemonStateDir: '/work/proj/.ktx/runtime',
daemonStatePath: '/work/proj/.ktx/runtime/daemon.json',
daemonStdoutPath: '/work/proj/.ktx/runtime/daemon.stdout.log',
daemonStderrPath: '/work/proj/.ktx/runtime/daemon.stderr.log',
},
baseUrl: 'http://127.0.0.1:61234',
state: {
schemaVersion: 1,
@ -87,8 +91,8 @@ function daemonResult(status: 'started' | 'reused' = 'reused'): ManagedPythonDae
version: '0.2.0',
features: ['core', 'local-embeddings'],
startedAt: '2026-05-11T00:00:00.000Z',
stdoutLog: '/runtime/0.2.0/daemon.stdout.log',
stderrLog: '/runtime/0.2.0/daemon.stderr.log',
stdoutLog: '/work/proj/.ktx/runtime/daemon.stdout.log',
stderrLog: '/work/proj/.ktx/runtime/daemon.stderr.log',
},
};
}
@ -138,6 +142,7 @@ describe('ensureManagedLocalEmbeddingsDaemon', () => {
await expect(
ensureManagedLocalEmbeddingsDaemon({
cliVersion: '0.2.0',
projectDir: '/work/proj',
installPolicy: 'auto',
io: io.io,
ensureRuntime,
@ -158,6 +163,7 @@ describe('ensureManagedLocalEmbeddingsDaemon', () => {
});
expect(startDaemon).toHaveBeenCalledWith({
cliVersion: '0.2.0',
projectDir: '/work/proj',
features: ['local-embeddings'],
force: false,
});
@ -169,6 +175,7 @@ describe('ensureManagedLocalEmbeddingsDaemon', () => {
await ensureManagedLocalEmbeddingsDaemon({
cliVersion: '0.2.0',
projectDir: '/work/proj',
installPolicy: 'prompt',
io: io.io,
ensureRuntime: vi.fn(async () => runtime()),