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,36 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
|
|
3
|
+
import { SelfActionError } from "../error.ts";
|
|
4
|
+
import { isAgentEnvironment } from "../format/output.ts";
|
|
5
|
+
import type { T3CliEnvShape } from "../../config/env/env.ts";
|
|
6
|
+
import type { CliRuntime } from "../runtime/service.ts";
|
|
7
|
+
|
|
8
|
+
export const requireSelfActionConfirmation = Effect.fn("requireSelfActionConfirmation")(
|
|
9
|
+
function* (input: {
|
|
10
|
+
readonly threadId: string;
|
|
11
|
+
readonly force: boolean;
|
|
12
|
+
readonly cliRuntime: CliRuntime["Service"];
|
|
13
|
+
readonly t3CliEnv: T3CliEnvShape;
|
|
14
|
+
readonly action: string;
|
|
15
|
+
}) {
|
|
16
|
+
if (input.force) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (!isAgentEnvironment(input.t3CliEnv)) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const callerThreadId = input.t3CliEnv.scope.t3codeThreadId;
|
|
23
|
+
if (callerThreadId === undefined || callerThreadId.length === 0) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (input.threadId !== callerThreadId) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
yield* Effect.fail(
|
|
30
|
+
new SelfActionError({
|
|
31
|
+
threadId: input.threadId,
|
|
32
|
+
message: `cannot ${input.action} thread ${input.threadId}: command targets the calling thread. Pass --force to confirm.`,
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
);
|
package/src/cli/message-input.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
2
|
import * as Schema from "effect/Schema";
|
|
3
|
-
import { ProviderUserInputAnswers } from "
|
|
3
|
+
import { ProviderUserInputAnswers } from "@t3tools/contracts";
|
|
4
4
|
|
|
5
5
|
import { MessageInputError } from "./error.ts";
|
|
6
6
|
import type { InputError } from "./input/error.ts";
|
package/src/cli/model.ts
CHANGED
|
@@ -2,11 +2,13 @@ import * as Effect from "effect/Effect";
|
|
|
2
2
|
import * as Option from "effect/Option";
|
|
3
3
|
import { Command, Flag } from "effect/unstable/cli";
|
|
4
4
|
|
|
5
|
+
import { extraArgsConfig } from "./extra-args.ts";
|
|
5
6
|
import { formatFlag } from "./flags.ts";
|
|
6
7
|
import { T3Application } from "../application/service.ts";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { CliRuntime } from "../cli/runtime/service.ts";
|
|
9
|
+
import { loadT3CliEnv } from "../config/env/env.ts";
|
|
10
|
+
import { formatModelsHuman } from "./format/model.ts";
|
|
11
|
+
import { resolveOutputFormat } from "./format/output.ts";
|
|
10
12
|
import { T3Output } from "./output/service.ts";
|
|
11
13
|
|
|
12
14
|
export function createModelCommand() {
|
|
@@ -22,14 +24,16 @@ const listCommand = Command.make(
|
|
|
22
24
|
all: Flag.boolean("all"),
|
|
23
25
|
provider: Flag.string("provider").pipe(Flag.optional),
|
|
24
26
|
format: formatFlag,
|
|
27
|
+
...extraArgsConfig,
|
|
25
28
|
},
|
|
26
29
|
({ all, provider, format }) =>
|
|
27
30
|
Effect.gen(function* () {
|
|
28
31
|
const application = yield* T3Application;
|
|
29
|
-
const
|
|
32
|
+
const cliRuntime = yield* CliRuntime;
|
|
33
|
+
const t3CliEnv = yield* loadT3CliEnv;
|
|
30
34
|
const output = yield* T3Output;
|
|
31
35
|
const providerValue = Option.getOrUndefined(provider);
|
|
32
|
-
const resolvedFormat = resolveOutputFormat(format,
|
|
36
|
+
const resolvedFormat = resolveOutputFormat(format, cliRuntime, t3CliEnv, "json");
|
|
33
37
|
const providers = yield* application.listModels({
|
|
34
38
|
all,
|
|
35
39
|
...(providerValue !== undefined && providerValue.length > 0
|
package/src/cli/project.ts
CHANGED
|
@@ -2,17 +2,20 @@ import * as Effect from "effect/Effect";
|
|
|
2
2
|
import * as Option from "effect/Option";
|
|
3
3
|
import { Command, Flag } from "effect/unstable/cli";
|
|
4
4
|
|
|
5
|
+
import { extraArgsConfig } from "./extra-args.ts";
|
|
5
6
|
import { formatFlag, projectPathFlag } from "./flags.ts";
|
|
6
|
-
import { formatProjectAddedHuman, formatProjectsHuman } from "./project
|
|
7
|
+
import { formatProjectAddedHuman, formatProjectsHuman } from "./format/project.ts";
|
|
8
|
+
import { deleteProjectCommand } from "./projects/delete.ts";
|
|
7
9
|
import { T3Application } from "../application/service.ts";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
+
import { CliRuntime } from "../cli/runtime/service.ts";
|
|
11
|
+
import { loadT3CliEnv } from "../config/env/env.ts";
|
|
12
|
+
import { resolveOutputFormat } from "./format/output.ts";
|
|
10
13
|
import { T3Output } from "./output/service.ts";
|
|
11
14
|
|
|
12
15
|
export function createProjectCommand() {
|
|
13
16
|
return Command.make("project").pipe(
|
|
14
17
|
Command.withDescription("project commands"),
|
|
15
|
-
Command.withSubcommands([listCommand, addCommand]),
|
|
18
|
+
Command.withSubcommands([listCommand, addCommand, deleteProjectCommand]),
|
|
16
19
|
);
|
|
17
20
|
}
|
|
18
21
|
|
|
@@ -20,13 +23,15 @@ const listCommand = Command.make(
|
|
|
20
23
|
"list",
|
|
21
24
|
{
|
|
22
25
|
format: formatFlag,
|
|
26
|
+
...extraArgsConfig,
|
|
23
27
|
},
|
|
24
28
|
({ format }) =>
|
|
25
29
|
Effect.gen(function* () {
|
|
26
30
|
const application = yield* T3Application;
|
|
27
|
-
const
|
|
31
|
+
const cliRuntime = yield* CliRuntime;
|
|
32
|
+
const t3CliEnv = yield* loadT3CliEnv;
|
|
28
33
|
const output = yield* T3Output;
|
|
29
|
-
const resolvedFormat = resolveOutputFormat(format,
|
|
34
|
+
const resolvedFormat = resolveOutputFormat(format, cliRuntime, t3CliEnv, "json");
|
|
30
35
|
const snapshot = yield* application.loadShell();
|
|
31
36
|
if (resolvedFormat === "json") {
|
|
32
37
|
yield* output.printJson(snapshot.projects);
|
|
@@ -42,13 +47,15 @@ const addCommand = Command.make(
|
|
|
42
47
|
path: projectPathFlag,
|
|
43
48
|
title: Flag.string("title").pipe(Flag.optional),
|
|
44
49
|
format: formatFlag,
|
|
50
|
+
...extraArgsConfig,
|
|
45
51
|
},
|
|
46
52
|
({ path, title, format }) =>
|
|
47
53
|
Effect.gen(function* () {
|
|
48
54
|
const application = yield* T3Application;
|
|
49
|
-
const
|
|
55
|
+
const cliRuntime = yield* CliRuntime;
|
|
56
|
+
const t3CliEnv = yield* loadT3CliEnv;
|
|
50
57
|
const output = yield* T3Output;
|
|
51
|
-
const resolvedFormat = resolveOutputFormat(format,
|
|
58
|
+
const resolvedFormat = resolveOutputFormat(format, cliRuntime, t3CliEnv, "json");
|
|
52
59
|
const titleValue = Option.getOrUndefined(title);
|
|
53
60
|
const result = yield* application.addProject({
|
|
54
61
|
path,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { Command } from "effect/unstable/cli";
|
|
3
|
+
|
|
4
|
+
import { extraArgsConfig } from "../extra-args.ts";
|
|
5
|
+
import { requireDestructiveConfirmation } from "../interaction/confirm.ts";
|
|
6
|
+
import { forceFlag, formatFlag, projectFlag, yesFlag } from "../flags.ts";
|
|
7
|
+
import { formatProjectDeletedHuman } from "../format/project.ts";
|
|
8
|
+
import { requireCommandProjectRef } from "../require.ts";
|
|
9
|
+
import { T3Application } from "../../application/service.ts";
|
|
10
|
+
import { CliRuntime } from "../../cli/runtime/service.ts";
|
|
11
|
+
import { loadT3CliEnv } from "../../config/env/env.ts";
|
|
12
|
+
import { resolveOutputFormat } from "../format/output.ts";
|
|
13
|
+
import { T3Output } from "../output/service.ts";
|
|
14
|
+
|
|
15
|
+
export const deleteProjectCommand = Command.make(
|
|
16
|
+
"delete",
|
|
17
|
+
{
|
|
18
|
+
project: projectFlag,
|
|
19
|
+
force: forceFlag,
|
|
20
|
+
yes: yesFlag,
|
|
21
|
+
format: formatFlag,
|
|
22
|
+
...extraArgsConfig,
|
|
23
|
+
},
|
|
24
|
+
({ project, force, yes, format }) =>
|
|
25
|
+
Effect.gen(function* () {
|
|
26
|
+
const application = yield* T3Application;
|
|
27
|
+
const cliRuntime = yield* CliRuntime;
|
|
28
|
+
const t3CliEnv = yield* loadT3CliEnv;
|
|
29
|
+
const output = yield* T3Output;
|
|
30
|
+
const projectRef = yield* requireCommandProjectRef({ project });
|
|
31
|
+
const resolvedProject = yield* application.resolveProject(projectRef);
|
|
32
|
+
yield* requireDestructiveConfirmation({
|
|
33
|
+
message: `Delete project ${resolvedProject.title} (${resolvedProject.id})?`,
|
|
34
|
+
yes,
|
|
35
|
+
cliRuntime,
|
|
36
|
+
t3CliEnv,
|
|
37
|
+
});
|
|
38
|
+
const resolvedFormat = resolveOutputFormat(format, cliRuntime, t3CliEnv, "json");
|
|
39
|
+
const result = yield* application.deleteProject({
|
|
40
|
+
projectId: resolvedProject.id,
|
|
41
|
+
...(force ? { force: true } : {}),
|
|
42
|
+
});
|
|
43
|
+
if (resolvedFormat === "json") {
|
|
44
|
+
return yield* output.printJson({
|
|
45
|
+
projectId: result.projectId,
|
|
46
|
+
dispatch: result.dispatch,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return yield* output.printInfo(formatProjectDeletedHuman(result));
|
|
50
|
+
}),
|
|
51
|
+
).pipe(Command.withDescription("delete project"));
|
package/src/cli/require.ts
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
2
|
import * as Option from "effect/Option";
|
|
3
3
|
|
|
4
|
-
import { T3Config } from "../config/
|
|
4
|
+
import { T3Config } from "../config/config.ts";
|
|
5
|
+
import { loadT3CliEnv } from "../config/env/env.ts";
|
|
6
|
+
import { CliRuntime } from "../cli/runtime/service.ts";
|
|
5
7
|
import { ProjectLookupError } from "../domain/error.ts";
|
|
6
|
-
import { resolveCommandProjectRef } from "
|
|
8
|
+
import { resolveCommandProjectRef } from "./scope/index.ts";
|
|
7
9
|
|
|
8
10
|
export const requireCommandProjectRef = Effect.fn("requireCommandProjectRef")(function* (input: {
|
|
9
11
|
readonly project: Option.Option<string>;
|
|
10
|
-
readonly env: Readonly<Record<string, string | undefined>>;
|
|
11
|
-
readonly cwd: string;
|
|
12
12
|
}) {
|
|
13
|
+
const cliRuntime = yield* CliRuntime;
|
|
14
|
+
const t3CliEnv = yield* loadT3CliEnv;
|
|
13
15
|
const config = yield* T3Config;
|
|
14
16
|
const resolved = yield* config.resolve();
|
|
15
17
|
const ref = resolveCommandProjectRef({
|
|
16
18
|
value: Option.getOrUndefined(input.project),
|
|
17
|
-
|
|
18
|
-
cwd:
|
|
19
|
+
scope: t3CliEnv.scope,
|
|
20
|
+
cwd: cliRuntime.cwd,
|
|
19
21
|
isLocal: resolved.local,
|
|
20
22
|
});
|
|
21
23
|
if (ref === undefined) {
|
|
@@ -23,7 +25,7 @@ export const requireCommandProjectRef = Effect.fn("requireCommandProjectRef")(fu
|
|
|
23
25
|
new ProjectLookupError({
|
|
24
26
|
message:
|
|
25
27
|
"project is required: pass --project, set T3CODE_PROJECT_ROOT / T3CODE_PROJECT_ID, or use local auth",
|
|
26
|
-
ref:
|
|
28
|
+
ref: cliRuntime.cwd,
|
|
27
29
|
}),
|
|
28
30
|
);
|
|
29
31
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CliRuntime, layer } from "./service.ts";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as Context from "effect/Context";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
|
|
4
|
+
export class CliRuntime extends Context.Service<
|
|
5
|
+
CliRuntime,
|
|
6
|
+
{
|
|
7
|
+
readonly cwd: string;
|
|
8
|
+
readonly stdoutIsTTY: boolean;
|
|
9
|
+
readonly stderrIsTTY: boolean;
|
|
10
|
+
}
|
|
11
|
+
>()("t3cli/CliRuntime") {
|
|
12
|
+
static layerTest = (input?: {
|
|
13
|
+
readonly cwd?: string;
|
|
14
|
+
readonly stdoutIsTTY?: boolean;
|
|
15
|
+
readonly stderrIsTTY?: boolean;
|
|
16
|
+
}) =>
|
|
17
|
+
Layer.succeed(CliRuntime, {
|
|
18
|
+
cwd: input?.cwd ?? process.cwd(),
|
|
19
|
+
stdoutIsTTY: input?.stdoutIsTTY ?? false,
|
|
20
|
+
stderrIsTTY: input?.stderrIsTTY ?? false,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const layer = Layer.succeed(CliRuntime, {
|
|
25
|
+
cwd: process.cwd(),
|
|
26
|
+
stdoutIsTTY: process.stdout.isTTY ?? false,
|
|
27
|
+
stderrIsTTY: process.stderr.isTTY ?? false,
|
|
28
|
+
});
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
import type { T3CliEnvScope } from "../../config/env/env.ts";
|
|
2
|
+
|
|
1
3
|
export function resolveProjectRef(input: {
|
|
2
4
|
readonly value: string | undefined;
|
|
3
|
-
readonly
|
|
5
|
+
readonly scope: T3CliEnvScope;
|
|
4
6
|
}): string | undefined {
|
|
5
7
|
if (input.value !== undefined && input.value.length > 0) {
|
|
6
8
|
return input.value;
|
|
7
9
|
}
|
|
8
|
-
const fromRoot = input.
|
|
10
|
+
const fromRoot = input.scope.t3codeProjectRoot;
|
|
9
11
|
if (fromRoot !== undefined && fromRoot.length > 0) {
|
|
10
12
|
return fromRoot;
|
|
11
13
|
}
|
|
12
|
-
const fromId = input.
|
|
14
|
+
const fromId = input.scope.t3codeProjectId;
|
|
13
15
|
if (fromId !== undefined && fromId.length > 0) {
|
|
14
16
|
return fromId;
|
|
15
17
|
}
|
|
@@ -18,11 +20,11 @@ export function resolveProjectRef(input: {
|
|
|
18
20
|
|
|
19
21
|
export function resolveCommandProjectRef(input: {
|
|
20
22
|
readonly value: string | undefined;
|
|
21
|
-
readonly
|
|
23
|
+
readonly scope: T3CliEnvScope;
|
|
22
24
|
readonly cwd: string;
|
|
23
25
|
readonly isLocal?: boolean;
|
|
24
26
|
}): string | undefined {
|
|
25
|
-
const explicit = resolveProjectRef({ value: input.value,
|
|
27
|
+
const explicit = resolveProjectRef({ value: input.value, scope: input.scope });
|
|
26
28
|
if (explicit !== undefined) {
|
|
27
29
|
return explicit;
|
|
28
30
|
}
|
|
@@ -34,13 +36,13 @@ export function resolveCommandProjectRef(input: {
|
|
|
34
36
|
|
|
35
37
|
export function resolveWorktreePath(input: {
|
|
36
38
|
readonly value: string | undefined;
|
|
37
|
-
readonly
|
|
39
|
+
readonly scope: T3CliEnvScope;
|
|
38
40
|
readonly inferred?: string;
|
|
39
41
|
}): string | undefined {
|
|
40
42
|
if (input.value !== undefined && input.value.length > 0) {
|
|
41
43
|
return input.value;
|
|
42
44
|
}
|
|
43
|
-
const fromEnv = input.
|
|
45
|
+
const fromEnv = input.scope.t3codeWorktreePath;
|
|
44
46
|
if (fromEnv !== undefined && fromEnv.length > 0) {
|
|
45
47
|
return fromEnv;
|
|
46
48
|
}
|
|
@@ -49,12 +51,12 @@ export function resolveWorktreePath(input: {
|
|
|
49
51
|
|
|
50
52
|
export function resolveThreadId(input: {
|
|
51
53
|
readonly value: string | undefined;
|
|
52
|
-
readonly
|
|
54
|
+
readonly scope: T3CliEnvScope;
|
|
53
55
|
}): string | undefined {
|
|
54
56
|
if (input.value !== undefined && input.value.length > 0) {
|
|
55
57
|
return input.value;
|
|
56
58
|
}
|
|
57
|
-
const fromEnv = input.
|
|
59
|
+
const fromEnv = input.scope.t3codeThreadId;
|
|
58
60
|
if (fromEnv !== undefined && fromEnv.length > 0) {
|
|
59
61
|
return fromEnv;
|
|
60
62
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { Argument, Command } from "effect/unstable/cli";
|
|
3
|
+
|
|
4
|
+
import { T3Application } from "../../application/service.ts";
|
|
5
|
+
import { extraArgsConfig } from "../extra-args.ts";
|
|
6
|
+
import { threadFlag } from "../flags.ts";
|
|
7
|
+
import { requireCommandThreadId } from "./scope.ts";
|
|
8
|
+
import { runAttachedTerminalSession, toTerminalAttachTarget } from "./shared.ts";
|
|
9
|
+
|
|
10
|
+
export const attachTerminalCommand = Command.make(
|
|
11
|
+
"attach",
|
|
12
|
+
{
|
|
13
|
+
thread: threadFlag,
|
|
14
|
+
terminalId: Argument.string("terminal-id"),
|
|
15
|
+
...extraArgsConfig,
|
|
16
|
+
},
|
|
17
|
+
({ thread, terminalId }) =>
|
|
18
|
+
Effect.gen(function* () {
|
|
19
|
+
const application = yield* T3Application;
|
|
20
|
+
const threadId = yield* requireCommandThreadId({ thread });
|
|
21
|
+
const terminal = yield* application.getTerminal({
|
|
22
|
+
threadId,
|
|
23
|
+
terminalId,
|
|
24
|
+
});
|
|
25
|
+
yield* runAttachedTerminalSession({
|
|
26
|
+
terminal: toTerminalAttachTarget(terminal),
|
|
27
|
+
});
|
|
28
|
+
}),
|
|
29
|
+
).pipe(Command.withDescription("attach to a terminal"));
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Option from "effect/Option";
|
|
3
|
+
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
4
|
+
|
|
5
|
+
import { extraArgsConfig } from "../extra-args.ts";
|
|
6
|
+
import { formatTerminalCreatedHuman } from "../format/terminal.ts";
|
|
7
|
+
import { T3Application } from "../../application/service.ts";
|
|
8
|
+
import { CliRuntime } from "../../cli/runtime/service.ts";
|
|
9
|
+
import { loadT3CliEnv } from "../../config/env/env.ts";
|
|
10
|
+
import { formatFlag, threadFlag } from "../flags.ts";
|
|
11
|
+
import { resolveOutputFormat } from "../format/output.ts";
|
|
12
|
+
import { T3Output } from "../output/service.ts";
|
|
13
|
+
import { requireCommandThreadId } from "./scope.ts";
|
|
14
|
+
import { runAttachedTerminalSession, snapshotToTerminalAttachTarget } from "./shared.ts";
|
|
15
|
+
|
|
16
|
+
export const createTerminalCommand = Command.make(
|
|
17
|
+
"create",
|
|
18
|
+
{
|
|
19
|
+
thread: threadFlag,
|
|
20
|
+
command: Argument.string("command").pipe(Argument.optional),
|
|
21
|
+
id: Flag.string("id").pipe(Flag.optional),
|
|
22
|
+
attach: Flag.boolean("attach"),
|
|
23
|
+
format: formatFlag,
|
|
24
|
+
...extraArgsConfig,
|
|
25
|
+
},
|
|
26
|
+
({ thread, command, id, attach, format }) =>
|
|
27
|
+
Effect.gen(function* () {
|
|
28
|
+
const application = yield* T3Application;
|
|
29
|
+
const cliRuntime = yield* CliRuntime;
|
|
30
|
+
const t3CliEnv = yield* loadT3CliEnv;
|
|
31
|
+
const output = yield* T3Output;
|
|
32
|
+
const threadId = yield* requireCommandThreadId({ thread });
|
|
33
|
+
const terminalId = Option.getOrUndefined(id);
|
|
34
|
+
const commandValue = Option.getOrUndefined(command);
|
|
35
|
+
const snapshot = yield* application.createTerminal({
|
|
36
|
+
threadId,
|
|
37
|
+
...(terminalId !== undefined ? { terminalId } : {}),
|
|
38
|
+
...(commandValue !== undefined ? { command: commandValue } : {}),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (attach) {
|
|
42
|
+
yield* runAttachedTerminalSession({
|
|
43
|
+
terminal: snapshotToTerminalAttachTarget(snapshot),
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const resolvedFormat = resolveOutputFormat(format, cliRuntime, t3CliEnv, "json");
|
|
49
|
+
if (resolvedFormat === "json") {
|
|
50
|
+
yield* output.printJson(snapshot);
|
|
51
|
+
} else {
|
|
52
|
+
yield* output.printInfo(formatTerminalCreatedHuman(snapshot));
|
|
53
|
+
}
|
|
54
|
+
}),
|
|
55
|
+
).pipe(Command.withDescription("create a terminal for a thread"));
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
3
|
+
|
|
4
|
+
import { extraArgsConfig } from "../extra-args.ts";
|
|
5
|
+
import { requireDestructiveConfirmation } from "../interaction/confirm.ts";
|
|
6
|
+
import { formatTerminalDestroyedHuman } from "../format/terminal.ts";
|
|
7
|
+
import { T3Application } from "../../application/service.ts";
|
|
8
|
+
import { CliRuntime } from "../../cli/runtime/service.ts";
|
|
9
|
+
import { loadT3CliEnv } from "../../config/env/env.ts";
|
|
10
|
+
import { formatFlag, threadFlag, yesFlag } from "../flags.ts";
|
|
11
|
+
import { resolveOutputFormat } from "../format/output.ts";
|
|
12
|
+
import { T3Output } from "../output/service.ts";
|
|
13
|
+
import { requireCommandThreadId } from "./scope.ts";
|
|
14
|
+
|
|
15
|
+
export const destroyTerminalCommand = Command.make(
|
|
16
|
+
"destroy",
|
|
17
|
+
{
|
|
18
|
+
thread: threadFlag,
|
|
19
|
+
terminalId: Argument.string("terminal-id"),
|
|
20
|
+
quiet: Flag.boolean("quiet"),
|
|
21
|
+
yes: yesFlag,
|
|
22
|
+
format: formatFlag,
|
|
23
|
+
...extraArgsConfig,
|
|
24
|
+
},
|
|
25
|
+
({ thread, terminalId, quiet, yes, format }) =>
|
|
26
|
+
Effect.gen(function* () {
|
|
27
|
+
const application = yield* T3Application;
|
|
28
|
+
const cliRuntime = yield* CliRuntime;
|
|
29
|
+
const t3CliEnv = yield* loadT3CliEnv;
|
|
30
|
+
const output = yield* T3Output;
|
|
31
|
+
const threadId = yield* requireCommandThreadId({ thread });
|
|
32
|
+
yield* requireDestructiveConfirmation({
|
|
33
|
+
message: `Destroy terminal ${terminalId} in thread ${threadId} and delete its history?`,
|
|
34
|
+
yes,
|
|
35
|
+
cliRuntime,
|
|
36
|
+
t3CliEnv,
|
|
37
|
+
});
|
|
38
|
+
const terminal = {
|
|
39
|
+
threadId,
|
|
40
|
+
terminalId,
|
|
41
|
+
};
|
|
42
|
+
yield* application.destroyTerminal(terminal);
|
|
43
|
+
if (quiet) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const resolvedFormat = resolveOutputFormat(format, cliRuntime, t3CliEnv, "json");
|
|
47
|
+
if (resolvedFormat === "json") {
|
|
48
|
+
yield* output.printJson({
|
|
49
|
+
threadId: terminal.threadId,
|
|
50
|
+
terminalId: terminal.terminalId,
|
|
51
|
+
destroyed: true,
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
yield* output.printInfo(
|
|
56
|
+
formatTerminalDestroyedHuman({
|
|
57
|
+
terminalId,
|
|
58
|
+
threadId,
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
}),
|
|
62
|
+
).pipe(Command.withDescription("destroy a terminal and its history"));
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import "vite-plus/test/config";
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vite-plus/test";
|
|
4
|
+
|
|
5
|
+
import { decodeBase64Payload, decodeHexPayload } from "./encoding.ts";
|
|
6
|
+
import { filterAttachStreamEvent } from "./shared.ts";
|
|
7
|
+
|
|
8
|
+
describe("terminal encoding", () => {
|
|
9
|
+
it("preserves non-utf8 bytes in hex payloads", () => {
|
|
10
|
+
expect(decodeHexPayload("ff")).toBe("\xff");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("preserves non-utf8 bytes in base64 payloads", () => {
|
|
14
|
+
expect(decodeBase64Payload("/w==")).toBe("\xff");
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("filterAttachStreamEvent", () => {
|
|
19
|
+
const snapshotEvent = {
|
|
20
|
+
type: "snapshot" as const,
|
|
21
|
+
snapshot: {
|
|
22
|
+
threadId: "thread-1",
|
|
23
|
+
terminalId: "term-1",
|
|
24
|
+
cwd: "/tmp",
|
|
25
|
+
worktreePath: null,
|
|
26
|
+
status: "running" as const,
|
|
27
|
+
pid: 1,
|
|
28
|
+
history: "hello",
|
|
29
|
+
exitCode: null,
|
|
30
|
+
exitSignal: null,
|
|
31
|
+
label: "shell",
|
|
32
|
+
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
33
|
+
sequence: 3,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
it("keeps snapshot history when --history is set with --from-sequence", () => {
|
|
38
|
+
const filtered = filterAttachStreamEvent(snapshotEvent, {
|
|
39
|
+
includeHistory: true,
|
|
40
|
+
fromSequence: 5,
|
|
41
|
+
});
|
|
42
|
+
expect(filtered?.type).toBe("snapshot");
|
|
43
|
+
if (filtered?.type === "snapshot") {
|
|
44
|
+
expect(filtered.snapshot.history).toBe("hello");
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("filters sequenced events inclusively from --from-sequence", () => {
|
|
49
|
+
const outputEvent = {
|
|
50
|
+
type: "output" as const,
|
|
51
|
+
threadId: "thread-1",
|
|
52
|
+
terminalId: "term-1",
|
|
53
|
+
sequence: 5,
|
|
54
|
+
data: "x",
|
|
55
|
+
};
|
|
56
|
+
expect(
|
|
57
|
+
filterAttachStreamEvent(outputEvent, { includeHistory: false, fromSequence: 5 }),
|
|
58
|
+
).toEqual(outputEvent);
|
|
59
|
+
expect(
|
|
60
|
+
filterAttachStreamEvent(outputEvent, { includeHistory: false, fromSequence: 6 }),
|
|
61
|
+
).toBeNull();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function bytesToLatin1String(bytes: Uint8Array): string {
|
|
2
|
+
return Buffer.from(bytes).toString("latin1");
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function decodeHexPayload(value: string): string | undefined {
|
|
6
|
+
const normalized = value.trim();
|
|
7
|
+
if (
|
|
8
|
+
normalized.length === 0 ||
|
|
9
|
+
normalized.length % 2 !== 0 ||
|
|
10
|
+
!/^[0-9a-fA-F]+$/.test(normalized)
|
|
11
|
+
) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return Buffer.from(normalized, "hex").toString("latin1");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function decodeBase64Payload(value: string): string | undefined {
|
|
18
|
+
const normalized = value.trim();
|
|
19
|
+
if (normalized.length === 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(normalized)) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return Buffer.from(normalized, "base64").toString("latin1");
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
|
|
3
|
+
export class TerminalCliError extends Schema.TaggedErrorClass<TerminalCliError>()(
|
|
4
|
+
"TerminalCliError",
|
|
5
|
+
{
|
|
6
|
+
message: Schema.String,
|
|
7
|
+
threadId: Schema.optionalKey(Schema.String),
|
|
8
|
+
terminalId: Schema.optionalKey(Schema.String),
|
|
9
|
+
},
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
export class TerminalIoError extends Schema.TaggedErrorClass<TerminalIoError>()("TerminalIoError", {
|
|
13
|
+
message: Schema.String,
|
|
14
|
+
}) {}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
import * as Queue from "effect/Queue";
|
|
4
|
+
import * as Stream from "effect/Stream";
|
|
5
|
+
|
|
6
|
+
import { TerminalIoError } from "./error.ts";
|
|
7
|
+
import { TerminalIo, type RawTerminalIoSession, type TerminalWindowSize } from "./io-service.ts";
|
|
8
|
+
|
|
9
|
+
const DEFAULT_COLS = 80;
|
|
10
|
+
const DEFAULT_ROWS = 24;
|
|
11
|
+
const INTERACTIVE_TTY_MESSAGE = "terminal attach requires an interactive TTY on stdin and stdout";
|
|
12
|
+
|
|
13
|
+
const requireInteractiveTty = Effect.gen(function* () {
|
|
14
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || process.stdin.setRawMode === undefined) {
|
|
15
|
+
return yield* Effect.fail(new TerminalIoError({ message: INTERACTIVE_TTY_MESSAGE }));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
stdin: process.stdin,
|
|
20
|
+
stdout: process.stdout,
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const NodeTerminalIoLive = Layer.succeed(TerminalIo, {
|
|
25
|
+
getWindowSize: requireInteractiveTty.pipe(Effect.map(readWindowSize)),
|
|
26
|
+
writeOutput: (text: string) =>
|
|
27
|
+
Effect.sync(() => {
|
|
28
|
+
process.stdout.write(text);
|
|
29
|
+
}),
|
|
30
|
+
openRawSession: Effect.acquireRelease(
|
|
31
|
+
Effect.gen(function* () {
|
|
32
|
+
const tty = yield* requireInteractiveTty;
|
|
33
|
+
const input = yield* Queue.unbounded<Uint8Array>();
|
|
34
|
+
const resize = yield* Queue.unbounded<TerminalWindowSize>();
|
|
35
|
+
|
|
36
|
+
const onResize = () => {
|
|
37
|
+
Effect.runFork(Queue.offer(resize, readWindowSize(tty)).pipe(Effect.ignore));
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const onData = (chunk: Buffer | string) => {
|
|
41
|
+
Effect.runFork(
|
|
42
|
+
Queue.offer(input, typeof chunk === "string" ? Buffer.from(chunk) : chunk).pipe(
|
|
43
|
+
Effect.ignore,
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
tty.stdin.resume();
|
|
49
|
+
tty.stdin.setRawMode(true);
|
|
50
|
+
process.on("SIGWINCH", onResize);
|
|
51
|
+
tty.stdin.on("data", onData);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
tty,
|
|
55
|
+
input,
|
|
56
|
+
resize,
|
|
57
|
+
onData,
|
|
58
|
+
onResize,
|
|
59
|
+
session: {
|
|
60
|
+
input: Stream.fromQueue(input),
|
|
61
|
+
resize: Stream.fromQueue(resize),
|
|
62
|
+
} satisfies RawTerminalIoSession,
|
|
63
|
+
};
|
|
64
|
+
}),
|
|
65
|
+
({ tty, input, resize, onData, onResize }) =>
|
|
66
|
+
Effect.gen(function* () {
|
|
67
|
+
tty.stdin.off("data", onData);
|
|
68
|
+
process.off("SIGWINCH", onResize);
|
|
69
|
+
tty.stdin.setRawMode(false);
|
|
70
|
+
tty.stdin.pause();
|
|
71
|
+
yield* Queue.shutdown(input);
|
|
72
|
+
yield* Queue.shutdown(resize);
|
|
73
|
+
}),
|
|
74
|
+
).pipe(Effect.map(({ session }) => session)),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
function readWindowSize(tty: { readonly stdout: NodeJS.WriteStream }): TerminalWindowSize {
|
|
78
|
+
return {
|
|
79
|
+
cols: tty.stdout.columns ?? DEFAULT_COLS,
|
|
80
|
+
rows: tty.stdout.rows ?? DEFAULT_ROWS,
|
|
81
|
+
};
|
|
82
|
+
}
|