@elhu/pit 0.1.1
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/README.md +380 -0
- package/dist/adapters/claude-code.d.ts +70 -0
- package/dist/adapters/claude-code.d.ts.map +1 -0
- package/dist/adapters/claude-code.js +166 -0
- package/dist/adapters/claude-code.js.map +1 -0
- package/dist/adapters/index.d.ts +16 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +49 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/opencode.d.ts +53 -0
- package/dist/adapters/opencode.d.ts.map +1 -0
- package/dist/adapters/opencode.js +120 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/adapters/process-utils.d.ts +29 -0
- package/dist/adapters/process-utils.d.ts.map +1 -0
- package/dist/adapters/process-utils.js +96 -0
- package/dist/adapters/process-utils.js.map +1 -0
- package/dist/adapters/types.d.ts +41 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +6 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/assets.generated.d.ts +13 -0
- package/dist/assets.generated.d.ts.map +1 -0
- package/dist/assets.generated.js +162 -0
- package/dist/assets.generated.js.map +1 -0
- package/dist/beads.d.ts +85 -0
- package/dist/beads.d.ts.map +1 -0
- package/dist/beads.js +120 -0
- package/dist/beads.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +39 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/add.d.ts +10 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +58 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/cleanup.d.ts +13 -0
- package/dist/commands/cleanup.d.ts.map +1 -0
- package/dist/commands/cleanup.js +174 -0
- package/dist/commands/cleanup.js.map +1 -0
- package/dist/commands/daemon.d.ts +3 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +162 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/init.d.ts +20 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +125 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/install-keybinding.d.ts +61 -0
- package/dist/commands/install-keybinding.d.ts.map +1 -0
- package/dist/commands/install-keybinding.js +138 -0
- package/dist/commands/install-keybinding.js.map +1 -0
- package/dist/commands/install-status.d.ts +35 -0
- package/dist/commands/install-status.d.ts.map +1 -0
- package/dist/commands/install-status.js +115 -0
- package/dist/commands/install-status.js.map +1 -0
- package/dist/commands/log.d.ts +7 -0
- package/dist/commands/log.d.ts.map +1 -0
- package/dist/commands/log.js +60 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/commands/pause.d.ts +12 -0
- package/dist/commands/pause.d.ts.map +1 -0
- package/dist/commands/pause.js +47 -0
- package/dist/commands/pause.js.map +1 -0
- package/dist/commands/resume.d.ts +12 -0
- package/dist/commands/resume.d.ts.map +1 -0
- package/dist/commands/resume.js +59 -0
- package/dist/commands/resume.js.map +1 -0
- package/dist/commands/shared.d.ts +7 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/commands/shared.js +56 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/commands/start.d.ts +12 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +274 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/status.d.ts +24 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +101 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/stop.d.ts +11 -0
- package/dist/commands/stop.d.ts.map +1 -0
- package/dist/commands/stop.js +52 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/commands/teardown.d.ts +15 -0
- package/dist/commands/teardown.d.ts.map +1 -0
- package/dist/commands/teardown.js +72 -0
- package/dist/commands/teardown.js.map +1 -0
- package/dist/config.d.ts +58 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +129 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon/client.d.ts +38 -0
- package/dist/daemon/client.d.ts.map +1 -0
- package/dist/daemon/client.js +254 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/context.d.ts +63 -0
- package/dist/daemon/context.d.ts.map +1 -0
- package/dist/daemon/context.js +14 -0
- package/dist/daemon/context.js.map +1 -0
- package/dist/daemon/handlers.d.ts +79 -0
- package/dist/daemon/handlers.d.ts.map +1 -0
- package/dist/daemon/handlers.js +1260 -0
- package/dist/daemon/handlers.js.map +1 -0
- package/dist/daemon/index.d.ts +6 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +7 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/lifecycle.d.ts +56 -0
- package/dist/daemon/lifecycle.d.ts.map +1 -0
- package/dist/daemon/lifecycle.js +341 -0
- package/dist/daemon/lifecycle.js.map +1 -0
- package/dist/daemon/protocol.d.ts +174 -0
- package/dist/daemon/protocol.d.ts.map +1 -0
- package/dist/daemon/protocol.js +3 -0
- package/dist/daemon/protocol.js.map +1 -0
- package/dist/daemon/recovery.d.ts +37 -0
- package/dist/daemon/recovery.d.ts.map +1 -0
- package/dist/daemon/recovery.js +197 -0
- package/dist/daemon/recovery.js.map +1 -0
- package/dist/daemon/server.d.ts +31 -0
- package/dist/daemon/server.d.ts.map +1 -0
- package/dist/daemon/server.js +294 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/socket.d.ts +18 -0
- package/dist/daemon/socket.d.ts.map +1 -0
- package/dist/daemon/socket.js +36 -0
- package/dist/daemon/socket.js.map +1 -0
- package/dist/daemon/state.d.ts +60 -0
- package/dist/daemon/state.d.ts.map +1 -0
- package/dist/daemon/state.js +156 -0
- package/dist/daemon/state.js.map +1 -0
- package/dist/daemon/systemd.d.ts +19 -0
- package/dist/daemon/systemd.d.ts.map +1 -0
- package/dist/daemon/systemd.js +131 -0
- package/dist/daemon/systemd.js.map +1 -0
- package/dist/hooks/claude-code-hook.d.ts +32 -0
- package/dist/hooks/claude-code-hook.d.ts.map +1 -0
- package/dist/hooks/claude-code-hook.js +112 -0
- package/dist/hooks/claude-code-hook.js.map +1 -0
- package/dist/instructions-template.d.ts +9 -0
- package/dist/instructions-template.d.ts.map +1 -0
- package/dist/instructions-template.js +123 -0
- package/dist/instructions-template.js.map +1 -0
- package/dist/logger.d.ts +25 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +44 -0
- package/dist/logger.js.map +1 -0
- package/dist/loop.d.ts +88 -0
- package/dist/loop.d.ts.map +1 -0
- package/dist/loop.js +161 -0
- package/dist/loop.js.map +1 -0
- package/dist/orchestrator-instructions-template.d.ts +13 -0
- package/dist/orchestrator-instructions-template.d.ts.map +1 -0
- package/dist/orchestrator-instructions-template.js +147 -0
- package/dist/orchestrator-instructions-template.js.map +1 -0
- package/dist/output.d.ts +12 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +25 -0
- package/dist/output.js.map +1 -0
- package/dist/plugin/pit.js +57 -0
- package/dist/session.d.ts +55 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +135 -0
- package/dist/session.js.map +1 -0
- package/dist/setup.d.ts +92 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +382 -0
- package/dist/setup.js.map +1 -0
- package/dist/shell-quote.d.ts +16 -0
- package/dist/shell-quote.d.ts.map +1 -0
- package/dist/shell-quote.js +18 -0
- package/dist/shell-quote.js.map +1 -0
- package/dist/signals.d.ts +17 -0
- package/dist/signals.d.ts.map +1 -0
- package/dist/signals.js +26 -0
- package/dist/signals.js.map +1 -0
- package/dist/state-machine.d.ts +74 -0
- package/dist/state-machine.d.ts.map +1 -0
- package/dist/state-machine.js +153 -0
- package/dist/state-machine.js.map +1 -0
- package/dist/tmux.d.ts +101 -0
- package/dist/tmux.d.ts.map +1 -0
- package/dist/tmux.js +208 -0
- package/dist/tmux.js.map +1 -0
- package/dist/worktree.d.ts +33 -0
- package/dist/worktree.d.ts.map +1 -0
- package/dist/worktree.js +116 -0
- package/dist/worktree.js.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EpicStateMachine — per-epic state management for pit.
|
|
3
|
+
*
|
|
4
|
+
* Manages the SETUP → RUNNING → CLEARING → PAUSED / DONE lifecycle.
|
|
5
|
+
* All transitions are serialized through a per-epic mutex (via a promise chain)
|
|
6
|
+
* to prevent races between concurrent signal events and CLI commands.
|
|
7
|
+
*
|
|
8
|
+
* State transitions:
|
|
9
|
+
* SETUP → RUNNING (initial prompt sent)
|
|
10
|
+
* RUNNING → CLEARING (TICKET_COMPLETE signal)
|
|
11
|
+
* RUNNING → PAUSED (NEEDS_HUMAN_INPUT / permission / unrecognized idle / ticket timeout)
|
|
12
|
+
* RUNNING → DONE (EPIC_COMPLETE signal)
|
|
13
|
+
* RUNNING → PAUSED (manual pit pause)
|
|
14
|
+
* CLEARING → RUNNING (/clear + re-prompt sent)
|
|
15
|
+
* CLEARING → PAUSED (CLEARING pipeline failed)
|
|
16
|
+
* PAUSED → CLEARING (pit resume, no message)
|
|
17
|
+
* PAUSED → RUNNING (pit resume --message)
|
|
18
|
+
*/
|
|
19
|
+
export type MachineState = "SETUP" | "RUNNING" | "CLEARING" | "PAUSED" | "DONE";
|
|
20
|
+
export type TransitionReason = "TUI_READY" | "TICKET_COMPLETE" | "EPIC_COMPLETE" | "NEEDS_HUMAN_INPUT" | "PERMISSION_ASKED" | "UNRECOGNIZED_IDLE" | "CLEARED" | "MANUAL_PAUSE" | "RESUME_NO_MESSAGE" | "RESUME_WITH_MESSAGE" | "MANUAL_STOP" | "TICKET_TIMEOUT";
|
|
21
|
+
export interface StateTransition {
|
|
22
|
+
from: MachineState;
|
|
23
|
+
to: MachineState;
|
|
24
|
+
reason: TransitionReason;
|
|
25
|
+
pauseReason?: string;
|
|
26
|
+
timestamp: string;
|
|
27
|
+
}
|
|
28
|
+
export interface EpicStateMachineOptions {
|
|
29
|
+
epic: string;
|
|
30
|
+
onTransition?: (transition: StateTransition) => void;
|
|
31
|
+
initialState?: MachineState;
|
|
32
|
+
initialPauseReason?: string | null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Error thrown when a state transition is invalid for the current state.
|
|
36
|
+
*/
|
|
37
|
+
export declare class InvalidTransitionError extends Error {
|
|
38
|
+
readonly from: MachineState;
|
|
39
|
+
readonly to: MachineState;
|
|
40
|
+
readonly reason: TransitionReason;
|
|
41
|
+
constructor(from: MachineState, to: MachineState, reason: TransitionReason);
|
|
42
|
+
}
|
|
43
|
+
export declare class EpicStateMachine {
|
|
44
|
+
private _state;
|
|
45
|
+
private _pauseReason;
|
|
46
|
+
private readonly epic;
|
|
47
|
+
private readonly onTransition?;
|
|
48
|
+
private mutexTail;
|
|
49
|
+
constructor(options: EpicStateMachineOptions);
|
|
50
|
+
/** The current state of this epic. */
|
|
51
|
+
get state(): MachineState;
|
|
52
|
+
/** The reason the epic is paused, if state is PAUSED. */
|
|
53
|
+
get pauseReason(): string | null;
|
|
54
|
+
/**
|
|
55
|
+
* Attempt a state transition, serialized through the mutex.
|
|
56
|
+
*
|
|
57
|
+
* @param to Target state
|
|
58
|
+
* @param reason Why this transition is happening
|
|
59
|
+
* @param opts Optional extra data (e.g. pauseReason for PAUSED transitions)
|
|
60
|
+
* @throws InvalidTransitionError if the transition is not valid from the current state
|
|
61
|
+
*/
|
|
62
|
+
transition(to: MachineState, reason: TransitionReason, opts?: {
|
|
63
|
+
pauseReason?: string;
|
|
64
|
+
}): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Execute a function while holding the mutex.
|
|
67
|
+
* The function receives the current state and can call transition() inside.
|
|
68
|
+
* Useful for compound operations that need to read + transition atomically.
|
|
69
|
+
*/
|
|
70
|
+
withLock<T>(fn: (state: MachineState) => Promise<T>): Promise<T>;
|
|
71
|
+
private _applyTransition;
|
|
72
|
+
private _validateTransition;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=state-machine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-machine.d.ts","sourceRoot":"","sources":["../src/state-machine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEhF,MAAM,MAAM,gBAAgB,GACxB,WAAW,GACX,iBAAiB,GACjB,eAAe,GACf,mBAAmB,GACnB,kBAAkB,GAClB,mBAAmB,GACnB,SAAS,GACT,cAAc,GACd,mBAAmB,GACnB,qBAAqB,GACrB,aAAa,GACb,gBAAgB,CAAC;AAErB,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,YAAY,CAAC;IACnB,EAAE,EAAE,YAAY,CAAC;IACjB,MAAM,EAAE,gBAAgB,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,eAAe,KAAK,IAAI,CAAC;IACrD,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;aAE7B,IAAI,EAAE,YAAY;aAClB,EAAE,EAAE,YAAY;aAChB,MAAM,EAAE,gBAAgB;gBAFxB,IAAI,EAAE,YAAY,EAClB,EAAE,EAAE,YAAY,EAChB,MAAM,EAAE,gBAAgB;CAK3C;AAmBD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAwC;IAGtE,OAAO,CAAC,SAAS,CAAoC;gBAEzC,OAAO,EAAE,uBAAuB;IAO5C,sCAAsC;IACtC,IAAI,KAAK,IAAI,YAAY,CAExB;IAED,yDAAyD;IACzD,IAAI,WAAW,IAAI,MAAM,GAAG,IAAI,CAE/B;IAED;;;;;;;OAOG;IACG,UAAU,CACd,EAAE,EAAE,YAAY,EAChB,MAAM,EAAE,gBAAgB,EACxB,IAAI,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAC9B,OAAO,CAAC,IAAI,CAAC;IAkBhB;;;;OAIG;IACG,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAetE,OAAO,CAAC,gBAAgB;IA0BxB,OAAO,CAAC,mBAAmB;CAW5B"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EpicStateMachine — per-epic state management for pit.
|
|
3
|
+
*
|
|
4
|
+
* Manages the SETUP → RUNNING → CLEARING → PAUSED / DONE lifecycle.
|
|
5
|
+
* All transitions are serialized through a per-epic mutex (via a promise chain)
|
|
6
|
+
* to prevent races between concurrent signal events and CLI commands.
|
|
7
|
+
*
|
|
8
|
+
* State transitions:
|
|
9
|
+
* SETUP → RUNNING (initial prompt sent)
|
|
10
|
+
* RUNNING → CLEARING (TICKET_COMPLETE signal)
|
|
11
|
+
* RUNNING → PAUSED (NEEDS_HUMAN_INPUT / permission / unrecognized idle / ticket timeout)
|
|
12
|
+
* RUNNING → DONE (EPIC_COMPLETE signal)
|
|
13
|
+
* RUNNING → PAUSED (manual pit pause)
|
|
14
|
+
* CLEARING → RUNNING (/clear + re-prompt sent)
|
|
15
|
+
* CLEARING → PAUSED (CLEARING pipeline failed)
|
|
16
|
+
* PAUSED → CLEARING (pit resume, no message)
|
|
17
|
+
* PAUSED → RUNNING (pit resume --message)
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Error thrown when a state transition is invalid for the current state.
|
|
21
|
+
*/
|
|
22
|
+
export class InvalidTransitionError extends Error {
|
|
23
|
+
from;
|
|
24
|
+
to;
|
|
25
|
+
reason;
|
|
26
|
+
constructor(from, to, reason) {
|
|
27
|
+
super(`Invalid transition from ${from} to ${to} (reason: ${reason})`);
|
|
28
|
+
this.from = from;
|
|
29
|
+
this.to = to;
|
|
30
|
+
this.reason = reason;
|
|
31
|
+
this.name = "InvalidTransitionError";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Per-epic state machine.
|
|
36
|
+
*
|
|
37
|
+
* State is stored in memory. Signal files are the persistent store; this
|
|
38
|
+
* class manages the in-memory representation and serializes transitions.
|
|
39
|
+
*
|
|
40
|
+
* Usage:
|
|
41
|
+
* const sm = new EpicStateMachine({ epic: "auth" });
|
|
42
|
+
* await sm.transition("RUNNING", "TUI_READY");
|
|
43
|
+
* // later:
|
|
44
|
+
* await sm.transition("CLEARING", "TICKET_COMPLETE");
|
|
45
|
+
*/
|
|
46
|
+
/** Silently swallow a rejected promise — used to keep mutex tail alive. */
|
|
47
|
+
function _swallow(_err) {
|
|
48
|
+
// intentionally empty
|
|
49
|
+
}
|
|
50
|
+
export class EpicStateMachine {
|
|
51
|
+
_state;
|
|
52
|
+
_pauseReason;
|
|
53
|
+
epic;
|
|
54
|
+
onTransition;
|
|
55
|
+
// Mutex: a promise chain that serializes transitions
|
|
56
|
+
mutexTail = Promise.resolve();
|
|
57
|
+
constructor(options) {
|
|
58
|
+
this.epic = options.epic;
|
|
59
|
+
this.onTransition = options.onTransition;
|
|
60
|
+
this._state = options.initialState ?? "SETUP";
|
|
61
|
+
this._pauseReason = options.initialPauseReason ?? null;
|
|
62
|
+
}
|
|
63
|
+
/** The current state of this epic. */
|
|
64
|
+
get state() {
|
|
65
|
+
return this._state;
|
|
66
|
+
}
|
|
67
|
+
/** The reason the epic is paused, if state is PAUSED. */
|
|
68
|
+
get pauseReason() {
|
|
69
|
+
return this._pauseReason;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Attempt a state transition, serialized through the mutex.
|
|
73
|
+
*
|
|
74
|
+
* @param to Target state
|
|
75
|
+
* @param reason Why this transition is happening
|
|
76
|
+
* @param opts Optional extra data (e.g. pauseReason for PAUSED transitions)
|
|
77
|
+
* @throws InvalidTransitionError if the transition is not valid from the current state
|
|
78
|
+
*/
|
|
79
|
+
async transition(to, reason, opts) {
|
|
80
|
+
// Enqueue onto mutex tail, catching to avoid unhandled rejection on the outer tail
|
|
81
|
+
const work = () => new Promise((resolve, reject) => {
|
|
82
|
+
try {
|
|
83
|
+
this._applyTransition(to, reason, opts);
|
|
84
|
+
resolve();
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
reject(err);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
// Chain: wait for previous work, then do ours
|
|
91
|
+
const next = this.mutexTail.then(work, work); // run even if previous failed
|
|
92
|
+
this.mutexTail = next.catch(_swallow); // swallow on tail to keep chain alive
|
|
93
|
+
return next; // caller gets the real error
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Execute a function while holding the mutex.
|
|
97
|
+
* The function receives the current state and can call transition() inside.
|
|
98
|
+
* Useful for compound operations that need to read + transition atomically.
|
|
99
|
+
*/
|
|
100
|
+
async withLock(fn) {
|
|
101
|
+
let result;
|
|
102
|
+
const work = async () => {
|
|
103
|
+
result = await fn(this._state);
|
|
104
|
+
};
|
|
105
|
+
const next = this.mutexTail.then(work);
|
|
106
|
+
this.mutexTail = next.catch(_swallow);
|
|
107
|
+
await next;
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// Private
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
_applyTransition(to, reason, opts) {
|
|
114
|
+
const from = this._state;
|
|
115
|
+
this._validateTransition(from, to, reason);
|
|
116
|
+
this._state = to;
|
|
117
|
+
if (to === "PAUSED") {
|
|
118
|
+
this._pauseReason = opts?.pauseReason ?? null;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
this._pauseReason = null;
|
|
122
|
+
}
|
|
123
|
+
const transition = {
|
|
124
|
+
from,
|
|
125
|
+
to,
|
|
126
|
+
reason,
|
|
127
|
+
pauseReason: to === "PAUSED" ? (opts?.pauseReason ?? undefined) : undefined,
|
|
128
|
+
timestamp: new Date().toISOString(),
|
|
129
|
+
};
|
|
130
|
+
this.onTransition?.(transition);
|
|
131
|
+
}
|
|
132
|
+
_validateTransition(from, to, reason) {
|
|
133
|
+
// Build a set of allowed (from, to) pairs
|
|
134
|
+
const valid = VALID_TRANSITIONS.some((t) => t.from === from && t.to === to);
|
|
135
|
+
if (!valid) {
|
|
136
|
+
throw new InvalidTransitionError(from, to, reason);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const VALID_TRANSITIONS = [
|
|
141
|
+
{ from: "SETUP", to: "RUNNING" }, // TUI ready → send initial prompt
|
|
142
|
+
{ from: "RUNNING", to: "CLEARING" }, // TICKET_COMPLETE
|
|
143
|
+
{ from: "RUNNING", to: "PAUSED" }, // NEEDS_HUMAN_INPUT / permission / manual pause / unrecognized idle
|
|
144
|
+
{ from: "RUNNING", to: "DONE" }, // EPIC_COMPLETE
|
|
145
|
+
{ from: "CLEARING", to: "RUNNING" }, // /clear + re-prompt sent
|
|
146
|
+
{ from: "CLEARING", to: "PAUSED" }, // CLEARING pipeline failed
|
|
147
|
+
{ from: "PAUSED", to: "CLEARING" }, // pit resume (no message)
|
|
148
|
+
{ from: "PAUSED", to: "RUNNING" }, // pit resume --message
|
|
149
|
+
{ from: "SETUP", to: "DONE" }, // MANUAL_STOP from setup
|
|
150
|
+
{ from: "PAUSED", to: "DONE" }, // MANUAL_STOP from paused
|
|
151
|
+
{ from: "CLEARING", to: "DONE" }, // MANUAL_STOP from clearing
|
|
152
|
+
];
|
|
153
|
+
//# sourceMappingURL=state-machine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-machine.js","sourceRoot":"","sources":["../src/state-machine.ts"],"names":[],"mappings":"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"}
|
package/dist/tmux.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tmux helpers for pit.
|
|
3
|
+
*
|
|
4
|
+
* All tmux operations shell out to the `tmux` CLI via execTmux().
|
|
5
|
+
* TmuxError captures the exit code, stderr, and original args for debugging.
|
|
6
|
+
*/
|
|
7
|
+
export declare class TmuxError extends Error {
|
|
8
|
+
readonly exitCode: number | null;
|
|
9
|
+
readonly stderr: string;
|
|
10
|
+
readonly args: string[];
|
|
11
|
+
constructor(message: string, exitCode: number | null, stderr: string, args: string[]);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Internal helper: run tmux with the given args.
|
|
15
|
+
* Throws TmuxError on non-zero exit or if tmux is not installed.
|
|
16
|
+
*/
|
|
17
|
+
export declare function execTmux(...args: string[]): Promise<{
|
|
18
|
+
stdout: string;
|
|
19
|
+
stderr: string;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Returns true if the named tmux session exists, false otherwise.
|
|
23
|
+
* Never throws for "session not found" — that is a valid false result.
|
|
24
|
+
*/
|
|
25
|
+
export declare function sessionExists(name: string): Promise<boolean>;
|
|
26
|
+
/**
|
|
27
|
+
* Creates a new detached tmux session with the given name.
|
|
28
|
+
* Throws if the session already exists or tmux is unavailable.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createSession(name: string): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Kills the named tmux session.
|
|
33
|
+
* Throws if the session does not exist or cannot be killed.
|
|
34
|
+
*/
|
|
35
|
+
export declare function killSession(name: string): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Creates a new window in the given session with the given name and working directory.
|
|
38
|
+
* Optionally sets per-window environment variables via the `-e` flag on `new-window`
|
|
39
|
+
* (NOT session-scoped `set-environment`) so each window gets its own env.
|
|
40
|
+
* Throws if the session does not exist.
|
|
41
|
+
*/
|
|
42
|
+
export declare function createWindow(session: string, name: string, cwd: string, env?: Record<string, string>): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Returns true if a window with the given name exists in the given session.
|
|
45
|
+
* Returns false if the session or window does not exist — never throws for
|
|
46
|
+
* missing session/window.
|
|
47
|
+
*/
|
|
48
|
+
export declare function windowExists(session: string, name: string): Promise<boolean>;
|
|
49
|
+
/**
|
|
50
|
+
* Returns the list of window names in the given session.
|
|
51
|
+
* Returns an empty array if the session does not exist — never throws.
|
|
52
|
+
*/
|
|
53
|
+
export declare function getWindowList(session: string): Promise<string[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Returns the name of the first window (index 0) in the given session.
|
|
56
|
+
* Returns null if the session does not exist or has no windows.
|
|
57
|
+
* Never throws.
|
|
58
|
+
*/
|
|
59
|
+
export declare function getFirstWindowName(session: string): Promise<string | null>;
|
|
60
|
+
/**
|
|
61
|
+
* Kills the named window in the given session.
|
|
62
|
+
* Throws if the window does not exist.
|
|
63
|
+
*/
|
|
64
|
+
export declare function killWindow(session: string, name: string): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Sends keys to a window in the given session.
|
|
67
|
+
* Fire-and-forget: there is no confirmation of delivery.
|
|
68
|
+
* The `--` separator prevents tmux from interpreting the keys string as flags.
|
|
69
|
+
* When autoSubmit is true, `Enter` is sent after typing to submit the input.
|
|
70
|
+
*/
|
|
71
|
+
export declare function sendKeys(session: string, window: string, keys: string, autoSubmit?: boolean): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Send a prompt to the agent by sending the text and Enter in separate calls.
|
|
74
|
+
* This is more reliable than sending them together, especially for OpenCode.
|
|
75
|
+
*/
|
|
76
|
+
export declare function sendPrompt(session: string, window: string, prompt: string): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Send a clear command to OpenCode by sending the command and Enter in separate calls.
|
|
79
|
+
* This prevents the "/" from triggering the agent selector instead of being a command.
|
|
80
|
+
*/
|
|
81
|
+
export declare function sendClearCommand(session: string, window: string, clearCommand: string): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Captures the pane output of a window in the given session.
|
|
84
|
+
* Defaults to the last 50 lines with ANSI escape codes stripped.
|
|
85
|
+
*/
|
|
86
|
+
export declare function capturePane(session: string, window: string, options?: {
|
|
87
|
+
lines?: number;
|
|
88
|
+
stripAnsi?: boolean;
|
|
89
|
+
}): Promise<string>;
|
|
90
|
+
/**
|
|
91
|
+
* Sends a BEL character (ASCII 7) to a window in the given session.
|
|
92
|
+
* Used to notify the human that attention is needed.
|
|
93
|
+
*/
|
|
94
|
+
export declare function sendBell(session: string, window: string): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Sets a tmux user option (prefixed with `@`) on the given session.
|
|
97
|
+
* User options are used for status bar content and other metadata.
|
|
98
|
+
* Example: setUserOption(session, "pit-status", "[auth: running 5/12]")
|
|
99
|
+
*/
|
|
100
|
+
export declare function setUserOption(session: string, key: string, value: string): Promise<void>;
|
|
101
|
+
//# sourceMappingURL=tmux.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tmux.d.ts","sourceRoot":"","sources":["../src/tmux.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,qBAAa,SAAU,SAAQ,KAAK;aAGhB,QAAQ,EAAE,MAAM,GAAG,IAAI;aACvB,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,MAAM,EAAE;gBAH9B,OAAO,EAAE,MAAM,EACC,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAAE;CAKjC;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAqBvF;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAWlE;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/D;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7D;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAelF;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAatE;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGhF;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7E;AAED;;;;;GAKG;AACH,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,UAAU,UAAO,GAChB,OAAO,CAAC,IAAI,CAAC,CAMf;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAO/F;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAOf;AAOD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAChD,OAAO,CAAC,MAAM,CAAC,CAcjB;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7E;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9F"}
|
package/dist/tmux.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tmux helpers for pit.
|
|
3
|
+
*
|
|
4
|
+
* All tmux operations shell out to the `tmux` CLI via execTmux().
|
|
5
|
+
* TmuxError captures the exit code, stderr, and original args for debugging.
|
|
6
|
+
*/
|
|
7
|
+
import { execFile } from "node:child_process";
|
|
8
|
+
export class TmuxError extends Error {
|
|
9
|
+
exitCode;
|
|
10
|
+
stderr;
|
|
11
|
+
args;
|
|
12
|
+
constructor(message, exitCode, stderr, args) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.exitCode = exitCode;
|
|
15
|
+
this.stderr = stderr;
|
|
16
|
+
this.args = args;
|
|
17
|
+
this.name = "TmuxError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Internal helper: run tmux with the given args.
|
|
22
|
+
* Throws TmuxError on non-zero exit or if tmux is not installed.
|
|
23
|
+
*/
|
|
24
|
+
export function execTmux(...args) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
execFile("tmux", args, (error, stdout, stderr) => {
|
|
27
|
+
if (error) {
|
|
28
|
+
if (error.code === "ENOENT") {
|
|
29
|
+
reject(new TmuxError("tmux is not installed or not in PATH", null, "", args));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
reject(new TmuxError(`tmux exited with code ${error.code ?? "unknown"}: ${stderr.trim()}`, typeof error.code === "number" ? error.code : null, stderr, args));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
resolve({ stdout, stderr });
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Returns true if the named tmux session exists, false otherwise.
|
|
41
|
+
* Never throws for "session not found" — that is a valid false result.
|
|
42
|
+
*/
|
|
43
|
+
export async function sessionExists(name) {
|
|
44
|
+
try {
|
|
45
|
+
await execTmux("has-session", "-t", name);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (err instanceof TmuxError && err.exitCode !== null) {
|
|
50
|
+
// Non-zero exit means session not found — not an error condition
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Creates a new detached tmux session with the given name.
|
|
58
|
+
* Throws if the session already exists or tmux is unavailable.
|
|
59
|
+
*/
|
|
60
|
+
export async function createSession(name) {
|
|
61
|
+
await execTmux("new-session", "-d", "-s", name);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Kills the named tmux session.
|
|
65
|
+
* Throws if the session does not exist or cannot be killed.
|
|
66
|
+
*/
|
|
67
|
+
export async function killSession(name) {
|
|
68
|
+
await execTmux("kill-session", "-t", name);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Creates a new window in the given session with the given name and working directory.
|
|
72
|
+
* Optionally sets per-window environment variables via the `-e` flag on `new-window`
|
|
73
|
+
* (NOT session-scoped `set-environment`) so each window gets its own env.
|
|
74
|
+
* Throws if the session does not exist.
|
|
75
|
+
*/
|
|
76
|
+
export async function createWindow(session, name, cwd, env) {
|
|
77
|
+
const args = ["new-window", "-t", session, "-n", name, "-c", cwd];
|
|
78
|
+
if (env) {
|
|
79
|
+
for (const [key, value] of Object.entries(env)) {
|
|
80
|
+
args.push("-e", `${key}=${value}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
await execTmux(...args);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Returns true if a window with the given name exists in the given session.
|
|
87
|
+
* Returns false if the session or window does not exist — never throws for
|
|
88
|
+
* missing session/window.
|
|
89
|
+
*/
|
|
90
|
+
export async function windowExists(session, name) {
|
|
91
|
+
try {
|
|
92
|
+
const { stdout } = await execTmux("list-windows", "-t", session, "-F", "#{window_name}");
|
|
93
|
+
return stdout
|
|
94
|
+
.split("\n")
|
|
95
|
+
.map((l) => l.trim())
|
|
96
|
+
.filter(Boolean)
|
|
97
|
+
.includes(name);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (err instanceof TmuxError && err.exitCode !== null) {
|
|
101
|
+
// Non-zero exit — session not found or other non-fatal error
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Returns the list of window names in the given session.
|
|
109
|
+
* Returns an empty array if the session does not exist — never throws.
|
|
110
|
+
*/
|
|
111
|
+
export async function getWindowList(session) {
|
|
112
|
+
try {
|
|
113
|
+
const { stdout } = await execTmux("list-windows", "-t", session, "-F", "#{window_name}");
|
|
114
|
+
return stdout
|
|
115
|
+
.split("\n")
|
|
116
|
+
.map((l) => l.trim())
|
|
117
|
+
.filter(Boolean);
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
if (err instanceof TmuxError && err.exitCode !== null) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Returns the name of the first window (index 0) in the given session.
|
|
128
|
+
* Returns null if the session does not exist or has no windows.
|
|
129
|
+
* Never throws.
|
|
130
|
+
*/
|
|
131
|
+
export async function getFirstWindowName(session) {
|
|
132
|
+
const windows = await getWindowList(session);
|
|
133
|
+
return windows.length > 0 ? windows[0] : null;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Kills the named window in the given session.
|
|
137
|
+
* Throws if the window does not exist.
|
|
138
|
+
*/
|
|
139
|
+
export async function killWindow(session, name) {
|
|
140
|
+
await execTmux("kill-window", "-t", `${session}:${name}`);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Sends keys to a window in the given session.
|
|
144
|
+
* Fire-and-forget: there is no confirmation of delivery.
|
|
145
|
+
* The `--` separator prevents tmux from interpreting the keys string as flags.
|
|
146
|
+
* When autoSubmit is true, `Enter` is sent after typing to submit the input.
|
|
147
|
+
*/
|
|
148
|
+
export async function sendKeys(session, window, keys, autoSubmit = true) {
|
|
149
|
+
const args = ["send-keys", "-t", `${session}:${window}`, "--", keys];
|
|
150
|
+
if (autoSubmit) {
|
|
151
|
+
args.push("Enter");
|
|
152
|
+
}
|
|
153
|
+
await execTmux(...args);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Send a prompt to the agent by sending the text and Enter in separate calls.
|
|
157
|
+
* This is more reliable than sending them together, especially for OpenCode.
|
|
158
|
+
*/
|
|
159
|
+
export async function sendPrompt(session, window, prompt) {
|
|
160
|
+
// Send the prompt text without Enter
|
|
161
|
+
await sendKeys(session, window, prompt, false);
|
|
162
|
+
// Wait a small moment for the text to appear
|
|
163
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
164
|
+
// Send Enter separately
|
|
165
|
+
await sendKeys(session, window, "Enter", false);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Send a clear command to OpenCode by sending the command and Enter in separate calls.
|
|
169
|
+
* This prevents the "/" from triggering the agent selector instead of being a command.
|
|
170
|
+
*/
|
|
171
|
+
export async function sendClearCommand(session, window, clearCommand) {
|
|
172
|
+
// Send the clear command text without Enter
|
|
173
|
+
await sendKeys(session, window, clearCommand, false);
|
|
174
|
+
// Wait for the command to be processed (longer delay than sendPrompt)
|
|
175
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
176
|
+
// Send Enter separately
|
|
177
|
+
await sendKeys(session, window, "Enter", false);
|
|
178
|
+
}
|
|
179
|
+
/** ANSI escape code stripping regex: covers CSI, OSC, character set, and SI sequences. */
|
|
180
|
+
const ANSI_REGEX =
|
|
181
|
+
// eslint-disable-next-line no-control-regex
|
|
182
|
+
/\x1b\[[0-9;]*[a-zA-Z]|\x1b\].*?(?:\x07|\x1b\\)|\x1b[()][0-9A-Z]|\x1b[>=<]|\x0f/g;
|
|
183
|
+
/**
|
|
184
|
+
* Captures the pane output of a window in the given session.
|
|
185
|
+
* Defaults to the last 50 lines with ANSI escape codes stripped.
|
|
186
|
+
*/
|
|
187
|
+
export async function capturePane(session, window, options) {
|
|
188
|
+
const lines = options?.lines ?? 50;
|
|
189
|
+
const stripAnsi = options?.stripAnsi ?? true;
|
|
190
|
+
const { stdout } = await execTmux("capture-pane", "-t", `${session}:${window}`, "-p", "-S", `-${lines}`);
|
|
191
|
+
return stripAnsi ? stdout.replace(ANSI_REGEX, "") : stdout;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Sends a BEL character (ASCII 7) to a window in the given session.
|
|
195
|
+
* Used to notify the human that attention is needed.
|
|
196
|
+
*/
|
|
197
|
+
export async function sendBell(session, window) {
|
|
198
|
+
await execTmux("send-keys", "-t", `${session}:${window}`, String.fromCharCode(7));
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Sets a tmux user option (prefixed with `@`) on the given session.
|
|
202
|
+
* User options are used for status bar content and other metadata.
|
|
203
|
+
* Example: setUserOption(session, "pit-status", "[auth: running 5/12]")
|
|
204
|
+
*/
|
|
205
|
+
export async function setUserOption(session, key, value) {
|
|
206
|
+
await execTmux("set-option", "-t", session, `@${key}`, value);
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=tmux.js.map
|
package/dist/tmux.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tmux.js","sourceRoot":"","sources":["../src/tmux.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface WorktreeResult {
|
|
2
|
+
path: string;
|
|
3
|
+
created: boolean;
|
|
4
|
+
branch: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Creates a git worktree for `epic` under `worktreeDir`.
|
|
8
|
+
*
|
|
9
|
+
* Idempotent: if the worktree already exists (directory + .git file), returns
|
|
10
|
+
* `{ created: false }` without running any git commands.
|
|
11
|
+
*
|
|
12
|
+
* Branch-exists fallback: if `git worktree add -b <branch>` fails because the
|
|
13
|
+
* branch already exists, retries with `git worktree add <path> <branch>`
|
|
14
|
+
* (checkout existing branch, no -b).
|
|
15
|
+
*/
|
|
16
|
+
export declare function createWorktree(worktreeDir: string, epic: string, baseBranch: string): Promise<WorktreeResult>;
|
|
17
|
+
/**
|
|
18
|
+
* Returns true if `<worktreeDir>/<epic>` exists as a directory AND contains a
|
|
19
|
+
* `.git` file (worktrees have a .git file, not a .git directory).
|
|
20
|
+
*/
|
|
21
|
+
export declare function worktreeExists(worktreeDir: string, epic: string): Promise<boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* Removes the worktree at `<worktreeDir>/<epic>` via `git worktree remove --force`.
|
|
24
|
+
* Throws on failure.
|
|
25
|
+
*/
|
|
26
|
+
export declare function removeWorktree(worktreeDir: string, epic: string): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Returns true if the worktree at the given absolute path has uncommitted
|
|
29
|
+
* changes (staged, unstaged, or untracked files).
|
|
30
|
+
* Returns false if the path does not exist or is not a git worktree.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isWorktreeDirty(worktreePath: string): Promise<boolean>;
|
|
33
|
+
//# sourceMappingURL=worktree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../src/worktree.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAMD;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,cAAc,CAAC,CAgDzB;AAMD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAaxF;AAMD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGrF;AAMD;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ5E"}
|