clawcontainer 1.0.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/CONTRIBUTING.md +76 -0
- package/DOCS.md +370 -0
- package/LICENSE +21 -0
- package/README.md +147 -0
- package/black_logo.png +0 -0
- package/dist/assets/abap-DLDM7-KI.js +1 -0
- package/dist/assets/apex-DNDY2TF8.js +1 -0
- package/dist/assets/azcli-Y6nb8tq_.js +1 -0
- package/dist/assets/bat-BwHxbl9M.js +1 -0
- package/dist/assets/bicep-CFznDFnq.js +2 -0
- package/dist/assets/cameligo-Bf6VGUru.js +1 -0
- package/dist/assets/clojure-Dnu-v4kV.js +1 -0
- package/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/dist/assets/coffee-Bd8akH9Z.js +1 -0
- package/dist/assets/cpp-BbWJElDN.js +1 -0
- package/dist/assets/csharp-Co3qMtFm.js +1 -0
- package/dist/assets/csp-D-4FJmMZ.js +1 -0
- package/dist/assets/css-DdJfP1eB.js +3 -0
- package/dist/assets/css.worker-GxEd3MMM.js +93 -0
- package/dist/assets/cssMode-DM_ONlf-.js +1 -0
- package/dist/assets/cypher-cTPe9QuQ.js +1 -0
- package/dist/assets/dart-BOtBlQCF.js +1 -0
- package/dist/assets/dockerfile-BG73LgW2.js +1 -0
- package/dist/assets/ecl-BEgZUVRK.js +1 -0
- package/dist/assets/elixir-BkW5O-1t.js +1 -0
- package/dist/assets/flow9-BeJ5waoc.js +1 -0
- package/dist/assets/freemarker2-VbwzOQPq.js +3 -0
- package/dist/assets/fsharp-PahG7c26.js +1 -0
- package/dist/assets/go-acbASCJo.js +1 -0
- package/dist/assets/graphql-BxJiqAUM.js +1 -0
- package/dist/assets/handlebars-DLvQ802u.js +1 -0
- package/dist/assets/hcl-DtV1sZF8.js +1 -0
- package/dist/assets/html-DuEPBzmS.js +1 -0
- package/dist/assets/html.worker-lU17Tx2m.js +470 -0
- package/dist/assets/htmlMode-BfeYTJaB.js +1 -0
- package/dist/assets/index-BnBKg8GZ.js +1291 -0
- package/dist/assets/index-Dq3FlPWe.css +32 -0
- package/dist/assets/ini-Kd9XrMLS.js +1 -0
- package/dist/assets/java-CXBNlu9o.js +1 -0
- package/dist/assets/javascript-DQO1Leza.js +1 -0
- package/dist/assets/json.worker-CUJs-dtA.js +58 -0
- package/dist/assets/jsonMode--qsURhHr.js +7 -0
- package/dist/assets/julia-cl7-CwDS.js +1 -0
- package/dist/assets/kotlin-s7OhZKlX.js +1 -0
- package/dist/assets/less-9HpZscsL.js +2 -0
- package/dist/assets/lexon-OrD6JF1K.js +1 -0
- package/dist/assets/liquid-PL6MZtM8.js +1 -0
- package/dist/assets/lspLanguageFeatures-Cy5rDFeq.js +4 -0
- package/dist/assets/lua-Cyyb5UIc.js +1 -0
- package/dist/assets/m3-B8OfTtLu.js +1 -0
- package/dist/assets/markdown-BFxVWTOG.js +1 -0
- package/dist/assets/mdx-Cb3Jy14X.js +1 -0
- package/dist/assets/mips-CiqrrVzr.js +1 -0
- package/dist/assets/msdax-DmeGPVcC.js +1 -0
- package/dist/assets/mysql-C_tMU-Nz.js +1 -0
- package/dist/assets/objective-c-BDtDVThU.js +1 -0
- package/dist/assets/pascal-vHIfCaH5.js +1 -0
- package/dist/assets/pascaligo-DtZ0uQbO.js +1 -0
- package/dist/assets/perl-Ub6l9XKa.js +1 -0
- package/dist/assets/pgsql-BlNEE0v7.js +1 -0
- package/dist/assets/php-BBUBE1dy.js +1 -0
- package/dist/assets/pla-DSh2-awV.js +1 -0
- package/dist/assets/postiats-CocnycG-.js +1 -0
- package/dist/assets/powerquery-tScXyioY.js +1 -0
- package/dist/assets/powershell-COWaemsV.js +1 -0
- package/dist/assets/protobuf-Brw8urJB.js +2 -0
- package/dist/assets/pug-8SOpv6rk.js +1 -0
- package/dist/assets/python-Usm4OUwq.js +1 -0
- package/dist/assets/qsharp-Bw9ernYp.js +1 -0
- package/dist/assets/r-j7ic8hl3.js +1 -0
- package/dist/assets/razor-BIOole7a.js +1 -0
- package/dist/assets/redis-Bu5POkcn.js +1 -0
- package/dist/assets/redshift-Bs9aos_-.js +1 -0
- package/dist/assets/restructuredtext-CqXO7rUv.js +1 -0
- package/dist/assets/ruby-zBfavPgS.js +1 -0
- package/dist/assets/rust-BzKRNQWT.js +1 -0
- package/dist/assets/sb-BBc9UKZt.js +1 -0
- package/dist/assets/scala-D9hQfWCl.js +1 -0
- package/dist/assets/scheme-BPhDTwHR.js +1 -0
- package/dist/assets/scss-CBJaRo0y.js +3 -0
- package/dist/assets/shell-DiJ1NA_G.js +1 -0
- package/dist/assets/solidity-Db0IVjzk.js +1 -0
- package/dist/assets/sophia-CnS9iZB_.js +1 -0
- package/dist/assets/sparql-CJmd_6j2.js +1 -0
- package/dist/assets/sql-ClhHkBeG.js +1 -0
- package/dist/assets/st-CHwy0fLd.js +1 -0
- package/dist/assets/swift-Bqt4WxQ4.js +3 -0
- package/dist/assets/systemverilog-Bs9z6M-B.js +1 -0
- package/dist/assets/tcl-Dm6ycUr_.js +1 -0
- package/dist/assets/ts.worker-Dy9lDQQT.js +67731 -0
- package/dist/assets/tsMode-CDjF3DWK.js +11 -0
- package/dist/assets/twig-Csy3S7wG.js +1 -0
- package/dist/assets/typescript-CJR4sLnG.js +1 -0
- package/dist/assets/typespec-Btyra-wh.js +1 -0
- package/dist/assets/vb-Db0cS2oM.js +1 -0
- package/dist/assets/wgsl-DumH7NcR.js +298 -0
- package/dist/assets/xml-CJZS3uh7.js +1 -0
- package/dist/assets/yaml-DB88cW5z.js +1 -0
- package/dist/audit.d.ts +48 -0
- package/dist/container.d.ts +100 -0
- package/dist/event-emitter.d.ts +7 -0
- package/dist/favicon.png +0 -0
- package/dist/git-service.d.ts +31 -0
- package/dist/index.html +188 -0
- package/dist/logo-sm.png +0 -0
- package/dist/logo.png +0 -0
- package/dist/main.d.ts +1 -0
- package/dist/monaco-editor.d.ts +11 -0
- package/dist/monacoeditorwork/css.worker.bundle.js +54264 -0
- package/dist/monacoeditorwork/editor.worker.bundle.js +14317 -0
- package/dist/monacoeditorwork/html.worker.bundle.js +30449 -0
- package/dist/monacoeditorwork/json.worker.bundle.js +22085 -0
- package/dist/monacoeditorwork/ts.worker.bundle.js +225552 -0
- package/dist/net-intercept.d.ts +2 -0
- package/dist/network-hook.d.ts +1 -0
- package/dist/plugin.d.ts +20 -0
- package/dist/policy.d.ts +58 -0
- package/dist/sdk.d.ts +61 -0
- package/dist/tab-manager.d.ts +11 -0
- package/dist/templates.d.ts +46 -0
- package/dist/terminal.d.ts +19 -0
- package/dist/types.d.ts +109 -0
- package/dist/ui.d.ts +81 -0
- package/dist/workspace.d.ts +16 -0
- package/index.html +159 -0
- package/logo.png +0 -0
- package/package.json +31 -0
- package/public/favicon.png +0 -0
- package/public/logo-sm.png +0 -0
- package/public/logo.png +0 -0
- package/src/audit.ts +196 -0
- package/src/container.ts +723 -0
- package/src/event-emitter.ts +28 -0
- package/src/git-service.ts +202 -0
- package/src/main.ts +9 -0
- package/src/monaco-editor.ts +111 -0
- package/src/net-intercept.ts +74 -0
- package/src/network-hook.ts +248 -0
- package/src/plugin.ts +63 -0
- package/src/policy.ts +403 -0
- package/src/sdk.ts +355 -0
- package/src/style.css +432 -0
- package/src/tab-manager.ts +30 -0
- package/src/templates.ts +271 -0
- package/src/terminal.ts +78 -0
- package/src/types.ts +113 -0
- package/src/ui.ts +1266 -0
- package/src/workspace.ts +107 -0
- package/tsconfig.json +20 -0
- package/vite.config.ts +52 -0
package/src/templates.ts
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// ─── Template System for ClawContainer ──────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
import type { AgentConfig, ClawContainerOptions, TabDefinition } from './types.js';
|
|
4
|
+
|
|
5
|
+
/** A named, reusable container configuration preset. */
|
|
6
|
+
export interface ContainerTemplate {
|
|
7
|
+
name: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
agent?: AgentConfig | false;
|
|
10
|
+
workspace?: Record<string, string>;
|
|
11
|
+
services?: Record<string, string>;
|
|
12
|
+
env?: Record<string, string>;
|
|
13
|
+
startupScript?: string;
|
|
14
|
+
tabs?: TabDefinition[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Built-in gitclaw template — mirrors the previous hardcoded defaults. */
|
|
18
|
+
export const GITCLAW_TEMPLATE: ContainerTemplate = {
|
|
19
|
+
name: 'gitclaw',
|
|
20
|
+
description: 'Default gitclaw agent template',
|
|
21
|
+
agent: {
|
|
22
|
+
package: 'gitclaw',
|
|
23
|
+
version: '1.1.4',
|
|
24
|
+
entry: 'dist/index.js',
|
|
25
|
+
args: ['--dir', '<home>/workspace'],
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ─── Template Registry ────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
export class TemplateRegistry {
|
|
32
|
+
private templates = new Map<string, ContainerTemplate>();
|
|
33
|
+
|
|
34
|
+
constructor() {
|
|
35
|
+
// Seed with built-in templates
|
|
36
|
+
this.register(GITCLAW_TEMPLATE);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
register(template: ContainerTemplate): void {
|
|
40
|
+
this.templates.set(template.name, template);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get(name: string): ContainerTemplate | undefined {
|
|
44
|
+
return this.templates.get(name);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
has(name: string): boolean {
|
|
48
|
+
return this.templates.has(name);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
list(): string[] {
|
|
52
|
+
return [...this.templates.keys()];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Expose the internal map (read-only use). */
|
|
56
|
+
get all(): Map<string, ContainerTemplate> {
|
|
57
|
+
return this.templates;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ─── Resolution & merging ─────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolve a template input to a concrete ContainerTemplate.
|
|
65
|
+
* - string → lookup by name in registry
|
|
66
|
+
* - object → use directly
|
|
67
|
+
* - undefined → default to 'gitclaw'
|
|
68
|
+
*/
|
|
69
|
+
export function resolveTemplate(
|
|
70
|
+
input: string | ContainerTemplate | undefined,
|
|
71
|
+
registry: TemplateRegistry,
|
|
72
|
+
): ContainerTemplate {
|
|
73
|
+
if (input === undefined) {
|
|
74
|
+
return registry.get('gitclaw')!;
|
|
75
|
+
}
|
|
76
|
+
if (typeof input === 'string') {
|
|
77
|
+
const tpl = registry.get(input);
|
|
78
|
+
if (!tpl) throw new Error(`Unknown template: "${input}". Registered: ${registry.list().join(', ')}`);
|
|
79
|
+
return tpl;
|
|
80
|
+
}
|
|
81
|
+
return input;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Merge a resolved template with user-supplied ClawContainerOptions.
|
|
86
|
+
* Options win for conflicts. Record fields (workspace, services, env) are merged per-key.
|
|
87
|
+
* Scalar fields (agent, startupScript) are replaced when present in options.
|
|
88
|
+
*/
|
|
89
|
+
export function mergeTemplateWithOptions(
|
|
90
|
+
template: ContainerTemplate,
|
|
91
|
+
options: ClawContainerOptions,
|
|
92
|
+
): ClawContainerOptions {
|
|
93
|
+
const merged: ClawContainerOptions = {};
|
|
94
|
+
|
|
95
|
+
// Agent: options.agent takes precedence if explicitly set (including false)
|
|
96
|
+
if (options.agent !== undefined) {
|
|
97
|
+
merged.agent = options.agent;
|
|
98
|
+
} else if (template.agent !== undefined) {
|
|
99
|
+
merged.agent = template.agent;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Record fields: template first, options overlay
|
|
103
|
+
merged.workspace = { ...template.workspace, ...options.workspace };
|
|
104
|
+
merged.services = { ...template.services, ...options.services };
|
|
105
|
+
merged.env = { ...template.env, ...options.env };
|
|
106
|
+
|
|
107
|
+
// Scalar: options win
|
|
108
|
+
merged.startupScript = options.startupScript ?? template.startupScript;
|
|
109
|
+
|
|
110
|
+
// Tabs: concat template tabs + options tabs (dedup by id, options win)
|
|
111
|
+
const templateTabs = template.tabs ?? [];
|
|
112
|
+
const optionTabs = options.tabs ?? [];
|
|
113
|
+
if (templateTabs.length > 0 || optionTabs.length > 0) {
|
|
114
|
+
const tabMap = new Map<string, TabDefinition>();
|
|
115
|
+
for (const t of templateTabs) tabMap.set(t.id, t);
|
|
116
|
+
for (const t of optionTabs) tabMap.set(t.id, t);
|
|
117
|
+
merged.tabs = [...tabMap.values()];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Pass through non-template fields
|
|
121
|
+
if (options.plugins) merged.plugins = options.plugins;
|
|
122
|
+
|
|
123
|
+
return merged;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ─── YAML Parsing (hand-rolled, no deps) ──────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Parse a YAML string into a ContainerTemplate.
|
|
130
|
+
* Supports the simple subset needed for template definitions:
|
|
131
|
+
* - Top-level scalar keys (name, description, startupScript)
|
|
132
|
+
* - Top-level `agent:` with nested scalar keys + args/env
|
|
133
|
+
* - Top-level Record sections (workspace, services, env)
|
|
134
|
+
* - `agent: false` to skip agent launch
|
|
135
|
+
*/
|
|
136
|
+
export function parseTemplateYaml(yaml: string): ContainerTemplate {
|
|
137
|
+
const lines = yaml.split('\n');
|
|
138
|
+
const template: ContainerTemplate = { name: '' };
|
|
139
|
+
|
|
140
|
+
let i = 0;
|
|
141
|
+
while (i < lines.length) {
|
|
142
|
+
const line = lines[i];
|
|
143
|
+
const trimmed = line.trim();
|
|
144
|
+
|
|
145
|
+
// Skip blank lines and comments
|
|
146
|
+
if (trimmed === '' || trimmed.startsWith('#')) { i++; continue; }
|
|
147
|
+
|
|
148
|
+
// Top-level key: value
|
|
149
|
+
const kvMatch = trimmed.match(/^(\w+):\s*(.*)$/);
|
|
150
|
+
if (!kvMatch) { i++; continue; }
|
|
151
|
+
|
|
152
|
+
const key = kvMatch[1];
|
|
153
|
+
const value = kvMatch[2].trim();
|
|
154
|
+
|
|
155
|
+
if (key === 'name') {
|
|
156
|
+
template.name = stripQuotes(value);
|
|
157
|
+
i++;
|
|
158
|
+
} else if (key === 'description') {
|
|
159
|
+
template.description = stripQuotes(value);
|
|
160
|
+
i++;
|
|
161
|
+
} else if (key === 'startupScript') {
|
|
162
|
+
template.startupScript = stripQuotes(value);
|
|
163
|
+
i++;
|
|
164
|
+
} else if (key === 'agent') {
|
|
165
|
+
if (value === 'false') {
|
|
166
|
+
template.agent = false;
|
|
167
|
+
i++;
|
|
168
|
+
} else if (value === '' || value === undefined) {
|
|
169
|
+
// Nested agent block
|
|
170
|
+
const agent: AgentConfig = { package: '', entry: '' };
|
|
171
|
+
i++;
|
|
172
|
+
while (i < lines.length) {
|
|
173
|
+
const aLine = lines[i];
|
|
174
|
+
const aTrimmed = aLine.trim();
|
|
175
|
+
if (aTrimmed === '' || aTrimmed.startsWith('#')) { i++; continue; }
|
|
176
|
+
// Check if we've left the agent block (no leading whitespace)
|
|
177
|
+
if (/^\S/.test(aLine)) break;
|
|
178
|
+
|
|
179
|
+
const aKv = aTrimmed.match(/^(\w+):\s*(.*)$/);
|
|
180
|
+
if (!aKv) { i++; continue; }
|
|
181
|
+
|
|
182
|
+
const aKey = aKv[1];
|
|
183
|
+
const aVal = aKv[2].trim();
|
|
184
|
+
|
|
185
|
+
if (aKey === 'package') { agent.package = stripQuotes(aVal); i++; }
|
|
186
|
+
else if (aKey === 'version') { agent.version = stripQuotes(aVal); i++; }
|
|
187
|
+
else if (aKey === 'entry') { agent.entry = stripQuotes(aVal); i++; }
|
|
188
|
+
else if (aKey === 'args') {
|
|
189
|
+
agent.args = parseYamlInlineArray(aVal);
|
|
190
|
+
i++;
|
|
191
|
+
} else if (aKey === 'env') {
|
|
192
|
+
// Nested env block under agent
|
|
193
|
+
agent.env = {};
|
|
194
|
+
i++;
|
|
195
|
+
while (i < lines.length) {
|
|
196
|
+
const eLine = lines[i];
|
|
197
|
+
const eTrimmed = eLine.trim();
|
|
198
|
+
if (eTrimmed === '' || eTrimmed.startsWith('#')) { i++; continue; }
|
|
199
|
+
// Must be indented deeper than agent keys (at least 4 spaces)
|
|
200
|
+
const indent = eLine.length - eLine.trimStart().length;
|
|
201
|
+
if (indent < 4) break;
|
|
202
|
+
const eKv = eTrimmed.match(/^(\w+):\s*(.*)$/);
|
|
203
|
+
if (eKv) {
|
|
204
|
+
agent.env[eKv[1]] = stripQuotes(eKv[2].trim());
|
|
205
|
+
}
|
|
206
|
+
i++;
|
|
207
|
+
}
|
|
208
|
+
} else { i++; }
|
|
209
|
+
}
|
|
210
|
+
template.agent = agent;
|
|
211
|
+
} else { i++; }
|
|
212
|
+
} else if (key === 'workspace' || key === 'services' || key === 'env') {
|
|
213
|
+
const record: Record<string, string> = {};
|
|
214
|
+
i++;
|
|
215
|
+
while (i < lines.length) {
|
|
216
|
+
const rLine = lines[i];
|
|
217
|
+
const rTrimmed = rLine.trim();
|
|
218
|
+
if (rTrimmed === '' || rTrimmed.startsWith('#')) { i++; continue; }
|
|
219
|
+
if (/^\S/.test(rLine)) break;
|
|
220
|
+
|
|
221
|
+
// key: value or key: | (block scalar)
|
|
222
|
+
const rKv = rTrimmed.match(/^(.+?):\s*(.*)$/);
|
|
223
|
+
if (!rKv) { i++; continue; }
|
|
224
|
+
|
|
225
|
+
const rKey = rKv[1].trim();
|
|
226
|
+
const rVal = rKv[2].trim();
|
|
227
|
+
|
|
228
|
+
if (rVal === '|') {
|
|
229
|
+
// Block scalar — collect indented lines
|
|
230
|
+
i++;
|
|
231
|
+
const blockLines: string[] = [];
|
|
232
|
+
const baseIndent = i < lines.length ? (lines[i].length - lines[i].trimStart().length) : 4;
|
|
233
|
+
while (i < lines.length) {
|
|
234
|
+
const bLine = lines[i];
|
|
235
|
+
if (bLine.trim() === '') { blockLines.push(''); i++; continue; }
|
|
236
|
+
const bIndent = bLine.length - bLine.trimStart().length;
|
|
237
|
+
if (bIndent < baseIndent) break;
|
|
238
|
+
blockLines.push(bLine.slice(baseIndent));
|
|
239
|
+
i++;
|
|
240
|
+
}
|
|
241
|
+
record[rKey] = blockLines.join('\n') + '\n';
|
|
242
|
+
} else {
|
|
243
|
+
record[rKey] = stripQuotes(rVal);
|
|
244
|
+
i++;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
(template as any)[key] = record;
|
|
248
|
+
} else {
|
|
249
|
+
i++;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!template.name) throw new Error('Template YAML must include a "name" field');
|
|
254
|
+
return template;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Strip surrounding quotes from a YAML value. */
|
|
258
|
+
function stripQuotes(s: string): string {
|
|
259
|
+
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
|
|
260
|
+
return s.slice(1, -1);
|
|
261
|
+
}
|
|
262
|
+
return s;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Parse a YAML inline array like ["--dir", "<home>/workspace"] */
|
|
266
|
+
function parseYamlInlineArray(s: string): string[] {
|
|
267
|
+
const trimmed = s.trim();
|
|
268
|
+
if (!trimmed.startsWith('[') || !trimmed.endsWith(']')) return [];
|
|
269
|
+
const inner = trimmed.slice(1, -1);
|
|
270
|
+
return inner.split(',').map(item => stripQuotes(item.trim())).filter(Boolean);
|
|
271
|
+
}
|
package/src/terminal.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Terminal } from '@xterm/xterm';
|
|
2
|
+
import { FitAddon } from '@xterm/addon-fit';
|
|
3
|
+
|
|
4
|
+
export class TerminalManager {
|
|
5
|
+
readonly xterm: Terminal;
|
|
6
|
+
private readonly fitAddon: FitAddon;
|
|
7
|
+
private resizeObserver: ResizeObserver | null = null;
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
this.xterm = new Terminal({
|
|
11
|
+
convertEol: true,
|
|
12
|
+
cursorBlink: true,
|
|
13
|
+
fontSize: 13,
|
|
14
|
+
fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', Menlo, monospace",
|
|
15
|
+
theme: {
|
|
16
|
+
background: '#0d1117',
|
|
17
|
+
foreground: '#e6edf3',
|
|
18
|
+
cursor: '#f78166',
|
|
19
|
+
selectionBackground: '#264f78',
|
|
20
|
+
black: '#484f58',
|
|
21
|
+
brightBlack: '#6e7681',
|
|
22
|
+
red: '#ff7b72',
|
|
23
|
+
brightRed: '#ffa198',
|
|
24
|
+
green: '#3fb950',
|
|
25
|
+
brightGreen: '#56d364',
|
|
26
|
+
yellow: '#d29922',
|
|
27
|
+
brightYellow:'#e3b341',
|
|
28
|
+
blue: '#58a6ff',
|
|
29
|
+
brightBlue: '#79c0ff',
|
|
30
|
+
magenta: '#bc8cff',
|
|
31
|
+
brightMagenta:'#d2a8ff',
|
|
32
|
+
cyan: '#39c5cf',
|
|
33
|
+
brightCyan: '#56d4dd',
|
|
34
|
+
white: '#b1bac4',
|
|
35
|
+
brightWhite: '#f0f6fc',
|
|
36
|
+
},
|
|
37
|
+
scrollback: 5000,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
this.fitAddon = new FitAddon();
|
|
41
|
+
this.xterm.loadAddon(this.fitAddon);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Mount the terminal into a DOM element and start auto-resize. */
|
|
45
|
+
mount(container: HTMLElement): void {
|
|
46
|
+
this.xterm.open(container);
|
|
47
|
+
this.fitAddon.fit();
|
|
48
|
+
|
|
49
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
50
|
+
try { this.fitAddon.fit(); } catch { /* ignore */ }
|
|
51
|
+
});
|
|
52
|
+
this.resizeObserver.observe(container);
|
|
53
|
+
|
|
54
|
+
window.addEventListener('resize', () => {
|
|
55
|
+
try { this.fitAddon.fit(); } catch { /* ignore */ }
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Write text/bytes to the terminal display (not to stdin). */
|
|
60
|
+
write(data: string | Uint8Array): void {
|
|
61
|
+
this.xterm.write(data);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Register a handler for user keystrokes (sent to shell stdin). */
|
|
65
|
+
onData(handler: (data: string) => void): void {
|
|
66
|
+
this.xterm.onData(handler);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Current terminal dimensions for pty resize. */
|
|
70
|
+
get dimensions(): { cols: number; rows: number } {
|
|
71
|
+
return { cols: this.xterm.cols, rows: this.xterm.rows };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
dispose(): void {
|
|
75
|
+
this.resizeObserver?.disconnect();
|
|
76
|
+
this.xterm.dispose();
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// ─── ClawContainer SDK Types ────────────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
import type { WebContainer } from '@webcontainer/api';
|
|
4
|
+
import type { AuditEntry, AuditSource, AuditLevel, AuditEvent } from './audit.js';
|
|
5
|
+
import type { ContainerTemplate } from './templates.js';
|
|
6
|
+
|
|
7
|
+
/** Configuration for launching an agent inside the container. */
|
|
8
|
+
export interface AgentConfig {
|
|
9
|
+
/** npm package name (e.g. 'gitclaw') */
|
|
10
|
+
package: string;
|
|
11
|
+
/** Package version (e.g. '1.1.4'). Defaults to 'latest'. */
|
|
12
|
+
version?: string;
|
|
13
|
+
/** Entry file relative to the package directory (e.g. 'dist/index.js') */
|
|
14
|
+
entry: string;
|
|
15
|
+
/** Extra CLI args passed to node */
|
|
16
|
+
args?: string[];
|
|
17
|
+
/** Extra environment variables for the agent process */
|
|
18
|
+
env?: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Options for creating a ClawContainer instance. */
|
|
22
|
+
export interface ClawContainerOptions {
|
|
23
|
+
/** Agent to launch. Pass `false` to skip agent launch entirely. Default: gitclaw. */
|
|
24
|
+
agent?: AgentConfig | false;
|
|
25
|
+
/** Extra workspace files to mount: flat map of relative path → content */
|
|
26
|
+
workspace?: Record<string, string>;
|
|
27
|
+
/** Extra npm dependencies to install: package → version */
|
|
28
|
+
services?: Record<string, string>;
|
|
29
|
+
/** Environment variables injected into the container */
|
|
30
|
+
env?: Record<string, string>;
|
|
31
|
+
/** Shell script to run after install, before agent launch */
|
|
32
|
+
startupScript?: string;
|
|
33
|
+
/** Template to use: name of a registered template, or a ContainerTemplate object. Default: 'gitclaw'. */
|
|
34
|
+
template?: string | ContainerTemplate;
|
|
35
|
+
/** Plugins to register before start */
|
|
36
|
+
plugins?: ClawContainerPlugin[];
|
|
37
|
+
/** Custom tabs to add on start */
|
|
38
|
+
tabs?: TabDefinition[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Definition for a custom UI tab. */
|
|
42
|
+
export interface TabDefinition {
|
|
43
|
+
/** Unique identifier for the tab */
|
|
44
|
+
id: string;
|
|
45
|
+
/** Display label in the tab bar */
|
|
46
|
+
label: string;
|
|
47
|
+
/** HTML content or a callback that receives the content container */
|
|
48
|
+
render: string | ((container: HTMLDivElement) => void);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Plugin lifecycle hooks. All are optional. */
|
|
52
|
+
export interface ClawContainerPlugin {
|
|
53
|
+
/** Plugin name (for debugging) */
|
|
54
|
+
name: string;
|
|
55
|
+
/** Extra npm deps this plugin needs */
|
|
56
|
+
services?: Record<string, string>;
|
|
57
|
+
/** Extra workspace files to merge */
|
|
58
|
+
workspace?: Record<string, string>;
|
|
59
|
+
/** Extra env vars to inject */
|
|
60
|
+
env?: Record<string, string>;
|
|
61
|
+
/** Custom tabs to register */
|
|
62
|
+
tabs?: TabDefinition[];
|
|
63
|
+
/** Called after plugin is registered, before container boot */
|
|
64
|
+
onInit?(cc: ClawContainerSDK): void;
|
|
65
|
+
/** Called after container is ready */
|
|
66
|
+
onReady?(cc: ClawContainerSDK): void;
|
|
67
|
+
/** Called when container is stopped */
|
|
68
|
+
onDestroy?(cc: ClawContainerSDK): void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Event map for ClawContainer typed events. */
|
|
72
|
+
export type ClawContainerEvents = {
|
|
73
|
+
ready: [];
|
|
74
|
+
error: [error: Error];
|
|
75
|
+
status: [status: string];
|
|
76
|
+
'file.change': [path: string];
|
|
77
|
+
'process.exit': [code: number];
|
|
78
|
+
'server.ready': [port: number, url: string];
|
|
79
|
+
log: [entry: AuditEntry];
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Public surface of the SDK passed to plugins.
|
|
84
|
+
* This is the same as ClawContainer but expressed as an interface
|
|
85
|
+
* so plugins don't depend on the concrete class.
|
|
86
|
+
*/
|
|
87
|
+
export interface ClawContainerSDK {
|
|
88
|
+
readonly container: WebContainer | null;
|
|
89
|
+
start(): Promise<void>;
|
|
90
|
+
stop(): Promise<void>;
|
|
91
|
+
restart(): Promise<void>;
|
|
92
|
+
exec(cmd: string): Promise<string>;
|
|
93
|
+
shell(): Promise<void>;
|
|
94
|
+
sendInput(data: string): Promise<void>;
|
|
95
|
+
fs: {
|
|
96
|
+
read(path: string): Promise<string>;
|
|
97
|
+
write(path: string, content: string): Promise<void>;
|
|
98
|
+
list(dir?: string): Promise<string[]>;
|
|
99
|
+
mkdir(path: string): Promise<void>;
|
|
100
|
+
remove(path: string): Promise<void>;
|
|
101
|
+
};
|
|
102
|
+
git: {
|
|
103
|
+
clone(url: string, token: string): Promise<void>;
|
|
104
|
+
push(message?: string): Promise<string>;
|
|
105
|
+
};
|
|
106
|
+
logs(filter?: { source?: AuditSource; level?: AuditLevel; event?: AuditEvent }): AuditEntry[];
|
|
107
|
+
use(plugin: ClawContainerPlugin): void;
|
|
108
|
+
addTab(def: TabDefinition): void;
|
|
109
|
+
removeTab(id: string): void;
|
|
110
|
+
on<K extends keyof ClawContainerEvents>(event: K, fn: (...args: ClawContainerEvents[K]) => void): void;
|
|
111
|
+
off<K extends keyof ClawContainerEvents>(event: K, fn: (...args: ClawContainerEvents[K]) => void): void;
|
|
112
|
+
once<K extends keyof ClawContainerEvents>(event: K, fn: (...args: ClawContainerEvents[K]) => void): void;
|
|
113
|
+
}
|