@lesscap/baton-cli 0.2.12 → 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.
- package/LICENSE +21 -0
- package/dist/baton.mjs +1742 -1106
- package/package.json +10 -11
package/dist/baton.mjs
CHANGED
|
@@ -467,234 +467,590 @@ function _getBuiltinFlags(long, short, userNames, userAliases) {
|
|
|
467
467
|
return [`--${long}`, `-${short}`];
|
|
468
468
|
}
|
|
469
469
|
|
|
470
|
-
//
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
var authHeaders = {};
|
|
476
|
-
var loginGate = null;
|
|
477
|
-
var setAuthHeaders = (h) => {
|
|
478
|
-
authHeaders = h;
|
|
479
|
-
};
|
|
480
|
-
var setLoginGate = (p) => {
|
|
481
|
-
loginGate = p;
|
|
482
|
-
};
|
|
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();
|
|
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
|
+
}
|
|
494
475
|
};
|
|
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
|
+
}
|
|
495
645
|
|
|
496
|
-
//
|
|
497
|
-
var
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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;
|
|
505
658
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
+
}
|
|
688
|
+
};
|
|
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);
|
|
708
|
+
};
|
|
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 || ""
|
|
515
778
|
});
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
})
|
|
520
|
-
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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));
|
|
869
|
+
}
|
|
543
870
|
};
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
+
}
|
|
948
|
+
|
|
949
|
+
// src/client/channel.ts
|
|
950
|
+
var channelClient = (baseUrl) => {
|
|
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)}`);
|
|
955
|
+
return {
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
-
|
|
607
|
-
|
|
1012
|
+
headers: sendJson(token),
|
|
1013
|
+
body: JSON.stringify(msg)
|
|
608
1014
|
});
|
|
609
|
-
if (!res.ok) throw new Error(`POST
|
|
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
|
-
|
|
613
|
-
|
|
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
|
-
|
|
646
|
-
return
|
|
1024
|
+
if (!res.ok) throw new Error(`GET messages \u2192 ${res.status}: ${await res.text()}`);
|
|
1025
|
+
return (await res.json()).messages;
|
|
647
1026
|
},
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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
|
-
};
|
|
696
|
-
};
|
|
697
|
-
|
|
698
1054
|
// src/project-config.ts
|
|
699
1055
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
700
1056
|
import { join } from "node:path";
|
|
@@ -747,121 +1103,19 @@ var worktreeConfig = (cfg) => ({
|
|
|
747
1103
|
worker: { id: cfg.workerId, name: cfg.name, machineId: cfg.machineId, apiToken: cfg.apiToken }
|
|
748
1104
|
});
|
|
749
1105
|
|
|
750
|
-
// src/config.ts
|
|
751
|
-
var resolveBaseUrl = (urlArg, env = process.env) => {
|
|
752
|
-
if (urlArg) return urlArg;
|
|
753
|
-
const cfg = loadProjectConfigOrNull(projectConfigPath());
|
|
754
|
-
if (cfg?.server) return cfg.server;
|
|
755
|
-
return env.BATON_URL ?? "http://localhost:3280";
|
|
756
|
-
};
|
|
757
|
-
var resolveBaseUrlOrNull = (urlArg, env = process.env) => {
|
|
758
|
-
if (urlArg) return urlArg;
|
|
759
|
-
const cfg = loadProjectConfigOrNull(projectConfigPath());
|
|
760
|
-
if (cfg?.server) return cfg.server;
|
|
761
|
-
return env.BATON_URL ?? null;
|
|
762
|
-
};
|
|
763
|
-
|
|
764
|
-
// src/util.ts
|
|
765
|
-
var common = {
|
|
766
|
-
url: {
|
|
767
|
-
type: "string",
|
|
768
|
-
description: "baton server url (--url > .baton.json > BATON_URL > http://localhost:3280)"
|
|
769
|
-
},
|
|
770
|
-
json: { type: "boolean", description: "output JSON" }
|
|
771
|
-
};
|
|
772
|
-
var resolveAuth = (env, fileToken) => {
|
|
773
|
-
const envBearer = env.BATON_TOKEN ?? env.BATON_WORKER_TOKEN;
|
|
774
|
-
if (envBearer) return { bearer: envBearer };
|
|
775
|
-
if (env.BATON_USER && env.BATON_PASS) return "cookie";
|
|
776
|
-
return fileToken ? { bearer: fileToken } : void 0;
|
|
777
|
-
};
|
|
778
|
-
var clientFor = (args) => {
|
|
779
|
-
const baseUrl = resolveBaseUrl(args.url);
|
|
780
|
-
const fileToken = loadProjectConfigOrNull(projectConfigPath())?.worker?.apiToken;
|
|
781
|
-
const auth = resolveAuth(process.env, fileToken);
|
|
782
|
-
return createClient(baseUrl, auth && auth !== "cookie" ? auth : void 0);
|
|
783
|
-
};
|
|
784
|
-
var resolveProjectId = (args) => {
|
|
785
|
-
if (args.project !== void 0 && args.project !== "") return Number(args.project);
|
|
786
|
-
const cfg = loadProjectConfigOrNull(projectConfigPath());
|
|
787
|
-
if (cfg?.project !== void 0) return cfg.project;
|
|
788
|
-
throw new Error("no project in scope. pass --project <id> or run `baton init` in this directory.");
|
|
789
|
-
};
|
|
790
|
-
var resolveWorkspaceId = (args) => {
|
|
791
|
-
if (args.workspace !== void 0 && args.workspace !== "") return Number(args.workspace);
|
|
792
|
-
const cfg = loadProjectConfigOrNull(projectConfigPath());
|
|
793
|
-
if (cfg?.workspace !== void 0) return cfg.workspace;
|
|
794
|
-
throw new Error(
|
|
795
|
-
"no workspace in scope. pass --workspace <id> or run `baton init` in this directory."
|
|
796
|
-
);
|
|
797
|
-
};
|
|
798
|
-
var parseWorkerHandle = (handle) => {
|
|
799
|
-
const m = handle.trim().match(/^(?:[wW]-)?(\d+)$/);
|
|
800
|
-
return m ? Number(m[1]) : null;
|
|
801
|
-
};
|
|
802
|
-
var splitCsv = (s) => s ? s.split(",").map((x) => x.trim()).filter(Boolean) : void 0;
|
|
803
|
-
|
|
804
|
-
// src/commands/init.ts
|
|
805
|
-
var promptPick = async (label, items) => {
|
|
806
|
-
if (items.length === 0) throw new Error(`no ${label}s found on server`);
|
|
807
|
-
console.log(`${label}s:`);
|
|
808
|
-
items.forEach((it, i) => {
|
|
809
|
-
console.log(` [${i + 1}] #${it.id} ${it.name}`);
|
|
810
|
-
});
|
|
811
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
812
|
-
try {
|
|
813
|
-
const ans = (await rl.question(`pick a ${label} (1-${items.length}): `)).trim();
|
|
814
|
-
const idx = Number(ans) - 1;
|
|
815
|
-
const picked = items[idx];
|
|
816
|
-
if (!picked) throw new Error(`invalid choice: ${ans}`);
|
|
817
|
-
return picked;
|
|
818
|
-
} finally {
|
|
819
|
-
rl.close();
|
|
820
|
-
}
|
|
821
|
-
};
|
|
822
|
-
var init = defineCommand({
|
|
823
|
-
meta: {
|
|
824
|
-
name: "init",
|
|
825
|
-
description: `write ${PROJECT_CONFIG_NAME} in the current directory (local state, gitignored)`
|
|
826
|
-
},
|
|
827
|
-
args: {
|
|
828
|
-
workspace: { type: "string", description: "workspace id (skip prompt)" },
|
|
829
|
-
project: { type: "string", description: "project id (skip prompt)" },
|
|
830
|
-
force: { type: "boolean", description: `overwrite existing ${PROJECT_CONFIG_NAME}` },
|
|
831
|
-
...common
|
|
832
|
-
},
|
|
833
|
-
run: async ({ args }) => {
|
|
834
|
-
const server = resolveBaseUrl(args.url);
|
|
835
|
-
const cfgPath = projectConfigPath();
|
|
836
|
-
if (existsSync2(cfgPath) && !args.force)
|
|
837
|
-
throw new Error(`${PROJECT_CONFIG_NAME} already exists. use --force to overwrite.`);
|
|
838
|
-
const c = createClient(server);
|
|
839
|
-
let project2;
|
|
840
|
-
let workspace2;
|
|
841
|
-
if (args.project) {
|
|
842
|
-
project2 = await c.projects.get(Number(args.project));
|
|
843
|
-
workspace2 = await c.workspaces.get(project2.workspaceId);
|
|
844
|
-
} else {
|
|
845
|
-
const wsList = await c.workspaces.list();
|
|
846
|
-
workspace2 = args.workspace ? wsList.find((w) => w.id === Number(args.workspace)) ?? (() => {
|
|
847
|
-
throw new Error(`workspace ${args.workspace} not found`);
|
|
848
|
-
})() : await promptPick("workspace", wsList);
|
|
849
|
-
const projects = await c.projects.listByWorkspace(workspace2.id);
|
|
850
|
-
project2 = await promptPick("project", projects);
|
|
851
|
-
}
|
|
852
|
-
const cfg = {
|
|
853
|
-
server,
|
|
854
|
-
workspace: workspace2.id,
|
|
855
|
-
project: project2.id,
|
|
856
|
-
name: project2.name
|
|
857
|
-
};
|
|
858
|
-
saveProjectConfig(cfgPath, cfg);
|
|
859
|
-
console.log(`wrote ${cfgPath}`);
|
|
860
|
-
console.log(` server: ${server}`);
|
|
861
|
-
console.log(` workspace: #${workspace2.id} ${workspace2.name}`);
|
|
862
|
-
console.log(` project: #${project2.id} ${project2.name}`);
|
|
863
|
-
}
|
|
864
|
-
});
|
|
1106
|
+
// src/config.ts
|
|
1107
|
+
var resolveBaseUrl = (urlArg, env = process.env) => {
|
|
1108
|
+
if (urlArg) return urlArg;
|
|
1109
|
+
const cfg = loadProjectConfigOrNull(projectConfigPath());
|
|
1110
|
+
if (cfg?.server) return cfg.server;
|
|
1111
|
+
return env.BATON_URL ?? "http://localhost:3280";
|
|
1112
|
+
};
|
|
1113
|
+
var resolveBaseUrlOrNull = (urlArg, env = process.env) => {
|
|
1114
|
+
if (urlArg) return urlArg;
|
|
1115
|
+
const cfg = loadProjectConfigOrNull(projectConfigPath());
|
|
1116
|
+
if (cfg?.server) return cfg.server;
|
|
1117
|
+
return env.BATON_URL ?? null;
|
|
1118
|
+
};
|
|
865
1119
|
|
|
866
1120
|
// src/output.ts
|
|
867
1121
|
var toJson = (data) => JSON.stringify(data, null, 2);
|
|
@@ -882,838 +1136,1199 @@ var renderList = (items, fmt, json) => {
|
|
|
882
1136
|
};
|
|
883
1137
|
var removed = (kind, id, json) => json ? toJson({ ok: true, deleted: id }) : `deleted ${kind} ${id}`;
|
|
884
1138
|
|
|
885
|
-
// src/
|
|
886
|
-
var
|
|
887
|
-
var
|
|
888
|
-
var
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
var
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
},
|
|
917
|
-
run: async ({ args }) => {
|
|
918
|
-
console.log(
|
|
919
|
-
await listProjects(clientFor(args), resolveWorkspaceId(args), Boolean(args.json))
|
|
920
|
-
);
|
|
921
|
-
}
|
|
922
|
-
}),
|
|
923
|
-
get: defineCommand({
|
|
924
|
-
meta: { name: "get", description: "get a project" },
|
|
925
|
-
args: { id: { type: "positional", required: true }, ...common },
|
|
926
|
-
run: async ({ args }) => {
|
|
927
|
-
console.log(await getProject(clientFor(args), Number(args.id), Boolean(args.json)));
|
|
928
|
-
}
|
|
929
|
-
}),
|
|
930
|
-
update: defineCommand({
|
|
931
|
-
meta: { name: "update", description: "rename a project / set its description" },
|
|
932
|
-
args: {
|
|
933
|
-
id: { type: "positional", required: true },
|
|
934
|
-
name: { type: "string", description: "new name" },
|
|
935
|
-
desc: { type: "string", description: "new description" },
|
|
936
|
-
...common
|
|
937
|
-
},
|
|
938
|
-
run: async ({ args }) => {
|
|
939
|
-
const patch2 = {
|
|
940
|
-
...args.name !== void 0 ? { name: args.name } : {},
|
|
941
|
-
...args.desc !== void 0 ? { description: args.desc } : {}
|
|
942
|
-
};
|
|
943
|
-
if (Object.keys(patch2).length === 0) throw new Error("pass --name and/or --desc");
|
|
944
|
-
console.log(
|
|
945
|
-
await updateProject(clientFor(args), Number(args.id), patch2, Boolean(args.json))
|
|
946
|
-
);
|
|
947
|
-
}
|
|
948
|
-
}),
|
|
949
|
-
rm: defineCommand({
|
|
950
|
-
meta: { name: "rm", description: "delete a project" },
|
|
951
|
-
args: { id: { type: "positional", required: true }, ...common },
|
|
952
|
-
run: async ({ args }) => {
|
|
953
|
-
console.log(await removeProject(clientFor(args), Number(args.id), Boolean(args.json)));
|
|
954
|
-
}
|
|
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;
|
|
956
1170
|
}
|
|
957
|
-
|
|
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(() => {
|
|
1184
|
+
})
|
|
1185
|
+
);
|
|
1186
|
+
};
|
|
958
1187
|
|
|
959
|
-
// src/
|
|
960
|
-
var
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
const patch2 = {
|
|
1005
|
-
...args.title !== void 0 ? { title: args.title } : {},
|
|
1006
|
-
...args.body !== void 0 ? { body: args.body } : {}
|
|
1007
|
-
};
|
|
1008
|
-
if (Object.keys(patch2).length === 0) throw new Error("pass --title and/or --body");
|
|
1009
|
-
const c = clientFor(args);
|
|
1010
|
-
const id = await resolveByCode(c, resolveProjectId(args), args.code);
|
|
1011
|
-
console.log(await updateRequirement(c, id, patch2, Boolean(args.json)));
|
|
1012
|
-
}
|
|
1013
|
-
}),
|
|
1014
|
-
ls: defineCommand({
|
|
1015
|
-
meta: { name: "ls", description: "list requirements in a project" },
|
|
1016
|
-
args: {
|
|
1017
|
-
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
1018
|
-
...common
|
|
1019
|
-
},
|
|
1020
|
-
run: async ({ args }) => {
|
|
1021
|
-
console.log(
|
|
1022
|
-
await listRequirements(clientFor(args), resolveProjectId(args), Boolean(args.json))
|
|
1023
|
-
);
|
|
1024
|
-
}
|
|
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
|
+
};
|
|
1199
|
+
|
|
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;
|
|
1208
|
+
};
|
|
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}` }
|
|
1025
1233
|
}),
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
1031
|
-
...common
|
|
1032
|
-
},
|
|
1033
|
-
run: async ({ args }) => {
|
|
1034
|
-
const c = clientFor(args);
|
|
1035
|
-
const id = await resolveByCode(c, resolveProjectId(args), args.code);
|
|
1036
|
-
console.log(await getRequirement(c, id, Boolean(args.json)));
|
|
1037
|
-
}
|
|
1234
|
+
setStatus: (id, active, workerToken) => request(u(`/sessions/${id}/status`), {
|
|
1235
|
+
method: "POST",
|
|
1236
|
+
body: { active },
|
|
1237
|
+
headers: { authorization: `Bearer ${workerToken}` }
|
|
1038
1238
|
}),
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
status: { type: "positional", required: true },
|
|
1044
|
-
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
1045
|
-
...common
|
|
1046
|
-
},
|
|
1047
|
-
run: async ({ args }) => {
|
|
1048
|
-
const c = clientFor(args);
|
|
1049
|
-
const id = await resolveByCode(c, resolveProjectId(args), args.code);
|
|
1050
|
-
console.log(
|
|
1051
|
-
await setRequirementStatus(c, id, args.status, Boolean(args.json))
|
|
1052
|
-
);
|
|
1053
|
-
}
|
|
1239
|
+
setName: (id, name, workerToken) => request(u(`/sessions/${id}`), {
|
|
1240
|
+
method: "PATCH",
|
|
1241
|
+
body: { name },
|
|
1242
|
+
headers: { authorization: `Bearer ${workerToken}` }
|
|
1054
1243
|
}),
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
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 } : {}
|
|
1066
1262
|
}
|
|
1067
|
-
})
|
|
1068
|
-
|
|
1069
|
-
|
|
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" });
|
|
1279
|
+
}
|
|
1280
|
+
};
|
|
1281
|
+
};
|
|
1070
1282
|
|
|
1071
|
-
// src/
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1283
|
+
// src/client/tasks.ts
|
|
1284
|
+
var taskClient = (baseUrl) => {
|
|
1285
|
+
const u = (p) => `${baseUrl}${p}`;
|
|
1286
|
+
return {
|
|
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 } })
|
|
1296
|
+
};
|
|
1085
1297
|
};
|
|
1086
1298
|
|
|
1087
|
-
// src/
|
|
1088
|
-
var
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
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"
|
|
1309
|
+
});
|
|
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)
|
|
1104
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
|
+
};
|
|
1105
1363
|
|
|
1106
|
-
// src/
|
|
1107
|
-
var
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
description: "
|
|
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)"
|
|
1111
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)" },
|
|
1112
1407
|
args: {
|
|
1113
|
-
|
|
1114
|
-
|
|
1408
|
+
channel: { type: "positional", required: true, description: "channel id" },
|
|
1409
|
+
token: { type: "string", required: true, description: "channel token" },
|
|
1115
1410
|
...common
|
|
1116
1411
|
},
|
|
1117
1412
|
run: async ({ args }) => {
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
const s = await c.sessions.clear(handle.id);
|
|
1121
|
-
console.log(renderOne(s, fmtSession, Boolean(args.json)));
|
|
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}`);
|
|
1122
1415
|
}
|
|
1123
1416
|
});
|
|
1124
1417
|
|
|
1125
|
-
// src/commands/
|
|
1126
|
-
var
|
|
1127
|
-
|
|
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" },
|
|
1128
1439
|
args: {
|
|
1129
|
-
name: { type: "
|
|
1130
|
-
|
|
1131
|
-
type: "string",
|
|
1132
|
-
description: "worker handle: W-N or int id (default: this machine's worker)"
|
|
1133
|
-
},
|
|
1134
|
-
project: {
|
|
1135
|
-
type: "string",
|
|
1136
|
-
description: "project id (default: the worker's own project, else .baton.json)"
|
|
1137
|
-
},
|
|
1440
|
+
name: { type: "string", description: "room title (optional)" },
|
|
1441
|
+
desc: { type: "string", description: "room self-description: purpose / topic / how to participate" },
|
|
1138
1442
|
...common
|
|
1139
1443
|
},
|
|
1140
1444
|
run: async ({ args }) => {
|
|
1141
|
-
const
|
|
1142
|
-
const
|
|
1143
|
-
if (
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
console.log(
|
|
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;
|
|
1450
|
+
}
|
|
1451
|
+
console.log(`channel ${channelId} opened
|
|
1452
|
+
`);
|
|
1453
|
+
console.log(inviteBlock(url, channelId, token));
|
|
1150
1454
|
}
|
|
1151
1455
|
});
|
|
1152
1456
|
|
|
1153
|
-
// src/commands/
|
|
1154
|
-
var
|
|
1155
|
-
meta: { name: "
|
|
1457
|
+
// src/commands/channel/info.ts
|
|
1458
|
+
var aboutCommand = defineCommand({
|
|
1459
|
+
meta: { name: "about", description: "show a channel's manifest (description + online roster)" },
|
|
1156
1460
|
args: {
|
|
1157
|
-
|
|
1158
|
-
|
|
1461
|
+
channel: { type: "positional", required: true, description: "channel id" },
|
|
1462
|
+
token: { type: "string", required: true, description: "channel token" },
|
|
1159
1463
|
...common
|
|
1160
1464
|
},
|
|
1161
1465
|
run: async ({ args }) => {
|
|
1162
|
-
const
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
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}"`);
|
|
1167
1479
|
}
|
|
1168
1480
|
});
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
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());
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
var membersCommand = defineCommand({
|
|
1489
|
+
meta: { name: "members", description: "list the channel's online roster" },
|
|
1173
1490
|
args: {
|
|
1174
|
-
|
|
1491
|
+
channel: { type: "positional", required: true, description: "channel id" },
|
|
1492
|
+
token: { type: "string", required: true, description: "channel token" },
|
|
1175
1493
|
...common
|
|
1176
1494
|
},
|
|
1177
1495
|
run: async ({ args }) => {
|
|
1178
|
-
const
|
|
1179
|
-
const
|
|
1180
|
-
console.log(
|
|
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));
|
|
1181
1499
|
}
|
|
1182
1500
|
});
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
var sessionRenameCommand = defineCommand({
|
|
1186
|
-
meta: { name: "rename", description: "rename a session (locks the name against auto-title)" },
|
|
1501
|
+
var updateCommand = defineCommand({
|
|
1502
|
+
meta: { name: "update", description: "update a channel's title / description (topic + rules)" },
|
|
1187
1503
|
args: {
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
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)" },
|
|
1191
1508
|
...common
|
|
1192
1509
|
},
|
|
1193
1510
|
run: async ({ args }) => {
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
const
|
|
1197
|
-
|
|
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}` : ""}`);
|
|
1198
1519
|
}
|
|
1199
1520
|
});
|
|
1200
1521
|
|
|
1201
|
-
// src/commands/
|
|
1202
|
-
|
|
1203
|
-
|
|
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);
|
|
1548
|
+
}
|
|
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
|
+
},
|
|
1204
1613
|
args: {
|
|
1205
|
-
|
|
1206
|
-
|
|
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" },
|
|
1207
1619
|
...common
|
|
1208
1620
|
},
|
|
1209
1621
|
run: async ({ args }) => {
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
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
|
+
});
|
|
1214
1630
|
}
|
|
1215
1631
|
});
|
|
1216
1632
|
|
|
1217
|
-
// src/commands/
|
|
1218
|
-
var
|
|
1219
|
-
meta: { name: "
|
|
1633
|
+
// src/commands/channel/join.ts
|
|
1634
|
+
var joinCommand = defineCommand({
|
|
1635
|
+
meta: { name: "join", description: "join a channel (register presence); --listen to also stream" },
|
|
1220
1636
|
args: {
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
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)" },
|
|
1224
1643
|
...common
|
|
1225
1644
|
},
|
|
1226
1645
|
run: async ({ args }) => {
|
|
1227
|
-
const
|
|
1228
|
-
const
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
console.log(
|
|
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)}`);
|
|
1232
1653
|
return;
|
|
1233
1654
|
}
|
|
1234
|
-
await
|
|
1235
|
-
|
|
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
|
+
});
|
|
1236
1662
|
}
|
|
1237
1663
|
});
|
|
1238
1664
|
|
|
1239
|
-
//
|
|
1240
|
-
var
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
throw new Error(
|
|
1260
|
-
"Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing."
|
|
1261
|
-
);
|
|
1262
|
-
if (isFirstChunk && (isFirstChunk = false, chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191 && (chunk = chunk.slice(3))), pendingFragments.length === 0) {
|
|
1263
|
-
const trailing2 = processLines(chunk);
|
|
1264
|
-
trailing2 !== "" && (pendingFragments.push(trailing2), pendingFragmentsLength = trailing2.length), checkBufferSize();
|
|
1265
|
-
return;
|
|
1266
|
-
}
|
|
1267
|
-
if (chunk.indexOf(`
|
|
1268
|
-
`) === -1 && chunk.indexOf("\r") === -1) {
|
|
1269
|
-
pendingFragments.push(chunk), pendingFragmentsLength += chunk.length, checkBufferSize();
|
|
1270
|
-
return;
|
|
1271
|
-
}
|
|
1272
|
-
pendingFragments.push(chunk);
|
|
1273
|
-
const input = pendingFragments.join("");
|
|
1274
|
-
pendingFragments.length = 0, pendingFragmentsLength = 0;
|
|
1275
|
-
const trailing = processLines(input);
|
|
1276
|
-
trailing !== "" && (pendingFragments.push(trailing), pendingFragmentsLength = trailing.length), checkBufferSize();
|
|
1277
|
-
}
|
|
1278
|
-
function checkBufferSize() {
|
|
1279
|
-
maxBufferSize !== void 0 && (pendingFragmentsLength + data.length <= maxBufferSize || (terminated = true, pendingFragments.length = 0, pendingFragmentsLength = 0, id = void 0, data = "", dataLines = 0, eventType = void 0, onError(
|
|
1280
|
-
new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {
|
|
1281
|
-
type: "max-buffer-size-exceeded"
|
|
1282
|
-
})
|
|
1283
|
-
)));
|
|
1284
|
-
}
|
|
1285
|
-
function processLines(chunk) {
|
|
1286
|
-
let searchIndex = 0;
|
|
1287
|
-
if (chunk.indexOf("\r") === -1) {
|
|
1288
|
-
let lfIndex = chunk.indexOf(`
|
|
1289
|
-
`, searchIndex);
|
|
1290
|
-
for (; lfIndex !== -1; ) {
|
|
1291
|
-
if (searchIndex === lfIndex) {
|
|
1292
|
-
dataLines > 0 && onEvent({ id, event: eventType, data }), id = void 0, data = "", dataLines = 0, eventType = void 0, searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
|
|
1293
|
-
`, searchIndex);
|
|
1294
|
-
continue;
|
|
1295
|
-
}
|
|
1296
|
-
const firstCharCode = chunk.charCodeAt(searchIndex);
|
|
1297
|
-
if (isDataPrefix(chunk, searchIndex, firstCharCode)) {
|
|
1298
|
-
const valueStart = chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5, value = chunk.slice(valueStart, lfIndex);
|
|
1299
|
-
if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {
|
|
1300
|
-
onEvent({ id, event: eventType, data: value }), id = void 0, data = "", eventType = void 0, searchIndex = lfIndex + 2, lfIndex = chunk.indexOf(`
|
|
1301
|
-
`, searchIndex);
|
|
1302
|
-
continue;
|
|
1303
|
-
}
|
|
1304
|
-
data = dataLines === 0 ? value : `${data}
|
|
1305
|
-
${value}`, dataLines++;
|
|
1306
|
-
} else isEventPrefix(chunk, searchIndex, firstCharCode) ? eventType = chunk.slice(
|
|
1307
|
-
chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6,
|
|
1308
|
-
lfIndex
|
|
1309
|
-
) || void 0 : parseLine(chunk, searchIndex, lfIndex);
|
|
1310
|
-
searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
|
|
1311
|
-
`, searchIndex);
|
|
1312
|
-
}
|
|
1313
|
-
return chunk.slice(searchIndex);
|
|
1314
|
-
}
|
|
1315
|
-
for (; searchIndex < chunk.length; ) {
|
|
1316
|
-
const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(`
|
|
1317
|
-
`, searchIndex);
|
|
1318
|
-
let lineEnd = -1;
|
|
1319
|
-
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)
|
|
1320
|
-
break;
|
|
1321
|
-
parseLine(chunk, searchIndex, lineEnd), searchIndex = lineEnd + 1, chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF && searchIndex++;
|
|
1322
|
-
}
|
|
1323
|
-
return chunk.slice(searchIndex);
|
|
1324
|
-
}
|
|
1325
|
-
function parseLine(chunk, start, end) {
|
|
1326
|
-
if (start === end) {
|
|
1327
|
-
dispatchEvent();
|
|
1328
|
-
return;
|
|
1329
|
-
}
|
|
1330
|
-
const firstCharCode = chunk.charCodeAt(start);
|
|
1331
|
-
if (isDataPrefix(chunk, start, firstCharCode)) {
|
|
1332
|
-
const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5, value2 = chunk.slice(valueStart, end);
|
|
1333
|
-
data = dataLines === 0 ? value2 : `${data}
|
|
1334
|
-
${value2}`, dataLines++;
|
|
1335
|
-
return;
|
|
1336
|
-
}
|
|
1337
|
-
if (isEventPrefix(chunk, start, firstCharCode)) {
|
|
1338
|
-
eventType = chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || void 0;
|
|
1339
|
-
return;
|
|
1340
|
-
}
|
|
1341
|
-
if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) {
|
|
1342
|
-
const value2 = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end);
|
|
1343
|
-
id = value2.includes("\0") ? void 0 : value2;
|
|
1344
|
-
return;
|
|
1345
|
-
}
|
|
1346
|
-
if (firstCharCode === 58) {
|
|
1347
|
-
if (onComment) {
|
|
1348
|
-
const line2 = chunk.slice(start, end);
|
|
1349
|
-
onComment(line2.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1));
|
|
1350
|
-
}
|
|
1351
|
-
return;
|
|
1352
|
-
}
|
|
1353
|
-
const line = chunk.slice(start, end), fieldSeparatorIndex = line.indexOf(":");
|
|
1354
|
-
if (fieldSeparatorIndex === -1) {
|
|
1355
|
-
processField(line, "", line);
|
|
1356
|
-
return;
|
|
1357
|
-
}
|
|
1358
|
-
const field = line.slice(0, fieldSeparatorIndex), offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1, value = line.slice(fieldSeparatorIndex + offset);
|
|
1359
|
-
processField(field, value, line);
|
|
1360
|
-
}
|
|
1361
|
-
function processField(field, value, line) {
|
|
1362
|
-
switch (field) {
|
|
1363
|
-
case "event":
|
|
1364
|
-
eventType = value || void 0;
|
|
1365
|
-
break;
|
|
1366
|
-
case "data":
|
|
1367
|
-
data = dataLines === 0 ? value : `${data}
|
|
1368
|
-
${value}`, dataLines++;
|
|
1369
|
-
break;
|
|
1370
|
-
case "id":
|
|
1371
|
-
id = value.includes("\0") ? void 0 : value;
|
|
1372
|
-
break;
|
|
1373
|
-
case "retry":
|
|
1374
|
-
/^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(
|
|
1375
|
-
new ParseError(`Invalid \`retry\` value: "${value}"`, {
|
|
1376
|
-
type: "invalid-retry",
|
|
1377
|
-
value,
|
|
1378
|
-
line
|
|
1379
|
-
})
|
|
1380
|
-
);
|
|
1381
|
-
break;
|
|
1382
|
-
default:
|
|
1383
|
-
onError(
|
|
1384
|
-
new ParseError(
|
|
1385
|
-
`Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`,
|
|
1386
|
-
{ type: "unknown-field", field, value, line }
|
|
1387
|
-
)
|
|
1388
|
-
);
|
|
1389
|
-
break;
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
function dispatchEvent() {
|
|
1393
|
-
dataLines > 0 && onEvent({
|
|
1394
|
-
id,
|
|
1395
|
-
event: eventType,
|
|
1396
|
-
data
|
|
1397
|
-
}), id = void 0, data = "", dataLines = 0, eventType = void 0;
|
|
1398
|
-
}
|
|
1399
|
-
function reset(options = {}) {
|
|
1400
|
-
if (options.consume && pendingFragments.length > 0) {
|
|
1401
|
-
const incompleteLine = pendingFragments.join("");
|
|
1402
|
-
parseLine(incompleteLine, 0, incompleteLine.length);
|
|
1403
|
-
}
|
|
1404
|
-
isFirstChunk = true, id = void 0, data = "", dataLines = 0, eventType = void 0, pendingFragments.length = 0, pendingFragmentsLength = 0, terminated = false;
|
|
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)");
|
|
1405
1685
|
}
|
|
1406
|
-
|
|
1407
|
-
}
|
|
1408
|
-
function isDataPrefix(chunk, i, firstCharCode) {
|
|
1409
|
-
return firstCharCode === 100 && chunk.charCodeAt(i + 1) === 97 && chunk.charCodeAt(i + 2) === 116 && chunk.charCodeAt(i + 3) === 97 && chunk.charCodeAt(i + 4) === 58;
|
|
1410
|
-
}
|
|
1411
|
-
function isEventPrefix(chunk, i, firstCharCode) {
|
|
1412
|
-
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;
|
|
1413
|
-
}
|
|
1686
|
+
});
|
|
1414
1687
|
|
|
1415
|
-
//
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
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})`);
|
|
1427
1715
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
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
|
|
1442
1735
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
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();
|
|
1456
1756
|
}
|
|
1457
1757
|
};
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
var
|
|
1504
|
-
var
|
|
1505
|
-
var
|
|
1506
|
-
var
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
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}`);
|
|
1799
|
+
}
|
|
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);
|
|
1516
1826
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
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
|
+
);
|
|
1520
1838
|
}
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
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)));
|
|
1524
1845
|
}
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
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)));
|
|
1532
1871
|
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1872
|
+
})
|
|
1873
|
+
}
|
|
1874
|
+
});
|
|
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";
|
|
1880
|
+
|
|
1881
|
+
// src/client/relay.ts
|
|
1882
|
+
var relayClient = (baseUrl) => {
|
|
1883
|
+
const u = (p) => `${baseUrl}${p}`;
|
|
1884
|
+
const authed = (token) => ({ authorization: `Bearer ${token}` });
|
|
1885
|
+
return {
|
|
1886
|
+
create: async () => {
|
|
1887
|
+
const res = await fetch(u("/relay/channels"), { method: "POST" });
|
|
1888
|
+
if (!res.ok) throw new Error(`POST /relay/channels \u2192 ${res.status}: ${await res.text()}`);
|
|
1889
|
+
return await res.json();
|
|
1890
|
+
},
|
|
1891
|
+
send: async (channelId, token, msg) => {
|
|
1892
|
+
const res = await fetch(u(`/relay/channels/${channelId}/messages`), {
|
|
1893
|
+
method: "POST",
|
|
1894
|
+
headers: { "content-type": "application/json", ...authed(token) },
|
|
1895
|
+
body: JSON.stringify(msg)
|
|
1547
1896
|
});
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1897
|
+
if (!res.ok) throw new Error(`POST relay message \u2192 ${res.status}: ${await res.text()}`);
|
|
1898
|
+
return await res.json();
|
|
1899
|
+
},
|
|
1900
|
+
listen: (channelId, token, opts) => {
|
|
1901
|
+
let cursor = opts.since ?? 0;
|
|
1902
|
+
const es = new EventSource(u(`/relay/channels/${channelId}/stream?since=${cursor}`), {
|
|
1903
|
+
fetch: (url, init2) => fetch(url, {
|
|
1904
|
+
...init2,
|
|
1905
|
+
headers: { ...init2?.headers, ...authed(token) }
|
|
1906
|
+
})
|
|
1907
|
+
});
|
|
1908
|
+
es.onmessage = (e) => {
|
|
1909
|
+
try {
|
|
1910
|
+
const m = JSON.parse(e.data);
|
|
1911
|
+
if (m.seq <= cursor) return;
|
|
1912
|
+
cursor = m.seq;
|
|
1913
|
+
opts.onMessage(m);
|
|
1914
|
+
} catch (err) {
|
|
1915
|
+
opts.onError?.(err);
|
|
1916
|
+
}
|
|
1917
|
+
};
|
|
1918
|
+
es.onerror = (err) => opts.onError?.(err);
|
|
1919
|
+
return () => es.close();
|
|
1563
1920
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1921
|
+
};
|
|
1922
|
+
};
|
|
1923
|
+
|
|
1924
|
+
// src/commands/relay.ts
|
|
1925
|
+
var PREVIEW2 = 280;
|
|
1926
|
+
var readStdin2 = async () => {
|
|
1927
|
+
const chunks = [];
|
|
1928
|
+
for await (const c of process.stdin) chunks.push(c);
|
|
1929
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
1930
|
+
};
|
|
1931
|
+
var inviteBlock2 = (url, channelId, token) => [
|
|
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",
|
|
1933
|
+
"You're invited to a live Claude\u2194Claude channel. Use the `hotline` skill in",
|
|
1934
|
+
"join mode, or run these two commands directly (pick your own NAME):",
|
|
1935
|
+
"",
|
|
1936
|
+
` baton relay listen ${channelId} --token ${token} --from NAME --url ${url}`,
|
|
1937
|
+
` baton relay send ${channelId} --token ${token} --from NAME --text "..." --url ${url}`,
|
|
1938
|
+
"",
|
|
1939
|
+
`connection: url=${url} channel=${channelId} token=${token}`,
|
|
1940
|
+
"\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"
|
|
1941
|
+
].join("\n");
|
|
1942
|
+
var hostCommand = defineCommand({
|
|
1943
|
+
meta: { name: "host", description: "open a relay channel and print an invite for a peer" },
|
|
1944
|
+
args: {
|
|
1945
|
+
name: { type: "string", description: "your participant name (default: host)" },
|
|
1946
|
+
...common
|
|
1947
|
+
},
|
|
1948
|
+
run: async ({ args }) => {
|
|
1949
|
+
const url = resolveBaseUrl(args.url);
|
|
1950
|
+
const { channelId, token } = await relayClient(url).create();
|
|
1951
|
+
if (args.json) {
|
|
1952
|
+
console.log(toJson({ channelId, token, url, host: args.name ?? "host" }));
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1955
|
+
console.log(`channel ${channelId} opened
|
|
1956
|
+
`);
|
|
1957
|
+
console.log(inviteBlock2(url, channelId, token));
|
|
1568
1958
|
}
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1959
|
+
});
|
|
1960
|
+
var sendCommand2 = defineCommand({
|
|
1961
|
+
meta: { name: "send", description: "post one message to a relay channel" },
|
|
1962
|
+
args: {
|
|
1963
|
+
channel: { type: "positional", required: true, description: "channel id" },
|
|
1964
|
+
text: { type: "positional", required: false, description: "message text (short one-liner)" },
|
|
1965
|
+
token: { type: "string", required: true, description: "channel token" },
|
|
1966
|
+
from: { type: "string", description: "your participant name (default: peer)" },
|
|
1967
|
+
file: { type: "string", description: "read message body from this file (for large content)" },
|
|
1968
|
+
...common
|
|
1969
|
+
},
|
|
1970
|
+
run: async ({ args }) => {
|
|
1971
|
+
const url = resolveBaseUrl(args.url);
|
|
1972
|
+
let text = args.text;
|
|
1973
|
+
if (text === void 0 && args.file) text = readFileSync3(args.file, "utf8");
|
|
1974
|
+
if (text === void 0 && !process.stdin.isTTY) text = await readStdin2();
|
|
1975
|
+
if (text === void 0 || text === "")
|
|
1976
|
+
throw new Error("nothing to send: provide text, --file <path>, or pipe stdin");
|
|
1977
|
+
const msg = await relayClient(url).send(args.channel, args.token, {
|
|
1978
|
+
from: args.from ?? "peer",
|
|
1979
|
+
text
|
|
1980
|
+
});
|
|
1981
|
+
if (args.json) console.log(toJson(msg));
|
|
1982
|
+
else console.log(`sent (seq ${msg.seq})`);
|
|
1581
1983
|
}
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1984
|
+
});
|
|
1985
|
+
var listenCommand2 = defineCommand({
|
|
1986
|
+
meta: {
|
|
1987
|
+
name: "listen",
|
|
1988
|
+
description: "stream a channel: one JSON line per peer message (for a background subscriber)"
|
|
1989
|
+
},
|
|
1990
|
+
args: {
|
|
1991
|
+
channel: { type: "positional", required: true, description: "channel id" },
|
|
1992
|
+
token: { type: "string", required: true, description: "channel token" },
|
|
1993
|
+
from: {
|
|
1994
|
+
type: "string",
|
|
1995
|
+
description: "your name; messages from this name are skipped (no echo)"
|
|
1996
|
+
},
|
|
1997
|
+
since: { type: "string", description: "replay messages after this seq (default 0 = full)" },
|
|
1998
|
+
...common
|
|
1999
|
+
},
|
|
2000
|
+
run: async ({ args }) => {
|
|
2001
|
+
const url = resolveBaseUrl(args.url);
|
|
2002
|
+
const me = args.from ?? "";
|
|
2003
|
+
const emit = (o) => {
|
|
2004
|
+
process.stdout.write(`${JSON.stringify(o)}
|
|
2005
|
+
`);
|
|
2006
|
+
};
|
|
2007
|
+
const spillDir = join3(tmpdir2(), "hotline", args.channel);
|
|
2008
|
+
mkdirSync2(spillDir, { recursive: true });
|
|
2009
|
+
const surface = (m) => {
|
|
2010
|
+
const text = typeof m.text === "string" ? m.text : "";
|
|
2011
|
+
const file = join3(spillDir, `${m.seq}.txt`);
|
|
2012
|
+
writeFileSync3(file, text);
|
|
2013
|
+
const truncated = text.length > PREVIEW2;
|
|
2014
|
+
emit({
|
|
2015
|
+
type: "relay.message",
|
|
2016
|
+
seq: m.seq,
|
|
2017
|
+
from: m.from,
|
|
2018
|
+
ts: m.ts,
|
|
2019
|
+
chars: text.length,
|
|
2020
|
+
truncated,
|
|
2021
|
+
preview: truncated ? `${text.slice(0, PREVIEW2)} \u2026` : text,
|
|
2022
|
+
file
|
|
2023
|
+
});
|
|
2024
|
+
};
|
|
2025
|
+
const describe2 = (e) => {
|
|
2026
|
+
const ev = e;
|
|
2027
|
+
return ev?.code ? `${ev.code}${ev.message ? ` ${ev.message}` : ""}` : String(e);
|
|
2028
|
+
};
|
|
2029
|
+
const close = relayClient(url).listen(args.channel, args.token, {
|
|
2030
|
+
since: args.since ? Number(args.since) : 0,
|
|
2031
|
+
// Skip our own messages (they come back over the same channel).
|
|
2032
|
+
onMessage: (m) => {
|
|
2033
|
+
if (m.from !== me) surface(m);
|
|
2034
|
+
},
|
|
2035
|
+
onError: (e) => emit({ type: "relay.error", error: describe2(e) })
|
|
2036
|
+
});
|
|
2037
|
+
emit({ type: "relay.listening", channel: args.channel, from: me, url });
|
|
2038
|
+
await new Promise((resolve2) => {
|
|
2039
|
+
const stop = () => {
|
|
2040
|
+
close();
|
|
2041
|
+
resolve2();
|
|
2042
|
+
};
|
|
2043
|
+
process.on("SIGINT", stop);
|
|
2044
|
+
process.on("SIGTERM", stop);
|
|
2045
|
+
});
|
|
1591
2046
|
}
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
2047
|
+
});
|
|
2048
|
+
var relay = defineCommand({
|
|
2049
|
+
meta: { name: "relay", description: "Claude\u2194Claude live channel: host / listen / send" },
|
|
2050
|
+
subCommands: { host: hostCommand, listen: listenCommand2, send: sendCommand2 }
|
|
2051
|
+
});
|
|
2052
|
+
|
|
2053
|
+
// src/commands/requirement.ts
|
|
2054
|
+
var createRequirement = (c, input, json) => c.requirements.create(input).then((r) => renderOne(r, fmtRequirement, json));
|
|
2055
|
+
var updateRequirement = (c, id, patch2, json) => c.requirements.update(id, patch2).then((r) => renderOne(r, fmtRequirement, json));
|
|
2056
|
+
var listRequirements = (c, projectId, json) => c.requirements.listByProject(projectId).then((rs) => renderList(rs, fmtRequirement, json));
|
|
2057
|
+
var getRequirement = (c, id, json) => c.requirements.get(id).then((r) => renderOne(r, fmtRequirement, json));
|
|
2058
|
+
var setRequirementStatus = (c, id, status, json) => c.requirements.setStatus(id, status).then((r) => renderOne(r, fmtRequirement, json));
|
|
2059
|
+
var removeRequirement = (c, id, label, json) => c.requirements.remove(id).then(() => removed("requirement", label, json));
|
|
2060
|
+
var resolveByCode = async (c, projectId, code) => (await c.requirements.getByCode(projectId, code)).id;
|
|
2061
|
+
var requirement = defineCommand({
|
|
2062
|
+
meta: { name: "requirement", description: "manage requirements" },
|
|
2063
|
+
subCommands: {
|
|
2064
|
+
create: defineCommand({
|
|
2065
|
+
meta: { name: "create", description: "create a requirement" },
|
|
2066
|
+
args: {
|
|
2067
|
+
title: { type: "positional", required: true },
|
|
2068
|
+
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
2069
|
+
desc: { type: "string", description: "description" },
|
|
2070
|
+
body: { type: "string", description: "requirement body (markdown)" },
|
|
2071
|
+
...common
|
|
2072
|
+
},
|
|
2073
|
+
run: async ({ args }) => {
|
|
2074
|
+
console.log(
|
|
2075
|
+
await createRequirement(
|
|
2076
|
+
clientFor(args),
|
|
2077
|
+
{
|
|
2078
|
+
projectId: resolveProjectId(args),
|
|
2079
|
+
title: args.title,
|
|
2080
|
+
description: args.desc,
|
|
2081
|
+
body: args.body
|
|
2082
|
+
},
|
|
2083
|
+
Boolean(args.json)
|
|
2084
|
+
)
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
}),
|
|
2088
|
+
update: defineCommand({
|
|
2089
|
+
meta: { name: "update", description: "update a requirement title / body" },
|
|
2090
|
+
args: {
|
|
2091
|
+
code: { type: "positional", required: true, description: "requirement code (R-N)" },
|
|
2092
|
+
title: { type: "string", description: "new title" },
|
|
2093
|
+
body: { type: "string", description: "new body (markdown)" },
|
|
2094
|
+
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
2095
|
+
...common
|
|
2096
|
+
},
|
|
2097
|
+
run: async ({ args }) => {
|
|
2098
|
+
const patch2 = {
|
|
2099
|
+
...args.title !== void 0 ? { title: args.title } : {},
|
|
2100
|
+
...args.body !== void 0 ? { body: args.body } : {}
|
|
2101
|
+
};
|
|
2102
|
+
if (Object.keys(patch2).length === 0) throw new Error("pass --title and/or --body");
|
|
2103
|
+
const c = clientFor(args);
|
|
2104
|
+
const id = await resolveByCode(c, resolveProjectId(args), args.code);
|
|
2105
|
+
console.log(await updateRequirement(c, id, patch2, Boolean(args.json)));
|
|
2106
|
+
}
|
|
2107
|
+
}),
|
|
2108
|
+
ls: defineCommand({
|
|
2109
|
+
meta: { name: "ls", description: "list requirements in a project" },
|
|
2110
|
+
args: {
|
|
2111
|
+
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
2112
|
+
...common
|
|
2113
|
+
},
|
|
2114
|
+
run: async ({ args }) => {
|
|
2115
|
+
console.log(
|
|
2116
|
+
await listRequirements(clientFor(args), resolveProjectId(args), Boolean(args.json))
|
|
2117
|
+
);
|
|
2118
|
+
}
|
|
2119
|
+
}),
|
|
2120
|
+
get: defineCommand({
|
|
2121
|
+
meta: { name: "get", description: "get a requirement by code (R-N)" },
|
|
2122
|
+
args: {
|
|
2123
|
+
code: { type: "positional", required: true, description: "requirement code, e.g. R-1" },
|
|
2124
|
+
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
2125
|
+
...common
|
|
2126
|
+
},
|
|
2127
|
+
run: async ({ args }) => {
|
|
2128
|
+
const c = clientFor(args);
|
|
2129
|
+
const id = await resolveByCode(c, resolveProjectId(args), args.code);
|
|
2130
|
+
console.log(await getRequirement(c, id, Boolean(args.json)));
|
|
2131
|
+
}
|
|
2132
|
+
}),
|
|
2133
|
+
"set-status": defineCommand({
|
|
2134
|
+
meta: { name: "set-status", description: "set requirement status (active|done|cancelled)" },
|
|
2135
|
+
args: {
|
|
2136
|
+
code: { type: "positional", required: true, description: "requirement code (R-N)" },
|
|
2137
|
+
status: { type: "positional", required: true },
|
|
2138
|
+
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
2139
|
+
...common
|
|
2140
|
+
},
|
|
2141
|
+
run: async ({ args }) => {
|
|
2142
|
+
const c = clientFor(args);
|
|
2143
|
+
const id = await resolveByCode(c, resolveProjectId(args), args.code);
|
|
2144
|
+
console.log(
|
|
2145
|
+
await setRequirementStatus(c, id, args.status, Boolean(args.json))
|
|
2146
|
+
);
|
|
2147
|
+
}
|
|
2148
|
+
}),
|
|
2149
|
+
rm: defineCommand({
|
|
2150
|
+
meta: { name: "rm", description: "delete a requirement by code" },
|
|
2151
|
+
args: {
|
|
2152
|
+
code: { type: "positional", required: true },
|
|
2153
|
+
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
2154
|
+
...common
|
|
2155
|
+
},
|
|
2156
|
+
run: async ({ args }) => {
|
|
2157
|
+
const c = clientFor(args);
|
|
2158
|
+
const id = await resolveByCode(c, resolveProjectId(args), args.code);
|
|
2159
|
+
console.log(await removeRequirement(c, id, args.code, Boolean(args.json)));
|
|
2160
|
+
}
|
|
2161
|
+
})
|
|
1599
2162
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
2163
|
+
});
|
|
2164
|
+
|
|
2165
|
+
// src/commands/session/shared.ts
|
|
2166
|
+
import { homedir } from "node:os";
|
|
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");
|
|
2169
|
+
var slug = (s) => s.toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
|
|
2170
|
+
var resolveSession = async (client, projectId, handle) => {
|
|
2171
|
+
const asInt = Number(handle);
|
|
2172
|
+
if (Number.isInteger(asInt) && asInt > 0) {
|
|
2173
|
+
const byId = await client.sessions.get(asInt).catch(() => null);
|
|
2174
|
+
if (byId) return { id: byId.id, name: byId.name };
|
|
1603
2175
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
2176
|
+
const byName = await client.sessions.findByName(projectId, handle);
|
|
2177
|
+
if (byName) return { id: byName.id, name: byName.name };
|
|
2178
|
+
throw new Error(`session "${handle}" not found in project ${projectId}`);
|
|
2179
|
+
};
|
|
2180
|
+
|
|
2181
|
+
// src/commands/session/abort.ts
|
|
2182
|
+
var sessionAbortCommand = defineCommand({
|
|
2183
|
+
meta: {
|
|
2184
|
+
name: "abort",
|
|
2185
|
+
description: "interrupt the in-flight turn (like Esc); keeps the session"
|
|
2186
|
+
},
|
|
2187
|
+
args: {
|
|
2188
|
+
session: { type: "positional", required: true, description: "session int id or name" },
|
|
2189
|
+
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
2190
|
+
...common
|
|
2191
|
+
},
|
|
2192
|
+
run: async ({ args }) => {
|
|
2193
|
+
const c = clientFor(args);
|
|
2194
|
+
const handle = await resolveSession(c, resolveProjectId(args), args.session);
|
|
2195
|
+
const s = await c.sessions.abort(handle.id);
|
|
2196
|
+
console.log(renderOne(s, fmtSession, Boolean(args.json)));
|
|
1606
2197
|
}
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
2198
|
+
});
|
|
2199
|
+
|
|
2200
|
+
// src/commands/session/clear.ts
|
|
2201
|
+
var sessionClearCommand = defineCommand({
|
|
2202
|
+
meta: {
|
|
2203
|
+
name: "clear",
|
|
2204
|
+
description: "reset the conversation context (keeps the session + worktree)"
|
|
2205
|
+
},
|
|
2206
|
+
args: {
|
|
2207
|
+
session: { type: "positional", required: true, description: "session int id or name" },
|
|
2208
|
+
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
2209
|
+
...common
|
|
2210
|
+
},
|
|
2211
|
+
run: async ({ args }) => {
|
|
2212
|
+
const c = clientFor(args);
|
|
2213
|
+
const handle = await resolveSession(c, resolveProjectId(args), args.session);
|
|
2214
|
+
const s = await c.sessions.clear(handle.id);
|
|
2215
|
+
console.log(renderOne(s, fmtSession, Boolean(args.json)));
|
|
1610
2216
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
2217
|
+
});
|
|
2218
|
+
|
|
2219
|
+
// src/commands/session/create.ts
|
|
2220
|
+
var sessionCreateCommand = defineCommand({
|
|
2221
|
+
meta: { name: "create", description: "create + start a session on a worker" },
|
|
2222
|
+
args: {
|
|
2223
|
+
name: { type: "positional", required: true, description: "session name" },
|
|
2224
|
+
worker: {
|
|
2225
|
+
type: "string",
|
|
2226
|
+
description: "worker handle: W-N or int id (default: this machine's worker)"
|
|
2227
|
+
},
|
|
2228
|
+
project: {
|
|
2229
|
+
type: "string",
|
|
2230
|
+
description: "project id (default: the worker's own project, else .baton.json)"
|
|
2231
|
+
},
|
|
2232
|
+
...common
|
|
2233
|
+
},
|
|
2234
|
+
run: async ({ args }) => {
|
|
2235
|
+
const c = clientFor(args);
|
|
2236
|
+
const workerId = args.worker ? parseWorkerHandle(args.worker) : loadProjectConfigOrNull(projectConfigPath())?.worker?.id ?? null;
|
|
2237
|
+
if (workerId === null)
|
|
2238
|
+
throw new Error(
|
|
2239
|
+
"no worker \u2014 pass --worker <W-N|id> or run `baton worker register` first (names need --project)"
|
|
2240
|
+
);
|
|
2241
|
+
const projectId = args.project !== void 0 && args.project !== "" ? Number(args.project) : args.worker ? (await c.workers.get(workerId)).projectId : resolveProjectId(args);
|
|
2242
|
+
const s = await c.sessions.create({ projectId, workerId, name: args.name });
|
|
2243
|
+
console.log(renderOne(s, fmtSession, Boolean(args.json)));
|
|
1613
2244
|
}
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
2245
|
+
});
|
|
2246
|
+
|
|
2247
|
+
// src/commands/session/get.ts
|
|
2248
|
+
var sessionGetCommand = defineCommand({
|
|
2249
|
+
meta: { name: "get", description: "get a session by int id or name" },
|
|
2250
|
+
args: {
|
|
2251
|
+
session: { type: "positional", required: true, description: "session int id or name" },
|
|
2252
|
+
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
2253
|
+
...common
|
|
2254
|
+
},
|
|
2255
|
+
run: async ({ args }) => {
|
|
2256
|
+
const c = clientFor(args);
|
|
2257
|
+
const projectId = resolveProjectId(args);
|
|
2258
|
+
const handle = await resolveSession(c, projectId, args.session);
|
|
2259
|
+
const s = await c.sessions.get(handle.id);
|
|
2260
|
+
console.log(renderOne(s, fmtSession, Boolean(args.json)));
|
|
1617
2261
|
}
|
|
1618
|
-
|
|
1619
|
-
|
|
2262
|
+
});
|
|
2263
|
+
|
|
2264
|
+
// src/commands/session/ls.ts
|
|
2265
|
+
var sessionLsCommand = defineCommand({
|
|
2266
|
+
meta: { name: "ls", description: "list sessions in a project" },
|
|
2267
|
+
args: {
|
|
2268
|
+
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
2269
|
+
...common
|
|
2270
|
+
},
|
|
2271
|
+
run: async ({ args }) => {
|
|
2272
|
+
const c = clientFor(args);
|
|
2273
|
+
const ss = await c.sessions.listByProject(resolveProjectId(args));
|
|
2274
|
+
console.log(renderList(ss, fmtSession, Boolean(args.json)));
|
|
1620
2275
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
2276
|
+
});
|
|
2277
|
+
|
|
2278
|
+
// src/commands/session/rename.ts
|
|
2279
|
+
var sessionRenameCommand = defineCommand({
|
|
2280
|
+
meta: { name: "rename", description: "rename a session (locks the name against auto-title)" },
|
|
2281
|
+
args: {
|
|
2282
|
+
session: { type: "positional", required: true, description: "session int id or name" },
|
|
2283
|
+
name: { type: "positional", required: true, description: "new name" },
|
|
2284
|
+
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
2285
|
+
...common
|
|
2286
|
+
},
|
|
2287
|
+
run: async ({ args }) => {
|
|
2288
|
+
const c = clientFor(args);
|
|
2289
|
+
const handle = await resolveSession(c, resolveProjectId(args), args.session);
|
|
2290
|
+
const s = await c.sessions.rename(handle.id, args.name);
|
|
2291
|
+
console.log(renderOne(s, fmtSession, Boolean(args.json)));
|
|
1624
2292
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
2293
|
+
});
|
|
2294
|
+
|
|
2295
|
+
// src/commands/session/resume.ts
|
|
2296
|
+
var sessionResumeCommand = defineCommand({
|
|
2297
|
+
meta: { name: "resume", description: "resume (re-start) an existing session" },
|
|
2298
|
+
args: {
|
|
2299
|
+
session: { type: "positional", required: true, description: "session int id or name" },
|
|
2300
|
+
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
2301
|
+
...common
|
|
2302
|
+
},
|
|
2303
|
+
run: async ({ args }) => {
|
|
2304
|
+
const c = clientFor(args);
|
|
2305
|
+
const handle = await resolveSession(c, resolveProjectId(args), args.session);
|
|
2306
|
+
const s = await c.sessions.resume(handle.id);
|
|
2307
|
+
console.log(renderOne(s, fmtSession, Boolean(args.json)));
|
|
1628
2308
|
}
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
2309
|
+
});
|
|
2310
|
+
|
|
2311
|
+
// src/commands/session/rm.ts
|
|
2312
|
+
var sessionRmCommand = defineCommand({
|
|
2313
|
+
meta: { name: "rm", description: "delete a session (irreversible; removes its worktree)" },
|
|
2314
|
+
args: {
|
|
2315
|
+
session: { type: "positional", required: true, description: "session int id or name" },
|
|
2316
|
+
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
2317
|
+
confirm: { type: "boolean", description: "actually perform the deletion" },
|
|
2318
|
+
...common
|
|
2319
|
+
},
|
|
2320
|
+
run: async ({ args }) => {
|
|
2321
|
+
const c = clientFor(args);
|
|
2322
|
+
const handle = await resolveSession(c, resolveProjectId(args), args.session);
|
|
2323
|
+
if (!args.confirm) {
|
|
2324
|
+
console.log(`[dry-run] would delete session ${handle.name} (#${handle.id}) + its worktree`);
|
|
2325
|
+
console.log("re-run with --confirm to proceed.");
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
2328
|
+
await c.sessions.destroy(handle.id);
|
|
2329
|
+
console.log(`deleted session ${handle.name} (#${handle.id})`);
|
|
1638
2330
|
}
|
|
1639
|
-
};
|
|
1640
|
-
_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(), /**
|
|
1641
|
-
* Connect to the given URL and start receiving events
|
|
1642
|
-
*
|
|
1643
|
-
* @internal
|
|
1644
|
-
*/
|
|
1645
|
-
connect_fn = function() {
|
|
1646
|
-
__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));
|
|
1647
|
-
}, _onFetchResponse = /* @__PURE__ */ new WeakMap(), _onFetchError = /* @__PURE__ */ new WeakMap(), /**
|
|
1648
|
-
* Get request options for the `fetch()` request
|
|
1649
|
-
*
|
|
1650
|
-
* @returns The request options
|
|
1651
|
-
* @internal
|
|
1652
|
-
*/
|
|
1653
|
-
getRequestOptions_fn = function() {
|
|
1654
|
-
var _a;
|
|
1655
|
-
const init2 = {
|
|
1656
|
-
// [spec] Let `corsAttributeState` be `Anonymous`…
|
|
1657
|
-
// [spec] …will have their mode set to "cors"…
|
|
1658
|
-
mode: "cors",
|
|
1659
|
-
redirect: "follow",
|
|
1660
|
-
headers: { Accept: "text/event-stream", ...__privateGet(this, _lastEventId) ? { "Last-Event-ID": __privateGet(this, _lastEventId) } : void 0 },
|
|
1661
|
-
cache: "no-store",
|
|
1662
|
-
signal: (_a = __privateGet(this, _controller)) == null ? void 0 : _a.signal
|
|
1663
|
-
};
|
|
1664
|
-
return "window" in globalThis && (init2.credentials = this.withCredentials ? "include" : "same-origin"), init2;
|
|
1665
|
-
}, _onEvent = /* @__PURE__ */ new WeakMap(), _onRetryChange = /* @__PURE__ */ new WeakMap(), /**
|
|
1666
|
-
* Handles the process referred to in the EventSource specification as "failing a connection".
|
|
1667
|
-
*
|
|
1668
|
-
* @param error - The error causing the connection to fail
|
|
1669
|
-
* @param code - The HTTP status code, if available
|
|
1670
|
-
* @internal
|
|
1671
|
-
*/
|
|
1672
|
-
failConnection_fn = function(message, code) {
|
|
1673
|
-
var _a;
|
|
1674
|
-
__privateGet(this, _readyState) !== this.CLOSED && __privateSet(this, _readyState, this.CLOSED);
|
|
1675
|
-
const errorEvent = new ErrorEvent("error", { code, message });
|
|
1676
|
-
(_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent);
|
|
1677
|
-
}, /**
|
|
1678
|
-
* Schedules a reconnection attempt against the EventSource endpoint.
|
|
1679
|
-
*
|
|
1680
|
-
* @param message - The error causing the connection to fail
|
|
1681
|
-
* @param code - The HTTP status code, if available
|
|
1682
|
-
* @internal
|
|
1683
|
-
*/
|
|
1684
|
-
scheduleReconnect_fn = function(message, code) {
|
|
1685
|
-
var _a;
|
|
1686
|
-
if (__privateGet(this, _readyState) === this.CLOSED)
|
|
1687
|
-
return;
|
|
1688
|
-
__privateSet(this, _readyState, this.CONNECTING);
|
|
1689
|
-
const errorEvent = new ErrorEvent("error", { code, message });
|
|
1690
|
-
(_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent), __privateSet(this, _reconnectTimer, setTimeout(__privateGet(this, _reconnect), __privateGet(this, _reconnectInterval)));
|
|
1691
|
-
}, _reconnect = /* @__PURE__ */ new WeakMap(), /**
|
|
1692
|
-
* ReadyState representing an EventSource currently trying to connect
|
|
1693
|
-
*
|
|
1694
|
-
* @public
|
|
1695
|
-
*/
|
|
1696
|
-
EventSource.CONNECTING = 0, /**
|
|
1697
|
-
* ReadyState representing an EventSource connection that is open (eg connected)
|
|
1698
|
-
*
|
|
1699
|
-
* @public
|
|
1700
|
-
*/
|
|
1701
|
-
EventSource.OPEN = 1, /**
|
|
1702
|
-
* ReadyState representing an EventSource connection that is closed (eg disconnected)
|
|
1703
|
-
*
|
|
1704
|
-
* @public
|
|
1705
|
-
*/
|
|
1706
|
-
EventSource.CLOSED = 2;
|
|
1707
|
-
Object.defineProperty(EventSource, /* @__PURE__ */ Symbol.for("eventsource.supports-fetch-override"), {
|
|
1708
|
-
value: true,
|
|
1709
|
-
writable: false,
|
|
1710
|
-
configurable: false,
|
|
1711
|
-
enumerable: false
|
|
1712
2331
|
});
|
|
1713
|
-
function getBaseURL() {
|
|
1714
|
-
const doc = "document" in globalThis ? globalThis.document : void 0;
|
|
1715
|
-
return doc && typeof doc == "object" && "baseURI" in doc && typeof doc.baseURI == "string" ? doc.baseURI : void 0;
|
|
1716
|
-
}
|
|
1717
2332
|
|
|
1718
2333
|
// src/session/runner.ts
|
|
1719
2334
|
import { spawnSync } from "node:child_process";
|
|
@@ -1735,14 +2350,14 @@ var additionalDirs = () => {
|
|
|
1735
2350
|
};
|
|
1736
2351
|
|
|
1737
2352
|
// src/session/runner/transcript.ts
|
|
1738
|
-
import { readdirSync, readFileSync as
|
|
2353
|
+
import { readdirSync, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
1739
2354
|
import { homedir as homedir2 } from "node:os";
|
|
1740
|
-
import { join as
|
|
2355
|
+
import { join as join5 } from "node:path";
|
|
1741
2356
|
var findTranscriptPath = (agentSessionId) => {
|
|
1742
|
-
const root =
|
|
2357
|
+
const root = join5(homedir2(), ".claude", "projects");
|
|
1743
2358
|
try {
|
|
1744
2359
|
for (const dir of readdirSync(root)) {
|
|
1745
|
-
const candidate =
|
|
2360
|
+
const candidate = join5(root, dir, `${agentSessionId}.jsonl`);
|
|
1746
2361
|
try {
|
|
1747
2362
|
if (statSync(candidate).isFile()) return candidate;
|
|
1748
2363
|
} catch {
|
|
@@ -1788,27 +2403,16 @@ var readFirstExchange = (agentSessionId) => {
|
|
|
1788
2403
|
const path = findTranscriptPath(agentSessionId);
|
|
1789
2404
|
if (!path) return null;
|
|
1790
2405
|
try {
|
|
1791
|
-
return parseFirstExchange(
|
|
2406
|
+
return parseFirstExchange(readFileSync4(path, "utf8"));
|
|
1792
2407
|
} catch {
|
|
1793
2408
|
return null;
|
|
1794
2409
|
}
|
|
1795
2410
|
};
|
|
1796
2411
|
|
|
1797
|
-
// ../shared/src/domain/attachment.ts
|
|
1798
|
-
var isImageAttachment = (att) => att.contentType.startsWith("image/");
|
|
1799
|
-
var labelAttachments = (attachments) => {
|
|
1800
|
-
const counters = { image: 0, file: 0 };
|
|
1801
|
-
return attachments.map((att) => {
|
|
1802
|
-
const kind = isImageAttachment(att) ? "image" : "file";
|
|
1803
|
-
counters[kind] += 1;
|
|
1804
|
-
return `${kind}-${counters[kind]}`;
|
|
1805
|
-
});
|
|
1806
|
-
};
|
|
1807
|
-
|
|
1808
2412
|
// src/session/runner/attachments.ts
|
|
1809
2413
|
import { createWriteStream } from "node:fs";
|
|
1810
2414
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
1811
|
-
import { join as
|
|
2415
|
+
import { join as join6 } from "node:path";
|
|
1812
2416
|
import { Readable } from "node:stream";
|
|
1813
2417
|
import { pipeline } from "node:stream/promises";
|
|
1814
2418
|
var ATTACH_DIR = "attachments";
|
|
@@ -1831,16 +2435,16 @@ var dedupe = (name, taken) => {
|
|
|
1831
2435
|
};
|
|
1832
2436
|
var materializeAttachments = async (opts) => {
|
|
1833
2437
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
1834
|
-
const dir =
|
|
2438
|
+
const dir = join6(opts.worktreePath, ATTACH_DIR);
|
|
1835
2439
|
await mkdir(dir, { recursive: true });
|
|
1836
|
-
await writeFile(
|
|
2440
|
+
await writeFile(join6(dir, ".gitignore"), "*\n", "utf8");
|
|
1837
2441
|
const taken = /* @__PURE__ */ new Set([".gitignore"]);
|
|
1838
2442
|
const relPaths = [];
|
|
1839
2443
|
for (const att of opts.attachments) {
|
|
1840
2444
|
const name = dedupe(att.filename, taken);
|
|
1841
2445
|
const res = await fetchImpl(`${opts.serverBase}${att.url}`);
|
|
1842
2446
|
if (!res.ok || !res.body) throw new Error(`attachment ${att.id} \u2192 ${res.status}`);
|
|
1843
|
-
await pipeline(Readable.fromWeb(res.body), createWriteStream(
|
|
2447
|
+
await pipeline(Readable.fromWeb(res.body), createWriteStream(join6(dir, name)));
|
|
1844
2448
|
relPaths.push(`${ATTACH_DIR}/${name}`);
|
|
1845
2449
|
}
|
|
1846
2450
|
return relPaths;
|
|
@@ -1867,6 +2471,10 @@ var startQuery = (config, text, resuming, queryFn, abortController, log, { envOv
|
|
|
1867
2471
|
const options = {
|
|
1868
2472
|
cwd: config.worktreePath,
|
|
1869
2473
|
permissionMode: planMode ? "plan" : "bypassPermissions",
|
|
2474
|
+
// Headless relay: no TTY/TUI can answer an interactive ask, so AskUserQuestion
|
|
2475
|
+
// would fail the whole turn. Block it — the model asks in plain text instead,
|
|
2476
|
+
// which the human reads in web/Feishu and replies to as a new turn.
|
|
2477
|
+
disallowedTools: ["AskUserQuestion"],
|
|
1870
2478
|
includePartialMessages: false,
|
|
1871
2479
|
abortController,
|
|
1872
2480
|
stderr: (line) => log(`[claude] ${line.trimEnd()}`),
|
|
@@ -2038,6 +2646,7 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2038
2646
|
};
|
|
2039
2647
|
const pendingQueue = [];
|
|
2040
2648
|
let busy = false;
|
|
2649
|
+
let reconciling = false;
|
|
2041
2650
|
let lastActivity = Date.now();
|
|
2042
2651
|
let currentTurn;
|
|
2043
2652
|
const drain = async () => {
|
|
@@ -2071,6 +2680,28 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2071
2680
|
busy = false;
|
|
2072
2681
|
}
|
|
2073
2682
|
};
|
|
2683
|
+
const reconcile = async () => {
|
|
2684
|
+
reconciling = true;
|
|
2685
|
+
try {
|
|
2686
|
+
const pending = unstartedUserMessages(await deps.worker.listEvents());
|
|
2687
|
+
let added = 0;
|
|
2688
|
+
for (const ev of pending) {
|
|
2689
|
+
if (state.seen.has(ev.sequence)) continue;
|
|
2690
|
+
state.seen.add(ev.sequence);
|
|
2691
|
+
pendingQueue.push(ev);
|
|
2692
|
+
added++;
|
|
2693
|
+
}
|
|
2694
|
+
if (added > 0) {
|
|
2695
|
+
log(`reconciled ${added} queued message(s) from transcript`);
|
|
2696
|
+
lastActivity = Date.now();
|
|
2697
|
+
void drain();
|
|
2698
|
+
}
|
|
2699
|
+
} catch (e) {
|
|
2700
|
+
log(`reconcile failed: ${String(e)}`);
|
|
2701
|
+
} finally {
|
|
2702
|
+
reconciling = false;
|
|
2703
|
+
}
|
|
2704
|
+
};
|
|
2074
2705
|
const es = subscribeStream(
|
|
2075
2706
|
// live-only: the server now replays history to viewers, but the child must
|
|
2076
2707
|
// not re-receive (and re-run) past user_messages on connect/resume.
|
|
@@ -2092,8 +2723,11 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2092
2723
|
},
|
|
2093
2724
|
// Report active only now that we're subscribed — before this, a message
|
|
2094
2725
|
// sent right after spawn would be missed (no replay on the live stream).
|
|
2726
|
+
// Then reconcile against the durable transcript to pick up anything that
|
|
2727
|
+
// landed while we weren't subscribed (initial spawn or a reconnect gap).
|
|
2095
2728
|
() => {
|
|
2096
2729
|
void deps.worker.setActive(true).then(() => log("\u25B2 active (subscribed)")).catch((e) => log(`status(active) failed: ${String(e)}`));
|
|
2730
|
+
void reconcile();
|
|
2097
2731
|
}
|
|
2098
2732
|
);
|
|
2099
2733
|
const idleMs = idleMsFromEnv();
|
|
@@ -2101,7 +2735,7 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2101
2735
|
const reaped = new Promise((resolve2) => {
|
|
2102
2736
|
idleTimer = setInterval(
|
|
2103
2737
|
() => {
|
|
2104
|
-
if (shouldReap(lastActivity, Date.now(), busy, pendingQueue.length, idleMs)) {
|
|
2738
|
+
if (!reconciling && shouldReap(lastActivity, Date.now(), busy, pendingQueue.length, idleMs)) {
|
|
2105
2739
|
log("idle \u2014 shutting down (resumes on next message)");
|
|
2106
2740
|
resolve2();
|
|
2107
2741
|
}
|
|
@@ -2446,7 +3080,7 @@ import { hostname as osHostname } from "node:os";
|
|
|
2446
3080
|
import { spawn } from "node:child_process";
|
|
2447
3081
|
import { randomUUID } from "node:crypto";
|
|
2448
3082
|
import { existsSync as existsSync5 } from "node:fs";
|
|
2449
|
-
import { dirname as dirname2, join as
|
|
3083
|
+
import { dirname as dirname2, join as join7 } from "node:path";
|
|
2450
3084
|
import { fileURLToPath } from "node:url";
|
|
2451
3085
|
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
2452
3086
|
|
|
@@ -2536,7 +3170,7 @@ var generateTitle = async (input) => {
|
|
|
2536
3170
|
|
|
2537
3171
|
// src/session/worktree.ts
|
|
2538
3172
|
import { execFileSync, spawnSync as spawnSync2 } from "node:child_process";
|
|
2539
|
-
import { appendFileSync, existsSync as existsSync4, mkdirSync, readFileSync as
|
|
3173
|
+
import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5 } from "node:fs";
|
|
2540
3174
|
import { dirname, isAbsolute as isAbsolute2, resolve } from "node:path";
|
|
2541
3175
|
var createWorktree = (input) => {
|
|
2542
3176
|
if (existsSync4(input.worktreePath))
|
|
@@ -2549,7 +3183,7 @@ var createWorktree = (input) => {
|
|
|
2549
3183
|
throw new Error(
|
|
2550
3184
|
`repo has no commits (unborn HEAD) \u2014 create an initial commit before starting sessions`
|
|
2551
3185
|
);
|
|
2552
|
-
|
|
3186
|
+
mkdirSync3(dirname(input.worktreePath), { recursive: true });
|
|
2553
3187
|
const branch = `baton/${input.sessionCode}`;
|
|
2554
3188
|
const r = spawnSync2(
|
|
2555
3189
|
"git",
|
|
@@ -2577,9 +3211,9 @@ var ensureExcluded = (repo, pattern) => {
|
|
|
2577
3211
|
if (r.status !== 0) return;
|
|
2578
3212
|
const rel = r.stdout.trim();
|
|
2579
3213
|
const path = isAbsolute2(rel) ? rel : resolve(repo, rel);
|
|
2580
|
-
const current = existsSync4(path) ?
|
|
3214
|
+
const current = existsSync4(path) ? readFileSync5(path, "utf8") : "";
|
|
2581
3215
|
if (current.split("\n").includes(pattern)) return;
|
|
2582
|
-
|
|
3216
|
+
mkdirSync3(dirname(path), { recursive: true });
|
|
2583
3217
|
const sep = current === "" || current.endsWith("\n") ? "" : "\n";
|
|
2584
3218
|
appendFileSync(path, `${sep}${pattern}
|
|
2585
3219
|
`, "utf8");
|
|
@@ -2603,7 +3237,7 @@ var streamWedged = (isOpen, okAt, now, thresholdMs) => !isOpen && stale(okAt, no
|
|
|
2603
3237
|
// src/worker/daemon.ts
|
|
2604
3238
|
var binPath = () => {
|
|
2605
3239
|
const here = dirname2(fileURLToPath(import.meta.url));
|
|
2606
|
-
const devShim =
|
|
3240
|
+
const devShim = join7(here, "..", "..", "bin", "baton.mjs");
|
|
2607
3241
|
return existsSync5(devShim) ? devShim : fileURLToPath(import.meta.url);
|
|
2608
3242
|
};
|
|
2609
3243
|
var runWorkerDaemon = async (cfg, client, signal) => {
|
|
@@ -2673,7 +3307,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2673
3307
|
let worktreePath = session2.worktreePath;
|
|
2674
3308
|
if (!session2.agentSessionId || !worktreePath) {
|
|
2675
3309
|
const agentSessionId = randomUUID();
|
|
2676
|
-
worktreePath =
|
|
3310
|
+
worktreePath = join7(worktreeDir, slug(`${name}-${agentSessionId.slice(0, 8)}`));
|
|
2677
3311
|
createWorktree({
|
|
2678
3312
|
repo,
|
|
2679
3313
|
worktreePath,
|
|
@@ -2684,7 +3318,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2684
3318
|
log(`materialized session #${sessionId} \u2192 ${worktreePath}`);
|
|
2685
3319
|
}
|
|
2686
3320
|
ensureExcluded(repo, PROJECT_CONFIG_NAME);
|
|
2687
|
-
saveProjectConfig(
|
|
3321
|
+
saveProjectConfig(join7(worktreePath, PROJECT_CONFIG_NAME), worktreeConfig(cfg));
|
|
2688
3322
|
spawnChild(sessionId, worktreePath);
|
|
2689
3323
|
};
|
|
2690
3324
|
const onStop = (sessionId) => {
|
|
@@ -2787,18 +3421,18 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2787
3421
|
|
|
2788
3422
|
// src/worker/machine-id.ts
|
|
2789
3423
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2790
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
3424
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2791
3425
|
import { homedir as homedir3 } from "node:os";
|
|
2792
|
-
import { dirname as dirname3, join as
|
|
2793
|
-
var machineIdPath = (env = process.env) =>
|
|
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");
|
|
2794
3428
|
var readOrCreateMachineId = (path = machineIdPath()) => {
|
|
2795
3429
|
if (existsSync6(path)) {
|
|
2796
|
-
const s =
|
|
3430
|
+
const s = readFileSync6(path, "utf8").trim();
|
|
2797
3431
|
if (s) return s;
|
|
2798
3432
|
}
|
|
2799
3433
|
const id = randomUUID2();
|
|
2800
|
-
|
|
2801
|
-
|
|
3434
|
+
mkdirSync4(dirname3(path), { recursive: true });
|
|
3435
|
+
writeFileSync4(path, `${id}
|
|
2802
3436
|
`, "utf8");
|
|
2803
3437
|
return id;
|
|
2804
3438
|
};
|
|
@@ -3066,6 +3700,8 @@ var main = defineCommand({
|
|
|
3066
3700
|
init,
|
|
3067
3701
|
worker,
|
|
3068
3702
|
session,
|
|
3703
|
+
relay,
|
|
3704
|
+
channel,
|
|
3069
3705
|
workspace,
|
|
3070
3706
|
project,
|
|
3071
3707
|
requirement,
|