@minhpnq1807/contextos 0.5.41 → 0.5.44

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/bin/ctx.js CHANGED
@@ -5,7 +5,7 @@ import path from "node:path";
5
5
  import readline from "node:readline/promises";
6
6
  import { stdin as input, stdout as output } from "node:process";
7
7
  import { fileURLToPath } from "node:url";
8
- import { execFileSync, execSync, spawn } from "node:child_process";
8
+ import { execFileSync, execSync } from "node:child_process";
9
9
 
10
10
  import { readAgentsChain } from "../plugins/ctx/lib/reader.js";
11
11
  import { filterActionableRules, parseRules, scoreRules } from "../plugins/ctx/lib/analyzer.js";
@@ -19,23 +19,27 @@ import { scoreContext } from "../plugins/ctx/lib/score-context.js";
19
19
  import { defaultDataRoot, workspaceDataDir, workspaceMarkerPath } from "../plugins/ctx/lib/workspace-data.js";
20
20
  import { installMcpTelemetryProxies } from "../plugins/ctx/lib/mcp-proxy-install.js";
21
21
  import { benchmarkWorkspace, formatBenchmark } from "../plugins/ctx/lib/benchmark.js";
22
- import { copyDir, copyPackageRoot } from "../plugins/ctx/lib/package-install.js";
22
+ import { copyDir, copyPackageRoot, syncPackageRoot } from "../plugins/ctx/lib/package-install.js";
23
23
  import { installClaudeHooks } from "../plugins/ctx/lib/claude-hooks.js";
24
24
  import { installClaudeMcp } from "../plugins/ctx/lib/claude-mcp.js";
25
25
  import { installAntigravityHooks } from "../plugins/ctx/lib/antigravity-hooks.js";
26
26
  import { installAntigravityMcp } from "../plugins/ctx/lib/antigravity-mcp.js";
27
27
  import { installCopilotHooks } from "../plugins/ctx/lib/copilot-hooks.js";
28
28
  import { installCopilotMcp } from "../plugins/ctx/lib/copilot-mcp.js";
29
- import { syncRules } from "../plugins/ctx/lib/ruler-sync.js";
29
+ import { readCodexMcpServers, syncRules } from "../plugins/ctx/lib/ruler-sync.js";
30
+ import { detectGraphStrategy, embedCodeReviewGraph, formatCodeReviewGraphEmbedding, formatGraphStrategy } from "../plugins/ctx/lib/graph-strategy.js";
30
31
  import { writeInnerGitignore, ensureRootGitignore } from "../plugins/ctx/lib/gitignore.js";
31
- import { syncSkills } from "../plugins/ctx/lib/skillshare-sync.js";
32
+ import { repairSkillSymlinks, syncSkills, detectExistingSkills } from "../plugins/ctx/lib/skillshare-sync.js";
32
33
  import { scanSkills, warmSkillEmbeddings } from "../plugins/ctx/lib/skill-discoverer.js";
33
34
  import { parsePassthroughArgs, runPassthrough } from "../plugins/ctx/lib/passthrough.js";
34
35
  import { parseAgentList, parseSetupArgs, setupSummaryLines } from "../plugins/ctx/lib/setup-wizard.js";
35
36
  import { multiSelect } from "../plugins/ctx/lib/multi-select.js";
37
+ import { configureOutputSections, enabledOutputSectionsLabel, loadOutputConfig } from "../plugins/ctx/lib/output-config.js";
36
38
  import { syncWorkflows, warmWorkflowEmbeddings } from "../plugins/ctx/lib/workflow-discoverer.js";
37
39
  import { checkForUpdate } from "../plugins/ctx/lib/update-notifier.js";
38
40
  import { fetchSkillsForAgents, printSkillRecommendations, getAllLibraries, getInstallCommands } from "../plugins/ctx/lib/skill-library.js";
41
+ import { invalidateCtxMcpSocket } from "../plugins/ctx/lib/ctx-mcp-client.js";
42
+ import { runPrefixedCommand } from "../plugins/ctx/lib/shell-runner.js";
39
43
 
40
44
  /**
41
45
  * Run a shell command with all output lines prefixed by │
@@ -43,28 +47,124 @@ import { fetchSkillsForAgents, printSkillRecommendations, getAllLibraries, getIn
43
47
  * stdin is inherited so interactive prompts (e.g. npx "Ok to proceed?") still work.
44
48
  */
45
49
  function runPrefixed(cmd) {
50
+ return runPrefixedCommand(cmd);
51
+ }
52
+
53
+ /**
54
+ * Interactive community skill library installer.
55
+ * Fetches library metadata, shows a multiSelect, and runs install commands.
56
+ * @param {string[]} agents - Agent names to filter libraries for.
57
+ * @returns {Promise<number>} Number of successfully installed sources.
58
+ */
59
+ async function runCommunitySkillInstaller(agents = []) {
60
+ const RESET = "\x1B[0m";
46
61
  const DIM = "\x1B[2m";
47
- const RST = "\x1B[0m";
48
- const pfx = `${DIM}│${RST} `;
49
- return new Promise((resolve, reject) => {
50
- const child = spawn("sh", ["-c", cmd], { stdio: ["inherit", "pipe", "pipe"] });
51
- function pipe(stream, target) {
52
- let needPrefix = true;
53
- stream.on("data", (buf) => {
54
- const str = buf.toString();
55
- let out = "";
56
- for (const ch of str) {
57
- if (needPrefix) { out += pfx; needPrefix = false; }
58
- out += ch;
59
- if (ch === "\n") needPrefix = true;
62
+ const CYAN = "\x1B[36m";
63
+ const GREEN = "\x1B[32m";
64
+ const YELLOW = "\x1B[33m";
65
+ const BOLD = "\x1B[1m";
66
+
67
+ console.log("Fetching community skill libraries...\n");
68
+ const libraryResults = await fetchSkillsForAgents(agents, { dataDir: contextOSDataDir() });
69
+
70
+ const totalSkills = libraryResults.reduce((sum, r) => sum + r.count, 0);
71
+ if (totalSkills === 0) {
72
+ console.log("No skills found. Check your network connection or try --refresh.");
73
+ return 0;
74
+ }
75
+
76
+ // Compact header
77
+ console.log(`${CYAN}◇${RESET} ${BOLD}Community skill libraries available:${RESET}`);
78
+ console.log(`${DIM}│${RESET} Browse and install curated skills from the community.`);
79
+ console.log(`${DIM}│${RESET}`);
80
+
81
+ const allLibs = getAllLibraries();
82
+ const availableLibs = allLibs.filter((lib) => {
83
+ const result = libraryResults.find((r) => r.library.id === lib.id);
84
+ return result && result.count > 0;
85
+ });
86
+
87
+ if (availableLibs.length === 0) {
88
+ console.log("No installable libraries available.");
89
+ return 0;
90
+ }
91
+
92
+ const selectedSources = await multiSelect({
93
+ message: "Select skill sources to install:",
94
+ options: availableLibs.map((lib) => {
95
+ const result = libraryResults.find((r) => r.library.id === lib.id);
96
+ return {
97
+ label: `${lib.name} (${result?.count || 0} skills)`,
98
+ value: lib.id,
99
+ hint: lib.url,
100
+ selected: false
101
+ };
102
+ })
103
+ });
104
+
105
+ if (!selectedSources || selectedSources.length === 0) {
106
+ console.log(`\n${DIM}No sources selected.${RESET}`);
107
+ return 0;
108
+ }
109
+
110
+ // Install each selected source
111
+ let successCount = 0;
112
+ for (const libId of selectedSources) {
113
+ const lib = allLibs.find((l) => l.id === libId);
114
+ if (!lib) continue;
115
+
116
+ const installInfo = getInstallCommands(libId);
117
+ if (!installInfo) {
118
+ console.log(`${YELLOW}⚠${RESET} No install info for ${lib.name}. Visit: ${lib.url}`);
119
+ continue;
120
+ }
121
+
122
+ console.log("");
123
+ console.log(`${CYAN}◇${RESET} ${BOLD}Installing from ${lib.name}${RESET}`);
124
+
125
+ if (installInfo.type === "manual") {
126
+ console.log(`${DIM}│${RESET} ${installInfo.instructions}`);
127
+ console.log(`${DIM}│${RESET} ${DIM}URL: ${lib.url}${RESET}`);
128
+ continue;
129
+ }
130
+
131
+ const installCmd = installInfo.fullInstall;
132
+ if (installCmd) {
133
+ console.log(`${DIM}│${RESET} ${GREEN}$ ${installCmd}${RESET}`);
134
+ console.log(`${DIM}│${RESET}`);
135
+
136
+ try {
137
+ const beforeRepair = repairSkillSymlinks({ cwd: process.cwd(), home: os.homedir() });
138
+ if (beforeRepair.repaired.length || beforeRepair.removedBroken.length) {
139
+ console.log(`${DIM}│${RESET} Repaired ${beforeRepair.repaired.length} skill links before install.`);
60
140
  }
61
- target.write(out);
62
- });
141
+ await runPrefixed(installCmd);
142
+ const afterRepair = repairSkillSymlinks({ cwd: process.cwd(), home: os.homedir() });
143
+ if (afterRepair.repaired.length || afterRepair.removedBroken.length) {
144
+ console.log(`${DIM}│${RESET} Repaired ${afterRepair.repaired.length} skill links after install.`);
145
+ }
146
+ successCount++;
147
+
148
+ if (installInfo.verify) {
149
+ try { await runPrefixed(installInfo.verify); } catch { /* best-effort */ }
150
+ }
151
+ console.log(`${DIM}│${RESET}`);
152
+ console.log(`${GREEN}✔${RESET} ${lib.name} installed successfully.`);
153
+ } catch (err) {
154
+ console.error(`${YELLOW}⚠${RESET} Install failed for ${lib.name}.`);
155
+ console.error(`${DIM}│${RESET} ${DIM}${err.message}${RESET}`);
156
+ console.error(`${DIM}│${RESET} ContextOS will continue setup; rerun \`ctx skills\` after fixing the environment.`);
157
+ }
63
158
  }
64
- pipe(child.stdout, process.stdout);
65
- pipe(child.stderr, process.stderr);
66
- child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`exit code ${code}`)));
67
- });
159
+ }
160
+
161
+ // Summary
162
+ console.log("");
163
+ if (successCount > 0) {
164
+ console.log(`${GREEN}✔${RESET} ${BOLD}${successCount} source${successCount > 1 ? "s" : ""} installed.${RESET}`);
165
+ console.log(`${DIM}│${RESET} Restart your agent to pick up new skills.`);
166
+ }
167
+ return successCount;
68
168
  }
69
169
 
70
170
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -105,6 +205,8 @@ Usage:
105
205
  ctx skills Browse community skill libraries
106
206
  ctx skills --agents <names> Filter skills for specific agents
107
207
  ctx skills --refresh Force refresh skill library cache
208
+ ctx --config Choose prompt context sections to show
209
+ ctx refresh Sync active Codex marketplace and rebuild indexes
108
210
  ctx embeddings warm -- "task" Pre-warm embedding caches for a task
109
211
  ctx ruler -- <ruler args> Passthrough to ruler CLI
110
212
  ctx skillshare -- <skillshare args> Passthrough to skillshare CLI
@@ -257,8 +359,12 @@ async function install({ copy = false, agent = "codex" } = {}) {
257
359
  }
258
360
  const progress = createInstallProgress({ quiet: false });
259
361
  progress.start(`installing ${agent || "codex"}`);
362
+ const graphStrategy = graphStrategyForInstall();
260
363
 
261
364
  try {
365
+ progress.step(5, "syncing active marketplace");
366
+ syncActiveCodexMarketplace();
367
+
262
368
  if (agent === "claude") {
263
369
  progress.step(10, "copying package");
264
370
  const installRoot = copyPackageRoot({ rootDir, targetRoot: agentInstallRoot("claude") });
@@ -274,7 +380,9 @@ async function install({ copy = false, agent = "codex" } = {}) {
274
380
  progress.done("claude ✓");
275
381
  console.log(`Hooks → ${hooksPath}`);
276
382
  console.log(`MCP → ${mcpConfigPath}`);
383
+ console.log(`Graph → ${graphStrategy}`);
277
384
  console.log(`Embeddings: ${warmResult.fileCount || 0} files, ${warmResult.skillCount || 0} skills`);
385
+ console.log(`Graph embeddings: ${formatCodeReviewGraphEmbedding(warmResult.graphEmbedding)}`);
278
386
  console.log("Restart Claude Code to activate ContextOS.");
279
387
  return;
280
388
  }
@@ -294,7 +402,9 @@ async function install({ copy = false, agent = "codex" } = {}) {
294
402
  progress.done("antigravity ✓");
295
403
  console.log(`Hooks → ${hooksPath}`);
296
404
  console.log(`MCP → ${mcpConfigPaths.join(", ")}`);
405
+ console.log(`Graph → ${graphStrategy}`);
297
406
  console.log(`Embeddings: ${warmResult.fileCount || 0} files, ${warmResult.skillCount || 0} skills`);
407
+ console.log(`Graph embeddings: ${formatCodeReviewGraphEmbedding(warmResult.graphEmbedding)}`);
298
408
  console.log("Restart Antigravity to activate ContextOS.");
299
409
  return;
300
410
  }
@@ -314,7 +424,9 @@ async function install({ copy = false, agent = "codex" } = {}) {
314
424
  progress.done("copilot ✓");
315
425
  console.log(`Instructions → ${hooksPath}`);
316
426
  console.log(`MCP → ${mcpConfigPath}`);
427
+ console.log(`Graph → ${graphStrategy}`);
317
428
  console.log(`Embeddings: ${warmResult.fileCount || 0} files, ${warmResult.skillCount || 0} skills`);
429
+ console.log(`Graph embeddings: ${formatCodeReviewGraphEmbedding(warmResult.graphEmbedding)}`);
318
430
  console.log("Restart VS Code to activate ContextOS.");
319
431
  return;
320
432
  }
@@ -324,8 +436,7 @@ async function install({ copy = false, agent = "codex" } = {}) {
324
436
  }
325
437
 
326
438
  progress.step(10, "copying marketplace");
327
- const marketplaceRoot = path.join(codexHome(), "marketplaces", "contextos");
328
- copyPackageRoot({ rootDir, targetRoot: marketplaceRoot });
439
+ const marketplaceRoot = activeCodexMarketplaceRoot();
329
440
 
330
441
  progress.step(25, "refreshing codex plugin");
331
442
  tryRunCodex(["plugin", "remove", "ctx@contextos"]);
@@ -350,7 +461,9 @@ async function install({ copy = false, agent = "codex" } = {}) {
350
461
  console.log(`Hooks → ${hooksPath}`);
351
462
  console.log(`MCP → ctx-mcp installed`);
352
463
  console.log(`Proxies → ${proxyResult.wrapped.length ? proxyResult.wrapped.map((item) => item.name).join(", ") : "none changed"}`);
464
+ console.log(`Graph → ${graphStrategy}`);
353
465
  console.log(`Embeddings: ${warmResult.fileCount || 0} files, ${warmResult.skillCount || 0} skills`);
466
+ console.log(`Graph embeddings: ${formatCodeReviewGraphEmbedding(warmResult.graphEmbedding)}`);
354
467
  console.log("Restart Codex to activate ContextOS.");
355
468
  } catch (error) {
356
469
  progress.fail("install failed");
@@ -358,6 +471,19 @@ async function install({ copy = false, agent = "codex" } = {}) {
358
471
  }
359
472
  }
360
473
 
474
+ function graphStrategyForInstall() {
475
+ let mcpServerNames = [];
476
+ try {
477
+ mcpServerNames = readCodexMcpServers().map((server) => server.name);
478
+ } catch {
479
+ // Graph detection is diagnostic and must not block installation.
480
+ }
481
+ return formatGraphStrategy(detectGraphStrategy({
482
+ cwd: process.cwd(),
483
+ mcpServerNames
484
+ }));
485
+ }
486
+
361
487
  async function warmInstallEmbeddings() {
362
488
  const dataDir = contextOSDataDir();
363
489
  const modelReady = isModelCacheReady(dataDir);
@@ -392,7 +518,21 @@ async function warmInstallEmbeddings() {
392
518
  allowRemote: !modelReady
393
519
  })
394
520
  : { count: 0 };
395
- return { ...result, modelAlreadyCached: modelReady, fileCount: fileResult.count, skillCount: skillResult.count, workflowCount: workflowResult.count };
521
+ const graphEmbedding = embedCodeReviewGraph({ cwd: process.cwd() });
522
+ return { ...result, modelAlreadyCached: modelReady, fileCount: fileResult.count, skillCount: skillResult.count, workflowCount: workflowResult.count, graphEmbedding };
523
+ }
524
+
525
+ function activeCodexMarketplaceRoot() {
526
+ return path.join(codexHome(), "marketplaces", "contextos");
527
+ }
528
+
529
+ function syncActiveCodexMarketplace() {
530
+ const result = syncPackageRoot({
531
+ rootDir,
532
+ targetRoot: activeCodexMarketplaceRoot()
533
+ });
534
+ writeInnerGitignore(result.targetRoot);
535
+ return result;
396
536
  }
397
537
 
398
538
  function tryRunCodex(args) {
@@ -502,7 +642,23 @@ async function debug(task) {
502
642
  console.log(scheduled.additionalContext || "(empty)");
503
643
  }
504
644
 
505
- async function warmEmbeddings(task) {
645
+ async function warmEmbeddings(task, { syncMarketplace = true, quiet = false } = {}) {
646
+ const warmResult = await warmWorkspaceIndexes({ task });
647
+ const marketplaceSync = syncMarketplace ? syncActiveCodexMarketplace() : null;
648
+ if (quiet) return { ...warmResult, marketplaceSync };
649
+ console.log(`Warmed ${warmResult.ruleCount} embeddings`);
650
+ console.log(`Warmed ${warmResult.fileCount} file path embeddings`);
651
+ console.log(`Warmed ${warmResult.skillCount} skill embeddings`);
652
+ console.log(`Warmed ${warmResult.workflowCount} workflow embeddings`);
653
+ console.log(`Cache: ${warmResult.cachePath}`);
654
+ console.log(`Graph embeddings: ${formatCodeReviewGraphEmbedding(warmResult.graphEmbedding)}`);
655
+ if (marketplaceSync) {
656
+ console.log(`Marketplace: ${marketplaceSync.synced ? "synced" : "already active"} (${marketplaceSync.targetRoot})`);
657
+ }
658
+ return { ...warmResult, marketplaceSync };
659
+ }
660
+
661
+ async function warmWorkspaceIndexes({ task = "project context" } = {}) {
506
662
  const cwd = process.cwd();
507
663
  const merged = readAgentsChain({ cwd });
508
664
  const rules = scoreRules(filterActionableRules(parseRules(merged.content)), task, []);
@@ -528,11 +684,26 @@ async function warmEmbeddings(task) {
528
684
  dataDir: contextOSDataDir(),
529
685
  allowRemote: true
530
686
  });
531
- console.log(`Warmed ${result.count} embeddings`);
532
- console.log(`Warmed ${fileResult.count} file path embeddings`);
533
- console.log(`Warmed ${skillResult.count} skill embeddings`);
534
- console.log(`Warmed ${workflowResult.count} workflow embeddings`);
535
- console.log(`Cache: ${result.cachePath}`);
687
+ const graphEmbedding = embedCodeReviewGraph({ cwd });
688
+ return {
689
+ ruleCount: result.count,
690
+ fileCount: fileResult.count,
691
+ skillCount: skillResult.count,
692
+ workflowCount: workflowResult.count,
693
+ cachePath: result.cachePath,
694
+ graphEmbedding
695
+ };
696
+ }
697
+
698
+ async function refresh() {
699
+ const marketplaceSync = syncActiveCodexMarketplace();
700
+ const invalidatedBridge = invalidateCtxMcpSocket(contextOSDataDir());
701
+ const warmResult = await warmInstallEmbeddings();
702
+ console.log(`Marketplace: ${marketplaceSync.synced ? "synced" : "already active"} (${marketplaceSync.targetRoot})`);
703
+ console.log(`Indexes: ${warmResult.fileCount || 0} file paths rebuilt`);
704
+ console.log(`Graph embeddings: ${formatCodeReviewGraphEmbedding(warmResult.graphEmbedding)}`);
705
+ if (invalidatedBridge) console.log("Bridge: stale private socket invalidated");
706
+ console.log("Restart Codex if ctx-mcp was already running.");
536
707
  }
537
708
 
538
709
  function printSetupBanner() {
@@ -559,6 +730,7 @@ async function askSetupYesNo(rl, question, defaultValue = true) {
559
730
  async function setup({ args = [], cwd = process.cwd() } = {}) {
560
731
  const options = parseSetupArgs(args);
561
732
  const interactive = !options.yes && process.stdin.isTTY;
733
+ let outputConfig = loadOutputConfig({ dataRoot: contextOSDataDir() });
562
734
 
563
735
  printSetupBanner();
564
736
  console.log(`◇ Installation directory:\n│ ${cwd}`);
@@ -599,11 +771,22 @@ async function setup({ args = [], cwd = process.cwd() } = {}) {
599
771
  rl.close();
600
772
  }
601
773
  }
774
+
775
+ console.log("");
776
+ console.log("◇ Configure prompt output:");
777
+ outputConfig = await configureOutputSections({
778
+ dataRoot: contextOSDataDir(),
779
+ select: multiSelect
780
+ });
602
781
  }
603
782
 
604
783
  console.log("");
605
784
  console.log("◇ Ready to setup:");
606
- for (const line of setupSummaryLines({ cwd, ...options })) console.log(`│ ${line}`);
785
+ for (const line of setupSummaryLines({
786
+ cwd,
787
+ ...options,
788
+ promptSections: enabledOutputSectionsLabel(outputConfig)
789
+ })) console.log(`│ ${line}`);
607
790
  console.log("");
608
791
 
609
792
  if (!options.agents.length) throw new Error("No agents selected. Use --agents codex,claude,antigravity,copilot.");
@@ -626,7 +809,8 @@ async function setup({ args = [], cwd = process.cwd() } = {}) {
626
809
  const skillAgents = options.agents.map((agent) => agent === "agy" ? "antigravity" : agent).join(",");
627
810
  const syncArgs = ["--skills", "--agents", skillAgents];
628
811
  if (options.yes) syncArgs.push("--yes");
629
- await streamSetupOutput(() => syncSkills({
812
+
813
+ const doSyncSkills = async () => streamSetupOutput(() => syncSkills({
630
814
  cwd,
631
815
  args: syncArgs,
632
816
  rebuildSkillEmbeddings: async ({ cwd: skillCwd, sourceDir }) => warmSkillEmbeddings({
@@ -636,6 +820,25 @@ async function setup({ args = [], cwd = process.cwd() } = {}) {
636
820
  skills: scanSkills({ cwd: skillCwd, roots: [sourceDir] })
637
821
  })
638
822
  }));
823
+
824
+ await doSyncSkills();
825
+
826
+ // Fallback: if no skills were found, offer community library installer
827
+ const existing = detectExistingSkills({ cwd });
828
+ const totalExisting = existing.reduce((sum, e) => sum + e.count, 0);
829
+ if (totalExisting === 0) {
830
+ console.log("");
831
+ console.log(`${YELLOW}⚠${RESET} No skills found on this machine.`);
832
+ console.log(`${DIM}│${RESET} Install community skills to get started.`);
833
+ console.log("");
834
+
835
+ const installed = await runCommunitySkillInstaller(options.agents);
836
+ if (installed > 0) {
837
+ console.log("");
838
+ console.log("◇ Re-syncing skills after install...");
839
+ await doSyncSkills();
840
+ }
841
+ }
639
842
  }
640
843
 
641
844
  console.log("");
@@ -643,12 +846,6 @@ async function setup({ args = [], cwd = process.cwd() } = {}) {
643
846
  console.log("│ Next: restart/open your agent from this project directory.");
644
847
  console.log("│ Try: ctx debug -- \"Recheck authen flow\"");
645
848
  console.log("");
646
-
647
- // Recommend community skills based on selected agents
648
- try {
649
- const libraryResults = await fetchSkillsForAgents(options.agents, { dataDir: contextOSDataDir() });
650
- printSkillRecommendations(libraryResults);
651
- } catch { /* skill library is best-effort */ }
652
849
  }
653
850
 
654
851
  const args = process.argv.slice(2);
@@ -670,6 +867,11 @@ try {
670
867
  console.log(usage());
671
868
  } else if (command === "--version" || command === "-v") {
672
869
  console.log(packageVersion());
870
+ } else if (command === "--config" || command === "config") {
871
+ await configureOutputSections({
872
+ dataRoot: contextOSDataDir(),
873
+ select: multiSelect
874
+ });
673
875
  } else if (command === "install") {
674
876
  const copy = args.includes("--copy");
675
877
  const explicitAgents = installAgentsFromArgs(args);
@@ -711,6 +913,12 @@ try {
711
913
  const task = marker >= 0 ? args.slice(marker + 1).join(" ") : args.slice(1).join(" ");
712
914
  if (!task.trim()) throw new Error('Usage: ctx debug -- "task"');
713
915
  await debug(task);
916
+ } else if (command === "refresh") {
917
+ await refresh();
918
+ } else if (command === "autowarm") {
919
+ const marker = args.indexOf("--");
920
+ const task = marker >= 0 ? args.slice(marker + 1).join(" ") : args.slice(1).join(" ");
921
+ await warmEmbeddings(task || "project context", { syncMarketplace: false, quiet: true });
714
922
  } else if (command === "embeddings") {
715
923
  if (args[1] === "warm") {
716
924
  const marker = args.indexOf("--");
@@ -748,105 +956,21 @@ try {
748
956
  const YELLOW = "\x1B[33m";
749
957
  const BOLD = "\x1B[1m";
750
958
 
751
- console.log("Fetching community skill libraries...\n");
752
- const libraryResults = await fetchSkillsForAgents(agents, {
753
- dataDir: contextOSDataDir()
754
- });
755
-
756
- const totalSkills = libraryResults.reduce((sum, r) => sum + r.count, 0);
757
- if (totalSkills === 0) {
758
- console.log("No skills found. Check your network connection or try --refresh.");
759
- process.exit(1);
760
- }
761
-
762
- // Compact header
763
- console.log(`${CYAN}◇${RESET} ${BOLD}Community skill libraries available:${RESET}`);
764
- console.log(`${DIM}│${RESET} Browse and install curated skills from the community.`);
765
- console.log(`${DIM}│${RESET}`);
766
-
767
- // Multi-select which sources to install from
768
- const allLibs = getAllLibraries();
769
- const availableLibs = allLibs.filter((lib) => {
770
- const result = libraryResults.find((r) => r.library.id === lib.id);
771
- return result && result.count > 0;
772
- });
773
-
774
- if (availableLibs.length === 0) {
775
- console.log("No installable libraries available.");
776
- process.exit(0);
777
- }
778
-
779
- const selectedSources = await multiSelect({
780
- message: "Select skill sources to install:",
781
- options: availableLibs.map((lib) => {
782
- const result = libraryResults.find((r) => r.library.id === lib.id);
783
- return {
784
- label: `${lib.name} (${result?.count || 0} skills)`,
785
- value: lib.id,
786
- hint: lib.url,
787
- selected: false
788
- };
789
- })
790
- });
791
-
792
- if (!selectedSources || selectedSources.length === 0) {
793
- console.log(`\n${DIM}No sources selected.${RESET}`);
794
- process.exit(0);
795
- }
796
-
797
- // Install each selected source using its provided commands
798
- let successCount = 0;
799
- for (const libId of selectedSources) {
800
- const lib = allLibs.find((l) => l.id === libId);
801
- if (!lib) continue;
802
-
803
- const installInfo = getInstallCommands(libId);
804
- if (!installInfo) {
805
- console.log(`${YELLOW}⚠${RESET} No install info for ${lib.name}. Visit: ${lib.url}`);
806
- continue;
807
- }
808
-
809
- console.log("");
810
- console.log(`${CYAN}◇${RESET} ${BOLD}Installing from ${lib.name}${RESET}`);
811
-
812
- if (installInfo.type === "manual") {
813
- console.log(`${DIM}│${RESET} ${installInfo.instructions}`);
814
- console.log(`${DIM}│${RESET} ${DIM}URL: ${lib.url}${RESET}`);
815
- continue;
816
- }
817
-
818
- const installCmd = installInfo.fullInstall;
819
- if (installCmd) {
820
- console.log(`${DIM}│${RESET} ${GREEN}$ ${installCmd}${RESET}`);
821
- console.log(`${DIM}│${RESET}`);
822
-
823
- try {
824
- await runPrefixed(installCmd);
825
- successCount++;
826
-
827
- // Run verify command if available
828
- if (installInfo.verify) {
829
- try {
830
- await runPrefixed(installInfo.verify);
831
- } catch { /* verify is best-effort */ }
832
- }
833
- console.log(`${DIM}│${RESET}`);
834
- console.log(`${GREEN}✔${RESET} ${lib.name} installed successfully.`);
835
- } catch (err) {
836
- console.error(`${YELLOW}⚠${RESET} Install failed for ${lib.name}. Try manually:`);
837
- console.error(` ${installCmd}`);
838
- console.error(` ${DIM}${err.message}${RESET}`);
839
- }
840
- }
841
- }
842
-
843
- // Final summary
844
- console.log("");
845
- if (successCount > 0) {
846
- console.log(`${GREEN}✔${RESET} ${BOLD}${successCount} source${successCount > 1 ? "s" : ""} installed.${RESET}`);
847
- console.log(`${DIM}│${RESET} Restart your agent to pick up new skills.`);
959
+ const installed = await runCommunitySkillInstaller(agents);
960
+ if (installed === 0) {
961
+ console.log(`\n${DIM}No installations were completed.${RESET}`);
848
962
  } else {
849
- console.log(`${DIM}No installations were completed.${RESET}`);
963
+ console.log(`${CYAN}◇${RESET} ${BOLD}Syncing installed skills${RESET}`);
964
+ await streamSetupOutput(() => syncSkills({
965
+ cwd: process.cwd(),
966
+ args: ["--skills", "--agents", agents.map((agent) => agent === "agy" ? "antigravity" : agent).join(","), "--yes"],
967
+ rebuildSkillEmbeddings: async ({ cwd, sourceDir }) => warmSkillEmbeddings({
968
+ cwd,
969
+ dataDir: contextOSDataDir(),
970
+ allowRemote: !isModelCacheReady(contextOSDataDir()),
971
+ skills: scanSkills({ cwd, roots: [sourceDir] })
972
+ })
973
+ }));
850
974
  }
851
975
  console.log("");
852
976
  } else if (command === "sync") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minhpnq1807/contextos",
3
- "version": "0.5.41",
3
+ "version": "0.5.44",
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": {
@@ -52,6 +52,7 @@
52
52
  "dependencies": {
53
53
  "@modelcontextprotocol/sdk": "^1.29.0",
54
54
  "@xenova/transformers": "^2.17.2",
55
+ "smol-toml": "^1.6.1",
55
56
  "sql.js": "^1.14.1",
56
57
  "zod": "^4.4.3"
57
58
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctx",
3
- "version": "0.5.41",
3
+ "version": "0.5.44",
4
4
  "description": "Inject task-relevant AGENTS.md rules into Codex through plugin hooks.",
5
5
  "author": {
6
6
  "name": "ContextOS"
@@ -1,12 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { readStdinJson, writeJson, failOpen, logDebug, pluginRuntimeFile, pluginDataRoot, resolveHookCwd } from "../lib/hook-io.js";
2
+ import { armHookDeadline, exitAfterStdout, readStdinJson, writeJson, failOpen, logDebug, pluginRuntimeFile, pluginDataRoot, resolveHookCwd } from "../lib/hook-io.js";
3
3
  import { handlePromptPayload } from "../lib/prompt-hook.js";
4
4
  import { appendTelemetry } from "../lib/telemetry.js";
5
5
 
6
6
  const started = Date.now();
7
+ const fallback = {
8
+ continue: true,
9
+ suppressOutput: true
10
+ };
11
+ let deadline;
7
12
 
8
13
  try {
9
14
  const payload = await readStdinJson();
15
+ deadline = armHookDeadline("UserPromptSubmit", fallback);
10
16
  const cwd = resolveHookCwd(payload);
11
17
  const normalized = { ...payload, cwd };
12
18
 
@@ -18,13 +24,10 @@ try {
18
24
  mcpDataDir: pluginDataRoot(),
19
25
  started
20
26
  }));
27
+ deadline.clear();
28
+ exitAfterStdout(0);
21
29
  } catch (error) {
22
- failOpen("UserPromptSubmit", error, {
23
- continue: true,
24
- suppressOutput: true,
25
- hookSpecificOutput: {
26
- hookEventName: "UserPromptSubmit",
27
- additionalContext: ""
28
- }
29
- });
30
+ deadline?.clear();
31
+ failOpen("UserPromptSubmit", error, fallback);
32
+ exitAfterStdout(0);
30
33
  }