litopencode 0.0.2 → 0.0.4

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
@@ -34,9 +34,9 @@
34
34
 
35
35
  npx litopencode install
36
36
 
37
- The installer writes a version-pinned plugin entry such as <code>litopencode@0.0.2</code> into OpenCode config. 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. Published installs register a version-pinned entry such as <code>litopencode@0.0.4</code>; local checkout installs register the local package path for development. Then restart OpenCode. The agent switcher should show <code>lit-plan</code> and <code>lit-loop</code>.
38
38
 
39
- For a preview without writing <code>opencode.json</code>:
39
+ For a preview without writing OpenCode config:
40
40
 
41
41
  npx litopencode install --dry-run
42
42
 
@@ -44,7 +44,7 @@ For health checks:
44
44
 
45
45
  npx litopencode doctor
46
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 &lt;dir&gt;</code> for a custom OpenCode config directory.
47
+ By default, OpenCode manages <code>~/.config/opencode/opencode.jsonc</code>, or <code>$XDG_CONFIG_HOME/opencode/opencode.jsonc</code> when <code>XDG_CONFIG_HOME</code> is set. Use <code>--root &lt;dir&gt;</code> for a custom OpenCode config directory; custom roots use a direct <code>opencode.json</code> patch for deterministic tests and previews.
48
48
 
49
49
  ### Local Source Probe
50
50
 
@@ -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>PluginModule</code>. 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>.
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
 
@@ -11,20 +11,37 @@ function toolsToConfig(tools) {
11
11
  }
12
12
  return enabledTools;
13
13
  }
14
+ function toolsToPermission(tools) {
15
+ const permission = {};
16
+ if (tools.includes("edit") || tools.includes("write"))
17
+ permission.edit = "allow";
18
+ if (tools.includes("bash"))
19
+ permission.bash = "allow";
20
+ if (tools.includes("webfetch"))
21
+ permission.webfetch = "allow";
22
+ return Object.keys(permission).length > 0 ? permission : undefined;
23
+ }
14
24
  export function toOpenCodeAgentConfig(agent) {
15
25
  return {
16
26
  description: agent.summary,
17
27
  prompt: agent.prompt,
18
- mode: agent.mode,
28
+ mode: agent.mode === "primary" ? "all" : "subagent",
19
29
  tools: toolsToConfig(agent.tools),
20
30
  color: agent.color,
21
- maxSteps: agent.maxSteps
31
+ maxSteps: agent.maxSteps,
32
+ ...(agent.mode === "subagent" ? { hidden: true } : {}),
33
+ ...(toolsToPermission(agent.tools) ? { permission: toolsToPermission(agent.tools) } : {})
22
34
  };
23
35
  }
24
36
  export function registerLitOpenCodeAgents(config) {
37
+ const existingBuild = config.agent?.build ?? {};
38
+ const existingPlan = config.agent?.plan ?? {};
25
39
  config.agent = {
26
- ...config.agent
40
+ ...config.agent,
41
+ build: { ...existingBuild, mode: "subagent", hidden: true },
42
+ plan: { ...existingPlan, mode: "subagent", hidden: true }
27
43
  };
44
+ config.default_agent = "lit-loop";
28
45
  for (const agent of litOpenCodeAgents) {
29
46
  config.agent[agent.id] = toOpenCodeAgentConfig(agent);
30
47
  }
@@ -1,7 +1,9 @@
1
1
  import type { Config } from "@opencode-ai/plugin";
2
2
  export type AgentTier = "default" | "role" | "specialist";
3
3
  export type AgentMode = "primary" | "subagent";
4
+ export type OpenCodeAgentMode = "all" | "subagent";
4
5
  export type AgentToolId = "read" | "write" | "edit" | "bash" | "webfetch" | "grep";
6
+ export type AgentPermission = "ask" | "allow" | "deny";
5
7
  export type LitOpenCodeAgent = {
6
8
  readonly id: string;
7
9
  readonly name: string;
@@ -18,12 +20,20 @@ export type LitOpenCodeAgent = {
18
20
  export type OpenCodeAgentConfig = {
19
21
  readonly description: string;
20
22
  readonly prompt: string;
21
- readonly mode: AgentMode;
23
+ readonly mode: OpenCodeAgentMode;
22
24
  readonly tools: Record<string, boolean>;
23
25
  readonly color: string;
24
26
  readonly maxSteps: number;
27
+ readonly hidden?: boolean;
28
+ readonly permission?: {
29
+ readonly edit?: AgentPermission;
30
+ readonly bash?: AgentPermission;
31
+ readonly webfetch?: AgentPermission;
32
+ };
33
+ };
34
+ export type AgentConfigTarget = Pick<Config, "agent"> & {
35
+ default_agent?: string;
25
36
  };
26
- export type AgentConfigTarget = Pick<Config, "agent">;
27
37
  export declare const planningTools: readonly ("read" | "grep")[];
28
38
  export declare const workerTools: readonly ("read" | "write" | "edit" | "bash" | "grep")[];
29
39
  export declare const reviewTools: readonly ("read" | "bash" | "grep")[];
package/dist/cli/args.js CHANGED
@@ -49,7 +49,7 @@ export function helpText() {
49
49
  " litopencode doctor [--root <dir>]",
50
50
  "",
51
51
  "Commands:",
52
- " install Add litopencode to opencode.json with a branded installer UI.",
52
+ " install Register litopencode with OpenCode using a branded installer UI.",
53
53
  " doctor Report package, config, and runtime path status without writing files.",
54
54
  "",
55
55
  "Default root:",
@@ -1,9 +1,13 @@
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
8
  const pluginName = "litopencode";
6
9
  const reset = "\u001b[0m";
10
+ const execFileAsync = promisify(execFile);
7
11
  function useColor() {
8
12
  return process.stdout.isTTY === true && process.env.NO_COLOR === undefined;
9
13
  }
@@ -24,6 +28,109 @@ function pluginSpec(metadata) {
24
28
  function isLitOpenCodeEntry(value) {
25
29
  return typeof value === "string" && (value === pluginName || value.startsWith(pluginName + "@"));
26
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
+ function openCodeConfigCandidates(root) {
41
+ const resolvedRoot = path.resolve(root);
42
+ return [path.join(resolvedRoot, "opencode.json"), path.join(resolvedRoot, "opencode.jsonc")];
43
+ }
44
+ async function openCodeManagedConfigFile(root) {
45
+ const [jsonFile, jsoncFile] = openCodeConfigCandidates(root);
46
+ if (await pathExists(jsonFile))
47
+ return jsonFile;
48
+ if (await pathExists(jsoncFile))
49
+ return jsoncFile;
50
+ return jsoncFile;
51
+ }
52
+ function outputText(value) {
53
+ if (typeof value === "string")
54
+ return value;
55
+ if (value instanceof Uint8Array)
56
+ return Buffer.from(value).toString("utf8");
57
+ return "";
58
+ }
59
+ function stripAnsi(value) {
60
+ return value.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g, "");
61
+ }
62
+ function commandFailureDetail(error) {
63
+ if (!(error instanceof Error))
64
+ return String(error);
65
+ const failure = error;
66
+ const parts = [];
67
+ const stderr = stripAnsi(outputText(failure.stderr)).trim();
68
+ const stdout = stripAnsi(outputText(failure.stdout)).trim();
69
+ const code = typeof failure.code === "number" || typeof failure.code === "string" ? String(failure.code) : "";
70
+ const signal = typeof failure.signal === "string" ? failure.signal : "";
71
+ if (stderr.length > 0)
72
+ parts.push("stderr:\n" + stderr);
73
+ if (stdout.length > 0)
74
+ parts.push("stdout:\n" + stdout);
75
+ if (code.length > 0)
76
+ parts.push("exit code: " + code);
77
+ if (signal.length > 0)
78
+ parts.push("signal: " + signal);
79
+ if (parts.length === 0 && error.message.length > 0)
80
+ parts.push(error.message);
81
+ return parts.join("\n\n");
82
+ }
83
+ async function pathExists(filePath) {
84
+ try {
85
+ await fs.stat(filePath);
86
+ return true;
87
+ }
88
+ catch (error) {
89
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
90
+ return false;
91
+ throw error;
92
+ }
93
+ }
94
+ async function isLocalSourcePackage(packageRoot) {
95
+ const realPackageRoot = await fs.realpath(packageRoot);
96
+ return (await pathExists(path.join(realPackageRoot, ".git"))) && (await pathExists(path.join(realPackageRoot, "src")));
97
+ }
98
+ async function openCodeInstallTarget(metadata) {
99
+ if (await isLocalSourcePackage(metadata.packageRoot)) {
100
+ const realPackageRoot = await fs.realpath(metadata.packageRoot);
101
+ return { value: realPackageRoot, label: metadata.name + "@" + metadata.version + " (local checkout)" };
102
+ }
103
+ const spec = pluginSpec(metadata);
104
+ return { value: spec, label: spec };
105
+ }
106
+ async function installWithOpenCode(target) {
107
+ try {
108
+ await execFileAsync("opencode", ["plugin", target.value, "--global", "--force"], {
109
+ timeout: 120_000,
110
+ maxBuffer: 1024 * 1024 * 4
111
+ });
112
+ }
113
+ catch (error) {
114
+ const detail = commandFailureDetail(error);
115
+ throw new Error("OpenCode plugin install failed for " + target.label + (detail ? ":\n" + detail : ""));
116
+ }
117
+ }
118
+ async function removeStaleOpenCodeEntries(root, target) {
119
+ for (const configFile of openCodeConfigCandidates(root)) {
120
+ const config = await readJsonObjectIfPresent(configFile);
121
+ const pluginValue = config?.plugin;
122
+ if (!Array.isArray(pluginValue))
123
+ continue;
124
+ const nextPlugin = pluginValue.filter((entry) => {
125
+ if (entry === target.value)
126
+ return true;
127
+ return !isLitOpenCodeEntry(entry);
128
+ });
129
+ if (nextPlugin.length === pluginValue.length)
130
+ continue;
131
+ await fs.writeFile(configFile, JSON.stringify({ ...config, plugin: nextPlugin }, null, 2) + "\n");
132
+ }
133
+ }
27
134
  function describePluginMutation(config, target) {
28
135
  const pluginValue = config?.plugin;
29
136
  const pluginIsArray = Array.isArray(pluginValue);
@@ -106,22 +213,29 @@ function renderInstallReport(report) {
106
213
  export async function install(root, dryRun) {
107
214
  const metadata = await readPackageMetadata();
108
215
  const paths = createRuntimePaths(root);
109
- const before = await readJsonObjectIfPresent(paths.opencodeConfigFile);
110
- const target = pluginSpec(metadata);
111
- const mutation = describePluginMutation(before, target);
216
+ const useOpenCodeInstaller = shouldUseOpenCodeInstaller(root);
217
+ const configFile = useOpenCodeInstaller ? await openCodeManagedConfigFile(root) : paths.opencodeConfigFile;
218
+ const before = useOpenCodeInstaller ? null : await readJsonObjectIfPresent(paths.opencodeConfigFile);
219
+ const spec = pluginSpec(metadata);
220
+ const target = useOpenCodeInstaller ? await openCodeInstallTarget(metadata) : { value: spec, label: spec };
221
+ const mutation = describePluginMutation(before, target.value);
112
222
  const report = {
113
223
  dryRun,
114
- path: paths.opencodeConfigFile,
224
+ path: configFile,
115
225
  plugin: mutation.plugin,
116
226
  patch: mutation.patch,
117
227
  changed: mutation.changed,
118
- package: metadata
228
+ package: { name: metadata.name, version: metadata.version }
119
229
  };
120
230
  if (dryRun) {
121
231
  return { exitCode: 0, stdout: JSON.stringify(report, null, 2) };
122
232
  }
123
- if (mutation.changed) {
124
- const next = applyPluginMutation(before, mutation, target);
233
+ if (useOpenCodeInstaller) {
234
+ await removeStaleOpenCodeEntries(root, target);
235
+ await installWithOpenCode(target);
236
+ }
237
+ else if (mutation.changed) {
238
+ const next = applyPluginMutation(before, mutation, target.value);
125
239
  await fs.mkdir(path.dirname(paths.opencodeConfigFile), { recursive: true });
126
240
  await fs.writeFile(paths.opencodeConfigFile, JSON.stringify(next, null, 2) + "\n");
127
241
  }
package/dist/cli/json.js CHANGED
@@ -5,12 +5,13 @@ export function isRecord(value) {
5
5
  return typeof value === "object" && value !== null && !Array.isArray(value);
6
6
  }
7
7
  export async function readPackageMetadata() {
8
- const packagePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../package.json");
8
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
9
+ const packagePath = path.join(packageRoot, "package.json");
9
10
  const parsed = JSON.parse(await fs.readFile(packagePath, "utf8"));
10
11
  if (!isRecord(parsed) || typeof parsed.name !== "string" || typeof parsed.version !== "string") {
11
12
  throw new Error(`Malformed package metadata at ${packagePath}`);
12
13
  }
13
- return { name: parsed.name, version: parsed.version };
14
+ return { name: parsed.name, version: parsed.version, packageRoot };
14
15
  }
15
16
  export async function readJsonObjectIfPresent(filePath) {
16
17
  let raw;
@@ -6,6 +6,7 @@ export type CliResult = {
6
6
  export type PackageMetadata = {
7
7
  readonly name: string;
8
8
  readonly version: string;
9
+ readonly packageRoot: string;
9
10
  };
10
11
  export type ParsedArgs = {
11
12
  readonly command?: string;
@@ -45,5 +46,5 @@ export type InstallReport = {
45
46
  readonly plugin: PluginMutation["plugin"];
46
47
  readonly patch: readonly JsonPatchOperation[];
47
48
  readonly changed: boolean;
48
- readonly package: PackageMetadata;
49
+ readonly package: Pick<PackageMetadata, "name" | "version">;
49
50
  };
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: "Adds or updates a version-pinned litopencode entry in opencode.json with branded progress output; --dry-run prints the mutation only."
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
@@ -1,4 +1,4 @@
1
- import type { Hooks, PluginInput } from "@opencode-ai/plugin";
1
+ import litOpenCodePlugin from "./server.ts";
2
2
  export { appendLedgerEvent, createLitGoalOperations, initializeLitGoal, readLedgerEvents, recoverLedgerTemps, LedgerIoError, LedgerParseError, type JsonValue, type LedgerAppendResult, type LedgerEvent, type LedgerRecoveryReport, type LitGoalInitialization, type LitGoalOperations } from "./ledger.ts";
3
3
  export { litActivationBanner, litOpenCodeCommands } from "./commands.ts";
4
4
  export { createToolExecuteAfterHook, createToolExecuteBeforeHook } from "./hooks.ts";
@@ -7,9 +7,8 @@ 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
- export declare const server: (input?: PluginInput) => Promise<Hooks>;
11
- declare const pluginModule: {
10
+ export declare const pluginModule: {
12
11
  id: string;
13
- server: (input?: PluginInput) => Promise<Hooks>;
12
+ server: (input?: import("@opencode-ai/plugin").PluginInput) => Promise<import("@opencode-ai/plugin").Hooks>;
14
13
  };
15
- export default pluginModule;
14
+ export default litOpenCodePlugin;
package/dist/index.js CHANGED
@@ -1,10 +1,5 @@
1
- import { registerLitOpenCodeAgents } from "./agents.js";
2
- import { createCommandActivationHook, litActivationBanner, litOpenCodeCommands } from "./commands.js";
3
- import { loadConfig } from "./config.js";
4
- import { createToolExecuteAfterHook, createToolExecuteBeforeHook } from "./hooks.js";
1
+ import litOpenCodePlugin from "./server.js";
5
2
  export { appendLedgerEvent, createLitGoalOperations, initializeLitGoal, readLedgerEvents, recoverLedgerTemps, LedgerIoError, LedgerParseError } from "./ledger.js";
6
- import { createLogger } from "./logger.js";
7
- import { litOpenCodeTools } from "./tools.js";
8
3
  export { litActivationBanner, litOpenCodeCommands } from "./commands.js";
9
4
  export { createToolExecuteAfterHook, createToolExecuteBeforeHook } from "./hooks.js";
10
5
  export { applyLitOpenCodeToolAfterHook, applyLitOpenCodeToolBeforeHook } from "./tool-guards.js";
@@ -12,34 +7,8 @@ export { litOpenCodeTools, litTool, litworkTool } from "./tools.js";
12
7
  export { findLitOpenCodeFeature, litOpenCodeFeatures } from "./features.js";
13
8
  export { findLitOpenCodeRuntimeSkill, litOpenCodeRuntimeSkills } from "./skills.js";
14
9
  export const pluginId = "litopencode";
15
- export const server = async (input) => {
16
- const root = input?.worktree ?? input?.directory ?? ".";
17
- const loaded = await loadConfig(root);
18
- const logger = createLogger(loaded.paths);
19
- const hooks = {
20
- config: async (config) => {
21
- registerLitOpenCodeAgents(config);
22
- },
23
- tool: litOpenCodeTools,
24
- "command.execute.before": createCommandActivationHook(root),
25
- dispose: async () => {
26
- await logger.dispose();
27
- }
28
- };
29
- Object.defineProperties(hooks, {
30
- "tool.execute.before": {
31
- value: createToolExecuteBeforeHook(),
32
- enumerable: false
33
- },
34
- "tool.execute.after": {
35
- value: createToolExecuteAfterHook(),
36
- enumerable: false
37
- }
38
- });
39
- return hooks;
40
- };
41
- const pluginModule = {
10
+ export const pluginModule = {
42
11
  id: pluginId,
43
- server
12
+ server: litOpenCodePlugin
44
13
  };
45
- export default pluginModule;
14
+ export default litOpenCodePlugin;
@@ -0,0 +1,3 @@
1
+ import type { Hooks, PluginInput } from "@opencode-ai/plugin";
2
+ declare const litOpenCodePlugin: (input?: PluginInput) => Promise<Hooks>;
3
+ export default litOpenCodePlugin;
package/dist/server.js ADDED
@@ -0,0 +1,33 @@
1
+ import { registerLitOpenCodeAgents } from "./agents.js";
2
+ import { createCommandActivationHook } from "./commands.js";
3
+ import { loadConfig } from "./config.js";
4
+ import { createToolExecuteAfterHook, createToolExecuteBeforeHook } from "./hooks.js";
5
+ import { createLogger } from "./logger.js";
6
+ import { litOpenCodeTools } from "./tools.js";
7
+ const litOpenCodePlugin = async (input) => {
8
+ const root = input?.worktree ?? input?.directory ?? ".";
9
+ const loaded = await loadConfig(root);
10
+ const logger = createLogger(loaded.paths);
11
+ const hooks = {
12
+ config: async (config) => {
13
+ registerLitOpenCodeAgents(config);
14
+ },
15
+ tool: litOpenCodeTools,
16
+ "command.execute.before": createCommandActivationHook(root),
17
+ dispose: async () => {
18
+ await logger.dispose();
19
+ }
20
+ };
21
+ Object.defineProperties(hooks, {
22
+ "tool.execute.before": {
23
+ value: createToolExecuteBeforeHook(),
24
+ enumerable: false
25
+ },
26
+ "tool.execute.after": {
27
+ value: createToolExecuteAfterHook(),
28
+ enumerable: false
29
+ }
30
+ });
31
+ return hooks;
32
+ };
33
+ export default litOpenCodePlugin;
package/dist/skills.js CHANGED
@@ -39,7 +39,8 @@ 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 writes only the version-pinned OpenCode opencode.json plugin entry.",
42
+ "Default installation delegates to the OpenCode plugin installer; custom roots write only the version-pinned opencode.json plugin entry.",
43
+ "Local checkout installs hand OpenCode the package path so unpublished versions can be tested before npm publish.",
43
44
  "Malformed config fails closed with a typed config error."
44
45
  ]
45
46
  },
package/docs/migration.md CHANGED
@@ -11,9 +11,9 @@ This guide describes the supported migration shape for moving an OpenCode workfl
11
11
  ## Environment And Config
12
12
 
13
13
  - Use the `LITOPENCODE_` prefix for LitOpenCode-owned environment variables.
14
- - Keep OpenCode plugin configuration in `opencode.json`.
14
+ - Keep default OpenCode plugin configuration in OpenCode's managed `opencode.jsonc`; custom-root LitOpenCode previews use `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 a version-pinned entry such as `litopencode@0.0.2` to the default OpenCode config at `~/.config/opencode/opencode.json`.
16
+ - Use `npx litopencode install` to delegate default setup to `opencode plugin <target> --global --force`; published installs use `litopencode@0.0.4`, while local checkout installs use the package path.
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.2.tgz -C "$tmp"
44
+ tar -xzf "$tmp"/litopencode-0.0.4.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.2.tgz
76
+ npm install "$tmp"/litopencode-0.0.4.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
@@ -89,12 +89,12 @@ Expected results:
89
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
90
90
  - package import prints `litopencode`
91
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
92
+ - write-enabled custom-root install creates or updates `opencode.json` without echoing unrelated config secrets
93
93
  - temporary project cleanup removes the probe directory
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 default plugin module\'s `server()` function, invoke the config hook, command hook, `lit` and `litwork` tools, and before/after tool guard hooks. Expected results:
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.2",
3
+ "version": "0.0.4",
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
- ".": "./dist/index.js"
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./server": {
16
+ "types": "./dist/server.d.ts",
17
+ "import": "./dist/server.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 or update the version-pinned OpenCode plugin entry with a bounded branded terminal flow.
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.