enigma-cli 1.0.0 → 1.1.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/assets/skills/backend-policy/skill.json +1 -1
- package/assets/skills/ciphera-style-policy/skill.json +1 -1
- package/assets/skills/code-review-policy/skill.json +1 -1
- package/assets/skills/core-engineering-policy/skill.json +1 -1
- package/assets/skills/database-expert/skill.json +1 -1
- package/assets/skills/debugging-policy/skill.json +1 -1
- package/assets/skills/dependency-policy/skill.json +1 -1
- package/assets/skills/frontend-policy/skill.json +1 -1
- package/assets/skills/git-policy/skill.json +1 -1
- package/assets/skills/security-policy/skill.json +1 -1
- package/assets/skills/testing-policy/skill.json +1 -1
- package/assets/skills/validation-policy/skill.json +1 -1
- package/dist/enigma.js +330 -93
- package/package.json +1 -1
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Backend/API architecture: controller-service-repository layering, API and request optimization, server-side caching (Redis), and Zod boundary validation.",
|
|
6
|
-
"cliVersion": "1.
|
|
6
|
+
"cliVersion": "1.1.1",
|
|
7
7
|
"sha": "c442bc9e39a7710cb709ef2abb8d15ecd8aa16ed4f5c8af92b7af6877401cba4"
|
|
8
8
|
}
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
"version": "1.1.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Ciphera code style conventions (formatting, naming, imports, comments, code-level anti-patterns; TypeScript-first, language-agnostic).",
|
|
6
|
-
"cliVersion": "1.
|
|
6
|
+
"cliVersion": "1.1.1",
|
|
7
7
|
"sha": "f8602bb79fbbe063ab39fbd59d0b7844a22c3a1583fcd11c1b4f98a2fe8ddc86"
|
|
8
8
|
}
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Pre-delivery self-review gate, prioritized review dimensions, and change-quality criteria.",
|
|
6
|
-
"cliVersion": "1.
|
|
6
|
+
"cliVersion": "1.1.1",
|
|
7
7
|
"sha": "3d3bbe0602d5bbb4afe37648fe3c2fa39376b1bcbac5d8c441f01fad1e866ed0"
|
|
8
8
|
}
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
"version": "1.4.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Core engineering execution policy and harness orchestration (highest-authority rules).",
|
|
6
|
-
"cliVersion": "1.
|
|
6
|
+
"cliVersion": "1.1.1",
|
|
7
7
|
"sha": "c9c69c59516794311cb7b306ed4d4ad971824de3689a39c2b86c7669c73f2e8b"
|
|
8
8
|
}
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Senior database architecture policy: query optimization, anti-duplication/normalization, scalability, and RGPD/GDPR encryption.",
|
|
6
|
-
"cliVersion": "1.
|
|
6
|
+
"cliVersion": "1.1.1",
|
|
7
7
|
"sha": "c4617ee8d1a57d9621c81bef3093e94de91f79eec0cc0ead41f6d18dd443e623"
|
|
8
8
|
}
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Reproduce-isolate-fix debugging methodology with root-cause discipline and regression verification.",
|
|
6
|
-
"cliVersion": "1.
|
|
6
|
+
"cliVersion": "1.1.1",
|
|
7
7
|
"sha": "14b0064c8b33a0dc85e51464b05005cf5801c756b1101789a6924b9548420f6b"
|
|
8
8
|
}
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Dependency and supply-chain security: lockfiles and reproducible installs, version pinning, vulnerability auditing, vetting/minimizing packages, vendoring, and SBOM/provenance.",
|
|
6
|
-
"cliVersion": "1.
|
|
6
|
+
"cliVersion": "1.1.1",
|
|
7
7
|
"sha": "6375d835c2aef2c9bd31ce116444dc3d796f510f9970a213aa3ac4696d7e21b9"
|
|
8
8
|
}
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Frontend architecture: reusable components, abstraction thresholds, state management, and optimistic UI with rollback.",
|
|
6
|
-
"cliVersion": "1.
|
|
6
|
+
"cliVersion": "1.1.1",
|
|
7
7
|
"sha": "b0355b0e15f9f528d32adf19f0722d2727cd64d6b3544307ecc7a3141338f023"
|
|
8
8
|
}
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Application and AI-agent security: secrets, authn/authz (least privilege), OWASP Top 10, transport/crypto baseline, secure logging, and agent/MCP/tool-use safety.",
|
|
6
|
-
"cliVersion": "1.
|
|
6
|
+
"cliVersion": "1.1.1",
|
|
7
7
|
"sha": "9971e9d9127397d0152e89d24aad3191e2935e55a8483db7fd15f5d4d7a60e7a"
|
|
8
8
|
}
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Test strategy, coverage gates, deterministic tests, mocking discipline, and regression-first bug fixing.",
|
|
6
|
-
"cliVersion": "1.
|
|
6
|
+
"cliVersion": "1.1.1",
|
|
7
7
|
"sha": "d19fa8ec7985ed231478be504d3c80360897f555d0bc0624bea19c091f459fb0"
|
|
8
8
|
}
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Strict frontend + backend schema validation, schema consistency, and safe client-facing error handling.",
|
|
6
|
-
"cliVersion": "1.
|
|
6
|
+
"cliVersion": "1.1.1",
|
|
7
7
|
"sha": "a33622a2f810ee4cea39824cb1a7ca34b355a917d4224025df50d77dd74f0b3a"
|
|
8
8
|
}
|
package/dist/enigma.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { dirname as dirname4, join as
|
|
4
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
5
5
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
6
|
-
import * as
|
|
6
|
+
import * as p5 from "@clack/prompts";
|
|
7
7
|
|
|
8
8
|
// src/util.ts
|
|
9
9
|
import { existsSync, statSync, readFileSync } from "fs";
|
|
@@ -17,7 +17,7 @@ function isDir(pth) {
|
|
|
17
17
|
}
|
|
18
18
|
function readJson(file) {
|
|
19
19
|
try {
|
|
20
|
-
return JSON.parse(readFileSync(file, "utf8"));
|
|
20
|
+
return JSON.parse(readFileSync(file, "utf8").replace(/^\uFEFF/, ""));
|
|
21
21
|
} catch {
|
|
22
22
|
return null;
|
|
23
23
|
}
|
|
@@ -29,11 +29,11 @@ function isOnPath(bin) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// src/skills.ts
|
|
32
|
-
import { existsSync as
|
|
33
|
-
import { dirname as dirname2, join as
|
|
32
|
+
import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync4, cpSync as cpSync2, mkdirSync as mkdirSync4, rmSync } from "fs";
|
|
33
|
+
import { dirname as dirname2, join as join6, resolve as resolve2, relative as relative2, sep as sep2 } from "path";
|
|
34
34
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
35
35
|
import { createHash } from "crypto";
|
|
36
|
-
import * as
|
|
36
|
+
import * as p3 from "@clack/prompts";
|
|
37
37
|
|
|
38
38
|
// src/agents.ts
|
|
39
39
|
import { homedir } from "os";
|
|
@@ -257,15 +257,134 @@ function disableClaudeAttribution(scope) {
|
|
|
257
257
|
writeFileSync2(path, JSON.stringify(next, null, 2) + "\n");
|
|
258
258
|
return true;
|
|
259
259
|
}
|
|
260
|
+
function enableClaudeBypass(scope, dryRun) {
|
|
261
|
+
const path = claudeSettingsPath(scope);
|
|
262
|
+
const current = readJson(path) || {};
|
|
263
|
+
const permissions = typeof current.permissions === "object" && current.permissions !== null ? current.permissions : {};
|
|
264
|
+
if (permissions.defaultMode === "bypassPermissions") return { path, changed: false };
|
|
265
|
+
if (dryRun) return { path, changed: true };
|
|
266
|
+
const next = { ...current, permissions: { ...permissions, defaultMode: "bypassPermissions" } };
|
|
267
|
+
const dir = join4(path, "..");
|
|
268
|
+
if (!isDir(dir)) mkdirSync2(dir, { recursive: true });
|
|
269
|
+
writeFileSync2(path, JSON.stringify(next, null, 2) + "\n");
|
|
270
|
+
return { path, changed: true };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/permissions.ts
|
|
274
|
+
import { homedir as homedir3 } from "os";
|
|
275
|
+
import { join as join5 } from "path";
|
|
276
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
277
|
+
import * as p2 from "@clack/prompts";
|
|
278
|
+
var BYPASS_SUPPORTED = ["claude", "codex", "opencode"];
|
|
279
|
+
var BYPASS_DEFAULT_ON = /* @__PURE__ */ new Set(["claude", "codex"]);
|
|
280
|
+
async function resolveBypassSelection(candidates, opts, interactive) {
|
|
281
|
+
const supported = candidates.filter((a) => BYPASS_SUPPORTED.includes(a.name));
|
|
282
|
+
if (!supported.length || opts.noBypass) return [];
|
|
283
|
+
const names = supported.map((a) => a.name);
|
|
284
|
+
if (opts.bypass !== null) {
|
|
285
|
+
const req = opts.bypass.map((s) => s.trim().toLowerCase());
|
|
286
|
+
if (req.includes("none")) return [];
|
|
287
|
+
if (req.includes("all")) return names;
|
|
288
|
+
return names.filter((n) => req.includes(n));
|
|
289
|
+
}
|
|
290
|
+
if (!interactive) return [];
|
|
291
|
+
const r = await p2.multiselect({
|
|
292
|
+
message: "Bypass approval prompts for which agents? (security trade-off: the agent stops asking before acting)",
|
|
293
|
+
options: supported.map((a) => ({
|
|
294
|
+
value: a.name,
|
|
295
|
+
label: a.label,
|
|
296
|
+
hint: BYPASS_DEFAULT_ON.has(a.name) ? "recommended" : "less reliable - off by default"
|
|
297
|
+
})),
|
|
298
|
+
initialValues: names.filter((n) => BYPASS_DEFAULT_ON.has(n)),
|
|
299
|
+
required: false
|
|
300
|
+
});
|
|
301
|
+
if (p2.isCancel(r)) return [];
|
|
302
|
+
return r;
|
|
303
|
+
}
|
|
304
|
+
function applyBypass(agentNames, scope, dryRun) {
|
|
305
|
+
for (const name of agentNames) {
|
|
306
|
+
const res = enableFor(name, scope, dryRun);
|
|
307
|
+
if (!res) continue;
|
|
308
|
+
const label = AGENTS[name]?.label || name;
|
|
309
|
+
if (dryRun) p2.log.info(`Bypass (dry run): would enable for ${label} -> ${res.path}.`);
|
|
310
|
+
else if (res.changed) p2.log.warn(`Bypass: enabled for ${label} (${res.path}) - approval prompts are now OFF.`);
|
|
311
|
+
else p2.log.info(`Bypass: already enabled for ${label}.`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function enableFor(name, scope, dryRun) {
|
|
315
|
+
switch (name) {
|
|
316
|
+
case "claude":
|
|
317
|
+
return enableClaudeBypass(scope, dryRun);
|
|
318
|
+
case "codex":
|
|
319
|
+
return enableCodexBypass(dryRun);
|
|
320
|
+
case "opencode":
|
|
321
|
+
return enableOpencodeBypass(scope, dryRun);
|
|
322
|
+
default:
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function enableCodexBypass(dryRun) {
|
|
327
|
+
const path = join5(homedir3(), ".codex", "config.toml");
|
|
328
|
+
const before = existsSync4(path) ? readFileSync2(path, "utf8") : "";
|
|
329
|
+
let after = setTomlTopLevelKey(before, "approval_policy", '"never"');
|
|
330
|
+
after = setTomlTopLevelKey(after, "sandbox_mode", '"danger-full-access"');
|
|
331
|
+
const changed = after !== before;
|
|
332
|
+
if (changed && !dryRun) {
|
|
333
|
+
const dir = join5(path, "..");
|
|
334
|
+
if (!isDir(dir)) mkdirSync3(dir, { recursive: true });
|
|
335
|
+
writeFileSync3(path, after);
|
|
336
|
+
}
|
|
337
|
+
return { path, changed };
|
|
338
|
+
}
|
|
339
|
+
function enableOpencodeBypass(scope, dryRun) {
|
|
340
|
+
const path = scope === "global" ? join5(homedir3(), ".config", "opencode", "opencode.json") : join5(process.cwd(), "opencode.json");
|
|
341
|
+
const current = readJson(path) || {};
|
|
342
|
+
const perm = current.permission;
|
|
343
|
+
const alreadyAllowAll = perm === "allow" || typeof perm === "object" && perm !== null && perm["*"] === "allow";
|
|
344
|
+
if (alreadyAllowAll) return { path, changed: false };
|
|
345
|
+
if (dryRun) return { path, changed: true };
|
|
346
|
+
const existing = typeof perm === "object" && perm !== null ? perm : {};
|
|
347
|
+
const rest = {};
|
|
348
|
+
for (const k of Object.keys(existing)) if (k !== "*") rest[k] = existing[k];
|
|
349
|
+
const next = { ...current, permission: { "*": "allow", ...rest } };
|
|
350
|
+
const dir = join5(path, "..");
|
|
351
|
+
if (!isDir(dir)) mkdirSync3(dir, { recursive: true });
|
|
352
|
+
writeFileSync3(path, JSON.stringify(next, null, 2) + "\n");
|
|
353
|
+
return { path, changed: true };
|
|
354
|
+
}
|
|
355
|
+
function setTomlTopLevelKey(content, key, tomlValue) {
|
|
356
|
+
const assign = `${key} = ${tomlValue}`;
|
|
357
|
+
if (content.trim() === "") return `${assign}
|
|
358
|
+
`;
|
|
359
|
+
const lines = content.split("\n");
|
|
360
|
+
const firstTable = lines.findIndex((l) => /^\s*\[/.test(l));
|
|
361
|
+
const scanEnd = firstTable === -1 ? lines.length : firstTable;
|
|
362
|
+
const keyRe = new RegExp(`^\\s*${key}\\s*=`);
|
|
363
|
+
for (let i = 0; i < scanEnd; i++) {
|
|
364
|
+
if (!keyRe.test(lines[i])) continue;
|
|
365
|
+
if (lines[i].trim() === assign) return content;
|
|
366
|
+
lines[i] = assign;
|
|
367
|
+
return normalizeTrailingNewline(lines.join("\n"));
|
|
368
|
+
}
|
|
369
|
+
let insertAt = scanEnd;
|
|
370
|
+
while (insertAt > 0 && lines[insertAt - 1].trim() === "") insertAt--;
|
|
371
|
+
const followsTable = insertAt < lines.length && /^\s*\[/.test(lines[insertAt]);
|
|
372
|
+
lines.splice(insertAt, 0, ...followsTable ? [assign, ""] : [assign]);
|
|
373
|
+
return normalizeTrailingNewline(lines.join("\n"));
|
|
374
|
+
}
|
|
375
|
+
function normalizeTrailingNewline(s) {
|
|
376
|
+
return `${s.replace(/\s+$/, "")}
|
|
377
|
+
`;
|
|
378
|
+
}
|
|
260
379
|
|
|
261
380
|
// src/skills.ts
|
|
262
381
|
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
263
382
|
var PKG_ROOT = resolve2(__dirname2, "..");
|
|
264
|
-
var ASSETS =
|
|
265
|
-
var SKILLS_ROOT =
|
|
266
|
-
var MEMORY_ROOT =
|
|
383
|
+
var ASSETS = join6(PKG_ROOT, "assets");
|
|
384
|
+
var SKILLS_ROOT = join6(ASSETS, "skills");
|
|
385
|
+
var MEMORY_ROOT = join6(ASSETS, "memory");
|
|
267
386
|
function cliVersion() {
|
|
268
|
-
return (readJson(
|
|
387
|
+
return (readJson(join6(PKG_ROOT, "package.json")) || {}).version || "0.0.0";
|
|
269
388
|
}
|
|
270
389
|
function serializeMeta(meta) {
|
|
271
390
|
const ordered = {};
|
|
@@ -276,12 +395,12 @@ function serializeMeta(meta) {
|
|
|
276
395
|
return JSON.stringify(ordered, null, 2) + "\n";
|
|
277
396
|
}
|
|
278
397
|
function readSkillMeta(skillDir) {
|
|
279
|
-
return readJson(
|
|
398
|
+
return readJson(join6(skillDir, "skill.json")) || {};
|
|
280
399
|
}
|
|
281
400
|
function listFilesRel(dir, base = dir) {
|
|
282
401
|
const out = [];
|
|
283
402
|
for (const e of readdirSync(dir)) {
|
|
284
|
-
const full =
|
|
403
|
+
const full = join6(dir, e);
|
|
285
404
|
if (isDir(full)) out.push(...listFilesRel(full, base));
|
|
286
405
|
else out.push(relative2(base, full).split(sep2).join("/"));
|
|
287
406
|
}
|
|
@@ -293,13 +412,13 @@ function computeContentSha(dir) {
|
|
|
293
412
|
for (const f of files) {
|
|
294
413
|
h.update(f);
|
|
295
414
|
h.update("\0");
|
|
296
|
-
h.update(
|
|
415
|
+
h.update(readFileSync3(join6(dir, f)));
|
|
297
416
|
h.update("\0");
|
|
298
417
|
}
|
|
299
418
|
return h.digest("hex");
|
|
300
419
|
}
|
|
301
420
|
function skillStatus(destDir, srcMeta) {
|
|
302
|
-
if (!
|
|
421
|
+
if (!existsSync5(destDir)) return { kind: "install", from: null, to: srcMeta.version || null };
|
|
303
422
|
const destMeta = readSkillMeta(destDir);
|
|
304
423
|
const from = destMeta.version || null;
|
|
305
424
|
const to = srcMeta.version || null;
|
|
@@ -326,27 +445,27 @@ function statusLabel(st) {
|
|
|
326
445
|
}
|
|
327
446
|
function filesEqual(a, b) {
|
|
328
447
|
try {
|
|
329
|
-
return
|
|
448
|
+
return readFileSync3(a).equals(readFileSync3(b));
|
|
330
449
|
} catch {
|
|
331
450
|
return false;
|
|
332
451
|
}
|
|
333
452
|
}
|
|
334
453
|
function memoryStatus(srcFile, destFile) {
|
|
335
|
-
if (!
|
|
454
|
+
if (!existsSync5(destFile)) return "install";
|
|
336
455
|
return filesEqual(srcFile, destFile) ? "identical" : "overwrite";
|
|
337
456
|
}
|
|
338
457
|
function computePrune(destSkillsDir, sourceNames) {
|
|
339
458
|
if (!isDir(destSkillsDir)) return [];
|
|
340
|
-
return readdirSync(destSkillsDir).filter((e) => isDir(
|
|
459
|
+
return readdirSync(destSkillsDir).filter((e) => isDir(join6(destSkillsDir, e)) && existsSync5(join6(destSkillsDir, e, "SKILL.md"))).filter((e) => !sourceNames.includes(e)).map((e) => ({ name: e, dir: join6(destSkillsDir, e), meta: readSkillMeta(join6(destSkillsDir, e)) })).filter((s) => isManagedProvider(s.meta.provider));
|
|
341
460
|
}
|
|
342
461
|
function inspectSkills() {
|
|
343
462
|
if (!isDir(SKILLS_ROOT)) return [];
|
|
344
|
-
return readdirSync(SKILLS_ROOT).filter((e) => isDir(
|
|
463
|
+
return readdirSync(SKILLS_ROOT).filter((e) => isDir(join6(SKILLS_ROOT, e)) && existsSync5(join6(SKILLS_ROOT, e, "SKILL.md"))).map((e) => ({ name: e, src: join6(SKILLS_ROOT, e), meta: readSkillMeta(join6(SKILLS_ROOT, e)) }));
|
|
345
464
|
}
|
|
346
465
|
function inspectMemory(agent) {
|
|
347
466
|
if (!agent.memoryFile) return [];
|
|
348
|
-
const src =
|
|
349
|
-
return
|
|
467
|
+
const src = join6(MEMORY_ROOT, agent.memoryFile);
|
|
468
|
+
return existsSync5(src) ? [{ name: agent.memoryFile, src }] : [];
|
|
350
469
|
}
|
|
351
470
|
function sealSources() {
|
|
352
471
|
if (!isDir(SKILLS_ROOT)) {
|
|
@@ -356,16 +475,16 @@ function sealSources() {
|
|
|
356
475
|
const cli = cliVersion();
|
|
357
476
|
let sealed = 0;
|
|
358
477
|
for (const name of readdirSync(SKILLS_ROOT)) {
|
|
359
|
-
const dir =
|
|
360
|
-
if (!isDir(dir) || !
|
|
361
|
-
const metaPath =
|
|
478
|
+
const dir = join6(SKILLS_ROOT, name);
|
|
479
|
+
if (!isDir(dir) || !existsSync5(join6(dir, "SKILL.md"))) continue;
|
|
480
|
+
const metaPath = join6(dir, "skill.json");
|
|
362
481
|
const meta = readJson(metaPath) || { name };
|
|
363
482
|
const before = JSON.stringify(meta);
|
|
364
483
|
meta.provider = MANAGED_PROVIDER;
|
|
365
484
|
meta.cliVersion = cli;
|
|
366
485
|
meta.sha = computeContentSha(dir);
|
|
367
486
|
const changed = JSON.stringify(meta) !== before;
|
|
368
|
-
|
|
487
|
+
writeFileSync4(metaPath, serializeMeta(meta));
|
|
369
488
|
console.log(`${changed ? "updated" : "ok "} ${name} cli=${cli} sha=${meta.sha.slice(0, 12)}`);
|
|
370
489
|
sealed++;
|
|
371
490
|
}
|
|
@@ -381,18 +500,18 @@ function checkSources() {
|
|
|
381
500
|
const problems = [];
|
|
382
501
|
let checked = 0;
|
|
383
502
|
for (const name of readdirSync(SKILLS_ROOT)) {
|
|
384
|
-
const dir =
|
|
385
|
-
if (!isDir(dir) || !
|
|
503
|
+
const dir = join6(SKILLS_ROOT, name);
|
|
504
|
+
if (!isDir(dir) || !existsSync5(join6(dir, "SKILL.md"))) continue;
|
|
386
505
|
checked++;
|
|
387
|
-
const md =
|
|
506
|
+
const md = readFileSync3(join6(dir, "SKILL.md"), "utf8");
|
|
388
507
|
const fm = md.match(/^---\n([\s\S]*?)\n---/);
|
|
389
508
|
if (!fm) problems.push(`${name}: SKILL.md is missing YAML frontmatter`);
|
|
390
509
|
else {
|
|
391
510
|
if (!/^name:\s*\S/m.test(fm[1])) problems.push(`${name}: frontmatter missing 'name'`);
|
|
392
511
|
if (!/^description:\s*\S/m.test(fm[1])) problems.push(`${name}: frontmatter missing 'description'`);
|
|
393
512
|
}
|
|
394
|
-
const metaPath =
|
|
395
|
-
if (!
|
|
513
|
+
const metaPath = join6(dir, "skill.json");
|
|
514
|
+
if (!existsSync5(metaPath)) {
|
|
396
515
|
problems.push(`${name}: missing skill.json`);
|
|
397
516
|
continue;
|
|
398
517
|
}
|
|
@@ -417,22 +536,22 @@ function checkSources() {
|
|
|
417
536
|
async function installSkills(opts, interactive) {
|
|
418
537
|
const available = discoverAgents();
|
|
419
538
|
if (available.length === 0) {
|
|
420
|
-
|
|
539
|
+
p3.cancel("No installable agents known.");
|
|
421
540
|
process.exit(1);
|
|
422
541
|
}
|
|
423
542
|
let scope;
|
|
424
543
|
if (opts.scope) {
|
|
425
544
|
scope = opts.scope;
|
|
426
545
|
} else if (interactive) {
|
|
427
|
-
const r = await
|
|
546
|
+
const r = await p3.select({
|
|
428
547
|
message: "Where should skills be installed?",
|
|
429
548
|
options: [
|
|
430
549
|
{ value: "global", label: "Global (user)", hint: "~/.claude, ~/.codex, ~/.config/opencode" },
|
|
431
550
|
{ value: "local", label: "Local (this project)", hint: process.cwd() }
|
|
432
551
|
]
|
|
433
552
|
});
|
|
434
|
-
if (
|
|
435
|
-
|
|
553
|
+
if (p3.isCancel(r)) {
|
|
554
|
+
p3.cancel("Aborted.");
|
|
436
555
|
return;
|
|
437
556
|
}
|
|
438
557
|
scope = r;
|
|
@@ -444,19 +563,19 @@ async function installSkills(opts, interactive) {
|
|
|
444
563
|
if (opts.agents.length) {
|
|
445
564
|
chosenAgents = available.filter((a) => opts.agents.includes(a.name));
|
|
446
565
|
const unknown = opts.agents.filter((n) => !available.some((a) => a.name === n));
|
|
447
|
-
if (unknown.length)
|
|
566
|
+
if (unknown.length) p3.log.warn(`Skipping unknown/absent agents: ${unknown.join(", ")}`);
|
|
448
567
|
} else if (opts.allAgents) {
|
|
449
568
|
chosenAgents = available;
|
|
450
569
|
} else if (interactive && available.length > 1) {
|
|
451
570
|
const preselect = (detected.length ? detected : available).map((a) => a.name);
|
|
452
|
-
const r = await
|
|
571
|
+
const r = await p3.multiselect({
|
|
453
572
|
message: "Which agents? (detected on this system are preselected)",
|
|
454
573
|
options: available.map((a) => ({ value: a.name, label: a.label, hint: a.installed ? "detected" : "not detected" })),
|
|
455
574
|
initialValues: preselect,
|
|
456
575
|
required: true
|
|
457
576
|
});
|
|
458
|
-
if (
|
|
459
|
-
|
|
577
|
+
if (p3.isCancel(r)) {
|
|
578
|
+
p3.cancel("Aborted.");
|
|
460
579
|
return;
|
|
461
580
|
}
|
|
462
581
|
chosenAgents = available.filter((a) => r.includes(a.name));
|
|
@@ -464,24 +583,26 @@ async function installSkills(opts, interactive) {
|
|
|
464
583
|
chosenAgents = detected;
|
|
465
584
|
} else {
|
|
466
585
|
chosenAgents = available;
|
|
467
|
-
|
|
586
|
+
p3.log.warn("No installed agents detected; defaulting to all supported agents.");
|
|
468
587
|
}
|
|
469
588
|
if (chosenAgents.length === 0) {
|
|
470
|
-
|
|
589
|
+
p3.cancel("No matching agents selected.");
|
|
471
590
|
process.exit(1);
|
|
472
591
|
}
|
|
473
592
|
const claudeScope = chosenAgents.some((a) => a.name === "claude") ? scope : null;
|
|
474
593
|
const applyClaudeConfig = () => {
|
|
475
594
|
if (!claudeScope || opts.dryRun) return;
|
|
476
595
|
if (disableClaudeAttribution(claudeScope)) {
|
|
477
|
-
|
|
596
|
+
p3.log.info("Claude Code: disabled Co-Authored-By and PR attribution in settings.json.");
|
|
478
597
|
}
|
|
479
598
|
};
|
|
599
|
+
const bypassAgents = await resolveBypassSelection(chosenAgents, opts, interactive);
|
|
600
|
+
const applyBypassConfig = () => applyBypass(bypassAgents, scope, opts.dryRun);
|
|
480
601
|
const plan = [];
|
|
481
602
|
for (const agent of chosenAgents) {
|
|
482
603
|
const target = agent.targets[scope];
|
|
483
604
|
if (!target) {
|
|
484
|
-
|
|
605
|
+
p3.log.warn(`${agent.label} has no '${scope}' target - skipping.`);
|
|
485
606
|
continue;
|
|
486
607
|
}
|
|
487
608
|
const skills = inspectSkills();
|
|
@@ -490,25 +611,25 @@ async function installSkills(opts, interactive) {
|
|
|
490
611
|
if (!opts.memoryOnly && opts.skills.length) {
|
|
491
612
|
chosenSkills = skills.filter((s2) => opts.skills.includes(s2.name));
|
|
492
613
|
} else if (!opts.memoryOnly && interactive && skills.length > 1) {
|
|
493
|
-
const r = await
|
|
614
|
+
const r = await p3.multiselect({
|
|
494
615
|
message: `Skills for ${agent.label} - all selected; deselect any you don't want`,
|
|
495
616
|
options: skills.map((s2) => {
|
|
496
|
-
const st = skillStatus(
|
|
617
|
+
const st = skillStatus(join6(target.skills, s2.name), s2.meta);
|
|
497
618
|
const prov = s2.meta.provider ? ` ${s2.meta.provider}` : "";
|
|
498
619
|
return { value: s2.name, label: s2.name, hint: `${statusLabel(st)}${prov}` };
|
|
499
620
|
}),
|
|
500
621
|
initialValues: skills.map((s2) => s2.name),
|
|
501
622
|
required: false
|
|
502
623
|
});
|
|
503
|
-
if (
|
|
504
|
-
|
|
624
|
+
if (p3.isCancel(r)) {
|
|
625
|
+
p3.cancel("Aborted.");
|
|
505
626
|
return;
|
|
506
627
|
}
|
|
507
628
|
chosenSkills = skills.filter((s2) => r.includes(s2.name));
|
|
508
629
|
}
|
|
509
630
|
const skillsWithStatus = (opts.memoryOnly ? [] : chosenSkills).map((s2) => ({
|
|
510
631
|
...s2,
|
|
511
|
-
status: skillStatus(
|
|
632
|
+
status: skillStatus(join6(target.skills, s2.name), s2.meta),
|
|
512
633
|
overwrite: true
|
|
513
634
|
}));
|
|
514
635
|
const prune = opts.prune && !opts.memoryOnly ? computePrune(target.skills, skills.map((s2) => s2.name)) : [];
|
|
@@ -518,16 +639,16 @@ async function installSkills(opts, interactive) {
|
|
|
518
639
|
if (tampered.length) {
|
|
519
640
|
if (opts.keepModified) {
|
|
520
641
|
for (const s2 of tampered) s2.overwrite = false;
|
|
521
|
-
|
|
642
|
+
p3.log.warn(`${tampered.length} locally-modified skill(s) will be kept (--keep-modified).`);
|
|
522
643
|
} else if (interactive && !opts.dryRun) {
|
|
523
|
-
const sel = await
|
|
644
|
+
const sel = await p3.multiselect({
|
|
524
645
|
message: `${tampered.length} skill(s) were modified locally since install. Select which to OVERWRITE`,
|
|
525
646
|
options: tampered.map((s2, i) => ({ value: i, label: s2.name, hint: s2.meta.version ? `v${s2.meta.version}` : "modified" })),
|
|
526
647
|
initialValues: tampered.map((_, i) => i),
|
|
527
648
|
required: false
|
|
528
649
|
});
|
|
529
|
-
if (
|
|
530
|
-
|
|
650
|
+
if (p3.isCancel(sel)) {
|
|
651
|
+
p3.cancel("Aborted.");
|
|
531
652
|
return;
|
|
532
653
|
}
|
|
533
654
|
tampered.forEach((s2, i) => {
|
|
@@ -562,7 +683,7 @@ async function installSkills(opts, interactive) {
|
|
|
562
683
|
lines.push(` ${label.padEnd(26)} skill ${s2.name}${prov}`);
|
|
563
684
|
}
|
|
564
685
|
for (const m of x.memory) {
|
|
565
|
-
const ms = memoryStatus(m.src,
|
|
686
|
+
const ms = memoryStatus(m.src, join6(x.target.memory, m.name));
|
|
566
687
|
if (ms === "identical") {
|
|
567
688
|
nSkip++;
|
|
568
689
|
lines.push(` ${"up-to-date (skip)".padEnd(26)} memory ${m.name}`);
|
|
@@ -581,13 +702,14 @@ async function installSkills(opts, interactive) {
|
|
|
581
702
|
}
|
|
582
703
|
}
|
|
583
704
|
if (nInstall + nUpdate + nRemove === 0) {
|
|
584
|
-
|
|
705
|
+
p3.note(lines.join("\n"), "Nothing to do");
|
|
585
706
|
applyClaudeConfig();
|
|
707
|
+
applyBypassConfig();
|
|
586
708
|
await maybeOfferGitHooks(interactive, opts);
|
|
587
|
-
|
|
709
|
+
p3.log.success(`Everything up-to-date - ${nSkip} item(s) unchanged${nKept ? `, ${nKept} kept modified` : ""} (${scope}).`);
|
|
588
710
|
return;
|
|
589
711
|
}
|
|
590
|
-
|
|
712
|
+
p3.note(lines.join("\n"), opts.dryRun ? "Dry run - planned changes" : "Planned changes");
|
|
591
713
|
if (interactive && !opts.dryRun) {
|
|
592
714
|
const summary = [
|
|
593
715
|
nInstall && `${nInstall} to install`,
|
|
@@ -595,63 +717,65 @@ async function installSkills(opts, interactive) {
|
|
|
595
717
|
nRemove && `${nRemove} to remove`,
|
|
596
718
|
nSkip && `${nSkip} unchanged`
|
|
597
719
|
].filter(Boolean).join(", ");
|
|
598
|
-
const ok = await
|
|
599
|
-
if (
|
|
600
|
-
|
|
720
|
+
const ok = await p3.confirm({ message: `Apply: ${summary}?` });
|
|
721
|
+
if (p3.isCancel(ok) || !ok) {
|
|
722
|
+
p3.cancel("Aborted.");
|
|
601
723
|
return;
|
|
602
724
|
}
|
|
603
725
|
}
|
|
604
726
|
if (opts.dryRun) {
|
|
605
|
-
|
|
727
|
+
applyBypassConfig();
|
|
728
|
+
p3.log.info("Dry run complete - no files written.");
|
|
606
729
|
return;
|
|
607
730
|
}
|
|
608
731
|
const changedAgents = plan.filter(
|
|
609
|
-
(x) => x.skills.some(willCopy) || x.memory.some((m) => memoryStatus(m.src,
|
|
732
|
+
(x) => x.skills.some(willCopy) || x.memory.some((m) => memoryStatus(m.src, join6(x.target.memory, m.name)) !== "identical") || x.prune.length > 0
|
|
610
733
|
);
|
|
611
|
-
const s =
|
|
734
|
+
const s = p3.spinner();
|
|
612
735
|
s.start("Installing...");
|
|
613
736
|
let copied = 0;
|
|
614
737
|
try {
|
|
615
738
|
for (const x of plan) {
|
|
616
|
-
|
|
617
|
-
|
|
739
|
+
mkdirSync4(x.target.skills, { recursive: true });
|
|
740
|
+
mkdirSync4(x.target.memory, { recursive: true });
|
|
618
741
|
for (const sk of x.skills) {
|
|
619
742
|
if (!willCopy(sk)) continue;
|
|
620
|
-
cpSync2(sk.src,
|
|
743
|
+
cpSync2(sk.src, join6(x.target.skills, sk.name), { recursive: true, force: true });
|
|
621
744
|
copied++;
|
|
622
745
|
}
|
|
623
746
|
for (const m of x.memory) {
|
|
624
|
-
if (memoryStatus(m.src,
|
|
625
|
-
cpSync2(m.src,
|
|
747
|
+
if (memoryStatus(m.src, join6(x.target.memory, m.name)) === "identical") continue;
|
|
748
|
+
cpSync2(m.src, join6(x.target.memory, m.name), { force: true });
|
|
626
749
|
copied++;
|
|
627
750
|
}
|
|
628
751
|
for (const pr of x.prune) rmSync(pr.dir, { recursive: true, force: true });
|
|
629
752
|
}
|
|
630
753
|
} catch (err) {
|
|
631
754
|
s.stop("Failed.");
|
|
632
|
-
|
|
755
|
+
p3.cancel(`Error while installing: ${err.message}`);
|
|
633
756
|
process.exit(1);
|
|
634
757
|
}
|
|
635
758
|
s.stop(`Wrote ${copied} item(s)${nRemove ? `, removed ${nRemove}` : ""}.`);
|
|
636
759
|
applyClaudeConfig();
|
|
760
|
+
applyBypassConfig();
|
|
637
761
|
await maybeOfferGitHooks(interactive, opts);
|
|
638
|
-
|
|
762
|
+
p3.log.success(`${nInstall} installed, ${nUpdate} updated/overwritten` + (nRemove ? `, ${nRemove} removed` : "") + (nSkip ? `, ${nSkip} unchanged` : "") + (nKept ? `, ${nKept} kept modified` : "") + ` (${scope}).`);
|
|
639
763
|
if (changedAgents.length) {
|
|
640
764
|
const { known, running } = runningStatus(changedAgents.map((x) => x.agent));
|
|
641
765
|
if (running.size) {
|
|
642
766
|
const names = changedAgents.filter((x) => running.has(x.agent.name)).map((x) => x.agent.label);
|
|
643
|
-
|
|
767
|
+
p3.log.warn(`Restart ${names.join(", ")} to apply the changes (running now).`);
|
|
644
768
|
} else if (!known) {
|
|
645
769
|
const names = changedAgents.map((x) => x.agent.label);
|
|
646
|
-
|
|
770
|
+
p3.log.info(`If any of these agents are running, restart them to apply the changes: ${names.join(", ")}.`);
|
|
647
771
|
}
|
|
648
772
|
}
|
|
649
773
|
}
|
|
650
774
|
|
|
651
775
|
// src/guard.ts
|
|
652
|
-
import { readFileSync as
|
|
776
|
+
import { readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
653
777
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
654
|
-
import { basename, extname, dirname as dirname3, join as
|
|
778
|
+
import { basename, extname, dirname as dirname3, join as join7 } from "path";
|
|
655
779
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
656
780
|
var LARGE_FILE_BYTES = 5 * 1024 * 1024;
|
|
657
781
|
var ENV_ALLOWED = /(example|sample|template)/i;
|
|
@@ -712,7 +836,7 @@ var SELF_DIR = dirname3(fileURLToPath3(import.meta.url));
|
|
|
712
836
|
function loadConfig() {
|
|
713
837
|
const defaults = { secrets: true, envFiles: true, depDirs: true, generatedDirs: true, junkFiles: true, largeFiles: true };
|
|
714
838
|
try {
|
|
715
|
-
const raw = JSON.parse(
|
|
839
|
+
const raw = JSON.parse(readFileSync4(join7(SELF_DIR, "enigma-guard.json"), "utf8"));
|
|
716
840
|
return { ...defaults, ...raw };
|
|
717
841
|
} catch {
|
|
718
842
|
return defaults;
|
|
@@ -727,7 +851,7 @@ function scanSecrets(file, blocks) {
|
|
|
727
851
|
if (SECRET_SKIP_EXT.has(extname(file).toLowerCase())) return;
|
|
728
852
|
let text;
|
|
729
853
|
try {
|
|
730
|
-
text =
|
|
854
|
+
text = readFileSync4(file, "utf8");
|
|
731
855
|
} catch {
|
|
732
856
|
return;
|
|
733
857
|
}
|
|
@@ -797,22 +921,22 @@ if (isGuardEntry && fileURLToPath3(import.meta.url) === guardEntry) {
|
|
|
797
921
|
}
|
|
798
922
|
|
|
799
923
|
// src/config.ts
|
|
800
|
-
import { homedir as
|
|
801
|
-
import { join as
|
|
802
|
-
import { existsSync as
|
|
924
|
+
import { homedir as homedir4 } from "os";
|
|
925
|
+
import { join as join8 } from "path";
|
|
926
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
803
927
|
var CONFIG_FILE = ".enigma.json";
|
|
804
928
|
var CONFIG_DEFAULTS = { commitEmoji: true };
|
|
805
929
|
var BOOLEAN_KEYS = ["commitEmoji"];
|
|
806
930
|
var CLI_KEYS = { "commit-emoji": "commitEmoji" };
|
|
807
931
|
function configPath(scope) {
|
|
808
|
-
return scope === "global" ?
|
|
932
|
+
return scope === "global" ? join8(homedir4(), CONFIG_FILE) : join8(process.cwd(), CONFIG_FILE);
|
|
809
933
|
}
|
|
810
934
|
function readConfig() {
|
|
811
935
|
const sources = [];
|
|
812
936
|
let config = { ...CONFIG_DEFAULTS };
|
|
813
937
|
for (const scope of ["global", "local"]) {
|
|
814
938
|
const path = configPath(scope);
|
|
815
|
-
const raw =
|
|
939
|
+
const raw = existsSync6(path) ? readJson(path) : null;
|
|
816
940
|
if (raw) {
|
|
817
941
|
config = { ...config, ...raw };
|
|
818
942
|
sources.push(path);
|
|
@@ -830,9 +954,9 @@ function setValue(scope, key, value) {
|
|
|
830
954
|
const path = configPath(scope);
|
|
831
955
|
const current = readJson(path) || {};
|
|
832
956
|
const next = { ...current, [key]: value };
|
|
833
|
-
const dir =
|
|
834
|
-
if (!isDir(dir))
|
|
835
|
-
|
|
957
|
+
const dir = join8(path, "..");
|
|
958
|
+
if (!isDir(dir)) mkdirSync5(dir, { recursive: true });
|
|
959
|
+
writeFileSync5(path, JSON.stringify(next, null, 2) + "\n");
|
|
836
960
|
return path;
|
|
837
961
|
}
|
|
838
962
|
function runConfigCli(positionals, scope) {
|
|
@@ -868,9 +992,105 @@ From: ${sources.join(", ")}` : "\nFrom: built-in defaults (no .enigma.json found
|
|
|
868
992
|
return 0;
|
|
869
993
|
}
|
|
870
994
|
|
|
995
|
+
// src/update.ts
|
|
996
|
+
import { homedir as homedir5 } from "os";
|
|
997
|
+
import { join as join9 } from "path";
|
|
998
|
+
import { writeFileSync as writeFileSync6 } from "fs";
|
|
999
|
+
import { spawn, spawnSync } from "child_process";
|
|
1000
|
+
import * as p4 from "@clack/prompts";
|
|
1001
|
+
var REGISTRY_URL = "https://registry.npmjs.org/enigma-cli/latest";
|
|
1002
|
+
var UPDATE_COMMAND = "npm i -g enigma-cli@latest";
|
|
1003
|
+
var CACHE_FILE = join9(homedir5(), ".enigma-update-check.json");
|
|
1004
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1005
|
+
var CHILD_SCRIPT = [
|
|
1006
|
+
"const fs = require('fs');",
|
|
1007
|
+
"const ctrl = new AbortController();",
|
|
1008
|
+
"const t = setTimeout(() => ctrl.abort(), 5000);",
|
|
1009
|
+
"fetch(process.env.E_URL, { signal: ctrl.signal, headers: { 'user-agent': 'enigma-cli-update-check' } })",
|
|
1010
|
+
" .then((r) => (r.ok ? r.json() : null))",
|
|
1011
|
+
" .then((d) => { if (d && d.version) fs.writeFileSync(process.env.E_FILE, JSON.stringify({ latest: d.version, checkedAt: Date.now() })); })",
|
|
1012
|
+
" .catch(() => {})",
|
|
1013
|
+
" .finally(() => clearTimeout(t));"
|
|
1014
|
+
].join("\n");
|
|
1015
|
+
function parseVersion(version) {
|
|
1016
|
+
const core = String(version).trim().replace(/^v/, "").split("-")[0];
|
|
1017
|
+
const [major, minor, patch] = core.split(".").map((n) => parseInt(n, 10) || 0);
|
|
1018
|
+
return [major || 0, minor || 0, patch || 0];
|
|
1019
|
+
}
|
|
1020
|
+
function isNewer(latest, current) {
|
|
1021
|
+
const a = parseVersion(latest);
|
|
1022
|
+
const b = parseVersion(current);
|
|
1023
|
+
for (let i = 0; i < 3; i++) {
|
|
1024
|
+
if (a[i] > b[i]) return true;
|
|
1025
|
+
if (a[i] < b[i]) return false;
|
|
1026
|
+
}
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
function paint(text, code) {
|
|
1030
|
+
return process.stdout.isTTY && !process.env.NO_COLOR ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
1031
|
+
}
|
|
1032
|
+
function renderUpdateBox(current, latest) {
|
|
1033
|
+
const lines = [
|
|
1034
|
+
{ text: `Update available ${current} -> ${latest}`, color: "1;33" },
|
|
1035
|
+
{ text: `Run ${UPDATE_COMMAND} to update`, color: "36" }
|
|
1036
|
+
];
|
|
1037
|
+
const width = Math.max(...lines.map((l) => l.text.length));
|
|
1038
|
+
const border = `+${"-".repeat(width + 4)}+`;
|
|
1039
|
+
const blank = `|${" ".repeat(width + 4)}|`;
|
|
1040
|
+
const rows = lines.map((l) => `|${paint(` ${l.text}${" ".repeat(width - l.text.length)} `, l.color)}|`);
|
|
1041
|
+
return [border, blank, ...rows, blank, border].join("\n");
|
|
1042
|
+
}
|
|
1043
|
+
function readCache() {
|
|
1044
|
+
return readJson(CACHE_FILE);
|
|
1045
|
+
}
|
|
1046
|
+
function scheduleUpdateCheck() {
|
|
1047
|
+
try {
|
|
1048
|
+
const cache = readCache();
|
|
1049
|
+
if (cache && Date.now() - cache.checkedAt < CHECK_INTERVAL_MS) return;
|
|
1050
|
+
writeFileSync6(CACHE_FILE, JSON.stringify({ latest: cache?.latest ?? null, checkedAt: Date.now() }));
|
|
1051
|
+
const child = spawn(process.execPath, ["-e", CHILD_SCRIPT], {
|
|
1052
|
+
detached: true,
|
|
1053
|
+
stdio: "ignore",
|
|
1054
|
+
windowsHide: true,
|
|
1055
|
+
env: { ...process.env, E_URL: REGISTRY_URL, E_FILE: CACHE_FILE }
|
|
1056
|
+
});
|
|
1057
|
+
child.unref();
|
|
1058
|
+
} catch {
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
function runUpdate() {
|
|
1062
|
+
try {
|
|
1063
|
+
const result = spawnSync("npm", ["i", "-g", "enigma-cli@latest"], {
|
|
1064
|
+
stdio: "inherit",
|
|
1065
|
+
shell: process.platform === "win32"
|
|
1066
|
+
});
|
|
1067
|
+
if (result.status === 0) console.log("Updated. Re-run your command to use the new version.");
|
|
1068
|
+
else console.log(`Update did not complete. Run '${UPDATE_COMMAND}' manually.`);
|
|
1069
|
+
} catch {
|
|
1070
|
+
console.log(`Could not run the update. Run '${UPDATE_COMMAND}' manually.`);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
async function notifyUpdate(current, interactive) {
|
|
1074
|
+
if (!process.stdout.isTTY || process.env.CI) return;
|
|
1075
|
+
try {
|
|
1076
|
+
scheduleUpdateCheck();
|
|
1077
|
+
const cache = readCache();
|
|
1078
|
+
const latest = cache?.latest ? String(cache.latest).replace(/[^\w.+-]/g, "") : "";
|
|
1079
|
+
if (!latest || !isNewer(latest, current)) return;
|
|
1080
|
+
process.stdout.write(`
|
|
1081
|
+
${renderUpdateBox(current, latest)}
|
|
1082
|
+
`);
|
|
1083
|
+
if (!interactive) return;
|
|
1084
|
+
const ok = await p4.confirm({ message: `Update now with ${UPDATE_COMMAND}?`, initialValue: true });
|
|
1085
|
+
if (p4.isCancel(ok) || !ok) return;
|
|
1086
|
+
runUpdate();
|
|
1087
|
+
} catch {
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
|
|
871
1091
|
// src/cli.ts
|
|
872
1092
|
var __dirname3 = dirname4(fileURLToPath4(import.meta.url));
|
|
873
|
-
var PKG = readJson(
|
|
1093
|
+
var PKG = readJson(join10(__dirname3, "..", "package.json")) || {};
|
|
874
1094
|
var COMMANDS = /* @__PURE__ */ new Set(["install", "security", "guard", "seal", "check", "config", "help", "version"]);
|
|
875
1095
|
function parseArgs(argv) {
|
|
876
1096
|
const opts = {
|
|
@@ -884,6 +1104,8 @@ function parseArgs(argv) {
|
|
|
884
1104
|
memoryOnly: false,
|
|
885
1105
|
prune: true,
|
|
886
1106
|
keepModified: false,
|
|
1107
|
+
bypass: null,
|
|
1108
|
+
noBypass: false,
|
|
887
1109
|
force: false,
|
|
888
1110
|
all: false,
|
|
889
1111
|
yes: false,
|
|
@@ -931,6 +1153,12 @@ function parseArgs(argv) {
|
|
|
931
1153
|
case "--keep-modified":
|
|
932
1154
|
opts.keepModified = true;
|
|
933
1155
|
break;
|
|
1156
|
+
case "--bypass":
|
|
1157
|
+
opts.bypass = (opts.bypass || []).concat(next().split(","));
|
|
1158
|
+
break;
|
|
1159
|
+
case "--no-bypass":
|
|
1160
|
+
opts.noBypass = true;
|
|
1161
|
+
break;
|
|
934
1162
|
case "--force":
|
|
935
1163
|
opts.force = true;
|
|
936
1164
|
break;
|
|
@@ -988,6 +1216,8 @@ Install options:
|
|
|
988
1216
|
--all Target every supported agent, ignoring detection
|
|
989
1217
|
--skills-only Only skill folders --memory-only Only memory files
|
|
990
1218
|
--no-prune Keep orphaned skills --keep-modified Don't overwrite local edits
|
|
1219
|
+
--bypass <names> Disable approval prompts for agents (claude,codex,opencode | all | none)
|
|
1220
|
+
--no-bypass Never configure permission bypass (skip the prompt)
|
|
991
1221
|
--dry-run Show the plan without writing
|
|
992
1222
|
|
|
993
1223
|
Security options:
|
|
@@ -1000,6 +1230,7 @@ Examples:
|
|
|
1000
1230
|
enigma # interactive
|
|
1001
1231
|
enigma install --global # skills for detected agents, user level
|
|
1002
1232
|
enigma install --all -y # every supported agent, non-interactive
|
|
1233
|
+
enigma install -y --bypass claude,codex # also disable approval prompts (unattended)
|
|
1003
1234
|
enigma security # configure git hooks (choose protections)
|
|
1004
1235
|
enigma config # show effective runtime config
|
|
1005
1236
|
enigma config commit-emoji off # opt out of commit-message emojis (global)
|
|
@@ -1007,12 +1238,16 @@ Examples:
|
|
|
1007
1238
|
}
|
|
1008
1239
|
async function run(argv) {
|
|
1009
1240
|
const opts = parseArgs(argv);
|
|
1241
|
+
const interactive = Boolean(process.stdout.isTTY) && !opts.yes;
|
|
1242
|
+
const version = PKG.version || "0.0.0";
|
|
1010
1243
|
if (opts.help || opts.command === "help") {
|
|
1011
1244
|
printHelp();
|
|
1245
|
+
await notifyUpdate(version, interactive);
|
|
1012
1246
|
return;
|
|
1013
1247
|
}
|
|
1014
1248
|
if (opts.version || opts.command === "version") {
|
|
1015
|
-
console.log(
|
|
1249
|
+
console.log(version);
|
|
1250
|
+
await notifyUpdate(version, interactive);
|
|
1016
1251
|
return;
|
|
1017
1252
|
}
|
|
1018
1253
|
if (opts.command === "seal") return sealSources();
|
|
@@ -1023,23 +1258,24 @@ async function run(argv) {
|
|
|
1023
1258
|
if (opts.command === "config") {
|
|
1024
1259
|
process.exit(runConfigCli(opts.positionals, opts.scope));
|
|
1025
1260
|
}
|
|
1026
|
-
const interactive = Boolean(process.stdout.isTTY) && !opts.yes;
|
|
1027
1261
|
if (opts.command === "install") {
|
|
1028
|
-
|
|
1262
|
+
p5.intro("enigma - install agent skills");
|
|
1029
1263
|
await installSkills(opts, interactive);
|
|
1030
|
-
|
|
1264
|
+
p5.outro("Done.");
|
|
1265
|
+
await notifyUpdate(version, interactive);
|
|
1031
1266
|
return;
|
|
1032
1267
|
}
|
|
1033
1268
|
if (opts.command === "security") {
|
|
1034
|
-
|
|
1269
|
+
p5.intro("enigma - git security hooks");
|
|
1035
1270
|
const done = await setupGitHooks(opts, interactive);
|
|
1036
|
-
|
|
1271
|
+
p5.outro(done ? "Git hooks configured." : "No changes made.");
|
|
1272
|
+
await notifyUpdate(version, interactive);
|
|
1037
1273
|
return;
|
|
1038
1274
|
}
|
|
1039
|
-
|
|
1275
|
+
p5.intro("enigma");
|
|
1040
1276
|
let features;
|
|
1041
1277
|
if (interactive) {
|
|
1042
|
-
const r = await
|
|
1278
|
+
const r = await p5.multiselect({
|
|
1043
1279
|
message: "What do you want to set up?",
|
|
1044
1280
|
options: [
|
|
1045
1281
|
{ value: "skills", label: "Agent skills", hint: "Claude Code, Codex, opencode" },
|
|
@@ -1048,8 +1284,8 @@ async function run(argv) {
|
|
|
1048
1284
|
initialValues: ["skills"],
|
|
1049
1285
|
required: true
|
|
1050
1286
|
});
|
|
1051
|
-
if (
|
|
1052
|
-
|
|
1287
|
+
if (p5.isCancel(r)) {
|
|
1288
|
+
p5.cancel("Aborted.");
|
|
1053
1289
|
return;
|
|
1054
1290
|
}
|
|
1055
1291
|
features = r;
|
|
@@ -1058,7 +1294,8 @@ async function run(argv) {
|
|
|
1058
1294
|
}
|
|
1059
1295
|
if (features.includes("skills")) await installSkills(opts, interactive);
|
|
1060
1296
|
if (features.includes("security")) await setupGitHooks(opts, interactive);
|
|
1061
|
-
|
|
1297
|
+
p5.outro("Done.");
|
|
1298
|
+
await notifyUpdate(version, interactive);
|
|
1062
1299
|
}
|
|
1063
1300
|
|
|
1064
1301
|
// src/bin/enigma.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "enigma-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Everything you need to work with a coding agent: install shared policy skills for Claude Code, OpenAI Codex and opencode, and set up portable git security hooks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|