@credibledata/cred-cli 0.0.103 → 0.0.105

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 (430) hide show
  1. package/README.md +106 -1
  2. package/dist/agent-contract.spec.js +548 -0
  3. package/dist/agent-contract.spec.js.map +1 -0
  4. package/dist/browser.js +72 -0
  5. package/dist/browser.js.map +1 -0
  6. package/dist/browser.spec.js +125 -0
  7. package/dist/browser.spec.js.map +1 -0
  8. package/dist/client/api.js +4137 -1159
  9. package/dist/client/api.js.map +1 -1
  10. package/dist/client-codeassist/api.js +476 -0
  11. package/dist/client-codeassist/api.js.map +1 -0
  12. package/dist/client-codeassist/base.js +69 -0
  13. package/dist/client-codeassist/base.js.map +1 -0
  14. package/dist/client-codeassist/common.js +162 -0
  15. package/dist/client-codeassist/common.js.map +1 -0
  16. package/dist/client-codeassist/configuration.js +44 -0
  17. package/dist/client-codeassist/configuration.js.map +1 -0
  18. package/dist/client-codeassist/index.js +32 -0
  19. package/dist/client-codeassist/index.js.map +1 -0
  20. package/dist/clientconfiguration.js +17 -289
  21. package/dist/clientconfiguration.js.map +1 -1
  22. package/dist/clientconfiguration.spec.js +35 -96
  23. package/dist/clientconfiguration.spec.js.map +1 -1
  24. package/dist/commands/add.js +103 -3
  25. package/dist/commands/add.js.map +1 -1
  26. package/dist/commands/add.spec.js +113 -0
  27. package/dist/commands/add.spec.js.map +1 -0
  28. package/dist/commands/connection/createconnections.js +104 -7
  29. package/dist/commands/connection/createconnections.js.map +1 -1
  30. package/dist/commands/connection/createconnections.spec.js +35 -1
  31. package/dist/commands/connection/createconnections.spec.js.map +1 -1
  32. package/dist/commands/connection/deleteconnection.js +4 -2
  33. package/dist/commands/connection/deleteconnection.js.map +1 -1
  34. package/dist/commands/connection/deleteconnection.spec.js +6 -1
  35. package/dist/commands/connection/deleteconnection.spec.js.map +1 -1
  36. package/dist/commands/connection/listconnections.js +2 -0
  37. package/dist/commands/connection/listconnections.js.map +1 -1
  38. package/dist/commands/connection/updateconnection.js +365 -0
  39. package/dist/commands/connection/updateconnection.js.map +1 -0
  40. package/dist/commands/connection/updateconnection.spec.js +625 -0
  41. package/dist/commands/connection/updateconnection.spec.js.map +1 -0
  42. package/dist/commands/database/createdatabase.js +52 -0
  43. package/dist/commands/database/createdatabase.js.map +1 -0
  44. package/dist/commands/database/database.spec.js +267 -0
  45. package/dist/commands/database/database.spec.js.map +1 -0
  46. package/dist/commands/database/deletedatabase.js +91 -0
  47. package/dist/commands/database/deletedatabase.js.map +1 -0
  48. package/dist/commands/database/getdatabase.js +97 -0
  49. package/dist/commands/database/getdatabase.js.map +1 -0
  50. package/dist/commands/database/listdatabases.js +36 -0
  51. package/dist/commands/database/listdatabases.js.map +1 -0
  52. package/dist/commands/database/shared.js +125 -0
  53. package/dist/commands/database/shared.js.map +1 -0
  54. package/dist/commands/database/shared.spec.js +109 -0
  55. package/dist/commands/database/shared.spec.js.map +1 -0
  56. package/dist/commands/database/updatedatabase.js +39 -0
  57. package/dist/commands/database/updatedatabase.js.map +1 -0
  58. package/dist/commands/environment/createenvironment.js +59 -28
  59. package/dist/commands/environment/createenvironment.js.map +1 -1
  60. package/dist/commands/environment/createenvironment.spec.js +29 -0
  61. package/dist/commands/environment/createenvironment.spec.js.map +1 -1
  62. package/dist/commands/environment/deleteenvironment.js +3 -2
  63. package/dist/commands/environment/deleteenvironment.js.map +1 -1
  64. package/dist/commands/environment/deleteenvironment.spec.js +5 -1
  65. package/dist/commands/environment/deleteenvironment.spec.js.map +1 -1
  66. package/dist/commands/environment/getenvironment.js +1 -0
  67. package/dist/commands/environment/getenvironment.js.map +1 -1
  68. package/dist/commands/environment/listenvironments.js +40 -8
  69. package/dist/commands/environment/listenvironments.js.map +1 -1
  70. package/dist/commands/environment/listenvironments.spec.js +41 -2
  71. package/dist/commands/environment/listenvironments.spec.js.map +1 -1
  72. package/dist/commands/environment/setenvironment.js +145 -6
  73. package/dist/commands/environment/setenvironment.js.map +1 -1
  74. package/dist/commands/environment/setenvironment.spec.js +256 -8
  75. package/dist/commands/environment/setenvironment.spec.js.map +1 -1
  76. package/dist/commands/environment/updateenvironment.js +12 -8
  77. package/dist/commands/environment/updateenvironment.js.map +1 -1
  78. package/dist/commands/environment/updateenvironment.spec.js +36 -2
  79. package/dist/commands/environment/updateenvironment.spec.js.map +1 -1
  80. package/dist/commands/get.js +64 -1
  81. package/dist/commands/get.js.map +1 -1
  82. package/dist/commands/group/addmember.js +3 -1
  83. package/dist/commands/group/addmember.js.map +1 -1
  84. package/dist/commands/group/addmember.spec.js +7 -2
  85. package/dist/commands/group/addmember.spec.js.map +1 -1
  86. package/dist/commands/group/creategroup.js +2 -0
  87. package/dist/commands/group/creategroup.js.map +1 -1
  88. package/dist/commands/group/deletegroup.js +3 -1
  89. package/dist/commands/group/deletegroup.js.map +1 -1
  90. package/dist/commands/group/deletegroup.spec.js +7 -1
  91. package/dist/commands/group/deletegroup.spec.js.map +1 -1
  92. package/dist/commands/group/getgroup.js +2 -0
  93. package/dist/commands/group/getgroup.js.map +1 -1
  94. package/dist/commands/group/listgroups.js +2 -0
  95. package/dist/commands/group/listgroups.js.map +1 -1
  96. package/dist/commands/group/removemember.js +3 -1
  97. package/dist/commands/group/removemember.js.map +1 -1
  98. package/dist/commands/group/removemember.spec.js +7 -2
  99. package/dist/commands/group/removemember.spec.js.map +1 -1
  100. package/dist/commands/groupaccesstoken/deleteToken.js +2 -1
  101. package/dist/commands/groupaccesstoken/deleteToken.js.map +1 -1
  102. package/dist/commands/groupaccesstoken/groupContainer.js +2 -0
  103. package/dist/commands/groupaccesstoken/groupContainer.js.map +1 -1
  104. package/dist/commands/list.js +24 -4
  105. package/dist/commands/list.js.map +1 -1
  106. package/dist/commands/materialization/cancelrun.js +3 -1
  107. package/dist/commands/materialization/cancelrun.js.map +1 -1
  108. package/dist/commands/materialization/getindex.js +2 -0
  109. package/dist/commands/materialization/getindex.js.map +1 -1
  110. package/dist/commands/materialization/getmaterialization.js +2 -0
  111. package/dist/commands/materialization/getmaterialization.js.map +1 -1
  112. package/dist/commands/materialization/getpreaggregate.js +2 -0
  113. package/dist/commands/materialization/getpreaggregate.js.map +1 -1
  114. package/dist/commands/materialization/getrun.js +2 -0
  115. package/dist/commands/materialization/getrun.js.map +1 -1
  116. package/dist/commands/materialization/listindexes.js +2 -0
  117. package/dist/commands/materialization/listindexes.js.map +1 -1
  118. package/dist/commands/materialization/listmaterializations.js +2 -0
  119. package/dist/commands/materialization/listmaterializations.js.map +1 -1
  120. package/dist/commands/materialization/listpreaggregates.js +2 -0
  121. package/dist/commands/materialization/listpreaggregates.js.map +1 -1
  122. package/dist/commands/materialization/listruns.js +3 -1
  123. package/dist/commands/materialization/listruns.js.map +1 -1
  124. package/dist/commands/materialization/rerun.js +3 -1
  125. package/dist/commands/materialization/rerun.js.map +1 -1
  126. package/dist/commands/misc/inquirerHost.js +87 -0
  127. package/dist/commands/misc/inquirerHost.js.map +1 -0
  128. package/dist/commands/misc/inquirerHost.spec.js +91 -0
  129. package/dist/commands/misc/inquirerHost.spec.js.map +1 -0
  130. package/dist/commands/misc/login.js +348 -58
  131. package/dist/commands/misc/login.js.map +1 -1
  132. package/dist/commands/misc/login.spec.js +525 -7
  133. package/dist/commands/misc/login.spec.js.map +1 -1
  134. package/dist/commands/misc/logout.js +26 -3
  135. package/dist/commands/misc/logout.js.map +1 -1
  136. package/dist/commands/misc/logout.spec.js +52 -12
  137. package/dist/commands/misc/logout.spec.js.map +1 -1
  138. package/dist/commands/misc/resolveLoginTarget.spec.js +90 -0
  139. package/dist/commands/misc/resolveLoginTarget.spec.js.map +1 -0
  140. package/dist/commands/misc/scope.spec.js +108 -0
  141. package/dist/commands/misc/scope.spec.js.map +1 -0
  142. package/dist/commands/misc/set-access-token.js +1 -1
  143. package/dist/commands/misc/set-access-token.js.map +1 -1
  144. package/dist/commands/misc/set-access-token.spec.js +13 -2
  145. package/dist/commands/misc/set-access-token.spec.js.map +1 -1
  146. package/dist/commands/misc/setup.js +819 -0
  147. package/dist/commands/misc/setup.js.map +1 -0
  148. package/dist/commands/misc/setup.spec.js +783 -0
  149. package/dist/commands/misc/setup.spec.js.map +1 -0
  150. package/dist/commands/misc/status.js +168 -0
  151. package/dist/commands/misc/status.js.map +1 -0
  152. package/dist/commands/misc/status.spec.js +301 -0
  153. package/dist/commands/misc/status.spec.js.map +1 -0
  154. package/dist/commands/misc/token.js +57 -0
  155. package/dist/commands/misc/token.js.map +1 -0
  156. package/dist/commands/misc/token.spec.js +92 -0
  157. package/dist/commands/misc/token.spec.js.map +1 -0
  158. package/dist/commands/package/deletepackage.js +4 -2
  159. package/dist/commands/package/deletepackage.js.map +1 -1
  160. package/dist/commands/package/deletepackage.spec.js +7 -1
  161. package/dist/commands/package/deletepackage.spec.js.map +1 -1
  162. package/dist/commands/package/getpackage.js +142 -0
  163. package/dist/commands/package/getpackage.js.map +1 -0
  164. package/dist/commands/package/getpackage.spec.js +246 -0
  165. package/dist/commands/package/getpackage.spec.js.map +1 -0
  166. package/dist/commands/package/listpackages.js +2 -0
  167. package/dist/commands/package/listpackages.js.map +1 -1
  168. package/dist/commands/package/publish.archive.spec.js +570 -0
  169. package/dist/commands/package/publish.archive.spec.js.map +1 -0
  170. package/dist/commands/package/publish.js +780 -49
  171. package/dist/commands/package/publish.js.map +1 -1
  172. package/dist/commands/package/publish.spec.js +451 -8
  173. package/dist/commands/package/publish.spec.js.map +1 -1
  174. package/dist/commands/package/updatepackage.js +22 -19
  175. package/dist/commands/package/updatepackage.js.map +1 -1
  176. package/dist/commands/package/updatepackage.spec.js +36 -1
  177. package/dist/commands/package/updatepackage.spec.js.map +1 -1
  178. package/dist/commands/permission/createpermission.js +3 -1
  179. package/dist/commands/permission/createpermission.js.map +1 -1
  180. package/dist/commands/permission/deletepermission.js +6 -3
  181. package/dist/commands/permission/deletepermission.js.map +1 -1
  182. package/dist/commands/permission/getpermission.js +2 -0
  183. package/dist/commands/permission/getpermission.js.map +1 -1
  184. package/dist/commands/permission/listpermissions.js +2 -0
  185. package/dist/commands/permission/listpermissions.js.map +1 -1
  186. package/dist/commands/rm.js +21 -0
  187. package/dist/commands/rm.js.map +1 -1
  188. package/dist/commands/set.js +40 -6
  189. package/dist/commands/set.js.map +1 -1
  190. package/dist/commands/set.spec.js +123 -0
  191. package/dist/commands/set.spec.js.map +1 -0
  192. package/dist/commands/table/addtable.js +155 -0
  193. package/dist/commands/table/addtable.js.map +1 -0
  194. package/dist/commands/table/deletetable.js +45 -0
  195. package/dist/commands/table/deletetable.js.map +1 -0
  196. package/dist/commands/table/gettable.js +32 -0
  197. package/dist/commands/table/gettable.js.map +1 -0
  198. package/dist/commands/table/listtables.js +36 -0
  199. package/dist/commands/table/listtables.js.map +1 -0
  200. package/dist/commands/table/table.spec.js +304 -0
  201. package/dist/commands/table/table.spec.js.map +1 -0
  202. package/dist/commands/table/tablefile.js +195 -0
  203. package/dist/commands/table/tablefile.js.map +1 -0
  204. package/dist/commands/table/tablefile.spec.js +158 -0
  205. package/dist/commands/table/tablefile.spec.js.map +1 -0
  206. package/dist/commands/update.js +21 -0
  207. package/dist/commands/update.js.map +1 -1
  208. package/dist/commands/version/archive-unarchive-version.js +4 -2
  209. package/dist/commands/version/archive-unarchive-version.js.map +1 -1
  210. package/dist/commands/version/archive-unarchive-version.spec.js +6 -1
  211. package/dist/commands/version/archive-unarchive-version.spec.js.map +1 -1
  212. package/dist/commands/version/listversions.js +2 -0
  213. package/dist/commands/version/listversions.js.map +1 -1
  214. package/dist/common/UserGroupId.js +9 -4
  215. package/dist/common/UserGroupId.js.map +1 -1
  216. package/dist/common/UserGroupId.spec.js +5 -0
  217. package/dist/common/UserGroupId.spec.js.map +1 -1
  218. package/dist/constants.js +37 -143
  219. package/dist/constants.js.map +1 -1
  220. package/dist/index.js +487 -90
  221. package/dist/index.js.map +1 -1
  222. package/dist/logger.js +42 -9
  223. package/dist/logger.js.map +1 -1
  224. package/dist/session.js +56 -0
  225. package/dist/session.js.map +1 -0
  226. package/dist/session.spec.js +103 -0
  227. package/dist/session.spec.js.map +1 -0
  228. package/dist/setup-core/briefing.js +176 -0
  229. package/dist/setup-core/briefing.js.map +1 -0
  230. package/dist/setup-core/credstore.js +415 -0
  231. package/dist/setup-core/credstore.js.map +1 -0
  232. package/dist/setup-core/credstore.spec.js +238 -0
  233. package/dist/setup-core/credstore.spec.js.map +1 -0
  234. package/dist/setup-core/environments.js +267 -0
  235. package/dist/setup-core/environments.js.map +1 -0
  236. package/dist/setup-core/flow.e2e.js +165 -0
  237. package/dist/setup-core/flow.e2e.js.map +1 -0
  238. package/dist/setup-core/flow.js +765 -0
  239. package/dist/setup-core/flow.js.map +1 -0
  240. package/dist/setup-core/flow.spec.js +1348 -0
  241. package/dist/setup-core/flow.spec.js.map +1 -0
  242. package/dist/setup-core/folderconfig.js +283 -0
  243. package/dist/setup-core/folderconfig.js.map +1 -0
  244. package/dist/setup-core/git.js +97 -0
  245. package/dist/setup-core/git.js.map +1 -0
  246. package/dist/setup-core/host.js +77 -0
  247. package/dist/setup-core/host.js.map +1 -0
  248. package/dist/setup-core/importconnections.js +470 -0
  249. package/dist/setup-core/importconnections.js.map +1 -0
  250. package/dist/setup-core/importconnections.spec.js +594 -0
  251. package/dist/setup-core/importconnections.spec.js.map +1 -0
  252. package/dist/setup-core/liveness.js +94 -0
  253. package/dist/setup-core/liveness.js.map +1 -0
  254. package/dist/setup-core/liveness.spec.js +59 -0
  255. package/dist/setup-core/liveness.spec.js.map +1 -0
  256. package/dist/setup-core/localpublisherconfig.js +26 -0
  257. package/dist/setup-core/localpublisherconfig.js.map +1 -0
  258. package/dist/setup-core/localpublisherconfig.spec.js +29 -0
  259. package/dist/setup-core/localpublisherconfig.spec.js.map +1 -0
  260. package/dist/setup-core/mcpconfig.js +198 -0
  261. package/dist/setup-core/mcpconfig.js.map +1 -0
  262. package/dist/setup-core/mcpconfig.spec.js +205 -0
  263. package/dist/setup-core/mcpconfig.spec.js.map +1 -0
  264. package/dist/setup-core/packagemanifest.js +55 -0
  265. package/dist/setup-core/packagemanifest.js.map +1 -0
  266. package/dist/setup-core/ports.js +182 -0
  267. package/dist/setup-core/ports.js.map +1 -0
  268. package/dist/setup-core/ports.spec.js +173 -0
  269. package/dist/setup-core/ports.spec.js.map +1 -0
  270. package/dist/setup-core/publisherconfig.js +268 -0
  271. package/dist/setup-core/publisherconfig.js.map +1 -0
  272. package/dist/setup-core/publisherconfig.oracle.js +166 -0
  273. package/dist/setup-core/publisherconfig.oracle.js.map +1 -0
  274. package/dist/setup-core/publisherconfig.spec.js +277 -0
  275. package/dist/setup-core/publisherconfig.spec.js.map +1 -0
  276. package/dist/setup-core/publisherversion.js +26 -0
  277. package/dist/setup-core/publisherversion.js.map +1 -0
  278. package/dist/setup-core/readiness.spec.js +161 -0
  279. package/dist/setup-core/readiness.spec.js.map +1 -0
  280. package/dist/setup-core/readylines.js +169 -0
  281. package/dist/setup-core/readylines.js.map +1 -0
  282. package/dist/setup-core/readylines.spec.js +70 -0
  283. package/dist/setup-core/readylines.spec.js.map +1 -0
  284. package/dist/setup-core/refresh.js +348 -0
  285. package/dist/setup-core/refresh.js.map +1 -0
  286. package/dist/setup-core/refresh.spec.js +501 -0
  287. package/dist/setup-core/refresh.spec.js.map +1 -0
  288. package/dist/setup-core/skills.js +279 -0
  289. package/dist/setup-core/skills.js.map +1 -0
  290. package/dist/setup-core/skills.spec.js +334 -0
  291. package/dist/setup-core/skills.spec.js.map +1 -0
  292. package/dist/setup-core/supervisor.js +879 -0
  293. package/dist/setup-core/supervisor.js.map +1 -0
  294. package/dist/setup-core/supervisor.spec.js +833 -0
  295. package/dist/setup-core/supervisor.spec.js.map +1 -0
  296. package/dist/setup-core/writefile.js +61 -0
  297. package/dist/setup-core/writefile.js.map +1 -0
  298. package/dist/setup-core/writefile.spec.js +68 -0
  299. package/dist/setup-core/writefile.spec.js.map +1 -0
  300. package/dist/utils.js +409 -3
  301. package/dist/utils.js.map +1 -1
  302. package/dist/utils.spec.js +373 -2
  303. package/dist/utils.spec.js.map +1 -1
  304. package/dist/versioncheck-refresh.js +18 -0
  305. package/dist/versioncheck-refresh.js.map +1 -0
  306. package/dist/versioncheck.js +324 -0
  307. package/dist/versioncheck.js.map +1 -0
  308. package/dist/versioncheck.spec.js +587 -0
  309. package/dist/versioncheck.spec.js.map +1 -0
  310. package/node_modules/@credibledata/cli-auth/dist/index.d.ts +86 -0
  311. package/node_modules/@credibledata/cli-auth/dist/index.js +102 -0
  312. package/node_modules/@credibledata/cli-auth/package.json +19 -0
  313. package/package.json +18 -6
  314. package/.github/workflows/build.yml +0 -23
  315. package/.mocharc.json +0 -10
  316. package/.prettierignore +0 -2
  317. package/.prettierrc +0 -4
  318. package/README-DEV.md +0 -40
  319. package/coverage/coverage-summary.json +0 -40
  320. package/coverage/tmp/coverage-2152-1789496974457-0.json +0 -1
  321. package/coverage/tmp/coverage-2153-1789496954404-0.json +0 -1
  322. package/docker/Dockerfile.ci +0 -18
  323. package/docker-compose.yml +0 -18
  324. package/eslint.config.mjs +0 -45
  325. package/generate-client.sh +0 -27
  326. package/mocha.setup.ts +0 -4
  327. package/openapitools.json +0 -10
  328. package/src/client/.openapi-generator/FILES +0 -9
  329. package/src/client/.openapi-generator/VERSION +0 -1
  330. package/src/client/.openapi-generator-ignore +0 -23
  331. package/src/client/api.ts +0 -15999
  332. package/src/client/base.ts +0 -86
  333. package/src/client/common.ts +0 -150
  334. package/src/client/configuration.ts +0 -110
  335. package/src/client/git_push.sh +0 -57
  336. package/src/client/index.ts +0 -18
  337. package/src/clientconfiguration.spec.ts +0 -615
  338. package/src/clientconfiguration.ts +0 -364
  339. package/src/commands/add.ts +0 -191
  340. package/src/commands/connection/createconnections.spec.ts +0 -413
  341. package/src/commands/connection/createconnections.ts +0 -109
  342. package/src/commands/connection/deleteconnection.spec.ts +0 -189
  343. package/src/commands/connection/deleteconnection.ts +0 -57
  344. package/src/commands/connection/listconnections.spec.ts +0 -132
  345. package/src/commands/connection/listconnections.ts +0 -58
  346. package/src/commands/environment/createenvironment.spec.ts +0 -249
  347. package/src/commands/environment/createenvironment.ts +0 -73
  348. package/src/commands/environment/deleteenvironment.spec.ts +0 -152
  349. package/src/commands/environment/deleteenvironment.ts +0 -48
  350. package/src/commands/environment/getenvironment.spec.ts +0 -110
  351. package/src/commands/environment/getenvironment.ts +0 -43
  352. package/src/commands/environment/listenvironments.spec.ts +0 -123
  353. package/src/commands/environment/listenvironments.ts +0 -67
  354. package/src/commands/environment/setenvironment.spec.ts +0 -63
  355. package/src/commands/environment/setenvironment.ts +0 -14
  356. package/src/commands/environment/updateenvironment.spec.ts +0 -386
  357. package/src/commands/environment/updateenvironment.ts +0 -168
  358. package/src/commands/get.ts +0 -153
  359. package/src/commands/group/addmember.spec.ts +0 -257
  360. package/src/commands/group/addmember.ts +0 -85
  361. package/src/commands/group/creategroup.spec.ts +0 -146
  362. package/src/commands/group/creategroup.ts +0 -54
  363. package/src/commands/group/deletegroup.spec.ts +0 -120
  364. package/src/commands/group/deletegroup.ts +0 -45
  365. package/src/commands/group/getgroup.spec.ts +0 -129
  366. package/src/commands/group/getgroup.ts +0 -47
  367. package/src/commands/group/listgroups.spec.ts +0 -137
  368. package/src/commands/group/listgroups.ts +0 -45
  369. package/src/commands/group/removemember.spec.ts +0 -224
  370. package/src/commands/group/removemember.ts +0 -75
  371. package/src/commands/groupaccesstoken/createToken.spec.ts +0 -211
  372. package/src/commands/groupaccesstoken/createToken.ts +0 -72
  373. package/src/commands/groupaccesstoken/deleteToken.spec.ts +0 -147
  374. package/src/commands/groupaccesstoken/deleteToken.ts +0 -29
  375. package/src/commands/groupaccesstoken/groupContainer.ts +0 -83
  376. package/src/commands/groupaccesstoken/listTokens.spec.ts +0 -147
  377. package/src/commands/groupaccesstoken/listTokens.ts +0 -29
  378. package/src/commands/legacy.ts +0 -5
  379. package/src/commands/list.ts +0 -326
  380. package/src/commands/materialization/cancelrun.ts +0 -70
  381. package/src/commands/materialization/getindex.ts +0 -55
  382. package/src/commands/materialization/getmaterialization.ts +0 -57
  383. package/src/commands/materialization/getpreaggregate.ts +0 -58
  384. package/src/commands/materialization/getrun.ts +0 -55
  385. package/src/commands/materialization/listindexes.ts +0 -69
  386. package/src/commands/materialization/listmaterializations.ts +0 -53
  387. package/src/commands/materialization/listpreaggregates.ts +0 -74
  388. package/src/commands/materialization/listruns.ts +0 -77
  389. package/src/commands/materialization/rerun.ts +0 -123
  390. package/src/commands/misc/login.spec.ts +0 -371
  391. package/src/commands/misc/login.ts +0 -343
  392. package/src/commands/misc/logout.spec.ts +0 -32
  393. package/src/commands/misc/logout.ts +0 -9
  394. package/src/commands/misc/loopbackOAuth.ts +0 -127
  395. package/src/commands/misc/set-access-token.spec.ts +0 -95
  396. package/src/commands/misc/set-access-token.ts +0 -32
  397. package/src/commands/package/deletepackage.spec.ts +0 -164
  398. package/src/commands/package/deletepackage.ts +0 -58
  399. package/src/commands/package/listpackages.spec.ts +0 -163
  400. package/src/commands/package/listpackages.ts +0 -74
  401. package/src/commands/package/publish.spec.ts +0 -642
  402. package/src/commands/package/publish.ts +0 -236
  403. package/src/commands/package/updatepackage.spec.ts +0 -319
  404. package/src/commands/package/updatepackage.ts +0 -95
  405. package/src/commands/permission/createpermission.spec.ts +0 -507
  406. package/src/commands/permission/createpermission.ts +0 -181
  407. package/src/commands/permission/deletepermission.ts +0 -117
  408. package/src/commands/permission/getpermission.ts +0 -115
  409. package/src/commands/permission/listpermissions.ts +0 -101
  410. package/src/commands/permission/scope.ts +0 -180
  411. package/src/commands/rm.ts +0 -146
  412. package/src/commands/set.ts +0 -25
  413. package/src/commands/update.ts +0 -107
  414. package/src/commands/version/archive-unarchive-version.spec.ts +0 -456
  415. package/src/commands/version/archive-unarchive-version.ts +0 -108
  416. package/src/commands/version/listversions.spec.ts +0 -138
  417. package/src/commands/version/listversions.ts +0 -45
  418. package/src/common/UserGroupId.spec.ts +0 -115
  419. package/src/common/UserGroupId.ts +0 -137
  420. package/src/constants.spec.ts +0 -111
  421. package/src/constants.ts +0 -228
  422. package/src/index.ts +0 -378
  423. package/src/logger.spec.ts +0 -158
  424. package/src/logger.ts +0 -90
  425. package/src/polyfills/file.polyfill.ts +0 -6
  426. package/src/utils.spec.ts +0 -135
  427. package/src/utils.ts +0 -115
  428. package/src/validator.spec.ts +0 -51
  429. package/src/validator.ts +0 -44
  430. package/tsconfig.json +0 -17
@@ -0,0 +1,1348 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ const chai_1 = require("chai");
49
+ const child_process_1 = require("child_process");
50
+ const fs_1 = __importDefault(require("fs"));
51
+ const http_1 = __importDefault(require("http"));
52
+ const os_1 = __importDefault(require("os"));
53
+ const path_1 = __importDefault(require("path"));
54
+ const sinon_1 = __importDefault(require("sinon"));
55
+ const briefing_1 = require("./briefing");
56
+ const credstore_1 = require("./credstore");
57
+ const flow_1 = require("./flow");
58
+ const folderconfig_1 = require("./folderconfig");
59
+ const host_1 = require("./host");
60
+ const ports_1 = require("./ports");
61
+ const publisherversion_1 = require("./publisherversion");
62
+ const liveness = __importStar(require("./liveness"));
63
+ const supervisor_1 = require("./supervisor");
64
+ /** A Host that answers scripted values and records what it was asked. */
65
+ function fakeHost(answers = {}) {
66
+ const logs = [];
67
+ const asked = [];
68
+ return {
69
+ logs,
70
+ asked,
71
+ select(message, choices) {
72
+ return __awaiter(this, void 0, void 0, function* () {
73
+ asked.push(message);
74
+ if (message in answers)
75
+ return answers[message];
76
+ const first = choices.find((choice) => !choice.disabled);
77
+ if (!first)
78
+ throw new Error(`nothing selectable for "${message}"`);
79
+ return first.value;
80
+ });
81
+ },
82
+ selectMany(message, choices) {
83
+ return __awaiter(this, void 0, void 0, function* () {
84
+ asked.push(message);
85
+ if (message in answers)
86
+ return answers[message];
87
+ return choices
88
+ .filter((choice) => !choice.disabled)
89
+ .map((choice) => choice.value);
90
+ });
91
+ },
92
+ confirm(message) {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ var _a;
95
+ asked.push(message);
96
+ return Boolean((_a = answers[message]) !== null && _a !== void 0 ? _a : true);
97
+ });
98
+ },
99
+ log(level, message) {
100
+ logs.push(`${level}: ${message}`);
101
+ },
102
+ write(chunk) {
103
+ logs.push(`raw: ${chunk}`);
104
+ },
105
+ openUrl(url) {
106
+ return __awaiter(this, void 0, void 0, function* () {
107
+ logs.push(`open: ${url}`);
108
+ });
109
+ },
110
+ };
111
+ }
112
+ /** Where the supervisor records the running publisher's pid, beside its config. */
113
+ function pidFile(configPath) {
114
+ return path_1.default.join(path_1.default.dirname(configPath), supervisor_1.SESSION_PID_FILE);
115
+ }
116
+ /** Run git in a directory, for building the repository states under test. */
117
+ function git(args, cwd) {
118
+ (0, child_process_1.execFileSync)("git", args, { cwd, stdio: "ignore" });
119
+ }
120
+ function jwt(claims) {
121
+ const part = (value) => Buffer.from(JSON.stringify(value)).toString("base64url");
122
+ return `${part({ alg: "HS256" })}.${part(claims)}.sig`;
123
+ }
124
+ describe("joinNames", () => {
125
+ it("joins one, two and three names the way a sentence would", () => {
126
+ (0, chai_1.expect)((0, flow_1.joinNames)(["a"])).to.equal("a");
127
+ (0, chai_1.expect)((0, flow_1.joinNames)(["a", "b"])).to.equal("a and b");
128
+ (0, chai_1.expect)((0, flow_1.joinNames)(["a", "b", "c"])).to.equal("a, b and c");
129
+ });
130
+ });
131
+ describe("setup flow", () => {
132
+ let workDir;
133
+ let homeDir;
134
+ let homeStub;
135
+ beforeEach(() => {
136
+ workDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), "flow-"));
137
+ // Session directories go under the home directory. Without this every
138
+ // run left one per test in the developer's real ~/.credible/sessions.
139
+ // Its own directory, not inside `workDir`: a session directory inside
140
+ // the modeling directory is exactly what one test asserts never happens.
141
+ homeDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), "flow-home-"));
142
+ homeStub = sinon_1.default.stub(os_1.default, "homedir").returns(homeDir);
143
+ });
144
+ afterEach(() => {
145
+ homeStub.restore();
146
+ fs_1.default.rmSync(workDir, { recursive: true, force: true });
147
+ fs_1.default.rmSync(homeDir, { recursive: true, force: true });
148
+ });
149
+ /** A credential store on disk, already "logged in" to acme/prod-bars. */
150
+ function loggedInStore() {
151
+ const credPath = path_1.default.join(workDir, ".cred");
152
+ fs_1.default.writeFileSync(credPath, JSON.stringify({
153
+ configPerEnvironment: {
154
+ production: {
155
+ organization: "acme",
156
+ project: "prod-bars",
157
+ jwtAccessToken: jwt({
158
+ aud: "https://ms2data.us.auth0.com/api/v2/",
159
+ exp: Math.floor(Date.now() / 1000) + 3600,
160
+ }),
161
+ },
162
+ },
163
+ activeEnvironment: "production",
164
+ }));
165
+ return new credstore_1.ClientConfiguration(credPath);
166
+ }
167
+ function context(host, credStore) {
168
+ return {
169
+ host,
170
+ credStore,
171
+ deployment: "production",
172
+ publisherVersion: publisherversion_1.PUBLISHER_VERSION,
173
+ cwd: workDir,
174
+ listConnections: () => __awaiter(this, void 0, void 0, function* () { return [{ name: "warehouse" }]; }),
175
+ };
176
+ }
177
+ // Without a terminal a question exits 2. It used to be asked after the
178
+ // environment was created, so an agent that stopped there left an empty
179
+ // environment behind. Every case here must throw before anything exists.
180
+ describe("refusing unanswerable questions before writing", () => {
181
+ function withWarehouse() {
182
+ fs_1.default.writeFileSync(path_1.default.join(workDir, "publisher.config.json"), JSON.stringify({
183
+ environments: [
184
+ {
185
+ name: "default",
186
+ connections: [
187
+ {
188
+ name: "warehouse",
189
+ type: "postgres",
190
+ postgresConnection: { host: "db" },
191
+ },
192
+ { name: "local", type: "duckdb", duckdbConnection: {} },
193
+ ],
194
+ },
195
+ ],
196
+ }));
197
+ }
198
+ const nothingWritten = () => (0, chai_1.expect)(fs_1.default.readdirSync(workDir).sort()).to.eql([
199
+ "publisher.config.json",
200
+ ]);
201
+ it("refuses a missing --connections, naming the importable ones", () => {
202
+ withWarehouse();
203
+ const error = (() => {
204
+ try {
205
+ (0, flow_1.refuseUnanswerable)(workDir, {}, false);
206
+ }
207
+ catch (e) {
208
+ return e;
209
+ }
210
+ })();
211
+ (0, chai_1.expect)(error).to.be.instanceOf(host_1.NeedsInputError);
212
+ (0, chai_1.expect)(error.message).to.equal('Cannot ask "Which connections should Credible test? Ticking one sends it to Credible for a reachability test; nothing is saved yet." without a terminal. Pass --connections <all|none|name,name>. Options: warehouse.');
213
+ nothingWritten();
214
+ });
215
+ it("refuses a misspelled connection name, with or without a terminal", () => {
216
+ withWarehouse();
217
+ for (const interactive of [false, true]) {
218
+ (0, chai_1.expect)(() => (0, flow_1.refuseUnanswerable)(workDir, { connections: ["warehous"] }, interactive))
219
+ .to.throw(host_1.SetupError)
220
+ .with.property("message")
221
+ .that.contains("No connection named 'warehous'");
222
+ }
223
+ nothingWritten();
224
+ });
225
+ // `findLocalPublisherConfig` reads a missing file and a malformed one
226
+ // alike as "nothing to offer", so a named selection went unchecked and
227
+ // setup exited 0 having imported nothing.
228
+ it("refuses named connections when there is no file to find them in", () => {
229
+ (0, chai_1.expect)(() => (0, flow_1.refuseUnanswerable)(workDir, { connections: ["warehouse"] }, true))
230
+ .to.throw(host_1.SetupError)
231
+ .with.property("message")
232
+ .that.contains(`There is no ${path_1.default.join(workDir, "publisher.config.json")}.`);
233
+ });
234
+ it("says so when the file is there but is not JSON", () => {
235
+ fs_1.default.writeFileSync(path_1.default.join(workDir, "publisher.config.json"), "{ not json");
236
+ (0, chai_1.expect)(() => (0, flow_1.refuseUnanswerable)(workDir, { connections: ["warehouse"] }, true))
237
+ .to.throw(host_1.SetupError)
238
+ .with.property("message")
239
+ .that.contains("is not valid JSON");
240
+ });
241
+ it("lets a named, all or none selection through", () => {
242
+ withWarehouse();
243
+ for (const connections of [["warehouse"], "all", "none"]) {
244
+ (0, chai_1.expect)(() => (0, flow_1.refuseUnanswerable)(workDir, { connections: connections }, false)).not.to.throw();
245
+ }
246
+ });
247
+ it("refuses a missing --skills when skills cred did not install are present", () => {
248
+ fs_1.default.mkdirSync(path_1.default.join(workDir, ".claude/skills/eval-answer"), {
249
+ recursive: true,
250
+ });
251
+ (0, chai_1.expect)(() => (0, flow_1.refuseUnanswerable)(workDir, {}, false))
252
+ .to.throw(host_1.NeedsInputError)
253
+ .with.property("message")
254
+ .that.contains("Pass --skills <replace|keep|none>.");
255
+ (0, chai_1.expect)(() => (0, flow_1.refuseUnanswerable)(workDir, { skills: "replace" }, false)).not.to.throw();
256
+ });
257
+ it("asks nothing up front at a terminal, where the pickers can run", () => {
258
+ withWarehouse();
259
+ fs_1.default.mkdirSync(path_1.default.join(workDir, ".claude/skills/eval-answer"), {
260
+ recursive: true,
261
+ });
262
+ (0, chai_1.expect)(() => (0, flow_1.refuseUnanswerable)(workDir, {}, true)).not.to.throw();
263
+ });
264
+ });
265
+ // The "I already run a publisher" journey: the user's own server is up on
266
+ // 4000/4040, started from this directory. Moving to a derived pair rewrote
267
+ // `.mcp.json` away from the one server the agent could reach.
268
+ describe("another publisher on the default ports", () => {
269
+ let serving;
270
+ beforeEach(() => {
271
+ serving = sinon_1.default
272
+ .stub(liveness, "isPublisherServing")
273
+ .callsFake((port) => __awaiter(void 0, void 0, void 0, function* () { return port === 4000; }));
274
+ // The journey: this directory has its own publisher config.
275
+ fs_1.default.writeFileSync(path_1.default.join(workDir, "publisher.config.json"), JSON.stringify({ environments: [] }));
276
+ });
277
+ afterEach(() => serving.restore());
278
+ // Two cred directories: the first one's `cred dev` holds 4000/4040.
279
+ // A second directory with no publisher config of its own is not the
280
+ // journey this check is for, and gets a derived pair as ports.ts
281
+ // intends, not a refusal or a pin onto the first one's server.
282
+ it("leaves a directory without its own publisher config to the derived pair", () => __awaiter(void 0, void 0, void 0, function* () {
283
+ fs_1.default.rmSync(path_1.default.join(workDir, "publisher.config.json"));
284
+ const host = fakeHost();
285
+ const wired = yield (0, flow_1.wireDirectory)(context(host, loggedInStore()), {
286
+ directory: workDir,
287
+ organization: "acme",
288
+ credibleEnvironment: "prod-bars",
289
+ // `wireDirectory` only wires; `start` is what makes the check
290
+ // refuse, and nothing is spawned here.
291
+ start: true,
292
+ open: false,
293
+ }).catch((e) => e);
294
+ (0, chai_1.expect)(wired).to.not.be.instanceOf(host_1.SetupError);
295
+ (0, chai_1.expect)(host.logs.join("\n")).to.not.contain("Another Publisher");
296
+ }));
297
+ it("keeps this directory on 4000/4040 with --no-start, and says to stop the other one", () => __awaiter(void 0, void 0, void 0, function* () {
298
+ const host = fakeHost();
299
+ const wired = yield (0, flow_1.wireDirectory)(context(host, loggedInStore()), {
300
+ directory: workDir,
301
+ organization: "acme",
302
+ credibleEnvironment: "prod-bars",
303
+ start: false,
304
+ open: false,
305
+ });
306
+ (0, chai_1.expect)([wired.uiPort, wired.mcpPort]).to.eql([4000, 4040]);
307
+ const mcp = JSON.parse(fs_1.default.readFileSync(path_1.default.join(workDir, ".mcp.json"), "utf-8"));
308
+ (0, chai_1.expect)(mcp.mcpServers.malloy.url).to.equal("http://127.0.0.1:4040/mcp");
309
+ (0, chai_1.expect)(host.logs.join("\n")).to.contain("stop that one before `cred dev`");
310
+ const free = yield (0, ports_1.suggestFreePair)(workDir);
311
+ (0, chai_1.expect)(host.logs.join("\n")).to.contain(`Or keep it running and move this directory: re-run with --port ${free.uiPort}`);
312
+ }));
313
+ it("refuses to start beside it, before importing anything", () => __awaiter(void 0, void 0, void 0, function* () {
314
+ const created = [];
315
+ const error = yield (0, flow_1.wireDirectory)(Object.assign(Object.assign({}, context(fakeHost(), loggedInStore())), { createConnection: (connection) => __awaiter(void 0, void 0, void 0, function* () {
316
+ created.push(String(connection.name));
317
+ return { ok: true };
318
+ }) }), {
319
+ directory: workDir,
320
+ organization: "acme",
321
+ credibleEnvironment: "prod-bars",
322
+ start: true,
323
+ open: false,
324
+ }).catch((e) => e);
325
+ (0, chai_1.expect)(error).to.be.instanceOf(host_1.SetupError);
326
+ (0, chai_1.expect)(error.message).to.contain("Another Publisher, not one this directory's cred session started, is running on ports 4000/4040.");
327
+ // The way out names a pair that is free now, so an agent can re-run
328
+ // without probing ports itself.
329
+ const free = yield (0, ports_1.suggestFreePair)(workDir);
330
+ (0, chai_1.expect)(error.fix).to.contain(`re-run with --port ${free.uiPort} (free now; MCP takes ${free.mcpPort}).`);
331
+ (0, chai_1.expect)(created).to.eql([]);
332
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".mcp.json"))).to.equal(false);
333
+ }));
334
+ it("serves on the --port pair beside it, and remembers it", () => __awaiter(void 0, void 0, void 0, function* () {
335
+ var _a;
336
+ const free = (yield (0, ports_1.suggestFreePair)(workDir));
337
+ const host = fakeHost();
338
+ const wired = yield (0, flow_1.wireDirectory)(context(host, loggedInStore()), {
339
+ directory: workDir,
340
+ organization: "acme",
341
+ credibleEnvironment: "prod-bars",
342
+ port: free.uiPort,
343
+ start: true,
344
+ open: false,
345
+ });
346
+ (0, chai_1.expect)([wired.uiPort, wired.mcpPort]).to.eql([
347
+ free.uiPort,
348
+ free.mcpPort,
349
+ ]);
350
+ (0, chai_1.expect)((_a = (0, folderconfig_1.findFolderConfig)(workDir)) === null || _a === void 0 ? void 0 : _a.config.ports).to.eql(free);
351
+ const mcp = JSON.parse(fs_1.default.readFileSync(path_1.default.join(workDir, ".mcp.json"), "utf-8"));
352
+ (0, chai_1.expect)(mcp.mcpServers.malloy.url).to.equal(`http://127.0.0.1:${free.mcpPort}/mcp`);
353
+ (0, chai_1.expect)(host.logs.join("\n")).to.contain(`This directory now uses ports ${free.uiPort}/${free.mcpPort}, and will keep doing so.`);
354
+ }));
355
+ it("refuses a busy --port pair before login, in the early guard", () => __awaiter(void 0, void 0, void 0, function* () {
356
+ const taken = (yield (0, ports_1.suggestFreePair)(workDir));
357
+ const holder = http_1.default.createServer();
358
+ yield new Promise((resolve) => holder.listen(taken.uiPort, "127.0.0.1", resolve));
359
+ try {
360
+ const error = yield (0, flow_1.refuseBesideOtherPublisher)(workDir, taken.uiPort).catch((e) => e);
361
+ (0, chai_1.expect)(error).to.be.instanceOf(host_1.SetupError);
362
+ const free = (yield (0, ports_1.suggestFreePair)(workDir));
363
+ (0, chai_1.expect)(error.message).to.equal(`Ports ${taken.uiPort}/${taken.mcpPort}, from --port ${taken.uiPort}, are in use. Fix: re-run with --port ${free.uiPort} (free now; MCP takes ${free.mcpPort}). cred remembers it and points .mcp.json there.`);
364
+ }
365
+ finally {
366
+ holder.close();
367
+ }
368
+ }));
369
+ it("refuses a --port pair something else holds, and names a free one", () => __awaiter(void 0, void 0, void 0, function* () {
370
+ const taken = (yield (0, ports_1.suggestFreePair)(workDir));
371
+ const holder = http_1.default.createServer();
372
+ yield new Promise((resolve) => holder.listen(taken.mcpPort, "127.0.0.1", resolve));
373
+ try {
374
+ const error = yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), {
375
+ directory: workDir,
376
+ organization: "acme",
377
+ credibleEnvironment: "prod-bars",
378
+ port: taken.uiPort,
379
+ start: true,
380
+ open: false,
381
+ }).catch((e) => e);
382
+ (0, chai_1.expect)(error).to.be.instanceOf(host_1.SetupError);
383
+ (0, chai_1.expect)(error.message).to.contain(`Ports ${taken.uiPort}/${taken.mcpPort}, from --port ${taken.uiPort}, are in use.`);
384
+ const free = (yield (0, ports_1.suggestFreePair)(workDir));
385
+ (0, chai_1.expect)(free.uiPort).to.not.equal(taken.uiPort);
386
+ (0, chai_1.expect)(error.fix).to.contain(`re-run with --port ${free.uiPort}`);
387
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".mcp.json"))).to.equal(false);
388
+ }
389
+ finally {
390
+ holder.close();
391
+ }
392
+ }));
393
+ });
394
+ describe("wireDirectory", () => {
395
+ it("warns on screen when the Credible environment has no connections", () => __awaiter(void 0, void 0, void 0, function* () {
396
+ const host = fakeHost();
397
+ yield (0, flow_1.wireDirectory)(Object.assign(Object.assign({}, context(host, loggedInStore())), { listConnections: () => __awaiter(void 0, void 0, void 0, function* () { return []; }) }), {
398
+ directory: workDir,
399
+ organization: "acme",
400
+ credibleEnvironment: "prod-bars",
401
+ start: false,
402
+ open: false,
403
+ });
404
+ (0, chai_1.expect)(host.logs.join("\n")).to.contain("The Credible environment 'prod-bars' has no connections, so the local Publisher will have nothing to query. To fix it, add one with `cred add connection`, then restart `cred dev`.");
405
+ }));
406
+ it("does not warn when it has some", () => __awaiter(void 0, void 0, void 0, function* () {
407
+ const host = fakeHost();
408
+ yield (0, flow_1.wireDirectory)(context(host, loggedInStore()), {
409
+ directory: workDir,
410
+ organization: "acme",
411
+ credibleEnvironment: "prod-bars",
412
+ start: false,
413
+ open: false,
414
+ });
415
+ (0, chai_1.expect)(host.logs.join("\n")).to.not.contain("has no connections");
416
+ }));
417
+ it("leaves a complete, self-consistent directory on disk", () => __awaiter(void 0, void 0, void 0, function* () {
418
+ const host = fakeHost();
419
+ const wired = yield (0, flow_1.wireDirectory)(context(host, loggedInStore()), {
420
+ directory: workDir,
421
+ organization: "acme",
422
+ credibleEnvironment: "prod-bars",
423
+ start: false,
424
+ open: false,
425
+ });
426
+ // publisher.config.json is NOT written by wiring — the supervisor
427
+ // writes it at spawn, because that is when a token can be stamped.
428
+ // And it is never written into the modeling directory at all: it
429
+ // carries a live warehouse token, so it must not be somewhere
430
+ // `git add -A` can reach. See `sessionConfigPath`.
431
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, "publisher.config.json")), "a token-bearing config must never land in the modeling directory").to.equal(false);
432
+ (0, chai_1.expect)(wired.configPath.startsWith(workDir)).to.equal(false);
433
+ (0, chai_1.expect)(wired.configPath).to.equal(path_1.default.join(wired.serverRoot, "publisher.config.json"));
434
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".mcp.json"))).to.equal(true);
435
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, "CLAUDE.md"))).to.equal(true);
436
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, "AGENTS.md"))).to.equal(true);
437
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".credible", "config.json"))).to.equal(true);
438
+ // Named after the Credible environment it proxies to, so the two names
439
+ // read as a pair. It was `default`, and an agent passed `prod-bars` to
440
+ // the local tools and got "Resource not found".
441
+ (0, chai_1.expect)(wired.config.environments[0].name).to.equal("prod-bars-local");
442
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(workDir, "CLAUDE.md"), "utf-8")).to.contain("- Local environment: `prod-bars-local`");
443
+ // The port in .mcp.json must be the port the pin remembers, or a later
444
+ // `cred dev` would bind one port and the agent would dial another.
445
+ const mcp = JSON.parse(fs_1.default.readFileSync(path_1.default.join(workDir, ".mcp.json"), "utf-8"));
446
+ const pin = (0, folderconfig_1.findFolderConfig)(workDir).config;
447
+ (0, chai_1.expect)(mcp.mcpServers.malloy.url).to.contain(String(pin.ports.mcpPort));
448
+ (0, chai_1.expect)(wired.mcpPort).to.equal(pin.ports.mcpPort);
449
+ // The manifest has to be one `cred publish` can send: a version is
450
+ // the package's version id, and without one the server 500s.
451
+ (0, chai_1.expect)(JSON.parse(fs_1.default.readFileSync(path_1.default.join(workDir, "publisher.json"), "utf-8"))).to.eql({
452
+ name: path_1.default.basename(workDir),
453
+ version: "0.0.1",
454
+ description: "",
455
+ });
456
+ }));
457
+ // Leaving production out of the pin made the directory follow the global
458
+ // deployment, so a production directory used staging's credentials once
459
+ // `~/.cred` moved to staging.
460
+ it("pins the deployment, production included", () => __awaiter(void 0, void 0, void 0, function* () {
461
+ yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), {
462
+ directory: workDir,
463
+ organization: "acme",
464
+ credibleEnvironment: "prod-bars",
465
+ start: false,
466
+ open: false,
467
+ });
468
+ (0, chai_1.expect)((0, folderconfig_1.findFolderConfig)(workDir).config.deployment).to.equal("production");
469
+ }));
470
+ it("re-pins a directory that moves from staging back to production", () => __awaiter(void 0, void 0, void 0, function* () {
471
+ const options = {
472
+ directory: workDir,
473
+ organization: "acme",
474
+ credibleEnvironment: "prod-bars",
475
+ start: false,
476
+ open: false,
477
+ };
478
+ yield (0, flow_1.wireDirectory)(Object.assign(Object.assign({}, context(fakeHost(), loggedInStore())), { deployment: "staging" }), options);
479
+ (0, chai_1.expect)((0, folderconfig_1.findFolderConfig)(workDir).config.deployment).to.equal("staging");
480
+ yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), options);
481
+ (0, chai_1.expect)((0, folderconfig_1.findFolderConfig)(workDir).config.deployment).to.equal("production");
482
+ // Only that key: the merge still has to preserve everything else.
483
+ (0, chai_1.expect)((0, folderconfig_1.findFolderConfig)(workDir).config.organization).to.equal("acme");
484
+ }));
485
+ it("keeps a directory's ports when it is named by a relative path", () => __awaiter(void 0, void 0, void 0, function* () {
486
+ // `directory` reaches three hash/compare sites — the session directory,
487
+ // the derived port pair, and `findFolderConfig`'s always-absolute
488
+ // `root` — so an unresolved `--directory` was a different identity at
489
+ // each. `cred init -d sub` re-derived its ports every run and rewrote
490
+ // `.mcp.json` with a URL the agent's saved config no longer matched.
491
+ const relative = path_1.default.relative(process.cwd(), workDir);
492
+ const options = {
493
+ organization: "acme",
494
+ credibleEnvironment: "prod-bars",
495
+ start: false,
496
+ open: false,
497
+ };
498
+ const first = yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), Object.assign(Object.assign({}, options), { directory: workDir }));
499
+ const second = yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), Object.assign(Object.assign({}, options), { directory: relative }));
500
+ (0, chai_1.expect)(second.directory).to.equal(workDir);
501
+ (0, chai_1.expect)(second.uiPort).to.equal(first.uiPort);
502
+ (0, chai_1.expect)(second.mcpPort).to.equal(first.mcpPort);
503
+ (0, chai_1.expect)(second.serverRoot).to.equal(first.serverRoot);
504
+ (0, chai_1.expect)(second.config.environments[0].packages[0].location).to.equal(workDir);
505
+ }));
506
+ it("refuses with an actionable message when no environment is selected", () => __awaiter(void 0, void 0, void 0, function* () {
507
+ let thrown;
508
+ try {
509
+ yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), {
510
+ directory: workDir,
511
+ organization: "acme",
512
+ credibleEnvironment: "",
513
+ start: false,
514
+ open: false,
515
+ });
516
+ }
517
+ catch (error) {
518
+ thrown = error;
519
+ }
520
+ (0, chai_1.expect)(thrown).to.be.instanceOf(host_1.SetupError);
521
+ (0, chai_1.expect)(thrown.message).to.contain("cred set environment");
522
+ }));
523
+ // A second `cred dev` found the pinned ports busy — held by the first —
524
+ // moved to another pair, rewrote `.mcp.json` and started a second server,
525
+ // leaving the first serving on the URL an open agent still had.
526
+ it("refuses to start a second session while this directory's publisher is running", () => __awaiter(void 0, void 0, void 0, function* () {
527
+ const server = http_1.default.createServer((_request, response) => {
528
+ response.writeHead(200).end("[]");
529
+ });
530
+ yield new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
531
+ const uiPort = server.address().port;
532
+ const store = loggedInStore();
533
+ const base = {
534
+ directory: workDir,
535
+ organization: "acme",
536
+ credibleEnvironment: "prod-bars",
537
+ open: false,
538
+ };
539
+ const wired = yield (0, flow_1.wireDirectory)(context(fakeHost(), store), Object.assign(Object.assign({}, base), { start: false }));
540
+ (0, folderconfig_1.writeFolderConfig)(workDir, { ports: { uiPort, mcpPort: uiPort + 1 } });
541
+ // The running session's config and pid: present only while one runs.
542
+ fs_1.default.writeFileSync(wired.configPath, "{}");
543
+ fs_1.default.writeFileSync(pidFile(wired.configPath), String(process.pid));
544
+ try {
545
+ yield (0, flow_1.wireDirectory)(context(fakeHost(), store), Object.assign(Object.assign({}, base), { start: true }));
546
+ chai_1.expect.fail("expected a refusal");
547
+ }
548
+ catch (error) {
549
+ (0, chai_1.expect)(error.message).to.equal(`A local Publisher for this directory is already running at http://localhost:${uiPort}. Fix: use that one (\`cred status --json\` reports it), or stop it — Ctrl-C in its terminal — and run this again.`);
550
+ }
551
+ finally {
552
+ fs_1.default.rmSync(wired.configPath, { force: true });
553
+ server.close();
554
+ }
555
+ // Wiring without starting is still allowed: it starts nothing.
556
+ yield (0, flow_1.wireDirectory)(context(fakeHost(), store), Object.assign(Object.assign({}, base), { start: false }));
557
+ }));
558
+ // A `kill -9` or a reboot leaves the session files behind. With another
559
+ // directory's publisher on the same pinned port, that read as this
560
+ // directory's session, and `cred dev` refused to start.
561
+ it("does not take leftover session files and another publisher for its own session", () => __awaiter(void 0, void 0, void 0, function* () {
562
+ const server = http_1.default.createServer((_request, response) => {
563
+ response.writeHead(200).end("[]");
564
+ });
565
+ yield new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
566
+ const uiPort = server.address().port;
567
+ try {
568
+ const wired = yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), {
569
+ directory: workDir,
570
+ organization: "acme",
571
+ credibleEnvironment: "prod-bars",
572
+ open: false,
573
+ start: false,
574
+ });
575
+ fs_1.default.writeFileSync(wired.configPath, "{}");
576
+ // A pid nothing runs as.
577
+ fs_1.default.writeFileSync(pidFile(wired.configPath), "2147483646");
578
+ (0, chai_1.expect)(yield (0, flow_1.isOwnSessionRunning)(workDir, uiPort)).to.equal(false);
579
+ fs_1.default.writeFileSync(pidFile(wired.configPath), String(process.pid));
580
+ (0, chai_1.expect)(yield (0, flow_1.isOwnSessionRunning)(workDir, uiPort)).to.equal(true);
581
+ // The same live pid, but in a file written before the last boot:
582
+ // after a reboot that pid can be some other process.
583
+ const beforeBoot = new Date(Date.now() - os_1.default.uptime() * 1000 - 60000);
584
+ fs_1.default.utimesSync(pidFile(wired.configPath), beforeBoot, beforeBoot);
585
+ (0, chai_1.expect)(yield (0, flow_1.isOwnSessionRunning)(workDir, uiPort)).to.equal(false);
586
+ }
587
+ finally {
588
+ server.close();
589
+ }
590
+ }));
591
+ // With the session holding the pinned ports, probing found them busy
592
+ // and moved `.mcp.json` to a pair nothing was listening on.
593
+ it("keeps the running session's ports when wiring without starting", () => __awaiter(void 0, void 0, void 0, function* () {
594
+ const server = http_1.default.createServer((_request, response) => {
595
+ response.writeHead(200).end("[]");
596
+ });
597
+ yield new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
598
+ const uiPort = server.address().port;
599
+ const mcp = http_1.default.createServer();
600
+ yield new Promise((resolve) => mcp.listen(uiPort + 1, "127.0.0.1", resolve));
601
+ const store = loggedInStore();
602
+ const base = {
603
+ directory: workDir,
604
+ organization: "acme",
605
+ credibleEnvironment: "prod-bars",
606
+ open: false,
607
+ start: false,
608
+ };
609
+ const wired = yield (0, flow_1.wireDirectory)(context(fakeHost(), store), base);
610
+ (0, folderconfig_1.writeFolderConfig)(workDir, { ports: { uiPort, mcpPort: uiPort + 1 } });
611
+ fs_1.default.writeFileSync(wired.configPath, "{}");
612
+ fs_1.default.writeFileSync(pidFile(wired.configPath), String(process.pid));
613
+ try {
614
+ const rewired = yield (0, flow_1.wireDirectory)(context(fakeHost(), store), base);
615
+ (0, chai_1.expect)(rewired.uiPort).to.equal(uiPort);
616
+ (0, chai_1.expect)(rewired.mcpPort).to.equal(uiPort + 1);
617
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(workDir, ".mcp.json"), "utf-8")).to.contain(`:${uiPort + 1}/mcp`);
618
+ }
619
+ finally {
620
+ fs_1.default.rmSync(wired.configPath, { force: true });
621
+ server.close();
622
+ mcp.close();
623
+ }
624
+ }));
625
+ // Moving the pin and .mcp.json off the pair the live session serves
626
+ // left the agent's URL pointing at nothing, and the next ownership
627
+ // check then missed the running server.
628
+ it("refuses a different --port while this directory's session is running", () => __awaiter(void 0, void 0, void 0, function* () {
629
+ var _a;
630
+ const server = http_1.default.createServer((_request, response) => {
631
+ response.writeHead(200).end("[]");
632
+ });
633
+ // A pair `--port` can name, so the same-pair case below is one.
634
+ const pair = (yield (0, ports_1.suggestFreePair)(workDir));
635
+ const { uiPort, mcpPort } = pair;
636
+ yield new Promise((resolve) => server.listen(uiPort, "127.0.0.1", resolve));
637
+ const store = loggedInStore();
638
+ const base = {
639
+ directory: workDir,
640
+ organization: "acme",
641
+ credibleEnvironment: "prod-bars",
642
+ open: false,
643
+ start: false,
644
+ };
645
+ const wired = yield (0, flow_1.wireDirectory)(context(fakeHost(), store), base);
646
+ (0, folderconfig_1.writeFolderConfig)(workDir, { ports: pair });
647
+ const mcpBefore = fs_1.default.readFileSync(path_1.default.join(workDir, ".mcp.json"), "utf-8");
648
+ fs_1.default.writeFileSync(wired.configPath, "{}");
649
+ fs_1.default.writeFileSync(pidFile(wired.configPath), String(process.pid));
650
+ try {
651
+ const other = uiPort === 4100 ? 4200 : 4100;
652
+ for (const attempt of [
653
+ () => (0, flow_1.refuseBesideOtherPublisher)(workDir, other),
654
+ () => (0, flow_1.wireDirectory)(context(fakeHost(), store), Object.assign(Object.assign({}, base), { port: other })),
655
+ ]) {
656
+ const error = yield attempt().catch((e) => e);
657
+ (0, chai_1.expect)(error).to.be.instanceOf(host_1.SetupError);
658
+ (0, chai_1.expect)(error.message).to.equal(`This directory's local Publisher is running on ${uiPort}/${mcpPort}, so --port ${other} cannot move it. Fix: stop that session (Ctrl-C in its terminal), then run this again with --port ${other}.`);
659
+ }
660
+ (0, chai_1.expect)((_a = (0, folderconfig_1.findFolderConfig)(workDir)) === null || _a === void 0 ? void 0 : _a.config.ports).to.eql(pair);
661
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(workDir, ".mcp.json"), "utf-8")).to.equal(mcpBefore);
662
+ // The same pair is not a move, so --no-start beside it still works.
663
+ const same = yield (0, flow_1.wireDirectory)(context(fakeHost(), store), Object.assign(Object.assign({}, base), { port: uiPort }));
664
+ (0, chai_1.expect)(same.uiPort).to.equal(uiPort);
665
+ }
666
+ finally {
667
+ fs_1.default.rmSync(wired.configPath, { force: true });
668
+ server.close();
669
+ }
670
+ }));
671
+ it("re-running is idempotent", () => __awaiter(void 0, void 0, void 0, function* () {
672
+ const options = {
673
+ directory: workDir,
674
+ organization: "acme",
675
+ credibleEnvironment: "prod-bars",
676
+ start: false,
677
+ open: false,
678
+ };
679
+ const first = yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), options);
680
+ const mcpAfterFirst = fs_1.default.readFileSync(path_1.default.join(workDir, ".mcp.json"), "utf-8");
681
+ const claudeAfterFirst = fs_1.default.readFileSync(path_1.default.join(workDir, "CLAUDE.md"), "utf-8");
682
+ const second = yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), options);
683
+ (0, chai_1.expect)(second.mcpPort).to.equal(first.mcpPort);
684
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(workDir, ".mcp.json"), "utf-8")).to.equal(mcpAfterFirst);
685
+ // Notably NOT duplicated: the managed section is replaced in place.
686
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(workDir, "CLAUDE.md"), "utf-8")).to.equal(claudeAfterFirst);
687
+ }));
688
+ it("carries the retrieval settings from this directory's publisher.config.json into the config it serves", () => __awaiter(void 0, void 0, void 0, function* () {
689
+ const retrieval = { llm: { provider: "anthropic", model: "m" } };
690
+ fs_1.default.writeFileSync(path_1.default.join(workDir, "publisher.config.json"), JSON.stringify({ retrieval }));
691
+ const host = fakeHost();
692
+ const wired = yield (0, flow_1.wireDirectory)(context(host, loggedInStore()), {
693
+ directory: workDir,
694
+ organization: "acme",
695
+ credibleEnvironment: "prod-bars",
696
+ start: false,
697
+ open: false,
698
+ });
699
+ (0, chai_1.expect)(wired.config.retrieval).to.eql(retrieval);
700
+ // A file holding only retrieval settings is not a package listing:
701
+ // the directory is still the package it serves.
702
+ (0, chai_1.expect)(wired.packageDir).to.equal(workDir);
703
+ (0, chai_1.expect)(host.logs).to.include("info: Using the retrieval settings in this directory's publisher.config.json.");
704
+ }));
705
+ it("serves without a retrieval that is not an object, and says why", () => __awaiter(void 0, void 0, void 0, function* () {
706
+ fs_1.default.writeFileSync(path_1.default.join(workDir, "publisher.config.json"), JSON.stringify({ retrieval: "anthropic" }));
707
+ const host = fakeHost();
708
+ const wired = yield (0, flow_1.wireDirectory)(context(host, loggedInStore()), {
709
+ directory: workDir,
710
+ organization: "acme",
711
+ credibleEnvironment: "prod-bars",
712
+ start: false,
713
+ open: false,
714
+ });
715
+ (0, chai_1.expect)(wired.config).to.not.have.property("retrieval");
716
+ (0, chai_1.expect)(host.logs.some((line) => line.startsWith("warn: Invalid retrieval in " +
717
+ path_1.default.join(workDir, "publisher.config.json") +
718
+ ': expected an object, got "anthropic".')), host.logs.join("\n")).to.equal(true);
719
+ }));
720
+ });
721
+ describe("briefing", () => {
722
+ const facts = {
723
+ organization: "acme",
724
+ credibleEnvironment: "prod-bars",
725
+ localEnvironment: "default",
726
+ connectionNames: ["warehouse"],
727
+ publisherUrl: "http://localhost:4000",
728
+ };
729
+ // The local publisher serves under its own environment name. An agent
730
+ // that only saw `prod-bars` passed it to the local tools and got
731
+ // "Resource not found: prod-bars/...".
732
+ it("names the environment each server takes", () => {
733
+ const text = (0, briefing_1.renderBriefing)(facts);
734
+ (0, chai_1.expect)(text).to.contain("- Credible environment: `prod-bars` (for the `credible` server)");
735
+ (0, chai_1.expect)(text).to.contain("- Local environment: `default` (for the `malloy` server)");
736
+ });
737
+ // In the "I already run a publisher" journey its config, with the key in
738
+ // it, is still here; "no credentials live on this machine" was false.
739
+ it("says a local publisher.config.json can hold credentials, only when there is one", () => {
740
+ // Credentials entered in Credible: none on this machine, said plainly.
741
+ (0, chai_1.expect)((0, briefing_1.renderBriefing)(facts)).to.not.contain("publisher.config.json");
742
+ (0, chai_1.expect)((0, briefing_1.renderBriefing)(facts)).to.contain("no warehouse credentials live on this machine");
743
+ const withLocal = (0, briefing_1.renderBriefing)(Object.assign(Object.assign({}, facts), { localConfigHasConnections: true }));
744
+ (0, chai_1.expect)(withLocal).to.contain("`publisher.config.json` in this directory is the config your own Publisher");
745
+ (0, chai_1.expect)(withLocal).to.contain("the credentials do not need to stay in it, if you no longer run that Publisher.");
746
+ (0, chai_1.expect)(withLocal).to.contain("`cred publish` never uploads it");
747
+ (0, chai_1.expect)(withLocal).to.not.contain("no warehouse credentials live");
748
+ });
749
+ it("says the remote server signs in separately from cred login", () => {
750
+ (0, chai_1.expect)((0, briefing_1.renderBriefing)(facts)).to.contain("It signs in on its own, separately from `cred login`");
751
+ });
752
+ it("states the local-vs-remote rule, which is the easiest thing to get wrong", () => {
753
+ const text = (0, briefing_1.renderBriefing)(facts);
754
+ (0, chai_1.expect)(text).to.contain("Iterate locally; evaluate against the published");
755
+ });
756
+ it("names the recovery for a refused local tool call", () => {
757
+ (0, chai_1.expect)((0, briefing_1.renderBriefing)(facts)).to.contain("cred dev");
758
+ });
759
+ // An environment with no connections looked like success: the briefing
760
+ // still told the agent queries go to "your real warehouse".
761
+ it("says there is nothing to query, and the fix, when there are no connections", () => {
762
+ const text = (0, briefing_1.renderBriefing)(Object.assign(Object.assign({}, facts), { connectionNames: [] }));
763
+ (0, chai_1.expect)(text).to.contain("- Connections: none yet, so there is nothing to query — add one with `cred add connection`, then restart `cred dev`");
764
+ (0, chai_1.expect)(text).to.not.contain("proxies queries to your real warehouse");
765
+ (0, chai_1.expect)((0, briefing_1.renderBriefing)(facts)).to.contain("proxies queries to your real warehouse");
766
+ });
767
+ it("tells a VS Code workspace to use the command, not the CLI", () => {
768
+ (0, chai_1.expect)((0, briefing_1.renderBriefing)(Object.assign(Object.assign({}, facts), { host: "vscode" }))).to.contain("Credible: Start Publisher");
769
+ });
770
+ it("says that git push publishes nothing when the directory pushes somewhere", () => {
771
+ (0, chai_1.expect)((0, briefing_1.renderBriefing)(Object.assign(Object.assign({}, facts), { gitRemote: "git@github.com:acme/m.git" }))).to.contain("git push");
772
+ });
773
+ // This line is the one an agent acts on, and it used to be decided by the
774
+ // setup prompt's "where did the code come from?" answer. That answer
775
+ // described how setup obtained the code, not whether the result pushes
776
+ // anywhere, so the commonest case got it backwards: a user who cloned
777
+ // their repo by hand and ran setup inside it answered "this directory" and
778
+ // was told the directory "is not wired to push anywhere". These drive the
779
+ // real `wireDirectory` against real directories, because the value now has
780
+ // to be observed from one.
781
+ it("tells a working copy that git push publishes nothing on its own", () => __awaiter(void 0, void 0, void 0, function* () {
782
+ const repo = path_1.default.join(workDir, "repo");
783
+ fs_1.default.mkdirSync(repo, { recursive: true });
784
+ fs_1.default.writeFileSync(path_1.default.join(repo, "model.malloy"), "// a model\n");
785
+ git(["init", "--quiet"], repo);
786
+ git(["remote", "add", "origin", "https://example.invalid/a.git"], repo);
787
+ const wired = yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), {
788
+ directory: repo,
789
+ organization: "acme",
790
+ credibleEnvironment: "prod-bars",
791
+ start: false,
792
+ open: false,
793
+ });
794
+ const briefing = fs_1.default.readFileSync(path_1.default.join(wired.directory, "CLAUDE.md"), "utf-8");
795
+ (0, chai_1.expect)(briefing).to.contain("git push");
796
+ (0, chai_1.expect)(briefing).to.not.contain("not wired to push anywhere");
797
+ }));
798
+ it("tells a directory with no remote that it is not wired to push anywhere", () => __awaiter(void 0, void 0, void 0, function* () {
799
+ // A repository is not enough: `git init` with no remote pushes nowhere,
800
+ // so keying off "is this a git repo" would produce the same wrong
801
+ // sentence the prompt did.
802
+ const local = path_1.default.join(workDir, "local");
803
+ fs_1.default.mkdirSync(local, { recursive: true });
804
+ git(["init", "--quiet"], local);
805
+ const wired = yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), {
806
+ directory: local,
807
+ organization: "acme",
808
+ credibleEnvironment: "prod-bars",
809
+ start: false,
810
+ open: false,
811
+ });
812
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(wired.directory, "CLAUDE.md"), "utf-8")).to.contain("not wired to push anywhere");
813
+ }));
814
+ it("preserves a user's own notes above the managed section", () => {
815
+ fs_1.default.writeFileSync(path_1.default.join(workDir, "CLAUDE.md"), "# My project\n\nDo not delete this.\n");
816
+ (0, briefing_1.writeBriefing)(workDir, facts);
817
+ const text = fs_1.default.readFileSync(path_1.default.join(workDir, "CLAUDE.md"), "utf-8");
818
+ (0, chai_1.expect)(text).to.contain("Do not delete this.");
819
+ (0, chai_1.expect)(text).to.contain("## Credible");
820
+ });
821
+ it("replaces the managed section rather than appending a second one", () => {
822
+ (0, briefing_1.writeBriefing)(workDir, facts);
823
+ (0, briefing_1.writeBriefing)(workDir, Object.assign(Object.assign({}, facts), { credibleEnvironment: "staging-x" }));
824
+ const text = fs_1.default.readFileSync(path_1.default.join(workDir, "CLAUDE.md"), "utf-8");
825
+ (0, chai_1.expect)(text.match(/BEGIN Credible managed section/g)).to.have.length(1);
826
+ (0, chai_1.expect)(text).to.contain("staging-x");
827
+ (0, chai_1.expect)(text).to.not.contain("prod-bars");
828
+ });
829
+ });
830
+ describe("identity resolution", () => {
831
+ // The bug this exists to stop: the pin was written and never read, so two
832
+ // directories pinned to different environments both followed whatever
833
+ // ~/.cred held. Nothing looked wrong until a query hit the wrong warehouse.
834
+ it("prefers the directory pin over the global value", () => {
835
+ const resolved = (0, folderconfig_1.resolveIdentity)({
836
+ folder: { organization: "acme", environment: "env-B" },
837
+ global: { organization: "acme", environment: "env-A" },
838
+ });
839
+ (0, chai_1.expect)(resolved.environment.value).to.equal("env-B");
840
+ (0, chai_1.expect)(resolved.environment.from).to.equal("folder");
841
+ });
842
+ it("prefers a flag over the pin, so a switch is a switch", () => {
843
+ const resolved = (0, folderconfig_1.resolveIdentity)({
844
+ flag: { environment: "staging" },
845
+ folder: { organization: "acme", environment: "env-B" },
846
+ global: { organization: "acme", environment: "env-A" },
847
+ });
848
+ (0, chai_1.expect)(resolved.environment.value).to.equal("staging");
849
+ (0, chai_1.expect)(resolved.environment.from).to.equal("flag");
850
+ // Resolved per field: the flag named only an environment, so the
851
+ // organization still comes from the pin.
852
+ (0, chai_1.expect)(resolved.organization.value).to.equal("acme");
853
+ (0, chai_1.expect)(resolved.organization.from).to.equal("folder");
854
+ });
855
+ it("falls back to the global value when nothing else says", () => {
856
+ const resolved = (0, folderconfig_1.resolveIdentity)({
857
+ global: { organization: "acme", environment: "env-A" },
858
+ });
859
+ (0, chai_1.expect)(resolved.environment.from).to.equal("global");
860
+ });
861
+ });
862
+ describe("keeping the pin out of commits", () => {
863
+ it("adds .credible/ to .gitignore in a repository", () => __awaiter(void 0, void 0, void 0, function* () {
864
+ git(["init", "--quiet"], workDir);
865
+ yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), {
866
+ directory: workDir,
867
+ organization: "acme",
868
+ credibleEnvironment: "prod-bars",
869
+ start: false,
870
+ open: false,
871
+ });
872
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(workDir, ".gitignore"), "utf-8")).to.contain(".credible/");
873
+ }));
874
+ it("appends rather than replacing, because the file is the user's", () => __awaiter(void 0, void 0, void 0, function* () {
875
+ git(["init", "--quiet"], workDir);
876
+ fs_1.default.writeFileSync(path_1.default.join(workDir, ".gitignore"), "node_modules\n");
877
+ yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), {
878
+ directory: workDir,
879
+ organization: "acme",
880
+ credibleEnvironment: "prod-bars",
881
+ start: false,
882
+ open: false,
883
+ });
884
+ const text = fs_1.default.readFileSync(path_1.default.join(workDir, ".gitignore"), "utf-8");
885
+ (0, chai_1.expect)(text).to.contain("node_modules");
886
+ (0, chai_1.expect)(text).to.contain(".credible/");
887
+ }));
888
+ it("writes nothing when git already ignores it", () => __awaiter(void 0, void 0, void 0, function* () {
889
+ // A rule inherited from a parent .gitignore, .git/info/exclude, or the
890
+ // user's global excludes counts. Re-adding would be a duplicate rule in
891
+ // a file we do not own.
892
+ git(["init", "--quiet"], workDir);
893
+ fs_1.default.writeFileSync(path_1.default.join(workDir, ".gitignore"), ".credible/\n");
894
+ yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), {
895
+ directory: workDir,
896
+ organization: "acme",
897
+ credibleEnvironment: "prod-bars",
898
+ start: false,
899
+ open: false,
900
+ });
901
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(workDir, ".gitignore"), "utf-8")).to.equal(".credible/\n");
902
+ }));
903
+ it("does not invent a .gitignore outside a repository", () => __awaiter(void 0, void 0, void 0, function* () {
904
+ yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), {
905
+ directory: workDir,
906
+ organization: "acme",
907
+ credibleEnvironment: "prod-bars",
908
+ start: false,
909
+ open: false,
910
+ });
911
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".gitignore"))).to.equal(false);
912
+ }));
913
+ });
914
+ describe("folder config", () => {
915
+ it("walks up from a subdirectory, like .nvmrc", () => {
916
+ var _a;
917
+ (0, folderconfig_1.writeFolderConfig)(workDir, { organization: "acme" });
918
+ const nested = path_1.default.join(workDir, "a", "b");
919
+ fs_1.default.mkdirSync(nested, { recursive: true });
920
+ (0, chai_1.expect)((_a = (0, folderconfig_1.findFolderConfig)(nested)) === null || _a === void 0 ? void 0 : _a.root).to.equal(workDir);
921
+ });
922
+ it("merges rather than replacing, so a pin keeps its other fields", () => {
923
+ (0, folderconfig_1.writeFolderConfig)(workDir, { organization: "acme" });
924
+ (0, folderconfig_1.writeFolderConfig)(workDir, { ports: { uiPort: 5000, mcpPort: 5040 } });
925
+ const config = (0, folderconfig_1.findFolderConfig)(workDir).config;
926
+ (0, chai_1.expect)(config.organization).to.equal("acme");
927
+ (0, chai_1.expect)(config.ports).to.eql({ uiPort: 5000, mcpPort: 5040 });
928
+ });
929
+ it("ignores an unreadable pin instead of failing the command", () => {
930
+ fs_1.default.mkdirSync(path_1.default.join(workDir, ".credible"), { recursive: true });
931
+ fs_1.default.writeFileSync(path_1.default.join(workDir, ".credible", "config.json"), "{ not json");
932
+ (0, chai_1.expect)((0, folderconfig_1.findFolderConfig)(workDir)).to.equal(undefined);
933
+ });
934
+ describe("resolveDeployment precedence", () => {
935
+ it("puts the flag above the global default", () => {
936
+ (0, chai_1.expect)((0, folderconfig_1.resolveDeployment)({
937
+ cwd: workDir,
938
+ globalDefault: "production",
939
+ flag: "staging",
940
+ })).to.eql({ deployment: "staging", from: "flag" });
941
+ });
942
+ it("puts the flag above the folder pin", () => {
943
+ (0, folderconfig_1.writeFolderConfig)(workDir, { deployment: "dev" });
944
+ (0, chai_1.expect)((0, folderconfig_1.resolveDeployment)({
945
+ cwd: workDir,
946
+ globalDefault: "production",
947
+ flag: "staging",
948
+ })).to.eql({ deployment: "staging", from: "flag" });
949
+ });
950
+ it("puts the folder pin above the global default", () => {
951
+ (0, folderconfig_1.writeFolderConfig)(workDir, { deployment: "staging" });
952
+ (0, chai_1.expect)((0, folderconfig_1.resolveDeployment)({ cwd: workDir, globalDefault: "production" })).to.eql({ deployment: "staging", from: "folder" });
953
+ });
954
+ it("falls back to the global default with no pin", () => {
955
+ (0, chai_1.expect)((0, folderconfig_1.resolveDeployment)({ cwd: workDir, globalDefault: "production" })).to.eql({ deployment: "production", from: "global" });
956
+ });
957
+ // Earlier builds wrote no `deployment` for production. Following the
958
+ // global value there paired a production organization with staging's
959
+ // credentials once `~/.cred` moved.
960
+ it("reads an initialized pin with no deployment as production", () => {
961
+ (0, folderconfig_1.writeFolderConfig)(workDir, {
962
+ organization: "acme",
963
+ environment: "prod-bars",
964
+ });
965
+ (0, chai_1.expect)((0, folderconfig_1.resolveDeployment)({ cwd: workDir, globalDefault: "staging" })).to.eql({ deployment: "production", from: "folder" });
966
+ });
967
+ it("still falls back to the global default for a deployment it does not know", () => {
968
+ fs_1.default.mkdirSync(path_1.default.join(workDir, ".credible"), { recursive: true });
969
+ fs_1.default.writeFileSync(path_1.default.join(workDir, ".credible", "config.json"), JSON.stringify({ deployment: "mars", organization: "acme" }));
970
+ (0, chai_1.expect)((0, folderconfig_1.resolveDeployment)({ cwd: workDir, globalDefault: "staging" })).to.eql({ deployment: "staging", from: "global" });
971
+ });
972
+ it("does not let an invalid folder pin drive the deployment", () => {
973
+ fs_1.default.mkdirSync(path_1.default.join(workDir, ".credible"), { recursive: true });
974
+ fs_1.default.writeFileSync(path_1.default.join(workDir, ".credible", "config.json"), JSON.stringify({ deployment: "nonsense" }));
975
+ (0, chai_1.expect)((0, folderconfig_1.resolveDeployment)({ cwd: workDir, globalDefault: "production" })).to.eql({ deployment: "production", from: "global" });
976
+ });
977
+ });
978
+ describe("reading a config written by another version", () => {
979
+ it("keeps a key it does not understand when writing", () => {
980
+ const filePath = path_1.default.join(workDir, ".credible", "config.json");
981
+ fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
982
+ fs_1.default.writeFileSync(filePath, JSON.stringify({ somethingNewer: { a: 1 } }));
983
+ (0, folderconfig_1.writeFolderConfig)(workDir, { organization: "acme" });
984
+ const onDisk = JSON.parse(fs_1.default.readFileSync(filePath, "utf-8"));
985
+ (0, chai_1.expect)(onDisk.organization).to.equal("acme");
986
+ (0, chai_1.expect)(onDisk.somethingNewer).to.eql({ a: 1 });
987
+ });
988
+ it("drops a ports value of the wrong shape rather than passing it on", () => {
989
+ const filePath = path_1.default.join(workDir, ".credible", "config.json");
990
+ fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
991
+ fs_1.default.writeFileSync(filePath, JSON.stringify({ ports: "4000/4040", organization: "acme" }));
992
+ const found = (0, folderconfig_1.findFolderConfig)(workDir);
993
+ (0, chai_1.expect)(found === null || found === void 0 ? void 0 : found.config.ports).to.equal(undefined);
994
+ // The fields that ARE valid still come through.
995
+ (0, chai_1.expect)(found === null || found === void 0 ? void 0 : found.config.organization).to.equal("acme");
996
+ });
997
+ it("drops a non-integer port pair", () => {
998
+ var _a;
999
+ const filePath = path_1.default.join(workDir, ".credible", "config.json");
1000
+ fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
1001
+ fs_1.default.writeFileSync(filePath, JSON.stringify({ ports: { uiPort: 4000, mcpPort: "4040" } }));
1002
+ (0, chai_1.expect)((_a = (0, folderconfig_1.findFolderConfig)(workDir)) === null || _a === void 0 ? void 0 : _a.config.ports).to.equal(undefined);
1003
+ });
1004
+ });
1005
+ });
1006
+ describe("telling an already-running agent to restart", () => {
1007
+ const wire = (host) => (0, flow_1.wireDirectory)(context(host, loggedInStore()), {
1008
+ directory: workDir,
1009
+ organization: "acme",
1010
+ credibleEnvironment: "prod-bars",
1011
+ start: false,
1012
+ open: false,
1013
+ });
1014
+ it("says so when .mcp.json changed", () => __awaiter(void 0, void 0, void 0, function* () {
1015
+ const host = fakeHost();
1016
+ yield wire(host);
1017
+ (0, chai_1.expect)(host.logs.join("\n")).to.contain("will not see the new MCP servers until it restarts");
1018
+ }));
1019
+ it("stays quiet when a later run rewrote nothing", () => __awaiter(void 0, void 0, void 0, function* () {
1020
+ yield wire(fakeHost());
1021
+ const host = fakeHost();
1022
+ yield wire(host);
1023
+ (0, chai_1.expect)(host.logs.join("\n")).to.not.contain("until it restarts");
1024
+ }));
1025
+ });
1026
+ describe("a Publisher server directory (create-malloy-package layout)", () => {
1027
+ function scaffold(...packages) {
1028
+ for (const name of packages) {
1029
+ fs_1.default.mkdirSync(path_1.default.join(workDir, name), { recursive: true });
1030
+ fs_1.default.writeFileSync(path_1.default.join(workDir, name, "publisher.json"), JSON.stringify({ name }));
1031
+ fs_1.default.writeFileSync(path_1.default.join(workDir, name, `${name}.malloy`), "");
1032
+ }
1033
+ fs_1.default.writeFileSync(path_1.default.join(workDir, "publisher.config.json"), JSON.stringify({
1034
+ environments: [
1035
+ {
1036
+ name: "default",
1037
+ packages: packages.map((name) => ({
1038
+ name,
1039
+ location: `./${name}`,
1040
+ })),
1041
+ connections: [],
1042
+ },
1043
+ ],
1044
+ }));
1045
+ }
1046
+ const wire = (host) => (0, flow_1.wireDirectory)(context(host, loggedInStore()), {
1047
+ directory: workDir,
1048
+ organization: "acme",
1049
+ credibleEnvironment: "prod-bars",
1050
+ start: false,
1051
+ open: false,
1052
+ });
1053
+ it("serves the package the config lists, and keeps the workspace files at the top", () => __awaiter(void 0, void 0, void 0, function* () {
1054
+ scaffold("shop");
1055
+ const host = fakeHost();
1056
+ const wired = yield wire(host);
1057
+ (0, chai_1.expect)(wired.directory).to.equal(workDir);
1058
+ (0, chai_1.expect)(wired.packageDir).to.equal(path_1.default.join(workDir, "shop"));
1059
+ (0, chai_1.expect)(wired.config.environments[0].packages[0]).to.eql({
1060
+ name: "shop",
1061
+ location: path_1.default.join(workDir, "shop"),
1062
+ });
1063
+ // No second manifest at the top: the top is the workspace, not a package.
1064
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, "publisher.json"))).to.equal(false);
1065
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".mcp.json"))).to.equal(true);
1066
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(workDir, "CLAUDE.md"), "utf-8")).to.contain("`shop/`");
1067
+ }));
1068
+ it("warns once when the package's own manifest has no version, and leaves it alone", () => __awaiter(void 0, void 0, void 0, function* () {
1069
+ scaffold("shop"); // the scaffolder writes { "name": "shop" } only
1070
+ const host = fakeHost();
1071
+ yield wire(host);
1072
+ (0, chai_1.expect)(host.logs.join("\n")).to.contain('shop/publisher.json has no "version"');
1073
+ (0, chai_1.expect)(JSON.parse(fs_1.default.readFileSync(path_1.default.join(workDir, "shop/publisher.json"), "utf-8"))).to.eql({ name: "shop" });
1074
+ }));
1075
+ it("asks which package when the config lists several, naming --package as the flag", () => __awaiter(void 0, void 0, void 0, function* () {
1076
+ scaffold("shop", "warehouse");
1077
+ const hints = [];
1078
+ const host = Object.assign(Object.assign({}, fakeHost()), { select(_message, choices, hint) {
1079
+ return __awaiter(this, void 0, void 0, function* () {
1080
+ hints.push(hint);
1081
+ return choices[1].value;
1082
+ });
1083
+ } });
1084
+ const wired = yield wire(host);
1085
+ (0, chai_1.expect)(wired.packageDir).to.equal(path_1.default.join(workDir, "warehouse"));
1086
+ (0, chai_1.expect)(hints).to.eql([
1087
+ { flag: "--package <dir>", options: ["shop", "warehouse"] },
1088
+ ]);
1089
+ }));
1090
+ // Asked after login and the environment, an agent that stopped at the
1091
+ // exit 2 had already written ~/.cred and maybe created an environment.
1092
+ it("refuses the package question up front without a terminal, and a --package it does not list", () => {
1093
+ scaffold("shop", "warehouse");
1094
+ (0, chai_1.expect)(() => (0, flow_1.refuseUnanswerable)(workDir, {}, false))
1095
+ .to.throw(host_1.NeedsInputError)
1096
+ .with.property("message")
1097
+ .that.contains("Pass --package <dir>. Options: shop, warehouse.");
1098
+ (0, chai_1.expect)(() => (0, flow_1.refuseUnanswerable)(workDir, { package: "shop" }, false)).not.to.throw();
1099
+ (0, chai_1.expect)(() => (0, flow_1.refuseUnanswerable)(workDir, { package: "shp" }, true))
1100
+ .to.throw(host_1.SetupError)
1101
+ .with.property("message")
1102
+ .that.contains("--package shp is not a package");
1103
+ });
1104
+ // The non-interactive answer to that question has to do what the picker
1105
+ // does: serve the subdirectory and keep the workspace files at the top.
1106
+ // `-d` used to be the named answer, and it moves the whole workspace.
1107
+ it("--package serves the named package, keeps the workspace at the top, and is remembered", () => __awaiter(void 0, void 0, void 0, function* () {
1108
+ scaffold("shop", "warehouse");
1109
+ const wired = yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), {
1110
+ directory: workDir,
1111
+ organization: "acme",
1112
+ credibleEnvironment: "prod-bars",
1113
+ start: false,
1114
+ open: false,
1115
+ package: "warehouse",
1116
+ });
1117
+ (0, chai_1.expect)(wired.directory).to.equal(workDir);
1118
+ (0, chai_1.expect)(wired.packageDir).to.equal(path_1.default.join(workDir, "warehouse"));
1119
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".mcp.json"))).to.equal(true);
1120
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, "warehouse", ".mcp.json"))).to.equal(false);
1121
+ (0, chai_1.expect)((0, folderconfig_1.findFolderConfig)(workDir).config.package).to.equal("warehouse");
1122
+ // `cred dev` on a later day: no flag, no question.
1123
+ const later = fakeHost();
1124
+ const rewired = yield wire(later);
1125
+ (0, chai_1.expect)(rewired.packageDir).to.equal(path_1.default.join(workDir, "warehouse"));
1126
+ (0, chai_1.expect)(later.asked.some((q) => q.includes("several packages"))).to.equal(false);
1127
+ }));
1128
+ it("refuses a --package the config does not list, naming the ones it does", () => __awaiter(void 0, void 0, void 0, function* () {
1129
+ scaffold("shop", "warehouse");
1130
+ try {
1131
+ yield (0, flow_1.wireDirectory)(context(fakeHost(), loggedInStore()), {
1132
+ directory: workDir,
1133
+ organization: "acme",
1134
+ credibleEnvironment: "prod-bars",
1135
+ start: false,
1136
+ open: false,
1137
+ package: "shp",
1138
+ });
1139
+ chai_1.expect.fail("expected a refusal");
1140
+ }
1141
+ catch (error) {
1142
+ (0, chai_1.expect)(error).to.be.instanceOf(host_1.SetupError);
1143
+ (0, chai_1.expect)(error.message).to.equal("--package shp is not a package this directory's publisher.config.json lists. Fix: pass one of: shop, warehouse.");
1144
+ }
1145
+ }));
1146
+ it("a directory with its own manifest is the package, whatever the config says", () => __awaiter(void 0, void 0, void 0, function* () {
1147
+ scaffold("shop");
1148
+ fs_1.default.writeFileSync(path_1.default.join(workDir, "publisher.json"), JSON.stringify({ name: "top", version: "1.0.0" }));
1149
+ const wired = yield wire(fakeHost());
1150
+ (0, chai_1.expect)(wired.packageDir).to.equal(workDir);
1151
+ }));
1152
+ });
1153
+ describe("the skills step", () => {
1154
+ const ASK_SKILLS = "Found 1 skill already in .claude/skills/ that cred did not install (malloy-getting-started). What should happen to them?";
1155
+ const skills = {
1156
+ manifest: "modeling-ide",
1157
+ version: "v1",
1158
+ trigger_hint: "When modeling",
1159
+ auto_discovered: [
1160
+ {
1161
+ name: "malloy-modeling",
1162
+ files: [{ relative_filepath: "SKILL.md", file_contents: "m" }],
1163
+ },
1164
+ ],
1165
+ supporting: [],
1166
+ };
1167
+ const wire = (host, fetchSkills) => (0, flow_1.wireDirectory)(Object.assign(Object.assign({}, context(host, loggedInStore())), { fetchSkills }), {
1168
+ directory: workDir,
1169
+ organization: "acme",
1170
+ credibleEnvironment: "prod-bars",
1171
+ start: false,
1172
+ open: false,
1173
+ });
1174
+ it("installs the skills, records them in the pin, and tells the briefing", () => __awaiter(void 0, void 0, void 0, function* () {
1175
+ const host = fakeHost();
1176
+ yield wire(host, () => __awaiter(void 0, void 0, void 0, function* () { return skills; }));
1177
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".claude/skills/malloy-modeling/SKILL.md"))).to.equal(true);
1178
+ (0, chai_1.expect)((0, folderconfig_1.findFolderConfig)(workDir).config.skills).to.deep.include({
1179
+ manifest: "modeling-ide",
1180
+ version: "v1",
1181
+ });
1182
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(workDir, "CLAUDE.md"), "utf-8")).to.contain(".claude/skills/");
1183
+ }));
1184
+ it("records the malloy skills version in the pin, and reads it back", () => __awaiter(void 0, void 0, void 0, function* () {
1185
+ yield wire(fakeHost(), () => __awaiter(void 0, void 0, void 0, function* () {
1186
+ return (Object.assign(Object.assign({}, skills), { publisher_skills_version: "0.1.30" }));
1187
+ }));
1188
+ (0, chai_1.expect)((0, folderconfig_1.findFolderConfig)(workDir).config.skills).to.deep.include({
1189
+ version: "v1",
1190
+ publisherVersion: "0.1.30",
1191
+ });
1192
+ }));
1193
+ it("adds the skill directories to .gitignore in a repository", () => __awaiter(void 0, void 0, void 0, function* () {
1194
+ git(["init", "-q"], workDir);
1195
+ yield wire(fakeHost(), () => __awaiter(void 0, void 0, void 0, function* () { return skills; }));
1196
+ const text = fs_1.default.readFileSync(path_1.default.join(workDir, ".gitignore"), "utf-8");
1197
+ (0, chai_1.expect)(text).to.contain(".claude/skills/malloy-modeling/");
1198
+ (0, chai_1.expect)(text).to.contain(".agents/skills/malloy-modeling/");
1199
+ (0, chai_1.expect)(text).to.contain(".claude/rules/credible-modeling.md");
1200
+ }));
1201
+ it("skips quietly when the server has no skills endpoint", () => __awaiter(void 0, void 0, void 0, function* () {
1202
+ const host = fakeHost();
1203
+ yield wire(host, () => __awaiter(void 0, void 0, void 0, function* () { return undefined; }));
1204
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".claude"))).to.equal(false);
1205
+ (0, chai_1.expect)((0, folderconfig_1.findFolderConfig)(workDir).config.skills).to.equal(undefined);
1206
+ (0, chai_1.expect)(host.logs.join("\n")).to.not.contain("skills");
1207
+ }));
1208
+ it("warns and continues when the fetch fails, keeping the previous record", () => __awaiter(void 0, void 0, void 0, function* () {
1209
+ var _a;
1210
+ yield wire(fakeHost(), () => __awaiter(void 0, void 0, void 0, function* () { return skills; }));
1211
+ const host = fakeHost();
1212
+ yield wire(host, () => __awaiter(void 0, void 0, void 0, function* () {
1213
+ throw new Error("code-assist is down");
1214
+ }));
1215
+ (0, chai_1.expect)(host.logs.join("\n")).to.contain("code-assist is down");
1216
+ // Setup still completed, and the earlier install is still recorded.
1217
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".mcp.json"))).to.equal(true);
1218
+ (0, chai_1.expect)((_a = (0, folderconfig_1.findFolderConfig)(workDir).config.skills) === null || _a === void 0 ? void 0 : _a.version).to.equal("v1");
1219
+ }));
1220
+ /** The scaffolder's leftovers: a skill we did not install. */
1221
+ function plantForeign(name = "malloy-getting-started") {
1222
+ const dir = path_1.default.join(workDir, ".claude/skills", name);
1223
+ fs_1.default.mkdirSync(dir, { recursive: true });
1224
+ fs_1.default.writeFileSync(path_1.default.join(dir, "SKILL.md"), "theirs");
1225
+ }
1226
+ const wireWith = (host, skillsPolicy) => (0, flow_1.wireDirectory)(Object.assign(Object.assign({}, context(host, loggedInStore())), { fetchSkills: () => __awaiter(void 0, void 0, void 0, function* () { return skills; }) }), {
1227
+ directory: workDir,
1228
+ organization: "acme",
1229
+ credibleEnvironment: "prod-bars",
1230
+ start: false,
1231
+ open: false,
1232
+ skills: skillsPolicy,
1233
+ });
1234
+ it("asks what to do with skills it did not install, naming the flag", () => __awaiter(void 0, void 0, void 0, function* () {
1235
+ plantForeign();
1236
+ const host = fakeHost({ [ASK_SKILLS]: "keep" });
1237
+ yield wireWith(host);
1238
+ const question = host.asked.find((q) => q.includes("did not install"));
1239
+ (0, chai_1.expect)(question).to.contain("malloy-getting-started");
1240
+ }));
1241
+ it("replace moves them aside, not away, and installs ours", () => __awaiter(void 0, void 0, void 0, function* () {
1242
+ plantForeign();
1243
+ const host = fakeHost();
1244
+ yield wireWith(host, "replace");
1245
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".claude/skills/malloy-getting-started"))).to.equal(false);
1246
+ const moved = fs_1.default.readdirSync(path_1.default.join(workDir, ".credible/replaced-skills"));
1247
+ (0, chai_1.expect)(moved).to.have.length(1);
1248
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(workDir, ".credible/replaced-skills", moved[0], ".claude/skills/malloy-getting-started/SKILL.md"), "utf-8")).to.equal("theirs");
1249
+ (0, chai_1.expect)(host.logs.join("\n")).to.contain(`Moved 1 skill that cred did not install to .credible/replaced-skills/${moved[0]}/`);
1250
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".claude/skills/malloy-modeling/SKILL.md"))).to.equal(true);
1251
+ }));
1252
+ it("keep leaves them and installs ours beside them", () => __awaiter(void 0, void 0, void 0, function* () {
1253
+ plantForeign();
1254
+ yield wireWith(fakeHost(), "keep");
1255
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(workDir, ".claude/skills/malloy-getting-started/SKILL.md"), "utf-8")).to.equal("theirs");
1256
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".claude/skills/malloy-modeling/SKILL.md"))).to.equal(true);
1257
+ }));
1258
+ it("keep moves aside one with the same name as ours, rather than overwriting it", () => __awaiter(void 0, void 0, void 0, function* () {
1259
+ plantForeign();
1260
+ plantForeign("malloy-modeling");
1261
+ const host = fakeHost();
1262
+ yield wireWith(host, "keep");
1263
+ // The other one stays where it was.
1264
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(workDir, ".claude/skills/malloy-getting-started/SKILL.md"), "utf-8")).to.equal("theirs");
1265
+ // Ours is installed under the shared name.
1266
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(workDir, ".claude/skills/malloy-modeling/SKILL.md"), "utf-8")).to.equal("m");
1267
+ // And theirs is kept, aside, not overwritten.
1268
+ const moved = fs_1.default.readdirSync(path_1.default.join(workDir, ".credible/replaced-skills"));
1269
+ (0, chai_1.expect)(moved).to.have.length(1);
1270
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(workDir, ".credible/replaced-skills", moved[0], ".claude/skills/malloy-modeling/SKILL.md"), "utf-8")).to.equal("theirs");
1271
+ (0, chai_1.expect)(host.logs.join("\n")).to.contain(`Kept your existing skills. malloy-modeling had the same name as Credible's, so yours was moved to .credible/replaced-skills/${moved[0]}/.`);
1272
+ }));
1273
+ it("keep moves aside one with the same name as a supporting skill of ours", () => __awaiter(void 0, void 0, void 0, function* () {
1274
+ // writeSkillFiles writes the supporting skills too, so they collide the same way.
1275
+ plantForeign("malloy-queries");
1276
+ const host = fakeHost();
1277
+ yield (0, flow_1.wireDirectory)(Object.assign(Object.assign({}, context(host, loggedInStore())), { fetchSkills: () => __awaiter(void 0, void 0, void 0, function* () {
1278
+ return (Object.assign(Object.assign({}, skills), { supporting: [
1279
+ {
1280
+ name: "malloy-queries",
1281
+ files: [
1282
+ {
1283
+ relative_filepath: "SKILL.md",
1284
+ file_contents: "q",
1285
+ },
1286
+ ],
1287
+ },
1288
+ ] }));
1289
+ }) }), {
1290
+ directory: workDir,
1291
+ organization: "acme",
1292
+ credibleEnvironment: "prod-bars",
1293
+ start: false,
1294
+ open: false,
1295
+ skills: "keep",
1296
+ });
1297
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(workDir, ".claude/skills/malloy-queries/SKILL.md"), "utf-8")).to.equal("q");
1298
+ const moved = fs_1.default.readdirSync(path_1.default.join(workDir, ".credible/replaced-skills"));
1299
+ (0, chai_1.expect)(moved).to.have.length(1);
1300
+ (0, chai_1.expect)(fs_1.default.readFileSync(path_1.default.join(workDir, ".credible/replaced-skills", moved[0], ".claude/skills/malloy-queries/SKILL.md"), "utf-8")).to.equal("theirs");
1301
+ (0, chai_1.expect)(host.logs.join("\n")).to.contain("Kept your existing skills. malloy-queries had the same name as Credible's, so yours was moved to");
1302
+ }));
1303
+ it("none installs nothing and touches nothing", () => __awaiter(void 0, void 0, void 0, function* () {
1304
+ plantForeign();
1305
+ yield wireWith(fakeHost(), "none");
1306
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".claude/skills/malloy-modeling"))).to.equal(false);
1307
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".claude/skills/malloy-getting-started"))).to.equal(true);
1308
+ }));
1309
+ // Recorded as a decision in the pin, apart from the installed-files
1310
+ // record. Without it the next `cred dev` asked again (exit 2 without a
1311
+ // terminal) or, with no foreign skills, installed the declined set.
1312
+ it("remembers --skills none, so a later run neither asks nor installs", () => __awaiter(void 0, void 0, void 0, function* () {
1313
+ plantForeign();
1314
+ yield wireWith(fakeHost(), "none");
1315
+ (0, chai_1.expect)((0, folderconfig_1.findFolderConfig)(workDir).config.skillsPolicy).to.equal("none");
1316
+ const host = fakeHost();
1317
+ yield wireWith(host);
1318
+ (0, chai_1.expect)(host.asked.some((q) => q.includes("did not install"))).to.equal(false);
1319
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".claude/skills/malloy-modeling"))).to.equal(false);
1320
+ (0, chai_1.expect)(() => (0, flow_1.refuseUnanswerable)(workDir, {}, false)).not.to.throw();
1321
+ }));
1322
+ it("remembers the picker's 'Do not install skills' the same way", () => __awaiter(void 0, void 0, void 0, function* () {
1323
+ plantForeign();
1324
+ yield wireWith(fakeHost({ [ASK_SKILLS]: "none" }));
1325
+ (0, chai_1.expect)((0, folderconfig_1.findFolderConfig)(workDir).config.skillsPolicy).to.equal("none");
1326
+ }));
1327
+ it("installs, and forgets the refusal, when a later run passes keep", () => __awaiter(void 0, void 0, void 0, function* () {
1328
+ plantForeign();
1329
+ yield wireWith(fakeHost(), "none");
1330
+ yield wireWith(fakeHost(), "keep");
1331
+ (0, chai_1.expect)((0, folderconfig_1.findFolderConfig)(workDir).config.skillsPolicy).to.equal(undefined);
1332
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".claude/skills/malloy-modeling/SKILL.md"))).to.equal(true);
1333
+ }));
1334
+ it("does not ask on a later run — the user's own skills are not ours to question", () => __awaiter(void 0, void 0, void 0, function* () {
1335
+ yield wireWith(fakeHost());
1336
+ plantForeign("my-own");
1337
+ const host = fakeHost();
1338
+ yield wireWith(host);
1339
+ (0, chai_1.expect)(host.asked.some((q) => q.includes("did not install"))).to.equal(false);
1340
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".claude/skills/my-own/SKILL.md"))).to.equal(true);
1341
+ }));
1342
+ it("does nothing when the front door cannot fetch skills", () => __awaiter(void 0, void 0, void 0, function* () {
1343
+ yield wire(fakeHost(), undefined);
1344
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(workDir, ".claude"))).to.equal(false);
1345
+ }));
1346
+ });
1347
+ });
1348
+ //# sourceMappingURL=flow.spec.js.map