devtronic 1.1.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-5V4PXTP6.js → chunk-V4QEAL7Y.js} +43 -19
- package/dist/index.js +1049 -214
- package/dist/{plugin-5ZMTSRIW.js → plugin-SGSFVXPA.js} +1 -1
- package/package.json +2 -2
- 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 +1 -1
- package/templates/claude-code/.claude/skills/design-audit/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/design-define/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/design-ia/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/design-research/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/design-review/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/design-spec/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/design-system/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/design-system-audit/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/design-system-define/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/design-system-sync/SKILL.md +1 -1
- package/templates/claude-code/.claude/skills/design-wireframe/SKILL.md +1 -1
- 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
|
@@ -24,12 +24,12 @@ import {
|
|
|
24
24
|
readManifest,
|
|
25
25
|
writeFile,
|
|
26
26
|
writeManifest
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-V4QEAL7Y.js";
|
|
28
28
|
|
|
29
29
|
// src/index.ts
|
|
30
30
|
import { Command } from "commander";
|
|
31
|
-
import * as
|
|
32
|
-
import
|
|
31
|
+
import * as p16 from "@clack/prompts";
|
|
32
|
+
import chalk17 from "chalk";
|
|
33
33
|
|
|
34
34
|
// src/commands/init.ts
|
|
35
35
|
import { existsSync as existsSync5, chmodSync } from "fs";
|
|
@@ -1207,9 +1207,9 @@ function getCliVersion() {
|
|
|
1207
1207
|
resolve(__dirname, "../package.json"),
|
|
1208
1208
|
resolve(__dirname, "../../package.json")
|
|
1209
1209
|
];
|
|
1210
|
-
for (const
|
|
1210
|
+
for (const p17 of paths) {
|
|
1211
1211
|
try {
|
|
1212
|
-
const pkg = JSON.parse(readFileSync(
|
|
1212
|
+
const pkg = JSON.parse(readFileSync(p17, "utf-8"));
|
|
1213
1213
|
if (pkg.name === "devtronic" && pkg.version) {
|
|
1214
1214
|
return pkg.version;
|
|
1215
1215
|
}
|
|
@@ -1374,7 +1374,18 @@ Valid addons: ${validAddons.join(", ")}`);
|
|
|
1374
1374
|
process.exit(0);
|
|
1375
1375
|
}
|
|
1376
1376
|
if (wantOrchestration) {
|
|
1377
|
-
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");
|
|
1378
1389
|
}
|
|
1379
1390
|
}
|
|
1380
1391
|
if (enabledAddons.length > 0) {
|
|
@@ -1604,6 +1615,13 @@ Valid addons: ${validAddons.join(", ")}`);
|
|
|
1604
1615
|
].join("\n"),
|
|
1605
1616
|
"Next Steps"
|
|
1606
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
|
+
);
|
|
1607
1625
|
p3.outro(chalk4.green("Setup complete!"));
|
|
1608
1626
|
} catch (err) {
|
|
1609
1627
|
spinner8.stop("Configuration failed");
|
|
@@ -1706,8 +1724,8 @@ function buildProjectConfigFromPreset(presetConfig, analysis) {
|
|
|
1706
1724
|
}
|
|
1707
1725
|
|
|
1708
1726
|
// src/commands/update.ts
|
|
1709
|
-
import { resolve as resolve3, join as
|
|
1710
|
-
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";
|
|
1711
1729
|
import * as p4 from "@clack/prompts";
|
|
1712
1730
|
import chalk5 from "chalk";
|
|
1713
1731
|
|
|
@@ -1735,6 +1753,553 @@ var REMOVED_FILES = {
|
|
|
1735
1753
|
}
|
|
1736
1754
|
};
|
|
1737
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
|
+
|
|
1738
2303
|
// src/commands/update.ts
|
|
1739
2304
|
async function updateCommand(options) {
|
|
1740
2305
|
if (!options.check && !options.dryRun) {
|
|
@@ -1800,7 +2365,7 @@ async function updateCommand(options) {
|
|
|
1800
2365
|
const modifiedFiles = [];
|
|
1801
2366
|
const outdatedFiles = [];
|
|
1802
2367
|
for (const [relativePath, fileInfo] of Object.entries(manifest.files)) {
|
|
1803
|
-
const filePath =
|
|
2368
|
+
const filePath = join11(targetDir, relativePath);
|
|
1804
2369
|
if (!fileExists(filePath)) {
|
|
1805
2370
|
continue;
|
|
1806
2371
|
}
|
|
@@ -1812,11 +2377,11 @@ async function updateCommand(options) {
|
|
|
1812
2377
|
}
|
|
1813
2378
|
const newFiles = [];
|
|
1814
2379
|
for (const ide of manifest.selectedIDEs) {
|
|
1815
|
-
const templateDir =
|
|
1816
|
-
if (!
|
|
2380
|
+
const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
|
|
2381
|
+
if (!existsSync8(templateDir)) continue;
|
|
1817
2382
|
const files = getAllFilesRecursive(templateDir);
|
|
1818
2383
|
for (const file of files) {
|
|
1819
|
-
const templatePath =
|
|
2384
|
+
const templatePath = join11(templateDir, file);
|
|
1820
2385
|
const templateContent = readFile(templatePath);
|
|
1821
2386
|
const templateChecksum = calculateChecksum(templateContent);
|
|
1822
2387
|
const fileInfo = manifest.files[file];
|
|
@@ -1832,14 +2397,14 @@ async function updateCommand(options) {
|
|
|
1832
2397
|
if (fileInfo.ignored) continue;
|
|
1833
2398
|
let foundInAnyTemplate = false;
|
|
1834
2399
|
for (const ide of manifest.selectedIDEs) {
|
|
1835
|
-
const templateDir =
|
|
1836
|
-
if (
|
|
2400
|
+
const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
|
|
2401
|
+
if (existsSync8(join11(templateDir, relativePath))) {
|
|
1837
2402
|
foundInAnyTemplate = true;
|
|
1838
2403
|
break;
|
|
1839
2404
|
}
|
|
1840
2405
|
}
|
|
1841
2406
|
if (!foundInAnyTemplate) {
|
|
1842
|
-
const localPath =
|
|
2407
|
+
const localPath = join11(targetDir, relativePath);
|
|
1843
2408
|
if (fileExists(localPath)) {
|
|
1844
2409
|
removedFromTemplate.push({
|
|
1845
2410
|
path: relativePath,
|
|
@@ -1967,8 +2532,8 @@ async function updateCommand(options) {
|
|
|
1967
2532
|
implantedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
1968
2533
|
};
|
|
1969
2534
|
for (const ide of manifest.selectedIDEs) {
|
|
1970
|
-
const templateDir =
|
|
1971
|
-
if (!
|
|
2535
|
+
const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
|
|
2536
|
+
if (!existsSync8(templateDir)) continue;
|
|
1972
2537
|
const isPluginMode = ide === "claude-code" && manifest.installMode === "plugin";
|
|
1973
2538
|
const files = getAllFilesRecursive(templateDir);
|
|
1974
2539
|
for (const file of files) {
|
|
@@ -1978,10 +2543,10 @@ async function updateCommand(options) {
|
|
|
1978
2543
|
if (isPluginMode && (file.startsWith(".claude/skills/") || file.startsWith(".claude/agents/"))) {
|
|
1979
2544
|
continue;
|
|
1980
2545
|
}
|
|
1981
|
-
const templatePath =
|
|
1982
|
-
const destPath =
|
|
2546
|
+
const templatePath = join11(templateDir, file);
|
|
2547
|
+
const destPath = join11(targetDir, file);
|
|
1983
2548
|
const templateContent = readFile(templatePath);
|
|
1984
|
-
ensureDir(
|
|
2549
|
+
ensureDir(dirname5(destPath));
|
|
1985
2550
|
writeFile(destPath, templateContent);
|
|
1986
2551
|
updatedManifest.files[file] = createManifestEntry(templateContent);
|
|
1987
2552
|
}
|
|
@@ -1989,7 +2554,7 @@ async function updateCommand(options) {
|
|
|
1989
2554
|
if (manifest.installMode === "plugin" && manifest.pluginPath) {
|
|
1990
2555
|
const userModifiedPluginFiles = /* @__PURE__ */ new Map();
|
|
1991
2556
|
for (const [relPath, fileInfo] of Object.entries(updatedManifest.files)) {
|
|
1992
|
-
const filePath =
|
|
2557
|
+
const filePath = join11(targetDir, relPath);
|
|
1993
2558
|
if (!fileExists(filePath)) continue;
|
|
1994
2559
|
const diskContent = readFile(filePath);
|
|
1995
2560
|
const diskChecksum = calculateChecksum(diskContent);
|
|
@@ -2006,13 +2571,13 @@ async function updateCommand(options) {
|
|
|
2006
2571
|
analysis.packageManager
|
|
2007
2572
|
);
|
|
2008
2573
|
for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
|
|
2009
|
-
const scriptPath =
|
|
2010
|
-
if (
|
|
2574
|
+
const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
|
|
2575
|
+
if (existsSync8(scriptPath)) {
|
|
2011
2576
|
chmodSync2(scriptPath, 493);
|
|
2012
2577
|
}
|
|
2013
2578
|
}
|
|
2014
2579
|
for (const [relPath, content] of userModifiedPluginFiles) {
|
|
2015
|
-
writeFile(
|
|
2580
|
+
writeFile(join11(targetDir, relPath), content);
|
|
2016
2581
|
}
|
|
2017
2582
|
for (const [relPath, newEntry] of Object.entries(pluginResult.files)) {
|
|
2018
2583
|
if (userModifiedPluginFiles.has(relPath)) {
|
|
@@ -2023,9 +2588,9 @@ async function updateCommand(options) {
|
|
|
2023
2588
|
updatedManifest.pluginPath = pluginResult.pluginPath;
|
|
2024
2589
|
}
|
|
2025
2590
|
for (const filePath of filesToDelete) {
|
|
2026
|
-
const localPath =
|
|
2591
|
+
const localPath = join11(targetDir, filePath);
|
|
2027
2592
|
if (fileExists(localPath)) {
|
|
2028
|
-
|
|
2593
|
+
unlinkSync2(localPath);
|
|
2029
2594
|
}
|
|
2030
2595
|
delete updatedManifest.files[filePath];
|
|
2031
2596
|
}
|
|
@@ -2034,12 +2599,12 @@ async function updateCommand(options) {
|
|
|
2034
2599
|
updatedManifest.files[filePath].ignored = true;
|
|
2035
2600
|
}
|
|
2036
2601
|
}
|
|
2037
|
-
const claudeMdPath =
|
|
2602
|
+
const claudeMdPath = join11(targetDir, "CLAUDE.md");
|
|
2038
2603
|
if (fileExists(claudeMdPath)) {
|
|
2039
2604
|
const stat = lstatSync(claudeMdPath);
|
|
2040
2605
|
if (stat.isSymbolicLink()) {
|
|
2041
2606
|
const content = readFile(claudeMdPath);
|
|
2042
|
-
|
|
2607
|
+
unlinkSync2(claudeMdPath);
|
|
2043
2608
|
writeFile(claudeMdPath, content);
|
|
2044
2609
|
updatedManifest.files["CLAUDE.md"] = createManifestEntry(content);
|
|
2045
2610
|
p4.log.info("Migrated CLAUDE.md from symlink to independent file.");
|
|
@@ -2047,6 +2612,28 @@ async function updateCommand(options) {
|
|
|
2047
2612
|
}
|
|
2048
2613
|
writeManifest(targetDir, updatedManifest);
|
|
2049
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
|
+
}
|
|
2050
2637
|
p4.outro(chalk5.green(`Updated to version ${currentVersion}`));
|
|
2051
2638
|
}
|
|
2052
2639
|
function detectStackChanges(savedConfig, analysis) {
|
|
@@ -2153,7 +2740,7 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2153
2740
|
const spinner8 = p4.spinner();
|
|
2154
2741
|
spinner8.start("Regenerating with new stack...");
|
|
2155
2742
|
const regeneratedFiles = [];
|
|
2156
|
-
const agentsMdPath =
|
|
2743
|
+
const agentsMdPath = join11(targetDir, "AGENTS.md");
|
|
2157
2744
|
if (fileExists(agentsMdPath)) {
|
|
2158
2745
|
const agentsMdContent = generateAgentsMdFromConfig(
|
|
2159
2746
|
newConfig,
|
|
@@ -2164,12 +2751,12 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2164
2751
|
regeneratedFiles.push("AGENTS.md");
|
|
2165
2752
|
manifest.files["AGENTS.md"] = createManifestEntry(agentsMdContent);
|
|
2166
2753
|
}
|
|
2167
|
-
const claudeMdPath =
|
|
2754
|
+
const claudeMdPath = join11(targetDir, "CLAUDE.md");
|
|
2168
2755
|
if (fileExists(claudeMdPath)) {
|
|
2169
2756
|
const stat = lstatSync(claudeMdPath);
|
|
2170
2757
|
if (stat.isSymbolicLink()) {
|
|
2171
2758
|
const content = readFile(claudeMdPath);
|
|
2172
|
-
|
|
2759
|
+
unlinkSync2(claudeMdPath);
|
|
2173
2760
|
writeFile(claudeMdPath, content);
|
|
2174
2761
|
manifest.files["CLAUDE.md"] = createManifestEntry(content);
|
|
2175
2762
|
p4.log.info("Migrated CLAUDE.md from symlink to independent file.");
|
|
@@ -2181,8 +2768,8 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2181
2768
|
const ruleContent = getRuleContentForIDE(ide, generatedRules);
|
|
2182
2769
|
const rulePath = DYNAMIC_RULE_FILES[ide]?.[0];
|
|
2183
2770
|
if (ruleContent && rulePath) {
|
|
2184
|
-
const destPath =
|
|
2185
|
-
ensureDir(
|
|
2771
|
+
const destPath = join11(targetDir, rulePath);
|
|
2772
|
+
ensureDir(dirname5(destPath));
|
|
2186
2773
|
writeFile(destPath, ruleContent);
|
|
2187
2774
|
regeneratedFiles.push(rulePath);
|
|
2188
2775
|
manifest.files[rulePath] = createManifestEntry(ruleContent);
|
|
@@ -2192,7 +2779,7 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2192
2779
|
if (manifest.installMode === "plugin" && manifest.pluginPath) {
|
|
2193
2780
|
const userModifiedPluginFiles = /* @__PURE__ */ new Map();
|
|
2194
2781
|
for (const [relPath, fileInfo] of Object.entries(manifest.files)) {
|
|
2195
|
-
const filePath =
|
|
2782
|
+
const filePath = join11(targetDir, relPath);
|
|
2196
2783
|
if (!fileExists(filePath)) continue;
|
|
2197
2784
|
const diskContent = readFile(filePath);
|
|
2198
2785
|
const diskChecksum = calculateChecksum(diskContent);
|
|
@@ -2208,13 +2795,13 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2208
2795
|
analysis.packageManager
|
|
2209
2796
|
);
|
|
2210
2797
|
for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
|
|
2211
|
-
const scriptPath =
|
|
2212
|
-
if (
|
|
2798
|
+
const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
|
|
2799
|
+
if (existsSync8(scriptPath)) {
|
|
2213
2800
|
chmodSync2(scriptPath, 493);
|
|
2214
2801
|
}
|
|
2215
2802
|
}
|
|
2216
2803
|
for (const [relPath, content] of userModifiedPluginFiles) {
|
|
2217
|
-
writeFile(
|
|
2804
|
+
writeFile(join11(targetDir, relPath), content);
|
|
2218
2805
|
}
|
|
2219
2806
|
for (const [relPath, newEntry] of Object.entries(pluginResult.files)) {
|
|
2220
2807
|
if (userModifiedPluginFiles.has(relPath)) {
|
|
@@ -2257,8 +2844,8 @@ async function migrateToPlugin(targetDir, manifest, analysis, dryRun) {
|
|
|
2257
2844
|
analysis.packageManager
|
|
2258
2845
|
);
|
|
2259
2846
|
for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
|
|
2260
|
-
const scriptPath =
|
|
2261
|
-
if (
|
|
2847
|
+
const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
|
|
2848
|
+
if (existsSync8(scriptPath)) {
|
|
2262
2849
|
chmodSync2(scriptPath, 493);
|
|
2263
2850
|
}
|
|
2264
2851
|
}
|
|
@@ -2267,19 +2854,19 @@ async function migrateToPlugin(targetDir, manifest, analysis, dryRun) {
|
|
|
2267
2854
|
const preserved = [];
|
|
2268
2855
|
for (const [path, fileInfo] of Object.entries(manifest.files)) {
|
|
2269
2856
|
if (!path.startsWith(".claude/skills/") && !path.startsWith(".claude/agents/")) continue;
|
|
2270
|
-
const filePath =
|
|
2857
|
+
const filePath = join11(targetDir, path);
|
|
2271
2858
|
if (!fileExists(filePath)) continue;
|
|
2272
2859
|
const current = calculateChecksum(readFile(filePath));
|
|
2273
2860
|
if (current === fileInfo.originalChecksum) {
|
|
2274
|
-
|
|
2861
|
+
unlinkSync2(filePath);
|
|
2275
2862
|
delete manifest.files[path];
|
|
2276
2863
|
removed.push(path);
|
|
2277
2864
|
} else {
|
|
2278
2865
|
preserved.push(path);
|
|
2279
2866
|
}
|
|
2280
2867
|
}
|
|
2281
|
-
cleanEmptyDirs(
|
|
2282
|
-
cleanEmptyDirs(
|
|
2868
|
+
cleanEmptyDirs(join11(targetDir, ".claude", "skills"));
|
|
2869
|
+
cleanEmptyDirs(join11(targetDir, ".claude", "agents"));
|
|
2283
2870
|
Object.assign(manifest.files, pluginResult.files);
|
|
2284
2871
|
manifest.installMode = "plugin";
|
|
2285
2872
|
manifest.pluginPath = pluginResult.pluginPath;
|
|
@@ -2328,22 +2915,22 @@ function buildDefaultConfig(analysis) {
|
|
|
2328
2915
|
};
|
|
2329
2916
|
}
|
|
2330
2917
|
function cleanEmptyDirs(dirPath) {
|
|
2331
|
-
if (!
|
|
2332
|
-
const entries =
|
|
2918
|
+
if (!existsSync8(dirPath)) return;
|
|
2919
|
+
const entries = readdirSync2(dirPath);
|
|
2333
2920
|
for (const entry of entries) {
|
|
2334
|
-
const fullPath =
|
|
2335
|
-
if (
|
|
2921
|
+
const fullPath = join11(dirPath, entry);
|
|
2922
|
+
if (existsSync8(fullPath) && lstatSync(fullPath).isDirectory()) {
|
|
2336
2923
|
cleanEmptyDirs(fullPath);
|
|
2337
2924
|
}
|
|
2338
2925
|
}
|
|
2339
|
-
const remaining =
|
|
2926
|
+
const remaining = readdirSync2(dirPath);
|
|
2340
2927
|
if (remaining.length === 0) {
|
|
2341
|
-
|
|
2928
|
+
rmdirSync2(dirPath);
|
|
2342
2929
|
}
|
|
2343
2930
|
}
|
|
2344
2931
|
|
|
2345
2932
|
// src/commands/status.ts
|
|
2346
|
-
import { resolve as resolve4, join as
|
|
2933
|
+
import { resolve as resolve4, join as join12 } from "path";
|
|
2347
2934
|
import * as p5 from "@clack/prompts";
|
|
2348
2935
|
import chalk6 from "chalk";
|
|
2349
2936
|
async function statusCommand(options = {}) {
|
|
@@ -2357,6 +2944,22 @@ async function statusCommand(options = {}) {
|
|
|
2357
2944
|
p5.outro("");
|
|
2358
2945
|
return;
|
|
2359
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");
|
|
2360
2963
|
p5.note(
|
|
2361
2964
|
[
|
|
2362
2965
|
formatKV("Version:", manifest.version),
|
|
@@ -2367,7 +2970,7 @@ async function statusCommand(options = {}) {
|
|
|
2367
2970
|
);
|
|
2368
2971
|
const fileStatuses = [];
|
|
2369
2972
|
for (const [relativePath, fileInfo] of Object.entries(manifest.files)) {
|
|
2370
|
-
const filePath =
|
|
2973
|
+
const filePath = join12(targetDir, relativePath);
|
|
2371
2974
|
if (!fileExists(filePath)) {
|
|
2372
2975
|
fileStatuses.push({ path: relativePath, status: "missing" });
|
|
2373
2976
|
continue;
|
|
@@ -2413,8 +3016,8 @@ async function statusCommand(options = {}) {
|
|
|
2413
3016
|
}
|
|
2414
3017
|
|
|
2415
3018
|
// src/commands/diff.ts
|
|
2416
|
-
import { resolve as resolve5, join as
|
|
2417
|
-
import { existsSync as
|
|
3019
|
+
import { resolve as resolve5, join as join13 } from "path";
|
|
3020
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2418
3021
|
import * as p6 from "@clack/prompts";
|
|
2419
3022
|
import chalk7 from "chalk";
|
|
2420
3023
|
async function diffCommand(options = {}) {
|
|
@@ -2429,12 +3032,12 @@ async function diffCommand(options = {}) {
|
|
|
2429
3032
|
}
|
|
2430
3033
|
const diffs = [];
|
|
2431
3034
|
for (const ide of manifest.selectedIDEs) {
|
|
2432
|
-
const templateDir =
|
|
2433
|
-
if (!
|
|
3035
|
+
const templateDir = join13(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
|
|
3036
|
+
if (!existsSync9(templateDir)) continue;
|
|
2434
3037
|
const templateFiles = getAllFilesRecursive(templateDir);
|
|
2435
3038
|
for (const file of templateFiles) {
|
|
2436
|
-
const templatePath =
|
|
2437
|
-
const localPath =
|
|
3039
|
+
const templatePath = join13(templateDir, file);
|
|
3040
|
+
const localPath = join13(targetDir, file);
|
|
2438
3041
|
const templateContent = readFile(templatePath);
|
|
2439
3042
|
const templateChecksum = calculateChecksum(templateContent);
|
|
2440
3043
|
const manifestEntry = manifest.files[file];
|
|
@@ -2487,8 +3090,8 @@ async function diffCommand(options = {}) {
|
|
|
2487
3090
|
}
|
|
2488
3091
|
|
|
2489
3092
|
// src/commands/add.ts
|
|
2490
|
-
import { existsSync as
|
|
2491
|
-
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";
|
|
2492
3095
|
import * as p7 from "@clack/prompts";
|
|
2493
3096
|
import chalk8 from "chalk";
|
|
2494
3097
|
var ALL_IDES = [
|
|
@@ -2582,8 +3185,8 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2582
3185
|
const generatedFiles = [];
|
|
2583
3186
|
const generatedRules = generateArchitectureRules(manifest.projectConfig);
|
|
2584
3187
|
const templateName = IDE_TEMPLATE_MAP[selectedIDE];
|
|
2585
|
-
const templateDir =
|
|
2586
|
-
if (!
|
|
3188
|
+
const templateDir = join14(TEMPLATES_DIR, templateName);
|
|
3189
|
+
if (!existsSync10(templateDir)) {
|
|
2587
3190
|
spinner8.stop("Error");
|
|
2588
3191
|
p7.cancel(`Template not found: ${templateName}`);
|
|
2589
3192
|
process.exit(1);
|
|
@@ -2594,8 +3197,8 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2594
3197
|
if (dynamicFiles.includes(file)) {
|
|
2595
3198
|
continue;
|
|
2596
3199
|
}
|
|
2597
|
-
const sourcePath =
|
|
2598
|
-
const destPath =
|
|
3200
|
+
const sourcePath = join14(templateDir, file);
|
|
3201
|
+
const destPath = join14(targetDir, file);
|
|
2599
3202
|
const sourceContent = readFile(sourcePath);
|
|
2600
3203
|
if (fileExists(destPath)) {
|
|
2601
3204
|
if (conflictResolution === "keep") {
|
|
@@ -2612,7 +3215,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2612
3215
|
continue;
|
|
2613
3216
|
}
|
|
2614
3217
|
}
|
|
2615
|
-
ensureDir(
|
|
3218
|
+
ensureDir(dirname6(destPath));
|
|
2616
3219
|
writeFile(destPath, sourceContent);
|
|
2617
3220
|
appliedFiles.push(file);
|
|
2618
3221
|
manifest.files[file] = createManifestEntry(sourceContent);
|
|
@@ -2621,7 +3224,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2621
3224
|
if (ruleContent) {
|
|
2622
3225
|
const rulePath = dynamicFiles[0];
|
|
2623
3226
|
if (rulePath) {
|
|
2624
|
-
const destPath =
|
|
3227
|
+
const destPath = join14(targetDir, rulePath);
|
|
2625
3228
|
if (fileExists(destPath) && conflictResolution === "keep") {
|
|
2626
3229
|
skippedFiles.push(rulePath);
|
|
2627
3230
|
} else if (fileExists(destPath) && conflictResolution === "merge") {
|
|
@@ -2632,7 +3235,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2632
3235
|
mergedFiles.push(rulePath);
|
|
2633
3236
|
manifest.files[rulePath] = createManifestEntry(mergedContent);
|
|
2634
3237
|
} else {
|
|
2635
|
-
ensureDir(
|
|
3238
|
+
ensureDir(dirname6(destPath));
|
|
2636
3239
|
writeFile(destPath, ruleContent);
|
|
2637
3240
|
generatedFiles.push(`${rulePath} (personalized)`);
|
|
2638
3241
|
manifest.files[rulePath] = createManifestEntry(ruleContent);
|
|
@@ -2673,14 +3276,14 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2673
3276
|
}
|
|
2674
3277
|
|
|
2675
3278
|
// src/commands/regenerate.ts
|
|
2676
|
-
import { existsSync as
|
|
2677
|
-
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";
|
|
2678
3281
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2679
3282
|
import * as p8 from "@clack/prompts";
|
|
2680
3283
|
import chalk9 from "chalk";
|
|
2681
3284
|
var __filename = fileURLToPath3(import.meta.url);
|
|
2682
|
-
var __regen_dirname =
|
|
2683
|
-
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");
|
|
2684
3287
|
async function regenerateCommand(target, options) {
|
|
2685
3288
|
ensureInteractive("regenerate");
|
|
2686
3289
|
const targetDir = resolve7(options.path || ".");
|
|
@@ -2757,7 +3360,7 @@ Valid options:
|
|
|
2757
3360
|
return;
|
|
2758
3361
|
}
|
|
2759
3362
|
if (regenerateClaudeMd) {
|
|
2760
|
-
const claudeMdPath =
|
|
3363
|
+
const claudeMdPath = join15(targetDir, "CLAUDE.md");
|
|
2761
3364
|
if (fileExists(claudeMdPath)) {
|
|
2762
3365
|
p8.log.warn(
|
|
2763
3366
|
"Regenerating CLAUDE.md will overwrite any self-improvements (Gotchas section)."
|
|
@@ -2794,7 +3397,7 @@ Valid options:
|
|
|
2794
3397
|
spinner8.start("Regenerating files...");
|
|
2795
3398
|
const regeneratedFiles = [];
|
|
2796
3399
|
if (regenerateClaudeMd) {
|
|
2797
|
-
const claudeMdPath =
|
|
3400
|
+
const claudeMdPath = join15(targetDir, "CLAUDE.md");
|
|
2798
3401
|
const claudeMdContent = generateClaudeMd(
|
|
2799
3402
|
fullProjectConfig,
|
|
2800
3403
|
analysis.scripts,
|
|
@@ -2805,7 +3408,7 @@ Valid options:
|
|
|
2805
3408
|
manifest.files["CLAUDE.md"] = createManifestEntry(claudeMdContent);
|
|
2806
3409
|
}
|
|
2807
3410
|
if (regenerateAgentsMd) {
|
|
2808
|
-
const agentsMdPath =
|
|
3411
|
+
const agentsMdPath = join15(targetDir, "AGENTS.md");
|
|
2809
3412
|
const agentsMdContent = generateAgentsMdFromConfig(
|
|
2810
3413
|
fullProjectConfig,
|
|
2811
3414
|
analysis.scripts,
|
|
@@ -2822,8 +3425,8 @@ Valid options:
|
|
|
2822
3425
|
const ruleContent = getRuleContentForIDE(ide, generatedRules);
|
|
2823
3426
|
const rulePath = DYNAMIC_RULE_FILES[ide]?.[0];
|
|
2824
3427
|
if (ruleContent && rulePath) {
|
|
2825
|
-
const destPath =
|
|
2826
|
-
ensureDir(
|
|
3428
|
+
const destPath = join15(targetDir, rulePath);
|
|
3429
|
+
ensureDir(dirname7(destPath));
|
|
2827
3430
|
writeFile(destPath, ruleContent);
|
|
2828
3431
|
regeneratedFiles.push(rulePath);
|
|
2829
3432
|
manifest.files[rulePath] = createManifestEntry(ruleContent);
|
|
@@ -2837,7 +3440,7 @@ Valid options:
|
|
|
2837
3440
|
if (!manifest.selectedIDEs.includes("claude-code") || manifest.installMode !== "plugin") {
|
|
2838
3441
|
p8.log.warn("Plugin regeneration only applies to Claude Code in plugin mode. Skipping.");
|
|
2839
3442
|
} else {
|
|
2840
|
-
const { generatePlugin: generatePlugin2 } = await import("./plugin-
|
|
3443
|
+
const { generatePlugin: generatePlugin2 } = await import("./plugin-SGSFVXPA.js");
|
|
2841
3444
|
const pluginResult = generatePlugin2(
|
|
2842
3445
|
targetDir,
|
|
2843
3446
|
TEMPLATES_DIR2,
|
|
@@ -2872,8 +3475,8 @@ Valid options:
|
|
|
2872
3475
|
}
|
|
2873
3476
|
|
|
2874
3477
|
// src/commands/info.ts
|
|
2875
|
-
import { resolve as resolve8, join as
|
|
2876
|
-
import { existsSync as
|
|
3478
|
+
import { resolve as resolve8, join as join16 } from "path";
|
|
3479
|
+
import { existsSync as existsSync12, readdirSync as readdirSync3 } from "fs";
|
|
2877
3480
|
import * as p9 from "@clack/prompts";
|
|
2878
3481
|
import chalk10 from "chalk";
|
|
2879
3482
|
async function infoCommand() {
|
|
@@ -2886,33 +3489,33 @@ async function infoCommand() {
|
|
|
2886
3489
|
let skillCount = 0;
|
|
2887
3490
|
let agentCount = 0;
|
|
2888
3491
|
if (manifest) {
|
|
2889
|
-
const pluginDir = manifest.pluginPath ?
|
|
2890
|
-
if (pluginDir &&
|
|
2891
|
-
const skillsDir =
|
|
2892
|
-
const agentsDir =
|
|
2893
|
-
if (
|
|
2894
|
-
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(
|
|
2895
3498
|
(e) => e.isDirectory() || e.isFile() && e.name.endsWith(".md")
|
|
2896
3499
|
).length;
|
|
2897
3500
|
}
|
|
2898
|
-
if (
|
|
2899
|
-
agentCount =
|
|
3501
|
+
if (existsSync12(agentsDir)) {
|
|
3502
|
+
agentCount = readdirSync3(agentsDir, { withFileTypes: true }).filter(
|
|
2900
3503
|
(e) => e.isFile() && e.name.endsWith(".md")
|
|
2901
3504
|
).length;
|
|
2902
3505
|
}
|
|
2903
3506
|
}
|
|
2904
3507
|
if (skillCount === 0) {
|
|
2905
|
-
const claudeSkills =
|
|
2906
|
-
if (
|
|
2907
|
-
skillCount =
|
|
3508
|
+
const claudeSkills = join16(targetDir, ".claude", "skills");
|
|
3509
|
+
if (existsSync12(claudeSkills)) {
|
|
3510
|
+
skillCount = readdirSync3(claudeSkills, { withFileTypes: true }).filter(
|
|
2908
3511
|
(e) => e.isDirectory() || e.isFile() && e.name.endsWith(".md")
|
|
2909
3512
|
).length;
|
|
2910
3513
|
}
|
|
2911
3514
|
}
|
|
2912
3515
|
if (agentCount === 0) {
|
|
2913
|
-
const claudeAgents =
|
|
2914
|
-
if (
|
|
2915
|
-
agentCount =
|
|
3516
|
+
const claudeAgents = join16(targetDir, ".claude", "agents");
|
|
3517
|
+
if (existsSync12(claudeAgents)) {
|
|
3518
|
+
agentCount = readdirSync3(claudeAgents, { withFileTypes: true }).filter(
|
|
2916
3519
|
(e) => e.isFile() && e.name.endsWith(".md")
|
|
2917
3520
|
).length;
|
|
2918
3521
|
}
|
|
@@ -2955,8 +3558,8 @@ async function infoCommand() {
|
|
|
2955
3558
|
}
|
|
2956
3559
|
|
|
2957
3560
|
// src/commands/list.ts
|
|
2958
|
-
import { resolve as resolve9, join as
|
|
2959
|
-
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";
|
|
2960
3563
|
import * as p10 from "@clack/prompts";
|
|
2961
3564
|
import chalk11 from "chalk";
|
|
2962
3565
|
async function listCommand(filter, options) {
|
|
@@ -2973,30 +3576,30 @@ Valid options: skills, agents`);
|
|
|
2973
3576
|
}
|
|
2974
3577
|
const skills = [];
|
|
2975
3578
|
const agents = [];
|
|
2976
|
-
const pluginDir = manifest?.pluginPath ?
|
|
2977
|
-
if (pluginDir &&
|
|
3579
|
+
const pluginDir = manifest?.pluginPath ? join17(targetDir, manifest.pluginPath) : null;
|
|
3580
|
+
if (pluginDir && existsSync13(pluginDir)) {
|
|
2978
3581
|
if (showSkills) {
|
|
2979
|
-
const skillsDir =
|
|
2980
|
-
if (
|
|
3582
|
+
const skillsDir = join17(pluginDir, "skills");
|
|
3583
|
+
if (existsSync13(skillsDir)) {
|
|
2981
3584
|
skills.push(...discoverSkills(skillsDir));
|
|
2982
3585
|
}
|
|
2983
3586
|
}
|
|
2984
3587
|
if (showAgents) {
|
|
2985
|
-
const agentsDir =
|
|
2986
|
-
if (
|
|
3588
|
+
const agentsDir = join17(pluginDir, "agents");
|
|
3589
|
+
if (existsSync13(agentsDir)) {
|
|
2987
3590
|
agents.push(...discoverAgents(agentsDir));
|
|
2988
3591
|
}
|
|
2989
3592
|
}
|
|
2990
3593
|
}
|
|
2991
3594
|
if (showSkills && skills.length === 0) {
|
|
2992
|
-
const claudeSkills =
|
|
2993
|
-
if (
|
|
3595
|
+
const claudeSkills = join17(targetDir, ".claude", "skills");
|
|
3596
|
+
if (existsSync13(claudeSkills)) {
|
|
2994
3597
|
skills.push(...discoverSkills(claudeSkills));
|
|
2995
3598
|
}
|
|
2996
3599
|
}
|
|
2997
3600
|
if (showAgents && agents.length === 0) {
|
|
2998
|
-
const claudeAgents =
|
|
2999
|
-
if (
|
|
3601
|
+
const claudeAgents = join17(targetDir, ".claude", "agents");
|
|
3602
|
+
if (existsSync13(claudeAgents)) {
|
|
3000
3603
|
agents.push(...discoverAgents(claudeAgents));
|
|
3001
3604
|
}
|
|
3002
3605
|
}
|
|
@@ -3020,15 +3623,15 @@ Valid options: skills, agents`);
|
|
|
3020
3623
|
}
|
|
3021
3624
|
function discoverSkills(skillsDir) {
|
|
3022
3625
|
const items = [];
|
|
3023
|
-
const entries =
|
|
3626
|
+
const entries = readdirSync4(skillsDir, { withFileTypes: true });
|
|
3024
3627
|
for (const entry of entries) {
|
|
3025
3628
|
if (entry.isDirectory()) {
|
|
3026
|
-
const skillMd =
|
|
3027
|
-
const description =
|
|
3629
|
+
const skillMd = join17(skillsDir, entry.name, "SKILL.md");
|
|
3630
|
+
const description = existsSync13(skillMd) ? extractDescription(skillMd) : "";
|
|
3028
3631
|
items.push({ name: entry.name, description });
|
|
3029
3632
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
3030
3633
|
const name = entry.name.replace(/\.md$/, "");
|
|
3031
|
-
const description = extractDescription(
|
|
3634
|
+
const description = extractDescription(join17(skillsDir, entry.name));
|
|
3032
3635
|
items.push({ name, description });
|
|
3033
3636
|
}
|
|
3034
3637
|
}
|
|
@@ -3036,11 +3639,11 @@ function discoverSkills(skillsDir) {
|
|
|
3036
3639
|
}
|
|
3037
3640
|
function discoverAgents(agentsDir) {
|
|
3038
3641
|
const items = [];
|
|
3039
|
-
const entries =
|
|
3642
|
+
const entries = readdirSync4(agentsDir, { withFileTypes: true });
|
|
3040
3643
|
for (const entry of entries) {
|
|
3041
3644
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
3042
3645
|
const name = entry.name.replace(/\.md$/, "");
|
|
3043
|
-
const description = extractDescription(
|
|
3646
|
+
const description = extractDescription(join17(agentsDir, entry.name));
|
|
3044
3647
|
items.push({ name, description });
|
|
3045
3648
|
}
|
|
3046
3649
|
}
|
|
@@ -3048,7 +3651,7 @@ function discoverAgents(agentsDir) {
|
|
|
3048
3651
|
}
|
|
3049
3652
|
function extractDescription(filePath) {
|
|
3050
3653
|
try {
|
|
3051
|
-
const content =
|
|
3654
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
3052
3655
|
const lines = content.split("\n");
|
|
3053
3656
|
let pastHeading = false;
|
|
3054
3657
|
for (const line of lines) {
|
|
@@ -3227,8 +3830,8 @@ async function configResetCommand(options) {
|
|
|
3227
3830
|
}
|
|
3228
3831
|
|
|
3229
3832
|
// src/commands/doctor.ts
|
|
3230
|
-
import { resolve as resolve11, join as
|
|
3231
|
-
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";
|
|
3232
3835
|
import { execSync } from "child_process";
|
|
3233
3836
|
import * as p12 from "@clack/prompts";
|
|
3234
3837
|
import chalk13 from "chalk";
|
|
@@ -3329,7 +3932,7 @@ function checkManifestFiles(targetDir, manifest) {
|
|
|
3329
3932
|
let existCount = 0;
|
|
3330
3933
|
const missing = [];
|
|
3331
3934
|
for (const relativePath of Object.keys(manifest.files)) {
|
|
3332
|
-
if (fileExists(
|
|
3935
|
+
if (fileExists(join18(targetDir, relativePath))) {
|
|
3333
3936
|
existCount++;
|
|
3334
3937
|
} else {
|
|
3335
3938
|
missing.push(relativePath);
|
|
@@ -3349,15 +3952,15 @@ function checkManifestFiles(targetDir, manifest) {
|
|
|
3349
3952
|
};
|
|
3350
3953
|
}
|
|
3351
3954
|
function checkScriptPermissions(targetDir, manifest) {
|
|
3352
|
-
const pluginDir = manifest.pluginPath ?
|
|
3955
|
+
const pluginDir = manifest.pluginPath ? join18(targetDir, manifest.pluginPath) : null;
|
|
3353
3956
|
const shFiles = [];
|
|
3354
|
-
if (pluginDir &&
|
|
3355
|
-
const scriptsDir =
|
|
3356
|
-
if (
|
|
3357
|
-
const entries =
|
|
3957
|
+
if (pluginDir && existsSync14(pluginDir)) {
|
|
3958
|
+
const scriptsDir = join18(pluginDir, "scripts");
|
|
3959
|
+
if (existsSync14(scriptsDir)) {
|
|
3960
|
+
const entries = readdirSync5(scriptsDir);
|
|
3358
3961
|
for (const entry of entries) {
|
|
3359
3962
|
if (entry.endsWith(".sh")) {
|
|
3360
|
-
shFiles.push(
|
|
3963
|
+
shFiles.push(join18(scriptsDir, entry));
|
|
3361
3964
|
}
|
|
3362
3965
|
}
|
|
3363
3966
|
}
|
|
@@ -3420,28 +4023,28 @@ function checkPluginRegistered(targetDir) {
|
|
|
3420
4023
|
};
|
|
3421
4024
|
}
|
|
3422
4025
|
function checkHookScripts(targetDir, manifest) {
|
|
3423
|
-
const pluginDir = manifest.pluginPath ?
|
|
3424
|
-
if (!pluginDir || !
|
|
4026
|
+
const pluginDir = manifest.pluginPath ? join18(targetDir, manifest.pluginPath) : null;
|
|
4027
|
+
if (!pluginDir || !existsSync14(pluginDir)) {
|
|
3425
4028
|
return {
|
|
3426
4029
|
name: "hooks",
|
|
3427
4030
|
status: "pass",
|
|
3428
4031
|
message: "No hooks directory to check"
|
|
3429
4032
|
};
|
|
3430
4033
|
}
|
|
3431
|
-
const hooksDir =
|
|
3432
|
-
if (!
|
|
4034
|
+
const hooksDir = join18(pluginDir, "hooks");
|
|
4035
|
+
if (!existsSync14(hooksDir)) {
|
|
3433
4036
|
return {
|
|
3434
4037
|
name: "hooks",
|
|
3435
4038
|
status: "pass",
|
|
3436
4039
|
message: "No hooks directory found"
|
|
3437
4040
|
};
|
|
3438
4041
|
}
|
|
3439
|
-
const hookFiles =
|
|
4042
|
+
const hookFiles = readdirSync5(hooksDir).filter((f) => f.endsWith(".json"));
|
|
3440
4043
|
let total = 0;
|
|
3441
4044
|
let valid = 0;
|
|
3442
4045
|
for (const hookFile of hookFiles) {
|
|
3443
4046
|
try {
|
|
3444
|
-
const hookContent = JSON.parse(readFile(
|
|
4047
|
+
const hookContent = JSON.parse(readFile(join18(hooksDir, hookFile)));
|
|
3445
4048
|
const hooks = Array.isArray(hookContent) ? hookContent : [hookContent];
|
|
3446
4049
|
for (const hook of hooks) {
|
|
3447
4050
|
if (hook.command) {
|
|
@@ -3455,8 +4058,8 @@ function checkHookScripts(targetDir, manifest) {
|
|
|
3455
4058
|
/\$\{[A-Z_]+\}\//g,
|
|
3456
4059
|
""
|
|
3457
4060
|
);
|
|
3458
|
-
const scriptPath =
|
|
3459
|
-
if (
|
|
4061
|
+
const scriptPath = join18(pluginDir, resolved);
|
|
4062
|
+
if (existsSync14(scriptPath)) {
|
|
3460
4063
|
valid++;
|
|
3461
4064
|
}
|
|
3462
4065
|
} else {
|
|
@@ -3480,7 +4083,7 @@ function checkHookScripts(targetDir, manifest) {
|
|
|
3480
4083
|
};
|
|
3481
4084
|
}
|
|
3482
4085
|
function checkQualityScripts(targetDir) {
|
|
3483
|
-
const pkgPath =
|
|
4086
|
+
const pkgPath = join18(targetDir, "package.json");
|
|
3484
4087
|
if (!fileExists(pkgPath)) {
|
|
3485
4088
|
return { name: "quality", status: "warn", message: "No package.json found" };
|
|
3486
4089
|
}
|
|
@@ -3506,8 +4109,8 @@ function checkQualityScripts(targetDir) {
|
|
|
3506
4109
|
}
|
|
3507
4110
|
}
|
|
3508
4111
|
function checkThoughtsDir(targetDir) {
|
|
3509
|
-
const thoughtsDir =
|
|
3510
|
-
if (
|
|
4112
|
+
const thoughtsDir = join18(targetDir, "thoughts");
|
|
4113
|
+
if (existsSync14(thoughtsDir)) {
|
|
3511
4114
|
return {
|
|
3512
4115
|
name: "thoughts",
|
|
3513
4116
|
status: "pass",
|
|
@@ -3531,15 +4134,15 @@ function checkThoughtsDir(targetDir) {
|
|
|
3531
4134
|
"thoughts/archive/backlog"
|
|
3532
4135
|
];
|
|
3533
4136
|
for (const dir of dirs) {
|
|
3534
|
-
|
|
4137
|
+
mkdirSync3(join18(targetDir, dir), { recursive: true });
|
|
3535
4138
|
}
|
|
3536
4139
|
}
|
|
3537
4140
|
};
|
|
3538
4141
|
}
|
|
3539
4142
|
function checkEslint(targetDir) {
|
|
3540
4143
|
try {
|
|
3541
|
-
const eslintLocal =
|
|
3542
|
-
if (
|
|
4144
|
+
const eslintLocal = join18(targetDir, "node_modules", ".bin", "eslint");
|
|
4145
|
+
if (existsSync14(eslintLocal)) {
|
|
3543
4146
|
return { name: "eslint", status: "pass", message: "eslint is available" };
|
|
3544
4147
|
}
|
|
3545
4148
|
execSync("which eslint", { stdio: "pipe" });
|
|
@@ -3554,8 +4157,8 @@ function checkEslint(targetDir) {
|
|
|
3554
4157
|
}
|
|
3555
4158
|
|
|
3556
4159
|
// src/commands/uninstall.ts
|
|
3557
|
-
import { existsSync as
|
|
3558
|
-
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";
|
|
3559
4162
|
import * as p13 from "@clack/prompts";
|
|
3560
4163
|
import chalk14 from "chalk";
|
|
3561
4164
|
var DEVTRONIC_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
@@ -3573,12 +4176,12 @@ async function uninstallCommand(options) {
|
|
|
3573
4176
|
return;
|
|
3574
4177
|
}
|
|
3575
4178
|
const managedFiles = Object.keys(manifest.files);
|
|
3576
|
-
const existingFiles = managedFiles.filter((f) => fileExists(
|
|
3577
|
-
const missingFiles = managedFiles.filter((f) => !fileExists(
|
|
3578
|
-
const hasPlugin = manifest.installMode === "plugin" &&
|
|
3579
|
-
const hasThoughts =
|
|
3580
|
-
const hasClaudeMd = fileExists(
|
|
3581
|
-
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"));
|
|
3582
4185
|
p13.log.info(`Installation found: v${manifest.version} (${manifest.implantedAt})`);
|
|
3583
4186
|
p13.log.info(`IDEs: ${manifest.selectedIDEs.join(", ")}`);
|
|
3584
4187
|
p13.log.info(`Mode: ${manifest.installMode || "standalone"}`);
|
|
@@ -3664,18 +4267,18 @@ async function uninstallCommand(options) {
|
|
|
3664
4267
|
}
|
|
3665
4268
|
if (hasPlugin) {
|
|
3666
4269
|
try {
|
|
3667
|
-
|
|
4270
|
+
rmSync2(join19(targetDir, PLUGIN_DIR, PLUGIN_NAME), { recursive: true, force: true });
|
|
3668
4271
|
removed.push(`${PLUGIN_DIR}/${PLUGIN_NAME}/`);
|
|
3669
|
-
const marketplaceDescDir =
|
|
3670
|
-
if (
|
|
3671
|
-
|
|
4272
|
+
const marketplaceDescDir = join19(targetDir, PLUGIN_DIR, ".claude-plugin");
|
|
4273
|
+
if (existsSync15(marketplaceDescDir)) {
|
|
4274
|
+
rmSync2(marketplaceDescDir, { recursive: true, force: true });
|
|
3672
4275
|
removed.push(`${PLUGIN_DIR}/.claude-plugin/`);
|
|
3673
4276
|
}
|
|
3674
|
-
const pluginsDir =
|
|
3675
|
-
if (
|
|
4277
|
+
const pluginsDir = join19(targetDir, PLUGIN_DIR);
|
|
4278
|
+
if (existsSync15(pluginsDir)) {
|
|
3676
4279
|
const remaining = readdirSafe(pluginsDir);
|
|
3677
4280
|
if (remaining.length === 0) {
|
|
3678
|
-
|
|
4281
|
+
rmSync2(pluginsDir, { recursive: true, force: true });
|
|
3679
4282
|
removed.push(`${PLUGIN_DIR}/ (empty)`);
|
|
3680
4283
|
}
|
|
3681
4284
|
}
|
|
@@ -3688,11 +4291,11 @@ async function uninstallCommand(options) {
|
|
|
3688
4291
|
if (file.startsWith("thoughts/")) continue;
|
|
3689
4292
|
if (file.startsWith(PLUGIN_DIR + "/")) continue;
|
|
3690
4293
|
try {
|
|
3691
|
-
const filePath =
|
|
3692
|
-
if (
|
|
3693
|
-
|
|
4294
|
+
const filePath = join19(targetDir, file);
|
|
4295
|
+
if (existsSync15(filePath)) {
|
|
4296
|
+
rmSync2(filePath, { force: true });
|
|
3694
4297
|
removed.push(file);
|
|
3695
|
-
cleanEmptyParents(targetDir,
|
|
4298
|
+
cleanEmptyParents(targetDir, dirname8(file));
|
|
3696
4299
|
}
|
|
3697
4300
|
} catch (err) {
|
|
3698
4301
|
errors.push(`Failed to remove ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3701,7 +4304,7 @@ async function uninstallCommand(options) {
|
|
|
3701
4304
|
if (hasClaudeMd) {
|
|
3702
4305
|
if (removeClaudeMd) {
|
|
3703
4306
|
try {
|
|
3704
|
-
|
|
4307
|
+
rmSync2(join19(targetDir, "CLAUDE.md"), { force: true });
|
|
3705
4308
|
removed.push("CLAUDE.md");
|
|
3706
4309
|
} catch (err) {
|
|
3707
4310
|
errors.push(`Failed to remove CLAUDE.md: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3713,7 +4316,7 @@ async function uninstallCommand(options) {
|
|
|
3713
4316
|
if (hasAgentsMd) {
|
|
3714
4317
|
if (removeAgentsMd) {
|
|
3715
4318
|
try {
|
|
3716
|
-
|
|
4319
|
+
rmSync2(join19(targetDir, "AGENTS.md"), { force: true });
|
|
3717
4320
|
removed.push("AGENTS.md");
|
|
3718
4321
|
} catch (err) {
|
|
3719
4322
|
errors.push(`Failed to remove AGENTS.md: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3725,7 +4328,7 @@ async function uninstallCommand(options) {
|
|
|
3725
4328
|
if (hasThoughts) {
|
|
3726
4329
|
if (removeThoughts) {
|
|
3727
4330
|
try {
|
|
3728
|
-
|
|
4331
|
+
rmSync2(join19(targetDir, "thoughts"), { recursive: true, force: true });
|
|
3729
4332
|
removed.push("thoughts/");
|
|
3730
4333
|
} catch (err) {
|
|
3731
4334
|
errors.push(`Failed to remove thoughts/: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3735,9 +4338,9 @@ async function uninstallCommand(options) {
|
|
|
3735
4338
|
}
|
|
3736
4339
|
}
|
|
3737
4340
|
try {
|
|
3738
|
-
const manifestDir =
|
|
3739
|
-
if (
|
|
3740
|
-
|
|
4341
|
+
const manifestDir = join19(targetDir, MANIFEST_DIR);
|
|
4342
|
+
if (existsSync15(manifestDir)) {
|
|
4343
|
+
rmSync2(manifestDir, { recursive: true, force: true });
|
|
3741
4344
|
removed.push(`${MANIFEST_DIR}/`);
|
|
3742
4345
|
}
|
|
3743
4346
|
} catch (err) {
|
|
@@ -3782,33 +4385,59 @@ async function uninstallCommand(options) {
|
|
|
3782
4385
|
}
|
|
3783
4386
|
function cleanEmptyParents(targetDir, relDir) {
|
|
3784
4387
|
if (!relDir || relDir === ".") return;
|
|
3785
|
-
const absDir =
|
|
3786
|
-
if (!
|
|
4388
|
+
const absDir = join19(targetDir, relDir);
|
|
4389
|
+
if (!existsSync15(absDir)) return;
|
|
3787
4390
|
const entries = readdirSafe(absDir);
|
|
3788
4391
|
if (entries.length === 0) {
|
|
3789
4392
|
try {
|
|
3790
|
-
|
|
3791
|
-
cleanEmptyParents(targetDir,
|
|
4393
|
+
rmSync2(absDir, { recursive: true, force: true });
|
|
4394
|
+
cleanEmptyParents(targetDir, dirname8(relDir));
|
|
3792
4395
|
} catch {
|
|
3793
4396
|
}
|
|
3794
4397
|
}
|
|
3795
4398
|
}
|
|
3796
4399
|
function readdirSafe(dir) {
|
|
3797
4400
|
try {
|
|
3798
|
-
return
|
|
4401
|
+
return readdirSync6(dir);
|
|
3799
4402
|
} catch {
|
|
3800
4403
|
return [];
|
|
3801
4404
|
}
|
|
3802
4405
|
}
|
|
3803
4406
|
|
|
3804
4407
|
// src/commands/addon.ts
|
|
3805
|
-
import { resolve as resolve13, join as
|
|
3806
|
-
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";
|
|
3807
4410
|
import * as p14 from "@clack/prompts";
|
|
3808
4411
|
import chalk15 from "chalk";
|
|
4412
|
+
function isFileBasedAddon(addonName) {
|
|
4413
|
+
return addonName !== "orchestration";
|
|
4414
|
+
}
|
|
3809
4415
|
async function addonCommand(action, addonName, options) {
|
|
3810
4416
|
const targetDir = resolve13(options.path || ".");
|
|
3811
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
|
+
}
|
|
3812
4441
|
const manifest = readManifest(targetDir);
|
|
3813
4442
|
if (!manifest) {
|
|
3814
4443
|
p14.log.warn("No devtronic installation found.");
|
|
@@ -3822,16 +4451,9 @@ async function addonCommand(action, addonName, options) {
|
|
|
3822
4451
|
p14.outro("");
|
|
3823
4452
|
return;
|
|
3824
4453
|
}
|
|
3825
|
-
const
|
|
3826
|
-
if (!validAddons.includes(addonName)) {
|
|
3827
|
-
p14.cancel(`Unknown addon: ${addonName}
|
|
3828
|
-
|
|
3829
|
-
Valid addons: ${validAddons.join(", ")}`);
|
|
3830
|
-
process.exit(1);
|
|
3831
|
-
}
|
|
3832
|
-
const addon = ADDONS[addonName];
|
|
4454
|
+
const addon = ADDONS[typedName];
|
|
3833
4455
|
const currentAddons = manifest.projectConfig?.enabledAddons ?? [];
|
|
3834
|
-
if (
|
|
4456
|
+
if (canonicalAction === "add") {
|
|
3835
4457
|
await addAddon(targetDir, manifest, addon.name, currentAddons);
|
|
3836
4458
|
} else {
|
|
3837
4459
|
await removeAddon(targetDir, manifest, addon.name, currentAddons);
|
|
@@ -3845,7 +4467,7 @@ async function addAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
3845
4467
|
}
|
|
3846
4468
|
const addon = ADDONS[addonName];
|
|
3847
4469
|
const pluginRoot = manifest.pluginPath;
|
|
3848
|
-
const skillsSourceDir =
|
|
4470
|
+
const skillsSourceDir = join20(TEMPLATES_DIR, "claude-code", ".claude", "skills");
|
|
3849
4471
|
p14.note(
|
|
3850
4472
|
[
|
|
3851
4473
|
` ${chalk15.dim("Name:")} ${addon.label}`,
|
|
@@ -3864,18 +4486,18 @@ async function addAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
3864
4486
|
spinner8.start(`Adding ${addon.label}...`);
|
|
3865
4487
|
const addedFiles = [];
|
|
3866
4488
|
for (const skillDir of addon.skills) {
|
|
3867
|
-
const sourceDir =
|
|
3868
|
-
if (!
|
|
4489
|
+
const sourceDir = join20(skillsSourceDir, skillDir);
|
|
4490
|
+
if (!existsSync16(sourceDir)) {
|
|
3869
4491
|
spinner8.stop(`Template not found for skill: ${skillDir}`);
|
|
3870
4492
|
p14.log.warn(`Skipping ${skillDir} \u2014 template not found.`);
|
|
3871
4493
|
continue;
|
|
3872
4494
|
}
|
|
3873
4495
|
const templateFiles = getAllFilesRecursive(sourceDir);
|
|
3874
4496
|
for (const file of templateFiles) {
|
|
3875
|
-
const content = readFile(
|
|
3876
|
-
const destRelPath =
|
|
3877
|
-
const destAbsPath =
|
|
3878
|
-
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));
|
|
3879
4501
|
writeFile(destAbsPath, content);
|
|
3880
4502
|
manifest.files[destRelPath] = createManifestEntry(content);
|
|
3881
4503
|
addedFiles.push(destRelPath);
|
|
@@ -3920,11 +4542,11 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
3920
4542
|
}
|
|
3921
4543
|
const modifiedFiles = [];
|
|
3922
4544
|
for (const skillDir of addon.skills) {
|
|
3923
|
-
const skillRelBase =
|
|
4545
|
+
const skillRelBase = join20(pluginRoot, "skills", skillDir);
|
|
3924
4546
|
for (const [filePath, fileInfo] of Object.entries(manifest.files)) {
|
|
3925
4547
|
if (!filePath.startsWith(skillRelBase)) continue;
|
|
3926
|
-
const absPath =
|
|
3927
|
-
if (!
|
|
4548
|
+
const absPath = join20(targetDir, filePath);
|
|
4549
|
+
if (!existsSync16(absPath)) continue;
|
|
3928
4550
|
const current = calculateChecksum(readFile(absPath));
|
|
3929
4551
|
if (current !== fileInfo.originalChecksum) {
|
|
3930
4552
|
modifiedFiles.push(filePath);
|
|
@@ -3947,18 +4569,18 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
3947
4569
|
const spinner8 = p14.spinner();
|
|
3948
4570
|
spinner8.start(`Removing ${addon.label}...`);
|
|
3949
4571
|
for (const skillDir of addon.skills) {
|
|
3950
|
-
const skillRelBase =
|
|
3951
|
-
const skillAbsDir =
|
|
4572
|
+
const skillRelBase = join20(pluginRoot, "skills", skillDir);
|
|
4573
|
+
const skillAbsDir = join20(targetDir, skillRelBase);
|
|
3952
4574
|
for (const filePath of Object.keys(manifest.files)) {
|
|
3953
4575
|
if (filePath.startsWith(skillRelBase)) {
|
|
3954
|
-
const absPath =
|
|
3955
|
-
if (
|
|
4576
|
+
const absPath = join20(targetDir, filePath);
|
|
4577
|
+
if (existsSync16(absPath)) unlinkSync3(absPath);
|
|
3956
4578
|
delete manifest.files[filePath];
|
|
3957
4579
|
}
|
|
3958
4580
|
}
|
|
3959
|
-
if (
|
|
4581
|
+
if (existsSync16(skillAbsDir)) {
|
|
3960
4582
|
try {
|
|
3961
|
-
|
|
4583
|
+
rmSync3(skillAbsDir, { recursive: true });
|
|
3962
4584
|
} catch {
|
|
3963
4585
|
}
|
|
3964
4586
|
}
|
|
@@ -3975,32 +4597,196 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
3975
4597
|
);
|
|
3976
4598
|
p14.outro("Done. Restart Claude Code to apply the changes.");
|
|
3977
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
|
+
}
|
|
3978
4731
|
function updateDescriptors(targetDir, manifest, pluginRoot, addonSkillCount) {
|
|
3979
4732
|
const cliVersion2 = getCliVersion();
|
|
3980
4733
|
const pluginJsonContent = generatePluginJson(cliVersion2, addonSkillCount);
|
|
3981
|
-
const pluginJsonRelPath =
|
|
3982
|
-
writeFile(
|
|
4734
|
+
const pluginJsonRelPath = join20(pluginRoot, ".claude-plugin", "plugin.json");
|
|
4735
|
+
writeFile(join20(targetDir, pluginJsonRelPath), pluginJsonContent);
|
|
3983
4736
|
manifest.files[pluginJsonRelPath] = createManifestEntry(pluginJsonContent);
|
|
3984
4737
|
const marketplaceContent = generateMarketplaceJson(addonSkillCount);
|
|
3985
|
-
const marketplaceRelPath =
|
|
3986
|
-
writeFile(
|
|
4738
|
+
const marketplaceRelPath = join20(PLUGIN_DIR, ".claude-plugin", "marketplace.json");
|
|
4739
|
+
writeFile(join20(targetDir, marketplaceRelPath), marketplaceContent);
|
|
3987
4740
|
manifest.files[marketplaceRelPath] = createManifestEntry(marketplaceContent);
|
|
3988
4741
|
}
|
|
3989
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
|
+
|
|
3990
4775
|
// src/index.ts
|
|
3991
4776
|
var cliVersion = getCliVersion();
|
|
3992
4777
|
var program = new Command();
|
|
3993
4778
|
program.name("devtronic").description("AI-assisted development toolkit").version(cliVersion).action(() => {
|
|
3994
4779
|
showLogo();
|
|
3995
|
-
console.log(
|
|
4780
|
+
console.log(chalk17.dim(` Agentic development toolkit v${cliVersion}`));
|
|
3996
4781
|
console.log();
|
|
3997
|
-
console.log(` ${
|
|
3998
|
-
console.log(` ${
|
|
3999
|
-
console.log(` ${
|
|
4000
|
-
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")}`);
|
|
4001
4787
|
console.log();
|
|
4002
|
-
console.log(` ${
|
|
4003
|
-
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")}`);
|
|
4004
4790
|
console.log();
|
|
4005
4791
|
});
|
|
4006
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(
|
|
@@ -4059,13 +4845,20 @@ program.command("doctor").description("Run health checks on your devtronic insta
|
|
|
4059
4845
|
program.command("uninstall").description("Remove devtronic from your project").option("--path <path>", "Target directory (default: current directory)").action(async (options) => {
|
|
4060
4846
|
await uninstallCommand({ path: options.path });
|
|
4061
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
|
+
});
|
|
4062
4855
|
program.command("help").description("Show help (use --all for full reference)").option("-a, --all", "Show all commands with their options").action((options) => {
|
|
4063
4856
|
if (!options.all) {
|
|
4064
4857
|
program.outputHelp();
|
|
4065
4858
|
return;
|
|
4066
4859
|
}
|
|
4067
4860
|
showLogo();
|
|
4068
|
-
console.log(
|
|
4861
|
+
console.log(chalk17.dim(` Agentic development toolkit v${cliVersion}
|
|
4069
4862
|
`));
|
|
4070
4863
|
const sections = [
|
|
4071
4864
|
{
|
|
@@ -4089,7 +4882,7 @@ program.command("help").description("Show help (use --all for full reference)").
|
|
|
4089
4882
|
{
|
|
4090
4883
|
title: "Addons",
|
|
4091
4884
|
usage: "addon add <name>",
|
|
4092
|
-
desc: "Add an optional skill pack (e.g., orchestration)",
|
|
4885
|
+
desc: "Add an optional skill pack (e.g., orchestration, design-best-practices)",
|
|
4093
4886
|
opts: ["--path <path> Target directory"]
|
|
4094
4887
|
},
|
|
4095
4888
|
{
|
|
@@ -4098,6 +4891,30 @@ program.command("help").description("Show help (use --all for full reference)").
|
|
|
4098
4891
|
desc: "Remove an addon skill pack",
|
|
4099
4892
|
opts: ["--path <path> Target directory"]
|
|
4100
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
|
+
},
|
|
4101
4918
|
{
|
|
4102
4919
|
title: "Maintenance",
|
|
4103
4920
|
usage: "update",
|
|
@@ -4151,6 +4968,12 @@ program.command("help").description("Show help (use --all for full reference)").
|
|
|
4151
4968
|
usage: "presets",
|
|
4152
4969
|
desc: "List available configuration presets"
|
|
4153
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
|
+
},
|
|
4154
4977
|
{
|
|
4155
4978
|
title: "Diagnostics",
|
|
4156
4979
|
usage: "status",
|
|
@@ -4186,14 +5009,14 @@ program.command("help").description("Show help (use --all for full reference)").
|
|
|
4186
5009
|
];
|
|
4187
5010
|
for (const section of sections) {
|
|
4188
5011
|
if (section.title) {
|
|
4189
|
-
console.log(` ${
|
|
5012
|
+
console.log(` ${chalk17.bold.underline(section.title)}
|
|
4190
5013
|
`);
|
|
4191
5014
|
}
|
|
4192
|
-
console.log(` ${
|
|
4193
|
-
console.log(` ${
|
|
5015
|
+
console.log(` ${chalk17.white("devtronic " + section.usage)}`);
|
|
5016
|
+
console.log(` ${chalk17.dim(section.desc)}`);
|
|
4194
5017
|
if (section.opts) {
|
|
4195
5018
|
for (const opt of section.opts) {
|
|
4196
|
-
console.log(` ${
|
|
5019
|
+
console.log(` ${chalk17.yellow(opt)}`);
|
|
4197
5020
|
}
|
|
4198
5021
|
}
|
|
4199
5022
|
console.log();
|
|
@@ -4206,21 +5029,33 @@ addonCmd.command("add").description("Add an addon to the devtronic plugin").argu
|
|
|
4206
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) => {
|
|
4207
5030
|
await addonCommand("remove", name, { path: options.path });
|
|
4208
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
|
+
});
|
|
4209
5044
|
program.command("presets").description("List available configuration presets").action(() => {
|
|
4210
|
-
|
|
5045
|
+
p16.intro(introTitle("Presets"));
|
|
4211
5046
|
const lines = Object.entries(PRESETS).map(([name, preset]) => {
|
|
4212
|
-
const parts = [` ${
|
|
5047
|
+
const parts = [` ${chalk17.bold(name)}`];
|
|
4213
5048
|
parts.push(` ${preset.description}`);
|
|
4214
5049
|
if (preset.config.architecture) {
|
|
4215
|
-
parts.push(` Architecture: ${
|
|
5050
|
+
parts.push(` Architecture: ${chalk17.cyan(preset.config.architecture)}`);
|
|
4216
5051
|
}
|
|
4217
5052
|
if (preset.config.layers) {
|
|
4218
|
-
parts.push(` Layers: ${
|
|
5053
|
+
parts.push(` Layers: ${chalk17.cyan(preset.config.layers.join(", "))}`);
|
|
4219
5054
|
}
|
|
4220
5055
|
return parts.join("\n");
|
|
4221
5056
|
});
|
|
4222
|
-
|
|
4223
|
-
|
|
5057
|
+
p16.note(lines.join("\n\n"), "Available Presets");
|
|
5058
|
+
p16.outro(`Usage: ${chalk17.cyan("npx devtronic init --preset <name>")}`);
|
|
4224
5059
|
});
|
|
4225
5060
|
program.parseAsync().catch((err) => {
|
|
4226
5061
|
console.error(`
|