t3code-cli 0.11.0 → 0.13.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 (330) hide show
  1. package/README.md +42 -10
  2. package/dist/application.js +2 -2
  3. package/dist/auth.js +1 -1
  4. package/dist/bin.js +85549 -2668
  5. package/dist/cli.js +15 -2
  6. package/dist/config.js +120 -2
  7. package/dist/connection.js +2 -2
  8. package/dist/index.js +1 -1
  9. package/dist/node.js +6 -2
  10. package/dist/orchestration.js +2 -2
  11. package/dist/rolldown-runtime.js +1 -1
  12. package/dist/rpc.js +1 -1
  13. package/dist/runtime.js +2 -2
  14. package/dist/shared.js +23313 -8022
  15. package/dist/src/application/actions.d.ts +25 -0
  16. package/dist/src/application/index.d.ts +3 -3
  17. package/dist/src/application/layer.d.ts +66 -50
  18. package/dist/src/application/model-selection.d.ts +2 -2
  19. package/dist/src/application/models.d.ts +1 -1
  20. package/dist/src/application/project-commands.d.ts +23 -3
  21. package/dist/src/application/projects.d.ts +7 -7
  22. package/dist/src/application/service.d.ts +65 -2
  23. package/dist/src/application/shell-sequence.d.ts +25 -19
  24. package/dist/src/application/terminals.d.ts +8 -8
  25. package/dist/src/application/thread-commands.d.ts +19 -19
  26. package/dist/src/application/thread-update.d.ts +1 -1
  27. package/dist/src/application/thread-wait.d.ts +19 -15
  28. package/dist/src/application/threads.d.ts +134 -108
  29. package/dist/src/auth/error.d.ts +3 -3
  30. package/dist/src/auth/index.d.ts +1 -1
  31. package/dist/src/auth/layer.d.ts +36 -8
  32. package/dist/src/auth/local-base-dir.d.ts +5 -2
  33. package/dist/src/auth/local-origin.d.ts +3 -3
  34. package/dist/src/auth/local-token.d.ts +3 -3
  35. package/dist/src/auth/remote-error.d.ts +3 -0
  36. package/dist/src/auth/service.d.ts +15 -3
  37. package/dist/src/auth/type.d.ts +40 -1
  38. package/dist/src/cli/env/flag.d.ts +2 -0
  39. package/dist/src/cli/env/index.d.ts +2 -0
  40. package/dist/src/cli/env/selection-layer.d.ts +3 -0
  41. package/dist/src/cli/flags.d.ts +2 -0
  42. package/dist/src/cli/format/index.d.ts +1 -0
  43. package/dist/src/cli/format/output.d.ts +12 -0
  44. package/dist/src/cli/index.d.ts +4 -0
  45. package/dist/src/cli/runtime/index.d.ts +1 -0
  46. package/dist/src/cli/runtime/service.d.ts +16 -0
  47. package/dist/src/{scope → cli/scope}/resolve.d.ts +5 -4
  48. package/dist/src/config/config.d.ts +49 -0
  49. package/dist/src/config/credential/cipher-node.d.ts +3 -0
  50. package/dist/src/config/credential/cipher-web.d.ts +3 -0
  51. package/dist/src/config/credential/cipher.d.ts +29 -0
  52. package/dist/src/config/credential/env.d.ts +3 -0
  53. package/dist/src/config/credential/error.d.ts +8 -0
  54. package/dist/src/config/credential/index.d.ts +6 -0
  55. package/dist/src/config/credential/service.d.ts +34 -0
  56. package/dist/src/config/env/env.d.ts +55 -0
  57. package/dist/src/config/env/index.d.ts +2 -0
  58. package/dist/src/{layout/base-dir.d.ts → config/env/layout.d.ts} +0 -1
  59. package/dist/src/config/environment-name/index.d.ts +1 -0
  60. package/dist/src/config/environment-name/name.d.ts +10 -0
  61. package/dist/src/config/error.d.ts +4 -11
  62. package/dist/src/config/index.d.ts +10 -5
  63. package/dist/src/config/keystore/error.d.ts +8 -0
  64. package/dist/src/config/keystore/file.d.ts +9 -0
  65. package/dist/src/config/keystore/index.d.ts +4 -0
  66. package/dist/src/config/keystore/keyring-node.d.ts +4 -0
  67. package/dist/src/config/keystore/service.d.ts +33 -0
  68. package/dist/src/config/paths/index.d.ts +1 -0
  69. package/dist/src/config/paths/paths.d.ts +6 -0
  70. package/dist/src/config/persist/file-mode.d.ts +4 -0
  71. package/dist/src/config/persist/migration.d.ts +68 -0
  72. package/dist/src/config/persist/persist.d.ts +40 -0
  73. package/dist/src/config/persist/schema.d.ts +68 -0
  74. package/dist/src/config/resolve/resolve.d.ts +40 -0
  75. package/dist/src/config/selection/index.d.ts +1 -0
  76. package/dist/src/config/selection/resolve.d.ts +4 -0
  77. package/dist/src/config/selection/service.d.ts +10 -0
  78. package/dist/src/config/types.d.ts +36 -0
  79. package/dist/src/config/url/error.d.ts +10 -0
  80. package/dist/src/config/url/index.d.ts +2 -0
  81. package/dist/src/connection/error.d.ts +3 -1
  82. package/dist/src/connection/index.d.ts +0 -1
  83. package/dist/src/connection/prepared.d.ts +14 -0
  84. package/dist/src/contracts/index.d.ts +1 -1
  85. package/dist/src/domain/error.d.ts +14 -1
  86. package/dist/src/domain/helpers.d.ts +53 -43
  87. package/dist/src/domain/model-config.d.ts +92 -84
  88. package/dist/src/domain/thread-activities.d.ts +1 -1
  89. package/dist/src/domain/thread-lifecycle.d.ts +24 -20
  90. package/dist/src/index.d.ts +1 -2
  91. package/dist/src/{connection/node.d.ts → node/connection.d.ts} +1 -1
  92. package/dist/src/node/index.d.ts +1 -2
  93. package/dist/src/orchestration/index.d.ts +0 -1
  94. package/dist/src/orchestration/layer.d.ts +321 -390
  95. package/dist/src/orchestration/service.d.ts +1 -1
  96. package/dist/src/rpc/error.d.ts +4 -6
  97. package/dist/src/rpc/layer.d.ts +2036 -1917
  98. package/dist/src/rpc/operation.d.ts +4 -5732
  99. package/dist/src/rpc/service.d.ts +1 -1
  100. package/dist/src/rpc/session.d.ts +22 -0
  101. package/dist/src/rpc/ws-group.d.ts +456 -1032
  102. package/dist/src/runtime/index.d.ts +1 -2
  103. package/dist/src/runtime/layer.d.ts +12 -8
  104. package/dist/src/sql/node-sqlite-client.d.ts +1 -1
  105. package/dist/src/t3tools/index.d.ts +1 -1
  106. package/dist/t3tools.js +2 -2
  107. package/package.json +30 -25
  108. package/src/application/actions.test.ts +328 -0
  109. package/src/application/actions.ts +475 -0
  110. package/src/application/index.ts +9 -5
  111. package/src/application/layer.ts +9 -0
  112. package/src/application/model-selection.ts +2 -2
  113. package/src/application/project-commands.ts +23 -5
  114. package/src/application/projects.ts +6 -7
  115. package/src/application/service.ts +79 -1
  116. package/src/application/shell-sequence.ts +1 -1
  117. package/src/application/terminals.ts +2 -1
  118. package/src/application/thread-commands.ts +1 -1
  119. package/src/application/thread-update.ts +1 -1
  120. package/src/application/thread-wait.ts +5 -1
  121. package/src/application/threads.test.ts +9 -3
  122. package/src/application/threads.ts +6 -6
  123. package/src/auth/error.ts +4 -9
  124. package/src/auth/index.ts +7 -0
  125. package/src/auth/layer.test-utils.ts +34 -0
  126. package/src/auth/layer.ts +116 -34
  127. package/src/auth/local-base-dir.ts +13 -6
  128. package/src/auth/local-origin.ts +10 -7
  129. package/src/auth/local-token.ts +28 -23
  130. package/src/auth/remote-error.ts +17 -0
  131. package/src/auth/service.test.ts +200 -0
  132. package/src/auth/service.ts +27 -3
  133. package/src/auth/transport.ts +45 -114
  134. package/src/auth/type.ts +47 -1
  135. package/src/bin.ts +24 -6
  136. package/src/cli/action-format.ts +110 -0
  137. package/src/cli/action.ts +398 -0
  138. package/src/cli/app.ts +6 -0
  139. package/src/cli/auth.ts +76 -23
  140. package/src/cli/env/flag.ts +8 -0
  141. package/src/cli/env/index.ts +2 -0
  142. package/src/cli/env/selection-layer.test.ts +49 -0
  143. package/src/cli/env/selection-layer.ts +27 -0
  144. package/src/cli/env.ts +107 -0
  145. package/src/cli/extra-args.test.ts +32 -0
  146. package/src/cli/extra-args.ts +22 -0
  147. package/src/cli/flags.ts +11 -1
  148. package/src/cli/format/auth.ts +92 -0
  149. package/src/cli/format/human.ts +131 -0
  150. package/src/cli/format/index.ts +12 -0
  151. package/src/cli/format/model.ts +29 -0
  152. package/src/cli/format/output.ts +51 -0
  153. package/src/cli/format/project.ts +32 -0
  154. package/src/cli/format/terminal.ts +87 -0
  155. package/src/cli/{thread-format.test.ts → format/thread.test.ts} +6 -4
  156. package/src/cli/format/thread.ts +185 -0
  157. package/src/cli/index.ts +20 -0
  158. package/src/cli/interaction/confirm.ts +64 -0
  159. package/src/cli/{self-action.ts → interaction/self-action.ts} +8 -6
  160. package/src/cli/message-input.ts +1 -1
  161. package/src/cli/model.ts +9 -5
  162. package/src/cli/project.ts +13 -7
  163. package/src/cli/projects/delete.ts +13 -12
  164. package/src/cli/require.ts +9 -7
  165. package/src/cli/runtime/index.ts +1 -0
  166. package/src/cli/runtime/service.ts +28 -0
  167. package/src/{scope → cli/scope}/resolve.ts +11 -9
  168. package/src/cli/terminal/attach.ts +3 -6
  169. package/src/cli/terminal/create.ts +10 -9
  170. package/src/cli/terminal/destroy.ts +13 -11
  171. package/src/cli/terminal/list.ts +10 -9
  172. package/src/cli/terminal/read.test-utils.ts +48 -0
  173. package/src/cli/terminal/read.test.ts +35 -0
  174. package/src/cli/terminal/read.ts +3 -6
  175. package/src/cli/terminal/scope.ts +10 -5
  176. package/src/cli/terminal/shared.ts +1 -1
  177. package/src/cli/terminal/stream.ts +3 -6
  178. package/src/cli/terminal/wait.test.ts +5 -3
  179. package/src/cli/terminal/wait.ts +10 -9
  180. package/src/cli/terminal/write.test.ts +34 -0
  181. package/src/cli/terminal/write.ts +10 -9
  182. package/src/cli/threads/approve.ts +11 -7
  183. package/src/cli/threads/archive.ts +14 -9
  184. package/src/cli/threads/callback.ts +6 -4
  185. package/src/cli/threads/delete.ts +17 -11
  186. package/src/cli/threads/interrupt.ts +14 -9
  187. package/src/cli/threads/list.test.ts +5 -3
  188. package/src/cli/threads/list.ts +10 -10
  189. package/src/cli/threads/messages.ts +11 -7
  190. package/src/cli/threads/respond.ts +11 -7
  191. package/src/cli/threads/send.ts +19 -9
  192. package/src/cli/threads/show.ts +11 -7
  193. package/src/cli/threads/start.ts +18 -13
  194. package/src/cli/threads/unarchive.ts +11 -7
  195. package/src/cli/threads/update.ts +14 -9
  196. package/src/cli/threads/wait.ts +11 -7
  197. package/src/cli/wait-events.ts +1 -1
  198. package/src/config/config.ts +260 -0
  199. package/src/config/credential/cipher-node.ts +40 -0
  200. package/src/config/credential/cipher-web.ts +82 -0
  201. package/src/config/credential/cipher.ts +39 -0
  202. package/src/config/credential/env.ts +13 -0
  203. package/src/config/credential/error.ts +9 -0
  204. package/src/config/credential/index.ts +6 -0
  205. package/src/config/credential/service.test-utils.ts +25 -0
  206. package/src/config/credential/service.test.ts +116 -0
  207. package/src/config/credential/service.ts +205 -0
  208. package/src/config/env/env.test-utils.ts +14 -0
  209. package/src/config/env/env.ts +81 -0
  210. package/src/config/env/index.ts +2 -0
  211. package/src/{layout/base-dir.ts → config/env/layout.ts} +0 -9
  212. package/src/config/environment-name/index.ts +7 -0
  213. package/src/config/environment-name/name.test.ts +66 -0
  214. package/src/config/environment-name/name.ts +49 -0
  215. package/src/config/error.ts +6 -8
  216. package/src/config/index.ts +16 -5
  217. package/src/config/keystore/error.ts +9 -0
  218. package/src/config/keystore/file.ts +96 -0
  219. package/src/config/keystore/index.ts +4 -0
  220. package/src/config/keystore/keyring-node.test.ts +12 -0
  221. package/src/config/keystore/keyring-node.ts +100 -0
  222. package/src/config/keystore/service.test-utils.ts +15 -0
  223. package/src/config/keystore/service.test.ts +18 -0
  224. package/src/config/keystore/service.ts +29 -0
  225. package/src/config/layer.test-utils.ts +45 -0
  226. package/src/config/layer.test.ts +209 -0
  227. package/src/config/paths/index.ts +1 -0
  228. package/src/config/paths/paths.ts +36 -0
  229. package/src/config/persist/file-mode.ts +21 -0
  230. package/src/config/persist/migration.test.ts +71 -0
  231. package/src/config/persist/migration.ts +82 -0
  232. package/src/config/persist/persist.test.ts +113 -0
  233. package/src/config/persist/persist.ts +65 -0
  234. package/src/config/persist/schema.ts +37 -0
  235. package/src/config/platform.test-utils.ts +20 -0
  236. package/src/config/resolve/resolve.test-utils.ts +20 -0
  237. package/src/config/resolve/resolve.test.ts +137 -0
  238. package/src/config/resolve/resolve.ts +104 -0
  239. package/src/config/selection/index.ts +1 -0
  240. package/src/config/selection/resolve.ts +14 -0
  241. package/src/config/selection/service.ts +29 -0
  242. package/src/config/temp-home.test-utils.ts +7 -0
  243. package/src/config/types.ts +49 -0
  244. package/src/config/url/error.ts +8 -0
  245. package/src/config/url/index.ts +7 -0
  246. package/src/config/url/url.test.ts +31 -0
  247. package/src/connection/error.ts +4 -1
  248. package/src/connection/index.ts +0 -1
  249. package/src/connection/layer.ts +5 -2
  250. package/src/connection/prepared.ts +83 -0
  251. package/src/contracts/index.ts +1 -1
  252. package/src/domain/error.ts +20 -1
  253. package/src/domain/helpers.test.ts +1 -1
  254. package/src/domain/helpers.ts +1 -1
  255. package/src/domain/model-config.ts +1 -1
  256. package/src/domain/thread-activities.test.ts +1 -1
  257. package/src/domain/thread-activities.ts +1 -1
  258. package/src/domain/thread-lifecycle.test.ts +1 -1
  259. package/src/domain/thread-lifecycle.ts +1 -1
  260. package/src/effect.test-utils.ts +25 -0
  261. package/src/index.ts +1 -1
  262. package/src/{connection/node.ts → node/connection.ts} +1 -1
  263. package/src/node/index.ts +1 -2
  264. package/src/orchestration/index.ts +0 -1
  265. package/src/orchestration/layer.ts +3 -2
  266. package/src/orchestration/service.ts +1 -1
  267. package/src/rpc/error.ts +10 -17
  268. package/src/rpc/layer.ts +17 -51
  269. package/src/rpc/operation.ts +16 -2
  270. package/src/rpc/service.ts +2 -1
  271. package/src/rpc/session.ts +168 -0
  272. package/src/rpc/ws-group.ts +13 -27
  273. package/src/runtime/index.ts +2 -1
  274. package/src/runtime/layer.test-utils.ts +32 -0
  275. package/src/runtime/layer.test.ts +66 -0
  276. package/src/runtime/layer.ts +21 -12
  277. package/src/t3tools/index.ts +1 -1
  278. package/src/types/marked-terminal.d.ts +11 -0
  279. package/dist/layout.js +0 -2
  280. package/dist/scope.js +0 -2
  281. package/dist/src/cli/output-format.d.ts +0 -11
  282. package/dist/src/config/layer.d.ts +0 -22
  283. package/dist/src/config/service.d.ts +0 -22
  284. package/dist/src/environment/layer.d.ts +0 -3
  285. package/dist/src/environment/service.d.ts +0 -12
  286. package/dist/src/layout/index.d.ts +0 -1
  287. package/dist/upstream-t3code/packages/contracts/src/auth.d.ts +0 -441
  288. package/dist/upstream-t3code/packages/contracts/src/baseSchemas.d.ts +0 -38
  289. package/dist/upstream-t3code/packages/contracts/src/desktopBootstrap.d.ts +0 -14
  290. package/dist/upstream-t3code/packages/contracts/src/editor.d.ts +0 -124
  291. package/dist/upstream-t3code/packages/contracts/src/environment.d.ts +0 -64
  292. package/dist/upstream-t3code/packages/contracts/src/environmentHttp.d.ts +0 -762
  293. package/dist/upstream-t3code/packages/contracts/src/filesystem.d.ts +0 -26
  294. package/dist/upstream-t3code/packages/contracts/src/git.d.ts +0 -500
  295. package/dist/upstream-t3code/packages/contracts/src/index.d.ts +0 -25
  296. package/dist/upstream-t3code/packages/contracts/src/ipc.d.ts +0 -490
  297. package/dist/upstream-t3code/packages/contracts/src/keybindings.d.ts +0 -133
  298. package/dist/upstream-t3code/packages/contracts/src/model.d.ts +0 -112
  299. package/dist/upstream-t3code/packages/contracts/src/orchestration.d.ts +0 -6682
  300. package/dist/upstream-t3code/packages/contracts/src/project.d.ts +0 -45
  301. package/dist/upstream-t3code/packages/contracts/src/provider.d.ts +0 -116
  302. package/dist/upstream-t3code/packages/contracts/src/providerInstance.d.ts +0 -99
  303. package/dist/upstream-t3code/packages/contracts/src/providerRuntime.d.ts +0 -4276
  304. package/dist/upstream-t3code/packages/contracts/src/relay.d.ts +0 -1262
  305. package/dist/upstream-t3code/packages/contracts/src/relayClient.d.ts +0 -48
  306. package/dist/upstream-t3code/packages/contracts/src/remoteAccess.d.ts +0 -45
  307. package/dist/upstream-t3code/packages/contracts/src/review.d.ts +0 -37
  308. package/dist/upstream-t3code/packages/contracts/src/rpc.d.ts +0 -8218
  309. package/dist/upstream-t3code/packages/contracts/src/server.d.ts +0 -2291
  310. package/dist/upstream-t3code/packages/contracts/src/settings.d.ts +0 -271
  311. package/dist/upstream-t3code/packages/contracts/src/sourceControl.d.ts +0 -177
  312. package/dist/upstream-t3code/packages/contracts/src/terminal.d.ts +0 -330
  313. package/dist/upstream-t3code/packages/contracts/src/vcs.d.ts +0 -148
  314. package/src/cli/auth-format.ts +0 -45
  315. package/src/cli/confirm.ts +0 -31
  316. package/src/cli/model-format.ts +0 -18
  317. package/src/cli/output-format.ts +0 -45
  318. package/src/cli/project-format.ts +0 -18
  319. package/src/cli/terminal/commands.test.ts +0 -101
  320. package/src/cli/terminal-format.ts +0 -167
  321. package/src/cli/thread-format.ts +0 -128
  322. package/src/config/layer.ts +0 -105
  323. package/src/config/service.ts +0 -26
  324. package/src/environment/layer.ts +0 -12
  325. package/src/environment/service.ts +0 -13
  326. package/src/layout/index.ts +0 -1
  327. /package/dist/src/{scope → cli/scope}/index.d.ts +0 -0
  328. /package/dist/src/config/{url.d.ts → url/url.d.ts} +0 -0
  329. /package/src/{scope → cli/scope}/index.ts +0 -0
  330. /package/src/config/{url.ts → url/url.ts} +0 -0
@@ -0,0 +1,475 @@
1
+ import * as Crypto from "effect/Crypto";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Path from "effect/Path";
4
+ import * as Schema from "effect/Schema";
5
+ import {
6
+ MAX_SCRIPT_ID_LENGTH,
7
+ SCRIPT_RUN_COMMAND_PATTERN,
8
+ type OrchestrationProjectShell,
9
+ type OrchestrationShellSnapshot,
10
+ type ProjectScript,
11
+ } from "@t3tools/contracts";
12
+
13
+ import {
14
+ ProjectActionLookupError,
15
+ ProjectActionValidationError,
16
+ ProjectLookupError,
17
+ ThreadLookupError,
18
+ } from "../domain/error.ts";
19
+ import { findProjectById, resolveProjectScope } from "../domain/helpers.ts";
20
+ import { T3Orchestration } from "../orchestration/service.ts";
21
+ import { makeProjectMetaUpdateCommand } from "./project-commands.ts";
22
+ import { waitForShellSequence } from "./shell-sequence.ts";
23
+ import {
24
+ T3TerminalApplication,
25
+ type AddProjectActionInput,
26
+ type ProjectActionSelector,
27
+ type T3ActionApplicationService,
28
+ type UpdateProjectActionInput,
29
+ } from "./service.ts";
30
+
31
+ const DEFAULT_ACTION_ICON = "play";
32
+ const isScriptRunCommand = Schema.is(SCRIPT_RUN_COMMAND_PATTERN);
33
+ type Mutable<T> = { -readonly [K in keyof T]: T[K] };
34
+ type MutablePartial<T> = { -readonly [K in keyof T]?: T[K] };
35
+
36
+ export const makeActionApplication = Effect.fn("makeActionApplication")(function* () {
37
+ const orchestration = yield* T3Orchestration;
38
+ const terminalApplication = yield* T3TerminalApplication;
39
+ const crypto = yield* Crypto.Crypto;
40
+ const path = yield* Path.Path;
41
+
42
+ const resolveProject = Effect.fn("T3ActionApplication.resolveProject")(function* (
43
+ snapshot: OrchestrationShellSnapshot,
44
+ projectRef: string,
45
+ ) {
46
+ const scope = yield* resolveProjectScope(snapshot, { ref: projectRef }).pipe(
47
+ Effect.provideService(Path.Path, path),
48
+ );
49
+ if (scope === undefined) {
50
+ return yield* Effect.fail(
51
+ new ProjectLookupError({
52
+ message: `project not found: ${projectRef}`,
53
+ ref: projectRef,
54
+ }),
55
+ );
56
+ }
57
+ return scope.project;
58
+ });
59
+
60
+ const dispatchScriptsUpdate = Effect.fn("T3ActionApplication.dispatchScriptsUpdate")(function* (
61
+ projectId: string,
62
+ scripts: ReadonlyArray<ProjectScript>,
63
+ ) {
64
+ const command = yield* makeProjectMetaUpdateCommand({
65
+ projectId,
66
+ scripts: [...scripts],
67
+ }).pipe(Effect.provideService(Crypto.Crypto, crypto));
68
+ const dispatch = yield* orchestration.dispatch(command);
69
+ const snapshot = yield* waitForShellSequence({ sequence: dispatch.sequence }).pipe(
70
+ Effect.provideService(T3Orchestration, orchestration),
71
+ );
72
+ const project = findProjectById(snapshot, projectId);
73
+ if (project === null) {
74
+ return yield* Effect.fail(
75
+ new ProjectLookupError({
76
+ message: `project not found after action update: ${projectId}`,
77
+ ref: projectId,
78
+ }),
79
+ );
80
+ }
81
+ return { dispatch, project };
82
+ });
83
+
84
+ const listActions: T3ActionApplicationService["listActions"] = Effect.fn(
85
+ "T3ActionApplication.listActions",
86
+ )(function* (projectRef: string) {
87
+ const snapshot = yield* orchestration.getShellSnapshot();
88
+ const project = yield* resolveProject(snapshot, projectRef);
89
+ return { project, actions: project.scripts };
90
+ });
91
+
92
+ const addAction: T3ActionApplicationService["addAction"] = Effect.fn(
93
+ "T3ActionApplication.addAction",
94
+ )(function* (input: AddProjectActionInput) {
95
+ const snapshot = yield* orchestration.getShellSnapshot();
96
+ const project = yield* resolveProject(snapshot, input.projectRef);
97
+ const name = yield* requireTrimmedNonEmpty(input.name, "action name", project.id);
98
+ const command = yield* requireTrimmedNonEmpty(input.command, "action command", project.id);
99
+ const id =
100
+ input.id !== undefined
101
+ ? yield* requireProjectScriptId(input.id, project.id)
102
+ : nextProjectScriptId(
103
+ name,
104
+ project.scripts.map((script) => script.id),
105
+ );
106
+ yield* validateUnusedId(project, id);
107
+ const preview = yield* normalizePreviewInput({
108
+ projectId: project.id,
109
+ autoOpenPreview: input.autoOpenPreview ?? false,
110
+ ...(input.previewUrl !== undefined ? { previewUrl: input.previewUrl } : {}),
111
+ });
112
+ const action: ProjectScript = {
113
+ id,
114
+ name,
115
+ command,
116
+ icon: input.icon ?? DEFAULT_ACTION_ICON,
117
+ runOnWorktreeCreate: input.setup === true,
118
+ ...preview,
119
+ };
120
+ const nextScripts = enforceSingleSetup(
121
+ [...project.scripts, action],
122
+ action.runOnWorktreeCreate ? action.id : null,
123
+ );
124
+ const result = yield* dispatchScriptsUpdate(project.id, nextScripts);
125
+ const persisted = yield* resolveActionBySelectorEffect(
126
+ result.project.scripts,
127
+ { id: action.id },
128
+ project.id,
129
+ );
130
+ return { ...result, action: persisted };
131
+ });
132
+
133
+ const updateAction: T3ActionApplicationService["updateAction"] = Effect.fn(
134
+ "T3ActionApplication.updateAction",
135
+ )(function* (input: UpdateProjectActionInput) {
136
+ const snapshot = yield* orchestration.getShellSnapshot();
137
+ const project = yield* resolveProject(snapshot, input.projectRef);
138
+ const existing = yield* resolveActionBySelectorEffect(
139
+ project.scripts,
140
+ input.selector,
141
+ project.id,
142
+ );
143
+ const updated = yield* normalizeActionUpdate(project.id, existing, input);
144
+ const nextScripts = enforceSingleSetup(
145
+ project.scripts.map((script) => (script.id === existing.id ? updated : script)),
146
+ updated.runOnWorktreeCreate ? updated.id : null,
147
+ );
148
+ const result = yield* dispatchScriptsUpdate(project.id, nextScripts);
149
+ const persisted = yield* resolveActionBySelectorEffect(
150
+ result.project.scripts,
151
+ { id: existing.id },
152
+ project.id,
153
+ );
154
+ return { ...result, action: persisted };
155
+ });
156
+
157
+ const deleteAction: T3ActionApplicationService["deleteAction"] = Effect.fn(
158
+ "T3ActionApplication.deleteAction",
159
+ )(function* (input: { readonly projectRef: string; readonly selector: ProjectActionSelector }) {
160
+ const snapshot = yield* orchestration.getShellSnapshot();
161
+ const project = yield* resolveProject(snapshot, input.projectRef);
162
+ const action = yield* resolveActionBySelectorEffect(
163
+ project.scripts,
164
+ input.selector,
165
+ project.id,
166
+ );
167
+ const nextScripts = project.scripts.filter((script) => script.id !== action.id);
168
+ const result = yield* dispatchScriptsUpdate(project.id, nextScripts);
169
+ return { ...result, action };
170
+ });
171
+
172
+ const runAction: T3ActionApplicationService["runAction"] = Effect.fn(
173
+ "T3ActionApplication.runAction",
174
+ )(function* (input: {
175
+ readonly threadId: string;
176
+ readonly selector: ProjectActionSelector;
177
+ readonly terminalId?: string;
178
+ }) {
179
+ const snapshot = yield* orchestration.getShellSnapshot();
180
+ const thread = snapshot.threads.find((candidate) => candidate.id === input.threadId);
181
+ if (thread === undefined) {
182
+ return yield* Effect.fail(
183
+ new ThreadLookupError({
184
+ message: `thread not found: ${input.threadId}`,
185
+ threadId: input.threadId,
186
+ }),
187
+ );
188
+ }
189
+ const project = findProjectById(snapshot, thread.projectId);
190
+ if (project === null) {
191
+ return yield* Effect.fail(
192
+ new ProjectLookupError({
193
+ message: `project not found for thread: ${input.threadId}`,
194
+ ref: thread.projectId,
195
+ }),
196
+ );
197
+ }
198
+ const action = yield* resolveActionBySelectorEffect(
199
+ project.scripts,
200
+ input.selector,
201
+ project.id,
202
+ );
203
+ const env = {
204
+ T3CODE_PROJECT_ROOT: project.workspaceRoot,
205
+ ...(thread.worktreePath !== null ? { T3CODE_WORKTREE_PATH: thread.worktreePath } : {}),
206
+ };
207
+ const terminal = yield* terminalApplication.createTerminal({
208
+ threadId: thread.id,
209
+ command: action.command,
210
+ env,
211
+ ...(input.terminalId !== undefined ? { terminalId: input.terminalId } : {}),
212
+ });
213
+ return { project, action, terminal };
214
+ });
215
+
216
+ return {
217
+ addAction,
218
+ deleteAction,
219
+ listActions,
220
+ runAction,
221
+ updateAction,
222
+ } satisfies T3ActionApplicationService;
223
+ });
224
+
225
+ export function nextProjectScriptId(name: string, existingIds: Iterable<string>): string {
226
+ const taken = new Set(Array.from(existingIds));
227
+ const baseId = normalizeScriptId(name);
228
+ if (!taken.has(baseId)) {
229
+ return baseId;
230
+ }
231
+
232
+ let suffix = 2;
233
+ while (suffix < 10_000) {
234
+ const candidate = `${baseId}-${suffix}`;
235
+ const safeCandidate =
236
+ candidate.length <= MAX_SCRIPT_ID_LENGTH
237
+ ? candidate
238
+ : `${baseId.slice(0, Math.max(1, MAX_SCRIPT_ID_LENGTH - String(suffix).length - 1))}-${suffix}`;
239
+ if (!taken.has(safeCandidate)) {
240
+ return safeCandidate;
241
+ }
242
+ suffix += 1;
243
+ }
244
+
245
+ return `${baseId}-${Date.now()}`.slice(0, MAX_SCRIPT_ID_LENGTH);
246
+ }
247
+
248
+ export function resolveActionBySelector(
249
+ scripts: ReadonlyArray<ProjectScript>,
250
+ selector: ProjectActionSelector,
251
+ projectId: string,
252
+ ): ProjectScript {
253
+ const result = resolveActionSelector(scripts, selector, projectId);
254
+ if ("action" in result) {
255
+ return result.action;
256
+ }
257
+ throw result.error;
258
+ }
259
+
260
+ function resolveActionBySelectorEffect(
261
+ scripts: ReadonlyArray<ProjectScript>,
262
+ selector: ProjectActionSelector,
263
+ projectId: string,
264
+ ) {
265
+ const result = resolveActionSelector(scripts, selector, projectId);
266
+ if ("action" in result) {
267
+ return Effect.succeed(result.action);
268
+ }
269
+ return Effect.fail(result.error);
270
+ }
271
+
272
+ function resolveActionSelector(
273
+ scripts: ReadonlyArray<ProjectScript>,
274
+ selector: ProjectActionSelector,
275
+ projectId: string,
276
+ ): { readonly action: ProjectScript } | { readonly error: ProjectActionLookupError } {
277
+ if (selector.id !== undefined) {
278
+ const id = selector.id.trim();
279
+ const match = scripts.find((script) => script.id === id);
280
+ if (match !== undefined) {
281
+ return { action: match };
282
+ }
283
+ return {
284
+ error: new ProjectActionLookupError({
285
+ message: `action not found by id: ${id}`,
286
+ projectId,
287
+ selector: `id:${id}`,
288
+ }),
289
+ };
290
+ }
291
+
292
+ const normalized = selector.name.trim().toLowerCase();
293
+ const matches = scripts.filter((script) => script.name.trim().toLowerCase() === normalized);
294
+ if (matches.length === 1) {
295
+ return { action: matches[0]! };
296
+ }
297
+ if (matches.length === 0) {
298
+ return {
299
+ error: new ProjectActionLookupError({
300
+ message: `action not found by name: ${selector.name.trim()}`,
301
+ projectId,
302
+ selector: `name:${selector.name.trim()}`,
303
+ }),
304
+ };
305
+ }
306
+ return {
307
+ error: new ProjectActionLookupError({
308
+ message: `action name is ambiguous: ${selector.name.trim()} matched ${matches.length} actions`,
309
+ projectId,
310
+ selector: `name:${selector.name.trim()}`,
311
+ }),
312
+ };
313
+ }
314
+
315
+ function normalizeScriptId(value: string): string {
316
+ const cleaned = value
317
+ .trim()
318
+ .toLowerCase()
319
+ .replace(/[^a-z0-9]+/g, "-")
320
+ .replace(/^-+|-+$/g, "");
321
+ if (cleaned.length === 0) {
322
+ return "script";
323
+ }
324
+ if (cleaned.length <= MAX_SCRIPT_ID_LENGTH) {
325
+ return cleaned;
326
+ }
327
+ const truncated = cleaned.slice(0, MAX_SCRIPT_ID_LENGTH).replace(/-+$/g, "");
328
+ return truncated.length > 0 ? truncated : "script";
329
+ }
330
+
331
+ function validateUnusedId(project: OrchestrationProjectShell, id: string) {
332
+ if (project.scripts.some((script) => script.id === id)) {
333
+ return Effect.fail(
334
+ new ProjectActionValidationError({
335
+ message: `action id already exists: ${id}`,
336
+ projectId: project.id,
337
+ }),
338
+ );
339
+ }
340
+ return Effect.void;
341
+ }
342
+
343
+ function requireProjectScriptId(value: string, projectId: string) {
344
+ return Effect.gen(function* () {
345
+ const id = yield* requireTrimmedNonEmpty(value, "action id", projectId);
346
+ if (!isScriptRunCommand(`script.${id}.run`)) {
347
+ return yield* Effect.fail(
348
+ new ProjectActionValidationError({
349
+ message: `action id must be lowercase alphanumeric or hyphenated and at most ${MAX_SCRIPT_ID_LENGTH} characters`,
350
+ projectId,
351
+ }),
352
+ );
353
+ }
354
+ return id;
355
+ });
356
+ }
357
+
358
+ function requireTrimmedNonEmpty(value: string, field: string, projectId: string) {
359
+ const trimmed = value.trim();
360
+ if (trimmed.length === 0) {
361
+ return Effect.fail(
362
+ new ProjectActionValidationError({
363
+ message: `${field} must not be empty`,
364
+ projectId,
365
+ }),
366
+ );
367
+ }
368
+ return Effect.succeed(trimmed);
369
+ }
370
+
371
+ function enforceSingleSetup(
372
+ scripts: ReadonlyArray<ProjectScript>,
373
+ setupActionId: string | null,
374
+ ): ReadonlyArray<ProjectScript> {
375
+ if (setupActionId === null) {
376
+ return scripts;
377
+ }
378
+ return scripts.map((script) =>
379
+ script.id === setupActionId
380
+ ? { ...script, runOnWorktreeCreate: true }
381
+ : script.runOnWorktreeCreate
382
+ ? { ...script, runOnWorktreeCreate: false }
383
+ : script,
384
+ );
385
+ }
386
+
387
+ function normalizePreviewInput(input: {
388
+ readonly projectId: string;
389
+ readonly previewUrl?: string;
390
+ readonly autoOpenPreview: boolean;
391
+ }) {
392
+ return Effect.gen(function* () {
393
+ const previewUrl = input.previewUrl?.trim();
394
+ if (previewUrl !== undefined && previewUrl.length > 0) {
395
+ return {
396
+ previewUrl,
397
+ ...(input.autoOpenPreview ? { autoOpenPreview: true } : {}),
398
+ } satisfies Partial<ProjectScript>;
399
+ }
400
+ if (input.autoOpenPreview) {
401
+ return yield* Effect.fail(
402
+ new ProjectActionValidationError({
403
+ message: "--auto-open-preview requires --preview-url",
404
+ projectId: input.projectId,
405
+ }),
406
+ );
407
+ }
408
+ return {};
409
+ });
410
+ }
411
+
412
+ function normalizeActionUpdate(
413
+ projectId: string,
414
+ existing: ProjectScript,
415
+ input: UpdateProjectActionInput,
416
+ ) {
417
+ return Effect.gen(function* () {
418
+ const patch: MutablePartial<ProjectScript> = {};
419
+ let clearPreviewUrl = false;
420
+ let clearAutoOpenPreview = false;
421
+ if (input.name !== undefined) {
422
+ patch.name = yield* requireTrimmedNonEmpty(input.name, "action name", projectId);
423
+ }
424
+ if (input.command !== undefined) {
425
+ patch.command = yield* requireTrimmedNonEmpty(input.command, "action command", projectId);
426
+ }
427
+ if (input.icon !== undefined) {
428
+ patch.icon = input.icon;
429
+ }
430
+ if (input.setup !== undefined) {
431
+ patch.runOnWorktreeCreate = input.setup;
432
+ }
433
+ if (input.previewUrl !== undefined) {
434
+ if (input.previewUrl === null) {
435
+ clearPreviewUrl = true;
436
+ clearAutoOpenPreview = true;
437
+ } else {
438
+ patch.previewUrl = yield* requireTrimmedNonEmpty(
439
+ input.previewUrl,
440
+ "preview URL",
441
+ projectId,
442
+ );
443
+ }
444
+ }
445
+ if (input.autoOpenPreview !== undefined) {
446
+ if (input.autoOpenPreview !== true) {
447
+ clearAutoOpenPreview = true;
448
+ } else {
449
+ const nextPreviewUrl = clearPreviewUrl
450
+ ? undefined
451
+ : (patch.previewUrl ?? existing.previewUrl);
452
+ if (nextPreviewUrl === undefined) {
453
+ return yield* Effect.fail(
454
+ new ProjectActionValidationError({
455
+ message: "--auto-open-preview requires an existing or updated --preview-url",
456
+ projectId,
457
+ }),
458
+ );
459
+ }
460
+ patch.autoOpenPreview = true;
461
+ }
462
+ }
463
+ const updated: Mutable<ProjectScript> = {
464
+ ...existing,
465
+ ...patch,
466
+ };
467
+ if (clearPreviewUrl) {
468
+ delete updated.previewUrl;
469
+ }
470
+ if (clearAutoOpenPreview) {
471
+ delete updated.autoOpenPreview;
472
+ }
473
+ return updated;
474
+ });
475
+ }
@@ -1,4 +1,5 @@
1
1
  export {
2
+ T3ActionApplication,
2
3
  T3Application,
3
4
  T3ModelApplication,
4
5
  T3ProjectApplication,
@@ -7,6 +8,7 @@ export {
7
8
  } from "./service.ts";
8
9
  export {
9
10
  makeT3Application,
11
+ T3ActionApplicationLive,
10
12
  T3ApplicationLive,
11
13
  T3ApplicationSlicesLive,
12
14
  T3ModelApplicationLive,
@@ -15,14 +17,16 @@ export {
15
17
  T3ThreadApplicationLive,
16
18
  } from "./layer.ts";
17
19
  export type {
20
+ AddProjectActionInput,
21
+ ProjectActionDeleteResult,
22
+ ProjectActionMutationResult,
23
+ ProjectActionRunResult,
24
+ ProjectActionSelector,
18
25
  SendThreadInput,
19
26
  StartThreadInput,
20
27
  StartThreadPolicy,
21
- T3ApplicationService,
22
- T3ModelApplicationService,
23
- T3ProjectApplicationService,
24
- T3TerminalApplicationService,
25
- T3ThreadApplicationService,
28
+ T3ActionApplicationService,
29
+ UpdateProjectActionInput,
26
30
  UpdateThreadInput,
27
31
  WaitEvent,
28
32
  } from "./service.ts";
@@ -1,9 +1,11 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Layer from "effect/Layer";
3
3
 
4
+ import { makeActionApplication } from "./actions.ts";
4
5
  import { makeModelsApplication } from "./models.ts";
5
6
  import { makeProjectApplication } from "./projects.ts";
6
7
  import {
8
+ T3ActionApplication,
7
9
  T3Application,
8
10
  T3ModelApplication,
9
11
  T3ProjectApplication,
@@ -15,10 +17,12 @@ import { makeThreadApplication } from "./threads.ts";
15
17
 
16
18
  export const makeT3Application = Effect.fn("makeT3Application")(function* () {
17
19
  const models = yield* T3ModelApplication;
20
+ const actions = yield* T3ActionApplication;
18
21
  const projects = yield* T3ProjectApplication;
19
22
  const terminals = yield* T3TerminalApplication;
20
23
  const threads = yield* T3ThreadApplication;
21
24
  return {
25
+ ...actions,
22
26
  ...models,
23
27
  ...projects,
24
28
  ...terminals,
@@ -35,9 +39,14 @@ export const T3TerminalApplicationLive = Layer.effect(
35
39
  T3TerminalApplication,
36
40
  makeTerminalApplication(),
37
41
  );
42
+ export const T3ActionApplicationLive = Layer.effect(
43
+ T3ActionApplication,
44
+ makeActionApplication(),
45
+ ).pipe(Layer.provide(T3TerminalApplicationLive));
38
46
  export const T3ThreadApplicationLive = Layer.effect(T3ThreadApplication, makeThreadApplication());
39
47
 
40
48
  export const T3ApplicationSlicesLive = Layer.mergeAll(
49
+ T3ActionApplicationLive,
41
50
  T3ModelApplicationLive,
42
51
  T3ProjectApplicationLive,
43
52
  T3TerminalApplicationLive,
@@ -1,6 +1,6 @@
1
1
  import * as Effect from "effect/Effect";
2
- import { ModelSelection, ProviderInstanceId } from "#t3tools/contracts";
3
- import type { OrchestrationProjectShell } from "#t3tools/contracts";
2
+ import { ModelSelection, ProviderInstanceId } from "@t3tools/contracts";
3
+ import type { OrchestrationProjectShell } from "@t3tools/contracts";
4
4
  import * as Schema from "effect/Schema";
5
5
 
6
6
  import { ModelSelectionError } from "../domain/error.ts";
@@ -2,18 +2,16 @@ import * as Crypto from "effect/Crypto";
2
2
  import * as DateTime from "effect/DateTime";
3
3
  import * as Effect from "effect/Effect";
4
4
  import * as Path from "effect/Path";
5
- import { CommandId, ProjectId, type ClientOrchestrationCommand } from "#t3tools/contracts";
6
-
7
- import { Environment } from "../environment/service.ts";
5
+ import { CommandId, ProjectId, type ClientOrchestrationCommand } from "@t3tools/contracts";
8
6
 
9
7
  export const makeProjectCreateCommand = Effect.fn("makeProjectCreateCommand")(function* (input: {
10
8
  readonly path: string;
11
9
  readonly title?: string;
10
+ readonly cwd: string;
12
11
  }) {
13
12
  const path = yield* Path.Path;
14
13
  const crypto = yield* Crypto.Crypto;
15
- const environment = yield* Environment;
16
- const workspaceRoot = path.resolve(environment.cwd, input.path);
14
+ const workspaceRoot = path.resolve(input.cwd, input.path);
17
15
  const projectId = ProjectId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
18
16
  const title = input.title?.trim();
19
17
  const createdAt = DateTime.formatIso(yield* DateTime.now);
@@ -43,3 +41,23 @@ export const makeProjectDeleteCommand = Effect.fn("makeProjectDeleteCommand")(fu
43
41
  ...(input.force === true ? { force: true } : {}),
44
42
  } satisfies Extract<ClientOrchestrationCommand, { readonly type: "project.delete" }>;
45
43
  });
44
+
45
+ export const makeProjectMetaUpdateCommand = Effect.fn("makeProjectMetaUpdateCommand")(
46
+ function* (input: {
47
+ readonly projectId: string;
48
+ readonly scripts: Extract<
49
+ ClientOrchestrationCommand,
50
+ { readonly type: "project.meta.update" }
51
+ >["scripts"];
52
+ }) {
53
+ const crypto = yield* Crypto.Crypto;
54
+ return {
55
+ type: "project.meta.update",
56
+ commandId: CommandId.make(
57
+ `t3cli:project-meta-update:${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`,
58
+ ),
59
+ projectId: ProjectId.make(input.projectId),
60
+ scripts: input.scripts,
61
+ } satisfies Extract<ClientOrchestrationCommand, { readonly type: "project.meta.update" }>;
62
+ },
63
+ );
@@ -2,7 +2,7 @@ import * as Crypto from "effect/Crypto";
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Path from "effect/Path";
4
4
 
5
- import { Environment } from "../environment/service.ts";
5
+ import { CliRuntime } from "../cli/runtime/service.ts";
6
6
  import { T3Orchestration } from "../orchestration/service.ts";
7
7
  import { ProjectCreateVisibilityError, ProjectLookupError } from "../domain/error.ts";
8
8
  import { findProjectById, resolveProjectScope } from "../domain/helpers.ts";
@@ -14,7 +14,7 @@ export const makeProjectApplication = Effect.fn("makeProjectApplication")(functi
14
14
  const orchestration = yield* T3Orchestration;
15
15
  const crypto = yield* Crypto.Crypto;
16
16
  const path = yield* Path.Path;
17
- const environment = yield* Environment;
17
+ const cliRuntime = yield* CliRuntime;
18
18
  const loadShell: T3ProjectApplicationService["loadShell"] = Effect.fn(
19
19
  "T3ApplicationLive.loadShell",
20
20
  )(function* () {
@@ -40,11 +40,10 @@ export const makeProjectApplication = Effect.fn("makeProjectApplication")(functi
40
40
  const addProject: T3ProjectApplicationService["addProject"] = Effect.fn(
41
41
  "T3ApplicationLive.addProject",
42
42
  )(function* (projectInput: { readonly path: string; readonly title?: string }) {
43
- const command = yield* makeProjectCreateCommand(projectInput).pipe(
44
- Effect.provideService(Path.Path, path),
45
- Effect.provideService(Crypto.Crypto, crypto),
46
- Effect.provideService(Environment, environment),
47
- );
43
+ const command = yield* makeProjectCreateCommand({
44
+ ...projectInput,
45
+ cwd: cliRuntime.cwd,
46
+ }).pipe(Effect.provideService(Path.Path, path), Effect.provideService(Crypto.Crypto, crypto));
48
47
  const dispatch = yield* orchestration.dispatch(command);
49
48
  const snapshot = yield* waitForShellSequence({ sequence: dispatch.sequence }).pipe(
50
49
  Effect.provideService(T3Orchestration, orchestration),