t3code-cli 0.11.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 (317) 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 +85526 -2662
  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/rpc.js +1 -1
  12. package/dist/runtime.js +2 -2
  13. package/dist/shared.js +42956 -38119
  14. package/dist/src/application/actions.d.ts +25 -0
  15. package/dist/src/application/index.d.ts +3 -3
  16. package/dist/src/application/layer.d.ts +51 -35
  17. package/dist/src/application/model-selection.d.ts +2 -2
  18. package/dist/src/application/models.d.ts +1 -1
  19. package/dist/src/application/project-commands.d.ts +22 -2
  20. package/dist/src/application/projects.d.ts +7 -7
  21. package/dist/src/application/service.d.ts +65 -2
  22. package/dist/src/application/shell-sequence.d.ts +11 -0
  23. package/dist/src/application/terminals.d.ts +2 -2
  24. package/dist/src/application/thread-commands.d.ts +1 -1
  25. package/dist/src/application/thread-wait.d.ts +9 -0
  26. package/dist/src/application/threads.d.ts +56 -5
  27. package/dist/src/auth/error.d.ts +2 -1
  28. package/dist/src/auth/index.d.ts +1 -1
  29. package/dist/src/auth/layer.d.ts +34 -6
  30. package/dist/src/auth/local-base-dir.d.ts +5 -2
  31. package/dist/src/auth/local-origin.d.ts +3 -3
  32. package/dist/src/auth/local-token.d.ts +3 -3
  33. package/dist/src/auth/service.d.ts +15 -3
  34. package/dist/src/auth/type.d.ts +40 -1
  35. package/dist/src/cli/env/flag.d.ts +2 -0
  36. package/dist/src/cli/env/index.d.ts +2 -0
  37. package/dist/src/cli/env/selection-layer.d.ts +3 -0
  38. package/dist/src/cli/flags.d.ts +2 -0
  39. package/dist/src/cli/format/index.d.ts +1 -0
  40. package/dist/src/cli/format/output.d.ts +12 -0
  41. package/dist/src/cli/index.d.ts +4 -0
  42. package/dist/src/cli/runtime/index.d.ts +1 -0
  43. package/dist/src/cli/runtime/service.d.ts +16 -0
  44. package/dist/src/{scope → cli/scope}/resolve.d.ts +5 -4
  45. package/dist/src/config/config.d.ts +49 -0
  46. package/dist/src/config/credential/cipher-node.d.ts +3 -0
  47. package/dist/src/config/credential/cipher-web.d.ts +3 -0
  48. package/dist/src/config/credential/cipher.d.ts +29 -0
  49. package/dist/src/config/credential/env.d.ts +3 -0
  50. package/dist/src/config/credential/error.d.ts +8 -0
  51. package/dist/src/config/credential/index.d.ts +6 -0
  52. package/dist/src/config/credential/service.d.ts +34 -0
  53. package/dist/src/config/env/env.d.ts +55 -0
  54. package/dist/src/config/env/index.d.ts +2 -0
  55. package/dist/src/{layout/base-dir.d.ts → config/env/layout.d.ts} +0 -1
  56. package/dist/src/config/environment-name/index.d.ts +1 -0
  57. package/dist/src/config/environment-name/name.d.ts +10 -0
  58. package/dist/src/config/error.d.ts +4 -11
  59. package/dist/src/config/index.d.ts +10 -5
  60. package/dist/src/config/keystore/error.d.ts +8 -0
  61. package/dist/src/config/keystore/file.d.ts +9 -0
  62. package/dist/src/config/keystore/index.d.ts +4 -0
  63. package/dist/src/config/keystore/keyring-node.d.ts +4 -0
  64. package/dist/src/config/keystore/service.d.ts +33 -0
  65. package/dist/src/config/paths/index.d.ts +1 -0
  66. package/dist/src/config/paths/paths.d.ts +6 -0
  67. package/dist/src/config/persist/file-mode.d.ts +4 -0
  68. package/dist/src/config/persist/migration.d.ts +68 -0
  69. package/dist/src/config/persist/persist.d.ts +40 -0
  70. package/dist/src/config/persist/schema.d.ts +68 -0
  71. package/dist/src/config/resolve/resolve.d.ts +40 -0
  72. package/dist/src/config/selection/index.d.ts +1 -0
  73. package/dist/src/config/selection/resolve.d.ts +4 -0
  74. package/dist/src/config/selection/service.d.ts +10 -0
  75. package/dist/src/config/types.d.ts +36 -0
  76. package/dist/src/config/url/error.d.ts +10 -0
  77. package/dist/src/config/url/index.d.ts +2 -0
  78. package/dist/src/connection/index.d.ts +0 -1
  79. package/dist/src/contracts/index.d.ts +1 -1
  80. package/dist/src/domain/error.d.ts +14 -1
  81. package/dist/src/domain/helpers.d.ts +16 -1
  82. package/dist/src/domain/model-config.d.ts +4 -1
  83. package/dist/src/domain/thread-activities.d.ts +1 -1
  84. package/dist/src/domain/thread-lifecycle.d.ts +10 -1
  85. package/dist/src/index.d.ts +1 -2
  86. package/dist/src/{connection/node.d.ts → node/connection.d.ts} +1 -1
  87. package/dist/src/node/index.d.ts +1 -2
  88. package/dist/src/orchestration/index.d.ts +0 -1
  89. package/dist/src/orchestration/layer.d.ts +153 -7
  90. package/dist/src/orchestration/service.d.ts +1 -1
  91. package/dist/src/rpc/error.d.ts +2 -2
  92. package/dist/src/rpc/index.d.ts +2 -3
  93. package/dist/src/rpc/layer.d.ts +574 -46
  94. package/dist/src/rpc/operation.d.ts +574 -46
  95. package/dist/src/rpc/ws-group.d.ts +184 -19
  96. package/dist/src/runtime/index.d.ts +1 -2
  97. package/dist/src/runtime/layer.d.ts +12 -8
  98. package/dist/src/t3tools/index.d.ts +1 -1
  99. package/dist/t3tools.js +2 -2
  100. package/package.json +25 -21
  101. package/src/application/actions.test.ts +328 -0
  102. package/src/application/actions.ts +475 -0
  103. package/src/application/index.ts +9 -5
  104. package/src/application/layer.ts +9 -0
  105. package/src/application/model-selection.ts +2 -2
  106. package/src/application/project-commands.ts +23 -5
  107. package/src/application/projects.ts +6 -7
  108. package/src/application/service.ts +79 -1
  109. package/src/application/shell-sequence.ts +1 -1
  110. package/src/application/terminals.ts +2 -1
  111. package/src/application/thread-commands.ts +1 -1
  112. package/src/application/thread-update.ts +1 -1
  113. package/src/application/thread-wait.ts +1 -1
  114. package/src/application/threads.test.ts +9 -3
  115. package/src/application/threads.ts +6 -6
  116. package/src/auth/error.ts +2 -1
  117. package/src/auth/index.ts +7 -0
  118. package/src/auth/layer.test-utils.ts +34 -0
  119. package/src/auth/layer.ts +116 -34
  120. package/src/auth/local-base-dir.ts +13 -6
  121. package/src/auth/local-origin.ts +10 -7
  122. package/src/auth/local-token.ts +28 -23
  123. package/src/auth/service.test.ts +200 -0
  124. package/src/auth/service.ts +27 -3
  125. package/src/auth/transport.ts +2 -2
  126. package/src/auth/type.ts +47 -1
  127. package/src/bin.ts +24 -6
  128. package/src/cli/action-format.ts +110 -0
  129. package/src/cli/action.ts +398 -0
  130. package/src/cli/app.ts +6 -0
  131. package/src/cli/auth.ts +76 -23
  132. package/src/cli/env/flag.ts +8 -0
  133. package/src/cli/env/index.ts +2 -0
  134. package/src/cli/env/selection-layer.test.ts +49 -0
  135. package/src/cli/env/selection-layer.ts +27 -0
  136. package/src/cli/env.ts +107 -0
  137. package/src/cli/extra-args.test.ts +32 -0
  138. package/src/cli/extra-args.ts +22 -0
  139. package/src/cli/flags.ts +11 -1
  140. package/src/cli/format/auth.ts +92 -0
  141. package/src/cli/format/human.ts +131 -0
  142. package/src/cli/format/index.ts +12 -0
  143. package/src/cli/format/model.ts +29 -0
  144. package/src/cli/format/output.ts +51 -0
  145. package/src/cli/format/project.ts +32 -0
  146. package/src/cli/format/terminal.ts +87 -0
  147. package/src/cli/{thread-format.test.ts → format/thread.test.ts} +6 -4
  148. package/src/cli/format/thread.ts +185 -0
  149. package/src/cli/index.ts +20 -0
  150. package/src/cli/interaction/confirm.ts +64 -0
  151. package/src/cli/{self-action.ts → interaction/self-action.ts} +8 -6
  152. package/src/cli/message-input.ts +1 -1
  153. package/src/cli/model.ts +9 -5
  154. package/src/cli/project.ts +13 -7
  155. package/src/cli/projects/delete.ts +13 -12
  156. package/src/cli/require.ts +9 -7
  157. package/src/cli/runtime/index.ts +1 -0
  158. package/src/cli/runtime/service.ts +28 -0
  159. package/src/{scope → cli/scope}/resolve.ts +11 -9
  160. package/src/cli/terminal/attach.ts +3 -6
  161. package/src/cli/terminal/create.ts +10 -9
  162. package/src/cli/terminal/destroy.ts +13 -11
  163. package/src/cli/terminal/list.ts +10 -9
  164. package/src/cli/terminal/read.test-utils.ts +48 -0
  165. package/src/cli/terminal/read.test.ts +35 -0
  166. package/src/cli/terminal/read.ts +3 -6
  167. package/src/cli/terminal/scope.ts +10 -5
  168. package/src/cli/terminal/shared.ts +1 -1
  169. package/src/cli/terminal/stream.ts +3 -6
  170. package/src/cli/terminal/wait.test.ts +5 -3
  171. package/src/cli/terminal/wait.ts +10 -9
  172. package/src/cli/terminal/write.test.ts +34 -0
  173. package/src/cli/terminal/write.ts +10 -9
  174. package/src/cli/threads/approve.ts +11 -7
  175. package/src/cli/threads/archive.ts +14 -9
  176. package/src/cli/threads/callback.ts +6 -4
  177. package/src/cli/threads/delete.ts +17 -11
  178. package/src/cli/threads/interrupt.ts +14 -9
  179. package/src/cli/threads/list.test.ts +5 -3
  180. package/src/cli/threads/list.ts +10 -10
  181. package/src/cli/threads/messages.ts +11 -7
  182. package/src/cli/threads/respond.ts +11 -7
  183. package/src/cli/threads/send.ts +19 -9
  184. package/src/cli/threads/show.ts +11 -7
  185. package/src/cli/threads/start.ts +18 -13
  186. package/src/cli/threads/unarchive.ts +11 -7
  187. package/src/cli/threads/update.ts +14 -9
  188. package/src/cli/threads/wait.ts +11 -7
  189. package/src/cli/wait-events.ts +1 -1
  190. package/src/config/config.ts +260 -0
  191. package/src/config/credential/cipher-node.ts +40 -0
  192. package/src/config/credential/cipher-web.ts +82 -0
  193. package/src/config/credential/cipher.ts +39 -0
  194. package/src/config/credential/env.ts +13 -0
  195. package/src/config/credential/error.ts +9 -0
  196. package/src/config/credential/index.ts +6 -0
  197. package/src/config/credential/service.test-utils.ts +25 -0
  198. package/src/config/credential/service.test.ts +116 -0
  199. package/src/config/credential/service.ts +205 -0
  200. package/src/config/env/env.test-utils.ts +14 -0
  201. package/src/config/env/env.ts +81 -0
  202. package/src/config/env/index.ts +2 -0
  203. package/src/{layout/base-dir.ts → config/env/layout.ts} +0 -9
  204. package/src/config/environment-name/index.ts +7 -0
  205. package/src/config/environment-name/name.test.ts +66 -0
  206. package/src/config/environment-name/name.ts +49 -0
  207. package/src/config/error.ts +6 -8
  208. package/src/config/index.ts +16 -5
  209. package/src/config/keystore/error.ts +9 -0
  210. package/src/config/keystore/file.ts +96 -0
  211. package/src/config/keystore/index.ts +4 -0
  212. package/src/config/keystore/keyring-node.test.ts +12 -0
  213. package/src/config/keystore/keyring-node.ts +100 -0
  214. package/src/config/keystore/service.test-utils.ts +15 -0
  215. package/src/config/keystore/service.test.ts +18 -0
  216. package/src/config/keystore/service.ts +29 -0
  217. package/src/config/layer.test-utils.ts +45 -0
  218. package/src/config/layer.test.ts +209 -0
  219. package/src/config/paths/index.ts +1 -0
  220. package/src/config/paths/paths.ts +36 -0
  221. package/src/config/persist/file-mode.ts +21 -0
  222. package/src/config/persist/migration.test.ts +71 -0
  223. package/src/config/persist/migration.ts +82 -0
  224. package/src/config/persist/persist.test.ts +113 -0
  225. package/src/config/persist/persist.ts +65 -0
  226. package/src/config/persist/schema.ts +37 -0
  227. package/src/config/platform.test-utils.ts +20 -0
  228. package/src/config/resolve/resolve.test-utils.ts +20 -0
  229. package/src/config/resolve/resolve.test.ts +137 -0
  230. package/src/config/resolve/resolve.ts +104 -0
  231. package/src/config/selection/index.ts +1 -0
  232. package/src/config/selection/resolve.ts +14 -0
  233. package/src/config/selection/service.ts +29 -0
  234. package/src/config/temp-home.test-utils.ts +7 -0
  235. package/src/config/types.ts +49 -0
  236. package/src/config/url/error.ts +8 -0
  237. package/src/config/url/index.ts +7 -0
  238. package/src/config/url/url.test.ts +31 -0
  239. package/src/connection/index.ts +0 -1
  240. package/src/contracts/index.ts +1 -1
  241. package/src/domain/error.ts +20 -1
  242. package/src/domain/helpers.test.ts +1 -1
  243. package/src/domain/helpers.ts +1 -1
  244. package/src/domain/model-config.ts +1 -1
  245. package/src/domain/thread-activities.test.ts +1 -1
  246. package/src/domain/thread-activities.ts +1 -1
  247. package/src/domain/thread-lifecycle.test.ts +1 -1
  248. package/src/domain/thread-lifecycle.ts +1 -1
  249. package/src/effect.test-utils.ts +25 -0
  250. package/src/index.ts +1 -1
  251. package/src/{connection/node.ts → node/connection.ts} +1 -1
  252. package/src/node/index.ts +1 -2
  253. package/src/orchestration/index.ts +0 -1
  254. package/src/orchestration/layer.ts +1 -1
  255. package/src/orchestration/service.ts +1 -1
  256. package/src/rpc/error.ts +2 -2
  257. package/src/rpc/index.ts +2 -3
  258. package/src/rpc/layer.ts +1 -1
  259. package/src/rpc/ws-group.ts +1 -1
  260. package/src/runtime/index.ts +2 -1
  261. package/src/runtime/layer.test-utils.ts +32 -0
  262. package/src/runtime/layer.test.ts +66 -0
  263. package/src/runtime/layer.ts +15 -9
  264. package/src/t3tools/index.ts +1 -1
  265. package/src/types/marked-terminal.d.ts +11 -0
  266. package/dist/layout.js +0 -2
  267. package/dist/scope.js +0 -2
  268. package/dist/src/cli/output-format.d.ts +0 -11
  269. package/dist/src/config/layer.d.ts +0 -22
  270. package/dist/src/config/service.d.ts +0 -22
  271. package/dist/src/environment/layer.d.ts +0 -3
  272. package/dist/src/environment/service.d.ts +0 -12
  273. package/dist/src/layout/index.d.ts +0 -1
  274. package/dist/upstream-t3code/packages/contracts/src/auth.d.ts +0 -441
  275. package/dist/upstream-t3code/packages/contracts/src/baseSchemas.d.ts +0 -38
  276. package/dist/upstream-t3code/packages/contracts/src/desktopBootstrap.d.ts +0 -14
  277. package/dist/upstream-t3code/packages/contracts/src/editor.d.ts +0 -124
  278. package/dist/upstream-t3code/packages/contracts/src/environment.d.ts +0 -64
  279. package/dist/upstream-t3code/packages/contracts/src/environmentHttp.d.ts +0 -762
  280. package/dist/upstream-t3code/packages/contracts/src/filesystem.d.ts +0 -26
  281. package/dist/upstream-t3code/packages/contracts/src/git.d.ts +0 -500
  282. package/dist/upstream-t3code/packages/contracts/src/index.d.ts +0 -25
  283. package/dist/upstream-t3code/packages/contracts/src/ipc.d.ts +0 -490
  284. package/dist/upstream-t3code/packages/contracts/src/keybindings.d.ts +0 -133
  285. package/dist/upstream-t3code/packages/contracts/src/model.d.ts +0 -112
  286. package/dist/upstream-t3code/packages/contracts/src/orchestration.d.ts +0 -6682
  287. package/dist/upstream-t3code/packages/contracts/src/project.d.ts +0 -45
  288. package/dist/upstream-t3code/packages/contracts/src/provider.d.ts +0 -116
  289. package/dist/upstream-t3code/packages/contracts/src/providerInstance.d.ts +0 -99
  290. package/dist/upstream-t3code/packages/contracts/src/providerRuntime.d.ts +0 -4276
  291. package/dist/upstream-t3code/packages/contracts/src/relay.d.ts +0 -1262
  292. package/dist/upstream-t3code/packages/contracts/src/relayClient.d.ts +0 -48
  293. package/dist/upstream-t3code/packages/contracts/src/remoteAccess.d.ts +0 -45
  294. package/dist/upstream-t3code/packages/contracts/src/review.d.ts +0 -37
  295. package/dist/upstream-t3code/packages/contracts/src/rpc.d.ts +0 -8218
  296. package/dist/upstream-t3code/packages/contracts/src/server.d.ts +0 -2291
  297. package/dist/upstream-t3code/packages/contracts/src/settings.d.ts +0 -271
  298. package/dist/upstream-t3code/packages/contracts/src/sourceControl.d.ts +0 -177
  299. package/dist/upstream-t3code/packages/contracts/src/terminal.d.ts +0 -330
  300. package/dist/upstream-t3code/packages/contracts/src/vcs.d.ts +0 -148
  301. package/src/cli/auth-format.ts +0 -45
  302. package/src/cli/confirm.ts +0 -31
  303. package/src/cli/model-format.ts +0 -18
  304. package/src/cli/output-format.ts +0 -45
  305. package/src/cli/project-format.ts +0 -18
  306. package/src/cli/terminal/commands.test.ts +0 -101
  307. package/src/cli/terminal-format.ts +0 -167
  308. package/src/cli/thread-format.ts +0 -128
  309. package/src/config/layer.ts +0 -105
  310. package/src/config/service.ts +0 -26
  311. package/src/environment/layer.ts +0 -12
  312. package/src/environment/service.ts +0 -13
  313. package/src/layout/index.ts +0 -1
  314. /package/dist/src/{scope → cli/scope}/index.d.ts +0 -0
  315. /package/dist/src/config/{url.d.ts → url/url.d.ts} +0 -0
  316. /package/src/{scope → cli/scope}/index.ts +0 -0
  317. /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),