@gitterm/sdk 0.0.7 → 0.1.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 +109 -0
- package/dist/direct/ascii.d.ts +2 -0
- package/dist/direct/client.d.ts +58 -0
- package/dist/direct/daytona.d.ts +2 -0
- package/dist/direct/e2b.d.ts +2 -0
- package/dist/direct/exedev.d.ts +2 -0
- package/dist/direct/index.d.ts +8 -0
- package/dist/direct/index.js +1802 -0
- package/dist/direct/provisioning.d.ts +34 -0
- package/dist/direct/railway.d.ts +2 -0
- package/dist/direct/types.d.ts +285 -0
- package/dist/direct/vercel.d.ts +2 -0
- package/dist/types.d.ts +21 -1
- package/package.json +15 -4
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DirectProviderWorkspaceInput, DirectProvisioningPlan, DirectModelCredential, DirectWorkspaceCreateInput, DirectWorkspaceRuntime } from "./types.js";
|
|
2
|
+
export declare const DIRECT_OPENCODE_PORT = 4096;
|
|
3
|
+
export declare const DIRECT_OPENCODE_COMMAND = "opencode serve --hostname 0.0.0.0 --port 4096";
|
|
4
|
+
export declare const DIRECT_OPENCODE_SERVER_IMAGE = "opeoginni/gitterm-opencode-server:latest";
|
|
5
|
+
export declare const DIRECT_E2B_TEMPLATES: {
|
|
6
|
+
readonly standard: "gitterm-opencode-server";
|
|
7
|
+
readonly large: "gitterm-opencode-server-lg";
|
|
8
|
+
};
|
|
9
|
+
export declare function resolveDirectImage(image?: string): string;
|
|
10
|
+
export declare function directModelAuth(credential: DirectModelCredential): {
|
|
11
|
+
enterpriseUrl?: string | undefined;
|
|
12
|
+
accountId?: string | undefined;
|
|
13
|
+
type: "oauth";
|
|
14
|
+
refresh: string;
|
|
15
|
+
access: string;
|
|
16
|
+
expires: number;
|
|
17
|
+
} | {
|
|
18
|
+
metadata?: Record<string, string> | undefined;
|
|
19
|
+
type: "api";
|
|
20
|
+
key: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function repositoryName(url: string): string;
|
|
23
|
+
export declare function buildDirectProvisioningPlan(input: DirectWorkspaceCreateInput & {
|
|
24
|
+
id: string;
|
|
25
|
+
lifecycle: DirectProviderWorkspaceInput["lifecycle"];
|
|
26
|
+
password: string;
|
|
27
|
+
}): DirectProvisioningPlan;
|
|
28
|
+
export declare function railwayContainerEnvironment(plan: DirectProvisioningPlan): Record<string, string>;
|
|
29
|
+
export declare function setupCommandScript(commands: string[]): string;
|
|
30
|
+
export declare function shellQuote(value: string): string;
|
|
31
|
+
export declare function cloneRepositoryScript(repository: NonNullable<DirectProvisioningPlan["repository"]>, directory: string): string;
|
|
32
|
+
export declare function pinFloatingDockerImage(image: string): Promise<string>;
|
|
33
|
+
export declare function basicAuthHeader(password: string): string;
|
|
34
|
+
export declare function waitForDirectRuntime(runtime: DirectWorkspaceRuntime, timeoutMs?: number): Promise<void>;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
export type DirectWorkspaceLifecycle = "ephemeral" | "persistent";
|
|
2
|
+
export type DirectWorkspaceStatus = "pending" | "running" | "paused" | "failed" | "terminated" | "unknown";
|
|
3
|
+
export type DirectProviderCapabilities = {
|
|
4
|
+
persistence: "supported" | "unsupported";
|
|
5
|
+
recommendedLifecycle: DirectWorkspaceLifecycle;
|
|
6
|
+
supportsPause: boolean;
|
|
7
|
+
/** Whether manually pausing an ephemeral workspace preserves its filesystem/session. */
|
|
8
|
+
ephemeralPause: "stateful" | "state-losing" | "unsupported";
|
|
9
|
+
supportsKeepAlive: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type DirectApiModelCredential = {
|
|
12
|
+
providerName: string;
|
|
13
|
+
type?: "api";
|
|
14
|
+
apiKey: string;
|
|
15
|
+
metadata?: Record<string, string>;
|
|
16
|
+
};
|
|
17
|
+
export type DirectOAuthModelCredential = {
|
|
18
|
+
providerName: string;
|
|
19
|
+
type: "oauth";
|
|
20
|
+
refreshToken: string;
|
|
21
|
+
/** May be omitted when OpenCode should refresh immediately. */
|
|
22
|
+
accessToken?: string;
|
|
23
|
+
/** Unix epoch time in milliseconds. Defaults to expired when omitted. */
|
|
24
|
+
expiresAt?: number;
|
|
25
|
+
accountId?: string;
|
|
26
|
+
enterpriseUrl?: string;
|
|
27
|
+
};
|
|
28
|
+
export type DirectModelCredential = DirectApiModelCredential | DirectOAuthModelCredential;
|
|
29
|
+
export type DirectAuthPrompt = {
|
|
30
|
+
type: "text";
|
|
31
|
+
key: string;
|
|
32
|
+
message: string;
|
|
33
|
+
placeholder?: string;
|
|
34
|
+
when?: {
|
|
35
|
+
key: string;
|
|
36
|
+
op: "eq" | "neq";
|
|
37
|
+
value: string;
|
|
38
|
+
};
|
|
39
|
+
} | {
|
|
40
|
+
type: "select";
|
|
41
|
+
key: string;
|
|
42
|
+
message: string;
|
|
43
|
+
options: Array<{
|
|
44
|
+
label: string;
|
|
45
|
+
value: string;
|
|
46
|
+
hint?: string;
|
|
47
|
+
}>;
|
|
48
|
+
when?: {
|
|
49
|
+
key: string;
|
|
50
|
+
op: "eq" | "neq";
|
|
51
|
+
value: string;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
export type DirectAuthMethod = {
|
|
55
|
+
type: "oauth";
|
|
56
|
+
id: string;
|
|
57
|
+
label: string;
|
|
58
|
+
prompts?: DirectAuthPrompt[];
|
|
59
|
+
} | {
|
|
60
|
+
type: "key";
|
|
61
|
+
label?: string;
|
|
62
|
+
} | {
|
|
63
|
+
type: "env";
|
|
64
|
+
names: string[];
|
|
65
|
+
};
|
|
66
|
+
export type DirectAuthIntegration = {
|
|
67
|
+
id: string;
|
|
68
|
+
name: string;
|
|
69
|
+
methods: DirectAuthMethod[];
|
|
70
|
+
connections: Array<{
|
|
71
|
+
type: "credential";
|
|
72
|
+
id: string;
|
|
73
|
+
label: string;
|
|
74
|
+
} | {
|
|
75
|
+
type: "env";
|
|
76
|
+
name: string;
|
|
77
|
+
}>;
|
|
78
|
+
};
|
|
79
|
+
export type DirectAuthAttempt = {
|
|
80
|
+
id: string;
|
|
81
|
+
workspaceId: string;
|
|
82
|
+
integrationId: string;
|
|
83
|
+
url: string;
|
|
84
|
+
instructions: string;
|
|
85
|
+
mode: "auto" | "code";
|
|
86
|
+
createdAt: number;
|
|
87
|
+
expiresAt: number;
|
|
88
|
+
};
|
|
89
|
+
export type DirectAuthAttemptStatus = {
|
|
90
|
+
status: "pending";
|
|
91
|
+
createdAt: number;
|
|
92
|
+
expiresAt: number;
|
|
93
|
+
} | {
|
|
94
|
+
status: "complete";
|
|
95
|
+
createdAt: number;
|
|
96
|
+
expiresAt: number;
|
|
97
|
+
} | {
|
|
98
|
+
status: "failed";
|
|
99
|
+
message: string;
|
|
100
|
+
createdAt: number;
|
|
101
|
+
expiresAt: number;
|
|
102
|
+
} | {
|
|
103
|
+
status: "expired";
|
|
104
|
+
createdAt: number;
|
|
105
|
+
expiresAt: number;
|
|
106
|
+
};
|
|
107
|
+
export type DirectAuthWaitOptions = {
|
|
108
|
+
timeoutMs?: number;
|
|
109
|
+
pollIntervalMs?: number;
|
|
110
|
+
};
|
|
111
|
+
export type DirectWorkspaceCreateInput = {
|
|
112
|
+
id?: string;
|
|
113
|
+
repo?: string;
|
|
114
|
+
branch?: string;
|
|
115
|
+
baseCommit?: string;
|
|
116
|
+
checkoutRef?: string;
|
|
117
|
+
repositoryCredentials?: {
|
|
118
|
+
username?: string;
|
|
119
|
+
token: string;
|
|
120
|
+
};
|
|
121
|
+
lifecycle?: DirectWorkspaceLifecycle;
|
|
122
|
+
environmentVariables?: Record<string, string>;
|
|
123
|
+
modelCredentials?: DirectModelCredential[];
|
|
124
|
+
setupCommands?: string[];
|
|
125
|
+
opencode?: {
|
|
126
|
+
config?: Record<string, unknown>;
|
|
127
|
+
plugins?: string[];
|
|
128
|
+
skills?: Array<{
|
|
129
|
+
name: string;
|
|
130
|
+
content: string;
|
|
131
|
+
}>;
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
export type DirectWorkspaceRuntime = {
|
|
135
|
+
url: string;
|
|
136
|
+
directory: string;
|
|
137
|
+
headers?: Record<string, string>;
|
|
138
|
+
password?: string;
|
|
139
|
+
};
|
|
140
|
+
export type DirectWorkspace = {
|
|
141
|
+
id: string;
|
|
142
|
+
provider: string;
|
|
143
|
+
externalId: string;
|
|
144
|
+
status: DirectWorkspaceStatus;
|
|
145
|
+
lifecycle: DirectWorkspaceLifecycle;
|
|
146
|
+
runtime: DirectWorkspaceRuntime;
|
|
147
|
+
createdAt: string;
|
|
148
|
+
};
|
|
149
|
+
export type DirectProviderWorkspaceInput = DirectWorkspaceCreateInput & {
|
|
150
|
+
id: string;
|
|
151
|
+
lifecycle: DirectWorkspaceLifecycle;
|
|
152
|
+
password: string;
|
|
153
|
+
provisioning: DirectProvisioningPlan;
|
|
154
|
+
};
|
|
155
|
+
export type DirectAgentFile = {
|
|
156
|
+
path: string;
|
|
157
|
+
contentBase64: string;
|
|
158
|
+
};
|
|
159
|
+
export type DirectProvisioningPlan = {
|
|
160
|
+
workspaceId: string;
|
|
161
|
+
lifecycle: DirectWorkspaceLifecycle;
|
|
162
|
+
repository?: {
|
|
163
|
+
url: string;
|
|
164
|
+
name: string;
|
|
165
|
+
branch?: string;
|
|
166
|
+
checkoutRef?: string;
|
|
167
|
+
baseCommit?: string;
|
|
168
|
+
authUsername?: string;
|
|
169
|
+
authToken?: string;
|
|
170
|
+
};
|
|
171
|
+
agent: {
|
|
172
|
+
files: DirectAgentFile[];
|
|
173
|
+
environmentVariables: Record<string, string>;
|
|
174
|
+
command: string;
|
|
175
|
+
port: number;
|
|
176
|
+
};
|
|
177
|
+
setupCommands: string[];
|
|
178
|
+
};
|
|
179
|
+
export interface DirectProviderAdapter {
|
|
180
|
+
readonly name: string;
|
|
181
|
+
readonly capabilities: DirectProviderCapabilities;
|
|
182
|
+
create(input: DirectProviderWorkspaceInput): Promise<{
|
|
183
|
+
externalId: string;
|
|
184
|
+
runtime: DirectWorkspaceRuntime;
|
|
185
|
+
}>;
|
|
186
|
+
status(workspace: DirectWorkspace): Promise<DirectWorkspaceStatus>;
|
|
187
|
+
pause?(workspace: DirectWorkspace): Promise<void>;
|
|
188
|
+
resume?(workspace: DirectWorkspace): Promise<Partial<DirectWorkspaceRuntime> | void>;
|
|
189
|
+
terminate(workspace: DirectWorkspace): Promise<void>;
|
|
190
|
+
keepAlive?(workspace: DirectWorkspace, timeoutMs: number): Promise<void>;
|
|
191
|
+
}
|
|
192
|
+
export type E2BDirectProviderConfig = {
|
|
193
|
+
type: "e2b";
|
|
194
|
+
apiKey: string;
|
|
195
|
+
/** Optional. Defaults to standard. */
|
|
196
|
+
size?: "standard" | "large";
|
|
197
|
+
/** Optional. Overrides size with a specific E2B template. */
|
|
198
|
+
templateId?: string;
|
|
199
|
+
timeoutMs?: number;
|
|
200
|
+
};
|
|
201
|
+
export type DaytonaDirectProviderConfig = {
|
|
202
|
+
type: "daytona";
|
|
203
|
+
apiKey: string;
|
|
204
|
+
target: "us" | "eu";
|
|
205
|
+
/** Optional. Defaults to the public Gitterm OpenCode server image. Floating tags are pinned to a digest. */
|
|
206
|
+
image?: string;
|
|
207
|
+
cpu?: number;
|
|
208
|
+
memory?: number;
|
|
209
|
+
disk?: number;
|
|
210
|
+
};
|
|
211
|
+
export type VercelDirectProviderConfig = {
|
|
212
|
+
type: "vercel";
|
|
213
|
+
apiToken: string;
|
|
214
|
+
teamId: string;
|
|
215
|
+
projectId: string;
|
|
216
|
+
/** Optional VCR image. When omitted, the configured Node runtime is used. */
|
|
217
|
+
image?: string;
|
|
218
|
+
runtime?: "node26" | "node24" | "node22" | "python3.13";
|
|
219
|
+
runtimeSetupCommands?: string[];
|
|
220
|
+
vcpus?: number;
|
|
221
|
+
timeoutMs?: number;
|
|
222
|
+
};
|
|
223
|
+
export type AsciiDirectProviderConfig = {
|
|
224
|
+
type: "ascii";
|
|
225
|
+
apiKey: string;
|
|
226
|
+
size?: "small" | "default" | "large";
|
|
227
|
+
runtimeSetupCommands?: string[];
|
|
228
|
+
timeoutMs?: number | null;
|
|
229
|
+
};
|
|
230
|
+
export type ExeDevDirectProviderConfig = {
|
|
231
|
+
type: "exedev";
|
|
232
|
+
/**
|
|
233
|
+
* Token `cmds` must include `new`, `ls`, `ssh`, `share`, `ssh-key`, `pause`, `resume`, and `rm`.
|
|
234
|
+
* Default tokens cannot provision or clean up a Gitterm workspace.
|
|
235
|
+
*/
|
|
236
|
+
apiToken: string;
|
|
237
|
+
/** Optional. Defaults to the public Gitterm OpenCode server image. */
|
|
238
|
+
image?: string;
|
|
239
|
+
cpu?: number;
|
|
240
|
+
memory?: string;
|
|
241
|
+
disk?: string;
|
|
242
|
+
runtimeSetupCommands?: string[];
|
|
243
|
+
};
|
|
244
|
+
export type RailwayDirectProviderConfig = {
|
|
245
|
+
type: "railway";
|
|
246
|
+
apiToken: string;
|
|
247
|
+
apiUrl?: string;
|
|
248
|
+
projectId: string;
|
|
249
|
+
environmentId: string;
|
|
250
|
+
region?: string;
|
|
251
|
+
/** Optional. Defaults to the public Gitterm OpenCode server image. */
|
|
252
|
+
image?: string;
|
|
253
|
+
runtimePort?: number;
|
|
254
|
+
};
|
|
255
|
+
export type DirectProviderConfig = E2BDirectProviderConfig | DaytonaDirectProviderConfig | VercelDirectProviderConfig | AsciiDirectProviderConfig | ExeDevDirectProviderConfig | RailwayDirectProviderConfig;
|
|
256
|
+
export type DirectRunCreateInput = {
|
|
257
|
+
workspace: DirectWorkspace;
|
|
258
|
+
prompt: string;
|
|
259
|
+
title?: string;
|
|
260
|
+
agent?: string;
|
|
261
|
+
model?: string;
|
|
262
|
+
/** Reuse this native OpenCode session for conversational context. */
|
|
263
|
+
sessionId?: string;
|
|
264
|
+
};
|
|
265
|
+
export type DirectRun = {
|
|
266
|
+
id: string;
|
|
267
|
+
workspaceId: string;
|
|
268
|
+
sessionId: string;
|
|
269
|
+
messageId: string;
|
|
270
|
+
title: string;
|
|
271
|
+
status: "running" | "retrying" | "completed" | "failed" | "cancelled";
|
|
272
|
+
error: string | null;
|
|
273
|
+
finalText: string | null;
|
|
274
|
+
submittedAt: string;
|
|
275
|
+
};
|
|
276
|
+
export type DirectRunMessage = {
|
|
277
|
+
id: string;
|
|
278
|
+
role: "user" | "assistant";
|
|
279
|
+
text: string;
|
|
280
|
+
error: string | null;
|
|
281
|
+
};
|
|
282
|
+
export type DirectRunWaitOptions = {
|
|
283
|
+
timeoutMs?: number;
|
|
284
|
+
pollIntervalMs?: number;
|
|
285
|
+
};
|
package/dist/types.d.ts
CHANGED
|
@@ -115,6 +115,11 @@ type ExeDevResources = {
|
|
|
115
115
|
memory?: string;
|
|
116
116
|
disk?: string;
|
|
117
117
|
};
|
|
118
|
+
/** E2B fixes CPU/RAM per template, so resources select a template build. */
|
|
119
|
+
type E2bResources = {
|
|
120
|
+
templateId?: string;
|
|
121
|
+
sshTemplateId?: string;
|
|
122
|
+
};
|
|
118
123
|
export type WorkspaceProviderSelection = {
|
|
119
124
|
type: "railway";
|
|
120
125
|
providerId?: string;
|
|
@@ -137,7 +142,11 @@ export type WorkspaceProviderSelection = {
|
|
|
137
142
|
} & Omit<ProviderSelectionBase, "machine"> & {
|
|
138
143
|
machine?: FlexibleMachine<ExeDevResources>;
|
|
139
144
|
}) | ({
|
|
140
|
-
type: "e2b"
|
|
145
|
+
type: "e2b";
|
|
146
|
+
} & Omit<ProviderSelectionBase, "machine"> & {
|
|
147
|
+
machine?: FlexibleMachine<E2bResources>;
|
|
148
|
+
}) | ({
|
|
149
|
+
type: "ascii";
|
|
141
150
|
} & ProviderSelectionBase) | {
|
|
142
151
|
type: "cloudflare";
|
|
143
152
|
providerId?: string;
|
|
@@ -149,7 +158,9 @@ export type WorkspaceCreateInput = {
|
|
|
149
158
|
name?: string;
|
|
150
159
|
repo: string;
|
|
151
160
|
branch?: string;
|
|
161
|
+
/** Commit SHA to pin the checkout to after cloning `branch`/`checkoutRef`. */
|
|
152
162
|
baseCommit?: string;
|
|
163
|
+
/** Branch or tag to clone when distinct from the display `branch`. Not a commit SHA — use `baseCommit` to pin a revision. */
|
|
153
164
|
checkoutRef?: string;
|
|
154
165
|
subdomain?: string;
|
|
155
166
|
/** Stable agent key. Defaults to `opencode`. */
|
|
@@ -169,6 +180,8 @@ export type WorkspaceCreateInput = {
|
|
|
169
180
|
* inline; connect those in the dashboard.
|
|
170
181
|
*/
|
|
171
182
|
modelCredentials?: WorkspaceModelCredentialInput[];
|
|
183
|
+
/** Ephemeral environment variables injected into this workspace only. */
|
|
184
|
+
environmentVariables?: Record<string, string>;
|
|
172
185
|
/**
|
|
173
186
|
* Ordered commands launched in the repository after the agent server starts.
|
|
174
187
|
* They do not block workspace readiness; inspect ~/.gitterm/setup for status
|
|
@@ -183,6 +196,12 @@ export type WorkspaceCreateInput = {
|
|
|
183
196
|
}>;
|
|
184
197
|
/** NPM package specs or plugin paths accepted by OpenCode. Pin versions for repeatable runs. */
|
|
185
198
|
plugins?: string[];
|
|
199
|
+
/**
|
|
200
|
+
* OpenCode config (opencode.json keys) merged over your saved config for
|
|
201
|
+
* this workspace only. e.g. { permission: { edit: "allow", bash: "allow",
|
|
202
|
+
* webfetch: "allow" } } disables tool approval prompts in headless runs.
|
|
203
|
+
*/
|
|
204
|
+
config?: Record<string, unknown>;
|
|
186
205
|
};
|
|
187
206
|
};
|
|
188
207
|
export type WorkspaceRestartResult = {
|
|
@@ -232,6 +251,7 @@ export type AgentRunCreateInput = {
|
|
|
232
251
|
};
|
|
233
252
|
/** Wait for workspace setup commands before submitting the prompt. */
|
|
234
253
|
waitForSetup?: boolean;
|
|
254
|
+
/** How long to wait for setup, in ms. Server maximum is 600000 (10 minutes). */
|
|
235
255
|
setupTimeoutMs?: number;
|
|
236
256
|
};
|
|
237
257
|
export type AgentRunMessage = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitterm/sdk",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist"
|
|
6
6
|
],
|
|
@@ -11,18 +11,29 @@
|
|
|
11
11
|
"bun": "./dist/index.js",
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
13
|
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./direct": {
|
|
16
|
+
"types": "./dist/direct/index.d.ts",
|
|
17
|
+
"bun": "./dist/direct/index.js",
|
|
18
|
+
"import": "./dist/direct/index.js",
|
|
19
|
+
"default": "./dist/direct/index.js"
|
|
14
20
|
}
|
|
15
21
|
},
|
|
16
22
|
"publishConfig": {
|
|
17
23
|
"access": "public"
|
|
18
24
|
},
|
|
19
25
|
"scripts": {
|
|
20
|
-
"build": "rm -rf dist && bun build src/index.ts --outdir dist --target node --format esm --external @trpc/client && tsc -p tsconfig.build.json",
|
|
26
|
+
"build": "rm -rf dist && bun build src/index.ts src/direct/index.ts --outdir dist --target node --format esm --external @trpc/client --external @opencode-ai/sdk --external @opencode-ai/sdk/v2 --external e2b --external @asciidev/box-sdk --external @daytonaio/sdk --external @vercel/sandbox && tsc -p tsconfig.build.json",
|
|
21
27
|
"prepublishOnly": "bun run check-types && bun run build",
|
|
22
28
|
"check-types": "tsc --noEmit"
|
|
23
29
|
},
|
|
24
30
|
"dependencies": {
|
|
25
|
-
"@
|
|
31
|
+
"@asciidev/box-sdk": "^0.0.34",
|
|
32
|
+
"@daytonaio/sdk": "^0.207.0",
|
|
33
|
+
"@opencode-ai/sdk": "^1.18.25",
|
|
34
|
+
"@trpc/client": "^11.8.1",
|
|
35
|
+
"@vercel/sandbox": "^3.2.1",
|
|
36
|
+
"e2b": "^2.46.1"
|
|
26
37
|
},
|
|
27
38
|
"devDependencies": {
|
|
28
39
|
"@gitterm/api": "workspace:*",
|
|
@@ -31,6 +42,6 @@
|
|
|
31
42
|
"typescript": "^5.8.2"
|
|
32
43
|
},
|
|
33
44
|
"engines": {
|
|
34
|
-
"node": ">=18"
|
|
45
|
+
"node": ">=20.18.1"
|
|
35
46
|
}
|
|
36
47
|
}
|