aiwcli 0.9.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 +1248 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +16 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +19 -0
- package/dist/commands/branch.d.ts +45 -0
- package/dist/commands/branch.js +488 -0
- package/dist/commands/clean.d.ts +34 -0
- package/dist/commands/clean.js +186 -0
- package/dist/commands/clear.d.ts +51 -0
- package/dist/commands/clear.js +835 -0
- package/dist/commands/init/index.d.ts +107 -0
- package/dist/commands/init/index.js +565 -0
- package/dist/commands/launch.d.ts +21 -0
- package/dist/commands/launch.js +108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/base-command.d.ts +114 -0
- package/dist/lib/base-command.js +153 -0
- package/dist/lib/bmad-installer.d.ts +38 -0
- package/dist/lib/bmad-installer.js +145 -0
- package/dist/lib/claude-settings-types.d.ts +102 -0
- package/dist/lib/claude-settings-types.js +5 -0
- package/dist/lib/config.d.ts +25 -0
- package/dist/lib/config.js +46 -0
- package/dist/lib/debug.d.ts +39 -0
- package/dist/lib/debug.js +74 -0
- package/dist/lib/env-compat.d.ts +26 -0
- package/dist/lib/env-compat.js +35 -0
- package/dist/lib/errors.d.ts +126 -0
- package/dist/lib/errors.js +145 -0
- package/dist/lib/generic-merge.d.ts +74 -0
- package/dist/lib/generic-merge.js +105 -0
- package/dist/lib/git/branch.d.ts +67 -0
- package/dist/lib/git/branch.js +155 -0
- package/dist/lib/git/index.d.ts +11 -0
- package/dist/lib/git/index.js +13 -0
- package/dist/lib/git/safety-checks.d.ts +44 -0
- package/dist/lib/git/safety-checks.js +102 -0
- package/dist/lib/git/types.d.ts +31 -0
- package/dist/lib/git/types.js +6 -0
- package/dist/lib/git/worktree.d.ts +67 -0
- package/dist/lib/git/worktree.js +220 -0
- package/dist/lib/gitignore-manager.d.ts +10 -0
- package/dist/lib/gitignore-manager.js +60 -0
- package/dist/lib/hooks-merger.d.ts +28 -0
- package/dist/lib/hooks-merger.js +94 -0
- package/dist/lib/ide-path-resolver.d.ts +102 -0
- package/dist/lib/ide-path-resolver.js +129 -0
- package/dist/lib/index.d.ts +13 -0
- package/dist/lib/index.js +22 -0
- package/dist/lib/output.d.ts +51 -0
- package/dist/lib/output.js +76 -0
- package/dist/lib/paths.d.ts +66 -0
- package/dist/lib/paths.js +136 -0
- package/dist/lib/quiet.d.ts +12 -0
- package/dist/lib/quiet.js +17 -0
- package/dist/lib/settings-hierarchy.d.ts +42 -0
- package/dist/lib/settings-hierarchy.js +105 -0
- package/dist/lib/spawn.d.ts +105 -0
- package/dist/lib/spawn.js +157 -0
- package/dist/lib/spinner.d.ts +19 -0
- package/dist/lib/spinner.js +34 -0
- package/dist/lib/stdin.d.ts +48 -0
- package/dist/lib/stdin.js +60 -0
- package/dist/lib/template-installer.d.ts +92 -0
- package/dist/lib/template-installer.js +375 -0
- package/dist/lib/template-linter.d.ts +49 -0
- package/dist/lib/template-linter.js +173 -0
- package/dist/lib/template-merger.d.ts +47 -0
- package/dist/lib/template-merger.js +173 -0
- package/dist/lib/template-resolver.d.ts +20 -0
- package/dist/lib/template-resolver.js +60 -0
- package/dist/lib/terminal.d.ts +102 -0
- package/dist/lib/terminal.js +245 -0
- package/dist/lib/tty-detection.d.ts +62 -0
- package/dist/lib/tty-detection.js +83 -0
- package/dist/lib/user-utils.d.ts +5 -0
- package/dist/lib/user-utils.js +23 -0
- package/dist/lib/version.d.ts +99 -0
- package/dist/lib/version.js +144 -0
- package/dist/lib/watch-templates.d.ts +6 -0
- package/dist/lib/watch-templates.js +73 -0
- package/dist/lib/windsurf-hooks-hierarchy.d.ts +30 -0
- package/dist/lib/windsurf-hooks-hierarchy.js +66 -0
- package/dist/lib/windsurf-hooks-merger.d.ts +26 -0
- package/dist/lib/windsurf-hooks-merger.js +53 -0
- package/dist/lib/windsurf-hooks-types.d.ts +33 -0
- package/dist/lib/windsurf-hooks-types.js +5 -0
- package/dist/templates/CLAUDE.md +174 -0
- package/dist/templates/_shared/.claude/commands/handoff.md +14 -0
- package/dist/templates/_shared/.claude/settings.json +61 -0
- package/dist/templates/_shared/.codex/workflows/handoff.md +14 -0
- package/dist/templates/_shared/.windsurf/workflows/handoff.md +14 -0
- package/dist/templates/_shared/hooks/__init__.py +16 -0
- package/dist/templates/_shared/hooks/archive_plan.py +270 -0
- package/dist/templates/_shared/hooks/context_enforcer.py +621 -0
- package/dist/templates/_shared/hooks/context_monitor.py +322 -0
- package/dist/templates/_shared/hooks/file-suggestion.py +188 -0
- package/dist/templates/_shared/hooks/task_create_capture.py +194 -0
- package/dist/templates/_shared/hooks/task_update_capture.py +254 -0
- package/dist/templates/_shared/hooks/user_prompt_submit.py +157 -0
- package/dist/templates/_shared/lib/__init__.py +1 -0
- package/dist/templates/_shared/lib/base/__init__.py +49 -0
- package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/atomic_write.py +180 -0
- package/dist/templates/_shared/lib/base/constants.py +299 -0
- package/dist/templates/_shared/lib/base/inference.py +189 -0
- package/dist/templates/_shared/lib/base/utils.py +216 -0
- package/dist/templates/_shared/lib/context/__init__.py +119 -0
- package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/cache.py +446 -0
- package/dist/templates/_shared/lib/context/context_manager.py +1171 -0
- package/dist/templates/_shared/lib/context/discovery.py +486 -0
- package/dist/templates/_shared/lib/context/event_log.py +308 -0
- package/dist/templates/_shared/lib/context/plan_archive.py +247 -0
- package/dist/templates/_shared/lib/context/task_sync.py +367 -0
- package/dist/templates/_shared/lib/handoff/__init__.py +22 -0
- package/dist/templates/_shared/lib/handoff/document_generator.py +307 -0
- package/dist/templates/_shared/lib/templates/README.md +215 -0
- package/dist/templates/_shared/lib/templates/__init__.py +40 -0
- package/dist/templates/_shared/lib/templates/formatters.py +147 -0
- package/dist/templates/_shared/lib/templates/plan_context.py +119 -0
- package/dist/templates/_shared/scripts/save_handoff.py +99 -0
- package/dist/templates/_shared/workflows/handoff.md +212 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ACCESSIBILITY-TESTER.md +80 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ARCHITECT-REVIEWER.md +75 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ASSUMPTION-CHAIN-TRACER.md +239 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CLARITY-AUDITOR.md +109 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CODE-REVIEWER.md +71 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/COMPLETENESS-CHECKER.md +104 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CONTEXT-EXTRACTOR.md +93 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/DEVILS-ADVOCATE.md +223 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/DOCUMENTATION-REVIEWER.md +73 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/FEASIBILITY-ANALYST.md +93 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/FRESH-PERSPECTIVE.md +103 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/HANDOFF-READINESS.md +145 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/HIDDEN-COMPLEXITY-DETECTOR.md +248 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/INCENTIVE-MAPPER.md +235 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PENETRATION-TESTER.md +80 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PERFORMANCE-ENGINEER.md +76 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PLAN-ORCHESTRATOR.md +141 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PRECEDENT-FINDER.md +240 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/REVERSIBILITY-ANALYST.md +211 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/RISK-ASSESSOR.md +101 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SECOND-ORDER-ANALYST.md +197 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SIMPLICITY-GUARDIAN.md +97 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SKEPTIC.md +349 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/STAKEHOLDER-ADVOCATE.md +106 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/TRADE-OFF-ILLUMINATOR.md +205 -0
- package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +8 -0
- package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -0
- package/dist/templates/cc-native/.claude/settings.json +119 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -0
- package/dist/templates/cc-native/CC-NATIVE-README.md +192 -0
- package/dist/templates/cc-native/MIGRATION.md +86 -0
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +331 -0
- package/dist/templates/cc-native/_cc-native/docs/PERMISSION_REQUEST_VERIFICATION.md +147 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-agent-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/test_permission_request.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +150 -0
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +746 -0
- package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +339 -0
- package/dist/templates/cc-native/_cc-native/lib/__init__.py +57 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/async_archive.py +68 -0
- package/dist/templates/cc-native/_cc-native/lib/atomic_write.py +98 -0
- package/dist/templates/cc-native/_cc-native/lib/constants.py +45 -0
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +273 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +28 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +164 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +89 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +119 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +103 -0
- package/dist/templates/cc-native/_cc-native/lib/state.py +251 -0
- package/dist/templates/cc-native/_cc-native/lib/utils.py +830 -0
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +76 -0
- package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +151 -0
- package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +134 -0
- package/dist/templates/cc-native/_cc-native/workflows/specdev.md +9 -0
- package/dist/types/exit-codes.d.ts +11 -0
- package/dist/types/exit-codes.js +10 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +7 -0
- package/oclif.manifest.json +405 -0
- package/package.json +109 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Content folder types that should be recursively merged rather than skipped.
|
|
5
|
+
* These are the canonical folder names used in templates for organizing content.
|
|
6
|
+
*/
|
|
7
|
+
export const CONTENT_FOLDER_TYPES = ['agents', 'commands', 'workflows', 'tasks'];
|
|
8
|
+
/**
|
|
9
|
+
* Check if a path exists
|
|
10
|
+
*/
|
|
11
|
+
async function pathExists(path) {
|
|
12
|
+
try {
|
|
13
|
+
await fs.access(path);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Recursively find folders matching the method name within a directory tree.
|
|
22
|
+
* This finds folders like `.claude/commands/bmad/` when methodName is 'bmad'.
|
|
23
|
+
*
|
|
24
|
+
* @param dir - Directory to search
|
|
25
|
+
* @param methodName - Method name to look for (e.g., 'bmad', 'gsd')
|
|
26
|
+
* @returns Array of paths to matching folders
|
|
27
|
+
*/
|
|
28
|
+
export async function findMethodFolders(dir, methodName) {
|
|
29
|
+
const results = [];
|
|
30
|
+
async function scan(currentDir) {
|
|
31
|
+
try {
|
|
32
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
33
|
+
const directories = entries.filter((entry) => entry.isDirectory());
|
|
34
|
+
// Process all directories in parallel
|
|
35
|
+
await Promise.all(directories.map(async (entry) => {
|
|
36
|
+
const entryPath = join(currentDir, entry.name);
|
|
37
|
+
if (entry.name === methodName) {
|
|
38
|
+
results.push(entryPath);
|
|
39
|
+
}
|
|
40
|
+
// Recursively scan subdirectories
|
|
41
|
+
await scan(entryPath);
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Ignore errors (permission issues, etc.)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
await scan(dir);
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Recursively merge a source directory into a target directory.
|
|
53
|
+
* Only copies files that don't already exist in the target.
|
|
54
|
+
* Creates directories as needed.
|
|
55
|
+
*
|
|
56
|
+
* @param srcDir - Source directory to copy from
|
|
57
|
+
* @param destDir - Destination directory to copy to
|
|
58
|
+
* @param result - Accumulator for merge results
|
|
59
|
+
*/
|
|
60
|
+
async function mergeDirectory(srcDir, destDir, result) {
|
|
61
|
+
// Create destination directory if it doesn't exist
|
|
62
|
+
if (!(await pathExists(destDir))) {
|
|
63
|
+
await fs.mkdir(destDir, { recursive: true });
|
|
64
|
+
result.createdDirs.push(destDir);
|
|
65
|
+
}
|
|
66
|
+
const entries = await fs.readdir(srcDir, { withFileTypes: true });
|
|
67
|
+
// Process all entries in parallel
|
|
68
|
+
await Promise.all(entries.map(async (entry) => {
|
|
69
|
+
const srcPath = join(srcDir, entry.name);
|
|
70
|
+
const destPath = join(destDir, entry.name);
|
|
71
|
+
if (entry.isDirectory()) {
|
|
72
|
+
// Recursively merge subdirectories
|
|
73
|
+
await mergeDirectory(srcPath, destPath, result);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// Copy file if it doesn't exist
|
|
77
|
+
const exists = await pathExists(destPath);
|
|
78
|
+
if (exists) {
|
|
79
|
+
result.skippedFiles.push(destPath);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
await fs.copyFile(srcPath, destPath);
|
|
83
|
+
result.copiedFiles.push(destPath);
|
|
84
|
+
}
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Merge template content into an existing IDE folder.
|
|
89
|
+
* Recursively finds folders matching the method name and merges their content.
|
|
90
|
+
*
|
|
91
|
+
* This enables adding new agents, commands, workflows, and tasks from a template
|
|
92
|
+
* even when the IDE folder (e.g., .claude) already exists.
|
|
93
|
+
*
|
|
94
|
+
* @param templateIdePath - Path to the template's IDE folder (e.g., template/.claude)
|
|
95
|
+
* @param targetIdePath - Path to the target's IDE folder (e.g., project/.claude)
|
|
96
|
+
* @param methodName - Method name to look for (e.g., 'bmad', 'gsd')
|
|
97
|
+
* @returns Merge results
|
|
98
|
+
*/
|
|
99
|
+
export async function mergeTemplateContent(templateIdePath, targetIdePath, methodName) {
|
|
100
|
+
const result = {
|
|
101
|
+
copiedFiles: [],
|
|
102
|
+
createdDirs: [],
|
|
103
|
+
skippedFiles: [],
|
|
104
|
+
};
|
|
105
|
+
// First, copy root-level files from the template IDE folder (e.g., settings.json)
|
|
106
|
+
// These are files directly in .claude/ that aren't in method-specific subfolders
|
|
107
|
+
try {
|
|
108
|
+
const entries = await fs.readdir(templateIdePath, { withFileTypes: true });
|
|
109
|
+
const rootFiles = entries.filter((entry) => entry.isFile());
|
|
110
|
+
await Promise.all(rootFiles.map(async (file) => {
|
|
111
|
+
const srcPath = join(templateIdePath, file.name);
|
|
112
|
+
const destPath = join(targetIdePath, file.name);
|
|
113
|
+
// Only copy if it doesn't exist (skip existing behavior)
|
|
114
|
+
const exists = await pathExists(destPath);
|
|
115
|
+
if (exists) {
|
|
116
|
+
result.skippedFiles.push(destPath);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
await fs.copyFile(srcPath, destPath);
|
|
120
|
+
result.copiedFiles.push(destPath);
|
|
121
|
+
}
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Ignore errors (permission issues, missing directory, etc.)
|
|
126
|
+
}
|
|
127
|
+
// Find all folders matching the method name in the template
|
|
128
|
+
const methodFolders = await findMethodFolders(templateIdePath, methodName);
|
|
129
|
+
// Merge all method folders in parallel
|
|
130
|
+
await Promise.all(methodFolders.map(async (srcMethodFolder) => {
|
|
131
|
+
// Calculate the relative path from the template IDE folder
|
|
132
|
+
const relativePath = srcMethodFolder.slice(templateIdePath.length);
|
|
133
|
+
const destMethodFolder = join(targetIdePath, relativePath);
|
|
134
|
+
// Merge this method folder into the target
|
|
135
|
+
await mergeDirectory(srcMethodFolder, destMethodFolder, result);
|
|
136
|
+
}));
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Merge content type folders (agents, commands, workflows, tasks) from template to target.
|
|
141
|
+
* This is an alternative approach that looks for specific folder types rather than method names.
|
|
142
|
+
*
|
|
143
|
+
* @param templateIdePath - Path to the template's IDE folder
|
|
144
|
+
* @param targetIdePath - Path to the target's IDE folder
|
|
145
|
+
* @returns Merge results
|
|
146
|
+
*/
|
|
147
|
+
export async function mergeContentTypeFolders(templateIdePath, targetIdePath) {
|
|
148
|
+
const result = {
|
|
149
|
+
copiedFiles: [],
|
|
150
|
+
createdDirs: [],
|
|
151
|
+
skippedFiles: [],
|
|
152
|
+
};
|
|
153
|
+
async function scanAndMerge(srcDir, destDir) {
|
|
154
|
+
try {
|
|
155
|
+
const entries = await fs.readdir(srcDir, { withFileTypes: true });
|
|
156
|
+
const directories = entries.filter((entry) => entry.isDirectory());
|
|
157
|
+
// Process all directories in parallel
|
|
158
|
+
await Promise.all(directories.map(async (entry) => {
|
|
159
|
+
const srcPath = join(srcDir, entry.name);
|
|
160
|
+
const destPath = join(destDir, entry.name);
|
|
161
|
+
// Check if this is a content type folder
|
|
162
|
+
const isContentType = CONTENT_FOLDER_TYPES.includes(entry.name);
|
|
163
|
+
// Merge content folders, recursively scan other directories
|
|
164
|
+
await (isContentType ? mergeDirectory(srcPath, destPath, result) : scanAndMerge(srcPath, destPath));
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Ignore errors
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
await scanAndMerge(templateIdePath, targetIdePath);
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the absolute path to a bundled template root.
|
|
3
|
+
* Works in both development (src/) and production (dist/) contexts.
|
|
4
|
+
*
|
|
5
|
+
* Resolution logic:
|
|
6
|
+
* - In development: src/lib/template-resolver.ts → src/templates/<templateName>/
|
|
7
|
+
* - In production: dist/lib/template-resolver.js → dist/templates/<templateName>/
|
|
8
|
+
*
|
|
9
|
+
* @param templateName - Name of the template to resolve (e.g., 'bmad')
|
|
10
|
+
* @returns Absolute path to the template directory
|
|
11
|
+
* @throws Error if template directory doesn't exist or templateName is invalid
|
|
12
|
+
*/
|
|
13
|
+
export declare function getTemplatePath(templateName: string): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Get list of available template names by scanning the templates directory.
|
|
16
|
+
*
|
|
17
|
+
* @returns Array of template names (e.g., ['bmad', 'gsr'])
|
|
18
|
+
* @throws Error if templates directory cannot be read (indicates corrupted installation)
|
|
19
|
+
*/
|
|
20
|
+
export declare function getAvailableTemplates(): Promise<string[]>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the absolute path to a bundled template root.
|
|
6
|
+
* Works in both development (src/) and production (dist/) contexts.
|
|
7
|
+
*
|
|
8
|
+
* Resolution logic:
|
|
9
|
+
* - In development: src/lib/template-resolver.ts → src/templates/<templateName>/
|
|
10
|
+
* - In production: dist/lib/template-resolver.js → dist/templates/<templateName>/
|
|
11
|
+
*
|
|
12
|
+
* @param templateName - Name of the template to resolve (e.g., 'bmad')
|
|
13
|
+
* @returns Absolute path to the template directory
|
|
14
|
+
* @throws Error if template directory doesn't exist or templateName is invalid
|
|
15
|
+
*/
|
|
16
|
+
export async function getTemplatePath(templateName) {
|
|
17
|
+
// Security: Prevent path traversal attacks
|
|
18
|
+
if (!templateName || templateName.includes('..') || templateName.includes('/') || templateName.includes('\\')) {
|
|
19
|
+
throw new Error(`Invalid template name: '${templateName}'. Template names must not contain path separators or traversal sequences.`);
|
|
20
|
+
}
|
|
21
|
+
// Get the directory of this file
|
|
22
|
+
// In dev: .../aiwcli/src/lib/
|
|
23
|
+
// In prod: .../aiwcli/dist/lib/
|
|
24
|
+
const currentFileUrl = import.meta.url;
|
|
25
|
+
const currentFilePath = fileURLToPath(currentFileUrl);
|
|
26
|
+
const currentDir = dirname(currentFilePath);
|
|
27
|
+
// Go up one level and into templates/<templateName>
|
|
28
|
+
// src/lib/ → src/templates/<templateName>/
|
|
29
|
+
// dist/lib/ → dist/templates/<templateName>/
|
|
30
|
+
const templatePath = join(currentDir, '..', 'templates', templateName);
|
|
31
|
+
// Validate template exists
|
|
32
|
+
try {
|
|
33
|
+
await fs.access(templatePath);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new Error(`Template '${templateName}' not found at ${templatePath}`);
|
|
37
|
+
}
|
|
38
|
+
return templatePath;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get list of available template names by scanning the templates directory.
|
|
42
|
+
*
|
|
43
|
+
* @returns Array of template names (e.g., ['bmad', 'gsr'])
|
|
44
|
+
* @throws Error if templates directory cannot be read (indicates corrupted installation)
|
|
45
|
+
*/
|
|
46
|
+
export async function getAvailableTemplates() {
|
|
47
|
+
const currentFileUrl = import.meta.url;
|
|
48
|
+
const currentFilePath = fileURLToPath(currentFileUrl);
|
|
49
|
+
const currentDir = dirname(currentFilePath);
|
|
50
|
+
const templatesDir = join(currentDir, '..', 'templates');
|
|
51
|
+
try {
|
|
52
|
+
const entries = await fs.readdir(templatesDir, { withFileTypes: true });
|
|
53
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const err = error;
|
|
57
|
+
throw new Error(`Failed to read templates directory at ${templatesDir}: ${err.message}. ` +
|
|
58
|
+
`This indicates a corrupted installation. Please reinstall aiwcli.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Cross-platform terminal launching utilities.
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for launching new terminal windows with commands
|
|
5
|
+
* across Windows, macOS, and Linux platforms.
|
|
6
|
+
*
|
|
7
|
+
* ## Supported Platforms
|
|
8
|
+
* - **Windows**: Windows Terminal (wt.exe) with PowerShell 7 (pwsh) fallback to PowerShell 5.1
|
|
9
|
+
* - **macOS**: Terminal.app via AppleScript
|
|
10
|
+
* - **Linux**: gnome-terminal, konsole, xterm, x-terminal-emulator (in order of preference)
|
|
11
|
+
*
|
|
12
|
+
* ## Usage
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { launchTerminal } from '../lib/terminal.js'
|
|
15
|
+
*
|
|
16
|
+
* const result = await launchTerminal({
|
|
17
|
+
* cwd: '/path/to/project',
|
|
18
|
+
* command: 'aiw launch',
|
|
19
|
+
* debugLog: (msg) => console.debug(msg),
|
|
20
|
+
* })
|
|
21
|
+
*
|
|
22
|
+
* if (!result.success) {
|
|
23
|
+
* console.error(result.error)
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @module lib/terminal
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Options for launching a new terminal window.
|
|
31
|
+
*/
|
|
32
|
+
export interface TerminalLaunchOptions {
|
|
33
|
+
/**
|
|
34
|
+
* Command to execute in the new terminal.
|
|
35
|
+
*/
|
|
36
|
+
command: string;
|
|
37
|
+
/**
|
|
38
|
+
* Working directory where the terminal should open.
|
|
39
|
+
*/
|
|
40
|
+
cwd: string;
|
|
41
|
+
/**
|
|
42
|
+
* Optional debug logging function.
|
|
43
|
+
* If provided, debug messages will be passed to this function.
|
|
44
|
+
*/
|
|
45
|
+
debugLog?: (message: string) => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Result of a terminal launch operation.
|
|
49
|
+
*/
|
|
50
|
+
export interface TerminalLaunchResult {
|
|
51
|
+
/**
|
|
52
|
+
* Error message if launch failed.
|
|
53
|
+
*/
|
|
54
|
+
error?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Whether the terminal was successfully launched.
|
|
57
|
+
*/
|
|
58
|
+
success: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Escape a shell argument for safe execution.
|
|
62
|
+
* Wraps path in double quotes and escapes internal quotes.
|
|
63
|
+
*
|
|
64
|
+
* @param arg - Argument to escape
|
|
65
|
+
* @returns Escaped argument safe for shell execution
|
|
66
|
+
*/
|
|
67
|
+
declare function escapeShellArg(arg: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Launch a new terminal window with the specified command.
|
|
70
|
+
*
|
|
71
|
+
* This function automatically detects the platform and uses the appropriate
|
|
72
|
+
* terminal emulator:
|
|
73
|
+
* - **Windows**: Windows Terminal (wt.exe) with PowerShell, falls back to PowerShell directly
|
|
74
|
+
* - **macOS**: Terminal.app via AppleScript
|
|
75
|
+
* - **Linux**: Tries gnome-terminal, konsole, xterm, x-terminal-emulator in order
|
|
76
|
+
*
|
|
77
|
+
* The terminal is launched in detached mode, allowing the parent process to exit
|
|
78
|
+
* without affecting the new terminal.
|
|
79
|
+
*
|
|
80
|
+
* @param options - Terminal launch options
|
|
81
|
+
* @returns Promise resolving to launch result
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* // Launch aiw in a new terminal
|
|
86
|
+
* const result = await launchTerminal({
|
|
87
|
+
* cwd: '/path/to/project',
|
|
88
|
+
* command: 'aiw launch',
|
|
89
|
+
* })
|
|
90
|
+
*
|
|
91
|
+
* if (result.success) {
|
|
92
|
+
* console.log('Terminal launched successfully')
|
|
93
|
+
* } else {
|
|
94
|
+
* console.error(`Failed: ${result.error}`)
|
|
95
|
+
* }
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export declare function launchTerminal(options: TerminalLaunchOptions): Promise<TerminalLaunchResult>;
|
|
99
|
+
/**
|
|
100
|
+
* Escape shell argument utility - exported for use in command construction.
|
|
101
|
+
*/
|
|
102
|
+
export { escapeShellArg };
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Cross-platform terminal launching utilities.
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for launching new terminal windows with commands
|
|
5
|
+
* across Windows, macOS, and Linux platforms.
|
|
6
|
+
*
|
|
7
|
+
* ## Supported Platforms
|
|
8
|
+
* - **Windows**: Windows Terminal (wt.exe) with PowerShell 7 (pwsh) fallback to PowerShell 5.1
|
|
9
|
+
* - **macOS**: Terminal.app via AppleScript
|
|
10
|
+
* - **Linux**: gnome-terminal, konsole, xterm, x-terminal-emulator (in order of preference)
|
|
11
|
+
*
|
|
12
|
+
* ## Usage
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { launchTerminal } from '../lib/terminal.js'
|
|
15
|
+
*
|
|
16
|
+
* const result = await launchTerminal({
|
|
17
|
+
* cwd: '/path/to/project',
|
|
18
|
+
* command: 'aiw launch',
|
|
19
|
+
* debugLog: (msg) => console.debug(msg),
|
|
20
|
+
* })
|
|
21
|
+
*
|
|
22
|
+
* if (!result.success) {
|
|
23
|
+
* console.error(result.error)
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @module lib/terminal
|
|
28
|
+
*/
|
|
29
|
+
import { execSync, spawn } from 'node:child_process';
|
|
30
|
+
/**
|
|
31
|
+
* Escape a shell argument for safe execution.
|
|
32
|
+
* Wraps path in double quotes and escapes internal quotes.
|
|
33
|
+
*
|
|
34
|
+
* @param arg - Argument to escape
|
|
35
|
+
* @returns Escaped argument safe for shell execution
|
|
36
|
+
*/
|
|
37
|
+
function escapeShellArg(arg) {
|
|
38
|
+
return `"${arg.replaceAll('\\', '\\\\').replaceAll('"', String.raw `\"`)}"`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Detect which PowerShell is available on Windows.
|
|
42
|
+
* Prefers PowerShell 7 (pwsh) over legacy PowerShell.
|
|
43
|
+
*
|
|
44
|
+
* @returns 'pwsh' if PowerShell 7 is available, 'powershell' otherwise
|
|
45
|
+
*/
|
|
46
|
+
function detectPowerShell() {
|
|
47
|
+
try {
|
|
48
|
+
execSync('where pwsh', { stdio: 'ignore' });
|
|
49
|
+
return 'pwsh';
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return 'powershell';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Launch PowerShell fallback when Windows Terminal is not available.
|
|
57
|
+
*
|
|
58
|
+
* @param cwd - Working directory
|
|
59
|
+
* @param command - Command to execute
|
|
60
|
+
* @param powershellCmd - PowerShell command to use (pwsh or powershell)
|
|
61
|
+
* @param debugLog - Optional debug logging function
|
|
62
|
+
*/
|
|
63
|
+
async function launchPowerShellFallback(cwd, command, powershellCmd, debugLog) {
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
const escapedPath = cwd.replaceAll("'", "''");
|
|
66
|
+
const psCommand = `Start-Process ${powershellCmd} -ArgumentList '-NoExit','-Command',"cd '${escapedPath}'; ${command}"`;
|
|
67
|
+
debugLog?.(`Launching PowerShell fallback with command: ${psCommand}`);
|
|
68
|
+
const terminal = spawn(powershellCmd, ['-Command', psCommand], {
|
|
69
|
+
detached: true,
|
|
70
|
+
stdio: 'ignore',
|
|
71
|
+
});
|
|
72
|
+
terminal.on('error', (err) => {
|
|
73
|
+
resolve({ success: false, error: `Failed to launch PowerShell: ${err.message}` });
|
|
74
|
+
});
|
|
75
|
+
terminal.unref();
|
|
76
|
+
resolve({ success: true });
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Launch Windows Terminal or PowerShell fallback.
|
|
81
|
+
*
|
|
82
|
+
* @param cwd - Working directory
|
|
83
|
+
* @param command - Command to execute
|
|
84
|
+
* @param debugLog - Optional debug logging function
|
|
85
|
+
*/
|
|
86
|
+
async function launchWindowsTerminal(cwd, command, debugLog) {
|
|
87
|
+
const powershellCmd = detectPowerShell();
|
|
88
|
+
debugLog?.(`Detected PowerShell: ${powershellCmd}`);
|
|
89
|
+
return new Promise((resolve) => {
|
|
90
|
+
const terminal = spawn('wt', ['-d', cwd, powershellCmd, '-NoExit', '-Command', command], {
|
|
91
|
+
detached: true,
|
|
92
|
+
stdio: 'ignore',
|
|
93
|
+
});
|
|
94
|
+
terminal.on('error', (err) => {
|
|
95
|
+
// If wt.exe not found, try fallback to Start-Process
|
|
96
|
+
if (err.message.includes('ENOENT')) {
|
|
97
|
+
debugLog?.('Windows Terminal not found, using PowerShell fallback');
|
|
98
|
+
launchPowerShellFallback(cwd, command, powershellCmd, debugLog)
|
|
99
|
+
.then(resolve)
|
|
100
|
+
.catch(() => resolve({ success: false, error: 'Failed to launch PowerShell fallback' }));
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
resolve({ success: false, error: `Failed to launch terminal: ${err.message}` });
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
terminal.unref();
|
|
107
|
+
resolve({ success: true });
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Launch macOS Terminal.app with command.
|
|
112
|
+
*
|
|
113
|
+
* @param cwd - Working directory
|
|
114
|
+
* @param command - Command to execute
|
|
115
|
+
* @param debugLog - Optional debug logging function
|
|
116
|
+
*/
|
|
117
|
+
async function launchMacTerminal(cwd, command, debugLog) {
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
// Escape single quotes for bash context
|
|
120
|
+
const escapedPath = cwd.replaceAll("'", String.raw `'\''`);
|
|
121
|
+
const fullCommand = `cd '${escapedPath}' && ${command}`;
|
|
122
|
+
// Escape double quotes and backslashes for AppleScript context
|
|
123
|
+
const escapedCommand = fullCommand.replaceAll('\\', '\\\\').replaceAll('"', String.raw `\"`);
|
|
124
|
+
debugLog?.(`Launching macOS Terminal with command: ${fullCommand}`);
|
|
125
|
+
const terminal = spawn('osascript', ['-e', `tell application "Terminal" to do script "${escapedCommand}"`], {
|
|
126
|
+
detached: true,
|
|
127
|
+
stdio: 'ignore',
|
|
128
|
+
});
|
|
129
|
+
terminal.on('error', (err) => {
|
|
130
|
+
resolve({ success: false, error: `Failed to launch Terminal.app: ${err.message}` });
|
|
131
|
+
});
|
|
132
|
+
terminal.unref();
|
|
133
|
+
resolve({ success: true });
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Linux terminal emulator configurations.
|
|
138
|
+
*/
|
|
139
|
+
const LINUX_TERMINALS = [
|
|
140
|
+
{ cmd: 'gnome-terminal', getArgs: (command) => ['--', 'bash', '-c', `${command}; exec bash`] },
|
|
141
|
+
{ cmd: 'konsole', getArgs: (command) => ['-e', `bash -c "${command}; exec bash"`] },
|
|
142
|
+
{ cmd: 'xterm', getArgs: (command) => ['-e', `bash -c "${command}; exec bash"`] },
|
|
143
|
+
{ cmd: 'x-terminal-emulator', getArgs: (command) => ['-e', `bash -c "${command}; exec bash"`] },
|
|
144
|
+
];
|
|
145
|
+
/**
|
|
146
|
+
* Find the first available Linux terminal emulator.
|
|
147
|
+
* Checks gnome-terminal, konsole, xterm, x-terminal-emulator in order.
|
|
148
|
+
*
|
|
149
|
+
* @returns Terminal configuration if found, null otherwise
|
|
150
|
+
*/
|
|
151
|
+
function findAvailableLinuxTerminal() {
|
|
152
|
+
for (const terminal of LINUX_TERMINALS) {
|
|
153
|
+
try {
|
|
154
|
+
execSync(`which ${terminal.cmd}`, { stdio: 'ignore' });
|
|
155
|
+
return terminal;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// Terminal not found, try next
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Launch Linux terminal emulator with command.
|
|
166
|
+
* Tries gnome-terminal, konsole, xterm, x-terminal-emulator in order.
|
|
167
|
+
*
|
|
168
|
+
* @param cwd - Working directory
|
|
169
|
+
* @param command - Command to execute
|
|
170
|
+
* @param debugLog - Optional debug logging function
|
|
171
|
+
*/
|
|
172
|
+
async function launchLinuxTerminal(cwd, command, debugLog) {
|
|
173
|
+
// Find available terminal first (synchronous)
|
|
174
|
+
const terminal = findAvailableLinuxTerminal();
|
|
175
|
+
if (!terminal) {
|
|
176
|
+
return {
|
|
177
|
+
error: 'No supported terminal emulator found. Please install gnome-terminal, konsole, or xterm.',
|
|
178
|
+
success: false,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
// Escape single quotes for bash shell
|
|
182
|
+
const escapedPath = cwd.replaceAll("'", String.raw `'\''`);
|
|
183
|
+
const fullCommand = `cd '${escapedPath}' && ${command}`;
|
|
184
|
+
debugLog?.(`Launching ${terminal.cmd} with command: ${fullCommand}`);
|
|
185
|
+
// Launch terminal (single async operation)
|
|
186
|
+
return new Promise((resolve) => {
|
|
187
|
+
const proc = spawn(terminal.cmd, terminal.getArgs(fullCommand), {
|
|
188
|
+
detached: true,
|
|
189
|
+
stdio: 'ignore',
|
|
190
|
+
});
|
|
191
|
+
proc.on('error', (err) => {
|
|
192
|
+
resolve({ error: `Failed to launch ${terminal.cmd}: ${err.message}`, success: false });
|
|
193
|
+
});
|
|
194
|
+
proc.unref();
|
|
195
|
+
resolve({ success: true });
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Launch a new terminal window with the specified command.
|
|
200
|
+
*
|
|
201
|
+
* This function automatically detects the platform and uses the appropriate
|
|
202
|
+
* terminal emulator:
|
|
203
|
+
* - **Windows**: Windows Terminal (wt.exe) with PowerShell, falls back to PowerShell directly
|
|
204
|
+
* - **macOS**: Terminal.app via AppleScript
|
|
205
|
+
* - **Linux**: Tries gnome-terminal, konsole, xterm, x-terminal-emulator in order
|
|
206
|
+
*
|
|
207
|
+
* The terminal is launched in detached mode, allowing the parent process to exit
|
|
208
|
+
* without affecting the new terminal.
|
|
209
|
+
*
|
|
210
|
+
* @param options - Terminal launch options
|
|
211
|
+
* @returns Promise resolving to launch result
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```typescript
|
|
215
|
+
* // Launch aiw in a new terminal
|
|
216
|
+
* const result = await launchTerminal({
|
|
217
|
+
* cwd: '/path/to/project',
|
|
218
|
+
* command: 'aiw launch',
|
|
219
|
+
* })
|
|
220
|
+
*
|
|
221
|
+
* if (result.success) {
|
|
222
|
+
* console.log('Terminal launched successfully')
|
|
223
|
+
* } else {
|
|
224
|
+
* console.error(`Failed: ${result.error}`)
|
|
225
|
+
* }
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
export async function launchTerminal(options) {
|
|
229
|
+
const { cwd, command, debugLog } = options;
|
|
230
|
+
const { platform } = process;
|
|
231
|
+
debugLog?.(`Launching terminal in ${cwd} with command: ${command}`);
|
|
232
|
+
debugLog?.(`Platform: ${platform}`);
|
|
233
|
+
if (platform === 'win32') {
|
|
234
|
+
return launchWindowsTerminal(cwd, command, debugLog);
|
|
235
|
+
}
|
|
236
|
+
if (platform === 'darwin') {
|
|
237
|
+
return launchMacTerminal(cwd, command, debugLog);
|
|
238
|
+
}
|
|
239
|
+
// Linux/Unix
|
|
240
|
+
return launchLinuxTerminal(cwd, command, debugLog);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Escape shell argument utility - exported for use in command construction.
|
|
244
|
+
*/
|
|
245
|
+
export { escapeShellArg };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TTY Detection Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides cross-platform TTY detection for controlling color output and spinners.
|
|
5
|
+
* Respects NO_COLOR and FORCE_COLOR environment variables per standard conventions.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Minimal interface for process-like objects used in TTY detection.
|
|
9
|
+
* Allows dependency injection for testing without mutating Node's process global.
|
|
10
|
+
*/
|
|
11
|
+
export interface ProcessLike {
|
|
12
|
+
env?: NodeJS.ProcessEnv;
|
|
13
|
+
stderr?: {
|
|
14
|
+
isTTY?: boolean | undefined;
|
|
15
|
+
};
|
|
16
|
+
stdout: {
|
|
17
|
+
isTTY?: boolean | undefined;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check if stdout is a TTY (terminal).
|
|
22
|
+
* Returns true for interactive terminal, false for piped/redirected output.
|
|
23
|
+
* @param proc - Optional process-like object for testing (defaults to global process)
|
|
24
|
+
*/
|
|
25
|
+
export declare function isTTY(proc?: ProcessLike): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Check if stderr is a TTY (terminal).
|
|
28
|
+
* Useful for determining if error output should use colors.
|
|
29
|
+
* @param proc - Optional process-like object for testing (defaults to global process)
|
|
30
|
+
*/
|
|
31
|
+
export declare function isStderrTTY(proc?: ProcessLike): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Determine if colors should be used in output.
|
|
34
|
+
* Respects NO_COLOR and FORCE_COLOR environment variables.
|
|
35
|
+
*
|
|
36
|
+
* Priority:
|
|
37
|
+
* 1. NO_COLOR (if set, always disable)
|
|
38
|
+
* 2. FORCE_COLOR (if set, use level 0-3)
|
|
39
|
+
* 3. TTY detection (colors only in TTY)
|
|
40
|
+
*
|
|
41
|
+
* @param proc - Optional process-like object for testing (defaults to global process)
|
|
42
|
+
*/
|
|
43
|
+
export declare function shouldUseColors(proc?: ProcessLike): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Check if quiet mode is enabled from flags.
|
|
46
|
+
* Quiet mode suppresses informational output (errors still shown).
|
|
47
|
+
* @param flags - Optional flags object
|
|
48
|
+
* @param flags.quiet - Quiet mode flag
|
|
49
|
+
*/
|
|
50
|
+
export declare function isQuietMode(flags?: {
|
|
51
|
+
quiet?: boolean;
|
|
52
|
+
}): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Determine if progress spinners should be shown.
|
|
55
|
+
* Spinners only make sense in interactive terminals.
|
|
56
|
+
* Automatically disabled in CI environments and quiet mode.
|
|
57
|
+
* @param flags - Optional flags object with quiet mode
|
|
58
|
+
* @param proc - Optional process-like object for testing (defaults to global process)
|
|
59
|
+
*/
|
|
60
|
+
export declare function shouldShowSpinners(flags?: {
|
|
61
|
+
quiet?: boolean;
|
|
62
|
+
}, proc?: ProcessLike): boolean;
|