@fission-ai/openspec 0.18.0 โ 0.19.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 +52 -0
- package/dist/cli/index.js +32 -2
- package/dist/commands/artifact-workflow.js +6 -1
- package/dist/commands/completion.js +42 -6
- package/dist/core/completions/command-registry.js +7 -1
- package/dist/core/completions/factory.d.ts +15 -2
- package/dist/core/completions/factory.js +19 -1
- package/dist/core/completions/generators/bash-generator.d.ts +32 -0
- package/dist/core/completions/generators/bash-generator.js +174 -0
- package/dist/core/completions/generators/fish-generator.d.ts +32 -0
- package/dist/core/completions/generators/fish-generator.js +157 -0
- package/dist/core/completions/generators/powershell-generator.d.ts +32 -0
- package/dist/core/completions/generators/powershell-generator.js +198 -0
- package/dist/core/completions/generators/zsh-generator.d.ts +0 -14
- package/dist/core/completions/generators/zsh-generator.js +55 -124
- package/dist/core/completions/installers/bash-installer.d.ts +87 -0
- package/dist/core/completions/installers/bash-installer.js +318 -0
- package/dist/core/completions/installers/fish-installer.d.ts +43 -0
- package/dist/core/completions/installers/fish-installer.js +143 -0
- package/dist/core/completions/installers/powershell-installer.d.ts +88 -0
- package/dist/core/completions/installers/powershell-installer.js +327 -0
- package/dist/core/completions/installers/zsh-installer.d.ts +1 -12
- package/dist/core/completions/templates/bash-templates.d.ts +6 -0
- package/dist/core/completions/templates/bash-templates.js +24 -0
- package/dist/core/completions/templates/fish-templates.d.ts +7 -0
- package/dist/core/completions/templates/fish-templates.js +39 -0
- package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
- package/dist/core/completions/templates/powershell-templates.js +25 -0
- package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
- package/dist/core/completions/templates/zsh-templates.js +36 -0
- package/dist/core/config.js +1 -0
- package/dist/core/configurators/slash/codebuddy.js +6 -9
- package/dist/core/configurators/slash/continue.d.ts +9 -0
- package/dist/core/configurators/slash/continue.js +46 -0
- package/dist/core/configurators/slash/registry.js +3 -0
- package/dist/core/templates/skill-templates.d.ts +10 -0
- package/dist/core/templates/skill-templates.js +482 -20
- package/dist/telemetry/config.d.ts +32 -0
- package/dist/telemetry/config.js +68 -0
- package/dist/telemetry/index.d.ts +31 -0
- package/dist/telemetry/index.js +145 -0
- package/dist/utils/file-system.d.ts +6 -0
- package/dist/utils/file-system.js +43 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
Follow <a href="https://x.com/0xTab">@0xTab on X</a> for updates ยท Join the <a href="https://discord.gg/YctCnvvshC">OpenSpec Discord</a> for help and questions.
|
|
27
27
|
</p>
|
|
28
28
|
|
|
29
|
+
<p align="center">
|
|
30
|
+
<sub>๐งช <strong>New:</strong> <a href="docs/experimental-workflow.md">Experimental Workflow (OPSX)</a> โ schema-driven, hackable, fluid. Iterate on workflows without code changes.</sub>
|
|
31
|
+
</p>
|
|
32
|
+
|
|
29
33
|
# OpenSpec
|
|
30
34
|
|
|
31
35
|
OpenSpec aligns humans and AI coding assistants with spec-driven development so you agree on what to build before any code is written. **No API keys required.**
|
|
@@ -99,6 +103,7 @@ These tools have built-in OpenSpec commands. Select the OpenSpec integration whe
|
|
|
99
103
|
| **Cline** | Workflows in `.clinerules/workflows/` directory (`.clinerules/workflows/openspec-*.md`) |
|
|
100
104
|
| **CodeBuddy Code (CLI)** | `/openspec:proposal`, `/openspec:apply`, `/openspec:archive` (`.codebuddy/commands/`) โ see [docs](https://www.codebuddy.ai/cli) |
|
|
101
105
|
| **Codex** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (global: `~/.codex/prompts`, auto-installed) |
|
|
106
|
+
| **Continue** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.continue/prompts/`) |
|
|
102
107
|
| **CoStrict** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.cospec/openspec/commands/`) โ see [docs](https://costrict.ai)|
|
|
103
108
|
| **Crush** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.crush/commands/openspec/`) |
|
|
104
109
|
| **Cursor** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` |
|
|
@@ -368,6 +373,53 @@ Run `openspec update` whenever someone switches tools so your agents pick up the
|
|
|
368
373
|
2. **Refresh agent instructions**
|
|
369
374
|
- Run `openspec update` inside each project to regenerate AI guidance and ensure the latest slash commands are active.
|
|
370
375
|
|
|
376
|
+
## Experimental Features
|
|
377
|
+
|
|
378
|
+
<details>
|
|
379
|
+
<summary><strong>๐งช OPSX: Fluid, Iterative Workflow</strong> (Claude Code only)</summary>
|
|
380
|
+
|
|
381
|
+
**Why this exists:**
|
|
382
|
+
- Standard workflow is locked down โ you can't tweak instructions or customize
|
|
383
|
+
- When AI output is bad, you can't improve the prompts yourself
|
|
384
|
+
- Same workflow for everyone, no way to match how your team works
|
|
385
|
+
|
|
386
|
+
**What's different:**
|
|
387
|
+
- **Hackable** โ edit templates and schemas yourself, test immediately, no rebuild
|
|
388
|
+
- **Granular** โ each artifact has its own instructions, test and tweak individually
|
|
389
|
+
- **Customizable** โ define your own workflows, artifacts, and dependencies
|
|
390
|
+
- **Fluid** โ no phase gates, update any artifact anytime
|
|
391
|
+
|
|
392
|
+
```
|
|
393
|
+
You can always go back:
|
|
394
|
+
|
|
395
|
+
proposal โโโ specs โโโ design โโโ tasks โโโ implement
|
|
396
|
+
โฒ โฒ โฒ โ
|
|
397
|
+
โโโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโ
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
| Command | What it does |
|
|
401
|
+
|---------|--------------|
|
|
402
|
+
| `/opsx:new` | Start a new change |
|
|
403
|
+
| `/opsx:continue` | Create the next artifact (based on what's ready) |
|
|
404
|
+
| `/opsx:ff` | Fast-forward (all planning artifacts at once) |
|
|
405
|
+
| `/opsx:apply` | Implement tasks, updating artifacts as needed |
|
|
406
|
+
| `/opsx:archive` | Archive when done |
|
|
407
|
+
|
|
408
|
+
**Setup:** `openspec artifact-experimental-setup`
|
|
409
|
+
|
|
410
|
+
[Full documentation โ](docs/experimental-workflow.md)
|
|
411
|
+
|
|
412
|
+
</details>
|
|
413
|
+
|
|
414
|
+
<details>
|
|
415
|
+
<summary><strong>Telemetry</strong> โ OpenSpec collects anonymous usage stats (opt-out: <code>OPENSPEC_TELEMETRY=0</code>)</summary>
|
|
416
|
+
|
|
417
|
+
We collect only command names and version to understand usage patterns. No arguments, paths, content, or PII. Automatically disabled in CI.
|
|
418
|
+
|
|
419
|
+
**Opt-out:** `export OPENSPEC_TELEMETRY=0` or `export DO_NOT_TRACK=1`
|
|
420
|
+
|
|
421
|
+
</details>
|
|
422
|
+
|
|
371
423
|
## Contributing
|
|
372
424
|
|
|
373
425
|
- Install dependencies: `pnpm install`
|
package/dist/cli/index.js
CHANGED
|
@@ -15,21 +15,51 @@ import { ShowCommand } from '../commands/show.js';
|
|
|
15
15
|
import { CompletionCommand } from '../commands/completion.js';
|
|
16
16
|
import { registerConfigCommand } from '../commands/config.js';
|
|
17
17
|
import { registerArtifactWorkflowCommands } from '../commands/artifact-workflow.js';
|
|
18
|
+
import { maybeShowTelemetryNotice, trackCommand, shutdown } from '../telemetry/index.js';
|
|
18
19
|
const program = new Command();
|
|
19
20
|
const require = createRequire(import.meta.url);
|
|
20
21
|
const { version } = require('../../package.json');
|
|
22
|
+
/**
|
|
23
|
+
* Get the full command path for nested commands.
|
|
24
|
+
* For example: 'change show' -> 'change:show'
|
|
25
|
+
*/
|
|
26
|
+
function getCommandPath(command) {
|
|
27
|
+
const names = [];
|
|
28
|
+
let current = command;
|
|
29
|
+
while (current) {
|
|
30
|
+
const name = current.name();
|
|
31
|
+
// Skip the root 'openspec' command
|
|
32
|
+
if (name && name !== 'openspec') {
|
|
33
|
+
names.unshift(name);
|
|
34
|
+
}
|
|
35
|
+
current = current.parent;
|
|
36
|
+
}
|
|
37
|
+
return names.join(':') || 'openspec';
|
|
38
|
+
}
|
|
21
39
|
program
|
|
22
40
|
.name('openspec')
|
|
23
41
|
.description('AI-native system for spec-driven development')
|
|
24
42
|
.version(version);
|
|
25
43
|
// Global options
|
|
26
44
|
program.option('--no-color', 'Disable color output');
|
|
27
|
-
// Apply global flags before any command runs
|
|
28
|
-
|
|
45
|
+
// Apply global flags and telemetry before any command runs
|
|
46
|
+
// Note: preAction receives (thisCommand, actionCommand) where:
|
|
47
|
+
// - thisCommand: the command where hook was added (root program)
|
|
48
|
+
// - actionCommand: the command actually being executed (subcommand)
|
|
49
|
+
program.hook('preAction', async (thisCommand, actionCommand) => {
|
|
29
50
|
const opts = thisCommand.opts();
|
|
30
51
|
if (opts.color === false) {
|
|
31
52
|
process.env.NO_COLOR = '1';
|
|
32
53
|
}
|
|
54
|
+
// Show first-run telemetry notice (if not seen)
|
|
55
|
+
await maybeShowTelemetryNotice();
|
|
56
|
+
// Track command execution (use actionCommand to get the actual subcommand)
|
|
57
|
+
const commandPath = getCommandPath(actionCommand);
|
|
58
|
+
await trackCommand(commandPath, version);
|
|
59
|
+
});
|
|
60
|
+
// Shutdown telemetry after command completes
|
|
61
|
+
program.hook('postAction', async () => {
|
|
62
|
+
await shutdown();
|
|
33
63
|
});
|
|
34
64
|
const availableToolIds = AI_TOOLS.filter((tool) => tool.available).map((tool) => tool.value);
|
|
35
65
|
const toolsOptionDescription = `Configure AI tools non-interactively. Use "all", "none", or a comma-separated list of: ${availableToolIds.join(', ')}`;
|
|
@@ -14,7 +14,7 @@ import path from 'path';
|
|
|
14
14
|
import * as fs from 'fs';
|
|
15
15
|
import { loadChangeContext, formatChangeStatus, generateInstructions, listSchemas, listSchemasWithInfo, getSchemaDir, resolveSchema, ArtifactGraph, } from '../core/artifact-graph/index.js';
|
|
16
16
|
import { createChange, validateChangeName } from '../utils/change-utils.js';
|
|
17
|
-
import { getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, getOpsxApplyCommandTemplate, getOpsxFfCommandTemplate, getOpsxSyncCommandTemplate, getOpsxArchiveCommandTemplate } from '../core/templates/skill-templates.js';
|
|
17
|
+
import { getExploreSkillTemplate, getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getOpsxExploreCommandTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, getOpsxApplyCommandTemplate, getOpsxFfCommandTemplate, getOpsxSyncCommandTemplate, getOpsxArchiveCommandTemplate } from '../core/templates/skill-templates.js';
|
|
18
18
|
import { FileSystemUtils } from '../utils/file-system.js';
|
|
19
19
|
const DEFAULT_SCHEMA = 'spec-driven';
|
|
20
20
|
/**
|
|
@@ -580,6 +580,7 @@ async function artifactExperimentalSetupCommand() {
|
|
|
580
580
|
const skillsDir = path.join(projectRoot, '.claude', 'skills');
|
|
581
581
|
const commandsDir = path.join(projectRoot, '.claude', 'commands', 'opsx');
|
|
582
582
|
// Get skill templates
|
|
583
|
+
const exploreSkill = getExploreSkillTemplate();
|
|
583
584
|
const newChangeSkill = getNewChangeSkillTemplate();
|
|
584
585
|
const continueChangeSkill = getContinueChangeSkillTemplate();
|
|
585
586
|
const applyChangeSkill = getApplyChangeSkillTemplate();
|
|
@@ -587,6 +588,7 @@ async function artifactExperimentalSetupCommand() {
|
|
|
587
588
|
const syncSpecsSkill = getSyncSpecsSkillTemplate();
|
|
588
589
|
const archiveChangeSkill = getArchiveChangeSkillTemplate();
|
|
589
590
|
// Get command templates
|
|
591
|
+
const exploreCommand = getOpsxExploreCommandTemplate();
|
|
590
592
|
const newCommand = getOpsxNewCommandTemplate();
|
|
591
593
|
const continueCommand = getOpsxContinueCommandTemplate();
|
|
592
594
|
const applyCommand = getOpsxApplyCommandTemplate();
|
|
@@ -595,6 +597,7 @@ async function artifactExperimentalSetupCommand() {
|
|
|
595
597
|
const archiveCommand = getOpsxArchiveCommandTemplate();
|
|
596
598
|
// Create skill directories and SKILL.md files
|
|
597
599
|
const skills = [
|
|
600
|
+
{ template: exploreSkill, dirName: 'openspec-explore' },
|
|
598
601
|
{ template: newChangeSkill, dirName: 'openspec-new-change' },
|
|
599
602
|
{ template: continueChangeSkill, dirName: 'openspec-continue-change' },
|
|
600
603
|
{ template: applyChangeSkill, dirName: 'openspec-apply-change' },
|
|
@@ -620,6 +623,7 @@ ${template.instructions}
|
|
|
620
623
|
}
|
|
621
624
|
// Create slash command files
|
|
622
625
|
const commands = [
|
|
626
|
+
{ template: exploreCommand, fileName: 'explore.md' },
|
|
623
627
|
{ template: newCommand, fileName: 'new.md' },
|
|
624
628
|
{ template: continueCommand, fileName: 'continue.md' },
|
|
625
629
|
{ template: applyCommand, fileName: 'apply.md' },
|
|
@@ -672,6 +676,7 @@ ${template.content}
|
|
|
672
676
|
console.log(' โข "Implement the tasks for this change"');
|
|
673
677
|
console.log();
|
|
674
678
|
console.log(' ' + chalk.cyan('Slash Commands') + ' for explicit invocation:');
|
|
679
|
+
console.log(' โข /opsx:explore - Think through ideas, investigate problems');
|
|
675
680
|
console.log(' โข /opsx:new - Start a new change');
|
|
676
681
|
console.log(' โข /opsx:continue - Create the next artifact');
|
|
677
682
|
console.log(' โข /opsx:apply - Implement tasks');
|
|
@@ -107,8 +107,24 @@ export class CompletionCommand {
|
|
|
107
107
|
if (result.backupPath) {
|
|
108
108
|
console.log(` Backup created: ${result.backupPath}`);
|
|
109
109
|
}
|
|
110
|
-
if
|
|
111
|
-
|
|
110
|
+
// Check if any shell config was updated
|
|
111
|
+
const configWasUpdated = result.zshrcConfigured || result.bashrcConfigured || result.profileConfigured;
|
|
112
|
+
if (configWasUpdated) {
|
|
113
|
+
const configPaths = {
|
|
114
|
+
zsh: '~/.zshrc',
|
|
115
|
+
bash: '~/.bashrc',
|
|
116
|
+
fish: '~/.config/fish/config.fish',
|
|
117
|
+
powershell: '$PROFILE',
|
|
118
|
+
};
|
|
119
|
+
const configPath = configPaths[shell] || 'config file';
|
|
120
|
+
console.log(` ${configPath} configured automatically`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Display warnings if present
|
|
124
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
125
|
+
console.log('');
|
|
126
|
+
for (const warning of result.warnings) {
|
|
127
|
+
console.log(warning);
|
|
112
128
|
}
|
|
113
129
|
}
|
|
114
130
|
// Print instructions (only shown if .zshrc wasn't auto-configured)
|
|
@@ -118,9 +134,21 @@ export class CompletionCommand {
|
|
|
118
134
|
console.log(instruction);
|
|
119
135
|
}
|
|
120
136
|
}
|
|
121
|
-
else
|
|
122
|
-
|
|
123
|
-
|
|
137
|
+
else {
|
|
138
|
+
// Check if any shell config was updated (InstallationResult has: zshrcConfigured, bashrcConfigured, profileConfigured)
|
|
139
|
+
const configWasUpdated = result.zshrcConfigured || result.bashrcConfigured || result.profileConfigured;
|
|
140
|
+
if (configWasUpdated) {
|
|
141
|
+
console.log('');
|
|
142
|
+
// Shell-specific reload instructions
|
|
143
|
+
const reloadCommands = {
|
|
144
|
+
zsh: 'exec zsh',
|
|
145
|
+
bash: 'exec bash',
|
|
146
|
+
fish: 'exec fish',
|
|
147
|
+
powershell: '. $PROFILE',
|
|
148
|
+
};
|
|
149
|
+
const reloadCmd = reloadCommands[shell] || `restart your ${shell} shell`;
|
|
150
|
+
console.log(`Restart your shell or run: ${reloadCmd}`);
|
|
151
|
+
}
|
|
124
152
|
}
|
|
125
153
|
}
|
|
126
154
|
else {
|
|
@@ -142,8 +170,16 @@ export class CompletionCommand {
|
|
|
142
170
|
// Prompt for confirmation unless --yes flag is provided
|
|
143
171
|
if (!skipConfirmation) {
|
|
144
172
|
const { confirm } = await import('@inquirer/prompts');
|
|
173
|
+
// Get shell-specific config file path
|
|
174
|
+
const configPaths = {
|
|
175
|
+
zsh: '~/.zshrc',
|
|
176
|
+
bash: '~/.bashrc',
|
|
177
|
+
fish: 'Fish configuration', // Fish doesn't modify profile, just removes script file
|
|
178
|
+
powershell: '$PROFILE',
|
|
179
|
+
};
|
|
180
|
+
const configPath = configPaths[shell] || `${shell} configuration`;
|
|
145
181
|
const confirmed = await confirm({
|
|
146
|
-
message:
|
|
182
|
+
message: `Remove OpenSpec configuration from ${configPath}?`,
|
|
147
183
|
default: false,
|
|
148
184
|
});
|
|
149
185
|
if (!confirmed) {
|
|
@@ -281,7 +281,13 @@ export const COMMAND_REGISTRY = [
|
|
|
281
281
|
description: 'Uninstall completion script for a shell',
|
|
282
282
|
acceptsPositional: true,
|
|
283
283
|
positionalType: 'shell',
|
|
284
|
-
flags: [
|
|
284
|
+
flags: [
|
|
285
|
+
{
|
|
286
|
+
name: 'yes',
|
|
287
|
+
short: 'y',
|
|
288
|
+
description: 'Skip confirmation prompts',
|
|
289
|
+
},
|
|
290
|
+
],
|
|
285
291
|
},
|
|
286
292
|
],
|
|
287
293
|
},
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { CompletionGenerator } from './types.js';
|
|
2
|
-
import { InstallationResult } from './installers/zsh-installer.js';
|
|
3
2
|
import { SupportedShell } from '../../utils/shell-detection.js';
|
|
3
|
+
/**
|
|
4
|
+
* Common installation result interface
|
|
5
|
+
*/
|
|
6
|
+
export interface InstallationResult {
|
|
7
|
+
success: boolean;
|
|
8
|
+
installedPath?: string;
|
|
9
|
+
backupPath?: string;
|
|
10
|
+
message: string;
|
|
11
|
+
instructions?: string[];
|
|
12
|
+
warnings?: string[];
|
|
13
|
+
isOhMyZsh?: boolean;
|
|
14
|
+
zshrcConfigured?: boolean;
|
|
15
|
+
bashrcConfigured?: boolean;
|
|
16
|
+
profileConfigured?: boolean;
|
|
17
|
+
}
|
|
4
18
|
/**
|
|
5
19
|
* Interface for completion installers
|
|
6
20
|
*/
|
|
@@ -11,7 +25,6 @@ export interface CompletionInstaller {
|
|
|
11
25
|
message: string;
|
|
12
26
|
}>;
|
|
13
27
|
}
|
|
14
|
-
export type { InstallationResult };
|
|
15
28
|
/**
|
|
16
29
|
* Factory for creating completion generators and installers
|
|
17
30
|
* This design makes it easy to add support for additional shells
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { ZshGenerator } from './generators/zsh-generator.js';
|
|
2
|
+
import { BashGenerator } from './generators/bash-generator.js';
|
|
3
|
+
import { FishGenerator } from './generators/fish-generator.js';
|
|
4
|
+
import { PowerShellGenerator } from './generators/powershell-generator.js';
|
|
2
5
|
import { ZshInstaller } from './installers/zsh-installer.js';
|
|
6
|
+
import { BashInstaller } from './installers/bash-installer.js';
|
|
7
|
+
import { FishInstaller } from './installers/fish-installer.js';
|
|
8
|
+
import { PowerShellInstaller } from './installers/powershell-installer.js';
|
|
3
9
|
/**
|
|
4
10
|
* Factory for creating completion generators and installers
|
|
5
11
|
* This design makes it easy to add support for additional shells
|
|
6
12
|
*/
|
|
7
13
|
export class CompletionFactory {
|
|
8
|
-
static SUPPORTED_SHELLS = ['zsh'];
|
|
14
|
+
static SUPPORTED_SHELLS = ['zsh', 'bash', 'fish', 'powershell'];
|
|
9
15
|
/**
|
|
10
16
|
* Create a completion generator for the specified shell
|
|
11
17
|
*
|
|
@@ -17,6 +23,12 @@ export class CompletionFactory {
|
|
|
17
23
|
switch (shell) {
|
|
18
24
|
case 'zsh':
|
|
19
25
|
return new ZshGenerator();
|
|
26
|
+
case 'bash':
|
|
27
|
+
return new BashGenerator();
|
|
28
|
+
case 'fish':
|
|
29
|
+
return new FishGenerator();
|
|
30
|
+
case 'powershell':
|
|
31
|
+
return new PowerShellGenerator();
|
|
20
32
|
default:
|
|
21
33
|
throw new Error(`Unsupported shell: ${shell}`);
|
|
22
34
|
}
|
|
@@ -32,6 +44,12 @@ export class CompletionFactory {
|
|
|
32
44
|
switch (shell) {
|
|
33
45
|
case 'zsh':
|
|
34
46
|
return new ZshInstaller();
|
|
47
|
+
case 'bash':
|
|
48
|
+
return new BashInstaller();
|
|
49
|
+
case 'fish':
|
|
50
|
+
return new FishInstaller();
|
|
51
|
+
case 'powershell':
|
|
52
|
+
return new PowerShellInstaller();
|
|
35
53
|
default:
|
|
36
54
|
throw new Error(`Unsupported shell: ${shell}`);
|
|
37
55
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CompletionGenerator, CommandDefinition } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates Bash completion scripts for the OpenSpec CLI.
|
|
4
|
+
* Follows Bash completion conventions using complete builtin and COMPREPLY array.
|
|
5
|
+
*/
|
|
6
|
+
export declare class BashGenerator implements CompletionGenerator {
|
|
7
|
+
readonly shell: "bash";
|
|
8
|
+
/**
|
|
9
|
+
* Generate a Bash completion script
|
|
10
|
+
*
|
|
11
|
+
* @param commands - Command definitions to generate completions for
|
|
12
|
+
* @returns Bash completion script as a string
|
|
13
|
+
*/
|
|
14
|
+
generate(commands: CommandDefinition[]): string;
|
|
15
|
+
/**
|
|
16
|
+
* Generate completion case logic for a command
|
|
17
|
+
*/
|
|
18
|
+
private generateCommandCase;
|
|
19
|
+
/**
|
|
20
|
+
* Generate argument completion (flags and positional arguments)
|
|
21
|
+
*/
|
|
22
|
+
private generateArgumentCompletion;
|
|
23
|
+
/**
|
|
24
|
+
* Generate positional argument completion based on type
|
|
25
|
+
*/
|
|
26
|
+
private generatePositionalCompletion;
|
|
27
|
+
/**
|
|
28
|
+
* Escape command/subcommand names for safe use in Bash scripts
|
|
29
|
+
*/
|
|
30
|
+
private escapeCommandName;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=bash-generator.d.ts.map
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { BASH_DYNAMIC_HELPERS } from '../templates/bash-templates.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates Bash completion scripts for the OpenSpec CLI.
|
|
4
|
+
* Follows Bash completion conventions using complete builtin and COMPREPLY array.
|
|
5
|
+
*/
|
|
6
|
+
export class BashGenerator {
|
|
7
|
+
shell = 'bash';
|
|
8
|
+
/**
|
|
9
|
+
* Generate a Bash completion script
|
|
10
|
+
*
|
|
11
|
+
* @param commands - Command definitions to generate completions for
|
|
12
|
+
* @returns Bash completion script as a string
|
|
13
|
+
*/
|
|
14
|
+
generate(commands) {
|
|
15
|
+
// Build command list for top-level completions
|
|
16
|
+
const commandList = commands.map(c => this.escapeCommandName(c.name)).join(' ');
|
|
17
|
+
// Build command cases using push() for loop clarity
|
|
18
|
+
const caseLines = [];
|
|
19
|
+
for (const cmd of commands) {
|
|
20
|
+
caseLines.push(` ${cmd.name})`);
|
|
21
|
+
caseLines.push(...this.generateCommandCase(cmd, ' '));
|
|
22
|
+
caseLines.push(' ;;');
|
|
23
|
+
}
|
|
24
|
+
const commandCases = caseLines.join('\n');
|
|
25
|
+
// Dynamic completion helpers from template
|
|
26
|
+
const helpers = BASH_DYNAMIC_HELPERS;
|
|
27
|
+
// Assemble final script with template literal
|
|
28
|
+
return `# Bash completion script for OpenSpec CLI
|
|
29
|
+
# Auto-generated - do not edit manually
|
|
30
|
+
|
|
31
|
+
_openspec_completion() {
|
|
32
|
+
local cur prev words cword
|
|
33
|
+
|
|
34
|
+
# Use _init_completion if available (from bash-completion package)
|
|
35
|
+
# The -n : option prevents colons from being treated as word separators
|
|
36
|
+
# (important for spec/change IDs that may contain colons)
|
|
37
|
+
# Otherwise, fall back to manual initialization
|
|
38
|
+
if declare -F _init_completion >/dev/null 2>&1; then
|
|
39
|
+
_init_completion -n : || return
|
|
40
|
+
else
|
|
41
|
+
# Manual fallback when bash-completion is not installed
|
|
42
|
+
COMPREPLY=()
|
|
43
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
44
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
45
|
+
words=("\${COMP_WORDS[@]}")
|
|
46
|
+
cword=$COMP_CWORD
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
local cmd="\${words[1]}"
|
|
50
|
+
local subcmd="\${words[2]}"
|
|
51
|
+
|
|
52
|
+
# Top-level commands
|
|
53
|
+
if [[ $cword -eq 1 ]]; then
|
|
54
|
+
local commands="${commandList}"
|
|
55
|
+
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
|
|
56
|
+
return 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# Command-specific completion
|
|
60
|
+
case "$cmd" in
|
|
61
|
+
${commandCases}
|
|
62
|
+
esac
|
|
63
|
+
|
|
64
|
+
return 0
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
${helpers}
|
|
68
|
+
complete -F _openspec_completion openspec
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Generate completion case logic for a command
|
|
73
|
+
*/
|
|
74
|
+
generateCommandCase(cmd, indent) {
|
|
75
|
+
const lines = [];
|
|
76
|
+
// Handle subcommands
|
|
77
|
+
if (cmd.subcommands && cmd.subcommands.length > 0) {
|
|
78
|
+
// First, check if user is typing a flag for the parent command
|
|
79
|
+
if (cmd.flags.length > 0) {
|
|
80
|
+
lines.push(`${indent}if [[ "$cur" == -* ]]; then`);
|
|
81
|
+
const flags = cmd.flags.map(f => {
|
|
82
|
+
const parts = [];
|
|
83
|
+
if (f.short)
|
|
84
|
+
parts.push(`-${f.short}`);
|
|
85
|
+
parts.push(`--${f.name}`);
|
|
86
|
+
return parts.join(' ');
|
|
87
|
+
}).join(' ');
|
|
88
|
+
lines.push(`${indent} local flags="${flags}"`);
|
|
89
|
+
lines.push(`${indent} COMPREPLY=($(compgen -W "$flags" -- "$cur"))`);
|
|
90
|
+
lines.push(`${indent} return 0`);
|
|
91
|
+
lines.push(`${indent}fi`);
|
|
92
|
+
lines.push('');
|
|
93
|
+
}
|
|
94
|
+
lines.push(`${indent}if [[ $cword -eq 2 ]]; then`);
|
|
95
|
+
lines.push(`${indent} local subcommands="` + cmd.subcommands.map(s => this.escapeCommandName(s.name)).join(' ') + '"');
|
|
96
|
+
lines.push(`${indent} COMPREPLY=($(compgen -W "$subcommands" -- "$cur"))`);
|
|
97
|
+
lines.push(`${indent} return 0`);
|
|
98
|
+
lines.push(`${indent}fi`);
|
|
99
|
+
lines.push('');
|
|
100
|
+
lines.push(`${indent}case "$subcmd" in`);
|
|
101
|
+
for (const subcmd of cmd.subcommands) {
|
|
102
|
+
lines.push(`${indent} ${subcmd.name})`);
|
|
103
|
+
lines.push(...this.generateArgumentCompletion(subcmd, indent + ' '));
|
|
104
|
+
lines.push(`${indent} ;;`);
|
|
105
|
+
}
|
|
106
|
+
lines.push(`${indent}esac`);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// No subcommands, just complete arguments
|
|
110
|
+
lines.push(...this.generateArgumentCompletion(cmd, indent));
|
|
111
|
+
}
|
|
112
|
+
return lines;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Generate argument completion (flags and positional arguments)
|
|
116
|
+
*/
|
|
117
|
+
generateArgumentCompletion(cmd, indent) {
|
|
118
|
+
const lines = [];
|
|
119
|
+
// Check for flag completion
|
|
120
|
+
if (cmd.flags.length > 0) {
|
|
121
|
+
lines.push(`${indent}if [[ "$cur" == -* ]]; then`);
|
|
122
|
+
const flags = cmd.flags.map(f => {
|
|
123
|
+
const parts = [];
|
|
124
|
+
if (f.short)
|
|
125
|
+
parts.push(`-${f.short}`);
|
|
126
|
+
parts.push(`--${f.name}`);
|
|
127
|
+
return parts.join(' ');
|
|
128
|
+
}).join(' ');
|
|
129
|
+
lines.push(`${indent} local flags="${flags}"`);
|
|
130
|
+
lines.push(`${indent} COMPREPLY=($(compgen -W "$flags" -- "$cur"))`);
|
|
131
|
+
lines.push(`${indent} return 0`);
|
|
132
|
+
lines.push(`${indent}fi`);
|
|
133
|
+
lines.push('');
|
|
134
|
+
}
|
|
135
|
+
// Handle positional completions
|
|
136
|
+
if (cmd.acceptsPositional) {
|
|
137
|
+
lines.push(...this.generatePositionalCompletion(cmd.positionalType, indent));
|
|
138
|
+
}
|
|
139
|
+
return lines;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Generate positional argument completion based on type
|
|
143
|
+
*/
|
|
144
|
+
generatePositionalCompletion(positionalType, indent) {
|
|
145
|
+
const lines = [];
|
|
146
|
+
switch (positionalType) {
|
|
147
|
+
case 'change-id':
|
|
148
|
+
lines.push(`${indent}_openspec_complete_changes`);
|
|
149
|
+
break;
|
|
150
|
+
case 'spec-id':
|
|
151
|
+
lines.push(`${indent}_openspec_complete_specs`);
|
|
152
|
+
break;
|
|
153
|
+
case 'change-or-spec-id':
|
|
154
|
+
lines.push(`${indent}_openspec_complete_items`);
|
|
155
|
+
break;
|
|
156
|
+
case 'shell':
|
|
157
|
+
lines.push(`${indent}local shells="zsh bash fish powershell"`);
|
|
158
|
+
lines.push(`${indent}COMPREPLY=($(compgen -W "$shells" -- "$cur"))`);
|
|
159
|
+
break;
|
|
160
|
+
case 'path':
|
|
161
|
+
lines.push(`${indent}COMPREPLY=($(compgen -f -- "$cur"))`);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
return lines;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Escape command/subcommand names for safe use in Bash scripts
|
|
168
|
+
*/
|
|
169
|
+
escapeCommandName(name) {
|
|
170
|
+
// Escape shell metacharacters to prevent command injection
|
|
171
|
+
return name.replace(/["\$`\\]/g, '\\$&');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=bash-generator.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CompletionGenerator, CommandDefinition } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates Fish completion scripts for the OpenSpec CLI.
|
|
4
|
+
* Follows Fish completion conventions using the complete command.
|
|
5
|
+
*/
|
|
6
|
+
export declare class FishGenerator implements CompletionGenerator {
|
|
7
|
+
readonly shell: "fish";
|
|
8
|
+
/**
|
|
9
|
+
* Generate a Fish completion script
|
|
10
|
+
*
|
|
11
|
+
* @param commands - Command definitions to generate completions for
|
|
12
|
+
* @returns Fish completion script as a string
|
|
13
|
+
*/
|
|
14
|
+
generate(commands: CommandDefinition[]): string;
|
|
15
|
+
/**
|
|
16
|
+
* Generate completions for a specific command
|
|
17
|
+
*/
|
|
18
|
+
private generateCommandCompletions;
|
|
19
|
+
/**
|
|
20
|
+
* Generate flag completion
|
|
21
|
+
*/
|
|
22
|
+
private generateFlagCompletion;
|
|
23
|
+
/**
|
|
24
|
+
* Generate positional argument completion
|
|
25
|
+
*/
|
|
26
|
+
private generatePositionalCompletion;
|
|
27
|
+
/**
|
|
28
|
+
* Escape description text for Fish
|
|
29
|
+
*/
|
|
30
|
+
private escapeDescription;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=fish-generator.d.ts.map
|