rl-workspaces 0.5.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.
Files changed (55) hide show
  1. package/README.md +234 -193
  2. package/bin/rl +2 -54
  3. package/dist/agent-launch.js +1 -1
  4. package/dist/agent-presets.js +42 -17
  5. package/dist/agent-resume.js +2 -2
  6. package/dist/agent-runner.js +20 -0
  7. package/dist/agent-workflows.js +60 -16
  8. package/dist/automation-config.js +144 -5
  9. package/dist/bootstrap.js +74 -0
  10. package/dist/cli.js +2 -0
  11. package/dist/command-context.js +1 -1
  12. package/dist/common.js +3 -2
  13. package/dist/dependencies.js +197 -0
  14. package/dist/doctor.js +1 -1
  15. package/dist/git-workflow.js +8 -102
  16. package/dist/help.js +113 -116
  17. package/dist/hooks.js +1 -1
  18. package/dist/install.js +143 -0
  19. package/dist/mcp-setup.js +171 -0
  20. package/dist/mcp.js +13 -16
  21. package/dist/picker.js +24 -7
  22. package/dist/pr-workflow.js +7 -28
  23. package/dist/repos.js +5 -1
  24. package/dist/session-workflows.js +122 -18
  25. package/dist/session.js +1 -1
  26. package/dist/state.js +4 -14
  27. package/dist/storage.js +2 -2
  28. package/dist/store.js +2 -2
  29. package/dist/summary.js +1 -1
  30. package/dist/workflow-collaboration.js +531 -0
  31. package/dist/workflow.js +24 -38
  32. package/lib/entry.zsh +54 -0
  33. package/lib/rl.zsh +37 -9
  34. package/node_modules/proper-lockfile/CHANGELOG.md +108 -0
  35. package/node_modules/smol-toml/LICENSE +24 -0
  36. package/node_modules/smol-toml/README.md +605 -0
  37. package/node_modules/smol-toml/dist/date.d.ts +41 -0
  38. package/node_modules/smol-toml/dist/date.js +156 -0
  39. package/node_modules/smol-toml/dist/error.d.ts +37 -0
  40. package/node_modules/smol-toml/dist/error.js +67 -0
  41. package/node_modules/smol-toml/dist/extract.js +293 -0
  42. package/node_modules/smol-toml/dist/index.cjs +771 -0
  43. package/node_modules/smol-toml/dist/index.d.ts +44 -0
  44. package/node_modules/smol-toml/dist/index.js +34 -0
  45. package/node_modules/smol-toml/dist/parse.d.ts +73 -0
  46. package/node_modules/smol-toml/dist/parse.js +164 -0
  47. package/node_modules/smol-toml/dist/primitive.js +151 -0
  48. package/node_modules/smol-toml/dist/stringify.d.ts +32 -0
  49. package/node_modules/smol-toml/dist/stringify.js +219 -0
  50. package/node_modules/smol-toml/dist/struct.js +156 -0
  51. package/node_modules/smol-toml/dist/util.d.ts +43 -0
  52. package/node_modules/smol-toml/dist/util.js +57 -0
  53. package/node_modules/smol-toml/package.json +68 -0
  54. package/package.json +6 -3
  55. package/shell/rl.zsh +1 -1
package/dist/help.js CHANGED
@@ -21,7 +21,7 @@ const topics = {
21
21
  options: [
22
22
  "-c, --create Create a new branch workspace",
23
23
  "-b, --base <ref> Creation base (default: origin/test unless configured)",
24
- "--no-fetch Use cached refs when creating",
24
+ "--no-fetch Use cached refs when opening or creating",
25
25
  "--setup Run configured project setup",
26
26
  "--agent codex|claude Start an agent after switching",
27
27
  "-- <agent args> Forward options to the selected agent",
@@ -30,13 +30,13 @@ const topics = {
30
30
  "Use rl checkout [repository] to switch repositories in your shell.",
31
31
  "@ opens the main checkout; - returns to the previous workspace.",
32
32
  "Shell integration changes your terminal's directory.",
33
- "Related creation helpers: new reopens existing workspaces; open requires an existing branch.",
33
+ "An existing branch without a checkout gets a managed workspace; origin branches are tracked.",
34
34
  ],
35
35
  examples: [
36
36
  "rl switch feature/login",
37
37
  "rl switch feature/login -c --base origin/test",
38
38
  ],
39
- related: ["new", "open", "path"],
39
+ related: ["path"],
40
40
  },
41
41
  list: {
42
42
  group: "Workspaces",
@@ -64,9 +64,9 @@ const topics = {
64
64
  notes: [
65
65
  "Shows repository selection, checkout path, base, workspaces and saved session counts.",
66
66
  "--workspace includes Git changes, PR, sessions, task handoff and latest workflow.",
67
- "Use rl list for workspace overviews; status --list and --all remain supported.",
67
+ "Use rl list for workspace overviews and --include-deleted for archived records.",
68
68
  ],
69
- related: ["list", "instances"],
69
+ related: ["list"],
70
70
  },
71
71
  delete: {
72
72
  group: "Workspaces",
@@ -122,6 +122,9 @@ const topics = {
122
122
  description: "Configure native agent roles and saved sessions",
123
123
  usage: "rl agent <command>",
124
124
  related: ["resume", "workflow", "policy"],
125
+ notes: [
126
+ "Existing agent sessions discover their workflow with rl workflow context --json. A special launcher or MCP connection is not required.",
127
+ ],
125
128
  },
126
129
  "agent start": {
127
130
  description: "Start an interactive agent",
@@ -149,6 +152,9 @@ const topics = {
149
152
  "agent set": {
150
153
  description: "Save an agent profile",
151
154
  usage: "rl agent set <name> --file <profile.json|->",
155
+ notes: [
156
+ "Use --instructions <file|-> to change instructions while preserving the profile's access, model and other settings.",
157
+ ],
152
158
  },
153
159
  "agent show": {
154
160
  description: "Inspect a workflow agent profile",
@@ -177,8 +183,11 @@ const topics = {
177
183
  },
178
184
  "agent mcp": {
179
185
  description: "Serve workspace context and notes through MCP",
180
- usage: "rl agent mcp",
181
- notes: ["Uses stdio. Start the server in the adopted workspace."],
186
+ usage: "rl agent mcp [--workspace <absolute-path>]",
187
+ notes: [
188
+ "Uses stdio. Start the server in the adopted workspace, or bind one explicitly with --workspace.",
189
+ ],
190
+ related: ["setup mcp"],
182
191
  },
183
192
  workflow: {
184
193
  group: "Agents and tasks",
@@ -196,7 +205,7 @@ const topics = {
196
205
  notes: [
197
206
  "Creates .rl/agents.json, .rl/policies.json and .rl/agent-workflows.json.",
198
207
  "Existing configuration is never overwritten.",
199
- "Roles inherit the current session by default. The preset queues a review before finishing changed work.",
208
+ "Adopted workspaces use built-in writer/reviewer/DRY roles immediately. Init writes editable copies of their profiles, policy and workflow.",
200
209
  ],
201
210
  },
202
211
  "workflow list": {
@@ -220,22 +229,25 @@ const topics = {
220
229
  "--request <UUID> Start a queued workflow request instead of choosing a name",
221
230
  ],
222
231
  notes: [
223
- "Default workflow: triple-review. RL never launches workflow agent or check processes.",
232
+ "Default workflow: writer-reviewer-dry. The parent spawns all three native peers in parallel and binds their handles; RL never launches agent processes.",
224
233
  "The parent claims ready steps, spawns native subagents and reports evidence. RL applies the policy; provider usage is parent-reported.",
225
234
  ],
226
235
  examples: ["rl workflow run --base origin/test --dry-run"],
227
236
  },
228
237
  "workflow add": {
229
238
  description: "Add a workflow with dependencies and spawn triggers",
230
- usage: "rl workflow add <name> --file <workflow.json|->",
239
+ usage: "rl workflow add <name> --file <workflow.json|-> [--default]",
231
240
  },
232
241
  "workflow set": {
233
242
  description: "Save a workflow definition and trigger rules",
234
- usage: "rl workflow set <name> --file <workflow.json|->",
243
+ usage: "rl workflow set <name> --file <workflow.json|-> [--default]",
235
244
  },
236
245
  "workflow context": {
237
246
  description: "Discover profiles, policies, workflows and trigger rules",
238
247
  usage: "rl workflow context [--json]",
248
+ notes: [
249
+ "For an existing agent session: --json includes profiles, policies, workflows, and protocol.tools with every CLI command and input schema. Use your normal shell tool; MCP and rl agent start are optional.",
250
+ ],
239
251
  },
240
252
  "workflow pending": {
241
253
  description: "Read queued workflows for the parent session",
@@ -257,6 +269,30 @@ const topics = {
257
269
  description: "Dismiss an unavailable workflow with a reason",
258
270
  usage: "rl workflow dismiss --file <dismissal.json|-> [--agent codex|claude] [--session <UUID>]",
259
271
  },
272
+ "workflow bind": {
273
+ description: "Bind a role to the native peer spawned by this session",
274
+ usage: "rl workflow bind <run> --file <binding.json|-> [--agent codex|claude] [--session <UUID>]",
275
+ },
276
+ "workflow inbox": {
277
+ description: "Read a peer's requests, review checkpoints and ratings",
278
+ usage: "rl workflow inbox <run> --step <step> --claim <token> [--agent codex|claude] [--session <UUID>]",
279
+ },
280
+ "workflow consult": {
281
+ description: "Ask an existing peer through a configured workflow route",
282
+ usage: "rl workflow consult <run> --file <consultation.json|-> [--agent codex|claude] [--session <UUID>]",
283
+ },
284
+ "workflow answer": {
285
+ description: "Return reuse or duplication evidence to a requesting peer",
286
+ usage: "rl workflow answer <run> --file <answer.json|-> [--agent codex|claude] [--session <UUID>]",
287
+ },
288
+ "workflow submit": {
289
+ description: "Submit writer work to the existing reviewer",
290
+ usage: "rl workflow submit <run> --file <submission.json|-> [--agent codex|claude] [--session <UUID>]",
291
+ },
292
+ "workflow review": {
293
+ description: "Return a rating and evidence for a stable writer checkpoint",
294
+ usage: "rl workflow review <run> --file <review.json|-> [--agent codex|claude] [--session <UUID>]",
295
+ },
260
296
  "workflow status": {
261
297
  description: "Inspect a saved run",
262
298
  usage: "rl workflow status [UUID|latest] [--json]",
@@ -338,33 +374,9 @@ const topics = {
338
374
  },
339
375
  git: {
340
376
  group: "Code and reviews",
341
- description: "Inspect and integrate branch changes",
377
+ description: "Integrate changes across workspaces",
342
378
  usage: "rl git <command>",
343
379
  },
344
- "git diff": {
345
- description: "Show workspace changes against the base",
346
- usage: "rl git diff [--base <ref>] [-- <display options> [-- paths...]]",
347
- examples: ["rl git diff -- --stat"],
348
- },
349
- "git log": {
350
- description: "Show feature commits",
351
- usage: "rl git log [--base <ref>] [-- <display options>]",
352
- examples: ["rl git log -- --graph"],
353
- },
354
- "git commit": {
355
- description: "Commit staged changes",
356
- usage: "rl git commit -m <message> [--all] [--dry-run]",
357
- notes: ["--all includes modified tracked files."],
358
- },
359
- "git rebase": {
360
- description: "Rebase onto the base branch",
361
- usage: "rl git rebase [--base <ref>] [--dry-run]",
362
- },
363
- "git squash": {
364
- description: "Squash feature commits",
365
- usage: "rl git squash -m <message> [--base <ref>] [--dry-run]",
366
- notes: ["The original HEAD is retained under a printed backup ref."],
367
- },
368
380
  "git merge": {
369
381
  description: "Integrate into a checked-out base branch",
370
382
  usage: "rl git merge [target] [--squash -m <message>] [--cleanup] [--dry-run]",
@@ -394,10 +406,6 @@ const topics = {
394
406
  description: "Refresh the saved PR snapshot",
395
407
  usage: "rl pr sync [number|URL] [--json]",
396
408
  },
397
- "pr checks": {
398
- description: "Show GitHub check results",
399
- usage: "rl pr checks [--json]",
400
- },
401
409
  "pr checkout": {
402
410
  description: "Create a workspace for a PR",
403
411
  usage: "rl pr checkout <number> [--name <branch>]",
@@ -510,6 +518,34 @@ const topics = {
510
518
  usage: "rl setup adopt [--extended-hooks]",
511
519
  notes: ["Preserves existing hooks. Review Codex hook trust with /hooks."],
512
520
  },
521
+ "setup mcp": {
522
+ description: "Connect this workspace's RL MCP server to your agent",
523
+ usage: "rl setup mcp [codex|claude] [--dry-run] [--json]",
524
+ notes: [
525
+ "Configures both clients by default: .codex/config.toml and .mcp.json in this adopted workspace.",
526
+ "Preserves other settings and servers. Existing unmanaged entries named rl require manual resolution.",
527
+ "Launches the installed Node/RL directly, with this workspace and its RL state bound explicitly. Rerun after moving the installation.",
528
+ "Reload your client and review its normal project trust/MCP approval. Your existing session creates native subagents.",
529
+ ],
530
+ },
531
+ "setup dependencies": {
532
+ description: "Install or check Git, zsh, fzf and GitHub CLI",
533
+ usage: "rl setup dependencies [--check | --dry-run] [--json]",
534
+ notes: [
535
+ "npm installation installs missing tools through an existing package manager. This command repairs skipped or failed dependency setup.",
536
+ "Uses Homebrew on macOS, and Homebrew, APT, DNF, Yum, Pacman or APK on Linux. Administrator access may be required on Linux.",
537
+ "Runs directly with Node, so it also works before zsh or a repository is available.",
538
+ ],
539
+ },
540
+ "setup install": {
541
+ description: "Complete or repair RL installation and shell integration",
542
+ usage: "rl setup install [--shell zsh|bash]",
543
+ notes: [
544
+ "Global npm installation runs this automatically: missing Git, zsh, fzf and gh are installed, then your shell startup file is updated.",
545
+ "Preserves your settings and updates one RL-owned block. New terminals load RL automatically; reload the installing terminal once.",
546
+ "Use this command after an install with lifecycle scripts disabled, or to repair setup.",
547
+ ],
548
+ },
513
549
  "setup shell": {
514
550
  description: "Print shell integration",
515
551
  usage: "rl setup shell <zsh|bash>",
@@ -524,16 +560,6 @@ const topics = {
524
560
  "--repair reinstalls this workspace's RL hooks.",
525
561
  ],
526
562
  },
527
- new: {
528
- description: "Create or reopen a branch workspace",
529
- usage: "rl new <branch> [--base <ref>] [--no-fetch] [--setup]",
530
- related: ["switch"],
531
- },
532
- open: {
533
- description: "Open an existing local or origin branch",
534
- usage: "rl open --branch <branch> [--setup] [--no-fetch]",
535
- related: ["switch"],
536
- },
537
563
  path: {
538
564
  description: "Print a workspace path",
539
565
  usage: "rl path [name|branch|@]",
@@ -544,55 +570,7 @@ const topics = {
544
570
  usage: "rl exec [--workspace <name>] -- <command> [args...]",
545
571
  related: ["setup run"],
546
572
  },
547
- instances: {
548
- description: "Inspect active and archived instance records",
549
- usage: "rl instances",
550
- related: ["status", "list"],
551
- },
552
573
  };
553
- const aliases = [
554
- [["step"], ["git"]],
555
- [["diff"], ["git", "diff"]],
556
- [["log"], ["git", "log"]],
557
- [["merge"], ["git", "merge"]],
558
- [["clean"], ["storage", "clean"]],
559
- [["summary"], ["notes", "summary"]],
560
- [["handoff"], ["agent", "handoff"]],
561
- [["mcp"], ["agent", "mcp"]],
562
- [
563
- ["session", "list"],
564
- ["agent", "sessions"],
565
- ],
566
- [
567
- ["session", "save"],
568
- ["agent", "save"],
569
- ],
570
- [
571
- ["session", "add"],
572
- ["agent", "save"],
573
- ],
574
- [["session"], ["agent"]],
575
- [
576
- ["config", "init"],
577
- ["setup", "init"],
578
- ],
579
- [
580
- ["config", "show"],
581
- ["setup", "show"],
582
- ],
583
- [["config"], ["setup"]],
584
- [["adopt"], ["setup", "adopt"]],
585
- [["init"], ["setup", "shell"]],
586
- ];
587
- function canonical(words) {
588
- if (words[0] === "status" &&
589
- words.some((word) => word === "--all" || word === "--list"))
590
- return ["list", ...words.slice(1)];
591
- if (["context", "progress"].includes(words[0]))
592
- return ["notes", words[1] ?? "show", words[0], ...words.slice(2)];
593
- const alias = aliases.find(([from]) => from.every((word, i) => words[i] === word));
594
- return alias ? [...alias[1], ...words.slice(alias[0].length)] : words;
595
- }
596
574
  const children = (name) => Object.entries(topics).filter(([key]) => key.startsWith(name + " ") &&
597
575
  key.split(" ").length === name.split(" ").length + 1);
598
576
  const safe = (value) => stripVTControlCharacters(value).replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
@@ -625,9 +603,8 @@ function match(words) {
625
603
  export function topicHelp(words) {
626
604
  if (!words.length)
627
605
  return mainHelp();
628
- const normalized = canonical(words), name = match(normalized);
629
- if (!name ||
630
- (children(name).length && normalized.length > name.split(" ").length))
606
+ const name = match(words);
607
+ if (!name || (children(name).length && words.length > name.split(" ").length))
631
608
  throw new Error(`Unknown help topic: ${safe(words.join(" "))}`);
632
609
  const topic = topics[name];
633
610
  const lines = [topic.description, "", `Usage: ${topic.usage}`];
@@ -666,28 +643,48 @@ export function invocation(module, argv) {
666
643
  while (argv[index]?.startsWith("--") && CONTEXT.has(argv[index].slice(2)))
667
644
  index += 2;
668
645
  const rest = argv.slice(index);
669
- const prefix = {
670
- delete: ["delete"],
671
- summary: ["summary"],
672
- mcp: ["mcp"],
673
- picker: [],
674
- };
675
- const words = Object.hasOwn(prefix, module)
676
- ? [...prefix[module], ...rest]
677
- : module === "agent" && rest[0] !== "handoff"
678
- ? ["agent", ...rest]
679
- : rest;
646
+ if (module === "summary")
647
+ return ["notes", "summary", ...rest];
648
+ if (module === "mcp")
649
+ return ["agent", "mcp", ...rest];
650
+ if (module === "mcp-setup")
651
+ return ["setup", "mcp", ...rest];
652
+ if (module === "delete")
653
+ return ["delete", ...rest];
654
+ if (module === "picker")
655
+ return [];
656
+ if (module === "agent")
657
+ return ["agent", ...rest];
658
+ if (module === "git")
659
+ return ["git", ...rest];
660
+ if (module === "storage")
661
+ return rest[0] === "clean" ? ["storage", ...rest] : rest;
662
+ if (module === "workflow" && rest[0] === "config")
663
+ return ["setup", ...rest.slice(1)];
664
+ if (module === "state") {
665
+ if (rest[0] === "session")
666
+ return [
667
+ "agent",
668
+ rest[1] === "list" ? "sessions" : "save",
669
+ ...rest.slice(2),
670
+ ];
671
+ if (rest[0] === "adopt")
672
+ return ["setup", ...rest];
673
+ if (["context", "progress"].includes(rest[0]))
674
+ return ["notes", rest[1] ?? "show", rest[0], ...rest.slice(2)];
675
+ }
676
+ if (module === "repos" &&
677
+ rest[0] === "status" &&
678
+ rest.some((word) => word === "--all" || word === "--list"))
679
+ return ["list", ...rest.slice(1)];
680
680
  const defaults = {
681
681
  state: "status",
682
682
  repos: "repo",
683
683
  workflow: "switch",
684
- git: "git",
685
- agent: "agent",
686
684
  automation: "workflow",
687
- storage: "storage",
688
685
  "pr-update": "pr",
689
686
  };
690
- return canonical(words.length ? words : defaults[module] ? [defaults[module]] : []);
687
+ return rest.length ? rest : defaults[module] ? [defaults[module]] : [];
691
688
  }
692
689
  export function usageHint(module, argv, details = false) {
693
690
  const words = invocation(module, argv), name = match(words);
package/dist/hooks.js CHANGED
@@ -21,7 +21,7 @@ export async function handleHook(store, key, name) {
21
21
  // cannot change the original instance or the destination instance.
22
22
  const [file, data] = store.instanceForWorktree(root, git(root, "branch", "--show-current"));
23
23
  if (data.key !== key)
24
- throw new Error("Hook belongs to a different RL instance; run 'rl adopt' to repair it");
24
+ throw new Error("Hook belongs to a different RL instance; run 'rl setup adopt' to repair it");
25
25
  try {
26
26
  const event = adapter.parse(payload);
27
27
  // Workflow workers are ephemeral. Supply task context without turning a
@@ -0,0 +1,143 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { parseArgs } from "node:util";
6
+ import { atomicWrite, canonical, inside, shellQuote } from "./common.js";
7
+ import { inspectDependencies, setupDependencies } from "./dependencies.js";
8
+ const BEGIN = "# >>> rl shell integration >>>";
9
+ const END = "# <<< rl shell integration <<<";
10
+ const SHIM = "#!/bin/sh\n# Managed by RL installation\n";
11
+ function profileChange(home, requested, after) {
12
+ const file = canonical(requested);
13
+ if (!inside(file, home))
14
+ throw new Error(`Shell setup path must stay in your home directory: ${requested}`);
15
+ const stat = fs.statSync(file, { throwIfNoEntry: false });
16
+ if (stat && (!stat.isFile() || stat.size > 1024 * 1024))
17
+ throw new Error(`Expected a shell setup file up to 1 MiB: ${requested}`);
18
+ const before = stat ? fs.readFileSync(file, "utf8") : undefined;
19
+ return {
20
+ file,
21
+ before,
22
+ after: after(before ?? ""),
23
+ ...(stat ? { mode: stat.mode & 0o777 } : {}),
24
+ };
25
+ }
26
+ function integration(text, block, shell) {
27
+ const lines = text.split(/\r?\n/);
28
+ const starts = lines.flatMap((line, index) => line === BEGIN ? [index] : []);
29
+ const ends = lines.flatMap((line, index) => (line === END ? [index] : []));
30
+ if (starts.length || ends.length) {
31
+ if (starts.length !== 1 || ends.length !== 1 || starts[0] >= ends[0])
32
+ throw new Error("Malformed RL shell integration block; preserve your settings and repair its markers before reinstalling");
33
+ const prefix = lines.slice(0, starts[0]).join("\n");
34
+ const suffix = lines.slice(ends[0] + 1).join("\n");
35
+ return (prefix ? prefix + "\n" : "") + block + "\n" + suffix;
36
+ }
37
+ const existing = lines.findIndex((line) => [
38
+ `eval "$(rl setup shell ${shell})"`,
39
+ `eval "$(command rl setup shell ${shell})"`,
40
+ `eval "$(rl init ${shell})"`,
41
+ `eval "$(command rl init ${shell})"`,
42
+ ].includes(line.trim()));
43
+ if (existing >= 0) {
44
+ lines[existing] = block;
45
+ return lines.join("\n");
46
+ }
47
+ return (text + (text ? (text.endsWith("\n") ? "\n" : "\n\n") : "") + block + "\n");
48
+ }
49
+ export function configureShell(host, requestedShell) {
50
+ const env = host.env ?? process.env;
51
+ const home = canonical(host.home ?? os.homedir());
52
+ const shell = requestedShell ??
53
+ path.basename(env.SHELL ??
54
+ ((host.platform ?? process.platform) === "darwin" ? "zsh" : "bash"));
55
+ if (shell !== "zsh" && shell !== "bash")
56
+ throw new Error(`Automatic RL shell setup supports zsh and bash. Run 'rl setup install --shell zsh' or '--shell bash' for the shell you use.`);
57
+ const root = host.packageRoot ?? fileURLToPath(new URL("..", import.meta.url));
58
+ const node = host.node ?? process.execPath;
59
+ const executable = path.join(root, "bin/rl");
60
+ const shimDirectory = path.join(home, ".local/share/rl/bin");
61
+ const shim = profileChange(home, path.join(shimDirectory, "rl"), (before) => {
62
+ if (before && !before.startsWith(SHIM))
63
+ throw new Error("The RL installation shim already contains an unmanaged command; it was not replaced");
64
+ return `${SHIM}exec ${shellQuote(node)} ${shellQuote(executable)} "$@"\n`;
65
+ });
66
+ shim.mode = 0o755;
67
+ const toolDirectories = [
68
+ ...new Set([
69
+ path.dirname(node),
70
+ ...inspectDependencies(env).flatMap((tool) => tool.path ? [path.dirname(tool.path)] : []),
71
+ ]),
72
+ ];
73
+ const block = [
74
+ BEGIN,
75
+ `export PATH=${shellQuote(shimDirectory)}:"$PATH":${toolDirectories.map(shellQuote).join(":")}`,
76
+ `eval "$(${shellQuote(node)} ${shellQuote(executable)} setup shell ${shell})"`,
77
+ END,
78
+ ].join("\n");
79
+ const targets = [
80
+ path.join(shell === "zsh" && env.ZDOTDIR ? path.resolve(home, env.ZDOTDIR) : home, shell === "zsh" ? ".zshrc" : ".bashrc"),
81
+ ];
82
+ if (shell === "bash") {
83
+ const login = [".bash_profile", ".bash_login", ".profile"]
84
+ .map((name) => path.join(home, name))
85
+ .find((file) => fs.existsSync(file));
86
+ targets.push(login ?? path.join(home, ".bash_profile"));
87
+ }
88
+ const profiles = targets.map((file) => profileChange(home, file, (text) => integration(text, block, shell)));
89
+ const changes = [
90
+ ...new Map([shim, ...profiles].map((change) => [change.file, change])).values(),
91
+ ];
92
+ for (const change of changes) {
93
+ const current = fs.existsSync(change.file)
94
+ ? fs.readFileSync(change.file, "utf8")
95
+ : undefined;
96
+ if (current !== change.before)
97
+ throw new Error("Shell configuration changed during installation; retry");
98
+ }
99
+ for (const change of changes) {
100
+ if (change.before !== change.after)
101
+ atomicWrite(change.file, change.after);
102
+ if (change.mode !== undefined)
103
+ fs.chmodSync(change.file, change.mode);
104
+ }
105
+ return {
106
+ shell,
107
+ profiles: profiles.map((change) => change.file),
108
+ shim: shim.file,
109
+ };
110
+ }
111
+ export async function installRl(argv, host = {}) {
112
+ const { values, positionals } = parseArgs({
113
+ args: argv,
114
+ options: { shell: { type: "string" } },
115
+ allowPositionals: true,
116
+ });
117
+ if (positionals.length)
118
+ throw new Error("Usage: rl setup install [--shell zsh|bash]");
119
+ const env = host.env ?? process.env;
120
+ if (env.npm_lifecycle_event === "postinstall" &&
121
+ env.npm_config_global !== "true") {
122
+ console.log("RL checkout installed. Run 'rl setup install' to install system tools and register shell integration.");
123
+ return 0;
124
+ }
125
+ const code = await setupDependencies([], host);
126
+ if (code)
127
+ return code;
128
+ const configured = configureShell(host, values.shell);
129
+ console.log(`RL shell integration installed in ${configured.profiles.join(", ")}.`);
130
+ console.log("New terminals can use rl directly. To activate directory switching in this terminal now, run:");
131
+ console.log(` eval "$(${shellQuote(configured.shim)} setup shell ${configured.shell})"`);
132
+ return 0;
133
+ }
134
+ if (process.argv[1] &&
135
+ path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
136
+ try {
137
+ process.exitCode = await installRl(process.argv.slice(2));
138
+ }
139
+ catch (error) {
140
+ console.error(`RL installation: ${error instanceof Error ? error.message : String(error)}`);
141
+ process.exitCode = 1;
142
+ }
143
+ }