xtrm-tools 2.1.1 → 2.1.3

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/README.md CHANGED
@@ -198,19 +198,23 @@ Task intake and service routing for Docker service projects.
198
198
 
199
199
  ### Standalone Hooks
200
200
 
201
- **pip-venv-guard.py**
202
- - Trigger: PreToolUse (Bash)
203
- - Purpose: Prevent `pip install` outside virtual environments
201
+ **main-guard.mjs**
202
+ - Trigger: PreToolUse (Write|Edit|MultiEdit)
203
+ - Purpose: Blocks direct edits on protected branches with structured deny output
204
204
 
205
205
  **type-safety-enforcement.py**
206
206
  - Trigger: PreToolUse (Bash|Edit|Write)
207
207
  - Purpose: Enforce type safety in Python code
208
208
 
209
- **statusline.js**
210
- - Trigger: StatusLine
211
- - Purpose: Display custom status line information
209
+ **agent_context.py**
210
+ - Trigger: Support module used by Python hooks
211
+ - Purpose: Shared hook input/output helper
212
212
 
213
- **NOTE** certain skills are third-party utilities, i believe they can be useful.
213
+ **beads gate hooks** (installed with `xtrm install all`, or included when beads+dolt is available):
214
+ - `beads-edit-gate.mjs` (PreToolUse)
215
+ - `beads-commit-gate.mjs` (PreToolUse)
216
+ - `beads-stop-gate.mjs` (Stop)
217
+ - `beads-close-memory-prompt.mjs` (PostToolUse)
214
218
 
215
219
  ## Project Skills
216
220
 
@@ -301,11 +305,12 @@ npm link # registers `xtrm` globally
301
305
  xtrm <command> [options]
302
306
  ```
303
307
 
304
- | Command | Description |
305
- | -------- | --------------------------------- |
306
- | `sync` | Sync tools to target environments |
307
- | `status` | Show diff without making changes |
308
- | `reset` | Clear saved preferences |
308
+ | Command | Description |
309
+ | --------- | -------------------------------- |
310
+ | `install` | Install/update tools |
311
+ | `status` | Show diff without making changes |
312
+ | `reset` | Clear saved preferences |
313
+ | `help` | Show command/component overview |
309
314
 
310
315
  ---
311
316
 
@@ -329,7 +334,7 @@ xtrm install --backport # reverse direction: copy drifted local edits → re
329
334
  - **cli-table3 plan table**: Formatted table showing Target / + New / ↑ Update / ! Drift / Total
330
335
  - **boxen summary card**: Completion summary with green/yellow border based on drift
331
336
  - **Themed output**: Semantic colors (success, error, warning, muted, accent) via `theme.ts`
332
- - **Interactive consent**: Multiselect for MCP servers (space to toggle, all pre-selected)
337
+ - **beads+dolt prerequisite flow**: `xtrm install` / `xtrm install all` check workflow backend and can install missing deps
333
338
  - **Auto-detection**: Scans Claude Code targets automatically (`~/.claude`, `%APPDATA%/Claude` on Windows) plus the `.agents/skills` cache for skills-only sync
334
339
  - **Inline sync**: `status` command offers to apply sync immediately after showing changes
335
340
  - **Single confirmation**: See full plan across all targets, confirm once
@@ -395,8 +400,8 @@ xtrm reset
395
400
 
396
401
  1. Clone this repository:
397
402
  ```bash
398
- git clone https://github.com/Jaggerxtrm/jaggers-agent-tools.git
399
- cd jaggers-agent-tools
403
+ git clone https://github.com/Jaggerxtrm/xtrm-tools.git
404
+ cd xtrm-tools
400
405
  ```
401
406
 
402
407
  2. Copy skills to Claude Code:
@@ -457,9 +462,9 @@ If you use Gemini CLI or Qwen CLI, you can still use xtrm-tools skills and hooks
457
462
 
458
463
  ## Configuration
459
464
 
460
- ### MCP Servers (v1.7.0 Unified System)
465
+ ### MCP Servers (Unified CLI Sync)
461
466
 
462
- MCP servers are configured from canonical sources with automatic format adaptation for each agent.
467
+ MCP servers are configured from canonical sources and synced via official CLI commands.
463
468
 
464
469
  **Core Servers** (installed by default):
465
470
  - **serena**: Code analysis (requires `uvx`, auto project detection)
@@ -484,22 +489,24 @@ MCP servers are configured from canonical sources with automatic format adaptati
484
489
  - **Persistence:** Values preserved across syncs; never overwritten
485
490
  - Edit `~/.config/xtrm-tools/.env` to add your API keys manually
486
491
 
487
- **Unified MCP CLI Sync (v1.7.0)**:
488
- - Uses official `mcp add`/`mcp remove`/`mcp list` commands for all agents
492
+ **Unified MCP CLI Sync**:
493
+ - Uses official `claude mcp add` / `claude mcp list` commands
489
494
  - **Idempotent:** Re-running is always safe — skips already-installed servers
490
495
  - **Deduplication:** Prevents same server from syncing N times when multiple dirs selected
491
- - **Interactive consent:** Multiselect prompt (space to toggle, all pre-selected)
492
496
  - **Prerequisite auto-install:** Runs `npm install -g gitnexus` automatically when selected
493
497
  - **Post-install guidance:** Shows required next steps (e.g., `npx gitnexus analyze`)
494
498
  - **Timeout protection:** 10s timeout on CLI calls to prevent hangs
495
499
  - **Clean errors:** User-friendly messages (no stack traces)
496
500
 
497
- **Supported Agents**:
498
- - Claude Code (`~/.claude.json` via `mcp add` CLI)
499
- - Antigravity (`~/.gemini/antigravity/mcp_config.json` via `mcp add` CLI)
501
+ **Scopes**:
502
+ - Global installs (`xtrm install`, `xtrm install all`, `xtrm install basic`) sync MCP into Claude global config
503
+ - Project installs (`xtrm install project <name|all>`) sync core MCP at project scope (`-s project`)
500
504
 
501
- **Deprecated (v1.7.0)**:
502
- - JSON file sync for Claude/Gemini/Qwen MCP — superseded by official `mcp` CLI method
505
+ **Supported Agent**:
506
+ - Claude Code
507
+
508
+ **Deprecated**:
509
+ - JSON file MCP sync (superseded by official CLI method)
503
510
  - Repo `.env` files — use centralized `~/.config/xtrm-tools/.env`
504
511
 
505
512
  **Documentation**: See [docs/mcp-servers-config.md](docs/mcp-servers-config.md) for complete setup guide.
@@ -526,7 +533,7 @@ Adjust hook execution timeouts in `settings.json`:
526
533
  "hooks": {
527
534
  "UserPromptSubmit": [{
528
535
  "hooks": [{
529
- "timeout": 5000 // Timeout in milliseconds (5000ms = 5 seconds) for both Claude and Gemini
536
+ "timeout": 5000 // Timeout in milliseconds (5000ms = 5 seconds)
530
537
  }]
531
538
  }]
532
539
  }
@@ -574,7 +581,7 @@ Project-specific operational knowledge system for Docker service projects. Gives
574
581
 
575
582
  ```bash
576
583
  cd ~/projects/my-project
577
- python3 /path/to/jaggers-agent-tools/project-skills/service-skills-set/install-service-skills.py
584
+ python3 /path/to/xtrm-tools/project-skills/service-skills-set/install-service-skills.py
578
585
  ```
579
586
 
580
587
  - Idempotent — safe to re-run after updates
@@ -657,7 +664,7 @@ See [CHANGELOG.md](CHANGELOG.md) for complete version history.
657
664
  ## Repository Structure
658
665
 
659
666
  ```
660
- jaggers-agent-tools/
667
+ xtrm-tools/
661
668
  ├── README.md # This file
662
669
  ├── CHANGELOG.md # Version history
663
670
  ├── ROADMAP.md # Future plans
@@ -667,7 +674,7 @@ jaggers-agent-tools/
667
674
  ├── cli/ # Config Manager CLI (TypeScript)
668
675
  │ ├── src/
669
676
  │ │ ├── index.ts # Entry point (Commander program)
670
- │ │ ├── commands/ # sync.ts, status.ts, reset.ts
677
+ │ │ ├── commands/ # install.ts, install-project.ts, status.ts, reset.ts, help.ts
671
678
  │ │ ├── adapters/ # base, claude, gemini, qwen, registry
672
679
  │ │ ├── core/ # context, diff, sync-executor, manifest, rollback
673
680
  │ │ ├── utils/ # hash, atomic-config, config-adapter, env-manager, theme…
@@ -708,7 +715,7 @@ jaggers-agent-tools/
708
715
  │ ├── type-safety-enforcement.py # Type safety
709
716
  │ ├── gitnexus/
710
717
  │ │ └── gitnexus-hook.cjs # PreToolUse knowledge graph enrichment
711
- │ └── statusline.js # Status line display
718
+ │ └── main-guard.mjs # Protected-branch edit guard
712
719
 
713
720
  ├── config/ # Canonical configuration
714
721
  │ ├── mcp_servers.json # Core MCP servers
@@ -41031,7 +41031,7 @@ function renderPlanTable(allChanges) {
41031
41031
  const missing = Object.values(changeSet).reduce((s, c) => s + c.missing.length, 0);
41032
41032
  const outdated = Object.values(changeSet).reduce((s, c) => s + c.outdated.length, 0);
41033
41033
  table.push([
41034
- kleur_default.white(import_path11.default.basename(target)),
41034
+ kleur_default.white(formatTargetLabel(target)),
41035
41035
  missing > 0 ? kleur_default.green(String(missing)) : t.label("\u2014"),
41036
41036
  outdated > 0 ? kleur_default.yellow(String(outdated)) : t.label("\u2014"),
41037
41037
  kleur_default.bold().white(String(totalChanges))
@@ -41060,6 +41060,12 @@ async function renderSummaryCard(allChanges, totalCount, allSkipped, isDryRun) {
41060
41060
  }) + "\n");
41061
41061
  }
41062
41062
  var BEADS_HOOK_PATTERN = /^beads-/;
41063
+ function formatTargetLabel(target) {
41064
+ const normalized = target.replace(/\\/g, "/").toLowerCase();
41065
+ if (normalized.endsWith("/.agents/skills") || normalized.includes("/.agents/skills/")) return "~/.agents/skills";
41066
+ if (normalized.endsWith("/.claude") || normalized.includes("/.claude/")) return "~/.claude";
41067
+ return import_path11.default.basename(target);
41068
+ }
41063
41069
  function filterBeadsFromChangeSet(changeSet) {
41064
41070
  return {
41065
41071
  ...changeSet,
@@ -41164,7 +41170,7 @@ async function runGlobalInstall(flags, installOpts = {}) {
41164
41170
  }
41165
41171
  const diffTasks = new Listr(
41166
41172
  targets.map((target) => ({
41167
- title: import_path11.default.basename(target),
41173
+ title: formatTargetLabel(target),
41168
41174
  task: async (listCtx, task) => {
41169
41175
  try {
41170
41176
  let changeSet = await calculateDiff(repoRoot, target, false);
@@ -41179,13 +41185,13 @@ async function runGlobalInstall(flags, installOpts = {}) {
41179
41185
  (sum, c) => sum + c.missing.length + c.outdated.length + c.drifted.length,
41180
41186
  0
41181
41187
  );
41182
- task.title = `${import_path11.default.basename(target)}${t.muted(` \u2014 ${totalChanges} change${totalChanges !== 1 ? "s" : ""}`)}`;
41188
+ task.title = `${formatTargetLabel(target)}${t.muted(` \u2014 ${totalChanges} change${totalChanges !== 1 ? "s" : ""}`)}`;
41183
41189
  if (totalChanges > 0) {
41184
41190
  listCtx.allChanges.push({ target, changeSet, totalChanges, skippedDrifted: [] });
41185
41191
  }
41186
41192
  } catch (err) {
41187
41193
  if (err instanceof PruneModeReadError) {
41188
- task.title = `${import_path11.default.basename(target)} ${kleur_default.red("(skipped \u2014 cannot read in prune mode)")}`;
41194
+ task.title = `${formatTargetLabel(target)} ${kleur_default.red("(skipped \u2014 cannot read in prune mode)")}`;
41189
41195
  } else {
41190
41196
  throw err;
41191
41197
  }
@@ -41221,7 +41227,7 @@ async function runGlobalInstall(flags, installOpts = {}) {
41221
41227
  let totalCount = 0;
41222
41228
  for (const { target, changeSet, skippedDrifted } of allChanges) {
41223
41229
  console.log(t.bold(`
41224
- ${sym.arrow} ${import_path11.default.basename(target)}`));
41230
+ ${sym.arrow} ${formatTargetLabel(target)}`));
41225
41231
  const count = await executeSync(repoRoot, target, changeSet, syncMode, "sync", dryRun, void 0, {
41226
41232
  skipMcp: noMcp,
41227
41233
  force
@@ -41315,7 +41321,7 @@ function createInstallCommand() {
41315
41321
  }
41316
41322
  const diffTasks = new Listr(
41317
41323
  targets.map((target) => ({
41318
- title: import_path11.default.basename(target),
41324
+ title: formatTargetLabel(target),
41319
41325
  task: async (listCtx, task) => {
41320
41326
  try {
41321
41327
  let changeSet = await calculateDiff(repoRoot, target, prune);
@@ -41332,13 +41338,13 @@ function createInstallCommand() {
41332
41338
  (sum, c) => sum + c.missing.length + c.outdated.length + c.drifted.length,
41333
41339
  0
41334
41340
  );
41335
- task.title = `${import_path11.default.basename(target)}${t.muted(` \u2014 ${totalChanges} change${totalChanges !== 1 ? "s" : ""}`)}`;
41341
+ task.title = `${formatTargetLabel(target)}${t.muted(` \u2014 ${totalChanges} change${totalChanges !== 1 ? "s" : ""}`)}`;
41336
41342
  if (totalChanges > 0) {
41337
41343
  listCtx.allChanges.push({ target, changeSet, totalChanges, skippedDrifted: [] });
41338
41344
  }
41339
41345
  } catch (err) {
41340
41346
  if (err instanceof PruneModeReadError) {
41341
- task.title = `${import_path11.default.basename(target)} ${kleur_default.red("(skipped \u2014 cannot read in prune mode)")}`;
41347
+ task.title = `${formatTargetLabel(target)} ${kleur_default.red("(skipped \u2014 cannot read in prune mode)")}`;
41342
41348
  } else {
41343
41349
  throw err;
41344
41350
  }
@@ -41360,7 +41366,7 @@ function createInstallCommand() {
41360
41366
  };
41361
41367
  for (const target of targets) {
41362
41368
  console.log(t.bold(`
41363
- ${sym.arrow} ${import_path11.default.basename(target)}`));
41369
+ ${sym.arrow} ${formatTargetLabel(target)}`));
41364
41370
  await executeSync(repoRoot, target, emptyChangeSet, syncMode, "sync", false);
41365
41371
  }
41366
41372
  }
@@ -41389,7 +41395,7 @@ function createInstallCommand() {
41389
41395
  let totalCount = 0;
41390
41396
  for (const { target, changeSet, skippedDrifted } of allChanges) {
41391
41397
  console.log(t.bold(`
41392
- ${sym.arrow} ${import_path11.default.basename(target)}`));
41398
+ ${sym.arrow} ${formatTargetLabel(target)}`));
41393
41399
  const count = await executeSync(repoRoot, target, changeSet, syncMode, syncType, dryRun);
41394
41400
  totalCount += count;
41395
41401
  for (const [category, cat] of Object.entries(changeSet)) {
@@ -55230,6 +55236,12 @@ function getManifestPath(projectDir) {
55230
55236
  // src/commands/status.ts
55231
55237
  var import_fs_extra12 = __toESM(require_lib2(), 1);
55232
55238
  var import_path13 = __toESM(require("path"), 1);
55239
+ function formatTargetLabel2(target) {
55240
+ const normalized = target.replace(/\\/g, "/").toLowerCase();
55241
+ if (normalized.endsWith("/.agents/skills") || normalized.includes("/.agents/skills/")) return "~/.agents/skills";
55242
+ if (normalized.endsWith("/.claude") || normalized.includes("/.claude/")) return "~/.claude";
55243
+ return import_path13.default.basename(target);
55244
+ }
55233
55245
  function formatRelativeTime(timestamp) {
55234
55246
  const now = Date.now();
55235
55247
  const diff = now - timestamp;
@@ -55270,7 +55282,7 @@ function createStatusCommand() {
55270
55282
  (sum, c) => sum + c.missing.length + c.outdated.length + c.drifted.length,
55271
55283
  0
55272
55284
  );
55273
- results.push({ path: target, name: import_path13.default.basename(target), lastSync, changes: changeSet, totalChanges });
55285
+ results.push({ path: target, name: formatTargetLabel2(target), lastSync, changes: changeSet, totalChanges });
55274
55286
  }
55275
55287
  if (json2) {
55276
55288
  console.log(JSON.stringify({ targets: results }, null, 2));
@@ -55380,6 +55392,32 @@ async function readSkillsFromDir(dir) {
55380
55392
  }
55381
55393
  return skills;
55382
55394
  }
55395
+ async function readProjectSkillsFromDir(dir) {
55396
+ if (!await import_fs_extra13.default.pathExists(dir)) return [];
55397
+ const entries = await import_fs_extra13.default.readdir(dir);
55398
+ const skills = [];
55399
+ for (const name of entries.sort()) {
55400
+ const readme = import_path14.default.join(dir, name, "README.md");
55401
+ if (!await import_fs_extra13.default.pathExists(readme)) continue;
55402
+ const content = await import_fs_extra13.default.readFile(readme, "utf8");
55403
+ const descLine = content.split("\n").find((line) => {
55404
+ const trimmed = line.trim();
55405
+ return Boolean(trimmed) && !trimmed.startsWith("#") && !trimmed.startsWith("[") && !trimmed.startsWith("<");
55406
+ }) || "";
55407
+ skills.push({ name, desc: descLine.replace(/[*_`]/g, "").trim() });
55408
+ }
55409
+ return skills;
55410
+ }
55411
+ function resolvePkgRootFallback() {
55412
+ const candidates = [
55413
+ import_path14.default.resolve(__dirname, "../.."),
55414
+ import_path14.default.resolve(__dirname, "../../..")
55415
+ ];
55416
+ const match = candidates.find(
55417
+ (candidate) => import_fs_extra13.default.existsSync(import_path14.default.join(candidate, "skills")) || import_fs_extra13.default.existsSync(import_path14.default.join(candidate, "project-skills"))
55418
+ );
55419
+ return match || null;
55420
+ }
55383
55421
  function col(s, width) {
55384
55422
  return s.length >= width ? s.slice(0, width - 1) + "\u2026" : s.padEnd(width);
55385
55423
  }
@@ -55391,8 +55429,11 @@ function createHelpCommand() {
55391
55429
  } catch {
55392
55430
  repoRoot = "";
55393
55431
  }
55394
- const skills = repoRoot ? await readSkillsFromDir(import_path14.default.join(repoRoot, "skills")) : [];
55395
- const projectSkills = repoRoot ? await readSkillsFromDir(import_path14.default.join(repoRoot, "project-skills")) : [];
55432
+ const pkgRoot = resolvePkgRootFallback();
55433
+ const skillsRoot = repoRoot || pkgRoot || "";
55434
+ const projectSkillsRoot = repoRoot || pkgRoot || "";
55435
+ const skills = skillsRoot ? await readSkillsFromDir(import_path14.default.join(skillsRoot, "skills")) : [];
55436
+ const projectSkills = projectSkillsRoot ? await readProjectSkillsFromDir(import_path14.default.join(projectSkillsRoot, "project-skills")) : [];
55396
55437
  const W = 80;
55397
55438
  const hr = kleur_default.dim("-".repeat(W));
55398
55439
  const section = (title) => `
@@ -55448,11 +55489,12 @@ ${hr}`;
55448
55489
  (s) => ` ${kleur_default.white(col(s.name, 30))}${kleur_default.dim(s.desc)}`
55449
55490
  ).join("\n");
55450
55491
  const psSection = [
55451
- section("PROJECT SKILLS"),
55492
+ section("PROJECT SKILLS + HOOKS"),
55452
55493
  "",
55453
- projectSkills.length ? psRows : kleur_default.dim(" (none found)"),
55494
+ projectSkills.length ? psRows : kleur_default.dim(" (none found in package)"),
55454
55495
  "",
55455
- ` ${kleur_default.dim("Install: xtrm install project <name> | xtrm install project list")}`
55496
+ ` ${kleur_default.dim("Install: xtrm install project <name> | xtrm install project list")}`,
55497
+ ` ${kleur_default.dim("Each project skill can install .claude/skills plus project hooks/settings.")}`
55456
55498
  ].join("\n");
55457
55499
  const otherSection = [
55458
55500
  section("OTHER COMMANDS"),