Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,17 @@ cd my-bot
npx @lobu/cli@latest run
```

## Starter Skills
## Agent configuration

Install the Lobu starter skill into any local `skills/` directory:
Runtime configuration is managed through the web app or the same org-scoped REST API used by the CLI:

```bash
npx @lobu/cli@latest skills add lobu
npx @lobu/cli@latest login
npx @lobu/cli@latest org set my-org
npx @lobu/cli@latest agent list
```

The bundled Lobu starter skill includes memory guidance. Configure local MCP clients when needed:

```bash
npx @lobu/cli@latest memory init
```
Local `lobu.toml` projects are still useful for `lobu validate` and `lobu apply` workflows.

### Deployment

Expand Down
20 changes: 2 additions & 18 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @lobu/cli

CLI tool for initializing and managing Lobu projects.
CLI tool for running Lobu locally and managing Lobu agents through the same REST API as the web app.

## Quick Start

Expand All @@ -13,21 +13,6 @@ npx @lobu/cli@latest run

Lobu boots as a single Node process. Postgres is a user-provided external (managed instance or local — `brew services start postgresql`).

## Starter Skills

Install the Lobu starter skill into a local `skills/` directory:

```bash
npx @lobu/cli@latest skills list
npx @lobu/cli@latest skills add lobu
```

The bundled Lobu starter skill includes memory guidance. Configure local MCP clients when needed:

```bash
npx @lobu/cli@latest memory init
```

## Commands

### `lobu init [name]`
Expand All @@ -36,7 +21,6 @@ Scaffold a new Lobu project with interactive prompts:

- **Project name**
- **Gateway port** and optional **public URL** (for OAuth callbacks)
- **Admin password**
- **Worker network access** (isolated, allowlist, or unrestricted)
- **AI provider** selection from the bundled provider registry + API key
- **Messaging platform** (Telegram, Slack, Discord, WhatsApp, Teams, Google Chat, or none)
Expand All @@ -54,7 +38,7 @@ For a custom Owletto deployment, `.env` keeps `MEMORY_URL` as the optional base

### `lobu run`

Boot the embedded Lobu stack — gateway + workers + embeddings + Owletto memory backend in a single Node process. Validates `lobu.toml` and that `DATABASE_URL` is set in `.env`, then spawns the bundled `@lobu/owletto-backend/dist/server.bundle.mjs`. Ctrl+C stops the process and any spawned worker subprocesses cleanly.
Boot the embedded Lobu stack — gateway + workers + embeddings + Owletto memory backend in a single Node process. `lobu.toml` is not required; set `DATABASE_URL` in the environment or `.env`, then the command spawns the bundled `@lobu/owletto-backend/dist/server.bundle.mjs`. Ctrl+C stops the process and any spawned worker subprocesses cleanly.

## License

Expand Down
42 changes: 0 additions & 42 deletions packages/cli/src/__tests__/skills.test.ts

This file was deleted.

2 changes: 0 additions & 2 deletions packages/cli/src/commands/_lib/apply/apply-cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export interface ApplyOptions {
only?: "agents" | "memory";
org?: string;
url?: string;
storePath?: string;
/** Test seam — inject a stubbed fetch. */
fetchImpl?: typeof fetch;
}
Expand Down Expand Up @@ -202,7 +201,6 @@ export async function applyCommand(opts: ApplyOptions = {}): Promise<void> {
const { client, orgSlug } = await resolveApplyClient({
url: opts.url,
org: opts.org,
storePath: opts.storePath,
fetchImpl: opts.fetchImpl,
});
printText(chalk.dim(`Org: ${orgSlug}`));
Expand Down
91 changes: 13 additions & 78 deletions packages/cli/src/commands/_lib/apply/client.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import type { AgentSettings } from "@lobu/core";
import { ApiError, ValidationError } from "../../memory/_lib/errors.js";
import {
getSessionForOrg,
getUsableToken,
mcpUrlForOrg,
orgFromMcpUrl,
resolveOrg,
resolveServerUrl,
} from "../../memory/_lib/openclaw-auth.js";
import { resolveApiClient } from "../../../internal/index.js";
import { ApiError } from "../../memory/_lib/errors.js";

// ── Wire types ─────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -100,61 +93,6 @@ async function parseResponseBody(
}
}

// ── Auth resolver — same shape as seed-cmd.ts (PR #459) ────────────────────

async function resolveAuth(
urlFlag?: string,
orgFlag?: string,
storePath?: string
): Promise<{ token: string; mcpUrl: string; orgSlug: string }> {
const org = resolveOrg(orgFlag);
if (org) {
const orgSession = getSessionForOrg(org, storePath, urlFlag);
if (orgSession) {
const result = await getUsableToken(orgSession.key, storePath);
if (result) {
return { token: result.token, mcpUrl: orgSession.key, orgSlug: org };
}
}
const serverUrl = resolveServerUrl(urlFlag, storePath);
if (serverUrl) {
const orgUrl = mcpUrlForOrg(serverUrl, org);
const result = await getUsableToken(orgUrl, storePath);
if (result) {
return { token: result.token, mcpUrl: orgUrl, orgSlug: org };
}
}
throw new ValidationError("Not logged in. Run: lobu login");
}

const serverUrl = resolveServerUrl(urlFlag, storePath);
const result = await getUsableToken(serverUrl || undefined, storePath);
if (!result) {
throw new ValidationError("Not logged in. Run: lobu login");
}
const resolvedOrg =
orgFromMcpUrl(result.session.mcpUrl) || result.session.org;
if (!resolvedOrg) {
throw new ValidationError(
"Cannot determine org. Use --org or set LOBU_MEMORY_ORG."
);
}
return {
token: result.token,
mcpUrl: result.session.mcpUrl,
orgSlug: resolvedOrg,
};
}

/** Strip the path off an MCP URL to reach the API root. */
export function deriveApiBaseUrl(mcpUrl: string): string {
const url = new URL(mcpUrl);
url.pathname = "";
url.search = "";
url.hash = "";
return url.toString().replace(/\/+$/, "");
}

// ── Client ─────────────────────────────────────────────────────────────────

export interface ApplyClientConfig {
Expand Down Expand Up @@ -264,7 +202,7 @@ export class ApplyClient {
): Promise<void> {
await this.request(
"PATCH",
`/api/${this.orgSlug}/agents/${agentId}`,
`/api/${this.orgSlug}/agents/${encodeURIComponent(agentId)}`,
agent
);
}
Expand All @@ -273,7 +211,7 @@ export class ApplyClient {
try {
const { body } = await this.request<AgentSettings>(
"GET",
`/api/${this.orgSlug}/agents/${agentId}/config`
`/api/${this.orgSlug}/agents/${encodeURIComponent(agentId)}/config`
);
return body;
} catch (err) {
Expand All @@ -288,7 +226,7 @@ export class ApplyClient {
): Promise<void> {
await this.request(
"PATCH",
`/api/${this.orgSlug}/agents/${agentId}/config`,
`/api/${this.orgSlug}/agents/${encodeURIComponent(agentId)}/config`,
settings
);
}
Expand All @@ -298,7 +236,7 @@ export class ApplyClient {
async listPlatforms(agentId: string): Promise<RemotePlatform[]> {
const { body } = await this.request<{ platforms?: RemotePlatform[] }>(
"GET",
`/api/${this.orgSlug}/agents/${agentId}/platforms`
`/api/${this.orgSlug}/agents/${encodeURIComponent(agentId)}/platforms`
);
return body.platforms ?? [];
}
Expand All @@ -320,7 +258,7 @@ export class ApplyClient {
): Promise<UpsertPlatformResult> {
const { body } = await this.request<UpsertPlatformResult>(
"PUT",
`/api/${this.orgSlug}/agents/${agentId}/platforms/by-stable-id/${encodeURIComponent(stableId)}`,
`/api/${this.orgSlug}/agents/${encodeURIComponent(agentId)}/platforms/by-stable-id/${encodeURIComponent(stableId)}`,
payload
);
return body;
Expand Down Expand Up @@ -471,24 +409,21 @@ export interface ResolvedClient {
client: ApplyClient;
apiBaseUrl: string;
orgSlug: string;
mcpUrl: string;
}

export async function resolveApplyClient(opts: {
url?: string;
org?: string;
storePath?: string;
fetchImpl?: typeof fetch;
}): Promise<ResolvedClient> {
const { token, mcpUrl, orgSlug } = await resolveAuth(
opts.url,
opts.org,
opts.storePath
);
const apiBaseUrl = deriveApiBaseUrl(mcpUrl);
const { token, apiBaseUrl, orgSlug } = await resolveApiClient({
org: opts.org,
apiUrl: opts.url,
fetchImpl: opts.fetchImpl,
});
const client = new ApplyClient(
{ apiBaseUrl, orgSlug, token },
opts.fetchImpl
);
return { client, apiBaseUrl, orgSlug, mcpUrl };
return { client, apiBaseUrl, orgSlug };
}
Loading
Loading