@lazyingart/agintiflow 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/AGENTS.md +41 -0
- package/LICENSE +201 -0
- package/README.md +294 -0
- package/bin/aginti-cli.js +7 -0
- package/docker/sandbox.Dockerfile +22 -0
- package/docs/npm-publishing.md +45 -0
- package/i18n/README.ar.md +51 -0
- package/i18n/README.de.md +51 -0
- package/i18n/README.es.md +51 -0
- package/i18n/README.fr.md +51 -0
- package/i18n/README.ja.md +51 -0
- package/i18n/README.ko.md +51 -0
- package/i18n/README.ru.md +51 -0
- package/i18n/README.vi.md +51 -0
- package/i18n/README.zh-Hans.md +51 -0
- package/i18n/README.zh-Hant.md +171 -0
- package/logos/banner-opaque.png +0 -0
- package/logos/logo.png +0 -0
- package/package.json +62 -0
- package/public/app.js +1060 -0
- package/public/index.html +198 -0
- package/public/styles.css +305 -0
- package/run.js +6 -0
- package/scripts/install-docker-ubuntu.sh +140 -0
- package/src/agent-runner.js +686 -0
- package/src/cli.js +181 -0
- package/src/command-policy.js +185 -0
- package/src/config.js +89 -0
- package/src/docker-sandbox.js +309 -0
- package/src/guardrails.js +122 -0
- package/src/model-client.js +212 -0
- package/src/model-routing.js +135 -0
- package/src/redaction.js +29 -0
- package/src/session-store.js +73 -0
- package/src/snapshot.js +55 -0
- package/src/tool-wrappers.js +193 -0
- package/src/web-db.js +158 -0
- package/web.js +530 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>AgInTiFlow</title>
|
|
7
|
+
<link rel="icon" type="image/png" href="/logos/logo.png" />
|
|
8
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<main class="page">
|
|
12
|
+
<section class="panel form-panel">
|
|
13
|
+
<div class="brand-header">
|
|
14
|
+
<img class="brand-logo" src="/logos/logo.png" alt="AgInTiFlow logo" />
|
|
15
|
+
<div>
|
|
16
|
+
<h1>AgInTiFlow</h1>
|
|
17
|
+
<p class="brand-kicker" data-i18n="brandKicker">AI automation product by AgInTi Inc.</p>
|
|
18
|
+
</div>
|
|
19
|
+
<label class="language-switch">
|
|
20
|
+
<span data-i18n="languageLabel">Language</span>
|
|
21
|
+
<select id="language" name="language" aria-label="Select language">
|
|
22
|
+
<option value="en">English</option>
|
|
23
|
+
<option value="ar">العربية</option>
|
|
24
|
+
<option value="es">Español</option>
|
|
25
|
+
<option value="fr">Français</option>
|
|
26
|
+
<option value="ja">日本語</option>
|
|
27
|
+
<option value="ko">한국어</option>
|
|
28
|
+
<option value="vi">Tiếng Việt</option>
|
|
29
|
+
<option value="zh-Hans">中文 (简体)</option>
|
|
30
|
+
<option value="zh-Hant">繁體中文</option>
|
|
31
|
+
<option value="de">Deutsch</option>
|
|
32
|
+
<option value="ru">Русский</option>
|
|
33
|
+
</select>
|
|
34
|
+
</label>
|
|
35
|
+
</div>
|
|
36
|
+
<p class="subtle" data-i18n="intro">
|
|
37
|
+
Browser agent with provider selection, resumable runs, persisted settings, and an optional guarded shell tool with Docker sandbox support.
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
<form id="run-form">
|
|
41
|
+
<label>
|
|
42
|
+
<span data-i18n="routingModeLabel">Routing policy</span>
|
|
43
|
+
<select id="routingMode" name="routingMode">
|
|
44
|
+
<option value="smart" data-i18n="routingSmartOption">Smart: fast unless complex</option>
|
|
45
|
+
<option value="fast" data-i18n="routingFastOption">DeepSeek v4 flash</option>
|
|
46
|
+
<option value="complex" data-i18n="routingComplexOption">DeepSeek v4 pro</option>
|
|
47
|
+
<option value="manual" data-i18n="routingManualOption">Manual provider/model</option>
|
|
48
|
+
</select>
|
|
49
|
+
</label>
|
|
50
|
+
|
|
51
|
+
<div class="grid">
|
|
52
|
+
<label>
|
|
53
|
+
<span data-i18n="providerLabel">Provider</span>
|
|
54
|
+
<select id="provider" name="provider">
|
|
55
|
+
<option value="deepseek">DeepSeek</option>
|
|
56
|
+
<option value="openai">OpenAI</option>
|
|
57
|
+
</select>
|
|
58
|
+
</label>
|
|
59
|
+
|
|
60
|
+
<label>
|
|
61
|
+
<span data-i18n="modelLabel">Model</span>
|
|
62
|
+
<input id="model" name="model" type="text" value="deepseek-v4-flash" />
|
|
63
|
+
</label>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<p id="routing-hint" class="subtle route-hint"></p>
|
|
67
|
+
|
|
68
|
+
<label>
|
|
69
|
+
<span data-i18n="goalLabel">Goal</span>
|
|
70
|
+
<textarea
|
|
71
|
+
id="goal"
|
|
72
|
+
name="goal"
|
|
73
|
+
rows="5"
|
|
74
|
+
data-i18n-placeholder="goalPlaceholder"
|
|
75
|
+
placeholder="Open a site and summarize it, or use run_command for simple terminal inspection."
|
|
76
|
+
></textarea>
|
|
77
|
+
</label>
|
|
78
|
+
|
|
79
|
+
<label>
|
|
80
|
+
<span data-i18n="startUrlLabel">Start URL</span>
|
|
81
|
+
<input id="startUrl" name="startUrl" type="url" data-i18n-placeholder="startUrlPlaceholder" placeholder="https://news.ycombinator.com" />
|
|
82
|
+
</label>
|
|
83
|
+
|
|
84
|
+
<label>
|
|
85
|
+
<span data-i18n="allowedDomainsLabel">Allowed domains</span>
|
|
86
|
+
<input id="allowedDomains" name="allowedDomains" type="text" data-i18n-placeholder="allowedDomainsPlaceholder" placeholder="news.ycombinator.com,github.com" />
|
|
87
|
+
</label>
|
|
88
|
+
|
|
89
|
+
<div class="grid">
|
|
90
|
+
<label>
|
|
91
|
+
<span data-i18n="commandCwdLabel">Working directory</span>
|
|
92
|
+
<input id="commandCwd" name="commandCwd" type="text" value="/home/lachlan/ProjectsLFS/Agent" />
|
|
93
|
+
</label>
|
|
94
|
+
<label>
|
|
95
|
+
<span data-i18n="maxStepsLabel">Max steps</span>
|
|
96
|
+
<input id="maxSteps" name="maxSteps" type="number" min="1" max="50" value="15" />
|
|
97
|
+
</label>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div class="grid">
|
|
101
|
+
<label>
|
|
102
|
+
<span data-i18n="sandboxModeLabel">Sandbox mode</span>
|
|
103
|
+
<select id="sandboxMode" name="sandboxMode">
|
|
104
|
+
<option value="host" data-i18n="sandboxHostOption">Host read-only</option>
|
|
105
|
+
<option value="docker-readonly" data-i18n="sandboxDockerReadonlyOption">Docker read-only</option>
|
|
106
|
+
<option value="docker-workspace" data-i18n="sandboxDockerWorkspaceOption">Docker workspace-write</option>
|
|
107
|
+
</select>
|
|
108
|
+
</label>
|
|
109
|
+
<label>
|
|
110
|
+
<span data-i18n="packagePolicyLabel">Package installs</span>
|
|
111
|
+
<select id="packageInstallPolicy" name="packageInstallPolicy">
|
|
112
|
+
<option value="block" data-i18n="packageBlockOption">Blocked</option>
|
|
113
|
+
<option value="prompt" data-i18n="packagePromptOption">Require approval</option>
|
|
114
|
+
<option value="allow" data-i18n="packageAllowOption">Approved in Docker</option>
|
|
115
|
+
</select>
|
|
116
|
+
</label>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div class="checks">
|
|
120
|
+
<label><input id="headless" name="headless" type="checkbox" checked /> <span data-i18n="headlessLabel">Headless browser</span></label>
|
|
121
|
+
<label><input id="allowShellTool" name="allowShellTool" type="checkbox" checked /> <span data-i18n="shellToolLabel">Enable shell tool</span></label>
|
|
122
|
+
<label><input id="allowWrapperTools" name="allowWrapperTools" type="checkbox" /> <span data-i18n="wrapperToolLabel">Enable agent wrappers</span></label>
|
|
123
|
+
<label><input id="allowPasswords" name="allowPasswords" type="checkbox" /> <span data-i18n="allowPasswordsLabel">Allow password typing</span></label>
|
|
124
|
+
<label><input id="allowDestructive" name="allowDestructive" type="checkbox" /> <span data-i18n="allowDestructiveLabel">Allow destructive actions</span></label>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<label>
|
|
128
|
+
<span data-i18n="dockerImageLabel">Docker image</span>
|
|
129
|
+
<input
|
|
130
|
+
id="dockerSandboxImage"
|
|
131
|
+
name="dockerSandboxImage"
|
|
132
|
+
type="text"
|
|
133
|
+
value="agintiflow-sandbox:latest"
|
|
134
|
+
data-i18n-placeholder="dockerImagePlaceholder"
|
|
135
|
+
placeholder="agintiflow-sandbox:latest"
|
|
136
|
+
/>
|
|
137
|
+
</label>
|
|
138
|
+
|
|
139
|
+
<div class="actions">
|
|
140
|
+
<button type="submit" data-i18n="startRunButton">Start run</button>
|
|
141
|
+
<span id="key-status" class="subtle"></span>
|
|
142
|
+
</div>
|
|
143
|
+
<p id="wrapper-status" class="subtle wrapper-status"></p>
|
|
144
|
+
|
|
145
|
+
<section class="sandbox-card">
|
|
146
|
+
<div class="header-row">
|
|
147
|
+
<div>
|
|
148
|
+
<h2 data-i18n="sandboxStatusTitle">Sandbox status</h2>
|
|
149
|
+
<p id="sandbox-status" class="subtle route-hint" data-i18n="sandboxStatusEmpty">Not checked yet.</p>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="sandbox-actions">
|
|
152
|
+
<button id="check-sandbox" type="button" class="secondary" data-i18n="checkSandboxButton">Check</button>
|
|
153
|
+
<button id="preflight-sandbox" type="button" class="secondary" data-i18n="preflightSandboxButton">Preflight</button>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
<pre id="sandbox-logs" class="sandbox-logs" data-i18n="sandboxLogsEmpty">No sandbox logs yet.</pre>
|
|
157
|
+
</section>
|
|
158
|
+
</form>
|
|
159
|
+
</section>
|
|
160
|
+
|
|
161
|
+
<section class="stack">
|
|
162
|
+
<section class="panel output-panel">
|
|
163
|
+
<div class="header-row">
|
|
164
|
+
<h2 data-i18n="runOutputTitle">Run output</h2>
|
|
165
|
+
<div id="run-meta" class="subtle"></div>
|
|
166
|
+
</div>
|
|
167
|
+
<pre id="logs" class="logs" data-i18n="noRunStarted">No run started.</pre>
|
|
168
|
+
</section>
|
|
169
|
+
|
|
170
|
+
<section class="panel chat-panel">
|
|
171
|
+
<div class="header-row">
|
|
172
|
+
<h2 data-i18n="conversationTitle">Conversation</h2>
|
|
173
|
+
<select id="session-select" class="session-select">
|
|
174
|
+
<option value="" data-i18n="noSessionSelected">No session selected</option>
|
|
175
|
+
</select>
|
|
176
|
+
</div>
|
|
177
|
+
<div id="chat-thread" class="chat-thread">
|
|
178
|
+
<div class="subtle" data-i18n="noConversationLoaded">No conversation loaded.</div>
|
|
179
|
+
</div>
|
|
180
|
+
<form id="chat-form" class="chat-form">
|
|
181
|
+
<textarea
|
|
182
|
+
id="chat-input"
|
|
183
|
+
rows="4"
|
|
184
|
+
data-i18n-placeholder="chatPlaceholder"
|
|
185
|
+
placeholder="Continue the selected conversation here."
|
|
186
|
+
></textarea>
|
|
187
|
+
<div class="actions">
|
|
188
|
+
<button type="submit" data-i18n="sendMessageButton">Send message</button>
|
|
189
|
+
<span id="chat-status" class="subtle"></span>
|
|
190
|
+
</div>
|
|
191
|
+
</form>
|
|
192
|
+
</section>
|
|
193
|
+
</section>
|
|
194
|
+
</main>
|
|
195
|
+
|
|
196
|
+
<script src="/app.js" type="module"></script>
|
|
197
|
+
</body>
|
|
198
|
+
</html>
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light;
|
|
3
|
+
--bg: #f4efe6;
|
|
4
|
+
--panel: rgba(255, 251, 244, 0.92);
|
|
5
|
+
--ink: #1d1c1a;
|
|
6
|
+
--muted: #5f564a;
|
|
7
|
+
--line: #d9cdbb;
|
|
8
|
+
--accent: #0f766e;
|
|
9
|
+
--assistant: #f3ede2;
|
|
10
|
+
--user: #dff4f0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
* {
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
body {
|
|
18
|
+
margin: 0;
|
|
19
|
+
font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
|
|
20
|
+
color: var(--ink);
|
|
21
|
+
background:
|
|
22
|
+
radial-gradient(circle at top left, rgba(217, 119, 6, 0.14), transparent 30%),
|
|
23
|
+
radial-gradient(circle at top right, rgba(15, 118, 110, 0.12), transparent 28%),
|
|
24
|
+
linear-gradient(180deg, #f8f3eb, var(--bg));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.page {
|
|
28
|
+
width: min(1240px, calc(100% - 32px));
|
|
29
|
+
margin: 32px auto;
|
|
30
|
+
display: grid;
|
|
31
|
+
gap: 20px;
|
|
32
|
+
grid-template-columns: minmax(320px, 420px) minmax(0, 1fr);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.stack {
|
|
36
|
+
display: grid;
|
|
37
|
+
gap: 20px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.panel {
|
|
41
|
+
background: var(--panel);
|
|
42
|
+
border: 1px solid var(--line);
|
|
43
|
+
border-radius: 18px;
|
|
44
|
+
padding: 20px;
|
|
45
|
+
box-shadow: 0 18px 50px rgba(62, 49, 26, 0.08);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
h1,
|
|
49
|
+
h2 {
|
|
50
|
+
margin-top: 0;
|
|
51
|
+
font-family: "IBM Plex Serif", "Georgia", serif;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
h1 {
|
|
55
|
+
margin-bottom: 4px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.brand-header {
|
|
59
|
+
display: grid;
|
|
60
|
+
grid-template-columns: 92px minmax(0, 1fr);
|
|
61
|
+
gap: 16px;
|
|
62
|
+
align-items: center;
|
|
63
|
+
margin-bottom: 14px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.brand-logo {
|
|
67
|
+
width: 92px;
|
|
68
|
+
aspect-ratio: 1;
|
|
69
|
+
object-fit: contain;
|
|
70
|
+
filter: drop-shadow(0 14px 28px rgba(15, 118, 110, 0.18));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.brand-kicker {
|
|
74
|
+
margin: 0;
|
|
75
|
+
color: var(--muted);
|
|
76
|
+
font-size: 0.82rem;
|
|
77
|
+
font-weight: 700;
|
|
78
|
+
letter-spacing: 0.08em;
|
|
79
|
+
text-transform: uppercase;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.language-switch {
|
|
83
|
+
grid-column: 1 / -1;
|
|
84
|
+
display: grid;
|
|
85
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
86
|
+
gap: 10px;
|
|
87
|
+
align-items: center;
|
|
88
|
+
margin-top: 2px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.language-switch span {
|
|
92
|
+
color: var(--muted);
|
|
93
|
+
font-size: 0.76rem;
|
|
94
|
+
font-weight: 800;
|
|
95
|
+
letter-spacing: 0.08em;
|
|
96
|
+
text-transform: uppercase;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.subtle {
|
|
100
|
+
color: var(--muted);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.route-hint,
|
|
104
|
+
.wrapper-status {
|
|
105
|
+
margin: -4px 0 0;
|
|
106
|
+
font-size: 0.88rem;
|
|
107
|
+
line-height: 1.45;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
form,
|
|
111
|
+
.checks {
|
|
112
|
+
display: grid;
|
|
113
|
+
gap: 14px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.grid {
|
|
117
|
+
display: grid;
|
|
118
|
+
gap: 14px;
|
|
119
|
+
grid-template-columns: 1fr 140px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
label {
|
|
123
|
+
display: grid;
|
|
124
|
+
gap: 6px;
|
|
125
|
+
font-size: 0.95rem;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
input,
|
|
129
|
+
textarea,
|
|
130
|
+
select,
|
|
131
|
+
button {
|
|
132
|
+
font: inherit;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
input,
|
|
136
|
+
textarea,
|
|
137
|
+
select {
|
|
138
|
+
width: 100%;
|
|
139
|
+
border: 1px solid var(--line);
|
|
140
|
+
background: #fffdfa;
|
|
141
|
+
color: var(--ink);
|
|
142
|
+
border-radius: 12px;
|
|
143
|
+
padding: 11px 12px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
textarea {
|
|
147
|
+
resize: vertical;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.checks {
|
|
151
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.checks label {
|
|
155
|
+
align-items: center;
|
|
156
|
+
grid-auto-flow: column;
|
|
157
|
+
justify-content: start;
|
|
158
|
+
gap: 10px;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
button {
|
|
162
|
+
border: 0;
|
|
163
|
+
border-radius: 999px;
|
|
164
|
+
padding: 11px 18px;
|
|
165
|
+
background: linear-gradient(135deg, var(--accent), #155e75);
|
|
166
|
+
color: white;
|
|
167
|
+
font-weight: 600;
|
|
168
|
+
cursor: pointer;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
button.secondary {
|
|
172
|
+
border: 1px solid var(--line);
|
|
173
|
+
background: #fffdfa;
|
|
174
|
+
color: var(--ink);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.actions,
|
|
178
|
+
.header-row {
|
|
179
|
+
display: flex;
|
|
180
|
+
gap: 12px;
|
|
181
|
+
align-items: center;
|
|
182
|
+
justify-content: space-between;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.sandbox-card {
|
|
186
|
+
display: grid;
|
|
187
|
+
gap: 12px;
|
|
188
|
+
margin-top: 4px;
|
|
189
|
+
padding: 14px;
|
|
190
|
+
border: 1px solid rgba(15, 118, 110, 0.18);
|
|
191
|
+
border-radius: 16px;
|
|
192
|
+
background: rgba(255, 253, 250, 0.7);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.sandbox-card h2 {
|
|
196
|
+
margin: 0 0 4px;
|
|
197
|
+
font-size: 1.05rem;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.sandbox-actions {
|
|
201
|
+
display: flex;
|
|
202
|
+
flex-wrap: wrap;
|
|
203
|
+
gap: 8px;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.sandbox-logs {
|
|
207
|
+
min-height: 96px;
|
|
208
|
+
max-height: 220px;
|
|
209
|
+
overflow: auto;
|
|
210
|
+
margin: 0;
|
|
211
|
+
padding: 12px;
|
|
212
|
+
border-radius: 12px;
|
|
213
|
+
background: #0f172a;
|
|
214
|
+
color: #dbeafe;
|
|
215
|
+
border: 1px solid #1e293b;
|
|
216
|
+
font-family: "IBM Plex Mono", "SFMono-Regular", monospace;
|
|
217
|
+
font-size: 0.78rem;
|
|
218
|
+
line-height: 1.45;
|
|
219
|
+
white-space: pre-wrap;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.session-select {
|
|
223
|
+
max-width: 340px;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.logs {
|
|
227
|
+
min-height: 360px;
|
|
228
|
+
max-height: 48vh;
|
|
229
|
+
overflow: auto;
|
|
230
|
+
margin: 0;
|
|
231
|
+
padding: 16px;
|
|
232
|
+
border-radius: 14px;
|
|
233
|
+
background: #151515;
|
|
234
|
+
color: #dfdfd8;
|
|
235
|
+
border: 1px solid #2d2d2d;
|
|
236
|
+
font-family: "IBM Plex Mono", "SFMono-Regular", monospace;
|
|
237
|
+
font-size: 0.9rem;
|
|
238
|
+
line-height: 1.5;
|
|
239
|
+
white-space: pre-wrap;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.chat-thread {
|
|
243
|
+
min-height: 260px;
|
|
244
|
+
max-height: 40vh;
|
|
245
|
+
overflow: auto;
|
|
246
|
+
display: grid;
|
|
247
|
+
gap: 12px;
|
|
248
|
+
padding: 6px 2px;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.chat-item {
|
|
252
|
+
border: 1px solid var(--line);
|
|
253
|
+
border-radius: 14px;
|
|
254
|
+
padding: 12px 14px;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.chat-item.user {
|
|
258
|
+
background: var(--user);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.chat-item.assistant {
|
|
262
|
+
background: var(--assistant);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.chat-meta {
|
|
266
|
+
font-size: 0.8rem;
|
|
267
|
+
color: var(--muted);
|
|
268
|
+
margin-bottom: 8px;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.chat-content {
|
|
272
|
+
line-height: 1.5;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.chat-form {
|
|
276
|
+
margin-top: 14px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
@media (max-width: 960px) {
|
|
280
|
+
.page {
|
|
281
|
+
grid-template-columns: 1fr;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.brand-header {
|
|
285
|
+
grid-template-columns: 76px 1fr;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.brand-logo {
|
|
289
|
+
width: 76px;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.language-switch {
|
|
293
|
+
grid-column: 1 / -1;
|
|
294
|
+
grid-template-columns: 1fr;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.logs {
|
|
298
|
+
min-height: 320px;
|
|
299
|
+
max-height: 44vh;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.chat-thread {
|
|
303
|
+
max-height: 34vh;
|
|
304
|
+
}
|
|
305
|
+
}
|
package/run.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -Eeuo pipefail
|
|
3
|
+
|
|
4
|
+
if [[ ! -r /etc/os-release ]]; then
|
|
5
|
+
echo "This script requires /etc/os-release." >&2
|
|
6
|
+
exit 1
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
. /etc/os-release
|
|
10
|
+
|
|
11
|
+
if [[ "${ID:-}" != "ubuntu" ]]; then
|
|
12
|
+
echo "This script targets Ubuntu hosts. Detected ID=${ID:-unknown}." >&2
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if ! command -v apt-get >/dev/null 2>&1; then
|
|
17
|
+
echo "apt-get is required on this host." >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if ! command -v dpkg >/dev/null 2>&1; then
|
|
22
|
+
echo "dpkg is required on this host." >&2
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
if [[ "${EUID}" -eq 0 ]]; then
|
|
27
|
+
SUDO=()
|
|
28
|
+
else
|
|
29
|
+
if ! command -v sudo >/dev/null 2>&1; then
|
|
30
|
+
echo "Run this script as root or install sudo first." >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
SUDO=(sudo)
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
TARGET_USER="${DOCKER_TARGET_USER:-${SUDO_USER:-}}"
|
|
37
|
+
ARCHITECTURE="$(dpkg --print-architecture)"
|
|
38
|
+
CODENAME="${UBUNTU_CODENAME:-${VERSION_CODENAME:-}}"
|
|
39
|
+
|
|
40
|
+
if [[ -z "${CODENAME}" ]]; then
|
|
41
|
+
echo "Unable to determine the Ubuntu codename." >&2
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
CONFLICTING_PACKAGES=(
|
|
46
|
+
docker.io
|
|
47
|
+
docker-compose
|
|
48
|
+
docker-compose-v2
|
|
49
|
+
docker-doc
|
|
50
|
+
podman-docker
|
|
51
|
+
containerd
|
|
52
|
+
runc
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
DOCKER_PACKAGES=(
|
|
56
|
+
docker-ce
|
|
57
|
+
docker-ce-cli
|
|
58
|
+
containerd.io
|
|
59
|
+
docker-buildx-plugin
|
|
60
|
+
docker-compose-plugin
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
run() {
|
|
64
|
+
echo "+ $*"
|
|
65
|
+
"$@"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
echo "Installing Docker Engine for Ubuntu ${VERSION_ID:-unknown} (${CODENAME}) on ${ARCHITECTURE}."
|
|
69
|
+
|
|
70
|
+
mapfile -t INSTALLED_CONFLICTS < <(
|
|
71
|
+
dpkg-query -W -f='${db:Status-Abbrev} ${binary:Package}\n' "${CONFLICTING_PACKAGES[@]}" 2>/dev/null |
|
|
72
|
+
awk '$1 ~ /^ii/ { print $2 }'
|
|
73
|
+
)
|
|
74
|
+
if [[ "${#INSTALLED_CONFLICTS[@]}" -gt 0 ]]; then
|
|
75
|
+
run "${SUDO[@]}" apt-get remove -y "${INSTALLED_CONFLICTS[@]}"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
run "${SUDO[@]}" apt-get update
|
|
79
|
+
run "${SUDO[@]}" apt-get install -y ca-certificates curl
|
|
80
|
+
run "${SUDO[@]}" install -m 0755 -d /etc/apt/keyrings
|
|
81
|
+
run "${SUDO[@]}" curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
|
82
|
+
run "${SUDO[@]}" chmod a+r /etc/apt/keyrings/docker.asc
|
|
83
|
+
|
|
84
|
+
DOCKER_SOURCE_FILE="/etc/apt/sources.list.d/docker.sources"
|
|
85
|
+
DOCKER_SOURCE_CONTENT="$(cat <<EOF
|
|
86
|
+
Types: deb
|
|
87
|
+
URIs: https://download.docker.com/linux/ubuntu
|
|
88
|
+
Suites: ${CODENAME}
|
|
89
|
+
Components: stable
|
|
90
|
+
Architectures: ${ARCHITECTURE}
|
|
91
|
+
Signed-By: /etc/apt/keyrings/docker.asc
|
|
92
|
+
EOF
|
|
93
|
+
)"
|
|
94
|
+
|
|
95
|
+
echo "+ writing ${DOCKER_SOURCE_FILE}"
|
|
96
|
+
printf '%s\n' "${DOCKER_SOURCE_CONTENT}" | "${SUDO[@]}" tee "${DOCKER_SOURCE_FILE}" >/dev/null
|
|
97
|
+
|
|
98
|
+
run "${SUDO[@]}" apt-get update
|
|
99
|
+
run "${SUDO[@]}" apt-get install -y "${DOCKER_PACKAGES[@]}"
|
|
100
|
+
|
|
101
|
+
if command -v systemctl >/dev/null 2>&1; then
|
|
102
|
+
run "${SUDO[@]}" systemctl enable --now containerd
|
|
103
|
+
run "${SUDO[@]}" systemctl enable --now docker
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if [[ -n "${TARGET_USER}" && "${TARGET_USER}" != "root" ]]; then
|
|
107
|
+
if ! getent group docker >/dev/null 2>&1; then
|
|
108
|
+
run "${SUDO[@]}" groupadd docker
|
|
109
|
+
fi
|
|
110
|
+
run "${SUDO[@]}" usermod -aG docker "${TARGET_USER}"
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
run "${SUDO[@]}" docker version
|
|
114
|
+
|
|
115
|
+
if [[ "${RUN_HELLO_WORLD:-1}" == "1" ]]; then
|
|
116
|
+
run "${SUDO[@]}" docker run --rm hello-world
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
if [[ -n "${TARGET_USER}" && "${TARGET_USER}" != "root" ]]; then
|
|
120
|
+
run "${SUDO[@]}" -u "${TARGET_USER}" sg docker -c "docker version >/dev/null"
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
echo
|
|
124
|
+
echo "Docker installation is complete."
|
|
125
|
+
echo "CLI checks:"
|
|
126
|
+
echo " docker --version"
|
|
127
|
+
echo " docker compose version"
|
|
128
|
+
echo
|
|
129
|
+
if [[ -n "${TARGET_USER}" && "${TARGET_USER}" != "root" ]]; then
|
|
130
|
+
echo "The user '${TARGET_USER}' was added to the docker group."
|
|
131
|
+
echo "Open a new login shell, sign out and back in, or run:"
|
|
132
|
+
echo " newgrp docker"
|
|
133
|
+
echo
|
|
134
|
+
echo "After group refresh, verify non-root access with:"
|
|
135
|
+
echo " docker run --rm hello-world"
|
|
136
|
+
else
|
|
137
|
+
echo "No non-root target user was configured."
|
|
138
|
+
echo "If you ran this as root and want a regular user to access Docker, rerun with:"
|
|
139
|
+
echo " DOCKER_TARGET_USER=<your-username> ./scripts/install-docker-ubuntu.sh"
|
|
140
|
+
fi
|