@ree-auz/nook 0.1.0 → 0.1.2
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 -21
- package/README.md +422 -410
- package/bin/nk.mjs +3 -3
- package/docs/setup-hooks.md +107 -107
- package/integrations/linear/README.md +94 -84
- package/integrations/linear/coding-tools.json +7 -7
- package/integrations/linear/open-issue.mjs +193 -147
- package/package.json +34 -34
- package/skills/nook/SKILL.md +71 -71
- package/src/agents.mjs +92 -92
- package/src/backup.mjs +74 -74
- package/src/ci.mjs +378 -378
- package/src/cli.mjs +151 -151
- package/src/commands/check-hooks.mjs +128 -128
- package/src/commands/clean.mjs +175 -175
- package/src/commands/doctor.mjs +100 -91
- package/src/commands/forget.mjs +57 -57
- package/src/commands/hooks.mjs +34 -27
- package/src/commands/init.mjs +154 -154
- package/src/commands/ls.mjs +107 -107
- package/src/commands/rm.mjs +190 -190
- package/src/commands/workspace.mjs +163 -163
- package/src/commands/write-hooks.mjs +185 -185
- package/src/compose-plan.mjs +228 -228
- package/src/compose.mjs +123 -123
- package/src/config.mjs +115 -115
- package/src/context.mjs +189 -189
- package/src/errors.mjs +8 -8
- package/src/forge.mjs +75 -75
- package/src/generate-hooks.mjs +367 -367
- package/src/git.mjs +86 -86
- package/src/home.mjs +48 -48
- package/src/hooks.mjs +135 -135
- package/src/names.mjs +65 -65
- package/src/occupancy-windows.ps1 +59 -59
- package/src/occupancy.mjs +131 -131
- package/src/ports.mjs +89 -45
- package/src/prompt.mjs +100 -100
- package/src/shared.mjs +97 -97
- package/src/status.mjs +30 -30
- package/src/tools.mjs +23 -23
- package/src/ui.mjs +60 -60
|
@@ -1,163 +1,163 @@
|
|
|
1
|
-
import { mkdirSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { agentCommand, launch } from "../agents.mjs";
|
|
4
|
-
import { NookError } from "../errors.mjs";
|
|
5
|
-
import { exists } from "../home.mjs";
|
|
6
|
-
import { baseRef, git, gitTry, refExists, remoteUrl } from "../git.mjs";
|
|
7
|
-
import { ensureTrusted, findHook, findHooks, runHook } from "../hooks.mjs";
|
|
8
|
-
import { nameProblem, normalizeName, toName } from "../names.mjs";
|
|
9
|
-
import { allocatePort } from "../ports.mjs";
|
|
10
|
-
import { createPrompter } from "../prompt.mjs";
|
|
11
|
-
import { copyShared } from "../shared.mjs";
|
|
12
|
-
import { resolveRepo, resolveWorkspace, workspaceEnv } from "../context.mjs";
|
|
13
|
-
import { setupChanges } from "./check-hooks.mjs";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Before work starts: hooks or CI that changed since nk hooks test last passed
|
|
17
|
-
* stop a new workspace (they may no longer set it up or clean it up), unless
|
|
18
|
-
* `untested` is passed; an existing workspace only gets a warning.
|
|
19
|
-
*/
|
|
20
|
-
function checkSetup(ctx, repo, { stop, untested }) {
|
|
21
|
-
const { tested, changes } = setupChanges(repo);
|
|
22
|
-
if (!changes.length) return;
|
|
23
|
-
if (!tested) {
|
|
24
|
-
if (findHooks(repo.path).length) ctx.warn("The hooks have never passed nk hooks test");
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
const what = `${changes.join(" and ")} changed since nk hooks test last passed`;
|
|
28
|
-
if (stop && !untested) throw new NookError(`${what}\nRun nk hooks test first, or pass --untested to start anyway`);
|
|
29
|
-
ctx.warn(`${what} (run: nk hooks test)`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** Asks once for hooks that are new or changed; nothing when all are allowed. */
|
|
33
|
-
export function approveHooks(ctx, { repoName, repo }, prompter = createPrompter()) {
|
|
34
|
-
return ensureTrusted({ repoName, repoConfig: repo, hooks: findHooks(repo.path), prompter, save: () => ctx.save(), ui: ctx });
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Runs one hook of the repository in the workspace. Hooks are read from the
|
|
39
|
-
* main checkout, like worktrunk reads its project config from where the
|
|
40
|
-
* command runs, so a branch made before the hooks existed still gets them.
|
|
41
|
-
* Throws on a non-zero exit.
|
|
42
|
-
*/
|
|
43
|
-
export async function hook(ctx, target, event, { prompter = createPrompter() } = {}) {
|
|
44
|
-
const found = findHook(target.repo.path, event);
|
|
45
|
-
if (!found) return null;
|
|
46
|
-
await approveHooks(ctx, target, prompter);
|
|
47
|
-
const result = await runHook(found, {
|
|
48
|
-
cwd: target.ws.path,
|
|
49
|
-
env: workspaceEnv(ctx, target),
|
|
50
|
-
logsDir: ctx.paths.logs,
|
|
51
|
-
label: `${target.repoName}-${target.name}`,
|
|
52
|
-
output: ctx.err,
|
|
53
|
-
});
|
|
54
|
-
if (result.status !== 0)
|
|
55
|
-
throw new NookError(`.nook/${found.name} failed (exit ${result.status}). Log: ${result.log}`, { code: result.status || 1 });
|
|
56
|
-
return result;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function finish(ctx, target, { agent, prompt, extra }) {
|
|
60
|
-
if (!agent) {
|
|
61
|
-
if (prompt || extra.length) throw new NookError("-p and extra arguments need an agent, e.g. nk <name> <agent> -p ...");
|
|
62
|
-
// On a terminal the path is a line like the others; captured (cd "$(nk …)"), it is the bare path.
|
|
63
|
-
if (ctx.out.isTTY) ctx.ok(`Ready at ${target.ws.path}`);
|
|
64
|
-
else ctx.print(target.ws.path);
|
|
65
|
-
return 0;
|
|
66
|
-
}
|
|
67
|
-
const command = agentCommand(ctx.config, agent, { prompt, extra }, ctx.env);
|
|
68
|
-
ctx.step(`Starting ${agent} in ${target.ws.path}`);
|
|
69
|
-
return launch(command, { cwd: target.ws.path, env: workspaceEnv(ctx, target) });
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** An existing local or origin branch spelled exactly like `input`, or null. */
|
|
73
|
-
function existingBranch(repoPath, input) {
|
|
74
|
-
if (!gitTry(["check-ref-format", "--branch", input], { cwd: repoPath }).ok) return null;
|
|
75
|
-
return refExists(repoPath, `refs/heads/${input}`) || refExists(repoPath, `refs/remotes/origin/${input}`) ? input : null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function addWorktree(repoPath, name, path, defaultBranch) {
|
|
79
|
-
if (refExists(repoPath, `refs/heads/${name}`)) git(["worktree", "add", path, name], { cwd: repoPath });
|
|
80
|
-
else if (refExists(repoPath, `refs/remotes/origin/${name}`))
|
|
81
|
-
git(["worktree", "add", "--track", "-b", name, path, `origin/${name}`], { cwd: repoPath });
|
|
82
|
-
else git(["worktree", "add", "--no-track", "-b", name, path, baseRef(repoPath, defaultBranch)], { cwd: repoPath });
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* The name for a new workspace. When nk had to change what was passed in and
|
|
87
|
-
* there is a terminal, the converted name is shown ready to edit.
|
|
88
|
-
*/
|
|
89
|
-
async function pickName(ctx, input, prompter) {
|
|
90
|
-
let name = toName(input);
|
|
91
|
-
if (name === input) return name;
|
|
92
|
-
if (!prompter.interactive) {
|
|
93
|
-
ctx.info(`Using the name ${name}`);
|
|
94
|
-
return name;
|
|
95
|
-
}
|
|
96
|
-
for (;;) {
|
|
97
|
-
const edited = normalizeName(await prompter.edit("Workspace name (edit or press Enter)", name));
|
|
98
|
-
const problem = nameProblem(edited);
|
|
99
|
-
if (!problem) return edited;
|
|
100
|
-
ctx.warn(problem);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** nk -b <name> [agent] */
|
|
105
|
-
export async function createWorkspace(ctx, { name: input, repo: repoFlag, agent, prompt, extra = [], prompter = createPrompter(), untested = false }) {
|
|
106
|
-
const { name: repoName, repo } = resolveRepo(ctx, repoFlag);
|
|
107
|
-
checkSetup(ctx, repo, { stop: true, untested });
|
|
108
|
-
// Asked before anything is made, so hooks that are not allowed leave no half-made workspace.
|
|
109
|
-
await approveHooks(ctx, { repoName, repo }, prompter);
|
|
110
|
-
if (remoteUrl(repo.path)) {
|
|
111
|
-
const fetched = gitTry(["fetch", "--quiet", "origin", repo.default_branch], { cwd: repo.path });
|
|
112
|
-
if (!fetched.ok) ctx.warn(`Could not fetch origin/${repo.default_branch}; using what is local`);
|
|
113
|
-
}
|
|
114
|
-
// A branch that already exists keeps its own name, "/" and all; the folder uses the converted name.
|
|
115
|
-
const existing = existingBranch(repo.path, String(input));
|
|
116
|
-
const name = existing ? toName(existing) : await pickName(ctx, input, prompter);
|
|
117
|
-
if (existing && existing !== name) ctx.info(`Using the name ${name} for branch ${existing}`);
|
|
118
|
-
const branch = existing ?? name;
|
|
119
|
-
const dir = join(ctx.paths.workspaces, repoName);
|
|
120
|
-
const path = join(dir, name);
|
|
121
|
-
// The name and ports are taken under the lock, so two nk -b at once never get the same ones.
|
|
122
|
-
const ws = await ctx.update(async (config) => {
|
|
123
|
-
const fresh = config.repos[repoName];
|
|
124
|
-
if (fresh.workspaces[name]) throw new NookError(`Workspace "${name}" already exists. Open it with: nk ${name}`);
|
|
125
|
-
if (exists(path)) throw new NookError(`${path} already exists`);
|
|
126
|
-
const port = await allocatePort(config);
|
|
127
|
-
fresh.workspaces[name] = { path, port, created: new Date().toISOString(), ...(branch !== name && { branch }) };
|
|
128
|
-
return fresh.workspaces[name];
|
|
129
|
-
});
|
|
130
|
-
const { port } = ws;
|
|
131
|
-
mkdirSync(dir, { recursive: true });
|
|
132
|
-
try {
|
|
133
|
-
addWorktree(repo.path, branch, path, repo.default_branch);
|
|
134
|
-
} catch (error) {
|
|
135
|
-
delete repo.workspaces[name];
|
|
136
|
-
ctx.save();
|
|
137
|
-
throw error;
|
|
138
|
-
}
|
|
139
|
-
const target = { repoName, repo, name, ws };
|
|
140
|
-
ctx.ok(`Created workspace ${name} @ ${path} (ports ${port}–${port + 9})`);
|
|
141
|
-
|
|
142
|
-
const shared = copyShared(join(ctx.paths.shared, repoName), path);
|
|
143
|
-
if (shared.copied.length) ctx.info(`Copied shared files: ${shared.copied.join(", ")}`);
|
|
144
|
-
if (shared.skipped.length) ctx.warn(`Not copied (git does not ignore them here): ${shared.skipped.join(", ")}`);
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
await hook(ctx, target, "create");
|
|
148
|
-
await hook(ctx, target, "open");
|
|
149
|
-
} catch (error) {
|
|
150
|
-
if (error instanceof NookError) error.message += `\nThe workspace is kept at ${path}; remove it with: nk rm ${name}`;
|
|
151
|
-
throw error;
|
|
152
|
-
}
|
|
153
|
-
return finish(ctx, target, { agent, prompt, extra });
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** nk <name> [agent] */
|
|
157
|
-
export async function openWorkspace(ctx, { name, repo: repoFlag, agent, prompt, extra = [] }) {
|
|
158
|
-
const target = resolveWorkspace(ctx, toName(name), repoFlag);
|
|
159
|
-
if (!exists(target.ws.path)) throw new NookError(`${target.ws.path} is missing. Run nk doctor`);
|
|
160
|
-
if (!target.main) checkSetup(ctx, target.repo, { stop: false });
|
|
161
|
-
await hook(ctx, target, "open");
|
|
162
|
-
return finish(ctx, target, { agent, prompt, extra });
|
|
163
|
-
}
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { agentCommand, launch } from "../agents.mjs";
|
|
4
|
+
import { NookError } from "../errors.mjs";
|
|
5
|
+
import { exists } from "../home.mjs";
|
|
6
|
+
import { baseRef, git, gitTry, refExists, remoteUrl } from "../git.mjs";
|
|
7
|
+
import { ensureTrusted, findHook, findHooks, runHook } from "../hooks.mjs";
|
|
8
|
+
import { nameProblem, normalizeName, toName } from "../names.mjs";
|
|
9
|
+
import { allocatePort } from "../ports.mjs";
|
|
10
|
+
import { createPrompter } from "../prompt.mjs";
|
|
11
|
+
import { copyShared } from "../shared.mjs";
|
|
12
|
+
import { resolveRepo, resolveWorkspace, workspaceEnv } from "../context.mjs";
|
|
13
|
+
import { setupChanges } from "./check-hooks.mjs";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Before work starts: hooks or CI that changed since nk hooks test last passed
|
|
17
|
+
* stop a new workspace (they may no longer set it up or clean it up), unless
|
|
18
|
+
* `untested` is passed; an existing workspace only gets a warning.
|
|
19
|
+
*/
|
|
20
|
+
function checkSetup(ctx, repo, { stop, untested }) {
|
|
21
|
+
const { tested, changes } = setupChanges(repo);
|
|
22
|
+
if (!changes.length) return;
|
|
23
|
+
if (!tested) {
|
|
24
|
+
if (findHooks(repo.path).length) ctx.warn("The hooks have never passed nk hooks test");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const what = `${changes.join(" and ")} changed since nk hooks test last passed`;
|
|
28
|
+
if (stop && !untested) throw new NookError(`${what}\nRun nk hooks test first, or pass --untested to start anyway`);
|
|
29
|
+
ctx.warn(`${what} (run: nk hooks test)`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Asks once for hooks that are new or changed; nothing when all are allowed. */
|
|
33
|
+
export function approveHooks(ctx, { repoName, repo }, prompter = createPrompter()) {
|
|
34
|
+
return ensureTrusted({ repoName, repoConfig: repo, hooks: findHooks(repo.path), prompter, save: () => ctx.save(), ui: ctx });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Runs one hook of the repository in the workspace. Hooks are read from the
|
|
39
|
+
* main checkout, like worktrunk reads its project config from where the
|
|
40
|
+
* command runs, so a branch made before the hooks existed still gets them.
|
|
41
|
+
* Throws on a non-zero exit.
|
|
42
|
+
*/
|
|
43
|
+
export async function hook(ctx, target, event, { prompter = createPrompter() } = {}) {
|
|
44
|
+
const found = findHook(target.repo.path, event);
|
|
45
|
+
if (!found) return null;
|
|
46
|
+
await approveHooks(ctx, target, prompter);
|
|
47
|
+
const result = await runHook(found, {
|
|
48
|
+
cwd: target.ws.path,
|
|
49
|
+
env: workspaceEnv(ctx, target),
|
|
50
|
+
logsDir: ctx.paths.logs,
|
|
51
|
+
label: `${target.repoName}-${target.name}`,
|
|
52
|
+
output: ctx.err,
|
|
53
|
+
});
|
|
54
|
+
if (result.status !== 0)
|
|
55
|
+
throw new NookError(`.nook/${found.name} failed (exit ${result.status}). Log: ${result.log}`, { code: result.status || 1 });
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function finish(ctx, target, { agent, prompt, extra }) {
|
|
60
|
+
if (!agent) {
|
|
61
|
+
if (prompt || extra.length) throw new NookError("-p and extra arguments need an agent, e.g. nk <name> <agent> -p ...");
|
|
62
|
+
// On a terminal the path is a line like the others; captured (cd "$(nk …)"), it is the bare path.
|
|
63
|
+
if (ctx.out.isTTY) ctx.ok(`Ready at ${target.ws.path}`);
|
|
64
|
+
else ctx.print(target.ws.path);
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
const command = agentCommand(ctx.config, agent, { prompt, extra }, ctx.env);
|
|
68
|
+
ctx.step(`Starting ${agent} in ${target.ws.path}`);
|
|
69
|
+
return launch(command, { cwd: target.ws.path, env: workspaceEnv(ctx, target) });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** An existing local or origin branch spelled exactly like `input`, or null. */
|
|
73
|
+
function existingBranch(repoPath, input) {
|
|
74
|
+
if (!gitTry(["check-ref-format", "--branch", input], { cwd: repoPath }).ok) return null;
|
|
75
|
+
return refExists(repoPath, `refs/heads/${input}`) || refExists(repoPath, `refs/remotes/origin/${input}`) ? input : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function addWorktree(repoPath, name, path, defaultBranch) {
|
|
79
|
+
if (refExists(repoPath, `refs/heads/${name}`)) git(["worktree", "add", path, name], { cwd: repoPath });
|
|
80
|
+
else if (refExists(repoPath, `refs/remotes/origin/${name}`))
|
|
81
|
+
git(["worktree", "add", "--track", "-b", name, path, `origin/${name}`], { cwd: repoPath });
|
|
82
|
+
else git(["worktree", "add", "--no-track", "-b", name, path, baseRef(repoPath, defaultBranch)], { cwd: repoPath });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The name for a new workspace. When nk had to change what was passed in and
|
|
87
|
+
* there is a terminal, the converted name is shown ready to edit.
|
|
88
|
+
*/
|
|
89
|
+
async function pickName(ctx, input, prompter) {
|
|
90
|
+
let name = toName(input);
|
|
91
|
+
if (name === input) return name;
|
|
92
|
+
if (!prompter.interactive) {
|
|
93
|
+
ctx.info(`Using the name ${name}`);
|
|
94
|
+
return name;
|
|
95
|
+
}
|
|
96
|
+
for (;;) {
|
|
97
|
+
const edited = normalizeName(await prompter.edit("Workspace name (edit or press Enter)", name));
|
|
98
|
+
const problem = nameProblem(edited);
|
|
99
|
+
if (!problem) return edited;
|
|
100
|
+
ctx.warn(problem);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** nk -b <name> [agent] */
|
|
105
|
+
export async function createWorkspace(ctx, { name: input, repo: repoFlag, agent, prompt, extra = [], prompter = createPrompter(), untested = false }) {
|
|
106
|
+
const { name: repoName, repo } = resolveRepo(ctx, repoFlag);
|
|
107
|
+
checkSetup(ctx, repo, { stop: true, untested });
|
|
108
|
+
// Asked before anything is made, so hooks that are not allowed leave no half-made workspace.
|
|
109
|
+
await approveHooks(ctx, { repoName, repo }, prompter);
|
|
110
|
+
if (remoteUrl(repo.path)) {
|
|
111
|
+
const fetched = gitTry(["fetch", "--quiet", "origin", repo.default_branch], { cwd: repo.path });
|
|
112
|
+
if (!fetched.ok) ctx.warn(`Could not fetch origin/${repo.default_branch}; using what is local`);
|
|
113
|
+
}
|
|
114
|
+
// A branch that already exists keeps its own name, "/" and all; the folder uses the converted name.
|
|
115
|
+
const existing = existingBranch(repo.path, String(input));
|
|
116
|
+
const name = existing ? toName(existing) : await pickName(ctx, input, prompter);
|
|
117
|
+
if (existing && existing !== name) ctx.info(`Using the name ${name} for branch ${existing}`);
|
|
118
|
+
const branch = existing ?? name;
|
|
119
|
+
const dir = join(ctx.paths.workspaces, repoName);
|
|
120
|
+
const path = join(dir, name);
|
|
121
|
+
// The name and ports are taken under the lock, so two nk -b at once never get the same ones.
|
|
122
|
+
const ws = await ctx.update(async (config) => {
|
|
123
|
+
const fresh = config.repos[repoName];
|
|
124
|
+
if (fresh.workspaces[name]) throw new NookError(`Workspace "${name}" already exists. Open it with: nk ${name}`);
|
|
125
|
+
if (exists(path)) throw new NookError(`${path} already exists`);
|
|
126
|
+
const port = await allocatePort(config);
|
|
127
|
+
fresh.workspaces[name] = { path, port, created: new Date().toISOString(), ...(branch !== name && { branch }) };
|
|
128
|
+
return fresh.workspaces[name];
|
|
129
|
+
});
|
|
130
|
+
const { port } = ws;
|
|
131
|
+
mkdirSync(dir, { recursive: true });
|
|
132
|
+
try {
|
|
133
|
+
addWorktree(repo.path, branch, path, repo.default_branch);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
delete repo.workspaces[name];
|
|
136
|
+
ctx.save();
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
const target = { repoName, repo, name, ws };
|
|
140
|
+
ctx.ok(`Created workspace ${name} @ ${path} (ports ${port}–${port + 9})`);
|
|
141
|
+
|
|
142
|
+
const shared = copyShared(join(ctx.paths.shared, repoName), path);
|
|
143
|
+
if (shared.copied.length) ctx.info(`Copied shared files: ${shared.copied.join(", ")}`);
|
|
144
|
+
if (shared.skipped.length) ctx.warn(`Not copied (git does not ignore them here): ${shared.skipped.join(", ")}`);
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
await hook(ctx, target, "create");
|
|
148
|
+
await hook(ctx, target, "open");
|
|
149
|
+
} catch (error) {
|
|
150
|
+
if (error instanceof NookError) error.message += `\nThe workspace is kept at ${path}; remove it with: nk rm ${name}`;
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
return finish(ctx, target, { agent, prompt, extra });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** nk <name> [agent] */
|
|
157
|
+
export async function openWorkspace(ctx, { name, repo: repoFlag, agent, prompt, extra = [] }) {
|
|
158
|
+
const target = resolveWorkspace(ctx, toName(name), repoFlag);
|
|
159
|
+
if (!exists(target.ws.path)) throw new NookError(`${target.ws.path} is missing. Run nk doctor`);
|
|
160
|
+
if (!target.main) checkSetup(ctx, target.repo, { stop: false });
|
|
161
|
+
await hook(ctx, target, "open");
|
|
162
|
+
return finish(ctx, target, { agent, prompt, extra });
|
|
163
|
+
}
|