cc-safety-net 1.0.1 → 1.0.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/README.md +5 -5
- package/dist/bin/cc-safety-net.js +166 -57
- package/dist/bin/doctor/types.d.ts +2 -2
- package/dist/bin/hook/constants.d.ts +2 -2
- package/dist/bin/hook/install/kimi-code.d.ts +3 -0
- package/dist/bin/hook/kimi-code.d.ts +1 -0
- package/dist/bin/integration-metadata.d.ts +9 -9
- package/dist/core/analyze/segment.d.ts +1 -0
- package/dist/index.js +104 -7
- package/dist/pi/index.js +104 -7
- package/dist/types.d.ts +2 -2
- package/package.json +2 -2
- package/dist/bin/hook/install/kimi-cli.d.ts +0 -3
- package/dist/bin/hook/kimi-cli.d.ts +0 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](#claude-code-installation)
|
|
8
8
|
[](#github-copilot-cli-installation)
|
|
9
9
|
[](#gemini-cli-installation)
|
|
10
|
-
[](#kimi-code-installation)
|
|
11
11
|
[](#opencode-installation)
|
|
12
12
|
[](#pi-installation)
|
|
13
13
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -31,7 +31,7 @@ A Coding Agent CLI plugin that acts as a safety net, catching destructive git an
|
|
|
31
31
|
- [Claude Code Installation](#claude-code-installation)
|
|
32
32
|
- [Gemini CLI Installation](#gemini-cli-installation)
|
|
33
33
|
- [GitHub Copilot CLI Installation](#github-copilot-cli-installation)
|
|
34
|
-
- [Kimi
|
|
34
|
+
- [Kimi Code Installation](#kimi-code-installation)
|
|
35
35
|
- [OpenCode Installation](#opencode-installation)
|
|
36
36
|
- [Pi Installation](#pi-installation)
|
|
37
37
|
- [Status Line Integration](#status-line-integration)
|
|
@@ -218,12 +218,12 @@ gemini extensions install https://github.com/kenryu42/gemini-safety-net
|
|
|
218
218
|
|
|
219
219
|
---
|
|
220
220
|
|
|
221
|
-
### Kimi
|
|
221
|
+
### Kimi Code Installation
|
|
222
222
|
|
|
223
|
-
Install CC Safety Net into your Kimi
|
|
223
|
+
Install CC Safety Net into your Kimi Code config:
|
|
224
224
|
|
|
225
225
|
```bash
|
|
226
|
-
npx -y cc-safety-net hook install --kimi-
|
|
226
|
+
npx -y cc-safety-net hook install --kimi-code
|
|
227
227
|
```
|
|
228
228
|
|
|
229
229
|
---
|
|
@@ -3,13 +3,54 @@ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports,
|
|
|
3
3
|
|
|
4
4
|
// node_modules/shell-quote/quote.js
|
|
5
5
|
var require_quote = __commonJS((exports, module) => {
|
|
6
|
+
var OPS = [
|
|
7
|
+
"||",
|
|
8
|
+
"&&",
|
|
9
|
+
";;",
|
|
10
|
+
"|&",
|
|
11
|
+
"<(",
|
|
12
|
+
"<<<",
|
|
13
|
+
">>",
|
|
14
|
+
">&",
|
|
15
|
+
"<&",
|
|
16
|
+
"&",
|
|
17
|
+
";",
|
|
18
|
+
"(",
|
|
19
|
+
")",
|
|
20
|
+
"|",
|
|
21
|
+
"<",
|
|
22
|
+
">"
|
|
23
|
+
];
|
|
24
|
+
var LINE_TERMINATORS = /[\n\r\u2028\u2029]/;
|
|
25
|
+
var GLOB_SHELL_SPECIAL = /[\s#!"$&'():;<=>@\\^`|]/g;
|
|
6
26
|
module.exports = function quote(xs) {
|
|
7
27
|
return xs.map(function(s) {
|
|
8
28
|
if (s === "") {
|
|
9
29
|
return "''";
|
|
10
30
|
}
|
|
11
31
|
if (s && typeof s === "object") {
|
|
12
|
-
|
|
32
|
+
if (s.op === "glob") {
|
|
33
|
+
if (typeof s.pattern !== "string") {
|
|
34
|
+
throw new TypeError("glob token requires a string `pattern`");
|
|
35
|
+
}
|
|
36
|
+
if (LINE_TERMINATORS.test(s.pattern)) {
|
|
37
|
+
throw new TypeError("glob `pattern` must not contain line terminators");
|
|
38
|
+
}
|
|
39
|
+
return s.pattern.replace(GLOB_SHELL_SPECIAL, "\\$&");
|
|
40
|
+
}
|
|
41
|
+
if (typeof s.op === "string") {
|
|
42
|
+
if (OPS.indexOf(s.op) < 0) {
|
|
43
|
+
throw new TypeError("invalid `op` value: " + JSON.stringify(s.op));
|
|
44
|
+
}
|
|
45
|
+
return s.op.replace(/[\s\S]/g, "\\$&");
|
|
46
|
+
}
|
|
47
|
+
if (typeof s.comment === "string") {
|
|
48
|
+
if (LINE_TERMINATORS.test(s.comment)) {
|
|
49
|
+
throw new TypeError("`comment` must not contain line terminators");
|
|
50
|
+
}
|
|
51
|
+
return "#" + s.comment;
|
|
52
|
+
}
|
|
53
|
+
throw new TypeError("unrecognized object token shape");
|
|
13
54
|
}
|
|
14
55
|
if (/["\s\\]/.test(s) && !/'/.test(s)) {
|
|
15
56
|
return "'" + s.replace(/(['])/g, "\\$1") + "'";
|
|
@@ -338,6 +379,10 @@ function dangerousInText(text) {
|
|
|
338
379
|
return null;
|
|
339
380
|
}
|
|
340
381
|
|
|
382
|
+
// src/core/analyze/segment.ts
|
|
383
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
384
|
+
import { normalize as normalize3 } from "node:path";
|
|
385
|
+
|
|
341
386
|
// src/core/analyze/awk.ts
|
|
342
387
|
var AWK_INTERPRETERS = new Set(["awk", "gawk", "nawk", "mawk"]);
|
|
343
388
|
var REASON_AWK_SYSTEM_DYNAMIC = "Detected awk system() call with dynamic command that cannot be safely analyzed.";
|
|
@@ -4143,8 +4188,7 @@ function analyzeParallelCommand(context) {
|
|
|
4143
4188
|
}
|
|
4144
4189
|
var CWD_CHANGE_REGEX = /^\s*(?:\$\(\s*)?[({]*\s*(?:command\s+|builtin\s+)?(?:cd|pushd|popd)(?:\s|$)/;
|
|
4145
4190
|
function segmentChangesCwd(segment) {
|
|
4146
|
-
const
|
|
4147
|
-
const unwrapped = stripWrappers([...stripped]);
|
|
4191
|
+
const unwrapped = getCwdChangeTokens(segment);
|
|
4148
4192
|
if (unwrapped.length === 0) {
|
|
4149
4193
|
return false;
|
|
4150
4194
|
}
|
|
@@ -4163,6 +4207,32 @@ function segmentChangesCwd(segment) {
|
|
|
4163
4207
|
const joined = segment.join(" ");
|
|
4164
4208
|
return CWD_CHANGE_REGEX.test(joined);
|
|
4165
4209
|
}
|
|
4210
|
+
function resolveCwdAfterSegment(segment, cwd) {
|
|
4211
|
+
if (!segmentChangesCwd(segment)) {
|
|
4212
|
+
return;
|
|
4213
|
+
}
|
|
4214
|
+
if (!cwd) {
|
|
4215
|
+
return null;
|
|
4216
|
+
}
|
|
4217
|
+
const unwrapped = getCwdChangeTokens(segment, cwd);
|
|
4218
|
+
const cdIndex = getCdCommandIndex(unwrapped);
|
|
4219
|
+
if (cdIndex === -1 || unwrapped[cdIndex] !== "cd") {
|
|
4220
|
+
return null;
|
|
4221
|
+
}
|
|
4222
|
+
const target = unwrapped[cdIndex + 1];
|
|
4223
|
+
if (!target || target === "-" || target.includes("$") || target.includes("`")) {
|
|
4224
|
+
return null;
|
|
4225
|
+
}
|
|
4226
|
+
try {
|
|
4227
|
+
const resolved = resolveChdirTarget(cwd, target);
|
|
4228
|
+
if (samePath(resolved, cwd)) {
|
|
4229
|
+
return cwd;
|
|
4230
|
+
}
|
|
4231
|
+
} catch {
|
|
4232
|
+
return null;
|
|
4233
|
+
}
|
|
4234
|
+
return null;
|
|
4235
|
+
}
|
|
4166
4236
|
function getHeadAfterTimePrefix(tokens, startIndex) {
|
|
4167
4237
|
let i = startIndex;
|
|
4168
4238
|
while (tokens[i]?.startsWith("-")) {
|
|
@@ -4170,6 +4240,31 @@ function getHeadAfterTimePrefix(tokens, startIndex) {
|
|
|
4170
4240
|
}
|
|
4171
4241
|
return tokens[i] ?? "";
|
|
4172
4242
|
}
|
|
4243
|
+
function getCdCommandIndex(tokens) {
|
|
4244
|
+
let headIndex = 0;
|
|
4245
|
+
if (tokens[0] === "builtin" && tokens.length > 1) {
|
|
4246
|
+
headIndex = 1;
|
|
4247
|
+
}
|
|
4248
|
+
if (tokens[headIndex] !== "time") {
|
|
4249
|
+
return headIndex;
|
|
4250
|
+
}
|
|
4251
|
+
let i = headIndex + 1;
|
|
4252
|
+
while (tokens[i]?.startsWith("-")) {
|
|
4253
|
+
i++;
|
|
4254
|
+
}
|
|
4255
|
+
return i;
|
|
4256
|
+
}
|
|
4257
|
+
function getCwdChangeTokens(segment, cwd) {
|
|
4258
|
+
const stripped = stripLeadingGrouping(segment);
|
|
4259
|
+
return stripWrappers([...stripped], cwd);
|
|
4260
|
+
}
|
|
4261
|
+
function samePath(a, b) {
|
|
4262
|
+
try {
|
|
4263
|
+
return normalize3(realpathSync6(a)) === normalize3(realpathSync6(b));
|
|
4264
|
+
} catch {
|
|
4265
|
+
return normalize3(a) === normalize3(b);
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4173
4268
|
function stripLeadingGrouping(tokens) {
|
|
4174
4269
|
let i = 0;
|
|
4175
4270
|
while (i < tokens.length) {
|
|
@@ -4520,8 +4615,9 @@ function analyzeCommandInternal(command2, depth, options2) {
|
|
|
4520
4615
|
if (textReason) {
|
|
4521
4616
|
return { reason: textReason, segment: segmentStr };
|
|
4522
4617
|
}
|
|
4523
|
-
|
|
4524
|
-
|
|
4618
|
+
const nextCwd2 = resolveCwdAfterSegment(segment, effectiveCwd);
|
|
4619
|
+
if (nextCwd2 !== undefined) {
|
|
4620
|
+
effectiveCwd = nextCwd2;
|
|
4525
4621
|
}
|
|
4526
4622
|
continue;
|
|
4527
4623
|
}
|
|
@@ -4543,8 +4639,9 @@ function analyzeCommandInternal(command2, depth, options2) {
|
|
|
4543
4639
|
if (reason) {
|
|
4544
4640
|
return { reason, segment: segmentStr };
|
|
4545
4641
|
}
|
|
4546
|
-
|
|
4547
|
-
|
|
4642
|
+
const nextCwd = resolveCwdAfterSegment(segment, effectiveCwd);
|
|
4643
|
+
if (nextCwd !== undefined) {
|
|
4644
|
+
effectiveCwd = nextCwd;
|
|
4548
4645
|
}
|
|
4549
4646
|
applyShellGitContextEnvSegment(segment, shellGitContextState);
|
|
4550
4647
|
}
|
|
@@ -6331,8 +6428,8 @@ var CLAUDE_CODE_HOOK_EVENT = "PreToolUse";
|
|
|
6331
6428
|
var CLAUDE_CODE_TOOL_NAME = "Bash";
|
|
6332
6429
|
var GEMINI_CLI_HOOK_EVENT = "BeforeTool";
|
|
6333
6430
|
var GEMINI_CLI_TOOL_NAME = "run_shell_command";
|
|
6334
|
-
var
|
|
6335
|
-
var
|
|
6431
|
+
var KIMI_CODE_HOOK_EVENT = "PreToolUse";
|
|
6432
|
+
var KIMI_CODE_TOOL_NAME = "Shell";
|
|
6336
6433
|
|
|
6337
6434
|
// src/bin/hook/claude-code.ts
|
|
6338
6435
|
async function runClaudeCodeHook() {
|
|
@@ -6381,8 +6478,8 @@ async function runGeminiCLIHook() {
|
|
|
6381
6478
|
});
|
|
6382
6479
|
}
|
|
6383
6480
|
|
|
6384
|
-
// src/bin/hook/kimi-
|
|
6385
|
-
async function
|
|
6481
|
+
// src/bin/hook/kimi-code.ts
|
|
6482
|
+
async function runKimiCodeHook() {
|
|
6386
6483
|
await runConfiguredHookAdapter({
|
|
6387
6484
|
createDenyOutput: (message) => ({
|
|
6388
6485
|
hookSpecificOutput: {
|
|
@@ -6391,7 +6488,7 @@ async function runKimiCliHook() {
|
|
|
6391
6488
|
permissionDecisionReason: message
|
|
6392
6489
|
}
|
|
6393
6490
|
}),
|
|
6394
|
-
isSupported: (input) => input.hook_event_name ===
|
|
6491
|
+
isSupported: (input) => input.hook_event_name === KIMI_CODE_HOOK_EVENT && input.tool_name === KIMI_CODE_TOOL_NAME,
|
|
6395
6492
|
getCommand: (input) => input.tool_input?.command,
|
|
6396
6493
|
getCwd: (input) => input.cwd,
|
|
6397
6494
|
getSessionId: (input) => input.session_id
|
|
@@ -6439,12 +6536,12 @@ var integrationMetadata = [
|
|
|
6439
6536
|
}
|
|
6440
6537
|
},
|
|
6441
6538
|
{
|
|
6442
|
-
id: "kimi-
|
|
6443
|
-
displayName: "Kimi
|
|
6539
|
+
id: "kimi-code",
|
|
6540
|
+
displayName: "Kimi Code",
|
|
6444
6541
|
doctorVisible: true,
|
|
6445
6542
|
runtimeHook: {
|
|
6446
|
-
flags: ["-kc", "--kimi-
|
|
6447
|
-
description: "Run as Kimi
|
|
6543
|
+
flags: ["-kc", "--kimi-code"],
|
|
6544
|
+
description: "Run as Kimi Code PreToolUse hook",
|
|
6448
6545
|
legacyTopLevel: false,
|
|
6449
6546
|
order: 4
|
|
6450
6547
|
}
|
|
@@ -6477,7 +6574,7 @@ var hookRunners = {
|
|
|
6477
6574
|
"claude-code": runClaudeCodeHook,
|
|
6478
6575
|
"copilot-cli": runCopilotCliHook,
|
|
6479
6576
|
"gemini-cli": runGeminiCLIHook,
|
|
6480
|
-
"kimi-
|
|
6577
|
+
"kimi-code": runKimiCodeHook
|
|
6481
6578
|
};
|
|
6482
6579
|
var hookIntegrations = runtimeHookIntegrationMetadata.map((integration) => ({
|
|
6483
6580
|
...integration,
|
|
@@ -6501,8 +6598,8 @@ var hookCommand = {
|
|
|
6501
6598
|
description: "Run as an agent CLI hook (reads JSON from stdin)",
|
|
6502
6599
|
usage: "hook <coding cli>",
|
|
6503
6600
|
subcommands: [
|
|
6504
|
-
{ usage: "install --kimi-
|
|
6505
|
-
{ usage: "uninstall --kimi-
|
|
6601
|
+
{ usage: "install --kimi-code", description: "Install Kimi Code hook config" },
|
|
6602
|
+
{ usage: "uninstall --kimi-code", description: "Uninstall Kimi Code hook config" }
|
|
6506
6603
|
],
|
|
6507
6604
|
options: [
|
|
6508
6605
|
...platformOptions,
|
|
@@ -6511,7 +6608,7 @@ var hookCommand = {
|
|
|
6511
6608
|
description: "Show this help"
|
|
6512
6609
|
}
|
|
6513
6610
|
],
|
|
6514
|
-
examples: [...platformExamples, "cc-safety-net hook install --kimi-
|
|
6611
|
+
examples: [...platformExamples, "cc-safety-net hook install --kimi-code"]
|
|
6515
6612
|
};
|
|
6516
6613
|
|
|
6517
6614
|
// src/bin/commands/rule.ts
|
|
@@ -7160,7 +7257,7 @@ function formatSystemInfoTable(system) {
|
|
|
7160
7257
|
{ label: "Codex", value: system.codexCliVersion },
|
|
7161
7258
|
{ label: "Copilot CLI", value: system.copilotCliVersion },
|
|
7162
7259
|
{ label: "Gemini CLI", value: system.geminiCliVersion },
|
|
7163
|
-
{ label: "Kimi
|
|
7260
|
+
{ label: "Kimi Code", value: system.kimiCodeVersion },
|
|
7164
7261
|
{ label: "OpenCode", value: system.openCodeVersion },
|
|
7165
7262
|
{ label: "Pi", value: system.piCliVersion },
|
|
7166
7263
|
{ label: "Node.js", value: system.nodeVersion },
|
|
@@ -7204,7 +7301,7 @@ var CLAUDE_PLUGIN_LIST_CONFIG_PATH = "claude plugin list";
|
|
|
7204
7301
|
var CLAUDE_SAFETY_NET_PLUGIN_ID = "safety-net@cc-marketplace";
|
|
7205
7302
|
var GEMINI_EXTENSIONS_LIST_CONFIG_PATH = "gemini extensions list";
|
|
7206
7303
|
var GEMINI_SAFETY_NET_SOURCE = "https://github.com/kenryu42/gemini-safety-net";
|
|
7207
|
-
var KIMI_HOOK_COMMAND_PATTERN = /cc-safety-net\s+hook\s+(?:[^\s]+\s+)*--kimi-
|
|
7304
|
+
var KIMI_HOOK_COMMAND_PATTERN = /cc-safety-net\s+hook\s+(?:[^\s]+\s+)*--kimi-code(\s|["']|$)/;
|
|
7208
7305
|
var CODEX_PLUGIN_HOOKS_WARNING = "Codex plugin hooks are behind a feature flag. Add `plugin_hooks = true` under [features] in $CODEX_HOME/config.toml.";
|
|
7209
7306
|
var CODEX_SAFETY_NET_PLUGIN_ID = "safety-net@cc-marketplace";
|
|
7210
7307
|
var SELF_TEST_CASES = [
|
|
@@ -7436,25 +7533,25 @@ function detectGeminiCLI(extensionsListOutput) {
|
|
|
7436
7533
|
function _getKimiConfigPath(homeDir) {
|
|
7437
7534
|
return join10(process.env.KIMI_SHARE_DIR || join10(homeDir, ".kimi"), "config.toml");
|
|
7438
7535
|
}
|
|
7439
|
-
function
|
|
7536
|
+
function detectKimiCode(homeDir) {
|
|
7440
7537
|
const configPath = _getKimiConfigPath(homeDir);
|
|
7441
7538
|
if (!existsSync13(configPath)) {
|
|
7442
|
-
return { platform: "kimi-
|
|
7539
|
+
return { platform: "kimi-code", status: "n/a", configPath };
|
|
7443
7540
|
}
|
|
7444
7541
|
try {
|
|
7445
7542
|
if (!KIMI_HOOK_COMMAND_PATTERN.test(readFileSync10(configPath, "utf-8"))) {
|
|
7446
|
-
return { platform: "kimi-
|
|
7543
|
+
return { platform: "kimi-code", status: "n/a", configPath };
|
|
7447
7544
|
}
|
|
7448
7545
|
} catch (e) {
|
|
7449
7546
|
return {
|
|
7450
|
-
platform: "kimi-
|
|
7547
|
+
platform: "kimi-code",
|
|
7451
7548
|
status: "n/a",
|
|
7452
7549
|
configPath,
|
|
7453
7550
|
errors: [`Failed to read ${configPath}: ${e instanceof Error ? e.message : String(e)}`]
|
|
7454
7551
|
};
|
|
7455
7552
|
}
|
|
7456
7553
|
return {
|
|
7457
|
-
platform: "kimi-
|
|
7554
|
+
platform: "kimi-code",
|
|
7458
7555
|
status: "configured",
|
|
7459
7556
|
method: "hook config",
|
|
7460
7557
|
configPath,
|
|
@@ -7813,8 +7910,8 @@ function detectAllHooks(cwd, options2) {
|
|
|
7813
7910
|
return detectGeminiCLI(options2?.geminiExtensionsListOutput);
|
|
7814
7911
|
case "copilot-cli":
|
|
7815
7912
|
return detectCopilotCLI();
|
|
7816
|
-
case "kimi-
|
|
7817
|
-
return
|
|
7913
|
+
case "kimi-code":
|
|
7914
|
+
return detectKimiCode(homeDir);
|
|
7818
7915
|
case "pi":
|
|
7819
7916
|
return detectPi(options2?.piSafetyNetProbe);
|
|
7820
7917
|
case "codex":
|
|
@@ -7830,7 +7927,7 @@ import { existsSync as existsSync14 } from "node:fs";
|
|
|
7830
7927
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
7831
7928
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
7832
7929
|
import { delimiter, extname, join as join11 } from "node:path";
|
|
7833
|
-
var CURRENT_VERSION = "1.0.
|
|
7930
|
+
var CURRENT_VERSION = "1.0.3";
|
|
7834
7931
|
var VERSION_FETCH_TIMEOUT_MS = 2000;
|
|
7835
7932
|
var PI_PROBE_TIMEOUT_MS = 5000;
|
|
7836
7933
|
var PI_SENTINEL_COMMAND = "cc-safety-net";
|
|
@@ -8193,7 +8290,7 @@ async function getSystemInfo(fetcher = defaultVersionFetcher, options2 = {}) {
|
|
|
8193
8290
|
geminiCliVersion: parseVersion(geminiRaw),
|
|
8194
8291
|
geminiExtensionsListOutput,
|
|
8195
8292
|
copilotCliVersion: parseVersion(copilotRaw),
|
|
8196
|
-
|
|
8293
|
+
kimiCodeVersion: parseVersion(kimiRaw),
|
|
8197
8294
|
piCliVersion: parseVersion(piRaw),
|
|
8198
8295
|
nodeVersion: parseVersion(nodeRaw),
|
|
8199
8296
|
npmVersion: parseVersion(npmRaw),
|
|
@@ -8882,12 +8979,14 @@ function explainCommand2(command2, options2) {
|
|
|
8882
8979
|
token: redactEnvAssignmentsInString(segment[0]),
|
|
8883
8980
|
matched: false
|
|
8884
8981
|
});
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
|
|
8890
|
-
|
|
8982
|
+
const nextCwd2 = resolveCwdAfterSegment(segment, effectiveCwd);
|
|
8983
|
+
if (nextCwd2 !== undefined) {
|
|
8984
|
+
if (nextCwd2 !== null) {
|
|
8985
|
+
effectiveCwd = nextCwd2;
|
|
8986
|
+
trace.segments.push({ index: i, steps: segmentSteps });
|
|
8987
|
+
continue;
|
|
8988
|
+
}
|
|
8989
|
+
segmentSteps.push(cwdChangeStep(segment));
|
|
8891
8990
|
effectiveCwd = null;
|
|
8892
8991
|
}
|
|
8893
8992
|
trace.segments.push({ index: i, steps: segmentSteps });
|
|
@@ -8903,12 +9002,15 @@ function explainCommand2(command2, options2) {
|
|
|
8903
9002
|
blockReason = result.reason;
|
|
8904
9003
|
blockSegment = redactEnvAssignmentsInString(segment.join(" "));
|
|
8905
9004
|
}
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
9005
|
+
const nextCwd = resolveCwdAfterSegment(segment, effectiveCwd);
|
|
9006
|
+
if (nextCwd !== undefined) {
|
|
9007
|
+
if (nextCwd !== null) {
|
|
9008
|
+
effectiveCwd = nextCwd;
|
|
9009
|
+
applyShellGitContextEnvSegment(segment, shellGitContextState);
|
|
9010
|
+
trace.segments.push({ index: i, steps: segmentSteps });
|
|
9011
|
+
continue;
|
|
9012
|
+
}
|
|
9013
|
+
segmentSteps.push(cwdChangeStep(segment));
|
|
8912
9014
|
effectiveCwd = null;
|
|
8913
9015
|
}
|
|
8914
9016
|
applyShellGitContextEnvSegment(segment, shellGitContextState);
|
|
@@ -8924,6 +9026,13 @@ function explainCommand2(command2, options2) {
|
|
|
8924
9026
|
configValid
|
|
8925
9027
|
};
|
|
8926
9028
|
}
|
|
9029
|
+
function cwdChangeStep(segment) {
|
|
9030
|
+
return {
|
|
9031
|
+
type: "cwd-change",
|
|
9032
|
+
segment: redactEnvAssignmentsInString(segment.join(" ")),
|
|
9033
|
+
effectiveCwdNowUnknown: true
|
|
9034
|
+
};
|
|
9035
|
+
}
|
|
8927
9036
|
function getCustomRuleMetadata(reason, options2, cwd) {
|
|
8928
9037
|
const id = reason?.match(/^\[([^\]]+)]/)?.[1];
|
|
8929
9038
|
if (!id)
|
|
@@ -9327,7 +9436,7 @@ function formatTraceJson(result) {
|
|
|
9327
9436
|
return JSON.stringify(result, null, 2);
|
|
9328
9437
|
}
|
|
9329
9438
|
// src/bin/help.ts
|
|
9330
|
-
var version = "1.0.
|
|
9439
|
+
var version = "1.0.3";
|
|
9331
9440
|
var INDENT = " ";
|
|
9332
9441
|
var PROGRAM_NAME = "cc-safety-net";
|
|
9333
9442
|
function formatOptionFlags(option) {
|
|
@@ -9434,7 +9543,7 @@ function showCommandHelp(commandName) {
|
|
|
9434
9543
|
// src/bin/hook/install.ts
|
|
9435
9544
|
import { homedir as homedir6 } from "node:os";
|
|
9436
9545
|
|
|
9437
|
-
// src/bin/hook/install/kimi-
|
|
9546
|
+
// src/bin/hook/install/kimi-code.ts
|
|
9438
9547
|
import { existsSync as existsSync16, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "node:fs";
|
|
9439
9548
|
import { dirname as dirname9, join as join12 } from "node:path";
|
|
9440
9549
|
|
|
@@ -9522,8 +9631,8 @@ function removeArrayRangeItem(content, item) {
|
|
|
9522
9631
|
return `${content.slice(0, removeStart)}${content.slice(removeEnd)}`;
|
|
9523
9632
|
}
|
|
9524
9633
|
|
|
9525
|
-
// src/bin/hook/install/kimi-
|
|
9526
|
-
var KIMI_HOOK_COMMAND = "npx -y cc-safety-net hook --kimi-
|
|
9634
|
+
// src/bin/hook/install/kimi-code.ts
|
|
9635
|
+
var KIMI_HOOK_COMMAND = "npx -y cc-safety-net hook --kimi-code";
|
|
9527
9636
|
var KIMI_HOOK_BLOCK = `[[hooks]]
|
|
9528
9637
|
event = "PreToolUse"
|
|
9529
9638
|
matcher = "Shell"
|
|
@@ -9558,8 +9667,8 @@ function skipTomlComment(content, index) {
|
|
|
9558
9667
|
function findTomlArrayClose(content, openIndex) {
|
|
9559
9668
|
return findMatchingBracket(content, openIndex, {
|
|
9560
9669
|
skipComment: skipTomlComment,
|
|
9561
|
-
stringError: "Unterminated string in Kimi
|
|
9562
|
-
bracketError: "Unmatched hooks array in Kimi
|
|
9670
|
+
stringError: "Unterminated string in Kimi Code config",
|
|
9671
|
+
bracketError: "Unmatched hooks array in Kimi Code config"
|
|
9563
9672
|
});
|
|
9564
9673
|
}
|
|
9565
9674
|
function findTopLevelInlineHooksArray(content) {
|
|
@@ -9618,7 +9727,7 @@ function removeKimiInlineHook(content, hooksRange) {
|
|
|
9618
9727
|
end: itemStart + KIMI_INLINE_HOOK.length
|
|
9619
9728
|
});
|
|
9620
9729
|
}
|
|
9621
|
-
function
|
|
9730
|
+
function installKimiCode(homeDir) {
|
|
9622
9731
|
const configPath = getKimiConfigPath(homeDir);
|
|
9623
9732
|
mkdirSync4(dirname9(configPath), { recursive: true });
|
|
9624
9733
|
if (!existsSync16(configPath)) {
|
|
@@ -9632,7 +9741,7 @@ function installKimiCli(homeDir) {
|
|
|
9632
9741
|
writeFileSync3(configPath, appendKimiHook(content));
|
|
9633
9742
|
return { path: configPath, alreadyInstalled: false };
|
|
9634
9743
|
}
|
|
9635
|
-
function
|
|
9744
|
+
function uninstallKimiCode(homeDir) {
|
|
9636
9745
|
const configPath = getKimiConfigPath(homeDir);
|
|
9637
9746
|
if (!existsSync16(configPath))
|
|
9638
9747
|
return { path: configPath, alreadyInstalled: false };
|
|
@@ -9651,21 +9760,21 @@ function getHomeDir() {
|
|
|
9651
9760
|
return process.env.HOME ?? homedir6();
|
|
9652
9761
|
}
|
|
9653
9762
|
function parseInstallTarget(args, action) {
|
|
9654
|
-
const unknownOption = args.find((arg) => arg.startsWith("-") && !["--kimi-
|
|
9763
|
+
const unknownOption = args.find((arg) => arg.startsWith("-") && !["--kimi-code"].includes(arg));
|
|
9655
9764
|
if (unknownOption)
|
|
9656
9765
|
throw new Error(`Unknown install option: ${unknownOption}`);
|
|
9657
9766
|
const unexpectedArg = args.find((arg) => !arg.startsWith("-"));
|
|
9658
9767
|
if (unexpectedArg)
|
|
9659
9768
|
throw new Error(`Unexpected argument for hook ${action}: ${unexpectedArg}`);
|
|
9660
|
-
if (!args.includes("--kimi-
|
|
9661
|
-
throw new Error("Choose exactly one install target: --kimi-
|
|
9769
|
+
if (!args.includes("--kimi-code"))
|
|
9770
|
+
throw new Error("Choose exactly one install target: --kimi-code");
|
|
9662
9771
|
}
|
|
9663
9772
|
function runHookInstallCommand(action, args) {
|
|
9664
9773
|
try {
|
|
9665
9774
|
parseInstallTarget(args, action);
|
|
9666
9775
|
const homeDir = getHomeDir();
|
|
9667
|
-
const result = action === "install" ?
|
|
9668
|
-
const name = "Kimi
|
|
9776
|
+
const result = action === "install" ? installKimiCode(homeDir) : uninstallKimiCode(homeDir);
|
|
9777
|
+
const name = "Kimi Code";
|
|
9669
9778
|
const pastTense = action === "install" ? "Installed" : "Uninstalled";
|
|
9670
9779
|
console.log(action === "install" && result.alreadyInstalled ? `${name} hook already installed in ${result.path}` : action === "uninstall" && !result.alreadyInstalled ? `${name} hook not installed in ${result.path}` : `${pastTense} ${name} hook ${action === "install" ? "in" : "from"} ${result.path}`);
|
|
9671
9780
|
return 0;
|
|
@@ -10683,7 +10792,7 @@ var commandParsers = {
|
|
|
10683
10792
|
const integration = findHookIntegrationByFlag(args);
|
|
10684
10793
|
if (integration)
|
|
10685
10794
|
return { mode: "hook", integration };
|
|
10686
|
-
console.error("hook requires a subcommand or integration flag. Try: cc-safety-net hook install --kimi-
|
|
10795
|
+
console.error("hook requires a subcommand or integration flag. Try: cc-safety-net hook install --kimi-code");
|
|
10687
10796
|
showCommandHelp("hook");
|
|
10688
10797
|
process.exit(1);
|
|
10689
10798
|
},
|
|
@@ -122,8 +122,8 @@ export interface SystemInfo {
|
|
|
122
122
|
geminiExtensionsListOutput: string | null;
|
|
123
123
|
/** Copilot CLI version (from `copilot --binary-version`, falling back to `copilot --version`) */
|
|
124
124
|
copilotCliVersion: string | null;
|
|
125
|
-
/** Kimi
|
|
126
|
-
|
|
125
|
+
/** Kimi Code version (from `kimi --version`) */
|
|
126
|
+
kimiCodeVersion: string | null;
|
|
127
127
|
/** Pi CLI version (from `pi --version`) */
|
|
128
128
|
piCliVersion: string | null;
|
|
129
129
|
/** Node.js version (from `node --version`) */
|
|
@@ -2,5 +2,5 @@ export declare const CLAUDE_CODE_HOOK_EVENT = "PreToolUse";
|
|
|
2
2
|
export declare const CLAUDE_CODE_TOOL_NAME = "Bash";
|
|
3
3
|
export declare const GEMINI_CLI_HOOK_EVENT = "BeforeTool";
|
|
4
4
|
export declare const GEMINI_CLI_TOOL_NAME = "run_shell_command";
|
|
5
|
-
export declare const
|
|
6
|
-
export declare const
|
|
5
|
+
export declare const KIMI_CODE_HOOK_EVENT = "PreToolUse";
|
|
6
|
+
export declare const KIMI_CODE_TOOL_NAME = "Shell";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runKimiCodeHook(): Promise<void>;
|
|
@@ -33,12 +33,12 @@ declare const integrationMetadata: readonly [{
|
|
|
33
33
|
readonly order: 3;
|
|
34
34
|
};
|
|
35
35
|
}, {
|
|
36
|
-
readonly id: "kimi-
|
|
37
|
-
readonly displayName: "Kimi
|
|
36
|
+
readonly id: "kimi-code";
|
|
37
|
+
readonly displayName: "Kimi Code";
|
|
38
38
|
readonly doctorVisible: true;
|
|
39
39
|
readonly runtimeHook: {
|
|
40
|
-
readonly flags: readonly ["-kc", "--kimi-
|
|
41
|
-
readonly description: "Run as Kimi
|
|
40
|
+
readonly flags: readonly ["-kc", "--kimi-code"];
|
|
41
|
+
readonly description: "Run as Kimi Code PreToolUse hook";
|
|
42
42
|
readonly legacyTopLevel: false;
|
|
43
43
|
readonly order: 4;
|
|
44
44
|
};
|
|
@@ -56,12 +56,12 @@ type RuntimeHookIntegrationMetadata = Extract<(typeof integrationMetadata)[numbe
|
|
|
56
56
|
runtimeHook: object;
|
|
57
57
|
}>;
|
|
58
58
|
export type RuntimeHookIntegrationId = RuntimeHookIntegrationMetadata['id'];
|
|
59
|
-
export declare const doctorIntegrationOrder: ("claude-code" | "codex" | "copilot-cli" | "gemini-cli" | "kimi-
|
|
59
|
+
export declare const doctorIntegrationOrder: ("claude-code" | "codex" | "copilot-cli" | "gemini-cli" | "kimi-code" | "opencode" | "pi")[];
|
|
60
60
|
export declare const runtimeHookIntegrationMetadata: {
|
|
61
|
-
id: "claude-code" | "copilot-cli" | "gemini-cli" | "kimi-
|
|
62
|
-
displayName: "Claude Code" | "Copilot CLI" | "Gemini CLI" | "Kimi
|
|
63
|
-
flags: readonly ["-cc", "--claude-code"] | readonly ["-cp", "--copilot-cli"] | readonly ["-gc", "--gemini-cli"] | readonly ["-kc", "--kimi-
|
|
64
|
-
description: "Run as Claude Code PreToolUse hook" | "Run as Copilot CLI PreToolUse hook" | "Run as Gemini CLI BeforeTool hook" | "Run as Kimi
|
|
61
|
+
id: "claude-code" | "copilot-cli" | "gemini-cli" | "kimi-code";
|
|
62
|
+
displayName: "Claude Code" | "Copilot CLI" | "Gemini CLI" | "Kimi Code";
|
|
63
|
+
flags: readonly ["-cc", "--claude-code"] | readonly ["-cp", "--copilot-cli"] | readonly ["-gc", "--gemini-cli"] | readonly ["-kc", "--kimi-code"];
|
|
64
|
+
description: "Run as Claude Code PreToolUse hook" | "Run as Copilot CLI PreToolUse hook" | "Run as Gemini CLI BeforeTool hook" | "Run as Kimi Code PreToolUse hook";
|
|
65
65
|
legacyTopLevel: boolean;
|
|
66
66
|
}[];
|
|
67
67
|
export declare function getIntegrationDisplayName(id: IntegrationId): string;
|
|
@@ -8,3 +8,4 @@ export type InternalOptions = AnalyzeOptions & {
|
|
|
8
8
|
};
|
|
9
9
|
export declare function analyzeSegment(tokens: string[], depth: number, options: InternalOptions): string | null;
|
|
10
10
|
export declare function segmentChangesCwd(segment: readonly string[]): boolean;
|
|
11
|
+
export declare function resolveCwdAfterSegment(segment: readonly string[], cwd: string | null | undefined): string | null | undefined;
|
package/dist/index.js
CHANGED
|
@@ -2,13 +2,54 @@ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports,
|
|
|
2
2
|
|
|
3
3
|
// node_modules/shell-quote/quote.js
|
|
4
4
|
var require_quote = __commonJS((exports, module) => {
|
|
5
|
+
var OPS = [
|
|
6
|
+
"||",
|
|
7
|
+
"&&",
|
|
8
|
+
";;",
|
|
9
|
+
"|&",
|
|
10
|
+
"<(",
|
|
11
|
+
"<<<",
|
|
12
|
+
">>",
|
|
13
|
+
">&",
|
|
14
|
+
"<&",
|
|
15
|
+
"&",
|
|
16
|
+
";",
|
|
17
|
+
"(",
|
|
18
|
+
")",
|
|
19
|
+
"|",
|
|
20
|
+
"<",
|
|
21
|
+
">"
|
|
22
|
+
];
|
|
23
|
+
var LINE_TERMINATORS = /[\n\r\u2028\u2029]/;
|
|
24
|
+
var GLOB_SHELL_SPECIAL = /[\s#!"$&'():;<=>@\\^`|]/g;
|
|
5
25
|
module.exports = function quote(xs) {
|
|
6
26
|
return xs.map(function(s) {
|
|
7
27
|
if (s === "") {
|
|
8
28
|
return "''";
|
|
9
29
|
}
|
|
10
30
|
if (s && typeof s === "object") {
|
|
11
|
-
|
|
31
|
+
if (s.op === "glob") {
|
|
32
|
+
if (typeof s.pattern !== "string") {
|
|
33
|
+
throw new TypeError("glob token requires a string `pattern`");
|
|
34
|
+
}
|
|
35
|
+
if (LINE_TERMINATORS.test(s.pattern)) {
|
|
36
|
+
throw new TypeError("glob `pattern` must not contain line terminators");
|
|
37
|
+
}
|
|
38
|
+
return s.pattern.replace(GLOB_SHELL_SPECIAL, "\\$&");
|
|
39
|
+
}
|
|
40
|
+
if (typeof s.op === "string") {
|
|
41
|
+
if (OPS.indexOf(s.op) < 0) {
|
|
42
|
+
throw new TypeError("invalid `op` value: " + JSON.stringify(s.op));
|
|
43
|
+
}
|
|
44
|
+
return s.op.replace(/[\s\S]/g, "\\$&");
|
|
45
|
+
}
|
|
46
|
+
if (typeof s.comment === "string") {
|
|
47
|
+
if (LINE_TERMINATORS.test(s.comment)) {
|
|
48
|
+
throw new TypeError("`comment` must not contain line terminators");
|
|
49
|
+
}
|
|
50
|
+
return "#" + s.comment;
|
|
51
|
+
}
|
|
52
|
+
throw new TypeError("unrecognized object token shape");
|
|
12
53
|
}
|
|
13
54
|
if (/["\s\\]/.test(s) && !/'/.test(s)) {
|
|
14
55
|
return "'" + s.replace(/(['])/g, "\\$1") + "'";
|
|
@@ -282,6 +323,10 @@ function dangerousInText(text) {
|
|
|
282
323
|
return null;
|
|
283
324
|
}
|
|
284
325
|
|
|
326
|
+
// src/core/analyze/segment.ts
|
|
327
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
328
|
+
import { normalize as normalize3 } from "node:path";
|
|
329
|
+
|
|
285
330
|
// src/core/analyze/awk.ts
|
|
286
331
|
var AWK_INTERPRETERS = new Set(["awk", "gawk", "nawk", "mawk"]);
|
|
287
332
|
var REASON_AWK_SYSTEM_DYNAMIC = "Detected awk system() call with dynamic command that cannot be safely analyzed.";
|
|
@@ -4087,8 +4132,7 @@ function analyzeParallelCommand(context) {
|
|
|
4087
4132
|
}
|
|
4088
4133
|
var CWD_CHANGE_REGEX = /^\s*(?:\$\(\s*)?[({]*\s*(?:command\s+|builtin\s+)?(?:cd|pushd|popd)(?:\s|$)/;
|
|
4089
4134
|
function segmentChangesCwd(segment) {
|
|
4090
|
-
const
|
|
4091
|
-
const unwrapped = stripWrappers([...stripped]);
|
|
4135
|
+
const unwrapped = getCwdChangeTokens(segment);
|
|
4092
4136
|
if (unwrapped.length === 0) {
|
|
4093
4137
|
return false;
|
|
4094
4138
|
}
|
|
@@ -4107,6 +4151,32 @@ function segmentChangesCwd(segment) {
|
|
|
4107
4151
|
const joined = segment.join(" ");
|
|
4108
4152
|
return CWD_CHANGE_REGEX.test(joined);
|
|
4109
4153
|
}
|
|
4154
|
+
function resolveCwdAfterSegment(segment, cwd) {
|
|
4155
|
+
if (!segmentChangesCwd(segment)) {
|
|
4156
|
+
return;
|
|
4157
|
+
}
|
|
4158
|
+
if (!cwd) {
|
|
4159
|
+
return null;
|
|
4160
|
+
}
|
|
4161
|
+
const unwrapped = getCwdChangeTokens(segment, cwd);
|
|
4162
|
+
const cdIndex = getCdCommandIndex(unwrapped);
|
|
4163
|
+
if (cdIndex === -1 || unwrapped[cdIndex] !== "cd") {
|
|
4164
|
+
return null;
|
|
4165
|
+
}
|
|
4166
|
+
const target = unwrapped[cdIndex + 1];
|
|
4167
|
+
if (!target || target === "-" || target.includes("$") || target.includes("`")) {
|
|
4168
|
+
return null;
|
|
4169
|
+
}
|
|
4170
|
+
try {
|
|
4171
|
+
const resolved = resolveChdirTarget(cwd, target);
|
|
4172
|
+
if (samePath(resolved, cwd)) {
|
|
4173
|
+
return cwd;
|
|
4174
|
+
}
|
|
4175
|
+
} catch {
|
|
4176
|
+
return null;
|
|
4177
|
+
}
|
|
4178
|
+
return null;
|
|
4179
|
+
}
|
|
4110
4180
|
function getHeadAfterTimePrefix(tokens, startIndex) {
|
|
4111
4181
|
let i = startIndex;
|
|
4112
4182
|
while (tokens[i]?.startsWith("-")) {
|
|
@@ -4114,6 +4184,31 @@ function getHeadAfterTimePrefix(tokens, startIndex) {
|
|
|
4114
4184
|
}
|
|
4115
4185
|
return tokens[i] ?? "";
|
|
4116
4186
|
}
|
|
4187
|
+
function getCdCommandIndex(tokens) {
|
|
4188
|
+
let headIndex = 0;
|
|
4189
|
+
if (tokens[0] === "builtin" && tokens.length > 1) {
|
|
4190
|
+
headIndex = 1;
|
|
4191
|
+
}
|
|
4192
|
+
if (tokens[headIndex] !== "time") {
|
|
4193
|
+
return headIndex;
|
|
4194
|
+
}
|
|
4195
|
+
let i = headIndex + 1;
|
|
4196
|
+
while (tokens[i]?.startsWith("-")) {
|
|
4197
|
+
i++;
|
|
4198
|
+
}
|
|
4199
|
+
return i;
|
|
4200
|
+
}
|
|
4201
|
+
function getCwdChangeTokens(segment, cwd) {
|
|
4202
|
+
const stripped = stripLeadingGrouping(segment);
|
|
4203
|
+
return stripWrappers([...stripped], cwd);
|
|
4204
|
+
}
|
|
4205
|
+
function samePath(a, b) {
|
|
4206
|
+
try {
|
|
4207
|
+
return normalize3(realpathSync6(a)) === normalize3(realpathSync6(b));
|
|
4208
|
+
} catch {
|
|
4209
|
+
return normalize3(a) === normalize3(b);
|
|
4210
|
+
}
|
|
4211
|
+
}
|
|
4117
4212
|
function stripLeadingGrouping(tokens) {
|
|
4118
4213
|
let i = 0;
|
|
4119
4214
|
while (i < tokens.length) {
|
|
@@ -4464,8 +4559,9 @@ function analyzeCommandInternal(command2, depth, options2) {
|
|
|
4464
4559
|
if (textReason) {
|
|
4465
4560
|
return { reason: textReason, segment: segmentStr };
|
|
4466
4561
|
}
|
|
4467
|
-
|
|
4468
|
-
|
|
4562
|
+
const nextCwd2 = resolveCwdAfterSegment(segment, effectiveCwd);
|
|
4563
|
+
if (nextCwd2 !== undefined) {
|
|
4564
|
+
effectiveCwd = nextCwd2;
|
|
4469
4565
|
}
|
|
4470
4566
|
continue;
|
|
4471
4567
|
}
|
|
@@ -4487,8 +4583,9 @@ function analyzeCommandInternal(command2, depth, options2) {
|
|
|
4487
4583
|
if (reason) {
|
|
4488
4584
|
return { reason, segment: segmentStr };
|
|
4489
4585
|
}
|
|
4490
|
-
|
|
4491
|
-
|
|
4586
|
+
const nextCwd = resolveCwdAfterSegment(segment, effectiveCwd);
|
|
4587
|
+
if (nextCwd !== undefined) {
|
|
4588
|
+
effectiveCwd = nextCwd;
|
|
4492
4589
|
}
|
|
4493
4590
|
applyShellGitContextEnvSegment(segment, shellGitContextState);
|
|
4494
4591
|
}
|
package/dist/pi/index.js
CHANGED
|
@@ -2,13 +2,54 @@ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports,
|
|
|
2
2
|
|
|
3
3
|
// node_modules/shell-quote/quote.js
|
|
4
4
|
var require_quote = __commonJS((exports, module) => {
|
|
5
|
+
var OPS = [
|
|
6
|
+
"||",
|
|
7
|
+
"&&",
|
|
8
|
+
";;",
|
|
9
|
+
"|&",
|
|
10
|
+
"<(",
|
|
11
|
+
"<<<",
|
|
12
|
+
">>",
|
|
13
|
+
">&",
|
|
14
|
+
"<&",
|
|
15
|
+
"&",
|
|
16
|
+
";",
|
|
17
|
+
"(",
|
|
18
|
+
")",
|
|
19
|
+
"|",
|
|
20
|
+
"<",
|
|
21
|
+
">"
|
|
22
|
+
];
|
|
23
|
+
var LINE_TERMINATORS = /[\n\r\u2028\u2029]/;
|
|
24
|
+
var GLOB_SHELL_SPECIAL = /[\s#!"$&'():;<=>@\\^`|]/g;
|
|
5
25
|
module.exports = function quote(xs) {
|
|
6
26
|
return xs.map(function(s) {
|
|
7
27
|
if (s === "") {
|
|
8
28
|
return "''";
|
|
9
29
|
}
|
|
10
30
|
if (s && typeof s === "object") {
|
|
11
|
-
|
|
31
|
+
if (s.op === "glob") {
|
|
32
|
+
if (typeof s.pattern !== "string") {
|
|
33
|
+
throw new TypeError("glob token requires a string `pattern`");
|
|
34
|
+
}
|
|
35
|
+
if (LINE_TERMINATORS.test(s.pattern)) {
|
|
36
|
+
throw new TypeError("glob `pattern` must not contain line terminators");
|
|
37
|
+
}
|
|
38
|
+
return s.pattern.replace(GLOB_SHELL_SPECIAL, "\\$&");
|
|
39
|
+
}
|
|
40
|
+
if (typeof s.op === "string") {
|
|
41
|
+
if (OPS.indexOf(s.op) < 0) {
|
|
42
|
+
throw new TypeError("invalid `op` value: " + JSON.stringify(s.op));
|
|
43
|
+
}
|
|
44
|
+
return s.op.replace(/[\s\S]/g, "\\$&");
|
|
45
|
+
}
|
|
46
|
+
if (typeof s.comment === "string") {
|
|
47
|
+
if (LINE_TERMINATORS.test(s.comment)) {
|
|
48
|
+
throw new TypeError("`comment` must not contain line terminators");
|
|
49
|
+
}
|
|
50
|
+
return "#" + s.comment;
|
|
51
|
+
}
|
|
52
|
+
throw new TypeError("unrecognized object token shape");
|
|
12
53
|
}
|
|
13
54
|
if (/["\s\\]/.test(s) && !/'/.test(s)) {
|
|
14
55
|
return "'" + s.replace(/(['])/g, "\\$1") + "'";
|
|
@@ -346,6 +387,10 @@ function dangerousInText(text) {
|
|
|
346
387
|
return null;
|
|
347
388
|
}
|
|
348
389
|
|
|
390
|
+
// src/core/analyze/segment.ts
|
|
391
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
392
|
+
import { normalize as normalize3 } from "node:path";
|
|
393
|
+
|
|
349
394
|
// src/core/analyze/awk.ts
|
|
350
395
|
var AWK_INTERPRETERS = new Set(["awk", "gawk", "nawk", "mawk"]);
|
|
351
396
|
var REASON_AWK_SYSTEM_DYNAMIC = "Detected awk system() call with dynamic command that cannot be safely analyzed.";
|
|
@@ -4151,8 +4196,7 @@ function analyzeParallelCommand(context) {
|
|
|
4151
4196
|
}
|
|
4152
4197
|
var CWD_CHANGE_REGEX = /^\s*(?:\$\(\s*)?[({]*\s*(?:command\s+|builtin\s+)?(?:cd|pushd|popd)(?:\s|$)/;
|
|
4153
4198
|
function segmentChangesCwd(segment) {
|
|
4154
|
-
const
|
|
4155
|
-
const unwrapped = stripWrappers([...stripped]);
|
|
4199
|
+
const unwrapped = getCwdChangeTokens(segment);
|
|
4156
4200
|
if (unwrapped.length === 0) {
|
|
4157
4201
|
return false;
|
|
4158
4202
|
}
|
|
@@ -4171,6 +4215,32 @@ function segmentChangesCwd(segment) {
|
|
|
4171
4215
|
const joined = segment.join(" ");
|
|
4172
4216
|
return CWD_CHANGE_REGEX.test(joined);
|
|
4173
4217
|
}
|
|
4218
|
+
function resolveCwdAfterSegment(segment, cwd) {
|
|
4219
|
+
if (!segmentChangesCwd(segment)) {
|
|
4220
|
+
return;
|
|
4221
|
+
}
|
|
4222
|
+
if (!cwd) {
|
|
4223
|
+
return null;
|
|
4224
|
+
}
|
|
4225
|
+
const unwrapped = getCwdChangeTokens(segment, cwd);
|
|
4226
|
+
const cdIndex = getCdCommandIndex(unwrapped);
|
|
4227
|
+
if (cdIndex === -1 || unwrapped[cdIndex] !== "cd") {
|
|
4228
|
+
return null;
|
|
4229
|
+
}
|
|
4230
|
+
const target = unwrapped[cdIndex + 1];
|
|
4231
|
+
if (!target || target === "-" || target.includes("$") || target.includes("`")) {
|
|
4232
|
+
return null;
|
|
4233
|
+
}
|
|
4234
|
+
try {
|
|
4235
|
+
const resolved = resolveChdirTarget(cwd, target);
|
|
4236
|
+
if (samePath(resolved, cwd)) {
|
|
4237
|
+
return cwd;
|
|
4238
|
+
}
|
|
4239
|
+
} catch {
|
|
4240
|
+
return null;
|
|
4241
|
+
}
|
|
4242
|
+
return null;
|
|
4243
|
+
}
|
|
4174
4244
|
function getHeadAfterTimePrefix(tokens, startIndex) {
|
|
4175
4245
|
let i = startIndex;
|
|
4176
4246
|
while (tokens[i]?.startsWith("-")) {
|
|
@@ -4178,6 +4248,31 @@ function getHeadAfterTimePrefix(tokens, startIndex) {
|
|
|
4178
4248
|
}
|
|
4179
4249
|
return tokens[i] ?? "";
|
|
4180
4250
|
}
|
|
4251
|
+
function getCdCommandIndex(tokens) {
|
|
4252
|
+
let headIndex = 0;
|
|
4253
|
+
if (tokens[0] === "builtin" && tokens.length > 1) {
|
|
4254
|
+
headIndex = 1;
|
|
4255
|
+
}
|
|
4256
|
+
if (tokens[headIndex] !== "time") {
|
|
4257
|
+
return headIndex;
|
|
4258
|
+
}
|
|
4259
|
+
let i = headIndex + 1;
|
|
4260
|
+
while (tokens[i]?.startsWith("-")) {
|
|
4261
|
+
i++;
|
|
4262
|
+
}
|
|
4263
|
+
return i;
|
|
4264
|
+
}
|
|
4265
|
+
function getCwdChangeTokens(segment, cwd) {
|
|
4266
|
+
const stripped = stripLeadingGrouping(segment);
|
|
4267
|
+
return stripWrappers([...stripped], cwd);
|
|
4268
|
+
}
|
|
4269
|
+
function samePath(a, b) {
|
|
4270
|
+
try {
|
|
4271
|
+
return normalize3(realpathSync6(a)) === normalize3(realpathSync6(b));
|
|
4272
|
+
} catch {
|
|
4273
|
+
return normalize3(a) === normalize3(b);
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4181
4276
|
function stripLeadingGrouping(tokens) {
|
|
4182
4277
|
let i = 0;
|
|
4183
4278
|
while (i < tokens.length) {
|
|
@@ -4528,8 +4623,9 @@ function analyzeCommandInternal(command2, depth, options2) {
|
|
|
4528
4623
|
if (textReason) {
|
|
4529
4624
|
return { reason: textReason, segment: segmentStr };
|
|
4530
4625
|
}
|
|
4531
|
-
|
|
4532
|
-
|
|
4626
|
+
const nextCwd2 = resolveCwdAfterSegment(segment, effectiveCwd);
|
|
4627
|
+
if (nextCwd2 !== undefined) {
|
|
4628
|
+
effectiveCwd = nextCwd2;
|
|
4533
4629
|
}
|
|
4534
4630
|
continue;
|
|
4535
4631
|
}
|
|
@@ -4551,8 +4647,9 @@ function analyzeCommandInternal(command2, depth, options2) {
|
|
|
4551
4647
|
if (reason) {
|
|
4552
4648
|
return { reason, segment: segmentStr };
|
|
4553
4649
|
}
|
|
4554
|
-
|
|
4555
|
-
|
|
4650
|
+
const nextCwd = resolveCwdAfterSegment(segment, effectiveCwd);
|
|
4651
|
+
if (nextCwd !== undefined) {
|
|
4652
|
+
effectiveCwd = nextCwd;
|
|
4556
4653
|
}
|
|
4557
4654
|
applyShellGitContextEnvSegment(segment, shellGitContextState);
|
|
4558
4655
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -83,8 +83,8 @@ export interface GeminiHookOutput {
|
|
|
83
83
|
stopReason?: string;
|
|
84
84
|
suppressOutput?: boolean;
|
|
85
85
|
}
|
|
86
|
-
/** Kimi
|
|
87
|
-
export interface
|
|
86
|
+
/** Kimi Code hook input format */
|
|
87
|
+
export interface KimiCodeHookInput {
|
|
88
88
|
session_id?: string;
|
|
89
89
|
cwd?: string;
|
|
90
90
|
hook_event_name: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-safety-net",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "A coding agent CLI hook - block destructive git and filesystem commands before execution",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
]
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"shell-quote": "^1.8.
|
|
67
|
+
"shell-quote": "^1.8.4"
|
|
68
68
|
},
|
|
69
69
|
"trustedDependencies": [
|
|
70
70
|
"@ast-grep/cli"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function runKimiCliHook(): Promise<void>;
|