aiur-cli 0.0.1 → 0.0.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/README.md +8 -0
- package/bin/aiur.js +120 -22
- package/package.json +9 -6
- package/scripts/postinstall.mjs +173 -0
package/README.md
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
Oversee a fleet of coding agents that **coordinate via events**.
|
|
6
6
|
|
|
7
|
+
> [!WARNING]
|
|
8
|
+
> **Use at your own risk.** Aiur is an unstable, vibecoded engineering preview for trusted
|
|
9
|
+
> environments only. It **bypasses all agent permission prompts** and has very few efficiency
|
|
10
|
+
> optimizations. Suggested for simple tasks under supervision.
|
|
11
|
+
>
|
|
12
|
+
> Provided "as is", without warranty of any kind. You assume all risk for any cost, token
|
|
13
|
+
> spend, data loss, or damage from running it.
|
|
14
|
+
|
|
7
15
|
## Install
|
|
8
16
|
|
|
9
17
|
```bash
|
package/bin/aiur.js
CHANGED
|
@@ -5,6 +5,17 @@ const { spawnSync } = require("node:child_process");
|
|
|
5
5
|
const path = require("node:path");
|
|
6
6
|
const fs = require("node:fs");
|
|
7
7
|
|
|
8
|
+
// Node's spawnSync reports a missing executable via `result.error`; some
|
|
9
|
+
// runtimes (e.g. Bun) throw instead. Normalize to the error-return shape so a
|
|
10
|
+
// missing tmux/opencode/brew degrades gracefully rather than crashing.
|
|
11
|
+
function safeSpawn(command, args, options) {
|
|
12
|
+
try {
|
|
13
|
+
return spawnSync(command, args, options);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
return { error, status: null, stdout: "" };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
8
19
|
// Map this host to the platform package that carries its OTP release.
|
|
9
20
|
const PLATFORMS = {
|
|
10
21
|
"darwin arm64": "darwin-arm64",
|
|
@@ -61,51 +72,138 @@ function resolveReleaseDir() {
|
|
|
61
72
|
return path.join(path.dirname(pkgJson), "release");
|
|
62
73
|
}
|
|
63
74
|
|
|
75
|
+
// Pinned opencode version — single source of truth is this package's
|
|
76
|
+
// package.json `opencodeVersion` (kept in sync with scripts/postinstall.mjs
|
|
77
|
+
// and mise.toml).
|
|
78
|
+
function opencodeVersion() {
|
|
79
|
+
const pkg = JSON.parse(
|
|
80
|
+
fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"),
|
|
81
|
+
);
|
|
82
|
+
return pkg.opencodeVersion;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Lazy first-run provisioning
|
|
87
|
+
//
|
|
88
|
+
// The npm postinstall (scripts/postinstall.mjs) provisions tmux + opencode, but
|
|
89
|
+
// bun/pnpm/yarn block postinstall scripts by default — a package cannot force a
|
|
90
|
+
// consumer to run them. So the launcher guarantees the peer tools here, on the
|
|
91
|
+
// first real (TUI) launch, regardless of which package manager installed
|
|
92
|
+
// aiur-cli. Both steps are idempotent (skip when already satisfied); tmux is a
|
|
93
|
+
// hard requirement, opencode is best-effort. Honors the same AIUR_SKIP_* env
|
|
94
|
+
// vars as the postinstall. This logic mirrors postinstall.mjs intentionally —
|
|
95
|
+
// the launcher stays dependency-free and cannot import the .mjs counterpart.
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
64
98
|
function tmuxVersion() {
|
|
65
|
-
const result =
|
|
99
|
+
const result = safeSpawn("tmux", ["-V"], { encoding: "utf8" });
|
|
66
100
|
if (result.error || result.status !== 0) return null;
|
|
67
101
|
const match = /tmux\s+(\d+)\.(\d+)/.exec(result.stdout || "");
|
|
68
102
|
if (!match) return null;
|
|
69
103
|
return [Number(match[1]), Number(match[2])];
|
|
70
104
|
}
|
|
71
105
|
|
|
106
|
+
function tmuxSatisfiesMin() {
|
|
107
|
+
const version = tmuxVersion();
|
|
108
|
+
if (!version) return false;
|
|
109
|
+
const [major, minor] = version;
|
|
110
|
+
const [needMajor, needMinor] = MIN_TMUX;
|
|
111
|
+
return major > needMajor || (major === needMajor && minor >= needMinor);
|
|
112
|
+
}
|
|
113
|
+
|
|
72
114
|
function tmuxHint() {
|
|
73
115
|
return process.platform === "darwin"
|
|
74
116
|
? "install it with: brew install tmux"
|
|
75
117
|
: "install it with: apt install tmux (or your distro's package manager)";
|
|
76
118
|
}
|
|
77
119
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
120
|
+
// Best-effort system install of tmux. macOS uses Homebrew; Linux uses the first
|
|
121
|
+
// package manager present. Never invokes sudo (a hidden password prompt mid-
|
|
122
|
+
// launch would hang). Returns false so the caller can fail with a clear hint.
|
|
123
|
+
function installTmux(platform) {
|
|
124
|
+
const has = (bin) => !safeSpawn(bin, ["--version"], { stdio: "ignore" }).error;
|
|
125
|
+
if (platform === "darwin") {
|
|
126
|
+
if (!has("brew")) return false;
|
|
127
|
+
return safeSpawn("brew", ["install", "tmux"], { stdio: "inherit" }).status === 0;
|
|
82
128
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
129
|
+
if (platform === "linux") {
|
|
130
|
+
const managers = [
|
|
131
|
+
["apt-get", ["install", "-y", "tmux"]],
|
|
132
|
+
["dnf", ["install", "-y", "tmux"]],
|
|
133
|
+
["apk", ["add", "tmux"]],
|
|
134
|
+
["pacman", ["-S", "--noconfirm", "tmux"]],
|
|
135
|
+
];
|
|
136
|
+
for (const [bin, argv] of managers) {
|
|
137
|
+
if (!has(bin)) continue;
|
|
138
|
+
return safeSpawn(bin, argv, { stdio: "inherit" }).status === 0;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// tmux is required — the TUI runs inside it — so this provisions if missing and
|
|
145
|
+
// fails hard only when tmux still can't be made available.
|
|
146
|
+
function ensureTmux() {
|
|
147
|
+
if (tmuxSatisfiesMin()) return;
|
|
148
|
+
if (process.env.AIUR_SKIP_TMUX_INSTALL !== "1") {
|
|
149
|
+
process.stderr.write(
|
|
150
|
+
`aiur: tmux (>= ${MIN_TMUX.join(".")}) runs the interactive TUI; installing it…\n`,
|
|
151
|
+
);
|
|
152
|
+
installTmux(process.platform);
|
|
153
|
+
}
|
|
154
|
+
if (!tmuxSatisfiesMin()) {
|
|
155
|
+
const v = tmuxVersion();
|
|
88
156
|
fail(
|
|
89
|
-
|
|
90
|
-
|
|
157
|
+
v
|
|
158
|
+
? `tmux ${v[0]}.${v[1]} is too old; aiur needs >= ${MIN_TMUX.join(".")}. ${tmuxHint()}`
|
|
159
|
+
: `tmux is required but was not found on PATH. ${tmuxHint()}`,
|
|
91
160
|
);
|
|
92
161
|
}
|
|
93
162
|
}
|
|
94
163
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
164
|
+
// True only when the pinned opencode is on PATH; a wrong version (e.g. 1.17.x,
|
|
165
|
+
// which broke the panes) must be replaced, so presence alone isn't enough.
|
|
166
|
+
function opencodeMatchesPin() {
|
|
167
|
+
const result = safeSpawn("opencode", ["--version"], { encoding: "utf8" });
|
|
168
|
+
return !result.error && result.status === 0 && (result.stdout || "").trim() === opencodeVersion();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Install the pinned opencode globally. Forces online so it never inherits an
|
|
172
|
+
// --offline / prefer-offline context (CI, mirrors, air-gapped) that would fail
|
|
173
|
+
// on a cold cache — mirrors scripts/postinstall.mjs:installOpencode.
|
|
174
|
+
function installOpencode() {
|
|
175
|
+
const env = { ...process.env };
|
|
176
|
+
delete env.npm_config_offline;
|
|
177
|
+
delete env.npm_config_prefer_offline;
|
|
178
|
+
const result = safeSpawn(
|
|
179
|
+
"npm",
|
|
180
|
+
["install", "-g", `opencode-ai@${opencodeVersion()}`, "--no-offline", "--no-prefer-offline"],
|
|
181
|
+
{ stdio: "inherit", env },
|
|
182
|
+
);
|
|
183
|
+
return !result.error && result.status === 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// opencode powers the interactive "take the wheel" panes. Non-fatal: everything
|
|
187
|
+
// else works without it.
|
|
188
|
+
function ensureOpencode() {
|
|
189
|
+
if (opencodeMatchesPin()) return;
|
|
190
|
+
if (process.env.AIUR_SKIP_OPENCODE_INSTALL !== "1") {
|
|
98
191
|
process.stderr.write(
|
|
99
|
-
|
|
100
|
-
"feature needs it; everything else works without it. " +
|
|
101
|
-
"Install from https://opencode.ai\n",
|
|
192
|
+
`aiur: provisioning opencode@${opencodeVersion()} (one-time, for the interactive 'take the wheel' panes)…\n`,
|
|
102
193
|
);
|
|
194
|
+
installOpencode();
|
|
195
|
+
if (opencodeMatchesPin()) return;
|
|
103
196
|
}
|
|
197
|
+
process.stderr.write(
|
|
198
|
+
"aiur: opencode was not found on PATH. The interactive 'take the wheel' " +
|
|
199
|
+
"feature needs it; everything else works without it. " +
|
|
200
|
+
"Install from https://opencode.ai\n",
|
|
201
|
+
);
|
|
104
202
|
}
|
|
105
203
|
|
|
106
204
|
// `init` and `--version` run as foreground one-shots that never start the
|
|
107
|
-
// tmux-backed UI, so their tmux/opencode
|
|
108
|
-
// legitimately be absent on a machine that only ever runs `aiur init`.
|
|
205
|
+
// tmux-backed UI, so their tmux/opencode provisioning is irrelevant — and tmux
|
|
206
|
+
// may legitimately be absent on a machine that only ever runs `aiur init`.
|
|
109
207
|
function isForegroundOneShot(argv) {
|
|
110
208
|
for (const arg of argv) {
|
|
111
209
|
if (arg === "--version") return true;
|
|
@@ -119,8 +217,8 @@ function main() {
|
|
|
119
217
|
const releaseDir = resolveReleaseDir();
|
|
120
218
|
|
|
121
219
|
if (!isForegroundOneShot(process.argv.slice(2))) {
|
|
122
|
-
|
|
123
|
-
|
|
220
|
+
ensureTmux();
|
|
221
|
+
ensureOpencode();
|
|
124
222
|
}
|
|
125
223
|
|
|
126
224
|
const pkgRoot = path.resolve(__dirname, "..");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiur-cli",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "AI Unit Runtime for Executors
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "AI Unit Runtime for Executors.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/its-everdred/aiur",
|
|
7
7
|
"repository": {
|
|
@@ -12,20 +12,23 @@
|
|
|
12
12
|
"aiur": "bin/aiur.js"
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
15
16
|
"test": "bun test"
|
|
16
17
|
},
|
|
17
18
|
"files": [
|
|
18
19
|
"bin/aiur.js",
|
|
19
20
|
"libexec/aiur-engine.sh",
|
|
21
|
+
"scripts/postinstall.mjs",
|
|
20
22
|
"share/aiur.tmux.conf"
|
|
21
23
|
],
|
|
22
24
|
"engines": {
|
|
23
25
|
"node": ">=18"
|
|
24
26
|
},
|
|
27
|
+
"opencodeVersion": "1.15.6",
|
|
25
28
|
"optionalDependencies": {
|
|
26
|
-
"aiur-cli-linux-x64": "0.0.
|
|
27
|
-
"aiur-cli-linux-arm64": "0.0.
|
|
28
|
-
"aiur-cli-darwin-x64": "0.0.
|
|
29
|
-
"aiur-cli-darwin-arm64": "0.0.
|
|
29
|
+
"aiur-cli-linux-x64": "0.0.2",
|
|
30
|
+
"aiur-cli-linux-arm64": "0.0.2",
|
|
31
|
+
"aiur-cli-darwin-x64": "0.0.2",
|
|
32
|
+
"aiur-cli-darwin-arm64": "0.0.2"
|
|
30
33
|
}
|
|
31
34
|
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Provisions the peer tools every aiur user needs at runtime, so that a single
|
|
3
|
+
// global `npm install -g aiur-cli` yields a working install with nothing left
|
|
4
|
+
// to install by hand:
|
|
5
|
+
//
|
|
6
|
+
// - tmux the multiplexer the interactive TUI runs inside. aiur refuses
|
|
7
|
+
// to launch without tmux >= MIN_TMUX, so it is a hard runtime
|
|
8
|
+
// requirement (not merely nice-to-have).
|
|
9
|
+
// - opencode the peer CLI behind the interactive "take the wheel" panes.
|
|
10
|
+
//
|
|
11
|
+
// A global aiur-cli install does not link a dependency's bin onto PATH, and
|
|
12
|
+
// tmux is a system package rather than an npm one, so we provision both here.
|
|
13
|
+
// Every step is idempotent (skips when already satisfied) and non-fatal (a
|
|
14
|
+
// failure prints a hint but never breaks the aiur install).
|
|
15
|
+
import { spawnSync } from "node:child_process";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// opencode
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
// Pinned opencode version, read from package.json `opencodeVersion` (single
|
|
25
|
+
// source of truth — keep in sync with mise.toml). aiur's opencode pane
|
|
26
|
+
// integration is validated against this version; 1.17.x broke custom-provider
|
|
27
|
+
// model resolution (ProviderModelNotFoundError), blanking the chat panes.
|
|
28
|
+
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
29
|
+
export const OPENCODE_PACKAGE = `opencode-ai@${pkg.opencodeVersion}`;
|
|
30
|
+
|
|
31
|
+
// True only when the *pinned* opencode version is on PATH. A wrong (e.g.
|
|
32
|
+
// newer) version must be reinstalled — leaving 1.17.x in place keeps the
|
|
33
|
+
// panes broken — so a mere presence check isn't enough.
|
|
34
|
+
export function opencodeMatchesPin() {
|
|
35
|
+
const result = spawnSync("opencode", ["--version"], { encoding: "utf8" });
|
|
36
|
+
return !result.error && result.status === 0 && result.stdout.trim() === pkg.opencodeVersion;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Build the nested-install command. Provisioning fetches opencode from the
|
|
40
|
+
// registry, so it must NOT inherit an offline context from the outer
|
|
41
|
+
// `npm install` that triggered this postinstall — CI's `npm install -g
|
|
42
|
+
// --offline`, a `prefer-offline` config, or an air-gapped box. npm hands those
|
|
43
|
+
// to lifecycle scripts as npm_config_* env vars; a naive nested install
|
|
44
|
+
// inherits them and dies with ENOTCACHED on a cold cache, silently leaving
|
|
45
|
+
// opencode unprovisioned. We force online two ways (CLI flags outrank env, and
|
|
46
|
+
// stripping the env vars is belt-and-suspenders) so a plain `npm install -g
|
|
47
|
+
// aiur-cli` provisions opencode regardless of how the parent was invoked.
|
|
48
|
+
export function opencodeInstallSpec(env = process.env) {
|
|
49
|
+
const childEnv = { ...env };
|
|
50
|
+
delete childEnv.npm_config_offline;
|
|
51
|
+
delete childEnv.npm_config_prefer_offline;
|
|
52
|
+
return {
|
|
53
|
+
command: "npm",
|
|
54
|
+
args: ["install", "-g", OPENCODE_PACKAGE, "--no-offline", "--no-prefer-offline"],
|
|
55
|
+
env: childEnv,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function installOpencode() {
|
|
60
|
+
const spec = opencodeInstallSpec();
|
|
61
|
+
const result = spawnSync(spec.command, spec.args, { stdio: "inherit", env: spec.env });
|
|
62
|
+
return !result.error && result.status === 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function provisionOpencode({
|
|
66
|
+
env = process.env,
|
|
67
|
+
isPresent = opencodeMatchesPin,
|
|
68
|
+
install = installOpencode,
|
|
69
|
+
log = (message) => process.stderr.write(message + "\n"),
|
|
70
|
+
} = {}) {
|
|
71
|
+
if (env.AIUR_SKIP_OPENCODE_INSTALL === "1") return "skipped:disabled";
|
|
72
|
+
if (isPresent()) return "skipped:present";
|
|
73
|
+
|
|
74
|
+
log(`aiur: installing pinned opencode (${OPENCODE_PACKAGE}) for the interactive 'take the wheel' feature…`);
|
|
75
|
+
|
|
76
|
+
if (install()) return "installed";
|
|
77
|
+
|
|
78
|
+
log(
|
|
79
|
+
"aiur: couldn't install opencode automatically. Install it manually: " +
|
|
80
|
+
"npm install -g opencode-ai (see https://opencode.ai)",
|
|
81
|
+
);
|
|
82
|
+
return "failed";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// tmux
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
// Minimum tmux the TUI needs. Keep in sync with the runtime preflight in
|
|
90
|
+
// bin/aiur.js — that preflight stays as a safety net, this just front-loads
|
|
91
|
+
// the install so the first `aiur` run isn't a dead end.
|
|
92
|
+
export const MIN_TMUX = [3, 3];
|
|
93
|
+
|
|
94
|
+
function tmuxHint(platform = process.platform) {
|
|
95
|
+
return platform === "darwin"
|
|
96
|
+
? "install it with: brew install tmux"
|
|
97
|
+
: "install it with: apt install tmux (or your distro's package manager)";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// True only when a new-enough tmux is on PATH. The TUI won't launch with an
|
|
101
|
+
// older tmux, so — as with opencode's pin — presence alone isn't enough.
|
|
102
|
+
export function tmuxSatisfiesMin(run = (cmd, argv) => spawnSync(cmd, argv, { encoding: "utf8" })) {
|
|
103
|
+
const result = run("tmux", ["-V"]);
|
|
104
|
+
if (result.error || result.status !== 0) return false;
|
|
105
|
+
const match = /tmux\s+(\d+)\.(\d+)/.exec(result.stdout || "");
|
|
106
|
+
if (!match) return false;
|
|
107
|
+
const major = Number(match[1]);
|
|
108
|
+
const minor = Number(match[2]);
|
|
109
|
+
const [needMajor, needMinor] = MIN_TMUX;
|
|
110
|
+
return major > needMajor || (major === needMajor && minor >= needMinor);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Best-effort system install of tmux. macOS uses Homebrew; Linux uses the
|
|
114
|
+
// first package manager that is present. Returns false (so the caller can
|
|
115
|
+
// print a manual hint) when no usable installer exists or the install fails —
|
|
116
|
+
// including the common non-root case where a Linux package manager can't
|
|
117
|
+
// proceed. We never invoke sudo: a hidden password prompt during `npm install`
|
|
118
|
+
// would hang the install.
|
|
119
|
+
export function installTmux(platform = process.platform) {
|
|
120
|
+
const has = (bin) => !spawnSync(bin, ["--version"], { stdio: "ignore" }).error;
|
|
121
|
+
|
|
122
|
+
if (platform === "darwin") {
|
|
123
|
+
if (!has("brew")) return false;
|
|
124
|
+
return spawnSync("brew", ["install", "tmux"], { stdio: "inherit" }).status === 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (platform === "linux") {
|
|
128
|
+
const managers = [
|
|
129
|
+
["apt-get", ["install", "-y", "tmux"]],
|
|
130
|
+
["dnf", ["install", "-y", "tmux"]],
|
|
131
|
+
["apk", ["add", "tmux"]],
|
|
132
|
+
["pacman", ["-S", "--noconfirm", "tmux"]],
|
|
133
|
+
];
|
|
134
|
+
for (const [bin, argv] of managers) {
|
|
135
|
+
if (!has(bin)) continue;
|
|
136
|
+
return spawnSync(bin, argv, { stdio: "inherit" }).status === 0;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function provisionTmux({
|
|
144
|
+
env = process.env,
|
|
145
|
+
isPresent = tmuxSatisfiesMin,
|
|
146
|
+
install = installTmux,
|
|
147
|
+
platform = process.platform,
|
|
148
|
+
log = (message) => process.stderr.write(message + "\n"),
|
|
149
|
+
} = {}) {
|
|
150
|
+
if (env.AIUR_SKIP_TMUX_INSTALL === "1") return "skipped:disabled";
|
|
151
|
+
if (isPresent()) return "skipped:present";
|
|
152
|
+
|
|
153
|
+
log(
|
|
154
|
+
`aiur: tmux (>= ${MIN_TMUX.join(".")}) not found; aiur's interactive TUI runs inside tmux. Installing…`,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
if (install(platform)) return "installed";
|
|
158
|
+
|
|
159
|
+
log(`aiur: couldn't install tmux automatically. ${tmuxHint(platform)}`);
|
|
160
|
+
return "failed";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Entry point
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
// Run only when invoked directly as the postinstall step, not when a test
|
|
168
|
+
// imports the helpers above. tmux is provisioned first — it is the hard
|
|
169
|
+
// requirement — then opencode for the optional panes.
|
|
170
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
171
|
+
provisionTmux();
|
|
172
|
+
provisionOpencode();
|
|
173
|
+
}
|