@cortask/core 0.2.13 → 0.2.15

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.js CHANGED
@@ -3048,9 +3048,9 @@ async function ensureBrowser() {
3048
3048
  async screenshot() {
3049
3049
  const tmpPath = `${process.env.TEMP || "/tmp"}/ab-screenshot-${Date.now()}.png`;
3050
3050
  await exec2(["screenshot", tmpPath]);
3051
- const fs18 = await import("fs/promises");
3052
- const buf = await fs18.readFile(tmpPath);
3053
- await fs18.unlink(tmpPath).catch(() => {
3051
+ const fs20 = await import("fs/promises");
3052
+ const buf = await fs20.readFile(tmpPath);
3053
+ await fs20.unlink(tmpPath).catch(() => {
3054
3054
  });
3055
3055
  return buf;
3056
3056
  },
@@ -3655,6 +3655,503 @@ ${lines.join("\n")}` };
3655
3655
  };
3656
3656
  }
3657
3657
 
3658
+ // src/agent/tools/skill.ts
3659
+ import fs14 from "fs/promises";
3660
+
3661
+ // src/skills/writer.ts
3662
+ import fs12 from "fs/promises";
3663
+ import path12 from "path";
3664
+
3665
+ // src/skills/loader.ts
3666
+ import fs11 from "fs/promises";
3667
+ import path11 from "path";
3668
+ import matter from "gray-matter";
3669
+
3670
+ // src/skills/credential-schema.ts
3671
+ import fs10 from "fs/promises";
3672
+ import path10 from "path";
3673
+ async function loadCredentialSchema(skillDir) {
3674
+ const schemaPath = path10.join(skillDir, "credentials.json");
3675
+ try {
3676
+ const raw = await fs10.readFile(schemaPath, "utf-8");
3677
+ const parsed = JSON.parse(raw);
3678
+ if (!parsed.credentials || !Array.isArray(parsed.credentials)) {
3679
+ return void 0;
3680
+ }
3681
+ return parsed;
3682
+ } catch {
3683
+ return void 0;
3684
+ }
3685
+ }
3686
+ function getCredentialStorageKey(skillName, credentialId, fieldKey, storeAs) {
3687
+ if (storeAs) {
3688
+ return `${storeAs}.${fieldKey}`;
3689
+ }
3690
+ return `skill.${skillName}.${credentialId}.${fieldKey}`;
3691
+ }
3692
+ function getInstanceStorageKey(skillName, credentialId, instanceId, fieldKey, storeAs) {
3693
+ if (storeAs) {
3694
+ return `${storeAs}.${instanceId}.${fieldKey}`;
3695
+ }
3696
+ return `skill.${skillName}.${credentialId}.${instanceId}.${fieldKey}`;
3697
+ }
3698
+ function getInstanceRegistryKey(skillName, credentialId, storeAs) {
3699
+ if (storeAs) {
3700
+ return `${storeAs}._instances`;
3701
+ }
3702
+ return `skill.${skillName}.${credentialId}._instances`;
3703
+ }
3704
+ function getOAuth2StorageKeys(skillName, credentialId) {
3705
+ const prefix = `skill.${skillName}.${credentialId}.oauth2`;
3706
+ return {
3707
+ accessToken: `${prefix}.accessToken`,
3708
+ refreshToken: `${prefix}.refreshToken`,
3709
+ expiresAt: `${prefix}.expiresAt`
3710
+ };
3711
+ }
3712
+
3713
+ // src/skills/loader.ts
3714
+ var skillCache = null;
3715
+ function clearSkillCache() {
3716
+ skillCache = null;
3717
+ }
3718
+ async function loadSkills(bundledDir, userDir, configDirs, credentialStore) {
3719
+ if (skillCache) return skillCache;
3720
+ const skills = [];
3721
+ if (bundledDir) {
3722
+ const entries = await scanSkillDir(bundledDir, "bundled");
3723
+ skills.push(...entries);
3724
+ }
3725
+ if (userDir) {
3726
+ const entries = await scanSkillDir(userDir, "user");
3727
+ skills.push(...entries);
3728
+ }
3729
+ for (const dir of configDirs) {
3730
+ const entries = await scanSkillDir(dir, "config-dir");
3731
+ skills.push(...entries);
3732
+ }
3733
+ for (const skill of skills) {
3734
+ skill.credentialSchema = await loadCredentialSchema(skill.path);
3735
+ const result = await checkEligibility(skill, credentialStore);
3736
+ skill.eligible = result.eligible;
3737
+ skill.ineligibleReason = result.reason;
3738
+ skill.credentialStatus = result.credentialStatus;
3739
+ if (skill.manifest.install) {
3740
+ skill.installOptions = skill.manifest.install.filter((spec) => {
3741
+ if (!spec.os) return true;
3742
+ return spec.os.includes(process.platform);
3743
+ }).map((spec, i) => ({
3744
+ id: spec.id ?? `install-${i}`,
3745
+ kind: spec.kind,
3746
+ label: spec.label ?? `Install via ${spec.kind}`
3747
+ }));
3748
+ }
3749
+ }
3750
+ skillCache = skills;
3751
+ return skills;
3752
+ }
3753
+ function getEligibleSkills(skills) {
3754
+ return skills.filter((s) => s.eligible);
3755
+ }
3756
+ async function scanSkillDir(dir, source) {
3757
+ const skills = [];
3758
+ try {
3759
+ const entries = await fs11.readdir(dir, { withFileTypes: true });
3760
+ for (const entry of entries) {
3761
+ if (!entry.isDirectory()) continue;
3762
+ const skillDir = path11.join(dir, entry.name);
3763
+ const skillMdPath = path11.join(skillDir, "SKILL.md");
3764
+ try {
3765
+ const raw = await fs11.readFile(skillMdPath, "utf-8");
3766
+ const parsed = matter(raw);
3767
+ const manifest = parsed.data;
3768
+ if (!manifest.name) {
3769
+ manifest.name = entry.name;
3770
+ }
3771
+ if (!manifest.description) {
3772
+ manifest.description = "";
3773
+ }
3774
+ let skillSource = source;
3775
+ try {
3776
+ await fs11.access(path11.join(skillDir, ".origin"));
3777
+ skillSource = "git";
3778
+ } catch {
3779
+ }
3780
+ let hasCodeTools = false;
3781
+ try {
3782
+ await fs11.access(path11.join(skillDir, "index.js"));
3783
+ hasCodeTools = true;
3784
+ } catch {
3785
+ }
3786
+ skills.push({
3787
+ manifest,
3788
+ content: parsed.content.trim(),
3789
+ path: skillDir,
3790
+ eligible: false,
3791
+ // Will be set by checkEligibility
3792
+ source: skillSource,
3793
+ editable: skillSource !== "bundled",
3794
+ hasCodeTools
3795
+ });
3796
+ } catch {
3797
+ }
3798
+ }
3799
+ } catch {
3800
+ }
3801
+ return skills;
3802
+ }
3803
+ async function checkEligibility(skill, credentialStore) {
3804
+ const { manifest } = skill;
3805
+ if (manifest.always) {
3806
+ return { eligible: true };
3807
+ }
3808
+ const requiredOs = manifest.compatibility?.os;
3809
+ if (requiredOs && !requiredOs.includes(process.platform)) {
3810
+ return {
3811
+ eligible: false,
3812
+ reason: `Requires OS: ${requiredOs.join(", ")} (current: ${process.platform})`
3813
+ };
3814
+ }
3815
+ if (manifest.requires?.env) {
3816
+ for (const envVar of manifest.requires.env) {
3817
+ if (!process.env[envVar]) {
3818
+ return {
3819
+ eligible: false,
3820
+ reason: `Missing environment variable: ${envVar}`
3821
+ };
3822
+ }
3823
+ }
3824
+ }
3825
+ if (manifest.requires?.bins) {
3826
+ for (const bin of manifest.requires.bins) {
3827
+ if (!await isBinaryAvailable(bin)) {
3828
+ return {
3829
+ eligible: false,
3830
+ reason: `Missing binary: ${bin}`
3831
+ };
3832
+ }
3833
+ }
3834
+ }
3835
+ if (skill.credentialSchema && credentialStore) {
3836
+ const credentialStatus = {};
3837
+ let allFulfilled = true;
3838
+ for (const cred of skill.credentialSchema.credentials) {
3839
+ if (cred.type === "oauth2") {
3840
+ const keys = getOAuth2StorageKeys(manifest.name, cred.id);
3841
+ const hasToken = await credentialStore.has(keys.accessToken);
3842
+ credentialStatus[cred.id] = hasToken;
3843
+ if (!hasToken) allFulfilled = false;
3844
+ } else if (cred.multiple && cred.fields) {
3845
+ const registryKey = getInstanceRegistryKey(manifest.name, cred.id, cred.storeAs);
3846
+ const raw = await credentialStore.get(registryKey);
3847
+ const instances = raw ? JSON.parse(raw) : [];
3848
+ let anyInstanceFulfilled = false;
3849
+ for (const instance2 of instances) {
3850
+ let instanceOk = true;
3851
+ for (const field of cred.fields) {
3852
+ if (field.required === false) continue;
3853
+ const key = getInstanceStorageKey(
3854
+ manifest.name,
3855
+ cred.id,
3856
+ instance2.id,
3857
+ field.key,
3858
+ cred.storeAs
3859
+ );
3860
+ if (!await credentialStore.has(key)) {
3861
+ instanceOk = false;
3862
+ break;
3863
+ }
3864
+ }
3865
+ if (instanceOk) {
3866
+ anyInstanceFulfilled = true;
3867
+ break;
3868
+ }
3869
+ }
3870
+ credentialStatus[cred.id] = anyInstanceFulfilled;
3871
+ if (!anyInstanceFulfilled) allFulfilled = false;
3872
+ } else if (cred.fields) {
3873
+ let fieldsFulfilled = true;
3874
+ for (const field of cred.fields) {
3875
+ if (field.required === false) continue;
3876
+ const key = getCredentialStorageKey(
3877
+ manifest.name,
3878
+ cred.id,
3879
+ field.key,
3880
+ cred.storeAs
3881
+ );
3882
+ const exists = await credentialStore.has(key);
3883
+ if (!exists) {
3884
+ fieldsFulfilled = false;
3885
+ break;
3886
+ }
3887
+ }
3888
+ credentialStatus[cred.id] = fieldsFulfilled;
3889
+ if (!fieldsFulfilled) allFulfilled = false;
3890
+ }
3891
+ }
3892
+ if (!allFulfilled) {
3893
+ return {
3894
+ eligible: false,
3895
+ reason: "Missing required credentials",
3896
+ credentialStatus
3897
+ };
3898
+ }
3899
+ return { eligible: true, credentialStatus };
3900
+ }
3901
+ return { eligible: true };
3902
+ }
3903
+ async function isBinaryAvailable(name) {
3904
+ const { exec: exec4 } = await import("child_process");
3905
+ const cmd = process.platform === "win32" ? `where ${name}` : `which ${name}`;
3906
+ return new Promise((resolve) => {
3907
+ exec4(cmd, (error) => {
3908
+ resolve(!error);
3909
+ });
3910
+ });
3911
+ }
3912
+
3913
+ // src/skills/writer.ts
3914
+ var SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3915
+ function validateSkillName(name) {
3916
+ if (!name || name.length < 2 || name.length > 50) {
3917
+ return "Skill name must be 2-50 characters";
3918
+ }
3919
+ if (!SKILL_NAME_RE.test(name)) {
3920
+ return "Skill name must be kebab-case (lowercase letters, numbers, hyphens)";
3921
+ }
3922
+ return null;
3923
+ }
3924
+ async function createSkill(skillsDir, name, rawContent) {
3925
+ const nameErr = validateSkillName(name);
3926
+ if (nameErr) throw new Error(nameErr);
3927
+ const skillDir = path12.join(skillsDir, name);
3928
+ try {
3929
+ await fs12.access(skillDir);
3930
+ throw new Error(`Skill "${name}" already exists`);
3931
+ } catch (err) {
3932
+ if (err.message.includes("already exists")) throw err;
3933
+ }
3934
+ await fs12.mkdir(skillDir, { recursive: true });
3935
+ await fs12.writeFile(path12.join(skillDir, "SKILL.md"), rawContent, "utf-8");
3936
+ clearSkillCache();
3937
+ return { name, path: skillDir };
3938
+ }
3939
+ async function updateSkill(skillsDir, name, rawContent) {
3940
+ const skillDir = path12.join(skillsDir, name);
3941
+ const skillMdPath = path12.join(skillDir, "SKILL.md");
3942
+ try {
3943
+ await fs12.access(skillMdPath);
3944
+ } catch {
3945
+ throw new Error(`Skill "${name}" not found`);
3946
+ }
3947
+ await fs12.writeFile(skillMdPath, rawContent, "utf-8");
3948
+ clearSkillCache();
3949
+ }
3950
+ async function readSkillFile(skillsDir, name) {
3951
+ const skillMdPath = path12.join(skillsDir, name, "SKILL.md");
3952
+ try {
3953
+ return await fs12.readFile(skillMdPath, "utf-8");
3954
+ } catch {
3955
+ throw new Error(`Skill "${name}" not found`);
3956
+ }
3957
+ }
3958
+
3959
+ // src/skills/installer.ts
3960
+ import { exec as exec3 } from "child_process";
3961
+ import fs13 from "fs/promises";
3962
+ import path13 from "path";
3963
+ async function installSkillFromGit(gitUrl, skillsDir) {
3964
+ await fs13.mkdir(skillsDir, { recursive: true });
3965
+ const repoName = gitUrl.split("/").pop()?.replace(/\.git$/, "");
3966
+ if (!repoName) {
3967
+ throw new Error(`Invalid git URL: ${gitUrl}`);
3968
+ }
3969
+ const targetDir = path13.join(skillsDir, repoName);
3970
+ try {
3971
+ await fs13.access(targetDir);
3972
+ throw new Error(`Skill "${repoName}" is already installed`);
3973
+ } catch (err) {
3974
+ if (err.message.includes("already installed")) throw err;
3975
+ }
3976
+ await execAsync(`git clone --depth 1 "${gitUrl}" "${targetDir}"`);
3977
+ try {
3978
+ await fs13.access(path13.join(targetDir, "SKILL.md"));
3979
+ } catch {
3980
+ await fs13.rm(targetDir, { recursive: true, force: true });
3981
+ throw new Error(
3982
+ `Invalid skill: no SKILL.md found in ${repoName}`
3983
+ );
3984
+ }
3985
+ await fs13.writeFile(
3986
+ path13.join(targetDir, ".origin"),
3987
+ gitUrl,
3988
+ "utf-8"
3989
+ );
3990
+ try {
3991
+ await fs13.rm(path13.join(targetDir, ".git"), {
3992
+ recursive: true,
3993
+ force: true
3994
+ });
3995
+ } catch {
3996
+ }
3997
+ clearSkillCache();
3998
+ logger.info(`Installed skill "${repoName}" from ${gitUrl}`, "skills");
3999
+ return { name: repoName, path: targetDir };
4000
+ }
4001
+ async function removeSkill(skillName, skillsDir) {
4002
+ const targetDir = path13.join(skillsDir, skillName);
4003
+ try {
4004
+ await fs13.access(targetDir);
4005
+ } catch {
4006
+ throw new Error(`Skill "${skillName}" not found`);
4007
+ }
4008
+ await fs13.rm(targetDir, { recursive: true, force: true });
4009
+ clearSkillCache();
4010
+ logger.info(`Removed skill "${skillName}"`, "skills");
4011
+ }
4012
+ function execAsync(command) {
4013
+ return new Promise((resolve, reject) => {
4014
+ exec3(command, { timeout: 6e4 }, (error, stdout, stderr) => {
4015
+ if (error) {
4016
+ reject(new Error(stderr || error.message));
4017
+ return;
4018
+ }
4019
+ resolve(stdout);
4020
+ });
4021
+ });
4022
+ }
4023
+
4024
+ // src/agent/tools/skill.ts
4025
+ function createSkillTool(userSkillsDir, bundledSkillNames) {
4026
+ const bundledNames = new Set(bundledSkillNames);
4027
+ return {
4028
+ definition: {
4029
+ name: "cortask_skill",
4030
+ description: 'Manage custom skills. To create: set action="create", name, and content (full SKILL.md with YAML frontmatter + markdown). To update: action="update" with name and content. To list: action="list". To remove: action="remove" and name.',
4031
+ inputSchema: {
4032
+ type: "object",
4033
+ properties: {
4034
+ action: {
4035
+ type: "string",
4036
+ description: "The action to perform",
4037
+ enum: ["create", "update", "list", "remove"]
4038
+ },
4039
+ name: {
4040
+ type: "string",
4041
+ description: "Skill name in kebab-case (for create/update/remove)"
4042
+ },
4043
+ content: {
4044
+ type: "string",
4045
+ description: "Full SKILL.md content including YAML frontmatter and markdown body (for create/update)"
4046
+ }
4047
+ },
4048
+ required: ["action"]
4049
+ }
4050
+ },
4051
+ execute: async (args, _context) => {
4052
+ const action = args.action;
4053
+ try {
4054
+ switch (action) {
4055
+ case "create": {
4056
+ const name = args.name;
4057
+ const content = args.content;
4058
+ if (!name || !content) {
4059
+ return {
4060
+ toolCallId: "",
4061
+ content: "name and content are required for create",
4062
+ isError: true
4063
+ };
4064
+ }
4065
+ const nameErr = validateSkillName(name);
4066
+ if (nameErr) {
4067
+ return { toolCallId: "", content: nameErr, isError: true };
4068
+ }
4069
+ if (bundledNames.has(name)) {
4070
+ return {
4071
+ toolCallId: "",
4072
+ content: `Cannot create skill "${name}" \u2014 a built-in skill with that name already exists. Choose a different name.`,
4073
+ isError: true
4074
+ };
4075
+ }
4076
+ const result = await createSkill(userSkillsDir, name, content);
4077
+ return {
4078
+ toolCallId: "",
4079
+ content: `Custom skill "${result.name}" created at ${result.path}. It will be available in the next conversation.`
4080
+ };
4081
+ }
4082
+ case "update": {
4083
+ const name = args.name;
4084
+ const content = args.content;
4085
+ if (!name || !content) {
4086
+ return {
4087
+ toolCallId: "",
4088
+ content: "name and content are required for update",
4089
+ isError: true
4090
+ };
4091
+ }
4092
+ await updateSkill(userSkillsDir, name, content);
4093
+ return {
4094
+ toolCallId: "",
4095
+ content: `Custom skill "${name}" updated. Changes will take effect in the next conversation.`
4096
+ };
4097
+ }
4098
+ case "list": {
4099
+ try {
4100
+ const entries = await fs14.readdir(userSkillsDir, {
4101
+ withFileTypes: true
4102
+ });
4103
+ const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
4104
+ if (dirs.length === 0) {
4105
+ return {
4106
+ toolCallId: "",
4107
+ content: "No custom skills found."
4108
+ };
4109
+ }
4110
+ return {
4111
+ toolCallId: "",
4112
+ content: `Custom skills:
4113
+ ${dirs.map((d) => `- ${d}`).join("\n")}`
4114
+ };
4115
+ } catch {
4116
+ return {
4117
+ toolCallId: "",
4118
+ content: "No custom skills found."
4119
+ };
4120
+ }
4121
+ }
4122
+ case "remove": {
4123
+ const name = args.name;
4124
+ if (!name) {
4125
+ return {
4126
+ toolCallId: "",
4127
+ content: "name is required for remove",
4128
+ isError: true
4129
+ };
4130
+ }
4131
+ await removeSkill(name, userSkillsDir);
4132
+ return {
4133
+ toolCallId: "",
4134
+ content: `Custom skill "${name}" removed.`
4135
+ };
4136
+ }
4137
+ default:
4138
+ return {
4139
+ toolCallId: "",
4140
+ content: `Unknown action: ${action}`,
4141
+ isError: true
4142
+ };
4143
+ }
4144
+ } catch (err) {
4145
+ return {
4146
+ toolCallId: "",
4147
+ content: `Skill error: ${err instanceof Error ? err.message : String(err)}`,
4148
+ isError: true
4149
+ };
4150
+ }
4151
+ }
4152
+ };
4153
+ }
4154
+
3658
4155
  // src/agent/tools/index.ts
3659
4156
  var builtinTools = [
3660
4157
  readFileTool,
@@ -3673,8 +4170,8 @@ var builtinTools = [
3673
4170
 
3674
4171
  // src/credentials/store.ts
3675
4172
  import crypto4 from "crypto";
3676
- import fs10 from "fs/promises";
3677
- import path10 from "path";
4173
+ import fs15 from "fs/promises";
4174
+ import path14 from "path";
3678
4175
  function deriveKey(secret, salt) {
3679
4176
  return crypto4.scryptSync(secret, salt, 32);
3680
4177
  }
@@ -3719,7 +4216,7 @@ var EncryptedCredentialStore = class {
3719
4216
  async load() {
3720
4217
  if (this.store) return this.store;
3721
4218
  try {
3722
- const raw = await fs10.readFile(this.filePath, "utf-8");
4219
+ const raw = await fs15.readFile(this.filePath, "utf-8");
3723
4220
  this.store = JSON.parse(raw);
3724
4221
  } catch {
3725
4222
  this.store = { version: 1, entries: {} };
@@ -3728,10 +4225,10 @@ var EncryptedCredentialStore = class {
3728
4225
  }
3729
4226
  async save() {
3730
4227
  if (!this.store) return;
3731
- await fs10.mkdir(path10.dirname(this.filePath), { recursive: true });
4228
+ await fs15.mkdir(path14.dirname(this.filePath), { recursive: true });
3732
4229
  const tmp = `${this.filePath}.${process.pid}.tmp`;
3733
- await fs10.writeFile(tmp, JSON.stringify(this.store, null, 2), "utf-8");
3734
- await fs10.rename(tmp, this.filePath);
4230
+ await fs15.writeFile(tmp, JSON.stringify(this.store, null, 2), "utf-8");
4231
+ await fs15.rename(tmp, this.filePath);
3735
4232
  }
3736
4233
  async get(key) {
3737
4234
  const store = await this.load();
@@ -3765,13 +4262,13 @@ var EncryptedCredentialStore = class {
3765
4262
  async function getOrCreateSecret(dataDir) {
3766
4263
  const secretFromEnv = process.env.CORTASK_SECRET;
3767
4264
  if (secretFromEnv) return secretFromEnv;
3768
- const keyPath = path10.join(dataDir, "master.key");
4265
+ const keyPath = path14.join(dataDir, "master.key");
3769
4266
  try {
3770
- return await fs10.readFile(keyPath, "utf-8");
4267
+ return await fs15.readFile(keyPath, "utf-8");
3771
4268
  } catch {
3772
4269
  const key = crypto4.randomBytes(32).toString("hex");
3773
- await fs10.mkdir(path10.dirname(keyPath), { recursive: true });
3774
- await fs10.writeFile(keyPath, key, { mode: 384 });
4270
+ await fs15.mkdir(path14.dirname(keyPath), { recursive: true });
4271
+ await fs15.writeFile(keyPath, key, { mode: 384 });
3775
4272
  return key;
3776
4273
  }
3777
4274
  }
@@ -3781,8 +4278,8 @@ function credentialKey(category, ...parts) {
3781
4278
 
3782
4279
  // src/config/schema.ts
3783
4280
  import { z } from "zod";
3784
- import fs11 from "fs/promises";
3785
- import path11 from "path";
4281
+ import fs16 from "fs/promises";
4282
+ import path15 from "path";
3786
4283
  var providerConfigSchema = z.object({
3787
4284
  model: z.string().optional()
3788
4285
  });
@@ -3835,7 +4332,7 @@ var cortaskConfigSchema = z.object({
3835
4332
  });
3836
4333
  async function loadConfig(configPath) {
3837
4334
  try {
3838
- const raw = await fs11.readFile(configPath, "utf-8");
4335
+ const raw = await fs16.readFile(configPath, "utf-8");
3839
4336
  const resolved = raw.replace(/\$\{(\w+)\}/g, (_, name) => {
3840
4337
  return process.env[name] ?? "";
3841
4338
  });
@@ -3854,15 +4351,15 @@ async function loadConfig(configPath) {
3854
4351
  }
3855
4352
  }
3856
4353
  async function saveConfig(configPath, config) {
3857
- const dir = path11.dirname(configPath);
3858
- await fs11.mkdir(dir, { recursive: true });
4354
+ const dir = path15.dirname(configPath);
4355
+ await fs16.mkdir(dir, { recursive: true });
3859
4356
  if (configPath.endsWith(".yaml") || configPath.endsWith(".yml")) {
3860
4357
  const { stringify } = await import("yaml").catch(() => ({
3861
4358
  stringify: (obj) => JSON.stringify(obj, null, 2)
3862
4359
  }));
3863
- await fs11.writeFile(configPath, stringify(config), "utf-8");
4360
+ await fs16.writeFile(configPath, stringify(config), "utf-8");
3864
4361
  } else {
3865
- await fs11.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
4362
+ await fs16.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
3866
4363
  }
3867
4364
  }
3868
4365
  function getDataDir() {
@@ -3870,7 +4367,7 @@ function getDataDir() {
3870
4367
  return process.env.CORTASK_DATA_DIR;
3871
4368
  }
3872
4369
  const home = process.env.HOME || process.env.USERPROFILE || process.cwd();
3873
- return path11.join(home, ".cortask");
4370
+ return path15.join(home, ".cortask");
3874
4371
  }
3875
4372
 
3876
4373
  // src/onboarding/validator.ts
@@ -3915,8 +4412,8 @@ function getDefaultModel(type) {
3915
4412
  }
3916
4413
 
3917
4414
  // src/workspace/manager.ts
3918
- import fs12 from "fs/promises";
3919
- import path12 from "path";
4415
+ import fs17 from "fs/promises";
4416
+ import path16 from "path";
3920
4417
  import Database2 from "better-sqlite3";
3921
4418
  var CORTASK_DIR2 = ".cortask";
3922
4419
  var WorkspaceManager = class {
@@ -3925,7 +4422,7 @@ var WorkspaceManager = class {
3925
4422
  activeWorkspaceId = null;
3926
4423
  constructor(dbPath) {
3927
4424
  this.db = new Database2(dbPath);
3928
- this.dataDir = path12.dirname(dbPath);
4425
+ this.dataDir = path16.dirname(dbPath);
3929
4426
  this.db.pragma("journal_mode = WAL");
3930
4427
  this.db.pragma("foreign_keys = ON");
3931
4428
  this.initSchema();
@@ -3982,14 +4479,14 @@ var WorkspaceManager = class {
3982
4479
  async create(name, rootPath) {
3983
4480
  const id = crypto.randomUUID();
3984
4481
  const now = (/* @__PURE__ */ new Date()).toISOString();
3985
- const absPath = rootPath ? path12.resolve(rootPath) : path12.join(this.dataDir, "projects", id);
3986
- const cortaskDir = path12.join(absPath, CORTASK_DIR2);
3987
- await fs12.mkdir(cortaskDir, { recursive: true });
3988
- const memoryPath = path12.join(cortaskDir, "memory.md");
4482
+ const absPath = rootPath ? path16.resolve(rootPath) : path16.join(this.dataDir, "projects", id);
4483
+ const cortaskDir = path16.join(absPath, CORTASK_DIR2);
4484
+ await fs17.mkdir(cortaskDir, { recursive: true });
4485
+ const memoryPath = path16.join(cortaskDir, "memory.md");
3989
4486
  try {
3990
- await fs12.access(memoryPath);
4487
+ await fs17.access(memoryPath);
3991
4488
  } catch {
3992
- await fs12.writeFile(
4489
+ await fs17.writeFile(
3993
4490
  memoryPath,
3994
4491
  "# Project Memory\n\nThis file is used by Cortask to remember important context about this project.\n",
3995
4492
  "utf-8"
@@ -4052,32 +4549,32 @@ var WorkspaceManager = class {
4052
4549
  return this.get(this.activeWorkspaceId);
4053
4550
  }
4054
4551
  async readMemory(workspacePath) {
4055
- const memoryPath = path12.join(workspacePath, CORTASK_DIR2, "memory.md");
4552
+ const memoryPath = path16.join(workspacePath, CORTASK_DIR2, "memory.md");
4056
4553
  try {
4057
- return await fs12.readFile(memoryPath, "utf-8");
4554
+ return await fs17.readFile(memoryPath, "utf-8");
4058
4555
  } catch {
4059
4556
  return void 0;
4060
4557
  }
4061
4558
  }
4062
4559
  async writeMemory(workspacePath, content) {
4063
- const memoryPath = path12.join(workspacePath, CORTASK_DIR2, "memory.md");
4064
- await fs12.mkdir(path12.dirname(memoryPath), { recursive: true });
4065
- await fs12.writeFile(memoryPath, content, "utf-8");
4560
+ const memoryPath = path16.join(workspacePath, CORTASK_DIR2, "memory.md");
4561
+ await fs17.mkdir(path16.dirname(memoryPath), { recursive: true });
4562
+ await fs17.writeFile(memoryPath, content, "utf-8");
4066
4563
  }
4067
4564
  async readGlobalMemory(dataDir) {
4068
- const memoryPath = path12.join(dataDir, "memory.md");
4565
+ const memoryPath = path16.join(dataDir, "memory.md");
4069
4566
  try {
4070
- return await fs12.readFile(memoryPath, "utf-8");
4567
+ return await fs17.readFile(memoryPath, "utf-8");
4071
4568
  } catch {
4072
4569
  return void 0;
4073
4570
  }
4074
4571
  }
4075
4572
  async writeGlobalMemory(dataDir, content) {
4076
- await fs12.mkdir(dataDir, { recursive: true });
4077
- await fs12.writeFile(path12.join(dataDir, "memory.md"), content, "utf-8");
4573
+ await fs17.mkdir(dataDir, { recursive: true });
4574
+ await fs17.writeFile(path16.join(dataDir, "memory.md"), content, "utf-8");
4078
4575
  }
4079
4576
  getSessionDbPath(workspacePath) {
4080
- return path12.join(workspacePath, CORTASK_DIR2, "sessions.db");
4577
+ return path16.join(workspacePath, CORTASK_DIR2, "sessions.db");
4081
4578
  }
4082
4579
  getChannelWorkspace(chatKey) {
4083
4580
  const row = this.db.prepare("SELECT workspace_id FROM channel_workspace_map WHERE chat_key = ?").get(chatKey);
@@ -4167,366 +4664,118 @@ var SessionStore = class {
4167
4664
  now
4168
4665
  );
4169
4666
  }
4170
- this.db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, sessionId);
4171
- });
4172
- transaction();
4173
- }
4174
- getMessages(sessionId) {
4175
- const rows = this.db.prepare(
4176
- "SELECT role, content FROM messages WHERE session_id = ? ORDER BY id ASC"
4177
- ).all(sessionId);
4178
- return rows.map((r) => ({
4179
- role: r.role,
4180
- content: JSON.parse(r.content)
4181
- }));
4182
- }
4183
- deleteSession(id) {
4184
- this.db.prepare("DELETE FROM sessions WHERE id = ?").run(id);
4185
- }
4186
- updateTitle(id, title) {
4187
- this.db.prepare("UPDATE sessions SET title = ? WHERE id = ?").run(title, id);
4188
- }
4189
- close() {
4190
- this.db.close();
4191
- }
4192
- };
4193
-
4194
- // src/session/migrate.ts
4195
- import Database4 from "better-sqlite3";
4196
- import fs13 from "fs";
4197
- function migrateSessionDatabase(dbPath) {
4198
- if (!fs13.existsSync(dbPath)) {
4199
- logger.debug(`Session database does not exist yet: ${dbPath}`, "migration");
4200
- return;
4201
- }
4202
- const db = new Database4(dbPath);
4203
- try {
4204
- const columns = db.prepare("PRAGMA table_info(sessions)").all();
4205
- if (columns.length === 0) {
4206
- logger.debug(`Sessions table does not exist yet in: ${dbPath}`, "migration");
4207
- db.close();
4208
- return;
4209
- }
4210
- const hasParentSessionId = columns.some((c) => c.name === "parent_session_id");
4211
- const hasDepth = columns.some((c) => c.name === "depth");
4212
- const hasChannel = columns.some((c) => c.name === "channel");
4213
- let changesMade = false;
4214
- if (!hasParentSessionId) {
4215
- logger.info(`Migrating database: adding parent_session_id to ${dbPath}`, "migration");
4216
- db.exec("ALTER TABLE sessions ADD COLUMN parent_session_id TEXT");
4217
- changesMade = true;
4218
- logger.info("\u2713 Added parent_session_id column", "migration");
4219
- }
4220
- if (!hasDepth) {
4221
- logger.info(`Migrating database: adding depth to ${dbPath}`, "migration");
4222
- db.exec("ALTER TABLE sessions ADD COLUMN depth INTEGER DEFAULT 0");
4223
- changesMade = true;
4224
- logger.info("\u2713 Added depth column", "migration");
4225
- }
4226
- if (!hasChannel) {
4227
- logger.info(`Migrating database: adding channel to ${dbPath}`, "migration");
4228
- db.exec("ALTER TABLE sessions ADD COLUMN channel TEXT");
4229
- changesMade = true;
4230
- logger.info("\u2713 Added channel column", "migration");
4231
- }
4232
- if (!hasParentSessionId) {
4233
- db.exec(
4234
- "CREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_session_id)"
4235
- );
4236
- logger.info("\u2713 Created parent session index", "migration");
4237
- }
4238
- if (changesMade) {
4239
- logger.info(`Migration complete for: ${dbPath}`, "migration");
4240
- } else {
4241
- logger.debug(`No migration needed for: ${dbPath}`, "migration");
4242
- }
4243
- } catch (err) {
4244
- logger.error(
4245
- `Migration failed for ${dbPath}: ${err instanceof Error ? err.message : String(err)}`,
4246
- "migration"
4247
- );
4248
- throw err;
4249
- } finally {
4250
- db.close();
4251
- }
4252
- }
4253
- function migrateAllWorkspaces(workspaces) {
4254
- logger.info(`Running migrations for ${workspaces.length} workspace(s)`, "migration");
4255
- let successCount = 0;
4256
- let errorCount = 0;
4257
- for (const workspace of workspaces) {
4258
- const dbPath = `${workspace.rootPath}/.cortask/sessions.db`;
4259
- try {
4260
- migrateSessionDatabase(dbPath);
4261
- successCount++;
4262
- } catch (err) {
4263
- errorCount++;
4264
- logger.error(
4265
- `Failed to migrate workspace ${workspace.rootPath}: ${err instanceof Error ? err.message : String(err)}`,
4266
- "migration"
4267
- );
4268
- }
4269
- }
4270
- logger.info(
4271
- `Migration summary: ${successCount} successful, ${errorCount} failed`,
4272
- "migration"
4273
- );
4274
- if (errorCount > 0) {
4275
- throw new Error(`Migration failed for ${errorCount} workspace(s)`);
4276
- }
4277
- }
4278
-
4279
- // src/skills/loader.ts
4280
- import fs15 from "fs/promises";
4281
- import path14 from "path";
4282
- import matter from "gray-matter";
4283
-
4284
- // src/skills/credential-schema.ts
4285
- import fs14 from "fs/promises";
4286
- import path13 from "path";
4287
- async function loadCredentialSchema(skillDir) {
4288
- const schemaPath = path13.join(skillDir, "credentials.json");
4289
- try {
4290
- const raw = await fs14.readFile(schemaPath, "utf-8");
4291
- const parsed = JSON.parse(raw);
4292
- if (!parsed.credentials || !Array.isArray(parsed.credentials)) {
4293
- return void 0;
4294
- }
4295
- return parsed;
4296
- } catch {
4297
- return void 0;
4298
- }
4299
- }
4300
- function getCredentialStorageKey(skillName, credentialId, fieldKey, storeAs) {
4301
- if (storeAs) {
4302
- return `${storeAs}.${fieldKey}`;
4303
- }
4304
- return `skill.${skillName}.${credentialId}.${fieldKey}`;
4305
- }
4306
- function getInstanceStorageKey(skillName, credentialId, instanceId, fieldKey, storeAs) {
4307
- if (storeAs) {
4308
- return `${storeAs}.${instanceId}.${fieldKey}`;
4309
- }
4310
- return `skill.${skillName}.${credentialId}.${instanceId}.${fieldKey}`;
4311
- }
4312
- function getInstanceRegistryKey(skillName, credentialId, storeAs) {
4313
- if (storeAs) {
4314
- return `${storeAs}._instances`;
4315
- }
4316
- return `skill.${skillName}.${credentialId}._instances`;
4317
- }
4318
- function getOAuth2StorageKeys(skillName, credentialId) {
4319
- const prefix = `skill.${skillName}.${credentialId}.oauth2`;
4320
- return {
4321
- accessToken: `${prefix}.accessToken`,
4322
- refreshToken: `${prefix}.refreshToken`,
4323
- expiresAt: `${prefix}.expiresAt`
4324
- };
4325
- }
4326
-
4327
- // src/skills/loader.ts
4328
- var skillCache = null;
4329
- function clearSkillCache() {
4330
- skillCache = null;
4331
- }
4332
- async function loadSkills(bundledDir, userDir, configDirs, credentialStore) {
4333
- if (skillCache) return skillCache;
4334
- const skills = [];
4335
- if (bundledDir) {
4336
- const entries = await scanSkillDir(bundledDir, "bundled");
4337
- skills.push(...entries);
4338
- }
4339
- if (userDir) {
4340
- const entries = await scanSkillDir(userDir, "user");
4341
- skills.push(...entries);
4342
- }
4343
- for (const dir of configDirs) {
4344
- const entries = await scanSkillDir(dir, "config-dir");
4345
- skills.push(...entries);
4346
- }
4347
- for (const skill of skills) {
4348
- skill.credentialSchema = await loadCredentialSchema(skill.path);
4349
- const result = await checkEligibility(skill, credentialStore);
4350
- skill.eligible = result.eligible;
4351
- skill.ineligibleReason = result.reason;
4352
- skill.credentialStatus = result.credentialStatus;
4353
- if (skill.manifest.install) {
4354
- skill.installOptions = skill.manifest.install.filter((spec) => {
4355
- if (!spec.os) return true;
4356
- return spec.os.includes(process.platform);
4357
- }).map((spec, i) => ({
4358
- id: spec.id ?? `install-${i}`,
4359
- kind: spec.kind,
4360
- label: spec.label ?? `Install via ${spec.kind}`
4361
- }));
4362
- }
4363
- }
4364
- skillCache = skills;
4365
- return skills;
4366
- }
4367
- function getEligibleSkills(skills) {
4368
- return skills.filter((s) => s.eligible);
4369
- }
4370
- async function scanSkillDir(dir, source) {
4371
- const skills = [];
4372
- try {
4373
- const entries = await fs15.readdir(dir, { withFileTypes: true });
4374
- for (const entry of entries) {
4375
- if (!entry.isDirectory()) continue;
4376
- const skillDir = path14.join(dir, entry.name);
4377
- const skillMdPath = path14.join(skillDir, "SKILL.md");
4378
- try {
4379
- const raw = await fs15.readFile(skillMdPath, "utf-8");
4380
- const parsed = matter(raw);
4381
- const manifest = parsed.data;
4382
- if (!manifest.name) {
4383
- manifest.name = entry.name;
4384
- }
4385
- if (!manifest.description) {
4386
- manifest.description = "";
4387
- }
4388
- let skillSource = source;
4389
- try {
4390
- await fs15.access(path14.join(skillDir, ".origin"));
4391
- skillSource = "git";
4392
- } catch {
4393
- }
4394
- let hasCodeTools = false;
4395
- try {
4396
- await fs15.access(path14.join(skillDir, "index.js"));
4397
- hasCodeTools = true;
4398
- } catch {
4399
- }
4400
- skills.push({
4401
- manifest,
4402
- content: parsed.content.trim(),
4403
- path: skillDir,
4404
- eligible: false,
4405
- // Will be set by checkEligibility
4406
- source: skillSource,
4407
- editable: skillSource !== "bundled",
4408
- hasCodeTools
4409
- });
4410
- } catch {
4411
- }
4412
- }
4413
- } catch {
4667
+ this.db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, sessionId);
4668
+ });
4669
+ transaction();
4414
4670
  }
4415
- return skills;
4416
- }
4417
- async function checkEligibility(skill, credentialStore) {
4418
- const { manifest } = skill;
4419
- if (manifest.always) {
4420
- return { eligible: true };
4671
+ getMessages(sessionId) {
4672
+ const rows = this.db.prepare(
4673
+ "SELECT role, content FROM messages WHERE session_id = ? ORDER BY id ASC"
4674
+ ).all(sessionId);
4675
+ return rows.map((r) => ({
4676
+ role: r.role,
4677
+ content: JSON.parse(r.content)
4678
+ }));
4421
4679
  }
4422
- const requiredOs = manifest.compatibility?.os;
4423
- if (requiredOs && !requiredOs.includes(process.platform)) {
4424
- return {
4425
- eligible: false,
4426
- reason: `Requires OS: ${requiredOs.join(", ")} (current: ${process.platform})`
4427
- };
4680
+ deleteSession(id) {
4681
+ this.db.prepare("DELETE FROM sessions WHERE id = ?").run(id);
4428
4682
  }
4429
- if (manifest.requires?.env) {
4430
- for (const envVar of manifest.requires.env) {
4431
- if (!process.env[envVar]) {
4432
- return {
4433
- eligible: false,
4434
- reason: `Missing environment variable: ${envVar}`
4435
- };
4436
- }
4437
- }
4683
+ updateTitle(id, title) {
4684
+ this.db.prepare("UPDATE sessions SET title = ? WHERE id = ?").run(title, id);
4438
4685
  }
4439
- if (manifest.requires?.bins) {
4440
- for (const bin of manifest.requires.bins) {
4441
- if (!await isBinaryAvailable(bin)) {
4442
- return {
4443
- eligible: false,
4444
- reason: `Missing binary: ${bin}`
4445
- };
4446
- }
4447
- }
4686
+ close() {
4687
+ this.db.close();
4448
4688
  }
4449
- if (skill.credentialSchema && credentialStore) {
4450
- const credentialStatus = {};
4451
- let allFulfilled = true;
4452
- for (const cred of skill.credentialSchema.credentials) {
4453
- if (cred.type === "oauth2") {
4454
- const keys = getOAuth2StorageKeys(manifest.name, cred.id);
4455
- const hasToken = await credentialStore.has(keys.accessToken);
4456
- credentialStatus[cred.id] = hasToken;
4457
- if (!hasToken) allFulfilled = false;
4458
- } else if (cred.multiple && cred.fields) {
4459
- const registryKey = getInstanceRegistryKey(manifest.name, cred.id, cred.storeAs);
4460
- const raw = await credentialStore.get(registryKey);
4461
- const instances = raw ? JSON.parse(raw) : [];
4462
- let anyInstanceFulfilled = false;
4463
- for (const instance2 of instances) {
4464
- let instanceOk = true;
4465
- for (const field of cred.fields) {
4466
- if (field.required === false) continue;
4467
- const key = getInstanceStorageKey(
4468
- manifest.name,
4469
- cred.id,
4470
- instance2.id,
4471
- field.key,
4472
- cred.storeAs
4473
- );
4474
- if (!await credentialStore.has(key)) {
4475
- instanceOk = false;
4476
- break;
4477
- }
4478
- }
4479
- if (instanceOk) {
4480
- anyInstanceFulfilled = true;
4481
- break;
4482
- }
4483
- }
4484
- credentialStatus[cred.id] = anyInstanceFulfilled;
4485
- if (!anyInstanceFulfilled) allFulfilled = false;
4486
- } else if (cred.fields) {
4487
- let fieldsFulfilled = true;
4488
- for (const field of cred.fields) {
4489
- if (field.required === false) continue;
4490
- const key = getCredentialStorageKey(
4491
- manifest.name,
4492
- cred.id,
4493
- field.key,
4494
- cred.storeAs
4495
- );
4496
- const exists = await credentialStore.has(key);
4497
- if (!exists) {
4498
- fieldsFulfilled = false;
4499
- break;
4500
- }
4501
- }
4502
- credentialStatus[cred.id] = fieldsFulfilled;
4503
- if (!fieldsFulfilled) allFulfilled = false;
4504
- }
4689
+ };
4690
+
4691
+ // src/session/migrate.ts
4692
+ import Database4 from "better-sqlite3";
4693
+ import fs18 from "fs";
4694
+ function migrateSessionDatabase(dbPath) {
4695
+ if (!fs18.existsSync(dbPath)) {
4696
+ logger.debug(`Session database does not exist yet: ${dbPath}`, "migration");
4697
+ return;
4698
+ }
4699
+ const db = new Database4(dbPath);
4700
+ try {
4701
+ const columns = db.prepare("PRAGMA table_info(sessions)").all();
4702
+ if (columns.length === 0) {
4703
+ logger.debug(`Sessions table does not exist yet in: ${dbPath}`, "migration");
4704
+ db.close();
4705
+ return;
4505
4706
  }
4506
- if (!allFulfilled) {
4507
- return {
4508
- eligible: false,
4509
- reason: "Missing required credentials",
4510
- credentialStatus
4511
- };
4707
+ const hasParentSessionId = columns.some((c) => c.name === "parent_session_id");
4708
+ const hasDepth = columns.some((c) => c.name === "depth");
4709
+ const hasChannel = columns.some((c) => c.name === "channel");
4710
+ let changesMade = false;
4711
+ if (!hasParentSessionId) {
4712
+ logger.info(`Migrating database: adding parent_session_id to ${dbPath}`, "migration");
4713
+ db.exec("ALTER TABLE sessions ADD COLUMN parent_session_id TEXT");
4714
+ changesMade = true;
4715
+ logger.info("\u2713 Added parent_session_id column", "migration");
4512
4716
  }
4513
- return { eligible: true, credentialStatus };
4717
+ if (!hasDepth) {
4718
+ logger.info(`Migrating database: adding depth to ${dbPath}`, "migration");
4719
+ db.exec("ALTER TABLE sessions ADD COLUMN depth INTEGER DEFAULT 0");
4720
+ changesMade = true;
4721
+ logger.info("\u2713 Added depth column", "migration");
4722
+ }
4723
+ if (!hasChannel) {
4724
+ logger.info(`Migrating database: adding channel to ${dbPath}`, "migration");
4725
+ db.exec("ALTER TABLE sessions ADD COLUMN channel TEXT");
4726
+ changesMade = true;
4727
+ logger.info("\u2713 Added channel column", "migration");
4728
+ }
4729
+ if (!hasParentSessionId) {
4730
+ db.exec(
4731
+ "CREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_session_id)"
4732
+ );
4733
+ logger.info("\u2713 Created parent session index", "migration");
4734
+ }
4735
+ if (changesMade) {
4736
+ logger.info(`Migration complete for: ${dbPath}`, "migration");
4737
+ } else {
4738
+ logger.debug(`No migration needed for: ${dbPath}`, "migration");
4739
+ }
4740
+ } catch (err) {
4741
+ logger.error(
4742
+ `Migration failed for ${dbPath}: ${err instanceof Error ? err.message : String(err)}`,
4743
+ "migration"
4744
+ );
4745
+ throw err;
4746
+ } finally {
4747
+ db.close();
4514
4748
  }
4515
- return { eligible: true };
4516
4749
  }
4517
- async function isBinaryAvailable(name) {
4518
- const { exec: exec4 } = await import("child_process");
4519
- const cmd = process.platform === "win32" ? `where ${name}` : `which ${name}`;
4520
- return new Promise((resolve) => {
4521
- exec4(cmd, (error) => {
4522
- resolve(!error);
4523
- });
4524
- });
4750
+ function migrateAllWorkspaces(workspaces) {
4751
+ logger.info(`Running migrations for ${workspaces.length} workspace(s)`, "migration");
4752
+ let successCount = 0;
4753
+ let errorCount = 0;
4754
+ for (const workspace of workspaces) {
4755
+ const dbPath = `${workspace.rootPath}/.cortask/sessions.db`;
4756
+ try {
4757
+ migrateSessionDatabase(dbPath);
4758
+ successCount++;
4759
+ } catch (err) {
4760
+ errorCount++;
4761
+ logger.error(
4762
+ `Failed to migrate workspace ${workspace.rootPath}: ${err instanceof Error ? err.message : String(err)}`,
4763
+ "migration"
4764
+ );
4765
+ }
4766
+ }
4767
+ logger.info(
4768
+ `Migration summary: ${successCount} successful, ${errorCount} failed`,
4769
+ "migration"
4770
+ );
4771
+ if (errorCount > 0) {
4772
+ throw new Error(`Migration failed for ${errorCount} workspace(s)`);
4773
+ }
4525
4774
  }
4526
4775
 
4527
4776
  // src/skills/tools.ts
4528
- import fs16 from "fs/promises";
4529
- import path15 from "path";
4777
+ import fs19 from "fs/promises";
4778
+ import path17 from "path";
4530
4779
  async function buildSkillTools(skills, credentialStore) {
4531
4780
  const toolDefs = [];
4532
4781
  const toolNames = /* @__PURE__ */ new Set();
@@ -4655,13 +4904,13 @@ async function executeHttpTool(template, args, skill, credentialStore, workspace
4655
4904
  isError: true
4656
4905
  };
4657
4906
  }
4658
- const tempDir = path15.join(workspacePath, "_temp");
4659
- await fs16.mkdir(tempDir, { recursive: true });
4907
+ const tempDir = path17.join(workspacePath, "_temp");
4908
+ await fs19.mkdir(tempDir, { recursive: true });
4660
4909
  const isJson = looksLikeJson(content);
4661
4910
  const ext = isJson ? "json" : "txt";
4662
4911
  const filename = `${template.name}_${Date.now()}.${ext}`;
4663
- const filePath = path15.join(tempDir, filename);
4664
- await fs16.writeFile(filePath, content, "utf-8");
4912
+ const filePath = path17.join(tempDir, filename);
4913
+ await fs19.writeFile(filePath, content, "utf-8");
4665
4914
  const summary = buildResponseSummary(content, isJson);
4666
4915
  return {
4667
4916
  toolCallId: "",
@@ -4795,71 +5044,6 @@ function resolvePlaceholders(template, args, credValues) {
4795
5044
  });
4796
5045
  }
4797
5046
 
4798
- // src/skills/installer.ts
4799
- import { exec as exec3 } from "child_process";
4800
- import fs17 from "fs/promises";
4801
- import path16 from "path";
4802
- async function installSkillFromGit(gitUrl, skillsDir) {
4803
- await fs17.mkdir(skillsDir, { recursive: true });
4804
- const repoName = gitUrl.split("/").pop()?.replace(/\.git$/, "");
4805
- if (!repoName) {
4806
- throw new Error(`Invalid git URL: ${gitUrl}`);
4807
- }
4808
- const targetDir = path16.join(skillsDir, repoName);
4809
- try {
4810
- await fs17.access(targetDir);
4811
- throw new Error(`Skill "${repoName}" is already installed`);
4812
- } catch (err) {
4813
- if (err.message.includes("already installed")) throw err;
4814
- }
4815
- await execAsync(`git clone --depth 1 "${gitUrl}" "${targetDir}"`);
4816
- try {
4817
- await fs17.access(path16.join(targetDir, "SKILL.md"));
4818
- } catch {
4819
- await fs17.rm(targetDir, { recursive: true, force: true });
4820
- throw new Error(
4821
- `Invalid skill: no SKILL.md found in ${repoName}`
4822
- );
4823
- }
4824
- await fs17.writeFile(
4825
- path16.join(targetDir, ".origin"),
4826
- gitUrl,
4827
- "utf-8"
4828
- );
4829
- try {
4830
- await fs17.rm(path16.join(targetDir, ".git"), {
4831
- recursive: true,
4832
- force: true
4833
- });
4834
- } catch {
4835
- }
4836
- clearSkillCache();
4837
- logger.info(`Installed skill "${repoName}" from ${gitUrl}`, "skills");
4838
- return { name: repoName, path: targetDir };
4839
- }
4840
- async function removeSkill(skillName, skillsDir) {
4841
- const targetDir = path16.join(skillsDir, skillName);
4842
- try {
4843
- await fs17.access(targetDir);
4844
- } catch {
4845
- throw new Error(`Skill "${skillName}" not found`);
4846
- }
4847
- await fs17.rm(targetDir, { recursive: true, force: true });
4848
- clearSkillCache();
4849
- logger.info(`Removed skill "${skillName}"`, "skills");
4850
- }
4851
- function execAsync(command) {
4852
- return new Promise((resolve, reject) => {
4853
- exec3(command, { timeout: 6e4 }, (error, stdout, stderr) => {
4854
- if (error) {
4855
- reject(new Error(stderr || error.message));
4856
- return;
4857
- }
4858
- resolve(stdout);
4859
- });
4860
- });
4861
- }
4862
-
4863
5047
  // src/skills/oauth2.ts
4864
5048
  function buildSkillOAuth2AuthUrl(skillName, oauth, clientId, redirectUri, state) {
4865
5049
  const params = new URLSearchParams({
@@ -5594,6 +5778,8 @@ export {
5594
5778
  createBrowserTool,
5595
5779
  createCronTool,
5596
5780
  createProvider,
5781
+ createSkill,
5782
+ createSkillTool,
5597
5783
  createSubagentTool,
5598
5784
  createSwitchWorkspaceTool,
5599
5785
  credentialKey,
@@ -5614,12 +5800,15 @@ export {
5614
5800
  logger,
5615
5801
  migrateAllWorkspaces,
5616
5802
  migrateSessionDatabase,
5803
+ readSkillFile,
5617
5804
  registerSubagentRun,
5618
5805
  removeSkill,
5619
5806
  revokeSkillOAuth2,
5620
5807
  saveConfig,
5621
5808
  setSubagentRunner,
5809
+ updateSkill,
5622
5810
  validateCronExpr,
5623
- validateProvider
5811
+ validateProvider,
5812
+ validateSkillName
5624
5813
  };
5625
5814
  //# sourceMappingURL=index.js.map