@bobby_z/openspec 0.0.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/LICENSE +22 -0
- package/README.md +204 -0
- package/bin/openspec.js +3 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +482 -0
- package/dist/commands/change.d.ts +35 -0
- package/dist/commands/change.js +277 -0
- package/dist/commands/completion.d.ts +72 -0
- package/dist/commands/completion.js +257 -0
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.js +198 -0
- package/dist/commands/feedback.d.ts +9 -0
- package/dist/commands/feedback.js +183 -0
- package/dist/commands/schema.d.ts +6 -0
- package/dist/commands/schema.js +869 -0
- package/dist/commands/show.d.ts +14 -0
- package/dist/commands/show.js +132 -0
- package/dist/commands/spec.d.ts +15 -0
- package/dist/commands/spec.js +225 -0
- package/dist/commands/validate.d.ts +24 -0
- package/dist/commands/validate.js +294 -0
- package/dist/commands/workflow/index.d.ts +17 -0
- package/dist/commands/workflow/index.js +12 -0
- package/dist/commands/workflow/instructions.d.ts +29 -0
- package/dist/commands/workflow/instructions.js +381 -0
- package/dist/commands/workflow/new-change.d.ts +11 -0
- package/dist/commands/workflow/new-change.js +44 -0
- package/dist/commands/workflow/schemas.d.ts +10 -0
- package/dist/commands/workflow/schemas.js +34 -0
- package/dist/commands/workflow/shared.d.ts +52 -0
- package/dist/commands/workflow/shared.js +111 -0
- package/dist/commands/workflow/status.d.ts +14 -0
- package/dist/commands/workflow/status.js +58 -0
- package/dist/commands/workflow/templates.d.ts +16 -0
- package/dist/commands/workflow/templates.js +68 -0
- package/dist/core/archive.d.ts +11 -0
- package/dist/core/archive.js +328 -0
- package/dist/core/artifact-graph/graph.d.ts +56 -0
- package/dist/core/artifact-graph/graph.js +141 -0
- package/dist/core/artifact-graph/index.d.ts +7 -0
- package/dist/core/artifact-graph/index.js +13 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts +143 -0
- package/dist/core/artifact-graph/instruction-loader.js +214 -0
- package/dist/core/artifact-graph/resolver.d.ts +81 -0
- package/dist/core/artifact-graph/resolver.js +257 -0
- package/dist/core/artifact-graph/schema.d.ts +13 -0
- package/dist/core/artifact-graph/schema.js +108 -0
- package/dist/core/artifact-graph/state.d.ts +12 -0
- package/dist/core/artifact-graph/state.js +54 -0
- package/dist/core/artifact-graph/types.d.ts +45 -0
- package/dist/core/artifact-graph/types.js +43 -0
- package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
- package/dist/core/command-generation/adapters/amazon-q.js +26 -0
- package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
- package/dist/core/command-generation/adapters/antigravity.js +26 -0
- package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
- package/dist/core/command-generation/adapters/auggie.js +27 -0
- package/dist/core/command-generation/adapters/claude.d.ts +13 -0
- package/dist/core/command-generation/adapters/claude.js +50 -0
- package/dist/core/command-generation/adapters/cline.d.ts +14 -0
- package/dist/core/command-generation/adapters/cline.js +27 -0
- package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
- package/dist/core/command-generation/adapters/codebuddy.js +28 -0
- package/dist/core/command-generation/adapters/codex.d.ts +16 -0
- package/dist/core/command-generation/adapters/codex.js +39 -0
- package/dist/core/command-generation/adapters/continue.d.ts +13 -0
- package/dist/core/command-generation/adapters/continue.js +28 -0
- package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
- package/dist/core/command-generation/adapters/costrict.js +27 -0
- package/dist/core/command-generation/adapters/crush.d.ts +13 -0
- package/dist/core/command-generation/adapters/crush.js +30 -0
- package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
- package/dist/core/command-generation/adapters/cursor.js +44 -0
- package/dist/core/command-generation/adapters/devagent.d.ts +15 -0
- package/dist/core/command-generation/adapters/devagent.js +28 -0
- package/dist/core/command-generation/adapters/factory.d.ts +13 -0
- package/dist/core/command-generation/adapters/factory.js +27 -0
- package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
- package/dist/core/command-generation/adapters/gemini.js +26 -0
- package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
- package/dist/core/command-generation/adapters/github-copilot.js +26 -0
- package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
- package/dist/core/command-generation/adapters/iflow.js +29 -0
- package/dist/core/command-generation/adapters/index.d.ts +28 -0
- package/dist/core/command-generation/adapters/index.js +28 -0
- package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
- package/dist/core/command-generation/adapters/kilocode.js +23 -0
- package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
- package/dist/core/command-generation/adapters/opencode.js +29 -0
- package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
- package/dist/core/command-generation/adapters/qoder.js +30 -0
- package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
- package/dist/core/command-generation/adapters/qwen.js +26 -0
- package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
- package/dist/core/command-generation/adapters/roocode.js +27 -0
- package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
- package/dist/core/command-generation/adapters/windsurf.js +51 -0
- package/dist/core/command-generation/generator.d.ts +21 -0
- package/dist/core/command-generation/generator.js +27 -0
- package/dist/core/command-generation/index.d.ts +22 -0
- package/dist/core/command-generation/index.js +24 -0
- package/dist/core/command-generation/registry.d.ts +36 -0
- package/dist/core/command-generation/registry.js +90 -0
- package/dist/core/command-generation/types.d.ts +56 -0
- package/dist/core/command-generation/types.js +8 -0
- package/dist/core/completions/command-registry.d.ts +7 -0
- package/dist/core/completions/command-registry.js +454 -0
- package/dist/core/completions/completion-provider.d.ts +60 -0
- package/dist/core/completions/completion-provider.js +102 -0
- package/dist/core/completions/factory.d.ts +64 -0
- package/dist/core/completions/factory.js +75 -0
- 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 +33 -0
- package/dist/core/completions/generators/powershell-generator.js +207 -0
- package/dist/core/completions/generators/zsh-generator.d.ts +44 -0
- package/dist/core/completions/generators/zsh-generator.js +250 -0
- 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 +125 -0
- package/dist/core/completions/installers/zsh-installer.js +449 -0
- 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/completions/types.d.ts +79 -0
- package/dist/core/completions/types.js +2 -0
- package/dist/core/config-prompts.d.ts +9 -0
- package/dist/core/config-prompts.js +34 -0
- package/dist/core/config-schema.d.ts +76 -0
- package/dist/core/config-schema.js +200 -0
- package/dist/core/config.d.ts +17 -0
- package/dist/core/config.js +175 -0
- package/dist/core/converters/json-converter.d.ts +6 -0
- package/dist/core/converters/json-converter.js +51 -0
- package/dist/core/global-config.d.ts +39 -0
- package/dist/core/global-config.js +115 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +3 -0
- package/dist/core/init.d.ts +32 -0
- package/dist/core/init.js +447 -0
- package/dist/core/legacy-cleanup.d.ts +162 -0
- package/dist/core/legacy-cleanup.js +520 -0
- package/dist/core/list.d.ts +9 -0
- package/dist/core/list.js +171 -0
- package/dist/core/parsers/change-parser.d.ts +13 -0
- package/dist/core/parsers/change-parser.js +193 -0
- package/dist/core/parsers/markdown-parser.d.ts +22 -0
- package/dist/core/parsers/markdown-parser.js +187 -0
- package/dist/core/parsers/requirement-blocks.d.ts +37 -0
- package/dist/core/parsers/requirement-blocks.js +201 -0
- package/dist/core/project-config.d.ts +64 -0
- package/dist/core/project-config.js +223 -0
- package/dist/core/schemas/base.schema.d.ts +13 -0
- package/dist/core/schemas/base.schema.js +13 -0
- package/dist/core/schemas/change.schema.d.ts +73 -0
- package/dist/core/schemas/change.schema.js +31 -0
- package/dist/core/schemas/index.d.ts +4 -0
- package/dist/core/schemas/index.js +4 -0
- package/dist/core/schemas/spec.schema.d.ts +18 -0
- package/dist/core/schemas/spec.schema.js +15 -0
- package/dist/core/shared/index.d.ts +8 -0
- package/dist/core/shared/index.js +8 -0
- package/dist/core/shared/skill-generation.d.ts +42 -0
- package/dist/core/shared/skill-generation.js +80 -0
- package/dist/core/shared/tool-detection.d.ts +66 -0
- package/dist/core/shared/tool-detection.js +140 -0
- package/dist/core/specs-apply.d.ts +73 -0
- package/dist/core/specs-apply.js +384 -0
- package/dist/core/styles/palette.d.ts +7 -0
- package/dist/core/styles/palette.js +8 -0
- package/dist/core/templates/index.d.ts +8 -0
- package/dist/core/templates/index.js +9 -0
- package/dist/core/templates/skill-templates.d.ts +122 -0
- package/dist/core/templates/skill-templates.js +3437 -0
- package/dist/core/update.d.ts +42 -0
- package/dist/core/update.js +311 -0
- package/dist/core/validation/constants.d.ts +34 -0
- package/dist/core/validation/constants.js +40 -0
- package/dist/core/validation/types.d.ts +18 -0
- package/dist/core/validation/types.js +2 -0
- package/dist/core/validation/validator.d.ts +33 -0
- package/dist/core/validation/validator.js +409 -0
- package/dist/core/view.d.ts +8 -0
- package/dist/core/view.js +168 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/prompts/searchable-multi-select.d.ts +27 -0
- package/dist/prompts/searchable-multi-select.js +149 -0
- 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/ui/ascii-patterns.d.ts +16 -0
- package/dist/ui/ascii-patterns.js +133 -0
- package/dist/ui/welcome-screen.d.ts +10 -0
- package/dist/ui/welcome-screen.js +146 -0
- package/dist/utils/change-metadata.d.ts +51 -0
- package/dist/utils/change-metadata.js +147 -0
- package/dist/utils/change-utils.d.ts +62 -0
- package/dist/utils/change-utils.js +121 -0
- package/dist/utils/command-references.d.ts +18 -0
- package/dist/utils/command-references.js +20 -0
- package/dist/utils/file-system.d.ts +36 -0
- package/dist/utils/file-system.js +281 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/interactive.d.ts +18 -0
- package/dist/utils/interactive.js +21 -0
- package/dist/utils/item-discovery.d.ts +4 -0
- package/dist/utils/item-discovery.js +72 -0
- package/dist/utils/match.d.ts +3 -0
- package/dist/utils/match.js +22 -0
- package/dist/utils/shell-detection.d.ts +20 -0
- package/dist/utils/shell-detection.js +41 -0
- package/dist/utils/task-progress.d.ts +8 -0
- package/dist/utils/task-progress.js +36 -0
- package/package.json +83 -0
- package/schemas/spec-driven/schema.yaml +151 -0
- package/schemas/spec-driven/templates/design.md +21 -0
- package/schemas/spec-driven/templates/proposal.md +25 -0
- package/schemas/spec-driven/templates/spec.md +10 -0
- package/schemas/spec-driven/templates/tasks.md +9 -0
- package/scripts/postinstall.js +147 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
// Constants
|
|
5
|
+
export const GLOBAL_CONFIG_DIR_NAME = 'openspec';
|
|
6
|
+
export const GLOBAL_CONFIG_FILE_NAME = 'config.json';
|
|
7
|
+
export const GLOBAL_DATA_DIR_NAME = 'openspec';
|
|
8
|
+
const DEFAULT_CONFIG = {
|
|
9
|
+
featureFlags: {}
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Gets the global configuration directory path following XDG Base Directory Specification.
|
|
13
|
+
*
|
|
14
|
+
* - All platforms: $XDG_CONFIG_HOME/openspec/ if XDG_CONFIG_HOME is set
|
|
15
|
+
* - Unix/macOS fallback: ~/.config/openspec/
|
|
16
|
+
* - Windows fallback: %APPDATA%/openspec/
|
|
17
|
+
*/
|
|
18
|
+
export function getGlobalConfigDir() {
|
|
19
|
+
// XDG_CONFIG_HOME takes precedence on all platforms when explicitly set
|
|
20
|
+
const xdgConfigHome = process.env.XDG_CONFIG_HOME;
|
|
21
|
+
if (xdgConfigHome) {
|
|
22
|
+
return path.join(xdgConfigHome, GLOBAL_CONFIG_DIR_NAME);
|
|
23
|
+
}
|
|
24
|
+
const platform = os.platform();
|
|
25
|
+
if (platform === 'win32') {
|
|
26
|
+
// Windows: use %APPDATA%
|
|
27
|
+
const appData = process.env.APPDATA;
|
|
28
|
+
if (appData) {
|
|
29
|
+
return path.join(appData, GLOBAL_CONFIG_DIR_NAME);
|
|
30
|
+
}
|
|
31
|
+
// Fallback for Windows if APPDATA is not set
|
|
32
|
+
return path.join(os.homedir(), 'AppData', 'Roaming', GLOBAL_CONFIG_DIR_NAME);
|
|
33
|
+
}
|
|
34
|
+
// Unix/macOS fallback: ~/.config
|
|
35
|
+
return path.join(os.homedir(), '.config', GLOBAL_CONFIG_DIR_NAME);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Gets the global data directory path following XDG Base Directory Specification.
|
|
39
|
+
* Used for user data like schema overrides.
|
|
40
|
+
*
|
|
41
|
+
* - All platforms: $XDG_DATA_HOME/openspec/ if XDG_DATA_HOME is set
|
|
42
|
+
* - Unix/macOS fallback: ~/.local/share/openspec/
|
|
43
|
+
* - Windows fallback: %LOCALAPPDATA%/openspec/
|
|
44
|
+
*/
|
|
45
|
+
export function getGlobalDataDir() {
|
|
46
|
+
// XDG_DATA_HOME takes precedence on all platforms when explicitly set
|
|
47
|
+
const xdgDataHome = process.env.XDG_DATA_HOME;
|
|
48
|
+
if (xdgDataHome) {
|
|
49
|
+
return path.join(xdgDataHome, GLOBAL_DATA_DIR_NAME);
|
|
50
|
+
}
|
|
51
|
+
const platform = os.platform();
|
|
52
|
+
if (platform === 'win32') {
|
|
53
|
+
// Windows: use %LOCALAPPDATA%
|
|
54
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
55
|
+
if (localAppData) {
|
|
56
|
+
return path.join(localAppData, GLOBAL_DATA_DIR_NAME);
|
|
57
|
+
}
|
|
58
|
+
// Fallback for Windows if LOCALAPPDATA is not set
|
|
59
|
+
return path.join(os.homedir(), 'AppData', 'Local', GLOBAL_DATA_DIR_NAME);
|
|
60
|
+
}
|
|
61
|
+
// Unix/macOS fallback: ~/.local/share
|
|
62
|
+
return path.join(os.homedir(), '.local', 'share', GLOBAL_DATA_DIR_NAME);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Gets the path to the global config file.
|
|
66
|
+
*/
|
|
67
|
+
export function getGlobalConfigPath() {
|
|
68
|
+
return path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE_NAME);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Loads the global configuration from disk.
|
|
72
|
+
* Returns default configuration if file doesn't exist or is invalid.
|
|
73
|
+
* Merges loaded config with defaults to ensure new fields are available.
|
|
74
|
+
*/
|
|
75
|
+
export function getGlobalConfig() {
|
|
76
|
+
const configPath = getGlobalConfigPath();
|
|
77
|
+
try {
|
|
78
|
+
if (!fs.existsSync(configPath)) {
|
|
79
|
+
return { ...DEFAULT_CONFIG };
|
|
80
|
+
}
|
|
81
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
82
|
+
const parsed = JSON.parse(content);
|
|
83
|
+
// Merge with defaults (loaded values take precedence)
|
|
84
|
+
return {
|
|
85
|
+
...DEFAULT_CONFIG,
|
|
86
|
+
...parsed,
|
|
87
|
+
// Deep merge featureFlags
|
|
88
|
+
featureFlags: {
|
|
89
|
+
...DEFAULT_CONFIG.featureFlags,
|
|
90
|
+
...(parsed.featureFlags || {})
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
// Log warning for parse errors, but not for missing files
|
|
96
|
+
if (error instanceof SyntaxError) {
|
|
97
|
+
console.error(`Warning: Invalid JSON in ${configPath}, using defaults`);
|
|
98
|
+
}
|
|
99
|
+
return { ...DEFAULT_CONFIG };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Saves the global configuration to disk.
|
|
104
|
+
* Creates the config directory if it doesn't exist.
|
|
105
|
+
*/
|
|
106
|
+
export function saveGlobalConfig(config) {
|
|
107
|
+
const configDir = getGlobalConfigDir();
|
|
108
|
+
const configPath = getGlobalConfigPath();
|
|
109
|
+
// Create directory if it doesn't exist
|
|
110
|
+
if (!fs.existsSync(configDir)) {
|
|
111
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=global-config.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// Core OpenSpec logic will be implemented here
|
|
2
|
+
export { GLOBAL_CONFIG_DIR_NAME, GLOBAL_CONFIG_FILE_NAME, GLOBAL_DATA_DIR_NAME, getGlobalConfigDir, getGlobalConfigPath, getGlobalConfig, saveGlobalConfig, getGlobalDataDir } from './global-config.js';
|
|
3
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init Command
|
|
3
|
+
*
|
|
4
|
+
* Sets up OpenSpec with Agent Skills and /opsx:* slash commands.
|
|
5
|
+
* This is the unified setup command that replaces both the old init and experimental commands.
|
|
6
|
+
*/
|
|
7
|
+
type InitCommandOptions = {
|
|
8
|
+
tools?: string;
|
|
9
|
+
force?: boolean;
|
|
10
|
+
interactive?: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare class InitCommand {
|
|
13
|
+
private readonly toolsArg?;
|
|
14
|
+
private readonly force;
|
|
15
|
+
private readonly interactiveOption?;
|
|
16
|
+
constructor(options?: InitCommandOptions);
|
|
17
|
+
execute(targetPath: string): Promise<void>;
|
|
18
|
+
private validate;
|
|
19
|
+
private canPromptInteractively;
|
|
20
|
+
private handleLegacyCleanup;
|
|
21
|
+
private performLegacyCleanup;
|
|
22
|
+
private getSelectedTools;
|
|
23
|
+
private resolveToolsArg;
|
|
24
|
+
private validateTools;
|
|
25
|
+
private createDirectoryStructure;
|
|
26
|
+
private generateSkillsAndCommands;
|
|
27
|
+
private createConfig;
|
|
28
|
+
private displaySuccessMessage;
|
|
29
|
+
private startSpinner;
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init Command
|
|
3
|
+
*
|
|
4
|
+
* Sets up OpenSpec with Agent Skills and /opsx:* slash commands.
|
|
5
|
+
* This is the unified setup command that replaces both the old init and experimental commands.
|
|
6
|
+
*/
|
|
7
|
+
import path from "path";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import ora from "ora";
|
|
10
|
+
import * as fs from "fs";
|
|
11
|
+
import { createRequire } from "module";
|
|
12
|
+
import { FileSystemUtils } from "../utils/file-system.js";
|
|
13
|
+
import { transformToHyphenCommands } from "../utils/command-references.js";
|
|
14
|
+
import { AI_TOOLS, OPENSPEC_DIR_NAME } from "./config.js";
|
|
15
|
+
import { PALETTE } from "./styles/palette.js";
|
|
16
|
+
import { isInteractive } from "../utils/interactive.js";
|
|
17
|
+
import { serializeConfig } from "./config-prompts.js";
|
|
18
|
+
import { generateCommands, CommandAdapterRegistry, } from "./command-generation/index.js";
|
|
19
|
+
import { detectLegacyArtifacts, cleanupLegacyArtifacts, formatCleanupSummary, formatDetectionSummary, } from "./legacy-cleanup.js";
|
|
20
|
+
import { getToolsWithSkillsDir, getToolStates, getSkillTemplates, getCommandContents, generateSkillContent, } from "./shared/index.js";
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
const { version: OPENSPEC_VERSION } = require("../../package.json");
|
|
23
|
+
// -----------------------------------------------------------------------------
|
|
24
|
+
// Constants
|
|
25
|
+
// -----------------------------------------------------------------------------
|
|
26
|
+
const DEFAULT_SCHEMA = "spec-driven";
|
|
27
|
+
const PROGRESS_SPINNER = {
|
|
28
|
+
interval: 80,
|
|
29
|
+
frames: ["░░░", "▒░░", "▒▒░", "▒▒▒", "▓▒▒", "▓▓▒", "▓▓▓", "▒▓▓", "░▒▓"],
|
|
30
|
+
};
|
|
31
|
+
// -----------------------------------------------------------------------------
|
|
32
|
+
// Init Command Class
|
|
33
|
+
// -----------------------------------------------------------------------------
|
|
34
|
+
export class InitCommand {
|
|
35
|
+
toolsArg;
|
|
36
|
+
force;
|
|
37
|
+
interactiveOption;
|
|
38
|
+
constructor(options = {}) {
|
|
39
|
+
this.toolsArg = options.tools;
|
|
40
|
+
this.force = options.force ?? false;
|
|
41
|
+
this.interactiveOption = options.interactive;
|
|
42
|
+
}
|
|
43
|
+
async execute(targetPath) {
|
|
44
|
+
const projectPath = path.resolve(targetPath);
|
|
45
|
+
const openspecDir = OPENSPEC_DIR_NAME;
|
|
46
|
+
const openspecPath = path.join(projectPath, openspecDir);
|
|
47
|
+
// Validation happens silently in the background
|
|
48
|
+
const extendMode = await this.validate(projectPath, openspecPath);
|
|
49
|
+
// Check for legacy artifacts and handle cleanup
|
|
50
|
+
await this.handleLegacyCleanup(projectPath, extendMode);
|
|
51
|
+
// Show animated welcome screen (interactive mode only)
|
|
52
|
+
const canPrompt = this.canPromptInteractively();
|
|
53
|
+
if (canPrompt) {
|
|
54
|
+
const { showWelcomeScreen } = await import("../ui/welcome-screen.js");
|
|
55
|
+
await showWelcomeScreen();
|
|
56
|
+
}
|
|
57
|
+
// Get tool states before processing
|
|
58
|
+
const toolStates = getToolStates(projectPath);
|
|
59
|
+
// Get tool selection
|
|
60
|
+
const selectedToolIds = await this.getSelectedTools(toolStates, extendMode);
|
|
61
|
+
// Validate selected tools
|
|
62
|
+
const validatedTools = this.validateTools(selectedToolIds, toolStates);
|
|
63
|
+
// Create directory structure and config
|
|
64
|
+
await this.createDirectoryStructure(openspecPath, extendMode);
|
|
65
|
+
// Generate skills and commands for each tool
|
|
66
|
+
const results = await this.generateSkillsAndCommands(projectPath, validatedTools);
|
|
67
|
+
// Create config.yaml if needed
|
|
68
|
+
const configStatus = await this.createConfig(openspecPath, extendMode);
|
|
69
|
+
// Display success message
|
|
70
|
+
this.displaySuccessMessage(projectPath, validatedTools, results, configStatus);
|
|
71
|
+
}
|
|
72
|
+
// ═══════════════════════════════════════════════════════════
|
|
73
|
+
// VALIDATION & SETUP
|
|
74
|
+
// ═══════════════════════════════════════════════════════════
|
|
75
|
+
async validate(projectPath, openspecPath) {
|
|
76
|
+
const extendMode = await FileSystemUtils.directoryExists(openspecPath);
|
|
77
|
+
// Check write permissions
|
|
78
|
+
if (!(await FileSystemUtils.ensureWritePermissions(projectPath))) {
|
|
79
|
+
throw new Error(`对 ${projectPath} 无写入权限`);
|
|
80
|
+
}
|
|
81
|
+
return extendMode;
|
|
82
|
+
}
|
|
83
|
+
canPromptInteractively() {
|
|
84
|
+
if (this.interactiveOption === false)
|
|
85
|
+
return false;
|
|
86
|
+
if (this.toolsArg !== undefined)
|
|
87
|
+
return false;
|
|
88
|
+
return isInteractive({ interactive: this.interactiveOption });
|
|
89
|
+
}
|
|
90
|
+
// ═══════════════════════════════════════════════════════════
|
|
91
|
+
// LEGACY CLEANUP
|
|
92
|
+
// ═══════════════════════════════════════════════════════════
|
|
93
|
+
async handleLegacyCleanup(projectPath, extendMode) {
|
|
94
|
+
// Detect legacy artifacts
|
|
95
|
+
const detection = await detectLegacyArtifacts(projectPath);
|
|
96
|
+
if (!detection.hasLegacyArtifacts) {
|
|
97
|
+
return; // No legacy artifacts found
|
|
98
|
+
}
|
|
99
|
+
// Show what was detected
|
|
100
|
+
console.log();
|
|
101
|
+
console.log(formatDetectionSummary(detection));
|
|
102
|
+
console.log();
|
|
103
|
+
const canPrompt = this.canPromptInteractively();
|
|
104
|
+
if (this.force) {
|
|
105
|
+
// --force flag: proceed with cleanup automatically
|
|
106
|
+
await this.performLegacyCleanup(projectPath, detection);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (!canPrompt) {
|
|
110
|
+
// Non-interactive mode without --force: abort
|
|
111
|
+
console.log(chalk.red("非交互模式下检测到旧版文件。"));
|
|
112
|
+
console.log(chalk.dim("请以交互方式运行以升级,或使用 --force 自动清理。"));
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
// Interactive mode: prompt for confirmation
|
|
116
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
117
|
+
const shouldCleanup = await confirm({
|
|
118
|
+
message: "是否升级并清理旧版文件?",
|
|
119
|
+
default: true,
|
|
120
|
+
});
|
|
121
|
+
if (!shouldCleanup) {
|
|
122
|
+
console.log(chalk.dim("已取消初始化。"));
|
|
123
|
+
console.log(chalk.dim("使用 --force 可跳过此提示,或手动删除旧版文件。"));
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
await this.performLegacyCleanup(projectPath, detection);
|
|
127
|
+
}
|
|
128
|
+
async performLegacyCleanup(projectPath, detection) {
|
|
129
|
+
const spinner = ora("正在清理旧版文件...").start();
|
|
130
|
+
const result = await cleanupLegacyArtifacts(projectPath, detection);
|
|
131
|
+
spinner.succeed("旧版文件已清理");
|
|
132
|
+
const summary = formatCleanupSummary(result);
|
|
133
|
+
if (summary) {
|
|
134
|
+
console.log();
|
|
135
|
+
console.log(summary);
|
|
136
|
+
}
|
|
137
|
+
console.log();
|
|
138
|
+
}
|
|
139
|
+
// ═══════════════════════════════════════════════════════════
|
|
140
|
+
// TOOL SELECTION
|
|
141
|
+
// ═══════════════════════════════════════════════════════════
|
|
142
|
+
async getSelectedTools(toolStates, extendMode) {
|
|
143
|
+
// Check for --tools flag first
|
|
144
|
+
const nonInteractiveSelection = this.resolveToolsArg();
|
|
145
|
+
if (nonInteractiveSelection !== null) {
|
|
146
|
+
return nonInteractiveSelection;
|
|
147
|
+
}
|
|
148
|
+
const validTools = getToolsWithSkillsDir();
|
|
149
|
+
const canPrompt = this.canPromptInteractively();
|
|
150
|
+
if (!canPrompt || validTools.length === 0) {
|
|
151
|
+
throw new Error(`Missing required option --tools. Valid tools:\n ${validTools.join("\n ")}\n\nUse --tools all, --tools none, or --tools claude,cursor,...`);
|
|
152
|
+
}
|
|
153
|
+
// Interactive mode: show searchable multi-select
|
|
154
|
+
const { searchableMultiSelect } = await import("../prompts/searchable-multi-select.js");
|
|
155
|
+
// Build choices with configured status and sort configured tools first
|
|
156
|
+
const sortedChoices = validTools
|
|
157
|
+
.map((toolId) => {
|
|
158
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
159
|
+
const status = toolStates.get(toolId);
|
|
160
|
+
const configured = status?.configured ?? false;
|
|
161
|
+
return {
|
|
162
|
+
name: tool?.name || toolId,
|
|
163
|
+
value: toolId,
|
|
164
|
+
configured,
|
|
165
|
+
preSelected: configured, // Pre-select configured tools for easy refresh
|
|
166
|
+
};
|
|
167
|
+
})
|
|
168
|
+
.sort((a, b) => {
|
|
169
|
+
// Configured tools first
|
|
170
|
+
if (a.configured && !b.configured)
|
|
171
|
+
return -1;
|
|
172
|
+
if (!a.configured && b.configured)
|
|
173
|
+
return 1;
|
|
174
|
+
return 0;
|
|
175
|
+
});
|
|
176
|
+
const selectedTools = await searchableMultiSelect({
|
|
177
|
+
message: `Select tools to set up (${validTools.length} available)`,
|
|
178
|
+
pageSize: 15,
|
|
179
|
+
choices: sortedChoices,
|
|
180
|
+
validate: (selected) => selected.length > 0 || "请至少选择一种工具",
|
|
181
|
+
});
|
|
182
|
+
if (selectedTools.length === 0) {
|
|
183
|
+
throw new Error("必须至少选择一种工具");
|
|
184
|
+
}
|
|
185
|
+
return selectedTools;
|
|
186
|
+
}
|
|
187
|
+
resolveToolsArg() {
|
|
188
|
+
if (typeof this.toolsArg === "undefined") {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
const raw = this.toolsArg.trim();
|
|
192
|
+
if (raw.length === 0) {
|
|
193
|
+
throw new Error('--tools 需要取值。可使用 "all"、"none" 或逗号分隔的工具 ID 列表。');
|
|
194
|
+
}
|
|
195
|
+
const availableTools = getToolsWithSkillsDir();
|
|
196
|
+
const availableSet = new Set(availableTools);
|
|
197
|
+
const availableList = ["all", "none", ...availableTools].join(", ");
|
|
198
|
+
const lowerRaw = raw.toLowerCase();
|
|
199
|
+
if (lowerRaw === "all") {
|
|
200
|
+
return availableTools;
|
|
201
|
+
}
|
|
202
|
+
if (lowerRaw === "none") {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
const tokens = raw
|
|
206
|
+
.split(",")
|
|
207
|
+
.map((token) => token.trim())
|
|
208
|
+
.filter((token) => token.length > 0);
|
|
209
|
+
if (tokens.length === 0) {
|
|
210
|
+
throw new Error('未使用 "all" 或 "none" 时,--tools 至少需要一个工具 ID。');
|
|
211
|
+
}
|
|
212
|
+
const normalizedTokens = tokens.map((token) => token.toLowerCase());
|
|
213
|
+
if (normalizedTokens.some((token) => token === "all" || token === "none")) {
|
|
214
|
+
throw new Error('不能将保留值 "all" 或 "none" 与具体工具 ID 混用。');
|
|
215
|
+
}
|
|
216
|
+
const invalidTokens = tokens.filter((_token, index) => !availableSet.has(normalizedTokens[index]));
|
|
217
|
+
if (invalidTokens.length > 0) {
|
|
218
|
+
throw new Error(`无效工具:${invalidTokens.join(", ")}。可用值:${availableList}`);
|
|
219
|
+
}
|
|
220
|
+
// Deduplicate while preserving order
|
|
221
|
+
const deduped = [];
|
|
222
|
+
for (const token of normalizedTokens) {
|
|
223
|
+
if (!deduped.includes(token)) {
|
|
224
|
+
deduped.push(token);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return deduped;
|
|
228
|
+
}
|
|
229
|
+
validateTools(toolIds, toolStates) {
|
|
230
|
+
const validatedTools = [];
|
|
231
|
+
for (const toolId of toolIds) {
|
|
232
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
233
|
+
if (!tool) {
|
|
234
|
+
const validToolIds = getToolsWithSkillsDir();
|
|
235
|
+
throw new Error(`未知工具 "${toolId}"。有效工具:\n ${validToolIds.join("\n ")}`);
|
|
236
|
+
}
|
|
237
|
+
if (!tool.skillsDir) {
|
|
238
|
+
const validToolsWithSkills = getToolsWithSkillsDir();
|
|
239
|
+
throw new Error(`工具 "${toolId}" 不支持技能生成。支持技能生成的工具:\n ${validToolsWithSkills.join("\n ")}`);
|
|
240
|
+
}
|
|
241
|
+
const preState = toolStates.get(tool.value);
|
|
242
|
+
validatedTools.push({
|
|
243
|
+
value: tool.value,
|
|
244
|
+
name: tool.name,
|
|
245
|
+
skillsDir: tool.skillsDir,
|
|
246
|
+
wasConfigured: preState?.configured ?? false,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return validatedTools;
|
|
250
|
+
}
|
|
251
|
+
// ═══════════════════════════════════════════════════════════
|
|
252
|
+
// DIRECTORY STRUCTURE
|
|
253
|
+
// ═══════════════════════════════════════════════════════════
|
|
254
|
+
async createDirectoryStructure(openspecPath, extendMode) {
|
|
255
|
+
if (extendMode) {
|
|
256
|
+
// In extend mode, just ensure directories exist without spinner
|
|
257
|
+
const directories = [
|
|
258
|
+
openspecPath,
|
|
259
|
+
path.join(openspecPath, "specs"),
|
|
260
|
+
path.join(openspecPath, "changes"),
|
|
261
|
+
path.join(openspecPath, "changes", "archive"),
|
|
262
|
+
];
|
|
263
|
+
for (const dir of directories) {
|
|
264
|
+
await FileSystemUtils.createDirectory(dir);
|
|
265
|
+
}
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const spinner = this.startSpinner("Creating OpenSpec structure...");
|
|
269
|
+
const directories = [
|
|
270
|
+
openspecPath,
|
|
271
|
+
path.join(openspecPath, "specs"),
|
|
272
|
+
path.join(openspecPath, "changes"),
|
|
273
|
+
path.join(openspecPath, "changes", "archive"),
|
|
274
|
+
];
|
|
275
|
+
for (const dir of directories) {
|
|
276
|
+
await FileSystemUtils.createDirectory(dir);
|
|
277
|
+
}
|
|
278
|
+
spinner.stopAndPersist({
|
|
279
|
+
symbol: PALETTE.white("▌"),
|
|
280
|
+
text: PALETTE.white("OpenSpec structure created"),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
// ═══════════════════════════════════════════════════════════
|
|
284
|
+
// SKILL & COMMAND GENERATION
|
|
285
|
+
// ═══════════════════════════════════════════════════════════
|
|
286
|
+
async generateSkillsAndCommands(projectPath, tools) {
|
|
287
|
+
const createdTools = [];
|
|
288
|
+
const refreshedTools = [];
|
|
289
|
+
const failedTools = [];
|
|
290
|
+
const commandsSkipped = [];
|
|
291
|
+
// Get skill and command templates once (shared across all tools)
|
|
292
|
+
const skillTemplates = getSkillTemplates();
|
|
293
|
+
const commandContents = getCommandContents();
|
|
294
|
+
// Process each tool
|
|
295
|
+
for (const tool of tools) {
|
|
296
|
+
const spinner = ora(`正在配置 ${tool.name}...`).start();
|
|
297
|
+
try {
|
|
298
|
+
// Use tool-specific skillsDir
|
|
299
|
+
const skillsDir = path.join(projectPath, tool.skillsDir, "skills");
|
|
300
|
+
// Create skill directories and SKILL.md files
|
|
301
|
+
for (const { template, dirName } of skillTemplates) {
|
|
302
|
+
const skillDir = path.join(skillsDir, dirName);
|
|
303
|
+
const skillFile = path.join(skillDir, "SKILL.md");
|
|
304
|
+
// Generate SKILL.md content with YAML frontmatter including generatedBy
|
|
305
|
+
// Use hyphen-based command references for OpenCode
|
|
306
|
+
const transformer = tool.value === "opencode" ? transformToHyphenCommands : undefined;
|
|
307
|
+
const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
|
|
308
|
+
// Write the skill file
|
|
309
|
+
await FileSystemUtils.writeFile(skillFile, skillContent);
|
|
310
|
+
}
|
|
311
|
+
// Generate commands using the adapter system
|
|
312
|
+
const adapter = CommandAdapterRegistry.get(tool.value);
|
|
313
|
+
if (adapter) {
|
|
314
|
+
const generatedCommands = generateCommands(commandContents, adapter);
|
|
315
|
+
for (const cmd of generatedCommands) {
|
|
316
|
+
const commandFile = path.isAbsolute(cmd.path)
|
|
317
|
+
? cmd.path
|
|
318
|
+
: path.join(projectPath, cmd.path);
|
|
319
|
+
await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
commandsSkipped.push(tool.value);
|
|
324
|
+
}
|
|
325
|
+
spinner.succeed(`${tool.name} 配置完成`);
|
|
326
|
+
if (tool.wasConfigured) {
|
|
327
|
+
refreshedTools.push(tool);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
createdTools.push(tool);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
spinner.fail(`${tool.name} 配置失败`);
|
|
335
|
+
failedTools.push({ name: tool.name, error: error });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return { createdTools, refreshedTools, failedTools, commandsSkipped };
|
|
339
|
+
}
|
|
340
|
+
// ═══════════════════════════════════════════════════════════
|
|
341
|
+
// CONFIG FILE
|
|
342
|
+
// ═══════════════════════════════════════════════════════════
|
|
343
|
+
async createConfig(openspecPath, extendMode) {
|
|
344
|
+
const configPath = path.join(openspecPath, "config.yaml");
|
|
345
|
+
const configYmlPath = path.join(openspecPath, "config.yml");
|
|
346
|
+
const configYamlExists = fs.existsSync(configPath);
|
|
347
|
+
const configYmlExists = fs.existsSync(configYmlPath);
|
|
348
|
+
if (configYamlExists || configYmlExists) {
|
|
349
|
+
return "exists";
|
|
350
|
+
}
|
|
351
|
+
// In non-interactive mode without --force, skip config creation
|
|
352
|
+
if (!this.canPromptInteractively() && !this.force) {
|
|
353
|
+
return "skipped";
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
const yamlContent = serializeConfig({ schema: DEFAULT_SCHEMA });
|
|
357
|
+
await FileSystemUtils.writeFile(configPath, yamlContent);
|
|
358
|
+
return "created";
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
return "skipped";
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// ═══════════════════════════════════════════════════════════
|
|
365
|
+
// UI & OUTPUT
|
|
366
|
+
// ═══════════════════════════════════════════════════════════
|
|
367
|
+
displaySuccessMessage(projectPath, tools, results, configStatus) {
|
|
368
|
+
console.log();
|
|
369
|
+
console.log(chalk.bold("OpenSpec 配置完成"));
|
|
370
|
+
console.log();
|
|
371
|
+
// Show created vs refreshed tools
|
|
372
|
+
if (results.createdTools.length > 0) {
|
|
373
|
+
console.log(`已创建:${results.createdTools.map((t) => t.name).join(", ")}`);
|
|
374
|
+
}
|
|
375
|
+
if (results.refreshedTools.length > 0) {
|
|
376
|
+
console.log(`已刷新:${results.refreshedTools.map((t) => t.name).join(", ")}`);
|
|
377
|
+
}
|
|
378
|
+
// Show counts
|
|
379
|
+
const successfulTools = [
|
|
380
|
+
...results.createdTools,
|
|
381
|
+
...results.refreshedTools,
|
|
382
|
+
];
|
|
383
|
+
if (successfulTools.length > 0) {
|
|
384
|
+
const toolDirs = [
|
|
385
|
+
...new Set(successfulTools.map((t) => t.skillsDir)),
|
|
386
|
+
].join(", ");
|
|
387
|
+
const hasCommands = results.commandsSkipped.length < successfulTools.length;
|
|
388
|
+
if (hasCommands) {
|
|
389
|
+
console.log(`${getSkillTemplates().length} 个技能与 ${getCommandContents().length} 个命令已写入 ${toolDirs}/`);
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
console.log(`${getSkillTemplates().length} 个技能已写入 ${toolDirs}/`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// Show failures
|
|
396
|
+
if (results.failedTools.length > 0) {
|
|
397
|
+
console.log(chalk.red(`失败:${results.failedTools.map((f) => `${f.name} (${f.error.message})`).join(", ")}`));
|
|
398
|
+
}
|
|
399
|
+
// Show skipped commands
|
|
400
|
+
if (results.commandsSkipped.length > 0) {
|
|
401
|
+
console.log(chalk.dim(`未生成命令:${results.commandsSkipped.join(", ")}(无适配器)`));
|
|
402
|
+
}
|
|
403
|
+
// Config status
|
|
404
|
+
if (configStatus === "created") {
|
|
405
|
+
console.log(`配置:openspec/config.yaml (schema: ${DEFAULT_SCHEMA})`);
|
|
406
|
+
}
|
|
407
|
+
else if (configStatus === "exists") {
|
|
408
|
+
// Show actual filename (config.yaml or config.yml)
|
|
409
|
+
const configYaml = path.join(projectPath, OPENSPEC_DIR_NAME, "config.yaml");
|
|
410
|
+
const configYml = path.join(projectPath, OPENSPEC_DIR_NAME, "config.yml");
|
|
411
|
+
const configName = fs.existsSync(configYaml)
|
|
412
|
+
? "config.yaml"
|
|
413
|
+
: fs.existsSync(configYml)
|
|
414
|
+
? "config.yml"
|
|
415
|
+
: "config.yaml";
|
|
416
|
+
console.log(`配置:openspec/${configName}(已存在)`);
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
console.log(chalk.dim(`配置:已跳过(非交互模式)`));
|
|
420
|
+
}
|
|
421
|
+
// Getting started
|
|
422
|
+
console.log();
|
|
423
|
+
console.log(chalk.bold("快速开始:"));
|
|
424
|
+
console.log(" /opsx:new 新建变更");
|
|
425
|
+
console.log(" /opsx:continue 创建下一个制品");
|
|
426
|
+
console.log(" /opsx:apply 实施任务");
|
|
427
|
+
// Links
|
|
428
|
+
console.log();
|
|
429
|
+
console.log(`了解更多:${chalk.cyan("https://github.com/Fission-AI/OpenSpec")}`);
|
|
430
|
+
console.log(`反馈: ${chalk.cyan("https://github.com/Fission-AI/OpenSpec/issues")}`);
|
|
431
|
+
// Restart instruction if any tools were configured
|
|
432
|
+
if (results.createdTools.length > 0 || results.refreshedTools.length > 0) {
|
|
433
|
+
console.log();
|
|
434
|
+
console.log(chalk.white("请重启 IDE 以使斜杠命令生效。"));
|
|
435
|
+
}
|
|
436
|
+
console.log();
|
|
437
|
+
}
|
|
438
|
+
startSpinner(text) {
|
|
439
|
+
return ora({
|
|
440
|
+
text,
|
|
441
|
+
stream: process.stdout,
|
|
442
|
+
color: "gray",
|
|
443
|
+
spinner: PROGRESS_SPINNER,
|
|
444
|
+
}).start();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
//# sourceMappingURL=init.js.map
|