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 +37 -30
- package/cli/dist/index.cjs +58 -16
- package/cli/dist/index.cjs.map +1 -1
- package/cli/package.json +1 -1
- package/package.json +1 -1
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
|
-
**
|
|
202
|
-
- Trigger: PreToolUse (
|
|
203
|
-
- Purpose:
|
|
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
|
-
**
|
|
210
|
-
- Trigger:
|
|
211
|
-
- Purpose:
|
|
209
|
+
**agent_context.py**
|
|
210
|
+
- Trigger: Support module used by Python hooks
|
|
211
|
+
- Purpose: Shared hook input/output helper
|
|
212
212
|
|
|
213
|
-
**
|
|
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
|
|
305
|
-
|
|
|
306
|
-
| `
|
|
307
|
-
| `status`
|
|
308
|
-
| `reset`
|
|
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
|
-
- **
|
|
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/
|
|
399
|
-
cd
|
|
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 (
|
|
465
|
+
### MCP Servers (Unified CLI Sync)
|
|
461
466
|
|
|
462
|
-
MCP servers are configured from canonical sources
|
|
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
|
|
488
|
-
- Uses official `mcp add
|
|
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
|
-
**
|
|
498
|
-
-
|
|
499
|
-
-
|
|
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
|
-
**
|
|
502
|
-
-
|
|
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)
|
|
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/
|
|
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
|
-
|
|
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/ #
|
|
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
|
-
│ └──
|
|
718
|
+
│ └── main-guard.mjs # Protected-branch edit guard
|
|
712
719
|
│
|
|
713
720
|
├── config/ # Canonical configuration
|
|
714
721
|
│ ├── mcp_servers.json # Core MCP servers
|
package/cli/dist/index.cjs
CHANGED
|
@@ -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(
|
|
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:
|
|
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 = `${
|
|
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 = `${
|
|
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} ${
|
|
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:
|
|
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 = `${
|
|
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 = `${
|
|
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} ${
|
|
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} ${
|
|
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:
|
|
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
|
|
55395
|
-
const
|
|
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"),
|