relay-flow 0.2.3-alpha → 0.2.4-alpha
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/README.md +116 -10
- package/cmd/relay-flow/main.go +2 -0
- package/cmd/relay-flow/pi_wiring_test.go +64 -0
- package/cmd/relay-flow/serve.go +2 -0
- package/examples/beads-workflow.yaml +3 -3
- package/examples/config-reference.yaml +144 -0
- package/examples/minimal-beads-task-workflow.yaml +34 -0
- package/examples/minimal-jira-task-workflow.yaml +68 -0
- package/examples/workflow-reference.yaml +111 -0
- package/internal/harness/opencode/opencode_test.go +1 -1
- package/internal/harness/opencode/repo_setup.go +1 -1
- package/internal/harness/pi/config_test.go +46 -0
- package/internal/harness/pi/lifecycle_test.go +69 -0
- package/internal/harness/pi/pi.go +261 -0
- package/internal/harness/pi/pi_test.go +322 -0
- package/internal/harness/pi/prompt_test.go +121 -0
- package/internal/harness/pi/testdata/pi-0.84.1/capture.json +126 -0
- package/internal/harness/pi/testdata/pi-0.84.1/noninteractive-output.txt +11 -0
- package/internal/harness/pi/testdata/pi-0.84.1/tui-output-sanitized.txt +17 -0
- package/internal/harness/pi/validation_test.go +168 -0
- package/internal/runner/herdr/baseref.go +60 -0
- package/internal/runner/herdr/herdr.go +578 -0
- package/internal/runner/herdr/herdr_test.go +688 -0
- package/internal/runner/herdr/herdrcli/contract.go +128 -0
- package/internal/runner/herdr/herdrcli/exec.go +68 -0
- package/internal/runner/herdr/herdrcli/herdrcli_test.go +274 -0
- package/internal/runner/herdr/herdrcli/live_test.go +132 -0
- package/internal/runner/herdr/herdrcli/operations.go +281 -0
- package/internal/runner/herdr/herdrcli/response.go +116 -0
- package/internal/runner/herdr/herdrcli/testdata/empty-panes.json +1 -0
- package/internal/runner/herdr/herdrcli/testdata/empty-tabs.json +1 -0
- package/internal/runner/herdr/herdrcli/testdata/error-not-git-worktree.json +1 -0
- package/internal/runner/herdr/herdrcli/testdata/error-pane-not-found.json +1 -0
- package/internal/runner/herdr/herdrcli/testdata/error-workspace-not-found.json +1 -0
- package/internal/runner/herdr/herdrcli/testdata/error-worktree-not-found.json +1 -0
- package/internal/runner/herdr/herdrcli/testdata/malformed.json +1 -0
- package/internal/runner/herdr/herdrcli/testdata/pane-close.json +6 -0
- package/internal/runner/herdr/herdrcli/testdata/pane-get.json +25 -0
- package/internal/runner/herdr/herdrcli/testdata/pane-list.json +45 -0
- package/internal/runner/herdr/herdrcli/testdata/pane-process-info-shell.json +22 -0
- package/internal/runner/herdr/herdrcli/testdata/pane-process-info.json +23 -0
- package/internal/runner/herdr/herdrcli/testdata/pane-rename.json +23 -0
- package/internal/runner/herdr/herdrcli/testdata/snapshot.json +213 -0
- package/internal/runner/herdr/herdrcli/testdata/strict-herdr.sh +175 -0
- package/internal/runner/herdr/herdrcli/testdata/tab-create.json +31 -0
- package/internal/runner/herdr/herdrcli/testdata/tab-list.json +26 -0
- package/internal/runner/herdr/herdrcli/testdata/workspace-close.json +6 -0
- package/internal/runner/herdr/herdrcli/testdata/worktree-create.json +58 -0
- package/internal/runner/herdr/herdrcli/testdata/worktree-list.json +35 -0
- package/internal/runner/herdr/herdrcli/testdata/worktree-open.json +59 -0
- package/internal/task/beads/beads.go +0 -16
- package/internal/task/beads/config_compatibility_test.go +7 -8
- package/internal/task/jira/filters_test.go +32 -6
- package/internal/task/jira/jira.go +27 -17
- package/package.json +1 -1
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
package herdrcli
|
|
2
|
+
|
|
3
|
+
import "context"
|
|
4
|
+
|
|
5
|
+
type workspaceResponse struct {
|
|
6
|
+
ID string `json:"workspace_id"`
|
|
7
|
+
Label string `json:"label"`
|
|
8
|
+
Worktree *worktreeRefwithID `json:"worktree"`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type worktreeRefwithID struct {
|
|
12
|
+
CheckoutPath string `json:"checkout_path"`
|
|
13
|
+
RepoName string `json:"repo_name"`
|
|
14
|
+
RepoRoot string `json:"repo_root"`
|
|
15
|
+
IsLinked bool `json:"is_linked_worktree"`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type worktreeResponse struct {
|
|
19
|
+
Path string `json:"path"`
|
|
20
|
+
Branch string `json:"branch"`
|
|
21
|
+
IsLinked bool `json:"is_linked_worktree"`
|
|
22
|
+
OpenWorkspaceID string `json:"open_workspace_id"`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type worktreeSourceResponse struct {
|
|
26
|
+
RepoName string `json:"repo_name"`
|
|
27
|
+
RepoRoot string `json:"repo_root"`
|
|
28
|
+
SourceCheckoutPath string `json:"source_checkout_path"`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type tabResponse struct {
|
|
32
|
+
ID string `json:"tab_id"`
|
|
33
|
+
WorkspaceID string `json:"workspace_id"`
|
|
34
|
+
Label string `json:"label"`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type paneResponse struct {
|
|
38
|
+
ID string `json:"pane_id"`
|
|
39
|
+
TerminalID string `json:"terminal_id"`
|
|
40
|
+
WorkspaceID string `json:"workspace_id"`
|
|
41
|
+
TabID string `json:"tab_id"`
|
|
42
|
+
Label string `json:"label"`
|
|
43
|
+
CWD string `json:"cwd"`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type processInfoResponse struct {
|
|
47
|
+
PaneID string `json:"pane_id"`
|
|
48
|
+
ShellPID *uint32 `json:"shell_pid"`
|
|
49
|
+
ForegroundProcesses []foregroundProcessResponse `json:"foreground_processes"`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type foregroundProcessResponse struct {
|
|
53
|
+
PID uint32 `json:"pid"`
|
|
54
|
+
Name string `json:"name"`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func (c *CLI) Snapshot(ctx context.Context) (Snapshot, error) {
|
|
58
|
+
var response struct {
|
|
59
|
+
Snapshot struct {
|
|
60
|
+
Workspaces []workspaceResponse `json:"workspaces"`
|
|
61
|
+
Tabs []tabResponse `json:"tabs"`
|
|
62
|
+
Panes []paneResponse `json:"panes"`
|
|
63
|
+
} `json:"snapshot"`
|
|
64
|
+
}
|
|
65
|
+
if err := c.runJSON(ctx, "api snapshot", &response, "api", "snapshot"); err != nil {
|
|
66
|
+
return Snapshot{}, err
|
|
67
|
+
}
|
|
68
|
+
return Snapshot{
|
|
69
|
+
Workspaces: convertWorkspaces(response.Snapshot.Workspaces),
|
|
70
|
+
Tabs: convertTabs(response.Snapshot.Tabs),
|
|
71
|
+
Panes: convertPanes(response.Snapshot.Panes),
|
|
72
|
+
}, nil
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func (c *CLI) WorktreeList(ctx context.Context, repoPath string) (WorktreeListing, error) {
|
|
76
|
+
var response struct {
|
|
77
|
+
Source worktreeSourceResponse `json:"source"`
|
|
78
|
+
Worktrees []worktreeResponse `json:"worktrees"`
|
|
79
|
+
}
|
|
80
|
+
if err := c.runJSON(ctx, "worktree list", &response, "worktree", "list", "--cwd", repoPath); err != nil {
|
|
81
|
+
return WorktreeListing{}, err
|
|
82
|
+
}
|
|
83
|
+
return WorktreeListing{
|
|
84
|
+
Source: WorktreeSource{
|
|
85
|
+
RepoName: response.Source.RepoName,
|
|
86
|
+
RepoRoot: response.Source.RepoRoot,
|
|
87
|
+
SourceCheckoutPath: response.Source.SourceCheckoutPath,
|
|
88
|
+
},
|
|
89
|
+
Worktrees: convertWorktrees(response.Worktrees),
|
|
90
|
+
}, nil
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// WorktreeCreate creates the ticket branch checkout and opens it as its own
|
|
94
|
+
// workspace. Herdr reuses an existing branch, so base applies only when the
|
|
95
|
+
// branch does not exist yet and prior agent commits are never discarded.
|
|
96
|
+
func (c *CLI) WorktreeCreate(ctx context.Context, repoPath, branch, base, label string) (Workspace, error) {
|
|
97
|
+
args := []string{
|
|
98
|
+
"worktree", "create",
|
|
99
|
+
"--cwd", repoPath,
|
|
100
|
+
"--branch", branch,
|
|
101
|
+
"--base", base,
|
|
102
|
+
"--label", label,
|
|
103
|
+
"--no-focus",
|
|
104
|
+
}
|
|
105
|
+
return c.workspaceFromWorktree(ctx, "worktree create", args)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// WorktreeOpen opens the existing ticket checkout, reusing the workspace when
|
|
109
|
+
// it is already open. It returns ErrWorktreeNotFound when no checkout exists
|
|
110
|
+
// for the branch.
|
|
111
|
+
func (c *CLI) WorktreeOpen(ctx context.Context, repoPath, branch, label string) (Workspace, error) {
|
|
112
|
+
args := []string{
|
|
113
|
+
"worktree", "open",
|
|
114
|
+
"--cwd", repoPath,
|
|
115
|
+
"--branch", branch,
|
|
116
|
+
"--label", label,
|
|
117
|
+
"--no-focus",
|
|
118
|
+
}
|
|
119
|
+
return c.workspaceFromWorktree(ctx, "worktree open", args)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
func (c *CLI) workspaceFromWorktree(ctx context.Context, operation string, args []string) (Workspace, error) {
|
|
123
|
+
var response struct {
|
|
124
|
+
Workspace workspaceResponse `json:"workspace"`
|
|
125
|
+
}
|
|
126
|
+
if err := c.runJSON(ctx, operation, &response, args...); err != nil {
|
|
127
|
+
return Workspace{}, err
|
|
128
|
+
}
|
|
129
|
+
return convertWorkspace(response.Workspace), nil
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
func (c *CLI) CreateTab(ctx context.Context, workspaceID, cwd, label string) (Tab, Pane, error) {
|
|
133
|
+
args := []string{
|
|
134
|
+
"tab", "create",
|
|
135
|
+
"--workspace", workspaceID,
|
|
136
|
+
"--cwd", cwd,
|
|
137
|
+
"--label", label,
|
|
138
|
+
"--no-focus",
|
|
139
|
+
}
|
|
140
|
+
var response struct {
|
|
141
|
+
Tab tabResponse `json:"tab"`
|
|
142
|
+
RootPane paneResponse `json:"root_pane"`
|
|
143
|
+
}
|
|
144
|
+
if err := c.runJSON(ctx, "tab create", &response, args...); err != nil {
|
|
145
|
+
return Tab{}, Pane{}, err
|
|
146
|
+
}
|
|
147
|
+
return convertTab(response.Tab), convertPane(response.RootPane), nil
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
func (c *CLI) ListTabs(ctx context.Context, workspaceID string) ([]Tab, error) {
|
|
151
|
+
var response struct {
|
|
152
|
+
Tabs []tabResponse `json:"tabs"`
|
|
153
|
+
}
|
|
154
|
+
if err := c.runJSON(ctx, "tab list", &response, "tab", "list", "--workspace", workspaceID); err != nil {
|
|
155
|
+
return nil, err
|
|
156
|
+
}
|
|
157
|
+
return convertTabs(response.Tabs), nil
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
func (c *CLI) ListPanes(ctx context.Context, workspaceID string) ([]Pane, error) {
|
|
161
|
+
var response struct {
|
|
162
|
+
Panes []paneResponse `json:"panes"`
|
|
163
|
+
}
|
|
164
|
+
if err := c.runJSON(ctx, "pane list", &response, "pane", "list", "--workspace", workspaceID); err != nil {
|
|
165
|
+
return nil, err
|
|
166
|
+
}
|
|
167
|
+
return convertPanes(response.Panes), nil
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
func (c *CLI) GetPane(ctx context.Context, paneID string) (Pane, error) {
|
|
171
|
+
var response struct {
|
|
172
|
+
Pane paneResponse `json:"pane"`
|
|
173
|
+
}
|
|
174
|
+
if err := c.runJSON(ctx, "pane get", &response, "pane", "get", paneID); err != nil {
|
|
175
|
+
return Pane{}, err
|
|
176
|
+
}
|
|
177
|
+
return convertPane(response.Pane), nil
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
func (c *CLI) ProcessInfo(ctx context.Context, paneID string) (ProcessInfo, error) {
|
|
181
|
+
var response struct {
|
|
182
|
+
ProcessInfo processInfoResponse `json:"process_info"`
|
|
183
|
+
}
|
|
184
|
+
if err := c.runJSON(ctx, "pane process-info", &response, "pane", "process-info", "--pane", paneID); err != nil {
|
|
185
|
+
return ProcessInfo{}, err
|
|
186
|
+
}
|
|
187
|
+
return convertProcessInfo(response.ProcessInfo), nil
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
func (c *CLI) RenamePane(ctx context.Context, paneID, label string) error {
|
|
191
|
+
return c.runCommand(ctx, "pane rename", "pane", "rename", paneID, label)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
func (c *CLI) RunPane(ctx context.Context, paneID, command string) error {
|
|
195
|
+
return c.runCommand(ctx, "pane run", "pane", "run", paneID, command)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
func (c *CLI) ClosePane(ctx context.Context, paneID string) error {
|
|
199
|
+
return c.runCommand(ctx, "pane close", "pane", "close", paneID)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
func (c *CLI) CloseWorkspace(ctx context.Context, workspaceID string) error {
|
|
203
|
+
return c.runCommand(ctx, "workspace close", "workspace", "close", workspaceID)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
func convertWorkspace(value workspaceResponse) Workspace {
|
|
207
|
+
workspace := Workspace{ID: value.ID, Label: value.Label}
|
|
208
|
+
if value.Worktree != nil {
|
|
209
|
+
workspace.Worktree = WorkspaceWorktree{
|
|
210
|
+
CheckoutPath: value.Worktree.CheckoutPath,
|
|
211
|
+
RepoName: value.Worktree.RepoName,
|
|
212
|
+
RepoRoot: value.Worktree.RepoRoot,
|
|
213
|
+
IsLinked: value.Worktree.IsLinked,
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return workspace
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
func convertWorkspaces(values []workspaceResponse) []Workspace {
|
|
220
|
+
out := make([]Workspace, len(values))
|
|
221
|
+
for i, value := range values {
|
|
222
|
+
out[i] = convertWorkspace(value)
|
|
223
|
+
}
|
|
224
|
+
return out
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
func convertWorktrees(values []worktreeResponse) []Worktree {
|
|
228
|
+
out := make([]Worktree, len(values))
|
|
229
|
+
for i, value := range values {
|
|
230
|
+
out[i] = Worktree{
|
|
231
|
+
Path: value.Path,
|
|
232
|
+
Branch: value.Branch,
|
|
233
|
+
IsLinked: value.IsLinked,
|
|
234
|
+
OpenWorkspaceID: value.OpenWorkspaceID,
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return out
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
func convertTab(value tabResponse) Tab {
|
|
241
|
+
return Tab{ID: value.ID, WorkspaceID: value.WorkspaceID, Label: value.Label}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
func convertTabs(values []tabResponse) []Tab {
|
|
245
|
+
out := make([]Tab, len(values))
|
|
246
|
+
for i, value := range values {
|
|
247
|
+
out[i] = convertTab(value)
|
|
248
|
+
}
|
|
249
|
+
return out
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
func convertPane(value paneResponse) Pane {
|
|
253
|
+
return Pane{
|
|
254
|
+
ID: value.ID,
|
|
255
|
+
TerminalID: value.TerminalID,
|
|
256
|
+
WorkspaceID: value.WorkspaceID,
|
|
257
|
+
TabID: value.TabID,
|
|
258
|
+
Label: value.Label,
|
|
259
|
+
CWD: value.CWD,
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
func convertPanes(values []paneResponse) []Pane {
|
|
264
|
+
out := make([]Pane, len(values))
|
|
265
|
+
for i, value := range values {
|
|
266
|
+
out[i] = convertPane(value)
|
|
267
|
+
}
|
|
268
|
+
return out
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
func convertProcessInfo(value processInfoResponse) ProcessInfo {
|
|
272
|
+
processes := make([]ForegroundProcess, len(value.ForegroundProcesses))
|
|
273
|
+
for i, process := range value.ForegroundProcesses {
|
|
274
|
+
processes[i] = ForegroundProcess{PID: process.PID, Name: process.Name}
|
|
275
|
+
}
|
|
276
|
+
return ProcessInfo{
|
|
277
|
+
PaneID: value.PaneID,
|
|
278
|
+
ShellPID: value.ShellPID,
|
|
279
|
+
ForegroundProcesses: processes,
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
package herdrcli
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"fmt"
|
|
8
|
+
"strings"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// Observed Herdr transport contract (herdr 0.8.2, protocol 20):
|
|
12
|
+
//
|
|
13
|
+
// success: JSON envelope {"id":...,"result":{...}} on stdout, exit 0.
|
|
14
|
+
// pane run writes nothing on either stream.
|
|
15
|
+
// failure: JSON envelope {"id":...,"error":{"code","message"}} on stderr,
|
|
16
|
+
// exit 1, empty stdout.
|
|
17
|
+
//
|
|
18
|
+
// There is no "ok" field; presence of result/error is the contract.
|
|
19
|
+
type responseEnvelope struct {
|
|
20
|
+
Result json.RawMessage `json:"result"`
|
|
21
|
+
Error *apiError `json:"error"`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type apiError struct {
|
|
25
|
+
Code string `json:"code"`
|
|
26
|
+
Message string `json:"message"`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func sentinelFor(code string) error {
|
|
30
|
+
switch code {
|
|
31
|
+
case "pane_not_found":
|
|
32
|
+
return ErrPaneNotFound
|
|
33
|
+
case "workspace_not_found":
|
|
34
|
+
return ErrWorkspaceNotFound
|
|
35
|
+
case "worktree_not_found":
|
|
36
|
+
return ErrWorktreeNotFound
|
|
37
|
+
case "not_git_worktree":
|
|
38
|
+
return ErrNotGitWorktree
|
|
39
|
+
default:
|
|
40
|
+
return nil
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// runJSON executes an operation that returns a result body and unmarshals it
|
|
45
|
+
// into dest. Command arguments can contain prompts, so failures report only
|
|
46
|
+
// the operation name and Herdr's own message.
|
|
47
|
+
func (c *CLI) runJSON(ctx context.Context, operation string, dest any, args ...string) error {
|
|
48
|
+
stdout, stderr, err := c.execute(ctx, args...)
|
|
49
|
+
if apiErr := decodeError(operation, stderr); apiErr != nil {
|
|
50
|
+
return apiErr
|
|
51
|
+
}
|
|
52
|
+
if err != nil {
|
|
53
|
+
return commandError(operation, stderr, err)
|
|
54
|
+
}
|
|
55
|
+
return decodeResult(operation, stdout, dest)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// runCommand executes a mutating operation. Herdr returns either an "ok"
|
|
59
|
+
// result body or no body at all; only the error envelope matters.
|
|
60
|
+
func (c *CLI) runCommand(ctx context.Context, operation string, args ...string) error {
|
|
61
|
+
_, stderr, err := c.execute(ctx, args...)
|
|
62
|
+
if apiErr := decodeError(operation, stderr); apiErr != nil {
|
|
63
|
+
return apiErr
|
|
64
|
+
}
|
|
65
|
+
if err != nil {
|
|
66
|
+
return commandError(operation, stderr, err)
|
|
67
|
+
}
|
|
68
|
+
return nil
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// decodeError converts a Herdr error envelope on stderr into a typed error.
|
|
72
|
+
// Non-JSON stderr is not an error by itself; Herdr also uses stderr for
|
|
73
|
+
// warnings.
|
|
74
|
+
func decodeError(operation string, stderr []byte) error {
|
|
75
|
+
trimmed := bytes.TrimSpace(stderr)
|
|
76
|
+
if len(trimmed) == 0 || trimmed[0] != '{' {
|
|
77
|
+
return nil
|
|
78
|
+
}
|
|
79
|
+
var envelope responseEnvelope
|
|
80
|
+
if err := json.Unmarshal(trimmed, &envelope); err != nil || envelope.Error == nil {
|
|
81
|
+
return nil
|
|
82
|
+
}
|
|
83
|
+
message := envelope.Error.Message
|
|
84
|
+
if message == "" {
|
|
85
|
+
message = envelope.Error.Code
|
|
86
|
+
}
|
|
87
|
+
if sentinel := sentinelFor(envelope.Error.Code); sentinel != nil {
|
|
88
|
+
return fmt.Errorf("herdr %s: %s: %w", operation, message, sentinel)
|
|
89
|
+
}
|
|
90
|
+
return fmt.Errorf("herdr %s: %s (%s)", operation, message, envelope.Error.Code)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func decodeResult(operation string, stdout []byte, dest any) error {
|
|
94
|
+
if dest == nil {
|
|
95
|
+
return nil
|
|
96
|
+
}
|
|
97
|
+
var envelope responseEnvelope
|
|
98
|
+
if err := json.Unmarshal(stdout, &envelope); err != nil {
|
|
99
|
+
return fmt.Errorf("herdr %s: malformed JSON response: %w", operation, err)
|
|
100
|
+
}
|
|
101
|
+
if len(bytes.TrimSpace(envelope.Result)) == 0 {
|
|
102
|
+
return fmt.Errorf("herdr %s: response missing result", operation)
|
|
103
|
+
}
|
|
104
|
+
if err := json.Unmarshal(envelope.Result, dest); err != nil {
|
|
105
|
+
return fmt.Errorf("herdr %s: malformed result: %w", operation, err)
|
|
106
|
+
}
|
|
107
|
+
return nil
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
func commandError(operation string, stderr []byte, commandErr error) error {
|
|
111
|
+
message := strings.TrimSpace(string(stderr))
|
|
112
|
+
if message == "" {
|
|
113
|
+
return fmt.Errorf("herdr %s: command failed: %w", operation, commandErr)
|
|
114
|
+
}
|
|
115
|
+
return fmt.Errorf("herdr %s: command failed: %w: %s", operation, commandErr, message)
|
|
116
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"cli:pane:list","result":{"panes":[],"type":"pane_list"}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"cli:tab:list","result":{"tabs":[],"type":"tab_list"}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"error":{"code":"not_git_worktree","message":"Herdr worktree actions require a path inside a Git work tree"},"id":"cli:worktree:list"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"error":{"code":"pane_not_found","message":"pane w9:p9 not found"},"id":"cli:pane:get"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"error":{"code":"workspace_not_found","message":"workspace w9 not found"},"id":"cli:tab:list"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"error":{"code":"worktree_not_found","message":"worktree branch not found"},"id":"cli:worktree:open"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"cli:pane:get","result":{"pane":
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "cli:pane:get",
|
|
3
|
+
"result": {
|
|
4
|
+
"pane": {
|
|
5
|
+
"agent_status": "unknown",
|
|
6
|
+
"cwd": "/work/worktrees/payments/pay-101",
|
|
7
|
+
"focused": false,
|
|
8
|
+
"foreground_cwd": "/work/worktrees/payments/pay-101",
|
|
9
|
+
"label": "PAY-101:coding",
|
|
10
|
+
"pane_id": "w2:p2",
|
|
11
|
+
"revision": 2,
|
|
12
|
+
"scroll": {
|
|
13
|
+
"max_offset_from_bottom": 0,
|
|
14
|
+
"offset_from_bottom": 0,
|
|
15
|
+
"viewport_rows": 39
|
|
16
|
+
},
|
|
17
|
+
"tab_id": "w2:t2",
|
|
18
|
+
"terminal_id": "term_65a5cfcc913e03",
|
|
19
|
+
"terminal_title": "sleep 300",
|
|
20
|
+
"terminal_title_stripped": "sleep 300",
|
|
21
|
+
"workspace_id": "w2"
|
|
22
|
+
},
|
|
23
|
+
"type": "pane_info"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "cli:pane:list",
|
|
3
|
+
"result": {
|
|
4
|
+
"panes": [
|
|
5
|
+
{
|
|
6
|
+
"agent_status": "unknown",
|
|
7
|
+
"cwd": "/work/worktrees/payments/pay-101",
|
|
8
|
+
"focused": false,
|
|
9
|
+
"foreground_cwd": "/work/worktrees/payments/pay-101",
|
|
10
|
+
"pane_id": "w2:p1",
|
|
11
|
+
"revision": 1,
|
|
12
|
+
"scroll": {
|
|
13
|
+
"max_offset_from_bottom": 0,
|
|
14
|
+
"offset_from_bottom": 0,
|
|
15
|
+
"viewport_rows": 39
|
|
16
|
+
},
|
|
17
|
+
"tab_id": "w2:t1",
|
|
18
|
+
"terminal_id": "term_65a5cfcc8cd9e2",
|
|
19
|
+
"terminal_title": "relay@relay-host:~/.herdr/worktrees/repo/pay-101",
|
|
20
|
+
"terminal_title_stripped": "relay@relay-host:~/.herdr/worktrees/repo/pay-101",
|
|
21
|
+
"workspace_id": "w2"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"agent_status": "unknown",
|
|
25
|
+
"cwd": "/work/worktrees/payments/pay-101",
|
|
26
|
+
"focused": false,
|
|
27
|
+
"foreground_cwd": "/work/worktrees/payments/pay-101",
|
|
28
|
+
"label": "PAY-101:coding",
|
|
29
|
+
"pane_id": "w2:p2",
|
|
30
|
+
"revision": 2,
|
|
31
|
+
"scroll": {
|
|
32
|
+
"max_offset_from_bottom": 0,
|
|
33
|
+
"offset_from_bottom": 0,
|
|
34
|
+
"viewport_rows": 39
|
|
35
|
+
},
|
|
36
|
+
"tab_id": "w2:t2",
|
|
37
|
+
"terminal_id": "term_65a5cfcc913e03",
|
|
38
|
+
"terminal_title": "sleep 300",
|
|
39
|
+
"terminal_title_stripped": "sleep 300",
|
|
40
|
+
"workspace_id": "w2"
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"type": "pane_list"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "cli:pane:process_info",
|
|
3
|
+
"result": {
|
|
4
|
+
"process_info": {
|
|
5
|
+
"foreground_process_group_id": 466928,
|
|
6
|
+
"foreground_processes": [
|
|
7
|
+
{
|
|
8
|
+
"argv": [
|
|
9
|
+
"/usr/bin/zsh"
|
|
10
|
+
],
|
|
11
|
+
"cmdline": "/usr/bin/zsh",
|
|
12
|
+
"cwd": "/work/worktrees/payments/pay-101",
|
|
13
|
+
"name": "zsh",
|
|
14
|
+
"pid": 466928
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"pane_id": "w2:p3",
|
|
18
|
+
"shell_pid": 466928
|
|
19
|
+
},
|
|
20
|
+
"type": "pane_process_info"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "cli:pane:process_info",
|
|
3
|
+
"result": {
|
|
4
|
+
"process_info": {
|
|
5
|
+
"foreground_process_group_id": 466891,
|
|
6
|
+
"foreground_processes": [
|
|
7
|
+
{
|
|
8
|
+
"argv": [
|
|
9
|
+
"sleep",
|
|
10
|
+
"300"
|
|
11
|
+
],
|
|
12
|
+
"cmdline": "sleep 300",
|
|
13
|
+
"cwd": "/work/worktrees/payments/pay-101",
|
|
14
|
+
"name": "sleep",
|
|
15
|
+
"pid": 466891
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"pane_id": "w2:p2",
|
|
19
|
+
"shell_pid": 466344
|
|
20
|
+
},
|
|
21
|
+
"type": "pane_process_info"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "cli:pane:rename",
|
|
3
|
+
"result": {
|
|
4
|
+
"pane": {
|
|
5
|
+
"agent_status": "unknown",
|
|
6
|
+
"cwd": "/work/worktrees/payments/pay-101",
|
|
7
|
+
"focused": false,
|
|
8
|
+
"foreground_cwd": "/work/worktrees/payments/pay-101",
|
|
9
|
+
"label": "PAY-101:coding",
|
|
10
|
+
"pane_id": "w2:p2",
|
|
11
|
+
"revision": 0,
|
|
12
|
+
"scroll": {
|
|
13
|
+
"max_offset_from_bottom": 0,
|
|
14
|
+
"offset_from_bottom": 0,
|
|
15
|
+
"viewport_rows": 39
|
|
16
|
+
},
|
|
17
|
+
"tab_id": "w2:t2",
|
|
18
|
+
"terminal_id": "term_65a5cfcc913e03",
|
|
19
|
+
"workspace_id": "w2"
|
|
20
|
+
},
|
|
21
|
+
"type": "pane_info"
|
|
22
|
+
}
|
|
23
|
+
}
|