@michaelhartmayer/agentctl 1.1.0 → 1.1.3
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/dist/ctl.js +171 -227
- package/dist/effects.js +61 -0
- package/dist/fs-utils.js +26 -19
- package/dist/index.js +190 -202
- package/dist/logic/ctl.js +172 -0
- package/dist/logic/index.js +64 -0
- package/dist/logic/install.js +57 -0
- package/dist/logic/manifest.js +8 -0
- package/dist/logic/resolve.js +73 -0
- package/dist/logic/skills.js +20 -0
- package/dist/logic/utils.js +31 -0
- package/dist/manifest.js +17 -1
- package/dist/resolve.js +30 -76
- package/dist/skills.js +11 -23
- package/package.json +1 -1
- package/dist/package.json +0 -60
- package/dist/src/ctl.js +0 -316
- package/dist/src/fs-utils.js +0 -35
- package/dist/src/index.js +0 -351
- package/dist/src/manifest.js +0 -19
- package/dist/src/resolve.js +0 -112
- package/dist/src/skills.js +0 -39
package/dist/ctl.js
CHANGED
|
@@ -8,206 +8,207 @@ exports.alias = alias;
|
|
|
8
8
|
exports.group = group;
|
|
9
9
|
exports.pushGlobal = pushGlobal;
|
|
10
10
|
exports.pullLocal = pullLocal;
|
|
11
|
-
exports.installSkill = installSkill;
|
|
12
11
|
exports.rm = rm;
|
|
13
12
|
exports.mv = mv;
|
|
14
13
|
exports.inspect = inspect;
|
|
14
|
+
exports.installSkill = installSkill;
|
|
15
|
+
exports.install = install;
|
|
15
16
|
exports.list = list;
|
|
16
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
17
17
|
const path_1 = __importDefault(require("path"));
|
|
18
|
-
const
|
|
18
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
19
|
+
const os_1 = __importDefault(require("os"));
|
|
19
20
|
const fs_utils_1 = require("./fs-utils");
|
|
20
21
|
const manifest_1 = require("./manifest");
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
const resolve_1 = require("./resolve");
|
|
23
|
+
const effects_1 = require("./effects");
|
|
24
|
+
const ctl_1 = require("./logic/ctl");
|
|
25
|
+
const install_1 = require("./logic/install");
|
|
26
|
+
async function getCappedAncestor(dir, baseDir) {
|
|
27
|
+
let current = path_1.default.dirname(dir);
|
|
28
|
+
while (current.length >= baseDir.length && current !== path_1.default.dirname(baseDir)) {
|
|
29
|
+
const mPath = path_1.default.join(current, 'manifest.json');
|
|
30
|
+
if (await fs_extra_1.default.pathExists(mPath)) {
|
|
31
|
+
try {
|
|
32
|
+
const m = await fs_extra_1.default.readJson(mPath);
|
|
33
|
+
if ((0, manifest_1.isCappedManifest)(m)) {
|
|
34
|
+
return { path: current, relPath: path_1.default.relative(baseDir, current) };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
/* ignore */
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
current = path_1.default.dirname(current);
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
async function getContext(options) {
|
|
46
|
+
const cwd = options.cwd || process.cwd();
|
|
47
|
+
return {
|
|
48
|
+
cwd: path_1.default.resolve(cwd),
|
|
49
|
+
platform: process.platform,
|
|
50
|
+
localRoot: (0, fs_utils_1.findLocalRoot)(cwd),
|
|
51
|
+
globalRoot: options.globalDir || (0, fs_utils_1.getGlobalRoot)(),
|
|
52
|
+
homedir: process.env.HOME || process.env.USERPROFILE || os_1.default.homedir()
|
|
36
53
|
};
|
|
37
|
-
|
|
38
|
-
|
|
54
|
+
}
|
|
55
|
+
async function scaffold(args, options = {}) {
|
|
56
|
+
const ctx = await getContext(options);
|
|
57
|
+
const localRoot = ctx.localRoot || ctx.cwd;
|
|
58
|
+
const agentctlDir = path_1.default.join(localRoot, '.agentctl');
|
|
59
|
+
const targetDir = path_1.default.join(agentctlDir, args.join(path_1.default.sep));
|
|
60
|
+
const exists = await fs_extra_1.default.pathExists(targetDir);
|
|
61
|
+
const cappedAncestor = await getCappedAncestor(targetDir, agentctlDir);
|
|
62
|
+
const { effects } = ctl_1.Logic.planScaffold(args, ctx, { exists, cappedAncestor: cappedAncestor || undefined, type: 'scaffold' });
|
|
63
|
+
await (0, effects_1.execute)(effects);
|
|
39
64
|
}
|
|
40
65
|
async function alias(args, target, options = {}) {
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
66
|
+
const ctx = await getContext(options);
|
|
67
|
+
const localRoot = ctx.localRoot || ctx.cwd;
|
|
68
|
+
const agentctlDir = path_1.default.join(localRoot, '.agentctl');
|
|
69
|
+
const targetDir = path_1.default.join(agentctlDir, args.join(path_1.default.sep));
|
|
70
|
+
const exists = await fs_extra_1.default.pathExists(targetDir);
|
|
71
|
+
const cappedAncestor = await getCappedAncestor(targetDir, agentctlDir);
|
|
72
|
+
const { effects } = ctl_1.Logic.planScaffold(args, ctx, {
|
|
73
|
+
exists,
|
|
74
|
+
cappedAncestor: cappedAncestor || undefined,
|
|
45
75
|
type: 'alias',
|
|
46
|
-
|
|
47
|
-
};
|
|
48
|
-
await
|
|
49
|
-
console.log(`Aliased command: ${args.join(' ')} -> ${target}`);
|
|
76
|
+
target
|
|
77
|
+
});
|
|
78
|
+
await (0, effects_1.execute)(effects);
|
|
50
79
|
}
|
|
51
80
|
async function group(args, options = {}) {
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
81
|
+
const ctx = await getContext(options);
|
|
82
|
+
const localRoot = ctx.localRoot || ctx.cwd;
|
|
83
|
+
const agentctlDir = path_1.default.join(localRoot, '.agentctl');
|
|
84
|
+
const targetDir = path_1.default.join(agentctlDir, args.join(path_1.default.sep));
|
|
85
|
+
const exists = await fs_extra_1.default.pathExists(targetDir);
|
|
86
|
+
const cappedAncestor = await getCappedAncestor(targetDir, agentctlDir);
|
|
87
|
+
const { effects } = ctl_1.Logic.planScaffold(args, ctx, {
|
|
88
|
+
exists,
|
|
89
|
+
cappedAncestor: cappedAncestor || undefined,
|
|
90
|
+
type: 'group'
|
|
91
|
+
});
|
|
92
|
+
await (0, effects_1.execute)(effects);
|
|
60
93
|
}
|
|
61
94
|
async function pushGlobal(args, options = {}) {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
if (!localRoot)
|
|
95
|
+
const ctx = await getContext(options);
|
|
96
|
+
if (!ctx.localRoot)
|
|
65
97
|
throw new Error('Not in a local context');
|
|
66
|
-
const globalRoot = options.globalDir || (0, fs_utils_1.getGlobalRoot)();
|
|
67
|
-
const localAgentctl = path_1.default.join(localRoot, '.agentctl');
|
|
68
98
|
const cmdPathStr = args.join(path_1.default.sep);
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(destDir));
|
|
78
|
-
if (options.move) {
|
|
79
|
-
await fs_extra_1.default.move(srcDir, destDir);
|
|
80
|
-
console.log(`Moved ${args.join(' ')} to global scope`);
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
await fs_extra_1.default.copy(srcDir, destDir);
|
|
84
|
-
console.log(`Copied ${args.join(' ')} to global scope`);
|
|
85
|
-
}
|
|
99
|
+
const existsInLocal = await fs_extra_1.default.pathExists(path_1.default.join(ctx.localRoot, '.agentctl', cmdPathStr));
|
|
100
|
+
const existsInGlobal = await fs_extra_1.default.pathExists(path_1.default.join(ctx.globalRoot, cmdPathStr));
|
|
101
|
+
const effects = ctl_1.Logic.planPushGlobal(args, ctx, {
|
|
102
|
+
move: options.move,
|
|
103
|
+
existsInLocal,
|
|
104
|
+
existsInGlobal
|
|
105
|
+
});
|
|
106
|
+
await (0, effects_1.execute)(effects);
|
|
86
107
|
}
|
|
87
108
|
async function pullLocal(args, options = {}) {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
if (!localRoot)
|
|
109
|
+
const ctx = await getContext(options);
|
|
110
|
+
if (!ctx.localRoot)
|
|
91
111
|
throw new Error('Not in a local context');
|
|
92
|
-
const globalRoot = options.globalDir || (0, fs_utils_1.getGlobalRoot)();
|
|
93
112
|
const cmdPathStr = args.join(path_1.default.sep);
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(destDir));
|
|
104
|
-
if (options.move) {
|
|
105
|
-
await fs_extra_1.default.move(srcDir, destDir);
|
|
106
|
-
console.log(`Moved ${args.join(' ')} to local scope`);
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
await fs_extra_1.default.copy(srcDir, destDir);
|
|
110
|
-
console.log(`Copied ${args.join(' ')} to local scope`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
const skills_1 = require("./skills");
|
|
114
|
-
async function installSkill(agent, options = {}) {
|
|
115
|
-
const cwd = options.cwd || process.cwd();
|
|
116
|
-
if (!skills_1.SUPPORTED_AGENTS.includes(agent)) {
|
|
117
|
-
throw new Error(`Agent '${agent}' not supported. Supported agents: ${skills_1.SUPPORTED_AGENTS.join(', ')}`);
|
|
118
|
-
}
|
|
119
|
-
let targetDir;
|
|
120
|
-
if (agent === 'cursor') {
|
|
121
|
-
targetDir = path_1.default.join(cwd, '.cursor', 'skills');
|
|
122
|
-
}
|
|
123
|
-
else if (agent === 'antigravity') {
|
|
124
|
-
if (options.global) {
|
|
125
|
-
const globalRoot = options.antigravityGlobalDir || (0, fs_utils_1.getAntigravityGlobalRoot)();
|
|
126
|
-
targetDir = path_1.default.join(globalRoot, 'skills', 'agentctl');
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
targetDir = path_1.default.join(cwd, '.agent', 'skills', 'agentctl');
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
else if (agent === 'agentsmd') {
|
|
133
|
-
targetDir = path_1.default.join(cwd, '.agents', 'skills', 'agentctl');
|
|
134
|
-
}
|
|
135
|
-
else if (agent === 'gemini') {
|
|
136
|
-
if (options.global) {
|
|
137
|
-
const globalRoot = options.geminiGlobalDir || path_1.default.join(process.env.HOME || process.env.USERPROFILE, '.gemini');
|
|
138
|
-
targetDir = path_1.default.join(globalRoot, 'skills', 'agentctl');
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
targetDir = path_1.default.join(cwd, '.gemini', 'skills', 'agentctl');
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
throw new Error(`Agent logic for '${agent}' not implemented.`);
|
|
146
|
-
}
|
|
147
|
-
const p = await (0, skills_1.copySkill)(targetDir, agent);
|
|
148
|
-
console.log(`Installed skill for ${agent} at ${p}`);
|
|
113
|
+
const existsInGlobal = await fs_extra_1.default.pathExists(path_1.default.join(ctx.globalRoot, cmdPathStr));
|
|
114
|
+
const existsInLocal = await fs_extra_1.default.pathExists(path_1.default.join(ctx.localRoot, '.agentctl', cmdPathStr));
|
|
115
|
+
const effects = ctl_1.Logic.planPullLocal(args, ctx, {
|
|
116
|
+
move: options.move,
|
|
117
|
+
existsInLocal,
|
|
118
|
+
existsInGlobal
|
|
119
|
+
});
|
|
120
|
+
await (0, effects_1.execute)(effects);
|
|
149
121
|
}
|
|
150
122
|
async function rm(args, options = {}) {
|
|
151
123
|
const resolved = await (0, resolve_1.resolveCommand)(args, options);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
124
|
+
const ctx = await getContext(options);
|
|
125
|
+
const effects = ctl_1.Logic.planRemove(args, ctx, {
|
|
126
|
+
resolvedPath: resolved?.manifestPath || null,
|
|
127
|
+
scope: resolved?.scope || 'unknown',
|
|
128
|
+
global: options.global
|
|
129
|
+
});
|
|
130
|
+
await (0, effects_1.execute)(effects);
|
|
158
131
|
}
|
|
159
132
|
async function mv(srcArgs, destArgs, options = {}) {
|
|
160
133
|
const resolved = await (0, resolve_1.resolveCommand)(srcArgs, options);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
let current = path_1.default.dirname(destDir);
|
|
179
|
-
while (current.length >= agentctlDir.length && !isSamePath(current, path_1.default.dirname(agentctlDir))) {
|
|
180
|
-
if (await isCapped(current)) {
|
|
181
|
-
const relPath = path_1.default.relative(agentctlDir, current); // relative to base
|
|
182
|
-
throw new Error(`Cannot nest command under capped command: ${relPath}`);
|
|
134
|
+
const ctx = await getContext(options);
|
|
135
|
+
let rootDir = null;
|
|
136
|
+
let agentctlDir = null;
|
|
137
|
+
let destExists = false;
|
|
138
|
+
let cappedAncestor = null;
|
|
139
|
+
if (resolved) {
|
|
140
|
+
rootDir = resolved.scope === 'local'
|
|
141
|
+
? (0, fs_utils_1.findLocalRoot)(options.cwd || process.cwd())
|
|
142
|
+
: (options.globalDir || (0, fs_utils_1.getGlobalRoot)());
|
|
143
|
+
if (rootDir) {
|
|
144
|
+
agentctlDir = resolved.scope === 'local' ? path_1.default.join(rootDir, '.agentctl') : rootDir;
|
|
145
|
+
const destPathStr = destArgs.join(path_1.default.sep);
|
|
146
|
+
const destDir = path_1.default.join(agentctlDir, destPathStr);
|
|
147
|
+
destExists = await fs_extra_1.default.pathExists(destDir);
|
|
148
|
+
const ancestor = await getCappedAncestor(destDir, agentctlDir);
|
|
149
|
+
if (ancestor)
|
|
150
|
+
cappedAncestor = { relPath: ancestor.relPath };
|
|
183
151
|
}
|
|
184
|
-
current = path_1.default.dirname(current);
|
|
185
|
-
}
|
|
186
|
-
await fs_extra_1.default.move(srcDir, destDir);
|
|
187
|
-
// Update manifest name
|
|
188
|
-
const manifestPath = path_1.default.join(destDir, 'manifest.json');
|
|
189
|
-
if (await fs_extra_1.default.pathExists(manifestPath)) {
|
|
190
|
-
const manifest = await fs_extra_1.default.readJson(manifestPath);
|
|
191
|
-
manifest.name = destArgs[destArgs.length - 1];
|
|
192
|
-
await fs_extra_1.default.writeJson(manifestPath, manifest, { spaces: 2 });
|
|
193
152
|
}
|
|
194
|
-
|
|
153
|
+
const effects = ctl_1.Logic.planMove(srcArgs, destArgs, ctx, {
|
|
154
|
+
resolvedSrc: resolved ? { manifestPath: resolved.manifestPath, scope: resolved.scope, manifest: resolved.manifest } : null,
|
|
155
|
+
destExists,
|
|
156
|
+
cappedAncestor: cappedAncestor || undefined,
|
|
157
|
+
rootDir,
|
|
158
|
+
agentctlDir
|
|
159
|
+
});
|
|
160
|
+
await (0, effects_1.execute)(effects);
|
|
195
161
|
}
|
|
196
162
|
async function inspect(args, options = {}) {
|
|
197
163
|
const resolved = await (0, resolve_1.resolveCommand)(args, options);
|
|
198
|
-
if (!resolved)
|
|
164
|
+
if (!resolved)
|
|
199
165
|
return null;
|
|
200
|
-
}
|
|
201
166
|
return {
|
|
202
167
|
manifest: resolved.manifest,
|
|
203
168
|
resolvedPath: resolved.manifestPath,
|
|
204
169
|
scope: resolved.scope
|
|
205
170
|
};
|
|
206
171
|
}
|
|
172
|
+
async function installSkill(agent, options = {}) {
|
|
173
|
+
const ctx = await getContext(options);
|
|
174
|
+
const effects = ctl_1.Logic.planInstallSkill(agent, ctx, options);
|
|
175
|
+
await (0, effects_1.execute)(effects);
|
|
176
|
+
}
|
|
177
|
+
async function install(repoUrl, pathArgs, options = {}) {
|
|
178
|
+
const ctx = await getContext(options);
|
|
179
|
+
const installCtx = {
|
|
180
|
+
repoUrl,
|
|
181
|
+
pathParts: pathArgs,
|
|
182
|
+
global: !!options.global,
|
|
183
|
+
allowCollisions: !!options.allowCollisions,
|
|
184
|
+
localRoot: ctx.localRoot,
|
|
185
|
+
globalRoot: ctx.globalRoot,
|
|
186
|
+
osTmpdir: os_1.default.tmpdir()
|
|
187
|
+
};
|
|
188
|
+
// Phase 1: Clone
|
|
189
|
+
const tempFolderName = `agentctl-install-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
|
|
190
|
+
const { effects: cloneEffects, tempDir } = (0, install_1.planInstallClone)(installCtx, { tempFolderName });
|
|
191
|
+
await (0, effects_1.execute)(cloneEffects);
|
|
192
|
+
// Phase 2: Copy
|
|
193
|
+
const tempAgentctlDir = path_1.default.join(tempDir, '.agentctl');
|
|
194
|
+
if (!(await fs_extra_1.default.pathExists(tempAgentctlDir))) {
|
|
195
|
+
await fs_extra_1.default.remove(tempDir);
|
|
196
|
+
throw new Error(`Repository does not contain an .agentctl directory at the root.`);
|
|
197
|
+
}
|
|
198
|
+
const downloadedItems = await fs_extra_1.default.readdir(tempAgentctlDir);
|
|
199
|
+
const rootDir = installCtx.global ? installCtx.globalRoot : installCtx.localRoot;
|
|
200
|
+
const agentctlDir = installCtx.global ? rootDir : path_1.default.join(rootDir, '.agentctl');
|
|
201
|
+
const targetDir = path_1.default.join(agentctlDir, ...installCtx.pathParts);
|
|
202
|
+
const existingItems = (await fs_extra_1.default.pathExists(targetDir)) ? await fs_extra_1.default.readdir(targetDir) : [];
|
|
203
|
+
const { effects: copyEffects } = (0, install_1.planInstallCopy)(installCtx, {
|
|
204
|
+
tempAgentctlDir,
|
|
205
|
+
existingItems,
|
|
206
|
+
downloadedItems
|
|
207
|
+
});
|
|
208
|
+
await (0, effects_1.execute)(copyEffects);
|
|
209
|
+
}
|
|
207
210
|
async function list(options = {}) {
|
|
208
|
-
const
|
|
209
|
-
const localRoot = (0, fs_utils_1.findLocalRoot)(cwd);
|
|
210
|
-
const globalRoot = options.globalDir || (0, fs_utils_1.getGlobalRoot)();
|
|
211
|
+
const ctx = await getContext(options);
|
|
211
212
|
const commands = new Map();
|
|
212
213
|
async function walk(dir, prefix, scope) {
|
|
213
214
|
if (!await fs_extra_1.default.pathExists(dir))
|
|
@@ -228,89 +229,32 @@ async function list(options = {}) {
|
|
|
228
229
|
const cmdPath = cmdPathParts.join(' ');
|
|
229
230
|
let manifest = null;
|
|
230
231
|
const mPath = path_1.default.join(filePath, 'manifest.json');
|
|
231
|
-
if (await fs_extra_1.default.pathExists(mPath))
|
|
232
|
+
if (await fs_extra_1.default.pathExists(mPath))
|
|
232
233
|
manifest = await (0, manifest_1.readManifest)(mPath);
|
|
233
|
-
}
|
|
234
234
|
let type = 'group';
|
|
235
235
|
if (manifest) {
|
|
236
|
-
if ((0, manifest_1.isCappedManifest)(manifest))
|
|
236
|
+
if ((0, manifest_1.isCappedManifest)(manifest))
|
|
237
237
|
type = manifest.type || 'scaffold';
|
|
238
|
-
|
|
239
|
-
else if (manifest.type) {
|
|
238
|
+
else if (manifest.type)
|
|
240
239
|
type = manifest.type;
|
|
241
|
-
}
|
|
242
240
|
}
|
|
243
|
-
const item = {
|
|
244
|
-
path: cmdPath,
|
|
245
|
-
type,
|
|
246
|
-
scope,
|
|
247
|
-
description: manifest?.description || ''
|
|
248
|
-
};
|
|
241
|
+
const item = { path: cmdPath, type, scope, description: manifest?.description || '' };
|
|
249
242
|
if (!commands.has(cmdPath)) {
|
|
250
243
|
commands.set(cmdPath, item);
|
|
251
244
|
const effectiveManifest = manifest || { name: file, type: 'group' };
|
|
252
|
-
if (!(0, manifest_1.isCappedManifest)(effectiveManifest))
|
|
245
|
+
if (!(0, manifest_1.isCappedManifest)(effectiveManifest))
|
|
253
246
|
await walk(filePath, cmdPathParts, scope);
|
|
254
|
-
}
|
|
255
247
|
}
|
|
256
248
|
else {
|
|
257
249
|
const existing = commands.get(cmdPath);
|
|
258
|
-
if (existing && existing.scope === 'local') {
|
|
259
|
-
|
|
260
|
-
await walk(filePath, cmdPathParts, scope);
|
|
261
|
-
}
|
|
250
|
+
if (existing && existing.scope === 'local' && existing.type === 'group' && type === 'group') {
|
|
251
|
+
await walk(filePath, cmdPathParts, scope);
|
|
262
252
|
}
|
|
263
253
|
}
|
|
264
254
|
}
|
|
265
255
|
}
|
|
266
|
-
if (localRoot)
|
|
267
|
-
await walk(path_1.default.join(localRoot, '.agentctl'), [], 'local');
|
|
268
|
-
|
|
269
|
-
await walk(globalRoot, [], 'global');
|
|
256
|
+
if (ctx.localRoot)
|
|
257
|
+
await walk(path_1.default.join(ctx.localRoot, '.agentctl'), [], 'local');
|
|
258
|
+
await walk(ctx.globalRoot, [], 'global');
|
|
270
259
|
return Array.from(commands.values());
|
|
271
260
|
}
|
|
272
|
-
// Helpers
|
|
273
|
-
async function prepareCommand(args, options = {}) {
|
|
274
|
-
const cwd = options.cwd || process.cwd();
|
|
275
|
-
const rootDir = cwd;
|
|
276
|
-
const agentctlDir = path_1.default.join(rootDir, '.agentctl');
|
|
277
|
-
if (args.length === 0)
|
|
278
|
-
throw new Error('No command path provided');
|
|
279
|
-
const cmdPath = args.join(path_1.default.sep);
|
|
280
|
-
const targetDir = path_1.default.join(agentctlDir, cmdPath);
|
|
281
|
-
if (await fs_extra_1.default.pathExists(targetDir)) {
|
|
282
|
-
throw new Error(`Command ${args.join(' ')} already exists`);
|
|
283
|
-
}
|
|
284
|
-
let current = path_1.default.dirname(targetDir);
|
|
285
|
-
while (current.length >= agentctlDir.length && !isSamePath(current, path_1.default.dirname(agentctlDir))) {
|
|
286
|
-
if (await isCapped(current)) {
|
|
287
|
-
const relPath = path_1.default.relative(agentctlDir, current); // This uses cwd for resolution if agentctlDir is cwd resolved
|
|
288
|
-
// Need to verify relative works predictably
|
|
289
|
-
// agentctlDir comes from path.join(cwd, '.agentctl').
|
|
290
|
-
throw new Error(`Cannot nest command under capped command: ${relPath}`);
|
|
291
|
-
}
|
|
292
|
-
current = path_1.default.dirname(current);
|
|
293
|
-
}
|
|
294
|
-
await fs_extra_1.default.ensureDir(targetDir);
|
|
295
|
-
return {
|
|
296
|
-
targetDir,
|
|
297
|
-
name: args[args.length - 1],
|
|
298
|
-
isWin: process.platform === 'win32'
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
async function isCapped(dir) {
|
|
302
|
-
const manifestPath = path_1.default.join(dir, 'manifest.json');
|
|
303
|
-
if (await fs_extra_1.default.pathExists(manifestPath)) {
|
|
304
|
-
try {
|
|
305
|
-
const m = await fs_extra_1.default.readJson(manifestPath);
|
|
306
|
-
return (0, manifest_1.isCappedManifest)(m);
|
|
307
|
-
}
|
|
308
|
-
catch {
|
|
309
|
-
return false;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
function isSamePath(p1, p2) {
|
|
315
|
-
return path_1.default.relative(p1, p2) === '';
|
|
316
|
-
}
|
package/dist/effects.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
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.execute = execute;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const skills_1 = require("./skills");
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
const util_1 = __importDefault(require("util"));
|
|
11
|
+
const execFileAsync = util_1.default.promisify(child_process_1.execFile);
|
|
12
|
+
/**
|
|
13
|
+
* THE EXECUTOR: The only part of the app that actually "does" things.
|
|
14
|
+
* It's a simple, dumb loop.
|
|
15
|
+
*/
|
|
16
|
+
async function execute(effects) {
|
|
17
|
+
for (const effect of effects) {
|
|
18
|
+
switch (effect.type) {
|
|
19
|
+
case 'writeFile':
|
|
20
|
+
await fs_extra_1.default.writeFile(effect.path, effect.content);
|
|
21
|
+
break;
|
|
22
|
+
case 'writeJson':
|
|
23
|
+
await fs_extra_1.default.writeJson(effect.path, effect.content, { spaces: 2 });
|
|
24
|
+
break;
|
|
25
|
+
case 'mkdir':
|
|
26
|
+
await fs_extra_1.default.ensureDir(effect.path);
|
|
27
|
+
break;
|
|
28
|
+
case 'chmod':
|
|
29
|
+
await fs_extra_1.default.chmod(effect.path, effect.mode);
|
|
30
|
+
break;
|
|
31
|
+
case 'move':
|
|
32
|
+
await fs_extra_1.default.move(effect.src, effect.dest);
|
|
33
|
+
break;
|
|
34
|
+
case 'copy':
|
|
35
|
+
await fs_extra_1.default.copy(effect.src, effect.dest, effect.options);
|
|
36
|
+
break;
|
|
37
|
+
case 'remove':
|
|
38
|
+
await fs_extra_1.default.remove(effect.path);
|
|
39
|
+
break;
|
|
40
|
+
case 'log':
|
|
41
|
+
console.log(effect.message);
|
|
42
|
+
break;
|
|
43
|
+
case 'installSkill': {
|
|
44
|
+
const p = await (0, skills_1.copySkill)(effect.targetDir, effect.agent);
|
|
45
|
+
console.log(`Installed skill for ${effect.agent} at ${p}`);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case 'spawn': {
|
|
49
|
+
const child = (0, child_process_1.spawn)(effect.command, effect.options);
|
|
50
|
+
if (effect.onExit) {
|
|
51
|
+
child.on('exit', effect.onExit);
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case 'gitClone': {
|
|
56
|
+
await execFileAsync('git', ['clone', '--depth', '1', effect.url, effect.dest]);
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
package/dist/fs-utils.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
2
16
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
18
|
};
|
|
@@ -6,30 +20,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
20
|
exports.getGlobalRoot = getGlobalRoot;
|
|
7
21
|
exports.getAntigravityGlobalRoot = getAntigravityGlobalRoot;
|
|
8
22
|
exports.findLocalRoot = findLocalRoot;
|
|
9
|
-
const path_1 = __importDefault(require("path"));
|
|
10
23
|
const os_1 = __importDefault(require("os"));
|
|
11
24
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
25
|
+
const utils_1 = require("./logic/utils");
|
|
26
|
+
__exportStar(require("./logic/utils"), exports);
|
|
27
|
+
function getRealContext() {
|
|
28
|
+
return {
|
|
29
|
+
platform: process.platform,
|
|
30
|
+
env: process.env,
|
|
31
|
+
homedir: process.env.HOME || process.env.USERPROFILE || os_1.default.homedir()
|
|
32
|
+
};
|
|
33
|
+
}
|
|
12
34
|
function getGlobalRoot() {
|
|
13
|
-
|
|
14
|
-
return path_1.default.join(process.env.APPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Roaming'), 'agentctl');
|
|
15
|
-
}
|
|
16
|
-
return path_1.default.join(os_1.default.homedir(), '.config', 'agentctl');
|
|
35
|
+
return utils_1.UtilsLogic.getGlobalRoot(getRealContext());
|
|
17
36
|
}
|
|
18
37
|
function getAntigravityGlobalRoot() {
|
|
19
|
-
return
|
|
38
|
+
return utils_1.UtilsLogic.getAntigravityGlobalRoot(getRealContext());
|
|
20
39
|
}
|
|
21
40
|
function findLocalRoot(cwd = process.cwd()) {
|
|
22
|
-
|
|
23
|
-
const root = path_1.default.parse(current).root;
|
|
24
|
-
// Safety break and root check
|
|
25
|
-
// Using for(;;) to avoid no-constant-condition
|
|
26
|
-
for (;;) {
|
|
27
|
-
if (fs_extra_1.default.existsSync(path_1.default.join(current, '.agentctl'))) {
|
|
28
|
-
return current;
|
|
29
|
-
}
|
|
30
|
-
if (current === root) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
current = path_1.default.dirname(current);
|
|
34
|
-
}
|
|
41
|
+
return utils_1.UtilsLogic.findLocalRoot(cwd, fs_extra_1.default.existsSync);
|
|
35
42
|
}
|