pruff-skills 1.14.0 → 2.0.0

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.
Files changed (3) hide show
  1. package/README.md +5 -6
  2. package/dist/cli.mjs +403 -588
  3. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -799,7 +799,7 @@ const CLONE_TIMEOUT_MS = (() => {
799
799
  const parsed = Number.parseInt(raw, 10);
800
800
  return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CLONE_TIMEOUT_MS;
801
801
  })();
802
- const execFileAsync$4 = promisify(execFile);
802
+ const execFileAsync$3 = promisify(execFile);
803
803
  var GitCloneError = class extends Error {
804
804
  url;
805
805
  isTimeout;
@@ -877,7 +877,7 @@ async function resetTempDir(dir) {
877
877
  async function tryGhClone(repo, tempDir, ref) {
878
878
  let cloneTarget = repo.slug;
879
879
  try {
880
- const { stdout, stderr } = await execFileAsync$4("gh", [
880
+ const { stdout, stderr } = await execFileAsync$3("gh", [
881
881
  "auth",
882
882
  "status",
883
883
  "-h",
@@ -894,7 +894,7 @@ async function tryGhClone(repo, tempDir, ref) {
894
894
  } catch {
895
895
  return false;
896
896
  }
897
- await execFileAsync$4("gh", [
897
+ await execFileAsync$3("gh", [
898
898
  "repo",
899
899
  "clone",
900
900
  cloneTarget,
@@ -1322,16 +1322,16 @@ function filterSkills(skills, inputNames) {
1322
1322
  return normalizedInputs.some((input) => input === name || input === displayName);
1323
1323
  });
1324
1324
  }
1325
- const home$1 = homedir();
1326
- const configHome = xdgConfig ?? join(home$1, ".config");
1327
- const codexHome = process.env.CODEX_HOME?.trim() || join(home$1, ".codex");
1328
- const claudeHome$2 = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home$1, ".claude");
1329
- const vibeHome = process.env.VIBE_HOME?.trim() || join(home$1, ".vibe");
1330
- const hermesHome = process.env.HERMES_HOME?.trim() || join(home$1, ".hermes");
1331
- const autohandHome = process.env.AUTOHAND_HOME?.trim() || join(home$1, ".autohand");
1325
+ const home = homedir();
1326
+ const configHome = xdgConfig ?? join(home, ".config");
1327
+ const codexHome = process.env.CODEX_HOME?.trim() || join(home, ".codex");
1328
+ const claudeHome$1 = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude");
1329
+ const vibeHome = process.env.VIBE_HOME?.trim() || join(home, ".vibe");
1330
+ const hermesHome = process.env.HERMES_HOME?.trim() || join(home, ".hermes");
1331
+ const autohandHome = process.env.AUTOHAND_HOME?.trim() || join(home, ".autohand");
1332
1332
  const zedAppDataHome = process.env.APPDATA?.trim();
1333
1333
  const zedFlatpakConfigHome = process.env.FLATPAK_XDG_CONFIG_HOME?.trim();
1334
- function getOpenClawGlobalSkillsDir(homeDir = home$1, pathExists = existsSync) {
1334
+ function getOpenClawGlobalSkillsDir(homeDir = home, pathExists = existsSync) {
1335
1335
  if (pathExists(join(homeDir, ".openclaw"))) return join(homeDir, ".openclaw/skills");
1336
1336
  if (pathExists(join(homeDir, ".clawdbot"))) return join(homeDir, ".clawdbot/skills");
1337
1337
  if (pathExists(join(homeDir, ".moltbot"))) return join(homeDir, ".moltbot/skills");
@@ -1342,9 +1342,9 @@ const agents = {
1342
1342
  name: "aider-desk",
1343
1343
  displayName: "AiderDesk",
1344
1344
  skillsDir: ".aider-desk/skills",
1345
- globalSkillsDir: join(home$1, ".aider-desk/skills"),
1345
+ globalSkillsDir: join(home, ".aider-desk/skills"),
1346
1346
  detectInstalled: async () => {
1347
- return existsSync(join(home$1, ".aider-desk"));
1347
+ return existsSync(join(home, ".aider-desk"));
1348
1348
  }
1349
1349
  },
1350
1350
  amp: {
@@ -1360,27 +1360,27 @@ const agents = {
1360
1360
  name: "antigravity",
1361
1361
  displayName: "Antigravity",
1362
1362
  skillsDir: ".agents/skills",
1363
- globalSkillsDir: join(home$1, ".gemini/antigravity/skills"),
1363
+ globalSkillsDir: join(home, ".gemini/antigravity/skills"),
1364
1364
  detectInstalled: async () => {
1365
- return existsSync(join(home$1, ".gemini/antigravity"));
1365
+ return existsSync(join(home, ".gemini/antigravity"));
1366
1366
  }
1367
1367
  },
1368
1368
  "antigravity-cli": {
1369
1369
  name: "antigravity-cli",
1370
1370
  displayName: "Antigravity CLI",
1371
1371
  skillsDir: ".agents/skills",
1372
- globalSkillsDir: join(home$1, ".gemini/antigravity-cli/skills"),
1372
+ globalSkillsDir: join(home, ".gemini/antigravity-cli/skills"),
1373
1373
  detectInstalled: async () => {
1374
- return existsSync(join(home$1, ".gemini/antigravity-cli"));
1374
+ return existsSync(join(home, ".gemini/antigravity-cli"));
1375
1375
  }
1376
1376
  },
1377
1377
  astrbot: {
1378
1378
  name: "astrbot",
1379
1379
  displayName: "AstrBot",
1380
1380
  skillsDir: "data/skills",
1381
- globalSkillsDir: join(home$1, ".astrbot/data/skills"),
1381
+ globalSkillsDir: join(home, ".astrbot/data/skills"),
1382
1382
  detectInstalled: async () => {
1383
- return existsSync(join(process.cwd(), "data/skills")) || existsSync(join(home$1, ".astrbot"));
1383
+ return existsSync(join(process.cwd(), "data/skills")) || existsSync(join(home, ".astrbot"));
1384
1384
  }
1385
1385
  },
1386
1386
  "autohand-code": {
@@ -1396,27 +1396,27 @@ const agents = {
1396
1396
  name: "augment",
1397
1397
  displayName: "Augment",
1398
1398
  skillsDir: ".augment/skills",
1399
- globalSkillsDir: join(home$1, ".augment/skills"),
1399
+ globalSkillsDir: join(home, ".augment/skills"),
1400
1400
  detectInstalled: async () => {
1401
- return existsSync(join(home$1, ".augment"));
1401
+ return existsSync(join(home, ".augment"));
1402
1402
  }
1403
1403
  },
1404
1404
  bob: {
1405
1405
  name: "bob",
1406
1406
  displayName: "IBM Bob",
1407
1407
  skillsDir: ".bob/skills",
1408
- globalSkillsDir: join(home$1, ".bob/skills"),
1408
+ globalSkillsDir: join(home, ".bob/skills"),
1409
1409
  detectInstalled: async () => {
1410
- return existsSync(join(home$1, ".bob"));
1410
+ return existsSync(join(home, ".bob"));
1411
1411
  }
1412
1412
  },
1413
1413
  "claude-code": {
1414
1414
  name: "claude-code",
1415
1415
  displayName: "Claude Code",
1416
1416
  skillsDir: ".claude/skills",
1417
- globalSkillsDir: join(claudeHome$2, "skills"),
1417
+ globalSkillsDir: join(claudeHome$1, "skills"),
1418
1418
  detectInstalled: async () => {
1419
- return existsSync(claudeHome$2);
1419
+ return existsSync(claudeHome$1);
1420
1420
  }
1421
1421
  },
1422
1422
  openclaw: {
@@ -1425,52 +1425,52 @@ const agents = {
1425
1425
  skillsDir: "skills",
1426
1426
  globalSkillsDir: getOpenClawGlobalSkillsDir(),
1427
1427
  detectInstalled: async () => {
1428
- return existsSync(join(home$1, ".openclaw")) || existsSync(join(home$1, ".clawdbot")) || existsSync(join(home$1, ".moltbot"));
1428
+ return existsSync(join(home, ".openclaw")) || existsSync(join(home, ".clawdbot")) || existsSync(join(home, ".moltbot"));
1429
1429
  }
1430
1430
  },
1431
1431
  cline: {
1432
1432
  name: "cline",
1433
1433
  displayName: "Cline",
1434
1434
  skillsDir: ".agents/skills",
1435
- globalSkillsDir: join(home$1, ".agents", "skills"),
1435
+ globalSkillsDir: join(home, ".agents", "skills"),
1436
1436
  detectInstalled: async () => {
1437
- return existsSync(join(home$1, ".cline"));
1437
+ return existsSync(join(home, ".cline"));
1438
1438
  }
1439
1439
  },
1440
1440
  "codearts-agent": {
1441
1441
  name: "codearts-agent",
1442
1442
  displayName: "CodeArts Agent",
1443
1443
  skillsDir: ".codeartsdoer/skills",
1444
- globalSkillsDir: join(home$1, ".codeartsdoer/skills"),
1444
+ globalSkillsDir: join(home, ".codeartsdoer/skills"),
1445
1445
  detectInstalled: async () => {
1446
- return existsSync(join(home$1, ".codeartsdoer"));
1446
+ return existsSync(join(home, ".codeartsdoer"));
1447
1447
  }
1448
1448
  },
1449
1449
  codebuddy: {
1450
1450
  name: "codebuddy",
1451
1451
  displayName: "CodeBuddy",
1452
1452
  skillsDir: ".codebuddy/skills",
1453
- globalSkillsDir: join(home$1, ".codebuddy/skills"),
1453
+ globalSkillsDir: join(home, ".codebuddy/skills"),
1454
1454
  detectInstalled: async () => {
1455
- return existsSync(join(process.cwd(), ".codebuddy")) || existsSync(join(home$1, ".codebuddy"));
1455
+ return existsSync(join(process.cwd(), ".codebuddy")) || existsSync(join(home, ".codebuddy"));
1456
1456
  }
1457
1457
  },
1458
1458
  codemaker: {
1459
1459
  name: "codemaker",
1460
1460
  displayName: "Codemaker",
1461
1461
  skillsDir: ".codemaker/skills",
1462
- globalSkillsDir: join(home$1, ".codemaker/skills"),
1462
+ globalSkillsDir: join(home, ".codemaker/skills"),
1463
1463
  detectInstalled: async () => {
1464
- return existsSync(join(home$1, ".codemaker"));
1464
+ return existsSync(join(home, ".codemaker"));
1465
1465
  }
1466
1466
  },
1467
1467
  codestudio: {
1468
1468
  name: "codestudio",
1469
1469
  displayName: "Code Studio",
1470
1470
  skillsDir: ".codestudio/skills",
1471
- globalSkillsDir: join(home$1, ".codestudio/skills"),
1471
+ globalSkillsDir: join(home, ".codestudio/skills"),
1472
1472
  detectInstalled: async () => {
1473
- return existsSync(join(home$1, ".codestudio"));
1473
+ return existsSync(join(home, ".codestudio"));
1474
1474
  }
1475
1475
  },
1476
1476
  codex: {
@@ -1486,54 +1486,54 @@ const agents = {
1486
1486
  name: "command-code",
1487
1487
  displayName: "Command Code",
1488
1488
  skillsDir: ".commandcode/skills",
1489
- globalSkillsDir: join(home$1, ".commandcode/skills"),
1489
+ globalSkillsDir: join(home, ".commandcode/skills"),
1490
1490
  detectInstalled: async () => {
1491
- return existsSync(join(home$1, ".commandcode"));
1491
+ return existsSync(join(home, ".commandcode"));
1492
1492
  }
1493
1493
  },
1494
1494
  continue: {
1495
1495
  name: "continue",
1496
1496
  displayName: "Continue",
1497
1497
  skillsDir: ".continue/skills",
1498
- globalSkillsDir: join(home$1, ".continue/skills"),
1498
+ globalSkillsDir: join(home, ".continue/skills"),
1499
1499
  detectInstalled: async () => {
1500
- return existsSync(join(process.cwd(), ".continue")) || existsSync(join(home$1, ".continue"));
1500
+ return existsSync(join(process.cwd(), ".continue")) || existsSync(join(home, ".continue"));
1501
1501
  }
1502
1502
  },
1503
1503
  cortex: {
1504
1504
  name: "cortex",
1505
1505
  displayName: "Cortex Code",
1506
1506
  skillsDir: ".cortex/skills",
1507
- globalSkillsDir: join(home$1, ".snowflake/cortex/skills"),
1507
+ globalSkillsDir: join(home, ".snowflake/cortex/skills"),
1508
1508
  detectInstalled: async () => {
1509
- return existsSync(join(home$1, ".snowflake/cortex"));
1509
+ return existsSync(join(home, ".snowflake/cortex"));
1510
1510
  }
1511
1511
  },
1512
1512
  crush: {
1513
1513
  name: "crush",
1514
1514
  displayName: "Crush",
1515
1515
  skillsDir: ".crush/skills",
1516
- globalSkillsDir: join(home$1, ".config/crush/skills"),
1516
+ globalSkillsDir: join(home, ".config/crush/skills"),
1517
1517
  detectInstalled: async () => {
1518
- return existsSync(join(home$1, ".config/crush"));
1518
+ return existsSync(join(home, ".config/crush"));
1519
1519
  }
1520
1520
  },
1521
1521
  cursor: {
1522
1522
  name: "cursor",
1523
1523
  displayName: "Cursor",
1524
1524
  skillsDir: ".agents/skills",
1525
- globalSkillsDir: join(home$1, ".cursor/skills"),
1525
+ globalSkillsDir: join(home, ".cursor/skills"),
1526
1526
  detectInstalled: async () => {
1527
- return existsSync(join(home$1, ".cursor"));
1527
+ return existsSync(join(home, ".cursor"));
1528
1528
  }
1529
1529
  },
1530
1530
  deepagents: {
1531
1531
  name: "deepagents",
1532
1532
  displayName: "Deep Agents",
1533
1533
  skillsDir: ".agents/skills",
1534
- globalSkillsDir: join(home$1, ".deepagents/agent/skills"),
1534
+ globalSkillsDir: join(home, ".deepagents/agent/skills"),
1535
1535
  detectInstalled: async () => {
1536
- return existsSync(join(home$1, ".deepagents"));
1536
+ return existsSync(join(home, ".deepagents"));
1537
1537
  }
1538
1538
  },
1539
1539
  devin: {
@@ -1549,56 +1549,56 @@ const agents = {
1549
1549
  name: "dexto",
1550
1550
  displayName: "Dexto",
1551
1551
  skillsDir: ".agents/skills",
1552
- globalSkillsDir: join(home$1, ".agents/skills"),
1552
+ globalSkillsDir: join(home, ".agents/skills"),
1553
1553
  showInUniversalPrompt: false,
1554
1554
  detectInstalled: async () => {
1555
- return existsSync(join(home$1, ".dexto"));
1555
+ return existsSync(join(home, ".dexto"));
1556
1556
  }
1557
1557
  },
1558
1558
  droid: {
1559
1559
  name: "droid",
1560
1560
  displayName: "Droid",
1561
1561
  skillsDir: ".factory/skills",
1562
- globalSkillsDir: join(home$1, ".factory/skills"),
1562
+ globalSkillsDir: join(home, ".factory/skills"),
1563
1563
  detectInstalled: async () => {
1564
- return existsSync(join(home$1, ".factory"));
1564
+ return existsSync(join(home, ".factory"));
1565
1565
  }
1566
1566
  },
1567
1567
  firebender: {
1568
1568
  name: "firebender",
1569
1569
  displayName: "Firebender",
1570
1570
  skillsDir: ".agents/skills",
1571
- globalSkillsDir: join(home$1, ".firebender/skills"),
1571
+ globalSkillsDir: join(home, ".firebender/skills"),
1572
1572
  showInUniversalPrompt: false,
1573
1573
  detectInstalled: async () => {
1574
- return existsSync(join(home$1, ".firebender"));
1574
+ return existsSync(join(home, ".firebender"));
1575
1575
  }
1576
1576
  },
1577
1577
  forgecode: {
1578
1578
  name: "forgecode",
1579
1579
  displayName: "ForgeCode",
1580
1580
  skillsDir: ".forge/skills",
1581
- globalSkillsDir: join(home$1, ".forge/skills"),
1581
+ globalSkillsDir: join(home, ".forge/skills"),
1582
1582
  detectInstalled: async () => {
1583
- return existsSync(join(home$1, ".forge"));
1583
+ return existsSync(join(home, ".forge"));
1584
1584
  }
1585
1585
  },
1586
1586
  "gemini-cli": {
1587
1587
  name: "gemini-cli",
1588
1588
  displayName: "Gemini CLI",
1589
1589
  skillsDir: ".agents/skills",
1590
- globalSkillsDir: join(home$1, ".gemini/skills"),
1590
+ globalSkillsDir: join(home, ".gemini/skills"),
1591
1591
  detectInstalled: async () => {
1592
- return existsSync(join(home$1, ".gemini"));
1592
+ return existsSync(join(home, ".gemini"));
1593
1593
  }
1594
1594
  },
1595
1595
  "github-copilot": {
1596
1596
  name: "github-copilot",
1597
1597
  displayName: "GitHub Copilot",
1598
1598
  skillsDir: ".agents/skills",
1599
- globalSkillsDir: join(home$1, ".copilot/skills"),
1599
+ globalSkillsDir: join(home, ".copilot/skills"),
1600
1600
  detectInstalled: async () => {
1601
- return existsSync(join(home$1, ".copilot"));
1601
+ return existsSync(join(home, ".copilot"));
1602
1602
  }
1603
1603
  },
1604
1604
  goose: {
@@ -1623,100 +1623,100 @@ const agents = {
1623
1623
  name: "inference-sh",
1624
1624
  displayName: "inference.sh",
1625
1625
  skillsDir: ".inferencesh/skills",
1626
- globalSkillsDir: join(home$1, ".inferencesh/skills"),
1626
+ globalSkillsDir: join(home, ".inferencesh/skills"),
1627
1627
  detectInstalled: async () => {
1628
- return existsSync(join(home$1, ".inferencesh"));
1628
+ return existsSync(join(home, ".inferencesh"));
1629
1629
  }
1630
1630
  },
1631
1631
  jazz: {
1632
1632
  name: "jazz",
1633
1633
  displayName: "Jazz",
1634
1634
  skillsDir: ".jazz/skills",
1635
- globalSkillsDir: join(home$1, ".jazz/skills"),
1635
+ globalSkillsDir: join(home, ".jazz/skills"),
1636
1636
  detectInstalled: async () => {
1637
- return existsSync(join(home$1, ".jazz")) || existsSync(join(process.cwd(), ".jazz"));
1637
+ return existsSync(join(home, ".jazz")) || existsSync(join(process.cwd(), ".jazz"));
1638
1638
  }
1639
1639
  },
1640
1640
  junie: {
1641
1641
  name: "junie",
1642
1642
  displayName: "Junie",
1643
1643
  skillsDir: ".junie/skills",
1644
- globalSkillsDir: join(home$1, ".junie/skills"),
1644
+ globalSkillsDir: join(home, ".junie/skills"),
1645
1645
  detectInstalled: async () => {
1646
- return existsSync(join(home$1, ".junie"));
1646
+ return existsSync(join(home, ".junie"));
1647
1647
  }
1648
1648
  },
1649
1649
  "iflow-cli": {
1650
1650
  name: "iflow-cli",
1651
1651
  displayName: "iFlow CLI",
1652
1652
  skillsDir: ".iflow/skills",
1653
- globalSkillsDir: join(home$1, ".iflow/skills"),
1653
+ globalSkillsDir: join(home, ".iflow/skills"),
1654
1654
  detectInstalled: async () => {
1655
- return existsSync(join(home$1, ".iflow"));
1655
+ return existsSync(join(home, ".iflow"));
1656
1656
  }
1657
1657
  },
1658
1658
  kilo: {
1659
1659
  name: "kilo",
1660
1660
  displayName: "Kilo Code",
1661
1661
  skillsDir: ".kilocode/skills",
1662
- globalSkillsDir: join(home$1, ".kilocode/skills"),
1662
+ globalSkillsDir: join(home, ".kilocode/skills"),
1663
1663
  detectInstalled: async () => {
1664
- return existsSync(join(home$1, ".kilocode"));
1664
+ return existsSync(join(home, ".kilocode"));
1665
1665
  }
1666
1666
  },
1667
1667
  "kimi-code-cli": {
1668
1668
  name: "kimi-code-cli",
1669
1669
  displayName: "Kimi Code CLI",
1670
1670
  skillsDir: ".agents/skills",
1671
- globalSkillsDir: join(home$1, ".agents/skills"),
1671
+ globalSkillsDir: join(home, ".agents/skills"),
1672
1672
  detectInstalled: async () => {
1673
- return existsSync(join(home$1, ".kimi-code")) || existsSync(join(home$1, ".kimi"));
1673
+ return existsSync(join(home, ".kimi-code")) || existsSync(join(home, ".kimi"));
1674
1674
  }
1675
1675
  },
1676
1676
  "kiro-cli": {
1677
1677
  name: "kiro-cli",
1678
1678
  displayName: "Kiro CLI",
1679
1679
  skillsDir: ".kiro/skills",
1680
- globalSkillsDir: join(home$1, ".kiro/skills"),
1680
+ globalSkillsDir: join(home, ".kiro/skills"),
1681
1681
  detectInstalled: async () => {
1682
- return existsSync(join(home$1, ".kiro"));
1682
+ return existsSync(join(home, ".kiro"));
1683
1683
  }
1684
1684
  },
1685
1685
  kode: {
1686
1686
  name: "kode",
1687
1687
  displayName: "Kode",
1688
1688
  skillsDir: ".kode/skills",
1689
- globalSkillsDir: join(home$1, ".kode/skills"),
1689
+ globalSkillsDir: join(home, ".kode/skills"),
1690
1690
  detectInstalled: async () => {
1691
- return existsSync(join(home$1, ".kode"));
1691
+ return existsSync(join(home, ".kode"));
1692
1692
  }
1693
1693
  },
1694
1694
  lingma: {
1695
1695
  name: "lingma",
1696
1696
  displayName: "Lingma",
1697
1697
  skillsDir: ".lingma/skills",
1698
- globalSkillsDir: join(home$1, ".lingma/skills"),
1698
+ globalSkillsDir: join(home, ".lingma/skills"),
1699
1699
  detectInstalled: async () => {
1700
- return existsSync(join(home$1, ".lingma"));
1700
+ return existsSync(join(home, ".lingma"));
1701
1701
  }
1702
1702
  },
1703
1703
  loaf: {
1704
1704
  name: "loaf",
1705
1705
  displayName: "Loaf",
1706
1706
  skillsDir: ".agents/skills",
1707
- globalSkillsDir: join(home$1, ".agents/skills"),
1707
+ globalSkillsDir: join(home, ".agents/skills"),
1708
1708
  showInUniversalPrompt: false,
1709
1709
  detectInstalled: async () => {
1710
- return existsSync(join(home$1, ".loaf"));
1710
+ return existsSync(join(home, ".loaf"));
1711
1711
  }
1712
1712
  },
1713
1713
  mcpjam: {
1714
1714
  name: "mcpjam",
1715
1715
  displayName: "MCPJam",
1716
1716
  skillsDir: ".mcpjam/skills",
1717
- globalSkillsDir: join(home$1, ".mcpjam/skills"),
1717
+ globalSkillsDir: join(home, ".mcpjam/skills"),
1718
1718
  detectInstalled: async () => {
1719
- return existsSync(join(home$1, ".mcpjam"));
1719
+ return existsSync(join(home, ".mcpjam"));
1720
1720
  }
1721
1721
  },
1722
1722
  "mistral-vibe": {
@@ -1732,18 +1732,18 @@ const agents = {
1732
1732
  name: "moxby",
1733
1733
  displayName: "Moxby",
1734
1734
  skillsDir: ".moxby/skills",
1735
- globalSkillsDir: join(home$1, ".moxby/skills"),
1735
+ globalSkillsDir: join(home, ".moxby/skills"),
1736
1736
  detectInstalled: async () => {
1737
- return existsSync(join(home$1, ".moxby"));
1737
+ return existsSync(join(home, ".moxby"));
1738
1738
  }
1739
1739
  },
1740
1740
  mux: {
1741
1741
  name: "mux",
1742
1742
  displayName: "Mux",
1743
1743
  skillsDir: ".mux/skills",
1744
- globalSkillsDir: join(home$1, ".mux/skills"),
1744
+ globalSkillsDir: join(home, ".mux/skills"),
1745
1745
  detectInstalled: async () => {
1746
- return existsSync(join(home$1, ".mux"));
1746
+ return existsSync(join(home, ".mux"));
1747
1747
  }
1748
1748
  },
1749
1749
  opencode: {
@@ -1759,54 +1759,54 @@ const agents = {
1759
1759
  name: "openhands",
1760
1760
  displayName: "OpenHands",
1761
1761
  skillsDir: ".openhands/skills",
1762
- globalSkillsDir: join(home$1, ".openhands/skills"),
1762
+ globalSkillsDir: join(home, ".openhands/skills"),
1763
1763
  detectInstalled: async () => {
1764
- return existsSync(join(home$1, ".openhands"));
1764
+ return existsSync(join(home, ".openhands"));
1765
1765
  }
1766
1766
  },
1767
1767
  ona: {
1768
1768
  name: "ona",
1769
1769
  displayName: "Ona",
1770
1770
  skillsDir: ".ona/skills",
1771
- globalSkillsDir: join(home$1, ".ona/skills"),
1771
+ globalSkillsDir: join(home, ".ona/skills"),
1772
1772
  detectInstalled: async () => {
1773
- return existsSync(join(home$1, ".ona"));
1773
+ return existsSync(join(home, ".ona"));
1774
1774
  }
1775
1775
  },
1776
1776
  pi: {
1777
1777
  name: "pi",
1778
1778
  displayName: "Pi",
1779
1779
  skillsDir: ".pi/skills",
1780
- globalSkillsDir: join(home$1, ".pi/agent/skills"),
1780
+ globalSkillsDir: join(home, ".pi/agent/skills"),
1781
1781
  detectInstalled: async () => {
1782
- return existsSync(join(home$1, ".pi/agent"));
1782
+ return existsSync(join(home, ".pi/agent"));
1783
1783
  }
1784
1784
  },
1785
1785
  qoder: {
1786
1786
  name: "qoder",
1787
1787
  displayName: "Qoder",
1788
1788
  skillsDir: ".qoder/skills",
1789
- globalSkillsDir: join(home$1, ".qoder/skills"),
1789
+ globalSkillsDir: join(home, ".qoder/skills"),
1790
1790
  detectInstalled: async () => {
1791
- return existsSync(join(home$1, ".qoder"));
1791
+ return existsSync(join(home, ".qoder"));
1792
1792
  }
1793
1793
  },
1794
1794
  "qoder-cn": {
1795
1795
  name: "qoder-cn",
1796
1796
  displayName: "Qoder CN",
1797
1797
  skillsDir: ".qoder/skills",
1798
- globalSkillsDir: join(home$1, ".qoder-cn/skills"),
1798
+ globalSkillsDir: join(home, ".qoder-cn/skills"),
1799
1799
  detectInstalled: async () => {
1800
- return existsSync(join(home$1, ".qoder-cn"));
1800
+ return existsSync(join(home, ".qoder-cn"));
1801
1801
  }
1802
1802
  },
1803
1803
  "qwen-code": {
1804
1804
  name: "qwen-code",
1805
1805
  displayName: "Qwen Code",
1806
1806
  skillsDir: ".qwen/skills",
1807
- globalSkillsDir: join(home$1, ".qwen/skills"),
1807
+ globalSkillsDir: join(home, ".qwen/skills"),
1808
1808
  detectInstalled: async () => {
1809
- return existsSync(join(home$1, ".qwen"));
1809
+ return existsSync(join(home, ".qwen"));
1810
1810
  }
1811
1811
  },
1812
1812
  replit: {
@@ -1823,97 +1823,97 @@ const agents = {
1823
1823
  name: "reasonix",
1824
1824
  displayName: "Reasonix",
1825
1825
  skillsDir: ".reasonix/skills",
1826
- globalSkillsDir: join(home$1, ".reasonix/skills"),
1826
+ globalSkillsDir: join(home, ".reasonix/skills"),
1827
1827
  detectInstalled: async () => {
1828
- return existsSync(join(home$1, ".reasonix"));
1828
+ return existsSync(join(home, ".reasonix"));
1829
1829
  }
1830
1830
  },
1831
1831
  rovodev: {
1832
1832
  name: "rovodev",
1833
1833
  displayName: "Rovo Dev",
1834
1834
  skillsDir: ".rovodev/skills",
1835
- globalSkillsDir: join(home$1, ".rovodev/skills"),
1835
+ globalSkillsDir: join(home, ".rovodev/skills"),
1836
1836
  detectInstalled: async () => {
1837
- return existsSync(join(home$1, ".rovodev"));
1837
+ return existsSync(join(home, ".rovodev"));
1838
1838
  }
1839
1839
  },
1840
1840
  roo: {
1841
1841
  name: "roo",
1842
1842
  displayName: "Roo Code",
1843
1843
  skillsDir: ".roo/skills",
1844
- globalSkillsDir: join(home$1, ".roo/skills"),
1844
+ globalSkillsDir: join(home, ".roo/skills"),
1845
1845
  detectInstalled: async () => {
1846
- return existsSync(join(home$1, ".roo"));
1846
+ return existsSync(join(home, ".roo"));
1847
1847
  }
1848
1848
  },
1849
1849
  "tabnine-cli": {
1850
1850
  name: "tabnine-cli",
1851
1851
  displayName: "Tabnine CLI",
1852
1852
  skillsDir: ".tabnine/agent/skills",
1853
- globalSkillsDir: join(home$1, ".tabnine/agent/skills"),
1853
+ globalSkillsDir: join(home, ".tabnine/agent/skills"),
1854
1854
  detectInstalled: async () => {
1855
- return existsSync(join(home$1, ".tabnine"));
1855
+ return existsSync(join(home, ".tabnine"));
1856
1856
  }
1857
1857
  },
1858
1858
  terramind: {
1859
1859
  name: "terramind",
1860
1860
  displayName: "Terramind",
1861
1861
  skillsDir: ".terramind/skills",
1862
- globalSkillsDir: join(home$1, ".terramind/skills"),
1862
+ globalSkillsDir: join(home, ".terramind/skills"),
1863
1863
  detectInstalled: async () => {
1864
- return existsSync(join(home$1, ".terramind"));
1864
+ return existsSync(join(home, ".terramind"));
1865
1865
  }
1866
1866
  },
1867
1867
  tinycloud: {
1868
1868
  name: "tinycloud",
1869
1869
  displayName: "Tinycloud",
1870
1870
  skillsDir: ".tinycloud/skills",
1871
- globalSkillsDir: join(home$1, ".tinycloud/skills"),
1871
+ globalSkillsDir: join(home, ".tinycloud/skills"),
1872
1872
  detectInstalled: async () => {
1873
- return existsSync(join(home$1, ".tinycloud"));
1873
+ return existsSync(join(home, ".tinycloud"));
1874
1874
  }
1875
1875
  },
1876
1876
  trae: {
1877
1877
  name: "trae",
1878
1878
  displayName: "Trae",
1879
1879
  skillsDir: ".trae/skills",
1880
- globalSkillsDir: join(home$1, ".trae/skills"),
1880
+ globalSkillsDir: join(home, ".trae/skills"),
1881
1881
  detectInstalled: async () => {
1882
- return existsSync(join(home$1, ".trae"));
1882
+ return existsSync(join(home, ".trae"));
1883
1883
  }
1884
1884
  },
1885
1885
  "trae-cn": {
1886
1886
  name: "trae-cn",
1887
1887
  displayName: "Trae CN",
1888
1888
  skillsDir: ".trae/skills",
1889
- globalSkillsDir: join(home$1, ".trae-cn/skills"),
1889
+ globalSkillsDir: join(home, ".trae-cn/skills"),
1890
1890
  detectInstalled: async () => {
1891
- return existsSync(join(home$1, ".trae-cn"));
1891
+ return existsSync(join(home, ".trae-cn"));
1892
1892
  }
1893
1893
  },
1894
1894
  warp: {
1895
1895
  name: "warp",
1896
1896
  displayName: "Warp",
1897
1897
  skillsDir: ".agents/skills",
1898
- globalSkillsDir: join(home$1, ".agents/skills"),
1898
+ globalSkillsDir: join(home, ".agents/skills"),
1899
1899
  detectInstalled: async () => {
1900
- return existsSync(join(home$1, ".warp"));
1900
+ return existsSync(join(home, ".warp"));
1901
1901
  }
1902
1902
  },
1903
1903
  windsurf: {
1904
1904
  name: "windsurf",
1905
1905
  displayName: "Windsurf",
1906
1906
  skillsDir: ".windsurf/skills",
1907
- globalSkillsDir: join(home$1, ".codeium/windsurf/skills"),
1907
+ globalSkillsDir: join(home, ".codeium/windsurf/skills"),
1908
1908
  detectInstalled: async () => {
1909
- return existsSync(join(home$1, ".codeium/windsurf"));
1909
+ return existsSync(join(home, ".codeium/windsurf"));
1910
1910
  }
1911
1911
  },
1912
1912
  zed: {
1913
1913
  name: "zed",
1914
1914
  displayName: "Zed",
1915
1915
  skillsDir: ".agents/skills",
1916
- globalSkillsDir: join(home$1, ".agents/skills"),
1916
+ globalSkillsDir: join(home, ".agents/skills"),
1917
1917
  detectInstalled: async () => {
1918
1918
  return existsSync(join(configHome, "zed")) || !!zedAppDataHome && existsSync(join(zedAppDataHome, "Zed")) || !!zedFlatpakConfigHome && existsSync(join(zedFlatpakConfigHome, "zed"));
1919
1919
  }
@@ -1922,36 +1922,36 @@ const agents = {
1922
1922
  name: "zencoder",
1923
1923
  displayName: "Zencoder",
1924
1924
  skillsDir: ".zencoder/skills",
1925
- globalSkillsDir: join(home$1, ".zencoder/skills"),
1925
+ globalSkillsDir: join(home, ".zencoder/skills"),
1926
1926
  detectInstalled: async () => {
1927
- return existsSync(join(home$1, ".zencoder"));
1927
+ return existsSync(join(home, ".zencoder"));
1928
1928
  }
1929
1929
  },
1930
1930
  zenflow: {
1931
1931
  name: "zenflow",
1932
1932
  displayName: "Zenflow",
1933
1933
  skillsDir: ".zencoder/skills",
1934
- globalSkillsDir: join(home$1, ".zencoder/skills"),
1934
+ globalSkillsDir: join(home, ".zencoder/skills"),
1935
1935
  detectInstalled: async () => {
1936
- return existsSync(join(home$1, ".zencoder"));
1936
+ return existsSync(join(home, ".zencoder"));
1937
1937
  }
1938
1938
  },
1939
1939
  neovate: {
1940
1940
  name: "neovate",
1941
1941
  displayName: "Neovate",
1942
1942
  skillsDir: ".neovate/skills",
1943
- globalSkillsDir: join(home$1, ".neovate/skills"),
1943
+ globalSkillsDir: join(home, ".neovate/skills"),
1944
1944
  detectInstalled: async () => {
1945
- return existsSync(join(home$1, ".neovate"));
1945
+ return existsSync(join(home, ".neovate"));
1946
1946
  }
1947
1947
  },
1948
1948
  pochi: {
1949
1949
  name: "pochi",
1950
1950
  displayName: "Pochi",
1951
1951
  skillsDir: ".pochi/skills",
1952
- globalSkillsDir: join(home$1, ".pochi/skills"),
1952
+ globalSkillsDir: join(home, ".pochi/skills"),
1953
1953
  detectInstalled: async () => {
1954
- return existsSync(join(home$1, ".pochi"));
1954
+ return existsSync(join(home, ".pochi"));
1955
1955
  }
1956
1956
  },
1957
1957
  promptscript: {
@@ -1968,9 +1968,9 @@ const agents = {
1968
1968
  name: "adal",
1969
1969
  displayName: "AdaL",
1970
1970
  skillsDir: ".adal/skills",
1971
- globalSkillsDir: join(home$1, ".adal/skills"),
1971
+ globalSkillsDir: join(home, ".adal/skills"),
1972
1972
  detectInstalled: async () => {
1973
- return existsSync(join(home$1, ".adal"));
1973
+ return existsSync(join(home, ".adal"));
1974
1974
  }
1975
1975
  },
1976
1976
  universal: {
@@ -4476,7 +4476,7 @@ function parseAddOptions(args) {
4476
4476
  options
4477
4477
  };
4478
4478
  }
4479
- const execFileAsync$3 = promisify(execFile);
4479
+ const execFileAsync$2 = promisify(execFile);
4480
4480
  const isLocalSource = (source) => source.startsWith(".") || isAbsolute(source);
4481
4481
  const isGitHubSource = (source) => /^[^/]+\/[^/]+$/.test(source);
4482
4482
  const ghEnv = () => ({
@@ -4511,7 +4511,7 @@ async function fetchCatalogFile(source, rel) {
4511
4511
  return existsSync(file) ? readFile(file, "utf8") : null;
4512
4512
  }
4513
4513
  if (isGitHubSource(source)) try {
4514
- const { stdout } = await execFileAsync$3("gh", [
4514
+ const { stdout } = await execFileAsync$2("gh", [
4515
4515
  "api",
4516
4516
  `repos/${source}/contents/${rel}`,
4517
4517
  "--jq",
@@ -4535,7 +4535,7 @@ async function listCatalogDir(source, dir) {
4535
4535
  return null;
4536
4536
  }
4537
4537
  if (isGitHubSource(source)) try {
4538
- const { stdout } = await execFileAsync$3("gh", [
4538
+ const { stdout } = await execFileAsync$2("gh", [
4539
4539
  "api",
4540
4540
  `repos/${source}/contents/${dir}`,
4541
4541
  "--jq",
@@ -4805,7 +4805,7 @@ async function runRules(args) {
4805
4805
  const { names, options } = parseRulesOptions(sub === "add" || sub === "a" || sub === "install" || sub === "i" ? args.slice(1) : args);
4806
4806
  await addRules(names, options);
4807
4807
  }
4808
- const execFileAsync$2 = promisify(execFile);
4808
+ const execFileAsync$1 = promisify(execFile);
4809
4809
  const RESET$4 = "\x1B[0m";
4810
4810
  const BOLD$4 = "\x1B[1m";
4811
4811
  const DIM$4 = "\x1B[38;5;102m";
@@ -4821,7 +4821,7 @@ async function fetchCatalogIndex(source) {
4821
4821
  const rel = ".well-known/agent-skills/index.json";
4822
4822
  if (source.startsWith(".") || isAbsolute(source)) return parseCatalogIndex(readFileSync(join(source, rel), "utf8"), source);
4823
4823
  if (source.match(/^[^/]+\/[^/]+$/)) {
4824
- const { stdout } = await execFileAsync$2("gh", [
4824
+ const { stdout } = await execFileAsync$1("gh", [
4825
4825
  "api",
4826
4826
  `repos/${source}/contents/${rel}`,
4827
4827
  "--jq",
@@ -5837,6 +5837,252 @@ function parseRemoveOptions(args) {
5837
5837
  options
5838
5838
  };
5839
5839
  }
5840
+ const claudeHome = () => process.env.CLAUDE_CONFIG_DIR?.trim() || join(homedir(), ".claude");
5841
+ const claudeSettingsPath = () => join(claudeHome(), "settings.json");
5842
+ const pruffHooksDir = () => join(claudeHome(), "hooks", "pruff");
5843
+ const statePath = () => join(claudeHome(), "pruff-skills", "state.json");
5844
+ function readClaudeSettings() {
5845
+ const path = claudeSettingsPath();
5846
+ if (!existsSync(path)) return {};
5847
+ let parsed;
5848
+ try {
5849
+ parsed = JSON.parse(readFileSync(path, "utf-8"));
5850
+ } catch {
5851
+ throw new Error(`${path} is not valid JSON. Fix it (or move it aside) before running this command.`);
5852
+ }
5853
+ return parsed && typeof parsed === "object" ? parsed : {};
5854
+ }
5855
+ function writeClaudeSettings(settings) {
5856
+ const path = claudeSettingsPath();
5857
+ const next = `${JSON.stringify(settings, null, 2)}\n`;
5858
+ const current = existsSync(path) ? readFileSync(path, "utf-8") : null;
5859
+ if (current === next) return null;
5860
+ mkdirSync(claudeHome(), { recursive: true });
5861
+ let backup = null;
5862
+ if (current !== null) {
5863
+ backup = `${path}.pruff-bak-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
5864
+ copyFileSync(path, backup);
5865
+ }
5866
+ writeFileSync(path, next, "utf-8");
5867
+ return backup;
5868
+ }
5869
+ function readState() {
5870
+ try {
5871
+ const parsed = JSON.parse(readFileSync(statePath(), "utf-8"));
5872
+ return {
5873
+ hooks: parsed.hooks ?? {},
5874
+ env: parsed.env ?? [],
5875
+ files: parsed.files ?? {}
5876
+ };
5877
+ } catch {
5878
+ return {
5879
+ hooks: {},
5880
+ env: [],
5881
+ files: {}
5882
+ };
5883
+ }
5884
+ }
5885
+ function writeState(state) {
5886
+ mkdirSync(join(claudeHome(), "pruff-skills"), { recursive: true });
5887
+ writeFileSync(statePath(), `${JSON.stringify(state, null, 2)}\n`, "utf-8");
5888
+ }
5889
+ function upsertHook(settings, event, matcher, command) {
5890
+ const hooks = { ...settings.hooks ?? {} };
5891
+ const groups = (hooks[event] ?? []).map((g) => ({
5892
+ ...g,
5893
+ hooks: [...g.hooks]
5894
+ }));
5895
+ const existing = groups.find((g) => g.matcher === matcher && g.hooks.some((h) => h.command === command.command));
5896
+ if (existing) existing.hooks = existing.hooks.map((h) => h.command === command.command ? command : h);
5897
+ else groups.push(matcher === void 0 ? { hooks: [command] } : {
5898
+ matcher,
5899
+ hooks: [command]
5900
+ });
5901
+ hooks[event] = groups;
5902
+ return {
5903
+ ...settings,
5904
+ hooks
5905
+ };
5906
+ }
5907
+ function removeHooks(settings, isOwned) {
5908
+ if (!settings.hooks) return settings;
5909
+ const hooks = {};
5910
+ for (const [event, groups] of Object.entries(settings.hooks)) {
5911
+ const kept = groups.map((g) => ({
5912
+ ...g,
5913
+ hooks: g.hooks.filter((h) => !isOwned(h.command))
5914
+ })).filter((g) => g.hooks.length > 0);
5915
+ if (kept.length > 0) hooks[event] = kept;
5916
+ }
5917
+ const next = { ...settings };
5918
+ if (Object.keys(hooks).length === 0) delete next.hooks;
5919
+ else next.hooks = hooks;
5920
+ return next;
5921
+ }
5922
+ function applyOwnedEnv(settings, desired, previouslyOwned) {
5923
+ const env = { ...settings.env ?? {} };
5924
+ const owned = [];
5925
+ const keptUserValues = [];
5926
+ for (const key of previouslyOwned) if (!(key in desired)) delete env[key];
5927
+ for (const [key, value] of Object.entries(desired)) {
5928
+ const isOurs = previouslyOwned.includes(key);
5929
+ if (key in env && env[key] !== value && !isOurs) {
5930
+ keptUserValues.push(key);
5931
+ continue;
5932
+ }
5933
+ env[key] = value;
5934
+ owned.push(key);
5935
+ }
5936
+ const next = { ...settings };
5937
+ if (Object.keys(env).length === 0) delete next.env;
5938
+ else next.env = env;
5939
+ return {
5940
+ settings: next,
5941
+ owned,
5942
+ keptUserValues
5943
+ };
5944
+ }
5945
+ function addAllowEntries(settings, permissionAllow, autoModeAllow) {
5946
+ const isObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
5947
+ const strings = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
5948
+ const merge = (current, added) => [...current, ...added.filter((entry) => !current.includes(entry))];
5949
+ const permissions = isObject(settings.permissions) ? settings.permissions : {};
5950
+ const autoMode = isObject(settings.autoMode) ? settings.autoMode : {};
5951
+ const currentAutoAllow = Array.isArray(autoMode.allow) ? strings(autoMode.allow) : ["$defaults"];
5952
+ return {
5953
+ ...settings,
5954
+ permissions: {
5955
+ ...permissions,
5956
+ allow: merge(strings(permissions.allow), permissionAllow)
5957
+ },
5958
+ autoMode: {
5959
+ ...autoMode,
5960
+ allow: merge(currentAutoAllow, autoModeAllow)
5961
+ }
5962
+ };
5963
+ }
5964
+ const POSTHOG_HOST = "https://us.i.posthog.com";
5965
+ const POSTHOG_LOGS_ENDPOINT = `${POSTHOG_HOST}/i/v1/logs`;
5966
+ const POSTHOG_METRICS_ENDPOINT = `${POSTHOG_HOST}/i/v1/metrics`;
5967
+ const ANALYTICS_ENV_KEYS = [
5968
+ "OTEL_LOG_TOOL_DETAILS",
5969
+ "CLAUDE_CODE_ENABLE_TELEMETRY",
5970
+ "OTEL_LOGS_EXPORTER",
5971
+ "OTEL_METRICS_EXPORTER",
5972
+ "OTEL_EXPORTER_OTLP_PROTOCOL",
5973
+ "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
5974
+ "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
5975
+ "OTEL_EXPORTER_OTLP_LOGS_HEADERS",
5976
+ "OTEL_EXPORTER_OTLP_METRICS_HEADERS",
5977
+ "OTEL_SERVICE_NAME",
5978
+ "OTEL_RESOURCE_ATTRIBUTES",
5979
+ "OTEL_METRICS_INCLUDE_SESSION_ID",
5980
+ "OTEL_LOG_USER_PROMPTS",
5981
+ "OTEL_LOG_ASSISTANT_RESPONSES"
5982
+ ];
5983
+ function hasAnalyticsEnv(settings) {
5984
+ return settings.env?.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT === POSTHOG_LOGS_ENDPOINT || settings.env?.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT === POSTHOG_METRICS_ENDPOINT;
5985
+ }
5986
+ function stripAnalyticsEnv(settings) {
5987
+ if (!settings.env || !hasAnalyticsEnv(settings)) return settings;
5988
+ const env = { ...settings.env };
5989
+ for (const key of ANALYTICS_ENV_KEYS) delete env[key];
5990
+ const next = { ...settings };
5991
+ if (Object.keys(env).length === 0) delete next.env;
5992
+ else next.env = env;
5993
+ return next;
5994
+ }
5995
+ function removeAnalyticsEnv(options = {}) {
5996
+ const settings = readClaudeSettings();
5997
+ if (!hasAnalyticsEnv(settings)) return { removed: false };
5998
+ if (options.dryRun) return {
5999
+ removed: true,
6000
+ dryRun: true,
6001
+ backup: null
6002
+ };
6003
+ return {
6004
+ removed: true,
6005
+ dryRun: false,
6006
+ backup: writeClaudeSettings(stripAnalyticsEnv(settings))
6007
+ };
6008
+ }
6009
+ function printRemoval(result) {
6010
+ if (result.dryRun) {
6011
+ console.log(` ${import_picocolors.default.yellow("!")} Would remove the PostHog usage exporter from ${import_picocolors.default.dim(claudeSettingsPath())}`);
6012
+ return;
6013
+ }
6014
+ console.log(` ${import_picocolors.default.green("✓")} Removed the PostHog usage exporter from ${import_picocolors.default.dim(claudeSettingsPath())}`);
6015
+ if (result.backup) console.log(import_picocolors.default.dim(` Backup: ${result.backup}`));
6016
+ console.log(import_picocolors.default.dim(" Restart Claude Code: the current session keeps exporting until then."));
6017
+ }
6018
+ function removeAnalyticsEnvOnUpdate(dryRun) {
6019
+ let result;
6020
+ try {
6021
+ result = removeAnalyticsEnv({ dryRun });
6022
+ } catch (error) {
6023
+ console.log(` ${import_picocolors.default.yellow("!")} Skipped the usage analytics cleanup: ${error instanceof Error ? error.message : String(error)}`);
6024
+ console.log();
6025
+ return;
6026
+ }
6027
+ if (!result.removed) return;
6028
+ console.log(`${import_picocolors.default.bold("Usage analytics")} ${import_picocolors.default.dim("(retired in 2.0.0)")}`);
6029
+ printRemoval(result);
6030
+ console.log();
6031
+ }
6032
+ function runStatus() {
6033
+ const enabled = hasAnalyticsEnv(readClaudeSettings());
6034
+ console.log();
6035
+ console.log(` ${import_picocolors.default.bold("Usage analytics")} ${enabled ? import_picocolors.default.yellow("on") : import_picocolors.default.dim("off")}`);
6036
+ console.log(` ${import_picocolors.default.dim("settings")} ${claudeSettingsPath()}`);
6037
+ console.log();
6038
+ console.log(import_picocolors.default.dim(enabled ? " Claude Code still exports to PostHog. Remove it: npx pruff-skills analytics disable" : " Retired in 2.0.0. Nothing is exported."));
6039
+ console.log();
6040
+ }
6041
+ function runDisable() {
6042
+ const result = removeAnalyticsEnv();
6043
+ console.log();
6044
+ if (result.removed) printRemoval(result);
6045
+ else console.log(` ${import_picocolors.default.dim("Nothing to remove: Claude Code is not exporting to PostHog.")}`);
6046
+ console.log();
6047
+ }
6048
+ function showAnalyticsHelp() {
6049
+ console.log(`
6050
+ ${import_picocolors.default.bold("Usage:")} pruff-skills analytics <command>
6051
+
6052
+ status Show whether Claude Code still exports usage to Pruff's PostHog (default)
6053
+ disable Remove the exporter from ~/.claude/settings.json
6054
+
6055
+ Usage analytics was retired in 2.0.0. \`pruff-skills update\` also removes the exporter,
6056
+ leaving every other key in your settings untouched.
6057
+ `);
6058
+ }
6059
+ function runAnalytics(args = []) {
6060
+ const sub = args[0];
6061
+ switch (sub) {
6062
+ case void 0:
6063
+ case "status":
6064
+ runStatus();
6065
+ return 0;
6066
+ case "disable":
6067
+ case "off":
6068
+ runDisable();
6069
+ return 0;
6070
+ case "--help":
6071
+ case "-h":
6072
+ showAnalyticsHelp();
6073
+ return 0;
6074
+ case "enable":
6075
+ case "on":
6076
+ case "verify":
6077
+ console.error(`\`analytics ${sub}\` was removed in 2.0.0: usage analytics is retired.`);
6078
+ showAnalyticsHelp();
6079
+ return 1;
6080
+ default:
6081
+ console.error(`Unknown analytics command: ${sub}`);
6082
+ showAnalyticsHelp();
6083
+ return 1;
6084
+ }
6085
+ }
5840
6086
  const RESET$2 = "\x1B[0m";
5841
6087
  const BOLD$2 = "\x1B[1m";
5842
6088
  const DIM$2 = "\x1B[38;5;102m";
@@ -6564,6 +6810,8 @@ async function syncAgentsForScope(isGlobal, options) {
6564
6810
  }
6565
6811
  async function runUpdate(args = []) {
6566
6812
  const options = parseUpdateOptions(args);
6813
+ setStep("update:remove-analytics");
6814
+ removeAnalyticsEnvOnUpdate(options.dryRun === true);
6567
6815
  setStep("update:resolve-scope");
6568
6816
  const scope = await resolveUpdateScope(options);
6569
6817
  if (options.skills) console.log(`${TEXT$1}Updating ${options.skills.join(", ")}...${RESET$1}`);
@@ -6992,130 +7240,6 @@ function fail(message) {
6992
7240
  process.exit(1);
6993
7241
  }
6994
7242
  var UseCommandError = class extends Error {};
6995
- const claudeHome$1 = () => process.env.CLAUDE_CONFIG_DIR?.trim() || join(homedir(), ".claude");
6996
- const claudeSettingsPath$1 = () => join(claudeHome$1(), "settings.json");
6997
- const pruffHooksDir = () => join(claudeHome$1(), "hooks", "pruff");
6998
- const statePath = () => join(claudeHome$1(), "pruff-skills", "state.json");
6999
- function readClaudeSettings$1() {
7000
- const path = claudeSettingsPath$1();
7001
- if (!existsSync(path)) return {};
7002
- let parsed;
7003
- try {
7004
- parsed = JSON.parse(readFileSync(path, "utf-8"));
7005
- } catch {
7006
- throw new Error(`${path} is not valid JSON. Fix it (or move it aside) before running this command.`);
7007
- }
7008
- return parsed && typeof parsed === "object" ? parsed : {};
7009
- }
7010
- function writeClaudeSettings$1(settings) {
7011
- const path = claudeSettingsPath$1();
7012
- const next = `${JSON.stringify(settings, null, 2)}\n`;
7013
- const current = existsSync(path) ? readFileSync(path, "utf-8") : null;
7014
- if (current === next) return null;
7015
- mkdirSync(claudeHome$1(), { recursive: true });
7016
- let backup = null;
7017
- if (current !== null) {
7018
- backup = `${path}.pruff-bak-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
7019
- copyFileSync(path, backup);
7020
- }
7021
- writeFileSync(path, next, "utf-8");
7022
- return backup;
7023
- }
7024
- function readState() {
7025
- try {
7026
- const parsed = JSON.parse(readFileSync(statePath(), "utf-8"));
7027
- return {
7028
- hooks: parsed.hooks ?? {},
7029
- env: parsed.env ?? [],
7030
- files: parsed.files ?? {}
7031
- };
7032
- } catch {
7033
- return {
7034
- hooks: {},
7035
- env: [],
7036
- files: {}
7037
- };
7038
- }
7039
- }
7040
- function writeState(state) {
7041
- mkdirSync(join(claudeHome$1(), "pruff-skills"), { recursive: true });
7042
- writeFileSync(statePath(), `${JSON.stringify(state, null, 2)}\n`, "utf-8");
7043
- }
7044
- function upsertHook(settings, event, matcher, command) {
7045
- const hooks = { ...settings.hooks ?? {} };
7046
- const groups = (hooks[event] ?? []).map((g) => ({
7047
- ...g,
7048
- hooks: [...g.hooks]
7049
- }));
7050
- const existing = groups.find((g) => g.matcher === matcher && g.hooks.some((h) => h.command === command.command));
7051
- if (existing) existing.hooks = existing.hooks.map((h) => h.command === command.command ? command : h);
7052
- else groups.push(matcher === void 0 ? { hooks: [command] } : {
7053
- matcher,
7054
- hooks: [command]
7055
- });
7056
- hooks[event] = groups;
7057
- return {
7058
- ...settings,
7059
- hooks
7060
- };
7061
- }
7062
- function removeHooks(settings, isOwned) {
7063
- if (!settings.hooks) return settings;
7064
- const hooks = {};
7065
- for (const [event, groups] of Object.entries(settings.hooks)) {
7066
- const kept = groups.map((g) => ({
7067
- ...g,
7068
- hooks: g.hooks.filter((h) => !isOwned(h.command))
7069
- })).filter((g) => g.hooks.length > 0);
7070
- if (kept.length > 0) hooks[event] = kept;
7071
- }
7072
- const next = { ...settings };
7073
- if (Object.keys(hooks).length === 0) delete next.hooks;
7074
- else next.hooks = hooks;
7075
- return next;
7076
- }
7077
- function applyOwnedEnv(settings, desired, previouslyOwned) {
7078
- const env = { ...settings.env ?? {} };
7079
- const owned = [];
7080
- const keptUserValues = [];
7081
- for (const key of previouslyOwned) if (!(key in desired)) delete env[key];
7082
- for (const [key, value] of Object.entries(desired)) {
7083
- const isOurs = previouslyOwned.includes(key);
7084
- if (key in env && env[key] !== value && !isOurs) {
7085
- keptUserValues.push(key);
7086
- continue;
7087
- }
7088
- env[key] = value;
7089
- owned.push(key);
7090
- }
7091
- const next = { ...settings };
7092
- if (Object.keys(env).length === 0) delete next.env;
7093
- else next.env = env;
7094
- return {
7095
- settings: next,
7096
- owned,
7097
- keptUserValues
7098
- };
7099
- }
7100
- function addAllowEntries(settings, permissionAllow, autoModeAllow) {
7101
- const isObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
7102
- const strings = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
7103
- const merge = (current, added) => [...current, ...added.filter((entry) => !current.includes(entry))];
7104
- const permissions = isObject(settings.permissions) ? settings.permissions : {};
7105
- const autoMode = isObject(settings.autoMode) ? settings.autoMode : {};
7106
- const currentAutoAllow = Array.isArray(autoMode.allow) ? strings(autoMode.allow) : ["$defaults"];
7107
- return {
7108
- ...settings,
7109
- permissions: {
7110
- ...permissions,
7111
- allow: merge(strings(permissions.allow), permissionAllow)
7112
- },
7113
- autoMode: {
7114
- ...autoMode,
7115
- allow: merge(currentAutoAllow, autoModeAllow)
7116
- }
7117
- };
7118
- }
7119
7243
  const HOOK_EVENTS = [
7120
7244
  "PreToolUse",
7121
7245
  "PostToolUse",
@@ -7186,7 +7310,7 @@ async function installHooks(names, options = {}) {
7186
7310
  backup: null
7187
7311
  };
7188
7312
  const state = readState();
7189
- let settings = readClaudeSettings$1();
7313
+ let settings = readClaudeSettings();
7190
7314
  const writes = [];
7191
7315
  for (const name of names) {
7192
7316
  if (!isBareSkillName(name)) {
@@ -7252,13 +7376,13 @@ async function installHooks(names, options = {}) {
7252
7376
  writeFileSync(w.path, w.content, "utf-8");
7253
7377
  chmodSync(w.path, 493);
7254
7378
  }
7255
- result.backup = writeClaudeSettings$1(settings);
7379
+ result.backup = writeClaudeSettings(settings);
7256
7380
  writeState(state);
7257
7381
  return result;
7258
7382
  }
7259
7383
  function uninstallHooks(names, options = {}) {
7260
7384
  const state = readState();
7261
- let settings = readClaudeSettings$1();
7385
+ let settings = readClaudeSettings();
7262
7386
  const removed = [];
7263
7387
  for (const name of names) {
7264
7388
  if (!isBareSkillName(name)) continue;
@@ -7274,7 +7398,7 @@ function uninstallHooks(names, options = {}) {
7274
7398
  });
7275
7399
  }
7276
7400
  if (!options.dryRun && removed.length > 0) {
7277
- writeClaudeSettings$1(settings);
7401
+ writeClaudeSettings(settings);
7278
7402
  writeState(state);
7279
7403
  }
7280
7404
  return removed;
@@ -7465,7 +7589,7 @@ async function installScripts() {
7465
7589
  }
7466
7590
  return null;
7467
7591
  }
7468
- function runVerify$1() {
7592
+ function runVerify() {
7469
7593
  const dir = dbReaderBinDir();
7470
7594
  const r = spawnSync(join(dir, "pruff-db-verify"), [DB_READER_VERIFY_DATABASE], {
7471
7595
  encoding: "utf-8",
@@ -7525,7 +7649,7 @@ async function installReaderRule(workspace, note) {
7525
7649
  }
7526
7650
  function allowInUserSettings(note) {
7527
7651
  try {
7528
- writeClaudeSettings$1(addAllowEntries(readClaudeSettings$1(), DB_READER_USER_ALLOW, [DB_READER_AUTO_MODE_ALLOW]));
7652
+ writeClaudeSettings(addAllowEntries(readClaudeSettings(), DB_READER_USER_ALLOW, [DB_READER_AUTO_MODE_ALLOW]));
7529
7653
  M.success("pruff-db-query allowed in every folder, auto mode included");
7530
7654
  } catch (error) {
7531
7655
  note(`Could not update ~/.claude/settings.json (${error instanceof Error ? error.message : String(error)}). Auto mode may deny pruff-db-query with [Production Reads]: add ${import_picocolors.default.cyan(DB_READER_ALLOW_ENTRY)} to permissions.allow and an autoMode.allow rule for it by hand.`);
@@ -7621,7 +7745,7 @@ async function setupDbReader({ workspace, note }) {
7621
7745
  M.success(`Installed ${DB_READER_SCRIPTS.join(" and ")} in ${dbReaderBinDir()}`);
7622
7746
  if (!(process.env.PATH ?? "").split(delimiter).includes(dbReaderBinDir())) M.warn(`${dbReaderBinDir()} is not on your PATH. Add ${import_picocolors.default.cyan("export PATH=\"$HOME/.local/bin:$PATH\"")} to ~/.zshrc.`);
7623
7747
  setStep("db-reader:verify");
7624
- const verify = runVerify$1();
7748
+ const verify = runVerify();
7625
7749
  if (!verify.ok) {
7626
7750
  note(`Prod DB access not enabled — ${import_picocolors.default.cyan("pruff-db-verify")} failed. Run ${import_picocolors.default.cyan(`${join(dbReaderBinDir(), "pruff-db-verify")} ${DB_READER_VERIFY_DATABASE}`)} and share the output with the infra owner.`);
7627
7751
  M.warn(verify.out);
@@ -8988,8 +9112,8 @@ async function installHarness(workspace) {
8988
9112
  M.step("Installing the team harness (hooks, settings, scripts)…");
8989
9113
  try {
8990
9114
  const state = readState();
8991
- const env = applyOwnedEnv(readClaudeSettings$1(), TEAM_USER_ENV, state.env);
8992
- writeClaudeSettings$1(env.settings);
9115
+ const env = applyOwnedEnv(readClaudeSettings(), TEAM_USER_ENV, state.env);
9116
+ writeClaudeSettings(env.settings);
8993
9117
  writeState({
8994
9118
  ...readState(),
8995
9119
  env: env.owned
@@ -9538,7 +9662,7 @@ function registerVault(vaultPath) {
9538
9662
  writeFileSync(configPath, JSON.stringify(config), "utf-8");
9539
9663
  M.success("Registered vault in Obsidian");
9540
9664
  }
9541
- const execFileAsync$1 = promisify(execFile);
9665
+ const execFileAsync = promisify(execFile);
9542
9666
  const BUZZ_LOGO_LINES = [
9543
9667
  "██████╗ ██╗ ██╗███████╗███████╗",
9544
9668
  "██╔══██╗██║ ██║╚══███╔╝╚══███╔╝",
@@ -9652,7 +9776,7 @@ const TEAM_AGENTS = {
9652
9776
  const TEAMS = Object.keys(TEAM_SKILLS);
9653
9777
  async function isBuzzRunning() {
9654
9778
  try {
9655
- const { stdout } = await execFileAsync$1("pgrep", ["-f", "Buzz.app|buzz-desktop|buzz-acp"]);
9779
+ const { stdout } = await execFileAsync("pgrep", ["-f", "Buzz.app|buzz-desktop|buzz-acp"]);
9656
9780
  return stdout.trim().length > 0;
9657
9781
  } catch {
9658
9782
  return false;
@@ -9660,7 +9784,7 @@ async function isBuzzRunning() {
9660
9784
  }
9661
9785
  async function claudeAuthStatus() {
9662
9786
  try {
9663
- const { stdout } = await execFileAsync$1("claude", ["auth", "status"]);
9787
+ const { stdout } = await execFileAsync("claude", ["auth", "status"]);
9664
9788
  const parsed = JSON.parse(stdout);
9665
9789
  const ok = parsed.apiProvider === "firstParty";
9666
9790
  return {
@@ -9703,14 +9827,14 @@ async function syncVault() {
9703
9827
  M.step("Pruff knowledge vault");
9704
9828
  const dest = join(nestDir(), "REPOS", "pruff-knowledge");
9705
9829
  if (existsSync(join(dest, ".git"))) try {
9706
- await execFileAsync$1("git", [
9830
+ await execFileAsync("git", [
9707
9831
  "-C",
9708
9832
  dest,
9709
9833
  "pull",
9710
9834
  "--ff-only",
9711
9835
  "--quiet"
9712
9836
  ]);
9713
- const { stdout } = await execFileAsync$1("git", [
9837
+ const { stdout } = await execFileAsync("git", [
9714
9838
  "-C",
9715
9839
  dest,
9716
9840
  "log",
@@ -9726,13 +9850,13 @@ async function syncVault() {
9726
9850
  else {
9727
9851
  mkdirSync(dirname(dest), { recursive: true });
9728
9852
  try {
9729
- await execFileAsync$1("git", [
9853
+ await execFileAsync("git", [
9730
9854
  "clone",
9731
9855
  "--quiet",
9732
9856
  VAULT_REPO,
9733
9857
  dest
9734
9858
  ]);
9735
- const { stdout } = await execFileAsync$1("git", [
9859
+ const { stdout } = await execFileAsync("git", [
9736
9860
  "-C",
9737
9861
  dest,
9738
9862
  "log",
@@ -10025,310 +10149,6 @@ async function runBuzz(args) {
10025
10149
  console.log(BUZZ_HELP);
10026
10150
  }
10027
10151
  }
10028
- const execFileAsync = promisify(execFile);
10029
- const __analyticsDirname = dirname(fileURLToPath(import.meta.url));
10030
- const POSTHOG_HOST = "https://us.i.posthog.com";
10031
- const ANALYTICS_TOKEN_CATALOG_PATH = "analytics/posthog.json";
10032
- let cachedToken = null;
10033
- async function getAnalyticsToken() {
10034
- const fromEnv = process.env.PRUFF_ANALYTICS_POSTHOG_TOKEN?.trim();
10035
- if (fromEnv) return fromEnv;
10036
- if (cachedToken !== null) return cachedToken;
10037
- const raw = readLocalCheckoutToken() ?? await fetchTokenFile(PRUFF_CATALOG, ANALYTICS_TOKEN_CATALOG_PATH);
10038
- cachedToken = raw ? JSON.parse(raw).token ?? "" : "";
10039
- return cachedToken;
10040
- }
10041
- function readLocalCheckoutToken() {
10042
- let dir = __analyticsDirname;
10043
- for (let depth = 0; depth < 4; depth++) {
10044
- const candidate = join(dir, ANALYTICS_TOKEN_CATALOG_PATH);
10045
- if (existsSync(candidate)) return readFileSync(candidate, "utf-8");
10046
- const parent = dirname(dir);
10047
- if (parent === dir) break;
10048
- dir = parent;
10049
- }
10050
- return null;
10051
- }
10052
- async function fetchTokenFile(source, rel) {
10053
- if (source.startsWith(".") || isAbsolute(source)) {
10054
- const file = join(source, rel);
10055
- return existsSync(file) ? readFileSync(file, "utf-8") : null;
10056
- }
10057
- if (/^[^/]+\/[^/]+$/.test(source)) try {
10058
- const { stdout } = await execFileAsync("gh", [
10059
- "api",
10060
- `repos/${source}/contents/${rel}`,
10061
- "--jq",
10062
- ".content"
10063
- ], { env: {
10064
- ...process.env,
10065
- GIT_TERMINAL_PROMPT: "0"
10066
- } });
10067
- return Buffer.from(stdout.trim(), "base64").toString("utf8");
10068
- } catch {
10069
- return null;
10070
- }
10071
- return null;
10072
- }
10073
- const home = homedir();
10074
- const claudeHome = () => process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude");
10075
- const claudeSettingsPath = () => join(claudeHome(), "settings.json");
10076
- function buildAnalyticsEnv(token, options = {}) {
10077
- return {
10078
- ...options.withToolDetails ? { OTEL_LOG_TOOL_DETAILS: "1" } : {},
10079
- CLAUDE_CODE_ENABLE_TELEMETRY: "1",
10080
- OTEL_LOGS_EXPORTER: "otlp",
10081
- OTEL_METRICS_EXPORTER: "otlp",
10082
- OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf",
10083
- OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: `${POSTHOG_HOST}/i/v1/logs`,
10084
- OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: `${POSTHOG_HOST}/i/v1/metrics`,
10085
- OTEL_EXPORTER_OTLP_LOGS_HEADERS: `Authorization=Bearer ${token}`,
10086
- OTEL_EXPORTER_OTLP_METRICS_HEADERS: `Authorization=Bearer ${token}`,
10087
- OTEL_SERVICE_NAME: "claude-code",
10088
- OTEL_RESOURCE_ATTRIBUTES: "pruff.rollout=team",
10089
- OTEL_METRICS_INCLUDE_SESSION_ID: "false",
10090
- OTEL_LOG_USER_PROMPTS: "0",
10091
- OTEL_LOG_ASSISTANT_RESPONSES: "0"
10092
- };
10093
- }
10094
- const ANALYTICS_ENV_KEYS = Object.keys(buildAnalyticsEnv("x", { withToolDetails: true }));
10095
- function readClaudeSettings() {
10096
- const path = claudeSettingsPath();
10097
- if (!existsSync(path)) return {};
10098
- try {
10099
- const parsed = JSON.parse(readFileSync(path, "utf-8"));
10100
- return parsed && typeof parsed === "object" ? parsed : {};
10101
- } catch {
10102
- throw new Error(`${path} is not valid JSON. Fix it (or move it aside) before running this command.`);
10103
- }
10104
- }
10105
- function writeClaudeSettings(settings) {
10106
- mkdirSync(claudeHome(), { recursive: true });
10107
- writeFileSync(claudeSettingsPath(), `${JSON.stringify(settings, null, 2)}\n`, "utf-8");
10108
- }
10109
- function applyAnalyticsEnv(settings, token, options = {}) {
10110
- const base = stripAnalyticsEnv(settings);
10111
- return {
10112
- ...base,
10113
- env: {
10114
- ...base.env ?? {},
10115
- ...buildAnalyticsEnv(token, options)
10116
- }
10117
- };
10118
- }
10119
- function stripAnalyticsEnv(settings) {
10120
- if (!settings.env) return settings;
10121
- const env = { ...settings.env };
10122
- for (const key of ANALYTICS_ENV_KEYS) delete env[key];
10123
- const next = { ...settings };
10124
- if (Object.keys(env).length === 0) delete next.env;
10125
- else next.env = env;
10126
- return next;
10127
- }
10128
- function isAnalyticsEnabled(settings = readClaudeSettings()) {
10129
- return settings.env?.CLAUDE_CODE_ENABLE_TELEMETRY === "1";
10130
- }
10131
- async function requireToken() {
10132
- const token = await getAnalyticsToken();
10133
- if (token.startsWith("phc_")) return token;
10134
- console.log();
10135
- console.log(import_picocolors.default.red(" Could not resolve the PostHog project token."));
10136
- console.log(import_picocolors.default.dim(` Looked in three places, in order:
10137
- 1. \$PRUFF_ANALYTICS_POSTHOG_TOKEN (unset)
10138
- 2. ${ANALYTICS_TOKEN_CATALOG_PATH} in this checkout (not found)\n 3. ${ANALYTICS_TOKEN_CATALOG_PATH} in ${PRUFF_CATALOG} via gh (not found)\n Expected a file containing {"token":"phc_..."}.\n Check \`gh auth status\` — the catalog is private and read with your own
10139
- GitHub auth. To test without the catalog, export PRUFF_ANALYTICS_POSTHOG_TOKEN.`));
10140
- console.log();
10141
- return null;
10142
- }
10143
- async function runStatus() {
10144
- const settings = readClaudeSettings();
10145
- const enabled = isAnalyticsEnabled(settings);
10146
- const token = await getAnalyticsToken();
10147
- console.log();
10148
- console.log(` ${import_picocolors.default.bold("Usage analytics")} ${enabled ? import_picocolors.default.green("on") : import_picocolors.default.dim("off")}`);
10149
- console.log(` ${import_picocolors.default.dim("settings")} ${claudeSettingsPath()}`);
10150
- if (enabled) {
10151
- console.log(` ${import_picocolors.default.dim("logs")} ${settings.env?.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT}`);
10152
- console.log(` ${import_picocolors.default.dim("metrics")} ${settings.env?.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT}`);
10153
- console.log(` ${import_picocolors.default.dim("token")} ${token ? `${token.slice(0, 12)}…` : import_picocolors.default.red("missing")}`);
10154
- console.log(` ${import_picocolors.default.dim("prompts")} not exported (OTEL_LOG_USER_PROMPTS=0)`);
10155
- const toolDetails = settings.env?.OTEL_LOG_TOOL_DETAILS === "1";
10156
- console.log(` ${import_picocolors.default.dim("cmds")} ${toolDetails ? import_picocolors.default.yellow("EXPORTED verbatim (--with-tool-details on)") : "not exported — skills report as \"custom_skill\""}`);
10157
- }
10158
- console.log();
10159
- console.log(import_picocolors.default.dim(enabled ? " Turn it off with: npx pruff-skills analytics disable" : " Turn it on with: npx pruff-skills analytics enable"));
10160
- console.log();
10161
- }
10162
- async function runEnable(args) {
10163
- const withToolDetails = args.includes("--with-tool-details");
10164
- const token = await requireToken();
10165
- if (!token) return;
10166
- const settings = readClaudeSettings();
10167
- const already = isAnalyticsEnabled(settings);
10168
- writeClaudeSettings(applyAnalyticsEnv(settings, token, { withToolDetails }));
10169
- console.log();
10170
- console.log(` ${import_picocolors.default.green("✓")} Usage analytics ${already ? "refreshed" : "enabled"} in ${import_picocolors.default.dim(claudeSettingsPath())}`);
10171
- console.log();
10172
- console.log(` ${import_picocolors.default.dim("Exports")} token + cost usage, tool names, results and timings,`);
10173
- console.log(` ${import_picocolors.default.dim(" ")} MCP tool calls, model, subagent vs main, your email.`);
10174
- console.log(` ${import_picocolors.default.dim("Never")} prompts, responses, file contents, raw API bodies.`);
10175
- console.log();
10176
- if (withToolDetails) {
10177
- console.log(` ${import_picocolors.default.yellow("!")} ${import_picocolors.default.bold("--with-tool-details is ON.")}`);
10178
- console.log(import_picocolors.default.dim(" Real skill and slash-command names will be exported — and so will"));
10179
- console.log(import_picocolors.default.dim(" every shell command and tool input, verbatim. Anything you type into"));
10180
- console.log(import_picocolors.default.dim(" a command (RUTs, connection strings, tokens) reaches PostHog."));
10181
- console.log(import_picocolors.default.dim(" Turn it back off by re-running `analytics enable` without the flag."));
10182
- } else {
10183
- console.log(import_picocolors.default.dim(" Pruff-authored skills report as \"custom_skill\", not their real names."));
10184
- console.log(import_picocolors.default.dim(" `analytics enable --with-tool-details` unlocks the names — read what that"));
10185
- console.log(import_picocolors.default.dim(" also exports first: npx pruff-skills analytics --help"));
10186
- }
10187
- console.log();
10188
- console.log(` ${import_picocolors.default.bold("Restart Claude Code")} — the OTEL exporter is read once at session start.`);
10189
- console.log(import_picocolors.default.dim(" Then confirm the pipeline works: npx pruff-skills analytics verify"));
10190
- console.log();
10191
- }
10192
- function runDisable() {
10193
- writeClaudeSettings(stripAnalyticsEnv(readClaudeSettings()));
10194
- console.log();
10195
- console.log(` ${import_picocolors.default.green("✓")} Usage analytics disabled — the env block is gone from`);
10196
- console.log(import_picocolors.default.dim(` ${claudeSettingsPath()}, your own keys untouched.`));
10197
- console.log(import_picocolors.default.dim(" Nothing turns it back on: only `analytics enable` ever writes it."));
10198
- console.log(import_picocolors.default.dim(" Restart Claude Code to stop the current session exporting."));
10199
- console.log();
10200
- }
10201
- async function verifyToken(token) {
10202
- try {
10203
- const res = await fetch(`${POSTHOG_HOST}/flags?v=2`, {
10204
- method: "POST",
10205
- headers: { "Content-Type": "application/json" },
10206
- body: JSON.stringify({
10207
- api_key: token,
10208
- distinct_id: "pruff-analytics-verify"
10209
- })
10210
- });
10211
- if (res.ok) {
10212
- console.log(` ${import_picocolors.default.green("✓")} Token accepted by PostHog (HTTP ${res.status}).`);
10213
- return true;
10214
- }
10215
- console.log(` ${import_picocolors.default.red("✗")} Token rejected (HTTP ${res.status}).`);
10216
- console.log(import_picocolors.default.dim(` ${ANALYTICS_TOKEN_CATALOG_PATH} in ${PRUFF_CATALOG} does not hold a valid`));
10217
- console.log(import_picocolors.default.dim(" project token for the Pruff PostHog project."));
10218
- return false;
10219
- } catch (error) {
10220
- console.log(` ${import_picocolors.default.red("✗")} Could not reach PostHog: ${error instanceof Error ? error.message : error}`);
10221
- return false;
10222
- }
10223
- }
10224
- async function runVerify() {
10225
- const token = await requireToken();
10226
- if (!token) return;
10227
- console.log();
10228
- console.log(import_picocolors.default.dim(` POST ${POSTHOG_HOST}/flags?v=2 ${import_picocolors.default.dim("(does this token exist?)")}`));
10229
- if (!await verifyToken(token)) {
10230
- console.log();
10231
- return;
10232
- }
10233
- const endpoint = `${POSTHOG_HOST}/i/v1/logs`;
10234
- const body = { resourceLogs: [{
10235
- resource: { attributes: [{
10236
- key: "service.name",
10237
- value: { stringValue: "claude-code" }
10238
- }, {
10239
- key: "pruff.rollout",
10240
- value: { stringValue: "team" }
10241
- }] },
10242
- scopeLogs: [{ logRecords: [{
10243
- timeUnixNano: `${Date.now() * 1e6}`,
10244
- severityText: "INFO",
10245
- body: { stringValue: "pruff_analytics_verify" },
10246
- attributes: [{
10247
- key: "pruff.verify",
10248
- value: { boolValue: true }
10249
- }]
10250
- }] }]
10251
- }] };
10252
- console.log(import_picocolors.default.dim(` POST ${endpoint} ${import_picocolors.default.dim("(does the OTLP path accept our payload?)")}`));
10253
- try {
10254
- const res = await fetch(endpoint, {
10255
- method: "POST",
10256
- headers: {
10257
- "Content-Type": "application/json",
10258
- Authorization: `Bearer ${token}`
10259
- },
10260
- body: JSON.stringify(body)
10261
- });
10262
- if (res.ok) console.log(` ${import_picocolors.default.green("✓")} Payload accepted (HTTP ${res.status}).`);
10263
- else {
10264
- console.log(` ${import_picocolors.default.red("✗")} Rejected (HTTP ${res.status}).`);
10265
- console.log(import_picocolors.default.dim(` ${(await res.text()).slice(0, 300)}`));
10266
- console.log();
10267
- return;
10268
- }
10269
- } catch (error) {
10270
- console.log(` ${import_picocolors.default.red("✗")} Request failed: ${error instanceof Error ? error.message : error}`);
10271
- console.log();
10272
- return;
10273
- }
10274
- console.log();
10275
- console.log(` ${import_picocolors.default.bold("Last step — confirm it landed:")}`);
10276
- console.log(import_picocolors.default.dim(` PostHog → Logs, filter service = "claude-code", search "pruff_analytics_verify".`));
10277
- console.log(import_picocolors.default.dim(" If the token check passed but nothing appears within a minute or two,"));
10278
- console.log(import_picocolors.default.dim(" the project has log ingestion disabled or is over quota."));
10279
- console.log();
10280
- }
10281
- function showAnalyticsHelp() {
10282
- console.log(`
10283
- ${import_picocolors.default.bold("Usage:")} pruff-skills analytics <command>
10284
-
10285
- status Show whether Claude Code is exporting to PostHog (default)
10286
- enable [--with-tool-details] Turn the exporter on in ~/.claude/settings.json
10287
- disable Turn the exporter off again
10288
- verify Prove the PostHog ingest path works
10289
-
10290
- ${import_picocolors.default.bold("What is exported by default")}
10291
- Token and cost usage, model, subagent vs main agent, tool names with their
10292
- success and duration, MCP tool calls, session and prompt counts, your email.
10293
-
10294
- ${import_picocolors.default.bold("What is never exported")}
10295
- Your prompts, Claude's responses, file contents and raw API bodies.
10296
-
10297
- ${import_picocolors.default.bold("--with-tool-details")}
10298
- Unlocks real skill and slash-command names, which are otherwise anonymized to
10299
- "custom_skill" / "custom". It ALSO exports every shell command and tool input
10300
- verbatim — measured, not theoretical. Off by default: Pruff's own rule is that
10301
- RUTs, SII credentials and API keys are never logged. MCP attribution does not
10302
- need this flag; mcp_server.name and mcp_tool.name are exported either way.
10303
- `);
10304
- }
10305
- async function runAnalytics(args = []) {
10306
- const sub = args[0];
10307
- switch (sub) {
10308
- case void 0:
10309
- case "status":
10310
- await runStatus();
10311
- break;
10312
- case "enable":
10313
- case "on":
10314
- await runEnable(args.slice(1));
10315
- break;
10316
- case "disable":
10317
- case "off":
10318
- runDisable();
10319
- break;
10320
- case "verify":
10321
- await runVerify();
10322
- break;
10323
- case "--help":
10324
- case "-h":
10325
- showAnalyticsHelp();
10326
- break;
10327
- default:
10328
- console.log(`Unknown analytics command: ${sub}`);
10329
- showAnalyticsHelp();
10330
- }
10331
- }
10332
10152
  const __dirname = dirname(fileURLToPath(import.meta.url));
10333
10153
  function getVersion() {
10334
10154
  try {
@@ -10480,13 +10300,10 @@ ${BOLD}Hooks:${RESET}
10480
10300
  hooks list Show installed and available hooks
10481
10301
  hooks remove <name> Unregister a hook and delete its script
10482
10302
 
10483
- ${BOLD}Usage analytics:${RESET}
10484
- analytics status Is Claude Code reporting usage to Pruff's PostHog? (alias: telemetry)
10485
- analytics enable Turn it on in ~/.claude/settings.json
10486
- analytics disable Turn it off again, leaving your own env keys untouched
10487
- analytics verify Prove the PostHog ingest path works before trusting it
10488
- ${DIM}Reports token/cost by skill, MCP tool, subagent and model.${RESET}
10489
- ${DIM}Never exports prompts, responses or file contents.${RESET}
10303
+ ${BOLD}Usage analytics (retired):${RESET}
10304
+ analytics status Does Claude Code still report usage to Pruff's PostHog? (alias: telemetry)
10305
+ analytics disable Remove the exporter, leaving your own env keys untouched
10306
+ ${DIM}\`update\` removes it too.${RESET}
10490
10307
 
10491
10308
  ${BOLD}Buzz nest:${RESET}
10492
10309
  buzz setup <team> Provision a Buzz agent nest (~/.buzz): vault, team skills,
@@ -10736,9 +10553,7 @@ async function main() {
10736
10553
  await runBuzz(restArgs);
10737
10554
  break;
10738
10555
  case "analytics":
10739
- case "telemetry":
10740
- await runAnalytics(restArgs);
10741
- break;
10556
+ case "telemetry": return runAnalytics(restArgs);
10742
10557
  case "onboarding":
10743
10558
  case "onboard":
10744
10559
  if (!inAgent) {