skillio 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +225 -39
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -854,15 +854,27 @@ function readCodexMentions(options) {
|
|
|
854
854
|
}
|
|
855
855
|
|
|
856
856
|
// src/utils/period.ts
|
|
857
|
-
var
|
|
858
|
-
var
|
|
857
|
+
var SECOND_MS = 1000;
|
|
858
|
+
var MINUTE_MS = 60 * SECOND_MS;
|
|
859
|
+
var HOUR_MS = 60 * MINUTE_MS;
|
|
860
|
+
var DAY_MS = 24 * HOUR_MS;
|
|
861
|
+
var UNITS_MS = {
|
|
862
|
+
sec: SECOND_MS,
|
|
863
|
+
min: MINUTE_MS,
|
|
864
|
+
h: HOUR_MS,
|
|
865
|
+
d: DAY_MS,
|
|
866
|
+
w: 7 * DAY_MS,
|
|
867
|
+
m: 30 * DAY_MS,
|
|
868
|
+
y: 365 * DAY_MS
|
|
869
|
+
};
|
|
859
870
|
function parsePeriod(period) {
|
|
860
871
|
if (period === "all")
|
|
861
872
|
return Number.POSITIVE_INFINITY;
|
|
862
|
-
const match = period.match(/^(\d+)([
|
|
863
|
-
if (!match)
|
|
864
|
-
throw new Error(`Invalid period: "${period}". Use values like 7d, 2w, 1m, 1y, all.`);
|
|
865
|
-
|
|
873
|
+
const match = period.match(/^(\d+)(sec|min|[hdwmy])$/);
|
|
874
|
+
if (!match) {
|
|
875
|
+
throw new Error(`Invalid period: "${period}". Use values like 30sec, 5min, 12h, 7d, 2w, 1m, 1y, all.`);
|
|
876
|
+
}
|
|
877
|
+
const unit = UNITS_MS[match[2] ?? ""] ?? 0;
|
|
866
878
|
return Number(match[1]) * unit;
|
|
867
879
|
}
|
|
868
880
|
|
|
@@ -885,7 +897,7 @@ function toRows(counts) {
|
|
|
885
897
|
async function runAudit(args) {
|
|
886
898
|
const agents = parseAgents(args.agent);
|
|
887
899
|
const allTime = !args.since && args.period === "all";
|
|
888
|
-
const since = args.since ? new Date(`${args.since}T00:00:00`) : args.period === "all" ? new Date(0) : new Date(Date.now() - parsePeriod(args.period)
|
|
900
|
+
const since = args.since ? new Date(`${args.since}T00:00:00`) : args.period === "all" ? new Date(0) : new Date(Date.now() - parsePeriod(args.period));
|
|
889
901
|
const scanAllFiles = allTime || args["scan-all-files"];
|
|
890
902
|
if (Number.isNaN(since.getTime())) {
|
|
891
903
|
console.error(`Invalid --since value: ${args.since}`);
|
|
@@ -959,7 +971,12 @@ var auditArgs = {
|
|
|
959
971
|
alias: "a",
|
|
960
972
|
description: "claude-code, codex (default: both; pass space-separated for both)"
|
|
961
973
|
},
|
|
962
|
-
period: {
|
|
974
|
+
period: {
|
|
975
|
+
type: "string",
|
|
976
|
+
alias: "p",
|
|
977
|
+
default: "all",
|
|
978
|
+
description: "30sec, 5min, 12h, 7d, 2w, 1m, 1y, all"
|
|
979
|
+
},
|
|
963
980
|
since: { type: "string", description: "yyyy-mm-dd, overrides --period" },
|
|
964
981
|
mode: { type: "string", description: "attributed | activations | mentions" },
|
|
965
982
|
format: { type: "string", default: "text", description: "text | json" },
|
|
@@ -974,16 +991,9 @@ var auditArgs = {
|
|
|
974
991
|
};
|
|
975
992
|
|
|
976
993
|
// src/lock/file.ts
|
|
977
|
-
import {
|
|
978
|
-
copyFileSync,
|
|
979
|
-
existsSync as existsSync4,
|
|
980
|
-
mkdirSync,
|
|
981
|
-
readFileSync as readFileSync4,
|
|
982
|
-
renameSync,
|
|
983
|
-
writeFileSync
|
|
984
|
-
} from "node:fs";
|
|
994
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, renameSync, writeFileSync } from "node:fs";
|
|
985
995
|
import { homedir as homedir3 } from "node:os";
|
|
986
|
-
import {
|
|
996
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
987
997
|
function getLockPath(global) {
|
|
988
998
|
return global ? join5(homedir3(), ".agents", ".skill-lock.json") : "skills-lock.json";
|
|
989
999
|
}
|
|
@@ -999,43 +1009,132 @@ function writeLock(path, lock) {
|
|
|
999
1009
|
`);
|
|
1000
1010
|
renameSync(tmp, path);
|
|
1001
1011
|
}
|
|
1002
|
-
function
|
|
1003
|
-
return join5(dirname2(path), ".tmp", `${basename(path)}.bak`);
|
|
1004
|
-
}
|
|
1005
|
-
function backupLock(path) {
|
|
1006
|
-
const backupPath = getBackupPath(path);
|
|
1007
|
-
mkdirSync(dirname2(backupPath), { recursive: true });
|
|
1008
|
-
copyFileSync(path, backupPath);
|
|
1009
|
-
return backupPath;
|
|
1010
|
-
}
|
|
1011
|
-
function removeSkillFromLock(path, skill, { skipBackup = false } = {}) {
|
|
1012
|
+
function removeSkillFromLock(path, skill) {
|
|
1012
1013
|
if (!existsSync4(path))
|
|
1013
1014
|
return { removed: false };
|
|
1014
1015
|
const lock = readLock(path);
|
|
1015
1016
|
if (!Object.hasOwn(lock.skills, skill))
|
|
1016
1017
|
return { removed: false };
|
|
1017
|
-
const backupPath = skipBackup ? undefined : backupLock(path);
|
|
1018
1018
|
delete lock.skills[skill];
|
|
1019
1019
|
writeLock(path, lock);
|
|
1020
|
-
return { removed: true
|
|
1020
|
+
return { removed: true };
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// src/utils/skill-files.ts
|
|
1024
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
1025
|
+
import { homedir as homedir4 } from "node:os";
|
|
1026
|
+
import { dirname as dirname3, join as join6, resolve as resolve2 } from "node:path";
|
|
1027
|
+
var CHARS_PER_TOKEN = 4;
|
|
1028
|
+
function getSkillPathCandidates(name, lockPath, isGlobal) {
|
|
1029
|
+
if (isGlobal) {
|
|
1030
|
+
return [
|
|
1031
|
+
join6(homedir4(), ".claude", "skills", name, "SKILL.md"),
|
|
1032
|
+
join6(homedir4(), ".agents", "skills", name, "SKILL.md")
|
|
1033
|
+
];
|
|
1034
|
+
}
|
|
1035
|
+
return [join6(dirname3(resolve2(lockPath)), ".claude", "skills", name, "SKILL.md")];
|
|
1021
1036
|
}
|
|
1037
|
+
function findSkillFile(name, lockPath, isGlobal) {
|
|
1038
|
+
for (const p of getSkillPathCandidates(name, lockPath, isGlobal)) {
|
|
1039
|
+
if (existsSync5(p))
|
|
1040
|
+
return p;
|
|
1041
|
+
}
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
function extractFrontmatter(content) {
|
|
1045
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
1046
|
+
return match?.[1];
|
|
1047
|
+
}
|
|
1048
|
+
function estimateTokens(text) {
|
|
1049
|
+
return Math.round(text.length / CHARS_PER_TOKEN);
|
|
1050
|
+
}
|
|
1051
|
+
function countFrontmatterTokens(filePath) {
|
|
1052
|
+
const content = readFileSync5(filePath, "utf8");
|
|
1053
|
+
const fm = extractFrontmatter(content);
|
|
1054
|
+
if (fm === undefined)
|
|
1055
|
+
return;
|
|
1056
|
+
return estimateTokens(fm);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// src/commands/cost.ts
|
|
1060
|
+
var costCommand = defineCommand({
|
|
1061
|
+
meta: {
|
|
1062
|
+
description: "Estimate ambient token cost (frontmatter) of each skill in the lock file"
|
|
1063
|
+
},
|
|
1064
|
+
args: {
|
|
1065
|
+
global: { type: "boolean", alias: "g", default: false, description: "Use global lock file" },
|
|
1066
|
+
json: { type: "boolean", default: false, description: "Output as JSON" }
|
|
1067
|
+
},
|
|
1068
|
+
run({ args }) {
|
|
1069
|
+
const lockPath = getLockPath(args.global);
|
|
1070
|
+
const lock = readLock(lockPath);
|
|
1071
|
+
const names = Object.keys(lock.skills).sort();
|
|
1072
|
+
const rows = names.map((skill) => {
|
|
1073
|
+
const file = findSkillFile(skill, lockPath, args.global);
|
|
1074
|
+
if (!file)
|
|
1075
|
+
return { skill, tokens: "missing" };
|
|
1076
|
+
const tokens = countFrontmatterTokens(file);
|
|
1077
|
+
if (tokens === undefined)
|
|
1078
|
+
return { skill, tokens: "no-frontmatter" };
|
|
1079
|
+
return { skill, tokens };
|
|
1080
|
+
});
|
|
1081
|
+
if (args.json) {
|
|
1082
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
if (rows.length === 0) {
|
|
1086
|
+
console.log(`No skills in ${lockPath}`);
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
const nameWidth = Math.max(...rows.map((r) => r.skill.length));
|
|
1090
|
+
let total = 0;
|
|
1091
|
+
let missing = 0;
|
|
1092
|
+
for (const r of rows) {
|
|
1093
|
+
let cell;
|
|
1094
|
+
if (typeof r.tokens === "number") {
|
|
1095
|
+
cell = `~${r.tokens} tok`;
|
|
1096
|
+
total += r.tokens;
|
|
1097
|
+
} else if (r.tokens === "missing") {
|
|
1098
|
+
cell = "missing";
|
|
1099
|
+
missing += 1;
|
|
1100
|
+
} else {
|
|
1101
|
+
cell = "(no frontmatter)";
|
|
1102
|
+
}
|
|
1103
|
+
console.log(`${r.skill.padEnd(nameWidth)} ${cell}`);
|
|
1104
|
+
}
|
|
1105
|
+
console.log("");
|
|
1106
|
+
const tail = missing > 0 ? ` (${missing} missing)` : "";
|
|
1107
|
+
console.log(`Total: ~${total} tok across ${rows.length} skills${tail}`);
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1022
1110
|
|
|
1023
1111
|
// src/commands/list.ts
|
|
1024
1112
|
var listCommand = defineCommand({
|
|
1025
1113
|
meta: { description: "List skills in the lock file" },
|
|
1026
1114
|
args: {
|
|
1027
|
-
global: { type: "boolean", alias: "g", default: false, description: "Use global lock file" }
|
|
1115
|
+
global: { type: "boolean", alias: "g", default: false, description: "Use global lock file" },
|
|
1116
|
+
json: { type: "boolean", default: false, description: "Output as JSON array" }
|
|
1028
1117
|
},
|
|
1029
1118
|
run({ args }) {
|
|
1030
1119
|
const path = getLockPath(args.global);
|
|
1031
1120
|
const lock = readLock(path);
|
|
1032
1121
|
const skills = Object.keys(lock.skills).sort();
|
|
1033
|
-
|
|
1122
|
+
if (args.json) {
|
|
1123
|
+
console.log(JSON.stringify(skills, null, 2));
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
if (skills.length === 0) {
|
|
1127
|
+
console.log(`No skills in ${path}`);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
for (const skill of skills)
|
|
1131
|
+
console.log(skill);
|
|
1132
|
+
console.log("");
|
|
1133
|
+
console.log(`Total: ${skills.length} skill${skills.length === 1 ? "" : "s"} in ${path}`);
|
|
1034
1134
|
}
|
|
1035
1135
|
});
|
|
1036
1136
|
|
|
1037
1137
|
// src/commands/remove.ts
|
|
1038
|
-
import { existsSync as existsSync5 } from "node:fs";
|
|
1039
1138
|
var removeCommand = defineCommand({
|
|
1040
1139
|
meta: { description: "Remove one or more skills from the lock file" },
|
|
1041
1140
|
args: {
|
|
@@ -1057,22 +1156,105 @@ var removeCommand = defineCommand({
|
|
|
1057
1156
|
}
|
|
1058
1157
|
return;
|
|
1059
1158
|
}
|
|
1060
|
-
const backupPath = existsSync5(path) ? backupLock(path) : undefined;
|
|
1061
1159
|
for (const skill of skills) {
|
|
1062
|
-
const result = removeSkillFromLock(path, skill
|
|
1160
|
+
const result = removeSkillFromLock(path, skill);
|
|
1063
1161
|
if (result.removed) {
|
|
1064
1162
|
console.log(`Removed "${skill}" from ${path}`);
|
|
1065
1163
|
} else {
|
|
1066
1164
|
console.log(`"${skill}" is not in ${path}`);
|
|
1067
1165
|
}
|
|
1068
1166
|
}
|
|
1069
|
-
if (backupPath)
|
|
1070
|
-
console.log(`Backup: ${backupPath}`);
|
|
1071
1167
|
const updated = readLock(path);
|
|
1072
1168
|
console.log(JSON.stringify(Object.keys(updated.skills).sort(), null, 2));
|
|
1073
1169
|
}
|
|
1074
1170
|
});
|
|
1075
1171
|
|
|
1172
|
+
// src/utils/update-check.ts
|
|
1173
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1174
|
+
import { get } from "node:https";
|
|
1175
|
+
import { homedir as homedir5 } from "node:os";
|
|
1176
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
1177
|
+
var PKG = "skillio";
|
|
1178
|
+
var TTL_MS = 24 * 60 * 60 * 1000;
|
|
1179
|
+
var FETCH_TIMEOUT_MS = 1500;
|
|
1180
|
+
function getCachePath() {
|
|
1181
|
+
return join7(homedir5(), ".cache", "skillio", "version.json");
|
|
1182
|
+
}
|
|
1183
|
+
function compareVersions(a, b) {
|
|
1184
|
+
const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1185
|
+
const pb = b.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1186
|
+
for (let i = 0;i < 3; i += 1) {
|
|
1187
|
+
const da = pa[i] ?? 0;
|
|
1188
|
+
const db = pb[i] ?? 0;
|
|
1189
|
+
if (da !== db)
|
|
1190
|
+
return da - db;
|
|
1191
|
+
}
|
|
1192
|
+
return 0;
|
|
1193
|
+
}
|
|
1194
|
+
function readCache(path = getCachePath()) {
|
|
1195
|
+
try {
|
|
1196
|
+
return JSON.parse(readFileSync6(path, "utf8"));
|
|
1197
|
+
} catch {
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
function writeCache(cache, path = getCachePath()) {
|
|
1202
|
+
try {
|
|
1203
|
+
mkdirSync2(dirname4(path), { recursive: true });
|
|
1204
|
+
writeFileSync2(path, JSON.stringify(cache));
|
|
1205
|
+
} catch {}
|
|
1206
|
+
}
|
|
1207
|
+
function fetchLatest() {
|
|
1208
|
+
return new Promise((resolve3) => {
|
|
1209
|
+
const req = get(`https://registry.npmjs.org/${PKG}/latest`, { timeout: FETCH_TIMEOUT_MS }, (res) => {
|
|
1210
|
+
if (res.statusCode !== 200) {
|
|
1211
|
+
res.resume();
|
|
1212
|
+
resolve3(undefined);
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
let body = "";
|
|
1216
|
+
res.setEncoding("utf8");
|
|
1217
|
+
res.on("data", (chunk) => {
|
|
1218
|
+
body += chunk;
|
|
1219
|
+
});
|
|
1220
|
+
res.on("end", () => {
|
|
1221
|
+
try {
|
|
1222
|
+
const data = JSON.parse(body);
|
|
1223
|
+
resolve3(typeof data.version === "string" ? data.version : undefined);
|
|
1224
|
+
} catch {
|
|
1225
|
+
resolve3(undefined);
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
});
|
|
1229
|
+
req.on("error", () => resolve3(undefined));
|
|
1230
|
+
req.on("timeout", () => {
|
|
1231
|
+
req.destroy();
|
|
1232
|
+
resolve3(undefined);
|
|
1233
|
+
});
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
async function maybePrintUpdateNotice(currentVersion) {
|
|
1237
|
+
if (process.env.SKILLIO_NO_UPDATE_CHECK)
|
|
1238
|
+
return;
|
|
1239
|
+
const now = Date.now();
|
|
1240
|
+
const cache = readCache();
|
|
1241
|
+
let latest = cache?.latest;
|
|
1242
|
+
if (!cache || now - cache.checkedAt > TTL_MS) {
|
|
1243
|
+
const fetched = await fetchLatest();
|
|
1244
|
+
if (fetched) {
|
|
1245
|
+
latest = fetched;
|
|
1246
|
+
writeCache({ checkedAt: now, latest });
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
if (latest && compareVersions(latest, currentVersion) > 0) {
|
|
1250
|
+
process.stderr.write(`
|
|
1251
|
+
Update available: ${currentVersion} → ${latest}
|
|
1252
|
+
Run: npm i -g skillio
|
|
1253
|
+
|
|
1254
|
+
`);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1076
1258
|
// src/cli.ts
|
|
1077
1259
|
var { version } = createRequire(import.meta.url)("../package.json");
|
|
1078
1260
|
if (process.argv[2] === "audit") {
|
|
@@ -1111,7 +1293,7 @@ function mergeAgentArgs(argv) {
|
|
|
1111
1293
|
return out;
|
|
1112
1294
|
}
|
|
1113
1295
|
process.argv = mergeAgentArgs(process.argv);
|
|
1114
|
-
var SUBCOMMAND_NAMES = new Set(["list", "ls", "remove", "rm"]);
|
|
1296
|
+
var SUBCOMMAND_NAMES = new Set(["list", "ls", "remove", "rm", "cost"]);
|
|
1115
1297
|
var main = defineCommand({
|
|
1116
1298
|
meta: {
|
|
1117
1299
|
name: "skillio",
|
|
@@ -1133,7 +1315,11 @@ var main = defineCommand({
|
|
|
1133
1315
|
list: listCommand,
|
|
1134
1316
|
ls: listCommand,
|
|
1135
1317
|
remove: removeCommand,
|
|
1136
|
-
rm: removeCommand
|
|
1318
|
+
rm: removeCommand,
|
|
1319
|
+
cost: costCommand
|
|
1137
1320
|
}
|
|
1138
1321
|
});
|
|
1139
|
-
|
|
1322
|
+
(async () => {
|
|
1323
|
+
await maybePrintUpdateNotice(version);
|
|
1324
|
+
runMain(main);
|
|
1325
|
+
})();
|