@skillkit/cli 1.3.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +171 -18
- package/dist/index.d.ts +238 -1
- package/dist/index.js +2032 -203
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1771,15 +1771,19 @@ ${chalk12.bold("Agents:")}`);
|
|
|
1771
1771
|
|
|
1772
1772
|
// src/commands/recommend.ts
|
|
1773
1773
|
import { Command as Command14, Option as Option13 } from "clipanion";
|
|
1774
|
-
import {
|
|
1775
|
-
import { resolve as resolve2, join as join6 } from "path";
|
|
1774
|
+
import { resolve as resolve2 } from "path";
|
|
1776
1775
|
import chalk13 from "chalk";
|
|
1776
|
+
import ora3 from "ora";
|
|
1777
1777
|
import {
|
|
1778
1778
|
ContextManager as ContextManager2,
|
|
1779
|
-
RecommendationEngine
|
|
1779
|
+
RecommendationEngine,
|
|
1780
|
+
buildSkillIndex,
|
|
1781
|
+
saveIndex,
|
|
1782
|
+
loadIndex as loadIndexFromCache,
|
|
1783
|
+
isIndexStale,
|
|
1784
|
+
INDEX_PATH,
|
|
1785
|
+
KNOWN_SKILL_REPOS
|
|
1780
1786
|
} from "@skillkit/core";
|
|
1781
|
-
var INDEX_PATH = join6(process.env.HOME || "~", ".skillkit", "index.json");
|
|
1782
|
-
var INDEX_CACHE_HOURS = 24;
|
|
1783
1787
|
var RecommendCommand = class extends Command14 {
|
|
1784
1788
|
static paths = [["recommend"], ["rec"]];
|
|
1785
1789
|
static usage = Command14.Usage({
|
|
@@ -1803,7 +1807,8 @@ var RecommendCommand = class extends Command14 {
|
|
|
1803
1807
|
["Filter by category", "$0 recommend --category security"],
|
|
1804
1808
|
["Show detailed reasons", "$0 recommend --verbose"],
|
|
1805
1809
|
["Update skill index", "$0 recommend --update"],
|
|
1806
|
-
["Search for skills by task", '$0 recommend --
|
|
1810
|
+
["Search for skills by task", '$0 recommend --task "authentication"'],
|
|
1811
|
+
["Search for skills (alias)", '$0 recommend --search "testing"']
|
|
1807
1812
|
]
|
|
1808
1813
|
});
|
|
1809
1814
|
// Limit number of results
|
|
@@ -1826,10 +1831,14 @@ var RecommendCommand = class extends Command14 {
|
|
|
1826
1831
|
update = Option13.Boolean("--update,-u", false, {
|
|
1827
1832
|
description: "Update skill index from sources"
|
|
1828
1833
|
});
|
|
1829
|
-
// Search mode
|
|
1834
|
+
// Search mode (--search or --task)
|
|
1830
1835
|
search = Option13.String("--search,-s", {
|
|
1831
1836
|
description: "Search skills by task/query"
|
|
1832
1837
|
});
|
|
1838
|
+
// Task alias for search (GSD-style)
|
|
1839
|
+
task = Option13.String("--task,-t", {
|
|
1840
|
+
description: "Search skills by task (alias for --search)"
|
|
1841
|
+
});
|
|
1833
1842
|
// Include installed skills
|
|
1834
1843
|
includeInstalled = Option13.Boolean("--include-installed", false, {
|
|
1835
1844
|
description: "Include already installed skills"
|
|
@@ -1845,7 +1854,7 @@ var RecommendCommand = class extends Command14 {
|
|
|
1845
1854
|
async execute() {
|
|
1846
1855
|
const targetPath = resolve2(this.projectPath || process.cwd());
|
|
1847
1856
|
if (this.update) {
|
|
1848
|
-
return this.updateIndex();
|
|
1857
|
+
return await this.updateIndex();
|
|
1849
1858
|
}
|
|
1850
1859
|
const profile = await this.getProjectProfile(targetPath);
|
|
1851
1860
|
if (!profile) {
|
|
@@ -1862,8 +1871,9 @@ var RecommendCommand = class extends Command14 {
|
|
|
1862
1871
|
}
|
|
1863
1872
|
const engine = new RecommendationEngine();
|
|
1864
1873
|
engine.loadIndex(index);
|
|
1865
|
-
|
|
1866
|
-
|
|
1874
|
+
const searchQuery = this.search || this.task;
|
|
1875
|
+
if (searchQuery) {
|
|
1876
|
+
return this.handleSearch(engine, searchQuery);
|
|
1867
1877
|
}
|
|
1868
1878
|
const result = engine.recommend(profile, {
|
|
1869
1879
|
limit: this.limit ? parseInt(this.limit, 10) : 10,
|
|
@@ -2008,219 +2018,2028 @@ var RecommendCommand = class extends Command14 {
|
|
|
2008
2018
|
* Load skill index from cache
|
|
2009
2019
|
*/
|
|
2010
2020
|
loadIndex() {
|
|
2011
|
-
|
|
2021
|
+
const index = loadIndexFromCache();
|
|
2022
|
+
if (!index) {
|
|
2012
2023
|
return null;
|
|
2013
2024
|
}
|
|
2014
|
-
|
|
2015
|
-
const content = readFileSync4(INDEX_PATH, "utf-8");
|
|
2016
|
-
const index = JSON.parse(content);
|
|
2025
|
+
if (isIndexStale(index) && !this.json) {
|
|
2017
2026
|
const lastUpdated = new Date(index.lastUpdated);
|
|
2018
2027
|
const hoursSinceUpdate = (Date.now() - lastUpdated.getTime()) / (1e3 * 60 * 60);
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
chalk13.dim(`Index is ${Math.round(hoursSinceUpdate)} hours old. Run --update to refresh.
|
|
2028
|
+
console.log(
|
|
2029
|
+
chalk13.dim(`Index is ${Math.round(hoursSinceUpdate)} hours old. Run --update to refresh.
|
|
2022
2030
|
`)
|
|
2023
|
-
|
|
2024
|
-
}
|
|
2025
|
-
return index;
|
|
2026
|
-
} catch {
|
|
2027
|
-
return null;
|
|
2031
|
+
);
|
|
2028
2032
|
}
|
|
2033
|
+
return index;
|
|
2029
2034
|
}
|
|
2030
2035
|
/**
|
|
2031
2036
|
* Update skill index from sources
|
|
2032
2037
|
*/
|
|
2033
|
-
updateIndex() {
|
|
2034
|
-
console.log(chalk13.cyan("Updating skill index...\n"));
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
name: "anthropics",
|
|
2048
|
-
url: "https://github.com/anthropics/skills",
|
|
2049
|
-
lastFetched: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2050
|
-
skillCount: 3
|
|
2038
|
+
async updateIndex() {
|
|
2039
|
+
console.log(chalk13.cyan("Updating skill index from GitHub repositories...\n"));
|
|
2040
|
+
console.log(chalk13.dim(`Sources: ${KNOWN_SKILL_REPOS.map((r) => `${r.owner}/${r.repo}`).join(", ")}
|
|
2041
|
+
`));
|
|
2042
|
+
const spinner = ora3("Fetching skills...").start();
|
|
2043
|
+
try {
|
|
2044
|
+
const { index, errors } = await buildSkillIndex(KNOWN_SKILL_REPOS, (message) => {
|
|
2045
|
+
spinner.text = message;
|
|
2046
|
+
});
|
|
2047
|
+
spinner.stop();
|
|
2048
|
+
if (errors.length > 0) {
|
|
2049
|
+
console.log(chalk13.yellow("\nWarnings:"));
|
|
2050
|
+
for (const error of errors) {
|
|
2051
|
+
console.log(chalk13.dim(` \u2022 ${error}`));
|
|
2051
2052
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
console.log(chalk13.dim(` Sources: ${sampleIndex.sources.map((s) => s.name).join(", ")}`));
|
|
2061
|
-
console.log(chalk13.dim(` Saved to: ${INDEX_PATH}
|
|
2053
|
+
console.log();
|
|
2054
|
+
}
|
|
2055
|
+
saveIndex(index);
|
|
2056
|
+
console.log(chalk13.green(`\u2713 Updated index with ${index.skills.length} skills`));
|
|
2057
|
+
if (index.sources.length > 0) {
|
|
2058
|
+
console.log(chalk13.dim(` Sources: ${index.sources.map((s) => s.name).join(", ")}`));
|
|
2059
|
+
}
|
|
2060
|
+
console.log(chalk13.dim(` Saved to: ${INDEX_PATH}
|
|
2062
2061
|
`));
|
|
2063
|
-
|
|
2062
|
+
return 0;
|
|
2063
|
+
} catch (error) {
|
|
2064
|
+
spinner.fail("Failed to update index");
|
|
2065
|
+
console.error(chalk13.red(error instanceof Error ? error.message : String(error)));
|
|
2066
|
+
return 1;
|
|
2067
|
+
}
|
|
2064
2068
|
}
|
|
2065
2069
|
};
|
|
2066
2070
|
function truncate2(str, maxLen) {
|
|
2067
2071
|
if (str.length <= maxLen) return str;
|
|
2068
2072
|
return str.slice(0, maxLen - 3) + "...";
|
|
2069
2073
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
},
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
{
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2074
|
+
|
|
2075
|
+
// src/commands/status.ts
|
|
2076
|
+
import { Command as Command15, Option as Option14 } from "clipanion";
|
|
2077
|
+
import { resolve as resolve3 } from "path";
|
|
2078
|
+
import chalk14 from "chalk";
|
|
2079
|
+
import { SessionManager } from "@skillkit/core";
|
|
2080
|
+
var StatusCommand = class extends Command15 {
|
|
2081
|
+
static paths = [["status"], ["st"]];
|
|
2082
|
+
static usage = Command15.Usage({
|
|
2083
|
+
description: "Show current session state and execution progress",
|
|
2084
|
+
details: `
|
|
2085
|
+
The status command shows the current state of skill execution sessions,
|
|
2086
|
+
including any paused executions and recent history.
|
|
2087
|
+
`,
|
|
2088
|
+
examples: [
|
|
2089
|
+
["Show session status", "$0 status"],
|
|
2090
|
+
["Show with history", "$0 status --history"],
|
|
2091
|
+
["Show JSON output", "$0 status --json"]
|
|
2092
|
+
]
|
|
2093
|
+
});
|
|
2094
|
+
// Show history
|
|
2095
|
+
history = Option14.Boolean("--history,-h", false, {
|
|
2096
|
+
description: "Show execution history"
|
|
2097
|
+
});
|
|
2098
|
+
// History limit
|
|
2099
|
+
limit = Option14.String("--limit,-l", {
|
|
2100
|
+
description: "Limit history entries (default: 10)"
|
|
2101
|
+
});
|
|
2102
|
+
// JSON output
|
|
2103
|
+
json = Option14.Boolean("--json,-j", false, {
|
|
2104
|
+
description: "Output in JSON format"
|
|
2105
|
+
});
|
|
2106
|
+
// Project path
|
|
2107
|
+
projectPath = Option14.String("--path,-p", {
|
|
2108
|
+
description: "Project path (default: current directory)"
|
|
2109
|
+
});
|
|
2110
|
+
async execute() {
|
|
2111
|
+
const targetPath = resolve3(this.projectPath || process.cwd());
|
|
2112
|
+
const manager = new SessionManager(targetPath);
|
|
2113
|
+
const state = manager.get();
|
|
2114
|
+
if (this.json) {
|
|
2115
|
+
console.log(JSON.stringify(state || { message: "No session found" }, null, 2));
|
|
2116
|
+
return 0;
|
|
2117
|
+
}
|
|
2118
|
+
if (!state) {
|
|
2119
|
+
console.log(chalk14.dim("No active session found."));
|
|
2120
|
+
console.log(chalk14.dim('Run a skill with "skillkit run <skill>" to start a session.'));
|
|
2121
|
+
return 0;
|
|
2122
|
+
}
|
|
2123
|
+
if (state.currentExecution) {
|
|
2124
|
+
const exec = state.currentExecution;
|
|
2125
|
+
const statusColor = exec.status === "paused" ? chalk14.yellow : chalk14.green;
|
|
2126
|
+
console.log(chalk14.cyan("Current Execution:\n"));
|
|
2127
|
+
console.log(` Skill: ${chalk14.bold(exec.skillName)}`);
|
|
2128
|
+
console.log(` Source: ${chalk14.dim(exec.skillSource)}`);
|
|
2129
|
+
console.log(` Status: ${statusColor(exec.status)}`);
|
|
2130
|
+
console.log(` Progress: ${exec.currentStep}/${exec.totalSteps} tasks`);
|
|
2131
|
+
console.log(` Started: ${chalk14.dim(new Date(exec.startedAt).toLocaleString())}`);
|
|
2132
|
+
if (exec.pausedAt) {
|
|
2133
|
+
console.log(` Paused: ${chalk14.dim(new Date(exec.pausedAt).toLocaleString())}`);
|
|
2134
|
+
}
|
|
2135
|
+
console.log(chalk14.cyan("\n Tasks:"));
|
|
2136
|
+
for (const task of exec.tasks) {
|
|
2137
|
+
const statusIcon = this.getStatusIcon(task.status);
|
|
2138
|
+
const statusText = this.getStatusColor(task.status)(task.status);
|
|
2139
|
+
console.log(` ${statusIcon} ${task.name} - ${statusText}`);
|
|
2140
|
+
if (task.error) {
|
|
2141
|
+
console.log(` ${chalk14.red("Error:")} ${task.error}`);
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
if (exec.status === "paused") {
|
|
2145
|
+
console.log(chalk14.yellow("\n Resume with: skillkit resume"));
|
|
2146
|
+
}
|
|
2147
|
+
} else {
|
|
2148
|
+
console.log(chalk14.dim("No active execution."));
|
|
2149
|
+
}
|
|
2150
|
+
if (this.history || !state.currentExecution && state.history.length > 0) {
|
|
2151
|
+
const limit = this.limit ? parseInt(this.limit, 10) : 10;
|
|
2152
|
+
const history = manager.getHistory(limit);
|
|
2153
|
+
if (history.length > 0) {
|
|
2154
|
+
console.log(chalk14.cyan("\nExecution History:\n"));
|
|
2155
|
+
for (const entry of history) {
|
|
2156
|
+
const statusColor = entry.status === "completed" ? chalk14.green : chalk14.red;
|
|
2157
|
+
const duration = this.formatDuration(entry.durationMs);
|
|
2158
|
+
console.log(` ${statusColor("\u25CF")} ${chalk14.bold(entry.skillName)}`);
|
|
2159
|
+
console.log(` ${chalk14.dim(entry.skillSource)} \u2022 ${duration}`);
|
|
2160
|
+
console.log(` ${chalk14.dim(new Date(entry.completedAt).toLocaleString())}`);
|
|
2161
|
+
if (entry.commits.length > 0) {
|
|
2162
|
+
console.log(` Commits: ${chalk14.dim(entry.commits.join(", "))}`);
|
|
2163
|
+
}
|
|
2164
|
+
if (entry.error) {
|
|
2165
|
+
console.log(` ${chalk14.red("Error:")} ${entry.error}`);
|
|
2166
|
+
}
|
|
2167
|
+
console.log();
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
if (state.decisions.length > 0) {
|
|
2172
|
+
console.log(chalk14.cyan("Saved Decisions:\n"));
|
|
2173
|
+
for (const decision of state.decisions.slice(0, 5)) {
|
|
2174
|
+
console.log(` ${chalk14.dim(decision.key)}: ${decision.value}`);
|
|
2175
|
+
}
|
|
2176
|
+
if (state.decisions.length > 5) {
|
|
2177
|
+
console.log(chalk14.dim(` ... and ${state.decisions.length - 5} more`));
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
return 0;
|
|
2181
|
+
}
|
|
2182
|
+
getStatusIcon(status) {
|
|
2183
|
+
switch (status) {
|
|
2184
|
+
case "completed":
|
|
2185
|
+
return chalk14.green("\u2713");
|
|
2186
|
+
case "failed":
|
|
2187
|
+
return chalk14.red("\u2717");
|
|
2188
|
+
case "in_progress":
|
|
2189
|
+
return chalk14.blue("\u25CF");
|
|
2190
|
+
case "paused":
|
|
2191
|
+
return chalk14.yellow("\u23F8");
|
|
2192
|
+
default:
|
|
2193
|
+
return chalk14.dim("\u25CB");
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
getStatusColor(status) {
|
|
2197
|
+
switch (status) {
|
|
2198
|
+
case "completed":
|
|
2199
|
+
return chalk14.green;
|
|
2200
|
+
case "failed":
|
|
2201
|
+
return chalk14.red;
|
|
2202
|
+
case "in_progress":
|
|
2203
|
+
return chalk14.blue;
|
|
2204
|
+
case "paused":
|
|
2205
|
+
return chalk14.yellow;
|
|
2206
|
+
default:
|
|
2207
|
+
return chalk14.dim;
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
formatDuration(ms) {
|
|
2211
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
2212
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
2213
|
+
const minutes = Math.floor(ms / 6e4);
|
|
2214
|
+
const seconds = Math.floor(ms % 6e4 / 1e3);
|
|
2215
|
+
return `${minutes}m ${seconds}s`;
|
|
2216
|
+
}
|
|
2217
|
+
};
|
|
2218
|
+
|
|
2219
|
+
// src/commands/pause.ts
|
|
2220
|
+
import { Command as Command16, Option as Option15 } from "clipanion";
|
|
2221
|
+
import { resolve as resolve4 } from "path";
|
|
2222
|
+
import chalk15 from "chalk";
|
|
2223
|
+
import { SessionManager as SessionManager2 } from "@skillkit/core";
|
|
2224
|
+
var PauseCommand = class extends Command16 {
|
|
2225
|
+
static paths = [["pause"]];
|
|
2226
|
+
static usage = Command16.Usage({
|
|
2227
|
+
description: "Pause current skill execution for later resumption",
|
|
2228
|
+
details: `
|
|
2229
|
+
The pause command saves the current execution state so you can
|
|
2230
|
+
continue later with "skillkit resume".
|
|
2231
|
+
|
|
2232
|
+
This is useful when you need to:
|
|
2233
|
+
- Take a break from a long skill execution
|
|
2234
|
+
- Handle an interruption
|
|
2235
|
+
- Review progress before continuing
|
|
2236
|
+
`,
|
|
2237
|
+
examples: [
|
|
2238
|
+
["Pause current execution", "$0 pause"]
|
|
2239
|
+
]
|
|
2240
|
+
});
|
|
2241
|
+
// Project path
|
|
2242
|
+
projectPath = Option15.String("--path,-p", {
|
|
2243
|
+
description: "Project path (default: current directory)"
|
|
2244
|
+
});
|
|
2245
|
+
async execute() {
|
|
2246
|
+
const targetPath = resolve4(this.projectPath || process.cwd());
|
|
2247
|
+
const manager = new SessionManager2(targetPath);
|
|
2248
|
+
const state = manager.get();
|
|
2249
|
+
if (!state) {
|
|
2250
|
+
console.log(chalk15.yellow("No active session found."));
|
|
2251
|
+
return 1;
|
|
2252
|
+
}
|
|
2253
|
+
if (!state.currentExecution) {
|
|
2254
|
+
console.log(chalk15.yellow("No skill execution in progress."));
|
|
2255
|
+
return 1;
|
|
2256
|
+
}
|
|
2257
|
+
if (state.currentExecution.status === "paused") {
|
|
2258
|
+
console.log(chalk15.yellow("Execution is already paused."));
|
|
2259
|
+
console.log(chalk15.dim("Resume with: skillkit resume"));
|
|
2260
|
+
return 0;
|
|
2261
|
+
}
|
|
2262
|
+
const success = manager.pause();
|
|
2263
|
+
if (success) {
|
|
2264
|
+
const exec = state.currentExecution;
|
|
2265
|
+
console.log(chalk15.green("\u2713 Execution paused"));
|
|
2266
|
+
console.log();
|
|
2267
|
+
console.log(` Skill: ${chalk15.bold(exec.skillName)}`);
|
|
2268
|
+
console.log(` Progress: ${exec.currentStep}/${exec.totalSteps} tasks completed`);
|
|
2269
|
+
console.log();
|
|
2270
|
+
console.log(chalk15.dim("Resume with: skillkit resume"));
|
|
2271
|
+
console.log(chalk15.dim("View status: skillkit status"));
|
|
2272
|
+
return 0;
|
|
2273
|
+
} else {
|
|
2274
|
+
console.log(chalk15.red("Failed to pause execution."));
|
|
2275
|
+
return 1;
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
};
|
|
2279
|
+
|
|
2280
|
+
// src/commands/resume.ts
|
|
2281
|
+
import { Command as Command17, Option as Option16 } from "clipanion";
|
|
2282
|
+
import { resolve as resolve5 } from "path";
|
|
2283
|
+
import chalk16 from "chalk";
|
|
2284
|
+
import { SessionManager as SessionManager3 } from "@skillkit/core";
|
|
2285
|
+
var ResumeCommand = class extends Command17 {
|
|
2286
|
+
static paths = [["resume"]];
|
|
2287
|
+
static usage = Command17.Usage({
|
|
2288
|
+
description: "Resume a paused skill execution",
|
|
2289
|
+
details: `
|
|
2290
|
+
The resume command continues a previously paused skill execution
|
|
2291
|
+
from where it left off.
|
|
2292
|
+
|
|
2293
|
+
The execution state is preserved, including:
|
|
2294
|
+
- Completed tasks
|
|
2295
|
+
- User decisions
|
|
2296
|
+
- Modified files
|
|
2297
|
+
`,
|
|
2298
|
+
examples: [
|
|
2299
|
+
["Resume paused execution", "$0 resume"]
|
|
2300
|
+
]
|
|
2301
|
+
});
|
|
2302
|
+
// Project path
|
|
2303
|
+
projectPath = Option16.String("--path,-p", {
|
|
2304
|
+
description: "Project path (default: current directory)"
|
|
2305
|
+
});
|
|
2306
|
+
async execute() {
|
|
2307
|
+
const targetPath = resolve5(this.projectPath || process.cwd());
|
|
2308
|
+
const manager = new SessionManager3(targetPath);
|
|
2309
|
+
const state = manager.get();
|
|
2310
|
+
if (!state) {
|
|
2311
|
+
console.log(chalk16.yellow("No active session found."));
|
|
2312
|
+
return 1;
|
|
2313
|
+
}
|
|
2314
|
+
if (!state.currentExecution) {
|
|
2315
|
+
console.log(chalk16.yellow("No skill execution to resume."));
|
|
2316
|
+
console.log(chalk16.dim("Start a new execution with: skillkit run <skill>"));
|
|
2317
|
+
return 1;
|
|
2318
|
+
}
|
|
2319
|
+
if (state.currentExecution.status !== "paused") {
|
|
2320
|
+
if (state.currentExecution.status === "running") {
|
|
2321
|
+
console.log(chalk16.yellow("Execution is already running."));
|
|
2322
|
+
} else {
|
|
2323
|
+
console.log(chalk16.yellow(`Execution is ${state.currentExecution.status}.`));
|
|
2324
|
+
}
|
|
2325
|
+
return 1;
|
|
2326
|
+
}
|
|
2327
|
+
const success = manager.resume();
|
|
2328
|
+
if (success) {
|
|
2329
|
+
const exec = state.currentExecution;
|
|
2330
|
+
console.log(chalk16.green("\u2713 Execution resumed"));
|
|
2331
|
+
console.log();
|
|
2332
|
+
console.log(` Skill: ${chalk16.bold(exec.skillName)}`);
|
|
2333
|
+
console.log(` Progress: ${exec.currentStep}/${exec.totalSteps} tasks`);
|
|
2334
|
+
console.log();
|
|
2335
|
+
const nextTask = exec.tasks.find((t) => t.status === "pending" || t.status === "in_progress");
|
|
2336
|
+
if (nextTask) {
|
|
2337
|
+
console.log(` Next task: ${chalk16.cyan(nextTask.name)}`);
|
|
2338
|
+
}
|
|
2339
|
+
console.log();
|
|
2340
|
+
console.log(chalk16.dim("The execution will continue from where it left off."));
|
|
2341
|
+
console.log(chalk16.dim("View status: skillkit status"));
|
|
2342
|
+
return 0;
|
|
2343
|
+
} else {
|
|
2344
|
+
console.log(chalk16.red("Failed to resume execution."));
|
|
2345
|
+
return 1;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
};
|
|
2349
|
+
|
|
2350
|
+
// src/commands/workflow/run.ts
|
|
2351
|
+
import { Command as Command18, Option as Option17 } from "clipanion";
|
|
2352
|
+
import { resolve as resolve6 } from "path";
|
|
2353
|
+
import chalk17 from "chalk";
|
|
2354
|
+
import ora4 from "ora";
|
|
2355
|
+
import {
|
|
2356
|
+
loadWorkflowByName,
|
|
2357
|
+
loadWorkflow,
|
|
2358
|
+
validateWorkflow,
|
|
2359
|
+
createWorkflowOrchestrator
|
|
2360
|
+
} from "@skillkit/core";
|
|
2361
|
+
var WorkflowRunCommand = class extends Command18 {
|
|
2362
|
+
static paths = [["workflow", "run"], ["wf", "run"]];
|
|
2363
|
+
static usage = Command18.Usage({
|
|
2364
|
+
description: "Execute a skill workflow",
|
|
2365
|
+
details: `
|
|
2366
|
+
The workflow run command executes a workflow definition,
|
|
2367
|
+
running skills in waves with parallel or sequential execution.
|
|
2368
|
+
|
|
2369
|
+
Workflows are defined in YAML files in .skillkit/workflows/
|
|
2370
|
+
`,
|
|
2371
|
+
examples: [
|
|
2372
|
+
["Run a workflow by name", "$0 workflow run setup-project"],
|
|
2373
|
+
["Run a workflow from file", "$0 workflow run --file my-workflow.yaml"],
|
|
2374
|
+
["Dry run (no execution)", "$0 workflow run setup-project --dry-run"]
|
|
2375
|
+
]
|
|
2376
|
+
});
|
|
2377
|
+
// Workflow name
|
|
2378
|
+
workflowName = Option17.String({ required: false });
|
|
2379
|
+
// Workflow file path
|
|
2380
|
+
file = Option17.String("--file,-f", {
|
|
2381
|
+
description: "Path to workflow YAML file"
|
|
2382
|
+
});
|
|
2383
|
+
// Dry run
|
|
2384
|
+
dryRun = Option17.Boolean("--dry-run,-n", false, {
|
|
2385
|
+
description: "Show what would be executed without running"
|
|
2386
|
+
});
|
|
2387
|
+
// Verbose output
|
|
2388
|
+
verbose = Option17.Boolean("--verbose,-v", false, {
|
|
2389
|
+
description: "Show detailed execution progress"
|
|
2390
|
+
});
|
|
2391
|
+
// Continue on error
|
|
2392
|
+
continueOnError = Option17.Boolean("--continue-on-error", false, {
|
|
2393
|
+
description: "Continue execution even if a skill fails"
|
|
2394
|
+
});
|
|
2395
|
+
// JSON output
|
|
2396
|
+
json = Option17.Boolean("--json,-j", false, {
|
|
2397
|
+
description: "Output in JSON format"
|
|
2398
|
+
});
|
|
2399
|
+
// Project path
|
|
2400
|
+
projectPath = Option17.String("--path,-p", {
|
|
2401
|
+
description: "Project path (default: current directory)"
|
|
2402
|
+
});
|
|
2403
|
+
async execute() {
|
|
2404
|
+
const targetPath = resolve6(this.projectPath || process.cwd());
|
|
2405
|
+
let workflow;
|
|
2406
|
+
try {
|
|
2407
|
+
if (this.file) {
|
|
2408
|
+
workflow = loadWorkflow(resolve6(this.file));
|
|
2409
|
+
} else if (this.workflowName) {
|
|
2410
|
+
workflow = loadWorkflowByName(targetPath, this.workflowName);
|
|
2411
|
+
if (!workflow) {
|
|
2412
|
+
console.error(chalk17.red(`Workflow "${this.workflowName}" not found`));
|
|
2413
|
+
console.log(chalk17.dim("List available workflows: skillkit workflow list"));
|
|
2414
|
+
return 1;
|
|
2415
|
+
}
|
|
2416
|
+
} else {
|
|
2417
|
+
console.error(chalk17.red("Please specify a workflow name or --file"));
|
|
2418
|
+
return 1;
|
|
2419
|
+
}
|
|
2420
|
+
} catch (error) {
|
|
2421
|
+
console.error(chalk17.red(`Failed to load workflow: ${error}`));
|
|
2422
|
+
return 1;
|
|
2423
|
+
}
|
|
2424
|
+
const validation = validateWorkflow(workflow);
|
|
2425
|
+
if (!validation.valid) {
|
|
2426
|
+
console.error(chalk17.red("Invalid workflow:"));
|
|
2427
|
+
for (const error of validation.errors) {
|
|
2428
|
+
console.error(chalk17.red(` \u2022 ${error}`));
|
|
2429
|
+
}
|
|
2430
|
+
return 1;
|
|
2431
|
+
}
|
|
2432
|
+
if (this.dryRun) {
|
|
2433
|
+
this.showDryRun(workflow);
|
|
2434
|
+
return 0;
|
|
2435
|
+
}
|
|
2436
|
+
console.log(chalk17.cyan(`Executing workflow: ${chalk17.bold(workflow.name)}`));
|
|
2437
|
+
if (workflow.description) {
|
|
2438
|
+
console.log(chalk17.dim(workflow.description));
|
|
2439
|
+
}
|
|
2440
|
+
console.log();
|
|
2441
|
+
const spinner = ora4();
|
|
2442
|
+
let currentWave = -1;
|
|
2443
|
+
const orchestrator = createWorkflowOrchestrator(
|
|
2444
|
+
async (_skillName, _config) => {
|
|
2445
|
+
await new Promise((resolve11) => setTimeout(resolve11, 500));
|
|
2446
|
+
return { success: true };
|
|
2216
2447
|
},
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2448
|
+
(event) => {
|
|
2449
|
+
if (this.json) return;
|
|
2450
|
+
switch (event.type) {
|
|
2451
|
+
case "wave_start":
|
|
2452
|
+
currentWave = event.waveIndex || 0;
|
|
2453
|
+
spinner.start(`Wave ${currentWave + 1}: ${event.waveName || "Executing..."}`);
|
|
2454
|
+
break;
|
|
2455
|
+
case "skill_start":
|
|
2456
|
+
if (this.verbose) {
|
|
2457
|
+
spinner.text = `Wave ${currentWave + 1}: Running ${event.skillName}...`;
|
|
2458
|
+
}
|
|
2459
|
+
break;
|
|
2460
|
+
case "skill_complete":
|
|
2461
|
+
if (this.verbose) {
|
|
2462
|
+
const icon = event.status === "completed" ? chalk17.green("\u2713") : chalk17.red("\u2717");
|
|
2463
|
+
console.log(` ${icon} ${event.skillName}`);
|
|
2464
|
+
}
|
|
2465
|
+
break;
|
|
2466
|
+
case "wave_complete":
|
|
2467
|
+
const waveIcon = event.status === "completed" ? chalk17.green("\u2713") : chalk17.red("\u2717");
|
|
2468
|
+
spinner.stopAndPersist({
|
|
2469
|
+
symbol: waveIcon,
|
|
2470
|
+
text: `Wave ${(event.waveIndex || 0) + 1}: ${event.waveName || "Complete"}`
|
|
2471
|
+
});
|
|
2472
|
+
break;
|
|
2473
|
+
case "workflow_complete":
|
|
2474
|
+
console.log();
|
|
2475
|
+
if (event.status === "completed") {
|
|
2476
|
+
console.log(chalk17.green("\u2713 Workflow completed successfully"));
|
|
2477
|
+
} else {
|
|
2478
|
+
console.log(chalk17.red(`\u2717 Workflow ${event.status}`));
|
|
2479
|
+
if (event.error) {
|
|
2480
|
+
console.log(chalk17.red(` Error: ${event.error}`));
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
break;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
);
|
|
2487
|
+
const execution = await orchestrator.execute(workflow);
|
|
2488
|
+
if (this.json) {
|
|
2489
|
+
console.log(JSON.stringify(execution, null, 2));
|
|
2221
2490
|
}
|
|
2222
|
-
|
|
2223
|
-
}
|
|
2491
|
+
return execution.status === "completed" ? 0 : 1;
|
|
2492
|
+
}
|
|
2493
|
+
showDryRun(workflow) {
|
|
2494
|
+
console.log(chalk17.cyan("Dry Run - Workflow Execution Plan"));
|
|
2495
|
+
console.log();
|
|
2496
|
+
console.log(`Workflow: ${chalk17.bold(workflow.name)}`);
|
|
2497
|
+
if (workflow.description) {
|
|
2498
|
+
console.log(`Description: ${chalk17.dim(workflow.description)}`);
|
|
2499
|
+
}
|
|
2500
|
+
console.log();
|
|
2501
|
+
for (let i = 0; i < workflow.waves.length; i++) {
|
|
2502
|
+
const wave = workflow.waves[i];
|
|
2503
|
+
const modeLabel = wave.parallel ? chalk17.blue("[parallel]") : chalk17.yellow("[sequential]");
|
|
2504
|
+
console.log(`${chalk17.cyan(`Wave ${i + 1}`)}: ${wave.name || "Unnamed"} ${modeLabel}`);
|
|
2505
|
+
for (const skill of wave.skills) {
|
|
2506
|
+
const skillName = typeof skill === "string" ? skill : skill.skill;
|
|
2507
|
+
console.log(` \u2022 ${skillName}`);
|
|
2508
|
+
}
|
|
2509
|
+
console.log();
|
|
2510
|
+
}
|
|
2511
|
+
console.log(chalk17.dim("This is a dry run. No skills were executed."));
|
|
2512
|
+
console.log(chalk17.dim("Remove --dry-run to execute the workflow."));
|
|
2513
|
+
}
|
|
2514
|
+
};
|
|
2515
|
+
|
|
2516
|
+
// src/commands/workflow/list.ts
|
|
2517
|
+
import { Command as Command19, Option as Option18 } from "clipanion";
|
|
2518
|
+
import { resolve as resolve7 } from "path";
|
|
2519
|
+
import chalk18 from "chalk";
|
|
2520
|
+
import { listWorkflows } from "@skillkit/core";
|
|
2521
|
+
var WorkflowListCommand = class extends Command19 {
|
|
2522
|
+
static paths = [["workflow", "list"], ["wf", "list"], ["workflow", "ls"], ["wf", "ls"]];
|
|
2523
|
+
static usage = Command19.Usage({
|
|
2524
|
+
description: "List available workflows",
|
|
2525
|
+
details: `
|
|
2526
|
+
The workflow list command shows all available workflows
|
|
2527
|
+
defined in .skillkit/workflows/
|
|
2528
|
+
`,
|
|
2529
|
+
examples: [
|
|
2530
|
+
["List all workflows", "$0 workflow list"],
|
|
2531
|
+
["List with details", "$0 workflow list --verbose"]
|
|
2532
|
+
]
|
|
2533
|
+
});
|
|
2534
|
+
// Verbose output
|
|
2535
|
+
verbose = Option18.Boolean("--verbose,-v", false, {
|
|
2536
|
+
description: "Show detailed workflow information"
|
|
2537
|
+
});
|
|
2538
|
+
// JSON output
|
|
2539
|
+
json = Option18.Boolean("--json,-j", false, {
|
|
2540
|
+
description: "Output in JSON format"
|
|
2541
|
+
});
|
|
2542
|
+
// Project path
|
|
2543
|
+
projectPath = Option18.String("--path,-p", {
|
|
2544
|
+
description: "Project path (default: current directory)"
|
|
2545
|
+
});
|
|
2546
|
+
async execute() {
|
|
2547
|
+
const targetPath = resolve7(this.projectPath || process.cwd());
|
|
2548
|
+
let workflows;
|
|
2549
|
+
try {
|
|
2550
|
+
workflows = listWorkflows(targetPath);
|
|
2551
|
+
} catch (err) {
|
|
2552
|
+
console.log(chalk18.red("Failed to list workflows."));
|
|
2553
|
+
console.log(chalk18.dim(String(err)));
|
|
2554
|
+
return 1;
|
|
2555
|
+
}
|
|
2556
|
+
if (this.json) {
|
|
2557
|
+
console.log(JSON.stringify(workflows, null, 2));
|
|
2558
|
+
return 0;
|
|
2559
|
+
}
|
|
2560
|
+
if (workflows.length === 0) {
|
|
2561
|
+
console.log(chalk18.yellow("No workflows found."));
|
|
2562
|
+
console.log(chalk18.dim("Create a workflow with: skillkit workflow create"));
|
|
2563
|
+
console.log(chalk18.dim("Or add YAML files to .skillkit/workflows/"));
|
|
2564
|
+
return 0;
|
|
2565
|
+
}
|
|
2566
|
+
console.log(chalk18.cyan(`Available Workflows (${workflows.length}):
|
|
2567
|
+
`));
|
|
2568
|
+
for (const workflow of workflows) {
|
|
2569
|
+
console.log(` ${chalk18.bold(workflow.name)}`);
|
|
2570
|
+
if (workflow.description) {
|
|
2571
|
+
console.log(` ${chalk18.dim(workflow.description)}`);
|
|
2572
|
+
}
|
|
2573
|
+
if (this.verbose) {
|
|
2574
|
+
console.log(` Version: ${chalk18.dim(workflow.version || "N/A")}`);
|
|
2575
|
+
console.log(` Waves: ${chalk18.dim(workflow.waves.length.toString())}`);
|
|
2576
|
+
const totalSkills = workflow.waves.reduce(
|
|
2577
|
+
(sum, wave) => sum + wave.skills.length,
|
|
2578
|
+
0
|
|
2579
|
+
);
|
|
2580
|
+
console.log(` Total Skills: ${chalk18.dim(totalSkills.toString())}`);
|
|
2581
|
+
if (workflow.tags && workflow.tags.length > 0) {
|
|
2582
|
+
console.log(` Tags: ${chalk18.dim(workflow.tags.join(", "))}`);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
console.log();
|
|
2586
|
+
}
|
|
2587
|
+
console.log(chalk18.dim("Run a workflow with: skillkit workflow run <name>"));
|
|
2588
|
+
return 0;
|
|
2589
|
+
}
|
|
2590
|
+
};
|
|
2591
|
+
|
|
2592
|
+
// src/commands/workflow/create.ts
|
|
2593
|
+
import { Command as Command20, Option as Option19 } from "clipanion";
|
|
2594
|
+
import { resolve as resolve8 } from "path";
|
|
2595
|
+
import chalk19 from "chalk";
|
|
2596
|
+
import { createWorkflowTemplate, saveWorkflow, serializeWorkflow } from "@skillkit/core";
|
|
2597
|
+
var WorkflowCreateCommand = class extends Command20 {
|
|
2598
|
+
static paths = [["workflow", "create"], ["wf", "create"], ["workflow", "new"], ["wf", "new"]];
|
|
2599
|
+
static usage = Command20.Usage({
|
|
2600
|
+
description: "Create a new workflow",
|
|
2601
|
+
details: `
|
|
2602
|
+
The workflow create command creates a new workflow template
|
|
2603
|
+
that you can customize with your skill composition.
|
|
2604
|
+
`,
|
|
2605
|
+
examples: [
|
|
2606
|
+
["Create a new workflow", "$0 workflow create my-workflow"],
|
|
2607
|
+
["Create with description", '$0 workflow create setup-project --description "Project setup workflow"'],
|
|
2608
|
+
["Print to stdout", "$0 workflow create my-workflow --stdout"]
|
|
2609
|
+
]
|
|
2610
|
+
});
|
|
2611
|
+
// Workflow name
|
|
2612
|
+
workflowName = Option19.String({ required: true });
|
|
2613
|
+
// Description
|
|
2614
|
+
description = Option19.String("--description,-d", {
|
|
2615
|
+
description: "Workflow description"
|
|
2616
|
+
});
|
|
2617
|
+
// Print to stdout instead of saving
|
|
2618
|
+
stdout = Option19.Boolean("--stdout", false, {
|
|
2619
|
+
description: "Print workflow YAML to stdout instead of saving"
|
|
2620
|
+
});
|
|
2621
|
+
// Project path
|
|
2622
|
+
projectPath = Option19.String("--path,-p", {
|
|
2623
|
+
description: "Project path (default: current directory)"
|
|
2624
|
+
});
|
|
2625
|
+
async execute() {
|
|
2626
|
+
const targetPath = resolve8(this.projectPath || process.cwd());
|
|
2627
|
+
const workflow = createWorkflowTemplate(this.workflowName, this.description);
|
|
2628
|
+
if (this.stdout) {
|
|
2629
|
+
console.log(serializeWorkflow(workflow));
|
|
2630
|
+
return 0;
|
|
2631
|
+
}
|
|
2632
|
+
const filePath = saveWorkflow(targetPath, workflow);
|
|
2633
|
+
console.log(chalk19.green(`\u2713 Created workflow: ${chalk19.bold(this.workflowName)}`));
|
|
2634
|
+
console.log(chalk19.dim(` File: ${filePath}`));
|
|
2635
|
+
console.log();
|
|
2636
|
+
console.log(chalk19.dim("Edit the workflow file to add skills and configure waves."));
|
|
2637
|
+
console.log(chalk19.dim("Run with: skillkit workflow run " + this.workflowName));
|
|
2638
|
+
return 0;
|
|
2639
|
+
}
|
|
2640
|
+
};
|
|
2641
|
+
|
|
2642
|
+
// src/commands/run.ts
|
|
2643
|
+
import { Command as Command21, Option as Option20 } from "clipanion";
|
|
2644
|
+
import { resolve as resolve9, join as join6 } from "path";
|
|
2645
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4, statSync } from "fs";
|
|
2646
|
+
import chalk20 from "chalk";
|
|
2647
|
+
import ora5 from "ora";
|
|
2648
|
+
import {
|
|
2649
|
+
createExecutionEngine,
|
|
2650
|
+
discoverSkills,
|
|
2651
|
+
extractFrontmatter
|
|
2652
|
+
} from "@skillkit/core";
|
|
2653
|
+
var RunCommand = class extends Command21 {
|
|
2654
|
+
static paths = [["run"]];
|
|
2655
|
+
static usage = Command21.Usage({
|
|
2656
|
+
description: "Execute a skill with task-based orchestration",
|
|
2657
|
+
details: `
|
|
2658
|
+
The run command executes a skill, optionally breaking it down into
|
|
2659
|
+
tasks with verification checkpoints.
|
|
2660
|
+
|
|
2661
|
+
Skills can be:
|
|
2662
|
+
- Installed skills (by name)
|
|
2663
|
+
- Local skill files (by path)
|
|
2664
|
+
- Remote skills (owner/repo/path)
|
|
2665
|
+
`,
|
|
2666
|
+
examples: [
|
|
2667
|
+
["Run an installed skill", "$0 run typescript-strict-mode"],
|
|
2668
|
+
["Run a local skill file", "$0 run ./my-skill/SKILL.md"],
|
|
2669
|
+
["Dry run (show what would happen)", "$0 run typescript-strict-mode --dry-run"],
|
|
2670
|
+
["Run with verification", "$0 run setup-testing --verify"]
|
|
2671
|
+
]
|
|
2672
|
+
});
|
|
2673
|
+
// Skill name or path
|
|
2674
|
+
skillRef = Option20.String({ required: true });
|
|
2675
|
+
// Target agent
|
|
2676
|
+
agent = Option20.String("--agent,-a", {
|
|
2677
|
+
description: "Target agent (claude-code, cursor, etc.)"
|
|
2678
|
+
});
|
|
2679
|
+
// Dry run
|
|
2680
|
+
dryRun = Option20.Boolean("--dry-run,-n", false, {
|
|
2681
|
+
description: "Show what would be executed without running"
|
|
2682
|
+
});
|
|
2683
|
+
// Enable verification
|
|
2684
|
+
verify = Option20.Boolean("--verify", false, {
|
|
2685
|
+
description: "Run verification checks after each task"
|
|
2686
|
+
});
|
|
2687
|
+
// Auto-commit
|
|
2688
|
+
autoCommit = Option20.Boolean("--auto-commit", false, {
|
|
2689
|
+
description: "Create git commits after each task"
|
|
2690
|
+
});
|
|
2691
|
+
// Continue on error
|
|
2692
|
+
continueOnError = Option20.Boolean("--continue-on-error", false, {
|
|
2693
|
+
description: "Continue execution even if a task fails"
|
|
2694
|
+
});
|
|
2695
|
+
// Verbose output
|
|
2696
|
+
verbose = Option20.Boolean("--verbose,-v", false, {
|
|
2697
|
+
description: "Show detailed execution progress"
|
|
2698
|
+
});
|
|
2699
|
+
// JSON output
|
|
2700
|
+
json = Option20.Boolean("--json,-j", false, {
|
|
2701
|
+
description: "Output in JSON format"
|
|
2702
|
+
});
|
|
2703
|
+
// Project path
|
|
2704
|
+
projectPath = Option20.String("--path,-p", {
|
|
2705
|
+
description: "Project path (default: current directory)"
|
|
2706
|
+
});
|
|
2707
|
+
async execute() {
|
|
2708
|
+
const targetPath = resolve9(this.projectPath || process.cwd());
|
|
2709
|
+
const skill = await this.loadSkill(targetPath);
|
|
2710
|
+
if (!skill) {
|
|
2711
|
+
return 1;
|
|
2712
|
+
}
|
|
2713
|
+
if (!this.json) {
|
|
2714
|
+
console.log(chalk20.cyan(`Executing skill: ${chalk20.bold(skill.name)}`));
|
|
2715
|
+
if (skill.description) {
|
|
2716
|
+
console.log(chalk20.dim(skill.description));
|
|
2717
|
+
}
|
|
2718
|
+
console.log();
|
|
2719
|
+
}
|
|
2720
|
+
if (this.dryRun) {
|
|
2721
|
+
this.showDryRun(skill);
|
|
2722
|
+
return 0;
|
|
2723
|
+
}
|
|
2724
|
+
const spinner = ora5();
|
|
2725
|
+
let currentTask = "";
|
|
2726
|
+
const engine = createExecutionEngine(targetPath, {
|
|
2727
|
+
onProgress: (event) => {
|
|
2728
|
+
if (this.json) return;
|
|
2729
|
+
switch (event.type) {
|
|
2730
|
+
case "task_start":
|
|
2731
|
+
currentTask = event.taskName || "";
|
|
2732
|
+
spinner.start(`Task ${(event.taskIndex || 0) + 1}/${event.totalTasks}: ${currentTask}`);
|
|
2733
|
+
break;
|
|
2734
|
+
case "task_complete":
|
|
2735
|
+
const icon = event.status === "completed" ? chalk20.green("\u2713") : chalk20.red("\u2717");
|
|
2736
|
+
spinner.stopAndPersist({
|
|
2737
|
+
symbol: icon,
|
|
2738
|
+
text: `Task ${(event.taskIndex || 0) + 1}/${event.totalTasks}: ${currentTask}`
|
|
2739
|
+
});
|
|
2740
|
+
if (event.error && this.verbose) {
|
|
2741
|
+
console.log(chalk20.red(` Error: ${event.error}`));
|
|
2742
|
+
}
|
|
2743
|
+
break;
|
|
2744
|
+
case "checkpoint":
|
|
2745
|
+
spinner.info(`Checkpoint: ${event.message}`);
|
|
2746
|
+
break;
|
|
2747
|
+
case "verification":
|
|
2748
|
+
if (this.verbose) {
|
|
2749
|
+
console.log(chalk20.dim(` ${event.message}`));
|
|
2750
|
+
}
|
|
2751
|
+
break;
|
|
2752
|
+
case "complete":
|
|
2753
|
+
console.log();
|
|
2754
|
+
if (event.status === "completed") {
|
|
2755
|
+
console.log(chalk20.green("\u2713 Skill execution completed"));
|
|
2756
|
+
} else {
|
|
2757
|
+
console.log(chalk20.red(`\u2717 Skill execution ${event.status}`));
|
|
2758
|
+
if (event.error) {
|
|
2759
|
+
console.log(chalk20.red(` Error: ${event.error}`));
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
break;
|
|
2763
|
+
}
|
|
2764
|
+
},
|
|
2765
|
+
checkpointHandler: async (task, _context) => {
|
|
2766
|
+
if (task.type === "checkpoint:decision" && task.options) {
|
|
2767
|
+
return {
|
|
2768
|
+
continue: true,
|
|
2769
|
+
selectedOption: task.options[0]
|
|
2770
|
+
};
|
|
2771
|
+
}
|
|
2772
|
+
return { continue: true };
|
|
2773
|
+
}
|
|
2774
|
+
});
|
|
2775
|
+
const result = await engine.execute(skill, {
|
|
2776
|
+
agent: this.agent,
|
|
2777
|
+
autoCommit: this.autoCommit,
|
|
2778
|
+
verify: this.verify,
|
|
2779
|
+
continueOnError: this.continueOnError
|
|
2780
|
+
});
|
|
2781
|
+
if (this.json) {
|
|
2782
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2783
|
+
} else {
|
|
2784
|
+
console.log();
|
|
2785
|
+
console.log(chalk20.cyan("Summary:"));
|
|
2786
|
+
console.log(` Duration: ${chalk20.dim(this.formatDuration(result.durationMs || 0))}`);
|
|
2787
|
+
console.log(` Tasks: ${chalk20.dim(`${result.tasks.filter((t) => t.status === "completed").length}/${result.tasks.length} completed`)}`);
|
|
2788
|
+
if (result.filesModified.length > 0) {
|
|
2789
|
+
console.log(` Files modified: ${chalk20.dim(result.filesModified.length.toString())}`);
|
|
2790
|
+
}
|
|
2791
|
+
if (result.commits.length > 0) {
|
|
2792
|
+
console.log(` Commits: ${chalk20.dim(result.commits.join(", "))}`);
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
return result.status === "completed" ? 0 : 1;
|
|
2796
|
+
}
|
|
2797
|
+
async loadSkill(projectPath) {
|
|
2798
|
+
if (this.skillRef.endsWith(".md") || existsSync9(this.skillRef) && statSync(this.skillRef).isFile()) {
|
|
2799
|
+
return this.loadSkillFromFile(resolve9(this.skillRef));
|
|
2800
|
+
}
|
|
2801
|
+
const skill = this.findInstalledSkill(projectPath, this.skillRef);
|
|
2802
|
+
if (skill) {
|
|
2803
|
+
return skill;
|
|
2804
|
+
}
|
|
2805
|
+
console.error(chalk20.red(`Skill "${this.skillRef}" not found`));
|
|
2806
|
+
console.log(chalk20.dim("Install skills with: skillkit install <source>"));
|
|
2807
|
+
return null;
|
|
2808
|
+
}
|
|
2809
|
+
loadSkillFromFile(filePath) {
|
|
2810
|
+
if (!existsSync9(filePath)) {
|
|
2811
|
+
console.error(chalk20.red(`File not found: ${filePath}`));
|
|
2812
|
+
return null;
|
|
2813
|
+
}
|
|
2814
|
+
try {
|
|
2815
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
2816
|
+
const frontmatter = extractFrontmatter(content);
|
|
2817
|
+
const tasks = this.parseTasksFromFrontmatter(frontmatter);
|
|
2818
|
+
return {
|
|
2819
|
+
name: frontmatter?.name || this.skillRef,
|
|
2820
|
+
description: frontmatter?.description,
|
|
2821
|
+
version: frontmatter?.version,
|
|
2822
|
+
source: filePath,
|
|
2823
|
+
content,
|
|
2824
|
+
tasks
|
|
2825
|
+
};
|
|
2826
|
+
} catch (error) {
|
|
2827
|
+
console.error(chalk20.red(`Failed to load skill: ${error}`));
|
|
2828
|
+
return null;
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
findInstalledSkill(projectPath, skillName) {
|
|
2832
|
+
const skillDirs = [
|
|
2833
|
+
join6(projectPath, ".claude", "skills"),
|
|
2834
|
+
join6(projectPath, ".cursor", "skills"),
|
|
2835
|
+
join6(projectPath, "skills"),
|
|
2836
|
+
join6(projectPath, ".skillkit", "skills")
|
|
2837
|
+
];
|
|
2838
|
+
for (const dir of skillDirs) {
|
|
2839
|
+
if (!existsSync9(dir)) continue;
|
|
2840
|
+
const skills = discoverSkills(dir);
|
|
2841
|
+
const skill = skills.find((s) => s.name === skillName);
|
|
2842
|
+
if (skill) {
|
|
2843
|
+
const skillMdPath = join6(skill.path, "SKILL.md");
|
|
2844
|
+
if (existsSync9(skillMdPath)) {
|
|
2845
|
+
return this.loadSkillFromFile(skillMdPath);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
return null;
|
|
2850
|
+
}
|
|
2851
|
+
parseTasksFromFrontmatter(frontmatter) {
|
|
2852
|
+
if (!frontmatter?.tasks || !Array.isArray(frontmatter.tasks)) {
|
|
2853
|
+
return void 0;
|
|
2854
|
+
}
|
|
2855
|
+
return frontmatter.tasks.map((task, index) => ({
|
|
2856
|
+
id: task.id || `task-${index}`,
|
|
2857
|
+
name: task.name || `Task ${index + 1}`,
|
|
2858
|
+
type: task.type || "auto",
|
|
2859
|
+
action: task.action || "",
|
|
2860
|
+
files: task.files,
|
|
2861
|
+
options: task.options,
|
|
2862
|
+
verify: task.verify
|
|
2863
|
+
}));
|
|
2864
|
+
}
|
|
2865
|
+
showDryRun(skill) {
|
|
2866
|
+
console.log(chalk20.cyan("Dry Run - Execution Plan"));
|
|
2867
|
+
console.log();
|
|
2868
|
+
console.log(`Skill: ${chalk20.bold(skill.name)}`);
|
|
2869
|
+
if (skill.description) {
|
|
2870
|
+
console.log(`Description: ${chalk20.dim(skill.description)}`);
|
|
2871
|
+
}
|
|
2872
|
+
console.log(`Source: ${chalk20.dim(skill.source)}`);
|
|
2873
|
+
console.log();
|
|
2874
|
+
if (skill.tasks && skill.tasks.length > 0) {
|
|
2875
|
+
console.log(chalk20.cyan("Tasks:"));
|
|
2876
|
+
for (let i = 0; i < skill.tasks.length; i++) {
|
|
2877
|
+
const task = skill.tasks[i];
|
|
2878
|
+
const typeLabel = this.getTaskTypeLabel(task.type);
|
|
2879
|
+
console.log(` ${i + 1}. ${task.name} ${typeLabel}`);
|
|
2880
|
+
if (task.action) {
|
|
2881
|
+
console.log(` ${chalk20.dim(task.action)}`);
|
|
2882
|
+
}
|
|
2883
|
+
if (task.files && task.files.length > 0) {
|
|
2884
|
+
console.log(` Files: ${chalk20.dim(task.files.join(", "))}`);
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
} else {
|
|
2888
|
+
console.log(chalk20.dim("No structured tasks defined. Skill will be executed as a single unit."));
|
|
2889
|
+
}
|
|
2890
|
+
console.log();
|
|
2891
|
+
console.log(chalk20.dim("This is a dry run. Remove --dry-run to execute."));
|
|
2892
|
+
}
|
|
2893
|
+
getTaskTypeLabel(type) {
|
|
2894
|
+
switch (type) {
|
|
2895
|
+
case "auto":
|
|
2896
|
+
return chalk20.green("[auto]");
|
|
2897
|
+
case "checkpoint:human-verify":
|
|
2898
|
+
return chalk20.yellow("[verify]");
|
|
2899
|
+
case "checkpoint:decision":
|
|
2900
|
+
return chalk20.blue("[decision]");
|
|
2901
|
+
case "checkpoint:human-action":
|
|
2902
|
+
return chalk20.magenta("[manual]");
|
|
2903
|
+
default:
|
|
2904
|
+
return "";
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
formatDuration(ms) {
|
|
2908
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
2909
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
2910
|
+
const minutes = Math.floor(ms / 6e4);
|
|
2911
|
+
const seconds = Math.floor(ms % 6e4 / 1e3);
|
|
2912
|
+
return `${minutes}m ${seconds}s`;
|
|
2913
|
+
}
|
|
2914
|
+
};
|
|
2915
|
+
|
|
2916
|
+
// src/commands/test.ts
|
|
2917
|
+
import { Command as Command22, Option as Option21 } from "clipanion";
|
|
2918
|
+
import { resolve as resolve10, join as join7 } from "path";
|
|
2919
|
+
import { existsSync as existsSync10, readFileSync as readFileSync5, readdirSync as readdirSync2 } from "fs";
|
|
2920
|
+
import chalk21 from "chalk";
|
|
2921
|
+
import {
|
|
2922
|
+
runTestSuite,
|
|
2923
|
+
createTestSuiteFromFrontmatter
|
|
2924
|
+
} from "@skillkit/core";
|
|
2925
|
+
import { parse as parseYaml } from "yaml";
|
|
2926
|
+
var TestCommand = class extends Command22 {
|
|
2927
|
+
static paths = [["test"]];
|
|
2928
|
+
static usage = Command22.Usage({
|
|
2929
|
+
description: "Run skill tests",
|
|
2930
|
+
details: `
|
|
2931
|
+
The test command runs test cases defined in skill frontmatter.
|
|
2932
|
+
|
|
2933
|
+
Tests are defined in the skill's YAML frontmatter using the 'tests' key.
|
|
2934
|
+
|
|
2935
|
+
Example skill with tests:
|
|
2936
|
+
\`\`\`yaml
|
|
2937
|
+
---
|
|
2938
|
+
name: my-skill
|
|
2939
|
+
tests:
|
|
2940
|
+
- name: "Creates config file"
|
|
2941
|
+
assertions:
|
|
2942
|
+
- type: file_exists
|
|
2943
|
+
target: config.json
|
|
2944
|
+
- name: "Types check"
|
|
2945
|
+
assertions:
|
|
2946
|
+
- type: type_check
|
|
2947
|
+
---
|
|
2948
|
+
\`\`\`
|
|
2949
|
+
`,
|
|
2950
|
+
examples: [
|
|
2951
|
+
["Run all skill tests", "$0 test"],
|
|
2952
|
+
["Run tests for a specific skill", "$0 test my-skill"],
|
|
2953
|
+
["Run with verbose output", "$0 test --verbose"],
|
|
2954
|
+
["Stop on first failure", "$0 test --bail"],
|
|
2955
|
+
["Run tests with specific tags", "$0 test --tags unit,integration"]
|
|
2956
|
+
]
|
|
2957
|
+
});
|
|
2958
|
+
skill = Option21.String({ required: false });
|
|
2959
|
+
verbose = Option21.Boolean("--verbose,-v", false, {
|
|
2960
|
+
description: "Verbose output"
|
|
2961
|
+
});
|
|
2962
|
+
bail = Option21.Boolean("--bail,-b", false, {
|
|
2963
|
+
description: "Stop on first failure"
|
|
2964
|
+
});
|
|
2965
|
+
tags = Option21.String("--tags,-t", {
|
|
2966
|
+
description: "Only run tests with these tags (comma-separated)"
|
|
2967
|
+
});
|
|
2968
|
+
skipTags = Option21.String("--skip-tags", {
|
|
2969
|
+
description: "Skip tests with these tags (comma-separated)"
|
|
2970
|
+
});
|
|
2971
|
+
json = Option21.Boolean("--json,-j", false, {
|
|
2972
|
+
description: "Output results as JSON"
|
|
2973
|
+
});
|
|
2974
|
+
projectPath = Option21.String("--path,-p", {
|
|
2975
|
+
description: "Project path"
|
|
2976
|
+
});
|
|
2977
|
+
timeout = Option21.String("--timeout", {
|
|
2978
|
+
description: "Test timeout in milliseconds"
|
|
2979
|
+
});
|
|
2980
|
+
async execute() {
|
|
2981
|
+
const targetPath = resolve10(this.projectPath || process.cwd());
|
|
2982
|
+
const skillFiles = this.findSkillFiles(targetPath);
|
|
2983
|
+
if (skillFiles.length === 0) {
|
|
2984
|
+
if (!this.json) {
|
|
2985
|
+
console.log(chalk21.yellow("No skills found with tests."));
|
|
2986
|
+
console.log(chalk21.dim("Add tests to your skills using YAML frontmatter."));
|
|
2987
|
+
} else {
|
|
2988
|
+
console.log(JSON.stringify({ results: [], passed: true, total: 0 }));
|
|
2989
|
+
}
|
|
2990
|
+
return 0;
|
|
2991
|
+
}
|
|
2992
|
+
const filesToTest = this.skill ? skillFiles.filter((f) => f.name.includes(this.skill)) : skillFiles;
|
|
2993
|
+
if (filesToTest.length === 0) {
|
|
2994
|
+
if (!this.json) {
|
|
2995
|
+
console.log(chalk21.yellow(`No skills found matching "${this.skill}"`));
|
|
2996
|
+
}
|
|
2997
|
+
return 1;
|
|
2998
|
+
}
|
|
2999
|
+
if (!this.json) {
|
|
3000
|
+
console.log(chalk21.bold("Running skill tests...\n"));
|
|
3001
|
+
}
|
|
3002
|
+
const tags = this.tags?.split(",").map((t) => t.trim());
|
|
3003
|
+
const skipTags = this.skipTags?.split(",").map((t) => t.trim());
|
|
3004
|
+
const timeout = this.timeout ? parseInt(this.timeout, 10) : void 0;
|
|
3005
|
+
const results = [];
|
|
3006
|
+
let allPassed = true;
|
|
3007
|
+
for (const file of filesToTest) {
|
|
3008
|
+
const suite = this.parseSkillTests(file.path, file.name);
|
|
3009
|
+
if (!suite || suite.tests.length === 0) {
|
|
3010
|
+
continue;
|
|
3011
|
+
}
|
|
3012
|
+
if (!this.json) {
|
|
3013
|
+
console.log(chalk21.blue(`Testing: ${suite.skillName}`));
|
|
3014
|
+
}
|
|
3015
|
+
const result = await runTestSuite(suite, {
|
|
3016
|
+
cwd: targetPath,
|
|
3017
|
+
verbose: this.verbose,
|
|
3018
|
+
bail: this.bail,
|
|
3019
|
+
tags,
|
|
3020
|
+
skipTags,
|
|
3021
|
+
timeout,
|
|
3022
|
+
onProgress: (event) => {
|
|
3023
|
+
if (this.json || !this.verbose) return;
|
|
3024
|
+
switch (event.type) {
|
|
3025
|
+
case "test_start":
|
|
3026
|
+
console.log(chalk21.dim(` Running: ${event.testName}`));
|
|
3027
|
+
break;
|
|
3028
|
+
case "test_end":
|
|
3029
|
+
if (event.passed) {
|
|
3030
|
+
console.log(chalk21.green(` \u2713 ${event.testName}`));
|
|
3031
|
+
} else {
|
|
3032
|
+
console.log(chalk21.red(` \u2717 ${event.testName}`));
|
|
3033
|
+
if (event.error) {
|
|
3034
|
+
console.log(chalk21.red(` ${event.error}`));
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
break;
|
|
3038
|
+
case "assertion_end":
|
|
3039
|
+
if (this.verbose && !event.passed) {
|
|
3040
|
+
console.log(chalk21.red(` - ${event.assertionType}: ${event.error}`));
|
|
3041
|
+
}
|
|
3042
|
+
break;
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
});
|
|
3046
|
+
results.push(result);
|
|
3047
|
+
if (!result.passed) {
|
|
3048
|
+
allPassed = false;
|
|
3049
|
+
}
|
|
3050
|
+
if (!this.json) {
|
|
3051
|
+
const icon = result.passed ? chalk21.green("\u2713") : chalk21.red("\u2717");
|
|
3052
|
+
const status = result.passed ? "PASSED" : "FAILED";
|
|
3053
|
+
console.log(
|
|
3054
|
+
`${icon} ${suite.skillName}: ${result.passedCount}/${result.tests.length} tests ${status} (${result.duration}ms)
|
|
3055
|
+
`
|
|
3056
|
+
);
|
|
3057
|
+
}
|
|
3058
|
+
if (this.bail && !result.passed) {
|
|
3059
|
+
break;
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
if (this.json) {
|
|
3063
|
+
console.log(
|
|
3064
|
+
JSON.stringify({
|
|
3065
|
+
results: results.map((r) => ({
|
|
3066
|
+
skill: r.skillName,
|
|
3067
|
+
passed: r.passed,
|
|
3068
|
+
tests: r.passedCount,
|
|
3069
|
+
failed: r.failedCount,
|
|
3070
|
+
skipped: r.skippedCount,
|
|
3071
|
+
duration: r.duration
|
|
3072
|
+
})),
|
|
3073
|
+
passed: allPassed,
|
|
3074
|
+
total: results.length
|
|
3075
|
+
})
|
|
3076
|
+
);
|
|
3077
|
+
} else {
|
|
3078
|
+
const totalTests = results.reduce((acc, r) => acc + r.tests.length, 0);
|
|
3079
|
+
const passedTests = results.reduce((acc, r) => acc + r.passedCount, 0);
|
|
3080
|
+
const failedTests = results.reduce((acc, r) => acc + r.failedCount, 0);
|
|
3081
|
+
const skippedTests = results.reduce((acc, r) => acc + r.skippedCount, 0);
|
|
3082
|
+
console.log(chalk21.bold("Summary:"));
|
|
3083
|
+
console.log(` Skills tested: ${results.length}`);
|
|
3084
|
+
console.log(` Total tests: ${totalTests}`);
|
|
3085
|
+
console.log(chalk21.green(` Passed: ${passedTests}`));
|
|
3086
|
+
if (failedTests > 0) {
|
|
3087
|
+
console.log(chalk21.red(` Failed: ${failedTests}`));
|
|
3088
|
+
}
|
|
3089
|
+
if (skippedTests > 0) {
|
|
3090
|
+
console.log(chalk21.yellow(` Skipped: ${skippedTests}`));
|
|
3091
|
+
}
|
|
3092
|
+
if (allPassed) {
|
|
3093
|
+
console.log(chalk21.green("\n\u2713 All tests passed!"));
|
|
3094
|
+
} else {
|
|
3095
|
+
console.log(chalk21.red("\n\u2717 Some tests failed."));
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
return allPassed ? 0 : 1;
|
|
3099
|
+
}
|
|
3100
|
+
/**
|
|
3101
|
+
* Find skill files with tests
|
|
3102
|
+
*/
|
|
3103
|
+
findSkillFiles(projectPath) {
|
|
3104
|
+
const files = [];
|
|
3105
|
+
const skillDirs = [
|
|
3106
|
+
".claude/skills",
|
|
3107
|
+
".cursor/skills",
|
|
3108
|
+
".skillkit/skills",
|
|
3109
|
+
"skills"
|
|
3110
|
+
];
|
|
3111
|
+
for (const dir of skillDirs) {
|
|
3112
|
+
const fullDir = join7(projectPath, dir);
|
|
3113
|
+
if (!existsSync10(fullDir)) continue;
|
|
3114
|
+
try {
|
|
3115
|
+
const entries = readdirSync2(fullDir, { withFileTypes: true });
|
|
3116
|
+
for (const entry of entries) {
|
|
3117
|
+
if (entry.isFile() && (entry.name.endsWith(".md") || entry.name.endsWith(".mdc"))) {
|
|
3118
|
+
files.push({
|
|
3119
|
+
name: entry.name.replace(/\.(md|mdc)$/, ""),
|
|
3120
|
+
path: join7(fullDir, entry.name)
|
|
3121
|
+
});
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
} catch {
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
return files;
|
|
3128
|
+
}
|
|
3129
|
+
/**
|
|
3130
|
+
* Parse skill file for tests
|
|
3131
|
+
*/
|
|
3132
|
+
parseSkillTests(filePath, skillName) {
|
|
3133
|
+
try {
|
|
3134
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
3135
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
3136
|
+
if (!frontmatterMatch) {
|
|
3137
|
+
return null;
|
|
3138
|
+
}
|
|
3139
|
+
const frontmatter = parseYaml(frontmatterMatch[1]);
|
|
3140
|
+
if (!frontmatter.tests) {
|
|
3141
|
+
return null;
|
|
3142
|
+
}
|
|
3143
|
+
return createTestSuiteFromFrontmatter(skillName, frontmatter);
|
|
3144
|
+
} catch {
|
|
3145
|
+
return null;
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
};
|
|
3149
|
+
|
|
3150
|
+
// src/commands/marketplace.ts
|
|
3151
|
+
import { Command as Command23, Option as Option22 } from "clipanion";
|
|
3152
|
+
import chalk22 from "chalk";
|
|
3153
|
+
import ora6 from "ora";
|
|
3154
|
+
import {
|
|
3155
|
+
createMarketplaceAggregator
|
|
3156
|
+
} from "@skillkit/core";
|
|
3157
|
+
var MarketplaceCommand = class extends Command23 {
|
|
3158
|
+
static paths = [["marketplace"], ["market"], ["mp"]];
|
|
3159
|
+
static usage = Command23.Usage({
|
|
3160
|
+
description: "Browse and install skills from the marketplace",
|
|
3161
|
+
details: `
|
|
3162
|
+
The marketplace command lets you discover and install skills
|
|
3163
|
+
from curated repositories.
|
|
3164
|
+
|
|
3165
|
+
Skills are aggregated from multiple sources including:
|
|
3166
|
+
- composioHQ/awesome-claude-code-skills
|
|
3167
|
+
- anthropics/courses
|
|
3168
|
+
- User-added custom sources
|
|
3169
|
+
`,
|
|
3170
|
+
examples: [
|
|
3171
|
+
["Browse marketplace", "$0 marketplace"],
|
|
3172
|
+
["Search for skills", "$0 marketplace search typescript"],
|
|
3173
|
+
["Refresh marketplace index", "$0 marketplace refresh"],
|
|
3174
|
+
["Show popular tags", "$0 marketplace tags"],
|
|
3175
|
+
["List sources", "$0 marketplace sources"]
|
|
3176
|
+
]
|
|
3177
|
+
});
|
|
3178
|
+
action = Option22.String({ required: false });
|
|
3179
|
+
query = Option22.String({ required: false });
|
|
3180
|
+
limit = Option22.String("--limit,-l", {
|
|
3181
|
+
description: "Limit results"
|
|
3182
|
+
});
|
|
3183
|
+
tags = Option22.String("--tags,-t", {
|
|
3184
|
+
description: "Filter by tags (comma-separated)"
|
|
3185
|
+
});
|
|
3186
|
+
source = Option22.String("--source,-s", {
|
|
3187
|
+
description: "Filter by source"
|
|
3188
|
+
});
|
|
3189
|
+
json = Option22.Boolean("--json,-j", false, {
|
|
3190
|
+
description: "Output as JSON"
|
|
3191
|
+
});
|
|
3192
|
+
async execute() {
|
|
3193
|
+
const marketplace = createMarketplaceAggregator();
|
|
3194
|
+
switch (this.action) {
|
|
3195
|
+
case "search":
|
|
3196
|
+
return this.searchSkills(marketplace);
|
|
3197
|
+
case "refresh":
|
|
3198
|
+
return this.refreshIndex(marketplace);
|
|
3199
|
+
case "tags":
|
|
3200
|
+
return this.showTags(marketplace);
|
|
3201
|
+
case "sources":
|
|
3202
|
+
return this.showSources(marketplace);
|
|
3203
|
+
default:
|
|
3204
|
+
if (this.query || this.action) {
|
|
3205
|
+
return this.searchSkills(marketplace);
|
|
3206
|
+
}
|
|
3207
|
+
return this.browseMarketplace(marketplace);
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
async browseMarketplace(marketplace) {
|
|
3211
|
+
const spinner = ora6("Loading marketplace...").start();
|
|
3212
|
+
try {
|
|
3213
|
+
const index = await marketplace.getIndex();
|
|
3214
|
+
spinner.stop();
|
|
3215
|
+
if (this.json) {
|
|
3216
|
+
console.log(JSON.stringify({
|
|
3217
|
+
totalSkills: index.totalCount,
|
|
3218
|
+
sources: index.sources.length,
|
|
3219
|
+
updatedAt: index.updatedAt
|
|
3220
|
+
}));
|
|
3221
|
+
return 0;
|
|
3222
|
+
}
|
|
3223
|
+
console.log(chalk22.bold("Skill Marketplace\n"));
|
|
3224
|
+
console.log(`Total skills: ${chalk22.cyan(index.totalCount)}`);
|
|
3225
|
+
console.log(`Sources: ${chalk22.cyan(index.sources.length)}`);
|
|
3226
|
+
console.log(`Last updated: ${chalk22.dim(new Date(index.updatedAt).toLocaleString())}
|
|
3227
|
+
`);
|
|
3228
|
+
console.log(chalk22.bold("Featured Skills:\n"));
|
|
3229
|
+
const featured = index.skills.slice(0, 10);
|
|
3230
|
+
for (const skill of featured) {
|
|
3231
|
+
console.log(` ${chalk22.cyan(skill.name)}`);
|
|
3232
|
+
if (skill.description) {
|
|
3233
|
+
console.log(` ${chalk22.dim(skill.description.slice(0, 60))}${skill.description.length > 60 ? "..." : ""}`);
|
|
3234
|
+
}
|
|
3235
|
+
console.log(` ${chalk22.dim(`Source: ${skill.source.name}`)}`);
|
|
3236
|
+
console.log();
|
|
3237
|
+
}
|
|
3238
|
+
console.log(chalk22.dim('Use "skillkit marketplace search <query>" to search for skills.'));
|
|
3239
|
+
console.log(chalk22.dim('Use "skillkit marketplace tags" to see popular tags.'));
|
|
3240
|
+
return 0;
|
|
3241
|
+
} catch (error) {
|
|
3242
|
+
spinner.fail("Failed to load marketplace");
|
|
3243
|
+
console.error(chalk22.red(error instanceof Error ? error.message : String(error)));
|
|
3244
|
+
return 1;
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
async searchSkills(marketplace) {
|
|
3248
|
+
const query = this.query || this.action;
|
|
3249
|
+
let limit = 20;
|
|
3250
|
+
if (this.limit) {
|
|
3251
|
+
const parsed = parseInt(this.limit, 10);
|
|
3252
|
+
if (isNaN(parsed) || parsed <= 0) {
|
|
3253
|
+
console.error(chalk22.red("Invalid --limit value. Must be a positive number."));
|
|
3254
|
+
return 1;
|
|
3255
|
+
}
|
|
3256
|
+
limit = parsed;
|
|
3257
|
+
}
|
|
3258
|
+
const tags = this.tags?.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
3259
|
+
const spinner = ora6(`Searching for "${query}"...`).start();
|
|
3260
|
+
try {
|
|
3261
|
+
const options = {
|
|
3262
|
+
query: query || void 0,
|
|
3263
|
+
limit,
|
|
3264
|
+
tags: tags && tags.length > 0 ? tags : void 0,
|
|
3265
|
+
source: this.source
|
|
3266
|
+
};
|
|
3267
|
+
const result = await marketplace.search(options);
|
|
3268
|
+
spinner.stop();
|
|
3269
|
+
if (this.json) {
|
|
3270
|
+
console.log(JSON.stringify(result));
|
|
3271
|
+
return 0;
|
|
3272
|
+
}
|
|
3273
|
+
if (result.skills.length === 0) {
|
|
3274
|
+
console.log(chalk22.yellow(`No skills found${query ? ` matching "${query}"` : ""}.`));
|
|
3275
|
+
return 0;
|
|
3276
|
+
}
|
|
3277
|
+
console.log(chalk22.bold(`Found ${result.total} skill(s):
|
|
3278
|
+
`));
|
|
3279
|
+
for (const skill of result.skills) {
|
|
3280
|
+
console.log(`${chalk22.cyan(skill.name)} ${chalk22.dim(`(${skill.source.name})`)}`);
|
|
3281
|
+
if (skill.description) {
|
|
3282
|
+
console.log(` ${skill.description}`);
|
|
3283
|
+
}
|
|
3284
|
+
if (skill.tags.length > 0) {
|
|
3285
|
+
console.log(` Tags: ${chalk22.dim(skill.tags.join(", "))}`);
|
|
3286
|
+
}
|
|
3287
|
+
console.log();
|
|
3288
|
+
}
|
|
3289
|
+
if (result.total > result.skills.length) {
|
|
3290
|
+
console.log(chalk22.dim(`Showing ${result.skills.length} of ${result.total} results. Use --limit to see more.`));
|
|
3291
|
+
}
|
|
3292
|
+
return 0;
|
|
3293
|
+
} catch (error) {
|
|
3294
|
+
spinner.fail("Search failed");
|
|
3295
|
+
console.error(chalk22.red(error instanceof Error ? error.message : String(error)));
|
|
3296
|
+
return 1;
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
async refreshIndex(marketplace) {
|
|
3300
|
+
const spinner = ora6("Refreshing marketplace index...").start();
|
|
3301
|
+
try {
|
|
3302
|
+
const index = await marketplace.refresh();
|
|
3303
|
+
spinner.succeed(`Marketplace refreshed: ${index.totalCount} skills from ${index.sources.length} sources`);
|
|
3304
|
+
return 0;
|
|
3305
|
+
} catch (error) {
|
|
3306
|
+
spinner.fail("Refresh failed");
|
|
3307
|
+
console.error(chalk22.red(error instanceof Error ? error.message : String(error)));
|
|
3308
|
+
return 1;
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
async showTags(marketplace) {
|
|
3312
|
+
const spinner = ora6("Loading tags...").start();
|
|
3313
|
+
try {
|
|
3314
|
+
const tags = await marketplace.getPopularTags(30);
|
|
3315
|
+
spinner.stop();
|
|
3316
|
+
if (this.json) {
|
|
3317
|
+
console.log(JSON.stringify(tags));
|
|
3318
|
+
return 0;
|
|
3319
|
+
}
|
|
3320
|
+
console.log(chalk22.bold("Popular Tags:\n"));
|
|
3321
|
+
for (const { tag, count } of tags) {
|
|
3322
|
+
const bar = "\u2588".repeat(Math.min(count, 20));
|
|
3323
|
+
console.log(` ${tag.padEnd(15)} ${chalk22.cyan(bar)} ${count}`);
|
|
3324
|
+
}
|
|
3325
|
+
return 0;
|
|
3326
|
+
} catch (error) {
|
|
3327
|
+
spinner.fail("Failed to load tags");
|
|
3328
|
+
console.error(chalk22.red(error instanceof Error ? error.message : String(error)));
|
|
3329
|
+
return 1;
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
async showSources(marketplace) {
|
|
3333
|
+
const sources = marketplace.getSources();
|
|
3334
|
+
if (this.json) {
|
|
3335
|
+
console.log(JSON.stringify(sources));
|
|
3336
|
+
return 0;
|
|
3337
|
+
}
|
|
3338
|
+
console.log(chalk22.bold("Skill Sources:\n"));
|
|
3339
|
+
for (const source of sources) {
|
|
3340
|
+
console.log(`${chalk22.cyan(source.name)} ${source.official ? chalk22.green("(official)") : ""}`);
|
|
3341
|
+
console.log(` ${chalk22.dim(`${source.owner}/${source.repo}`)}`);
|
|
3342
|
+
if (source.description) {
|
|
3343
|
+
console.log(` ${source.description}`);
|
|
3344
|
+
}
|
|
3345
|
+
console.log();
|
|
3346
|
+
}
|
|
3347
|
+
return 0;
|
|
3348
|
+
}
|
|
3349
|
+
};
|
|
3350
|
+
|
|
3351
|
+
// src/commands/memory.ts
|
|
3352
|
+
import { Command as Command24, Option as Option23 } from "clipanion";
|
|
3353
|
+
import chalk23 from "chalk";
|
|
3354
|
+
import {
|
|
3355
|
+
ObservationStore,
|
|
3356
|
+
LearningStore,
|
|
3357
|
+
getMemoryPaths,
|
|
3358
|
+
initializeMemoryDirectory,
|
|
3359
|
+
getMemoryStatus,
|
|
3360
|
+
createMemoryCompressor,
|
|
3361
|
+
createMemoryInjector
|
|
3362
|
+
} from "@skillkit/core";
|
|
3363
|
+
var MemoryCommand = class extends Command24 {
|
|
3364
|
+
static paths = [["memory"], ["mem"]];
|
|
3365
|
+
static usage = Command24.Usage({
|
|
3366
|
+
description: "Manage session memory across AI coding agents",
|
|
3367
|
+
details: `
|
|
3368
|
+
The memory command helps you view, search, and manage learnings
|
|
3369
|
+
captured from coding sessions across all AI agents.
|
|
3370
|
+
|
|
3371
|
+
Subcommands:
|
|
3372
|
+
- status: Show current memory status
|
|
3373
|
+
- search: Search memories by query
|
|
3374
|
+
- list: List all learnings
|
|
3375
|
+
- show: Show a specific learning
|
|
3376
|
+
- compress: Compress observations into learnings
|
|
3377
|
+
- export: Export a learning as a skill
|
|
3378
|
+
- import: Import memories from another project
|
|
3379
|
+
- clear: Clear session observations
|
|
3380
|
+
- add: Manually add a learning
|
|
3381
|
+
- rate: Rate a learning's effectiveness
|
|
3382
|
+
- config: Configure memory settings
|
|
3383
|
+
`,
|
|
3384
|
+
examples: [
|
|
3385
|
+
["Show memory status", "$0 memory status"],
|
|
3386
|
+
["Search memories", '$0 memory search "authentication"'],
|
|
3387
|
+
["Search global memories", '$0 memory search --global "react hooks"'],
|
|
3388
|
+
["List project learnings", "$0 memory list"],
|
|
3389
|
+
["List global learnings", "$0 memory list --global"],
|
|
3390
|
+
["Show a learning", "$0 memory show <id>"],
|
|
3391
|
+
["Compress observations", "$0 memory compress"],
|
|
3392
|
+
["Export as skill", "$0 memory export <id> --name my-skill"],
|
|
3393
|
+
["Clear session", "$0 memory clear"],
|
|
3394
|
+
["Add manual learning", '$0 memory add --title "..." --content "..."'],
|
|
3395
|
+
["Rate effectiveness", "$0 memory rate <id> 85"]
|
|
3396
|
+
]
|
|
3397
|
+
});
|
|
3398
|
+
// Subcommand (status, search, list, show, compress, export, import, clear, add, rate, config)
|
|
3399
|
+
action = Option23.String({ required: false });
|
|
3400
|
+
// Second argument (query for search, id for show/export/rate)
|
|
3401
|
+
arg = Option23.String({ required: false });
|
|
3402
|
+
// Third argument (rating value for rate command)
|
|
3403
|
+
ratingArg = Option23.String({ required: false });
|
|
3404
|
+
// Global scope
|
|
3405
|
+
global = Option23.Boolean("--global,-g", false, {
|
|
3406
|
+
description: "Use global memories instead of project"
|
|
3407
|
+
});
|
|
3408
|
+
// Tags filter
|
|
3409
|
+
tags = Option23.String("--tags,-t", {
|
|
3410
|
+
description: "Filter by tags (comma-separated)"
|
|
3411
|
+
});
|
|
3412
|
+
// Limit results
|
|
3413
|
+
limit = Option23.String("--limit,-l", {
|
|
3414
|
+
description: "Maximum number of results"
|
|
3415
|
+
});
|
|
3416
|
+
// Title for add
|
|
3417
|
+
title = Option23.String("--title", {
|
|
3418
|
+
description: "Title for new learning"
|
|
3419
|
+
});
|
|
3420
|
+
// Content for add
|
|
3421
|
+
content = Option23.String("--content,-c", {
|
|
3422
|
+
description: "Content for new learning"
|
|
3423
|
+
});
|
|
3424
|
+
// Name for export
|
|
3425
|
+
name = Option23.String("--name,-n", {
|
|
3426
|
+
description: "Name for exported skill"
|
|
3427
|
+
});
|
|
3428
|
+
// Output file
|
|
3429
|
+
output = Option23.String("--output,-o", {
|
|
3430
|
+
description: "Output file path"
|
|
3431
|
+
});
|
|
3432
|
+
// Input file
|
|
3433
|
+
input = Option23.String("--input,-i", {
|
|
3434
|
+
description: "Input file path"
|
|
3435
|
+
});
|
|
3436
|
+
// Keep learnings when clearing
|
|
3437
|
+
keepLearnings = Option23.Boolean("--keep-learnings", false, {
|
|
3438
|
+
description: "Keep learnings when clearing"
|
|
3439
|
+
});
|
|
3440
|
+
// Dry run
|
|
3441
|
+
dryRun = Option23.Boolean("--dry-run", false, {
|
|
3442
|
+
description: "Preview without making changes"
|
|
3443
|
+
});
|
|
3444
|
+
// JSON output
|
|
3445
|
+
json = Option23.Boolean("--json,-j", false, {
|
|
3446
|
+
description: "Output in JSON format"
|
|
3447
|
+
});
|
|
3448
|
+
// Verbose output
|
|
3449
|
+
verbose = Option23.Boolean("--verbose,-v", false, {
|
|
3450
|
+
description: "Show detailed output"
|
|
3451
|
+
});
|
|
3452
|
+
async execute() {
|
|
3453
|
+
const action = this.action || "status";
|
|
3454
|
+
switch (action) {
|
|
3455
|
+
case "status":
|
|
3456
|
+
return this.showStatus();
|
|
3457
|
+
case "search":
|
|
3458
|
+
return this.searchMemories();
|
|
3459
|
+
case "list":
|
|
3460
|
+
return this.listLearnings();
|
|
3461
|
+
case "show":
|
|
3462
|
+
return this.showLearning();
|
|
3463
|
+
case "compress":
|
|
3464
|
+
return this.compressObservations();
|
|
3465
|
+
case "export":
|
|
3466
|
+
return this.exportLearning();
|
|
3467
|
+
case "import":
|
|
3468
|
+
return this.importMemories();
|
|
3469
|
+
case "clear":
|
|
3470
|
+
return this.clearMemory();
|
|
3471
|
+
case "add":
|
|
3472
|
+
return this.addLearning();
|
|
3473
|
+
case "rate":
|
|
3474
|
+
return this.rateLearning();
|
|
3475
|
+
case "config":
|
|
3476
|
+
return this.showConfig();
|
|
3477
|
+
default:
|
|
3478
|
+
console.error(chalk23.red(`Unknown action: ${action}`));
|
|
3479
|
+
console.log(chalk23.gray("Available actions: status, search, list, show, compress, export, import, clear, add, rate, config"));
|
|
3480
|
+
return 1;
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
/**
|
|
3484
|
+
* Show memory status
|
|
3485
|
+
*/
|
|
3486
|
+
async showStatus() {
|
|
3487
|
+
const projectPath = process.cwd();
|
|
3488
|
+
const status = getMemoryStatus(projectPath);
|
|
3489
|
+
const paths = getMemoryPaths(projectPath);
|
|
3490
|
+
const observationStore = new ObservationStore(projectPath);
|
|
3491
|
+
const projectLearningStore = new LearningStore("project", projectPath);
|
|
3492
|
+
const globalLearningStore = new LearningStore("global");
|
|
3493
|
+
const sessionObservations = status.hasObservations ? observationStore.count() : 0;
|
|
3494
|
+
const projectLearnings = status.hasLearnings ? projectLearningStore.count() : 0;
|
|
3495
|
+
const globalLearnings = status.hasGlobalLearnings ? globalLearningStore.count() : 0;
|
|
3496
|
+
const sessionId = status.hasObservations ? observationStore.getSessionId() : null;
|
|
3497
|
+
if (this.json) {
|
|
3498
|
+
console.log(JSON.stringify({
|
|
3499
|
+
...status,
|
|
3500
|
+
sessionObservations,
|
|
3501
|
+
projectLearnings,
|
|
3502
|
+
globalLearnings,
|
|
3503
|
+
sessionId,
|
|
3504
|
+
paths
|
|
3505
|
+
}, null, 2));
|
|
3506
|
+
return 0;
|
|
3507
|
+
}
|
|
3508
|
+
console.log(chalk23.bold("\nMemory Status\n"));
|
|
3509
|
+
console.log(chalk23.cyan("Session:"));
|
|
3510
|
+
console.log(` Observations: ${sessionObservations}`);
|
|
3511
|
+
if (sessionId) {
|
|
3512
|
+
console.log(` Session ID: ${chalk23.gray(sessionId.slice(0, 8))}`);
|
|
3513
|
+
}
|
|
3514
|
+
console.log();
|
|
3515
|
+
console.log(chalk23.cyan("Project:"));
|
|
3516
|
+
console.log(` Learnings: ${projectLearnings}`);
|
|
3517
|
+
console.log(` Path: ${chalk23.gray(status.projectMemoryExists ? paths.projectMemoryDir : "Not initialized")}`);
|
|
3518
|
+
console.log();
|
|
3519
|
+
console.log(chalk23.cyan("Global:"));
|
|
3520
|
+
console.log(` Learnings: ${globalLearnings}`);
|
|
3521
|
+
console.log(` Path: ${chalk23.gray(status.globalMemoryExists ? paths.globalMemoryDir : "Not initialized")}`);
|
|
3522
|
+
console.log();
|
|
3523
|
+
if (sessionObservations >= 50) {
|
|
3524
|
+
console.log(chalk23.yellow("\u{1F4A1} You have many uncompressed observations. Consider running:"));
|
|
3525
|
+
console.log(chalk23.gray(" skillkit memory compress"));
|
|
3526
|
+
}
|
|
3527
|
+
return 0;
|
|
3528
|
+
}
|
|
3529
|
+
/**
|
|
3530
|
+
* Search memories
|
|
3531
|
+
*/
|
|
3532
|
+
async searchMemories() {
|
|
3533
|
+
const query = this.arg;
|
|
3534
|
+
if (!query) {
|
|
3535
|
+
console.error(chalk23.red("Error: Search query is required"));
|
|
3536
|
+
console.log(chalk23.gray('Usage: skillkit memory search "your query"'));
|
|
3537
|
+
return 1;
|
|
3538
|
+
}
|
|
3539
|
+
const projectPath = process.cwd();
|
|
3540
|
+
const injector = createMemoryInjector(projectPath);
|
|
3541
|
+
let maxLearnings = 10;
|
|
3542
|
+
if (this.limit) {
|
|
3543
|
+
const parsed = parseInt(this.limit, 10);
|
|
3544
|
+
if (isNaN(parsed) || parsed <= 0) {
|
|
3545
|
+
console.log(chalk23.red("Invalid --limit value. Must be a positive number."));
|
|
3546
|
+
return 1;
|
|
3547
|
+
}
|
|
3548
|
+
maxLearnings = parsed;
|
|
3549
|
+
}
|
|
3550
|
+
const tags = this.tags?.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
3551
|
+
const results = injector.search(query, {
|
|
3552
|
+
includeGlobal: this.global,
|
|
3553
|
+
tags: tags && tags.length > 0 ? tags : void 0,
|
|
3554
|
+
maxLearnings,
|
|
3555
|
+
minRelevance: 0
|
|
3556
|
+
});
|
|
3557
|
+
if (this.json) {
|
|
3558
|
+
console.log(JSON.stringify(results, null, 2));
|
|
3559
|
+
return 0;
|
|
3560
|
+
}
|
|
3561
|
+
if (results.length === 0) {
|
|
3562
|
+
console.log(chalk23.yellow(`No memories found for: "${query}"`));
|
|
3563
|
+
return 0;
|
|
3564
|
+
}
|
|
3565
|
+
console.log(chalk23.bold(`
|
|
3566
|
+
Found ${results.length} memories:
|
|
3567
|
+
`));
|
|
3568
|
+
for (const { learning, relevanceScore, matchedBy } of results) {
|
|
3569
|
+
console.log(`${chalk23.cyan("\u25CF")} ${chalk23.bold(learning.title)}`);
|
|
3570
|
+
console.log(` ID: ${chalk23.gray(learning.id.slice(0, 8))}`);
|
|
3571
|
+
console.log(` Relevance: ${this.formatScore(relevanceScore)}%`);
|
|
3572
|
+
console.log(` Tags: ${learning.tags.join(", ")}`);
|
|
3573
|
+
console.log(` Scope: ${learning.scope}`);
|
|
3574
|
+
if (this.verbose) {
|
|
3575
|
+
console.log(` Matched: ${this.formatMatchedBy(matchedBy)}`);
|
|
3576
|
+
console.log(` Created: ${new Date(learning.createdAt).toLocaleDateString()}`);
|
|
3577
|
+
console.log(` Uses: ${learning.useCount}`);
|
|
3578
|
+
}
|
|
3579
|
+
const excerpt = learning.content.slice(0, 100);
|
|
3580
|
+
console.log(` ${chalk23.gray(excerpt)}${learning.content.length > 100 ? "..." : ""}`);
|
|
3581
|
+
console.log();
|
|
3582
|
+
}
|
|
3583
|
+
return 0;
|
|
3584
|
+
}
|
|
3585
|
+
/**
|
|
3586
|
+
* List all learnings
|
|
3587
|
+
*/
|
|
3588
|
+
async listLearnings() {
|
|
3589
|
+
const projectPath = process.cwd();
|
|
3590
|
+
const store = new LearningStore(
|
|
3591
|
+
this.global ? "global" : "project",
|
|
3592
|
+
this.global ? void 0 : projectPath
|
|
3593
|
+
);
|
|
3594
|
+
let learnings = store.getAll();
|
|
3595
|
+
if (this.tags) {
|
|
3596
|
+
const tagList = this.tags.split(",").map((t) => t.trim().toLowerCase());
|
|
3597
|
+
learnings = learnings.filter(
|
|
3598
|
+
(l) => l.tags.some((t) => tagList.includes(t.toLowerCase()))
|
|
3599
|
+
);
|
|
3600
|
+
}
|
|
3601
|
+
const limit = this.limit ? parseInt(this.limit, 10) : learnings.length;
|
|
3602
|
+
learnings = learnings.slice(0, limit);
|
|
3603
|
+
if (this.json) {
|
|
3604
|
+
console.log(JSON.stringify(learnings, null, 2));
|
|
3605
|
+
return 0;
|
|
3606
|
+
}
|
|
3607
|
+
const scope = this.global ? "Global" : "Project";
|
|
3608
|
+
console.log(chalk23.bold(`
|
|
3609
|
+
${scope} Learnings (${learnings.length}):
|
|
3610
|
+
`));
|
|
3611
|
+
if (learnings.length === 0) {
|
|
3612
|
+
console.log(chalk23.gray("No learnings found."));
|
|
3613
|
+
console.log(chalk23.gray("\nCapture learnings by running skills or add manually:"));
|
|
3614
|
+
console.log(chalk23.gray(' skillkit memory add --title "..." --content "..."'));
|
|
3615
|
+
return 0;
|
|
3616
|
+
}
|
|
3617
|
+
for (const learning of learnings) {
|
|
3618
|
+
const effectiveness = learning.effectiveness !== void 0 ? ` [${this.formatScore(learning.effectiveness)}%]` : "";
|
|
3619
|
+
console.log(`${chalk23.cyan("\u25CF")} ${learning.title}${chalk23.green(effectiveness)}`);
|
|
3620
|
+
console.log(` ${chalk23.gray(learning.id.slice(0, 8))} | ${learning.tags.join(", ")} | ${learning.useCount} uses`);
|
|
3621
|
+
if (this.verbose) {
|
|
3622
|
+
const excerpt = learning.content.slice(0, 80);
|
|
3623
|
+
console.log(` ${chalk23.gray(excerpt)}${learning.content.length > 80 ? "..." : ""}`);
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
console.log();
|
|
3627
|
+
return 0;
|
|
3628
|
+
}
|
|
3629
|
+
/**
|
|
3630
|
+
* Show a specific learning
|
|
3631
|
+
*/
|
|
3632
|
+
async showLearning() {
|
|
3633
|
+
const id = this.arg;
|
|
3634
|
+
if (!id) {
|
|
3635
|
+
console.error(chalk23.red("Error: Learning ID is required"));
|
|
3636
|
+
console.log(chalk23.gray("Usage: skillkit memory show <id>"));
|
|
3637
|
+
return 1;
|
|
3638
|
+
}
|
|
3639
|
+
const projectPath = process.cwd();
|
|
3640
|
+
let learning = new LearningStore("project", projectPath).getById(id);
|
|
3641
|
+
if (!learning) {
|
|
3642
|
+
learning = new LearningStore("global").getById(id);
|
|
3643
|
+
}
|
|
3644
|
+
if (!learning) {
|
|
3645
|
+
const projectLearnings = new LearningStore("project", projectPath).getAll();
|
|
3646
|
+
const globalLearnings = new LearningStore("global").getAll();
|
|
3647
|
+
const all = [...projectLearnings, ...globalLearnings];
|
|
3648
|
+
learning = all.find((l) => l.id.startsWith(id));
|
|
3649
|
+
}
|
|
3650
|
+
if (!learning) {
|
|
3651
|
+
console.error(chalk23.red(`Learning not found: ${id}`));
|
|
3652
|
+
return 1;
|
|
3653
|
+
}
|
|
3654
|
+
if (this.json) {
|
|
3655
|
+
console.log(JSON.stringify(learning, null, 2));
|
|
3656
|
+
return 0;
|
|
3657
|
+
}
|
|
3658
|
+
console.log(chalk23.bold(`
|
|
3659
|
+
${learning.title}
|
|
3660
|
+
`));
|
|
3661
|
+
console.log(chalk23.gray(`ID: ${learning.id}`));
|
|
3662
|
+
console.log(chalk23.gray(`Scope: ${learning.scope}`));
|
|
3663
|
+
console.log(chalk23.gray(`Source: ${learning.source}`));
|
|
3664
|
+
console.log(chalk23.gray(`Tags: ${learning.tags.join(", ")}`));
|
|
3665
|
+
if (learning.frameworks?.length) {
|
|
3666
|
+
console.log(chalk23.gray(`Frameworks: ${learning.frameworks.join(", ")}`));
|
|
3667
|
+
}
|
|
3668
|
+
if (learning.patterns?.length) {
|
|
3669
|
+
console.log(chalk23.gray(`Patterns: ${learning.patterns.join(", ")}`));
|
|
3670
|
+
}
|
|
3671
|
+
console.log(chalk23.gray(`Created: ${new Date(learning.createdAt).toLocaleString()}`));
|
|
3672
|
+
console.log(chalk23.gray(`Updated: ${new Date(learning.updatedAt).toLocaleString()}`));
|
|
3673
|
+
console.log(chalk23.gray(`Uses: ${learning.useCount}`));
|
|
3674
|
+
if (learning.effectiveness !== void 0) {
|
|
3675
|
+
console.log(chalk23.gray(`Effectiveness: ${this.formatScore(learning.effectiveness)}%`));
|
|
3676
|
+
}
|
|
3677
|
+
console.log(chalk23.bold("\nContent:\n"));
|
|
3678
|
+
console.log(learning.content);
|
|
3679
|
+
console.log();
|
|
3680
|
+
return 0;
|
|
3681
|
+
}
|
|
3682
|
+
/**
|
|
3683
|
+
* Compress observations into learnings
|
|
3684
|
+
*/
|
|
3685
|
+
async compressObservations() {
|
|
3686
|
+
const projectPath = process.cwd();
|
|
3687
|
+
initializeMemoryDirectory(projectPath);
|
|
3688
|
+
const observationStore = new ObservationStore(projectPath);
|
|
3689
|
+
const observations = observationStore.getAll();
|
|
3690
|
+
if (observations.length === 0) {
|
|
3691
|
+
console.log(chalk23.yellow("No observations to compress."));
|
|
3692
|
+
return 0;
|
|
3693
|
+
}
|
|
3694
|
+
console.log(chalk23.cyan(`Found ${observations.length} observations to compress...
|
|
3695
|
+
`));
|
|
3696
|
+
const compressor = createMemoryCompressor(projectPath);
|
|
3697
|
+
const compressionOptions = {
|
|
3698
|
+
minObservations: 2,
|
|
3699
|
+
additionalTags: this.tags?.split(",").map((t) => t.trim())
|
|
3700
|
+
};
|
|
3701
|
+
if (this.dryRun) {
|
|
3702
|
+
console.log(chalk23.gray("(Dry run - no changes will be made)\n"));
|
|
3703
|
+
const result2 = await compressor.compress(observations, compressionOptions);
|
|
3704
|
+
console.log(chalk23.green(`\u2713 Would compress ${result2.stats.inputCount} observations into ${result2.stats.outputCount} learnings
|
|
3705
|
+
`));
|
|
3706
|
+
if (result2.learnings.length > 0) {
|
|
3707
|
+
console.log(chalk23.bold("Learnings that would be created:"));
|
|
3708
|
+
for (const learning of result2.learnings) {
|
|
3709
|
+
console.log(` ${chalk23.cyan("\u25CF")} ${learning.title}`);
|
|
3710
|
+
console.log(` Tags: ${learning.tags.join(", ")}`);
|
|
3711
|
+
}
|
|
3712
|
+
console.log();
|
|
3713
|
+
}
|
|
3714
|
+
return 0;
|
|
3715
|
+
}
|
|
3716
|
+
const { learnings, result } = await compressor.compressAndStore(observations, compressionOptions);
|
|
3717
|
+
console.log(chalk23.green(`\u2713 Compressed ${result.stats.inputCount} observations into ${result.stats.outputCount} learnings
|
|
3718
|
+
`));
|
|
3719
|
+
if (learnings.length > 0) {
|
|
3720
|
+
console.log(chalk23.bold("New Learnings:"));
|
|
3721
|
+
for (const learning of learnings) {
|
|
3722
|
+
console.log(` ${chalk23.cyan("\u25CF")} ${learning.title}`);
|
|
3723
|
+
console.log(` Tags: ${learning.tags.join(", ")}`);
|
|
3724
|
+
}
|
|
3725
|
+
console.log();
|
|
3726
|
+
}
|
|
3727
|
+
if (result.processedObservationIds.length > 0) {
|
|
3728
|
+
const deleted = observationStore.deleteMany(result.processedObservationIds);
|
|
3729
|
+
console.log(chalk23.gray(`Cleared ${deleted} processed observations.`));
|
|
3730
|
+
}
|
|
3731
|
+
return 0;
|
|
3732
|
+
}
|
|
3733
|
+
/**
|
|
3734
|
+
* Export a learning as a skill
|
|
3735
|
+
*/
|
|
3736
|
+
async exportLearning() {
|
|
3737
|
+
const id = this.arg;
|
|
3738
|
+
if (!id) {
|
|
3739
|
+
console.error(chalk23.red("Error: Learning ID is required"));
|
|
3740
|
+
console.log(chalk23.gray("Usage: skillkit memory export <id> --name my-skill"));
|
|
3741
|
+
return 1;
|
|
3742
|
+
}
|
|
3743
|
+
const projectPath = process.cwd();
|
|
3744
|
+
let learning = new LearningStore("project", projectPath).getById(id);
|
|
3745
|
+
if (!learning) {
|
|
3746
|
+
learning = new LearningStore("global").getById(id);
|
|
3747
|
+
}
|
|
3748
|
+
if (!learning) {
|
|
3749
|
+
const projectLearnings = new LearningStore("project", projectPath).getAll();
|
|
3750
|
+
const globalLearnings = new LearningStore("global").getAll();
|
|
3751
|
+
const all = [...projectLearnings, ...globalLearnings];
|
|
3752
|
+
learning = all.find((l) => l.id.startsWith(id));
|
|
3753
|
+
}
|
|
3754
|
+
if (!learning) {
|
|
3755
|
+
console.error(chalk23.red(`Learning not found: ${id}`));
|
|
3756
|
+
return 1;
|
|
3757
|
+
}
|
|
3758
|
+
const skillName = this.name || this.slugify(learning.title);
|
|
3759
|
+
const skillContent = this.generateSkillContent(learning, skillName);
|
|
3760
|
+
if (this.dryRun) {
|
|
3761
|
+
console.log(chalk23.gray("(Dry run preview)\n"));
|
|
3762
|
+
console.log(skillContent);
|
|
3763
|
+
return 0;
|
|
3764
|
+
}
|
|
3765
|
+
const outputPath = this.output || `.skillkit/exports/${skillName}/SKILL.md`;
|
|
3766
|
+
const { dirname: dirname3 } = await import("path");
|
|
3767
|
+
const { existsSync: existsSync11, mkdirSync: mkdirSync5, writeFileSync: writeFileSync5 } = await import("fs");
|
|
3768
|
+
const outputDir = dirname3(outputPath);
|
|
3769
|
+
if (!existsSync11(outputDir)) {
|
|
3770
|
+
mkdirSync5(outputDir, { recursive: true });
|
|
3771
|
+
}
|
|
3772
|
+
writeFileSync5(outputPath, skillContent, "utf-8");
|
|
3773
|
+
console.log(chalk23.green(`\u2713 Exported learning as skill: ${skillName}`));
|
|
3774
|
+
console.log(chalk23.gray(` Path: ${outputPath}`));
|
|
3775
|
+
return 0;
|
|
3776
|
+
}
|
|
3777
|
+
/**
|
|
3778
|
+
* Import memories from another project
|
|
3779
|
+
*/
|
|
3780
|
+
async importMemories() {
|
|
3781
|
+
const inputPath = this.input || this.arg;
|
|
3782
|
+
if (!inputPath) {
|
|
3783
|
+
console.error(chalk23.red("Error: Input path is required"));
|
|
3784
|
+
console.log(chalk23.gray("Usage: skillkit memory import --input <path>"));
|
|
3785
|
+
return 1;
|
|
3786
|
+
}
|
|
3787
|
+
const { existsSync: existsSync11, readFileSync: readFileSync6 } = await import("fs");
|
|
3788
|
+
const { resolve: resolve11 } = await import("path");
|
|
3789
|
+
const fullPath = resolve11(inputPath);
|
|
3790
|
+
if (!existsSync11(fullPath)) {
|
|
3791
|
+
console.error(chalk23.red(`File not found: ${fullPath}`));
|
|
3792
|
+
return 1;
|
|
3793
|
+
}
|
|
3794
|
+
const projectPath = process.cwd();
|
|
3795
|
+
const store = new LearningStore(
|
|
3796
|
+
this.global ? "global" : "project",
|
|
3797
|
+
this.global ? void 0 : projectPath
|
|
3798
|
+
);
|
|
3799
|
+
try {
|
|
3800
|
+
const content = readFileSync6(fullPath, "utf-8");
|
|
3801
|
+
const { parse: parseYaml2 } = await import("yaml");
|
|
3802
|
+
const data = parseYaml2(content);
|
|
3803
|
+
if (!data.learnings || !Array.isArray(data.learnings)) {
|
|
3804
|
+
console.error(chalk23.red("Invalid memory file format"));
|
|
3805
|
+
return 1;
|
|
3806
|
+
}
|
|
3807
|
+
let imported = 0;
|
|
3808
|
+
for (const learning of data.learnings) {
|
|
3809
|
+
if (this.dryRun) {
|
|
3810
|
+
console.log(chalk23.gray(`Would import: ${learning.title}`));
|
|
3811
|
+
} else {
|
|
3812
|
+
store.add({
|
|
3813
|
+
source: "imported",
|
|
3814
|
+
title: learning.title,
|
|
3815
|
+
content: learning.content,
|
|
3816
|
+
// Default to empty arrays if not present in imported YAML
|
|
3817
|
+
tags: Array.isArray(learning.tags) ? learning.tags : [],
|
|
3818
|
+
frameworks: Array.isArray(learning.frameworks) ? learning.frameworks : [],
|
|
3819
|
+
patterns: Array.isArray(learning.patterns) ? learning.patterns : []
|
|
3820
|
+
});
|
|
3821
|
+
imported++;
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
if (this.dryRun) {
|
|
3825
|
+
console.log(chalk23.gray(`
|
|
3826
|
+
(Dry run - ${data.learnings.length} learnings would be imported)`));
|
|
3827
|
+
} else {
|
|
3828
|
+
console.log(chalk23.green(`\u2713 Imported ${imported} learnings`));
|
|
3829
|
+
}
|
|
3830
|
+
return 0;
|
|
3831
|
+
} catch (error) {
|
|
3832
|
+
console.error(chalk23.red(`Import failed: ${error}`));
|
|
3833
|
+
return 1;
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3836
|
+
/**
|
|
3837
|
+
* Clear session observations
|
|
3838
|
+
*/
|
|
3839
|
+
async clearMemory() {
|
|
3840
|
+
const projectPath = process.cwd();
|
|
3841
|
+
if (this.dryRun) {
|
|
3842
|
+
const observationStore2 = new ObservationStore(projectPath);
|
|
3843
|
+
const learningStore = new LearningStore("project", projectPath);
|
|
3844
|
+
console.log(chalk23.gray("(Dry run preview)\n"));
|
|
3845
|
+
console.log(`Would clear ${observationStore2.count()} observations`);
|
|
3846
|
+
if (!this.keepLearnings) {
|
|
3847
|
+
console.log(`Would clear ${learningStore.count()} learnings`);
|
|
3848
|
+
}
|
|
3849
|
+
return 0;
|
|
3850
|
+
}
|
|
3851
|
+
const observationStore = new ObservationStore(projectPath);
|
|
3852
|
+
observationStore.clear();
|
|
3853
|
+
console.log(chalk23.green("\u2713 Cleared session observations"));
|
|
3854
|
+
if (!this.keepLearnings) {
|
|
3855
|
+
const learningStore = new LearningStore("project", projectPath);
|
|
3856
|
+
learningStore.clear();
|
|
3857
|
+
console.log(chalk23.green("\u2713 Cleared project learnings"));
|
|
3858
|
+
}
|
|
3859
|
+
return 0;
|
|
3860
|
+
}
|
|
3861
|
+
/**
|
|
3862
|
+
* Add a manual learning
|
|
3863
|
+
*/
|
|
3864
|
+
async addLearning() {
|
|
3865
|
+
if (!this.title) {
|
|
3866
|
+
console.error(chalk23.red("Error: --title is required"));
|
|
3867
|
+
console.log(chalk23.gray('Usage: skillkit memory add --title "..." --content "..."'));
|
|
3868
|
+
return 1;
|
|
3869
|
+
}
|
|
3870
|
+
if (!this.content) {
|
|
3871
|
+
console.error(chalk23.red("Error: --content is required"));
|
|
3872
|
+
console.log(chalk23.gray('Usage: skillkit memory add --title "..." --content "..."'));
|
|
3873
|
+
return 1;
|
|
3874
|
+
}
|
|
3875
|
+
const projectPath = process.cwd();
|
|
3876
|
+
const store = new LearningStore(
|
|
3877
|
+
this.global ? "global" : "project",
|
|
3878
|
+
this.global ? void 0 : projectPath
|
|
3879
|
+
);
|
|
3880
|
+
const tags = this.tags?.split(",").map((t) => t.trim()) || [];
|
|
3881
|
+
const learning = store.add({
|
|
3882
|
+
source: "manual",
|
|
3883
|
+
title: this.title,
|
|
3884
|
+
content: this.content,
|
|
3885
|
+
tags
|
|
3886
|
+
});
|
|
3887
|
+
console.log(chalk23.green(`\u2713 Added learning: ${learning.title}`));
|
|
3888
|
+
console.log(chalk23.gray(` ID: ${learning.id}`));
|
|
3889
|
+
return 0;
|
|
3890
|
+
}
|
|
3891
|
+
/**
|
|
3892
|
+
* Rate a learning's effectiveness
|
|
3893
|
+
*/
|
|
3894
|
+
async rateLearning() {
|
|
3895
|
+
const id = this.arg;
|
|
3896
|
+
if (!id) {
|
|
3897
|
+
console.error(chalk23.red("Error: Learning ID is required"));
|
|
3898
|
+
console.log(chalk23.gray("Usage: skillkit memory rate <id> <rating>"));
|
|
3899
|
+
return 1;
|
|
3900
|
+
}
|
|
3901
|
+
const rating = parseInt(this.ratingArg || "0", 10);
|
|
3902
|
+
if (isNaN(rating) || rating < 0 || rating > 100) {
|
|
3903
|
+
console.error(chalk23.red("Error: Rating must be 0-100"));
|
|
3904
|
+
console.log(chalk23.gray("Usage: skillkit memory rate <id> <rating>"));
|
|
3905
|
+
return 1;
|
|
3906
|
+
}
|
|
3907
|
+
const projectPath = process.cwd();
|
|
3908
|
+
const projectStore = new LearningStore("project", projectPath);
|
|
3909
|
+
let learning = projectStore.getById(id);
|
|
3910
|
+
let store = projectStore;
|
|
3911
|
+
if (!learning) {
|
|
3912
|
+
const globalStore = new LearningStore("global");
|
|
3913
|
+
learning = globalStore.getById(id);
|
|
3914
|
+
store = globalStore;
|
|
3915
|
+
}
|
|
3916
|
+
if (!learning) {
|
|
3917
|
+
const projectLearnings = projectStore.getAll();
|
|
3918
|
+
const globalStore = new LearningStore("global");
|
|
3919
|
+
const globalLearnings = globalStore.getAll();
|
|
3920
|
+
learning = projectLearnings.find((l) => l.id.startsWith(id));
|
|
3921
|
+
if (learning) {
|
|
3922
|
+
store = projectStore;
|
|
3923
|
+
} else {
|
|
3924
|
+
learning = globalLearnings.find((l) => l.id.startsWith(id));
|
|
3925
|
+
if (learning) {
|
|
3926
|
+
store = globalStore;
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
if (!learning) {
|
|
3931
|
+
console.error(chalk23.red(`Learning not found: ${id}`));
|
|
3932
|
+
return 1;
|
|
3933
|
+
}
|
|
3934
|
+
store.setEffectiveness(learning.id, rating);
|
|
3935
|
+
console.log(chalk23.green(`\u2713 Rated "${learning.title}" as ${rating}% effective`));
|
|
3936
|
+
return 0;
|
|
3937
|
+
}
|
|
3938
|
+
/**
|
|
3939
|
+
* Show memory configuration
|
|
3940
|
+
*/
|
|
3941
|
+
async showConfig() {
|
|
3942
|
+
const projectPath = process.cwd();
|
|
3943
|
+
const paths = getMemoryPaths(projectPath);
|
|
3944
|
+
if (this.json) {
|
|
3945
|
+
console.log(JSON.stringify(paths, null, 2));
|
|
3946
|
+
return 0;
|
|
3947
|
+
}
|
|
3948
|
+
console.log(chalk23.bold("\nMemory Configuration\n"));
|
|
3949
|
+
console.log(chalk23.cyan("Paths:"));
|
|
3950
|
+
console.log(` Project observations: ${chalk23.gray(paths.observationsFile)}`);
|
|
3951
|
+
console.log(` Project learnings: ${chalk23.gray(paths.learningsFile)}`);
|
|
3952
|
+
console.log(` Project index: ${chalk23.gray(paths.indexFile)}`);
|
|
3953
|
+
console.log(` Global learnings: ${chalk23.gray(paths.globalLearningsFile)}`);
|
|
3954
|
+
console.log(` Global index: ${chalk23.gray(paths.globalIndexFile)}`);
|
|
3955
|
+
console.log();
|
|
3956
|
+
return 0;
|
|
3957
|
+
}
|
|
3958
|
+
/**
|
|
3959
|
+
* Format relevance/effectiveness score with color
|
|
3960
|
+
*/
|
|
3961
|
+
formatScore(score) {
|
|
3962
|
+
if (score >= 80) return chalk23.green(score.toString());
|
|
3963
|
+
if (score >= 50) return chalk23.yellow(score.toString());
|
|
3964
|
+
return chalk23.red(score.toString());
|
|
3965
|
+
}
|
|
3966
|
+
/**
|
|
3967
|
+
* Format matched by info
|
|
3968
|
+
*/
|
|
3969
|
+
formatMatchedBy(matchedBy) {
|
|
3970
|
+
const parts = [];
|
|
3971
|
+
if (matchedBy.frameworks.length > 0) {
|
|
3972
|
+
parts.push(`frameworks: ${matchedBy.frameworks.join(", ")}`);
|
|
3973
|
+
}
|
|
3974
|
+
if (matchedBy.tags.length > 0) {
|
|
3975
|
+
parts.push(`tags: ${matchedBy.tags.join(", ")}`);
|
|
3976
|
+
}
|
|
3977
|
+
if (matchedBy.keywords.length > 0) {
|
|
3978
|
+
parts.push(`keywords: ${matchedBy.keywords.slice(0, 3).join(", ")}`);
|
|
3979
|
+
}
|
|
3980
|
+
if (matchedBy.patterns.length > 0) {
|
|
3981
|
+
parts.push(`patterns: ${matchedBy.patterns.join(", ")}`);
|
|
3982
|
+
}
|
|
3983
|
+
return parts.join(" | ") || "general";
|
|
3984
|
+
}
|
|
3985
|
+
/**
|
|
3986
|
+
* Convert title to slug
|
|
3987
|
+
*/
|
|
3988
|
+
slugify(text) {
|
|
3989
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
|
|
3990
|
+
}
|
|
3991
|
+
/**
|
|
3992
|
+
* Escape a YAML scalar value if it contains special characters
|
|
3993
|
+
*/
|
|
3994
|
+
escapeYamlValue(value) {
|
|
3995
|
+
if (/[:#\[\]{}|>&*!?,'"\\@`]/.test(value) || value.includes("\n")) {
|
|
3996
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
3997
|
+
}
|
|
3998
|
+
return value;
|
|
3999
|
+
}
|
|
4000
|
+
/**
|
|
4001
|
+
* Generate skill content from learning
|
|
4002
|
+
*/
|
|
4003
|
+
generateSkillContent(learning, skillName) {
|
|
4004
|
+
const escapedName = this.escapeYamlValue(skillName);
|
|
4005
|
+
const escapedDesc = this.escapeYamlValue(learning.title);
|
|
4006
|
+
const escapedTags = learning.tags.map((t) => this.escapeYamlValue(t));
|
|
4007
|
+
const lines = [
|
|
4008
|
+
"---",
|
|
4009
|
+
`name: ${escapedName}`,
|
|
4010
|
+
`description: ${escapedDesc}`,
|
|
4011
|
+
`version: 1.0.0`,
|
|
4012
|
+
`tags: [${escapedTags.join(", ")}]`,
|
|
4013
|
+
`source: skillkit-memory`,
|
|
4014
|
+
`sourceType: local`
|
|
4015
|
+
];
|
|
4016
|
+
if (learning.frameworks?.length) {
|
|
4017
|
+
const escapedFrameworks = learning.frameworks.map((f) => this.escapeYamlValue(f));
|
|
4018
|
+
lines.push(`compatibility:`);
|
|
4019
|
+
lines.push(` frameworks: [${escapedFrameworks.join(", ")}]`);
|
|
4020
|
+
}
|
|
4021
|
+
lines.push("---", "", `# ${learning.title}`, "");
|
|
4022
|
+
if (learning.patterns?.length) {
|
|
4023
|
+
lines.push(`## Patterns`, "");
|
|
4024
|
+
for (const pattern of learning.patterns) {
|
|
4025
|
+
lines.push(`- ${pattern}`);
|
|
4026
|
+
}
|
|
4027
|
+
lines.push("");
|
|
4028
|
+
}
|
|
4029
|
+
lines.push(`## Content`, "", learning.content, "");
|
|
4030
|
+
lines.push(
|
|
4031
|
+
"---",
|
|
4032
|
+
"",
|
|
4033
|
+
"*Exported from SkillKit session memory*",
|
|
4034
|
+
`*Created: ${new Date(learning.createdAt).toLocaleDateString()}*`,
|
|
4035
|
+
`*Uses: ${learning.useCount}*`
|
|
4036
|
+
);
|
|
4037
|
+
if (learning.effectiveness !== void 0) {
|
|
4038
|
+
lines.push(`*Effectiveness: ${learning.effectiveness}%*`);
|
|
4039
|
+
}
|
|
4040
|
+
return lines.join("\n");
|
|
4041
|
+
}
|
|
4042
|
+
};
|
|
2224
4043
|
export {
|
|
2225
4044
|
ContextCommand,
|
|
2226
4045
|
CreateCommand,
|
|
@@ -2229,14 +4048,24 @@ export {
|
|
|
2229
4048
|
InitCommand,
|
|
2230
4049
|
InstallCommand,
|
|
2231
4050
|
ListCommand,
|
|
4051
|
+
MarketplaceCommand,
|
|
4052
|
+
MemoryCommand,
|
|
4053
|
+
PauseCommand,
|
|
2232
4054
|
ReadCommand,
|
|
2233
4055
|
RecommendCommand,
|
|
2234
4056
|
RemoveCommand,
|
|
4057
|
+
ResumeCommand,
|
|
4058
|
+
RunCommand,
|
|
4059
|
+
StatusCommand,
|
|
2235
4060
|
SyncCommand,
|
|
4061
|
+
TestCommand,
|
|
2236
4062
|
TranslateCommand,
|
|
2237
4063
|
UICommand,
|
|
2238
4064
|
UpdateCommand,
|
|
2239
4065
|
ValidateCommand,
|
|
4066
|
+
WorkflowCreateCommand,
|
|
4067
|
+
WorkflowListCommand,
|
|
4068
|
+
WorkflowRunCommand,
|
|
2240
4069
|
getAgentConfigPath,
|
|
2241
4070
|
getInstallDir,
|
|
2242
4071
|
getSearchDirs,
|