@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/bin/gaia-doctor.js +7 -1
- package/bin/gaia-scan +34 -10
- package/bin/gaia-update.js +15 -4
- package/dist/gaia-ops/.claude-plugin/plugin.json +1 -1
- package/dist/gaia-ops/hooks/modules/security/mutative_verbs.py +5 -1
- package/dist/gaia-ops/tools/scan/setup.py +30 -4
- package/dist/gaia-security/.claude-plugin/plugin.json +1 -1
- package/dist/gaia-security/hooks/modules/security/mutative_verbs.py +5 -1
- package/hooks/modules/security/mutative_verbs.py +5 -1
- package/package.json +1 -1
- package/tools/scan/setup.py +30 -4
|
@@ -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.
|
|
11
|
+
"version": "5.0.0-beta.3",
|
|
12
12
|
"source": "./dist/gaia-security"
|
|
13
13
|
}
|
|
14
14
|
]
|
package/bin/gaia-doctor.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
9
|
-
|
|
12
|
+
import { execFileSync } from 'child_process';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import { dirname, join } from 'path';
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/bin/gaia-update.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
package/tools/scan/setup.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|