devtronic 1.1.0 → 1.2.1
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 +26 -4
- package/dist/{chunk-5V4PXTP6.js → chunk-V4QEAL7Y.js} +43 -19
- package/dist/index.js +1052 -222
- 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";
|
|
@@ -453,10 +453,16 @@ async function promptForAgentsMd() {
|
|
|
453
453
|
initialValue: true
|
|
454
454
|
});
|
|
455
455
|
}
|
|
456
|
-
async function
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
456
|
+
async function promptForAddons() {
|
|
457
|
+
const options = Object.values(ADDONS).map((addon) => ({
|
|
458
|
+
value: addon.name,
|
|
459
|
+
label: addon.label,
|
|
460
|
+
hint: addon.description
|
|
461
|
+
}));
|
|
462
|
+
return p.multiselect({
|
|
463
|
+
message: "Enable optional addon packs? (space to toggle, enter to confirm)",
|
|
464
|
+
options,
|
|
465
|
+
required: false
|
|
460
466
|
});
|
|
461
467
|
}
|
|
462
468
|
|
|
@@ -1207,9 +1213,9 @@ function getCliVersion() {
|
|
|
1207
1213
|
resolve(__dirname, "../package.json"),
|
|
1208
1214
|
resolve(__dirname, "../../package.json")
|
|
1209
1215
|
];
|
|
1210
|
-
for (const
|
|
1216
|
+
for (const p17 of paths) {
|
|
1211
1217
|
try {
|
|
1212
|
-
const pkg = JSON.parse(readFileSync(
|
|
1218
|
+
const pkg = JSON.parse(readFileSync(p17, "utf-8"));
|
|
1213
1219
|
if (pkg.name === "devtronic" && pkg.version) {
|
|
1214
1220
|
return pkg.version;
|
|
1215
1221
|
}
|
|
@@ -1368,14 +1374,12 @@ Valid addons: ${validAddons.join(", ")}`);
|
|
|
1368
1374
|
}
|
|
1369
1375
|
}
|
|
1370
1376
|
if (selectedIDEs.includes("claude-code") && !options.addon && !options.yes && !options.preset && !options.preview) {
|
|
1371
|
-
const
|
|
1372
|
-
if (p3.isCancel(
|
|
1377
|
+
const selectedAddons = await promptForAddons();
|
|
1378
|
+
if (p3.isCancel(selectedAddons)) {
|
|
1373
1379
|
p3.cancel("Operation cancelled");
|
|
1374
1380
|
process.exit(0);
|
|
1375
1381
|
}
|
|
1376
|
-
|
|
1377
|
-
enabledAddons = ["orchestration"];
|
|
1378
|
-
}
|
|
1382
|
+
enabledAddons = selectedAddons;
|
|
1379
1383
|
}
|
|
1380
1384
|
if (enabledAddons.length > 0) {
|
|
1381
1385
|
projectConfig.enabledAddons = enabledAddons;
|
|
@@ -1604,6 +1608,13 @@ Valid addons: ${validAddons.join(", ")}`);
|
|
|
1604
1608
|
].join("\n"),
|
|
1605
1609
|
"Next Steps"
|
|
1606
1610
|
);
|
|
1611
|
+
p3.note(
|
|
1612
|
+
[
|
|
1613
|
+
`${chalk4.dim("Execution mode:")} HITL ${chalk4.dim("(default \u2014 safe for unfamiliar codebases)")}`,
|
|
1614
|
+
`${chalk4.dim("Change anytime:")} ${chalk4.cyan("npx devtronic mode afk")}`
|
|
1615
|
+
].join("\n"),
|
|
1616
|
+
"Autonomous Mode"
|
|
1617
|
+
);
|
|
1607
1618
|
p3.outro(chalk4.green("Setup complete!"));
|
|
1608
1619
|
} catch (err) {
|
|
1609
1620
|
spinner8.stop("Configuration failed");
|
|
@@ -1706,8 +1717,8 @@ function buildProjectConfigFromPreset(presetConfig, analysis) {
|
|
|
1706
1717
|
}
|
|
1707
1718
|
|
|
1708
1719
|
// src/commands/update.ts
|
|
1709
|
-
import { resolve as resolve3, join as
|
|
1710
|
-
import { existsSync as
|
|
1720
|
+
import { resolve as resolve3, join as join11, dirname as dirname5 } from "path";
|
|
1721
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2, lstatSync, readdirSync as readdirSync2, rmdirSync as rmdirSync2, chmodSync as chmodSync2 } from "fs";
|
|
1711
1722
|
import * as p4 from "@clack/prompts";
|
|
1712
1723
|
import chalk5 from "chalk";
|
|
1713
1724
|
|
|
@@ -1735,6 +1746,553 @@ var REMOVED_FILES = {
|
|
|
1735
1746
|
}
|
|
1736
1747
|
};
|
|
1737
1748
|
|
|
1749
|
+
// src/utils/addonConfig.ts
|
|
1750
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync, mkdirSync, renameSync } from "fs";
|
|
1751
|
+
import { join as join9, dirname as dirname3 } from "path";
|
|
1752
|
+
|
|
1753
|
+
// src/addons/registry.ts
|
|
1754
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1755
|
+
import { join as join8 } from "path";
|
|
1756
|
+
function getAddonSourceDir(name) {
|
|
1757
|
+
const addonsDir = new URL(".", import.meta.url).pathname;
|
|
1758
|
+
return join8(addonsDir, name);
|
|
1759
|
+
}
|
|
1760
|
+
function getAddonManifest(name) {
|
|
1761
|
+
const sourceDir = getAddonSourceDir(name);
|
|
1762
|
+
const manifestPath = join8(sourceDir, "manifest.json");
|
|
1763
|
+
const raw = readFileSync2(manifestPath, "utf-8");
|
|
1764
|
+
const manifest = JSON.parse(raw);
|
|
1765
|
+
if (!manifest.name) throw new Error(`Addon manifest missing "name": ${manifestPath}`);
|
|
1766
|
+
if (!manifest.version) throw new Error(`Addon manifest missing "version": ${manifestPath}`);
|
|
1767
|
+
if (!manifest.files?.skills?.length) throw new Error(`Addon manifest has empty skills: ${manifestPath}`);
|
|
1768
|
+
return manifest;
|
|
1769
|
+
}
|
|
1770
|
+
function getAvailableAddons() {
|
|
1771
|
+
return Object.values(ADDONS);
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
// src/utils/addonConfig.ts
|
|
1775
|
+
var CONFIG_DIR = ".claude";
|
|
1776
|
+
var CONFIG_FILE = "devtronic.json";
|
|
1777
|
+
function getConfigPath(targetDir) {
|
|
1778
|
+
return join9(targetDir, CONFIG_DIR, CONFIG_FILE);
|
|
1779
|
+
}
|
|
1780
|
+
function getLegacyConfigPath(targetDir) {
|
|
1781
|
+
return join9(targetDir, CONFIG_FILE);
|
|
1782
|
+
}
|
|
1783
|
+
function readAddonConfig(targetDir) {
|
|
1784
|
+
const configPath = getConfigPath(targetDir);
|
|
1785
|
+
const legacyPath = getLegacyConfigPath(targetDir);
|
|
1786
|
+
if (!existsSync6(configPath) && existsSync6(legacyPath)) {
|
|
1787
|
+
mkdirSync(dirname3(configPath), { recursive: true });
|
|
1788
|
+
renameSync(legacyPath, configPath);
|
|
1789
|
+
}
|
|
1790
|
+
if (!existsSync6(configPath)) {
|
|
1791
|
+
return { agents: ["claude"], installed: {} };
|
|
1792
|
+
}
|
|
1793
|
+
const raw = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1794
|
+
const data = raw.addons ?? raw;
|
|
1795
|
+
return {
|
|
1796
|
+
version: 1,
|
|
1797
|
+
mode: data.mode,
|
|
1798
|
+
agents: data.agents ?? ["claude"],
|
|
1799
|
+
installed: data.installed ?? {}
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
function writeAddonConfig(targetDir, config) {
|
|
1803
|
+
const configPath = getConfigPath(targetDir);
|
|
1804
|
+
mkdirSync(dirname3(configPath), { recursive: true });
|
|
1805
|
+
const payload = { version: 1, ...config };
|
|
1806
|
+
writeFileSync(configPath, JSON.stringify(payload, null, 2) + "\n");
|
|
1807
|
+
}
|
|
1808
|
+
function writeAddonToConfig(targetDir, name, entry) {
|
|
1809
|
+
const config = readAddonConfig(targetDir);
|
|
1810
|
+
config.installed[name] = entry;
|
|
1811
|
+
writeAddonConfig(targetDir, config);
|
|
1812
|
+
}
|
|
1813
|
+
function removeAddonFromConfig(targetDir, name) {
|
|
1814
|
+
const config = readAddonConfig(targetDir);
|
|
1815
|
+
delete config.installed[name];
|
|
1816
|
+
writeAddonConfig(targetDir, config);
|
|
1817
|
+
}
|
|
1818
|
+
function readMode(targetDir) {
|
|
1819
|
+
const config = readAddonConfig(targetDir);
|
|
1820
|
+
return config.mode ?? "hitl";
|
|
1821
|
+
}
|
|
1822
|
+
function writeMode(targetDir, mode) {
|
|
1823
|
+
const config = readAddonConfig(targetDir);
|
|
1824
|
+
config.mode = mode;
|
|
1825
|
+
writeAddonConfig(targetDir, config);
|
|
1826
|
+
}
|
|
1827
|
+
function detectOrphanedAddonFiles(targetDir) {
|
|
1828
|
+
const config = readAddonConfig(targetDir);
|
|
1829
|
+
const orphaned = [];
|
|
1830
|
+
for (const addon of getAvailableAddons()) {
|
|
1831
|
+
if (config.installed[addon.name]) continue;
|
|
1832
|
+
const skillDir = join9(targetDir, ".claude", "skills", addon.name);
|
|
1833
|
+
if (existsSync6(skillDir)) {
|
|
1834
|
+
orphaned.push(addon.name);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
return orphaned;
|
|
1838
|
+
}
|
|
1839
|
+
function registerAddonInConfig(targetDir, addonName) {
|
|
1840
|
+
const manifest = getAddonManifest(addonName);
|
|
1841
|
+
const fileList = [
|
|
1842
|
+
...(manifest.files.skills ?? []).map((s) => `skills/${s}`),
|
|
1843
|
+
...(manifest.files.agents ?? []).map((a) => `agents/${a}.md`),
|
|
1844
|
+
...(manifest.files.rules ?? []).map((r) => `rules/${r}`)
|
|
1845
|
+
];
|
|
1846
|
+
writeAddonToConfig(targetDir, addonName, {
|
|
1847
|
+
version: manifest.version,
|
|
1848
|
+
files: fileList
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
// src/generators/addonFiles.ts
|
|
1853
|
+
import {
|
|
1854
|
+
existsSync as existsSync7,
|
|
1855
|
+
readFileSync as readFileSync4,
|
|
1856
|
+
writeFileSync as writeFileSync2,
|
|
1857
|
+
mkdirSync as mkdirSync2,
|
|
1858
|
+
rmSync,
|
|
1859
|
+
unlinkSync,
|
|
1860
|
+
readdirSync,
|
|
1861
|
+
rmdirSync
|
|
1862
|
+
} from "fs";
|
|
1863
|
+
import { join as join10, dirname as dirname4 } from "path";
|
|
1864
|
+
import { createHash } from "crypto";
|
|
1865
|
+
var AGENT_PATHS = {
|
|
1866
|
+
claude: ".claude",
|
|
1867
|
+
cursor: ".cursor",
|
|
1868
|
+
gemini: ".gemini"
|
|
1869
|
+
};
|
|
1870
|
+
function stripFrontmatterName(content) {
|
|
1871
|
+
return content.replace(/^(---\n)([\s\S]*?)(---)/m, (_match, open, body, close) => {
|
|
1872
|
+
const cleaned = body.split("\n").filter((l) => !l.startsWith("name:")).join("\n");
|
|
1873
|
+
return `${open}${cleaned}${close}`;
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
var RUNTIME_SPECS = {
|
|
1877
|
+
claude: {
|
|
1878
|
+
baseDir: ".claude",
|
|
1879
|
+
skillAdapter: (name, content) => ({
|
|
1880
|
+
relPath: `skills/${name}/SKILL.md`,
|
|
1881
|
+
content
|
|
1882
|
+
}),
|
|
1883
|
+
rulesDir: "rules"
|
|
1884
|
+
},
|
|
1885
|
+
gemini: {
|
|
1886
|
+
baseDir: ".gemini",
|
|
1887
|
+
skillAdapter: (name, content) => ({
|
|
1888
|
+
relPath: `skills/${name}/SKILL.md`,
|
|
1889
|
+
content
|
|
1890
|
+
}),
|
|
1891
|
+
rulesDir: "rules"
|
|
1892
|
+
},
|
|
1893
|
+
opencode: {
|
|
1894
|
+
baseDir: ".opencode",
|
|
1895
|
+
skillAdapter: (name, content) => ({
|
|
1896
|
+
relPath: `command/${name}.md`,
|
|
1897
|
+
content: stripFrontmatterName(content)
|
|
1898
|
+
})
|
|
1899
|
+
},
|
|
1900
|
+
cursor: {
|
|
1901
|
+
baseDir: ".cursor",
|
|
1902
|
+
skillAdapter: (name, content) => ({
|
|
1903
|
+
relPath: `skills/${name}/SKILL.md`,
|
|
1904
|
+
content
|
|
1905
|
+
}),
|
|
1906
|
+
rulesDir: "rules"
|
|
1907
|
+
},
|
|
1908
|
+
codex: {
|
|
1909
|
+
baseDir: ".codex",
|
|
1910
|
+
skillAdapter: (name, content) => ({
|
|
1911
|
+
relPath: `skills/${name}/SKILL.md`,
|
|
1912
|
+
content
|
|
1913
|
+
})
|
|
1914
|
+
}
|
|
1915
|
+
};
|
|
1916
|
+
function checksum(content) {
|
|
1917
|
+
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
1918
|
+
}
|
|
1919
|
+
function ensureDir2(dir) {
|
|
1920
|
+
if (!existsSync7(dir)) mkdirSync2(dir, { recursive: true });
|
|
1921
|
+
}
|
|
1922
|
+
function readManifest2(addonSourceDir) {
|
|
1923
|
+
const manifestPath = join10(addonSourceDir, "manifest.json");
|
|
1924
|
+
return JSON.parse(readFileSync4(manifestPath, "utf-8"));
|
|
1925
|
+
}
|
|
1926
|
+
function buildFileMap(addonSourceDir) {
|
|
1927
|
+
const manifest = readManifest2(addonSourceDir);
|
|
1928
|
+
const files = /* @__PURE__ */ new Map();
|
|
1929
|
+
for (const skill of manifest.files.skills ?? []) {
|
|
1930
|
+
const skillDir = join10(addonSourceDir, "skills", skill);
|
|
1931
|
+
if (!existsSync7(skillDir)) continue;
|
|
1932
|
+
const skillFile = join10(skillDir, "SKILL.md");
|
|
1933
|
+
if (existsSync7(skillFile)) {
|
|
1934
|
+
files.set(`skills/${skill}/SKILL.md`, readFileSync4(skillFile, "utf-8"));
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
for (const agent of manifest.files.agents ?? []) {
|
|
1938
|
+
const agentFile = join10(addonSourceDir, "agents", `${agent}.md`);
|
|
1939
|
+
if (existsSync7(agentFile)) {
|
|
1940
|
+
files.set(`agents/${agent}.md`, readFileSync4(agentFile, "utf-8"));
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
for (const ref of manifest.files.reference ?? []) {
|
|
1944
|
+
const refPath = join10(addonSourceDir, "reference", ref);
|
|
1945
|
+
if (existsSync7(refPath)) {
|
|
1946
|
+
files.set(`skills/design-harden/reference/${ref}`, readFileSync4(refPath, "utf-8"));
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
for (const rule of manifest.files.rules ?? []) {
|
|
1950
|
+
const rulePath = join10(addonSourceDir, "rules", rule);
|
|
1951
|
+
if (existsSync7(rulePath)) {
|
|
1952
|
+
files.set(`rules/${rule}`, readFileSync4(rulePath, "utf-8"));
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
return files;
|
|
1956
|
+
}
|
|
1957
|
+
function generateAddonFiles(projectDir, addonSourceDir, agents) {
|
|
1958
|
+
const fileMap = buildFileMap(addonSourceDir);
|
|
1959
|
+
const manifest = readManifest2(addonSourceDir);
|
|
1960
|
+
const result = { written: 0, skipped: 0, conflicts: [], checksums: {} };
|
|
1961
|
+
for (const agent of agents) {
|
|
1962
|
+
const spec = RUNTIME_SPECS[agent];
|
|
1963
|
+
if (!spec) {
|
|
1964
|
+
const basePath = AGENT_PATHS[agent] ?? `.${agent}`;
|
|
1965
|
+
for (const [relPath, content] of fileMap) {
|
|
1966
|
+
const destPath = join10(projectDir, basePath, relPath);
|
|
1967
|
+
if (existsSync7(destPath)) {
|
|
1968
|
+
result.skipped++;
|
|
1969
|
+
continue;
|
|
1970
|
+
}
|
|
1971
|
+
ensureDir2(dirname4(destPath));
|
|
1972
|
+
writeFileSync2(destPath, content);
|
|
1973
|
+
result.written++;
|
|
1974
|
+
result.checksums[relPath] = checksum(content);
|
|
1975
|
+
}
|
|
1976
|
+
continue;
|
|
1977
|
+
}
|
|
1978
|
+
for (const skillName of manifest.files.skills ?? []) {
|
|
1979
|
+
const skillFile = join10(addonSourceDir, "skills", skillName, "SKILL.md");
|
|
1980
|
+
if (!existsSync7(skillFile)) continue;
|
|
1981
|
+
const rawContent = readFileSync4(skillFile, "utf-8");
|
|
1982
|
+
const { relPath, content } = spec.skillAdapter(skillName, rawContent);
|
|
1983
|
+
const destPath = join10(projectDir, spec.baseDir, relPath);
|
|
1984
|
+
if (existsSync7(destPath)) {
|
|
1985
|
+
const existing = readFileSync4(destPath, "utf-8");
|
|
1986
|
+
if (existing === content) {
|
|
1987
|
+
result.skipped++;
|
|
1988
|
+
continue;
|
|
1989
|
+
}
|
|
1990
|
+
result.skipped++;
|
|
1991
|
+
continue;
|
|
1992
|
+
}
|
|
1993
|
+
ensureDir2(dirname4(destPath));
|
|
1994
|
+
writeFileSync2(destPath, content);
|
|
1995
|
+
result.written++;
|
|
1996
|
+
result.checksums[relPath] = checksum(content);
|
|
1997
|
+
}
|
|
1998
|
+
for (const agentName of manifest.files.agents ?? []) {
|
|
1999
|
+
const agentFile = join10(addonSourceDir, "agents", `${agentName}.md`);
|
|
2000
|
+
if (!existsSync7(agentFile)) continue;
|
|
2001
|
+
const content = readFileSync4(agentFile, "utf-8");
|
|
2002
|
+
const destPath = join10(projectDir, spec.baseDir, "agents", `${agentName}.md`);
|
|
2003
|
+
if (!existsSync7(destPath)) {
|
|
2004
|
+
ensureDir2(dirname4(destPath));
|
|
2005
|
+
writeFileSync2(destPath, content);
|
|
2006
|
+
result.written++;
|
|
2007
|
+
result.checksums[`agents/${agentName}.md`] = checksum(content);
|
|
2008
|
+
} else {
|
|
2009
|
+
result.skipped++;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
if (spec.rulesDir) {
|
|
2013
|
+
for (const rule of manifest.files.rules ?? []) {
|
|
2014
|
+
const ruleSrcPath = join10(addonSourceDir, "rules", rule);
|
|
2015
|
+
if (!existsSync7(ruleSrcPath)) continue;
|
|
2016
|
+
const content = readFileSync4(ruleSrcPath, "utf-8");
|
|
2017
|
+
const destPath = join10(projectDir, spec.baseDir, spec.rulesDir, rule);
|
|
2018
|
+
if (!existsSync7(destPath)) {
|
|
2019
|
+
ensureDir2(dirname4(destPath));
|
|
2020
|
+
writeFileSync2(destPath, content);
|
|
2021
|
+
result.written++;
|
|
2022
|
+
result.checksums[`rules/${rule}`] = checksum(content);
|
|
2023
|
+
} else {
|
|
2024
|
+
result.skipped++;
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
for (const ref of manifest.files.reference ?? []) {
|
|
2029
|
+
const refSrcPath = join10(addonSourceDir, "reference", ref);
|
|
2030
|
+
if (!existsSync7(refSrcPath)) continue;
|
|
2031
|
+
const content = readFileSync4(refSrcPath, "utf-8");
|
|
2032
|
+
const relPath = `skills/design-harden/reference/${ref}`;
|
|
2033
|
+
const destPath = join10(projectDir, spec.baseDir, relPath);
|
|
2034
|
+
if (!existsSync7(destPath)) {
|
|
2035
|
+
ensureDir2(dirname4(destPath));
|
|
2036
|
+
writeFileSync2(destPath, content);
|
|
2037
|
+
result.written++;
|
|
2038
|
+
result.checksums[relPath] = checksum(content);
|
|
2039
|
+
} else {
|
|
2040
|
+
result.skipped++;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
if (manifest.attribution) {
|
|
2045
|
+
const noticePath = join10(projectDir, "NOTICE.md");
|
|
2046
|
+
const noticeContent = [
|
|
2047
|
+
"# NOTICE",
|
|
2048
|
+
"",
|
|
2049
|
+
"This project includes materials from third-party sources.",
|
|
2050
|
+
"",
|
|
2051
|
+
`## ${manifest.name}`,
|
|
2052
|
+
"",
|
|
2053
|
+
manifest.attribution,
|
|
2054
|
+
"",
|
|
2055
|
+
"Original source: Anthropic frontend-design skill",
|
|
2056
|
+
"License: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)",
|
|
2057
|
+
""
|
|
2058
|
+
].join("\n");
|
|
2059
|
+
writeFileSync2(noticePath, noticeContent);
|
|
2060
|
+
}
|
|
2061
|
+
return result;
|
|
2062
|
+
}
|
|
2063
|
+
function removeAddonFiles(projectDir, addonName, agents, addonSourceDir) {
|
|
2064
|
+
const sourceDir = addonSourceDir ?? getAddonSourceDir(addonName);
|
|
2065
|
+
const manifest = readManifest2(sourceDir);
|
|
2066
|
+
const knownSkills = manifest.files.skills ?? [];
|
|
2067
|
+
const knownAgents = manifest.files.agents ?? [];
|
|
2068
|
+
for (const agent of agents) {
|
|
2069
|
+
const spec = RUNTIME_SPECS[agent];
|
|
2070
|
+
if (!spec) {
|
|
2071
|
+
const basePath = AGENT_PATHS[agent] ?? `.${agent}`;
|
|
2072
|
+
for (const skill of knownSkills) {
|
|
2073
|
+
const skillDir = join10(projectDir, basePath, "skills", skill);
|
|
2074
|
+
if (existsSync7(skillDir)) rmSync(skillDir, { recursive: true, force: true });
|
|
2075
|
+
}
|
|
2076
|
+
for (const agentName of knownAgents) {
|
|
2077
|
+
const agentPath = join10(projectDir, basePath, "agents", `${agentName}.md`);
|
|
2078
|
+
if (existsSync7(agentPath)) unlinkSync(agentPath);
|
|
2079
|
+
}
|
|
2080
|
+
continue;
|
|
2081
|
+
}
|
|
2082
|
+
for (const skillName of knownSkills) {
|
|
2083
|
+
const { relPath } = spec.skillAdapter(skillName, "");
|
|
2084
|
+
const destPath = join10(projectDir, spec.baseDir, relPath);
|
|
2085
|
+
if (existsSync7(destPath)) unlinkSync(destPath);
|
|
2086
|
+
const parentDir = dirname4(destPath);
|
|
2087
|
+
if (existsSync7(parentDir)) {
|
|
2088
|
+
try {
|
|
2089
|
+
const entries = readdirSync(parentDir);
|
|
2090
|
+
if (entries.length === 0) rmdirSync(parentDir);
|
|
2091
|
+
} catch {
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
for (const agentName of knownAgents) {
|
|
2096
|
+
const agentPath = join10(projectDir, spec.baseDir, "agents", `${agentName}.md`);
|
|
2097
|
+
if (existsSync7(agentPath)) unlinkSync(agentPath);
|
|
2098
|
+
}
|
|
2099
|
+
if (spec.rulesDir) {
|
|
2100
|
+
for (const rule of manifest.files.rules ?? []) {
|
|
2101
|
+
const rulePath = join10(projectDir, spec.baseDir, spec.rulesDir, rule);
|
|
2102
|
+
if (existsSync7(rulePath)) unlinkSync(rulePath);
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
for (const ref of manifest.files.reference ?? []) {
|
|
2106
|
+
const refPath = join10(projectDir, spec.baseDir, "skills", "design-harden", "reference", ref);
|
|
2107
|
+
if (existsSync7(refPath)) unlinkSync(refPath);
|
|
2108
|
+
}
|
|
2109
|
+
const refDir = join10(projectDir, spec.baseDir, "skills", "design-harden", "reference");
|
|
2110
|
+
if (existsSync7(refDir)) {
|
|
2111
|
+
try {
|
|
2112
|
+
const entries = readdirSync(refDir);
|
|
2113
|
+
if (entries.length === 0) rmdirSync(refDir);
|
|
2114
|
+
} catch {
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
const noticePath = join10(projectDir, "NOTICE.md");
|
|
2119
|
+
if (existsSync7(noticePath)) unlinkSync(noticePath);
|
|
2120
|
+
}
|
|
2121
|
+
function syncAddonFiles(projectDir, addonSourceDir, agents) {
|
|
2122
|
+
const fileMap = buildFileMap(addonSourceDir);
|
|
2123
|
+
const manifest = readManifest2(addonSourceDir);
|
|
2124
|
+
const addonName = manifest.name;
|
|
2125
|
+
const result = { written: 0, skipped: 0, conflicts: [], updated: 0 };
|
|
2126
|
+
let installedChecksums = {};
|
|
2127
|
+
try {
|
|
2128
|
+
const config = readAddonConfig(projectDir);
|
|
2129
|
+
const installed = config.installed?.[addonName];
|
|
2130
|
+
if (installed?.checksums) {
|
|
2131
|
+
installedChecksums = installed.checksums;
|
|
2132
|
+
}
|
|
2133
|
+
} catch {
|
|
2134
|
+
}
|
|
2135
|
+
for (const agent of agents) {
|
|
2136
|
+
const spec = RUNTIME_SPECS[agent];
|
|
2137
|
+
if (!spec) {
|
|
2138
|
+
const basePath = AGENT_PATHS[agent] ?? `.${agent}`;
|
|
2139
|
+
for (const [relPath, newContent] of fileMap) {
|
|
2140
|
+
const destPath = join10(projectDir, basePath, relPath);
|
|
2141
|
+
if (!existsSync7(destPath)) {
|
|
2142
|
+
ensureDir2(dirname4(destPath));
|
|
2143
|
+
writeFileSync2(destPath, newContent);
|
|
2144
|
+
result.written++;
|
|
2145
|
+
continue;
|
|
2146
|
+
}
|
|
2147
|
+
const existing = readFileSync4(destPath, "utf-8");
|
|
2148
|
+
const existingChecksum = checksum(existing);
|
|
2149
|
+
const originalChecksum = installedChecksums[relPath];
|
|
2150
|
+
if (existing === newContent) {
|
|
2151
|
+
result.skipped++;
|
|
2152
|
+
continue;
|
|
2153
|
+
}
|
|
2154
|
+
if (originalChecksum && existingChecksum !== originalChecksum) {
|
|
2155
|
+
result.conflicts.push(relPath);
|
|
2156
|
+
continue;
|
|
2157
|
+
}
|
|
2158
|
+
writeFileSync2(destPath, newContent);
|
|
2159
|
+
result.updated = (result.updated ?? 0) + 1;
|
|
2160
|
+
}
|
|
2161
|
+
continue;
|
|
2162
|
+
}
|
|
2163
|
+
for (const skillName of manifest.files.skills ?? []) {
|
|
2164
|
+
const skillFile = join10(addonSourceDir, "skills", skillName, "SKILL.md");
|
|
2165
|
+
if (!existsSync7(skillFile)) continue;
|
|
2166
|
+
const rawContent = readFileSync4(skillFile, "utf-8");
|
|
2167
|
+
const { relPath, content: newContent } = spec.skillAdapter(skillName, rawContent);
|
|
2168
|
+
const destPath = join10(projectDir, spec.baseDir, relPath);
|
|
2169
|
+
if (!existsSync7(destPath)) {
|
|
2170
|
+
ensureDir2(dirname4(destPath));
|
|
2171
|
+
writeFileSync2(destPath, newContent);
|
|
2172
|
+
result.written++;
|
|
2173
|
+
continue;
|
|
2174
|
+
}
|
|
2175
|
+
const existing = readFileSync4(destPath, "utf-8");
|
|
2176
|
+
const existingChecksum = checksum(existing);
|
|
2177
|
+
const originalChecksum = installedChecksums[relPath];
|
|
2178
|
+
if (existing === newContent) {
|
|
2179
|
+
result.skipped++;
|
|
2180
|
+
continue;
|
|
2181
|
+
}
|
|
2182
|
+
if (originalChecksum && existingChecksum !== originalChecksum) {
|
|
2183
|
+
result.conflicts.push(relPath);
|
|
2184
|
+
continue;
|
|
2185
|
+
}
|
|
2186
|
+
writeFileSync2(destPath, newContent);
|
|
2187
|
+
result.updated = (result.updated ?? 0) + 1;
|
|
2188
|
+
}
|
|
2189
|
+
for (const agentName of manifest.files.agents ?? []) {
|
|
2190
|
+
const agentFile = join10(addonSourceDir, "agents", `${agentName}.md`);
|
|
2191
|
+
if (!existsSync7(agentFile)) continue;
|
|
2192
|
+
const newContent = readFileSync4(agentFile, "utf-8");
|
|
2193
|
+
const destPath = join10(projectDir, spec.baseDir, "agents", `${agentName}.md`);
|
|
2194
|
+
const relPath = `agents/${agentName}.md`;
|
|
2195
|
+
if (!existsSync7(destPath)) {
|
|
2196
|
+
ensureDir2(dirname4(destPath));
|
|
2197
|
+
writeFileSync2(destPath, newContent);
|
|
2198
|
+
result.written++;
|
|
2199
|
+
continue;
|
|
2200
|
+
}
|
|
2201
|
+
const existing = readFileSync4(destPath, "utf-8");
|
|
2202
|
+
const existingChecksum = checksum(existing);
|
|
2203
|
+
const originalChecksum = installedChecksums[relPath];
|
|
2204
|
+
if (existing === newContent) {
|
|
2205
|
+
result.skipped++;
|
|
2206
|
+
continue;
|
|
2207
|
+
}
|
|
2208
|
+
if (originalChecksum && existingChecksum !== originalChecksum) {
|
|
2209
|
+
result.conflicts.push(relPath);
|
|
2210
|
+
continue;
|
|
2211
|
+
}
|
|
2212
|
+
writeFileSync2(destPath, newContent);
|
|
2213
|
+
result.updated = (result.updated ?? 0) + 1;
|
|
2214
|
+
}
|
|
2215
|
+
if (spec.rulesDir) {
|
|
2216
|
+
for (const rule of manifest.files.rules ?? []) {
|
|
2217
|
+
const ruleSrcPath = join10(addonSourceDir, "rules", rule);
|
|
2218
|
+
if (!existsSync7(ruleSrcPath)) continue;
|
|
2219
|
+
const newContent = readFileSync4(ruleSrcPath, "utf-8");
|
|
2220
|
+
const destPath = join10(projectDir, spec.baseDir, spec.rulesDir, rule);
|
|
2221
|
+
const relPath = `rules/${rule}`;
|
|
2222
|
+
if (!existsSync7(destPath)) {
|
|
2223
|
+
ensureDir2(dirname4(destPath));
|
|
2224
|
+
writeFileSync2(destPath, newContent);
|
|
2225
|
+
result.written++;
|
|
2226
|
+
continue;
|
|
2227
|
+
}
|
|
2228
|
+
const existing = readFileSync4(destPath, "utf-8");
|
|
2229
|
+
const existingChecksum = checksum(existing);
|
|
2230
|
+
const originalChecksum = installedChecksums[relPath];
|
|
2231
|
+
if (existing === newContent) {
|
|
2232
|
+
result.skipped++;
|
|
2233
|
+
continue;
|
|
2234
|
+
}
|
|
2235
|
+
if (originalChecksum && existingChecksum !== originalChecksum) {
|
|
2236
|
+
result.conflicts.push(relPath);
|
|
2237
|
+
continue;
|
|
2238
|
+
}
|
|
2239
|
+
writeFileSync2(destPath, newContent);
|
|
2240
|
+
result.updated = (result.updated ?? 0) + 1;
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
for (const ref of manifest.files.reference ?? []) {
|
|
2244
|
+
const refSrcPath = join10(addonSourceDir, "reference", ref);
|
|
2245
|
+
if (!existsSync7(refSrcPath)) continue;
|
|
2246
|
+
const newContent = readFileSync4(refSrcPath, "utf-8");
|
|
2247
|
+
const relPath = `skills/design-harden/reference/${ref}`;
|
|
2248
|
+
const destPath = join10(projectDir, spec.baseDir, relPath);
|
|
2249
|
+
if (!existsSync7(destPath)) {
|
|
2250
|
+
ensureDir2(dirname4(destPath));
|
|
2251
|
+
writeFileSync2(destPath, newContent);
|
|
2252
|
+
result.written++;
|
|
2253
|
+
continue;
|
|
2254
|
+
}
|
|
2255
|
+
const existing = readFileSync4(destPath, "utf-8");
|
|
2256
|
+
const existingChecksum = checksum(existing);
|
|
2257
|
+
const originalChecksum = installedChecksums[relPath];
|
|
2258
|
+
if (existing === newContent) {
|
|
2259
|
+
result.skipped++;
|
|
2260
|
+
continue;
|
|
2261
|
+
}
|
|
2262
|
+
if (originalChecksum && existingChecksum !== originalChecksum) {
|
|
2263
|
+
result.conflicts.push(relPath);
|
|
2264
|
+
continue;
|
|
2265
|
+
}
|
|
2266
|
+
writeFileSync2(destPath, newContent);
|
|
2267
|
+
result.updated = (result.updated ?? 0) + 1;
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
return result;
|
|
2271
|
+
}
|
|
2272
|
+
function detectModifiedAddonFiles(projectDir, addonName) {
|
|
2273
|
+
let installedChecksums = {};
|
|
2274
|
+
try {
|
|
2275
|
+
const config = readAddonConfig(projectDir);
|
|
2276
|
+
const installed = config.installed?.[addonName];
|
|
2277
|
+
if (!installed?.checksums) return [];
|
|
2278
|
+
installedChecksums = installed.checksums;
|
|
2279
|
+
} catch {
|
|
2280
|
+
return [];
|
|
2281
|
+
}
|
|
2282
|
+
const modified = [];
|
|
2283
|
+
for (const agentDir of Object.values(AGENT_PATHS)) {
|
|
2284
|
+
for (const [relPath, originalHash] of Object.entries(installedChecksums)) {
|
|
2285
|
+
const absPath = join10(projectDir, agentDir, relPath);
|
|
2286
|
+
if (!existsSync7(absPath)) continue;
|
|
2287
|
+
const current = checksum(readFileSync4(absPath, "utf-8"));
|
|
2288
|
+
if (current !== originalHash) {
|
|
2289
|
+
modified.push(relPath);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
return [...new Set(modified)];
|
|
2294
|
+
}
|
|
2295
|
+
|
|
1738
2296
|
// src/commands/update.ts
|
|
1739
2297
|
async function updateCommand(options) {
|
|
1740
2298
|
if (!options.check && !options.dryRun) {
|
|
@@ -1800,7 +2358,7 @@ async function updateCommand(options) {
|
|
|
1800
2358
|
const modifiedFiles = [];
|
|
1801
2359
|
const outdatedFiles = [];
|
|
1802
2360
|
for (const [relativePath, fileInfo] of Object.entries(manifest.files)) {
|
|
1803
|
-
const filePath =
|
|
2361
|
+
const filePath = join11(targetDir, relativePath);
|
|
1804
2362
|
if (!fileExists(filePath)) {
|
|
1805
2363
|
continue;
|
|
1806
2364
|
}
|
|
@@ -1812,11 +2370,11 @@ async function updateCommand(options) {
|
|
|
1812
2370
|
}
|
|
1813
2371
|
const newFiles = [];
|
|
1814
2372
|
for (const ide of manifest.selectedIDEs) {
|
|
1815
|
-
const templateDir =
|
|
1816
|
-
if (!
|
|
2373
|
+
const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
|
|
2374
|
+
if (!existsSync8(templateDir)) continue;
|
|
1817
2375
|
const files = getAllFilesRecursive(templateDir);
|
|
1818
2376
|
for (const file of files) {
|
|
1819
|
-
const templatePath =
|
|
2377
|
+
const templatePath = join11(templateDir, file);
|
|
1820
2378
|
const templateContent = readFile(templatePath);
|
|
1821
2379
|
const templateChecksum = calculateChecksum(templateContent);
|
|
1822
2380
|
const fileInfo = manifest.files[file];
|
|
@@ -1832,14 +2390,14 @@ async function updateCommand(options) {
|
|
|
1832
2390
|
if (fileInfo.ignored) continue;
|
|
1833
2391
|
let foundInAnyTemplate = false;
|
|
1834
2392
|
for (const ide of manifest.selectedIDEs) {
|
|
1835
|
-
const templateDir =
|
|
1836
|
-
if (
|
|
2393
|
+
const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
|
|
2394
|
+
if (existsSync8(join11(templateDir, relativePath))) {
|
|
1837
2395
|
foundInAnyTemplate = true;
|
|
1838
2396
|
break;
|
|
1839
2397
|
}
|
|
1840
2398
|
}
|
|
1841
2399
|
if (!foundInAnyTemplate) {
|
|
1842
|
-
const localPath =
|
|
2400
|
+
const localPath = join11(targetDir, relativePath);
|
|
1843
2401
|
if (fileExists(localPath)) {
|
|
1844
2402
|
removedFromTemplate.push({
|
|
1845
2403
|
path: relativePath,
|
|
@@ -1967,8 +2525,8 @@ async function updateCommand(options) {
|
|
|
1967
2525
|
implantedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
1968
2526
|
};
|
|
1969
2527
|
for (const ide of manifest.selectedIDEs) {
|
|
1970
|
-
const templateDir =
|
|
1971
|
-
if (!
|
|
2528
|
+
const templateDir = join11(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
|
|
2529
|
+
if (!existsSync8(templateDir)) continue;
|
|
1972
2530
|
const isPluginMode = ide === "claude-code" && manifest.installMode === "plugin";
|
|
1973
2531
|
const files = getAllFilesRecursive(templateDir);
|
|
1974
2532
|
for (const file of files) {
|
|
@@ -1978,10 +2536,10 @@ async function updateCommand(options) {
|
|
|
1978
2536
|
if (isPluginMode && (file.startsWith(".claude/skills/") || file.startsWith(".claude/agents/"))) {
|
|
1979
2537
|
continue;
|
|
1980
2538
|
}
|
|
1981
|
-
const templatePath =
|
|
1982
|
-
const destPath =
|
|
2539
|
+
const templatePath = join11(templateDir, file);
|
|
2540
|
+
const destPath = join11(targetDir, file);
|
|
1983
2541
|
const templateContent = readFile(templatePath);
|
|
1984
|
-
ensureDir(
|
|
2542
|
+
ensureDir(dirname5(destPath));
|
|
1985
2543
|
writeFile(destPath, templateContent);
|
|
1986
2544
|
updatedManifest.files[file] = createManifestEntry(templateContent);
|
|
1987
2545
|
}
|
|
@@ -1989,7 +2547,7 @@ async function updateCommand(options) {
|
|
|
1989
2547
|
if (manifest.installMode === "plugin" && manifest.pluginPath) {
|
|
1990
2548
|
const userModifiedPluginFiles = /* @__PURE__ */ new Map();
|
|
1991
2549
|
for (const [relPath, fileInfo] of Object.entries(updatedManifest.files)) {
|
|
1992
|
-
const filePath =
|
|
2550
|
+
const filePath = join11(targetDir, relPath);
|
|
1993
2551
|
if (!fileExists(filePath)) continue;
|
|
1994
2552
|
const diskContent = readFile(filePath);
|
|
1995
2553
|
const diskChecksum = calculateChecksum(diskContent);
|
|
@@ -2006,13 +2564,13 @@ async function updateCommand(options) {
|
|
|
2006
2564
|
analysis.packageManager
|
|
2007
2565
|
);
|
|
2008
2566
|
for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
|
|
2009
|
-
const scriptPath =
|
|
2010
|
-
if (
|
|
2567
|
+
const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
|
|
2568
|
+
if (existsSync8(scriptPath)) {
|
|
2011
2569
|
chmodSync2(scriptPath, 493);
|
|
2012
2570
|
}
|
|
2013
2571
|
}
|
|
2014
2572
|
for (const [relPath, content] of userModifiedPluginFiles) {
|
|
2015
|
-
writeFile(
|
|
2573
|
+
writeFile(join11(targetDir, relPath), content);
|
|
2016
2574
|
}
|
|
2017
2575
|
for (const [relPath, newEntry] of Object.entries(pluginResult.files)) {
|
|
2018
2576
|
if (userModifiedPluginFiles.has(relPath)) {
|
|
@@ -2023,9 +2581,9 @@ async function updateCommand(options) {
|
|
|
2023
2581
|
updatedManifest.pluginPath = pluginResult.pluginPath;
|
|
2024
2582
|
}
|
|
2025
2583
|
for (const filePath of filesToDelete) {
|
|
2026
|
-
const localPath =
|
|
2584
|
+
const localPath = join11(targetDir, filePath);
|
|
2027
2585
|
if (fileExists(localPath)) {
|
|
2028
|
-
|
|
2586
|
+
unlinkSync2(localPath);
|
|
2029
2587
|
}
|
|
2030
2588
|
delete updatedManifest.files[filePath];
|
|
2031
2589
|
}
|
|
@@ -2034,12 +2592,12 @@ async function updateCommand(options) {
|
|
|
2034
2592
|
updatedManifest.files[filePath].ignored = true;
|
|
2035
2593
|
}
|
|
2036
2594
|
}
|
|
2037
|
-
const claudeMdPath =
|
|
2595
|
+
const claudeMdPath = join11(targetDir, "CLAUDE.md");
|
|
2038
2596
|
if (fileExists(claudeMdPath)) {
|
|
2039
2597
|
const stat = lstatSync(claudeMdPath);
|
|
2040
2598
|
if (stat.isSymbolicLink()) {
|
|
2041
2599
|
const content = readFile(claudeMdPath);
|
|
2042
|
-
|
|
2600
|
+
unlinkSync2(claudeMdPath);
|
|
2043
2601
|
writeFile(claudeMdPath, content);
|
|
2044
2602
|
updatedManifest.files["CLAUDE.md"] = createManifestEntry(content);
|
|
2045
2603
|
p4.log.info("Migrated CLAUDE.md from symlink to independent file.");
|
|
@@ -2047,6 +2605,28 @@ async function updateCommand(options) {
|
|
|
2047
2605
|
}
|
|
2048
2606
|
writeManifest(targetDir, updatedManifest);
|
|
2049
2607
|
spinner8.stop("Updates applied");
|
|
2608
|
+
const orphaned = detectOrphanedAddonFiles(targetDir);
|
|
2609
|
+
if (orphaned.length > 0) {
|
|
2610
|
+
const migSpinner = p4.spinner();
|
|
2611
|
+
migSpinner.start("Migrating detected addon files to config...");
|
|
2612
|
+
for (const addonName of orphaned) {
|
|
2613
|
+
registerAddonInConfig(targetDir, addonName);
|
|
2614
|
+
}
|
|
2615
|
+
migSpinner.stop(`${symbols.pass} Migrated: ${orphaned.join(", ")}`);
|
|
2616
|
+
}
|
|
2617
|
+
const addonConfig = readAddonConfig(targetDir);
|
|
2618
|
+
const enabledAddons = Object.keys(addonConfig.installed);
|
|
2619
|
+
if (enabledAddons.length > 0 && !options.check) {
|
|
2620
|
+
const syncSpinner = p4.spinner();
|
|
2621
|
+
syncSpinner.start("Updating enabled addon files...");
|
|
2622
|
+
let totalUpdated = 0;
|
|
2623
|
+
for (const name of enabledAddons) {
|
|
2624
|
+
const addonSourceDir = getAddonSourceDir(name);
|
|
2625
|
+
const result = syncAddonFiles(targetDir, addonSourceDir, addonConfig.agents);
|
|
2626
|
+
totalUpdated += (result.updated ?? 0) + result.written;
|
|
2627
|
+
}
|
|
2628
|
+
syncSpinner.stop(`${symbols.pass} Addon files updated (${totalUpdated} files)`);
|
|
2629
|
+
}
|
|
2050
2630
|
p4.outro(chalk5.green(`Updated to version ${currentVersion}`));
|
|
2051
2631
|
}
|
|
2052
2632
|
function detectStackChanges(savedConfig, analysis) {
|
|
@@ -2153,7 +2733,7 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2153
2733
|
const spinner8 = p4.spinner();
|
|
2154
2734
|
spinner8.start("Regenerating with new stack...");
|
|
2155
2735
|
const regeneratedFiles = [];
|
|
2156
|
-
const agentsMdPath =
|
|
2736
|
+
const agentsMdPath = join11(targetDir, "AGENTS.md");
|
|
2157
2737
|
if (fileExists(agentsMdPath)) {
|
|
2158
2738
|
const agentsMdContent = generateAgentsMdFromConfig(
|
|
2159
2739
|
newConfig,
|
|
@@ -2164,12 +2744,12 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2164
2744
|
regeneratedFiles.push("AGENTS.md");
|
|
2165
2745
|
manifest.files["AGENTS.md"] = createManifestEntry(agentsMdContent);
|
|
2166
2746
|
}
|
|
2167
|
-
const claudeMdPath =
|
|
2747
|
+
const claudeMdPath = join11(targetDir, "CLAUDE.md");
|
|
2168
2748
|
if (fileExists(claudeMdPath)) {
|
|
2169
2749
|
const stat = lstatSync(claudeMdPath);
|
|
2170
2750
|
if (stat.isSymbolicLink()) {
|
|
2171
2751
|
const content = readFile(claudeMdPath);
|
|
2172
|
-
|
|
2752
|
+
unlinkSync2(claudeMdPath);
|
|
2173
2753
|
writeFile(claudeMdPath, content);
|
|
2174
2754
|
manifest.files["CLAUDE.md"] = createManifestEntry(content);
|
|
2175
2755
|
p4.log.info("Migrated CLAUDE.md from symlink to independent file.");
|
|
@@ -2181,8 +2761,8 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2181
2761
|
const ruleContent = getRuleContentForIDE(ide, generatedRules);
|
|
2182
2762
|
const rulePath = DYNAMIC_RULE_FILES[ide]?.[0];
|
|
2183
2763
|
if (ruleContent && rulePath) {
|
|
2184
|
-
const destPath =
|
|
2185
|
-
ensureDir(
|
|
2764
|
+
const destPath = join11(targetDir, rulePath);
|
|
2765
|
+
ensureDir(dirname5(destPath));
|
|
2186
2766
|
writeFile(destPath, ruleContent);
|
|
2187
2767
|
regeneratedFiles.push(rulePath);
|
|
2188
2768
|
manifest.files[rulePath] = createManifestEntry(ruleContent);
|
|
@@ -2192,7 +2772,7 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2192
2772
|
if (manifest.installMode === "plugin" && manifest.pluginPath) {
|
|
2193
2773
|
const userModifiedPluginFiles = /* @__PURE__ */ new Map();
|
|
2194
2774
|
for (const [relPath, fileInfo] of Object.entries(manifest.files)) {
|
|
2195
|
-
const filePath =
|
|
2775
|
+
const filePath = join11(targetDir, relPath);
|
|
2196
2776
|
if (!fileExists(filePath)) continue;
|
|
2197
2777
|
const diskContent = readFile(filePath);
|
|
2198
2778
|
const diskChecksum = calculateChecksum(diskContent);
|
|
@@ -2208,13 +2788,13 @@ async function regenerateWithNewStack(targetDir, manifest, analysis, dryRun) {
|
|
|
2208
2788
|
analysis.packageManager
|
|
2209
2789
|
);
|
|
2210
2790
|
for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
|
|
2211
|
-
const scriptPath =
|
|
2212
|
-
if (
|
|
2791
|
+
const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
|
|
2792
|
+
if (existsSync8(scriptPath)) {
|
|
2213
2793
|
chmodSync2(scriptPath, 493);
|
|
2214
2794
|
}
|
|
2215
2795
|
}
|
|
2216
2796
|
for (const [relPath, content] of userModifiedPluginFiles) {
|
|
2217
|
-
writeFile(
|
|
2797
|
+
writeFile(join11(targetDir, relPath), content);
|
|
2218
2798
|
}
|
|
2219
2799
|
for (const [relPath, newEntry] of Object.entries(pluginResult.files)) {
|
|
2220
2800
|
if (userModifiedPluginFiles.has(relPath)) {
|
|
@@ -2257,8 +2837,8 @@ async function migrateToPlugin(targetDir, manifest, analysis, dryRun) {
|
|
|
2257
2837
|
analysis.packageManager
|
|
2258
2838
|
);
|
|
2259
2839
|
for (const script of ["checkpoint.sh", "stop-guard.sh"]) {
|
|
2260
|
-
const scriptPath =
|
|
2261
|
-
if (
|
|
2840
|
+
const scriptPath = join11(targetDir, pluginResult.pluginPath, "scripts", script);
|
|
2841
|
+
if (existsSync8(scriptPath)) {
|
|
2262
2842
|
chmodSync2(scriptPath, 493);
|
|
2263
2843
|
}
|
|
2264
2844
|
}
|
|
@@ -2267,19 +2847,19 @@ async function migrateToPlugin(targetDir, manifest, analysis, dryRun) {
|
|
|
2267
2847
|
const preserved = [];
|
|
2268
2848
|
for (const [path, fileInfo] of Object.entries(manifest.files)) {
|
|
2269
2849
|
if (!path.startsWith(".claude/skills/") && !path.startsWith(".claude/agents/")) continue;
|
|
2270
|
-
const filePath =
|
|
2850
|
+
const filePath = join11(targetDir, path);
|
|
2271
2851
|
if (!fileExists(filePath)) continue;
|
|
2272
2852
|
const current = calculateChecksum(readFile(filePath));
|
|
2273
2853
|
if (current === fileInfo.originalChecksum) {
|
|
2274
|
-
|
|
2854
|
+
unlinkSync2(filePath);
|
|
2275
2855
|
delete manifest.files[path];
|
|
2276
2856
|
removed.push(path);
|
|
2277
2857
|
} else {
|
|
2278
2858
|
preserved.push(path);
|
|
2279
2859
|
}
|
|
2280
2860
|
}
|
|
2281
|
-
cleanEmptyDirs(
|
|
2282
|
-
cleanEmptyDirs(
|
|
2861
|
+
cleanEmptyDirs(join11(targetDir, ".claude", "skills"));
|
|
2862
|
+
cleanEmptyDirs(join11(targetDir, ".claude", "agents"));
|
|
2283
2863
|
Object.assign(manifest.files, pluginResult.files);
|
|
2284
2864
|
manifest.installMode = "plugin";
|
|
2285
2865
|
manifest.pluginPath = pluginResult.pluginPath;
|
|
@@ -2328,22 +2908,22 @@ function buildDefaultConfig(analysis) {
|
|
|
2328
2908
|
};
|
|
2329
2909
|
}
|
|
2330
2910
|
function cleanEmptyDirs(dirPath) {
|
|
2331
|
-
if (!
|
|
2332
|
-
const entries =
|
|
2911
|
+
if (!existsSync8(dirPath)) return;
|
|
2912
|
+
const entries = readdirSync2(dirPath);
|
|
2333
2913
|
for (const entry of entries) {
|
|
2334
|
-
const fullPath =
|
|
2335
|
-
if (
|
|
2914
|
+
const fullPath = join11(dirPath, entry);
|
|
2915
|
+
if (existsSync8(fullPath) && lstatSync(fullPath).isDirectory()) {
|
|
2336
2916
|
cleanEmptyDirs(fullPath);
|
|
2337
2917
|
}
|
|
2338
2918
|
}
|
|
2339
|
-
const remaining =
|
|
2919
|
+
const remaining = readdirSync2(dirPath);
|
|
2340
2920
|
if (remaining.length === 0) {
|
|
2341
|
-
|
|
2921
|
+
rmdirSync2(dirPath);
|
|
2342
2922
|
}
|
|
2343
2923
|
}
|
|
2344
2924
|
|
|
2345
2925
|
// src/commands/status.ts
|
|
2346
|
-
import { resolve as resolve4, join as
|
|
2926
|
+
import { resolve as resolve4, join as join12 } from "path";
|
|
2347
2927
|
import * as p5 from "@clack/prompts";
|
|
2348
2928
|
import chalk6 from "chalk";
|
|
2349
2929
|
async function statusCommand(options = {}) {
|
|
@@ -2357,6 +2937,22 @@ async function statusCommand(options = {}) {
|
|
|
2357
2937
|
p5.outro("");
|
|
2358
2938
|
return;
|
|
2359
2939
|
}
|
|
2940
|
+
const mode = readMode(targetDir);
|
|
2941
|
+
const addonConfig = readAddonConfig(targetDir);
|
|
2942
|
+
const allAddons = getAvailableAddons();
|
|
2943
|
+
p5.note(
|
|
2944
|
+
[
|
|
2945
|
+
`${formatKV("Mode:", chalk6.cyan(mode))}`,
|
|
2946
|
+
`${formatKV("Config:", chalk6.dim(".claude/devtronic.json"))}`
|
|
2947
|
+
].join("\n"),
|
|
2948
|
+
"Execution Mode"
|
|
2949
|
+
);
|
|
2950
|
+
const addonLines = allAddons.map((addon) => {
|
|
2951
|
+
const isEnabled = !!addonConfig.installed[addon.name];
|
|
2952
|
+
const statusStr = isEnabled ? chalk6.green("enabled") : chalk6.dim("disabled");
|
|
2953
|
+
return ` ${chalk6.bold((addon.name + ":").padEnd(28))} ${statusStr}`;
|
|
2954
|
+
});
|
|
2955
|
+
p5.note(addonLines.join("\n"), "Addons");
|
|
2360
2956
|
p5.note(
|
|
2361
2957
|
[
|
|
2362
2958
|
formatKV("Version:", manifest.version),
|
|
@@ -2367,7 +2963,7 @@ async function statusCommand(options = {}) {
|
|
|
2367
2963
|
);
|
|
2368
2964
|
const fileStatuses = [];
|
|
2369
2965
|
for (const [relativePath, fileInfo] of Object.entries(manifest.files)) {
|
|
2370
|
-
const filePath =
|
|
2966
|
+
const filePath = join12(targetDir, relativePath);
|
|
2371
2967
|
if (!fileExists(filePath)) {
|
|
2372
2968
|
fileStatuses.push({ path: relativePath, status: "missing" });
|
|
2373
2969
|
continue;
|
|
@@ -2413,8 +3009,8 @@ async function statusCommand(options = {}) {
|
|
|
2413
3009
|
}
|
|
2414
3010
|
|
|
2415
3011
|
// src/commands/diff.ts
|
|
2416
|
-
import { resolve as resolve5, join as
|
|
2417
|
-
import { existsSync as
|
|
3012
|
+
import { resolve as resolve5, join as join13 } from "path";
|
|
3013
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2418
3014
|
import * as p6 from "@clack/prompts";
|
|
2419
3015
|
import chalk7 from "chalk";
|
|
2420
3016
|
async function diffCommand(options = {}) {
|
|
@@ -2429,12 +3025,12 @@ async function diffCommand(options = {}) {
|
|
|
2429
3025
|
}
|
|
2430
3026
|
const diffs = [];
|
|
2431
3027
|
for (const ide of manifest.selectedIDEs) {
|
|
2432
|
-
const templateDir =
|
|
2433
|
-
if (!
|
|
3028
|
+
const templateDir = join13(TEMPLATES_DIR, IDE_TEMPLATE_MAP[ide]);
|
|
3029
|
+
if (!existsSync9(templateDir)) continue;
|
|
2434
3030
|
const templateFiles = getAllFilesRecursive(templateDir);
|
|
2435
3031
|
for (const file of templateFiles) {
|
|
2436
|
-
const templatePath =
|
|
2437
|
-
const localPath =
|
|
3032
|
+
const templatePath = join13(templateDir, file);
|
|
3033
|
+
const localPath = join13(targetDir, file);
|
|
2438
3034
|
const templateContent = readFile(templatePath);
|
|
2439
3035
|
const templateChecksum = calculateChecksum(templateContent);
|
|
2440
3036
|
const manifestEntry = manifest.files[file];
|
|
@@ -2487,8 +3083,8 @@ async function diffCommand(options = {}) {
|
|
|
2487
3083
|
}
|
|
2488
3084
|
|
|
2489
3085
|
// src/commands/add.ts
|
|
2490
|
-
import { existsSync as
|
|
2491
|
-
import { resolve as resolve6, join as
|
|
3086
|
+
import { existsSync as existsSync10 } from "fs";
|
|
3087
|
+
import { resolve as resolve6, join as join14, dirname as dirname6 } from "path";
|
|
2492
3088
|
import * as p7 from "@clack/prompts";
|
|
2493
3089
|
import chalk8 from "chalk";
|
|
2494
3090
|
var ALL_IDES = [
|
|
@@ -2582,8 +3178,8 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2582
3178
|
const generatedFiles = [];
|
|
2583
3179
|
const generatedRules = generateArchitectureRules(manifest.projectConfig);
|
|
2584
3180
|
const templateName = IDE_TEMPLATE_MAP[selectedIDE];
|
|
2585
|
-
const templateDir =
|
|
2586
|
-
if (!
|
|
3181
|
+
const templateDir = join14(TEMPLATES_DIR, templateName);
|
|
3182
|
+
if (!existsSync10(templateDir)) {
|
|
2587
3183
|
spinner8.stop("Error");
|
|
2588
3184
|
p7.cancel(`Template not found: ${templateName}`);
|
|
2589
3185
|
process.exit(1);
|
|
@@ -2594,8 +3190,8 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2594
3190
|
if (dynamicFiles.includes(file)) {
|
|
2595
3191
|
continue;
|
|
2596
3192
|
}
|
|
2597
|
-
const sourcePath =
|
|
2598
|
-
const destPath =
|
|
3193
|
+
const sourcePath = join14(templateDir, file);
|
|
3194
|
+
const destPath = join14(targetDir, file);
|
|
2599
3195
|
const sourceContent = readFile(sourcePath);
|
|
2600
3196
|
if (fileExists(destPath)) {
|
|
2601
3197
|
if (conflictResolution === "keep") {
|
|
@@ -2612,7 +3208,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2612
3208
|
continue;
|
|
2613
3209
|
}
|
|
2614
3210
|
}
|
|
2615
|
-
ensureDir(
|
|
3211
|
+
ensureDir(dirname6(destPath));
|
|
2616
3212
|
writeFile(destPath, sourceContent);
|
|
2617
3213
|
appliedFiles.push(file);
|
|
2618
3214
|
manifest.files[file] = createManifestEntry(sourceContent);
|
|
@@ -2621,7 +3217,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2621
3217
|
if (ruleContent) {
|
|
2622
3218
|
const rulePath = dynamicFiles[0];
|
|
2623
3219
|
if (rulePath) {
|
|
2624
|
-
const destPath =
|
|
3220
|
+
const destPath = join14(targetDir, rulePath);
|
|
2625
3221
|
if (fileExists(destPath) && conflictResolution === "keep") {
|
|
2626
3222
|
skippedFiles.push(rulePath);
|
|
2627
3223
|
} else if (fileExists(destPath) && conflictResolution === "merge") {
|
|
@@ -2632,7 +3228,7 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2632
3228
|
mergedFiles.push(rulePath);
|
|
2633
3229
|
manifest.files[rulePath] = createManifestEntry(mergedContent);
|
|
2634
3230
|
} else {
|
|
2635
|
-
ensureDir(
|
|
3231
|
+
ensureDir(dirname6(destPath));
|
|
2636
3232
|
writeFile(destPath, ruleContent);
|
|
2637
3233
|
generatedFiles.push(`${rulePath} (personalized)`);
|
|
2638
3234
|
manifest.files[rulePath] = createManifestEntry(ruleContent);
|
|
@@ -2673,14 +3269,14 @@ Valid options: ${ALL_IDES.map((i) => i.value).join(", ")}`
|
|
|
2673
3269
|
}
|
|
2674
3270
|
|
|
2675
3271
|
// src/commands/regenerate.ts
|
|
2676
|
-
import { existsSync as
|
|
2677
|
-
import { resolve as resolve7, join as
|
|
3272
|
+
import { existsSync as existsSync11 } from "fs";
|
|
3273
|
+
import { resolve as resolve7, join as join15, dirname as dirname7 } from "path";
|
|
2678
3274
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2679
3275
|
import * as p8 from "@clack/prompts";
|
|
2680
3276
|
import chalk9 from "chalk";
|
|
2681
3277
|
var __filename = fileURLToPath3(import.meta.url);
|
|
2682
|
-
var __regen_dirname =
|
|
2683
|
-
var TEMPLATES_DIR2 =
|
|
3278
|
+
var __regen_dirname = dirname7(__filename);
|
|
3279
|
+
var TEMPLATES_DIR2 = existsSync11(resolve7(__regen_dirname, "../templates")) ? resolve7(__regen_dirname, "../templates") : resolve7(__regen_dirname, "../../templates");
|
|
2684
3280
|
async function regenerateCommand(target, options) {
|
|
2685
3281
|
ensureInteractive("regenerate");
|
|
2686
3282
|
const targetDir = resolve7(options.path || ".");
|
|
@@ -2757,7 +3353,7 @@ Valid options:
|
|
|
2757
3353
|
return;
|
|
2758
3354
|
}
|
|
2759
3355
|
if (regenerateClaudeMd) {
|
|
2760
|
-
const claudeMdPath =
|
|
3356
|
+
const claudeMdPath = join15(targetDir, "CLAUDE.md");
|
|
2761
3357
|
if (fileExists(claudeMdPath)) {
|
|
2762
3358
|
p8.log.warn(
|
|
2763
3359
|
"Regenerating CLAUDE.md will overwrite any self-improvements (Gotchas section)."
|
|
@@ -2794,7 +3390,7 @@ Valid options:
|
|
|
2794
3390
|
spinner8.start("Regenerating files...");
|
|
2795
3391
|
const regeneratedFiles = [];
|
|
2796
3392
|
if (regenerateClaudeMd) {
|
|
2797
|
-
const claudeMdPath =
|
|
3393
|
+
const claudeMdPath = join15(targetDir, "CLAUDE.md");
|
|
2798
3394
|
const claudeMdContent = generateClaudeMd(
|
|
2799
3395
|
fullProjectConfig,
|
|
2800
3396
|
analysis.scripts,
|
|
@@ -2805,7 +3401,7 @@ Valid options:
|
|
|
2805
3401
|
manifest.files["CLAUDE.md"] = createManifestEntry(claudeMdContent);
|
|
2806
3402
|
}
|
|
2807
3403
|
if (regenerateAgentsMd) {
|
|
2808
|
-
const agentsMdPath =
|
|
3404
|
+
const agentsMdPath = join15(targetDir, "AGENTS.md");
|
|
2809
3405
|
const agentsMdContent = generateAgentsMdFromConfig(
|
|
2810
3406
|
fullProjectConfig,
|
|
2811
3407
|
analysis.scripts,
|
|
@@ -2822,8 +3418,8 @@ Valid options:
|
|
|
2822
3418
|
const ruleContent = getRuleContentForIDE(ide, generatedRules);
|
|
2823
3419
|
const rulePath = DYNAMIC_RULE_FILES[ide]?.[0];
|
|
2824
3420
|
if (ruleContent && rulePath) {
|
|
2825
|
-
const destPath =
|
|
2826
|
-
ensureDir(
|
|
3421
|
+
const destPath = join15(targetDir, rulePath);
|
|
3422
|
+
ensureDir(dirname7(destPath));
|
|
2827
3423
|
writeFile(destPath, ruleContent);
|
|
2828
3424
|
regeneratedFiles.push(rulePath);
|
|
2829
3425
|
manifest.files[rulePath] = createManifestEntry(ruleContent);
|
|
@@ -2837,7 +3433,7 @@ Valid options:
|
|
|
2837
3433
|
if (!manifest.selectedIDEs.includes("claude-code") || manifest.installMode !== "plugin") {
|
|
2838
3434
|
p8.log.warn("Plugin regeneration only applies to Claude Code in plugin mode. Skipping.");
|
|
2839
3435
|
} else {
|
|
2840
|
-
const { generatePlugin: generatePlugin2 } = await import("./plugin-
|
|
3436
|
+
const { generatePlugin: generatePlugin2 } = await import("./plugin-SGSFVXPA.js");
|
|
2841
3437
|
const pluginResult = generatePlugin2(
|
|
2842
3438
|
targetDir,
|
|
2843
3439
|
TEMPLATES_DIR2,
|
|
@@ -2872,8 +3468,8 @@ Valid options:
|
|
|
2872
3468
|
}
|
|
2873
3469
|
|
|
2874
3470
|
// src/commands/info.ts
|
|
2875
|
-
import { resolve as resolve8, join as
|
|
2876
|
-
import { existsSync as
|
|
3471
|
+
import { resolve as resolve8, join as join16 } from "path";
|
|
3472
|
+
import { existsSync as existsSync12, readdirSync as readdirSync3 } from "fs";
|
|
2877
3473
|
import * as p9 from "@clack/prompts";
|
|
2878
3474
|
import chalk10 from "chalk";
|
|
2879
3475
|
async function infoCommand() {
|
|
@@ -2886,33 +3482,33 @@ async function infoCommand() {
|
|
|
2886
3482
|
let skillCount = 0;
|
|
2887
3483
|
let agentCount = 0;
|
|
2888
3484
|
if (manifest) {
|
|
2889
|
-
const pluginDir = manifest.pluginPath ?
|
|
2890
|
-
if (pluginDir &&
|
|
2891
|
-
const skillsDir =
|
|
2892
|
-
const agentsDir =
|
|
2893
|
-
if (
|
|
2894
|
-
skillCount =
|
|
3485
|
+
const pluginDir = manifest.pluginPath ? join16(targetDir, manifest.pluginPath) : null;
|
|
3486
|
+
if (pluginDir && existsSync12(pluginDir)) {
|
|
3487
|
+
const skillsDir = join16(pluginDir, "skills");
|
|
3488
|
+
const agentsDir = join16(pluginDir, "agents");
|
|
3489
|
+
if (existsSync12(skillsDir)) {
|
|
3490
|
+
skillCount = readdirSync3(skillsDir, { withFileTypes: true }).filter(
|
|
2895
3491
|
(e) => e.isDirectory() || e.isFile() && e.name.endsWith(".md")
|
|
2896
3492
|
).length;
|
|
2897
3493
|
}
|
|
2898
|
-
if (
|
|
2899
|
-
agentCount =
|
|
3494
|
+
if (existsSync12(agentsDir)) {
|
|
3495
|
+
agentCount = readdirSync3(agentsDir, { withFileTypes: true }).filter(
|
|
2900
3496
|
(e) => e.isFile() && e.name.endsWith(".md")
|
|
2901
3497
|
).length;
|
|
2902
3498
|
}
|
|
2903
3499
|
}
|
|
2904
3500
|
if (skillCount === 0) {
|
|
2905
|
-
const claudeSkills =
|
|
2906
|
-
if (
|
|
2907
|
-
skillCount =
|
|
3501
|
+
const claudeSkills = join16(targetDir, ".claude", "skills");
|
|
3502
|
+
if (existsSync12(claudeSkills)) {
|
|
3503
|
+
skillCount = readdirSync3(claudeSkills, { withFileTypes: true }).filter(
|
|
2908
3504
|
(e) => e.isDirectory() || e.isFile() && e.name.endsWith(".md")
|
|
2909
3505
|
).length;
|
|
2910
3506
|
}
|
|
2911
3507
|
}
|
|
2912
3508
|
if (agentCount === 0) {
|
|
2913
|
-
const claudeAgents =
|
|
2914
|
-
if (
|
|
2915
|
-
agentCount =
|
|
3509
|
+
const claudeAgents = join16(targetDir, ".claude", "agents");
|
|
3510
|
+
if (existsSync12(claudeAgents)) {
|
|
3511
|
+
agentCount = readdirSync3(claudeAgents, { withFileTypes: true }).filter(
|
|
2916
3512
|
(e) => e.isFile() && e.name.endsWith(".md")
|
|
2917
3513
|
).length;
|
|
2918
3514
|
}
|
|
@@ -2955,8 +3551,8 @@ async function infoCommand() {
|
|
|
2955
3551
|
}
|
|
2956
3552
|
|
|
2957
3553
|
// src/commands/list.ts
|
|
2958
|
-
import { resolve as resolve9, join as
|
|
2959
|
-
import { existsSync as
|
|
3554
|
+
import { resolve as resolve9, join as join17 } from "path";
|
|
3555
|
+
import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync5 } from "fs";
|
|
2960
3556
|
import * as p10 from "@clack/prompts";
|
|
2961
3557
|
import chalk11 from "chalk";
|
|
2962
3558
|
async function listCommand(filter, options) {
|
|
@@ -2973,30 +3569,30 @@ Valid options: skills, agents`);
|
|
|
2973
3569
|
}
|
|
2974
3570
|
const skills = [];
|
|
2975
3571
|
const agents = [];
|
|
2976
|
-
const pluginDir = manifest?.pluginPath ?
|
|
2977
|
-
if (pluginDir &&
|
|
3572
|
+
const pluginDir = manifest?.pluginPath ? join17(targetDir, manifest.pluginPath) : null;
|
|
3573
|
+
if (pluginDir && existsSync13(pluginDir)) {
|
|
2978
3574
|
if (showSkills) {
|
|
2979
|
-
const skillsDir =
|
|
2980
|
-
if (
|
|
3575
|
+
const skillsDir = join17(pluginDir, "skills");
|
|
3576
|
+
if (existsSync13(skillsDir)) {
|
|
2981
3577
|
skills.push(...discoverSkills(skillsDir));
|
|
2982
3578
|
}
|
|
2983
3579
|
}
|
|
2984
3580
|
if (showAgents) {
|
|
2985
|
-
const agentsDir =
|
|
2986
|
-
if (
|
|
3581
|
+
const agentsDir = join17(pluginDir, "agents");
|
|
3582
|
+
if (existsSync13(agentsDir)) {
|
|
2987
3583
|
agents.push(...discoverAgents(agentsDir));
|
|
2988
3584
|
}
|
|
2989
3585
|
}
|
|
2990
3586
|
}
|
|
2991
3587
|
if (showSkills && skills.length === 0) {
|
|
2992
|
-
const claudeSkills =
|
|
2993
|
-
if (
|
|
3588
|
+
const claudeSkills = join17(targetDir, ".claude", "skills");
|
|
3589
|
+
if (existsSync13(claudeSkills)) {
|
|
2994
3590
|
skills.push(...discoverSkills(claudeSkills));
|
|
2995
3591
|
}
|
|
2996
3592
|
}
|
|
2997
3593
|
if (showAgents && agents.length === 0) {
|
|
2998
|
-
const claudeAgents =
|
|
2999
|
-
if (
|
|
3594
|
+
const claudeAgents = join17(targetDir, ".claude", "agents");
|
|
3595
|
+
if (existsSync13(claudeAgents)) {
|
|
3000
3596
|
agents.push(...discoverAgents(claudeAgents));
|
|
3001
3597
|
}
|
|
3002
3598
|
}
|
|
@@ -3020,15 +3616,15 @@ Valid options: skills, agents`);
|
|
|
3020
3616
|
}
|
|
3021
3617
|
function discoverSkills(skillsDir) {
|
|
3022
3618
|
const items = [];
|
|
3023
|
-
const entries =
|
|
3619
|
+
const entries = readdirSync4(skillsDir, { withFileTypes: true });
|
|
3024
3620
|
for (const entry of entries) {
|
|
3025
3621
|
if (entry.isDirectory()) {
|
|
3026
|
-
const skillMd =
|
|
3027
|
-
const description =
|
|
3622
|
+
const skillMd = join17(skillsDir, entry.name, "SKILL.md");
|
|
3623
|
+
const description = existsSync13(skillMd) ? extractDescription(skillMd) : "";
|
|
3028
3624
|
items.push({ name: entry.name, description });
|
|
3029
3625
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
3030
3626
|
const name = entry.name.replace(/\.md$/, "");
|
|
3031
|
-
const description = extractDescription(
|
|
3627
|
+
const description = extractDescription(join17(skillsDir, entry.name));
|
|
3032
3628
|
items.push({ name, description });
|
|
3033
3629
|
}
|
|
3034
3630
|
}
|
|
@@ -3036,11 +3632,11 @@ function discoverSkills(skillsDir) {
|
|
|
3036
3632
|
}
|
|
3037
3633
|
function discoverAgents(agentsDir) {
|
|
3038
3634
|
const items = [];
|
|
3039
|
-
const entries =
|
|
3635
|
+
const entries = readdirSync4(agentsDir, { withFileTypes: true });
|
|
3040
3636
|
for (const entry of entries) {
|
|
3041
3637
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
3042
3638
|
const name = entry.name.replace(/\.md$/, "");
|
|
3043
|
-
const description = extractDescription(
|
|
3639
|
+
const description = extractDescription(join17(agentsDir, entry.name));
|
|
3044
3640
|
items.push({ name, description });
|
|
3045
3641
|
}
|
|
3046
3642
|
}
|
|
@@ -3048,7 +3644,7 @@ function discoverAgents(agentsDir) {
|
|
|
3048
3644
|
}
|
|
3049
3645
|
function extractDescription(filePath) {
|
|
3050
3646
|
try {
|
|
3051
|
-
const content =
|
|
3647
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
3052
3648
|
const lines = content.split("\n");
|
|
3053
3649
|
let pastHeading = false;
|
|
3054
3650
|
for (const line of lines) {
|
|
@@ -3227,8 +3823,8 @@ async function configResetCommand(options) {
|
|
|
3227
3823
|
}
|
|
3228
3824
|
|
|
3229
3825
|
// src/commands/doctor.ts
|
|
3230
|
-
import { resolve as resolve11, join as
|
|
3231
|
-
import { existsSync as
|
|
3826
|
+
import { resolve as resolve11, join as join18 } from "path";
|
|
3827
|
+
import { existsSync as existsSync14, readdirSync as readdirSync5, statSync, chmodSync as chmodSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
3232
3828
|
import { execSync } from "child_process";
|
|
3233
3829
|
import * as p12 from "@clack/prompts";
|
|
3234
3830
|
import chalk13 from "chalk";
|
|
@@ -3329,7 +3925,7 @@ function checkManifestFiles(targetDir, manifest) {
|
|
|
3329
3925
|
let existCount = 0;
|
|
3330
3926
|
const missing = [];
|
|
3331
3927
|
for (const relativePath of Object.keys(manifest.files)) {
|
|
3332
|
-
if (fileExists(
|
|
3928
|
+
if (fileExists(join18(targetDir, relativePath))) {
|
|
3333
3929
|
existCount++;
|
|
3334
3930
|
} else {
|
|
3335
3931
|
missing.push(relativePath);
|
|
@@ -3349,15 +3945,15 @@ function checkManifestFiles(targetDir, manifest) {
|
|
|
3349
3945
|
};
|
|
3350
3946
|
}
|
|
3351
3947
|
function checkScriptPermissions(targetDir, manifest) {
|
|
3352
|
-
const pluginDir = manifest.pluginPath ?
|
|
3948
|
+
const pluginDir = manifest.pluginPath ? join18(targetDir, manifest.pluginPath) : null;
|
|
3353
3949
|
const shFiles = [];
|
|
3354
|
-
if (pluginDir &&
|
|
3355
|
-
const scriptsDir =
|
|
3356
|
-
if (
|
|
3357
|
-
const entries =
|
|
3950
|
+
if (pluginDir && existsSync14(pluginDir)) {
|
|
3951
|
+
const scriptsDir = join18(pluginDir, "scripts");
|
|
3952
|
+
if (existsSync14(scriptsDir)) {
|
|
3953
|
+
const entries = readdirSync5(scriptsDir);
|
|
3358
3954
|
for (const entry of entries) {
|
|
3359
3955
|
if (entry.endsWith(".sh")) {
|
|
3360
|
-
shFiles.push(
|
|
3956
|
+
shFiles.push(join18(scriptsDir, entry));
|
|
3361
3957
|
}
|
|
3362
3958
|
}
|
|
3363
3959
|
}
|
|
@@ -3420,28 +4016,28 @@ function checkPluginRegistered(targetDir) {
|
|
|
3420
4016
|
};
|
|
3421
4017
|
}
|
|
3422
4018
|
function checkHookScripts(targetDir, manifest) {
|
|
3423
|
-
const pluginDir = manifest.pluginPath ?
|
|
3424
|
-
if (!pluginDir || !
|
|
4019
|
+
const pluginDir = manifest.pluginPath ? join18(targetDir, manifest.pluginPath) : null;
|
|
4020
|
+
if (!pluginDir || !existsSync14(pluginDir)) {
|
|
3425
4021
|
return {
|
|
3426
4022
|
name: "hooks",
|
|
3427
4023
|
status: "pass",
|
|
3428
4024
|
message: "No hooks directory to check"
|
|
3429
4025
|
};
|
|
3430
4026
|
}
|
|
3431
|
-
const hooksDir =
|
|
3432
|
-
if (!
|
|
4027
|
+
const hooksDir = join18(pluginDir, "hooks");
|
|
4028
|
+
if (!existsSync14(hooksDir)) {
|
|
3433
4029
|
return {
|
|
3434
4030
|
name: "hooks",
|
|
3435
4031
|
status: "pass",
|
|
3436
4032
|
message: "No hooks directory found"
|
|
3437
4033
|
};
|
|
3438
4034
|
}
|
|
3439
|
-
const hookFiles =
|
|
4035
|
+
const hookFiles = readdirSync5(hooksDir).filter((f) => f.endsWith(".json"));
|
|
3440
4036
|
let total = 0;
|
|
3441
4037
|
let valid = 0;
|
|
3442
4038
|
for (const hookFile of hookFiles) {
|
|
3443
4039
|
try {
|
|
3444
|
-
const hookContent = JSON.parse(readFile(
|
|
4040
|
+
const hookContent = JSON.parse(readFile(join18(hooksDir, hookFile)));
|
|
3445
4041
|
const hooks = Array.isArray(hookContent) ? hookContent : [hookContent];
|
|
3446
4042
|
for (const hook of hooks) {
|
|
3447
4043
|
if (hook.command) {
|
|
@@ -3455,8 +4051,8 @@ function checkHookScripts(targetDir, manifest) {
|
|
|
3455
4051
|
/\$\{[A-Z_]+\}\//g,
|
|
3456
4052
|
""
|
|
3457
4053
|
);
|
|
3458
|
-
const scriptPath =
|
|
3459
|
-
if (
|
|
4054
|
+
const scriptPath = join18(pluginDir, resolved);
|
|
4055
|
+
if (existsSync14(scriptPath)) {
|
|
3460
4056
|
valid++;
|
|
3461
4057
|
}
|
|
3462
4058
|
} else {
|
|
@@ -3480,7 +4076,7 @@ function checkHookScripts(targetDir, manifest) {
|
|
|
3480
4076
|
};
|
|
3481
4077
|
}
|
|
3482
4078
|
function checkQualityScripts(targetDir) {
|
|
3483
|
-
const pkgPath =
|
|
4079
|
+
const pkgPath = join18(targetDir, "package.json");
|
|
3484
4080
|
if (!fileExists(pkgPath)) {
|
|
3485
4081
|
return { name: "quality", status: "warn", message: "No package.json found" };
|
|
3486
4082
|
}
|
|
@@ -3506,8 +4102,8 @@ function checkQualityScripts(targetDir) {
|
|
|
3506
4102
|
}
|
|
3507
4103
|
}
|
|
3508
4104
|
function checkThoughtsDir(targetDir) {
|
|
3509
|
-
const thoughtsDir =
|
|
3510
|
-
if (
|
|
4105
|
+
const thoughtsDir = join18(targetDir, "thoughts");
|
|
4106
|
+
if (existsSync14(thoughtsDir)) {
|
|
3511
4107
|
return {
|
|
3512
4108
|
name: "thoughts",
|
|
3513
4109
|
status: "pass",
|
|
@@ -3531,15 +4127,15 @@ function checkThoughtsDir(targetDir) {
|
|
|
3531
4127
|
"thoughts/archive/backlog"
|
|
3532
4128
|
];
|
|
3533
4129
|
for (const dir of dirs) {
|
|
3534
|
-
|
|
4130
|
+
mkdirSync3(join18(targetDir, dir), { recursive: true });
|
|
3535
4131
|
}
|
|
3536
4132
|
}
|
|
3537
4133
|
};
|
|
3538
4134
|
}
|
|
3539
4135
|
function checkEslint(targetDir) {
|
|
3540
4136
|
try {
|
|
3541
|
-
const eslintLocal =
|
|
3542
|
-
if (
|
|
4137
|
+
const eslintLocal = join18(targetDir, "node_modules", ".bin", "eslint");
|
|
4138
|
+
if (existsSync14(eslintLocal)) {
|
|
3543
4139
|
return { name: "eslint", status: "pass", message: "eslint is available" };
|
|
3544
4140
|
}
|
|
3545
4141
|
execSync("which eslint", { stdio: "pipe" });
|
|
@@ -3554,8 +4150,8 @@ function checkEslint(targetDir) {
|
|
|
3554
4150
|
}
|
|
3555
4151
|
|
|
3556
4152
|
// src/commands/uninstall.ts
|
|
3557
|
-
import { existsSync as
|
|
3558
|
-
import { resolve as resolve12, join as
|
|
4153
|
+
import { existsSync as existsSync15, rmSync as rmSync2, readdirSync as readdirSync6 } from "fs";
|
|
4154
|
+
import { resolve as resolve12, join as join19, dirname as dirname8 } from "path";
|
|
3559
4155
|
import * as p13 from "@clack/prompts";
|
|
3560
4156
|
import chalk14 from "chalk";
|
|
3561
4157
|
var DEVTRONIC_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
@@ -3573,12 +4169,12 @@ async function uninstallCommand(options) {
|
|
|
3573
4169
|
return;
|
|
3574
4170
|
}
|
|
3575
4171
|
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(
|
|
4172
|
+
const existingFiles = managedFiles.filter((f) => fileExists(join19(targetDir, f)));
|
|
4173
|
+
const missingFiles = managedFiles.filter((f) => !fileExists(join19(targetDir, f)));
|
|
4174
|
+
const hasPlugin = manifest.installMode === "plugin" && existsSync15(join19(targetDir, PLUGIN_DIR, PLUGIN_NAME));
|
|
4175
|
+
const hasThoughts = existsSync15(join19(targetDir, "thoughts"));
|
|
4176
|
+
const hasClaudeMd = fileExists(join19(targetDir, "CLAUDE.md"));
|
|
4177
|
+
const hasAgentsMd = fileExists(join19(targetDir, "AGENTS.md"));
|
|
3582
4178
|
p13.log.info(`Installation found: v${manifest.version} (${manifest.implantedAt})`);
|
|
3583
4179
|
p13.log.info(`IDEs: ${manifest.selectedIDEs.join(", ")}`);
|
|
3584
4180
|
p13.log.info(`Mode: ${manifest.installMode || "standalone"}`);
|
|
@@ -3664,18 +4260,18 @@ async function uninstallCommand(options) {
|
|
|
3664
4260
|
}
|
|
3665
4261
|
if (hasPlugin) {
|
|
3666
4262
|
try {
|
|
3667
|
-
|
|
4263
|
+
rmSync2(join19(targetDir, PLUGIN_DIR, PLUGIN_NAME), { recursive: true, force: true });
|
|
3668
4264
|
removed.push(`${PLUGIN_DIR}/${PLUGIN_NAME}/`);
|
|
3669
|
-
const marketplaceDescDir =
|
|
3670
|
-
if (
|
|
3671
|
-
|
|
4265
|
+
const marketplaceDescDir = join19(targetDir, PLUGIN_DIR, ".claude-plugin");
|
|
4266
|
+
if (existsSync15(marketplaceDescDir)) {
|
|
4267
|
+
rmSync2(marketplaceDescDir, { recursive: true, force: true });
|
|
3672
4268
|
removed.push(`${PLUGIN_DIR}/.claude-plugin/`);
|
|
3673
4269
|
}
|
|
3674
|
-
const pluginsDir =
|
|
3675
|
-
if (
|
|
4270
|
+
const pluginsDir = join19(targetDir, PLUGIN_DIR);
|
|
4271
|
+
if (existsSync15(pluginsDir)) {
|
|
3676
4272
|
const remaining = readdirSafe(pluginsDir);
|
|
3677
4273
|
if (remaining.length === 0) {
|
|
3678
|
-
|
|
4274
|
+
rmSync2(pluginsDir, { recursive: true, force: true });
|
|
3679
4275
|
removed.push(`${PLUGIN_DIR}/ (empty)`);
|
|
3680
4276
|
}
|
|
3681
4277
|
}
|
|
@@ -3688,11 +4284,11 @@ async function uninstallCommand(options) {
|
|
|
3688
4284
|
if (file.startsWith("thoughts/")) continue;
|
|
3689
4285
|
if (file.startsWith(PLUGIN_DIR + "/")) continue;
|
|
3690
4286
|
try {
|
|
3691
|
-
const filePath =
|
|
3692
|
-
if (
|
|
3693
|
-
|
|
4287
|
+
const filePath = join19(targetDir, file);
|
|
4288
|
+
if (existsSync15(filePath)) {
|
|
4289
|
+
rmSync2(filePath, { force: true });
|
|
3694
4290
|
removed.push(file);
|
|
3695
|
-
cleanEmptyParents(targetDir,
|
|
4291
|
+
cleanEmptyParents(targetDir, dirname8(file));
|
|
3696
4292
|
}
|
|
3697
4293
|
} catch (err) {
|
|
3698
4294
|
errors.push(`Failed to remove ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3701,7 +4297,7 @@ async function uninstallCommand(options) {
|
|
|
3701
4297
|
if (hasClaudeMd) {
|
|
3702
4298
|
if (removeClaudeMd) {
|
|
3703
4299
|
try {
|
|
3704
|
-
|
|
4300
|
+
rmSync2(join19(targetDir, "CLAUDE.md"), { force: true });
|
|
3705
4301
|
removed.push("CLAUDE.md");
|
|
3706
4302
|
} catch (err) {
|
|
3707
4303
|
errors.push(`Failed to remove CLAUDE.md: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3713,7 +4309,7 @@ async function uninstallCommand(options) {
|
|
|
3713
4309
|
if (hasAgentsMd) {
|
|
3714
4310
|
if (removeAgentsMd) {
|
|
3715
4311
|
try {
|
|
3716
|
-
|
|
4312
|
+
rmSync2(join19(targetDir, "AGENTS.md"), { force: true });
|
|
3717
4313
|
removed.push("AGENTS.md");
|
|
3718
4314
|
} catch (err) {
|
|
3719
4315
|
errors.push(`Failed to remove AGENTS.md: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3725,7 +4321,7 @@ async function uninstallCommand(options) {
|
|
|
3725
4321
|
if (hasThoughts) {
|
|
3726
4322
|
if (removeThoughts) {
|
|
3727
4323
|
try {
|
|
3728
|
-
|
|
4324
|
+
rmSync2(join19(targetDir, "thoughts"), { recursive: true, force: true });
|
|
3729
4325
|
removed.push("thoughts/");
|
|
3730
4326
|
} catch (err) {
|
|
3731
4327
|
errors.push(`Failed to remove thoughts/: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3735,9 +4331,9 @@ async function uninstallCommand(options) {
|
|
|
3735
4331
|
}
|
|
3736
4332
|
}
|
|
3737
4333
|
try {
|
|
3738
|
-
const manifestDir =
|
|
3739
|
-
if (
|
|
3740
|
-
|
|
4334
|
+
const manifestDir = join19(targetDir, MANIFEST_DIR);
|
|
4335
|
+
if (existsSync15(manifestDir)) {
|
|
4336
|
+
rmSync2(manifestDir, { recursive: true, force: true });
|
|
3741
4337
|
removed.push(`${MANIFEST_DIR}/`);
|
|
3742
4338
|
}
|
|
3743
4339
|
} catch (err) {
|
|
@@ -3782,33 +4378,59 @@ async function uninstallCommand(options) {
|
|
|
3782
4378
|
}
|
|
3783
4379
|
function cleanEmptyParents(targetDir, relDir) {
|
|
3784
4380
|
if (!relDir || relDir === ".") return;
|
|
3785
|
-
const absDir =
|
|
3786
|
-
if (!
|
|
4381
|
+
const absDir = join19(targetDir, relDir);
|
|
4382
|
+
if (!existsSync15(absDir)) return;
|
|
3787
4383
|
const entries = readdirSafe(absDir);
|
|
3788
4384
|
if (entries.length === 0) {
|
|
3789
4385
|
try {
|
|
3790
|
-
|
|
3791
|
-
cleanEmptyParents(targetDir,
|
|
4386
|
+
rmSync2(absDir, { recursive: true, force: true });
|
|
4387
|
+
cleanEmptyParents(targetDir, dirname8(relDir));
|
|
3792
4388
|
} catch {
|
|
3793
4389
|
}
|
|
3794
4390
|
}
|
|
3795
4391
|
}
|
|
3796
4392
|
function readdirSafe(dir) {
|
|
3797
4393
|
try {
|
|
3798
|
-
return
|
|
4394
|
+
return readdirSync6(dir);
|
|
3799
4395
|
} catch {
|
|
3800
4396
|
return [];
|
|
3801
4397
|
}
|
|
3802
4398
|
}
|
|
3803
4399
|
|
|
3804
4400
|
// src/commands/addon.ts
|
|
3805
|
-
import { resolve as resolve13, join as
|
|
3806
|
-
import { existsSync as
|
|
4401
|
+
import { resolve as resolve13, join as join20, dirname as dirname9 } from "path";
|
|
4402
|
+
import { existsSync as existsSync16, unlinkSync as unlinkSync3, rmSync as rmSync3, readFileSync as readFileSync6 } from "fs";
|
|
3807
4403
|
import * as p14 from "@clack/prompts";
|
|
3808
4404
|
import chalk15 from "chalk";
|
|
4405
|
+
function isFileBasedAddon(addonName) {
|
|
4406
|
+
return addonName !== "orchestration";
|
|
4407
|
+
}
|
|
3809
4408
|
async function addonCommand(action, addonName, options) {
|
|
3810
4409
|
const targetDir = resolve13(options.path || ".");
|
|
3811
4410
|
p14.intro(introTitle(`Addon ${action}`));
|
|
4411
|
+
const validAddons = Object.keys(ADDONS);
|
|
4412
|
+
if (!validAddons.includes(addonName)) {
|
|
4413
|
+
p14.cancel(`Unknown addon: ${addonName}
|
|
4414
|
+
|
|
4415
|
+
Valid addons: ${validAddons.join(", ")}`);
|
|
4416
|
+
process.exit(1);
|
|
4417
|
+
}
|
|
4418
|
+
const typedName = addonName;
|
|
4419
|
+
const canonicalAction = action === "enable" ? "add" : action === "disable" ? "remove" : action;
|
|
4420
|
+
if (action === "add" || action === "remove") {
|
|
4421
|
+
const canonical = action === "add" ? "enable" : "disable";
|
|
4422
|
+
p14.log.warn(
|
|
4423
|
+
`"addon ${action}" is deprecated. Use "addon ${canonical}" instead.`
|
|
4424
|
+
);
|
|
4425
|
+
}
|
|
4426
|
+
if (isFileBasedAddon(typedName)) {
|
|
4427
|
+
if (canonicalAction === "add") {
|
|
4428
|
+
await addFileBasedAddon(targetDir, typedName, options);
|
|
4429
|
+
} else {
|
|
4430
|
+
await removeFileBasedAddon(targetDir, typedName, options);
|
|
4431
|
+
}
|
|
4432
|
+
return;
|
|
4433
|
+
}
|
|
3812
4434
|
const manifest = readManifest(targetDir);
|
|
3813
4435
|
if (!manifest) {
|
|
3814
4436
|
p14.log.warn("No devtronic installation found.");
|
|
@@ -3822,16 +4444,9 @@ async function addonCommand(action, addonName, options) {
|
|
|
3822
4444
|
p14.outro("");
|
|
3823
4445
|
return;
|
|
3824
4446
|
}
|
|
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];
|
|
4447
|
+
const addon = ADDONS[typedName];
|
|
3833
4448
|
const currentAddons = manifest.projectConfig?.enabledAddons ?? [];
|
|
3834
|
-
if (
|
|
4449
|
+
if (canonicalAction === "add") {
|
|
3835
4450
|
await addAddon(targetDir, manifest, addon.name, currentAddons);
|
|
3836
4451
|
} else {
|
|
3837
4452
|
await removeAddon(targetDir, manifest, addon.name, currentAddons);
|
|
@@ -3845,7 +4460,7 @@ async function addAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
3845
4460
|
}
|
|
3846
4461
|
const addon = ADDONS[addonName];
|
|
3847
4462
|
const pluginRoot = manifest.pluginPath;
|
|
3848
|
-
const skillsSourceDir =
|
|
4463
|
+
const skillsSourceDir = join20(TEMPLATES_DIR, "claude-code", ".claude", "skills");
|
|
3849
4464
|
p14.note(
|
|
3850
4465
|
[
|
|
3851
4466
|
` ${chalk15.dim("Name:")} ${addon.label}`,
|
|
@@ -3864,18 +4479,18 @@ async function addAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
3864
4479
|
spinner8.start(`Adding ${addon.label}...`);
|
|
3865
4480
|
const addedFiles = [];
|
|
3866
4481
|
for (const skillDir of addon.skills) {
|
|
3867
|
-
const sourceDir =
|
|
3868
|
-
if (!
|
|
4482
|
+
const sourceDir = join20(skillsSourceDir, skillDir);
|
|
4483
|
+
if (!existsSync16(sourceDir)) {
|
|
3869
4484
|
spinner8.stop(`Template not found for skill: ${skillDir}`);
|
|
3870
4485
|
p14.log.warn(`Skipping ${skillDir} \u2014 template not found.`);
|
|
3871
4486
|
continue;
|
|
3872
4487
|
}
|
|
3873
4488
|
const templateFiles = getAllFilesRecursive(sourceDir);
|
|
3874
4489
|
for (const file of templateFiles) {
|
|
3875
|
-
const content = readFile(
|
|
3876
|
-
const destRelPath =
|
|
3877
|
-
const destAbsPath =
|
|
3878
|
-
ensureDir(
|
|
4490
|
+
const content = readFile(join20(sourceDir, file));
|
|
4491
|
+
const destRelPath = join20(pluginRoot, "skills", skillDir, file);
|
|
4492
|
+
const destAbsPath = join20(targetDir, destRelPath);
|
|
4493
|
+
ensureDir(dirname9(destAbsPath));
|
|
3879
4494
|
writeFile(destAbsPath, content);
|
|
3880
4495
|
manifest.files[destRelPath] = createManifestEntry(content);
|
|
3881
4496
|
addedFiles.push(destRelPath);
|
|
@@ -3920,11 +4535,11 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
3920
4535
|
}
|
|
3921
4536
|
const modifiedFiles = [];
|
|
3922
4537
|
for (const skillDir of addon.skills) {
|
|
3923
|
-
const skillRelBase =
|
|
4538
|
+
const skillRelBase = join20(pluginRoot, "skills", skillDir);
|
|
3924
4539
|
for (const [filePath, fileInfo] of Object.entries(manifest.files)) {
|
|
3925
4540
|
if (!filePath.startsWith(skillRelBase)) continue;
|
|
3926
|
-
const absPath =
|
|
3927
|
-
if (!
|
|
4541
|
+
const absPath = join20(targetDir, filePath);
|
|
4542
|
+
if (!existsSync16(absPath)) continue;
|
|
3928
4543
|
const current = calculateChecksum(readFile(absPath));
|
|
3929
4544
|
if (current !== fileInfo.originalChecksum) {
|
|
3930
4545
|
modifiedFiles.push(filePath);
|
|
@@ -3947,18 +4562,18 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
3947
4562
|
const spinner8 = p14.spinner();
|
|
3948
4563
|
spinner8.start(`Removing ${addon.label}...`);
|
|
3949
4564
|
for (const skillDir of addon.skills) {
|
|
3950
|
-
const skillRelBase =
|
|
3951
|
-
const skillAbsDir =
|
|
4565
|
+
const skillRelBase = join20(pluginRoot, "skills", skillDir);
|
|
4566
|
+
const skillAbsDir = join20(targetDir, skillRelBase);
|
|
3952
4567
|
for (const filePath of Object.keys(manifest.files)) {
|
|
3953
4568
|
if (filePath.startsWith(skillRelBase)) {
|
|
3954
|
-
const absPath =
|
|
3955
|
-
if (
|
|
4569
|
+
const absPath = join20(targetDir, filePath);
|
|
4570
|
+
if (existsSync16(absPath)) unlinkSync3(absPath);
|
|
3956
4571
|
delete manifest.files[filePath];
|
|
3957
4572
|
}
|
|
3958
4573
|
}
|
|
3959
|
-
if (
|
|
4574
|
+
if (existsSync16(skillAbsDir)) {
|
|
3960
4575
|
try {
|
|
3961
|
-
|
|
4576
|
+
rmSync3(skillAbsDir, { recursive: true });
|
|
3962
4577
|
} catch {
|
|
3963
4578
|
}
|
|
3964
4579
|
}
|
|
@@ -3975,32 +4590,198 @@ async function removeAddon(targetDir, manifest, addonName, currentAddons) {
|
|
|
3975
4590
|
);
|
|
3976
4591
|
p14.outro("Done. Restart Claude Code to apply the changes.");
|
|
3977
4592
|
}
|
|
4593
|
+
async function addFileBasedAddon(targetDir, addonName, _options) {
|
|
4594
|
+
const addon = ADDONS[addonName];
|
|
4595
|
+
const config = readAddonConfig(targetDir);
|
|
4596
|
+
if (config.installed[addonName]) {
|
|
4597
|
+
p14.log.warn(`Addon "${addonName}" is already installed.`);
|
|
4598
|
+
p14.outro("");
|
|
4599
|
+
return;
|
|
4600
|
+
}
|
|
4601
|
+
p14.note(
|
|
4602
|
+
[
|
|
4603
|
+
` ${chalk15.dim("Name:")} ${addon.label}`,
|
|
4604
|
+
` ${chalk15.dim("Description:")} ${addon.description}`,
|
|
4605
|
+
` ${chalk15.dim("Skills:")} ${addon.skills.map((s) => chalk15.cyan(`/${s}`)).join(", ")}`,
|
|
4606
|
+
addon.agents.length ? ` ${chalk15.dim("Agents:")} ${addon.agents.map((a) => chalk15.cyan(a)).join(", ")}` : ` ${chalk15.dim("Agents:")} ${chalk15.dim("\u2014")}`
|
|
4607
|
+
].join("\n"),
|
|
4608
|
+
"Adding addon"
|
|
4609
|
+
);
|
|
4610
|
+
const confirmed = await p14.confirm({ message: "Add this addon?" });
|
|
4611
|
+
if (p14.isCancel(confirmed) || !confirmed) {
|
|
4612
|
+
p14.cancel("Addon installation cancelled.");
|
|
4613
|
+
process.exit(0);
|
|
4614
|
+
}
|
|
4615
|
+
const spinner8 = p14.spinner();
|
|
4616
|
+
spinner8.start(`Adding ${addon.label}...`);
|
|
4617
|
+
const addonSourceDir = getAddonSourceDir(addonName);
|
|
4618
|
+
const result = generateAddonFiles(targetDir, addonSourceDir, config.agents);
|
|
4619
|
+
const addonManifest = JSON.parse(
|
|
4620
|
+
readFileSync6(join20(addonSourceDir, "manifest.json"), "utf-8")
|
|
4621
|
+
);
|
|
4622
|
+
const fileList = [
|
|
4623
|
+
...(addonManifest.files.skills ?? []).map((s) => `skills/${s}`),
|
|
4624
|
+
...(addonManifest.files.agents ?? []).map((a) => `agents/${a}.md`),
|
|
4625
|
+
...(addonManifest.files.rules ?? []).map((r) => `rules/${r}`)
|
|
4626
|
+
];
|
|
4627
|
+
writeAddonToConfig(targetDir, addonName, {
|
|
4628
|
+
version: addonManifest.version,
|
|
4629
|
+
files: fileList,
|
|
4630
|
+
checksums: result.checksums ?? {}
|
|
4631
|
+
});
|
|
4632
|
+
spinner8.stop(`${symbols.pass} ${addon.label} added (${result.written} files written)`);
|
|
4633
|
+
p14.note(
|
|
4634
|
+
addon.skills.map((s) => ` ${chalk15.cyan(`/${s}`)}`).join("\n"),
|
|
4635
|
+
"New skills available"
|
|
4636
|
+
);
|
|
4637
|
+
p14.outro("Done. Skills are now available in your agent directories.");
|
|
4638
|
+
}
|
|
4639
|
+
async function removeFileBasedAddon(targetDir, addonName, _options) {
|
|
4640
|
+
const addon = ADDONS[addonName];
|
|
4641
|
+
const config = readAddonConfig(targetDir);
|
|
4642
|
+
if (!config.installed[addonName]) {
|
|
4643
|
+
p14.log.warn(`Addon "${addonName}" is not currently installed.`);
|
|
4644
|
+
p14.outro("");
|
|
4645
|
+
return;
|
|
4646
|
+
}
|
|
4647
|
+
const modified = detectModifiedAddonFiles(targetDir, addonName);
|
|
4648
|
+
if (modified.length > 0) {
|
|
4649
|
+
p14.log.warn("The following files have been customized:");
|
|
4650
|
+
for (const f of modified) {
|
|
4651
|
+
p14.log.message(` ${chalk15.yellow(f)}`);
|
|
4652
|
+
}
|
|
4653
|
+
const confirm9 = await p14.confirm({
|
|
4654
|
+
message: "Remove them anyway? (customizations will be lost)"
|
|
4655
|
+
});
|
|
4656
|
+
if (p14.isCancel(confirm9) || !confirm9) {
|
|
4657
|
+
p14.cancel("Addon removal cancelled.");
|
|
4658
|
+
process.exit(0);
|
|
4659
|
+
}
|
|
4660
|
+
}
|
|
4661
|
+
const spinner8 = p14.spinner();
|
|
4662
|
+
spinner8.start(`Removing ${addon.label}...`);
|
|
4663
|
+
removeAddonFiles(targetDir, addonName, config.agents);
|
|
4664
|
+
removeAddonFromConfig(targetDir, addonName);
|
|
4665
|
+
spinner8.stop(`${symbols.pass} ${addon.label} removed`);
|
|
4666
|
+
p14.note(
|
|
4667
|
+
addon.skills.map((s) => ` ${chalk15.dim(`/${s}`)}`).join("\n"),
|
|
4668
|
+
"Skills removed"
|
|
4669
|
+
);
|
|
4670
|
+
p14.outro("Done.");
|
|
4671
|
+
}
|
|
4672
|
+
function getAddonListInfo(targetDir) {
|
|
4673
|
+
const config = readAddonConfig(targetDir);
|
|
4674
|
+
const manifest = readManifest(targetDir);
|
|
4675
|
+
const manifestAddons = manifest?.projectConfig?.enabledAddons ?? [];
|
|
4676
|
+
return getAvailableAddons().map((addon) => ({
|
|
4677
|
+
name: addon.name,
|
|
4678
|
+
label: addon.label,
|
|
4679
|
+
description: addon.description,
|
|
4680
|
+
installed: !!config.installed[addon.name] || manifestAddons.includes(addon.name),
|
|
4681
|
+
agents: config.installed[addon.name] ? config.agents : void 0
|
|
4682
|
+
}));
|
|
4683
|
+
}
|
|
4684
|
+
async function addonListCommand(options) {
|
|
4685
|
+
const targetDir = resolve13(options.path || ".");
|
|
4686
|
+
p14.intro(introTitle("Addon List"));
|
|
4687
|
+
const items = getAddonListInfo(targetDir);
|
|
4688
|
+
const lines = items.map((item) => {
|
|
4689
|
+
const status = item.installed ? chalk15.green("\u2713 installed") : chalk15.dim("available");
|
|
4690
|
+
const agents = item.agents ? chalk15.dim(` \u2192 ${item.agents.join(", ")}`) : "";
|
|
4691
|
+
return ` ${chalk15.bold(item.name)} ${status}${agents}
|
|
4692
|
+
${chalk15.dim(item.description)}`;
|
|
4693
|
+
});
|
|
4694
|
+
p14.note(lines.join("\n\n"), "Addons");
|
|
4695
|
+
p14.outro(`Use ${chalk15.cyan("devtronic addon add <name>")} to install.`);
|
|
4696
|
+
}
|
|
4697
|
+
async function addonSyncCommand(options) {
|
|
4698
|
+
const targetDir = resolve13(options.path || ".");
|
|
4699
|
+
p14.intro(introTitle("Addon Sync"));
|
|
4700
|
+
const config = readAddonConfig(targetDir);
|
|
4701
|
+
const installedNames = Object.keys(config.installed);
|
|
4702
|
+
if (installedNames.length === 0) {
|
|
4703
|
+
p14.log.info("No addons installed. Nothing to sync.");
|
|
4704
|
+
p14.outro("");
|
|
4705
|
+
return;
|
|
4706
|
+
}
|
|
4707
|
+
const spinner8 = p14.spinner();
|
|
4708
|
+
spinner8.start("Syncing addon files...");
|
|
4709
|
+
let totalWritten = 0;
|
|
4710
|
+
let totalConflicts = [];
|
|
4711
|
+
for (const name of installedNames) {
|
|
4712
|
+
const addonSourceDir = getAddonSourceDir(name);
|
|
4713
|
+
const result = syncAddonFiles(targetDir, addonSourceDir, config.agents);
|
|
4714
|
+
totalWritten += result.written + (result.updated ?? 0);
|
|
4715
|
+
totalConflicts = totalConflicts.concat(result.conflicts);
|
|
4716
|
+
}
|
|
4717
|
+
spinner8.stop(`${symbols.pass} Sync complete (${totalWritten} files updated)`);
|
|
4718
|
+
if (totalConflicts.length > 0) {
|
|
4719
|
+
p14.log.warn("Customized files were preserved:");
|
|
4720
|
+
for (const f of totalConflicts) {
|
|
4721
|
+
p14.log.message(` ${chalk15.yellow(f)}`);
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
p14.outro("Done.");
|
|
4725
|
+
}
|
|
3978
4726
|
function updateDescriptors(targetDir, manifest, pluginRoot, addonSkillCount) {
|
|
3979
4727
|
const cliVersion2 = getCliVersion();
|
|
3980
4728
|
const pluginJsonContent = generatePluginJson(cliVersion2, addonSkillCount);
|
|
3981
|
-
const pluginJsonRelPath =
|
|
3982
|
-
writeFile(
|
|
4729
|
+
const pluginJsonRelPath = join20(pluginRoot, ".claude-plugin", "plugin.json");
|
|
4730
|
+
writeFile(join20(targetDir, pluginJsonRelPath), pluginJsonContent);
|
|
3983
4731
|
manifest.files[pluginJsonRelPath] = createManifestEntry(pluginJsonContent);
|
|
3984
4732
|
const marketplaceContent = generateMarketplaceJson(addonSkillCount);
|
|
3985
|
-
const marketplaceRelPath =
|
|
3986
|
-
writeFile(
|
|
4733
|
+
const marketplaceRelPath = join20(PLUGIN_DIR, ".claude-plugin", "marketplace.json");
|
|
4734
|
+
writeFile(join20(targetDir, marketplaceRelPath), marketplaceContent);
|
|
3987
4735
|
manifest.files[marketplaceRelPath] = createManifestEntry(marketplaceContent);
|
|
3988
4736
|
}
|
|
3989
4737
|
|
|
4738
|
+
// src/commands/mode.ts
|
|
4739
|
+
import { resolve as resolve14, join as join21 } from "path";
|
|
4740
|
+
import { existsSync as existsSync17 } from "fs";
|
|
4741
|
+
import * as p15 from "@clack/prompts";
|
|
4742
|
+
import chalk16 from "chalk";
|
|
4743
|
+
async function modeCommand(action, options) {
|
|
4744
|
+
const targetDir = resolve14(options.path || ".");
|
|
4745
|
+
p15.intro(introTitle("Mode"));
|
|
4746
|
+
if (action === "show") {
|
|
4747
|
+
const configPath = join21(targetDir, ".claude", "devtronic.json");
|
|
4748
|
+
const hasConfig = existsSync17(configPath);
|
|
4749
|
+
const config = readAddonConfig(targetDir);
|
|
4750
|
+
const currentMode = config.mode;
|
|
4751
|
+
const isDefault = !hasConfig || currentMode === void 0;
|
|
4752
|
+
const displayMode = currentMode ?? "hitl";
|
|
4753
|
+
p15.log.info(
|
|
4754
|
+
`Mode: ${chalk16.cyan(displayMode)}${isDefault ? chalk16.dim(" (default)") : ""}`
|
|
4755
|
+
);
|
|
4756
|
+
p15.log.info(`Config: ${chalk16.dim(".claude/devtronic.json")}`);
|
|
4757
|
+
p15.outro("");
|
|
4758
|
+
return;
|
|
4759
|
+
}
|
|
4760
|
+
const newMode = action;
|
|
4761
|
+
writeMode(targetDir, newMode);
|
|
4762
|
+
const description = newMode === "afk" ? "Fully autonomous \u2014 no human gates" : "Human-in-the-loop \u2014 pauses for approval at key stages";
|
|
4763
|
+
p15.log.success(`Mode set to ${chalk16.cyan(newMode)}`);
|
|
4764
|
+
p15.log.info(chalk16.dim(description));
|
|
4765
|
+
p15.outro(
|
|
4766
|
+
`Change back anytime with ${chalk16.cyan(`npx devtronic mode ${newMode === "afk" ? "hitl" : "afk"}`)}`
|
|
4767
|
+
);
|
|
4768
|
+
}
|
|
4769
|
+
|
|
3990
4770
|
// src/index.ts
|
|
3991
4771
|
var cliVersion = getCliVersion();
|
|
3992
4772
|
var program = new Command();
|
|
3993
4773
|
program.name("devtronic").description("AI-assisted development toolkit").version(cliVersion).action(() => {
|
|
3994
4774
|
showLogo();
|
|
3995
|
-
console.log(
|
|
4775
|
+
console.log(chalk17.dim(` Agentic development toolkit v${cliVersion}`));
|
|
3996
4776
|
console.log();
|
|
3997
|
-
console.log(` ${
|
|
3998
|
-
console.log(` ${
|
|
3999
|
-
console.log(` ${
|
|
4000
|
-
console.log(` ${
|
|
4777
|
+
console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic init")} ${chalk17.dim("[path]")} ${chalk17.dim("Initialize in a project")}`);
|
|
4778
|
+
console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic info")} ${chalk17.dim("Version & config summary")}`);
|
|
4779
|
+
console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic doctor")} ${chalk17.dim("Health diagnostics")}`);
|
|
4780
|
+
console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic status")} ${chalk17.dim("File status overview")}`);
|
|
4781
|
+
console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic mode")} ${chalk17.dim("<afk|hitl|show>")} ${chalk17.dim("Get or set execution mode")}`);
|
|
4001
4782
|
console.log();
|
|
4002
|
-
console.log(` ${
|
|
4003
|
-
console.log(` ${
|
|
4783
|
+
console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic help")} ${chalk17.dim("Show all commands")}`);
|
|
4784
|
+
console.log(` ${chalk17.dim("$")} ${chalk17.white("devtronic help --all")} ${chalk17.dim("Full reference with all options")}`);
|
|
4004
4785
|
console.log();
|
|
4005
4786
|
});
|
|
4006
4787
|
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 +4840,20 @@ program.command("doctor").description("Run health checks on your devtronic insta
|
|
|
4059
4840
|
program.command("uninstall").description("Remove devtronic from your project").option("--path <path>", "Target directory (default: current directory)").action(async (options) => {
|
|
4060
4841
|
await uninstallCommand({ path: options.path });
|
|
4061
4842
|
});
|
|
4843
|
+
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) => {
|
|
4844
|
+
if (!["afk", "hitl", "show"].includes(mode)) {
|
|
4845
|
+
console.error(`Invalid mode: "${mode}". Valid values: afk, hitl, show`);
|
|
4846
|
+
process.exit(1);
|
|
4847
|
+
}
|
|
4848
|
+
await modeCommand(mode, { path: options.path });
|
|
4849
|
+
});
|
|
4062
4850
|
program.command("help").description("Show help (use --all for full reference)").option("-a, --all", "Show all commands with their options").action((options) => {
|
|
4063
4851
|
if (!options.all) {
|
|
4064
4852
|
program.outputHelp();
|
|
4065
4853
|
return;
|
|
4066
4854
|
}
|
|
4067
4855
|
showLogo();
|
|
4068
|
-
console.log(
|
|
4856
|
+
console.log(chalk17.dim(` Agentic development toolkit v${cliVersion}
|
|
4069
4857
|
`));
|
|
4070
4858
|
const sections = [
|
|
4071
4859
|
{
|
|
@@ -4089,7 +4877,7 @@ program.command("help").description("Show help (use --all for full reference)").
|
|
|
4089
4877
|
{
|
|
4090
4878
|
title: "Addons",
|
|
4091
4879
|
usage: "addon add <name>",
|
|
4092
|
-
desc: "Add an optional skill pack (e.g., orchestration)",
|
|
4880
|
+
desc: "Add an optional skill pack (e.g., orchestration, design-best-practices)",
|
|
4093
4881
|
opts: ["--path <path> Target directory"]
|
|
4094
4882
|
},
|
|
4095
4883
|
{
|
|
@@ -4098,6 +4886,30 @@ program.command("help").description("Show help (use --all for full reference)").
|
|
|
4098
4886
|
desc: "Remove an addon skill pack",
|
|
4099
4887
|
opts: ["--path <path> Target directory"]
|
|
4100
4888
|
},
|
|
4889
|
+
{
|
|
4890
|
+
title: "",
|
|
4891
|
+
usage: "addon list",
|
|
4892
|
+
desc: "List available and installed addons",
|
|
4893
|
+
opts: ["--path <path> Target directory"]
|
|
4894
|
+
},
|
|
4895
|
+
{
|
|
4896
|
+
title: "",
|
|
4897
|
+
usage: "addon sync",
|
|
4898
|
+
desc: "Regenerate addon files for current agent configuration",
|
|
4899
|
+
opts: ["--path <path> Target directory"]
|
|
4900
|
+
},
|
|
4901
|
+
{
|
|
4902
|
+
title: "",
|
|
4903
|
+
usage: "addon enable <name>",
|
|
4904
|
+
desc: "Enable an addon (copies files to .claude/)",
|
|
4905
|
+
opts: ["--path <path> Target directory"]
|
|
4906
|
+
},
|
|
4907
|
+
{
|
|
4908
|
+
title: "",
|
|
4909
|
+
usage: "addon disable <name>",
|
|
4910
|
+
desc: "Disable an addon (removes files from .claude/)",
|
|
4911
|
+
opts: ["--path <path> Target directory"]
|
|
4912
|
+
},
|
|
4101
4913
|
{
|
|
4102
4914
|
title: "Maintenance",
|
|
4103
4915
|
usage: "update",
|
|
@@ -4151,6 +4963,12 @@ program.command("help").description("Show help (use --all for full reference)").
|
|
|
4151
4963
|
usage: "presets",
|
|
4152
4964
|
desc: "List available configuration presets"
|
|
4153
4965
|
},
|
|
4966
|
+
{
|
|
4967
|
+
title: "Mode",
|
|
4968
|
+
usage: "mode <afk|hitl|show>",
|
|
4969
|
+
desc: "Set or show the persistent execution mode for /auto-devtronic",
|
|
4970
|
+
opts: ["--path <path> Target directory"]
|
|
4971
|
+
},
|
|
4154
4972
|
{
|
|
4155
4973
|
title: "Diagnostics",
|
|
4156
4974
|
usage: "status",
|
|
@@ -4186,14 +5004,14 @@ program.command("help").description("Show help (use --all for full reference)").
|
|
|
4186
5004
|
];
|
|
4187
5005
|
for (const section of sections) {
|
|
4188
5006
|
if (section.title) {
|
|
4189
|
-
console.log(` ${
|
|
5007
|
+
console.log(` ${chalk17.bold.underline(section.title)}
|
|
4190
5008
|
`);
|
|
4191
5009
|
}
|
|
4192
|
-
console.log(` ${
|
|
4193
|
-
console.log(` ${
|
|
5010
|
+
console.log(` ${chalk17.white("devtronic " + section.usage)}`);
|
|
5011
|
+
console.log(` ${chalk17.dim(section.desc)}`);
|
|
4194
5012
|
if (section.opts) {
|
|
4195
5013
|
for (const opt of section.opts) {
|
|
4196
|
-
console.log(` ${
|
|
5014
|
+
console.log(` ${chalk17.yellow(opt)}`);
|
|
4197
5015
|
}
|
|
4198
5016
|
}
|
|
4199
5017
|
console.log();
|
|
@@ -4206,21 +5024,33 @@ addonCmd.command("add").description("Add an addon to the devtronic plugin").argu
|
|
|
4206
5024
|
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
5025
|
await addonCommand("remove", name, { path: options.path });
|
|
4208
5026
|
});
|
|
5027
|
+
addonCmd.command("list").description("List available and installed addons").option("--path <path>", "Target directory (default: current directory)").action(async (options) => {
|
|
5028
|
+
await addonListCommand({ path: options.path });
|
|
5029
|
+
});
|
|
5030
|
+
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) => {
|
|
5031
|
+
await addonCommand("enable", name, { path: options.path });
|
|
5032
|
+
});
|
|
5033
|
+
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) => {
|
|
5034
|
+
await addonCommand("disable", name, { path: options.path });
|
|
5035
|
+
});
|
|
5036
|
+
addonCmd.command("sync").description("Regenerate addon files for current agent configuration").option("--path <path>", "Target directory (default: current directory)").action(async (options) => {
|
|
5037
|
+
await addonSyncCommand({ path: options.path });
|
|
5038
|
+
});
|
|
4209
5039
|
program.command("presets").description("List available configuration presets").action(() => {
|
|
4210
|
-
|
|
5040
|
+
p16.intro(introTitle("Presets"));
|
|
4211
5041
|
const lines = Object.entries(PRESETS).map(([name, preset]) => {
|
|
4212
|
-
const parts = [` ${
|
|
5042
|
+
const parts = [` ${chalk17.bold(name)}`];
|
|
4213
5043
|
parts.push(` ${preset.description}`);
|
|
4214
5044
|
if (preset.config.architecture) {
|
|
4215
|
-
parts.push(` Architecture: ${
|
|
5045
|
+
parts.push(` Architecture: ${chalk17.cyan(preset.config.architecture)}`);
|
|
4216
5046
|
}
|
|
4217
5047
|
if (preset.config.layers) {
|
|
4218
|
-
parts.push(` Layers: ${
|
|
5048
|
+
parts.push(` Layers: ${chalk17.cyan(preset.config.layers.join(", "))}`);
|
|
4219
5049
|
}
|
|
4220
5050
|
return parts.join("\n");
|
|
4221
5051
|
});
|
|
4222
|
-
|
|
4223
|
-
|
|
5052
|
+
p16.note(lines.join("\n\n"), "Available Presets");
|
|
5053
|
+
p16.outro(`Usage: ${chalk17.cyan("npx devtronic init --preset <name>")}`);
|
|
4224
5054
|
});
|
|
4225
5055
|
program.parseAsync().catch((err) => {
|
|
4226
5056
|
console.error(`
|