@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.
- package/dist/index.d.ts +48 -17
- package/dist/index.js +146 -182
- 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
|
|
70
|
+
private fetcher;
|
|
31
71
|
/**
|
|
32
|
-
* @param
|
|
33
|
-
*
|
|
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(
|
|
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
|
|
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
|
-
|
|
11
|
+
fetcher;
|
|
14
12
|
/**
|
|
15
|
-
* @param
|
|
16
|
-
*
|
|
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(
|
|
19
|
-
this.
|
|
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
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
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
|
|
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
|
|
846
|
-
this.runtimeAppliers = options
|
|
847
|
-
this.lockManager = new LockManager(options
|
|
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,
|
|
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.
|
|
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.
|
|
16
|
+
"@alfe.ai/integration-manifest": "^0.0.2"
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
19
|
"dist"
|