diff --git a/README.md b/README.md
index 7b6c7354..ec55265d 100644
--- a/README.md
+++ b/README.md
@@ -1,27 +1,52 @@
-
+
-
-
-
+
+
+
-
+
Spec-driven development for AI coding assistants.
-
-
+
-
-
-
+
+# OpenSplx
+
+> **Fork Notice:** OpenSplx is a community fork of [OpenSpec](https://github.com/Fission-AI/OpenSpec).
+> It adds the `plx` command alias and extended features while maintaining full compatibility
+> with the original OpenSpec workflow.
+
+## What's Different in OpenSplx
+
+| Feature | OpenSpec | OpenSplx |
+|---------|----------|----------|
+| Command | `openspec` | `openspec` + `plx` alias |
+| Install | `npm i -g @fission-ai/openspec` | Clone & `npm link` (local) |
+
+### Quick Start (OpenSplx)
+
+```bash
+git clone https://github.com/appyboypov/OpenSplx.git
+cd OpenSplx
+pnpm install && pnpm build
+npm link
+plx --version # or openspec --version
+```
+
+---
+
+
+Original OpenSpec Documentation (click to expand)
+
Follow @0xTab on X for updates ยท Join the OpenSpec Discord for help and questions.
@@ -379,3 +404,5 @@ Run `openspec update` whenever someone switches tools so your agents pick up the
## License
MIT
+
+
diff --git a/assets/opensplx_pixel_dark.svg b/assets/opensplx_pixel_dark.svg
new file mode 100644
index 00000000..40d18f7e
--- /dev/null
+++ b/assets/opensplx_pixel_dark.svg
@@ -0,0 +1,94 @@
+
diff --git a/assets/opensplx_pixel_light.svg b/assets/opensplx_pixel_light.svg
new file mode 100644
index 00000000..9f9c273f
--- /dev/null
+++ b/assets/opensplx_pixel_light.svg
@@ -0,0 +1,94 @@
+
diff --git a/bin/plx.js b/bin/plx.js
new file mode 100755
index 00000000..75996ba5
--- /dev/null
+++ b/bin/plx.js
@@ -0,0 +1,3 @@
+#!/usr/bin/env node
+
+import '../dist/cli/index.js';
diff --git a/package.json b/package.json
index 9ad409fe..c0cc11be 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,8 @@
}
},
"bin": {
- "openspec": "./bin/openspec.js"
+ "openspec": "./bin/openspec.js",
+ "plx": "./bin/plx.js"
},
"files": [
"dist",
diff --git a/scripts/postinstall.js b/scripts/postinstall.js
index bfe6e123..b10bfdec 100644
--- a/scripts/postinstall.js
+++ b/scripts/postinstall.js
@@ -18,6 +18,16 @@ import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
+/**
+ * Detect the command name from the invocation path
+ */
+function getCommandName() {
+ const scriptPath = process.argv[1] || '';
+ const scriptName = path.basename(scriptPath).replace(/\.js$/, '');
+ // Default to 'openspec' for postinstall context
+ return scriptName === 'plx' ? 'plx' : 'openspec';
+}
+
/**
* Check if we should skip installation
*/
@@ -72,7 +82,7 @@ async function installCompletions(shell) {
// Check if shell is supported
if (!CompletionFactory.isSupported(shell)) {
- console.log(`\nTip: Run 'openspec completion install' for shell completions`);
+ console.log(`\nTip: Run 'openspec completion install' or 'plx completion install' for shell completions`);
return;
}
@@ -99,11 +109,11 @@ async function installCompletions(shell) {
}
} else {
// Installation failed, show tip for manual install
- console.log(`\nTip: Run 'openspec completion install' for shell completions`);
+ console.log(`\nTip: Run 'openspec completion install' or 'plx completion install' for shell completions`);
}
} catch (error) {
// Fail gracefully - show tip for manual install
- console.log(`\nTip: Run 'openspec completion install' for shell completions`);
+ console.log(`\nTip: Run 'openspec completion install' or 'plx completion install' for shell completions`);
}
}
@@ -127,7 +137,7 @@ async function main() {
// Detect shell
const shell = await detectShell();
if (!shell) {
- console.log(`\nTip: Run 'openspec completion install' for shell completions`);
+ console.log(`\nTip: Run 'openspec completion install' or 'plx completion install' for shell completions`);
return;
}
@@ -136,7 +146,7 @@ async function main() {
} catch (error) {
// Fail gracefully - never break npm install
// Show tip for manual install
- console.log(`\nTip: Run 'openspec completion install' for shell completions`);
+ console.log(`\nTip: Run 'openspec completion install' or 'plx completion install' for shell completions`);
}
}
diff --git a/src/cli/index.ts b/src/cli/index.ts
index 0cd5885f..a58e8b9c 100644
--- a/src/cli/index.ts
+++ b/src/cli/index.ts
@@ -15,12 +15,15 @@ import { ValidateCommand } from '../commands/validate.js';
import { ShowCommand } from '../commands/show.js';
import { CompletionCommand } from '../commands/completion.js';
+// Import command name detection utility
+import { commandName } from '../utils/command-name.js';
+
const program = new Command();
const require = createRequire(import.meta.url);
const { version } = require('../../package.json');
program
- .name('openspec')
+ .name(commandName)
.description('AI-native system for spec-driven development')
.version(version);
diff --git a/src/commands/completion.ts b/src/commands/completion.ts
index 2baedc0c..f57f81f4 100644
--- a/src/commands/completion.ts
+++ b/src/commands/completion.ts
@@ -5,6 +5,7 @@ import { COMMAND_REGISTRY } from '../core/completions/command-registry.js';
import { detectShell, SupportedShell } from '../utils/shell-detection.js';
import { CompletionProvider } from '../core/completions/completion-provider.js';
import { getArchivedChangeIds } from '../utils/item-discovery.js';
+import { commandName } from '../utils/command-name.js';
interface GenerateOptions {
shell?: string;
@@ -59,7 +60,7 @@ export class CompletionCommand {
// No shell specified and cannot auto-detect
console.error('Error: Could not auto-detect shell. Please specify shell explicitly.');
- console.error(`Usage: openspec completion ${operationName} [shell]`);
+ console.error(`Usage: ${commandName} completion ${operationName} [shell]`);
console.error(`Currently supported: ${CompletionFactory.getSupportedShells().join(', ')}`);
process.exitCode = 1;
return null;
@@ -115,7 +116,7 @@ export class CompletionCommand {
*/
private async generateForShell(shell: SupportedShell): Promise {
const generator = CompletionFactory.createGenerator(shell);
- const script = generator.generate(COMMAND_REGISTRY);
+ const script = generator.generate(COMMAND_REGISTRY, commandName);
console.log(script);
}
@@ -126,11 +127,11 @@ export class CompletionCommand {
const generator = CompletionFactory.createGenerator(shell);
const installer = CompletionFactory.createInstaller(shell);
- const spinner = ora(`Installing ${shell} completion script...`).start();
+ const spinner = ora(`Installing ${shell} completion script for ${commandName}...`).start();
try {
// Generate the completion script
- const script = generator.generate(COMMAND_REGISTRY);
+ const script = generator.generate(COMMAND_REGISTRY, commandName);
// Install it
const result = await installer.install(script);
@@ -180,7 +181,7 @@ export class CompletionCommand {
// Prompt for confirmation unless --yes flag is provided
if (!skipConfirmation) {
const confirmed = await confirm({
- message: 'Remove OpenSpec configuration from ~/.zshrc?',
+ message: `Remove ${commandName} configuration from ~/.zshrc?`,
default: false,
});
diff --git a/src/core/completions/generators/zsh-generator.ts b/src/core/completions/generators/zsh-generator.ts
index 765fd5b4..75f39ae4 100644
--- a/src/core/completions/generators/zsh-generator.ts
+++ b/src/core/completions/generators/zsh-generator.ts
@@ -1,8 +1,8 @@
import { CompletionGenerator, CommandDefinition, FlagDefinition } from '../types.js';
/**
- * Generates Zsh completion scripts for the OpenSpec CLI.
- * Follows Zsh completion system conventions using the _openspec function.
+ * Generates Zsh completion scripts for the OpenSpec/OpenSplx CLI.
+ * Follows Zsh completion system conventions.
*/
export class ZshGenerator implements CompletionGenerator {
readonly shell = 'zsh' as const;
@@ -11,20 +11,21 @@ export class ZshGenerator implements CompletionGenerator {
* Generate a Zsh completion script
*
* @param commands - Command definitions to generate completions for
+ * @param commandName - The CLI command name (defaults to 'openspec')
* @returns Zsh completion script as a string
*/
- generate(commands: CommandDefinition[]): string {
+ generate(commands: CommandDefinition[], commandName: string = 'openspec'): string {
const script: string[] = [];
// Header comment
- script.push('#compdef openspec');
+ script.push(`#compdef ${commandName}`);
script.push('');
- script.push('# Zsh completion script for OpenSpec CLI');
+ script.push(`# Zsh completion script for ${commandName} CLI`);
script.push('# Auto-generated - do not edit manually');
script.push('');
// Main completion function
- script.push('_openspec() {');
+ script.push(`_${commandName}() {`);
script.push(' local context state line');
script.push(' typeset -A opt_args');
script.push('');
@@ -48,7 +49,7 @@ export class ZshGenerator implements CompletionGenerator {
// Command dispatch logic
script.push(' case $state in');
script.push(' command)');
- script.push(' _describe "openspec command" commands');
+ script.push(` _describe "${commandName} command" commands`);
script.push(' ;;');
script.push(' args)');
script.push(' case $words[1] in');
@@ -56,7 +57,7 @@ export class ZshGenerator implements CompletionGenerator {
// Generate completion for each command
for (const cmd of commands) {
script.push(` ${cmd.name})`);
- script.push(` _openspec_${this.sanitizeFunctionName(cmd.name)}`);
+ script.push(` _${commandName}_${this.sanitizeFunctionName(cmd.name)}`);
script.push(' ;;');
}
@@ -68,15 +69,15 @@ export class ZshGenerator implements CompletionGenerator {
// Generate individual command completion functions
for (const cmd of commands) {
- script.push(...this.generateCommandFunction(cmd));
+ script.push(...this.generateCommandFunction(cmd, commandName));
script.push('');
}
// Add dynamic completion helper functions
- script.push(...this.generateDynamicCompletionHelpers());
+ script.push(...this.generateDynamicCompletionHelpers(commandName));
// Register the completion function
- script.push('compdef _openspec openspec');
+ script.push(`compdef _${commandName} ${commandName}`);
script.push('');
return script.join('\n');
@@ -128,44 +129,44 @@ export class ZshGenerator implements CompletionGenerator {
/**
* Generate dynamic completion helper functions for change and spec IDs
*/
- private generateDynamicCompletionHelpers(): string[] {
+ private generateDynamicCompletionHelpers(commandName: string): string[] {
const lines: string[] = [];
lines.push('# Dynamic completion helpers');
lines.push('');
// Helper function for completing change IDs
- lines.push('# Use openspec __complete to get available changes');
- lines.push('_openspec_complete_changes() {');
+ lines.push(`# Use ${commandName} __complete to get available changes`);
+ lines.push(`_${commandName}_complete_changes() {`);
lines.push(' local -a changes');
lines.push(' while IFS=$\'\\t\' read -r id desc; do');
lines.push(' changes+=("$id:$desc")');
- lines.push(' done < <(openspec __complete changes 2>/dev/null)');
+ lines.push(` done < <(${commandName} __complete changes 2>/dev/null)`);
lines.push(' _describe "change" changes');
lines.push('}');
lines.push('');
// Helper function for completing spec IDs
- lines.push('# Use openspec __complete to get available specs');
- lines.push('_openspec_complete_specs() {');
+ lines.push(`# Use ${commandName} __complete to get available specs`);
+ lines.push(`_${commandName}_complete_specs() {`);
lines.push(' local -a specs');
lines.push(' while IFS=$\'\\t\' read -r id desc; do');
lines.push(' specs+=("$id:$desc")');
- lines.push(' done < <(openspec __complete specs 2>/dev/null)');
+ lines.push(` done < <(${commandName} __complete specs 2>/dev/null)`);
lines.push(' _describe "spec" specs');
lines.push('}');
lines.push('');
// Helper function for completing both changes and specs
lines.push('# Get both changes and specs');
- lines.push('_openspec_complete_items() {');
+ lines.push(`_${commandName}_complete_items() {`);
lines.push(' local -a items');
lines.push(' while IFS=$\'\\t\' read -r id desc; do');
lines.push(' items+=("$id:$desc")');
- lines.push(' done < <(openspec __complete changes 2>/dev/null)');
+ lines.push(` done < <(${commandName} __complete changes 2>/dev/null)`);
lines.push(' while IFS=$\'\\t\' read -r id desc; do');
lines.push(' items+=("$id:$desc")');
- lines.push(' done < <(openspec __complete specs 2>/dev/null)');
+ lines.push(` done < <(${commandName} __complete specs 2>/dev/null)`);
lines.push(' _describe "item" items');
lines.push('}');
lines.push('');
@@ -176,8 +177,8 @@ export class ZshGenerator implements CompletionGenerator {
/**
* Generate completion function for a specific command
*/
- private generateCommandFunction(cmd: CommandDefinition): string[] {
- const funcName = `_openspec_${this.sanitizeFunctionName(cmd.name)}`;
+ private generateCommandFunction(cmd: CommandDefinition, commandName: string): string[] {
+ const funcName = `_${commandName}_${this.sanitizeFunctionName(cmd.name)}`;
const lines: string[] = [];
lines.push(`${funcName}() {`);
@@ -216,7 +217,7 @@ export class ZshGenerator implements CompletionGenerator {
for (const subcmd of cmd.subcommands) {
lines.push(` ${subcmd.name})`);
- lines.push(` _openspec_${this.sanitizeFunctionName(cmd.name)}_${this.sanitizeFunctionName(subcmd.name)}`);
+ lines.push(` _${commandName}_${this.sanitizeFunctionName(cmd.name)}_${this.sanitizeFunctionName(subcmd.name)}`);
lines.push(' ;;');
}
@@ -234,7 +235,7 @@ export class ZshGenerator implements CompletionGenerator {
// Add positional argument completion
if (cmd.acceptsPositional) {
- const positionalSpec = this.generatePositionalSpec(cmd.positionalType);
+ const positionalSpec = this.generatePositionalSpec(cmd.positionalType, commandName);
lines.push(' ' + positionalSpec);
} else {
// Remove trailing backslash from last flag
@@ -250,7 +251,7 @@ export class ZshGenerator implements CompletionGenerator {
if (cmd.subcommands) {
for (const subcmd of cmd.subcommands) {
lines.push('');
- lines.push(...this.generateSubcommandFunction(cmd.name, subcmd));
+ lines.push(...this.generateSubcommandFunction(cmd.name, subcmd, commandName));
}
}
@@ -260,8 +261,8 @@ export class ZshGenerator implements CompletionGenerator {
/**
* Generate completion function for a subcommand
*/
- private generateSubcommandFunction(parentName: string, subcmd: CommandDefinition): string[] {
- const funcName = `_openspec_${this.sanitizeFunctionName(parentName)}_${this.sanitizeFunctionName(subcmd.name)}`;
+ private generateSubcommandFunction(parentName: string, subcmd: CommandDefinition, commandName: string): string[] {
+ const funcName = `_${commandName}_${this.sanitizeFunctionName(parentName)}_${this.sanitizeFunctionName(subcmd.name)}`;
const lines: string[] = [];
lines.push(`${funcName}() {`);
@@ -274,7 +275,7 @@ export class ZshGenerator implements CompletionGenerator {
// Add positional argument completion
if (subcmd.acceptsPositional) {
- const positionalSpec = this.generatePositionalSpec(subcmd.positionalType);
+ const positionalSpec = this.generatePositionalSpec(subcmd.positionalType, commandName);
lines.push(' ' + positionalSpec);
} else {
// Remove trailing backslash from last flag
@@ -326,14 +327,14 @@ export class ZshGenerator implements CompletionGenerator {
/**
* Generate positional argument specification
*/
- private generatePositionalSpec(positionalType?: string): string {
+ private generatePositionalSpec(positionalType?: string, commandName: string = 'openspec'): string {
switch (positionalType) {
case 'change-id':
- return "'*: :_openspec_complete_changes'";
+ return `'*: :_${commandName}_complete_changes'`;
case 'spec-id':
- return "'*: :_openspec_complete_specs'";
+ return `'*: :_${commandName}_complete_specs'`;
case 'change-or-spec-id':
- return "'*: :_openspec_complete_items'";
+ return `'*: :_${commandName}_complete_items'`;
case 'path':
return "'*:path:_files'";
case 'shell':
diff --git a/src/core/completions/types.ts b/src/core/completions/types.ts
index fef908b6..4d426e1b 100644
--- a/src/core/completions/types.ts
+++ b/src/core/completions/types.ts
@@ -84,7 +84,8 @@ export interface CompletionGenerator {
* Generate the completion script content
*
* @param commands - Command definitions to generate completions for
+ * @param commandName - The CLI command name (e.g., 'openspec' or 'plx')
* @returns The shell-specific completion script as a string
*/
- generate(commands: CommandDefinition[]): string;
+ generate(commands: CommandDefinition[], commandName?: string): string;
}
diff --git a/src/utils/command-name.ts b/src/utils/command-name.ts
new file mode 100644
index 00000000..150d5ef7
--- /dev/null
+++ b/src/utils/command-name.ts
@@ -0,0 +1,16 @@
+import path from 'path';
+
+/**
+ * Detect the CLI command name from the invocation path.
+ * Returns 'plx' if invoked via plx, otherwise defaults to 'openspec'.
+ */
+export function getCommandName(): string {
+ const scriptPath = process.argv[1] || '';
+ const scriptName = path.basename(scriptPath).replace(/\.js$/, '');
+ return scriptName === 'plx' ? 'plx' : 'openspec';
+}
+
+/**
+ * The detected command name for the current invocation.
+ */
+export const commandName = getCommandName();
diff --git a/test/core/completions/generators/zsh-generator.test.ts b/test/core/completions/generators/zsh-generator.test.ts
index 74bef2ac..67682122 100644
--- a/test/core/completions/generators/zsh-generator.test.ts
+++ b/test/core/completions/generators/zsh-generator.test.ts
@@ -32,7 +32,7 @@ describe('ZshGenerator', () => {
const script = generator.generate(commands);
expect(script).toContain('#compdef openspec');
- expect(script).toContain('# Zsh completion script for OpenSpec CLI');
+ expect(script).toContain('# Zsh completion script for openspec CLI');
expect(script).toContain('_openspec() {');
});