@codyswann/lisa 2.150.1 → 2.152.0
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/dist/codex/agent-installer.d.ts +5 -1
- package/dist/codex/agent-installer.d.ts.map +1 -1
- package/dist/codex/agent-installer.js +7 -2
- package/dist/codex/agent-installer.js.map +1 -1
- package/dist/core/lisa-skill-sources.d.ts +27 -1
- package/dist/core/lisa-skill-sources.d.ts.map +1 -1
- package/dist/core/lisa-skill-sources.js +33 -4
- package/dist/core/lisa-skill-sources.js.map +1 -1
- package/dist/core/lisa.d.ts +11 -7
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +41 -14
- package/dist/core/lisa.js.map +1 -1
- package/dist/opencode/agent-installer.d.ts +52 -0
- package/dist/opencode/agent-installer.d.ts.map +1 -0
- package/dist/opencode/agent-installer.js +120 -0
- package/dist/opencode/agent-installer.js.map +1 -0
- package/dist/opencode/agent-transformer.d.ts +52 -0
- package/dist/opencode/agent-transformer.d.ts.map +1 -0
- package/dist/opencode/agent-transformer.js +133 -0
- package/dist/opencode/agent-transformer.js.map +1 -0
- package/dist/opencode/command-installer.d.ts +47 -0
- package/dist/opencode/command-installer.d.ts.map +1 -0
- package/dist/opencode/command-installer.js +112 -0
- package/dist/opencode/command-installer.js.map +1 -0
- package/dist/opencode/command-transformer.d.ts +10 -0
- package/dist/opencode/command-transformer.d.ts.map +1 -0
- package/dist/opencode/command-transformer.js +74 -0
- package/dist/opencode/command-transformer.js.map +1 -0
- package/dist/opencode/hooks-installer.d.ts +54 -0
- package/dist/opencode/hooks-installer.d.ts.map +1 -0
- package/dist/opencode/hooks-installer.js +300 -0
- package/dist/opencode/hooks-installer.js.map +1 -0
- package/dist/opencode/plugin-templates/lisa-block-migration-edits.ts +44 -0
- package/dist/opencode/plugin-templates/lisa-block-suppress-directives.ts +57 -0
- package/dist/opencode/plugin-templates/lisa-lint-on-edit.ts +53 -0
- package/dist/opencode/plugin-templates/lisa-rubocop-on-edit.ts +54 -0
- package/dist/opencode/plugin-templates/lisa-session-bootstrap.ts +76 -0
- package/dist/opencode/plugin-templates/lisa-sg-scan-on-edit.ts +50 -0
- package/package.json +2 -2
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/scripts/copy-opencode-plugin-templates.mjs +29 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install Lisa-bundled agents into a host project's `.opencode/agents/`.
|
|
3
|
+
*
|
|
4
|
+
* Pipeline per agent:
|
|
5
|
+
* 1. Read the source `.md` from the Lisa plugin (discovered via the shared
|
|
6
|
+
* `discoverLisaAgents` walker, identical to the Codex overlay).
|
|
7
|
+
* 2. Transform Markdown + YAML frontmatter → OpenCode subagent Markdown
|
|
8
|
+
* (frontmatter `description` + `mode: subagent`; body is the prompt). This
|
|
9
|
+
* is a near-passthrough — OpenCode agents are Markdown like Claude's, so it
|
|
10
|
+
* is simpler than the Codex TOML transform.
|
|
11
|
+
* 3. Write the result to `.opencode/agents/lisa-<id>.md`.
|
|
12
|
+
*
|
|
13
|
+
* The `lisa-` filename prefix is the ownership boundary: OpenCode derives an
|
|
14
|
+
* agent's name from its filename and reads `.opencode/agents/` flat (verified on
|
|
15
|
+
* opencode 1.16.2 — `opencode agent list` surfaces files placed there), so a
|
|
16
|
+
* prefix — rather than a `lisa/` subdir as used for skills — is what keeps Lisa
|
|
17
|
+
* agents from colliding with host-authored agents and gives stale cleanup a safe
|
|
18
|
+
* scope. Host agents (any file NOT starting with `lisa-`) are never touched.
|
|
19
|
+
*
|
|
20
|
+
* Stale agents (in the previous manifest but no longer shipped by Lisa) are
|
|
21
|
+
* deleted so renames in the source tree don't leave orphans behind.
|
|
22
|
+
* @module opencode/agent-installer
|
|
23
|
+
*/
|
|
24
|
+
import * as fse from "fs-extra";
|
|
25
|
+
import { readFile, unlink, writeFile } from "node:fs/promises";
|
|
26
|
+
import * as path from "node:path";
|
|
27
|
+
import { discoverLisaAgents, } from "../codex/agent-installer.js";
|
|
28
|
+
import { isHarnessVariantPlugin } from "../core/lisa-skill-sources.js";
|
|
29
|
+
import { transformAgentMarkdownToOpencode } from "./agent-transformer.js";
|
|
30
|
+
import { OPENCODE_CONFIG_DIR } from "./manifest.js";
|
|
31
|
+
export { discoverLisaAgents, } from "../codex/agent-installer.js";
|
|
32
|
+
/** Subdirectory inside `.opencode/` where Lisa-owned agents live */
|
|
33
|
+
export const LISA_AGENTS_SUBDIR = "agents";
|
|
34
|
+
/**
|
|
35
|
+
* Filename prefix marking an agent file as Lisa-owned. OpenCode reads
|
|
36
|
+
* `.opencode/agents/` flat, so the prefix (not a subdirectory) is the ownership
|
|
37
|
+
* boundary used for collision avoidance and stale cleanup.
|
|
38
|
+
*/
|
|
39
|
+
export const LISA_AGENT_FILE_PREFIX = "lisa-";
|
|
40
|
+
/**
|
|
41
|
+
* Install all discovered agents into `<destDir>/.opencode/agents/`.
|
|
42
|
+
*
|
|
43
|
+
* Returns a result describing what was written and what stale files were
|
|
44
|
+
* deleted, so the caller can update the Lisa-managed manifest.
|
|
45
|
+
* @param sources - Agent sources from discoverLisaAgents
|
|
46
|
+
* @param destDir - Absolute path to the destination project root
|
|
47
|
+
* @param previousManagedFiles - Files Lisa managed on the previous run
|
|
48
|
+
* (relative to `.opencode/`); used to detect stale agents to delete
|
|
49
|
+
* @returns Result describing installed/deleted/managedFiles
|
|
50
|
+
*/
|
|
51
|
+
export async function installAgents(sources, destDir, previousManagedFiles) {
|
|
52
|
+
const agentsDir = path.join(destDir, OPENCODE_CONFIG_DIR, LISA_AGENTS_SUBDIR);
|
|
53
|
+
await fse.ensureDir(agentsDir);
|
|
54
|
+
const installed = await Promise.all(sources.map(source => installSingleAgent(source, agentsDir)));
|
|
55
|
+
const managedFiles = installed.map(agent => agent.relativePath);
|
|
56
|
+
const deleted = await deleteStaleAgents(previousManagedFiles, managedFiles, destDir);
|
|
57
|
+
return {
|
|
58
|
+
installed: Object.freeze(installed),
|
|
59
|
+
deleted: Object.freeze(deleted),
|
|
60
|
+
managedFiles: Object.freeze(managedFiles),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Transform + write a single agent file.
|
|
65
|
+
* @param source - Discovered agent source (id, plugin, source path)
|
|
66
|
+
* @param agentsDir - Absolute path to `.opencode/agents/` in the host project
|
|
67
|
+
* @returns Result describing the installed file
|
|
68
|
+
*/
|
|
69
|
+
async function installSingleAgent(source, agentsDir) {
|
|
70
|
+
const sourceContent = await readFile(source.sourcePath, "utf8");
|
|
71
|
+
const markdown = transformAgentMarkdownToOpencode(sourceContent);
|
|
72
|
+
const filename = `${LISA_AGENT_FILE_PREFIX}${source.id}.md`;
|
|
73
|
+
await writeFile(path.join(agentsDir, filename), markdown, "utf8");
|
|
74
|
+
return {
|
|
75
|
+
id: source.id,
|
|
76
|
+
relativePath: path.join(LISA_AGENTS_SUBDIR, filename),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Delete files that were Lisa-managed last run but aren't shipped this run.
|
|
81
|
+
* Only deletes files inside `.opencode/agents/` whose basename carries the
|
|
82
|
+
* `lisa-` prefix, so host-authored agents are never removed even if the
|
|
83
|
+
* previous manifest somehow references them.
|
|
84
|
+
* @param previousManagedFiles - Files Lisa managed on the previous run
|
|
85
|
+
* (relative to `.opencode/`)
|
|
86
|
+
* @param currentManagedFiles - Files Lisa is shipping this run (relative
|
|
87
|
+
* to `.opencode/`)
|
|
88
|
+
* @param destDir - Absolute path to the host project root
|
|
89
|
+
* @returns The list of relative paths that were deleted
|
|
90
|
+
*/
|
|
91
|
+
async function deleteStaleAgents(previousManagedFiles, currentManagedFiles, destDir) {
|
|
92
|
+
const currentSet = new Set(currentManagedFiles);
|
|
93
|
+
const lisaAgentPrefix = `${LISA_AGENTS_SUBDIR}${path.sep}${LISA_AGENT_FILE_PREFIX}`;
|
|
94
|
+
const stale = previousManagedFiles.filter(file => !currentSet.has(file) && file.startsWith(lisaAgentPrefix));
|
|
95
|
+
await Promise.all(stale.map(async (file) => {
|
|
96
|
+
const absPath = path.join(destDir, OPENCODE_CONFIG_DIR, file);
|
|
97
|
+
if (await fse.pathExists(absPath)) {
|
|
98
|
+
await unlink(absPath);
|
|
99
|
+
}
|
|
100
|
+
}));
|
|
101
|
+
return Object.freeze(stale);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Convenience one-shot: discover Lisa agents from `lisaDir` and install them.
|
|
105
|
+
*
|
|
106
|
+
* Discovery is restricted to canonical plugins — the per-harness fanout variants
|
|
107
|
+
* (`*-agy`, `*-copilot`, `*-cursor`) are skipped. The copilot variants rename
|
|
108
|
+
* agents to `*.agent.md`, which would otherwise slip past id-dedup and ship a
|
|
109
|
+
* duplicate `lisa-<name>.agent` for every agent; the cursor/agy variants are
|
|
110
|
+
* just reformatted copies of the base agents.
|
|
111
|
+
* @param lisaDir - Absolute path to the Lisa repo / installed package
|
|
112
|
+
* @param destDir - Absolute path to the destination project root
|
|
113
|
+
* @param previousManagedFiles - Files Lisa managed on the previous run
|
|
114
|
+
* @returns Result describing installed/deleted/managedFiles
|
|
115
|
+
*/
|
|
116
|
+
export async function discoverAndInstallAgents(lisaDir, destDir, previousManagedFiles) {
|
|
117
|
+
const sources = await discoverLisaAgents(lisaDir, name => !isHarnessVariantPlugin(name));
|
|
118
|
+
return installAgents(sources, destDir, previousManagedFiles);
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=agent-installer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-installer.js","sourceRoot":"","sources":["../../src/opencode/agent-installer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transform a Claude Code-style agent definition (Markdown with YAML
|
|
3
|
+
* frontmatter) into an OpenCode subagent Markdown file.
|
|
4
|
+
*
|
|
5
|
+
* Claude Code agent shape:
|
|
6
|
+
* ```
|
|
7
|
+
* ---
|
|
8
|
+
* name: bug-fixer
|
|
9
|
+
* description: ...
|
|
10
|
+
* tools: Read, Bash # optional; preserved as compatibility context
|
|
11
|
+
* model: sonnet # optional; preserved as compatibility context
|
|
12
|
+
* skills: [skill-a, skill-b] # optional; preserved as context
|
|
13
|
+
* ---
|
|
14
|
+
* # Body becomes the agent prompt
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* OpenCode agent shape (per https://opencode.ai/docs/agents):
|
|
18
|
+
* ```
|
|
19
|
+
* ---
|
|
20
|
+
* description: ...
|
|
21
|
+
* mode: subagent
|
|
22
|
+
* ---
|
|
23
|
+
* # Body is the agent prompt
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* OpenCode agents are Markdown like Claude's, so — unlike the Codex TOML
|
|
27
|
+
* transform — this is a near-passthrough: the body is preserved verbatim and
|
|
28
|
+
* the only required reshape is the frontmatter. The agent NAME is derived by
|
|
29
|
+
* OpenCode from the destination filename (not a frontmatter field), so the
|
|
30
|
+
* installer owns naming; this transformer only owns the file contents.
|
|
31
|
+
*
|
|
32
|
+
* Claude-only metadata (`tools`, `model`, `skills`) is NOT emitted as OpenCode
|
|
33
|
+
* frontmatter — `model` expects a `provider/model` string that a Claude alias
|
|
34
|
+
* (`sonnet`) would violate, and `tools`/`skills` have no 1:1 frontmatter slot.
|
|
35
|
+
* Instead that metadata is preserved as a context block in the body so nothing
|
|
36
|
+
* is silently dropped, mirroring the Codex transformer's compatibility blocks.
|
|
37
|
+
* @module opencode/agent-transformer
|
|
38
|
+
*/
|
|
39
|
+
import { type ParsedAgent } from "../codex/agent-transformer.js";
|
|
40
|
+
/**
|
|
41
|
+
* Transform a parsed Lisa agent into OpenCode subagent Markdown.
|
|
42
|
+
* @param parsed - Output of parseAgentMarkdown (frontmatter + body)
|
|
43
|
+
* @returns OpenCode agent Markdown (frontmatter + prompt body)
|
|
44
|
+
*/
|
|
45
|
+
export declare function transformAgentToOpencodeMarkdown(parsed: ParsedAgent): string;
|
|
46
|
+
/**
|
|
47
|
+
* One-shot helper: parse + transform.
|
|
48
|
+
* @param source - Raw contents of an agent .md file
|
|
49
|
+
* @returns OpenCode agent Markdown
|
|
50
|
+
*/
|
|
51
|
+
export declare function transformAgentMarkdownToOpencode(source: string): string;
|
|
52
|
+
//# sourceMappingURL=agent-transformer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-transformer.d.ts","sourceRoot":"","sources":["../../src/opencode/agent-transformer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EACL,KAAK,WAAW,EAEjB,MAAM,+BAA+B,CAAC;AAEvC;;;;GAIG;AACH,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAU5E;AAED;;;;GAIG;AACH,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvE"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transform a Claude Code-style agent definition (Markdown with YAML
|
|
3
|
+
* frontmatter) into an OpenCode subagent Markdown file.
|
|
4
|
+
*
|
|
5
|
+
* Claude Code agent shape:
|
|
6
|
+
* ```
|
|
7
|
+
* ---
|
|
8
|
+
* name: bug-fixer
|
|
9
|
+
* description: ...
|
|
10
|
+
* tools: Read, Bash # optional; preserved as compatibility context
|
|
11
|
+
* model: sonnet # optional; preserved as compatibility context
|
|
12
|
+
* skills: [skill-a, skill-b] # optional; preserved as context
|
|
13
|
+
* ---
|
|
14
|
+
* # Body becomes the agent prompt
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* OpenCode agent shape (per https://opencode.ai/docs/agents):
|
|
18
|
+
* ```
|
|
19
|
+
* ---
|
|
20
|
+
* description: ...
|
|
21
|
+
* mode: subagent
|
|
22
|
+
* ---
|
|
23
|
+
* # Body is the agent prompt
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* OpenCode agents are Markdown like Claude's, so — unlike the Codex TOML
|
|
27
|
+
* transform — this is a near-passthrough: the body is preserved verbatim and
|
|
28
|
+
* the only required reshape is the frontmatter. The agent NAME is derived by
|
|
29
|
+
* OpenCode from the destination filename (not a frontmatter field), so the
|
|
30
|
+
* installer owns naming; this transformer only owns the file contents.
|
|
31
|
+
*
|
|
32
|
+
* Claude-only metadata (`tools`, `model`, `skills`) is NOT emitted as OpenCode
|
|
33
|
+
* frontmatter — `model` expects a `provider/model` string that a Claude alias
|
|
34
|
+
* (`sonnet`) would violate, and `tools`/`skills` have no 1:1 frontmatter slot.
|
|
35
|
+
* Instead that metadata is preserved as a context block in the body so nothing
|
|
36
|
+
* is silently dropped, mirroring the Codex transformer's compatibility blocks.
|
|
37
|
+
* @module opencode/agent-transformer
|
|
38
|
+
*/
|
|
39
|
+
import { parseAgentMarkdown, } from "../codex/agent-transformer.js";
|
|
40
|
+
/**
|
|
41
|
+
* Transform a parsed Lisa agent into OpenCode subagent Markdown.
|
|
42
|
+
* @param parsed - Output of parseAgentMarkdown (frontmatter + body)
|
|
43
|
+
* @returns OpenCode agent Markdown (frontmatter + prompt body)
|
|
44
|
+
*/
|
|
45
|
+
export function transformAgentToOpencodeMarkdown(parsed) {
|
|
46
|
+
const frontmatter = [
|
|
47
|
+
"---",
|
|
48
|
+
`description: ${jsonScalar(parsed.frontmatter.description)}`,
|
|
49
|
+
"mode: subagent",
|
|
50
|
+
"---",
|
|
51
|
+
"",
|
|
52
|
+
].join("\n");
|
|
53
|
+
const body = composeAgentBody(parsed);
|
|
54
|
+
return `${frontmatter}${body}\n`;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* One-shot helper: parse + transform.
|
|
58
|
+
* @param source - Raw contents of an agent .md file
|
|
59
|
+
* @returns OpenCode agent Markdown
|
|
60
|
+
*/
|
|
61
|
+
export function transformAgentMarkdownToOpencode(source) {
|
|
62
|
+
return transformAgentToOpencodeMarkdown(parseAgentMarkdown(source));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Build the OpenCode agent body. The original Claude body is preserved
|
|
66
|
+
* verbatim; any Claude-only metadata that has no OpenCode frontmatter slot is
|
|
67
|
+
* prepended as a context block so the information survives the transform.
|
|
68
|
+
* @param parsed - Output of parseAgentMarkdown for the source agent file
|
|
69
|
+
* @returns The composed Markdown body
|
|
70
|
+
*/
|
|
71
|
+
function composeAgentBody(parsed) {
|
|
72
|
+
const compatibilityBlock = formatCompatibilityBlock(parsed);
|
|
73
|
+
const skillsBlock = formatSkillsBlock(parsed);
|
|
74
|
+
return [compatibilityBlock, skillsBlock, parsed.body.trimEnd()]
|
|
75
|
+
.filter(block => block.length > 0)
|
|
76
|
+
.join("\n\n");
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Preserve Claude-only `model`/`tools` metadata as an OpenCode context block.
|
|
80
|
+
* OpenCode governs model and tool access through its own runtime/config, so
|
|
81
|
+
* these are advisory notes rather than enforced settings.
|
|
82
|
+
* @param parsed - Parsed source agent
|
|
83
|
+
* @returns Markdown compatibility block, or an empty string when unnecessary
|
|
84
|
+
*/
|
|
85
|
+
function formatCompatibilityBlock(parsed) {
|
|
86
|
+
const { model, tools } = parsed.frontmatter;
|
|
87
|
+
const modelLines = model === undefined
|
|
88
|
+
? []
|
|
89
|
+
: [
|
|
90
|
+
`- Claude requested model: \`${model}\`. OpenCode uses the active session model unless the host sets \`model\` on this agent.`,
|
|
91
|
+
];
|
|
92
|
+
const toolLines = tools === undefined
|
|
93
|
+
? []
|
|
94
|
+
: [
|
|
95
|
+
`- Claude allowed tools: \`${tools}\`. OpenCode tool access is governed by the active OpenCode runtime, \`permission\` settings, and project policy.`,
|
|
96
|
+
];
|
|
97
|
+
const lines = [...modelLines, ...toolLines];
|
|
98
|
+
if (lines.length === 0) {
|
|
99
|
+
return "";
|
|
100
|
+
}
|
|
101
|
+
return ["## Claude Agent Compatibility", "", ...lines].join("\n");
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Preserve the source agent's `skills:` list as a context block. OpenCode
|
|
105
|
+
* discovers these skills natively from `.opencode/skills/lisa/`, so this block
|
|
106
|
+
* just documents which ones the agent is expected to use.
|
|
107
|
+
* @param parsed - Parsed source agent
|
|
108
|
+
* @returns Markdown skills block, or an empty string when no skills are listed
|
|
109
|
+
*/
|
|
110
|
+
function formatSkillsBlock(parsed) {
|
|
111
|
+
const skills = parsed.frontmatter.skills;
|
|
112
|
+
if (skills === undefined || skills.length === 0) {
|
|
113
|
+
return "";
|
|
114
|
+
}
|
|
115
|
+
return [
|
|
116
|
+
"## Available Lisa Skills",
|
|
117
|
+
"",
|
|
118
|
+
"This agent operates in a Lisa-managed OpenCode environment with access to the following skills:",
|
|
119
|
+
"",
|
|
120
|
+
...skills.map(skill => `- ${skill}`),
|
|
121
|
+
].join("\n");
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Encode a string as a YAML-safe flow scalar. A JSON-encoded string is always a
|
|
125
|
+
* valid YAML double-quoted scalar, so this safely handles descriptions that
|
|
126
|
+
* contain colons, quotes, or other YAML-significant characters.
|
|
127
|
+
* @param value - The raw string to encode
|
|
128
|
+
* @returns A double-quoted YAML scalar
|
|
129
|
+
*/
|
|
130
|
+
function jsonScalar(value) {
|
|
131
|
+
return JSON.stringify(value);
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=agent-transformer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-transformer.js","sourceRoot":"","sources":["../../src/opencode/agent-transformer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EAEL,kBAAkB,GACnB,MAAM,+BAA+B,CAAC;AAEvC;;;;GAIG;AACH,MAAM,UAAU,gCAAgC,CAAC,MAAmB;IAClE,MAAM,WAAW,GAAG;QAClB,KAAK;QACL,gBAAgB,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE;QAC5D,gBAAgB;QAChB,KAAK;QACL,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACtC,OAAO,GAAG,WAAW,GAAG,IAAI,IAAI,CAAC;AACnC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gCAAgC,CAAC,MAAc;IAC7D,OAAO,gCAAgC,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;GAMG;AACH,SAAS,gBAAgB,CAAC,MAAmB;IAC3C,MAAM,kBAAkB,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAC5D,MAAM,WAAW,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC9C,OAAO,CAAC,kBAAkB,EAAE,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;SAC5D,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;SACjC,IAAI,CAAC,MAAM,CAAC,CAAC;AAClB,CAAC;AAED;;;;;;GAMG;AACH,SAAS,wBAAwB,CAAC,MAAmB;IACnD,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC;IAC5C,MAAM,UAAU,GACd,KAAK,KAAK,SAAS;QACjB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC;YACE,+BAA+B,KAAK,0FAA0F;SAC/H,CAAC;IACR,MAAM,SAAS,GACb,KAAK,KAAK,SAAS;QACjB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC;YACE,6BAA6B,KAAK,mHAAmH;SACtJ,CAAC;IACR,MAAM,KAAK,GAAG,CAAC,GAAG,UAAU,EAAE,GAAG,SAAS,CAAC,CAAC;IAC5C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,CAAC,+BAA+B,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACpE,CAAC;AAED;;;;;;GAMG;AACH,SAAS,iBAAiB,CAAC,MAAmB;IAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC;IACzC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO;QACL,0BAA0B;QAC1B,EAAE;QACF,iGAAiG;QACjG,EAAE;QACF,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC;KACrC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type LisaCommandSource } from "../core/lisa-skill-sources.js";
|
|
2
|
+
export { type LisaCommandSource, discoverLisaCommands, } from "../core/lisa-skill-sources.js";
|
|
3
|
+
/** Subdirectory inside `.opencode/` where Lisa-owned commands live */
|
|
4
|
+
export declare const LISA_COMMANDS_SUBDIR = "commands";
|
|
5
|
+
/**
|
|
6
|
+
* Filename prefix marking a command file as Lisa-owned. The shared command
|
|
7
|
+
* discovery already names every command `lisa-<...>`, so this matches the
|
|
8
|
+
* basename of every file Lisa writes and scopes stale cleanup safely.
|
|
9
|
+
*/
|
|
10
|
+
export declare const LISA_COMMAND_FILE_PREFIX = "lisa-";
|
|
11
|
+
/** Result of installing one command */
|
|
12
|
+
export interface InstalledCommand {
|
|
13
|
+
/** Command name (the `lisa-` prefixed, dash-joined skill name) */
|
|
14
|
+
readonly name: string;
|
|
15
|
+
/** Path written, relative to the destination project's `.opencode/` directory */
|
|
16
|
+
readonly relativePath: string;
|
|
17
|
+
}
|
|
18
|
+
/** Aggregated result of an install pass */
|
|
19
|
+
export interface CommandInstallResult {
|
|
20
|
+
readonly installed: readonly InstalledCommand[];
|
|
21
|
+
/** Stale command files deleted from `.opencode/commands/` (relative to `.opencode/`) */
|
|
22
|
+
readonly deleted: readonly string[];
|
|
23
|
+
/** Files written, relative to `.opencode/`. Used to update the manifest. */
|
|
24
|
+
readonly managedFiles: readonly string[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Install all discovered commands into `<destDir>/.opencode/commands/`.
|
|
28
|
+
* @param sources - Command sources from discoverLisaCommands
|
|
29
|
+
* @param destDir - Absolute path to the destination project root
|
|
30
|
+
* @param previousManagedFiles - Files Lisa managed on the previous run
|
|
31
|
+
* (relative to `.opencode/`); used to detect stale commands to delete
|
|
32
|
+
* @returns Result describing installed/deleted/managedFiles
|
|
33
|
+
*/
|
|
34
|
+
export declare function installCommands(sources: readonly LisaCommandSource[], destDir: string, previousManagedFiles: readonly string[]): Promise<CommandInstallResult>;
|
|
35
|
+
/**
|
|
36
|
+
* Convenience one-shot: discover Lisa commands from `lisaDir` and install them.
|
|
37
|
+
*
|
|
38
|
+
* Discovery is restricted to canonical plugins — the per-harness fanout variants
|
|
39
|
+
* (`*-agy`, `*-copilot`, `*-cursor`) are skipped so a reformatted variant copy
|
|
40
|
+
* of a command body never wins the last-wins dedup over the canonical source.
|
|
41
|
+
* @param lisaDir - Absolute path to the Lisa repo / installed package
|
|
42
|
+
* @param destDir - Absolute path to the destination project root
|
|
43
|
+
* @param previousManagedFiles - Files Lisa managed on the previous run
|
|
44
|
+
* @returns Result describing installed/deleted/managedFiles
|
|
45
|
+
*/
|
|
46
|
+
export declare function discoverAndInstallCommands(lisaDir: string, destDir: string, previousManagedFiles: readonly string[]): Promise<CommandInstallResult>;
|
|
47
|
+
//# sourceMappingURL=command-installer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-installer.d.ts","sourceRoot":"","sources":["../../src/opencode/command-installer.ts"],"names":[],"mappings":"AAyBA,OAAO,EACL,KAAK,iBAAiB,EAGvB,MAAM,+BAA+B,CAAC;AAIvC,OAAO,EACL,KAAK,iBAAiB,EACtB,oBAAoB,GACrB,MAAM,+BAA+B,CAAC;AAEvC,sEAAsE;AACtE,eAAO,MAAM,oBAAoB,aAAa,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,UAAU,CAAC;AAEhD,uCAAuC;AACvC,MAAM,WAAW,gBAAgB;IAC/B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,2CAA2C;AAC3C,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAChD,wFAAwF;IACxF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,4EAA4E;IAC5E,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,SAAS,iBAAiB,EAAE,EACrC,OAAO,EAAE,MAAM,EACf,oBAAoB,EAAE,SAAS,MAAM,EAAE,GACtC,OAAO,CAAC,oBAAoB,CAAC,CA0B/B;AAyDD;;;;;;;;;;GAUG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,oBAAoB,EAAE,SAAS,MAAM,EAAE,GACtC,OAAO,CAAC,oBAAoB,CAAC,CAM/B"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install Lisa commands as native OpenCode custom commands in
|
|
3
|
+
* `.opencode/commands/`.
|
|
4
|
+
*
|
|
5
|
+
* Pipeline per command:
|
|
6
|
+
* 1. Discover `plugins/<plugin>/commands/**\/*.md` via the shared
|
|
7
|
+
* `discoverLisaCommands` walker (identical discovery to the skills path).
|
|
8
|
+
* 2. Transform the command Markdown → OpenCode command Markdown (frontmatter
|
|
9
|
+
* `description`; body preserves `$ARGUMENTS` for native substitution).
|
|
10
|
+
* 3. Write the result to `.opencode/commands/<lisa-name>.md`, where the
|
|
11
|
+
* filename is the shared dash-joined `lisa-` prefixed name (e.g.
|
|
12
|
+
* `lisa-git-commit`), so the command surfaces as `/lisa-git-commit`.
|
|
13
|
+
*
|
|
14
|
+
* This is ADDITIVE and native-fidelity: Lisa commands already work on OpenCode
|
|
15
|
+
* as `lisa-` prefixed skills, so the lower-priority value here is exposing them
|
|
16
|
+
* through OpenCode's first-class command surface with native argument handling.
|
|
17
|
+
*
|
|
18
|
+
* The `lisa-` filename prefix (already baked into the shared command skill name)
|
|
19
|
+
* is the ownership boundary — host-authored commands (any file NOT starting with
|
|
20
|
+
* `lisa-`) are never touched, and stale cleanup is scoped to `lisa-` files only.
|
|
21
|
+
* @module opencode/command-installer
|
|
22
|
+
*/
|
|
23
|
+
import * as fse from "fs-extra";
|
|
24
|
+
import { readFile, unlink, writeFile } from "node:fs/promises";
|
|
25
|
+
import * as path from "node:path";
|
|
26
|
+
import { discoverLisaCommands, isHarnessVariantPlugin, } from "../core/lisa-skill-sources.js";
|
|
27
|
+
import { transformCommandToOpencode } from "./command-transformer.js";
|
|
28
|
+
import { OPENCODE_CONFIG_DIR } from "./manifest.js";
|
|
29
|
+
export { discoverLisaCommands, } from "../core/lisa-skill-sources.js";
|
|
30
|
+
/** Subdirectory inside `.opencode/` where Lisa-owned commands live */
|
|
31
|
+
export const LISA_COMMANDS_SUBDIR = "commands";
|
|
32
|
+
/**
|
|
33
|
+
* Filename prefix marking a command file as Lisa-owned. The shared command
|
|
34
|
+
* discovery already names every command `lisa-<...>`, so this matches the
|
|
35
|
+
* basename of every file Lisa writes and scopes stale cleanup safely.
|
|
36
|
+
*/
|
|
37
|
+
export const LISA_COMMAND_FILE_PREFIX = "lisa-";
|
|
38
|
+
/**
|
|
39
|
+
* Install all discovered commands into `<destDir>/.opencode/commands/`.
|
|
40
|
+
* @param sources - Command sources from discoverLisaCommands
|
|
41
|
+
* @param destDir - Absolute path to the destination project root
|
|
42
|
+
* @param previousManagedFiles - Files Lisa managed on the previous run
|
|
43
|
+
* (relative to `.opencode/`); used to detect stale commands to delete
|
|
44
|
+
* @returns Result describing installed/deleted/managedFiles
|
|
45
|
+
*/
|
|
46
|
+
export async function installCommands(sources, destDir, previousManagedFiles) {
|
|
47
|
+
const commandsDir = path.join(destDir, OPENCODE_CONFIG_DIR, LISA_COMMANDS_SUBDIR);
|
|
48
|
+
await fse.ensureDir(commandsDir);
|
|
49
|
+
const installed = await Promise.all(sources.map(source => installSingleCommand(source, commandsDir)));
|
|
50
|
+
const managedFiles = installed.map(command => command.relativePath);
|
|
51
|
+
const deleted = await deleteStaleCommands(previousManagedFiles, managedFiles, destDir);
|
|
52
|
+
return {
|
|
53
|
+
installed: Object.freeze(installed),
|
|
54
|
+
deleted: Object.freeze(deleted),
|
|
55
|
+
managedFiles: Object.freeze(managedFiles),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Transform + write a single command file.
|
|
60
|
+
* @param source - Discovered command source (skill name, display name, path)
|
|
61
|
+
* @param commandsDir - Absolute path to `.opencode/commands/` in the host
|
|
62
|
+
* @returns Result describing the installed file
|
|
63
|
+
*/
|
|
64
|
+
async function installSingleCommand(source, commandsDir) {
|
|
65
|
+
const sourceContent = await readFile(source.sourcePath, "utf8");
|
|
66
|
+
const markdown = transformCommandToOpencode(sourceContent, source.displayName);
|
|
67
|
+
const filename = `${source.skillName}.md`;
|
|
68
|
+
await writeFile(path.join(commandsDir, filename), markdown, "utf8");
|
|
69
|
+
return {
|
|
70
|
+
name: source.skillName,
|
|
71
|
+
relativePath: path.join(LISA_COMMANDS_SUBDIR, filename),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Delete files that were Lisa-managed last run but aren't shipped this run.
|
|
76
|
+
* Only deletes files inside `.opencode/commands/` whose basename carries the
|
|
77
|
+
* `lisa-` prefix, so host-authored commands are never removed.
|
|
78
|
+
* @param previousManagedFiles - Files Lisa managed on the previous run
|
|
79
|
+
* (relative to `.opencode/`)
|
|
80
|
+
* @param currentManagedFiles - Files Lisa is shipping this run (relative
|
|
81
|
+
* to `.opencode/`)
|
|
82
|
+
* @param destDir - Absolute path to the host project root
|
|
83
|
+
* @returns The list of relative paths that were deleted
|
|
84
|
+
*/
|
|
85
|
+
async function deleteStaleCommands(previousManagedFiles, currentManagedFiles, destDir) {
|
|
86
|
+
const currentSet = new Set(currentManagedFiles);
|
|
87
|
+
const lisaCommandPrefix = `${LISA_COMMANDS_SUBDIR}${path.sep}${LISA_COMMAND_FILE_PREFIX}`;
|
|
88
|
+
const stale = previousManagedFiles.filter(file => !currentSet.has(file) && file.startsWith(lisaCommandPrefix));
|
|
89
|
+
await Promise.all(stale.map(async (file) => {
|
|
90
|
+
const absPath = path.join(destDir, OPENCODE_CONFIG_DIR, file);
|
|
91
|
+
if (await fse.pathExists(absPath)) {
|
|
92
|
+
await unlink(absPath);
|
|
93
|
+
}
|
|
94
|
+
}));
|
|
95
|
+
return Object.freeze(stale);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Convenience one-shot: discover Lisa commands from `lisaDir` and install them.
|
|
99
|
+
*
|
|
100
|
+
* Discovery is restricted to canonical plugins — the per-harness fanout variants
|
|
101
|
+
* (`*-agy`, `*-copilot`, `*-cursor`) are skipped so a reformatted variant copy
|
|
102
|
+
* of a command body never wins the last-wins dedup over the canonical source.
|
|
103
|
+
* @param lisaDir - Absolute path to the Lisa repo / installed package
|
|
104
|
+
* @param destDir - Absolute path to the destination project root
|
|
105
|
+
* @param previousManagedFiles - Files Lisa managed on the previous run
|
|
106
|
+
* @returns Result describing installed/deleted/managedFiles
|
|
107
|
+
*/
|
|
108
|
+
export async function discoverAndInstallCommands(lisaDir, destDir, previousManagedFiles) {
|
|
109
|
+
const sources = await discoverLisaCommands(lisaDir, name => !isHarnessVariantPlugin(name));
|
|
110
|
+
return installCommands(sources, destDir, previousManagedFiles);
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=command-installer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-installer.js","sourceRoot":"","sources":["../../src/opencode/command-installer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure transform: convert a Lisa command Markdown into an OpenCode command
|
|
3
|
+
* Markdown.
|
|
4
|
+
* @param commandSource - Raw contents of the command .md file
|
|
5
|
+
* @param displayName - Human-readable name used as a fallback description
|
|
6
|
+
* (e.g. "lisa:git:commit")
|
|
7
|
+
* @returns The OpenCode command Markdown content as a string
|
|
8
|
+
*/
|
|
9
|
+
export declare function transformCommandToOpencode(commandSource: string, displayName: string): string;
|
|
10
|
+
//# sourceMappingURL=command-transformer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-transformer.d.ts","sourceRoot":"","sources":["../../src/opencode/command-transformer.ts"],"names":[],"mappings":"AAmCA;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,GAClB,MAAM,CAoBR"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a Lisa command Markdown file into an OpenCode custom command.
|
|
3
|
+
*
|
|
4
|
+
* Lisa commands already work today on OpenCode as `lisa-` prefixed skills (see
|
|
5
|
+
* `opencode/skills-installer.ts`). This transformer is the additive, native
|
|
6
|
+
* path: OpenCode has a first-class command format
|
|
7
|
+
* (https://opencode.ai/docs/commands) with its own frontmatter and native
|
|
8
|
+
* `$ARGUMENTS` / `$1` / `@file` substitution.
|
|
9
|
+
*
|
|
10
|
+
* Claude command shape:
|
|
11
|
+
* ```
|
|
12
|
+
* ---
|
|
13
|
+
* description: ...
|
|
14
|
+
* argument-hint: "<x>" # optional
|
|
15
|
+
* ---
|
|
16
|
+
* Body with $ARGUMENTS
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* OpenCode command shape:
|
|
20
|
+
* ```
|
|
21
|
+
* ---
|
|
22
|
+
* description: ...
|
|
23
|
+
* ---
|
|
24
|
+
* Body with $ARGUMENTS
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Unlike the command-to-SKILL conversion (which strips `$ARGUMENTS` because a
|
|
28
|
+
* skill has no argument channel), this PRESERVES `$ARGUMENTS` verbatim because
|
|
29
|
+
* OpenCode substitutes it natively (verified on opencode 1.16.2 — the resolved
|
|
30
|
+
* config keeps `$ARGUMENTS` in the command template). The body is otherwise
|
|
31
|
+
* passed through unchanged.
|
|
32
|
+
* @module opencode/command-transformer
|
|
33
|
+
*/
|
|
34
|
+
import yaml from "js-yaml";
|
|
35
|
+
/**
|
|
36
|
+
* Pure transform: convert a Lisa command Markdown into an OpenCode command
|
|
37
|
+
* Markdown.
|
|
38
|
+
* @param commandSource - Raw contents of the command .md file
|
|
39
|
+
* @param displayName - Human-readable name used as a fallback description
|
|
40
|
+
* (e.g. "lisa:git:commit")
|
|
41
|
+
* @returns The OpenCode command Markdown content as a string
|
|
42
|
+
*/
|
|
43
|
+
export function transformCommandToOpencode(commandSource, displayName) {
|
|
44
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(commandSource);
|
|
45
|
+
if (match === null || match[1] === undefined || match[2] === undefined) {
|
|
46
|
+
throw new Error(`Command source is missing YAML frontmatter for ${displayName}`);
|
|
47
|
+
}
|
|
48
|
+
const description = parseDescription(yaml.load(match[1]), displayName);
|
|
49
|
+
const body = match[2].trimStart().trimEnd();
|
|
50
|
+
const frontmatter = [
|
|
51
|
+
"---",
|
|
52
|
+
`description: ${JSON.stringify(description)}`,
|
|
53
|
+
"---",
|
|
54
|
+
"",
|
|
55
|
+
].join("\n");
|
|
56
|
+
return `${frontmatter}${body}\n`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Extract the command description from parsed frontmatter, falling back to the
|
|
60
|
+
* display name when none is present.
|
|
61
|
+
* @param parsed - Output of `yaml.load(rawFrontmatter)` (untrusted shape)
|
|
62
|
+
* @param displayName - Fallback used when no description is available
|
|
63
|
+
* @returns The description string
|
|
64
|
+
*/
|
|
65
|
+
function parseDescription(parsed, displayName) {
|
|
66
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
67
|
+
return displayName;
|
|
68
|
+
}
|
|
69
|
+
const obj = parsed;
|
|
70
|
+
return typeof obj.description === "string" && obj.description.length > 0
|
|
71
|
+
? obj.description
|
|
72
|
+
: displayName;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=command-transformer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-transformer.js","sourceRoot":"","sources":["../../src/opencode/command-transformer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,IAAI,MAAM,SAAS,CAAC;AAE3B;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B,CACxC,aAAqB,EACrB,WAAmB;IAEnB,MAAM,KAAK,GAAG,6CAA6C,CAAC,IAAI,CAC9D,aAAa,CACd,CAAC;IACF,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;QACvE,MAAM,IAAI,KAAK,CACb,kDAAkD,WAAW,EAAE,CAChE,CAAC;IACJ,CAAC;IACD,MAAM,WAAW,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IACvE,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC;IAE5C,MAAM,WAAW,GAAG;QAClB,KAAK;QACL,gBAAgB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE;QAC7C,KAAK;QACL,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,OAAO,GAAG,WAAW,GAAG,IAAI,IAAI,CAAC;AACnC,CAAC;AAED;;;;;;GAMG;AACH,SAAS,gBAAgB,CAAC,MAAe,EAAE,WAAmB;IAC5D,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAClD,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,MAAM,GAAG,GAAG,MAAiC,CAAC;IAC9C,OAAO,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ,IAAI,GAAG,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;QACtE,CAAC,CAAC,GAAG,CAAC,WAAW;QACjB,CAAC,CAAC,WAAW,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ProjectType } from "../core/config.js";
|
|
2
|
+
/** Subdirectory inside `.opencode/` where OpenCode discovers project plugins */
|
|
3
|
+
export declare const OPENCODE_PLUGIN_SUBDIR = "plugin";
|
|
4
|
+
/** Filename of the OpenCode project config merged at the host root */
|
|
5
|
+
export declare const OPENCODE_CONFIG_FILENAME = "opencode.json";
|
|
6
|
+
/** Result of the OpenCode hooks install pass */
|
|
7
|
+
export interface OpencodeHooksInstallResult {
|
|
8
|
+
/** Files written, relative to `.opencode/` (added to the manifest). */
|
|
9
|
+
readonly managedFiles: readonly string[];
|
|
10
|
+
/** Number of plugin templates emitted into `.opencode/plugin/`. */
|
|
11
|
+
readonly pluginCount: number;
|
|
12
|
+
/** Whether `opencode.json` was created (vs merged into an existing file). */
|
|
13
|
+
readonly configCreated: boolean;
|
|
14
|
+
/** Stale Lisa plugin files removed because they're no longer shipped. */
|
|
15
|
+
readonly deleted: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Install Lisa's OpenCode hooks: merge `permission.bash` deny rules into
|
|
19
|
+
* `opencode.json` and emit the applicable `.opencode/plugin/lisa-*.ts` modules.
|
|
20
|
+
*
|
|
21
|
+
* `opencode.json` lives at the host ROOT (outside `.opencode/`), is a shared
|
|
22
|
+
* merged file, and is intentionally NOT returned in `managedFiles` — the
|
|
23
|
+
* `.opencode/`-relative manifest never deletes it. Plugin files under
|
|
24
|
+
* `.opencode/plugin/` ARE tracked so renames clean up stale modules.
|
|
25
|
+
* @param destDir - Absolute path to the host project root.
|
|
26
|
+
* @param detectedTypes - Project types Lisa detected; gates stack-specific
|
|
27
|
+
* plugins. Universal plugins/config install regardless.
|
|
28
|
+
* @param previousManagedFiles - Files Lisa managed last run (relative to
|
|
29
|
+
* `.opencode/`); used to detect stale plugin modules.
|
|
30
|
+
* @returns Result describing what was written + removed.
|
|
31
|
+
*/
|
|
32
|
+
export declare function installHooks(destDir: string, detectedTypes: readonly ProjectType[], previousManagedFiles: readonly string[]): Promise<OpencodeHooksInstallResult>;
|
|
33
|
+
/**
|
|
34
|
+
* Merge the `block-no-verify` deny globs into an `opencode.json` (JSONC) body.
|
|
35
|
+
* Pure function for testability; `mergeOpencodeConfig` is the I/O wrapper.
|
|
36
|
+
*
|
|
37
|
+
* Empty input yields a clean Lisa-authored document. A host `permission.bash`
|
|
38
|
+
* set to a bare string (e.g. `"allow"`) is re-seeded as a `{ "*": <string> }`
|
|
39
|
+
* catch-all so the host's posture survives alongside the deny rules. Otherwise
|
|
40
|
+
* each deny glob is added as its own key, preserving host bash patterns. `$schema`
|
|
41
|
+
* is set only when absent. Throws on invalid JSONC so a corrupt host file is
|
|
42
|
+
* surfaced, not silently overwritten.
|
|
43
|
+
* @param existingJsonc - Current contents of `opencode.json` (or "").
|
|
44
|
+
* @returns Merged JSON/JSONC string with host content preserved.
|
|
45
|
+
*/
|
|
46
|
+
export declare function mergeNoVerifyDenyRules(existingJsonc: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* Enumerate the Lisa plugin filenames currently present in a host's
|
|
49
|
+
* `.opencode/plugin/` directory. Exposed for tests/diagnostics.
|
|
50
|
+
* @param destDir - Absolute path to the host project root.
|
|
51
|
+
* @returns Sorted list of `lisa-*.ts` filenames (empty if the dir is absent).
|
|
52
|
+
*/
|
|
53
|
+
export declare function listInstalledPluginFiles(destDir: string): Promise<readonly string[]>;
|
|
54
|
+
//# sourceMappingURL=hooks-installer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks-installer.d.ts","sourceRoot":"","sources":["../../src/opencode/hooks-installer.ts"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,gFAAgF;AAChF,eAAO,MAAM,sBAAsB,WAAW,CAAC;AAE/C,sEAAsE;AACtE,eAAO,MAAM,wBAAwB,kBAAkB,CAAC;AAiExD,gDAAgD;AAChD,MAAM,WAAW,0BAA0B;IACzC,uEAAuE;IACvE,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,mEAAmE;IACnE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,SAAS,WAAW,EAAE,EACrC,oBAAoB,EAAE,SAAS,MAAM,EAAE,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAmCrC;AAgGD;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAoCpE;AA+ED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAa5B"}
|