appduct 0.11.0 → 0.12.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 +4 -4
- package/dist/bin.js +7 -1
- package/dist/cli/result-types.d.ts +5 -4
- package/dist/cli/routes/daemon/run.js +83 -21
- package/dist/cli/routes/daemon/start.js +18 -3
- package/dist/cli/routes/daemon/status.js +18 -3
- package/dist/cli/routes/daemon/stop.js +18 -3
- package/dist/cli/routes/events.js +20 -4
- package/dist/cli/routes/invoke.js +18 -3
- package/dist/cli/routes/keygen.js +1 -0
- package/dist/cli/routes/link.js +18 -3
- package/dist/cli/routes/ls.js +18 -3
- package/dist/cli/routes/mcp.js +33 -19
- package/dist/cli/routes/revoke.js +18 -3
- package/dist/cli/routes/tools.js +18 -3
- package/dist/client/app-client.d.ts +4 -3
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +22 -8
- package/dist/commands/events.d.ts +3 -2
- package/dist/daemon/config.d.ts +4 -1
- package/dist/daemon/daemon.d.ts +3 -0
- package/dist/daemon/event-bus.d.ts +4 -4
- package/dist/daemon/events-log.d.ts +31 -0
- package/dist/daemon/memory-append-only-file.d.ts +17 -0
- package/dist/daemon/node-append-only-file.d.ts +9 -0
- package/dist/daemon/state-dir.d.ts +2 -0
- package/dist/index.js +7 -1
- package/dist/mcp/app-tools.d.ts +7 -7
- package/dist/mcp/events-tool.d.ts +0 -7
- package/dist/rpc/client.d.ts +3 -2
- package/package.json +4 -2
|
@@ -9,7 +9,7 @@ import { join as join3 } from "node:path";
|
|
|
9
9
|
import { RPC_METHODS } from "@appduct/shared";
|
|
10
10
|
|
|
11
11
|
// src/daemon/log-rotation.ts
|
|
12
|
-
import { chmod, rename, stat } from "node:fs/promises";
|
|
12
|
+
import { chmod as chmod2, rename, stat } from "node:fs/promises";
|
|
13
13
|
|
|
14
14
|
// src/daemon/config.ts
|
|
15
15
|
import { readFile } from "node:fs/promises";
|
|
@@ -22,6 +22,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
22
22
|
"eventBufferSize",
|
|
23
23
|
"auditRetentionDays",
|
|
24
24
|
"daemonLogMaxBytes",
|
|
25
|
+
"eventsLogMaxBytes",
|
|
25
26
|
"policy",
|
|
26
27
|
"advertisedIp",
|
|
27
28
|
"scheme",
|
|
@@ -81,6 +82,7 @@ var defaultConfig = (paths) => {
|
|
|
81
82
|
eventBufferSize: 256,
|
|
82
83
|
auditRetentionDays: DEFAULT_AUDIT_RETENTION_DAYS,
|
|
83
84
|
daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
|
|
85
|
+
eventsLogMaxBytes: 10 * 1024 * 1024,
|
|
84
86
|
restartDaemonOnVersionMismatch: false,
|
|
85
87
|
policy: {
|
|
86
88
|
default: "allow",
|
|
@@ -143,6 +145,9 @@ var loadConfig = async (paths, options = {}) => {
|
|
|
143
145
|
if (parsed.daemonLogMaxBytes !== void 0) {
|
|
144
146
|
config.daemonLogMaxBytes = requirePositiveInteger(parsed.daemonLogMaxBytes, "daemonLogMaxBytes");
|
|
145
147
|
}
|
|
148
|
+
if (parsed.eventsLogMaxBytes !== void 0) {
|
|
149
|
+
config.eventsLogMaxBytes = requirePositiveInteger(parsed.eventsLogMaxBytes, "eventsLogMaxBytes");
|
|
150
|
+
}
|
|
146
151
|
if (parsed.restartDaemonOnVersionMismatch !== void 0) {
|
|
147
152
|
config.restartDaemonOnVersionMismatch = requireBoolean(
|
|
148
153
|
parsed.restartDaemonOnVersionMismatch,
|
|
@@ -236,18 +241,27 @@ var isSocketConnectable = (socketPath) => {
|
|
|
236
241
|
|
|
237
242
|
// src/daemon/state-dir.ts
|
|
238
243
|
import { join } from "node:path";
|
|
244
|
+
import { mkdir, chmod } from "node:fs/promises";
|
|
239
245
|
var getStateDirPaths = (stateDir) => {
|
|
240
246
|
return {
|
|
241
247
|
root: stateDir,
|
|
242
248
|
socketPath: join(stateDir, "daemon.sock"),
|
|
243
249
|
pidFilePath: join(stateDir, "daemon.pid"),
|
|
244
250
|
logFilePath: join(stateDir, "daemon.log"),
|
|
251
|
+
eventsLogPath: join(stateDir, "events.log"),
|
|
245
252
|
keyPath: join(stateDir, "key.pem"),
|
|
246
253
|
configPath: join(stateDir, "config.json"),
|
|
247
254
|
auditDir: join(stateDir, "audit"),
|
|
248
255
|
spawnLockPath: join(stateDir, "daemon.spawn.lock")
|
|
249
256
|
};
|
|
250
257
|
};
|
|
258
|
+
var ensureStateDir = async (stateDir) => {
|
|
259
|
+
const paths = getStateDirPaths(stateDir);
|
|
260
|
+
await mkdir(paths.auditDir, { recursive: true });
|
|
261
|
+
await chmod(paths.root, 448);
|
|
262
|
+
await chmod(paths.auditDir, 448);
|
|
263
|
+
return paths;
|
|
264
|
+
};
|
|
251
265
|
|
|
252
266
|
// src/daemon/log-rotation.ts
|
|
253
267
|
var STATE_FILE_MODE = 384;
|
|
@@ -280,7 +294,7 @@ var rotateDaemonLogIfNeeded = async (options) => {
|
|
|
280
294
|
const backupPath = daemonLogBackupPath(options.logFilePath);
|
|
281
295
|
await rename(options.logFilePath, backupPath);
|
|
282
296
|
try {
|
|
283
|
-
await
|
|
297
|
+
await chmod2(backupPath, STATE_FILE_MODE);
|
|
284
298
|
} catch (error) {
|
|
285
299
|
warn(`appduct: rotated "${options.logFilePath}" but could not set 0600 on "${backupPath}": ${error.message}`);
|
|
286
300
|
}
|
|
@@ -296,7 +310,7 @@ var rotateDaemonLogIfNeeded = async (options) => {
|
|
|
296
310
|
var ensureDaemonLogMode = async (logFilePath, warn = (message) => process.stderr.write(`${message}
|
|
297
311
|
`)) => {
|
|
298
312
|
try {
|
|
299
|
-
await
|
|
313
|
+
await chmod2(logFilePath, STATE_FILE_MODE);
|
|
300
314
|
return true;
|
|
301
315
|
} catch (error) {
|
|
302
316
|
warn(`appduct: could not set 0600 on "${logFilePath}": ${error.message}`);
|
|
@@ -637,6 +651,7 @@ var acquireSpawnLock = async (lockPath) => {
|
|
|
637
651
|
return false;
|
|
638
652
|
};
|
|
639
653
|
var spawnDaemonAndWait = async (paths, spawn, waitTimeoutMs, pollIntervalMs, options = {}) => {
|
|
654
|
+
await ensureStateDir(paths.root);
|
|
640
655
|
const acquiredLock = await acquireSpawnLock(paths.spawnLockPath);
|
|
641
656
|
let lockWaitTimedOut = false;
|
|
642
657
|
if (acquiredLock) {
|
|
@@ -1019,7 +1034,8 @@ var handleEventsCommand = async (options, context) => {
|
|
|
1019
1034
|
const stream = await openDaemonStream({ stateDir: context.stateDir, spawn: context.spawn });
|
|
1020
1035
|
try {
|
|
1021
1036
|
await stream.call(RPC_METHODS2.eventsSubscribe, {
|
|
1022
|
-
sessionSelector: options.selector
|
|
1037
|
+
sessionSelector: options.selector,
|
|
1038
|
+
kinds: ["app_event"]
|
|
1023
1039
|
});
|
|
1024
1040
|
} catch (error) {
|
|
1025
1041
|
stream.close();
|
|
@@ -29,7 +29,7 @@ import { join as join3 } from "node:path";
|
|
|
29
29
|
import { RPC_METHODS } from "@appduct/shared";
|
|
30
30
|
|
|
31
31
|
// src/daemon/log-rotation.ts
|
|
32
|
-
import { chmod, rename, stat } from "node:fs/promises";
|
|
32
|
+
import { chmod as chmod2, rename, stat } from "node:fs/promises";
|
|
33
33
|
|
|
34
34
|
// src/daemon/config.ts
|
|
35
35
|
import { readFile } from "node:fs/promises";
|
|
@@ -42,6 +42,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
42
42
|
"eventBufferSize",
|
|
43
43
|
"auditRetentionDays",
|
|
44
44
|
"daemonLogMaxBytes",
|
|
45
|
+
"eventsLogMaxBytes",
|
|
45
46
|
"policy",
|
|
46
47
|
"advertisedIp",
|
|
47
48
|
"scheme",
|
|
@@ -101,6 +102,7 @@ var defaultConfig = (paths) => {
|
|
|
101
102
|
eventBufferSize: 256,
|
|
102
103
|
auditRetentionDays: DEFAULT_AUDIT_RETENTION_DAYS,
|
|
103
104
|
daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
|
|
105
|
+
eventsLogMaxBytes: 10 * 1024 * 1024,
|
|
104
106
|
restartDaemonOnVersionMismatch: false,
|
|
105
107
|
policy: {
|
|
106
108
|
default: "allow",
|
|
@@ -163,6 +165,9 @@ var loadConfig = async (paths, options = {}) => {
|
|
|
163
165
|
if (parsed.daemonLogMaxBytes !== void 0) {
|
|
164
166
|
config.daemonLogMaxBytes = requirePositiveInteger(parsed.daemonLogMaxBytes, "daemonLogMaxBytes");
|
|
165
167
|
}
|
|
168
|
+
if (parsed.eventsLogMaxBytes !== void 0) {
|
|
169
|
+
config.eventsLogMaxBytes = requirePositiveInteger(parsed.eventsLogMaxBytes, "eventsLogMaxBytes");
|
|
170
|
+
}
|
|
166
171
|
if (parsed.restartDaemonOnVersionMismatch !== void 0) {
|
|
167
172
|
config.restartDaemonOnVersionMismatch = requireBoolean(
|
|
168
173
|
parsed.restartDaemonOnVersionMismatch,
|
|
@@ -256,18 +261,27 @@ var isSocketConnectable = (socketPath) => {
|
|
|
256
261
|
|
|
257
262
|
// src/daemon/state-dir.ts
|
|
258
263
|
import { join } from "node:path";
|
|
264
|
+
import { mkdir, chmod } from "node:fs/promises";
|
|
259
265
|
var getStateDirPaths = (stateDir) => {
|
|
260
266
|
return {
|
|
261
267
|
root: stateDir,
|
|
262
268
|
socketPath: join(stateDir, "daemon.sock"),
|
|
263
269
|
pidFilePath: join(stateDir, "daemon.pid"),
|
|
264
270
|
logFilePath: join(stateDir, "daemon.log"),
|
|
271
|
+
eventsLogPath: join(stateDir, "events.log"),
|
|
265
272
|
keyPath: join(stateDir, "key.pem"),
|
|
266
273
|
configPath: join(stateDir, "config.json"),
|
|
267
274
|
auditDir: join(stateDir, "audit"),
|
|
268
275
|
spawnLockPath: join(stateDir, "daemon.spawn.lock")
|
|
269
276
|
};
|
|
270
277
|
};
|
|
278
|
+
var ensureStateDir = async (stateDir) => {
|
|
279
|
+
const paths = getStateDirPaths(stateDir);
|
|
280
|
+
await mkdir(paths.auditDir, { recursive: true });
|
|
281
|
+
await chmod(paths.root, 448);
|
|
282
|
+
await chmod(paths.auditDir, 448);
|
|
283
|
+
return paths;
|
|
284
|
+
};
|
|
271
285
|
|
|
272
286
|
// src/daemon/log-rotation.ts
|
|
273
287
|
var STATE_FILE_MODE = 384;
|
|
@@ -300,7 +314,7 @@ var rotateDaemonLogIfNeeded = async (options) => {
|
|
|
300
314
|
const backupPath = daemonLogBackupPath(options.logFilePath);
|
|
301
315
|
await rename(options.logFilePath, backupPath);
|
|
302
316
|
try {
|
|
303
|
-
await
|
|
317
|
+
await chmod2(backupPath, STATE_FILE_MODE);
|
|
304
318
|
} catch (error) {
|
|
305
319
|
warn(`appduct: rotated "${options.logFilePath}" but could not set 0600 on "${backupPath}": ${error.message}`);
|
|
306
320
|
}
|
|
@@ -316,7 +330,7 @@ var rotateDaemonLogIfNeeded = async (options) => {
|
|
|
316
330
|
var ensureDaemonLogMode = async (logFilePath, warn = (message) => process.stderr.write(`${message}
|
|
317
331
|
`)) => {
|
|
318
332
|
try {
|
|
319
|
-
await
|
|
333
|
+
await chmod2(logFilePath, STATE_FILE_MODE);
|
|
320
334
|
return true;
|
|
321
335
|
} catch (error) {
|
|
322
336
|
warn(`appduct: could not set 0600 on "${logFilePath}": ${error.message}`);
|
|
@@ -657,6 +671,7 @@ var acquireSpawnLock = async (lockPath) => {
|
|
|
657
671
|
return false;
|
|
658
672
|
};
|
|
659
673
|
var spawnDaemonAndWait = async (paths, spawn, waitTimeoutMs, pollIntervalMs, options = {}) => {
|
|
674
|
+
await ensureStateDir(paths.root);
|
|
660
675
|
const acquiredLock = await acquireSpawnLock(paths.spawnLockPath);
|
|
661
676
|
let lockWaitTimedOut = false;
|
|
662
677
|
if (acquiredLock) {
|
|
@@ -9,6 +9,7 @@ var getStateDirPaths = (stateDir) => {
|
|
|
9
9
|
socketPath: join(stateDir, "daemon.sock"),
|
|
10
10
|
pidFilePath: join(stateDir, "daemon.pid"),
|
|
11
11
|
logFilePath: join(stateDir, "daemon.log"),
|
|
12
|
+
eventsLogPath: join(stateDir, "events.log"),
|
|
12
13
|
keyPath: join(stateDir, "key.pem"),
|
|
13
14
|
configPath: join(stateDir, "config.json"),
|
|
14
15
|
auditDir: join(stateDir, "audit"),
|
package/dist/cli/routes/link.js
CHANGED
|
@@ -12,7 +12,7 @@ import { join as join3 } from "node:path";
|
|
|
12
12
|
import { RPC_METHODS } from "@appduct/shared";
|
|
13
13
|
|
|
14
14
|
// src/daemon/log-rotation.ts
|
|
15
|
-
import { chmod, rename, stat } from "node:fs/promises";
|
|
15
|
+
import { chmod as chmod2, rename, stat } from "node:fs/promises";
|
|
16
16
|
|
|
17
17
|
// src/daemon/config.ts
|
|
18
18
|
import { readFile } from "node:fs/promises";
|
|
@@ -25,6 +25,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
25
25
|
"eventBufferSize",
|
|
26
26
|
"auditRetentionDays",
|
|
27
27
|
"daemonLogMaxBytes",
|
|
28
|
+
"eventsLogMaxBytes",
|
|
28
29
|
"policy",
|
|
29
30
|
"advertisedIp",
|
|
30
31
|
"scheme",
|
|
@@ -84,6 +85,7 @@ var defaultConfig = (paths) => {
|
|
|
84
85
|
eventBufferSize: 256,
|
|
85
86
|
auditRetentionDays: DEFAULT_AUDIT_RETENTION_DAYS,
|
|
86
87
|
daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
|
|
88
|
+
eventsLogMaxBytes: 10 * 1024 * 1024,
|
|
87
89
|
restartDaemonOnVersionMismatch: false,
|
|
88
90
|
policy: {
|
|
89
91
|
default: "allow",
|
|
@@ -146,6 +148,9 @@ var loadConfig = async (paths, options = {}) => {
|
|
|
146
148
|
if (parsed.daemonLogMaxBytes !== void 0) {
|
|
147
149
|
config.daemonLogMaxBytes = requirePositiveInteger(parsed.daemonLogMaxBytes, "daemonLogMaxBytes");
|
|
148
150
|
}
|
|
151
|
+
if (parsed.eventsLogMaxBytes !== void 0) {
|
|
152
|
+
config.eventsLogMaxBytes = requirePositiveInteger(parsed.eventsLogMaxBytes, "eventsLogMaxBytes");
|
|
153
|
+
}
|
|
149
154
|
if (parsed.restartDaemonOnVersionMismatch !== void 0) {
|
|
150
155
|
config.restartDaemonOnVersionMismatch = requireBoolean(
|
|
151
156
|
parsed.restartDaemonOnVersionMismatch,
|
|
@@ -239,18 +244,27 @@ var isSocketConnectable = (socketPath) => {
|
|
|
239
244
|
|
|
240
245
|
// src/daemon/state-dir.ts
|
|
241
246
|
import { join } from "node:path";
|
|
247
|
+
import { mkdir, chmod } from "node:fs/promises";
|
|
242
248
|
var getStateDirPaths = (stateDir) => {
|
|
243
249
|
return {
|
|
244
250
|
root: stateDir,
|
|
245
251
|
socketPath: join(stateDir, "daemon.sock"),
|
|
246
252
|
pidFilePath: join(stateDir, "daemon.pid"),
|
|
247
253
|
logFilePath: join(stateDir, "daemon.log"),
|
|
254
|
+
eventsLogPath: join(stateDir, "events.log"),
|
|
248
255
|
keyPath: join(stateDir, "key.pem"),
|
|
249
256
|
configPath: join(stateDir, "config.json"),
|
|
250
257
|
auditDir: join(stateDir, "audit"),
|
|
251
258
|
spawnLockPath: join(stateDir, "daemon.spawn.lock")
|
|
252
259
|
};
|
|
253
260
|
};
|
|
261
|
+
var ensureStateDir = async (stateDir) => {
|
|
262
|
+
const paths = getStateDirPaths(stateDir);
|
|
263
|
+
await mkdir(paths.auditDir, { recursive: true });
|
|
264
|
+
await chmod(paths.root, 448);
|
|
265
|
+
await chmod(paths.auditDir, 448);
|
|
266
|
+
return paths;
|
|
267
|
+
};
|
|
254
268
|
|
|
255
269
|
// src/daemon/log-rotation.ts
|
|
256
270
|
var STATE_FILE_MODE = 384;
|
|
@@ -283,7 +297,7 @@ var rotateDaemonLogIfNeeded = async (options) => {
|
|
|
283
297
|
const backupPath = daemonLogBackupPath(options.logFilePath);
|
|
284
298
|
await rename(options.logFilePath, backupPath);
|
|
285
299
|
try {
|
|
286
|
-
await
|
|
300
|
+
await chmod2(backupPath, STATE_FILE_MODE);
|
|
287
301
|
} catch (error) {
|
|
288
302
|
warn(`appduct: rotated "${options.logFilePath}" but could not set 0600 on "${backupPath}": ${error.message}`);
|
|
289
303
|
}
|
|
@@ -299,7 +313,7 @@ var rotateDaemonLogIfNeeded = async (options) => {
|
|
|
299
313
|
var ensureDaemonLogMode = async (logFilePath, warn = (message) => process.stderr.write(`${message}
|
|
300
314
|
`)) => {
|
|
301
315
|
try {
|
|
302
|
-
await
|
|
316
|
+
await chmod2(logFilePath, STATE_FILE_MODE);
|
|
303
317
|
return true;
|
|
304
318
|
} catch (error) {
|
|
305
319
|
warn(`appduct: could not set 0600 on "${logFilePath}": ${error.message}`);
|
|
@@ -640,6 +654,7 @@ var acquireSpawnLock = async (lockPath) => {
|
|
|
640
654
|
return false;
|
|
641
655
|
};
|
|
642
656
|
var spawnDaemonAndWait = async (paths, spawn, waitTimeoutMs, pollIntervalMs, options = {}) => {
|
|
657
|
+
await ensureStateDir(paths.root);
|
|
643
658
|
const acquiredLock = await acquireSpawnLock(paths.spawnLockPath);
|
|
644
659
|
let lockWaitTimedOut = false;
|
|
645
660
|
if (acquiredLock) {
|
package/dist/cli/routes/ls.js
CHANGED
|
@@ -9,7 +9,7 @@ import { join as join3 } from "node:path";
|
|
|
9
9
|
import { RPC_METHODS } from "@appduct/shared";
|
|
10
10
|
|
|
11
11
|
// src/daemon/log-rotation.ts
|
|
12
|
-
import { chmod, rename, stat } from "node:fs/promises";
|
|
12
|
+
import { chmod as chmod2, rename, stat } from "node:fs/promises";
|
|
13
13
|
|
|
14
14
|
// src/daemon/config.ts
|
|
15
15
|
import { readFile } from "node:fs/promises";
|
|
@@ -22,6 +22,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
22
22
|
"eventBufferSize",
|
|
23
23
|
"auditRetentionDays",
|
|
24
24
|
"daemonLogMaxBytes",
|
|
25
|
+
"eventsLogMaxBytes",
|
|
25
26
|
"policy",
|
|
26
27
|
"advertisedIp",
|
|
27
28
|
"scheme",
|
|
@@ -81,6 +82,7 @@ var defaultConfig = (paths) => {
|
|
|
81
82
|
eventBufferSize: 256,
|
|
82
83
|
auditRetentionDays: DEFAULT_AUDIT_RETENTION_DAYS,
|
|
83
84
|
daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
|
|
85
|
+
eventsLogMaxBytes: 10 * 1024 * 1024,
|
|
84
86
|
restartDaemonOnVersionMismatch: false,
|
|
85
87
|
policy: {
|
|
86
88
|
default: "allow",
|
|
@@ -143,6 +145,9 @@ var loadConfig = async (paths, options = {}) => {
|
|
|
143
145
|
if (parsed.daemonLogMaxBytes !== void 0) {
|
|
144
146
|
config.daemonLogMaxBytes = requirePositiveInteger(parsed.daemonLogMaxBytes, "daemonLogMaxBytes");
|
|
145
147
|
}
|
|
148
|
+
if (parsed.eventsLogMaxBytes !== void 0) {
|
|
149
|
+
config.eventsLogMaxBytes = requirePositiveInteger(parsed.eventsLogMaxBytes, "eventsLogMaxBytes");
|
|
150
|
+
}
|
|
146
151
|
if (parsed.restartDaemonOnVersionMismatch !== void 0) {
|
|
147
152
|
config.restartDaemonOnVersionMismatch = requireBoolean(
|
|
148
153
|
parsed.restartDaemonOnVersionMismatch,
|
|
@@ -236,18 +241,27 @@ var isSocketConnectable = (socketPath) => {
|
|
|
236
241
|
|
|
237
242
|
// src/daemon/state-dir.ts
|
|
238
243
|
import { join } from "node:path";
|
|
244
|
+
import { mkdir, chmod } from "node:fs/promises";
|
|
239
245
|
var getStateDirPaths = (stateDir) => {
|
|
240
246
|
return {
|
|
241
247
|
root: stateDir,
|
|
242
248
|
socketPath: join(stateDir, "daemon.sock"),
|
|
243
249
|
pidFilePath: join(stateDir, "daemon.pid"),
|
|
244
250
|
logFilePath: join(stateDir, "daemon.log"),
|
|
251
|
+
eventsLogPath: join(stateDir, "events.log"),
|
|
245
252
|
keyPath: join(stateDir, "key.pem"),
|
|
246
253
|
configPath: join(stateDir, "config.json"),
|
|
247
254
|
auditDir: join(stateDir, "audit"),
|
|
248
255
|
spawnLockPath: join(stateDir, "daemon.spawn.lock")
|
|
249
256
|
};
|
|
250
257
|
};
|
|
258
|
+
var ensureStateDir = async (stateDir) => {
|
|
259
|
+
const paths = getStateDirPaths(stateDir);
|
|
260
|
+
await mkdir(paths.auditDir, { recursive: true });
|
|
261
|
+
await chmod(paths.root, 448);
|
|
262
|
+
await chmod(paths.auditDir, 448);
|
|
263
|
+
return paths;
|
|
264
|
+
};
|
|
251
265
|
|
|
252
266
|
// src/daemon/log-rotation.ts
|
|
253
267
|
var STATE_FILE_MODE = 384;
|
|
@@ -280,7 +294,7 @@ var rotateDaemonLogIfNeeded = async (options) => {
|
|
|
280
294
|
const backupPath = daemonLogBackupPath(options.logFilePath);
|
|
281
295
|
await rename(options.logFilePath, backupPath);
|
|
282
296
|
try {
|
|
283
|
-
await
|
|
297
|
+
await chmod2(backupPath, STATE_FILE_MODE);
|
|
284
298
|
} catch (error) {
|
|
285
299
|
warn(`appduct: rotated "${options.logFilePath}" but could not set 0600 on "${backupPath}": ${error.message}`);
|
|
286
300
|
}
|
|
@@ -296,7 +310,7 @@ var rotateDaemonLogIfNeeded = async (options) => {
|
|
|
296
310
|
var ensureDaemonLogMode = async (logFilePath, warn = (message) => process.stderr.write(`${message}
|
|
297
311
|
`)) => {
|
|
298
312
|
try {
|
|
299
|
-
await
|
|
313
|
+
await chmod2(logFilePath, STATE_FILE_MODE);
|
|
300
314
|
return true;
|
|
301
315
|
} catch (error) {
|
|
302
316
|
warn(`appduct: could not set 0600 on "${logFilePath}": ${error.message}`);
|
|
@@ -637,6 +651,7 @@ var acquireSpawnLock = async (lockPath) => {
|
|
|
637
651
|
return false;
|
|
638
652
|
};
|
|
639
653
|
var spawnDaemonAndWait = async (paths, spawn, waitTimeoutMs, pollIntervalMs, options = {}) => {
|
|
654
|
+
await ensureStateDir(paths.root);
|
|
640
655
|
const acquiredLock = await acquireSpawnLock(paths.spawnLockPath);
|
|
641
656
|
let lockWaitTimedOut = false;
|
|
642
657
|
if (acquiredLock) {
|
package/dist/cli/routes/mcp.js
CHANGED
|
@@ -12,6 +12,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
12
12
|
"eventBufferSize",
|
|
13
13
|
"auditRetentionDays",
|
|
14
14
|
"daemonLogMaxBytes",
|
|
15
|
+
"eventsLogMaxBytes",
|
|
15
16
|
"policy",
|
|
16
17
|
"advertisedIp",
|
|
17
18
|
"scheme",
|
|
@@ -71,6 +72,7 @@ var defaultConfig = (paths) => {
|
|
|
71
72
|
eventBufferSize: 256,
|
|
72
73
|
auditRetentionDays: DEFAULT_AUDIT_RETENTION_DAYS,
|
|
73
74
|
daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
|
|
75
|
+
eventsLogMaxBytes: 10 * 1024 * 1024,
|
|
74
76
|
restartDaemonOnVersionMismatch: false,
|
|
75
77
|
policy: {
|
|
76
78
|
default: "allow",
|
|
@@ -133,6 +135,9 @@ var loadConfig = async (paths, options = {}) => {
|
|
|
133
135
|
if (parsed.daemonLogMaxBytes !== void 0) {
|
|
134
136
|
config.daemonLogMaxBytes = requirePositiveInteger(parsed.daemonLogMaxBytes, "daemonLogMaxBytes");
|
|
135
137
|
}
|
|
138
|
+
if (parsed.eventsLogMaxBytes !== void 0) {
|
|
139
|
+
config.eventsLogMaxBytes = requirePositiveInteger(parsed.eventsLogMaxBytes, "eventsLogMaxBytes");
|
|
140
|
+
}
|
|
136
141
|
if (parsed.restartDaemonOnVersionMismatch !== void 0) {
|
|
137
142
|
config.restartDaemonOnVersionMismatch = requireBoolean(
|
|
138
143
|
parsed.restartDaemonOnVersionMismatch,
|
|
@@ -171,18 +176,27 @@ var loadConfig = async (paths, options = {}) => {
|
|
|
171
176
|
|
|
172
177
|
// src/daemon/state-dir.ts
|
|
173
178
|
import { join } from "node:path";
|
|
179
|
+
import { mkdir, chmod } from "node:fs/promises";
|
|
174
180
|
var getStateDirPaths = (stateDir) => {
|
|
175
181
|
return {
|
|
176
182
|
root: stateDir,
|
|
177
183
|
socketPath: join(stateDir, "daemon.sock"),
|
|
178
184
|
pidFilePath: join(stateDir, "daemon.pid"),
|
|
179
185
|
logFilePath: join(stateDir, "daemon.log"),
|
|
186
|
+
eventsLogPath: join(stateDir, "events.log"),
|
|
180
187
|
keyPath: join(stateDir, "key.pem"),
|
|
181
188
|
configPath: join(stateDir, "config.json"),
|
|
182
189
|
auditDir: join(stateDir, "audit"),
|
|
183
190
|
spawnLockPath: join(stateDir, "daemon.spawn.lock")
|
|
184
191
|
};
|
|
185
192
|
};
|
|
193
|
+
var ensureStateDir = async (stateDir) => {
|
|
194
|
+
const paths = getStateDirPaths(stateDir);
|
|
195
|
+
await mkdir(paths.auditDir, { recursive: true });
|
|
196
|
+
await chmod(paths.root, 448);
|
|
197
|
+
await chmod(paths.auditDir, 448);
|
|
198
|
+
return paths;
|
|
199
|
+
};
|
|
186
200
|
|
|
187
201
|
// src/mcp/server.ts
|
|
188
202
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -267,7 +281,7 @@ import { join as join4 } from "node:path";
|
|
|
267
281
|
import { RPC_METHODS } from "@appduct/shared";
|
|
268
282
|
|
|
269
283
|
// src/daemon/log-rotation.ts
|
|
270
|
-
import { chmod, rename, stat } from "node:fs/promises";
|
|
284
|
+
import { chmod as chmod2, rename, stat } from "node:fs/promises";
|
|
271
285
|
|
|
272
286
|
// src/daemon/pidfile.ts
|
|
273
287
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
@@ -355,7 +369,7 @@ var rotateDaemonLogIfNeeded = async (options) => {
|
|
|
355
369
|
const backupPath = daemonLogBackupPath(options.logFilePath);
|
|
356
370
|
await rename(options.logFilePath, backupPath);
|
|
357
371
|
try {
|
|
358
|
-
await
|
|
372
|
+
await chmod2(backupPath, STATE_FILE_MODE);
|
|
359
373
|
} catch (error) {
|
|
360
374
|
warn(`appduct: rotated "${options.logFilePath}" but could not set 0600 on "${backupPath}": ${error.message}`);
|
|
361
375
|
}
|
|
@@ -371,7 +385,7 @@ var rotateDaemonLogIfNeeded = async (options) => {
|
|
|
371
385
|
var ensureDaemonLogMode = async (logFilePath, warn = (message) => process.stderr.write(`${message}
|
|
372
386
|
`)) => {
|
|
373
387
|
try {
|
|
374
|
-
await
|
|
388
|
+
await chmod2(logFilePath, STATE_FILE_MODE);
|
|
375
389
|
return true;
|
|
376
390
|
} catch (error) {
|
|
377
391
|
warn(`appduct: could not set 0600 on "${logFilePath}": ${error.message}`);
|
|
@@ -677,6 +691,7 @@ var acquireSpawnLock = async (lockPath) => {
|
|
|
677
691
|
return false;
|
|
678
692
|
};
|
|
679
693
|
var spawnDaemonAndWait = async (paths, spawn, waitTimeoutMs, pollIntervalMs, options = {}) => {
|
|
694
|
+
await ensureStateDir(paths.root);
|
|
680
695
|
const acquiredLock = await acquireSpawnLock(paths.spawnLockPath);
|
|
681
696
|
let lockWaitTimedOut = false;
|
|
682
697
|
if (acquiredLock) {
|
|
@@ -2531,7 +2546,10 @@ var toDescriptor = (entry) => {
|
|
|
2531
2546
|
output_schema: entry.output_schema,
|
|
2532
2547
|
annotations: entry.annotations,
|
|
2533
2548
|
timeout_ms: entry.timeout_ms,
|
|
2534
|
-
|
|
2549
|
+
// Normalised the same way the listing normalises it, so `appduct_describe_tool` never
|
|
2550
|
+
// disagrees with `appduct_list_tools` about whether a tool has a group. The `?? null` also
|
|
2551
|
+
// covers a daemon that predates groups and omits the key entirely.
|
|
2552
|
+
group: entry.group ?? null
|
|
2535
2553
|
};
|
|
2536
2554
|
};
|
|
2537
2555
|
var resolveSession = async (call, selector) => {
|
|
@@ -2638,27 +2656,24 @@ var resolveAppTool = async (call, selector, name) => {
|
|
|
2638
2656
|
|
|
2639
2657
|
// src/mcp/events-tool.ts
|
|
2640
2658
|
import {
|
|
2641
|
-
RPC_METHODS as RPC_METHODS5
|
|
2642
|
-
EVENT_KINDS
|
|
2659
|
+
RPC_METHODS as RPC_METHODS5
|
|
2643
2660
|
} from "@appduct/shared";
|
|
2644
2661
|
var EVENTS_TOOL_NAME = "appduct_events";
|
|
2645
2662
|
var WAIT_FOR_EVENT_TOOL_NAME = "appduct_wait_for_event";
|
|
2646
2663
|
var DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MS = 12e4;
|
|
2647
2664
|
var MAX_WAIT_FOR_EVENT_TIMEOUT_MS = 25 * 60 * 1e3;
|
|
2648
2665
|
var WAIT_FOR_EVENT_PROGRESS_INTERVAL_MS = 6e4;
|
|
2649
|
-
var KNOWN_EVENT_KINDS = new Set(EVENT_KINDS);
|
|
2650
2666
|
var clampWaitForEventTimeoutMs = (requestedMs) => {
|
|
2651
2667
|
return Math.min(requestedMs ?? DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MS, MAX_WAIT_FOR_EVENT_TIMEOUT_MS);
|
|
2652
2668
|
};
|
|
2653
2669
|
var EVENTS_TOOL_DESCRIPTOR = {
|
|
2654
2670
|
name: EVENTS_TOOL_NAME,
|
|
2655
|
-
description: "Drain events
|
|
2671
|
+
description: "Drain the events the app posted with postEvent(name, payload) that the daemon retained for a session since a cursor, for checking what the app reported after calling a tool or triggering app behavior. With no selector, targets the sole active/suspended session. Returns { events, cursor }; pass cursor back as since on the next call to avoid re-reading events you've already seen. limit (if given) keeps the OLDEST events in the window and advances cursor only past what was actually returned, so repeated calls page forward through everything retained rather than skipping ahead.",
|
|
2656
2672
|
inputSchema: {
|
|
2657
2673
|
type: "object",
|
|
2658
2674
|
properties: {
|
|
2659
2675
|
selector: { type: "string" },
|
|
2660
2676
|
since: { type: "integer", minimum: 0 },
|
|
2661
|
-
kinds: { type: "array", items: { type: "string", enum: EVENT_KINDS } },
|
|
2662
2677
|
limit: { type: "integer", exclusiveMinimum: 0 }
|
|
2663
2678
|
},
|
|
2664
2679
|
additionalProperties: false
|
|
@@ -2722,14 +2737,13 @@ var asOptionalPositiveNumber2 = (value, field) => {
|
|
|
2722
2737
|
}
|
|
2723
2738
|
return value;
|
|
2724
2739
|
};
|
|
2725
|
-
var
|
|
2726
|
-
if (
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2740
|
+
var rejectKinds = (args) => {
|
|
2741
|
+
if (args.kinds !== void 0) {
|
|
2742
|
+
throw new McpBuiltinToolError(
|
|
2743
|
+
"invalid_request",
|
|
2744
|
+
'"kinds" is not supported: these tools return only the events the app posted with postEvent.'
|
|
2745
|
+
);
|
|
2731
2746
|
}
|
|
2732
|
-
return value;
|
|
2733
2747
|
};
|
|
2734
2748
|
var isMatchPrimitive = (value) => {
|
|
2735
2749
|
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
@@ -2754,10 +2768,10 @@ var asOptionalMatch = (value) => {
|
|
|
2754
2768
|
};
|
|
2755
2769
|
var handleEventsTool = async (rawArgs, deps) => {
|
|
2756
2770
|
const args = asRecord3(rawArgs);
|
|
2771
|
+
rejectKinds(args);
|
|
2757
2772
|
return deps.call(RPC_METHODS5.eventsSince, {
|
|
2758
2773
|
selector: asOptionalString2(args.selector, "selector"),
|
|
2759
2774
|
since: asOptionalNonNegativeInteger(args.since, "since"),
|
|
2760
|
-
kinds: asOptionalEventKinds(args.kinds),
|
|
2761
2775
|
limit: asOptionalPositiveInteger(args.limit, "limit")
|
|
2762
2776
|
});
|
|
2763
2777
|
};
|
|
@@ -2770,6 +2784,7 @@ var payloadShallowMatches = (payload, match) => {
|
|
|
2770
2784
|
};
|
|
2771
2785
|
var handleWaitForEventTool = async (rawArgs, deps) => {
|
|
2772
2786
|
const args = asRecord3(rawArgs);
|
|
2787
|
+
rejectKinds(args);
|
|
2773
2788
|
const selector = asOptionalString2(args.selector, "selector");
|
|
2774
2789
|
const name = asOptionalString2(args.name, "name");
|
|
2775
2790
|
if (!name) {
|
|
@@ -2812,8 +2827,7 @@ var handleWaitForEventTool = async (rawArgs, deps) => {
|
|
|
2812
2827
|
await stream.call(RPC_METHODS5.eventsSubscribe, { sessionSelector: sessionId, kinds: ["app_event"] });
|
|
2813
2828
|
const sinceResult = await stream.call(RPC_METHODS5.eventsSince, {
|
|
2814
2829
|
selector: sessionId,
|
|
2815
|
-
since
|
|
2816
|
-
kinds: ["app_event"]
|
|
2830
|
+
since
|
|
2817
2831
|
});
|
|
2818
2832
|
const seenSeqs = /* @__PURE__ */ new Set();
|
|
2819
2833
|
const backlog = [];
|
|
@@ -9,7 +9,7 @@ import { join as join3 } from "node:path";
|
|
|
9
9
|
import { RPC_METHODS } from "@appduct/shared";
|
|
10
10
|
|
|
11
11
|
// src/daemon/log-rotation.ts
|
|
12
|
-
import { chmod, rename, stat } from "node:fs/promises";
|
|
12
|
+
import { chmod as chmod2, rename, stat } from "node:fs/promises";
|
|
13
13
|
|
|
14
14
|
// src/daemon/config.ts
|
|
15
15
|
import { readFile } from "node:fs/promises";
|
|
@@ -22,6 +22,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
22
22
|
"eventBufferSize",
|
|
23
23
|
"auditRetentionDays",
|
|
24
24
|
"daemonLogMaxBytes",
|
|
25
|
+
"eventsLogMaxBytes",
|
|
25
26
|
"policy",
|
|
26
27
|
"advertisedIp",
|
|
27
28
|
"scheme",
|
|
@@ -81,6 +82,7 @@ var defaultConfig = (paths) => {
|
|
|
81
82
|
eventBufferSize: 256,
|
|
82
83
|
auditRetentionDays: DEFAULT_AUDIT_RETENTION_DAYS,
|
|
83
84
|
daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
|
|
85
|
+
eventsLogMaxBytes: 10 * 1024 * 1024,
|
|
84
86
|
restartDaemonOnVersionMismatch: false,
|
|
85
87
|
policy: {
|
|
86
88
|
default: "allow",
|
|
@@ -143,6 +145,9 @@ var loadConfig = async (paths, options = {}) => {
|
|
|
143
145
|
if (parsed.daemonLogMaxBytes !== void 0) {
|
|
144
146
|
config.daemonLogMaxBytes = requirePositiveInteger(parsed.daemonLogMaxBytes, "daemonLogMaxBytes");
|
|
145
147
|
}
|
|
148
|
+
if (parsed.eventsLogMaxBytes !== void 0) {
|
|
149
|
+
config.eventsLogMaxBytes = requirePositiveInteger(parsed.eventsLogMaxBytes, "eventsLogMaxBytes");
|
|
150
|
+
}
|
|
146
151
|
if (parsed.restartDaemonOnVersionMismatch !== void 0) {
|
|
147
152
|
config.restartDaemonOnVersionMismatch = requireBoolean(
|
|
148
153
|
parsed.restartDaemonOnVersionMismatch,
|
|
@@ -236,18 +241,27 @@ var isSocketConnectable = (socketPath) => {
|
|
|
236
241
|
|
|
237
242
|
// src/daemon/state-dir.ts
|
|
238
243
|
import { join } from "node:path";
|
|
244
|
+
import { mkdir, chmod } from "node:fs/promises";
|
|
239
245
|
var getStateDirPaths = (stateDir) => {
|
|
240
246
|
return {
|
|
241
247
|
root: stateDir,
|
|
242
248
|
socketPath: join(stateDir, "daemon.sock"),
|
|
243
249
|
pidFilePath: join(stateDir, "daemon.pid"),
|
|
244
250
|
logFilePath: join(stateDir, "daemon.log"),
|
|
251
|
+
eventsLogPath: join(stateDir, "events.log"),
|
|
245
252
|
keyPath: join(stateDir, "key.pem"),
|
|
246
253
|
configPath: join(stateDir, "config.json"),
|
|
247
254
|
auditDir: join(stateDir, "audit"),
|
|
248
255
|
spawnLockPath: join(stateDir, "daemon.spawn.lock")
|
|
249
256
|
};
|
|
250
257
|
};
|
|
258
|
+
var ensureStateDir = async (stateDir) => {
|
|
259
|
+
const paths = getStateDirPaths(stateDir);
|
|
260
|
+
await mkdir(paths.auditDir, { recursive: true });
|
|
261
|
+
await chmod(paths.root, 448);
|
|
262
|
+
await chmod(paths.auditDir, 448);
|
|
263
|
+
return paths;
|
|
264
|
+
};
|
|
251
265
|
|
|
252
266
|
// src/daemon/log-rotation.ts
|
|
253
267
|
var STATE_FILE_MODE = 384;
|
|
@@ -280,7 +294,7 @@ var rotateDaemonLogIfNeeded = async (options) => {
|
|
|
280
294
|
const backupPath = daemonLogBackupPath(options.logFilePath);
|
|
281
295
|
await rename(options.logFilePath, backupPath);
|
|
282
296
|
try {
|
|
283
|
-
await
|
|
297
|
+
await chmod2(backupPath, STATE_FILE_MODE);
|
|
284
298
|
} catch (error) {
|
|
285
299
|
warn(`appduct: rotated "${options.logFilePath}" but could not set 0600 on "${backupPath}": ${error.message}`);
|
|
286
300
|
}
|
|
@@ -296,7 +310,7 @@ var rotateDaemonLogIfNeeded = async (options) => {
|
|
|
296
310
|
var ensureDaemonLogMode = async (logFilePath, warn = (message) => process.stderr.write(`${message}
|
|
297
311
|
`)) => {
|
|
298
312
|
try {
|
|
299
|
-
await
|
|
313
|
+
await chmod2(logFilePath, STATE_FILE_MODE);
|
|
300
314
|
return true;
|
|
301
315
|
} catch (error) {
|
|
302
316
|
warn(`appduct: could not set 0600 on "${logFilePath}": ${error.message}`);
|
|
@@ -637,6 +651,7 @@ var acquireSpawnLock = async (lockPath) => {
|
|
|
637
651
|
return false;
|
|
638
652
|
};
|
|
639
653
|
var spawnDaemonAndWait = async (paths, spawn, waitTimeoutMs, pollIntervalMs, options = {}) => {
|
|
654
|
+
await ensureStateDir(paths.root);
|
|
640
655
|
const acquiredLock = await acquireSpawnLock(paths.spawnLockPath);
|
|
641
656
|
let lockWaitTimedOut = false;
|
|
642
657
|
if (acquiredLock) {
|