asdm-cli 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -6
- package/dist/index.mjs +807 -219
- package/package.json +11 -2
- package/registry/latest.json +6 -4
- package/registry/policy.yaml +1 -0
- package/registry/profiles/base/profile.yaml +1 -0
- package/registry/v0.1.3.json +253 -0
- package/registry/v0.1.4.json +253 -0
- package/registry/v0.2.0.json +255 -0
- package/registry/v0.3.0.json +255 -0
- package/schemas/config.schema.json +1 -1
- package/schemas/manifest.schema.json +1 -1
- package/schemas/profile.schema.json +5 -1
package/dist/index.mjs
CHANGED
|
@@ -10,33 +10,19 @@ var __export = (target, all) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// src/utils/fs.ts
|
|
13
|
-
var fs_exports = {};
|
|
14
|
-
__export(fs_exports, {
|
|
15
|
-
copyFile: () => copyFile,
|
|
16
|
-
ensureDir: () => ensureDir,
|
|
17
|
-
exists: () => exists,
|
|
18
|
-
getAsdmCacheDir: () => getAsdmCacheDir,
|
|
19
|
-
getAsdmConfigDir: () => getAsdmConfigDir,
|
|
20
|
-
listFiles: () => listFiles,
|
|
21
|
-
normalizePath: () => normalizePath,
|
|
22
|
-
readFile: () => readFile,
|
|
23
|
-
readJson: () => readJson,
|
|
24
|
-
removeFile: () => removeFile,
|
|
25
|
-
writeFile: () => writeFile,
|
|
26
|
-
writeJson: () => writeJson
|
|
27
|
-
});
|
|
28
13
|
import { promises as fs } from "fs";
|
|
29
14
|
import path from "path";
|
|
30
15
|
import os from "os";
|
|
31
|
-
function normalizePath(p) {
|
|
32
|
-
return p.replace(/\\/g, "/");
|
|
33
|
-
}
|
|
34
16
|
async function ensureDir(dirPath) {
|
|
35
17
|
await fs.mkdir(dirPath, { recursive: true });
|
|
36
18
|
}
|
|
37
19
|
async function writeFile(filePath, content) {
|
|
38
20
|
await ensureDir(path.dirname(filePath));
|
|
39
|
-
|
|
21
|
+
if (typeof content === "string") {
|
|
22
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
23
|
+
} else {
|
|
24
|
+
await fs.writeFile(filePath, content);
|
|
25
|
+
}
|
|
40
26
|
}
|
|
41
27
|
async function readFile(filePath) {
|
|
42
28
|
try {
|
|
@@ -80,6 +66,23 @@ async function listFiles(dirPath) {
|
|
|
80
66
|
function getAsdmConfigDir() {
|
|
81
67
|
return path.join(os.homedir(), ".config", "asdm");
|
|
82
68
|
}
|
|
69
|
+
function resolveGlobalEmitPath(relativePath, provider) {
|
|
70
|
+
const prefix = PROVIDER_PATH_PREFIXES[provider];
|
|
71
|
+
const globalDir = PROVIDER_GLOBAL_DIRS[provider];
|
|
72
|
+
if (!prefix || !globalDir) return null;
|
|
73
|
+
if (!relativePath.startsWith(prefix)) return null;
|
|
74
|
+
const stripped = relativePath.slice(prefix.length);
|
|
75
|
+
const resolved = path.resolve(globalDir, stripped);
|
|
76
|
+
const safeBase = path.resolve(globalDir) + path.sep;
|
|
77
|
+
if (!resolved.startsWith(safeBase)) return null;
|
|
78
|
+
return resolved;
|
|
79
|
+
}
|
|
80
|
+
function getGlobalLockfilePath() {
|
|
81
|
+
return path.join(getAsdmConfigDir(), "global-lock.json");
|
|
82
|
+
}
|
|
83
|
+
function getGlobalConfigPath() {
|
|
84
|
+
return path.join(getAsdmConfigDir(), "config.json");
|
|
85
|
+
}
|
|
83
86
|
function getAsdmCacheDir() {
|
|
84
87
|
const xdgCache = process.env["XDG_CACHE_HOME"];
|
|
85
88
|
if (xdgCache) return path.join(xdgCache, "asdm");
|
|
@@ -93,13 +96,23 @@ async function readJson(filePath) {
|
|
|
93
96
|
async function writeJson(filePath, data) {
|
|
94
97
|
await writeFile(filePath, JSON.stringify(data, null, 2));
|
|
95
98
|
}
|
|
96
|
-
|
|
97
|
-
await ensureDir(path.dirname(dest));
|
|
98
|
-
await fs.copyFile(src, dest);
|
|
99
|
-
}
|
|
99
|
+
var PROVIDER_GLOBAL_DIRS, PROVIDER_PATH_PREFIXES;
|
|
100
100
|
var init_fs = __esm({
|
|
101
101
|
"src/utils/fs.ts"() {
|
|
102
102
|
"use strict";
|
|
103
|
+
PROVIDER_GLOBAL_DIRS = {
|
|
104
|
+
opencode: process.platform === "win32" ? path.join(os.homedir(), "AppData", "Roaming", "opencode") : path.join(os.homedir(), ".config", "opencode"),
|
|
105
|
+
"claude-code": process.platform === "win32" ? path.join(os.homedir(), "AppData", "Roaming", "Claude") : path.join(os.homedir(), ".claude"),
|
|
106
|
+
copilot: process.platform === "win32" ? path.join(os.homedir(), "AppData", "Roaming", "GitHub Copilot") : path.join(os.homedir(), ".config", "github-copilot"),
|
|
107
|
+
// agents-dir uses %USERPROFILE%\.agents on all platforms (no AppData variant)
|
|
108
|
+
"agents-dir": path.join(os.homedir(), ".agents")
|
|
109
|
+
};
|
|
110
|
+
PROVIDER_PATH_PREFIXES = {
|
|
111
|
+
opencode: ".opencode/",
|
|
112
|
+
"claude-code": ".claude/",
|
|
113
|
+
copilot: ".github/",
|
|
114
|
+
"agents-dir": ".agents/"
|
|
115
|
+
};
|
|
103
116
|
}
|
|
104
117
|
});
|
|
105
118
|
|
|
@@ -531,8 +544,79 @@ var init_copilot = __esm({
|
|
|
531
544
|
}
|
|
532
545
|
});
|
|
533
546
|
|
|
547
|
+
// src/adapters/agents-dir.ts
|
|
548
|
+
var agents_dir_exports = {};
|
|
549
|
+
__export(agents_dir_exports, {
|
|
550
|
+
AgentsDirAdapter: () => AgentsDirAdapter,
|
|
551
|
+
createAgentsDirAdapter: () => createAgentsDirAdapter
|
|
552
|
+
});
|
|
553
|
+
import path11 from "path";
|
|
554
|
+
function formatAgentContent4(parsed) {
|
|
555
|
+
return [managedFileHeader(ADAPTER_NAME4), "", parsed.body].join("\n");
|
|
556
|
+
}
|
|
557
|
+
function formatSkillContent4(parsed) {
|
|
558
|
+
return [managedFileHeader(ADAPTER_NAME4), "", parsed.body].join("\n");
|
|
559
|
+
}
|
|
560
|
+
function formatCommandContent3(parsed) {
|
|
561
|
+
return [managedFileHeader(ADAPTER_NAME4), "", parsed.body].join("\n");
|
|
562
|
+
}
|
|
563
|
+
function createAgentsDirAdapter() {
|
|
564
|
+
return new AgentsDirAdapter();
|
|
565
|
+
}
|
|
566
|
+
var ADAPTER_NAME4, AgentsDirAdapter;
|
|
567
|
+
var init_agents_dir = __esm({
|
|
568
|
+
"src/adapters/agents-dir.ts"() {
|
|
569
|
+
"use strict";
|
|
570
|
+
init_fs();
|
|
571
|
+
init_base();
|
|
572
|
+
ADAPTER_NAME4 = "agents-dir";
|
|
573
|
+
AgentsDirAdapter = class {
|
|
574
|
+
name = ADAPTER_NAME4;
|
|
575
|
+
emitAgent(parsed, _targetDir) {
|
|
576
|
+
const relativePath = `.agents/agents/${parsed.name}.md`;
|
|
577
|
+
const content = formatAgentContent4(parsed);
|
|
578
|
+
return [createEmittedFile(relativePath, content, ADAPTER_NAME4, parsed.sourcePath)];
|
|
579
|
+
}
|
|
580
|
+
emitSkill(parsed, _targetDir) {
|
|
581
|
+
const relativePath = `.agents/skills/${parsed.name}/SKILL.md`;
|
|
582
|
+
const content = formatSkillContent4(parsed);
|
|
583
|
+
return [createEmittedFile(relativePath, content, ADAPTER_NAME4, parsed.sourcePath)];
|
|
584
|
+
}
|
|
585
|
+
emitCommand(parsed, _targetDir) {
|
|
586
|
+
const relativePath = `.agents/commands/${parsed.name}.md`;
|
|
587
|
+
const content = formatCommandContent3(parsed);
|
|
588
|
+
return [createEmittedFile(relativePath, content, ADAPTER_NAME4, parsed.sourcePath)];
|
|
589
|
+
}
|
|
590
|
+
emitRootInstructions(_profile, _targetDir) {
|
|
591
|
+
return [];
|
|
592
|
+
}
|
|
593
|
+
emitConfig(_profile, _targetDir) {
|
|
594
|
+
return [];
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Remove all ASDM-managed files from .agents/ in the given project root.
|
|
598
|
+
* Returns the list of absolute paths that were removed.
|
|
599
|
+
*/
|
|
600
|
+
async clean(projectRoot) {
|
|
601
|
+
const agentsDir = path11.join(projectRoot, ".agents");
|
|
602
|
+
if (!await exists(agentsDir)) return [];
|
|
603
|
+
const allFiles = await listFiles(agentsDir);
|
|
604
|
+
const removedPaths = [];
|
|
605
|
+
for (const filePath of allFiles) {
|
|
606
|
+
const content = await readFile(filePath);
|
|
607
|
+
if (content && content.includes("ASDM MANAGED FILE")) {
|
|
608
|
+
await removeFile(filePath);
|
|
609
|
+
removedPaths.push(filePath);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return removedPaths;
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
|
|
534
618
|
// src/cli/index.ts
|
|
535
|
-
import { defineCommand as
|
|
619
|
+
import { defineCommand as defineCommand17, runMain } from "citty";
|
|
536
620
|
|
|
537
621
|
// src/cli/commands/init.ts
|
|
538
622
|
init_fs();
|
|
@@ -679,6 +763,26 @@ function parseRegistryUrl(url) {
|
|
|
679
763
|
}
|
|
680
764
|
return { org: match[1], repo: match[2] };
|
|
681
765
|
}
|
|
766
|
+
function validateProjectConfig(config, label) {
|
|
767
|
+
if (!config.registry) {
|
|
768
|
+
throw new ConfigError(`Missing required field 'registry' in ${label}`);
|
|
769
|
+
}
|
|
770
|
+
if (!config.profile) {
|
|
771
|
+
throw new ConfigError(`Missing required field 'profile' in ${label}`);
|
|
772
|
+
}
|
|
773
|
+
parseRegistryUrl(config.registry);
|
|
774
|
+
}
|
|
775
|
+
async function readProjectConfigFromPath(filePath) {
|
|
776
|
+
const config = await readJson(filePath);
|
|
777
|
+
if (!config) {
|
|
778
|
+
throw new ConfigError(
|
|
779
|
+
`No config found at ${filePath}`,
|
|
780
|
+
"Run `asdm init` to initialize"
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
validateProjectConfig(config, path3.basename(filePath));
|
|
784
|
+
return config;
|
|
785
|
+
}
|
|
682
786
|
async function readProjectConfig(cwd) {
|
|
683
787
|
const filePath = path3.join(cwd, PROJECT_CONFIG_FILE);
|
|
684
788
|
const config = await readJson(filePath);
|
|
@@ -688,13 +792,7 @@ async function readProjectConfig(cwd) {
|
|
|
688
792
|
"Run `asdm init --profile <name>` to initialize"
|
|
689
793
|
);
|
|
690
794
|
}
|
|
691
|
-
|
|
692
|
-
throw new ConfigError(`Missing required field 'registry' in ${PROJECT_CONFIG_FILE}`);
|
|
693
|
-
}
|
|
694
|
-
if (!config.profile) {
|
|
695
|
-
throw new ConfigError(`Missing required field 'profile' in ${PROJECT_CONFIG_FILE}`);
|
|
696
|
-
}
|
|
697
|
-
parseRegistryUrl(config.registry);
|
|
795
|
+
validateProjectConfig(config, PROJECT_CONFIG_FILE);
|
|
698
796
|
return config;
|
|
699
797
|
}
|
|
700
798
|
async function readUserConfig(cwd) {
|
|
@@ -729,8 +827,7 @@ Run \`asdm profiles\` to see available profiles`
|
|
|
729
827
|
policy
|
|
730
828
|
};
|
|
731
829
|
}
|
|
732
|
-
async function
|
|
733
|
-
const filePath = path3.join(cwd, PROJECT_CONFIG_FILE);
|
|
830
|
+
async function createProjectConfigAtPath(filePath, registry, profile, providers = ["opencode"]) {
|
|
734
831
|
const config = {
|
|
735
832
|
$schema: "https://asdm.dev/schemas/config.schema.json",
|
|
736
833
|
registry,
|
|
@@ -739,6 +836,10 @@ async function createProjectConfig(cwd, registry, profile, providers = ["opencod
|
|
|
739
836
|
};
|
|
740
837
|
await writeJson(filePath, config);
|
|
741
838
|
}
|
|
839
|
+
async function createProjectConfig(cwd, registry, profile, providers = ["opencode"]) {
|
|
840
|
+
const filePath = path3.join(cwd, PROJECT_CONFIG_FILE);
|
|
841
|
+
await createProjectConfigAtPath(filePath, registry, profile, providers);
|
|
842
|
+
}
|
|
742
843
|
|
|
743
844
|
// src/core/telemetry.ts
|
|
744
845
|
init_hash();
|
|
@@ -755,7 +856,7 @@ var TelemetryWriter = class {
|
|
|
755
856
|
const fullEvent = {
|
|
756
857
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
757
858
|
machineId: machineId(),
|
|
758
|
-
version: "0.
|
|
859
|
+
version: "0.3.0",
|
|
759
860
|
...event
|
|
760
861
|
};
|
|
761
862
|
const line = JSON.stringify(fullEvent) + "\n";
|
|
@@ -924,7 +1025,12 @@ var init_default = defineCommand({
|
|
|
924
1025
|
},
|
|
925
1026
|
force: {
|
|
926
1027
|
type: "boolean",
|
|
927
|
-
description: "Overwrite existing
|
|
1028
|
+
description: "Overwrite existing config",
|
|
1029
|
+
default: false
|
|
1030
|
+
},
|
|
1031
|
+
global: {
|
|
1032
|
+
type: "boolean",
|
|
1033
|
+
description: "Write config to ~/.config/asdm/config.json instead of .asdm.json",
|
|
928
1034
|
default: false
|
|
929
1035
|
},
|
|
930
1036
|
gitignore: {
|
|
@@ -935,15 +1041,36 @@ var init_default = defineCommand({
|
|
|
935
1041
|
},
|
|
936
1042
|
async run(ctx) {
|
|
937
1043
|
const cwd = process.cwd();
|
|
1044
|
+
const profile = ctx.args.profile || "base";
|
|
1045
|
+
const registry = ctx.args.registry || DEFAULT_REGISTRY;
|
|
1046
|
+
const providers = ["opencode"];
|
|
1047
|
+
if (ctx.args.global) {
|
|
1048
|
+
const targetPath = getGlobalConfigPath();
|
|
1049
|
+
const alreadyExists2 = await exists(targetPath);
|
|
1050
|
+
if (alreadyExists2 && !ctx.args.force) {
|
|
1051
|
+
logger.warn(`Global config already exists at ${targetPath}. Use --force to overwrite.`);
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
try {
|
|
1055
|
+
await ensureDir(path5.dirname(targetPath));
|
|
1056
|
+
await createProjectConfigAtPath(targetPath, registry, profile, providers);
|
|
1057
|
+
logger.success(`Global config written to ${targetPath}`);
|
|
1058
|
+
logger.info(`Registry: ${registry}`);
|
|
1059
|
+
logger.info("Next step: run `asdm sync --global` to install agents, skills, and commands");
|
|
1060
|
+
} catch (err) {
|
|
1061
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1062
|
+
logger.error(message);
|
|
1063
|
+
process.exitCode = 1;
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
938
1068
|
const configPath = path5.join(cwd, ".asdm.json");
|
|
939
1069
|
const alreadyExists = await exists(configPath);
|
|
940
1070
|
if (alreadyExists && !ctx.args.force) {
|
|
941
1071
|
logger.warn(".asdm.json already exists. Use --force to overwrite.");
|
|
942
1072
|
return;
|
|
943
1073
|
}
|
|
944
|
-
const profile = ctx.args.profile || "base";
|
|
945
|
-
const registry = ctx.args.registry || DEFAULT_REGISTRY;
|
|
946
|
-
const providers = ["opencode"];
|
|
947
1074
|
try {
|
|
948
1075
|
await createProjectConfig(cwd, registry, profile, providers);
|
|
949
1076
|
logger.success(`Initialized .asdm.json with profile "${profile}"`);
|
|
@@ -973,9 +1100,10 @@ var init_default = defineCommand({
|
|
|
973
1100
|
|
|
974
1101
|
// src/cli/commands/sync.ts
|
|
975
1102
|
import { defineCommand as defineCommand2 } from "citty";
|
|
1103
|
+
import path13 from "path";
|
|
976
1104
|
|
|
977
1105
|
// src/core/syncer.ts
|
|
978
|
-
import
|
|
1106
|
+
import path12 from "path";
|
|
979
1107
|
|
|
980
1108
|
// src/core/registry-client.ts
|
|
981
1109
|
var GITHUB_API_BASE = "https://api.github.com";
|
|
@@ -1244,16 +1372,16 @@ function diffManifest(manifest, localShas, assetPaths) {
|
|
|
1244
1372
|
function getProfileAssetPaths(manifest, agentNames, skillNames, commandNames) {
|
|
1245
1373
|
const paths = [];
|
|
1246
1374
|
for (const name of agentNames) {
|
|
1247
|
-
const
|
|
1248
|
-
if (manifest.assets[
|
|
1375
|
+
const path22 = `agents/${name}.asdm.md`;
|
|
1376
|
+
if (manifest.assets[path22]) paths.push(path22);
|
|
1249
1377
|
}
|
|
1250
1378
|
for (const name of skillNames) {
|
|
1251
|
-
const
|
|
1252
|
-
if (manifest.assets[
|
|
1379
|
+
const path22 = `skills/${name}/SKILL.asdm.md`;
|
|
1380
|
+
if (manifest.assets[path22]) paths.push(path22);
|
|
1253
1381
|
}
|
|
1254
1382
|
for (const name of commandNames) {
|
|
1255
|
-
const
|
|
1256
|
-
if (manifest.assets[
|
|
1383
|
+
const path22 = `commands/${name}.asdm.md`;
|
|
1384
|
+
if (manifest.assets[path22]) paths.push(path22);
|
|
1257
1385
|
}
|
|
1258
1386
|
return paths;
|
|
1259
1387
|
}
|
|
@@ -1264,13 +1392,13 @@ async function loadCachedManifest(cacheDir) {
|
|
|
1264
1392
|
// src/core/lockfile.ts
|
|
1265
1393
|
init_fs();
|
|
1266
1394
|
import path7 from "path";
|
|
1267
|
-
var
|
|
1268
|
-
async function readLockfile(cwd) {
|
|
1269
|
-
const filePath = path7.join(cwd,
|
|
1395
|
+
var LOCKFILE_FILENAME = ".asdm-lock.json";
|
|
1396
|
+
async function readLockfile(cwd, lockfilePath) {
|
|
1397
|
+
const filePath = lockfilePath ?? path7.join(cwd, LOCKFILE_FILENAME);
|
|
1270
1398
|
return readJson(filePath);
|
|
1271
1399
|
}
|
|
1272
|
-
async function writeLockfile(cwd, lockfile) {
|
|
1273
|
-
const filePath = path7.join(cwd,
|
|
1400
|
+
async function writeLockfile(cwd, lockfile, lockfilePath) {
|
|
1401
|
+
const filePath = lockfilePath ?? path7.join(cwd, LOCKFILE_FILENAME);
|
|
1274
1402
|
await writeJson(filePath, {
|
|
1275
1403
|
$schema: "https://asdm.dev/schemas/lock.schema.json",
|
|
1276
1404
|
...lockfile
|
|
@@ -1373,12 +1501,17 @@ async function loadAdapters(providers) {
|
|
|
1373
1501
|
adapters.push(createCopilotAdapter2());
|
|
1374
1502
|
break;
|
|
1375
1503
|
}
|
|
1504
|
+
case "agents-dir": {
|
|
1505
|
+
const { createAgentsDirAdapter: createAgentsDirAdapter2 } = await Promise.resolve().then(() => (init_agents_dir(), agents_dir_exports));
|
|
1506
|
+
adapters.push(createAgentsDirAdapter2());
|
|
1507
|
+
break;
|
|
1508
|
+
}
|
|
1376
1509
|
}
|
|
1377
1510
|
}
|
|
1378
1511
|
return adapters;
|
|
1379
1512
|
}
|
|
1380
1513
|
async function getCliVersion() {
|
|
1381
|
-
return "0.
|
|
1514
|
+
return "0.3.0";
|
|
1382
1515
|
}
|
|
1383
1516
|
async function sync(options) {
|
|
1384
1517
|
const startTime = Date.now();
|
|
@@ -1386,7 +1519,8 @@ async function sync(options) {
|
|
|
1386
1519
|
options.telemetry?.write({ event: "sync.started" }).catch(() => {
|
|
1387
1520
|
});
|
|
1388
1521
|
try {
|
|
1389
|
-
const
|
|
1522
|
+
const configFilePath = options.configPath ?? path12.join(cwd, ".asdm.json");
|
|
1523
|
+
const projectConfig = await readProjectConfigFromPath(configFilePath);
|
|
1390
1524
|
const userConfig = await readUserConfig(cwd);
|
|
1391
1525
|
const client = new RegistryClient(projectConfig.registry);
|
|
1392
1526
|
const manifest = await client.getLatestManifest();
|
|
@@ -1399,7 +1533,8 @@ async function sync(options) {
|
|
|
1399
1533
|
resolvedProfile.skills,
|
|
1400
1534
|
resolvedProfile.commands
|
|
1401
1535
|
);
|
|
1402
|
-
const
|
|
1536
|
+
const lockfilePath = options.global ? getGlobalLockfilePath() : void 0;
|
|
1537
|
+
const existingLockfile = options.force ? null : await readLockfile(cwd, lockfilePath);
|
|
1403
1538
|
const localSourceShas = {};
|
|
1404
1539
|
if (existingLockfile) {
|
|
1405
1540
|
for (const [, entry] of Object.entries(existingLockfile.files)) {
|
|
@@ -1410,7 +1545,7 @@ async function sync(options) {
|
|
|
1410
1545
|
}
|
|
1411
1546
|
const diff = diffManifest(manifest, localSourceShas, assetPaths);
|
|
1412
1547
|
const toDownload = options.force ? assetPaths : [...diff.added, ...diff.updated];
|
|
1413
|
-
const cacheDir =
|
|
1548
|
+
const cacheDir = path12.join(getAsdmCacheDir(), manifest.version);
|
|
1414
1549
|
await ensureDir(cacheDir);
|
|
1415
1550
|
const downloadedAssets = /* @__PURE__ */ new Map();
|
|
1416
1551
|
for (const assetPath of toDownload) {
|
|
@@ -1424,17 +1559,16 @@ async function sync(options) {
|
|
|
1424
1559
|
"The asset may have been tampered with or the manifest is stale. Run `asdm sync --force`."
|
|
1425
1560
|
);
|
|
1426
1561
|
}
|
|
1427
|
-
const cachedPath =
|
|
1562
|
+
const cachedPath = path12.join(cacheDir, assetPath);
|
|
1428
1563
|
if (!options.dryRun) {
|
|
1429
1564
|
await writeFile(cachedPath, content);
|
|
1430
1565
|
}
|
|
1431
1566
|
downloadedAssets.set(assetPath, content);
|
|
1432
1567
|
}
|
|
1433
1568
|
for (const assetPath of diff.unchanged) {
|
|
1434
|
-
const cachedPath =
|
|
1569
|
+
const cachedPath = path12.join(cacheDir, assetPath);
|
|
1435
1570
|
try {
|
|
1436
|
-
const
|
|
1437
|
-
const cached = await readFile2(cachedPath);
|
|
1571
|
+
const cached = await readFile(cachedPath);
|
|
1438
1572
|
if (cached) downloadedAssets.set(assetPath, cached);
|
|
1439
1573
|
} catch {
|
|
1440
1574
|
}
|
|
@@ -1493,12 +1627,21 @@ async function sync(options) {
|
|
|
1493
1627
|
const configFiles = adapter.emitConfig(resolvedProfile, cwd);
|
|
1494
1628
|
allEmittedFiles.push(...configFiles);
|
|
1495
1629
|
}
|
|
1630
|
+
const resolvedPaths = /* @__PURE__ */ new Map();
|
|
1496
1631
|
for (const emittedFile of allEmittedFiles) {
|
|
1497
|
-
const absolutePath =
|
|
1632
|
+
const absolutePath = options.global ? resolveGlobalEmitPath(emittedFile.relativePath, emittedFile.adapter) : path12.join(cwd, emittedFile.relativePath);
|
|
1633
|
+
if (absolutePath !== null) {
|
|
1634
|
+
resolvedPaths.set(emittedFile.relativePath, absolutePath);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
for (const emittedFile of allEmittedFiles) {
|
|
1638
|
+
const absolutePath = resolvedPaths.get(emittedFile.relativePath);
|
|
1639
|
+
if (absolutePath === void 0) continue;
|
|
1498
1640
|
await writeFile(absolutePath, emittedFile.content);
|
|
1499
1641
|
}
|
|
1500
1642
|
const lockfileFiles = {};
|
|
1501
1643
|
for (const emittedFile of allEmittedFiles) {
|
|
1644
|
+
if (!resolvedPaths.has(emittedFile.relativePath)) continue;
|
|
1502
1645
|
lockfileFiles[emittedFile.relativePath] = createLockEntry(
|
|
1503
1646
|
emittedFile.sha256,
|
|
1504
1647
|
emittedFile.sourcePath,
|
|
@@ -1515,7 +1658,7 @@ async function sync(options) {
|
|
|
1515
1658
|
resolvedProfiles: resolvedProfile.resolvedFrom,
|
|
1516
1659
|
files: lockfileFiles
|
|
1517
1660
|
});
|
|
1518
|
-
await writeLockfile(cwd, lockfile);
|
|
1661
|
+
await writeLockfile(cwd, lockfile, lockfilePath);
|
|
1519
1662
|
const stats = {
|
|
1520
1663
|
filesAdded: diff.added.length,
|
|
1521
1664
|
filesUpdated: diff.updated.length,
|
|
@@ -1547,6 +1690,19 @@ async function sync(options) {
|
|
|
1547
1690
|
}
|
|
1548
1691
|
|
|
1549
1692
|
// src/cli/commands/sync.ts
|
|
1693
|
+
init_fs();
|
|
1694
|
+
async function resolveConfigPath(cwd, isGlobal) {
|
|
1695
|
+
const localPath = path13.join(cwd, ".asdm.json");
|
|
1696
|
+
if (await exists(localPath)) return localPath;
|
|
1697
|
+
if (isGlobal) {
|
|
1698
|
+
const globalPath = getGlobalConfigPath();
|
|
1699
|
+
if (await exists(globalPath)) return globalPath;
|
|
1700
|
+
}
|
|
1701
|
+
throw new ConfigError(
|
|
1702
|
+
"No config found.",
|
|
1703
|
+
isGlobal ? "Run `asdm init` (project) or `asdm init --global` (machine-wide setup)." : "Run `asdm init` to initialize this project."
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1550
1706
|
var sync_default = defineCommand2({
|
|
1551
1707
|
meta: {
|
|
1552
1708
|
name: "sync",
|
|
@@ -1571,6 +1727,11 @@ var sync_default = defineCommand2({
|
|
|
1571
1727
|
provider: {
|
|
1572
1728
|
type: "string",
|
|
1573
1729
|
description: "Sync only for a specific provider"
|
|
1730
|
+
},
|
|
1731
|
+
global: {
|
|
1732
|
+
type: "boolean",
|
|
1733
|
+
description: "Install to global provider config directories instead of project-local folders",
|
|
1734
|
+
default: false
|
|
1574
1735
|
}
|
|
1575
1736
|
},
|
|
1576
1737
|
async run(ctx) {
|
|
@@ -1584,12 +1745,15 @@ var sync_default = defineCommand2({
|
|
|
1584
1745
|
logger.asdm("Starting sync\u2026");
|
|
1585
1746
|
const telemetry = new TelemetryWriter(cwd);
|
|
1586
1747
|
try {
|
|
1748
|
+
const configPath = await resolveConfigPath(cwd, ctx.args.global ?? false);
|
|
1587
1749
|
const result = await sync({
|
|
1588
1750
|
cwd,
|
|
1751
|
+
configPath,
|
|
1589
1752
|
force: ctx.args.force,
|
|
1590
1753
|
dryRun,
|
|
1591
1754
|
verbose,
|
|
1592
1755
|
provider: ctx.args.provider,
|
|
1756
|
+
global: ctx.args.global ?? false,
|
|
1593
1757
|
telemetry
|
|
1594
1758
|
});
|
|
1595
1759
|
const { stats } = result;
|
|
@@ -1626,7 +1790,7 @@ var sync_default = defineCommand2({
|
|
|
1626
1790
|
import { defineCommand as defineCommand3 } from "citty";
|
|
1627
1791
|
|
|
1628
1792
|
// src/core/verifier.ts
|
|
1629
|
-
import
|
|
1793
|
+
import path14 from "path";
|
|
1630
1794
|
init_hash();
|
|
1631
1795
|
init_fs();
|
|
1632
1796
|
var VERIFY_EXIT_CODES = {
|
|
@@ -1635,8 +1799,9 @@ var VERIFY_EXIT_CODES = {
|
|
|
1635
1799
|
NO_LOCK: 2,
|
|
1636
1800
|
OUTDATED: 3
|
|
1637
1801
|
};
|
|
1638
|
-
async function verify(cwd, latestManifestVersion, onlyManaged = true, telemetry) {
|
|
1639
|
-
const lockfile = await readLockfile(cwd);
|
|
1802
|
+
async function verify(cwd, latestManifestVersion, onlyManaged = true, telemetry, lockfilePath) {
|
|
1803
|
+
const lockfile = await readLockfile(cwd, lockfilePath);
|
|
1804
|
+
const isGlobal = lockfilePath !== void 0;
|
|
1640
1805
|
if (!lockfile) {
|
|
1641
1806
|
telemetry?.write({ event: "verify.failed" }).catch(() => {
|
|
1642
1807
|
});
|
|
@@ -1650,7 +1815,7 @@ async function verify(cwd, latestManifestVersion, onlyManaged = true, telemetry)
|
|
|
1650
1815
|
let checkedFiles = 0;
|
|
1651
1816
|
const filesToCheck = onlyManaged ? Object.entries(lockfile.files).filter(([, entry]) => entry.managed) : Object.entries(lockfile.files);
|
|
1652
1817
|
for (const [relativePath, entry] of filesToCheck) {
|
|
1653
|
-
const absolutePath =
|
|
1818
|
+
const absolutePath = isGlobal ? resolveGlobalEmitPath(relativePath, entry.adapter) ?? path14.join(cwd, relativePath) : path14.join(cwd, relativePath);
|
|
1654
1819
|
checkedFiles++;
|
|
1655
1820
|
const fileExists = await exists(absolutePath);
|
|
1656
1821
|
if (!fileExists) {
|
|
@@ -1694,6 +1859,17 @@ async function verify(cwd, latestManifestVersion, onlyManaged = true, telemetry)
|
|
|
1694
1859
|
}
|
|
1695
1860
|
|
|
1696
1861
|
// src/cli/commands/verify.ts
|
|
1862
|
+
init_fs();
|
|
1863
|
+
async function fetchLatestManifestVersion(cwd) {
|
|
1864
|
+
try {
|
|
1865
|
+
const config = await readProjectConfig(cwd);
|
|
1866
|
+
const client = new RegistryClient(config.registry);
|
|
1867
|
+
const manifest = await client.getLatestManifest();
|
|
1868
|
+
return manifest.version;
|
|
1869
|
+
} catch {
|
|
1870
|
+
return void 0;
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1697
1873
|
var verify_default = defineCommand3({
|
|
1698
1874
|
meta: {
|
|
1699
1875
|
name: "verify",
|
|
@@ -1714,6 +1890,16 @@ var verify_default = defineCommand3({
|
|
|
1714
1890
|
type: "boolean",
|
|
1715
1891
|
description: "Suppress non-error output",
|
|
1716
1892
|
default: false
|
|
1893
|
+
},
|
|
1894
|
+
global: {
|
|
1895
|
+
type: "boolean",
|
|
1896
|
+
description: "Verify files installed to global provider config directories",
|
|
1897
|
+
default: false
|
|
1898
|
+
},
|
|
1899
|
+
offline: {
|
|
1900
|
+
type: "boolean",
|
|
1901
|
+
description: "Skip remote manifest version check (exit code 3 will never trigger)",
|
|
1902
|
+
default: false
|
|
1717
1903
|
}
|
|
1718
1904
|
},
|
|
1719
1905
|
async run(ctx) {
|
|
@@ -1722,8 +1908,9 @@ var verify_default = defineCommand3({
|
|
|
1722
1908
|
const quiet = ctx.args.quiet;
|
|
1723
1909
|
if (quiet) logger.setQuiet(true);
|
|
1724
1910
|
const telemetry = new TelemetryWriter(cwd);
|
|
1911
|
+
const lockfilePath = ctx.args.global ? getGlobalLockfilePath() : void 0;
|
|
1725
1912
|
if (ctx.args.strict) {
|
|
1726
|
-
const result = await verify(cwd, void 0, true, telemetry);
|
|
1913
|
+
const result = await verify(cwd, void 0, true, telemetry, lockfilePath);
|
|
1727
1914
|
if (useJson) {
|
|
1728
1915
|
console.log(JSON.stringify({
|
|
1729
1916
|
status: result.exitCode === VERIFY_EXIT_CODES.OK ? "ok" : "error",
|
|
@@ -1740,16 +1927,20 @@ var verify_default = defineCommand3({
|
|
|
1740
1927
|
process.exitCode = result.exitCode;
|
|
1741
1928
|
return;
|
|
1742
1929
|
}
|
|
1930
|
+
let latestManifestVersion;
|
|
1931
|
+
if (!ctx.args.offline && !ctx.args.global) {
|
|
1932
|
+
latestManifestVersion = await fetchLatestManifestVersion(cwd);
|
|
1933
|
+
}
|
|
1743
1934
|
try {
|
|
1744
|
-
const result = await verify(cwd,
|
|
1935
|
+
const result = await verify(cwd, latestManifestVersion, true, telemetry, lockfilePath);
|
|
1745
1936
|
if (useJson) {
|
|
1746
1937
|
console.log(JSON.stringify(result, null, 2));
|
|
1747
1938
|
process.exitCode = result.exitCode;
|
|
1748
1939
|
return;
|
|
1749
1940
|
}
|
|
1750
1941
|
if (result.exitCode === VERIFY_EXIT_CODES.NO_LOCK) {
|
|
1751
|
-
logger.warn("No lockfile found (.asdm-lock.json)");
|
|
1752
|
-
logger.info("Run `asdm sync` to initialize");
|
|
1942
|
+
logger.warn(lockfilePath ? "No global lockfile found" : "No lockfile found (.asdm-lock.json)");
|
|
1943
|
+
logger.info(lockfilePath ? "Run `asdm sync --global` to initialize" : "Run `asdm sync` to initialize");
|
|
1753
1944
|
process.exitCode = VERIFY_EXIT_CODES.NO_LOCK;
|
|
1754
1945
|
return;
|
|
1755
1946
|
}
|
|
@@ -2252,7 +2443,7 @@ var version_default = defineCommand10({
|
|
|
2252
2443
|
description: "Print CLI version and environment info"
|
|
2253
2444
|
},
|
|
2254
2445
|
run(_ctx) {
|
|
2255
|
-
console.log(`asdm v${"0.
|
|
2446
|
+
console.log(`asdm v${"0.3.0"}`);
|
|
2256
2447
|
console.log(`node ${process.version}`);
|
|
2257
2448
|
console.log(`os ${os3.type()} ${os3.release()} (${process.platform})`);
|
|
2258
2449
|
}
|
|
@@ -2260,11 +2451,11 @@ var version_default = defineCommand10({
|
|
|
2260
2451
|
|
|
2261
2452
|
// src/cli/commands/doctor.ts
|
|
2262
2453
|
import { defineCommand as defineCommand11 } from "citty";
|
|
2263
|
-
import
|
|
2454
|
+
import path16 from "path";
|
|
2264
2455
|
|
|
2265
2456
|
// src/core/overlay.ts
|
|
2266
2457
|
init_fs();
|
|
2267
|
-
import
|
|
2458
|
+
import path15 from "path";
|
|
2268
2459
|
import { promises as fs4 } from "fs";
|
|
2269
2460
|
var OVERLAY_SEPARATOR = [
|
|
2270
2461
|
"",
|
|
@@ -2275,7 +2466,7 @@ var OVERLAY_SEPARATOR = [
|
|
|
2275
2466
|
""
|
|
2276
2467
|
].join("\n");
|
|
2277
2468
|
async function readOverlays(projectRoot) {
|
|
2278
|
-
const overlaysDir =
|
|
2469
|
+
const overlaysDir = path15.join(projectRoot, "overlays");
|
|
2279
2470
|
const dirExists = await exists(overlaysDir);
|
|
2280
2471
|
if (!dirExists) return /* @__PURE__ */ new Map();
|
|
2281
2472
|
const entries = await fs4.readdir(overlaysDir, { withFileTypes: true });
|
|
@@ -2283,7 +2474,7 @@ async function readOverlays(projectRoot) {
|
|
|
2283
2474
|
for (const entry of entries) {
|
|
2284
2475
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
2285
2476
|
const agentId = entry.name.slice(0, -".md".length);
|
|
2286
|
-
const overlayPath =
|
|
2477
|
+
const overlayPath = path15.join(overlaysDir, entry.name);
|
|
2287
2478
|
const content = await readFile(overlayPath);
|
|
2288
2479
|
if (content === null) continue;
|
|
2289
2480
|
overlayMap.set(agentId, { agentId, content, path: overlayPath });
|
|
@@ -2314,7 +2505,7 @@ var doctor_default = defineCommand11({
|
|
|
2314
2505
|
let anyFailed = false;
|
|
2315
2506
|
let projectConfig = null;
|
|
2316
2507
|
let registryClient = null;
|
|
2317
|
-
const configPath =
|
|
2508
|
+
const configPath = path16.join(cwd, ".asdm.json");
|
|
2318
2509
|
const hasConfig = await exists(configPath);
|
|
2319
2510
|
checks.push({
|
|
2320
2511
|
label: ".asdm.json present",
|
|
@@ -2379,10 +2570,10 @@ var doctor_default = defineCommand11({
|
|
|
2379
2570
|
}
|
|
2380
2571
|
const managedEntries = Object.entries(lockfile.files).filter(([, e]) => e.managed);
|
|
2381
2572
|
const missingFiles = [];
|
|
2382
|
-
const resolvedCwd =
|
|
2573
|
+
const resolvedCwd = path16.resolve(cwd);
|
|
2383
2574
|
for (const [filePath] of managedEntries) {
|
|
2384
|
-
const absPath =
|
|
2385
|
-
if (!absPath.startsWith(resolvedCwd +
|
|
2575
|
+
const absPath = path16.resolve(cwd, filePath);
|
|
2576
|
+
if (!absPath.startsWith(resolvedCwd + path16.sep) && absPath !== resolvedCwd) {
|
|
2386
2577
|
continue;
|
|
2387
2578
|
}
|
|
2388
2579
|
const fileExists = await exists(absPath);
|
|
@@ -2437,7 +2628,7 @@ var doctor_default = defineCommand11({
|
|
|
2437
2628
|
detail: "Skipped \u2014 no lockfile"
|
|
2438
2629
|
});
|
|
2439
2630
|
}
|
|
2440
|
-
const gitignoreContent = await readFile(
|
|
2631
|
+
const gitignoreContent = await readFile(path16.join(cwd, ".gitignore"));
|
|
2441
2632
|
const hasAsdmBlock = gitignoreContent?.includes(ASDM_MARKER_START) ?? false;
|
|
2442
2633
|
checks.push({
|
|
2443
2634
|
label: ".gitignore has ASDM block",
|
|
@@ -2491,11 +2682,11 @@ var doctor_default = defineCommand11({
|
|
|
2491
2682
|
|
|
2492
2683
|
// src/cli/commands/clean.ts
|
|
2493
2684
|
import { defineCommand as defineCommand12 } from "citty";
|
|
2494
|
-
import
|
|
2685
|
+
import path17 from "path";
|
|
2495
2686
|
import { promises as fs5 } from "fs";
|
|
2496
2687
|
import readline from "readline";
|
|
2497
2688
|
init_fs();
|
|
2498
|
-
var
|
|
2689
|
+
var LOCKFILE_NAME = ".asdm-lock.json";
|
|
2499
2690
|
async function getFileSizeBytes(filePath) {
|
|
2500
2691
|
try {
|
|
2501
2692
|
const stat = await fs5.stat(filePath);
|
|
@@ -2533,9 +2724,14 @@ var clean_default = defineCommand12({
|
|
|
2533
2724
|
description: "Preview what would be removed without deleting",
|
|
2534
2725
|
default: false
|
|
2535
2726
|
},
|
|
2727
|
+
global: {
|
|
2728
|
+
type: "boolean",
|
|
2729
|
+
description: "Clean files installed to global provider config directories",
|
|
2730
|
+
default: false
|
|
2731
|
+
},
|
|
2536
2732
|
target: {
|
|
2537
2733
|
type: "string",
|
|
2538
|
-
description: "Only clean files for a specific provider (opencode | claude-code | copilot)",
|
|
2734
|
+
description: "Only clean files for a specific provider (opencode | claude-code | copilot | agents-dir)",
|
|
2539
2735
|
alias: "t"
|
|
2540
2736
|
}
|
|
2541
2737
|
},
|
|
@@ -2543,119 +2739,226 @@ var clean_default = defineCommand12({
|
|
|
2543
2739
|
const cwd = process.cwd();
|
|
2544
2740
|
const dryRun = ctx.args["dry-run"];
|
|
2545
2741
|
const target = ctx.args.target;
|
|
2742
|
+
const isGlobal = ctx.args.global ?? false;
|
|
2546
2743
|
if (dryRun) {
|
|
2547
2744
|
logger.info("Dry run \u2014 no files will be removed");
|
|
2548
2745
|
}
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
logger.warn("No lockfile found \u2014 nothing to clean");
|
|
2746
|
+
if (isGlobal) {
|
|
2747
|
+
await runGlobalClean(dryRun, target);
|
|
2552
2748
|
return;
|
|
2553
2749
|
}
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2750
|
+
await runLocalClean(cwd, dryRun, target);
|
|
2751
|
+
}
|
|
2752
|
+
});
|
|
2753
|
+
async function runGlobalClean(dryRun, target) {
|
|
2754
|
+
const globalLockfilePath = getGlobalLockfilePath();
|
|
2755
|
+
const lockfile = await readLockfile(process.cwd(), globalLockfilePath);
|
|
2756
|
+
if (!lockfile) {
|
|
2757
|
+
logger.warn("No global lockfile found \u2014 nothing to clean");
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
const managedEntries = Object.entries(lockfile.files).filter(([, entry]) => {
|
|
2761
|
+
if (!entry.managed) return false;
|
|
2762
|
+
if (target) return entry.adapter === target;
|
|
2763
|
+
return true;
|
|
2764
|
+
});
|
|
2765
|
+
if (managedEntries.length === 0) {
|
|
2766
|
+
if (target) {
|
|
2767
|
+
logger.warn(`No globally managed files found for provider "${target}"`);
|
|
2768
|
+
} else {
|
|
2769
|
+
logger.warn("No globally managed files found \u2014 nothing to clean");
|
|
2770
|
+
}
|
|
2771
|
+
return;
|
|
2772
|
+
}
|
|
2773
|
+
if (!dryRun && process.stdout.isTTY && process.stdin.isTTY) {
|
|
2774
|
+
const suffix = target ? ` for provider "${target}"` : "";
|
|
2775
|
+
const confirmed = await confirmPrompt(
|
|
2776
|
+
`About to delete ${managedEntries.length} globally managed file(s)${suffix}. Continue? [y/N] `
|
|
2777
|
+
);
|
|
2778
|
+
if (!confirmed) {
|
|
2779
|
+
logger.info("Aborted \u2014 no files were removed");
|
|
2565
2780
|
return;
|
|
2566
2781
|
}
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2782
|
+
}
|
|
2783
|
+
logger.asdm(`Cleaning ${managedEntries.length} globally managed file(s)\u2026`);
|
|
2784
|
+
logger.divider();
|
|
2785
|
+
let removed = 0;
|
|
2786
|
+
let skippedMissing = 0;
|
|
2787
|
+
let totalBytesFreed = 0;
|
|
2788
|
+
for (const [relativePath, entry] of managedEntries) {
|
|
2789
|
+
const absolutePath = resolveGlobalEmitPath(relativePath, entry.adapter);
|
|
2790
|
+
if (absolutePath === null) {
|
|
2791
|
+
logger.dim(` skip ${relativePath} (project-root file, not applicable in global mode)`);
|
|
2792
|
+
skippedMissing++;
|
|
2793
|
+
continue;
|
|
2794
|
+
}
|
|
2795
|
+
const filePresent = await exists(absolutePath);
|
|
2796
|
+
if (!filePresent) {
|
|
2797
|
+
logger.dim(` skip ${absolutePath} (not found)`);
|
|
2798
|
+
skippedMissing++;
|
|
2799
|
+
continue;
|
|
2800
|
+
}
|
|
2801
|
+
if (dryRun) {
|
|
2802
|
+
logger.bullet(`would remove: ${absolutePath}`);
|
|
2803
|
+
removed++;
|
|
2804
|
+
continue;
|
|
2805
|
+
}
|
|
2806
|
+
const fileSize = await getFileSizeBytes(absolutePath);
|
|
2807
|
+
await removeFile(absolutePath);
|
|
2808
|
+
totalBytesFreed += fileSize;
|
|
2809
|
+
logger.bullet(`removed: ${absolutePath}`);
|
|
2810
|
+
removed++;
|
|
2811
|
+
}
|
|
2812
|
+
const lockfilePresent = await exists(globalLockfilePath);
|
|
2813
|
+
if (target) {
|
|
2814
|
+
if (!dryRun && lockfilePresent) {
|
|
2815
|
+
const updatedFiles = Object.fromEntries(
|
|
2816
|
+
Object.entries(lockfile.files).filter(([, entry]) => entry.adapter !== target)
|
|
2581
2817
|
);
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2818
|
+
const hasRemainingEntries = Object.keys(updatedFiles).length > 0;
|
|
2819
|
+
if (hasRemainingEntries) {
|
|
2820
|
+
await writeLockfile(process.cwd(), { ...lockfile, files: updatedFiles }, globalLockfilePath);
|
|
2821
|
+
logger.bullet(`updated: global lockfile (removed ${target} entries)`);
|
|
2822
|
+
} else {
|
|
2823
|
+
await removeFile(globalLockfilePath);
|
|
2824
|
+
logger.bullet(`removed: global lockfile (no entries remaining)`);
|
|
2585
2825
|
}
|
|
2826
|
+
} else if (dryRun) {
|
|
2827
|
+
logger.bullet(`would update: global lockfile (remove ${target} entries)`);
|
|
2586
2828
|
}
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
let removed = 0;
|
|
2590
|
-
let skippedMissing = 0;
|
|
2591
|
-
let totalBytesFreed = 0;
|
|
2592
|
-
for (const relativePath of safePaths) {
|
|
2593
|
-
const absolutePath = path15.resolve(cwd, relativePath);
|
|
2594
|
-
const fileExists = await exists(absolutePath);
|
|
2595
|
-
if (!fileExists) {
|
|
2596
|
-
logger.dim(` skip ${relativePath} (not found)`);
|
|
2597
|
-
skippedMissing++;
|
|
2598
|
-
continue;
|
|
2599
|
-
}
|
|
2829
|
+
} else {
|
|
2830
|
+
if (lockfilePresent) {
|
|
2600
2831
|
if (dryRun) {
|
|
2601
|
-
logger.bullet(`would remove:
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
logger.bullet(`removed: ${relativePath}`);
|
|
2609
|
-
removed++;
|
|
2832
|
+
logger.bullet(`would remove: global lockfile`);
|
|
2833
|
+
} else {
|
|
2834
|
+
const lockfileSize = await getFileSizeBytes(globalLockfilePath);
|
|
2835
|
+
await removeFile(globalLockfilePath);
|
|
2836
|
+
totalBytesFreed += lockfileSize;
|
|
2837
|
+
logger.bullet(`removed: global lockfile`);
|
|
2838
|
+
}
|
|
2610
2839
|
}
|
|
2611
|
-
|
|
2612
|
-
|
|
2840
|
+
}
|
|
2841
|
+
logger.divider();
|
|
2842
|
+
if (dryRun) {
|
|
2843
|
+
const suffix = target ? ` for provider "${target}"` : "";
|
|
2844
|
+
logger.info(`${removed} file(s) would be removed${suffix}, ${skippedMissing} skipped`);
|
|
2845
|
+
logger.info("Run without --dry-run to actually remove them");
|
|
2846
|
+
} else {
|
|
2847
|
+
const suffix = target ? ` (${target})` : "";
|
|
2848
|
+
logger.success(`Cleaned ${removed} globally managed file(s)${suffix} \u2014 ${formatBytes(totalBytesFreed)} freed`);
|
|
2849
|
+
if (skippedMissing > 0) logger.dim(` ${skippedMissing} file(s) were already missing`);
|
|
2850
|
+
logger.info("Run `asdm sync --global` to reinstall");
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
async function runLocalClean(cwd, dryRun, target) {
|
|
2854
|
+
const lockfile = await readLockfile(cwd);
|
|
2855
|
+
if (!lockfile) {
|
|
2856
|
+
logger.warn("No lockfile found \u2014 nothing to clean");
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2859
|
+
const managedEntries = Object.entries(lockfile.files).filter(([, entry]) => {
|
|
2860
|
+
if (!entry.managed) return false;
|
|
2861
|
+
if (target) return entry.adapter === target;
|
|
2862
|
+
return true;
|
|
2863
|
+
});
|
|
2864
|
+
if (managedEntries.length === 0) {
|
|
2613
2865
|
if (target) {
|
|
2614
|
-
|
|
2615
|
-
const updatedFiles = Object.fromEntries(
|
|
2616
|
-
Object.entries(lockfile.files).filter(([, entry]) => entry.adapter !== target)
|
|
2617
|
-
);
|
|
2618
|
-
await writeLockfile(cwd, { ...lockfile, files: updatedFiles });
|
|
2619
|
-
logger.bullet(`updated: ${LOCKFILE_NAME2} (removed ${target} entries)`);
|
|
2620
|
-
} else if (dryRun) {
|
|
2621
|
-
logger.bullet(`would update: ${LOCKFILE_NAME2} (remove ${target} entries)`);
|
|
2622
|
-
}
|
|
2866
|
+
logger.warn(`No managed files found for provider "${target}"`);
|
|
2623
2867
|
} else {
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2868
|
+
logger.warn("No managed files found \u2014 nothing to clean");
|
|
2869
|
+
}
|
|
2870
|
+
return;
|
|
2871
|
+
}
|
|
2872
|
+
const managedPaths = managedEntries.map(([filePath]) => filePath);
|
|
2873
|
+
const resolvedCwd = path17.resolve(cwd);
|
|
2874
|
+
const safePaths = managedPaths.filter((relativePath) => {
|
|
2875
|
+
const absPath = path17.resolve(cwd, relativePath);
|
|
2876
|
+
return absPath.startsWith(resolvedCwd + path17.sep) || absPath === resolvedCwd;
|
|
2877
|
+
});
|
|
2878
|
+
const skippedSuspicious = managedPaths.length - safePaths.length;
|
|
2879
|
+
if (skippedSuspicious > 0) {
|
|
2880
|
+
logger.warn(`Skipping ${skippedSuspicious} path(s) outside project root`);
|
|
2881
|
+
}
|
|
2882
|
+
if (!dryRun && process.stdout.isTTY && process.stdin.isTTY) {
|
|
2883
|
+
const suffix = target ? ` for provider "${target}"` : "";
|
|
2884
|
+
const confirmed = await confirmPrompt(
|
|
2885
|
+
`About to delete ${safePaths.length} file(s)${suffix}. Continue? [y/N] `
|
|
2886
|
+
);
|
|
2887
|
+
if (!confirmed) {
|
|
2888
|
+
logger.info("Aborted \u2014 no files were removed");
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
logger.asdm(`Cleaning ${safePaths.length} managed file(s)\u2026`);
|
|
2893
|
+
logger.divider();
|
|
2894
|
+
let removed = 0;
|
|
2895
|
+
let skippedMissing = 0;
|
|
2896
|
+
let totalBytesFreed = 0;
|
|
2897
|
+
for (const relativePath of safePaths) {
|
|
2898
|
+
const absolutePath = path17.resolve(cwd, relativePath);
|
|
2899
|
+
const filePresent = await exists(absolutePath);
|
|
2900
|
+
if (!filePresent) {
|
|
2901
|
+
logger.dim(` skip ${relativePath} (not found)`);
|
|
2902
|
+
skippedMissing++;
|
|
2903
|
+
continue;
|
|
2634
2904
|
}
|
|
2635
|
-
logger.divider();
|
|
2636
2905
|
if (dryRun) {
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
}
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2906
|
+
logger.bullet(`would remove: ${relativePath}`);
|
|
2907
|
+
removed++;
|
|
2908
|
+
continue;
|
|
2909
|
+
}
|
|
2910
|
+
const fileSize = await getFileSizeBytes(absolutePath);
|
|
2911
|
+
await removeFile(absolutePath);
|
|
2912
|
+
totalBytesFreed += fileSize;
|
|
2913
|
+
logger.bullet(`removed: ${relativePath}`);
|
|
2914
|
+
removed++;
|
|
2915
|
+
}
|
|
2916
|
+
const lockfilePath = path17.join(cwd, LOCKFILE_NAME);
|
|
2917
|
+
const lockfileOnDisk = await exists(lockfilePath);
|
|
2918
|
+
if (target) {
|
|
2919
|
+
if (!dryRun && lockfileOnDisk) {
|
|
2920
|
+
const updatedFiles = Object.fromEntries(
|
|
2921
|
+
Object.entries(lockfile.files).filter(([, entry]) => entry.adapter !== target)
|
|
2922
|
+
);
|
|
2923
|
+
await writeLockfile(cwd, { ...lockfile, files: updatedFiles });
|
|
2924
|
+
logger.bullet(`updated: ${LOCKFILE_NAME} (removed ${target} entries)`);
|
|
2925
|
+
} else if (dryRun) {
|
|
2926
|
+
logger.bullet(`would update: ${LOCKFILE_NAME} (remove ${target} entries)`);
|
|
2927
|
+
}
|
|
2928
|
+
} else {
|
|
2929
|
+
if (lockfileOnDisk) {
|
|
2930
|
+
if (dryRun) {
|
|
2931
|
+
logger.bullet(`would remove: ${LOCKFILE_NAME}`);
|
|
2932
|
+
} else {
|
|
2933
|
+
const lockfileSize = await getFileSizeBytes(lockfilePath);
|
|
2934
|
+
await removeFile(lockfilePath);
|
|
2935
|
+
totalBytesFreed += lockfileSize;
|
|
2936
|
+
logger.bullet(`removed: ${LOCKFILE_NAME}`);
|
|
2937
|
+
}
|
|
2645
2938
|
}
|
|
2646
2939
|
}
|
|
2647
|
-
|
|
2940
|
+
logger.divider();
|
|
2941
|
+
if (dryRun) {
|
|
2942
|
+
const suffix = target ? ` for provider "${target}"` : "";
|
|
2943
|
+
logger.info(`${removed} file(s) would be removed${suffix}, ${skippedMissing} not found`);
|
|
2944
|
+
logger.info("Run without --dry-run to actually remove them");
|
|
2945
|
+
} else {
|
|
2946
|
+
const suffix = target ? ` (${target})` : "";
|
|
2947
|
+
logger.success(`Cleaned ${removed} managed file(s)${suffix} \u2014 ${formatBytes(totalBytesFreed)} freed`);
|
|
2948
|
+
if (skippedMissing > 0) logger.dim(` ${skippedMissing} file(s) were already missing`);
|
|
2949
|
+
logger.info("Run `asdm sync` to reinstall");
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2648
2952
|
|
|
2649
2953
|
// src/cli/commands/hooks.ts
|
|
2650
2954
|
init_fs();
|
|
2651
2955
|
import { defineCommand as defineCommand13 } from "citty";
|
|
2652
|
-
import
|
|
2956
|
+
import path19 from "path";
|
|
2653
2957
|
import { promises as fs6 } from "fs";
|
|
2654
2958
|
|
|
2655
2959
|
// src/utils/post-merge-hook.ts
|
|
2656
|
-
function
|
|
2657
|
-
return
|
|
2658
|
-
# ASDM MANAGED \u2014 post-merge hook
|
|
2960
|
+
function generatePostMergeHookBody() {
|
|
2961
|
+
return `# ASDM MANAGED \u2014 post-merge hook
|
|
2659
2962
|
if [ -f ".asdm.yaml" ] || [ -f ".asdm.json" ]; then
|
|
2660
2963
|
echo "\u{1F504} ASDM: syncing after merge..."
|
|
2661
2964
|
npx asdm sync
|
|
@@ -2663,43 +2966,108 @@ fi
|
|
|
2663
2966
|
`;
|
|
2664
2967
|
}
|
|
2665
2968
|
|
|
2969
|
+
// src/utils/husky-detect.ts
|
|
2970
|
+
init_fs();
|
|
2971
|
+
import path18 from "path";
|
|
2972
|
+
function parseMajorVersion(versionString) {
|
|
2973
|
+
const stripped = versionString.replace(/^[^0-9]*/, "");
|
|
2974
|
+
const majorPart = stripped.split(".")[0] ?? "";
|
|
2975
|
+
const major = parseInt(majorPart, 10);
|
|
2976
|
+
return isNaN(major) ? null : major;
|
|
2977
|
+
}
|
|
2978
|
+
function majorToHuskyVersion(major) {
|
|
2979
|
+
return major >= 9 ? "v9+" : "v8";
|
|
2980
|
+
}
|
|
2981
|
+
async function detectHusky(cwd) {
|
|
2982
|
+
const huskyDirPath = path18.join(cwd, ".husky");
|
|
2983
|
+
const huskyDirExists = await exists(huskyDirPath);
|
|
2984
|
+
const pkg = await readJson(path18.join(cwd, "package.json"));
|
|
2985
|
+
const huskyVersionString = pkg?.devDependencies?.["husky"] ?? pkg?.dependencies?.["husky"];
|
|
2986
|
+
if (huskyVersionString !== void 0) {
|
|
2987
|
+
const major = parseMajorVersion(huskyVersionString);
|
|
2988
|
+
const version2 = major !== null ? majorToHuskyVersion(major) : "v9+";
|
|
2989
|
+
return {
|
|
2990
|
+
detected: true,
|
|
2991
|
+
version: version2,
|
|
2992
|
+
huskyDir: huskyDirExists ? huskyDirPath : null
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
if (!huskyDirExists) {
|
|
2996
|
+
return { detected: false, version: null, huskyDir: null };
|
|
2997
|
+
}
|
|
2998
|
+
const huskyShExists = await exists(path18.join(huskyDirPath, "_", "husky.sh"));
|
|
2999
|
+
const version = huskyShExists ? "v8" : "v9+";
|
|
3000
|
+
return {
|
|
3001
|
+
detected: true,
|
|
3002
|
+
version,
|
|
3003
|
+
huskyDir: huskyDirPath
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
3006
|
+
|
|
2666
3007
|
// src/cli/commands/hooks.ts
|
|
2667
|
-
var
|
|
2668
|
-
"pre-commit":
|
|
2669
|
-
relativePath: ".git/hooks/pre-commit",
|
|
2670
|
-
content: `#!/usr/bin/env sh
|
|
2671
|
-
# ASDM \u2014 managed pre-commit hook
|
|
3008
|
+
var HOOK_BODIES = {
|
|
3009
|
+
"pre-commit": `# ASDM \u2014 managed pre-commit hook
|
|
2672
3010
|
# Verifies integrity of managed files before allowing commits.
|
|
2673
3011
|
npx asdm verify --strict --quiet
|
|
2674
3012
|
`,
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
"
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
3013
|
+
"post-merge": generatePostMergeHookBody()
|
|
3014
|
+
};
|
|
3015
|
+
var HOOK_MARKERS = {
|
|
3016
|
+
"pre-commit": "ASDM \u2014 managed pre-commit hook",
|
|
3017
|
+
"post-merge": "ASDM MANAGED \u2014 post-merge hook"
|
|
3018
|
+
};
|
|
3019
|
+
var HOOK_DESCRIPTIONS = {
|
|
3020
|
+
"pre-commit": "runs `asdm verify --strict --quiet` before every commit",
|
|
3021
|
+
"post-merge": "runs `asdm sync` after git pull/merge"
|
|
2684
3022
|
};
|
|
2685
3023
|
function resolveHookTypes(hookFlag) {
|
|
2686
|
-
if (hookFlag === "pre-commit" || hookFlag === "post-merge")
|
|
2687
|
-
return [hookFlag];
|
|
2688
|
-
}
|
|
3024
|
+
if (hookFlag === "pre-commit" || hookFlag === "post-merge") return [hookFlag];
|
|
2689
3025
|
return ["pre-commit", "post-merge"];
|
|
2690
3026
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
3027
|
+
function determineHookMode(huskyInfo, noHusky) {
|
|
3028
|
+
if (noHusky || !huskyInfo.detected) return "git";
|
|
3029
|
+
if (huskyInfo.version === "v8") return "husky-v8";
|
|
3030
|
+
return "husky-v9+";
|
|
3031
|
+
}
|
|
3032
|
+
function buildHookContent(body, mode) {
|
|
3033
|
+
if (mode === "git") return `#!/usr/bin/env sh
|
|
3034
|
+
${body}`;
|
|
3035
|
+
if (mode === "husky-v8") return `#!/usr/bin/env sh
|
|
3036
|
+
. "$(dirname -- "$0")/_/husky.sh"
|
|
3037
|
+
|
|
3038
|
+
${body}`;
|
|
3039
|
+
return body;
|
|
3040
|
+
}
|
|
3041
|
+
function resolveHookDefinition(cwd, hookType, huskyInfo, noHusky) {
|
|
3042
|
+
const mode = determineHookMode(huskyInfo, noHusky);
|
|
3043
|
+
const body = HOOK_BODIES[hookType];
|
|
3044
|
+
const marker = HOOK_MARKERS[hookType];
|
|
3045
|
+
const description = HOOK_DESCRIPTIONS[hookType];
|
|
3046
|
+
const relativePath = mode === "git" ? `.git/hooks/${hookType}` : `.husky/${hookType}`;
|
|
3047
|
+
const content = buildHookContent(body, mode);
|
|
3048
|
+
return {
|
|
3049
|
+
absolutePath: path19.join(cwd, relativePath),
|
|
3050
|
+
relativePath,
|
|
3051
|
+
content,
|
|
3052
|
+
marker,
|
|
3053
|
+
description
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
async function installHook(cwd, hookType, huskyInfo, noHusky) {
|
|
3057
|
+
const mode = determineHookMode(huskyInfo, noHusky);
|
|
3058
|
+
const def = resolveHookDefinition(cwd, hookType, huskyInfo, noHusky);
|
|
3059
|
+
if (mode === "git") {
|
|
3060
|
+
const hasGit = await exists(path19.join(cwd, ".git"));
|
|
3061
|
+
if (!hasGit) {
|
|
3062
|
+
logger.error("No .git directory found", "Run `git init` first");
|
|
3063
|
+
process.exit(1);
|
|
3064
|
+
}
|
|
3065
|
+
} else {
|
|
3066
|
+
await ensureDir(path19.join(cwd, ".husky"));
|
|
2699
3067
|
}
|
|
2700
|
-
const hookExists = await exists(
|
|
3068
|
+
const hookExists = await exists(def.absolutePath);
|
|
2701
3069
|
if (hookExists) {
|
|
2702
|
-
const existing = await fs6.readFile(
|
|
3070
|
+
const existing = await fs6.readFile(def.absolutePath, "utf-8");
|
|
2703
3071
|
if (existing.includes(def.marker)) {
|
|
2704
3072
|
logger.info(`ASDM ${hookType} hook is already installed`);
|
|
2705
3073
|
return;
|
|
@@ -2708,30 +3076,42 @@ async function installHook(cwd, hookType) {
|
|
|
2708
3076
|
logger.warn(`Manual action required: add the ASDM logic to ${def.relativePath}`);
|
|
2709
3077
|
process.exit(1);
|
|
2710
3078
|
}
|
|
2711
|
-
await writeFile(
|
|
3079
|
+
await writeFile(def.absolutePath, def.content);
|
|
2712
3080
|
try {
|
|
2713
|
-
await fs6.chmod(
|
|
3081
|
+
await fs6.chmod(def.absolutePath, 493);
|
|
2714
3082
|
} catch {
|
|
2715
3083
|
}
|
|
2716
3084
|
logger.success(`Installed ${hookType} hook at ${def.relativePath}`);
|
|
2717
3085
|
logger.info(`The hook ${def.description}`);
|
|
2718
3086
|
}
|
|
2719
3087
|
async function uninstallHook(cwd, hookType) {
|
|
2720
|
-
const
|
|
2721
|
-
const
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
3088
|
+
const marker = HOOK_MARKERS[hookType];
|
|
3089
|
+
const candidates = [
|
|
3090
|
+
{
|
|
3091
|
+
relativePath: `.git/hooks/${hookType}`,
|
|
3092
|
+
absolutePath: path19.join(cwd, ".git", "hooks", hookType)
|
|
3093
|
+
},
|
|
3094
|
+
{
|
|
3095
|
+
relativePath: `.husky/${hookType}`,
|
|
3096
|
+
absolutePath: path19.join(cwd, ".husky", hookType)
|
|
3097
|
+
}
|
|
3098
|
+
];
|
|
3099
|
+
let removed = false;
|
|
3100
|
+
for (const { relativePath, absolutePath } of candidates) {
|
|
3101
|
+
const hookExists = await exists(absolutePath);
|
|
3102
|
+
if (!hookExists) continue;
|
|
3103
|
+
const content = await fs6.readFile(absolutePath, "utf-8");
|
|
3104
|
+
if (!content.includes(marker)) {
|
|
3105
|
+
logger.warn(`A ${hookType} hook at ${relativePath} was not installed by ASDM \u2014 skipping`);
|
|
3106
|
+
continue;
|
|
3107
|
+
}
|
|
3108
|
+
await removeFile(absolutePath);
|
|
3109
|
+
logger.success(`Removed ASDM ${hookType} hook from ${relativePath}`);
|
|
3110
|
+
removed = true;
|
|
2726
3111
|
}
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
logger.warn(`The ${hookType} hook was not installed by ASDM \u2014 not removing it`);
|
|
2730
|
-
logger.info(`If you want to remove it manually: rm ${def.relativePath}`);
|
|
2731
|
-
process.exit(1);
|
|
3112
|
+
if (!removed) {
|
|
3113
|
+
logger.info(`No ASDM-managed ${hookType} hook found \u2014 nothing to remove`);
|
|
2732
3114
|
}
|
|
2733
|
-
await removeFile(hookPath);
|
|
2734
|
-
logger.success(`Removed ASDM ${hookType} hook from ${def.relativePath}`);
|
|
2735
3115
|
}
|
|
2736
3116
|
var installCommand = defineCommand13({
|
|
2737
3117
|
meta: {
|
|
@@ -2743,13 +3123,25 @@ var installCommand = defineCommand13({
|
|
|
2743
3123
|
type: "string",
|
|
2744
3124
|
description: "Which hook to install: pre-commit | post-merge | all",
|
|
2745
3125
|
default: "all"
|
|
3126
|
+
},
|
|
3127
|
+
"no-husky": {
|
|
3128
|
+
type: "boolean",
|
|
3129
|
+
description: "Force .git/hooks/ mode even when Husky is detected",
|
|
3130
|
+
default: false
|
|
2746
3131
|
}
|
|
2747
3132
|
},
|
|
2748
3133
|
async run(ctx) {
|
|
2749
3134
|
const cwd = process.cwd();
|
|
2750
3135
|
const hookTypes = resolveHookTypes(ctx.args.hook);
|
|
3136
|
+
const noHusky = ctx.args["no-husky"] ?? false;
|
|
3137
|
+
const huskyInfo = noHusky ? { detected: false, version: null, huskyDir: null } : await detectHusky(cwd);
|
|
3138
|
+
if (huskyInfo.detected) {
|
|
3139
|
+
logger.info(`Using Husky hooks in .husky/ (${huskyInfo.version})`);
|
|
3140
|
+
} else {
|
|
3141
|
+
logger.info("Using Git hooks in .git/hooks/");
|
|
3142
|
+
}
|
|
2751
3143
|
for (const hookType of hookTypes) {
|
|
2752
|
-
await installHook(cwd, hookType);
|
|
3144
|
+
await installHook(cwd, hookType, huskyInfo, noHusky);
|
|
2753
3145
|
}
|
|
2754
3146
|
}
|
|
2755
3147
|
});
|
|
@@ -2925,9 +3317,204 @@ var telemetry_default = defineCommand15({
|
|
|
2925
3317
|
}
|
|
2926
3318
|
});
|
|
2927
3319
|
|
|
3320
|
+
// src/cli/commands/templates.ts
|
|
3321
|
+
init_fs();
|
|
3322
|
+
import { defineCommand as defineCommand16 } from "citty";
|
|
3323
|
+
import path20 from "path";
|
|
3324
|
+
function generateAgentTemplate(name) {
|
|
3325
|
+
return `---
|
|
3326
|
+
name: ${name}
|
|
3327
|
+
type: agent
|
|
3328
|
+
description: "Short description"
|
|
3329
|
+
version: "1.0.0"
|
|
3330
|
+
tags:
|
|
3331
|
+
- tag1
|
|
3332
|
+
- tag2
|
|
3333
|
+
providers:
|
|
3334
|
+
opencode:
|
|
3335
|
+
model: claude-sonnet-4-5
|
|
3336
|
+
permissions: {}
|
|
3337
|
+
tools: []
|
|
3338
|
+
claude-code:
|
|
3339
|
+
model: claude-opus-4-5
|
|
3340
|
+
allowedTools: []
|
|
3341
|
+
copilot:
|
|
3342
|
+
on:
|
|
3343
|
+
push:
|
|
3344
|
+
branches: [main]
|
|
3345
|
+
permissions:
|
|
3346
|
+
contents: read
|
|
3347
|
+
---
|
|
3348
|
+
|
|
3349
|
+
# ${name}
|
|
3350
|
+
|
|
3351
|
+
## Role
|
|
3352
|
+
Describe the agent's primary responsibility here.
|
|
3353
|
+
|
|
3354
|
+
## Instructions
|
|
3355
|
+
- Instruction 1
|
|
3356
|
+
- Instruction 2
|
|
3357
|
+
|
|
3358
|
+
## Guidelines
|
|
3359
|
+
- Guideline 1
|
|
3360
|
+
- Guideline 2
|
|
3361
|
+
`;
|
|
3362
|
+
}
|
|
3363
|
+
function generateSkillTemplate(name) {
|
|
3364
|
+
return `---
|
|
3365
|
+
name: ${name}
|
|
3366
|
+
type: skill
|
|
3367
|
+
description: "Short description"
|
|
3368
|
+
version: "1.0.0"
|
|
3369
|
+
tags:
|
|
3370
|
+
- tag1
|
|
3371
|
+
---
|
|
3372
|
+
|
|
3373
|
+
# ${name}
|
|
3374
|
+
|
|
3375
|
+
## Overview
|
|
3376
|
+
Describe the skill purpose.
|
|
3377
|
+
|
|
3378
|
+
## Usage
|
|
3379
|
+
How to use this skill.
|
|
3380
|
+
|
|
3381
|
+
## Examples
|
|
3382
|
+
- Example 1
|
|
3383
|
+
- Example 2
|
|
3384
|
+
`;
|
|
3385
|
+
}
|
|
3386
|
+
function generateCommandTemplate(name) {
|
|
3387
|
+
return `---
|
|
3388
|
+
name: ${name}
|
|
3389
|
+
type: command
|
|
3390
|
+
description: "Short description"
|
|
3391
|
+
version: "1.0.0"
|
|
3392
|
+
tags:
|
|
3393
|
+
- tag1
|
|
3394
|
+
---
|
|
3395
|
+
|
|
3396
|
+
# /${name}
|
|
3397
|
+
|
|
3398
|
+
## Description
|
|
3399
|
+
What this command does.
|
|
3400
|
+
|
|
3401
|
+
## Usage
|
|
3402
|
+
\`/${name} [options]\`
|
|
3403
|
+
|
|
3404
|
+
## Examples
|
|
3405
|
+
- \`/${name}\` \u2014 basic usage
|
|
3406
|
+
`;
|
|
3407
|
+
}
|
|
3408
|
+
async function writeTemplateFile(outputDir, name, content, force) {
|
|
3409
|
+
const filePath = path20.join(outputDir, `${name}.asdm.md`);
|
|
3410
|
+
const alreadyExists = await exists(filePath);
|
|
3411
|
+
if (alreadyExists && !force) {
|
|
3412
|
+
logger.error(
|
|
3413
|
+
`File already exists: ${filePath}`,
|
|
3414
|
+
"Use --force to overwrite"
|
|
3415
|
+
);
|
|
3416
|
+
process.exitCode = 1;
|
|
3417
|
+
return;
|
|
3418
|
+
}
|
|
3419
|
+
await writeFile(filePath, content);
|
|
3420
|
+
logger.success(`Created ${filePath}`);
|
|
3421
|
+
}
|
|
3422
|
+
var agentCommand = defineCommand16({
|
|
3423
|
+
meta: {
|
|
3424
|
+
name: "agent",
|
|
3425
|
+
description: "Scaffold an agent definition (.asdm.md)"
|
|
3426
|
+
},
|
|
3427
|
+
args: {
|
|
3428
|
+
name: {
|
|
3429
|
+
type: "positional",
|
|
3430
|
+
description: "Agent identifier (used as filename and frontmatter name)",
|
|
3431
|
+
required: true
|
|
3432
|
+
},
|
|
3433
|
+
output: {
|
|
3434
|
+
type: "string",
|
|
3435
|
+
description: "Output directory (default: current working directory)"
|
|
3436
|
+
},
|
|
3437
|
+
force: {
|
|
3438
|
+
type: "boolean",
|
|
3439
|
+
description: "Overwrite existing file",
|
|
3440
|
+
default: false
|
|
3441
|
+
}
|
|
3442
|
+
},
|
|
3443
|
+
async run(ctx) {
|
|
3444
|
+
const cwd = process.cwd();
|
|
3445
|
+
const outputDir = ctx.args.output ?? cwd;
|
|
3446
|
+
await writeTemplateFile(outputDir, ctx.args.name, generateAgentTemplate(ctx.args.name), ctx.args.force);
|
|
3447
|
+
}
|
|
3448
|
+
});
|
|
3449
|
+
var skillCommand = defineCommand16({
|
|
3450
|
+
meta: {
|
|
3451
|
+
name: "skill",
|
|
3452
|
+
description: "Scaffold a skill definition (.asdm.md)"
|
|
3453
|
+
},
|
|
3454
|
+
args: {
|
|
3455
|
+
name: {
|
|
3456
|
+
type: "positional",
|
|
3457
|
+
description: "Skill identifier (used as filename and frontmatter name)",
|
|
3458
|
+
required: true
|
|
3459
|
+
},
|
|
3460
|
+
output: {
|
|
3461
|
+
type: "string",
|
|
3462
|
+
description: "Output directory (default: current working directory)"
|
|
3463
|
+
},
|
|
3464
|
+
force: {
|
|
3465
|
+
type: "boolean",
|
|
3466
|
+
description: "Overwrite existing file",
|
|
3467
|
+
default: false
|
|
3468
|
+
}
|
|
3469
|
+
},
|
|
3470
|
+
async run(ctx) {
|
|
3471
|
+
const cwd = process.cwd();
|
|
3472
|
+
const outputDir = ctx.args.output ?? cwd;
|
|
3473
|
+
await writeTemplateFile(outputDir, ctx.args.name, generateSkillTemplate(ctx.args.name), ctx.args.force);
|
|
3474
|
+
}
|
|
3475
|
+
});
|
|
3476
|
+
var commandCommand = defineCommand16({
|
|
3477
|
+
meta: {
|
|
3478
|
+
name: "command",
|
|
3479
|
+
description: "Scaffold a slash-command definition (.asdm.md)"
|
|
3480
|
+
},
|
|
3481
|
+
args: {
|
|
3482
|
+
name: {
|
|
3483
|
+
type: "positional",
|
|
3484
|
+
description: "Command identifier (used as filename and frontmatter name)",
|
|
3485
|
+
required: true
|
|
3486
|
+
},
|
|
3487
|
+
output: {
|
|
3488
|
+
type: "string",
|
|
3489
|
+
description: "Output directory (default: current working directory)"
|
|
3490
|
+
},
|
|
3491
|
+
force: {
|
|
3492
|
+
type: "boolean",
|
|
3493
|
+
description: "Overwrite existing file",
|
|
3494
|
+
default: false
|
|
3495
|
+
}
|
|
3496
|
+
},
|
|
3497
|
+
async run(ctx) {
|
|
3498
|
+
const cwd = process.cwd();
|
|
3499
|
+
const outputDir = ctx.args.output ?? cwd;
|
|
3500
|
+
await writeTemplateFile(outputDir, ctx.args.name, generateCommandTemplate(ctx.args.name), ctx.args.force);
|
|
3501
|
+
}
|
|
3502
|
+
});
|
|
3503
|
+
var templates_default = defineCommand16({
|
|
3504
|
+
meta: {
|
|
3505
|
+
name: "templates",
|
|
3506
|
+
description: "Scaffold new .asdm.md asset files from built-in templates"
|
|
3507
|
+
},
|
|
3508
|
+
subCommands: {
|
|
3509
|
+
agent: agentCommand,
|
|
3510
|
+
skill: skillCommand,
|
|
3511
|
+
command: commandCommand
|
|
3512
|
+
}
|
|
3513
|
+
});
|
|
3514
|
+
|
|
2928
3515
|
// src/core/version-check.ts
|
|
2929
3516
|
init_fs();
|
|
2930
|
-
import
|
|
3517
|
+
import path21 from "path";
|
|
2931
3518
|
var CACHE_FILENAME = "version-check-cache.json";
|
|
2932
3519
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
2933
3520
|
var FETCH_TIMEOUT_MS2 = 3e3;
|
|
@@ -2948,7 +3535,7 @@ function isNewerVersion(current, latest) {
|
|
|
2948
3535
|
}
|
|
2949
3536
|
async function checkForUpdate(currentVersion) {
|
|
2950
3537
|
const cacheDir = getAsdmConfigDir();
|
|
2951
|
-
const cachePath =
|
|
3538
|
+
const cachePath = path21.join(cacheDir, CACHE_FILENAME);
|
|
2952
3539
|
const cache = await readJson(cachePath);
|
|
2953
3540
|
if (cache) {
|
|
2954
3541
|
const ageMs = Date.now() - new Date(cache.checkedAt).getTime();
|
|
@@ -2984,10 +3571,10 @@ async function checkForUpdate(currentVersion) {
|
|
|
2984
3571
|
}
|
|
2985
3572
|
|
|
2986
3573
|
// src/cli/index.ts
|
|
2987
|
-
var rootCommand =
|
|
3574
|
+
var rootCommand = defineCommand17({
|
|
2988
3575
|
meta: {
|
|
2989
3576
|
name: "asdm",
|
|
2990
|
-
version: "0.
|
|
3577
|
+
version: "0.3.0",
|
|
2991
3578
|
description: "Agentic Software Delivery Model \u2014 Write Once, Emit Many"
|
|
2992
3579
|
},
|
|
2993
3580
|
subCommands: {
|
|
@@ -3005,7 +3592,8 @@ var rootCommand = defineCommand16({
|
|
|
3005
3592
|
clean: clean_default,
|
|
3006
3593
|
hooks: hooks_default,
|
|
3007
3594
|
gitignore: gitignore_default,
|
|
3008
|
-
telemetry: telemetry_default
|
|
3595
|
+
telemetry: telemetry_default,
|
|
3596
|
+
templates: templates_default
|
|
3009
3597
|
}
|
|
3010
3598
|
});
|
|
3011
3599
|
function printUpdateBox(currentVersion, latestVersion) {
|
|
@@ -3033,9 +3621,9 @@ async function main() {
|
|
|
3033
3621
|
await runMain(rootCommand);
|
|
3034
3622
|
if (process.exitCode !== void 0 && process.exitCode !== 0) return;
|
|
3035
3623
|
try {
|
|
3036
|
-
const latestVersion = await checkForUpdate("0.
|
|
3624
|
+
const latestVersion = await checkForUpdate("0.3.0");
|
|
3037
3625
|
if (latestVersion) {
|
|
3038
|
-
printUpdateBox("0.
|
|
3626
|
+
printUpdateBox("0.3.0", latestVersion);
|
|
3039
3627
|
}
|
|
3040
3628
|
} catch {
|
|
3041
3629
|
}
|