devtronic 1.0.0 → 1.2.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.
- package/README.md +5 -3
- package/dist/{chunk-B6Q6YVID.js → chunk-V4QEAL7Y.js} +53 -23
- package/dist/index.js +1070 -220
- package/dist/{plugin-JTDPHWUF.js → plugin-SGSFVXPA.js} +5 -1
- package/package.json +1 -1
- package/templates/claude-code/.claude/agents/a11y-auditor.md +82 -0
- package/templates/claude-code/.claude/agents/design-critic.md +77 -0
- package/templates/claude-code/.claude/agents/design-system-guardian.md +78 -0
- package/templates/claude-code/.claude/agents/design-token-extractor.md +53 -0
- package/templates/claude-code/.claude/agents/ia-architect.md +81 -0
- package/templates/claude-code/.claude/agents/ux-researcher.md +69 -0
- package/templates/claude-code/.claude/agents/visual-qa.md +74 -0
- package/templates/claude-code/.claude/skills/audit/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/backlog/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/brief/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/briefing/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/checkpoint/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/create-plan/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/create-skill/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/design/SKILL.md +48 -0
- package/templates/claude-code/.claude/skills/design-audit/SKILL.md +114 -0
- package/templates/claude-code/.claude/skills/design-define/SKILL.md +109 -0
- package/templates/claude-code/.claude/skills/design-ia/SKILL.md +103 -0
- package/templates/claude-code/.claude/skills/design-research/SKILL.md +116 -0
- package/templates/claude-code/.claude/skills/design-review/SKILL.md +99 -0
- package/templates/claude-code/.claude/skills/design-spec/SKILL.md +136 -0
- package/templates/claude-code/.claude/skills/design-system/SKILL.md +39 -0
- package/templates/claude-code/.claude/skills/design-system-audit/SKILL.md +106 -0
- package/templates/claude-code/.claude/skills/design-system-define/SKILL.md +141 -0
- package/templates/claude-code/.claude/skills/design-system-sync/SKILL.md +105 -0
- package/templates/claude-code/.claude/skills/design-wireframe/SKILL.md +120 -0
- package/templates/claude-code/.claude/skills/execute-plan/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/generate-tests/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/handoff/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/investigate/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/learn/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/opensrc/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/post-review/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/quick/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/recap/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/research/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/scaffold/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/setup/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/spec/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/summary/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/worktree/SKILL.md +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ADDONS,
|
|
4
|
+
BASE_AGENT_COUNT,
|
|
4
5
|
BASE_SKILL_COUNT,
|
|
6
|
+
DESIGN_SKILL_COUNT,
|
|
5
7
|
MANIFEST_DIR,
|
|
6
8
|
MARKETPLACE_NAME,
|
|
7
9
|
PLUGIN_DIR,
|
|
@@ -22,12 +24,12 @@ import {
|
|
|
22
24
|
readManifest,
|
|
23
25
|
writeFile,
|
|
24
26
|
writeManifest
|
|
25
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-V4QEAL7Y.js";
|
|
26
28
|
|
|
27
29
|
// src/index.ts
|
|
28
30
|
import { Command } from "commander";
|
|
29
|
-
import * as
|
|
30
|
-
import
|
|
31
|
+
import * as p16 from "@clack/prompts";
|
|
32
|
+
import chalk17 from "chalk";
|
|
31
33
|
|
|
32
34
|
// src/commands/init.ts
|
|
33
35
|
import { existsSync as existsSync5, chmodSync } from "fs";
|
|
@@ -1205,9 +1207,9 @@ function getCliVersion() {
|
|
|
1205
1207
|
resolve(__dirname, "../package.json"),
|
|
1206
1208
|
resolve(__dirname, "../../package.json")
|
|
1207
1209
|
];
|
|
1208
|
-
for (const
|
|
1210
|
+
for (const p17 of paths) {
|
|
1209
1211
|
try {
|
|
1210
|
-
const pkg = JSON.parse(readFileSync(
|
|
1212
|
+
const pkg = JSON.parse(readFileSync(p17, "utf-8"));
|
|
1211
1213
|
if (pkg.name === "devtronic" && pkg.version) {
|
|
1212
1214
|
return pkg.version;
|
|
1213
1215
|
}
|
|
@@ -1372,7 +1374,18 @@ Valid addons: ${validAddons.join(", ")}`);
|
|
|
1372
1374
|
process.exit(0);
|
|
1373
1375
|
}
|
|
1374
1376
|
if (wantOrchestration) {
|
|
1375
|
-
enabledAddons
|
|
1377
|
+
enabledAddons.push("orchestration");
|
|
1378
|
+
}
|
|
1379
|
+
const wantDesign = await p3.confirm({
|
|
1380
|
+
message: "Enable design best practices? (design-init \u2192 design-review \u2192 design-refine \u2192 design-harden)",
|
|
1381
|
+
initialValue: false
|
|
1382
|
+
});
|
|
1383
|
+
if (p3.isCancel(wantDesign)) {
|
|
1384
|
+
p3.cancel("Operation cancelled");
|
|
1385
|
+
process.exit(0);
|
|
1386
|
+
}
|
|
1387
|
+
if (wantDesign) {
|
|
1388
|
+
enabledAddons.push("design-best-practices");
|
|
1376
1389
|
}
|
|
1377
1390
|
}
|
|
1378
1391
|
if (enabledAddons.length > 0) {
|
|
@@ -1452,8 +1465,9 @@ Valid addons: ${validAddons.join(", ")}`);
|
|
|
1452
1465
|
manifest.installMode = "plugin";
|
|
1453
1466
|
manifest.pluginPath = pluginResult.pluginPath;
|
|
1454
1467
|
const addonSkillCount = (projectConfig.enabledAddons || []).reduce((sum, a) => sum + (ADDONS[a]?.skills.length ?? 0), 0);
|
|
1455
|
-
const
|
|
1456
|
-
|
|
1468
|
+
const baseTotal = BASE_SKILL_COUNT + DESIGN_SKILL_COUNT;
|
|
1469
|
+
const skillLabel = addonSkillCount > 0 ? `${baseTotal} + ${addonSkillCount} addon skills` : `${baseTotal} skills`;
|
|
1470
|
+
generatedFiles.push(`Plugin ${PLUGIN_NAME} (${skillLabel}, ${BASE_AGENT_COUNT} agents, 5 hooks)`);
|
|
1457
1471
|
}
|
|
1458
1472
|
for (const ide of selectedIDEs) {
|
|
1459
1473
|
const templateName = IDE_TEMPLATE_MAP[ide];
|
|
@@ -1601,6 +1615,13 @@ Valid addons: ${validAddons.join(", ")}`);
|
|
|
1601
1615
|
].join("\n"),
|
|
1602
1616
|
"Next Steps"
|
|
1603
1617
|
);
|
|
1618
|
+
p3.note(
|
|
1619
|
+
[
|
|
1620
|
+
`${chalk4.dim("Execution mode:")} HITL ${chalk4.dim("(default \u2014 safe for unfamiliar codebases)")}`,
|
|
1621
|
+
`${chalk4.dim("Change anytime:")} ${chalk4.cyan("npx devtronic mode afk")}`
|
|
1622
|
+
].join("\n"),
|
|
1623
|
+
"Autonomous Mode"
|
|
1624
|
+
);
|
|
1604
1625
|
p3.outro(chalk4.green("Setup complete!"));
|
|
1605
1626
|
} catch (err) {
|
|
1606
1627
|
spinner8.stop("Configuration failed");
|
|
@@ -1642,8 +1663,9 @@ async function showPreview(targetDir, selectedIDEs, projectConfig, analysis) {
|
|
|
1642
1663
|
fileLines.push(` ${symbols.star} AGENTS.md ${chalk4.dim(`(${agentsMdLines} lines)`)}`);
|
|
1643
1664
|
if (selectedIDEs.includes("claude-code")) {
|
|
1644
1665
|
const previewAddonCount = (projectConfig.enabledAddons || []).reduce((sum, a) => sum + (ADDONS[a]?.skills.length ?? 0), 0);
|
|
1645
|
-
const
|
|
1646
|
-
|
|
1666
|
+
const previewBaseTotal = BASE_SKILL_COUNT + DESIGN_SKILL_COUNT;
|
|
1667
|
+
const previewSkillLabel = previewAddonCount > 0 ? `${previewBaseTotal} + ${previewAddonCount} addon skills` : `${previewBaseTotal} skills`;
|
|
1668
|
+
fileLines.push(` ${symbols.star} Plugin ${chalk4.cyan(PLUGIN_NAME)} ${chalk4.dim(`(${previewSkillLabel}, ${BASE_AGENT_COUNT} agents, 5 hooks)`)}`);
|
|
1647
1669
|
}
|
|
1648
1670
|
const generatedRules = generateArchitectureRules(projectConfig);
|
|
1649
1671
|
for (const ide of selectedIDEs) {
|
|
@@ -1702,8 +1724,8 @@ function buildProjectConfigFromPreset(presetConfig, analysis) {
|
|
|
1702
1724
|
}
|
|
1703
1725
|
|
|
1704
1726
|
// src/commands/update.ts
|
|
1705
|
-
import { resolve as resolve3, join as
|
|
1706
|
-
import { existsSync as
|
|
1727
|
+
import { resolve as resolve3, join as join11, dirname as dirname5 } from "path";
|
|
1728
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2, lstatSync, readdirSync as readdirSync2, rmdirSync as rmdirSync2, chmodSync as chmodSync2 } from "fs";
|
|
1707
1729
|
import * as p4 from "@clack/prompts";
|
|
1708
1730
|
import chalk5 from "chalk";
|
|
1709
1731
|
|
|
@@ -1731,6 +1753,553 @@ var REMOVED_FILES = {
|
|
|
1731
1753
|
}
|
|
1732
1754
|
};
|
|
1733
1755
|
|
|
1756
|
+
// src/utils/addonConfig.ts
|
|
1757
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync, mkdirSync, renameSync } from "fs";
|
|
1758
|
+
import { join as join9, dirname as dirname3 } from "path";
|
|
1759
|
+
|
|
1760
|
+
// src/addons/registry.ts
|
|
1761
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1762
|
+
import { join as join8 } from "path";
|
|
1763
|
+
function getAddonSourceDir(name) {
|
|
1764
|
+
const addonsDir = new URL(".", import.meta.url).pathname;
|
|
1765
|
+
return join8(addonsDir, name);
|
|
1766
|
+
}
|
|
1767
|
+
function getAddonManifest(name) {
|
|
1768
|
+
const sourceDir = getAddonSourceDir(name);
|
|
1769
|
+
const manifestPath = join8(sourceDir, "manifest.json");
|
|
1770
|
+
const raw = readFileSync2(manifestPath, "utf-8");
|
|
1771
|
+
const manifest = JSON.parse(raw);
|
|
1772
|
+
if (!manifest.name) throw new Error(`Addon manifest missing "name": ${manifestPath}`);
|
|
1773
|
+
if (!manifest.version) throw new Error(`Addon manifest missing "version": ${manifestPath}`);
|
|
1774
|
+
if (!manifest.files?.skills?.length) throw new Error(`Addon manifest has empty skills: ${manifestPath}`);
|
|
1775
|
+
return manifest;
|
|
1776
|
+
}
|
|
1777
|
+
function getAvailableAddons() {
|
|
1778
|
+
return Object.values(ADDONS);
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// src/utils/addonConfig.ts
|
|
1782
|
+
var CONFIG_DIR = ".claude";
|
|
1783
|
+
var CONFIG_FILE = "devtronic.json";
|
|
1784
|
+
function getConfigPath(targetDir) {
|
|
1785
|
+
return join9(targetDir, CONFIG_DIR, CONFIG_FILE);
|
|
1786
|
+
}
|
|
1787
|
+
function getLegacyConfigPath(targetDir) {
|
|
1788
|
+
return join9(targetDir, CONFIG_FILE);
|
|
1789
|
+
}
|
|
1790
|
+
function readAddonConfig(targetDir) {
|
|
1791
|
+
const configPath = getConfigPath(targetDir);
|
|
1792
|
+
const legacyPath = getLegacyConfigPath(targetDir);
|
|
1793
|
+
if (!existsSync6(configPath) && existsSync6(legacyPath)) {
|
|
1794
|
+
mkdirSync(dirname3(configPath), { recursive: true });
|
|
1795
|
+
renameSync(legacyPath, configPath);
|
|
1796
|
+
}
|
|
1797
|
+
if (!existsSync6(configPath)) {
|
|
1798
|
+
return { agents: ["claude"], installed: {} };
|
|
1799
|
+
}
|
|
1800
|
+
const raw = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1801
|
+
const data = raw.addons ?? raw;
|
|
1802
|
+
return {
|
|
1803
|
+
version: 1,
|
|
1804
|
+
mode: data.mode,
|
|
1805
|
+
agents: data.agents ?? ["claude"],
|
|
1806
|
+
installed: data.installed ?? {}
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
function writeAddonConfig(targetDir, config) {
|
|
1810
|
+
const configPath = getConfigPath(targetDir);
|
|
1811
|
+
mkdirSync(dirname3(configPath), { recursive: true });
|
|
1812
|
+
const payload = { version: 1, ...config };
|
|
1813
|
+
writeFileSync(configPath, JSON.stringify(payload, null, 2) + "\n");
|
|
1814
|
+
}
|
|
1815
|
+
function writeAddonToConfig(targetDir, name, entry) {
|
|
1816
|
+
const config = readAddonConfig(targetDir);
|
|
1817
|
+
config.installed[name] = entry;
|
|
1818
|
+
writeAddonConfig(targetDir, config);
|
|
1819
|
+
}
|
|
1820
|
+
function removeAddonFromConfig(targetDir, name) {
|
|
1821
|
+
const config = readAddonConfig(targetDir);
|
|
1822
|
+
delete config.installed[name];
|
|
1823
|
+
writeAddonConfig(targetDir, config);
|
|
1824
|
+
}
|
|
1825
|
+
function readMode(targetDir) {
|
|
1826
|
+
const config = readAddonConfig(targetDir);
|
|
1827
|
+
return config.mode ?? "hitl";
|
|
1828
|
+
}
|
|
1829
|
+
function writeMode(targetDir, mode) {
|
|
1830
|
+
const config = readAddonConfig(targetDir);
|
|
1831
|
+
config.mode = mode;
|
|
1832
|
+
writeAddonConfig(targetDir, config);
|
|
1833
|
+
}
|
|
1834
|
+
function detectOrphanedAddonFiles(targetDir) {
|
|
1835
|
+
const config = readAddonConfig(targetDir);
|
|
1836
|
+
const orphaned = [];
|
|
1837
|
+
for (const addon of getAvailableAddons()) {
|
|
1838
|
+
if (config.installed[addon.name]) continue;
|
|
1839
|
+
const skillDir = join9(targetDir, ".claude", "skills", addon.name);
|
|
1840
|
+
if (existsSync6(skillDir)) {
|
|
1841
|
+
orphaned.push(addon.name);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
return orphaned;
|
|
1845
|
+
}
|
|
1846
|
+
function registerAddonInConfig(targetDir, addonName) {
|
|
1847
|
+
const manifest = getAddonManifest(addonName);
|
|
1848
|
+
const fileList = [
|
|
1849
|
+
...(manifest.files.skills ?? []).map((s) => `skills/${s}`),
|
|
1850
|
+
...(manifest.files.agents ?? []).map((a) => `agents/${a}.md`),
|
|
1851
|
+
...(manifest.files.rules ?? []).map((r) => `rules/${r}`)
|
|
1852
|
+
];
|
|
1853
|
+
writeAddonToConfig(targetDir, addonName, {
|
|
1854
|
+
version: manifest.version,
|
|
1855
|
+
files: fileList
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
// src/generators/addonFiles.ts
|
|
1860
|
+
import {
|
|
1861
|
+
existsSync as existsSync7,
|
|
1862
|
+
readFileSync as readFileSync4,
|
|
1863
|
+
writeFileSync as writeFileSync2,
|
|
1864
|
+
mkdirSync as mkdirSync2,
|
|
1865
|
+
rmSync,
|
|
1866
|
+
unlinkSync,
|
|
1867
|
+
readdirSync,
|
|
1868
|
+
rmdirSync
|
|
1869
|
+
} from "fs";
|
|
1870
|
+
import { join as join10, dirname as dirname4 } from "path";
|
|
1871
|
+
import { createHash } from "crypto";
|
|
1872
|
+
var AGENT_PATHS = {
|
|
1873
|
+
claude: ".claude",
|
|
1874
|
+
cursor: ".cursor",
|
|
1875
|
+
gemini: ".gemini"
|
|
1876
|
+
};
|
|
1877
|
+
function stripFrontmatterName(content) {
|
|
1878
|
+
return content.replace(/^(---\n)([\s\S]*?)(---)/m, (_match, open, body, close) => {
|
|
1879
|
+
const cleaned = body.split("\n").filter((l) => !l.startsWith("name:")).join("\n");
|
|
1880
|
+
return `${open}${cleaned}${close}`;
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
var RUNTIME_SPECS = {
|
|
1884
|
+
claude: {
|
|
1885
|
+
baseDir: ".claude",
|
|
1886
|
+
skillAdapter: (name, content) => ({
|
|
1887
|
+
relPath: `skills/${name}/SKILL.md`,
|
|
1888
|
+
content
|
|
1889
|
+
}),
|
|
1890
|
+
rulesDir: "rules"
|
|
1891
|
+
},
|
|
1892
|
+
gemini: {
|
|
1893
|
+
baseDir: ".gemini",
|
|
1894
|
+
skillAdapter: (name, content) => ({
|
|
1895
|
+
relPath: `skills/${name}/SKILL.md`,
|
|
1896
|
+
content
|
|
1897
|
+
}),
|
|
1898
|
+
rulesDir: "rules"
|
|
1899
|
+
},
|
|
1900
|
+
opencode: {
|
|
1901
|
+
baseDir: ".opencode",
|
|
1902
|
+
skillAdapter: (name, content) => ({
|
|
1903
|
+
relPath: `command/${name}.md`,
|
|
1904
|
+
content: stripFrontmatterName(content)
|
|
1905
|
+
})
|
|
1906
|
+
},
|
|
1907
|
+
cursor: {
|
|
1908
|
+
baseDir: ".cursor",
|
|
1909
|
+
skillAdapter: (name, content) => ({
|
|
1910
|
+
relPath: `skills/${name}/SKILL.md`,
|
|
1911
|
+
content
|
|
1912
|
+
}),
|
|
1913
|
+
rulesDir: "rules"
|
|
1914
|
+
},
|
|
1915
|
+
codex: {
|
|
1916
|
+
baseDir: ".codex",
|
|
1917
|
+
skillAdapter: (name, content) => ({
|
|
1918
|
+
relPath: `skills/${name}/SKILL.md`,
|
|
1919
|
+
content
|
|
1920
|
+
})
|
|
1921
|
+
}
|
|
1922
|
+
};
|
|
1923
|
+
function checksum(content) {
|
|
1924
|
+
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
1925
|
+
}
|
|
1926
|
+
function ensureDir2(dir) {
|
|
1927
|
+
if (!existsSync7(dir)) mkdirSync2(dir, { recursive: true });
|
|
1928
|
+
}
|
|
1929
|
+
function readManifest2(addonSourceDir) {
|
|
1930
|
+
const manifestPath = join10(addonSourceDir, "manifest.json");
|
|
1931
|
+
return JSON.parse(readFileSync4(manifestPath, "utf-8"));
|
|
1932
|
+
}
|
|
1933
|
+
function buildFileMap(addonSourceDir) {
|
|
1934
|
+
const manifest = readManifest2(addonSourceDir);
|
|
1935
|
+
const files = /* @__PURE__ */ new Map();
|
|
1936
|
+
for (const skill of manifest.files.skills ?? []) {
|
|
1937
|
+
const skillDir = join10(addonSourceDir, "skills", skill);
|
|
1938
|
+
if (!existsSync7(skillDir)) continue;
|
|
1939
|
+
const skillFile = join10(skillDir, "SKILL.md");
|
|
1940
|
+
if (existsSync7(skillFile)) {
|
|
1941
|
+
files.set(`skills/${skill}/SKILL.md`, readFileSync4(skillFile, "utf-8"));
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
for (const agent of manifest.files.agents ?? []) {
|
|
1945
|
+
const agentFile = join10(addonSourceDir, "agents", `${agent}.md`);
|
|
1946
|
+
if (existsSync7(agentFile)) {
|
|
1947
|
+
files.set(`agents/${agent}.md`, readFileSync4(agentFile, "utf-8"));
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
for (const ref of manifest.files.reference ?? []) {
|
|
1951
|
+
const refPath = join10(addonSourceDir, "reference", ref);
|
|
1952
|
+
if (existsSync7(refPath)) {
|
|
1953
|
+
files.set(`skills/design-harden/reference/${ref}`, readFileSync4(refPath, "utf-8"));
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
for (const rule of manifest.files.rules ?? []) {
|
|
1957
|
+
const rulePath = join10(addonSourceDir, "rules", rule);
|
|
1958
|
+
if (existsSync7(rulePath)) {
|
|
1959
|
+
files.set(`rules/${rule}`, readFileSync4(rulePath, "utf-8"));
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
return files;
|
|
1963
|
+
}
|
|
1964
|
+
function generateAddonFiles(projectDir, addonSourceDir, agents) {
|
|
1965
|
+
const fileMap = buildFileMap(addonSourceDir);
|
|
1966
|
+
const manifest = readManifest2(addonSourceDir);
|
|
1967
|
+
const result = { written: 0, skipped: 0, conflicts: [], checksums: {} };
|
|
1968
|
+
for (const agent of agents) {
|
|
1969
|
+
const spec = RUNTIME_SPECS[agent];
|
|
1970
|
+
if (!spec) {
|
|
1971
|
+
const basePath = AGENT_PATHS[agent] ?? `.${agent}`;
|
|
1972
|
+
for (const [relPath, content] of fileMap) {
|
|
1973
|
+
const destPath = join10(projectDir, basePath, relPath);
|
|
1974
|
+
if (existsSync7(destPath)) {
|
|
1975
|
+
result.skipped++;
|
|
1976
|
+
continue;
|
|
1977
|
+
}
|
|
1978
|
+
ensureDir2(dirname4(destPath));
|
|
1979
|
+
writeFileSync2(destPath, content);
|
|
1980
|
+
result.written++;
|
|
1981
|
+
result.checksums[relPath] = checksum(content);
|
|
1982
|
+
}
|
|
1983
|
+
continue;
|
|
1984
|
+
}
|
|
1985
|
+
for (const skillName of manifest.files.skills ?? []) {
|
|
1986
|
+
const skillFile = join10(addonSourceDir, "skills", skillName, "SKILL.md");
|
|
1987
|
+
if (!existsSync7(skillFile)) continue;
|
|
1988
|
+
const rawContent = readFileSync4(skillFile, "utf-8");
|
|
1989
|
+
const { relPath, content } = spec.skillAdapter(skillName, rawContent);
|
|
1990
|
+
const destPath = join10(projectDir, spec.baseDir, relPath);
|
|
1991
|
+
if (existsSync7(destPath)) {
|
|
1992
|
+
const existing = readFileSync4(destPath, "utf-8");
|
|
1993
|
+
if (existing === content) {
|
|
1994
|
+
result.skipped++;
|
|
1995
|
+
continue;
|
|
1996
|
+
}
|
|
1997
|
+
result.skipped++;
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
2000
|
+
ensureDir2(dirname4(destPath));
|
|
2001
|
+
writeFileSync2(destPath, content);
|
|
2002
|
+
result.written++;
|
|
2003
|
+
result.checksums[relPath] = checksum(content);
|
|
2004
|
+
}
|
|
2005
|
+
for (const agentName of manifest.files.agents ?? []) {
|
|
2006
|
+
const agentFile = join10(addonSourceDir, "agents", `${agentName}.md`);
|
|
2007
|
+
if (!existsSync7(agentFile)) continue;
|
|
2008
|
+
const content = readFileSync4(agentFile, "utf-8");
|
|
2009
|
+
const destPath = join10(projectDir, spec.baseDir, "agents", `${agentName}.md`);
|
|
2010
|
+
if (!existsSync7(destPath)) {
|
|
2011
|
+
ensureDir2(dirname4(destPath));
|
|
2012
|
+
writeFileSync2(destPath, content);
|
|
2013
|
+
result.written++;
|
|
2014
|
+
result.checksums[`agents/${agentName}.md`] = checksum(content);
|
|
2015
|
+
} else {
|
|
2016
|
+
result.skipped++;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
if (spec.rulesDir) {
|
|
2020
|
+
for (const rule of manifest.files.rules ?? []) {
|
|
2021
|
+
const ruleSrcPath = join10(addonSourceDir, "rules", rule);
|
|
2022
|
+
if (!existsSync7(ruleSrcPath)) continue;
|
|
2023
|
+
const content = readFileSync4(ruleSrcPath, "utf-8");
|
|
2024
|
+
const destPath = join10(projectDir, spec.baseDir, spec.rulesDir, rule);
|
|
2025
|
+
if (!existsSync7(destPath)) {
|
|
2026
|
+
ensureDir2(dirname4(destPath));
|
|
2027
|
+
writeFileSync2(destPath, content);
|
|
2028
|
+
result.written++;
|
|
2029
|
+
result.checksums[`rules/${rule}`] = checksum(content);
|
|
2030
|
+
} else {
|
|
2031
|
+
result.skipped++;
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
for (const ref of manifest.files.reference ?? []) {
|
|
2036
|
+
const refSrcPath = join10(addonSourceDir, "reference", ref);
|
|
2037
|
+
if (!existsSync7(refSrcPath)) continue;
|
|
2038
|
+
const content = readFileSync4(refSrcPath, "utf-8");
|
|
2039
|
+
const relPath = `skills/design-harden/reference/${ref}`;
|
|
2040
|
+
const destPath = join10(projectDir, spec.baseDir, relPath);
|
|
2041
|
+
if (!existsSync7(destPath)) {
|
|
2042
|
+
ensureDir2(dirname4(destPath));
|
|
2043
|
+
writeFileSync2(destPath, content);
|
|
2044
|
+
result.written++;
|
|
2045
|
+
result.checksums[relPath] = checksum(content);
|
|
2046
|
+
} else {
|
|
2047
|
+
result.skipped++;
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
if (manifest.attribution) {
|
|
2052
|
+
const noticePath = join10(projectDir, "NOTICE.md");
|
|
2053
|
+
const noticeContent = [
|
|
2054
|
+
"# NOTICE",
|
|
2055
|
+
"",
|
|
2056
|
+
"This project includes materials from third-party sources.",
|
|
2057
|
+
"",
|
|
2058
|
+
`## ${manifest.name}`,
|
|
2059
|
+
"",
|
|
2060
|
+
manifest.attribution,
|
|
2061
|
+
"",
|
|
2062
|
+
"Original source: Anthropic frontend-design skill",
|
|
2063
|
+
"License: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)",
|
|
2064
|
+
""
|
|
2065
|
+
].join("\n");
|
|
2066
|
+
writeFileSync2(noticePath, noticeContent);
|
|
2067
|
+
}
|
|
2068
|
+
return result;
|
|
2069
|
+
}
|
|
2070
|
+
function removeAddonFiles(projectDir, addonName, agents, addonSourceDir) {
|
|
2071
|
+
const sourceDir = addonSourceDir ?? getAddonSourceDir(addonName);
|
|
2072
|
+
const manifest = readManifest2(sourceDir);
|
|
2073
|
+
const knownSkills = manifest.files.skills ?? [];
|
|
2074
|
+
const knownAgents = manifest.files.agents ?? [];
|
|
2075
|
+
for (const agent of agents) {
|
|
2076
|
+
const spec = RUNTIME_SPECS[agent];
|
|
2077
|
+
if (!spec) {
|
|
2078
|
+
const basePath = AGENT_PATHS[agent] ?? `.${agent}`;
|
|
2079
|
+
for (const skill of knownSkills) {
|
|
2080
|
+
const skillDir = join10(projectDir, basePath, "skills", skill);
|
|
2081
|
+
if (existsSync7(skillDir)) rmSync(skillDir, { recursive: true, force: true });
|
|
2082
|
+
}
|
|
2083
|
+
for (const agentName of knownAgents) {
|
|
2084
|
+
const agentPath = join10(projectDir, basePath, "agents", `${agentName}.md`);
|
|
2085
|
+
if (existsSync7(agentPath)) unlinkSync(agentPath);
|
|
2086
|
+
}
|
|
2087
|
+
continue;
|
|
2088
|
+
}
|
|
2089
|
+
for (const skillName of knownSkills) {
|
|
2090
|
+
const { relPath } = spec.skillAdapter(skillName, "");
|
|
2091
|
+
const destPath = join10(projectDir, spec.baseDir, relPath);
|
|
2092
|
+
if (existsSync7(destPath)) unlinkSync(destPath);
|
|
2093
|
+
const parentDir = dirname4(destPath);
|
|
2094
|
+
if (existsSync7(parentDir)) {
|
|
2095
|
+
try {
|
|
2096
|
+
const entries = readdirSync(parentDir);
|
|
2097
|
+
if (entries.length === 0) rmdirSync(parentDir);
|
|
2098
|
+
} catch {
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
for (const agentName of knownAgents) {
|
|
2103
|
+
const agentPath = join10(projectDir, spec.baseDir, "agents", `${agentName}.md`);
|
|
2104
|
+
if (existsSync7(agentPath)) unlinkSync(agentPath);
|
|
2105
|
+
}
|
|
2106
|
+
if (spec.rulesDir) {
|
|
2107
|
+
for (const rule of manifest.files.rules ?? []) {
|
|
2108
|
+
const rulePath = join10(projectDir, spec.baseDir, spec.rulesDir, rule);
|
|
2109
|
+
if (existsSync7(rulePath)) unlinkSync(rulePath);
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
for (const ref of manifest.files.reference ?? []) {
|
|
2113
|
+
const refPath = join10(projectDir, spec.baseDir, "skills", "design-harden", "reference", ref);
|
|
2114
|
+
if (existsSync7(refPath)) unlinkSync(refPath);
|
|
2115
|
+
}
|
|
2116
|
+
const refDir = join10(projectDir, spec.baseDir, "skills", "design-harden", "reference");
|
|
2117
|
+
if (existsSync7(refDir)) {
|
|
2118
|
+
try {
|
|
2119
|
+
const entries = readdirSync(refDir);
|
|
2120
|
+
if (entries.length === 0) rmdirSync(refDir);
|
|
2121
|
+
} catch {
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
const noticePath = join10(projectDir, "NOTICE.md");
|
|
2126
|
+
if (existsSync7(noticePath)) unlinkSync(noticePath);
|
|
2127
|
+
}
|
|
2128
|
+
function syncAddonFiles(projectDir, addonSourceDir, agents) {
|
|
2129
|
+
const fileMap = buildFileMap(addonSourceDir);
|
|
2130
|
+
const manifest = readManifest2(addonSourceDir);
|
|
2131
|
+
const addonName = manifest.name;
|
|
2132
|
+
const result = { written: 0, skipped: 0, conflicts: [], updated: 0 };
|
|
2133
|
+
let installedChecksums = {};
|
|
2134
|
+
try {
|
|
2135
|
+
const config = readAddonConfig(projectDir);
|
|
2136
|
+
const installed = config.installed?.[addonName];
|
|
2137
|
+
if (installed?.checksums) {
|
|
2138
|
+
installedChecksums = installed.checksums;
|
|
2139
|
+
}
|
|
2140
|
+
} catch {
|
|
2141
|
+
}
|
|
2142
|
+
for (const agent of agents) {
|
|
2143
|
+
const spec = RUNTIME_SPECS[agent];
|
|
2144
|
+
if (!spec) {
|
|
2145
|
+
const basePath = AGENT_PATHS[agent] ?? `.${agent}`;
|
|
2146
|
+
for (const [relPath, newContent] of fileMap) {
|
|
2147
|
+
const destPath = join10(projectDir, basePath, relPath);
|
|
2148
|
+
if (!existsSync7(destPath)) {
|
|
2149
|
+
ensureDir2(dirname4(destPath));
|
|
2150
|
+
writeFileSync2(destPath, newContent);
|
|
2151
|
+
result.written++;
|
|
2152
|
+
continue;
|
|
2153
|
+
}
|
|
2154
|
+
const existing = readFileSync4(destPath, "utf-8");
|
|
2155
|
+
const existingChecksum = checksum(existing);
|
|
2156
|
+
const originalChecksum = installedChecksums[relPath];
|
|
2157
|
+
if (existing === newContent) {
|
|
2158
|
+
result.skipped++;
|
|
2159
|
+
continue;
|
|
2160
|
+
}
|
|
2161
|
+
if (originalChecksum && existingChecksum !== originalChecksum) {
|
|
2162
|
+
result.conflicts.push(relPath);
|
|
2163
|
+
continue;
|
|
2164
|
+
}
|
|
2165
|
+
writeFileSync2(destPath, newContent);
|
|
2166
|
+
result.updated = (result.updated ?? 0) + 1;
|
|
2167
|
+
}
|
|
2168
|
+
continue;
|
|
2169
|
+
}
|
|
2170
|
+
for (const skillName of manifest.files.skills ?? []) {
|
|
2171
|
+
const skillFile = join10(addonSourceDir, "skills", skillName, "SKILL.md");
|
|
2172
|
+
if (!existsSync7(skillFile)) continue;
|
|
2173
|
+
const rawContent = readFileSync4(skillFile, "utf-8");
|
|
2174
|
+
const { relPath, content: newContent } = spec.skillAdapter(skillName, rawContent);
|
|
2175
|
+
const destPath = join10(projectDir, spec.baseDir, relPath);
|
|
2176
|
+
if (!existsSync7(destPath)) {
|
|
2177
|
+
ensureDir2(dirname4(destPath));
|
|
2178
|
+
writeFileSync2(destPath, newContent);
|
|
2179
|
+
result.written++;
|
|
2180
|
+
continue;
|
|
2181
|
+
}
|
|
2182
|
+
const existing = readFileSync4(destPath, "utf-8");
|
|
2183
|
+
const existingChecksum = checksum(existing);
|
|
2184
|
+
const originalChecksum = installedChecksums[relPath];
|
|
2185
|
+
if (existing === newContent) {
|
|
2186
|
+
result.skipped++;
|
|
2187
|
+
continue;
|
|
2188
|
+
}
|
|
2189
|
+
if (originalChecksum && existingChecksum !== originalChecksum) {
|
|
2190
|
+
result.conflicts.push(relPath);
|
|
2191
|
+
continue;
|
|
2192
|
+
}
|
|
2193
|
+
writeFileSync2(destPath, newContent);
|
|
2194
|
+
result.updated = (result.updated ?? 0) + 1;
|
|
2195
|
+
}
|
|
2196
|
+
for (const agentName of manifest.files.agents ?? []) {
|
|
2197
|
+
const agentFile = join10(addonSourceDir, "agents", `${agentName}.md`);
|
|
2198
|
+
if (!existsSync7(agentFile)) continue;
|
|
2199
|
+
const newContent = readFileSync4(agentFile, "utf-8");
|
|
2200
|
+
const destPath = join10(projectDir, spec.baseDir, "agents", `${agentName}.md`);
|
|
2201
|
+
const relPath = `agents/${agentName}.md`;
|
|
2202
|
+
if (!existsSync7(destPath)) {
|
|
2203
|
+
ensureDir2(dirname4(destPath));
|
|
2204
|
+
writeFileSync2(destPath, newContent);
|
|
2205
|
+
result.written++;
|
|
2206
|
+
continue;
|
|
2207
|
+
}
|
|
2208
|
+
const existing = readFileSync4(destPath, "utf-8");
|
|
2209
|
+
const existingChecksum = checksum(existing);
|
|
2210
|
+
const originalChecksum = installedChecksums[relPath];
|
|
2211
|
+
if (existing === newContent) {
|
|
2212
|
+
result.skipped++;
|
|
2213
|
+
continue;
|
|
2214
|
+
}
|
|
2215
|
+
if (originalChecksum && existingChecksum !== originalChecksum) {
|
|
2216
|
+
result.conflicts.push(relPath);
|
|
2217
|
+
continue;
|
|
2218
|
+
}
|
|
2219
|
+
writeFileSync2(destPath, newContent);
|
|
2220
|
+
result.updated = (result.updated ?? 0) + 1;
|
|
2221
|
+
}
|
|
2222
|
+
if (spec.rulesDir) {
|
|
2223
|
+
for (const rule of manifest.files.rules ?? []) {
|
|
2224
|
+
const ruleSrcPath = join10(addonSourceDir, "rules", rule);
|
|
2225
|
+
if (!existsSync7(ruleSrcPath)) continue;
|
|
2226
|
+
const newContent = readFileSync4(ruleSrcPath, "utf-8");
|
|
2227
|
+
const destPath = join10(projectDir, spec.baseDir, spec.rulesDir, rule);
|
|
2228
|
+
const relPath = `rules/${rule}`;
|
|
2229
|
+
if (!existsSync7(destPath)) {
|
|
2230
|
+
ensureDir2(dirname4(destPath));
|
|
2231
|
+
writeFileSync2(destPath, newContent);
|
|
2232
|
+
result.written++;
|
|
2233
|
+
continue;
|
|
2234
|
+
}
|
|
2235
|
+
const existing = readFileSync4(destPath, "utf-8");
|
|
2236
|
+
const existingChecksum = checksum(existing);
|
|
2237
|
+
const originalChecksum = installedChecksums[relPath];
|
|
2238
|
+
if (existing === newContent) {
|
|
2239
|
+
result.skipped++;
|
|
2240
|
+
continue;
|
|
2241
|
+
}
|
|
2242
|
+
if (originalChecksum && existingChecksum !== originalChecksum) {
|
|
2243
|
+
result.conflicts.push(relPath);
|
|
2244
|
+
continue;
|
|
2245
|
+
}
|
|
2246
|
+
writeFileSync2(destPath, newContent);
|
|
2247
|
+
result.updated = (result.updated ?? 0) + 1;
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
for (const ref of manifest.files.reference ?? []) {
|
|
2251
|
+
const refSrcPath = join10(addonSourceDir, "reference", ref);
|
|
2252
|
+
if (!existsSync7(refSrcPath)) continue;
|
|
2253
|
+
const newContent = readFileSync4(refSrcPath, "utf-8");
|
|
2254
|
+
const relPath = `skills/design-harden/reference/${ref}`;
|
|
2255
|
+
const destPath = join10(projectDir, spec.baseDir, relPath);
|
|
2256
|
+
if (!existsSync7(destPath)) {
|
|
2257
|
+
ensureDir2(dirname4(destPath));
|
|
2258
|
+
writeFileSync2(destPath, newContent);
|
|
2259
|
+
result.written++;
|
|
2260
|
+
continue;
|
|
2261
|
+
}
|
|
2262
|
+
const existing = readFileSync4(destPath, "utf-8");
|
|
2263
|
+
const existingChecksum = checksum(existing);
|
|
2264
|
+
const originalChecksum = installedChecksums[relPath];
|
|
2265
|
+
if (existing === newContent) {
|
|
2266
|
+
result.skipped++;
|
|
2267
|
+
continue;
|
|
2268
|
+
}
|
|
2269
|
+
if (originalChecksum && existingChecksum !== originalChecksum) {
|
|
2270
|
+
result.conflicts.push(relPath);
|
|
2271
|
+
continue;
|
|
2272
|
+
}
|
|
2273
|
+
writeFileSync2(destPath, newContent);
|
|
2274
|
+
result.updated = (result.updated ?? 0) + 1;
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
return result;
|
|
2278
|
+
}
|
|
2279
|
+
function detectModifiedAddonFiles(projectDir, addonName) {
|
|
2280
|
+
let installedChecksums = {};
|
|
2281
|
+
try {
|
|
2282
|
+
const config = readAddonConfig(projectDir);
|
|
2283
|
+
const installed = config.installed?.[addonName];
|
|
2284
|
+
if (!installed?.checksums) return [];
|
|
2285
|
+
installedChecksums = installed.checksums;
|
|
2286
|
+
} catch {
|
|
2287
|
+
return [];
|
|
2288
|
+
}
|
|
2289
|
+
const modified = [];
|
|
2290
|
+
for (const agentDir of Object.values(AGENT_PATHS)) {
|
|
2291
|
+
for (const [relPath, originalHash] of Object.entries(installedChecksums)) {
|
|
2292
|
+
const absPath = join10(projectDir, agentDir, relPath);
|
|
2293
|
+
if (!existsSync7(absPath)) continue;
|
|
2294
|
+
const current = checksum(readFileSync4(absPath, "utf-8"));
|
|
2295
|
+
if (current !== originalHash) {
|
|
2296
|
+
modified.push(relPath);
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
return [...new Set(modified)];
|
|
2301
|
+
}
|
|
2302
|
+
|
|
1734
2303
|
// src/commands/update.ts
|
|
1735
2304
|
async function updateCommand(options) {
|
|
1736
2305
|
if (!options.check && !options.dryRun) {
|
|
@@ -1796,7 +2365,7 @@ async function updateCommand(options) {
|
|
|
1796
2365
|
const modifiedFiles = [];
|
|
1797
2366
|
const outdatedFiles = [];
|
|
1798
2367
|
for (const [relativePath, fileInfo] of Object.entries(manifest.files)) {
|
|
1799
|
-
const filePath =
|
|
2368
|
+
const filePath = join11(targetDir, relativePath);
|
|
1800
2369
|
if (!fileExists(filePath)) {
|
|
1801
2370
|
continue;
|
|
1802
2371
|
}
|
|
@@ -1806,17 +2375,20 @@ async function updateCommand(options) {
|
|
|
1806
2375
|
modifiedFiles.push(relativePath);
|
|
1807
2376
|
}
|
|
1808
2377
|
}
|
|
2378
|
+
const newFiles = [];
|
|
1809
2379
|
for (const ide of manifest.selectedIDEs) {
|
|
1810
|
-
const templateDir =
|
|
1811
|
-
if (!
|
|
2380
|
+
const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
|
|
2381
|
+
if (!existsSync8(templateDir)) continue;
|
|
1812
2382
|
const files = getAllFilesRecursive(templateDir);
|
|
1813
2383
|
for (const file of files) {
|
|
1814
|
-
const templatePath =
|
|
2384
|
+
const templatePath = join11(templateDir, file);
|
|
1815
2385
|
const templateContent = readFile(templatePath);
|
|
1816
2386
|
const templateChecksum = calculateChecksum(templateContent);
|
|
1817
2387
|
const fileInfo = manifest.files[file];
|
|
1818
2388
|
if (fileInfo && fileInfo.checksum !== templateChecksum) {
|
|
1819
2389
|
outdatedFiles.push(file);
|
|
2390
|
+
} else if (!fileInfo) {
|
|
2391
|
+
newFiles.push(file);
|
|
1820
2392
|
}
|
|
1821
2393
|
}
|
|
1822
2394
|
}
|
|
@@ -1825,14 +2397,14 @@ async function updateCommand(options) {
|
|
|
1825
2397
|
if (fileInfo.ignored) continue;
|
|
1826
2398
|
let foundInAnyTemplate = false;
|
|
1827
2399
|
for (const ide of manifest.selectedIDEs) {
|
|
1828
|
-
const templateDir =
|
|
1829
|
-
if (
|
|
2400
|
+
const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
|
|
2401
|
+
if (existsSync8(join11(templateDir, relativePath))) {
|
|
1830
2402
|
foundInAnyTemplate = true;
|
|
1831
2403
|
break;
|
|
1832
2404
|
}
|
|
1833
2405
|
}
|
|
1834
2406
|
if (!foundInAnyTemplate) {
|
|
1835
|
-
const localPath =
|
|
2407
|
+
const localPath = join11(targetDir, relativePath);
|
|
1836
2408
|
if (fileExists(localPath)) {
|
|
1837
2409
|
removedFromTemplate.push({
|
|
1838
2410
|
path: relativePath,
|
|
@@ -1847,7 +2419,7 @@ async function updateCommand(options) {
|
|
|
1847
2419
|
"Locally Modified Files (will be preserved)"
|
|
1848
2420
|
);
|
|
1849
2421
|
}
|
|
1850
|
-
if (outdatedFiles.length === 0 && removedFromTemplate.length === 0) {
|
|
2422
|
+
if (outdatedFiles.length === 0 && removedFromTemplate.length === 0 && newFiles.length === 0) {
|
|
1851
2423
|
p4.log.success("All files are up to date!");
|
|
1852
2424
|
p4.outro("No updates needed");
|
|
1853
2425
|
return;
|
|
@@ -1859,6 +2431,12 @@ async function updateCommand(options) {
|
|
|
1859
2431
|
"Files to Update"
|
|
1860
2432
|
);
|
|
1861
2433
|
}
|
|
2434
|
+
if (newFiles.length > 0) {
|
|
2435
|
+
p4.note(
|
|
2436
|
+
newFiles.map((f) => ` ${symbols.star} ${f}`).join("\n"),
|
|
2437
|
+
"New Files in This Version"
|
|
2438
|
+
);
|
|
2439
|
+
}
|
|
1862
2440
|
const conflictFiles = outdatedFiles.filter((f) => modifiedFiles.includes(f));
|
|
1863
2441
|
if (conflictFiles.length > 0) {
|
|
1864
2442
|
p4.note(
|
|
@@ -1925,15 +2503,17 @@ async function updateCommand(options) {
|
|
|
1925
2503
|
return;
|
|
1926
2504
|
}
|
|
1927
2505
|
const hasUpdates = filesToUpdate.length > 0;
|
|
2506
|
+
const hasNewFiles = newFiles.length > 0;
|
|
1928
2507
|
const hasDeletions = filesToDelete.length > 0;
|
|
1929
2508
|
const hasIgnores = filesToIgnore.length > 0;
|
|
1930
|
-
if (!hasUpdates && !hasDeletions && !hasIgnores) {
|
|
2509
|
+
if (!hasUpdates && !hasNewFiles && !hasDeletions && !hasIgnores) {
|
|
1931
2510
|
p4.log.success("No changes to apply");
|
|
1932
2511
|
p4.outro("Update complete");
|
|
1933
2512
|
return;
|
|
1934
2513
|
}
|
|
1935
2514
|
const actionParts = [];
|
|
1936
2515
|
if (hasUpdates) actionParts.push(`update ${filesToUpdate.length} files`);
|
|
2516
|
+
if (hasNewFiles) actionParts.push(`add ${newFiles.length} new files`);
|
|
1937
2517
|
if (hasDeletions) actionParts.push(`delete ${filesToDelete.length} files`);
|
|
1938
2518
|
if (hasIgnores) actionParts.push(`ignore ${filesToIgnore.length} files`);
|
|
1939
2519
|
const proceed = await p4.confirm({
|
|
@@ -1952,8 +2532,8 @@ async function updateCommand(options) {
|
|
|
1952
2532
|
implantedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
1953
2533
|
};
|
|
1954
2534
|
for (const ide of manifest.selectedIDEs) {
|
|
1955
|
-
const templateDir =
|
|
1956
|
-
if (!
|
|
2535
|
+
const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
|
|
2536
|
+
if (!existsSync8(templateDir)) continue;
|
|
1957
2537
|
const isPluginMode = ide === "claude-code" && manifest.installMode === "plugin";
|
|
1958
2538
|
const files = getAllFilesRecursive(templateDir);
|
|
1959
2539
|
for (const file of files) {
|
|
@@ -1963,10 +2543,10 @@ async function updateCommand(options) {
|
|
|
1963
2543
|
if (isPluginMode && (file.startsWith(".claude/skills/") || file.startsWith(".claude/agents/"))) {
|
|
1964
2544
|
continue;
|
|
1965
2545
|
}
|
|
1966
|
-
const templatePath =
|
|
1967
|
-
const destPath =
|
|
2546
|
+
const templatePath = join11(templateDir, file);
|
|
2547
|
+
const destPath = join11(targetDir, file);
|
|
1968
2548
|
const templateContent = readFile(templatePath);
|
|
1969
|
-
ensureDir(
|
|
2549
|
+
ensureDir(dirname5(destPath));
|
|
1970
2550
|
writeFile(destPath, templateContent);
|
|
1971
2551
|
updatedManifest.files[file] = createManifestEntry(templateContent);
|
|
1972
2552
|
}
|
|
@@ -1974,7 +2554,7 @@ async function updateCommand(options) {
|
|
|
1974
2554
|
if (manifest.installMode === "plugin" && manifest.pluginPath) {
|
|
1975
2555
|
const userModifiedPluginFiles = /* @__PURE__ */ new Map();
|
|
1976
2556
|
for (const [relPath, fileInfo] of Object.entries(updatedManifest.files)) {
|
|
1977
|
-
const filePath =
|
|
2557
|
+
const filePath = join11(targetDir, relPath);
|
|
1978
2558
|
if (!fileExists(filePath)) continue;
|
|
1979
2559
|
const diskContent = readFile(filePath);
|
|
1980
2560
|
const diskChecksum = calculateChecksum(diskContent);
|
|
@@ -1991,13 +2571,13 @@ async function updateCommand(options) {
|
|
|
1991
2571
|
analysis.packageManager
|
|
1992
2572
|
);
|
|
1993
2573
|
for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
|
|
1994
|
-
const scriptPath =
|
|
1995
|
-
if (
|
|
2574
|
+
const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
|
|
2575
|
+
if (existsSync8(scriptPath)) {
|
|
1996
2576
|
chmodSync2(scriptPath, 493);
|
|
1997
2577
|
}
|
|
1998
2578
|
}
|
|
1999
2579
|
for (const [relPath, content] of userModifiedPluginFiles) {
|
|
2000
|
-
writeFile(
|
|
2580
|
+
writeFile(join11(targetDir, relPath), content);
|
|
2001
2581
|
}
|
|
2002
2582
|
for (const [relPath, newEntry] of Object.entries(pluginResult.files)) {
|
|
2003
2583
|
if (userModifiedPluginFiles.has(relPath)) {
|
|
@@ -2008,9 +2588,9 @@ async function updateCommand(options) {
|
|
|
2008
2588
|
updatedManifest.pluginPath = pluginResult.pluginPath;
|
|
2009
2589
|
}
|
|
2010
2590
|
for (const filePath of filesToDelete) {
|
|
2011
|
-
const localPath =
|
|
2591
|
+
const localPath = join11(targetDir, filePath);
|
|
2012
2592
|
if (fileExists(localPath)) {
|
|
2013
|
-
|
|
2593
|
+
unlinkSync2(localPath);
|
|
2014
2594
|
}
|
|
2015
2595
|
delete updatedManifest.files[filePath];
|
|
2016
2596
|
}
|
|
@@ -2019,12 +2599,12 @@ async function updateCommand(options) {
|
|
|
2019
2599
|
updatedManifest.files[filePath].ignored = true;
|
|
2020
2600
|
}
|
|
2021
2601
|
}
|
|
2022
|
-
const claudeMdPath =
|
|
2602
|
+
const claudeMdPath = join11(targetDir, "CLAUDE.md");
|
|
2023
2603
|
if (fileExists(claudeMdPath)) {
|
|
2024
2604
|
const stat = lstatSync(claudeMdPath);
|
|
2025
2605
|
if (stat.isSymbolicLink()) {
|
|
2026
2606
|
const content = readFile(claudeMdPath);
|
|
2027
|
-
|
|
2607
|
+
unlinkSync2(claudeMdPath);
|
|
2028
2608
|
writeFile(claudeMdPath, content);
|
|
2029
2609
|
updatedManifest.files["CLAUDE.md"] = createManifestEntry(content);
|
|
2030
2610
|
p4.log.info("Migrated CLAUDE.md from symlink to independent file.");
|
|
@@ -2032,6 +2612,28 @@ async function updateCommand(options) {
|
|
|
2032
2612
|
}
|
|
2033
2613
|
writeManifest(targetDir, updatedManifest);
|
|
2034
2614
|
spinner8.stop("Updates applied");
|
|
2615
|
+
const orphaned = detectOrphanedAddonFiles(targetDir);
|
|
2616
|
+
if (orphaned.length > 0) {
|
|
2617
|
+
const migSpinner = p4.spinner();
|
|
2618
|
+
migSpinner.start("Migrating detected addon files to config...");
|
|
2619
|
+
for (const addonName of orphaned) {
|
|
2620
|
+
registerAddonInConfig(targetDir, addonName);
|
|
2621
|
+
}
|
|
2622
|
+
migSpinner.stop(`${symbols.pass} Migrated: ${orphaned.join(", ")}`);
|
|
2623
|
+
}
|
|
2624
|
+
const addonConfig = readAddonConfig(targetDir);
|
|
2625
|
+
const enabledAddons = Object.keys(addonConfig.installed);
|
|
2626
|
+
if (enabledAddons.length > 0 && !options.check) {
|
|
2627
|
+
const syncSpinner = p4.spinner();
|
|
2628
|
+
syncSpinner.start("Updating enabled addon files...");
|
|
2629
|
+
let totalUpdated = 0;
|
|
2630
|
+
for (const name of enabledAddons) {
|
|
2631
|
+
const addonSourceDir = getAddonSourceDir(name);
|
|
2632
|
+
const result = syncAddonFiles(targetDir, addonSourceDir, addonConfig.agents);
|
|
2633
|
+
totalUpdated += (result.updated ?? 0) + result.written;
|
|
2634
|
+
}
|
|
2635
|
+
syncSpinner.stop(`${symbols.pass} Addon files updated (${totalUpdated} files)`);
|
|
2636
|
+
}
|
|
2035
2637
|
p4.outro(chalk5.green(`Updated to version ${currentVersion}`));
|
|
2036
2638
|
}
|
|
2037
2639
|
function detectStackChanges(savedConfig, analysis) {
|
|
@@ -2138,7 +2740,7 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2138
2740
|
const spinner8 = p4.spinner();
|
|
2139
2741
|
spinner8.start("Regenerating with new stack...");
|
|
2140
2742
|
const regeneratedFiles = [];
|
|
2141
|
-
const agentsMdPath =
|
|
2743
|
+
const agentsMdPath = join11(targetDir, "AGENTS.md");
|
|
2142
2744
|
if (fileExists(agentsMdPath)) {
|
|
2143
2745
|
const agentsMdContent = generateAgentsMdFromConfig(
|
|
2144
2746
|
newConfig,
|
|
@@ -2149,12 +2751,12 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2149
2751
|
regeneratedFiles.push("AGENTS.md");
|
|
2150
2752
|
manifest.files["AGENTS.md"] = createManifestEntry(agentsMdContent);
|
|
2151
2753
|
}
|
|
2152
|
-
const claudeMdPath =
|
|
2754
|
+
const claudeMdPath = join11(targetDir, "CLAUDE.md");
|
|
2153
2755
|
if (fileExists(claudeMdPath)) {
|
|
2154
2756
|
const stat = lstatSync(claudeMdPath);
|
|
2155
2757
|
if (stat.isSymbolicLink()) {
|
|
2156
2758
|
const content = readFile(claudeMdPath);
|
|
2157
|
-
|
|
2759
|
+
unlinkSync2(claudeMdPath);
|
|
2158
2760
|
writeFile(claudeMdPath, content);
|
|
2159
2761
|
manifest.files["CLAUDE.md"] = createManifestEntry(content);
|
|
2160
2762
|
p4.log.info("Migrated CLAUDE.md from symlink to independent file.");
|
|
@@ -2166,8 +2768,8 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2166
2768
|
const ruleContent = getRuleContentForIDE(ide, generatedRules);
|
|
2167
2769
|
const rulePath = DYNAMIC_RULE_FILES[ide]?.[0];
|
|
2168
2770
|
if (ruleContent && rulePath) {
|
|
2169
|
-
const destPath =
|
|
2170
|
-
ensureDir(
|
|
2771
|
+
const destPath = join11(targetDir, rulePath);
|
|
2772
|
+
ensureDir(dirname5(destPath));
|
|
2171
2773
|
writeFile(destPath, ruleContent);
|
|
2172
2774
|
regeneratedFiles.push(rulePath);
|
|
2173
2775
|
manifest.files[rulePath] = createManifestEntry(ruleContent);
|
|
@@ -2177,7 +2779,7 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2177
2779
|
if (manifest.installMode === "plugin" && manifest.pluginPath) {
|
|
2178
2780
|
const userModifiedPluginFiles = /* @__PURE__ */ new Map();
|
|
2179
2781
|
for (const [relPath, fileInfo] of Object.entries(manifest.files)) {
|
|
2180
|
-
const filePath =
|
|
2782
|
+
const filePath = join11(targetDir, relPath);
|
|
2181
2783
|
if (!fileExists(filePath)) continue;
|
|
2182
2784
|
const diskContent = readFile(filePath);
|
|
2183
2785
|
const diskChecksum = calculateChecksum(diskContent);
|
|
@@ -2193,13 +2795,13 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2193
2795
|
analysis.packageManager
|
|
2194
2796
|
);
|
|
2195
2797
|
for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
|
|
2196
|
-
const scriptPath =
|
|
2197
|
-
if (
|
|
2798
|
+
const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
|
|
2799
|
+
if (existsSync8(scriptPath)) {
|
|
2198
2800
|
chmodSync2(scriptPath, 493);
|
|
2199
2801
|
}
|
|
2200
2802
|
}
|
|
2201
2803
|
for (const [relPath, content] of userModifiedPluginFiles) {
|
|
2202
|
-
writeFile(
|
|
2804
|
+
writeFile(join11(targetDir, relPath), content);
|
|
2203
2805
|
}
|
|
2204
2806
|
for (const [relPath, newEntry] of Object.entries(pluginResult.files)) {
|
|
2205
2807
|
if (userModifiedPluginFiles.has(relPath)) {
|
|
@@ -2242,8 +2844,8 @@ async function migrateToPlugin(targetDir, manifest, analysis, dryRun) {
|
|
|
2242
2844
|
analysis.packageManager
|
|
2243
2845
|
);
|
|
2244
2846
|
for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
|
|
2245
|
-
const scriptPath =
|
|
2246
|
-
if (
|
|
2847
|
+
const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
|
|
2848
|
+
if (existsSync8(scriptPath)) {
|
|
2247
2849
|
chmodSync2(scriptPath, 493);
|
|
2248
2850
|
}
|
|
2249
2851
|
}
|
|
@@ -2252,19 +2854,19 @@ async function migrateToPlugin(targetDir, manifest, analysis, dryRun) {
|
|
|
2252
2854
|
const preserved = [];
|
|
2253
2855
|
for (const [path, fileInfo] of Object.entries(manifest.files)) {
|
|
2254
2856
|
if (!path.startsWith(".claude/skills/") && !path.startsWith(".claude/agents/")) continue;
|
|
2255
|
-
const filePath =
|
|
2857
|
+
const filePath = join11(targetDir, path);
|
|
2256
2858
|
if (!fileExists(filePath)) continue;
|
|
2257
2859
|
const current = calculateChecksum(readFile(filePath));
|
|
2258
2860
|
if (current === fileInfo.originalChecksum) {
|
|
2259
|
-
|
|
2861
|
+
unlinkSync2(filePath);
|
|
2260
2862
|
delete manifest.files[path];
|
|
2261
2863
|
removed.push(path);
|
|
2262
2864
|
} else {
|
|
2263
2865
|
preserved.push(path);
|
|
2264
2866
|
}
|
|
2265
2867
|
}
|
|
2266
|
-
cleanEmptyDirs(
|
|
2267
|
-
cleanEmptyDirs(
|
|
2868
|
+
cleanEmptyDirs(join11(targetDir, ".claude", "skills"));
|
|
2869
|
+
cleanEmptyDirs(join11(targetDir, ".claude", "agents"));
|
|
2268
2870
|
Object.assign(manifest.files, pluginResult.files);
|
|
2269
2871
|
manifest.installMode = "plugin";
|
|
2270
2872
|
manifest.pluginPath = pluginResult.pluginPath;
|
|
@@ -2313,22 +2915,22 @@ function buildDefaultConfig(analysis) {
|
|
|
2313
2915
|
};
|
|
2314
2916
|
}
|
|
2315
2917
|
function cleanEmptyDirs(dirPath) {
|
|
2316
|
-
if (!
|
|
2317
|
-
const entries =
|
|
2918
|
+
if (!existsSync8(dirPath)) return;
|
|
2919
|
+
const entries = readdirSync2(dirPath);
|
|
2318
2920
|
for (const entry of entries) {
|
|
2319
|
-
const fullPath =
|
|
2320
|
-
if (
|
|
2921
|
+
const fullPath = join11(dirPath, entry);
|
|
2922
|
+
if (existsSync8(fullPath) && lstatSync(fullPath).isDirectory()) {
|
|
2321
2923
|
cleanEmptyDirs(fullPath);
|
|
2322
2924
|
}
|
|
2323
2925
|
}
|
|
2324
|
-
const remaining =
|
|
2926
|
+
const remaining = readdirSync2(dirPath);
|
|
2325
2927
|
if (remaining.length === 0) {
|
|
2326
|
-
|
|
2928
|
+
rmdirSync2(dirPath);
|
|
2327
2929
|
}
|
|
2328
2930
|
}
|
|
2329
2931
|
|
|
2330
2932
|
// src/commands/status.ts
|
|
2331
|
-
import { resolve as resolve4, join as
|
|
2933
|
+
import { resolve as resolve4, join as join12 } from "path";
|
|
2332
2934
|
import * as p5 from "@clack/prompts";
|
|
2333
2935
|
import chalk6 from "chalk";
|
|
2334
2936
|
async function statusCommand(options = {}) {
|
|
@@ -2342,6 +2944,22 @@ async function statusCommand(options = {}) {
|
|
|
2342
2944
|
p5.outro("");
|
|
2343
2945
|
return;
|
|
2344
2946
|
}
|
|
2947
|
+
const mode = readMode(targetDir);
|
|
2948
|
+
const addonConfig = readAddonConfig(targetDir);
|
|
2949
|
+
const allAddons = getAvailableAddons();
|
|
2950
|
+
p5.note(
|
|
2951
|
+
[
|
|
2952
|
+
`${formatKV("Mode:", chalk6.cyan(mode))}`,
|
|
2953
|
+
`${formatKV("Config:", chalk6.dim(".claude/devtronic.json"))}`
|
|
2954
|
+
].join("\n"),
|
|
2955
|
+
"Execution Mode"
|
|
2956
|
+
);
|
|
2957
|
+
const addonLines = allAddons.map((addon) => {
|
|
2958
|
+
const isEnabled = !!addonConfig.installed[addon.name];
|
|
2959
|
+
const statusStr = isEnabled ? chalk6.green("enabled") : chalk6.dim("disabled");
|
|
2960
|
+
return ` ${chalk6.bold((addon.name + ":").padEnd(28))} ${statusStr}`;
|
|
2961
|
+
});
|
|
2962
|
+
p5.note(addonLines.join("\n"), "Addons");
|
|
2345
2963
|
p5.note(
|
|
2346
2964
|
[
|
|
2347
2965
|
formatKV("Version:", manifest.version),
|
|
@@ -2352,7 +2970,7 @@ async function statusCommand(options = {}) {
|
|
|
2352
2970
|
);
|
|
2353
2971
|
const fileStatuses = [];
|
|
2354
2972
|
for (const [relativePath, fileInfo] of Object.entries(manifest.files)) {
|
|
2355
|
-
const filePath =
|
|
2973
|
+
const filePath = join12(targetDir, relativePath);
|
|
2356
2974
|
if (!fileExists(filePath)) {
|
|
2357
2975
|
fileStatuses.push({ path: relativePath, status: "missing" });
|
|
2358
2976
|
continue;
|
|
@@ -2398,8 +3016,8 @@ async function statusCommand(options = {}) {
|
|
|
2398
3016
|
}
|
|
2399
3017
|
|
|
2400
3018
|
// src/commands/diff.ts
|
|
2401
|
-
import { resolve as resolve5, join as
|
|
2402
|
-
import { existsSync as
|
|
3019
|
+
import { resolve as resolve5, join as join13 } from "path";
|
|
3020
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2403
3021
|
import * as p6 from "@clack/prompts";
|
|
2404
3022
|
import chalk7 from "chalk";
|
|
2405
3023
|
async function diffCommand(options = {}) {
|
|
@@ -2414,12 +3032,12 @@ async function diffCommand(options = {}) {
|
|
|
2414
3032
|
}
|
|
2415
3033
|
const diffs = [];
|
|
2416
3034
|
for (const ide of manifest.selectedIDEs) {
|
|
2417
|
-
const templateDir =
|
|
2418
|
-
if (!
|
|
3035
|
+
const templateDir = join13(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
|
|
3036
|
+
if (!existsSync9(templateDir)) continue;
|
|
2419
3037
|
const templateFiles = getAllFilesRecursive(templateDir);
|
|
2420
3038
|
for (const file of templateFiles) {
|
|
2421
|
-
const templatePath =
|
|
2422
|
-
const localPath =
|
|
3039
|
+
const templatePath = join13(templateDir, file);
|
|
3040
|
+
const localPath = join13(targetDir, file);
|
|
2423
3041
|
const templateContent = readFile(templatePath);
|
|
2424
3042
|
const templateChecksum = calculateChecksum(templateContent);
|
|
2425
3043
|
const manifestEntry = manifest.files[file];
|
|
@@ -2472,8 +3090,8 @@ async function diffCommand(options = {}) {
|
|
|
2472
3090
|
}
|
|
2473
3091
|
|
|
2474
3092
|
// src/commands/add.ts
|
|
2475
|
-
import { existsSync as
|
|
2476
|
-
import { resolve as resolve6, join as
|
|
3093
|
+
import { existsSync as existsSync10 } from "fs";
|
|
3094
|
+
import { resolve as resolve6, join as join14, dirname as dirname6 } from "path";
|
|
2477
3095
|
import * as p7 from "@clack/prompts";
|
|
2478
3096
|
import chalk8 from "chalk";
|
|
2479
3097
|
var ALL_IDES = [
|
|
@@ -2567,8 +3185,8 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2567
3185
|
const generatedFiles = [];
|
|
2568
3186
|
const generatedRules = generateArchitectureRules(manifest.projectConfig);
|
|
2569
3187
|
const templateName = IDE_TEMPLATE_MAP[selectedIDE];
|
|
2570
|
-
const templateDir =
|
|
2571
|
-
if (!
|
|
3188
|
+
const templateDir = join14(TEMPLATES_DIR, templateName);
|
|
3189
|
+
if (!existsSync10(templateDir)) {
|
|
2572
3190
|
spinner8.stop("Error");
|
|
2573
3191
|
p7.cancel(`Template not found: ${templateName}`);
|
|
2574
3192
|
process.exit(1);
|
|
@@ -2579,8 +3197,8 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2579
3197
|
if (dynamicFiles.includes(file)) {
|
|
2580
3198
|
continue;
|
|
2581
3199
|
}
|
|
2582
|
-
const sourcePath =
|
|
2583
|
-
const destPath =
|
|
3200
|
+
const sourcePath = join14(templateDir, file);
|
|
3201
|
+
const destPath = join14(targetDir, file);
|
|
2584
3202
|
const sourceContent = readFile(sourcePath);
|
|
2585
3203
|
if (fileExists(destPath)) {
|
|
2586
3204
|
if (conflictResolution === "keep") {
|
|
@@ -2597,7 +3215,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2597
3215
|
continue;
|
|
2598
3216
|
}
|
|
2599
3217
|
}
|
|
2600
|
-
ensureDir(
|
|
3218
|
+
ensureDir(dirname6(destPath));
|
|
2601
3219
|
writeFile(destPath, sourceContent);
|
|
2602
3220
|
appliedFiles.push(file);
|
|
2603
3221
|
manifest.files[file] = createManifestEntry(sourceContent);
|
|
@@ -2606,7 +3224,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2606
3224
|
if (ruleContent) {
|
|
2607
3225
|
const rulePath = dynamicFiles[0];
|
|
2608
3226
|
if (rulePath) {
|
|
2609
|
-
const destPath =
|
|
3227
|
+
const destPath = join14(targetDir, rulePath);
|
|
2610
3228
|
if (fileExists(destPath) && conflictResolution === "keep") {
|
|
2611
3229
|
skippedFiles.push(rulePath);
|
|
2612
3230
|
} else if (fileExists(destPath) && conflictResolution === "merge") {
|
|
@@ -2617,7 +3235,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2617
3235
|
mergedFiles.push(rulePath);
|
|
2618
3236
|
manifest.files[rulePath] = createManifestEntry(mergedContent);
|
|
2619
3237
|
} else {
|
|
2620
|
-
ensureDir(
|
|
3238
|
+
ensureDir(dirname6(destPath));
|
|
2621
3239
|
writeFile(destPath, ruleContent);
|
|
2622
3240
|
generatedFiles.push(`${rulePath} (personalized)`);
|
|
2623
3241
|
manifest.files[rulePath] = createManifestEntry(ruleContent);
|
|
@@ -2658,14 +3276,14 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2658
3276
|
}
|
|
2659
3277
|
|
|
2660
3278
|
// src/commands/regenerate.ts
|
|
2661
|
-
import { existsSync as
|
|
2662
|
-
import { resolve as resolve7, join as
|
|
3279
|
+
import { existsSync as existsSync11 } from "fs";
|
|
3280
|
+
import { resolve as resolve7, join as join15, dirname as dirname7 } from "path";
|
|
2663
3281
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2664
3282
|
import * as p8 from "@clack/prompts";
|
|
2665
3283
|
import chalk9 from "chalk";
|
|
2666
3284
|
var __filename = fileURLToPath3(import.meta.url);
|
|
2667
|
-
var __regen_dirname =
|
|
2668
|
-
var TEMPLATES_DIR2 =
|
|
3285
|
+
var __regen_dirname = dirname7(__filename);
|
|
3286
|
+
var TEMPLATES_DIR2 = existsSync11(resolve7(__regen_dirname, "../templates")) ? resolve7(__regen_dirname, "../templates") : resolve7(__regen_dirname, "../../templates");
|
|
2669
3287
|
async function regenerateCommand(target, options) {
|
|
2670
3288
|
ensureInteractive("regenerate");
|
|
2671
3289
|
const targetDir = resolve7(options.path || ".");
|
|
@@ -2742,7 +3360,7 @@ Valid options:
|
|
|
2742
3360
|
return;
|
|
2743
3361
|
}
|
|
2744
3362
|
if (regenerateClaudeMd) {
|
|
2745
|
-
const claudeMdPath =
|
|
3363
|
+
const claudeMdPath = join15(targetDir, "CLAUDE.md");
|
|
2746
3364
|
if (fileExists(claudeMdPath)) {
|
|
2747
3365
|
p8.log.warn(
|
|
2748
3366
|
"Regenerating CLAUDE.md will overwrite any self-improvements (Gotchas section)."
|
|
@@ -2779,7 +3397,7 @@ Valid options:
|
|
|
2779
3397
|
spinner8.start("Regenerating files...");
|
|
2780
3398
|
const regeneratedFiles = [];
|
|
2781
3399
|
if (regenerateClaudeMd) {
|
|
2782
|
-
const claudeMdPath =
|
|
3400
|
+
const claudeMdPath = join15(targetDir, "CLAUDE.md");
|
|
2783
3401
|
const claudeMdContent = generateClaudeMd(
|
|
2784
3402
|
fullProjectConfig,
|
|
2785
3403
|
analysis.scripts,
|
|
@@ -2790,7 +3408,7 @@ Valid options:
|
|
|
2790
3408
|
manifest.files["CLAUDE.md"] = createManifestEntry(claudeMdContent);
|
|
2791
3409
|
}
|
|
2792
3410
|
if (regenerateAgentsMd) {
|
|
2793
|
-
const agentsMdPath =
|
|
3411
|
+
const agentsMdPath = join15(targetDir, "AGENTS.md");
|
|
2794
3412
|
const agentsMdContent = generateAgentsMdFromConfig(
|
|
2795
3413
|
fullProjectConfig,
|
|
2796
3414
|
analysis.scripts,
|
|
@@ -2807,8 +3425,8 @@ Valid options:
|
|
|
2807
3425
|
const ruleContent = getRuleContentForIDE(ide, generatedRules);
|
|
2808
3426
|
const rulePath = DYNAMIC_RULE_FILES[ide]?.[0];
|
|
2809
3427
|
if (ruleContent && rulePath) {
|
|
2810
|
-
const destPath =
|
|
2811
|
-
ensureDir(
|
|
3428
|
+
const destPath = join15(targetDir, rulePath);
|
|
3429
|
+
ensureDir(dirname7(destPath));
|
|
2812
3430
|
writeFile(destPath, ruleContent);
|
|
2813
3431
|
regeneratedFiles.push(rulePath);
|
|
2814
3432
|
manifest.files[rulePath] = createManifestEntry(ruleContent);
|
|
@@ -2822,7 +3440,7 @@ Valid options:
|
|
|
2822
3440
|
if (!manifest.selectedIDEs.includes("claude-code") || manifest.installMode !== "plugin") {
|
|
2823
3441
|
p8.log.warn("Plugin regeneration only applies to Claude Code in plugin mode. Skipping.");
|
|
2824
3442
|
} else {
|
|
2825
|
-
const { generatePlugin: generatePlugin2 } = await import("./plugin-
|
|
3443
|
+
const { generatePlugin: generatePlugin2 } = await import("./plugin-SGSFVXPA.js");
|
|
2826
3444
|
const pluginResult = generatePlugin2(
|
|
2827
3445
|
targetDir,
|
|
2828
3446
|
TEMPLATES_DIR2,
|
|
@@ -2857,8 +3475,8 @@ Valid options:
|
|
|
2857
3475
|
}
|
|
2858
3476
|
|
|
2859
3477
|
// src/commands/info.ts
|
|
2860
|
-
import { resolve as resolve8, join as
|
|
2861
|
-
import { existsSync as
|
|
3478
|
+
import { resolve as resolve8, join as join16 } from "path";
|
|
3479
|
+
import { existsSync as existsSync12, readdirSync as readdirSync3 } from "fs";
|
|
2862
3480
|
import * as p9 from "@clack/prompts";
|
|
2863
3481
|
import chalk10 from "chalk";
|
|
2864
3482
|
async function infoCommand() {
|
|
@@ -2871,33 +3489,33 @@ async function infoCommand() {
|
|
|
2871
3489
|
let skillCount = 0;
|
|
2872
3490
|
let agentCount = 0;
|
|
2873
3491
|
if (manifest) {
|
|
2874
|
-
const pluginDir = manifest.pluginPath ?
|
|
2875
|
-
if (pluginDir &&
|
|
2876
|
-
const skillsDir =
|
|
2877
|
-
const agentsDir =
|
|
2878
|
-
if (
|
|
2879
|
-
skillCount =
|
|
3492
|
+
const pluginDir = manifest.pluginPath ? join16(targetDir, manifest.pluginPath) : null;
|
|
3493
|
+
if (pluginDir && existsSync12(pluginDir)) {
|
|
3494
|
+
const skillsDir = join16(pluginDir, "skills");
|
|
3495
|
+
const agentsDir = join16(pluginDir, "agents");
|
|
3496
|
+
if (existsSync12(skillsDir)) {
|
|
3497
|
+
skillCount = readdirSync3(skillsDir, { withFileTypes: true }).filter(
|
|
2880
3498
|
(e) => e.isDirectory() || e.isFile() && e.name.endsWith(".md")
|
|
2881
3499
|
).length;
|
|
2882
3500
|
}
|
|
2883
|
-
if (
|
|
2884
|
-
agentCount =
|
|
3501
|
+
if (existsSync12(agentsDir)) {
|
|
3502
|
+
agentCount = readdirSync3(agentsDir, { withFileTypes: true }).filter(
|
|
2885
3503
|
(e) => e.isFile() && e.name.endsWith(".md")
|
|
2886
3504
|
).length;
|
|
2887
3505
|
}
|
|
2888
3506
|
}
|
|
2889
3507
|
if (skillCount === 0) {
|
|
2890
|
-
const claudeSkills =
|
|
2891
|
-
if (
|
|
2892
|
-
skillCount =
|
|
3508
|
+
const claudeSkills = join16(targetDir, ".claude", "skills");
|
|
3509
|
+
if (existsSync12(claudeSkills)) {
|
|
3510
|
+
skillCount = readdirSync3(claudeSkills, { withFileTypes: true }).filter(
|
|
2893
3511
|
(e) => e.isDirectory() || e.isFile() && e.name.endsWith(".md")
|
|
2894
3512
|
).length;
|
|
2895
3513
|
}
|
|
2896
3514
|
}
|
|
2897
3515
|
if (agentCount === 0) {
|
|
2898
|
-
const claudeAgents =
|
|
2899
|
-
if (
|
|
2900
|
-
agentCount =
|
|
3516
|
+
const claudeAgents = join16(targetDir, ".claude", "agents");
|
|
3517
|
+
if (existsSync12(claudeAgents)) {
|
|
3518
|
+
agentCount = readdirSync3(claudeAgents, { withFileTypes: true }).filter(
|
|
2901
3519
|
(e) => e.isFile() && e.name.endsWith(".md")
|
|
2902
3520
|
).length;
|
|
2903
3521
|
}
|
|
@@ -2940,8 +3558,8 @@ async function infoCommand() {
|
|
|
2940
3558
|
}
|
|
2941
3559
|
|
|
2942
3560
|
// src/commands/list.ts
|
|
2943
|
-
import { resolve as resolve9, join as
|
|
2944
|
-
import { existsSync as
|
|
3561
|
+
import { resolve as resolve9, join as join17 } from "path";
|
|
3562
|
+
import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync5 } from "fs";
|
|
2945
3563
|
import * as p10 from "@clack/prompts";
|
|
2946
3564
|
import chalk11 from "chalk";
|
|
2947
3565
|
async function listCommand(filter, options) {
|
|
@@ -2958,30 +3576,30 @@ Valid options: skills, agents`);
|
|
|
2958
3576
|
}
|
|
2959
3577
|
const skills = [];
|
|
2960
3578
|
const agents = [];
|
|
2961
|
-
const pluginDir = manifest?.pluginPath ?
|
|
2962
|
-
if (pluginDir &&
|
|
3579
|
+
const pluginDir = manifest?.pluginPath ? join17(targetDir, manifest.pluginPath) : null;
|
|
3580
|
+
if (pluginDir && existsSync13(pluginDir)) {
|
|
2963
3581
|
if (showSkills) {
|
|
2964
|
-
const skillsDir =
|
|
2965
|
-
if (
|
|
3582
|
+
const skillsDir = join17(pluginDir, "skills");
|
|
3583
|
+
if (existsSync13(skillsDir)) {
|
|
2966
3584
|
skills.push(...discoverSkills(skillsDir));
|
|
2967
3585
|
}
|
|
2968
3586
|
}
|
|
2969
3587
|
if (showAgents) {
|
|
2970
|
-
const agentsDir =
|
|
2971
|
-
if (
|
|
3588
|
+
const agentsDir = join17(pluginDir, "agents");
|
|
3589
|
+
if (existsSync13(agentsDir)) {
|
|
2972
3590
|
agents.push(...discoverAgents(agentsDir));
|
|
2973
3591
|
}
|
|
2974
3592
|
}
|
|
2975
3593
|
}
|
|
2976
3594
|
if (showSkills && skills.length === 0) {
|
|
2977
|
-
const claudeSkills =
|
|
2978
|
-
if (
|
|
3595
|
+
const claudeSkills = join17(targetDir, ".claude", "skills");
|
|
3596
|
+
if (existsSync13(claudeSkills)) {
|
|
2979
3597
|
skills.push(...discoverSkills(claudeSkills));
|
|
2980
3598
|
}
|
|
2981
3599
|
}
|
|
2982
3600
|
if (showAgents && agents.length === 0) {
|
|
2983
|
-
const claudeAgents =
|
|
2984
|
-
if (
|
|
3601
|
+
const claudeAgents = join17(targetDir, ".claude", "agents");
|
|
3602
|
+
if (existsSync13(claudeAgents)) {
|
|
2985
3603
|
agents.push(...discoverAgents(claudeAgents));
|
|
2986
3604
|
}
|
|
2987
3605
|
}
|
|
@@ -3005,15 +3623,15 @@ Valid options: skills, agents`);
|
|
|
3005
3623
|
}
|
|
3006
3624
|
function discoverSkills(skillsDir) {
|
|
3007
3625
|
const items = [];
|
|
3008
|
-
const entries =
|
|
3626
|
+
const entries = readdirSync4(skillsDir, { withFileTypes: true });
|
|
3009
3627
|
for (const entry of entries) {
|
|
3010
3628
|
if (entry.isDirectory()) {
|
|
3011
|
-
const skillMd =
|
|
3012
|
-
const description =
|
|
3629
|
+
const skillMd = join17(skillsDir, entry.name, "SKILL.md");
|
|
3630
|
+
const description = existsSync13(skillMd) ? extractDescription(skillMd) : "";
|
|
3013
3631
|
items.push({ name: entry.name, description });
|
|
3014
3632
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
3015
3633
|
const name = entry.name.replace(/\.md$/, "");
|
|
3016
|
-
const description = extractDescription(
|
|
3634
|
+
const description = extractDescription(join17(skillsDir, entry.name));
|
|
3017
3635
|
items.push({ name, description });
|
|
3018
3636
|
}
|
|
3019
3637
|
}
|
|
@@ -3021,11 +3639,11 @@ function discoverSkills(skillsDir) {
|
|
|
3021
3639
|
}
|
|
3022
3640
|
function discoverAgents(agentsDir) {
|
|
3023
3641
|
const items = [];
|
|
3024
|
-
const entries =
|
|
3642
|
+
const entries = readdirSync4(agentsDir, { withFileTypes: true });
|
|
3025
3643
|
for (const entry of entries) {
|
|
3026
3644
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
3027
3645
|
const name = entry.name.replace(/\.md$/, "");
|
|
3028
|
-
const description = extractDescription(
|
|
3646
|
+
const description = extractDescription(join17(agentsDir, entry.name));
|
|
3029
3647
|
items.push({ name, description });
|
|
3030
3648
|
}
|
|
3031
3649
|
}
|
|
@@ -3033,7 +3651,7 @@ function discoverAgents(agentsDir) {
|
|
|
3033
3651
|
}
|
|
3034
3652
|
function extractDescription(filePath) {
|
|
3035
3653
|
try {
|
|
3036
|
-
const content =
|
|
3654
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
3037
3655
|
const lines = content.split("\n");
|
|
3038
3656
|
let pastHeading = false;
|
|
3039
3657
|
for (const line of lines) {
|
|
@@ -3212,8 +3830,8 @@ async function configResetCommand(options) {
|
|
|
3212
3830
|
}
|
|
3213
3831
|
|
|
3214
3832
|
// src/commands/doctor.ts
|
|
3215
|
-
import { resolve as resolve11, join as
|
|
3216
|
-
import { existsSync as
|
|
3833
|
+
import { resolve as resolve11, join as join18 } from "path";
|
|
3834
|
+
import { existsSync as existsSync14, readdirSync as readdirSync5, statSync, chmodSync as chmodSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
3217
3835
|
import { execSync } from "child_process";
|
|
3218
3836
|
import * as p12 from "@clack/prompts";
|
|
3219
3837
|
import chalk13 from "chalk";
|
|
@@ -3314,7 +3932,7 @@ function checkManifestFiles(targetDir, manifest) {
|
|
|
3314
3932
|
let existCount = 0;
|
|
3315
3933
|
const missing = [];
|
|
3316
3934
|
for (const relativePath of Object.keys(manifest.files)) {
|
|
3317
|
-
if (fileExists(
|
|
3935
|
+
if (fileExists(join18(targetDir, relativePath))) {
|
|
3318
3936
|
existCount++;
|
|
3319
3937
|
} else {
|
|
3320
3938
|
missing.push(relativePath);
|
|
@@ -3334,15 +3952,15 @@ function checkManifestFiles(targetDir, manifest) {
|
|
|
3334
3952
|
};
|
|
3335
3953
|
}
|
|
3336
3954
|
function checkScriptPermissions(targetDir, manifest) {
|
|
3337
|
-
const pluginDir = manifest.pluginPath ?
|
|
3955
|
+
const pluginDir = manifest.pluginPath ? join18(targetDir, manifest.pluginPath) : null;
|
|
3338
3956
|
const shFiles = [];
|
|
3339
|
-
if (pluginDir &&
|
|
3340
|
-
const scriptsDir =
|
|
3341
|
-
if (
|
|
3342
|
-
const entries =
|
|
3957
|
+
if (pluginDir && existsSync14(pluginDir)) {
|
|
3958
|
+
const scriptsDir = join18(pluginDir, "scripts");
|
|
3959
|
+
if (existsSync14(scriptsDir)) {
|
|
3960
|
+
const entries = readdirSync5(scriptsDir);
|
|
3343
3961
|
for (const entry of entries) {
|
|
3344
3962
|
if (entry.endsWith(".sh")) {
|
|
3345
|
-
shFiles.push(
|
|
3963
|
+
shFiles.push(join18(scriptsDir, entry));
|
|
3346
3964
|
}
|
|
3347
3965
|
}
|
|
3348
3966
|
}
|
|
@@ -3405,28 +4023,28 @@ function checkPluginRegistered(targetDir) {
|
|
|
3405
4023
|
};
|
|
3406
4024
|
}
|
|
3407
4025
|
function checkHookScripts(targetDir, manifest) {
|
|
3408
|
-
const pluginDir = manifest.pluginPath ?
|
|
3409
|
-
if (!pluginDir || !
|
|
4026
|
+
const pluginDir = manifest.pluginPath ? join18(targetDir, manifest.pluginPath) : null;
|
|
4027
|
+
if (!pluginDir || !existsSync14(pluginDir)) {
|
|
3410
4028
|
return {
|
|
3411
4029
|
name: "hooks",
|
|
3412
4030
|
status: "pass",
|
|
3413
4031
|
message: "No hooks directory to check"
|
|
3414
4032
|
};
|
|
3415
4033
|
}
|
|
3416
|
-
const hooksDir =
|
|
3417
|
-
if (!
|
|
4034
|
+
const hooksDir = join18(pluginDir, "hooks");
|
|
4035
|
+
if (!existsSync14(hooksDir)) {
|
|
3418
4036
|
return {
|
|
3419
4037
|
name: "hooks",
|
|
3420
4038
|
status: "pass",
|
|
3421
4039
|
message: "No hooks directory found"
|
|
3422
4040
|
};
|
|
3423
4041
|
}
|
|
3424
|
-
const hookFiles =
|
|
4042
|
+
const hookFiles = readdirSync5(hooksDir).filter((f) => f.endsWith(".json"));
|
|
3425
4043
|
let total = 0;
|
|
3426
4044
|
let valid = 0;
|
|
3427
4045
|
for (const hookFile of hookFiles) {
|
|
3428
4046
|
try {
|
|
3429
|
-
const hookContent = JSON.parse(readFile(
|
|
4047
|
+
const hookContent = JSON.parse(readFile(join18(hooksDir, hookFile)));
|
|
3430
4048
|
const hooks = Array.isArray(hookContent) ? hookContent : [hookContent];
|
|
3431
4049
|
for (const hook of hooks) {
|
|
3432
4050
|
if (hook.command) {
|
|
@@ -3440,8 +4058,8 @@ function checkHookScripts(targetDir, manifest) {
|
|
|
3440
4058
|
/\$\{[A-Z_]+\}\//g,
|
|
3441
4059
|
""
|
|
3442
4060
|
);
|
|
3443
|
-
const scriptPath =
|
|
3444
|
-
if (
|
|
4061
|
+
const scriptPath = join18(pluginDir, resolved);
|
|
4062
|
+
if (existsSync14(scriptPath)) {
|
|
3445
4063
|
valid++;
|
|
3446
4064
|
}
|
|
3447
4065
|
} else {
|
|
@@ -3465,7 +4083,7 @@ function checkHookScripts(targetDir, manifest) {
|
|
|
3465
4083
|
};
|
|
3466
4084
|
}
|
|
3467
4085
|
function checkQualityScripts(targetDir) {
|
|
3468
|
-
const pkgPath =
|
|
4086
|
+
const pkgPath = join18(targetDir, "package.json");
|
|
3469
4087
|
if (!fileExists(pkgPath)) {
|
|
3470
4088
|
return { name: "quality", status: "warn", message: "No package.json found" };
|
|
3471
4089
|
}
|
|
@@ -3491,8 +4109,8 @@ function checkQualityScripts(targetDir) {
|
|
|
3491
4109
|
}
|
|
3492
4110
|
}
|
|
3493
4111
|
function checkThoughtsDir(targetDir) {
|
|
3494
|
-
const thoughtsDir =
|
|
3495
|
-
if (
|
|
4112
|
+
const thoughtsDir = join18(targetDir, "thoughts");
|
|
4113
|
+
if (existsSync14(thoughtsDir)) {
|
|
3496
4114
|
return {
|
|
3497
4115
|
name: "thoughts",
|
|
3498
4116
|
status: "pass",
|
|
@@ -3516,15 +4134,15 @@ function checkThoughtsDir(targetDir) {
|
|
|
3516
4134
|
"thoughts/archive/backlog"
|
|
3517
4135
|
];
|
|
3518
4136
|
for (const dir of dirs) {
|
|
3519
|
-
|
|
4137
|
+
mkdirSync3(join18(targetDir, dir), { recursive: true });
|
|
3520
4138
|
}
|
|
3521
4139
|
}
|
|
3522
4140
|
};
|
|
3523
4141
|
}
|
|
3524
4142
|
function checkEslint(targetDir) {
|
|
3525
4143
|
try {
|
|
3526
|
-
const eslintLocal =
|
|
3527
|
-
if (
|
|
4144
|
+
const eslintLocal = join18(targetDir, "node_modules", ".bin", "eslint");
|
|
4145
|
+
if (existsSync14(eslintLocal)) {
|
|
3528
4146
|
return { name: "eslint", status: "pass", message: "eslint is available" };
|
|
3529
4147
|
}
|
|
3530
4148
|
execSync("which eslint", { stdio: "pipe" });
|
|
@@ -3539,8 +4157,8 @@ function checkEslint(targetDir) {
|
|
|
3539
4157
|
}
|
|
3540
4158
|
|
|
3541
4159
|
// src/commands/uninstall.ts
|
|
3542
|
-
import { existsSync as
|
|
3543
|
-
import { resolve as resolve12, join as
|
|
4160
|
+
import { existsSync as existsSync15, rmSync as rmSync2, readdirSync as readdirSync6 } from "fs";
|
|
4161
|
+
import { resolve as resolve12, join as join19, dirname as dirname8 } from "path";
|
|
3544
4162
|
import * as p13 from "@clack/prompts";
|
|
3545
4163
|
import chalk14 from "chalk";
|
|
3546
4164
|
var DEVTRONIC_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
@@ -3558,12 +4176,12 @@ async function uninstallCommand(options) {
|
|
|
3558
4176
|
return;
|
|
3559
4177
|
}
|
|
3560
4178
|
const managedFiles = Object.keys(manifest.files);
|
|
3561
|
-
const existingFiles = managedFiles.filter((f) => fileExists(
|
|
3562
|
-
const missingFiles = managedFiles.filter((f) => !fileExists(
|
|
3563
|
-
const hasPlugin = manifest.installMode === "plugin" &&
|
|
3564
|
-
const hasThoughts =
|
|
3565
|
-
const hasClaudeMd = fileExists(
|
|
3566
|
-
const hasAgentsMd = fileExists(
|
|
4179
|
+
const existingFiles = managedFiles.filter((f) => fileExists(join19(targetDir, f)));
|
|
4180
|
+
const missingFiles = managedFiles.filter((f) => !fileExists(join19(targetDir, f)));
|
|
4181
|
+
const hasPlugin = manifest.installMode === "plugin" && existsSync15(join19(targetDir, PLUGIN_DIR, PLUGIN_NAME));
|
|
4182
|
+
const hasThoughts = existsSync15(join19(targetDir, "thoughts"));
|
|
4183
|
+
const hasClaudeMd = fileExists(join19(targetDir, "CLAUDE.md"));
|
|
4184
|
+
const hasAgentsMd = fileExists(join19(targetDir, "AGENTS.md"));
|
|
3567
4185
|
p13.log.info(`Installation found: v${manifest.version} (${manifest.implantedAt})`);
|
|
3568
4186
|
p13.log.info(`IDEs: ${manifest.selectedIDEs.join(", ")}`);
|
|
3569
4187
|
p13.log.info(`Mode: ${manifest.installMode || "standalone"}`);
|
|
@@ -3649,18 +4267,18 @@ async function uninstallCommand(options) {
|
|
|
3649
4267
|
}
|
|
3650
4268
|
if (hasPlugin) {
|
|
3651
4269
|
try {
|
|
3652
|
-
|
|
4270
|
+
rmSync2(join19(targetDir, PLUGIN_DIR, PLUGIN_NAME), { recursive: true, force: true });
|
|
3653
4271
|
removed.push(`${PLUGIN_DIR}/${PLUGIN_NAME}/`);
|
|
3654
|
-
const marketplaceDescDir =
|
|
3655
|
-
if (
|
|
3656
|
-
|
|
4272
|
+
const marketplaceDescDir = join19(targetDir, PLUGIN_DIR, ".claude-plugin");
|
|
4273
|
+
if (existsSync15(marketplaceDescDir)) {
|
|
4274
|
+
rmSync2(marketplaceDescDir, { recursive: true, force: true });
|
|
3657
4275
|
removed.push(`${PLUGIN_DIR}/.claude-plugin/`);
|
|
3658
4276
|
}
|
|
3659
|
-
const pluginsDir =
|
|
3660
|
-
if (
|
|
4277
|
+
const pluginsDir = join19(targetDir, PLUGIN_DIR);
|
|
4278
|
+
if (existsSync15(pluginsDir)) {
|
|
3661
4279
|
const remaining = readdirSafe(pluginsDir);
|
|
3662
4280
|
if (remaining.length === 0) {
|
|
3663
|
-
|
|
4281
|
+
rmSync2(pluginsDir, { recursive: true, force: true });
|
|
3664
4282
|
removed.push(`${PLUGIN_DIR}/ (empty)`);
|
|
3665
4283
|
}
|
|
3666
4284
|
}
|
|
@@ -3673,11 +4291,11 @@ async function uninstallCommand(options) {
|
|
|
3673
4291
|
if (file.startsWith("thoughts/")) continue;
|
|
3674
4292
|
if (file.startsWith(PLUGIN_DIR + "/")) continue;
|
|
3675
4293
|
try {
|
|
3676
|
-
const filePath =
|
|
3677
|
-
if (
|
|
3678
|
-
|
|
4294
|
+
const filePath = join19(targetDir, file);
|
|
4295
|
+
if (existsSync15(filePath)) {
|
|
4296
|
+
rmSync2(filePath, { force: true });
|
|
3679
4297
|
removed.push(file);
|
|
3680
|
-
cleanEmptyParents(targetDir,
|
|
4298
|
+
cleanEmptyParents(targetDir, dirname8(file));
|
|
3681
4299
|
}
|
|
3682
4300
|
} catch (err) {
|
|
3683
4301
|
errors.push(`Failed to remove ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3686,7 +4304,7 @@ async function uninstallCommand(options) {
|
|
|
3686
4304
|
if (hasClaudeMd) {
|
|
3687
4305
|
if (removeClaudeMd) {
|
|
3688
4306
|
try {
|
|
3689
|
-
|
|
4307
|
+
rmSync2(join19(targetDir, "CLAUDE.md"), { force: true });
|
|
3690
4308
|
removed.push("CLAUDE.md");
|
|
3691
4309
|
} catch (err) {
|
|
3692
4310
|
errors.push(`Failed to remove CLAUDE.md: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3698,7 +4316,7 @@ async function uninstallCommand(options) {
|
|
|
3698
4316
|
if (hasAgentsMd) {
|
|
3699
4317
|
if (removeAgentsMd) {
|
|
3700
4318
|
try {
|
|
3701
|
-
|
|
4319
|
+
rmSync2(join19(targetDir, "AGENTS.md"), { force: true });
|
|
3702
4320
|
removed.push("AGENTS.md");
|
|
3703
4321
|
} catch (err) {
|
|
3704
4322
|
errors.push(`Failed to remove AGENTS.md: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3710,7 +4328,7 @@ async function uninstallCommand(options) {
|
|
|
3710
4328
|
if (hasThoughts) {
|
|
3711
4329
|
if (removeThoughts) {
|
|
3712
4330
|
try {
|
|
3713
|
-
|
|
4331
|
+
rmSync2(join19(targetDir, "thoughts"), { recursive: true, force: true });
|
|
3714
4332
|
removed.push("thoughts/");
|
|
3715
4333
|
} catch (err) {
|
|
3716
4334
|
errors.push(`Failed to remove thoughts/: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3720,9 +4338,9 @@ async function uninstallCommand(options) {
|
|
|
3720
4338
|
}
|
|
3721
4339
|
}
|
|
3722
4340
|
try {
|
|
3723
|
-
const manifestDir =
|
|
3724
|
-
if (
|
|
3725
|
-
|
|
4341
|
+
const manifestDir = join19(targetDir, MANIFEST_DIR);
|
|
4342
|
+
if (existsSync15(manifestDir)) {
|
|
4343
|
+
rmSync2(manifestDir, { recursive: true, force: true });
|
|
3726
4344
|
removed.push(`${MANIFEST_DIR}/`);
|
|
3727
4345
|
}
|
|
3728
4346
|
} catch (err) {
|
|
@@ -3767,33 +4385,59 @@ async function uninstallCommand(options) {
|
|
|
3767
4385
|
}
|
|
3768
4386
|
function cleanEmptyParents(targetDir, relDir) {
|
|
3769
4387
|
if (!relDir || relDir === ".") return;
|
|
3770
|
-
const absDir =
|
|
3771
|
-
if (!
|
|
4388
|
+
const absDir = join19(targetDir, relDir);
|
|
4389
|
+
if (!existsSync15(absDir)) return;
|
|
3772
4390
|
const entries = readdirSafe(absDir);
|
|
3773
4391
|
if (entries.length === 0) {
|
|
3774
4392
|
try {
|
|
3775
|
-
|
|
3776
|
-
cleanEmptyParents(targetDir,
|
|
4393
|
+
rmSync2(absDir, { recursive: true, force: true });
|
|
4394
|
+
cleanEmptyParents(targetDir, dirname8(relDir));
|
|
3777
4395
|
} catch {
|
|
3778
4396
|
}
|
|
3779
4397
|
}
|
|
3780
4398
|
}
|
|
3781
4399
|
function readdirSafe(dir) {
|
|
3782
4400
|
try {
|
|
3783
|
-
return
|
|
4401
|
+
return readdirSync6(dir);
|
|
3784
4402
|
} catch {
|
|
3785
4403
|
return [];
|
|
3786
4404
|
}
|
|
3787
4405
|
}
|
|
3788
4406
|
|
|
3789
4407
|
// src/commands/addon.ts
|
|
3790
|
-
import { resolve as resolve13, join as
|
|
3791
|
-
import { existsSync as
|
|
4408
|
+
import { resolve as resolve13, join as join20, dirname as dirname9 } from "path";
|
|
4409
|
+
import { existsSync as existsSync16, unlinkSync as unlinkSync3, rmSync as rmSync3, readFileSync as readFileSync6 } from "fs";
|
|
3792
4410
|
import * as p14 from "@clack/prompts";
|
|
3793
4411
|
import chalk15 from "chalk";
|
|
4412
|
+
function isFileBasedAddon(addonName) {
|
|
4413
|
+
return addonName !== "orchestration";
|
|
4414
|
+
}
|
|
3794
4415
|
async function addonCommand(action, addonName, options) {
|
|
3795
4416
|
const targetDir = resolve13(options.path || ".");
|
|
3796
4417
|
p14.intro(introTitle(`Addon ${action}`));
|
|
4418
|
+
const validAddons = Object.keys(ADDONS);
|
|
4419
|
+
if (!validAddons.includes(addonName)) {
|
|
4420
|
+
p14.cancel(`Unknown addon: ${addonName}
|
|
4421
|
+
|
|
4422
|
+
Valid addons: ${validAddons.join(", ")}`);
|
|
4423
|
+
process.exit(1);
|
|
4424
|
+
}
|
|
4425
|
+
const typedName = addonName;
|
|
4426
|
+
const canonicalAction = action === "enable" ? "add" : action === "disable" ? "remove" : action;
|
|
4427
|
+
if (action === "add" || action === "remove") {
|
|
4428
|
+
const canonical = action === "add" ? "enable" : "disable";
|
|
4429
|
+
p14.log.warn(
|
|
4430
|
+
`"addon ${action}" is deprecated. Use "addon ${canonical}" instead.`
|
|
4431
|
+
);
|
|
4432
|
+
}
|
|
4433
|
+
if (isFileBasedAddon(typedName)) {
|
|
4434
|
+
if (canonicalAction === "add") {
|
|
4435
|
+
await addFileBasedAddon(targetDir, typedName, options);
|
|
4436
|
+
} else {
|
|
4437
|
+
await removeFileBasedAddon(targetDir, typedName, options);
|
|
4438
|
+
}
|
|
4439
|
+
return;
|
|
4440
|
+
}
|
|
3797
4441
|
const manifest = readManifest(targetDir);
|
|
3798
4442
|
if (!manifest) {
|
|
3799
4443
|
p14.log.warn("No devtronic installation found.");
|
|
@@ -3807,16 +4451,9 @@ async function addonCommand(action, addonName, options) {
|
|
|
3807
4451
|
p14.outro("");
|
|
3808
4452
|
return;
|
|
3809
4453
|
}
|
|
3810
|
-
const
|
|
3811
|
-
if (!validAddons.includes(addonName)) {
|
|
3812
|
-
p14.cancel(`Unknown addon: ${addonName}
|
|
3813
|
-
|
|
3814
|
-
Valid addons: ${validAddons.join(", ")}`);
|
|
3815
|
-
process.exit(1);
|
|
3816
|
-
}
|
|
3817
|
-
const addon = ADDONS[addonName];
|
|
4454
|
+
const addon = ADDONS[typedName];
|
|
3818
4455
|
const currentAddons = manifest.projectConfig?.enabledAddons ?? [];
|
|
3819
|
-
if (
|
|
4456
|
+
if (canonicalAction === "add") {
|
|
3820
4457
|
await addAddon(targetDir, manifest, addon.name, currentAddons);
|
|
3821
4458
|
} else {
|
|
3822
4459
|
await removeAddon(targetDir, manifest, addon.name, currentAddons);
|
|
@@ -3830,7 +4467,7 @@ async function addAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
3830
4467
|
}
|
|
3831
4468
|
const addon = ADDONS[addonName];
|
|
3832
4469
|
const pluginRoot = manifest.pluginPath;
|
|
3833
|
-
const skillsSourceDir =
|
|
4470
|
+
const skillsSourceDir = join20(TEMPLATES_DIR, "claude-code", ".claude", "skills");
|
|
3834
4471
|
p14.note(
|
|
3835
4472
|
[
|
|
3836
4473
|
` ${chalk15.dim("Name:")} ${addon.label}`,
|
|
@@ -3849,18 +4486,18 @@ async function addAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
3849
4486
|
spinner8.start(`Adding ${addon.label}...`);
|
|
3850
4487
|
const addedFiles = [];
|
|
3851
4488
|
for (const skillDir of addon.skills) {
|
|
3852
|
-
const sourceDir =
|
|
3853
|
-
if (!
|
|
4489
|
+
const sourceDir = join20(skillsSourceDir, skillDir);
|
|
4490
|
+
if (!existsSync16(sourceDir)) {
|
|
3854
4491
|
spinner8.stop(`Template not found for skill: ${skillDir}`);
|
|
3855
4492
|
p14.log.warn(`Skipping ${skillDir} \u2014 template not found.`);
|
|
3856
4493
|
continue;
|
|
3857
4494
|
}
|
|
3858
4495
|
const templateFiles = getAllFilesRecursive(sourceDir);
|
|
3859
4496
|
for (const file of templateFiles) {
|
|
3860
|
-
const content = readFile(
|
|
3861
|
-
const destRelPath =
|
|
3862
|
-
const destAbsPath =
|
|
3863
|
-
ensureDir(
|
|
4497
|
+
const content = readFile(join20(sourceDir, file));
|
|
4498
|
+
const destRelPath = join20(pluginRoot, "skills", skillDir, file);
|
|
4499
|
+
const destAbsPath = join20(targetDir, destRelPath);
|
|
4500
|
+
ensureDir(dirname9(destAbsPath));
|
|
3864
4501
|
writeFile(destAbsPath, content);
|
|
3865
4502
|
manifest.files[destRelPath] = createManifestEntry(content);
|
|
3866
4503
|
addedFiles.push(destRelPath);
|
|
@@ -3905,11 +4542,11 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
3905
4542
|
}
|
|
3906
4543
|
const modifiedFiles = [];
|
|
3907
4544
|
for (const skillDir of addon.skills) {
|
|
3908
|
-
const skillRelBase =
|
|
4545
|
+
const skillRelBase = join20(pluginRoot, "skills", skillDir);
|
|
3909
4546
|
for (const [filePath, fileInfo] of Object.entries(manifest.files)) {
|
|
3910
4547
|
if (!filePath.startsWith(skillRelBase)) continue;
|
|
3911
|
-
const absPath =
|
|
3912
|
-
if (!
|
|
4548
|
+
const absPath = join20(targetDir, filePath);
|
|
4549
|
+
if (!existsSync16(absPath)) continue;
|
|
3913
4550
|
const current = calculateChecksum(readFile(absPath));
|
|
3914
4551
|
if (current !== fileInfo.originalChecksum) {
|
|
3915
4552
|
modifiedFiles.push(filePath);
|
|
@@ -3932,18 +4569,18 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
3932
4569
|
const spinner8 = p14.spinner();
|
|
3933
4570
|
spinner8.start(`Removing ${addon.label}...`);
|
|
3934
4571
|
for (const skillDir of addon.skills) {
|
|
3935
|
-
const skillRelBase =
|
|
3936
|
-
const skillAbsDir =
|
|
4572
|
+
const skillRelBase = join20(pluginRoot, "skills", skillDir);
|
|
4573
|
+
const skillAbsDir = join20(targetDir, skillRelBase);
|
|
3937
4574
|
for (const filePath of Object.keys(manifest.files)) {
|
|
3938
4575
|
if (filePath.startsWith(skillRelBase)) {
|
|
3939
|
-
const absPath =
|
|
3940
|
-
if (
|
|
4576
|
+
const absPath = join20(targetDir, filePath);
|
|
4577
|
+
if (existsSync16(absPath)) unlinkSync3(absPath);
|
|
3941
4578
|
delete manifest.files[filePath];
|
|
3942
4579
|
}
|
|
3943
4580
|
}
|
|
3944
|
-
if (
|
|
4581
|
+
if (existsSync16(skillAbsDir)) {
|
|
3945
4582
|
try {
|
|
3946
|
-
|
|
4583
|
+
rmSync3(skillAbsDir, { recursive: true });
|
|
3947
4584
|
} catch {
|
|
3948
4585
|
}
|
|
3949
4586
|
}
|
|
@@ -3960,32 +4597,196 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
3960
4597
|
);
|
|
3961
4598
|
p14.outro("Done. Restart Claude Code to apply the changes.");
|
|
3962
4599
|
}
|
|
4600
|
+
async function addFileBasedAddon(targetDir, addonName, _options) {
|
|
4601
|
+
const addon = ADDONS[addonName];
|
|
4602
|
+
const config = readAddonConfig(targetDir);
|
|
4603
|
+
if (config.installed[addonName]) {
|
|
4604
|
+
p14.log.warn(`Addon "${addonName}" is already installed.`);
|
|
4605
|
+
p14.outro("");
|
|
4606
|
+
return;
|
|
4607
|
+
}
|
|
4608
|
+
p14.note(
|
|
4609
|
+
[
|
|
4610
|
+
` ${chalk15.dim("Name:")} ${addon.label}`,
|
|
4611
|
+
` ${chalk15.dim("Description:")} ${addon.description}`,
|
|
4612
|
+
` ${chalk15.dim("Skills:")} ${addon.skills.map((s) => chalk15.cyan(`/${s}`)).join(", ")}`,
|
|
4613
|
+
addon.agents.length ? ` ${chalk15.dim("Agents:")} ${addon.agents.map((a) => chalk15.cyan(a)).join(", ")}` : ` ${chalk15.dim("Agents:")} ${chalk15.dim("\u2014")}`
|
|
4614
|
+
].join("\n"),
|
|
4615
|
+
"Adding addon"
|
|
4616
|
+
);
|
|
4617
|
+
const confirmed = await p14.confirm({ message: "Add this addon?" });
|
|
4618
|
+
if (p14.isCancel(confirmed) || !confirmed) {
|
|
4619
|
+
p14.cancel("Addon installation cancelled.");
|
|
4620
|
+
process.exit(0);
|
|
4621
|
+
}
|
|
4622
|
+
const spinner8 = p14.spinner();
|
|
4623
|
+
spinner8.start(`Adding ${addon.label}...`);
|
|
4624
|
+
const addonSourceDir = getAddonSourceDir(addonName);
|
|
4625
|
+
const result = generateAddonFiles(targetDir, addonSourceDir, config.agents);
|
|
4626
|
+
const addonManifest = JSON.parse(
|
|
4627
|
+
readFileSync6(join20(addonSourceDir, "manifest.json"), "utf-8")
|
|
4628
|
+
);
|
|
4629
|
+
const fileList = [
|
|
4630
|
+
...(addonManifest.files.skills ?? []).map((s) => `skills/${s}`),
|
|
4631
|
+
...(addonManifest.files.agents ?? []).map((a) => `agents/${a}.md`),
|
|
4632
|
+
...(addonManifest.files.rules ?? []).map((r) => `rules/${r}`)
|
|
4633
|
+
];
|
|
4634
|
+
writeAddonToConfig(targetDir, addonName, {
|
|
4635
|
+
version: addonManifest.version,
|
|
4636
|
+
files: fileList,
|
|
4637
|
+
checksums: result.checksums ?? {}
|
|
4638
|
+
});
|
|
4639
|
+
spinner8.stop(`${symbols.pass} ${addon.label} added (${result.written} files written)`);
|
|
4640
|
+
p14.note(
|
|
4641
|
+
addon.skills.map((s) => ` ${chalk15.cyan(`/${s}`)}`).join("\n"),
|
|
4642
|
+
"New skills available"
|
|
4643
|
+
);
|
|
4644
|
+
p14.outro("Done. Skills are now available in your agent directories.");
|
|
4645
|
+
}
|
|
4646
|
+
async function removeFileBasedAddon(targetDir, addonName, _options) {
|
|
4647
|
+
const addon = ADDONS[addonName];
|
|
4648
|
+
const config = readAddonConfig(targetDir);
|
|
4649
|
+
if (!config.installed[addonName]) {
|
|
4650
|
+
p14.log.warn(`Addon "${addonName}" is not currently installed.`);
|
|
4651
|
+
p14.outro("");
|
|
4652
|
+
return;
|
|
4653
|
+
}
|
|
4654
|
+
const modified = detectModifiedAddonFiles(targetDir, addonName);
|
|
4655
|
+
if (modified.length > 0) {
|
|
4656
|
+
p14.log.warn("The following files have been customized:");
|
|
4657
|
+
for (const f of modified) {
|
|
4658
|
+
p14.log.message(` ${chalk15.yellow(f)}`);
|
|
4659
|
+
}
|
|
4660
|
+
const confirm9 = await p14.confirm({
|
|
4661
|
+
message: "Remove them anyway? (customizations will be lost)"
|
|
4662
|
+
});
|
|
4663
|
+
if (p14.isCancel(confirm9) || !confirm9) {
|
|
4664
|
+
p14.cancel("Addon removal cancelled.");
|
|
4665
|
+
process.exit(0);
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
const spinner8 = p14.spinner();
|
|
4669
|
+
spinner8.start(`Removing ${addon.label}...`);
|
|
4670
|
+
removeAddonFiles(targetDir, addonName, config.agents);
|
|
4671
|
+
removeAddonFromConfig(targetDir, addonName);
|
|
4672
|
+
spinner8.stop(`${symbols.pass} ${addon.label} removed`);
|
|
4673
|
+
p14.note(
|
|
4674
|
+
addon.skills.map((s) => ` ${chalk15.dim(`/${s}`)}`).join("\n"),
|
|
4675
|
+
"Skills removed"
|
|
4676
|
+
);
|
|
4677
|
+
p14.outro("Done.");
|
|
4678
|
+
}
|
|
4679
|
+
function getAddonListInfo(targetDir) {
|
|
4680
|
+
const config = readAddonConfig(targetDir);
|
|
4681
|
+
return getAvailableAddons().map((addon) => ({
|
|
4682
|
+
name: addon.name,
|
|
4683
|
+
label: addon.label,
|
|
4684
|
+
description: addon.description,
|
|
4685
|
+
installed: !!config.installed[addon.name],
|
|
4686
|
+
agents: config.installed[addon.name] ? config.agents : void 0
|
|
4687
|
+
}));
|
|
4688
|
+
}
|
|
4689
|
+
async function addonListCommand(options) {
|
|
4690
|
+
const targetDir = resolve13(options.path || ".");
|
|
4691
|
+
p14.intro(introTitle("Addon List"));
|
|
4692
|
+
const items = getAddonListInfo(targetDir);
|
|
4693
|
+
const lines = items.map((item) => {
|
|
4694
|
+
const status = item.installed ? chalk15.green("\u2713 installed") : chalk15.dim("available");
|
|
4695
|
+
const agents = item.agents ? chalk15.dim(` \u2192 ${item.agents.join(", ")}`) : "";
|
|
4696
|
+
return ` ${chalk15.bold(item.name)} ${status}${agents}
|
|
4697
|
+
${chalk15.dim(item.description)}`;
|
|
4698
|
+
});
|
|
4699
|
+
p14.note(lines.join("\n\n"), "Addons");
|
|
4700
|
+
p14.outro(`Use ${chalk15.cyan("devtronic addon add <name>")} to install.`);
|
|
4701
|
+
}
|
|
4702
|
+
async function addonSyncCommand(options) {
|
|
4703
|
+
const targetDir = resolve13(options.path || ".");
|
|
4704
|
+
p14.intro(introTitle("Addon Sync"));
|
|
4705
|
+
const config = readAddonConfig(targetDir);
|
|
4706
|
+
const installedNames = Object.keys(config.installed);
|
|
4707
|
+
if (installedNames.length === 0) {
|
|
4708
|
+
p14.log.info("No addons installed. Nothing to sync.");
|
|
4709
|
+
p14.outro("");
|
|
4710
|
+
return;
|
|
4711
|
+
}
|
|
4712
|
+
const spinner8 = p14.spinner();
|
|
4713
|
+
spinner8.start("Syncing addon files...");
|
|
4714
|
+
let totalWritten = 0;
|
|
4715
|
+
let totalConflicts = [];
|
|
4716
|
+
for (const name of installedNames) {
|
|
4717
|
+
const addonSourceDir = getAddonSourceDir(name);
|
|
4718
|
+
const result = syncAddonFiles(targetDir, addonSourceDir, config.agents);
|
|
4719
|
+
totalWritten += result.written + (result.updated ?? 0);
|
|
4720
|
+
totalConflicts = totalConflicts.concat(result.conflicts);
|
|
4721
|
+
}
|
|
4722
|
+
spinner8.stop(`${symbols.pass} Sync complete (${totalWritten} files updated)`);
|
|
4723
|
+
if (totalConflicts.length > 0) {
|
|
4724
|
+
p14.log.warn("Customized files were preserved:");
|
|
4725
|
+
for (const f of totalConflicts) {
|
|
4726
|
+
p14.log.message(` ${chalk15.yellow(f)}`);
|
|
4727
|
+
}
|
|
4728
|
+
}
|
|
4729
|
+
p14.outro("Done.");
|
|
4730
|
+
}
|
|
3963
4731
|
function updateDescriptors(targetDir, manifest, pluginRoot, addonSkillCount) {
|
|
3964
4732
|
const cliVersion2 = getCliVersion();
|
|
3965
4733
|
const pluginJsonContent = generatePluginJson(cliVersion2, addonSkillCount);
|
|
3966
|
-
const pluginJsonRelPath =
|
|
3967
|
-
writeFile(
|
|
4734
|
+
const pluginJsonRelPath = join20(pluginRoot, ".claude-plugin", "plugin.json");
|
|
4735
|
+
writeFile(join20(targetDir, pluginJsonRelPath), pluginJsonContent);
|
|
3968
4736
|
manifest.files[pluginJsonRelPath] = createManifestEntry(pluginJsonContent);
|
|
3969
4737
|
const marketplaceContent = generateMarketplaceJson(addonSkillCount);
|
|
3970
|
-
const marketplaceRelPath =
|
|
3971
|
-
writeFile(
|
|
4738
|
+
const marketplaceRelPath = join20(PLUGIN_DIR, ".claude-plugin", "marketplace.json");
|
|
4739
|
+
writeFile(join20(targetDir, marketplaceRelPath), marketplaceContent);
|
|
3972
4740
|
manifest.files[marketplaceRelPath] = createManifestEntry(marketplaceContent);
|
|
3973
4741
|
}
|
|
3974
4742
|
|
|
4743
|
+
// src/commands/mode.ts
|
|
4744
|
+
import { resolve as resolve14, join as join21 } from "path";
|
|
4745
|
+
import { existsSync as existsSync17 } from "fs";
|
|
4746
|
+
import * as p15 from "@clack/prompts";
|
|
4747
|
+
import chalk16 from "chalk";
|
|
4748
|
+
async function modeCommand(action, options) {
|
|
4749
|
+
const targetDir = resolve14(options.path || ".");
|
|
4750
|
+
p15.intro(introTitle("Mode"));
|
|
4751
|
+
if (action === "show") {
|
|
4752
|
+
const configPath = join21(targetDir, ".claude", "devtronic.json");
|
|
4753
|
+
const hasConfig = existsSync17(configPath);
|
|
4754
|
+
const config = readAddonConfig(targetDir);
|
|
4755
|
+
const currentMode = config.mode;
|
|
4756
|
+
const isDefault = !hasConfig || currentMode === void 0;
|
|
4757
|
+
const displayMode = currentMode ?? "hitl";
|
|
4758
|
+
p15.log.info(
|
|
4759
|
+
`Mode: ${chalk16.cyan(displayMode)}${isDefault ? chalk16.dim(" (default)") : ""}`
|
|
4760
|
+
);
|
|
4761
|
+
p15.log.info(`Config: ${chalk16.dim(".claude/devtronic.json")}`);
|
|
4762
|
+
p15.outro("");
|
|
4763
|
+
return;
|
|
4764
|
+
}
|
|
4765
|
+
const newMode = action;
|
|
4766
|
+
writeMode(targetDir, newMode);
|
|
4767
|
+
const description = newMode === "afk" ? "Fully autonomous \u2014 no human gates" : "Human-in-the-loop \u2014 pauses for approval at key stages";
|
|
4768
|
+
p15.log.success(`Mode set to ${chalk16.cyan(newMode)}`);
|
|
4769
|
+
p15.log.info(chalk16.dim(description));
|
|
4770
|
+
p15.outro(
|
|
4771
|
+
`Change back anytime with ${chalk16.cyan(`npx devtronic mode ${newMode === "afk" ? "hitl" : "afk"}`)}`
|
|
4772
|
+
);
|
|
4773
|
+
}
|
|
4774
|
+
|
|
3975
4775
|
// src/index.ts
|
|
3976
4776
|
var cliVersion = getCliVersion();
|
|
3977
4777
|
var program = new Command();
|
|
3978
4778
|
program.name("devtronic").description("AI-assisted development toolkit").version(cliVersion).action(() => {
|
|
3979
4779
|
showLogo();
|
|
3980
|
-
console.log(
|
|
4780
|
+
console.log(chalk17.dim(` Agentic development toolkit v${cliVersion}`));
|
|
3981
4781
|
console.log();
|
|
3982
|
-
console.log(` ${
|
|
3983
|
-
console.log(` ${
|
|
3984
|
-
console.log(` ${
|
|
3985
|
-
console.log(` ${
|
|
4782
|
+
console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic init")} ${chalk17.dim("[path]")} ${chalk17.dim("Initialize in a project")}`);
|
|
4783
|
+
console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic info")} ${chalk17.dim("Version & config summary")}`);
|
|
4784
|
+
console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic doctor")} ${chalk17.dim("Health diagnostics")}`);
|
|
4785
|
+
console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic status")} ${chalk17.dim("File status overview")}`);
|
|
4786
|
+
console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic mode")} ${chalk17.dim("<afk|hitl|show>")} ${chalk17.dim("Get or set execution mode")}`);
|
|
3986
4787
|
console.log();
|
|
3987
|
-
console.log(` ${
|
|
3988
|
-
console.log(` ${
|
|
4788
|
+
console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic help")} ${chalk17.dim("Show all commands")}`);
|
|
4789
|
+
console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic help --all")} ${chalk17.dim("Full reference with all options")}`);
|
|
3989
4790
|
console.log();
|
|
3990
4791
|
});
|
|
3991
4792
|
program.command("init").description("Initialize devtronic in your project").argument("[path]", "Target directory (default: current directory)").option("--ide <ides>", "Comma-separated list of IDEs to configure").option("-y, --yes", "Skip prompts and use defaults").option("--preview", "Show what would be generated without making changes").option(
|
|
@@ -4044,13 +4845,20 @@ program.command("doctor").description("Run health checks on your devtronic insta
|
|
|
4044
4845
|
program.command("uninstall").description("Remove devtronic from your project").option("--path <path>", "Target directory (default: current directory)").action(async (options) => {
|
|
4045
4846
|
await uninstallCommand({ path: options.path });
|
|
4046
4847
|
});
|
|
4848
|
+
program.command("mode").description("Set or show the execution mode (hitl or afk)").argument("<mode>", "Mode: afk, hitl, or show").option("--path <path>", "Target directory (default: current directory)").action(async (mode, options) => {
|
|
4849
|
+
if (!["afk", "hitl", "show"].includes(mode)) {
|
|
4850
|
+
console.error(`Invalid mode: "${mode}". Valid values: afk, hitl, show`);
|
|
4851
|
+
process.exit(1);
|
|
4852
|
+
}
|
|
4853
|
+
await modeCommand(mode, { path: options.path });
|
|
4854
|
+
});
|
|
4047
4855
|
program.command("help").description("Show help (use --all for full reference)").option("-a, --all", "Show all commands with their options").action((options) => {
|
|
4048
4856
|
if (!options.all) {
|
|
4049
4857
|
program.outputHelp();
|
|
4050
4858
|
return;
|
|
4051
4859
|
}
|
|
4052
4860
|
showLogo();
|
|
4053
|
-
console.log(
|
|
4861
|
+
console.log(chalk17.dim(` Agentic development toolkit v${cliVersion}
|
|
4054
4862
|
`));
|
|
4055
4863
|
const sections = [
|
|
4056
4864
|
{
|
|
@@ -4074,7 +4882,7 @@ program.command("help").description("Show help (use --all for full reference)").
|
|
|
4074
4882
|
{
|
|
4075
4883
|
title: "Addons",
|
|
4076
4884
|
usage: "addon add <name>",
|
|
4077
|
-
desc: "Add an optional skill pack (e.g., orchestration)",
|
|
4885
|
+
desc: "Add an optional skill pack (e.g., orchestration, design-best-practices)",
|
|
4078
4886
|
opts: ["--path <path> Target directory"]
|
|
4079
4887
|
},
|
|
4080
4888
|
{
|
|
@@ -4083,6 +4891,30 @@ program.command("help").description("Show help (use --all for full reference)").
|
|
|
4083
4891
|
desc: "Remove an addon skill pack",
|
|
4084
4892
|
opts: ["--path <path> Target directory"]
|
|
4085
4893
|
},
|
|
4894
|
+
{
|
|
4895
|
+
title: "",
|
|
4896
|
+
usage: "addon list",
|
|
4897
|
+
desc: "List available and installed addons",
|
|
4898
|
+
opts: ["--path <path> Target directory"]
|
|
4899
|
+
},
|
|
4900
|
+
{
|
|
4901
|
+
title: "",
|
|
4902
|
+
usage: "addon sync",
|
|
4903
|
+
desc: "Regenerate addon files for current agent configuration",
|
|
4904
|
+
opts: ["--path <path> Target directory"]
|
|
4905
|
+
},
|
|
4906
|
+
{
|
|
4907
|
+
title: "",
|
|
4908
|
+
usage: "addon enable <name>",
|
|
4909
|
+
desc: "Enable an addon (copies files to .claude/)",
|
|
4910
|
+
opts: ["--path <path> Target directory"]
|
|
4911
|
+
},
|
|
4912
|
+
{
|
|
4913
|
+
title: "",
|
|
4914
|
+
usage: "addon disable <name>",
|
|
4915
|
+
desc: "Disable an addon (removes files from .claude/)",
|
|
4916
|
+
opts: ["--path <path> Target directory"]
|
|
4917
|
+
},
|
|
4086
4918
|
{
|
|
4087
4919
|
title: "Maintenance",
|
|
4088
4920
|
usage: "update",
|
|
@@ -4136,6 +4968,12 @@ program.command("help").description("Show help (use --all for full reference)").
|
|
|
4136
4968
|
usage: "presets",
|
|
4137
4969
|
desc: "List available configuration presets"
|
|
4138
4970
|
},
|
|
4971
|
+
{
|
|
4972
|
+
title: "Mode",
|
|
4973
|
+
usage: "mode <afk|hitl|show>",
|
|
4974
|
+
desc: "Set or show the persistent execution mode for /auto-devtronic",
|
|
4975
|
+
opts: ["--path <path> Target directory"]
|
|
4976
|
+
},
|
|
4139
4977
|
{
|
|
4140
4978
|
title: "Diagnostics",
|
|
4141
4979
|
usage: "status",
|
|
@@ -4171,14 +5009,14 @@ program.command("help").description("Show help (use --all for full reference)").
|
|
|
4171
5009
|
];
|
|
4172
5010
|
for (const section of sections) {
|
|
4173
5011
|
if (section.title) {
|
|
4174
|
-
console.log(` ${
|
|
5012
|
+
console.log(` ${chalk17.bold.underline(section.title)}
|
|
4175
5013
|
`);
|
|
4176
5014
|
}
|
|
4177
|
-
console.log(` ${
|
|
4178
|
-
console.log(` ${
|
|
5015
|
+
console.log(` ${chalk17.white("devtronic " + section.usage)}`);
|
|
5016
|
+
console.log(` ${chalk17.dim(section.desc)}`);
|
|
4179
5017
|
if (section.opts) {
|
|
4180
5018
|
for (const opt of section.opts) {
|
|
4181
|
-
console.log(` ${
|
|
5019
|
+
console.log(` ${chalk17.yellow(opt)}`);
|
|
4182
5020
|
}
|
|
4183
5021
|
}
|
|
4184
5022
|
console.log();
|
|
@@ -4191,21 +5029,33 @@ addonCmd.command("add").description("Add an addon to the devtronic plugin").argu
|
|
|
4191
5029
|
addonCmd.command("remove").description("Remove an addon from the devtronic plugin").argument("<name>", "Addon name (e.g., orchestration)").option("--path <path>", "Target directory (default: current directory)").action(async (name, options) => {
|
|
4192
5030
|
await addonCommand("remove", name, { path: options.path });
|
|
4193
5031
|
});
|
|
5032
|
+
addonCmd.command("list").description("List available and installed addons").option("--path <path>", "Target directory (default: current directory)").action(async (options) => {
|
|
5033
|
+
await addonListCommand({ path: options.path });
|
|
5034
|
+
});
|
|
5035
|
+
addonCmd.command("enable").description("Enable an addon (copies files to .claude/)").argument("<name>", "Addon name (e.g., auto-devtronic)").option("--path <path>", "Target directory (default: current directory)").action(async (name, options) => {
|
|
5036
|
+
await addonCommand("enable", name, { path: options.path });
|
|
5037
|
+
});
|
|
5038
|
+
addonCmd.command("disable").description("Disable an addon (removes files from .claude/)").argument("<name>", "Addon name (e.g., auto-devtronic)").option("--path <path>", "Target directory (default: current directory)").action(async (name, options) => {
|
|
5039
|
+
await addonCommand("disable", name, { path: options.path });
|
|
5040
|
+
});
|
|
5041
|
+
addonCmd.command("sync").description("Regenerate addon files for current agent configuration").option("--path <path>", "Target directory (default: current directory)").action(async (options) => {
|
|
5042
|
+
await addonSyncCommand({ path: options.path });
|
|
5043
|
+
});
|
|
4194
5044
|
program.command("presets").description("List available configuration presets").action(() => {
|
|
4195
|
-
|
|
5045
|
+
p16.intro(introTitle("Presets"));
|
|
4196
5046
|
const lines = Object.entries(PRESETS).map(([name, preset]) => {
|
|
4197
|
-
const parts = [` ${
|
|
5047
|
+
const parts = [` ${chalk17.bold(name)}`];
|
|
4198
5048
|
parts.push(` ${preset.description}`);
|
|
4199
5049
|
if (preset.config.architecture) {
|
|
4200
|
-
parts.push(` Architecture: ${
|
|
5050
|
+
parts.push(` Architecture: ${chalk17.cyan(preset.config.architecture)}`);
|
|
4201
5051
|
}
|
|
4202
5052
|
if (preset.config.layers) {
|
|
4203
|
-
parts.push(` Layers: ${
|
|
5053
|
+
parts.push(` Layers: ${chalk17.cyan(preset.config.layers.join(", "))}`);
|
|
4204
5054
|
}
|
|
4205
5055
|
return parts.join("\n");
|
|
4206
5056
|
});
|
|
4207
|
-
|
|
4208
|
-
|
|
5057
|
+
p16.note(lines.join("\n\n"), "Available Presets");
|
|
5058
|
+
p16.outro(`Usage: ${chalk17.cyan("npx devtronic init --preset <name>")}`);
|
|
4209
5059
|
});
|
|
4210
5060
|
program.parseAsync().catch((err) => {
|
|
4211
5061
|
console.error(`
|