@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 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
- * Returns the effective install path (with subdir if present).
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
- * Returns the effective install path (with subdir if present).
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 clonePath = this.getClonePath(resolved.id);
167
- if (existsSync(clonePath)) throw new InstallerError(`Integration "${resolved.id}" is already installed at ${clonePath}`);
168
- if (resolved.subdir) this.subdirs.set(resolved.id, resolved.subdir);
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
- clonePath
178
+ installPath
175
179
  ], { timeout: GIT_TIMEOUT_MS });
176
180
  await execFileAsync$1("git", ["checkout", resolved.commit], {
177
- cwd: clonePath,
181
+ cwd: installPath,
178
182
  timeout: GIT_TIMEOUT_MS
179
183
  });
180
184
  } catch (err) {
181
- if (existsSync(clonePath)) rmSync(clonePath, {
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
- * Update an installed integration to a new version.
194
+ * Clone monorepo to a temp dir, extract the subdir to the install path, discard the clone.
193
195
  */
194
- async update(name, resolved) {
195
- const clonePath = this.getClonePath(name);
196
- if (!existsSync(clonePath)) throw new InstallerError(`Integration "${name}" is not installed — cannot update`);
197
- if (resolved.subdir) this.subdirs.set(name, resolved.subdir);
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", ["fetch", "origin"], {
201
- cwd: clonePath,
202
- timeout: GIT_TIMEOUT_MS
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: clonePath,
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 update integration "${name}" to ${resolved.version}: ${message}`);
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
- return this.getInstallPath(name);
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 clonePath = this.getClonePath(name);
219
- if (!existsSync(clonePath)) throw new InstallerError(`Integration "${name}" is not installed at ${clonePath}`);
220
- rmSync(clonePath, {
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.getClonePath(name));
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 `pnpm add` in the OpenClaw workspace directory.
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("pnpm", ["add", pkg], {
1171
- cwd: this.workspace,
1172
- timeout: 6e4
1173
- });
1196
+ await execFileAsync("openclaw", [
1197
+ "plugins",
1198
+ "install",
1199
+ pkg
1200
+ ], { timeout: 6e4 });
1174
1201
  }
1175
1202
  async removePlugin(pkg) {
1176
- await execFileAsync("pnpm", ["remove", pkg], {
1177
- cwd: this.workspace,
1178
- timeout: 3e4
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/integrations",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Integration lifecycle management for Alfe — registry, resolution, installation, and state",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",