skillio 0.1.1

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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +85 -0
  3. package/dist/cli.js +1018 -0
  4. package/package.json +61 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ihororlovskyi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # skillio
2
+
3
+ [![npm version](https://img.shields.io/npm/v/skillio)](https://www.npmjs.com/package/skillio)
4
+ [![CI](https://github.com/ihororlovskyi/skillio/actions/workflows/ci.yml/badge.svg)](https://github.com/ihororlovskyi/skillio/actions/workflows/ci.yml)
5
+
6
+ Audit and manage AI agent skills for Claude Code and OpenAI Codex.
7
+
8
+ ## Installation
9
+
10
+ ```sh
11
+ # one-off (no install needed)
12
+ npx skillio audit --agent claude --period 7d
13
+ pnpm dlx skillio audit --agent codex --period 2w
14
+
15
+ # global install
16
+ npm install -g skillio
17
+ pnpm add -g skillio
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ```sh
23
+ skillio --agent claude --period 7d # audit last 7 days (default subcommand)
24
+ skillio audit --agent claude --period 7d # audit last 7 days (attributed mode)
25
+ skillio audit --agent codex --mode activations # codex activations
26
+ skillio audit -a claude codex --period 2w # both agents, space-separated
27
+ skillio audit -a claude,codex --period 2w # both agents, comma-separated
28
+ skillio list # list skills in local skills-lock.json
29
+ skillio list --global # list from ~/.agents/.skill-lock.json
30
+ skillio remove brainstorming # remove skill from lock
31
+ skillio remove brainstorming writing-plans # remove multiple skills
32
+ skillio remove --dry-run brainstorming # preview removal
33
+ ```
34
+
35
+ ## What it does
36
+
37
+ - **Audit skill usage** — parse agent session logs and count which skills were invoked, when, and how often
38
+ - **Manage a skills lock** — list and remove skills from a local or global lock file
39
+
40
+ ## Options
41
+
42
+ ### `skillio` / `skillio audit`
43
+
44
+ Audits skill usage from agent session logs. `audit` is the default subcommand when the first argument is an audit flag.
45
+
46
+ ```sh
47
+ skillio --agent claude --period 7d
48
+ skillio audit --agent codex --mode activations
49
+ ```
50
+
51
+ | Flag | Default | Description |
52
+ |------|---------|-------------|
53
+ | `-a, --agent` | required | `claude-code`/`claude`, `codex`, comma- or space-separated |
54
+ | `-p, --period` | `7d` | `7d`, `2w`, `1m`, `1y` |
55
+ | `--since` | — | `yyyy-mm-dd`, overrides `--period` |
56
+ | `--mode` | `attributed` | `attributed` \| `activations` \| `mentions` |
57
+ | `--format` | `text` | `text` \| `json` |
58
+ | `--root` | — | Override agent sessions directory |
59
+ | `--scan-all-files` | — | Ignore file mtime, read everything |
60
+
61
+ ### Modes
62
+
63
+ - **`attributed`** — entries with an `attributionSkill` field set by Claude Code. This is the default and most reliable Claude mode.
64
+ - **`activations`** — explicit `Skill` tool invocations found anywhere in the entry tree (Claude) or `exec_command_end` events / `<skill>` XML (Codex). This is the default and most reliable Codex mode.
65
+ - **`mentions`** — skill paths (`foo/SKILL.md`) or `superpowers:name` strings found in any string value. This is a broad search mode and can include examples from prompts, specs, or documentation.
66
+
67
+ ### `skillio list` / `ls`
68
+
69
+ ```sh
70
+ skillio list # local skills-lock.json
71
+ skillio list --global # ~/.agents/.skill-lock.json
72
+ ```
73
+
74
+ ### `skillio remove` / `rm`
75
+
76
+ ```sh
77
+ skillio remove <skill-name>
78
+ skillio remove <skill-one> <skill-two>
79
+ skillio remove --global <skill-name>
80
+ skillio remove --dry-run <skill-name>
81
+ ```
82
+
83
+ ## Requirements
84
+
85
+ - Node.js ≥ 20
package/dist/cli.js ADDED
@@ -0,0 +1,1018 @@
1
+ #!/usr/bin/env node
2
+
3
+ // node_modules/citty/dist/_chunks/libs/scule.mjs
4
+ var NUMBER_CHAR_RE = /\d/;
5
+ var STR_SPLITTERS = [
6
+ "-",
7
+ "_",
8
+ "/",
9
+ "."
10
+ ];
11
+ function isUppercase(char = "") {
12
+ if (NUMBER_CHAR_RE.test(char))
13
+ return;
14
+ return char !== char.toLowerCase();
15
+ }
16
+ function splitByCase(str, separators) {
17
+ const splitters = separators ?? STR_SPLITTERS;
18
+ const parts = [];
19
+ if (!str || typeof str !== "string")
20
+ return parts;
21
+ let buff = "";
22
+ let previousUpper;
23
+ let previousSplitter;
24
+ for (const char of str) {
25
+ const isSplitter = splitters.includes(char);
26
+ if (isSplitter === true) {
27
+ parts.push(buff);
28
+ buff = "";
29
+ previousUpper = undefined;
30
+ continue;
31
+ }
32
+ const isUpper = isUppercase(char);
33
+ if (previousSplitter === false) {
34
+ if (previousUpper === false && isUpper === true) {
35
+ parts.push(buff);
36
+ buff = char;
37
+ previousUpper = isUpper;
38
+ continue;
39
+ }
40
+ if (previousUpper === true && isUpper === false && buff.length > 1) {
41
+ const lastChar = buff.at(-1);
42
+ parts.push(buff.slice(0, Math.max(0, buff.length - 1)));
43
+ buff = lastChar + char;
44
+ previousUpper = isUpper;
45
+ continue;
46
+ }
47
+ }
48
+ buff += char;
49
+ previousUpper = isUpper;
50
+ previousSplitter = isSplitter;
51
+ }
52
+ parts.push(buff);
53
+ return parts;
54
+ }
55
+ function upperFirst(str) {
56
+ return str ? str[0].toUpperCase() + str.slice(1) : "";
57
+ }
58
+ function lowerFirst(str) {
59
+ return str ? str[0].toLowerCase() + str.slice(1) : "";
60
+ }
61
+ function pascalCase(str, opts) {
62
+ return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => upperFirst(opts?.normalize ? p.toLowerCase() : p)).join("") : "";
63
+ }
64
+ function camelCase(str, opts) {
65
+ return lowerFirst(pascalCase(str || "", opts));
66
+ }
67
+ function kebabCase(str, joiner) {
68
+ return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => p.toLowerCase()).join(joiner ?? "-") : "";
69
+ }
70
+ function snakeCase(str) {
71
+ return kebabCase(str || "", "_");
72
+ }
73
+
74
+ // node_modules/citty/dist/index.mjs
75
+ import { parseArgs as parseArgs$1 } from "node:util";
76
+ function toArray(val) {
77
+ if (Array.isArray(val))
78
+ return val;
79
+ return val === undefined ? [] : [val];
80
+ }
81
+ function formatLineColumns(lines, linePrefix = "") {
82
+ const maxLength = [];
83
+ for (const line of lines)
84
+ for (const [i, element] of line.entries())
85
+ maxLength[i] = Math.max(maxLength[i] || 0, element.length);
86
+ return lines.map((l) => l.map((c, i) => linePrefix + c[i === 0 ? "padStart" : "padEnd"](maxLength[i])).join(" ")).join(`
87
+ `);
88
+ }
89
+ function resolveValue(input) {
90
+ return typeof input === "function" ? input() : input;
91
+ }
92
+ var CLIError = class extends Error {
93
+ code;
94
+ constructor(message, code) {
95
+ super(message);
96
+ this.name = "CLIError";
97
+ this.code = code;
98
+ }
99
+ };
100
+ function parseRawArgs(args = [], opts = {}) {
101
+ const booleans = new Set(opts.boolean || []);
102
+ const strings = new Set(opts.string || []);
103
+ const aliasMap = opts.alias || {};
104
+ const defaults = opts.default || {};
105
+ const aliasToMain = /* @__PURE__ */ new Map;
106
+ const mainToAliases = /* @__PURE__ */ new Map;
107
+ for (const [key, value] of Object.entries(aliasMap)) {
108
+ const targets = value;
109
+ for (const target of targets) {
110
+ aliasToMain.set(key, target);
111
+ if (!mainToAliases.has(target))
112
+ mainToAliases.set(target, []);
113
+ mainToAliases.get(target).push(key);
114
+ aliasToMain.set(target, key);
115
+ if (!mainToAliases.has(key))
116
+ mainToAliases.set(key, []);
117
+ mainToAliases.get(key).push(target);
118
+ }
119
+ }
120
+ const options = {};
121
+ function getType(name) {
122
+ if (booleans.has(name))
123
+ return "boolean";
124
+ const aliases = mainToAliases.get(name) || [];
125
+ for (const alias of aliases)
126
+ if (booleans.has(alias))
127
+ return "boolean";
128
+ return "string";
129
+ }
130
+ function isStringType(name) {
131
+ if (strings.has(name))
132
+ return true;
133
+ const aliases = mainToAliases.get(name) || [];
134
+ for (const alias of aliases)
135
+ if (strings.has(alias))
136
+ return true;
137
+ return false;
138
+ }
139
+ const allOptions = new Set([
140
+ ...booleans,
141
+ ...strings,
142
+ ...Object.keys(aliasMap),
143
+ ...Object.values(aliasMap).flat(),
144
+ ...Object.keys(defaults)
145
+ ]);
146
+ for (const name of allOptions)
147
+ if (!options[name])
148
+ options[name] = {
149
+ type: getType(name),
150
+ default: defaults[name]
151
+ };
152
+ for (const [alias, main] of aliasToMain.entries())
153
+ if (alias.length === 1 && options[main] && !options[main].short)
154
+ options[main].short = alias;
155
+ const processedArgs = [];
156
+ const negatedFlags = {};
157
+ for (let i = 0;i < args.length; i++) {
158
+ const arg = args[i];
159
+ if (arg === "--") {
160
+ processedArgs.push(...args.slice(i));
161
+ break;
162
+ }
163
+ if (arg.startsWith("--no-")) {
164
+ const flagName = arg.slice(5);
165
+ negatedFlags[flagName] = true;
166
+ continue;
167
+ }
168
+ processedArgs.push(arg);
169
+ }
170
+ let parsed;
171
+ try {
172
+ parsed = parseArgs$1({
173
+ args: processedArgs,
174
+ options: Object.keys(options).length > 0 ? options : undefined,
175
+ allowPositionals: true,
176
+ strict: false
177
+ });
178
+ } catch {
179
+ parsed = {
180
+ values: {},
181
+ positionals: processedArgs
182
+ };
183
+ }
184
+ const out = { _: [] };
185
+ out._ = parsed.positionals;
186
+ for (const [key, value] of Object.entries(parsed.values)) {
187
+ let coerced = value;
188
+ if (getType(key) === "boolean" && typeof value === "string")
189
+ coerced = value !== "false";
190
+ else if (isStringType(key) && typeof value === "boolean")
191
+ coerced = "";
192
+ out[key] = coerced;
193
+ }
194
+ for (const [name] of Object.entries(negatedFlags)) {
195
+ out[name] = false;
196
+ const mainName = aliasToMain.get(name);
197
+ if (mainName)
198
+ out[mainName] = false;
199
+ const aliases = mainToAliases.get(name);
200
+ if (aliases)
201
+ for (const alias of aliases)
202
+ out[alias] = false;
203
+ }
204
+ for (const [alias, main] of aliasToMain.entries()) {
205
+ if (out[alias] !== undefined && out[main] === undefined)
206
+ out[main] = out[alias];
207
+ if (out[main] !== undefined && out[alias] === undefined)
208
+ out[alias] = out[main];
209
+ if (out[alias] !== out[main] && defaults[main] === out[main])
210
+ out[main] = out[alias];
211
+ }
212
+ return out;
213
+ }
214
+ var noColor = /* @__PURE__ */ (() => {
215
+ const env = globalThis.process?.env ?? {};
216
+ return env.NO_COLOR === "1" || env.TERM === "dumb" || env.TEST || env.CI;
217
+ })();
218
+ var _c = (c, r = 39) => (t) => noColor ? t : `\x1B[${c}m${t}\x1B[${r}m`;
219
+ var bold = /* @__PURE__ */ _c(1, 22);
220
+ var cyan = /* @__PURE__ */ _c(36);
221
+ var gray = /* @__PURE__ */ _c(90);
222
+ var underline = /* @__PURE__ */ _c(4, 24);
223
+ function parseArgs(rawArgs, argsDef) {
224
+ const parseOptions = {
225
+ boolean: [],
226
+ string: [],
227
+ alias: {},
228
+ default: {}
229
+ };
230
+ const args = resolveArgs(argsDef);
231
+ for (const arg of args) {
232
+ if (arg.type === "positional")
233
+ continue;
234
+ if (arg.type === "string" || arg.type === "enum")
235
+ parseOptions.string.push(arg.name);
236
+ else if (arg.type === "boolean")
237
+ parseOptions.boolean.push(arg.name);
238
+ if (arg.default !== undefined)
239
+ parseOptions.default[arg.name] = arg.default;
240
+ if (arg.alias)
241
+ parseOptions.alias[arg.name] = arg.alias;
242
+ const camelName = camelCase(arg.name);
243
+ const kebabName = kebabCase(arg.name);
244
+ if (camelName !== arg.name || kebabName !== arg.name) {
245
+ const existingAliases = toArray(parseOptions.alias[arg.name] || []);
246
+ if (camelName !== arg.name && !existingAliases.includes(camelName))
247
+ existingAliases.push(camelName);
248
+ if (kebabName !== arg.name && !existingAliases.includes(kebabName))
249
+ existingAliases.push(kebabName);
250
+ if (existingAliases.length > 0)
251
+ parseOptions.alias[arg.name] = existingAliases;
252
+ }
253
+ }
254
+ const parsed = parseRawArgs(rawArgs, parseOptions);
255
+ const [...positionalArguments] = parsed._;
256
+ const parsedArgsProxy = new Proxy(parsed, { get(target, prop) {
257
+ return target[prop] ?? target[camelCase(prop)] ?? target[kebabCase(prop)];
258
+ } });
259
+ for (const [, arg] of args.entries())
260
+ if (arg.type === "positional") {
261
+ const nextPositionalArgument = positionalArguments.shift();
262
+ if (nextPositionalArgument !== undefined)
263
+ parsedArgsProxy[arg.name] = nextPositionalArgument;
264
+ else if (arg.default === undefined && arg.required !== false)
265
+ throw new CLIError(`Missing required positional argument: ${arg.name.toUpperCase()}`, "EARG");
266
+ else
267
+ parsedArgsProxy[arg.name] = arg.default;
268
+ } else if (arg.type === "enum") {
269
+ const argument = parsedArgsProxy[arg.name];
270
+ const options = arg.options || [];
271
+ if (argument !== undefined && options.length > 0 && !options.includes(argument))
272
+ throw new CLIError(`Invalid value for argument: ${cyan(`--${arg.name}`)} (${cyan(argument)}). Expected one of: ${options.map((o) => cyan(o)).join(", ")}.`, "EARG");
273
+ } else if (arg.required && parsedArgsProxy[arg.name] === undefined)
274
+ throw new CLIError(`Missing required argument: --${arg.name}`, "EARG");
275
+ return parsedArgsProxy;
276
+ }
277
+ function resolveArgs(argsDef) {
278
+ const args = [];
279
+ for (const [name, argDef] of Object.entries(argsDef || {}))
280
+ args.push({
281
+ ...argDef,
282
+ name,
283
+ alias: toArray(argDef.alias)
284
+ });
285
+ return args;
286
+ }
287
+ async function resolvePlugins(plugins) {
288
+ return Promise.all(plugins.map((p) => resolveValue(p)));
289
+ }
290
+ function defineCommand(def) {
291
+ return def;
292
+ }
293
+ async function runCommand(cmd, opts) {
294
+ const cmdArgs = await resolveValue(cmd.args || {});
295
+ const parsedArgs = parseArgs(opts.rawArgs, cmdArgs);
296
+ const context = {
297
+ rawArgs: opts.rawArgs,
298
+ args: parsedArgs,
299
+ data: opts.data,
300
+ cmd
301
+ };
302
+ const plugins = await resolvePlugins(cmd.plugins ?? []);
303
+ let result;
304
+ let runError;
305
+ try {
306
+ for (const plugin of plugins)
307
+ await plugin.setup?.(context);
308
+ if (typeof cmd.setup === "function")
309
+ await cmd.setup(context);
310
+ const subCommands = await resolveValue(cmd.subCommands);
311
+ if (subCommands && Object.keys(subCommands).length > 0) {
312
+ const subCommandArgIndex = findSubCommandIndex(opts.rawArgs, cmdArgs);
313
+ const explicitName = opts.rawArgs[subCommandArgIndex];
314
+ if (explicitName) {
315
+ const subCommand = await _findSubCommand(subCommands, explicitName);
316
+ if (!subCommand)
317
+ throw new CLIError(`Unknown command ${cyan(explicitName)}`, "E_UNKNOWN_COMMAND");
318
+ await runCommand(subCommand, { rawArgs: opts.rawArgs.slice(subCommandArgIndex + 1) });
319
+ } else {
320
+ const defaultSubCommand = await resolveValue(cmd.default);
321
+ if (defaultSubCommand) {
322
+ if (cmd.run)
323
+ throw new CLIError(`Cannot specify both 'run' and 'default' on the same command.`, "E_DEFAULT_CONFLICT");
324
+ const subCommand = await _findSubCommand(subCommands, defaultSubCommand);
325
+ if (!subCommand)
326
+ throw new CLIError(`Default sub command ${cyan(defaultSubCommand)} not found in subCommands.`, "E_UNKNOWN_COMMAND");
327
+ await runCommand(subCommand, { rawArgs: opts.rawArgs });
328
+ } else if (!cmd.run)
329
+ throw new CLIError(`No command specified.`, "E_NO_COMMAND");
330
+ }
331
+ }
332
+ if (typeof cmd.run === "function")
333
+ result = await cmd.run(context);
334
+ } catch (error) {
335
+ runError = error;
336
+ }
337
+ const cleanupErrors = [];
338
+ if (typeof cmd.cleanup === "function")
339
+ try {
340
+ await cmd.cleanup(context);
341
+ } catch (error) {
342
+ cleanupErrors.push(error);
343
+ }
344
+ for (const plugin of [...plugins].reverse())
345
+ try {
346
+ await plugin.cleanup?.(context);
347
+ } catch (error) {
348
+ cleanupErrors.push(error);
349
+ }
350
+ if (runError)
351
+ throw runError;
352
+ if (cleanupErrors.length === 1)
353
+ throw cleanupErrors[0];
354
+ if (cleanupErrors.length > 1)
355
+ throw new Error("Multiple cleanup errors", { cause: cleanupErrors });
356
+ return { result };
357
+ }
358
+ async function resolveSubCommand(cmd, rawArgs, parent) {
359
+ const subCommands = await resolveValue(cmd.subCommands);
360
+ if (subCommands && Object.keys(subCommands).length > 0) {
361
+ const subCommandArgIndex = findSubCommandIndex(rawArgs, await resolveValue(cmd.args || {}));
362
+ const subCommandName = rawArgs[subCommandArgIndex];
363
+ const subCommand = await _findSubCommand(subCommands, subCommandName);
364
+ if (subCommand)
365
+ return resolveSubCommand(subCommand, rawArgs.slice(subCommandArgIndex + 1), cmd);
366
+ }
367
+ return [cmd, parent];
368
+ }
369
+ async function _findSubCommand(subCommands, name) {
370
+ if (name in subCommands)
371
+ return resolveValue(subCommands[name]);
372
+ for (const sub of Object.values(subCommands)) {
373
+ const resolved = await resolveValue(sub);
374
+ const meta = await resolveValue(resolved?.meta);
375
+ if (meta?.alias) {
376
+ if (toArray(meta.alias).includes(name))
377
+ return resolved;
378
+ }
379
+ }
380
+ }
381
+ function findSubCommandIndex(rawArgs, argsDef) {
382
+ for (let i = 0;i < rawArgs.length; i++) {
383
+ const arg = rawArgs[i];
384
+ if (arg === "--")
385
+ return -1;
386
+ if (arg.startsWith("-")) {
387
+ if (!arg.includes("=") && _isValueFlag(arg, argsDef))
388
+ i++;
389
+ continue;
390
+ }
391
+ return i;
392
+ }
393
+ return -1;
394
+ }
395
+ function _isValueFlag(flag, argsDef) {
396
+ const name = flag.replace(/^-{1,2}/, "");
397
+ const normalized = camelCase(name);
398
+ for (const [key, def] of Object.entries(argsDef)) {
399
+ if (def.type !== "string" && def.type !== "enum")
400
+ continue;
401
+ if (normalized === camelCase(key))
402
+ return true;
403
+ if ((Array.isArray(def.alias) ? def.alias : def.alias ? [def.alias] : []).includes(name))
404
+ return true;
405
+ }
406
+ return false;
407
+ }
408
+ async function showUsage(cmd, parent) {
409
+ try {
410
+ console.log(await renderUsage(cmd, parent) + `
411
+ `);
412
+ } catch (error) {
413
+ console.error(error);
414
+ }
415
+ }
416
+ var negativePrefixRe = /^no[-A-Z]/;
417
+ async function renderUsage(cmd, parent) {
418
+ const cmdMeta = await resolveValue(cmd.meta || {});
419
+ const cmdArgs = resolveArgs(await resolveValue(cmd.args || {}));
420
+ const parentMeta = await resolveValue(parent?.meta || {});
421
+ const commandName = `${parentMeta.name ? `${parentMeta.name} ` : ""}` + (cmdMeta.name || process.argv[1]);
422
+ const argLines = [];
423
+ const posLines = [];
424
+ const commandsLines = [];
425
+ const usageLine = [];
426
+ for (const arg of cmdArgs)
427
+ if (arg.type === "positional") {
428
+ const name = arg.name.toUpperCase();
429
+ const isRequired = arg.required !== false && arg.default === undefined;
430
+ posLines.push([cyan(name + renderValueHint(arg)), renderDescription(arg, isRequired)]);
431
+ usageLine.push(isRequired ? `<${name}>` : `[${name}]`);
432
+ } else {
433
+ const isRequired = arg.required === true && arg.default === undefined;
434
+ const argStr = [...(arg.alias || []).map((a) => `-${a}`), `--${arg.name}`].join(", ") + renderValueHint(arg);
435
+ argLines.push([cyan(argStr), renderDescription(arg, isRequired)]);
436
+ if (arg.type === "boolean" && (arg.default === true || arg.negativeDescription) && !negativePrefixRe.test(arg.name)) {
437
+ const negativeArgStr = [...(arg.alias || []).map((a) => `--no-${a}`), `--no-${arg.name}`].join(", ");
438
+ argLines.push([cyan(negativeArgStr), [arg.negativeDescription, isRequired ? gray("(Required)") : ""].filter(Boolean).join(" ")]);
439
+ }
440
+ if (isRequired)
441
+ usageLine.push(`--${arg.name}` + renderValueHint(arg));
442
+ }
443
+ if (cmd.subCommands) {
444
+ const commandNames = [];
445
+ const subCommands = await resolveValue(cmd.subCommands);
446
+ for (const [name, sub] of Object.entries(subCommands)) {
447
+ const meta = await resolveValue((await resolveValue(sub))?.meta);
448
+ if (meta?.hidden)
449
+ continue;
450
+ const aliases = toArray(meta?.alias);
451
+ const label = [name, ...aliases].join(", ");
452
+ commandsLines.push([cyan(label), meta?.description || ""]);
453
+ commandNames.push(name, ...aliases);
454
+ }
455
+ usageLine.push(commandNames.join("|"));
456
+ }
457
+ const usageLines = [];
458
+ const version = cmdMeta.version || parentMeta.version;
459
+ usageLines.push(gray(`${cmdMeta.description} (${commandName + (version ? ` v${version}` : "")})`), "");
460
+ const hasOptions = argLines.length > 0 || posLines.length > 0;
461
+ usageLines.push(`${underline(bold("USAGE"))} ${cyan(`${commandName}${hasOptions ? " [OPTIONS]" : ""} ${usageLine.join(" ")}`)}`, "");
462
+ if (posLines.length > 0) {
463
+ usageLines.push(underline(bold("ARGUMENTS")), "");
464
+ usageLines.push(formatLineColumns(posLines, " "));
465
+ usageLines.push("");
466
+ }
467
+ if (argLines.length > 0) {
468
+ usageLines.push(underline(bold("OPTIONS")), "");
469
+ usageLines.push(formatLineColumns(argLines, " "));
470
+ usageLines.push("");
471
+ }
472
+ if (commandsLines.length > 0) {
473
+ usageLines.push(underline(bold("COMMANDS")), "");
474
+ usageLines.push(formatLineColumns(commandsLines, " "));
475
+ usageLines.push("", `Use ${cyan(`${commandName} <command> --help`)} for more information about a command.`);
476
+ }
477
+ return usageLines.filter((l) => typeof l === "string").join(`
478
+ `);
479
+ }
480
+ function renderValueHint(arg) {
481
+ const valueHint = arg.valueHint ? `=<${arg.valueHint}>` : "";
482
+ const fallbackValueHint = valueHint || `=<${snakeCase(arg.name)}>`;
483
+ if (!arg.type || arg.type === "positional" || arg.type === "boolean")
484
+ return valueHint;
485
+ if (arg.type === "enum" && arg.options?.length)
486
+ return `=<${arg.options.join("|")}>`;
487
+ return fallbackValueHint;
488
+ }
489
+ function renderDescription(arg, required) {
490
+ const requiredHint = required ? gray("(Required)") : "";
491
+ const defaultHint = arg.default === undefined ? "" : gray(`(Default: ${arg.default})`);
492
+ return [
493
+ arg.description,
494
+ requiredHint,
495
+ defaultHint
496
+ ].filter(Boolean).join(" ");
497
+ }
498
+ async function runMain(cmd, opts = {}) {
499
+ const rawArgs = opts.rawArgs || process.argv.slice(2);
500
+ const showUsage$1 = opts.showUsage || showUsage;
501
+ try {
502
+ const builtinFlags = await _resolveBuiltinFlags(cmd);
503
+ if (builtinFlags.help.length > 0 && rawArgs.some((arg) => builtinFlags.help.includes(arg))) {
504
+ await showUsage$1(...await resolveSubCommand(cmd, rawArgs));
505
+ process.exit(0);
506
+ } else if (rawArgs.length === 1 && builtinFlags.version.includes(rawArgs[0])) {
507
+ const meta = typeof cmd.meta === "function" ? await cmd.meta() : await cmd.meta;
508
+ if (!meta?.version)
509
+ throw new CLIError("No version specified", "E_NO_VERSION");
510
+ console.log(meta.version);
511
+ } else
512
+ await runCommand(cmd, { rawArgs });
513
+ } catch (error) {
514
+ if (error instanceof CLIError) {
515
+ await showUsage$1(...await resolveSubCommand(cmd, rawArgs));
516
+ console.error(error.message);
517
+ } else
518
+ console.error(error, `
519
+ `);
520
+ process.exit(1);
521
+ }
522
+ }
523
+ async function _resolveBuiltinFlags(cmd) {
524
+ const argsDef = await resolveValue(cmd.args || {});
525
+ const userNames = /* @__PURE__ */ new Set;
526
+ const userAliases = /* @__PURE__ */ new Set;
527
+ for (const [name, def] of Object.entries(argsDef)) {
528
+ userNames.add(name);
529
+ for (const alias of toArray(def.alias))
530
+ userAliases.add(alias);
531
+ }
532
+ return {
533
+ help: _getBuiltinFlags("help", "h", userNames, userAliases),
534
+ version: _getBuiltinFlags("version", "v", userNames, userAliases)
535
+ };
536
+ }
537
+ function _getBuiltinFlags(long, short, userNames, userAliases) {
538
+ if (userNames.has(long) || userAliases.has(long))
539
+ return [];
540
+ if (userNames.has(short) || userAliases.has(short))
541
+ return [`--${long}`];
542
+ return [`--${long}`, `-${short}`];
543
+ }
544
+
545
+ // src/readers/claude.ts
546
+ import { readFileSync as readFileSync2 } from "node:fs";
547
+
548
+ // src/utils/walk.ts
549
+ function walk(value, visit) {
550
+ visit(value);
551
+ if (Array.isArray(value)) {
552
+ for (const item of value)
553
+ walk(item, visit);
554
+ } else if (value !== null && typeof value === "object") {
555
+ for (const item of Object.values(value))
556
+ walk(item, visit);
557
+ }
558
+ }
559
+
560
+ // src/extractors/activations.ts
561
+ function extractClaudeActivations(entry) {
562
+ const skills = [];
563
+ walk(entry, (node) => {
564
+ if (typeof node !== "object" || node === null)
565
+ return;
566
+ const n = node;
567
+ if (n.type === "tool_use" && n.name === "Skill" && typeof n.input === "object") {
568
+ const skill = n.input.skill;
569
+ if (typeof skill === "string")
570
+ skills.push(skill);
571
+ }
572
+ });
573
+ return skills;
574
+ }
575
+ function skillNameFromPath(p) {
576
+ const parts = p.replace("/SKILL.md", "").split("/");
577
+ return parts[parts.length - 1] ?? null;
578
+ }
579
+ function extractCodexActivations(entry) {
580
+ if (typeof entry !== "object" || entry === null)
581
+ return [];
582
+ const e = entry;
583
+ const payload = e.payload;
584
+ if (e.type === "response_item" && payload?.type === "message" && payload?.role === "user") {
585
+ const skills = new Set;
586
+ const content = payload?.content ?? [];
587
+ for (const item of content) {
588
+ const i = item;
589
+ if (i.type === "input_text" && typeof i.text === "string") {
590
+ for (const m of i.text.matchAll(/<skill>\s*<name>([^<]+)<\/name>/g)) {
591
+ if (m[1] !== undefined)
592
+ skills.add(m[1]);
593
+ }
594
+ }
595
+ }
596
+ return [...skills];
597
+ }
598
+ if (e.type === "event_msg" && payload?.type === "exec_command_end") {
599
+ const cmds = payload?.parsed_cmd ?? [];
600
+ const paths = new Set;
601
+ for (const cmd of cmds) {
602
+ if (typeof cmd.path === "string" && cmd.path.endsWith("/SKILL.md")) {
603
+ paths.add(cmd.path);
604
+ }
605
+ }
606
+ return [...paths].map(skillNameFromPath).filter((s) => s !== null);
607
+ }
608
+ return [];
609
+ }
610
+
611
+ // src/extractors/attributed.ts
612
+ function extractAttributed(entry) {
613
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry))
614
+ return [];
615
+ const skill = entry.attributionSkill;
616
+ return typeof skill === "string" ? [skill] : [];
617
+ }
618
+
619
+ // src/extractors/mentions.ts
620
+ function extractClaudeMentions(entry) {
621
+ const seen = new Set;
622
+ walk(entry, (node) => {
623
+ if (typeof node !== "string")
624
+ return;
625
+ for (const m of node.matchAll(/(?:^|[":\s])((?:[a-z0-9-]+:)?[a-z0-9][a-z0-9-]{1,})\/SKILL\.md\b/g)) {
626
+ if (m[1] !== undefined)
627
+ seen.add(m[1]);
628
+ }
629
+ for (const m of node.matchAll(/\bsuperpowers:([a-z0-9-]+)\b/g)) {
630
+ if (m[1] !== undefined)
631
+ seen.add(`superpowers:${m[1]}`);
632
+ }
633
+ });
634
+ return [...seen];
635
+ }
636
+ function extractCodexMentions(entry) {
637
+ const seen = new Set;
638
+ walk(entry, (node) => {
639
+ if (typeof node !== "string")
640
+ return;
641
+ for (const m of node.matchAll(/\/([^/)\]\s]+)\/SKILL\.md\b/g)) {
642
+ if (m[1] !== undefined)
643
+ seen.add(m[1]);
644
+ }
645
+ for (const m of node.matchAll(/\$([a-z0-9][a-z0-9-]{1,})\b/g)) {
646
+ if (m[1] !== undefined)
647
+ seen.add(m[1]);
648
+ }
649
+ });
650
+ return [...seen];
651
+ }
652
+
653
+ // src/utils/expand-home.ts
654
+ import { homedir } from "node:os";
655
+ import { join, resolve } from "node:path";
656
+ function expandHome(p) {
657
+ if (p === "~")
658
+ return homedir();
659
+ if (p.startsWith("~/"))
660
+ return join(homedir(), p.slice(2));
661
+ return resolve(p);
662
+ }
663
+
664
+ // src/utils/jsonl.ts
665
+ import { readdirSync, readFileSync, statSync } from "node:fs";
666
+ import { join as join2 } from "node:path";
667
+ function* findJsonlFiles(dir, since) {
668
+ for (const item of readdirSync(dir, { withFileTypes: true })) {
669
+ const path = join2(dir, item.name);
670
+ if (item.isDirectory()) {
671
+ yield* findJsonlFiles(path, since);
672
+ } else if (item.isFile() && item.name.endsWith(".jsonl")) {
673
+ if (!since || statSync(path).mtime >= since)
674
+ yield path;
675
+ }
676
+ }
677
+ }
678
+ function isRecentEntry(entry, since) {
679
+ if (typeof entry !== "object" || entry === null)
680
+ return true;
681
+ const e = entry;
682
+ if (typeof e.timestamp === "string") {
683
+ const d = new Date(e.timestamp);
684
+ return Number.isNaN(d.getTime()) || d >= since;
685
+ }
686
+ if (typeof e.ts === "number")
687
+ return new Date(e.ts * 1000) >= since;
688
+ return true;
689
+ }
690
+
691
+ // src/readers/claude.ts
692
+ function extractSkills(entry, mode) {
693
+ if (mode === "attributed")
694
+ return extractAttributed(entry);
695
+ if (mode === "activations")
696
+ return extractClaudeActivations(entry);
697
+ return extractClaudeMentions(entry);
698
+ }
699
+ function readClaudeUsage(options) {
700
+ const root = expandHome(options.root ?? "~/.claude/projects");
701
+ const counts = new Map;
702
+ let filesRead = 0;
703
+ let linesRead = 0;
704
+ const since = options.scanAllFiles ? undefined : options.since;
705
+ for (const file of findJsonlFiles(root, since)) {
706
+ filesRead++;
707
+ for (const line of readFileSync2(file, "utf8").split(`
708
+ `)) {
709
+ if (!line.trim())
710
+ continue;
711
+ linesRead++;
712
+ let entry;
713
+ try {
714
+ entry = JSON.parse(line);
715
+ } catch {
716
+ continue;
717
+ }
718
+ if (!isRecentEntry(entry, options.since))
719
+ continue;
720
+ for (const skill of extractSkills(entry, options.mode)) {
721
+ counts.set(skill, (counts.get(skill) ?? 0) + 1);
722
+ }
723
+ }
724
+ }
725
+ return { counts, filesRead, linesRead };
726
+ }
727
+
728
+ // src/readers/codex.ts
729
+ import { existsSync, readFileSync as readFileSync3 } from "node:fs";
730
+ function readCodexUsage(options) {
731
+ return options.mode === "mentions" ? readCodexMentions(options) : readCodexActivations(options);
732
+ }
733
+ function readCodexActivations(options) {
734
+ const root = expandHome(options.root ?? "~/.codex/sessions");
735
+ const counts = new Map;
736
+ let filesRead = 0;
737
+ let linesRead = 0;
738
+ const since = options.scanAllFiles ? undefined : options.since;
739
+ for (const file of findJsonlFiles(root, since)) {
740
+ filesRead++;
741
+ for (const line of readFileSync3(file, "utf8").split(`
742
+ `)) {
743
+ if (!line.trim())
744
+ continue;
745
+ linesRead++;
746
+ let entry;
747
+ try {
748
+ entry = JSON.parse(line);
749
+ } catch {
750
+ continue;
751
+ }
752
+ if (!isRecentEntry(entry, options.since))
753
+ continue;
754
+ for (const skill of extractCodexActivations(entry)) {
755
+ counts.set(skill, (counts.get(skill) ?? 0) + 1);
756
+ }
757
+ }
758
+ }
759
+ return { counts, filesRead, linesRead };
760
+ }
761
+ function readCodexMentions(options) {
762
+ const historyPath = expandHome(options.history ?? "~/.codex/history.jsonl");
763
+ const counts = new Map;
764
+ let linesRead = 0;
765
+ if (!existsSync(historyPath))
766
+ return { counts, filesRead: 0, linesRead: 0 };
767
+ for (const line of readFileSync3(historyPath, "utf8").split(`
768
+ `)) {
769
+ if (!line.trim())
770
+ continue;
771
+ linesRead++;
772
+ let entry;
773
+ try {
774
+ entry = JSON.parse(line);
775
+ } catch {
776
+ continue;
777
+ }
778
+ if (!isRecentEntry(entry, options.since))
779
+ continue;
780
+ for (const skill of extractCodexMentions(entry)) {
781
+ counts.set(skill, (counts.get(skill) ?? 0) + 1);
782
+ }
783
+ }
784
+ return { counts, filesRead: 1, linesRead };
785
+ }
786
+
787
+ // src/utils/period.ts
788
+ var UNITS = { d: 1, w: 7, m: 30, y: 365 };
789
+ var MS_PER_DAY = 24 * 60 * 60 * 1000;
790
+ function parsePeriod(period) {
791
+ const match = period.match(/^(\d+)([dwmy])$/);
792
+ if (!match)
793
+ throw new Error(`Invalid period: "${period}". Use values like 7d, 2w, 1m, 1y.`);
794
+ const unit = UNITS[match[2] ?? ""] ?? 1;
795
+ return Number(match[1]) * unit;
796
+ }
797
+
798
+ // src/commands/audit.ts
799
+ function parseAgents(agent) {
800
+ if (!agent)
801
+ throw new Error("--agent is required. Use --agent claude-code, --agent codex, or both.");
802
+ const normalized = agent.split(",").map((a) => a.trim()).map((a) => {
803
+ if (a === "codex")
804
+ return "codex";
805
+ if (["claude", "claude-code", "claudecode"].includes(a))
806
+ return "claude-code";
807
+ throw new Error(`Unknown agent: "${a}". Use "claude-code" or "codex".`);
808
+ });
809
+ return [...new Set(normalized)];
810
+ }
811
+ function toRows(counts) {
812
+ return [...counts.entries()].sort(([sa, ca], [sb, cb]) => cb - ca || sa.localeCompare(sb)).map(([skill, count]) => ({ skill, count }));
813
+ }
814
+ async function runAudit(args) {
815
+ const agents = parseAgents(args.agent);
816
+ const since = args.since ? new Date(`${args.since}T00:00:00`) : new Date(Date.now() - parsePeriod(args.period) * 24 * 60 * 60 * 1000);
817
+ if (Number.isNaN(since.getTime())) {
818
+ console.error(`Invalid --since value: ${args.since}`);
819
+ process.exit(1);
820
+ }
821
+ const results = [];
822
+ for (const agent of agents) {
823
+ if (agent === "claude-code") {
824
+ const mode = args.mode ?? "attributed";
825
+ const result = readClaudeUsage({
826
+ since,
827
+ mode,
828
+ root: args.root,
829
+ scanAllFiles: args["scan-all-files"]
830
+ });
831
+ results.push({
832
+ agent,
833
+ mode,
834
+ rows: toRows(result.counts),
835
+ stats: { filesRead: result.filesRead, linesRead: result.linesRead }
836
+ });
837
+ } else {
838
+ const mode = args.mode ?? "activations";
839
+ const result = readCodexUsage({
840
+ since,
841
+ mode,
842
+ root: args.root,
843
+ scanAllFiles: args["scan-all-files"]
844
+ });
845
+ results.push({
846
+ agent,
847
+ mode,
848
+ rows: toRows(result.counts),
849
+ stats: { filesRead: result.filesRead, linesRead: result.linesRead }
850
+ });
851
+ }
852
+ }
853
+ if (args.format === "json") {
854
+ const output = results.map(({ agent, mode, rows }) => ({
855
+ agent,
856
+ mode,
857
+ since: since.toISOString(),
858
+ skills: rows
859
+ }));
860
+ console.log(JSON.stringify(output.length === 1 ? output[0] : output, null, 2));
861
+ return;
862
+ }
863
+ for (const { agent, mode, rows, stats } of results) {
864
+ console.log(`
865
+ ${agent} skill usage since ${since.toISOString().slice(0, 10)} (${mode})`);
866
+ console.log(`Files read: ${stats.filesRead}; JSONL lines read: ${stats.linesRead}`);
867
+ if (rows.length === 0) {
868
+ console.log("No skills found.");
869
+ } else {
870
+ const maxLen = Math.max(...rows.map((r) => String(r.count).length));
871
+ for (const r of rows)
872
+ console.log(`${String(r.count).padStart(maxLen)} ${r.skill}`);
873
+ }
874
+ }
875
+ }
876
+ var auditCommand = defineCommand({
877
+ meta: { description: "Audit skill usage from agent session logs" },
878
+ args: {
879
+ agent: { type: "string", alias: "a", description: "claude-code, codex, or comma-separated" },
880
+ period: { type: "string", alias: "p", default: "7d", description: "7d, 2w, 1m, 1y" },
881
+ since: { type: "string", description: "yyyy-mm-dd, overrides --period" },
882
+ mode: { type: "string", description: "attributed | activations | mentions" },
883
+ format: { type: "string", default: "text", description: "text | json" },
884
+ root: { type: "string", description: "Override agent sessions directory" },
885
+ "scan-all-files": { type: "boolean", default: false, description: "Ignore file mtime" }
886
+ },
887
+ async run({ args }) {
888
+ try {
889
+ await runAudit(args);
890
+ } catch (e) {
891
+ console.error(e instanceof Error ? e.message : String(e));
892
+ process.exit(1);
893
+ }
894
+ }
895
+ });
896
+
897
+ // src/lock/file.ts
898
+ import {
899
+ copyFileSync,
900
+ existsSync as existsSync2,
901
+ mkdirSync,
902
+ readFileSync as readFileSync4,
903
+ renameSync,
904
+ writeFileSync
905
+ } from "node:fs";
906
+ import { homedir as homedir2 } from "node:os";
907
+ import { basename, dirname, join as join3 } from "node:path";
908
+ function getLockPath(global) {
909
+ return global ? join3(homedir2(), ".agents", ".skill-lock.json") : "skills-lock.json";
910
+ }
911
+ function readLock(path) {
912
+ if (!existsSync2(path))
913
+ return { skills: {} };
914
+ return JSON.parse(readFileSync4(path, "utf8"));
915
+ }
916
+ function writeLock(path, lock) {
917
+ mkdirSync(dirname(path), { recursive: true });
918
+ const tmp = join3(dirname(path), `.${Date.now()}.skill-lock.json`);
919
+ writeFileSync(tmp, `${JSON.stringify(lock, null, 2)}
920
+ `);
921
+ renameSync(tmp, path);
922
+ }
923
+ function getBackupPath(path) {
924
+ return join3(dirname(path), ".tmp", `${basename(path)}.bak`);
925
+ }
926
+ function backupLock(path) {
927
+ const backupPath = getBackupPath(path);
928
+ mkdirSync(dirname(backupPath), { recursive: true });
929
+ copyFileSync(path, backupPath);
930
+ return backupPath;
931
+ }
932
+ function removeSkillFromLock(path, skill, { skipBackup = false } = {}) {
933
+ if (!existsSync2(path))
934
+ return { removed: false };
935
+ const lock = readLock(path);
936
+ if (!Object.hasOwn(lock.skills, skill))
937
+ return { removed: false };
938
+ const backupPath = skipBackup ? undefined : backupLock(path);
939
+ delete lock.skills[skill];
940
+ writeLock(path, lock);
941
+ return { removed: true, backupPath };
942
+ }
943
+
944
+ // src/commands/list.ts
945
+ var listCommand = defineCommand({
946
+ meta: { description: "List skills in the lock file" },
947
+ args: {
948
+ global: { type: "boolean", alias: "g", default: false, description: "Use global lock file" }
949
+ },
950
+ run({ args }) {
951
+ const path = getLockPath(args.global);
952
+ const lock = readLock(path);
953
+ const skills = Object.keys(lock.skills).sort();
954
+ console.log(JSON.stringify(skills, null, 2));
955
+ }
956
+ });
957
+
958
+ // src/commands/remove.ts
959
+ import { existsSync as existsSync3 } from "node:fs";
960
+ var removeCommand = defineCommand({
961
+ meta: { description: "Remove one or more skills from the lock file" },
962
+ args: {
963
+ global: { type: "boolean", alias: "g", default: false, description: "Use global lock file" },
964
+ "dry-run": { type: "boolean", default: false, description: "Print without making changes" }
965
+ },
966
+ run({ args }) {
967
+ const { global: isGlobal, "dry-run": dryRun } = args;
968
+ const subcmdIdx = process.argv.findIndex((a) => a === "remove" || a === "rm");
969
+ const skills = process.argv.slice(subcmdIdx + 1).filter((a) => !a.startsWith("-"));
970
+ if (skills.length === 0) {
971
+ console.error("No skill names provided");
972
+ process.exit(1);
973
+ }
974
+ const path = getLockPath(isGlobal);
975
+ if (dryRun) {
976
+ for (const skill of skills) {
977
+ console.log(`Would remove "${skill}" from ${path}`);
978
+ }
979
+ return;
980
+ }
981
+ const backupPath = existsSync3(path) ? backupLock(path) : undefined;
982
+ for (const skill of skills) {
983
+ const result = removeSkillFromLock(path, skill, { skipBackup: true });
984
+ if (result.removed) {
985
+ console.log(`Removed "${skill}" from ${path}`);
986
+ } else {
987
+ console.log(`"${skill}" is not in ${path}`);
988
+ }
989
+ }
990
+ if (backupPath)
991
+ console.log(`Backup: ${backupPath}`);
992
+ const updated = readLock(path);
993
+ console.log(JSON.stringify(Object.keys(updated.skills).sort(), null, 2));
994
+ }
995
+ });
996
+
997
+ // src/cli.ts
998
+ var SUBCOMMANDS = new Set(["audit", "list", "ls", "remove", "rm"]);
999
+ var HELP_FLAGS = new Set(["--help", "-h", "--version", "-v"]);
1000
+ var firstArg = process.argv[2];
1001
+ if (firstArg === undefined || !SUBCOMMANDS.has(firstArg) && !HELP_FLAGS.has(firstArg)) {
1002
+ process.argv.splice(2, 0, "audit");
1003
+ }
1004
+ var main = defineCommand({
1005
+ meta: {
1006
+ name: "skillio",
1007
+ version: "0.1.1",
1008
+ description: "Audit and manage AI agent skills"
1009
+ },
1010
+ subCommands: {
1011
+ audit: auditCommand,
1012
+ list: listCommand,
1013
+ ls: listCommand,
1014
+ remove: removeCommand,
1015
+ rm: removeCommand
1016
+ }
1017
+ });
1018
+ runMain(main);
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "skillio",
3
+ "version": "0.1.1",
4
+ "description": "Audit and manage AI agent skills for Claude Code and Codex",
5
+ "license": "MIT",
6
+ "author": "ihororlovskyi",
7
+ "homepage": "https://github.com/ihororlovskyi/skillio#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/ihororlovskyi/skillio.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/ihororlovskyi/skillio/issues"
14
+ },
15
+ "keywords": [
16
+ "ai",
17
+ "agent",
18
+ "skills",
19
+ "claude",
20
+ "codex",
21
+ "audit",
22
+ "cli"
23
+ ],
24
+ "type": "module",
25
+ "bin": {
26
+ "skillio": "dist/cli.js",
27
+ "skl": "dist/cli.js"
28
+ },
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js",
33
+ "require": "./dist/index.cjs"
34
+ },
35
+ "./package.json": "./package.json"
36
+ },
37
+ "files": [
38
+ "dist"
39
+ ],
40
+ "engines": {
41
+ "node": ">=20"
42
+ },
43
+ "scripts": {
44
+ "build": "~/.bun/bin/bun run node_modules/.bin/bunup",
45
+ "lint": "biome check src/",
46
+ "format": "biome format --write src/",
47
+ "test": "vitest run",
48
+ "test:e2e": "vitest run --config vitest.e2e.config.ts",
49
+ "release": "changeset publish",
50
+ "prepublishOnly": "biome check src/ && vitest run && ~/.bun/bin/bun run node_modules/.bin/bunup && vitest run --config vitest.e2e.config.ts"
51
+ },
52
+ "devDependencies": {
53
+ "@biomejs/biome": "^2.4.14",
54
+ "@changesets/cli": "^2.31.0",
55
+ "@types/node": "^25.6.2",
56
+ "bunup": "^0.16.31",
57
+ "citty": "^0.2.2",
58
+ "typescript": "^6.0.3",
59
+ "vitest": "^4.1.5"
60
+ }
61
+ }