@ree-auz/nook 0.1.1 → 0.1.3
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.
|
@@ -6,7 +6,7 @@ issue's branch (running the repository's hooks) and starts your agent in it
|
|
|
6
6
|
with Linear's prompt, in the terminal Linear opens for the script. Nook
|
|
7
7
|
itself knows nothing about Linear: this folder only connects the two.
|
|
8
8
|
|
|
9
|
-
Tested on Windows. `--terminal`
|
|
9
|
+
Tested on Windows. `--terminal wt` and `--terminal warp` are Windows only.
|
|
10
10
|
|
|
11
11
|
## Set up once
|
|
12
12
|
|
|
@@ -42,6 +42,15 @@ Tested on Windows. `--terminal` (Windows Terminal or Warp) is Windows only.
|
|
|
42
42
|
Terminal; the terminal choice in Linear's settings is macOS only). To
|
|
43
43
|
work in Warp instead, add `"--terminal", "warp"` to `args`: the script
|
|
44
44
|
opens a tab in Warp and closes Linear's window.
|
|
45
|
+
- To keep agents in [Orca](https://www.onorca.dev) (its accounts and
|
|
46
|
+
sessions), add `"--terminal", "orca"`: Nook creates the workspace and
|
|
47
|
+
runs its hooks in Linear's window, then
|
|
48
|
+
`orca agent launch --agent <agent> --worktree path:<workspace> --prompt <Linear's prompt> --focus`
|
|
49
|
+
starts the agent in Orca. Orca shows worktrees it did not create, and
|
|
50
|
+
`nk rm` / `nk clean` still remove them with their containers and
|
|
51
|
+
volumes. Orca's command-line tool must be installed; if Linear cannot
|
|
52
|
+
find it on PATH, add `"--orca", "<full path to orca>"`. Linear's window
|
|
53
|
+
closes when Orca has the agent, and stays open with the error otherwise.
|
|
45
54
|
- To let each issue pick its agent, label issues from one label group
|
|
46
55
|
(say `Agent › Codex`, `Agent › Claude`) and add `"--agent-label", "Agent"`
|
|
47
56
|
to `args`. Linear's prompt lists the issue's labels, and the script starts
|
|
@@ -59,7 +68,8 @@ the issue belongs to; nothing runs there.
|
|
|
59
68
|
The terminal runs `nk -b <issue branch> <agent> -y -p <Linear's prompt>` in
|
|
60
69
|
that folder (`-y` keeps the converted workspace name without asking).
|
|
61
70
|
Choosing the same issue again reopens its workspace and starts the agent
|
|
62
|
-
without the prompt
|
|
71
|
+
without the prompt (with `--terminal orca`, a new agent session in Orca;
|
|
72
|
+
earlier sessions stay in Orca's list).
|
|
63
73
|
|
|
64
74
|
Linear's GitHub integration moves the issue along as the branch and its pull
|
|
65
75
|
request change, because the branch name holds the issue identifier. After the
|
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
// Linear runs this file through ~/.linear/coding-tools.json
|
|
4
4
|
// (Work on issue -> Custom script). See README.md next to it.
|
|
5
5
|
//
|
|
6
|
-
// node open-issue.mjs [agent]
|
|
6
|
+
// node open-issue.mjs [--agent a] [--terminal wt|warp|orca] started by Linear
|
|
7
7
|
// node open-issue.mjs --run <file> ... inside the new tab
|
|
8
8
|
import { spawn, spawnSync } from "node:child_process";
|
|
9
|
-
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { homedir, tmpdir } from "node:os";
|
|
11
11
|
import { delimiter, dirname, join, resolve } from "node:path";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { agentCommand, windowsDirect } from "../../src/agents.mjs";
|
|
13
14
|
import { normalizeName } from "../../src/names.mjs";
|
|
15
|
+
import crossSpawn from "cross-spawn";
|
|
14
16
|
|
|
15
17
|
const self = fileURLToPath(import.meta.url);
|
|
16
18
|
const nk = fileURLToPath(new URL("../../bin/nk.mjs", import.meta.url));
|
|
@@ -30,10 +32,15 @@ function runNk(args, opts = {}) {
|
|
|
30
32
|
return spawnSync(process.execPath, [nk, ...args], { stdio: "inherit", env: withNodeOnPath(), ...opts });
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
/** The issue's workspace from `nk ls --json`, if it exists. */
|
|
36
|
+
function findWorkspace(branch) {
|
|
37
|
+
const listed = spawnSync(process.execPath, [nk, "ls", "--json"], { encoding: "utf8", env: withNodeOnPath() });
|
|
38
|
+
return listed.status === 0 ? JSON.parse(listed.stdout).find((ws) => !ws.main && (ws.branch === branch || ws.name === normalizeName(branch))) : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
/** Creates the issue's workspace and starts the agent, or reopens it. */
|
|
34
42
|
function startWork({ branch, agent, prompt }) {
|
|
35
|
-
const
|
|
36
|
-
const existing = listed.status === 0 ? JSON.parse(listed.stdout).find((ws) => !ws.main && (ws.branch === branch || ws.name === normalizeName(branch))) : null;
|
|
43
|
+
const existing = findWorkspace(branch);
|
|
37
44
|
// Clicked again for the same issue: reopen the workspace without restarting the task.
|
|
38
45
|
// -y: the branch name Linear suggests is converted without asking.
|
|
39
46
|
const result = existing ? runNk([existing.name, agent]) : runNk(["-b", branch, agent, "-y", "-p", prompt]);
|
|
@@ -91,6 +98,38 @@ function which(command) {
|
|
|
91
98
|
|
|
92
99
|
const TERMINALS = { wt: openInWindowsTerminal, warp: openInWarp };
|
|
93
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Orca: nk creates the workspace (hooks, ports, database), then Orca starts
|
|
103
|
+
* the agent in it, so Orca keeps its accounts and sessions. Orca shows
|
|
104
|
+
* worktrees it did not create. A new workspace gets Linear's prompt; one
|
|
105
|
+
* that exists only gets a new agent session.
|
|
106
|
+
*/
|
|
107
|
+
export function workInOrca({ branch, agent, prompt, orca = "orca", run = runOrca }) {
|
|
108
|
+
const existing = findWorkspace(branch);
|
|
109
|
+
if (!existing) {
|
|
110
|
+
const created = runNk(["-b", branch, "-y"]);
|
|
111
|
+
if (created.status !== 0) return created.status ?? 1;
|
|
112
|
+
}
|
|
113
|
+
const ws = existing ?? findWorkspace(branch);
|
|
114
|
+
if (!ws) throw new Error(`nk created no workspace for ${branch}`);
|
|
115
|
+
run(orca, ["open", "--json"]);
|
|
116
|
+
const args = ["agent", "launch", "--agent", agent, "--worktree", `path:${ws.path}`, "--focus", "--json"];
|
|
117
|
+
if (!existing && prompt) args.push("--prompt", prompt);
|
|
118
|
+
return run(orca, args);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Runs the Orca CLI, never through cmd.exe, which would cut the prompt. */
|
|
122
|
+
function runOrca(orca, args) {
|
|
123
|
+
const env = withNodeOnPath();
|
|
124
|
+
const spec = windowsDirect({ command: agentCommand({}, orca, {}, env).command, args });
|
|
125
|
+
// cross-spawn: a .cmd shim still runs when the arguments have no line breaks.
|
|
126
|
+
const result = crossSpawn.sync(spec.command, spec.args, { stdio: ["ignore", "pipe", "inherit"], encoding: "utf8", env });
|
|
127
|
+
if (result.error) throw new Error(`could not run ${orca}: ${result.error.message}. Is Orca's command-line tool installed? Pass its path with --orca`);
|
|
128
|
+
// Orca answers in JSON; show it only when something went wrong.
|
|
129
|
+
if (result.status !== 0) process.stdout.write(result.stdout ?? "");
|
|
130
|
+
return result.status ?? 1;
|
|
131
|
+
}
|
|
132
|
+
|
|
94
133
|
/**
|
|
95
134
|
* Closes the terminal window Linear opened for this script, once the work
|
|
96
135
|
* runs in another terminal: its shell is this process's parent.
|
|
@@ -130,8 +169,15 @@ function fromLinear(argv) {
|
|
|
130
169
|
process.chdir(dir);
|
|
131
170
|
return startWork({ branch, agent, prompt });
|
|
132
171
|
}
|
|
172
|
+
if (terminal === "orca") {
|
|
173
|
+
process.chdir(dir);
|
|
174
|
+
process.exitCode = workInOrca({ branch, agent, prompt, orca: option(argv, "--orca") ?? "orca" });
|
|
175
|
+
// Keep Linear's window open when something failed, so its output can be read.
|
|
176
|
+
if (process.exitCode === 0 && process.platform === "win32") closeLinearWindow();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
133
179
|
const open = TERMINALS[terminal];
|
|
134
|
-
if (!open) throw new Error(`Unknown terminal "${terminal}"; use ${Object.keys(TERMINALS).join("
|
|
180
|
+
if (!open) throw new Error(`Unknown terminal "${terminal}"; use ${[...Object.keys(TERMINALS), "orca"].join(", ")}, or leave --terminal out`);
|
|
135
181
|
if (process.platform !== "win32") throw new Error("--terminal only works on Windows so far");
|
|
136
182
|
// The prompt is long Markdown with quotes and semicolons, so it travels in a file.
|
|
137
183
|
const file = join(tmpdir(), `nk-linear-${id}-${process.pid}.md`);
|
|
@@ -140,7 +186,20 @@ function fromLinear(argv) {
|
|
|
140
186
|
closeLinearWindow();
|
|
141
187
|
}
|
|
142
188
|
|
|
143
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Run directly, not imported. Node gives this file its real path, so a path
|
|
191
|
+
* through a link (fnm's aliases\default, for one) is resolved before comparing.
|
|
192
|
+
*/
|
|
193
|
+
function runDirectly() {
|
|
194
|
+
if (!process.argv[1]) return false;
|
|
195
|
+
try {
|
|
196
|
+
return realpathSync(resolve(process.argv[1])) === realpathSync(self);
|
|
197
|
+
} catch {
|
|
198
|
+
return resolve(process.argv[1]) === self;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (runDirectly()) {
|
|
144
203
|
const [first, ...rest] = process.argv.slice(2);
|
|
145
204
|
if (first === "--run") insideTab(rest);
|
|
146
205
|
else fromLinear(process.argv.slice(2));
|
package/package.json
CHANGED