@meltstudio/meltctl 4.22.0 → 4.23.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 CHANGED
@@ -20,7 +20,8 @@ meltctl project init
20
20
  # Select specific tools
21
21
  meltctl project init --claude # Claude Code only
22
22
  meltctl project init --cursor # Cursor only
23
- meltctl project init --claude --cursor # Both
23
+ meltctl project init --opencode # OpenCode only
24
+ meltctl project init --claude --cursor --opencode # All tools
24
25
 
25
26
  # Re-initialize
26
27
  meltctl project init --force
@@ -49,6 +50,7 @@ meltctl version --check
49
50
  - `.claude/settings.json` — Claude Code permissions
50
51
  - `.claude/skills/melt-{setup,plan,...,update}/SKILL.md` — Claude Code workflow skills
51
52
  - `.cursor/commands/melt-{setup,plan,...,update}.md` — Cursor workflow commands
53
+ - `.opencode/commands/melt-{setup,plan,...,update}.md` — OpenCode workflow commands
52
54
  - `.mcp.json` — MCP server configuration (Chrome DevTools)
53
55
 
54
56
  ## Requirements
@@ -2,6 +2,7 @@ interface InitOptions {
2
2
  force?: boolean;
3
3
  claude?: boolean;
4
4
  cursor?: boolean;
5
+ opencode?: boolean;
5
6
  }
6
7
  export declare function initCommand(options: InitOptions): Promise<void>;
7
8
  export {};
@@ -96,6 +96,53 @@ description: >-
96
96
  This is a reference skill — it explains the process, not executes it.
97
97
  ---
98
98
 
99
+ `,
100
+ };
101
+ const OPENCODE_COMMAND_FRONTMATTER = {
102
+ setup: `---
103
+ description: Analyze the project and customize AGENTS.md for this codebase.
104
+ ---
105
+
106
+ `,
107
+ plan: `---
108
+ description: Design an implementation approach before writing code.
109
+ ---
110
+
111
+ `,
112
+ review: `---
113
+ description: Review code changes against project standards and address PR feedback.
114
+ ---
115
+
116
+ `,
117
+ pr: `---
118
+ description: Create a well-structured pull request from current changes.
119
+ ---
120
+
121
+ `,
122
+ debug: `---
123
+ description: Systematically investigate and fix bugs.
124
+ ---
125
+
126
+ `,
127
+ audit: `---
128
+ description: Run a comprehensive project compliance audit against team standards.
129
+ ---
130
+
131
+ `,
132
+ validate: `---
133
+ description: Run the validation plan from the plan document after implementation.
134
+ ---
135
+
136
+ `,
137
+ update: `---
138
+ description: Update Melt skills and standards to the latest version.
139
+ ---
140
+
141
+ `,
142
+ help: `---
143
+ description: Answer questions about the AI-First Development Playbook and team workflow.
144
+ ---
145
+
99
146
  `,
100
147
  };
101
148
  const GITIGNORE_ENTRIES = ['.env.local', '.claude/settings.local.json'];
@@ -103,6 +150,7 @@ function detectExistingTools(cwd) {
103
150
  return {
104
151
  claude: fs.pathExistsSync(path.join(cwd, '.claude/settings.json')),
105
152
  cursor: fs.pathExistsSync(path.join(cwd, '.cursor/commands')),
153
+ opencode: fs.pathExistsSync(path.join(cwd, '.opencode/commands')),
106
154
  };
107
155
  }
108
156
  async function promptToolSelection(existingTools) {
@@ -113,6 +161,9 @@ async function promptToolSelection(existingTools) {
113
161
  if (!existingTools?.cursor) {
114
162
  choices.push({ name: 'Cursor', value: 'cursor', checked: true });
115
163
  }
164
+ if (!existingTools?.opencode) {
165
+ choices.push({ name: 'OpenCode', value: 'opencode', checked: true });
166
+ }
116
167
  choices.push({
117
168
  name: 'Other — contact us in #dev on Slack to request support',
118
169
  value: 'other',
@@ -124,6 +175,7 @@ async function promptToolSelection(existingTools) {
124
175
  return {
125
176
  claude: selected.includes('claude'),
126
177
  cursor: selected.includes('cursor'),
178
+ opencode: selected.includes('opencode'),
127
179
  other: selected.includes('other'),
128
180
  };
129
181
  }
@@ -156,18 +208,27 @@ export async function initCommand(options) {
156
208
  if (alreadyInitialized && !options.force) {
157
209
  // Re-init scenario: project already set up, developer wants to add another tool
158
210
  const existing = detectExistingTools(cwd);
159
- const existingNames = [existing.claude ? 'Claude Code' : '', existing.cursor ? 'Cursor' : '']
211
+ const existingNames = [
212
+ existing.claude ? 'Claude Code' : '',
213
+ existing.cursor ? 'Cursor' : '',
214
+ existing.opencode ? 'OpenCode' : '',
215
+ ]
160
216
  .filter(Boolean)
161
- .join(' and ');
162
- if (existing.claude && existing.cursor) {
163
- console.log(chalk.yellow('Project already initialized with Claude Code and Cursor. Use --force to overwrite.'));
217
+ .join(', ');
218
+ if (existing.claude && existing.cursor && existing.opencode) {
219
+ console.log(chalk.yellow('Project already initialized with all tools. Use --force to overwrite.'));
164
220
  process.exit(1);
165
221
  }
166
222
  console.log(chalk.dim(`Project already initialized${existingNames ? ` with ${existingNames}` : ''}.`));
167
223
  isReInit = true;
168
- if (options.claude || options.cursor) {
224
+ if (options.claude || options.cursor || options.opencode) {
169
225
  // Non-interactive: use flags directly, skip confirm prompt
170
- tools = { claude: !!options.claude, cursor: !!options.cursor, other: false };
226
+ tools = {
227
+ claude: !!options.claude,
228
+ cursor: !!options.cursor,
229
+ opencode: !!options.opencode,
230
+ other: false,
231
+ };
171
232
  }
172
233
  else {
173
234
  const addMore = await confirm({
@@ -182,14 +243,19 @@ export async function initCommand(options) {
182
243
  }
183
244
  else {
184
245
  // Fresh init or --force
185
- if (options.claude || options.cursor) {
186
- tools = { claude: !!options.claude, cursor: !!options.cursor, other: false };
246
+ if (options.claude || options.cursor || options.opencode) {
247
+ tools = {
248
+ claude: !!options.claude,
249
+ cursor: !!options.cursor,
250
+ opencode: !!options.opencode,
251
+ other: false,
252
+ };
187
253
  }
188
254
  else {
189
255
  tools = await promptToolSelection();
190
256
  }
191
257
  }
192
- if (!tools.claude && !tools.cursor && !tools.other) {
258
+ if (!tools.claude && !tools.cursor && !tools.opencode && !tools.other) {
193
259
  console.log(chalk.yellow('No tools selected. Nothing to do.'));
194
260
  process.exit(0);
195
261
  }
@@ -267,6 +333,18 @@ export async function initCommand(options) {
267
333
  }
268
334
  createdFiles.push('.cursor/commands/melt-{setup,plan,validate,review,pr,debug,audit,update,help}.md');
269
335
  }
336
+ // OpenCode files
337
+ if (tools.opencode) {
338
+ await fs.ensureDir(path.join(cwd, '.opencode/commands'));
339
+ for (const name of workflows) {
340
+ const workflowContent = templates[`workflows/${name}.md`];
341
+ if (workflowContent) {
342
+ const commandContent = OPENCODE_COMMAND_FRONTMATTER[name] + workflowContent;
343
+ await fs.writeFile(path.join(cwd, `.opencode/commands/melt-${name}.md`), commandContent, 'utf-8');
344
+ }
345
+ }
346
+ createdFiles.push('.opencode/commands/melt-{setup,plan,validate,review,pr,debug,audit,update,help}.md');
347
+ }
270
348
  // Print summary
271
349
  console.log(chalk.green('Created files:'));
272
350
  for (const file of createdFiles) {
@@ -277,35 +355,58 @@ export async function initCommand(options) {
277
355
  console.log(chalk.cyan('Want support for your tool? Let us know in #dev on Slack'));
278
356
  console.log();
279
357
  }
358
+ const commandNames = 'melt-setup, melt-plan, melt-validate, melt-review, melt-pr, melt-debug, melt-audit, melt-update, melt-help';
280
359
  if (tools.claude) {
281
- const skills = '/melt-setup, /melt-plan, /melt-validate, /melt-review, /melt-pr, /melt-debug, /melt-audit, /melt-update, /melt-help';
282
- console.log(chalk.dim(`Available skills: ${skills}`));
360
+ console.log(chalk.dim(`Available skills: /${commandNames.replace(/, /g, ', /')}`));
283
361
  }
284
362
  if (tools.cursor) {
285
- console.log(chalk.dim('Available commands: melt-setup, melt-plan, melt-validate, melt-review, melt-pr, melt-debug, melt-audit, melt-update, melt-help'));
363
+ console.log(chalk.dim(`Available Cursor commands: ${commandNames}`));
364
+ }
365
+ if (tools.opencode) {
366
+ console.log(chalk.dim(`Available OpenCode commands: /${commandNames.replace(/, /g, ', /')}`));
286
367
  }
287
- if (tools.claude || tools.cursor) {
368
+ if (tools.claude || tools.cursor || tools.opencode) {
288
369
  console.log();
289
370
  }
290
371
  if (!isReInit) {
291
372
  console.log(chalk.bold.cyan(' Next step: run /melt-setup to customize AGENTS.md for your project'));
292
373
  console.log();
293
- if (tools.claude && tools.cursor) {
294
- console.log(chalk.dim(' Claude Code:'));
295
- console.log(chalk.dim(' - Type /melt-setup in the chat prompt, or'));
296
- console.log(chalk.dim(' - Open the skills menu and select melt-setup'));
297
- console.log();
298
- console.log(chalk.dim(' Cursor:'));
299
- console.log(chalk.dim(' - Open the command menu (Cmd+K) and search for melt-setup'));
374
+ const toolInstructions = [];
375
+ if (tools.claude) {
376
+ toolInstructions.push({
377
+ name: 'Claude Code',
378
+ steps: [
379
+ 'Type /melt-setup in the chat prompt, or',
380
+ 'Open the skills menu and select melt-setup',
381
+ ],
382
+ });
300
383
  }
301
- else if (tools.claude) {
302
- console.log(chalk.dim(' In Claude Code:'));
303
- console.log(chalk.dim(' - Type /melt-setup in the chat prompt, or'));
304
- console.log(chalk.dim(' - Open the skills menu and select melt-setup'));
384
+ if (tools.cursor) {
385
+ toolInstructions.push({
386
+ name: 'Cursor',
387
+ steps: ['Open the command menu (Cmd+K) and search for melt-setup'],
388
+ });
389
+ }
390
+ if (tools.opencode) {
391
+ toolInstructions.push({
392
+ name: 'OpenCode',
393
+ steps: ['Type /melt-setup in the chat prompt'],
394
+ });
395
+ }
396
+ if (toolInstructions.length === 1 && toolInstructions[0]) {
397
+ console.log(chalk.dim(` In ${toolInstructions[0].name}:`));
398
+ for (const step of toolInstructions[0].steps) {
399
+ console.log(chalk.dim(` - ${step}`));
400
+ }
305
401
  }
306
402
  else {
307
- console.log(chalk.dim(' In Cursor:'));
308
- console.log(chalk.dim(' - Open the command menu (Cmd+K) and search for melt-setup'));
403
+ for (const tool of toolInstructions) {
404
+ console.log(chalk.dim(` ${tool.name}:`));
405
+ for (const step of tool.steps) {
406
+ console.log(chalk.dim(` - ${step}`));
407
+ }
408
+ console.log();
409
+ }
309
410
  }
310
411
  console.log();
311
412
  console.log(chalk.dim(' The setup skill will analyze your project and fill in AGENTS.md.'));
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'),
21
21
  const program = new Command();
22
22
  program
23
23
  .name('meltctl')
24
- .description('Set up AI-first development standards (AGENTS.md, Claude skills, Cursor commands, MCP config) in your project.\n\nRequires a @meltstudio.co Google Workspace account. Run `npx @meltstudio/meltctl@latest login` first, then `npx @meltstudio/meltctl@latest project init` in your repo.')
24
+ .description('Set up AI-first development standards (AGENTS.md, Claude skills, Cursor commands, OpenCode commands, MCP config) in your project.\n\nRequires a @meltstudio.co Google Workspace account. Run `npx @meltstudio/meltctl@latest login` first, then `npx @meltstudio/meltctl@latest project init` in your repo.')
25
25
  .version(packageJson.version)
26
26
  .addHelpText('beforeAll', () => {
27
27
  printBanner();
@@ -45,12 +45,18 @@ program
45
45
  const project = program.command('project').description('manage project configuration');
46
46
  project
47
47
  .command('init')
48
- .description('scaffold Melt development tools into the current directory (AGENTS.md, .claude/, .cursor/, .mcp.json)')
48
+ .description('scaffold Melt development tools into the current directory (AGENTS.md, .claude/, .cursor/, .opencode/, .mcp.json)')
49
49
  .option('--force', 'overwrite existing files if already initialized')
50
50
  .option('--claude', 'generate Claude Code configuration')
51
51
  .option('--cursor', 'generate Cursor configuration')
52
+ .option('--opencode', 'generate OpenCode configuration')
52
53
  .action(options => {
53
- return initCommand({ force: options.force, claude: options.claude, cursor: options.cursor });
54
+ return initCommand({
55
+ force: options.force,
56
+ claude: options.claude,
57
+ cursor: options.cursor,
58
+ opencode: options.opencode,
59
+ });
54
60
  });
55
61
  project
56
62
  .command('templates')
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meltstudio/meltctl",
3
- "version": "4.22.0",
4
- "description": "AI-first development tools for teams - set up AGENTS.md, Claude Code, Cursor, and Copilot standards",
3
+ "version": "4.23.1",
4
+ "description": "AI-first development tools for teams - set up AGENTS.md, Claude Code, Cursor, and OpenCode standards",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "bin": {