Repository navigation
Expand file tree
/
Copy pathenvironment.ts
More file actions
171 lines (162 loc) · 6.24 KB
/
Copy pathenvironment.ts
File metadata and controls
171 lines (162 loc) · 6.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import { AuthMcpClientAccess, EnvironmentId, type PeerLinkSummary } from "@t3tools/contracts";
import * as Console from "effect/Console";
import * as DateTime from "effect/DateTime";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as References from "effect/References";
import * as Schema from "effect/Schema";
import * as Argument from "effect/cli/Argument";
import * as Command from "effect/cli/Command";
import * as Flag from "effect/cli/Flag";
import * as GlobalFlag from "effect/cli/GlobalFlag";
import * as FetchHttpClient from "effect/http/FetchHttpClient";
import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
import * as ServerConfig from "../config.ts";
import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
import * as PeerLinks from "../peer/PeerLinks.ts";
import * as ServerSettings from "../serverSettings.ts";
import * as ManagedPolicy from "../policy/ManagedPolicy.ts";
import * as SqlitePersistence from "../persistence/Sqlite.ts";
import { authLocationFlags, type CliAuthLocationFlags, resolveCliAuthConfig } from "./config.ts";
/**
* Links live in this environment's database and secret store, so these
* commands work whether or not its server is running.
*/
const runWithPeerLinks = Effect.fn("environment.runWithPeerLinks")(function* <A, E>(
flags: CliAuthLocationFlags,
run: (links: PeerLinks.PeerLinks["Service"]) => Effect.Effect<A, E>,
options?: { readonly quietLogs?: boolean },
) {
const logLevel = yield* GlobalFlag.LogLevel;
const config = yield* resolveCliAuthConfig(flags, logLevel, options);
return yield* PeerLinks.PeerLinks.pipe(
Effect.flatMap(run),
Effect.provide(
PeerLinks.layer.pipe(
Layer.provide(
Layer.mergeAll(
ServerSecretStore.layer,
SqlitePersistence.layerCliConfig,
FetchHttpClient.layer,
),
),
Layer.provide(ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer))),
Layer.provide(
ServerSettings.layer.pipe(
Layer.provide(
Layer.mergeAll(
ServerSecretStore.layer,
SqlitePersistence.layerCliConfig,
ManagedPolicy.layer,
),
),
),
),
Layer.provide(ServerConfig.layer(config)),
Layer.provide(
Layer.succeed(References.MinimumLogLevel, options?.quietLogs ? "Error" : config.logLevel),
),
),
),
);
});
const jsonFlag = Flag.Boolean("json").pipe(
Flag.withDescription("Emit JSON instead of human-readable output."),
Flag.withDefault(false),
);
const encodeLinks = Schema.encodeEffect(
Schema.fromJsonString(Schema.Array(Schema.toEncoded(Schema.Unknown))),
);
const formatLinks = (links: ReadonlyArray<PeerLinkSummary>) =>
links.length === 0
? "No linked environments.\n"
: `${links
.map(
(link) =>
`${link.label} (${link.environmentId})\n ${link.status}, access ${link.access}, expires ${DateTime.formatIso(link.expiresAt)}\n ${link.urls.join(", ")}${link.lastError === null ? "" : `\n last error: ${link.lastError}`}`,
)
.join("\n\n")}\n`;
const environmentLinkCommand = Command.make("link", {
...authLocationFlags,
url: Argument.String("url").pipe(
Argument.withDescription(
"Where the other environment answers: its T3 Connect, Tailscale, LAN or loopback address.",
),
),
alternateUrl: Flag.String("url").pipe(
Flag.withDescription("Another address for the same environment, tried after the first."),
Flag.atLeast(0),
),
pairingCode: Flag.String("pairing-code").pipe(
Flag.withDescription("A pairing code from the other environment (`t3 pair` there)."),
),
access: Flag.Literals("access", AuthMcpClientAccess.literals).pipe(
Flag.withDescription(
"What this environment's agents may do there: read-only, or the broadest runtime mode.",
),
Flag.withDefault("approval-required" as const),
),
}).pipe(
Command.withDescription(
"Link another T3 Code environment, so agents here can work there. It shows up there under Settings → Connections, where it can be revoked.",
),
Command.withHandler((flags) =>
runWithPeerLinks(flags, (links) =>
Effect.gen(function* () {
const linked = yield* links.link({
url: flags.url,
alternateUrls: flags.alternateUrl,
pairingCode: flags.pairingCode,
access: flags.access,
});
yield* Console.log(
`Linked ${linked.label} (${linked.environmentId}) with ${linked.access} access, until ${DateTime.formatIso(linked.expiresAt)}.\n`,
);
}),
),
),
);
const environmentListCommand = Command.make("list", { ...authLocationFlags, json: jsonFlag }).pipe(
Command.withDescription("List linked environments and whether each answers now."),
Command.withHandler((flags) =>
runWithPeerLinks(
flags,
(links) =>
Effect.gen(function* () {
const listed = yield* links.list;
yield* Console.log(flags.json ? `${yield* encodeLinks(listed)}\n` : formatLinks(listed));
}),
{ quietLogs: flags.json },
),
),
);
const environmentUnlinkCommand = Command.make("unlink", {
...authLocationFlags,
environmentId: Argument.String("environment-id").pipe(
Argument.withDescription("The linked environment's id, from `t3 environment list`."),
),
}).pipe(
Command.withDescription(
"Forget a linked environment here. Revoke it in that environment's Connections to end its session there too.",
),
Command.withHandler((flags) =>
runWithPeerLinks(flags, (links) =>
Effect.gen(function* () {
const removed = yield* links.unlink(EnvironmentId.make(flags.environmentId));
yield* Console.log(
removed
? `Forgot ${flags.environmentId}. Revoke it in that environment's Connections to end its session there.\n`
: `No linked environment ${flags.environmentId}.\n`,
);
}),
),
),
);
export const environmentCommand = Command.make("environment").pipe(
Command.withDescription("Link other T3 Code environments, so agents here can work there."),
Command.withSubcommands([
environmentLinkCommand,
environmentListCommand,
environmentUnlinkCommand,
]),
);