@hasna/skills 0.1.41 → 0.1.42
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/bin/index.js +899 -217
- package/bin/mcp.js +4 -3
- package/dist/index.js +4 -3
- package/package.json +2 -1
- package/skills/apidocs/.claude/settings.json +0 -5
package/bin/index.js
CHANGED
|
@@ -861,7 +861,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
861
861
|
this._exitCallback = (err) => {
|
|
862
862
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
863
863
|
throw err;
|
|
864
|
-
}
|
|
864
|
+
}
|
|
865
865
|
};
|
|
866
866
|
}
|
|
867
867
|
return this;
|
|
@@ -1910,7 +1910,7 @@ var package_default;
|
|
|
1910
1910
|
var init_package = __esm(() => {
|
|
1911
1911
|
package_default = {
|
|
1912
1912
|
name: "@hasna/skills",
|
|
1913
|
-
version: "0.1.
|
|
1913
|
+
version: "0.1.42",
|
|
1914
1914
|
description: "Skills library for AI coding agents",
|
|
1915
1915
|
type: "module",
|
|
1916
1916
|
bin: {
|
|
@@ -1973,6 +1973,7 @@ var init_package = __esm(() => {
|
|
|
1973
1973
|
typescript: "^5"
|
|
1974
1974
|
},
|
|
1975
1975
|
dependencies: {
|
|
1976
|
+
"@hasna/events": "^0.1.3",
|
|
1976
1977
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
1977
1978
|
chalk: "^5.3.0",
|
|
1978
1979
|
commander: "^12.1.0",
|
|
@@ -4061,9 +4062,9 @@ __export(exports_registry, {
|
|
|
4061
4062
|
CATEGORIES: () => CATEGORIES,
|
|
4062
4063
|
BASIC_SKILL_NAMES: () => BASIC_SKILL_NAMES
|
|
4063
4064
|
});
|
|
4064
|
-
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
4065
|
-
import { homedir } from "os";
|
|
4066
|
-
import { join } from "path";
|
|
4065
|
+
import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
|
|
4066
|
+
import { homedir as homedir2 } from "os";
|
|
4067
|
+
import { join as join2 } from "path";
|
|
4067
4068
|
function isBasicSkillName(name) {
|
|
4068
4069
|
return BASIC_SKILL_NAMES.includes(name);
|
|
4069
4070
|
}
|
|
@@ -4096,7 +4097,7 @@ function parseSkillMdFrontmatter(content) {
|
|
|
4096
4097
|
return Object.keys(result).length > 0 ? result : null;
|
|
4097
4098
|
}
|
|
4098
4099
|
function discoverSkillsInDir(dir) {
|
|
4099
|
-
if (!
|
|
4100
|
+
if (!existsSync2(dir))
|
|
4100
4101
|
return [];
|
|
4101
4102
|
const result = [];
|
|
4102
4103
|
try {
|
|
@@ -4104,8 +4105,8 @@ function discoverSkillsInDir(dir) {
|
|
|
4104
4105
|
for (const entry of entries) {
|
|
4105
4106
|
if (!entry.isDirectory())
|
|
4106
4107
|
continue;
|
|
4107
|
-
const skillMdPath =
|
|
4108
|
-
if (!
|
|
4108
|
+
const skillMdPath = join2(dir, entry.name, "SKILL.md");
|
|
4109
|
+
if (!existsSync2(skillMdPath))
|
|
4109
4110
|
continue;
|
|
4110
4111
|
let content;
|
|
4111
4112
|
try {
|
|
@@ -4130,16 +4131,16 @@ function discoverSkillsInDir(dir) {
|
|
|
4130
4131
|
return result;
|
|
4131
4132
|
}
|
|
4132
4133
|
function loadRegistry(cwd) {
|
|
4133
|
-
const
|
|
4134
|
-
if (registryCache &&
|
|
4134
|
+
const now2 = Date.now();
|
|
4135
|
+
if (registryCache && now2 - registryCacheTime < REGISTRY_CACHE_TTL) {
|
|
4135
4136
|
return registryCache;
|
|
4136
4137
|
}
|
|
4137
4138
|
const official = SKILLS.map((s) => ({ ...s, source: "official" }));
|
|
4138
|
-
const globalCustom = discoverSkillsInDir(
|
|
4139
|
+
const globalCustom = discoverSkillsInDir(join2(homedir2(), ".hasna", "skills", "custom"));
|
|
4139
4140
|
const customNames = new Set(globalCustom.map((s) => s.name));
|
|
4140
4141
|
const filtered = official.filter((s) => !customNames.has(s.name));
|
|
4141
4142
|
registryCache = [...filtered, ...globalCustom];
|
|
4142
|
-
registryCacheTime =
|
|
4143
|
+
registryCacheTime = now2;
|
|
4143
4144
|
return registryCache;
|
|
4144
4145
|
}
|
|
4145
4146
|
function loadBasicRegistry(cwd) {
|
|
@@ -5828,17 +5829,17 @@ function normalizeSkillName(name) {
|
|
|
5828
5829
|
}
|
|
5829
5830
|
|
|
5830
5831
|
// src/lib/project-state.ts
|
|
5831
|
-
import { existsSync as
|
|
5832
|
-
import { join as
|
|
5832
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
5833
|
+
import { join as join3 } from "path";
|
|
5833
5834
|
function getProjectStateDir(targetDir = process.cwd()) {
|
|
5834
|
-
return
|
|
5835
|
+
return join3(targetDir, SKILLS_PROJECT_DIR);
|
|
5835
5836
|
}
|
|
5836
5837
|
function getProjectConfigPath(targetDir = process.cwd()) {
|
|
5837
|
-
return
|
|
5838
|
+
return join3(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
|
|
5838
5839
|
}
|
|
5839
5840
|
function loadProjectConfig(targetDir = process.cwd()) {
|
|
5840
5841
|
const path = getProjectConfigPath(targetDir);
|
|
5841
|
-
if (!
|
|
5842
|
+
if (!existsSync3(path))
|
|
5842
5843
|
return null;
|
|
5843
5844
|
try {
|
|
5844
5845
|
return normalizeProjectConfig(JSON.parse(readFileSync2(path, "utf-8")));
|
|
@@ -5850,14 +5851,14 @@ function ensureProjectConfig(targetDir = process.cwd()) {
|
|
|
5850
5851
|
const existing = loadProjectConfig(targetDir);
|
|
5851
5852
|
if (existing)
|
|
5852
5853
|
return existing;
|
|
5853
|
-
const
|
|
5854
|
+
const now2 = new Date().toISOString();
|
|
5854
5855
|
return {
|
|
5855
5856
|
version: 1,
|
|
5856
5857
|
defaultExportDir: DEFAULT_EXPORT_DIR,
|
|
5857
5858
|
pinnedSkills: [],
|
|
5858
5859
|
pins: {},
|
|
5859
|
-
createdAt:
|
|
5860
|
-
updatedAt:
|
|
5860
|
+
createdAt: now2,
|
|
5861
|
+
updatedAt: now2
|
|
5861
5862
|
};
|
|
5862
5863
|
}
|
|
5863
5864
|
function saveProjectConfig(config, targetDir = process.cwd()) {
|
|
@@ -5927,7 +5928,7 @@ function getDisabledProjectSkills(targetDir = process.cwd()) {
|
|
|
5927
5928
|
return loadProjectConfig(targetDir)?.disabledSkills ?? [];
|
|
5928
5929
|
}
|
|
5929
5930
|
function normalizeProjectConfig(raw) {
|
|
5930
|
-
const
|
|
5931
|
+
const now2 = new Date().toISOString();
|
|
5931
5932
|
const pinnedSkills = Array.isArray(raw.pinnedSkills) ? [...new Set(raw.pinnedSkills.map((name) => normalizeSkillName(String(name))))].sort() : [];
|
|
5932
5933
|
const pins = {};
|
|
5933
5934
|
const rawPins = raw.pins && typeof raw.pins === "object" ? raw.pins : {};
|
|
@@ -5935,7 +5936,7 @@ function normalizeProjectConfig(raw) {
|
|
|
5935
5936
|
const pin = rawPins[name];
|
|
5936
5937
|
pins[name] = {
|
|
5937
5938
|
name,
|
|
5938
|
-
pinnedAt: typeof pin?.pinnedAt === "string" ? pin.pinnedAt :
|
|
5939
|
+
pinnedAt: typeof pin?.pinnedAt === "string" ? pin.pinnedAt : now2,
|
|
5939
5940
|
version: typeof pin?.version === "string" ? pin.version : "unknown",
|
|
5940
5941
|
source: isPinSource(pin?.source) ? pin.source : "official"
|
|
5941
5942
|
};
|
|
@@ -5946,8 +5947,8 @@ function normalizeProjectConfig(raw) {
|
|
|
5946
5947
|
pinnedSkills,
|
|
5947
5948
|
pins,
|
|
5948
5949
|
disabledSkills: Array.isArray(raw.disabledSkills) ? [...new Set(raw.disabledSkills.map((name) => normalizeSkillName(String(name))))].sort() : [],
|
|
5949
|
-
createdAt: typeof raw.createdAt === "string" ? raw.createdAt :
|
|
5950
|
-
updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt :
|
|
5950
|
+
createdAt: typeof raw.createdAt === "string" ? raw.createdAt : now2,
|
|
5951
|
+
updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : now2
|
|
5951
5952
|
};
|
|
5952
5953
|
}
|
|
5953
5954
|
function isPinSource(value) {
|
|
@@ -5984,35 +5985,35 @@ __export(exports_installer, {
|
|
|
5984
5985
|
AGENT_TARGETS: () => AGENT_TARGETS,
|
|
5985
5986
|
AGENT_LABELS: () => AGENT_LABELS
|
|
5986
5987
|
});
|
|
5987
|
-
import { existsSync as
|
|
5988
|
-
import { dirname, join as
|
|
5989
|
-
import { homedir as
|
|
5988
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
5989
|
+
import { dirname, join as join4 } from "path";
|
|
5990
|
+
import { homedir as homedir3 } from "os";
|
|
5990
5991
|
import { fileURLToPath } from "url";
|
|
5991
5992
|
function findSkillsDir() {
|
|
5992
5993
|
let dir = __dirname2;
|
|
5993
5994
|
for (let i = 0;i < 5; i++) {
|
|
5994
|
-
const candidate =
|
|
5995
|
-
if (
|
|
5995
|
+
const candidate = join4(dir, "skills");
|
|
5996
|
+
if (existsSync4(candidate) && !dir.includes(".skills"))
|
|
5996
5997
|
return candidate;
|
|
5997
5998
|
dir = dirname(dir);
|
|
5998
5999
|
}
|
|
5999
|
-
return
|
|
6000
|
+
return join4(__dirname2, "..", "skills");
|
|
6000
6001
|
}
|
|
6001
6002
|
function getSkillPath(name) {
|
|
6002
6003
|
const skillName = normalizeSkillName(getCanonicalSkillName(name));
|
|
6003
|
-
return
|
|
6004
|
+
return join4(SKILLS_DIR, skillName);
|
|
6004
6005
|
}
|
|
6005
6006
|
function getCanonicalSkillName(name) {
|
|
6006
6007
|
return getSkill(name)?.name ?? resolveSkillAlias(normalizeSkillSlug(name));
|
|
6007
6008
|
}
|
|
6008
6009
|
function skillExists(name) {
|
|
6009
|
-
return
|
|
6010
|
+
return existsSync4(getSkillPath(name));
|
|
6010
6011
|
}
|
|
6011
6012
|
function installSkill(name, options = {}) {
|
|
6012
6013
|
const { targetDir = process.cwd(), overwrite = false } = options;
|
|
6013
6014
|
const canonicalName = getCanonicalSkillName(name);
|
|
6014
6015
|
const skillName = normalizeSkillName(canonicalName);
|
|
6015
|
-
if (!
|
|
6016
|
+
if (!existsSync4(getSkillPath(name))) {
|
|
6016
6017
|
return { skill: canonicalName, success: false, error: `Skill '${name}' not found`, mode: "pin" };
|
|
6017
6018
|
}
|
|
6018
6019
|
const existing = new Set(listPinnedSkills(targetDir));
|
|
@@ -6056,7 +6057,7 @@ function installRemoteSkill(skill, options = {}) {
|
|
|
6056
6057
|
}
|
|
6057
6058
|
function installSkillSource(name, _options = {}) {
|
|
6058
6059
|
const canonicalName = getCanonicalSkillName(name);
|
|
6059
|
-
if (!
|
|
6060
|
+
if (!existsSync4(getSkillPath(name))) {
|
|
6060
6061
|
return { skill: canonicalName, success: false, error: `Skill '${name}' not found`, mode: "source" };
|
|
6061
6062
|
}
|
|
6062
6063
|
return {
|
|
@@ -6077,11 +6078,11 @@ function installSkillManifest(manifest, _options = {}) {
|
|
|
6077
6078
|
}
|
|
6078
6079
|
function createLocalSkillManifest(name, generateSkillMd) {
|
|
6079
6080
|
const sourcePath = getSkillPath(name);
|
|
6080
|
-
if (!
|
|
6081
|
+
if (!existsSync4(sourcePath))
|
|
6081
6082
|
return null;
|
|
6082
6083
|
let skillMd = "";
|
|
6083
|
-
const skillMdPath =
|
|
6084
|
-
if (
|
|
6084
|
+
const skillMdPath = join4(sourcePath, "SKILL.md");
|
|
6085
|
+
if (existsSync4(skillMdPath)) {
|
|
6085
6086
|
skillMd = readFileSync3(skillMdPath, "utf-8");
|
|
6086
6087
|
} else if (generateSkillMd) {
|
|
6087
6088
|
skillMd = generateSkillMd(name) ?? "";
|
|
@@ -6162,20 +6163,20 @@ function getAgentSkillsDir(agent, scope = "global", projectDir) {
|
|
|
6162
6163
|
const base = projectDir || process.cwd();
|
|
6163
6164
|
switch (agent) {
|
|
6164
6165
|
case "pi":
|
|
6165
|
-
return scope === "project" ?
|
|
6166
|
+
return scope === "project" ? join4(base, ".pi", "skills") : join4(homedir3(), ".pi", "agent", "skills");
|
|
6166
6167
|
case "opencode":
|
|
6167
|
-
return scope === "project" ?
|
|
6168
|
+
return scope === "project" ? join4(base, ".opencode", "skills") : join4(homedir3(), ".config", "opencode", "skills");
|
|
6168
6169
|
default:
|
|
6169
|
-
return scope === "project" ?
|
|
6170
|
+
return scope === "project" ? join4(base, `.${agent}`, "skills") : join4(homedir3(), `.${agent}`, "skills");
|
|
6170
6171
|
}
|
|
6171
6172
|
}
|
|
6172
6173
|
function getAgentSkillPath(name, agent, scope = "global", projectDir) {
|
|
6173
6174
|
const skillName = normalizeSkillName(getCanonicalSkillName(name));
|
|
6174
|
-
return
|
|
6175
|
+
return join4(getAgentSkillsDir(agent, scope, projectDir), skillName);
|
|
6175
6176
|
}
|
|
6176
6177
|
function installSkillForAgent(name, options, _generateSkillMd) {
|
|
6177
6178
|
const canonicalName = getCanonicalSkillName(name);
|
|
6178
|
-
if (!
|
|
6179
|
+
if (!existsSync4(getSkillPath(name))) {
|
|
6179
6180
|
return { skill: canonicalName, success: false, error: `Skill '${name}' not found` };
|
|
6180
6181
|
}
|
|
6181
6182
|
return {
|
|
@@ -6200,7 +6201,7 @@ function warnMissingDependencies(name, targetDir) {
|
|
|
6200
6201
|
}
|
|
6201
6202
|
function generateMinimalSkillMd(name) {
|
|
6202
6203
|
const sourcePath = getSkillPath(name);
|
|
6203
|
-
if (!
|
|
6204
|
+
if (!existsSync4(sourcePath))
|
|
6204
6205
|
return null;
|
|
6205
6206
|
const canonicalName = getCanonicalSkillName(name);
|
|
6206
6207
|
const meta = getSkill(canonicalName);
|
|
@@ -6215,7 +6216,7 @@ function generateMinimalSkillMd(name) {
|
|
|
6215
6216
|
"---",
|
|
6216
6217
|
""
|
|
6217
6218
|
].filter(Boolean);
|
|
6218
|
-
const fallbackDoc = readFileIfExists(
|
|
6219
|
+
const fallbackDoc = readFileIfExists(join4(sourcePath, "README.md")) || readFileIfExists(join4(sourcePath, "CLAUDE.md"));
|
|
6219
6220
|
if (fallbackDoc)
|
|
6220
6221
|
return `${frontmatter.join(`
|
|
6221
6222
|
`)}${fallbackDoc.trim()}
|
|
@@ -6234,8 +6235,8 @@ skills run ${canonicalName}
|
|
|
6234
6235
|
`;
|
|
6235
6236
|
}
|
|
6236
6237
|
function readBundledSkillVersion(name) {
|
|
6237
|
-
const pkgPath =
|
|
6238
|
-
if (!
|
|
6238
|
+
const pkgPath = join4(getSkillPath(name), "package.json");
|
|
6239
|
+
if (!existsSync4(pkgPath))
|
|
6239
6240
|
return "unknown";
|
|
6240
6241
|
try {
|
|
6241
6242
|
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
@@ -6245,7 +6246,7 @@ function readBundledSkillVersion(name) {
|
|
|
6245
6246
|
}
|
|
6246
6247
|
}
|
|
6247
6248
|
function readFileIfExists(path) {
|
|
6248
|
-
return
|
|
6249
|
+
return existsSync4(path) ? readFileSync3(path, "utf-8") : null;
|
|
6249
6250
|
}
|
|
6250
6251
|
function loadProjectConfigCompat(targetDir) {
|
|
6251
6252
|
return loadProjectConfig(targetDir);
|
|
@@ -6879,9 +6880,9 @@ var init_discovery = __esm(() => {
|
|
|
6879
6880
|
});
|
|
6880
6881
|
|
|
6881
6882
|
// src/lib/config.ts
|
|
6882
|
-
import { existsSync as
|
|
6883
|
-
import { join as
|
|
6884
|
-
import { homedir as
|
|
6883
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, copyFileSync } from "fs";
|
|
6884
|
+
import { join as join5, dirname as dirname2 } from "path";
|
|
6885
|
+
import { homedir as homedir4 } from "os";
|
|
6885
6886
|
function validKeys() {
|
|
6886
6887
|
return ["mode", ...Object.keys(ENUM_KEYS), ...STRING_KEYS];
|
|
6887
6888
|
}
|
|
@@ -6911,13 +6912,13 @@ function normalizeConfigValue(key, value) {
|
|
|
6911
6912
|
return;
|
|
6912
6913
|
}
|
|
6913
6914
|
function getDataDir() {
|
|
6914
|
-
const home = process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
6915
|
-
const newDir =
|
|
6916
|
-
const oldConfigFile =
|
|
6917
|
-
if (
|
|
6915
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir4();
|
|
6916
|
+
const newDir = join5(home, ".hasna", "skills");
|
|
6917
|
+
const oldConfigFile = join5(home, ".skillsrc");
|
|
6918
|
+
if (existsSync5(oldConfigFile) && !existsSync5(join5(newDir, "config.json"))) {
|
|
6918
6919
|
mkdirSync2(newDir, { recursive: true });
|
|
6919
6920
|
try {
|
|
6920
|
-
copyFileSync(oldConfigFile,
|
|
6921
|
+
copyFileSync(oldConfigFile, join5(newDir, "config.json"));
|
|
6921
6922
|
} catch {}
|
|
6922
6923
|
}
|
|
6923
6924
|
mkdirSync2(newDir, { recursive: true });
|
|
@@ -6925,12 +6926,12 @@ function getDataDir() {
|
|
|
6925
6926
|
}
|
|
6926
6927
|
function getConfigPath(scope) {
|
|
6927
6928
|
if (scope === "global") {
|
|
6928
|
-
return
|
|
6929
|
+
return join5(getDataDir(), "config.json");
|
|
6929
6930
|
}
|
|
6930
|
-
return
|
|
6931
|
+
return join5(process.cwd(), "skills.config.json");
|
|
6931
6932
|
}
|
|
6932
6933
|
function readConfigFile(path) {
|
|
6933
|
-
if (!
|
|
6934
|
+
if (!existsSync5(path))
|
|
6934
6935
|
return {};
|
|
6935
6936
|
try {
|
|
6936
6937
|
const raw = readFileSync4(path, "utf-8");
|
|
@@ -6964,7 +6965,7 @@ function saveConfig(key, value, scope = "project") {
|
|
|
6964
6965
|
}
|
|
6965
6966
|
const filePath = getConfigPath(scope);
|
|
6966
6967
|
let existing = {};
|
|
6967
|
-
if (
|
|
6968
|
+
if (existsSync5(filePath)) {
|
|
6968
6969
|
try {
|
|
6969
6970
|
existing = JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
6970
6971
|
if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
|
|
@@ -6975,7 +6976,7 @@ function saveConfig(key, value, scope = "project") {
|
|
|
6975
6976
|
}
|
|
6976
6977
|
} else {
|
|
6977
6978
|
const dir = dirname2(filePath);
|
|
6978
|
-
if (!
|
|
6979
|
+
if (!existsSync5(dir)) {
|
|
6979
6980
|
mkdirSync2(dir, { recursive: true });
|
|
6980
6981
|
}
|
|
6981
6982
|
}
|
|
@@ -17698,7 +17699,7 @@ function finalize(ctx, schema) {
|
|
|
17698
17699
|
result.$schema = "http://json-schema.org/draft-07/schema#";
|
|
17699
17700
|
} else if (ctx.target === "draft-04") {
|
|
17700
17701
|
result.$schema = "http://json-schema.org/draft-04/schema#";
|
|
17701
|
-
} else if (ctx.target === "openapi-3.0") {}
|
|
17702
|
+
} else if (ctx.target === "openapi-3.0") {}
|
|
17702
17703
|
if (ctx.external?.uri) {
|
|
17703
17704
|
const id = ctx.external.registry.get(schema)?.id;
|
|
17704
17705
|
if (!id)
|
|
@@ -17963,7 +17964,7 @@ var formatMap, stringProcessor = (schema, ctx, _json, _params) => {
|
|
|
17963
17964
|
if (val === undefined) {
|
|
17964
17965
|
if (ctx.unrepresentable === "throw") {
|
|
17965
17966
|
throw new Error("Literal `undefined` cannot be represented in JSON Schema");
|
|
17966
|
-
}
|
|
17967
|
+
}
|
|
17967
17968
|
} else if (typeof val === "bigint") {
|
|
17968
17969
|
if (ctx.unrepresentable === "throw") {
|
|
17969
17970
|
throw new Error("BigInt literals cannot be represented in JSON Schema");
|
|
@@ -20878,14 +20879,14 @@ __export(exports_auth_store, {
|
|
|
20878
20879
|
getApiKey: () => getApiKey,
|
|
20879
20880
|
clearAuthConfig: () => clearAuthConfig
|
|
20880
20881
|
});
|
|
20881
|
-
import { existsSync as
|
|
20882
|
-
import { join as
|
|
20883
|
-
import { homedir as
|
|
20882
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync } from "fs";
|
|
20883
|
+
import { join as join6 } from "path";
|
|
20884
|
+
import { homedir as homedir5 } from "os";
|
|
20884
20885
|
function getAuthConfig() {
|
|
20885
20886
|
if (cachedConfig !== undefined)
|
|
20886
20887
|
return cachedConfig;
|
|
20887
20888
|
try {
|
|
20888
|
-
const raw = readFileSync5(
|
|
20889
|
+
const raw = readFileSync5(existsSync6(AUTH_FILE) ? AUTH_FILE : LEGACY_AUTH_FILE, "utf-8");
|
|
20889
20890
|
const config2 = JSON.parse(raw);
|
|
20890
20891
|
if (!config2.apiKey || !config2.email) {
|
|
20891
20892
|
cachedConfig = null;
|
|
@@ -20938,9 +20939,9 @@ function getApiUrl() {
|
|
|
20938
20939
|
var AUTH_DIR, AUTH_FILE, LEGACY_AUTH_FILE, cachedConfig;
|
|
20939
20940
|
var init_auth_store = __esm(() => {
|
|
20940
20941
|
init_config();
|
|
20941
|
-
AUTH_DIR =
|
|
20942
|
-
AUTH_FILE =
|
|
20943
|
-
LEGACY_AUTH_FILE =
|
|
20942
|
+
AUTH_DIR = join6(homedir5(), ".hasna", "skills");
|
|
20943
|
+
AUTH_FILE = join6(AUTH_DIR, "auth.json");
|
|
20944
|
+
LEGACY_AUTH_FILE = join6(homedir5(), ".skills", "auth.json");
|
|
20944
20945
|
});
|
|
20945
20946
|
|
|
20946
20947
|
// src/lib/remote-registry.ts
|
|
@@ -21591,16 +21592,16 @@ __export(exports_skillinfo, {
|
|
|
21591
21592
|
generateEnvExample: () => generateEnvExample,
|
|
21592
21593
|
detectProjectSkills: () => detectProjectSkills
|
|
21593
21594
|
});
|
|
21594
|
-
import { existsSync as
|
|
21595
|
-
import { join as
|
|
21595
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
21596
|
+
import { join as join7 } from "path";
|
|
21596
21597
|
function getSkillDocs(name) {
|
|
21597
21598
|
const skillPath = getSkillPath(name);
|
|
21598
|
-
if (!
|
|
21599
|
+
if (!existsSync7(skillPath))
|
|
21599
21600
|
return null;
|
|
21600
21601
|
return {
|
|
21601
|
-
skillMd: readIfExists(
|
|
21602
|
-
readme: readIfExists(
|
|
21603
|
-
claudeMd: readIfExists(
|
|
21602
|
+
skillMd: readIfExists(join7(skillPath, "SKILL.md")),
|
|
21603
|
+
readme: readIfExists(join7(skillPath, "README.md")),
|
|
21604
|
+
claudeMd: readIfExists(join7(skillPath, "CLAUDE.md"))
|
|
21604
21605
|
};
|
|
21605
21606
|
}
|
|
21606
21607
|
function getSkillBestDoc(name) {
|
|
@@ -21611,11 +21612,11 @@ function getSkillBestDoc(name) {
|
|
|
21611
21612
|
}
|
|
21612
21613
|
function getSkillRequirements(name) {
|
|
21613
21614
|
const skillPath = getSkillPath(name);
|
|
21614
|
-
if (!
|
|
21615
|
+
if (!existsSync7(skillPath))
|
|
21615
21616
|
return null;
|
|
21616
21617
|
const texts = [];
|
|
21617
21618
|
for (const file2 of ["SKILL.md", "README.md", "CLAUDE.md", ".env.example", ".env.local.example"]) {
|
|
21618
|
-
const content = readIfExists(
|
|
21619
|
+
const content = readIfExists(join7(skillPath, file2));
|
|
21619
21620
|
if (content)
|
|
21620
21621
|
texts.push(content);
|
|
21621
21622
|
}
|
|
@@ -21654,8 +21655,8 @@ function getSkillRequirements(name) {
|
|
|
21654
21655
|
const skillName = normalizeSkillName(name);
|
|
21655
21656
|
let cliCommand = `skills run ${skillName}`;
|
|
21656
21657
|
let dependencies = {};
|
|
21657
|
-
const pkgPath =
|
|
21658
|
-
if (
|
|
21658
|
+
const pkgPath = join7(skillPath, "package.json");
|
|
21659
|
+
if (existsSync7(pkgPath)) {
|
|
21659
21660
|
try {
|
|
21660
21661
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
21661
21662
|
dependencies = pkg.dependencies || {};
|
|
@@ -21674,11 +21675,11 @@ function isHostedPremiumSkill(skillName, meta3) {
|
|
|
21674
21675
|
async function runSkill(name, args, options = {}) {
|
|
21675
21676
|
const canonicalName = getSkill(name)?.name ?? name;
|
|
21676
21677
|
const skillPath = getSkillPath(canonicalName);
|
|
21677
|
-
if (!
|
|
21678
|
+
if (!existsSync7(skillPath)) {
|
|
21678
21679
|
return { exitCode: 1, error: `Skill '${name}' not found` };
|
|
21679
21680
|
}
|
|
21680
|
-
const pkgPath =
|
|
21681
|
-
if (!
|
|
21681
|
+
const pkgPath = join7(skillPath, "package.json");
|
|
21682
|
+
if (!existsSync7(pkgPath)) {
|
|
21682
21683
|
return { exitCode: 1, error: `No package.json in skill '${name}'` };
|
|
21683
21684
|
}
|
|
21684
21685
|
let entryPoint;
|
|
@@ -21697,12 +21698,12 @@ async function runSkill(name, args, options = {}) {
|
|
|
21697
21698
|
} catch {
|
|
21698
21699
|
return { exitCode: 1, error: `Failed to parse package.json for skill '${name}'` };
|
|
21699
21700
|
}
|
|
21700
|
-
const entryPath =
|
|
21701
|
-
if (!
|
|
21701
|
+
const entryPath = join7(skillPath, entryPoint);
|
|
21702
|
+
if (!existsSync7(entryPath)) {
|
|
21702
21703
|
return { exitCode: 1, error: `Entry point '${entryPoint}' not found in skill '${name}'` };
|
|
21703
21704
|
}
|
|
21704
|
-
const nodeModules =
|
|
21705
|
-
if (!
|
|
21705
|
+
const nodeModules = join7(skillPath, "node_modules");
|
|
21706
|
+
if (!existsSync7(nodeModules)) {
|
|
21706
21707
|
const install = Bun.spawn(["bun", "install", "--no-save"], {
|
|
21707
21708
|
cwd: skillPath,
|
|
21708
21709
|
stdout: "pipe",
|
|
@@ -21729,8 +21730,8 @@ async function runSkill(name, args, options = {}) {
|
|
|
21729
21730
|
return { exitCode };
|
|
21730
21731
|
}
|
|
21731
21732
|
function detectProjectSkills(cwd = process.cwd()) {
|
|
21732
|
-
const pkgPath =
|
|
21733
|
-
if (!
|
|
21733
|
+
const pkgPath = join7(cwd, "package.json");
|
|
21734
|
+
if (!existsSync7(pkgPath)) {
|
|
21734
21735
|
const alwaysRecommend = ["implementation-plan", "write", "deepresearch"];
|
|
21735
21736
|
const recommended2 = alwaysRecommend.map((name) => loadRegistry().find((s) => s.name === name)).filter((s) => s !== undefined);
|
|
21736
21737
|
return { detected: [], recommended: recommended2 };
|
|
@@ -21861,7 +21862,7 @@ function generateSkillMd(name) {
|
|
|
21861
21862
|
if (!meta3)
|
|
21862
21863
|
return null;
|
|
21863
21864
|
const skillPath = getSkillPath(name);
|
|
21864
|
-
if (!
|
|
21865
|
+
if (!existsSync7(skillPath))
|
|
21865
21866
|
return null;
|
|
21866
21867
|
const frontmatter = [
|
|
21867
21868
|
"---",
|
|
@@ -21870,11 +21871,11 @@ function generateSkillMd(name) {
|
|
|
21870
21871
|
"---"
|
|
21871
21872
|
].join(`
|
|
21872
21873
|
`);
|
|
21873
|
-
const readme = readIfExists(
|
|
21874
|
-
const claudeMd = readIfExists(
|
|
21874
|
+
const readme = readIfExists(join7(skillPath, "README.md"));
|
|
21875
|
+
const claudeMd = readIfExists(join7(skillPath, "CLAUDE.md"));
|
|
21875
21876
|
let cliCommand = null;
|
|
21876
|
-
const pkgPath =
|
|
21877
|
-
if (
|
|
21877
|
+
const pkgPath = join7(skillPath, "package.json");
|
|
21878
|
+
if (existsSync7(pkgPath)) {
|
|
21878
21879
|
try {
|
|
21879
21880
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
21880
21881
|
if (pkg.bin) {
|
|
@@ -21949,7 +21950,7 @@ function extractEnvVars(text) {
|
|
|
21949
21950
|
}
|
|
21950
21951
|
function readIfExists(path) {
|
|
21951
21952
|
try {
|
|
21952
|
-
if (
|
|
21953
|
+
if (existsSync7(path)) {
|
|
21953
21954
|
return readFileSync6(path, "utf-8");
|
|
21954
21955
|
}
|
|
21955
21956
|
} catch {}
|
|
@@ -21981,8 +21982,8 @@ var init_skillinfo = __esm(() => {
|
|
|
21981
21982
|
});
|
|
21982
21983
|
|
|
21983
21984
|
// src/lib/skill-validation.ts
|
|
21984
|
-
import { existsSync as
|
|
21985
|
-
import { isAbsolute, join as
|
|
21985
|
+
import { existsSync as existsSync8, lstatSync, readFileSync as readFileSync7, readdirSync as readdirSync2, statSync } from "fs";
|
|
21986
|
+
import { isAbsolute, join as join8, normalize } from "path";
|
|
21986
21987
|
function add(target, code, message) {
|
|
21987
21988
|
target.push({ code, message });
|
|
21988
21989
|
}
|
|
@@ -22075,7 +22076,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
22075
22076
|
binCommands: [],
|
|
22076
22077
|
docFiles: []
|
|
22077
22078
|
};
|
|
22078
|
-
if (!
|
|
22079
|
+
if (!existsSync8(skillPath)) {
|
|
22079
22080
|
add(issues, "skill.dir_missing", `Skill directory not found: ${skillPath}`);
|
|
22080
22081
|
return {
|
|
22081
22082
|
name: bareName,
|
|
@@ -22090,7 +22091,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
22090
22091
|
add(issues, "skill.name_invalid", `Skill name '${bareName}' must use lowercase letters, numbers, dots, underscores, or hyphens`);
|
|
22091
22092
|
}
|
|
22092
22093
|
for (const entry of readdirSync2(skillPath).sort()) {
|
|
22093
|
-
const entryPath =
|
|
22094
|
+
const entryPath = join8(skillPath, entry);
|
|
22094
22095
|
if (RESERVED_SKILL_ENTRIES.has(entry)) {
|
|
22095
22096
|
add(issues, "skill.reserved_file", `Reserved file '${entry}' is not allowed in skill packages`);
|
|
22096
22097
|
}
|
|
@@ -22102,14 +22103,14 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
22102
22103
|
}
|
|
22103
22104
|
}
|
|
22104
22105
|
for (const docFile of DOC_FILES) {
|
|
22105
|
-
if (
|
|
22106
|
+
if (existsSync8(join8(skillPath, docFile)))
|
|
22106
22107
|
metadata.docFiles.push(docFile);
|
|
22107
22108
|
}
|
|
22108
22109
|
if (metadata.docFiles.length === 0) {
|
|
22109
22110
|
add(issues, "skill.docs_missing", "Missing documentation file: expected SKILL.md, README.md, or CLAUDE.md");
|
|
22110
22111
|
}
|
|
22111
|
-
const skillMdPath =
|
|
22112
|
-
if (
|
|
22112
|
+
const skillMdPath = join8(skillPath, "SKILL.md");
|
|
22113
|
+
if (existsSync8(skillMdPath)) {
|
|
22113
22114
|
const frontmatter = parseSkillFrontmatter(readFileSync7(skillMdPath, "utf-8"));
|
|
22114
22115
|
if (!frontmatter) {
|
|
22115
22116
|
add(warnings, "skill.frontmatter_missing", "SKILL.md has no YAML frontmatter");
|
|
@@ -22143,8 +22144,8 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
22143
22144
|
} else {
|
|
22144
22145
|
add(warnings, "skill.skill_md_missing", "Missing SKILL.md; registry docs may need generated agent-facing instructions");
|
|
22145
22146
|
}
|
|
22146
|
-
const pkgPath =
|
|
22147
|
-
if (!
|
|
22147
|
+
const pkgPath = join8(skillPath, "package.json");
|
|
22148
|
+
if (!existsSync8(pkgPath)) {
|
|
22148
22149
|
add(issues, "package.missing", "Missing package.json");
|
|
22149
22150
|
} else {
|
|
22150
22151
|
try {
|
|
@@ -22201,8 +22202,8 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
22201
22202
|
add(issues, "package.bin_target_unsafe", `package.json bin '${command}' target '${target}' must stay inside the skill directory`);
|
|
22202
22203
|
continue;
|
|
22203
22204
|
}
|
|
22204
|
-
const targetPath =
|
|
22205
|
-
if (!
|
|
22205
|
+
const targetPath = join8(skillPath, target);
|
|
22206
|
+
if (!existsSync8(targetPath)) {
|
|
22206
22207
|
add(warnings, "package.bin_target_missing", `package.json bin '${command}' target '${target}' is not present before build`);
|
|
22207
22208
|
} else if (statSync(targetPath).isDirectory()) {
|
|
22208
22209
|
add(issues, "package.bin_target_directory", `package.json bin '${command}' target '${target}' must point to a file, not a directory`);
|
|
@@ -22216,17 +22217,17 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
|
|
|
22216
22217
|
}
|
|
22217
22218
|
const hostedMetadata = isHostedMetadataSkill(bareName, metadata.skillMdFrontmatter, registryMeta, packageDeclaresHosted);
|
|
22218
22219
|
metadata.runtime = hostedMetadata ? "hosted" : "local";
|
|
22219
|
-
const srcDir =
|
|
22220
|
+
const srcDir = join8(skillPath, "src");
|
|
22220
22221
|
if (hostedMetadata) {
|
|
22221
|
-
if (
|
|
22222
|
+
if (existsSync8(srcDir)) {
|
|
22222
22223
|
add(issues, "skill.hosted_source_forbidden", "Hosted metadata skills must not include local implementation source");
|
|
22223
22224
|
}
|
|
22224
|
-
} else if (!
|
|
22225
|
+
} else if (!existsSync8(srcDir)) {
|
|
22225
22226
|
add(issues, "skill.src_missing", "Missing src/ directory");
|
|
22226
|
-
} else if (!
|
|
22227
|
+
} else if (!existsSync8(join8(srcDir, "index.ts")) && !existsSync8(join8(srcDir, "index.js"))) {
|
|
22227
22228
|
add(issues, "skill.src_index_missing", "Missing src/index.ts or src/index.js");
|
|
22228
22229
|
} else {
|
|
22229
|
-
const indexPath =
|
|
22230
|
+
const indexPath = existsSync8(join8(srcDir, "index.ts")) ? join8(srcDir, "index.ts") : join8(srcDir, "index.js");
|
|
22230
22231
|
const size = statSync(indexPath).size;
|
|
22231
22232
|
if (size < 50)
|
|
22232
22233
|
add(warnings, "skill.src_index_minimal", `Source entry point is very small (${size}B)`);
|
|
@@ -22295,8 +22296,8 @@ __export(exports_introspect, {
|
|
|
22295
22296
|
registerIntrospect: () => registerIntrospect
|
|
22296
22297
|
});
|
|
22297
22298
|
import chalk4 from "chalk";
|
|
22298
|
-
import { existsSync as
|
|
22299
|
-
import { join as
|
|
22299
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
22300
|
+
import { join as join9 } from "path";
|
|
22300
22301
|
import { execSync } from "child_process";
|
|
22301
22302
|
function registerIntrospect(parent) {
|
|
22302
22303
|
parent.command("info").argument("<skill>", "Skill name").option("--json", "Output as JSON", false).option("--brief", "Single line: name \u2014 description [category] (tags: ...)", false).option("--remote", "Use remote registry from SKILLS_API_URL or config apiUrl", false).description("Show details about a specific skill").action((name, options) => {
|
|
@@ -22507,7 +22508,7 @@ function handleValidate(name, options) {
|
|
|
22507
22508
|
function handleDiff(name, options) {
|
|
22508
22509
|
const bare = name;
|
|
22509
22510
|
const sourcePath = getSkillPath(bare);
|
|
22510
|
-
if (!
|
|
22511
|
+
if (!existsSync9(sourcePath)) {
|
|
22511
22512
|
if (options.json)
|
|
22512
22513
|
console.log(JSON.stringify({ error: `Skill '${bare}' not found in registry` }));
|
|
22513
22514
|
else
|
|
@@ -22524,9 +22525,9 @@ function handleDiff(name, options) {
|
|
|
22524
22525
|
}
|
|
22525
22526
|
const installMeta = getInstallMeta();
|
|
22526
22527
|
const installedVersion = installMeta.skills[bare]?.version ?? "unknown";
|
|
22527
|
-
const registryPkgPath =
|
|
22528
|
+
const registryPkgPath = join9(sourcePath, "package.json");
|
|
22528
22529
|
let registryVersion = "unknown";
|
|
22529
|
-
if (
|
|
22530
|
+
if (existsSync9(registryPkgPath)) {
|
|
22530
22531
|
try {
|
|
22531
22532
|
registryVersion = JSON.parse(readFileSync8(registryPkgPath, "utf-8")).version || "unknown";
|
|
22532
22533
|
} catch {}
|
|
@@ -22559,8 +22560,8 @@ __export(exports_init, {
|
|
|
22559
22560
|
registerSetup: () => registerSetup
|
|
22560
22561
|
});
|
|
22561
22562
|
import chalk5 from "chalk";
|
|
22562
|
-
import { existsSync as
|
|
22563
|
-
import { join as
|
|
22563
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync4, appendFileSync } from "fs";
|
|
22564
|
+
import { join as join10 } from "path";
|
|
22564
22565
|
function registerSetup(parent) {
|
|
22565
22566
|
parent.command("init").option("--json", "Output as JSON", false).option("--for <agent>", "Detect project type and show MCP registration guidance for agent").option("--scope <scope>", "Deprecated; agent skill-folder installs are disabled", "global").description("Initialize project for pinned skills (.env.example, .gitignore)").action((options) => handleInit(options));
|
|
22566
22567
|
parent.command("export").option("--json", "Output as JSON (default behavior)", false).description("Export pinned skills to JSON for sharing or backup").action((_options) => handleExport());
|
|
@@ -22644,7 +22645,7 @@ Use: skills mcp --register ${options.for}`));
|
|
|
22644
22645
|
lines.push(`# Used by: ${skills.join(", ")}`);
|
|
22645
22646
|
lines.push(`${envVar}=`);
|
|
22646
22647
|
}
|
|
22647
|
-
writeFileSync4(
|
|
22648
|
+
writeFileSync4(join10(cwd, ".env.example"), lines.join(`
|
|
22648
22649
|
`) + `
|
|
22649
22650
|
`);
|
|
22650
22651
|
envVarCount = envMap.size;
|
|
@@ -22652,9 +22653,9 @@ Use: skills mcp --register ${options.for}`));
|
|
|
22652
22653
|
console.log(chalk5.green(`\u2713 Generated .env.example (${envVarCount} variables from ${installed.length} skills)`));
|
|
22653
22654
|
} else if (!options.json)
|
|
22654
22655
|
console.log(chalk5.dim(" No environment variables detected across pinned skills"));
|
|
22655
|
-
const gitignorePath =
|
|
22656
|
+
const gitignorePath = join10(cwd, ".gitignore");
|
|
22656
22657
|
const gitignoreEntries = [".skills/runs/", ".skills/exports/", ".skills/tmp/"];
|
|
22657
|
-
let gitignoreContent =
|
|
22658
|
+
let gitignoreContent = existsSync10(gitignorePath) ? readFileSync9(gitignorePath, "utf-8") : "";
|
|
22658
22659
|
let gitignoreUpdated = false;
|
|
22659
22660
|
const missingEntries = gitignoreEntries.filter((entry) => !gitignoreContent.includes(entry));
|
|
22660
22661
|
if (missingEntries.length > 0) {
|
|
@@ -22700,7 +22701,7 @@ async function handleImport(file2, options) {
|
|
|
22700
22701
|
if (file2 === "-")
|
|
22701
22702
|
raw = await new Response(process.stdin).text();
|
|
22702
22703
|
else {
|
|
22703
|
-
if (!
|
|
22704
|
+
if (!existsSync10(file2)) {
|
|
22704
22705
|
const error48 = `File not found: ${file2}`;
|
|
22705
22706
|
if (options.json)
|
|
22706
22707
|
console.log(JSON.stringify({ imported: 0, error: error48 }));
|
|
@@ -22810,8 +22811,8 @@ __export(exports_diagnostic, {
|
|
|
22810
22811
|
registerDiagnostic: () => registerDiagnostic
|
|
22811
22812
|
});
|
|
22812
22813
|
import chalk6 from "chalk";
|
|
22813
|
-
import { existsSync as
|
|
22814
|
-
import { join as
|
|
22814
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10, readdirSync as readdirSync3, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
22815
|
+
import { join as join11 } from "path";
|
|
22815
22816
|
import { execSync as execSync2 } from "child_process";
|
|
22816
22817
|
function registerDiagnostic(parent) {
|
|
22817
22818
|
parent.command("doctor").option("--json", "Output as JSON", false).description("Check env vars, system deps, and readiness for pinned skills").action((options) => handleDoctor(options));
|
|
@@ -22924,7 +22925,7 @@ Skills Test (${results.length} skill${results.length === 1 ? "" : "s"}):
|
|
|
22924
22925
|
}
|
|
22925
22926
|
function handleAuth(name, options) {
|
|
22926
22927
|
const cwd = process.cwd();
|
|
22927
|
-
const envFilePath =
|
|
22928
|
+
const envFilePath = join11(cwd, ".env");
|
|
22928
22929
|
if (options.set) {
|
|
22929
22930
|
const eqIdx = options.set.indexOf("=");
|
|
22930
22931
|
if (eqIdx === -1) {
|
|
@@ -22946,7 +22947,7 @@ function handleAuth(name, options) {
|
|
|
22946
22947
|
process.exitCode = 1;
|
|
22947
22948
|
return;
|
|
22948
22949
|
}
|
|
22949
|
-
let existing =
|
|
22950
|
+
let existing = existsSync11(envFilePath) ? readFileSync10(envFilePath, "utf-8") : "";
|
|
22950
22951
|
const keyPattern = new RegExp(`^${key}=.*$`, "m");
|
|
22951
22952
|
const updated = keyPattern.test(existing) ? existing.replace(keyPattern, `${key}=${value}`) : existing.endsWith(`
|
|
22952
22953
|
`) || existing === "" ? existing + `${key}=${value}
|
|
@@ -23012,11 +23013,11 @@ function handleWhoami(options) {
|
|
|
23012
23013
|
const agentConfigs = [];
|
|
23013
23014
|
for (const agent of AGENT_TARGETS) {
|
|
23014
23015
|
const agentSkillsPath = getAgentSkillsDir(agent, "global");
|
|
23015
|
-
const exists =
|
|
23016
|
+
const exists = existsSync11(agentSkillsPath);
|
|
23016
23017
|
let skillCount = 0;
|
|
23017
23018
|
if (exists)
|
|
23018
23019
|
try {
|
|
23019
|
-
skillCount = readdirSync3(agentSkillsPath).filter((f) => !f.startsWith(".") && statSync2(
|
|
23020
|
+
skillCount = readdirSync3(agentSkillsPath).filter((f) => !f.startsWith(".") && statSync2(join11(agentSkillsPath, f)).isDirectory()).length;
|
|
23020
23021
|
} catch {}
|
|
23021
23022
|
agentConfigs.push({ agent, label: AGENT_LABELS[agent], path: agentSkillsPath, exists, skillCount });
|
|
23022
23023
|
}
|
|
@@ -23055,9 +23056,9 @@ function handleOutdated(options) {
|
|
|
23055
23056
|
for (const name of installed) {
|
|
23056
23057
|
const installedVersion = meta3.skills[name]?.version ?? "unknown";
|
|
23057
23058
|
const registryPath = getSkillPath(name);
|
|
23058
|
-
const registryPkgPath =
|
|
23059
|
+
const registryPkgPath = join11(registryPath, "package.json");
|
|
23059
23060
|
let registryVersion = "unknown";
|
|
23060
|
-
if (
|
|
23061
|
+
if (existsSync11(registryPkgPath))
|
|
23061
23062
|
try {
|
|
23062
23063
|
registryVersion = JSON.parse(readFileSync10(registryPkgPath, "utf-8")).version || "unknown";
|
|
23063
23064
|
} catch {}
|
|
@@ -23148,27 +23149,27 @@ var REMOTE_SKILL_RUN_CONTRACT_VERSION = 1;
|
|
|
23148
23149
|
|
|
23149
23150
|
// src/lib/run-state.ts
|
|
23150
23151
|
import { createHash, randomBytes } from "crypto";
|
|
23151
|
-
import { existsSync as
|
|
23152
|
-
import { extname, join as
|
|
23152
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync11, readdirSync as readdirSync4, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
23153
|
+
import { extname, join as join12, relative } from "path";
|
|
23153
23154
|
function createSkillRun(params, targetDir = process.cwd()) {
|
|
23154
|
-
const
|
|
23155
|
-
const id = createRunId(
|
|
23156
|
-
const day =
|
|
23155
|
+
const now2 = new Date;
|
|
23156
|
+
const id = createRunId(now2);
|
|
23157
|
+
const day = now2.toISOString().slice(0, 10);
|
|
23157
23158
|
const skillName = normalizeSkillName(params.skill);
|
|
23158
23159
|
const root = getProjectStateDir(targetDir);
|
|
23159
|
-
const runDir =
|
|
23160
|
-
const logsDir =
|
|
23161
|
-
const exportDir =
|
|
23160
|
+
const runDir = join12(root, "runs", day, id);
|
|
23161
|
+
const logsDir = join12(runDir, "logs");
|
|
23162
|
+
const exportDir = join12(root, "exports", skillName, id);
|
|
23162
23163
|
mkdirSync4(logsDir, { recursive: true });
|
|
23163
23164
|
mkdirSync4(exportDir, { recursive: true });
|
|
23164
|
-
mkdirSync4(
|
|
23165
|
+
mkdirSync4(join12(root, "tmp"), { recursive: true });
|
|
23165
23166
|
const record2 = {
|
|
23166
23167
|
id,
|
|
23167
23168
|
skill: skillName,
|
|
23168
23169
|
status: params.status ?? "running",
|
|
23169
23170
|
...params.prompt ? { prompt: params.prompt } : {},
|
|
23170
23171
|
args: params.args ?? [],
|
|
23171
|
-
startedAt:
|
|
23172
|
+
startedAt: now2.toISOString(),
|
|
23172
23173
|
remote: params.remote ?? false,
|
|
23173
23174
|
...params.remoteRunId ? { remoteRunId: params.remoteRunId } : {},
|
|
23174
23175
|
...params.costCents !== undefined ? { costCents: params.costCents } : {},
|
|
@@ -23208,27 +23209,27 @@ function updateSkillRun(context, patch) {
|
|
|
23208
23209
|
return context.record;
|
|
23209
23210
|
}
|
|
23210
23211
|
function writeRunLogs(context, stdout = "", stderr = "") {
|
|
23211
|
-
writeFileSync6(
|
|
23212
|
-
writeFileSync6(
|
|
23212
|
+
writeFileSync6(join12(context.logsDir, "stdout.log"), stdout);
|
|
23213
|
+
writeFileSync6(join12(context.logsDir, "stderr.log"), stderr);
|
|
23213
23214
|
}
|
|
23214
23215
|
function appendRunEvent(context, event, data = {}) {
|
|
23215
23216
|
const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
|
|
23216
23217
|
`;
|
|
23217
|
-
const path =
|
|
23218
|
-
const previous =
|
|
23218
|
+
const path = join12(context.runDir, "events.ndjson");
|
|
23219
|
+
const previous = existsSync12(path) ? readFileSync11(path, "utf-8") : "";
|
|
23219
23220
|
writeFileSync6(path, previous + line);
|
|
23220
23221
|
}
|
|
23221
23222
|
function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
23222
|
-
const runsRoot =
|
|
23223
|
-
if (!
|
|
23223
|
+
const runsRoot = join12(getProjectStateDir(targetDir), "runs");
|
|
23224
|
+
if (!existsSync12(runsRoot))
|
|
23224
23225
|
return [];
|
|
23225
23226
|
const records = [];
|
|
23226
23227
|
for (const day of readdirSync4(runsRoot).sort().reverse()) {
|
|
23227
|
-
const dayDir =
|
|
23228
|
+
const dayDir = join12(runsRoot, day);
|
|
23228
23229
|
if (!statSync3(dayDir).isDirectory())
|
|
23229
23230
|
continue;
|
|
23230
23231
|
for (const runId of readdirSync4(dayDir).sort().reverse()) {
|
|
23231
|
-
const record2 = readRunRecord(
|
|
23232
|
+
const record2 = readRunRecord(join12(dayDir, runId));
|
|
23232
23233
|
if (record2)
|
|
23233
23234
|
records.push(record2);
|
|
23234
23235
|
if (records.length >= limit)
|
|
@@ -23238,29 +23239,29 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
|
|
|
23238
23239
|
return records;
|
|
23239
23240
|
}
|
|
23240
23241
|
function findSkillRun(runId, targetDir = process.cwd()) {
|
|
23241
|
-
const runsRoot =
|
|
23242
|
-
if (!
|
|
23242
|
+
const runsRoot = join12(getProjectStateDir(targetDir), "runs");
|
|
23243
|
+
if (!existsSync12(runsRoot))
|
|
23243
23244
|
return null;
|
|
23244
23245
|
for (const day of readdirSync4(runsRoot)) {
|
|
23245
|
-
const record2 = readRunRecord(
|
|
23246
|
+
const record2 = readRunRecord(join12(runsRoot, day, runId));
|
|
23246
23247
|
if (record2)
|
|
23247
23248
|
return record2;
|
|
23248
23249
|
}
|
|
23249
23250
|
return null;
|
|
23250
23251
|
}
|
|
23251
23252
|
function getRunExportDir(runId, skill, targetDir = process.cwd()) {
|
|
23252
|
-
return
|
|
23253
|
+
return join12(getProjectStateDir(targetDir), "exports", normalizeSkillName(skill), runId);
|
|
23253
23254
|
}
|
|
23254
23255
|
function writeRunRecord(context) {
|
|
23255
|
-
writeFileSync6(
|
|
23256
|
+
writeFileSync6(join12(context.runDir, "run.json"), JSON.stringify(context.record, null, 2) + `
|
|
23256
23257
|
`);
|
|
23257
23258
|
}
|
|
23258
23259
|
function writeArtifactsManifest(context, artifacts) {
|
|
23259
|
-
writeFileSync6(
|
|
23260
|
+
writeFileSync6(join12(context.runDir, "artifacts.json"), JSON.stringify({ runId: context.record.id, artifacts }, null, 2) + `
|
|
23260
23261
|
`);
|
|
23261
23262
|
}
|
|
23262
23263
|
function collectRunArtifacts(context) {
|
|
23263
|
-
if (!
|
|
23264
|
+
if (!existsSync12(context.exportDir))
|
|
23264
23265
|
return [];
|
|
23265
23266
|
const artifacts = [];
|
|
23266
23267
|
for (const path of walkFiles(context.exportDir)) {
|
|
@@ -23276,8 +23277,8 @@ function collectRunArtifacts(context) {
|
|
|
23276
23277
|
return artifacts.sort((a, b) => a.path.localeCompare(b.path));
|
|
23277
23278
|
}
|
|
23278
23279
|
function readRunRecord(runDir) {
|
|
23279
|
-
const path =
|
|
23280
|
-
if (!
|
|
23280
|
+
const path = join12(runDir, "run.json");
|
|
23281
|
+
if (!existsSync12(path))
|
|
23281
23282
|
return null;
|
|
23282
23283
|
try {
|
|
23283
23284
|
return JSON.parse(readFileSync11(path, "utf-8"));
|
|
@@ -23288,7 +23289,7 @@ function readRunRecord(runDir) {
|
|
|
23288
23289
|
function walkFiles(dir) {
|
|
23289
23290
|
const files = [];
|
|
23290
23291
|
for (const entry of readdirSync4(dir)) {
|
|
23291
|
-
const full =
|
|
23292
|
+
const full = join12(dir, entry);
|
|
23292
23293
|
if (statSync3(full).isDirectory())
|
|
23293
23294
|
files.push(...walkFiles(full));
|
|
23294
23295
|
else
|
|
@@ -23296,8 +23297,8 @@ function walkFiles(dir) {
|
|
|
23296
23297
|
}
|
|
23297
23298
|
return files;
|
|
23298
23299
|
}
|
|
23299
|
-
function createRunId(
|
|
23300
|
-
return `run_${
|
|
23300
|
+
function createRunId(now2) {
|
|
23301
|
+
return `run_${now2.getTime().toString(36)}_${randomBytes(4).toString("hex")}`;
|
|
23301
23302
|
}
|
|
23302
23303
|
function toProjectRelative(targetDir, path) {
|
|
23303
23304
|
const rel = relative(targetDir, path).split(/[\\/]/).join("/");
|
|
@@ -39375,8 +39376,8 @@ var init_remote_client = __esm(() => {
|
|
|
39375
39376
|
});
|
|
39376
39377
|
|
|
39377
39378
|
// src/mcp/operation-tools.ts
|
|
39378
|
-
import { existsSync as
|
|
39379
|
-
import { join as
|
|
39379
|
+
import { existsSync as existsSync13, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
39380
|
+
import { join as join13 } from "path";
|
|
39380
39381
|
function registerOperationTools(server) {
|
|
39381
39382
|
server.registerTool("pin_skill", {
|
|
39382
39383
|
title: "Pin Skill",
|
|
@@ -39770,12 +39771,12 @@ function registerOperationTools(server) {
|
|
|
39770
39771
|
const agents = [];
|
|
39771
39772
|
for (const agent of AGENT_TARGETS) {
|
|
39772
39773
|
const agentSkillsPath = getAgentSkillsDir(agent, "global");
|
|
39773
|
-
const exists =
|
|
39774
|
+
const exists = existsSync13(agentSkillsPath);
|
|
39774
39775
|
let skillCount = 0;
|
|
39775
39776
|
if (exists) {
|
|
39776
39777
|
try {
|
|
39777
39778
|
skillCount = readdirSync5(agentSkillsPath).filter((f) => {
|
|
39778
|
-
const full =
|
|
39779
|
+
const full = join13(agentSkillsPath, f);
|
|
39779
39780
|
return !f.startsWith(".") && statSync4(full).isDirectory();
|
|
39780
39781
|
}).length;
|
|
39781
39782
|
} catch {}
|
|
@@ -39805,17 +39806,17 @@ var init_operation_tools = __esm(() => {
|
|
|
39805
39806
|
});
|
|
39806
39807
|
|
|
39807
39808
|
// src/lib/feedback.ts
|
|
39808
|
-
import { existsSync as
|
|
39809
|
-
import { homedir as
|
|
39810
|
-
import { dirname as dirname3, join as
|
|
39809
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5 } from "fs";
|
|
39810
|
+
import { homedir as homedir6 } from "os";
|
|
39811
|
+
import { dirname as dirname3, join as join14 } from "path";
|
|
39811
39812
|
import { Database } from "bun:sqlite";
|
|
39812
39813
|
function getFeedbackDbPath() {
|
|
39813
|
-
return
|
|
39814
|
+
return join14(homedir6(), ".hasna", "skills", "skills.db");
|
|
39814
39815
|
}
|
|
39815
39816
|
function getFeedbackDb() {
|
|
39816
39817
|
const dbPath = getFeedbackDbPath();
|
|
39817
39818
|
const dir = dirname3(dbPath);
|
|
39818
|
-
if (!
|
|
39819
|
+
if (!existsSync14(dir))
|
|
39819
39820
|
mkdirSync5(dir, { recursive: true });
|
|
39820
39821
|
const db = new Database(dbPath);
|
|
39821
39822
|
db.exec("PRAGMA journal_mode = WAL");
|
|
@@ -39976,14 +39977,14 @@ var init_resource_meta_tools = __esm(() => {
|
|
|
39976
39977
|
});
|
|
39977
39978
|
|
|
39978
39979
|
// src/lib/scheduler.ts
|
|
39979
|
-
import { existsSync as
|
|
39980
|
-
import { join as
|
|
39980
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6 } from "fs";
|
|
39981
|
+
import { join as join15 } from "path";
|
|
39981
39982
|
function getSchedulesPath(targetDir = process.cwd()) {
|
|
39982
|
-
return
|
|
39983
|
+
return join15(targetDir, ".skills", "schedules.json");
|
|
39983
39984
|
}
|
|
39984
39985
|
function loadSchedules(targetDir = process.cwd()) {
|
|
39985
39986
|
const path = getSchedulesPath(targetDir);
|
|
39986
|
-
if (
|
|
39987
|
+
if (existsSync15(path)) {
|
|
39987
39988
|
try {
|
|
39988
39989
|
return JSON.parse(readFileSync12(path, "utf-8"));
|
|
39989
39990
|
} catch {}
|
|
@@ -39992,8 +39993,8 @@ function loadSchedules(targetDir = process.cwd()) {
|
|
|
39992
39993
|
}
|
|
39993
39994
|
function saveSchedules(data, targetDir = process.cwd()) {
|
|
39994
39995
|
const path = getSchedulesPath(targetDir);
|
|
39995
|
-
const dir =
|
|
39996
|
-
if (!
|
|
39996
|
+
const dir = join15(targetDir, ".skills");
|
|
39997
|
+
if (!existsSync15(dir))
|
|
39997
39998
|
mkdirSync6(dir, { recursive: true });
|
|
39998
39999
|
writeFileSync7(path, JSON.stringify(data, null, 2));
|
|
39999
40000
|
}
|
|
@@ -40124,8 +40125,8 @@ function addSchedule(skill, cron, options = {}) {
|
|
|
40124
40125
|
return { schedule: null, error: error48 };
|
|
40125
40126
|
const data = loadSchedules(options.targetDir);
|
|
40126
40127
|
const id = `${skill}-${Date.now()}`;
|
|
40127
|
-
const
|
|
40128
|
-
const nextRun = getNextRun(cron,
|
|
40128
|
+
const now2 = new Date;
|
|
40129
|
+
const nextRun = getNextRun(cron, now2);
|
|
40129
40130
|
const schedule = {
|
|
40130
40131
|
id,
|
|
40131
40132
|
name: options.name || `${skill} (${cron})`,
|
|
@@ -40133,7 +40134,7 @@ function addSchedule(skill, cron, options = {}) {
|
|
|
40133
40134
|
cron,
|
|
40134
40135
|
args: options.args,
|
|
40135
40136
|
enabled: true,
|
|
40136
|
-
createdAt:
|
|
40137
|
+
createdAt: now2.toISOString(),
|
|
40137
40138
|
nextRun: nextRun?.toISOString()
|
|
40138
40139
|
};
|
|
40139
40140
|
data.schedules.push(schedule);
|
|
@@ -40165,18 +40166,18 @@ function setScheduleEnabled(idOrName, enabled, targetDir) {
|
|
|
40165
40166
|
return true;
|
|
40166
40167
|
}
|
|
40167
40168
|
function getDueSchedules(targetDir) {
|
|
40168
|
-
const
|
|
40169
|
-
return listSchedules(targetDir).filter((s) => s.enabled && s.nextRun && new Date(s.nextRun) <=
|
|
40169
|
+
const now2 = new Date;
|
|
40170
|
+
return listSchedules(targetDir).filter((s) => s.enabled && s.nextRun && new Date(s.nextRun) <= now2);
|
|
40170
40171
|
}
|
|
40171
40172
|
function recordScheduleRun(id, status, targetDir) {
|
|
40172
40173
|
const data = loadSchedules(targetDir);
|
|
40173
40174
|
const schedule = data.schedules.find((s) => s.id === id);
|
|
40174
40175
|
if (!schedule)
|
|
40175
40176
|
return;
|
|
40176
|
-
const
|
|
40177
|
-
schedule.lastRun =
|
|
40177
|
+
const now2 = new Date;
|
|
40178
|
+
schedule.lastRun = now2.toISOString();
|
|
40178
40179
|
schedule.lastRunStatus = status;
|
|
40179
|
-
schedule.nextRun = getNextRun(schedule.cron,
|
|
40180
|
+
schedule.nextRun = getNextRun(schedule.cron, now2)?.toISOString();
|
|
40180
40181
|
saveSchedules(data, targetDir);
|
|
40181
40182
|
}
|
|
40182
40183
|
var init_scheduler = () => {};
|
|
@@ -40538,9 +40539,9 @@ var init_mcp2 = __esm(() => {
|
|
|
40538
40539
|
|
|
40539
40540
|
// src/cli/commands/runtime-mcp.ts
|
|
40540
40541
|
import chalk7 from "chalk";
|
|
40541
|
-
import { existsSync as
|
|
40542
|
-
import { homedir as
|
|
40543
|
-
import { dirname as dirname4, join as
|
|
40542
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
|
|
40543
|
+
import { homedir as homedir7 } from "os";
|
|
40544
|
+
import { dirname as dirname4, join as join16 } from "path";
|
|
40544
40545
|
async function handleMcp(options) {
|
|
40545
40546
|
if (options.register) {
|
|
40546
40547
|
let agents;
|
|
@@ -40582,24 +40583,24 @@ async function registerMcpForAgent(agent, command) {
|
|
|
40582
40583
|
case "codex":
|
|
40583
40584
|
return registerCodexMcp(command);
|
|
40584
40585
|
case "gemini":
|
|
40585
|
-
return registerJsonMcpServer(agent,
|
|
40586
|
+
return registerJsonMcpServer(agent, join16(homedir7(), ".gemini", "settings.json"), "mcpServers", {
|
|
40586
40587
|
command,
|
|
40587
40588
|
args: []
|
|
40588
40589
|
});
|
|
40589
40590
|
case "pi":
|
|
40590
|
-
return registerJsonMcpServer(agent,
|
|
40591
|
+
return registerJsonMcpServer(agent, join16(homedir7(), ".pi", "agent", "mcp.json"), "mcpServers", {
|
|
40591
40592
|
command,
|
|
40592
40593
|
args: []
|
|
40593
40594
|
});
|
|
40594
40595
|
case "opencode":
|
|
40595
40596
|
return registerOpenCodeMcp(command);
|
|
40596
40597
|
case "cursor":
|
|
40597
|
-
return registerJsonMcpServer(agent,
|
|
40598
|
+
return registerJsonMcpServer(agent, join16(homedir7(), ".cursor", "mcp.json"), "mcpServers", {
|
|
40598
40599
|
command,
|
|
40599
40600
|
args: []
|
|
40600
40601
|
});
|
|
40601
40602
|
case "windsurf":
|
|
40602
|
-
return registerJsonMcpServer(agent,
|
|
40603
|
+
return registerJsonMcpServer(agent, join16(homedir7(), ".windsurf", "mcp.json"), "mcpServers", {
|
|
40603
40604
|
command,
|
|
40604
40605
|
args: []
|
|
40605
40606
|
});
|
|
@@ -40622,7 +40623,7 @@ async function registerClaudeMcp(command) {
|
|
|
40622
40623
|
if (exitCode === 0) {
|
|
40623
40624
|
return { agent: "claude", success: true, command: cliCommand };
|
|
40624
40625
|
}
|
|
40625
|
-
const fallback = registerJsonMcpServer("claude",
|
|
40626
|
+
const fallback = registerJsonMcpServer("claude", join16(homedir7(), ".claude", ".mcp.json"), "mcpServers", {
|
|
40626
40627
|
command,
|
|
40627
40628
|
args: []
|
|
40628
40629
|
});
|
|
@@ -40632,7 +40633,7 @@ async function registerClaudeMcp(command) {
|
|
|
40632
40633
|
error: fallback.success ? undefined : `claude exited with ${exitCode}: ${(stderr || stdout).trim()}`
|
|
40633
40634
|
};
|
|
40634
40635
|
} catch (err) {
|
|
40635
|
-
const fallback = registerJsonMcpServer("claude",
|
|
40636
|
+
const fallback = registerJsonMcpServer("claude", join16(homedir7(), ".claude", ".mcp.json"), "mcpServers", {
|
|
40636
40637
|
command,
|
|
40637
40638
|
args: []
|
|
40638
40639
|
});
|
|
@@ -40644,11 +40645,11 @@ async function registerClaudeMcp(command) {
|
|
|
40644
40645
|
}
|
|
40645
40646
|
}
|
|
40646
40647
|
function registerCodexMcp(command) {
|
|
40647
|
-
const path =
|
|
40648
|
+
const path = join16(homedir7(), ".codex", "config.toml");
|
|
40648
40649
|
const config2 = `[mcp_servers.${MCP_SERVER_NAME}]
|
|
40649
40650
|
command = ${JSON.stringify(command)}`;
|
|
40650
40651
|
try {
|
|
40651
|
-
const current =
|
|
40652
|
+
const current = existsSync16(path) ? readFileSync13(path, "utf-8") : "";
|
|
40652
40653
|
writeTextFile(path, upsertTomlSection(current, `[mcp_servers.${MCP_SERVER_NAME}]`, `command = ${JSON.stringify(command)}`));
|
|
40653
40654
|
return { agent: "codex", success: true, path, config: config2 };
|
|
40654
40655
|
} catch (err) {
|
|
@@ -40656,7 +40657,7 @@ command = ${JSON.stringify(command)}`;
|
|
|
40656
40657
|
}
|
|
40657
40658
|
}
|
|
40658
40659
|
function registerOpenCodeMcp(command) {
|
|
40659
|
-
const path =
|
|
40660
|
+
const path = join16(homedir7(), ".config", "opencode", "opencode.json");
|
|
40660
40661
|
const config2 = JSON.stringify({
|
|
40661
40662
|
$schema: "https://opencode.ai/config.json",
|
|
40662
40663
|
mcp: {
|
|
@@ -40698,7 +40699,7 @@ function registerJsonMcpServer(agent, path, containerKey, server2) {
|
|
|
40698
40699
|
}
|
|
40699
40700
|
}
|
|
40700
40701
|
function readJsonObject(path) {
|
|
40701
|
-
if (!
|
|
40702
|
+
if (!existsSync16(path))
|
|
40702
40703
|
return {};
|
|
40703
40704
|
const raw = readFileSync13(path, "utf-8").trim();
|
|
40704
40705
|
if (!raw)
|
|
@@ -40744,8 +40745,8 @@ function findCommandOnPath(command) {
|
|
|
40744
40745
|
for (const dir of pathValue.split(":")) {
|
|
40745
40746
|
if (!dir)
|
|
40746
40747
|
continue;
|
|
40747
|
-
const candidate =
|
|
40748
|
-
if (
|
|
40748
|
+
const candidate = join16(dir, command);
|
|
40749
|
+
if (existsSync16(candidate))
|
|
40749
40750
|
return candidate;
|
|
40750
40751
|
}
|
|
40751
40752
|
return command;
|
|
@@ -40765,7 +40766,7 @@ __export(exports_runtime, {
|
|
|
40765
40766
|
});
|
|
40766
40767
|
import chalk8 from "chalk";
|
|
40767
40768
|
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
40768
|
-
import { dirname as dirname5, join as
|
|
40769
|
+
import { dirname as dirname5, join as join17 } from "path";
|
|
40769
40770
|
import { createInterface } from "readline";
|
|
40770
40771
|
function registerRuntime(parent) {
|
|
40771
40772
|
parent.command("quote").argument("<skill>", "Skill name").argument("[args...]", "Arguments that affect pricing, such as --count 8").allowUnknownOption(true).passThroughOptions(true).option("--json", "Output quote as JSON", false).description("Quote a skill run before spending account balance").action((name, args2, options) => handleQuote(name, args2, options));
|
|
@@ -41317,7 +41318,7 @@ async function handleExportsDownload(runId, options) {
|
|
|
41317
41318
|
if (!response.ok)
|
|
41318
41319
|
throw new Error(`download failed for artifact ${artifactId}: ${response.status}`);
|
|
41319
41320
|
const relativePath = safeArtifactRelativePath(typeof artifact.relativePath === "string" ? artifact.relativePath : artifact.fileName, String(artifact.fileName || artifactId));
|
|
41320
|
-
const outputPath =
|
|
41321
|
+
const outputPath = join17(exportDir, relativePath);
|
|
41321
41322
|
mkdirSync8(dirname5(outputPath), { recursive: true });
|
|
41322
41323
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
41323
41324
|
writeFileSync9(outputPath, bytes);
|
|
@@ -41711,9 +41712,9 @@ __export(exports_create_sync_config, {
|
|
|
41711
41712
|
registerCreateSync: () => registerCreateSync
|
|
41712
41713
|
});
|
|
41713
41714
|
import chalk9 from "chalk";
|
|
41714
|
-
import { existsSync as
|
|
41715
|
-
import { join as
|
|
41716
|
-
import { homedir as
|
|
41715
|
+
import { existsSync as existsSync17, writeFileSync as writeFileSync10, mkdirSync as mkdirSync9 } from "fs";
|
|
41716
|
+
import { join as join18 } from "path";
|
|
41717
|
+
import { homedir as homedir8 } from "os";
|
|
41717
41718
|
function registerCreateSync(parent) {
|
|
41718
41719
|
const configCmd = parent.command("config").description("Manage skills configuration");
|
|
41719
41720
|
configCmd.command("show", { isDefault: true }).option("--json", "Output as JSON", false).description("Show current merged configuration").action((options) => {
|
|
@@ -41761,13 +41762,13 @@ function registerCreateSync(parent) {
|
|
|
41761
41762
|
const pp = getConfigPath("project");
|
|
41762
41763
|
if (options.json) {
|
|
41763
41764
|
console.log(JSON.stringify({
|
|
41764
|
-
global: { path: gp, exists:
|
|
41765
|
-
project: { path: pp, exists:
|
|
41765
|
+
global: { path: gp, exists: existsSync17(gp) },
|
|
41766
|
+
project: { path: pp, exists: existsSync17(pp) }
|
|
41766
41767
|
}, null, 2));
|
|
41767
41768
|
return;
|
|
41768
41769
|
}
|
|
41769
|
-
console.log(`${chalk9.cyan("global")}: ${gp}${
|
|
41770
|
-
console.log(`${chalk9.cyan("project")}: ${pp}${
|
|
41770
|
+
console.log(`${chalk9.cyan("global")}: ${gp}${existsSync17(gp) ? chalk9.green(" (exists)") : chalk9.dim(" (not found)")}`);
|
|
41771
|
+
console.log(`${chalk9.cyan("project")}: ${pp}${existsSync17(pp) ? chalk9.green(" (exists)") : chalk9.dim(" (not found)")}`);
|
|
41771
41772
|
});
|
|
41772
41773
|
parent.command("create").argument("<name>", "Skill name (e.g. my-tool)").option("--category <category>", "Skill category", "Development Tools").option("--description <description>", "Short description of what the skill does").option("--tags <tags>", "Comma-separated tags (e.g. api,testing,automation)").option("--global", "Deprecated; custom skills are always global", false).option("--json", "Output result as JSON", false).description("Scaffold a new custom skill directory").action((name, options) => handleCreate(name, options));
|
|
41773
41774
|
parent.command("sync").option("--to <agent>", "Deprecated; use skills mcp --register <agent|all>").option("--from <agent>", "Deprecated; agent skill-folder sync is disabled").option("--register", "Deprecated; agent skill-folder imports are disabled", false).option("--scope <scope>", "Deprecated; ignored", "global").option("--json", "Output as JSON", false).description("Disabled legacy agent skill-folder sync").action((options) => handleSync(options));
|
|
@@ -41775,9 +41776,9 @@ function registerCreateSync(parent) {
|
|
|
41775
41776
|
function handleCreate(name, options) {
|
|
41776
41777
|
const bare = name.trim();
|
|
41777
41778
|
const dirName = bare;
|
|
41778
|
-
const baseDir =
|
|
41779
|
-
const skillDir =
|
|
41780
|
-
if (
|
|
41779
|
+
const baseDir = join18(homedir8(), ".hasna", "skills", "custom");
|
|
41780
|
+
const skillDir = join18(baseDir, dirName);
|
|
41781
|
+
if (existsSync17(skillDir)) {
|
|
41781
41782
|
console.log(options.json ? JSON.stringify({ error: `Skill '${bare}' already exists at ${skillDir}` }) : chalk9.red(`Skill '${bare}' already exists at ${skillDir}`));
|
|
41782
41783
|
process.exitCode = 1;
|
|
41783
41784
|
return;
|
|
@@ -41785,8 +41786,8 @@ function handleCreate(name, options) {
|
|
|
41785
41786
|
const description = options.description || `${bare} skill`;
|
|
41786
41787
|
const tags = options.tags ? options.tags.split(",").map((t) => t.trim()).filter(Boolean) : [bare];
|
|
41787
41788
|
const displayName = bare.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
41788
|
-
mkdirSync9(
|
|
41789
|
-
writeFileSync10(
|
|
41789
|
+
mkdirSync9(join18(skillDir, "src"), { recursive: true });
|
|
41790
|
+
writeFileSync10(join18(skillDir, "SKILL.md"), [
|
|
41790
41791
|
"---",
|
|
41791
41792
|
`name: ${bare}`,
|
|
41792
41793
|
`description: ${description}`,
|
|
@@ -41806,11 +41807,11 @@ function handleCreate(name, options) {
|
|
|
41806
41807
|
""
|
|
41807
41808
|
].join(`
|
|
41808
41809
|
`));
|
|
41809
|
-
writeFileSync10(
|
|
41810
|
+
writeFileSync10(join18(skillDir, "src", "index.ts"), [`#!/usr/bin/env bun`, `/**`, ` * ${displayName} \u2014 ${description}`, ` */`, "", `console.log("${displayName}");`, ""].join(`
|
|
41810
41811
|
`));
|
|
41811
|
-
writeFileSync10(
|
|
41812
|
+
writeFileSync10(join18(skillDir, "package.json"), JSON.stringify({ name: bare, version: "0.1.0", description, bin: { [bare]: "./src/index.ts" }, scripts: { dev: `bun src/index.ts` }, dependencies: {} }, null, 2) + `
|
|
41812
41813
|
`);
|
|
41813
|
-
writeFileSync10(
|
|
41814
|
+
writeFileSync10(join18(skillDir, "tsconfig.json"), JSON.stringify({ compilerOptions: { target: "ES2022", module: "ESNext", moduleResolution: "bundler", strict: true, outDir: "dist" }, include: ["src/**/*.ts"] }, null, 2) + `
|
|
41814
41815
|
`);
|
|
41815
41816
|
clearRegistryCache();
|
|
41816
41817
|
if (options.json)
|
|
@@ -41819,8 +41820,8 @@ function handleCreate(name, options) {
|
|
|
41819
41820
|
console.log(chalk9.green(`\u2713 Created custom skill '${bare}' at ${skillDir}`));
|
|
41820
41821
|
console.log(chalk9.dim(` Category: ${options.category}`));
|
|
41821
41822
|
console.log(chalk9.dim(` Tags: ${tags.join(", ")}`));
|
|
41822
|
-
console.log(` ${chalk9.cyan("Edit:")} ${
|
|
41823
|
-
console.log(` ${chalk9.cyan("Run:")} bun ${
|
|
41823
|
+
console.log(` ${chalk9.cyan("Edit:")} ${join18(skillDir, "src", "index.ts")}`);
|
|
41824
|
+
console.log(` ${chalk9.cyan("Run:")} bun ${join18(skillDir, "src", "index.ts")}`);
|
|
41824
41825
|
}
|
|
41825
41826
|
}
|
|
41826
41827
|
function handleSync(options) {
|
|
@@ -42763,8 +42764,688 @@ var init_feedback2 = __esm(() => {
|
|
|
42763
42764
|
|
|
42764
42765
|
// src/cli/index.tsx
|
|
42765
42766
|
init_esm();
|
|
42766
|
-
init_package();
|
|
42767
42767
|
import { render } from "ink";
|
|
42768
|
+
|
|
42769
|
+
// node_modules/@hasna/events/dist/commander.js
|
|
42770
|
+
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
42771
|
+
import { existsSync } from "fs";
|
|
42772
|
+
import { homedir } from "os";
|
|
42773
|
+
import { join } from "path";
|
|
42774
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
42775
|
+
import { randomUUID } from "crypto";
|
|
42776
|
+
import { spawn } from "child_process";
|
|
42777
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
42778
|
+
function getPathValue(input, path) {
|
|
42779
|
+
return path.split(".").reduce((value, part) => {
|
|
42780
|
+
if (value && typeof value === "object" && part in value) {
|
|
42781
|
+
return value[part];
|
|
42782
|
+
}
|
|
42783
|
+
return;
|
|
42784
|
+
}, input);
|
|
42785
|
+
}
|
|
42786
|
+
function wildcardToRegExp(pattern) {
|
|
42787
|
+
const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
|
|
42788
|
+
return new RegExp(`^${escaped}$`);
|
|
42789
|
+
}
|
|
42790
|
+
function matchString(value, matcher) {
|
|
42791
|
+
if (matcher === undefined)
|
|
42792
|
+
return true;
|
|
42793
|
+
if (value === undefined)
|
|
42794
|
+
return false;
|
|
42795
|
+
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
42796
|
+
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
42797
|
+
}
|
|
42798
|
+
function matchRecord(input, matcher) {
|
|
42799
|
+
if (!matcher)
|
|
42800
|
+
return true;
|
|
42801
|
+
return Object.entries(matcher).every(([path, expected]) => {
|
|
42802
|
+
const actual = getPathValue(input, path);
|
|
42803
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
42804
|
+
return matchString(actual === undefined ? undefined : String(actual), expected);
|
|
42805
|
+
}
|
|
42806
|
+
return actual === expected;
|
|
42807
|
+
});
|
|
42808
|
+
}
|
|
42809
|
+
function eventMatchesFilter(event, filter) {
|
|
42810
|
+
return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
|
|
42811
|
+
}
|
|
42812
|
+
function channelMatchesEvent(channel, event) {
|
|
42813
|
+
if (!channel.enabled)
|
|
42814
|
+
return false;
|
|
42815
|
+
if (!channel.filters || channel.filters.length === 0)
|
|
42816
|
+
return true;
|
|
42817
|
+
return channel.filters.some((filter) => eventMatchesFilter(event, filter));
|
|
42818
|
+
}
|
|
42819
|
+
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
42820
|
+
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
42821
|
+
function getEventsDataDir(override) {
|
|
42822
|
+
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
|
|
42823
|
+
}
|
|
42824
|
+
|
|
42825
|
+
class JsonEventsStore {
|
|
42826
|
+
dataDir;
|
|
42827
|
+
channelsPath;
|
|
42828
|
+
eventsPath;
|
|
42829
|
+
deliveriesPath;
|
|
42830
|
+
constructor(dataDir = getEventsDataDir()) {
|
|
42831
|
+
this.dataDir = dataDir;
|
|
42832
|
+
this.channelsPath = join(dataDir, "channels.json");
|
|
42833
|
+
this.eventsPath = join(dataDir, "events.json");
|
|
42834
|
+
this.deliveriesPath = join(dataDir, "deliveries.json");
|
|
42835
|
+
}
|
|
42836
|
+
async init() {
|
|
42837
|
+
await mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
42838
|
+
await chmod(this.dataDir, 448).catch(() => {
|
|
42839
|
+
return;
|
|
42840
|
+
});
|
|
42841
|
+
await this.ensureArrayFile(this.channelsPath);
|
|
42842
|
+
await this.ensureArrayFile(this.eventsPath);
|
|
42843
|
+
await this.ensureArrayFile(this.deliveriesPath);
|
|
42844
|
+
}
|
|
42845
|
+
async addChannel(channel) {
|
|
42846
|
+
await this.init();
|
|
42847
|
+
const channels = await this.readJson(this.channelsPath, []);
|
|
42848
|
+
const index = channels.findIndex((item) => item.id === channel.id);
|
|
42849
|
+
if (index >= 0) {
|
|
42850
|
+
channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
|
|
42851
|
+
} else {
|
|
42852
|
+
channels.push(channel);
|
|
42853
|
+
}
|
|
42854
|
+
await this.writeJson(this.channelsPath, channels);
|
|
42855
|
+
return index >= 0 ? channels[index] : channel;
|
|
42856
|
+
}
|
|
42857
|
+
async listChannels() {
|
|
42858
|
+
await this.init();
|
|
42859
|
+
return this.readJson(this.channelsPath, []);
|
|
42860
|
+
}
|
|
42861
|
+
async getChannel(id) {
|
|
42862
|
+
const channels = await this.listChannels();
|
|
42863
|
+
return channels.find((channel) => channel.id === id);
|
|
42864
|
+
}
|
|
42865
|
+
async removeChannel(id) {
|
|
42866
|
+
await this.init();
|
|
42867
|
+
const channels = await this.readJson(this.channelsPath, []);
|
|
42868
|
+
const next = channels.filter((channel) => channel.id !== id);
|
|
42869
|
+
await this.writeJson(this.channelsPath, next);
|
|
42870
|
+
return next.length !== channels.length;
|
|
42871
|
+
}
|
|
42872
|
+
async appendEvent(event) {
|
|
42873
|
+
await this.init();
|
|
42874
|
+
const events = await this.readJson(this.eventsPath, []);
|
|
42875
|
+
events.push(event);
|
|
42876
|
+
await this.writeJson(this.eventsPath, events);
|
|
42877
|
+
return event;
|
|
42878
|
+
}
|
|
42879
|
+
async listEvents() {
|
|
42880
|
+
await this.init();
|
|
42881
|
+
return this.readJson(this.eventsPath, []);
|
|
42882
|
+
}
|
|
42883
|
+
async findEventByIdentity(identity) {
|
|
42884
|
+
const events = await this.listEvents();
|
|
42885
|
+
return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
|
|
42886
|
+
}
|
|
42887
|
+
async appendDelivery(result) {
|
|
42888
|
+
await this.init();
|
|
42889
|
+
const deliveries = await this.readJson(this.deliveriesPath, []);
|
|
42890
|
+
deliveries.push(result);
|
|
42891
|
+
await this.writeJson(this.deliveriesPath, deliveries);
|
|
42892
|
+
return result;
|
|
42893
|
+
}
|
|
42894
|
+
async listDeliveries() {
|
|
42895
|
+
await this.init();
|
|
42896
|
+
return this.readJson(this.deliveriesPath, []);
|
|
42897
|
+
}
|
|
42898
|
+
async exportData() {
|
|
42899
|
+
return {
|
|
42900
|
+
channels: await this.listChannels(),
|
|
42901
|
+
events: await this.listEvents(),
|
|
42902
|
+
deliveries: await this.listDeliveries()
|
|
42903
|
+
};
|
|
42904
|
+
}
|
|
42905
|
+
async ensureArrayFile(path) {
|
|
42906
|
+
if (!existsSync(path)) {
|
|
42907
|
+
await writeFile(path, `[]
|
|
42908
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
42909
|
+
}
|
|
42910
|
+
await chmod(path, 384).catch(() => {
|
|
42911
|
+
return;
|
|
42912
|
+
});
|
|
42913
|
+
}
|
|
42914
|
+
async readJson(path, fallback) {
|
|
42915
|
+
try {
|
|
42916
|
+
const raw = await readFile(path, "utf-8");
|
|
42917
|
+
if (!raw.trim())
|
|
42918
|
+
return fallback;
|
|
42919
|
+
return JSON.parse(raw);
|
|
42920
|
+
} catch (error) {
|
|
42921
|
+
if (error.code === "ENOENT")
|
|
42922
|
+
return fallback;
|
|
42923
|
+
throw error;
|
|
42924
|
+
}
|
|
42925
|
+
}
|
|
42926
|
+
async writeJson(path, value) {
|
|
42927
|
+
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
42928
|
+
await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
|
|
42929
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
42930
|
+
await rename(tempPath, path);
|
|
42931
|
+
await chmod(path, 384).catch(() => {
|
|
42932
|
+
return;
|
|
42933
|
+
});
|
|
42934
|
+
}
|
|
42935
|
+
}
|
|
42936
|
+
var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
42937
|
+
function buildSignatureBase(timestamp, body) {
|
|
42938
|
+
return `${timestamp}.${body}`;
|
|
42939
|
+
}
|
|
42940
|
+
function signPayload(secret, timestamp, body) {
|
|
42941
|
+
const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
|
|
42942
|
+
return `sha256=${digest}`;
|
|
42943
|
+
}
|
|
42944
|
+
function now() {
|
|
42945
|
+
return new Date().toISOString();
|
|
42946
|
+
}
|
|
42947
|
+
function truncate(value, max = 4096) {
|
|
42948
|
+
return value.length > max ? `${value.slice(0, max)}...` : value;
|
|
42949
|
+
}
|
|
42950
|
+
function buildWebhookRequest(event, channel) {
|
|
42951
|
+
if (!channel.webhook)
|
|
42952
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
42953
|
+
const body = JSON.stringify(event);
|
|
42954
|
+
const timestamp = event.time;
|
|
42955
|
+
const headers = {
|
|
42956
|
+
"Content-Type": "application/json",
|
|
42957
|
+
"User-Agent": "@hasna/events",
|
|
42958
|
+
"X-Hasna-Event-Id": event.id,
|
|
42959
|
+
"X-Hasna-Event-Type": event.type,
|
|
42960
|
+
"X-Hasna-Timestamp": timestamp,
|
|
42961
|
+
...channel.webhook.headers
|
|
42962
|
+
};
|
|
42963
|
+
if (channel.webhook.secret) {
|
|
42964
|
+
headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
|
|
42965
|
+
}
|
|
42966
|
+
return { body, headers };
|
|
42967
|
+
}
|
|
42968
|
+
async function dispatchWebhook(event, channel, options = {}) {
|
|
42969
|
+
if (!channel.webhook)
|
|
42970
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
42971
|
+
const startedAt = now();
|
|
42972
|
+
const { body, headers } = buildWebhookRequest(event, channel);
|
|
42973
|
+
const controller = new AbortController;
|
|
42974
|
+
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
42975
|
+
try {
|
|
42976
|
+
const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
|
|
42977
|
+
method: "POST",
|
|
42978
|
+
headers,
|
|
42979
|
+
body,
|
|
42980
|
+
signal: controller.signal
|
|
42981
|
+
});
|
|
42982
|
+
const responseBody = truncate(await response.text());
|
|
42983
|
+
return {
|
|
42984
|
+
attempt: 1,
|
|
42985
|
+
status: response.ok ? "success" : "failed",
|
|
42986
|
+
startedAt,
|
|
42987
|
+
completedAt: now(),
|
|
42988
|
+
responseStatus: response.status,
|
|
42989
|
+
responseBody,
|
|
42990
|
+
error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
|
|
42991
|
+
};
|
|
42992
|
+
} catch (error) {
|
|
42993
|
+
return {
|
|
42994
|
+
attempt: 1,
|
|
42995
|
+
status: "failed",
|
|
42996
|
+
startedAt,
|
|
42997
|
+
completedAt: now(),
|
|
42998
|
+
error: error instanceof Error ? error.message : String(error)
|
|
42999
|
+
};
|
|
43000
|
+
} finally {
|
|
43001
|
+
clearTimeout(timeout);
|
|
43002
|
+
}
|
|
43003
|
+
}
|
|
43004
|
+
async function dispatchCommand(event, channel) {
|
|
43005
|
+
if (!channel.command)
|
|
43006
|
+
throw new Error(`Channel ${channel.id} has no command config`);
|
|
43007
|
+
const startedAt = now();
|
|
43008
|
+
const eventJson = JSON.stringify(event);
|
|
43009
|
+
const env = {
|
|
43010
|
+
...process.env,
|
|
43011
|
+
...channel.command.env,
|
|
43012
|
+
HASNA_CHANNEL_ID: channel.id,
|
|
43013
|
+
HASNA_EVENT_ID: event.id,
|
|
43014
|
+
HASNA_EVENT_TYPE: event.type,
|
|
43015
|
+
HASNA_EVENT_SOURCE: event.source,
|
|
43016
|
+
HASNA_EVENT_SUBJECT: event.subject ?? "",
|
|
43017
|
+
HASNA_EVENT_SEVERITY: event.severity,
|
|
43018
|
+
HASNA_EVENT_TIME: event.time,
|
|
43019
|
+
HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
|
|
43020
|
+
HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
|
|
43021
|
+
HASNA_EVENT_JSON: eventJson
|
|
43022
|
+
};
|
|
43023
|
+
return new Promise((resolve) => {
|
|
43024
|
+
const child = spawn(channel.command.command, channel.command.args ?? [], {
|
|
43025
|
+
cwd: channel.command.cwd,
|
|
43026
|
+
env,
|
|
43027
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
43028
|
+
});
|
|
43029
|
+
let stdout = "";
|
|
43030
|
+
let stderr = "";
|
|
43031
|
+
const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
|
|
43032
|
+
child.stdin.end(eventJson);
|
|
43033
|
+
child.stdout.on("data", (chunk) => {
|
|
43034
|
+
stdout += chunk.toString();
|
|
43035
|
+
});
|
|
43036
|
+
child.stderr.on("data", (chunk) => {
|
|
43037
|
+
stderr += chunk.toString();
|
|
43038
|
+
});
|
|
43039
|
+
child.on("error", (error) => {
|
|
43040
|
+
clearTimeout(timeout);
|
|
43041
|
+
resolve({
|
|
43042
|
+
attempt: 1,
|
|
43043
|
+
status: "failed",
|
|
43044
|
+
startedAt,
|
|
43045
|
+
completedAt: now(),
|
|
43046
|
+
stdout: truncate(stdout),
|
|
43047
|
+
stderr: truncate(stderr),
|
|
43048
|
+
error: error.message
|
|
43049
|
+
});
|
|
43050
|
+
});
|
|
43051
|
+
child.on("close", (code, signal) => {
|
|
43052
|
+
clearTimeout(timeout);
|
|
43053
|
+
const success = code === 0;
|
|
43054
|
+
resolve({
|
|
43055
|
+
attempt: 1,
|
|
43056
|
+
status: success ? "success" : "failed",
|
|
43057
|
+
startedAt,
|
|
43058
|
+
completedAt: now(),
|
|
43059
|
+
stdout: truncate(stdout),
|
|
43060
|
+
stderr: truncate(stderr),
|
|
43061
|
+
error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
|
|
43062
|
+
});
|
|
43063
|
+
});
|
|
43064
|
+
});
|
|
43065
|
+
}
|
|
43066
|
+
async function dispatchChannel(event, channel, options = {}) {
|
|
43067
|
+
if (channel.transport === "webhook")
|
|
43068
|
+
return dispatchWebhook(event, channel, options);
|
|
43069
|
+
if (channel.transport === "command")
|
|
43070
|
+
return dispatchCommand(event, channel);
|
|
43071
|
+
return {
|
|
43072
|
+
attempt: 1,
|
|
43073
|
+
status: "skipped",
|
|
43074
|
+
startedAt: now(),
|
|
43075
|
+
completedAt: now(),
|
|
43076
|
+
error: `Unsupported transport: ${channel.transport}`
|
|
43077
|
+
};
|
|
43078
|
+
}
|
|
43079
|
+
function createDeliveryResult(event, channel, attempts) {
|
|
43080
|
+
const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
|
|
43081
|
+
return {
|
|
43082
|
+
id: randomUUID(),
|
|
43083
|
+
eventId: event.id,
|
|
43084
|
+
channelId: channel.id,
|
|
43085
|
+
transport: channel.transport,
|
|
43086
|
+
status,
|
|
43087
|
+
attempts,
|
|
43088
|
+
createdAt: attempts[0]?.startedAt ?? now(),
|
|
43089
|
+
completedAt: attempts.at(-1)?.completedAt ?? now()
|
|
43090
|
+
};
|
|
43091
|
+
}
|
|
43092
|
+
function createEvent(input) {
|
|
43093
|
+
return {
|
|
43094
|
+
id: input.id ?? randomUUID2(),
|
|
43095
|
+
source: input.source,
|
|
43096
|
+
type: input.type,
|
|
43097
|
+
time: normalizeTime(input.time),
|
|
43098
|
+
subject: input.subject,
|
|
43099
|
+
severity: input.severity ?? "info",
|
|
43100
|
+
data: input.data ?? {},
|
|
43101
|
+
message: input.message,
|
|
43102
|
+
dedupeKey: input.dedupeKey,
|
|
43103
|
+
schemaVersion: input.schemaVersion ?? "1.0",
|
|
43104
|
+
metadata: input.metadata ?? {}
|
|
43105
|
+
};
|
|
43106
|
+
}
|
|
43107
|
+
|
|
43108
|
+
class EventsClient {
|
|
43109
|
+
store;
|
|
43110
|
+
redactors;
|
|
43111
|
+
transportOptions;
|
|
43112
|
+
constructor(options = {}) {
|
|
43113
|
+
this.store = options.store ?? new JsonEventsStore(options.dataDir);
|
|
43114
|
+
this.redactors = options.redactors ?? [];
|
|
43115
|
+
this.transportOptions = { fetchImpl: options.fetchImpl };
|
|
43116
|
+
}
|
|
43117
|
+
async addChannel(input) {
|
|
43118
|
+
const timestamp = new Date().toISOString();
|
|
43119
|
+
return this.store.addChannel({
|
|
43120
|
+
...input,
|
|
43121
|
+
createdAt: input.createdAt ?? timestamp,
|
|
43122
|
+
updatedAt: input.updatedAt ?? timestamp
|
|
43123
|
+
});
|
|
43124
|
+
}
|
|
43125
|
+
async listChannels() {
|
|
43126
|
+
return this.store.listChannels();
|
|
43127
|
+
}
|
|
43128
|
+
async removeChannel(id) {
|
|
43129
|
+
return this.store.removeChannel(id);
|
|
43130
|
+
}
|
|
43131
|
+
async emit(input, options = {}) {
|
|
43132
|
+
const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
|
|
43133
|
+
if (options.dedupe !== false) {
|
|
43134
|
+
const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
|
|
43135
|
+
if (existing) {
|
|
43136
|
+
return { event: existing, deliveries: [], deduped: true };
|
|
43137
|
+
}
|
|
43138
|
+
}
|
|
43139
|
+
await this.store.appendEvent(event);
|
|
43140
|
+
const deliveries = options.deliver === false ? [] : await this.deliver(event);
|
|
43141
|
+
return { event, deliveries, deduped: false };
|
|
43142
|
+
}
|
|
43143
|
+
async listEvents() {
|
|
43144
|
+
return this.store.listEvents();
|
|
43145
|
+
}
|
|
43146
|
+
async listDeliveries() {
|
|
43147
|
+
return this.store.listDeliveries();
|
|
43148
|
+
}
|
|
43149
|
+
async deliver(event) {
|
|
43150
|
+
const channels = await this.store.listChannels();
|
|
43151
|
+
const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
|
|
43152
|
+
const deliveries = [];
|
|
43153
|
+
for (const channel of selected) {
|
|
43154
|
+
const eventForChannel = await this.applyRedaction(event, channel);
|
|
43155
|
+
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
43156
|
+
await this.store.appendDelivery(result);
|
|
43157
|
+
deliveries.push(result);
|
|
43158
|
+
}
|
|
43159
|
+
return deliveries;
|
|
43160
|
+
}
|
|
43161
|
+
async testChannel(id, input = {}) {
|
|
43162
|
+
const channel = await this.store.getChannel(id);
|
|
43163
|
+
if (!channel)
|
|
43164
|
+
throw new Error(`Channel not found: ${id}`);
|
|
43165
|
+
const event = createEvent({
|
|
43166
|
+
source: input.source ?? "hasna.events",
|
|
43167
|
+
type: input.type ?? "events.test",
|
|
43168
|
+
subject: input.subject ?? id,
|
|
43169
|
+
severity: input.severity ?? "info",
|
|
43170
|
+
data: input.data ?? { test: true },
|
|
43171
|
+
message: input.message ?? "Hasna events test delivery",
|
|
43172
|
+
dedupeKey: input.dedupeKey,
|
|
43173
|
+
schemaVersion: input.schemaVersion,
|
|
43174
|
+
metadata: input.metadata,
|
|
43175
|
+
time: input.time,
|
|
43176
|
+
id: input.id
|
|
43177
|
+
});
|
|
43178
|
+
const eventForChannel = await this.applyRedaction(event, channel);
|
|
43179
|
+
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
43180
|
+
await this.store.appendDelivery(result);
|
|
43181
|
+
return result;
|
|
43182
|
+
}
|
|
43183
|
+
async replay(options = {}) {
|
|
43184
|
+
const events = (await this.store.listEvents()).filter((event) => {
|
|
43185
|
+
if (options.eventId && event.id !== options.eventId)
|
|
43186
|
+
return false;
|
|
43187
|
+
if (options.source && event.source !== options.source)
|
|
43188
|
+
return false;
|
|
43189
|
+
if (options.type && event.type !== options.type)
|
|
43190
|
+
return false;
|
|
43191
|
+
return true;
|
|
43192
|
+
});
|
|
43193
|
+
if (options.dryRun)
|
|
43194
|
+
return { events, deliveries: [] };
|
|
43195
|
+
const deliveries = [];
|
|
43196
|
+
for (const event of events) {
|
|
43197
|
+
deliveries.push(...await this.deliver(event));
|
|
43198
|
+
}
|
|
43199
|
+
return { events, deliveries };
|
|
43200
|
+
}
|
|
43201
|
+
async applyRedaction(event, channel) {
|
|
43202
|
+
let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
|
|
43203
|
+
for (const redactor of this.redactors) {
|
|
43204
|
+
next = await redactor(next, channel);
|
|
43205
|
+
}
|
|
43206
|
+
return next;
|
|
43207
|
+
}
|
|
43208
|
+
async deliverWithRetry(event, channel) {
|
|
43209
|
+
const policy = normalizeRetryPolicy(channel.retry);
|
|
43210
|
+
const attempts = [];
|
|
43211
|
+
for (let index = 0;index < policy.maxAttempts; index += 1) {
|
|
43212
|
+
const attempt = await dispatchChannel(event, channel, this.transportOptions);
|
|
43213
|
+
attempt.attempt = index + 1;
|
|
43214
|
+
if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
|
|
43215
|
+
attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
|
|
43216
|
+
}
|
|
43217
|
+
attempts.push(attempt);
|
|
43218
|
+
if (attempt.status !== "failed")
|
|
43219
|
+
break;
|
|
43220
|
+
if (attempt.nextBackoffMs)
|
|
43221
|
+
await Bun.sleep(attempt.nextBackoffMs);
|
|
43222
|
+
}
|
|
43223
|
+
return createDeliveryResult(event, channel, attempts);
|
|
43224
|
+
}
|
|
43225
|
+
}
|
|
43226
|
+
function redactPaths(event, paths, replacement = "[REDACTED]") {
|
|
43227
|
+
if (paths.length === 0)
|
|
43228
|
+
return event;
|
|
43229
|
+
const copy = structuredClone(event);
|
|
43230
|
+
for (const path of paths) {
|
|
43231
|
+
setPath(copy, path, replacement);
|
|
43232
|
+
}
|
|
43233
|
+
return copy;
|
|
43234
|
+
}
|
|
43235
|
+
function sanitizeChannelForOutput(channel) {
|
|
43236
|
+
const copy = structuredClone(channel);
|
|
43237
|
+
if (copy.webhook?.secret)
|
|
43238
|
+
copy.webhook.secret = "[REDACTED]";
|
|
43239
|
+
if (copy.command?.env) {
|
|
43240
|
+
copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
|
|
43241
|
+
}
|
|
43242
|
+
return copy;
|
|
43243
|
+
}
|
|
43244
|
+
function sanitizeChannelsForOutput(channels) {
|
|
43245
|
+
return channels.map(sanitizeChannelForOutput);
|
|
43246
|
+
}
|
|
43247
|
+
function redactSensitiveKeys(event, replacement = "[REDACTED]") {
|
|
43248
|
+
return redactValue(event, replacement);
|
|
43249
|
+
}
|
|
43250
|
+
function shouldRedactKey(key) {
|
|
43251
|
+
return /secret|token|password|api[_-]?key|authorization/i.test(key);
|
|
43252
|
+
}
|
|
43253
|
+
function redactValue(value, replacement) {
|
|
43254
|
+
if (Array.isArray(value))
|
|
43255
|
+
return value.map((item) => redactValue(item, replacement));
|
|
43256
|
+
if (!value || typeof value !== "object")
|
|
43257
|
+
return value;
|
|
43258
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
43259
|
+
key,
|
|
43260
|
+
shouldRedactKey(key) ? replacement : redactValue(item, replacement)
|
|
43261
|
+
]));
|
|
43262
|
+
}
|
|
43263
|
+
function setPath(input, path, replacement) {
|
|
43264
|
+
const parts = path.split(".");
|
|
43265
|
+
let cursor = input;
|
|
43266
|
+
for (const part of parts.slice(0, -1)) {
|
|
43267
|
+
const next = cursor[part];
|
|
43268
|
+
if (!next || typeof next !== "object")
|
|
43269
|
+
return;
|
|
43270
|
+
cursor = next;
|
|
43271
|
+
}
|
|
43272
|
+
const last = parts.at(-1);
|
|
43273
|
+
if (last && last in cursor)
|
|
43274
|
+
cursor[last] = replacement;
|
|
43275
|
+
}
|
|
43276
|
+
function normalizeTime(value) {
|
|
43277
|
+
if (!value)
|
|
43278
|
+
return new Date().toISOString();
|
|
43279
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
43280
|
+
}
|
|
43281
|
+
function normalizeRetryPolicy(policy) {
|
|
43282
|
+
return {
|
|
43283
|
+
maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
|
|
43284
|
+
backoffMs: Math.max(0, policy?.backoffMs ?? 250),
|
|
43285
|
+
multiplier: Math.max(1, policy?.multiplier ?? 2)
|
|
43286
|
+
};
|
|
43287
|
+
}
|
|
43288
|
+
function parseJsonObject(value, fallback) {
|
|
43289
|
+
if (!value)
|
|
43290
|
+
return fallback;
|
|
43291
|
+
const parsed = JSON.parse(value);
|
|
43292
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
43293
|
+
throw new Error("Expected a JSON object");
|
|
43294
|
+
}
|
|
43295
|
+
return parsed;
|
|
43296
|
+
}
|
|
43297
|
+
function parseHeaders(values) {
|
|
43298
|
+
if (!values?.length)
|
|
43299
|
+
return;
|
|
43300
|
+
const headers = {};
|
|
43301
|
+
for (const value of values) {
|
|
43302
|
+
const separator = value.indexOf("=");
|
|
43303
|
+
if (separator === -1)
|
|
43304
|
+
throw new Error(`Invalid header, expected name=value: ${value}`);
|
|
43305
|
+
headers[value.slice(0, separator)] = value.slice(separator + 1);
|
|
43306
|
+
}
|
|
43307
|
+
return headers;
|
|
43308
|
+
}
|
|
43309
|
+
function parseFilter(options) {
|
|
43310
|
+
const filter2 = {};
|
|
43311
|
+
if (options.source)
|
|
43312
|
+
filter2.source = options.source;
|
|
43313
|
+
if (options.type)
|
|
43314
|
+
filter2.type = options.type;
|
|
43315
|
+
if (options.subject)
|
|
43316
|
+
filter2.subject = options.subject;
|
|
43317
|
+
if (options.severity)
|
|
43318
|
+
filter2.severity = options.severity;
|
|
43319
|
+
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
43320
|
+
}
|
|
43321
|
+
function createClient(options) {
|
|
43322
|
+
if (options.createClient)
|
|
43323
|
+
return options.createClient();
|
|
43324
|
+
return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
|
|
43325
|
+
}
|
|
43326
|
+
function print(value, json, text) {
|
|
43327
|
+
if (json)
|
|
43328
|
+
console.log(JSON.stringify(value, null, 2));
|
|
43329
|
+
else
|
|
43330
|
+
console.log(text);
|
|
43331
|
+
}
|
|
43332
|
+
function registerWebhookCommands(program2, options) {
|
|
43333
|
+
const webhooks = program2.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
|
|
43334
|
+
webhooks.command("add").description("Add or replace a webhook or command subscription").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Subscription/channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions) => {
|
|
43335
|
+
const timestamp = new Date().toISOString();
|
|
43336
|
+
const channel = {
|
|
43337
|
+
id: actionOptions.id,
|
|
43338
|
+
name: actionOptions.name,
|
|
43339
|
+
enabled: !actionOptions.disabled,
|
|
43340
|
+
transport: actionOptions.transport,
|
|
43341
|
+
filters: parseFilter(actionOptions),
|
|
43342
|
+
retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
|
|
43343
|
+
redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
|
|
43344
|
+
createdAt: timestamp,
|
|
43345
|
+
updatedAt: timestamp
|
|
43346
|
+
};
|
|
43347
|
+
if (actionOptions.transport === "webhook") {
|
|
43348
|
+
channel.webhook = { url: target, secret: actionOptions.secret, headers: parseHeaders(actionOptions.header), timeoutMs: actionOptions.timeoutMs };
|
|
43349
|
+
} else if (actionOptions.transport === "command") {
|
|
43350
|
+
channel.command = { command: target, args: actionOptions.arg ?? [], timeoutMs: actionOptions.timeoutMs };
|
|
43351
|
+
} else {
|
|
43352
|
+
throw new Error(`Transport ${actionOptions.transport} is reserved for future use and cannot be added yet`);
|
|
43353
|
+
}
|
|
43354
|
+
const saved = await createClient(options).addChannel(channel);
|
|
43355
|
+
print(sanitizeChannelForOutput(saved), Boolean(actionOptions.json), `Added ${saved.transport} channel ${saved.id}`);
|
|
43356
|
+
});
|
|
43357
|
+
webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
|
|
43358
|
+
const channels = await createClient(options).listChannels();
|
|
43359
|
+
if (actionOptions.json) {
|
|
43360
|
+
console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
|
|
43361
|
+
return;
|
|
43362
|
+
}
|
|
43363
|
+
if (!channels.length) {
|
|
43364
|
+
console.log("No channels configured.");
|
|
43365
|
+
return;
|
|
43366
|
+
}
|
|
43367
|
+
for (const channel of channels) {
|
|
43368
|
+
console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
|
|
43369
|
+
}
|
|
43370
|
+
});
|
|
43371
|
+
webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
|
|
43372
|
+
const removed = await createClient(options).removeChannel(id);
|
|
43373
|
+
print({ removed }, Boolean(actionOptions.json), removed ? `Removed ${id}` : `Channel not found: ${id}`);
|
|
43374
|
+
});
|
|
43375
|
+
webhooks.command("test").description("Send a test event to one subscription").argument("<id>", "Subscription/channel identifier").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events test delivery").option("--data <json>", "Event data JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
|
|
43376
|
+
const result = await createClient(options).testChannel(id, {
|
|
43377
|
+
source: options.source,
|
|
43378
|
+
type: actionOptions.type,
|
|
43379
|
+
subject: actionOptions.subject ?? id,
|
|
43380
|
+
message: actionOptions.message,
|
|
43381
|
+
data: parseJsonObject(actionOptions.data, { test: true })
|
|
43382
|
+
});
|
|
43383
|
+
print(result, Boolean(actionOptions.json), `${result.status}: ${result.channelId}`);
|
|
43384
|
+
});
|
|
43385
|
+
return webhooks;
|
|
43386
|
+
}
|
|
43387
|
+
function registerEventCommands(program2, options) {
|
|
43388
|
+
const events = program2.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
|
|
43389
|
+
events.command("emit").description("Emit an event from this app").argument("<type>", "Event type").option("--source <source>", "Event source override").option("--subject <subject>", "Event subject").option("--severity <severity>", "Event severity", "info").option("--message <message>", "Event message").option("--dedupe-key <key>", "Dedupe key").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("--no-deliver", "Record without delivering").option("--no-dedupe", "Allow duplicate id/dedupeKey events").option("-j, --json", "Print JSON output", false).action(async (type, actionOptions) => {
|
|
43390
|
+
const result = await createClient(options).emit({
|
|
43391
|
+
source: actionOptions.source ?? options.source,
|
|
43392
|
+
type,
|
|
43393
|
+
subject: actionOptions.subject,
|
|
43394
|
+
severity: actionOptions.severity,
|
|
43395
|
+
message: actionOptions.message,
|
|
43396
|
+
dedupeKey: actionOptions.dedupeKey,
|
|
43397
|
+
data: parseJsonObject(actionOptions.data, {}),
|
|
43398
|
+
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
43399
|
+
}, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
|
|
43400
|
+
print(result, Boolean(actionOptions.json), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
|
|
43401
|
+
});
|
|
43402
|
+
events.command("list").description("List recorded events").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--limit <n>", "Limit results", parseNumber).option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
|
|
43403
|
+
let rows = await createClient(options).listEvents();
|
|
43404
|
+
if (actionOptions.source)
|
|
43405
|
+
rows = rows.filter((event) => event.source === actionOptions.source);
|
|
43406
|
+
if (actionOptions.type)
|
|
43407
|
+
rows = rows.filter((event) => event.type === actionOptions.type);
|
|
43408
|
+
if (actionOptions.limit)
|
|
43409
|
+
rows = rows.slice(-actionOptions.limit);
|
|
43410
|
+
if (actionOptions.json) {
|
|
43411
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
43412
|
+
return;
|
|
43413
|
+
}
|
|
43414
|
+
if (!rows.length) {
|
|
43415
|
+
console.log("No events recorded.");
|
|
43416
|
+
return;
|
|
43417
|
+
}
|
|
43418
|
+
for (const event of rows)
|
|
43419
|
+
console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
|
|
43420
|
+
});
|
|
43421
|
+
events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--dry-run", "Preview without delivery", false).option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
|
|
43422
|
+
const result = await createClient(options).replay({
|
|
43423
|
+
eventId: actionOptions.id,
|
|
43424
|
+
source: actionOptions.source,
|
|
43425
|
+
type: actionOptions.type,
|
|
43426
|
+
dryRun: actionOptions.dryRun
|
|
43427
|
+
});
|
|
43428
|
+
print(result, Boolean(actionOptions.json), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
|
|
43429
|
+
});
|
|
43430
|
+
return events;
|
|
43431
|
+
}
|
|
43432
|
+
function registerEventsCommands(program2, options) {
|
|
43433
|
+
registerWebhookCommands(program2, options);
|
|
43434
|
+
registerEventCommands(program2, options);
|
|
43435
|
+
}
|
|
43436
|
+
function parseNumber(value) {
|
|
43437
|
+
const parsed = Number(value);
|
|
43438
|
+
if (!Number.isFinite(parsed))
|
|
43439
|
+
throw new Error(`Expected a number, got ${value}`);
|
|
43440
|
+
return parsed;
|
|
43441
|
+
}
|
|
43442
|
+
function collectValues(value, previous) {
|
|
43443
|
+
previous.push(value);
|
|
43444
|
+
return previous;
|
|
43445
|
+
}
|
|
43446
|
+
|
|
43447
|
+
// src/cli/index.tsx
|
|
43448
|
+
init_package();
|
|
42768
43449
|
import chalk14 from "chalk";
|
|
42769
43450
|
|
|
42770
43451
|
// src/cli/components/App.tsx
|
|
@@ -43975,6 +44656,7 @@ var { registerAuth: registerAuth2 } = await Promise.resolve().then(() => (init_a
|
|
|
43975
44656
|
registerAuth2(program2);
|
|
43976
44657
|
var { registerFeedback: registerFeedback2 } = await Promise.resolve().then(() => (init_feedback2(), exports_feedback));
|
|
43977
44658
|
registerFeedback2(program2);
|
|
44659
|
+
registerEventsCommands(program2, { source: "skills" });
|
|
43978
44660
|
program2.hook("preAction", (_thisCommand, actionCommand) => {
|
|
43979
44661
|
maybePrintFirstRunOnboarding(actionCommand, process.argv.slice(2), isTTY2);
|
|
43980
44662
|
});
|