@safedep/gryph 0.0.1
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/.npmignore +17 -0
- package/README.md +37 -0
- package/bin/gryph.js +76 -0
- package/config.js +34 -0
- package/install.js +234 -0
- package/package.json +58 -0
package/.npmignore
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Exclude the actual binary (but keep the wrapper script)
|
|
2
|
+
bin/gryph
|
|
3
|
+
bin/gryph.exe
|
|
4
|
+
|
|
5
|
+
# Exclude temp files and directories
|
|
6
|
+
temp/
|
|
7
|
+
.temp/
|
|
8
|
+
|
|
9
|
+
# Exclude development files
|
|
10
|
+
node_modules/
|
|
11
|
+
.git/
|
|
12
|
+
.gitignore
|
|
13
|
+
*.log
|
|
14
|
+
.DS_Store
|
|
15
|
+
|
|
16
|
+
# Keep only the wrapper script in bin/
|
|
17
|
+
!bin/gryph.js
|
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Gryph
|
|
2
|
+
|
|
3
|
+
AI coding agents read files, write code, and execute commands on your behalf. But what exactly did they do?
|
|
4
|
+
|
|
5
|
+
**Gryph** is a local-first audit trail for AI coding agents. It hooks into your agents, logs every action to a local SQLite database, and gives you powerful querying capabilities to understand, review, and debug agent activity.
|
|
6
|
+
|
|
7
|
+
## Why Gryph?
|
|
8
|
+
|
|
9
|
+
- **Transparency** - See exactly what files were read, written, and what commands were run
|
|
10
|
+
- **Pre-commit review** - Verify agent changes before committing to git
|
|
11
|
+
- **Debugging** - Replay sessions to understand what went wrong
|
|
12
|
+
- **Privacy** - All data stays local. No cloud, no telemetry
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g @safedep/gryph
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Install hooks for all detected agents
|
|
24
|
+
gryph install
|
|
25
|
+
|
|
26
|
+
# Verify installation
|
|
27
|
+
gryph status
|
|
28
|
+
|
|
29
|
+
# Start using your AI coding agent (Claude Code, Cursor, Gemini CLI, etc.)
|
|
30
|
+
# ...
|
|
31
|
+
|
|
32
|
+
# Review what happened
|
|
33
|
+
gryph logs
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
See more details at our [GitHub Project](https://github.com/safedep/gryph)
|
package/bin/gryph.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { spawn } = require("child_process");
|
|
6
|
+
const { ORG_NAME, PACKAGE_NAME, BINARY_NAME } = require("../config");
|
|
7
|
+
|
|
8
|
+
const BINARY_NAME_WITH_EXT =
|
|
9
|
+
process.platform === "win32" ? `${BINARY_NAME}.exe` : BINARY_NAME;
|
|
10
|
+
const BINARY_PATH = path.join(__dirname, BINARY_NAME_WITH_EXT);
|
|
11
|
+
|
|
12
|
+
function main() {
|
|
13
|
+
// Check if binary exists
|
|
14
|
+
if (!fs.existsSync(BINARY_PATH)) {
|
|
15
|
+
console.error(`❌ ${BINARY_NAME_WITH_EXT} binary not found`);
|
|
16
|
+
console.error(
|
|
17
|
+
`Try reinstalling: npm install -g ${ORG_NAME}/${PACKAGE_NAME}`,
|
|
18
|
+
);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Verify binary is executable
|
|
23
|
+
try {
|
|
24
|
+
fs.accessSync(BINARY_PATH, fs.constants.F_OK | fs.constants.X_OK);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error(`❌ ${BINARY_NAME_WITH_EXT} is not executable`);
|
|
27
|
+
console.error(
|
|
28
|
+
`Try reinstalling: npm install -g ${ORG_NAME}/${PACKAGE_NAME}`,
|
|
29
|
+
);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Pass all arguments to the binary
|
|
34
|
+
const args = process.argv.slice(2);
|
|
35
|
+
|
|
36
|
+
// Spawn the binary with inherited stdio for proper terminal interaction
|
|
37
|
+
const child = spawn(BINARY_PATH, args, {
|
|
38
|
+
stdio: "inherit",
|
|
39
|
+
windowsHide: false,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Handle process termination
|
|
43
|
+
child.on("error", (error) => {
|
|
44
|
+
console.error(
|
|
45
|
+
`❌ Failed to execute ${BINARY_NAME_WITH_EXT}: ${error.message}`,
|
|
46
|
+
);
|
|
47
|
+
console.error(
|
|
48
|
+
`Try reinstalling: npm install -g ${ORG_NAME}/${PACKAGE_NAME}`,
|
|
49
|
+
);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Exit with the same code as the child process
|
|
54
|
+
child.on("exit", (code, signal) => {
|
|
55
|
+
if (signal) {
|
|
56
|
+
process.kill(process.pid, signal);
|
|
57
|
+
} else {
|
|
58
|
+
process.exit(code || 0);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Handle termination signals
|
|
63
|
+
process.on("SIGTERM", () => {
|
|
64
|
+
child.kill("SIGTERM");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
process.on("SIGINT", () => {
|
|
68
|
+
child.kill("SIGINT");
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (require.main === module) {
|
|
73
|
+
main();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { main };
|
package/config.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Configuration for npm binary wrapper
|
|
2
|
+
|
|
3
|
+
const ORG_NAME = "@safedep";
|
|
4
|
+
const PACKAGE_NAME = "gryph";
|
|
5
|
+
const BINARY_NAME = "gryph";
|
|
6
|
+
|
|
7
|
+
// GitHub repository information for releases
|
|
8
|
+
const REPO_OWNER = "safedep";
|
|
9
|
+
const REPO_NAME = "gryph";
|
|
10
|
+
|
|
11
|
+
// GitHub releases base URL (constructed from repo info)
|
|
12
|
+
const GITHUB_RELEASES_BASE = `https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download`;
|
|
13
|
+
|
|
14
|
+
// Platform-specific binary filename patterns (GoReleaser format)
|
|
15
|
+
const BINARY_PATTERNS = {
|
|
16
|
+
"darwin-x64": `${BINARY_NAME}_Darwin_all.tar.gz`,
|
|
17
|
+
"darwin-arm64": `${BINARY_NAME}_Darwin_all.tar.gz`,
|
|
18
|
+
"linux-x64": `${BINARY_NAME}_Linux_x86_64.tar.gz`,
|
|
19
|
+
"linux-arm64": `${BINARY_NAME}_Linux_arm64.tar.gz`,
|
|
20
|
+
"linux-ia32": `${BINARY_NAME}_Linux_i386.tar.gz`,
|
|
21
|
+
"win32-x64": `${BINARY_NAME}_Windows_x86_64.zip`,
|
|
22
|
+
"win32-arm64": `${BINARY_NAME}_Windows_arm64.zip`,
|
|
23
|
+
"win32-ia32": `${BINARY_NAME}_Windows_i386.zip`,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
module.exports = {
|
|
27
|
+
ORG_NAME,
|
|
28
|
+
PACKAGE_NAME,
|
|
29
|
+
BINARY_NAME,
|
|
30
|
+
REPO_OWNER,
|
|
31
|
+
REPO_NAME,
|
|
32
|
+
GITHUB_RELEASES_BASE,
|
|
33
|
+
BINARY_PATTERNS,
|
|
34
|
+
};
|
package/install.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const https = require("https");
|
|
7
|
+
const crypto = require("crypto");
|
|
8
|
+
const { execSync } = require("child_process");
|
|
9
|
+
const {
|
|
10
|
+
BINARY_NAME,
|
|
11
|
+
REPO_OWNER,
|
|
12
|
+
REPO_NAME,
|
|
13
|
+
GITHUB_RELEASES_BASE,
|
|
14
|
+
BINARY_PATTERNS,
|
|
15
|
+
} = require("./config");
|
|
16
|
+
|
|
17
|
+
// Read version from package.json with strict validation
|
|
18
|
+
function getValidatedVersion() {
|
|
19
|
+
try {
|
|
20
|
+
const packageJson = JSON.parse(
|
|
21
|
+
fs.readFileSync(path.join(__dirname, "package.json"), "utf8"),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const version = packageJson.version;
|
|
25
|
+
|
|
26
|
+
// Strict validation: must be valid semver (x.y.z)
|
|
27
|
+
if (!/^\d+\.\d+\.\d+$/.test(version)) {
|
|
28
|
+
throw new Error(`Invalid version format: ${version}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return `v${version}`;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
throw new Error(`Failed to read valid version: ${error.message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const RELEASE_VERSION = getValidatedVersion();
|
|
38
|
+
const BASE_URL = `${GITHUB_RELEASES_BASE}/${RELEASE_VERSION}`;
|
|
39
|
+
|
|
40
|
+
// Platform-specific binary URLs (constructed from config)
|
|
41
|
+
const BINARY_URLS = {};
|
|
42
|
+
Object.keys(BINARY_PATTERNS).forEach((platform) => {
|
|
43
|
+
BINARY_URLS[platform] = `${BASE_URL}/${BINARY_PATTERNS[platform]}`;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const CHECKSUMS_URL = `${BASE_URL}/checksums.txt`;
|
|
47
|
+
|
|
48
|
+
function getPlatformKey() {
|
|
49
|
+
const platform = process.platform;
|
|
50
|
+
const arch = process.arch;
|
|
51
|
+
return `${platform}-${arch}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function downloadFile(url, dest, maxRedirects = 5) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
if (maxRedirects < 0) {
|
|
57
|
+
reject(new Error("Too many redirects"));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const file = fs.createWriteStream(dest);
|
|
62
|
+
|
|
63
|
+
https
|
|
64
|
+
.get(url, (response) => {
|
|
65
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
66
|
+
file.close();
|
|
67
|
+
fs.unlink(dest, () => { });
|
|
68
|
+
return downloadFile(response.headers.location, dest, maxRedirects - 1)
|
|
69
|
+
.then(resolve)
|
|
70
|
+
.catch(reject);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (response.statusCode !== 200) {
|
|
74
|
+
file.close();
|
|
75
|
+
fs.unlink(dest, () => { });
|
|
76
|
+
reject(new Error(`Download failed: ${response.statusCode}`));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
response.pipe(file);
|
|
81
|
+
|
|
82
|
+
file.on("finish", () => {
|
|
83
|
+
file.close();
|
|
84
|
+
resolve();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
file.on("error", (err) => {
|
|
88
|
+
fs.unlink(dest, () => { });
|
|
89
|
+
reject(err);
|
|
90
|
+
});
|
|
91
|
+
})
|
|
92
|
+
.on("error", reject);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function calculateChecksum(filePath) {
|
|
97
|
+
const fileBuffer = fs.readFileSync(filePath);
|
|
98
|
+
const hashSum = crypto.createHash("sha256");
|
|
99
|
+
hashSum.update(fileBuffer);
|
|
100
|
+
return hashSum.digest("hex");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function validateChecksum(filePath, expectedChecksum) {
|
|
104
|
+
const actualChecksum = calculateChecksum(filePath);
|
|
105
|
+
return actualChecksum === expectedChecksum;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function extractArchive(archivePath, extractDir) {
|
|
109
|
+
const isZip = archivePath.endsWith(".zip");
|
|
110
|
+
|
|
111
|
+
if (isZip) {
|
|
112
|
+
execSync(`unzip -o "${archivePath}" -d "${extractDir}"`, { stdio: "pipe" });
|
|
113
|
+
} else {
|
|
114
|
+
execSync(`tar -xzf "${archivePath}" -C "${extractDir}"`, { stdio: "pipe" });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function install() {
|
|
119
|
+
let tempWorkspace;
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
console.log("📦 Installing Gryph binary...");
|
|
123
|
+
|
|
124
|
+
// Get platform-specific URL
|
|
125
|
+
const platformKey = getPlatformKey();
|
|
126
|
+
const binaryUrl = BINARY_URLS[platformKey];
|
|
127
|
+
|
|
128
|
+
if (!binaryUrl) {
|
|
129
|
+
throw new Error(`Unsupported platform: ${platformKey}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log(`🔍 Platform: ${platformKey}`);
|
|
133
|
+
console.log(`📡 Version: ${RELEASE_VERSION}`);
|
|
134
|
+
|
|
135
|
+
// Create directories
|
|
136
|
+
const binDir = path.join(__dirname, "bin");
|
|
137
|
+
tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), "gryph-install-"));
|
|
138
|
+
|
|
139
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
140
|
+
|
|
141
|
+
// Download binary archive
|
|
142
|
+
const archiveFilename = path.basename(binaryUrl);
|
|
143
|
+
const archivePath = path.join(tempWorkspace, archiveFilename);
|
|
144
|
+
|
|
145
|
+
console.log(`⬇️ Downloading binary...`);
|
|
146
|
+
await downloadFile(binaryUrl, archivePath);
|
|
147
|
+
|
|
148
|
+
// Download checksums
|
|
149
|
+
const checksumsPath = path.join(tempWorkspace, "checksums.txt");
|
|
150
|
+
console.log(`⬇️ Downloading checksums...`);
|
|
151
|
+
await downloadFile(CHECKSUMS_URL, checksumsPath);
|
|
152
|
+
|
|
153
|
+
// Parse checksums file
|
|
154
|
+
const checksumsContent = fs.readFileSync(checksumsPath, "utf8");
|
|
155
|
+
const checksumLines = checksumsContent.split("\n");
|
|
156
|
+
|
|
157
|
+
let expectedChecksum = null;
|
|
158
|
+
for (const line of checksumLines) {
|
|
159
|
+
if (line.includes(archiveFilename)) {
|
|
160
|
+
expectedChecksum = line.split(/\s+/)[0];
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!expectedChecksum) {
|
|
166
|
+
throw new Error(`Checksum not found for ${archiveFilename}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Validate checksum
|
|
170
|
+
console.log(`🔐 Validating checksum...`);
|
|
171
|
+
if (!validateChecksum(archivePath, expectedChecksum)) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
"Checksum validation failed - binary may be corrupted or tampered",
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log(`✅ Checksum validated`);
|
|
178
|
+
|
|
179
|
+
// Extract archive
|
|
180
|
+
console.log(`📂 Extracting binary...`);
|
|
181
|
+
extractArchive(archivePath, tempWorkspace);
|
|
182
|
+
|
|
183
|
+
// Find and move binary
|
|
184
|
+
const binaryName =
|
|
185
|
+
process.platform === "win32" ? `${BINARY_NAME}.exe` : BINARY_NAME;
|
|
186
|
+
const extractedBinaryPath = path.join(tempWorkspace, binaryName);
|
|
187
|
+
const finalBinaryPath = path.join(binDir, binaryName);
|
|
188
|
+
|
|
189
|
+
if (!fs.existsSync(extractedBinaryPath)) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`Binary not found at expected location: ${extractedBinaryPath}`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Move binary to final location (handle cross-device links)
|
|
196
|
+
try {
|
|
197
|
+
fs.renameSync(extractedBinaryPath, finalBinaryPath);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (error.code === "EXDEV") {
|
|
200
|
+
// Cross-device link not permitted, copy and delete instead
|
|
201
|
+
fs.copyFileSync(extractedBinaryPath, finalBinaryPath);
|
|
202
|
+
fs.unlinkSync(extractedBinaryPath);
|
|
203
|
+
} else {
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Make executable on Unix systems
|
|
209
|
+
if (process.platform !== "win32") {
|
|
210
|
+
fs.chmodSync(finalBinaryPath, "755");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Clean up
|
|
214
|
+
fs.rmSync(tempWorkspace, { recursive: true, force: true });
|
|
215
|
+
|
|
216
|
+
console.log("✅ Gryph binary installed successfully!");
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.error("❌ Installation failed:", error.message);
|
|
219
|
+
|
|
220
|
+
// Clean up on failure
|
|
221
|
+
try {
|
|
222
|
+
if (tempWorkspace && fs.existsSync(tempWorkspace)) {
|
|
223
|
+
fs.rmSync(tempWorkspace, { recursive: true, force: true });
|
|
224
|
+
}
|
|
225
|
+
} catch (cleanupError) {
|
|
226
|
+
console.warn("⚠️ Failed to clean up:", cleanupError.message);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Run installation
|
|
234
|
+
install();
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@safedep/gryph",
|
|
3
|
+
"description": "AI coding agent audit trail tool",
|
|
4
|
+
"main": "bin/gryph.js",
|
|
5
|
+
"bin": {
|
|
6
|
+
"gryph": "bin/gryph.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"preinstall": "echo \"Installing Gryph binary for your platform...\"",
|
|
10
|
+
"postinstall": "node install.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"security",
|
|
14
|
+
"package-manager",
|
|
15
|
+
"malicious-packages",
|
|
16
|
+
"npm",
|
|
17
|
+
"cli",
|
|
18
|
+
"vulnerability",
|
|
19
|
+
"dependency-security",
|
|
20
|
+
"safedep"
|
|
21
|
+
],
|
|
22
|
+
"author": "SafeDep <devops@safedep.io>",
|
|
23
|
+
"license": "Apache-2.0",
|
|
24
|
+
"homepage": "https://github.com/safedep/gryph#readme",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/safedep/gryph.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/safedep/gryph/issues"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=14"
|
|
34
|
+
},
|
|
35
|
+
"os": [
|
|
36
|
+
"darwin",
|
|
37
|
+
"linux",
|
|
38
|
+
"win32"
|
|
39
|
+
],
|
|
40
|
+
"cpu": [
|
|
41
|
+
"x64",
|
|
42
|
+
"arm64",
|
|
43
|
+
"ia32"
|
|
44
|
+
],
|
|
45
|
+
"files": [
|
|
46
|
+
"bin/gryph.js",
|
|
47
|
+
"install.js",
|
|
48
|
+
"config.js",
|
|
49
|
+
"test.js",
|
|
50
|
+
"README.md",
|
|
51
|
+
".npmignore"
|
|
52
|
+
],
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {},
|
|
57
|
+
"version": "0.0.1"
|
|
58
|
+
}
|