@babyclaw/gateway 0.0.1 → 0.2.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/dist/index.d.ts CHANGED
@@ -134,6 +134,9 @@ declare class AdminClient {
134
134
  shutdown(): Promise<{
135
135
  ok: boolean;
136
136
  }>;
137
+ reloadSkills(): Promise<{
138
+ ok: boolean;
139
+ }>;
137
140
  private get;
138
141
  }
139
142
 
@@ -148,6 +151,16 @@ declare function writeConfig({ config }: {
148
151
  declare const CONFIG_PATH_ENV_VAR = "BABYCLAW_CONFIG_PATH";
149
152
  declare function getDefaultConfigPath(): string;
150
153
  declare function getConfigPath(): string;
154
+ declare const DEFAULT_WORKSPACE_ROOT = "~/babyclaw";
155
+ /**
156
+ * Resolves the workspace path from the config's workspace.root value.
157
+ * Supports ~ expansion for home-relative paths.
158
+ * Plain relative paths are resolved against the config file's directory
159
+ * so the result is deterministic regardless of where the process starts.
160
+ */
161
+ declare function resolveWorkspaceRoot({ configRoot }: {
162
+ configRoot: string;
163
+ }): string;
151
164
 
152
165
  declare function getDefaultConfigTemplate(): string;
153
166
 
@@ -204,6 +217,83 @@ declare function resolveLanguageModel({ config }: {
204
217
  config: BabyclawConfig;
205
218
  }): LanguageModel;
206
219
 
220
+ type SkillInstallSpec = {
221
+ id?: string;
222
+ kind: "brew" | "node" | "go" | "uv" | "download";
223
+ label?: string;
224
+ bins?: string[];
225
+ os?: string[];
226
+ formula?: string;
227
+ package?: string;
228
+ module?: string;
229
+ url?: string;
230
+ archive?: string;
231
+ extract?: boolean;
232
+ stripComponents?: number;
233
+ targetDir?: string;
234
+ };
235
+ type OpenClawSkillMetadata = {
236
+ always?: boolean;
237
+ skillKey?: string;
238
+ primaryEnv?: string;
239
+ emoji?: string;
240
+ homepage?: string;
241
+ os?: string[];
242
+ requires?: {
243
+ bins?: string[];
244
+ anyBins?: string[];
245
+ env?: string[];
246
+ config?: string[];
247
+ };
248
+ install?: SkillInstallSpec[];
249
+ };
250
+ type SkillFrontmatter = {
251
+ name: string;
252
+ description: string;
253
+ homepage?: string;
254
+ userInvocable: boolean;
255
+ disableModelInvocation: boolean;
256
+ commandDispatch?: string;
257
+ commandTool?: string;
258
+ commandArgMode?: string;
259
+ openclaw?: OpenClawSkillMetadata;
260
+ };
261
+ type SkillEntry = {
262
+ frontmatter: SkillFrontmatter;
263
+ slug: string;
264
+ relativePath: string;
265
+ };
266
+ declare function getSkillKey({ frontmatter, slug, }: {
267
+ frontmatter: SkillFrontmatter | null;
268
+ slug: string;
269
+ }): string;
270
+ type SkillsConfig = {
271
+ entries: Record<string, {
272
+ enabled: boolean;
273
+ apiKey?: string;
274
+ env?: Record<string, string>;
275
+ }>;
276
+ };
277
+
278
+ type BundledSkillStatus = {
279
+ slug: string;
280
+ frontmatter: SkillFrontmatter | null;
281
+ enabled: boolean;
282
+ eligible: boolean;
283
+ ineligibilityReason: string | null;
284
+ };
285
+ declare function listBundledSkills({ skillsConfig, fullConfig, }: {
286
+ skillsConfig: SkillsConfig;
287
+ fullConfig: Record<string, unknown>;
288
+ }): BundledSkillStatus[];
289
+ declare function getEnabledBundledSkills({ skillsConfig, fullConfig, }: {
290
+ skillsConfig: SkillsConfig;
291
+ fullConfig: Record<string, unknown>;
292
+ }): SkillEntry[];
293
+ declare function getBundledSkillPath({ slug }: {
294
+ slug: string;
295
+ }): string | null;
296
+
207
297
  declare const LOG_LEVELS: readonly ["debug", "info", "warn", "error"];
208
298
  type LogLevel = (typeof LOG_LEVELS)[number];
209
299
  declare const LOG_FORMATS: readonly ["json", "pretty"];
@@ -225,4 +315,4 @@ declare function createChildLogger({ context }: {
225
315
  context: Record<string, unknown>;
226
316
  }): pino.Logger;
227
317
 
228
- export { AdminClient, type BabyclawConfig, CONFIG_PATH_ENV_VAR, ClawHubError, GatewayRuntime, type GatewayStatus, type InstallSkillResult, type LogFormat, type LogLevel, type LoggingConfig, SUPPORTED_PROVIDERS, SkillAlreadyInstalledError, type SkillSetupResult, babyclawConfigSchema, createChildLogger, createLogger, getAdminSocketPath, getConfigPath, getDefaultConfigPath, getDefaultConfigTemplate, getLogger, installSkillFromClawHub, loadConfig, loadConfigRaw, resolveLanguageModel, runSkillSetup, writeConfig };
318
+ export { AdminClient, type BabyclawConfig, type BundledSkillStatus, CONFIG_PATH_ENV_VAR, ClawHubError, DEFAULT_WORKSPACE_ROOT, GatewayRuntime, type GatewayStatus, type InstallSkillResult, type LogFormat, type LogLevel, type LoggingConfig, SUPPORTED_PROVIDERS, SkillAlreadyInstalledError, type SkillSetupResult, babyclawConfigSchema, createChildLogger, createLogger, getAdminSocketPath, getBundledSkillPath, getConfigPath, getDefaultConfigPath, getDefaultConfigTemplate, getEnabledBundledSkills, getLogger, getSkillKey, installSkillFromClawHub, listBundledSkills, loadConfig, loadConfigRaw, resolveLanguageModel, resolveWorkspaceRoot, runSkillSetup, writeConfig };
package/dist/index.js CHANGED
@@ -5,7 +5,8 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/runtime.ts
8
- import { resolve as resolve5 } from "path";
8
+ import { mkdirSync as mkdirSync3 } from "fs";
9
+ import { getBundledSkillsDir } from "@babyclaw/skills";
9
10
 
10
11
  // src/ai/agent.ts
11
12
  import {
@@ -2042,9 +2043,26 @@ import { z } from "zod";
2042
2043
  // src/utils/path.ts
2043
2044
  import { resolve, sep } from "path";
2044
2045
  import { stat } from "fs/promises";
2046
+ var BUNDLED_SKILLS_PREFIX = "bundled-skills/";
2045
2047
  function normalizeSeparators({ path }) {
2046
2048
  return path.replaceAll("\\", "/");
2047
2049
  }
2050
+ function resolveBundledSkillPath({
2051
+ bundledSkillsDir,
2052
+ requestedPath
2053
+ }) {
2054
+ const relative3 = requestedPath.slice(BUNDLED_SKILLS_PREFIX.length);
2055
+ const normalized = normalizeSeparators({ path: relative3 }).trim();
2056
+ if (normalized.length === 0) {
2057
+ throw new Error("Path is required");
2058
+ }
2059
+ const absoluteRoot = resolve(bundledSkillsDir);
2060
+ const absoluteTarget = resolve(absoluteRoot, normalized);
2061
+ if (!isSubPath({ parent: absoluteRoot, child: absoluteTarget })) {
2062
+ throw new Error("Path escapes bundled skills root");
2063
+ }
2064
+ return absoluteTarget;
2065
+ }
2048
2066
  function resolveWorkspacePath({
2049
2067
  workspaceRoot,
2050
2068
  requestedPath
@@ -3801,10 +3819,25 @@ function createWorkspaceTools({ context }) {
3801
3819
  defaultCode: "WORKSPACE_READ_FAILED",
3802
3820
  input: { path, format },
3803
3821
  action: async () => {
3804
- const absolutePath = resolveWorkspacePath({
3805
- workspaceRoot: context.workspaceRoot,
3806
- requestedPath: path
3807
- });
3822
+ const isBundled = path.startsWith(BUNDLED_SKILLS_PREFIX);
3823
+ let absolutePath;
3824
+ if (isBundled) {
3825
+ if (!context.bundledSkillsDir) {
3826
+ throw new ToolExecutionError({
3827
+ code: "BUNDLED_SKILLS_UNAVAILABLE",
3828
+ message: "Bundled skills directory is not configured"
3829
+ });
3830
+ }
3831
+ absolutePath = resolveBundledSkillPath({
3832
+ bundledSkillsDir: context.bundledSkillsDir,
3833
+ requestedPath: path
3834
+ });
3835
+ } else {
3836
+ absolutePath = resolveWorkspacePath({
3837
+ workspaceRoot: context.workspaceRoot,
3838
+ requestedPath: path
3839
+ });
3840
+ }
3808
3841
  const raw = await readFile6(absolutePath, "utf8");
3809
3842
  ensurePayloadWithinLimit({
3810
3843
  value: raw,
@@ -3862,6 +3895,12 @@ function createWorkspaceTools({ context }) {
3862
3895
  hasValue: value !== void 0
3863
3896
  },
3864
3897
  action: async () => {
3898
+ if (path.startsWith(BUNDLED_SKILLS_PREFIX)) {
3899
+ throw new ToolExecutionError({
3900
+ code: "BUNDLED_SKILLS_READONLY",
3901
+ message: "Bundled skills are read-only and cannot be written to"
3902
+ });
3903
+ }
3865
3904
  const absolutePath = resolveWorkspacePath({
3866
3905
  workspaceRoot: context.workspaceRoot,
3867
3906
  requestedPath: path
@@ -4001,6 +4040,12 @@ function createWorkspaceTools({ context }) {
4001
4040
  defaultCode: "WORKSPACE_DELETE_FAILED",
4002
4041
  input: { path, recursive },
4003
4042
  action: async () => {
4043
+ if (path.startsWith(BUNDLED_SKILLS_PREFIX)) {
4044
+ throw new ToolExecutionError({
4045
+ code: "BUNDLED_SKILLS_READONLY",
4046
+ message: "Bundled skills are read-only and cannot be deleted"
4047
+ });
4048
+ }
4004
4049
  const absolutePath = resolveWorkspacePath({
4005
4050
  workspaceRoot: context.workspaceRoot,
4006
4051
  requestedPath: path
@@ -4037,6 +4082,12 @@ function createWorkspaceTools({ context }) {
4037
4082
  defaultCode: "WORKSPACE_MOVE_FAILED",
4038
4083
  input: { from_path, to_path, overwrite },
4039
4084
  action: async () => {
4085
+ if (from_path.startsWith(BUNDLED_SKILLS_PREFIX) || to_path.startsWith(BUNDLED_SKILLS_PREFIX)) {
4086
+ throw new ToolExecutionError({
4087
+ code: "BUNDLED_SKILLS_READONLY",
4088
+ message: "Bundled skills are read-only and cannot be moved"
4089
+ });
4090
+ }
4040
4091
  const fromAbsolute = resolveWorkspacePath({
4041
4092
  workspaceRoot: context.workspaceRoot,
4042
4093
  requestedPath: from_path
@@ -4217,44 +4268,15 @@ function createUnifiedTools({
4217
4268
  };
4218
4269
  }
4219
4270
 
4220
- // src/onboarding/personality.ts
4221
- import { readFile as readFile7 } from "fs/promises";
4222
- import { join as join9 } from "path";
4223
- function getPersonalityFilePaths({ workspacePath }) {
4224
- return {
4225
- identityPath: join9(workspacePath, "IDENTITY.md"),
4226
- soulPath: join9(workspacePath, "SOUL.md"),
4227
- userPath: join9(workspacePath, "USER.md")
4228
- };
4229
- }
4230
- async function readPersonalityFiles({
4231
- workspacePath
4271
+ // src/bundled-skills/index.ts
4272
+ import { listBundledSlugs, readBundledSkillContent, getBundledSkillInfo } from "@babyclaw/skills";
4273
+
4274
+ // src/workspace/skills/types.ts
4275
+ function getSkillKey({
4276
+ frontmatter,
4277
+ slug
4232
4278
  }) {
4233
- const { identityPath, soulPath, userPath } = getPersonalityFilePaths({ workspacePath });
4234
- const [identity, soul, user] = await Promise.all([
4235
- readOptionalFile2({ path: identityPath }),
4236
- readOptionalFile2({ path: soulPath }),
4237
- readOptionalFile2({ path: userPath })
4238
- ]);
4239
- return {
4240
- identity,
4241
- soul,
4242
- user
4243
- };
4244
- }
4245
- function hasCompletePersonalityFiles(files) {
4246
- return typeof files.identity === "string" && files.identity.trim().length > 0 && typeof files.soul === "string" && files.soul.trim().length > 0 && typeof files.user === "string" && files.user.trim().length > 0;
4247
- }
4248
- async function readOptionalFile2({ path }) {
4249
- try {
4250
- return await readFile7(path, "utf8");
4251
- } catch (error) {
4252
- const maybeErrno = error;
4253
- if (maybeErrno.code === "ENOENT") {
4254
- return null;
4255
- }
4256
- throw error;
4257
- }
4279
+ return frontmatter?.openclaw?.skillKey ?? frontmatter?.name ?? slug;
4258
4280
  }
4259
4281
 
4260
4282
  // src/workspace/skills/eligibility.ts
@@ -4264,54 +4286,84 @@ function getEligibleSkills({
4264
4286
  skillsConfig,
4265
4287
  fullConfig
4266
4288
  }) {
4267
- return skills.filter((skill) => shouldIncludeSkill({ skill, skillsConfig, fullConfig }));
4289
+ return skills.filter((skill) => {
4290
+ const { frontmatter, slug } = skill;
4291
+ const entry = skillsConfig.entries[getSkillKey({ frontmatter, slug })];
4292
+ if (entry?.enabled === false) return false;
4293
+ return checkSkillEligibility({ frontmatter, skillsConfig, fullConfig }).eligible;
4294
+ });
4268
4295
  }
4269
- function shouldIncludeSkill({
4270
- skill,
4296
+ function checkSkillEligibility({
4297
+ frontmatter,
4271
4298
  skillsConfig,
4272
4299
  fullConfig
4273
4300
  }) {
4274
- const { frontmatter } = skill;
4301
+ if (frontmatter.disableModelInvocation) {
4302
+ return { eligible: false, reason: "Model invocation disabled" };
4303
+ }
4275
4304
  const openclaw = frontmatter.openclaw;
4276
- const skillKey = openclaw?.skillKey ?? frontmatter.name;
4277
- const entry = skillsConfig.entries[skillKey];
4278
- if (entry?.enabled === false) return false;
4279
- if (frontmatter.disableModelInvocation) return false;
4280
4305
  if (openclaw?.os && openclaw.os.length > 0) {
4281
- if (!openclaw.os.includes(process.platform)) return false;
4306
+ if (!openclaw.os.includes(process.platform)) {
4307
+ return {
4308
+ eligible: false,
4309
+ reason: `Requires OS: ${openclaw.os.join(", ")} (current: ${process.platform})`
4310
+ };
4311
+ }
4312
+ }
4313
+ if (openclaw?.always) {
4314
+ return { eligible: true, reason: null };
4282
4315
  }
4283
- if (openclaw?.always) return true;
4284
4316
  const requires = openclaw?.requires;
4285
- if (!requires) return true;
4317
+ if (!requires) {
4318
+ return { eligible: true, reason: null };
4319
+ }
4286
4320
  if (requires.bins && requires.bins.length > 0) {
4287
- if (!requires.bins.every((bin) => binaryExists({ name: bin }))) return false;
4321
+ const missing = requires.bins.filter((bin) => !binaryExists({ name: bin }));
4322
+ if (missing.length > 0) {
4323
+ return { eligible: false, reason: `Missing binaries: ${missing.join(", ")}` };
4324
+ }
4288
4325
  }
4289
4326
  if (requires.anyBins && requires.anyBins.length > 0) {
4290
- if (!requires.anyBins.some((bin) => binaryExists({ name: bin }))) return false;
4327
+ if (!requires.anyBins.some((bin) => binaryExists({ name: bin }))) {
4328
+ return {
4329
+ eligible: false,
4330
+ reason: `Requires at least one of: ${requires.anyBins.join(", ")}`
4331
+ };
4332
+ }
4291
4333
  }
4292
4334
  if (requires.env && requires.env.length > 0) {
4335
+ const skillKey = openclaw?.skillKey ?? frontmatter.name;
4336
+ const entry = skillsConfig.entries[skillKey];
4293
4337
  const primaryEnv = openclaw?.primaryEnv;
4294
4338
  const hasApiKeyInConfig = Boolean(entry?.apiKey);
4295
4339
  for (const envVar of requires.env) {
4296
4340
  if (process.env[envVar]) continue;
4297
4341
  if (primaryEnv === envVar && hasApiKeyInConfig) continue;
4298
- return false;
4342
+ return { eligible: false, reason: `Missing environment variable: ${envVar}` };
4299
4343
  }
4300
4344
  }
4301
4345
  if (requires.config && requires.config.length > 0) {
4302
4346
  for (const configPath of requires.config) {
4303
- if (!getConfigValue({ config: fullConfig, path: configPath })) return false;
4347
+ if (!getConfigValue({ config: fullConfig, path: configPath })) {
4348
+ return { eligible: false, reason: `Missing config value: ${configPath}` };
4349
+ }
4304
4350
  }
4305
4351
  }
4306
- return true;
4352
+ return { eligible: true, reason: null };
4307
4353
  }
4354
+ var binaryExistsCache = /* @__PURE__ */ new Map();
4308
4355
  function binaryExists({ name }) {
4356
+ const cached = binaryExistsCache.get(name);
4357
+ if (cached !== void 0) return cached;
4358
+ let exists;
4309
4359
  try {
4310
4360
  execFileSync("which", [name], { stdio: "ignore" });
4311
- return true;
4361
+ exists = true;
4312
4362
  } catch {
4313
- return false;
4363
+ exists = false;
4314
4364
  }
4365
+ binaryExistsCache.set(name, exists);
4366
+ return exists;
4315
4367
  }
4316
4368
  function getConfigValue({
4317
4369
  config,
@@ -4326,6 +4378,94 @@ function getConfigValue({
4326
4378
  return current;
4327
4379
  }
4328
4380
 
4381
+ // src/bundled-skills/index.ts
4382
+ function listBundledSkills({
4383
+ skillsConfig,
4384
+ fullConfig
4385
+ }) {
4386
+ const slugs = listBundledSlugs();
4387
+ return slugs.map((slug) => {
4388
+ const content = readBundledSkillContent({ slug });
4389
+ if (!content) {
4390
+ return {
4391
+ slug,
4392
+ frontmatter: null,
4393
+ enabled: false,
4394
+ eligible: false,
4395
+ ineligibilityReason: "Could not read SKILL.md"
4396
+ };
4397
+ }
4398
+ const raw = parseFrontmatter({ content });
4399
+ const frontmatter = raw ? buildFrontmatter({ raw }) : null;
4400
+ const skillKey = getSkillKey({ frontmatter, slug });
4401
+ const entry = skillsConfig.entries[skillKey];
4402
+ const enabled = entry?.enabled === true;
4403
+ const { eligible, reason } = frontmatter ? checkSkillEligibility({ frontmatter, skillsConfig, fullConfig }) : { eligible: false, reason: "Invalid frontmatter" };
4404
+ return {
4405
+ slug,
4406
+ frontmatter,
4407
+ enabled,
4408
+ eligible,
4409
+ ineligibilityReason: reason
4410
+ };
4411
+ });
4412
+ }
4413
+ function getEnabledBundledSkills({
4414
+ skillsConfig,
4415
+ fullConfig
4416
+ }) {
4417
+ const all = listBundledSkills({ skillsConfig, fullConfig });
4418
+ return all.filter((s) => s.enabled && s.eligible && s.frontmatter).map((s) => ({
4419
+ frontmatter: s.frontmatter,
4420
+ slug: s.slug,
4421
+ relativePath: `bundled-skills/${s.slug}/SKILL.md`
4422
+ }));
4423
+ }
4424
+ function getBundledSkillPath({ slug }) {
4425
+ const info = getBundledSkillInfo({ slug });
4426
+ return info?.skillDir ?? null;
4427
+ }
4428
+
4429
+ // src/onboarding/personality.ts
4430
+ import { readFile as readFile7 } from "fs/promises";
4431
+ import { join as join9 } from "path";
4432
+ function getPersonalityFilePaths({ workspacePath }) {
4433
+ return {
4434
+ identityPath: join9(workspacePath, "IDENTITY.md"),
4435
+ soulPath: join9(workspacePath, "SOUL.md"),
4436
+ userPath: join9(workspacePath, "USER.md")
4437
+ };
4438
+ }
4439
+ async function readPersonalityFiles({
4440
+ workspacePath
4441
+ }) {
4442
+ const { identityPath, soulPath, userPath } = getPersonalityFilePaths({ workspacePath });
4443
+ const [identity, soul, user] = await Promise.all([
4444
+ readOptionalFile2({ path: identityPath }),
4445
+ readOptionalFile2({ path: soulPath }),
4446
+ readOptionalFile2({ path: userPath })
4447
+ ]);
4448
+ return {
4449
+ identity,
4450
+ soul,
4451
+ user
4452
+ };
4453
+ }
4454
+ function hasCompletePersonalityFiles(files) {
4455
+ return typeof files.identity === "string" && files.identity.trim().length > 0 && typeof files.soul === "string" && files.soul.trim().length > 0 && typeof files.user === "string" && files.user.trim().length > 0;
4456
+ }
4457
+ async function readOptionalFile2({ path }) {
4458
+ try {
4459
+ return await readFile7(path, "utf8");
4460
+ } catch (error) {
4461
+ const maybeErrno = error;
4462
+ if (maybeErrno.code === "ENOENT") {
4463
+ return null;
4464
+ }
4465
+ throw error;
4466
+ }
4467
+ }
4468
+
4329
4469
  // src/agent/context.ts
4330
4470
  async function loadAgentContext({
4331
4471
  workspacePath,
@@ -4338,7 +4478,11 @@ async function loadAgentContext({
4338
4478
  readWorkspaceGuide({ workspacePath }),
4339
4479
  scanWorkspaceSkills({ workspacePath })
4340
4480
  ]);
4341
- const skills = getEligibleSkills({ skills: allSkills, skillsConfig, fullConfig });
4481
+ const workspaceSkills = getEligibleSkills({ skills: allSkills, skillsConfig, fullConfig });
4482
+ const bundledSkills = getEnabledBundledSkills({ skillsConfig, fullConfig });
4483
+ const workspaceSlugs = new Set(workspaceSkills.map((s) => s.slug));
4484
+ const dedupedBundled = bundledSkills.filter((s) => !workspaceSlugs.has(s.slug));
4485
+ const skills = [...workspaceSkills, ...dedupedBundled];
4342
4486
  const personalityFiles = hasCompletePersonalityFiles(rawPersonalityFiles) ? rawPersonalityFiles : void 0;
4343
4487
  return { personalityFiles, toolNotesContent, agentsContent, skills };
4344
4488
  }
@@ -4519,6 +4663,7 @@ var AgentTurnOrchestrator = class {
4519
4663
  if (abortSignal.aborted) return;
4520
4664
  const {
4521
4665
  workspacePath,
4666
+ bundledSkillsDir,
4522
4667
  aiAgent,
4523
4668
  sessionManager,
4524
4669
  schedulerService,
@@ -4618,6 +4763,7 @@ ${description}`;
4618
4763
  toolDeps: this.toolDeps,
4619
4764
  executionContext: {
4620
4765
  workspaceRoot: workspacePath,
4766
+ bundledSkillsDir,
4621
4767
  botTimezone: schedulerService.getTimezone(),
4622
4768
  platform: event.platform,
4623
4769
  chatId: event.chatId,
@@ -5932,11 +6078,11 @@ var ChatRegistry = class {
5932
6078
 
5933
6079
  // src/config/loader.ts
5934
6080
  import { access, mkdir as mkdir5, readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
5935
- import { dirname as dirname5 } from "path";
6081
+ import { dirname as dirname6 } from "path";
5936
6082
 
5937
6083
  // src/config/paths.ts
5938
6084
  import { homedir as homedir2 } from "os";
5939
- import { join as join11 } from "path";
6085
+ import { dirname as dirname5, join as join11, resolve as resolve4, isAbsolute } from "path";
5940
6086
  var CONFIG_PATH_ENV_VAR = "BABYCLAW_CONFIG_PATH";
5941
6087
  function getDefaultConfigPath() {
5942
6088
  return join11(homedir2(), ".babyclaw", "babyclaw.json");
@@ -5948,6 +6094,19 @@ function getConfigPath() {
5948
6094
  }
5949
6095
  return getDefaultConfigPath();
5950
6096
  }
6097
+ function getConfigDir() {
6098
+ return dirname5(getConfigPath());
6099
+ }
6100
+ var DEFAULT_WORKSPACE_ROOT = "~/babyclaw";
6101
+ function resolveWorkspaceRoot({ configRoot }) {
6102
+ if (configRoot === "~" || configRoot.startsWith("~/")) {
6103
+ return join11(homedir2(), configRoot.slice(2));
6104
+ }
6105
+ if (isAbsolute(configRoot)) {
6106
+ return configRoot;
6107
+ }
6108
+ return resolve4(getConfigDir(), configRoot);
6109
+ }
5951
6110
 
5952
6111
  // src/config/schema.ts
5953
6112
  import { z as z11 } from "zod";
@@ -6101,9 +6260,9 @@ var babyclawConfigSchema = z11.object({
6101
6260
  timezone: "UTC"
6102
6261
  }),
6103
6262
  workspace: z11.object({
6104
- root: z11.string().min(1).default(".")
6263
+ root: z11.string().min(1).default("~/babyclaw")
6105
6264
  }).strict().default({
6106
- root: "."
6265
+ root: "~/babyclaw"
6107
6266
  }),
6108
6267
  session: z11.object({
6109
6268
  maxMessagesPerSession: z11.number().int().positive().default(120),
@@ -6163,7 +6322,7 @@ var DEFAULT_CONFIG_TEMPLATE = {
6163
6322
  timezone: "UTC"
6164
6323
  },
6165
6324
  workspace: {
6166
- root: "."
6325
+ root: "~/babyclaw"
6167
6326
  },
6168
6327
  session: {
6169
6328
  maxMessagesPerSession: 120,
@@ -6238,7 +6397,7 @@ async function loadConfigRaw() {
6238
6397
  }
6239
6398
  async function writeConfig({ config }) {
6240
6399
  const configPath = getConfigPath();
6241
- await mkdir5(dirname5(configPath), { recursive: true });
6400
+ await mkdir5(dirname6(configPath), { recursive: true });
6242
6401
  await writeFile5(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
6243
6402
  }
6244
6403
  async function ensureConfigFileExists({ configPath }) {
@@ -6249,7 +6408,7 @@ async function ensureConfigFileExists({ configPath }) {
6249
6408
  if (error.code !== "ENOENT") {
6250
6409
  throw error;
6251
6410
  }
6252
- await mkdir5(dirname5(configPath), { recursive: true });
6411
+ await mkdir5(dirname6(configPath), { recursive: true });
6253
6412
  await writeFile5(configPath, getDefaultConfigTemplate(), "utf8");
6254
6413
  getLogger().warn({ configPath }, "Created config file. Fill required secrets and restart.");
6255
6414
  }
@@ -6421,6 +6580,7 @@ var HeartbeatExecutor = class {
6421
6580
  toolDeps: this.toolDeps,
6422
6581
  executionContext: {
6423
6582
  workspaceRoot: workspacePath,
6583
+ bundledSkillsDir: this.toolDeps.bundledSkillsDir,
6424
6584
  botTimezone: schedulerService.getTimezone(),
6425
6585
  platform: mainChat.platform,
6426
6586
  chatId: platformChatId,
@@ -6877,6 +7037,7 @@ var SchedulerExecutor = class {
6877
7037
  toolDeps: this.toolDeps,
6878
7038
  executionContext: {
6879
7039
  workspaceRoot: workspacePath,
7040
+ bundledSkillsDir: this.toolDeps.bundledSkillsDir,
6880
7041
  botTimezone: schedulerService.getTimezone(),
6881
7042
  platform: this.channelSender.platform,
6882
7043
  chatId: chatIdStr,
@@ -7623,13 +7784,13 @@ function createDatabase({ workspacePath }) {
7623
7784
  }
7624
7785
 
7625
7786
  // src/database/migrate.ts
7626
- import { dirname as dirname6, resolve as resolve4 } from "path";
7787
+ import { dirname as dirname7, resolve as resolve5 } from "path";
7627
7788
  import { fileURLToPath } from "url";
7628
7789
  import { migrate } from "drizzle-orm/better-sqlite3/migrator";
7629
- var __dirname = dirname6(fileURLToPath(import.meta.url));
7790
+ var __dirname = dirname7(fileURLToPath(import.meta.url));
7630
7791
  function applyMigrations({ workspacePath }) {
7631
7792
  const db = createDatabase({ workspacePath });
7632
- const migrationsFolder = resolve4(__dirname, "..", "drizzle");
7793
+ const migrationsFolder = resolve5(__dirname, "..", "drizzle");
7633
7794
  migrate(db, { migrationsFolder });
7634
7795
  }
7635
7796
 
@@ -7667,7 +7828,8 @@ var GatewayRuntime = class {
7667
7828
  },
7668
7829
  "Gateway configuration summary"
7669
7830
  );
7670
- const workspacePath = resolve5(process.cwd(), config.workspace.root);
7831
+ const workspacePath = resolveWorkspaceRoot({ configRoot: config.workspace.root });
7832
+ mkdirSync3(workspacePath, { recursive: true });
7671
7833
  log.info("Applying database migrations...");
7672
7834
  applyMigrations({ workspacePath });
7673
7835
  log.info("Database migrations applied");
@@ -7751,6 +7913,7 @@ var GatewayRuntime = class {
7751
7913
  const adminSocketPath = getAdminSocketPath();
7752
7914
  const toolDeps = {
7753
7915
  workspacePath,
7916
+ bundledSkillsDir: getBundledSkillsDir(),
7754
7917
  aiAgent,
7755
7918
  sessionManager,
7756
7919
  schedulerService,
@@ -7856,6 +8019,16 @@ var GatewayRuntime = class {
7856
8019
  void this.stop().then(() => process.exit(0));
7857
8020
  });
7858
8021
  return { ok: true };
8022
+ },
8023
+ "/reload-skills": async () => {
8024
+ const freshConfig = await loadConfigRaw();
8025
+ if (!freshConfig) {
8026
+ return { ok: false, error: "config_not_found" };
8027
+ }
8028
+ toolDeps.skillsConfig = freshConfig.skills;
8029
+ toolDeps.fullConfig = freshConfig;
8030
+ log.info("Skills configuration reloaded via admin socket");
8031
+ return { ok: true };
7859
8032
  }
7860
8033
  }
7861
8034
  });
@@ -7956,6 +8129,9 @@ var AdminClient = class {
7956
8129
  async shutdown() {
7957
8130
  return this.get({ path: "/shutdown" });
7958
8131
  }
8132
+ async reloadSkills() {
8133
+ return this.get({ path: "/reload-skills" });
8134
+ }
7959
8135
  get({ path }) {
7960
8136
  return new Promise((resolve6, reject) => {
7961
8137
  const req = request(
@@ -7997,6 +8173,7 @@ export {
7997
8173
  AdminClient,
7998
8174
  CONFIG_PATH_ENV_VAR,
7999
8175
  ClawHubError,
8176
+ DEFAULT_WORKSPACE_ROOT,
8000
8177
  GatewayRuntime,
8001
8178
  SUPPORTED_PROVIDERS,
8002
8179
  SkillAlreadyInstalledError,
@@ -8004,14 +8181,19 @@ export {
8004
8181
  createChildLogger,
8005
8182
  createLogger,
8006
8183
  getAdminSocketPath,
8184
+ getBundledSkillPath,
8007
8185
  getConfigPath,
8008
8186
  getDefaultConfigPath,
8009
8187
  getDefaultConfigTemplate,
8188
+ getEnabledBundledSkills,
8010
8189
  getLogger,
8190
+ getSkillKey,
8011
8191
  installSkillFromClawHub,
8192
+ listBundledSkills,
8012
8193
  loadConfig,
8013
8194
  loadConfigRaw,
8014
8195
  resolveLanguageModel,
8196
+ resolveWorkspaceRoot,
8015
8197
  runSkillSetup,
8016
8198
  writeConfig
8017
8199
  };