augure 0.8.0 → 0.9.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/dist/bin.js +1031 -9285
- package/dist/chunk-3BGYMW62.js +272 -0
- package/dist/chunk-62OPINAX.js +16 -0
- package/dist/chunk-63TMJ6FS.js +5091 -0
- package/dist/chunk-DZMTL3S3.js +120 -0
- package/dist/chunk-FYS2JH42.js +31 -0
- package/dist/chunk-H6PBSIJ5.js +145 -0
- package/dist/chunk-KC5S36DB.js +2074 -0
- package/dist/chunk-M63XRSRV.js +164 -0
- package/dist/chunk-TP6A6GFU.js +348 -0
- package/dist/chunk-XOMIXLMX.js +1294 -0
- package/dist/dist-6CLLI773.js +15 -0
- package/dist/dist-BKJP4DMX.js +45 -0
- package/dist/dist-FE4CO3VD.js +11 -0
- package/dist/dist-FMYMCIS2.js +30 -0
- package/dist/dist-JEXFY3AR.js +41 -0
- package/dist/dist-LIV4W4QA.js +13 -0
- package/dist/dist-QR2YZUIK.js +12 -0
- package/dist/mcp-server-AUASYDPU.js +8 -0
- package/package.json +11 -9
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../memory/dist/store.js
|
|
4
|
+
import { readFile, writeFile, mkdir, readdir, access } from "fs/promises";
|
|
5
|
+
import { join, dirname, relative } from "path";
|
|
6
|
+
var FileMemoryStore = class {
|
|
7
|
+
basePath;
|
|
8
|
+
constructor(basePath) {
|
|
9
|
+
this.basePath = basePath;
|
|
10
|
+
}
|
|
11
|
+
async read(path) {
|
|
12
|
+
return readFile(this.resolve(path), "utf-8");
|
|
13
|
+
}
|
|
14
|
+
async write(path, content) {
|
|
15
|
+
const full = this.resolve(path);
|
|
16
|
+
await mkdir(dirname(full), { recursive: true });
|
|
17
|
+
await writeFile(full, content, "utf-8");
|
|
18
|
+
}
|
|
19
|
+
async append(path, content) {
|
|
20
|
+
const full = this.resolve(path);
|
|
21
|
+
try {
|
|
22
|
+
const existing = await readFile(full, "utf-8");
|
|
23
|
+
await writeFile(full, existing + content, "utf-8");
|
|
24
|
+
} catch {
|
|
25
|
+
await this.write(path, content);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async list(directory) {
|
|
29
|
+
const dir = directory ? this.resolve(directory) : this.basePath;
|
|
30
|
+
return this.listRecursive(dir);
|
|
31
|
+
}
|
|
32
|
+
async exists(path) {
|
|
33
|
+
try {
|
|
34
|
+
await access(this.resolve(path));
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
resolve(path) {
|
|
41
|
+
return join(this.basePath, path);
|
|
42
|
+
}
|
|
43
|
+
async listRecursive(dir) {
|
|
44
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
45
|
+
const files = [];
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
const full = join(dir, entry.name);
|
|
48
|
+
if (entry.isDirectory()) {
|
|
49
|
+
files.push(...await this.listRecursive(full));
|
|
50
|
+
} else {
|
|
51
|
+
files.push(relative(this.basePath, full));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return files;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ../memory/dist/ingest.js
|
|
59
|
+
var EXTRACTION_PROMPT = `You are a memory extraction agent. Given a conversation, extract key factual observations about the user.
|
|
60
|
+
|
|
61
|
+
Rules:
|
|
62
|
+
- Return a markdown bullet list of observations (one per line, starting with "- ")
|
|
63
|
+
- Only extract facts, preferences, decisions, plans, and personal details
|
|
64
|
+
- Be concise: one fact per bullet
|
|
65
|
+
- If there are no notable observations, return exactly "No notable observations."
|
|
66
|
+
- Do not include greetings, small talk, or meta-conversation
|
|
67
|
+
- Use present tense ("User prefers X", not "User said they prefer X")
|
|
68
|
+
|
|
69
|
+
Example output:
|
|
70
|
+
- User prefers TypeScript over JavaScript
|
|
71
|
+
- User is building a project called Augure
|
|
72
|
+
- User lives in Bordeaux, France`;
|
|
73
|
+
var MemoryIngester = class {
|
|
74
|
+
llm;
|
|
75
|
+
store;
|
|
76
|
+
constructor(llm, store) {
|
|
77
|
+
this.llm = llm;
|
|
78
|
+
this.store = store;
|
|
79
|
+
}
|
|
80
|
+
async ingest(conversation) {
|
|
81
|
+
if (conversation.length === 0)
|
|
82
|
+
return;
|
|
83
|
+
const conversationText = conversation.filter((m) => m.role === "user" || m.role === "assistant").map((m) => `${m.role}: ${m.content}`).join("\n");
|
|
84
|
+
const messages = [
|
|
85
|
+
{ role: "system", content: EXTRACTION_PROMPT },
|
|
86
|
+
{ role: "user", content: conversationText }
|
|
87
|
+
];
|
|
88
|
+
const response = await this.llm.chat(messages);
|
|
89
|
+
const observations = this.parseObservations(response.content);
|
|
90
|
+
if (observations.length === 0)
|
|
91
|
+
return;
|
|
92
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
93
|
+
const block = `## ${date}
|
|
94
|
+
${observations.map((o) => `- ${o}`).join("\n")}
|
|
95
|
+
|
|
96
|
+
`;
|
|
97
|
+
await this.store.append("observations.md", block);
|
|
98
|
+
}
|
|
99
|
+
parseObservations(content) {
|
|
100
|
+
const lines = content.split("\n");
|
|
101
|
+
return lines.filter((line) => line.trim().startsWith("- ")).map((line) => line.trim().slice(2).trim()).filter((line) => line.length > 0);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// ../memory/dist/retrieve.js
|
|
106
|
+
var PRIORITY_FILES = ["identity.md", "observations.md"];
|
|
107
|
+
var CHARS_PER_TOKEN = 4;
|
|
108
|
+
var MemoryRetriever = class {
|
|
109
|
+
store;
|
|
110
|
+
maxChars;
|
|
111
|
+
constructor(store, options = {}) {
|
|
112
|
+
this.store = store;
|
|
113
|
+
const maxTokens = options.maxTokens ?? 1e4;
|
|
114
|
+
this.maxChars = maxTokens * CHARS_PER_TOKEN;
|
|
115
|
+
}
|
|
116
|
+
async retrieve() {
|
|
117
|
+
const allFiles = await this.safeList();
|
|
118
|
+
if (allFiles.length === 0)
|
|
119
|
+
return "";
|
|
120
|
+
const prioritySet = new Set(PRIORITY_FILES);
|
|
121
|
+
const orderedFiles = [
|
|
122
|
+
...PRIORITY_FILES.filter((f) => allFiles.includes(f)),
|
|
123
|
+
...allFiles.filter((f) => !prioritySet.has(f)).sort()
|
|
124
|
+
];
|
|
125
|
+
const sections = [];
|
|
126
|
+
let totalChars = 0;
|
|
127
|
+
for (const file of orderedFiles) {
|
|
128
|
+
if (totalChars >= this.maxChars)
|
|
129
|
+
break;
|
|
130
|
+
try {
|
|
131
|
+
const content = await this.store.read(file);
|
|
132
|
+
const header = `### ${file}`;
|
|
133
|
+
const section = `${header}
|
|
134
|
+
${content}`;
|
|
135
|
+
const sectionChars = section.length;
|
|
136
|
+
if (totalChars + sectionChars > this.maxChars) {
|
|
137
|
+
const remaining = this.maxChars - totalChars;
|
|
138
|
+
if (remaining > header.length + 50) {
|
|
139
|
+
sections.push(section.slice(0, remaining) + "\n[...truncated]");
|
|
140
|
+
totalChars = this.maxChars;
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
sections.push(section);
|
|
145
|
+
totalChars += sectionChars;
|
|
146
|
+
} catch {
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return sections.join("\n\n");
|
|
150
|
+
}
|
|
151
|
+
async safeList() {
|
|
152
|
+
try {
|
|
153
|
+
return await this.store.list();
|
|
154
|
+
} catch {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export {
|
|
161
|
+
FileMemoryStore,
|
|
162
|
+
MemoryIngester,
|
|
163
|
+
MemoryRetriever
|
|
164
|
+
};
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
noopLogger
|
|
4
|
+
} from "./chunk-62OPINAX.js";
|
|
5
|
+
|
|
6
|
+
// ../sandbox/dist/container.js
|
|
7
|
+
import { PassThrough } from "stream";
|
|
8
|
+
var MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
9
|
+
var DockerContainer = class {
|
|
10
|
+
id;
|
|
11
|
+
_status = "idle";
|
|
12
|
+
raw;
|
|
13
|
+
demux;
|
|
14
|
+
constructor(raw, demux) {
|
|
15
|
+
this.raw = raw;
|
|
16
|
+
this.id = raw.id;
|
|
17
|
+
this.demux = demux;
|
|
18
|
+
}
|
|
19
|
+
get status() {
|
|
20
|
+
return this._status;
|
|
21
|
+
}
|
|
22
|
+
async exec(command, opts) {
|
|
23
|
+
this._status = "busy";
|
|
24
|
+
try {
|
|
25
|
+
return await this._exec(command, opts);
|
|
26
|
+
} finally {
|
|
27
|
+
if (this._status !== "stopped") {
|
|
28
|
+
this._status = "idle";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async stop() {
|
|
33
|
+
if (this._status === "stopped")
|
|
34
|
+
return;
|
|
35
|
+
try {
|
|
36
|
+
await this.raw.stop({ t: 5 });
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
await this.raw.remove({ force: true });
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
this._status = "stopped";
|
|
44
|
+
}
|
|
45
|
+
/* ------------------------------------------------------------------ */
|
|
46
|
+
async _exec(command, opts) {
|
|
47
|
+
const createOpts = {
|
|
48
|
+
Cmd: ["sh", "-c", command],
|
|
49
|
+
AttachStdout: true,
|
|
50
|
+
AttachStderr: true
|
|
51
|
+
};
|
|
52
|
+
if (opts?.cwd) {
|
|
53
|
+
createOpts.WorkingDir = opts.cwd;
|
|
54
|
+
}
|
|
55
|
+
if (opts?.env) {
|
|
56
|
+
createOpts.Env = Object.entries(opts.env).map(([k, v]) => `${k}=${v}`);
|
|
57
|
+
}
|
|
58
|
+
const exec = await this.raw.exec(createOpts);
|
|
59
|
+
const stream = await exec.start({ hijack: true, stdin: false });
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const stdoutPT = new PassThrough();
|
|
62
|
+
const stderrPT = new PassThrough();
|
|
63
|
+
const stdoutBufs = [];
|
|
64
|
+
const stderrBufs = [];
|
|
65
|
+
let stdoutLen = 0;
|
|
66
|
+
let stderrLen = 0;
|
|
67
|
+
stdoutPT.on("data", (chunk) => {
|
|
68
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
69
|
+
const remaining = MAX_OUTPUT_BYTES - stdoutLen;
|
|
70
|
+
if (remaining > 0) {
|
|
71
|
+
stdoutBufs.push(buf.subarray(0, remaining));
|
|
72
|
+
stdoutLen += Math.min(buf.length, remaining);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
stderrPT.on("data", (chunk) => {
|
|
76
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
77
|
+
const remaining = MAX_OUTPUT_BYTES - stderrLen;
|
|
78
|
+
if (remaining > 0) {
|
|
79
|
+
stderrBufs.push(buf.subarray(0, remaining));
|
|
80
|
+
stderrLen += Math.min(buf.length, remaining);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
let timer;
|
|
84
|
+
let settled = false;
|
|
85
|
+
let stdoutEnded = false;
|
|
86
|
+
let stderrEnded = false;
|
|
87
|
+
const cleanup = () => {
|
|
88
|
+
if (timer)
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
};
|
|
91
|
+
if (opts?.timeout && opts.timeout > 0) {
|
|
92
|
+
const timeoutMs = opts.timeout * 1e3;
|
|
93
|
+
timer = setTimeout(() => {
|
|
94
|
+
if (!settled) {
|
|
95
|
+
settled = true;
|
|
96
|
+
stream.destroy();
|
|
97
|
+
reject(new Error(`Exec timed out after ${opts.timeout}s`));
|
|
98
|
+
}
|
|
99
|
+
}, timeoutMs);
|
|
100
|
+
}
|
|
101
|
+
const tryResolve = async () => {
|
|
102
|
+
if (!stdoutEnded || !stderrEnded)
|
|
103
|
+
return;
|
|
104
|
+
if (settled)
|
|
105
|
+
return;
|
|
106
|
+
settled = true;
|
|
107
|
+
cleanup();
|
|
108
|
+
try {
|
|
109
|
+
const info = await exec.inspect();
|
|
110
|
+
resolve({
|
|
111
|
+
exitCode: info.ExitCode ?? 1,
|
|
112
|
+
stdout: Buffer.concat(stdoutBufs).toString("utf-8"),
|
|
113
|
+
stderr: Buffer.concat(stderrBufs).toString("utf-8")
|
|
114
|
+
});
|
|
115
|
+
} catch (err) {
|
|
116
|
+
reject(err);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
stdoutPT.on("end", () => {
|
|
120
|
+
stdoutEnded = true;
|
|
121
|
+
tryResolve();
|
|
122
|
+
});
|
|
123
|
+
stderrPT.on("end", () => {
|
|
124
|
+
stderrEnded = true;
|
|
125
|
+
tryResolve();
|
|
126
|
+
});
|
|
127
|
+
stream.on("error", (err) => {
|
|
128
|
+
if (!settled) {
|
|
129
|
+
settled = true;
|
|
130
|
+
cleanup();
|
|
131
|
+
reject(err);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
this.demux(stream, stdoutPT, stderrPT);
|
|
135
|
+
stream.on("end", () => {
|
|
136
|
+
stdoutPT.end();
|
|
137
|
+
stderrPT.end();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// ../sandbox/dist/pool.js
|
|
144
|
+
function parseMemory(mem) {
|
|
145
|
+
const match = mem.match(/^(\d+(?:\.\d+)?)\s*([mg])$/i);
|
|
146
|
+
if (!match)
|
|
147
|
+
throw new Error(`Invalid memory value: ${mem}`);
|
|
148
|
+
const value = parseFloat(match[1]);
|
|
149
|
+
const unit = match[2].toLowerCase();
|
|
150
|
+
if (unit === "m")
|
|
151
|
+
return Math.round(value * 1024 * 1024);
|
|
152
|
+
return Math.round(value * 1024 * 1024 * 1024);
|
|
153
|
+
}
|
|
154
|
+
function parseCpu(cpu) {
|
|
155
|
+
const value = parseFloat(cpu);
|
|
156
|
+
if (Number.isNaN(value))
|
|
157
|
+
throw new Error(`Invalid cpu value: ${cpu}`);
|
|
158
|
+
return Math.round(value * 1e9);
|
|
159
|
+
}
|
|
160
|
+
var DockerContainerPool = class {
|
|
161
|
+
docker;
|
|
162
|
+
image;
|
|
163
|
+
maxTotal;
|
|
164
|
+
log;
|
|
165
|
+
// C3: idle cache keyed by trust level to prevent cross-trust reuse
|
|
166
|
+
idle = /* @__PURE__ */ new Map([
|
|
167
|
+
["sandboxed", /* @__PURE__ */ new Set()],
|
|
168
|
+
["trusted", /* @__PURE__ */ new Set()]
|
|
169
|
+
]);
|
|
170
|
+
busy = /* @__PURE__ */ new Set();
|
|
171
|
+
containerTrust = /* @__PURE__ */ new Map();
|
|
172
|
+
constructor(docker, config) {
|
|
173
|
+
this.docker = docker;
|
|
174
|
+
this.image = config.image;
|
|
175
|
+
this.maxTotal = config.maxTotal;
|
|
176
|
+
this.log = config.logger ?? noopLogger;
|
|
177
|
+
}
|
|
178
|
+
get idleCount() {
|
|
179
|
+
let count = 0;
|
|
180
|
+
for (const set of this.idle.values())
|
|
181
|
+
count += set.size;
|
|
182
|
+
return count;
|
|
183
|
+
}
|
|
184
|
+
/* ---- acquire ---- */
|
|
185
|
+
async acquire(opts) {
|
|
186
|
+
this.log.debug(`Acquiring container: trust=${opts.trust} memory=${opts.memory} cpu=${opts.cpu}`);
|
|
187
|
+
const trustIdle = this.idle.get(opts.trust);
|
|
188
|
+
const cached = trustIdle.values().next();
|
|
189
|
+
if (!cached.done) {
|
|
190
|
+
const container2 = cached.value;
|
|
191
|
+
trustIdle.delete(container2);
|
|
192
|
+
this.busy.add(container2);
|
|
193
|
+
this.log.debug(`Reusing cached container: ${container2.id.slice(0, 12)}`);
|
|
194
|
+
return container2;
|
|
195
|
+
}
|
|
196
|
+
const total = this.idleCount + this.busy.size;
|
|
197
|
+
if (total >= this.maxTotal) {
|
|
198
|
+
this.log.error(`Pool limit reached: ${total}/${this.maxTotal}`);
|
|
199
|
+
throw new Error("Pool limit reached");
|
|
200
|
+
}
|
|
201
|
+
this.log.debug("Creating new container...");
|
|
202
|
+
const raw = await this.docker.createContainer(this.buildCreateOpts(opts));
|
|
203
|
+
await raw.start();
|
|
204
|
+
const modem = this.docker.modem;
|
|
205
|
+
const demux = modem.demuxStream.bind(modem);
|
|
206
|
+
const container = new DockerContainer(raw, demux);
|
|
207
|
+
this.containerTrust.set(container.id, opts.trust);
|
|
208
|
+
this.busy.add(container);
|
|
209
|
+
this.log.debug(`Container created: ${container.id.slice(0, 12)}`);
|
|
210
|
+
return container;
|
|
211
|
+
}
|
|
212
|
+
/* ---- release ---- */
|
|
213
|
+
async release(container) {
|
|
214
|
+
this.busy.delete(container);
|
|
215
|
+
if (container.status === "stopped") {
|
|
216
|
+
this.containerTrust.delete(container.id);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const trust = this.containerTrust.get(container.id) ?? "sandboxed";
|
|
220
|
+
this.idle.get(trust).add(container);
|
|
221
|
+
this.log.debug(`Container released: ${container.id.slice(0, 12)} \u2192 idle (${trust})`);
|
|
222
|
+
}
|
|
223
|
+
/* ---- destroy ---- */
|
|
224
|
+
async destroy(container) {
|
|
225
|
+
this.busy.delete(container);
|
|
226
|
+
for (const set of this.idle.values())
|
|
227
|
+
set.delete(container);
|
|
228
|
+
this.containerTrust.delete(container.id);
|
|
229
|
+
await container.stop();
|
|
230
|
+
}
|
|
231
|
+
/* ---- destroyAll ---- */
|
|
232
|
+
async destroyAll() {
|
|
233
|
+
const all = [...this.busy];
|
|
234
|
+
for (const set of this.idle.values()) {
|
|
235
|
+
all.push(...set);
|
|
236
|
+
set.clear();
|
|
237
|
+
}
|
|
238
|
+
this.busy.clear();
|
|
239
|
+
this.containerTrust.clear();
|
|
240
|
+
await Promise.allSettled(all.map((c) => c.stop()));
|
|
241
|
+
}
|
|
242
|
+
/* ---- stats ---- */
|
|
243
|
+
stats() {
|
|
244
|
+
const idle = this.idleCount;
|
|
245
|
+
return {
|
|
246
|
+
idle,
|
|
247
|
+
busy: this.busy.size,
|
|
248
|
+
total: idle + this.busy.size,
|
|
249
|
+
maxTotal: this.maxTotal
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/* ---- internal ---- */
|
|
253
|
+
buildCreateOpts(opts) {
|
|
254
|
+
const isSandboxed = opts.trust === "sandboxed";
|
|
255
|
+
const hostConfig = {
|
|
256
|
+
Memory: parseMemory(opts.memory),
|
|
257
|
+
NanoCpus: parseCpu(opts.cpu),
|
|
258
|
+
PidsLimit: 512
|
|
259
|
+
// I6: prevent fork bombs
|
|
260
|
+
};
|
|
261
|
+
if (!isSandboxed) {
|
|
262
|
+
hostConfig.NetworkMode = "host";
|
|
263
|
+
}
|
|
264
|
+
if (opts.mounts?.length) {
|
|
265
|
+
hostConfig.Binds = opts.mounts.map((m) => `${m.host}:${m.container}${m.readonly ? ":ro" : ""}`);
|
|
266
|
+
}
|
|
267
|
+
const createOpts = {
|
|
268
|
+
Image: this.image,
|
|
269
|
+
Cmd: ["sleep", "infinity"],
|
|
270
|
+
WorkingDir: "/workspace",
|
|
271
|
+
NetworkDisabled: isSandboxed,
|
|
272
|
+
HostConfig: hostConfig
|
|
273
|
+
};
|
|
274
|
+
if (opts.env) {
|
|
275
|
+
createOpts.Env = Object.entries(opts.env).map(([k, v]) => `${k}=${v}`);
|
|
276
|
+
}
|
|
277
|
+
return createOpts;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// ../sandbox/dist/ensure-image.js
|
|
282
|
+
import { Readable } from "stream";
|
|
283
|
+
var DOCKERFILE = `FROM node:22-slim
|
|
284
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \\
|
|
285
|
+
python3 curl jq git \\
|
|
286
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
287
|
+
WORKDIR /workspace
|
|
288
|
+
`;
|
|
289
|
+
function buildTar(content) {
|
|
290
|
+
const data = Buffer.from(content, "utf-8");
|
|
291
|
+
const name = "Dockerfile";
|
|
292
|
+
const header = Buffer.alloc(512, 0);
|
|
293
|
+
header.write(name, 0, 100, "utf-8");
|
|
294
|
+
header.write("0000644\0", 100, 8, "utf-8");
|
|
295
|
+
header.write("0000000\0", 108, 8, "utf-8");
|
|
296
|
+
header.write("0000000\0", 116, 8, "utf-8");
|
|
297
|
+
header.write(data.length.toString(8).padStart(11, "0") + "\0", 124, 12, "utf-8");
|
|
298
|
+
header.write(Math.floor(Date.now() / 1e3).toString(8).padStart(11, "0") + "\0", 136, 12, "utf-8");
|
|
299
|
+
header.write("0", 156, 1, "utf-8");
|
|
300
|
+
header.fill(32, 148, 156);
|
|
301
|
+
let checksum = 0;
|
|
302
|
+
for (let i = 0; i < 512; i++)
|
|
303
|
+
checksum += header[i];
|
|
304
|
+
header.write(checksum.toString(8).padStart(6, "0") + "\0 ", 148, 8, "utf-8");
|
|
305
|
+
const padding = 512 - (data.length % 512 || 512);
|
|
306
|
+
const dataPadded = padding > 0 && padding < 512 ? Buffer.concat([data, Buffer.alloc(padding, 0)]) : data;
|
|
307
|
+
const end = Buffer.alloc(1024, 0);
|
|
308
|
+
return Buffer.concat([header, dataPadded, end]);
|
|
309
|
+
}
|
|
310
|
+
async function ensureImage(docker, imageName, logger) {
|
|
311
|
+
const log = logger ?? noopLogger;
|
|
312
|
+
log.debug(`Checking image: ${imageName}`);
|
|
313
|
+
try {
|
|
314
|
+
await docker.getImage(imageName).inspect();
|
|
315
|
+
log.debug("Image exists");
|
|
316
|
+
return;
|
|
317
|
+
} catch (err) {
|
|
318
|
+
const statusCode = err.statusCode;
|
|
319
|
+
if (statusCode !== void 0 && statusCode !== 404)
|
|
320
|
+
throw err;
|
|
321
|
+
}
|
|
322
|
+
log.info(`Image "${imageName}" not found, building...`);
|
|
323
|
+
const tar = buildTar(DOCKERFILE);
|
|
324
|
+
const stream = await docker.buildImage(Readable.from(tar), {
|
|
325
|
+
t: imageName
|
|
326
|
+
});
|
|
327
|
+
await new Promise((resolve, reject) => {
|
|
328
|
+
docker.modem.followProgress(stream, (err) => {
|
|
329
|
+
if (err)
|
|
330
|
+
reject(err);
|
|
331
|
+
else
|
|
332
|
+
resolve();
|
|
333
|
+
}, (event) => {
|
|
334
|
+
if (event.stream) {
|
|
335
|
+
const line = event.stream.trim();
|
|
336
|
+
if (line)
|
|
337
|
+
log.debug(line);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
log.info(`Image "${imageName}" built`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export {
|
|
345
|
+
DockerContainer,
|
|
346
|
+
DockerContainerPool,
|
|
347
|
+
ensureImage
|
|
348
|
+
};
|