ft-scout 1.0.9 → 1.1.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 (240) hide show
  1. package/.firebase/hosting.d2Vi.cache +9 -0
  2. package/.firebaserc +14 -0
  3. package/AUTONOMOUS_FEATURES.md +192 -0
  4. package/README.md +482 -29
  5. package/bin/ft.js +2 -0
  6. package/bin/src/commands/agent.d.ts.map +1 -0
  7. package/bin/src/commands/agent.js +1230 -0
  8. package/bin/src/commands/agent.js.map +1 -0
  9. package/bin/src/commands/audit.d.ts.map +1 -0
  10. package/bin/src/commands/audit.js +104 -0
  11. package/bin/src/commands/audit.js.map +1 -0
  12. package/bin/src/commands/authCommand.d.ts.map +1 -0
  13. package/bin/src/commands/authCommand.js +38 -0
  14. package/bin/src/commands/authCommand.js.map +1 -0
  15. package/bin/src/commands/benchmark.d.ts.map +1 -0
  16. package/bin/src/commands/benchmark.js +135 -0
  17. package/bin/src/commands/benchmark.js.map +1 -0
  18. package/bin/src/commands/brief.d.ts.map +1 -0
  19. package/bin/src/commands/brief.js +37 -0
  20. package/bin/src/commands/brief.js.map +1 -0
  21. package/bin/src/commands/configCommand.d.ts.map +1 -0
  22. package/bin/src/commands/configCommand.js +53 -0
  23. package/bin/src/commands/configCommand.js.map +1 -0
  24. package/bin/src/commands/dashboard.d.ts.map +1 -0
  25. package/bin/src/commands/dashboard.js +41 -0
  26. package/bin/src/commands/dashboard.js.map +1 -0
  27. package/bin/src/commands/history.d.ts.map +1 -0
  28. package/bin/src/commands/history.js +20 -0
  29. package/bin/src/commands/history.js.map +1 -0
  30. package/bin/src/commands/init.d.ts.map +1 -1
  31. package/bin/src/commands/init.js +408 -79
  32. package/bin/src/commands/init.js.map +1 -1
  33. package/bin/src/commands/login.d.ts.map +1 -0
  34. package/bin/src/commands/login.js +190 -0
  35. package/bin/src/commands/login.js.map +1 -0
  36. package/bin/src/commands/logout.d.ts.map +1 -0
  37. package/bin/src/commands/logout.js +15 -0
  38. package/bin/src/commands/logout.js.map +1 -0
  39. package/bin/src/commands/mcp.d.ts.map +1 -0
  40. package/bin/src/commands/mcp.js +393 -0
  41. package/bin/src/commands/mcp.js.map +1 -0
  42. package/bin/src/commands/owners.js +6 -6
  43. package/bin/src/commands/owners.js.map +1 -1
  44. package/bin/src/commands/plan.d.ts.map +1 -0
  45. package/bin/src/commands/plan.js +152 -0
  46. package/bin/src/commands/plan.js.map +1 -0
  47. package/bin/src/commands/quota.d.ts.map +1 -0
  48. package/bin/src/commands/quota.js +32 -0
  49. package/bin/src/commands/quota.js.map +1 -0
  50. package/bin/src/commands/recommend.d.ts.map +1 -0
  51. package/bin/src/commands/recommend.js +46 -0
  52. package/bin/src/commands/recommend.js.map +1 -0
  53. package/bin/src/commands/risky.d.ts.map +1 -1
  54. package/bin/src/commands/risky.js +235 -39
  55. package/bin/src/commands/risky.js.map +1 -1
  56. package/bin/src/commands/setup.d.ts.map +1 -1
  57. package/bin/src/commands/setup.js +411 -16
  58. package/bin/src/commands/setup.js.map +1 -1
  59. package/bin/src/commands/signup.d.ts.map +1 -0
  60. package/bin/src/commands/signup.js +118 -0
  61. package/bin/src/commands/signup.js.map +1 -0
  62. package/bin/src/commands/undo.d.ts.map +1 -0
  63. package/bin/src/commands/undo.js +59 -0
  64. package/bin/src/commands/undo.js.map +1 -0
  65. package/bin/src/commands/web.d.ts.map +1 -0
  66. package/bin/src/commands/web.js +90 -0
  67. package/bin/src/commands/web.js.map +1 -0
  68. package/bin/src/discovery/config.d.ts.map +1 -0
  69. package/bin/src/discovery/config.js +41 -0
  70. package/bin/src/discovery/config.js.map +1 -0
  71. package/bin/src/discovery/display.d.ts.map +1 -0
  72. package/bin/src/discovery/display.js +57 -0
  73. package/bin/src/discovery/display.js.map +1 -0
  74. package/bin/src/discovery/messages.d.ts.map +1 -0
  75. package/bin/src/discovery/messages.js +7 -0
  76. package/bin/src/discovery/messages.js.map +1 -0
  77. package/bin/src/discovery/signals.d.ts.map +1 -0
  78. package/bin/src/discovery/signals.js +33 -0
  79. package/bin/src/discovery/signals.js.map +1 -0
  80. package/bin/src/engine/actionVerifier.d.ts.map +1 -0
  81. package/bin/src/engine/actionVerifier.js +283 -0
  82. package/bin/src/engine/actionVerifier.js.map +1 -0
  83. package/bin/src/engine/agentEngine.d.ts.map +1 -0
  84. package/bin/src/engine/agentEngine.js +2188 -0
  85. package/bin/src/engine/agentEngine.js.map +1 -0
  86. package/bin/src/engine/agentState.d.ts.map +1 -0
  87. package/bin/src/engine/agentState.js +381 -0
  88. package/bin/src/engine/agentState.js.map +1 -0
  89. package/bin/src/engine/browser/browserController.d.ts.map +1 -0
  90. package/bin/src/engine/browser/browserController.js +737 -0
  91. package/bin/src/engine/browser/browserController.js.map +1 -0
  92. package/bin/src/engine/browser/index.d.ts.map +1 -0
  93. package/bin/src/engine/browser/index.js +2 -0
  94. package/bin/src/engine/browser/index.js.map +1 -0
  95. package/bin/src/engine/computer/computerController.d.ts.map +1 -0
  96. package/bin/src/engine/computer/computerController.js +773 -0
  97. package/bin/src/engine/computer/computerController.js.map +1 -0
  98. package/bin/src/engine/computer/computerDiagnostic.d.ts.map +1 -0
  99. package/bin/src/engine/computer/computerDiagnostic.js +71 -0
  100. package/bin/src/engine/computer/computerDiagnostic.js.map +1 -0
  101. package/bin/src/engine/computer/index.d.ts.map +1 -0
  102. package/bin/src/engine/computer/index.js +4 -0
  103. package/bin/src/engine/computer/index.js.map +1 -0
  104. package/bin/src/engine/computer/mockComputer.d.ts.map +1 -0
  105. package/bin/src/engine/computer/mockComputer.js +260 -0
  106. package/bin/src/engine/computer/mockComputer.js.map +1 -0
  107. package/bin/src/engine/computer/scoutDriver.cs +1161 -0
  108. package/bin/src/engine/computer/scout_driver.exe +0 -0
  109. package/bin/src/engine/git.d.ts.map +1 -1
  110. package/bin/src/engine/git.js +71 -0
  111. package/bin/src/engine/git.js.map +1 -1
  112. package/bin/src/engine/llm.d.ts.map +1 -1
  113. package/bin/src/engine/llm.js +1264 -35
  114. package/bin/src/engine/llm.js.map +1 -1
  115. package/bin/src/engine/planner.d.ts.map +1 -0
  116. package/bin/src/engine/planner.js +321 -0
  117. package/bin/src/engine/planner.js.map +1 -0
  118. package/bin/src/engine/runtime/agentRuntime.d.ts.map +1 -0
  119. package/bin/src/engine/runtime/agentRuntime.js +1258 -0
  120. package/bin/src/engine/runtime/agentRuntime.js.map +1 -0
  121. package/bin/src/engine/runtime/index.d.ts.map +1 -0
  122. package/bin/src/engine/runtime/index.js +2 -0
  123. package/bin/src/engine/runtime/index.js.map +1 -0
  124. package/bin/src/engine/safety.d.ts.map +1 -0
  125. package/bin/src/engine/safety.js +316 -0
  126. package/bin/src/engine/safety.js.map +1 -0
  127. package/bin/src/engine/scoutAudioRecorder.cs +215 -0
  128. package/bin/src/engine/scout_audio_recorder.exe +0 -0
  129. package/bin/src/engine/toolRegistry.d.ts.map +1 -0
  130. package/bin/src/engine/toolRegistry.js +521 -0
  131. package/bin/src/engine/toolRegistry.js.map +1 -0
  132. package/bin/src/engine/verifier.d.ts.map +1 -0
  133. package/bin/src/engine/verifier.js +414 -0
  134. package/bin/src/engine/verifier.js.map +1 -0
  135. package/bin/src/engine/voiceEngine.d.ts.map +1 -0
  136. package/bin/src/engine/voiceEngine.js +474 -0
  137. package/bin/src/engine/voiceEngine.js.map +1 -0
  138. package/bin/src/engine/web.d.ts.map +1 -0
  139. package/bin/src/engine/web.js +161 -0
  140. package/bin/src/engine/web.js.map +1 -0
  141. package/bin/src/engine/workspace/agentWorkspace.d.ts.map +1 -0
  142. package/bin/src/engine/workspace/agentWorkspace.js +505 -0
  143. package/bin/src/engine/workspace/agentWorkspace.js.map +1 -0
  144. package/bin/src/engine/workspace/index.d.ts.map +1 -0
  145. package/bin/src/engine/workspace/index.js +2 -0
  146. package/bin/src/engine/workspace/index.js.map +1 -0
  147. package/bin/src/index.js +369 -19
  148. package/bin/src/index.js.map +1 -1
  149. package/bin/src/mcp/client.d.ts.map +1 -0
  150. package/bin/src/mcp/client.js +127 -0
  151. package/bin/src/mcp/client.js.map +1 -0
  152. package/bin/src/mcp/discovery.d.ts.map +1 -0
  153. package/bin/src/mcp/discovery.js +91 -0
  154. package/bin/src/mcp/discovery.js.map +1 -0
  155. package/bin/src/mcp/health.d.ts.map +1 -0
  156. package/bin/src/mcp/health.js +44 -0
  157. package/bin/src/mcp/health.js.map +1 -0
  158. package/bin/src/mcp/index.d.ts.map +1 -0
  159. package/bin/src/mcp/index.js +15 -0
  160. package/bin/src/mcp/index.js.map +1 -0
  161. package/bin/src/mcp/manager.d.ts.map +1 -0
  162. package/bin/src/mcp/manager.js +648 -0
  163. package/bin/src/mcp/manager.js.map +1 -0
  164. package/bin/src/mcp/permissions.d.ts.map +1 -0
  165. package/bin/src/mcp/permissions.js +50 -0
  166. package/bin/src/mcp/permissions.js.map +1 -0
  167. package/bin/src/mcp/persistence.d.ts.map +1 -0
  168. package/bin/src/mcp/persistence.js +171 -0
  169. package/bin/src/mcp/persistence.js.map +1 -0
  170. package/bin/src/mcp/recovery.d.ts.map +1 -0
  171. package/bin/src/mcp/recovery.js +75 -0
  172. package/bin/src/mcp/recovery.js.map +1 -0
  173. package/bin/src/mcp/telemetry.d.ts.map +1 -0
  174. package/bin/src/mcp/telemetry.js +82 -0
  175. package/bin/src/mcp/telemetry.js.map +1 -0
  176. package/bin/src/mcp/toolRouter.d.ts.map +1 -0
  177. package/bin/src/mcp/toolRouter.js +127 -0
  178. package/bin/src/mcp/toolRouter.js.map +1 -0
  179. package/bin/src/mcp/transport.d.ts.map +1 -0
  180. package/bin/src/mcp/transport.js +328 -0
  181. package/bin/src/mcp/transport.js.map +1 -0
  182. package/bin/src/mcp/types.d.ts.map +1 -0
  183. package/bin/src/mcp/types.js +6 -0
  184. package/bin/src/mcp/types.js.map +1 -0
  185. package/bin/src/server/server.d.ts.map +1 -0
  186. package/bin/src/server/server.js +989 -0
  187. package/bin/src/server/server.js.map +1 -0
  188. package/bin/src/utils/admin.d.ts.map +1 -0
  189. package/bin/src/utils/admin.js +231 -0
  190. package/bin/src/utils/admin.js.map +1 -0
  191. package/bin/src/utils/auditLogger.d.ts.map +1 -0
  192. package/bin/src/utils/auditLogger.js +70 -0
  193. package/bin/src/utils/auditLogger.js.map +1 -0
  194. package/bin/src/utils/auth.d.ts.map +1 -0
  195. package/bin/src/utils/auth.js +679 -0
  196. package/bin/src/utils/auth.js.map +1 -0
  197. package/bin/src/utils/branding.d.ts.map +1 -0
  198. package/bin/src/utils/branding.js +14 -0
  199. package/bin/src/utils/branding.js.map +1 -0
  200. package/bin/src/utils/config.d.ts.map +1 -1
  201. package/bin/src/utils/config.js +124 -3
  202. package/bin/src/utils/config.js.map +1 -1
  203. package/bin/src/utils/email.d.ts.map +1 -0
  204. package/bin/src/utils/email.js +267 -0
  205. package/bin/src/utils/email.js.map +1 -0
  206. package/bin/src/utils/firebaseAuth.d.ts.map +1 -0
  207. package/bin/src/utils/firebaseAuth.js +174 -0
  208. package/bin/src/utils/firebaseAuth.js.map +1 -0
  209. package/bin/src/utils/holoseneAuth.d.ts.map +1 -0
  210. package/bin/src/utils/holoseneAuth.js +307 -0
  211. package/bin/src/utils/holoseneAuth.js.map +1 -0
  212. package/bin/src/utils/language.d.ts.map +1 -0
  213. package/bin/src/utils/language.js +181 -0
  214. package/bin/src/utils/language.js.map +1 -0
  215. package/bin/src/utils/markdown.d.ts.map +1 -0
  216. package/bin/src/utils/markdown.js +215 -0
  217. package/bin/src/utils/markdown.js.map +1 -0
  218. package/bin/src/utils/payment.d.ts.map +1 -0
  219. package/bin/src/utils/payment.js +290 -0
  220. package/bin/src/utils/payment.js.map +1 -0
  221. package/bin/src/utils/pricing.d.ts.map +1 -0
  222. package/bin/src/utils/pricing.js +165 -0
  223. package/bin/src/utils/pricing.js.map +1 -0
  224. package/bin/src/utils/razorpay.d.ts.map +1 -0
  225. package/bin/src/utils/razorpay.js +144 -0
  226. package/bin/src/utils/razorpay.js.map +1 -0
  227. package/bin/src/utils/session.d.ts.map +1 -0
  228. package/bin/src/utils/session.js +82 -0
  229. package/bin/src/utils/session.js.map +1 -0
  230. package/bin/src/utils/updateCheck.d.ts.map +1 -0
  231. package/bin/src/utils/updateCheck.js +108 -0
  232. package/bin/src/utils/updateCheck.js.map +1 -0
  233. package/downloads/invoice-2026-03.pdf +1 -0
  234. package/firebase.json +74 -0
  235. package/package.json +49 -46
  236. package/web/ScoutFavicon.png +0 -0
  237. package/web/app.js +3944 -0
  238. package/web/styles.css +3081 -0
  239. package/.ft/context.json +0 -17
  240. package/email-template.txt +0 -44
@@ -0,0 +1,20 @@
1
+ import chalk from 'chalk';
2
+ import { loadHistory } from '../utils/config.js';
3
+ import { safeNote } from '../utils/markdown.js';
4
+ export async function handleHistory() {
5
+ const history = loadHistory();
6
+ if (history.length === 0) {
7
+ safeNote(chalk.yellow('No recorded FT actions found in this repository yet.\nCommands like `ft fix` and configuration updates will log history entries here.'), ' FrontTerrain Execution History');
8
+ return;
9
+ }
10
+ const logLines = history.map((item, idx) => {
11
+ const timeStr = new Date(item.timestamp).toLocaleString();
12
+ return (`${chalk.bold.cyan(`#${history.length - idx}`)} ${chalk.bold.green(item.command)} ${chalk.dim(`(${timeStr})`)}\n` +
13
+ ` • ${item.description}\n` +
14
+ (item.branchName ? ` • Branch: ${chalk.yellow(item.branchName)}\n` : '') +
15
+ (item.affectedFiles && item.affectedFiles.length > 0 ? ` • Files: ${chalk.dim(item.affectedFiles.join(', '))}` : ''));
16
+ });
17
+ safeNote(logLines.join('\n\n') +
18
+ `\n\n${chalk.dim('Run `ft undo` to revert the last action performed by Scout.')}`, ' FrontTerrain Execution History');
19
+ }
20
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","sourceRoot":"","sources":["history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,MAAM,CAAC,KAAK,UAAU,aAAa;IAC/B,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAE9B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,QAAQ,CACJ,KAAK,CAAC,MAAM,CAAC,uIAAuI,CAAC,EACrJ,iCAAiC,CACpC,CAAC;QACF,OAAO;IACX,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QACvC,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,cAAc,EAAE,CAAC;QAC1D,OAAO,CACH,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,IAAI;YACjH,MAAM,IAAI,CAAC,WAAW,IAAI;YAC1B,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,cAAc,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACxE,CAAC,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACvH,CAAC;IACN,CAAC,CAAC,CAAC;IAEH,QAAQ,CACJ,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;QACrB,OAAO,KAAK,CAAC,GAAG,CAAC,6DAA6D,CAAC,EAAE,EACjF,iCAAiC,CACpC,CAAC;AACN,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["init.ts"],"names":[],"mappings":"AAsFA,wBAAsB,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,iBAkMhD"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["init.ts"],"names":[],"mappings":"AAuOA,wBAAsB,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,iBA+ZjJ"}
@@ -1,11 +1,99 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
- import { spinner, note, password, isCancel } from '@clack/prompts';
4
+ import { spinner, note, password, isCancel, text, select, confirm } from '@clack/prompts';
5
5
  import chalk from 'chalk';
6
6
  import { simpleGit } from 'simple-git';
7
- import { generateRepoSummary } from '../engine/llm.js';
8
- import { saveContext } from '../utils/config.js';
7
+ import { generateRepoSummary, askRepoQuestion } from '../engine/llm.js';
8
+ import { saveContext, loadContext, saveScanReport } from '../utils/config.js';
9
+ import { renderMarkdown, formatInlineMarkdown, safeNote } from '../utils/markdown.js';
10
+ import { normalizeLanguage, getLanguageInstruction } from '../utils/language.js';
11
+ function detectPackageManager(dir) {
12
+ if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml')))
13
+ return 'pnpm';
14
+ if (fs.existsSync(path.join(dir, 'yarn.lock')))
15
+ return 'yarn';
16
+ if (fs.existsSync(path.join(dir, 'bun.lockb')) || fs.existsSync(path.join(dir, 'bun.lock')))
17
+ return 'bun';
18
+ if (fs.existsSync(path.join(dir, 'package-lock.json')))
19
+ return 'npm';
20
+ if (fs.existsSync(path.join(dir, 'Cargo.lock')))
21
+ return 'cargo';
22
+ if (fs.existsSync(path.join(dir, 'poetry.lock')))
23
+ return 'poetry';
24
+ if (fs.existsSync(path.join(dir, 'Pipfile')))
25
+ return 'pipenv';
26
+ if (fs.existsSync(path.join(dir, 'build.gradle')) || fs.existsSync(path.join(dir, 'gradlew')))
27
+ return 'gradle';
28
+ return 'npm';
29
+ }
30
+ function detectWorkspaces(dir) {
31
+ const workspaces = [];
32
+ const checks = [
33
+ { file: 'pnpm-workspace.yaml', type: 'pnpm Workspaces' },
34
+ { file: 'turbo.json', type: 'Turborepo' },
35
+ { file: 'lerna.json', type: 'Lerna Monorepo' },
36
+ { file: 'nx.json', type: 'Nx Workspace' },
37
+ ];
38
+ for (const c of checks) {
39
+ if (fs.existsSync(path.join(dir, c.file))) {
40
+ workspaces.push(c.type);
41
+ }
42
+ }
43
+ const pkgJsonPath = path.join(dir, 'package.json');
44
+ if (fs.existsSync(pkgJsonPath)) {
45
+ try {
46
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
47
+ if (pkg.workspaces) {
48
+ if (Array.isArray(pkg.workspaces)) {
49
+ pkg.workspaces.forEach((w) => workspaces.push(`package (${w})`));
50
+ }
51
+ else if (pkg.workspaces.packages && Array.isArray(pkg.workspaces.packages)) {
52
+ pkg.workspaces.packages.forEach((w) => workspaces.push(`package (${w})`));
53
+ }
54
+ }
55
+ }
56
+ catch { }
57
+ }
58
+ // Check common subdirectories
59
+ const subDirs = ['apps', 'packages', 'services', 'modules', 'libs'];
60
+ for (const sub of subDirs) {
61
+ const subPath = path.join(dir, sub);
62
+ if (fs.existsSync(subPath) && fs.statSync(subPath).isDirectory()) {
63
+ try {
64
+ const contents = fs.readdirSync(subPath).filter((f) => {
65
+ const p = path.join(subPath, f);
66
+ return fs.statSync(p).isDirectory() && !f.startsWith('.');
67
+ });
68
+ if (contents.length > 0) {
69
+ workspaces.push(`${sub}/ (${contents.length} packages: ${contents.slice(0, 3).join(', ')}${contents.length > 3 ? '...' : ''})`);
70
+ }
71
+ }
72
+ catch { }
73
+ }
74
+ }
75
+ const isMonorepo = workspaces.length > 0;
76
+ return { isMonorepo, workspaces: Array.from(new Set(workspaces)) };
77
+ }
78
+ async function bootstrapEnvironmentFile(dir) {
79
+ const envPath = path.join(dir, '.env');
80
+ if (fs.existsSync(envPath))
81
+ return false;
82
+ const exampleCandidates = ['.env.example', '.env.template', '.env.local.example', '.env.sample', 'env.example'];
83
+ for (const candidate of exampleCandidates) {
84
+ const examplePath = path.join(dir, candidate);
85
+ if (fs.existsSync(examplePath)) {
86
+ try {
87
+ const content = fs.readFileSync(examplePath, 'utf-8');
88
+ fs.writeFileSync(envPath, content);
89
+ note(chalk.green(`Auto-created ${chalk.bold('.env')} from ${chalk.bold(candidate)}.\nPlease update any API keys or secrets in .env before running services.`), ' Environment Auto-Setup');
90
+ return true;
91
+ }
92
+ catch { }
93
+ }
94
+ }
95
+ return false;
96
+ }
9
97
  function extractNpmPackageName(target) {
10
98
  if (target.startsWith('npm:')) {
11
99
  return target.slice(4).trim();
@@ -18,7 +106,8 @@ function extractNpmPackageName(target) {
18
106
  /^git@/i.test(target) ||
19
107
  target.includes('github.com') ||
20
108
  target.includes('gitlab.com') ||
21
- target.endsWith('.git');
109
+ target.endsWith('.git') ||
110
+ (/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+$/.test(target) && !target.startsWith('@'));
22
111
  if (!isGitOrFile) {
23
112
  if (/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/i.test(target)) {
24
113
  return target;
@@ -59,8 +148,9 @@ async function getNpmRepoUrl(pkgName) {
59
148
  }
60
149
  }
61
150
  function cleanGitHubUrl(targetUrl) {
62
- if (targetUrl.includes('github.com')) {
63
- const match = targetUrl.match(/github\.com\/([^/]+(?:\/[^/]+)+)/i);
151
+ const trimmed = targetUrl.trim();
152
+ if (trimmed.includes('github.com')) {
153
+ const match = trimmed.match(/github\.com\/([^/]+(?:\/[^/]+)+)/i);
64
154
  if (match && match[1]) {
65
155
  let parts = match[1].split('/').filter(Boolean);
66
156
  const branchIndex = parts.findIndex((p) => ['tree', 'blob', 'src'].includes(p.toLowerCase()));
@@ -75,11 +165,67 @@ function cleanGitHubUrl(targetUrl) {
75
165
  }
76
166
  }
77
167
  }
78
- return targetUrl;
168
+ // Short format: owner/repo (e.g. facebook/react)
169
+ if (/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+$/.test(trimmed) && !trimmed.startsWith('@')) {
170
+ const parts = trimmed.split('/');
171
+ if (parts.length === 2 && parts[0] && parts[1]) {
172
+ const owner = parts[0];
173
+ const repo = parts[1].replace(/\.git$/i, '');
174
+ return `https://github.com/${owner}/${repo}.git`;
175
+ }
176
+ }
177
+ return trimmed;
79
178
  }
80
- export async function handleInit(repoUrl) {
179
+ async function isPublicRepo(gitUrl) {
180
+ if (!gitUrl.startsWith('https://') && !gitUrl.startsWith('http://')) {
181
+ return false;
182
+ }
183
+ try {
184
+ const cleanUrl = gitUrl.replace(/\.git$/i, '');
185
+ const smartHttpEndpoint = `${cleanUrl}.git/info/refs?service=git-upload-pack`;
186
+ const res = await fetch(smartHttpEndpoint, {
187
+ method: 'GET',
188
+ headers: {
189
+ 'User-Agent': 'git/2.39.0',
190
+ },
191
+ signal: AbortSignal.timeout(5000),
192
+ });
193
+ if (res.status === 200) {
194
+ return true;
195
+ }
196
+ }
197
+ catch { }
198
+ if (gitUrl.includes('github.com')) {
199
+ try {
200
+ const match = gitUrl.match(/github\.com\/([^/]+)\/([^/]+)/i);
201
+ if (match && match[1] && match[2]) {
202
+ const owner = match[1];
203
+ const repo = match[2].replace(/\.git$/i, '');
204
+ const apiRes = await fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`, {
205
+ headers: {
206
+ 'User-Agent': 'FrontTerrain-CLI',
207
+ 'Accept': 'application/vnd.github.v3+json',
208
+ },
209
+ signal: AbortSignal.timeout(5000),
210
+ });
211
+ if (apiRes.ok) {
212
+ const data = (await apiRes.json());
213
+ if (data && data.private === false) {
214
+ return true;
215
+ }
216
+ }
217
+ }
218
+ }
219
+ catch { }
220
+ }
221
+ return false;
222
+ }
223
+ export async function handleInit(repoUrl, options) {
81
224
  const s = spinner();
82
225
  let targetDir = process.cwd();
226
+ const existingContext = loadContext();
227
+ const targetLang = normalizeLanguage(options?.lang) || normalizeLanguage(existingContext?.language);
228
+ const langDisplay = targetLang ? getLanguageInstruction(targetLang, options?.native).displayName : undefined;
83
229
  if (repoUrl && repoUrl.trim()) {
84
230
  const rawTarget = repoUrl.trim();
85
231
  const resolvedPath = path.resolve(rawTarget);
@@ -93,98 +239,191 @@ export async function handleInit(repoUrl) {
93
239
  /^git@/i.test(rawTarget) ||
94
240
  rawTarget.includes('github.com') ||
95
241
  rawTarget.includes('gitlab.com') ||
96
- rawTarget.endsWith('.git');
242
+ rawTarget.endsWith('.git') ||
243
+ (/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+$/.test(rawTarget) && !rawTarget.startsWith('@'));
97
244
  if (npmPkgName && !isGitUrl) {
98
245
  s.start(`Resolving npm package repository (${chalk.cyan(npmPkgName)})...`);
99
246
  const resolvedUrl = await getNpmRepoUrl(npmPkgName);
100
247
  if (!resolvedUrl) {
101
248
  s.stop(chalk.red(`Failed to resolve npm package repository.`));
102
- note(chalk.red(`The specified repository is private or does not exist.`), '❌ Invalid Target');
249
+ note(chalk.red(`The specified repository is private or does not exist.`), ' Invalid Target');
103
250
  return;
104
251
  }
105
252
  gitTargetUrl = cleanGitHubUrl(resolvedUrl);
106
253
  s.stop(`Resolved npm package to repository ${chalk.cyan(gitTargetUrl)}.`);
107
254
  }
108
- s.start(`Cloning remote repository (${chalk.cyan(gitTargetUrl)})...`);
109
255
  const repoName = path.basename(gitTargetUrl, '.git') || 'cloned-repo';
110
- const tempParent = path.join(os.tmpdir(), 'ft-scout-repos');
111
- targetDir = path.join(tempParent, `${repoName}-${Date.now()}`);
112
- if (!fs.existsSync(tempParent)) {
113
- fs.mkdirSync(tempParent, { recursive: true });
256
+ if (process.stdout.isTTY) {
257
+ const cloneChoice = await select({
258
+ message: chalk.bold(`Where would you like to clone ${chalk.cyan(repoName)}?`),
259
+ options: [
260
+ { value: 'root', label: ' Root Repo (Current Directory)', hint: `Clone inside current directory: ./${repoName}` },
261
+ { value: 'temp', label: ' Temp (Temporary Folder)', hint: 'Clone to OS temporary directory (isolated sandbox)' },
262
+ { value: 'custom', label: '️ Input Yourself (Custom Path)', hint: 'Specify your own custom folder destination' },
263
+ ],
264
+ });
265
+ if (isCancel(cloneChoice)) {
266
+ note(chalk.yellow('Clone operation cancelled.'), ' Init Cancelled');
267
+ return;
268
+ }
269
+ if (cloneChoice === 'root') {
270
+ targetDir = path.resolve(process.cwd(), repoName);
271
+ }
272
+ else if (cloneChoice === 'custom') {
273
+ const customPath = await text({
274
+ message: 'Enter custom folder path to clone into:',
275
+ placeholder: `./${repoName} or C:\\projects\\${repoName}`,
276
+ validate: (val) => (!val || !val.trim() ? 'Path cannot be empty' : undefined),
277
+ });
278
+ if (isCancel(customPath)) {
279
+ note(chalk.yellow('Clone operation cancelled.'), ' Init Cancelled');
280
+ return;
281
+ }
282
+ targetDir = path.resolve(process.cwd(), customPath.trim());
283
+ }
284
+ else {
285
+ const tempParent = path.join(os.tmpdir(), 'scout-repos');
286
+ targetDir = path.join(tempParent, `${repoName}-${Date.now()}`);
287
+ }
114
288
  }
289
+ else {
290
+ const tempParent = path.join(os.tmpdir(), 'scout-repos');
291
+ targetDir = path.join(tempParent, `${repoName}-${Date.now()}`);
292
+ }
293
+ if (!fs.existsSync(path.dirname(targetDir))) {
294
+ fs.mkdirSync(path.dirname(targetDir), { recursive: true });
295
+ }
296
+ s.start(`Cloning remote repository (${chalk.cyan(gitTargetUrl)}) to ${chalk.cyan(targetDir)}...`);
115
297
  const git = simpleGit();
116
298
  try {
117
299
  await git.clone(gitTargetUrl, targetDir, ['--depth', '1']);
118
300
  s.stop(`Remote repository cloned successfully.`);
119
301
  }
120
302
  catch (err) {
121
- s.stop(chalk.yellow(`Initial clone failed (repository may be private or require authentication).`));
122
- // Interactive login / token prompt for private repos
123
- if (gitTargetUrl.startsWith('https://')) {
124
- const tokenInput = await password({
125
- message: 'Enter your GitHub / GitLab Personal Access Token (PAT) to access this private repository:',
126
- mask: '*',
127
- validate: (val) => (!val || !val.trim() ? 'Token cannot be empty' : undefined),
128
- });
129
- if (!isCancel(tokenInput) && typeof tokenInput === 'string' && tokenInput.trim()) {
130
- const cleanToken = tokenInput.trim();
131
- // Inject token into URL: https://<token>@github.com/...
132
- const authenticatedUrl = gitTargetUrl.replace(/^https:\/\//i, `https://${cleanToken}@`);
133
- const retrySpinner = spinner();
134
- retrySpinner.start(`Retrying clone with provided token...`);
135
- try {
136
- if (fs.existsSync(targetDir)) {
137
- fs.rmSync(targetDir, { recursive: true, force: true });
303
+ const isPublic = await isPublicRepo(gitTargetUrl);
304
+ if (isPublic) {
305
+ // Retry without depth if initial clone failed for a public repo
306
+ try {
307
+ if (fs.existsSync(targetDir)) {
308
+ fs.rmSync(targetDir, { recursive: true, force: true });
309
+ }
310
+ await git.clone(gitTargetUrl, targetDir);
311
+ s.stop(`Remote repository cloned successfully.`);
312
+ }
313
+ catch (retryErr) {
314
+ s.stop(chalk.red(`Failed to clone public repository.`));
315
+ note(chalk.red(`Could not clone public repository (${gitTargetUrl}).\n` +
316
+ `Git Error: ${retryErr?.message || err?.message || 'Unknown git clone error'}\n\n` +
317
+ `Please check your network connection or verify the repository URL.`), ' Clone Failed');
318
+ return;
319
+ }
320
+ }
321
+ else {
322
+ s.stop(chalk.yellow(`Initial clone failed (repository may be private or require authentication).`));
323
+ // Interactive login / token prompt for private repos ONLY
324
+ if (gitTargetUrl.startsWith('https://')) {
325
+ const tokenInput = await password({
326
+ message: 'Enter your GitHub / GitLab Personal Access Token (PAT) to access this private repository:',
327
+ mask: '*',
328
+ validate: (val) => (!val || !val.trim() ? 'Token cannot be empty' : undefined),
329
+ });
330
+ if (!isCancel(tokenInput) && typeof tokenInput === 'string' && tokenInput.trim()) {
331
+ const cleanToken = tokenInput.trim();
332
+ const encodedToken = encodeURIComponent(cleanToken);
333
+ // Inject encoded token into URL: https://<token>@github.com/...
334
+ const authenticatedUrl = gitTargetUrl.replace(/^https:\/\//i, `https://${encodedToken}@`);
335
+ const retrySpinner = spinner();
336
+ retrySpinner.start(`Retrying clone with provided token...`);
337
+ try {
338
+ if (fs.existsSync(targetDir)) {
339
+ fs.rmSync(targetDir, { recursive: true, force: true });
340
+ }
341
+ await git.clone(authenticatedUrl, targetDir, ['--depth', '1']);
342
+ retrySpinner.stop(chalk.green(`Remote repository cloned successfully!`));
343
+ }
344
+ catch (retryErr) {
345
+ retrySpinner.stop(chalk.red(`Authentication failed with the provided token.`));
346
+ note(chalk.red(`Could not clone repository with the provided token.\n` +
347
+ `Error details: ${retryErr?.message || 'Authentication error'}\n\n` +
348
+ `Alternative options:\n` +
349
+ ` • Use SSH: ${chalk.cyan('scout init git@github.com:owner/repo.git')}\n` +
350
+ ` • Or clone locally first: ${chalk.cyan('scout init ./my-repo-folder')}`), ' Authentication Failed');
351
+ return;
138
352
  }
139
- await git.clone(authenticatedUrl, targetDir, ['--depth', '1']);
140
- retrySpinner.stop(chalk.green(`Remote repository cloned successfully!`));
141
353
  }
142
- catch (retryErr) {
143
- retrySpinner.stop(chalk.red(`Authentication failed with the provided token.`));
144
- note(chalk.red(`Could not clone repository with the provided token.\n` +
145
- `Please check that your token has repo read permissions.\n\n` +
146
- `Alternative options:\n` +
147
- ` • Use SSH: ${chalk.cyan('ft-scout init git@github.com:owner/repo.git')}\n` +
148
- ` • Or clone locally first: ${chalk.cyan('ft-scout init ./my-repo-folder')}`), '❌ Authentication Failed');
354
+ else {
355
+ note(chalk.yellow('Login cancelled. Repository initialization aborted.'), ' Clone Cancelled');
149
356
  return;
150
357
  }
151
358
  }
152
359
  else {
153
- note(chalk.yellow('Login cancelled. Repository initialization aborted.'), '❌ Clone Cancelled');
360
+ note(chalk.red(`The specified repository is private or does not exist.\n\n` +
361
+ chalk.bold(' How to access private repositories:') + '\n' +
362
+ ` • Use SSH URL: ${chalk.cyan('scout init git@github.com:owner/repo.git')}\n` +
363
+ ` • Or use PAT Token URL: ${chalk.cyan('scout init https://<TOKEN>@github.com/owner/repo.git')}\n` +
364
+ ` • Or clone locally first and run: ${chalk.cyan('scout init ./my-repo-folder')}`), ' Clone Error');
154
365
  return;
155
366
  }
156
367
  }
157
- else {
158
- note(chalk.red(`The specified repository is private or does not exist.\n\n` +
159
- chalk.bold('🔒 How to access private repositories:') + '\n' +
160
- ` • Use SSH URL: ${chalk.cyan('ft-scout init git@github.com:owner/repo.git')}\n` +
161
- ` • Or use PAT Token URL: ${chalk.cyan('ft-scout init https://<TOKEN>@github.com/owner/repo.git')}\n` +
162
- ` • Or clone locally first and run: ${chalk.cyan('ft-scout init ./my-repo-folder')}`), '❌ Clone Error');
163
- return;
164
- }
165
368
  }
166
369
  }
167
370
  }
168
- s.start(`Analyzing repository at ${chalk.cyan(targetDir)}...`);
371
+ const pkgManager = detectPackageManager(targetDir);
372
+ const workspaceInfo = detectWorkspaces(targetDir);
373
+ await bootstrapEnvironmentFile(targetDir);
374
+ s.start(`Analyzing repository at ${chalk.cyan(targetDir)}${langDisplay ? ` (Language: ${chalk.bold.yellow(langDisplay)})` : ''}...`);
375
+ // Perform quick pre-flight fragility scan for .ft/scan-report.json
376
+ try {
377
+ const git = simpleGit(targetDir);
378
+ const rawLog = await git.raw(['log', '-n', '100', '--name-only', '--format=COMMIT:%H %s']);
379
+ const fileStats = {};
380
+ const logLines = rawLog.split('\n');
381
+ let currentIsHotfix = false;
382
+ for (const rawLine of logLines) {
383
+ const line = rawLine.trim();
384
+ if (!line)
385
+ continue;
386
+ if (line.startsWith('COMMIT:')) {
387
+ currentIsHotfix = /fix|bug|hotfix|patch/i.test(line);
388
+ }
389
+ else if (currentIsHotfix) {
390
+ fileStats[line] = (fileStats[line] || 0) + 1;
391
+ }
392
+ }
393
+ const scanReport = Object.entries(fileStats).map(([file, hotfixes]) => ({
394
+ file,
395
+ hotfixes,
396
+ riskLevel: hotfixes >= 3 ? 'HIGH' : hotfixes >= 1 ? 'MEDIUM' : 'LOW',
397
+ }));
398
+ if (scanReport.length > 0) {
399
+ saveScanReport(scanReport);
400
+ }
401
+ }
402
+ catch { }
169
403
  // Scan file tree up to 2 levels deep
170
- const getFiles = (dir, depth = 2) => {
404
+ const ignoredDirs = new Set([
405
+ 'node_modules', '.git', 'dist', 'build', '.dart_tool', '.next', 'out', 'vendor',
406
+ 'target', 'bin', 'obj', 'coverage', '.ft', '.vscode', '.idea', 'tmp', 'temp',
407
+ 'venv', '.venv', '__pycache__', '.turbo', '.cache'
408
+ ]);
409
+ const getFiles = (dir, currentDepth = 2) => {
171
410
  let results = [];
172
411
  try {
173
412
  const list = fs.readdirSync(dir);
174
413
  for (const file of list) {
175
- if (['node_modules', '.git', 'dist', 'build', '.dart_tool', '.next', 'out', 'vendor'].includes(file))
414
+ if (ignoredDirs.has(file) || (file.startsWith('.') && file !== '.github'))
176
415
  continue;
177
416
  const fullPath = path.join(dir, file);
178
- const relPath = path.relative(dir, fullPath);
417
+ const relPath = path.relative(targetDir, fullPath);
179
418
  const stat = fs.statSync(fullPath);
180
419
  if (stat.isDirectory()) {
181
- results.push(relPath + '/');
182
- if (depth > 1) {
183
- results = results.concat(getFiles(fullPath, depth - 1));
420
+ results.push(relPath.replace(/\\/g, '/') + '/');
421
+ if (currentDepth > 1) {
422
+ results = results.concat(getFiles(fullPath, currentDepth - 1));
184
423
  }
185
424
  }
186
425
  else {
187
- results.push(relPath);
426
+ results.push(relPath.replace(/\\/g, '/'));
188
427
  }
189
428
  }
190
429
  }
@@ -208,33 +447,123 @@ export async function handleInit(repoUrl) {
208
447
  projectName = path.basename(rawTarget, '.git');
209
448
  }
210
449
  }
211
- const aiInsight = await generateRepoSummary(projectName, filesList, manifestContent || '{}');
212
- // Save context into local .ft folder
213
- saveContext({
214
- projectName,
215
- repoUrl: repoUrl?.trim() || undefined,
216
- targetDir,
217
- stack: aiInsight.summary,
218
- entryPoint: aiInsight.entryPoint,
219
- painPoints: aiInsight.painPoints,
220
- readOrder: aiInsight.readOrder,
221
- createdAt: new Date().toISOString(),
222
- });
450
+ const aiInsight = await generateRepoSummary(projectName, filesList, manifestContent || '{}', targetLang, options?.native);
451
+ // Save context into local .ft folder (only if not skipSave)
452
+ if (!options?.skipSave) {
453
+ saveContext({
454
+ projectName,
455
+ repoUrl: repoUrl?.trim() || undefined,
456
+ targetDir,
457
+ stack: aiInsight.summary,
458
+ entryPoint: aiInsight.entryPoint,
459
+ painPoints: aiInsight.painPoints,
460
+ readOrder: aiInsight.readOrder,
461
+ createdAt: new Date().toISOString(),
462
+ language: targetLang,
463
+ packageManager: pkgManager,
464
+ isMonorepo: workspaceInfo.isMonorepo,
465
+ workspaces: workspaceInfo.workspaces,
466
+ architectureDiagram: aiInsight.architectureDiagram,
467
+ });
468
+ }
223
469
  s.stop('Repository analysis complete.');
470
+ const cleanedPainPoints = (aiInsight.painPoints || [])
471
+ .map((p) => p.trim())
472
+ .filter((p) => p.length > 5 && !/^[,\s.\-[\]:]+$/.test(p));
473
+ const cleanedReadOrder = (aiInsight.readOrder || [])
474
+ .map((r) => r.trim())
475
+ .filter((r) => r.length > 5 && !/^[,\s.\-[\]:]+$/.test(r));
476
+ const painPointsToDisplay = cleanedPainPoints.length > 0 ? cleanedPainPoints : [
477
+ ' [Subsystem Dependency]: Verify API keys or external proxy authentication service connectivity.',
478
+ ' [Environment Requirements]: Ensure runtime dependencies match system execution requirements.'
479
+ ];
480
+ const readOrderToDisplay = cleanedReadOrder.length > 0 ? cleanedReadOrder : [
481
+ 'package.json -> Core manifest defining build targets and dependency graph',
482
+ 'src/index.ts -> Primary router establishing CLI hooks and command workflow'
483
+ ];
484
+ // Format workspace & package manager details
485
+ const workspaceStr = workspaceInfo.isMonorepo
486
+ ? `\nWorkspace Type: ${chalk.bold.magenta('Monorepo')} (${workspaceInfo.workspaces.join(', ')})`
487
+ : '';
488
+ const archDiagramStr = aiInsight.architectureDiagram
489
+ ? `\n\n${chalk.bold.blue('️ Subsystem Architecture Flow:')}\n${chalk.dim(aiInsight.architectureDiagram)}`
490
+ : '';
224
491
  // Print repository summary
225
- note(`Project Name: ${chalk.bold.green(projectName)}
226
- Location: ${chalk.dim(targetDir)}
492
+ safeNote(`Project Name: ${chalk.bold.green(projectName)}
493
+ Location: ${chalk.dim(targetDir)}${langDisplay ? `\nLanguage: ${chalk.bold.yellow(langDisplay)}` : ''}
494
+ Package Manager: ${chalk.bold.cyan(pkgManager.toUpperCase())}${workspaceStr}
227
495
 
228
- ${chalk.bold.magenta('🚀 Architectural Executive Summary & Stack:')}
229
- ${chalk.yellow(aiInsight.summary)}
496
+ ${chalk.bold.magenta(' Architectural Executive Summary & Stack:')}
497
+ ${renderMarkdown(aiInsight.summary)}${archDiagramStr}
230
498
 
231
- ${chalk.bold.cyan('📌 Main Entry Point:')}
499
+ ${chalk.bold.cyan(' Main Entry Point:')}
232
500
  ${chalk.cyan(aiInsight.entryPoint)}
233
501
 
234
- ${chalk.bold.red('🔥 Known Architectural Risks & Hotspots:')}
235
- ${aiInsight.painPoints.map((p) => ` • ${p}`).join('\n\n')}
502
+ ${chalk.bold.red(' Known Architectural Risks & Hotspots:')}
503
+ ${painPointsToDisplay.map((p) => ` • ${formatInlineMarkdown(p)}`).join('\n\n')}
236
504
 
237
- ${chalk.bold.green('🧭 Strategic Onboarding Read Order:')}
238
- ${aiInsight.readOrder.map((r, idx) => ` ${idx + 1}. ${r}`).join('\n')}`, '⚡ FrontTerrain Codebase Teardown (YC Pitch Edition)');
505
+ ${chalk.bold.green(' Strategic Onboarding Read Order:')}
506
+ ${readOrderToDisplay.map((r, idx) => ` ${idx + 1}. ${formatInlineMarkdown(r)}`).join('\n')}`, ' FrontTerrain Codebase Teardown.');
507
+ if (options?.ask) {
508
+ const { speakText, listenSpeechToText, promptInteractionMode } = await import('../engine/voiceEngine.js');
509
+ const modeChoice = await promptInteractionMode();
510
+ const isAudioMode = modeChoice === 'audio';
511
+ safeNote(chalk.cyan(`Interactive AI Q&A session active${targetLang ? ` (${targetLang})` : ''} [${isAudioMode ? ' Audio Mode' : ' Text Mode'}].\nAsk any question about this codebase. Press Ctrl+C to exit at any time.`), ' Interactive Q&A Mode');
512
+ const chatHistory = [];
513
+ while (true) {
514
+ let qText = '';
515
+ if (isAudioMode) {
516
+ safeNote(chalk.cyan(' Listening to your microphone for repository query...'), ' Scout Audio Interaction');
517
+ const transcript = await listenSpeechToText();
518
+ if (transcript && transcript.trim()) {
519
+ qText = transcript.trim();
520
+ safeNote(` Voice Question: "${chalk.bold.green(qText)}"`, ' Scout STT Input');
521
+ }
522
+ else {
523
+ safeNote(chalk.yellow('No speech transcript detected or listener timed out.'), ' Scout Voice Input');
524
+ continue;
525
+ }
526
+ }
527
+ else {
528
+ const query = await text({
529
+ message: chalk.bold('Ask a query about this repository:'),
530
+ placeholder: 'e.g. How does authentication work? (Ctrl+C to exit)',
531
+ validate: (value) => {
532
+ if (!value || !value.trim())
533
+ return 'Query cannot be empty';
534
+ return undefined;
535
+ },
536
+ });
537
+ if (isCancel(query)) {
538
+ safeNote(chalk.yellow('Exiting interactive session. Goodbye!'), ' Q&A Ended');
539
+ break;
540
+ }
541
+ qText = query.trim();
542
+ }
543
+ if (['exit', 'quit', 'q'].includes(qText.toLowerCase())) {
544
+ safeNote(chalk.yellow('Exiting interactive session. Goodbye!'), ' Q&A Ended');
545
+ break;
546
+ }
547
+ const askSpinner = spinner();
548
+ const isWebNeeded = options?.web ||
549
+ /^\/(web|internet|search)\b/i.test(qText) ||
550
+ /\b(search the web|internet|online docs|latest docs|web search)\b/i.test(qText);
551
+ if (isWebNeeded) {
552
+ askSpinner.start('Searching internet & analyzing codebase context...');
553
+ }
554
+ else {
555
+ askSpinner.start('Analyzing codebase');
556
+ }
557
+ const answer = await askRepoQuestion(qText, projectName, filesList, manifestContent || '{}', chatHistory, { useWeb: options?.web, language: targetLang, native: options?.native });
558
+ askSpinner.stop('Response:');
559
+ const formattedAnswer = renderMarkdown(answer);
560
+ safeNote(formattedAnswer, ` ${qText}`);
561
+ if (isAudioMode) {
562
+ await speakText(answer);
563
+ }
564
+ chatHistory.push({ role: 'user', content: qText });
565
+ chatHistory.push({ role: 'assistant', content: answer });
566
+ }
567
+ }
239
568
  }
240
569
  //# sourceMappingURL=init.js.map