@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 +71 -0
- package/index.ts +119 -0
- package/openclaw.plugin.json +28 -0
- package/package.json +31 -0
- package/src/client.ts +81 -0
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
|
+
}
|