@alfe.ai/integrations 0.0.1 → 0.0.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +48 -17
  2. package/dist/index.js +146 -182
  3. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -20,19 +20,59 @@ interface RegistryEntry {
20
20
  description: string;
21
21
  /** Agent runtimes this integration supports. Empty/absent = universal. */
22
22
  supported_agents?: string[];
23
+ /** URL to integration icon */
24
+ icon?: string;
25
+ /** Author info */
26
+ author?: {
27
+ name: string;
28
+ url?: string;
29
+ } | string;
30
+ /** Pricing details */
31
+ pricing?: {
32
+ type: "free" | "paid";
33
+ price?: number;
34
+ currency?: string;
35
+ interval?: "month" | "year";
36
+ };
37
+ /** Feature list for marketplace display */
38
+ features?: string[];
39
+ /** Preview image URLs */
40
+ preview_images?: string[];
41
+ /** Configuration schema fields */
42
+ config_schema?: {
43
+ key: string;
44
+ label: string;
45
+ type: string;
46
+ description?: string;
47
+ required?: boolean;
48
+ default?: string | number | boolean;
49
+ options?: string[];
50
+ select_options?: {
51
+ value: string;
52
+ label: string;
53
+ }[];
54
+ oauth_provider?: string;
55
+ }[];
23
56
  }
24
57
  interface RegistryIndex {
25
58
  version: number;
26
59
  integrations: Record<string, RegistryEntry>;
27
60
  }
61
+ /**
62
+ * Fetcher function that returns the raw integrations array from the registry API.
63
+ * Callers wire this up using their api-client instance so Registry stays dependency-free.
64
+ */
65
+ type RegistryFetcher = () => Promise<(RegistryEntry & {
66
+ id: string;
67
+ })[]>;
28
68
  declare class Registry {
29
69
  private index;
30
- private apiUrl;
70
+ private fetcher;
31
71
  /**
32
- * @param apiUrl - Base URL for the Alfe API (e.g. "https://api.alfe.ai").
33
- * Falls back to ALFE_API_URL env var, then the production URL.
72
+ * @param fetcher - Function that fetches the integrations array from the registry API.
73
+ * Typically backed by api-client's IntegrationsService.getRegistry().
34
74
  */
35
- constructor(apiUrl?: string);
75
+ constructor(fetcher: RegistryFetcher);
36
76
  /**
37
77
  * Load the registry index. Uses cache if already loaded.
38
78
  */
@@ -331,6 +371,8 @@ interface IntegrationManagerOptions {
331
371
  runtimeAppliers?: Map<string, RuntimeApplier>;
332
372
  /** Override lock file path (defaults to ~/.alfe/runtime-lock.json) */
333
373
  lockPath?: string;
374
+ /** Fetcher for the integration registry — should be wired to api-client */
375
+ registryFetcher: RegistryFetcher;
334
376
  }
335
377
  interface ManagerResponse {
336
378
  ok: boolean;
@@ -367,7 +409,7 @@ declare class IntegrationManager {
367
409
  private lockManager;
368
410
  /** In-memory secret store — NEVER persisted to disk */
369
411
  private secrets;
370
- constructor(options?: IntegrationManagerOptions);
412
+ constructor(options: IntegrationManagerOptions);
371
413
  /**
372
414
  * Install an integration from the registry.
373
415
  *
@@ -560,17 +602,6 @@ interface OpenClawApplierOptions {
560
602
  /** Path to the OpenClaw agent config file (defaults to {workspace}/config.json) */
561
603
  configPath?: string;
562
604
  }
563
- /**
564
- * Resolve LLM provider runtime config based on mode.
565
- *
566
- * - "alfe_credits": keep manifest config as-is (baseUrl points to local proxy,
567
- * apiKey is a dummy — the proxy injects the real key)
568
- * - "byok": remove baseUrl (SDK uses provider default), set apiKey from
569
- * decrypted secret
570
- *
571
- * Non-LLM integrations pass through unchanged.
572
- */
573
-
574
605
  declare class OpenClawApplier implements RuntimeApplier {
575
606
  readonly runtime = "openclaw";
576
607
  private workspace;
@@ -692,4 +723,4 @@ declare class IntegrationManagerAdapter implements IIntegrationManager {
692
723
  uninstall(integrationId: string): Promise<void>;
693
724
  }
694
725
  //#endregion
695
- export { type HookEnvOptions, type HookResult, type IIntegrationManager, type InstalledInfo, Installer, InstallerError, type IntegrationConfigureParams, type IntegrationHealthParams, type IntegrationInfo, type IntegrationInstallParams, IntegrationManager, IntegrationManagerAdapter, type IntegrationManagerOptions, type IntegrationRemoveParams, LockManager, OpenClawApplier, type OpenClawApplierOptions, Registry, type RegistryEntry, type RegistryIndex, RegistryResolveError, type ResolvedIntegration, Resolver, type RuntimeApplier, type RuntimeDesiredState, type RuntimeLockFile, type RuntimePluginEntry, type RuntimeSkillEntry, StateManager, buildHookEnv, resolveInstallsForRuntime, runHook, runHookWithContext };
726
+ export { type HookEnvOptions, type HookResult, type IIntegrationManager, type InstalledInfo, Installer, InstallerError, type IntegrationConfigureParams, type IntegrationHealthParams, type IntegrationInfo, type IntegrationInstallParams, IntegrationManager, IntegrationManagerAdapter, type IntegrationManagerOptions, type IntegrationRemoveParams, LockManager, OpenClawApplier, type OpenClawApplierOptions, Registry, type RegistryEntry, type RegistryFetcher, type RegistryIndex, RegistryResolveError, type ResolvedIntegration, Resolver, type RuntimeApplier, type RuntimeDesiredState, type RuntimeLockFile, type RuntimePluginEntry, type RuntimeSkillEntry, StateManager, buildHookEnv, resolveInstallsForRuntime, runHook, runHookWithContext };
package/dist/index.js CHANGED
@@ -6,37 +6,27 @@ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, write
6
6
  import { buildConfigValidationSchema, parseManifestFile } from "@alfe.ai/integration-manifest";
7
7
  import { createLogger } from "@auriclabs/logger";
8
8
  //#region src/registry.ts
9
- const DEFAULT_API_URL = "https://api.alfe.ai";
10
- const REGISTRY_PATH = "/integrations/registry";
11
9
  var Registry = class {
12
10
  index = null;
13
- apiUrl;
11
+ fetcher;
14
12
  /**
15
- * @param apiUrl - Base URL for the Alfe API (e.g. "https://api.alfe.ai").
16
- * Falls back to ALFE_API_URL env var, then the production URL.
13
+ * @param fetcher - Function that fetches the integrations array from the registry API.
14
+ * Typically backed by api-client's IntegrationsService.getRegistry().
17
15
  */
18
- constructor(apiUrl) {
19
- this.apiUrl = (apiUrl ?? process.env.ALFE_API_URL ?? DEFAULT_API_URL).replace(/\/+$/, "");
16
+ constructor(fetcher) {
17
+ this.fetcher = fetcher;
20
18
  }
21
19
  /**
22
20
  * Load the registry index. Uses cache if already loaded.
23
21
  */
24
22
  async load() {
25
23
  if (this.index) return this.index;
26
- const url = `${this.apiUrl}${REGISTRY_PATH}`;
27
- const res = await fetch(url);
28
- if (!res.ok) throw new Error(`Failed to fetch registry index from ${url}: ${String(res.status)} ${res.statusText}`);
29
- const body = await res.json();
30
- if (!body.success || !body.data?.integrations) throw new Error(`Invalid registry response from ${url}`);
31
- const raw = body.data.integrations;
32
- let integrations;
33
- if (Array.isArray(raw)) {
34
- integrations = {};
35
- for (const entry of raw) {
36
- const { id, ...rest } = entry;
37
- integrations[id] = rest;
38
- }
39
- } else integrations = raw;
24
+ const raw = await this.fetcher();
25
+ const integrations = {};
26
+ for (const entry of raw) {
27
+ const { id, ...rest } = entry;
28
+ integrations[id] = rest;
29
+ }
40
30
  this.index = {
41
31
  version: 1,
42
32
  integrations
@@ -98,7 +88,7 @@ var Resolver = class {
98
88
  async resolve(id, version) {
99
89
  const entry = await this.registry.get(id);
100
90
  if (!entry) throw new RegistryResolveError(`Integration "${id}" not found in registry`);
101
- const resolvedVersion = version ?? entry.latest;
91
+ const resolvedVersion = version && version.length > 0 ? version : entry.latest;
102
92
  if (!entry.versions.includes(resolvedVersion)) throw new RegistryResolveError(`Version "${resolvedVersion}" not found for integration "${id}". Available versions: ${entry.versions.join(", ")}`);
103
93
  return {
104
94
  id,
@@ -652,159 +642,6 @@ async function runHookWithContext(integrationPath, hookScript, options) {
652
642
  return runHook(integrationPath, hookScript, buildHookEnv(options));
653
643
  }
654
644
  //#endregion
655
- //#region src/appliers/openclaw-applier.ts
656
- /**
657
- * OpenClawApplier — applies plugins, skills, and config to the OpenClaw runtime.
658
- *
659
- * Plugins are installed via `pnpm add` in the OpenClaw workspace directory.
660
- * Skills are copied to ~/.alfe/skills/{name}.
661
- * Config is deep-merged into the OpenClaw agent config, with per-integration
662
- * tracking so changes can be cleanly removed on deactivation.
663
- */
664
- const execFileAsync = promisify(execFile);
665
- const DEFAULT_SKILLS_DIR = join(homedir(), ".alfe", "skills");
666
- /**
667
- * Deep-merge source into target, returning a new object.
668
- * Arrays are replaced, not concatenated.
669
- */
670
- function deepMerge(target, source) {
671
- const result = { ...target };
672
- for (const key of Object.keys(source)) {
673
- const srcVal = source[key];
674
- const tgtVal = result[key];
675
- if (srcVal !== null && typeof srcVal === "object" && !Array.isArray(srcVal) && tgtVal !== null && typeof tgtVal === "object" && !Array.isArray(tgtVal)) result[key] = deepMerge(tgtVal, srcVal);
676
- else result[key] = srcVal;
677
- }
678
- return result;
679
- }
680
- const LLM_INTEGRATION_IDS = new Set(["anthropic", "openai"]);
681
- /**
682
- * Resolve LLM provider runtime config based on mode.
683
- *
684
- * - "alfe_credits": keep manifest config as-is (baseUrl points to local proxy,
685
- * apiKey is a dummy — the proxy injects the real key)
686
- * - "byok": remove baseUrl (SDK uses provider default), set apiKey from
687
- * decrypted secret
688
- *
689
- * Non-LLM integrations pass through unchanged.
690
- */
691
- function resolveLlmRuntimeConfig(integrationId, runtimeConfig, integrationConfig, secrets) {
692
- if (!LLM_INTEGRATION_IDS.has(integrationId)) return runtimeConfig;
693
- if (integrationConfig?.mode === "byok") {
694
- const apiKey = secrets?.get("api_key");
695
- const config = structuredClone(runtimeConfig);
696
- const providers = config.models?.providers;
697
- if (providers?.[integrationId]) {
698
- delete providers[integrationId].baseUrl;
699
- if (apiKey) providers[integrationId].apiKey = apiKey;
700
- }
701
- return config;
702
- }
703
- return runtimeConfig;
704
- }
705
- var OpenClawApplier = class {
706
- runtime = "openclaw";
707
- workspace;
708
- skillsDir;
709
- configPath;
710
- constructor(options) {
711
- this.workspace = options.workspace;
712
- this.skillsDir = options.skillsDir ?? DEFAULT_SKILLS_DIR;
713
- this.configPath = options.configPath ?? join(this.workspace, "config.json");
714
- }
715
- async applyPlugin(pkg) {
716
- await execFileAsync("pnpm", ["add", pkg], {
717
- cwd: this.workspace,
718
- timeout: 6e4
719
- });
720
- }
721
- async removePlugin(pkg) {
722
- await execFileAsync("pnpm", ["remove", pkg], {
723
- cwd: this.workspace,
724
- timeout: 3e4
725
- });
726
- }
727
- applySkill(name, srcPath) {
728
- if (!existsSync(srcPath)) throw new Error(`Skill source path not found: ${srcPath}`);
729
- mkdirSync(this.skillsDir, { recursive: true });
730
- cpSync(srcPath, join(this.skillsDir, name), { recursive: true });
731
- return Promise.resolve();
732
- }
733
- removeSkill(name) {
734
- const skillPath = join(this.skillsDir, name);
735
- if (existsSync(skillPath)) rmSync(skillPath, {
736
- recursive: true,
737
- force: true
738
- });
739
- return Promise.resolve();
740
- }
741
- /**
742
- * Deep-merge integration config into the OpenClaw agent config file.
743
- *
744
- * Each integration's config contribution is tracked in
745
- * `_integrations.{integrationId}` within the config file so it can be
746
- * cleanly removed later.
747
- */
748
- applyConfig(integrationId, config) {
749
- const current = this.readConfig();
750
- const integrations = current._integrations ?? {};
751
- integrations[integrationId] = config;
752
- current._integrations = integrations;
753
- const merged = deepMerge(current, config);
754
- merged._integrations = current._integrations;
755
- this.writeConfig(merged);
756
- return Promise.resolve();
757
- }
758
- /**
759
- * Remove config previously applied by an integration.
760
- *
761
- * Rebuilds the config by re-merging all remaining integrations' configs,
762
- * ensuring clean removal without orphaned keys.
763
- */
764
- removeConfig(integrationId) {
765
- const current = this.readConfig();
766
- const integrations = current._integrations ?? {};
767
- if (!(integrationId in integrations)) return Promise.resolve();
768
- const remainingIntegrations = Object.fromEntries(Object.entries(integrations).filter(([key]) => key !== integrationId));
769
- let rebuilt = this.getBaseConfig(current);
770
- for (const cfg of Object.values(remainingIntegrations)) rebuilt = deepMerge(rebuilt, cfg);
771
- rebuilt._integrations = remainingIntegrations;
772
- this.writeConfig(rebuilt);
773
- return Promise.resolve();
774
- }
775
- isAvailable() {
776
- return Promise.resolve(existsSync(this.workspace));
777
- }
778
- readConfig() {
779
- if (!existsSync(this.configPath)) return {};
780
- try {
781
- return JSON.parse(readFileSync(this.configPath, "utf-8"));
782
- } catch {
783
- return {};
784
- }
785
- }
786
- writeConfig(config) {
787
- mkdirSync(join(this.configPath, ".."), { recursive: true });
788
- writeFileSync(this.configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
789
- }
790
- /**
791
- * Extract the base config by stripping all keys that were contributed
792
- * by integrations. This is done by removing each integration's keys
793
- * from the current config.
794
- */
795
- getBaseConfig(current) {
796
- const integrations = current._integrations ?? {};
797
- const allIntegrationKeys = /* @__PURE__ */ new Set();
798
- for (const cfg of Object.values(integrations)) for (const key of Object.keys(cfg)) allIntegrationKeys.add(key);
799
- const base = {};
800
- for (const [key, val] of Object.entries(current)) {
801
- if (key === "_integrations") continue;
802
- if (!allIntegrationKeys.has(key)) base[key] = val;
803
- }
804
- return base;
805
- }
806
- };
807
- //#endregion
808
645
  //#region src/integration-manager.ts
809
646
  /**
810
647
  * Integration Manager — full lifecycle management for Alfe integrations.
@@ -839,12 +676,12 @@ var IntegrationManager = class {
839
676
  /** In-memory secret store — NEVER persisted to disk */
840
677
  secrets = /* @__PURE__ */ new Map();
841
678
  constructor(options) {
842
- this.state = new StateManager(options?.statePath);
843
- this.registry = new Registry();
679
+ this.state = new StateManager(options.statePath);
680
+ this.registry = new Registry(options.registryFetcher);
844
681
  this.resolver = new Resolver(this.registry);
845
- this.installer = new Installer(options?.integrationsDir);
846
- this.runtimeAppliers = options?.runtimeAppliers ?? /* @__PURE__ */ new Map();
847
- this.lockManager = new LockManager(options?.lockPath);
682
+ this.installer = new Installer(options.integrationsDir);
683
+ this.runtimeAppliers = options.runtimeAppliers ?? /* @__PURE__ */ new Map();
684
+ this.lockManager = new LockManager(options.lockPath);
848
685
  }
849
686
  /**
850
687
  * Install an integration from the registry.
@@ -1026,9 +863,8 @@ var IntegrationManager = class {
1026
863
  await applier.applySkill(skillName, srcPath);
1027
864
  }
1028
865
  if (runtimeConfig && Object.keys(runtimeConfig).length > 0) {
1029
- const resolvedConfig = resolveLlmRuntimeConfig(integrationId, runtimeConfig, entry.config, this.secrets.get(integrationId));
1030
866
  this.log.info(`Applying config for ${integrationId} to ${runtimeName}`);
1031
- await applier.applyConfig(integrationId, resolvedConfig);
867
+ await applier.applyConfig(integrationId, runtimeConfig);
1032
868
  }
1033
869
  if (plugins.length > 0 || skills.length > 0) this.lockManager.addEntries(runtimeName, integrationId, manifest.version, plugins, skills, installPath);
1034
870
  }
@@ -1313,6 +1149,134 @@ var IntegrationManager = class {
1313
1149
  }
1314
1150
  };
1315
1151
  //#endregion
1152
+ //#region src/appliers/openclaw-applier.ts
1153
+ /**
1154
+ * OpenClawApplier — applies plugins, skills, and config to the OpenClaw runtime.
1155
+ *
1156
+ * Plugins are installed via `pnpm add` in the OpenClaw workspace directory.
1157
+ * Skills are copied to ~/.alfe/skills/{name}.
1158
+ * Config is deep-merged into the OpenClaw agent config, with per-integration
1159
+ * tracking so changes can be cleanly removed on deactivation.
1160
+ */
1161
+ const execFileAsync = promisify(execFile);
1162
+ const DEFAULT_SKILLS_DIR = join(homedir(), ".alfe", "skills");
1163
+ /**
1164
+ * Deep-merge source into target, returning a new object.
1165
+ * Arrays are replaced, not concatenated.
1166
+ */
1167
+ function deepMerge(target, source) {
1168
+ const result = { ...target };
1169
+ for (const key of Object.keys(source)) {
1170
+ const srcVal = source[key];
1171
+ const tgtVal = result[key];
1172
+ if (srcVal !== null && typeof srcVal === "object" && !Array.isArray(srcVal) && tgtVal !== null && typeof tgtVal === "object" && !Array.isArray(tgtVal)) result[key] = deepMerge(tgtVal, srcVal);
1173
+ else result[key] = srcVal;
1174
+ }
1175
+ return result;
1176
+ }
1177
+ var OpenClawApplier = class {
1178
+ runtime = "openclaw";
1179
+ workspace;
1180
+ skillsDir;
1181
+ configPath;
1182
+ constructor(options) {
1183
+ this.workspace = options.workspace;
1184
+ this.skillsDir = options.skillsDir ?? DEFAULT_SKILLS_DIR;
1185
+ this.configPath = options.configPath ?? join(this.workspace, "config.json");
1186
+ }
1187
+ async applyPlugin(pkg) {
1188
+ await execFileAsync("pnpm", ["add", pkg], {
1189
+ cwd: this.workspace,
1190
+ timeout: 6e4
1191
+ });
1192
+ }
1193
+ async removePlugin(pkg) {
1194
+ await execFileAsync("pnpm", ["remove", pkg], {
1195
+ cwd: this.workspace,
1196
+ timeout: 3e4
1197
+ });
1198
+ }
1199
+ applySkill(name, srcPath) {
1200
+ if (!existsSync(srcPath)) throw new Error(`Skill source path not found: ${srcPath}`);
1201
+ mkdirSync(this.skillsDir, { recursive: true });
1202
+ cpSync(srcPath, join(this.skillsDir, name), { recursive: true });
1203
+ return Promise.resolve();
1204
+ }
1205
+ removeSkill(name) {
1206
+ const skillPath = join(this.skillsDir, name);
1207
+ if (existsSync(skillPath)) rmSync(skillPath, {
1208
+ recursive: true,
1209
+ force: true
1210
+ });
1211
+ return Promise.resolve();
1212
+ }
1213
+ /**
1214
+ * Deep-merge integration config into the OpenClaw agent config file.
1215
+ *
1216
+ * Each integration's config contribution is tracked in
1217
+ * `_integrations.{integrationId}` within the config file so it can be
1218
+ * cleanly removed later.
1219
+ */
1220
+ applyConfig(integrationId, config) {
1221
+ const current = this.readConfig();
1222
+ const integrations = current._integrations ?? {};
1223
+ integrations[integrationId] = config;
1224
+ current._integrations = integrations;
1225
+ const merged = deepMerge(current, config);
1226
+ merged._integrations = current._integrations;
1227
+ this.writeConfig(merged);
1228
+ return Promise.resolve();
1229
+ }
1230
+ /**
1231
+ * Remove config previously applied by an integration.
1232
+ *
1233
+ * Rebuilds the config by re-merging all remaining integrations' configs,
1234
+ * ensuring clean removal without orphaned keys.
1235
+ */
1236
+ removeConfig(integrationId) {
1237
+ const current = this.readConfig();
1238
+ const integrations = current._integrations ?? {};
1239
+ if (!(integrationId in integrations)) return Promise.resolve();
1240
+ const remainingIntegrations = Object.fromEntries(Object.entries(integrations).filter(([key]) => key !== integrationId));
1241
+ let rebuilt = this.getBaseConfig(current);
1242
+ for (const cfg of Object.values(remainingIntegrations)) rebuilt = deepMerge(rebuilt, cfg);
1243
+ rebuilt._integrations = remainingIntegrations;
1244
+ this.writeConfig(rebuilt);
1245
+ return Promise.resolve();
1246
+ }
1247
+ isAvailable() {
1248
+ return Promise.resolve(existsSync(this.workspace));
1249
+ }
1250
+ readConfig() {
1251
+ if (!existsSync(this.configPath)) return {};
1252
+ try {
1253
+ return JSON.parse(readFileSync(this.configPath, "utf-8"));
1254
+ } catch {
1255
+ return {};
1256
+ }
1257
+ }
1258
+ writeConfig(config) {
1259
+ mkdirSync(join(this.configPath, ".."), { recursive: true });
1260
+ writeFileSync(this.configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
1261
+ }
1262
+ /**
1263
+ * Extract the base config by stripping all keys that were contributed
1264
+ * by integrations. This is done by removing each integration's keys
1265
+ * from the current config.
1266
+ */
1267
+ getBaseConfig(current) {
1268
+ const integrations = current._integrations ?? {};
1269
+ const allIntegrationKeys = /* @__PURE__ */ new Set();
1270
+ for (const cfg of Object.values(integrations)) for (const key of Object.keys(cfg)) allIntegrationKeys.add(key);
1271
+ const base = {};
1272
+ for (const [key, val] of Object.entries(current)) {
1273
+ if (key === "_integrations") continue;
1274
+ if (!allIntegrationKeys.has(key)) base[key] = val;
1275
+ }
1276
+ return base;
1277
+ }
1278
+ };
1279
+ //#endregion
1316
1280
  //#region src/adapter.ts
1317
1281
  var IntegrationManagerAdapter = class {
1318
1282
  constructor(manager) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/integrations",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Integration lifecycle management for Alfe — registry, resolution, installation, and state",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -13,7 +13,7 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "@auriclabs/logger": "^0.1.1",
16
- "@alfe.ai/integration-manifest": "^0.0.1"
16
+ "@alfe.ai/integration-manifest": "^0.0.2"
17
17
  },
18
18
  "files": [
19
19
  "dist"