@michaelhartmayer/agentctl 1.0.3 → 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 +217 -308
- package/dist/effects.js +61 -0
- package/dist/fs-utils.js +26 -19
- package/dist/index.js +256 -232
- 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 +23 -18
- package/dist/resolve.js +52 -109
- package/dist/skills.js +16 -39
- 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 -305
- 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
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
4
|
};
|
|
@@ -17,335 +8,253 @@ exports.alias = alias;
|
|
|
17
8
|
exports.group = group;
|
|
18
9
|
exports.pushGlobal = pushGlobal;
|
|
19
10
|
exports.pullLocal = pullLocal;
|
|
20
|
-
exports.installSkill = installSkill;
|
|
21
11
|
exports.rm = rm;
|
|
22
12
|
exports.mv = mv;
|
|
23
13
|
exports.inspect = inspect;
|
|
14
|
+
exports.installSkill = installSkill;
|
|
15
|
+
exports.install = install;
|
|
24
16
|
exports.list = list;
|
|
25
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
26
17
|
const path_1 = __importDefault(require("path"));
|
|
27
|
-
const
|
|
18
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
19
|
+
const os_1 = __importDefault(require("os"));
|
|
28
20
|
const fs_utils_1 = require("./fs-utils");
|
|
29
21
|
const manifest_1 = require("./manifest");
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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;
|
|
50
44
|
}
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
yield fs_extra_1.default.writeJson(path_1.default.join(targetDir, 'manifest.json'), manifest, { spaces: 2 });
|
|
61
|
-
console.log(`Aliased command: ${args.join(' ')} -> ${target}`);
|
|
62
|
-
});
|
|
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()
|
|
53
|
+
};
|
|
63
54
|
}
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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);
|
|
64
|
+
}
|
|
65
|
+
async function alias(args, target, options = {}) {
|
|
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,
|
|
75
|
+
type: 'alias',
|
|
76
|
+
target
|
|
74
77
|
});
|
|
78
|
+
await (0, effects_1.execute)(effects);
|
|
75
79
|
}
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
throw new Error(`Local command ${args.join(' ')} not found`);
|
|
88
|
-
}
|
|
89
|
-
const destDir = path_1.default.join(globalRoot, cmdPathStr);
|
|
90
|
-
if (yield fs_extra_1.default.pathExists(destDir)) {
|
|
91
|
-
throw new Error(`Global command ${args.join(' ')} already exists`);
|
|
92
|
-
}
|
|
93
|
-
yield fs_extra_1.default.ensureDir(path_1.default.dirname(destDir));
|
|
94
|
-
if (options.move) {
|
|
95
|
-
yield fs_extra_1.default.move(srcDir, destDir);
|
|
96
|
-
console.log(`Moved ${args.join(' ')} to global scope`);
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
yield fs_extra_1.default.copy(srcDir, destDir);
|
|
100
|
-
console.log(`Copied ${args.join(' ')} to global scope`);
|
|
101
|
-
}
|
|
80
|
+
async function group(args, options = {}) {
|
|
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'
|
|
102
91
|
});
|
|
92
|
+
await (0, effects_1.execute)(effects);
|
|
103
93
|
}
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
const localAgentctl = path_1.default.join(localRoot, '.agentctl');
|
|
117
|
-
const destDir = path_1.default.join(localAgentctl, cmdPathStr);
|
|
118
|
-
if (yield fs_extra_1.default.pathExists(destDir)) {
|
|
119
|
-
throw new Error(`Local command ${args.join(' ')} already exists`);
|
|
120
|
-
}
|
|
121
|
-
yield fs_extra_1.default.ensureDir(path_1.default.dirname(destDir));
|
|
122
|
-
if (options.move) {
|
|
123
|
-
yield fs_extra_1.default.move(srcDir, destDir);
|
|
124
|
-
console.log(`Moved ${args.join(' ')} to local scope`);
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
yield fs_extra_1.default.copy(srcDir, destDir);
|
|
128
|
-
console.log(`Copied ${args.join(' ')} to local scope`);
|
|
129
|
-
}
|
|
94
|
+
async function pushGlobal(args, options = {}) {
|
|
95
|
+
const ctx = await getContext(options);
|
|
96
|
+
if (!ctx.localRoot)
|
|
97
|
+
throw new Error('Not in a local context');
|
|
98
|
+
const cmdPathStr = args.join(path_1.default.sep);
|
|
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
|
|
130
105
|
});
|
|
106
|
+
await (0, effects_1.execute)(effects);
|
|
131
107
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
else if (agent === 'antigravity') {
|
|
144
|
-
if (options.global) {
|
|
145
|
-
const globalRoot = options.antigravityGlobalDir || (0, fs_utils_1.getAntigravityGlobalRoot)();
|
|
146
|
-
targetDir = path_1.default.join(globalRoot, 'skills', 'agentctl');
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
targetDir = path_1.default.join(cwd, '.agent', 'skills', 'agentctl');
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
else if (agent === 'agentsmd') {
|
|
153
|
-
targetDir = path_1.default.join(cwd, '.agents', 'skills', 'agentctl');
|
|
154
|
-
}
|
|
155
|
-
else if (agent === 'gemini') {
|
|
156
|
-
if (options.global) {
|
|
157
|
-
const globalRoot = options.geminiGlobalDir || path_1.default.join(process.env.HOME || process.env.USERPROFILE, '.gemini');
|
|
158
|
-
targetDir = path_1.default.join(globalRoot, 'skills', 'agentctl');
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
targetDir = path_1.default.join(cwd, '.gemini', 'skills', 'agentctl');
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
throw new Error(`Agent logic for '${agent}' not implemented.`);
|
|
166
|
-
}
|
|
167
|
-
const p = yield (0, skills_1.copySkill)(targetDir, agent);
|
|
168
|
-
console.log(`Installed skill for ${agent} at ${p}`);
|
|
108
|
+
async function pullLocal(args, options = {}) {
|
|
109
|
+
const ctx = await getContext(options);
|
|
110
|
+
if (!ctx.localRoot)
|
|
111
|
+
throw new Error('Not in a local context');
|
|
112
|
+
const cmdPathStr = args.join(path_1.default.sep);
|
|
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
|
|
169
119
|
});
|
|
120
|
+
await (0, effects_1.execute)(effects);
|
|
170
121
|
}
|
|
171
|
-
function rm(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
yield fs_extra_1.default.remove(targetDir);
|
|
179
|
-
console.log(`Removed ${resolved.scope} command: ${args.join(' ')}`);
|
|
122
|
+
async function rm(args, options = {}) {
|
|
123
|
+
const resolved = await (0, resolve_1.resolveCommand)(args, options);
|
|
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
|
|
180
129
|
});
|
|
130
|
+
await (0, effects_1.execute)(effects);
|
|
181
131
|
}
|
|
182
|
-
function mv(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
132
|
+
async function mv(srcArgs, destArgs, options = {}) {
|
|
133
|
+
const resolved = await (0, resolve_1.resolveCommand)(srcArgs, options);
|
|
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'
|
|
190
141
|
? (0, fs_utils_1.findLocalRoot)(options.cwd || process.cwd())
|
|
191
142
|
: (options.globalDir || (0, fs_utils_1.getGlobalRoot)());
|
|
192
|
-
if (
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
// Check parent validity (nesting under capped)
|
|
202
|
-
let current = path_1.default.dirname(destDir);
|
|
203
|
-
while (current.length >= agentctlDir.length && !isSamePath(current, path_1.default.dirname(agentctlDir))) {
|
|
204
|
-
if (yield isCapped(current)) {
|
|
205
|
-
const relPath = path_1.default.relative(agentctlDir, current); // relative to base
|
|
206
|
-
throw new Error(`Cannot nest command under capped command: ${relPath}`);
|
|
207
|
-
}
|
|
208
|
-
current = path_1.default.dirname(current);
|
|
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 };
|
|
209
151
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
console.log(`Moved ${srcArgs.join(' ')} to ${destArgs.join(' ')}`);
|
|
152
|
+
}
|
|
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
|
|
219
159
|
});
|
|
160
|
+
await (0, effects_1.execute)(effects);
|
|
220
161
|
}
|
|
221
|
-
function inspect(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
scope: resolved.scope
|
|
231
|
-
};
|
|
232
|
-
});
|
|
162
|
+
async function inspect(args, options = {}) {
|
|
163
|
+
const resolved = await (0, resolve_1.resolveCommand)(args, options);
|
|
164
|
+
if (!resolved)
|
|
165
|
+
return null;
|
|
166
|
+
return {
|
|
167
|
+
manifest: resolved.manifest,
|
|
168
|
+
resolvedPath: resolved.manifestPath,
|
|
169
|
+
scope: resolved.scope
|
|
170
|
+
};
|
|
233
171
|
}
|
|
234
|
-
function
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const globalRoot = options.globalDir || (0, fs_utils_1.getGlobalRoot)();
|
|
239
|
-
const commands = new Map();
|
|
240
|
-
function walk(dir, prefix, scope) {
|
|
241
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
242
|
-
if (!(yield fs_extra_1.default.pathExists(dir)))
|
|
243
|
-
return;
|
|
244
|
-
const files = yield fs_extra_1.default.readdir(dir);
|
|
245
|
-
for (const file of files) {
|
|
246
|
-
const filePath = path_1.default.join(dir, file);
|
|
247
|
-
let stats;
|
|
248
|
-
try {
|
|
249
|
-
stats = yield fs_extra_1.default.stat(filePath);
|
|
250
|
-
}
|
|
251
|
-
catch (_a) {
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
if (!stats.isDirectory())
|
|
255
|
-
continue;
|
|
256
|
-
const cmdPathParts = [...prefix, file];
|
|
257
|
-
const cmdPath = cmdPathParts.join(' ');
|
|
258
|
-
let manifest = null;
|
|
259
|
-
const mPath = path_1.default.join(filePath, 'manifest.json');
|
|
260
|
-
if (yield fs_extra_1.default.pathExists(mPath)) {
|
|
261
|
-
manifest = yield (0, manifest_1.readManifest)(mPath);
|
|
262
|
-
}
|
|
263
|
-
let type = 'group';
|
|
264
|
-
if (manifest) {
|
|
265
|
-
if ((0, manifest_1.isCappedManifest)(manifest)) {
|
|
266
|
-
type = manifest.type || 'scaffold';
|
|
267
|
-
}
|
|
268
|
-
else if (manifest.type) {
|
|
269
|
-
type = manifest.type;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
const item = {
|
|
273
|
-
path: cmdPath,
|
|
274
|
-
type,
|
|
275
|
-
scope,
|
|
276
|
-
description: (manifest === null || manifest === void 0 ? void 0 : manifest.description) || ''
|
|
277
|
-
};
|
|
278
|
-
if (!commands.has(cmdPath)) {
|
|
279
|
-
commands.set(cmdPath, item);
|
|
280
|
-
const effectiveManifest = manifest || { name: file, type: 'group' };
|
|
281
|
-
if (!(0, manifest_1.isCappedManifest)(effectiveManifest)) {
|
|
282
|
-
yield walk(filePath, cmdPathParts, scope);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
else {
|
|
286
|
-
const existing = commands.get(cmdPath);
|
|
287
|
-
if (existing && existing.scope === 'local') {
|
|
288
|
-
if (existing.type === 'group' && type === 'group') {
|
|
289
|
-
yield walk(filePath, cmdPathParts, scope);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
if (localRoot) {
|
|
297
|
-
yield walk(path_1.default.join(localRoot, '.agentctl'), [], 'local');
|
|
298
|
-
}
|
|
299
|
-
yield walk(globalRoot, [], 'global');
|
|
300
|
-
return Array.from(commands.values());
|
|
301
|
-
});
|
|
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);
|
|
302
176
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
|
332
207
|
});
|
|
208
|
+
await (0, effects_1.execute)(copyEffects);
|
|
333
209
|
}
|
|
334
|
-
function
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
210
|
+
async function list(options = {}) {
|
|
211
|
+
const ctx = await getContext(options);
|
|
212
|
+
const commands = new Map();
|
|
213
|
+
async function walk(dir, prefix, scope) {
|
|
214
|
+
if (!await fs_extra_1.default.pathExists(dir))
|
|
215
|
+
return;
|
|
216
|
+
const files = await fs_extra_1.default.readdir(dir);
|
|
217
|
+
for (const file of files) {
|
|
218
|
+
const filePath = path_1.default.join(dir, file);
|
|
219
|
+
let stats;
|
|
338
220
|
try {
|
|
339
|
-
|
|
340
|
-
return (0, manifest_1.isCappedManifest)(m);
|
|
221
|
+
stats = await fs_extra_1.default.stat(filePath);
|
|
341
222
|
}
|
|
342
|
-
catch
|
|
343
|
-
|
|
223
|
+
catch {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (!stats.isDirectory())
|
|
227
|
+
continue;
|
|
228
|
+
const cmdPathParts = [...prefix, file];
|
|
229
|
+
const cmdPath = cmdPathParts.join(' ');
|
|
230
|
+
let manifest = null;
|
|
231
|
+
const mPath = path_1.default.join(filePath, 'manifest.json');
|
|
232
|
+
if (await fs_extra_1.default.pathExists(mPath))
|
|
233
|
+
manifest = await (0, manifest_1.readManifest)(mPath);
|
|
234
|
+
let type = 'group';
|
|
235
|
+
if (manifest) {
|
|
236
|
+
if ((0, manifest_1.isCappedManifest)(manifest))
|
|
237
|
+
type = manifest.type || 'scaffold';
|
|
238
|
+
else if (manifest.type)
|
|
239
|
+
type = manifest.type;
|
|
240
|
+
}
|
|
241
|
+
const item = { path: cmdPath, type, scope, description: manifest?.description || '' };
|
|
242
|
+
if (!commands.has(cmdPath)) {
|
|
243
|
+
commands.set(cmdPath, item);
|
|
244
|
+
const effectiveManifest = manifest || { name: file, type: 'group' };
|
|
245
|
+
if (!(0, manifest_1.isCappedManifest)(effectiveManifest))
|
|
246
|
+
await walk(filePath, cmdPathParts, scope);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
const existing = commands.get(cmdPath);
|
|
250
|
+
if (existing && existing.scope === 'local' && existing.type === 'group' && type === 'group') {
|
|
251
|
+
await walk(filePath, cmdPathParts, scope);
|
|
252
|
+
}
|
|
344
253
|
}
|
|
345
254
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
return
|
|
255
|
+
}
|
|
256
|
+
if (ctx.localRoot)
|
|
257
|
+
await walk(path_1.default.join(ctx.localRoot, '.agentctl'), [], 'local');
|
|
258
|
+
await walk(ctx.globalRoot, [], 'global');
|
|
259
|
+
return Array.from(commands.values());
|
|
351
260
|
}
|
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
|
+
}
|