@outerlayer/cli 0.1.1 → 0.4.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/CHANGELOG.md +134 -0
- package/README.md +238 -77
- 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-WQP2CZDW.js +94 -0
- package/dist/chunk-2E54Z3P5.js +232 -0
- package/dist/chunk-3TT7RQYB.js +55 -0
- package/dist/chunk-3YPKXE5L.js +1683 -0
- package/dist/{chunk-HNCCD2IX.js → chunk-5DLBAXY5.js} +209 -61
- package/dist/{chunk-R5KBGVII.js → chunk-5QRP3MVS.js} +30 -14
- package/dist/chunk-5Y3QQRUH.js +300 -0
- package/dist/chunk-6Y63SXFO.js +383 -0
- package/dist/chunk-774HEQL6.js +178 -0
- package/dist/chunk-7A6RVXPZ.js +2233 -0
- package/dist/chunk-7YIOOFVJ.js +71 -0
- 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-CBZB6XY6.js +102 -0
- package/dist/{chunk-RCQXYLMO.js → chunk-DVDEBNQJ.js} +18 -18
- package/dist/{work-pr-cmd-GOYSPEN2.js → chunk-DVYHCMB2.js} +10 -25
- 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/chunk-FAJSS2WN.js +1357 -0
- package/dist/chunk-FS2VYGZT.js +1764 -0
- package/dist/chunk-FV2CCGXK.js +9 -0
- package/dist/{chunk-BQB3U2VD.js → chunk-G7FSV7JX.js} +5113 -3729
- package/dist/{emit-cmd-TWSYYEZI.js → chunk-H5NCNVDA.js} +68 -17
- package/dist/chunk-HE2D4EY5.js +83 -0
- package/dist/chunk-L2DBRKRA.js +94 -0
- package/dist/chunk-LF6MLJCY.js +38 -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-NYC54VBD.js +1084 -0
- package/dist/chunk-OGFGZQA3.js +23 -0
- package/dist/chunk-QOZKPLVJ.js +226 -0
- package/dist/chunk-RA3O54FC.js +30 -0
- package/dist/chunk-TBT347UY.js +41 -0
- package/dist/{chunk-3VQ4XXQA.js → chunk-UFFXNXLQ.js} +115 -71
- package/dist/chunk-USO2DKBD.js +421 -0
- package/dist/chunk-W4SQCOZU.js +206 -0
- package/dist/{chunk-A3WLZX2F.js → chunk-WIOZAJ2W.js} +15 -2
- package/dist/chunk-WO2BXCTQ.js +83 -0
- package/dist/chunk-WZBHBH3J.js +20 -0
- package/dist/{chunk-IWIUYLDR.js → chunk-XCCLFVXM.js} +118 -189
- package/dist/{context-materialize-J6CRQ7O7.js → chunk-XDDW4FRS.js} +134 -26
- 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-EFJIG2VT.js → cli-NHUXABYH.js} +597 -965
- package/dist/{paths-D2VGWWFI.js → cli-build-K56DK4DS.js} +1 -1
- package/dist/config-XVJYZ4IQ.js +9 -0
- package/dist/connect-cmd-OOG4TPU7.js +16 -0
- package/dist/context-adopt-UMA4O5NY.js +105 -0
- package/dist/context-materialize-G2FUFC72.js +19 -0
- package/dist/dist-RW4UPPC2.js +6 -0
- package/dist/docker-NEGDLU6D.js +7 -0
- package/dist/doctor-53F2PYY7.js +43 -0
- package/dist/doctor-UFVL4PZY.js +426 -0
- package/dist/{emit-artifact-cmd-KPPD3YLO.js → emit-artifact-cmd-KTYW2TN2.js} +25 -19
- package/dist/emit-cmd-DCERAU27.js +9 -0
- package/dist/emit-criteria-cmd-VAUUSAYD.js +166 -0
- package/dist/{emit-finding-cmd-VR5VEGXJ.js → emit-finding-cmd-UR4ID45Z.js} +35 -30
- package/dist/{emit-result-cmd-YOA5BJKL.js → emit-result-cmd-FDTUKGKE.js} +35 -61
- 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-DXBCOAJK.js +36 -0
- package/dist/init-YEXK35CF.js +221 -0
- package/dist/init-cmd-NHR7PRSQ.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-VZOLL4WQ.js +43 -0
- package/dist/machine-WSG52J75.js +35 -0
- package/dist/mcp-install-cmd-RLK4SO3N.js +9 -0
- package/dist/{mcp-serve-cmd-57EZZOTL.js → mcp-serve-cmd-JPTP5FMS.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-JHVSZYC5.js → status-2UZKKNB7.js} +31 -12
- package/dist/{statusline-fast-3C5OXHDD.js → statusline-fast-SVTMBMD7.js} +3 -2
- package/dist/sync-cmd-VPPYCNNM.js +28 -0
- package/dist/version-BWM6VLDI.js +6 -0
- package/dist/{watch-NRLXJUST.js → watch-5C4ZBBGO.js} +20 -7
- package/dist/{work-claim-cmd-HXB7K27H.js → work-claim-cmd-LSO2B2EX.js} +34 -19
- package/dist/work-cmd-UOQO2AD4.js +17 -0
- package/dist/work-comment-cmd-WF3EOLAE.js +106 -0
- package/dist/{work-launch-YNNCKIH3.js → work-launch-YI4CJDAP.js} +2 -1
- package/dist/work-open-pr-cmd-4SSQZMYG.js +156 -0
- package/dist/work-pr-cmd-6AN6RRQJ.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 +99 -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-JJP7YLMN.js +0 -25
- package/dist/chunk-KKEU6FFR.js +0 -589
- package/dist/chunk-OZ7C3XUE.js +0 -34
- package/dist/chunk-WQ6VGRGZ.js +0 -150
- package/dist/emit-commit-credit-cmd-656BRAQ4.js +0 -121
- package/dist/hook-fast-I2XTHDE6.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-BWE2BS6J.js +0 -646
- package/dist/sync-cmd-S7Q6DWU3.js +0 -15
- package/dist/work-cmd-ABP6XDBP.js +0 -16
package/dist/chunk-WQ6VGRGZ.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
4
|
-
import { existsSync, statSync, accessSync, constants, readFileSync } from 'fs';
|
|
5
|
-
import { hostname } from 'os';
|
|
6
|
-
import { join } from 'path';
|
|
7
|
-
|
|
8
|
-
createRequire(import.meta.url);
|
|
9
|
-
var RUNNER_QUEUES = ["implement", "amend"];
|
|
10
|
-
var RunnerConfigError = class extends Error {
|
|
11
|
-
};
|
|
12
|
-
var DEFAULT_CONCURRENCY = 1;
|
|
13
|
-
var DEFAULT_POLL_SECONDS = 60;
|
|
14
|
-
var DEFAULT_TIME_LIMIT_MINUTES = 480;
|
|
15
|
-
var DEFAULT_IDLE_LIMIT_MINUTES = 30;
|
|
16
|
-
function defaultRunnerConfigPath(home = homeFromEnv()) {
|
|
17
|
-
return join(home, ".outerlayer", "config.json");
|
|
18
|
-
}
|
|
19
|
-
function fail(configPath, key, reason) {
|
|
20
|
-
throw new RunnerConfigError(`${configPath}: ${key} ${reason}`);
|
|
21
|
-
}
|
|
22
|
-
function readConfigFile(configPath) {
|
|
23
|
-
if (!existsSync(configPath)) return {};
|
|
24
|
-
const text = readFileSync(configPath, "utf8");
|
|
25
|
-
try {
|
|
26
|
-
return JSON.parse(text);
|
|
27
|
-
} catch (err) {
|
|
28
|
-
throw new RunnerConfigError(
|
|
29
|
-
`${configPath}: is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
var RUNNER_KEYS = [
|
|
34
|
-
"host",
|
|
35
|
-
"queues",
|
|
36
|
-
"concurrency",
|
|
37
|
-
"pollSeconds",
|
|
38
|
-
"timeLimitMinutes",
|
|
39
|
-
"idleLimitMinutes",
|
|
40
|
-
"commands",
|
|
41
|
-
"hooks"
|
|
42
|
-
];
|
|
43
|
-
var RUNNER_NUMBERS = [
|
|
44
|
-
{ key: "concurrency", min: 1 },
|
|
45
|
-
// Below five seconds a runner is asking the gateway a question it has
|
|
46
|
-
// just been answered.
|
|
47
|
-
{ key: "pollSeconds", min: 5 },
|
|
48
|
-
{ key: "timeLimitMinutes", min: 1 },
|
|
49
|
-
{ key: "idleLimitMinutes", min: 1 }
|
|
50
|
-
];
|
|
51
|
-
function nearestKey(unknown, accepted) {
|
|
52
|
-
const lower = unknown.toLowerCase();
|
|
53
|
-
let best;
|
|
54
|
-
for (const key of accepted) {
|
|
55
|
-
const distance = editDistance(lower, key.toLowerCase());
|
|
56
|
-
if (distance <= Math.max(1, Math.floor(key.length / 4)) && (!best || distance < best.distance)) {
|
|
57
|
-
best = { key, distance };
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return best?.key;
|
|
61
|
-
}
|
|
62
|
-
function editDistance(a, b) {
|
|
63
|
-
const rows = Array.from({ length: a.length + 1 }, (_unused, i) => [i, ...Array(b.length).fill(0)]);
|
|
64
|
-
for (let j = 0; j <= b.length; j++) rows[0][j] = j;
|
|
65
|
-
for (let i = 1; i <= a.length; i++) {
|
|
66
|
-
for (let j = 1; j <= b.length; j++) {
|
|
67
|
-
rows[i][j] = Math.min(
|
|
68
|
-
rows[i - 1][j] + 1,
|
|
69
|
-
rows[i][j - 1] + 1,
|
|
70
|
-
rows[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return rows[a.length][b.length];
|
|
75
|
-
}
|
|
76
|
-
function checkHooks(config) {
|
|
77
|
-
const named = [
|
|
78
|
-
["runner.hooks.provision", config.hooks.provision],
|
|
79
|
-
["runner.hooks.cleanup", config.hooks.cleanup],
|
|
80
|
-
["runner.hooks.report", config.hooks.report]
|
|
81
|
-
];
|
|
82
|
-
for (const [key, path] of named) {
|
|
83
|
-
if (path === void 0) continue;
|
|
84
|
-
if (path.includes("$") || path.startsWith("~")) {
|
|
85
|
-
return `${key}: ${path} \u2014 hooks are executable paths, not shell lines; $HOME and ~ are not expanded`;
|
|
86
|
-
}
|
|
87
|
-
if (!existsSync(path)) return `${key}: ${path} does not exist`;
|
|
88
|
-
if (!statSync(path).isFile()) return `${key}: ${path} is not a file`;
|
|
89
|
-
try {
|
|
90
|
-
accessSync(path, constants.X_OK);
|
|
91
|
-
} catch {
|
|
92
|
-
return `${key}: ${path} is not executable`;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return void 0;
|
|
96
|
-
}
|
|
97
|
-
function loadRunnerConfig(configPath) {
|
|
98
|
-
const config = readConfigFile(configPath);
|
|
99
|
-
if (!config.url || !config.apiKey || !config.appId) {
|
|
100
|
-
fail(configPath, "url/apiKey/appId", "missing \u2014 run `outerlayer login` against this config file first");
|
|
101
|
-
}
|
|
102
|
-
const runner = config.runner;
|
|
103
|
-
if (!runner) {
|
|
104
|
-
fail(configPath, "runner", "block is missing");
|
|
105
|
-
}
|
|
106
|
-
for (const key of Object.keys(runner)) {
|
|
107
|
-
if (RUNNER_KEYS.includes(key)) continue;
|
|
108
|
-
const near = nearestKey(key, RUNNER_KEYS);
|
|
109
|
-
fail(configPath, `runner.${key}`, near ? `is not a key this block accepts; did you mean ${near}?` : "is not a key this block accepts");
|
|
110
|
-
}
|
|
111
|
-
for (const { key, min } of RUNNER_NUMBERS) {
|
|
112
|
-
const value = runner[key];
|
|
113
|
-
if (value === void 0) continue;
|
|
114
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value < min) {
|
|
115
|
-
fail(configPath, `runner.${key}`, `must be a whole number of at least ${min}`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
const queues = runner.queues && runner.queues.length > 0 ? runner.queues : [...RUNNER_QUEUES];
|
|
119
|
-
for (const queue of queues) {
|
|
120
|
-
if (!RUNNER_QUEUES.includes(queue)) {
|
|
121
|
-
fail(configPath, "runner.queues", `names an unknown queue "${queue}" \u2014 must be implement or amend`);
|
|
122
|
-
}
|
|
123
|
-
if (!runner.commands?.[queue]) {
|
|
124
|
-
fail(configPath, `runner.commands.${queue}`, "is required for an enabled queue");
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
if (!runner.hooks?.provision) fail(configPath, "runner.hooks.provision", "is required");
|
|
128
|
-
if (!runner.hooks?.cleanup) fail(configPath, "runner.hooks.cleanup", "is required");
|
|
129
|
-
return {
|
|
130
|
-
configPath,
|
|
131
|
-
url: config.url,
|
|
132
|
-
apiKey: config.apiKey,
|
|
133
|
-
appId: config.appId,
|
|
134
|
-
repos: config.repos,
|
|
135
|
-
host: runner.host ?? hostname(),
|
|
136
|
-
queues,
|
|
137
|
-
concurrency: runner.concurrency ?? DEFAULT_CONCURRENCY,
|
|
138
|
-
pollSeconds: runner.pollSeconds ?? DEFAULT_POLL_SECONDS,
|
|
139
|
-
timeLimitMinutes: runner.timeLimitMinutes ?? DEFAULT_TIME_LIMIT_MINUTES,
|
|
140
|
-
idleLimitMinutes: runner.idleLimitMinutes ?? DEFAULT_IDLE_LIMIT_MINUTES,
|
|
141
|
-
commands: runner.commands ?? {},
|
|
142
|
-
hooks: {
|
|
143
|
-
provision: runner.hooks.provision,
|
|
144
|
-
cleanup: runner.hooks.cleanup,
|
|
145
|
-
report: runner.hooks.report
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export { RunnerConfigError, checkHooks, defaultRunnerConfigPath, loadRunnerConfig };
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
import { resolveRepoIdentity, emitLaunchRefusal } from './chunk-BQB3U2VD.js';
|
|
4
|
-
import './chunk-YRYMNBGQ.js';
|
|
5
|
-
import { parseTrackerNumber, trackerNumberError } from './chunk-RCQXYLMO.js';
|
|
6
|
-
import './chunk-ZJSNLVCE.js';
|
|
7
|
-
import './chunk-3VQ4XXQA.js';
|
|
8
|
-
import './chunk-IWIUYLDR.js';
|
|
9
|
-
import './chunk-YOOSBOKS.js';
|
|
10
|
-
import './chunk-R5KBGVII.js';
|
|
11
|
-
import { readCloudConfig } from './chunk-TFUIDMOB.js';
|
|
12
|
-
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
13
|
-
import './chunk-Z5GEFMRW.js';
|
|
14
|
-
|
|
15
|
-
createRequire(import.meta.url);
|
|
16
|
-
|
|
17
|
-
// src/emit-commit-credit-cmd.ts
|
|
18
|
-
var GREEN = "\x1B[32m";
|
|
19
|
-
var DIM = "\x1B[2m";
|
|
20
|
-
var RESET = "\x1B[0m";
|
|
21
|
-
var FULL_SHA = /^[0-9a-f]{40}$/i;
|
|
22
|
-
var EmitCommitCreditError = class extends Error {
|
|
23
|
-
};
|
|
24
|
-
async function runEmitCommitCredit(opts) {
|
|
25
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
26
|
-
const home = opts.home ?? homeFromEnv();
|
|
27
|
-
const env = opts.env ?? process.env;
|
|
28
|
-
const shaInput = opts.sha ?? "";
|
|
29
|
-
if (!FULL_SHA.test(shaInput)) {
|
|
30
|
-
throw new EmitCommitCreditError(
|
|
31
|
-
`invalid --sha "${opts.sha ?? ""}" \u2014 expected a full 40-character hex SHA (git's abbreviated form isn't enough)`
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
const sha = shaInput.toLowerCase();
|
|
35
|
-
if (opts.pr === void 0) {
|
|
36
|
-
throw new EmitCommitCreditError("--pr is required \u2014 an attestation carries its PR anchor at emit time");
|
|
37
|
-
}
|
|
38
|
-
const prNumber = parseTrackerNumber(opts.pr);
|
|
39
|
-
if (prNumber === void 0) {
|
|
40
|
-
throw new EmitCommitCreditError(trackerNumberError("--pr", opts.pr));
|
|
41
|
-
}
|
|
42
|
-
const identity = resolveRepoIdentity(cwd);
|
|
43
|
-
const repository = env.GITHUB_REPOSITORY;
|
|
44
|
-
const gitRepo = repository === void 0 ? identity.gitRepo : void 0;
|
|
45
|
-
if (repository === void 0 && gitRepo === void 0) {
|
|
46
|
-
throw new EmitCommitCreditError(
|
|
47
|
-
"nothing to attach this to \u2014 no repository. Run in CI (GITHUB_REPOSITORY) or from a git checkout with a remote."
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
const refusal = emitLaunchRefusal({
|
|
51
|
-
env,
|
|
52
|
-
home,
|
|
53
|
-
cwds: identity.repoRoot !== void 0 ? [identity.repoRoot, cwd] : [cwd],
|
|
54
|
-
nowMs: Date.now()
|
|
55
|
-
});
|
|
56
|
-
if (refusal !== void 0) throw new EmitCommitCreditError(refusal);
|
|
57
|
-
const fileConfig = readCloudConfig(home);
|
|
58
|
-
const url = opts.url ?? env.OUTERLAYER_URL ?? fileConfig.url;
|
|
59
|
-
const apiKey = opts.apiKey ?? env.OUTERLAYER_API_KEY ?? fileConfig.apiKey;
|
|
60
|
-
const appId = opts.appId ?? env.OUTERLAYER_APP_ID ?? fileConfig.appId;
|
|
61
|
-
if (!url || !apiKey || !appId) {
|
|
62
|
-
const missing = [!url && "--url", !apiKey && "--api-key", !appId && "--app-id"].filter(Boolean).join(", ");
|
|
63
|
-
throw new EmitCommitCreditError(
|
|
64
|
-
`missing ${missing}. Run \`outerlayer login --url <your gateway>\` and paste the key at the prompt, or set OUTERLAYER_URL / OUTERLAYER_API_KEY / OUTERLAYER_APP_ID. Mint a key in the dashboard: Settings \u2192 API keys.`
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
const request = {
|
|
68
|
-
schemaVersion: 1,
|
|
69
|
-
commitCredit: {
|
|
70
|
-
sha,
|
|
71
|
-
prNumber,
|
|
72
|
-
...repository !== void 0 ? { repository } : {},
|
|
73
|
-
...gitRepo !== void 0 ? { gitRepo } : {}
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
77
|
-
let endpoint;
|
|
78
|
-
try {
|
|
79
|
-
endpoint = new URL("/v1/commit-credit", url).toString();
|
|
80
|
-
} catch {
|
|
81
|
-
throw new EmitCommitCreditError(`invalid --url "${url}" \u2014 not a URL`);
|
|
82
|
-
}
|
|
83
|
-
let response;
|
|
84
|
-
try {
|
|
85
|
-
response = await fetchImpl(endpoint, {
|
|
86
|
-
method: "POST",
|
|
87
|
-
headers: {
|
|
88
|
-
"content-type": "application/json",
|
|
89
|
-
authorization: `Bearer ${apiKey}`,
|
|
90
|
-
"x-outerlayer-app-id": appId
|
|
91
|
-
},
|
|
92
|
-
body: JSON.stringify(request)
|
|
93
|
-
});
|
|
94
|
-
} catch (err) {
|
|
95
|
-
throw new EmitCommitCreditError(`network error reaching ${endpoint}: ${String(err)}`);
|
|
96
|
-
}
|
|
97
|
-
if (response.status === 401) {
|
|
98
|
-
throw new EmitCommitCreditError(
|
|
99
|
-
"not authorized \u2014 the API key is unknown, expired, or bound to a different app. Mint a fresh key: dashboard \u2192 Settings \u2192 API keys."
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
if (!response.ok) {
|
|
103
|
-
let detail = "";
|
|
104
|
-
try {
|
|
105
|
-
detail = (await response.text()).replace(/\s+/g, " ").trim().slice(0, 160);
|
|
106
|
-
} catch {
|
|
107
|
-
}
|
|
108
|
-
throw new EmitCommitCreditError(`commit-credit emit failed (${response.status})${detail ? `: ${detail}` : ""}`);
|
|
109
|
-
}
|
|
110
|
-
let data = {};
|
|
111
|
-
try {
|
|
112
|
-
data = (await response.json()).data ?? {};
|
|
113
|
-
} catch {
|
|
114
|
-
}
|
|
115
|
-
const output = opts.json ? JSON.stringify(data) : `${GREEN}\u2713${RESET} attested ${sha.slice(0, 7)} ${DIM}\xB7${RESET} PR #${prNumber}
|
|
116
|
-
${DIM}(if this PR was decided more than a few hours ago, its comment is frozen and won't re-render)${RESET}`;
|
|
117
|
-
if (!opts.quiet) process.stdout.write(output + "\n");
|
|
118
|
-
return { data, output, exitCode: 0 };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export { EmitCommitCreditError, runEmitCommitCredit };
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
export { AUTO_SYNC_DEBOUNCE_MS, WORK_ADD_RETRIES, contextPossiblyGoverned, handleWorkLaunch, hookFiredMarkerPath, maybeAutoSync, runHookFast, spoolDir } from './chunk-3VQ4XXQA.js';
|
|
4
|
-
import './chunk-IWIUYLDR.js';
|
|
5
|
-
import './chunk-VNVZDWO3.js';
|
|
6
|
-
import './chunk-Z5GEFMRW.js';
|
|
7
|
-
|
|
8
|
-
createRequire(import.meta.url);
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
import './chunk-Z5GEFMRW.js';
|
|
4
|
-
import { statSync, existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
5
|
-
import { join, dirname } from 'path';
|
|
6
|
-
|
|
7
|
-
createRequire(import.meta.url);
|
|
8
|
-
var GREEN = "\x1B[32m";
|
|
9
|
-
var DIM = "\x1B[2m";
|
|
10
|
-
var YELLOW = "\x1B[33m";
|
|
11
|
-
var RESET = "\x1B[0m";
|
|
12
|
-
var ImportCaptureError = class extends Error {
|
|
13
|
-
};
|
|
14
|
-
var SKILL_PATH = ".outerlayer/skills/emitting-evidence/SKILL.md";
|
|
15
|
-
var SNIPPET_PATH = ".outerlayer/skills/emitting-evidence/references/agents-snippet.md";
|
|
16
|
-
var AGENTS_MD_PATH = ".outerlayer/AGENTS.md";
|
|
17
|
-
var AGENTS_MD_MARKER = "<!-- outerlayer:capture-pack -->";
|
|
18
|
-
var SKILL_CONTENT = `---
|
|
19
|
-
name: emitting-evidence
|
|
20
|
-
description: >
|
|
21
|
-
Emit artifacts \u2014 screenshots, recordings, reports, logs \u2014 as proof that a
|
|
22
|
-
change works, bound to the pull request via \`outerlayer emit artifact\`.
|
|
23
|
-
Use when finishing work a spec criterion or tracked requirement covers,
|
|
24
|
-
when asked to "provide evidence", "attach a screenshot", or "prove it
|
|
25
|
-
works", and in CI steps that produce verifiable output.
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
An artifact is an exhibit: evidence *of* a specific change, anchored to the
|
|
29
|
-
pull request and rendered in its evidence comment. Emit the few exhibits the
|
|
30
|
-
spec asks for \u2014 a reviewer should see the change working, not a gallery.
|
|
31
|
-
|
|
32
|
-
## When to capture
|
|
33
|
-
|
|
34
|
-
Capture AFTER the state exists, never before: run the app, the test, or the
|
|
35
|
-
flow first, and shoot the working result. A screenshot of code is not
|
|
36
|
-
evidence; a screenshot of the rendered page is. If the state takes setup
|
|
37
|
-
(seed data, a logged-in user), finish the setup, verify by eye, then capture.
|
|
38
|
-
|
|
39
|
-
## Per-kind mechanics
|
|
40
|
-
|
|
41
|
-
Kind is inferred from the file's media type \u2014 name files honestly. Every
|
|
42
|
-
artifact shares one upload cap of 8 MiB, whatever its kind:
|
|
43
|
-
|
|
44
|
-
- **screenshot** (\`.png\`, \`.jpg\`) \u2014 one focused window or region showing the
|
|
45
|
-
proven state. Crop noise; keep enough chrome (URL bar, test summary line)
|
|
46
|
-
to show it is real.
|
|
47
|
-
- **video** (\`.webm\`, \`.mp4\`) \u2014 a short recording of the flow, start to
|
|
48
|
-
outcome. Video is the kind most likely to hit the 8 MiB cap: trim dead
|
|
49
|
-
time, prefer webm.
|
|
50
|
-
- **report** (\`.html\`, \`.pdf\`) \u2014 generated reports: coverage, benchmark,
|
|
51
|
-
audit output. Emit the file the tool produced, unedited.
|
|
52
|
-
- **log** (\`.txt\`, \`.log\`) \u2014 command output proving a run happened: test
|
|
53
|
-
runs, migrations, gate output. Pipe to a file and emit that file.
|
|
54
|
-
|
|
55
|
-
Anything else uploads as plain \`file\` \u2014 it is never guessed into a stronger
|
|
56
|
-
kind, so a \`.mov\` will NOT count where a video is required; convert first.
|
|
57
|
-
|
|
58
|
-
## Captions
|
|
59
|
-
|
|
60
|
-
One sentence, present tense, saying what the exhibit shows and what that
|
|
61
|
-
proves: "Signup blocked for a disallowed domain \u2014 the 403 page renders."
|
|
62
|
-
Never put secrets, tokens, or personal data in the caption \u2014 or the pixels.
|
|
63
|
-
|
|
64
|
-
## Binding with --for
|
|
65
|
-
|
|
66
|
-
When a criterion is the reason you captured, bind it:
|
|
67
|
-
|
|
68
|
-
outerlayer emit artifact shot.png --caption "\u2026" --for LOGIN-01
|
|
69
|
-
|
|
70
|
-
The id comes from your team's own spec or tracker \u2014 wherever criteria for
|
|
71
|
-
this change are written down. The PR comment's Evidence section shows the
|
|
72
|
-
id next to the artifact, and the dashboard's Floor lists it against the
|
|
73
|
-
work item \u2014 but deciding whether a given criterion still needs proof, and
|
|
74
|
-
of what kind, is your team's own check to write, typically a custom
|
|
75
|
-
validator recorded with \`outerlayer emit\`.
|
|
76
|
-
|
|
77
|
-
## The noise rule
|
|
78
|
-
|
|
79
|
-
Satisfy the declared proofs; don't document everything. One exhibit per
|
|
80
|
-
criterion is the norm. An unrequested artifact is worth emitting only when
|
|
81
|
-
it would change how a reviewer reads the diff. When in doubt, leave it out \u2014
|
|
82
|
-
evidence works because there is little of it.
|
|
83
|
-
|
|
84
|
-
## Mechanics
|
|
85
|
-
|
|
86
|
-
Inside a recorded session, just run the command \u2014 the artifact spools
|
|
87
|
-
locally and uploads with the next \`outerlayer sync\`, bound to this session
|
|
88
|
-
and turn. In CI, run it after the step that produced the file (repo and PR
|
|
89
|
-
come from the CI environment). From a plain machine it anchors through the
|
|
90
|
-
git checkout, or pass \`--pr <n>\` explicitly. If there is nothing to attach
|
|
91
|
-
to, the command refuses \u2014 emit from the work, not from nowhere.
|
|
92
|
-
`;
|
|
93
|
-
var AGENTS_SNIPPET_CONTENT = `${AGENTS_MD_MARKER}
|
|
94
|
-
## Emitting evidence
|
|
95
|
-
|
|
96
|
-
When you finish work a spec criterion or tracked requirement covers,
|
|
97
|
-
capture the working state and emit it:
|
|
98
|
-
|
|
99
|
-
outerlayer emit artifact <file> --caption "what it shows" [--for <criterion-id>] [--pr <n>]
|
|
100
|
-
|
|
101
|
-
Rules: capture AFTER the state exists (run it, then shoot it); kind comes
|
|
102
|
-
from the file type (png/jpg screenshot, webm/mp4 video, html/pdf report,
|
|
103
|
-
txt/log log \u2014 anything else is a plain file); every artifact caps at 8
|
|
104
|
-
MiB, and video is the kind most likely to hit it; captions are one
|
|
105
|
-
present-tense sentence with no secrets; bind an artifact to the id of the
|
|
106
|
-
requirement it proves \u2014 \`--for <id>\`, from the team's own spec or
|
|
107
|
-
tracker \u2014 and pick the kind that shows the state best; the platform
|
|
108
|
-
records the kind and the id and shows them next to the artifact; satisfy
|
|
109
|
-
what's asked for and stop \u2014 don't document everything. Inside a recorded
|
|
110
|
-
session the artifact uploads on the next \`outerlayer sync\`; in CI it
|
|
111
|
-
anchors via the CI environment; otherwise the git checkout or \`--pr\`
|
|
112
|
-
anchors it, and with nothing to attach to the command refuses.
|
|
113
|
-
`;
|
|
114
|
-
function writeOutFile(repoRoot, relPath, content) {
|
|
115
|
-
const abs = join(repoRoot, relPath);
|
|
116
|
-
mkdirSync(dirname(abs), { recursive: true });
|
|
117
|
-
writeFileSync(abs, content);
|
|
118
|
-
}
|
|
119
|
-
function runImportCapture(opts = {}) {
|
|
120
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
121
|
-
let cwdStat;
|
|
122
|
-
try {
|
|
123
|
-
cwdStat = statSync(cwd);
|
|
124
|
-
} catch {
|
|
125
|
-
throw new ImportCaptureError(`no such directory: ${cwd}`);
|
|
126
|
-
}
|
|
127
|
-
if (!cwdStat.isDirectory()) throw new ImportCaptureError(`not a directory: ${cwd}`);
|
|
128
|
-
const skillAbs = join(cwd, SKILL_PATH);
|
|
129
|
-
if (existsSync(skillAbs) && readFileSync(skillAbs, "utf8") !== SKILL_CONTENT) {
|
|
130
|
-
throw new ImportCaptureError(
|
|
131
|
-
`refusing to import \u2014 ${SKILL_PATH} already exists with local edits (never overwritten; remove it first to reinstall)`
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
const files = [];
|
|
135
|
-
for (const [relPath, content] of [
|
|
136
|
-
[SKILL_PATH, SKILL_CONTENT],
|
|
137
|
-
[SNIPPET_PATH, AGENTS_SNIPPET_CONTENT]
|
|
138
|
-
]) {
|
|
139
|
-
if (existsSync(join(cwd, relPath))) continue;
|
|
140
|
-
writeOutFile(cwd, relPath, content);
|
|
141
|
-
files.push(relPath);
|
|
142
|
-
}
|
|
143
|
-
let appendedAgentsMd = false;
|
|
144
|
-
const agentsMdAbs = join(cwd, AGENTS_MD_PATH);
|
|
145
|
-
if (existsSync(agentsMdAbs)) {
|
|
146
|
-
const existing = readFileSync(agentsMdAbs, "utf8");
|
|
147
|
-
if (!existing.includes(AGENTS_MD_MARKER)) {
|
|
148
|
-
writeFileSync(agentsMdAbs, existing + "\n" + AGENTS_SNIPPET_CONTENT);
|
|
149
|
-
appendedAgentsMd = true;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
let output;
|
|
153
|
-
if (opts.json) {
|
|
154
|
-
output = JSON.stringify({ files, appendedAgentsMd });
|
|
155
|
-
} else {
|
|
156
|
-
const lines = files.map((f) => `${GREEN}\u2713${RESET} wrote ${f}`);
|
|
157
|
-
if (appendedAgentsMd) lines.push(`${GREEN}\u2713${RESET} appended the evidence snippet to ${AGENTS_MD_PATH}`);
|
|
158
|
-
if (files.length === 0 && !appendedAgentsMd) {
|
|
159
|
-
lines.push(`${DIM}capture pack already installed \u2014 nothing to write${RESET}`);
|
|
160
|
-
}
|
|
161
|
-
lines.push(`Run ${YELLOW}\`outerlayer emit\`${RESET} to compile the pack into your agent's native files.`);
|
|
162
|
-
output = lines.join("\n");
|
|
163
|
-
}
|
|
164
|
-
if (!opts.quiet) {
|
|
165
|
-
process.stdout.write(output + "\n");
|
|
166
|
-
if (!opts.json && !appendedAgentsMd && !existsSync(agentsMdAbs)) {
|
|
167
|
-
process.stdout.write(
|
|
168
|
-
`${DIM} no ${AGENTS_MD_PATH} to append to \u2014 paste ${SNIPPET_PATH} into your instructions file if your harness reads no skills${RESET}
|
|
169
|
-
`
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return { files, appendedAgentsMd, output };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export { ImportCaptureError, runImportCapture };
|
package/dist/init-PTBITAUO.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
import { runnerDir, RUNNER_DIR_MODE, RUNNER_FILE_MODE } from './chunk-JJP7YLMN.js';
|
|
4
|
-
import './chunk-Z5GEFMRW.js';
|
|
5
|
-
import { existsSync, readFileSync, mkdirSync, writeFileSync, chmodSync } from 'fs';
|
|
6
|
-
import { join, dirname } from 'path';
|
|
7
|
-
|
|
8
|
-
createRequire(import.meta.url);
|
|
9
|
-
var RunnerInitError = class extends Error {
|
|
10
|
-
};
|
|
11
|
-
var PLACEHOLDER_COMMAND = "echo 'edit runner.commands in this config file: it must start your agent on item $OUTERLAYER_WORK' >&2; exit 1";
|
|
12
|
-
var PROVISION = `#!/bin/sh
|
|
13
|
-
# provision.sh \u2014 makes the place this job runs in.
|
|
14
|
-
# Runs in $OUTERLAYER_JOB_DIR, before the command. Exit 75 to decline the
|
|
15
|
-
# item for now; any other non-zero exit fails the job.
|
|
16
|
-
set -e
|
|
17
|
-
|
|
18
|
-
if ! df -Pk . | awk 'NR==2 {exit ($4 < 6*1024*1024)}'; then
|
|
19
|
-
exit 75 # not enough disk free right now
|
|
20
|
-
fi
|
|
21
|
-
|
|
22
|
-
repo=$(outerlayer work status --item "$OUTERLAYER_WORK" --json \\
|
|
23
|
-
| jq -r '.trackerKey | split("#")[0]')
|
|
24
|
-
workdir="$OUTERLAYER_JOB_DIR/work"
|
|
25
|
-
git clone --depth 1 "https://github.com/$repo.git" "$workdir"
|
|
26
|
-
|
|
27
|
-
echo "workdir=$workdir" > "$OUTERLAYER_JOB_DIR/provision.out"
|
|
28
|
-
`;
|
|
29
|
-
var CLEANUP = `#!/bin/sh
|
|
30
|
-
# cleanup.sh \u2014 runs after the command, however it ended, and on recovery.
|
|
31
|
-
# $OUTERLAYER_OUTCOME is the attempt's fate, not a verdict on the work: a
|
|
32
|
-
# command that ran and exited says "ok" whatever its exit status.
|
|
33
|
-
workdir="$OUTERLAYER_JOB_DIR/work"
|
|
34
|
-
|
|
35
|
-
if grep -q "pull request opened" "$OUTERLAYER_LOG" 2>/dev/null; then
|
|
36
|
-
rm -rf "$workdir"
|
|
37
|
-
else
|
|
38
|
-
echo "kept $workdir for inspection (outcome: $OUTERLAYER_OUTCOME)"
|
|
39
|
-
fi
|
|
40
|
-
`;
|
|
41
|
-
var REPORT = `#!/bin/sh
|
|
42
|
-
# report.sh \u2014 optional. Runs once at the start of a job and once at the end.
|
|
43
|
-
# At the start there is no working directory yet and $OUTERLAYER_LOG is
|
|
44
|
-
# unset; the command's log is always $OUTERLAYER_JOB_DIR/command.log.
|
|
45
|
-
case "$OUTERLAYER_PHASE" in
|
|
46
|
-
start) echo "starting #$OUTERLAYER_WORK" ;;
|
|
47
|
-
end) echo "#$OUTERLAYER_WORK ended: $OUTERLAYER_OUTCOME" ;;
|
|
48
|
-
esac
|
|
49
|
-
`;
|
|
50
|
-
function runRunnerInit(configPath) {
|
|
51
|
-
if (!existsSync(configPath)) {
|
|
52
|
-
throw new RunnerInitError(
|
|
53
|
-
`${configPath}: no config file. Run \`outerlayer login --url <your gateway>\` first, then this command.`
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
let config;
|
|
57
|
-
try {
|
|
58
|
-
config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
59
|
-
} catch (err) {
|
|
60
|
-
throw new RunnerInitError(`${configPath}: not valid JSON (${err instanceof Error ? err.message : String(err)})`);
|
|
61
|
-
}
|
|
62
|
-
if (config.runner !== void 0) {
|
|
63
|
-
throw new RunnerInitError(`${configPath}: a runner block is already here. Edit it, or remove it and run this again.`);
|
|
64
|
-
}
|
|
65
|
-
const hooksDir = join(runnerDir(configPath), "hooks");
|
|
66
|
-
mkdirSync(hooksDir, { recursive: true, mode: RUNNER_DIR_MODE });
|
|
67
|
-
const written = [];
|
|
68
|
-
for (const [name, body] of [
|
|
69
|
-
["provision.sh", PROVISION],
|
|
70
|
-
["cleanup.sh", CLEANUP],
|
|
71
|
-
["report.sh", REPORT]
|
|
72
|
-
]) {
|
|
73
|
-
const path = join(hooksDir, name);
|
|
74
|
-
if (existsSync(path)) continue;
|
|
75
|
-
writeFileSync(path, body, { mode: 448 });
|
|
76
|
-
chmodSync(path, 448);
|
|
77
|
-
written.push(path);
|
|
78
|
-
}
|
|
79
|
-
config.runner = {
|
|
80
|
-
queues: ["implement", "amend"],
|
|
81
|
-
concurrency: 1,
|
|
82
|
-
pollSeconds: 60,
|
|
83
|
-
timeLimitMinutes: 480,
|
|
84
|
-
idleLimitMinutes: 30,
|
|
85
|
-
// A placeholder that fails loudly. A command line that quietly did
|
|
86
|
-
// nothing would claim an item, run, exit zero, and record `ok` —
|
|
87
|
-
// every attempt reading as a success that produced nothing.
|
|
88
|
-
commands: {
|
|
89
|
-
implement: PLACEHOLDER_COMMAND,
|
|
90
|
-
amend: PLACEHOLDER_COMMAND
|
|
91
|
-
},
|
|
92
|
-
hooks: {
|
|
93
|
-
provision: join(hooksDir, "provision.sh"),
|
|
94
|
-
cleanup: join(hooksDir, "cleanup.sh")
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
mkdirSync(dirname(configPath), { recursive: true });
|
|
98
|
-
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
99
|
-
`, { mode: RUNNER_FILE_MODE });
|
|
100
|
-
return { configPath, hooksDir, hooks: written };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export { RunnerInitError, runRunnerInit };
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
import { writeCloudConfig, readCloudConfig, cloudConfigPath } from './chunk-TFUIDMOB.js';
|
|
4
|
-
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
5
|
-
import './chunk-Z5GEFMRW.js';
|
|
6
|
-
import { createInterface } from 'readline';
|
|
7
|
-
import { Writable } from 'stream';
|
|
8
|
-
|
|
9
|
-
createRequire(import.meta.url);
|
|
10
|
-
var LoginError = class extends Error {
|
|
11
|
-
};
|
|
12
|
-
function runLogin(opts) {
|
|
13
|
-
const apiKey = opts.apiKey.trim();
|
|
14
|
-
if (apiKey.length === 0) throw new LoginError("no API key given \u2014 pipe it on stdin or enter it at the prompt");
|
|
15
|
-
if (/\s/.test(apiKey)) throw new LoginError("the API key must be a single token with no spaces or line breaks");
|
|
16
|
-
const url = opts.url?.trim() || void 0;
|
|
17
|
-
const appId = opts.appId?.trim() || void 0;
|
|
18
|
-
const home = opts.home ?? homeFromEnv();
|
|
19
|
-
writeCloudConfig(
|
|
20
|
-
{
|
|
21
|
-
apiKey,
|
|
22
|
-
...url ? { url } : {},
|
|
23
|
-
...appId ? { appId } : {}
|
|
24
|
-
},
|
|
25
|
-
home
|
|
26
|
-
);
|
|
27
|
-
const saved = readCloudConfig(home);
|
|
28
|
-
return { path: cloudConfigPath(home), url: saved.url, appId: saved.appId };
|
|
29
|
-
}
|
|
30
|
-
async function readAllStdin(stdin) {
|
|
31
|
-
const chunks = [];
|
|
32
|
-
for await (const chunk of stdin) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
33
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
34
|
-
}
|
|
35
|
-
function promptHidden(stdin, stdout, question) {
|
|
36
|
-
return new Promise((resolve, reject) => {
|
|
37
|
-
const muted = new Writable({
|
|
38
|
-
write(_chunk, _encoding, callback) {
|
|
39
|
-
callback();
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
const rl = createInterface({ input: stdin, output: muted, terminal: true });
|
|
43
|
-
stdout.write(question);
|
|
44
|
-
rl.question("", (answer) => {
|
|
45
|
-
rl.close();
|
|
46
|
-
stdout.write("\n");
|
|
47
|
-
resolve(answer);
|
|
48
|
-
});
|
|
49
|
-
rl.on("error", reject);
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
async function readApiKey(input = {}) {
|
|
53
|
-
const stdin = input.stdin ?? process.stdin;
|
|
54
|
-
const stdout = input.stdout ?? process.stdout;
|
|
55
|
-
if (!stdin.isTTY) return (await readAllStdin(stdin)).trim();
|
|
56
|
-
if (input.noInput) {
|
|
57
|
-
throw new LoginError('no API key on stdin and --no-input forbids prompting \u2014 pipe the key: echo "$KEY" | outerlayer login');
|
|
58
|
-
}
|
|
59
|
-
return (await promptHidden(stdin, stdout, "API key (input hidden): ")).trim();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export { LoginError, readApiKey, runLogin };
|