litopencode 0.0.0 → 0.0.1
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 +17 -7
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.js +58 -0
- package/dist/cli/doctor.d.ts +2 -0
- package/dist/cli/doctor.js +34 -0
- package/dist/cli/install.d.ts +2 -0
- package/dist/cli/install.js +85 -0
- package/dist/cli/json.d.ts +4 -0
- package/dist/cli/json.js +30 -0
- package/dist/cli/types.d.ts +45 -0
- package/dist/cli/types.js +1 -0
- package/dist/cli.d.ts +1 -6
- package/dist/cli.js +4 -171
- package/dist/features.js +3 -3
- package/dist/skills.js +3 -3
- package/docs/migration.md +2 -1
- package/docs/release-checklist.md +7 -5
- package/package.json +8 -2
- package/skills/doctor-installer/SKILL.md +4 -2
package/README.md
CHANGED
|
@@ -24,12 +24,28 @@
|
|
|
24
24
|
- **Two-agent OpenCode surface** -- Keep the visible agent switcher focused on <code>lit-plan</code> and <code>lit-loop</code>.
|
|
25
25
|
- **Durable goal ledger** -- Persist resumable progress under <code>.litopencode/litgoal/lit-loop/ledger.jsonl</code>.
|
|
26
26
|
- **OpenCode-native plugin hooks** -- Register config, tools, command activation, tool guards, and dispose lifecycle hooks.
|
|
27
|
-
- **
|
|
27
|
+
- **npx installer/doctor CLI** -- Install the plugin into OpenCode with a branded terminal flow, or preview the exact config patch with dry-run.
|
|
28
28
|
- **Role aliases and specialists** -- Keep deeper planning, implementation, verification, QA, review, research, and specialist roles available without crowding the default UI.
|
|
29
29
|
- **Release guardrails** -- Verify scanner, lockstep, packed payload, source gates, and dry-pack behavior before publication.
|
|
30
30
|
|
|
31
31
|
## Quick Start
|
|
32
32
|
|
|
33
|
+
### Install Into OpenCode
|
|
34
|
+
|
|
35
|
+
npx litopencode install
|
|
36
|
+
|
|
37
|
+
Then restart OpenCode. The agent switcher should show <code>lit-plan</code> and <code>lit-loop</code>.
|
|
38
|
+
|
|
39
|
+
For a preview without writing <code>opencode.json</code>:
|
|
40
|
+
|
|
41
|
+
npx litopencode install --dry-run
|
|
42
|
+
|
|
43
|
+
For health checks:
|
|
44
|
+
|
|
45
|
+
npx litopencode doctor
|
|
46
|
+
|
|
47
|
+
By default, the installer targets <code>~/.config/opencode/opencode.json</code>, or <code>$XDG_CONFIG_HOME/opencode/opencode.json</code> when <code>XDG_CONFIG_HOME</code> is set. Use <code>--root <dir></code> for a custom OpenCode config directory.
|
|
48
|
+
|
|
33
49
|
### Local Source Probe
|
|
34
50
|
|
|
35
51
|
npm install
|
|
@@ -41,12 +57,6 @@
|
|
|
41
57
|
node bin/litopencode doctor --root .
|
|
42
58
|
node bin/litopencode install --dry-run --root .
|
|
43
59
|
|
|
44
|
-
### OpenCode Plugin Install Preview
|
|
45
|
-
|
|
46
|
-
node bin/litopencode install --dry-run --root ~/.config/opencode
|
|
47
|
-
|
|
48
|
-
<code>install --dry-run</code> prints the exact <code>opencode.json</code> plugin mutation without writing files. <code>doctor</code> reports package, config, and runtime path status without creating runtime state.
|
|
49
|
-
|
|
50
60
|
## Package Surface
|
|
51
61
|
|
|
52
62
|
| Surface | Value |
|
package/dist/cli/args.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function defaultOpenCodeRoot() {
|
|
4
|
+
const configHome = process.env.XDG_CONFIG_HOME;
|
|
5
|
+
if (configHome && configHome.length > 0)
|
|
6
|
+
return path.join(configHome, "opencode");
|
|
7
|
+
return path.join(os.homedir(), ".config", "opencode");
|
|
8
|
+
}
|
|
9
|
+
export function parseArgs(argv) {
|
|
10
|
+
let command = argv[0];
|
|
11
|
+
let root = defaultOpenCodeRoot();
|
|
12
|
+
let dryRun = false;
|
|
13
|
+
if (command === "--help" || command === "-h") {
|
|
14
|
+
command = "help";
|
|
15
|
+
}
|
|
16
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
17
|
+
const arg = argv[index];
|
|
18
|
+
if (arg === "--root" || arg === "--workdir") {
|
|
19
|
+
const value = argv[index + 1];
|
|
20
|
+
if (!value)
|
|
21
|
+
throw new Error(`${arg} requires a value`);
|
|
22
|
+
root = value;
|
|
23
|
+
index += 1;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (arg === "--dry-run") {
|
|
27
|
+
dryRun = true;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (arg === "--help" || arg === "-h") {
|
|
31
|
+
command = "help";
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
35
|
+
}
|
|
36
|
+
return { command, root, dryRun };
|
|
37
|
+
}
|
|
38
|
+
export function helpText() {
|
|
39
|
+
return [
|
|
40
|
+
"litopencode",
|
|
41
|
+
"",
|
|
42
|
+
"Install LitOpenCode into OpenCode with a polished npx flow.",
|
|
43
|
+
"",
|
|
44
|
+
"Quick start:",
|
|
45
|
+
" npx litopencode install",
|
|
46
|
+
"",
|
|
47
|
+
"Usage:",
|
|
48
|
+
" litopencode install [--dry-run] [--root <dir>]",
|
|
49
|
+
" litopencode doctor [--root <dir>]",
|
|
50
|
+
"",
|
|
51
|
+
"Commands:",
|
|
52
|
+
" install Add litopencode to opencode.json with a branded installer UI.",
|
|
53
|
+
" doctor Report package, config, and runtime path status without writing files.",
|
|
54
|
+
"",
|
|
55
|
+
"Default root:",
|
|
56
|
+
" ~/.config/opencode, or $XDG_CONFIG_HOME/opencode when XDG_CONFIG_HOME is set."
|
|
57
|
+
].join("\n");
|
|
58
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { loadConfig } from "../config.js";
|
|
3
|
+
import { readPackageMetadata } from "./json.js";
|
|
4
|
+
export async function doctor(root) {
|
|
5
|
+
const metadata = await readPackageMetadata();
|
|
6
|
+
const loaded = await loadConfig(root);
|
|
7
|
+
const runtimeExists = await fs
|
|
8
|
+
.stat(loaded.paths.runtimeDir)
|
|
9
|
+
.then(() => true)
|
|
10
|
+
.catch((error) => {
|
|
11
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
12
|
+
return false;
|
|
13
|
+
throw error;
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
exitCode: 0,
|
|
17
|
+
stdout: JSON.stringify({
|
|
18
|
+
package: metadata,
|
|
19
|
+
config: {
|
|
20
|
+
source: loaded.source,
|
|
21
|
+
path: loaded.paths.configFile,
|
|
22
|
+
enabled: loaded.config.enabled,
|
|
23
|
+
logLevel: loaded.config.logLevel
|
|
24
|
+
},
|
|
25
|
+
state: {
|
|
26
|
+
runtimeDir: loaded.paths.runtimeDir,
|
|
27
|
+
runtimeExists,
|
|
28
|
+
stateFile: loaded.paths.stateFile,
|
|
29
|
+
logFile: loaded.paths.logFile,
|
|
30
|
+
ledgerFile: loaded.paths.ledgerFile
|
|
31
|
+
}
|
|
32
|
+
}, null, 2)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createRuntimePaths } from "../state.js";
|
|
4
|
+
import { readJsonObjectIfPresent, readPackageMetadata } from "./json.js";
|
|
5
|
+
const pluginId = "litopencode";
|
|
6
|
+
function describePluginMutation(config) {
|
|
7
|
+
const pluginValue = config?.plugin;
|
|
8
|
+
const pluginIsArray = Array.isArray(pluginValue);
|
|
9
|
+
const alreadyPresent = pluginIsArray && pluginValue.includes(pluginId);
|
|
10
|
+
const currentCount = pluginIsArray ? pluginValue.length : 0;
|
|
11
|
+
if (alreadyPresent) {
|
|
12
|
+
return {
|
|
13
|
+
changed: false,
|
|
14
|
+
plugin: { alreadyPresent: true, currentCount, resultCount: currentCount, add: [] },
|
|
15
|
+
patch: []
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const add = [pluginId];
|
|
19
|
+
if (pluginIsArray) {
|
|
20
|
+
return {
|
|
21
|
+
changed: true,
|
|
22
|
+
plugin: { alreadyPresent: false, currentCount, resultCount: currentCount + 1, add },
|
|
23
|
+
patch: [{ op: "add", path: "/plugin/-", value: pluginId }]
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const op = config !== null && Object.hasOwn(config, "plugin") ? "replace" : "add";
|
|
27
|
+
return {
|
|
28
|
+
changed: true,
|
|
29
|
+
plugin: { alreadyPresent: false, currentCount, resultCount: 1, add },
|
|
30
|
+
patch: [{ op, path: "/plugin", value: [pluginId] }]
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function applyPluginMutation(config, mutation) {
|
|
34
|
+
const next = config === null ? { "$schema": "https://opencode.ai/config.json" } : { ...config };
|
|
35
|
+
if (!mutation.changed)
|
|
36
|
+
return next;
|
|
37
|
+
const pluginValue = next.plugin;
|
|
38
|
+
if (Array.isArray(pluginValue)) {
|
|
39
|
+
next.plugin = [...pluginValue, pluginId];
|
|
40
|
+
return next;
|
|
41
|
+
}
|
|
42
|
+
next.plugin = [pluginId];
|
|
43
|
+
return next;
|
|
44
|
+
}
|
|
45
|
+
function renderInstallReport(report) {
|
|
46
|
+
const status = report.changed ? "Complete" : "Already installed";
|
|
47
|
+
const action = report.changed ? "Added litopencode to plugin[]" : "No config change needed";
|
|
48
|
+
return [
|
|
49
|
+
"LitOpenCode Installer",
|
|
50
|
+
"=====================",
|
|
51
|
+
"",
|
|
52
|
+
"[-] Resolving package " + `${report.package.name}@${report.package.version}`,
|
|
53
|
+
"[\\] Reading OpenCode config " + report.path,
|
|
54
|
+
"[|] Planning plugin patch " + `${report.plugin.currentCount} -> ${report.plugin.resultCount}`,
|
|
55
|
+
"[/] Writing config " + action,
|
|
56
|
+
"",
|
|
57
|
+
status,
|
|
58
|
+
"",
|
|
59
|
+
"Next:",
|
|
60
|
+
" Restart OpenCode and press tab to switch between lit-plan and lit-loop."
|
|
61
|
+
].join("\n");
|
|
62
|
+
}
|
|
63
|
+
export async function install(root, dryRun) {
|
|
64
|
+
const metadata = await readPackageMetadata();
|
|
65
|
+
const paths = createRuntimePaths(root);
|
|
66
|
+
const before = await readJsonObjectIfPresent(paths.opencodeConfigFile);
|
|
67
|
+
const mutation = describePluginMutation(before);
|
|
68
|
+
const report = {
|
|
69
|
+
dryRun,
|
|
70
|
+
path: paths.opencodeConfigFile,
|
|
71
|
+
plugin: mutation.plugin,
|
|
72
|
+
patch: mutation.patch,
|
|
73
|
+
changed: mutation.changed,
|
|
74
|
+
package: metadata
|
|
75
|
+
};
|
|
76
|
+
if (dryRun) {
|
|
77
|
+
return { exitCode: 0, stdout: JSON.stringify(report, null, 2) };
|
|
78
|
+
}
|
|
79
|
+
if (mutation.changed) {
|
|
80
|
+
const next = applyPluginMutation(before, mutation);
|
|
81
|
+
await fs.mkdir(path.dirname(paths.opencodeConfigFile), { recursive: true });
|
|
82
|
+
await fs.writeFile(paths.opencodeConfigFile, JSON.stringify(next, null, 2) + "\n");
|
|
83
|
+
}
|
|
84
|
+
return { exitCode: 0, stdout: renderInstallReport(report) };
|
|
85
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PackageMetadata } from "./types.ts";
|
|
2
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
3
|
+
export declare function readPackageMetadata(): Promise<PackageMetadata>;
|
|
4
|
+
export declare function readJsonObjectIfPresent(filePath: string): Promise<Record<string, unknown> | null>;
|
package/dist/cli/json.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
export function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
export async function readPackageMetadata() {
|
|
8
|
+
const packagePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../package.json");
|
|
9
|
+
const parsed = JSON.parse(await fs.readFile(packagePath, "utf8"));
|
|
10
|
+
if (!isRecord(parsed) || typeof parsed.name !== "string" || typeof parsed.version !== "string") {
|
|
11
|
+
throw new Error(`Malformed package metadata at ${packagePath}`);
|
|
12
|
+
}
|
|
13
|
+
return { name: parsed.name, version: parsed.version };
|
|
14
|
+
}
|
|
15
|
+
export async function readJsonObjectIfPresent(filePath) {
|
|
16
|
+
let raw;
|
|
17
|
+
try {
|
|
18
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
22
|
+
return null;
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
const parsed = JSON.parse(raw);
|
|
26
|
+
if (!isRecord(parsed)) {
|
|
27
|
+
throw new Error(`Malformed JSON at ${filePath}: expected an object.`);
|
|
28
|
+
}
|
|
29
|
+
return parsed;
|
|
30
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type CliResult = {
|
|
2
|
+
readonly exitCode: number;
|
|
3
|
+
readonly stdout?: string;
|
|
4
|
+
readonly stderr?: string;
|
|
5
|
+
};
|
|
6
|
+
export type PackageMetadata = {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly version: string;
|
|
9
|
+
};
|
|
10
|
+
export type ParsedArgs = {
|
|
11
|
+
readonly command?: string;
|
|
12
|
+
readonly root: string;
|
|
13
|
+
readonly dryRun: boolean;
|
|
14
|
+
};
|
|
15
|
+
export type JsonPatchOperation = {
|
|
16
|
+
readonly op: "add";
|
|
17
|
+
readonly path: "/plugin";
|
|
18
|
+
readonly value: readonly ["litopencode"];
|
|
19
|
+
} | {
|
|
20
|
+
readonly op: "replace";
|
|
21
|
+
readonly path: "/plugin";
|
|
22
|
+
readonly value: readonly ["litopencode"];
|
|
23
|
+
} | {
|
|
24
|
+
readonly op: "add";
|
|
25
|
+
readonly path: "/plugin/-";
|
|
26
|
+
readonly value: "litopencode";
|
|
27
|
+
};
|
|
28
|
+
export type PluginMutation = {
|
|
29
|
+
readonly changed: boolean;
|
|
30
|
+
readonly plugin: {
|
|
31
|
+
readonly alreadyPresent: boolean;
|
|
32
|
+
readonly currentCount: number;
|
|
33
|
+
readonly resultCount: number;
|
|
34
|
+
readonly add: readonly string[];
|
|
35
|
+
};
|
|
36
|
+
readonly patch: readonly JsonPatchOperation[];
|
|
37
|
+
};
|
|
38
|
+
export type InstallReport = {
|
|
39
|
+
readonly dryRun: boolean;
|
|
40
|
+
readonly path: string;
|
|
41
|
+
readonly plugin: PluginMutation["plugin"];
|
|
42
|
+
readonly patch: readonly JsonPatchOperation[];
|
|
43
|
+
readonly changed: boolean;
|
|
44
|
+
readonly package: PackageMetadata;
|
|
45
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
type CliResult
|
|
2
|
-
exitCode: number;
|
|
3
|
-
stdout?: string;
|
|
4
|
-
stderr?: string;
|
|
5
|
-
};
|
|
1
|
+
import type { CliResult } from "./cli/types.ts";
|
|
6
2
|
export declare function runCli(argv?: readonly string[]): Promise<CliResult>;
|
|
7
3
|
export declare function main(argv?: readonly string[]): Promise<void>;
|
|
8
|
-
export {};
|
package/dist/cli.js
CHANGED
|
@@ -1,174 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import { LitOpenCodeConfigError
|
|
5
|
-
import { createRuntimePaths } from "./state.js";
|
|
6
|
-
function isRecord(value) {
|
|
7
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
|
-
}
|
|
9
|
-
async function readPackageMetadata() {
|
|
10
|
-
const packagePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../package.json");
|
|
11
|
-
const parsed = JSON.parse(await fs.readFile(packagePath, "utf8"));
|
|
12
|
-
if (!isRecord(parsed) || typeof parsed.name !== "string" || typeof parsed.version !== "string") {
|
|
13
|
-
throw new Error(`Malformed package metadata at ${packagePath}`);
|
|
14
|
-
}
|
|
15
|
-
return { name: parsed.name, version: parsed.version };
|
|
16
|
-
}
|
|
17
|
-
function parseArgs(argv) {
|
|
18
|
-
const parsed = {
|
|
19
|
-
command: argv[0],
|
|
20
|
-
root: process.cwd(),
|
|
21
|
-
dryRun: false
|
|
22
|
-
};
|
|
23
|
-
if (parsed.command === "--help" || parsed.command === "-h") {
|
|
24
|
-
parsed.command = "help";
|
|
25
|
-
}
|
|
26
|
-
for (let index = 1; index < argv.length; index += 1) {
|
|
27
|
-
const arg = argv[index];
|
|
28
|
-
if (arg === "--root" || arg === "--workdir") {
|
|
29
|
-
const value = argv[index + 1];
|
|
30
|
-
if (!value)
|
|
31
|
-
throw new Error(`${arg} requires a value`);
|
|
32
|
-
parsed.root = value;
|
|
33
|
-
index += 1;
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
if (arg === "--dry-run") {
|
|
37
|
-
parsed.dryRun = true;
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
if (arg === "--help" || arg === "-h") {
|
|
41
|
-
parsed.command = "help";
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
throw new Error(`Unknown argument: ${arg}`);
|
|
45
|
-
}
|
|
46
|
-
return parsed;
|
|
47
|
-
}
|
|
48
|
-
function helpText() {
|
|
49
|
-
return [
|
|
50
|
-
"litopencode",
|
|
51
|
-
"",
|
|
52
|
-
"Usage:",
|
|
53
|
-
" litopencode doctor [--root <dir>]",
|
|
54
|
-
" litopencode install --dry-run [--root <dir>]",
|
|
55
|
-
"",
|
|
56
|
-
"Commands:",
|
|
57
|
-
" doctor Report package, config, and runtime path status without writing files.",
|
|
58
|
-
" install Print the exact opencode.json mutation for adding this plugin."
|
|
59
|
-
].join("\n");
|
|
60
|
-
}
|
|
61
|
-
async function readJsonObjectIfPresent(filePath) {
|
|
62
|
-
let raw;
|
|
63
|
-
try {
|
|
64
|
-
raw = await fs.readFile(filePath, "utf8");
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
68
|
-
return null;
|
|
69
|
-
throw error;
|
|
70
|
-
}
|
|
71
|
-
const parsed = JSON.parse(raw);
|
|
72
|
-
if (!isRecord(parsed)) {
|
|
73
|
-
throw new Error(`Malformed JSON at ${filePath}: expected an object.`);
|
|
74
|
-
}
|
|
75
|
-
return parsed;
|
|
76
|
-
}
|
|
77
|
-
function describePluginMutation(config) {
|
|
78
|
-
const pluginValue = config?.plugin;
|
|
79
|
-
const pluginIsArray = Array.isArray(pluginValue);
|
|
80
|
-
const alreadyPresent = pluginIsArray && pluginValue.includes("litopencode");
|
|
81
|
-
const currentCount = pluginIsArray ? pluginValue.length : 0;
|
|
82
|
-
if (alreadyPresent) {
|
|
83
|
-
return {
|
|
84
|
-
changed: false,
|
|
85
|
-
plugin: {
|
|
86
|
-
alreadyPresent: true,
|
|
87
|
-
currentCount,
|
|
88
|
-
resultCount: currentCount,
|
|
89
|
-
add: []
|
|
90
|
-
},
|
|
91
|
-
patch: []
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
const add = ["litopencode"];
|
|
95
|
-
const resultCount = currentCount + add.length;
|
|
96
|
-
if (pluginIsArray) {
|
|
97
|
-
return {
|
|
98
|
-
changed: true,
|
|
99
|
-
plugin: {
|
|
100
|
-
alreadyPresent: false,
|
|
101
|
-
currentCount,
|
|
102
|
-
resultCount,
|
|
103
|
-
add
|
|
104
|
-
},
|
|
105
|
-
patch: [{ op: "add", path: "/plugin/-", value: "litopencode" }]
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
const op = config !== null && Object.hasOwn(config, "plugin") ? "replace" : "add";
|
|
109
|
-
return {
|
|
110
|
-
changed: true,
|
|
111
|
-
plugin: {
|
|
112
|
-
alreadyPresent: false,
|
|
113
|
-
currentCount,
|
|
114
|
-
resultCount: 1,
|
|
115
|
-
add
|
|
116
|
-
},
|
|
117
|
-
patch: [{ op, path: "/plugin", value: ["litopencode"] }]
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
async function doctor(root) {
|
|
121
|
-
const metadata = await readPackageMetadata();
|
|
122
|
-
const loaded = await loadConfig(root);
|
|
123
|
-
const runtimeExists = await fs
|
|
124
|
-
.stat(loaded.paths.runtimeDir)
|
|
125
|
-
.then(() => true)
|
|
126
|
-
.catch((error) => {
|
|
127
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
128
|
-
return false;
|
|
129
|
-
throw error;
|
|
130
|
-
});
|
|
131
|
-
return {
|
|
132
|
-
exitCode: 0,
|
|
133
|
-
stdout: JSON.stringify({
|
|
134
|
-
package: metadata,
|
|
135
|
-
config: {
|
|
136
|
-
source: loaded.source,
|
|
137
|
-
path: loaded.paths.configFile,
|
|
138
|
-
enabled: loaded.config.enabled,
|
|
139
|
-
logLevel: loaded.config.logLevel
|
|
140
|
-
},
|
|
141
|
-
state: {
|
|
142
|
-
runtimeDir: loaded.paths.runtimeDir,
|
|
143
|
-
runtimeExists,
|
|
144
|
-
stateFile: loaded.paths.stateFile,
|
|
145
|
-
logFile: loaded.paths.logFile,
|
|
146
|
-
ledgerFile: loaded.paths.ledgerFile
|
|
147
|
-
}
|
|
148
|
-
}, null, 2)
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
async function install(root, dryRun) {
|
|
152
|
-
if (!dryRun) {
|
|
153
|
-
return {
|
|
154
|
-
exitCode: 2,
|
|
155
|
-
stderr: "litopencode install currently requires --dry-run; no files were written."
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
const paths = createRuntimePaths(root);
|
|
159
|
-
const before = await readJsonObjectIfPresent(paths.opencodeConfigFile);
|
|
160
|
-
const mutation = describePluginMutation(before);
|
|
161
|
-
return {
|
|
162
|
-
exitCode: 0,
|
|
163
|
-
stdout: JSON.stringify({
|
|
164
|
-
dryRun: true,
|
|
165
|
-
path: paths.opencodeConfigFile,
|
|
166
|
-
plugin: mutation.plugin,
|
|
167
|
-
patch: mutation.patch,
|
|
168
|
-
changed: mutation.changed
|
|
169
|
-
}, null, 2)
|
|
170
|
-
};
|
|
171
|
-
}
|
|
1
|
+
import { helpText, parseArgs } from "./cli/args.js";
|
|
2
|
+
import { doctor } from "./cli/doctor.js";
|
|
3
|
+
import { install } from "./cli/install.js";
|
|
4
|
+
import { LitOpenCodeConfigError } from "./config.js";
|
|
172
5
|
export async function runCli(argv = process.argv.slice(2)) {
|
|
173
6
|
let parsed;
|
|
174
7
|
try {
|
package/dist/features.js
CHANGED
|
@@ -112,7 +112,7 @@ export const litOpenCodeFeatures = Object.freeze([
|
|
|
112
112
|
{
|
|
113
113
|
id: "doctor-install",
|
|
114
114
|
title: "Doctor And Installer CLI",
|
|
115
|
-
summary: "Reports package, config, and state health and previews the OpenCode plugin config mutation.",
|
|
115
|
+
summary: "Reports package, config, and state health and installs or previews the OpenCode plugin config mutation.",
|
|
116
116
|
bindings: [
|
|
117
117
|
{
|
|
118
118
|
kind: "cli",
|
|
@@ -122,9 +122,9 @@ export const litOpenCodeFeatures = Object.freeze([
|
|
|
122
122
|
},
|
|
123
123
|
{
|
|
124
124
|
kind: "cli",
|
|
125
|
-
id: "litopencode install
|
|
125
|
+
id: "npx litopencode install",
|
|
126
126
|
surface: "litopencode CLI install command",
|
|
127
|
-
description: "
|
|
127
|
+
description: "Adds litopencode to opencode.json with branded progress output; --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/skills.js
CHANGED
|
@@ -35,11 +35,11 @@ export const litOpenCodeRuntimeSkills = Object.freeze([
|
|
|
35
35
|
{
|
|
36
36
|
id: "doctor-installer",
|
|
37
37
|
title: "Doctor Installer",
|
|
38
|
-
summary: "Use the CLI to
|
|
38
|
+
summary: "Use the CLI to install the plugin into OpenCode or preview the plugin mutation without writing files.",
|
|
39
39
|
featureIds: ["doctor-install"],
|
|
40
|
-
discovery: "Run litopencode doctor or litopencode install --dry-run.",
|
|
40
|
+
discovery: "Run npx litopencode install, litopencode doctor, or litopencode install --dry-run.",
|
|
41
41
|
safety: [
|
|
42
|
-
"Default installation
|
|
42
|
+
"Default installation writes only the OpenCode 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,8 +13,9 @@ 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 add `litopencode` to the default OpenCode config at `~/.config/opencode/opencode.json`.
|
|
16
17
|
- Use `litopencode doctor --root <workspace>` to inspect package metadata, config source, runtime paths, and ledger location without writing files.
|
|
17
|
-
- Use `litopencode install --dry-run --root <workspace>` to preview the `opencode.json` plugin mutation
|
|
18
|
+
- Use `litopencode install --dry-run --root <workspace>` to preview the `opencode.json` plugin mutation without writing files.
|
|
18
19
|
|
|
19
20
|
## Durable State
|
|
20
21
|
|
|
@@ -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.1.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,21 +73,23 @@ mkdir "$tmp/consumer"
|
|
|
73
73
|
(
|
|
74
74
|
cd "$tmp/consumer"
|
|
75
75
|
npm init -y
|
|
76
|
-
npm install "$tmp"/litopencode-0.0.
|
|
77
|
-
npm ls litopencode
|
|
76
|
+
npm install "$tmp"/litopencode-0.0.1.tgz
|
|
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
|
|
80
80
|
node_modules/.bin/litopencode doctor --root .
|
|
81
81
|
node_modules/.bin/litopencode install --dry-run --root .
|
|
82
|
+
node_modules/.bin/litopencode install --root .
|
|
82
83
|
)
|
|
83
84
|
rm -rf "$tmp"
|
|
84
85
|
```
|
|
85
86
|
|
|
86
87
|
Expected results:
|
|
87
88
|
|
|
88
|
-
- `@opencode-ai/plugin` is
|
|
89
|
+
- `@opencode-ai/plugin` is declared as an optional peer for TypeScript host typings without forcing npx/global installs to pull its transitive runtime tree
|
|
89
90
|
- package import prints `litopencode`
|
|
90
|
-
- CLI help, doctor,
|
|
91
|
+
- CLI help, doctor, install dry-run, and write-enabled install exit 0
|
|
92
|
+
- write-enabled install creates or updates `opencode.json` without echoing unrelated config secrets
|
|
91
93
|
- temporary project cleanup removes the probe directory
|
|
92
94
|
|
|
93
95
|
## OpenCode Host Probe
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "litopencode",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.1",
|
|
4
4
|
"description": "LitOpenCode OpenCode plugin bootstrap package.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,10 +19,16 @@
|
|
|
19
19
|
"test": "node --test",
|
|
20
20
|
"typecheck": "node tools/run-typecheck.mjs"
|
|
21
21
|
},
|
|
22
|
-
"
|
|
22
|
+
"peerDependencies": {
|
|
23
23
|
"@opencode-ai/plugin": "^1.17.6"
|
|
24
24
|
},
|
|
25
|
+
"peerDependenciesMeta": {
|
|
26
|
+
"@opencode-ai/plugin": {
|
|
27
|
+
"optional": true
|
|
28
|
+
}
|
|
29
|
+
},
|
|
25
30
|
"devDependencies": {
|
|
31
|
+
"@opencode-ai/plugin": "^1.17.6",
|
|
26
32
|
"@types/node": "^24.12.2",
|
|
27
33
|
"typescript": "^5.9.3"
|
|
28
34
|
}
|
|
@@ -5,13 +5,15 @@ 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
|
-
-
|
|
8
|
+
- Install the OpenCode plugin entry with a bounded branded terminal flow.
|
|
9
|
+
- Preview OpenCode plugin configuration changes without writing files when `--dry-run` is set.
|
|
9
10
|
- Keep malformed config handling fail-closed.
|
|
10
11
|
- Keep installer output bounded and avoid leaking existing user config content.
|
|
11
12
|
|
|
12
13
|
## OpenCode Surfaces
|
|
13
14
|
|
|
14
15
|
- CLI: `litopencode doctor`
|
|
16
|
+
- CLI: `npx litopencode install`
|
|
15
17
|
- CLI: `litopencode install --dry-run`
|
|
16
18
|
- Runtime feature id: `doctor-install`
|
|
17
19
|
|
|
@@ -19,4 +21,4 @@ Use this LitOpenCode skill when a contributor needs the static CLI health and in
|
|
|
19
21
|
|
|
20
22
|
- This file is static documentation.
|
|
21
23
|
- Do not execute commands from this file automatically.
|
|
22
|
-
- Treat
|
|
24
|
+
- Treat installer output as a bounded summary, not as a full config dump.
|