rigline 1.0.0-alpha.0 → 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 CHANGED
@@ -1,18 +1,29 @@
1
1
  # rigline
2
2
 
3
- The command-line surface for [Rigline](https://github.com/Rigline/Rigline), a plugin layer for the
4
- Claude Code VS Code extension.
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 update move each npm plugin to what its tag resolves to
34
- rigline list every plugin, in load order: origin, source, switch, what it can do
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
- `add` never runs a package manager: a published plugin is one bundled ES module and a manifest, so
43
- there is nothing to resolve. A version must reach a minimum age (a day, by default) before `add` or
44
- `update` will take it, and a withheld version is named rather than skipped in silence.
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
@@ -0,0 +1,4 @@
1
+ export class UserError extends Error {
2
+ name = "UserError";
3
+ }
4
+ //# sourceMappingURL=errors.js.map