@alfe.ai/integrations 0.0.4 → 0.0.6
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/index.d.ts +13 -8
- package/dist/index.js +77 -49
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -146,8 +146,6 @@ declare class InstallerError extends Error {
|
|
|
146
146
|
}
|
|
147
147
|
declare class Installer {
|
|
148
148
|
private basePath;
|
|
149
|
-
/** Tracks subdir overrides per integration id */
|
|
150
|
-
private subdirs;
|
|
151
149
|
/**
|
|
152
150
|
* @param basePath - Override the integrations directory (for testing).
|
|
153
151
|
* Defaults to ~/.alfe/integrations/
|
|
@@ -155,20 +153,27 @@ declare class Installer {
|
|
|
155
153
|
constructor(basePath?: string);
|
|
156
154
|
/**
|
|
157
155
|
* Get the path where an integration's content lives.
|
|
158
|
-
* When a subdir is set, returns the subdir within the clone.
|
|
159
156
|
*/
|
|
160
157
|
getInstallPath(name: string): string;
|
|
161
|
-
/**
|
|
162
|
-
* Get the root clone path (without subdir).
|
|
163
|
-
*/
|
|
164
|
-
getClonePath(name: string): string;
|
|
165
158
|
/**
|
|
166
159
|
* Install an integration by cloning its git repo and checking out the pinned commit.
|
|
167
|
-
*
|
|
160
|
+
* For monorepo integrations (with subdir), extracts only the subdir contents.
|
|
161
|
+
*
|
|
162
|
+
* If the install path already exists (e.g. from a failed previous install),
|
|
163
|
+
* it is cleaned up before retrying.
|
|
168
164
|
*/
|
|
169
165
|
install(resolved: ResolvedIntegration): Promise<string>;
|
|
166
|
+
/**
|
|
167
|
+
* Clone repo directly to the install path (no subdir).
|
|
168
|
+
*/
|
|
169
|
+
private cloneDirect;
|
|
170
|
+
/**
|
|
171
|
+
* Clone monorepo to a temp dir, extract the subdir to the install path, discard the clone.
|
|
172
|
+
*/
|
|
173
|
+
private cloneAndExtractSubdir;
|
|
170
174
|
/**
|
|
171
175
|
* Update an installed integration to a new version.
|
|
176
|
+
* Removes old install and does a fresh clone.
|
|
172
177
|
*/
|
|
173
178
|
update(name: string, resolved: ResolvedIntegration): Promise<string>;
|
|
174
179
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { execFile, spawn } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { homedir, tmpdir } from "node:os";
|
|
5
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { buildConfigValidationSchema, parseManifestFile } from "@alfe.ai/integration-manifest";
|
|
7
7
|
import { createLogger } from "@auriclabs/logger";
|
|
8
8
|
//#region src/registry.ts
|
|
@@ -122,6 +122,8 @@ var Resolver = class {
|
|
|
122
122
|
*
|
|
123
123
|
* Manages the local installation directory for integration repos.
|
|
124
124
|
* Each integration is cloned and checked out to a pinned commit hash.
|
|
125
|
+
* For monorepo integrations (with subdir), only the subdir contents
|
|
126
|
+
* are extracted — the full clone is discarded.
|
|
125
127
|
*/
|
|
126
128
|
const execFileAsync$1 = promisify(execFile);
|
|
127
129
|
const INTEGRATIONS_DIR = join(homedir(), ".alfe", "integrations");
|
|
@@ -134,8 +136,6 @@ var InstallerError = class extends Error {
|
|
|
134
136
|
};
|
|
135
137
|
var Installer = class {
|
|
136
138
|
basePath;
|
|
137
|
-
/** Tracks subdir overrides per integration id */
|
|
138
|
-
subdirs = /* @__PURE__ */ new Map();
|
|
139
139
|
/**
|
|
140
140
|
* @param basePath - Override the integrations directory (for testing).
|
|
141
141
|
* Defaults to ~/.alfe/integrations/
|
|
@@ -145,83 +145,109 @@ var Installer = class {
|
|
|
145
145
|
}
|
|
146
146
|
/**
|
|
147
147
|
* Get the path where an integration's content lives.
|
|
148
|
-
* When a subdir is set, returns the subdir within the clone.
|
|
149
148
|
*/
|
|
150
149
|
getInstallPath(name) {
|
|
151
|
-
const base = join(this.basePath, name);
|
|
152
|
-
const subdir = this.subdirs.get(name);
|
|
153
|
-
return subdir ? join(base, subdir) : base;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Get the root clone path (without subdir).
|
|
157
|
-
*/
|
|
158
|
-
getClonePath(name) {
|
|
159
150
|
return join(this.basePath, name);
|
|
160
151
|
}
|
|
161
152
|
/**
|
|
162
153
|
* Install an integration by cloning its git repo and checking out the pinned commit.
|
|
163
|
-
*
|
|
154
|
+
* For monorepo integrations (with subdir), extracts only the subdir contents.
|
|
155
|
+
*
|
|
156
|
+
* If the install path already exists (e.g. from a failed previous install),
|
|
157
|
+
* it is cleaned up before retrying.
|
|
164
158
|
*/
|
|
165
159
|
async install(resolved) {
|
|
166
|
-
const
|
|
167
|
-
if (existsSync(
|
|
168
|
-
|
|
160
|
+
const installPath = this.getInstallPath(resolved.id);
|
|
161
|
+
if (existsSync(installPath)) rmSync(installPath, {
|
|
162
|
+
recursive: true,
|
|
163
|
+
force: true
|
|
164
|
+
});
|
|
169
165
|
mkdirSync(this.basePath, { recursive: true });
|
|
166
|
+
if (resolved.subdir) await this.cloneAndExtractSubdir(resolved, installPath);
|
|
167
|
+
else await this.cloneDirect(resolved, installPath);
|
|
168
|
+
return installPath;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Clone repo directly to the install path (no subdir).
|
|
172
|
+
*/
|
|
173
|
+
async cloneDirect(resolved, installPath) {
|
|
170
174
|
try {
|
|
171
175
|
await execFileAsync$1("git", [
|
|
172
176
|
"clone",
|
|
173
177
|
resolved.repository,
|
|
174
|
-
|
|
178
|
+
installPath
|
|
175
179
|
], { timeout: GIT_TIMEOUT_MS });
|
|
176
180
|
await execFileAsync$1("git", ["checkout", resolved.commit], {
|
|
177
|
-
cwd:
|
|
181
|
+
cwd: installPath,
|
|
178
182
|
timeout: GIT_TIMEOUT_MS
|
|
179
183
|
});
|
|
180
184
|
} catch (err) {
|
|
181
|
-
if (existsSync(
|
|
185
|
+
if (existsSync(installPath)) rmSync(installPath, {
|
|
182
186
|
recursive: true,
|
|
183
187
|
force: true
|
|
184
188
|
});
|
|
185
|
-
this.subdirs.delete(resolved.id);
|
|
186
189
|
const message = err instanceof Error ? err.message : String(err);
|
|
187
190
|
throw new InstallerError(`Failed to clone integration "${resolved.id}": ${message}`);
|
|
188
191
|
}
|
|
189
|
-
return this.getInstallPath(resolved.id);
|
|
190
192
|
}
|
|
191
193
|
/**
|
|
192
|
-
*
|
|
194
|
+
* Clone monorepo to a temp dir, extract the subdir to the install path, discard the clone.
|
|
193
195
|
*/
|
|
194
|
-
async
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
else this.subdirs.delete(name);
|
|
196
|
+
async cloneAndExtractSubdir(resolved, installPath) {
|
|
197
|
+
if (!resolved.subdir) throw new InstallerError("subdir is required for monorepo extraction");
|
|
198
|
+
const subdir = resolved.subdir;
|
|
199
|
+
const tempDir = mkdtempSync(join(tmpdir(), `alfe-clone-${resolved.id}-`));
|
|
199
200
|
try {
|
|
200
|
-
await execFileAsync$1("git", [
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
201
|
+
await execFileAsync$1("git", [
|
|
202
|
+
"clone",
|
|
203
|
+
resolved.repository,
|
|
204
|
+
tempDir
|
|
205
|
+
], { timeout: GIT_TIMEOUT_MS });
|
|
204
206
|
await execFileAsync$1("git", ["checkout", resolved.commit], {
|
|
205
|
-
cwd:
|
|
207
|
+
cwd: tempDir,
|
|
206
208
|
timeout: GIT_TIMEOUT_MS
|
|
207
209
|
});
|
|
210
|
+
const subdirPath = join(tempDir, subdir);
|
|
211
|
+
if (!existsSync(subdirPath)) throw new InstallerError(`Subdir "${subdir}" not found in repo after checkout`);
|
|
212
|
+
cpSync(subdirPath, installPath, { recursive: true });
|
|
208
213
|
} catch (err) {
|
|
214
|
+
if (existsSync(installPath)) rmSync(installPath, {
|
|
215
|
+
recursive: true,
|
|
216
|
+
force: true
|
|
217
|
+
});
|
|
218
|
+
if (err instanceof InstallerError) throw err;
|
|
209
219
|
const message = err instanceof Error ? err.message : String(err);
|
|
210
|
-
throw new InstallerError(`Failed to
|
|
220
|
+
throw new InstallerError(`Failed to clone integration "${resolved.id}": ${message}`);
|
|
221
|
+
} finally {
|
|
222
|
+
rmSync(tempDir, {
|
|
223
|
+
recursive: true,
|
|
224
|
+
force: true
|
|
225
|
+
});
|
|
211
226
|
}
|
|
212
|
-
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Update an installed integration to a new version.
|
|
230
|
+
* Removes old install and does a fresh clone.
|
|
231
|
+
*/
|
|
232
|
+
async update(name, resolved) {
|
|
233
|
+
const installPath = this.getInstallPath(name);
|
|
234
|
+
if (!existsSync(installPath)) throw new InstallerError(`Integration "${name}" is not installed — cannot update`);
|
|
235
|
+
rmSync(installPath, {
|
|
236
|
+
recursive: true,
|
|
237
|
+
force: true
|
|
238
|
+
});
|
|
239
|
+
return this.install(resolved);
|
|
213
240
|
}
|
|
214
241
|
/**
|
|
215
242
|
* Remove an installed integration.
|
|
216
243
|
*/
|
|
217
244
|
remove(name) {
|
|
218
|
-
const
|
|
219
|
-
if (!existsSync(
|
|
220
|
-
rmSync(
|
|
245
|
+
const installPath = this.getInstallPath(name);
|
|
246
|
+
if (!existsSync(installPath)) throw new InstallerError(`Integration "${name}" is not installed at ${installPath}`);
|
|
247
|
+
rmSync(installPath, {
|
|
221
248
|
recursive: true,
|
|
222
249
|
force: true
|
|
223
250
|
});
|
|
224
|
-
this.subdirs.delete(name);
|
|
225
251
|
return Promise.resolve();
|
|
226
252
|
}
|
|
227
253
|
/**
|
|
@@ -258,7 +284,7 @@ var Installer = class {
|
|
|
258
284
|
* Check if an integration is installed.
|
|
259
285
|
*/
|
|
260
286
|
isInstalled(name) {
|
|
261
|
-
return existsSync(this.
|
|
287
|
+
return existsSync(this.getInstallPath(name));
|
|
262
288
|
}
|
|
263
289
|
};
|
|
264
290
|
//#endregion
|
|
@@ -1135,7 +1161,7 @@ var IntegrationManager = class {
|
|
|
1135
1161
|
/**
|
|
1136
1162
|
* OpenClawApplier — applies plugins, skills, and config to the OpenClaw runtime.
|
|
1137
1163
|
*
|
|
1138
|
-
* Plugins are installed via `
|
|
1164
|
+
* Plugins are installed via `npm install` in the OpenClaw workspace directory.
|
|
1139
1165
|
* Skills are copied to ~/.alfe/skills/{name}.
|
|
1140
1166
|
* Config is deep-merged into the OpenClaw agent config, with per-integration
|
|
1141
1167
|
* tracking so changes can be cleanly removed on deactivation.
|
|
@@ -1167,16 +1193,18 @@ var OpenClawApplier = class {
|
|
|
1167
1193
|
this.configPath = options.configPath ?? join(this.workspace, "config.json");
|
|
1168
1194
|
}
|
|
1169
1195
|
async applyPlugin(pkg) {
|
|
1170
|
-
await execFileAsync("
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1196
|
+
await execFileAsync("openclaw", [
|
|
1197
|
+
"plugins",
|
|
1198
|
+
"install",
|
|
1199
|
+
pkg
|
|
1200
|
+
], { timeout: 6e4 });
|
|
1174
1201
|
}
|
|
1175
1202
|
async removePlugin(pkg) {
|
|
1176
|
-
await execFileAsync("
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1203
|
+
await execFileAsync("openclaw", [
|
|
1204
|
+
"plugins",
|
|
1205
|
+
"uninstall",
|
|
1206
|
+
pkg
|
|
1207
|
+
], { timeout: 3e4 });
|
|
1180
1208
|
}
|
|
1181
1209
|
applySkill(name, srcPath) {
|
|
1182
1210
|
if (!existsSync(srcPath)) throw new Error(`Skill source path not found: ${srcPath}`);
|
package/package.json
CHANGED