left-skills 0.5.0 → 0.7.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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.js +191 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -58,6 +58,7 @@ left-skills usage --json # for AI (JSON)
58
58
  left-skills usage --since 7 # last 7 days
59
59
  left-skills lint # static quality check (0-100 score)
60
60
  left-skills evolve <skill> # generate improvement prompt (for AI, human review)
61
+ left-skills inspire # find repeated commands → suggest new skill
61
62
  left-skills doctor # diagnose install (✓/✗ + fix)
62
63
  left-skills report --markdown > report.md # export report
63
64
  ```
package/dist/cli.js CHANGED
@@ -6991,10 +6991,195 @@ function evolvePrompt(skillName) {
6991
6991
  return lines.join("\n");
6992
6992
  }
6993
6993
 
6994
+ // src/inspire.ts
6995
+ var import_node_fs8 = require("fs");
6996
+ var import_node_path8 = require("path");
6997
+ var import_node_os8 = require("os");
6998
+
6999
+ // src/llm.ts
7000
+ var import_node_fs7 = require("fs");
7001
+ var import_node_os7 = require("os");
7002
+ var import_node_path7 = require("path");
7003
+ var ANTHROPIC_API_URL = "https://api.anthropic.com/v1/messages";
7004
+ function getApiKey() {
7005
+ const env = process.env.ANTHROPIC_API_KEY;
7006
+ if (env) return env;
7007
+ try {
7008
+ const settings = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path7.join)((0, import_node_os7.homedir)(), ".claude", "settings.json"), "utf-8"));
7009
+ return settings.env?.ANTHROPIC_API_KEY || null;
7010
+ } catch {
7011
+ return null;
7012
+ }
7013
+ }
7014
+ async function llmAnalyze(prompt, system) {
7015
+ const apiKey = getApiKey();
7016
+ if (!apiKey) return null;
7017
+ const model = process.env.ANTHROPIC_MODEL || "claude-sonnet-4-6";
7018
+ try {
7019
+ const response = await fetch(ANTHROPIC_API_URL, {
7020
+ method: "POST",
7021
+ headers: {
7022
+ "Content-Type": "application/json",
7023
+ "x-api-key": apiKey,
7024
+ "anthropic-version": "2023-06-01"
7025
+ },
7026
+ body: JSON.stringify({
7027
+ model,
7028
+ max_tokens: 1024,
7029
+ system,
7030
+ messages: [{ role: "user", content: prompt }]
7031
+ })
7032
+ });
7033
+ if (!response.ok) return null;
7034
+ const data = await response.json();
7035
+ return data.content?.[0]?.text || null;
7036
+ } catch {
7037
+ return null;
7038
+ }
7039
+ }
7040
+
7041
+ // src/inspire.ts
7042
+ function scanSessions(sinceDays = 30) {
7043
+ const projectsDir = (0, import_node_path8.join)((0, import_node_os8.homedir)(), ".claude", "projects");
7044
+ const cmdCount = /* @__PURE__ */ new Map();
7045
+ const seqCount = /* @__PURE__ */ new Map();
7046
+ if (!(0, import_node_fs8.existsSync)(projectsDir)) return { cmdCount, seqCount };
7047
+ const cutoff = Date.now() - sinceDays * 864e5;
7048
+ for (const project of (0, import_node_fs8.readdirSync)(projectsDir, { withFileTypes: true })) {
7049
+ if (!project.isDirectory()) continue;
7050
+ const projectDir = (0, import_node_path8.join)(projectsDir, project.name);
7051
+ for (const file of (0, import_node_fs8.readdirSync)(projectDir)) {
7052
+ if (!file.endsWith(".jsonl")) continue;
7053
+ try {
7054
+ const content = (0, import_node_fs8.readFileSync)((0, import_node_path8.join)(projectDir, file), "utf-8");
7055
+ const toolNames = [];
7056
+ for (const line of content.split("\n")) {
7057
+ if (!line.trim()) continue;
7058
+ try {
7059
+ const obj = JSON.parse(line);
7060
+ const ts = obj.timestamp;
7061
+ if (ts && new Date(ts).getTime() < cutoff) continue;
7062
+ const msg = obj.message;
7063
+ if (msg && msg.content && Array.isArray(msg.content)) {
7064
+ for (const block of msg.content) {
7065
+ if (block.type === "tool_use") {
7066
+ toolNames.push(block.name);
7067
+ if (block.name === "Bash") {
7068
+ const cmd = block.input?.command;
7069
+ if (cmd && cmd.length >= 30) {
7070
+ cmdCount.set(cmd, (cmdCount.get(cmd) || 0) + 1);
7071
+ }
7072
+ }
7073
+ }
7074
+ }
7075
+ }
7076
+ } catch {
7077
+ }
7078
+ }
7079
+ for (let i = 0; i + 2 < toolNames.length; i++) {
7080
+ const seq = toolNames.slice(i, i + 3).join(",");
7081
+ seqCount.set(seq, (seqCount.get(seq) || 0) + 1);
7082
+ }
7083
+ } catch {
7084
+ }
7085
+ }
7086
+ }
7087
+ return { cmdCount, seqCount };
7088
+ }
7089
+ function skeletonize(cmd) {
7090
+ return cmd.replace(/"[^"]*"/g, "*").replace(/'[^']*'/g, "*").replace(/\/[^\s"']+/g, "/*").replace(/\s+/g, " ").trim();
7091
+ }
7092
+ function isNoise(skeleton) {
7093
+ const simple = /^(echo|ls|cat|cd|pwd|clear|exit|which|whoami|date|uptime)\b/i;
7094
+ return simple.test(skeleton) || skeleton.length < 10;
7095
+ }
7096
+ function isSeqNoise(seq) {
7097
+ const parts = seq.split(",");
7098
+ if (parts.every((p) => p === parts[0])) return true;
7099
+ return false;
7100
+ }
7101
+ async function inspirePrompt(sinceDays = 30) {
7102
+ const { cmdCount, seqCount } = scanSessions(sinceDays);
7103
+ const skeletonCount = /* @__PURE__ */ new Map();
7104
+ for (const [cmd, count] of cmdCount) {
7105
+ const skeleton = skeletonize(cmd);
7106
+ if (isNoise(skeleton)) continue;
7107
+ const existing = skeletonCount.get(skeleton);
7108
+ if (existing) {
7109
+ existing.count += count;
7110
+ if (existing.examples.length < 2) existing.examples.push(cmd.slice(0, 80));
7111
+ } else {
7112
+ skeletonCount.set(skeleton, { count, examples: [cmd.slice(0, 80)] });
7113
+ }
7114
+ }
7115
+ const cmdCandidates = [...skeletonCount.entries()].filter(([, info]) => info.count >= 3).sort((a, b) => b[1].count - a[1].count);
7116
+ const seqCandidates = [...seqCount.entries()].filter(([seq, count]) => count >= 3 && !isSeqNoise(seq)).sort((a, b) => b[1] - a[1]).slice(0, 10);
7117
+ if (cmdCandidates.length === 0 && seqCandidates.length === 0) {
7118
+ return "\u65E0\u91CD\u590D\u6A21\u5F0F(Bash \u9AA8\u67B6 \u22653 \u6216 tool \u5E8F\u5217 \u22653),\u6682\u4E0D\u5EFA\u8BAE\u5199 skill\u3002\n(\u8BD5\u66F4\u591A\u5929?left-skills inspire --since 90)";
7119
+ }
7120
+ const installed = listInstalledSkills();
7121
+ const llmPrompt = `\u4F60\u53CD\u590D\u8DD1\u8FD9\u4E9B(${cmdCandidates.length} \u4E2A Bash \u9AA8\u67B6 + ${seqCandidates.length} \u4E2A tool \u5E8F\u5217):
7122
+
7123
+ ## Bash \u9AA8\u67B6\u5019\u9009
7124
+ ${cmdCandidates.slice(0, 10).map(([sk, info]) => `- ${sk}(${info.count} \u6B21,\u4F8B: ${info.examples[0]})`).join("\n") || "(\u65E0)"}
7125
+
7126
+ ## tool \u5E8F\u5217\u5019\u9009(all tools,\u5DE5\u4F5C\u6D41)
7127
+ ${seqCandidates.map(([seq, count]) => `- ${seq}(${count} \u6B21)`).join("\n") || "(\u65E0)"}
7128
+
7129
+ \u5DF2\u88C5 skill(\u907F\u514D\u91CD\u590D\u63D0\u8BAE):
7130
+ ${installed.length > 0 ? installed.map((s) => "- " + s).join("\n") : "(\u65E0)"}
7131
+
7132
+ \u8BF7\u5224\u65AD:\u54EA\u4E9B\u8BE5\u5199 skill(\u81EA\u52A8\u5316)?\u6311 1-2 \u4E2A\u6700\u9891\u7E41\u7684,\u751F\u6210 SKILL.md \u8349\u7A3F\u6307\u4EE4\u3002
7133
+ - description \u8BF4\u660E"\u4F55\u65F6\u7528"(\u8BA9 AI \u81EA\u51B3\u89E6\u53D1)
7134
+ - body \u542B\u8BE5\u547D\u4EE4/\u5DE5\u4F5C\u6D41(\u81EA\u52A8\u5316)
7135
+ - **\u542B test cases(Happy/Edge/Error 3 \u573A\u666F)**(\u7ED9\u4EBA\u5BA1\u9A8C)
7136
+ - \u4E0D\u8981\u63D0\u8BAE\u5DF2\u88C5 skill \u91CD\u590D\u7684`;
7137
+ const system = "\u4F60\u662F skill \u67B6\u6784\u5E08\u3002\u4ECE\u91CD\u590D\u547D\u4EE4/\u5DE5\u4F5C\u6D41\u5224\u65AD\u8BE5\u4E0D\u8BE5\u5199 skill,\u907F\u514D\u91CD\u590D\u5DF2\u88C5\u7684\u3002\u8F93\u51FA\u542B test cases \u7684\u6539\u8FDB\u6307\u4EE4(\u7ED9\u4EBA\u5BA1,\u4E0D\u81EA\u52A8\u521B\u5EFA)\u3002";
7138
+ const llmResult = await llmAnalyze(llmPrompt, system);
7139
+ const lines = [];
7140
+ lines.push("# inspire:\u4F60\u53CD\u590D\u8DD1\u7684\u547D\u4EE4 + \u5DE5\u4F5C\u6D41(\u5EFA\u8BAE\u5199 skill \u81EA\u52A8\u5316)");
7141
+ lines.push("");
7142
+ lines.push("## Bash \u9AA8\u67B6\u5019\u9009(\u6B63\u5219\u7C97\u7B5B \u22653)");
7143
+ if (cmdCandidates.length > 0) {
7144
+ for (const [sk, info] of cmdCandidates) lines.push(`- ${sk}(${info.count} \u6B21)`);
7145
+ } else {
7146
+ lines.push("(\u65E0)");
7147
+ }
7148
+ lines.push("");
7149
+ lines.push("## tool \u5E8F\u5217\u5019\u9009(all tools,\u5DE5\u4F5C\u6D41 \u22653)");
7150
+ if (seqCandidates.length > 0) {
7151
+ for (const [seq, count] of seqCandidates) lines.push(`- ${seq}(${count} \u6B21)`);
7152
+ } else {
7153
+ lines.push("(\u65E0)");
7154
+ }
7155
+ lines.push("");
7156
+ lines.push("## \u5DF2\u88C5 skill(OBSERVE,\u907F\u514D\u91CD\u590D)");
7157
+ if (installed.length > 0) {
7158
+ for (const s of installed) lines.push(`- ${s}`);
7159
+ } else {
7160
+ lines.push("(\u65E0)");
7161
+ }
7162
+ lines.push("");
7163
+ if (llmResult) {
7164
+ lines.push("## LLM \u7CBE\u63D0(\u542B test cases \u6307\u4EE4,\u4EBA\u5BA1)");
7165
+ lines.push(llmResult);
7166
+ } else {
7167
+ lines.push("## \u6539\u8FDB\u6307\u4EE4(\u7ED9 AI,\u65E0 LLM \u964D\u7EA7\u7EAF\u6B63\u5219)");
7168
+ lines.push("\u8BF7\u57FA\u4E8E\u4EE5\u4E0A\u5019\u9009,\u6311 1-2 \u4E2A\u6700\u9891\u7E41\u7684,\u751F\u6210 SKILL.md \u8349\u7A3F:");
7169
+ lines.push('- description \u8BF4\u660E"\u4F55\u65F6\u7528"');
7170
+ lines.push("- body \u542B\u8BE5\u547D\u4EE4/\u5DE5\u4F5C\u6D41");
7171
+ lines.push("- **\u542B test cases(Happy/Edge/Error)**");
7172
+ lines.push("- \u4E0D\u8981\u63D0\u8BAE\u5DF2\u88C5 skill \u91CD\u590D\u7684");
7173
+ }
7174
+ lines.push("");
7175
+ lines.push("\u751F\u6210\u8349\u7A3F,\u6211\u5BA1\u8FC7\u540E\u4E22\u8FDB .claude/skills/(\u4E0D\u81EA\u52A8\u521B\u5EFA)\u3002");
7176
+ return lines.join("\n");
7177
+ }
7178
+
6994
7179
  // package.json
6995
7180
  var package_default = {
6996
7181
  name: "left-skills",
6997
- version: "0.5.0",
7182
+ version: "0.7.0",
6998
7183
  description: "\u7ED9 AI \u7528\u7684 skill \u751F\u547D\u5468\u671F\u7BA1\u7406\u5DE5\u5177 \u2014 MVP: skill \u8C03\u7528\u4F7F\u7528\u7EDF\u8BA1",
6999
7184
  bin: {
7000
7185
  "left-skills": "./dist/cli.js"
@@ -7038,7 +7223,7 @@ var package_default = {
7038
7223
 
7039
7224
  // src/cli.ts
7040
7225
  var program2 = new Command();
7041
- program2.name("left-skills").description("\u7ED9 AI \u7528\u7684 skill \u751F\u547D\u5468\u671F\u7BA1\u7406\u5DE5\u5177 \u2014 MVP: skill \u8C03\u7528\u4F7F\u7528\u7EDF\u8BA1").version(package_default.version);
7226
+ program2.name("left-skills").description("\u7ED9 AI \u7528\u7684 skill \u751F\u547D\u5468\u671F\u7BA1\u7406\u5DE5\u5177 \u2014 skill \u8C03\u7528\u4F7F\u7528\u7EDF\u8BA1").version(package_default.version);
7042
7227
  program2.command("usage").description("skill \u8C03\u7528\u4F7F\u7528\u62A5\u544A").option("--json", "\u8F93\u51FA JSON(AI \u7528)").option("--since <days>", "\u65F6\u95F4\u7A97\u53E3(\u5929,\u9ED8\u8BA4 30)", "30").action((opts) => {
7043
7228
  const since = parseInt(opts.since, 10) || 30;
7044
7229
  const report = buildReport(since);
@@ -7055,6 +7240,10 @@ program2.command("lint").description("\u9759\u6001\u8D28\u91CF\u68C0\u67E5 SKILL
7055
7240
  program2.command("evolve <skill>").description("\u6536\u96C6 usage+lint \u4FE1\u53F7,\u8F93\u51FA\u6539\u8FDB prompt(\u7ED9 AI,\u4EBA\u5BA1,\u4E0D\u81EA\u52A8\u6539)").action((skill) => {
7056
7241
  console.log(evolvePrompt(skill));
7057
7242
  });
7243
+ program2.command("inspire").description("\u626B\u4F1A\u8BDD\u627E\u91CD\u590D\u547D\u4EE4,\u63D0\u8BAE\u5199 skill(hybrid \u6B63\u5219+LLM,\u7ED9 AI,\u4EBA\u5BA1,\u4E0D\u81EA\u52A8\u521B\u5EFA)").option("--since <days>", "\u65F6\u95F4\u7A97\u53E3(\u5929,\u9ED8\u8BA4 30)", "30").action(async (opts) => {
7244
+ const since = parseInt(opts.since, 10) || 30;
7245
+ console.log(await inspirePrompt(since));
7246
+ });
7058
7247
  program2.command("report").description("\u5BFC\u51FA usage \u62A5\u544A markdown(\u53EF > report.md \u5206\u4EAB)").option("--markdown", "\u8F93\u51FA markdown(\u9ED8\u8BA4\u5373 markdown)").option("--since <days>", "\u65F6\u95F4\u7A97\u53E3(\u5929,\u9ED8\u8BA4 30)", "30").action((opts) => {
7059
7248
  const since = parseInt(opts.since, 10) || 30;
7060
7249
  const report = buildReport(since);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "left-skills",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "给 AI 用的 skill 生命周期管理工具 — MVP: skill 调用使用统计",
5
5
  "bin": {
6
6
  "left-skills": "./dist/cli.js"