t3code-cli 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/README.md +75 -11
  2. package/dist/application.js +2 -2
  3. package/dist/auth.js +1 -1
  4. package/dist/bin.js +86754 -2675
  5. package/dist/cli.js +15 -2
  6. package/dist/config.js +120 -2
  7. package/dist/connection.js +2 -12
  8. package/dist/index.js +1 -1
  9. package/dist/node.js +6 -0
  10. package/dist/orchestration.js +2 -2
  11. package/dist/rpc.js +2 -2
  12. package/dist/runtime.js +2 -2
  13. package/dist/shared.js +43850 -38040
  14. package/dist/src/application/actions.d.ts +25 -0
  15. package/dist/src/application/index.d.ts +3 -2
  16. package/dist/src/application/layer.d.ts +99 -842
  17. package/dist/src/application/model-selection.d.ts +17 -2
  18. package/dist/src/application/models.d.ts +1 -87
  19. package/dist/src/application/project-commands.d.ts +31 -2
  20. package/dist/src/application/projects.d.ts +15 -137
  21. package/dist/src/application/service.d.ts +147 -5
  22. package/dist/src/application/shell-sequence.d.ts +5 -4
  23. package/dist/src/application/terminals.d.ts +66 -0
  24. package/dist/src/application/thread-commands.d.ts +48 -1
  25. package/dist/src/application/thread-update.d.ts +8 -0
  26. package/dist/src/application/thread-wait.d.ts +4 -6
  27. package/dist/src/application/threads.d.ts +36 -14
  28. package/dist/src/auth/error.d.ts +2 -1
  29. package/dist/src/auth/index.d.ts +1 -1
  30. package/dist/src/auth/layer.d.ts +34 -6
  31. package/dist/src/auth/local-base-dir.d.ts +5 -2
  32. package/dist/src/auth/local-origin.d.ts +3 -3
  33. package/dist/src/auth/local-token.d.ts +3 -3
  34. package/dist/src/auth/service.d.ts +15 -3
  35. package/dist/src/auth/type.d.ts +40 -1
  36. package/dist/src/cli/env/flag.d.ts +2 -0
  37. package/dist/src/cli/env/index.d.ts +2 -0
  38. package/dist/src/cli/env/selection-layer.d.ts +3 -0
  39. package/dist/src/cli/flags.d.ts +5 -0
  40. package/dist/src/cli/format/index.d.ts +1 -0
  41. package/dist/src/cli/format/output.d.ts +12 -0
  42. package/dist/src/cli/index.d.ts +4 -0
  43. package/dist/src/cli/runtime/index.d.ts +1 -0
  44. package/dist/src/cli/runtime/service.d.ts +16 -0
  45. package/dist/src/{scope → cli/scope}/resolve.d.ts +5 -4
  46. package/dist/src/config/config.d.ts +49 -0
  47. package/dist/src/config/credential/cipher-node.d.ts +3 -0
  48. package/dist/src/config/credential/cipher-web.d.ts +3 -0
  49. package/dist/src/config/credential/cipher.d.ts +29 -0
  50. package/dist/src/config/credential/env.d.ts +3 -0
  51. package/dist/src/config/credential/error.d.ts +8 -0
  52. package/dist/src/config/credential/index.d.ts +6 -0
  53. package/dist/src/config/credential/service.d.ts +34 -0
  54. package/dist/src/config/env/env.d.ts +55 -0
  55. package/dist/src/config/env/index.d.ts +2 -0
  56. package/dist/src/{layout/base-dir.d.ts → config/env/layout.d.ts} +0 -1
  57. package/dist/src/config/environment-name/index.d.ts +1 -0
  58. package/dist/src/config/environment-name/name.d.ts +10 -0
  59. package/dist/src/config/error.d.ts +4 -11
  60. package/dist/src/config/index.d.ts +10 -5
  61. package/dist/src/config/keystore/error.d.ts +8 -0
  62. package/dist/src/config/keystore/file.d.ts +9 -0
  63. package/dist/src/config/keystore/index.d.ts +4 -0
  64. package/dist/src/config/keystore/keyring-node.d.ts +4 -0
  65. package/dist/src/config/keystore/service.d.ts +33 -0
  66. package/dist/src/config/paths/index.d.ts +1 -0
  67. package/dist/src/config/paths/paths.d.ts +6 -0
  68. package/dist/src/config/persist/file-mode.d.ts +4 -0
  69. package/dist/src/config/persist/migration.d.ts +68 -0
  70. package/dist/src/config/persist/persist.d.ts +40 -0
  71. package/dist/src/config/persist/schema.d.ts +68 -0
  72. package/dist/src/config/resolve/resolve.d.ts +40 -0
  73. package/dist/src/config/selection/index.d.ts +1 -0
  74. package/dist/src/config/selection/resolve.d.ts +4 -0
  75. package/dist/src/config/selection/service.d.ts +10 -0
  76. package/dist/src/config/types.d.ts +36 -0
  77. package/dist/src/config/url/error.d.ts +10 -0
  78. package/dist/src/config/url/index.d.ts +2 -0
  79. package/dist/src/connection/index.d.ts +0 -1
  80. package/dist/src/contracts/index.d.ts +1 -1
  81. package/dist/src/domain/error.d.ts +27 -1
  82. package/dist/src/domain/helpers.d.ts +8 -2
  83. package/dist/src/domain/model-config.d.ts +30 -1
  84. package/dist/src/domain/thread-activities.d.ts +1 -1
  85. package/dist/src/domain/thread-lifecycle.d.ts +3 -2
  86. package/dist/src/index.d.ts +1 -2
  87. package/dist/src/{connection/node.d.ts → node/connection.d.ts} +1 -1
  88. package/dist/src/node/index.d.ts +1 -0
  89. package/dist/src/orchestration/index.d.ts +1 -1
  90. package/dist/src/orchestration/layer.d.ts +120 -829
  91. package/dist/src/orchestration/service.d.ts +2 -1
  92. package/dist/src/rpc/error.d.ts +5 -3
  93. package/dist/src/rpc/index.d.ts +5 -1
  94. package/dist/src/rpc/layer.d.ts +1166 -40
  95. package/dist/src/rpc/operation.d.ts +6280 -0
  96. package/dist/src/rpc/ws-group.d.ts +372 -27
  97. package/dist/src/runtime/index.d.ts +1 -2
  98. package/dist/src/runtime/layer.d.ts +12 -8
  99. package/dist/src/t3tools/index.d.ts +1 -1
  100. package/dist/t3tools.js +2 -2
  101. package/package.json +27 -19
  102. package/src/application/actions.test.ts +328 -0
  103. package/src/application/actions.ts +475 -0
  104. package/src/application/index.ts +32 -2
  105. package/src/application/layer.ts +43 -5
  106. package/src/application/model-selection.ts +70 -4
  107. package/src/application/models.ts +14 -14
  108. package/src/application/project-commands.ts +38 -5
  109. package/src/application/projects.ts +49 -20
  110. package/src/application/service.ts +275 -78
  111. package/src/application/shell-sequence.ts +6 -8
  112. package/src/application/terminals.ts +208 -0
  113. package/src/application/thread-commands.test.ts +43 -0
  114. package/src/application/thread-commands.ts +82 -1
  115. package/src/application/thread-update.ts +72 -0
  116. package/src/application/thread-wait.ts +47 -48
  117. package/src/application/threads.test.ts +172 -0
  118. package/src/application/threads.ts +128 -45
  119. package/src/auth/error.ts +2 -1
  120. package/src/auth/index.ts +7 -0
  121. package/src/auth/layer.test-utils.ts +34 -0
  122. package/src/auth/layer.ts +116 -34
  123. package/src/auth/local-base-dir.ts +13 -6
  124. package/src/auth/local-origin.ts +10 -7
  125. package/src/auth/local-token.ts +28 -23
  126. package/src/auth/service.test.ts +200 -0
  127. package/src/auth/service.ts +27 -3
  128. package/src/auth/transport.ts +2 -2
  129. package/src/auth/type.ts +47 -1
  130. package/src/bin.ts +26 -6
  131. package/src/cli/action-format.ts +110 -0
  132. package/src/cli/action.ts +398 -0
  133. package/src/cli/app.ts +8 -0
  134. package/src/cli/auth.ts +76 -23
  135. package/src/cli/env/flag.ts +8 -0
  136. package/src/cli/env/index.ts +2 -0
  137. package/src/cli/env/selection-layer.test.ts +49 -0
  138. package/src/cli/env/selection-layer.ts +27 -0
  139. package/src/cli/env.ts +107 -0
  140. package/src/cli/error.ts +26 -3
  141. package/src/cli/extra-args.test.ts +32 -0
  142. package/src/cli/extra-args.ts +22 -0
  143. package/src/cli/flags.ts +26 -1
  144. package/src/cli/format/auth.ts +92 -0
  145. package/src/cli/format/human.ts +131 -0
  146. package/src/cli/format/index.ts +12 -0
  147. package/src/cli/format/model.ts +29 -0
  148. package/src/cli/format/output.ts +51 -0
  149. package/src/cli/format/project.ts +32 -0
  150. package/src/cli/format/terminal.ts +87 -0
  151. package/src/cli/format/thread.test.ts +34 -0
  152. package/src/cli/format/thread.ts +185 -0
  153. package/src/cli/index.ts +20 -0
  154. package/src/cli/input/layer.ts +20 -0
  155. package/src/cli/input/service.ts +1 -0
  156. package/src/cli/interaction/confirm.ts +64 -0
  157. package/src/cli/interaction/self-action.ts +36 -0
  158. package/src/cli/message-input.ts +1 -1
  159. package/src/cli/model.ts +9 -5
  160. package/src/cli/project.ts +15 -8
  161. package/src/cli/projects/delete.ts +51 -0
  162. package/src/cli/require.ts +9 -7
  163. package/src/cli/runtime/index.ts +1 -0
  164. package/src/cli/runtime/service.ts +28 -0
  165. package/src/{scope → cli/scope}/resolve.ts +11 -9
  166. package/src/cli/terminal/attach.ts +29 -0
  167. package/src/cli/terminal/create.ts +55 -0
  168. package/src/cli/terminal/destroy.ts +62 -0
  169. package/src/cli/terminal/encoding.test.ts +63 -0
  170. package/src/cli/terminal/encoding.ts +23 -0
  171. package/src/cli/terminal/error.ts +14 -0
  172. package/src/cli/terminal/io-node-layer.ts +82 -0
  173. package/src/cli/terminal/io-service.ts +25 -0
  174. package/src/cli/terminal/list.ts +36 -0
  175. package/src/cli/terminal/read.test-utils.ts +48 -0
  176. package/src/cli/terminal/read.test.ts +35 -0
  177. package/src/cli/terminal/read.ts +99 -0
  178. package/src/cli/terminal/scope.ts +35 -0
  179. package/src/cli/terminal/shared.ts +250 -0
  180. package/src/cli/terminal/stream.ts +61 -0
  181. package/src/cli/terminal/wait.test.ts +148 -0
  182. package/src/cli/terminal/wait.ts +223 -0
  183. package/src/cli/terminal/write.test.ts +34 -0
  184. package/src/cli/terminal/write.ts +133 -0
  185. package/src/cli/terminal.ts +26 -0
  186. package/src/cli/thread.ts +8 -0
  187. package/src/cli/threads/approve.ts +11 -7
  188. package/src/cli/threads/archive.ts +23 -25
  189. package/src/cli/threads/callback.ts +6 -4
  190. package/src/cli/threads/delete.ts +67 -0
  191. package/src/cli/threads/interrupt.ts +57 -0
  192. package/src/cli/threads/list.test.ts +71 -0
  193. package/src/cli/threads/list.ts +25 -13
  194. package/src/cli/threads/messages.ts +11 -7
  195. package/src/cli/threads/respond.ts +11 -7
  196. package/src/cli/threads/send.ts +39 -9
  197. package/src/cli/threads/show.ts +11 -7
  198. package/src/cli/threads/start.ts +18 -13
  199. package/src/cli/threads/unarchive.ts +48 -0
  200. package/src/cli/threads/update.ts +155 -0
  201. package/src/cli/threads/wait.ts +11 -7
  202. package/src/cli/wait-events.ts +1 -1
  203. package/src/config/config.ts +260 -0
  204. package/src/config/credential/cipher-node.ts +40 -0
  205. package/src/config/credential/cipher-web.ts +82 -0
  206. package/src/config/credential/cipher.ts +39 -0
  207. package/src/config/credential/env.ts +13 -0
  208. package/src/config/credential/error.ts +9 -0
  209. package/src/config/credential/index.ts +6 -0
  210. package/src/config/credential/service.test-utils.ts +25 -0
  211. package/src/config/credential/service.test.ts +116 -0
  212. package/src/config/credential/service.ts +205 -0
  213. package/src/config/env/env.test-utils.ts +14 -0
  214. package/src/config/env/env.ts +81 -0
  215. package/src/config/env/index.ts +2 -0
  216. package/src/{layout/base-dir.ts → config/env/layout.ts} +0 -9
  217. package/src/config/environment-name/index.ts +7 -0
  218. package/src/config/environment-name/name.test.ts +66 -0
  219. package/src/config/environment-name/name.ts +49 -0
  220. package/src/config/error.ts +6 -8
  221. package/src/config/index.ts +16 -5
  222. package/src/config/keystore/error.ts +9 -0
  223. package/src/config/keystore/file.ts +96 -0
  224. package/src/config/keystore/index.ts +4 -0
  225. package/src/config/keystore/keyring-node.test.ts +12 -0
  226. package/src/config/keystore/keyring-node.ts +100 -0
  227. package/src/config/keystore/service.test-utils.ts +15 -0
  228. package/src/config/keystore/service.test.ts +18 -0
  229. package/src/config/keystore/service.ts +29 -0
  230. package/src/config/layer.test-utils.ts +45 -0
  231. package/src/config/layer.test.ts +209 -0
  232. package/src/config/paths/index.ts +1 -0
  233. package/src/config/paths/paths.ts +36 -0
  234. package/src/config/persist/file-mode.ts +21 -0
  235. package/src/config/persist/migration.test.ts +71 -0
  236. package/src/config/persist/migration.ts +82 -0
  237. package/src/config/persist/persist.test.ts +113 -0
  238. package/src/config/persist/persist.ts +65 -0
  239. package/src/config/persist/schema.ts +37 -0
  240. package/src/config/platform.test-utils.ts +20 -0
  241. package/src/config/resolve/resolve.test-utils.ts +20 -0
  242. package/src/config/resolve/resolve.test.ts +137 -0
  243. package/src/config/resolve/resolve.ts +104 -0
  244. package/src/config/selection/index.ts +1 -0
  245. package/src/config/selection/resolve.ts +14 -0
  246. package/src/config/selection/service.ts +29 -0
  247. package/src/config/temp-home.test-utils.ts +7 -0
  248. package/src/config/types.ts +49 -0
  249. package/src/config/url/error.ts +8 -0
  250. package/src/config/url/index.ts +7 -0
  251. package/src/config/url/url.test.ts +31 -0
  252. package/src/connection/index.ts +0 -1
  253. package/src/contracts/index.ts +1 -1
  254. package/src/domain/error.ts +39 -1
  255. package/src/domain/helpers.test.ts +1 -1
  256. package/src/domain/helpers.ts +1 -1
  257. package/src/domain/model-config.ts +5 -1
  258. package/src/domain/thread-activities.test.ts +1 -1
  259. package/src/domain/thread-activities.ts +1 -1
  260. package/src/domain/thread-lifecycle.test.ts +1 -1
  261. package/src/domain/thread-lifecycle.ts +15 -1
  262. package/src/effect.test-utils.ts +25 -0
  263. package/src/index.ts +1 -1
  264. package/src/{connection/node.ts → node/connection.ts} +1 -1
  265. package/src/node/index.ts +1 -0
  266. package/src/orchestration/index.ts +1 -1
  267. package/src/orchestration/layer.ts +34 -86
  268. package/src/orchestration/service.ts +5 -1
  269. package/src/rpc/error.ts +12 -2
  270. package/src/rpc/index.ts +5 -1
  271. package/src/rpc/layer.ts +1 -1
  272. package/src/rpc/operation.ts +83 -0
  273. package/src/rpc/ws-group.ts +25 -1
  274. package/src/runtime/index.ts +2 -1
  275. package/src/runtime/layer.test-utils.ts +32 -0
  276. package/src/runtime/layer.test.ts +66 -0
  277. package/src/runtime/layer.ts +22 -11
  278. package/src/t3tools/index.ts +1 -1
  279. package/src/types/marked-terminal.d.ts +11 -0
  280. package/dist/layout.js +0 -2
  281. package/dist/scope.js +0 -2
  282. package/dist/src/cli/output-format.d.ts +0 -9
  283. package/dist/src/config/layer.d.ts +0 -22
  284. package/dist/src/config/service.d.ts +0 -22
  285. package/dist/src/environment/layer.d.ts +0 -3
  286. package/dist/src/environment/service.d.ts +0 -12
  287. package/dist/src/layout/index.d.ts +0 -1
  288. package/dist/upstream-t3code/packages/contracts/src/auth.d.ts +0 -441
  289. package/dist/upstream-t3code/packages/contracts/src/baseSchemas.d.ts +0 -38
  290. package/dist/upstream-t3code/packages/contracts/src/desktopBootstrap.d.ts +0 -14
  291. package/dist/upstream-t3code/packages/contracts/src/editor.d.ts +0 -124
  292. package/dist/upstream-t3code/packages/contracts/src/environment.d.ts +0 -64
  293. package/dist/upstream-t3code/packages/contracts/src/environmentHttp.d.ts +0 -785
  294. package/dist/upstream-t3code/packages/contracts/src/filesystem.d.ts +0 -26
  295. package/dist/upstream-t3code/packages/contracts/src/git.d.ts +0 -500
  296. package/dist/upstream-t3code/packages/contracts/src/index.d.ts +0 -25
  297. package/dist/upstream-t3code/packages/contracts/src/ipc.d.ts +0 -496
  298. package/dist/upstream-t3code/packages/contracts/src/keybindings.d.ts +0 -133
  299. package/dist/upstream-t3code/packages/contracts/src/model.d.ts +0 -112
  300. package/dist/upstream-t3code/packages/contracts/src/orchestration.d.ts +0 -7338
  301. package/dist/upstream-t3code/packages/contracts/src/project.d.ts +0 -45
  302. package/dist/upstream-t3code/packages/contracts/src/provider.d.ts +0 -130
  303. package/dist/upstream-t3code/packages/contracts/src/providerInstance.d.ts +0 -99
  304. package/dist/upstream-t3code/packages/contracts/src/providerRuntime.d.ts +0 -4450
  305. package/dist/upstream-t3code/packages/contracts/src/relay.d.ts +0 -1262
  306. package/dist/upstream-t3code/packages/contracts/src/relayClient.d.ts +0 -48
  307. package/dist/upstream-t3code/packages/contracts/src/remoteAccess.d.ts +0 -45
  308. package/dist/upstream-t3code/packages/contracts/src/review.d.ts +0 -37
  309. package/dist/upstream-t3code/packages/contracts/src/rpc.d.ts +0 -8710
  310. package/dist/upstream-t3code/packages/contracts/src/server.d.ts +0 -2336
  311. package/dist/upstream-t3code/packages/contracts/src/settings.d.ts +0 -292
  312. package/dist/upstream-t3code/packages/contracts/src/sourceControl.d.ts +0 -177
  313. package/dist/upstream-t3code/packages/contracts/src/terminal.d.ts +0 -333
  314. package/dist/upstream-t3code/packages/contracts/src/vcs.d.ts +0 -148
  315. package/src/cli/auth-format.ts +0 -45
  316. package/src/cli/model-format.ts +0 -18
  317. package/src/cli/output-format.ts +0 -41
  318. package/src/cli/project-format.ts +0 -11
  319. package/src/cli/thread-format.ts +0 -121
  320. package/src/config/layer.ts +0 -105
  321. package/src/config/service.ts +0 -26
  322. package/src/environment/layer.ts +0 -12
  323. package/src/environment/service.ts +0 -13
  324. package/src/layout/index.ts +0 -1
  325. /package/dist/src/{scope → cli/scope}/index.d.ts +0 -0
  326. /package/dist/src/config/{url.d.ts → url/url.d.ts} +0 -0
  327. /package/src/{scope → cli/scope}/index.ts +0 -0
  328. /package/src/config/{url.ts → url/url.ts} +0 -0
@@ -0,0 +1,131 @@
1
+ import { Marked } from "marked";
2
+ import { markedTerminal } from "marked-terminal";
3
+ import stringWidth from "string-width";
4
+ import wrapAnsi from "wrap-ansi";
5
+
6
+ type TableColumn<Row> = {
7
+ readonly header: string;
8
+ readonly value: (row: Row) => string;
9
+ readonly maxWidth?: number;
10
+ };
11
+
12
+ type RecordEntry = {
13
+ readonly field: string;
14
+ readonly value: string;
15
+ };
16
+
17
+ const terminalMarkdown = new Marked(
18
+ markedTerminal({
19
+ reflowText: false,
20
+ showSectionPrefix: true,
21
+ width: 100,
22
+ }),
23
+ );
24
+
25
+ export function formatTable<Row>(
26
+ columns: ReadonlyArray<TableColumn<Row>>,
27
+ rows: ReadonlyArray<Row>,
28
+ ) {
29
+ if (rows.length === 0) {
30
+ return "";
31
+ }
32
+
33
+ const widths = columns.map((column) =>
34
+ Math.min(
35
+ column.maxWidth ?? Number.POSITIVE_INFINITY,
36
+ Math.max(stringWidth(column.header), ...rows.map((row) => widestLine(column.value(row)))),
37
+ ),
38
+ );
39
+
40
+ const header = formatTableLine(
41
+ columns.map((column) => column.header),
42
+ widths,
43
+ );
44
+ const divider = widths.map((width) => "-".repeat(width)).join(" ");
45
+ const body = rows.map((row) =>
46
+ formatTableRow(
47
+ columns.map((column) => column.value(row)),
48
+ widths,
49
+ ),
50
+ );
51
+
52
+ return [header, divider, ...body].join("\n");
53
+ }
54
+
55
+ export function formatRecord(entries: ReadonlyArray<RecordEntry>) {
56
+ return formatTable(
57
+ [
58
+ { header: "field", value: (entry) => entry.field, maxWidth: 24 },
59
+ { header: "value", value: (entry) => entry.value, maxWidth: 96 },
60
+ ],
61
+ entries,
62
+ );
63
+ }
64
+
65
+ export function formatMarkdown(text: string) {
66
+ return terminalMarkdown
67
+ .parse(text, {
68
+ async: false,
69
+ })
70
+ .trimEnd();
71
+ }
72
+
73
+ export function formatChatTranscript(
74
+ messages: ReadonlyArray<{
75
+ readonly role: string;
76
+ readonly text: string;
77
+ }>,
78
+ ) {
79
+ return `${messages.map(formatChatMessage).join("\n\n")}\n`;
80
+ }
81
+
82
+ function formatChatMessage(message: { readonly role: string; readonly text: string }) {
83
+ const rendered = formatMarkdown(message.text);
84
+ return `${message.role}\n${indentBlock(rendered.length > 0 ? rendered : "(empty)")}`;
85
+ }
86
+
87
+ function formatTableRow(cells: ReadonlyArray<string>, widths: ReadonlyArray<number>) {
88
+ const wrappedCells = cells.map((cell, index) => wrapCell(cell, widths[index]!));
89
+ const height = Math.max(...wrappedCells.map((cell) => cell.length));
90
+ const lines: string[] = [];
91
+
92
+ for (let rowIndex = 0; rowIndex < height; rowIndex += 1) {
93
+ lines.push(
94
+ formatTableLine(
95
+ wrappedCells.map((cell) => cell[rowIndex] ?? ""),
96
+ widths,
97
+ ),
98
+ );
99
+ }
100
+
101
+ return lines.join("\n");
102
+ }
103
+
104
+ function formatTableLine(cells: ReadonlyArray<string>, widths: ReadonlyArray<number>) {
105
+ return cells.map((cell, index) => padRight(cell, widths[index]!)).join(" ");
106
+ }
107
+
108
+ function wrapCell(value: string, width: number) {
109
+ const lines = value
110
+ .split("\n")
111
+ .flatMap((line) =>
112
+ wrapAnsi(line.length > 0 ? line : " ", width, { hard: true, trim: false }).split("\n"),
113
+ );
114
+ return lines.length > 0 ? lines : [""];
115
+ }
116
+
117
+ function widestLine(value: string) {
118
+ return Math.max(...value.split("\n").map((line) => stringWidth(line)));
119
+ }
120
+
121
+ function padRight(value: string, width: number) {
122
+ const padding = width - stringWidth(value);
123
+ return padding > 0 ? `${value}${" ".repeat(padding)}` : value;
124
+ }
125
+
126
+ function indentBlock(value: string) {
127
+ return value
128
+ .split("\n")
129
+ .map((line) => ` ${line}`)
130
+ .join("\n");
131
+ }
@@ -0,0 +1,12 @@
1
+ export {
2
+ resolveOutputFormat,
3
+ canRenderLiveTerminal,
4
+ isInteractiveHumanTerminal,
5
+ isAgentEnvironment,
6
+ humanJsonFormatChoices,
7
+ humanNdjsonFormatChoices,
8
+ humanJsonNdjsonFormatChoices,
9
+ type HumanJsonFormat,
10
+ type HumanNdjsonFormat,
11
+ type HumanJsonNdjsonFormat,
12
+ } from "./output.ts";
@@ -0,0 +1,29 @@
1
+ import type { ServerProvider } from "@t3tools/contracts";
2
+
3
+ import { formatTable } from "./human.ts";
4
+
5
+ export function formatModelsHuman(providers: ReadonlyArray<ServerProvider>) {
6
+ if (providers.length === 0) {
7
+ return "no models found\n";
8
+ }
9
+ return providers
10
+ .map((provider) => {
11
+ const models = provider.models;
12
+ const displayName = provider.displayName ?? provider.instanceId;
13
+ const header = `${displayName} (${provider.instanceId}) - ${provider.status}`;
14
+ if (models.length === 0) {
15
+ return `${header}\n no models`;
16
+ }
17
+ return `${header}\n${formatTable(
18
+ [
19
+ { header: "slug", value: (model) => model.slug, maxWidth: 40 },
20
+ { header: "name", value: (model) => model.name, maxWidth: 48 },
21
+ { header: "custom", value: (model) => (model.isCustom ? "yes" : "no"), maxWidth: 6 },
22
+ { header: "sub-provider", value: (model) => model.subProvider ?? "-", maxWidth: 24 },
23
+ ],
24
+ models,
25
+ )}`;
26
+ })
27
+ .join("\n\n")
28
+ .concat("\n");
29
+ }
@@ -0,0 +1,51 @@
1
+ import * as Option from "effect/Option";
2
+
3
+ import type { T3CliEnvShape } from "../../config/env/env.ts";
4
+ import type { CliRuntime } from "../runtime/service.ts";
5
+
6
+ export const humanJsonFormatChoices = ["auto", "human", "json"] as const;
7
+ export const humanNdjsonFormatChoices = ["auto", "human", "ndjson"] as const;
8
+ export const humanJsonNdjsonFormatChoices = ["auto", "human", "json", "ndjson"] as const;
9
+
10
+ export type HumanJsonFormat = (typeof humanJsonFormatChoices)[number];
11
+ export type HumanNdjsonFormat = (typeof humanNdjsonFormatChoices)[number];
12
+ export type HumanJsonNdjsonFormat = (typeof humanJsonNdjsonFormatChoices)[number];
13
+
14
+ export function resolveOutputFormat<T extends "json" | "ndjson">(
15
+ format: "auto" | "human" | T,
16
+ cliRuntime: CliRuntime["Service"],
17
+ t3CliEnv: T3CliEnvShape,
18
+ nonHumanFormat: T,
19
+ ): "human" | T {
20
+ if (format !== "auto") {
21
+ return format;
22
+ }
23
+ return isHumanTerminal(cliRuntime, t3CliEnv) ? "human" : nonHumanFormat;
24
+ }
25
+
26
+ export function canRenderLiveTerminal(cliRuntime: CliRuntime["Service"], t3CliEnv: T3CliEnvShape) {
27
+ return (
28
+ cliRuntime.stderrIsTTY &&
29
+ Option.getOrElse(t3CliEnv.term, () => "") !== "dumb" &&
30
+ !isAgentEnvironment(t3CliEnv)
31
+ );
32
+ }
33
+
34
+ export function isInteractiveHumanTerminal(
35
+ cliRuntime: CliRuntime["Service"],
36
+ t3CliEnv: T3CliEnvShape,
37
+ ) {
38
+ return (
39
+ cliRuntime.stdoutIsTTY &&
40
+ !isAgentEnvironment(t3CliEnv) &&
41
+ Option.getOrElse(t3CliEnv.term, () => "") !== "dumb"
42
+ );
43
+ }
44
+
45
+ function isHumanTerminal(cliRuntime: CliRuntime["Service"], t3CliEnv: T3CliEnvShape) {
46
+ return isInteractiveHumanTerminal(cliRuntime, t3CliEnv);
47
+ }
48
+
49
+ export function isAgentEnvironment(t3CliEnv: T3CliEnvShape) {
50
+ return t3CliEnv.ci || t3CliEnv.codexCi || t3CliEnv.codexThreadId || t3CliEnv.t3cliAgent;
51
+ }
@@ -0,0 +1,32 @@
1
+ import type { OrchestrationProjectShell } from "@t3tools/contracts";
2
+
3
+ import { formatRecord, formatTable } from "./human.ts";
4
+
5
+ export function formatProjectsHuman(projects: ReadonlyArray<OrchestrationProjectShell>) {
6
+ if (projects.length === 0) {
7
+ return "no projects\n";
8
+ }
9
+ return `${formatTable(
10
+ [
11
+ { header: "title", value: (project) => project.title, maxWidth: 32 },
12
+ { header: "id", value: (project) => project.id, maxWidth: 40 },
13
+ { header: "path", value: (project) => project.workspaceRoot, maxWidth: 72 },
14
+ ],
15
+ projects,
16
+ )}\n`;
17
+ }
18
+
19
+ export function formatProjectAddedHuman(project: OrchestrationProjectShell) {
20
+ return `project added\n${formatRecord([
21
+ { field: "title", value: project.title },
22
+ { field: "id", value: project.id },
23
+ { field: "path", value: project.workspaceRoot },
24
+ ])}`;
25
+ }
26
+
27
+ export function formatProjectDeletedHuman(input: {
28
+ readonly projectId: string;
29
+ readonly dispatch: { readonly sequence: number };
30
+ }) {
31
+ return `project deleted: ${input.projectId} (sequence ${input.dispatch.sequence})`;
32
+ }
@@ -0,0 +1,87 @@
1
+ import type { TerminalSessionSnapshot, TerminalSummary } from "@t3tools/contracts";
2
+
3
+ import { formatTable } from "./human.ts";
4
+
5
+ type TerminalRow = {
6
+ readonly id: string;
7
+ readonly status: string;
8
+ readonly label: string;
9
+ readonly cwd: string;
10
+ readonly updated: string;
11
+ readonly process: string;
12
+ };
13
+
14
+ export function formatTerminalListHuman(terminals: ReadonlyArray<TerminalSummary>) {
15
+ if (terminals.length === 0) {
16
+ return "no terminals\n";
17
+ }
18
+ return `${formatTerminalTable(terminals.map(toSummaryRow))}\n`;
19
+ }
20
+
21
+ export function formatTerminalCreatedHuman(snapshot: TerminalSessionSnapshot) {
22
+ return `terminal created\n${formatTerminalTable([toSnapshotRow(snapshot)])}`;
23
+ }
24
+
25
+ export function formatTerminalWrittenHuman(input: {
26
+ readonly terminalId: string;
27
+ readonly threadId: string;
28
+ readonly bytes: number;
29
+ }) {
30
+ return `wrote ${input.bytes} bytes to ${input.terminalId} (${input.threadId})`;
31
+ }
32
+
33
+ export function formatTerminalDestroyedHuman(input: {
34
+ readonly terminalId: string;
35
+ readonly threadId: string;
36
+ }) {
37
+ return `destroyed ${input.terminalId} (${input.threadId})`;
38
+ }
39
+
40
+ function formatTerminalTable(rows: ReadonlyArray<TerminalRow>) {
41
+ return formatTable(
42
+ [
43
+ { header: "id", value: (row) => row.id, maxWidth: 44 },
44
+ { header: "status", value: (row) => row.status, maxWidth: 8 },
45
+ { header: "label", value: (row) => row.label, maxWidth: 24 },
46
+ { header: "cwd", value: (row) => row.cwd, maxWidth: 40 },
47
+ { header: "updated", value: (row) => row.updated, maxWidth: 24 },
48
+ { header: "process", value: (row) => row.process, maxWidth: 20 },
49
+ ],
50
+ rows,
51
+ );
52
+ }
53
+
54
+ function toSummaryRow(terminal: TerminalSummary): TerminalRow {
55
+ return {
56
+ id: terminal.terminalId,
57
+ status: terminal.status,
58
+ label: terminal.label,
59
+ cwd: terminal.cwd,
60
+ updated: terminal.updatedAt,
61
+ process: formatProcessInfo(terminal),
62
+ };
63
+ }
64
+
65
+ function toSnapshotRow(snapshot: TerminalSessionSnapshot): TerminalRow {
66
+ return {
67
+ id: snapshot.terminalId,
68
+ status: snapshot.status,
69
+ label: snapshot.label,
70
+ cwd: snapshot.cwd,
71
+ updated: snapshot.updatedAt,
72
+ process: formatProcessInfo(snapshot),
73
+ };
74
+ }
75
+
76
+ function formatProcessInfo(input: {
77
+ readonly pid: number | null;
78
+ readonly exitCode: number | null;
79
+ readonly exitSignal: number | null;
80
+ }) {
81
+ const parts = [
82
+ typeof input.pid === "number" ? `pid ${input.pid}` : null,
83
+ typeof input.exitCode === "number" ? `exit ${input.exitCode}` : null,
84
+ typeof input.exitSignal === "number" ? `sig ${input.exitSignal}` : null,
85
+ ].filter((part): part is string => part !== null);
86
+ return parts.length > 0 ? parts.join(", ") : "-";
87
+ }
@@ -0,0 +1,34 @@
1
+ import "vite-plus/test/config";
2
+
3
+ import { assert, describe, it } from "@effect/vitest";
4
+ import { fromPartial } from "@total-typescript/shoehorn";
5
+ import type { OrchestrationThreadShell } from "@t3tools/contracts";
6
+
7
+ import { formatThreadsHuman } from "./thread.ts";
8
+
9
+ describe("formatThreadsHuman", () => {
10
+ it("appends (archived) for archived threads", () => {
11
+ const threads: OrchestrationThreadShell[] = [
12
+ fromPartial({
13
+ id: "active-1",
14
+ title: "Active thread",
15
+ archivedAt: null,
16
+ updatedAt: "2026-01-01T00:00:00.000Z",
17
+ session: null,
18
+ }),
19
+ fromPartial({
20
+ id: "archived-1",
21
+ title: "Archived thread",
22
+ archivedAt: "2026-01-02T00:00:00.000Z",
23
+ updatedAt: "2026-01-02T00:00:00.000Z",
24
+ session: null,
25
+ }),
26
+ ];
27
+
28
+ const output = formatThreadsHuman(threads);
29
+ assert.include(output, "title id status updated flags");
30
+ assert.include(output, "Active thread active-1");
31
+ assert.include(output, "Archived thread archived-1");
32
+ assert.include(output, "archived");
33
+ });
34
+ });
@@ -0,0 +1,185 @@
1
+ import type { ThreadShow } from "../../application/threads.ts";
2
+ import type { WaitEvent } from "../../application/service.ts";
3
+ import type { OrchestrationThread, OrchestrationThreadShell } from "@t3tools/contracts";
4
+ import { latestAssistantMessage, threadStatus } from "../../domain/thread-lifecycle.ts";
5
+ import { formatChatTranscript, formatRecord, formatTable } from "./human.ts";
6
+
7
+ export function formatThreadShowJson(thread: ThreadShow) {
8
+ return {
9
+ id: thread.id,
10
+ projectId: thread.projectId,
11
+ title: thread.title,
12
+ status: thread.status,
13
+ session: thread.session,
14
+ latestTurn: thread.latestTurn,
15
+ modelSelection: thread.modelSelection,
16
+ runtimeMode: thread.runtimeMode,
17
+ interactionMode: thread.interactionMode,
18
+ branch: thread.branch,
19
+ worktreePath: thread.worktreePath,
20
+ archivedAt: thread.archivedAt,
21
+ createdAt: thread.createdAt,
22
+ updatedAt: thread.updatedAt,
23
+ messageCount: thread.messageCount,
24
+ hasPendingApprovals: thread.hasPendingApprovals,
25
+ hasPendingUserInput: thread.hasPendingUserInput,
26
+ hasActionableProposedPlan: thread.hasActionableProposedPlan,
27
+ pendingApprovals: thread.pendingApprovals,
28
+ pendingUserInputs: thread.pendingUserInputs,
29
+ };
30
+ }
31
+
32
+ export function formatThreadShowHuman(thread: ThreadShow) {
33
+ const sections = [
34
+ formatRecord([
35
+ { field: "title", value: thread.title },
36
+ { field: "id", value: thread.id },
37
+ { field: "project", value: thread.projectId },
38
+ { field: "status", value: thread.status },
39
+ {
40
+ field: "model",
41
+ value: `${thread.modelSelection.instanceId}/${thread.modelSelection.model}`,
42
+ },
43
+ { field: "runtime", value: thread.runtimeMode },
44
+ { field: "interaction", value: thread.interactionMode },
45
+ ...(thread.branch !== null ? [{ field: "branch", value: thread.branch }] : []),
46
+ ...(thread.worktreePath !== null ? [{ field: "worktree", value: thread.worktreePath }] : []),
47
+ ...(thread.archivedAt !== null ? [{ field: "archived", value: thread.archivedAt }] : []),
48
+ { field: "messages", value: String(thread.messageCount) },
49
+ { field: "updated", value: thread.updatedAt },
50
+ ]),
51
+ ];
52
+ if (thread.pendingApprovals.length > 0) {
53
+ sections.push(
54
+ `\npending approvals\n${formatTable(
55
+ [
56
+ { header: "request", value: (approval) => approval.requestId, maxWidth: 40 },
57
+ { header: "kind", value: (approval) => approval.requestKind, maxWidth: 16 },
58
+ { header: "created", value: (approval) => approval.createdAt, maxWidth: 28 },
59
+ { header: "detail", value: (approval) => approval.detail ?? "-", maxWidth: 72 },
60
+ ],
61
+ thread.pendingApprovals,
62
+ )}`,
63
+ );
64
+ }
65
+ if (thread.pendingUserInputs.length > 0) {
66
+ sections.push(
67
+ `\npending user inputs\n${formatTable(
68
+ [
69
+ { header: "request", value: (input) => input.requestId, maxWidth: 40 },
70
+ { header: "questions", value: (input) => String(input.questions.length), maxWidth: 9 },
71
+ { header: "created", value: (input) => input.createdAt, maxWidth: 28 },
72
+ {
73
+ header: "prompt",
74
+ value: (input) => input.questions.map((question) => question.question).join("\n"),
75
+ maxWidth: 72,
76
+ },
77
+ ],
78
+ thread.pendingUserInputs,
79
+ )}`,
80
+ );
81
+ }
82
+ return `${sections.join("\n")}\n`;
83
+ }
84
+
85
+ export function formatThreadsHuman(threads: ReadonlyArray<OrchestrationThreadShell>) {
86
+ if (threads.length === 0) {
87
+ return "no threads\n";
88
+ }
89
+ return `${formatTable(
90
+ [
91
+ { header: "title", value: (thread) => thread.title, maxWidth: 36 },
92
+ { header: "id", value: (thread) => thread.id, maxWidth: 40 },
93
+ { header: "status", value: (thread) => threadStatus(thread), maxWidth: 18 },
94
+ { header: "updated", value: (thread) => thread.updatedAt, maxWidth: 28 },
95
+ { header: "flags", value: formatThreadFlags, maxWidth: 34 },
96
+ ],
97
+ threads,
98
+ )}\n`;
99
+ }
100
+
101
+ export function formatThreadDeletedHuman(input: {
102
+ readonly threadId: string;
103
+ readonly dispatch: { readonly sequence: number };
104
+ }) {
105
+ return `thread deleted: ${input.threadId} (sequence ${input.dispatch.sequence})`;
106
+ }
107
+
108
+ export function formatThreadStartedHuman(input: {
109
+ readonly thread: OrchestrationThread;
110
+ readonly sequence: number;
111
+ }) {
112
+ return `thread started\n${formatRecord([
113
+ { field: "title", value: input.thread.title },
114
+ { field: "id", value: input.thread.id },
115
+ { field: "status", value: threadStatus(input.thread) },
116
+ { field: "sequence", value: String(input.sequence) },
117
+ ])}`;
118
+ }
119
+
120
+ export function formatThreadMessagesHuman(thread: OrchestrationThread, limit: number) {
121
+ const messages = limit === 0 ? thread.messages : thread.messages.slice(-limit);
122
+ return formatChatTranscript(messages);
123
+ }
124
+
125
+ export function formatWaitDoneHuman(thread: OrchestrationThread) {
126
+ const latest = latestAssistantMessage(thread);
127
+ return `status: ${threadStatus(thread)}\n${
128
+ latest !== undefined ? `\n${formatChatTranscript([latest])}` : ""
129
+ }`;
130
+ }
131
+
132
+ export function formatThreadMessagesJson(thread: OrchestrationThread, full: boolean) {
133
+ return full ? thread : { thread: stripThreadMessages(thread), messages: thread.messages };
134
+ }
135
+
136
+ export function formatWaitEventNdjson(event: WaitEvent) {
137
+ if (event.type !== "thread" && event.type !== "done") {
138
+ return event;
139
+ }
140
+ const compactThread = stripThreadHeavy(event.thread);
141
+ return event.type === "done"
142
+ ? {
143
+ type: "done",
144
+ thread: compactThread,
145
+ latestAssistantMessage: latestAssistantMessage(event.thread) ?? null,
146
+ }
147
+ : {
148
+ type: "thread",
149
+ thread: compactThread,
150
+ messageCount: event.thread.messages.length,
151
+ };
152
+ }
153
+
154
+ function stripThreadMessages<T extends { readonly messages: unknown }>(thread: T) {
155
+ const { messages: _messages, ...rest } = thread;
156
+ return rest;
157
+ }
158
+
159
+ function formatThreadFlags(thread: OrchestrationThreadShell) {
160
+ const flags = [
161
+ thread.archivedAt !== null ? "archived" : null,
162
+ thread.hasPendingApprovals ? "approval" : null,
163
+ thread.hasPendingUserInput ? "input" : null,
164
+ thread.hasActionableProposedPlan ? "plan" : null,
165
+ ].filter((flag): flag is string => flag !== null);
166
+ return flags.length > 0 ? flags.join(", ") : "-";
167
+ }
168
+
169
+ function stripThreadHeavy<
170
+ T extends {
171
+ readonly messages: unknown;
172
+ readonly activities?: unknown;
173
+ readonly proposedPlans?: unknown;
174
+ readonly checkpoints?: unknown;
175
+ },
176
+ >(thread: T) {
177
+ const {
178
+ messages: _messages,
179
+ activities: _activities,
180
+ proposedPlans: _proposedPlans,
181
+ checkpoints: _checkpoints,
182
+ ...rest
183
+ } = thread;
184
+ return rest;
185
+ }
package/src/cli/index.ts CHANGED
@@ -8,3 +8,23 @@ export {
8
8
  waitFormatFlag,
9
9
  worktreeFlag,
10
10
  } from "./flags.ts";
11
+ export * as Env from "./env/index.ts";
12
+ export * as Runtime from "./runtime/index.ts";
13
+ export {
14
+ resolveCommandProjectRef,
15
+ resolveProjectRef,
16
+ resolveThreadId,
17
+ resolveWorktreePath,
18
+ } from "./scope/index.ts";
19
+ export {
20
+ resolveOutputFormat,
21
+ canRenderLiveTerminal,
22
+ isInteractiveHumanTerminal,
23
+ isAgentEnvironment,
24
+ humanJsonFormatChoices,
25
+ humanNdjsonFormatChoices,
26
+ humanJsonNdjsonFormatChoices,
27
+ type HumanJsonFormat,
28
+ type HumanNdjsonFormat,
29
+ type HumanJsonNdjsonFormat,
30
+ } from "./format/index.ts";
@@ -23,8 +23,28 @@ export const makeT3Input = Effect.fn("makeT3Input")(function* () {
23
23
  );
24
24
  });
25
25
 
26
+ const readStdinBinary = Effect.fn("T3InputLive.readStdinBinary")(function* () {
27
+ return yield* stdio.stdin.pipe(
28
+ Stream.runFold(
29
+ () => new Uint8Array(0),
30
+ (acc, chunk) => {
31
+ const merged = new Uint8Array(acc.length + chunk.length);
32
+ merged.set(acc);
33
+ merged.set(chunk, acc.length);
34
+ return merged;
35
+ },
36
+ ),
37
+ Effect.map((bytes) => Buffer.from(bytes).toString("latin1")),
38
+ Effect.catchTags({
39
+ PlatformError: (error) =>
40
+ Effect.fail(new InputError({ message: "failed to read stdin", cause: error })),
41
+ }),
42
+ );
43
+ });
44
+
26
45
  return {
27
46
  readStdin,
47
+ readStdinBinary,
28
48
  };
29
49
  });
30
50
 
@@ -7,5 +7,6 @@ export class T3Input extends Context.Service<
7
7
  T3Input,
8
8
  {
9
9
  readonly readStdin: () => Effect.Effect<string, InputError>;
10
+ readonly readStdinBinary: () => Effect.Effect<string, InputError>;
10
11
  }
11
12
  >()("t3cli/T3Input") {}
@@ -0,0 +1,64 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Prompt from "effect/unstable/cli/Prompt";
3
+
4
+ import { DestructiveConfirmationRequiredError } from "../error.ts";
5
+ import { isInteractiveHumanTerminal } from "../format/output.ts";
6
+ import type { T3CliEnvShape } from "../../config/env/env.ts";
7
+ import type { CliRuntime } from "../runtime/service.ts";
8
+
9
+ export const requireDestructiveConfirmation = Effect.fn("requireDestructiveConfirmation")(
10
+ function* (input: {
11
+ readonly message: string;
12
+ readonly yes: boolean;
13
+ readonly cliRuntime: CliRuntime["Service"];
14
+ readonly t3CliEnv: T3CliEnvShape;
15
+ }) {
16
+ if (input.yes) {
17
+ return;
18
+ }
19
+ if (!isInteractiveHumanTerminal(input.cliRuntime, input.t3CliEnv)) {
20
+ yield* Effect.fail(
21
+ new DestructiveConfirmationRequiredError({
22
+ message: "destructive action requires --yes in non-interactive mode",
23
+ }),
24
+ );
25
+ return;
26
+ }
27
+ const confirmed = yield* Prompt.run(Prompt.confirm({ message: input.message, initial: false }));
28
+ if (confirmed) {
29
+ return;
30
+ }
31
+ yield* Effect.fail(new DestructiveConfirmationRequiredError({ message: "aborted" }));
32
+ },
33
+ );
34
+
35
+ export const requireEnvironmentReplaceConfirmation = Effect.fn(
36
+ "requireEnvironmentReplaceConfirmation",
37
+ )(function* (input: {
38
+ readonly name: string;
39
+ readonly replace: boolean;
40
+ readonly cliRuntime: CliRuntime["Service"];
41
+ readonly t3CliEnv: T3CliEnvShape;
42
+ }) {
43
+ if (input.replace) {
44
+ return;
45
+ }
46
+ if (!isInteractiveHumanTerminal(input.cliRuntime, input.t3CliEnv)) {
47
+ yield* Effect.fail(
48
+ new DestructiveConfirmationRequiredError({
49
+ message: `environment '${input.name}' already exists: pass --replace`,
50
+ }),
51
+ );
52
+ return;
53
+ }
54
+ const confirmed = yield* Prompt.run(
55
+ Prompt.confirm({
56
+ message: `Environment '${input.name}' already exists. Replace?`,
57
+ initial: false,
58
+ }),
59
+ );
60
+ if (confirmed) {
61
+ return;
62
+ }
63
+ yield* Effect.fail(new DestructiveConfirmationRequiredError({ message: "aborted" }));
64
+ });