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.
Files changed (204) hide show
  1. package/README.md +1248 -0
  2. package/bin/dev.cmd +3 -0
  3. package/bin/dev.js +16 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +19 -0
  6. package/dist/commands/branch.d.ts +45 -0
  7. package/dist/commands/branch.js +488 -0
  8. package/dist/commands/clean.d.ts +34 -0
  9. package/dist/commands/clean.js +186 -0
  10. package/dist/commands/clear.d.ts +51 -0
  11. package/dist/commands/clear.js +835 -0
  12. package/dist/commands/init/index.d.ts +107 -0
  13. package/dist/commands/init/index.js +565 -0
  14. package/dist/commands/launch.d.ts +21 -0
  15. package/dist/commands/launch.js +108 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist/lib/base-command.d.ts +114 -0
  19. package/dist/lib/base-command.js +153 -0
  20. package/dist/lib/bmad-installer.d.ts +38 -0
  21. package/dist/lib/bmad-installer.js +145 -0
  22. package/dist/lib/claude-settings-types.d.ts +102 -0
  23. package/dist/lib/claude-settings-types.js +5 -0
  24. package/dist/lib/config.d.ts +25 -0
  25. package/dist/lib/config.js +46 -0
  26. package/dist/lib/debug.d.ts +39 -0
  27. package/dist/lib/debug.js +74 -0
  28. package/dist/lib/env-compat.d.ts +26 -0
  29. package/dist/lib/env-compat.js +35 -0
  30. package/dist/lib/errors.d.ts +126 -0
  31. package/dist/lib/errors.js +145 -0
  32. package/dist/lib/generic-merge.d.ts +74 -0
  33. package/dist/lib/generic-merge.js +105 -0
  34. package/dist/lib/git/branch.d.ts +67 -0
  35. package/dist/lib/git/branch.js +155 -0
  36. package/dist/lib/git/index.d.ts +11 -0
  37. package/dist/lib/git/index.js +13 -0
  38. package/dist/lib/git/safety-checks.d.ts +44 -0
  39. package/dist/lib/git/safety-checks.js +102 -0
  40. package/dist/lib/git/types.d.ts +31 -0
  41. package/dist/lib/git/types.js +6 -0
  42. package/dist/lib/git/worktree.d.ts +67 -0
  43. package/dist/lib/git/worktree.js +220 -0
  44. package/dist/lib/gitignore-manager.d.ts +10 -0
  45. package/dist/lib/gitignore-manager.js +60 -0
  46. package/dist/lib/hooks-merger.d.ts +28 -0
  47. package/dist/lib/hooks-merger.js +94 -0
  48. package/dist/lib/ide-path-resolver.d.ts +102 -0
  49. package/dist/lib/ide-path-resolver.js +129 -0
  50. package/dist/lib/index.d.ts +13 -0
  51. package/dist/lib/index.js +22 -0
  52. package/dist/lib/output.d.ts +51 -0
  53. package/dist/lib/output.js +76 -0
  54. package/dist/lib/paths.d.ts +66 -0
  55. package/dist/lib/paths.js +136 -0
  56. package/dist/lib/quiet.d.ts +12 -0
  57. package/dist/lib/quiet.js +17 -0
  58. package/dist/lib/settings-hierarchy.d.ts +42 -0
  59. package/dist/lib/settings-hierarchy.js +105 -0
  60. package/dist/lib/spawn.d.ts +105 -0
  61. package/dist/lib/spawn.js +157 -0
  62. package/dist/lib/spinner.d.ts +19 -0
  63. package/dist/lib/spinner.js +34 -0
  64. package/dist/lib/stdin.d.ts +48 -0
  65. package/dist/lib/stdin.js +60 -0
  66. package/dist/lib/template-installer.d.ts +92 -0
  67. package/dist/lib/template-installer.js +375 -0
  68. package/dist/lib/template-linter.d.ts +49 -0
  69. package/dist/lib/template-linter.js +173 -0
  70. package/dist/lib/template-merger.d.ts +47 -0
  71. package/dist/lib/template-merger.js +173 -0
  72. package/dist/lib/template-resolver.d.ts +20 -0
  73. package/dist/lib/template-resolver.js +60 -0
  74. package/dist/lib/terminal.d.ts +102 -0
  75. package/dist/lib/terminal.js +245 -0
  76. package/dist/lib/tty-detection.d.ts +62 -0
  77. package/dist/lib/tty-detection.js +83 -0
  78. package/dist/lib/user-utils.d.ts +5 -0
  79. package/dist/lib/user-utils.js +23 -0
  80. package/dist/lib/version.d.ts +99 -0
  81. package/dist/lib/version.js +144 -0
  82. package/dist/lib/watch-templates.d.ts +6 -0
  83. package/dist/lib/watch-templates.js +73 -0
  84. package/dist/lib/windsurf-hooks-hierarchy.d.ts +30 -0
  85. package/dist/lib/windsurf-hooks-hierarchy.js +66 -0
  86. package/dist/lib/windsurf-hooks-merger.d.ts +26 -0
  87. package/dist/lib/windsurf-hooks-merger.js +53 -0
  88. package/dist/lib/windsurf-hooks-types.d.ts +33 -0
  89. package/dist/lib/windsurf-hooks-types.js +5 -0
  90. package/dist/templates/CLAUDE.md +174 -0
  91. package/dist/templates/_shared/.claude/commands/handoff.md +14 -0
  92. package/dist/templates/_shared/.claude/settings.json +61 -0
  93. package/dist/templates/_shared/.codex/workflows/handoff.md +14 -0
  94. package/dist/templates/_shared/.windsurf/workflows/handoff.md +14 -0
  95. package/dist/templates/_shared/hooks/__init__.py +16 -0
  96. package/dist/templates/_shared/hooks/archive_plan.py +270 -0
  97. package/dist/templates/_shared/hooks/context_enforcer.py +621 -0
  98. package/dist/templates/_shared/hooks/context_monitor.py +322 -0
  99. package/dist/templates/_shared/hooks/file-suggestion.py +188 -0
  100. package/dist/templates/_shared/hooks/task_create_capture.py +194 -0
  101. package/dist/templates/_shared/hooks/task_update_capture.py +254 -0
  102. package/dist/templates/_shared/hooks/user_prompt_submit.py +157 -0
  103. package/dist/templates/_shared/lib/__init__.py +1 -0
  104. package/dist/templates/_shared/lib/base/__init__.py +49 -0
  105. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  106. package/dist/templates/_shared/lib/base/atomic_write.py +180 -0
  107. package/dist/templates/_shared/lib/base/constants.py +299 -0
  108. package/dist/templates/_shared/lib/base/inference.py +189 -0
  109. package/dist/templates/_shared/lib/base/utils.py +216 -0
  110. package/dist/templates/_shared/lib/context/__init__.py +119 -0
  111. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  112. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  113. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  114. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  115. package/dist/templates/_shared/lib/context/cache.py +446 -0
  116. package/dist/templates/_shared/lib/context/context_manager.py +1171 -0
  117. package/dist/templates/_shared/lib/context/discovery.py +486 -0
  118. package/dist/templates/_shared/lib/context/event_log.py +308 -0
  119. package/dist/templates/_shared/lib/context/plan_archive.py +247 -0
  120. package/dist/templates/_shared/lib/context/task_sync.py +367 -0
  121. package/dist/templates/_shared/lib/handoff/__init__.py +22 -0
  122. package/dist/templates/_shared/lib/handoff/document_generator.py +307 -0
  123. package/dist/templates/_shared/lib/templates/README.md +215 -0
  124. package/dist/templates/_shared/lib/templates/__init__.py +40 -0
  125. package/dist/templates/_shared/lib/templates/formatters.py +147 -0
  126. package/dist/templates/_shared/lib/templates/plan_context.py +119 -0
  127. package/dist/templates/_shared/scripts/save_handoff.py +99 -0
  128. package/dist/templates/_shared/workflows/handoff.md +212 -0
  129. package/dist/templates/cc-native/.claude/agents/cc-native/ACCESSIBILITY-TESTER.md +80 -0
  130. package/dist/templates/cc-native/.claude/agents/cc-native/ARCHITECT-REVIEWER.md +75 -0
  131. package/dist/templates/cc-native/.claude/agents/cc-native/ASSUMPTION-CHAIN-TRACER.md +239 -0
  132. package/dist/templates/cc-native/.claude/agents/cc-native/CLARITY-AUDITOR.md +109 -0
  133. package/dist/templates/cc-native/.claude/agents/cc-native/CODE-REVIEWER.md +71 -0
  134. package/dist/templates/cc-native/.claude/agents/cc-native/COMPLETENESS-CHECKER.md +104 -0
  135. package/dist/templates/cc-native/.claude/agents/cc-native/CONTEXT-EXTRACTOR.md +93 -0
  136. package/dist/templates/cc-native/.claude/agents/cc-native/DEVILS-ADVOCATE.md +223 -0
  137. package/dist/templates/cc-native/.claude/agents/cc-native/DOCUMENTATION-REVIEWER.md +73 -0
  138. package/dist/templates/cc-native/.claude/agents/cc-native/FEASIBILITY-ANALYST.md +93 -0
  139. package/dist/templates/cc-native/.claude/agents/cc-native/FRESH-PERSPECTIVE.md +103 -0
  140. package/dist/templates/cc-native/.claude/agents/cc-native/HANDOFF-READINESS.md +145 -0
  141. package/dist/templates/cc-native/.claude/agents/cc-native/HIDDEN-COMPLEXITY-DETECTOR.md +248 -0
  142. package/dist/templates/cc-native/.claude/agents/cc-native/INCENTIVE-MAPPER.md +235 -0
  143. package/dist/templates/cc-native/.claude/agents/cc-native/PENETRATION-TESTER.md +80 -0
  144. package/dist/templates/cc-native/.claude/agents/cc-native/PERFORMANCE-ENGINEER.md +76 -0
  145. package/dist/templates/cc-native/.claude/agents/cc-native/PLAN-ORCHESTRATOR.md +141 -0
  146. package/dist/templates/cc-native/.claude/agents/cc-native/PRECEDENT-FINDER.md +240 -0
  147. package/dist/templates/cc-native/.claude/agents/cc-native/REVERSIBILITY-ANALYST.md +211 -0
  148. package/dist/templates/cc-native/.claude/agents/cc-native/RISK-ASSESSOR.md +101 -0
  149. package/dist/templates/cc-native/.claude/agents/cc-native/SECOND-ORDER-ANALYST.md +197 -0
  150. package/dist/templates/cc-native/.claude/agents/cc-native/SIMPLICITY-GUARDIAN.md +97 -0
  151. package/dist/templates/cc-native/.claude/agents/cc-native/SKEPTIC.md +349 -0
  152. package/dist/templates/cc-native/.claude/agents/cc-native/STAKEHOLDER-ADVOCATE.md +106 -0
  153. package/dist/templates/cc-native/.claude/agents/cc-native/TRADE-OFF-ILLUMINATOR.md +205 -0
  154. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +8 -0
  155. package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -0
  156. package/dist/templates/cc-native/.claude/settings.json +119 -0
  157. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -0
  158. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +8 -0
  159. package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -0
  160. package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -0
  161. package/dist/templates/cc-native/CC-NATIVE-README.md +192 -0
  162. package/dist/templates/cc-native/MIGRATION.md +86 -0
  163. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +331 -0
  164. package/dist/templates/cc-native/_cc-native/docs/PERMISSION_REQUEST_VERIFICATION.md +147 -0
  165. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  166. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  167. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-agent-review.cpython-313.pyc +0 -0
  168. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  169. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/test_permission_request.cpython-313.pyc +0 -0
  170. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +150 -0
  171. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +746 -0
  172. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +339 -0
  173. package/dist/templates/cc-native/_cc-native/lib/__init__.py +57 -0
  174. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  175. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  176. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  177. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  178. package/dist/templates/cc-native/_cc-native/lib/async_archive.py +68 -0
  179. package/dist/templates/cc-native/_cc-native/lib/atomic_write.py +98 -0
  180. package/dist/templates/cc-native/_cc-native/lib/constants.py +45 -0
  181. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +273 -0
  182. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +28 -0
  183. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  184. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  185. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  186. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  187. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  188. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +164 -0
  189. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +89 -0
  190. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +119 -0
  191. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +103 -0
  192. package/dist/templates/cc-native/_cc-native/lib/state.py +251 -0
  193. package/dist/templates/cc-native/_cc-native/lib/utils.py +830 -0
  194. package/dist/templates/cc-native/_cc-native/plan-review.config.json +76 -0
  195. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  196. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +151 -0
  197. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +134 -0
  198. package/dist/templates/cc-native/_cc-native/workflows/specdev.md +9 -0
  199. package/dist/types/exit-codes.d.ts +11 -0
  200. package/dist/types/exit-codes.js +10 -0
  201. package/dist/types/index.d.ts +5 -0
  202. package/dist/types/index.js +7 -0
  203. package/oclif.manifest.json +405 -0
  204. 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;