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,25 @@
1
+ import * as Context from "effect/Context";
2
+ import type * as Effect from "effect/Effect";
3
+ import type * as Scope from "effect/Scope";
4
+ import type * as Stream from "effect/Stream";
5
+
6
+ import type { TerminalIoError } from "./error.ts";
7
+
8
+ export type TerminalWindowSize = {
9
+ readonly cols: number;
10
+ readonly rows: number;
11
+ };
12
+
13
+ export type RawTerminalIoSession = {
14
+ readonly input: Stream.Stream<Uint8Array>;
15
+ readonly resize: Stream.Stream<TerminalWindowSize>;
16
+ };
17
+
18
+ export class TerminalIo extends Context.Service<
19
+ TerminalIo,
20
+ {
21
+ readonly getWindowSize: Effect.Effect<TerminalWindowSize, TerminalIoError>;
22
+ readonly writeOutput: (text: string) => Effect.Effect<void>;
23
+ readonly openRawSession: Effect.Effect<RawTerminalIoSession, TerminalIoError, Scope.Scope>;
24
+ }
25
+ >()("t3cli/T3TerminalIo") {}
@@ -0,0 +1,36 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { Command } from "effect/unstable/cli";
3
+
4
+ import { extraArgsConfig } from "../extra-args.ts";
5
+ import { formatTerminalListHuman } from "../format/terminal.ts";
6
+ import { T3Application } from "../../application/service.ts";
7
+ import { CliRuntime } from "../../cli/runtime/service.ts";
8
+ import { loadT3CliEnv } from "../../config/env/env.ts";
9
+ import { formatFlag, threadFlag } from "../flags.ts";
10
+ import { resolveOutputFormat } from "../format/output.ts";
11
+ import { T3Output } from "../output/service.ts";
12
+ import { requireCommandThreadId } from "./scope.ts";
13
+
14
+ export const listTerminalCommand = Command.make(
15
+ "list",
16
+ {
17
+ thread: threadFlag,
18
+ format: formatFlag,
19
+ ...extraArgsConfig,
20
+ },
21
+ ({ thread, format }) =>
22
+ Effect.gen(function* () {
23
+ const application = yield* T3Application;
24
+ const cliRuntime = yield* CliRuntime;
25
+ const t3CliEnv = yield* loadT3CliEnv;
26
+ const output = yield* T3Output;
27
+ const threadId = yield* requireCommandThreadId({ thread });
28
+ const resolvedFormat = resolveOutputFormat(format, cliRuntime, t3CliEnv, "json");
29
+ const terminals = yield* application.listTerminals(threadId);
30
+ if (resolvedFormat === "json") {
31
+ yield* output.printJson(terminals);
32
+ } else {
33
+ yield* output.writeStdout(formatTerminalListHuman(terminals));
34
+ }
35
+ }),
36
+ ).pipe(Command.withDescription("list terminals for a thread"));
@@ -0,0 +1,48 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Layer from "effect/Layer";
3
+ import * as NodeServices from "@effect/platform-node/NodeServices";
4
+ import { fromPartial } from "@total-typescript/shoehorn";
5
+
6
+ import { T3Application } from "../../application/service.ts";
7
+ import { T3Config } from "../../config/config.ts";
8
+ import * as CliRuntime from "../../cli/runtime/service.ts";
9
+ import { t3CliEnvConfigLayer } from "../../config/env/env.test-utils.ts";
10
+ import { T3Input } from "../input/service.ts";
11
+ import { T3Output } from "../output/service.ts";
12
+
13
+ export const terminalCommandTestLayer = Layer.mergeAll(
14
+ Layer.succeed(
15
+ T3Application,
16
+ fromPartial({
17
+ getTerminal: () => Effect.die("getTerminal should not be called"),
18
+ attachTerminal: () => Effect.die("attachTerminal should not be called"),
19
+ writeTerminal: () => Effect.die("writeTerminal should not be called"),
20
+ }),
21
+ ),
22
+ Layer.succeed(
23
+ T3Config,
24
+ fromPartial({
25
+ resolve: () =>
26
+ Effect.succeed({
27
+ url: "ws://localhost",
28
+ token: "token",
29
+ source: "config",
30
+ local: true,
31
+ }),
32
+ }),
33
+ ),
34
+ Layer.succeed(T3Output, {
35
+ writeStdout: () => Effect.void,
36
+ writeStderr: () => Effect.void,
37
+ printJson: () => Effect.void,
38
+ printNdjson: () => Effect.void,
39
+ printInfo: () => Effect.void,
40
+ }),
41
+ Layer.succeed(T3Input, {
42
+ readStdin: () => Effect.die("readStdin should not be called"),
43
+ readStdinBinary: () => Effect.die("readStdinBinary should not be called"),
44
+ }),
45
+ NodeServices.layer,
46
+ CliRuntime.layer,
47
+ t3CliEnvConfigLayer("/tmp/t3cli-test"),
48
+ );
@@ -0,0 +1,35 @@
1
+ import "vite-plus/test/config";
2
+
3
+ import * as Cause from "effect/Cause";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Exit from "effect/Exit";
6
+ import * as Option from "effect/Option";
7
+ import { assert, describe, it } from "@effect/vitest";
8
+ import { Command } from "effect/unstable/cli";
9
+
10
+ import { InvalidFlagCombinationError } from "../error.ts";
11
+ import { readTerminalCommand } from "./read.ts";
12
+ import { terminalCommandTestLayer } from "./read.test-utils.ts";
13
+
14
+ describe("readTerminalCommand", () => {
15
+ it.layer(terminalCommandTestLayer)("readTerminalCommand", (t) => {
16
+ t.effect("rejects --from-sequence without --follow", () =>
17
+ Effect.gen(function* () {
18
+ const run = Command.runWith(readTerminalCommand, { version: "0.0.0-test" });
19
+ const exit = yield* run(["--thread", "thread-1", "term-1", "--from-sequence", "1"]).pipe(
20
+ Effect.exit,
21
+ );
22
+ assert.isTrue(Exit.isFailure(exit));
23
+ if (!Exit.isFailure(exit)) {
24
+ return;
25
+ }
26
+ const error = Cause.findErrorOption(exit.cause);
27
+ assert.isTrue(Option.isSome(error));
28
+ if (Option.isSome(error)) {
29
+ assert.instanceOf(error.value, InvalidFlagCombinationError);
30
+ assert.equal(error.value.message, "--from-sequence requires --follow");
31
+ }
32
+ }),
33
+ );
34
+ });
35
+ });
@@ -0,0 +1,99 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import * as Stream from "effect/Stream";
4
+ import { Argument, Command, Flag } from "effect/unstable/cli";
5
+
6
+ import { T3Application } from "../../application/service.ts";
7
+ import { extraArgsConfig } from "../extra-args.ts";
8
+ import { InvalidFlagCombinationError, InvalidLimitError } from "../error.ts";
9
+ import { threadFlag } from "../flags.ts";
10
+ import { T3Output } from "../output/service.ts";
11
+ import { TerminalCliError } from "./error.ts";
12
+ import { requireCommandThreadId } from "./scope.ts";
13
+ import { filterAttachStreamEvent, toTerminalAttachTarget } from "./shared.ts";
14
+
15
+ const jsonNdjsonFormatChoices = ["json", "ndjson"] as const;
16
+
17
+ export const readTerminalCommand = Command.make(
18
+ "read",
19
+ {
20
+ thread: threadFlag,
21
+ terminalId: Argument.string("terminal-id"),
22
+ history: Flag.boolean("history"),
23
+ follow: Flag.boolean("follow"),
24
+ fromSequence: Flag.integer("from-sequence").pipe(Flag.optional),
25
+ format: Flag.choice("format", jsonNdjsonFormatChoices).pipe(Flag.withDefault("json")),
26
+ ...extraArgsConfig,
27
+ },
28
+ ({ thread, terminalId, history, follow, fromSequence, format }) =>
29
+ Effect.gen(function* () {
30
+ const output = yield* T3Output;
31
+ const application = yield* T3Application;
32
+ const threadId = yield* requireCommandThreadId({ thread });
33
+ const fromSequenceValue = Option.getOrUndefined(fromSequence);
34
+
35
+ if (fromSequenceValue !== undefined && fromSequenceValue < 0) {
36
+ yield* Effect.fail(
37
+ new InvalidLimitError({
38
+ message: "from-sequence must be a non-negative integer",
39
+ value: String(fromSequenceValue),
40
+ }),
41
+ );
42
+ }
43
+ if (fromSequenceValue !== undefined && !follow) {
44
+ yield* Effect.fail(
45
+ new InvalidFlagCombinationError({
46
+ message: "--from-sequence requires --follow",
47
+ }),
48
+ );
49
+ }
50
+ if (follow && format !== "ndjson") {
51
+ yield* Effect.fail(
52
+ new InvalidFlagCombinationError({
53
+ message: "--follow requires --format ndjson",
54
+ }),
55
+ );
56
+ }
57
+
58
+ const terminal = yield* application.getTerminal({
59
+ threadId,
60
+ terminalId,
61
+ });
62
+ const stream = application
63
+ .attachTerminal({
64
+ terminal: toTerminalAttachTarget(terminal),
65
+ })
66
+ .pipe(
67
+ Stream.map((event) =>
68
+ filterAttachStreamEvent(event, {
69
+ includeHistory: history,
70
+ ...(fromSequenceValue !== undefined ? { fromSequence: fromSequenceValue } : {}),
71
+ }),
72
+ ),
73
+ Stream.filter((event): event is NonNullable<typeof event> => event !== null),
74
+ );
75
+
76
+ if (follow) {
77
+ yield* Stream.runForEach(stream, (event) => output.printNdjson(event));
78
+ return;
79
+ }
80
+
81
+ const item = yield* Stream.runHead(stream);
82
+ const event = Option.getOrUndefined(item);
83
+ if (event === undefined || event.type !== "snapshot") {
84
+ yield* Effect.fail(
85
+ new TerminalCliError({
86
+ message: "server did not return terminal snapshot",
87
+ threadId,
88
+ terminalId,
89
+ }),
90
+ );
91
+ } else {
92
+ if (format === "ndjson") {
93
+ yield* output.printNdjson(event);
94
+ } else {
95
+ yield* output.printJson(event.snapshot);
96
+ }
97
+ }
98
+ }),
99
+ ).pipe(Command.withDescription("read terminal state and optional history"));
@@ -0,0 +1,35 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+
4
+ import type { T3CliEnvScope } from "../../config/env/env.ts";
5
+ import { loadT3CliEnv } from "../../config/env/env.ts";
6
+ import { resolveThreadId } from "../scope/index.ts";
7
+ import { MissingThreadError } from "../error.ts";
8
+
9
+ export function resolveCommandThreadId(input: {
10
+ readonly thread: Option.Option<string>;
11
+ readonly scope: T3CliEnvScope;
12
+ }): string | undefined {
13
+ return resolveThreadId({
14
+ value: Option.getOrUndefined(input.thread),
15
+ scope: input.scope,
16
+ });
17
+ }
18
+
19
+ export const requireCommandThreadId = Effect.fn("requireCommandThreadId")(function* (input: {
20
+ readonly thread: Option.Option<string>;
21
+ }) {
22
+ const t3CliEnv = yield* loadT3CliEnv;
23
+ const threadId = resolveCommandThreadId({
24
+ thread: input.thread,
25
+ scope: t3CliEnv.scope,
26
+ });
27
+ if (threadId === undefined) {
28
+ return yield* Effect.fail(
29
+ new MissingThreadError({
30
+ message: "thread id is required: pass --thread or set T3CODE_THREAD_ID",
31
+ }),
32
+ );
33
+ }
34
+ return threadId;
35
+ });
@@ -0,0 +1,250 @@
1
+ import * as Deferred from "effect/Deferred";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Match from "effect/Match";
4
+ import * as Stream from "effect/Stream";
5
+ import type {
6
+ TerminalAttachStreamEvent,
7
+ TerminalSessionSnapshot,
8
+ TerminalSummary,
9
+ } from "@t3tools/contracts";
10
+
11
+ import type { TerminalAttachTarget, TerminalRef } from "../../application/service.ts";
12
+ import { T3Application } from "../../application/service.ts";
13
+ import type { ApplicationError } from "../../application/error.ts";
14
+ import { bytesToLatin1String } from "./encoding.ts";
15
+ import { TerminalCliError, type TerminalIoError } from "./error.ts";
16
+ import { TerminalIo } from "./io-service.ts";
17
+
18
+ const DETACH_BYTE = 0x1d;
19
+ const ANSI_CLEAR_SCREEN = "\u001bc";
20
+
21
+ type AttachSessionResult =
22
+ | {
23
+ readonly _tag: "Failure";
24
+ readonly error: ApplicationError | TerminalCliError;
25
+ readonly message?: string;
26
+ }
27
+ | {
28
+ readonly _tag: "Success";
29
+ readonly message?: string;
30
+ };
31
+
32
+ export function runAttachedTerminalSession(input: { readonly terminal: TerminalAttachTarget }) {
33
+ return Effect.scoped(
34
+ Effect.gen(function* () {
35
+ const application = yield* T3Application;
36
+ const io = yield* TerminalIo;
37
+ const { cols, rows } = yield* io.getWindowSize.pipe(
38
+ Effect.mapError((error) => mapTerminalIoError(error, input.terminal)),
39
+ );
40
+ const session = yield* io.openRawSession.pipe(
41
+ Effect.mapError((error) => mapTerminalIoError(error, input.terminal)),
42
+ );
43
+ const completion = yield* Deferred.make<AttachSessionResult>();
44
+ const stream = application.attachTerminal({
45
+ terminal: input.terminal,
46
+ cols,
47
+ rows,
48
+ });
49
+
50
+ yield* Stream.runForEach(stream, applyAttachEvent).pipe(
51
+ Effect.match({
52
+ onFailure: (error) => completeAttachSession(completion, { _tag: "Failure", error }),
53
+ onSuccess: () => completeAttachSession(completion, { _tag: "Success" }),
54
+ }),
55
+ Effect.forkScoped,
56
+ );
57
+
58
+ yield* Stream.runForEach(session.resize, ({ cols: nextCols, rows: nextRows }) =>
59
+ application.resizeTerminal({
60
+ terminal: input.terminal,
61
+ cols: nextCols,
62
+ rows: nextRows,
63
+ }),
64
+ ).pipe(
65
+ Effect.match({
66
+ onFailure: (error) => completeAttachSession(completion, { _tag: "Failure", error }),
67
+ onSuccess: () => completeAttachSession(completion, { _tag: "Success" }),
68
+ }),
69
+ Effect.forkScoped,
70
+ );
71
+
72
+ yield* Stream.runForEach(session.input, (chunk) =>
73
+ handleSessionInput({
74
+ completion,
75
+ terminal: input.terminal,
76
+ chunk,
77
+ }),
78
+ ).pipe(
79
+ Effect.match({
80
+ onFailure: (error) => completeAttachSession(completion, { _tag: "Failure", error }),
81
+ onSuccess: () => completeAttachSession(completion, { _tag: "Success" }),
82
+ }),
83
+ Effect.forkScoped,
84
+ );
85
+
86
+ const result = yield* Deferred.await(completion);
87
+ if (result.message !== undefined) {
88
+ yield* writeSystemMessage(result.message);
89
+ }
90
+ return yield* Match.value(result).pipe(
91
+ Match.tag("Failure", ({ error }) => Effect.fail(error)),
92
+ Match.orElse(() => Effect.void),
93
+ );
94
+ }),
95
+ );
96
+ }
97
+
98
+ function completeAttachSession(
99
+ completion: Deferred.Deferred<AttachSessionResult>,
100
+ result: AttachSessionResult,
101
+ ) {
102
+ return Deferred.succeed(completion, result).pipe(Effect.ignore);
103
+ }
104
+
105
+ function handleSessionInput(input: {
106
+ readonly completion: Deferred.Deferred<AttachSessionResult>;
107
+ readonly terminal: TerminalRef;
108
+ readonly chunk: Uint8Array;
109
+ }) {
110
+ const detachOffset = input.chunk.indexOf(DETACH_BYTE);
111
+ const payload = detachOffset === -1 ? input.chunk : input.chunk.slice(0, detachOffset);
112
+
113
+ if (payload.length === 0 && detachOffset === -1) {
114
+ return Effect.void;
115
+ }
116
+
117
+ return Effect.gen(function* () {
118
+ const application = yield* T3Application;
119
+ if (payload.length > 0) {
120
+ yield* application.writeTerminal({
121
+ terminal: input.terminal,
122
+ data: bytesToLatin1String(payload),
123
+ });
124
+ }
125
+
126
+ if (detachOffset !== -1) {
127
+ yield* completeAttachSession(input.completion, {
128
+ _tag: "Success",
129
+ message: "detached",
130
+ });
131
+ }
132
+ });
133
+ }
134
+
135
+ function mapTerminalIoError(
136
+ error: TerminalIoError,
137
+ terminal: TerminalAttachTarget,
138
+ ): TerminalCliError {
139
+ return new TerminalCliError({
140
+ message: error.message,
141
+ threadId: terminal.threadId,
142
+ terminalId: terminal.terminalId,
143
+ });
144
+ }
145
+
146
+ function applyAttachEvent(event: TerminalAttachStreamEvent) {
147
+ return Effect.gen(function* () {
148
+ const io = yield* TerminalIo;
149
+ yield* Match.value(event).pipe(
150
+ Match.when({ type: "activity" }, () => Effect.void),
151
+ Match.when({ type: "snapshot" }, ({ snapshot }) => writeSnapshot(snapshot.history)),
152
+ Match.when({ type: "restarted" }, ({ snapshot }) => writeSnapshot(snapshot.history)),
153
+ Match.when({ type: "output" }, ({ data }) => io.writeOutput(data)),
154
+ Match.when({ type: "cleared" }, () => io.writeOutput(ANSI_CLEAR_SCREEN)),
155
+ Match.when({ type: "error" }, ({ message }) => writeSystemMessage(message)),
156
+ Match.when({ type: "closed" }, () => writeSystemMessage("Terminal closed")),
157
+ Match.orElse((next) => {
158
+ const details = [
159
+ typeof next.exitCode === "number" ? `code ${next.exitCode}` : null,
160
+ typeof next.exitSignal === "number" ? `signal ${next.exitSignal}` : null,
161
+ ]
162
+ .filter((value): value is string => value !== null)
163
+ .join(", ");
164
+ return writeSystemMessage(
165
+ details.length > 0 ? `Process exited (${details})` : "Process exited",
166
+ );
167
+ }),
168
+ );
169
+ });
170
+ }
171
+
172
+ function writeSnapshot(history: string) {
173
+ return Effect.gen(function* () {
174
+ const io = yield* TerminalIo;
175
+ yield* io.writeOutput(ANSI_CLEAR_SCREEN);
176
+ if (history.length > 0) {
177
+ yield* io.writeOutput(history);
178
+ }
179
+ });
180
+ }
181
+
182
+ function writeSystemMessage(message: string) {
183
+ return Effect.gen(function* () {
184
+ const io = yield* TerminalIo;
185
+ yield* io.writeOutput(`\r\n[terminal] ${message}\r\n`);
186
+ });
187
+ }
188
+
189
+ export function toTerminalAttachTarget(terminal: TerminalSummary): TerminalAttachTarget {
190
+ return {
191
+ threadId: terminal.threadId,
192
+ terminalId: terminal.terminalId,
193
+ cwd: terminal.cwd,
194
+ worktreePath: terminal.worktreePath,
195
+ };
196
+ }
197
+
198
+ export function snapshotToTerminalAttachTarget(
199
+ snapshot: TerminalSessionSnapshot,
200
+ ): TerminalAttachTarget {
201
+ return {
202
+ threadId: snapshot.threadId,
203
+ terminalId: snapshot.terminalId,
204
+ cwd: snapshot.cwd,
205
+ worktreePath: snapshot.worktreePath,
206
+ };
207
+ }
208
+
209
+ export function toTerminalRef(terminal: TerminalSummary): TerminalRef {
210
+ return {
211
+ threadId: terminal.threadId,
212
+ terminalId: terminal.terminalId,
213
+ };
214
+ }
215
+
216
+ export function filterAttachStreamEvent(
217
+ event: TerminalAttachStreamEvent,
218
+ options: {
219
+ readonly includeHistory: boolean;
220
+ readonly fromSequence?: number;
221
+ },
222
+ ): TerminalAttachStreamEvent | null {
223
+ if (event.type === "snapshot" || event.type === "restarted") {
224
+ if (
225
+ options.fromSequence !== undefined &&
226
+ event.type === "restarted" &&
227
+ typeof event.snapshot.sequence === "number" &&
228
+ event.snapshot.sequence < options.fromSequence
229
+ ) {
230
+ return null;
231
+ }
232
+ return {
233
+ ...event,
234
+ snapshot: {
235
+ ...event.snapshot,
236
+ history: options.includeHistory ? event.snapshot.history : "",
237
+ },
238
+ };
239
+ }
240
+
241
+ if (
242
+ options.fromSequence !== undefined &&
243
+ typeof event.sequence === "number" &&
244
+ event.sequence < options.fromSequence
245
+ ) {
246
+ return null;
247
+ }
248
+
249
+ return event;
250
+ }
@@ -0,0 +1,61 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import * as Stream from "effect/Stream";
4
+ import { Argument, Command, Flag } from "effect/unstable/cli";
5
+
6
+ import { T3Application } from "../../application/service.ts";
7
+ import { extraArgsConfig } from "../extra-args.ts";
8
+ import { InvalidLimitError } from "../error.ts";
9
+ import { threadFlag } from "../flags.ts";
10
+ import { T3Output } from "../output/service.ts";
11
+ import { requireCommandThreadId } from "./scope.ts";
12
+ import { filterAttachStreamEvent, toTerminalAttachTarget } from "./shared.ts";
13
+
14
+ const ndjsonOnlyFormatChoices = ["ndjson"] as const;
15
+
16
+ export const streamTerminalCommand = Command.make(
17
+ "stream",
18
+ {
19
+ thread: threadFlag,
20
+ terminalId: Argument.string("terminal-id"),
21
+ fromSequence: Flag.integer("from-sequence").pipe(Flag.optional),
22
+ format: Flag.choice("format", ndjsonOnlyFormatChoices).pipe(Flag.withDefault("ndjson")),
23
+ ...extraArgsConfig,
24
+ },
25
+ ({ thread, terminalId, fromSequence }) =>
26
+ Effect.gen(function* () {
27
+ const output = yield* T3Output;
28
+ const application = yield* T3Application;
29
+ const threadId = yield* requireCommandThreadId({ thread });
30
+ const fromSequenceValue = Option.getOrUndefined(fromSequence);
31
+
32
+ if (fromSequenceValue !== undefined && fromSequenceValue < 0) {
33
+ yield* Effect.fail(
34
+ new InvalidLimitError({
35
+ message: "from-sequence must be a non-negative integer",
36
+ value: String(fromSequenceValue),
37
+ }),
38
+ );
39
+ }
40
+
41
+ const terminal = yield* application.getTerminal({
42
+ threadId,
43
+ terminalId,
44
+ });
45
+ const stream = application
46
+ .attachTerminal({
47
+ terminal: toTerminalAttachTarget(terminal),
48
+ })
49
+ .pipe(
50
+ Stream.map((event) =>
51
+ filterAttachStreamEvent(event, {
52
+ includeHistory: true,
53
+ ...(fromSequenceValue !== undefined ? { fromSequence: fromSequenceValue } : {}),
54
+ }),
55
+ ),
56
+ Stream.filter((event): event is NonNullable<typeof event> => event !== null),
57
+ );
58
+
59
+ yield* Stream.runForEach(stream, (event) => output.printNdjson(event));
60
+ }),
61
+ ).pipe(Command.withDescription("stream terminal attach events as ndjson"));