@alfe.ai/integrations 0.0.30 → 0.0.32
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 +10 -0
- package/dist/index.js +148 -19
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -796,6 +796,16 @@ declare class OpenClawApplier implements RuntimeApplier {
|
|
|
796
796
|
* plugins under 2026.5+, which made force-mode skip its uninstall step.
|
|
797
797
|
*/
|
|
798
798
|
private isPluginInstalled;
|
|
799
|
+
/**
|
|
800
|
+
* Remove an extensions/ install that has no matching record in
|
|
801
|
+
* plugins/installs.json. OpenClaw 2026.5+ tracks installs via the records
|
|
802
|
+
* file — a leftover dir from 2026.4 is invisible to `openclaw plugins
|
|
803
|
+
* uninstall` (it errors with "not managed by plugins config/install
|
|
804
|
+
* records") and its pre-migration manifest blocks the new effective-plugin-
|
|
805
|
+
* set loader from importing it. Resetting the dir makes the next install
|
|
806
|
+
* write through the npm path with a proper tracking record.
|
|
807
|
+
*/
|
|
808
|
+
private cleanupUntrackedExtensionInstall;
|
|
799
809
|
removePlugin(pkg: string): Promise<void>;
|
|
800
810
|
applySkill(name: string, srcPath: string): Promise<void>;
|
|
801
811
|
applyClawHubSkill(slug: string): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -1509,18 +1509,63 @@ var IntegrationManager = class {
|
|
|
1509
1509
|
const execFileAsync = promisify(execFile);
|
|
1510
1510
|
const log = createLogger("OpenClawApplier");
|
|
1511
1511
|
const DEFAULT_SKILLS_DIR = join(homedir(), ".alfe", "skills");
|
|
1512
|
-
/**
|
|
1513
|
-
* Flatten a nested config object into dot-path key-value pairs.
|
|
1514
|
-
* e.g. { gateway: { bind: "lan" } } → [["gateway.bind", "lan"]]
|
|
1515
|
-
*/
|
|
1516
1512
|
function flattenConfig(obj, prefix = "") {
|
|
1517
|
-
const
|
|
1513
|
+
const entries = [];
|
|
1518
1514
|
for (const [key, val] of Object.entries(obj)) {
|
|
1515
|
+
if (key.includes(".")) {
|
|
1516
|
+
entries.push({
|
|
1517
|
+
kind: "subtree",
|
|
1518
|
+
parentPath: prefix,
|
|
1519
|
+
dottedKey: key,
|
|
1520
|
+
value: val
|
|
1521
|
+
});
|
|
1522
|
+
continue;
|
|
1523
|
+
}
|
|
1519
1524
|
const path = prefix ? `${prefix}.${key}` : key;
|
|
1520
|
-
if (val !== null && typeof val === "object" && !Array.isArray(val))
|
|
1521
|
-
else
|
|
1525
|
+
if (val !== null && typeof val === "object" && !Array.isArray(val)) entries.push(...flattenConfig(val, path));
|
|
1526
|
+
else entries.push({
|
|
1527
|
+
kind: "leaf",
|
|
1528
|
+
path,
|
|
1529
|
+
value: val
|
|
1530
|
+
});
|
|
1522
1531
|
}
|
|
1523
|
-
return
|
|
1532
|
+
return entries;
|
|
1533
|
+
}
|
|
1534
|
+
function partitionEntries(entries) {
|
|
1535
|
+
const leaves = [];
|
|
1536
|
+
const subtreesByParent = /* @__PURE__ */ new Map();
|
|
1537
|
+
for (const entry of entries) {
|
|
1538
|
+
if (entry.kind === "leaf") {
|
|
1539
|
+
leaves.push({
|
|
1540
|
+
path: entry.path,
|
|
1541
|
+
value: entry.value
|
|
1542
|
+
});
|
|
1543
|
+
continue;
|
|
1544
|
+
}
|
|
1545
|
+
if (!entry.parentPath) throw new Error(`OpenClawApplier: top-level config keys containing dots are not supported (key: "${entry.dottedKey}")`);
|
|
1546
|
+
let bucket = subtreesByParent.get(entry.parentPath);
|
|
1547
|
+
if (!bucket) {
|
|
1548
|
+
bucket = /* @__PURE__ */ new Map();
|
|
1549
|
+
subtreesByParent.set(entry.parentPath, bucket);
|
|
1550
|
+
}
|
|
1551
|
+
bucket.set(entry.dottedKey, entry.value);
|
|
1552
|
+
}
|
|
1553
|
+
return {
|
|
1554
|
+
leaves,
|
|
1555
|
+
subtreesByParent
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
async function readParentObject(parentPath) {
|
|
1559
|
+
try {
|
|
1560
|
+
const { stdout } = await execFileAsync("openclaw", [
|
|
1561
|
+
"config",
|
|
1562
|
+
"get",
|
|
1563
|
+
parentPath
|
|
1564
|
+
], { timeout: 1e4 });
|
|
1565
|
+
const parsed = JSON.parse(stdout.trim());
|
|
1566
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
1567
|
+
} catch {}
|
|
1568
|
+
return {};
|
|
1524
1569
|
}
|
|
1525
1570
|
var OpenClawApplier = class {
|
|
1526
1571
|
runtime = "openclaw";
|
|
@@ -1538,6 +1583,7 @@ var OpenClawApplier = class {
|
|
|
1538
1583
|
}
|
|
1539
1584
|
async applyPlugin(pkg, _installPath, opts) {
|
|
1540
1585
|
await this.ensurePluginsAllow(pkg);
|
|
1586
|
+
this.cleanupUntrackedExtensionInstall(pkg);
|
|
1541
1587
|
if (opts?.force && this.isPluginInstalled(pkg)) {
|
|
1542
1588
|
log.info({ pkg }, "Force mode — uninstalling plugin before reinstall");
|
|
1543
1589
|
try {
|
|
@@ -1623,12 +1669,57 @@ var OpenClawApplier = class {
|
|
|
1623
1669
|
* plugins under 2026.5+, which made force-mode skip its uninstall step.
|
|
1624
1670
|
*/
|
|
1625
1671
|
isPluginInstalled(pkg) {
|
|
1626
|
-
if (existsSync(join(
|
|
1627
|
-
const extensionsDir = join(
|
|
1672
|
+
if (existsSync(join(this.home, "npm", "node_modules", ...pkg.split("/")))) return true;
|
|
1673
|
+
const extensionsDir = join(this.home, "extensions");
|
|
1628
1674
|
if (!existsSync(extensionsDir)) return false;
|
|
1629
1675
|
const prefix = pkg.replaceAll("/", "-") + "-";
|
|
1630
1676
|
return readdirSync(extensionsDir).some((dir) => dir.startsWith(prefix) && /^[0-9a-f]+$/i.test(dir.slice(prefix.length)));
|
|
1631
1677
|
}
|
|
1678
|
+
/**
|
|
1679
|
+
* Remove an extensions/ install that has no matching record in
|
|
1680
|
+
* plugins/installs.json. OpenClaw 2026.5+ tracks installs via the records
|
|
1681
|
+
* file — a leftover dir from 2026.4 is invisible to `openclaw plugins
|
|
1682
|
+
* uninstall` (it errors with "not managed by plugins config/install
|
|
1683
|
+
* records") and its pre-migration manifest blocks the new effective-plugin-
|
|
1684
|
+
* set loader from importing it. Resetting the dir makes the next install
|
|
1685
|
+
* write through the npm path with a proper tracking record.
|
|
1686
|
+
*/
|
|
1687
|
+
cleanupUntrackedExtensionInstall(pkg) {
|
|
1688
|
+
const installsPath = join(this.home, "plugins", "installs.json");
|
|
1689
|
+
if (!existsSync(installsPath)) return;
|
|
1690
|
+
let tracked = false;
|
|
1691
|
+
try {
|
|
1692
|
+
const raw = readFileSync(installsPath, "utf8");
|
|
1693
|
+
const data = JSON.parse(raw);
|
|
1694
|
+
tracked = Object.prototype.hasOwnProperty.call(data.installRecords ?? {}, pkg);
|
|
1695
|
+
} catch {
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
if (tracked) return;
|
|
1699
|
+
const extensionsDir = join(this.home, "extensions");
|
|
1700
|
+
if (!existsSync(extensionsDir)) return;
|
|
1701
|
+
const prefix = pkg.replaceAll("/", "-") + "-";
|
|
1702
|
+
const stale = readdirSync(extensionsDir).filter((dir) => dir.startsWith(prefix) && /^[0-9a-f]+$/i.test(dir.slice(prefix.length)));
|
|
1703
|
+
for (const dir of stale) {
|
|
1704
|
+
const fullPath = join(extensionsDir, dir);
|
|
1705
|
+
try {
|
|
1706
|
+
rmSync(fullPath, {
|
|
1707
|
+
recursive: true,
|
|
1708
|
+
force: true
|
|
1709
|
+
});
|
|
1710
|
+
log.info({
|
|
1711
|
+
pkg,
|
|
1712
|
+
removed: fullPath
|
|
1713
|
+
}, "Removed untracked extensions/ install — will reinstall via npm path");
|
|
1714
|
+
} catch (err) {
|
|
1715
|
+
log.warn({
|
|
1716
|
+
pkg,
|
|
1717
|
+
removed: fullPath,
|
|
1718
|
+
err: err instanceof Error ? err.message : String(err)
|
|
1719
|
+
}, "Failed to remove untracked extensions/ install");
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1632
1723
|
async removePlugin(pkg) {
|
|
1633
1724
|
await execFileAsync("openclaw", [
|
|
1634
1725
|
"plugins",
|
|
@@ -1695,21 +1786,36 @@ var OpenClawApplier = class {
|
|
|
1695
1786
|
integrations[integrationId] = config;
|
|
1696
1787
|
tracking._integrations = integrations;
|
|
1697
1788
|
this.writeTracking(tracking);
|
|
1698
|
-
const
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1789
|
+
const { leaves, subtreesByParent } = partitionEntries(flattenConfig(config));
|
|
1790
|
+
for (const [parentPath, dottedKvs] of subtreesByParent) {
|
|
1791
|
+
const merged = { ...await readParentObject(parentPath) };
|
|
1792
|
+
for (const [k, v] of dottedKvs) merged[k] = v;
|
|
1793
|
+
try {
|
|
1794
|
+
await execFileAsync("openclaw", [
|
|
1795
|
+
"config",
|
|
1796
|
+
"set",
|
|
1797
|
+
parentPath,
|
|
1798
|
+
JSON.stringify(merged)
|
|
1799
|
+
], { timeout: 1e4 });
|
|
1800
|
+
} catch (err) {
|
|
1801
|
+
log.error({
|
|
1802
|
+
err: err instanceof Error ? err.message : String(err),
|
|
1803
|
+
parentPath
|
|
1804
|
+
}, "Failed to set config subtree via openclaw config set");
|
|
1805
|
+
throw err;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
if (leaves.length > 0) try {
|
|
1703
1809
|
await execFileAsync("openclaw", [
|
|
1704
1810
|
"config",
|
|
1705
1811
|
"set",
|
|
1706
1812
|
"--batch-json",
|
|
1707
|
-
JSON.stringify(
|
|
1813
|
+
JSON.stringify(leaves)
|
|
1708
1814
|
], { timeout: 1e4 });
|
|
1709
1815
|
} catch (err) {
|
|
1710
1816
|
log.error({
|
|
1711
1817
|
err: err instanceof Error ? err.message : String(err),
|
|
1712
|
-
batch
|
|
1818
|
+
batch: leaves
|
|
1713
1819
|
}, "Failed to set config via openclaw config set --batch-json");
|
|
1714
1820
|
throw err;
|
|
1715
1821
|
}
|
|
@@ -1725,8 +1831,8 @@ var OpenClawApplier = class {
|
|
|
1725
1831
|
const integrations = tracking._integrations ?? {};
|
|
1726
1832
|
if (!(integrationId in integrations)) return;
|
|
1727
1833
|
const integrationConfig = integrations[integrationId];
|
|
1728
|
-
const
|
|
1729
|
-
for (const
|
|
1834
|
+
const { leaves, subtreesByParent } = partitionEntries(flattenConfig(integrationConfig));
|
|
1835
|
+
for (const { path } of leaves) try {
|
|
1730
1836
|
await execFileAsync("openclaw", [
|
|
1731
1837
|
"config",
|
|
1732
1838
|
"unset",
|
|
@@ -1738,6 +1844,29 @@ var OpenClawApplier = class {
|
|
|
1738
1844
|
path
|
|
1739
1845
|
}, "Failed to unset config via openclaw config unset");
|
|
1740
1846
|
}
|
|
1847
|
+
for (const [parentPath, dottedKvs] of subtreesByParent) {
|
|
1848
|
+
const existing = await readParentObject(parentPath);
|
|
1849
|
+
const remaining = Object.fromEntries(Object.entries(existing).filter(([k]) => !dottedKvs.has(k)));
|
|
1850
|
+
if (Object.keys(existing).length === 0) continue;
|
|
1851
|
+
try {
|
|
1852
|
+
if (Object.keys(remaining).length === 0) await execFileAsync("openclaw", [
|
|
1853
|
+
"config",
|
|
1854
|
+
"unset",
|
|
1855
|
+
parentPath
|
|
1856
|
+
], { timeout: 1e4 });
|
|
1857
|
+
else await execFileAsync("openclaw", [
|
|
1858
|
+
"config",
|
|
1859
|
+
"set",
|
|
1860
|
+
parentPath,
|
|
1861
|
+
JSON.stringify(remaining)
|
|
1862
|
+
], { timeout: 1e4 });
|
|
1863
|
+
} catch (err) {
|
|
1864
|
+
log.warn({
|
|
1865
|
+
err: err instanceof Error ? err.message : String(err),
|
|
1866
|
+
parentPath
|
|
1867
|
+
}, "Failed to update parent config during remove");
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1741
1870
|
tracking._integrations = Object.fromEntries(Object.entries(integrations).filter(([key]) => key !== integrationId));
|
|
1742
1871
|
this.writeTracking(tracking);
|
|
1743
1872
|
}
|
package/package.json
CHANGED