t3code-cli 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -11
- package/dist/application.js +2 -2
- package/dist/auth.js +1 -1
- package/dist/bin.js +86754 -2675
- package/dist/cli.js +15 -2
- package/dist/config.js +120 -2
- package/dist/connection.js +2 -12
- package/dist/index.js +1 -1
- package/dist/node.js +6 -0
- package/dist/orchestration.js +2 -2
- package/dist/rpc.js +2 -2
- package/dist/runtime.js +2 -2
- package/dist/shared.js +43850 -38040
- package/dist/src/application/actions.d.ts +25 -0
- package/dist/src/application/index.d.ts +3 -2
- package/dist/src/application/layer.d.ts +99 -842
- package/dist/src/application/model-selection.d.ts +17 -2
- package/dist/src/application/models.d.ts +1 -87
- package/dist/src/application/project-commands.d.ts +31 -2
- package/dist/src/application/projects.d.ts +15 -137
- package/dist/src/application/service.d.ts +147 -5
- package/dist/src/application/shell-sequence.d.ts +5 -4
- package/dist/src/application/terminals.d.ts +66 -0
- package/dist/src/application/thread-commands.d.ts +48 -1
- package/dist/src/application/thread-update.d.ts +8 -0
- package/dist/src/application/thread-wait.d.ts +4 -6
- package/dist/src/application/threads.d.ts +36 -14
- package/dist/src/auth/error.d.ts +2 -1
- package/dist/src/auth/index.d.ts +1 -1
- package/dist/src/auth/layer.d.ts +34 -6
- package/dist/src/auth/local-base-dir.d.ts +5 -2
- package/dist/src/auth/local-origin.d.ts +3 -3
- package/dist/src/auth/local-token.d.ts +3 -3
- package/dist/src/auth/service.d.ts +15 -3
- package/dist/src/auth/type.d.ts +40 -1
- package/dist/src/cli/env/flag.d.ts +2 -0
- package/dist/src/cli/env/index.d.ts +2 -0
- package/dist/src/cli/env/selection-layer.d.ts +3 -0
- package/dist/src/cli/flags.d.ts +5 -0
- package/dist/src/cli/format/index.d.ts +1 -0
- package/dist/src/cli/format/output.d.ts +12 -0
- package/dist/src/cli/index.d.ts +4 -0
- package/dist/src/cli/runtime/index.d.ts +1 -0
- package/dist/src/cli/runtime/service.d.ts +16 -0
- package/dist/src/{scope → cli/scope}/resolve.d.ts +5 -4
- package/dist/src/config/config.d.ts +49 -0
- package/dist/src/config/credential/cipher-node.d.ts +3 -0
- package/dist/src/config/credential/cipher-web.d.ts +3 -0
- package/dist/src/config/credential/cipher.d.ts +29 -0
- package/dist/src/config/credential/env.d.ts +3 -0
- package/dist/src/config/credential/error.d.ts +8 -0
- package/dist/src/config/credential/index.d.ts +6 -0
- package/dist/src/config/credential/service.d.ts +34 -0
- package/dist/src/config/env/env.d.ts +55 -0
- package/dist/src/config/env/index.d.ts +2 -0
- package/dist/src/{layout/base-dir.d.ts → config/env/layout.d.ts} +0 -1
- package/dist/src/config/environment-name/index.d.ts +1 -0
- package/dist/src/config/environment-name/name.d.ts +10 -0
- package/dist/src/config/error.d.ts +4 -11
- package/dist/src/config/index.d.ts +10 -5
- package/dist/src/config/keystore/error.d.ts +8 -0
- package/dist/src/config/keystore/file.d.ts +9 -0
- package/dist/src/config/keystore/index.d.ts +4 -0
- package/dist/src/config/keystore/keyring-node.d.ts +4 -0
- package/dist/src/config/keystore/service.d.ts +33 -0
- package/dist/src/config/paths/index.d.ts +1 -0
- package/dist/src/config/paths/paths.d.ts +6 -0
- package/dist/src/config/persist/file-mode.d.ts +4 -0
- package/dist/src/config/persist/migration.d.ts +68 -0
- package/dist/src/config/persist/persist.d.ts +40 -0
- package/dist/src/config/persist/schema.d.ts +68 -0
- package/dist/src/config/resolve/resolve.d.ts +40 -0
- package/dist/src/config/selection/index.d.ts +1 -0
- package/dist/src/config/selection/resolve.d.ts +4 -0
- package/dist/src/config/selection/service.d.ts +10 -0
- package/dist/src/config/types.d.ts +36 -0
- package/dist/src/config/url/error.d.ts +10 -0
- package/dist/src/config/url/index.d.ts +2 -0
- package/dist/src/connection/index.d.ts +0 -1
- package/dist/src/contracts/index.d.ts +1 -1
- package/dist/src/domain/error.d.ts +27 -1
- package/dist/src/domain/helpers.d.ts +8 -2
- package/dist/src/domain/model-config.d.ts +30 -1
- package/dist/src/domain/thread-activities.d.ts +1 -1
- package/dist/src/domain/thread-lifecycle.d.ts +3 -2
- package/dist/src/index.d.ts +1 -2
- package/dist/src/{connection/node.d.ts → node/connection.d.ts} +1 -1
- package/dist/src/node/index.d.ts +1 -0
- package/dist/src/orchestration/index.d.ts +1 -1
- package/dist/src/orchestration/layer.d.ts +120 -829
- package/dist/src/orchestration/service.d.ts +2 -1
- package/dist/src/rpc/error.d.ts +5 -3
- package/dist/src/rpc/index.d.ts +5 -1
- package/dist/src/rpc/layer.d.ts +1166 -40
- package/dist/src/rpc/operation.d.ts +6280 -0
- package/dist/src/rpc/ws-group.d.ts +372 -27
- package/dist/src/runtime/index.d.ts +1 -2
- package/dist/src/runtime/layer.d.ts +12 -8
- package/dist/src/t3tools/index.d.ts +1 -1
- package/dist/t3tools.js +2 -2
- package/package.json +27 -19
- package/src/application/actions.test.ts +328 -0
- package/src/application/actions.ts +475 -0
- package/src/application/index.ts +32 -2
- package/src/application/layer.ts +43 -5
- package/src/application/model-selection.ts +70 -4
- package/src/application/models.ts +14 -14
- package/src/application/project-commands.ts +38 -5
- package/src/application/projects.ts +49 -20
- package/src/application/service.ts +275 -78
- package/src/application/shell-sequence.ts +6 -8
- package/src/application/terminals.ts +208 -0
- package/src/application/thread-commands.test.ts +43 -0
- package/src/application/thread-commands.ts +82 -1
- package/src/application/thread-update.ts +72 -0
- package/src/application/thread-wait.ts +47 -48
- package/src/application/threads.test.ts +172 -0
- package/src/application/threads.ts +128 -45
- package/src/auth/error.ts +2 -1
- package/src/auth/index.ts +7 -0
- package/src/auth/layer.test-utils.ts +34 -0
- package/src/auth/layer.ts +116 -34
- package/src/auth/local-base-dir.ts +13 -6
- package/src/auth/local-origin.ts +10 -7
- package/src/auth/local-token.ts +28 -23
- package/src/auth/service.test.ts +200 -0
- package/src/auth/service.ts +27 -3
- package/src/auth/transport.ts +2 -2
- package/src/auth/type.ts +47 -1
- package/src/bin.ts +26 -6
- package/src/cli/action-format.ts +110 -0
- package/src/cli/action.ts +398 -0
- package/src/cli/app.ts +8 -0
- package/src/cli/auth.ts +76 -23
- package/src/cli/env/flag.ts +8 -0
- package/src/cli/env/index.ts +2 -0
- package/src/cli/env/selection-layer.test.ts +49 -0
- package/src/cli/env/selection-layer.ts +27 -0
- package/src/cli/env.ts +107 -0
- package/src/cli/error.ts +26 -3
- package/src/cli/extra-args.test.ts +32 -0
- package/src/cli/extra-args.ts +22 -0
- package/src/cli/flags.ts +26 -1
- package/src/cli/format/auth.ts +92 -0
- package/src/cli/format/human.ts +131 -0
- package/src/cli/format/index.ts +12 -0
- package/src/cli/format/model.ts +29 -0
- package/src/cli/format/output.ts +51 -0
- package/src/cli/format/project.ts +32 -0
- package/src/cli/format/terminal.ts +87 -0
- package/src/cli/format/thread.test.ts +34 -0
- package/src/cli/format/thread.ts +185 -0
- package/src/cli/index.ts +20 -0
- package/src/cli/input/layer.ts +20 -0
- package/src/cli/input/service.ts +1 -0
- package/src/cli/interaction/confirm.ts +64 -0
- package/src/cli/interaction/self-action.ts +36 -0
- package/src/cli/message-input.ts +1 -1
- package/src/cli/model.ts +9 -5
- package/src/cli/project.ts +15 -8
- package/src/cli/projects/delete.ts +51 -0
- package/src/cli/require.ts +9 -7
- package/src/cli/runtime/index.ts +1 -0
- package/src/cli/runtime/service.ts +28 -0
- package/src/{scope → cli/scope}/resolve.ts +11 -9
- package/src/cli/terminal/attach.ts +29 -0
- package/src/cli/terminal/create.ts +55 -0
- package/src/cli/terminal/destroy.ts +62 -0
- package/src/cli/terminal/encoding.test.ts +63 -0
- package/src/cli/terminal/encoding.ts +23 -0
- package/src/cli/terminal/error.ts +14 -0
- package/src/cli/terminal/io-node-layer.ts +82 -0
- package/src/cli/terminal/io-service.ts +25 -0
- package/src/cli/terminal/list.ts +36 -0
- package/src/cli/terminal/read.test-utils.ts +48 -0
- package/src/cli/terminal/read.test.ts +35 -0
- package/src/cli/terminal/read.ts +99 -0
- package/src/cli/terminal/scope.ts +35 -0
- package/src/cli/terminal/shared.ts +250 -0
- package/src/cli/terminal/stream.ts +61 -0
- package/src/cli/terminal/wait.test.ts +148 -0
- package/src/cli/terminal/wait.ts +223 -0
- package/src/cli/terminal/write.test.ts +34 -0
- package/src/cli/terminal/write.ts +133 -0
- package/src/cli/terminal.ts +26 -0
- package/src/cli/thread.ts +8 -0
- package/src/cli/threads/approve.ts +11 -7
- package/src/cli/threads/archive.ts +23 -25
- package/src/cli/threads/callback.ts +6 -4
- package/src/cli/threads/delete.ts +67 -0
- package/src/cli/threads/interrupt.ts +57 -0
- package/src/cli/threads/list.test.ts +71 -0
- package/src/cli/threads/list.ts +25 -13
- package/src/cli/threads/messages.ts +11 -7
- package/src/cli/threads/respond.ts +11 -7
- package/src/cli/threads/send.ts +39 -9
- package/src/cli/threads/show.ts +11 -7
- package/src/cli/threads/start.ts +18 -13
- package/src/cli/threads/unarchive.ts +48 -0
- package/src/cli/threads/update.ts +155 -0
- package/src/cli/threads/wait.ts +11 -7
- package/src/cli/wait-events.ts +1 -1
- package/src/config/config.ts +260 -0
- package/src/config/credential/cipher-node.ts +40 -0
- package/src/config/credential/cipher-web.ts +82 -0
- package/src/config/credential/cipher.ts +39 -0
- package/src/config/credential/env.ts +13 -0
- package/src/config/credential/error.ts +9 -0
- package/src/config/credential/index.ts +6 -0
- package/src/config/credential/service.test-utils.ts +25 -0
- package/src/config/credential/service.test.ts +116 -0
- package/src/config/credential/service.ts +205 -0
- package/src/config/env/env.test-utils.ts +14 -0
- package/src/config/env/env.ts +81 -0
- package/src/config/env/index.ts +2 -0
- package/src/{layout/base-dir.ts → config/env/layout.ts} +0 -9
- package/src/config/environment-name/index.ts +7 -0
- package/src/config/environment-name/name.test.ts +66 -0
- package/src/config/environment-name/name.ts +49 -0
- package/src/config/error.ts +6 -8
- package/src/config/index.ts +16 -5
- package/src/config/keystore/error.ts +9 -0
- package/src/config/keystore/file.ts +96 -0
- package/src/config/keystore/index.ts +4 -0
- package/src/config/keystore/keyring-node.test.ts +12 -0
- package/src/config/keystore/keyring-node.ts +100 -0
- package/src/config/keystore/service.test-utils.ts +15 -0
- package/src/config/keystore/service.test.ts +18 -0
- package/src/config/keystore/service.ts +29 -0
- package/src/config/layer.test-utils.ts +45 -0
- package/src/config/layer.test.ts +209 -0
- package/src/config/paths/index.ts +1 -0
- package/src/config/paths/paths.ts +36 -0
- package/src/config/persist/file-mode.ts +21 -0
- package/src/config/persist/migration.test.ts +71 -0
- package/src/config/persist/migration.ts +82 -0
- package/src/config/persist/persist.test.ts +113 -0
- package/src/config/persist/persist.ts +65 -0
- package/src/config/persist/schema.ts +37 -0
- package/src/config/platform.test-utils.ts +20 -0
- package/src/config/resolve/resolve.test-utils.ts +20 -0
- package/src/config/resolve/resolve.test.ts +137 -0
- package/src/config/resolve/resolve.ts +104 -0
- package/src/config/selection/index.ts +1 -0
- package/src/config/selection/resolve.ts +14 -0
- package/src/config/selection/service.ts +29 -0
- package/src/config/temp-home.test-utils.ts +7 -0
- package/src/config/types.ts +49 -0
- package/src/config/url/error.ts +8 -0
- package/src/config/url/index.ts +7 -0
- package/src/config/url/url.test.ts +31 -0
- package/src/connection/index.ts +0 -1
- package/src/contracts/index.ts +1 -1
- package/src/domain/error.ts +39 -1
- package/src/domain/helpers.test.ts +1 -1
- package/src/domain/helpers.ts +1 -1
- package/src/domain/model-config.ts +5 -1
- package/src/domain/thread-activities.test.ts +1 -1
- package/src/domain/thread-activities.ts +1 -1
- package/src/domain/thread-lifecycle.test.ts +1 -1
- package/src/domain/thread-lifecycle.ts +15 -1
- package/src/effect.test-utils.ts +25 -0
- package/src/index.ts +1 -1
- package/src/{connection/node.ts → node/connection.ts} +1 -1
- package/src/node/index.ts +1 -0
- package/src/orchestration/index.ts +1 -1
- package/src/orchestration/layer.ts +34 -86
- package/src/orchestration/service.ts +5 -1
- package/src/rpc/error.ts +12 -2
- package/src/rpc/index.ts +5 -1
- package/src/rpc/layer.ts +1 -1
- package/src/rpc/operation.ts +83 -0
- package/src/rpc/ws-group.ts +25 -1
- package/src/runtime/index.ts +2 -1
- package/src/runtime/layer.test-utils.ts +32 -0
- package/src/runtime/layer.test.ts +66 -0
- package/src/runtime/layer.ts +22 -11
- package/src/t3tools/index.ts +1 -1
- package/src/types/marked-terminal.d.ts +11 -0
- package/dist/layout.js +0 -2
- package/dist/scope.js +0 -2
- package/dist/src/cli/output-format.d.ts +0 -9
- package/dist/src/config/layer.d.ts +0 -22
- package/dist/src/config/service.d.ts +0 -22
- package/dist/src/environment/layer.d.ts +0 -3
- package/dist/src/environment/service.d.ts +0 -12
- package/dist/src/layout/index.d.ts +0 -1
- package/dist/upstream-t3code/packages/contracts/src/auth.d.ts +0 -441
- package/dist/upstream-t3code/packages/contracts/src/baseSchemas.d.ts +0 -38
- package/dist/upstream-t3code/packages/contracts/src/desktopBootstrap.d.ts +0 -14
- package/dist/upstream-t3code/packages/contracts/src/editor.d.ts +0 -124
- package/dist/upstream-t3code/packages/contracts/src/environment.d.ts +0 -64
- package/dist/upstream-t3code/packages/contracts/src/environmentHttp.d.ts +0 -785
- package/dist/upstream-t3code/packages/contracts/src/filesystem.d.ts +0 -26
- package/dist/upstream-t3code/packages/contracts/src/git.d.ts +0 -500
- package/dist/upstream-t3code/packages/contracts/src/index.d.ts +0 -25
- package/dist/upstream-t3code/packages/contracts/src/ipc.d.ts +0 -496
- package/dist/upstream-t3code/packages/contracts/src/keybindings.d.ts +0 -133
- package/dist/upstream-t3code/packages/contracts/src/model.d.ts +0 -112
- package/dist/upstream-t3code/packages/contracts/src/orchestration.d.ts +0 -7338
- package/dist/upstream-t3code/packages/contracts/src/project.d.ts +0 -45
- package/dist/upstream-t3code/packages/contracts/src/provider.d.ts +0 -130
- package/dist/upstream-t3code/packages/contracts/src/providerInstance.d.ts +0 -99
- package/dist/upstream-t3code/packages/contracts/src/providerRuntime.d.ts +0 -4450
- package/dist/upstream-t3code/packages/contracts/src/relay.d.ts +0 -1262
- package/dist/upstream-t3code/packages/contracts/src/relayClient.d.ts +0 -48
- package/dist/upstream-t3code/packages/contracts/src/remoteAccess.d.ts +0 -45
- package/dist/upstream-t3code/packages/contracts/src/review.d.ts +0 -37
- package/dist/upstream-t3code/packages/contracts/src/rpc.d.ts +0 -8710
- package/dist/upstream-t3code/packages/contracts/src/server.d.ts +0 -2336
- package/dist/upstream-t3code/packages/contracts/src/settings.d.ts +0 -292
- package/dist/upstream-t3code/packages/contracts/src/sourceControl.d.ts +0 -177
- package/dist/upstream-t3code/packages/contracts/src/terminal.d.ts +0 -333
- package/dist/upstream-t3code/packages/contracts/src/vcs.d.ts +0 -148
- package/src/cli/auth-format.ts +0 -45
- package/src/cli/model-format.ts +0 -18
- package/src/cli/output-format.ts +0 -41
- package/src/cli/project-format.ts +0 -11
- package/src/cli/thread-format.ts +0 -121
- package/src/config/layer.ts +0 -105
- package/src/config/service.ts +0 -26
- package/src/environment/layer.ts +0 -12
- package/src/environment/service.ts +0 -13
- package/src/layout/index.ts +0 -1
- /package/dist/src/{scope → cli/scope}/index.d.ts +0 -0
- /package/dist/src/config/{url.d.ts → url/url.d.ts} +0 -0
- /package/src/{scope → cli/scope}/index.ts +0 -0
- /package/src/config/{url.ts → url/url.ts} +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
|
|
4
|
+
import { credentialCipherTagByteLength, T3CredentialCipher } from "./cipher.ts";
|
|
5
|
+
import { CredentialCipherError } from "./error.ts";
|
|
6
|
+
|
|
7
|
+
const aesGcmAlgorithm = "AES-GCM";
|
|
8
|
+
|
|
9
|
+
function copyBytes(bytes: Uint8Array) {
|
|
10
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
11
|
+
copy.set(bytes);
|
|
12
|
+
return copy;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function importAesGcmKey(key: Uint8Array, operation: "encrypt" | "decrypt") {
|
|
16
|
+
return Effect.tryPromise({
|
|
17
|
+
try: () =>
|
|
18
|
+
globalThis.crypto.subtle.importKey("raw", copyBytes(key), { name: aesGcmAlgorithm }, false, [
|
|
19
|
+
"encrypt",
|
|
20
|
+
"decrypt",
|
|
21
|
+
]),
|
|
22
|
+
catch: (cause) => new CredentialCipherError({ operation, cause }),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const make = (): T3CredentialCipher["Service"] => ({
|
|
27
|
+
encrypt: (input) =>
|
|
28
|
+
Effect.gen(function* () {
|
|
29
|
+
const cryptoKey = yield* importAesGcmKey(input.key, "encrypt");
|
|
30
|
+
const encrypted = yield* Effect.tryPromise({
|
|
31
|
+
try: () =>
|
|
32
|
+
globalThis.crypto.subtle.encrypt(
|
|
33
|
+
{
|
|
34
|
+
name: aesGcmAlgorithm,
|
|
35
|
+
iv: copyBytes(input.nonce),
|
|
36
|
+
additionalData: copyBytes(input.additionalData),
|
|
37
|
+
tagLength: credentialCipherTagByteLength * 8,
|
|
38
|
+
},
|
|
39
|
+
cryptoKey,
|
|
40
|
+
copyBytes(input.plaintext),
|
|
41
|
+
),
|
|
42
|
+
catch: (cause) => new CredentialCipherError({ operation: "encrypt", cause }),
|
|
43
|
+
});
|
|
44
|
+
const bytes = new Uint8Array(encrypted);
|
|
45
|
+
if (bytes.byteLength < credentialCipherTagByteLength) {
|
|
46
|
+
return yield* Effect.fail(
|
|
47
|
+
new CredentialCipherError({
|
|
48
|
+
operation: "encrypt",
|
|
49
|
+
cause: new Error("encrypted output shorter than auth tag"),
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
ciphertext: bytes.slice(0, -credentialCipherTagByteLength),
|
|
55
|
+
tag: bytes.slice(-credentialCipherTagByteLength),
|
|
56
|
+
};
|
|
57
|
+
}),
|
|
58
|
+
decrypt: (input) =>
|
|
59
|
+
Effect.gen(function* () {
|
|
60
|
+
const cryptoKey = yield* importAesGcmKey(input.key, "decrypt");
|
|
61
|
+
const combined = new Uint8Array(input.ciphertext.byteLength + input.tag.byteLength);
|
|
62
|
+
combined.set(input.ciphertext);
|
|
63
|
+
combined.set(input.tag, input.ciphertext.byteLength);
|
|
64
|
+
const plaintext = yield* Effect.tryPromise({
|
|
65
|
+
try: () =>
|
|
66
|
+
globalThis.crypto.subtle.decrypt(
|
|
67
|
+
{
|
|
68
|
+
name: aesGcmAlgorithm,
|
|
69
|
+
iv: copyBytes(input.nonce),
|
|
70
|
+
additionalData: copyBytes(input.additionalData),
|
|
71
|
+
tagLength: credentialCipherTagByteLength * 8,
|
|
72
|
+
},
|
|
73
|
+
cryptoKey,
|
|
74
|
+
combined,
|
|
75
|
+
),
|
|
76
|
+
catch: (cause) => new CredentialCipherError({ operation: "decrypt", cause }),
|
|
77
|
+
});
|
|
78
|
+
return new Uint8Array(plaintext);
|
|
79
|
+
}),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export const layerWeb = Layer.succeed(T3CredentialCipher, make());
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as Context from "effect/Context";
|
|
2
|
+
import type * as Effect from "effect/Effect";
|
|
3
|
+
|
|
4
|
+
import type { CredentialCipherError } from "./error.ts";
|
|
5
|
+
|
|
6
|
+
export const credentialCipherNonceByteLength = 12;
|
|
7
|
+
export const credentialCipherTagByteLength = 16;
|
|
8
|
+
|
|
9
|
+
export type AesGcmEncryptInput = {
|
|
10
|
+
readonly key: Uint8Array;
|
|
11
|
+
readonly nonce: Uint8Array;
|
|
12
|
+
readonly plaintext: Uint8Array;
|
|
13
|
+
readonly additionalData: Uint8Array;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type AesGcmDecryptInput = {
|
|
17
|
+
readonly key: Uint8Array;
|
|
18
|
+
readonly nonce: Uint8Array;
|
|
19
|
+
readonly ciphertext: Uint8Array;
|
|
20
|
+
readonly tag: Uint8Array;
|
|
21
|
+
readonly additionalData: Uint8Array;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type AesGcmEncryptResult = {
|
|
25
|
+
readonly ciphertext: Uint8Array;
|
|
26
|
+
readonly tag: Uint8Array;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class T3CredentialCipher extends Context.Service<
|
|
30
|
+
T3CredentialCipher,
|
|
31
|
+
{
|
|
32
|
+
readonly encrypt: (
|
|
33
|
+
input: AesGcmEncryptInput,
|
|
34
|
+
) => Effect.Effect<AesGcmEncryptResult, CredentialCipherError>;
|
|
35
|
+
readonly decrypt: (
|
|
36
|
+
input: AesGcmDecryptInput,
|
|
37
|
+
) => Effect.Effect<Uint8Array, CredentialCipherError>;
|
|
38
|
+
}
|
|
39
|
+
>()("t3cli/T3CredentialCipher") {}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as Option from "effect/Option";
|
|
2
|
+
import * as Redacted from "effect/Redacted";
|
|
3
|
+
|
|
4
|
+
import type { T3CliEnvShape } from "../env/env.ts";
|
|
5
|
+
|
|
6
|
+
export function credentialTokenFromEnv(
|
|
7
|
+
t3CliEnv: Pick<T3CliEnvShape, "t3codeToken">,
|
|
8
|
+
): Option.Option<string> {
|
|
9
|
+
return Option.flatMap(t3CliEnv.t3codeToken, (token) => {
|
|
10
|
+
const value = Redacted.value(token).trim();
|
|
11
|
+
return value.length > 0 ? Option.some(value) : Option.none();
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * as Service from "./service.ts";
|
|
2
|
+
export * as Cipher from "./cipher.ts";
|
|
3
|
+
export * as CipherNode from "./cipher-node.ts";
|
|
4
|
+
export * as CipherWeb from "./cipher-web.ts";
|
|
5
|
+
export { credentialTokenFromEnv } from "./env.ts";
|
|
6
|
+
export { CredentialCipherError } from "./error.ts";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as Layer from "effect/Layer";
|
|
2
|
+
|
|
3
|
+
import { CliRuntime } from "../../cli/runtime/service.ts";
|
|
4
|
+
import { ConfigPlatformLayer } from "../platform.test-utils.ts";
|
|
5
|
+
import { t3CliEnvConfigLayer } from "../env/env.test-utils.ts";
|
|
6
|
+
import * as CredentialCipherWeb from "./cipher-web.ts";
|
|
7
|
+
import * as Keystore from "../keystore/service.test-utils.ts";
|
|
8
|
+
import * as Credential from "./service.ts";
|
|
9
|
+
|
|
10
|
+
export function t3CredentialCryptoDepsLayer(homeDir: string) {
|
|
11
|
+
return Credential.layer.pipe(
|
|
12
|
+
Layer.provide(
|
|
13
|
+
Layer.mergeAll(
|
|
14
|
+
CliRuntime.layerTest({ cwd: homeDir }),
|
|
15
|
+
t3CliEnvConfigLayer(homeDir),
|
|
16
|
+
CredentialCipherWeb.layerWeb,
|
|
17
|
+
Keystore.unavailableKeystoreFactoryLayer,
|
|
18
|
+
),
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function t3CredentialCryptoLayerTest(homeDir: string) {
|
|
24
|
+
return Layer.mergeAll(ConfigPlatformLayer, t3CredentialCryptoDepsLayer(homeDir));
|
|
25
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import "vite-plus/test/config";
|
|
2
|
+
|
|
3
|
+
import * as Crypto from "effect/Crypto";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Exit from "effect/Exit";
|
|
6
|
+
import * as FileSystem from "effect/FileSystem";
|
|
7
|
+
import * as Path from "effect/Path";
|
|
8
|
+
import { assert, describe, it } from "@effect/vitest";
|
|
9
|
+
|
|
10
|
+
import { ConfigPlatformLayer } from "../platform.test-utils.ts";
|
|
11
|
+
import { makeTempHomeScoped } from "../temp-home.test-utils.ts";
|
|
12
|
+
import { T3CredentialCrypto } from "./service.ts";
|
|
13
|
+
import { t3CredentialCryptoDepsLayer } from "./service.test-utils.ts";
|
|
14
|
+
|
|
15
|
+
describe("T3CredentialCrypto", () => {
|
|
16
|
+
it.effect("falls back to key file when keyring backend is unavailable", () =>
|
|
17
|
+
Effect.gen(function* () {
|
|
18
|
+
const fs = yield* FileSystem.FileSystem;
|
|
19
|
+
const path = yield* Path.Path;
|
|
20
|
+
const cryptoService = yield* Crypto.Crypto;
|
|
21
|
+
const homeDir = yield* makeTempHomeScoped("t3cli-credential-");
|
|
22
|
+
yield* Effect.gen(function* () {
|
|
23
|
+
const keyPath = path.join(homeDir, ".config", "t3cli", "key");
|
|
24
|
+
const masterKey = yield* cryptoService.randomBytes(32);
|
|
25
|
+
yield* fs.makeDirectory(path.dirname(keyPath), { recursive: true });
|
|
26
|
+
yield* fs.writeFileString(keyPath, `${Buffer.from(masterKey).toString("base64")}\n`, {
|
|
27
|
+
mode: 0o600,
|
|
28
|
+
});
|
|
29
|
+
const credentialCrypto = yield* T3CredentialCrypto;
|
|
30
|
+
const encrypted = yield* credentialCrypto.encrypt({
|
|
31
|
+
environmentName: "home",
|
|
32
|
+
url: "https://home.example",
|
|
33
|
+
local: false,
|
|
34
|
+
token: "secret-token",
|
|
35
|
+
});
|
|
36
|
+
const token = yield* credentialCrypto.decrypt({
|
|
37
|
+
environmentName: "home",
|
|
38
|
+
url: "https://home.example",
|
|
39
|
+
local: false,
|
|
40
|
+
token: encrypted,
|
|
41
|
+
});
|
|
42
|
+
assert.equal(token, "secret-token");
|
|
43
|
+
}).pipe(Effect.provide(t3CredentialCryptoDepsLayer(homeDir)));
|
|
44
|
+
}).pipe(Effect.provide(ConfigPlatformLayer), Effect.scoped),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
it.effect("hardens existing key file permissions to 0600 on use", () =>
|
|
48
|
+
Effect.gen(function* () {
|
|
49
|
+
const fs = yield* FileSystem.FileSystem;
|
|
50
|
+
const path = yield* Path.Path;
|
|
51
|
+
const cryptoService = yield* Crypto.Crypto;
|
|
52
|
+
const homeDir = yield* makeTempHomeScoped("t3cli-credential-");
|
|
53
|
+
yield* Effect.gen(function* () {
|
|
54
|
+
const keyPath = path.join(homeDir, ".config", "t3cli", "key");
|
|
55
|
+
const masterKey = yield* cryptoService.randomBytes(32);
|
|
56
|
+
yield* fs.makeDirectory(path.dirname(keyPath), { recursive: true });
|
|
57
|
+
yield* fs.writeFileString(keyPath, `${Buffer.from(masterKey).toString("base64")}\n`, {
|
|
58
|
+
mode: 0o644,
|
|
59
|
+
});
|
|
60
|
+
const credentialCrypto = yield* T3CredentialCrypto;
|
|
61
|
+
yield* credentialCrypto.encrypt({
|
|
62
|
+
environmentName: "home",
|
|
63
|
+
url: "https://home.example",
|
|
64
|
+
local: false,
|
|
65
|
+
token: "secret-token",
|
|
66
|
+
});
|
|
67
|
+
const keyStat = yield* fs.stat(keyPath);
|
|
68
|
+
assert.equal(keyStat.mode & 0o777, 0o600);
|
|
69
|
+
}).pipe(Effect.provide(t3CredentialCryptoDepsLayer(homeDir)));
|
|
70
|
+
}).pipe(Effect.provide(ConfigPlatformLayer), Effect.scoped),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
it.effect("creates key file with 0600 permissions when keyring is unavailable", () =>
|
|
74
|
+
Effect.gen(function* () {
|
|
75
|
+
const fs = yield* FileSystem.FileSystem;
|
|
76
|
+
const path = yield* Path.Path;
|
|
77
|
+
const homeDir = yield* makeTempHomeScoped("t3cli-credential-");
|
|
78
|
+
yield* Effect.gen(function* () {
|
|
79
|
+
const keyPath = path.join(homeDir, ".config", "t3cli", "key");
|
|
80
|
+
const credentialCrypto = yield* T3CredentialCrypto;
|
|
81
|
+
yield* credentialCrypto.encrypt({
|
|
82
|
+
environmentName: "home",
|
|
83
|
+
url: "https://home.example",
|
|
84
|
+
local: false,
|
|
85
|
+
token: "rotate-write",
|
|
86
|
+
});
|
|
87
|
+
const keyStat = yield* fs.stat(keyPath);
|
|
88
|
+
assert.equal(keyStat.mode & 0o777, 0o600);
|
|
89
|
+
}).pipe(Effect.provide(t3CredentialCryptoDepsLayer(homeDir)));
|
|
90
|
+
}).pipe(Effect.provide(ConfigPlatformLayer), Effect.scoped),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
it.effect("fails decrypt when ciphertext AAD does not match", () =>
|
|
94
|
+
Effect.gen(function* () {
|
|
95
|
+
const homeDir = yield* makeTempHomeScoped("t3cli-credential-");
|
|
96
|
+
yield* Effect.gen(function* () {
|
|
97
|
+
const crypto = yield* T3CredentialCrypto;
|
|
98
|
+
const token = yield* crypto.encrypt({
|
|
99
|
+
environmentName: "home",
|
|
100
|
+
url: "https://home.example",
|
|
101
|
+
local: false,
|
|
102
|
+
token: "secret",
|
|
103
|
+
});
|
|
104
|
+
const result = yield* crypto
|
|
105
|
+
.decrypt({
|
|
106
|
+
environmentName: "home",
|
|
107
|
+
url: "https://tampered.example",
|
|
108
|
+
local: false,
|
|
109
|
+
token,
|
|
110
|
+
})
|
|
111
|
+
.pipe(Effect.exit);
|
|
112
|
+
assert.equal(Exit.isFailure(result), true);
|
|
113
|
+
}).pipe(Effect.provide(t3CredentialCryptoDepsLayer(homeDir)));
|
|
114
|
+
}).pipe(Effect.provide(ConfigPlatformLayer), Effect.scoped),
|
|
115
|
+
);
|
|
116
|
+
});
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import * as Context from "effect/Context";
|
|
2
|
+
import * as Crypto from "effect/Crypto";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as ConfigProvider from "effect/ConfigProvider";
|
|
5
|
+
import * as Encoding from "effect/Encoding";
|
|
6
|
+
import * as FileSystem from "effect/FileSystem";
|
|
7
|
+
import * as Layer from "effect/Layer";
|
|
8
|
+
import * as Path from "effect/Path";
|
|
9
|
+
|
|
10
|
+
import { T3CredentialCipher, credentialCipherNonceByteLength } from "./cipher.ts";
|
|
11
|
+
import type { EncryptedToken } from "../persist/schema.ts";
|
|
12
|
+
import { ConfigError } from "../error.ts";
|
|
13
|
+
import { CredentialCipherError } from "./error.ts";
|
|
14
|
+
import { makeFileKeystore } from "../keystore/file.ts";
|
|
15
|
+
import { T3MasterKeyKeystoreFactory, masterKeyByteLength } from "../keystore/service.ts";
|
|
16
|
+
|
|
17
|
+
export type CredentialEncryptInput = {
|
|
18
|
+
readonly environmentName: string;
|
|
19
|
+
readonly url: string;
|
|
20
|
+
readonly local: boolean;
|
|
21
|
+
readonly token: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type CredentialDecryptInput = {
|
|
25
|
+
readonly environmentName: string;
|
|
26
|
+
readonly url: string;
|
|
27
|
+
readonly local: boolean;
|
|
28
|
+
readonly token: EncryptedToken;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export class T3CredentialCrypto extends Context.Service<
|
|
32
|
+
T3CredentialCrypto,
|
|
33
|
+
{
|
|
34
|
+
readonly encrypt: (input: CredentialEncryptInput) => Effect.Effect<EncryptedToken, ConfigError>;
|
|
35
|
+
readonly decrypt: (input: CredentialDecryptInput) => Effect.Effect<string, ConfigError>;
|
|
36
|
+
}
|
|
37
|
+
>()("t3cli/T3CredentialCrypto") {}
|
|
38
|
+
|
|
39
|
+
const configSchemaVersion = 2;
|
|
40
|
+
const textEncoder = new TextEncoder();
|
|
41
|
+
const textDecoder = new TextDecoder();
|
|
42
|
+
|
|
43
|
+
export const make = Effect.fn("makeT3CredentialCrypto")(function* () {
|
|
44
|
+
const fs = yield* FileSystem.FileSystem;
|
|
45
|
+
const path = yield* Path.Path;
|
|
46
|
+
const configProvider = yield* ConfigProvider.ConfigProvider;
|
|
47
|
+
const cryptoService = yield* Crypto.Crypto;
|
|
48
|
+
const cipher = yield* T3CredentialCipher;
|
|
49
|
+
const keystoreFactory = yield* T3MasterKeyKeystoreFactory;
|
|
50
|
+
const services = Layer.mergeAll(
|
|
51
|
+
Layer.succeed(FileSystem.FileSystem, fs),
|
|
52
|
+
Layer.succeed(Path.Path, path),
|
|
53
|
+
Layer.succeed(Crypto.Crypto, cryptoService),
|
|
54
|
+
Layer.succeed(ConfigProvider.ConfigProvider, configProvider),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const fileKeystore = yield* Effect.provide(makeFileKeystore(), services);
|
|
58
|
+
const primaryKeystore = yield* keystoreFactory.make().pipe(
|
|
59
|
+
Effect.catchTags({
|
|
60
|
+
KeystoreUnavailableError: () => Effect.succeed(undefined),
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const readMasterKey = Effect.fn("readMasterKey")(function* () {
|
|
65
|
+
if (primaryKeystore !== undefined) {
|
|
66
|
+
const primaryResult = yield* primaryKeystore.read();
|
|
67
|
+
if (primaryResult.kind === "present") {
|
|
68
|
+
return primaryResult.key;
|
|
69
|
+
}
|
|
70
|
+
if (primaryResult.kind === "corrupt") {
|
|
71
|
+
return yield* Effect.fail(
|
|
72
|
+
new ConfigError({
|
|
73
|
+
message: `corrupt credential key in OS keyring: ${primaryResult.message}`,
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const fileResult = yield* fileKeystore.read();
|
|
80
|
+
if (fileResult.kind === "present") {
|
|
81
|
+
return fileResult.key;
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const writeMasterKey = Effect.fn("writeMasterKey")(function* (key: Uint8Array) {
|
|
87
|
+
if (primaryKeystore !== undefined) {
|
|
88
|
+
yield* primaryKeystore.write(key).pipe(
|
|
89
|
+
Effect.catchTags({
|
|
90
|
+
KeystoreUnavailableError: () => fileKeystore.write(key),
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
yield* fileKeystore.write(key);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const getMasterKey = Effect.fn("getMasterKey")(function* () {
|
|
99
|
+
const existing = yield* readMasterKey();
|
|
100
|
+
if (existing !== undefined) {
|
|
101
|
+
return existing;
|
|
102
|
+
}
|
|
103
|
+
const generated = yield* cryptoService.randomBytes(masterKeyByteLength).pipe(
|
|
104
|
+
Effect.mapError(
|
|
105
|
+
(error) =>
|
|
106
|
+
new ConfigError({
|
|
107
|
+
message: "failed to generate secure random bytes",
|
|
108
|
+
cause: error,
|
|
109
|
+
}),
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
yield* writeMasterKey(generated);
|
|
113
|
+
return generated;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const encrypt: (input: CredentialEncryptInput) => Effect.Effect<EncryptedToken, ConfigError> =
|
|
117
|
+
Effect.fn("encrypt")(function* (input) {
|
|
118
|
+
const masterKey = yield* getMasterKey();
|
|
119
|
+
const nonce = yield* cryptoService.randomBytes(credentialCipherNonceByteLength).pipe(
|
|
120
|
+
Effect.mapError(
|
|
121
|
+
(error) =>
|
|
122
|
+
new ConfigError({
|
|
123
|
+
message: "failed to generate secure random bytes",
|
|
124
|
+
cause: error,
|
|
125
|
+
}),
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
const encrypted = yield* cipher
|
|
129
|
+
.encrypt({
|
|
130
|
+
key: masterKey,
|
|
131
|
+
nonce,
|
|
132
|
+
plaintext: encodeUtf8(input.token),
|
|
133
|
+
additionalData: buildCredentialAad(input),
|
|
134
|
+
})
|
|
135
|
+
.pipe(Effect.mapError(mapCredentialCipherError));
|
|
136
|
+
return {
|
|
137
|
+
kind: "encrypted" as const,
|
|
138
|
+
alg: "aes-256-gcm" as const,
|
|
139
|
+
key: "default" as const,
|
|
140
|
+
nonce: Encoding.encodeBase64(nonce),
|
|
141
|
+
ciphertext: Encoding.encodeBase64(encrypted.ciphertext),
|
|
142
|
+
tag: Encoding.encodeBase64(encrypted.tag),
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const decrypt: (input: CredentialDecryptInput) => Effect.Effect<string, ConfigError> = Effect.fn(
|
|
147
|
+
"decrypt",
|
|
148
|
+
)(function* (input) {
|
|
149
|
+
const masterKey = yield* getMasterKey();
|
|
150
|
+
const nonce = yield* decodeBase64Field(input.token.nonce, "token nonce");
|
|
151
|
+
const ciphertext = yield* decodeBase64Field(input.token.ciphertext, "token ciphertext");
|
|
152
|
+
const tag = yield* decodeBase64Field(input.token.tag, "token tag");
|
|
153
|
+
const plaintext = yield* cipher
|
|
154
|
+
.decrypt({
|
|
155
|
+
key: masterKey,
|
|
156
|
+
nonce,
|
|
157
|
+
ciphertext,
|
|
158
|
+
tag,
|
|
159
|
+
additionalData: buildCredentialAad(input),
|
|
160
|
+
})
|
|
161
|
+
.pipe(Effect.mapError(mapCredentialCipherError));
|
|
162
|
+
return decodeUtf8(plaintext);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return { encrypt, decrypt } satisfies T3CredentialCrypto["Service"];
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
export const layer = Layer.effect(T3CredentialCrypto, make());
|
|
169
|
+
|
|
170
|
+
function buildCredentialAad(input: {
|
|
171
|
+
readonly environmentName: string;
|
|
172
|
+
readonly url: string;
|
|
173
|
+
readonly local: boolean;
|
|
174
|
+
}) {
|
|
175
|
+
return encodeUtf8(
|
|
176
|
+
`${configSchemaVersion}\0${input.environmentName}\0${input.url}\0${input.local}`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function encodeUtf8(value: string) {
|
|
181
|
+
return textEncoder.encode(value);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function decodeUtf8(value: Uint8Array) {
|
|
185
|
+
return textDecoder.decode(value);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function decodeBase64Field(value: string, field: string) {
|
|
189
|
+
return Effect.fromResult(Encoding.decodeBase64(value)).pipe(
|
|
190
|
+
Effect.catchTags({
|
|
191
|
+
EncodingError: (error) =>
|
|
192
|
+
Effect.fail(new ConfigError({ message: `invalid encrypted token ${field}`, cause: error })),
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function mapCredentialCipherError(error: CredentialCipherError) {
|
|
198
|
+
return new ConfigError({
|
|
199
|
+
message:
|
|
200
|
+
error.operation === "encrypt"
|
|
201
|
+
? "failed to encrypt credential cipher payload"
|
|
202
|
+
: "failed to decrypt credential cipher payload",
|
|
203
|
+
cause: error,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as ConfigProvider from "effect/ConfigProvider";
|
|
2
|
+
|
|
3
|
+
export function t3CliEnvConfigLayer(
|
|
4
|
+
homeDir: string,
|
|
5
|
+
env: Readonly<Record<string, string | undefined>> = {},
|
|
6
|
+
) {
|
|
7
|
+
const record: Record<string, string> = { HOME: homeDir };
|
|
8
|
+
for (const [key, value] of Object.entries(env)) {
|
|
9
|
+
if (value !== undefined) {
|
|
10
|
+
record[key] = value;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return ConfigProvider.layer(ConfigProvider.fromEnv({ env: record }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as Config from "effect/Config";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Option from "effect/Option";
|
|
4
|
+
import type * as Redacted from "effect/Redacted";
|
|
5
|
+
|
|
6
|
+
export type T3CliEnvScope = {
|
|
7
|
+
readonly t3codeProjectRoot?: string | undefined;
|
|
8
|
+
readonly t3codeProjectId?: string | undefined;
|
|
9
|
+
readonly t3codeWorktreePath?: string | undefined;
|
|
10
|
+
readonly t3codeThreadId?: string | undefined;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type T3CliEnvShape = {
|
|
14
|
+
readonly home: Option.Option<string>;
|
|
15
|
+
readonly xdgConfigHome: Option.Option<string>;
|
|
16
|
+
readonly t3cliEnv: Option.Option<string>;
|
|
17
|
+
readonly t3codeUrl: Option.Option<string>;
|
|
18
|
+
readonly t3codeToken: Option.Option<Redacted.Redacted>;
|
|
19
|
+
readonly t3codeHome: Option.Option<string>;
|
|
20
|
+
readonly term: Option.Option<string>;
|
|
21
|
+
readonly ci: boolean;
|
|
22
|
+
readonly codexCi: boolean;
|
|
23
|
+
readonly codexThreadId: boolean;
|
|
24
|
+
readonly t3cliAgent: boolean;
|
|
25
|
+
readonly scope: T3CliEnvScope;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const T3CliEnvConfig = Config.all({
|
|
29
|
+
home: Config.string("HOME").pipe(Config.option),
|
|
30
|
+
xdgConfigHome: Config.string("XDG_CONFIG_HOME").pipe(Config.option),
|
|
31
|
+
t3cliEnv: Config.string("T3CLI_ENV").pipe(Config.option),
|
|
32
|
+
t3codeUrl: Config.string("T3CODE_URL").pipe(Config.option),
|
|
33
|
+
t3codeToken: Config.redacted("T3CODE_TOKEN").pipe(Config.option),
|
|
34
|
+
t3codeHome: Config.string("T3CODE_HOME").pipe(Config.option),
|
|
35
|
+
t3codeProjectRoot: Config.string("T3CODE_PROJECT_ROOT").pipe(Config.option),
|
|
36
|
+
t3codeProjectId: Config.string("T3CODE_PROJECT_ID").pipe(Config.option),
|
|
37
|
+
t3codeWorktreePath: Config.string("T3CODE_WORKTREE_PATH").pipe(Config.option),
|
|
38
|
+
t3codeThreadId: Config.string("T3CODE_THREAD_ID").pipe(Config.option),
|
|
39
|
+
term: Config.string("TERM").pipe(Config.option),
|
|
40
|
+
ci: Config.string("CI").pipe(Config.option, Config.map(Option.isSome)),
|
|
41
|
+
codexCi: Config.string("CODEX_CI").pipe(Config.option, Config.map(Option.isSome)),
|
|
42
|
+
codexThreadId: Config.string("CODEX_THREAD_ID").pipe(Config.option, Config.map(Option.isSome)),
|
|
43
|
+
t3cliAgent: Config.string("T3CLI_AGENT").pipe(Config.option, Config.map(Option.isSome)),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
function toScope(loaded: {
|
|
47
|
+
readonly t3codeProjectRoot: Option.Option<string>;
|
|
48
|
+
readonly t3codeProjectId: Option.Option<string>;
|
|
49
|
+
readonly t3codeWorktreePath: Option.Option<string>;
|
|
50
|
+
readonly t3codeThreadId: Option.Option<string>;
|
|
51
|
+
}): T3CliEnvScope {
|
|
52
|
+
return {
|
|
53
|
+
...(Option.isSome(loaded.t3codeProjectRoot)
|
|
54
|
+
? { t3codeProjectRoot: loaded.t3codeProjectRoot.value }
|
|
55
|
+
: {}),
|
|
56
|
+
...(Option.isSome(loaded.t3codeProjectId)
|
|
57
|
+
? { t3codeProjectId: loaded.t3codeProjectId.value }
|
|
58
|
+
: {}),
|
|
59
|
+
...(Option.isSome(loaded.t3codeWorktreePath)
|
|
60
|
+
? { t3codeWorktreePath: loaded.t3codeWorktreePath.value }
|
|
61
|
+
: {}),
|
|
62
|
+
...(Option.isSome(loaded.t3codeThreadId)
|
|
63
|
+
? { t3codeThreadId: loaded.t3codeThreadId.value }
|
|
64
|
+
: {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const loadT3CliEnv = Effect.map(T3CliEnvConfig, (loaded) => ({
|
|
69
|
+
home: loaded.home,
|
|
70
|
+
xdgConfigHome: loaded.xdgConfigHome,
|
|
71
|
+
t3cliEnv: loaded.t3cliEnv,
|
|
72
|
+
t3codeUrl: loaded.t3codeUrl,
|
|
73
|
+
t3codeToken: loaded.t3codeToken,
|
|
74
|
+
t3codeHome: loaded.t3codeHome,
|
|
75
|
+
term: loaded.term,
|
|
76
|
+
ci: loaded.ci,
|
|
77
|
+
codexCi: loaded.codexCi,
|
|
78
|
+
codexThreadId: loaded.codexThreadId,
|
|
79
|
+
t3cliAgent: loaded.t3cliAgent,
|
|
80
|
+
scope: toScope(loaded),
|
|
81
|
+
}));
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { homedir } from "node:os";
|
|
2
1
|
import * as Effect from "effect/Effect";
|
|
3
2
|
import * as Path from "effect/Path";
|
|
4
3
|
|
|
@@ -8,14 +7,6 @@ export type T3Layout = {
|
|
|
8
7
|
readonly t3codeHome?: string | undefined;
|
|
9
8
|
};
|
|
10
9
|
|
|
11
|
-
export function readT3LayoutFromNodeProcess(): T3Layout {
|
|
12
|
-
return {
|
|
13
|
-
cwd: process.cwd(),
|
|
14
|
-
homeDir: homedir(),
|
|
15
|
-
t3codeHome: process.env["T3CODE_HOME"],
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
10
|
export const resolveT3BaseDir = Effect.fn("resolveT3BaseDir")(function* (input: {
|
|
20
11
|
readonly layout: T3Layout;
|
|
21
12
|
readonly baseDir?: string | undefined;
|