litopencode 0.0.1 → 0.0.3
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 +2 -2
- package/dist/cli/install.js +98 -22
- package/dist/cli/types.d.ts +7 -3
- package/dist/cli.js +28 -0
- package/dist/features.js +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/skills.js +1 -1
- package/docs/migration.md +1 -1
- package/docs/release-checklist.md +3 -3
- package/package.json +9 -2
- package/skills/doctor-installer/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
npx litopencode install
|
|
36
36
|
|
|
37
|
-
Then restart OpenCode. The agent switcher should show <code>lit-plan</code> and <code>lit-loop</code>.
|
|
37
|
+
The installer delegates the default install path to OpenCode's own plugin installer and registers a version-pinned entry such as <code>litopencode@0.0.3</code>. Then restart OpenCode. The agent switcher should show <code>lit-plan</code> and <code>lit-loop</code>.
|
|
38
38
|
|
|
39
39
|
For a preview without writing <code>opencode.json</code>:
|
|
40
40
|
|
|
@@ -72,7 +72,7 @@ By default, the installer targets <code>~/.config/opencode/opencode.json</code>,
|
|
|
72
72
|
|
|
73
73
|
## OpenCode Plugin
|
|
74
74
|
|
|
75
|
-
The default export is an OpenCode <code>
|
|
75
|
+
The default export is an OpenCode plugin function, and <code>litopencode/server</code> points at the same compiled server entrypoint for OpenCode's plugin installer. The packed artifact exports compiled JavaScript from <code>dist/index.js</code>, so installed npm consumers do not rely on Node's TypeScript stripping behavior for files under <code>node_modules</code>.
|
|
76
76
|
|
|
77
77
|
The server surface registers:
|
|
78
78
|
|
package/dist/cli/install.js
CHANGED
|
@@ -1,12 +1,63 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
2
4
|
import path from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
3
6
|
import { createRuntimePaths } from "../state.js";
|
|
4
7
|
import { readJsonObjectIfPresent, readPackageMetadata } from "./json.js";
|
|
5
|
-
const
|
|
6
|
-
|
|
8
|
+
const pluginName = "litopencode";
|
|
9
|
+
const reset = "\u001b[0m";
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
function useColor() {
|
|
12
|
+
return process.stdout.isTTY === true && process.env.NO_COLOR === undefined;
|
|
13
|
+
}
|
|
14
|
+
function tint(value, code) {
|
|
15
|
+
if (!useColor())
|
|
16
|
+
return value;
|
|
17
|
+
return code + value + reset;
|
|
18
|
+
}
|
|
19
|
+
function padLabel(label) {
|
|
20
|
+
return label.padEnd(24, " ");
|
|
21
|
+
}
|
|
22
|
+
function headerLine(value, code) {
|
|
23
|
+
return "| " + tint(value.padEnd(58, " "), code) + " |";
|
|
24
|
+
}
|
|
25
|
+
function pluginSpec(metadata) {
|
|
26
|
+
return metadata.name + "@" + metadata.version;
|
|
27
|
+
}
|
|
28
|
+
function isLitOpenCodeEntry(value) {
|
|
29
|
+
return typeof value === "string" && (value === pluginName || value.startsWith(pluginName + "@"));
|
|
30
|
+
}
|
|
31
|
+
function defaultOpenCodeRoot() {
|
|
32
|
+
const configHome = process.env.XDG_CONFIG_HOME;
|
|
33
|
+
if (configHome && configHome.length > 0)
|
|
34
|
+
return path.join(configHome, "opencode");
|
|
35
|
+
return path.join(os.homedir(), ".config", "opencode");
|
|
36
|
+
}
|
|
37
|
+
function shouldUseOpenCodeInstaller(root) {
|
|
38
|
+
return path.resolve(root) === path.resolve(defaultOpenCodeRoot());
|
|
39
|
+
}
|
|
40
|
+
async function installWithOpenCode(target) {
|
|
41
|
+
try {
|
|
42
|
+
await execFileAsync("opencode", ["plugin", target, "--global", "--force"], {
|
|
43
|
+
timeout: 120_000,
|
|
44
|
+
maxBuffer: 1024 * 1024 * 4
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
const detail = error instanceof Error && "stderr" in error && typeof error.stderr === "string"
|
|
49
|
+
? error.stderr.trim()
|
|
50
|
+
: error instanceof Error
|
|
51
|
+
? error.message
|
|
52
|
+
: String(error);
|
|
53
|
+
throw new Error("OpenCode plugin install failed for " + target + (detail ? ":\n" + detail : ""));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function describePluginMutation(config, target) {
|
|
7
57
|
const pluginValue = config?.plugin;
|
|
8
58
|
const pluginIsArray = Array.isArray(pluginValue);
|
|
9
|
-
const
|
|
59
|
+
const existingIndex = pluginIsArray ? pluginValue.findIndex(isLitOpenCodeEntry) : -1;
|
|
60
|
+
const alreadyPresent = pluginIsArray && pluginValue.includes(target);
|
|
10
61
|
const currentCount = pluginIsArray ? pluginValue.length : 0;
|
|
11
62
|
if (alreadyPresent) {
|
|
12
63
|
return {
|
|
@@ -15,56 +66,78 @@ function describePluginMutation(config) {
|
|
|
15
66
|
patch: []
|
|
16
67
|
};
|
|
17
68
|
}
|
|
18
|
-
const add = [
|
|
69
|
+
const add = [target];
|
|
19
70
|
if (pluginIsArray) {
|
|
71
|
+
if (existingIndex >= 0) {
|
|
72
|
+
return {
|
|
73
|
+
changed: true,
|
|
74
|
+
plugin: { alreadyPresent: true, currentCount, resultCount: currentCount, add },
|
|
75
|
+
patch: [{ op: "replace", path: `/plugin/${existingIndex}`, value: target }]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
20
78
|
return {
|
|
21
79
|
changed: true,
|
|
22
80
|
plugin: { alreadyPresent: false, currentCount, resultCount: currentCount + 1, add },
|
|
23
|
-
patch: [{ op: "add", path: "/plugin/-", value:
|
|
81
|
+
patch: [{ op: "add", path: "/plugin/-", value: target }]
|
|
24
82
|
};
|
|
25
83
|
}
|
|
26
84
|
const op = config !== null && Object.hasOwn(config, "plugin") ? "replace" : "add";
|
|
27
85
|
return {
|
|
28
86
|
changed: true,
|
|
29
87
|
plugin: { alreadyPresent: false, currentCount, resultCount: 1, add },
|
|
30
|
-
patch: [{ op, path: "/plugin", value: [
|
|
88
|
+
patch: [{ op, path: "/plugin", value: [target] }]
|
|
31
89
|
};
|
|
32
90
|
}
|
|
33
|
-
function applyPluginMutation(config, mutation) {
|
|
91
|
+
function applyPluginMutation(config, mutation, target) {
|
|
34
92
|
const next = config === null ? { "$schema": "https://opencode.ai/config.json" } : { ...config };
|
|
35
93
|
if (!mutation.changed)
|
|
36
94
|
return next;
|
|
37
95
|
const pluginValue = next.plugin;
|
|
38
96
|
if (Array.isArray(pluginValue)) {
|
|
39
|
-
|
|
97
|
+
const existingIndex = pluginValue.findIndex(isLitOpenCodeEntry);
|
|
98
|
+
if (existingIndex >= 0) {
|
|
99
|
+
next.plugin = pluginValue.map((value, index) => (index === existingIndex ? target : value));
|
|
100
|
+
return next;
|
|
101
|
+
}
|
|
102
|
+
next.plugin = [...pluginValue, target];
|
|
40
103
|
return next;
|
|
41
104
|
}
|
|
42
|
-
next.plugin = [
|
|
105
|
+
next.plugin = [target];
|
|
43
106
|
return next;
|
|
44
107
|
}
|
|
45
108
|
function renderInstallReport(report) {
|
|
46
109
|
const status = report.changed ? "Complete" : "Already installed";
|
|
47
|
-
const action = report.changed ? "
|
|
110
|
+
const action = report.changed ? "plugin[] updated" : "no write needed";
|
|
111
|
+
const ok = tint("ok", "\u001b[38;5;82m");
|
|
48
112
|
return [
|
|
49
|
-
"
|
|
50
|
-
"
|
|
113
|
+
"+------------------------------------------------------------+",
|
|
114
|
+
headerLine("LitOpenCode", "\u001b[1;38;5;81m"),
|
|
115
|
+
headerLine("OpenCode plugin installer", "\u001b[38;5;245m"),
|
|
116
|
+
"+------------------------------------------------------------+",
|
|
117
|
+
"",
|
|
118
|
+
" " + padLabel("Package") + " " + report.package.name + "@" + report.package.version,
|
|
119
|
+
" " + padLabel("Config") + " " + report.path,
|
|
120
|
+
" " + padLabel("Plugin entries") + " " + report.plugin.currentCount + " -> " + report.plugin.resultCount,
|
|
51
121
|
"",
|
|
52
|
-
"[
|
|
53
|
-
"[
|
|
54
|
-
"[
|
|
55
|
-
"[/]
|
|
122
|
+
" [1/4] " + padLabel("Resolve package") + " " + ok,
|
|
123
|
+
" [2/4] " + padLabel("Read OpenCode config") + " " + ok,
|
|
124
|
+
" [3/4] " + padLabel("Register plugin") + " " + ok,
|
|
125
|
+
" [4/4] " + padLabel("Verify install") + " " + ok,
|
|
56
126
|
"",
|
|
57
|
-
status,
|
|
127
|
+
" Status " + status,
|
|
128
|
+
" Result " + action,
|
|
58
129
|
"",
|
|
59
|
-
"Next
|
|
60
|
-
" Restart OpenCode
|
|
130
|
+
" Next",
|
|
131
|
+
" Restart OpenCode, then press Tab.",
|
|
132
|
+
" Agents: lit-plan / lit-loop"
|
|
61
133
|
].join("\n");
|
|
62
134
|
}
|
|
63
135
|
export async function install(root, dryRun) {
|
|
64
136
|
const metadata = await readPackageMetadata();
|
|
65
137
|
const paths = createRuntimePaths(root);
|
|
66
138
|
const before = await readJsonObjectIfPresent(paths.opencodeConfigFile);
|
|
67
|
-
const
|
|
139
|
+
const target = pluginSpec(metadata);
|
|
140
|
+
const mutation = describePluginMutation(before, target);
|
|
68
141
|
const report = {
|
|
69
142
|
dryRun,
|
|
70
143
|
path: paths.opencodeConfigFile,
|
|
@@ -76,8 +149,11 @@ export async function install(root, dryRun) {
|
|
|
76
149
|
if (dryRun) {
|
|
77
150
|
return { exitCode: 0, stdout: JSON.stringify(report, null, 2) };
|
|
78
151
|
}
|
|
79
|
-
if (
|
|
80
|
-
|
|
152
|
+
if (shouldUseOpenCodeInstaller(root)) {
|
|
153
|
+
await installWithOpenCode(target);
|
|
154
|
+
}
|
|
155
|
+
else if (mutation.changed) {
|
|
156
|
+
const next = applyPluginMutation(before, mutation, target);
|
|
81
157
|
await fs.mkdir(path.dirname(paths.opencodeConfigFile), { recursive: true });
|
|
82
158
|
await fs.writeFile(paths.opencodeConfigFile, JSON.stringify(next, null, 2) + "\n");
|
|
83
159
|
}
|
package/dist/cli/types.d.ts
CHANGED
|
@@ -15,15 +15,19 @@ export type ParsedArgs = {
|
|
|
15
15
|
export type JsonPatchOperation = {
|
|
16
16
|
readonly op: "add";
|
|
17
17
|
readonly path: "/plugin";
|
|
18
|
-
readonly value: readonly [
|
|
18
|
+
readonly value: readonly string[];
|
|
19
19
|
} | {
|
|
20
20
|
readonly op: "replace";
|
|
21
21
|
readonly path: "/plugin";
|
|
22
|
-
readonly value: readonly [
|
|
22
|
+
readonly value: readonly string[];
|
|
23
23
|
} | {
|
|
24
24
|
readonly op: "add";
|
|
25
25
|
readonly path: "/plugin/-";
|
|
26
|
-
readonly value:
|
|
26
|
+
readonly value: string;
|
|
27
|
+
} | {
|
|
28
|
+
readonly op: "replace";
|
|
29
|
+
readonly path: `/plugin/${number}`;
|
|
30
|
+
readonly value: string;
|
|
27
31
|
};
|
|
28
32
|
export type PluginMutation = {
|
|
29
33
|
readonly changed: boolean;
|
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,32 @@ import { helpText, parseArgs } from "./cli/args.js";
|
|
|
2
2
|
import { doctor } from "./cli/doctor.js";
|
|
3
3
|
import { install } from "./cli/install.js";
|
|
4
4
|
import { LitOpenCodeConfigError } from "./config.js";
|
|
5
|
+
const installFrames = [
|
|
6
|
+
{ bar: "[##........]", label: "resolving litopencode package" },
|
|
7
|
+
{ bar: "[####......]", label: "opening OpenCode config" },
|
|
8
|
+
{ bar: "[######....]", label: "patching plugin registry" },
|
|
9
|
+
{ bar: "[########..]", label: "checking lit-plan / lit-loop" },
|
|
10
|
+
{ bar: "[##########]", label: "sealing installer state" }
|
|
11
|
+
];
|
|
12
|
+
function sleep(milliseconds) {
|
|
13
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
14
|
+
}
|
|
15
|
+
function shouldAnimateInstall(argv, result) {
|
|
16
|
+
return (process.stdout.isTTY === true &&
|
|
17
|
+
process.env.CI === undefined &&
|
|
18
|
+
argv[0] === "install" &&
|
|
19
|
+
!argv.includes("--dry-run") &&
|
|
20
|
+
result.exitCode === 0 &&
|
|
21
|
+
result.stdout !== undefined);
|
|
22
|
+
}
|
|
23
|
+
async function renderInstallEffect() {
|
|
24
|
+
process.stdout.write("\n");
|
|
25
|
+
for (const frame of installFrames) {
|
|
26
|
+
process.stdout.write("\r\u001b[2K " + frame.bar + " " + frame.label);
|
|
27
|
+
await sleep(95);
|
|
28
|
+
}
|
|
29
|
+
process.stdout.write("\r\u001b[2K");
|
|
30
|
+
}
|
|
5
31
|
export async function runCli(argv = process.argv.slice(2)) {
|
|
6
32
|
let parsed;
|
|
7
33
|
try {
|
|
@@ -29,6 +55,8 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
29
55
|
}
|
|
30
56
|
export async function main(argv = process.argv.slice(2)) {
|
|
31
57
|
const result = await runCli(argv);
|
|
58
|
+
if (shouldAnimateInstall(argv, result))
|
|
59
|
+
await renderInstallEffect();
|
|
32
60
|
if (result.stdout)
|
|
33
61
|
process.stdout.write(`${result.stdout}\n`);
|
|
34
62
|
if (result.stderr)
|
package/dist/features.js
CHANGED
|
@@ -124,7 +124,7 @@ export const litOpenCodeFeatures = Object.freeze([
|
|
|
124
124
|
kind: "cli",
|
|
125
125
|
id: "npx litopencode install",
|
|
126
126
|
surface: "litopencode CLI install command",
|
|
127
|
-
description: "
|
|
127
|
+
description: "Delegates default setup to OpenCode's plugin installer, or adds a version-pinned litopencode entry for custom roots; --dry-run prints the mutation only."
|
|
128
128
|
}
|
|
129
129
|
],
|
|
130
130
|
verification: ["node --test test/cli.test.mjs", "node --test test/config-state.test.mjs"]
|
package/dist/index.d.ts
CHANGED
|
@@ -7,9 +7,9 @@ export { litOpenCodeTools, litTool, litworkTool } from "./tools.ts";
|
|
|
7
7
|
export { findLitOpenCodeFeature, litOpenCodeFeatures, type LitOpenCodeBindingKind, type LitOpenCodeFeature, type LitOpenCodeFeatureBinding, type LitOpenCodeFeatureId } from "./features.ts";
|
|
8
8
|
export { findLitOpenCodeRuntimeSkill, litOpenCodeRuntimeSkills, type LitOpenCodeRuntimeSkill, type LitOpenCodeRuntimeSkillId } from "./skills.ts";
|
|
9
9
|
export declare const pluginId = "litopencode";
|
|
10
|
-
|
|
11
|
-
declare const pluginModule: {
|
|
10
|
+
declare const litOpenCodePlugin: (input?: PluginInput) => Promise<Hooks>;
|
|
11
|
+
export declare const pluginModule: {
|
|
12
12
|
id: string;
|
|
13
13
|
server: (input?: PluginInput) => Promise<Hooks>;
|
|
14
14
|
};
|
|
15
|
-
export default
|
|
15
|
+
export default litOpenCodePlugin;
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export { litOpenCodeTools, litTool, litworkTool } from "./tools.js";
|
|
|
12
12
|
export { findLitOpenCodeFeature, litOpenCodeFeatures } from "./features.js";
|
|
13
13
|
export { findLitOpenCodeRuntimeSkill, litOpenCodeRuntimeSkills } from "./skills.js";
|
|
14
14
|
export const pluginId = "litopencode";
|
|
15
|
-
|
|
15
|
+
const litOpenCodePlugin = async (input) => {
|
|
16
16
|
const root = input?.worktree ?? input?.directory ?? ".";
|
|
17
17
|
const loaded = await loadConfig(root);
|
|
18
18
|
const logger = createLogger(loaded.paths);
|
|
@@ -38,8 +38,8 @@ export const server = async (input) => {
|
|
|
38
38
|
});
|
|
39
39
|
return hooks;
|
|
40
40
|
};
|
|
41
|
-
const pluginModule = {
|
|
41
|
+
export const pluginModule = {
|
|
42
42
|
id: pluginId,
|
|
43
|
-
server
|
|
43
|
+
server: litOpenCodePlugin
|
|
44
44
|
};
|
|
45
|
-
export default
|
|
45
|
+
export default litOpenCodePlugin;
|
package/dist/skills.js
CHANGED
|
@@ -39,7 +39,7 @@ export const litOpenCodeRuntimeSkills = Object.freeze([
|
|
|
39
39
|
featureIds: ["doctor-install"],
|
|
40
40
|
discovery: "Run npx litopencode install, litopencode doctor, or litopencode install --dry-run.",
|
|
41
41
|
safety: [
|
|
42
|
-
"Default installation
|
|
42
|
+
"Default installation delegates to the OpenCode plugin installer; custom roots write only the version-pinned opencode.json plugin entry.",
|
|
43
43
|
"Malformed config fails closed with a typed config error."
|
|
44
44
|
]
|
|
45
45
|
},
|
package/docs/migration.md
CHANGED
|
@@ -13,7 +13,7 @@ This guide describes the supported migration shape for moving an OpenCode workfl
|
|
|
13
13
|
- Use the `LITOPENCODE_` prefix for LitOpenCode-owned environment variables.
|
|
14
14
|
- Keep OpenCode plugin configuration in `opencode.json`.
|
|
15
15
|
- Keep LitOpenCode runtime config in `.litopencode/config.json` when project-local config is needed.
|
|
16
|
-
- Use `npx litopencode install` to
|
|
16
|
+
- Use `npx litopencode install` to delegate default setup to `opencode plugin litopencode@0.0.3 --global --force` and register the version-pinned plugin entry.
|
|
17
17
|
- Use `litopencode doctor --root <workspace>` to inspect package metadata, config source, runtime paths, and ledger location without writing files.
|
|
18
18
|
- Use `litopencode install --dry-run --root <workspace>` to preview the `opencode.json` plugin mutation without writing files.
|
|
19
19
|
|
|
@@ -41,7 +41,7 @@ After the source gates pass, verify the packed artifact in a temporary directory
|
|
|
41
41
|
```sh
|
|
42
42
|
tmp="$(mktemp -d)"
|
|
43
43
|
npm pack --pack-destination "$tmp"
|
|
44
|
-
tar -xzf "$tmp"/litopencode-0.0.
|
|
44
|
+
tar -xzf "$tmp"/litopencode-0.0.3.tgz -C "$tmp"
|
|
45
45
|
node --input-type=module -e "import('$tmp/package/dist/index.js').then((m) => console.log(m.default?.id ?? m.pluginId))"
|
|
46
46
|
(
|
|
47
47
|
cd "$tmp/package"
|
|
@@ -73,7 +73,7 @@ mkdir "$tmp/consumer"
|
|
|
73
73
|
(
|
|
74
74
|
cd "$tmp/consumer"
|
|
75
75
|
npm init -y
|
|
76
|
-
npm install "$tmp"/litopencode-0.0.
|
|
76
|
+
npm install "$tmp"/litopencode-0.0.3.tgz
|
|
77
77
|
npm ls litopencode --all
|
|
78
78
|
node --input-type=module -e "import('litopencode').then((m) => console.log(m.default.id))"
|
|
79
79
|
node_modules/.bin/litopencode --help
|
|
@@ -94,7 +94,7 @@ Expected results:
|
|
|
94
94
|
|
|
95
95
|
## OpenCode Host Probe
|
|
96
96
|
|
|
97
|
-
Use the installed temp-project package, not the source tree, to import `litopencode`, call the
|
|
97
|
+
Use the installed temp-project package, not the source tree, to import `litopencode`, import `litopencode/server`, call the plugin function, invoke the config hook, command hook, `lit` and `litwork` tools, and before/after tool guard hooks. Expected results:
|
|
98
98
|
|
|
99
99
|
- `server()` exposes config, tool, command activation, dispose, and non-enumerable tool guard hooks
|
|
100
100
|
- config registers the LitOpenCode agent roster
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "litopencode",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "LitOpenCode OpenCode plugin bootstrap package.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,14 @@
|
|
|
8
8
|
},
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"exports": {
|
|
11
|
-
".":
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./server": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
}
|
|
12
19
|
},
|
|
13
20
|
"scripts": {
|
|
14
21
|
"build": "node tools/run-build.mjs",
|
|
@@ -5,7 +5,7 @@ Use this LitOpenCode skill when a contributor needs the static CLI health and in
|
|
|
5
5
|
## Covers
|
|
6
6
|
|
|
7
7
|
- Inspect package metadata, config source, runtime paths, and state presence.
|
|
8
|
-
- Install the OpenCode plugin
|
|
8
|
+
- Install or update the version-pinned OpenCode plugin through the OpenCode plugin installer with a bounded branded terminal flow.
|
|
9
9
|
- Preview OpenCode plugin configuration changes without writing files when `--dry-run` is set.
|
|
10
10
|
- Keep malformed config handling fail-closed.
|
|
11
11
|
- Keep installer output bounded and avoid leaking existing user config content.
|