@mandipadk7/kavi 0.1.0
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 +108 -0
- package/bin/kavi.js +44 -0
- package/dist/adapters/claude.js +122 -0
- package/dist/adapters/codex.js +45 -0
- package/dist/adapters/shared.js +69 -0
- package/dist/approvals.js +185 -0
- package/dist/command-queue.js +25 -0
- package/dist/config.js +175 -0
- package/dist/daemon.js +202 -0
- package/dist/doctor.js +78 -0
- package/dist/fs.js +30 -0
- package/dist/git.js +289 -0
- package/dist/history.js +39 -0
- package/dist/main.js +667 -0
- package/dist/paths.js +43 -0
- package/dist/process.js +72 -0
- package/dist/router.js +55 -0
- package/dist/runtime.js +43 -0
- package/dist/session.js +113 -0
- package/dist/task-artifacts.js +37 -0
- package/dist/toml.js +55 -0
- package/dist/tui.js +92 -0
- package/dist/types.js +3 -0
- package/package.json +53 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ensureDir, fileExists } from "./fs.js";
|
|
4
|
+
import { parseToml } from "./toml.js";
|
|
5
|
+
const DEFAULT_CONFIG = `version = 1
|
|
6
|
+
base_branch = "main"
|
|
7
|
+
validation_command = "npm test"
|
|
8
|
+
message_limit = 6
|
|
9
|
+
|
|
10
|
+
[routing]
|
|
11
|
+
frontend_keywords = ["frontend", "ui", "ux", "design", "copy", "react", "css", "html"]
|
|
12
|
+
backend_keywords = ["backend", "api", "server", "db", "schema", "migration", "auth", "test"]
|
|
13
|
+
|
|
14
|
+
[agents.codex]
|
|
15
|
+
role = "planning-backend"
|
|
16
|
+
model = ""
|
|
17
|
+
|
|
18
|
+
[agents.claude]
|
|
19
|
+
role = "frontend-intent"
|
|
20
|
+
model = ""
|
|
21
|
+
`;
|
|
22
|
+
const DEFAULT_PROMPTS = {
|
|
23
|
+
"codex.md": `You are Codex inside Kavi.
|
|
24
|
+
|
|
25
|
+
Default role:
|
|
26
|
+
- Own planning, architecture, backend, debugging, and review-heavy work.
|
|
27
|
+
- Keep updates compact and task-scoped.
|
|
28
|
+
- Emit peer messages only when they materially help Claude.
|
|
29
|
+
`,
|
|
30
|
+
"claude.md": `You are Claude inside Kavi.
|
|
31
|
+
|
|
32
|
+
Default role:
|
|
33
|
+
- Own frontend, UX, intent-shaping, copy, and product-sense work.
|
|
34
|
+
- Keep updates compact and task-scoped.
|
|
35
|
+
- Emit peer messages only when they materially help Codex.
|
|
36
|
+
`
|
|
37
|
+
};
|
|
38
|
+
const DEFAULT_HOME_CONFIG = `version = 1
|
|
39
|
+
|
|
40
|
+
[runtime]
|
|
41
|
+
node_bin = ""
|
|
42
|
+
codex_bin = "codex"
|
|
43
|
+
claude_bin = "claude"
|
|
44
|
+
`;
|
|
45
|
+
function asString(value, fallback) {
|
|
46
|
+
return typeof value === "string" ? value : fallback;
|
|
47
|
+
}
|
|
48
|
+
function asNumber(value, fallback) {
|
|
49
|
+
return typeof value === "number" ? value : fallback;
|
|
50
|
+
}
|
|
51
|
+
function asStringArray(value, fallback) {
|
|
52
|
+
return Array.isArray(value) ? value.map((item)=>String(item)) : fallback;
|
|
53
|
+
}
|
|
54
|
+
export function defaultConfig() {
|
|
55
|
+
return {
|
|
56
|
+
version: 1,
|
|
57
|
+
baseBranch: "main",
|
|
58
|
+
validationCommand: "npm test",
|
|
59
|
+
messageLimit: 6,
|
|
60
|
+
routing: {
|
|
61
|
+
frontendKeywords: [
|
|
62
|
+
"frontend",
|
|
63
|
+
"ui",
|
|
64
|
+
"ux",
|
|
65
|
+
"design",
|
|
66
|
+
"copy",
|
|
67
|
+
"react",
|
|
68
|
+
"css",
|
|
69
|
+
"html"
|
|
70
|
+
],
|
|
71
|
+
backendKeywords: [
|
|
72
|
+
"backend",
|
|
73
|
+
"api",
|
|
74
|
+
"server",
|
|
75
|
+
"db",
|
|
76
|
+
"schema",
|
|
77
|
+
"migration",
|
|
78
|
+
"auth",
|
|
79
|
+
"test"
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
agents: {
|
|
83
|
+
codex: {
|
|
84
|
+
role: "planning-backend",
|
|
85
|
+
model: ""
|
|
86
|
+
},
|
|
87
|
+
claude: {
|
|
88
|
+
role: "frontend-intent",
|
|
89
|
+
model: ""
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function defaultHomeConfig() {
|
|
95
|
+
return {
|
|
96
|
+
version: 1,
|
|
97
|
+
runtime: {
|
|
98
|
+
nodeBin: "",
|
|
99
|
+
codexBin: "codex",
|
|
100
|
+
claudeBin: "claude"
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export async function ensureProjectScaffold(paths) {
|
|
105
|
+
await ensureDir(paths.kaviDir);
|
|
106
|
+
await ensureDir(paths.promptsDir);
|
|
107
|
+
await ensureDir(paths.stateDir);
|
|
108
|
+
await ensureDir(paths.runtimeDir);
|
|
109
|
+
await ensureDir(paths.runsDir);
|
|
110
|
+
if (!await fileExists(paths.configFile)) {
|
|
111
|
+
await fs.writeFile(paths.configFile, DEFAULT_CONFIG, "utf8");
|
|
112
|
+
}
|
|
113
|
+
for (const [fileName, content] of Object.entries(DEFAULT_PROMPTS)){
|
|
114
|
+
const promptPath = path.join(paths.promptsDir, fileName);
|
|
115
|
+
if (!await fileExists(promptPath)) {
|
|
116
|
+
await fs.writeFile(promptPath, content, "utf8");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export async function ensureHomeConfig(paths) {
|
|
121
|
+
await ensureDir(paths.homeConfigDir);
|
|
122
|
+
if (!await fileExists(paths.homeConfigFile)) {
|
|
123
|
+
await fs.writeFile(paths.homeConfigFile, DEFAULT_HOME_CONFIG, "utf8");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export async function loadConfig(paths) {
|
|
127
|
+
if (!await fileExists(paths.configFile)) {
|
|
128
|
+
return defaultConfig();
|
|
129
|
+
}
|
|
130
|
+
const content = await fs.readFile(paths.configFile, "utf8");
|
|
131
|
+
const parsed = parseToml(content);
|
|
132
|
+
const routing = parsed.routing ?? {};
|
|
133
|
+
const agents = parsed.agents ?? {};
|
|
134
|
+
const codex = agents.codex ?? {};
|
|
135
|
+
const claude = agents.claude ?? {};
|
|
136
|
+
return {
|
|
137
|
+
version: asNumber(parsed.version, 1),
|
|
138
|
+
baseBranch: asString(parsed.base_branch, "main"),
|
|
139
|
+
validationCommand: asString(parsed.validation_command, "npm test"),
|
|
140
|
+
messageLimit: asNumber(parsed.message_limit, 6),
|
|
141
|
+
routing: {
|
|
142
|
+
frontendKeywords: asStringArray(routing.frontend_keywords, defaultConfig().routing.frontendKeywords),
|
|
143
|
+
backendKeywords: asStringArray(routing.backend_keywords, defaultConfig().routing.backendKeywords)
|
|
144
|
+
},
|
|
145
|
+
agents: {
|
|
146
|
+
codex: {
|
|
147
|
+
role: asString(codex.role, "planning-backend"),
|
|
148
|
+
model: asString(codex.model, "")
|
|
149
|
+
},
|
|
150
|
+
claude: {
|
|
151
|
+
role: asString(claude.role, "frontend-intent"),
|
|
152
|
+
model: asString(claude.model, "")
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
export async function loadHomeConfig(paths) {
|
|
158
|
+
if (!await fileExists(paths.homeConfigFile)) {
|
|
159
|
+
return defaultHomeConfig();
|
|
160
|
+
}
|
|
161
|
+
const content = await fs.readFile(paths.homeConfigFile, "utf8");
|
|
162
|
+
const parsed = parseToml(content);
|
|
163
|
+
const runtime = parsed.runtime ?? {};
|
|
164
|
+
return {
|
|
165
|
+
version: asNumber(parsed.version, 1),
|
|
166
|
+
runtime: {
|
|
167
|
+
nodeBin: asString(runtime.node_bin, ""),
|
|
168
|
+
codexBin: asString(runtime.codex_bin, "codex"),
|
|
169
|
+
claudeBin: asString(runtime.claude_bin, "claude")
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
//# sourceURL=config.ts
|
package/dist/daemon.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { consumeCommands } from "./command-queue.js";
|
|
2
|
+
import { buildPeerMessages as buildClaudePeerMessages, runClaudeTask } from "./adapters/claude.js";
|
|
3
|
+
import { buildPeerMessages as buildCodexPeerMessages, runCodexTask } from "./adapters/codex.js";
|
|
4
|
+
import { nowIso } from "./paths.js";
|
|
5
|
+
import { buildAdHocTask, buildKickoffTasks } from "./router.js";
|
|
6
|
+
import { loadSessionRecord, recordEvent, saveSessionRecord } from "./session.js";
|
|
7
|
+
import { saveTaskArtifact } from "./task-artifacts.js";
|
|
8
|
+
export class KaviDaemon {
|
|
9
|
+
paths;
|
|
10
|
+
session;
|
|
11
|
+
managedSessionId = "";
|
|
12
|
+
running = false;
|
|
13
|
+
processing = false;
|
|
14
|
+
interval = null;
|
|
15
|
+
stopResolver = null;
|
|
16
|
+
constructor(paths){
|
|
17
|
+
this.paths = paths;
|
|
18
|
+
}
|
|
19
|
+
async start() {
|
|
20
|
+
this.session = await loadSessionRecord(this.paths);
|
|
21
|
+
this.managedSessionId = this.session.id;
|
|
22
|
+
this.session.status = "running";
|
|
23
|
+
this.session.daemonPid = process.pid;
|
|
24
|
+
this.session.daemonHeartbeatAt = new Date().toISOString();
|
|
25
|
+
await saveSessionRecord(this.paths, this.session);
|
|
26
|
+
await recordEvent(this.paths, this.session.id, "daemon.started", {
|
|
27
|
+
daemonPid: process.pid
|
|
28
|
+
});
|
|
29
|
+
this.running = true;
|
|
30
|
+
void this.tick();
|
|
31
|
+
this.interval = setInterval(()=>{
|
|
32
|
+
void this.tick();
|
|
33
|
+
}, 1000);
|
|
34
|
+
await new Promise((resolve)=>{
|
|
35
|
+
this.stopResolver = resolve;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async tick() {
|
|
39
|
+
if (!this.running || this.processing) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
this.processing = true;
|
|
43
|
+
try {
|
|
44
|
+
const onDiskSession = await loadSessionRecord(this.paths);
|
|
45
|
+
if (onDiskSession.id !== this.managedSessionId || onDiskSession.status === "stopped") {
|
|
46
|
+
this.running = false;
|
|
47
|
+
if (this.interval) {
|
|
48
|
+
clearInterval(this.interval);
|
|
49
|
+
this.interval = null;
|
|
50
|
+
}
|
|
51
|
+
this.stopResolver?.();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.session = onDiskSession;
|
|
55
|
+
this.session.daemonPid = process.pid;
|
|
56
|
+
this.session.daemonHeartbeatAt = new Date().toISOString();
|
|
57
|
+
await saveSessionRecord(this.paths, this.session);
|
|
58
|
+
await this.consumeQueuedCommands();
|
|
59
|
+
if (this.session.tasks.length === 0 && this.session.goal) {
|
|
60
|
+
this.session.tasks = buildKickoffTasks(this.session.goal);
|
|
61
|
+
await saveSessionRecord(this.paths, this.session);
|
|
62
|
+
await recordEvent(this.paths, this.session.id, "tasks.kickoff_created", {
|
|
63
|
+
count: this.session.tasks.length
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const pending = this.session.tasks.filter((task)=>task.status === "pending");
|
|
67
|
+
for (const task of pending){
|
|
68
|
+
await this.runTask(task);
|
|
69
|
+
}
|
|
70
|
+
} finally{
|
|
71
|
+
this.processing = false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async consumeQueuedCommands() {
|
|
75
|
+
const commands = await consumeCommands(this.paths);
|
|
76
|
+
if (commands.length === 0) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
for (const command of commands){
|
|
80
|
+
if (command.type === "shutdown") {
|
|
81
|
+
this.session.status = "stopped";
|
|
82
|
+
this.running = false;
|
|
83
|
+
this.session.daemonHeartbeatAt = new Date().toISOString();
|
|
84
|
+
await saveSessionRecord(this.paths, this.session);
|
|
85
|
+
await recordEvent(this.paths, this.session.id, "daemon.stopped", {});
|
|
86
|
+
if (this.interval) {
|
|
87
|
+
clearInterval(this.interval);
|
|
88
|
+
this.interval = null;
|
|
89
|
+
}
|
|
90
|
+
this.stopResolver?.();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (command.type === "kickoff" && typeof command.payload.prompt === "string") {
|
|
94
|
+
this.session.goal = command.payload.prompt;
|
|
95
|
+
this.session.tasks.push(...buildKickoffTasks(command.payload.prompt));
|
|
96
|
+
await saveSessionRecord(this.paths, this.session);
|
|
97
|
+
await recordEvent(this.paths, this.session.id, "tasks.kickoff_enqueued", {
|
|
98
|
+
count: 2
|
|
99
|
+
});
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (command.type === "enqueue" && typeof command.payload.prompt === "string") {
|
|
103
|
+
const owner = command.payload.owner === "claude" ? "claude" : "codex";
|
|
104
|
+
this.session.tasks.push(buildAdHocTask(owner, command.payload.prompt, `task-${command.id}`));
|
|
105
|
+
await saveSessionRecord(this.paths, this.session);
|
|
106
|
+
await recordEvent(this.paths, this.session.id, "task.enqueued", {
|
|
107
|
+
owner
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async runTask(task) {
|
|
113
|
+
const startedAt = nowIso();
|
|
114
|
+
task.status = "running";
|
|
115
|
+
task.updatedAt = startedAt;
|
|
116
|
+
await saveSessionRecord(this.paths, this.session);
|
|
117
|
+
await recordEvent(this.paths, this.session.id, "task.started", {
|
|
118
|
+
taskId: task.id,
|
|
119
|
+
owner: task.owner
|
|
120
|
+
});
|
|
121
|
+
try {
|
|
122
|
+
let envelope;
|
|
123
|
+
let peerMessages;
|
|
124
|
+
let rawOutput = null;
|
|
125
|
+
if (task.owner === "codex") {
|
|
126
|
+
const result = await runCodexTask(this.session, task, this.paths);
|
|
127
|
+
envelope = result.envelope;
|
|
128
|
+
rawOutput = result.raw;
|
|
129
|
+
peerMessages = buildCodexPeerMessages(result.envelope, "codex", task.id);
|
|
130
|
+
await this.markAgent("codex", result.envelope.summary, 0, null);
|
|
131
|
+
} else if (task.owner === "claude") {
|
|
132
|
+
const result = await runClaudeTask(this.session, task, this.paths);
|
|
133
|
+
envelope = result.envelope;
|
|
134
|
+
rawOutput = result.raw;
|
|
135
|
+
peerMessages = buildClaudePeerMessages(result.envelope, "claude", task.id);
|
|
136
|
+
await this.markAgent("claude", result.envelope.summary, 0, `${this.session.id}-claude`);
|
|
137
|
+
} else {
|
|
138
|
+
throw new Error(`Unsupported task owner ${task.owner}.`);
|
|
139
|
+
}
|
|
140
|
+
task.status = envelope.status === "completed" ? "completed" : "blocked";
|
|
141
|
+
task.summary = envelope.summary;
|
|
142
|
+
task.updatedAt = new Date().toISOString();
|
|
143
|
+
this.session.peerMessages.push(...peerMessages);
|
|
144
|
+
await saveSessionRecord(this.paths, this.session);
|
|
145
|
+
await saveTaskArtifact(this.paths, {
|
|
146
|
+
taskId: task.id,
|
|
147
|
+
sessionId: this.session.id,
|
|
148
|
+
title: task.title,
|
|
149
|
+
owner: task.owner,
|
|
150
|
+
status: task.status,
|
|
151
|
+
summary: task.summary,
|
|
152
|
+
rawOutput,
|
|
153
|
+
error: null,
|
|
154
|
+
envelope,
|
|
155
|
+
startedAt,
|
|
156
|
+
finishedAt: task.updatedAt
|
|
157
|
+
});
|
|
158
|
+
await recordEvent(this.paths, this.session.id, "task.completed", {
|
|
159
|
+
taskId: task.id,
|
|
160
|
+
owner: task.owner,
|
|
161
|
+
status: task.status,
|
|
162
|
+
peerMessages: peerMessages.length
|
|
163
|
+
});
|
|
164
|
+
} catch (error) {
|
|
165
|
+
task.status = "failed";
|
|
166
|
+
task.summary = error instanceof Error ? error.message : String(error);
|
|
167
|
+
task.updatedAt = new Date().toISOString();
|
|
168
|
+
await this.markAgent(task.owner, task.summary, 1, task.owner === "claude" ? `${this.session.id}-claude` : null);
|
|
169
|
+
await saveSessionRecord(this.paths, this.session);
|
|
170
|
+
await saveTaskArtifact(this.paths, {
|
|
171
|
+
taskId: task.id,
|
|
172
|
+
sessionId: this.session.id,
|
|
173
|
+
title: task.title,
|
|
174
|
+
owner: task.owner,
|
|
175
|
+
status: task.status,
|
|
176
|
+
summary: task.summary,
|
|
177
|
+
rawOutput: null,
|
|
178
|
+
error: task.summary,
|
|
179
|
+
envelope: null,
|
|
180
|
+
startedAt,
|
|
181
|
+
finishedAt: task.updatedAt
|
|
182
|
+
});
|
|
183
|
+
await recordEvent(this.paths, this.session.id, "task.failed", {
|
|
184
|
+
taskId: task.id,
|
|
185
|
+
owner: task.owner,
|
|
186
|
+
error: task.summary
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async markAgent(agent, summary, exitCode, sessionId) {
|
|
191
|
+
this.session.agentStatus[agent] = {
|
|
192
|
+
...this.session.agentStatus[agent],
|
|
193
|
+
lastRunAt: new Date().toISOString(),
|
|
194
|
+
lastExitCode: exitCode,
|
|
195
|
+
sessionId,
|
|
196
|
+
summary
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
//# sourceURL=daemon.ts
|
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { fileExists } from "./fs.js";
|
|
2
|
+
import { runCommand } from "./process.js";
|
|
3
|
+
import { hasSupportedNode, minimumNodeMajor, resolveSessionRuntime } from "./runtime.js";
|
|
4
|
+
function normalizeVersion(output) {
|
|
5
|
+
return output.trim().split(/\s+/).slice(-1)[0] ?? output.trim();
|
|
6
|
+
}
|
|
7
|
+
export async function runDoctor(repoRoot, paths) {
|
|
8
|
+
const checks = [];
|
|
9
|
+
const runtime = await resolveSessionRuntime(paths);
|
|
10
|
+
const nodeVersion = await runCommand(runtime.nodeExecutable, [
|
|
11
|
+
"--version"
|
|
12
|
+
], {
|
|
13
|
+
cwd: repoRoot
|
|
14
|
+
});
|
|
15
|
+
checks.push({
|
|
16
|
+
name: "node",
|
|
17
|
+
ok: nodeVersion.code === 0 && hasSupportedNode(nodeVersion.stdout.replace(/^v/, "").trim()),
|
|
18
|
+
detail: nodeVersion.code === 0 ? `${nodeVersion.stdout.trim()} via ${runtime.nodeExecutable} (need >= ${minimumNodeMajor()})` : nodeVersion.stderr.trim()
|
|
19
|
+
});
|
|
20
|
+
const codexVersion = await runCommand(runtime.codexExecutable, [
|
|
21
|
+
"--version"
|
|
22
|
+
], {
|
|
23
|
+
cwd: repoRoot
|
|
24
|
+
});
|
|
25
|
+
checks.push({
|
|
26
|
+
name: "codex",
|
|
27
|
+
ok: codexVersion.code === 0,
|
|
28
|
+
detail: codexVersion.code === 0 ? `${normalizeVersion(codexVersion.stdout)} via ${runtime.codexExecutable}` : codexVersion.stderr.trim()
|
|
29
|
+
});
|
|
30
|
+
const claudeVersion = await runCommand(runtime.claudeExecutable, [
|
|
31
|
+
"--version"
|
|
32
|
+
], {
|
|
33
|
+
cwd: repoRoot
|
|
34
|
+
});
|
|
35
|
+
checks.push({
|
|
36
|
+
name: "claude",
|
|
37
|
+
ok: claudeVersion.code === 0,
|
|
38
|
+
detail: claudeVersion.code === 0 ? `${claudeVersion.stdout.trim()} via ${runtime.claudeExecutable}` : claudeVersion.stderr.trim()
|
|
39
|
+
});
|
|
40
|
+
const worktreeCheck = await runCommand("git", [
|
|
41
|
+
"worktree",
|
|
42
|
+
"list"
|
|
43
|
+
], {
|
|
44
|
+
cwd: repoRoot
|
|
45
|
+
});
|
|
46
|
+
checks.push({
|
|
47
|
+
name: "git-worktree",
|
|
48
|
+
ok: worktreeCheck.code === 0,
|
|
49
|
+
detail: worktreeCheck.code === 0 ? "available" : worktreeCheck.stderr.trim()
|
|
50
|
+
});
|
|
51
|
+
const appServerCheck = await runCommand(runtime.codexExecutable, [
|
|
52
|
+
"app-server",
|
|
53
|
+
"--help"
|
|
54
|
+
], {
|
|
55
|
+
cwd: repoRoot
|
|
56
|
+
});
|
|
57
|
+
checks.push({
|
|
58
|
+
name: "codex-app-server",
|
|
59
|
+
ok: appServerCheck.code === 0,
|
|
60
|
+
detail: appServerCheck.code === 0 ? "supported" : appServerCheck.stderr.trim()
|
|
61
|
+
});
|
|
62
|
+
const authCheck = await fileExists(`${process.env.HOME}/.codex/auth.json`);
|
|
63
|
+
checks.push({
|
|
64
|
+
name: "codex-auth-file",
|
|
65
|
+
ok: authCheck,
|
|
66
|
+
detail: authCheck ? "present" : "missing ~/.codex/auth.json"
|
|
67
|
+
});
|
|
68
|
+
const homeConfigCheck = await fileExists(paths.homeConfigFile);
|
|
69
|
+
checks.push({
|
|
70
|
+
name: "home-config",
|
|
71
|
+
ok: true,
|
|
72
|
+
detail: homeConfigCheck ? "present" : "will be created on demand"
|
|
73
|
+
});
|
|
74
|
+
return checks;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
//# sourceURL=doctor.ts
|
package/dist/fs.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export async function ensureDir(dirPath) {
|
|
4
|
+
await fs.mkdir(dirPath, {
|
|
5
|
+
recursive: true
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
export async function fileExists(filePath) {
|
|
9
|
+
try {
|
|
10
|
+
await fs.access(filePath);
|
|
11
|
+
return true;
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export async function writeJson(filePath, value) {
|
|
17
|
+
await ensureDir(path.dirname(filePath));
|
|
18
|
+
await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
19
|
+
}
|
|
20
|
+
export async function readJson(filePath) {
|
|
21
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
22
|
+
return JSON.parse(content);
|
|
23
|
+
}
|
|
24
|
+
export async function appendEvent(filePath, event) {
|
|
25
|
+
await ensureDir(path.dirname(filePath));
|
|
26
|
+
await fs.appendFile(filePath, `${JSON.stringify(event)}\n`, "utf8");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
//# sourceURL=fs.ts
|