appduct 0.11.1 → 0.13.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.
Files changed (74) hide show
  1. package/README.md +43 -38
  2. package/dist/bin.js +50 -36
  3. package/dist/cli/command-options.d.ts +7 -17
  4. package/dist/cli/result-types.d.ts +11 -8
  5. package/dist/cli/routes/daemon/index.js +12 -16
  6. package/dist/cli/routes/daemon/run.js +216 -46
  7. package/dist/cli/routes/daemon/start.js +18 -16
  8. package/dist/cli/routes/daemon/status.js +18 -16
  9. package/dist/cli/routes/daemon/stop.js +18 -16
  10. package/dist/cli/routes/doctor.js +12 -16
  11. package/dist/cli/routes/events/index.d.ts +9 -0
  12. package/dist/cli/routes/events/index.js +842 -0
  13. package/dist/cli/routes/events/shared-options.d.ts +12 -0
  14. package/dist/cli/routes/events/shared-options.js +102 -0
  15. package/dist/cli/routes/events/since.d.ts +6 -0
  16. package/dist/cli/routes/events/since.js +1977 -0
  17. package/dist/cli/routes/events/tail.d.ts +6 -0
  18. package/dist/cli/routes/{events.js → events/tail.js} +184 -162
  19. package/dist/cli/routes/init.js +14 -18
  20. package/dist/cli/routes/keygen.js +13 -16
  21. package/dist/cli/routes/mcp.js +193 -175
  22. package/dist/cli/routes/sessions/index.d.ts +7 -0
  23. package/dist/cli/routes/sessions/index.js +843 -0
  24. package/dist/cli/routes/sessions/link.d.ts +4 -0
  25. package/dist/cli/routes/{link.js → sessions/link.js} +19 -17
  26. package/dist/cli/routes/sessions/ls.d.ts +4 -0
  27. package/dist/cli/routes/{ls.js → sessions/ls.js} +19 -17
  28. package/dist/cli/routes/sessions/revoke.d.ts +4 -0
  29. package/dist/cli/routes/{revoke.js → sessions/revoke.js} +20 -18
  30. package/dist/cli/routes/tools/call.d.ts +4 -0
  31. package/dist/cli/routes/{invoke.js → tools/call.js} +21 -19
  32. package/dist/cli/routes/tools/describe.d.ts +6 -0
  33. package/dist/cli/routes/tools/describe.js +1837 -0
  34. package/dist/cli/routes/tools/index.d.ts +6 -0
  35. package/dist/cli/routes/tools/index.js +843 -0
  36. package/dist/cli/routes/tools/ls.d.ts +4 -0
  37. package/dist/cli/routes/{tools.js → tools/ls.js} +46 -73
  38. package/dist/cli/routes/tools/shared-options.d.ts +16 -0
  39. package/dist/cli/routes/tools/shared-options.js +122 -0
  40. package/dist/client/app-client.d.ts +96 -37
  41. package/dist/client/index.d.ts +3 -2
  42. package/dist/client/index.js +183 -139
  43. package/dist/commands/events.d.ts +31 -13
  44. package/dist/commands/invoke.d.ts +1 -1
  45. package/dist/commands/link.d.ts +1 -1
  46. package/dist/commands/ls.d.ts +1 -1
  47. package/dist/commands/revoke.d.ts +1 -1
  48. package/dist/commands/tools.d.ts +12 -13
  49. package/dist/daemon/config.d.ts +5 -2
  50. package/dist/daemon/daemon.d.ts +3 -0
  51. package/dist/daemon/event-bus.d.ts +36 -4
  52. package/dist/daemon/events-log.d.ts +31 -0
  53. package/dist/daemon/memory-append-only-file.d.ts +17 -0
  54. package/dist/daemon/node-append-only-file.d.ts +9 -0
  55. package/dist/daemon/rpc-errors.d.ts +1 -1
  56. package/dist/daemon/state-dir.d.ts +2 -0
  57. package/dist/errors.d.ts +1 -1
  58. package/dist/events/index.d.ts +60 -0
  59. package/dist/index.js +62 -39
  60. package/dist/link.d.ts +1 -1
  61. package/dist/mcp/app-tools.d.ts +3 -2
  62. package/dist/mcp/connect-tool.d.ts +1 -1
  63. package/dist/mcp/events-tool.d.ts +28 -34
  64. package/dist/native-scheme.d.ts +1 -1
  65. package/dist/output.d.ts +29 -6
  66. package/dist/rpc/client.d.ts +1 -1
  67. package/dist/scheme.d.ts +4 -4
  68. package/package.json +2 -2
  69. package/dist/cli/routes/events.d.ts +0 -3
  70. package/dist/cli/routes/invoke.d.ts +0 -3
  71. package/dist/cli/routes/link.d.ts +0 -3
  72. package/dist/cli/routes/ls.d.ts +0 -3
  73. package/dist/cli/routes/revoke.d.ts +0 -3
  74. package/dist/cli/routes/tools.d.ts +0 -3
@@ -139,6 +139,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
139
139
  "eventBufferSize",
140
140
  "auditRetentionDays",
141
141
  "daemonLogMaxBytes",
142
+ "eventsLogMaxBytes",
142
143
  "policy",
143
144
  "advertisedIp",
144
145
  "scheme",
@@ -198,6 +199,7 @@ var defaultConfig = (paths) => {
198
199
  eventBufferSize: 256,
199
200
  auditRetentionDays: DEFAULT_AUDIT_RETENTION_DAYS,
200
201
  daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
202
+ eventsLogMaxBytes: 10 * 1024 * 1024,
201
203
  restartDaemonOnVersionMismatch: false,
202
204
  policy: {
203
205
  default: "allow",
@@ -260,6 +262,9 @@ var loadConfig = async (paths, options = {}) => {
260
262
  if (parsed.daemonLogMaxBytes !== void 0) {
261
263
  config.daemonLogMaxBytes = requirePositiveInteger(parsed.daemonLogMaxBytes, "daemonLogMaxBytes");
262
264
  }
265
+ if (parsed.eventsLogMaxBytes !== void 0) {
266
+ config.eventsLogMaxBytes = requirePositiveInteger(parsed.eventsLogMaxBytes, "eventsLogMaxBytes");
267
+ }
263
268
  if (parsed.restartDaemonOnVersionMismatch !== void 0) {
264
269
  config.restartDaemonOnVersionMismatch = requireBoolean(
265
270
  parsed.restartDaemonOnVersionMismatch,
@@ -609,7 +614,71 @@ var createCallsManager = (options) => {
609
614
 
610
615
  // src/daemon/event-bus.ts
611
616
  var TERMINAL_EVENT_KINDS = /* @__PURE__ */ new Set(["session_expired", "session_revoked"]);
612
- var EXCLUDED_FROM_RETENTION = /* @__PURE__ */ new Set(["tool_call_progress"]);
617
+ var utf8ByteLength = (text) => Buffer.byteLength(text, "utf8");
618
+ var truncateToUtf8Bytes = (text, maxBytes) => {
619
+ const encoded = Buffer.from(text, "utf8");
620
+ if (encoded.length <= maxBytes) {
621
+ return text;
622
+ }
623
+ const decoder = new TextDecoder("utf-8", { fatal: true });
624
+ for (let end = maxBytes; end > 0; end--) {
625
+ try {
626
+ return decoder.decode(encoded.subarray(0, end));
627
+ } catch {
628
+ }
629
+ }
630
+ return "";
631
+ };
632
+ var matchesNameGlob = (pattern, name) => {
633
+ const pieces = pattern.split("*");
634
+ const first = pieces[0] ?? "";
635
+ if (pieces.length === 1) {
636
+ return name === first;
637
+ }
638
+ const last = pieces[pieces.length - 1] ?? "";
639
+ if (first.length + last.length > name.length || !name.startsWith(first) || !name.endsWith(last)) {
640
+ return false;
641
+ }
642
+ const end = name.length - last.length;
643
+ let position = first.length;
644
+ for (const piece of pieces.slice(1, -1)) {
645
+ const found = name.indexOf(piece, position);
646
+ if (found === -1 || found + piece.length > end) {
647
+ return false;
648
+ }
649
+ position = found + piece.length;
650
+ }
651
+ return true;
652
+ };
653
+ var projectAppEvent = (event, options = {}) => {
654
+ if (event.kind !== "app_event") {
655
+ return event;
656
+ }
657
+ const data = event.data;
658
+ if (options.name !== void 0 && !(typeof data.name === "string" && matchesNameGlob(options.name, data.name))) {
659
+ return void 0;
660
+ }
661
+ if (options.payloadMaxBytes === void 0 || !("payload" in data)) {
662
+ return event;
663
+ }
664
+ const payloadJson = JSON.stringify(data.payload);
665
+ if (payloadJson === void 0) {
666
+ return event;
667
+ }
668
+ const payloadBytes = utf8ByteLength(payloadJson);
669
+ if (payloadBytes <= options.payloadMaxBytes) {
670
+ return event;
671
+ }
672
+ return {
673
+ ...event,
674
+ data: {
675
+ name: data.name,
676
+ payloadPreview: truncateToUtf8Bytes(payloadJson, options.payloadMaxBytes),
677
+ truncated: true,
678
+ payloadBytes
679
+ }
680
+ };
681
+ };
613
682
  var createEventBus = (options = {}) => {
614
683
  const clock = options.clock ?? { now: () => /* @__PURE__ */ new Date() };
615
684
  const bufferSize = options.bufferSize ?? 256;
@@ -627,8 +696,9 @@ var createEventBus = (options = {}) => {
627
696
  return {
628
697
  emit: (event) => {
629
698
  const sessionId = event.sessionId;
630
- const seq = sessionId !== void 0 ? (cursors.get(sessionId) ?? 0) + 1 : 0;
631
- if (sessionId !== void 0) {
699
+ let seq = 0;
700
+ if (sessionId !== void 0 && event.kind === "app_event") {
701
+ seq = (cursors.get(sessionId) ?? 0) + 1;
632
702
  cursors.set(sessionId, seq);
633
703
  }
634
704
  const notification = {
@@ -640,7 +710,7 @@ var createEventBus = (options = {}) => {
640
710
  if (TERMINAL_EVENT_KINDS.has(notification.kind)) {
641
711
  buffers.delete(sessionId);
642
712
  cursors.delete(sessionId);
643
- } else if (!EXCLUDED_FROM_RETENTION.has(notification.kind)) {
713
+ } else if (notification.kind === "app_event") {
644
714
  appendToBuffer(sessionId, notification);
645
715
  }
646
716
  }
@@ -660,19 +730,26 @@ var createEventBus = (options = {}) => {
660
730
  since: (sessionId, query = {}) => {
661
731
  const buffer = buffers.get(sessionId) ?? [];
662
732
  const sessionCursor = cursors.get(sessionId) ?? 0;
663
- let events = buffer.slice();
733
+ const sinceValue = query.since ?? 0;
734
+ const oldestRetainedSeq = buffer.length > 0 ? buffer[0].seq : void 0;
735
+ const dropped = oldestRetainedSeq !== void 0 ? Math.max(0, oldestRetainedSeq - sinceValue - 1) : 0;
736
+ let matching = buffer.slice();
664
737
  if (query.since !== void 0) {
665
- events = events.filter((event) => event.seq > query.since);
738
+ matching = matching.filter((event) => event.seq > query.since);
666
739
  }
667
- if (query.kinds !== void 0) {
668
- const kinds = new Set(query.kinds);
669
- events = events.filter((event) => kinds.has(event.kind));
740
+ if (query.name !== void 0) {
741
+ matching = matching.filter((event) => projectAppEvent(event, { name: query.name }) !== void 0);
670
742
  }
743
+ let events = matching;
671
744
  if (query.limit !== void 0 && events.length > query.limit) {
672
745
  events = events.slice(0, query.limit);
673
746
  }
747
+ const remaining = matching.length - events.length;
674
748
  const cursor = events.length > 0 ? events[events.length - 1].seq : sessionCursor;
675
- return { events, cursor };
749
+ if (query.payloadMaxBytes !== void 0) {
750
+ events = events.map((event) => projectAppEvent(event, { payloadMaxBytes: query.payloadMaxBytes }));
751
+ }
752
+ return { events, cursor, dropped, remaining };
676
753
  },
677
754
  drop: (sessionId) => {
678
755
  buffers.delete(sessionId);
@@ -681,6 +758,28 @@ var createEventBus = (options = {}) => {
681
758
  };
682
759
  };
683
760
 
761
+ // src/daemon/events-log.ts
762
+ var createEventsLog = (options) => {
763
+ const { file, maxBytes, warn } = options;
764
+ let queue = Promise.resolve();
765
+ return {
766
+ record: (event) => {
767
+ const line = JSON.stringify(event);
768
+ queue = queue.then(async () => {
769
+ try {
770
+ await file.append(line);
771
+ if (await file.size() > maxBytes) {
772
+ await file.rotate();
773
+ }
774
+ } catch (error) {
775
+ warn(`appduct daemon: failed to write events.log: ${error.message}`);
776
+ }
777
+ });
778
+ },
779
+ flush: () => queue
780
+ };
781
+ };
782
+
684
783
  // src/daemon/listener.ts
685
784
  import { createServer as createHttpsServer } from "node:https";
686
785
  import { isSessionClaimMessage, isSessionResumeMessage, isSessionBoundMessage } from "@appduct/shared";
@@ -1409,6 +1508,37 @@ var acquirePidfile = async (pidFilePath, options = {}) => {
1409
1508
  throw new Error(`Failed to acquire the Appduct daemon pidfile at "${pidFilePath}".`);
1410
1509
  };
1411
1510
 
1511
+ // src/daemon/node-append-only-file.ts
1512
+ import { open as open3, rename, stat as stat2 } from "node:fs/promises";
1513
+ var NodeAppendOnlyFile = class {
1514
+ constructor(path) {
1515
+ this.path = path;
1516
+ }
1517
+ async append(line) {
1518
+ const handle = await open3(this.path, "a", 384);
1519
+ try {
1520
+ await handle.appendFile(`${line}
1521
+ `);
1522
+ await handle.chmod(384);
1523
+ } finally {
1524
+ await handle.close();
1525
+ }
1526
+ }
1527
+ async size() {
1528
+ try {
1529
+ return (await stat2(this.path)).size;
1530
+ } catch (error) {
1531
+ if (error.code === "ENOENT") {
1532
+ return 0;
1533
+ }
1534
+ throw error;
1535
+ }
1536
+ }
1537
+ async rotate() {
1538
+ await rename(this.path, `${this.path}.1`);
1539
+ }
1540
+ };
1541
+
1412
1542
  // src/daemon/rpc-server.ts
1413
1543
  import { createServer } from "node:net";
1414
1544
  import { chmod as chmod2, rm as rm3 } from "node:fs/promises";
@@ -1594,6 +1724,7 @@ var getStateDirPaths = (stateDir) => {
1594
1724
  socketPath: join4(stateDir, "daemon.sock"),
1595
1725
  pidFilePath: join4(stateDir, "daemon.pid"),
1596
1726
  logFilePath: join4(stateDir, "daemon.log"),
1727
+ eventsLogPath: join4(stateDir, "events.log"),
1597
1728
  keyPath: join4(stateDir, "key.pem"),
1598
1729
  configPath: join4(stateDir, "config.json"),
1599
1730
  auditDir: join4(stateDir, "audit"),
@@ -1612,7 +1743,7 @@ var ensureStateDir = async (stateDir) => {
1612
1743
  };
1613
1744
 
1614
1745
  // src/daemon/tls.ts
1615
- import { stat as stat2, readFile as readFile3 } from "node:fs/promises";
1746
+ import { stat as stat3, readFile as readFile3 } from "node:fs/promises";
1616
1747
 
1617
1748
  // src/host-certificate.ts
1618
1749
  import "reflect-metadata";
@@ -1789,7 +1920,7 @@ var generateHostCertificate = async (keyPem, advertisedIp) => {
1789
1920
  // src/key-material.ts
1790
1921
  import { generateKeyPairSync } from "node:crypto";
1791
1922
  import { dirname as dirname2 } from "node:path";
1792
- import { mkdir as mkdir3, rename, rm as rm4, writeFile } from "node:fs/promises";
1923
+ import { mkdir as mkdir3, rename as rename2, rm as rm4, writeFile } from "node:fs/promises";
1793
1924
  var generatePrivateKeyPem = () => {
1794
1925
  const { privateKey } = generateKeyPairSync("rsa", {
1795
1926
  modulusLength: 2048,
@@ -1809,7 +1940,7 @@ var writePrivateKeyAtomically = async (path, keyPem) => {
1809
1940
  mode: 384,
1810
1941
  flag: "wx"
1811
1942
  });
1812
- await rename(temporaryPath, path);
1943
+ await rename2(temporaryPath, path);
1813
1944
  } catch (error) {
1814
1945
  await rm4(temporaryPath, { force: true }).catch(() => {
1815
1946
  });
@@ -1825,7 +1956,7 @@ var HostKeyError = class extends Error {
1825
1956
  }
1826
1957
  };
1827
1958
  var loadHostKeyPem = async (keyPath) => {
1828
- const fileStat = await stat2(keyPath);
1959
+ const fileStat = await stat3(keyPath);
1829
1960
  if ((fileStat.mode & 63) !== 0) {
1830
1961
  throw new HostKeyError(
1831
1962
  `Appduct host key at "${keyPath}" is group- or world-readable (mode ${(fileStat.mode & 511).toString(8)}). Refusing to load it \u2014 run "chmod 600 ${keyPath}" or re-run "appduct keygen".`
@@ -2018,7 +2149,26 @@ var asEventsSubscribeParams = (params) => {
2018
2149
  }
2019
2150
  kinds = kindsRaw;
2020
2151
  }
2021
- return { sessionSelector, kinds };
2152
+ const name = record.name;
2153
+ if (name !== void 0 && typeof name !== "string") {
2154
+ throw new RpcApplicationError("invalid_request", '"name" must be a string.');
2155
+ }
2156
+ const payloadMaxBytes = asPayloadMaxBytes(record.payloadMaxBytes);
2157
+ return {
2158
+ sessionSelector,
2159
+ kinds,
2160
+ name,
2161
+ payloadMaxBytes
2162
+ };
2163
+ };
2164
+ var asPayloadMaxBytes = (value) => {
2165
+ if (value === void 0) {
2166
+ return void 0;
2167
+ }
2168
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
2169
+ throw new RpcApplicationError("invalid_request", '"payloadMaxBytes" must be a positive integer.');
2170
+ }
2171
+ return value;
2022
2172
  };
2023
2173
  var asEventsSinceParams = (params) => {
2024
2174
  const record = asRecordParams(params);
@@ -2030,23 +2180,21 @@ var asEventsSinceParams = (params) => {
2030
2180
  if (since !== void 0 && (typeof since !== "number" || !Number.isInteger(since) || since < 0)) {
2031
2181
  throw new RpcApplicationError("invalid_request", '"since" must be a non-negative integer.');
2032
2182
  }
2033
- const kindsRaw = record.kinds;
2034
- let kinds;
2035
- if (kindsRaw !== void 0) {
2036
- if (!Array.isArray(kindsRaw) || !kindsRaw.every((kind) => typeof kind === "string" && KNOWN_EVENT_KINDS.has(kind))) {
2037
- throw new RpcApplicationError("invalid_request", '"kinds" must be an array of known event kinds.');
2038
- }
2039
- kinds = kindsRaw;
2040
- }
2041
2183
  const limit = record.limit;
2042
2184
  if (limit !== void 0 && (typeof limit !== "number" || !Number.isInteger(limit) || limit <= 0)) {
2043
2185
  throw new RpcApplicationError("invalid_request", '"limit" must be a positive integer.');
2044
2186
  }
2187
+ const name = record.name;
2188
+ if (name !== void 0 && typeof name !== "string") {
2189
+ throw new RpcApplicationError("invalid_request", '"name" must be a string.');
2190
+ }
2191
+ const payloadMaxBytes = asPayloadMaxBytes(record.payloadMaxBytes);
2045
2192
  return {
2046
2193
  selector,
2047
2194
  since,
2048
- kinds,
2049
- limit
2195
+ limit,
2196
+ name,
2197
+ payloadMaxBytes
2050
2198
  };
2051
2199
  };
2052
2200
  var asLinkCreateParams = (params) => {
@@ -2080,10 +2228,19 @@ var startDaemon = async (options) => {
2080
2228
  retentionDays: config.auditRetentionDays,
2081
2229
  warn: options.warn
2082
2230
  });
2231
+ const eventsLog = createEventsLog({
2232
+ file: new NodeAppendOnlyFile(paths.eventsLogPath),
2233
+ maxBytes: config.eventsLogMaxBytes,
2234
+ warn: options.warn ?? ((message) => {
2235
+ process.stderr.write(`${message}
2236
+ `);
2237
+ })
2238
+ });
2083
2239
  let auditPruneInterval;
2084
2240
  let pidfile;
2085
2241
  let server;
2086
2242
  let listener;
2243
+ let tlsManager;
2087
2244
  let sessionManager;
2088
2245
  let callsManager;
2089
2246
  let eventBus;
@@ -2110,6 +2267,7 @@ var startDaemon = async (options) => {
2110
2267
  await pidfile?.release();
2111
2268
  await rm5(getSocketPath(paths), { force: true });
2112
2269
  await auditLogger.flush();
2270
+ await eventsLog.flush();
2113
2271
  } finally {
2114
2272
  resolveExited();
2115
2273
  }
@@ -2122,8 +2280,14 @@ var startDaemon = async (options) => {
2122
2280
  });
2123
2281
  eventBus = createEventBus({ clock, bufferSize: config.eventBufferSize });
2124
2282
  const activeEventBus = eventBus;
2283
+ activeEventBus.subscribe((event) => {
2284
+ if (event.kind !== "app_event") {
2285
+ eventsLog.record(event);
2286
+ }
2287
+ });
2125
2288
  const detectAddress = options.detectAddress ?? (() => detectAdvertisedAddress({ override: config.advertisedIp }));
2126
2289
  const tls = await createTlsManager({ keyPath: config.keyPath, detectAddress, warn: options.warn });
2290
+ tlsManager = tls;
2127
2291
  sessionManager = createSessionManager({
2128
2292
  graceSeconds: config.graceSeconds,
2129
2293
  keepaliveIntervalSeconds: config.keepaliveIntervalSeconds,
@@ -2177,10 +2341,17 @@ var startDaemon = async (options) => {
2177
2341
  if (subscription.kinds && !subscription.kinds.has(event.kind)) {
2178
2342
  continue;
2179
2343
  }
2344
+ const projected = projectAppEvent(event, {
2345
+ name: subscription.name,
2346
+ payloadMaxBytes: subscription.payloadMaxBytes
2347
+ });
2348
+ if (projected === void 0) {
2349
+ continue;
2350
+ }
2180
2351
  if (subscription.sessionSelector !== void 0 && event.sessionId !== subscription.sessionSelector && event.alias !== subscription.sessionSelector) {
2181
2352
  continue;
2182
2353
  }
2183
- server?.notify(connection, event);
2354
+ server?.notify(connection, projected);
2184
2355
  }
2185
2356
  });
2186
2357
  server = await startRpcServer({
@@ -2359,17 +2530,19 @@ var startDaemon = async (options) => {
2359
2530
  return { cancelled };
2360
2531
  },
2361
2532
  [RPC_METHODS.eventsSubscribe]: (params, context) => {
2362
- const { sessionSelector, kinds } = asEventsSubscribeParams(params);
2533
+ const { sessionSelector, kinds, name, payloadMaxBytes } = asEventsSubscribeParams(params);
2363
2534
  context.connection.state.eventSubscription = {
2364
2535
  sessionSelector,
2365
- kinds: kinds ? new Set(kinds) : void 0
2536
+ kinds: kinds ? new Set(kinds) : void 0,
2537
+ name,
2538
+ payloadMaxBytes
2366
2539
  };
2367
2540
  return { ok: true };
2368
2541
  },
2369
2542
  [RPC_METHODS.eventsSince]: (params) => {
2370
- const { selector, since, kinds, limit } = asEventsSinceParams(params);
2543
+ const { selector, since, limit, name, payloadMaxBytes } = asEventsSinceParams(params);
2371
2544
  const resolved = activeSessionManager.describe(selector);
2372
- return activeEventBus.since(resolved.sessionId, { since, kinds, limit });
2545
+ return activeEventBus.since(resolved.sessionId, { since, limit, name, payloadMaxBytes });
2373
2546
  }
2374
2547
  }
2375
2548
  });
@@ -2395,6 +2568,7 @@ var startDaemon = async (options) => {
2395
2568
  startedAt,
2396
2569
  server,
2397
2570
  listener,
2571
+ tls: tlsManager,
2398
2572
  eventBus,
2399
2573
  exited,
2400
2574
  shutdown
@@ -2729,9 +2903,6 @@ var renderLsData = (colors, data, now) => {
2729
2903
  ...rows.map((row) => ` ${row.map((cell, index) => cell.padEnd(widths[index])).join(" ")}`)
2730
2904
  ];
2731
2905
  };
2732
- var isToolsListing = (data) => {
2733
- return typeof data === "object" && data !== null && Array.isArray(data.tools);
2734
- };
2735
2906
  var isToolGroupsListing = (data) => {
2736
2907
  return typeof data === "object" && data !== null && Array.isArray(data.groups) && !Array.isArray(data.tools);
2737
2908
  };
@@ -2749,7 +2920,7 @@ var renderEmptyToolsLine = (data) => {
2749
2920
  }
2750
2921
  if (data.group !== void 0) {
2751
2922
  const match = data.filter === void 0 ? "" : ` match ${JSON.stringify(data.filter)}`;
2752
- return ` No tools in group ${JSON.stringify(data.group)}${match}. Run \`appduct tools --groups\` to see the session's groups.`;
2923
+ return ` No tools in group ${JSON.stringify(data.group)}${match}. Run \`appduct tools ls --groups\` to see the session's groups.`;
2753
2924
  }
2754
2925
  return data.filter === void 0 ? " No tools registered." : ` No tools match ${JSON.stringify(data.filter)}.`;
2755
2926
  };
@@ -2831,7 +3002,7 @@ var renderToolSummaryTable = (colors, data) => {
2831
3002
  ...grouped ? renderGroupedToolLines(colors, data.tools) : data.tools.flatMap((tool) => renderToolSummaryLines(tool, " ")),
2832
3003
  ...renderTruncationLine(data),
2833
3004
  "",
2834
- "Run `appduct tools <name>` for a tool's full schema."
3005
+ "Run `appduct tools describe <name>` for a tool's full schema."
2835
3006
  ];
2836
3007
  };
2837
3008
  var renderToolDetail = (colors, tool, flags) => {
@@ -2883,16 +3054,13 @@ var renderToolGroups = (colors, data) => {
2883
3054
  colors.green("Groups"),
2884
3055
  ...data.groups.map((entry) => ` ${label(entry).padEnd(width)} ${String(entry.total).padStart(countWidth)}`),
2885
3056
  "",
2886
- `${data.total} tool${data.total === 1 ? "" : "s"} in total. ` + (hasAnyGroup(data.groups) ? "Run `appduct tools --group <name>` to list one group's tools." : "No tool declares a group.")
3057
+ `${data.total} tool${data.total === 1 ? "" : "s"} in total. ` + (hasAnyGroup(data.groups) ? "Run `appduct tools ls --group <name>` to list one group's tools." : "No tool declares a group.")
2887
3058
  ];
2888
3059
  };
2889
- var renderToolsData = (colors, data, flags, full) => {
3060
+ var renderToolsListData = (colors, data, flags, full) => {
2890
3061
  if (isToolGroupsListing(data)) {
2891
3062
  return renderToolGroups(colors, data);
2892
3063
  }
2893
- if (!isToolsListing(data)) {
2894
- return renderToolDetail(colors, data, flags);
2895
- }
2896
3064
  return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
2897
3065
  };
2898
3066
  var renderInvokeData = (colors, data, flags) => {
@@ -3078,15 +3246,17 @@ var renderSuccessData = (colors, command, data, options) => {
3078
3246
  return renderInitData(colors, data, flags);
3079
3247
  case "keygen":
3080
3248
  return renderKeygenData(colors, data, flags);
3081
- case "link":
3249
+ case "sessions link":
3082
3250
  return renderLinkData(colors, data, flags, options.qr);
3083
- case "ls":
3251
+ case "sessions ls":
3084
3252
  return renderLsData(colors, data, options.now ?? /* @__PURE__ */ new Date());
3085
- case "tools":
3086
- return renderToolsData(colors, data, flags, options.full);
3087
- case "invoke":
3253
+ case "tools ls":
3254
+ return renderToolsListData(colors, data, flags, options.full);
3255
+ case "tools describe":
3256
+ return renderToolDetail(colors, data, flags);
3257
+ case "tools call":
3088
3258
  return renderInvokeData(colors, data, flags);
3089
- case "revoke":
3259
+ case "sessions revoke":
3090
3260
  return renderRevokeData(colors, data);
3091
3261
  case "daemon run":
3092
3262
  return renderDaemonRunData(colors, data, flags);
@@ -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,
@@ -243,6 +248,7 @@ var getStateDirPaths = (stateDir) => {
243
248
  socketPath: join(stateDir, "daemon.sock"),
244
249
  pidFilePath: join(stateDir, "daemon.pid"),
245
250
  logFilePath: join(stateDir, "daemon.log"),
251
+ eventsLogPath: join(stateDir, "events.log"),
246
252
  keyPath: join(stateDir, "key.pem"),
247
253
  configPath: join(stateDir, "config.json"),
248
254
  auditDir: join(stateDir, "audit"),
@@ -1160,9 +1166,6 @@ var renderLsData = (colors, data, now) => {
1160
1166
  ...rows.map((row) => ` ${row.map((cell, index) => cell.padEnd(widths[index])).join(" ")}`)
1161
1167
  ];
1162
1168
  };
1163
- var isToolsListing = (data) => {
1164
- return typeof data === "object" && data !== null && Array.isArray(data.tools);
1165
- };
1166
1169
  var isToolGroupsListing = (data) => {
1167
1170
  return typeof data === "object" && data !== null && Array.isArray(data.groups) && !Array.isArray(data.tools);
1168
1171
  };
@@ -1180,7 +1183,7 @@ var renderEmptyToolsLine = (data) => {
1180
1183
  }
1181
1184
  if (data.group !== void 0) {
1182
1185
  const match = data.filter === void 0 ? "" : ` match ${JSON.stringify(data.filter)}`;
1183
- return ` No tools in group ${JSON.stringify(data.group)}${match}. Run \`appduct tools --groups\` to see the session's groups.`;
1186
+ return ` No tools in group ${JSON.stringify(data.group)}${match}. Run \`appduct tools ls --groups\` to see the session's groups.`;
1184
1187
  }
1185
1188
  return data.filter === void 0 ? " No tools registered." : ` No tools match ${JSON.stringify(data.filter)}.`;
1186
1189
  };
@@ -1262,7 +1265,7 @@ var renderToolSummaryTable = (colors, data) => {
1262
1265
  ...grouped ? renderGroupedToolLines(colors, data.tools) : data.tools.flatMap((tool) => renderToolSummaryLines(tool, " ")),
1263
1266
  ...renderTruncationLine(data),
1264
1267
  "",
1265
- "Run `appduct tools <name>` for a tool's full schema."
1268
+ "Run `appduct tools describe <name>` for a tool's full schema."
1266
1269
  ];
1267
1270
  };
1268
1271
  var renderToolDetail = (colors, tool, flags) => {
@@ -1314,16 +1317,13 @@ var renderToolGroups = (colors, data) => {
1314
1317
  colors.green("Groups"),
1315
1318
  ...data.groups.map((entry) => ` ${label(entry).padEnd(width)} ${String(entry.total).padStart(countWidth)}`),
1316
1319
  "",
1317
- `${data.total} tool${data.total === 1 ? "" : "s"} in total. ` + (hasAnyGroup(data.groups) ? "Run `appduct tools --group <name>` to list one group's tools." : "No tool declares a group.")
1320
+ `${data.total} tool${data.total === 1 ? "" : "s"} in total. ` + (hasAnyGroup(data.groups) ? "Run `appduct tools ls --group <name>` to list one group's tools." : "No tool declares a group.")
1318
1321
  ];
1319
1322
  };
1320
- var renderToolsData = (colors, data, flags, full) => {
1323
+ var renderToolsListData = (colors, data, flags, full) => {
1321
1324
  if (isToolGroupsListing(data)) {
1322
1325
  return renderToolGroups(colors, data);
1323
1326
  }
1324
- if (!isToolsListing(data)) {
1325
- return renderToolDetail(colors, data, flags);
1326
- }
1327
1327
  return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
1328
1328
  };
1329
1329
  var renderInvokeData = (colors, data, flags) => {
@@ -1509,15 +1509,17 @@ var renderSuccessData = (colors, command, data, options) => {
1509
1509
  return renderInitData(colors, data, flags);
1510
1510
  case "keygen":
1511
1511
  return renderKeygenData(colors, data, flags);
1512
- case "link":
1512
+ case "sessions link":
1513
1513
  return renderLinkData(colors, data, flags, options.qr);
1514
- case "ls":
1514
+ case "sessions ls":
1515
1515
  return renderLsData(colors, data, options.now ?? /* @__PURE__ */ new Date());
1516
- case "tools":
1517
- return renderToolsData(colors, data, flags, options.full);
1518
- case "invoke":
1516
+ case "tools ls":
1517
+ return renderToolsListData(colors, data, flags, options.full);
1518
+ case "tools describe":
1519
+ return renderToolDetail(colors, data, flags);
1520
+ case "tools call":
1519
1521
  return renderInvokeData(colors, data, flags);
1520
- case "revoke":
1522
+ case "sessions revoke":
1521
1523
  return renderRevokeData(colors, data);
1522
1524
  case "daemon run":
1523
1525
  return renderDaemonRunData(colors, data, flags);
@@ -66,6 +66,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
66
66
  "eventBufferSize",
67
67
  "auditRetentionDays",
68
68
  "daemonLogMaxBytes",
69
+ "eventsLogMaxBytes",
69
70
  "policy",
70
71
  "advertisedIp",
71
72
  "scheme",
@@ -125,6 +126,7 @@ var defaultConfig = (paths) => {
125
126
  eventBufferSize: 256,
126
127
  auditRetentionDays: DEFAULT_AUDIT_RETENTION_DAYS,
127
128
  daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
129
+ eventsLogMaxBytes: 10 * 1024 * 1024,
128
130
  restartDaemonOnVersionMismatch: false,
129
131
  policy: {
130
132
  default: "allow",
@@ -187,6 +189,9 @@ var loadConfig = async (paths, options = {}) => {
187
189
  if (parsed.daemonLogMaxBytes !== void 0) {
188
190
  config.daemonLogMaxBytes = requirePositiveInteger(parsed.daemonLogMaxBytes, "daemonLogMaxBytes");
189
191
  }
192
+ if (parsed.eventsLogMaxBytes !== void 0) {
193
+ config.eventsLogMaxBytes = requirePositiveInteger(parsed.eventsLogMaxBytes, "eventsLogMaxBytes");
194
+ }
190
195
  if (parsed.restartDaemonOnVersionMismatch !== void 0) {
191
196
  config.restartDaemonOnVersionMismatch = requireBoolean(
192
197
  parsed.restartDaemonOnVersionMismatch,
@@ -287,6 +292,7 @@ var getStateDirPaths = (stateDir) => {
287
292
  socketPath: join3(stateDir, "daemon.sock"),
288
293
  pidFilePath: join3(stateDir, "daemon.pid"),
289
294
  logFilePath: join3(stateDir, "daemon.log"),
295
+ eventsLogPath: join3(stateDir, "events.log"),
290
296
  keyPath: join3(stateDir, "key.pem"),
291
297
  configPath: join3(stateDir, "config.json"),
292
298
  auditDir: join3(stateDir, "audit"),
@@ -1189,9 +1195,6 @@ var renderLsData = (colors, data, now) => {
1189
1195
  ...rows.map((row) => ` ${row.map((cell, index) => cell.padEnd(widths[index])).join(" ")}`)
1190
1196
  ];
1191
1197
  };
1192
- var isToolsListing = (data) => {
1193
- return typeof data === "object" && data !== null && Array.isArray(data.tools);
1194
- };
1195
1198
  var isToolGroupsListing = (data) => {
1196
1199
  return typeof data === "object" && data !== null && Array.isArray(data.groups) && !Array.isArray(data.tools);
1197
1200
  };
@@ -1209,7 +1212,7 @@ var renderEmptyToolsLine = (data) => {
1209
1212
  }
1210
1213
  if (data.group !== void 0) {
1211
1214
  const match = data.filter === void 0 ? "" : ` match ${JSON.stringify(data.filter)}`;
1212
- return ` No tools in group ${JSON.stringify(data.group)}${match}. Run \`appduct tools --groups\` to see the session's groups.`;
1215
+ return ` No tools in group ${JSON.stringify(data.group)}${match}. Run \`appduct tools ls --groups\` to see the session's groups.`;
1213
1216
  }
1214
1217
  return data.filter === void 0 ? " No tools registered." : ` No tools match ${JSON.stringify(data.filter)}.`;
1215
1218
  };
@@ -1291,7 +1294,7 @@ var renderToolSummaryTable = (colors, data) => {
1291
1294
  ...grouped ? renderGroupedToolLines(colors, data.tools) : data.tools.flatMap((tool) => renderToolSummaryLines(tool, " ")),
1292
1295
  ...renderTruncationLine(data),
1293
1296
  "",
1294
- "Run `appduct tools <name>` for a tool's full schema."
1297
+ "Run `appduct tools describe <name>` for a tool's full schema."
1295
1298
  ];
1296
1299
  };
1297
1300
  var renderToolDetail = (colors, tool, flags) => {
@@ -1343,16 +1346,13 @@ var renderToolGroups = (colors, data) => {
1343
1346
  colors.green("Groups"),
1344
1347
  ...data.groups.map((entry) => ` ${label(entry).padEnd(width)} ${String(entry.total).padStart(countWidth)}`),
1345
1348
  "",
1346
- `${data.total} tool${data.total === 1 ? "" : "s"} in total. ` + (hasAnyGroup(data.groups) ? "Run `appduct tools --group <name>` to list one group's tools." : "No tool declares a group.")
1349
+ `${data.total} tool${data.total === 1 ? "" : "s"} in total. ` + (hasAnyGroup(data.groups) ? "Run `appduct tools ls --group <name>` to list one group's tools." : "No tool declares a group.")
1347
1350
  ];
1348
1351
  };
1349
- var renderToolsData = (colors, data, flags, full) => {
1352
+ var renderToolsListData = (colors, data, flags, full) => {
1350
1353
  if (isToolGroupsListing(data)) {
1351
1354
  return renderToolGroups(colors, data);
1352
1355
  }
1353
- if (!isToolsListing(data)) {
1354
- return renderToolDetail(colors, data, flags);
1355
- }
1356
1356
  return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
1357
1357
  };
1358
1358
  var renderInvokeData = (colors, data, flags) => {
@@ -1538,15 +1538,17 @@ var renderSuccessData = (colors, command, data, options) => {
1538
1538
  return renderInitData(colors, data, flags);
1539
1539
  case "keygen":
1540
1540
  return renderKeygenData(colors, data, flags);
1541
- case "link":
1541
+ case "sessions link":
1542
1542
  return renderLinkData(colors, data, flags, options.qr);
1543
- case "ls":
1543
+ case "sessions ls":
1544
1544
  return renderLsData(colors, data, options.now ?? /* @__PURE__ */ new Date());
1545
- case "tools":
1546
- return renderToolsData(colors, data, flags, options.full);
1547
- case "invoke":
1545
+ case "tools ls":
1546
+ return renderToolsListData(colors, data, flags, options.full);
1547
+ case "tools describe":
1548
+ return renderToolDetail(colors, data, flags);
1549
+ case "tools call":
1548
1550
  return renderInvokeData(colors, data, flags);
1549
- case "revoke":
1551
+ case "sessions revoke":
1550
1552
  return renderRevokeData(colors, data);
1551
1553
  case "daemon run":
1552
1554
  return renderDaemonRunData(colors, data, flags);