@kernel.chat/kbot 3.99.31 → 3.99.34
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 +14 -1
- package/dist/agents/security-agent.d.ts +31 -0
- package/dist/agents/security-agent.js +180 -0
- package/dist/agents/security-rules.d.ts +35 -0
- package/dist/agents/security-rules.js +206 -0
- package/dist/agents/specialists.d.ts +6 -0
- package/dist/agents/specialists.js +45 -0
- package/dist/architect.js +5 -0
- package/dist/auth.js +1 -1
- package/dist/channels/matrix.d.ts +4 -0
- package/dist/channels/matrix.js +28 -0
- package/dist/channels/office.d.ts +78 -0
- package/dist/channels/office.js +169 -0
- package/dist/channels/registry.d.ts +8 -0
- package/dist/channels/registry.js +38 -0
- package/dist/channels/signal.d.ts +4 -0
- package/dist/channels/signal.js +29 -0
- package/dist/channels/slack.d.ts +4 -0
- package/dist/channels/slack.js +97 -0
- package/dist/channels/teams.d.ts +4 -0
- package/dist/channels/teams.js +29 -0
- package/dist/channels/telegram.d.ts +4 -0
- package/dist/channels/telegram.js +28 -0
- package/dist/channels/types.d.ts +50 -0
- package/dist/channels/types.js +13 -0
- package/dist/channels/whatsapp.d.ts +4 -0
- package/dist/channels/whatsapp.js +28 -0
- package/dist/cli.js +81 -0
- package/dist/computer-use-coordinator.d.ts +44 -0
- package/dist/computer-use-coordinator.js +0 -0
- package/dist/file-library.d.ts +76 -0
- package/dist/file-library.js +269 -0
- package/dist/ide/mcp-server.js +4 -4
- package/dist/managed-agents-anthropic.d.ts +90 -0
- package/dist/managed-agents-anthropic.js +123 -0
- package/dist/plugins-integrity.d.ts +72 -0
- package/dist/plugins-integrity.js +153 -0
- package/dist/plugins.d.ts +13 -2
- package/dist/plugins.js +87 -10
- package/dist/setup-editor.d.ts +28 -0
- package/dist/setup-editor.js +222 -0
- package/dist/tools/anthropic-managed-agents-tools.d.ts +22 -0
- package/dist/tools/anthropic-managed-agents-tools.js +191 -0
- package/dist/tools/channel-tools.d.ts +4 -0
- package/dist/tools/channel-tools.js +80 -0
- package/dist/tools/computer-coordinator-tools.d.ts +13 -0
- package/dist/tools/computer-coordinator-tools.js +104 -0
- package/dist/tools/computer.js +463 -299
- package/dist/tools/file-library-tools.d.ts +12 -0
- package/dist/tools/file-library-tools.js +191 -0
- package/dist/tools/image-thoughtful.d.ts +31 -0
- package/dist/tools/image-thoughtful.js +233 -0
- package/dist/tools/index.js +1 -0
- package/dist/tools/matrix.js +3 -3
- package/dist/tools/redblue.js +2 -2
- package/dist/tools/security-agent-tools.d.ts +34 -0
- package/dist/tools/security-agent-tools.js +30 -0
- package/dist/tools/security-hunt.js +1 -1
- package/dist/tools/subagent.js +2 -2
- package/dist/tools/swarm-2026-04.d.ts +2 -0
- package/dist/tools/swarm-2026-04.js +91 -0
- package/dist/tools/visa-payments.js +1 -1
- package/dist/tools/workspace-agent-tools.d.ts +19 -0
- package/dist/tools/workspace-agent-tools.js +191 -0
- package/dist/workspace-agents.d.ts +132 -0
- package/dist/workspace-agents.js +379 -0
- package/package.json +1 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic Managed Agents client (April 2026 launch).
|
|
3
|
+
*
|
|
4
|
+
* Hosted long-horizon agent platform. This module is a STANDALONE backend
|
|
5
|
+
* that workspace agents can route through when ANTHROPIC_API_KEY is set.
|
|
6
|
+
* Wiring into ./workspace-agents.ts happens in a follow-up pass.
|
|
7
|
+
*
|
|
8
|
+
* Beta header: `anthropic-beta: managed-agents-2026-04-01` is sent on every
|
|
9
|
+
* request.
|
|
10
|
+
*
|
|
11
|
+
* SPEC: best-effort, refine when official docs published.
|
|
12
|
+
* Endpoint shape mirrors the public beta announcement; refine when the
|
|
13
|
+
* official OpenAPI spec lands.
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_BASE_URL = 'https://api.anthropic.com/v1';
|
|
16
|
+
const BETA_HEADER_VALUE = 'managed-agents-2026-04-01';
|
|
17
|
+
const ANTHROPIC_VERSION = '2023-06-01';
|
|
18
|
+
export class AnthropicManagedAgentsError extends Error {
|
|
19
|
+
status;
|
|
20
|
+
body;
|
|
21
|
+
constructor(message, status, body) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = 'AnthropicManagedAgentsError';
|
|
24
|
+
this.status = status;
|
|
25
|
+
this.body = body;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class AnthropicManagedAgentsClient {
|
|
29
|
+
apiKey;
|
|
30
|
+
baseUrl;
|
|
31
|
+
fetchImpl;
|
|
32
|
+
constructor(opts = {}) {
|
|
33
|
+
const apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
34
|
+
if (!apiKey) {
|
|
35
|
+
throw new Error('AnthropicManagedAgentsClient: ANTHROPIC_API_KEY is not set');
|
|
36
|
+
}
|
|
37
|
+
this.apiKey = apiKey;
|
|
38
|
+
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
39
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
40
|
+
}
|
|
41
|
+
// ── Sessions ────────────────────────────────────────────────────────────
|
|
42
|
+
async createSession(input) {
|
|
43
|
+
if (!input.mission || !input.mission.trim()) {
|
|
44
|
+
throw new Error('createSession: mission is required');
|
|
45
|
+
}
|
|
46
|
+
const body = { mission: input.mission };
|
|
47
|
+
if (input.model)
|
|
48
|
+
body.model = input.model;
|
|
49
|
+
if (input.allowedTools)
|
|
50
|
+
body.tools = input.allowedTools;
|
|
51
|
+
return this.request('POST', '/agents/sessions', body);
|
|
52
|
+
}
|
|
53
|
+
async sendTurn(input) {
|
|
54
|
+
if (!input.sessionId)
|
|
55
|
+
throw new Error('sendTurn: sessionId is required');
|
|
56
|
+
return this.request('POST', `/agents/sessions/${encodeURIComponent(input.sessionId)}/turns`, { input: input.input });
|
|
57
|
+
}
|
|
58
|
+
async getSession(input) {
|
|
59
|
+
if (!input.sessionId)
|
|
60
|
+
throw new Error('getSession: sessionId is required');
|
|
61
|
+
return this.request('GET', `/agents/sessions/${encodeURIComponent(input.sessionId)}`);
|
|
62
|
+
}
|
|
63
|
+
async listSessions() {
|
|
64
|
+
return this.request('GET', '/agents/sessions');
|
|
65
|
+
}
|
|
66
|
+
async closeSession(input) {
|
|
67
|
+
if (!input.sessionId) {
|
|
68
|
+
throw new Error('closeSession: sessionId is required');
|
|
69
|
+
}
|
|
70
|
+
return this.request('DELETE', `/agents/sessions/${encodeURIComponent(input.sessionId)}`);
|
|
71
|
+
}
|
|
72
|
+
// ── Memory ──────────────────────────────────────────────────────────────
|
|
73
|
+
async memoryRead(input) {
|
|
74
|
+
if (!input.sessionId)
|
|
75
|
+
throw new Error('memoryRead: sessionId is required');
|
|
76
|
+
const path = input.key
|
|
77
|
+
? `/agents/sessions/${encodeURIComponent(input.sessionId)}/memory/${encodeURIComponent(input.key)}`
|
|
78
|
+
: `/agents/sessions/${encodeURIComponent(input.sessionId)}/memory`;
|
|
79
|
+
return this.request('GET', path);
|
|
80
|
+
}
|
|
81
|
+
async memoryWrite(input) {
|
|
82
|
+
if (!input.sessionId)
|
|
83
|
+
throw new Error('memoryWrite: sessionId is required');
|
|
84
|
+
if (!input.key)
|
|
85
|
+
throw new Error('memoryWrite: key is required');
|
|
86
|
+
return this.request('POST', `/agents/sessions/${encodeURIComponent(input.sessionId)}/memory`, { key: input.key, value: input.value });
|
|
87
|
+
}
|
|
88
|
+
// ── Internals ───────────────────────────────────────────────────────────
|
|
89
|
+
async request(method, path, body) {
|
|
90
|
+
const url = `${this.baseUrl}${path}`;
|
|
91
|
+
const headers = {
|
|
92
|
+
'x-api-key': this.apiKey,
|
|
93
|
+
'anthropic-version': ANTHROPIC_VERSION,
|
|
94
|
+
'anthropic-beta': BETA_HEADER_VALUE,
|
|
95
|
+
};
|
|
96
|
+
const init = { method, headers };
|
|
97
|
+
if (body !== undefined && method !== 'GET') {
|
|
98
|
+
headers['content-type'] = 'application/json';
|
|
99
|
+
init.body = JSON.stringify(body);
|
|
100
|
+
}
|
|
101
|
+
let res;
|
|
102
|
+
try {
|
|
103
|
+
res = await this.fetchImpl(url, init);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
107
|
+
throw new AnthropicManagedAgentsError(`network error contacting ${url}: ${msg}`, 0, '');
|
|
108
|
+
}
|
|
109
|
+
const text = await res.text();
|
|
110
|
+
if (!res.ok) {
|
|
111
|
+
throw new AnthropicManagedAgentsError(`Anthropic Managed Agents ${method} ${path} failed: ${res.status} ${res.statusText}`, res.status, text);
|
|
112
|
+
}
|
|
113
|
+
if (!text)
|
|
114
|
+
return {};
|
|
115
|
+
try {
|
|
116
|
+
return JSON.parse(text);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
throw new AnthropicManagedAgentsError(`Anthropic Managed Agents ${method} ${path} returned non-JSON body`, res.status, text);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=managed-agents-anthropic.js.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin integrity verification — fail-closed SHA-256 manifest checking.
|
|
3
|
+
*
|
|
4
|
+
* Ports OpenClaw's `plugins.json5` integrity-pinned plugin model. Plugins must
|
|
5
|
+
* be declared in a manifest with a SHA-256 hash; the loader verifies the file
|
|
6
|
+
* on disk against the manifest entry and refuses to load on drift.
|
|
7
|
+
*
|
|
8
|
+
* Manifest format here is plain JSON. If we add a JSON5 dep later (e.g.
|
|
9
|
+
* `json5`), swap `JSON.parse` for `JSON5.parse` and accept `.json5` files.
|
|
10
|
+
*/
|
|
11
|
+
export interface ManifestEntry {
|
|
12
|
+
name: string;
|
|
13
|
+
version: string;
|
|
14
|
+
/** Path relative to the plugins directory (e.g. ~/.kbot/plugins). */
|
|
15
|
+
path: string;
|
|
16
|
+
/** SHA-256 hash, base64-encoded, prefixed with "sha256-". */
|
|
17
|
+
integrity: string;
|
|
18
|
+
}
|
|
19
|
+
export interface Manifest {
|
|
20
|
+
schemaVersion: 1;
|
|
21
|
+
plugins: ManifestEntry[];
|
|
22
|
+
}
|
|
23
|
+
export type VerifyResult = {
|
|
24
|
+
ok: true;
|
|
25
|
+
} | {
|
|
26
|
+
ok: false;
|
|
27
|
+
reason: string;
|
|
28
|
+
expected: string;
|
|
29
|
+
actual: string;
|
|
30
|
+
};
|
|
31
|
+
export interface VerifyAllResult {
|
|
32
|
+
verified: string[];
|
|
33
|
+
failed: Array<{
|
|
34
|
+
name: string;
|
|
35
|
+
reason: string;
|
|
36
|
+
expected?: string;
|
|
37
|
+
actual?: string;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
export declare class IntegrityError extends Error {
|
|
41
|
+
failed: VerifyAllResult['failed'];
|
|
42
|
+
constructor(failed: VerifyAllResult['failed']);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Load and validate a manifest from disk. Throws on missing file, invalid
|
|
46
|
+
* JSON, or schema violation.
|
|
47
|
+
*/
|
|
48
|
+
export declare function loadManifest(path: string): Promise<Manifest>;
|
|
49
|
+
/**
|
|
50
|
+
* Verify a single plugin file against its manifest entry.
|
|
51
|
+
*
|
|
52
|
+
* - Missing file → `{ ok: false, reason: "plugin file missing" }`
|
|
53
|
+
* - Hash mismatch → `{ ok: false, reason: "integrity drift" }`
|
|
54
|
+
* - Match → `{ ok: true }`
|
|
55
|
+
*/
|
|
56
|
+
export declare function verifyPlugin(filePath: string, manifestEntry: ManifestEntry): Promise<VerifyResult>;
|
|
57
|
+
/**
|
|
58
|
+
* Verify every plugin in a manifest against the corresponding files under
|
|
59
|
+
* `pluginsDir`. Resolves entry paths relative to `pluginsDir` unless
|
|
60
|
+
* absolute.
|
|
61
|
+
*
|
|
62
|
+
* Always returns a result; never throws on drift. Callers should pass the
|
|
63
|
+
* result to `enforce()` to fail closed.
|
|
64
|
+
*/
|
|
65
|
+
export declare function verifyAllPlugins(manifestPath: string, pluginsDir: string): Promise<VerifyAllResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Fail-closed gate. Throws `IntegrityError` if any plugin failed verification.
|
|
68
|
+
* No-op when all plugins are verified. Loaders should call this unless
|
|
69
|
+
* integrity checking has been explicitly disabled (e.g. dev override).
|
|
70
|
+
*/
|
|
71
|
+
export declare function enforce(result: VerifyAllResult): void;
|
|
72
|
+
//# sourceMappingURL=plugins-integrity.d.ts.map
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin integrity verification — fail-closed SHA-256 manifest checking.
|
|
3
|
+
*
|
|
4
|
+
* Ports OpenClaw's `plugins.json5` integrity-pinned plugin model. Plugins must
|
|
5
|
+
* be declared in a manifest with a SHA-256 hash; the loader verifies the file
|
|
6
|
+
* on disk against the manifest entry and refuses to load on drift.
|
|
7
|
+
*
|
|
8
|
+
* Manifest format here is plain JSON. If we add a JSON5 dep later (e.g.
|
|
9
|
+
* `json5`), swap `JSON.parse` for `JSON5.parse` and accept `.json5` files.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
13
|
+
import { resolve, isAbsolute } from 'node:path';
|
|
14
|
+
export class IntegrityError extends Error {
|
|
15
|
+
failed;
|
|
16
|
+
constructor(failed) {
|
|
17
|
+
const names = failed.map((f) => `${f.name} (${f.reason})`).join(', ');
|
|
18
|
+
super(`Plugin integrity check failed: ${names}`);
|
|
19
|
+
this.name = 'IntegrityError';
|
|
20
|
+
this.failed = failed;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const INTEGRITY_PREFIX = 'sha256-';
|
|
24
|
+
function isManifestEntry(v) {
|
|
25
|
+
if (!v || typeof v !== 'object')
|
|
26
|
+
return false;
|
|
27
|
+
const e = v;
|
|
28
|
+
return (typeof e.name === 'string' &&
|
|
29
|
+
typeof e.version === 'string' &&
|
|
30
|
+
typeof e.path === 'string' &&
|
|
31
|
+
typeof e.integrity === 'string' &&
|
|
32
|
+
e.integrity.startsWith(INTEGRITY_PREFIX));
|
|
33
|
+
}
|
|
34
|
+
function isManifest(v) {
|
|
35
|
+
if (!v || typeof v !== 'object')
|
|
36
|
+
return false;
|
|
37
|
+
const m = v;
|
|
38
|
+
if (m.schemaVersion !== 1)
|
|
39
|
+
return false;
|
|
40
|
+
if (!Array.isArray(m.plugins))
|
|
41
|
+
return false;
|
|
42
|
+
return m.plugins.every(isManifestEntry);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Load and validate a manifest from disk. Throws on missing file, invalid
|
|
46
|
+
* JSON, or schema violation.
|
|
47
|
+
*/
|
|
48
|
+
export async function loadManifest(path) {
|
|
49
|
+
let raw;
|
|
50
|
+
try {
|
|
51
|
+
raw = await readFile(path, 'utf8');
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
throw new Error(`Failed to read manifest at ${path}: ${err.message}`);
|
|
55
|
+
}
|
|
56
|
+
let parsed;
|
|
57
|
+
try {
|
|
58
|
+
parsed = JSON.parse(raw);
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
throw new Error(`Manifest is not valid JSON: ${err.message}`);
|
|
62
|
+
}
|
|
63
|
+
if (!isManifest(parsed)) {
|
|
64
|
+
throw new Error(`Manifest at ${path} is malformed: expected { schemaVersion: 1, plugins: [{name, version, path, integrity: "sha256-..."}] }`);
|
|
65
|
+
}
|
|
66
|
+
return parsed;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Compute SHA-256 of a file and return it formatted as `sha256-<base64>`.
|
|
70
|
+
*/
|
|
71
|
+
async function computeIntegrity(filePath) {
|
|
72
|
+
const buf = await readFile(filePath);
|
|
73
|
+
const digest = createHash('sha256').update(buf).digest('base64');
|
|
74
|
+
return `${INTEGRITY_PREFIX}${digest}`;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Verify a single plugin file against its manifest entry.
|
|
78
|
+
*
|
|
79
|
+
* - Missing file → `{ ok: false, reason: "plugin file missing" }`
|
|
80
|
+
* - Hash mismatch → `{ ok: false, reason: "integrity drift" }`
|
|
81
|
+
* - Match → `{ ok: true }`
|
|
82
|
+
*/
|
|
83
|
+
export async function verifyPlugin(filePath, manifestEntry) {
|
|
84
|
+
try {
|
|
85
|
+
const s = await stat(filePath);
|
|
86
|
+
if (!s.isFile()) {
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
reason: 'plugin file missing',
|
|
90
|
+
expected: manifestEntry.integrity,
|
|
91
|
+
actual: '',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
reason: 'plugin file missing',
|
|
99
|
+
expected: manifestEntry.integrity,
|
|
100
|
+
actual: '',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const actual = await computeIntegrity(filePath);
|
|
104
|
+
if (actual !== manifestEntry.integrity) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
reason: 'integrity drift',
|
|
108
|
+
expected: manifestEntry.integrity,
|
|
109
|
+
actual,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return { ok: true };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Verify every plugin in a manifest against the corresponding files under
|
|
116
|
+
* `pluginsDir`. Resolves entry paths relative to `pluginsDir` unless
|
|
117
|
+
* absolute.
|
|
118
|
+
*
|
|
119
|
+
* Always returns a result; never throws on drift. Callers should pass the
|
|
120
|
+
* result to `enforce()` to fail closed.
|
|
121
|
+
*/
|
|
122
|
+
export async function verifyAllPlugins(manifestPath, pluginsDir) {
|
|
123
|
+
const manifest = await loadManifest(manifestPath);
|
|
124
|
+
const verified = [];
|
|
125
|
+
const failed = [];
|
|
126
|
+
for (const entry of manifest.plugins) {
|
|
127
|
+
const filePath = isAbsolute(entry.path) ? entry.path : resolve(pluginsDir, entry.path);
|
|
128
|
+
const result = await verifyPlugin(filePath, entry);
|
|
129
|
+
if (result.ok) {
|
|
130
|
+
verified.push(entry.name);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
failed.push({
|
|
134
|
+
name: entry.name,
|
|
135
|
+
reason: result.reason,
|
|
136
|
+
expected: result.expected,
|
|
137
|
+
actual: result.actual,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return { verified, failed };
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Fail-closed gate. Throws `IntegrityError` if any plugin failed verification.
|
|
145
|
+
* No-op when all plugins are verified. Loaders should call this unless
|
|
146
|
+
* integrity checking has been explicitly disabled (e.g. dev override).
|
|
147
|
+
*/
|
|
148
|
+
export function enforce(result) {
|
|
149
|
+
if (result.failed.length > 0) {
|
|
150
|
+
throw new IntegrityError(result.failed);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=plugins-integrity.js.map
|
package/dist/plugins.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
export interface LoadPluginsOptions {
|
|
2
|
+
/** Override the plugin directory (used by tests). Defaults to ~/.kbot/plugins. */
|
|
3
|
+
pluginsDir?: string;
|
|
4
|
+
/** Override the integrity manifest path. Defaults to ~/.kbot/plugins.json. */
|
|
5
|
+
manifestPath?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Override the integrity-disabled flag. Defaults to reading
|
|
8
|
+
* `process.env.KBOT_PLUGIN_INTEGRITY === 'off'`.
|
|
9
|
+
*/
|
|
10
|
+
integrityDisabled?: boolean;
|
|
11
|
+
}
|
|
1
12
|
export interface PluginDefinition {
|
|
2
13
|
name: string;
|
|
3
14
|
description: string;
|
|
@@ -18,9 +29,9 @@ export interface PluginManifest {
|
|
|
18
29
|
error?: string;
|
|
19
30
|
}
|
|
20
31
|
/** Ensure the plugins directory exists */
|
|
21
|
-
export declare function ensurePluginsDir(): string;
|
|
32
|
+
export declare function ensurePluginsDir(dir?: string): string;
|
|
22
33
|
/** Load all plugins from ~/.kbot/plugins/ */
|
|
23
|
-
export declare function loadPlugins(verbose?: boolean): Promise<PluginManifest[]>;
|
|
34
|
+
export declare function loadPlugins(verbose?: boolean, opts?: LoadPluginsOptions): Promise<PluginManifest[]>;
|
|
24
35
|
/** List loaded plugins */
|
|
25
36
|
export declare function getLoadedPlugins(): PluginManifest[];
|
|
26
37
|
/** Format plugin list for display */
|
package/dist/plugins.js
CHANGED
|
@@ -14,23 +14,82 @@ import { existsSync, readdirSync, mkdirSync, writeFileSync, statSync } from 'nod
|
|
|
14
14
|
import { join } from 'node:path';
|
|
15
15
|
import { homedir } from 'node:os';
|
|
16
16
|
import { pathToFileURL } from 'node:url';
|
|
17
|
+
import chalk from 'chalk';
|
|
17
18
|
import { registerTool } from './tools/index.js';
|
|
19
|
+
import { IntegrityError, verifyAllPlugins, enforce, } from './plugins-integrity.js';
|
|
18
20
|
const PLUGINS_DIR = join(homedir(), '.kbot', 'plugins');
|
|
19
21
|
const PLUGIN_EXTENSIONS = ['.js', '.mjs'];
|
|
22
|
+
/**
|
|
23
|
+
* Default integrity manifest path. Override with KBOT_PLUGIN_MANIFEST env var
|
|
24
|
+
* or the `manifestPath` option to `loadPlugins`.
|
|
25
|
+
*/
|
|
26
|
+
const DEFAULT_MANIFEST_PATH = join(homedir(), '.kbot', 'plugins.json');
|
|
20
27
|
const loadedPlugins = [];
|
|
21
28
|
/** Ensure the plugins directory exists */
|
|
22
|
-
export function ensurePluginsDir() {
|
|
23
|
-
if (!existsSync(
|
|
24
|
-
mkdirSync(
|
|
29
|
+
export function ensurePluginsDir(dir = PLUGINS_DIR) {
|
|
30
|
+
if (!existsSync(dir)) {
|
|
31
|
+
mkdirSync(dir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
return dir;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Run the integrity manifest gate before discovery.
|
|
37
|
+
*
|
|
38
|
+
* Behaviour:
|
|
39
|
+
* - If KBOT_PLUGIN_INTEGRITY=off (or `opts.integrityDisabled === true`):
|
|
40
|
+
* emit a yellow warning and return `null` (verification skipped, all
|
|
41
|
+
* discovered plugins will load).
|
|
42
|
+
* - If the manifest file does not exist: emit a yellow info note and
|
|
43
|
+
* return `null` (back-compat — manifest is optional today).
|
|
44
|
+
* - If the manifest exists and verifies: return the `VerifyAllResult` so
|
|
45
|
+
* the caller can restrict loads to `result.verified`.
|
|
46
|
+
* - If the manifest exists and any plugin fails: throw `IntegrityError`
|
|
47
|
+
* (kbot refuses to start).
|
|
48
|
+
*/
|
|
49
|
+
async function runIntegrityGate(manifestPath, pluginsDir, integrityDisabled) {
|
|
50
|
+
if (integrityDisabled) {
|
|
51
|
+
console.error(chalk.yellow(` ⚠ Plugin integrity verification is DISABLED (KBOT_PLUGIN_INTEGRITY=off). ` +
|
|
52
|
+
`Plugins under ${pluginsDir} will load without hash checking.`));
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
if (!existsSync(manifestPath)) {
|
|
56
|
+
console.error(chalk.yellow(` ⚠ no plugin manifest at ${manifestPath} — plugins are unverified. ` +
|
|
57
|
+
`Create one to pin plugins by SHA-256 (see PLUGINS_INTEGRITY.md).`));
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
// verifyAllPlugins resolves manifest entry paths against pluginsDir, so the
|
|
61
|
+
// file layout is: <pluginsDir>/<entry.path> matches the hash in the manifest.
|
|
62
|
+
try {
|
|
63
|
+
const result = await verifyAllPlugins(manifestPath, pluginsDir);
|
|
64
|
+
if (result.failed.length > 0) {
|
|
65
|
+
const lines = result.failed
|
|
66
|
+
.map((f) => ` - ${f.name}: ${f.reason}`)
|
|
67
|
+
.join('\n');
|
|
68
|
+
console.error(chalk.red(` ✗ Plugin integrity check failed for ${result.failed.length} plugin(s):\n${lines}\n` +
|
|
69
|
+
` Manifest: ${manifestPath}\n` +
|
|
70
|
+
` To refresh hashes, recompute SHA-256 for each plugin file and update the manifest. ` +
|
|
71
|
+
`Set KBOT_PLUGIN_INTEGRITY=off ONLY for local dev; never in production.`));
|
|
72
|
+
enforce(result); // throws IntegrityError
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
if (err instanceof IntegrityError)
|
|
78
|
+
throw err;
|
|
79
|
+
// loadManifest threw (malformed JSON, schema violation, etc.) — fail closed.
|
|
80
|
+
console.error(chalk.red(` ✗ Failed to load plugin integrity manifest at ${manifestPath}: ${err.message}`));
|
|
81
|
+
throw err;
|
|
25
82
|
}
|
|
26
|
-
return PLUGINS_DIR;
|
|
27
83
|
}
|
|
28
84
|
/** Load all plugins from ~/.kbot/plugins/ */
|
|
29
|
-
export async function loadPlugins(verbose = false) {
|
|
30
|
-
|
|
85
|
+
export async function loadPlugins(verbose = false, opts = {}) {
|
|
86
|
+
const pluginsDir = opts.pluginsDir ?? PLUGINS_DIR;
|
|
87
|
+
const manifestPath = opts.manifestPath ?? process.env.KBOT_PLUGIN_MANIFEST ?? DEFAULT_MANIFEST_PATH;
|
|
88
|
+
const integrityDisabled = opts.integrityDisabled ?? process.env.KBOT_PLUGIN_INTEGRITY === 'off';
|
|
89
|
+
ensurePluginsDir(pluginsDir);
|
|
31
90
|
// Security: reject plugins if directory is world/group-writable
|
|
32
91
|
try {
|
|
33
|
-
const dirStat = statSync(
|
|
92
|
+
const dirStat = statSync(pluginsDir);
|
|
34
93
|
if ((dirStat.mode & 0o022) !== 0) {
|
|
35
94
|
if (verbose)
|
|
36
95
|
console.error(' ⚠ Plugin directory is writable by others — skipping plugins for security');
|
|
@@ -40,13 +99,31 @@ export async function loadPlugins(verbose = false) {
|
|
|
40
99
|
catch {
|
|
41
100
|
return [];
|
|
42
101
|
}
|
|
43
|
-
|
|
102
|
+
// Integrity gate — runs BEFORE any file is imported. Throws IntegrityError
|
|
103
|
+
// on drift unless KBOT_PLUGIN_INTEGRITY=off.
|
|
104
|
+
const integrity = await runIntegrityGate(manifestPath, pluginsDir, integrityDisabled);
|
|
105
|
+
// When a manifest verified successfully, restrict loads to verified names.
|
|
106
|
+
// When the manifest is missing or integrity is disabled, allow every file.
|
|
107
|
+
const verifiedNames = integrity
|
|
108
|
+
? new Set(integrity.verified)
|
|
109
|
+
: null;
|
|
110
|
+
const files = readdirSync(pluginsDir).filter(f => PLUGIN_EXTENSIONS.some(ext => f.endsWith(ext)));
|
|
44
111
|
if (files.length === 0)
|
|
45
112
|
return [];
|
|
46
113
|
for (const file of files) {
|
|
114
|
+
const pluginName = file.replace(/\.[^.]+$/, '');
|
|
115
|
+
// When manifest verification ran, only load plugins whose name appears in
|
|
116
|
+
// the verified set. Files not declared in the manifest are silently
|
|
117
|
+
// skipped (they did not pass — and could not have passed — verification).
|
|
118
|
+
if (verifiedNames && !verifiedNames.has(pluginName)) {
|
|
119
|
+
if (verbose) {
|
|
120
|
+
console.error(chalk.yellow(` ⚠ Skipping ${file} — not declared in plugin manifest`));
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
47
124
|
// Security: reject plugin files that are group/world-writable
|
|
48
125
|
try {
|
|
49
|
-
const fileStat = statSync(join(
|
|
126
|
+
const fileStat = statSync(join(pluginsDir, file));
|
|
50
127
|
if ((fileStat.mode & 0o022) !== 0) {
|
|
51
128
|
loadedPlugins.push({
|
|
52
129
|
name: file.replace(/\.[^.]+$/, ''),
|
|
@@ -61,7 +138,7 @@ export async function loadPlugins(verbose = false) {
|
|
|
61
138
|
catch {
|
|
62
139
|
continue;
|
|
63
140
|
}
|
|
64
|
-
const filePath = join(
|
|
141
|
+
const filePath = join(pluginsDir, file);
|
|
65
142
|
const manifest = {
|
|
66
143
|
name: file.replace(/\.[^.]+$/, ''),
|
|
67
144
|
file,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { tmpdir } from 'node:os';
|
|
2
|
+
export interface SetupOptions {
|
|
3
|
+
force?: boolean;
|
|
4
|
+
/** Override $HOME for testing. */
|
|
5
|
+
home?: string;
|
|
6
|
+
/** Override cwd for testing. */
|
|
7
|
+
cwd?: string;
|
|
8
|
+
/** Override the kbot binary path. */
|
|
9
|
+
kbotBin?: string;
|
|
10
|
+
/** Override the kbot-local-mcp script path. */
|
|
11
|
+
kbotLocalMcpPath?: string;
|
|
12
|
+
/** Override the bundled skill template path. */
|
|
13
|
+
skillTemplatePath?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SetupResult {
|
|
16
|
+
configPath: string;
|
|
17
|
+
mcpAdded: string[];
|
|
18
|
+
mcpAlreadyPresent: string[];
|
|
19
|
+
skillCopied?: string;
|
|
20
|
+
skillAlreadyPresent?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function setupClaudeCode(opts?: SetupOptions): SetupResult;
|
|
23
|
+
export declare function setupCursor(opts?: SetupOptions): SetupResult;
|
|
24
|
+
export declare function setupZed(opts?: SetupOptions): SetupResult;
|
|
25
|
+
export declare const _testHelpers: {
|
|
26
|
+
tmpdir: typeof tmpdir;
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=setup-editor.d.ts.map
|