@alfe.ai/integrations 0.0.29 → 0.0.31
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 +28 -6
- package/dist/index.js +126 -37
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -245,6 +245,10 @@ interface ConfigSchemaField {
|
|
|
245
245
|
select_options?: SelectOption[];
|
|
246
246
|
/** OAuth provider identifier for oauth_connect fields */
|
|
247
247
|
oauth_provider?: string;
|
|
248
|
+
/** Force specific scope groups for OAuth (e.g. ["chat"]) — hides other scopes in the dashboard */
|
|
249
|
+
oauth_scopes?: string[];
|
|
250
|
+
/** Integration ID to patch on OAuth callback (when using a shared OAuth provider) */
|
|
251
|
+
oauth_integration_id?: string;
|
|
248
252
|
/** If true, this field is not shown in the dashboard UI (install wizard or configure modal) */
|
|
249
253
|
hidden?: boolean;
|
|
250
254
|
/** Only show this field if another field has a truthy value (string) or matches a specific value (object) */
|
|
@@ -748,16 +752,30 @@ declare function runHookWithContext(integrationPath: string, hookScript: string,
|
|
|
748
752
|
//#endregion
|
|
749
753
|
//#region src/appliers/openclaw-applier.d.ts
|
|
750
754
|
interface OpenClawApplierOptions {
|
|
751
|
-
/**
|
|
752
|
-
|
|
755
|
+
/**
|
|
756
|
+
* Path to the OpenClaw home directory (e.g. ~/.openclaw) — where openclaw.json,
|
|
757
|
+
* plugins/, and extensions/ live.
|
|
758
|
+
*/
|
|
759
|
+
home?: string;
|
|
760
|
+
/**
|
|
761
|
+
* Path to the agent workspace directory (e.g. ~/.openclaw/workspace) — where
|
|
762
|
+
* the agent's CWD lives, including SOUL.md / AGENTS.md / skills/. Defaults to
|
|
763
|
+
* `${home}/workspace` (OpenClaw's default for `agents.defaults.workspace`).
|
|
764
|
+
*/
|
|
765
|
+
agentWorkspace?: string;
|
|
766
|
+
/**
|
|
767
|
+
* @deprecated Use `home` instead. Kept for back-compat — when set, used as `home`.
|
|
768
|
+
*/
|
|
769
|
+
workspace?: string;
|
|
753
770
|
/** Override skills directory (defaults to ~/.alfe/skills/) */
|
|
754
771
|
skillsDir?: string;
|
|
755
|
-
/** Path to the integration tracking file (defaults to {
|
|
772
|
+
/** Path to the integration tracking file (defaults to {home}/config.json) */
|
|
756
773
|
configPath?: string;
|
|
757
774
|
}
|
|
758
775
|
declare class OpenClawApplier implements RuntimeApplier {
|
|
759
776
|
readonly runtime = "openclaw";
|
|
760
|
-
private
|
|
777
|
+
private home;
|
|
778
|
+
private agentWorkspace;
|
|
761
779
|
private skillsDir;
|
|
762
780
|
private trackingPath;
|
|
763
781
|
constructor(options: OpenClawApplierOptions);
|
|
@@ -770,8 +788,12 @@ declare class OpenClawApplier implements RuntimeApplier {
|
|
|
770
788
|
*/
|
|
771
789
|
private ensurePluginsAllow;
|
|
772
790
|
/**
|
|
773
|
-
* Check if a plugin is already installed
|
|
774
|
-
*
|
|
791
|
+
* Check if a plugin is already installed.
|
|
792
|
+
*
|
|
793
|
+
* OpenClaw 2026.4 stored plugins under `~/.openclaw/extensions/{pkg-name-with-dashes}-{hash}`.
|
|
794
|
+
* 2026.5+ added an npm registry at `~/.openclaw/npm/node_modules/<pkg>` for npm-source
|
|
795
|
+
* plugins. We must check both — the extensions-only check missed npm-installed
|
|
796
|
+
* plugins under 2026.5+, which made force-mode skip its uninstall step.
|
|
775
797
|
*/
|
|
776
798
|
private isPluginInstalled;
|
|
777
799
|
removePlugin(pkg: string): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -1509,28 +1509,77 @@ 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
|
+
});
|
|
1531
|
+
}
|
|
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);
|
|
1522
1552
|
}
|
|
1523
|
-
return
|
|
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";
|
|
1527
|
-
|
|
1572
|
+
home;
|
|
1573
|
+
agentWorkspace;
|
|
1528
1574
|
skillsDir;
|
|
1529
1575
|
trackingPath;
|
|
1530
1576
|
constructor(options) {
|
|
1531
|
-
|
|
1577
|
+
const home = options.home ?? options.workspace;
|
|
1578
|
+
if (!home) throw new Error("OpenClawApplier requires `home` (or legacy `workspace`) option");
|
|
1579
|
+
this.home = home;
|
|
1580
|
+
this.agentWorkspace = options.agentWorkspace ?? join(home, "workspace");
|
|
1532
1581
|
this.skillsDir = options.skillsDir ?? DEFAULT_SKILLS_DIR;
|
|
1533
|
-
this.trackingPath = options.configPath ?? join(this.
|
|
1582
|
+
this.trackingPath = options.configPath ?? join(this.home, "config.json");
|
|
1534
1583
|
}
|
|
1535
1584
|
async applyPlugin(pkg, _installPath, opts) {
|
|
1536
1585
|
await this.ensurePluginsAllow(pkg);
|
|
@@ -1546,25 +1595,22 @@ var OpenClawApplier = class {
|
|
|
1546
1595
|
}
|
|
1547
1596
|
}
|
|
1548
1597
|
if (!this.isPluginInstalled(pkg)) {
|
|
1598
|
+
const baseArgs = [
|
|
1599
|
+
"plugins",
|
|
1600
|
+
"install",
|
|
1601
|
+
pkg,
|
|
1602
|
+
"--force"
|
|
1603
|
+
];
|
|
1604
|
+
const useUnsafeFlag = pkg.startsWith("@alfe.ai/");
|
|
1605
|
+
const args = useUnsafeFlag ? [...baseArgs, "--dangerously-force-unsafe-install"] : baseArgs;
|
|
1549
1606
|
try {
|
|
1550
|
-
const args = [
|
|
1551
|
-
"plugins",
|
|
1552
|
-
"install",
|
|
1553
|
-
pkg
|
|
1554
|
-
];
|
|
1555
|
-
const useUnsafeFlag = pkg.startsWith("@alfe.ai/");
|
|
1556
|
-
if (useUnsafeFlag) args.push("--dangerously-force-unsafe-install");
|
|
1557
1607
|
try {
|
|
1558
1608
|
await execFileAsync("openclaw", args, { timeout: 6e4 });
|
|
1559
1609
|
} catch (err) {
|
|
1560
1610
|
const errText = err instanceof Error ? `${err.message}\n${err.stderr ?? ""}` : String(err);
|
|
1561
1611
|
if (useUnsafeFlag && errText.includes("unknown option")) {
|
|
1562
1612
|
log.info({ pkg }, "OpenClaw does not support --dangerously-force-unsafe-install, retrying without");
|
|
1563
|
-
await execFileAsync("openclaw",
|
|
1564
|
-
"plugins",
|
|
1565
|
-
"install",
|
|
1566
|
-
pkg
|
|
1567
|
-
], { timeout: 6e4 });
|
|
1613
|
+
await execFileAsync("openclaw", baseArgs, { timeout: 6e4 });
|
|
1568
1614
|
} else throw err;
|
|
1569
1615
|
}
|
|
1570
1616
|
} catch (err) {
|
|
@@ -1614,11 +1660,16 @@ var OpenClawApplier = class {
|
|
|
1614
1660
|
}
|
|
1615
1661
|
}
|
|
1616
1662
|
/**
|
|
1617
|
-
* Check if a plugin is already installed
|
|
1618
|
-
*
|
|
1663
|
+
* Check if a plugin is already installed.
|
|
1664
|
+
*
|
|
1665
|
+
* OpenClaw 2026.4 stored plugins under `~/.openclaw/extensions/{pkg-name-with-dashes}-{hash}`.
|
|
1666
|
+
* 2026.5+ added an npm registry at `~/.openclaw/npm/node_modules/<pkg>` for npm-source
|
|
1667
|
+
* plugins. We must check both — the extensions-only check missed npm-installed
|
|
1668
|
+
* plugins under 2026.5+, which made force-mode skip its uninstall step.
|
|
1619
1669
|
*/
|
|
1620
1670
|
isPluginInstalled(pkg) {
|
|
1621
|
-
|
|
1671
|
+
if (existsSync(join(this.home, "npm", "node_modules", ...pkg.split("/")))) return true;
|
|
1672
|
+
const extensionsDir = join(this.home, "extensions");
|
|
1622
1673
|
if (!existsSync(extensionsDir)) return false;
|
|
1623
1674
|
const prefix = pkg.replaceAll("/", "-") + "-";
|
|
1624
1675
|
return readdirSync(extensionsDir).some((dir) => dir.startsWith(prefix) && /^[0-9a-f]+$/i.test(dir.slice(prefix.length)));
|
|
@@ -1659,7 +1710,7 @@ var OpenClawApplier = class {
|
|
|
1659
1710
|
}
|
|
1660
1711
|
}
|
|
1661
1712
|
removeClawHubSkill(slug) {
|
|
1662
|
-
const workspaceSkillsDir = join(this.
|
|
1713
|
+
const workspaceSkillsDir = join(this.agentWorkspace, "skills", slug);
|
|
1663
1714
|
if (existsSync(workspaceSkillsDir)) {
|
|
1664
1715
|
rmSync(workspaceSkillsDir, {
|
|
1665
1716
|
recursive: true,
|
|
@@ -1689,21 +1740,36 @@ var OpenClawApplier = class {
|
|
|
1689
1740
|
integrations[integrationId] = config;
|
|
1690
1741
|
tracking._integrations = integrations;
|
|
1691
1742
|
this.writeTracking(tracking);
|
|
1692
|
-
const
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1743
|
+
const { leaves, subtreesByParent } = partitionEntries(flattenConfig(config));
|
|
1744
|
+
for (const [parentPath, dottedKvs] of subtreesByParent) {
|
|
1745
|
+
const merged = { ...await readParentObject(parentPath) };
|
|
1746
|
+
for (const [k, v] of dottedKvs) merged[k] = v;
|
|
1747
|
+
try {
|
|
1748
|
+
await execFileAsync("openclaw", [
|
|
1749
|
+
"config",
|
|
1750
|
+
"set",
|
|
1751
|
+
parentPath,
|
|
1752
|
+
JSON.stringify(merged)
|
|
1753
|
+
], { timeout: 1e4 });
|
|
1754
|
+
} catch (err) {
|
|
1755
|
+
log.error({
|
|
1756
|
+
err: err instanceof Error ? err.message : String(err),
|
|
1757
|
+
parentPath
|
|
1758
|
+
}, "Failed to set config subtree via openclaw config set");
|
|
1759
|
+
throw err;
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
if (leaves.length > 0) try {
|
|
1697
1763
|
await execFileAsync("openclaw", [
|
|
1698
1764
|
"config",
|
|
1699
1765
|
"set",
|
|
1700
1766
|
"--batch-json",
|
|
1701
|
-
JSON.stringify(
|
|
1767
|
+
JSON.stringify(leaves)
|
|
1702
1768
|
], { timeout: 1e4 });
|
|
1703
1769
|
} catch (err) {
|
|
1704
1770
|
log.error({
|
|
1705
1771
|
err: err instanceof Error ? err.message : String(err),
|
|
1706
|
-
batch
|
|
1772
|
+
batch: leaves
|
|
1707
1773
|
}, "Failed to set config via openclaw config set --batch-json");
|
|
1708
1774
|
throw err;
|
|
1709
1775
|
}
|
|
@@ -1719,8 +1785,8 @@ var OpenClawApplier = class {
|
|
|
1719
1785
|
const integrations = tracking._integrations ?? {};
|
|
1720
1786
|
if (!(integrationId in integrations)) return;
|
|
1721
1787
|
const integrationConfig = integrations[integrationId];
|
|
1722
|
-
const
|
|
1723
|
-
for (const
|
|
1788
|
+
const { leaves, subtreesByParent } = partitionEntries(flattenConfig(integrationConfig));
|
|
1789
|
+
for (const { path } of leaves) try {
|
|
1724
1790
|
await execFileAsync("openclaw", [
|
|
1725
1791
|
"config",
|
|
1726
1792
|
"unset",
|
|
@@ -1732,6 +1798,29 @@ var OpenClawApplier = class {
|
|
|
1732
1798
|
path
|
|
1733
1799
|
}, "Failed to unset config via openclaw config unset");
|
|
1734
1800
|
}
|
|
1801
|
+
for (const [parentPath, dottedKvs] of subtreesByParent) {
|
|
1802
|
+
const existing = await readParentObject(parentPath);
|
|
1803
|
+
const remaining = Object.fromEntries(Object.entries(existing).filter(([k]) => !dottedKvs.has(k)));
|
|
1804
|
+
if (Object.keys(existing).length === 0) continue;
|
|
1805
|
+
try {
|
|
1806
|
+
if (Object.keys(remaining).length === 0) await execFileAsync("openclaw", [
|
|
1807
|
+
"config",
|
|
1808
|
+
"unset",
|
|
1809
|
+
parentPath
|
|
1810
|
+
], { timeout: 1e4 });
|
|
1811
|
+
else await execFileAsync("openclaw", [
|
|
1812
|
+
"config",
|
|
1813
|
+
"set",
|
|
1814
|
+
parentPath,
|
|
1815
|
+
JSON.stringify(remaining)
|
|
1816
|
+
], { timeout: 1e4 });
|
|
1817
|
+
} catch (err) {
|
|
1818
|
+
log.warn({
|
|
1819
|
+
err: err instanceof Error ? err.message : String(err),
|
|
1820
|
+
parentPath
|
|
1821
|
+
}, "Failed to update parent config during remove");
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1735
1824
|
tracking._integrations = Object.fromEntries(Object.entries(integrations).filter(([key]) => key !== integrationId));
|
|
1736
1825
|
this.writeTracking(tracking);
|
|
1737
1826
|
}
|
|
@@ -1812,7 +1901,7 @@ var OpenClawApplier = class {
|
|
|
1812
1901
|
this.writeTracking(tracking);
|
|
1813
1902
|
}
|
|
1814
1903
|
isAvailable() {
|
|
1815
|
-
return Promise.resolve(existsSync(this.
|
|
1904
|
+
return Promise.resolve(existsSync(this.home));
|
|
1816
1905
|
}
|
|
1817
1906
|
readTracking() {
|
|
1818
1907
|
if (!existsSync(this.trackingPath)) return {};
|
package/package.json
CHANGED