From d583586449340c395e49d8f6247c4a7180a626c9 Mon Sep 17 00:00:00 2001 From: kaghni Date: Wed, 15 Apr 2026 14:00:10 -0700 Subject: [PATCH 1/2] Add org/workspace management commands for OpenClaw --- openclaw/skills/SKILL.md | 10 ++++++ openclaw/src/index.ts | 71 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/openclaw/skills/SKILL.md b/openclaw/skills/SKILL.md index 188ac6f3..5dc467f1 100644 --- a/openclaw/skills/SKILL.md +++ b/openclaw/skills/SKILL.md @@ -23,6 +23,16 @@ The plugin automatically: - **Recalls** relevant memories before each agent turn via keyword search - All data stored as structured rows — searchable, persistent, shared +## Commands + +- `/hivemind_login` — sign in +- `/hivemind_capture` — toggle capture on/off +- `/hivemind_whoami` — show current org and workspace +- `/hivemind_orgs` — list organizations +- `/hivemind_switch_org ` — switch organization +- `/hivemind_workspaces` — list workspaces +- `/hivemind_switch_workspace ` — switch workspace + ## Sharing memory Multiple agents share memory when users are in the same Deeplake organization. diff --git a/openclaw/src/index.ts b/openclaw/src/index.ts index 3fdbe51c..06720f5b 100644 --- a/openclaw/src/index.ts +++ b/openclaw/src/index.ts @@ -5,7 +5,7 @@ import { join } from "node:path"; // Shared core imports import { loadConfig } from "../../src/config.js"; -import { loadCredentials, saveCredentials, requestDeviceCode, pollForToken, listOrgs } from "../../src/commands/auth.js"; +import { loadCredentials, saveCredentials, requestDeviceCode, pollForToken, listOrgs, switchOrg, listWorkspaces, switchWorkspace } from "../../src/commands/auth.js"; import { DeeplakeApi } from "../../src/deeplake-api.js"; import { sqlStr, sqlLike } from "../../src/utils/sql.js"; @@ -183,6 +183,75 @@ export default definePluginEntry({ return { text: captureEnabled ? "✅ Capture enabled — conversations will be stored to Hivemind." : "⏸️ Capture paused — conversations will NOT be stored until you run /hivemind_capture again." }; }, }); + + pluginApi.registerCommand({ + name: "hivemind_whoami", + description: "Show current Hivemind org and workspace", + handler: async () => { + const creds = loadCredentials(); + if (!creds?.token) return { text: "Not logged in. Run /hivemind_login" }; + return { text: `Org: ${creds.orgName ?? creds.orgId}\nWorkspace: ${creds.workspaceId ?? "default"}` }; + }, + }); + + pluginApi.registerCommand({ + name: "hivemind_orgs", + description: "List available organizations", + handler: async () => { + const creds = loadCredentials(); + if (!creds?.token) return { text: "Not logged in. Run /hivemind_login" }; + const orgs = await listOrgs(creds.token, creds.apiUrl); + if (!orgs.length) return { text: "No organizations found." }; + const lines = orgs.map(o => `${o.id === creds.orgId ? "→ " : " "}${o.name}`); + return { text: lines.join("\n") }; + }, + }); + + pluginApi.registerCommand({ + name: "hivemind_switch_org", + description: "Switch to a different organization", + acceptsArgs: true, + handler: async (ctx: CommandContext) => { + const creds = loadCredentials(); + if (!creds?.token) return { text: "Not logged in. Run /hivemind_login" }; + const target = ctx.args?.trim(); + if (!target) return { text: "Usage: /hivemind_switch_org " }; + const orgs = await listOrgs(creds.token, creds.apiUrl); + const match = orgs.find(o => o.id === target || o.name.toLowerCase() === target.toLowerCase()); + if (!match) return { text: `Org not found: ${target}` }; + await switchOrg(match.id, match.name); + api = null; + return { text: `Switched to org: ${match.name}` }; + }, + }); + + pluginApi.registerCommand({ + name: "hivemind_workspaces", + description: "List available workspaces", + handler: async () => { + const creds = loadCredentials(); + if (!creds?.token) return { text: "Not logged in. Run /hivemind_login" }; + const ws = await listWorkspaces(creds.token, creds.apiUrl, creds.orgId); + if (!ws.length) return { text: "No workspaces found." }; + const lines = ws.map(w => `${w.id === (creds.workspaceId ?? "default") ? "→ " : " "}${w.name}`); + return { text: lines.join("\n") }; + }, + }); + + pluginApi.registerCommand({ + name: "hivemind_switch_workspace", + description: "Switch to a different workspace", + acceptsArgs: true, + handler: async (ctx: CommandContext) => { + const creds = loadCredentials(); + if (!creds?.token) return { text: "Not logged in. Run /hivemind_login" }; + const wsId = ctx.args?.trim(); + if (!wsId) return { text: "Usage: /hivemind_switch_workspace " }; + await switchWorkspace(wsId); + api = null; + return { text: `Switched to workspace: ${wsId}` }; + }, + }); } const config = (pluginApi.pluginConfig ?? {}) as PluginConfig; From 5de774089685a657e6a639aaac100e75e3ace829 Mon Sep 17 00:00:00 2001 From: kaghni Date: Fri, 17 Apr 2026 01:25:19 -0700 Subject: [PATCH 2/2] Validate workspace ID against listWorkspaces before switching --- openclaw/src/index.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openclaw/src/index.ts b/openclaw/src/index.ts index 06720f5b..469bc264 100644 --- a/openclaw/src/index.ts +++ b/openclaw/src/index.ts @@ -245,11 +245,14 @@ export default definePluginEntry({ handler: async (ctx: CommandContext) => { const creds = loadCredentials(); if (!creds?.token) return { text: "Not logged in. Run /hivemind_login" }; - const wsId = ctx.args?.trim(); - if (!wsId) return { text: "Usage: /hivemind_switch_workspace " }; - await switchWorkspace(wsId); + const target = ctx.args?.trim(); + if (!target) return { text: "Usage: /hivemind_switch_workspace " }; + const ws = await listWorkspaces(creds.token, creds.apiUrl, creds.orgId); + const match = ws.find(w => w.id === target || w.name.toLowerCase() === target.toLowerCase()); + if (!match) return { text: `Workspace not found: ${target}` }; + await switchWorkspace(match.id); api = null; - return { text: `Switched to workspace: ${wsId}` }; + return { text: `Switched to workspace: ${match.name}` }; }, }); }