jorgex-stack 1.0.22 → 1.0.23

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/README.md CHANGED
@@ -24,6 +24,8 @@ For development from a clone, run the same commands through `pnpm cli <command>`
24
24
 
25
25
  Every command supports `--dry-run`, `--yes`, and `--target-dir <dir>` for testing without touching the real config. Writes create automatic backups and verify idempotency; merges into user config are surgical (marked markdown sections, JSON/TOML upserts), so user-owned content is never touched.
26
26
 
27
+ Permission defaults by runtime (what gets installed, what stays custom, and the read-anywhere limitations) are documented in [docs/references/permissions.md](docs/references/permissions.md).
28
+
27
29
  ### Modes: Human and Programmatic
28
30
 
29
31
  `install` and `sync` accept two mutually-exclusive installation modes. The choice is global (not per runtime) and is saved in `~/.jorgex-stack/install-mode.json` on first run; subsequent `sync` calls reuse it. Re-run `install` with `--mode` to switch.
package/dist/cli.js CHANGED
@@ -614,11 +614,17 @@ ${agent.body}`,
614
614
  planMainConfig(canonical, ctx) {
615
615
  const file = path7.join(ctx.configDir, "opencode.json");
616
616
  const { pluginsDir } = this.paths(ctx.configDir);
617
- const content = upsertJson(readTextIfExists(file), (root) => {
617
+ const original = readTextIfExists(file);
618
+ const contentSource = original === null || original.trim() === "" ? null : original;
619
+ const isFreshConfig = contentSource === null;
620
+ const content = upsertJson(contentSource, (root) => {
618
621
  root["$schema"] ??= "https://opencode.ai/config.json";
619
622
  const defaults = loadCanonicalDefaults(ctx.stackDir)["opencode"];
620
- if (defaults?.["permission"] !== void 0 && !("permission" in root)) {
623
+ if (isFreshConfig && defaults?.["permission"] !== void 0) {
621
624
  root["permission"] = defaults["permission"];
625
+ ctx.warnings.push(
626
+ "OpenCode: fresh config enables read-anywhere via external_directory:*; edits, web egress and arbitrary bash remain approval-gated, but broad local reads can expose secrets not covered by deny rules."
627
+ );
622
628
  }
623
629
  const mcp = root["mcp"] ??= {};
624
630
  for (const [name, server] of Object.entries(canonical.servers)) {
@@ -818,12 +824,19 @@ ${agent.body}`,
818
824
  planHooks(canonical, ctx) {
819
825
  const actions = [];
820
826
  const { scriptsDir } = this.paths(ctx.configDir);
827
+ const original = readTextIfExists(path8.join(ctx.configDir, "settings.json"));
828
+ const contentSource = original === null || original.trim() === "" ? null : original;
821
829
  const settingsFile = path8.join(ctx.configDir, "settings.json");
822
- let content = upsertNativeHooks(readTextIfExists(settingsFile), canonical, scriptsDir);
830
+ let content = upsertNativeHooks(contentSource, canonical, scriptsDir);
823
831
  const defaults = loadCanonicalDefaults(ctx.stackDir)["claude-code"];
824
- if (defaults?.["permissions"] !== void 0) {
832
+ if (contentSource === null && defaults?.["permissions"] !== void 0) {
825
833
  content = upsertJson(content, (root) => {
826
- if (!("permissions" in root)) root["permissions"] = defaults["permissions"];
834
+ if (root["permissions"] === void 0) {
835
+ root["permissions"] = defaults["permissions"];
836
+ ctx.warnings.push(
837
+ "Claude Code: fresh config enables read-anywhere via Read/Grep/Glob allow rules; shell, writes and web egress remain approval-gated, but broad local reads can expose secrets not covered by deny rules."
838
+ );
839
+ }
827
840
  });
828
841
  }
829
842
  actions.push({ kind: "write", target: settingsFile, content });
@@ -1026,13 +1039,52 @@ ${body}`
1026
1039
  },
1027
1040
  planMainConfig(canonical, ctx) {
1028
1041
  const file = path9.join(ctx.configDir, "config.toml");
1029
- let content = readTextIfExists(file);
1030
- const defaults = loadCanonicalDefaults(ctx.stackDir)["codex"] ?? {};
1031
- for (const [key, value] of Object.entries(defaults)) {
1032
- if (content !== null && new RegExp(`^\\s*${key}\\s*=`, "m").test(content)) continue;
1033
- const line = `${key} = ${tomlString(String(value))}
1042
+ const original = readTextIfExists(file);
1043
+ const contentSource = original === null || original.trim() === "" ? null : original;
1044
+ let content = contentSource;
1045
+ if (contentSource === null) {
1046
+ const defaults = loadCanonicalDefaults(ctx.stackDir)["codex"] ?? {};
1047
+ for (const [key, value] of Object.entries(defaults)) {
1048
+ const line = `${key} = ${tomlString(String(value))}
1034
1049
  `;
1035
- content = content === null ? line : line + content;
1050
+ content = content === null ? line : line + content;
1051
+ }
1052
+ ctx.warnings.push(
1053
+ "Codex: fresh config enables read-anywhere via the jorgex-read-anywhere permission profile; broad local reads can expose secrets not covered by deny rules."
1054
+ );
1055
+ content = upsertTomlSection(content, "permissions.jorgex-read-anywhere", 'extends = ":workspace"');
1056
+ content = upsertTomlSection(
1057
+ content,
1058
+ "permissions.jorgex-read-anywhere.filesystem",
1059
+ [
1060
+ '":root" = "read"',
1061
+ '"*.env" = "deny"',
1062
+ '"*.env.*" = "deny"',
1063
+ '"~/.ssh/**" = "deny"',
1064
+ '"~/.aws/credentials" = "deny"',
1065
+ '"~/.npmrc" = "deny"',
1066
+ '"~/.git-credentials" = "deny"',
1067
+ '"**/id_rsa" = "deny"',
1068
+ '"**/id_ed25519" = "deny"',
1069
+ '"**/*.pem" = "deny"',
1070
+ '"**/*.key" = "deny"'
1071
+ ].join("\n")
1072
+ );
1073
+ content += [
1074
+ '\n[permissions.jorgex-read-anywhere.filesystem.":workspace_roots"]',
1075
+ '"." = "write"',
1076
+ '"*.env" = "deny"',
1077
+ '"*.env.*" = "deny"',
1078
+ '".ssh/**" = "deny"',
1079
+ '".aws/credentials" = "deny"',
1080
+ '".npmrc" = "deny"',
1081
+ '".git-credentials" = "deny"',
1082
+ '"**/id_rsa" = "deny"',
1083
+ '"**/id_ed25519" = "deny"',
1084
+ '"**/*.pem" = "deny"',
1085
+ '"**/*.key" = "deny"',
1086
+ ""
1087
+ ].join("\n");
1036
1088
  }
1037
1089
  for (const [name, server] of Object.entries(canonical.servers)) {
1038
1090
  const section = `mcp_servers.${name}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jorgex-stack",
3
- "version": "1.0.22",
3
+ "version": "1.0.23",
4
4
  "description": "Harness multi-agente portable: instala la config JorgeX (agentes, skills, hooks, Engram, MCPs) en Claude Code, Codex CLI y OpenCode",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,17 +1,36 @@
1
1
  {
2
- "$comment": "Permisos por defecto del stack: casi todo permitido, lo destructivo pregunta o se bloquea. REGLA DE ORO: cada bloque se escribe SOLO si el usuario aún no tiene esa clave en su config si ya la tiene (suya o ajustada después), el sync no la toca jamás. Quitar o editar estos valores tras el install es seguro: pasan a ser config del usuario.",
2
+ "$comment": "Permisos por defecto del stack: cada bloque se escribe SOLO en config fresca o vacía; una config existente no se auto-expande jamás. OpenCode fresh default permite leer fuera del cwd, pero deja edit/bash en ask salvo allow explícito para lecturas seguras.",
3
3
  "opencode": {
4
4
  "permission": {
5
- "edit": "allow",
6
- "read": "allow",
5
+ "edit": "ask",
6
+ "read": {
7
+ "*": "allow",
8
+ "*.env": "deny",
9
+ "*.env.*": "deny",
10
+ "*.env.example": "allow",
11
+ "*/.ssh/*": "deny",
12
+ "*/.aws/credentials": "deny",
13
+ "*/.npmrc": "deny",
14
+ "*/.git-credentials": "deny",
15
+ "*/id_rsa": "deny",
16
+ "*/id_ed25519": "deny",
17
+ "*.pem": "deny",
18
+ "*.key": "deny"
19
+ },
20
+ "external_directory": {
21
+ "*": "allow"
22
+ },
7
23
  "glob": "allow",
8
24
  "grep": "allow",
9
25
  "list": "allow",
10
26
  "lsp": "allow",
11
- "webfetch": "allow",
12
- "websearch": "allow",
27
+ "webfetch": "ask",
28
+ "websearch": "ask",
13
29
  "bash": {
14
- "*": "allow",
30
+ "*": "ask",
31
+ "git diff*": "ask",
32
+ "git log*": "allow",
33
+ "git status*": "allow",
15
34
  "rm *": "ask",
16
35
  "del *": "ask",
17
36
  "rmdir *": "ask",
@@ -25,13 +44,28 @@
25
44
  },
26
45
  "claude-code": {
27
46
  "permissions": {
28
- "allow": ["Bash", "Edit", "Write", "WebFetch", "WebSearch"],
29
- "ask": ["Bash(rm:*)", "Bash(rmdir:*)", "Bash(del:*)", "Bash(git push --force:*)"],
30
- "deny": ["Bash(format:*)", "Bash(mkfs:*)", "Bash(dd:*)", "Bash(shred:*)", "Read(./.env)", "Read(./.env.*)"]
47
+ "allow": ["Read", "Grep", "Glob"],
48
+ "ask": ["Bash", "Edit", "Write", "WebFetch", "WebSearch", "Bash(rm:*)", "Bash(rmdir:*)", "Bash(del:*)", "Bash(git push --force:*)"],
49
+ "deny": [
50
+ "Bash(format:*)",
51
+ "Bash(mkfs:*)",
52
+ "Bash(dd:*)",
53
+ "Bash(shred:*)",
54
+ "Read(//**/.env)",
55
+ "Read(//**/.env.*)",
56
+ "Read(//**/.ssh/**)",
57
+ "Read(//**/.aws/credentials)",
58
+ "Read(//**/.npmrc)",
59
+ "Read(//**/.git-credentials)",
60
+ "Read(//**/id_rsa)",
61
+ "Read(//**/id_ed25519)",
62
+ "Read(//**/*.pem)",
63
+ "Read(//**/*.key)"
64
+ ]
31
65
  }
32
66
  },
33
67
  "codex": {
34
68
  "approval_policy": "on-request",
35
- "sandbox_mode": "workspace-write"
69
+ "default_permissions": "jorgex-read-anywhere"
36
70
  }
37
71
  }