rigline 1.0.0-alpha.1 → 1.0.0-alpha.10
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 +58 -7
- package/dist/engine.js +241 -0
- package/dist/errors.js +4 -0
- package/dist/index.js +53 -464
- package/dist/lock.js +88 -0
- package/dist/registry.js +134 -0
- package/dist/remote.js +132 -0
- package/dist/tarball.js +125 -0
- package/package.json +5 -6
- package/dist/build.js +0 -25
package/README.md
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
# rigline
|
|
2
2
|
|
|
3
|
-
The command
|
|
4
|
-
|
|
3
|
+
The command for [Rigline](https://github.com/Rigline/Rigline), a plugin layer for the Claude Code
|
|
4
|
+
VS Code extension.
|
|
5
5
|
|
|
6
6
|
Rigline injects a small loader into the installed extension's webview, gives plugins a
|
|
7
7
|
capability-scoped context to write against, and harvests the identifiers those plugins depend on
|
|
8
8
|
from whichever extension version you have installed — so an extension update is a diff to read
|
|
9
9
|
rather than a breakage to chase.
|
|
10
10
|
|
|
11
|
+
**This modifies files belonging to Anthropic's extension, on your machine, and that is your call to
|
|
12
|
+
make.** [Anthropic compliance](https://github.com/Rigline/Rigline/blob/main/docs/anthropic-compliance.md)
|
|
13
|
+
is a straight account of what Rigline does and does not do — no credentials, no network calls, no
|
|
14
|
+
rerouting of Claude usage, no redistribution — and how we read Anthropic's terms. `rigline restore`
|
|
15
|
+
puts every install back to Anthropic's own bytes and needs nothing but Node.
|
|
16
|
+
|
|
11
17
|
## Install
|
|
12
18
|
|
|
13
19
|
npm install -g rigline
|
|
14
20
|
rigline install
|
|
15
21
|
|
|
22
|
+
The first command you run fetches the engine, [`@rigline/core`](https://www.npmjs.com/package/@rigline/core),
|
|
23
|
+
into `~/.rigline/engine` and runs it from there. It takes a few seconds and needs the registry;
|
|
24
|
+
after that nothing reaches the network unless you ask it to. If that directory is ever in a bad
|
|
25
|
+
state, delete it and run any command again.
|
|
26
|
+
|
|
16
27
|
`install` finds every installed version of the extension, harvests it, bakes the discovered plugins
|
|
17
28
|
into a payload and injects the loader. It keeps a byte-faithful backup of each bundle it touches,
|
|
18
29
|
and `rigline restore` puts everything back using nothing but Node and the backup.
|
|
@@ -21,6 +32,25 @@ The extension updates about weekly and an update installs a fresh directory, whi
|
|
|
21
32
|
the injection. Run `install` again afterwards; a window that is already open keeps running the old
|
|
22
33
|
directory until *Developer: Reload Window*.
|
|
23
34
|
|
|
35
|
+
**That is the whole of Rigline, and it needs nothing else.** Everything below is optional.
|
|
36
|
+
|
|
37
|
+
## Optional: let an extension do the remembering
|
|
38
|
+
|
|
39
|
+
rigline vscode-setup
|
|
40
|
+
|
|
41
|
+
If running `install` after every update is a chore, this installs a companion extension that does it
|
|
42
|
+
for you — into every VS Code found on your `PATH`, Insiders, VSCodium, Cursor and Windsurf included,
|
|
43
|
+
from a VSIX that ships inside the engine. Nothing is downloaded, and the companion moves when the
|
|
44
|
+
engine does. It injects as it goes, so it is a step *instead of* `install` rather than after it.
|
|
45
|
+
|
|
46
|
+
From then on it watches for the extension update and re-injects behind it, so there is nothing to
|
|
47
|
+
remember after one.
|
|
48
|
+
|
|
49
|
+
**Decline it and lose nothing.** `install` remains complete on its own, and is the right answer if
|
|
50
|
+
you would rather not add an extension, cannot install one, or simply prefer running the thing
|
|
51
|
+
yourself. `rigline vscode-setup --remove` goes back to that — it takes the companion out and leaves
|
|
52
|
+
your injection alone, and `rigline restore` is what undoes the injection itself.
|
|
53
|
+
|
|
24
54
|
## Commands
|
|
25
55
|
|
|
26
56
|
rigline install inject every version, bake plugins, report drift, record the baseline
|
|
@@ -30,8 +60,10 @@ directory until *Developer: Reload Window*.
|
|
|
30
60
|
|
|
31
61
|
rigline add SPEC install a plugin from a directory or npm, and re-inject
|
|
32
62
|
rigline remove N delete a plugin rigline installed, and re-inject
|
|
33
|
-
rigline
|
|
34
|
-
rigline
|
|
63
|
+
rigline disable N switch a plugin off, and re-inject
|
|
64
|
+
rigline enable N switch it back on, and re-inject
|
|
65
|
+
rigline update move the engine and every npm plugin to what its tag resolves to
|
|
66
|
+
rigline list every plugin, in load order: version, origin, source, switch, what it can do
|
|
35
67
|
|
|
36
68
|
rigline build build a plugin to one browser ES module
|
|
37
69
|
rigline dev rebuild and re-inject as you edit
|
|
@@ -39,9 +71,25 @@ directory until *Developer: Reload Window*.
|
|
|
39
71
|
rigline diff A B identifier drift between two extension directories
|
|
40
72
|
rigline doctor install state per version, as a pasteable report
|
|
41
73
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
74
|
+
rigline --version this command's version and the installed engine's
|
|
75
|
+
rigline --help the whole surface, from the engine
|
|
76
|
+
|
|
77
|
+
`add` never runs a package manager for a plugin: a published plugin is one bundled ES module and a
|
|
78
|
+
manifest, so there is nothing to resolve. A version must reach a minimum age (a day, by default)
|
|
79
|
+
before `add` or `update` will take it, and a withheld version is named rather than skipped in
|
|
80
|
+
silence. `update` applies the same rule to the engine, and says which version it moved from so
|
|
81
|
+
going back is one command.
|
|
82
|
+
|
|
83
|
+
## How it is put together
|
|
84
|
+
|
|
85
|
+
`rigline` is a small retrieval layer. It fetches bytes — a plugin tarball, the engine — checks them,
|
|
86
|
+
and hands them over; `@rigline/core` does everything else and answers every verb above. The split is
|
|
87
|
+
not decoration: a process cannot replace the package it is running out of, so whatever performs an
|
|
88
|
+
update has to sit above the thing being updated.
|
|
89
|
+
|
|
90
|
+
**It belongs in no project's dependencies.** If you are writing plugins, your workspace declares
|
|
91
|
+
`@rigline/core` and runs `rigline-engine`; `npm create rigline-plugin` scaffolds it that way. A
|
|
92
|
+
project that can declare dependencies does not need a delivery mechanism.
|
|
45
93
|
|
|
46
94
|
## Writing a plugin
|
|
47
95
|
|
|
@@ -51,4 +99,7 @@ there is nothing to resolve. A version must reach a minimum age (a day, by defau
|
|
|
51
99
|
form. [Anchors](https://github.com/Rigline/Rigline/blob/main/docs/anchors.md) is what to read when
|
|
52
100
|
an extension update breaks a plugin and you would rather not wait for a release.
|
|
53
101
|
|
|
102
|
+
[Changelog](https://github.com/Rigline/Rigline/blob/main/CHANGELOG.md) — every package in this
|
|
103
|
+
workspace shares it, and one version number.
|
|
104
|
+
|
|
54
105
|
MIT.
|
package/dist/engine.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { UserError } from "./errors.js";
|
|
7
|
+
import { withHomeLock } from "./lock.js";
|
|
8
|
+
import { releaseAgeProblem, resolveVersion } from "./registry.js";
|
|
9
|
+
export const ENGINE_PACKAGE = "@rigline/core";
|
|
10
|
+
export const ENGINE_BIN = "rigline-engine";
|
|
11
|
+
export function riglineHome(env = process.env) {
|
|
12
|
+
const override = env.RIGLINE_HOME;
|
|
13
|
+
return override && override.length > 0 ? override : join(homedir(), ".rigline");
|
|
14
|
+
}
|
|
15
|
+
export function engineDir(home = riglineHome()) {
|
|
16
|
+
return join(home, "engine");
|
|
17
|
+
}
|
|
18
|
+
export function wrapperVersion() {
|
|
19
|
+
const manifest = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
20
|
+
const parsed = JSON.parse(readFileSync(manifest, "utf8"));
|
|
21
|
+
const version = parsed.version;
|
|
22
|
+
if (typeof version !== "string")
|
|
23
|
+
throw new Error(`${manifest} has no version`);
|
|
24
|
+
return version;
|
|
25
|
+
}
|
|
26
|
+
export function majorOf(version) {
|
|
27
|
+
const match = /^(\d+)\./.exec(version);
|
|
28
|
+
return match === null ? null : Number(match[1]);
|
|
29
|
+
}
|
|
30
|
+
export function readEngineState(prefix) {
|
|
31
|
+
const dir = join(prefix, "node_modules", ENGINE_PACKAGE);
|
|
32
|
+
const manifest = join(dir, "package.json");
|
|
33
|
+
if (!existsSync(manifest))
|
|
34
|
+
return { kind: "none" };
|
|
35
|
+
let parsed;
|
|
36
|
+
try {
|
|
37
|
+
parsed = JSON.parse(readFileSync(manifest, "utf8"));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return { kind: "unusable", version: "unreadable", why: `${manifest} is not readable JSON` };
|
|
41
|
+
}
|
|
42
|
+
const version = typeof parsed.version === "string" ? parsed.version : "unreadable";
|
|
43
|
+
const bin = parsed.bin;
|
|
44
|
+
const named = typeof bin === "object" && bin !== null
|
|
45
|
+
? bin[ENGINE_BIN]
|
|
46
|
+
: undefined;
|
|
47
|
+
if (typeof named !== "string") {
|
|
48
|
+
return { kind: "unusable", version, why: `it has no ${ENGINE_BIN} command` };
|
|
49
|
+
}
|
|
50
|
+
const entry = resolve(dir, named);
|
|
51
|
+
if (!existsSync(entry)) {
|
|
52
|
+
return { kind: "unusable", version, why: `its ${ENGINE_BIN} entry ${entry} is missing` };
|
|
53
|
+
}
|
|
54
|
+
return { kind: "ready", version, entry };
|
|
55
|
+
}
|
|
56
|
+
export function majorProblem(wrapper, engine) {
|
|
57
|
+
const ours = majorOf(wrapper);
|
|
58
|
+
const theirs = majorOf(engine);
|
|
59
|
+
if (ours === null || theirs === null || ours === theirs)
|
|
60
|
+
return null;
|
|
61
|
+
return (`rigline ${ours}.x cannot run engine ${theirs}.x (${ENGINE_PACKAGE} ${engine}). ` +
|
|
62
|
+
"Run npm i -g rigline@latest.");
|
|
63
|
+
}
|
|
64
|
+
export function findNpmCli(execPath = process.execPath, exists = existsSync) {
|
|
65
|
+
const here = dirname(execPath);
|
|
66
|
+
const candidates = [
|
|
67
|
+
join(here, "node_modules", "npm", "bin", "npm-cli.js"),
|
|
68
|
+
join(here, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js"),
|
|
69
|
+
];
|
|
70
|
+
const found = candidates.find(exists);
|
|
71
|
+
if (found === undefined) {
|
|
72
|
+
throw new UserError(`no npm was found beside ${execPath}, so the engine cannot be installed. ` +
|
|
73
|
+
`Run: npm install --prefix ${engineDir()} --save-exact --ignore-scripts ${ENGINE_PACKAGE}@latest`);
|
|
74
|
+
}
|
|
75
|
+
return found;
|
|
76
|
+
}
|
|
77
|
+
export function engineInstallArgv(options) {
|
|
78
|
+
return [
|
|
79
|
+
options.npmCli,
|
|
80
|
+
"install",
|
|
81
|
+
"--prefix",
|
|
82
|
+
options.prefix,
|
|
83
|
+
"--save-exact",
|
|
84
|
+
"--ignore-scripts",
|
|
85
|
+
"--no-audit",
|
|
86
|
+
"--no-fund",
|
|
87
|
+
"--loglevel=error",
|
|
88
|
+
...options.specs,
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
const nodeSpawn = (command, args, options) => spawn(command, [...args], { stdio: [...options.stdio] });
|
|
92
|
+
export async function ensureEngine(options = {}) {
|
|
93
|
+
const prefix = engineDir(options.home ?? riglineHome());
|
|
94
|
+
const version = options.version ?? wrapperVersion();
|
|
95
|
+
const state = readEngineState(prefix);
|
|
96
|
+
if (state.kind === "ready") {
|
|
97
|
+
const problem = majorProblem(version, state.version);
|
|
98
|
+
if (problem !== null)
|
|
99
|
+
throw new UserError(problem);
|
|
100
|
+
return engineAt(state, options);
|
|
101
|
+
}
|
|
102
|
+
const resolved = await resolveEngine(options);
|
|
103
|
+
if (state.kind === "unusable" && resolved.version === state.version) {
|
|
104
|
+
throw new UserError(`the engine in ${prefix} is ${ENGINE_PACKAGE} ${state.version} and ${state.why}, ` +
|
|
105
|
+
`and that is what ${ENGINE_PACKAGE}@${options.tag ?? "latest"} resolves to. ` +
|
|
106
|
+
`Delete ${prefix} and try again, or install a version that has one.`);
|
|
107
|
+
}
|
|
108
|
+
const problem = majorProblem(version, resolved.version);
|
|
109
|
+
if (problem !== null)
|
|
110
|
+
throw new UserError(problem);
|
|
111
|
+
console.log(`rigline: installing the engine, ${ENGINE_PACKAGE} ${resolved.version}`);
|
|
112
|
+
await installEngine(prefix, `${ENGINE_PACKAGE}@${resolved.version}`, options);
|
|
113
|
+
const installed = readEngineState(prefix);
|
|
114
|
+
if (installed.kind !== "ready") {
|
|
115
|
+
throw new UserError(`${ENGINE_PACKAGE} ${resolved.version} was installed into ${prefix}, but ` +
|
|
116
|
+
(installed.kind === "unusable" ? installed.why : "nothing is there"));
|
|
117
|
+
}
|
|
118
|
+
return engineAt(installed, options);
|
|
119
|
+
}
|
|
120
|
+
async function resolveEngine(options) {
|
|
121
|
+
return await resolveVersion({ name: ENGINE_PACKAGE, version: null, tag: options.tag ?? "latest" }, options.registry);
|
|
122
|
+
}
|
|
123
|
+
async function installEngine(prefix, spec, options) {
|
|
124
|
+
const home = options.home ?? riglineHome();
|
|
125
|
+
await withHomeLock({ home, what: options.label ?? "rigline", ...options.lock }, async () => {
|
|
126
|
+
mkdirSync(prefix, { recursive: true });
|
|
127
|
+
const node = options.nodePath ?? process.execPath;
|
|
128
|
+
const argv = engineInstallArgv({ npmCli: findNpmCli(node), prefix, specs: [spec] });
|
|
129
|
+
const run = await capture(node, argv, options.spawnImpl);
|
|
130
|
+
if (run.code !== 0) {
|
|
131
|
+
throw new UserError(`installing ${spec} into ${prefix} failed (npm exited ${run.code}).\n${run.output.trim()}`);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function engineAt(state, options) {
|
|
136
|
+
const spawnImpl = options.spawnImpl ?? nodeSpawn;
|
|
137
|
+
const node = options.nodePath ?? process.execPath;
|
|
138
|
+
return {
|
|
139
|
+
version: state.version,
|
|
140
|
+
entry: state.entry,
|
|
141
|
+
run: (argv) => new Promise((done, fail) => {
|
|
142
|
+
const child = spawnImpl(node, [state.entry, ...argv], {
|
|
143
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
144
|
+
});
|
|
145
|
+
child.on("error", fail);
|
|
146
|
+
child.on("close", (code) => done(code ?? 1));
|
|
147
|
+
}),
|
|
148
|
+
json: async (argv) => {
|
|
149
|
+
const run = await capture(node, [state.entry, ...argv], spawnImpl, "inherit");
|
|
150
|
+
if (run.code !== 0) {
|
|
151
|
+
throw new UserError(`the engine exited ${run.code} for \`${argv.join(" ")}\``);
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
return JSON.parse(run.output);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
throw new UserError(`the engine answered \`${argv.join(" ")}\` with something that is not JSON`);
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async function capture(command, argv, spawnImpl = nodeSpawn, stderr = "pipe") {
|
|
163
|
+
return await new Promise((done, fail) => {
|
|
164
|
+
const child = spawnImpl(command, argv, { stdio: ["ignore", "pipe", stderr] });
|
|
165
|
+
let output = "";
|
|
166
|
+
child.stdout?.setEncoding("utf8");
|
|
167
|
+
child.stdout?.on("data", (chunk) => {
|
|
168
|
+
output += chunk;
|
|
169
|
+
});
|
|
170
|
+
child.stderr?.setEncoding("utf8");
|
|
171
|
+
child.stderr?.on("data", (chunk) => {
|
|
172
|
+
output += chunk;
|
|
173
|
+
});
|
|
174
|
+
child.on("error", fail);
|
|
175
|
+
child.on("close", (code) => done({ code: code ?? 1, output }));
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
export async function updateEngine(options = {}) {
|
|
179
|
+
const prefix = engineDir(options.home ?? riglineHome());
|
|
180
|
+
const state = readEngineState(prefix);
|
|
181
|
+
const installed = state.kind === "none" ? null : state.version;
|
|
182
|
+
let resolved;
|
|
183
|
+
try {
|
|
184
|
+
resolved = await resolveEngine(options);
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
return {
|
|
188
|
+
outcome: "failed",
|
|
189
|
+
...(installed === null ? {} : { from: installed }),
|
|
190
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (state.kind === "ready" && resolved.version === state.version) {
|
|
194
|
+
return { outcome: "current", from: state.version };
|
|
195
|
+
}
|
|
196
|
+
const problem = majorProblem(options.version ?? wrapperVersion(), resolved.version);
|
|
197
|
+
if (problem !== null) {
|
|
198
|
+
return {
|
|
199
|
+
outcome: "failed",
|
|
200
|
+
...(installed === null ? {} : { from: installed }),
|
|
201
|
+
reason: problem,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (installed !== null) {
|
|
205
|
+
const withheld = releaseAgeProblem(resolved, options.registry);
|
|
206
|
+
if (withheld !== null) {
|
|
207
|
+
return { outcome: "withheld", from: installed, to: resolved.version, reason: withheld };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
await installEngine(prefix, `${ENGINE_PACKAGE}@${resolved.version}`, options);
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
return {
|
|
215
|
+
outcome: "failed",
|
|
216
|
+
...(installed === null ? {} : { from: installed }),
|
|
217
|
+
to: resolved.version,
|
|
218
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return installed === null
|
|
222
|
+
? { outcome: "installed", to: resolved.version }
|
|
223
|
+
: { outcome: "moved", from: installed, to: resolved.version };
|
|
224
|
+
}
|
|
225
|
+
export function formatEngineUpdate(update, home = riglineHome()) {
|
|
226
|
+
switch (update.outcome) {
|
|
227
|
+
case "installed":
|
|
228
|
+
return `engine: installed ${update.to}`;
|
|
229
|
+
case "moved":
|
|
230
|
+
return (`engine: ${update.from} -> ${update.to}\n` +
|
|
231
|
+
` to go back: npm install --prefix ${engineDir(home)} --save-exact ` +
|
|
232
|
+
`${ENGINE_PACKAGE}@${update.from}`);
|
|
233
|
+
case "current":
|
|
234
|
+
return `engine: ${update.from}, which is what its tag resolves to`;
|
|
235
|
+
case "withheld":
|
|
236
|
+
return `engine: staying on ${update.from} — ${update.reason}`;
|
|
237
|
+
default:
|
|
238
|
+
return `engine: FAILED — ${update.reason}`;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=engine.js.map
|
package/dist/errors.js
ADDED