wormclaude 1.0.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/dist/skills.js ADDED
@@ -0,0 +1,275 @@
1
+ // Skill sistemi — klasör-tabanlı (manifest) + registry + install/update/remove.
2
+ // Claude Code skills/ mimarisinin uyarlaması. Kod gerektirmez; markdown + skill.json.
3
+ //
4
+ // .wormclaude/skills/<ad>/
5
+ // skill.json { name, description, autoInvoke, context, tools, whenToUse }
6
+ // prompt.md (uzmanlık talimatı = gövde)
7
+ // examples.md (opsiyonel) · templates/ assets/ (opsiyonel, model Read ile kullanır)
8
+ //
9
+ // Geriye uyumluluk: .wormclaude/skills/<ad>.md (frontmatter'lı düz dosya) da çalışır.
10
+ import { execSync } from 'node:child_process';
11
+ import * as fs from 'node:fs';
12
+ import * as os from 'node:os';
13
+ import * as path from 'node:path';
14
+ import { getLang } from './i18n.js';
15
+ let SKILLS = [];
16
+ const SKILLS_DIR = path.join(process.cwd(), '.wormclaude', 'skills');
17
+ export function getSkillsDir() { return SKILLS_DIR; }
18
+ function parseFrontmatter(raw) {
19
+ if (!raw.startsWith('---'))
20
+ return { fm: {}, body: raw };
21
+ const end = raw.indexOf('\n---', 3);
22
+ if (end === -1)
23
+ return { fm: {}, body: raw };
24
+ const fm = {};
25
+ for (const line of raw.slice(3, end).trim().split('\n')) {
26
+ const i = line.indexOf(':');
27
+ if (i > 0)
28
+ fm[line.slice(0, i).trim()] = line.slice(i + 1).trim().replace(/^["']|["']$/g, '');
29
+ }
30
+ return { fm, body: raw.slice(end + 4).replace(/^\r?\n/, '') };
31
+ }
32
+ function readFirst(dir, names) {
33
+ for (const n of names) {
34
+ try {
35
+ return fs.readFileSync(path.join(dir, n), 'utf8');
36
+ }
37
+ catch { }
38
+ }
39
+ return '';
40
+ }
41
+ export function loadSkills() {
42
+ SKILLS = [];
43
+ let entries = [];
44
+ try {
45
+ entries = fs.readdirSync(SKILLS_DIR, { withFileTypes: true });
46
+ }
47
+ catch {
48
+ return SKILLS;
49
+ }
50
+ for (const e of entries) {
51
+ const full = path.join(SKILLS_DIR, e.name);
52
+ if (e.isDirectory()) {
53
+ // Klasör skill'i: skill.json + prompt.md
54
+ let manifest = {};
55
+ try {
56
+ manifest = JSON.parse(fs.readFileSync(path.join(full, 'skill.json'), 'utf8'));
57
+ }
58
+ catch { }
59
+ const body = readFirst(full, ['prompt.md', 'skill.md', 'SKILL.md']);
60
+ if (!body && !manifest.name)
61
+ continue; // skill değil
62
+ const name = (manifest.name || e.name).trim().replace(/[^a-zA-Z0-9_-]/g, '-');
63
+ SKILLS.push({
64
+ name,
65
+ description: manifest.description || body.split('\n').find((l) => l.trim())?.slice(0, 80) || name,
66
+ whenToUse: manifest.whenToUse,
67
+ context: manifest.context === 'fork' ? 'fork' : 'inline',
68
+ autoInvoke: !!manifest.autoInvoke,
69
+ tools: Array.isArray(manifest.tools) ? manifest.tools : undefined,
70
+ body: body.trim(),
71
+ dir: full,
72
+ });
73
+ }
74
+ else if (e.name.endsWith('.md')) {
75
+ // Eski düz .md (frontmatter)
76
+ let raw = '';
77
+ try {
78
+ raw = fs.readFileSync(full, 'utf8');
79
+ }
80
+ catch {
81
+ continue;
82
+ }
83
+ const { fm, body } = parseFrontmatter(raw);
84
+ const name = (fm.name || e.name.replace(/\.md$/, '')).trim().replace(/[^a-zA-Z0-9_-]/g, '-');
85
+ if (!name)
86
+ continue;
87
+ SKILLS.push({
88
+ name,
89
+ description: fm.description || body.split('\n').find((l) => l.trim())?.slice(0, 80) || name,
90
+ whenToUse: fm.whenToUse,
91
+ context: fm.context === 'fork' ? 'fork' : 'inline',
92
+ autoInvoke: fm.autoInvoke === 'true',
93
+ tools: fm.tools ? fm.tools.split(',').map((s) => s.trim()) : undefined,
94
+ body: body.trim(),
95
+ });
96
+ }
97
+ }
98
+ return SKILLS;
99
+ }
100
+ export function getSkills() { return SKILLS; }
101
+ export function getSkill(name) { return SKILLS.find((s) => s.name === name); }
102
+ export function getAutoSkills() { return SKILLS.filter((s) => s.autoInvoke); }
103
+ // Skill gövdesi + (klasör skill'iyse) dosya referansı + kullanıcı argümanı → prompt.
104
+ export function buildSkillPrompt(skill, args) {
105
+ const en = getLang() === 'en';
106
+ let p = skill.body;
107
+ if (skill.dir) {
108
+ p = (en
109
+ ? `Skill base directory: ${skill.dir}\n(You may Read/Grep files here — examples.md, templates/, assets/ — as needed.)\n\n`
110
+ : `Skill kök dizini: ${skill.dir}\n(Gerekirse buradaki dosyaları Read/Grep ile oku — examples.md, templates/, assets/.)\n\n`) + p;
111
+ }
112
+ if (args && args.trim()) {
113
+ p += (en ? '\n\n## Additional context from user\n\n' : '\n\n## Kullanıcıdan ek bağlam\n\n') + args.trim();
114
+ }
115
+ return p;
116
+ }
117
+ // ── Registry ──────────────────────────────────────────────────────────────────
118
+ // Kaynak: WORMCLAUDE_SKILL_REGISTRY (URL) veya yerel .wormclaude/registry.json
119
+ // Biçim: { "skills": { "pentest": { "repo": "owner/repo", "description": "..." } } }
120
+ const REGISTRY_URL = process.env.WORMCLAUDE_SKILL_REGISTRY || '';
121
+ const LOCAL_REGISTRY = path.join(process.cwd(), '.wormclaude', 'registry.json');
122
+ export async function getRegistry() {
123
+ try {
124
+ if (REGISTRY_URL) {
125
+ const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(15000) });
126
+ if (res.ok) {
127
+ const j = await res.json();
128
+ return j.skills || j || {};
129
+ }
130
+ }
131
+ }
132
+ catch { }
133
+ try {
134
+ const j = JSON.parse(fs.readFileSync(LOCAL_REGISTRY, 'utf8'));
135
+ return j.skills || j || {};
136
+ }
137
+ catch { }
138
+ return {};
139
+ }
140
+ function copyDir(src, dst) {
141
+ fs.mkdirSync(dst, { recursive: true });
142
+ for (const e of fs.readdirSync(src, { withFileTypes: true })) {
143
+ if (e.name === '.git')
144
+ continue;
145
+ const s = path.join(src, e.name), d = path.join(dst, e.name);
146
+ if (e.isDirectory())
147
+ copyDir(s, d);
148
+ else
149
+ fs.copyFileSync(s, d);
150
+ }
151
+ }
152
+ // Skill kur. source: registry adı | owner/repo | git url | https://.../x.md
153
+ // Döner: { names, error }
154
+ export async function installSkill(source) {
155
+ const en = getLang() === 'en';
156
+ fs.mkdirSync(SKILLS_DIR, { recursive: true });
157
+ // 1) Tek .md indirme
158
+ if (/^https?:\/\/\S+\.md$/i.test(source)) {
159
+ try {
160
+ const res = await fetch(source, { signal: AbortSignal.timeout(20000) });
161
+ if (!res.ok)
162
+ return { names: [], error: `HTTP ${res.status}` };
163
+ const fname = (source.split('/').pop() || 'skill.md').replace(/[^\w.-]/g, '_');
164
+ fs.writeFileSync(path.join(SKILLS_DIR, fname), await res.text());
165
+ loadSkills();
166
+ return { names: [fname.replace(/\.md$/, '')] };
167
+ }
168
+ catch (e) {
169
+ return { names: [], error: e?.message || String(e) };
170
+ }
171
+ }
172
+ // 2) Kaynağı çöz: registry adı / owner-repo / url
173
+ let repo = '';
174
+ let nameHint = '';
175
+ const reg = await getRegistry();
176
+ if (reg[source]) {
177
+ repo = reg[source].repo;
178
+ nameHint = source;
179
+ }
180
+ else if (/^[\w.-]+\/[\w.-]+$/.test(source))
181
+ repo = `https://github.com/${source}`;
182
+ else if (/^https?:\/\//i.test(source))
183
+ repo = source;
184
+ else
185
+ return { names: [], error: en ? 'Invalid source' : 'Geçersiz kaynak' };
186
+ if (/^[\w.-]+\/[\w.-]+$/.test(repo))
187
+ repo = `https://github.com/${repo}`;
188
+ const tmp = path.join(os.tmpdir(), `wc-skill-${Date.now()}`);
189
+ try {
190
+ execSync(`git clone --depth 1 ${repo} "${tmp}"`, { timeout: 60000, windowsHide: true, stdio: 'pipe' });
191
+ }
192
+ catch (gitErr) {
193
+ try {
194
+ fs.rmSync(tmp, { recursive: true, force: true });
195
+ }
196
+ catch { }
197
+ const msg = String(gitErr?.stderr || gitErr?.message || '');
198
+ if (/not found|could not read|repository .* not found|fatal: repository/i.test(msg))
199
+ return { names: [], error: en ? `Repo not found: ${repo}` : `Repo bulunamadı: ${repo}` };
200
+ if (/not recognized|command not found|'git'/i.test(msg))
201
+ return { names: [], error: en ? 'git is not installed' : 'git kurulu değil' };
202
+ return { names: [], error: msg.split('\n')[0].slice(0, 120) };
203
+ }
204
+ const installed = [];
205
+ const place = (srcDir, name) => {
206
+ const dst = path.join(SKILLS_DIR, name);
207
+ copyDir(srcDir, dst);
208
+ try {
209
+ fs.writeFileSync(path.join(dst, '.source'), repo);
210
+ }
211
+ catch { } // update için
212
+ installed.push(name);
213
+ };
214
+ if (fs.existsSync(path.join(tmp, 'skill.json'))) {
215
+ // Repo kökü tek skill
216
+ let nm = nameHint;
217
+ try {
218
+ nm = nm || JSON.parse(fs.readFileSync(path.join(tmp, 'skill.json'), 'utf8')).name;
219
+ }
220
+ catch { }
221
+ nm = (nm || repo.split('/').pop().replace(/\.git$/, '')).replace(/[^\w-]/g, '-');
222
+ place(tmp, nm);
223
+ }
224
+ else if (fs.existsSync(path.join(tmp, 'skills'))) {
225
+ // Repo içinde skills/ — her alt klasör (skill.json olan) bir skill
226
+ for (const e of fs.readdirSync(path.join(tmp, 'skills'), { withFileTypes: true })) {
227
+ if (e.isDirectory() && fs.existsSync(path.join(tmp, 'skills', e.name, 'skill.json'))) {
228
+ place(path.join(tmp, 'skills', e.name), e.name.replace(/[^\w-]/g, '-'));
229
+ }
230
+ }
231
+ }
232
+ else {
233
+ // Düz .md'ler (README hariç)
234
+ for (const e of fs.readdirSync(tmp, { withFileTypes: true })) {
235
+ if (e.isFile() && e.name.endsWith('.md') && e.name.toLowerCase() !== 'readme.md') {
236
+ fs.copyFileSync(path.join(tmp, e.name), path.join(SKILLS_DIR, e.name));
237
+ installed.push(e.name.replace(/\.md$/, ''));
238
+ }
239
+ }
240
+ }
241
+ try {
242
+ fs.rmSync(tmp, { recursive: true, force: true });
243
+ }
244
+ catch { }
245
+ loadSkills();
246
+ return { names: installed };
247
+ }
248
+ // Skill güncelle (kayıtlı .source repo'sundan yeniden çek)
249
+ export async function updateSkill(name) {
250
+ const dir = path.join(SKILLS_DIR, name);
251
+ let repo = '';
252
+ try {
253
+ repo = fs.readFileSync(path.join(dir, '.source'), 'utf8').trim();
254
+ }
255
+ catch { }
256
+ if (!repo)
257
+ return { ok: false, error: getLang() === 'en' ? 'No source info (.source) for this skill' : 'Bu skill için kaynak (.source) yok' };
258
+ const res = await installSkill(repo);
259
+ return { ok: res.names.length > 0, error: res.error };
260
+ }
261
+ export function removeSkill(name) {
262
+ const dir = path.join(SKILLS_DIR, name);
263
+ const md = path.join(SKILLS_DIR, name.endsWith('.md') ? name : name + '.md');
264
+ try {
265
+ if (fs.existsSync(dir))
266
+ fs.rmSync(dir, { recursive: true, force: true });
267
+ else
268
+ fs.unlinkSync(md);
269
+ loadSkills();
270
+ return true;
271
+ }
272
+ catch {
273
+ return false;
274
+ }
275
+ }
package/dist/tasks.js ADDED
@@ -0,0 +1,63 @@
1
+ // Faz 1 — Arka plan görev kayıt defteri.
2
+ // Hem arka plan shell komutlarını (LocalShellTask) hem de arka plan alt-agent'larını
3
+ // (LocalAgentTask) ve hafıza konsolidasyonunu (DreamTask) izler.
4
+ import { EventEmitter } from 'node:events';
5
+ class TaskRegistry extends EventEmitter {
6
+ tasks = new Map();
7
+ counters = { shell: 0, agent: 0, dream: 0 };
8
+ create(kind, label) {
9
+ const id = `${kind}_${++this.counters[kind]}`;
10
+ const task = { id, kind, label, status: 'running', output: '', startedAt: Date.now() };
11
+ this.tasks.set(id, task);
12
+ this.emit('change');
13
+ return task;
14
+ }
15
+ get(id) {
16
+ return this.tasks.get(id);
17
+ }
18
+ list() {
19
+ return [...this.tasks.values()].sort((a, b) => b.startedAt - a.startedAt);
20
+ }
21
+ running() {
22
+ return this.list().filter((t) => t.status === 'running');
23
+ }
24
+ append(id, text) {
25
+ const t = this.tasks.get(id);
26
+ if (!t)
27
+ return;
28
+ t.output += text;
29
+ if (t.output.length > 200000)
30
+ t.output = t.output.slice(-200000);
31
+ this.emit('change');
32
+ }
33
+ finish(id, status) {
34
+ const t = this.tasks.get(id);
35
+ if (!t)
36
+ return;
37
+ t.status = status;
38
+ t.endedAt = Date.now();
39
+ this.emit('change');
40
+ }
41
+ stop(id) {
42
+ const t = this.tasks.get(id);
43
+ if (!t || t.status !== 'running')
44
+ return false;
45
+ try {
46
+ t.child?.kill();
47
+ }
48
+ catch { }
49
+ try {
50
+ t.onStop?.();
51
+ }
52
+ catch { }
53
+ this.finish(id, 'stopped');
54
+ return true;
55
+ }
56
+ clearFinished() {
57
+ for (const [id, t] of this.tasks)
58
+ if (t.status !== 'running')
59
+ this.tasks.delete(id);
60
+ this.emit('change');
61
+ }
62
+ }
63
+ export const tasks = new TaskRegistry();
package/dist/theme.js ADDED
@@ -0,0 +1,11 @@
1
+ // WormClaude renk teması — siyah zemin + kırmızı/gri/beyaz (WormGPT tarzı)
2
+ export const theme = {
3
+ red: '#ff3b3b',
4
+ redBright: '#ff5c5c',
5
+ white: '#ffffff',
6
+ grey: '#9a9a9a',
7
+ greyDim: '#666666',
8
+ green: '#4ade80',
9
+ errorRed: '#ff6b6b',
10
+ };
11
+ export const VERSION = '1.0.0';
package/dist/tips.js ADDED
@@ -0,0 +1,60 @@
1
+ // Spinner/footer ipuçları (TR/EN) — Claude Code tips/ uyarlaması.
2
+ // LLM gerektirmez. Cooldown: en uzun süredir gösterilmeyen ipucu seçilir.
3
+ import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+ import { getLang } from './i18n.js';
6
+ const TIPS = [
7
+ { id: 'mcp', tr: '.wormclaude/mcp.json ile MCP sunucu ekle, /mcp ile gör', en: 'Add MCP servers via .wormclaude/mcp.json, view with /mcp' },
8
+ { id: 'agent', tr: 'Büyük işleri bir alt-agent\'a yaptır — paralel çalışır', en: 'Delegate big tasks to a sub-agent — runs in parallel' },
9
+ { id: 'bg', tr: 'Uzun komutları arka planda çalıştır, /tasks ile izle', en: 'Run long commands in the background, track with /tasks' },
10
+ { id: 'compact', tr: '/compact ile bağlamı küçült; eşik aşılınca otomatik olur', en: '/compact shrinks context; auto-runs when the threshold is hit' },
11
+ { id: 'dream', tr: '/dream ile hafızayı .wormclaude/memory.md\'ye konsolide et', en: '/dream consolidates memory into .wormclaude/memory.md' },
12
+ { id: 'cost', tr: '/cost ile token ve maliyet kullanımını gör', en: '/cost shows token and cost usage' },
13
+ { id: 'model', tr: '/model qwen2.5:14b ile daha güçlü modele geç', en: '/model qwen2.5:14b switches to a stronger model' },
14
+ { id: 'review', tr: '/review ile git diff\'ini modele incelet', en: '/review lets the model review your git diff' },
15
+ { id: 'commit', tr: '/commit ile stage\'li değişikliklerden otomatik commit', en: '/commit auto-writes a commit from staged changes' },
16
+ { id: 'config', tr: '/config ile backend url/key/model ayarla', en: '/config sets backend url/key/model' },
17
+ { id: 'init', tr: '/init ile projeyi tarayıp WORMCLAUDE.md üret', en: '/init scans the project and generates WORMCLAUDE.md' },
18
+ { id: 'context', tr: '/context ile bağlam doluluğunu ve eşiği gör', en: '/context shows context fill and the threshold' },
19
+ { id: 'parallel', tr: 'Salt-okunur araçlar (Read/Grep/Glob) eşzamanlı çalışır', en: 'Read-only tools (Read/Grep/Glob) run concurrently' },
20
+ { id: 'export', tr: '/export ile sohbeti kaydet, /resume ile geri yükle', en: '/export saves the chat, /resume restores it' },
21
+ { id: 'lang', tr: '/lang ile arayüz dilini değiştir (tr/en)', en: '/lang changes the interface language (tr/en)' },
22
+ ];
23
+ const FILE = path.join(process.cwd(), '.wormclaude', 'tips.json');
24
+ function load() {
25
+ try {
26
+ return JSON.parse(fs.readFileSync(FILE, 'utf8'));
27
+ }
28
+ catch {
29
+ return { seq: 0, shown: {} };
30
+ }
31
+ }
32
+ function save(h) {
33
+ try {
34
+ fs.mkdirSync(path.dirname(FILE), { recursive: true });
35
+ fs.writeFileSync(FILE, JSON.stringify(h));
36
+ }
37
+ catch { }
38
+ }
39
+ // Bir ipucu seç (en uzun süredir gösterilmeyen) ve id'sini döndür. Dilden bağımsız.
40
+ export function pickTipId() {
41
+ const h = load();
42
+ h.seq += 1;
43
+ let best = TIPS[0];
44
+ let bestSeq = Infinity;
45
+ for (const tp of TIPS) {
46
+ const s = h.shown[tp.id] ?? -1;
47
+ if (s < bestSeq) {
48
+ bestSeq = s;
49
+ best = tp;
50
+ }
51
+ }
52
+ h.shown[best.id] = h.seq;
53
+ save(h);
54
+ return best.id;
55
+ }
56
+ // id'nin metnini GÜNCEL dile göre döndürür (dil değişince güncellenir).
57
+ export function tipText(id) {
58
+ const tp = TIPS.find((x) => x.id === id) ?? TIPS[0];
59
+ return getLang() === 'en' ? tp.en : tp.tr;
60
+ }
@@ -0,0 +1,24 @@
1
+ // Araç-grubu özeti — Claude Code toolUseSummary/ uyarlaması.
2
+ // Tamamlanan bir araç batch'ini tek satır git-commit-konusu tarzı etiketle özetler.
3
+ import { completeText } from './agent.js';
4
+ const SYSTEM = 'Bu araç çağrılarının ne başardığını anlatan KISA bir etiket yaz. Tek satır, ' +
5
+ '~50 karakter, git-commit konusu gibi (cümle değil). Sadece etiketi döndür.\n\n' +
6
+ 'Örnekler:\n- auth/ içinde arandı\n- UserService\'te NPE düzeltildi\n- 3 dosya okundu';
7
+ export async function summarizeTools(tools, config) {
8
+ if (!tools.length)
9
+ return null;
10
+ const desc = tools
11
+ .map((t) => `Araç: ${t.name}\nGirdi: ${JSON.stringify(t.args).slice(0, 200)}`)
12
+ .join('\n\n');
13
+ try {
14
+ const out = await completeText([
15
+ { role: 'system', content: SYSTEM },
16
+ { role: 'user', content: `Tamamlanan araçlar:\n\n${desc}\n\nEtiket:` },
17
+ ], config);
18
+ const label = (out || '').replace(/^["'`]+|["'`]+$/g, '').split('\n')[0].trim().slice(0, 60);
19
+ return label || null;
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }