@shruubi/agentconfig 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/cli.js +191 -0
- package/dist/config.js +58 -0
- package/dist/errors.js +18 -0
- package/dist/filesystem.js +78 -0
- package/dist/paths.js +46 -0
- package/dist/state.js +40 -0
- package/dist/status.js +56 -0
- package/dist/sync.js +296 -0
- package/dist/templates.js +89 -0
- package/dist/types.js +2 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -39,7 +39,8 @@ agentconfig sync
|
|
|
39
39
|
- `--dry-run` Show actions without writing.
|
|
40
40
|
- `--link` Force symlink mode.
|
|
41
41
|
- `--copy` Force copy mode.
|
|
42
|
-
- `--force` Overwrite unmanaged targets.
|
|
42
|
+
- `--force` Overwrite unmanaged targets (alias for `--on-conflict overwrite`).
|
|
43
|
+
- `--on-conflict <policy>` Choose how to handle existing unmanaged targets: `overwrite`, `backup`, `skip`, `cancel`.
|
|
43
44
|
- `--agent <name>` Restrict to one agent.
|
|
44
45
|
|
|
45
46
|
## Config location
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const errors_1 = require("./errors");
|
|
9
|
+
const config_1 = require("./config");
|
|
10
|
+
const templates_1 = require("./templates");
|
|
11
|
+
const status_1 = require("./status");
|
|
12
|
+
const sync_1 = require("./sync");
|
|
13
|
+
const paths_1 = require("./paths");
|
|
14
|
+
function parseArgs(argv) {
|
|
15
|
+
const args = argv.slice(2);
|
|
16
|
+
const result = {
|
|
17
|
+
command: null,
|
|
18
|
+
dryRun: false,
|
|
19
|
+
mode: null,
|
|
20
|
+
force: false,
|
|
21
|
+
help: false
|
|
22
|
+
};
|
|
23
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
24
|
+
const arg = args[i];
|
|
25
|
+
if (!result.command && !arg.startsWith("-")) {
|
|
26
|
+
result.command = arg;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (arg === "--project") {
|
|
30
|
+
result.project = args[i + 1];
|
|
31
|
+
i += 1;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (arg === "--dry-run") {
|
|
35
|
+
result.dryRun = true;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (arg === "--link") {
|
|
39
|
+
result.mode = "link";
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (arg === "--copy") {
|
|
43
|
+
result.mode = "copy";
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (arg === "--force") {
|
|
47
|
+
result.force = true;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (arg === "--on-conflict") {
|
|
51
|
+
const value = args[i + 1];
|
|
52
|
+
if (value === "overwrite" || value === "backup" || value === "skip" || value === "cancel") {
|
|
53
|
+
result.conflictPolicy = value;
|
|
54
|
+
}
|
|
55
|
+
i += 1;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (arg === "--agent") {
|
|
59
|
+
result.agent = args[i + 1];
|
|
60
|
+
i += 1;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (arg === "--help" || arg === "-h") {
|
|
64
|
+
result.help = true;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
function printHelp() {
|
|
71
|
+
const lines = [
|
|
72
|
+
"agentconfig <command> [options]",
|
|
73
|
+
"",
|
|
74
|
+
"Commands:",
|
|
75
|
+
" init Create agentconfig.yml in source root",
|
|
76
|
+
" sync Sync configs to agents",
|
|
77
|
+
" status Show drift status",
|
|
78
|
+
" doctor Validate config and paths",
|
|
79
|
+
" list-agents List supported agents",
|
|
80
|
+
"",
|
|
81
|
+
"Options:",
|
|
82
|
+
" --project <path> Use project mode and root",
|
|
83
|
+
" --dry-run Show actions without writing",
|
|
84
|
+
" --link Force symlink mode",
|
|
85
|
+
" --copy Force copy mode",
|
|
86
|
+
" --force Overwrite unmanaged targets (alias for --on-conflict overwrite)",
|
|
87
|
+
" --on-conflict <policy> overwrite | backup | skip | cancel",
|
|
88
|
+
" --agent <name> Filter to one agent",
|
|
89
|
+
" -h, --help Show help"
|
|
90
|
+
];
|
|
91
|
+
console.log(lines.join("\n"));
|
|
92
|
+
}
|
|
93
|
+
function ensureSourceRoot() {
|
|
94
|
+
const envRoot = process.env.AGENTCONFIG_HOME;
|
|
95
|
+
if (envRoot && envRoot.length > 0) {
|
|
96
|
+
return (0, paths_1.resolvePath)(envRoot, process.env);
|
|
97
|
+
}
|
|
98
|
+
return (0, paths_1.resolvePath)("~/.agentconfig", process.env);
|
|
99
|
+
}
|
|
100
|
+
async function run() {
|
|
101
|
+
const args = parseArgs(process.argv);
|
|
102
|
+
if (args.help || !args.command) {
|
|
103
|
+
printHelp();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const sourceRoot = ensureSourceRoot();
|
|
107
|
+
switch (args.command) {
|
|
108
|
+
case "init": {
|
|
109
|
+
const configPath = (0, config_1.getConfigPath)(sourceRoot);
|
|
110
|
+
const exists = await promises_1.default
|
|
111
|
+
.access(configPath)
|
|
112
|
+
.then(() => true)
|
|
113
|
+
.catch(() => false);
|
|
114
|
+
if (exists) {
|
|
115
|
+
throw new errors_1.AgentConfigError(`Config already exists: ${configPath}`, errors_1.ExitCodes.Conflict);
|
|
116
|
+
}
|
|
117
|
+
await (0, config_1.writeConfig)(sourceRoot, (0, templates_1.createDefaultConfig)());
|
|
118
|
+
console.log(`Created ${configPath}`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
case "sync": {
|
|
122
|
+
const config = await (0, config_1.readConfig)(sourceRoot);
|
|
123
|
+
const mode = args.project ? "project" : "global";
|
|
124
|
+
const projectRoot = args.project ? (0, paths_1.resolvePath)(args.project, process.env) : null;
|
|
125
|
+
const linkMode = args.mode ?? config.defaults.mode;
|
|
126
|
+
const result = await (0, sync_1.syncConfigs)({
|
|
127
|
+
config,
|
|
128
|
+
sourceRoot,
|
|
129
|
+
mode,
|
|
130
|
+
projectRoot,
|
|
131
|
+
linkMode,
|
|
132
|
+
dryRun: args.dryRun,
|
|
133
|
+
force: args.force,
|
|
134
|
+
conflictPolicy: args.conflictPolicy ?? (args.force ? "overwrite" : undefined),
|
|
135
|
+
agentFilter: args.agent
|
|
136
|
+
});
|
|
137
|
+
result.warnings.forEach((warning) => console.warn(warning));
|
|
138
|
+
console.log(`Planned: ${result.planned.length}`);
|
|
139
|
+
console.log(`Updated: ${result.updated.length}`);
|
|
140
|
+
console.log(`Skipped: ${result.skipped.length}`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
case "status": {
|
|
144
|
+
const entries = await (0, status_1.getStatus)(sourceRoot);
|
|
145
|
+
if (entries.length === 0) {
|
|
146
|
+
console.log("No sync state found.");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const drifted = entries.filter((entry) => entry.status !== "ok");
|
|
150
|
+
entries.forEach((entry) => {
|
|
151
|
+
const suffix = entry.reason ? ` (${entry.reason})` : "";
|
|
152
|
+
console.log(`${entry.status}: ${entry.path}${suffix}`);
|
|
153
|
+
});
|
|
154
|
+
if (drifted.length > 0) {
|
|
155
|
+
process.exitCode = errors_1.ExitCodes.Validation;
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
case "doctor": {
|
|
160
|
+
const config = await (0, config_1.readConfig)(sourceRoot);
|
|
161
|
+
if (!config.defaults || !config.agents) {
|
|
162
|
+
throw new errors_1.AgentConfigError("Config missing defaults or agents", errors_1.ExitCodes.Validation);
|
|
163
|
+
}
|
|
164
|
+
console.log("Config OK");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
case "list-agents": {
|
|
168
|
+
const config = await (0, config_1.readConfig)(sourceRoot);
|
|
169
|
+
const agents = Object.entries(config.agents);
|
|
170
|
+
agents.forEach(([name, entry]) => {
|
|
171
|
+
console.log(`${name}: ${entry.displayName}`);
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
default:
|
|
176
|
+
throw new errors_1.AgentConfigError(`Unknown command: ${String(args.command)}`, errors_1.ExitCodes.Usage);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
run().catch((error) => {
|
|
180
|
+
if (error instanceof errors_1.AgentConfigError) {
|
|
181
|
+
console.error(error.message);
|
|
182
|
+
process.exit(error.code);
|
|
183
|
+
}
|
|
184
|
+
if (error instanceof Error) {
|
|
185
|
+
console.error(error.message);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
console.error("Unexpected error");
|
|
189
|
+
}
|
|
190
|
+
process.exit(errors_1.ExitCodes.Failure);
|
|
191
|
+
});
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEFAULT_CONFIG_FILE = void 0;
|
|
7
|
+
exports.getConfigPath = getConfigPath;
|
|
8
|
+
exports.readConfig = readConfig;
|
|
9
|
+
exports.writeConfig = writeConfig;
|
|
10
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const yaml_1 = __importDefault(require("yaml"));
|
|
13
|
+
const errors_1 = require("./errors");
|
|
14
|
+
exports.DEFAULT_CONFIG_FILE = "agentconfig.yml";
|
|
15
|
+
function getConfigPath(root) {
|
|
16
|
+
return path_1.default.join(root, exports.DEFAULT_CONFIG_FILE);
|
|
17
|
+
}
|
|
18
|
+
function isErrnoException(error) {
|
|
19
|
+
return typeof error === "object" && error !== null && "code" in error;
|
|
20
|
+
}
|
|
21
|
+
function formatYamlError(error, configPath) {
|
|
22
|
+
if (error instanceof Error) {
|
|
23
|
+
const linePos = error.linePos?.[0];
|
|
24
|
+
const location = linePos ? ` (line ${linePos.line}, col ${linePos.col})` : "";
|
|
25
|
+
return new errors_1.AgentConfigError(`Invalid YAML in ${configPath}${location}: ${error.message}`, errors_1.ExitCodes.Validation);
|
|
26
|
+
}
|
|
27
|
+
return new errors_1.AgentConfigError(`Invalid YAML in ${configPath}`, errors_1.ExitCodes.Validation);
|
|
28
|
+
}
|
|
29
|
+
async function readConfig(root) {
|
|
30
|
+
const configPath = getConfigPath(root);
|
|
31
|
+
let raw;
|
|
32
|
+
try {
|
|
33
|
+
raw = await promises_1.default.readFile(configPath, "utf8");
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
37
|
+
throw new errors_1.AgentConfigError(`Missing config: ${configPath}`, errors_1.ExitCodes.Validation);
|
|
38
|
+
}
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
let parsed;
|
|
42
|
+
try {
|
|
43
|
+
parsed = yaml_1.default.parse(raw, { prettyErrors: true });
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw formatYamlError(error, configPath);
|
|
47
|
+
}
|
|
48
|
+
if (!parsed || typeof parsed !== "object") {
|
|
49
|
+
throw new errors_1.AgentConfigError(`Invalid config format in ${configPath}`, errors_1.ExitCodes.Validation);
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
async function writeConfig(root, config) {
|
|
54
|
+
const configPath = getConfigPath(root);
|
|
55
|
+
const contents = yaml_1.default.stringify(config);
|
|
56
|
+
await promises_1.default.mkdir(root, { recursive: true });
|
|
57
|
+
await promises_1.default.writeFile(configPath, contents, "utf8");
|
|
58
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExitCodes = exports.AgentConfigError = void 0;
|
|
4
|
+
class AgentConfigError extends Error {
|
|
5
|
+
constructor(message, code) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.AgentConfigError = AgentConfigError;
|
|
11
|
+
exports.ExitCodes = {
|
|
12
|
+
Success: 0,
|
|
13
|
+
Failure: 1,
|
|
14
|
+
Usage: 2,
|
|
15
|
+
Validation: 3,
|
|
16
|
+
Conflict: 4,
|
|
17
|
+
Filesystem: 5
|
|
18
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ensureDir = ensureDir;
|
|
7
|
+
exports.fileExists = fileExists;
|
|
8
|
+
exports.readLinkTarget = readLinkTarget;
|
|
9
|
+
exports.hashFile = hashFile;
|
|
10
|
+
exports.listEntries = listEntries;
|
|
11
|
+
exports.copyFileOrDir = copyFileOrDir;
|
|
12
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
13
|
+
const fs_1 = require("fs");
|
|
14
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
15
|
+
const path_1 = __importDefault(require("path"));
|
|
16
|
+
function isErrnoException(error) {
|
|
17
|
+
return typeof error === "object" && error !== null && "code" in error;
|
|
18
|
+
}
|
|
19
|
+
async function ensureDir(target) {
|
|
20
|
+
await promises_1.default.mkdir(target, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
async function fileExists(target) {
|
|
23
|
+
try {
|
|
24
|
+
await promises_1.default.lstat(target);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function readLinkTarget(target) {
|
|
35
|
+
try {
|
|
36
|
+
return await promises_1.default.readlink(target);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function hashFile(target) {
|
|
46
|
+
const hash = crypto_1.default.createHash("sha256");
|
|
47
|
+
await new Promise((resolve, reject) => {
|
|
48
|
+
const stream = (0, fs_1.createReadStream)(target);
|
|
49
|
+
stream.on("data", (chunk) => {
|
|
50
|
+
hash.update(chunk);
|
|
51
|
+
});
|
|
52
|
+
stream.on("error", (error) => {
|
|
53
|
+
reject(error);
|
|
54
|
+
});
|
|
55
|
+
stream.on("end", () => {
|
|
56
|
+
resolve();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
return hash.digest("hex");
|
|
60
|
+
}
|
|
61
|
+
async function listEntries(target) {
|
|
62
|
+
return await promises_1.default.readdir(target);
|
|
63
|
+
}
|
|
64
|
+
async function copyFileOrDir(source, target) {
|
|
65
|
+
const stat = await promises_1.default.lstat(source);
|
|
66
|
+
if (stat.isDirectory()) {
|
|
67
|
+
await ensureDir(target);
|
|
68
|
+
const entries = await promises_1.default.readdir(source);
|
|
69
|
+
await Promise.all(entries.map(async (entry) => {
|
|
70
|
+
const src = path_1.default.join(source, entry);
|
|
71
|
+
const dest = path_1.default.join(target, entry);
|
|
72
|
+
await copyFileOrDir(src, dest);
|
|
73
|
+
}));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
await ensureDir(path_1.default.dirname(target));
|
|
77
|
+
await promises_1.default.copyFile(source, target);
|
|
78
|
+
}
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.expandHome = expandHome;
|
|
7
|
+
exports.expandEnv = expandEnv;
|
|
8
|
+
exports.resolvePath = resolvePath;
|
|
9
|
+
exports.resolveFromRoot = resolveFromRoot;
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
function expandHome(inputPath) {
|
|
13
|
+
if (inputPath === "~") {
|
|
14
|
+
return os_1.default.homedir();
|
|
15
|
+
}
|
|
16
|
+
if (inputPath.startsWith("~/")) {
|
|
17
|
+
return path_1.default.join(os_1.default.homedir(), inputPath.slice(2));
|
|
18
|
+
}
|
|
19
|
+
return inputPath;
|
|
20
|
+
}
|
|
21
|
+
function expandEnv(inputPath, env) {
|
|
22
|
+
const resolved = inputPath.replace(/\$\{([A-Z0-9_]+)(:-([^}]*))?\}/gi, (_match, varName, _fallbackGroup, fallback) => {
|
|
23
|
+
const value = env[varName];
|
|
24
|
+
if (value && value.length > 0) {
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
if (fallback !== undefined) {
|
|
28
|
+
return fallback;
|
|
29
|
+
}
|
|
30
|
+
return "";
|
|
31
|
+
});
|
|
32
|
+
return resolved;
|
|
33
|
+
}
|
|
34
|
+
function resolvePath(inputPath, env) {
|
|
35
|
+
const expanded = expandHome(expandEnv(inputPath, env));
|
|
36
|
+
if (path_1.default.isAbsolute(expanded)) {
|
|
37
|
+
return expanded;
|
|
38
|
+
}
|
|
39
|
+
return path_1.default.resolve(expanded);
|
|
40
|
+
}
|
|
41
|
+
function resolveFromRoot(root, relativePath) {
|
|
42
|
+
if (path_1.default.isAbsolute(relativePath)) {
|
|
43
|
+
return relativePath;
|
|
44
|
+
}
|
|
45
|
+
return path_1.default.join(root, relativePath);
|
|
46
|
+
}
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getStatePath = getStatePath;
|
|
7
|
+
exports.readState = readState;
|
|
8
|
+
exports.writeState = writeState;
|
|
9
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const errors_1 = require("./errors");
|
|
12
|
+
const STATE_FILE = ".sync-state.json";
|
|
13
|
+
function getStatePath(root) {
|
|
14
|
+
return path_1.default.join(root, STATE_FILE);
|
|
15
|
+
}
|
|
16
|
+
function isErrnoException(error) {
|
|
17
|
+
return typeof error === "object" && error !== null && "code" in error;
|
|
18
|
+
}
|
|
19
|
+
async function readState(root) {
|
|
20
|
+
const statePath = getStatePath(root);
|
|
21
|
+
try {
|
|
22
|
+
const raw = await promises_1.default.readFile(statePath, "utf8");
|
|
23
|
+
return JSON.parse(raw);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
if (error instanceof SyntaxError) {
|
|
30
|
+
throw new errors_1.AgentConfigError(`Invalid JSON in ${statePath}: ${error.message}`, errors_1.ExitCodes.Validation);
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function writeState(root, state) {
|
|
36
|
+
const statePath = getStatePath(root);
|
|
37
|
+
const contents = JSON.stringify(state, null, 2);
|
|
38
|
+
await promises_1.default.mkdir(root, { recursive: true });
|
|
39
|
+
await promises_1.default.writeFile(statePath, contents, "utf8");
|
|
40
|
+
}
|
package/dist/status.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getStatus = getStatus;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const state_1 = require("./state");
|
|
9
|
+
const filesystem_1 = require("./filesystem");
|
|
10
|
+
async function getStatus(stateRoot) {
|
|
11
|
+
const state = await (0, state_1.readState)(stateRoot);
|
|
12
|
+
if (!state) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
const results = [];
|
|
16
|
+
for (const record of Object.values(state.files)) {
|
|
17
|
+
const exists = await (0, filesystem_1.fileExists)(record.path);
|
|
18
|
+
if (!exists) {
|
|
19
|
+
results.push({ path: record.path, status: "missing", reason: "target missing" });
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
let stat = null;
|
|
23
|
+
if (record.mode === "link" || record.mode === "auto") {
|
|
24
|
+
stat = await promises_1.default.lstat(record.path);
|
|
25
|
+
}
|
|
26
|
+
const mode = record.mode === "auto" ? (stat?.isSymbolicLink() ? "link" : "copy") : record.mode;
|
|
27
|
+
if (mode === "link") {
|
|
28
|
+
if (!stat || !stat.isSymbolicLink()) {
|
|
29
|
+
results.push({ path: record.path, status: "drifted", reason: "link target changed" });
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (!record.linkTarget) {
|
|
33
|
+
results.push({ path: record.path, status: "drifted", reason: "link target changed" });
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const linkTarget = await (0, filesystem_1.readLinkTarget)(record.path);
|
|
37
|
+
if (linkTarget !== record.linkTarget) {
|
|
38
|
+
results.push({ path: record.path, status: "drifted", reason: "link target changed" });
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
results.push({ path: record.path, status: "ok" });
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (!record.hash) {
|
|
45
|
+
results.push({ path: record.path, status: "drifted", reason: "content changed" });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const currentHash = await (0, filesystem_1.hashFile)(record.path);
|
|
49
|
+
if (currentHash !== record.hash) {
|
|
50
|
+
results.push({ path: record.path, status: "drifted", reason: "content changed" });
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
results.push({ path: record.path, status: "ok" });
|
|
54
|
+
}
|
|
55
|
+
return results;
|
|
56
|
+
}
|
package/dist/sync.js
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.syncConfigs = syncConfigs;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const promises_2 = require("readline/promises");
|
|
11
|
+
const errors_1 = require("./errors");
|
|
12
|
+
const filesystem_1 = require("./filesystem");
|
|
13
|
+
const paths_1 = require("./paths");
|
|
14
|
+
const state_1 = require("./state");
|
|
15
|
+
async function syncConfigs(options) {
|
|
16
|
+
const { config, sourceRoot, mode, projectRoot, linkMode, dryRun, force, conflictPolicy, agentFilter } = options;
|
|
17
|
+
const resolvedMode = await resolveSyncMode(linkMode);
|
|
18
|
+
const resolvedMappings = resolveMappings(config, sourceRoot, mode, projectRoot, resolvedMode, agentFilter);
|
|
19
|
+
const warnings = [];
|
|
20
|
+
const stateRoot = sourceRoot;
|
|
21
|
+
const existingState = await (0, state_1.readState)(stateRoot);
|
|
22
|
+
const updatedMappings = [];
|
|
23
|
+
const skippedMappings = [];
|
|
24
|
+
let conflictState = { policy: conflictPolicy ?? null, canAsk: true };
|
|
25
|
+
for (const mapping of resolvedMappings) {
|
|
26
|
+
const targetExists = await (0, filesystem_1.fileExists)(mapping.target);
|
|
27
|
+
if (targetExists && !force && !isManaged(mapping.target, existingState)) {
|
|
28
|
+
const decision = await resolveConflictPolicy(mapping.target, conflictState);
|
|
29
|
+
conflictState = decision.state;
|
|
30
|
+
if (decision.action === "cancel") {
|
|
31
|
+
throw new errors_1.AgentConfigError("Sync cancelled", errors_1.ExitCodes.Conflict);
|
|
32
|
+
}
|
|
33
|
+
if (decision.action === "skip") {
|
|
34
|
+
warnings.push(`Skipping unmanaged target: ${mapping.target}`);
|
|
35
|
+
skippedMappings.push(mapping);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (decision.action === "backup") {
|
|
39
|
+
if (!dryRun) {
|
|
40
|
+
await backupTarget(mapping.target, sourceRoot);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (dryRun) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
await applyMapping(mapping, warnings);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error instanceof errors_1.AgentConfigError) {
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
throw new errors_1.AgentConfigError(`Failed to sync ${mapping.target}`, errors_1.ExitCodes.Filesystem);
|
|
55
|
+
}
|
|
56
|
+
updatedMappings.push(mapping);
|
|
57
|
+
}
|
|
58
|
+
if (!dryRun) {
|
|
59
|
+
const newState = await buildState(stateRoot, mode, projectRoot, updatedMappings, existingState);
|
|
60
|
+
await (0, state_1.writeState)(stateRoot, newState);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
planned: resolvedMappings,
|
|
64
|
+
updated: updatedMappings,
|
|
65
|
+
skipped: skippedMappings,
|
|
66
|
+
warnings
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function resolveConflictPolicy(target, state) {
|
|
70
|
+
if (state.policy) {
|
|
71
|
+
return { action: state.policy, state };
|
|
72
|
+
}
|
|
73
|
+
if (!state.canAsk || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
74
|
+
return { action: "skip", state: { ...state, policy: "skip" } };
|
|
75
|
+
}
|
|
76
|
+
const choice = await promptConflictAction(target);
|
|
77
|
+
const resolved = choice.applyToAll ? choice.action : null;
|
|
78
|
+
return { action: choice.action, state: { policy: resolved, canAsk: !choice.applyToAll } };
|
|
79
|
+
}
|
|
80
|
+
async function promptConflictAction(target) {
|
|
81
|
+
const promptLines = [
|
|
82
|
+
`Config already exists at ${target}. Choose action:`,
|
|
83
|
+
"1) Overwrite",
|
|
84
|
+
"2) Backup then overwrite",
|
|
85
|
+
"3) Skip",
|
|
86
|
+
"4) Cancel"
|
|
87
|
+
];
|
|
88
|
+
process.stdout.write(`${promptLines.join("\n")}\n> `);
|
|
89
|
+
const readline = (0, promises_2.createInterface)({ input: process.stdin, output: process.stdout });
|
|
90
|
+
const choice = await readline.question("");
|
|
91
|
+
const applyToAllAnswer = await readline.question("Apply to all conflicts? (y/N) ");
|
|
92
|
+
readline.close();
|
|
93
|
+
const applyToAll = /^(y|yes)$/i.test(applyToAllAnswer.trim());
|
|
94
|
+
const selection = Number.parseInt(choice.trim(), 10);
|
|
95
|
+
switch (selection) {
|
|
96
|
+
case 1:
|
|
97
|
+
return { action: "overwrite", applyToAll };
|
|
98
|
+
case 2:
|
|
99
|
+
return { action: "backup", applyToAll };
|
|
100
|
+
case 4:
|
|
101
|
+
return { action: "cancel", applyToAll };
|
|
102
|
+
case 3:
|
|
103
|
+
default:
|
|
104
|
+
return { action: "skip", applyToAll };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function backupTarget(target, sourceRoot) {
|
|
108
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
109
|
+
const backupRoot = path_1.default.join(sourceRoot, "backup", timestamp);
|
|
110
|
+
const relativeTarget = target.replace(/^[/\\]+/, "");
|
|
111
|
+
const destination = path_1.default.join(backupRoot, relativeTarget);
|
|
112
|
+
await (0, filesystem_1.ensureDir)(path_1.default.dirname(destination));
|
|
113
|
+
await (0, filesystem_1.copyFileOrDir)(target, destination);
|
|
114
|
+
}
|
|
115
|
+
function resolveMappings(config, sourceRoot, mode, projectRoot, linkMode, agentFilter) {
|
|
116
|
+
const mappings = [];
|
|
117
|
+
const agents = Object.entries(config.agents);
|
|
118
|
+
const profileFiles = getProfileFiles(config);
|
|
119
|
+
for (const [agent, agentConfig] of agents) {
|
|
120
|
+
if (agentFilter && agentFilter !== agent) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const scopeConfig = mode === "global" ? agentConfig.global : agentConfig.project;
|
|
124
|
+
if (!scopeConfig) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
let root = scopeConfig.root;
|
|
128
|
+
if (mode === "project") {
|
|
129
|
+
if (!projectRoot) {
|
|
130
|
+
throw new errors_1.AgentConfigError("Project root is required for project mode", errors_1.ExitCodes.Validation);
|
|
131
|
+
}
|
|
132
|
+
root = root.replace("<project-root>", projectRoot);
|
|
133
|
+
}
|
|
134
|
+
const resolvedRoot = (0, paths_1.resolvePath)(root, process.env);
|
|
135
|
+
for (const mapping of [...scopeConfig.files, ...profileFiles]) {
|
|
136
|
+
const source = (0, paths_1.resolveFromRoot)(sourceRoot, mapping.source);
|
|
137
|
+
const target = (0, paths_1.resolveFromRoot)(resolvedRoot, mapping.target);
|
|
138
|
+
mappings.push({
|
|
139
|
+
agent,
|
|
140
|
+
source,
|
|
141
|
+
target,
|
|
142
|
+
mode: linkMode
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return mappings;
|
|
147
|
+
}
|
|
148
|
+
function getProfileFiles(config) {
|
|
149
|
+
const profileName = config.defaults.profile;
|
|
150
|
+
const profile = config.profiles?.[profileName];
|
|
151
|
+
return profile?.files ?? [];
|
|
152
|
+
}
|
|
153
|
+
async function resolveSyncMode(linkMode) {
|
|
154
|
+
if (linkMode !== "auto") {
|
|
155
|
+
return linkMode;
|
|
156
|
+
}
|
|
157
|
+
const canLink = await canCreateSymlink();
|
|
158
|
+
return canLink ? "link" : "copy";
|
|
159
|
+
}
|
|
160
|
+
async function canCreateSymlink() {
|
|
161
|
+
const tempRoot = await promises_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "agentconfig-"));
|
|
162
|
+
const source = path_1.default.join(tempRoot, "source");
|
|
163
|
+
const target = path_1.default.join(tempRoot, "target");
|
|
164
|
+
try {
|
|
165
|
+
await promises_1.default.writeFile(source, "test", "utf8");
|
|
166
|
+
await promises_1.default.symlink(source, target);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
catch (_error) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
await promises_1.default.rm(tempRoot, { recursive: true, force: true });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function applyMapping(mapping, warnings) {
|
|
177
|
+
const sourceExists = await (0, filesystem_1.fileExists)(mapping.source);
|
|
178
|
+
if (!sourceExists) {
|
|
179
|
+
throw new errors_1.AgentConfigError(`Missing source: ${mapping.source}`, errors_1.ExitCodes.Validation);
|
|
180
|
+
}
|
|
181
|
+
if (mapping.mode === "copy") {
|
|
182
|
+
await applyCopyMapping(mapping);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
await (0, filesystem_1.ensureDir)(path_1.default.dirname(mapping.target));
|
|
186
|
+
try {
|
|
187
|
+
const sourceStat = await promises_1.default.lstat(mapping.source);
|
|
188
|
+
const existing = await getTargetInfo(mapping.target);
|
|
189
|
+
if (existing) {
|
|
190
|
+
if (existing.isSymlink) {
|
|
191
|
+
if (existing.linkTarget === mapping.source) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
await promises_1.default.unlink(mapping.target);
|
|
195
|
+
}
|
|
196
|
+
else if (existing.isDirectory) {
|
|
197
|
+
const removed = await removeEmptyDirectory(mapping.target);
|
|
198
|
+
if (!removed) {
|
|
199
|
+
throw new errors_1.AgentConfigError(`Refusing to replace non-empty directory: ${mapping.target}`, errors_1.ExitCodes.Filesystem);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
await promises_1.default.unlink(mapping.target);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
await promises_1.default.symlink(mapping.source, mapping.target, sourceStat.isDirectory() ? "dir" : "file");
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
if (error instanceof errors_1.AgentConfigError) {
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
warnings.push(`Symlink failed for ${mapping.target}; falling back to copy`);
|
|
213
|
+
await applyCopyMapping(mapping);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function applyCopyMapping(mapping) {
|
|
217
|
+
const sourceStat = await promises_1.default.lstat(mapping.source);
|
|
218
|
+
const existing = await getTargetInfo(mapping.target);
|
|
219
|
+
if (sourceStat.isDirectory()) {
|
|
220
|
+
if (existing && !existing.isDirectory) {
|
|
221
|
+
await promises_1.default.unlink(mapping.target);
|
|
222
|
+
}
|
|
223
|
+
await (0, filesystem_1.copyFileOrDir)(mapping.source, mapping.target);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (existing?.isDirectory) {
|
|
227
|
+
const removed = await removeEmptyDirectory(mapping.target);
|
|
228
|
+
if (!removed) {
|
|
229
|
+
throw new errors_1.AgentConfigError(`Refusing to replace non-empty directory: ${mapping.target}`, errors_1.ExitCodes.Filesystem);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else if (existing) {
|
|
233
|
+
await promises_1.default.unlink(mapping.target);
|
|
234
|
+
}
|
|
235
|
+
await (0, filesystem_1.copyFileOrDir)(mapping.source, mapping.target);
|
|
236
|
+
}
|
|
237
|
+
async function getTargetInfo(target) {
|
|
238
|
+
try {
|
|
239
|
+
const stat = await promises_1.default.lstat(target);
|
|
240
|
+
if (stat.isSymbolicLink()) {
|
|
241
|
+
return {
|
|
242
|
+
isDirectory: false,
|
|
243
|
+
isSymlink: true,
|
|
244
|
+
linkTarget: await (0, filesystem_1.readLinkTarget)(target)
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
isDirectory: stat.isDirectory(),
|
|
249
|
+
isSymlink: false,
|
|
250
|
+
linkTarget: null
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
catch (_error) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async function removeEmptyDirectory(target) {
|
|
258
|
+
const entries = await promises_1.default.readdir(target);
|
|
259
|
+
if (entries.length > 0) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
await promises_1.default.rmdir(target);
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
function isManaged(target, state) {
|
|
266
|
+
if (!state) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
return Boolean(state.files[target]);
|
|
270
|
+
}
|
|
271
|
+
async function buildState(stateRoot, mode, projectRoot, mappings, previousState) {
|
|
272
|
+
const files = { ...(previousState?.files ?? {}) };
|
|
273
|
+
for (const mapping of mappings) {
|
|
274
|
+
const stat = await promises_1.default.lstat(mapping.target);
|
|
275
|
+
const isLink = stat.isSymbolicLink();
|
|
276
|
+
const modeValue = isLink ? "link" : "copy";
|
|
277
|
+
const record = {
|
|
278
|
+
path: mapping.target,
|
|
279
|
+
source: mapping.source,
|
|
280
|
+
agent: mapping.agent,
|
|
281
|
+
mode: modeValue,
|
|
282
|
+
size: stat.size,
|
|
283
|
+
mtimeMs: stat.mtimeMs,
|
|
284
|
+
hash: modeValue === "copy" ? await (0, filesystem_1.hashFile)(mapping.target) : null,
|
|
285
|
+
linkTarget: modeValue === "link" ? await (0, filesystem_1.readLinkTarget)(mapping.target) : null
|
|
286
|
+
};
|
|
287
|
+
files[mapping.target] = record;
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
version: 1,
|
|
291
|
+
updatedAt: new Date().toISOString(),
|
|
292
|
+
mode,
|
|
293
|
+
projectRoot,
|
|
294
|
+
files
|
|
295
|
+
};
|
|
296
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDefaultConfig = createDefaultConfig;
|
|
4
|
+
function createDefaultConfig() {
|
|
5
|
+
return {
|
|
6
|
+
version: 1,
|
|
7
|
+
defaults: {
|
|
8
|
+
mode: "auto",
|
|
9
|
+
profile: "default",
|
|
10
|
+
sourceRoot: "${AGENTCONFIG_HOME:-~/.agentconfig}"
|
|
11
|
+
},
|
|
12
|
+
agents: {
|
|
13
|
+
claude: {
|
|
14
|
+
displayName: "Claude Code",
|
|
15
|
+
global: {
|
|
16
|
+
root: "~/.claude",
|
|
17
|
+
files: [
|
|
18
|
+
{ source: "agent.md", target: "CLAUDE.md" },
|
|
19
|
+
{ source: "skills/", target: "skills/" }
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
project: {
|
|
23
|
+
root: "<project-root>",
|
|
24
|
+
files: [
|
|
25
|
+
{ source: "agent.md", target: "CLAUDE.md" },
|
|
26
|
+
{ source: "rules/", target: ".claude/rules/" },
|
|
27
|
+
{ source: "skills/", target: ".claude/skills/" }
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
codex: {
|
|
32
|
+
displayName: "Codex CLI",
|
|
33
|
+
global: {
|
|
34
|
+
root: "${CODEX_HOME:-~/.codex}",
|
|
35
|
+
files: [
|
|
36
|
+
{ source: "agent.md", target: "AGENTS.md" },
|
|
37
|
+
{ source: "skills/", target: "skills/" }
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
project: {
|
|
41
|
+
root: "<project-root>",
|
|
42
|
+
files: [
|
|
43
|
+
{ source: "agent.md", target: "AGENTS.md" },
|
|
44
|
+
{ source: "skills/", target: ".codex/skills/" }
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
cursor: {
|
|
49
|
+
displayName: "Cursor",
|
|
50
|
+
global: {
|
|
51
|
+
root: "~/.cursor",
|
|
52
|
+
files: [{ source: "skills/", target: "skills/" }]
|
|
53
|
+
},
|
|
54
|
+
project: {
|
|
55
|
+
root: "<project-root>",
|
|
56
|
+
files: [
|
|
57
|
+
{ source: "agent.md", target: "AGENTS.md" },
|
|
58
|
+
{ source: "rules/", target: ".cursor/rules/" },
|
|
59
|
+
{ source: "skills/", target: ".cursor/skills/" }
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
opencode: {
|
|
64
|
+
displayName: "OpenCode",
|
|
65
|
+
global: {
|
|
66
|
+
root: "~/.config/opencode",
|
|
67
|
+
files: [
|
|
68
|
+
{ source: "agent.md", target: "AGENTS.md" },
|
|
69
|
+
{ source: "rules/", target: "rules/" },
|
|
70
|
+
{ source: "skills/", target: "skills/" }
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
project: {
|
|
74
|
+
root: "<project-root>",
|
|
75
|
+
files: [
|
|
76
|
+
{ source: "agent.md", target: "AGENTS.md" },
|
|
77
|
+
{ source: "rules/", target: ".opencode/rules/" },
|
|
78
|
+
{ source: "skills/", target: ".opencode/skills/" }
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
profiles: {
|
|
84
|
+
default: {
|
|
85
|
+
files: []
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shruubi/agentconfig",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Sync a single agent config source to multiple coding agents",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"repository":
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/damonswayn/agentconfig.git"
|
|
9
|
+
},
|
|
7
10
|
"type": "commonjs",
|
|
8
11
|
"main": "dist/cli.js",
|
|
9
12
|
"files": [
|