@karmaniverous/jeeves 0.5.10 → 0.5.12

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.
@@ -24,6 +24,20 @@ When editing files outside the workspace, use the bridge pattern: copy in → ed
24
24
 
25
25
  **Cross-channel sends:** Use the `message` tool with an explicit `target` to send to a different channel or DM.
26
26
 
27
+ ### Slack File Downloads
28
+
29
+ To download a Slack-hosted file, first try the `message` tool's `download-file` action. If that fails, fall back to a direct HTTP fetch using the bot token:
30
+
31
+ ```js
32
+ fetch(url_private_download, {
33
+ headers: { Authorization: 'Bearer ' + botToken },
34
+ });
35
+ ```
36
+
37
+ The bot token is at `channels.slack.accounts.default.botToken` in `openclaw.json`.
38
+
39
+ Never tell the user a file can't be downloaded until both methods have been tried.
40
+
27
41
  ### Plugin Lifecycle
28
42
 
29
43
  ```bash
@@ -268,14 +268,14 @@ const PLATFORM_COMPONENTS = [
268
268
  * Core library version, inlined at build time.
269
269
  *
270
270
  * @remarks
271
- * The `0.5.9` placeholder is replaced by
271
+ * The `0.5.11` placeholder is replaced by
272
272
  * `@rollup/plugin-replace` during the build with the actual version
273
273
  * from `package.json`. This ensures the correct version survives
274
274
  * when consumers bundle core into their own dist (where runtime
275
275
  * `import.meta.url`-based resolution would find the wrong package.json).
276
276
  */
277
277
  /** The core library version from package.json (inlined at build time). */
278
- const CORE_VERSION = '0.5.9';
278
+ const CORE_VERSION = '0.5.11';
279
279
 
280
280
  /**
281
281
  * Runtime Node.js version floor check.
@@ -430,12 +430,26 @@ function resolveConfigValue(flagValue, envValue, fileValue, defaultValue) {
430
430
  * - `{configRoot}/jeeves-{name}/` for each component
431
431
  */
432
432
  let state;
433
+ const WINDOWS_DRIVE_RE = /^[a-zA-Z]:/;
434
+ /**
435
+ * Throw if a path looks like a Windows drive letter on a non-Windows platform.
436
+ *
437
+ * @param label - Human-readable name for the path (used in error messages).
438
+ * @param value - The raw path string to validate.
439
+ */
440
+ function rejectWindowsDrivePath(label, value) {
441
+ if (process.platform !== 'win32' && WINDOWS_DRIVE_RE.test(value)) {
442
+ throw new Error(`jeeves-core: ${label} "${value}" looks like a Windows drive-letter path and will not resolve correctly on this platform.`);
443
+ }
444
+ }
433
445
  /**
434
446
  * Initialize the core library with workspace and config root paths.
435
447
  *
436
448
  * @param options - Workspace and config root paths.
437
449
  */
438
450
  function init(options) {
451
+ rejectWindowsDrivePath('configRoot', options.configRoot);
452
+ rejectWindowsDrivePath('workspacePath', options.workspacePath);
439
453
  state = {
440
454
  workspacePath: options.workspacePath,
441
455
  configRoot: options.configRoot,
@@ -468,7 +482,8 @@ var init$1 = /*#__PURE__*/Object.freeze({
468
482
  __proto__: null,
469
483
  getCoreConfigDir: getCoreConfigDir,
470
484
  getWorkspacePath: getWorkspacePath,
471
- init: init
485
+ init: init,
486
+ rejectWindowsDrivePath: rejectWindowsDrivePath
472
487
  });
473
488
 
474
489
  /**
@@ -519,6 +534,10 @@ function resolveCliConfig(opts) {
519
534
  */
520
535
  function initFromOptions(opts) {
521
536
  const resolved = resolveCliConfig(opts);
537
+ // Validate raw values BEFORE resolve() — on Linux, resolve('j:/config')
538
+ // produces '/cwd/j:/config' which masks the drive-letter pattern.
539
+ rejectWindowsDrivePath('configRoot', resolved.core.configRoot.value);
540
+ rejectWindowsDrivePath('workspacePath', resolved.core.workspace.value);
522
541
  init({
523
542
  workspacePath: resolve(resolved.core.workspace.value),
524
543
  configRoot: resolve(resolved.core.configRoot.value),
@@ -1358,6 +1377,20 @@ When editing files outside the workspace, use the bridge pattern: copy in → ed
1358
1377
 
1359
1378
  **Cross-channel sends:** Use the \`message\` tool with an explicit \`target\` to send to a different channel or DM.
1360
1379
 
1380
+ ### Slack File Downloads
1381
+
1382
+ To download a Slack-hosted file, first try the \`message\` tool's \`download-file\` action. If that fails, fall back to a direct HTTP fetch using the bot token:
1383
+
1384
+ \`\`\`js
1385
+ fetch(url_private_download, {
1386
+ headers: { Authorization: 'Bearer ' + botToken },
1387
+ });
1388
+ \`\`\`
1389
+
1390
+ The bot token is at \`channels.slack.accounts.default.botToken\` in \`openclaw.json\`.
1391
+
1392
+ Never tell the user a file can't be downloaded until both methods have been tried.
1393
+
1361
1394
  ### Plugin Lifecycle
1362
1395
 
1363
1396
  \`\`\`bash
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import { execSync } from 'node:child_process';
2
3
  import { writeFileSync, renameSync, unlinkSync, existsSync, mkdirSync, readFileSync, readdirSync, copyFileSync, rmSync } from 'node:fs';
3
4
  import { dirname, basename, join, resolve } from 'node:path';
4
5
  import * as commander from 'commander';
5
6
  import { randomUUID } from 'node:crypto';
6
7
  import { lock } from 'proper-lockfile';
7
8
  import 'semver';
8
- import 'node:child_process';
9
9
  import { homedir } from 'node:os';
10
10
  import { z } from 'zod';
11
11
  import { fileURLToPath } from 'node:url';
@@ -128,14 +128,14 @@ const COMPONENT_VERSIONS_FILE = 'component-versions.json';
128
128
  * Core library version, inlined at build time.
129
129
  *
130
130
  * @remarks
131
- * The `0.5.9` placeholder is replaced by
131
+ * The `0.5.11` placeholder is replaced by
132
132
  * `@rollup/plugin-replace` during the build with the actual version
133
133
  * from `package.json`. This ensures the correct version survives
134
134
  * when consumers bundle core into their own dist (where runtime
135
135
  * `import.meta.url`-based resolution would find the wrong package.json).
136
136
  */
137
137
  /** The core library version from package.json (inlined at build time). */
138
- const CORE_VERSION = '0.5.9';
138
+ const CORE_VERSION = '0.5.11';
139
139
 
140
140
  /**
141
141
  * Shared file I/O helpers for managed section operations.
@@ -374,12 +374,26 @@ const SECTION_ORDER = [
374
374
  * - `{configRoot}/jeeves-{name}/` for each component
375
375
  */
376
376
  let state;
377
+ const WINDOWS_DRIVE_RE = /^[a-zA-Z]:/;
378
+ /**
379
+ * Throw if a path looks like a Windows drive letter on a non-Windows platform.
380
+ *
381
+ * @param label - Human-readable name for the path (used in error messages).
382
+ * @param value - The raw path string to validate.
383
+ */
384
+ function rejectWindowsDrivePath(label, value) {
385
+ if (process.platform !== 'win32' && WINDOWS_DRIVE_RE.test(value)) {
386
+ throw new Error(`jeeves-core: ${label} "${value}" looks like a Windows drive-letter path and will not resolve correctly on this platform.`);
387
+ }
388
+ }
377
389
  /**
378
390
  * Initialize the core library with workspace and config root paths.
379
391
  *
380
392
  * @param options - Workspace and config root paths.
381
393
  */
382
394
  function init(options) {
395
+ rejectWindowsDrivePath('configRoot', options.configRoot);
396
+ rejectWindowsDrivePath('workspacePath', options.workspacePath);
383
397
  state = {
384
398
  workspacePath: options.workspacePath,
385
399
  configRoot: options.configRoot,
@@ -1695,9 +1709,8 @@ function createPluginCli(options) {
1695
1709
  const configPath = resolveConfigPath(openClawHome);
1696
1710
  // 1. Copy dist to extensions
1697
1711
  const extensionsDir = join(openClawHome, 'extensions', pluginId);
1698
- if (!existsSync(distDir)) {
1699
- throw new Error(`Plugin dist directory not found: ${distDir}. Ensure the plugin is built before installing.`);
1700
- }
1712
+ if (!existsSync(distDir))
1713
+ throw new Error(`Plugin dist directory not found: ${distDir}`);
1701
1714
  console.log(`Copying dist to ${extensionsDir}...`);
1702
1715
  copyDistFiles(distDir, join(extensionsDir, 'dist'));
1703
1716
  // Copy package.json and openclaw.plugin.json from package root
@@ -1708,6 +1721,20 @@ function createPluginCli(options) {
1708
1721
  }
1709
1722
  }
1710
1723
  console.log(' ✓ Dist files copied');
1724
+ // 1b. Install production dependencies
1725
+ console.log('Installing dependencies...');
1726
+ try {
1727
+ execSync('npm install --omit=dev', {
1728
+ cwd: extensionsDir,
1729
+ stdio: 'pipe',
1730
+ });
1731
+ }
1732
+ catch (err) {
1733
+ throw new Error(`npm install failed in ${extensionsDir}`, {
1734
+ cause: err,
1735
+ });
1736
+ }
1737
+ console.log(' ✓ Dependencies installed');
1711
1738
  // 2. Patch openclaw.json
1712
1739
  console.log('Patching OpenClaw config...');
1713
1740
  const config = readJsonFile(configPath);
@@ -1753,9 +1780,9 @@ function createPluginCli(options) {
1753
1780
  // 4. Write initial HEARTBEAT entry and seed jeeves skill
1754
1781
  try {
1755
1782
  const cfgRoot = opts.configRoot;
1756
- const agents = config.agents;
1757
- const defaults = agents?.defaults;
1758
- const ws = opts.workspace ?? defaults?.workspace;
1783
+ const ag = config.agents;
1784
+ const defs = ag?.defaults;
1785
+ const ws = opts.workspace ?? defs?.workspace;
1759
1786
  if (ws) {
1760
1787
  init({ workspacePath: ws, configRoot: cfgRoot });
1761
1788
  const heartbeatPath = join(ws, WORKSPACE_FILES.heartbeat);
@@ -251,12 +251,26 @@ const COMPONENT_CONFIG_PREFIX = 'jeeves-';
251
251
  * - `{configRoot}/jeeves-{name}/` for each component
252
252
  */
253
253
  let state;
254
+ const WINDOWS_DRIVE_RE = /^[a-zA-Z]:/;
255
+ /**
256
+ * Throw if a path looks like a Windows drive letter on a non-Windows platform.
257
+ *
258
+ * @param label - Human-readable name for the path (used in error messages).
259
+ * @param value - The raw path string to validate.
260
+ */
261
+ function rejectWindowsDrivePath(label, value) {
262
+ if (process.platform !== 'win32' && WINDOWS_DRIVE_RE.test(value)) {
263
+ throw new Error(`jeeves-core: ${label} "${value}" looks like a Windows drive-letter path and will not resolve correctly on this platform.`);
264
+ }
265
+ }
254
266
  /**
255
267
  * Initialize the core library with workspace and config root paths.
256
268
  *
257
269
  * @param options - Workspace and config root paths.
258
270
  */
259
271
  function init(options) {
272
+ rejectWindowsDrivePath('configRoot', options.configRoot);
273
+ rejectWindowsDrivePath('workspacePath', options.workspacePath);
260
274
  state = {
261
275
  workspacePath: options.workspacePath,
262
276
  configRoot: options.configRoot,
package/dist/index.d.ts CHANGED
@@ -176,9 +176,11 @@ type ConfigApplyHandler = (request: ConfigApplyRequest) => Promise<ConfigApplyRe
176
176
  * 5. Calls `descriptor.onConfigApply` with the merged config (if defined)
177
177
  *
178
178
  * @param descriptor - The component descriptor.
179
+ * @param configPath - Optional explicit config file path. When provided, takes
180
+ * precedence over registered and derived paths.
179
181
  * @returns An async handler returning `{ status, body }`.
180
182
  */
181
- declare function createConfigApplyHandler(descriptor: JeevesComponentDescriptor): ConfigApplyHandler;
183
+ declare function createConfigApplyHandler(descriptor: JeevesComponentDescriptor, configPath?: string): ConfigApplyHandler;
182
184
 
183
185
  /**
184
186
  * Generic config query handler with JSONPath support.
@@ -275,6 +277,20 @@ declare function createStatusHandler(options: CreateStatusHandlerOptions): Statu
275
277
  */
276
278
  declare function checkNodeVersion(): void;
277
279
 
280
+ /**
281
+ * @packageDocumentation
282
+ *
283
+ * Deep-walks config objects and replaces `${VAR_NAME}` patterns with environment variable values.
284
+ * Canonical implementation in `@karmaniverous/jeeves` (jeeves-core).
285
+ */
286
+ /**
287
+ * Deep-walk a value and substitute `${VAR_NAME}` patterns in all string values.
288
+ *
289
+ * @param value - The value to walk (object, array, or primitive).
290
+ * @returns A new value with all env var references resolved.
291
+ */
292
+ declare function substituteEnvVars<T>(value: T): T;
293
+
278
294
  /**
279
295
  * Workspace-level shared configuration: `jeeves.config.json`.
280
296
  *
@@ -1068,9 +1084,16 @@ declare function checkRegistryVersion(packageName: string, cacheDir: string, ttl
1068
1084
  interface InitOptions {
1069
1085
  /** Absolute path to the OpenClaw workspace root. */
1070
1086
  workspacePath: string;
1071
- /** Absolute path to the platform config root (e.g., `j:/config`). */
1087
+ /** Absolute path to the platform config root. */
1072
1088
  configRoot: string;
1073
1089
  }
1090
+ /**
1091
+ * Throw if a path looks like a Windows drive letter on a non-Windows platform.
1092
+ *
1093
+ * @param label - Human-readable name for the path (used in error messages).
1094
+ * @param value - The raw path string to validate.
1095
+ */
1096
+ declare function rejectWindowsDrivePath(label: string, value: string): void;
1074
1097
  /**
1075
1098
  * Initialize the core library with workspace and config root paths.
1076
1099
  *
@@ -1947,5 +1970,5 @@ declare function getErrorMessage(err: unknown): string;
1947
1970
  */
1948
1971
  declare function isTransientError(err: unknown): boolean;
1949
1972
 
1950
- export { AGENTS_MARKERS, CLEANUP_FLAG, COMPONENT_CONFIG_PREFIX, COMPONENT_VERSIONS_FILE, CONFIG_FILE, CORE_CONFIG_DIR, CORE_VERSION, ComponentWriter, DEFAULT_BIND_ADDRESS, DEFAULT_CORE_VERSION, DEFAULT_PORTS, HEARTBEAT_HEADING, JEEVES_SKILL_DIR, MEMORY_HEARTBEAT_NAME, META_PORT, PLATFORM_COMPONENTS, REGISTRY_CACHE_FILE, RUNNER_PORT, SECTION_IDS, SECTION_ORDER, SERVER_PORT, SKILLS_DIR, SOUL_MARKERS, STALENESS_THRESHOLD_MS, STALE_LOCK_MS, TEMPLATES_DIR, TOOLS_MARKERS, VERSION_STAMP_PATTERN, WATCHER_PORT, WORKSPACE_CONFIG_DEFAULTS, WORKSPACE_CONFIG_FILE, WORKSPACE_FILES, analyzeMemory, appendJsonl, atomicWrite, buildEffectiveConfig, buildHeartbeatSection, checkMemoryHealth, checkNodeVersion, checkRegistryVersion, connectionFail, coreConfigSchema, createAsyncContentCache, createComponentWriter, createConfigApplyHandler, createConfigQueryHandler, createGoogleAuth, createPluginCli, createPluginToolset, createServiceCli, createServiceManager, createStatusHandler, ensureDir, fail, fetchJson, fetchWithTimeout, formatBeginMarker, formatEndMarker, generateJsonSchema, generateWorkspaceJsonSchema, getArg, getBindAddress, getChannelWorkspace, getComponentConfigDir, getComponentConfigPath, getConfigRoot, getCoreConfigDir, getCoreConfigFile, getEffectiveServiceName, getErrorMessage, getPackageRoot, getPackageVersion, getServiceState, getServiceUrl, getWorkspacePath, init, isPrime, isTransientError, jaccard, jeevesComponentDescriptorSchema, loadEnvFile, loadWorkspaceConfig, needsCleanup, nowIso, ok, orchestrateHeartbeat, parseArgs, parseHeartbeat, parseManaged, patchConfig, postJson, readComponentVersions, readJson, readJsonl, refreshPlatformContent, registerComponentConfigPath, removeComponentVersion, removeManagedSection, resetInit, resolveConfigPath, resolveConfigValue, resolveOpenClawHome, resolveOptionalPluginSetting, resolvePluginSetting, resolveWorkspacePath, run, runScript, runWithRetry, saveCache, seedContent, seedSkill, seedSkills, shingles, shouldWrite, sleepAsync, sleepMs, updateManagedSection, uuid, withFileLock, workspaceConfigSchema, writeComponentVersion, writeHeartbeatSection, writeJsonAtomic, writeJsonl };
1973
+ export { AGENTS_MARKERS, CLEANUP_FLAG, COMPONENT_CONFIG_PREFIX, COMPONENT_VERSIONS_FILE, CONFIG_FILE, CORE_CONFIG_DIR, CORE_VERSION, ComponentWriter, DEFAULT_BIND_ADDRESS, DEFAULT_CORE_VERSION, DEFAULT_PORTS, HEARTBEAT_HEADING, JEEVES_SKILL_DIR, MEMORY_HEARTBEAT_NAME, META_PORT, PLATFORM_COMPONENTS, REGISTRY_CACHE_FILE, RUNNER_PORT, SECTION_IDS, SECTION_ORDER, SERVER_PORT, SKILLS_DIR, SOUL_MARKERS, STALENESS_THRESHOLD_MS, STALE_LOCK_MS, TEMPLATES_DIR, TOOLS_MARKERS, VERSION_STAMP_PATTERN, WATCHER_PORT, WORKSPACE_CONFIG_DEFAULTS, WORKSPACE_CONFIG_FILE, WORKSPACE_FILES, analyzeMemory, appendJsonl, atomicWrite, buildEffectiveConfig, buildHeartbeatSection, checkMemoryHealth, checkNodeVersion, checkRegistryVersion, connectionFail, coreConfigSchema, createAsyncContentCache, createComponentWriter, createConfigApplyHandler, createConfigQueryHandler, createGoogleAuth, createPluginCli, createPluginToolset, createServiceCli, createServiceManager, createStatusHandler, ensureDir, fail, fetchJson, fetchWithTimeout, formatBeginMarker, formatEndMarker, generateJsonSchema, generateWorkspaceJsonSchema, getArg, getBindAddress, getChannelWorkspace, getComponentConfigDir, getComponentConfigPath, getConfigRoot, getCoreConfigDir, getCoreConfigFile, getEffectiveServiceName, getErrorMessage, getPackageRoot, getPackageVersion, getServiceState, getServiceUrl, getWorkspacePath, init, isPrime, isTransientError, jaccard, jeevesComponentDescriptorSchema, loadEnvFile, loadWorkspaceConfig, needsCleanup, nowIso, ok, orchestrateHeartbeat, parseArgs, parseHeartbeat, parseManaged, patchConfig, postJson, readComponentVersions, readJson, readJsonl, refreshPlatformContent, registerComponentConfigPath, rejectWindowsDrivePath, removeComponentVersion, removeManagedSection, resetInit, resolveConfigPath, resolveConfigValue, resolveOpenClawHome, resolveOptionalPluginSetting, resolvePluginSetting, resolveWorkspacePath, run, runScript, runWithRetry, saveCache, seedContent, seedSkill, seedSkills, shingles, shouldWrite, sleepAsync, sleepMs, substituteEnvVars, updateManagedSection, uuid, withFileLock, workspaceConfigSchema, writeComponentVersion, writeHeartbeatSection, writeJsonAtomic, writeJsonl };
1951
1974
  export type { AccountConfig, AsyncContentCacheOptions, ComponentDependencies, ComponentState, ComponentVersionEntry, ComponentVersionsState, ComponentWriterOptions, ConfigApplyHandler, ConfigApplyRequest, ConfigApplyResult, ConfigProvenance, ConfigQueryHandler, ConfigQueryResponse, CoreConfig, CreatePluginCliOptions, CreateStatusHandlerOptions, GoogleAuthOptions, HeartbeatEntry, InitOptions, JeevesComponentDescriptor, ManagedMarkers, ManagedSection, MemoryHygieneOptions, MemoryHygieneResult, OrchestrateHeartbeatOptions, ParseManagedResult, ParsedHeartbeat, PlatformComponent, PluginApi, PluginInstallRecord, RefreshPlatformContentOptions, RemoveManagedSectionOptions, ResolvedCliConfig, ResolvedValue, RetryOptions, RunOptions, SectionId, SeedContentOptions, ServiceAccountFileConfig, ServiceManager, ServiceManagerOptions, ServiceState, SlackWorkspaceOptions, StatusHandler, StatusHandlerResult, StatusResponse, ToolDescriptor, ToolRegistrationOptions, ToolResult, UpdateManagedSectionOptions, VersionStamp, WorkspaceConfig, WorkspaceOptions, WriteComponentVersionOptions };
package/dist/index.js CHANGED
@@ -5,8 +5,8 @@ import { lock } from 'proper-lockfile';
5
5
  import { JSONPath } from 'jsonpath-plus';
6
6
  import { major, valid, gte, gt } from 'semver';
7
7
  import { z } from 'zod';
8
- import * as commander from 'commander';
9
8
  import cp, { execSync } from 'node:child_process';
9
+ import * as commander from 'commander';
10
10
  import { homedir } from 'node:os';
11
11
  import { fileURLToPath } from 'node:url';
12
12
  import { packageDirectorySync } from 'package-directory';
@@ -184,14 +184,14 @@ const PLATFORM_COMPONENTS = [
184
184
  * Core library version, inlined at build time.
185
185
  *
186
186
  * @remarks
187
- * The `0.5.9` placeholder is replaced by
187
+ * The `0.5.11` placeholder is replaced by
188
188
  * `@rollup/plugin-replace` during the build with the actual version
189
189
  * from `package.json`. This ensures the correct version survives
190
190
  * when consumers bundle core into their own dist (where runtime
191
191
  * `import.meta.url`-based resolution would find the wrong package.json).
192
192
  */
193
193
  /** The core library version from package.json (inlined at build time). */
194
- const CORE_VERSION = '0.5.9';
194
+ const CORE_VERSION = '0.5.11';
195
195
 
196
196
  /**
197
197
  * Workspace and config root initialization.
@@ -204,12 +204,26 @@ const CORE_VERSION = '0.5.9';
204
204
  * - `{configRoot}/jeeves-{name}/` for each component
205
205
  */
206
206
  let state;
207
+ const WINDOWS_DRIVE_RE = /^[a-zA-Z]:/;
208
+ /**
209
+ * Throw if a path looks like a Windows drive letter on a non-Windows platform.
210
+ *
211
+ * @param label - Human-readable name for the path (used in error messages).
212
+ * @param value - The raw path string to validate.
213
+ */
214
+ function rejectWindowsDrivePath(label, value) {
215
+ if (process.platform !== 'win32' && WINDOWS_DRIVE_RE.test(value)) {
216
+ throw new Error(`jeeves-core: ${label} "${value}" looks like a Windows drive-letter path and will not resolve correctly on this platform.`);
217
+ }
218
+ }
207
219
  /**
208
220
  * Initialize the core library with workspace and config root paths.
209
221
  *
210
222
  * @param options - Workspace and config root paths.
211
223
  */
212
224
  function init(options) {
225
+ rejectWindowsDrivePath('configRoot', options.configRoot);
226
+ rejectWindowsDrivePath('workspacePath', options.workspacePath);
213
227
  state = {
214
228
  workspacePath: options.workspacePath,
215
229
  configRoot: options.configRoot,
@@ -537,16 +551,19 @@ function readConfigFile(filePath) {
537
551
  * 5. Calls `descriptor.onConfigApply` with the merged config (if defined)
538
552
  *
539
553
  * @param descriptor - The component descriptor.
554
+ * @param configPath - Optional explicit config file path. When provided, takes
555
+ * precedence over registered and derived paths.
540
556
  * @returns An async handler returning `{ status, body }`.
541
557
  */
542
- function createConfigApplyHandler(descriptor) {
558
+ function createConfigApplyHandler(descriptor, configPath) {
543
559
  return async (request) => {
544
560
  const { patch, replace } = request;
545
- // Use registered config path if available, otherwise derive from configRoot
546
- const configPath = getComponentConfigPath(descriptor.name) ??
561
+ // Prefer explicit > registered > derived config path
562
+ const resolvedConfigPath = configPath ??
563
+ getComponentConfigPath(descriptor.name) ??
547
564
  join(getComponentConfigDir(descriptor.name), descriptor.configFileName);
548
565
  // Read existing config
549
- const existing = readConfigFile(configPath);
566
+ const existing = readConfigFile(resolvedConfigPath);
550
567
  // Merge or replace
551
568
  const mergeFn = descriptor.customMerge ?? deepMerge;
552
569
  const merged = replace ? { ...patch } : mergeFn(existing, patch);
@@ -567,7 +584,7 @@ function createConfigApplyHandler(descriptor) {
567
584
  // Write atomically
568
585
  try {
569
586
  const json = JSON.stringify(validatedConfig, null, 2) + '\n';
570
- atomicWrite(configPath, json);
587
+ atomicWrite(resolvedConfigPath, json);
571
588
  }
572
589
  catch (err) {
573
590
  return {
@@ -704,6 +721,52 @@ function checkNodeVersion() {
704
721
  }
705
722
  }
706
723
 
724
+ /**
725
+ * @packageDocumentation
726
+ *
727
+ * Deep-walks config objects and replaces `${VAR_NAME}` patterns with environment variable values.
728
+ * Canonical implementation in `@karmaniverous/jeeves` (jeeves-core).
729
+ */
730
+ const ENV_PATTERN = /\$\{([^}]+)\}/g;
731
+ /**
732
+ * Replace `${VAR_NAME}` patterns in a string with `process.env.VAR_NAME`.
733
+ *
734
+ * @param value - The string to process.
735
+ * @returns The string with resolved env vars; unresolvable expressions left untouched.
736
+ */
737
+ function substituteString(value) {
738
+ return value.replace(ENV_PATTERN, (match, varName) => {
739
+ const envValue = process.env[varName];
740
+ if (envValue === undefined)
741
+ return match;
742
+ return envValue;
743
+ });
744
+ }
745
+ /**
746
+ * Deep-walk a value and substitute `${VAR_NAME}` patterns in all string values.
747
+ *
748
+ * @param value - The value to walk (object, array, or primitive).
749
+ * @returns A new value with all env var references resolved.
750
+ */
751
+ function substituteEnvVars(value) {
752
+ if (typeof value === 'string') {
753
+ return substituteString(value);
754
+ }
755
+ if (Array.isArray(value)) {
756
+ return value.map((item) => substituteEnvVars(item));
757
+ }
758
+ if (value !== null &&
759
+ typeof value === 'object' &&
760
+ Object.getPrototypeOf(value) === Object.prototype) {
761
+ const result = {};
762
+ for (const [key, val] of Object.entries(value)) {
763
+ result[key] = substituteEnvVars(val);
764
+ }
765
+ return result;
766
+ }
767
+ return value;
768
+ }
769
+
707
770
  /**
708
771
  * Workspace-level shared configuration: `jeeves.config.json`.
709
772
  *
@@ -913,6 +976,10 @@ function resolveCliConfig(opts) {
913
976
  */
914
977
  function initFromOptions(opts) {
915
978
  const resolved = resolveCliConfig(opts);
979
+ // Validate raw values BEFORE resolve() — on Linux, resolve('j:/config')
980
+ // produces '/cwd/j:/config' which masks the drive-letter pattern.
981
+ rejectWindowsDrivePath('configRoot', resolved.core.configRoot.value);
982
+ rejectWindowsDrivePath('workspacePath', resolved.core.workspace.value);
916
983
  init({
917
984
  workspacePath: resolve(resolved.core.workspace.value),
918
985
  configRoot: resolve(resolved.core.configRoot.value),
@@ -3219,9 +3286,8 @@ function createPluginCli(options) {
3219
3286
  const configPath = resolveConfigPath(openClawHome);
3220
3287
  // 1. Copy dist to extensions
3221
3288
  const extensionsDir = join(openClawHome, 'extensions', pluginId);
3222
- if (!existsSync(distDir)) {
3223
- throw new Error(`Plugin dist directory not found: ${distDir}. Ensure the plugin is built before installing.`);
3224
- }
3289
+ if (!existsSync(distDir))
3290
+ throw new Error(`Plugin dist directory not found: ${distDir}`);
3225
3291
  console.log(`Copying dist to ${extensionsDir}...`);
3226
3292
  copyDistFiles(distDir, join(extensionsDir, 'dist'));
3227
3293
  // Copy package.json and openclaw.plugin.json from package root
@@ -3232,6 +3298,20 @@ function createPluginCli(options) {
3232
3298
  }
3233
3299
  }
3234
3300
  console.log(' ✓ Dist files copied');
3301
+ // 1b. Install production dependencies
3302
+ console.log('Installing dependencies...');
3303
+ try {
3304
+ execSync('npm install --omit=dev', {
3305
+ cwd: extensionsDir,
3306
+ stdio: 'pipe',
3307
+ });
3308
+ }
3309
+ catch (err) {
3310
+ throw new Error(`npm install failed in ${extensionsDir}`, {
3311
+ cause: err,
3312
+ });
3313
+ }
3314
+ console.log(' ✓ Dependencies installed');
3235
3315
  // 2. Patch openclaw.json
3236
3316
  console.log('Patching OpenClaw config...');
3237
3317
  const config = readJsonFile(configPath);
@@ -3277,9 +3357,9 @@ function createPluginCli(options) {
3277
3357
  // 4. Write initial HEARTBEAT entry and seed jeeves skill
3278
3358
  try {
3279
3359
  const cfgRoot = opts.configRoot;
3280
- const agents = config.agents;
3281
- const defaults = agents?.defaults;
3282
- const ws = opts.workspace ?? defaults?.workspace;
3360
+ const ag = config.agents;
3361
+ const defs = ag?.defaults;
3362
+ const ws = opts.workspace ?? defs?.workspace;
3283
3363
  if (ws) {
3284
3364
  init({ workspacePath: ws, configRoot: cfgRoot });
3285
3365
  const heartbeatPath = join(ws, WORKSPACE_FILES.heartbeat);
@@ -4110,6 +4190,20 @@ When editing files outside the workspace, use the bridge pattern: copy in → ed
4110
4190
 
4111
4191
  **Cross-channel sends:** Use the \`message\` tool with an explicit \`target\` to send to a different channel or DM.
4112
4192
 
4193
+ ### Slack File Downloads
4194
+
4195
+ To download a Slack-hosted file, first try the \`message\` tool's \`download-file\` action. If that fails, fall back to a direct HTTP fetch using the bot token:
4196
+
4197
+ \`\`\`js
4198
+ fetch(url_private_download, {
4199
+ headers: { Authorization: 'Bearer ' + botToken },
4200
+ });
4201
+ \`\`\`
4202
+
4203
+ The bot token is at \`channels.slack.accounts.default.botToken\` in \`openclaw.json\`.
4204
+
4205
+ Never tell the user a file can't be downloaded until both methods have been tried.
4206
+
4113
4207
  ### Plugin Lifecycle
4114
4208
 
4115
4209
  \`\`\`bash
@@ -5806,4 +5900,4 @@ async function getChannelWorkspace(channelId, token, options) {
5806
5900
  return teamId;
5807
5901
  }
5808
5902
 
5809
- export { AGENTS_MARKERS, CLEANUP_FLAG, COMPONENT_CONFIG_PREFIX, COMPONENT_VERSIONS_FILE, CONFIG_FILE, CORE_CONFIG_DIR, CORE_VERSION, ComponentWriter, DEFAULT_BIND_ADDRESS, DEFAULT_CORE_VERSION, DEFAULT_PORTS, HEARTBEAT_HEADING, JEEVES_SKILL_DIR, MEMORY_HEARTBEAT_NAME, META_PORT, PLATFORM_COMPONENTS, REGISTRY_CACHE_FILE, RUNNER_PORT, SECTION_IDS, SECTION_ORDER, SERVER_PORT, SKILLS_DIR, SOUL_MARKERS, STALENESS_THRESHOLD_MS, STALE_LOCK_MS, TEMPLATES_DIR, TOOLS_MARKERS, VERSION_STAMP_PATTERN, WATCHER_PORT, WORKSPACE_CONFIG_DEFAULTS, WORKSPACE_CONFIG_FILE, WORKSPACE_FILES, analyzeMemory, appendJsonl, atomicWrite, buildEffectiveConfig, buildHeartbeatSection, checkMemoryHealth, checkNodeVersion, checkRegistryVersion, connectionFail, coreConfigSchema, createAsyncContentCache, createComponentWriter, createConfigApplyHandler, createConfigQueryHandler, createGoogleAuth, createPluginCli, createPluginToolset, createServiceCli, createServiceManager, createStatusHandler, ensureDir, fail, fetchJson, fetchWithTimeout, formatBeginMarker, formatEndMarker, generateJsonSchema, generateWorkspaceJsonSchema, getArg, getBindAddress, getChannelWorkspace, getComponentConfigDir, getComponentConfigPath, getConfigRoot, getCoreConfigDir, getCoreConfigFile, getEffectiveServiceName, getErrorMessage, getPackageRoot, getPackageVersion, getServiceState, getServiceUrl, getWorkspacePath, init, isPrime, isTransientError, jaccard, jeevesComponentDescriptorSchema, loadEnvFile, loadWorkspaceConfig, needsCleanup, nowIso, ok, orchestrateHeartbeat, parseArgs, parseHeartbeat, parseManaged, patchConfig, postJson, readComponentVersions, readJson, readJsonl, refreshPlatformContent, registerComponentConfigPath, removeComponentVersion, removeManagedSection, resetInit, resolveConfigPath, resolveConfigValue, resolveOpenClawHome, resolveOptionalPluginSetting, resolvePluginSetting, resolveWorkspacePath, run, runScript, runWithRetry, saveCache, seedContent, seedSkill, seedSkills, shingles, shouldWrite, sleepAsync, sleepMs, updateManagedSection, uuid, withFileLock, workspaceConfigSchema, writeComponentVersion, writeHeartbeatSection, writeJsonAtomic, writeJsonl };
5903
+ export { AGENTS_MARKERS, CLEANUP_FLAG, COMPONENT_CONFIG_PREFIX, COMPONENT_VERSIONS_FILE, CONFIG_FILE, CORE_CONFIG_DIR, CORE_VERSION, ComponentWriter, DEFAULT_BIND_ADDRESS, DEFAULT_CORE_VERSION, DEFAULT_PORTS, HEARTBEAT_HEADING, JEEVES_SKILL_DIR, MEMORY_HEARTBEAT_NAME, META_PORT, PLATFORM_COMPONENTS, REGISTRY_CACHE_FILE, RUNNER_PORT, SECTION_IDS, SECTION_ORDER, SERVER_PORT, SKILLS_DIR, SOUL_MARKERS, STALENESS_THRESHOLD_MS, STALE_LOCK_MS, TEMPLATES_DIR, TOOLS_MARKERS, VERSION_STAMP_PATTERN, WATCHER_PORT, WORKSPACE_CONFIG_DEFAULTS, WORKSPACE_CONFIG_FILE, WORKSPACE_FILES, analyzeMemory, appendJsonl, atomicWrite, buildEffectiveConfig, buildHeartbeatSection, checkMemoryHealth, checkNodeVersion, checkRegistryVersion, connectionFail, coreConfigSchema, createAsyncContentCache, createComponentWriter, createConfigApplyHandler, createConfigQueryHandler, createGoogleAuth, createPluginCli, createPluginToolset, createServiceCli, createServiceManager, createStatusHandler, ensureDir, fail, fetchJson, fetchWithTimeout, formatBeginMarker, formatEndMarker, generateJsonSchema, generateWorkspaceJsonSchema, getArg, getBindAddress, getChannelWorkspace, getComponentConfigDir, getComponentConfigPath, getConfigRoot, getCoreConfigDir, getCoreConfigFile, getEffectiveServiceName, getErrorMessage, getPackageRoot, getPackageVersion, getServiceState, getServiceUrl, getWorkspacePath, init, isPrime, isTransientError, jaccard, jeevesComponentDescriptorSchema, loadEnvFile, loadWorkspaceConfig, needsCleanup, nowIso, ok, orchestrateHeartbeat, parseArgs, parseHeartbeat, parseManaged, patchConfig, postJson, readComponentVersions, readJson, readJsonl, refreshPlatformContent, registerComponentConfigPath, rejectWindowsDrivePath, removeComponentVersion, removeManagedSection, resetInit, resolveConfigPath, resolveConfigValue, resolveOpenClawHome, resolveOptionalPluginSetting, resolvePluginSetting, resolveWorkspacePath, run, runScript, runWithRetry, saveCache, seedContent, seedSkill, seedSkills, shingles, shouldWrite, sleepAsync, sleepMs, substituteEnvVars, updateManagedSection, uuid, withFileLock, workspaceConfigSchema, writeComponentVersion, writeHeartbeatSection, writeJsonAtomic, writeJsonl };
package/package.json CHANGED
@@ -3,12 +3,6 @@
3
3
  "bin": {
4
4
  "jeeves": "./dist/cli/jeeves/index.js"
5
5
  },
6
- "auto-changelog": {
7
- "output": "CHANGELOG.md",
8
- "unreleased": true,
9
- "commitLimit": false,
10
- "hideCredit": true
11
- },
12
6
  "bugs": {
13
7
  "url": "https://github.com/karmaniverous/jeeves/issues"
14
8
  },
@@ -18,13 +12,13 @@
18
12
  "jsonpath-plus": "^10.4.0",
19
13
  "package-directory": "^8.2.0",
20
14
  "proper-lockfile": "^4.1.2",
21
- "semver": "^7.8.0",
15
+ "semver": "^7.8.1",
22
16
  "zod": "^4.4.3"
23
17
  },
24
18
  "description": "Shared library and CLI for the Jeeves AI assistant platform.",
25
19
  "devDependencies": {
26
20
  "@commander-js/extra-typings": "^14.0.0",
27
- "@dotenvx/dotenvx": "^1.65.0",
21
+ "@dotenvx/dotenvx": "^1.69.1",
28
22
  "@eslint/js": "^10.0.1",
29
23
  "@rollup/plugin-alias": "^6.0.0",
30
24
  "@rollup/plugin-commonjs": "^29.0.2",
@@ -33,33 +27,33 @@
33
27
  "@rollup/plugin-replace": "^6.0.3",
34
28
  "@rollup/plugin-typescript": "^12.3.0",
35
29
  "@types/fs-extra": "^11.0.4",
36
- "@types/node": "^25.7.0",
30
+ "@types/node": "^25.9.1",
37
31
  "@types/proper-lockfile": "^4.1.4",
38
32
  "@types/semver": "^7.7.1",
39
- "@vitest/coverage-v8": "^4.1.6",
40
- "@vitest/eslint-plugin": "^1.6.17",
41
- "auto-changelog": "^2.5.1",
33
+ "@vitest/coverage-v8": "^4.1.7",
34
+ "@vitest/eslint-plugin": "^1.6.18",
42
35
  "cross-env": "^10.1.0",
43
- "eslint": "^10.3.0",
36
+ "eslint": "^10.4.0",
44
37
  "eslint-config-prettier": "^10.1.8",
45
- "eslint-plugin-prettier": "^5.5.5",
38
+ "eslint-plugin-prettier": "^5.5.6",
46
39
  "eslint-plugin-simple-import-sort": "^13.0.0",
47
40
  "eslint-plugin-tsdoc": "^0.5.2",
48
41
  "fs-extra": "^11.3.5",
49
- "knip": "^6.13.0",
50
- "lefthook": "^2.1.6",
42
+ "git-cliff": "^2.13.1",
43
+ "knip": "^6.14.2",
44
+ "lefthook": "^2.1.9",
51
45
  "prettier": "^3.8.3",
52
46
  "release-it": "^20.0.1",
53
47
  "rimraf": "^6.1.3",
54
- "rollup": "^4.60.3",
48
+ "rollup": "^4.60.4",
55
49
  "rollup-plugin-dts": "^6.4.1",
56
50
  "tslib": "^2.8.1",
57
51
  "typedoc": "^0.28.19",
58
52
  "typedoc-plugin-mdn-links": "^5.1.1",
59
53
  "typedoc-plugin-replace-text": "^4.2.0",
60
54
  "typescript": "^6.0.3",
61
- "typescript-eslint": "^8.59.3",
62
- "vitest": "^4.1.6"
55
+ "typescript-eslint": "^8.60.0",
56
+ "vitest": "^4.1.7"
63
57
  },
64
58
  "engines": {
65
59
  "node": ">=22"
@@ -92,7 +86,7 @@
92
86
  },
93
87
  "release-it": {
94
88
  "git": {
95
- "changelog": "npx auto-changelog --unreleased-only --stdout --template https://raw.githubusercontent.com/release-it/release-it/main/templates/changelog-compact.hbs",
89
+ "changelog": "npx git-cliff --unreleased --strip header",
96
90
  "commitMessage": "chore: release v${version}",
97
91
  "requireBranch": "main"
98
92
  },
@@ -112,7 +106,7 @@
112
106
  "git switch ${branchName}"
113
107
  ],
114
108
  "after:bump": [
115
- "npx auto-changelog -p",
109
+ "npx git-cliff -o CHANGELOG.md",
116
110
  "npm run docs",
117
111
  "git add CHANGELOG.md"
118
112
  ]
@@ -127,7 +121,7 @@
127
121
  },
128
122
  "scripts": {
129
123
  "build": "rimraf dist && cross-env NO_COLOR=1 rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript",
130
- "changelog": "auto-changelog",
124
+ "changelog": "git-cliff -o CHANGELOG.md",
131
125
  "diagrams": "cd diagrams/src && plantuml -tpng -o ../out -r .",
132
126
  "docs": "typedoc",
133
127
  "knip": "knip",
@@ -140,5 +134,5 @@
140
134
  },
141
135
  "type": "module",
142
136
  "types": "dist/index.d.ts",
143
- "version": "0.5.10"
137
+ "version": "0.5.12"
144
138
  }