@minhpnq1807/contextos 0.5.36 → 0.5.37

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.37
4
+
5
+ - **Real-time animated progress bar for `ctx install`:** The progress spinner now updates in-place using raw stderr writes (`\r`) instead of being captured by `streamSetupOutput`. Uses a smooth 10-frame Braille spinner (`⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏`) with a visual bar (`[████████░░░░]`) that animates at 80ms intervals.
6
+ - **Clean install output:** Reduced verbose per-agent install summary from 10+ lines of paths to a compact 4-line summary (Hooks →, MCP →, Embeddings count, restart instruction). Removed redundant "embedding model already cached" log line from `warmInstallEmbeddings`.
7
+ - **Fix `streamSetupOutput` breaking spinner:** Previously intercepted `process.stderr.write` and converted `\r` carriage returns to newlines, preventing in-place updates. Now only intercepts `console.log` for `│ ` prefixed output, leaving stderr untouched for spinner rendering.
8
+
3
9
  ## 0.5.36
4
10
 
5
11
  - **Fix ctx_score_context MCP output not rendering in Antigravity editor:** The `content` text block returned by the MCP tool previously only contained raw telemetry JSON, which editors cannot render as user-facing context. Now the tool uses `scheduleContext()` to produce the same human-readable markdown (Critical ContextOS rules, Suggested files, Skills, Workflows) that the hook path generates, and returns it as the primary `content[0].text` block. Telemetry JSON is pushed to a secondary content block. This ensures Antigravity (and any MCP-compatible editor) displays the scored rules and file suggestions.
package/bin/ctx.js CHANGED
@@ -102,45 +102,42 @@ function normalizeInstallAgent(agent) {
102
102
  return normalized;
103
103
  }
104
104
  /**
105
- * Intercept console.log and process.stderr.write from an async fn,
105
+ * Intercept console.log from an async fn,
106
106
  * printing each line immediately with "│ " prefix for real-time feedback.
107
+ * stderr is left untouched so \r-based spinner writes render in-place.
107
108
  * Returns the collected lines array (for callers that inspect it).
108
109
  */
109
110
  async function streamSetupOutput(fn) {
110
111
  const lines = [];
111
112
  const origLog = console.log;
112
- const origStderrWrite = process.stderr.write;
113
113
  const emit = (text) => {
114
114
  lines.push(text);
115
115
  origLog(`│ ${text}`);
116
116
  };
117
117
  console.log = (...args) => emit(args.map(String).join(" "));
118
- process.stderr.write = (chunk) => {
119
- const text = String(chunk).replace(/\r/g, "").trim();
120
- if (text) emit(text);
121
- return true;
122
- };
123
118
  try {
124
119
  await fn();
125
120
  } finally {
126
121
  console.log = origLog;
127
- process.stderr.write = origStderrWrite;
128
122
  }
129
123
  return lines;
130
124
  }
131
125
 
132
126
  function createInstallProgress({ quiet = false } = {}) {
133
- const enabled = !quiet && process.stderr.isTTY;
134
- const frames = ["-", "\\", "|", "/"];
127
+ const isTTY = !quiet && process.stderr.isTTY;
128
+ const frames = ["", "", "", "", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
135
129
  let percent = 0;
136
130
  let label = "starting";
137
131
  let frame = 0;
138
132
  let timer = null;
133
+ // Use the raw stderr binding so streamSetupOutput cannot intercept spinner writes.
134
+ const rawStderrWrite = process.stderr.write.bind(process.stderr);
139
135
 
140
136
  function render() {
141
- if (!enabled) return;
142
- const text = `[ctx] install ${String(percent).padStart(3)}% ${frames[frame % frames.length]} ${label}`;
143
- process.stderr.write(`\r${text.padEnd(92)}`);
137
+ if (!isTTY) return;
138
+ const bar = progressBar(percent);
139
+ const text = ` ${frames[frame % frames.length]} ${bar} ${label}`;
140
+ rawStderrWrite(`\r${text.padEnd(72)}`);
144
141
  frame += 1;
145
142
  }
146
143
 
@@ -148,43 +145,48 @@ function createInstallProgress({ quiet = false } = {}) {
148
145
  start(initialLabel = "starting") {
149
146
  label = initialLabel;
150
147
  percent = 0;
151
- if (enabled) {
148
+ if (isTTY) {
152
149
  render();
153
- timer = setInterval(render, 120);
150
+ timer = setInterval(render, 80);
154
151
  } else if (!quiet) {
155
- console.log(`[ctx] install 0% ${label}`);
152
+ console.log(`[ctx] ${label}...`);
156
153
  }
157
154
  },
158
155
  step(nextPercent, nextLabel) {
159
156
  percent = Math.max(percent, Math.min(100, nextPercent));
160
157
  label = nextLabel;
161
- if (enabled) render();
162
- else if (!quiet) console.log(`[ctx] install ${percent}% ${label}`);
158
+ if (isTTY) render();
163
159
  },
164
160
  done(finalLabel = "done") {
165
161
  percent = 100;
166
162
  label = finalLabel;
167
163
  if (timer) clearInterval(timer);
168
164
  timer = null;
169
- if (enabled) {
170
- render();
171
- process.stderr.write("\n");
165
+ if (isTTY) {
166
+ const bar = progressBar(100);
167
+ rawStderrWrite(`\r ✓ ${bar} ${label}`.padEnd(72) + "\n");
172
168
  } else if (!quiet) {
173
- console.log(`[ctx] install 100% ${label}`);
169
+ console.log(`[ctx] ${label}`);
174
170
  }
175
171
  },
176
172
  fail(errorLabel = "failed") {
177
173
  label = errorLabel;
178
174
  if (timer) clearInterval(timer);
179
175
  timer = null;
180
- if (enabled) {
181
- render();
182
- process.stderr.write("\n");
176
+ if (isTTY) {
177
+ rawStderrWrite(`\r ✗ ${errorLabel}`.padEnd(72) + "\n");
183
178
  }
184
179
  }
185
180
  };
186
181
  }
187
182
 
183
+ function progressBar(percent) {
184
+ const width = 20;
185
+ const filled = Math.round(width * percent / 100);
186
+ const empty = width - filled;
187
+ return `[${'█'.repeat(filled)}${'░'.repeat(empty)}] ${String(percent).padStart(3)}%`;
188
+ }
189
+
188
190
  function packageVersion() {
189
191
  try {
190
192
  const packageJson = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
@@ -224,81 +226,60 @@ async function install({ copy = false, agent = "codex" } = {}) {
224
226
  if (agent === "claude") {
225
227
  progress.step(10, "copying package");
226
228
  const installRoot = copyPackageRoot({ rootDir, targetRoot: agentInstallRoot("claude") });
227
- progress.step(25, "installing hooks");
229
+ progress.step(30, "installing hooks");
228
230
  const hooksPath = installClaudeHooks({ installRoot, injectPromptContext: inject });
229
- progress.step(40, "installing mcp");
231
+ progress.step(50, "installing mcp");
230
232
  const mcpConfigPath = installClaudeMcp({ installRoot });
231
- progress.step(50, "configuring gitignore");
233
+ progress.step(60, "configuring gitignore");
232
234
  writeInnerGitignore(installRoot);
233
235
  ensureRootGitignore(process.cwd());
234
- progress.step(55, "warming embeddings");
236
+ progress.step(70, "warming embeddings");
235
237
  const warmResult = await warmInstallEmbeddings();
236
- progress.done("claude installed");
237
- console.log("Installed ctx hooks for Claude Code.");
238
- console.log(`Stable install root: ${installRoot}`);
239
- console.log(`Installed ContextOS hooks to ${hooksPath}`);
240
- console.log(`Installed ctx-mcp MCP server to ${mcpConfigPath}`);
241
- console.log(`Embedding model cache: ${modelCacheDir(contextOSDataDir())}`);
242
- console.log(`Embedding vectors cache: ${warmResult.cachePath}`);
243
- console.log(`File path embeddings warmed: ${warmResult.fileCount || 0}`);
244
- console.log(`Skill embeddings warmed: ${warmResult.skillCount || 0}`);
245
- console.log(`Workflow embeddings warmed: ${warmResult.workflowCount || 0}`);
246
- console.log(`Prompt context injection: ${inject ? "enabled" : "quiet logging only"}`);
247
- console.log("Restart Claude Code if it was already running, then submit a task to trigger ContextOS.");
238
+ progress.done("claude ");
239
+ console.log(`Hooks ${hooksPath}`);
240
+ console.log(`MCP → ${mcpConfigPath}`);
241
+ console.log(`Embeddings: ${warmResult.fileCount || 0} files, ${warmResult.skillCount || 0} skills`);
242
+ console.log("Restart Claude Code to activate ContextOS.");
248
243
  return;
249
244
  }
250
245
 
251
246
  if (agent === "agy") {
252
247
  progress.step(10, "copying package");
253
248
  const installRoot = copyPackageRoot({ rootDir, targetRoot: agentInstallRoot("agy") });
254
- progress.step(25, "installing hooks");
249
+ progress.step(30, "installing hooks");
255
250
  const hooksPath = installAntigravityHooks({ installRoot, injectPromptContext: inject });
256
- progress.step(40, "installing mcp");
251
+ progress.step(50, "installing mcp");
257
252
  const mcpConfigPaths = installAntigravityMcp({ installRoot });
258
- progress.step(50, "configuring gitignore");
253
+ progress.step(60, "configuring gitignore");
259
254
  writeInnerGitignore(installRoot);
260
255
  ensureRootGitignore(process.cwd());
261
- progress.step(55, "warming embeddings");
256
+ progress.step(70, "warming embeddings");
262
257
  const warmResult = await warmInstallEmbeddings();
263
- progress.done("agy installed");
264
- console.log("Installed ctx hooks for Antigravity.");
265
- console.log(`Stable install root: ${installRoot}`);
266
- console.log(`Installed ContextOS hooks to ${hooksPath}`);
267
- console.log(`Installed ctx-mcp MCP server to ${mcpConfigPaths.join(", ")}`);
268
- console.log(`Embedding model cache: ${modelCacheDir(contextOSDataDir())}`);
269
- console.log(`Embedding vectors cache: ${warmResult.cachePath}`);
270
- console.log(`File path embeddings warmed: ${warmResult.fileCount || 0}`);
271
- console.log(`Skill embeddings warmed: ${warmResult.skillCount || 0}`);
272
- console.log(`Workflow embeddings warmed: ${warmResult.workflowCount || 0}`);
273
- console.log(`Prompt context injection: ${inject ? "enabled" : "quiet logging only"}`);
274
- console.log("Restart Antigravity or agy if it was already running, then submit a task to trigger ContextOS.");
258
+ progress.done("antigravity ");
259
+ console.log(`Hooks ${hooksPath}`);
260
+ console.log(`MCP → ${mcpConfigPaths.join(", ")}`);
261
+ console.log(`Embeddings: ${warmResult.fileCount || 0} files, ${warmResult.skillCount || 0} skills`);
262
+ console.log("Restart Antigravity to activate ContextOS.");
275
263
  return;
276
264
  }
277
265
 
278
266
  if (agent === "copilot") {
279
267
  progress.step(10, "copying package");
280
268
  const installRoot = copyPackageRoot({ rootDir, targetRoot: agentInstallRoot("copilot") });
281
- progress.step(25, "installing hooks");
269
+ progress.step(30, "installing hooks");
282
270
  const hooksPath = installCopilotHooks({ cwd: process.cwd(), installRoot });
283
- progress.step(40, "installing mcp");
271
+ progress.step(50, "installing mcp");
284
272
  const mcpConfigPath = installCopilotMcp({ cwd: process.cwd(), installRoot });
285
- progress.step(50, "configuring gitignore");
273
+ progress.step(60, "configuring gitignore");
286
274
  writeInnerGitignore(installRoot);
287
275
  ensureRootGitignore(process.cwd());
288
- progress.step(55, "warming embeddings");
276
+ progress.step(70, "warming embeddings");
289
277
  const warmResult = await warmInstallEmbeddings();
290
- progress.done("copilot installed");
291
- console.log("Installed ctx hooks for GitHub Copilot.");
292
- console.log(`Stable install root: ${installRoot}`);
293
- console.log(`Installed ContextOS instructions to ${hooksPath}`);
294
- console.log(`Installed ctx-mcp MCP server to ${mcpConfigPath}`);
295
- console.log(`Embedding model cache: ${modelCacheDir(contextOSDataDir())}`);
296
- console.log(`Embedding vectors cache: ${warmResult.cachePath}`);
297
- console.log(`File path embeddings warmed: ${warmResult.fileCount || 0}`);
298
- console.log(`Skill embeddings warmed: ${warmResult.skillCount || 0}`);
299
- console.log(`Workflow embeddings warmed: ${warmResult.workflowCount || 0}`);
300
- console.log(`Prompt context injection: ${inject ? "enabled" : "quiet logging only"}`);
301
- console.log("Restart VS Code or Copilot if it was already running, then submit a task to trigger ContextOS.");
278
+ progress.done("copilot ");
279
+ console.log(`Instructions ${hooksPath}`);
280
+ console.log(`MCP → ${mcpConfigPath}`);
281
+ console.log(`Embeddings: ${warmResult.fileCount || 0} files, ${warmResult.skillCount || 0} skills`);
282
+ console.log("Restart VS Code to activate ContextOS.");
302
283
  return;
303
284
  }
304
285
 
@@ -310,38 +291,31 @@ async function install({ copy = false, agent = "codex" } = {}) {
310
291
  const marketplaceRoot = path.join(codexHome(), "marketplaces", "contextos");
311
292
  copyPackageRoot({ rootDir, targetRoot: marketplaceRoot });
312
293
 
313
- progress.step(20, "refreshing codex plugin");
294
+ progress.step(25, "refreshing codex plugin");
314
295
  tryRunCodex(["plugin", "remove", "ctx@contextos"]);
315
296
  tryRunCodex(["plugin", "marketplace", "remove", "contextos"]);
316
297
  tryRunCodex(["mcp", "remove", "ctx-mcp"]);
317
298
  runCodex(["plugin", "marketplace", "add", marketplaceRoot]);
318
299
  runCodex(["plugin", "add", "ctx@contextos"]);
319
- progress.step(40, "installing mcp");
300
+ progress.step(45, "installing mcp");
320
301
  runCodex(["mcp", "add", "ctx-mcp", "--", "node", path.join(marketplaceRoot, "plugins", "ctx", "mcp", "server.js")]);
321
- progress.step(50, "installing telemetry proxies");
302
+ progress.step(55, "installing telemetry proxies");
322
303
  const proxyResult = installMcpTelemetryProxies({ codexHome: codexHome(), marketplaceRoot });
323
- progress.step(60, "installing hooks");
304
+ progress.step(65, "installing hooks");
324
305
  const hooksPath = installGlobalHooks({ codexHome: codexHome(), marketplaceRoot, injectPromptContext: inject });
325
306
 
326
- progress.step(65, "configuring gitignore");
307
+ progress.step(70, "configuring gitignore");
327
308
  writeInnerGitignore(marketplaceRoot);
328
309
  ensureRootGitignore(process.cwd());
329
310
 
330
- progress.step(70, "warming embeddings");
311
+ progress.step(80, "warming embeddings");
331
312
  const warmResult = await warmInstallEmbeddings();
332
- progress.done("codex installed");
333
- console.log("Installed ctx through Codex plugin marketplace.");
334
- console.log(`Stable marketplace root: ${marketplaceRoot}`);
335
- console.log(`Installed ContextOS global hooks to ${hooksPath}`);
336
- console.log("Installed ctx-mcp MCP server.");
337
- console.log(`MCP telemetry proxies: ${proxyResult.wrapped.length ? proxyResult.wrapped.map((item) => item.name).join(", ") : "none changed"}`);
338
- console.log(`Embedding model cache: ${modelCacheDir(contextOSDataDir())}`);
339
- console.log(`Embedding vectors cache: ${warmResult.cachePath}`);
340
- console.log(`File path embeddings warmed: ${warmResult.fileCount || 0}`);
341
- console.log(`Skill embeddings warmed: ${warmResult.skillCount || 0}`);
342
- console.log(`Workflow embeddings warmed: ${warmResult.workflowCount || 0}`);
343
- console.log(`Prompt context injection: ${inject ? "enabled" : "quiet logging only"}`);
344
- console.log("Restart Codex if it was already running, then submit a task to trigger ContextOS.");
313
+ progress.done("codex ");
314
+ console.log(`Hooks → ${hooksPath}`);
315
+ console.log(`MCP → ctx-mcp installed`);
316
+ console.log(`Proxies ${proxyResult.wrapped.length ? proxyResult.wrapped.map((item) => item.name).join(", ") : "none changed"}`);
317
+ console.log(`Embeddings: ${warmResult.fileCount || 0} files, ${warmResult.skillCount || 0} skills`);
318
+ console.log("Restart Codex to activate ContextOS.");
345
319
  } catch (error) {
346
320
  progress.fail("install failed");
347
321
  throw error;
@@ -351,9 +325,6 @@ async function install({ copy = false, agent = "codex" } = {}) {
351
325
  async function warmInstallEmbeddings() {
352
326
  const dataDir = contextOSDataDir();
353
327
  const modelReady = isModelCacheReady(dataDir);
354
- console.log(modelReady
355
- ? "Required local embedding model already cached."
356
- : "Preparing required local embedding model...");
357
328
  const result = await warmRuleEmbeddings({
358
329
  rules: [
359
330
  { content: "Always use project rules that are semantically relevant to the user prompt." },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minhpnq1807/contextos",
3
- "version": "0.5.36",
3
+ "version": "0.5.37",
4
4
  "description": "Task-aware AGENTS.md context injection and compliance reporting for Codex, Claude Code, and Antigravity.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctx",
3
- "version": "0.5.36",
3
+ "version": "0.5.37",
4
4
  "description": "Inject task-relevant AGENTS.md rules into Codex through plugin hooks.",
5
5
  "author": {
6
6
  "name": "ContextOS"