opencode-lisa 0.2.1 → 0.3.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/README.md CHANGED
@@ -26,10 +26,27 @@ The **Ralph Wiggum pattern** is a simple bash loop that keeps feeding prompts to
26
26
  ## Install
27
27
 
28
28
  ```bash
29
- npm install opencode-lisa
29
+ npm install -D opencode-lisa
30
+ npx opencode-lisa init
30
31
  ```
31
32
 
32
- Add to your `opencode.json`:
33
+ This will:
34
+ - Install command and skill files to `.opencode/`
35
+ - Add the plugin to your `opencode.json`
36
+ - Set up Lisa for use in your project
37
+
38
+ Requires [OpenCode](https://opencode.ai) 1.0+.
39
+
40
+ ### Global Installation
41
+
42
+ To install Lisa globally for all projects:
43
+
44
+ ```bash
45
+ npm install -g opencode-lisa
46
+ npx opencode-lisa init --global
47
+ ```
48
+
49
+ Add to your global `~/.config/opencode/opencode.json`:
33
50
 
34
51
  ```json
35
52
  {
@@ -37,8 +54,6 @@ Add to your `opencode.json`:
37
54
  }
38
55
  ```
39
56
 
40
- Restart OpenCode. Requires [OpenCode](https://opencode.ai).
41
-
42
57
  ## Usage
43
58
 
44
59
  ```
package/bin/cli.js ADDED
@@ -0,0 +1,252 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from 'fs';
4
+ import { join, dirname } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { createInterface } from 'readline';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+ const ASSETS_DIR = join(__dirname, '..', 'assets');
11
+
12
+ // ANSI colors
13
+ const colors = {
14
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
15
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
16
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
17
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
18
+ dim: (s) => `\x1b[2m${s}\x1b[0m`,
19
+ };
20
+
21
+ async function prompt(question) {
22
+ const rl = createInterface({
23
+ input: process.stdin,
24
+ output: process.stdout,
25
+ });
26
+
27
+ return new Promise((resolve) => {
28
+ rl.question(question, (answer) => {
29
+ rl.close();
30
+ resolve(answer.toLowerCase().trim());
31
+ });
32
+ });
33
+ }
34
+
35
+ async function confirmOverwrite(filePath, force) {
36
+ if (!existsSync(filePath)) return true;
37
+ if (force) return true;
38
+
39
+ const answer = await prompt(
40
+ `${colors.yellow('?')} ${filePath} already exists. Overwrite? (y/N) `
41
+ );
42
+ return answer === 'y' || answer === 'yes';
43
+ }
44
+
45
+ function ensureDir(filePath) {
46
+ const dir = dirname(filePath);
47
+ if (!existsSync(dir)) {
48
+ mkdirSync(dir, { recursive: true });
49
+ }
50
+ }
51
+
52
+ function copyAsset(srcRelative, destPath, force) {
53
+ return async () => {
54
+ const srcPath = join(ASSETS_DIR, srcRelative);
55
+
56
+ if (!existsSync(srcPath)) {
57
+ console.error(colors.red(`Error: Source file not found: ${srcPath}`));
58
+ process.exit(1);
59
+ }
60
+
61
+ const shouldWrite = await confirmOverwrite(destPath, force);
62
+ if (!shouldWrite) {
63
+ console.log(colors.dim(` Skipped: ${destPath}`));
64
+ return false;
65
+ }
66
+
67
+ ensureDir(destPath);
68
+ copyFileSync(srcPath, destPath);
69
+ console.log(colors.green(` Created: ${destPath}`));
70
+ return true;
71
+ };
72
+ }
73
+
74
+ function updateOpencodeJson(targetDir, force) {
75
+ return async () => {
76
+ const configPath = join(targetDir, 'opencode.json');
77
+ let config = {};
78
+ let existed = false;
79
+
80
+ if (existsSync(configPath)) {
81
+ existed = true;
82
+ try {
83
+ const content = readFileSync(configPath, 'utf-8');
84
+ config = JSON.parse(content);
85
+ } catch (e) {
86
+ console.error(colors.red(`Error parsing ${configPath}: ${e.message}`));
87
+ const answer = await prompt(
88
+ `${colors.yellow('?')} Create a new opencode.json? (y/N) `
89
+ );
90
+ if (answer !== 'y' && answer !== 'yes') {
91
+ console.log(colors.dim(` Skipped: opencode.json update`));
92
+ return false;
93
+ }
94
+ config = {};
95
+ }
96
+ }
97
+
98
+ // Ensure plugin array exists
99
+ if (!config.plugin) {
100
+ config.plugin = [];
101
+ }
102
+
103
+ // Check if already has the plugin
104
+ if (config.plugin.includes('opencode-lisa')) {
105
+ console.log(colors.dim(` Already configured: opencode-lisa in plugins`));
106
+ return false;
107
+ }
108
+
109
+ // Add the plugin
110
+ config.plugin.push('opencode-lisa');
111
+
112
+ // Add schema if not present
113
+ if (!config.$schema) {
114
+ config.$schema = 'https://opencode.ai/config.json';
115
+ }
116
+
117
+ ensureDir(configPath);
118
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
119
+
120
+ if (existed) {
121
+ console.log(colors.green(` Updated: opencode.json (added opencode-lisa to plugins)`));
122
+ } else {
123
+ console.log(colors.green(` Created: opencode.json`));
124
+ }
125
+ return true;
126
+ };
127
+ }
128
+
129
+ async function init(targetDir, options) {
130
+ const { force, global: isGlobal } = options;
131
+
132
+ console.log('');
133
+ console.log(colors.cyan('Lisa - Intelligent Epic Workflow Plugin'));
134
+ console.log(colors.dim('Installing command and skill files...'));
135
+ console.log('');
136
+
137
+ const tasks = [
138
+ {
139
+ name: 'skill',
140
+ run: copyAsset(
141
+ 'skills/lisa/SKILL.md',
142
+ join(targetDir, '.opencode', 'skills', 'lisa', 'SKILL.md'),
143
+ force
144
+ ),
145
+ },
146
+ {
147
+ name: 'command',
148
+ run: copyAsset(
149
+ 'commands/lisa.md',
150
+ join(targetDir, '.opencode', 'commands', 'lisa.md'),
151
+ force
152
+ ),
153
+ },
154
+ ];
155
+
156
+ // Only update opencode.json for local installs (not global)
157
+ if (!isGlobal) {
158
+ tasks.push({
159
+ name: 'config',
160
+ run: updateOpencodeJson(targetDir, force),
161
+ });
162
+ }
163
+
164
+ let anyCreated = false;
165
+ for (const task of tasks) {
166
+ const created = await task.run();
167
+ if (created) anyCreated = true;
168
+ }
169
+
170
+ console.log('');
171
+
172
+ if (anyCreated) {
173
+ console.log(colors.green('Done!'));
174
+ console.log('');
175
+ console.log('Lisa is now installed. You can use:');
176
+ console.log('');
177
+ console.log(` ${colors.cyan('/lisa')} - Run Lisa commands in OpenCode`);
178
+ console.log(` ${colors.cyan('/lisa help')} - Show available Lisa commands`);
179
+ console.log(` ${colors.cyan('/lisa list')} - List all your epics`);
180
+ console.log('');
181
+ console.log(colors.dim('Start by running /lisa <epic-name> to create your first epic!'));
182
+ } else {
183
+ console.log(colors.dim('Nothing to do - everything is already set up.'));
184
+ }
185
+
186
+ console.log('');
187
+ }
188
+
189
+ function printHelp() {
190
+ console.log(`
191
+ ${colors.cyan('Lisa - Intelligent Epic Workflow Plugin')}
192
+
193
+ Like Ralph Wiggum, but smarter. Lisa plans before she acts.
194
+
195
+ Usage:
196
+ npx opencode-lisa init [options]
197
+
198
+ Commands:
199
+ init Install the Lisa command and skill files
200
+
201
+ Options:
202
+ --force Overwrite existing files without asking
203
+ --global Install to ~/.config/opencode/ instead of current directory
204
+ --help, -h Show this help message
205
+
206
+ Examples:
207
+ npx opencode-lisa init
208
+ npx opencode-lisa init --force
209
+ npx opencode-lisa init --global
210
+
211
+ After installation, use /lisa in OpenCode to get started!
212
+ `);
213
+ }
214
+
215
+ async function main() {
216
+ const args = process.argv.slice(2);
217
+
218
+ // Parse flags
219
+ const force = args.includes('--force') || args.includes('-f');
220
+ const isGlobal = args.includes('--global') || args.includes('-g');
221
+ const help = args.includes('--help') || args.includes('-h');
222
+
223
+ // Get command (first non-flag argument)
224
+ const command = args.find((arg) => !arg.startsWith('-'));
225
+
226
+ if (help || (!command && args.length === 0)) {
227
+ printHelp();
228
+ process.exit(0);
229
+ }
230
+
231
+ if (command !== 'init') {
232
+ console.error(colors.red(`Unknown command: ${command}`));
233
+ console.error(`Run ${colors.cyan('npx opencode-lisa --help')} for usage.`);
234
+ process.exit(1);
235
+ }
236
+
237
+ // Determine target directory
238
+ let targetDir;
239
+ if (isGlobal) {
240
+ const home = process.env.HOME || process.env.USERPROFILE;
241
+ targetDir = join(home, '.config', 'opencode');
242
+ } else {
243
+ targetDir = process.cwd();
244
+ }
245
+
246
+ await init(targetDir, { force, global: isGlobal });
247
+ }
248
+
249
+ main().catch((err) => {
250
+ console.error(colors.red('Error:'), err.message);
251
+ process.exit(1);
252
+ });
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "opencode-lisa",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Lisa - intelligent epic workflow plugin for OpenCode. Like Ralph Wiggum, but smarter.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
+ "bin": {
8
+ "opencode-lisa": "./bin/cli.js"
9
+ },
7
10
  "exports": {
8
11
  ".": {
9
12
  "import": "./dist/index.js"
@@ -11,8 +14,8 @@
11
14
  },
12
15
  "files": [
13
16
  "dist",
14
- ".opencode/command",
15
- ".opencode/skill"
17
+ "bin",
18
+ "assets"
16
19
  ],
17
20
  "scripts": {
18
21
  "build": "bun build src/index.ts --outdir dist --target bun --format esm",
@@ -39,11 +42,17 @@
39
42
  "bugs": {
40
43
  "url": "https://github.com/fractalswift/lisa-simpson/issues"
41
44
  },
45
+ "engines": {
46
+ "node": ">=18.0.0"
47
+ },
42
48
  "peerDependencies": {
43
49
  "@opencode-ai/plugin": ">=1.0.0"
44
50
  },
45
51
  "devDependencies": {
46
52
  "@opencode-ai/plugin": "^1.1.25",
47
53
  "typescript": "^5.0.0"
54
+ },
55
+ "dependencies": {
56
+ "opencode-lisa": "^0.2.1"
48
57
  }
49
58
  }
File without changes