enigma-cli 1.1.2 → 1.1.3

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.
@@ -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.2",
6
+ "cliVersion": "1.1.3",
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.2",
6
+ "cliVersion": "1.1.3",
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.2",
6
+ "cliVersion": "1.1.3",
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.2",
6
+ "cliVersion": "1.1.3",
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.2",
6
+ "cliVersion": "1.1.3",
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.2",
6
+ "cliVersion": "1.1.3",
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.2",
6
+ "cliVersion": "1.1.3",
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.2",
6
+ "cliVersion": "1.1.3",
7
7
  "sha": "b0355b0e15f9f528d32adf19f0722d2727cd64d6b3544307ecc7a3141338f023"
8
8
  }
@@ -3,6 +3,6 @@
3
3
  "version": "1.2.0",
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Git & contribution policy (senior engineering standards).",
6
- "cliVersion": "1.1.2",
6
+ "cliVersion": "1.1.3",
7
7
  "sha": "ada4b7eb5bb7e013429e23703c271c0f34b0d76327c059efa148ea2794f96178"
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.2",
6
+ "cliVersion": "1.1.3",
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.2",
6
+ "cliVersion": "1.1.3",
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.2",
6
+ "cliVersion": "1.1.3",
7
7
  "sha": "a33622a2f810ee4cea39824cb1a7ca34b355a917d4224025df50d77dd74f0b3a"
8
8
  }
package/dist/enigma.js CHANGED
@@ -1,9 +1,13 @@
1
1
  #!/usr/bin/env node
2
-
3
- // src/cli.ts
4
- import { dirname as dirname4, join as join10 } from "path";
5
- import { fileURLToPath as fileURLToPath4 } from "url";
6
- import * as p6 from "@clack/prompts";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
7
11
 
8
12
  // src/util.ts
9
13
  import { existsSync, statSync, readFileSync } from "fs";
@@ -27,58 +31,20 @@ function isOnPath(bin) {
27
31
  const exts = sep === "\\" ? (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";").map((e) => e.toLowerCase()) : [""];
28
32
  return dirs.some((d) => exts.some((ext) => existsSync(join(d, bin + ext))));
29
33
  }
30
-
31
- // src/skills.ts
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
- import { fileURLToPath as fileURLToPath2 } from "url";
35
- import { createHash } from "crypto";
36
- import * as p3 from "@clack/prompts";
34
+ var init_util = __esm({
35
+ "src/util.ts"() {
36
+ "use strict";
37
+ }
38
+ });
37
39
 
38
40
  // src/agents.ts
39
41
  import { homedir } from "os";
40
42
  import { join as join2 } from "path";
41
43
  import { existsSync as existsSync2 } from "fs";
42
44
  import { execFileSync } from "child_process";
43
- var HOME = homedir();
44
- var MANAGED_PROVIDER = "FJRG2007/enigma";
45
- var LEGACY_PROVIDERS = ["FJRG2007"];
46
45
  function isManagedProvider(provider) {
47
46
  return provider === MANAGED_PROVIDER || typeof provider === "string" && LEGACY_PROVIDERS.includes(provider);
48
47
  }
49
- var AGENTS = {
50
- claude: {
51
- label: "Claude Code",
52
- memoryFile: "CLAUDE.md",
53
- detect: { bins: ["claude"], dirs: [join2(HOME, ".claude")] },
54
- targets: {
55
- global: { skills: join2(HOME, ".claude", "skills"), memory: join2(HOME, ".claude") },
56
- local: { skills: join2(process.cwd(), ".claude", "skills"), memory: process.cwd() }
57
- }
58
- },
59
- codex: {
60
- label: "OpenAI Codex",
61
- memoryFile: "AGENTS.md",
62
- // Codex reads AGENTS.md from its home (~/.codex) and project root, but
63
- // discovers skills from the shared `.agents/skills` location, not ~/.codex/skills.
64
- detect: { bins: ["codex"], dirs: [join2(HOME, ".codex")] },
65
- targets: {
66
- global: { skills: join2(HOME, ".agents", "skills"), memory: join2(HOME, ".codex") },
67
- local: { skills: join2(process.cwd(), ".agents", "skills"), memory: process.cwd() }
68
- }
69
- },
70
- opencode: {
71
- label: "opencode",
72
- memoryFile: "AGENTS.md",
73
- // opencode reads AGENTS.md from ~/.config/opencode (global) or the project
74
- // root (local); skills from ~/.config/opencode/skills and .opencode/skills.
75
- detect: { bins: ["opencode"], dirs: [join2(HOME, ".config", "opencode"), join2(HOME, ".opencode")] },
76
- targets: {
77
- global: { skills: join2(HOME, ".config", "opencode", "skills"), memory: join2(HOME, ".config", "opencode") },
78
- local: { skills: join2(process.cwd(), ".opencode", "skills"), memory: process.cwd() }
79
- }
80
- }
81
- };
82
48
  function isInstalled(agent) {
83
49
  const det = agent.detect || {};
84
50
  return (det.dirs || []).some((d) => existsSync2(d)) || (det.bins || []).some((b) => isOnPath(b));
@@ -110,129 +76,49 @@ function runningStatus(agents) {
110
76
  }
111
77
  return { known: true, running };
112
78
  }
113
-
114
- // src/security.ts
115
- import { existsSync as existsSync3, mkdirSync, cpSync, writeFileSync, chmodSync } from "fs";
116
- import { dirname, join as join3, resolve, relative } from "path";
117
- import { fileURLToPath } from "url";
118
- import { execFileSync as execFileSync2 } from "child_process";
119
- import * as p from "@clack/prompts";
120
- var __dirname = dirname(fileURLToPath(import.meta.url));
121
- function findGuardSrc() {
122
- const candidates = [
123
- join3(__dirname, "guard.js"),
124
- join3(__dirname, "..", "dist", "guard.js")
125
- ];
126
- return candidates.find((c) => existsSync3(c)) ?? null;
127
- }
128
- var GUARD_PROTECTIONS = [
129
- { value: "secrets", label: "Block committed secrets", hint: "API keys, tokens, private keys" },
130
- { value: "envFiles", label: "Block .env files", hint: "allows .env.example / .sample / .template" },
131
- { value: "depDirs", label: "Block dependency/cache dirs", hint: "node_modules, __pycache__, venv" },
132
- { value: "generatedDirs", label: "Warn on generated dirs", hint: "dist, build, .next, coverage" },
133
- { value: "junkFiles", label: "Warn on log / OS junk files", hint: ".log, .DS_Store, Thumbs.db" },
134
- { value: "largeFiles", label: "Warn on files over 5 MB", hint: "oversized blobs" }
135
- ];
136
- function findGitRoot(start) {
137
- let dir = resolve(start);
138
- for (; ; ) {
139
- if (existsSync3(join3(dir, ".git"))) return dir;
140
- const parent = dirname(dir);
141
- if (parent === dir) return null;
142
- dir = parent;
143
- }
144
- }
145
- function currentHooksPath(root) {
146
- try {
147
- return execFileSync2("git", ["-C", root, "config", "--get", "core.hooksPath"], { encoding: "utf8" }).trim();
148
- } catch {
149
- return "";
150
- }
151
- }
152
- async function setupGitHooks(opts, interactive) {
153
- const root = findGitRoot(process.cwd());
154
- if (!root) {
155
- p.log.error("Not inside a git repository (no .git found). Run this from your project root.");
156
- return false;
157
- }
158
- const guardSrc = findGuardSrc();
159
- if (!guardSrc) {
160
- p.log.error("Cannot find the built guard (dist/guard.js). Run 'npm run build' first.");
161
- return false;
162
- }
163
- const current = currentHooksPath(root);
164
- if (current && current !== ".githooks" && !opts.force) {
165
- p.log.warn(`core.hooksPath is already set to '${current}'.`);
166
- if (interactive) {
167
- const ok = await p.confirm({ message: `Override existing core.hooksPath '${current}' with '.githooks'?` });
168
- if (p.isCancel(ok) || !ok) {
169
- p.log.info("Left git hooks unchanged.");
170
- return false;
79
+ var HOME, MANAGED_PROVIDER, LEGACY_PROVIDERS, AGENTS;
80
+ var init_agents = __esm({
81
+ "src/agents.ts"() {
82
+ "use strict";
83
+ init_util();
84
+ HOME = homedir();
85
+ MANAGED_PROVIDER = "FJRG2007/enigma";
86
+ LEGACY_PROVIDERS = ["FJRG2007"];
87
+ AGENTS = {
88
+ claude: {
89
+ label: "Claude Code",
90
+ memoryFile: "CLAUDE.md",
91
+ detect: { bins: ["claude"], dirs: [join2(HOME, ".claude")] },
92
+ targets: {
93
+ global: { skills: join2(HOME, ".claude", "skills"), memory: join2(HOME, ".claude") },
94
+ local: { skills: join2(process.cwd(), ".claude", "skills"), memory: process.cwd() }
95
+ }
96
+ },
97
+ codex: {
98
+ label: "OpenAI Codex",
99
+ memoryFile: "AGENTS.md",
100
+ // Codex reads AGENTS.md from its home (~/.codex) and project root, but
101
+ // discovers skills from the shared `.agents/skills` location, not ~/.codex/skills.
102
+ detect: { bins: ["codex"], dirs: [join2(HOME, ".codex")] },
103
+ targets: {
104
+ global: { skills: join2(HOME, ".agents", "skills"), memory: join2(HOME, ".codex") },
105
+ local: { skills: join2(process.cwd(), ".agents", "skills"), memory: process.cwd() }
106
+ }
107
+ },
108
+ opencode: {
109
+ label: "opencode",
110
+ memoryFile: "AGENTS.md",
111
+ // opencode reads AGENTS.md from ~/.config/opencode (global) or the project
112
+ // root (local); skills from ~/.config/opencode/skills and .opencode/skills.
113
+ detect: { bins: ["opencode"], dirs: [join2(HOME, ".config", "opencode"), join2(HOME, ".opencode")] },
114
+ targets: {
115
+ global: { skills: join2(HOME, ".config", "opencode", "skills"), memory: join2(HOME, ".config", "opencode") },
116
+ local: { skills: join2(process.cwd(), ".opencode", "skills"), memory: process.cwd() }
117
+ }
171
118
  }
172
- } else {
173
- p.log.info("Re-run with --force to override.");
174
- return false;
175
- }
119
+ };
176
120
  }
177
- let enabled = opts.protections;
178
- if (!enabled && interactive) {
179
- const r = await p.multiselect({
180
- message: "Which protections should the commit guard enforce?",
181
- options: GUARD_PROTECTIONS,
182
- initialValues: GUARD_PROTECTIONS.map((o) => o.value),
183
- required: true
184
- });
185
- if (p.isCancel(r)) {
186
- p.log.info("Left git hooks unchanged.");
187
- return false;
188
- }
189
- enabled = r;
190
- }
191
- const config = {};
192
- for (const o of GUARD_PROTECTIONS) config[o.value] = enabled ? enabled.includes(o.value) : true;
193
- const hooksDir = join3(root, ".githooks");
194
- mkdirSync(hooksDir, { recursive: true });
195
- cpSync(guardSrc, join3(hooksDir, "guard.mjs"), { force: true });
196
- writeFileSync(join3(hooksDir, "enigma-guard.json"), JSON.stringify(config, null, 2) + "\n");
197
- const shimPath = join3(hooksDir, "pre-commit");
198
- const shim = [
199
- "#!/bin/sh",
200
- "# Managed by enigma (enigma-cli) - blocks committed secrets, .env files, and dependency dirs.",
201
- "# Toggle protections in .githooks/enigma-guard.json. Bypass once: git commit --no-verify",
202
- 'exec node "$(git rev-parse --show-toplevel)/.githooks/guard.mjs" "$@"',
203
- ""
204
- ].join("\n");
205
- writeFileSync(shimPath, shim);
206
- try {
207
- chmodSync(shimPath, 493);
208
- } catch {
209
- }
210
- try {
211
- chmodSync(join3(hooksDir, "guard.mjs"), 493);
212
- } catch {
213
- }
214
- try {
215
- execFileSync2("git", ["-C", root, "config", "core.hooksPath", ".githooks"]);
216
- } catch (err) {
217
- p.log.error(`Failed to set core.hooksPath: ${err.message}`);
218
- return false;
219
- }
220
- const on = Object.entries(config).filter(([, v]) => v).map(([k]) => k);
221
- p.log.success(`Git security hooks installed in ${relative(process.cwd(), hooksDir) || ".githooks"} (core.hooksPath set).`);
222
- p.log.info(`Enforcing: ${on.join(", ") || "nothing"}. Commit .githooks/ so your team inherits it.`);
223
- if (isOnPath("gh")) {
224
- p.log.info("GitHub CLI (gh) detected: these hooks also run for commits made via gh, since gh uses git underneath.");
225
- }
226
- return true;
227
- }
228
- async function maybeOfferGitHooks(interactive, opts) {
229
- if (!interactive || opts.security) return;
230
- const root = findGitRoot(process.cwd());
231
- if (!root) return;
232
- if (currentHooksPath(root) === ".githooks") return;
233
- const ok = await p.confirm({ message: "Set up git security hooks here too (block secrets, .env, node_modules)?" });
234
- if (!p.isCancel(ok) && ok) await setupGitHooks({ ...opts, protections: void 0 }, interactive);
235
- }
121
+ });
236
122
 
237
123
  // src/claude.ts
238
124
  import { homedir as homedir2 } from "os";
@@ -320,14 +206,18 @@ function writeClaudeSettings(path, data) {
320
206
  if (!isDir(dir)) mkdirSync2(dir, { recursive: true });
321
207
  writeFileSync2(path, JSON.stringify(data, null, 2) + "\n");
322
208
  }
209
+ var init_claude = __esm({
210
+ "src/claude.ts"() {
211
+ "use strict";
212
+ init_util();
213
+ }
214
+ });
323
215
 
324
216
  // src/permissions.ts
325
217
  import { homedir as homedir3 } from "os";
326
218
  import { join as join5 } from "path";
327
219
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
328
220
  import * as p2 from "@clack/prompts";
329
- var BYPASS_SUPPORTED = ["claude", "codex", "opencode"];
330
- var BYPASS_DEFAULT_ON = /* @__PURE__ */ new Set(["claude", "codex"]);
331
221
  async function resolveBypassSelection(candidates, opts, interactive) {
332
222
  const supported = candidates.filter((a) => BYPASS_SUPPORTED.includes(a.name));
333
223
  if (!supported.length || opts.noBypass) return [];
@@ -516,8 +406,391 @@ function normalizeTrailingNewline(s) {
516
406
  return `${s.replace(/\s+$/, "")}
517
407
  `;
518
408
  }
409
+ var BYPASS_SUPPORTED, BYPASS_DEFAULT_ON;
410
+ var init_permissions = __esm({
411
+ "src/permissions.ts"() {
412
+ "use strict";
413
+ init_agents();
414
+ init_util();
415
+ init_claude();
416
+ BYPASS_SUPPORTED = ["claude", "codex", "opencode"];
417
+ BYPASS_DEFAULT_ON = /* @__PURE__ */ new Set(["claude", "codex"]);
418
+ }
419
+ });
420
+
421
+ // src/config.ts
422
+ import { homedir as homedir4 } from "os";
423
+ import { join as join8 } from "path";
424
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
425
+ function configPath(scope) {
426
+ return scope === "global" ? join8(homedir4(), CONFIG_FILE) : join8(process.cwd(), CONFIG_FILE);
427
+ }
428
+ function readConfig() {
429
+ const sources = [];
430
+ let config = { ...CONFIG_DEFAULTS };
431
+ for (const scope of ["global", "local"]) {
432
+ const path = configPath(scope);
433
+ const raw = existsSync6(path) ? readJson(path) : null;
434
+ if (raw) {
435
+ config = { ...config, ...raw };
436
+ sources.push(path);
437
+ }
438
+ }
439
+ return { config, sources };
440
+ }
441
+ function setEnigmaToggle(key, value, scope) {
442
+ const path = configPath(scope);
443
+ const current = readJson(path) || {};
444
+ const next = { ...current, [key]: value };
445
+ const dir = join8(path, "..");
446
+ if (!isDir(dir)) mkdirSync5(dir, { recursive: true });
447
+ writeFileSync5(path, JSON.stringify(next, null, 2) + "\n");
448
+ return path;
449
+ }
450
+ var CONFIG_FILE, CONFIG_DEFAULTS;
451
+ var init_config = __esm({
452
+ "src/config.ts"() {
453
+ "use strict";
454
+ init_util();
455
+ CONFIG_FILE = ".enigma.json";
456
+ CONFIG_DEFAULTS = { commitEmoji: true, updateNotifier: true };
457
+ }
458
+ });
459
+
460
+ // src/settings-registry.ts
461
+ function enigmaToggle(key, field, label, hint) {
462
+ return {
463
+ key,
464
+ label,
465
+ hint,
466
+ read: () => readConfig().config[field],
467
+ write: (value, scope) => ({ path: setEnigmaToggle(field, value, scope), changed: true })
468
+ };
469
+ }
470
+ function valueLabel(on) {
471
+ return on ? "on" : "off";
472
+ }
473
+ function parseBool(value) {
474
+ const v = value.toLowerCase();
475
+ if (["on", "true", "yes", "1", "enable", "enabled"].includes(v)) return true;
476
+ if (["off", "false", "no", "0", "disable", "disabled"].includes(v)) return false;
477
+ return null;
478
+ }
479
+ var CATEGORIES, ALL_SETTINGS;
480
+ var init_settings_registry = __esm({
481
+ "src/settings-registry.ts"() {
482
+ "use strict";
483
+ init_agents();
484
+ init_config();
485
+ init_claude();
486
+ init_permissions();
487
+ CATEGORIES = [
488
+ {
489
+ title: "General",
490
+ blurb: "enigma runtime toggles (.enigma.json)",
491
+ settings: [
492
+ enigmaToggle("commit-emoji", "commitEmoji", "Commit subject emoji", "leading gitmoji on commit subjects"),
493
+ enigmaToggle("update-notifier", "updateNotifier", "Update notifications", "notify when a newer enigma-cli is published")
494
+ ]
495
+ },
496
+ {
497
+ title: "Git & attribution",
498
+ blurb: "how the coding agent attributes its work in git",
499
+ settings: [
500
+ {
501
+ key: "claude-attribution",
502
+ label: "Claude commit attribution",
503
+ hint: "let Claude Code commit as its own contributor (Co-Authored-By / PR footer); enigma default: off",
504
+ read: (scope) => getClaudeAttribution(scope),
505
+ write: (value, scope) => ({ changed: setClaudeAttribution(scope, value) })
506
+ }
507
+ ]
508
+ },
509
+ {
510
+ title: "Permissions",
511
+ blurb: "approval-prompt bypass per agent (security trade-off)",
512
+ settings: BYPASS_SUPPORTED.map((name) => ({
513
+ key: `bypass-${name}`,
514
+ label: `${AGENTS[name]?.label || name} approval bypass`,
515
+ hint: name === "codex" ? "skip approval prompts (global ~/.codex only)" : "skip per-action approval prompts",
516
+ globalOnly: name === "codex",
517
+ read: (scope) => getBypass(name, scope),
518
+ write: (value, scope) => setBypass(name, scope, value, false) || { changed: false }
519
+ }))
520
+ }
521
+ ];
522
+ ALL_SETTINGS = CATEGORIES.flatMap((c) => c.settings);
523
+ }
524
+ });
525
+
526
+ // src/tui/settings.ts
527
+ var settings_exports = {};
528
+ __export(settings_exports, {
529
+ runSettingsTui: () => runSettingsTui
530
+ });
531
+ async function runSettingsTui() {
532
+ if (!process.stdout.isTTY) return;
533
+ const React = (await import("react")).default;
534
+ const ink = await import("ink");
535
+ const { render, useApp, useInput } = ink;
536
+ const Box = ink.Box;
537
+ const Text = ink.Text;
538
+ const { useState } = React;
539
+ const h = React.createElement;
540
+ function App() {
541
+ const { exit } = useApp();
542
+ const [scope, setScope] = useState("global");
543
+ const [focusSettings, setFocusSettings] = useState(false);
544
+ const [catIndex, setCatIndex] = useState(0);
545
+ const [setIndex, setSetIndex] = useState(0);
546
+ const [, bump] = useState(0);
547
+ const category = CATEGORIES[catIndex];
548
+ const settings = category.settings;
549
+ useInput((input, key) => {
550
+ if (input === "q" || key.escape || key.ctrl && input === "c") {
551
+ exit();
552
+ return;
553
+ }
554
+ if (input === "g") {
555
+ setScope((s) => s === "global" ? "local" : "global");
556
+ return;
557
+ }
558
+ if (key.tab) {
559
+ setFocusSettings((f) => !f);
560
+ return;
561
+ }
562
+ if (key.leftArrow || input === "h") {
563
+ setFocusSettings(false);
564
+ return;
565
+ }
566
+ if (key.rightArrow || input === "l") {
567
+ setFocusSettings(true);
568
+ return;
569
+ }
570
+ if (key.upArrow || input === "k") {
571
+ if (focusSettings) setSetIndex((i) => Math.max(0, i - 1));
572
+ else {
573
+ setCatIndex((i) => Math.max(0, i - 1));
574
+ setSetIndex(0);
575
+ }
576
+ return;
577
+ }
578
+ if (key.downArrow || input === "j") {
579
+ if (focusSettings) setSetIndex((i) => Math.min(settings.length - 1, i + 1));
580
+ else {
581
+ setCatIndex((i) => Math.min(CATEGORIES.length - 1, i + 1));
582
+ setSetIndex(0);
583
+ }
584
+ return;
585
+ }
586
+ if (key.return || input === " ") {
587
+ if (!focusSettings) {
588
+ setFocusSettings(true);
589
+ return;
590
+ }
591
+ const setting = settings[setIndex];
592
+ setting.write(!setting.read(scope), scope);
593
+ bump((n) => n + 1);
594
+ }
595
+ });
596
+ const header = h(
597
+ Box,
598
+ { marginBottom: 1 },
599
+ h(Text, { bold: true, color: "cyan" }, "enigma settings"),
600
+ h(Text, { dimColor: true }, " scope: "),
601
+ h(Text, { bold: true, color: scope === "global" ? "green" : "yellow" }, scope),
602
+ h(Text, { dimColor: true }, " (g to change)")
603
+ );
604
+ const left = h(Box, {
605
+ flexDirection: "column",
606
+ borderStyle: "round",
607
+ borderColor: focusSettings ? "gray" : "cyan",
608
+ paddingX: 1,
609
+ width: 26,
610
+ marginRight: 1
611
+ }, CATEGORIES.map((c, i) => h(Text, {
612
+ key: c.title,
613
+ color: i === catIndex ? "cyan" : void 0,
614
+ inverse: !focusSettings && i === catIndex
615
+ }, `${i === catIndex ? ">" : " "} ${c.title}`)));
616
+ const right = h(Box, {
617
+ flexDirection: "column",
618
+ borderStyle: "round",
619
+ borderColor: focusSettings ? "cyan" : "gray",
620
+ paddingX: 1,
621
+ flexGrow: 1
622
+ }, [
623
+ h(Text, { key: "__blurb", dimColor: true }, category.blurb),
624
+ ...settings.map((s, i) => {
625
+ const on = s.read(scope);
626
+ const selected = focusSettings && i === setIndex;
627
+ return h(
628
+ Box,
629
+ { key: s.key, justifyContent: "space-between" },
630
+ h(Text, { inverse: selected }, `${selected ? ">" : " "} ${s.label}${s.globalOnly ? " (global)" : ""}`),
631
+ h(Text, { bold: true, color: on ? "green" : "gray" }, ` ${valueLabel(on)}`)
632
+ );
633
+ })
634
+ ]);
635
+ const footer = h(Box, { marginTop: 1 }, h(
636
+ Text,
637
+ { dimColor: true },
638
+ "up/down move - tab/left/right switch pane - enter/space toggle - g scope - q quit"
639
+ ));
640
+ return h(Box, { flexDirection: "column" }, header, h(Box, {}, left, right), footer);
641
+ }
642
+ const app = render(h(App));
643
+ await app.waitUntilExit();
644
+ }
645
+ var init_settings = __esm({
646
+ "src/tui/settings.ts"() {
647
+ "use strict";
648
+ init_settings_registry();
649
+ }
650
+ });
651
+
652
+ // src/cli.ts
653
+ init_util();
654
+ import { dirname as dirname4, join as join10 } from "path";
655
+ import { fileURLToPath as fileURLToPath4 } from "url";
656
+ import * as p5 from "@clack/prompts";
657
+
658
+ // src/skills.ts
659
+ init_util();
660
+ init_agents();
661
+ import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync4, cpSync as cpSync2, mkdirSync as mkdirSync4, rmSync } from "fs";
662
+ import { dirname as dirname2, join as join6, resolve as resolve2, relative as relative2, sep as sep2 } from "path";
663
+ import { fileURLToPath as fileURLToPath2 } from "url";
664
+ import { createHash } from "crypto";
665
+ import * as p3 from "@clack/prompts";
666
+
667
+ // src/security.ts
668
+ init_util();
669
+ import { existsSync as existsSync3, mkdirSync, cpSync, writeFileSync, chmodSync } from "fs";
670
+ import { dirname, join as join3, resolve, relative } from "path";
671
+ import { fileURLToPath } from "url";
672
+ import { execFileSync as execFileSync2 } from "child_process";
673
+ import * as p from "@clack/prompts";
674
+ var __dirname = dirname(fileURLToPath(import.meta.url));
675
+ function findGuardSrc() {
676
+ const candidates = [
677
+ join3(__dirname, "guard.js"),
678
+ join3(__dirname, "..", "dist", "guard.js")
679
+ ];
680
+ return candidates.find((c) => existsSync3(c)) ?? null;
681
+ }
682
+ var GUARD_PROTECTIONS = [
683
+ { value: "secrets", label: "Block committed secrets", hint: "API keys, tokens, private keys" },
684
+ { value: "envFiles", label: "Block .env files", hint: "allows .env.example / .sample / .template" },
685
+ { value: "depDirs", label: "Block dependency/cache dirs", hint: "node_modules, __pycache__, venv" },
686
+ { value: "generatedDirs", label: "Warn on generated dirs", hint: "dist, build, .next, coverage" },
687
+ { value: "junkFiles", label: "Warn on log / OS junk files", hint: ".log, .DS_Store, Thumbs.db" },
688
+ { value: "largeFiles", label: "Warn on files over 5 MB", hint: "oversized blobs" }
689
+ ];
690
+ function findGitRoot(start) {
691
+ let dir = resolve(start);
692
+ for (; ; ) {
693
+ if (existsSync3(join3(dir, ".git"))) return dir;
694
+ const parent = dirname(dir);
695
+ if (parent === dir) return null;
696
+ dir = parent;
697
+ }
698
+ }
699
+ function currentHooksPath(root) {
700
+ try {
701
+ return execFileSync2("git", ["-C", root, "config", "--get", "core.hooksPath"], { encoding: "utf8" }).trim();
702
+ } catch {
703
+ return "";
704
+ }
705
+ }
706
+ async function setupGitHooks(opts, interactive) {
707
+ const root = findGitRoot(process.cwd());
708
+ if (!root) {
709
+ p.log.error("Not inside a git repository (no .git found). Run this from your project root.");
710
+ return false;
711
+ }
712
+ const guardSrc = findGuardSrc();
713
+ if (!guardSrc) {
714
+ p.log.error("Cannot find the built guard (dist/guard.js). Run 'npm run build' first.");
715
+ return false;
716
+ }
717
+ const current = currentHooksPath(root);
718
+ if (current && current !== ".githooks" && !opts.force) {
719
+ p.log.warn(`core.hooksPath is already set to '${current}'.`);
720
+ if (interactive) {
721
+ const ok = await p.confirm({ message: `Override existing core.hooksPath '${current}' with '.githooks'?` });
722
+ if (p.isCancel(ok) || !ok) {
723
+ p.log.info("Left git hooks unchanged.");
724
+ return false;
725
+ }
726
+ } else {
727
+ p.log.info("Re-run with --force to override.");
728
+ return false;
729
+ }
730
+ }
731
+ let enabled = opts.protections;
732
+ if (!enabled && interactive) {
733
+ const r = await p.multiselect({
734
+ message: "Which protections should the commit guard enforce?",
735
+ options: GUARD_PROTECTIONS,
736
+ initialValues: GUARD_PROTECTIONS.map((o) => o.value),
737
+ required: true
738
+ });
739
+ if (p.isCancel(r)) {
740
+ p.log.info("Left git hooks unchanged.");
741
+ return false;
742
+ }
743
+ enabled = r;
744
+ }
745
+ const config = {};
746
+ for (const o of GUARD_PROTECTIONS) config[o.value] = enabled ? enabled.includes(o.value) : true;
747
+ const hooksDir = join3(root, ".githooks");
748
+ mkdirSync(hooksDir, { recursive: true });
749
+ cpSync(guardSrc, join3(hooksDir, "guard.mjs"), { force: true });
750
+ writeFileSync(join3(hooksDir, "enigma-guard.json"), JSON.stringify(config, null, 2) + "\n");
751
+ const shimPath = join3(hooksDir, "pre-commit");
752
+ const shim = [
753
+ "#!/bin/sh",
754
+ "# Managed by enigma (enigma-cli) - blocks committed secrets, .env files, and dependency dirs.",
755
+ "# Toggle protections in .githooks/enigma-guard.json. Bypass once: git commit --no-verify",
756
+ 'exec node "$(git rev-parse --show-toplevel)/.githooks/guard.mjs" "$@"',
757
+ ""
758
+ ].join("\n");
759
+ writeFileSync(shimPath, shim);
760
+ try {
761
+ chmodSync(shimPath, 493);
762
+ } catch {
763
+ }
764
+ try {
765
+ chmodSync(join3(hooksDir, "guard.mjs"), 493);
766
+ } catch {
767
+ }
768
+ try {
769
+ execFileSync2("git", ["-C", root, "config", "core.hooksPath", ".githooks"]);
770
+ } catch (err) {
771
+ p.log.error(`Failed to set core.hooksPath: ${err.message}`);
772
+ return false;
773
+ }
774
+ const on = Object.entries(config).filter(([, v]) => v).map(([k]) => k);
775
+ p.log.success(`Git security hooks installed in ${relative(process.cwd(), hooksDir) || ".githooks"} (core.hooksPath set).`);
776
+ p.log.info(`Enforcing: ${on.join(", ") || "nothing"}. Commit .githooks/ so your team inherits it.`);
777
+ if (isOnPath("gh")) {
778
+ p.log.info("GitHub CLI (gh) detected: these hooks also run for commits made via gh, since gh uses git underneath.");
779
+ }
780
+ return true;
781
+ }
782
+ async function maybeOfferGitHooks(interactive, opts) {
783
+ if (!interactive || opts.security) return;
784
+ const root = findGitRoot(process.cwd());
785
+ if (!root) return;
786
+ if (currentHooksPath(root) === ".githooks") return;
787
+ const ok = await p.confirm({ message: "Set up git security hooks here too (block secrets, .env, node_modules)?" });
788
+ if (!p.isCancel(ok) && ok) await setupGitHooks({ ...opts, protections: void 0 }, interactive);
789
+ }
519
790
 
520
791
  // src/skills.ts
792
+ init_claude();
793
+ init_permissions();
521
794
  var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
522
795
  var PKG_ROOT = resolve2(__dirname2, "..");
523
796
  var ASSETS = join6(PKG_ROOT, "assets");
@@ -1061,159 +1334,8 @@ if (isGuardEntry && fileURLToPath3(import.meta.url) === guardEntry) {
1061
1334
  }
1062
1335
 
1063
1336
  // src/settings.ts
1064
- import * as p4 from "@clack/prompts";
1065
-
1066
- // src/config.ts
1067
- import { homedir as homedir4 } from "os";
1068
- import { join as join8 } from "path";
1069
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
1070
- var CONFIG_FILE = ".enigma.json";
1071
- var CONFIG_DEFAULTS = { commitEmoji: true, updateNotifier: true };
1072
- function configPath(scope) {
1073
- return scope === "global" ? join8(homedir4(), CONFIG_FILE) : join8(process.cwd(), CONFIG_FILE);
1074
- }
1075
- function readConfig() {
1076
- const sources = [];
1077
- let config = { ...CONFIG_DEFAULTS };
1078
- for (const scope of ["global", "local"]) {
1079
- const path = configPath(scope);
1080
- const raw = existsSync6(path) ? readJson(path) : null;
1081
- if (raw) {
1082
- config = { ...config, ...raw };
1083
- sources.push(path);
1084
- }
1085
- }
1086
- return { config, sources };
1087
- }
1088
- function setEnigmaToggle(key, value, scope) {
1089
- const path = configPath(scope);
1090
- const current = readJson(path) || {};
1091
- const next = { ...current, [key]: value };
1092
- const dir = join8(path, "..");
1093
- if (!isDir(dir)) mkdirSync5(dir, { recursive: true });
1094
- writeFileSync5(path, JSON.stringify(next, null, 2) + "\n");
1095
- return path;
1096
- }
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
- }
1337
+ init_config();
1338
+ init_settings_registry();
1217
1339
  function printEffective() {
1218
1340
  console.log("Effective enigma settings:\n");
1219
1341
  for (const category of CATEGORIES) {
@@ -1229,9 +1351,8 @@ async function runConfigCli(positionals, scope, interactive) {
1229
1351
  const [rawKey, rawValue] = positionals;
1230
1352
  if (!rawKey) {
1231
1353
  if (interactive) {
1232
- p4.intro("enigma config");
1233
- await runSettingsMenu();
1234
- p4.outro("Done.");
1354
+ const { runSettingsTui: runSettingsTui2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
1355
+ await runSettingsTui2();
1235
1356
  } else {
1236
1357
  printEffective();
1237
1358
  }
@@ -1259,11 +1380,13 @@ async function runConfigCli(positionals, scope, interactive) {
1259
1380
  }
1260
1381
 
1261
1382
  // src/update.ts
1383
+ init_util();
1384
+ init_config();
1262
1385
  import { homedir as homedir5 } from "os";
1263
1386
  import { join as join9 } from "path";
1264
1387
  import { writeFileSync as writeFileSync6 } from "fs";
1265
1388
  import { spawn, spawnSync } from "child_process";
1266
- import * as p5 from "@clack/prompts";
1389
+ import * as p4 from "@clack/prompts";
1267
1390
  var REGISTRY_URL = "https://registry.npmjs.org/enigma-cli/latest";
1268
1391
  var UPDATE_COMMAND = "npm i -g enigma-cli@latest";
1269
1392
  var CACHE_FILE = join9(homedir5(), ".enigma-update-check.json");
@@ -1348,8 +1471,8 @@ async function notifyUpdate(current, interactive) {
1348
1471
  ${renderUpdateBox(current, latest)}
1349
1472
  `);
1350
1473
  if (!interactive) return;
1351
- const ok = await p5.confirm({ message: `Update now with ${UPDATE_COMMAND}?`, initialValue: true });
1352
- if (p5.isCancel(ok) || !ok) return;
1474
+ const ok = await p4.confirm({ message: `Update now with ${UPDATE_COMMAND}?`, initialValue: true });
1475
+ if (p4.isCancel(ok) || !ok) return;
1353
1476
  runUpdate();
1354
1477
  } catch {
1355
1478
  }
@@ -1530,16 +1653,16 @@ async function run(argv) {
1530
1653
  process.exit(await runConfigCli(opts.positionals, opts.scope, interactive));
1531
1654
  }
1532
1655
  if (opts.command === "install") {
1533
- p6.intro("enigma - install agent skills");
1656
+ p5.intro("enigma - install agent skills");
1534
1657
  await installSkills(opts, interactive);
1535
- p6.outro("Done.");
1658
+ p5.outro("Done.");
1536
1659
  await notifyUpdate(version, interactive);
1537
1660
  return;
1538
1661
  }
1539
1662
  if (opts.command === "security") {
1540
- p6.intro("enigma - git security hooks");
1663
+ p5.intro("enigma - git security hooks");
1541
1664
  const done = await setupGitHooks(opts, interactive);
1542
- p6.outro(done ? "Git hooks configured." : "No changes made.");
1665
+ p5.outro(done ? "Git hooks configured." : "No changes made.");
1543
1666
  await notifyUpdate(version, interactive);
1544
1667
  return;
1545
1668
  }
@@ -1548,9 +1671,9 @@ async function run(argv) {
1548
1671
  await notifyUpdate(version, interactive);
1549
1672
  return;
1550
1673
  }
1551
- p6.intro("enigma");
1674
+ p5.intro("enigma");
1552
1675
  for (; ; ) {
1553
- const action = await p6.select({
1676
+ const action = await p5.select({
1554
1677
  message: "What would you like to do?",
1555
1678
  options: [
1556
1679
  { value: "config", label: "Configure settings", hint: "emoji, attribution, permission bypass, ..." },
@@ -1559,12 +1682,14 @@ async function run(argv) {
1559
1682
  { value: "exit", label: "Exit" }
1560
1683
  ]
1561
1684
  });
1562
- if (p6.isCancel(action) || action === "exit") break;
1563
- if (action === "config") await runSettingsMenu();
1564
- else if (action === "skills") await installSkills(opts, interactive);
1685
+ if (p5.isCancel(action) || action === "exit") break;
1686
+ if (action === "config") {
1687
+ const { runSettingsTui: runSettingsTui2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
1688
+ await runSettingsTui2();
1689
+ } else if (action === "skills") await installSkills(opts, interactive);
1565
1690
  else if (action === "security") await setupGitHooks(opts, interactive);
1566
1691
  }
1567
- p6.outro("Done.");
1692
+ p5.outro("Done.");
1568
1693
  await notifyUpdate(version, interactive);
1569
1694
  }
1570
1695
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enigma-cli",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
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": {
@@ -9,7 +9,8 @@
9
9
  "scripts": {
10
10
  "enigma": "tsx src/bin/enigma.ts",
11
11
  "build": "tsup",
12
- "dev": "tsup --watch",
12
+ "dev": "node scripts/dev.mjs",
13
+ "dev:watch": "tsup --watch",
13
14
  "typecheck": "tsc --noEmit",
14
15
  "seal": "tsx src/bin/enigma.ts seal",
15
16
  "check": "tsx src/bin/enigma.ts check",
@@ -27,10 +28,13 @@
27
28
  "node": ">=18"
28
29
  },
29
30
  "dependencies": {
30
- "@clack/prompts": "^0.7.0"
31
+ "@clack/prompts": "^0.7.0",
32
+ "ink": "^5.2.1",
33
+ "react": "^18.3.1"
31
34
  },
32
35
  "devDependencies": {
33
36
  "@types/node": "^22.0.0",
37
+ "@types/react": "^18.3.29",
34
38
  "tsup": "^8.0.0",
35
39
  "tsx": "^4.0.0",
36
40
  "typescript": "^5.0.0"