agent-yes 1.73.0 → 1.74.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/agent-yes.config.schema.json +32 -8
- package/default.config.yaml +154 -0
- package/dist/SUPPORTED_CLIS-BCn8YIi2.js +11 -0
- package/dist/{agent-yes.config-CtQprJrA.js → agent-yes.config-1LMoK18R.js} +75 -119
- package/dist/cli.js +25 -9
- package/dist/globalPidIndex-DNEh8a_O.js +103 -0
- package/dist/index.js +4 -2
- package/dist/logger-B9h0djqx.js +51 -0
- package/dist/package-Bn0B_jWZ.js +7 -0
- package/dist/pidStore-CHLHMBEM.js +340 -0
- package/dist/pidStore-DR1yPY3t.js +5 -0
- package/dist/{runningLock-BBI_URhR.js → runningLock-DQWJSptq.js} +3 -3
- package/dist/subcommands-CR1i1sjy.js +387 -0
- package/dist/{tray-CPpdxTV-.js → tray-D5deJPjk.js} +4 -4
- package/dist/{SUPPORTED_CLIS-DgHs-Q6i.js → ts-C8vuG5y-.js} +20 -337
- package/package.json +2 -1
- package/ts/cli.ts +24 -8
- package/ts/configLoader.spec.ts +19 -0
- package/ts/configLoader.ts +8 -2
- package/ts/configShared.spec.ts +97 -0
- package/ts/configShared.ts +158 -0
- package/ts/globalPidIndex.spec.ts +166 -0
- package/ts/globalPidIndex.ts +143 -0
- package/ts/index.ts +7 -3
- package/ts/logger.spec.ts +27 -0
- package/ts/logger.ts +63 -19
- package/ts/parseCliArgs.ts +3 -4
- package/ts/pidStore.ts +24 -0
- package/ts/subcommands.spec.ts +581 -0
- package/ts/subcommands.ts +521 -0
- package/ts/versionChecker.ts +1 -1
- package/dist/logger-CX77vJDA.js +0 -16
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region ts/logger.ts
|
|
2
|
+
let _inner = null;
|
|
3
|
+
let _initPromise = null;
|
|
4
|
+
const _queue = [];
|
|
5
|
+
function init() {
|
|
6
|
+
if (_initPromise) return _initPromise;
|
|
7
|
+
_initPromise = import("winston").then(({ default: winston }) => {
|
|
8
|
+
const logFormat = winston.format.combine(winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), winston.format.printf(({ timestamp, level, message, ...meta }) => {
|
|
9
|
+
return `${timestamp} [${level}]: ${message}${Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : ""}`;
|
|
10
|
+
}));
|
|
11
|
+
_inner = winston.createLogger({
|
|
12
|
+
level: process.env.VERBOSE ? "debug" : "info",
|
|
13
|
+
format: logFormat,
|
|
14
|
+
transports: [new winston.transports.Console({ format: winston.format.combine(winston.format.colorize(), logFormat) })],
|
|
15
|
+
silent: false
|
|
16
|
+
});
|
|
17
|
+
for (const { level, msg, meta } of _queue.splice(0)) _inner[level](msg, ...meta);
|
|
18
|
+
});
|
|
19
|
+
return _initPromise;
|
|
20
|
+
}
|
|
21
|
+
function makeMethod(level) {
|
|
22
|
+
return (msg, ...meta) => {
|
|
23
|
+
if (_inner) _inner[level](msg, ...meta);
|
|
24
|
+
else {
|
|
25
|
+
_queue.push({
|
|
26
|
+
level,
|
|
27
|
+
msg,
|
|
28
|
+
meta
|
|
29
|
+
});
|
|
30
|
+
init().catch((e) => console.error("[logger] Failed to load winston:", e));
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Add a winston transport. Awaits logger initialization first. */
|
|
35
|
+
async function addTransport(transport) {
|
|
36
|
+
await init();
|
|
37
|
+
_inner.add(transport);
|
|
38
|
+
}
|
|
39
|
+
const logger = {
|
|
40
|
+
error: makeMethod("error"),
|
|
41
|
+
warn: makeMethod("warn"),
|
|
42
|
+
info: makeMethod("info"),
|
|
43
|
+
http: makeMethod("http"),
|
|
44
|
+
verbose: makeMethod("verbose"),
|
|
45
|
+
debug: makeMethod("debug"),
|
|
46
|
+
silly: makeMethod("silly")
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { logger as n, addTransport as t };
|
|
51
|
+
//# sourceMappingURL=logger-B9h0djqx.js.map
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { n as logger } from "./logger-B9h0djqx.js";
|
|
2
|
+
import { r as updateGlobalPidStatus, t as appendGlobalPid } from "./globalPidIndex-DNEh8a_O.js";
|
|
3
|
+
import { closeSync, existsSync, fsyncSync, openSync } from "fs";
|
|
4
|
+
import { appendFile, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { lock } from "proper-lockfile";
|
|
7
|
+
|
|
8
|
+
//#region ts/JsonlStore.ts
|
|
9
|
+
/**
|
|
10
|
+
* A lightweight NeDB-style JSONL persistence layer.
|
|
11
|
+
*
|
|
12
|
+
* - Append-only writes (one JSON object per line)
|
|
13
|
+
* - Same `_id` → last line wins (fields merged)
|
|
14
|
+
* - `$$deleted` lines act as tombstones
|
|
15
|
+
* - Crash recovery: skip partial last line, recover from temp file
|
|
16
|
+
* - Multi-process safe via proper-lockfile (reads don't need lock)
|
|
17
|
+
* - Compact on close: deduplicates into clean file via atomic rename
|
|
18
|
+
*/
|
|
19
|
+
var JsonlStore = class {
|
|
20
|
+
filePath;
|
|
21
|
+
tempPath;
|
|
22
|
+
docs = /* @__PURE__ */ new Map();
|
|
23
|
+
constructor(filePath) {
|
|
24
|
+
this.filePath = filePath;
|
|
25
|
+
this.tempPath = filePath + "~";
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Load all records from the JSONL file. No lock needed.
|
|
29
|
+
* Handles crash recovery: partial last line skipped, temp file recovery.
|
|
30
|
+
*/
|
|
31
|
+
async load() {
|
|
32
|
+
await mkdir(path.dirname(this.filePath), { recursive: true });
|
|
33
|
+
if (!existsSync(this.filePath) && existsSync(this.tempPath)) {
|
|
34
|
+
logger.debug("[JsonlStore] Recovering from temp file");
|
|
35
|
+
await rename(this.tempPath, this.filePath);
|
|
36
|
+
}
|
|
37
|
+
this.docs = /* @__PURE__ */ new Map();
|
|
38
|
+
let raw = "";
|
|
39
|
+
try {
|
|
40
|
+
raw = await readFile(this.filePath, "utf-8");
|
|
41
|
+
} catch (err) {
|
|
42
|
+
if (err.code === "ENOENT") return this.docs;
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
const lines = raw.split("\n");
|
|
46
|
+
let corruptCount = 0;
|
|
47
|
+
for (const line of lines) {
|
|
48
|
+
const trimmed = line.trim();
|
|
49
|
+
if (!trimmed) continue;
|
|
50
|
+
try {
|
|
51
|
+
const doc = JSON.parse(trimmed);
|
|
52
|
+
if (!doc._id) continue;
|
|
53
|
+
if (doc.$$deleted) this.docs.delete(doc._id);
|
|
54
|
+
else {
|
|
55
|
+
const existing = this.docs.get(doc._id);
|
|
56
|
+
if (existing) this.docs.set(doc._id, {
|
|
57
|
+
...existing,
|
|
58
|
+
...doc
|
|
59
|
+
});
|
|
60
|
+
else this.docs.set(doc._id, doc);
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
corruptCount++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (corruptCount > 0) logger.debug(`[JsonlStore] Skipped ${corruptCount} corrupt line(s) in ${this.filePath}`);
|
|
67
|
+
return this.docs;
|
|
68
|
+
}
|
|
69
|
+
/** Get all live documents. */
|
|
70
|
+
getAll() {
|
|
71
|
+
return Array.from(this.docs.values());
|
|
72
|
+
}
|
|
73
|
+
/** Find a document by _id. */
|
|
74
|
+
getById(id) {
|
|
75
|
+
return this.docs.get(id);
|
|
76
|
+
}
|
|
77
|
+
/** Find documents matching a predicate. */
|
|
78
|
+
find(predicate) {
|
|
79
|
+
return this.getAll().filter(predicate);
|
|
80
|
+
}
|
|
81
|
+
/** Find first document matching a predicate. */
|
|
82
|
+
findOne(predicate) {
|
|
83
|
+
for (const doc of this.docs.values()) if (predicate(doc)) return doc;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Append a new document. Acquires lock.
|
|
87
|
+
* If no _id is provided, one is generated.
|
|
88
|
+
*/
|
|
89
|
+
async append(doc) {
|
|
90
|
+
const id = doc._id || generateId();
|
|
91
|
+
const { _id: _, ...rest } = doc;
|
|
92
|
+
const fullDoc = {
|
|
93
|
+
_id: id,
|
|
94
|
+
...rest
|
|
95
|
+
};
|
|
96
|
+
return await this.withLock(async () => {
|
|
97
|
+
await appendFile(this.filePath, JSON.stringify(fullDoc) + "\n");
|
|
98
|
+
const existing = this.docs.get(fullDoc._id);
|
|
99
|
+
if (existing) this.docs.set(fullDoc._id, {
|
|
100
|
+
...existing,
|
|
101
|
+
...fullDoc
|
|
102
|
+
});
|
|
103
|
+
else this.docs.set(fullDoc._id, fullDoc);
|
|
104
|
+
return fullDoc;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Update a document by _id. Appends a merge line. Acquires lock.
|
|
109
|
+
*/
|
|
110
|
+
async updateById(id, patch) {
|
|
111
|
+
await this.withLock(async () => {
|
|
112
|
+
const line = {
|
|
113
|
+
_id: id,
|
|
114
|
+
...patch
|
|
115
|
+
};
|
|
116
|
+
await appendFile(this.filePath, JSON.stringify(line) + "\n");
|
|
117
|
+
const existing = this.docs.get(id);
|
|
118
|
+
if (existing) this.docs.set(id, {
|
|
119
|
+
...existing,
|
|
120
|
+
...patch
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Delete a document by _id. Appends a tombstone. Acquires lock.
|
|
126
|
+
*/
|
|
127
|
+
async deleteById(id) {
|
|
128
|
+
await this.withLock(async () => {
|
|
129
|
+
const tombstone = {
|
|
130
|
+
_id: id,
|
|
131
|
+
$$deleted: true
|
|
132
|
+
};
|
|
133
|
+
await appendFile(this.filePath, JSON.stringify(tombstone) + "\n");
|
|
134
|
+
this.docs.delete(id);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Compact the file: deduplicate entries, remove tombstones.
|
|
139
|
+
* Writes to temp file, fsyncs, then atomic renames.
|
|
140
|
+
* Acquires lock.
|
|
141
|
+
*/
|
|
142
|
+
async compact() {
|
|
143
|
+
const lines = Array.from(this.docs.values()).map((doc) => {
|
|
144
|
+
const { _id, $$deleted: _$$deleted, ...rest } = doc;
|
|
145
|
+
return JSON.stringify({
|
|
146
|
+
_id,
|
|
147
|
+
...rest
|
|
148
|
+
});
|
|
149
|
+
}).join("\n");
|
|
150
|
+
const content = lines ? lines + "\n" : "";
|
|
151
|
+
try {
|
|
152
|
+
await this.withLock(async () => {
|
|
153
|
+
await writeFile(this.tempPath, content);
|
|
154
|
+
const fd = openSync(this.tempPath, "r");
|
|
155
|
+
fsyncSync(fd);
|
|
156
|
+
closeSync(fd);
|
|
157
|
+
await rename(this.tempPath, this.filePath);
|
|
158
|
+
});
|
|
159
|
+
} catch {
|
|
160
|
+
await writeFile(this.filePath, content);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async withLock(fn) {
|
|
164
|
+
const dir = path.dirname(this.filePath);
|
|
165
|
+
let release;
|
|
166
|
+
try {
|
|
167
|
+
release = await lock(dir, {
|
|
168
|
+
lockfilePath: this.filePath + ".lock",
|
|
169
|
+
retries: {
|
|
170
|
+
retries: 5,
|
|
171
|
+
minTimeout: 50,
|
|
172
|
+
maxTimeout: 500
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
return await fn();
|
|
176
|
+
} finally {
|
|
177
|
+
if (release) await release();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
let idCounter = 0;
|
|
182
|
+
function generateId() {
|
|
183
|
+
return Date.now().toString(36) + (idCounter++).toString(36) + Math.random().toString(36).slice(2, 6);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region ts/pidStore.ts
|
|
188
|
+
var PidStore = class PidStore {
|
|
189
|
+
storeDir;
|
|
190
|
+
store;
|
|
191
|
+
constructor(workingDir) {
|
|
192
|
+
this.storeDir = path.resolve(workingDir, ".agent-yes");
|
|
193
|
+
this.store = new JsonlStore(path.join(this.storeDir, "pid-records.jsonl"));
|
|
194
|
+
}
|
|
195
|
+
async init() {
|
|
196
|
+
try {
|
|
197
|
+
await this.ensureGitignore();
|
|
198
|
+
await this.store.load();
|
|
199
|
+
await this.cleanStaleRecords();
|
|
200
|
+
} catch (error) {
|
|
201
|
+
logger.warn("[pidStore] Failed to initialize:", error);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async registerProcess({ pid, cli, args, prompt, cwd }) {
|
|
205
|
+
const now = Date.now();
|
|
206
|
+
const argsJson = JSON.stringify(args);
|
|
207
|
+
const logFile = path.resolve(this.getLogDir(), `${pid}.log`);
|
|
208
|
+
const fifoFile = this.getFifoPath(pid);
|
|
209
|
+
const record = {
|
|
210
|
+
pid,
|
|
211
|
+
cli,
|
|
212
|
+
args: argsJson,
|
|
213
|
+
prompt,
|
|
214
|
+
cwd,
|
|
215
|
+
logFile,
|
|
216
|
+
fifoFile,
|
|
217
|
+
status: "active",
|
|
218
|
+
exitReason: "",
|
|
219
|
+
startedAt: now
|
|
220
|
+
};
|
|
221
|
+
const existing = this.store.findOne((doc) => doc.pid === pid);
|
|
222
|
+
if (existing) await this.store.updateById(existing._id, record);
|
|
223
|
+
else await this.store.append(record);
|
|
224
|
+
const result = this.store.findOne((doc) => doc.pid === pid);
|
|
225
|
+
if (!result) {
|
|
226
|
+
const allRecords = this.store.getAll();
|
|
227
|
+
logger.error(`[pidStore] Failed to find record for PID ${pid}. All records:`, allRecords);
|
|
228
|
+
throw new Error(`Failed to register process ${pid}`);
|
|
229
|
+
}
|
|
230
|
+
logger.debug(`[pidStore] Registered process ${pid}`);
|
|
231
|
+
appendGlobalPid({
|
|
232
|
+
pid,
|
|
233
|
+
cli,
|
|
234
|
+
prompt: prompt ?? null,
|
|
235
|
+
cwd,
|
|
236
|
+
log_file: logFile,
|
|
237
|
+
fifo_file: fifoFile,
|
|
238
|
+
status: "active",
|
|
239
|
+
exit_code: null,
|
|
240
|
+
exit_reason: null,
|
|
241
|
+
started_at: now
|
|
242
|
+
}).catch(() => null);
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
async updateStatus(pid, status, extra) {
|
|
246
|
+
const existing = this.store.findOne((doc) => doc.pid === pid);
|
|
247
|
+
if (!existing) return;
|
|
248
|
+
const patch = { status };
|
|
249
|
+
if (extra?.exitReason !== void 0) patch.exitReason = extra.exitReason;
|
|
250
|
+
if (extra?.exitCode !== void 0) patch.exitCode = extra.exitCode;
|
|
251
|
+
await this.store.updateById(existing._id, patch);
|
|
252
|
+
logger.debug(`[pidStore] Updated process ${pid} status=${status}`);
|
|
253
|
+
updateGlobalPidStatus(pid, {
|
|
254
|
+
status,
|
|
255
|
+
exit_code: extra?.exitCode ?? null,
|
|
256
|
+
exit_reason: extra?.exitReason ?? null
|
|
257
|
+
}).catch(() => null);
|
|
258
|
+
}
|
|
259
|
+
getAllRecords() {
|
|
260
|
+
return this.store.getAll();
|
|
261
|
+
}
|
|
262
|
+
getLogDir() {
|
|
263
|
+
return path.resolve(this.storeDir, "logs");
|
|
264
|
+
}
|
|
265
|
+
getFifoPath(pid) {
|
|
266
|
+
if (process.platform === "win32") return `\\\\.\\pipe\\agent-yes-${pid}`;
|
|
267
|
+
else return path.resolve(this.storeDir, "fifo", `${pid}.stdin`);
|
|
268
|
+
}
|
|
269
|
+
async cleanStaleRecords() {
|
|
270
|
+
const activeRecords = this.store.find((r) => r.status !== "exited");
|
|
271
|
+
for (const record of activeRecords) if (!this.isProcessAlive(record.pid)) {
|
|
272
|
+
await this.store.updateById(record._id, {
|
|
273
|
+
status: "exited",
|
|
274
|
+
exitReason: "stale-cleanup"
|
|
275
|
+
});
|
|
276
|
+
logger.debug(`[pidStore] Cleaned stale record for PID ${record.pid}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async close() {
|
|
280
|
+
try {
|
|
281
|
+
await this.store.compact();
|
|
282
|
+
} catch (error) {
|
|
283
|
+
logger.debug("[pidStore] Compact on close failed:", error);
|
|
284
|
+
}
|
|
285
|
+
logger.debug("[pidStore] Database compacted and closed");
|
|
286
|
+
}
|
|
287
|
+
isProcessAlive(pid) {
|
|
288
|
+
try {
|
|
289
|
+
process.kill(pid, 0);
|
|
290
|
+
return true;
|
|
291
|
+
} catch {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
async ensureGitignore() {
|
|
296
|
+
const gitignorePath = path.join(this.storeDir, ".gitignore");
|
|
297
|
+
const gitignoreContent = `# Auto-generated .gitignore for agent-yes
|
|
298
|
+
# Ignore all log files and runtime data
|
|
299
|
+
logs/
|
|
300
|
+
fifo/
|
|
301
|
+
pid-db/
|
|
302
|
+
*.jsonl
|
|
303
|
+
*.jsonl~
|
|
304
|
+
*.jsonl.lock
|
|
305
|
+
*.sqlite
|
|
306
|
+
*.sqlite-*
|
|
307
|
+
*.log
|
|
308
|
+
*.raw.log
|
|
309
|
+
*.lines.log
|
|
310
|
+
*.debug.log
|
|
311
|
+
|
|
312
|
+
# Ignore .gitignore itself
|
|
313
|
+
.gitignore
|
|
314
|
+
|
|
315
|
+
`;
|
|
316
|
+
try {
|
|
317
|
+
await mkdir(this.storeDir, { recursive: true });
|
|
318
|
+
await writeFile(gitignorePath, gitignoreContent, { flag: "wx" });
|
|
319
|
+
logger.debug(`[pidStore] Created .gitignore in ${this.storeDir}`);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
if (error.code !== "EEXIST") logger.warn(`[pidStore] Failed to create .gitignore:`, error);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
static async findActiveFifo(workingDir) {
|
|
325
|
+
try {
|
|
326
|
+
const store = new PidStore(workingDir);
|
|
327
|
+
await store.init();
|
|
328
|
+
const records = store.store.find((r) => r.status !== "exited").sort((a, b) => b.startedAt - a.startedAt);
|
|
329
|
+
await store.close();
|
|
330
|
+
return records[0]?.fifoFile ?? null;
|
|
331
|
+
} catch (error) {
|
|
332
|
+
logger.warn("[pidStore] findActiveFifo failed:", error);
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
//#endregion
|
|
339
|
+
export { PidStore as t };
|
|
340
|
+
//# sourceMappingURL=pidStore-CHLHMBEM.js.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
|
+
import { existsSync } from "fs";
|
|
2
3
|
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
3
|
-
import path from "path";
|
|
4
4
|
import { homedir } from "os";
|
|
5
|
-
import
|
|
5
|
+
import path from "path";
|
|
6
6
|
|
|
7
7
|
//#region ts/runningLock.ts
|
|
8
8
|
const getLockDir = () => path.join(process.env.CLAUDE_YES_HOME || homedir(), ".claude-yes");
|
|
@@ -260,4 +260,4 @@ function shouldUseLock(_cwd) {
|
|
|
260
260
|
|
|
261
261
|
//#endregion
|
|
262
262
|
export { shouldUseLock as i, getRunningAgentCount as n, releaseLock as r, acquireLock as t };
|
|
263
|
-
//# sourceMappingURL=runningLock-
|
|
263
|
+
//# sourceMappingURL=runningLock-DQWJSptq.js.map
|