@outerlayer/cli 0.2.0 → 0.4.1
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/CHANGELOG.md +132 -0
- package/README.md +229 -60
- package/dist/agent-setup-IFH3FB5X.js +197 -0
- package/dist/build-TZUUTRGH.js +9 -0
- package/dist/build-info.json +1 -1
- package/dist/check-7MFH6HNC.js +94 -0
- package/dist/chunk-3TFPDVTI.js +94 -0
- package/dist/{chunk-4TNZMO7V.js → chunk-4C4THABW.js} +5566 -754
- package/dist/{chunk-R5KBGVII.js → chunk-5QRP3MVS.js} +30 -14
- package/dist/chunk-5Y3QQRUH.js +300 -0
- package/dist/chunk-774HEQL6.js +178 -0
- package/dist/{work-pr-cmd-AZQPWH4H.js → chunk-7WERLFVR.js} +9 -26
- package/dist/chunk-ABIWDUMS.js +104 -0
- package/dist/chunk-AUFG23AA.js +534 -0
- package/dist/chunk-B2G7JHHB.js +42 -0
- package/dist/chunk-BCJNHMZT.js +34 -0
- package/dist/chunk-BCLJSQCV.js +56 -0
- package/dist/chunk-BFESLKPP.js +61 -0
- package/dist/chunk-BJ3KTMDH.js +63 -0
- package/dist/chunk-BKO6JEZI.js +47 -0
- package/dist/chunk-BOLTI6LR.js +37 -0
- package/dist/chunk-CBPPJSAR.js +89 -0
- package/dist/chunk-DM3VFDS3.js +1461 -0
- package/dist/{chunk-65WXAANR.js → chunk-DVDEBNQJ.js} +17 -2
- package/dist/{chunk-TFUIDMOB.js → chunk-EABW6AJQ.js} +12 -1
- package/dist/{chunk-KFYJV2ZG.js → chunk-EMY4I27X.js} +1 -1
- package/dist/{chunk-NTTPJV35.js → chunk-F47JBIAW.js} +3 -1
- package/dist/chunk-F6GFZXZ3.js +60 -0
- package/dist/{chunk-YOOSBOKS.js → chunk-F7CU5ABH.js} +1 -1
- package/dist/{emit-cmd-TWSYYEZI.js → chunk-H5NCNVDA.js} +68 -17
- package/dist/chunk-HZRNMGLF.js +2233 -0
- package/dist/chunk-LF6MLJCY.js +38 -0
- package/dist/{chunk-U32VSRLO.js → chunk-LT5TZHYH.js} +95 -634
- package/dist/chunk-LYCDNMOH.js +83 -0
- package/dist/chunk-M5POMKKW.js +1051 -0
- package/dist/{mcp-install-cmd-FDQH6SEN.js → chunk-MQ3IPHIZ.js} +35 -18
- package/dist/{chunk-D77LS3UI.js → chunk-N5FOE5PS.js} +33 -4
- package/dist/chunk-N6LURUEF.js +64 -0
- package/dist/chunk-OGFGZQA3.js +23 -0
- package/dist/chunk-QDQEUVUF.js +9 -0
- package/dist/chunk-QOZKPLVJ.js +226 -0
- package/dist/chunk-QY22NZUW.js +1764 -0
- package/dist/chunk-RA3O54FC.js +30 -0
- package/dist/chunk-RFUPN6KX.js +71 -0
- package/dist/chunk-SY4GK4SP.js +55 -0
- package/dist/chunk-TBT347UY.js +41 -0
- package/dist/chunk-TWNWNS2Q.js +1084 -0
- package/dist/{chunk-NXLLURA4.js → chunk-UFFXNXLQ.js} +115 -71
- package/dist/chunk-USO2DKBD.js +421 -0
- package/dist/chunk-W4SQCOZU.js +206 -0
- package/dist/{chunk-Q6CL5THG.js → chunk-W5FVUZXV.js} +208 -61
- package/dist/chunk-WFJ65NUD.js +383 -0
- package/dist/{chunk-A3WLZX2F.js → chunk-WIOZAJ2W.js} +15 -2
- package/dist/chunk-WO2BXCTQ.js +83 -0
- package/dist/{chunk-I3ETLSNE.js → chunk-XCCLFVXM.js} +120 -11
- package/dist/{context-materialize-6WKT3RBQ.js → chunk-XDDW4FRS.js} +134 -26
- package/dist/chunk-Y7KJLXYN.js +232 -0
- package/dist/chunk-YFHIIH4B.js +102 -0
- package/dist/chunk-YYYXRJUW.js +101 -0
- package/dist/chunk-ZM2IMMYN.js +76 -0
- package/dist/chunk-ZMYPLFG3.js +71 -0
- package/dist/chunk-ZNA27WEV.js +470 -0
- package/dist/{cli-G7DILYJY.js → cli-W62AFRSW.js} +616 -878
- package/dist/{paths-D2VGWWFI.js → cli-build-K56DK4DS.js} +1 -1
- package/dist/config-XVJYZ4IQ.js +9 -0
- package/dist/connect-cmd-ZTJONP37.js +16 -0
- package/dist/context-adopt-UMA4O5NY.js +105 -0
- package/dist/context-materialize-G2FUFC72.js +19 -0
- package/dist/dist-44CQVWGT.js +6 -0
- package/dist/docker-NEGDLU6D.js +7 -0
- package/dist/doctor-53F2PYY7.js +43 -0
- package/dist/doctor-ZLGZYMDK.js +426 -0
- package/dist/{emit-artifact-cmd-UQVT3OKW.js → emit-artifact-cmd-AKZP7TMF.js} +87 -23
- package/dist/emit-cmd-DCERAU27.js +9 -0
- package/dist/emit-criteria-cmd-7MXQVCKD.js +166 -0
- package/dist/{emit-finding-cmd-FDOU2OPD.js → emit-finding-cmd-N2FTGCVC.js} +34 -31
- package/dist/{emit-result-cmd-H2T4C2K3.js → emit-result-cmd-4CPDBMN2.js} +34 -62
- package/dist/exec-client-4XGXV3XN.js +8 -0
- package/dist/guest-init-GENRHNP7.js +8 -0
- package/dist/hook-fast-EYENPK6F.js +9 -0
- package/dist/{hook-wrap-fast-KXYNX3AD.js → hook-wrap-fast-NPLHJXFK.js} +2 -2
- package/dist/host-key-KDYZ5ECC.js +7 -0
- package/dist/import-capture-cmd-AVZ4VIVJ.js +68 -0
- package/dist/{import-ruler-cmd-7LH2QOMG.js → import-ruler-cmd-GUHL6IY5.js} +1 -1
- package/dist/index.js +4 -4
- package/dist/init-55Y4LHWF.js +36 -0
- package/dist/init-XXEKWZQP.js +221 -0
- package/dist/init-cmd-WLST2F7G.js +135 -0
- package/dist/install-cmd-7WXOIFDO.js +55 -0
- package/dist/lima-XN65D7GN.js +55 -0
- package/dist/login-browser-7XGSV7MA.js +10 -0
- package/dist/{config-POF7DEQW.js → logout-cmd-QAUFDJ6J.js} +3 -1
- package/dist/{logs-TRNPQM42.js → logs-7RYUH5A6.js} +1 -1
- package/dist/loop-JFIBP4B7.js +43 -0
- package/dist/machine-ZBPT2J3R.js +35 -0
- package/dist/mcp-install-cmd-RLK4SO3N.js +9 -0
- package/dist/{mcp-serve-cmd-57EZZOTL.js → mcp-serve-cmd-N6UD2KBX.js} +20 -9
- package/dist/paths-OYKMVYJP.js +6 -0
- package/dist/{pidfile-PTW76F56.js → pidfile-UZRH774M.js} +2 -3
- package/dist/real-deps-SU24ZA2K.js +21 -0
- package/dist/relay-L76HDX72.js +46 -0
- package/dist/settings-J2652U5N.js +7 -0
- package/dist/starter-pack-LIZYMKYQ.js +10 -0
- package/dist/{status-I27IST4L.js → status-UJXWZRN5.js} +30 -13
- package/dist/{statusline-fast-3C5OXHDD.js → statusline-fast-SVTMBMD7.js} +3 -2
- package/dist/sync-cmd-4G4A7EVN.js +28 -0
- package/dist/version-BWM6VLDI.js +6 -0
- package/dist/{watch-3DTPJETH.js → watch-7SKJKGAA.js} +19 -8
- package/dist/{work-claim-cmd-5AUNCLS2.js → work-claim-cmd-5F7VZ42N.js} +34 -21
- package/dist/work-cmd-QAUZ7MTD.js +17 -0
- package/dist/work-comment-cmd-BNCSBTLH.js +106 -0
- package/dist/{work-launch-LT663PB3.js → work-launch-YI4CJDAP.js} +1 -1
- package/dist/work-open-pr-cmd-VFYZHIIW.js +156 -0
- package/dist/work-pr-cmd-PV32ZLSI.js +16 -0
- package/package.json +13 -3
- package/skill-pack/maintained/amend/SKILL.md +104 -0
- package/skill-pack/maintained/emitting-evidence/SKILL.md +108 -0
- package/skill-pack/maintained/emitting-evidence/references/agents-snippet.md +20 -0
- package/skill-pack/maintained/outerlayer/SKILL.md +55 -0
- package/skill-pack/maintained/reporting-findings/SKILL.md +148 -0
- package/skill-pack/template/build/SKILL.md +193 -0
- package/skill-pack/template/build/references/agent-briefs.md +243 -0
- package/skill-pack/template/build/references/criteria-judge.md +91 -0
- package/skill-pack/template/build/references/evidence.md +42 -0
- package/skill-pack/template/build/references/release.md +74 -0
- package/skill-pack/template/build/references/review-briefs.md +275 -0
- package/skill-pack/template/build/references/review-loop.md +158 -0
- package/skill-pack/template/build/scripts/record-criteria.mjs +235 -0
- package/skill-pack/template/spec/SKILL.md +84 -0
- package/skill-pack/template/writing-specs/SKILL.md +134 -0
- package/dist/chunk-DCNOXRMV.js +0 -589
- package/dist/chunk-JJP7YLMN.js +0 -25
- package/dist/chunk-OZ7C3XUE.js +0 -34
- package/dist/chunk-WQ6VGRGZ.js +0 -150
- package/dist/hook-fast-J5LCDHUJ.js +0 -8
- package/dist/import-capture-cmd-EUMBGIV3.js +0 -176
- package/dist/init-PTBITAUO.js +0 -103
- package/dist/login-cmd-IRX6LZT7.js +0 -62
- package/dist/loop-ASZRX3CZ.js +0 -648
- package/dist/sync-cmd-BBAUZ5JD.js +0 -17
- package/dist/work-cmd-2P4BVX47.js +0 -18
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { readLaunchSpoolState, launchesPath } from './chunk-DVDEBNQJ.js';
|
|
4
|
+
|
|
5
|
+
createRequire(import.meta.url);
|
|
6
|
+
|
|
7
|
+
// src/launch-filter.ts
|
|
8
|
+
function launchRefusalForSession(sessionId, home) {
|
|
9
|
+
const spool = readLaunchSpoolState(home);
|
|
10
|
+
if (spool.ids.has(sessionId)) return void 0;
|
|
11
|
+
if (spool.unreadable !== void 0) {
|
|
12
|
+
return `the launch spool at ${launchesPath(home)} could not be read (${spool.unreadable}), so this session's launch could not be checked and nothing it produces may leave the machine. Fix the file's permissions and re-run.`;
|
|
13
|
+
}
|
|
14
|
+
if (spool.malformedLines !== void 0) {
|
|
15
|
+
return `the launch spool at ${launchesPath(home)} has ${spool.malformedLines} damaged line(s), so this session's launch could not be checked and nothing it produces may leave the machine.`;
|
|
16
|
+
}
|
|
17
|
+
return `this session was not launched with OUTERLAYER_WORK naming a work item, so nothing it produces may leave the machine. Start the session with OUTERLAYER_WORK set (for example OUTERLAYER_WORK=42), or run this from CI.`;
|
|
18
|
+
}
|
|
19
|
+
var LaunchFilter = class {
|
|
20
|
+
constructor(launchedSessionIds) {
|
|
21
|
+
this.launchedSessionIds = launchedSessionIds;
|
|
22
|
+
/** Sessions dropped for carrying no launch record — for the sync summary. */
|
|
23
|
+
this.skippedTotal = 0;
|
|
24
|
+
}
|
|
25
|
+
/** True when the session should sync. Records skips for reporting. */
|
|
26
|
+
allows(session) {
|
|
27
|
+
const key = session.subagent?.parentSessionId ?? session.id;
|
|
28
|
+
const allowed = typeof key === "string" && this.launchedSessionIds.has(key);
|
|
29
|
+
if (!allowed) this.skippedTotal += 1;
|
|
30
|
+
return allowed;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export { LaunchFilter, launchRefusalForSession };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { readCloudConfig, cloudConfigPath } from './chunk-EABW6AJQ.js';
|
|
4
|
+
import { signRunnerRequest, signerFor } from './chunk-M5POMKKW.js';
|
|
5
|
+
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
6
|
+
|
|
7
|
+
createRequire(import.meta.url);
|
|
8
|
+
|
|
9
|
+
// src/signing-fetch.ts
|
|
10
|
+
function bodyBytes(body) {
|
|
11
|
+
if (body === void 0 || body === null) return new Uint8Array(0);
|
|
12
|
+
if (typeof body === "string") return new TextEncoder().encode(body);
|
|
13
|
+
if (body instanceof Uint8Array) return body;
|
|
14
|
+
if (body instanceof ArrayBuffer) return new Uint8Array(body);
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
async function savedKeySigner(headers, home) {
|
|
18
|
+
const authorization = headers.get("authorization");
|
|
19
|
+
if (authorization === null || !authorization.startsWith("Bearer ")) return void 0;
|
|
20
|
+
if (headers.has("signature")) return void 0;
|
|
21
|
+
if (readCloudConfig(home).apiKey !== authorization.slice("Bearer ".length)) return void 0;
|
|
22
|
+
try {
|
|
23
|
+
return await signerFor(cloudConfigPath(home));
|
|
24
|
+
} catch {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function signedBy(base, signerOf) {
|
|
29
|
+
return (async (input, init) => {
|
|
30
|
+
if (input instanceof Request) return base(input, init);
|
|
31
|
+
const headers = new Headers(init?.headers);
|
|
32
|
+
const signer = await signerOf(headers);
|
|
33
|
+
const body = bodyBytes(init?.body);
|
|
34
|
+
if (signer === void 0 || body === void 0) return base(input, init);
|
|
35
|
+
const signature = await signRunnerRequest({ method: init?.method ?? "GET", url: input.toString(), body, signer });
|
|
36
|
+
for (const [name, value] of Object.entries(signature)) headers.set(name, value);
|
|
37
|
+
return base(input, { ...init, headers });
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function signingFetch(base, home = () => homeFromEnv()) {
|
|
41
|
+
return signedBy(base, (headers) => savedKeySigner(headers, home()));
|
|
42
|
+
}
|
|
43
|
+
function signingFetchFor(base, configPath, apiKey) {
|
|
44
|
+
return signedBy(base, async (headers) => {
|
|
45
|
+
const authorization = headers.get("authorization");
|
|
46
|
+
if (authorization !== `Bearer ${apiKey}` || headers.has("signature")) return void 0;
|
|
47
|
+
try {
|
|
48
|
+
return await signerFor(configPath);
|
|
49
|
+
} catch {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
var defaultFetch = signingFetch((input, init) => globalThis.fetch(input, init));
|
|
55
|
+
|
|
56
|
+
export { defaultFetch, signingFetchFor };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { openSync, fstatSync, readSync, closeSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
|
|
6
|
+
createRequire(import.meta.url);
|
|
7
|
+
var SESSION_DETECT_TAIL_BYTES = 64 * 1024;
|
|
8
|
+
var SESSION_DETECT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
9
|
+
var readTail = (path, maxBytes) => {
|
|
10
|
+
try {
|
|
11
|
+
const fd = openSync(path, "r");
|
|
12
|
+
try {
|
|
13
|
+
const size = fstatSync(fd).size;
|
|
14
|
+
const length = Math.min(size, maxBytes);
|
|
15
|
+
const data = Buffer.alloc(length);
|
|
16
|
+
readSync(fd, data, 0, length, size - length);
|
|
17
|
+
return { data, truncated: size > length };
|
|
18
|
+
} finally {
|
|
19
|
+
closeSync(fd);
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
function detectActiveSession(opts) {
|
|
26
|
+
if (!opts.env.CLAUDECODE) return void 0;
|
|
27
|
+
const read = opts.readTailImpl ?? readTail;
|
|
28
|
+
const tail = read(join(opts.home, ".outerlayer", "spool", "events.jsonl"), SESSION_DETECT_TAIL_BYTES);
|
|
29
|
+
if (!tail || tail.data.length === 0) return void 0;
|
|
30
|
+
let lines = tail.data.toString("utf8").split("\n");
|
|
31
|
+
if (tail.truncated) lines = lines.slice(1);
|
|
32
|
+
const records = [];
|
|
33
|
+
for (const line of lines) {
|
|
34
|
+
const text = line.trim();
|
|
35
|
+
if (!text) continue;
|
|
36
|
+
try {
|
|
37
|
+
records.push(JSON.parse(text));
|
|
38
|
+
} catch {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const ended = /* @__PURE__ */ new Set();
|
|
43
|
+
for (const record of records) {
|
|
44
|
+
if (record.event === "SessionEnd" && typeof record.sessionId === "string" && record.sessionId !== "") {
|
|
45
|
+
ended.add(record.sessionId);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
let newest;
|
|
49
|
+
for (const record of records) {
|
|
50
|
+
if (typeof record.sessionId !== "string" || record.sessionId === "") continue;
|
|
51
|
+
if (record.event === "SessionEnd" || ended.has(record.sessionId)) continue;
|
|
52
|
+
if (typeof record.cwd !== "string" || !opts.cwds.includes(record.cwd)) continue;
|
|
53
|
+
if (typeof record.t !== "string") continue;
|
|
54
|
+
const tMs = Date.parse(record.t);
|
|
55
|
+
if (!Number.isFinite(tMs) || opts.nowMs - tMs > SESSION_DETECT_MAX_AGE_MS) continue;
|
|
56
|
+
newest = record.sessionId;
|
|
57
|
+
}
|
|
58
|
+
return newest;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { detectActiveSession };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { statSync, readFileSync, existsSync, mkdirSync, writeFileSync, chmodSync } from 'fs';
|
|
4
|
+
import { join, isAbsolute, dirname } from 'path';
|
|
5
|
+
|
|
6
|
+
createRequire(import.meta.url);
|
|
7
|
+
function findGitDirs(cwd) {
|
|
8
|
+
let dir = cwd;
|
|
9
|
+
for (; ; ) {
|
|
10
|
+
const gitPath = join(dir, ".git");
|
|
11
|
+
let stat;
|
|
12
|
+
try {
|
|
13
|
+
stat = statSync(gitPath);
|
|
14
|
+
} catch {
|
|
15
|
+
stat = void 0;
|
|
16
|
+
}
|
|
17
|
+
if (stat?.isDirectory()) return { repoRoot: dir, gitCommonDir: gitPath };
|
|
18
|
+
if (stat?.isFile()) {
|
|
19
|
+
try {
|
|
20
|
+
const content = readFileSync(gitPath, "utf8").trim();
|
|
21
|
+
const match = /^gitdir:\s*(.+)$/.exec(content);
|
|
22
|
+
if (match) {
|
|
23
|
+
const gitdir = isAbsolute(match[1]) ? match[1] : join(dir, match[1]);
|
|
24
|
+
try {
|
|
25
|
+
const commondir = readFileSync(join(gitdir, "commondir"), "utf8").trim();
|
|
26
|
+
return { repoRoot: dir, gitCommonDir: isAbsolute(commondir) ? commondir : join(gitdir, commondir) };
|
|
27
|
+
} catch {
|
|
28
|
+
return { repoRoot: dir, gitCommonDir: gitdir };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const parent = dirname(dir);
|
|
35
|
+
if (parent === dir) return void 0;
|
|
36
|
+
dir = parent;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function connectionPath(gitCommonDir) {
|
|
40
|
+
return join(gitCommonDir, "outerlayer", "connection.json");
|
|
41
|
+
}
|
|
42
|
+
function readRepoConnection(cwd) {
|
|
43
|
+
try {
|
|
44
|
+
const dirs = findGitDirs(cwd);
|
|
45
|
+
if (dirs === void 0) return void 0;
|
|
46
|
+
const parsed = JSON.parse(readFileSync(connectionPath(dirs.gitCommonDir), "utf8"));
|
|
47
|
+
if (typeof parsed?.appId !== "string" || parsed.appId === "") return void 0;
|
|
48
|
+
return parsed;
|
|
49
|
+
} catch {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function writeRepoConnection(gitCommonDir, connection) {
|
|
54
|
+
const path = connectionPath(gitCommonDir);
|
|
55
|
+
if (!existsSync(dirname(path))) mkdirSync(dirname(path), { recursive: true });
|
|
56
|
+
writeFileSync(path, JSON.stringify(connection, null, 2) + "\n", { mode: 384 });
|
|
57
|
+
try {
|
|
58
|
+
chmodSync(path, 384);
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { findGitDirs, readRepoConnection, writeRepoConnection };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { mkdirSync, writeFileSync, renameSync, unlinkSync, readFileSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
|
|
6
|
+
createRequire(import.meta.url);
|
|
7
|
+
var SAFE_SESSION_ID = /^[A-Za-z0-9_-]+$/;
|
|
8
|
+
function reportedCostDir(home) {
|
|
9
|
+
return join(home, ".outerlayer", "reported-cost");
|
|
10
|
+
}
|
|
11
|
+
function reportedCostPath(home, sessionId) {
|
|
12
|
+
return join(reportedCostDir(home), `${sessionId}.json`);
|
|
13
|
+
}
|
|
14
|
+
function recordReportedCost(home, sessionId, costUsd) {
|
|
15
|
+
if (typeof sessionId !== "string" || !SAFE_SESSION_ID.test(sessionId)) return;
|
|
16
|
+
if (!Number.isFinite(costUsd) || costUsd < 0) return;
|
|
17
|
+
if (readReportedCost(home, sessionId) === costUsd) return;
|
|
18
|
+
const target = reportedCostPath(home, sessionId);
|
|
19
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
20
|
+
try {
|
|
21
|
+
mkdirSync(reportedCostDir(home), { recursive: true });
|
|
22
|
+
writeFileSync(tmp, JSON.stringify({ costUsd, recordedAt: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
23
|
+
renameSync(tmp, target);
|
|
24
|
+
} catch {
|
|
25
|
+
try {
|
|
26
|
+
unlinkSync(tmp);
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function readReportedCost(home, sessionId) {
|
|
32
|
+
if (!SAFE_SESSION_ID.test(sessionId)) return null;
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(readFileSync(reportedCostPath(home, sessionId), "utf8"));
|
|
35
|
+
return Number.isFinite(parsed.costUsd) && parsed.costUsd >= 0 ? parsed.costUsd : null;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function applyReportedCost(session, home) {
|
|
41
|
+
if (session.agent.type !== "claude-code" || session.subagent?.parentSessionId) return;
|
|
42
|
+
const costUsd = readReportedCost(home, session.id);
|
|
43
|
+
if (costUsd === null) return;
|
|
44
|
+
session.totals.agentReportedCostUsd = costUsd;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { applyReportedCost, recordReportedCost };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { readCloudConfig } from './chunk-EABW6AJQ.js';
|
|
4
|
+
import { readRepoConnection, findGitDirs } from './chunk-BJ3KTMDH.js';
|
|
5
|
+
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
6
|
+
|
|
7
|
+
createRequire(import.meta.url);
|
|
8
|
+
|
|
9
|
+
// src/credentials.ts
|
|
10
|
+
var MISSING_HELP = "Run `outerlayer login` to sign in, or set OUTERLAYER_URL / OUTERLAYER_API_KEY / OUTERLAYER_APP_ID. Mint a key in the dashboard: Settings \u2192 API keys.";
|
|
11
|
+
var NO_FACTORY_MESSAGE = "you are logged in, but no factory is chosen for this repository. Run `outerlayer connect` to choose one, or pass --app-id / set OUTERLAYER_APP_ID.";
|
|
12
|
+
var NO_FACTORY_OUTSIDE_REPOSITORY_MESSAGE = "you are logged in, but this directory is not in a git repository, so no factory is chosen. Pass --app-id / set OUTERLAYER_APP_ID, or run this from inside a connected repository.";
|
|
13
|
+
var LOGIN_TOKEN_PREFIX = "olu_";
|
|
14
|
+
function unauthorizedMessage(credential) {
|
|
15
|
+
return credential.startsWith(LOGIN_TOKEN_PREFIX) ? "not authorized \u2014 the login is expired or was revoked. Run `outerlayer login` to sign in again." : "not authorized \u2014 the API key is unknown, expired, or bound to a different factory. Mint a fresh key: dashboard \u2192 Settings \u2192 API keys.";
|
|
16
|
+
}
|
|
17
|
+
function resolveGatewayCredentials(sources = {}) {
|
|
18
|
+
const env = sources.env ?? process.env;
|
|
19
|
+
const config = readCloudConfig(sources.home ?? homeFromEnv(env));
|
|
20
|
+
const url = sources.url ?? env.OUTERLAYER_URL ?? config.url;
|
|
21
|
+
const key = sources.apiKey ?? (env.OUTERLAYER_API_KEY || void 0) ?? config.apiKey;
|
|
22
|
+
const kind = key ? "key" : "login";
|
|
23
|
+
const credential = key ?? config.accountToken;
|
|
24
|
+
const cwd = sources.cwd ?? process.cwd();
|
|
25
|
+
const appId = sources.appId ?? (env.OUTERLAYER_APP_ID || void 0) ?? (kind === "key" ? config.appId : readRepoConnection(cwd)?.appId);
|
|
26
|
+
if (credential && url && !appId && kind === "login") {
|
|
27
|
+
const message = findGitDirs(cwd) === void 0 ? NO_FACTORY_OUTSIDE_REPOSITORY_MESSAGE : NO_FACTORY_MESSAGE;
|
|
28
|
+
return { ok: false, message, url, credential, appId, kind };
|
|
29
|
+
}
|
|
30
|
+
if (!url || !credential || !appId) {
|
|
31
|
+
const missing = [!url && "--url", !credential && "--api-key", !appId && "--app-id"].filter(Boolean).join(", ");
|
|
32
|
+
return { ok: false, message: `missing ${missing}. ${MISSING_HELP}`, url, credential, appId, kind };
|
|
33
|
+
}
|
|
34
|
+
return { ok: true, url, credential, appId, kind };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { resolveGatewayCredentials, unauthorizedMessage };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { EXEC_PORT } from './chunk-B2G7JHHB.js';
|
|
4
|
+
import { RELAY_PORT } from './chunk-AUFG23AA.js';
|
|
5
|
+
import { spawn, spawnSync } from 'child_process';
|
|
6
|
+
import { writeFileSync, chmodSync, mkdirSync } from 'fs';
|
|
7
|
+
|
|
8
|
+
createRequire(import.meta.url);
|
|
9
|
+
var HELPER_PATH = "/opt/outerlayer/bin/outerlayer-guest";
|
|
10
|
+
var KERNEL_FLAG = "outerlayer.relay";
|
|
11
|
+
var DOCKER_FLAG = "outerlayer.docker";
|
|
12
|
+
var DOCKERD_CONFIG = "/run/outerlayer-dockerd.json";
|
|
13
|
+
var DOCKERD_LOG = "/run/dockerd.log";
|
|
14
|
+
var DOCKER_DATA_ROOT = "/build/docker";
|
|
15
|
+
var BUILD_DISK = "/dev/vdb";
|
|
16
|
+
var HOSTNAME = "outerlayer-build";
|
|
17
|
+
var HOSTS = "127.0.0.1 localhost\n::1 localhost ip6-localhost ip6-loopback\n";
|
|
18
|
+
var RESOLV_CONF = "# This microVM has no network device. Names are resolved by the runner's proxy.\n";
|
|
19
|
+
var BUILD_DIRECTORIES = ["/build/tmp", "/build/home", "/build/work"];
|
|
20
|
+
var WORLD_WRITABLE = 1023;
|
|
21
|
+
function relayPortsFrom(cmdline) {
|
|
22
|
+
const value = new RegExp(`(?:^|\\s)${KERNEL_FLAG.replace(".", "\\.")}=(\\S*)`).exec(cmdline)?.[1];
|
|
23
|
+
if (value === void 0 || value === "") return [];
|
|
24
|
+
return value.split(",").map((text) => {
|
|
25
|
+
const port = Number(text);
|
|
26
|
+
if (!/^\d+$/.test(text) || port < 1 || port > 65535) throw new Error(`${KERNEL_FLAG} on the kernel command line names ${JSON.stringify(text)}, which is not a port`);
|
|
27
|
+
return port;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function dockerRequestedBy(cmdline) {
|
|
31
|
+
return new RegExp(`(?:^|\\s)${DOCKER_FLAG.replace(".", "\\.")}=1(?:\\s|$)`).test(cmdline);
|
|
32
|
+
}
|
|
33
|
+
function startDocker(host) {
|
|
34
|
+
const proxy = `http://127.0.0.1:${RELAY_PORT}`;
|
|
35
|
+
host.mount({ source: "cgroup2", target: "/sys/fs/cgroup", type: "cgroup2" });
|
|
36
|
+
host.mkdir(DOCKER_DATA_ROOT, 457);
|
|
37
|
+
host.writeFile(
|
|
38
|
+
DOCKERD_CONFIG,
|
|
39
|
+
`${JSON.stringify({ "data-root": DOCKER_DATA_ROOT, proxies: { "http-proxy": proxy, "https-proxy": proxy, "no-proxy": "localhost,127.0.0.1" } }, null, 2)}
|
|
40
|
+
`
|
|
41
|
+
);
|
|
42
|
+
host.spawnDetached(["/bin/sh", "-c", `exec dockerd --config-file ${DOCKERD_CONFIG} >${DOCKERD_LOG} 2>&1`]);
|
|
43
|
+
}
|
|
44
|
+
function runGuestInit(host, cmdline) {
|
|
45
|
+
const ports = relayPortsFrom(cmdline);
|
|
46
|
+
host.mount({ source: BUILD_DISK, target: "/build", type: "ext4" });
|
|
47
|
+
host.chmod("/build", WORLD_WRITABLE);
|
|
48
|
+
for (const path of BUILD_DIRECTORIES) {
|
|
49
|
+
host.mkdir(path, WORLD_WRITABLE);
|
|
50
|
+
host.chmod(path, WORLD_WRITABLE);
|
|
51
|
+
}
|
|
52
|
+
host.mount({ source: "/build/tmp", target: "/tmp", bind: true });
|
|
53
|
+
const files = [
|
|
54
|
+
["hosts", HOSTS],
|
|
55
|
+
["hostname", `${HOSTNAME}
|
|
56
|
+
`],
|
|
57
|
+
["resolv.conf", RESOLV_CONF]
|
|
58
|
+
];
|
|
59
|
+
for (const [name, text] of files) {
|
|
60
|
+
host.writeFile(`/run/${name}`, text);
|
|
61
|
+
host.mount({ source: `/run/${name}`, target: `/etc/${name}`, bind: true });
|
|
62
|
+
}
|
|
63
|
+
host.writeFile("/proc/sys/kernel/hostname", HOSTNAME);
|
|
64
|
+
host.mount({ source: "tmpfs", target: "/outerlayer/job", type: "tmpfs", options: "mode=0755" });
|
|
65
|
+
host.spawnDetached([HELPER_PATH, "relay", ...ports.flatMap((port) => ["--forward", String(port)])]);
|
|
66
|
+
if (dockerRequestedBy(cmdline)) startDocker(host);
|
|
67
|
+
host.spawnDetached([HELPER_PATH, "exec-agent", "--port", String(EXEC_PORT)]);
|
|
68
|
+
}
|
|
69
|
+
function realGuestHost() {
|
|
70
|
+
return {
|
|
71
|
+
mount(args) {
|
|
72
|
+
const argv = ["mount", "--target", args.target];
|
|
73
|
+
if (args.source !== void 0) argv.push("--source", args.source);
|
|
74
|
+
if (args.type !== void 0) argv.push("--type", args.type);
|
|
75
|
+
if (args.options !== void 0) argv.push("--options", args.options);
|
|
76
|
+
if (args.bind === true) argv.push("--bind");
|
|
77
|
+
const result = spawnSync(HELPER_PATH, argv, { encoding: "utf8" });
|
|
78
|
+
if (result.status !== 0) throw new Error(`mounting ${args.target} failed: ${result.stderr.trim()}`);
|
|
79
|
+
},
|
|
80
|
+
mkdir: (path, mode) => void mkdirSync(path, { recursive: true, mode }),
|
|
81
|
+
chmod: (path, mode) => chmodSync(path, mode),
|
|
82
|
+
writeFile: (path, text) => writeFileSync(path, text),
|
|
83
|
+
spawnDetached(argv) {
|
|
84
|
+
spawn(argv[0], argv.slice(1), { detached: true, stdio: ["ignore", "inherit", "inherit"] }).unref();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { DOCKERD_LOG, HELPER_PATH, realGuestHost, relayPortsFrom, runGuestInit };
|