@michelj/context-guard 0.4.3 → 0.6.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/Coordinator.md +88 -0
- package/Executor.md +53 -0
- package/README.md +89 -224
- package/README.zh-CN.md +89 -224
- package/SKILL.md +26 -684
- package/THIRD_PARTY_NOTICES.md +47 -0
- package/Tester.md +53 -0
- package/agents/openai.yaml +2 -2
- package/bin/build-runtime.mjs +96 -0
- package/bin/context-guard-skill.js +399 -78
- package/bin/postinstall.js +2 -2
- package/hooks.json +89 -13
- package/licenses/JSONParse-MIT.txt +24 -0
- package/licenses/Marked-MIT.txt +44 -0
- package/licenses/Portless-Apache-2.0.txt +201 -0
- package/package.json +35 -6
- package/prototype/LICENSES/Marked-MIT.txt +44 -0
- package/prototype/LICENSES/Ready-redistribution.txt +14 -0
- package/prototype/attachments.mjs +75 -0
- package/prototype/coordinator-markdown.mjs +283 -0
- package/prototype/coordinator-working-blot.mjs +124 -0
- package/prototype/vendor/marked.mjs +2189 -0
- package/prototype/workbench-app.js +5197 -0
- package/prototype/workbench-data.js +33 -0
- package/prototype/workbench-sync.mjs +898 -0
- package/prototype/workbench.css +1050 -0
- package/prototype/workbench.html +211 -0
- package/prototype/working-blot-atlas.png +0 -0
- package/references/agent-handoff.md +40 -0
- package/references/claude-runtime.md +120 -0
- package/references/cloud-sync-interface.md +66 -0
- package/references/design-current.md +14 -0
- package/references/map-mount.md +41 -0
- package/references/map-read.md +50 -0
- package/references/memory-definition.md +120 -0
- package/references/memory-filesystem-v2/Bug.en.md +162 -0
- package/references/memory-filesystem-v2/Bug.md +162 -0
- package/references/memory-filesystem-v2/Bug_Coordinater.md +8 -0
- package/references/memory-filesystem-v2/Bug_Executor.md +8 -0
- package/references/memory-filesystem-v2/Bug_Tester.md +7 -0
- package/references/memory-filesystem-v2/Idea.en.md +36 -0
- package/references/memory-filesystem-v2/Idea.md +36 -0
- package/references/memory-filesystem-v2/Node_Module_Index.en.md +88 -0
- package/references/memory-filesystem-v2/Node_Module_Index.md +88 -0
- package/references/memory-filesystem-v2/README.md +60 -0
- package/references/memory-filesystem-v2/Todo.en.md +137 -0
- package/references/memory-filesystem-v2/Todo.md +137 -0
- package/references/memory-filesystem-v2/Todo_Coordinater.md +7 -0
- package/references/memory-filesystem-v2/Todo_Executor.md +7 -0
- package/references/memory-filesystem-v2/Todo_Tester.md +7 -0
- package/references/named-workbench.md +124 -0
- package/references/plan-review.md +12 -0
- package/references/server-memory.md +276 -0
- package/references/test-check.md +7 -0
- package/references/user-reply.md +38 -0
- package/references/workbench-interface.md +531 -0
- package/roles.md +13 -0
- package/scripts/context_guard.py +1366 -7602
- package/scripts/context_guard_hook.py +1960 -711
- package/scripts/map_owns.py +699 -0
- package/scripts/shared/LICENSES/JSONParse-MIT.txt +24 -0
- package/scripts/shared/filesystem-v2.mjs +430 -0
- package/scripts/shared/io.mjs +117 -0
- package/scripts/shared/map-model.mjs +506 -0
- package/scripts/shared/memory-schema.mjs +13 -0
- package/scripts/shared/protocol-blobs.mjs +112 -0
- package/scripts/shared/protocol-map.mjs +146 -0
- package/scripts/shared/protocol-snapshots.mjs +84 -0
- package/scripts/shared/protocol-store.mjs +624 -0
- package/scripts/shared/protocol-workflow.mjs +226 -0
- package/scripts/shared/protocol.mjs +125 -0
- package/scripts/shared/vendor/jsonparse.cjs +413 -0
- package/scripts/workbench/access.mjs +496 -0
- package/scripts/workbench/attachments.mjs +92 -0
- package/scripts/workbench/browser-login.mjs +78 -0
- package/scripts/workbench/claude-runtime.mjs +372 -0
- package/scripts/workbench/cli.mjs +980 -0
- package/scripts/workbench/device-heartbeat.mjs +72 -0
- package/scripts/workbench/hook-status.mjs +38 -0
- package/scripts/workbench/inbox.mjs +155 -0
- package/scripts/workbench/journal.mjs +56 -0
- package/scripts/workbench/memory-merge.mjs +65 -0
- package/scripts/workbench/memory.mjs +252 -0
- package/scripts/workbench/named-proxy.mjs +108 -0
- package/scripts/workbench/named.mjs +152 -0
- package/scripts/workbench/portless-routes.mjs +51 -0
- package/scripts/workbench/project.mjs +327 -0
- package/scripts/workbench/projections.mjs +68 -0
- package/scripts/workbench/protocol-client.mjs +165 -0
- package/scripts/workbench/protocol-delivery.mjs +133 -0
- package/scripts/workbench/protocol-device.mjs +316 -0
- package/scripts/workbench/protocol-events.mjs +53 -0
- package/scripts/workbench/protocol-repository.mjs +58 -0
- package/scripts/workbench/reconcile.mjs +244 -0
- package/scripts/workbench/registry.mjs +111 -0
- package/scripts/workbench/runtime.mjs +54 -0
- package/scripts/workbench/server.mjs +1171 -0
- package/scripts/workbench/store.mjs +243 -0
- package/scripts/workbench/sync-coordinator.mjs +518 -0
- package/scripts/workbench/sync.mjs +86 -0
- package/references/context-template.md +0 -341
- package/references/feature-chain-methodology.md +0 -228
- package/references/register-template.md +0 -85
- package/references/task-case-template.md +0 -63
- package/tests/BC-20260618-063.sh +0 -116
- package/tests/BC-20260618-065.sh +0 -66
- package/tests/BC-20260626-080.sh +0 -48
- package/tests/BC-20260626-081.sh +0 -40
- package/tests/BC-20260626-082.sh +0 -32
- package/tests/BC-20260626-083.sh +0 -66
- package/tests/BC-20260627-084.sh +0 -74
- package/tests/BC-20260630-086.sh +0 -50
- package/tests/BC-20260630-087.sh +0 -103
- package/tests/BC-20260630-088.sh +0 -32
- package/tests/BC-20260630-089.sh +0 -63
- package/tests/BC-20260701-090.sh +0 -84
- package/tests/BC-20260702-096.sh +0 -48
- package/tests/BC-20260706-098.sh +0 -66
- package/tests/BC-20260707-099.sh +0 -47
- package/tests/BC-20260707-100.sh +0 -46
- package/tests/BC-20260707-101.sh +0 -47
- package/tests/BC-20260707-102.sh +0 -68
- package/tests/BC-20260707-103.sh +0 -59
- package/tests/BC-20260707-104.sh +0 -103
- package/tests/BC-20260707-105.sh +0 -109
- package/tests/BC-20260707-106.sh +0 -80
- package/tests/BC-20260707-107.sh +0 -74
- package/tests/BC-20260707-108.sh +0 -48
- package/tests/BC-20260707-109.sh +0 -56
- package/tests/BC-20260707-110.sh +0 -71
- package/tests/BC-20260707-111.sh +0 -70
- package/tests/BC-20260707-112.sh +0 -45
- package/tests/BC-20260707-113.sh +0 -73
- package/tests/BC-20260707-115.sh +0 -77
- package/tests/BC-20260707-116.sh +0 -77
- package/tests/BC-20260707-118.sh +0 -115
- package/tests/BC-20260707-119.sh +0 -47
- package/tests/BC-20260707-120.sh +0 -60
- package/tests/BC-20260707-121.sh +0 -66
- package/tests/BC-20260707-122.sh +0 -48
- package/tests/BC-20260707-123.sh +0 -43
- package/tests/BC-20260707-124.sh +0 -56
- package/tests/BC-20260707-125.sh +0 -64
- package/tests/BC-20260707-126.sh +0 -80
- package/tests/BC-20260707-127.sh +0 -88
- package/tests/BC-20260707-129.sh +0 -59
- package/tests/BC-20260707-130.sh +0 -69
- package/tests/BC-20260707-131.sh +0 -140
- package/tests/BC-20260707-132.sh +0 -150
- package/tests/BC-20260707-133.sh +0 -70
- package/tests/BC-20260708-136.sh +0 -210
- package/tests/BC-20260708-137.sh +0 -106
- package/tests/BC-20260708-138.sh +0 -168
- package/tests/BC-20260708-139.sh +0 -79
- package/tests/BC-20260709-002.sh +0 -63
- package/tests/BC-20260709-003.sh +0 -239
- package/tests/BC-20260709-006.sh +0 -76
- package/tests/BC-20260709-008.sh +0 -168
- package/tests/BC-20260710-001.sh +0 -61
- package/tests/BC-20260710-002.sh +0 -111
- package/tests/npm-install-smoke.sh +0 -53
|
@@ -12,26 +12,37 @@ const sourceHooksPath = path.join(packageRoot, "hooks.json");
|
|
|
12
12
|
const pythonScript = path.join(sourceSkillDir, "scripts", "context_guard.py");
|
|
13
13
|
const skillInstallEntries = [
|
|
14
14
|
"SKILL.md",
|
|
15
|
+
"roles.md",
|
|
16
|
+
"Coordinator.md",
|
|
17
|
+
"Executor.md",
|
|
18
|
+
"Tester.md",
|
|
15
19
|
"README.md",
|
|
16
20
|
"README.zh-CN.md",
|
|
21
|
+
"THIRD_PARTY_NOTICES.md",
|
|
22
|
+
"licenses",
|
|
23
|
+
"bin",
|
|
17
24
|
"agents",
|
|
25
|
+
"prototype",
|
|
18
26
|
"references",
|
|
19
|
-
"scripts"
|
|
20
|
-
"tests"
|
|
27
|
+
"scripts"
|
|
21
28
|
];
|
|
22
29
|
|
|
23
30
|
function usage() {
|
|
24
31
|
console.log(`Context Guard Skill
|
|
25
32
|
|
|
26
33
|
Usage:
|
|
27
|
-
context-guard install [--
|
|
34
|
+
context-guard install [--platform auto|all|codex|cursor|claude] [--no-hooks]
|
|
35
|
+
[--target <dir>] [--hooks-target <file>] [--config-target <file>]
|
|
36
|
+
context-guard doctor [--platform auto|all|codex|cursor|claude] [--root <project>]
|
|
37
|
+
[--target <dir>] [--hooks-target <file>] [--config-target <file>] [--json]
|
|
28
38
|
context-guard path
|
|
39
|
+
context-guard sync ensure|status|pull|prepare|checkpoint|finish [args...]
|
|
29
40
|
context-guard <context_guard.py command> [args...]
|
|
30
41
|
|
|
31
42
|
Examples:
|
|
32
43
|
npx @michelj/context-guard install
|
|
33
|
-
npx @michelj/context-guard install --
|
|
34
|
-
npx @michelj/context-guard
|
|
44
|
+
npx @michelj/context-guard install --platform all
|
|
45
|
+
npx @michelj/context-guard init --root /path/to/project
|
|
35
46
|
`);
|
|
36
47
|
}
|
|
37
48
|
|
|
@@ -47,34 +58,68 @@ function expandHome(inputPath) {
|
|
|
47
58
|
return inputPath;
|
|
48
59
|
}
|
|
49
60
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
const PLATFORM_SPECS = {
|
|
62
|
+
codex: { env: "CODEX_HOME", folder: ".codex", hooksFile: "hooks.json", configFile: "config.toml" },
|
|
63
|
+
cursor: { env: "CURSOR_HOME", folder: ".cursor", hooksFile: "hooks.json" },
|
|
64
|
+
claude: { env: "CLAUDE_CONFIG_DIR", legacyEnv: "CLAUDE_HOME", folder: ".claude", hooksFile: "settings.json" }
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function platformHome(platform) {
|
|
68
|
+
return platformHomeBySpec(PLATFORM_SPECS[platform]);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function platformTargets(platform) {
|
|
72
|
+
const home = platformHome(platform);
|
|
73
|
+
return {
|
|
74
|
+
target: path.join(home, "skills", "context-guard"),
|
|
75
|
+
hooksTarget: path.join(home, PLATFORM_SPECS[platform].hooksFile),
|
|
76
|
+
configTarget: PLATFORM_SPECS[platform].configFile
|
|
77
|
+
? path.join(home, PLATFORM_SPECS[platform].configFile)
|
|
78
|
+
: null
|
|
79
|
+
};
|
|
56
80
|
}
|
|
57
81
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
82
|
+
let detectedPython;
|
|
83
|
+
function pythonCommand() {
|
|
84
|
+
if (detectedPython !== undefined) return detectedPython;
|
|
85
|
+
const candidates = process.platform === "win32" ? ["python", "python3"] : ["python3", "python"];
|
|
86
|
+
detectedPython = null;
|
|
87
|
+
for (const command of candidates) {
|
|
88
|
+
const probe = spawnSync(command, ["--version"], { encoding: "utf8", windowsHide: true, timeout: 5000 });
|
|
89
|
+
if (!probe.error && probe.status === 0 && /^Python 3\./m.test(`${probe.stdout || ""}\n${probe.stderr || ""}`)) {
|
|
90
|
+
detectedPython = command;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
61
93
|
}
|
|
62
|
-
|
|
63
|
-
return path.join(codexHome, "hooks.json");
|
|
94
|
+
return detectedPython;
|
|
64
95
|
}
|
|
65
96
|
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
97
|
+
function selectedPlatforms(requested) {
|
|
98
|
+
if (requested === "all") return Object.keys(PLATFORM_SPECS);
|
|
99
|
+
if (requested !== "auto") return [requested];
|
|
100
|
+
const detected = Object.entries(PLATFORM_SPECS)
|
|
101
|
+
.filter(([, spec]) => Boolean(process.env[spec.env] || process.env[spec.legacyEnv]) || fs.existsSync(platformHomeBySpec(spec)))
|
|
102
|
+
.map(([name]) => name);
|
|
103
|
+
return detected.length ? detected : ["codex"];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function platformHomeBySpec(spec) {
|
|
107
|
+
return path.resolve(expandHome(process.env[spec.env] || process.env[spec.legacyEnv] || path.join(os.homedir(), spec.folder)));
|
|
69
108
|
}
|
|
70
109
|
|
|
71
110
|
function parseInstallArgs(args) {
|
|
72
111
|
const options = {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
112
|
+
platform: "auto",
|
|
113
|
+
target: process.env.CONTEXT_GUARD_SKILL_TARGET
|
|
114
|
+
? path.resolve(expandHome(process.env.CONTEXT_GUARD_SKILL_TARGET))
|
|
115
|
+
: null,
|
|
116
|
+
withHooks: true,
|
|
117
|
+
hooksTarget: process.env.CONTEXT_GUARD_HOOKS_TARGET
|
|
118
|
+
? path.resolve(expandHome(process.env.CONTEXT_GUARD_HOOKS_TARGET))
|
|
119
|
+
: null,
|
|
120
|
+
configTarget: null,
|
|
121
|
+
targetExplicit: Boolean(process.env.CONTEXT_GUARD_SKILL_TARGET),
|
|
122
|
+
hooksTargetExplicit: Boolean(process.env.CONTEXT_GUARD_HOOKS_TARGET),
|
|
78
123
|
dryRun: false
|
|
79
124
|
};
|
|
80
125
|
|
|
@@ -84,20 +129,26 @@ function parseInstallArgs(args) {
|
|
|
84
129
|
const value = args[++i];
|
|
85
130
|
if (!value) fail("--target requires a directory");
|
|
86
131
|
options.target = path.resolve(expandHome(value));
|
|
132
|
+
options.targetExplicit = true;
|
|
133
|
+
} else if (arg === "--platform") {
|
|
134
|
+
const value = String(args[++i] || "").toLowerCase();
|
|
135
|
+
if (!["auto", "all", ...Object.keys(PLATFORM_SPECS)].includes(value)) {
|
|
136
|
+
fail("--platform must be auto, all, codex, cursor, or claude");
|
|
137
|
+
}
|
|
138
|
+
options.platform = value;
|
|
87
139
|
} else if (arg === "--with-hooks" || arg === "--hooks") {
|
|
88
140
|
options.withHooks = true;
|
|
141
|
+
} else if (arg === "--no-hooks") {
|
|
142
|
+
options.withHooks = false;
|
|
89
143
|
} else if (arg === "--hooks-target") {
|
|
90
144
|
const value = args[++i];
|
|
91
145
|
if (!value) fail("--hooks-target requires a file path");
|
|
92
146
|
options.hooksTarget = path.resolve(expandHome(value));
|
|
93
|
-
|
|
94
|
-
options.configTarget = path.join(path.dirname(options.hooksTarget), "config.toml");
|
|
95
|
-
}
|
|
147
|
+
options.hooksTargetExplicit = true;
|
|
96
148
|
} else if (arg === "--config-target") {
|
|
97
149
|
const value = args[++i];
|
|
98
150
|
if (!value) fail("--config-target requires a file path");
|
|
99
151
|
options.configTarget = path.resolve(expandHome(value));
|
|
100
|
-
options.configTargetExplicit = true;
|
|
101
152
|
} else if (arg === "--dry-run") {
|
|
102
153
|
options.dryRun = true;
|
|
103
154
|
} else if (arg === "-h" || arg === "--help") {
|
|
@@ -107,10 +158,20 @@ function parseInstallArgs(args) {
|
|
|
107
158
|
fail(`unknown install option: ${arg}`);
|
|
108
159
|
}
|
|
109
160
|
}
|
|
161
|
+
if ((options.target || options.hooksTarget || options.configTarget) && options.platform === "all") {
|
|
162
|
+
fail("custom targets can only be used with one platform");
|
|
163
|
+
}
|
|
164
|
+
if (options.targetExplicit && !options.hooksTargetExplicit) {
|
|
165
|
+
const platform = options.platform === "auto" ? "codex" : options.platform;
|
|
166
|
+
options.hooksTarget = path.join(path.dirname(options.target), PLATFORM_SPECS[platform].hooksFile);
|
|
167
|
+
}
|
|
168
|
+
if (!options.configTarget && options.hooksTarget && (options.platform === "auto" || options.platform === "codex")) {
|
|
169
|
+
options.configTarget = path.join(path.dirname(options.hooksTarget), "config.toml");
|
|
170
|
+
}
|
|
110
171
|
return options;
|
|
111
172
|
}
|
|
112
173
|
|
|
113
|
-
function
|
|
174
|
+
function migratedHooksFeatureConfig(configTarget) {
|
|
114
175
|
const original = fs.existsSync(configTarget) ? fs.readFileSync(configTarget, "utf8") : "";
|
|
115
176
|
const lines = original ? original.replace(/\r\n/g, "\n").split("\n") : [];
|
|
116
177
|
const sectionStart = lines.findIndex((line) => /^\s*\[features\]\s*(?:#.*)?$/.test(line));
|
|
@@ -149,50 +210,59 @@ function migrateHooksFeatureConfig(configTarget, dryRun) {
|
|
|
149
210
|
}
|
|
150
211
|
|
|
151
212
|
const next = `${nextLines.join("\n").replace(/\n*$/, "")}\n`;
|
|
152
|
-
|
|
153
|
-
if (dryRun) {
|
|
154
|
-
console.log(`[context-guard-skill] would enable hooks in ${configTarget}`);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
fs.mkdirSync(path.dirname(configTarget), { recursive: true });
|
|
158
|
-
if (fs.existsSync(configTarget)) {
|
|
159
|
-
const backupPath = `${configTarget}.bak-${new Date().toISOString().replace(/[:.]/g, "-")}`;
|
|
160
|
-
fs.copyFileSync(configTarget, backupPath);
|
|
161
|
-
console.log(`[context-guard-skill] backed up config: ${backupPath}`);
|
|
162
|
-
}
|
|
163
|
-
fs.writeFileSync(configTarget, next);
|
|
164
|
-
console.log(`[context-guard-skill] enabled hooks: ${configTarget}`);
|
|
213
|
+
return next === original.replace(/\r\n/g, "\n") ? null : next;
|
|
165
214
|
}
|
|
166
215
|
|
|
167
|
-
function copySkill(target
|
|
216
|
+
function copySkill(target) {
|
|
168
217
|
if (!fs.existsSync(path.join(sourceSkillDir, "SKILL.md"))) {
|
|
169
|
-
|
|
218
|
+
throw new Error(`source skill folder is missing: ${sourceSkillDir}`);
|
|
170
219
|
}
|
|
171
|
-
if (dryRun) {
|
|
172
|
-
console.log(`[context-guard-skill] would install skill to ${target}`);
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
fs.rmSync(target, { recursive: true, force: true });
|
|
176
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
177
220
|
fs.mkdirSync(target, { recursive: true });
|
|
178
221
|
for (const entry of skillInstallEntries) {
|
|
179
222
|
const from = path.join(sourceSkillDir, entry);
|
|
180
223
|
if (!fs.existsSync(from)) continue;
|
|
181
224
|
const to = path.join(target, entry);
|
|
182
|
-
|
|
225
|
+
const options = entry === "scripts"
|
|
226
|
+
? {
|
|
227
|
+
recursive: true,
|
|
228
|
+
filter(source) {
|
|
229
|
+
// Node 18 may pass a namespaced Windows path to this callback.
|
|
230
|
+
const relative = path.relative(path.toNamespacedPath(from), path.toNamespacedPath(source));
|
|
231
|
+
if (relative === "") return true;
|
|
232
|
+
const parts = relative.split(path.sep);
|
|
233
|
+
return parts[0] !== "cloud"
|
|
234
|
+
&& relative !== "branch_guard.py"
|
|
235
|
+
&& !parts.includes("__pycache__")
|
|
236
|
+
&& !/\.py[co]$/i.test(parts.at(-1));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
: entry === "bin"
|
|
240
|
+
? { recursive: true, filter(source) {
|
|
241
|
+
const relative = path.relative(path.toNamespacedPath(from), path.toNamespacedPath(source));
|
|
242
|
+
return relative === "" || relative === "context-guard-skill.js";
|
|
243
|
+
} }
|
|
244
|
+
: { recursive: true };
|
|
245
|
+
fs.cpSync(from, to, options);
|
|
183
246
|
}
|
|
184
|
-
console.log(`[context-guard-skill] installed skill: ${target}`);
|
|
185
247
|
}
|
|
186
248
|
|
|
187
|
-
function
|
|
249
|
+
function hookCommand(skillTarget, event, platform) {
|
|
188
250
|
const hookScript = path.join(skillTarget, "scripts", "context_guard_hook.py");
|
|
189
251
|
const encodedHookScript = JSON.stringify(hookScript);
|
|
252
|
+
const python = pythonCommand();
|
|
253
|
+
if (!python) throw new Error("Python 3 is required before lifecycle hooks can be installed");
|
|
254
|
+
return `${python} ${encodedHookScript} ${event} --platform ${platform}`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function rewriteGroupedHookCommands(hooksConfig, skillTarget, platform) {
|
|
190
258
|
const next = JSON.parse(JSON.stringify(hooksConfig));
|
|
191
259
|
for (const groups of Object.values(next.hooks || {})) {
|
|
192
260
|
for (const group of groups || []) {
|
|
193
261
|
for (const hook of group.hooks || []) {
|
|
194
262
|
if (hook.type === "command" && typeof hook.command === "string" && hook.command.includes("context_guard_hook.py")) {
|
|
195
|
-
|
|
263
|
+
const match = hook.command.match(/context_guard_hook\.py["']?\s+([a-z-]+)/);
|
|
264
|
+
const event = match ? match[1] : "session-start";
|
|
265
|
+
hook.command = hookCommand(skillTarget, event, platform);
|
|
196
266
|
}
|
|
197
267
|
}
|
|
198
268
|
}
|
|
@@ -200,59 +270,305 @@ function rewriteHookCommands(hooksConfig, skillTarget) {
|
|
|
200
270
|
return next;
|
|
201
271
|
}
|
|
202
272
|
|
|
273
|
+
function cursorHooks(skillTarget) {
|
|
274
|
+
const events = {
|
|
275
|
+
sessionStart: "session-start",
|
|
276
|
+
subagentStart: "subagent-start",
|
|
277
|
+
beforeSubmitPrompt: "user-prompt-submit",
|
|
278
|
+
subagentStop: "subagent-stop",
|
|
279
|
+
stop: "stop"
|
|
280
|
+
};
|
|
281
|
+
const hooks = {};
|
|
282
|
+
for (const [cursorEvent, normalizedEvent] of Object.entries(events)) {
|
|
283
|
+
hooks[cursorEvent] = [{
|
|
284
|
+
type: "command",
|
|
285
|
+
command: hookCommand(skillTarget, normalizedEvent, "cursor"),
|
|
286
|
+
timeout: normalizedEvent === "session-start" ? 20 : 10
|
|
287
|
+
}];
|
|
288
|
+
}
|
|
289
|
+
return { version: 1, hooks };
|
|
290
|
+
}
|
|
291
|
+
|
|
203
292
|
function mergeHooks(existing, incoming) {
|
|
204
293
|
const merged = existing && typeof existing === "object" ? existing : {};
|
|
205
294
|
merged.hooks = merged.hooks && typeof merged.hooks === "object" ? merged.hooks : {};
|
|
206
295
|
for (const [event, groups] of Object.entries(incoming.hooks || {})) {
|
|
207
296
|
const current = Array.isArray(merged.hooks[event]) ? merged.hooks[event] : [];
|
|
208
|
-
const withoutOldContextGuard = current.
|
|
209
|
-
const hooks =
|
|
210
|
-
|
|
297
|
+
const withoutOldContextGuard = current.flatMap((group) => {
|
|
298
|
+
const hooks = group.hooks.filter((hook) => !String(hook.command || "").includes("context_guard_hook.py"));
|
|
299
|
+
if (hooks.length === group.hooks.length) return [group];
|
|
300
|
+
return hooks.length ? [{ ...group, hooks }] : [];
|
|
211
301
|
});
|
|
212
302
|
merged.hooks[event] = withoutOldContextGuard.concat(groups);
|
|
213
303
|
}
|
|
214
304
|
return merged;
|
|
215
305
|
}
|
|
216
306
|
|
|
217
|
-
function
|
|
307
|
+
function mergeCursorHooks(existing, incoming) {
|
|
308
|
+
const merged = existing && typeof existing === "object" ? existing : {};
|
|
309
|
+
merged.version = merged.version || 1;
|
|
310
|
+
merged.hooks = merged.hooks && typeof merged.hooks === "object" ? merged.hooks : {};
|
|
311
|
+
for (const [event, hooks] of Object.entries(incoming.hooks || {})) {
|
|
312
|
+
const current = Array.isArray(merged.hooks[event]) ? merged.hooks[event] : [];
|
|
313
|
+
merged.hooks[event] = current
|
|
314
|
+
.filter((hook) => !String(hook && hook.command || "").includes("context_guard_hook.py"))
|
|
315
|
+
.concat(hooks);
|
|
316
|
+
}
|
|
317
|
+
return merged;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function readObject(target, platform) {
|
|
321
|
+
if (!fs.existsSync(target)) return {};
|
|
322
|
+
const object = value => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
323
|
+
let value;
|
|
324
|
+
try {
|
|
325
|
+
value = JSON.parse(fs.readFileSync(target, "utf8").replace(/^\uFEFF/, ""));
|
|
326
|
+
} catch (error) {
|
|
327
|
+
throw new Error(`Cannot read JSON config ${target}: ${error.message}`);
|
|
328
|
+
}
|
|
329
|
+
if (!object(value) || (value.hooks !== undefined && !object(value.hooks))) {
|
|
330
|
+
throw new Error(`Invalid config object or hooks in ${target}; original file was not changed.`);
|
|
331
|
+
}
|
|
332
|
+
for (const [event, entries] of Object.entries(value.hooks || {})) {
|
|
333
|
+
if (!Array.isArray(entries) || entries.some(entry => !object(entry) ||
|
|
334
|
+
(platform !== "cursor" && (!Array.isArray(entry.hooks) || entry.hooks.some(hook => !object(hook)))))) {
|
|
335
|
+
throw new Error(`Invalid hook event ${event} in ${target}; original file was not changed.`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return value;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const CLAUDE_EVENTS = ["SessionStart", "SubagentStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest", "PostToolUse", "PostToolUseFailure", "PreCompact", "PostCompact", "SubagentStop", "Stop", "StopFailure", "SessionEnd"];
|
|
342
|
+
|
|
343
|
+
function plannedHooks(platform, skillTarget, hooksTarget) {
|
|
218
344
|
if (!fs.existsSync(sourceHooksPath)) {
|
|
219
|
-
|
|
345
|
+
throw new Error(`source hooks file is missing: ${sourceHooksPath}`);
|
|
346
|
+
}
|
|
347
|
+
const existing = readObject(hooksTarget, platform);
|
|
348
|
+
if (platform === "cursor") {
|
|
349
|
+
return mergeCursorHooks(existing, cursorHooks(skillTarget));
|
|
220
350
|
}
|
|
221
351
|
const rawIncoming = JSON.parse(fs.readFileSync(sourceHooksPath, "utf8"));
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
352
|
+
if (platform === "claude") {
|
|
353
|
+
for (const [name, source, from, to] of [["PostToolUseFailure", "PostToolUse", "post-tool-use", "post-tool-use-failure"], ["SessionEnd", "Stop", "stop", "session-end"], ["StopFailure", "Interrupt", "interrupt", "stop-failure"]]) {
|
|
354
|
+
rawIncoming.hooks[name] = JSON.parse(JSON.stringify(rawIncoming.hooks[source]));
|
|
355
|
+
for (const group of rawIncoming.hooks[name]) for (const hook of group.hooks) hook.command = hook.command.replace(`context_guard_hook.py ${from} `, `context_guard_hook.py ${to} `);
|
|
356
|
+
}
|
|
357
|
+
const supported = new Set(CLAUDE_EVENTS);
|
|
358
|
+
rawIncoming.hooks = Object.fromEntries(Object.entries(rawIncoming.hooks || {}).filter(([event]) => supported.has(event)));
|
|
359
|
+
}
|
|
360
|
+
const incoming = rewriteGroupedHookCommands(rawIncoming, skillTarget, platform);
|
|
361
|
+
return mergeHooks(existing, incoming);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function containsPath(parent, child) {
|
|
365
|
+
const relative = path.relative(parent, child);
|
|
366
|
+
return relative === "" || (!relative.startsWith(".." + path.sep) && relative !== ".." && !path.isAbsolute(relative));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function applyInstallPlan(plan, dryRun) {
|
|
370
|
+
for (const entry of plan) {
|
|
371
|
+
if (containsPath(entry.target, os.homedir())) {
|
|
372
|
+
throw new Error(`Install destination must not replace the user home or its parent: ${entry.target}`);
|
|
373
|
+
}
|
|
374
|
+
if (containsPath(entry.target, sourceSkillDir) || containsPath(sourceSkillDir, entry.target)) {
|
|
375
|
+
throw new Error(`Install destination overlaps its source package: ${entry.target}`);
|
|
376
|
+
}
|
|
377
|
+
const stat = fs.lstatSync(entry.target, { throwIfNoEntry: false });
|
|
378
|
+
if (stat && (stat.isSymbolicLink() || (entry.kind === "skill" ? !stat.isDirectory() : !stat.isFile()))) {
|
|
379
|
+
throw new Error(`Invalid install destination: ${entry.target}`);
|
|
380
|
+
}
|
|
381
|
+
entry.mode = stat ? stat.mode & 0o777 : 0o600;
|
|
382
|
+
for (const other of plan) if (entry !== other && containsPath(entry.target, other.target)) {
|
|
383
|
+
throw new Error(`Overlapping install destinations: ${entry.target} and ${other.target}`);
|
|
384
|
+
}
|
|
226
385
|
}
|
|
227
|
-
const merged = mergeHooks(existing, incoming);
|
|
228
386
|
if (dryRun) {
|
|
229
|
-
console.log(`[context-guard-skill] would install
|
|
387
|
+
for (const entry of plan) console.log(`[context-guard-skill] would install ${entry.kind}: ${entry.target}`);
|
|
230
388
|
return;
|
|
231
389
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
390
|
+
|
|
391
|
+
const staged = [];
|
|
392
|
+
try {
|
|
393
|
+
// Prepare every replacement before touching existing files. Temporary
|
|
394
|
+
// siblings allow renames on the same filesystem, including on Windows.
|
|
395
|
+
for (const entry of plan) {
|
|
396
|
+
fs.mkdirSync(path.dirname(entry.target), { recursive: true });
|
|
397
|
+
entry.temp = fs.mkdtempSync(path.join(path.dirname(entry.target), ".context-guard-install-"));
|
|
398
|
+
entry.next = path.join(entry.temp, "next");
|
|
399
|
+
entry.previous = path.join(entry.temp, "previous");
|
|
400
|
+
staged.push(entry);
|
|
401
|
+
if (entry.kind === "skill") {
|
|
402
|
+
copySkill(entry.next);
|
|
403
|
+
// Existing Claude receivers may still reference this absolute prompt path.
|
|
404
|
+
if (fs.existsSync(path.join(entry.target, "Developer.md"))) {
|
|
405
|
+
fs.copyFileSync(path.join(entry.next, "Executor.md"), path.join(entry.next, "Developer.md"));
|
|
406
|
+
}
|
|
407
|
+
} else fs.writeFileSync(entry.next, entry.content, { mode: entry.mode });
|
|
408
|
+
}
|
|
409
|
+
for (const entry of staged) {
|
|
410
|
+
if (fs.existsSync(entry.target)) {
|
|
411
|
+
fs.renameSync(entry.target, entry.previous);
|
|
412
|
+
entry.oldMoved = true;
|
|
413
|
+
}
|
|
414
|
+
fs.renameSync(entry.next, entry.target);
|
|
415
|
+
entry.newMoved = true;
|
|
416
|
+
}
|
|
417
|
+
} catch (error) {
|
|
418
|
+
for (const entry of staged.slice().reverse()) {
|
|
419
|
+
try {
|
|
420
|
+
if (entry.newMoved) fs.rmSync(entry.target, { recursive: entry.kind === "skill", force: true });
|
|
421
|
+
if (entry.oldMoved) fs.renameSync(entry.previous, entry.target);
|
|
422
|
+
} catch (rollbackError) {
|
|
423
|
+
entry.preserve = true;
|
|
424
|
+
console.error(`[context-guard-skill] Recovery needed; kept ${entry.temp}: ${rollbackError.message}`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
throw error;
|
|
428
|
+
} finally {
|
|
429
|
+
for (const entry of staged) {
|
|
430
|
+
if (entry.preserve) continue;
|
|
431
|
+
try {
|
|
432
|
+
// Keep original client configuration as a uniquely named backup only
|
|
433
|
+
// after a successful replacement. Failed installs restore it instead.
|
|
434
|
+
if (entry.kind !== "skill" && fs.existsSync(entry.previous)) {
|
|
435
|
+
const backup = `${entry.target}.bak-${path.basename(entry.temp)}`;
|
|
436
|
+
fs.renameSync(entry.previous, backup);
|
|
437
|
+
console.log(`[context-guard-skill] backed up config: ${backup}`);
|
|
438
|
+
}
|
|
439
|
+
fs.rmSync(entry.temp, { recursive: true, force: true });
|
|
440
|
+
} catch (error) {
|
|
441
|
+
console.warn(`[context-guard-skill] Kept recovery files at ${entry.temp}: ${error.message}`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
237
444
|
}
|
|
238
|
-
|
|
239
|
-
console.log(`[context-guard-skill] installed hooks: ${hooksTarget}`);
|
|
445
|
+
for (const entry of plan) console.log(`[context-guard-skill] installed ${entry.kind}: ${entry.target}`);
|
|
240
446
|
}
|
|
241
447
|
|
|
242
448
|
function install(args) {
|
|
243
449
|
const options = parseInstallArgs(args);
|
|
244
|
-
|
|
245
|
-
if (options.
|
|
246
|
-
|
|
247
|
-
|
|
450
|
+
let platforms = selectedPlatforms(options.platform);
|
|
451
|
+
if ((options.target || options.hooksTarget || options.configTarget) && options.platform === "auto") {
|
|
452
|
+
platforms = ["codex"];
|
|
453
|
+
}
|
|
454
|
+
const plan = [];
|
|
455
|
+
for (const platform of platforms) {
|
|
456
|
+
const defaults = platformTargets(platform);
|
|
457
|
+
const skillTarget = options.target || defaults.target;
|
|
458
|
+
const hooksTarget = options.hooksTarget || defaults.hooksTarget;
|
|
459
|
+
const configTarget = options.configTarget || defaults.configTarget;
|
|
460
|
+
plan.push({ target: skillTarget, kind: "skill" });
|
|
461
|
+
if (options.withHooks) {
|
|
462
|
+
const hooks = plannedHooks(platform, skillTarget, hooksTarget);
|
|
463
|
+
if (platform === "codex" && configTarget) {
|
|
464
|
+
const content = migratedHooksFeatureConfig(configTarget);
|
|
465
|
+
if (content !== null) plan.push({ target: configTarget, kind: "config", content });
|
|
466
|
+
}
|
|
467
|
+
plan.push({ target: hooksTarget, kind: "hooks", content: `${JSON.stringify(hooks, null, 2)}\n` });
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
applyInstallPlan(plan, options.dryRun);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function parseDoctorArgs(args) {
|
|
474
|
+
const options = { platform: "auto", root: process.cwd(), target: null, hooksTarget: null, configTarget: null, json: false };
|
|
475
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
476
|
+
const arg = args[i];
|
|
477
|
+
if (["--platform", "--root", "--target", "--hooks-target", "--config-target"].includes(arg)) {
|
|
478
|
+
const value = args[++i];
|
|
479
|
+
if (!value) fail(`${arg} requires a value`);
|
|
480
|
+
if (arg === "--platform") {
|
|
481
|
+
const platform = String(value).toLowerCase();
|
|
482
|
+
if (!["auto", "all", ...Object.keys(PLATFORM_SPECS)].includes(platform)) fail("--platform must be auto, all, codex, cursor, or claude");
|
|
483
|
+
options.platform = platform;
|
|
484
|
+
} else options[{ "--root": "root", "--target": "target", "--hooks-target": "hooksTarget", "--config-target": "configTarget" }[arg]] = path.resolve(expandHome(value));
|
|
485
|
+
} else if (arg === "--json") options.json = true;
|
|
486
|
+
else fail(`unknown doctor option: ${arg}`);
|
|
487
|
+
}
|
|
488
|
+
if ((options.target || options.hooksTarget || options.configTarget) && options.platform === "all") fail("custom doctor targets can only be used with one platform");
|
|
489
|
+
return options;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function hookCommands(config, platform, event) {
|
|
493
|
+
const entries = config?.hooks?.[event];
|
|
494
|
+
if (!Array.isArray(entries)) return [];
|
|
495
|
+
if (platform === "cursor") return entries.map(item => item?.command).filter(Boolean);
|
|
496
|
+
return entries.flatMap(group => (group?.hooks || []).map(item => item?.command)).filter(Boolean);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function doctor(args) {
|
|
500
|
+
const options = parseDoctorArgs(args);
|
|
501
|
+
const results = [];
|
|
502
|
+
const check = (name, ok, detail, required = true) => results.push({ name, ok: Boolean(ok), detail, required });
|
|
503
|
+
const python = pythonCommand();
|
|
504
|
+
check("python", python, python ? `${python} is Python 3` : "no working Python 3 interpreter");
|
|
505
|
+
let platforms = selectedPlatforms(options.platform);
|
|
506
|
+
if ((options.target || options.hooksTarget || options.configTarget) && options.platform === "auto") platforms = ["codex"];
|
|
507
|
+
const eventNames = {
|
|
508
|
+
codex: ["SessionStart", "SubagentStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest", "PostToolUse", "PreCompact", "PostCompact", "SubagentStop", "Stop", "Interrupt"],
|
|
509
|
+
claude: CLAUDE_EVENTS,
|
|
510
|
+
cursor: ["sessionStart", "subagentStart", "beforeSubmitPrompt", "subagentStop", "stop"]
|
|
511
|
+
};
|
|
512
|
+
for (const platform of platforms) {
|
|
513
|
+
const defaults = platformTargets(platform);
|
|
514
|
+
const target = options.target || defaults.target;
|
|
515
|
+
const hooksTarget = options.hooksTarget || defaults.hooksTarget;
|
|
516
|
+
const configTarget = options.configTarget || defaults.configTarget;
|
|
517
|
+
const installedLauncher = path.join(target, "bin", "context-guard-skill.js");
|
|
518
|
+
check(`${platform}.skill`, fs.existsSync(path.join(target, "SKILL.md")) && fs.existsSync(path.join(target, "scripts", "context_guard_hook.py")) && fs.existsSync(installedLauncher), target);
|
|
519
|
+
check(`${platform}.cli`, fs.existsSync(installedLauncher), installedLauncher);
|
|
520
|
+
let config = null;
|
|
521
|
+
try { config = readObject(hooksTarget, platform); } catch (error) { check(`${platform}.hooks`, false, error.message); }
|
|
522
|
+
if (config) {
|
|
523
|
+
const missing = eventNames[platform].filter(event => !hookCommands(config, platform, event).some(command => command.includes("context_guard_hook.py") && command.includes(`--platform ${platform}`)));
|
|
524
|
+
check(`${platform}.hooks`, missing.length === 0, missing.length ? `missing ${missing.join(", ")}` : hooksTarget);
|
|
525
|
+
}
|
|
526
|
+
if (platform === "codex" && configTarget) {
|
|
527
|
+
const text = fs.existsSync(configTarget) ? fs.readFileSync(configTarget, "utf8") : "";
|
|
528
|
+
check("codex.hooks-feature", /^\s*hooks\s*=\s*true\s*$/m.test(text), configTarget);
|
|
529
|
+
const probe = spawnSync(process.execPath, [path.join(sourceSkillDir, 'scripts/workbench/hook-status.mjs'), options.root, target], { encoding: 'utf8', windowsHide: true, timeout: 7000 });
|
|
530
|
+
let native = {}; try { native = JSON.parse(probe.stdout); } catch {}
|
|
531
|
+
check('codex.hooks-trust', native.trusted, native.unavailable ? 'native trust status unavailable; not ready' : JSON.stringify(native));
|
|
532
|
+
const crypto = require('crypto');
|
|
533
|
+
let expected = '', installed = '';
|
|
534
|
+
try { expected = crypto.createHash('sha256').update(fs.readFileSync(path.join(sourceSkillDir, 'scripts/context_guard_hook.py'))).digest('hex'); installed = crypto.createHash('sha256').update(fs.readFileSync(path.join(target, 'scripts/context_guard_hook.py'))).digest('hex'); } catch {}
|
|
535
|
+
check('codex.hooks-version', expected && expected === installed, 'installed Hook must match this release');
|
|
536
|
+
let events = [];
|
|
537
|
+
try { events = fs.readFileSync(path.join(options.root, '.codex/context/sessions.jsonl'), 'utf8').split('\n').filter(Boolean).map(JSON.parse); } catch {}
|
|
538
|
+
const current = events.filter(event => event.hook_sha256 === installed && (!process.env.CODEX_THREAD_ID || event.session_id === process.env.CODEX_THREAD_ID));
|
|
539
|
+
check('codex.hooks-executed', current.length > 0, 'execution evidence for the current installed script');
|
|
540
|
+
check('codex.context-emitted', current.some(event => event.context_emitted), 'context output emitted; delivery to a model is not observable by doctor');
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
const ctx = path.join(options.root, ".codex", "context");
|
|
544
|
+
let map = null;
|
|
545
|
+
try { map = JSON.parse(fs.readFileSync(path.join(ctx, "map.json"), "utf8")); } catch {}
|
|
546
|
+
check("project.map", map?.root?.id || map?.root === null, path.join(ctx, "map.json"));
|
|
547
|
+
check("project.sessions", fs.existsSync(path.join(ctx, "sessions.jsonl")), path.join(ctx, "sessions.jsonl"));
|
|
548
|
+
const diagnosticLauncher = platforms.length === 1 ? path.join((options.target || platformTargets(platforms[0]).target), "bin", "context-guard-skill.js") : null;
|
|
549
|
+
let diagnostic = null;
|
|
550
|
+
if (diagnosticLauncher && fs.existsSync(diagnosticLauncher)) {
|
|
551
|
+
const probe = spawnSync(process.execPath, [diagnosticLauncher, "workbench", "--diagnose", "--root", options.root, ...(process.env.CODEX_THREAD_ID ? ["--session", process.env.CODEX_THREAD_ID] : [])], { encoding: "utf8", windowsHide: true, timeout: 7000 });
|
|
552
|
+
try { diagnostic = JSON.parse(probe.stdout); } catch {}
|
|
553
|
+
}
|
|
554
|
+
const runtimeStatus = diagnostic?.runtime?.status || "unknown";
|
|
555
|
+
check("project.workbench", ["ready", "stopped"].includes(runtimeStatus), diagnostic ? JSON.stringify({ status: runtimeStatus, named: diagnostic.runtime.named, services: diagnostic.runtime.services }) : "diagnosis unavailable", false);
|
|
556
|
+
const ok = results.every(item => !item.required || item.ok);
|
|
557
|
+
if (options.json) console.log(JSON.stringify({ ok, results }, null, 2));
|
|
558
|
+
else {
|
|
559
|
+
for (const item of results) console.log(`[${item.ok ? "ok" : item.required ? "fail" : "warn"}] ${item.name}: ${item.detail}`);
|
|
560
|
+
console.log(`[context-guard-skill] doctor: ${ok ? "ready" : "not ready"}`);
|
|
248
561
|
}
|
|
562
|
+
if (!ok) process.exitCode = 1;
|
|
249
563
|
}
|
|
250
564
|
|
|
251
565
|
function runPython(args) {
|
|
252
566
|
if (!fs.existsSync(pythonScript)) {
|
|
253
567
|
fail(`context_guard.py is missing: ${pythonScript}`);
|
|
254
568
|
}
|
|
255
|
-
const
|
|
569
|
+
const command = pythonCommand();
|
|
570
|
+
if (!command) fail("Python 3 is required; no working Python 3 interpreter was found (`python`, `python3`).");
|
|
571
|
+
const result = spawnSync(command, [pythonScript, ...args], { stdio: "inherit", windowsHide: true });
|
|
256
572
|
if (result.error) fail(result.error.message);
|
|
257
573
|
process.exit(result.status === null ? 1 : result.status);
|
|
258
574
|
}
|
|
@@ -262,9 +578,14 @@ const [command, ...rest] = process.argv.slice(2);
|
|
|
262
578
|
if (!command || command === "-h" || command === "--help" || command === "help") {
|
|
263
579
|
usage();
|
|
264
580
|
} else if (command === "install") {
|
|
265
|
-
install(rest);
|
|
581
|
+
try { install(rest); } catch (error) { fail(error.message); }
|
|
266
582
|
} else if (command === "path") {
|
|
267
583
|
console.log(sourceSkillDir);
|
|
584
|
+
} else if (command === "doctor") {
|
|
585
|
+
doctor(rest);
|
|
586
|
+
} else if (["map", "workbench", "memory", "preferences", "sync"].includes(command)) {
|
|
587
|
+
const result = spawnSync(process.execPath, [path.join(sourceSkillDir, "scripts", "workbench", "cli.mjs"), command, ...rest], { stdio: "inherit", windowsHide: true });
|
|
588
|
+
process.exit(result.status ?? 1);
|
|
268
589
|
} else {
|
|
269
590
|
runPython([command, ...rest]);
|
|
270
591
|
}
|
package/bin/postinstall.js
CHANGED
|
@@ -13,12 +13,12 @@ if (skipInstall) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
if (!isGlobalInstall && !autoInstall) {
|
|
16
|
-
console.log("[context-guard-skill] package installed. Run `npx @michelj/context-guard install` to install the
|
|
16
|
+
console.log("[context-guard-skill] package installed. Run `npx @michelj/context-guard install` to install the skill and hooks for detected clients.");
|
|
17
17
|
process.exit(0);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const cli = path.join(__dirname, "context-guard-skill.js");
|
|
21
|
-
const result = spawnSync(process.execPath, [cli, "install"], { stdio: "inherit" });
|
|
21
|
+
const result = spawnSync(process.execPath, [cli, "install"], { stdio: "inherit", windowsHide: true });
|
|
22
22
|
|
|
23
23
|
if (result.error) {
|
|
24
24
|
console.warn(`[context-guard-skill] auto install skipped: ${result.error.message}`);
|