@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.
Files changed (67) hide show
  1. package/README.md +14 -1
  2. package/dist/agents/security-agent.d.ts +31 -0
  3. package/dist/agents/security-agent.js +180 -0
  4. package/dist/agents/security-rules.d.ts +35 -0
  5. package/dist/agents/security-rules.js +206 -0
  6. package/dist/agents/specialists.d.ts +6 -0
  7. package/dist/agents/specialists.js +45 -0
  8. package/dist/architect.js +5 -0
  9. package/dist/auth.js +1 -1
  10. package/dist/channels/matrix.d.ts +4 -0
  11. package/dist/channels/matrix.js +28 -0
  12. package/dist/channels/office.d.ts +78 -0
  13. package/dist/channels/office.js +169 -0
  14. package/dist/channels/registry.d.ts +8 -0
  15. package/dist/channels/registry.js +38 -0
  16. package/dist/channels/signal.d.ts +4 -0
  17. package/dist/channels/signal.js +29 -0
  18. package/dist/channels/slack.d.ts +4 -0
  19. package/dist/channels/slack.js +97 -0
  20. package/dist/channels/teams.d.ts +4 -0
  21. package/dist/channels/teams.js +29 -0
  22. package/dist/channels/telegram.d.ts +4 -0
  23. package/dist/channels/telegram.js +28 -0
  24. package/dist/channels/types.d.ts +50 -0
  25. package/dist/channels/types.js +13 -0
  26. package/dist/channels/whatsapp.d.ts +4 -0
  27. package/dist/channels/whatsapp.js +28 -0
  28. package/dist/cli.js +81 -0
  29. package/dist/computer-use-coordinator.d.ts +44 -0
  30. package/dist/computer-use-coordinator.js +0 -0
  31. package/dist/file-library.d.ts +76 -0
  32. package/dist/file-library.js +269 -0
  33. package/dist/ide/mcp-server.js +4 -4
  34. package/dist/managed-agents-anthropic.d.ts +90 -0
  35. package/dist/managed-agents-anthropic.js +123 -0
  36. package/dist/plugins-integrity.d.ts +72 -0
  37. package/dist/plugins-integrity.js +153 -0
  38. package/dist/plugins.d.ts +13 -2
  39. package/dist/plugins.js +87 -10
  40. package/dist/setup-editor.d.ts +28 -0
  41. package/dist/setup-editor.js +222 -0
  42. package/dist/tools/anthropic-managed-agents-tools.d.ts +22 -0
  43. package/dist/tools/anthropic-managed-agents-tools.js +191 -0
  44. package/dist/tools/channel-tools.d.ts +4 -0
  45. package/dist/tools/channel-tools.js +80 -0
  46. package/dist/tools/computer-coordinator-tools.d.ts +13 -0
  47. package/dist/tools/computer-coordinator-tools.js +104 -0
  48. package/dist/tools/computer.js +463 -299
  49. package/dist/tools/file-library-tools.d.ts +12 -0
  50. package/dist/tools/file-library-tools.js +191 -0
  51. package/dist/tools/image-thoughtful.d.ts +31 -0
  52. package/dist/tools/image-thoughtful.js +233 -0
  53. package/dist/tools/index.js +1 -0
  54. package/dist/tools/matrix.js +3 -3
  55. package/dist/tools/redblue.js +2 -2
  56. package/dist/tools/security-agent-tools.d.ts +34 -0
  57. package/dist/tools/security-agent-tools.js +30 -0
  58. package/dist/tools/security-hunt.js +1 -1
  59. package/dist/tools/subagent.js +2 -2
  60. package/dist/tools/swarm-2026-04.d.ts +2 -0
  61. package/dist/tools/swarm-2026-04.js +91 -0
  62. package/dist/tools/visa-payments.js +1 -1
  63. package/dist/tools/workspace-agent-tools.d.ts +19 -0
  64. package/dist/tools/workspace-agent-tools.js +191 -0
  65. package/dist/workspace-agents.d.ts +132 -0
  66. package/dist/workspace-agents.js +379 -0
  67. 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(PLUGINS_DIR)) {
24
- mkdirSync(PLUGINS_DIR, { recursive: true });
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
- ensurePluginsDir();
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(PLUGINS_DIR);
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
- const files = readdirSync(PLUGINS_DIR).filter(f => PLUGIN_EXTENSIONS.some(ext => f.endsWith(ext)));
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(PLUGINS_DIR, file));
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(PLUGINS_DIR, file);
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