@lesscap/baton-cli 0.2.13 → 0.3.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 (3) hide show
  1. package/LICENSE +21 -0
  2. package/dist/baton.mjs +1322 -910
  3. package/package.json +10 -11
package/dist/baton.mjs CHANGED
@@ -467,235 +467,590 @@ function _getBuiltinFlags(long, short, userNames, userAliases) {
467
467
  return [`--${long}`, `-${short}`];
468
468
  }
469
469
 
470
- // src/commands/init.ts
471
- import { existsSync as existsSync2 } from "node:fs";
472
- import * as readline from "node:readline/promises";
473
-
474
- // src/client/request.ts
475
- var authHeaders = {};
476
- var loginGate = null;
477
- var setAuthHeaders = (h) => {
478
- authHeaders = h;
470
+ // ../../node_modules/.pnpm/eventsource-parser@3.1.0/node_modules/eventsource-parser/dist/index.js
471
+ var ParseError = class extends Error {
472
+ constructor(message, options) {
473
+ super(message), this.name = "ParseError", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line;
474
+ }
479
475
  };
480
- var setLoginGate = (p) => {
481
- loginGate = p;
476
+ var LF = 10;
477
+ var CR = 13;
478
+ var SPACE = 32;
479
+ function noop(_arg) {
480
+ }
481
+ function createParser(config) {
482
+ if (typeof config == "function")
483
+ throw new TypeError(
484
+ "`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?"
485
+ );
486
+ const { onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize } = config, pendingFragments = [];
487
+ let pendingFragmentsLength = 0, isFirstChunk = true, id, data = "", dataLines = 0, eventType, terminated = false;
488
+ function feed(chunk) {
489
+ if (terminated)
490
+ throw new Error(
491
+ "Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing."
492
+ );
493
+ if (isFirstChunk && (isFirstChunk = false, chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191 && (chunk = chunk.slice(3))), pendingFragments.length === 0) {
494
+ const trailing2 = processLines(chunk);
495
+ trailing2 !== "" && (pendingFragments.push(trailing2), pendingFragmentsLength = trailing2.length), checkBufferSize();
496
+ return;
497
+ }
498
+ if (chunk.indexOf(`
499
+ `) === -1 && chunk.indexOf("\r") === -1) {
500
+ pendingFragments.push(chunk), pendingFragmentsLength += chunk.length, checkBufferSize();
501
+ return;
502
+ }
503
+ pendingFragments.push(chunk);
504
+ const input = pendingFragments.join("");
505
+ pendingFragments.length = 0, pendingFragmentsLength = 0;
506
+ const trailing = processLines(input);
507
+ trailing !== "" && (pendingFragments.push(trailing), pendingFragmentsLength = trailing.length), checkBufferSize();
508
+ }
509
+ function checkBufferSize() {
510
+ maxBufferSize !== void 0 && (pendingFragmentsLength + data.length <= maxBufferSize || (terminated = true, pendingFragments.length = 0, pendingFragmentsLength = 0, id = void 0, data = "", dataLines = 0, eventType = void 0, onError(
511
+ new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {
512
+ type: "max-buffer-size-exceeded"
513
+ })
514
+ )));
515
+ }
516
+ function processLines(chunk) {
517
+ let searchIndex = 0;
518
+ if (chunk.indexOf("\r") === -1) {
519
+ let lfIndex = chunk.indexOf(`
520
+ `, searchIndex);
521
+ for (; lfIndex !== -1; ) {
522
+ if (searchIndex === lfIndex) {
523
+ dataLines > 0 && onEvent({ id, event: eventType, data }), id = void 0, data = "", dataLines = 0, eventType = void 0, searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
524
+ `, searchIndex);
525
+ continue;
526
+ }
527
+ const firstCharCode = chunk.charCodeAt(searchIndex);
528
+ if (isDataPrefix(chunk, searchIndex, firstCharCode)) {
529
+ const valueStart = chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5, value = chunk.slice(valueStart, lfIndex);
530
+ if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {
531
+ onEvent({ id, event: eventType, data: value }), id = void 0, data = "", eventType = void 0, searchIndex = lfIndex + 2, lfIndex = chunk.indexOf(`
532
+ `, searchIndex);
533
+ continue;
534
+ }
535
+ data = dataLines === 0 ? value : `${data}
536
+ ${value}`, dataLines++;
537
+ } else isEventPrefix(chunk, searchIndex, firstCharCode) ? eventType = chunk.slice(
538
+ chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6,
539
+ lfIndex
540
+ ) || void 0 : parseLine(chunk, searchIndex, lfIndex);
541
+ searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
542
+ `, searchIndex);
543
+ }
544
+ return chunk.slice(searchIndex);
545
+ }
546
+ for (; searchIndex < chunk.length; ) {
547
+ const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(`
548
+ `, searchIndex);
549
+ let lineEnd = -1;
550
+ if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = crIndex < lfIndex ? crIndex : lfIndex : crIndex !== -1 ? crIndex === chunk.length - 1 ? lineEnd = -1 : lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1)
551
+ break;
552
+ parseLine(chunk, searchIndex, lineEnd), searchIndex = lineEnd + 1, chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF && searchIndex++;
553
+ }
554
+ return chunk.slice(searchIndex);
555
+ }
556
+ function parseLine(chunk, start, end) {
557
+ if (start === end) {
558
+ dispatchEvent();
559
+ return;
560
+ }
561
+ const firstCharCode = chunk.charCodeAt(start);
562
+ if (isDataPrefix(chunk, start, firstCharCode)) {
563
+ const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5, value2 = chunk.slice(valueStart, end);
564
+ data = dataLines === 0 ? value2 : `${data}
565
+ ${value2}`, dataLines++;
566
+ return;
567
+ }
568
+ if (isEventPrefix(chunk, start, firstCharCode)) {
569
+ eventType = chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || void 0;
570
+ return;
571
+ }
572
+ if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) {
573
+ const value2 = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end);
574
+ id = value2.includes("\0") ? void 0 : value2;
575
+ return;
576
+ }
577
+ if (firstCharCode === 58) {
578
+ if (onComment) {
579
+ const line2 = chunk.slice(start, end);
580
+ onComment(line2.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1));
581
+ }
582
+ return;
583
+ }
584
+ const line = chunk.slice(start, end), fieldSeparatorIndex = line.indexOf(":");
585
+ if (fieldSeparatorIndex === -1) {
586
+ processField(line, "", line);
587
+ return;
588
+ }
589
+ const field = line.slice(0, fieldSeparatorIndex), offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1, value = line.slice(fieldSeparatorIndex + offset);
590
+ processField(field, value, line);
591
+ }
592
+ function processField(field, value, line) {
593
+ switch (field) {
594
+ case "event":
595
+ eventType = value || void 0;
596
+ break;
597
+ case "data":
598
+ data = dataLines === 0 ? value : `${data}
599
+ ${value}`, dataLines++;
600
+ break;
601
+ case "id":
602
+ id = value.includes("\0") ? void 0 : value;
603
+ break;
604
+ case "retry":
605
+ /^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(
606
+ new ParseError(`Invalid \`retry\` value: "${value}"`, {
607
+ type: "invalid-retry",
608
+ value,
609
+ line
610
+ })
611
+ );
612
+ break;
613
+ default:
614
+ onError(
615
+ new ParseError(
616
+ `Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`,
617
+ { type: "unknown-field", field, value, line }
618
+ )
619
+ );
620
+ break;
621
+ }
622
+ }
623
+ function dispatchEvent() {
624
+ dataLines > 0 && onEvent({
625
+ id,
626
+ event: eventType,
627
+ data
628
+ }), id = void 0, data = "", dataLines = 0, eventType = void 0;
629
+ }
630
+ function reset(options = {}) {
631
+ if (options.consume && pendingFragments.length > 0) {
632
+ const incompleteLine = pendingFragments.join("");
633
+ parseLine(incompleteLine, 0, incompleteLine.length);
634
+ }
635
+ isFirstChunk = true, id = void 0, data = "", dataLines = 0, eventType = void 0, pendingFragments.length = 0, pendingFragmentsLength = 0, terminated = false;
636
+ }
637
+ return { feed, reset };
638
+ }
639
+ function isDataPrefix(chunk, i, firstCharCode) {
640
+ return firstCharCode === 100 && chunk.charCodeAt(i + 1) === 97 && chunk.charCodeAt(i + 2) === 116 && chunk.charCodeAt(i + 3) === 97 && chunk.charCodeAt(i + 4) === 58;
641
+ }
642
+ function isEventPrefix(chunk, i, firstCharCode) {
643
+ return firstCharCode === 101 && chunk.charCodeAt(i + 1) === 118 && chunk.charCodeAt(i + 2) === 101 && chunk.charCodeAt(i + 3) === 110 && chunk.charCodeAt(i + 4) === 116 && chunk.charCodeAt(i + 5) === 58;
644
+ }
645
+
646
+ // ../../node_modules/.pnpm/eventsource@4.1.0/node_modules/eventsource/dist/index.js
647
+ var ErrorEvent = class extends Event {
648
+ /**
649
+ * Constructs a new `ErrorEvent` instance. This is typically not called directly,
650
+ * but rather emitted by the `EventSource` object when an error occurs.
651
+ *
652
+ * @param type - The type of the event (should be "error")
653
+ * @param errorEventInitDict - Optional properties to include in the error event
654
+ */
655
+ constructor(type, errorEventInitDict) {
656
+ var _a, _b;
657
+ super(type), this.code = (_a = errorEventInitDict == null ? void 0 : errorEventInitDict.code) != null ? _a : void 0, this.message = (_b = errorEventInitDict == null ? void 0 : errorEventInitDict.message) != null ? _b : void 0;
658
+ }
659
+ /**
660
+ * Node.js "hides" the `message` and `code` properties of the `ErrorEvent` instance,
661
+ * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,
662
+ * we explicitly include the properties in the `inspect` method.
663
+ *
664
+ * This is automatically called by Node.js when you `console.log` an instance of this class.
665
+ *
666
+ * @param _depth - The current depth
667
+ * @param options - The options passed to `util.inspect`
668
+ * @param inspect - The inspect function to use (prevents having to import it from `util`)
669
+ * @returns A string representation of the error
670
+ */
671
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")](_depth, options, inspect) {
672
+ return inspect(inspectableError(this), options);
673
+ }
674
+ /**
675
+ * Deno "hides" the `message` and `code` properties of the `ErrorEvent` instance,
676
+ * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,
677
+ * we explicitly include the properties in the `inspect` method.
678
+ *
679
+ * This is automatically called by Deno when you `console.log` an instance of this class.
680
+ *
681
+ * @param inspect - The inspect function to use (prevents having to import it from `util`)
682
+ * @param options - The options passed to `Deno.inspect`
683
+ * @returns A string representation of the error
684
+ */
685
+ [/* @__PURE__ */ Symbol.for("Deno.customInspect")](inspect, options) {
686
+ return inspect(inspectableError(this), options);
687
+ }
482
688
  };
483
- var request = async (url, init2) => {
484
- if (loginGate) await loginGate;
485
- const baseHeaders = init2.body !== void 0 ? { "content-type": "application/json" } : {};
486
- const res = await fetch(url, {
487
- method: init2.method,
488
- headers: { ...baseHeaders, ...authHeaders, ...init2.headers ?? {} },
489
- ...init2.body !== void 0 ? { body: JSON.stringify(init2.body) } : {}
490
- });
491
- if (!res.ok) throw new Error(`${init2.method} ${url} \u2192 ${res.status}: ${await res.text()}`);
492
- if (res.status === 204) return void 0;
493
- return await res.json();
689
+ function syntaxError(message) {
690
+ const DomException = globalThis.DOMException;
691
+ return typeof DomException == "function" ? new DomException(message, "SyntaxError") : new SyntaxError(message);
692
+ }
693
+ function flattenError(err) {
694
+ return err instanceof Error ? "errors" in err && Array.isArray(err.errors) ? err.errors.map(flattenError).join(", ") : "cause" in err && err.cause instanceof Error ? `${err}: ${flattenError(err.cause)}` : err.message : `${err}`;
695
+ }
696
+ function inspectableError(err) {
697
+ return {
698
+ type: err.type,
699
+ message: err.message,
700
+ code: err.code,
701
+ defaultPrevented: err.defaultPrevented,
702
+ cancelable: err.cancelable,
703
+ timeStamp: err.timeStamp
704
+ };
705
+ }
706
+ var __typeError = (msg) => {
707
+ throw TypeError(msg);
494
708
  };
495
-
496
- // src/client/auth.ts
497
- var primed = false;
498
- var primeLogin = (baseUrl) => {
499
- if (primed) return;
500
- primed = true;
501
- const token = process.env.BATON_TOKEN;
502
- if (token) {
503
- setAuthHeaders({ authorization: `Bearer ${token}` });
504
- return;
709
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
710
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
711
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
712
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
713
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
714
+ var _readyState;
715
+ var _url;
716
+ var _redirectUrl;
717
+ var _withCredentials;
718
+ var _fetch;
719
+ var _reconnectInterval;
720
+ var _reconnectTimer;
721
+ var _lastEventId;
722
+ var _controller;
723
+ var _parser;
724
+ var _onError;
725
+ var _onMessage;
726
+ var _onOpen;
727
+ var _EventSource_instances;
728
+ var connect_fn;
729
+ var _onFetchResponse;
730
+ var _onFetchError;
731
+ var getRequestOptions_fn;
732
+ var _onEvent;
733
+ var _onRetryChange;
734
+ var failConnection_fn;
735
+ var scheduleReconnect_fn;
736
+ var _reconnect;
737
+ var EventSource = class extends EventTarget {
738
+ constructor(url, eventSourceInitDict) {
739
+ var _a, _b;
740
+ super(), __privateAdd(this, _EventSource_instances), this.CONNECTING = 0, this.OPEN = 1, this.CLOSED = 2, __privateAdd(this, _readyState), __privateAdd(this, _url), __privateAdd(this, _redirectUrl), __privateAdd(this, _withCredentials), __privateAdd(this, _fetch), __privateAdd(this, _reconnectInterval), __privateAdd(this, _reconnectTimer), __privateAdd(this, _lastEventId, null), __privateAdd(this, _controller), __privateAdd(this, _parser), __privateAdd(this, _onError, null), __privateAdd(this, _onMessage, null), __privateAdd(this, _onOpen, null), __privateAdd(this, _onFetchResponse, async (response) => {
741
+ var _a2;
742
+ __privateGet(this, _parser).reset();
743
+ const { body, redirected, status, headers } = response;
744
+ if (status === 204) {
745
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Server sent HTTP 204, not reconnecting", 204), this.close();
746
+ return;
747
+ }
748
+ if (redirected ? __privateSet(this, _redirectUrl, new URL(response.url)) : __privateSet(this, _redirectUrl, void 0), status !== 200) {
749
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, `Non-200 status code (${status})`, status);
750
+ return;
751
+ }
752
+ if (!(headers.get("content-type") || "").startsWith("text/event-stream")) {
753
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, 'Invalid content type, expected "text/event-stream"', status);
754
+ return;
755
+ }
756
+ if (__privateGet(this, _readyState) === this.CLOSED)
757
+ return;
758
+ __privateSet(this, _readyState, this.OPEN);
759
+ const openEvent = new Event("open");
760
+ if ((_a2 = __privateGet(this, _onOpen)) == null || _a2.call(this, openEvent), this.dispatchEvent(openEvent), typeof body != "object" || !body || !("getReader" in body)) {
761
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Invalid response body, expected a web ReadableStream", status), this.close();
762
+ return;
763
+ }
764
+ const decoder = new TextDecoder(), reader = body.getReader();
765
+ let open = true;
766
+ do {
767
+ const { done, value } = await reader.read();
768
+ value && __privateGet(this, _parser).feed(decoder.decode(value, { stream: !done })), done && (open = false, __privateGet(this, _parser).reset(), __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this));
769
+ } while (open);
770
+ }), __privateAdd(this, _onFetchError, (err) => {
771
+ __privateSet(this, _controller, void 0), !(err.name === "AbortError" || err.type === "aborted") && __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this, flattenError(err));
772
+ }), __privateAdd(this, _onEvent, (event) => {
773
+ typeof event.id == "string" && __privateSet(this, _lastEventId, event.id);
774
+ const messageEvent = new MessageEvent(event.event || "message", {
775
+ data: event.data,
776
+ origin: __privateGet(this, _redirectUrl) ? __privateGet(this, _redirectUrl).origin : __privateGet(this, _url).origin,
777
+ lastEventId: event.id || ""
778
+ });
779
+ __privateGet(this, _onMessage) && (!event.event || event.event === "message") && __privateGet(this, _onMessage).call(this, messageEvent), this.dispatchEvent(messageEvent);
780
+ }), __privateAdd(this, _onRetryChange, (value) => {
781
+ __privateSet(this, _reconnectInterval, value);
782
+ }), __privateAdd(this, _reconnect, () => {
783
+ __privateSet(this, _reconnectTimer, void 0), __privateGet(this, _readyState) === this.CONNECTING && __privateMethod(this, _EventSource_instances, connect_fn).call(this);
784
+ });
785
+ try {
786
+ if (url instanceof URL)
787
+ __privateSet(this, _url, url);
788
+ else if (typeof url == "string")
789
+ __privateSet(this, _url, new URL(url, getBaseURL()));
790
+ else
791
+ throw new Error("Invalid URL");
792
+ } catch {
793
+ throw syntaxError("An invalid or illegal string was specified");
794
+ }
795
+ __privateSet(this, _parser, createParser({
796
+ onEvent: __privateGet(this, _onEvent),
797
+ onRetry: __privateGet(this, _onRetryChange)
798
+ })), __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _reconnectInterval, 3e3), __privateSet(this, _fetch, (_a = eventSourceInitDict == null ? void 0 : eventSourceInitDict.fetch) != null ? _a : globalThis.fetch), __privateSet(this, _withCredentials, (_b = eventSourceInitDict == null ? void 0 : eventSourceInitDict.withCredentials) != null ? _b : false), __privateMethod(this, _EventSource_instances, connect_fn).call(this);
799
+ }
800
+ /**
801
+ * Returns the state of this EventSource object's connection. It can have the values described below.
802
+ *
803
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState)
804
+ *
805
+ * Note: typed as `number` instead of `0 | 1 | 2` for compatibility with the `EventSource` interface,
806
+ * defined in the TypeScript `dom` library.
807
+ *
808
+ * @public
809
+ */
810
+ get readyState() {
811
+ return __privateGet(this, _readyState);
812
+ }
813
+ /**
814
+ * Returns the URL providing the event stream.
815
+ *
816
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url)
817
+ *
818
+ * @public
819
+ */
820
+ get url() {
821
+ return __privateGet(this, _url).href;
822
+ }
823
+ /**
824
+ * Returns true if the credentials mode for connection requests to the URL providing the event stream is set to "include", and false otherwise.
825
+ *
826
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials)
827
+ */
828
+ get withCredentials() {
829
+ return __privateGet(this, _withCredentials);
830
+ }
831
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */
832
+ get onerror() {
833
+ return __privateGet(this, _onError);
834
+ }
835
+ set onerror(value) {
836
+ __privateSet(this, _onError, value);
837
+ }
838
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */
839
+ get onmessage() {
840
+ return __privateGet(this, _onMessage);
841
+ }
842
+ set onmessage(value) {
843
+ __privateSet(this, _onMessage, value);
844
+ }
845
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */
846
+ get onopen() {
847
+ return __privateGet(this, _onOpen);
848
+ }
849
+ set onopen(value) {
850
+ __privateSet(this, _onOpen, value);
851
+ }
852
+ addEventListener(type, listener, options) {
853
+ const listen = listener;
854
+ super.addEventListener(type, listen, options);
855
+ }
856
+ removeEventListener(type, listener, options) {
857
+ const listen = listener;
858
+ super.removeEventListener(type, listen, options);
859
+ }
860
+ /**
861
+ * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED.
862
+ *
863
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)
864
+ *
865
+ * @public
866
+ */
867
+ close() {
868
+ __privateGet(this, _reconnectTimer) && clearTimeout(__privateGet(this, _reconnectTimer)), __privateGet(this, _readyState) !== this.CLOSED && (__privateGet(this, _controller) && __privateGet(this, _controller).abort(), __privateSet(this, _readyState, this.CLOSED), __privateSet(this, _controller, void 0));
505
869
  }
506
- const username = process.env.BATON_USER;
507
- const password = process.env.BATON_PASS;
508
- if (!username || !password) return;
509
- setLoginGate(
510
- (async () => {
511
- const res = await fetch(`${baseUrl}/auth/login`, {
512
- method: "POST",
513
- headers: { "content-type": "application/json" },
514
- body: JSON.stringify({ username, password })
515
- });
516
- const cookie = (res.headers.getSetCookie()[0] ?? "").split(";")[0];
517
- if (res.ok && cookie) setAuthHeaders({ cookie });
518
- })().catch(() => {
519
- })
520
- );
521
- };
522
-
523
- // src/client/projects.ts
524
- var projectClient = (baseUrl) => {
525
- const u = (p) => `${baseUrl}${p}`;
526
- return {
527
- create: (input) => request(u("/projects"), { method: "POST", body: input }),
528
- listByWorkspace: (workspaceId) => request(u(`/workspaces/${workspaceId}/projects`), { method: "GET" }),
529
- get: (id) => request(u(`/projects/${id}`), { method: "GET" }),
530
- update: (id, patch2) => request(u(`/projects/${id}`), { method: "PATCH", body: patch2 }),
531
- remove: (id) => request(u(`/projects/${id}`), { method: "DELETE" })
532
- };
533
- };
534
-
535
- // src/client/items.ts
536
- var fetchItemByCode = async (baseUrl, projectId, code, expectKind) => {
537
- const r = await request(
538
- `${baseUrl}/projects/${projectId}/items/${encodeURIComponent(code)}`,
539
- { method: "GET" }
540
- );
541
- if (r.kind !== expectKind) throw new Error(`expected ${expectKind} but got ${r.kind} for ${code}`);
542
- return r.item;
543
870
  };
544
-
545
- // src/client/requirements.ts
546
- var requirementClient = (baseUrl) => {
547
- const u = (p) => `${baseUrl}${p}`;
548
- return {
549
- create: (input) => request(u("/requirements"), { method: "POST", body: input }),
550
- listByProject: (projectId) => request(u(`/projects/${projectId}/requirements`), { method: "GET" }),
551
- get: (id) => request(u(`/requirements/${id}`), { method: "GET" }),
552
- getByCode: (projectId, code) => fetchItemByCode(baseUrl, projectId, code, "requirement"),
553
- update: (id, patch2) => request(u(`/requirements/${id}`), { method: "PATCH", body: patch2 }),
554
- setStatus: (id, status) => request(u(`/requirements/${id}`), { method: "PATCH", body: { status } }),
555
- remove: (id) => request(u(`/requirements/${id}`), { method: "DELETE" })
871
+ _readyState = /* @__PURE__ */ new WeakMap(), _url = /* @__PURE__ */ new WeakMap(), _redirectUrl = /* @__PURE__ */ new WeakMap(), _withCredentials = /* @__PURE__ */ new WeakMap(), _fetch = /* @__PURE__ */ new WeakMap(), _reconnectInterval = /* @__PURE__ */ new WeakMap(), _reconnectTimer = /* @__PURE__ */ new WeakMap(), _lastEventId = /* @__PURE__ */ new WeakMap(), _controller = /* @__PURE__ */ new WeakMap(), _parser = /* @__PURE__ */ new WeakMap(), _onError = /* @__PURE__ */ new WeakMap(), _onMessage = /* @__PURE__ */ new WeakMap(), _onOpen = /* @__PURE__ */ new WeakMap(), _EventSource_instances = /* @__PURE__ */ new WeakSet(), /**
872
+ * Connect to the given URL and start receiving events
873
+ *
874
+ * @internal
875
+ */
876
+ connect_fn = function() {
877
+ __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _controller, new AbortController()), __privateGet(this, _fetch)(__privateGet(this, _url), __privateMethod(this, _EventSource_instances, getRequestOptions_fn).call(this)).then(__privateGet(this, _onFetchResponse)).catch(__privateGet(this, _onFetchError));
878
+ }, _onFetchResponse = /* @__PURE__ */ new WeakMap(), _onFetchError = /* @__PURE__ */ new WeakMap(), /**
879
+ * Get request options for the `fetch()` request
880
+ *
881
+ * @returns The request options
882
+ * @internal
883
+ */
884
+ getRequestOptions_fn = function() {
885
+ var _a;
886
+ const init2 = {
887
+ // [spec] Let `corsAttributeState` be `Anonymous`…
888
+ // [spec] …will have their mode set to "cors"…
889
+ mode: "cors",
890
+ redirect: "follow",
891
+ headers: { Accept: "text/event-stream", ...__privateGet(this, _lastEventId) ? { "Last-Event-ID": __privateGet(this, _lastEventId) } : void 0 },
892
+ cache: "no-store",
893
+ signal: (_a = __privateGet(this, _controller)) == null ? void 0 : _a.signal
556
894
  };
557
- };
895
+ return "window" in globalThis && (init2.credentials = this.withCredentials ? "include" : "same-origin"), init2;
896
+ }, _onEvent = /* @__PURE__ */ new WeakMap(), _onRetryChange = /* @__PURE__ */ new WeakMap(), /**
897
+ * Handles the process referred to in the EventSource specification as "failing a connection".
898
+ *
899
+ * @param error - The error causing the connection to fail
900
+ * @param code - The HTTP status code, if available
901
+ * @internal
902
+ */
903
+ failConnection_fn = function(message, code) {
904
+ var _a;
905
+ __privateGet(this, _readyState) !== this.CLOSED && __privateSet(this, _readyState, this.CLOSED);
906
+ const errorEvent = new ErrorEvent("error", { code, message });
907
+ (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent);
908
+ }, /**
909
+ * Schedules a reconnection attempt against the EventSource endpoint.
910
+ *
911
+ * @param message - The error causing the connection to fail
912
+ * @param code - The HTTP status code, if available
913
+ * @internal
914
+ */
915
+ scheduleReconnect_fn = function(message, code) {
916
+ var _a;
917
+ if (__privateGet(this, _readyState) === this.CLOSED)
918
+ return;
919
+ __privateSet(this, _readyState, this.CONNECTING);
920
+ const errorEvent = new ErrorEvent("error", { code, message });
921
+ (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent), __privateSet(this, _reconnectTimer, setTimeout(__privateGet(this, _reconnect), __privateGet(this, _reconnectInterval)));
922
+ }, _reconnect = /* @__PURE__ */ new WeakMap(), /**
923
+ * ReadyState representing an EventSource currently trying to connect
924
+ *
925
+ * @public
926
+ */
927
+ EventSource.CONNECTING = 0, /**
928
+ * ReadyState representing an EventSource connection that is open (eg connected)
929
+ *
930
+ * @public
931
+ */
932
+ EventSource.OPEN = 1, /**
933
+ * ReadyState representing an EventSource connection that is closed (eg disconnected)
934
+ *
935
+ * @public
936
+ */
937
+ EventSource.CLOSED = 2;
938
+ Object.defineProperty(EventSource, /* @__PURE__ */ Symbol.for("eventsource.supports-fetch-override"), {
939
+ value: true,
940
+ writable: false,
941
+ configurable: false,
942
+ enumerable: false
943
+ });
944
+ function getBaseURL() {
945
+ const doc = "document" in globalThis ? globalThis.document : void 0;
946
+ return doc && typeof doc == "object" && "baseURI" in doc && typeof doc.baseURI == "string" ? doc.baseURI : void 0;
947
+ }
558
948
 
559
- // src/client/sessions.ts
560
- var sessionsClient = (baseUrl) => {
949
+ // src/client/channel.ts
950
+ var channelClient = (baseUrl) => {
561
951
  const u = (p) => `${baseUrl}${p}`;
952
+ const authed = (token) => ({ authorization: `Bearer ${token}` });
953
+ const sendJson = (token) => ({ "content-type": "application/json", ...authed(token) });
954
+ const member = (channelId, name) => u(`/channels/${channelId}/members/${encodeURIComponent(name)}`);
562
955
  return {
563
- create: (input) => request(u("/sessions"), { method: "POST", body: input }),
564
- materialize: (id, input, workerToken) => request(u(`/sessions/${id}`), {
565
- method: "PATCH",
566
- body: input,
567
- headers: { authorization: `Bearer ${workerToken}` }
568
- }),
569
- setStatus: (id, active, workerToken) => request(u(`/sessions/${id}/status`), {
570
- method: "POST",
571
- body: { active },
572
- headers: { authorization: `Bearer ${workerToken}` }
573
- }),
574
- setName: (id, name, workerToken) => request(u(`/sessions/${id}`), {
575
- method: "PATCH",
576
- body: { name },
577
- headers: { authorization: `Bearer ${workerToken}` }
578
- }),
579
- resume: (id) => request(u(`/sessions/${id}/resume`), { method: "POST" }),
580
- stop: (id) => request(u(`/sessions/${id}/stop`), { method: "POST" }),
581
- clear: (id) => request(u(`/sessions/${id}/clear`), { method: "POST" }),
582
- abort: (id) => request(u(`/sessions/${id}/abort`), { method: "POST" }),
583
- rename: (id, name) => request(u(`/sessions/${id}/rename`), { method: "POST", body: { name } }),
584
- listByProject: (projectId) => request(u(`/projects/${projectId}/sessions`), { method: "GET" }),
585
- get: (id) => request(u(`/sessions/${id}`), { method: "GET" }),
586
- findByName: async (projectId, name) => {
587
- const all = await request(u(`/projects/${projectId}/sessions`), { method: "GET" });
588
- const matches = all.filter((s) => s.name === name);
589
- return matches.length === 0 ? null : matches[matches.length - 1] ?? null;
956
+ create: async (title, description) => {
957
+ const res = await fetch(u("/channels"), {
958
+ method: "POST",
959
+ headers: { "content-type": "application/json" },
960
+ body: JSON.stringify({ ...title ? { title } : {}, ...description ? { description } : {} })
961
+ });
962
+ if (!res.ok) throw new Error(`POST /channels \u2192 ${res.status}: ${await res.text()}`);
963
+ return await res.json();
590
964
  },
591
- sendMessage: (id, text, attachments, planMode) => request(u(`/sessions/${id}/messages`), {
592
- method: "POST",
593
- body: {
594
- text,
595
- ...attachments && attachments.length > 0 ? { attachments } : {},
596
- ...planMode ? { planMode: true } : {}
597
- }
598
- }),
599
- // Raw-body upload: the file streams as the request body (no multipart), so
600
- // a Blob backed by a file on disk uploads without buffering. filename rides
601
- // a query param; content-type carries the media type.
602
- uploadAttachment: async (id, input) => {
603
- const url = u(`/sessions/${id}/attachments?filename=${encodeURIComponent(input.filename)}`);
604
- const res = await fetch(url, {
965
+ manifest: async (channelId, token) => {
966
+ const res = await fetch(u(`/channels/${channelId}`), { headers: authed(token) });
967
+ if (!res.ok) throw new Error(`GET channel \u2192 ${res.status}: ${await res.text()}`);
968
+ return await res.json();
969
+ },
970
+ update: async (channelId, token, patch2) => {
971
+ const res = await fetch(u(`/channels/${channelId}`), {
972
+ method: "PATCH",
973
+ headers: sendJson(token),
974
+ body: JSON.stringify(patch2)
975
+ });
976
+ if (!res.ok) throw new Error(`PATCH channel \u2192 ${res.status}: ${await res.text()}`);
977
+ return await res.json();
978
+ },
979
+ help: async () => {
980
+ const res = await fetch(u("/channels/help"));
981
+ if (!res.ok) throw new Error(`GET /channels/help \u2192 ${res.status}: ${await res.text()}`);
982
+ return await res.text();
983
+ },
984
+ destroy: async (channelId, token) => {
985
+ const res = await fetch(u(`/channels/${channelId}`), {
986
+ method: "DELETE",
987
+ headers: authed(token)
988
+ });
989
+ if (!res.ok) throw new Error(`DELETE channel \u2192 ${res.status}: ${await res.text()}`);
990
+ },
991
+ join: async (channelId, token, name, kind) => {
992
+ const res = await fetch(member(channelId, name), {
993
+ method: "PUT",
994
+ headers: sendJson(token),
995
+ body: JSON.stringify({ kind: kind ?? "agent" })
996
+ });
997
+ if (!res.ok) throw new Error(`join channel \u2192 ${res.status}: ${await res.text()}`);
998
+ return (await res.json()).members;
999
+ },
1000
+ leave: async (channelId, token, name) => {
1001
+ const res = await fetch(member(channelId, name), { method: "DELETE", headers: authed(token) });
1002
+ if (!res.ok) throw new Error(`leave channel \u2192 ${res.status}: ${await res.text()}`);
1003
+ },
1004
+ members: async (channelId, token) => {
1005
+ const res = await fetch(u(`/channels/${channelId}/members`), { headers: authed(token) });
1006
+ if (!res.ok) throw new Error(`GET members \u2192 ${res.status}: ${await res.text()}`);
1007
+ return (await res.json()).members;
1008
+ },
1009
+ send: async (channelId, token, msg) => {
1010
+ const res = await fetch(u(`/channels/${channelId}/messages`), {
605
1011
  method: "POST",
606
- body: input.body,
607
- headers: { "content-type": input.contentType }
1012
+ headers: sendJson(token),
1013
+ body: JSON.stringify(msg)
608
1014
  });
609
- if (!res.ok) throw new Error(`POST ${url} \u2192 ${res.status}: ${await res.text()}`);
1015
+ if (!res.ok) throw new Error(`POST channel message \u2192 ${res.status}: ${await res.text()}`);
610
1016
  return await res.json();
611
1017
  },
612
- destroy: async (id) => {
613
- await request(u(`/sessions/${id}`), { method: "DELETE" });
614
- }
615
- };
616
- };
617
-
618
- // src/client/tasks.ts
619
- var taskClient = (baseUrl) => {
620
- const u = (p) => `${baseUrl}${p}`;
621
- return {
622
- create: (input) => request(u("/tasks"), { method: "POST", body: input }),
623
- listByRequirement: (requirementId) => request(u(`/requirements/${requirementId}/tasks`), { method: "GET" }),
624
- get: (id) => request(u(`/tasks/${id}`), { method: "GET" }),
625
- getByCode: (projectId, code) => fetchItemByCode(baseUrl, projectId, code, "task"),
626
- update: (id, patch2) => request(u(`/tasks/${id}`), { method: "PATCH", body: patch2 }),
627
- setStatus: (id, status) => request(u(`/tasks/${id}`), { method: "PATCH", body: { status } }),
628
- remove: (id) => request(u(`/tasks/${id}`), { method: "DELETE" }),
629
- listComments: (id) => request(u(`/tasks/${id}/comments`), { method: "GET" }),
630
- addComment: (id, body, workerId) => request(u(`/tasks/${id}/comments`), { method: "POST", body: { body, workerId } })
631
- };
632
- };
633
-
634
- // src/client/workers.ts
635
- var workersClient = (baseUrl) => {
636
- const u = (p) => `${baseUrl}${p}`;
637
- return {
638
- register: (input) => request(u("/workers"), { method: "POST", body: input }),
639
- listByProject: (projectId) => request(u(`/projects/${projectId}/workers`), { method: "GET" }),
640
- get: (id) => request(u(`/workers/${id}`), { method: "GET" }),
641
- findByName: async (projectId, name) => {
642
- const all = await request(u(`/projects/${projectId}/workers`), {
643
- method: "GET"
1018
+ read: async (channelId, token, opts) => {
1019
+ const params = new URLSearchParams({ since: String(opts?.since ?? 0) });
1020
+ if (opts?.for) params.set("for", opts.for);
1021
+ const res = await fetch(u(`/channels/${channelId}/messages?${params}`), {
1022
+ headers: authed(token)
644
1023
  });
645
- const matches = all.filter((w) => w.name === name);
646
- return matches.length === 0 ? null : matches[matches.length - 1] ?? null;
1024
+ if (!res.ok) throw new Error(`GET messages \u2192 ${res.status}: ${await res.text()}`);
1025
+ return (await res.json()).messages;
647
1026
  },
648
- heartbeat: (machineId) => request(u("/workers/heartbeat"), { method: "POST", body: { machineId } }),
649
- destroy: async (id) => {
650
- await request(u(`/workers/${id}`), { method: "DELETE" });
1027
+ listen: (channelId, token, opts) => {
1028
+ let cursor = opts.since ?? 0;
1029
+ const params = new URLSearchParams({ since: String(cursor) });
1030
+ if (opts.for) params.set("for", opts.for);
1031
+ if (opts.as) params.set("as", opts.as);
1032
+ const es = new EventSource(u(`/channels/${channelId}/stream?${params}`), {
1033
+ fetch: (url, init2) => fetch(url, {
1034
+ ...init2,
1035
+ headers: { ...init2?.headers, ...authed(token) }
1036
+ })
1037
+ });
1038
+ es.onmessage = (e) => {
1039
+ try {
1040
+ const m = JSON.parse(e.data);
1041
+ if (m.seq <= cursor) return;
1042
+ cursor = m.seq;
1043
+ opts.onMessage(m);
1044
+ } catch (err) {
1045
+ opts.onError?.(err);
1046
+ }
1047
+ };
1048
+ es.onerror = (err) => opts.onError?.(err);
1049
+ return () => es.close();
651
1050
  }
652
1051
  };
653
1052
  };
654
1053
 
655
- // src/client/workspaces.ts
656
- var workspaceClient = (baseUrl) => {
657
- const u = (p) => `${baseUrl}${p}`;
658
- return {
659
- create: (input) => request(u("/workspaces"), { method: "POST", body: input }),
660
- list: () => request(u("/workspaces"), { method: "GET" }),
661
- get: (id) => request(u(`/workspaces/${id}`), { method: "GET" }),
662
- update: (id, patch2) => request(u(`/workspaces/${id}`), { method: "PATCH", body: patch2 }),
663
- remove: (id) => request(u(`/workspaces/${id}`), { method: "DELETE" })
664
- };
665
- };
666
-
667
- // src/client.ts
668
- var createClient = (baseUrl, opts) => {
669
- if (opts?.bearer) setAuthHeaders({ authorization: `Bearer ${opts.bearer}` });
670
- else primeLogin(baseUrl);
671
- return clientFromBase(baseUrl);
672
- };
673
- var clientFromBase = (baseUrl) => ({
674
- workspaces: workspaceClient(baseUrl),
675
- projects: projectClient(baseUrl),
676
- requirements: requirementClient(baseUrl),
677
- tasks: taskClient(baseUrl),
678
- sessions: sessionsClient(baseUrl),
679
- workers: workersClient(baseUrl)
680
- });
681
- var createWorkerClient = (baseUrl, workerToken, sessionId) => {
682
- const u = (p) => `${baseUrl}${p}`;
683
- const auth = { authorization: `Bearer ${workerToken}` };
684
- return {
685
- emitEvent: (type, payload) => request(u(`/sessions/${sessionId}/events`), {
686
- method: "POST",
687
- body: { type, payload },
688
- headers: auth
689
- }),
690
- setActive: (active) => request(u(`/sessions/${sessionId}/status`), {
691
- method: "POST",
692
- body: { active },
693
- headers: auth
694
- }),
695
- listEvents: () => request(u(`/sessions/${sessionId}/events`), { method: "GET", headers: auth })
696
- };
697
- };
698
-
699
1054
  // src/project-config.ts
700
1055
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
701
1056
  import { join } from "node:path";
@@ -762,108 +1117,6 @@ var resolveBaseUrlOrNull = (urlArg, env = process.env) => {
762
1117
  return env.BATON_URL ?? null;
763
1118
  };
764
1119
 
765
- // src/util.ts
766
- var common = {
767
- url: {
768
- type: "string",
769
- description: "baton server url (--url > .baton.json > BATON_URL > http://localhost:3280)"
770
- },
771
- json: { type: "boolean", description: "output JSON" }
772
- };
773
- var resolveAuth = (env, fileToken) => {
774
- const envBearer = env.BATON_TOKEN ?? env.BATON_WORKER_TOKEN;
775
- if (envBearer) return { bearer: envBearer };
776
- if (env.BATON_USER && env.BATON_PASS) return "cookie";
777
- return fileToken ? { bearer: fileToken } : void 0;
778
- };
779
- var clientFor = (args) => {
780
- const baseUrl = resolveBaseUrl(args.url);
781
- const fileToken = loadProjectConfigOrNull(projectConfigPath())?.worker?.apiToken;
782
- const auth = resolveAuth(process.env, fileToken);
783
- return createClient(baseUrl, auth && auth !== "cookie" ? auth : void 0);
784
- };
785
- var resolveProjectId = (args) => {
786
- if (args.project !== void 0 && args.project !== "") return Number(args.project);
787
- const cfg = loadProjectConfigOrNull(projectConfigPath());
788
- if (cfg?.project !== void 0) return cfg.project;
789
- throw new Error("no project in scope. pass --project <id> or run `baton init` in this directory.");
790
- };
791
- var resolveWorkspaceId = (args) => {
792
- if (args.workspace !== void 0 && args.workspace !== "") return Number(args.workspace);
793
- const cfg = loadProjectConfigOrNull(projectConfigPath());
794
- if (cfg?.workspace !== void 0) return cfg.workspace;
795
- throw new Error(
796
- "no workspace in scope. pass --workspace <id> or run `baton init` in this directory."
797
- );
798
- };
799
- var parseWorkerHandle = (handle) => {
800
- const m = handle.trim().match(/^(?:[wW]-)?(\d+)$/);
801
- return m ? Number(m[1]) : null;
802
- };
803
- var splitCsv = (s) => s ? s.split(",").map((x) => x.trim()).filter(Boolean) : void 0;
804
-
805
- // src/commands/init.ts
806
- var promptPick = async (label, items) => {
807
- if (items.length === 0) throw new Error(`no ${label}s found on server`);
808
- console.log(`${label}s:`);
809
- items.forEach((it, i) => {
810
- console.log(` [${i + 1}] #${it.id} ${it.name}`);
811
- });
812
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
813
- try {
814
- const ans = (await rl.question(`pick a ${label} (1-${items.length}): `)).trim();
815
- const idx = Number(ans) - 1;
816
- const picked = items[idx];
817
- if (!picked) throw new Error(`invalid choice: ${ans}`);
818
- return picked;
819
- } finally {
820
- rl.close();
821
- }
822
- };
823
- var init = defineCommand({
824
- meta: {
825
- name: "init",
826
- description: `write ${PROJECT_CONFIG_NAME} in the current directory (local state, gitignored)`
827
- },
828
- args: {
829
- workspace: { type: "string", description: "workspace id (skip prompt)" },
830
- project: { type: "string", description: "project id (skip prompt)" },
831
- force: { type: "boolean", description: `overwrite existing ${PROJECT_CONFIG_NAME}` },
832
- ...common
833
- },
834
- run: async ({ args }) => {
835
- const server = resolveBaseUrl(args.url);
836
- const cfgPath = projectConfigPath();
837
- if (existsSync2(cfgPath) && !args.force)
838
- throw new Error(`${PROJECT_CONFIG_NAME} already exists. use --force to overwrite.`);
839
- const c = createClient(server);
840
- let project2;
841
- let workspace2;
842
- if (args.project) {
843
- project2 = await c.projects.get(Number(args.project));
844
- workspace2 = await c.workspaces.get(project2.workspaceId);
845
- } else {
846
- const wsList = await c.workspaces.list();
847
- workspace2 = args.workspace ? wsList.find((w) => w.id === Number(args.workspace)) ?? (() => {
848
- throw new Error(`workspace ${args.workspace} not found`);
849
- })() : await promptPick("workspace", wsList);
850
- const projects = await c.projects.listByWorkspace(workspace2.id);
851
- project2 = await promptPick("project", projects);
852
- }
853
- const cfg = {
854
- server,
855
- workspace: workspace2.id,
856
- project: project2.id,
857
- name: project2.name
858
- };
859
- saveProjectConfig(cfgPath, cfg);
860
- console.log(`wrote ${cfgPath}`);
861
- console.log(` server: ${server}`);
862
- console.log(` workspace: #${workspace2.id} ${workspace2.name}`);
863
- console.log(` project: #${project2.id} ${project2.name}`);
864
- }
865
- });
866
-
867
1120
  // src/output.ts
868
1121
  var toJson = (data) => JSON.stringify(data, null, 2);
869
1122
  var fmtWorkspace = (w) => `${w.id} ${w.name}`;
@@ -883,563 +1136,747 @@ var renderList = (items, fmt, json) => {
883
1136
  };
884
1137
  var removed = (kind, id, json) => json ? toJson({ ok: true, deleted: id }) : `deleted ${kind} ${id}`;
885
1138
 
886
- // src/commands/project.ts
887
- var createProject = (c, input, json) => c.projects.create(input).then((p) => renderOne(p, fmtProject, json));
888
- var listProjects = (c, workspaceId, json) => c.projects.listByWorkspace(workspaceId).then((ps) => renderList(ps, fmtProject, json));
889
- var getProject = (c, id, json) => c.projects.get(id).then((p) => renderOne(p, fmtProject, json));
890
- var updateProject = (c, id, patch2, json) => c.projects.update(id, patch2).then((p) => renderOne(p, fmtProject, json));
891
- var removeProject = (c, id, json) => c.projects.remove(id).then(() => removed("project", id, json));
892
- var project = defineCommand({
893
- meta: { name: "project", description: "manage projects" },
894
- subCommands: {
895
- create: defineCommand({
896
- meta: { name: "create", description: "create a project" },
897
- args: {
898
- name: { type: "positional", required: true },
899
- workspace: { type: "string", description: "workspace id (overrides .baton.json)" },
900
- desc: { type: "string", description: "description" },
901
- ...common
902
- },
903
- run: async ({ args }) => {
904
- const out = await createProject(
905
- clientFor(args),
906
- { workspaceId: resolveWorkspaceId(args), name: args.name, description: args.desc },
907
- Boolean(args.json)
908
- );
909
- console.log(out);
910
- }
911
- }),
912
- ls: defineCommand({
913
- meta: { name: "ls", description: "list projects in a workspace" },
914
- args: {
915
- workspace: { type: "string", description: "workspace id (overrides .baton.json)" },
916
- ...common
917
- },
918
- run: async ({ args }) => {
919
- console.log(
920
- await listProjects(clientFor(args), resolveWorkspaceId(args), Boolean(args.json))
921
- );
922
- }
923
- }),
924
- get: defineCommand({
925
- meta: { name: "get", description: "get a project" },
926
- args: { id: { type: "positional", required: true }, ...common },
927
- run: async ({ args }) => {
928
- console.log(await getProject(clientFor(args), Number(args.id), Boolean(args.json)));
929
- }
930
- }),
931
- update: defineCommand({
932
- meta: { name: "update", description: "rename a project / set its description" },
933
- args: {
934
- id: { type: "positional", required: true },
935
- name: { type: "string", description: "new name" },
936
- desc: { type: "string", description: "new description" },
937
- ...common
938
- },
939
- run: async ({ args }) => {
940
- const patch2 = {
941
- ...args.name !== void 0 ? { name: args.name } : {},
942
- ...args.desc !== void 0 ? { description: args.desc } : {}
943
- };
944
- if (Object.keys(patch2).length === 0) throw new Error("pass --name and/or --desc");
945
- console.log(
946
- await updateProject(clientFor(args), Number(args.id), patch2, Boolean(args.json))
947
- );
948
- }
949
- }),
950
- rm: defineCommand({
951
- meta: { name: "rm", description: "delete a project" },
952
- args: { id: { type: "positional", required: true }, ...common },
953
- run: async ({ args }) => {
954
- console.log(await removeProject(clientFor(args), Number(args.id), Boolean(args.json)));
955
- }
1139
+ // src/client/request.ts
1140
+ var authHeaders = {};
1141
+ var loginGate = null;
1142
+ var setAuthHeaders = (h) => {
1143
+ authHeaders = h;
1144
+ };
1145
+ var setLoginGate = (p) => {
1146
+ loginGate = p;
1147
+ };
1148
+ var request = async (url, init2) => {
1149
+ if (loginGate) await loginGate;
1150
+ const baseHeaders = init2.body !== void 0 ? { "content-type": "application/json" } : {};
1151
+ const res = await fetch(url, {
1152
+ method: init2.method,
1153
+ headers: { ...baseHeaders, ...authHeaders, ...init2.headers ?? {} },
1154
+ ...init2.body !== void 0 ? { body: JSON.stringify(init2.body) } : {}
1155
+ });
1156
+ if (!res.ok) throw new Error(`${init2.method} ${url} \u2192 ${res.status}: ${await res.text()}`);
1157
+ if (res.status === 204) return void 0;
1158
+ return await res.json();
1159
+ };
1160
+
1161
+ // src/client/auth.ts
1162
+ var primed = false;
1163
+ var primeLogin = (baseUrl) => {
1164
+ if (primed) return;
1165
+ primed = true;
1166
+ const token = process.env.BATON_TOKEN;
1167
+ if (token) {
1168
+ setAuthHeaders({ authorization: `Bearer ${token}` });
1169
+ return;
1170
+ }
1171
+ const username = process.env.BATON_USER;
1172
+ const password = process.env.BATON_PASS;
1173
+ if (!username || !password) return;
1174
+ setLoginGate(
1175
+ (async () => {
1176
+ const res = await fetch(`${baseUrl}/auth/login`, {
1177
+ method: "POST",
1178
+ headers: { "content-type": "application/json" },
1179
+ body: JSON.stringify({ username, password })
1180
+ });
1181
+ const cookie = (res.headers.getSetCookie()[0] ?? "").split(";")[0];
1182
+ if (res.ok && cookie) setAuthHeaders({ cookie });
1183
+ })().catch(() => {
956
1184
  })
957
- }
958
- });
1185
+ );
1186
+ };
959
1187
 
960
- // src/commands/relay.ts
961
- import { mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
962
- import { tmpdir } from "node:os";
963
- import { join as join2 } from "node:path";
1188
+ // src/client/projects.ts
1189
+ var projectClient = (baseUrl) => {
1190
+ const u = (p) => `${baseUrl}${p}`;
1191
+ return {
1192
+ create: (input) => request(u("/projects"), { method: "POST", body: input }),
1193
+ listByWorkspace: (workspaceId) => request(u(`/workspaces/${workspaceId}/projects`), { method: "GET" }),
1194
+ get: (id) => request(u(`/projects/${id}`), { method: "GET" }),
1195
+ update: (id, patch2) => request(u(`/projects/${id}`), { method: "PATCH", body: patch2 }),
1196
+ remove: (id) => request(u(`/projects/${id}`), { method: "DELETE" })
1197
+ };
1198
+ };
964
1199
 
965
- // ../../node_modules/.pnpm/eventsource-parser@3.1.0/node_modules/eventsource-parser/dist/index.js
966
- var ParseError = class extends Error {
967
- constructor(message, options) {
968
- super(message), this.name = "ParseError", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line;
969
- }
1200
+ // src/client/items.ts
1201
+ var fetchItemByCode = async (baseUrl, projectId, code, expectKind) => {
1202
+ const r = await request(
1203
+ `${baseUrl}/projects/${projectId}/items/${encodeURIComponent(code)}`,
1204
+ { method: "GET" }
1205
+ );
1206
+ if (r.kind !== expectKind) throw new Error(`expected ${expectKind} but got ${r.kind} for ${code}`);
1207
+ return r.item;
970
1208
  };
971
- var LF = 10;
972
- var CR = 13;
973
- var SPACE = 32;
974
- function noop(_arg) {
975
- }
976
- function createParser(config) {
977
- if (typeof config == "function")
978
- throw new TypeError(
979
- "`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?"
980
- );
981
- const { onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize } = config, pendingFragments = [];
982
- let pendingFragmentsLength = 0, isFirstChunk = true, id, data = "", dataLines = 0, eventType, terminated = false;
983
- function feed(chunk) {
984
- if (terminated)
985
- throw new Error(
986
- "Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing."
987
- );
988
- if (isFirstChunk && (isFirstChunk = false, chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191 && (chunk = chunk.slice(3))), pendingFragments.length === 0) {
989
- const trailing2 = processLines(chunk);
990
- trailing2 !== "" && (pendingFragments.push(trailing2), pendingFragmentsLength = trailing2.length), checkBufferSize();
991
- return;
992
- }
993
- if (chunk.indexOf(`
994
- `) === -1 && chunk.indexOf("\r") === -1) {
995
- pendingFragments.push(chunk), pendingFragmentsLength += chunk.length, checkBufferSize();
996
- return;
997
- }
998
- pendingFragments.push(chunk);
999
- const input = pendingFragments.join("");
1000
- pendingFragments.length = 0, pendingFragmentsLength = 0;
1001
- const trailing = processLines(input);
1002
- trailing !== "" && (pendingFragments.push(trailing), pendingFragmentsLength = trailing.length), checkBufferSize();
1003
- }
1004
- function checkBufferSize() {
1005
- maxBufferSize !== void 0 && (pendingFragmentsLength + data.length <= maxBufferSize || (terminated = true, pendingFragments.length = 0, pendingFragmentsLength = 0, id = void 0, data = "", dataLines = 0, eventType = void 0, onError(
1006
- new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {
1007
- type: "max-buffer-size-exceeded"
1008
- })
1009
- )));
1010
- }
1011
- function processLines(chunk) {
1012
- let searchIndex = 0;
1013
- if (chunk.indexOf("\r") === -1) {
1014
- let lfIndex = chunk.indexOf(`
1015
- `, searchIndex);
1016
- for (; lfIndex !== -1; ) {
1017
- if (searchIndex === lfIndex) {
1018
- dataLines > 0 && onEvent({ id, event: eventType, data }), id = void 0, data = "", dataLines = 0, eventType = void 0, searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
1019
- `, searchIndex);
1020
- continue;
1021
- }
1022
- const firstCharCode = chunk.charCodeAt(searchIndex);
1023
- if (isDataPrefix(chunk, searchIndex, firstCharCode)) {
1024
- const valueStart = chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5, value = chunk.slice(valueStart, lfIndex);
1025
- if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {
1026
- onEvent({ id, event: eventType, data: value }), id = void 0, data = "", eventType = void 0, searchIndex = lfIndex + 2, lfIndex = chunk.indexOf(`
1027
- `, searchIndex);
1028
- continue;
1029
- }
1030
- data = dataLines === 0 ? value : `${data}
1031
- ${value}`, dataLines++;
1032
- } else isEventPrefix(chunk, searchIndex, firstCharCode) ? eventType = chunk.slice(
1033
- chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6,
1034
- lfIndex
1035
- ) || void 0 : parseLine(chunk, searchIndex, lfIndex);
1036
- searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
1037
- `, searchIndex);
1038
- }
1039
- return chunk.slice(searchIndex);
1040
- }
1041
- for (; searchIndex < chunk.length; ) {
1042
- const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(`
1043
- `, searchIndex);
1044
- let lineEnd = -1;
1045
- if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = crIndex < lfIndex ? crIndex : lfIndex : crIndex !== -1 ? crIndex === chunk.length - 1 ? lineEnd = -1 : lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1)
1046
- break;
1047
- parseLine(chunk, searchIndex, lineEnd), searchIndex = lineEnd + 1, chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF && searchIndex++;
1048
- }
1049
- return chunk.slice(searchIndex);
1050
- }
1051
- function parseLine(chunk, start, end) {
1052
- if (start === end) {
1053
- dispatchEvent();
1054
- return;
1055
- }
1056
- const firstCharCode = chunk.charCodeAt(start);
1057
- if (isDataPrefix(chunk, start, firstCharCode)) {
1058
- const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5, value2 = chunk.slice(valueStart, end);
1059
- data = dataLines === 0 ? value2 : `${data}
1060
- ${value2}`, dataLines++;
1061
- return;
1062
- }
1063
- if (isEventPrefix(chunk, start, firstCharCode)) {
1064
- eventType = chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || void 0;
1065
- return;
1066
- }
1067
- if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) {
1068
- const value2 = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end);
1069
- id = value2.includes("\0") ? void 0 : value2;
1070
- return;
1071
- }
1072
- if (firstCharCode === 58) {
1073
- if (onComment) {
1074
- const line2 = chunk.slice(start, end);
1075
- onComment(line2.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1));
1209
+
1210
+ // src/client/requirements.ts
1211
+ var requirementClient = (baseUrl) => {
1212
+ const u = (p) => `${baseUrl}${p}`;
1213
+ return {
1214
+ create: (input) => request(u("/requirements"), { method: "POST", body: input }),
1215
+ listByProject: (projectId) => request(u(`/projects/${projectId}/requirements`), { method: "GET" }),
1216
+ get: (id) => request(u(`/requirements/${id}`), { method: "GET" }),
1217
+ getByCode: (projectId, code) => fetchItemByCode(baseUrl, projectId, code, "requirement"),
1218
+ update: (id, patch2) => request(u(`/requirements/${id}`), { method: "PATCH", body: patch2 }),
1219
+ setStatus: (id, status) => request(u(`/requirements/${id}`), { method: "PATCH", body: { status } }),
1220
+ remove: (id) => request(u(`/requirements/${id}`), { method: "DELETE" })
1221
+ };
1222
+ };
1223
+
1224
+ // src/client/sessions.ts
1225
+ var sessionsClient = (baseUrl) => {
1226
+ const u = (p) => `${baseUrl}${p}`;
1227
+ return {
1228
+ create: (input) => request(u("/sessions"), { method: "POST", body: input }),
1229
+ materialize: (id, input, workerToken) => request(u(`/sessions/${id}`), {
1230
+ method: "PATCH",
1231
+ body: input,
1232
+ headers: { authorization: `Bearer ${workerToken}` }
1233
+ }),
1234
+ setStatus: (id, active, workerToken) => request(u(`/sessions/${id}/status`), {
1235
+ method: "POST",
1236
+ body: { active },
1237
+ headers: { authorization: `Bearer ${workerToken}` }
1238
+ }),
1239
+ setName: (id, name, workerToken) => request(u(`/sessions/${id}`), {
1240
+ method: "PATCH",
1241
+ body: { name },
1242
+ headers: { authorization: `Bearer ${workerToken}` }
1243
+ }),
1244
+ resume: (id) => request(u(`/sessions/${id}/resume`), { method: "POST" }),
1245
+ stop: (id) => request(u(`/sessions/${id}/stop`), { method: "POST" }),
1246
+ clear: (id) => request(u(`/sessions/${id}/clear`), { method: "POST" }),
1247
+ abort: (id) => request(u(`/sessions/${id}/abort`), { method: "POST" }),
1248
+ rename: (id, name) => request(u(`/sessions/${id}/rename`), { method: "POST", body: { name } }),
1249
+ listByProject: (projectId) => request(u(`/projects/${projectId}/sessions`), { method: "GET" }),
1250
+ get: (id) => request(u(`/sessions/${id}`), { method: "GET" }),
1251
+ findByName: async (projectId, name) => {
1252
+ const all = await request(u(`/projects/${projectId}/sessions`), { method: "GET" });
1253
+ const matches = all.filter((s) => s.name === name);
1254
+ return matches.length === 0 ? null : matches[matches.length - 1] ?? null;
1255
+ },
1256
+ sendMessage: (id, text, attachments, planMode) => request(u(`/sessions/${id}/messages`), {
1257
+ method: "POST",
1258
+ body: {
1259
+ text,
1260
+ ...attachments && attachments.length > 0 ? { attachments } : {},
1261
+ ...planMode ? { planMode: true } : {}
1076
1262
  }
1077
- return;
1078
- }
1079
- const line = chunk.slice(start, end), fieldSeparatorIndex = line.indexOf(":");
1080
- if (fieldSeparatorIndex === -1) {
1081
- processField(line, "", line);
1082
- return;
1083
- }
1084
- const field = line.slice(0, fieldSeparatorIndex), offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1, value = line.slice(fieldSeparatorIndex + offset);
1085
- processField(field, value, line);
1086
- }
1087
- function processField(field, value, line) {
1088
- switch (field) {
1089
- case "event":
1090
- eventType = value || void 0;
1091
- break;
1092
- case "data":
1093
- data = dataLines === 0 ? value : `${data}
1094
- ${value}`, dataLines++;
1095
- break;
1096
- case "id":
1097
- id = value.includes("\0") ? void 0 : value;
1098
- break;
1099
- case "retry":
1100
- /^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(
1101
- new ParseError(`Invalid \`retry\` value: "${value}"`, {
1102
- type: "invalid-retry",
1103
- value,
1104
- line
1105
- })
1106
- );
1107
- break;
1108
- default:
1109
- onError(
1110
- new ParseError(
1111
- `Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`,
1112
- { type: "unknown-field", field, value, line }
1113
- )
1114
- );
1115
- break;
1116
- }
1117
- }
1118
- function dispatchEvent() {
1119
- dataLines > 0 && onEvent({
1120
- id,
1121
- event: eventType,
1122
- data
1123
- }), id = void 0, data = "", dataLines = 0, eventType = void 0;
1124
- }
1125
- function reset(options = {}) {
1126
- if (options.consume && pendingFragments.length > 0) {
1127
- const incompleteLine = pendingFragments.join("");
1128
- parseLine(incompleteLine, 0, incompleteLine.length);
1263
+ }),
1264
+ // Raw-body upload: the file streams as the request body (no multipart), so
1265
+ // a Blob backed by a file on disk uploads without buffering. filename rides
1266
+ // a query param; content-type carries the media type.
1267
+ uploadAttachment: async (id, input) => {
1268
+ const url = u(`/sessions/${id}/attachments?filename=${encodeURIComponent(input.filename)}`);
1269
+ const res = await fetch(url, {
1270
+ method: "POST",
1271
+ body: input.body,
1272
+ headers: { "content-type": input.contentType }
1273
+ });
1274
+ if (!res.ok) throw new Error(`POST ${url} \u2192 ${res.status}: ${await res.text()}`);
1275
+ return await res.json();
1276
+ },
1277
+ destroy: async (id) => {
1278
+ await request(u(`/sessions/${id}`), { method: "DELETE" });
1129
1279
  }
1130
- isFirstChunk = true, id = void 0, data = "", dataLines = 0, eventType = void 0, pendingFragments.length = 0, pendingFragmentsLength = 0, terminated = false;
1131
- }
1132
- return { feed, reset };
1133
- }
1134
- function isDataPrefix(chunk, i, firstCharCode) {
1135
- return firstCharCode === 100 && chunk.charCodeAt(i + 1) === 97 && chunk.charCodeAt(i + 2) === 116 && chunk.charCodeAt(i + 3) === 97 && chunk.charCodeAt(i + 4) === 58;
1136
- }
1137
- function isEventPrefix(chunk, i, firstCharCode) {
1138
- return firstCharCode === 101 && chunk.charCodeAt(i + 1) === 118 && chunk.charCodeAt(i + 2) === 101 && chunk.charCodeAt(i + 3) === 110 && chunk.charCodeAt(i + 4) === 116 && chunk.charCodeAt(i + 5) === 58;
1139
- }
1140
-
1141
- // ../../node_modules/.pnpm/eventsource@4.1.0/node_modules/eventsource/dist/index.js
1142
- var ErrorEvent = class extends Event {
1143
- /**
1144
- * Constructs a new `ErrorEvent` instance. This is typically not called directly,
1145
- * but rather emitted by the `EventSource` object when an error occurs.
1146
- *
1147
- * @param type - The type of the event (should be "error")
1148
- * @param errorEventInitDict - Optional properties to include in the error event
1149
- */
1150
- constructor(type, errorEventInitDict) {
1151
- var _a, _b;
1152
- super(type), this.code = (_a = errorEventInitDict == null ? void 0 : errorEventInitDict.code) != null ? _a : void 0, this.message = (_b = errorEventInitDict == null ? void 0 : errorEventInitDict.message) != null ? _b : void 0;
1153
- }
1154
- /**
1155
- * Node.js "hides" the `message` and `code` properties of the `ErrorEvent` instance,
1156
- * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,
1157
- * we explicitly include the properties in the `inspect` method.
1158
- *
1159
- * This is automatically called by Node.js when you `console.log` an instance of this class.
1160
- *
1161
- * @param _depth - The current depth
1162
- * @param options - The options passed to `util.inspect`
1163
- * @param inspect - The inspect function to use (prevents having to import it from `util`)
1164
- * @returns A string representation of the error
1165
- */
1166
- [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")](_depth, options, inspect) {
1167
- return inspect(inspectableError(this), options);
1168
- }
1169
- /**
1170
- * Deno "hides" the `message` and `code` properties of the `ErrorEvent` instance,
1171
- * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,
1172
- * we explicitly include the properties in the `inspect` method.
1173
- *
1174
- * This is automatically called by Deno when you `console.log` an instance of this class.
1175
- *
1176
- * @param inspect - The inspect function to use (prevents having to import it from `util`)
1177
- * @param options - The options passed to `Deno.inspect`
1178
- * @returns A string representation of the error
1179
- */
1180
- [/* @__PURE__ */ Symbol.for("Deno.customInspect")](inspect, options) {
1181
- return inspect(inspectableError(this), options);
1182
- }
1280
+ };
1183
1281
  };
1184
- function syntaxError(message) {
1185
- const DomException = globalThis.DOMException;
1186
- return typeof DomException == "function" ? new DomException(message, "SyntaxError") : new SyntaxError(message);
1187
- }
1188
- function flattenError(err) {
1189
- return err instanceof Error ? "errors" in err && Array.isArray(err.errors) ? err.errors.map(flattenError).join(", ") : "cause" in err && err.cause instanceof Error ? `${err}: ${flattenError(err.cause)}` : err.message : `${err}`;
1190
- }
1191
- function inspectableError(err) {
1282
+
1283
+ // src/client/tasks.ts
1284
+ var taskClient = (baseUrl) => {
1285
+ const u = (p) => `${baseUrl}${p}`;
1192
1286
  return {
1193
- type: err.type,
1194
- message: err.message,
1195
- code: err.code,
1196
- defaultPrevented: err.defaultPrevented,
1197
- cancelable: err.cancelable,
1198
- timeStamp: err.timeStamp
1287
+ create: (input) => request(u("/tasks"), { method: "POST", body: input }),
1288
+ listByRequirement: (requirementId) => request(u(`/requirements/${requirementId}/tasks`), { method: "GET" }),
1289
+ get: (id) => request(u(`/tasks/${id}`), { method: "GET" }),
1290
+ getByCode: (projectId, code) => fetchItemByCode(baseUrl, projectId, code, "task"),
1291
+ update: (id, patch2) => request(u(`/tasks/${id}`), { method: "PATCH", body: patch2 }),
1292
+ setStatus: (id, status) => request(u(`/tasks/${id}`), { method: "PATCH", body: { status } }),
1293
+ remove: (id) => request(u(`/tasks/${id}`), { method: "DELETE" }),
1294
+ listComments: (id) => request(u(`/tasks/${id}/comments`), { method: "GET" }),
1295
+ addComment: (id, body, workerId) => request(u(`/tasks/${id}/comments`), { method: "POST", body: { body, workerId } })
1199
1296
  };
1200
- }
1201
- var __typeError = (msg) => {
1202
- throw TypeError(msg);
1203
1297
  };
1204
- var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
1205
- var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
1206
- var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
1207
- var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
1208
- var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
1209
- var _readyState;
1210
- var _url;
1211
- var _redirectUrl;
1212
- var _withCredentials;
1213
- var _fetch;
1214
- var _reconnectInterval;
1215
- var _reconnectTimer;
1216
- var _lastEventId;
1217
- var _controller;
1218
- var _parser;
1219
- var _onError;
1220
- var _onMessage;
1221
- var _onOpen;
1222
- var _EventSource_instances;
1223
- var connect_fn;
1224
- var _onFetchResponse;
1225
- var _onFetchError;
1226
- var getRequestOptions_fn;
1227
- var _onEvent;
1228
- var _onRetryChange;
1229
- var failConnection_fn;
1230
- var scheduleReconnect_fn;
1231
- var _reconnect;
1232
- var EventSource = class extends EventTarget {
1233
- constructor(url, eventSourceInitDict) {
1234
- var _a, _b;
1235
- super(), __privateAdd(this, _EventSource_instances), this.CONNECTING = 0, this.OPEN = 1, this.CLOSED = 2, __privateAdd(this, _readyState), __privateAdd(this, _url), __privateAdd(this, _redirectUrl), __privateAdd(this, _withCredentials), __privateAdd(this, _fetch), __privateAdd(this, _reconnectInterval), __privateAdd(this, _reconnectTimer), __privateAdd(this, _lastEventId, null), __privateAdd(this, _controller), __privateAdd(this, _parser), __privateAdd(this, _onError, null), __privateAdd(this, _onMessage, null), __privateAdd(this, _onOpen, null), __privateAdd(this, _onFetchResponse, async (response) => {
1236
- var _a2;
1237
- __privateGet(this, _parser).reset();
1238
- const { body, redirected, status, headers } = response;
1239
- if (status === 204) {
1240
- __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Server sent HTTP 204, not reconnecting", 204), this.close();
1241
- return;
1242
- }
1243
- if (redirected ? __privateSet(this, _redirectUrl, new URL(response.url)) : __privateSet(this, _redirectUrl, void 0), status !== 200) {
1244
- __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, `Non-200 status code (${status})`, status);
1245
- return;
1246
- }
1247
- if (!(headers.get("content-type") || "").startsWith("text/event-stream")) {
1248
- __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, 'Invalid content type, expected "text/event-stream"', status);
1249
- return;
1250
- }
1251
- if (__privateGet(this, _readyState) === this.CLOSED)
1252
- return;
1253
- __privateSet(this, _readyState, this.OPEN);
1254
- const openEvent = new Event("open");
1255
- if ((_a2 = __privateGet(this, _onOpen)) == null || _a2.call(this, openEvent), this.dispatchEvent(openEvent), typeof body != "object" || !body || !("getReader" in body)) {
1256
- __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Invalid response body, expected a web ReadableStream", status), this.close();
1257
- return;
1258
- }
1259
- const decoder = new TextDecoder(), reader = body.getReader();
1260
- let open = true;
1261
- do {
1262
- const { done, value } = await reader.read();
1263
- value && __privateGet(this, _parser).feed(decoder.decode(value, { stream: !done })), done && (open = false, __privateGet(this, _parser).reset(), __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this));
1264
- } while (open);
1265
- }), __privateAdd(this, _onFetchError, (err) => {
1266
- __privateSet(this, _controller, void 0), !(err.name === "AbortError" || err.type === "aborted") && __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this, flattenError(err));
1267
- }), __privateAdd(this, _onEvent, (event) => {
1268
- typeof event.id == "string" && __privateSet(this, _lastEventId, event.id);
1269
- const messageEvent = new MessageEvent(event.event || "message", {
1270
- data: event.data,
1271
- origin: __privateGet(this, _redirectUrl) ? __privateGet(this, _redirectUrl).origin : __privateGet(this, _url).origin,
1272
- lastEventId: event.id || ""
1298
+
1299
+ // src/client/workers.ts
1300
+ var workersClient = (baseUrl) => {
1301
+ const u = (p) => `${baseUrl}${p}`;
1302
+ return {
1303
+ register: (input) => request(u("/workers"), { method: "POST", body: input }),
1304
+ listByProject: (projectId) => request(u(`/projects/${projectId}/workers`), { method: "GET" }),
1305
+ get: (id) => request(u(`/workers/${id}`), { method: "GET" }),
1306
+ findByName: async (projectId, name) => {
1307
+ const all = await request(u(`/projects/${projectId}/workers`), {
1308
+ method: "GET"
1273
1309
  });
1274
- __privateGet(this, _onMessage) && (!event.event || event.event === "message") && __privateGet(this, _onMessage).call(this, messageEvent), this.dispatchEvent(messageEvent);
1275
- }), __privateAdd(this, _onRetryChange, (value) => {
1276
- __privateSet(this, _reconnectInterval, value);
1277
- }), __privateAdd(this, _reconnect, () => {
1278
- __privateSet(this, _reconnectTimer, void 0), __privateGet(this, _readyState) === this.CONNECTING && __privateMethod(this, _EventSource_instances, connect_fn).call(this);
1279
- });
1280
- try {
1281
- if (url instanceof URL)
1282
- __privateSet(this, _url, url);
1283
- else if (typeof url == "string")
1284
- __privateSet(this, _url, new URL(url, getBaseURL()));
1285
- else
1286
- throw new Error("Invalid URL");
1287
- } catch {
1288
- throw syntaxError("An invalid or illegal string was specified");
1310
+ const matches = all.filter((w) => w.name === name);
1311
+ return matches.length === 0 ? null : matches[matches.length - 1] ?? null;
1312
+ },
1313
+ heartbeat: (machineId) => request(u("/workers/heartbeat"), { method: "POST", body: { machineId } }),
1314
+ destroy: async (id) => {
1315
+ await request(u(`/workers/${id}`), { method: "DELETE" });
1316
+ }
1317
+ };
1318
+ };
1319
+
1320
+ // src/client/workspaces.ts
1321
+ var workspaceClient = (baseUrl) => {
1322
+ const u = (p) => `${baseUrl}${p}`;
1323
+ return {
1324
+ create: (input) => request(u("/workspaces"), { method: "POST", body: input }),
1325
+ list: () => request(u("/workspaces"), { method: "GET" }),
1326
+ get: (id) => request(u(`/workspaces/${id}`), { method: "GET" }),
1327
+ update: (id, patch2) => request(u(`/workspaces/${id}`), { method: "PATCH", body: patch2 }),
1328
+ remove: (id) => request(u(`/workspaces/${id}`), { method: "DELETE" })
1329
+ };
1330
+ };
1331
+
1332
+ // src/client.ts
1333
+ var createClient = (baseUrl, opts) => {
1334
+ if (opts?.bearer) setAuthHeaders({ authorization: `Bearer ${opts.bearer}` });
1335
+ else primeLogin(baseUrl);
1336
+ return clientFromBase(baseUrl);
1337
+ };
1338
+ var clientFromBase = (baseUrl) => ({
1339
+ workspaces: workspaceClient(baseUrl),
1340
+ projects: projectClient(baseUrl),
1341
+ requirements: requirementClient(baseUrl),
1342
+ tasks: taskClient(baseUrl),
1343
+ sessions: sessionsClient(baseUrl),
1344
+ workers: workersClient(baseUrl)
1345
+ });
1346
+ var createWorkerClient = (baseUrl, workerToken, sessionId) => {
1347
+ const u = (p) => `${baseUrl}${p}`;
1348
+ const auth = { authorization: `Bearer ${workerToken}` };
1349
+ return {
1350
+ emitEvent: (type, payload) => request(u(`/sessions/${sessionId}/events`), {
1351
+ method: "POST",
1352
+ body: { type, payload },
1353
+ headers: auth
1354
+ }),
1355
+ setActive: (active) => request(u(`/sessions/${sessionId}/status`), {
1356
+ method: "POST",
1357
+ body: { active },
1358
+ headers: auth
1359
+ }),
1360
+ listEvents: () => request(u(`/sessions/${sessionId}/events`), { method: "GET", headers: auth })
1361
+ };
1362
+ };
1363
+
1364
+ // src/util.ts
1365
+ var common = {
1366
+ url: {
1367
+ type: "string",
1368
+ description: "baton server url (--url > .baton.json > BATON_URL > http://localhost:3280)"
1369
+ },
1370
+ json: { type: "boolean", description: "output JSON" }
1371
+ };
1372
+ var resolveAuth = (env, fileToken) => {
1373
+ const envBearer = env.BATON_TOKEN ?? env.BATON_WORKER_TOKEN;
1374
+ if (envBearer) return { bearer: envBearer };
1375
+ if (env.BATON_USER && env.BATON_PASS) return "cookie";
1376
+ return fileToken ? { bearer: fileToken } : void 0;
1377
+ };
1378
+ var clientFor = (args) => {
1379
+ const baseUrl = resolveBaseUrl(args.url);
1380
+ const fileToken = loadProjectConfigOrNull(projectConfigPath())?.worker?.apiToken;
1381
+ const auth = resolveAuth(process.env, fileToken);
1382
+ return createClient(baseUrl, auth && auth !== "cookie" ? auth : void 0);
1383
+ };
1384
+ var resolveProjectId = (args) => {
1385
+ if (args.project !== void 0 && args.project !== "") return Number(args.project);
1386
+ const cfg = loadProjectConfigOrNull(projectConfigPath());
1387
+ if (cfg?.project !== void 0) return cfg.project;
1388
+ throw new Error("no project in scope. pass --project <id> or run `baton init` in this directory.");
1389
+ };
1390
+ var resolveWorkspaceId = (args) => {
1391
+ if (args.workspace !== void 0 && args.workspace !== "") return Number(args.workspace);
1392
+ const cfg = loadProjectConfigOrNull(projectConfigPath());
1393
+ if (cfg?.workspace !== void 0) return cfg.workspace;
1394
+ throw new Error(
1395
+ "no workspace in scope. pass --workspace <id> or run `baton init` in this directory."
1396
+ );
1397
+ };
1398
+ var parseWorkerHandle = (handle) => {
1399
+ const m = handle.trim().match(/^(?:[wW]-)?(\d+)$/);
1400
+ return m ? Number(m[1]) : null;
1401
+ };
1402
+ var splitCsv = (s) => s ? s.split(",").map((x) => x.trim()).filter(Boolean) : void 0;
1403
+
1404
+ // src/commands/channel/close.ts
1405
+ var closeCommand = defineCommand({
1406
+ meta: { name: "close", description: "delete a channel (removes the room and all its messages)" },
1407
+ args: {
1408
+ channel: { type: "positional", required: true, description: "channel id" },
1409
+ token: { type: "string", required: true, description: "channel token" },
1410
+ ...common
1411
+ },
1412
+ run: async ({ args }) => {
1413
+ await channelClient(resolveBaseUrl(args.url)).destroy(args.channel, args.token);
1414
+ console.log(args.json ? toJson({ ok: true, deleted: args.channel }) : `closed ${args.channel}`);
1415
+ }
1416
+ });
1417
+
1418
+ // src/commands/channel/shared.ts
1419
+ var readStdin = async () => {
1420
+ const chunks = [];
1421
+ for await (const c of process.stdin) chunks.push(c);
1422
+ return Buffer.concat(chunks).toString("utf8");
1423
+ };
1424
+ var renderRoster = (members) => members.length ? members.map((m) => ` ${m.name} (${m.kind})`).join("\n") : " (nobody online)";
1425
+ var inviteBlock = (url, channelId, token) => [
1426
+ "\u2500\u2500 Share this to invite anyone into the room \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1427
+ "You're invited to a baton channel. Three steps to get going:",
1428
+ ` 1) see the room: curl -sS -H "authorization: Bearer ${token}" "${url}/channels/${channelId}"`,
1429
+ ` 2) read protocol: curl -sS "${url}/channels/help"`,
1430
+ " 3) follow it to join / listen / send (pick your own NAME).",
1431
+ "",
1432
+ `connection: url=${url} channel=${channelId} token=${token}`,
1433
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
1434
+ ].join("\n");
1435
+
1436
+ // src/commands/channel/create.ts
1437
+ var createCommand = defineCommand({
1438
+ meta: { name: "create", description: "open a channel (chat room) and print an invite" },
1439
+ args: {
1440
+ name: { type: "string", description: "room title (optional)" },
1441
+ desc: { type: "string", description: "room self-description: purpose / topic / how to participate" },
1442
+ ...common
1443
+ },
1444
+ run: async ({ args }) => {
1445
+ const url = resolveBaseUrl(args.url);
1446
+ const { channelId, token } = await channelClient(url).create(args.name, args.desc);
1447
+ if (args.json) {
1448
+ console.log(toJson({ channelId, token, url, name: args.name, description: args.desc }));
1449
+ return;
1289
1450
  }
1290
- __privateSet(this, _parser, createParser({
1291
- onEvent: __privateGet(this, _onEvent),
1292
- onRetry: __privateGet(this, _onRetryChange)
1293
- })), __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _reconnectInterval, 3e3), __privateSet(this, _fetch, (_a = eventSourceInitDict == null ? void 0 : eventSourceInitDict.fetch) != null ? _a : globalThis.fetch), __privateSet(this, _withCredentials, (_b = eventSourceInitDict == null ? void 0 : eventSourceInitDict.withCredentials) != null ? _b : false), __privateMethod(this, _EventSource_instances, connect_fn).call(this);
1451
+ console.log(`channel ${channelId} opened
1452
+ `);
1453
+ console.log(inviteBlock(url, channelId, token));
1294
1454
  }
1295
- /**
1296
- * Returns the state of this EventSource object's connection. It can have the values described below.
1297
- *
1298
- * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState)
1299
- *
1300
- * Note: typed as `number` instead of `0 | 1 | 2` for compatibility with the `EventSource` interface,
1301
- * defined in the TypeScript `dom` library.
1302
- *
1303
- * @public
1304
- */
1305
- get readyState() {
1306
- return __privateGet(this, _readyState);
1455
+ });
1456
+
1457
+ // src/commands/channel/info.ts
1458
+ var aboutCommand = defineCommand({
1459
+ meta: { name: "about", description: "show a channel's manifest (description + online roster)" },
1460
+ args: {
1461
+ channel: { type: "positional", required: true, description: "channel id" },
1462
+ token: { type: "string", required: true, description: "channel token" },
1463
+ ...common
1464
+ },
1465
+ run: async ({ args }) => {
1466
+ const url = resolveBaseUrl(args.url);
1467
+ const m = await channelClient(url).manifest(args.channel, args.token);
1468
+ if (args.json) {
1469
+ console.log(toJson(m));
1470
+ return;
1471
+ }
1472
+ console.log(`${m.title ?? "(untitled)"} [${m.id}]`);
1473
+ if (m.description) console.log(m.description);
1474
+ console.log(`
1475
+ online (${m.members.length}):
1476
+ ${renderRoster(m.members)}`);
1477
+ console.log(`
1478
+ protocol: curl -sS "${url}${m.help}"`);
1307
1479
  }
1308
- /**
1309
- * Returns the URL providing the event stream.
1310
- *
1311
- * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url)
1312
- *
1313
- * @public
1314
- */
1315
- get url() {
1316
- return __privateGet(this, _url).href;
1480
+ });
1481
+ var helpCommand = defineCommand({
1482
+ meta: { name: "help", description: "print the channel protocol doc (no token needed)" },
1483
+ args: { ...common },
1484
+ run: async ({ args }) => {
1485
+ console.log(await channelClient(resolveBaseUrl(args.url)).help());
1317
1486
  }
1318
- /**
1319
- * Returns true if the credentials mode for connection requests to the URL providing the event stream is set to "include", and false otherwise.
1320
- *
1321
- * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials)
1322
- */
1323
- get withCredentials() {
1324
- return __privateGet(this, _withCredentials);
1487
+ });
1488
+ var membersCommand = defineCommand({
1489
+ meta: { name: "members", description: "list the channel's online roster" },
1490
+ args: {
1491
+ channel: { type: "positional", required: true, description: "channel id" },
1492
+ token: { type: "string", required: true, description: "channel token" },
1493
+ ...common
1494
+ },
1495
+ run: async ({ args }) => {
1496
+ const url = resolveBaseUrl(args.url);
1497
+ const members = await channelClient(url).members(args.channel, args.token);
1498
+ console.log(args.json ? toJson(members) : renderRoster(members));
1325
1499
  }
1326
- /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */
1327
- get onerror() {
1328
- return __privateGet(this, _onError);
1500
+ });
1501
+ var updateCommand = defineCommand({
1502
+ meta: { name: "update", description: "update a channel's title / description (topic + rules)" },
1503
+ args: {
1504
+ channel: { type: "positional", required: true, description: "channel id" },
1505
+ token: { type: "string", required: true, description: "channel token" },
1506
+ title: { type: "string", description: "new title" },
1507
+ desc: { type: "string", description: "new description (purpose / topic / rules)" },
1508
+ ...common
1509
+ },
1510
+ run: async ({ args }) => {
1511
+ if (args.title === void 0 && args.desc === void 0)
1512
+ throw new Error("nothing to update: pass --title and/or --desc");
1513
+ const url = resolveBaseUrl(args.url);
1514
+ const ch = await channelClient(url).update(args.channel, args.token, {
1515
+ title: args.title,
1516
+ description: args.desc
1517
+ });
1518
+ console.log(args.json ? toJson(ch) : `updated ${ch.id}${ch.title ? ` \u2014 ${ch.title}` : ""}`);
1329
1519
  }
1330
- set onerror(value) {
1331
- __privateSet(this, _onError, value);
1520
+ });
1521
+
1522
+ // src/commands/channel/listen.ts
1523
+ import { mkdirSync, writeFileSync as writeFileSync2 } from "node:fs";
1524
+ import { tmpdir } from "node:os";
1525
+ import { join as join2 } from "node:path";
1526
+
1527
+ // ../shared/src/domain/attachment.ts
1528
+ var isImageAttachment = (att) => att.contentType.startsWith("image/");
1529
+ var labelAttachments = (attachments) => {
1530
+ const counters = { image: 0, file: 0 };
1531
+ return attachments.map((att) => {
1532
+ const kind = isImageAttachment(att) ? "image" : "file";
1533
+ counters[kind] += 1;
1534
+ return `${kind}-${counters[kind]}`;
1535
+ });
1536
+ };
1537
+
1538
+ // ../shared/src/domain/channel.ts
1539
+ var isMessageFor = (msg, name) => !msg.to || msg.to.length === 0 || msg.to.includes(name);
1540
+
1541
+ // ../shared/src/domain/session-event.ts
1542
+ var startedMessageIds = (events) => {
1543
+ const ids = /* @__PURE__ */ new Set();
1544
+ for (const e of events) {
1545
+ if (e.type !== "turn_start") continue;
1546
+ const id = e.payload?.messageId;
1547
+ if (typeof id === "number") ids.add(id);
1332
1548
  }
1333
- /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */
1334
- get onmessage() {
1335
- return __privateGet(this, _onMessage);
1549
+ return ids;
1550
+ };
1551
+ var unstartedUserMessages = (events) => {
1552
+ const started = startedMessageIds(events);
1553
+ return events.filter((e) => e.type === "user_message" && !started.has(e.id));
1554
+ };
1555
+
1556
+ // src/commands/channel/listen.ts
1557
+ var PREVIEW = 280;
1558
+ var describe = (e) => {
1559
+ const ev = e;
1560
+ return ev?.code ? `${ev.code}${ev.message ? ` ${ev.message}` : ""}` : String(e);
1561
+ };
1562
+ var runListen = async (o) => {
1563
+ const emit = (x) => {
1564
+ process.stdout.write(`${JSON.stringify(x)}
1565
+ `);
1566
+ };
1567
+ const spillDir = join2(tmpdir(), "channel", o.channel);
1568
+ mkdirSync(spillDir, { recursive: true });
1569
+ const surface = (m) => {
1570
+ const text = typeof m.text === "string" ? m.text : "";
1571
+ const file = join2(spillDir, `${m.seq}.txt`);
1572
+ writeFileSync2(file, text);
1573
+ const truncated = text.length > PREVIEW;
1574
+ emit({
1575
+ type: "channel.message",
1576
+ seq: m.seq,
1577
+ from: m.from,
1578
+ senderKind: m.senderKind,
1579
+ to: m.to,
1580
+ mine: o.from ? isMessageFor(m, o.from) : void 0,
1581
+ ts: m.ts,
1582
+ chars: text.length,
1583
+ truncated,
1584
+ preview: truncated ? `${text.slice(0, PREVIEW)} \u2026` : text,
1585
+ file
1586
+ });
1587
+ };
1588
+ const close = channelClient(o.url).listen(o.channel, o.token, {
1589
+ since: o.since,
1590
+ for: o.for,
1591
+ as: o.from,
1592
+ // Skip our own messages (they echo back over the same channel).
1593
+ onMessage: (m) => {
1594
+ if (m.from !== o.from) surface(m);
1595
+ },
1596
+ onError: (e) => emit({ type: "channel.error", error: describe(e) })
1597
+ });
1598
+ emit({ type: "channel.listening", channel: o.channel, from: o.from, url: o.url });
1599
+ await new Promise((resolve2) => {
1600
+ const stop = () => {
1601
+ close();
1602
+ resolve2();
1603
+ };
1604
+ process.on("SIGINT", stop);
1605
+ process.on("SIGTERM", stop);
1606
+ });
1607
+ };
1608
+ var listenCommand = defineCommand({
1609
+ meta: {
1610
+ name: "listen",
1611
+ description: "stream a channel: one JSON line per message (for a background subscriber)"
1612
+ },
1613
+ args: {
1614
+ channel: { type: "positional", required: true, description: "channel id" },
1615
+ token: { type: "string", required: true, description: "channel token" },
1616
+ from: { type: "string", description: "your name; your own messages skipped, presence kept fresh" },
1617
+ since: { type: "string", description: "replay messages after this seq (default 0 = full)" },
1618
+ for: { type: "string", description: "only broadcasts + messages addressed to this name" },
1619
+ ...common
1620
+ },
1621
+ run: async ({ args }) => {
1622
+ await runListen({
1623
+ url: resolveBaseUrl(args.url),
1624
+ channel: args.channel,
1625
+ token: args.token,
1626
+ from: args.from ?? "",
1627
+ since: args.since ? Number(args.since) : 0,
1628
+ for: args.for
1629
+ });
1336
1630
  }
1337
- set onmessage(value) {
1338
- __privateSet(this, _onMessage, value);
1631
+ });
1632
+
1633
+ // src/commands/channel/join.ts
1634
+ var joinCommand = defineCommand({
1635
+ meta: { name: "join", description: "join a channel (register presence); --listen to also stream" },
1636
+ args: {
1637
+ channel: { type: "positional", required: true, description: "channel id" },
1638
+ token: { type: "string", required: true, description: "channel token" },
1639
+ from: { type: "string", required: true, description: "your participant name" },
1640
+ kind: { type: "string", description: "your kind: agent (default) | human" },
1641
+ listen: { type: "boolean", description: "after joining, stream messages (background subscriber)" },
1642
+ since: { type: "string", description: "with --listen: replay after this seq (default 0)" },
1643
+ ...common
1644
+ },
1645
+ run: async ({ args }) => {
1646
+ const url = resolveBaseUrl(args.url);
1647
+ const kind = args.kind === "human" ? "human" : "agent";
1648
+ const members = await channelClient(url).join(args.channel, args.token, args.from, kind);
1649
+ if (!args.listen) {
1650
+ if (args.json) console.log(toJson({ joined: args.from, members }));
1651
+ else console.log(`joined as ${args.from} (${members.length} online)
1652
+ ${renderRoster(members)}`);
1653
+ return;
1654
+ }
1655
+ await runListen({
1656
+ url,
1657
+ channel: args.channel,
1658
+ token: args.token,
1659
+ from: args.from,
1660
+ since: args.since ? Number(args.since) : 0
1661
+ });
1339
1662
  }
1340
- /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */
1341
- get onopen() {
1342
- return __privateGet(this, _onOpen);
1663
+ });
1664
+
1665
+ // src/commands/channel/read.ts
1666
+ var readCommand = defineCommand({
1667
+ meta: { name: "read", description: "poll channel messages once (curl-style, since a cursor)" },
1668
+ args: {
1669
+ channel: { type: "positional", required: true, description: "channel id" },
1670
+ token: { type: "string", required: true, description: "channel token" },
1671
+ since: { type: "string", description: "only messages after this seq (default 0)" },
1672
+ from: { type: "string", description: "skip messages from this name (no echo)" },
1673
+ for: { type: "string", description: "only broadcasts + messages addressed to this name" },
1674
+ ...common
1675
+ },
1676
+ run: async ({ args }) => {
1677
+ const url = resolveBaseUrl(args.url);
1678
+ const msgs = await channelClient(url).read(args.channel, args.token, {
1679
+ since: args.since ? Number(args.since) : 0,
1680
+ for: args.for
1681
+ });
1682
+ const out = args.from ? msgs.filter((m) => m.from !== args.from) : msgs;
1683
+ if (args.json) console.log(toJson(out));
1684
+ else console.log(out.length ? out.map((m) => `${m.seq} ${m.from} ${m.text}`).join("\n") : "(none)");
1343
1685
  }
1344
- set onopen(value) {
1345
- __privateSet(this, _onOpen, value);
1686
+ });
1687
+
1688
+ // src/commands/channel/send.ts
1689
+ import { readFileSync as readFileSync2 } from "node:fs";
1690
+ var sendCommand = defineCommand({
1691
+ meta: { name: "send", description: "post one message to a channel" },
1692
+ args: {
1693
+ channel: { type: "positional", required: true, description: "channel id" },
1694
+ text: { type: "positional", required: false, description: "message text (short one-liner)" },
1695
+ token: { type: "string", required: true, description: "channel token" },
1696
+ from: { type: "string", description: "your participant name (default: peer)" },
1697
+ to: { type: "string", description: "direct it at these names (comma-separated); omit = broadcast" },
1698
+ file: { type: "string", description: "read message body from this file (for large content)" },
1699
+ ...common
1700
+ },
1701
+ run: async ({ args }) => {
1702
+ const url = resolveBaseUrl(args.url);
1703
+ let text = args.text;
1704
+ if (text === void 0 && args.file) text = readFileSync2(args.file, "utf8");
1705
+ if (text === void 0 && !process.stdin.isTTY) text = await readStdin();
1706
+ if (text === void 0 || text === "")
1707
+ throw new Error("nothing to send: provide text, --file <path>, or pipe stdin");
1708
+ const msg = await channelClient(url).send(args.channel, args.token, {
1709
+ from: args.from ?? "peer",
1710
+ text,
1711
+ to: splitCsv(args.to)
1712
+ });
1713
+ if (args.json) console.log(toJson(msg));
1714
+ else console.log(`sent (seq ${msg.seq})`);
1346
1715
  }
1347
- addEventListener(type, listener, options) {
1348
- const listen = listener;
1349
- super.addEventListener(type, listen, options);
1716
+ });
1717
+
1718
+ // src/commands/channel.ts
1719
+ var channel = defineCommand({
1720
+ meta: {
1721
+ name: "channel",
1722
+ description: "multi-agent chat room: create / about / update / help / join / members / send / read / listen / close"
1723
+ },
1724
+ subCommands: {
1725
+ create: createCommand,
1726
+ about: aboutCommand,
1727
+ update: updateCommand,
1728
+ help: helpCommand,
1729
+ join: joinCommand,
1730
+ members: membersCommand,
1731
+ send: sendCommand,
1732
+ read: readCommand,
1733
+ listen: listenCommand,
1734
+ close: closeCommand
1735
+ }
1736
+ });
1737
+
1738
+ // src/commands/init.ts
1739
+ import { existsSync as existsSync2 } from "node:fs";
1740
+ import * as readline from "node:readline/promises";
1741
+ var promptPick = async (label, items) => {
1742
+ if (items.length === 0) throw new Error(`no ${label}s found on server`);
1743
+ console.log(`${label}s:`);
1744
+ items.forEach((it, i) => {
1745
+ console.log(` [${i + 1}] #${it.id} ${it.name}`);
1746
+ });
1747
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1748
+ try {
1749
+ const ans = (await rl.question(`pick a ${label} (1-${items.length}): `)).trim();
1750
+ const idx = Number(ans) - 1;
1751
+ const picked = items[idx];
1752
+ if (!picked) throw new Error(`invalid choice: ${ans}`);
1753
+ return picked;
1754
+ } finally {
1755
+ rl.close();
1350
1756
  }
1351
- removeEventListener(type, listener, options) {
1352
- const listen = listener;
1353
- super.removeEventListener(type, listen, options);
1757
+ };
1758
+ var init = defineCommand({
1759
+ meta: {
1760
+ name: "init",
1761
+ description: `write ${PROJECT_CONFIG_NAME} in the current directory (local state, gitignored)`
1762
+ },
1763
+ args: {
1764
+ workspace: { type: "string", description: "workspace id (skip prompt)" },
1765
+ project: { type: "string", description: "project id (skip prompt)" },
1766
+ force: { type: "boolean", description: `overwrite existing ${PROJECT_CONFIG_NAME}` },
1767
+ ...common
1768
+ },
1769
+ run: async ({ args }) => {
1770
+ const server = resolveBaseUrl(args.url);
1771
+ const cfgPath = projectConfigPath();
1772
+ if (existsSync2(cfgPath) && !args.force)
1773
+ throw new Error(`${PROJECT_CONFIG_NAME} already exists. use --force to overwrite.`);
1774
+ const c = createClient(server);
1775
+ let project2;
1776
+ let workspace2;
1777
+ if (args.project) {
1778
+ project2 = await c.projects.get(Number(args.project));
1779
+ workspace2 = await c.workspaces.get(project2.workspaceId);
1780
+ } else {
1781
+ const wsList = await c.workspaces.list();
1782
+ workspace2 = args.workspace ? wsList.find((w) => w.id === Number(args.workspace)) ?? (() => {
1783
+ throw new Error(`workspace ${args.workspace} not found`);
1784
+ })() : await promptPick("workspace", wsList);
1785
+ const projects = await c.projects.listByWorkspace(workspace2.id);
1786
+ project2 = await promptPick("project", projects);
1787
+ }
1788
+ const cfg = {
1789
+ server,
1790
+ workspace: workspace2.id,
1791
+ project: project2.id,
1792
+ name: project2.name
1793
+ };
1794
+ saveProjectConfig(cfgPath, cfg);
1795
+ console.log(`wrote ${cfgPath}`);
1796
+ console.log(` server: ${server}`);
1797
+ console.log(` workspace: #${workspace2.id} ${workspace2.name}`);
1798
+ console.log(` project: #${project2.id} ${project2.name}`);
1354
1799
  }
1355
- /**
1356
- * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED.
1357
- *
1358
- * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)
1359
- *
1360
- * @public
1361
- */
1362
- close() {
1363
- __privateGet(this, _reconnectTimer) && clearTimeout(__privateGet(this, _reconnectTimer)), __privateGet(this, _readyState) !== this.CLOSED && (__privateGet(this, _controller) && __privateGet(this, _controller).abort(), __privateSet(this, _readyState, this.CLOSED), __privateSet(this, _controller, void 0));
1800
+ });
1801
+
1802
+ // src/commands/project.ts
1803
+ var createProject = (c, input, json) => c.projects.create(input).then((p) => renderOne(p, fmtProject, json));
1804
+ var listProjects = (c, workspaceId, json) => c.projects.listByWorkspace(workspaceId).then((ps) => renderList(ps, fmtProject, json));
1805
+ var getProject = (c, id, json) => c.projects.get(id).then((p) => renderOne(p, fmtProject, json));
1806
+ var updateProject = (c, id, patch2, json) => c.projects.update(id, patch2).then((p) => renderOne(p, fmtProject, json));
1807
+ var removeProject = (c, id, json) => c.projects.remove(id).then(() => removed("project", id, json));
1808
+ var project = defineCommand({
1809
+ meta: { name: "project", description: "manage projects" },
1810
+ subCommands: {
1811
+ create: defineCommand({
1812
+ meta: { name: "create", description: "create a project" },
1813
+ args: {
1814
+ name: { type: "positional", required: true },
1815
+ workspace: { type: "string", description: "workspace id (overrides .baton.json)" },
1816
+ desc: { type: "string", description: "description" },
1817
+ ...common
1818
+ },
1819
+ run: async ({ args }) => {
1820
+ const out = await createProject(
1821
+ clientFor(args),
1822
+ { workspaceId: resolveWorkspaceId(args), name: args.name, description: args.desc },
1823
+ Boolean(args.json)
1824
+ );
1825
+ console.log(out);
1826
+ }
1827
+ }),
1828
+ ls: defineCommand({
1829
+ meta: { name: "ls", description: "list projects in a workspace" },
1830
+ args: {
1831
+ workspace: { type: "string", description: "workspace id (overrides .baton.json)" },
1832
+ ...common
1833
+ },
1834
+ run: async ({ args }) => {
1835
+ console.log(
1836
+ await listProjects(clientFor(args), resolveWorkspaceId(args), Boolean(args.json))
1837
+ );
1838
+ }
1839
+ }),
1840
+ get: defineCommand({
1841
+ meta: { name: "get", description: "get a project" },
1842
+ args: { id: { type: "positional", required: true }, ...common },
1843
+ run: async ({ args }) => {
1844
+ console.log(await getProject(clientFor(args), Number(args.id), Boolean(args.json)));
1845
+ }
1846
+ }),
1847
+ update: defineCommand({
1848
+ meta: { name: "update", description: "rename a project / set its description" },
1849
+ args: {
1850
+ id: { type: "positional", required: true },
1851
+ name: { type: "string", description: "new name" },
1852
+ desc: { type: "string", description: "new description" },
1853
+ ...common
1854
+ },
1855
+ run: async ({ args }) => {
1856
+ const patch2 = {
1857
+ ...args.name !== void 0 ? { name: args.name } : {},
1858
+ ...args.desc !== void 0 ? { description: args.desc } : {}
1859
+ };
1860
+ if (Object.keys(patch2).length === 0) throw new Error("pass --name and/or --desc");
1861
+ console.log(
1862
+ await updateProject(clientFor(args), Number(args.id), patch2, Boolean(args.json))
1863
+ );
1864
+ }
1865
+ }),
1866
+ rm: defineCommand({
1867
+ meta: { name: "rm", description: "delete a project" },
1868
+ args: { id: { type: "positional", required: true }, ...common },
1869
+ run: async ({ args }) => {
1870
+ console.log(await removeProject(clientFor(args), Number(args.id), Boolean(args.json)));
1871
+ }
1872
+ })
1364
1873
  }
1365
- };
1366
- _readyState = /* @__PURE__ */ new WeakMap(), _url = /* @__PURE__ */ new WeakMap(), _redirectUrl = /* @__PURE__ */ new WeakMap(), _withCredentials = /* @__PURE__ */ new WeakMap(), _fetch = /* @__PURE__ */ new WeakMap(), _reconnectInterval = /* @__PURE__ */ new WeakMap(), _reconnectTimer = /* @__PURE__ */ new WeakMap(), _lastEventId = /* @__PURE__ */ new WeakMap(), _controller = /* @__PURE__ */ new WeakMap(), _parser = /* @__PURE__ */ new WeakMap(), _onError = /* @__PURE__ */ new WeakMap(), _onMessage = /* @__PURE__ */ new WeakMap(), _onOpen = /* @__PURE__ */ new WeakMap(), _EventSource_instances = /* @__PURE__ */ new WeakSet(), /**
1367
- * Connect to the given URL and start receiving events
1368
- *
1369
- * @internal
1370
- */
1371
- connect_fn = function() {
1372
- __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _controller, new AbortController()), __privateGet(this, _fetch)(__privateGet(this, _url), __privateMethod(this, _EventSource_instances, getRequestOptions_fn).call(this)).then(__privateGet(this, _onFetchResponse)).catch(__privateGet(this, _onFetchError));
1373
- }, _onFetchResponse = /* @__PURE__ */ new WeakMap(), _onFetchError = /* @__PURE__ */ new WeakMap(), /**
1374
- * Get request options for the `fetch()` request
1375
- *
1376
- * @returns The request options
1377
- * @internal
1378
- */
1379
- getRequestOptions_fn = function() {
1380
- var _a;
1381
- const init2 = {
1382
- // [spec] Let `corsAttributeState` be `Anonymous`…
1383
- // [spec] …will have their mode set to "cors"…
1384
- mode: "cors",
1385
- redirect: "follow",
1386
- headers: { Accept: "text/event-stream", ...__privateGet(this, _lastEventId) ? { "Last-Event-ID": __privateGet(this, _lastEventId) } : void 0 },
1387
- cache: "no-store",
1388
- signal: (_a = __privateGet(this, _controller)) == null ? void 0 : _a.signal
1389
- };
1390
- return "window" in globalThis && (init2.credentials = this.withCredentials ? "include" : "same-origin"), init2;
1391
- }, _onEvent = /* @__PURE__ */ new WeakMap(), _onRetryChange = /* @__PURE__ */ new WeakMap(), /**
1392
- * Handles the process referred to in the EventSource specification as "failing a connection".
1393
- *
1394
- * @param error - The error causing the connection to fail
1395
- * @param code - The HTTP status code, if available
1396
- * @internal
1397
- */
1398
- failConnection_fn = function(message, code) {
1399
- var _a;
1400
- __privateGet(this, _readyState) !== this.CLOSED && __privateSet(this, _readyState, this.CLOSED);
1401
- const errorEvent = new ErrorEvent("error", { code, message });
1402
- (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent);
1403
- }, /**
1404
- * Schedules a reconnection attempt against the EventSource endpoint.
1405
- *
1406
- * @param message - The error causing the connection to fail
1407
- * @param code - The HTTP status code, if available
1408
- * @internal
1409
- */
1410
- scheduleReconnect_fn = function(message, code) {
1411
- var _a;
1412
- if (__privateGet(this, _readyState) === this.CLOSED)
1413
- return;
1414
- __privateSet(this, _readyState, this.CONNECTING);
1415
- const errorEvent = new ErrorEvent("error", { code, message });
1416
- (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent), __privateSet(this, _reconnectTimer, setTimeout(__privateGet(this, _reconnect), __privateGet(this, _reconnectInterval)));
1417
- }, _reconnect = /* @__PURE__ */ new WeakMap(), /**
1418
- * ReadyState representing an EventSource currently trying to connect
1419
- *
1420
- * @public
1421
- */
1422
- EventSource.CONNECTING = 0, /**
1423
- * ReadyState representing an EventSource connection that is open (eg connected)
1424
- *
1425
- * @public
1426
- */
1427
- EventSource.OPEN = 1, /**
1428
- * ReadyState representing an EventSource connection that is closed (eg disconnected)
1429
- *
1430
- * @public
1431
- */
1432
- EventSource.CLOSED = 2;
1433
- Object.defineProperty(EventSource, /* @__PURE__ */ Symbol.for("eventsource.supports-fetch-override"), {
1434
- value: true,
1435
- writable: false,
1436
- configurable: false,
1437
- enumerable: false
1438
1874
  });
1439
- function getBaseURL() {
1440
- const doc = "document" in globalThis ? globalThis.document : void 0;
1441
- return doc && typeof doc == "object" && "baseURI" in doc && typeof doc.baseURI == "string" ? doc.baseURI : void 0;
1442
- }
1875
+
1876
+ // src/commands/relay.ts
1877
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
1878
+ import { tmpdir as tmpdir2 } from "node:os";
1879
+ import { join as join3 } from "node:path";
1443
1880
 
1444
1881
  // src/client/relay.ts
1445
1882
  var relayClient = (baseUrl) => {
@@ -1485,13 +1922,13 @@ var relayClient = (baseUrl) => {
1485
1922
  };
1486
1923
 
1487
1924
  // src/commands/relay.ts
1488
- var PREVIEW = 280;
1489
- var readStdin = async () => {
1925
+ var PREVIEW2 = 280;
1926
+ var readStdin2 = async () => {
1490
1927
  const chunks = [];
1491
1928
  for await (const c of process.stdin) chunks.push(c);
1492
1929
  return Buffer.concat(chunks).toString("utf8");
1493
1930
  };
1494
- var inviteBlock = (url, channelId, token) => [
1931
+ var inviteBlock2 = (url, channelId, token) => [
1495
1932
  "\u2500\u2500 Copy everything below to your peer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1496
1933
  "You're invited to a live Claude\u2194Claude channel. Use the `hotline` skill in",
1497
1934
  "join mode, or run these two commands directly (pick your own NAME):",
@@ -1517,10 +1954,10 @@ var hostCommand = defineCommand({
1517
1954
  }
1518
1955
  console.log(`channel ${channelId} opened
1519
1956
  `);
1520
- console.log(inviteBlock(url, channelId, token));
1957
+ console.log(inviteBlock2(url, channelId, token));
1521
1958
  }
1522
1959
  });
1523
- var sendCommand = defineCommand({
1960
+ var sendCommand2 = defineCommand({
1524
1961
  meta: { name: "send", description: "post one message to a relay channel" },
1525
1962
  args: {
1526
1963
  channel: { type: "positional", required: true, description: "channel id" },
@@ -1533,8 +1970,8 @@ var sendCommand = defineCommand({
1533
1970
  run: async ({ args }) => {
1534
1971
  const url = resolveBaseUrl(args.url);
1535
1972
  let text = args.text;
1536
- if (text === void 0 && args.file) text = readFileSync2(args.file, "utf8");
1537
- if (text === void 0 && !process.stdin.isTTY) text = await readStdin();
1973
+ if (text === void 0 && args.file) text = readFileSync3(args.file, "utf8");
1974
+ if (text === void 0 && !process.stdin.isTTY) text = await readStdin2();
1538
1975
  if (text === void 0 || text === "")
1539
1976
  throw new Error("nothing to send: provide text, --file <path>, or pipe stdin");
1540
1977
  const msg = await relayClient(url).send(args.channel, args.token, {
@@ -1545,7 +1982,7 @@ var sendCommand = defineCommand({
1545
1982
  else console.log(`sent (seq ${msg.seq})`);
1546
1983
  }
1547
1984
  });
1548
- var listenCommand = defineCommand({
1985
+ var listenCommand2 = defineCommand({
1549
1986
  meta: {
1550
1987
  name: "listen",
1551
1988
  description: "stream a channel: one JSON line per peer message (for a background subscriber)"
@@ -1567,13 +2004,13 @@ var listenCommand = defineCommand({
1567
2004
  process.stdout.write(`${JSON.stringify(o)}
1568
2005
  `);
1569
2006
  };
1570
- const spillDir = join2(tmpdir(), "hotline", args.channel);
1571
- mkdirSync(spillDir, { recursive: true });
2007
+ const spillDir = join3(tmpdir2(), "hotline", args.channel);
2008
+ mkdirSync2(spillDir, { recursive: true });
1572
2009
  const surface = (m) => {
1573
2010
  const text = typeof m.text === "string" ? m.text : "";
1574
- const file = join2(spillDir, `${m.seq}.txt`);
1575
- writeFileSync2(file, text);
1576
- const truncated = text.length > PREVIEW;
2011
+ const file = join3(spillDir, `${m.seq}.txt`);
2012
+ writeFileSync3(file, text);
2013
+ const truncated = text.length > PREVIEW2;
1577
2014
  emit({
1578
2015
  type: "relay.message",
1579
2016
  seq: m.seq,
@@ -1581,11 +2018,11 @@ var listenCommand = defineCommand({
1581
2018
  ts: m.ts,
1582
2019
  chars: text.length,
1583
2020
  truncated,
1584
- preview: truncated ? `${text.slice(0, PREVIEW)} \u2026` : text,
2021
+ preview: truncated ? `${text.slice(0, PREVIEW2)} \u2026` : text,
1585
2022
  file
1586
2023
  });
1587
2024
  };
1588
- const describe = (e) => {
2025
+ const describe2 = (e) => {
1589
2026
  const ev = e;
1590
2027
  return ev?.code ? `${ev.code}${ev.message ? ` ${ev.message}` : ""}` : String(e);
1591
2028
  };
@@ -1595,7 +2032,7 @@ var listenCommand = defineCommand({
1595
2032
  onMessage: (m) => {
1596
2033
  if (m.from !== me) surface(m);
1597
2034
  },
1598
- onError: (e) => emit({ type: "relay.error", error: describe(e) })
2035
+ onError: (e) => emit({ type: "relay.error", error: describe2(e) })
1599
2036
  });
1600
2037
  emit({ type: "relay.listening", channel: args.channel, from: me, url });
1601
2038
  await new Promise((resolve2) => {
@@ -1610,7 +2047,7 @@ var listenCommand = defineCommand({
1610
2047
  });
1611
2048
  var relay = defineCommand({
1612
2049
  meta: { name: "relay", description: "Claude\u2194Claude live channel: host / listen / send" },
1613
- subCommands: { host: hostCommand, listen: listenCommand, send: sendCommand }
2050
+ subCommands: { host: hostCommand, listen: listenCommand2, send: sendCommand2 }
1614
2051
  });
1615
2052
 
1616
2053
  // src/commands/requirement.ts
@@ -1727,8 +2164,8 @@ var requirement = defineCommand({
1727
2164
 
1728
2165
  // src/commands/session/shared.ts
1729
2166
  import { homedir } from "node:os";
1730
- import { join as join3 } from "node:path";
1731
- var defaultWorktreeDir = (env = process.env) => env.BATON_WORKTREE_DIR ?? join3(env.XDG_DATA_HOME ?? join3(env.HOME ?? homedir(), ".local/share"), "baton", "worktrees");
2167
+ import { join as join4 } from "node:path";
2168
+ var defaultWorktreeDir = (env = process.env) => env.BATON_WORKTREE_DIR ?? join4(env.XDG_DATA_HOME ?? join4(env.HOME ?? homedir(), ".local/share"), "baton", "worktrees");
1732
2169
  var slug = (s) => s.toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
1733
2170
  var resolveSession = async (client, projectId, handle) => {
1734
2171
  const asInt = Number(handle);
@@ -1897,32 +2334,6 @@ var sessionRmCommand = defineCommand({
1897
2334
  import { spawnSync } from "node:child_process";
1898
2335
  import { query } from "@anthropic-ai/claude-agent-sdk";
1899
2336
 
1900
- // ../shared/src/domain/attachment.ts
1901
- var isImageAttachment = (att) => att.contentType.startsWith("image/");
1902
- var labelAttachments = (attachments) => {
1903
- const counters = { image: 0, file: 0 };
1904
- return attachments.map((att) => {
1905
- const kind = isImageAttachment(att) ? "image" : "file";
1906
- counters[kind] += 1;
1907
- return `${kind}-${counters[kind]}`;
1908
- });
1909
- };
1910
-
1911
- // ../shared/src/domain/session-event.ts
1912
- var startedMessageIds = (events) => {
1913
- const ids = /* @__PURE__ */ new Set();
1914
- for (const e of events) {
1915
- if (e.type !== "turn_start") continue;
1916
- const id = e.payload?.messageId;
1917
- if (typeof id === "number") ids.add(id);
1918
- }
1919
- return ids;
1920
- };
1921
- var unstartedUserMessages = (events) => {
1922
- const started = startedMessageIds(events);
1923
- return events.filter((e) => e.type === "user_message" && !started.has(e.id));
1924
- };
1925
-
1926
2337
  // src/session/runner/log.ts
1927
2338
  var maskedEnvKeys = (env) => env ? Object.keys(env).sort().join(", ") : "(none)";
1928
2339
 
@@ -1939,14 +2350,14 @@ var additionalDirs = () => {
1939
2350
  };
1940
2351
 
1941
2352
  // src/session/runner/transcript.ts
1942
- import { readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
2353
+ import { readdirSync, readFileSync as readFileSync4, statSync } from "node:fs";
1943
2354
  import { homedir as homedir2 } from "node:os";
1944
- import { join as join4 } from "node:path";
2355
+ import { join as join5 } from "node:path";
1945
2356
  var findTranscriptPath = (agentSessionId) => {
1946
- const root = join4(homedir2(), ".claude", "projects");
2357
+ const root = join5(homedir2(), ".claude", "projects");
1947
2358
  try {
1948
2359
  for (const dir of readdirSync(root)) {
1949
- const candidate = join4(root, dir, `${agentSessionId}.jsonl`);
2360
+ const candidate = join5(root, dir, `${agentSessionId}.jsonl`);
1950
2361
  try {
1951
2362
  if (statSync(candidate).isFile()) return candidate;
1952
2363
  } catch {
@@ -1992,7 +2403,7 @@ var readFirstExchange = (agentSessionId) => {
1992
2403
  const path = findTranscriptPath(agentSessionId);
1993
2404
  if (!path) return null;
1994
2405
  try {
1995
- return parseFirstExchange(readFileSync3(path, "utf8"));
2406
+ return parseFirstExchange(readFileSync4(path, "utf8"));
1996
2407
  } catch {
1997
2408
  return null;
1998
2409
  }
@@ -2001,7 +2412,7 @@ var readFirstExchange = (agentSessionId) => {
2001
2412
  // src/session/runner/attachments.ts
2002
2413
  import { createWriteStream } from "node:fs";
2003
2414
  import { mkdir, writeFile } from "node:fs/promises";
2004
- import { join as join5 } from "node:path";
2415
+ import { join as join6 } from "node:path";
2005
2416
  import { Readable } from "node:stream";
2006
2417
  import { pipeline } from "node:stream/promises";
2007
2418
  var ATTACH_DIR = "attachments";
@@ -2024,16 +2435,16 @@ var dedupe = (name, taken) => {
2024
2435
  };
2025
2436
  var materializeAttachments = async (opts) => {
2026
2437
  const fetchImpl = opts.fetchImpl ?? fetch;
2027
- const dir = join5(opts.worktreePath, ATTACH_DIR);
2438
+ const dir = join6(opts.worktreePath, ATTACH_DIR);
2028
2439
  await mkdir(dir, { recursive: true });
2029
- await writeFile(join5(dir, ".gitignore"), "*\n", "utf8");
2440
+ await writeFile(join6(dir, ".gitignore"), "*\n", "utf8");
2030
2441
  const taken = /* @__PURE__ */ new Set([".gitignore"]);
2031
2442
  const relPaths = [];
2032
2443
  for (const att of opts.attachments) {
2033
2444
  const name = dedupe(att.filename, taken);
2034
2445
  const res = await fetchImpl(`${opts.serverBase}${att.url}`);
2035
2446
  if (!res.ok || !res.body) throw new Error(`attachment ${att.id} \u2192 ${res.status}`);
2036
- await pipeline(Readable.fromWeb(res.body), createWriteStream(join5(dir, name)));
2447
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(join6(dir, name)));
2037
2448
  relPaths.push(`${ATTACH_DIR}/${name}`);
2038
2449
  }
2039
2450
  return relPaths;
@@ -2669,7 +3080,7 @@ import { hostname as osHostname } from "node:os";
2669
3080
  import { spawn } from "node:child_process";
2670
3081
  import { randomUUID } from "node:crypto";
2671
3082
  import { existsSync as existsSync5 } from "node:fs";
2672
- import { dirname as dirname2, join as join6 } from "node:path";
3083
+ import { dirname as dirname2, join as join7 } from "node:path";
2673
3084
  import { fileURLToPath } from "node:url";
2674
3085
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
2675
3086
 
@@ -2759,7 +3170,7 @@ var generateTitle = async (input) => {
2759
3170
 
2760
3171
  // src/session/worktree.ts
2761
3172
  import { execFileSync, spawnSync as spawnSync2 } from "node:child_process";
2762
- import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4 } from "node:fs";
3173
+ import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5 } from "node:fs";
2763
3174
  import { dirname, isAbsolute as isAbsolute2, resolve } from "node:path";
2764
3175
  var createWorktree = (input) => {
2765
3176
  if (existsSync4(input.worktreePath))
@@ -2772,7 +3183,7 @@ var createWorktree = (input) => {
2772
3183
  throw new Error(
2773
3184
  `repo has no commits (unborn HEAD) \u2014 create an initial commit before starting sessions`
2774
3185
  );
2775
- mkdirSync2(dirname(input.worktreePath), { recursive: true });
3186
+ mkdirSync3(dirname(input.worktreePath), { recursive: true });
2776
3187
  const branch = `baton/${input.sessionCode}`;
2777
3188
  const r = spawnSync2(
2778
3189
  "git",
@@ -2800,9 +3211,9 @@ var ensureExcluded = (repo, pattern) => {
2800
3211
  if (r.status !== 0) return;
2801
3212
  const rel = r.stdout.trim();
2802
3213
  const path = isAbsolute2(rel) ? rel : resolve(repo, rel);
2803
- const current = existsSync4(path) ? readFileSync4(path, "utf8") : "";
3214
+ const current = existsSync4(path) ? readFileSync5(path, "utf8") : "";
2804
3215
  if (current.split("\n").includes(pattern)) return;
2805
- mkdirSync2(dirname(path), { recursive: true });
3216
+ mkdirSync3(dirname(path), { recursive: true });
2806
3217
  const sep = current === "" || current.endsWith("\n") ? "" : "\n";
2807
3218
  appendFileSync(path, `${sep}${pattern}
2808
3219
  `, "utf8");
@@ -2826,7 +3237,7 @@ var streamWedged = (isOpen, okAt, now, thresholdMs) => !isOpen && stale(okAt, no
2826
3237
  // src/worker/daemon.ts
2827
3238
  var binPath = () => {
2828
3239
  const here = dirname2(fileURLToPath(import.meta.url));
2829
- const devShim = join6(here, "..", "..", "bin", "baton.mjs");
3240
+ const devShim = join7(here, "..", "..", "bin", "baton.mjs");
2830
3241
  return existsSync5(devShim) ? devShim : fileURLToPath(import.meta.url);
2831
3242
  };
2832
3243
  var runWorkerDaemon = async (cfg, client, signal) => {
@@ -2896,7 +3307,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
2896
3307
  let worktreePath = session2.worktreePath;
2897
3308
  if (!session2.agentSessionId || !worktreePath) {
2898
3309
  const agentSessionId = randomUUID();
2899
- worktreePath = join6(worktreeDir, slug(`${name}-${agentSessionId.slice(0, 8)}`));
3310
+ worktreePath = join7(worktreeDir, slug(`${name}-${agentSessionId.slice(0, 8)}`));
2900
3311
  createWorktree({
2901
3312
  repo,
2902
3313
  worktreePath,
@@ -2907,7 +3318,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
2907
3318
  log(`materialized session #${sessionId} \u2192 ${worktreePath}`);
2908
3319
  }
2909
3320
  ensureExcluded(repo, PROJECT_CONFIG_NAME);
2910
- saveProjectConfig(join6(worktreePath, PROJECT_CONFIG_NAME), worktreeConfig(cfg));
3321
+ saveProjectConfig(join7(worktreePath, PROJECT_CONFIG_NAME), worktreeConfig(cfg));
2911
3322
  spawnChild(sessionId, worktreePath);
2912
3323
  };
2913
3324
  const onStop = (sessionId) => {
@@ -3010,18 +3421,18 @@ var runWorkerDaemon = async (cfg, client, signal) => {
3010
3421
 
3011
3422
  // src/worker/machine-id.ts
3012
3423
  import { randomUUID as randomUUID2 } from "node:crypto";
3013
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
3424
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
3014
3425
  import { homedir as homedir3 } from "node:os";
3015
- import { dirname as dirname3, join as join7 } from "node:path";
3016
- var machineIdPath = (env = process.env) => join7(env.XDG_DATA_HOME ?? join7(env.HOME ?? homedir3(), ".local/share"), "baton", "machine-id");
3426
+ import { dirname as dirname3, join as join8 } from "node:path";
3427
+ var machineIdPath = (env = process.env) => join8(env.XDG_DATA_HOME ?? join8(env.HOME ?? homedir3(), ".local/share"), "baton", "machine-id");
3017
3428
  var readOrCreateMachineId = (path = machineIdPath()) => {
3018
3429
  if (existsSync6(path)) {
3019
- const s = readFileSync5(path, "utf8").trim();
3430
+ const s = readFileSync6(path, "utf8").trim();
3020
3431
  if (s) return s;
3021
3432
  }
3022
3433
  const id = randomUUID2();
3023
- mkdirSync3(dirname3(path), { recursive: true });
3024
- writeFileSync3(path, `${id}
3434
+ mkdirSync4(dirname3(path), { recursive: true });
3435
+ writeFileSync4(path, `${id}
3025
3436
  `, "utf8");
3026
3437
  return id;
3027
3438
  };
@@ -3290,6 +3701,7 @@ var main = defineCommand({
3290
3701
  worker,
3291
3702
  session,
3292
3703
  relay,
3704
+ channel,
3293
3705
  workspace,
3294
3706
  project,
3295
3707
  requirement,