enigma-cli 1.1.0 → 1.1.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/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 +432 -60
- 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.1.
|
|
6
|
+
"cliVersion": "1.1.2",
|
|
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.1.
|
|
6
|
+
"cliVersion": "1.1.2",
|
|
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.1.
|
|
6
|
+
"cliVersion": "1.1.2",
|
|
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.1.
|
|
6
|
+
"cliVersion": "1.1.2",
|
|
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.1.
|
|
6
|
+
"cliVersion": "1.1.2",
|
|
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.1.
|
|
6
|
+
"cliVersion": "1.1.2",
|
|
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.1.
|
|
6
|
+
"cliVersion": "1.1.2",
|
|
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.1.
|
|
6
|
+
"cliVersion": "1.1.2",
|
|
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.1.
|
|
6
|
+
"cliVersion": "1.1.2",
|
|
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.1.
|
|
6
|
+
"cliVersion": "1.1.2",
|
|
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.1.
|
|
6
|
+
"cliVersion": "1.1.2",
|
|
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 p6 from "@clack/prompts";
|
|
7
7
|
|
|
8
8
|
// src/util.ts
|
|
9
9
|
import { existsSync, statSync, readFileSync } from "fs";
|
|
@@ -257,6 +257,54 @@ function disableClaudeAttribution(scope) {
|
|
|
257
257
|
writeFileSync2(path, JSON.stringify(next, null, 2) + "\n");
|
|
258
258
|
return true;
|
|
259
259
|
}
|
|
260
|
+
function getClaudeAttribution(scope) {
|
|
261
|
+
const current = readJson(claudeSettingsPath(scope)) || {};
|
|
262
|
+
const attribution = current.attribution;
|
|
263
|
+
const disabled = Boolean(attribution) && attribution.commit === "" && attribution.pr === "" && current.includeCoAuthoredBy === false;
|
|
264
|
+
return !disabled;
|
|
265
|
+
}
|
|
266
|
+
function setClaudeAttribution(scope, enabled) {
|
|
267
|
+
if (!enabled) return disableClaudeAttribution(scope);
|
|
268
|
+
const path = claudeSettingsPath(scope);
|
|
269
|
+
const current = readJson(path) || {};
|
|
270
|
+
const attribution = typeof current.attribution === "object" && current.attribution !== null ? { ...current.attribution } : {};
|
|
271
|
+
let changed = false;
|
|
272
|
+
if (attribution.commit === "") {
|
|
273
|
+
delete attribution.commit;
|
|
274
|
+
changed = true;
|
|
275
|
+
}
|
|
276
|
+
if (attribution.pr === "") {
|
|
277
|
+
delete attribution.pr;
|
|
278
|
+
changed = true;
|
|
279
|
+
}
|
|
280
|
+
if (current.includeCoAuthoredBy === false) changed = true;
|
|
281
|
+
if (!changed) return false;
|
|
282
|
+
const next = { ...current };
|
|
283
|
+
if (Object.keys(attribution).length) next.attribution = attribution;
|
|
284
|
+
else delete next.attribution;
|
|
285
|
+
delete next.includeCoAuthoredBy;
|
|
286
|
+
writeClaudeSettings(path, next);
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
function getClaudeBypass(scope) {
|
|
290
|
+
const current = readJson(claudeSettingsPath(scope)) || {};
|
|
291
|
+
const permissions = current.permissions;
|
|
292
|
+
return Boolean(permissions) && permissions.defaultMode === "bypassPermissions";
|
|
293
|
+
}
|
|
294
|
+
function setClaudeBypass(scope, on, dryRun) {
|
|
295
|
+
if (on) return enableClaudeBypass(scope, dryRun);
|
|
296
|
+
const path = claudeSettingsPath(scope);
|
|
297
|
+
const current = readJson(path) || {};
|
|
298
|
+
const permissions = typeof current.permissions === "object" && current.permissions !== null ? { ...current.permissions } : {};
|
|
299
|
+
if (permissions.defaultMode !== "bypassPermissions") return { path, changed: false };
|
|
300
|
+
if (dryRun) return { path, changed: true };
|
|
301
|
+
delete permissions.defaultMode;
|
|
302
|
+
const next = { ...current };
|
|
303
|
+
if (Object.keys(permissions).length) next.permissions = permissions;
|
|
304
|
+
else delete next.permissions;
|
|
305
|
+
writeClaudeSettings(path, next);
|
|
306
|
+
return { path, changed: true };
|
|
307
|
+
}
|
|
260
308
|
function enableClaudeBypass(scope, dryRun) {
|
|
261
309
|
const path = claudeSettingsPath(scope);
|
|
262
310
|
const current = readJson(path) || {};
|
|
@@ -264,10 +312,13 @@ function enableClaudeBypass(scope, dryRun) {
|
|
|
264
312
|
if (permissions.defaultMode === "bypassPermissions") return { path, changed: false };
|
|
265
313
|
if (dryRun) return { path, changed: true };
|
|
266
314
|
const next = { ...current, permissions: { ...permissions, defaultMode: "bypassPermissions" } };
|
|
315
|
+
writeClaudeSettings(path, next);
|
|
316
|
+
return { path, changed: true };
|
|
317
|
+
}
|
|
318
|
+
function writeClaudeSettings(path, data) {
|
|
267
319
|
const dir = join4(path, "..");
|
|
268
320
|
if (!isDir(dir)) mkdirSync2(dir, { recursive: true });
|
|
269
|
-
writeFileSync2(path, JSON.stringify(
|
|
270
|
-
return { path, changed: true };
|
|
321
|
+
writeFileSync2(path, JSON.stringify(data, null, 2) + "\n");
|
|
271
322
|
}
|
|
272
323
|
|
|
273
324
|
// src/permissions.ts
|
|
@@ -323,6 +374,74 @@ function enableFor(name, scope, dryRun) {
|
|
|
323
374
|
return null;
|
|
324
375
|
}
|
|
325
376
|
}
|
|
377
|
+
function getBypass(name, scope) {
|
|
378
|
+
switch (name) {
|
|
379
|
+
case "claude":
|
|
380
|
+
return getClaudeBypass(scope);
|
|
381
|
+
case "codex":
|
|
382
|
+
return getCodexBypass();
|
|
383
|
+
case "opencode":
|
|
384
|
+
return getOpencodeBypass(scope);
|
|
385
|
+
default:
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function setBypass(name, scope, on, dryRun) {
|
|
390
|
+
switch (name) {
|
|
391
|
+
case "claude":
|
|
392
|
+
return setClaudeBypass(scope, on, dryRun);
|
|
393
|
+
case "codex":
|
|
394
|
+
return on ? enableCodexBypass(dryRun) : disableCodexBypass(dryRun);
|
|
395
|
+
case "opencode":
|
|
396
|
+
return on ? enableOpencodeBypass(scope, dryRun) : disableOpencodeBypass(scope, dryRun);
|
|
397
|
+
default:
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
function getCodexBypass() {
|
|
402
|
+
const path = join5(homedir3(), ".codex", "config.toml");
|
|
403
|
+
const content = existsSync4(path) ? readFileSync2(path, "utf8") : "";
|
|
404
|
+
return getTomlTopLevelKey(content, "approval_policy") === '"never"';
|
|
405
|
+
}
|
|
406
|
+
function disableCodexBypass(dryRun) {
|
|
407
|
+
const path = join5(homedir3(), ".codex", "config.toml");
|
|
408
|
+
const before = existsSync4(path) ? readFileSync2(path, "utf8") : "";
|
|
409
|
+
let after = removeTomlTopLevelKey(before, "approval_policy");
|
|
410
|
+
after = removeTomlTopLevelKey(after, "sandbox_mode");
|
|
411
|
+
const changed = after !== before;
|
|
412
|
+
if (changed && !dryRun) writeFileSync3(path, after);
|
|
413
|
+
return { path, changed };
|
|
414
|
+
}
|
|
415
|
+
function getOpencodeBypass(scope) {
|
|
416
|
+
const path = opencodeConfigPath(scope);
|
|
417
|
+
const perm = (readJson(path) || {}).permission;
|
|
418
|
+
return perm === "allow" || typeof perm === "object" && perm !== null && perm["*"] === "allow";
|
|
419
|
+
}
|
|
420
|
+
function disableOpencodeBypass(scope, dryRun) {
|
|
421
|
+
const path = opencodeConfigPath(scope);
|
|
422
|
+
const current = readJson(path) || {};
|
|
423
|
+
const perm = current.permission;
|
|
424
|
+
if (!getOpencodeBypass(scope)) return { path, changed: false };
|
|
425
|
+
if (dryRun) return { path, changed: true };
|
|
426
|
+
const next = { ...current };
|
|
427
|
+
if (typeof perm === "object" && perm !== null) {
|
|
428
|
+
const rest = {};
|
|
429
|
+
for (const k of Object.keys(perm)) {
|
|
430
|
+
if (k !== "*") rest[k] = perm[k];
|
|
431
|
+
}
|
|
432
|
+
if (Object.keys(rest).length) next.permission = rest;
|
|
433
|
+
else delete next.permission;
|
|
434
|
+
} else {
|
|
435
|
+
delete next.permission;
|
|
436
|
+
}
|
|
437
|
+
const dir = join5(path, "..");
|
|
438
|
+
if (!isDir(dir)) mkdirSync3(dir, { recursive: true });
|
|
439
|
+
writeFileSync3(path, JSON.stringify(next, null, 2) + "\n");
|
|
440
|
+
return { path, changed: true };
|
|
441
|
+
}
|
|
442
|
+
function opencodeConfigPath(scope) {
|
|
443
|
+
return scope === "global" ? join5(homedir3(), ".config", "opencode", "opencode.json") : join5(process.cwd(), "opencode.json");
|
|
444
|
+
}
|
|
326
445
|
function enableCodexBypass(dryRun) {
|
|
327
446
|
const path = join5(homedir3(), ".codex", "config.toml");
|
|
328
447
|
const before = existsSync4(path) ? readFileSync2(path, "utf8") : "";
|
|
@@ -337,7 +456,7 @@ function enableCodexBypass(dryRun) {
|
|
|
337
456
|
return { path, changed };
|
|
338
457
|
}
|
|
339
458
|
function enableOpencodeBypass(scope, dryRun) {
|
|
340
|
-
const path = scope
|
|
459
|
+
const path = opencodeConfigPath(scope);
|
|
341
460
|
const current = readJson(path) || {};
|
|
342
461
|
const perm = current.permission;
|
|
343
462
|
const alreadyAllowAll = perm === "allow" || typeof perm === "object" && perm !== null && perm["*"] === "allow";
|
|
@@ -372,6 +491,27 @@ function setTomlTopLevelKey(content, key, tomlValue) {
|
|
|
372
491
|
lines.splice(insertAt, 0, ...followsTable ? [assign, ""] : [assign]);
|
|
373
492
|
return normalizeTrailingNewline(lines.join("\n"));
|
|
374
493
|
}
|
|
494
|
+
function getTomlTopLevelKey(content, key) {
|
|
495
|
+
const lines = content.split("\n");
|
|
496
|
+
const firstTable = lines.findIndex((l) => /^\s*\[/.test(l));
|
|
497
|
+
const scanEnd = firstTable === -1 ? lines.length : firstTable;
|
|
498
|
+
const keyRe = new RegExp(`^\\s*${key}\\s*=\\s*(.+?)\\s*$`);
|
|
499
|
+
for (let i = 0; i < scanEnd; i++) {
|
|
500
|
+
const m = keyRe.exec(lines[i]);
|
|
501
|
+
if (m) return m[1];
|
|
502
|
+
}
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
function removeTomlTopLevelKey(content, key) {
|
|
506
|
+
if (content.trim() === "") return content;
|
|
507
|
+
const lines = content.split("\n");
|
|
508
|
+
const firstTable = lines.findIndex((l) => /^\s*\[/.test(l));
|
|
509
|
+
const scanEnd = firstTable === -1 ? lines.length : firstTable;
|
|
510
|
+
const keyRe = new RegExp(`^\\s*${key}\\s*=`);
|
|
511
|
+
const kept = lines.filter((l, i) => !(i < scanEnd && keyRe.test(l)));
|
|
512
|
+
if (kept.length === lines.length) return content;
|
|
513
|
+
return normalizeTrailingNewline(kept.join("\n"));
|
|
514
|
+
}
|
|
375
515
|
function normalizeTrailingNewline(s) {
|
|
376
516
|
return `${s.replace(/\s+$/, "")}
|
|
377
517
|
`;
|
|
@@ -920,14 +1060,15 @@ if (isGuardEntry && fileURLToPath3(import.meta.url) === guardEntry) {
|
|
|
920
1060
|
process.exit(runGuardCli(process.argv.includes("--all")));
|
|
921
1061
|
}
|
|
922
1062
|
|
|
1063
|
+
// src/settings.ts
|
|
1064
|
+
import * as p4 from "@clack/prompts";
|
|
1065
|
+
|
|
923
1066
|
// src/config.ts
|
|
924
1067
|
import { homedir as homedir4 } from "os";
|
|
925
1068
|
import { join as join8 } from "path";
|
|
926
1069
|
import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
927
1070
|
var CONFIG_FILE = ".enigma.json";
|
|
928
|
-
var CONFIG_DEFAULTS = { commitEmoji: true };
|
|
929
|
-
var BOOLEAN_KEYS = ["commitEmoji"];
|
|
930
|
-
var CLI_KEYS = { "commit-emoji": "commitEmoji" };
|
|
1071
|
+
var CONFIG_DEFAULTS = { commitEmoji: true, updateNotifier: true };
|
|
931
1072
|
function configPath(scope) {
|
|
932
1073
|
return scope === "global" ? join8(homedir4(), CONFIG_FILE) : join8(process.cwd(), CONFIG_FILE);
|
|
933
1074
|
}
|
|
@@ -944,13 +1085,7 @@ function readConfig() {
|
|
|
944
1085
|
}
|
|
945
1086
|
return { config, sources };
|
|
946
1087
|
}
|
|
947
|
-
function
|
|
948
|
-
const v = value.toLowerCase();
|
|
949
|
-
if (["on", "true", "yes", "1", "enable", "enabled"].includes(v)) return true;
|
|
950
|
-
if (["off", "false", "no", "0", "disable", "disabled"].includes(v)) return false;
|
|
951
|
-
return null;
|
|
952
|
-
}
|
|
953
|
-
function setValue(scope, key, value) {
|
|
1088
|
+
function setEnigmaToggle(key, value, scope) {
|
|
954
1089
|
const path = configPath(scope);
|
|
955
1090
|
const current = readJson(path) || {};
|
|
956
1091
|
const next = { ...current, [key]: value };
|
|
@@ -959,26 +1094,156 @@ function setValue(scope, key, value) {
|
|
|
959
1094
|
writeFileSync5(path, JSON.stringify(next, null, 2) + "\n");
|
|
960
1095
|
return path;
|
|
961
1096
|
}
|
|
962
|
-
|
|
1097
|
+
|
|
1098
|
+
// src/settings.ts
|
|
1099
|
+
function enigmaToggle(key, field, label, hint) {
|
|
1100
|
+
return {
|
|
1101
|
+
key,
|
|
1102
|
+
label,
|
|
1103
|
+
hint,
|
|
1104
|
+
read: () => readConfig().config[field],
|
|
1105
|
+
write: (value, scope) => ({ path: setEnigmaToggle(field, value, scope), changed: true })
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
var CATEGORIES = [
|
|
1109
|
+
{
|
|
1110
|
+
title: "General",
|
|
1111
|
+
blurb: "enigma runtime toggles (.enigma.json)",
|
|
1112
|
+
settings: [
|
|
1113
|
+
enigmaToggle("commit-emoji", "commitEmoji", "Commit subject emoji", "leading gitmoji on commit subjects"),
|
|
1114
|
+
enigmaToggle("update-notifier", "updateNotifier", "Update notifications", "notify when a newer enigma-cli is published")
|
|
1115
|
+
]
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
title: "Git & attribution",
|
|
1119
|
+
blurb: "how the coding agent attributes its work in git",
|
|
1120
|
+
settings: [
|
|
1121
|
+
{
|
|
1122
|
+
key: "claude-attribution",
|
|
1123
|
+
label: "Claude commit attribution",
|
|
1124
|
+
hint: "let Claude Code commit as its own contributor (Co-Authored-By / PR footer); enigma default: off",
|
|
1125
|
+
read: (scope) => getClaudeAttribution(scope),
|
|
1126
|
+
write: (value, scope) => ({ changed: setClaudeAttribution(scope, value) })
|
|
1127
|
+
}
|
|
1128
|
+
]
|
|
1129
|
+
},
|
|
1130
|
+
{
|
|
1131
|
+
title: "Permissions",
|
|
1132
|
+
blurb: "approval-prompt bypass per agent (security trade-off)",
|
|
1133
|
+
settings: BYPASS_SUPPORTED.map((name) => ({
|
|
1134
|
+
key: `bypass-${name}`,
|
|
1135
|
+
label: `${AGENTS[name]?.label || name} approval bypass`,
|
|
1136
|
+
hint: name === "codex" ? "skip approval prompts (global ~/.codex only)" : "skip per-action approval prompts",
|
|
1137
|
+
globalOnly: name === "codex",
|
|
1138
|
+
read: (scope) => getBypass(name, scope),
|
|
1139
|
+
write: (value, scope) => setBypass(name, scope, value, false) || { changed: false }
|
|
1140
|
+
}))
|
|
1141
|
+
}
|
|
1142
|
+
];
|
|
1143
|
+
var ALL_SETTINGS = CATEGORIES.flatMap((c) => c.settings);
|
|
1144
|
+
function valueLabel(on) {
|
|
1145
|
+
return on ? "on" : "off";
|
|
1146
|
+
}
|
|
1147
|
+
function parseBool(value) {
|
|
1148
|
+
const v = value.toLowerCase();
|
|
1149
|
+
if (["on", "true", "yes", "1", "enable", "enabled"].includes(v)) return true;
|
|
1150
|
+
if (["off", "false", "no", "0", "disable", "disabled"].includes(v)) return false;
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
async function runSettingsMenu() {
|
|
1154
|
+
for (; ; ) {
|
|
1155
|
+
const choice = await p4.select({
|
|
1156
|
+
message: "Settings - choose a category",
|
|
1157
|
+
options: [
|
|
1158
|
+
...CATEGORIES.map((c) => ({ value: c.title, label: c.title, hint: c.blurb })),
|
|
1159
|
+
{ value: "__back", label: "< Back" }
|
|
1160
|
+
]
|
|
1161
|
+
});
|
|
1162
|
+
if (p4.isCancel(choice) || choice === "__back") return;
|
|
1163
|
+
await runCategory(CATEGORIES.find((c) => c.title === choice));
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
async function runCategory(category) {
|
|
1167
|
+
for (; ; ) {
|
|
1168
|
+
const choice = await p4.select({
|
|
1169
|
+
message: `${category.title} - ${category.blurb}`,
|
|
1170
|
+
options: [
|
|
1171
|
+
...category.settings.map((s) => ({
|
|
1172
|
+
value: s.key,
|
|
1173
|
+
label: `${s.label}: ${valueLabel(s.read("global"))}`,
|
|
1174
|
+
hint: s.hint
|
|
1175
|
+
})),
|
|
1176
|
+
{ value: "__back", label: "< Back" }
|
|
1177
|
+
]
|
|
1178
|
+
});
|
|
1179
|
+
if (p4.isCancel(choice) || choice === "__back") return;
|
|
1180
|
+
await editSetting(category.settings.find((s) => s.key === choice));
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
async function editSetting(setting) {
|
|
1184
|
+
let scope = "global";
|
|
1185
|
+
if (!setting.globalOnly) {
|
|
1186
|
+
const picked2 = await p4.select({
|
|
1187
|
+
message: `Apply "${setting.label}" to which scope?`,
|
|
1188
|
+
options: [
|
|
1189
|
+
{ value: "global", label: "Global", hint: "all projects (~)" },
|
|
1190
|
+
{ value: "local", label: "This project", hint: "current directory" }
|
|
1191
|
+
],
|
|
1192
|
+
initialValue: "global"
|
|
1193
|
+
});
|
|
1194
|
+
if (p4.isCancel(picked2)) return;
|
|
1195
|
+
scope = picked2;
|
|
1196
|
+
}
|
|
1197
|
+
const current = setting.read(scope);
|
|
1198
|
+
const picked = await p4.select({
|
|
1199
|
+
message: `${setting.label} (${scope})`,
|
|
1200
|
+
options: [{ value: "on", label: "On" }, { value: "off", label: "Off" }],
|
|
1201
|
+
initialValue: current ? "on" : "off"
|
|
1202
|
+
});
|
|
1203
|
+
if (p4.isCancel(picked)) return;
|
|
1204
|
+
const value = picked === "on";
|
|
1205
|
+
if (value === current) {
|
|
1206
|
+
p4.log.info(`${setting.label}: unchanged (${valueLabel(current)}).`);
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
const result = setting.write(value, scope);
|
|
1210
|
+
if (result.changed) {
|
|
1211
|
+
const where = result.path ? ` -> ${result.path}` : "";
|
|
1212
|
+
p4.log.success(`${setting.label}: ${valueLabel(value)} (${scope})${where}.`);
|
|
1213
|
+
} else {
|
|
1214
|
+
p4.log.info(`${setting.label}: already ${valueLabel(value)} (${scope}).`);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
function printEffective() {
|
|
1218
|
+
console.log("Effective enigma settings:\n");
|
|
1219
|
+
for (const category of CATEGORIES) {
|
|
1220
|
+
console.log(`${category.title}:`);
|
|
1221
|
+
for (const s of category.settings) console.log(` ${s.key}: ${valueLabel(s.read("global"))}`);
|
|
1222
|
+
console.log("");
|
|
1223
|
+
}
|
|
1224
|
+
const { sources } = readConfig();
|
|
1225
|
+
console.log(sources.length ? `.enigma.json sources: ${sources.join(", ")}` : ".enigma.json: built-in defaults (no file found)");
|
|
1226
|
+
console.log("Agent settings (attribution, bypass) reflect each agent's own config at the global scope.");
|
|
1227
|
+
}
|
|
1228
|
+
async function runConfigCli(positionals, scope, interactive) {
|
|
963
1229
|
const [rawKey, rawValue] = positionals;
|
|
964
1230
|
if (!rawKey) {
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1231
|
+
if (interactive) {
|
|
1232
|
+
p4.intro("enigma config");
|
|
1233
|
+
await runSettingsMenu();
|
|
1234
|
+
p4.outro("Done.");
|
|
1235
|
+
} else {
|
|
1236
|
+
printEffective();
|
|
970
1237
|
}
|
|
971
|
-
console.log(sources.length ? `
|
|
972
|
-
From: ${sources.join(", ")}` : "\nFrom: built-in defaults (no .enigma.json found)");
|
|
973
1238
|
return 0;
|
|
974
1239
|
}
|
|
975
|
-
const
|
|
976
|
-
if (!
|
|
977
|
-
console.error(`Unknown config key: ${rawKey}. Known keys: ${
|
|
1240
|
+
const setting = ALL_SETTINGS.find((s) => s.key === rawKey);
|
|
1241
|
+
if (!setting) {
|
|
1242
|
+
console.error(`Unknown config key: ${rawKey}. Known keys: ${ALL_SETTINGS.map((s) => s.key).join(", ")}.`);
|
|
978
1243
|
return 1;
|
|
979
1244
|
}
|
|
980
1245
|
if (rawValue === void 0) {
|
|
981
|
-
console.error(`Missing value for '${rawKey}'. Usage: enigma config ${rawKey} <on|off
|
|
1246
|
+
console.error(`Missing value for '${rawKey}'. Usage: enigma config ${rawKey} <on|off> [-g|-l]`);
|
|
982
1247
|
return 1;
|
|
983
1248
|
}
|
|
984
1249
|
const value = parseBool(rawValue);
|
|
@@ -986,15 +1251,113 @@ From: ${sources.join(", ")}` : "\nFrom: built-in defaults (no .enigma.json found
|
|
|
986
1251
|
console.error(`Invalid value '${rawValue}' for '${rawKey}'. Use on or off.`);
|
|
987
1252
|
return 1;
|
|
988
1253
|
}
|
|
989
|
-
const target = scope || "global";
|
|
990
|
-
const
|
|
991
|
-
|
|
1254
|
+
const target = setting.globalOnly ? "global" : scope || "global";
|
|
1255
|
+
const result = setting.write(value, target);
|
|
1256
|
+
const where = result.path ? ` in ${result.path}` : "";
|
|
1257
|
+
console.log(`Set ${rawKey} = ${valueLabel(value)} (${target})${where}.`);
|
|
992
1258
|
return 0;
|
|
993
1259
|
}
|
|
994
1260
|
|
|
1261
|
+
// src/update.ts
|
|
1262
|
+
import { homedir as homedir5 } from "os";
|
|
1263
|
+
import { join as join9 } from "path";
|
|
1264
|
+
import { writeFileSync as writeFileSync6 } from "fs";
|
|
1265
|
+
import { spawn, spawnSync } from "child_process";
|
|
1266
|
+
import * as p5 from "@clack/prompts";
|
|
1267
|
+
var REGISTRY_URL = "https://registry.npmjs.org/enigma-cli/latest";
|
|
1268
|
+
var UPDATE_COMMAND = "npm i -g enigma-cli@latest";
|
|
1269
|
+
var CACHE_FILE = join9(homedir5(), ".enigma-update-check.json");
|
|
1270
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1271
|
+
var CHILD_SCRIPT = [
|
|
1272
|
+
"const fs = require('fs');",
|
|
1273
|
+
"const ctrl = new AbortController();",
|
|
1274
|
+
"const t = setTimeout(() => ctrl.abort(), 5000);",
|
|
1275
|
+
"fetch(process.env.E_URL, { signal: ctrl.signal, headers: { 'user-agent': 'enigma-cli-update-check' } })",
|
|
1276
|
+
" .then((r) => (r.ok ? r.json() : null))",
|
|
1277
|
+
" .then((d) => { if (d && d.version) fs.writeFileSync(process.env.E_FILE, JSON.stringify({ latest: d.version, checkedAt: Date.now() })); })",
|
|
1278
|
+
" .catch(() => {})",
|
|
1279
|
+
" .finally(() => clearTimeout(t));"
|
|
1280
|
+
].join("\n");
|
|
1281
|
+
function parseVersion(version) {
|
|
1282
|
+
const core = String(version).trim().replace(/^v/, "").split("-")[0];
|
|
1283
|
+
const [major, minor, patch] = core.split(".").map((n) => parseInt(n, 10) || 0);
|
|
1284
|
+
return [major || 0, minor || 0, patch || 0];
|
|
1285
|
+
}
|
|
1286
|
+
function isNewer(latest, current) {
|
|
1287
|
+
const a = parseVersion(latest);
|
|
1288
|
+
const b = parseVersion(current);
|
|
1289
|
+
for (let i = 0; i < 3; i++) {
|
|
1290
|
+
if (a[i] > b[i]) return true;
|
|
1291
|
+
if (a[i] < b[i]) return false;
|
|
1292
|
+
}
|
|
1293
|
+
return false;
|
|
1294
|
+
}
|
|
1295
|
+
function paint(text, code) {
|
|
1296
|
+
return process.stdout.isTTY && !process.env.NO_COLOR ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
1297
|
+
}
|
|
1298
|
+
function renderUpdateBox(current, latest) {
|
|
1299
|
+
const lines = [
|
|
1300
|
+
{ text: `Update available ${current} -> ${latest}`, color: "1;33" },
|
|
1301
|
+
{ text: `Run ${UPDATE_COMMAND} to update`, color: "36" }
|
|
1302
|
+
];
|
|
1303
|
+
const width = Math.max(...lines.map((l) => l.text.length));
|
|
1304
|
+
const border = `+${"-".repeat(width + 4)}+`;
|
|
1305
|
+
const blank = `|${" ".repeat(width + 4)}|`;
|
|
1306
|
+
const rows = lines.map((l) => `|${paint(` ${l.text}${" ".repeat(width - l.text.length)} `, l.color)}|`);
|
|
1307
|
+
return [border, blank, ...rows, blank, border].join("\n");
|
|
1308
|
+
}
|
|
1309
|
+
function readCache() {
|
|
1310
|
+
return readJson(CACHE_FILE);
|
|
1311
|
+
}
|
|
1312
|
+
function scheduleUpdateCheck() {
|
|
1313
|
+
try {
|
|
1314
|
+
const cache = readCache();
|
|
1315
|
+
if (cache && Date.now() - cache.checkedAt < CHECK_INTERVAL_MS) return;
|
|
1316
|
+
writeFileSync6(CACHE_FILE, JSON.stringify({ latest: cache?.latest ?? null, checkedAt: Date.now() }));
|
|
1317
|
+
const child = spawn(process.execPath, ["-e", CHILD_SCRIPT], {
|
|
1318
|
+
detached: true,
|
|
1319
|
+
stdio: "ignore",
|
|
1320
|
+
windowsHide: true,
|
|
1321
|
+
env: { ...process.env, E_URL: REGISTRY_URL, E_FILE: CACHE_FILE }
|
|
1322
|
+
});
|
|
1323
|
+
child.unref();
|
|
1324
|
+
} catch {
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
function runUpdate() {
|
|
1328
|
+
try {
|
|
1329
|
+
const result = spawnSync("npm", ["i", "-g", "enigma-cli@latest"], {
|
|
1330
|
+
stdio: "inherit",
|
|
1331
|
+
shell: process.platform === "win32"
|
|
1332
|
+
});
|
|
1333
|
+
if (result.status === 0) console.log("Updated. Re-run your command to use the new version.");
|
|
1334
|
+
else console.log(`Update did not complete. Run '${UPDATE_COMMAND}' manually.`);
|
|
1335
|
+
} catch {
|
|
1336
|
+
console.log(`Could not run the update. Run '${UPDATE_COMMAND}' manually.`);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
async function notifyUpdate(current, interactive) {
|
|
1340
|
+
if (!process.stdout.isTTY || process.env.CI) return;
|
|
1341
|
+
try {
|
|
1342
|
+
if (!readConfig().config.updateNotifier) return;
|
|
1343
|
+
scheduleUpdateCheck();
|
|
1344
|
+
const cache = readCache();
|
|
1345
|
+
const latest = cache?.latest ? String(cache.latest).replace(/[^\w.+-]/g, "") : "";
|
|
1346
|
+
if (!latest || !isNewer(latest, current)) return;
|
|
1347
|
+
process.stdout.write(`
|
|
1348
|
+
${renderUpdateBox(current, latest)}
|
|
1349
|
+
`);
|
|
1350
|
+
if (!interactive) return;
|
|
1351
|
+
const ok = await p5.confirm({ message: `Update now with ${UPDATE_COMMAND}?`, initialValue: true });
|
|
1352
|
+
if (p5.isCancel(ok) || !ok) return;
|
|
1353
|
+
runUpdate();
|
|
1354
|
+
} catch {
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
995
1358
|
// src/cli.ts
|
|
996
1359
|
var __dirname3 = dirname4(fileURLToPath4(import.meta.url));
|
|
997
|
-
var PKG = readJson(
|
|
1360
|
+
var PKG = readJson(join10(__dirname3, "..", "package.json")) || {};
|
|
998
1361
|
var COMMANDS = /* @__PURE__ */ new Set(["install", "security", "guard", "seal", "check", "config", "help", "version"]);
|
|
999
1362
|
function parseArgs(argv) {
|
|
1000
1363
|
const opts = {
|
|
@@ -1103,15 +1466,19 @@ Usage:
|
|
|
1103
1466
|
enigma [command] [options]
|
|
1104
1467
|
|
|
1105
1468
|
Commands:
|
|
1106
|
-
(none) Interactive
|
|
1469
|
+
(none) Interactive hub: configure settings or set up features
|
|
1107
1470
|
install Install/update agent skills (Claude Code, Codex, opencode)
|
|
1108
1471
|
security Set up git security hooks in the current repo
|
|
1109
1472
|
guard [--all] Run the commit guard (staged files, or --all for every tracked file)
|
|
1110
|
-
config [key val]
|
|
1473
|
+
config [key val] Configure settings: no args opens the interactive menu;
|
|
1474
|
+
'config <key> <on|off> [-g|-l]' sets one (e.g. config claude-attribution on)
|
|
1111
1475
|
seal Maintenance: (re)compute skill content hashes
|
|
1112
1476
|
check Integrity gate: verify skills are well-formed and sealed
|
|
1113
1477
|
help, version
|
|
1114
1478
|
|
|
1479
|
+
Config keys: commit-emoji, update-notifier, claude-attribution,
|
|
1480
|
+
bypass-claude, bypass-codex, bypass-opencode
|
|
1481
|
+
|
|
1115
1482
|
Install options:
|
|
1116
1483
|
-g, --global Install at user level
|
|
1117
1484
|
-l, --local Install into the current project
|
|
@@ -1142,12 +1509,16 @@ Examples:
|
|
|
1142
1509
|
}
|
|
1143
1510
|
async function run(argv) {
|
|
1144
1511
|
const opts = parseArgs(argv);
|
|
1512
|
+
const interactive = Boolean(process.stdout.isTTY) && !opts.yes;
|
|
1513
|
+
const version = PKG.version || "0.0.0";
|
|
1145
1514
|
if (opts.help || opts.command === "help") {
|
|
1146
1515
|
printHelp();
|
|
1516
|
+
await notifyUpdate(version, interactive);
|
|
1147
1517
|
return;
|
|
1148
1518
|
}
|
|
1149
1519
|
if (opts.version || opts.command === "version") {
|
|
1150
|
-
console.log(
|
|
1520
|
+
console.log(version);
|
|
1521
|
+
await notifyUpdate(version, interactive);
|
|
1151
1522
|
return;
|
|
1152
1523
|
}
|
|
1153
1524
|
if (opts.command === "seal") return sealSources();
|
|
@@ -1156,44 +1527,45 @@ async function run(argv) {
|
|
|
1156
1527
|
process.exit(runGuardCli(opts.all));
|
|
1157
1528
|
}
|
|
1158
1529
|
if (opts.command === "config") {
|
|
1159
|
-
process.exit(runConfigCli(opts.positionals, opts.scope));
|
|
1530
|
+
process.exit(await runConfigCli(opts.positionals, opts.scope, interactive));
|
|
1160
1531
|
}
|
|
1161
|
-
const interactive = Boolean(process.stdout.isTTY) && !opts.yes;
|
|
1162
1532
|
if (opts.command === "install") {
|
|
1163
|
-
|
|
1533
|
+
p6.intro("enigma - install agent skills");
|
|
1164
1534
|
await installSkills(opts, interactive);
|
|
1165
|
-
|
|
1535
|
+
p6.outro("Done.");
|
|
1536
|
+
await notifyUpdate(version, interactive);
|
|
1166
1537
|
return;
|
|
1167
1538
|
}
|
|
1168
1539
|
if (opts.command === "security") {
|
|
1169
|
-
|
|
1540
|
+
p6.intro("enigma - git security hooks");
|
|
1170
1541
|
const done = await setupGitHooks(opts, interactive);
|
|
1171
|
-
|
|
1542
|
+
p6.outro(done ? "Git hooks configured." : "No changes made.");
|
|
1543
|
+
await notifyUpdate(version, interactive);
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
if (!interactive) {
|
|
1547
|
+
await installSkills(opts, interactive);
|
|
1548
|
+
await notifyUpdate(version, interactive);
|
|
1172
1549
|
return;
|
|
1173
1550
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
message: "What do you want to set up?",
|
|
1551
|
+
p6.intro("enigma");
|
|
1552
|
+
for (; ; ) {
|
|
1553
|
+
const action = await p6.select({
|
|
1554
|
+
message: "What would you like to do?",
|
|
1179
1555
|
options: [
|
|
1180
|
-
{ value: "
|
|
1181
|
-
{ value: "
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1556
|
+
{ value: "config", label: "Configure settings", hint: "emoji, attribution, permission bypass, ..." },
|
|
1557
|
+
{ value: "skills", label: "Install agent skills", hint: "Claude Code, Codex, opencode" },
|
|
1558
|
+
{ value: "security", label: "Git security hooks", hint: "block secrets, .env, node_modules on commit" },
|
|
1559
|
+
{ value: "exit", label: "Exit" }
|
|
1560
|
+
]
|
|
1185
1561
|
});
|
|
1186
|
-
if (
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
features = r;
|
|
1191
|
-
} else {
|
|
1192
|
-
features = ["skills"];
|
|
1562
|
+
if (p6.isCancel(action) || action === "exit") break;
|
|
1563
|
+
if (action === "config") await runSettingsMenu();
|
|
1564
|
+
else if (action === "skills") await installSkills(opts, interactive);
|
|
1565
|
+
else if (action === "security") await setupGitHooks(opts, interactive);
|
|
1193
1566
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
p4.outro("Done.");
|
|
1567
|
+
p6.outro("Done.");
|
|
1568
|
+
await notifyUpdate(version, interactive);
|
|
1197
1569
|
}
|
|
1198
1570
|
|
|
1199
1571
|
// src/bin/enigma.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "enigma-cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
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": {
|