@minhpnq1807/contextos 0.2.0 → 0.3.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.
@@ -0,0 +1,56 @@
1
+ import fs from "node:fs";
2
+ import { resolveHookCwd } from "./hook-io.js";
3
+
4
+ export function antigravityCwd(payload) {
5
+ return payload.cwd
6
+ || payload.working_directory
7
+ || payload.workspacePath
8
+ || payload.workspacePaths?.[0]
9
+ || resolveHookCwd(payload);
10
+ }
11
+
12
+ function textFromValue(value) {
13
+ if (!value) return "";
14
+ if (typeof value === "string") return value;
15
+ if (Array.isArray(value)) return value.map(textFromValue).filter(Boolean).join("\n");
16
+ if (typeof value !== "object") return "";
17
+ if (typeof value.text === "string") return value.text;
18
+ if (typeof value.content === "string") return value.content;
19
+ if (typeof value.message === "string") return value.message;
20
+ if (typeof value.userMessage === "string") return value.userMessage;
21
+ if (Array.isArray(value.parts)) return textFromValue(value.parts);
22
+ if (Array.isArray(value.content)) return textFromValue(value.content);
23
+ return "";
24
+ }
25
+
26
+ function looksUserAuthored(record) {
27
+ const role = String(record.role || record.author || record.type || record.sender || "").toLowerCase();
28
+ return !role || role.includes("user") || role.includes("human");
29
+ }
30
+
31
+ export function extractPromptFromAntigravityPayload(payload) {
32
+ const direct = payload.prompt || payload.userPrompt || payload.userMessage || payload.message;
33
+ if (direct) return textFromValue(direct);
34
+
35
+ const transcriptPath = payload.transcriptPath || payload.transcript_path;
36
+ if (!transcriptPath || !fs.existsSync(transcriptPath)) return "";
37
+
38
+ try {
39
+ const lines = fs.readFileSync(transcriptPath, "utf8").trim().split(/\r?\n/).filter(Boolean).slice(-200);
40
+ for (const line of lines.reverse()) {
41
+ let record;
42
+ try {
43
+ record = JSON.parse(line);
44
+ } catch {
45
+ continue;
46
+ }
47
+ if (!looksUserAuthored(record)) continue;
48
+ const text = textFromValue(record.prompt || record.userPrompt || record.userMessage || record.message || record.content || record.parts);
49
+ if (text.trim()) return text.trim();
50
+ }
51
+ } catch {
52
+ return "";
53
+ }
54
+
55
+ return "";
56
+ }
@@ -0,0 +1,53 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ function shellQuote(value) {
5
+ return `'${String(value).replaceAll("'", "'\\''")}'`;
6
+ }
7
+
8
+ function readJsonFile(filePath, fallback) {
9
+ if (!fs.existsSync(filePath)) return fallback;
10
+ const raw = fs.readFileSync(filePath, "utf8").trim();
11
+ if (!raw) return fallback;
12
+ return JSON.parse(raw);
13
+ }
14
+
15
+ function commandFor(installRoot, scriptName, { injectPromptContext = true } = {}) {
16
+ const envPrefix = scriptName === "on-antigravity-preinvocation.js" && !injectPromptContext ? "CONTEXTOS_INJECT=0 " : "";
17
+ return `${envPrefix}node ${shellQuote(path.join(installRoot, "plugins", "ctx", "bin", scriptName))}`;
18
+ }
19
+
20
+ export function antigravityHooksPath() {
21
+ return process.env.ANTIGRAVITY_HOOKS_PATH
22
+ || path.join(process.env.HOME || process.cwd(), ".gemini", "config", "hooks.json");
23
+ }
24
+
25
+ export function buildAntigravityHooksConfig(existingConfig, { installRoot, injectPromptContext = true } = {}) {
26
+ const config = existingConfig && typeof existingConfig === "object" ? structuredClone(existingConfig) : {};
27
+ config.contextos = {
28
+ enabled: true,
29
+ PreInvocation: [
30
+ {
31
+ type: "command",
32
+ command: commandFor(installRoot, "on-antigravity-preinvocation.js", { injectPromptContext }),
33
+ timeout: 10
34
+ }
35
+ ],
36
+ Stop: [
37
+ {
38
+ type: "command",
39
+ command: commandFor(installRoot, "on-antigravity-stop.js"),
40
+ timeout: 10
41
+ }
42
+ ]
43
+ };
44
+ return config;
45
+ }
46
+
47
+ export function installAntigravityHooks({ hooksPath = antigravityHooksPath(), installRoot, injectPromptContext = true } = {}) {
48
+ const existing = readJsonFile(hooksPath, {});
49
+ const next = buildAntigravityHooksConfig(existing, { installRoot, injectPromptContext });
50
+ fs.mkdirSync(path.dirname(hooksPath), { recursive: true });
51
+ fs.writeFileSync(hooksPath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
52
+ return hooksPath;
53
+ }
@@ -0,0 +1,43 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ function readJsonFile(filePath, fallback) {
5
+ if (!fs.existsSync(filePath)) return fallback;
6
+ const raw = fs.readFileSync(filePath, "utf8").trim();
7
+ if (!raw) return fallback;
8
+ return JSON.parse(raw);
9
+ }
10
+
11
+ export function antigravityMcpConfigPaths() {
12
+ if (process.env.ANTIGRAVITY_MCP_CONFIG_PATH) {
13
+ return [process.env.ANTIGRAVITY_MCP_CONFIG_PATH];
14
+ }
15
+ const home = process.env.HOME || process.cwd();
16
+ return [
17
+ path.join(home, ".gemini", "antigravity", "mcp_config.json"),
18
+ path.join(home, ".gemini", "antigravity-cli", "mcp_config.json"),
19
+ path.join(home, ".gemini", "config", "mcp_config.json")
20
+ ];
21
+ }
22
+
23
+ export function buildAntigravityMcpConfig(existingConfig, { installRoot } = {}) {
24
+ const config = existingConfig && typeof existingConfig === "object" ? structuredClone(existingConfig) : {};
25
+ if (!config.mcpServers || typeof config.mcpServers !== "object") config.mcpServers = {};
26
+ config.mcpServers["ctx-mcp"] = {
27
+ command: "node",
28
+ args: [path.join(installRoot, "plugins", "ctx", "mcp", "server.js")]
29
+ };
30
+ return config;
31
+ }
32
+
33
+ export function installAntigravityMcp({ configPaths = antigravityMcpConfigPaths(), installRoot } = {}) {
34
+ const written = [];
35
+ for (const configPath of configPaths) {
36
+ const existing = readJsonFile(configPath, {});
37
+ const next = buildAntigravityMcpConfig(existing, { installRoot });
38
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
39
+ fs.writeFileSync(configPath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
40
+ written.push(configPath);
41
+ }
42
+ return written;
43
+ }
@@ -0,0 +1,27 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import { buildGlobalHooksConfig } from "./global-hooks.js";
5
+
6
+ function readJsonFile(filePath, fallback) {
7
+ if (!fs.existsSync(filePath)) return fallback;
8
+ const raw = fs.readFileSync(filePath, "utf8").trim();
9
+ if (!raw) return fallback;
10
+ return JSON.parse(raw);
11
+ }
12
+
13
+ export function claudeHome() {
14
+ return process.env.CLAUDE_HOME || path.join(process.env.HOME || process.cwd(), ".claude");
15
+ }
16
+
17
+ export function installClaudeHooks({ claudeHome: home = claudeHome(), installRoot, injectPromptContext = true } = {}) {
18
+ const settingsPath = path.join(home, "settings.json");
19
+ const existing = readJsonFile(settingsPath, {});
20
+ const next = buildGlobalHooksConfig(existing, {
21
+ marketplaceRoot: installRoot,
22
+ injectPromptContext
23
+ });
24
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
25
+ fs.writeFileSync(settingsPath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
26
+ return settingsPath;
27
+ }
@@ -0,0 +1,33 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ function readJsonFile(filePath, fallback) {
5
+ if (!fs.existsSync(filePath)) return fallback;
6
+ const raw = fs.readFileSync(filePath, "utf8").trim();
7
+ if (!raw) return fallback;
8
+ return JSON.parse(raw);
9
+ }
10
+
11
+ export function claudeConfigPath() {
12
+ return process.env.CLAUDE_CONFIG_PATH || path.join(process.env.HOME || process.cwd(), ".claude.json");
13
+ }
14
+
15
+ export function buildClaudeMcpConfig(existingConfig, { installRoot } = {}) {
16
+ const config = existingConfig && typeof existingConfig === "object" ? structuredClone(existingConfig) : {};
17
+ if (!config.mcpServers || typeof config.mcpServers !== "object") config.mcpServers = {};
18
+ config.mcpServers["ctx-mcp"] = {
19
+ type: "stdio",
20
+ command: "node",
21
+ args: [path.join(installRoot, "plugins", "ctx", "mcp", "server.js")],
22
+ env: {}
23
+ };
24
+ return config;
25
+ }
26
+
27
+ export function installClaudeMcp({ configPath = claudeConfigPath(), installRoot } = {}) {
28
+ const existing = readJsonFile(configPath, {});
29
+ const next = buildClaudeMcpConfig(existing, { installRoot });
30
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
31
+ fs.writeFileSync(configPath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
32
+ return configPath;
33
+ }
@@ -57,6 +57,9 @@ export async function warmRuleEmbeddings({
57
57
  sources = [],
58
58
  allowRemote = true
59
59
  } = {}) {
60
+ if (!allowRemote && !isModelCacheReady(dataDir)) {
61
+ return { count: 0, cachePath: path.join(dataDir, "embeddings.db"), status: "missing-model" };
62
+ }
60
63
  const texts = [...new Set([
61
64
  task,
62
65
  ...rules.map((rule) => rule.content || "")
@@ -131,6 +134,16 @@ export function modelCacheDir(dataDir = defaultDataRoot()) {
131
134
  return path.join(dataDir, "models");
132
135
  }
133
136
 
137
+ export function isModelCacheReady(dataDir = defaultDataRoot()) {
138
+ const modelDir = path.join(modelCacheDir(dataDir), ...DEFAULT_MODEL.split("/"));
139
+ return [
140
+ "config.json",
141
+ "tokenizer.json",
142
+ "tokenizer_config.json",
143
+ path.join("onnx", "model_quantized.onnx")
144
+ ].every((relativePath) => fs.existsSync(path.join(modelDir, relativePath)));
145
+ }
146
+
134
147
  async function getCachedEmbedding({ cache, embedder, text, sources }) {
135
148
  const key = cacheKey(text, sources);
136
149
  const existing = cache.get(key);
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { enhanceRuleScoresWithEmbeddings, warmRuleEmbeddings } from "./embedding-scorer.js";
3
+ import { enhanceRuleScoresWithEmbeddings, isModelCacheReady, warmRuleEmbeddings } from "./embedding-scorer.js";
4
4
 
5
5
  const SOURCE_EXTENSIONS = new Set([
6
6
  ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".sql", ".md", ".json"
@@ -65,6 +65,7 @@ export async function warmFileEmbeddings({
65
65
  maxFiles = Number(process.env.CONTEXTOS_FILE_EMBEDDING_MAX_FILES || DEFAULT_MAX_FILES)
66
66
  } = {}) {
67
67
  if (!dataDir) return { count: 0, cachePath: null };
68
+ if (!allowRemote && !isModelCacheReady(dataDir)) return { count: 0, cachePath: null, status: "missing-model" };
68
69
  const files = listSourceFiles(cwd, { maxFiles });
69
70
  const rules = files.map((filePath) => ({ content: fileSearchText(filePath) }));
70
71
  return warmRuleEmbeddings({
@@ -15,6 +15,22 @@ export function writeJson(value) {
15
15
  process.stdout.write(`${JSON.stringify(value)}\n`);
16
16
  }
17
17
 
18
+ export function resolveHookCwd(payload = {}) {
19
+ return payload.cwd
20
+ || payload.working_directory
21
+ || payload.workspacePath
22
+ || payload.workspace_path
23
+ || payload.workspaceRoot
24
+ || payload.workspace_root
25
+ || payload.projectDir
26
+ || payload.project_dir
27
+ || payload.workspacePaths?.[0]
28
+ || payload.workspace_paths?.[0]
29
+ || process.env.CLAUDE_PROJECT_DIR
30
+ || process.env.PWD
31
+ || process.cwd();
32
+ }
33
+
18
34
  export function pluginDataDir(fileName = "", cwd = process.cwd()) {
19
35
  let root;
20
36
  try {
@@ -0,0 +1,36 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export function copyDir(src, dest) {
5
+ fs.mkdirSync(dest, { recursive: true });
6
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
7
+ const srcPath = path.join(src, entry.name);
8
+ const destPath = path.join(dest, entry.name);
9
+ if (entry.isDirectory()) {
10
+ copyDir(srcPath, destPath);
11
+ } else if (entry.isFile()) {
12
+ fs.copyFileSync(srcPath, destPath);
13
+ fs.chmodSync(destPath, fs.statSync(srcPath).mode);
14
+ }
15
+ }
16
+ }
17
+
18
+ export function copyPath(src, dest) {
19
+ const stat = fs.statSync(src);
20
+ if (stat.isDirectory()) {
21
+ copyDir(src, dest);
22
+ } else {
23
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
24
+ fs.copyFileSync(src, dest);
25
+ fs.chmodSync(dest, stat.mode);
26
+ }
27
+ }
28
+
29
+ export function copyPackageRoot({ rootDir, targetRoot }) {
30
+ fs.rmSync(targetRoot, { recursive: true, force: true });
31
+ for (const entry of [".agents", "bin", "plugins", "package.json", "package-lock.json", "README.md", "LICENSE", "node_modules"]) {
32
+ const src = path.join(rootDir, entry);
33
+ if (fs.existsSync(src)) copyPath(src, path.join(targetRoot, entry));
34
+ }
35
+ return targetRoot;
36
+ }
@@ -1,6 +1,7 @@
1
1
  import { scheduleContext } from "./scheduler.js";
2
2
  import { appendJsonLine, writeJsonFile } from "./fs-utils.js";
3
3
  import { callCtxScoreContext } from "./ctx-mcp-client.js";
4
+ import { resolveHookCwd } from "./hook-io.js";
4
5
  import path from "node:path";
5
6
 
6
7
  export async function handlePromptPayload(
@@ -16,7 +17,7 @@ export async function handlePromptPayload(
16
17
  } = {}
17
18
  ) {
18
19
  const prompt = payload.prompt || payload.message || payload.user_prompt || "";
19
- const cwd = payload.cwd || payload.working_directory || process.cwd();
20
+ const cwd = resolveHookCwd(payload);
20
21
  const openFiles = payload.openFiles || payload.open_files || payload.files || [];
21
22
  const dataDir = dataPath ? path.dirname(dataPath) : undefined;
22
23