jules-orchestrator-kit 0.5.1 → 0.5.2
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/.env.example
CHANGED
|
@@ -1,17 +1,63 @@
|
|
|
1
1
|
# Google Jules Orchestration Kit - Environment Configuration Example
|
|
2
2
|
# Copy this file to .env and populate with your credentials/settings.
|
|
3
3
|
|
|
4
|
+
# --- Authentication & Core Settings ---
|
|
4
5
|
# Google Jules API Key (Required for direct REST API dispatches)
|
|
5
6
|
JULES_API_KEY=your_google_jules_api_key_here
|
|
6
7
|
|
|
8
|
+
# Alias for API key (fallback if JULES_API_KEY is not set)
|
|
9
|
+
# GEMINI_API_KEY=your_gemini_api_key_here
|
|
10
|
+
|
|
11
|
+
# Override the Jules REST API URL
|
|
12
|
+
# JULES_API_URL=https://jules.googleapis.com/v1alpha/sessions
|
|
13
|
+
|
|
7
14
|
# Target GitHub Repository (Format: owner/repo)
|
|
8
|
-
JULES_REPO=
|
|
15
|
+
JULES_REPO=owner/repo
|
|
16
|
+
|
|
17
|
+
# Set to "true" or "1" to run in repoless/serverless mode
|
|
18
|
+
# JULES_REPOLESS=false
|
|
19
|
+
|
|
20
|
+
# Enable Dry-Run mode to simulate payload dispatch without making API calls
|
|
21
|
+
# JULES_DRY_RUN=false
|
|
9
22
|
|
|
23
|
+
# --- Execution Scope & Git ---
|
|
10
24
|
# Base Branch for PR Audits & Merge-Base Calculations (Default: main)
|
|
11
25
|
BASE_BRANCH=main
|
|
12
26
|
|
|
13
|
-
#
|
|
27
|
+
# PR Head Branch (Used by CI to dynamically target branches during OODA repair)
|
|
28
|
+
# GITHUB_HEAD_REF=my-feature-branch
|
|
29
|
+
|
|
30
|
+
# Root directory of the project (Auto-assigned during swarm executions)
|
|
31
|
+
# JULES_PROJECT_ROOT=/path/to/project
|
|
32
|
+
|
|
33
|
+
# --- Swarm & Concurrency ---
|
|
34
|
+
# Maximum parallel dispatches for swarm runs (Default: 3)
|
|
14
35
|
JULES_SWARM_CONCURRENCY=3
|
|
15
36
|
|
|
16
|
-
#
|
|
37
|
+
# Dispatch Stagger Interval in Milliseconds (Default: 1500)
|
|
17
38
|
JULES_SWARM_STAGGER_MS=1500
|
|
39
|
+
|
|
40
|
+
# Rate-limit for the jules:queue command (Default: 500)
|
|
41
|
+
# JULES_PACE_MS=500
|
|
42
|
+
|
|
43
|
+
# Use Git Worktrees instead of cloning for swarm isolation
|
|
44
|
+
# JULES_USE_WORKTREES=false
|
|
45
|
+
|
|
46
|
+
# Current slot index for partitioning tasks (Swarm mode)
|
|
47
|
+
# JULES_SLOT_INDEX=1
|
|
48
|
+
|
|
49
|
+
# Total number of slots for partitioning tasks (Swarm mode)
|
|
50
|
+
# JULES_SLOT_TOTAL=3
|
|
51
|
+
|
|
52
|
+
# --- CI & Output ---
|
|
53
|
+
# Is this running in a CI environment? (Changes log output and fail-fast behaviors)
|
|
54
|
+
# CI=true
|
|
55
|
+
|
|
56
|
+
# Allow OODA Auto-Repair even in CI environments
|
|
57
|
+
# ALLOW_AUTO_REPAIR=true
|
|
58
|
+
|
|
59
|
+
# GitHub Actions Step Summary File Path
|
|
60
|
+
# GITHUB_STEP_SUMMARY=/path/to/step_summary.md
|
|
61
|
+
|
|
62
|
+
# Disable color in terminal output
|
|
63
|
+
# NO_COLOR=true
|
|
@@ -27,8 +27,8 @@ jobs:
|
|
|
27
27
|
echo "Checking files modified in PR..."
|
|
28
28
|
MODIFIED_FILES=$(git diff --name-only $BASE_SHA $HEAD_SHA)
|
|
29
29
|
|
|
30
|
-
# Read protected paths
|
|
31
|
-
PROTECTED_PATHS=$(node -e "const paths = require('
|
|
30
|
+
# Read protected paths from origin/main
|
|
31
|
+
PROTECTED_PATHS=$(node -e "const paths = JSON.parse(require('child_process').execSync('git show origin/main:.agent/protected-paths.json', {encoding:'utf8'})).protected; console.log(paths.join(' '));")
|
|
32
32
|
|
|
33
33
|
echo "Protected patterns: $PROTECTED_PATHS"
|
|
34
34
|
|
|
@@ -9,6 +9,9 @@ on:
|
|
|
9
9
|
jobs:
|
|
10
10
|
audit-and-test:
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
node-version: ['18.x', '20.x', '22.x', '24.x']
|
|
12
15
|
steps:
|
|
13
16
|
- name: Checkout repository
|
|
14
17
|
uses: actions/checkout@v4
|
|
@@ -18,7 +21,10 @@ jobs:
|
|
|
18
21
|
- name: Setup Node.js
|
|
19
22
|
uses: actions/setup-node@v4
|
|
20
23
|
with:
|
|
21
|
-
node-version:
|
|
24
|
+
node-version: ${{ matrix.node-version }}
|
|
25
|
+
|
|
26
|
+
- name: Syntax Check
|
|
27
|
+
run: node --check scripts/*.mjs bin/*.js
|
|
22
28
|
|
|
23
29
|
- name: Run Unit Tests
|
|
24
30
|
run: npm test
|
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ npm run jules:status
|
|
|
60
60
|
- If tests failed, Jules fixed the code and re-ran tests.
|
|
61
61
|
- You'll receive a Pull Request with working, verified code.
|
|
62
62
|
|
|
63
|
-
> 🔐 **New in v0.
|
|
63
|
+
> 🔐 **New in v0.5.0 (The Epistemic Bridge)**: The init script generates a cryptographic Handshake Token (`.agent/JULES_WEB_SETUP.md`). Paste this into the Jules Web UI to sync your environment perfectly.
|
|
64
64
|
|
|
65
65
|
---
|
|
66
66
|
|
|
@@ -193,7 +193,7 @@ allow_paths: []
|
|
|
193
193
|
| Feature | What It Does | Example |
|
|
194
194
|
| --------------------- | -------------------------------------------- | ------------------------------------- |
|
|
195
195
|
| **Automatic Testing** | Runs test suite against every AI change | `test_cmd: "npm test"` |
|
|
196
|
-
| **Self-Fixing** | Jules automatically corrects failed tests | Retries up to
|
|
196
|
+
| **Self-Fixing** | Jules automatically corrects failed tests | Retries up to 3 times before blocking |
|
|
197
197
|
| **Secret Protection** | Hides API keys, passwords, tokens | Entropy > 3.6, length ≥ 20 |
|
|
198
198
|
| **Path Restrictions** | Blocks changes to sensitive files | `.env`, `*.pem`, `.github/**` |
|
|
199
199
|
| **Scope Boundaries** | Prevents changes outside task scope | `scope: ["src/auth/**"]` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jules-orchestrator-kit",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
|
+
"description": "Orchestration kit for running Google Jules autonomous agents.",
|
|
4
5
|
"repository": {
|
|
5
6
|
"type": "git",
|
|
6
7
|
"url": "git+https://github.com/FullThrottle83/jules-orchestrator-kit.git"
|
|
@@ -56,6 +57,6 @@
|
|
|
56
57
|
"mcp",
|
|
57
58
|
"automation"
|
|
58
59
|
],
|
|
59
|
-
"author": "
|
|
60
|
+
"author": "FullThrottle83",
|
|
60
61
|
"license": "MIT"
|
|
61
62
|
}
|
|
@@ -41,7 +41,9 @@ function loadEnv() {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
if (isMainModule) {
|
|
45
|
+
loadEnv();
|
|
46
|
+
}
|
|
45
47
|
|
|
46
48
|
// 0.2 Pre-Flight Secret Redaction Gate (RegEx + Env Denylist)
|
|
47
49
|
export function redactSecrets(text) {
|
|
@@ -52,7 +54,7 @@ export function redactSecrets(text) {
|
|
|
52
54
|
for (const [envKey, envVal] of Object.entries(process.env)) {
|
|
53
55
|
if (
|
|
54
56
|
envVal &&
|
|
55
|
-
envVal.length >=
|
|
57
|
+
envVal.length >= 20 &&
|
|
56
58
|
/KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|AUTH/i.test(envKey)
|
|
57
59
|
) {
|
|
58
60
|
if (sanitized.includes(envVal)) {
|
|
@@ -155,7 +157,7 @@ if (isMainModule) {
|
|
|
155
157
|
if (fs.existsSync(possiblePath)) {
|
|
156
158
|
try {
|
|
157
159
|
const verifiedPath = path.resolve(process.cwd(), possiblePath);
|
|
158
|
-
if (
|
|
160
|
+
if (!path.relative(process.cwd(), verifiedPath).startsWith('..')) {
|
|
159
161
|
const fd = fs.openSync(verifiedPath, "r");
|
|
160
162
|
try {
|
|
161
163
|
const stat = fs.fstatSync(fd);
|
|
@@ -269,10 +271,11 @@ async function executeDispatch() {
|
|
|
269
271
|
"X-Goog-Api-Key": apiKey,
|
|
270
272
|
"Content-Type": "application/json"
|
|
271
273
|
},
|
|
272
|
-
body: JSON.stringify(payload)
|
|
274
|
+
body: JSON.stringify(payload),
|
|
275
|
+
signal: AbortSignal.timeout(30000)
|
|
273
276
|
});
|
|
274
277
|
|
|
275
|
-
if (res.ok || res.status === 429) {
|
|
278
|
+
if (res.ok || res.status === 429 || res.status === 400 || res.status === 401) {
|
|
276
279
|
break;
|
|
277
280
|
}
|
|
278
281
|
|
|
@@ -300,8 +303,17 @@ async function executeDispatch() {
|
|
|
300
303
|
}
|
|
301
304
|
|
|
302
305
|
if (res.status === 429) {
|
|
303
|
-
|
|
304
|
-
|
|
306
|
+
const retryAfter = res.headers.get("Retry-After");
|
|
307
|
+
const err = new Error(`❌ Jules REST API Rate Limit Exceeded (HTTP 429). Retry after: ${retryAfter || 'unknown'}`);
|
|
308
|
+
err.code = 1;
|
|
309
|
+
throw err;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (res.status === 400 || res.status === 401) {
|
|
313
|
+
const errText = await res.text().catch(() => "");
|
|
314
|
+
const err = new Error(`❌ Jules REST API returned HTTP ${res.status}: ${errText}`);
|
|
315
|
+
err.code = 1;
|
|
316
|
+
throw err;
|
|
305
317
|
}
|
|
306
318
|
|
|
307
319
|
if (!res.ok) {
|
|
@@ -350,8 +362,9 @@ function dispatchViaCli(targetRepo, payloadFile, title) {
|
|
|
350
362
|
execFileSync("jules", args, { input: promptToSend, stdio: ["pipe", "inherit", "inherit"] });
|
|
351
363
|
log.success(`Successfully dispatched task "${title}" to Jules CLI.`);
|
|
352
364
|
} catch (error) {
|
|
353
|
-
|
|
354
|
-
|
|
365
|
+
const err = new Error(`Failed to dispatch task to Jules CLI: ${error.message}`);
|
|
366
|
+
err.code = 1;
|
|
367
|
+
throw err;
|
|
355
368
|
}
|
|
356
369
|
}
|
|
357
370
|
|
|
@@ -11,10 +11,9 @@ function runGitCommand(args, ignoreError = false) {
|
|
|
11
11
|
try {
|
|
12
12
|
return execFileSync("git", args, { encoding: "utf-8" }).trim();
|
|
13
13
|
} catch (error) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
process.exit(1);
|
|
14
|
+
const err = new Error(`Git command failed: git ${args.join(" ")}\n${error.message}`);
|
|
15
|
+
err.code = 1;
|
|
16
|
+
throw err;
|
|
18
17
|
}
|
|
19
18
|
}
|
|
20
19
|
|
|
@@ -153,16 +152,18 @@ export function runSelfAudit() {
|
|
|
153
152
|
const targetBranch = process.env.BASE_BRANCH || "main";
|
|
154
153
|
const SAFE_BRANCH = /^[a-zA-Z0-9._\/-]+$/;
|
|
155
154
|
if (!SAFE_BRANCH.test(targetBranch)) {
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
const err = new Error(`FATAL: Invalid BASE_BRANCH "${targetBranch}". Must match ^[a-zA-Z0-9._\\/-]+$`);
|
|
156
|
+
err.code = 2;
|
|
157
|
+
throw err;
|
|
158
158
|
}
|
|
159
159
|
log.info(`Target Branch: ${targetBranch}`);
|
|
160
160
|
|
|
161
161
|
try {
|
|
162
162
|
execFileSync("git", ["--version"], { stdio: "ignore" });
|
|
163
163
|
} catch {
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
const err = new Error("FATAL: git is not installed or not in PATH.");
|
|
165
|
+
err.code = 2;
|
|
166
|
+
throw err;
|
|
166
167
|
}
|
|
167
168
|
|
|
168
169
|
if (process.env.CI) {
|
|
@@ -180,8 +181,9 @@ export function runSelfAudit() {
|
|
|
180
181
|
const mergeBase = runGitCommand(["merge-base", "HEAD", mainRef], true);
|
|
181
182
|
|
|
182
183
|
if (!mergeBase) {
|
|
183
|
-
|
|
184
|
-
|
|
184
|
+
const err = new Error(`FATAL: Could not compute merge-base with ${mainRef}. Make sure git history is unshallowed.`);
|
|
185
|
+
err.code = 2;
|
|
186
|
+
throw err;
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
log.info(`Merge-Base Hash: ${mergeBase}`);
|
|
@@ -211,10 +213,9 @@ export function runSelfAudit() {
|
|
|
211
213
|
});
|
|
212
214
|
|
|
213
215
|
if (violations.length > 0) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
process.exit(3);
|
|
216
|
+
const err = new Error("RESTRICTED FILE VIOLATION DETECTED!");
|
|
217
|
+
err.code = 3;
|
|
218
|
+
throw err;
|
|
218
219
|
}
|
|
219
220
|
log.success("Restricted File Boundary Check: PASSED");
|
|
220
221
|
|
|
@@ -254,7 +255,7 @@ export function runSelfAudit() {
|
|
|
254
255
|
|
|
255
256
|
if (!resolvedCmds.testCmd && !resolvedCmds.buildCmd) {
|
|
256
257
|
log.info("No build or test scripts found. Running git status check.");
|
|
257
|
-
|
|
258
|
+
execSync("git status", { stdio: "inherit" });
|
|
258
259
|
}
|
|
259
260
|
|
|
260
261
|
const durationMs = Date.now() - startTime;
|
|
@@ -270,45 +271,10 @@ export function runSelfAudit() {
|
|
|
270
271
|
log.error("OODA SELF-HEALING FEEDBACK LOG:");
|
|
271
272
|
log.dim(failureLog.slice(-1500));
|
|
272
273
|
|
|
273
|
-
// OODA Circuit Breaker: Fingerprint failure trace using SHA-256
|
|
274
|
-
const normalizedError = failureLog
|
|
275
|
-
.replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z/g, "TIMESTAMP")
|
|
276
|
-
.replace(/0x[0-9a-fA-F]+/g, "HEX")
|
|
277
|
-
.replace(/\d+/g, "NUM")
|
|
278
|
-
.trim();
|
|
279
|
-
const errorHash = crypto.createHash("sha256").update(normalizedError).digest("hex");
|
|
280
|
-
|
|
281
|
-
const circuitFile = path.resolve(process.cwd(), ".agent/history/ooda-circuit.json");
|
|
282
|
-
let prevCircuit = { hash: "", count: 0 };
|
|
283
|
-
if (fs.existsSync(circuitFile)) {
|
|
284
|
-
try {
|
|
285
|
-
prevCircuit = JSON.parse(fs.readFileSync(circuitFile, "utf-8"));
|
|
286
|
-
} catch (_) {}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if (prevCircuit.hash === errorHash && prevCircuit.count >= 1) {
|
|
290
|
-
log.error("❌ OODA CIRCUIT BREAKER TRIPPED: Consecutive identical failure detected.");
|
|
291
|
-
log.error("Agent generated code with identical error trace twice in a row. Aborting auto-repair.");
|
|
292
|
-
try {
|
|
293
|
-
fs.writeFileSync(circuitFile, JSON.stringify({ hash: "", count: 0 }));
|
|
294
|
-
} catch (_) {}
|
|
295
|
-
process.exit(4);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
try {
|
|
299
|
-
fs.writeFileSync(
|
|
300
|
-
circuitFile,
|
|
301
|
-
JSON.stringify({
|
|
302
|
-
hash: errorHash,
|
|
303
|
-
count: prevCircuit.hash === errorHash ? prevCircuit.count + 1 : 1,
|
|
304
|
-
updatedAt: new Date().toISOString()
|
|
305
|
-
})
|
|
306
|
-
);
|
|
307
|
-
} catch (_) {}
|
|
308
|
-
|
|
309
274
|
if (process.env.CI && !process.env.ALLOW_AUTO_REPAIR) {
|
|
310
|
-
|
|
311
|
-
|
|
275
|
+
const err = new Error("CI environment detected and ALLOW_AUTO_REPAIR is not set. Failing fast to save CI minutes.");
|
|
276
|
+
err.code = 4;
|
|
277
|
+
throw err;
|
|
312
278
|
}
|
|
313
279
|
|
|
314
280
|
let oodaRetries = 0;
|
|
@@ -342,28 +308,29 @@ export function runSelfAudit() {
|
|
|
342
308
|
} else {
|
|
343
309
|
log.error("❌ OODA Auto-Repair exhausted maximum retries (3). Giving up.");
|
|
344
310
|
}
|
|
345
|
-
|
|
346
|
-
|
|
311
|
+
const err = new Error("OODA Auto-Repair failed or exhausted.");
|
|
312
|
+
err.code = 4;
|
|
313
|
+
throw err;
|
|
347
314
|
}
|
|
348
315
|
|
|
349
316
|
log.success("🎉 JULES PR SELF-AUDIT PASSED SUCCESSFULLY!");
|
|
350
317
|
}
|
|
351
318
|
|
|
352
319
|
export function runPreflightSandbox() {
|
|
353
|
-
log.header("Initializing Pre-Flight Sandbox (
|
|
320
|
+
log.header("Initializing Pre-Flight Sandbox (Sterile Simulation)...");
|
|
354
321
|
|
|
355
322
|
const runId = crypto.randomBytes(4).toString("hex");
|
|
356
323
|
const sandboxDir = path.join(os.tmpdir(), `jules-preflight-${runId}`);
|
|
357
324
|
const tempArchive = path.join(os.tmpdir(), `jules-archive-${runId}.tar`);
|
|
358
325
|
|
|
359
326
|
try {
|
|
360
|
-
log.step("[1/
|
|
327
|
+
log.step("[1/5]", `Creating isolated environment in ${sandboxDir}...`);
|
|
361
328
|
fs.mkdirSync(sandboxDir, { recursive: true });
|
|
362
329
|
|
|
363
330
|
execSync(`git archive HEAD -o "${tempArchive}"`);
|
|
364
331
|
execSync(`tar -xf "${tempArchive}" -C "${sandboxDir}"`);
|
|
365
332
|
|
|
366
|
-
log.step("[2/
|
|
333
|
+
log.step("[2/5]", "Sterilizing environment variables...");
|
|
367
334
|
const sterileEnv = { ...process.env };
|
|
368
335
|
const stripKeys = ["DATABASE_URL", "NPM_TOKEN", "GITHUB_TOKEN", "GH_TOKEN", "AWS_ACCESS_KEY_ID", "STRIPE_TEST_KEY"];
|
|
369
336
|
for (const key of stripKeys) {
|
|
@@ -372,11 +339,21 @@ export function runPreflightSandbox() {
|
|
|
372
339
|
}
|
|
373
340
|
}
|
|
374
341
|
|
|
375
|
-
log.step("[3/
|
|
342
|
+
log.step("[3/5]", "Installing dependencies in sandbox (npm ci)...");
|
|
343
|
+
const packageJsonPath = path.join(sandboxDir, "package.json");
|
|
344
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
345
|
+
try {
|
|
346
|
+
execSync("npm ci --ignore-scripts", { cwd: sandboxDir, stdio: "ignore" });
|
|
347
|
+
} catch (e) {
|
|
348
|
+
log.warn("npm ci failed or wasn't needed. Proceeding...");
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
log.step("[4/5]", "Resolving execution boundary in sandbox...");
|
|
376
353
|
const resolvedCmds = resolveWorkspaceExecutionBoundary([], sandboxDir);
|
|
377
354
|
|
|
378
355
|
if (resolvedCmds.testCmd || resolvedCmds.buildCmd) {
|
|
379
|
-
log.step("[
|
|
356
|
+
log.step("[5/5]", `Running verification: ${resolvedCmds.testCmd || resolvedCmds.buildCmd}`);
|
|
380
357
|
execSync(resolvedCmds.testCmd || resolvedCmds.buildCmd, {
|
|
381
358
|
cwd: sandboxDir,
|
|
382
359
|
env: sterileEnv,
|
|
@@ -384,14 +361,15 @@ export function runPreflightSandbox() {
|
|
|
384
361
|
});
|
|
385
362
|
log.success("Pre-Flight Passed. Zero epistemic drift detected.");
|
|
386
363
|
} else {
|
|
387
|
-
log.info("
|
|
364
|
+
log.info("No test/build commands found for verification.");
|
|
388
365
|
log.success("Pre-Flight Passed (No-Op).");
|
|
389
366
|
}
|
|
390
367
|
} catch (err) {
|
|
391
|
-
log.error("\nPRE-FLIGHT
|
|
392
|
-
log.error("
|
|
393
|
-
|
|
394
|
-
|
|
368
|
+
log.error("\nPRE-FLIGHT FAILED!");
|
|
369
|
+
log.error("The agent would likely crash (The 5-Minute Drop-off) in the Web UI due to missing dependencies.");
|
|
370
|
+
const wrappedErr = new Error(err.message);
|
|
371
|
+
wrappedErr.code = 1;
|
|
372
|
+
throw wrappedErr;
|
|
395
373
|
} finally {
|
|
396
374
|
try {
|
|
397
375
|
fs.rmSync(sandboxDir, { recursive: true, force: true });
|
|
@@ -406,10 +384,15 @@ export function runPreflightSandbox() {
|
|
|
406
384
|
|
|
407
385
|
// Execute when invoked directly from CLI
|
|
408
386
|
if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
387
|
+
try {
|
|
388
|
+
if (process.argv.includes("--preflight")) {
|
|
389
|
+
runPreflightSandbox();
|
|
390
|
+
} else {
|
|
391
|
+
runSelfAudit();
|
|
392
|
+
}
|
|
393
|
+
} catch (err) {
|
|
394
|
+
if (err.message) log.error(err.message);
|
|
395
|
+
process.exit(err.code || 1);
|
|
413
396
|
}
|
|
414
397
|
}
|
|
415
398
|
|