@constellaapp/openclaw 0.1.0

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/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # @openclaw/constella-openclaw
2
+
3
+ OpenClaw plugin that connects to Constella external APIs and exposes two tools:
4
+
5
+ - `constella_search_notes`
6
+ - `constella_insert_note`
7
+
8
+ ## Install
9
+
10
+ ### From npm (official)
11
+
12
+ ```bash
13
+ openclaw plugins install @openclaw/constella-openclaw
14
+ ```
15
+
16
+ ### Local dev install
17
+
18
+ ```bash
19
+ openclaw plugins install -l /Users/tejas1/Documents/Constella\ Codebases/third_party/constella-openclaw
20
+ ```
21
+
22
+ ## Configure
23
+
24
+ Set plugin config in your OpenClaw config file:
25
+
26
+ ```json
27
+ {
28
+ "plugins": {
29
+ "entries": {
30
+ "constella-openclaw": {
31
+ "enabled": true,
32
+ "config": {
33
+ "baseUrl": "https://fastfind.app",
34
+ "apiKey": "csk_your_key_here"
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ ## Enable the tools
43
+
44
+ Optional plugin tools must be allowlisted:
45
+
46
+ ```json
47
+ {
48
+ "agents": {
49
+ "list": [
50
+ {
51
+ "id": "main",
52
+ "tools": {
53
+ "allow": [
54
+ "constella_search_notes",
55
+ "constella_insert_note"
56
+ ]
57
+ }
58
+ }
59
+ ]
60
+ }
61
+ }
62
+ ```
63
+
64
+ ## API endpoints used
65
+
66
+ - `POST /constella-external-api/retrieve-info`
67
+ - `POST /constella-external-api/insert-note`
68
+
69
+ Auth header sent by this plugin:
70
+
71
+ - `x_access_key: csk_...`
package/index.ts ADDED
@@ -0,0 +1,119 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk";
3
+
4
+ import { createConstellaClient } from "./src/client.js";
5
+
6
+ type PluginConfig = {
7
+ baseUrl?: string;
8
+ apiKey?: string;
9
+ };
10
+
11
+ function toTextResult(payload: unknown) {
12
+ return {
13
+ content: [
14
+ {
15
+ type: "text",
16
+ text: JSON.stringify(payload, null, 2),
17
+ },
18
+ ],
19
+ };
20
+ }
21
+
22
+ function readStringArray(value: unknown): string[] | undefined {
23
+ if (!Array.isArray(value)) {
24
+ return undefined;
25
+ }
26
+ const out = value.filter((item): item is string => typeof item === "string");
27
+ return out.length > 0 ? out : undefined;
28
+ }
29
+
30
+ export default function register(api: OpenClawPluginApi) {
31
+ const raw = (api.pluginConfig ?? {}) as PluginConfig;
32
+ const baseUrl =
33
+ typeof raw.baseUrl === "string" && raw.baseUrl.trim()
34
+ ? raw.baseUrl.trim()
35
+ : "https://fastfind.app";
36
+ const apiKey = typeof raw.apiKey === "string" ? raw.apiKey.trim() : "";
37
+
38
+ if (!apiKey) {
39
+ api.logger.warn(
40
+ "[constella-openclaw] Missing apiKey in plugin config. Tools will not be registered.",
41
+ );
42
+ return;
43
+ }
44
+
45
+ const client = createConstellaClient({
46
+ baseUrl,
47
+ apiKey,
48
+ logger: api.logger,
49
+ });
50
+
51
+ const searchTool: AnyAgentTool = {
52
+ name: "constella_search_notes",
53
+ description: "Search Constella notes with optional filters and date range.",
54
+ parameters: Type.Object(
55
+ {
56
+ query: Type.Optional(Type.String()),
57
+ queries: Type.Optional(Type.Array(Type.String())),
58
+ categories: Type.Optional(Type.Array(Type.String())),
59
+ tags: Type.Optional(Type.Array(Type.String())),
60
+ from_date_time: Type.Optional(Type.String()),
61
+ to_end_date_time: Type.Optional(Type.String()),
62
+ top_k: Type.Optional(Type.Number()),
63
+ similarity_setting: Type.Optional(Type.Number()),
64
+ fixedIntegrationNames: Type.Optional(Type.Array(Type.String())),
65
+ },
66
+ { additionalProperties: false },
67
+ ),
68
+ async execute(_id: string, params: Record<string, unknown>) {
69
+ const result = await client.searchNotes({
70
+ query: typeof params.query === "string" ? params.query : undefined,
71
+ queries: readStringArray(params.queries),
72
+ categories: readStringArray(params.categories),
73
+ tags: readStringArray(params.tags),
74
+ from_date_time:
75
+ typeof params.from_date_time === "string"
76
+ ? params.from_date_time
77
+ : undefined,
78
+ to_end_date_time:
79
+ typeof params.to_end_date_time === "string"
80
+ ? params.to_end_date_time
81
+ : undefined,
82
+ top_k: typeof params.top_k === "number" ? params.top_k : undefined,
83
+ similarity_setting:
84
+ typeof params.similarity_setting === "number"
85
+ ? params.similarity_setting
86
+ : undefined,
87
+ fixedIntegrationNames: readStringArray(params.fixedIntegrationNames),
88
+ });
89
+ return toTextResult(result);
90
+ },
91
+ };
92
+
93
+ const insertTool: AnyAgentTool = {
94
+ name: "constella_insert_note",
95
+ description: "Insert a note into Constella.",
96
+ parameters: Type.Object(
97
+ {
98
+ title: Type.String(),
99
+ content: Type.Optional(Type.String()),
100
+ },
101
+ { additionalProperties: false },
102
+ ),
103
+ async execute(_id: string, params: Record<string, unknown>) {
104
+ const title = typeof params.title === "string" ? params.title.trim() : "";
105
+ if (!title) {
106
+ throw new Error("title is required");
107
+ }
108
+
109
+ const result = await client.insertNote({
110
+ title,
111
+ content: typeof params.content === "string" ? params.content : "",
112
+ });
113
+ return toTextResult(result);
114
+ },
115
+ };
116
+
117
+ api.registerTool(searchTool, { optional: true });
118
+ api.registerTool(insertTool, { optional: true });
119
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "id": "constella-openclaw",
3
+ "name": "Constella",
4
+ "description": "Constella external API plugin (search + insert notes).",
5
+ "uiHints": {
6
+ "baseUrl": {
7
+ "label": "Constella Base URL",
8
+ "placeholder": "https://fastfind.app"
9
+ },
10
+ "apiKey": {
11
+ "label": "Constella API Key",
12
+ "sensitive": true,
13
+ "placeholder": "csk_..."
14
+ }
15
+ },
16
+ "configSchema": {
17
+ "type": "object",
18
+ "additionalProperties": false,
19
+ "properties": {
20
+ "baseUrl": {
21
+ "type": "string"
22
+ },
23
+ "apiKey": {
24
+ "type": "string"
25
+ }
26
+ }
27
+ }
28
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@constellaapp/openclaw",
3
+ "version": "0.1.0",
4
+ "description": "OpenClaw plugin for Constella search and note insertion.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "files": [
8
+ "index.ts",
9
+ "src/",
10
+ "openclaw.plugin.json",
11
+ "README.md"
12
+ ],
13
+ "dependencies": {
14
+ "@sinclair/typebox": "^0.34.48"
15
+ },
16
+ "peerDependencies": {
17
+ "openclaw": ">=2026.2.13"
18
+ },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "openclaw": {
23
+ "extensions": [
24
+ "./index.ts"
25
+ ],
26
+ "install": {
27
+ "npmSpec": "@constellaapp/openclaw",
28
+ "defaultChoice": "npm"
29
+ }
30
+ }
31
+ }
package/src/client.ts ADDED
@@ -0,0 +1,81 @@
1
+ export type ConstellaLogger = {
2
+ info?: (message: string) => void;
3
+ warn?: (message: string) => void;
4
+ error?: (message: string) => void;
5
+ };
6
+
7
+ export type ConstellaClientOptions = {
8
+ baseUrl: string;
9
+ apiKey: string;
10
+ logger?: ConstellaLogger;
11
+ };
12
+
13
+ type RetrieveInfoRequest = {
14
+ query?: string;
15
+ queries?: string[];
16
+ categories?: string[];
17
+ tags?: string[];
18
+ from_date_time?: string;
19
+ to_end_date_time?: string;
20
+ top_k?: number;
21
+ similarity_setting?: number;
22
+ fixedIntegrationNames?: string[];
23
+ };
24
+
25
+ type InsertNoteRequest = {
26
+ title: string;
27
+ content?: string;
28
+ };
29
+
30
+ function normalizeBaseUrl(input: string): string {
31
+ return input.replace(/\/+$/, "");
32
+ }
33
+
34
+ export function createConstellaClient(options: ConstellaClientOptions) {
35
+ const baseUrl = normalizeBaseUrl(options.baseUrl);
36
+ const apiKey = options.apiKey;
37
+ const logger = options.logger;
38
+
39
+ async function postJson<TResponse>(
40
+ path: string,
41
+ body: Record<string, unknown>,
42
+ ): Promise<TResponse> {
43
+ const url = `${baseUrl}${path}`;
44
+ const response = await fetch(url, {
45
+ method: "POST",
46
+ headers: {
47
+ "Content-Type": "application/json",
48
+ x_access_key: apiKey,
49
+ },
50
+ body: JSON.stringify(body),
51
+ });
52
+
53
+ const text = await response.text();
54
+ let parsed: unknown;
55
+ try {
56
+ parsed = text ? JSON.parse(text) : {};
57
+ } catch {
58
+ parsed = { raw: text };
59
+ }
60
+
61
+ if (!response.ok) {
62
+ const detail =
63
+ (parsed as { detail?: string })?.detail ||
64
+ (parsed as { error?: string })?.error ||
65
+ `Constella request failed (${response.status})`;
66
+ logger?.error?.(detail);
67
+ throw new Error(detail);
68
+ }
69
+
70
+ return parsed as TResponse;
71
+ }
72
+
73
+ return {
74
+ async searchNotes(payload: RetrieveInfoRequest): Promise<unknown> {
75
+ return await postJson("/constella-external-api/retrieve-info", payload);
76
+ },
77
+ async insertNote(payload: InsertNoteRequest): Promise<unknown> {
78
+ return await postJson("/constella-external-api/insert-note", payload);
79
+ },
80
+ };
81
+ }