khala-cli 0.4.4 → 0.4.5

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 (27) hide show
  1. package/dist/{chunk-LN3G2LJV.mjs → chunk-6LC66VHK.mjs} +5 -5
  2. package/dist/{chunk-ENPBK5W2.mjs → chunk-7G2G4E4U.mjs} +84 -35
  3. package/dist/{chunk-LQ6KNUNN.mjs → chunk-EW22K3JO.mjs} +393 -160
  4. package/dist/{chunk-GE3RXO2X.mjs → chunk-EXHCVFVH.mjs} +2 -2
  5. package/dist/{chunk-MU4FUENP.mjs → chunk-H46OG22B.mjs} +20 -5
  6. package/dist/{chunk-LHAMBQK7.mjs → chunk-HB3UGIZG.mjs} +3 -3
  7. package/dist/{chunk-DC3OX6F4.mjs → chunk-IR3VN2ID.mjs} +1 -1
  8. package/dist/{chunk-XEYKJMZL.mjs → chunk-KCJFSVDN.mjs} +54 -3
  9. package/dist/{chunk-6OADQWV7.mjs → chunk-MBLP7LQJ.mjs} +93 -32
  10. package/dist/{chunk-ZRFXZK72.mjs → chunk-OLZ62X7S.mjs} +1 -1
  11. package/dist/{chunk-ZKDDKUUA.mjs → chunk-PXKGKVGW.mjs} +1 -1
  12. package/dist/{chunk-AK74S7EN.mjs → chunk-RSDOTTZQ.mjs} +2 -2
  13. package/dist/{chunk-7CVU5OZ7.mjs → chunk-SQXDATEC.mjs} +1 -1
  14. package/dist/chunk-T7YRVE43.mjs +238 -0
  15. package/dist/{chunk-OS2JLDL7.mjs → chunk-TZKEQIHP.mjs} +4 -2
  16. package/dist/{chunk-PJ3E3IVH.mjs → chunk-V4Y26O4R.mjs} +7 -7
  17. package/dist/{chunk-FA6Z65DW.mjs → chunk-V7M27QLT.mjs} +6 -4
  18. package/dist/{chunk-MZS7XFMC.mjs → chunk-Y2DRTRBJ.mjs} +2 -2
  19. package/dist/{chunk-FUU775QM.mjs → chunk-Y4WVILEJ.mjs} +14 -1
  20. package/dist/khala.mjs +10 -10
  21. package/dist/web/assets/local-C1vz22ps.js +9 -0
  22. package/dist/web/assets/local-DdjwMERJ.css +1 -0
  23. package/dist/web/index.html +2 -2
  24. package/package.json +1 -1
  25. package/dist/chunk-N5VYKQMR.mjs +0 -108
  26. package/dist/web/assets/local-Bk8hMIT-.js +0 -9
  27. package/dist/web/assets/local-vdvavyq0.css +0 -1
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
2
2
  import {
3
3
  ensureHelper
4
- } from "./chunk-LHAMBQK7.mjs";
4
+ } from "./chunk-HB3UGIZG.mjs";
5
5
  import {
6
6
  KhalaClientError,
7
7
  LOCAL_LONG_POLL_MAX_S,
@@ -16,19 +16,19 @@ import {
16
16
  decodeLocalNameContent,
17
17
  decodeLocalSendResult,
18
18
  localRoomPath
19
- } from "./chunk-OS2JLDL7.mjs";
20
- import "./chunk-ZRFXZK72.mjs";
19
+ } from "./chunk-TZKEQIHP.mjs";
20
+ import "./chunk-OLZ62X7S.mjs";
21
21
  import "./chunk-5GLTKWJ5.mjs";
22
22
  import {
23
23
  CHANNEL_EVENT_TYPE,
24
24
  memberRenameContent
25
25
  } from "./chunk-P5CP32X2.mjs";
26
- import "./chunk-XEYKJMZL.mjs";
26
+ import "./chunk-KCJFSVDN.mjs";
27
27
  import {
28
28
  LISTENING_MODE_COMMAND_TYPE
29
29
  } from "./chunk-AUJEUKVM.mjs";
30
30
  import "./chunk-SN3STAO2.mjs";
31
- import "./chunk-ZKDDKUUA.mjs";
31
+ import "./chunk-PXKGKVGW.mjs";
32
32
 
33
33
  // src/local/session.ts
34
34
  import { randomUUID } from "node:crypto";
@@ -1,19 +1,21 @@
1
1
  import { createRequire as __khalaCreateRequire } from 'node:module'; const require = __khalaCreateRequire(import.meta.url);
2
2
  import {
3
+ listChannels,
3
4
  readCursor,
4
5
  readEntries,
5
6
  readListeningMode
6
- } from "./chunk-N5VYKQMR.mjs";
7
+ } from "./chunk-T7YRVE43.mjs";
7
8
  import {
8
9
  CURSOR_WORKSPACE_ENV,
9
10
  cursorSessionId
10
11
  } from "./chunk-VUE2DDYY.mjs";
11
12
  import {
13
+ SESSION_ID_PATTERN,
12
14
  readJson,
13
15
  readStatus,
14
16
  sessionFiles,
15
17
  writeJsonAtomic
16
- } from "./chunk-XEYKJMZL.mjs";
18
+ } from "./chunk-KCJFSVDN.mjs";
17
19
  import {
18
20
  HARNESSES
19
21
  } from "./chunk-SN3STAO2.mjs";
@@ -67,6 +69,7 @@ function channelLabel(name) {
67
69
  async function watchSession(files, io) {
68
70
  const nonce = randomUUID();
69
71
  let watcher;
72
+ let timer;
70
73
  let running;
71
74
  let dirty = false;
72
75
  let done = false;
@@ -79,47 +82,79 @@ async function watchSession(files, io) {
79
82
  done = true;
80
83
  code = exit;
81
84
  watcher?.close();
85
+ clearInterval(timer);
82
86
  finish();
83
87
  };
84
88
  const abort = () => stop();
85
89
  try {
86
- const session = await readJson(files.session);
87
90
  const status = await readStatus(files);
88
- if (!session || typeof session.userId !== "string" || typeof session.roomId !== "string" || !["connected", "send_failed"].includes(status?.state ?? "")) return 0;
89
- const initialDelivered = (await readCursor(files)).deliveredCount;
91
+ const initialSession = await readJson(files.session);
92
+ if (!["connected", "send_failed"].includes(status?.state ?? "") || !initialSession && !(await listChannels(files)).length) return 0;
93
+ const initialDelivered = /* @__PURE__ */ new Map();
94
+ const seedBaseline = async (target, session) => {
95
+ initialDelivered.set(target.dir, {
96
+ identity: JSON.stringify([session.roomId, session.userId]),
97
+ cursor: await readCursor(target)
98
+ });
99
+ };
100
+ if (initialSession) await seedBaseline(files, initialSession);
101
+ for (const channel of await listChannels(files)) {
102
+ const session = await readJson(channel.files.session);
103
+ if (session) await seedBaseline(channel.files, session);
104
+ }
90
105
  const evaluate = async () => {
91
- const owner = await readJson(path.join(files.dir, MARKER));
92
- const current = await readJson(files.session);
93
- const status2 = await readStatus(files);
94
- if (owner?.nonce !== nonce || current?.userId !== session.userId || current?.roomId !== session.roomId || !["connected", "send_failed"].includes(status2?.state ?? "")) {
95
- stop();
96
- return;
97
- }
98
- const mode = await readListeningMode(files);
99
- const entries = await readEntries(files);
100
- const observed = await readJson(path.join(files.dir, OBSERVATION));
101
- const position = observed?.userId === session.userId && observed?.roomId === session.roomId && Number.isSafeInteger(observed.count) && observed.count >= 0 && observed.count <= entries.length && (observed.count === 0 || entries[observed.count - 1]?.eventId === observed.lastEventId) ? observed.count : 0;
102
- const fresh = entries.slice(Math.max(initialDelivered, position));
103
- const latestMode = await readListeningMode(files);
104
- const latestSession = await readJson(files.session);
105
- const latestStatus = await readStatus(files);
106
- const latestOwner = await readJson(path.join(files.dir, MARKER));
107
- if (latestOwner?.nonce !== nonce || latestSession?.userId !== session.userId || latestSession?.roomId !== session.roomId || !["connected", "send_failed"].includes(latestStatus?.state ?? "")) {
106
+ const ownsSession = async () => {
107
+ const owner = await readJson(path.join(files.dir, MARKER));
108
+ const status2 = await readJson(files.status);
109
+ return owner?.nonce === nonce && !["disconnected", "closed"].includes(status2?.state ?? "");
110
+ };
111
+ if (!await ownsSession()) {
108
112
  stop();
109
113
  return;
110
114
  }
111
- for (const entry of fresh) {
112
- if (done || mode === "async" || latestMode === "async" || entry.kind !== "message" || entry.sender === session.userId || entry.roomId !== session.roomId) continue;
113
- const mention = mentions(entry, latestStatus?.displayName);
114
- io.write(`khala: 1 new message in #${channelLabel(latestStatus?.channelName ?? "channel")} (${mention ? 1 : 0} mentions you)
115
+ for (const channel of await listChannels(files)) {
116
+ const target = channel.files;
117
+ const session = await readJson(target.session);
118
+ const status2 = await readStatus(target);
119
+ if (!session || session.roomId !== channel.roomId || typeof session.userId !== "string" || !["connected", "send_failed"].includes(status2?.state ?? "")) continue;
120
+ const identity = JSON.stringify([session.roomId, session.userId]);
121
+ if (initialDelivered.get(target.dir)?.identity !== identity) {
122
+ await seedBaseline(target, session);
123
+ }
124
+ const mode = await readListeningMode(target);
125
+ const entries = await readEntries(target);
126
+ const observed = await readJson(path.join(target.dir, OBSERVATION));
127
+ const position = observed?.userId === session.userId && observed?.roomId === session.roomId && Number.isSafeInteger(observed.count) && observed.count >= 0 && observed.count <= entries.length && (observed.count === 0 || entries[observed.count - 1]?.eventId === observed.lastEventId) ? observed.count : 0;
128
+ const baseline = initialDelivered.get(target.dir).cursor;
129
+ const initialCount = baseline.deliveredCount === 0 || entries[baseline.deliveredCount - 1]?.eventId === baseline.lastDeliveredEventId ? baseline.deliveredCount : 0;
130
+ const fresh = entries.slice(Math.max(initialCount, position));
131
+ const latestMode = await readListeningMode(target);
132
+ const latestSession = await readJson(target.session);
133
+ const latestStatus = await readStatus(target);
134
+ if (!await ownsSession()) {
135
+ stop();
136
+ return;
137
+ }
138
+ if (latestSession?.userId !== session.userId || latestSession?.roomId !== session.roomId || !["connected", "send_failed"].includes(latestStatus?.state ?? "")) continue;
139
+ const messages = fresh.filter((entry) => entry.kind === "message" && entry.sender !== session.userId && entry.roomId === session.roomId);
140
+ if (!done && mode !== "async" && latestMode !== "async" && messages.length) {
141
+ const count = messages.filter((entry) => mentions(entry, latestStatus?.displayName)).length;
142
+ io.write(`khala: ${messages.length} new message${messages.length === 1 ? "" : "s"} in #${channelLabel(latestStatus?.channelName ?? channel.channelName ?? "channel")} (${count} mentions you)
115
143
  `);
144
+ }
145
+ if (position !== entries.length && !done && await ownsSession() && JSON.stringify(await readJson(target.session)) === JSON.stringify(latestSession)) {
146
+ try {
147
+ await writeJsonAtomic(path.join(target.dir, OBSERVATION), {
148
+ userId: session.userId,
149
+ roomId: session.roomId,
150
+ count: entries.length,
151
+ lastEventId: entries.at(-1)?.eventId ?? null
152
+ });
153
+ } catch (error) {
154
+ if (await readJson(target.session)) throw error;
155
+ }
156
+ }
116
157
  }
117
- if (position !== entries.length && !done && (await readJson(path.join(files.dir, MARKER)))?.nonce === nonce) await writeJsonAtomic(path.join(files.dir, OBSERVATION), {
118
- userId: session.userId,
119
- roomId: session.roomId,
120
- count: entries.length,
121
- lastEventId: entries.at(-1)?.eventId ?? null
122
- });
123
158
  };
124
159
  const notify = () => {
125
160
  if (done) return;
@@ -141,6 +176,7 @@ async function watchSession(files, io) {
141
176
  if (filename === null || [MARKER, "inbox.jsonl", "cursor.json", "mode.json", "session.json", "status.json"].includes(String(filename))) notify();
142
177
  });
143
178
  watcher.on("error", () => stop(1));
179
+ timer = setInterval(notify, 100);
144
180
  io.signal.addEventListener("abort", abort, { once: true });
145
181
  if (io.signal.aborted) stop();
146
182
  else notify();
@@ -148,6 +184,7 @@ async function watchSession(files, io) {
148
184
  await running;
149
185
  return code;
150
186
  } finally {
187
+ clearInterval(timer);
151
188
  watcher?.close();
152
189
  io.signal.removeEventListener("abort", abort);
153
190
  await fs.unlink(leaseFile(files, nonce)).catch((error) => {
@@ -155,13 +192,24 @@ async function watchSession(files, io) {
155
192
  });
156
193
  }
157
194
  }
195
+ var WATCH_USAGE = "usage: khala watch [--harness claude|codex|cursor --session <id>]";
158
196
  async function run(argv) {
159
- if (argv.length !== 0 && !(argv.length === 2 && argv[0] === "--session") && !(argv.length === 4 && argv[0] === "--harness" && argv[2] === "--session")) throw new Error("invalid_arguments");
197
+ if (argv.length === 1 && ["--help", "-h"].includes(argv[0])) {
198
+ console.log(WATCH_USAGE);
199
+ return 0;
200
+ }
201
+ const invalid = (reason) => {
202
+ console.error(`khala: ${reason}
203
+ ${WATCH_USAGE}`);
204
+ return 1;
205
+ };
206
+ if (argv.length !== 0 && !(argv.length === 2 && argv[0] === "--session") && !(argv.length === 4 && argv[0] === "--harness" && argv[2] === "--session")) return invalid("invalid_arguments");
160
207
  const harness = resolveHarness(argv, process.env);
161
- if (harness === "invalid") throw new Error("invalid_harness");
208
+ if (harness === "invalid") return invalid("invalid_harness");
162
209
  const index = argv.indexOf("--session");
163
210
  const id = index === -1 ? resolveSessionId(harness, void 0, process.env) : argv[index + 1];
164
- if (!id) throw new Error("session_unknown");
211
+ if (!id) return invalid("session_unknown");
212
+ if (!SESSION_ID_PATTERN.test(id)) return invalid("invalid_session_id");
165
213
  const files = sessionFiles(harness, id, process.env);
166
214
  const controller = new AbortController();
167
215
  const stop = () => controller.abort();
@@ -183,5 +231,6 @@ export {
183
231
  monitorArmed,
184
232
  mentions,
185
233
  watchSession,
234
+ WATCH_USAGE,
186
235
  run
187
236
  };