@jaguilar87/gaia-ops 5.0.0-beta.1 → 5.0.0-beta.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.
@@ -8,7 +8,7 @@
8
8
  {
9
9
  "name": "gaia-security",
10
10
  "description": "Keeps you in the loop only when it matters. Gaia Security analyzes every command and classifies it into risk tiers: read-only queries run freely, simulations and validations pass through, and state-changing operations (create, delete, apply, push) pause for your explicit approval before executing. Irreversible commands like dropping databases or deleting cloud infrastructure are permanently blocked.",
11
- "version": "5.0.0-beta.1",
11
+ "version": "5.0.0-beta.3",
12
12
  "source": "./dist/gaia-security"
13
13
  }
14
14
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gaia-ops",
3
- "version": "5.0.0-beta.1",
3
+ "version": "5.0.0-beta.3",
4
4
  "description": "Security-first orchestrator with specialized agents, hooks, and governance for AI coding",
5
5
  "author": {
6
6
  "name": "jaguilar87"
@@ -341,12 +341,18 @@ async function autoFix() {
341
341
  if (existsSync(packagePath)) {
342
342
  const relPath = relative(claudeDir, packagePath);
343
343
  const names = ['agents', 'tools', 'hooks', 'commands', 'templates', 'config', 'speckit', 'skills'];
344
+ // Use junctions on Windows (no admin required), regular symlinks elsewhere
345
+ const linkType = process.platform === 'win32' ? 'junction' : 'dir';
344
346
 
345
347
  for (const name of names) {
346
348
  const link = join(claudeDir, name);
347
349
  if (!existsSync(link)) {
348
350
  try {
349
- await fs.symlink(join(relPath, name), link);
351
+ // Junctions on Windows require absolute targets; symlinks on Unix use relative
352
+ const target = process.platform === 'win32'
353
+ ? join(packagePath, name)
354
+ : join(relPath, name);
355
+ await fs.symlink(target, link, linkType);
350
356
  console.log(chalk.green(` Fixed: .claude/${name} symlink`));
351
357
  fixed++;
352
358
  } catch {
package/bin/gaia-scan CHANGED
@@ -1,14 +1,38 @@
1
- #!/usr/bin/env bash
2
- # Shell wrapper for gaia-scan CLI.
3
- # Invokes the Python scanner with all forwarded arguments.
4
- # Used as the npm bin entry point for `npx gaia-scan` / `npx gaia scan`.
1
+ #!/usr/bin/env node
5
2
 
6
- set -euo pipefail
3
+ /**
4
+ * Cross-platform wrapper for gaia-scan CLI.
5
+ * Invokes the Python scanner with all forwarded arguments.
6
+ * Used as the npm bin entry point for `npx gaia-scan`.
7
+ *
8
+ * Replaces the previous bash wrapper so that it works on Windows
9
+ * (where /usr/bin/env bash does not exist).
10
+ */
7
11
 
8
- SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0" 2>/dev/null || realpath "$0" 2>/dev/null || echo "$0")")" && pwd)"
9
- PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
12
+ import { execFileSync } from 'child_process';
13
+ import { fileURLToPath } from 'url';
14
+ import { dirname, join } from 'path';
10
15
 
11
- # Ensure PYTHONPATH includes the plugin root so `tools.scan` resolves
12
- export PYTHONPATH="${PLUGIN_ROOT}${PYTHONPATH:+:${PYTHONPATH}}"
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = dirname(__filename);
18
+ const pluginRoot = join(__dirname, '..');
19
+ const scanScript = join(__dirname, 'gaia-scan.py');
13
20
 
14
- exec python3 "${SCRIPT_DIR}/gaia-scan.py" "$@"
21
+ // Forward all CLI arguments after the script name
22
+ const args = process.argv.slice(2);
23
+
24
+ // Set PYTHONPATH so `tools.scan` resolves inside the package
25
+ const sep = process.platform === 'win32' ? ';' : ':';
26
+ const pythonPath = process.env.PYTHONPATH
27
+ ? `${pluginRoot}${sep}${process.env.PYTHONPATH}`
28
+ : pluginRoot;
29
+
30
+ try {
31
+ execFileSync('python3', [scanScript, ...args], {
32
+ stdio: 'inherit',
33
+ env: { ...process.env, PYTHONPATH: pythonPath },
34
+ });
35
+ } catch (error) {
36
+ // execFileSync throws on non-zero exit; propagate the exit code
37
+ process.exit(error.status || 1);
38
+ }
@@ -44,6 +44,9 @@ const __dirname = dirname(__filename);
44
44
  const CWD = process.env.INIT_CWD || process.cwd();
45
45
  const VERBOSE = process.argv.includes('--verbose') || process.argv.includes('-v');
46
46
 
47
+ // Use junctions on Windows (no admin required), regular symlinks elsewhere
48
+ const LINK_TYPE = process.platform === 'win32' ? 'junction' : 'dir';
49
+
47
50
  // ============================================================================
48
51
  // Version Detection
49
52
  // ============================================================================
@@ -376,11 +379,14 @@ async function updateSymlinks() {
376
379
 
377
380
  for (const name of symlinks) {
378
381
  const link = join(claudeDir, name);
379
- const target = join(relativePath, name);
382
+ // Junctions on Windows require absolute targets; symlinks on Unix use relative
383
+ const target = process.platform === 'win32'
384
+ ? join(packagePath, name)
385
+ : join(relativePath, name);
380
386
 
381
387
  if (!existsSync(link)) {
382
388
  try {
383
- await fs.symlink(target, link);
389
+ await fs.symlink(target, link, LINK_TYPE);
384
390
  fixed++;
385
391
  } catch { /* skip */ }
386
392
  } else {
@@ -391,7 +397,7 @@ async function updateSymlinks() {
391
397
  // Broken symlink — remove and recreate
392
398
  try {
393
399
  await fs.unlink(link);
394
- await fs.symlink(target, link);
400
+ await fs.symlink(target, link, LINK_TYPE);
395
401
  fixed++;
396
402
  } catch { /* skip */ }
397
403
  }
@@ -402,7 +408,12 @@ async function updateSymlinks() {
402
408
  const changelogLink = join(claudeDir, 'CHANGELOG.md');
403
409
  if (!existsSync(changelogLink)) {
404
410
  try {
405
- await fs.symlink(join(relativePath, 'CHANGELOG.md'), changelogLink);
411
+ if (process.platform === 'win32') {
412
+ // Junctions only work for directories; copy the file on Windows
413
+ await fs.copyFile(join(packagePath, 'CHANGELOG.md'), changelogLink);
414
+ } else {
415
+ await fs.symlink(join(relativePath, 'CHANGELOG.md'), changelogLink);
416
+ }
406
417
  fixed++;
407
418
  } catch { /* skip */ }
408
419
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gaia-ops",
3
- "version": "5.0.0-beta.1",
3
+ "version": "5.0.0-beta.3",
4
4
  "description": "Full DevOps orchestration for Claude Code. Six specialized agents handle the complete development lifecycle \u2014 analysis, planning, execution, and deployment. Gaia-Ops scans your codebase to understand it and injects the right context into each sub-agent. Every command is classified by risk: read-only runs freely, state changes pause for your approval, and irreversible operations are permanently blocked.",
5
5
  "author": {
6
6
  "name": "jaguilar87"
@@ -86,7 +86,11 @@ MUTATIVE_VERBS: FrozenSet[str] = frozenset({
86
86
  "update", "patch", "set", "modify", "edit", "configure",
87
87
  "replace", "overwrite", "write",
88
88
  # Deployment / packaging
89
- "deploy", "install", "upgrade", "downgrade", "publish", "release", "promote",
89
+ # NOTE: "release" removed -- it is a CLI subcommand group noun in `gh release`,
90
+ # `glab release`, etc. The actual mutative actions (create, delete, edit, upload)
91
+ # are already in MUTATIVE_VERBS. Keeping "release" here causes false positives on
92
+ # `gh release view` and any command with "release" as an argument string.
93
+ "deploy", "install", "upgrade", "downgrade", "publish", "promote",
90
94
  # Scaling
91
95
  "scale", "resize", "autoscale",
92
96
  # Lifecycle
@@ -19,6 +19,7 @@ Functions:
19
19
  import json
20
20
  import logging
21
21
  import os
22
+ import platform
22
23
  import shutil
23
24
  import subprocess
24
25
  from datetime import datetime, timezone
@@ -27,6 +28,23 @@ from typing import Any, Dict, List, Optional
27
28
 
28
29
  logger = logging.getLogger(__name__)
29
30
 
31
+ # Windows detection: junctions don't require admin privileges
32
+ _IS_WINDOWS = platform.system() == "Windows"
33
+
34
+
35
+ def _create_dir_link(target: str, link: str) -> None:
36
+ """Create a directory link: junction on Windows, symlink on Unix.
37
+
38
+ Windows junctions don't require admin/developer-mode privileges,
39
+ unlike directory symlinks. The target must be an absolute path
40
+ on Windows (junctions don't support relative targets).
41
+ """
42
+ if _IS_WINDOWS:
43
+ import _winapi # stdlib on Windows, unavailable elsewhere
44
+ _winapi.CreateJunction(target, link)
45
+ else:
46
+ os.symlink(target, link)
47
+
30
48
 
31
49
  def _find_package_root() -> Path:
32
50
  """Find the gaia-ops plugin root directory.
@@ -191,23 +209,31 @@ def create_claude_directory(project_root: Path) -> List[str]:
191
209
 
192
210
  for name in symlink_names:
193
211
  link_path = claude_dir / name
194
- target = os.path.join(rel_path, name)
212
+ # Junctions on Windows require absolute targets; symlinks on Unix use relative
213
+ if _IS_WINDOWS:
214
+ target = str(package_path / name)
215
+ else:
216
+ target = os.path.join(rel_path, name)
195
217
 
196
218
  if link_path.exists() or link_path.is_symlink():
197
219
  link_path.unlink()
198
220
 
199
221
  try:
200
- os.symlink(target, str(link_path))
222
+ _create_dir_link(target, str(link_path))
201
223
  created.append(name)
202
224
  except OSError as exc:
203
225
  logger.warning("Failed to create symlink %s: %s", name, exc)
204
226
 
205
- # CHANGELOG.md symlink
227
+ # CHANGELOG.md symlink (file, not directory — junctions only work for dirs)
206
228
  changelog_link = claude_dir / "CHANGELOG.md"
207
229
  if changelog_link.exists() or changelog_link.is_symlink():
208
230
  changelog_link.unlink()
209
231
  try:
210
- os.symlink(os.path.join(rel_path, "CHANGELOG.md"), str(changelog_link))
232
+ if _IS_WINDOWS:
233
+ # File symlinks need admin on Windows; copy instead
234
+ shutil.copy2(str(package_path / "CHANGELOG.md"), str(changelog_link))
235
+ else:
236
+ os.symlink(os.path.join(rel_path, "CHANGELOG.md"), str(changelog_link))
211
237
  created.append("CHANGELOG.md")
212
238
  except OSError as exc:
213
239
  logger.warning("Failed to create CHANGELOG.md symlink: %s", exc)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gaia-security",
3
- "version": "5.0.0-beta.1",
3
+ "version": "5.0.0-beta.3",
4
4
  "description": "Keeps you in the loop only when it matters. Gaia Security analyzes every command and classifies it into risk tiers: read-only queries run freely, simulations and validations pass through, and state-changing operations (create, delete, apply, push) pause for your explicit approval before executing. Irreversible commands like dropping databases or deleting cloud infrastructure are permanently blocked.",
5
5
  "author": {
6
6
  "name": "jaguilar87"
@@ -86,7 +86,11 @@ MUTATIVE_VERBS: FrozenSet[str] = frozenset({
86
86
  "update", "patch", "set", "modify", "edit", "configure",
87
87
  "replace", "overwrite", "write",
88
88
  # Deployment / packaging
89
- "deploy", "install", "upgrade", "downgrade", "publish", "release", "promote",
89
+ # NOTE: "release" removed -- it is a CLI subcommand group noun in `gh release`,
90
+ # `glab release`, etc. The actual mutative actions (create, delete, edit, upload)
91
+ # are already in MUTATIVE_VERBS. Keeping "release" here causes false positives on
92
+ # `gh release view` and any command with "release" as an argument string.
93
+ "deploy", "install", "upgrade", "downgrade", "publish", "promote",
90
94
  # Scaling
91
95
  "scale", "resize", "autoscale",
92
96
  # Lifecycle
@@ -86,7 +86,11 @@ MUTATIVE_VERBS: FrozenSet[str] = frozenset({
86
86
  "update", "patch", "set", "modify", "edit", "configure",
87
87
  "replace", "overwrite", "write",
88
88
  # Deployment / packaging
89
- "deploy", "install", "upgrade", "downgrade", "publish", "release", "promote",
89
+ # NOTE: "release" removed -- it is a CLI subcommand group noun in `gh release`,
90
+ # `glab release`, etc. The actual mutative actions (create, delete, edit, upload)
91
+ # are already in MUTATIVE_VERBS. Keeping "release" here causes false positives on
92
+ # `gh release view` and any command with "release" as an argument string.
93
+ "deploy", "install", "upgrade", "downgrade", "publish", "promote",
90
94
  # Scaling
91
95
  "scale", "resize", "autoscale",
92
96
  # Lifecycle
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jaguilar87/gaia-ops",
3
- "version": "5.0.0-beta.1",
3
+ "version": "5.0.0-beta.3",
4
4
  "description": "Multi-agent orchestration system for Claude Code - DevOps automation toolkit",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -19,6 +19,7 @@ Functions:
19
19
  import json
20
20
  import logging
21
21
  import os
22
+ import platform
22
23
  import shutil
23
24
  import subprocess
24
25
  from datetime import datetime, timezone
@@ -27,6 +28,23 @@ from typing import Any, Dict, List, Optional
27
28
 
28
29
  logger = logging.getLogger(__name__)
29
30
 
31
+ # Windows detection: junctions don't require admin privileges
32
+ _IS_WINDOWS = platform.system() == "Windows"
33
+
34
+
35
+ def _create_dir_link(target: str, link: str) -> None:
36
+ """Create a directory link: junction on Windows, symlink on Unix.
37
+
38
+ Windows junctions don't require admin/developer-mode privileges,
39
+ unlike directory symlinks. The target must be an absolute path
40
+ on Windows (junctions don't support relative targets).
41
+ """
42
+ if _IS_WINDOWS:
43
+ import _winapi # stdlib on Windows, unavailable elsewhere
44
+ _winapi.CreateJunction(target, link)
45
+ else:
46
+ os.symlink(target, link)
47
+
30
48
 
31
49
  def _find_package_root() -> Path:
32
50
  """Find the gaia-ops plugin root directory.
@@ -191,23 +209,31 @@ def create_claude_directory(project_root: Path) -> List[str]:
191
209
 
192
210
  for name in symlink_names:
193
211
  link_path = claude_dir / name
194
- target = os.path.join(rel_path, name)
212
+ # Junctions on Windows require absolute targets; symlinks on Unix use relative
213
+ if _IS_WINDOWS:
214
+ target = str(package_path / name)
215
+ else:
216
+ target = os.path.join(rel_path, name)
195
217
 
196
218
  if link_path.exists() or link_path.is_symlink():
197
219
  link_path.unlink()
198
220
 
199
221
  try:
200
- os.symlink(target, str(link_path))
222
+ _create_dir_link(target, str(link_path))
201
223
  created.append(name)
202
224
  except OSError as exc:
203
225
  logger.warning("Failed to create symlink %s: %s", name, exc)
204
226
 
205
- # CHANGELOG.md symlink
227
+ # CHANGELOG.md symlink (file, not directory — junctions only work for dirs)
206
228
  changelog_link = claude_dir / "CHANGELOG.md"
207
229
  if changelog_link.exists() or changelog_link.is_symlink():
208
230
  changelog_link.unlink()
209
231
  try:
210
- os.symlink(os.path.join(rel_path, "CHANGELOG.md"), str(changelog_link))
232
+ if _IS_WINDOWS:
233
+ # File symlinks need admin on Windows; copy instead
234
+ shutil.copy2(str(package_path / "CHANGELOG.md"), str(changelog_link))
235
+ else:
236
+ os.symlink(os.path.join(rel_path, "CHANGELOG.md"), str(changelog_link))
211
237
  created.append("CHANGELOG.md")
212
238
  except OSError as exc:
213
239
  logger.warning("Failed to create CHANGELOG.md symlink: %s", exc)