/** * Non-interactive setup script for the DKG OpenClaw adapter. * * Handles the entire DKG node + adapter setup end-to-end: * 1. Discover OpenClaw workspace * 2. Discover agent name * 3. Write ~/.dkg/config.json with testnet defaults * 4. Preflight ~/.openclaw/openclaw.json (exists / parseable / writable / * not wrong-slot-wired) so deterministic setup errors fail fast * before daemon start and the faucet call burn resources * 5. Start the DKG daemon * 6. Read wallets and fund the admin + operational wallets via the testnet faucet * (skippable with `--no-fund`; non-fatal on failure) * 7. Copy the canonical DKG node skill into the OpenClaw workspace * 8. Merge adapter plugin into ~/.openclaw/openclaw.json (including * plugins.entries.adapter-openclaw.config with feature flags) * 9. Verify setup * * Every step is idempotent — re-running is safe. */ import { logManualFundingInstructions, readWallets, readWalletsWithRetry, resolveCliPackageDir, startDaemon } from '@origintrail-official/dkg-core'; import type { DkgOpenClawConfig } from './types.js'; export { logManualFundingInstructions, readWallets, readWalletsWithRetry, resolveCliPackageDir, startDaemon, }; export interface SetupOptions { workspace?: string; name?: string; port?: string; verify?: boolean; start?: boolean; dryRun?: boolean; /** * Fund the node's admin + operational wallets via the testnet faucet on first setup. * Defaults to `true`; the adapter treats `fund === false` (set by * `--no-fund`) as the only opt-out. Faucet failures are non-fatal — a * failed call logs manual `curl` instructions and setup continues. */ fund?: boolean; /** * Network overlay to set up on (e.g. `mainnet-gnosis`, `mainnet-base`, * `testnet`). Persisted as `config.networkConfig`. When omitted, a fresh * node defaults to mainnet-gnosis and an existing node keeps its current * network — see `resolveSetupNetworkName`. */ network?: string; /** * Abort signal for cooperative cancellation. Checked at each step boundary * so an aborted job stops between steps without further filesystem writes * — matches the granularity of the previous child-process SIGKILL model. * Long-running sync calls (e.g. `execSync('dkg start', ...)`) are not * interrupted mid-call; cancellation takes effect before the next step. */ signal?: AbortSignal; } interface NetworkConfig { networkName: string; relays: string[]; defaultContextGraphs: string[]; defaultNodeRole: string; autoUpdate?: { enabled: boolean; repo: string; branch: string; checkIntervalMinutes: number; }; chain?: { type: string; rpcUrl: string; hubAddress: string; chainId: string; }; faucet?: { url: string; mode: string; }; } /** * Resolve the `openclaw.json` config path, honoring `OPENCLAW_HOME` the same * way `runSetup()` does. Exported so out-of-process callers (e.g. the DKG * daemon's post-setup invariant check and disconnect handler in * `packages/cli/src/daemon.ts`) read the same file that `mergeOpenClawConfig` * writes to; hardcoding `join(homedir(), '.openclaw', 'openclaw.json')` on the * daemon side caused false slot-election failures when the user set * `OPENCLAW_HOME` to a non-default location. */ export declare function openclawConfigPath(): string; /** * Pure resolver for the workspace directory from an already-parsed * openclaw.json object. Shared between `discoverWorkspace` (setup / install * path) and the daemon's Disconnect path so install + removal agree on * exactly the same target directory. * * Resolution rules (matching `discoverWorkspace` semantics): * 1. Priority order across the three known keys: * `agents.defaults.workspace` → `workspace` → `workspaceDir`. * First non-empty string wins. * 2. Leading `~` is expanded to `homedir()`. * 3. Relative paths are resolved against `dirname(openclawConfigPath)`, * not `cwd` — so a given openclaw.json produces the same absolute * workspace no matter where the process is invoked from. * 4. When no key is set, fall back to `dirname(openclawConfigPath)/workspace` * (co-located with the config file, matching the relative-path * resolution in rule 3) only if that directory exists on disk. R9-1: * must NOT read the process-wide `$OPENCLAW_HOME` here — a legacy * install whose openclaw.json lives at a non-default path would * otherwise resolve to the default `~/.openclaw/workspace` on * Disconnect and clean up (or miss) the wrong SKILL.md. * 5. Otherwise return `null`. Callers decide whether to throw or * skip-best-effort. */ export declare function resolveWorkspaceDirFromConfig(config: unknown, openclawConfigPath: string): string | null; export declare function discoverWorkspace(override?: string): { configPath: string; workspaceDir: string; }; export declare function discoverAgentName(workspaceDir: string, override?: string): string; export declare function loadNetworkConfig(networkName?: string): NetworkConfig; /** * Resolve which network this setup run should use and load its config. * * The name follows {@link resolveSetupNetworkName}: an explicit `--network` * wins; otherwise an existing node keeps its persisted `networkConfig`; a * fresh node defaults to mainnet-gnosis; and a legacy config that never set * a network stays on testnet. Loading the SAME name we persist keeps the * written `networkConfig` and the network slice (relays/faucet/chain) in * agreement. */ export declare function resolveSetupNetwork(explicitNetwork?: string): { networkName: string; network: NetworkConfig; }; export declare function resolveCanonicalNodeSkillSourcePath(): string; export interface DkgConfigOverrides { /** True when the user explicitly passed --name. */ nameExplicit?: boolean; /** True when the user explicitly passed --port. */ portExplicit?: boolean; } export declare function writeDkgConfig(agentName: string, network: NetworkConfig, apiPort: number, overrides?: DkgConfigOverrides, networkConfigName?: string): void; /** * Result of `preflightOpenClawConfig` — the pure, staged validation of * `openclaw.json` that Step 8 (merge) relies on. Surfacing the parsed * config + migration pointer here lets the caller reuse them without * re-reading the file. */ export interface OpenClawPreflightResult { /** * Absolute path to the `openclaw.json` the subsequent merge step will * write to. Resolves the `--workspace`-override case (where * `discoverWorkspace` returns an empty configPath) to the default * `~/.openclaw/openclaw.json`, matching `mergeOpenClawConfig`'s own * resolution. */ effectiveConfigPath: string; /** * The parsed JSON of the file at `effectiveConfigPath`. Reused by the * caller for migration-pointer discovery and any downstream inspection * so we don't re-parse the file twice. */ rawExisting: any; /** * If a prior install wrote `plugins.entries.adapter-openclaw.config.installedWorkspace` * and this run is targeting a different workspace, the migration-cleanup * step later in `runSetup` retires the prior install's SKILL.md. Empty * string means "no migration cleanup required". */ priorInstalledForMigration: string; } /** * Validate `openclaw.json` before any destructive setup step runs. This * is the Codex PR #234 R6-2 + R8-2 preflight lifted out of `runSetup` so * it can run earlier (before `startDaemon` + the faucet call) — catching * a deterministic misconfiguration (missing file, invalid JSON, non- * writable, wrong-slot-wired) before the user's 3-calls-per-8h faucet * allowance is spent on a setup that was always going to fail at merge. * * Pure staged checks: * 1. `openclaw.json` exists at the effective config path. * 2. The file parses as JSON. * 3. The file is writable. * 4. The containing directory is writable (R11-3 — `mergeOpenClawConfig` * writes `openclaw.json.bak.` as a sibling of the config, so a * file-writable-but-dir-readonly arrangement would fail mid-merge). * 5. `plugins.slots.contextEngine !== ADAPTER_PLUGIN_ID` (R8-2 — the * adapter declares `kind: "memory"`; mis-wiring would let step 8 * write SKILL.md to disk before throwing). * * Also captures the migration pointer (`entry.config.installedWorkspace`) * for the post-merge cleanup step. A missing pointer means no migration * cleanup is required; we decline to fall back to `resolveWorkspaceDirFromConfig` * here (R11-2 — no destructive best-guess on pre-launch configs). * * Throws with actionable messages matching the in-body preflight's error * text. Callers are responsible for swallowing/abortng as appropriate — * in the `runSetup` happy path these errors should propagate so the user * sees them immediately on the command line. */ export declare function preflightOpenClawConfig(openclawConfigPath: string): OpenClawPreflightResult; /** * Shape of `plugins.entries.adapter-openclaw.config` that * `mergeOpenClawConfig` accepts. A `Pick` of `DkgOpenClawConfig` so the * write-path stays aligned with the runtime's config contract: callers * can pass any of the same sub-fields the adapter reads at load time, * including `channel.port` for advanced bridge-port overrides. */ export type AdapterEntryConfig = Pick; export declare function mergeOpenClawConfig(openclawConfigPath: string, adapterPath: string, entryConfig: AdapterEntryConfig, installedWorkspace: string, options?: { overrideDaemonUrl?: boolean; }): void; /** * Symmetric undo of `mergeOpenClawConfig`. Scoped to only the fields setup * would have written: * - removes `"adapter-openclaw"` from `plugins.allow` * - filters `plugins.load.paths` by the same `isAdapterLoadPath` predicate * - removes `plugins.entries["adapter-openclaw"]` entirely (including any * `config` sub-object — the adapter owns the whole entry) * - restores `plugins.slots.memory` to the prior owner captured during merge * (`entries["adapter-openclaw"].previousMemorySlotOwner`, read before the * entry is deleted), or clears it when no prior owner was persisted. * * Leaves `tools.alsoAllow` (shared with other plugins), workspace `config.json` * (user-owned), and any workspace `SKILL.md` copies alone. Idempotent — a * second call produces zero diff. Backs up to `.bak.` only when content * changes (same contract as merge). * * Already-disconnected shortcut: a missing or unparseable `openclaw.json` is * treated as "nothing to unmerge" — logs a one-liner and returns without * throwing or writing `.bak`. The adapter cannot be loading from a config * that doesn't exist (or can't be parsed), so blocking the Disconnect UI * flow on it would strand users who removed or relocated OpenClaw. */ export interface UnmergeResult { /** * Prior memory-slot owner captured by `mergeOpenClawConfig` and read out of * the adapter entry BEFORE it was deleted. Used to restore `plugins.slots.memory` * when the adapter had displaced another plugin at install time. */ previousMemorySlotOwner?: string; } export declare function unmergeOpenClawConfig(openclawConfigPath: string): UnmergeResult; /** * Post-unmerge invariant check, counterpart to `verifyMemorySlotInvariants` * but for the Disconnect flow. Confirms every field `mergeOpenClawConfig` * writes has been unwound by `unmergeOpenClawConfig`: * - `plugins.slots.memory !== "adapter-openclaw"`. * - `plugins.allow` does not contain `"adapter-openclaw"`. * - `plugins.load.paths` contains no entry matching `isAdapterLoadPath`. * - `plugins.entries["adapter-openclaw"]?.enabled` is not `true`. * * Returns `null` when all invariants hold (disconnect is clean). Returns a * descriptive string naming the first failed invariant otherwise — callers * (e.g. the DKG daemon's PUT `/api/local-agent-integrations/:id` handler in * `packages/cli/src/daemon.ts`) surface the string as `runtime.lastError` and * refuse to transition the integration to `disconnected`. * * Non-throwing by design. File-state handling mirrors * `unmergeOpenClawConfig`: * - Missing file → returns `null`. The invariants hold trivially because * no adapter can be loading from a config that doesn't exist; blocking * Disconnect in this case would strand users who removed OpenClaw. * - Unparseable file → returns a descriptive string. That's a genuinely * broken state worth surfacing — we can't verify invariants one way or * the other. */ export declare function verifyUnmergeInvariants(configPath: string): string | null; export declare function installCanonicalNodeSkill(workspaceDir: string, sourcePath?: string): string; /** * Symmetric counterpart to {@link installCanonicalNodeSkill}: removes the * adapter-owned `$WORKSPACE_DIR/skills/dkg-node/SKILL.md` doc installed by * step 7 of setup. Called from the daemon-side disconnect path so the agent- * facing skill is retired alongside the openclaw.json entry. * * Idempotent: a missing file is a no-op. After removing the file we also try * `rmdirSync` on the now-empty `skills/dkg-node/` parent so Disconnect leaves * no adapter-named empty directories behind — but we never touch the outer * `skills/` dir (user skills live there) and we swallow ENOTEMPTY when a * sibling file was placed alongside SKILL.md. */ export declare function removeCanonicalNodeSkill(workspaceDir: string): void; /** * Post-remove invariant check, counterpart to `verifyUnmergeInvariants` but * for the skill-file side of Disconnect. Returns `null` when the canonical * node skill at `/skills/dkg-node/SKILL.md` is absent * (clean retirement). Returns a descriptive string when it's still present — * the daemon's Disconnect path treats this as a failure to surface via * `runtime.lastError`, so the UI never reports "disconnected" while the * workspace still carries the adapter-owned skill doc (Codex PR #234 R2-2). * * Non-throwing by design. */ export declare function verifySkillRemoved(installedWorkspace: string): string | null; export declare function verifySetup(apiPort: number, opts?: { openclawConfigPath?: string; }): Promise; /** * Memory-authority invariants that must hold after setup: * (a) ~/.openclaw/openclaw.json plugins.slots.memory === "adapter-openclaw" * (b) ~/.openclaw/openclaw.json plugins.slots.contextEngine !== "adapter-openclaw" * (c) packages/adapter-openclaw/openclaw.plugin.json has "kind": "memory" * Logged as pass/fail per invariant. Non-throwing (warns on failure) so * that a partial install is still surfaced rather than hidden. */ export declare function verifyMemorySlotInvariants(configPath?: string): void; /** * Injected runtime deps for `runSetup`. Kept separate from the user-facing * `SetupOptions` so the adapter stays free of a `@origintrail-official/dkg-agent` * dependency: the cli layer (which has dkg-agent) provides `loadOpWallets`. */ export interface RunSetupDeps { /** * Eagerly create the node's operational wallets (generate-if-absent) after * the config write and before the daemon starts, so faucet funding and * manual mainnet funding have wallets even if the daemon never fully boots * (issue #1306). Best-effort; omitted by callers where the daemon is already * running (the node-UI route) or in unit tests. */ loadOpWallets?: (dir: string) => Promise; } export declare function runSetup(options: SetupOptions, deps?: RunSetupDeps): Promise; //# sourceMappingURL=setup.d.ts.map