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
@@ -2,7 +2,7 @@ import {
2
2
  AuthAdministrativeScopes,
3
3
  AuthSessionId,
4
4
  type AuthEnvironmentScope,
5
- } from "#t3tools/contracts";
5
+ } from "@t3tools/contracts";
6
6
  import * as Context from "effect/Context";
7
7
  import * as Crypto from "effect/Crypto";
8
8
  import * as DateTime from "effect/DateTime";
@@ -15,7 +15,8 @@ import * as Path from "effect/Path";
15
15
  import * as Predicate from "effect/Predicate";
16
16
  import * as SqlClient from "effect/unstable/sql/SqlClient";
17
17
 
18
- import { Environment } from "../environment/service.ts";
18
+ import { loadT3CliEnv } from "../config/env/env.ts";
19
+ import { CliRuntime } from "../cli/runtime/service.ts";
19
20
  import { SqlClientFactory } from "../sql/service.ts";
20
21
  import {
21
22
  AuthLocalDatabaseError,
@@ -38,7 +39,8 @@ export class T3LocalAuthToken extends Context.Service<
38
39
  export const makeT3LocalAuthToken = Effect.fn("makeT3LocalAuthToken")(function* () {
39
40
  const fs = yield* FileSystem.FileSystem;
40
41
  const path = yield* Path.Path;
41
- const environment = yield* Environment;
42
+ const cliRuntime = yield* CliRuntime;
43
+ const t3CliEnv = yield* loadT3CliEnv;
42
44
  const crypto = yield* Crypto.Crypto;
43
45
  const sqlClientFactory = yield* SqlClientFactory;
44
46
 
@@ -100,14 +102,15 @@ export const makeT3LocalAuthToken = Effect.fn("makeT3LocalAuthToken")(function*
100
102
 
101
103
  function openAuthDatabase(dbPath: string) {
102
104
  return sqlClientFactory.sqliteClient({ filename: dbPath }).pipe(
103
- Effect.catchTag("SqlError", (error) =>
104
- Effect.fail(
105
- new AuthLocalDatabaseError({
106
- operation: "connect",
107
- message: error.message,
108
- }),
109
- ),
110
- ),
105
+ Effect.catchTags({
106
+ SqlError: (error) =>
107
+ Effect.fail(
108
+ new AuthLocalDatabaseError({
109
+ operation: "connect",
110
+ message: error.message,
111
+ }),
112
+ ),
113
+ }),
111
114
  );
112
115
  }
113
116
 
@@ -157,14 +160,15 @@ export const makeT3LocalAuthToken = Effect.fn("makeT3LocalAuthToken")(function*
157
160
  expiresAt: DateTime.formatIso(expiresAt),
158
161
  }).pipe(
159
162
  provideAuthDatabase(input.dbPath),
160
- Effect.catchTag("SqlError", (error) =>
161
- Effect.fail(
162
- new AuthLocalDatabaseError({
163
- operation: Predicate.isTagged(error.reason, "ConnectionError") ? "connect" : "query",
164
- message: error.message,
165
- }),
166
- ),
167
- ),
163
+ Effect.catchTags({
164
+ SqlError: (error) =>
165
+ Effect.fail(
166
+ new AuthLocalDatabaseError({
167
+ operation: Predicate.isTagged(error.reason, "ConnectionError") ? "connect" : "query",
168
+ message: error.message,
169
+ }),
170
+ ),
171
+ }),
168
172
  );
169
173
  return {
170
174
  token,
@@ -185,10 +189,11 @@ export const makeT3LocalAuthToken = Effect.fn("makeT3LocalAuthToken")(function*
185
189
  );
186
190
  }
187
191
 
188
- const baseDir = yield* resolveLocalBaseDir({ baseDir: input.baseDir }).pipe(
189
- Effect.provideService(Environment, environment),
190
- Effect.provideService(Path.Path, path),
191
- );
192
+ const baseDir = yield* resolveLocalBaseDir({
193
+ baseDir: input.baseDir,
194
+ cliRuntime,
195
+ t3CliEnv,
196
+ }).pipe(Effect.provideService(Path.Path, path));
192
197
  const session = yield* issueLocalDatabaseSession({
193
198
  dbPath: path.join(baseDir, "userdata", "state.sqlite"),
194
199
  secretsDir: path.join(baseDir, "userdata", "secrets"),
@@ -0,0 +1,17 @@
1
+ import {
2
+ RemoteEnvironmentAuthFetchError,
3
+ type RemoteEnvironmentAuthError,
4
+ RemoteEnvironmentAuthInvalidJsonError,
5
+ RemoteEnvironmentAuthTimeoutError,
6
+ RemoteEnvironmentAuthUndeclaredStatusError,
7
+ } from "@t3tools/client-runtime/authorization";
8
+ import { EnvironmentHttpCommonError } from "@t3tools/contracts";
9
+ import * as Schema from "effect/Schema";
10
+
11
+ export const RemoteEnvironmentAuthErrorSchema = Schema.Union([
12
+ EnvironmentHttpCommonError,
13
+ Schema.instanceOf(RemoteEnvironmentAuthFetchError),
14
+ Schema.instanceOf(RemoteEnvironmentAuthInvalidJsonError),
15
+ Schema.instanceOf(RemoteEnvironmentAuthTimeoutError),
16
+ Schema.instanceOf(RemoteEnvironmentAuthUndeclaredStatusError),
17
+ ]) satisfies Schema.Schema<RemoteEnvironmentAuthError>;
@@ -0,0 +1,200 @@
1
+ import "vite-plus/test/config";
2
+
3
+ import * as Effect from "effect/Effect";
4
+ import * as Exit from "effect/Exit";
5
+ import * as FileSystem from "effect/FileSystem";
6
+ import * as Path from "effect/Path";
7
+ import * as Schema from "effect/Schema";
8
+ import { assert, describe, it } from "@effect/vitest";
9
+
10
+ import { T3Auth } from "./service.ts";
11
+ import { StoredConfigV2FileJson } from "../config/persist/schema.ts";
12
+ import { ConfigPlatformLayer } from "../config/platform.test-utils.ts";
13
+ import { makeTempHomeScoped } from "../config/temp-home.test-utils.ts";
14
+ import { t3AuthDepsLayer } from "./layer.test-utils.ts";
15
+
16
+ describe("T3Auth persistence", () => {
17
+ it.effect("fails to persist a duplicate environment without allowReplace", () =>
18
+ Effect.gen(function* () {
19
+ return yield* makeTempHomeScoped("t3cli-auth-");
20
+ }).pipe(
21
+ Effect.flatMap((homeDir) =>
22
+ Effect.gen(function* () {
23
+ const auth = yield* T3Auth;
24
+ yield* auth.persistEnvironment({
25
+ name: "work",
26
+ url: "https://work.example",
27
+ token: "work-token",
28
+ local: false,
29
+ allowReplace: true,
30
+ });
31
+ const result = yield* auth
32
+ .persistEnvironment({
33
+ name: "work",
34
+ url: "https://work.example",
35
+ token: "work-token-2",
36
+ local: false,
37
+ allowReplace: false,
38
+ })
39
+ .pipe(Effect.exit);
40
+ assert.equal(Exit.isFailure(result), true);
41
+ }).pipe(Effect.provide(t3AuthDepsLayer(homeDir))),
42
+ ),
43
+ Effect.provide(ConfigPlatformLayer),
44
+ Effect.scoped,
45
+ ),
46
+ );
47
+
48
+ it.effect("does not change default when replace is used for a new environment", () =>
49
+ Effect.gen(function* () {
50
+ return yield* makeTempHomeScoped("t3cli-auth-");
51
+ }).pipe(
52
+ Effect.flatMap((homeDir) =>
53
+ Effect.gen(function* () {
54
+ const auth = yield* T3Auth;
55
+ yield* auth.persistEnvironment({
56
+ name: "home",
57
+ url: "https://home.example",
58
+ token: "home-token",
59
+ local: false,
60
+ allowReplace: true,
61
+ });
62
+ yield* auth.persistEnvironment({
63
+ name: "work",
64
+ url: "https://work.example",
65
+ token: "work-token",
66
+ local: false,
67
+ replace: true,
68
+ allowReplace: true,
69
+ });
70
+ const listed = yield* auth.listEnvironments();
71
+ assert.equal(listed.find((environment) => environment.name === "home")?.default, true);
72
+ assert.equal(listed.find((environment) => environment.name === "work")?.default, false);
73
+ }).pipe(Effect.provide(t3AuthDepsLayer(homeDir))),
74
+ ),
75
+ Effect.provide(ConfigPlatformLayer),
76
+ Effect.scoped,
77
+ ),
78
+ );
79
+
80
+ it.effect("promotes default when replacing an existing environment with replace", () =>
81
+ Effect.gen(function* () {
82
+ return yield* makeTempHomeScoped("t3cli-auth-");
83
+ }).pipe(
84
+ Effect.flatMap((homeDir) =>
85
+ Effect.gen(function* () {
86
+ const auth = yield* T3Auth;
87
+ yield* auth.persistEnvironment({
88
+ name: "home",
89
+ url: "https://home.example",
90
+ token: "home-token",
91
+ local: false,
92
+ allowReplace: true,
93
+ });
94
+ yield* auth.persistEnvironment({
95
+ name: "work",
96
+ url: "https://work.example",
97
+ token: "work-token",
98
+ local: false,
99
+ allowReplace: true,
100
+ });
101
+ yield* auth.persistEnvironment({
102
+ name: "work",
103
+ url: "https://work.example",
104
+ token: "work-token-2",
105
+ local: false,
106
+ replace: true,
107
+ allowReplace: true,
108
+ });
109
+ const listed = yield* auth.listEnvironments();
110
+ assert.equal(listed.find((environment) => environment.name === "work")?.default, true);
111
+ }).pipe(Effect.provide(t3AuthDepsLayer(homeDir))),
112
+ ),
113
+ Effect.provide(ConfigPlatformLayer),
114
+ Effect.scoped,
115
+ ),
116
+ );
117
+
118
+ it.effect("lists environments without decrypting tokens", () =>
119
+ Effect.gen(function* () {
120
+ return yield* makeTempHomeScoped("t3cli-auth-");
121
+ }).pipe(
122
+ Effect.flatMap((homeDir) =>
123
+ Effect.gen(function* () {
124
+ const fs = yield* FileSystem.FileSystem;
125
+ const path = yield* Path.Path;
126
+ const configPath = path.join(homeDir, ".config", "t3cli", "config.json");
127
+
128
+ yield* Effect.gen(function* () {
129
+ const auth = yield* T3Auth;
130
+ yield* auth.persistEnvironment({
131
+ name: "home",
132
+ url: "https://home.example",
133
+ token: "home-token",
134
+ local: false,
135
+ allowReplace: true,
136
+ });
137
+ yield* auth.persistEnvironment({
138
+ name: "work",
139
+ url: "https://work.example",
140
+ token: "work-token",
141
+ local: false,
142
+ allowReplace: true,
143
+ });
144
+ }).pipe(Effect.provide(t3AuthDepsLayer(homeDir)));
145
+
146
+ const raw = yield* fs.readFileString(configPath);
147
+ const parsed = yield* Schema.decodeUnknownEffect(StoredConfigV2FileJson)(raw);
148
+ const homeEnvironment = parsed.environments.home;
149
+ assert.isDefined(homeEnvironment);
150
+ const corrupted = yield* Schema.encodeEffect(StoredConfigV2FileJson)({
151
+ ...parsed,
152
+ environments: {
153
+ ...parsed.environments,
154
+ home: {
155
+ ...homeEnvironment,
156
+ token: {
157
+ ...homeEnvironment.token,
158
+ tag: "AAAAAAAAAAAAAAAAAAAAAA==",
159
+ },
160
+ },
161
+ },
162
+ });
163
+ yield* fs.writeFileString(configPath, `${corrupted}\n`, { mode: 0o600 });
164
+
165
+ yield* Effect.gen(function* () {
166
+ const auth = yield* T3Auth;
167
+ const listed = yield* auth.listEnvironments();
168
+ assert.equal(listed.length, 2);
169
+ assert.equal(listed.find((environment) => environment.name === "home")?.active, true);
170
+ }).pipe(Effect.provide(t3AuthDepsLayer(homeDir)));
171
+ }),
172
+ ),
173
+ Effect.provide(ConfigPlatformLayer),
174
+ Effect.scoped,
175
+ ),
176
+ );
177
+
178
+ it.effect("resolves unpair target from encrypted default metadata", () =>
179
+ Effect.gen(function* () {
180
+ return yield* makeTempHomeScoped("t3cli-auth-");
181
+ }).pipe(
182
+ Effect.flatMap((homeDir) =>
183
+ Effect.gen(function* () {
184
+ const auth = yield* T3Auth;
185
+ yield* auth.persistEnvironment({
186
+ name: "home",
187
+ url: "https://home.example",
188
+ token: "home-token",
189
+ local: false,
190
+ allowReplace: true,
191
+ });
192
+ const target = yield* auth.resolveUnpairTarget({});
193
+ assert.equal(target, "home");
194
+ }).pipe(Effect.provide(t3AuthDepsLayer(homeDir))),
195
+ ),
196
+ Effect.provide(ConfigPlatformLayer),
197
+ Effect.scoped,
198
+ ),
199
+ );
200
+ });
@@ -1,9 +1,19 @@
1
1
  import * as Context from "effect/Context";
2
2
  import type * as Effect from "effect/Effect";
3
3
 
4
- import type { AuthSessionState, AuthWebSocketTicketResult } from "./schema.ts";
4
+ import type { AuthWebSocketTicketResult } from "./schema.ts";
5
5
  import type { AuthError } from "./error.ts";
6
- import type { AuthConfigInput, LocalAuthInput, LocalAuthResult, PairResult } from "./type.ts";
6
+ import type {
7
+ AuthConfigInput,
8
+ AuthEnvironmentListItem,
9
+ AuthStatusResult,
10
+ AuthUnpairResult,
11
+ AuthUseResult,
12
+ LocalAuthInput,
13
+ LocalAuthResult,
14
+ PairResult,
15
+ PersistEnvironmentInput,
16
+ } from "./type.ts";
7
17
 
8
18
  export class T3Auth extends Context.Service<
9
19
  T3Auth,
@@ -11,7 +21,21 @@ export class T3Auth extends Context.Service<
11
21
  readonly pair: (value: string) => Effect.Effect<PairResult, AuthError>;
12
22
  readonly local: (input: LocalAuthInput) => Effect.Effect<LocalAuthResult, AuthError>;
13
23
  readonly writeConfig: (input: AuthConfigInput) => Effect.Effect<void, AuthError>;
14
- readonly status: () => Effect.Effect<AuthSessionState, AuthError>;
24
+ readonly persistEnvironment: (
25
+ input: PersistEnvironmentInput,
26
+ ) => Effect.Effect<string, AuthError>;
27
+ readonly environmentExists: (name: string) => Effect.Effect<boolean, AuthError>;
28
+ readonly defaultNameFromUrl: (url: string) => Effect.Effect<string, AuthError>;
29
+ readonly defaultNameForLocal: () => Effect.Effect<string, AuthError>;
30
+ readonly listEnvironments: () => Effect.Effect<readonly AuthEnvironmentListItem[], AuthError>;
31
+ readonly useEnvironment: (name: string) => Effect.Effect<AuthUseResult, AuthError>;
32
+ readonly unpairEnvironment: (input: {
33
+ readonly name: string;
34
+ }) => Effect.Effect<AuthUnpairResult, AuthError>;
35
+ readonly resolveUnpairTarget: (input: {
36
+ readonly name?: string;
37
+ }) => Effect.Effect<string, AuthError>;
38
+ readonly status: () => Effect.Effect<AuthStatusResult, AuthError>;
15
39
  readonly issueWebSocketTicket: () => Effect.Effect<AuthWebSocketTicketResult, AuthError>;
16
40
  }
17
41
  >()("t3cli/T3Auth") {}
@@ -1,23 +1,19 @@
1
1
  import {
2
- AuthAccessTokenType,
3
- AuthEnvironmentBootstrapTokenType,
4
- AuthTokenExchangeGrantType,
5
- } from "#t3tools/contracts";
2
+ bootstrapRemoteBearerSession,
3
+ fetchRemoteSessionState,
4
+ issueRemoteWebSocketTicket,
5
+ } from "@t3tools/client-runtime/authorization";
6
6
  import * as Context from "effect/Context";
7
7
  import * as DateTime from "effect/DateTime";
8
8
  import * as Effect from "effect/Effect";
9
9
  import * as Layer from "effect/Layer";
10
- import { HttpClient, HttpClientError, HttpClientRequest } from "effect/unstable/http";
10
+ import { HttpClient } from "effect/unstable/http";
11
11
 
12
- import { toHttpEndpointUrl } from "../config/url.ts";
13
12
  import { AuthTransportError } from "./error.ts";
14
13
  import {
15
14
  type AuthBearerBootstrapResult,
16
- decodeAuthAccessTokenResult,
17
15
  type AuthSessionState,
18
- decodeAuthSessionState,
19
16
  type AuthWebSocketTicketResult,
20
- decodeAuthWebSocketTicketResult,
21
17
  } from "./schema.ts";
22
18
 
23
19
  export type AuthTransportConnection = {
@@ -42,53 +38,25 @@ export class T3AuthTransport extends Context.Service<
42
38
  >()("t3cli/T3AuthTransport") {}
43
39
 
44
40
  const makeT3AuthTransport = Effect.fn("makeT3AuthTransport")(function* () {
45
- const client = HttpClient.filterStatusOk(yield* HttpClient.HttpClient);
41
+ const httpClient = yield* HttpClient.HttpClient;
46
42
 
47
43
  const bootstrapBearer = Effect.fn("AuthTransport.bootstrapBearer")(function* (input: {
48
44
  readonly baseUrl: string;
49
45
  readonly credential: string;
50
46
  }) {
51
- const url = yield* makeHttpEndpointUrl(input.baseUrl, "/oauth/token");
52
- const request = HttpClientRequest.post(url).pipe(
53
- HttpClientRequest.acceptJson,
54
- HttpClientRequest.bodyUrlParams({
55
- grant_type: AuthTokenExchangeGrantType,
56
- subject_token: input.credential,
57
- subject_token_type: AuthEnvironmentBootstrapTokenType,
58
- requested_token_type: AuthAccessTokenType,
59
- }),
60
- );
61
- const response = yield* client.execute(request).pipe(
62
- Effect.catchTags({
63
- HttpClientError: (error) =>
64
- Effect.fail(
65
- new AuthTransportError({
66
- message: "auth request failed",
67
- cause: HttpClientError.HttpClientErrorSchema.fromHttpClientError(error),
68
- }),
69
- ),
70
- }),
71
- );
72
- const result = yield* response.json.pipe(
73
- Effect.flatMap(decodeAuthAccessTokenResult),
74
- Effect.catchTags({
75
- HttpClientError: (error) =>
76
- Effect.fail(
77
- new AuthTransportError({
78
- message: "auth request failed",
79
- cause: HttpClientError.HttpClientErrorSchema.fromHttpClientError(error),
80
- }),
81
- ),
82
- SchemaError: (error) =>
83
- Effect.fail(
84
- new AuthTransportError({ message: "auth response decode failed", cause: error }),
85
- ),
86
- }),
47
+ const result = yield* bootstrapRemoteBearerSession({
48
+ httpBaseUrl: input.baseUrl,
49
+ credential: input.credential,
50
+ }).pipe(
51
+ Effect.provideService(HttpClient.HttpClient, httpClient),
52
+ Effect.mapError(
53
+ (error) => new AuthTransportError({ message: "auth request failed", cause: error }),
54
+ ),
87
55
  );
88
56
  const now = yield* DateTime.now;
89
57
  return {
90
58
  authenticated: true,
91
- role: result.scope.split(/\s+/u).includes("access:write") ? "owner" : "client",
59
+ role: inferRole(result.scope),
92
60
  sessionMethod: "bearer-access-token",
93
61
  expiresAt: DateTime.formatIso(DateTime.add(now, { seconds: result.expires_in })),
94
62
  sessionToken: result.access_token,
@@ -98,69 +66,41 @@ const makeT3AuthTransport = Effect.fn("makeT3AuthTransport")(function* () {
98
66
  const getSession = Effect.fn("AuthTransport.getSession")(function* (
99
67
  connection: AuthTransportConnection,
100
68
  ) {
101
- const url = yield* makeHttpEndpointUrl(connection.url, "/api/auth/session");
102
- const request = HttpClientRequest.get(url).pipe(authenticatedRequest(connection));
103
- const response = yield* client.execute(request).pipe(
104
- Effect.catchTags({
105
- HttpClientError: (error) =>
106
- Effect.fail(
107
- new AuthTransportError({
108
- message: "auth request failed",
109
- cause: HttpClientError.HttpClientErrorSchema.fromHttpClientError(error),
110
- }),
111
- ),
112
- }),
113
- );
114
- return yield* response.json.pipe(
115
- Effect.flatMap(decodeAuthSessionState),
116
- Effect.catchTags({
117
- HttpClientError: (error) =>
118
- Effect.fail(
119
- new AuthTransportError({
120
- message: "auth request failed",
121
- cause: HttpClientError.HttpClientErrorSchema.fromHttpClientError(error),
122
- }),
123
- ),
124
- SchemaError: (error) =>
125
- Effect.fail(
126
- new AuthTransportError({ message: "auth response decode failed", cause: error }),
127
- ),
128
- }),
69
+ const result = yield* fetchRemoteSessionState({
70
+ httpBaseUrl: connection.url,
71
+ bearerToken: connection.token,
72
+ }).pipe(
73
+ Effect.provideService(HttpClient.HttpClient, httpClient),
74
+ Effect.mapError(
75
+ (error) => new AuthTransportError({ message: "auth request failed", cause: error }),
76
+ ),
129
77
  );
78
+ return {
79
+ authenticated: result.authenticated,
80
+ ...(result.scopes !== undefined ? { role: inferRole(result.scopes.join(" ")) } : {}),
81
+ ...(result.sessionMethod !== undefined ? { sessionMethod: result.sessionMethod } : {}),
82
+ ...(result.expiresAt !== undefined
83
+ ? { expiresAt: DateTime.formatIso(result.expiresAt) }
84
+ : {}),
85
+ } satisfies AuthSessionState;
130
86
  });
131
87
 
132
88
  const issueWebSocketTicket = Effect.fn("AuthTransport.issueWebSocketTicket")(function* (
133
89
  connection: AuthTransportConnection,
134
90
  ) {
135
- const url = yield* makeHttpEndpointUrl(connection.url, "/api/auth/websocket-ticket");
136
- const request = HttpClientRequest.post(url).pipe(authenticatedRequest(connection));
137
- const response = yield* client.execute(request).pipe(
138
- Effect.catchTags({
139
- HttpClientError: (error) =>
140
- Effect.fail(
141
- new AuthTransportError({
142
- message: "auth request failed",
143
- cause: HttpClientError.HttpClientErrorSchema.fromHttpClientError(error),
144
- }),
145
- ),
146
- }),
147
- );
148
- return yield* response.json.pipe(
149
- Effect.flatMap(decodeAuthWebSocketTicketResult),
150
- Effect.catchTags({
151
- HttpClientError: (error) =>
152
- Effect.fail(
153
- new AuthTransportError({
154
- message: "auth request failed",
155
- cause: HttpClientError.HttpClientErrorSchema.fromHttpClientError(error),
156
- }),
157
- ),
158
- SchemaError: (error) =>
159
- Effect.fail(
160
- new AuthTransportError({ message: "auth response decode failed", cause: error }),
161
- ),
162
- }),
91
+ const result = yield* issueRemoteWebSocketTicket({
92
+ httpBaseUrl: connection.url,
93
+ bearerToken: connection.token,
94
+ }).pipe(
95
+ Effect.provideService(HttpClient.HttpClient, httpClient),
96
+ Effect.mapError(
97
+ (error) => new AuthTransportError({ message: "auth request failed", cause: error }),
98
+ ),
163
99
  );
100
+ return {
101
+ ticket: result.ticket,
102
+ expiresAt: DateTime.formatIso(result.expiresAt),
103
+ } satisfies AuthWebSocketTicketResult;
164
104
  });
165
105
 
166
106
  return {
@@ -172,15 +112,6 @@ const makeT3AuthTransport = Effect.fn("makeT3AuthTransport")(function* () {
172
112
 
173
113
  export const T3AuthTransportLive = Layer.effect(T3AuthTransport, makeT3AuthTransport());
174
114
 
175
- const authenticatedRequest =
176
- (connection: AuthTransportConnection) => (request: HttpClientRequest.HttpClientRequest) =>
177
- request.pipe(HttpClientRequest.acceptJson, HttpClientRequest.bearerToken(connection.token));
178
-
179
- function makeHttpEndpointUrl(baseUrl: string, path: string) {
180
- return toHttpEndpointUrl(baseUrl, path).pipe(
181
- Effect.catchTags({
182
- UrlError: (error) =>
183
- Effect.fail(new AuthTransportError({ message: "auth request failed", cause: error })),
184
- }),
185
- );
115
+ function inferRole(scope: string): AuthBearerBootstrapResult["role"] {
116
+ return scope.split(/\s+/u).includes("access:write") ? "owner" : "client";
186
117
  }
package/src/auth/type.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { AuthBearerBootstrapResult } from "./schema.ts";
2
+ import type { AuthSessionState } from "./schema.ts";
2
3
 
3
4
  export type AuthSessionRole = AuthBearerBootstrapResult["role"];
4
5
 
@@ -8,9 +9,44 @@ export type PairingUrl = {
8
9
  };
9
10
 
10
11
  export type AuthConfigInput = {
12
+ readonly name: string;
11
13
  readonly url: string;
12
14
  readonly token: string;
13
- readonly local?: boolean;
15
+ readonly local: boolean;
16
+ readonly makeDefault?: boolean;
17
+ };
18
+
19
+ export type PersistEnvironmentInput = {
20
+ readonly name: string;
21
+ readonly url: string;
22
+ readonly token: string;
23
+ readonly local: boolean;
24
+ readonly replace?: boolean;
25
+ readonly allowReplace: boolean;
26
+ };
27
+
28
+ export type AuthResolvedConfig = {
29
+ readonly url: string;
30
+ readonly token: string;
31
+ readonly source: "env" | "config";
32
+ readonly local: boolean;
33
+ readonly environment?: string;
34
+ };
35
+
36
+ export type AuthEnvironmentSummary = {
37
+ readonly name: string;
38
+ readonly url: string;
39
+ readonly local: boolean;
40
+ readonly default: boolean;
41
+ };
42
+
43
+ export type AuthStatusResult = {
44
+ readonly config: AuthResolvedConfig;
45
+ readonly session: AuthSessionState;
46
+ };
47
+
48
+ export type AuthEnvironmentListItem = AuthEnvironmentSummary & {
49
+ readonly active: boolean;
14
50
  };
15
51
 
16
52
  export type PairResult = {
@@ -51,3 +87,13 @@ export type LocalAuthResult = {
51
87
  readonly source: "local";
52
88
  readonly baseDir: string;
53
89
  };
90
+
91
+ export type AuthUseResult = {
92
+ readonly name: string;
93
+ readonly default: true;
94
+ };
95
+
96
+ export type AuthUnpairResult = {
97
+ readonly name: string;
98
+ readonly removed: true;
99
+ };