@minhpnq1807/contextos 0.5.21 → 0.5.24
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/CHANGELOG.md +14 -0
- package/LAUNCH.md +4 -2
- package/README.md +3 -2
- package/bin/ctx.js +45 -15
- package/package.json +1 -1
- package/plugins/ctx/lib/antigravity-hooks.js +2 -1
- package/plugins/ctx/lib/antigravity-mcp.js +2 -1
- package/plugins/ctx/lib/claude-hooks.js +2 -1
- package/plugins/ctx/lib/claude-mcp.js +2 -1
- package/plugins/ctx/lib/gitignore.js +69 -0
- package/plugins/ctx/lib/multi-select.js +142 -0
- package/plugins/ctx/lib/ruler-sync.js +23 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.24
|
|
4
|
+
|
|
5
|
+
- **Interactive agent selection:** Replaces the comma-separated text input in `ctx setup` with an interactive multi-select prompt — use ↑/↓ to navigate, Space to toggle agents on/off, and Enter to confirm.
|
|
6
|
+
|
|
7
|
+
## 0.5.23
|
|
8
|
+
|
|
9
|
+
- **Fix Windows install paths:** Replaces all `process.env.HOME || process.cwd()` fallbacks with `os.homedir()` across `ctx.js`, `claude-hooks.js`, `antigravity-hooks.js`, `claude-mcp.js`, `antigravity-mcp.js`, and `ruler-sync.js`. On Windows, `HOME` is not set, causing `.codex/`, `.claude/`, and `.gemini/` directories (with full `node_modules` and source code) to be created inside the project tree instead of the user's home directory.
|
|
10
|
+
- **Fix ephemeral MCP server path:** `ctx sync --rules` now resolves the MCP server path from stable install roots (`~/.codex/marketplaces/contextos/`, `~/.ctx/contextos/agents/`) instead of `rootDir`, which may point to a temporary npm extraction directory (e.g. `/tmp/contextos/`) that disappears after cleanup.
|
|
11
|
+
|
|
12
|
+
## 0.5.22
|
|
13
|
+
|
|
14
|
+
- Adds `.gitignore` management to `ctx install`: writes inner `.gitignore` (excludes `node_modules/`, `bin/`, `lib/`, `mcp/`) inside installed agent directories and ensures the project root `.gitignore` excludes `.codex/marketplaces/contextos/`, `.claude/settings.json`, and `.gemini/`.
|
|
15
|
+
- Splits the `npm install -g && ctx setup` one-liner into two separate commands in README and LAUNCH docs to avoid shell PATH resolution failures.
|
|
16
|
+
|
|
3
17
|
## 0.5.21
|
|
4
18
|
|
|
5
19
|
- Makes prompt hooks fall back to direct scoring when the `ctx-mcp` bridge socket is missing, stale, or unavailable, avoiding empty `hook context` output.
|
package/LAUNCH.md
CHANGED
|
@@ -37,7 +37,8 @@ It supports Codex, Claude Code, and Antigravity. It is local-first and uses loca
|
|
|
37
37
|
|
|
38
38
|
Install:
|
|
39
39
|
|
|
40
|
-
npm install -g @minhpnq1807/contextos
|
|
40
|
+
npm install -g @minhpnq1807/contextos
|
|
41
|
+
ctx setup
|
|
41
42
|
|
|
42
43
|
Repo: https://github.com/khovan123/contextOS
|
|
43
44
|
```
|
|
@@ -51,7 +52,8 @@ Codex can read AGENTS.md and still ignore the rule that matters.
|
|
|
51
52
|
|
|
52
53
|
ContextOS ranks rules per prompt, injects the important ones before work starts, then reports followed / ignored / unknown after the task.
|
|
53
54
|
|
|
54
|
-
npm install -g @minhpnq1807/contextos
|
|
55
|
+
npm install -g @minhpnq1807/contextos
|
|
56
|
+
ctx setup
|
|
55
57
|
|
|
56
58
|
https://github.com/khovan123/contextOS
|
|
57
59
|
```
|
package/README.md
CHANGED
|
@@ -49,10 +49,11 @@ Rule outcomes: 8 followed, 0 ignored, 0 unknown
|
|
|
49
49
|
Runtime telemetry: code-review-graph, code-review-graph.query_graph_tool
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
## Install
|
|
52
|
+
## Quick Install
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
|
-
npm install -g @minhpnq1807/contextos
|
|
55
|
+
npm install -g @minhpnq1807/contextos
|
|
56
|
+
ctx setup
|
|
56
57
|
```
|
|
57
58
|
|
|
58
59
|
No postinstall surprise: `npm install` only installs the CLI. Setup runs only when you call `ctx setup`.
|
package/bin/ctx.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import readline from "node:readline/promises";
|
|
5
6
|
import { stdin as input, stdout as output } from "node:process";
|
|
@@ -24,10 +25,12 @@ import { installClaudeMcp } from "../plugins/ctx/lib/claude-mcp.js";
|
|
|
24
25
|
import { installAntigravityHooks } from "../plugins/ctx/lib/antigravity-hooks.js";
|
|
25
26
|
import { installAntigravityMcp } from "../plugins/ctx/lib/antigravity-mcp.js";
|
|
26
27
|
import { syncRules } from "../plugins/ctx/lib/ruler-sync.js";
|
|
28
|
+
import { writeInnerGitignore, ensureRootGitignore } from "../plugins/ctx/lib/gitignore.js";
|
|
27
29
|
import { syncSkills } from "../plugins/ctx/lib/skillshare-sync.js";
|
|
28
30
|
import { scanSkills, warmSkillEmbeddings } from "../plugins/ctx/lib/skill-discoverer.js";
|
|
29
31
|
import { parsePassthroughArgs, runPassthrough } from "../plugins/ctx/lib/passthrough.js";
|
|
30
32
|
import { parseAgentList, parseSetupArgs, setupSummaryLines } from "../plugins/ctx/lib/setup-wizard.js";
|
|
33
|
+
import { multiSelect } from "../plugins/ctx/lib/multi-select.js";
|
|
31
34
|
import { syncWorkflows, warmWorkflowEmbeddings } from "../plugins/ctx/lib/workflow-discoverer.js";
|
|
32
35
|
|
|
33
36
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -162,7 +165,7 @@ function packageVersion() {
|
|
|
162
165
|
}
|
|
163
166
|
|
|
164
167
|
function codexHome() {
|
|
165
|
-
return process.env.CODEX_HOME || path.join(
|
|
168
|
+
return process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
|
|
166
169
|
}
|
|
167
170
|
|
|
168
171
|
function copyInstall() {
|
|
@@ -194,6 +197,9 @@ async function install({ copy = false, inject = true, agent = "codex" } = {}) {
|
|
|
194
197
|
const hooksPath = installClaudeHooks({ installRoot, injectPromptContext: inject });
|
|
195
198
|
progress.step(40, "installing mcp");
|
|
196
199
|
const mcpConfigPath = installClaudeMcp({ installRoot });
|
|
200
|
+
progress.step(50, "configuring gitignore");
|
|
201
|
+
writeInnerGitignore(installRoot);
|
|
202
|
+
ensureRootGitignore(process.cwd());
|
|
197
203
|
progress.step(55, "warming embeddings");
|
|
198
204
|
const warmResult = await warmInstallEmbeddings();
|
|
199
205
|
progress.done("claude installed");
|
|
@@ -218,6 +224,9 @@ async function install({ copy = false, inject = true, agent = "codex" } = {}) {
|
|
|
218
224
|
const hooksPath = installAntigravityHooks({ installRoot, injectPromptContext: inject });
|
|
219
225
|
progress.step(40, "installing mcp");
|
|
220
226
|
const mcpConfigPaths = installAntigravityMcp({ installRoot });
|
|
227
|
+
progress.step(50, "configuring gitignore");
|
|
228
|
+
writeInnerGitignore(installRoot);
|
|
229
|
+
ensureRootGitignore(process.cwd());
|
|
221
230
|
progress.step(55, "warming embeddings");
|
|
222
231
|
const warmResult = await warmInstallEmbeddings();
|
|
223
232
|
progress.done("agy installed");
|
|
@@ -256,6 +265,10 @@ async function install({ copy = false, inject = true, agent = "codex" } = {}) {
|
|
|
256
265
|
progress.step(60, "installing hooks");
|
|
257
266
|
const hooksPath = installGlobalHooks({ codexHome: codexHome(), marketplaceRoot, injectPromptContext: inject });
|
|
258
267
|
|
|
268
|
+
progress.step(65, "configuring gitignore");
|
|
269
|
+
writeInnerGitignore(marketplaceRoot);
|
|
270
|
+
ensureRootGitignore(process.cwd());
|
|
271
|
+
|
|
259
272
|
progress.step(70, "warming embeddings");
|
|
260
273
|
const warmResult = await warmInstallEmbeddings();
|
|
261
274
|
progress.done("codex installed");
|
|
@@ -481,23 +494,40 @@ async function setup({ args = [], cwd = process.cwd() } = {}) {
|
|
|
481
494
|
|
|
482
495
|
if (interactive) {
|
|
483
496
|
const rl = readline.createInterface({ input, output });
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
497
|
+
const proceed = await askSetupYesNo(rl, "Install to this directory?", true);
|
|
498
|
+
if (!proceed) {
|
|
499
|
+
rl.close();
|
|
500
|
+
console.log("Setup cancelled.");
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (!options.agentsProvided) {
|
|
504
|
+
rl.close();
|
|
505
|
+
const selected = await multiSelect({
|
|
506
|
+
message: "Select agents to install:",
|
|
507
|
+
options: [
|
|
508
|
+
{ label: "Codex", value: "codex", selected: options.agents.includes("codex") },
|
|
509
|
+
{ label: "Claude", value: "claude", selected: options.agents.includes("claude") },
|
|
510
|
+
{ label: "Antigravity (agy)", value: "agy", selected: options.agents.includes("agy") }
|
|
511
|
+
]
|
|
512
|
+
});
|
|
513
|
+
options.agents = selected;
|
|
514
|
+
const rl2 = readline.createInterface({ input, output });
|
|
515
|
+
try {
|
|
516
|
+
options.inject = await askSetupYesNo(rl2, "Enable prompt context injection?", options.inject);
|
|
517
|
+
options.syncRules = await askSetupYesNo(rl2, "Sync project rules and MCP servers through Ruler?", options.syncRules);
|
|
518
|
+
options.syncSkills = await askSetupYesNo(rl2, "Sync skills through skillshare?", options.syncSkills);
|
|
519
|
+
} finally {
|
|
520
|
+
rl2.close();
|
|
489
521
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
options.agents = parseAgentList(agents);
|
|
493
|
-
} else {
|
|
522
|
+
} else {
|
|
523
|
+
try {
|
|
494
524
|
console.log(`◇ Install for agents:\n│ ${options.agents.join(", ")}`);
|
|
525
|
+
options.inject = await askSetupYesNo(rl, "Enable prompt context injection?", options.inject);
|
|
526
|
+
options.syncRules = await askSetupYesNo(rl, "Sync project rules and MCP servers through Ruler?", options.syncRules);
|
|
527
|
+
options.syncSkills = await askSetupYesNo(rl, "Sync skills through skillshare?", options.syncSkills);
|
|
528
|
+
} finally {
|
|
529
|
+
rl.close();
|
|
495
530
|
}
|
|
496
|
-
options.inject = await askSetupYesNo(rl, "Enable prompt context injection?", options.inject);
|
|
497
|
-
options.syncRules = await askSetupYesNo(rl, "Sync project rules and MCP servers through Ruler?", options.syncRules);
|
|
498
|
-
options.syncSkills = await askSetupYesNo(rl, "Sync skills through skillshare?", options.syncSkills);
|
|
499
|
-
} finally {
|
|
500
|
-
rl.close();
|
|
501
531
|
}
|
|
502
532
|
}
|
|
503
533
|
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
|
|
4
5
|
function shellQuote(value) {
|
|
@@ -19,7 +20,7 @@ function commandFor(installRoot, scriptName, { injectPromptContext = true } = {}
|
|
|
19
20
|
|
|
20
21
|
export function antigravityHooksPath() {
|
|
21
22
|
return process.env.ANTIGRAVITY_HOOKS_PATH
|
|
22
|
-
|| path.join(
|
|
23
|
+
|| path.join(os.homedir(), ".gemini", "config", "hooks.json");
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export function buildAntigravityHooksConfig(existingConfig, { installRoot, injectPromptContext = true } = {}) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
|
|
4
5
|
function readJsonFile(filePath, fallback) {
|
|
@@ -12,7 +13,7 @@ export function antigravityMcpConfigPaths() {
|
|
|
12
13
|
if (process.env.ANTIGRAVITY_MCP_CONFIG_PATH) {
|
|
13
14
|
return [process.env.ANTIGRAVITY_MCP_CONFIG_PATH];
|
|
14
15
|
}
|
|
15
|
-
const home =
|
|
16
|
+
const home = os.homedir();
|
|
16
17
|
return [
|
|
17
18
|
path.join(home, ".gemini", "antigravity", "mcp_config.json"),
|
|
18
19
|
path.join(home, ".gemini", "antigravity-cli", "mcp_config.json"),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
|
|
4
5
|
import { buildGlobalHooksConfig } from "./global-hooks.js";
|
|
@@ -11,7 +12,7 @@ function readJsonFile(filePath, fallback) {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function claudeHome() {
|
|
14
|
-
return process.env.CLAUDE_HOME || path.join(
|
|
15
|
+
return process.env.CLAUDE_HOME || path.join(os.homedir(), ".claude");
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export function installClaudeHooks({ claudeHome: home = claudeHome(), installRoot, injectPromptContext = true } = {}) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
|
|
4
5
|
function readJsonFile(filePath, fallback) {
|
|
@@ -9,7 +10,7 @@ function readJsonFile(filePath, fallback) {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export function claudeConfigPath() {
|
|
12
|
-
return process.env.CLAUDE_CONFIG_PATH || path.join(
|
|
13
|
+
return process.env.CLAUDE_CONFIG_PATH || path.join(os.homedir(), ".claude.json");
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export function buildClaudeMcpConfig(existingConfig, { installRoot } = {}) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Entries to exclude inside the installed contextos directory.
|
|
6
|
+
* Keeps node_modules, bin, lib, and mcp out of version control.
|
|
7
|
+
*/
|
|
8
|
+
const INNER_GITIGNORE_ENTRIES = [
|
|
9
|
+
"node_modules/",
|
|
10
|
+
"bin/",
|
|
11
|
+
"lib/",
|
|
12
|
+
"mcp/",
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Entries that ctx install should add to the project root .gitignore.
|
|
17
|
+
*
|
|
18
|
+
* .codex/marketplaces/contextos/ — Codex agent install dir
|
|
19
|
+
* .claude/settings.json — Claude hooks written by ctx install
|
|
20
|
+
* .gemini/ — Antigravity hooks/config
|
|
21
|
+
*/
|
|
22
|
+
const ROOT_GITIGNORE_ENTRIES = [
|
|
23
|
+
".codex/marketplaces/contextos/",
|
|
24
|
+
".claude/settings.json",
|
|
25
|
+
".gemini/",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Write a .gitignore inside `dir` that excludes build/runtime artefacts.
|
|
30
|
+
* Creates the directory if it does not exist yet.
|
|
31
|
+
*/
|
|
32
|
+
export function writeInnerGitignore(dir) {
|
|
33
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
34
|
+
const gitignorePath = path.join(dir, ".gitignore");
|
|
35
|
+
const content = INNER_GITIGNORE_ENTRIES.join("\n") + "\n";
|
|
36
|
+
fs.writeFileSync(gitignorePath, content, "utf8");
|
|
37
|
+
return gitignorePath;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Ensure the project root .gitignore exists and contains the entries
|
|
42
|
+
* needed to keep ctx install artefacts out of version control.
|
|
43
|
+
*
|
|
44
|
+
* Only appends entries that are not already present.
|
|
45
|
+
* Creates the file if it does not exist.
|
|
46
|
+
*/
|
|
47
|
+
export function ensureRootGitignore(projectRoot) {
|
|
48
|
+
const gitignorePath = path.join(projectRoot, ".gitignore");
|
|
49
|
+
let existing = "";
|
|
50
|
+
if (fs.existsSync(gitignorePath)) {
|
|
51
|
+
existing = fs.readFileSync(gitignorePath, "utf8");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const lines = existing.split("\n");
|
|
55
|
+
const missing = ROOT_GITIGNORE_ENTRIES.filter(
|
|
56
|
+
(entry) => !lines.some((line) => line.trim() === entry)
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (missing.length === 0) return gitignorePath;
|
|
60
|
+
|
|
61
|
+
const block = [
|
|
62
|
+
"",
|
|
63
|
+
"# ContextOS install artefacts",
|
|
64
|
+
...missing,
|
|
65
|
+
].join("\n") + "\n";
|
|
66
|
+
|
|
67
|
+
fs.writeFileSync(gitignorePath, existing.trimEnd() + "\n" + block, "utf8");
|
|
68
|
+
return gitignorePath;
|
|
69
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive multi-select prompt using raw stdin.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* const selected = await multiSelect({
|
|
6
|
+
* message: "Select agents:",
|
|
7
|
+
* options: [
|
|
8
|
+
* { label: "Codex", value: "codex", selected: true },
|
|
9
|
+
* { label: "Claude", value: "claude", selected: true },
|
|
10
|
+
* { label: "Antigravity (agy)", value: "agy", selected: true }
|
|
11
|
+
* ]
|
|
12
|
+
* });
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const KEYS = {
|
|
16
|
+
UP: ["\x1B[A", "\x1Bk"], // Arrow Up, Alt+k
|
|
17
|
+
DOWN: ["\x1B[B", "\x1Bj"], // Arrow Down, Alt+j
|
|
18
|
+
SPACE: [" "],
|
|
19
|
+
ENTER: ["\r", "\n"],
|
|
20
|
+
CTRL_C: ["\x03"],
|
|
21
|
+
// j/k vim-style (single char)
|
|
22
|
+
J: ["j"],
|
|
23
|
+
K: ["k"]
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const DIM = "\x1B[2m";
|
|
27
|
+
const RESET = "\x1B[0m";
|
|
28
|
+
const CYAN = "\x1B[36m";
|
|
29
|
+
const GREEN = "\x1B[32m";
|
|
30
|
+
const BOLD = "\x1B[1m";
|
|
31
|
+
const HIDE_CURSOR = "\x1B[?25l";
|
|
32
|
+
const SHOW_CURSOR = "\x1B[?25h";
|
|
33
|
+
|
|
34
|
+
function matchKey(data, keySet) {
|
|
35
|
+
return keySet.some((k) => data === k);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {{ message: string, options: Array<{label: string, value: string, selected?: boolean}> }} config
|
|
40
|
+
* @returns {Promise<string[]>} selected values
|
|
41
|
+
*/
|
|
42
|
+
export function multiSelect({ message, options }) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
if (!process.stdin.isTTY) {
|
|
45
|
+
// Non-interactive: return all pre-selected
|
|
46
|
+
resolve(options.filter((o) => o.selected).map((o) => o.value));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let cursor = 0;
|
|
51
|
+
const selections = options.map((o) => o.selected !== false);
|
|
52
|
+
|
|
53
|
+
function render() {
|
|
54
|
+
// Move cursor up to overwrite previous render (except first time)
|
|
55
|
+
const lines = [];
|
|
56
|
+
lines.push(`${CYAN}◇${RESET} ${message}`);
|
|
57
|
+
lines.push(`${DIM} Use ↑/↓ to navigate, Space to toggle, Enter to confirm${RESET}`);
|
|
58
|
+
for (let i = 0; i < options.length; i++) {
|
|
59
|
+
const isCursor = i === cursor;
|
|
60
|
+
const isSelected = selections[i];
|
|
61
|
+
const checkbox = isSelected ? `${GREEN}◉${RESET}` : `${DIM}○${RESET}`;
|
|
62
|
+
const label = isCursor ? `${BOLD}${CYAN}${options[i].label}${RESET}` : options[i].label;
|
|
63
|
+
const pointer = isCursor ? `${CYAN}❯${RESET}` : " ";
|
|
64
|
+
lines.push(` ${pointer} ${checkbox} ${label}`);
|
|
65
|
+
}
|
|
66
|
+
return lines;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let prevLineCount = 0;
|
|
70
|
+
|
|
71
|
+
function draw() {
|
|
72
|
+
const lines = render();
|
|
73
|
+
// Clear previous output
|
|
74
|
+
if (prevLineCount > 0) {
|
|
75
|
+
process.stdout.write(`\x1B[${prevLineCount}A`); // move up
|
|
76
|
+
for (let i = 0; i < prevLineCount; i++) {
|
|
77
|
+
process.stdout.write("\x1B[2K"); // clear line
|
|
78
|
+
if (i < prevLineCount - 1) process.stdout.write("\x1B[1B"); // move down
|
|
79
|
+
}
|
|
80
|
+
process.stdout.write(`\x1B[${prevLineCount - 1}A`); // back to top
|
|
81
|
+
}
|
|
82
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
83
|
+
prevLineCount = lines.length;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
process.stdout.write(HIDE_CURSOR);
|
|
87
|
+
|
|
88
|
+
const wasRaw = process.stdin.isRaw;
|
|
89
|
+
process.stdin.setRawMode(true);
|
|
90
|
+
process.stdin.resume();
|
|
91
|
+
process.stdin.setEncoding("utf8");
|
|
92
|
+
|
|
93
|
+
function cleanup() {
|
|
94
|
+
process.stdin.setRawMode(wasRaw || false);
|
|
95
|
+
process.stdin.pause();
|
|
96
|
+
process.stdin.removeListener("data", onData);
|
|
97
|
+
process.stdout.write(SHOW_CURSOR);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function onData(data) {
|
|
101
|
+
if (matchKey(data, KEYS.CTRL_C)) {
|
|
102
|
+
cleanup();
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (matchKey(data, KEYS.UP) || matchKey(data, KEYS.K)) {
|
|
107
|
+
cursor = (cursor - 1 + options.length) % options.length;
|
|
108
|
+
draw();
|
|
109
|
+
} else if (matchKey(data, KEYS.DOWN) || matchKey(data, KEYS.J)) {
|
|
110
|
+
cursor = (cursor + 1) % options.length;
|
|
111
|
+
draw();
|
|
112
|
+
} else if (matchKey(data, KEYS.SPACE)) {
|
|
113
|
+
selections[cursor] = !selections[cursor];
|
|
114
|
+
draw();
|
|
115
|
+
} else if (matchKey(data, KEYS.ENTER)) {
|
|
116
|
+
cleanup();
|
|
117
|
+
const selected = options
|
|
118
|
+
.filter((_, i) => selections[i])
|
|
119
|
+
.map((o) => o.value);
|
|
120
|
+
// Print final summary
|
|
121
|
+
const summary = selected.length > 0
|
|
122
|
+
? selected.join(", ")
|
|
123
|
+
: "(none)";
|
|
124
|
+
// Overwrite prompt area with final state
|
|
125
|
+
if (prevLineCount > 0) {
|
|
126
|
+
process.stdout.write(`\x1B[${prevLineCount}A`);
|
|
127
|
+
for (let i = 0; i < prevLineCount; i++) {
|
|
128
|
+
process.stdout.write("\x1B[2K");
|
|
129
|
+
if (i < prevLineCount - 1) process.stdout.write("\x1B[1B");
|
|
130
|
+
}
|
|
131
|
+
process.stdout.write(`\x1B[${prevLineCount - 1}A`);
|
|
132
|
+
}
|
|
133
|
+
process.stdout.write(`${CYAN}◇${RESET} ${message}\n`);
|
|
134
|
+
process.stdout.write(`${DIM}│${RESET} ${GREEN}${summary}${RESET}\n`);
|
|
135
|
+
resolve(selected);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
process.stdin.on("data", onData);
|
|
140
|
+
draw();
|
|
141
|
+
});
|
|
142
|
+
}
|
|
@@ -5,9 +5,12 @@ import readline from "node:readline/promises";
|
|
|
5
5
|
import { stdin as input, stdout as output } from "node:process";
|
|
6
6
|
import { execFileSync } from "node:child_process";
|
|
7
7
|
|
|
8
|
+
import { defaultDataRoot } from "./workspace-data.js";
|
|
9
|
+
|
|
8
10
|
const DEFAULT_AGENTS = ["codex", "claude", "antigravity"];
|
|
9
11
|
const CTX_MCP_NAME = "ctx-mcp";
|
|
10
12
|
const CONTEXTOS_PROXY_MARKER = "/contextos/plugins/ctx/mcp/proxy.js";
|
|
13
|
+
const MCP_SERVER_RELATIVE = path.join("plugins", "ctx", "mcp", "server.js");
|
|
11
14
|
const AGENT_ALIASES = new Map([
|
|
12
15
|
["agy", "antigravity"],
|
|
13
16
|
["antigravity", "antigravity"],
|
|
@@ -54,11 +57,11 @@ function displayAgentName(agent) {
|
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
function codexConfigPath() {
|
|
57
|
-
return path.join(process.env.CODEX_HOME || path.join(
|
|
60
|
+
return path.join(process.env.CODEX_HOME || path.join(os.homedir(), ".codex"), "config.toml");
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
function claudeUserConfigPath() {
|
|
61
|
-
return process.env.CLAUDE_CONFIG_PATH || path.join(
|
|
64
|
+
return process.env.CLAUDE_CONFIG_PATH || path.join(os.homedir(), ".claude.json");
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
export function rulerTomlPath(cwd = process.cwd()) {
|
|
@@ -273,7 +276,7 @@ function readRulerMcpServer({ tomlPath, name } = {}) {
|
|
|
273
276
|
}
|
|
274
277
|
|
|
275
278
|
function antigravityMcpConfigPaths() {
|
|
276
|
-
const home =
|
|
279
|
+
const home = os.homedir();
|
|
277
280
|
return [
|
|
278
281
|
path.join(home, ".gemini", "antigravity", "mcp_config.json"),
|
|
279
282
|
path.join(home, ".gemini", "antigravity-cli", "mcp_config.json"),
|
|
@@ -456,7 +459,7 @@ export function verifySync({ cwd = process.cwd(), agents = DEFAULT_AGENTS } = {}
|
|
|
456
459
|
const checks = [];
|
|
457
460
|
const definitions = {
|
|
458
461
|
codex: [path.join(cwd, ".codex", "config.toml")],
|
|
459
|
-
claude: [path.join(cwd, ".mcp.json"), path.join(cwd, ".claude", "settings.json"), path.join(
|
|
462
|
+
claude: [path.join(cwd, ".mcp.json"), path.join(cwd, ".claude", "settings.json"), path.join(os.homedir(), ".claude.json")],
|
|
460
463
|
antigravity: [
|
|
461
464
|
path.join(cwd, ".gemini", "settings.json"),
|
|
462
465
|
path.join(cwd, ".gemini", "mcp.json"),
|
|
@@ -473,6 +476,21 @@ export function verifySync({ cwd = process.cwd(), agents = DEFAULT_AGENTS } = {}
|
|
|
473
476
|
return checks;
|
|
474
477
|
}
|
|
475
478
|
|
|
479
|
+
function resolveStableMcpServerPath(rootDir) {
|
|
480
|
+
const codexRoot = path.join(process.env.CODEX_HOME || path.join(os.homedir(), ".codex"), "marketplaces", "contextos");
|
|
481
|
+
const dataRoot = defaultDataRoot();
|
|
482
|
+
const candidates = [
|
|
483
|
+
path.join(codexRoot, MCP_SERVER_RELATIVE),
|
|
484
|
+
path.join(dataRoot, "agents", "claude", "contextos", MCP_SERVER_RELATIVE),
|
|
485
|
+
path.join(dataRoot, "agents", "agy", "contextos", MCP_SERVER_RELATIVE),
|
|
486
|
+
path.join(rootDir, MCP_SERVER_RELATIVE)
|
|
487
|
+
];
|
|
488
|
+
for (const candidate of candidates) {
|
|
489
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
490
|
+
}
|
|
491
|
+
return path.join(rootDir, MCP_SERVER_RELATIVE);
|
|
492
|
+
}
|
|
493
|
+
|
|
476
494
|
export async function syncRules({
|
|
477
495
|
cwd = process.cwd(),
|
|
478
496
|
rootDir,
|
|
@@ -495,7 +513,7 @@ export async function syncRules({
|
|
|
495
513
|
const init = ensureRulerInit({ cwd, run, dryRun: options.dryRun });
|
|
496
514
|
logger(statusLine("Checking .ruler/ruler.toml...", init.created ? "✓ created" : "✓ found"));
|
|
497
515
|
|
|
498
|
-
const mcpServerPath =
|
|
516
|
+
const mcpServerPath = resolveStableMcpServerPath(rootDir);
|
|
499
517
|
const injected = injectCtxMcp({
|
|
500
518
|
tomlPath: init.tomlPath,
|
|
501
519
|
mcpServerPath,
|