lazyclaw 6.0.0 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/channels-discord/index.mjs +76 -0
- package/channels-discord/package.json +14 -0
- package/channels-email/index.mjs +109 -0
- package/channels-email/package.json +14 -0
- package/channels-signal/index.mjs +69 -0
- package/channels-signal/package.json +9 -0
- package/channels-voice/index.mjs +81 -0
- package/channels-voice/package.json +9 -0
- package/channels-whatsapp/index.mjs +70 -0
- package/channels-whatsapp/package.json +13 -0
- package/commands/agents.mjs +669 -0
- package/commands/auth_nodes.mjs +323 -0
- package/commands/automation.mjs +582 -0
- package/commands/channels.mjs +255 -0
- package/commands/chat.mjs +1217 -0
- package/commands/config.mjs +315 -0
- package/commands/daemon.mjs +260 -0
- package/commands/misc.mjs +128 -0
- package/commands/providers.mjs +511 -0
- package/commands/sessions.mjs +343 -0
- package/commands/setup.mjs +741 -0
- package/commands/skills.mjs +218 -0
- package/commands/workflow.mjs +661 -0
- package/daemon/lib/auth.mjs +58 -0
- package/daemon/lib/cost.mjs +30 -0
- package/daemon/lib/provider.mjs +69 -0
- package/daemon/lib/respond.mjs +83 -0
- package/daemon/route_table.mjs +96 -0
- package/daemon/routes/_deps.mjs +36 -0
- package/daemon/routes/config.mjs +99 -0
- package/daemon/routes/conversation.mjs +371 -0
- package/daemon/routes/meta.mjs +239 -0
- package/daemon/routes/ops.mjs +185 -0
- package/daemon/routes/providers.mjs +211 -0
- package/daemon/routes/rates.mjs +90 -0
- package/daemon/routes/registry.mjs +223 -0
- package/daemon/routes/sessions.mjs +213 -0
- package/daemon/routes/skills.mjs +260 -0
- package/daemon/routes/workflows.mjs +224 -0
- package/dotenv_min.mjs +23 -0
- package/first_run.mjs +15 -0
- package/goals_cron.mjs +37 -0
- package/lib/args.mjs +216 -0
- package/lib/config.mjs +113 -0
- package/lib/registry_boot.mjs +55 -0
- package/package.json +14 -1
- package/secure_write.mjs +46 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// Skill management commands (list/show/install/remove/search/curate/classify),
|
|
2
|
+
// extracted from cli.mjs in Phase D3. Self-contained over skills*.mjs modules.
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { configPath } from '../lib/config.mjs';
|
|
6
|
+
|
|
7
|
+
export async function cmdSkills(sub, positional, flags = {}) {
|
|
8
|
+
const skillsMod = await import('../skills.mjs');
|
|
9
|
+
const cfgDir = path.dirname(configPath());
|
|
10
|
+
switch (sub) {
|
|
11
|
+
case undefined:
|
|
12
|
+
case 'list': {
|
|
13
|
+
// Same --filter / --limit semantic as v3.33's sessions list:
|
|
14
|
+
// case-insensitive name substring, then post-filter cap.
|
|
15
|
+
let items = skillsMod.listSkills(cfgDir);
|
|
16
|
+
if (flags.filter) {
|
|
17
|
+
const f = String(flags.filter).toLowerCase();
|
|
18
|
+
items = items.filter(s => s.name.toLowerCase().includes(f));
|
|
19
|
+
}
|
|
20
|
+
if (flags.limit !== undefined) {
|
|
21
|
+
const n = parseInt(flags.limit, 10);
|
|
22
|
+
if (Number.isFinite(n) && n > 0) items = items.slice(0, n);
|
|
23
|
+
}
|
|
24
|
+
console.log(JSON.stringify(items.map(s => ({ name: s.name, bytes: s.bytes, summary: s.summary })), null, 2));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
case 'show': {
|
|
28
|
+
const name = positional[0];
|
|
29
|
+
if (!name) { console.error('Usage: lazyclaw skills show <name>'); process.exit(2); }
|
|
30
|
+
try { process.stdout.write(skillsMod.loadSkill(name, cfgDir)); }
|
|
31
|
+
catch (e) { console.error(e.message); process.exit(1); }
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
case 'install': {
|
|
35
|
+
// Four forms:
|
|
36
|
+
// 1. install user/repo[@ref][:subpath] — GitHub bundle
|
|
37
|
+
// 2. install <name> --from <path>
|
|
38
|
+
// 3. install <name> --from-url <https://...>
|
|
39
|
+
// 4. install <name> — body via stdin
|
|
40
|
+
// Detect form 1 via a slash in the first positional and the
|
|
41
|
+
// absence of any --from* flag (so a literal local skill name
|
|
42
|
+
// with `/` still routes to the explicit-flag branch — though
|
|
43
|
+
// skillPath() rejects slashes anyway).
|
|
44
|
+
const name = positional[0];
|
|
45
|
+
if (!name) { console.error('Usage: lazyclaw skills install <user/repo[@ref][:path]> | <name> [--from <path> | --from-url <https://...>]'); process.exit(2); }
|
|
46
|
+
if (name.includes('/') && !flags.from && !flags['from-url']) {
|
|
47
|
+
const inst = await import('../skills_install.mjs');
|
|
48
|
+
try {
|
|
49
|
+
const r = await inst.installFromGithub(name, cfgDir, {
|
|
50
|
+
prefix: flags.prefix || '',
|
|
51
|
+
force: !!flags.force,
|
|
52
|
+
maxBytes: flags['max-bytes'] !== undefined ? parseInt(flags['max-bytes'], 10) : undefined,
|
|
53
|
+
timeoutMs: flags['timeout-ms'] !== undefined ? parseInt(flags['timeout-ms'], 10) : undefined,
|
|
54
|
+
});
|
|
55
|
+
console.log(JSON.stringify({
|
|
56
|
+
ok: true,
|
|
57
|
+
spec: `${r.spec.owner}/${r.spec.repo}@${r.spec.ref}${r.spec.subpath ? ':' + r.spec.subpath : ''}`,
|
|
58
|
+
installed: r.installed,
|
|
59
|
+
skipped: r.skipped,
|
|
60
|
+
}, null, 2));
|
|
61
|
+
return;
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.error(`error: ${e?.message || e}`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
let content;
|
|
68
|
+
if (flags['from-url']) {
|
|
69
|
+
const url = String(flags['from-url']);
|
|
70
|
+
// Refuse http/file/data — only https. The skill content goes
|
|
71
|
+
// straight into the system prompt, so source authenticity matters.
|
|
72
|
+
if (!url.startsWith('https://')) {
|
|
73
|
+
console.error('skills install --from-url requires an https:// URL');
|
|
74
|
+
process.exit(2);
|
|
75
|
+
}
|
|
76
|
+
const fetchFn = globalThis.fetch;
|
|
77
|
+
if (!fetchFn) { console.error('fetch is not available in this Node runtime'); process.exit(1); }
|
|
78
|
+
// Configurable max size — protect against pathological responses
|
|
79
|
+
// that would balloon the prompt and the disk file. 1 MiB cap.
|
|
80
|
+
const MAX_BYTES = 1_048_576;
|
|
81
|
+
try {
|
|
82
|
+
const res = await fetchFn(url, { redirect: 'follow' });
|
|
83
|
+
if (!res.ok) { console.error(`fetch ${url} → ${res.status}`); process.exit(1); }
|
|
84
|
+
// Stream the body so we can stop at the cap rather than loading
|
|
85
|
+
// an arbitrarily large response into memory.
|
|
86
|
+
const reader = res.body?.getReader?.();
|
|
87
|
+
if (!reader) { content = await res.text(); }
|
|
88
|
+
else {
|
|
89
|
+
const chunks = [];
|
|
90
|
+
let total = 0;
|
|
91
|
+
while (true) {
|
|
92
|
+
const { value, done } = await reader.read();
|
|
93
|
+
if (done) break;
|
|
94
|
+
total += value.length;
|
|
95
|
+
if (total > MAX_BYTES) {
|
|
96
|
+
console.error(`skills install: response exceeds ${MAX_BYTES} bytes; refusing`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
chunks.push(value);
|
|
100
|
+
}
|
|
101
|
+
content = new TextDecoder('utf-8', { fatal: false }).decode(Buffer.concat(chunks.map(c => Buffer.from(c))));
|
|
102
|
+
}
|
|
103
|
+
} catch (e) {
|
|
104
|
+
console.error(`skills install fetch failed: ${e?.message || e}`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
} else if (flags.from) {
|
|
108
|
+
content = fs.readFileSync(flags.from, 'utf8');
|
|
109
|
+
} else {
|
|
110
|
+
content = await new Promise(resolve => {
|
|
111
|
+
let buf = '';
|
|
112
|
+
process.stdin.setEncoding('utf8');
|
|
113
|
+
process.stdin.on('data', d => { buf += d; });
|
|
114
|
+
process.stdin.on('end', () => resolve(buf));
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const written = skillsMod.installSkill(name, content, cfgDir);
|
|
118
|
+
console.log(JSON.stringify({ ok: true, name, path: written, bytes: content.length }));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
case 'remove': {
|
|
122
|
+
const name = positional[0];
|
|
123
|
+
if (!name) { console.error('Usage: lazyclaw skills remove <name>'); process.exit(2); }
|
|
124
|
+
skillsMod.removeSkill(name, cfgDir);
|
|
125
|
+
console.log(JSON.stringify({ ok: true, removed: name }));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
case 'search': {
|
|
129
|
+
// Mirror of `lazyclaw sessions search` — case-insensitive substring
|
|
130
|
+
// by default, --regex for pattern mode. Returns per-skill match
|
|
131
|
+
// count + first-excerpt window (40 chars before/after match).
|
|
132
|
+
// The skill body IS markdown so users typically search for terms
|
|
133
|
+
// mentioned in instructions or examples.
|
|
134
|
+
const query = positional[0];
|
|
135
|
+
if (!query) { console.error('Usage: lazyclaw skills search <query> [--regex]'); process.exit(2); }
|
|
136
|
+
const useRegex = !!flags.regex;
|
|
137
|
+
let matcher;
|
|
138
|
+
if (useRegex) {
|
|
139
|
+
try { matcher = new RegExp(query, 'i'); }
|
|
140
|
+
catch (e) { console.error(`invalid regex: ${e.message}`); process.exit(2); }
|
|
141
|
+
} else {
|
|
142
|
+
const q = query.toLowerCase();
|
|
143
|
+
matcher = { test: (s) => String(s).toLowerCase().includes(q) };
|
|
144
|
+
}
|
|
145
|
+
const items = skillsMod.listSkills(cfgDir);
|
|
146
|
+
const matches = [];
|
|
147
|
+
for (const s of items) {
|
|
148
|
+
let body;
|
|
149
|
+
try { body = skillsMod.loadSkill(s.name, cfgDir); }
|
|
150
|
+
catch { continue; } // file may have been removed mid-listing
|
|
151
|
+
// Count matches across the whole body, not per-line. For a
|
|
152
|
+
// skill body that's a few KB this is plenty fast and the count
|
|
153
|
+
// matches the user's intuition of "how many times does it
|
|
154
|
+
// mention X."
|
|
155
|
+
let matchCount = 0;
|
|
156
|
+
let firstExcerpt = null;
|
|
157
|
+
if (useRegex) {
|
|
158
|
+
// Re-anchor the regex with /gi so we can iterate; the original
|
|
159
|
+
// matcher was /i for boolean test() above. Rebuild here.
|
|
160
|
+
const gFlag = new RegExp(query, 'gi');
|
|
161
|
+
for (const m of body.matchAll(gFlag)) {
|
|
162
|
+
matchCount++;
|
|
163
|
+
if (firstExcerpt === null) {
|
|
164
|
+
const pos = m.index ?? 0;
|
|
165
|
+
const start = Math.max(0, pos - 40);
|
|
166
|
+
const end = Math.min(body.length, pos + m[0].length + 40);
|
|
167
|
+
firstExcerpt = (start > 0 ? '…' : '') + body.slice(start, end) + (end < body.length ? '…' : '');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
const lower = body.toLowerCase();
|
|
172
|
+
const q = query.toLowerCase();
|
|
173
|
+
let pos = 0;
|
|
174
|
+
while (true) {
|
|
175
|
+
const i = lower.indexOf(q, pos);
|
|
176
|
+
if (i < 0) break;
|
|
177
|
+
matchCount++;
|
|
178
|
+
if (firstExcerpt === null) {
|
|
179
|
+
const start = Math.max(0, i - 40);
|
|
180
|
+
const end = Math.min(body.length, i + q.length + 40);
|
|
181
|
+
firstExcerpt = (start > 0 ? '…' : '') + body.slice(start, end) + (end < body.length ? '…' : '');
|
|
182
|
+
}
|
|
183
|
+
pos = i + q.length;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (matchCount > 0) {
|
|
187
|
+
matches.push({
|
|
188
|
+
name: s.name,
|
|
189
|
+
bytes: s.bytes,
|
|
190
|
+
matchCount,
|
|
191
|
+
excerpt: firstExcerpt,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
console.log(JSON.stringify({ query, regex: useRegex, matches }, null, 2));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
case 'curate': {
|
|
199
|
+
// Lifecycle sweep: agent-authored skills unused >90d move into
|
|
200
|
+
// skills/.archive/ (recoverable). Human-authored skills are never
|
|
201
|
+
// moved. The real clock is injected here; the module stays pure.
|
|
202
|
+
const curator = await import('../skills_curator.mjs');
|
|
203
|
+
const r = curator.curate(cfgDir, Date.now());
|
|
204
|
+
console.log(JSON.stringify(r, null, 2));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
case 'classify': {
|
|
208
|
+
const name = positional[0];
|
|
209
|
+
if (!name) { console.error('Usage: lazyclaw skills classify <name>'); process.exit(2); }
|
|
210
|
+
const curator = await import('../skills_curator.mjs');
|
|
211
|
+
console.log(JSON.stringify({ name, state: curator.classify(name, cfgDir, Date.now()), usage: curator.usageOf(name, cfgDir) }, null, 2));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
default:
|
|
215
|
+
console.error('Usage: lazyclaw skills <list|show <name>|install <name> [--from path]|remove <name>|search <query> [--regex]|curate|classify <name>>');
|
|
216
|
+
process.exit(2);
|
|
217
|
+
}
|
|
218
|
+
}
|