@alfe.ai/integrations 0.0.7 → 0.0.9
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 +14 -16
- package/dist/index.js +132 -70
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -604,16 +604,21 @@ interface OpenClawApplierOptions {
|
|
|
604
604
|
workspace: string;
|
|
605
605
|
/** Override skills directory (defaults to ~/.alfe/skills/) */
|
|
606
606
|
skillsDir?: string;
|
|
607
|
-
/** Path to the
|
|
607
|
+
/** Path to the integration tracking file (defaults to {workspace}/config.json) */
|
|
608
608
|
configPath?: string;
|
|
609
609
|
}
|
|
610
610
|
declare class OpenClawApplier implements RuntimeApplier {
|
|
611
611
|
readonly runtime = "openclaw";
|
|
612
612
|
private workspace;
|
|
613
613
|
private skillsDir;
|
|
614
|
-
private
|
|
614
|
+
private trackingPath;
|
|
615
615
|
constructor(options: OpenClawApplierOptions);
|
|
616
616
|
applyPlugin(pkg: string): Promise<void>;
|
|
617
|
+
/**
|
|
618
|
+
* Ensure the plugin is in plugins.allow in openclaw.json.
|
|
619
|
+
* Uses `openclaw config set` to avoid clobbering OpenClaw's own file format.
|
|
620
|
+
*/
|
|
621
|
+
private ensurePluginsAllow;
|
|
617
622
|
/**
|
|
618
623
|
* Check if a plugin is already installed in ~/.openclaw/extensions/.
|
|
619
624
|
* OpenClaw names extension dirs as {pkg-name-with-dashes}-{hash}.
|
|
@@ -623,29 +628,22 @@ declare class OpenClawApplier implements RuntimeApplier {
|
|
|
623
628
|
applySkill(name: string, srcPath: string): Promise<void>;
|
|
624
629
|
removeSkill(name: string): Promise<void>;
|
|
625
630
|
/**
|
|
626
|
-
*
|
|
631
|
+
* Apply integration config to the OpenClaw runtime via `openclaw config set`.
|
|
627
632
|
*
|
|
628
|
-
* Each integration's config contribution is tracked in
|
|
629
|
-
*
|
|
630
|
-
* cleanly removed later.
|
|
633
|
+
* Each integration's config contribution is tracked in the tracking file
|
|
634
|
+
* (config.json) so it can be cleanly removed later.
|
|
631
635
|
*/
|
|
632
636
|
applyConfig(integrationId: string, config: Record<string, unknown>): Promise<void>;
|
|
633
637
|
/**
|
|
634
638
|
* Remove config previously applied by an integration.
|
|
635
639
|
*
|
|
636
|
-
*
|
|
637
|
-
*
|
|
640
|
+
* Reads the tracking file to find which config keys this integration set,
|
|
641
|
+
* then removes them via `openclaw config unset`.
|
|
638
642
|
*/
|
|
639
643
|
removeConfig(integrationId: string): Promise<void>;
|
|
640
644
|
isAvailable(): Promise<boolean>;
|
|
641
|
-
private
|
|
642
|
-
private
|
|
643
|
-
/**
|
|
644
|
-
* Extract the base config by stripping all keys that were contributed
|
|
645
|
-
* by integrations. This is done by removing each integration's keys
|
|
646
|
-
* from the current config.
|
|
647
|
-
*/
|
|
648
|
-
private getBaseConfig;
|
|
645
|
+
private readTracking;
|
|
646
|
+
private writeTracking;
|
|
649
647
|
}
|
|
650
648
|
//#endregion
|
|
651
649
|
//#region src/lock.d.ts
|
package/dist/index.js
CHANGED
|
@@ -662,6 +662,21 @@ async function runHookWithContext(integrationPath, hookScript, options) {
|
|
|
662
662
|
* plugins/skills to runtimes. deactivate() removes them.
|
|
663
663
|
*/
|
|
664
664
|
/**
|
|
665
|
+
* Deep-walk an object and replace `{{config.KEY}}` patterns with values
|
|
666
|
+
* from the per-agent config. Used to compose manifest runtime config
|
|
667
|
+
* with per-agent secrets (e.g., gateway token).
|
|
668
|
+
*/
|
|
669
|
+
function interpolateSelfConfig(obj, config) {
|
|
670
|
+
const result = {};
|
|
671
|
+
for (const [key, value] of Object.entries(obj)) if (typeof value === "string") result[key] = value.replace(/\{\{config\.([a-zA-Z0-9_]+)\}\}/g, (_match, configKey) => {
|
|
672
|
+
const val = config[configKey];
|
|
673
|
+
return typeof val === "string" || typeof val === "number" ? String(val) : _match;
|
|
674
|
+
});
|
|
675
|
+
else if (value && typeof value === "object" && !Array.isArray(value)) result[key] = interpolateSelfConfig(value, config);
|
|
676
|
+
else result[key] = value;
|
|
677
|
+
return result;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
665
680
|
* Merge universal installs with runtime-specific installs from the manifest.
|
|
666
681
|
*/
|
|
667
682
|
function resolveInstallsForRuntime(manifest, runtime) {
|
|
@@ -871,8 +886,10 @@ var IntegrationManager = class {
|
|
|
871
886
|
await applier.applySkill(skillName, srcPath);
|
|
872
887
|
}
|
|
873
888
|
if (runtimeConfig && Object.keys(runtimeConfig).length > 0) {
|
|
889
|
+
const agentConfig = entry.config;
|
|
890
|
+
const interpolatedConfig = interpolateSelfConfig(runtimeConfig, agentConfig);
|
|
874
891
|
this.log.info(`Applying config for ${integrationId} to ${runtimeName}`);
|
|
875
|
-
await applier.applyConfig(integrationId,
|
|
892
|
+
await applier.applyConfig(integrationId, interpolatedConfig);
|
|
876
893
|
}
|
|
877
894
|
if (plugins.length > 0 || skills.length > 0) this.lockManager.addEntries(runtimeName, integrationId, manifest.version, plugins, skills, installPath);
|
|
878
895
|
}
|
|
@@ -1161,44 +1178,82 @@ var IntegrationManager = class {
|
|
|
1161
1178
|
/**
|
|
1162
1179
|
* OpenClawApplier — applies plugins, skills, and config to the OpenClaw runtime.
|
|
1163
1180
|
*
|
|
1164
|
-
* Plugins are installed via `
|
|
1181
|
+
* Plugins are installed via `openclaw plugins install`.
|
|
1165
1182
|
* Skills are copied to ~/.alfe/skills/{name}.
|
|
1166
|
-
* Config is
|
|
1167
|
-
*
|
|
1183
|
+
* Config is applied via `openclaw config set` so OpenClaw manages its own
|
|
1184
|
+
* config file (openclaw.json) without clobbering. Per-integration tracking
|
|
1185
|
+
* is stored in a separate tracking file (config.json) for clean removal.
|
|
1168
1186
|
*/
|
|
1169
1187
|
const execFileAsync = promisify(execFile);
|
|
1188
|
+
const log = createLogger("OpenClawApplier");
|
|
1170
1189
|
const DEFAULT_SKILLS_DIR = join(homedir(), ".alfe", "skills");
|
|
1171
1190
|
/**
|
|
1172
|
-
*
|
|
1173
|
-
*
|
|
1191
|
+
* Flatten a nested config object into dot-path key-value pairs.
|
|
1192
|
+
* e.g. { gateway: { bind: "lan" } } → [["gateway.bind", "lan"]]
|
|
1174
1193
|
*/
|
|
1175
|
-
function
|
|
1176
|
-
const
|
|
1177
|
-
for (const key of Object.
|
|
1178
|
-
const
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
return result;
|
|
1194
|
+
function flattenConfig(obj, prefix = "") {
|
|
1195
|
+
const pairs = [];
|
|
1196
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
1197
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
1198
|
+
if (val !== null && typeof val === "object" && !Array.isArray(val)) pairs.push(...flattenConfig(val, path));
|
|
1199
|
+
else pairs.push([path, val]);
|
|
1200
|
+
}
|
|
1201
|
+
return pairs;
|
|
1184
1202
|
}
|
|
1185
1203
|
var OpenClawApplier = class {
|
|
1186
1204
|
runtime = "openclaw";
|
|
1187
1205
|
workspace;
|
|
1188
1206
|
skillsDir;
|
|
1189
|
-
|
|
1207
|
+
trackingPath;
|
|
1190
1208
|
constructor(options) {
|
|
1191
1209
|
this.workspace = options.workspace;
|
|
1192
1210
|
this.skillsDir = options.skillsDir ?? DEFAULT_SKILLS_DIR;
|
|
1193
|
-
this.
|
|
1211
|
+
this.trackingPath = options.configPath ?? join(this.workspace, "config.json");
|
|
1194
1212
|
}
|
|
1195
1213
|
async applyPlugin(pkg) {
|
|
1196
|
-
if (this.isPluginInstalled(pkg))
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1214
|
+
if (!this.isPluginInstalled(pkg)) {
|
|
1215
|
+
await execFileAsync("openclaw", [
|
|
1216
|
+
"plugins",
|
|
1217
|
+
"install",
|
|
1218
|
+
pkg
|
|
1219
|
+
], { timeout: 6e4 });
|
|
1220
|
+
await new Promise((r) => {
|
|
1221
|
+
setTimeout(r, 500);
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
await this.ensurePluginsAllow(pkg);
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Ensure the plugin is in plugins.allow in openclaw.json.
|
|
1228
|
+
* Uses `openclaw config set` to avoid clobbering OpenClaw's own file format.
|
|
1229
|
+
*/
|
|
1230
|
+
async ensurePluginsAllow(pkg) {
|
|
1231
|
+
let currentAllow = [];
|
|
1232
|
+
try {
|
|
1233
|
+
const { stdout } = await execFileAsync("openclaw", [
|
|
1234
|
+
"config",
|
|
1235
|
+
"get",
|
|
1236
|
+
"plugins.allow"
|
|
1237
|
+
], { timeout: 1e4 });
|
|
1238
|
+
const parsed = JSON.parse(stdout.trim());
|
|
1239
|
+
if (Array.isArray(parsed)) currentAllow = parsed;
|
|
1240
|
+
} catch {}
|
|
1241
|
+
if (currentAllow.includes(pkg)) return;
|
|
1242
|
+
const updated = [...currentAllow, pkg];
|
|
1243
|
+
try {
|
|
1244
|
+
await execFileAsync("openclaw", [
|
|
1245
|
+
"config",
|
|
1246
|
+
"set",
|
|
1247
|
+
"plugins.allow",
|
|
1248
|
+
JSON.stringify(updated),
|
|
1249
|
+
"--strict-json"
|
|
1250
|
+
], { timeout: 1e4 });
|
|
1251
|
+
} catch (err) {
|
|
1252
|
+
log.warn({
|
|
1253
|
+
err: err instanceof Error ? err.message : String(err),
|
|
1254
|
+
pkg
|
|
1255
|
+
}, "Failed to set plugins.allow via openclaw config set");
|
|
1256
|
+
}
|
|
1202
1257
|
}
|
|
1203
1258
|
/**
|
|
1204
1259
|
* Check if a plugin is already installed in ~/.openclaw/extensions/.
|
|
@@ -1207,7 +1262,7 @@ var OpenClawApplier = class {
|
|
|
1207
1262
|
isPluginInstalled(pkg) {
|
|
1208
1263
|
const extensionsDir = join(homedir(), ".openclaw", "extensions");
|
|
1209
1264
|
if (!existsSync(extensionsDir)) return false;
|
|
1210
|
-
const prefix = pkg.replaceAll("/", "-")
|
|
1265
|
+
const prefix = pkg.replaceAll("/", "-");
|
|
1211
1266
|
return readdirSync(extensionsDir).some((dir) => dir.startsWith(prefix));
|
|
1212
1267
|
}
|
|
1213
1268
|
async removePlugin(pkg) {
|
|
@@ -1233,69 +1288,76 @@ var OpenClawApplier = class {
|
|
|
1233
1288
|
return Promise.resolve();
|
|
1234
1289
|
}
|
|
1235
1290
|
/**
|
|
1236
|
-
*
|
|
1291
|
+
* Apply integration config to the OpenClaw runtime via `openclaw config set`.
|
|
1237
1292
|
*
|
|
1238
|
-
* Each integration's config contribution is tracked in
|
|
1239
|
-
*
|
|
1240
|
-
* cleanly removed later.
|
|
1293
|
+
* Each integration's config contribution is tracked in the tracking file
|
|
1294
|
+
* (config.json) so it can be cleanly removed later.
|
|
1241
1295
|
*/
|
|
1242
|
-
applyConfig(integrationId, config) {
|
|
1243
|
-
const
|
|
1244
|
-
const integrations =
|
|
1296
|
+
async applyConfig(integrationId, config) {
|
|
1297
|
+
const tracking = this.readTracking();
|
|
1298
|
+
const integrations = tracking._integrations ?? {};
|
|
1245
1299
|
integrations[integrationId] = config;
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1300
|
+
tracking._integrations = integrations;
|
|
1301
|
+
this.writeTracking(tracking);
|
|
1302
|
+
const pairs = flattenConfig(config);
|
|
1303
|
+
for (const [path, value] of pairs) try {
|
|
1304
|
+
await execFileAsync("openclaw", [
|
|
1305
|
+
"config",
|
|
1306
|
+
"set",
|
|
1307
|
+
path,
|
|
1308
|
+
JSON.stringify(value),
|
|
1309
|
+
"--strict-json"
|
|
1310
|
+
], { timeout: 1e4 });
|
|
1311
|
+
} catch (err) {
|
|
1312
|
+
log.error({
|
|
1313
|
+
err: err instanceof Error ? err.message : String(err),
|
|
1314
|
+
path,
|
|
1315
|
+
value
|
|
1316
|
+
}, "Failed to set config via openclaw config set");
|
|
1317
|
+
throw err;
|
|
1318
|
+
}
|
|
1251
1319
|
}
|
|
1252
1320
|
/**
|
|
1253
1321
|
* Remove config previously applied by an integration.
|
|
1254
1322
|
*
|
|
1255
|
-
*
|
|
1256
|
-
*
|
|
1323
|
+
* Reads the tracking file to find which config keys this integration set,
|
|
1324
|
+
* then removes them via `openclaw config unset`.
|
|
1257
1325
|
*/
|
|
1258
|
-
removeConfig(integrationId) {
|
|
1259
|
-
const
|
|
1260
|
-
const integrations =
|
|
1261
|
-
if (!(integrationId in integrations)) return
|
|
1262
|
-
const
|
|
1263
|
-
|
|
1264
|
-
for (const
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1326
|
+
async removeConfig(integrationId) {
|
|
1327
|
+
const tracking = this.readTracking();
|
|
1328
|
+
const integrations = tracking._integrations ?? {};
|
|
1329
|
+
if (!(integrationId in integrations)) return;
|
|
1330
|
+
const integrationConfig = integrations[integrationId];
|
|
1331
|
+
const pairs = flattenConfig(integrationConfig);
|
|
1332
|
+
for (const [path] of pairs) try {
|
|
1333
|
+
await execFileAsync("openclaw", [
|
|
1334
|
+
"config",
|
|
1335
|
+
"unset",
|
|
1336
|
+
path
|
|
1337
|
+
], { timeout: 1e4 });
|
|
1338
|
+
} catch (err) {
|
|
1339
|
+
log.warn({
|
|
1340
|
+
err: err instanceof Error ? err.message : String(err),
|
|
1341
|
+
path
|
|
1342
|
+
}, "Failed to unset config via openclaw config unset");
|
|
1343
|
+
}
|
|
1344
|
+
tracking._integrations = Object.fromEntries(Object.entries(integrations).filter(([key]) => key !== integrationId));
|
|
1345
|
+
this.writeTracking(tracking);
|
|
1268
1346
|
}
|
|
1269
1347
|
isAvailable() {
|
|
1270
1348
|
return Promise.resolve(existsSync(this.workspace));
|
|
1271
1349
|
}
|
|
1272
|
-
|
|
1273
|
-
if (!existsSync(this.
|
|
1350
|
+
readTracking() {
|
|
1351
|
+
if (!existsSync(this.trackingPath)) return {};
|
|
1274
1352
|
try {
|
|
1275
|
-
return JSON.parse(readFileSync(this.
|
|
1353
|
+
return JSON.parse(readFileSync(this.trackingPath, "utf-8"));
|
|
1276
1354
|
} catch {
|
|
1277
1355
|
return {};
|
|
1278
1356
|
}
|
|
1279
1357
|
}
|
|
1280
|
-
|
|
1281
|
-
mkdirSync(join(this.
|
|
1282
|
-
writeFileSync(this.
|
|
1283
|
-
}
|
|
1284
|
-
/**
|
|
1285
|
-
* Extract the base config by stripping all keys that were contributed
|
|
1286
|
-
* by integrations. This is done by removing each integration's keys
|
|
1287
|
-
* from the current config.
|
|
1288
|
-
*/
|
|
1289
|
-
getBaseConfig(current) {
|
|
1290
|
-
const integrations = current._integrations ?? {};
|
|
1291
|
-
const allIntegrationKeys = /* @__PURE__ */ new Set();
|
|
1292
|
-
for (const cfg of Object.values(integrations)) for (const key of Object.keys(cfg)) allIntegrationKeys.add(key);
|
|
1293
|
-
const base = {};
|
|
1294
|
-
for (const [key, val] of Object.entries(current)) {
|
|
1295
|
-
if (key === "_integrations") continue;
|
|
1296
|
-
if (!allIntegrationKeys.has(key)) base[key] = val;
|
|
1297
|
-
}
|
|
1298
|
-
return base;
|
|
1358
|
+
writeTracking(config) {
|
|
1359
|
+
mkdirSync(join(this.trackingPath, ".."), { recursive: true });
|
|
1360
|
+
writeFileSync(this.trackingPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1299
1361
|
}
|
|
1300
1362
|
};
|
|
1301
1363
|
//#endregion
|
package/package.json
CHANGED