@melihmucuk/leash 1.0.9 → 1.0.11

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/package.json CHANGED
@@ -1,16 +1,13 @@
1
1
  {
2
2
  "name": "@melihmucuk/leash",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "type": "module",
5
5
  "description": "Security guardrails for AI coding agents",
6
6
  "bin": {
7
- "leash": "./bin/leash.js"
7
+ "leash": "dist/cli/leash.js"
8
8
  },
9
9
  "files": [
10
- "dist/",
11
- "bin/",
12
- "!bin/test/",
13
- "packages/core/lib/version-checker.js"
10
+ "dist/"
14
11
  ],
15
12
  "author": "Melih Mucuk",
16
13
  "license": "MIT",
@@ -28,31 +25,43 @@
28
25
  "plugin",
29
26
  "hooks",
30
27
  "file-system",
31
- "protection"
28
+ "protection",
29
+ "pi-package"
32
30
  ],
31
+ "pi": {
32
+ "extensions": [
33
+ "./dist/pi/leash.js"
34
+ ]
35
+ },
33
36
  "homepage": "https://github.com/melihmucuk/leash",
34
37
  "bugs": {
35
38
  "url": "https://github.com/melihmucuk/leash/issues"
36
39
  },
37
40
  "repository": {
38
41
  "type": "git",
39
- "url": "https://github.com/melihmucuk/leash.git"
42
+ "url": "git+https://github.com/melihmucuk/leash.git"
40
43
  },
41
44
  "scripts": {
42
- "test": "npm run build:core && node --test 'packages/core/test/*.test.js' 'bin/test/*.test.js'",
45
+ "test": "npm run build:core && npm run build:cli-lib && node --test 'packages/core/test/*.test.js' 'packages/cli/test/*.test.js'",
43
46
  "typecheck": "tsc --noEmit",
44
- "build": "npm run build:core && npm run build:opencode && npm run build:pi && npm run build:claude-code && npm run build:factory",
47
+ "build": "npm run build:core && npm run build:cli && npm run build:opencode && npm run build:pi && npm run build:claude-code && npm run build:factory",
45
48
  "build:core": "esbuild packages/core/*.ts --outdir=packages/core/lib --platform=node --format=esm",
49
+ "build:cli-lib": "esbuild packages/cli/lib.ts --outdir=packages/cli/lib --platform=node --format=esm --main-fields=module,main",
50
+ "build:cli": "esbuild packages/cli/leash.ts --bundle --outfile=dist/cli/leash.js --platform=node --format=esm --main-fields=module,main --banner:js='#!/usr/bin/env node'",
46
51
  "build:opencode": "esbuild packages/opencode/leash.ts --bundle --outfile=dist/opencode/leash.js --platform=node --format=esm --external:@opencode-ai/plugin",
47
52
  "build:pi": "esbuild packages/pi/leash.ts --bundle --outfile=dist/pi/leash.js --platform=node --format=esm --external:@mariozechner/pi-coding-agent",
48
53
  "build:claude-code": "esbuild packages/claude-code/leash.ts --bundle --outfile=dist/claude-code/leash.js --platform=node --format=esm",
49
54
  "build:factory": "esbuild packages/factory/leash.ts --bundle --outfile=dist/factory/leash.js --platform=node --format=esm"
50
55
  },
56
+ "peerDependencies": {
57
+ "@mariozechner/pi-coding-agent": "*"
58
+ },
51
59
  "devDependencies": {
52
- "@mariozechner/pi-coding-agent": "^0.36.0",
53
- "@opencode-ai/plugin": "^1.0.224",
60
+ "@mariozechner/pi-coding-agent": "^0.37.8",
61
+ "@opencode-ai/plugin": "^1.1.4",
54
62
  "@types/node": "^22.19.3",
55
63
  "esbuild": "^0.27.2",
64
+ "jsonc-parser": "^3.3.1",
56
65
  "typescript": "^5.9.3"
57
66
  }
58
- }
67
+ }
package/bin/leash.js DELETED
@@ -1,204 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { existsSync } from "fs";
4
- import { dirname, join } from "path";
5
- import { homedir } from "os";
6
- import { fileURLToPath } from "url";
7
- import { execSync } from "child_process";
8
- import { PLATFORMS, setupPlatform, removePlatform } from "./lib.js";
9
- import { checkForUpdates } from "../packages/core/lib/version-checker.js";
10
-
11
- const __dirname = dirname(fileURLToPath(import.meta.url));
12
-
13
- function getDistPath() {
14
- return join(__dirname, "..", "dist");
15
- }
16
-
17
- function getConfigPath(platformKey) {
18
- const platform = PLATFORMS[platformKey];
19
- if (!platform) return null;
20
-
21
- // Support multiple config paths (first existing wins, fallback to last)
22
- if (platform.configPaths) {
23
- for (const p of platform.configPaths) {
24
- const full = join(homedir(), p);
25
- if (existsSync(full)) return full;
26
- }
27
- return join(homedir(), platform.configPaths.at(-1));
28
- }
29
-
30
- return join(homedir(), platform.configPath);
31
- }
32
-
33
- function getLeashPath(platformKey) {
34
- const platform = PLATFORMS[platformKey];
35
- return platform ? join(getDistPath(), platform.distPath) : null;
36
- }
37
-
38
- function setup(platformKey) {
39
- const configPath = getConfigPath(platformKey);
40
- const leashPath = getLeashPath(platformKey);
41
-
42
- if (!configPath || !leashPath) {
43
- console.error(`Unknown platform: ${platformKey}`);
44
- console.error(`Available: ${Object.keys(PLATFORMS).join(", ")}`);
45
- process.exit(1);
46
- }
47
-
48
- if (!existsSync(leashPath)) {
49
- console.error(`Leash not found at: ${leashPath}`);
50
- process.exit(1);
51
- }
52
-
53
- const result = setupPlatform(platformKey, configPath, leashPath);
54
-
55
- if (result.error) {
56
- console.error(result.error);
57
- process.exit(1);
58
- }
59
-
60
- if (result.skipped) {
61
- console.log(`[ok] Leash already installed for ${result.platform}`);
62
- return;
63
- }
64
-
65
- console.log(`[ok] Config: ${result.configPath}`);
66
- console.log(`[ok] Leash installed for ${result.platform}`);
67
- console.log(`[ok] Restart ${result.platform} to apply changes`);
68
- }
69
-
70
- function remove(platformKey) {
71
- const configPath = getConfigPath(platformKey);
72
-
73
- if (!configPath) {
74
- console.error(`Unknown platform: ${platformKey}`);
75
- console.error(`Available: ${Object.keys(PLATFORMS).join(", ")}`);
76
- process.exit(1);
77
- }
78
-
79
- const result = removePlatform(platformKey, configPath);
80
-
81
- if (result.error) {
82
- console.error(result.error);
83
- process.exit(1);
84
- }
85
-
86
- if (result.notFound) {
87
- console.log(`[ok] No config found for ${result.platform}`);
88
- return;
89
- }
90
-
91
- if (result.notInstalled) {
92
- console.log(`[ok] Leash not found in ${result.platform} config`);
93
- return;
94
- }
95
-
96
- console.log(`[ok] Leash removed from ${result.platform}`);
97
- console.log(`[ok] Restart ${result.platform} to apply changes`);
98
- }
99
-
100
- function showPath(platformKey) {
101
- const leashPath = getLeashPath(platformKey);
102
-
103
- if (!leashPath) {
104
- console.error(`Unknown platform: ${platformKey}`);
105
- console.error(`Available: ${Object.keys(PLATFORMS).join(", ")}`);
106
- process.exit(1);
107
- }
108
-
109
- console.log(leashPath);
110
- }
111
-
112
- async function update() {
113
- console.log("Checking for updates...");
114
-
115
- const result = await checkForUpdates();
116
-
117
- if (!result.hasUpdate) {
118
- console.log(`[ok] Already up to date (v${result.currentVersion})`);
119
- return;
120
- }
121
-
122
- console.log(`[ok] Update available: v${result.currentVersion} → v${result.latestVersion}`);
123
- console.log("[ok] Updating...");
124
-
125
- try {
126
- execSync("npm update -g @melihmucuk/leash", { stdio: "inherit" });
127
- console.log("[ok] Update complete");
128
- } catch {
129
- console.error("[error] Update failed. Try manually: npm update -g @melihmucuk/leash");
130
- process.exit(1);
131
- }
132
- }
133
-
134
- function showHelp() {
135
- console.log(`
136
- leash - Security guardrails for AI coding agents
137
-
138
- Usage:
139
- leash --setup <platform> Install leash for a platform
140
- leash --remove <platform> Remove leash from a platform
141
- leash --path <platform> Show leash path for a platform
142
- leash --update Update leash to latest version
143
- leash --help Show this help
144
-
145
- Platforms:
146
- opencode OpenCode
147
- pi Pi Coding Agent
148
- claude-code Claude Code
149
- factory Factory Droid
150
-
151
- Examples:
152
- leash --setup opencode
153
- leash --remove claude-code
154
- leash --path pi
155
- leash --update
156
- `);
157
- }
158
-
159
- const args = process.argv.slice(2);
160
- const command = args[0];
161
- const platform = args[1];
162
-
163
- switch (command) {
164
- case "--setup":
165
- case "-s":
166
- if (!platform) {
167
- console.error("Missing platform argument");
168
- showHelp();
169
- process.exit(1);
170
- }
171
- setup(platform);
172
- break;
173
- case "--remove":
174
- case "-r":
175
- if (!platform) {
176
- console.error("Missing platform argument");
177
- showHelp();
178
- process.exit(1);
179
- }
180
- remove(platform);
181
- break;
182
- case "--path":
183
- case "-p":
184
- if (!platform) {
185
- console.error("Missing platform argument");
186
- showHelp();
187
- process.exit(1);
188
- }
189
- showPath(platform);
190
- break;
191
- case "--update":
192
- case "-u":
193
- await update();
194
- break;
195
- case "--help":
196
- case "-h":
197
- case undefined:
198
- showHelp();
199
- break;
200
- default:
201
- console.error(`Unknown command: ${command}`);
202
- showHelp();
203
- process.exit(1);
204
- }
package/bin/lib.js DELETED
@@ -1,225 +0,0 @@
1
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
2
- import { dirname } from "path";
3
-
4
- export const PLATFORMS = {
5
- opencode: {
6
- name: "OpenCode",
7
- configPaths: [
8
- ".config/opencode/opencode.jsonc",
9
- ".config/opencode/opencode.json",
10
- ],
11
- distPath: "opencode/leash.js",
12
- setup: (config, leashPath) => {
13
- config.plugin = config.plugin || [];
14
- if (config.plugin.some((p) => p.includes("leash"))) {
15
- return { skipped: true };
16
- }
17
- config.plugin.push(leashPath);
18
- return { skipped: false };
19
- },
20
- remove: (config) => {
21
- if (!config.plugin) return false;
22
- const before = config.plugin.length;
23
- config.plugin = config.plugin.filter((p) => !p.includes("leash"));
24
- return config.plugin.length < before;
25
- },
26
- },
27
- pi: {
28
- name: "Pi",
29
- configPath: ".pi/agent/settings.json",
30
- distPath: "pi/leash.js",
31
- setup: (config, leashPath) => {
32
- config.extensions = config.extensions || [];
33
- if (config.extensions.some((e) => e.includes("leash"))) {
34
- return { skipped: true };
35
- }
36
- config.extensions.push(leashPath);
37
- return { skipped: false };
38
- },
39
- remove: (config) => {
40
- if (!config.extensions) return false;
41
- const before = config.extensions.length;
42
- config.extensions = config.extensions.filter((e) => !e.includes("leash"));
43
- return config.extensions.length < before;
44
- },
45
- },
46
- "claude-code": {
47
- name: "Claude Code",
48
- configPath: ".claude/settings.json",
49
- distPath: "claude-code/leash.js",
50
- setup: (config, leashPath) => {
51
- config.hooks = config.hooks || {};
52
- const hookCommand = { type: "command", command: `node ${leashPath}` };
53
-
54
- // Check if already installed in either hook
55
- const inSessionStart = config.hooks.SessionStart?.some((entry) =>
56
- entry.hooks?.some((h) => h.command?.includes("leash"))
57
- );
58
- const inPreToolUse = config.hooks.PreToolUse?.some((entry) =>
59
- entry.hooks?.some((h) => h.command?.includes("leash"))
60
- );
61
- if (inSessionStart && inPreToolUse) {
62
- return { skipped: true };
63
- }
64
-
65
- // Add SessionStart hook
66
- if (!inSessionStart) {
67
- config.hooks.SessionStart = config.hooks.SessionStart || [];
68
- config.hooks.SessionStart.push({
69
- hooks: [hookCommand],
70
- });
71
- }
72
-
73
- // Add PreToolUse hook
74
- if (!inPreToolUse) {
75
- config.hooks.PreToolUse = config.hooks.PreToolUse || [];
76
- config.hooks.PreToolUse.push({
77
- matcher: "Bash|Write|Edit",
78
- hooks: [hookCommand],
79
- });
80
- }
81
-
82
- return { skipped: false };
83
- },
84
- remove: (config) => {
85
- if (!config.hooks) return false;
86
- let removed = false;
87
-
88
- if (config.hooks.SessionStart) {
89
- const before = config.hooks.SessionStart.length;
90
- config.hooks.SessionStart = config.hooks.SessionStart.filter(
91
- (entry) => !entry.hooks?.some((h) => h.command?.includes("leash"))
92
- );
93
- if (config.hooks.SessionStart.length < before) removed = true;
94
- }
95
-
96
- if (config.hooks.PreToolUse) {
97
- const before = config.hooks.PreToolUse.length;
98
- config.hooks.PreToolUse = config.hooks.PreToolUse.filter(
99
- (entry) => !entry.hooks?.some((h) => h.command?.includes("leash"))
100
- );
101
- if (config.hooks.PreToolUse.length < before) removed = true;
102
- }
103
-
104
- return removed;
105
- },
106
- },
107
- factory: {
108
- name: "Factory",
109
- configPath: ".factory/settings.json",
110
- distPath: "factory/leash.js",
111
- setup: (config, leashPath) => {
112
- config.hooks = config.hooks || {};
113
- const hookCommand = { type: "command", command: `node ${leashPath}` };
114
-
115
- // Check if already installed in either hook
116
- const inSessionStart = config.hooks.SessionStart?.some((entry) =>
117
- entry.hooks?.some((h) => h.command?.includes("leash"))
118
- );
119
- const inPreToolUse = config.hooks.PreToolUse?.some((entry) =>
120
- entry.hooks?.some((h) => h.command?.includes("leash"))
121
- );
122
- if (inSessionStart && inPreToolUse) {
123
- return { skipped: true };
124
- }
125
-
126
- // Add SessionStart hook
127
- if (!inSessionStart) {
128
- config.hooks.SessionStart = config.hooks.SessionStart || [];
129
- config.hooks.SessionStart.push({
130
- hooks: [hookCommand],
131
- });
132
- }
133
-
134
- // Add PreToolUse hook
135
- if (!inPreToolUse) {
136
- config.hooks.PreToolUse = config.hooks.PreToolUse || [];
137
- config.hooks.PreToolUse.push({
138
- matcher: "Execute|Write|Edit",
139
- hooks: [hookCommand],
140
- });
141
- }
142
-
143
- return { skipped: false };
144
- },
145
- remove: (config) => {
146
- if (!config.hooks) return false;
147
- let removed = false;
148
-
149
- if (config.hooks.SessionStart) {
150
- const before = config.hooks.SessionStart.length;
151
- config.hooks.SessionStart = config.hooks.SessionStart.filter(
152
- (entry) => !entry.hooks?.some((h) => h.command?.includes("leash"))
153
- );
154
- if (config.hooks.SessionStart.length < before) removed = true;
155
- }
156
-
157
- if (config.hooks.PreToolUse) {
158
- const before = config.hooks.PreToolUse.length;
159
- config.hooks.PreToolUse = config.hooks.PreToolUse.filter(
160
- (entry) => !entry.hooks?.some((h) => h.command?.includes("leash"))
161
- );
162
- if (config.hooks.PreToolUse.length < before) removed = true;
163
- }
164
-
165
- return removed;
166
- },
167
- },
168
- };
169
-
170
- export function readConfig(configPath) {
171
- if (!existsSync(configPath)) {
172
- return {};
173
- }
174
- try {
175
- return JSON.parse(readFileSync(configPath, "utf-8"));
176
- } catch {
177
- return {};
178
- }
179
- }
180
-
181
- export function writeConfig(configPath, config) {
182
- const dir = dirname(configPath);
183
- if (!existsSync(dir)) {
184
- mkdirSync(dir, { recursive: true });
185
- }
186
- writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
187
- }
188
-
189
- export function setupPlatform(platformKey, configPath, leashPath) {
190
- const platform = PLATFORMS[platformKey];
191
- if (!platform) {
192
- return { error: `Unknown platform: ${platformKey}` };
193
- }
194
-
195
- const config = readConfig(configPath);
196
- const result = platform.setup(config, leashPath);
197
-
198
- if (result.skipped) {
199
- return { skipped: true, platform: platform.name };
200
- }
201
-
202
- writeConfig(configPath, config);
203
- return { success: true, platform: platform.name, configPath };
204
- }
205
-
206
- export function removePlatform(platformKey, configPath) {
207
- const platform = PLATFORMS[platformKey];
208
- if (!platform) {
209
- return { error: `Unknown platform: ${platformKey}` };
210
- }
211
-
212
- if (!existsSync(configPath)) {
213
- return { notFound: true, platform: platform.name };
214
- }
215
-
216
- const config = readConfig(configPath);
217
- const removed = platform.remove(config);
218
-
219
- if (!removed) {
220
- return { notInstalled: true, platform: platform.name };
221
- }
222
-
223
- writeConfig(configPath, config);
224
- return { success: true, platform: platform.name };
225
- }
@@ -1,44 +0,0 @@
1
- import { readFileSync, existsSync } from "fs";
2
- import { dirname, join } from "path";
3
- import { fileURLToPath } from "url";
4
- function getVersion() {
5
- const __dirname = dirname(fileURLToPath(import.meta.url));
6
- const candidates = [
7
- join(__dirname, "..", "..", "package.json"),
8
- join(__dirname, "..", "..", "..", "package.json")
9
- ];
10
- for (const path of candidates) {
11
- if (existsSync(path)) {
12
- try {
13
- const pkg = JSON.parse(readFileSync(path, "utf-8"));
14
- if (pkg.name === "@melihmucuk/leash") {
15
- return pkg.version;
16
- }
17
- } catch {
18
- }
19
- }
20
- }
21
- return "0.0.0";
22
- }
23
- const CURRENT_VERSION = getVersion();
24
- const NPM_REGISTRY_URL = "https://registry.npmjs.org/@melihmucuk/leash/latest";
25
- async function checkForUpdates() {
26
- try {
27
- const response = await fetch(NPM_REGISTRY_URL);
28
- if (!response.ok) {
29
- return { hasUpdate: false, currentVersion: CURRENT_VERSION };
30
- }
31
- const data = await response.json();
32
- return {
33
- hasUpdate: data.version !== CURRENT_VERSION,
34
- latestVersion: data.version,
35
- currentVersion: CURRENT_VERSION
36
- };
37
- } catch {
38
- return { hasUpdate: false, currentVersion: CURRENT_VERSION };
39
- }
40
- }
41
- export {
42
- CURRENT_VERSION,
43
- checkForUpdates
44
- };