cc-safety-net 0.8.0 → 0.8.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/README.md CHANGED
@@ -164,11 +164,9 @@ Running both together provides defense-in-depth. Sandboxing handles unknown thre
164
164
  ```bash
165
165
  /plugin marketplace add kenryu42/cc-marketplace
166
166
  /plugin install safety-net@cc-marketplace
167
+ /reload-plugins
167
168
  ```
168
169
 
169
- > [!NOTE]
170
- > After installing the plugin, you need to restart your Claude Code for it to take effect.
171
-
172
170
  ### Claude Code Auto-Update
173
171
 
174
172
  1. Run `/plugin` → Select `Marketplaces` → Choose `cc-marketplace` → Enable auto-update
@@ -207,95 +205,14 @@ gemini extensions install https://github.com/kenryu42/gemini-safety-net
207
205
 
208
206
  ### GitHub Copilot CLI Installation
209
207
 
210
- Safety Net supports GitHub Copilot CLI via its [hooks system](https://docs.github.com/en/copilot/concepts/agents/coding-agent/about-hooks).
211
-
212
- > [!NOTE]
213
- > Copilot CLI currently supports two hook configuration styles:
214
- >
215
- > - Hook files:
216
- > - repository: `.github/hooks/*.json`
217
- > - user: `~/.copilot/hooks/*.json` on Copilot CLI `0.0.422+`
218
- > - Inline `hooks` inside Copilot config files on Copilot CLI `1.0.8+`:
219
- > - user: `~/.copilot/config.json`
220
- > - repository: `.github/copilot/settings.json`
221
- > - local override: `.github/copilot/settings.local.json`
222
- >
223
- > Copilot settings cascade from user -> repository -> local (later files override earlier ones, so local overrides repository overrides user). `disableAllHooks: true` disables both repo-level and user-level hooks. If you use `COPILOT_HOME`, replace `~/.copilot` with that directory.
224
-
225
- #### Option A: Hook Files
226
-
227
- This is the classic hook-file format. It still works, and it is the easiest shared setup for a repository.
228
-
229
- 1. **Create the hooks directory** in your repository:
230
-
231
- ```bash
232
- mkdir -p .github/hooks
233
- ```
234
-
235
- 2. **Create `.github/hooks/safety-net.json`**:
236
-
237
- ```json
238
- {
239
- "version": 1,
240
- "hooks": {
241
- "preToolUse": [
242
- {
243
- "type": "command",
244
- "bash": "npx -y cc-safety-net --copilot-cli",
245
- "cwd": ".",
246
- "timeoutSec": 15
247
- }
248
- ]
249
- }
250
- }
251
- ```
252
-
253
- 3. **Restart Copilot CLI** — hooks are loaded at session start.
254
-
255
- The hook will intercept shell commands and block destructive operations before they execute.
256
-
257
- To install the same hook globally for your user account on Copilot CLI `0.0.422+`, place the same JSON file in:
258
-
259
- - `~/.copilot/hooks/safety-net.json`
260
-
261
- #### Option B: Inline Hooks In Copilot Settings
262
-
263
- On Copilot CLI `1.0.8+`, you can define the same hook inline in Copilot settings files instead of a separate `.json` file under `.github/hooks` or `~/.copilot/hooks`.
264
-
265
- ```json
266
- {
267
- "hooks": {
268
- "preToolUse": [
269
- {
270
- "type": "command",
271
- "bash": "npx -y cc-safety-net --copilot-cli",
272
- "cwd": ".",
273
- "timeoutSec": 15
274
- }
275
- ]
276
- }
277
- }
208
+ ```bash
209
+ /plugin install kenryu42/copilot-safety-net
278
210
  ```
279
211
 
280
- Use that schema in one of these files:
281
-
282
- - `~/.copilot/config.json`
283
- - `.github/copilot/settings.json`
284
- - `.github/copilot/settings.local.json`
285
-
286
- Recommended usage:
287
-
288
- - Use `~/.copilot/config.json` for your personal default across repositories.
289
- - Use `.github/copilot/settings.json` for a committed repository-wide setup.
290
- - Use `.github/copilot/settings.local.json` for personal repo-specific overrides, and add it to `.gitignore`.
291
-
292
- If you need to turn hooks off explicitly, set:
212
+ > [!NOTE]
213
+ > After installing the plugin, you need to restart your Copilot CLI for it to take effect.
293
214
 
294
- ```json
295
- {
296
- "disableAllHooks": true
297
- }
298
- ```
215
+ ---
299
216
 
300
217
  ## Status Line Integration
301
218
 
@@ -3731,6 +3731,7 @@ function analyzeCommand(command, options = {}) {
3731
3731
  }
3732
3732
 
3733
3733
  // src/bin/doctor/hooks.ts
3734
+ var COPILOT_PLUGIN_CONFIG_PATH = "copilot-plugin";
3734
3735
  var SELF_TEST_CASES = [
3735
3736
  { command: "git reset --hard", description: "git reset --hard", expectBlocked: true },
3736
3737
  { command: "rm -rf /", description: "rm -rf /", expectBlocked: true },
@@ -4178,13 +4179,15 @@ function detectAllHooks(cwd, options) {
4178
4179
  errors: errors.length > 0 ? errors : undefined
4179
4180
  };
4180
4181
  }
4181
- if (hooksCheck.activeConfigPaths.length > 0) {
4182
+ if (options?.copilotPluginInstalled === true || hooksCheck.activeConfigPaths.length > 0) {
4183
+ const viaPlugin = options?.copilotPluginInstalled === true;
4184
+ const primaryConfigPath = hooksCheck.activeConfigPaths[0];
4182
4185
  return {
4183
4186
  platform: "copilot-cli",
4184
4187
  status: "configured",
4185
- method: "hook config",
4186
- configPath: hooksCheck.activeConfigPaths[0],
4187
- configPaths: hooksCheck.activeConfigPaths,
4188
+ method: viaPlugin ? "plugin list" : "hook config",
4189
+ configPath: primaryConfigPath ?? (viaPlugin ? COPILOT_PLUGIN_CONFIG_PATH : undefined),
4190
+ configPaths: hooksCheck.activeConfigPaths.length > 0 ? hooksCheck.activeConfigPaths : undefined,
4188
4191
  selfTest: runSelfTest(),
4189
4192
  errors: errors.length > 0 ? errors : undefined
4190
4193
  };
@@ -4205,11 +4208,12 @@ function detectAllHooks(cwd, options) {
4205
4208
 
4206
4209
  // src/bin/doctor/system-info.ts
4207
4210
  import { spawn } from "node:child_process";
4208
- var CURRENT_VERSION = "0.8.0";
4211
+ var CURRENT_VERSION = "0.8.1";
4209
4212
  var VERSION_FETCH_TIMEOUT_MS = 2000;
4210
4213
  function getPackageVersion() {
4211
4214
  return CURRENT_VERSION;
4212
4215
  }
4216
+ var COPILOT_PLUGIN_ID = "copilot-safety-net";
4213
4217
  var defaultVersionFetcher = async (args) => {
4214
4218
  const [cmd, ...rest] = args;
4215
4219
  if (!cmd)
@@ -4260,6 +4264,12 @@ function parseVersion(output) {
4260
4264
  `)[0]?.trim();
4261
4265
  return firstLine || null;
4262
4266
  }
4267
+ function hasCopilotSafetyNetPlugin(output) {
4268
+ if (!output)
4269
+ return false;
4270
+ const pluginPattern = new RegExp(`(^|[^a-z0-9-])${COPILOT_PLUGIN_ID}([^a-z0-9-]|$)`, "m");
4271
+ return pluginPattern.test(output);
4272
+ }
4263
4273
  async function getSystemInfo(fetcher = defaultVersionFetcher) {
4264
4274
  const fetchCopilotVersion = async () => {
4265
4275
  const binaryVersionPromise = fetcher(["copilot", "--binary-version"]);
@@ -4270,14 +4280,15 @@ async function getSystemInfo(fetcher = defaultVersionFetcher) {
4270
4280
  }
4271
4281
  return fallbackVersionPromise;
4272
4282
  };
4273
- const [claudeRaw, openCodeRaw, geminiRaw, copilotRaw, nodeRaw, npmRaw, bunRaw] = await Promise.all([
4283
+ const [claudeRaw, openCodeRaw, geminiRaw, copilotRaw, nodeRaw, npmRaw, bunRaw, pluginListRaw] = await Promise.all([
4274
4284
  fetcher(["claude", "--version"]),
4275
4285
  fetcher(["opencode", "--version"]),
4276
4286
  fetcher(["gemini", "--version"]),
4277
4287
  fetchCopilotVersion(),
4278
4288
  fetcher(["node", "--version"]),
4279
4289
  fetcher(["npm", "--version"]),
4280
- fetcher(["bun", "--version"])
4290
+ fetcher(["bun", "--version"]),
4291
+ fetcher(["copilot", "plugin", "list"])
4281
4292
  ]);
4282
4293
  return {
4283
4294
  version: CURRENT_VERSION,
@@ -4288,6 +4299,7 @@ async function getSystemInfo(fetcher = defaultVersionFetcher) {
4288
4299
  nodeVersion: parseVersion(nodeRaw),
4289
4300
  npmVersion: parseVersion(npmRaw),
4290
4301
  bunVersion: parseVersion(bunRaw),
4302
+ copilotPluginInstalled: hasCopilotSafetyNetPlugin(pluginListRaw),
4291
4303
  platform: `${process.platform} ${process.arch}`
4292
4304
  };
4293
4305
  }
@@ -4353,7 +4365,10 @@ function parseDoctorFlags(args) {
4353
4365
  async function runDoctor(options = {}) {
4354
4366
  const cwd = options.cwd ?? process.cwd();
4355
4367
  const system = await getSystemInfo();
4356
- const hooks = detectAllHooks(cwd, { copilotCliVersion: system.copilotCliVersion });
4368
+ const hooks = detectAllHooks(cwd, {
4369
+ copilotCliVersion: system.copilotCliVersion,
4370
+ copilotPluginInstalled: system.copilotPluginInstalled
4371
+ });
4357
4372
  const configInfo = getConfigInfo(cwd);
4358
4373
  const environment = getEnvironmentInfo();
4359
4374
  const activity = getActivitySummary(7);
@@ -5276,7 +5291,7 @@ function formatTraceJson(result) {
5276
5291
  return JSON.stringify(result, null, 2);
5277
5292
  }
5278
5293
  // src/bin/help.ts
5279
- var version = "0.8.0";
5294
+ var version = "0.8.1";
5280
5295
  var INDENT = " ";
5281
5296
  var PROGRAM_NAME = "cc-safety-net";
5282
5297
  function formatOptionFlags(option) {
@@ -6,6 +6,7 @@ import type { LoadConfigOptions } from '@/core/config';
6
6
  interface HookDetectOptions extends LoadConfigOptions {
7
7
  homeDir?: string;
8
8
  copilotCliVersion?: string | null;
9
+ copilotPluginInstalled?: boolean;
9
10
  }
10
11
  /**
11
12
  * Strip JSONC-style comments and trailing commas from a string.
@@ -105,6 +105,8 @@ export interface SystemInfo {
105
105
  npmVersion: string | null;
106
106
  /** Bun version (from `bun --version`) */
107
107
  bunVersion: string | null;
108
+ /** Whether the copilot-safety-net plugin is installed (from `copilot plugin list`) */
109
+ copilotPluginInstalled: boolean;
108
110
  /** Platform (e.g., "darwin arm64") */
109
111
  platform: string;
110
112
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safety-net",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Claude Code / OpenCode plugin - block destructive git and filesystem commands before execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",