rl-workspaces 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -53
- package/bin/rl +2 -54
- package/dist/agent-launch.js +1 -1
- package/dist/agent-resume.js +2 -2
- package/dist/bootstrap.js +74 -0
- package/dist/cli.js +2 -0
- package/dist/command-context.js +1 -1
- package/dist/common.js +3 -2
- package/dist/dependencies.js +197 -0
- package/dist/doctor.js +1 -1
- package/dist/help.js +81 -84
- package/dist/hooks.js +1 -1
- package/dist/install.js +143 -0
- package/dist/mcp-setup.js +171 -0
- package/dist/mcp.js +13 -16
- package/dist/picker.js +24 -7
- package/dist/pr-workflow.js +7 -28
- package/dist/repos.js +5 -1
- package/dist/session-workflows.js +71 -1
- package/dist/session.js +1 -1
- package/dist/state.js +4 -14
- package/dist/storage.js +2 -2
- package/dist/store.js +2 -2
- package/dist/summary.js +1 -1
- package/dist/workflow.js +24 -38
- package/lib/entry.zsh +54 -0
- package/lib/rl.zsh +35 -6
- package/node_modules/proper-lockfile/CHANGELOG.md +108 -0
- package/node_modules/smol-toml/LICENSE +24 -0
- package/node_modules/smol-toml/README.md +605 -0
- package/node_modules/smol-toml/dist/date.d.ts +41 -0
- package/node_modules/smol-toml/dist/date.js +156 -0
- package/node_modules/smol-toml/dist/error.d.ts +37 -0
- package/node_modules/smol-toml/dist/error.js +67 -0
- package/node_modules/smol-toml/dist/extract.js +293 -0
- package/node_modules/smol-toml/dist/index.cjs +771 -0
- package/node_modules/smol-toml/dist/index.d.ts +44 -0
- package/node_modules/smol-toml/dist/index.js +34 -0
- package/node_modules/smol-toml/dist/parse.d.ts +73 -0
- package/node_modules/smol-toml/dist/parse.js +164 -0
- package/node_modules/smol-toml/dist/primitive.js +151 -0
- package/node_modules/smol-toml/dist/stringify.d.ts +32 -0
- package/node_modules/smol-toml/dist/stringify.js +219 -0
- package/node_modules/smol-toml/dist/struct.js +156 -0
- package/node_modules/smol-toml/dist/util.d.ts +43 -0
- package/node_modules/smol-toml/dist/util.js +57 -0
- package/node_modules/smol-toml/package.json +68 -0
- package/package.json +6 -3
- package/shell/rl.zsh +1 -1
package/README.md
CHANGED
|
@@ -6,31 +6,41 @@ Create a workspace, start an agent, and keep task notes and sessions together.
|
|
|
6
6
|
Switch projects, prepare local dependencies, check PRs, and merge finished work
|
|
7
7
|
without losing your place.
|
|
8
8
|
|
|
9
|
-
Requires macOS or Linux
|
|
10
|
-
|
|
11
|
-
you use.
|
|
9
|
+
Requires macOS or Linux and Node.js 22+. Global npm installation sets up Git,
|
|
10
|
+
zsh, fzf, GitHub CLI and integration for your zsh or bash terminal.
|
|
12
11
|
|
|
13
12
|
## Install
|
|
14
13
|
|
|
15
14
|
Install from npm:
|
|
16
15
|
|
|
17
16
|
```sh
|
|
18
|
-
npm install --global rl-workspaces
|
|
17
|
+
npm install --global rl-workspaces --foreground-scripts
|
|
19
18
|
```
|
|
20
19
|
|
|
21
20
|
See Development below to build and try unpublished changes.
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
The installer installs missing tools through your package manager, preserves
|
|
23
|
+
your shell settings, and registers one RL integration block. Open a new terminal
|
|
24
|
+
after installation; bare `rl` can then select a workspace and change directory.
|
|
25
|
+
For the terminal that ran the install, use the activation command printed by the
|
|
26
|
+
installer, or:
|
|
24
27
|
|
|
25
28
|
```sh
|
|
26
|
-
eval "$(command rl setup shell zsh)"
|
|
29
|
+
eval "$(command rl setup shell zsh)"
|
|
27
30
|
# or
|
|
28
|
-
eval "$(command rl setup shell bash)"
|
|
31
|
+
eval "$(command rl setup shell bash)"
|
|
29
32
|
```
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
This activates the setup already registered for future terminals. Shell
|
|
35
|
+
integration is what lets `rl` change the calling terminal's directory; an
|
|
36
|
+
executable by itself can only print the selected path. The integration loads
|
|
37
|
+
the current runtime on each call, including after npm upgrades.
|
|
38
|
+
|
|
39
|
+
Tool installation needs Homebrew on macOS, or Homebrew/APT/DNF/Yum/Pacman/APK
|
|
40
|
+
on Linux, with permission to install packages. A failed setup reports the command
|
|
41
|
+
to finish in your terminal. If lifecycle scripts were disabled, run
|
|
42
|
+
`rl setup install` to complete everything. `rl setup dependencies --check`
|
|
43
|
+
checks tools without changing them. GitHub PR commands require `gh auth login`.
|
|
34
44
|
|
|
35
45
|
## Get started
|
|
36
46
|
|
|
@@ -81,13 +91,17 @@ under `rl agent`, cleanup under `rl storage`, and project/shell setup under
|
|
|
81
91
|
| `rl delete` | Remove workspaces |
|
|
82
92
|
| `rl storage` | Inspect disk usage and clean generated files |
|
|
83
93
|
|
|
84
|
-
`rl
|
|
85
|
-
|
|
94
|
+
`rl switch <branch>` opens an existing checkout or creates a managed workspace
|
|
95
|
+
for an existing local or origin branch. Remote branches are tracked automatically.
|
|
96
|
+
`rl switch -c <branch>` creates a feature workspace from the configured base and
|
|
97
|
+
refuses an already checked-out branch. Both use nested branch names as directory
|
|
98
|
+
paths. Switching installs hooks and preserves existing notes. A checkout that RL
|
|
99
|
+
adopts outside its usual worktree folder also appears in the open picker and can
|
|
100
|
+
be selected by directory name or branch. Unadopted outside checkouts stay hidden;
|
|
101
|
+
the delete picker remains restricted to the managed worktree folder.
|
|
86
102
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
new checkout; `new` and `open` can reopen an existing checkout. `open` flattens
|
|
90
|
-
slashes in directory names; `new` and `switch -c` retain nested names.
|
|
103
|
+
`rl help path` documents printing workspace paths, and `rl help exec` describes
|
|
104
|
+
running a command with a workspace's configured environment.
|
|
91
105
|
|
|
92
106
|
Pickers show cached PR state, commits ahead/behind the base, and recent use.
|
|
93
107
|
The `COMMITS + / −` column counts commits ahead/behind, rather than changed lines.
|
|
@@ -210,7 +224,6 @@ rl pr # open this branch's PR in your browser
|
|
|
210
224
|
rl pr sync # save this branch's PR state
|
|
211
225
|
rl pr show # readable saved snapshot with the full PR link
|
|
212
226
|
rl pr show --json # saved metadata for scripts
|
|
213
|
-
rl pr checks [--json] # query checks and preserve gh's exit status
|
|
214
227
|
rl pr checkout 123 # new managed pr-123 workspace
|
|
215
228
|
rl pr checkout 123 --name review/login
|
|
216
229
|
rl pr update --base test # select your RL session PRs behind test, then update them
|
|
@@ -224,7 +237,8 @@ refresh output. `rl pr show` works offline. `rl pr` uses the saved URL when
|
|
|
224
237
|
available; if the browser cannot open, that link remains in its output. With no
|
|
225
238
|
saved PR, it looks up the current branch through `gh pr view --web`.
|
|
226
239
|
|
|
227
|
-
Opening, refreshing,
|
|
240
|
+
Opening, refreshing, and checkout require authenticated `gh`. Use `gh pr checks`
|
|
241
|
+
to inspect CI results. Checkout verifies the origin repository,
|
|
228
242
|
PR identity, and fetched commit before creating a workspace. Fork PR checkout
|
|
229
243
|
is currently rejected. It does not change your current branch, publish, or merge
|
|
230
244
|
a PR. Failed refreshes retain the previous snapshot.
|
|
@@ -336,7 +350,7 @@ used matching session directly.
|
|
|
336
350
|
`rl continue` still resumes the exact session saved in its task handoff.
|
|
337
351
|
|
|
338
352
|
Run Codex normally in an adopted RL workspace, including in the Codex app.
|
|
339
|
-
`rl status`, `rl list`, `rl
|
|
353
|
+
`rl status`, `rl list`, `rl agent sessions`, and `rl resume` / `rl resume codex`
|
|
340
354
|
automatically discover and save matching sessions through Codex's
|
|
341
355
|
[`thread/list` API](https://learn.chatgpt.com/docs/app-server#list-threads-with-pagination--filters).
|
|
342
356
|
Discovery filters by the exact workspace directory, excludes subagents and
|
|
@@ -387,6 +401,14 @@ creates and controls its native children. RL supplies their editable roles,
|
|
|
387
401
|
handoff rules, review policy and durable run records. RL never launches a
|
|
388
402
|
provider CLI, model API or check process for a workflow.
|
|
389
403
|
|
|
404
|
+
Use your normal agent session in the workspace. RL's hooks advertise discovery
|
|
405
|
+
automatically; a special launcher or pasted workflow prompt is unnecessary.
|
|
406
|
+
An agent can run `rl workflow context --json` through its ordinary shell tool.
|
|
407
|
+
The response includes active definitions plus `protocol.tools`: each operation's
|
|
408
|
+
input schema, CLI command, positional/flag mappings and JSON stdin requirements.
|
|
409
|
+
The same schemas are advertised through MCP, so an agent can use either connection.
|
|
410
|
+
Native subagent support still comes from the current session's host.
|
|
411
|
+
|
|
390
412
|
The default team starts together and stays available for the task:
|
|
391
413
|
|
|
392
414
|
- **Writer** implements scoped changes. Before adding a function, it asks DRY
|
|
@@ -600,27 +622,30 @@ the server does not inherit it. Claude requires an explicit UUID.
|
|
|
600
622
|
Append tools take `{"text":"..."}`. They share the same locked document updates
|
|
601
623
|
as the CLI and expose no arbitrary command, deletion, or filesystem tool.
|
|
602
624
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
```toml
|
|
606
|
-
[mcp_servers.rl]
|
|
607
|
-
command = "rl"
|
|
608
|
-
args = ["agent", "mcp"]
|
|
609
|
-
```
|
|
625
|
+
From an adopted RL workspace, connect your current agent with:
|
|
610
626
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
```json
|
|
614
|
-
{
|
|
615
|
-
"mcpServers": {
|
|
616
|
-
"rl": { "type": "stdio", "command": "rl", "args": ["agent", "mcp"] }
|
|
617
|
-
}
|
|
618
|
-
}
|
|
627
|
+
```sh
|
|
628
|
+
rl setup mcp
|
|
619
629
|
```
|
|
620
630
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
631
|
+
This configures both `.codex/config.toml` and `.mcp.json` in the current checkout.
|
|
632
|
+
Pass `codex` or `claude` to select one client, or `--dry-run` to preview changes.
|
|
633
|
+
Setup preserves other servers and settings, including Codex comments. It updates
|
|
634
|
+
RL-managed entries on later runs and leaves a conflicting manually configured
|
|
635
|
+
`rl` entry for you to resolve. Both files are validated before writing either.
|
|
636
|
+
|
|
637
|
+
The client starts the installed Node and RL entry point directly. The server is
|
|
638
|
+
bound to this checkout, repository and RL state directory, even when the client
|
|
639
|
+
starts it elsewhere or has no `rl` executable on PATH. Entries contain local
|
|
640
|
+
installation/workspace paths: run setup in each checkout and rerun after moving
|
|
641
|
+
Node or RL. Setup changes only this workspace's configuration.
|
|
642
|
+
|
|
643
|
+
Reload the client and review its normal project trust/MCP approval. The server
|
|
644
|
+
supplies discovery, consultations and rated reviews to your existing session;
|
|
645
|
+
the host supplies native agent creation and messaging. CLI discovery remains
|
|
646
|
+
available without an MCP connection. The stdio server can also be launched with
|
|
647
|
+
`rl agent mcp`, or `rl agent mcp --workspace <absolute-path>` when the repository
|
|
648
|
+
has been selected explicitly. Configuration follows the official
|
|
624
649
|
[Codex MCP guide](https://learn.chatgpt.com/docs/extend/mcp?surface=cli) and
|
|
625
650
|
[Claude MCP guide](https://code.claude.com/docs/en/mcp).
|
|
626
651
|
RL does not modify global client settings or relax agent sandbox permissions.
|
|
@@ -697,22 +722,6 @@ Environment variables can also come from trusted zsh configuration selected by
|
|
|
697
722
|
[config.example.zsh](config.example.zsh). Registration supplies repository/base
|
|
698
723
|
settings; `rl help` and individual `--help` show command options.
|
|
699
724
|
|
|
700
|
-
## Compatibility aliases
|
|
701
|
-
|
|
702
|
-
Existing commands keep their behavior. They are omitted from the main help to
|
|
703
|
-
keep one primary spelling for each operation:
|
|
704
|
-
|
|
705
|
-
| Previous command | Primary command |
|
|
706
|
-
| --- | --- |
|
|
707
|
-
| `rl merge` | `rl git merge` |
|
|
708
|
-
| `rl context/progress show/set/append/path` | `rl notes show/set/append/path context/progress` |
|
|
709
|
-
| `rl summary` | `rl notes summary` |
|
|
710
|
-
| `rl session list/save/add` | `rl agent sessions/save` |
|
|
711
|
-
| `rl handoff`, `rl mcp` | `rl agent handoff`, `rl agent mcp` |
|
|
712
|
-
| `rl clean` | `rl storage clean` |
|
|
713
|
-
| `rl config init/show`, `rl adopt`, `rl init` | `rl setup init/show`, `rl setup adopt`, `rl setup shell` |
|
|
714
|
-
| `rl status --list`, `rl status --all` | `rl list`, `rl list --all` |
|
|
715
|
-
|
|
716
725
|
## Development
|
|
717
726
|
|
|
718
727
|
```sh
|
package/bin/rl
CHANGED
|
@@ -1,54 +1,2 @@
|
|
|
1
|
-
#!/bin/
|
|
2
|
-
|
|
3
|
-
readonly rl_home="${${(%):-%x}:A:h:h}"
|
|
4
|
-
if [[ "$1" == __hook ]]; then
|
|
5
|
-
# Hooks resolve the installed CLI on PATH, not a version-specific package path.
|
|
6
|
-
# Their explicit repository/state arguments must not be replaced by user config.
|
|
7
|
-
shift
|
|
8
|
-
exec node "$rl_home/dist/cli.js" state "$@"
|
|
9
|
-
fi
|
|
10
|
-
if [[ "$1" == --version || "$1" == -V ]]; then
|
|
11
|
-
exec node -p 'JSON.parse(require("node:fs").readFileSync(process.argv[1], "utf8")).version' "$rl_home/package.json"
|
|
12
|
-
fi
|
|
13
|
-
if [[ "$1" == init ]]; then
|
|
14
|
-
if (( $# == 1 )); then
|
|
15
|
-
exec node "$rl_home/dist/help.js" init
|
|
16
|
-
fi
|
|
17
|
-
for argument in "$@"; do
|
|
18
|
-
[[ "$argument" == -- ]] && break
|
|
19
|
-
if [[ "$argument" == --help || "$argument" == -h ]]; then
|
|
20
|
-
exec node "$rl_home/dist/help.js" "$@"
|
|
21
|
-
fi
|
|
22
|
-
done
|
|
23
|
-
if [[ "$2" != zsh && "$2" != bash || $# != 2 ]]; then
|
|
24
|
-
print -u2 'Usage: rl setup shell <zsh|bash>'
|
|
25
|
-
exit 1
|
|
26
|
-
fi
|
|
27
|
-
if [[ "$2" == zsh ]]; then
|
|
28
|
-
print -r -- "source ${(q)rl_home}/shell/rl.zsh"
|
|
29
|
-
else
|
|
30
|
-
print -r -- "_rl_executable=${(qq)rl_home}/bin/rl"
|
|
31
|
-
cat <<'BASH'
|
|
32
|
-
rl() {
|
|
33
|
-
local destination_file destination result previous="$PWD"
|
|
34
|
-
local -a arguments=("$@")
|
|
35
|
-
if [[ "$1" == switch && "${2:-}" == - ]]; then
|
|
36
|
-
[[ -n "${RL_PREVIOUS_WORKSPACE:-}" ]] || { printf '%s\n' 'No previous workspace in this shell' >&2; return 1; }
|
|
37
|
-
arguments[1]="$RL_PREVIOUS_WORKSPACE"
|
|
38
|
-
fi
|
|
39
|
-
destination_file=$(mktemp "${TMPDIR:-/tmp}/rl-shell.XXXXXXXX") || return 1
|
|
40
|
-
if RL_SHELL_OUTPUT="$destination_file" command "$_rl_executable" "${arguments[@]}"; then result=0; else result=$?; fi
|
|
41
|
-
destination=$(cat -- "$destination_file")
|
|
42
|
-
rm -f -- "$destination_file"
|
|
43
|
-
if [[ -n "$destination" ]]; then
|
|
44
|
-
builtin cd -- "$destination" || return 1
|
|
45
|
-
RL_PREVIOUS_WORKSPACE="$previous"
|
|
46
|
-
fi
|
|
47
|
-
return "$result"
|
|
48
|
-
}
|
|
49
|
-
BASH
|
|
50
|
-
fi
|
|
51
|
-
exit 0
|
|
52
|
-
fi
|
|
53
|
-
source "$rl_home/lib/rl.zsh" || exit 1
|
|
54
|
-
_rl_main "$@"
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
await import('../dist/bootstrap.js');
|
package/dist/agent-launch.js
CHANGED
|
@@ -53,7 +53,7 @@ export async function main(argv) {
|
|
|
53
53
|
if (extra.length ||
|
|
54
54
|
values.prompt !== undefined ||
|
|
55
55
|
(values.stdout && values.output !== undefined))
|
|
56
|
-
throw new Error("Usage: rl handoff [agent] [--stdout | --output path]");
|
|
56
|
+
throw new Error("Usage: rl agent handoff [agent] [--stdout | --output path]");
|
|
57
57
|
const text = await handoff(context.store, name, target.path);
|
|
58
58
|
if (typeof values.output === "string")
|
|
59
59
|
fs.writeFileSync(values.output, text, { flag: "wx", mode: 0o600 });
|
package/dist/agent-resume.js
CHANGED
|
@@ -19,7 +19,7 @@ export async function resumeSessions(store, kind, options = {}) {
|
|
|
19
19
|
: a.agent.localeCompare(b.agent) || a.id.localeCompare(b.id);
|
|
20
20
|
});
|
|
21
21
|
if (!choices.length)
|
|
22
|
-
throw new Error(`No ${kind ? `${kind} ` : ""}session recorded for ${data.id}. Use 'rl
|
|
22
|
+
throw new Error(`No ${kind ? `${kind} ` : ""}session recorded for ${data.id}. Use 'rl agent save' to save a session first`);
|
|
23
23
|
let selected = choices[0];
|
|
24
24
|
if (choices.length > 1 && !options.latest) {
|
|
25
25
|
const visible = (value) => stripVTControlCharacters(value).replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
|
|
@@ -70,7 +70,7 @@ export async function resumeAgent(store, kind, options = {}) {
|
|
|
70
70
|
? sessions.find((item) => item.id === sessionId(options.sessionId))
|
|
71
71
|
: sessions.reduce((a, b) => !a || timestamp(b.lastUsedAt) > timestamp(a.lastUsedAt) ? b : a, undefined);
|
|
72
72
|
if (!item)
|
|
73
|
-
throw new Error(`No ${kind} session recorded for ${data.id}. Use 'rl
|
|
73
|
+
throw new Error(`No ${kind} session recorded for ${data.id}. Use 'rl agent save ${kind}${kind === "claude" ? " <UUID>" : ""}'`);
|
|
74
74
|
return [file, data, sessionId(item.id)];
|
|
75
75
|
});
|
|
76
76
|
const stamp = now();
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { findExecutable, setupDependencies } from "./dependencies.js";
|
|
6
|
+
import { installRl } from "./install.js";
|
|
7
|
+
import { explicitHelp } from "./help.js";
|
|
8
|
+
const argv = process.argv.slice(2);
|
|
9
|
+
const selected = argv[0] === "--repo"
|
|
10
|
+
? argv.slice(2)
|
|
11
|
+
: argv[0]?.startsWith("--repo=")
|
|
12
|
+
? argv.slice(1)
|
|
13
|
+
: argv;
|
|
14
|
+
if (selected[0] === "setup" &&
|
|
15
|
+
["dependencies", "install"].includes(selected[1])) {
|
|
16
|
+
try {
|
|
17
|
+
if (selected.includes("--help") || selected.includes("-h"))
|
|
18
|
+
console.log(explicitHelp(selected));
|
|
19
|
+
else
|
|
20
|
+
process.exitCode =
|
|
21
|
+
selected[1] === "install"
|
|
22
|
+
? await installRl(selected.slice(2))
|
|
23
|
+
: await setupDependencies(selected.slice(2));
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
console.error(`rl: ${error instanceof Error ? error.message : String(error)}`);
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else if (argv[0] === "--version" || argv[0] === "-V") {
|
|
31
|
+
console.log(JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8")).version);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const zsh = findExecutable("zsh");
|
|
35
|
+
if (!zsh) {
|
|
36
|
+
console.error("rl: zsh is required. Run 'rl setup dependencies' to install the RL toolchain.");
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const child = spawn(zsh, [
|
|
41
|
+
"-f",
|
|
42
|
+
fileURLToPath(new URL("../lib/entry.zsh", import.meta.url)),
|
|
43
|
+
...argv,
|
|
44
|
+
], {
|
|
45
|
+
stdio: "inherit",
|
|
46
|
+
env: {
|
|
47
|
+
...process.env,
|
|
48
|
+
PATH: `${process.env.PATH ? process.env.PATH + path.delimiter : ""}${path.dirname(process.execPath)}`,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
const interrupt = () => child.kill("SIGINT");
|
|
52
|
+
const terminate = () => child.kill("SIGTERM");
|
|
53
|
+
process.on("SIGINT", interrupt);
|
|
54
|
+
process.on("SIGTERM", terminate);
|
|
55
|
+
await new Promise((resolve) => {
|
|
56
|
+
const cleanup = () => {
|
|
57
|
+
process.off("SIGINT", interrupt);
|
|
58
|
+
process.off("SIGTERM", terminate);
|
|
59
|
+
};
|
|
60
|
+
child.on("error", (error) => {
|
|
61
|
+
cleanup();
|
|
62
|
+
console.error(`rl: ${error.message}`);
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
resolve();
|
|
65
|
+
});
|
|
66
|
+
child.on("close", (code, signal) => {
|
|
67
|
+
cleanup();
|
|
68
|
+
process.exitCode =
|
|
69
|
+
code ?? (signal === "SIGINT" ? 130 : signal === "SIGTERM" ? 143 : 1);
|
|
70
|
+
resolve();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ import { main as git } from "./git-workflow.js";
|
|
|
10
10
|
import { main as pr } from "./pr-workflow.js";
|
|
11
11
|
import { main as agent } from "./agent-launch.js";
|
|
12
12
|
import { main as mcp } from "./mcp.js";
|
|
13
|
+
import { main as mcpSetup } from "./mcp-setup.js";
|
|
13
14
|
import { main as task } from "./task.js";
|
|
14
15
|
import { main as storage } from "./storage.js";
|
|
15
16
|
import { main as prUpdate } from "./pr-update.js";
|
|
@@ -25,6 +26,7 @@ const commands = {
|
|
|
25
26
|
pr,
|
|
26
27
|
agent,
|
|
27
28
|
mcp,
|
|
29
|
+
"mcp-setup": mcpSetup,
|
|
28
30
|
task,
|
|
29
31
|
storage,
|
|
30
32
|
"pr-update": prUpdate,
|
package/dist/command-context.js
CHANGED
|
@@ -49,7 +49,7 @@ export function resolveWorkspace(context, selector) {
|
|
|
49
49
|
const [root, branch] = context.store.worktree(context.repo);
|
|
50
50
|
return { path: root, name: "@", branch };
|
|
51
51
|
}
|
|
52
|
-
const choices = candidates(context.repo, context.managedDir);
|
|
52
|
+
const choices = candidates(context.repo, context.managedDir, context.store);
|
|
53
53
|
const matches = choices.filter((item) => item.name === selector ||
|
|
54
54
|
item.branch === selector ||
|
|
55
55
|
(path.isAbsolute(selector) && item.path === canonical(selector)));
|
package/dist/common.js
CHANGED
|
@@ -6,6 +6,7 @@ import { spawnSync, spawn } from "node:child_process";
|
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
import lockfile from "proper-lockfile";
|
|
8
8
|
import { readJsonObject } from "./json.js";
|
|
9
|
+
import { executableError } from "./dependencies.js";
|
|
9
10
|
export const now = () => new Date().toISOString();
|
|
10
11
|
export const message = (error) => error instanceof Error ? error.message : String(error);
|
|
11
12
|
export const json = (data) => console.log(JSON.stringify(data, null, 2));
|
|
@@ -54,7 +55,7 @@ export function run(argv, cwd, raw = false) {
|
|
|
54
55
|
maxBuffer: 32 * 1024 * 1024,
|
|
55
56
|
});
|
|
56
57
|
if (result.error)
|
|
57
|
-
throw result.error;
|
|
58
|
+
throw executableError(result.error, argv[0], cwd);
|
|
58
59
|
if (result.status !== 0)
|
|
59
60
|
throw new Error(result.stderr.trim() || `Command failed: ${argv.join(" ")}`);
|
|
60
61
|
return raw ? result.stdout : result.stdout.trim();
|
|
@@ -130,7 +131,7 @@ export async function execute(argv, cwd, options = {}) {
|
|
|
130
131
|
};
|
|
131
132
|
child.on("error", (error) => {
|
|
132
133
|
cleanup();
|
|
133
|
-
reject(error);
|
|
134
|
+
reject(executableError(error, argv[0], cwd));
|
|
134
135
|
});
|
|
135
136
|
child.on("close", (code, signal) => {
|
|
136
137
|
cleanup();
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
export const TOOL_NAMES = ["git", "zsh", "fzf", "gh"];
|
|
7
|
+
export function findExecutable(name, env = process.env) {
|
|
8
|
+
for (const directory of (env.PATH ?? "").split(path.delimiter)) {
|
|
9
|
+
if (!directory)
|
|
10
|
+
continue;
|
|
11
|
+
const file = path.resolve(directory, name);
|
|
12
|
+
try {
|
|
13
|
+
fs.accessSync(file, fs.constants.X_OK);
|
|
14
|
+
if (fs.statSync(file).isFile())
|
|
15
|
+
return file;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
/* Try the next PATH entry. */
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
export function inspectDependencies(env = process.env) {
|
|
24
|
+
return TOOL_NAMES.map((name) => {
|
|
25
|
+
const executable = findExecutable(name, env);
|
|
26
|
+
const probe = executable
|
|
27
|
+
? spawnSync(executable, ["--version"], {
|
|
28
|
+
env,
|
|
29
|
+
encoding: "utf8",
|
|
30
|
+
timeout: 5000,
|
|
31
|
+
})
|
|
32
|
+
: undefined;
|
|
33
|
+
return {
|
|
34
|
+
name,
|
|
35
|
+
path: executable,
|
|
36
|
+
available: probe?.status === 0,
|
|
37
|
+
version: probe?.status === 0
|
|
38
|
+
? (probe.stdout.trim().split(/\r?\n/)[0]?.slice(0, 200) ?? null)
|
|
39
|
+
: null,
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function installPlan(missing, host, interactive) {
|
|
44
|
+
const env = host.env ?? process.env;
|
|
45
|
+
const platform = host.platform ?? process.platform;
|
|
46
|
+
const brew = findExecutable("brew", env);
|
|
47
|
+
if (brew)
|
|
48
|
+
return [{ executable: brew, args: ["install", ...missing] }];
|
|
49
|
+
if (platform === "darwin")
|
|
50
|
+
throw new Error("Homebrew is required to install missing RL tools on macOS. Install it from https://brew.sh, then run 'rl setup dependencies'.");
|
|
51
|
+
if (platform !== "linux")
|
|
52
|
+
throw new Error("RL supports macOS and Linux");
|
|
53
|
+
const managers = [
|
|
54
|
+
{ name: "apt-get", args: ["install", "-y"], packages: missing },
|
|
55
|
+
{ name: "dnf", args: ["install", "-y"], packages: missing },
|
|
56
|
+
{ name: "yum", args: ["install", "-y"], packages: missing },
|
|
57
|
+
{
|
|
58
|
+
name: "pacman",
|
|
59
|
+
args: ["-S", "--needed", "--noconfirm"],
|
|
60
|
+
packages: missing.map((name) => (name === "gh" ? "github-cli" : name)),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "apk",
|
|
64
|
+
args: ["add"],
|
|
65
|
+
packages: missing.map((name) => (name === "gh" ? "github-cli" : name)),
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
for (const manager of managers) {
|
|
69
|
+
const executable = findExecutable(manager.name, env);
|
|
70
|
+
if (!executable)
|
|
71
|
+
continue;
|
|
72
|
+
const commands = manager.name === "apt-get"
|
|
73
|
+
? [
|
|
74
|
+
{ executable, args: ["update"] },
|
|
75
|
+
{ executable, args: [...manager.args, ...manager.packages] },
|
|
76
|
+
]
|
|
77
|
+
: [{ executable, args: [...manager.args, ...manager.packages] }];
|
|
78
|
+
const uid = host.uid ?? process.getuid?.();
|
|
79
|
+
if (uid === 0)
|
|
80
|
+
return commands;
|
|
81
|
+
const sudo = findExecutable("sudo", env);
|
|
82
|
+
if (!sudo)
|
|
83
|
+
throw new Error(`Installing missing RL tools requires administrator access. Run: sudo ${manager.name} ${[...manager.args, ...manager.packages].join(" ")}`);
|
|
84
|
+
return commands.map((command) => ({
|
|
85
|
+
executable: sudo,
|
|
86
|
+
args: [
|
|
87
|
+
...(interactive ? [] : ["-n"]),
|
|
88
|
+
command.executable,
|
|
89
|
+
...command.args,
|
|
90
|
+
],
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
throw new Error("No supported package manager found. Install Git, zsh, fzf and gh, then run 'rl setup dependencies --check'.");
|
|
94
|
+
}
|
|
95
|
+
export async function setupDependencies(argv, host = {}) {
|
|
96
|
+
const { values, positionals } = parseArgs({
|
|
97
|
+
args: argv,
|
|
98
|
+
allowPositionals: true,
|
|
99
|
+
options: {
|
|
100
|
+
install: { type: "boolean" },
|
|
101
|
+
check: { type: "boolean" },
|
|
102
|
+
"dry-run": { type: "boolean" },
|
|
103
|
+
json: { type: "boolean" },
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
if (positionals.length ||
|
|
107
|
+
(values.check && (values.install || values["dry-run"])))
|
|
108
|
+
throw new Error("Usage: rl setup dependencies [--check | --dry-run] [--json]");
|
|
109
|
+
const env = host.env ?? process.env;
|
|
110
|
+
let tools = inspectDependencies(env);
|
|
111
|
+
const missing = tools
|
|
112
|
+
.filter((tool) => !tool.available)
|
|
113
|
+
.map((tool) => tool.name);
|
|
114
|
+
let commands = [];
|
|
115
|
+
let error;
|
|
116
|
+
const interactive = !!process.stdin.isTTY && !!process.stdout.isTTY && !values.json;
|
|
117
|
+
if (missing.length && !values.check) {
|
|
118
|
+
try {
|
|
119
|
+
commands = installPlan(missing, host, interactive);
|
|
120
|
+
if (!values["dry-run"]) {
|
|
121
|
+
console.error(`RL: installing missing tools: ${missing.join(", ")}`);
|
|
122
|
+
for (const command of commands) {
|
|
123
|
+
const result = spawnSync(command.executable, command.args, {
|
|
124
|
+
env: {
|
|
125
|
+
...env,
|
|
126
|
+
HOMEBREW_NO_AUTO_UPDATE: "1",
|
|
127
|
+
...(interactive ? {} : { NONINTERACTIVE: "1" }),
|
|
128
|
+
},
|
|
129
|
+
stdio: interactive ? "inherit" : ["ignore", "pipe", "pipe"],
|
|
130
|
+
encoding: "utf8",
|
|
131
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
132
|
+
});
|
|
133
|
+
if (!interactive) {
|
|
134
|
+
if (result.stdout)
|
|
135
|
+
process.stderr.write(result.stdout);
|
|
136
|
+
if (result.stderr)
|
|
137
|
+
process.stderr.write(result.stderr);
|
|
138
|
+
}
|
|
139
|
+
if (result.error || result.status !== 0)
|
|
140
|
+
throw new Error(`Could not install RL tools. Run '${command.executable} ${command.args.filter((arg) => arg !== "-n").join(" ")}' in your terminal, then rerun 'rl setup dependencies'.`);
|
|
141
|
+
}
|
|
142
|
+
tools = inspectDependencies(env);
|
|
143
|
+
const remaining = tools
|
|
144
|
+
.filter((tool) => !tool.available)
|
|
145
|
+
.map((tool) => tool.name);
|
|
146
|
+
if (remaining.length)
|
|
147
|
+
throw new Error(`Tools still unavailable on PATH: ${remaining.join(", ")}. Add the package manager's bin directory to PATH, then rerun 'rl setup dependencies'.`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (failure) {
|
|
151
|
+
error = failure instanceof Error ? failure.message : String(failure);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const ready = tools.every((tool) => tool.available);
|
|
155
|
+
const report = {
|
|
156
|
+
ready,
|
|
157
|
+
dryRun: !!values["dry-run"],
|
|
158
|
+
tools,
|
|
159
|
+
commands,
|
|
160
|
+
...(error ? { error } : {}),
|
|
161
|
+
};
|
|
162
|
+
if (values.json)
|
|
163
|
+
console.log(JSON.stringify(report, null, 2));
|
|
164
|
+
else {
|
|
165
|
+
for (const tool of tools)
|
|
166
|
+
console.log(`${tool.name}: ${tool.available ? (tool.version ?? "installed") : "missing or not working"}`);
|
|
167
|
+
if (values["dry-run"])
|
|
168
|
+
for (const command of commands)
|
|
169
|
+
console.log(`Would run: ${command.executable} ${command.args.join(" ")}`);
|
|
170
|
+
if (error)
|
|
171
|
+
console.error(`RL: ${error}`);
|
|
172
|
+
else if (!ready && values.check)
|
|
173
|
+
console.error("Run 'rl setup dependencies' to install missing tools.");
|
|
174
|
+
}
|
|
175
|
+
return error ? 1 : values["dry-run"] || ready ? 0 : 1;
|
|
176
|
+
}
|
|
177
|
+
export function executableError(error, executable, cwd) {
|
|
178
|
+
if (error.code !== "ENOENT")
|
|
179
|
+
return error;
|
|
180
|
+
if (!fs.existsSync(cwd))
|
|
181
|
+
return new Error(`Working directory does not exist: ${cwd}`, {
|
|
182
|
+
cause: error,
|
|
183
|
+
});
|
|
184
|
+
if (TOOL_NAMES.includes(path.basename(executable)))
|
|
185
|
+
return new Error(`Missing required tool ${path.basename(executable)}. Run 'rl setup dependencies' to install Git, zsh, fzf and gh.`, { cause: error });
|
|
186
|
+
return error;
|
|
187
|
+
}
|
|
188
|
+
if (process.argv[1] &&
|
|
189
|
+
path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
190
|
+
try {
|
|
191
|
+
process.exitCode = await setupDependencies(process.argv.slice(2));
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
console.error(`RL: ${error instanceof Error ? error.message : String(error)}`);
|
|
195
|
+
process.exitCode = 1;
|
|
196
|
+
}
|
|
197
|
+
}
|
package/dist/doctor.js
CHANGED
|
@@ -126,7 +126,7 @@ export async function doctor(store, selected, asJson = false) {
|
|
|
126
126
|
console.log(` CLI: ${item.executablePath ?? "not on PATH"}; launch flags: ${item.launchVerified ? "verified in installed help" : "unverified"}; hook trust: unknown`);
|
|
127
127
|
console.log(` Last received event: ${item.lastSuccess ? `${item.lastSuccess.event} at ${item.lastSuccess.at}` : "none"}`);
|
|
128
128
|
if (item.missingEvents.length)
|
|
129
|
-
console.log(` Missing hooks: ${item.missingEvents.join(", ")}. Run rl adopt.`);
|
|
129
|
+
console.log(` Missing hooks: ${item.missingEvents.join(", ")}. Run rl setup adopt.`);
|
|
130
130
|
if (item.configError)
|
|
131
131
|
console.log(` Configuration error: ${item.configError}`);
|
|
132
132
|
if (item.cliError)
|