smolerclaw 1.0.0 → 1.0.2

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 (72) hide show
  1. package/dist/README.md +159 -0
  2. package/package.json +11 -3
  3. package/.github/workflows/ci.yml +0 -30
  4. package/.github/workflows/release.yml +0 -67
  5. package/bun.lock +0 -33
  6. package/install.ps1 +0 -119
  7. package/skills/business.md +0 -77
  8. package/skills/default.md +0 -77
  9. package/src/ansi.ts +0 -164
  10. package/src/approval.ts +0 -74
  11. package/src/auth.ts +0 -125
  12. package/src/briefing.ts +0 -52
  13. package/src/claude.ts +0 -267
  14. package/src/cli.ts +0 -137
  15. package/src/clipboard.ts +0 -27
  16. package/src/config.ts +0 -87
  17. package/src/context-window.ts +0 -190
  18. package/src/context.ts +0 -125
  19. package/src/decisions.ts +0 -122
  20. package/src/email.ts +0 -123
  21. package/src/errors.ts +0 -78
  22. package/src/export.ts +0 -82
  23. package/src/finance.ts +0 -148
  24. package/src/git.ts +0 -62
  25. package/src/history.ts +0 -100
  26. package/src/images.ts +0 -68
  27. package/src/index.ts +0 -1431
  28. package/src/investigate.ts +0 -415
  29. package/src/markdown.ts +0 -125
  30. package/src/memos.ts +0 -191
  31. package/src/models.ts +0 -94
  32. package/src/monitor.ts +0 -169
  33. package/src/morning.ts +0 -108
  34. package/src/news.ts +0 -329
  35. package/src/openai-provider.ts +0 -127
  36. package/src/people.ts +0 -472
  37. package/src/personas.ts +0 -99
  38. package/src/platform.ts +0 -84
  39. package/src/plugins.ts +0 -125
  40. package/src/pomodoro.ts +0 -169
  41. package/src/providers.ts +0 -70
  42. package/src/retry.ts +0 -108
  43. package/src/session.ts +0 -128
  44. package/src/skills.ts +0 -102
  45. package/src/tasks.ts +0 -418
  46. package/src/tokens.ts +0 -102
  47. package/src/tool-safety.ts +0 -100
  48. package/src/tools.ts +0 -1479
  49. package/src/tui.ts +0 -693
  50. package/src/types.ts +0 -55
  51. package/src/undo.ts +0 -83
  52. package/src/windows.ts +0 -299
  53. package/src/workflows.ts +0 -197
  54. package/tests/ansi.test.ts +0 -58
  55. package/tests/approval.test.ts +0 -43
  56. package/tests/briefing.test.ts +0 -10
  57. package/tests/cli.test.ts +0 -53
  58. package/tests/context-window.test.ts +0 -83
  59. package/tests/images.test.ts +0 -28
  60. package/tests/memos.test.ts +0 -116
  61. package/tests/models.test.ts +0 -34
  62. package/tests/news.test.ts +0 -13
  63. package/tests/path-guard.test.ts +0 -37
  64. package/tests/people.test.ts +0 -204
  65. package/tests/skills.test.ts +0 -35
  66. package/tests/ssrf.test.ts +0 -80
  67. package/tests/tasks.test.ts +0 -152
  68. package/tests/tokens.test.ts +0 -44
  69. package/tests/tool-safety.test.ts +0 -55
  70. package/tests/windows-security.test.ts +0 -59
  71. package/tests/windows.test.ts +0 -20
  72. package/tsconfig.json +0 -19
@@ -1,415 +0,0 @@
1
- /**
2
- * Investigation system — collect evidence, analyze, and produce structured reports.
3
- *
4
- * Types of investigation:
5
- * bug — malfunction diagnosis
6
- * feature — material gathering for feature construction
7
- * test — collecting scenarios and test material
8
- * audit — code/system audit
9
- * incident — runtime/production incident
10
- */
11
-
12
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
13
- import { join, resolve, relative } from 'node:path'
14
-
15
- // ─── Types ──────────────────────────────────────────────────
16
-
17
- export type InvestigationType = 'bug' | 'feature' | 'test' | 'audit' | 'incident'
18
- export type InvestigationStatus = 'aberta' | 'em_andamento' | 'concluida' | 'arquivada'
19
- export type EvidenceSource = 'file' | 'command' | 'log' | 'diff' | 'url' | 'observation'
20
-
21
- export interface Evidence {
22
- id: string
23
- source: EvidenceSource
24
- label: string // short description
25
- content: string // the actual evidence data
26
- path?: string // file path or URL (when applicable)
27
- timestamp: string
28
- }
29
-
30
- export interface Finding {
31
- id: string
32
- severity: 'critical' | 'high' | 'medium' | 'low' | 'info'
33
- title: string
34
- description: string
35
- evidence_ids: string[] // references to evidence that supports this finding
36
- timestamp: string
37
- }
38
-
39
- export interface Investigation {
40
- id: string
41
- title: string
42
- type: InvestigationType
43
- status: InvestigationStatus
44
- hypothesis?: string // initial theory to test
45
- tags: string[]
46
- evidence: Evidence[]
47
- findings: Finding[]
48
- summary?: string // final summary when closed
49
- recommendations?: string // action items when closed
50
- created: string
51
- updated: string
52
- }
53
-
54
- // ─── Storage ────────────────────────────────────────────────
55
-
56
- let _dataDir = ''
57
- let _investigations: Investigation[] = []
58
-
59
- const DATA_FILE = () => join(_dataDir, 'investigations.json')
60
-
61
- function save(): void {
62
- writeFileSync(DATA_FILE(), JSON.stringify(_investigations, null, 2))
63
- }
64
-
65
- function load(): void {
66
- const file = DATA_FILE()
67
- if (!existsSync(file)) { _investigations = []; return }
68
- try { _investigations = JSON.parse(readFileSync(file, 'utf-8')) }
69
- catch { _investigations = [] }
70
- }
71
-
72
- // ─── Init ───────────────────────────────────────────────────
73
-
74
- export function initInvestigations(dataDir: string): void {
75
- _dataDir = dataDir
76
- if (!existsSync(dataDir)) mkdirSync(dataDir, { recursive: true })
77
- load()
78
- }
79
-
80
- // ─── Operations ─────────────────────────────────────────────
81
-
82
- export function openInvestigation(
83
- title: string,
84
- type: InvestigationType,
85
- hypothesis?: string,
86
- tags: string[] = [],
87
- ): Investigation {
88
- const now = new Date().toISOString()
89
- const inv: Investigation = {
90
- id: genId(),
91
- title: title.trim(),
92
- type,
93
- status: 'aberta',
94
- hypothesis: hypothesis?.trim(),
95
- tags: tags.map((t) => t.toLowerCase()),
96
- evidence: [],
97
- findings: [],
98
- created: now,
99
- updated: now,
100
- }
101
- _investigations = [..._investigations, inv]
102
- save()
103
- return inv
104
- }
105
-
106
- export function collectEvidence(
107
- investigationRef: string,
108
- source: EvidenceSource,
109
- label: string,
110
- content: string,
111
- path?: string,
112
- ): Evidence | null {
113
- const inv = findInvestigation(investigationRef)
114
- if (!inv) return null
115
-
116
- const ev: Evidence = {
117
- id: genId(),
118
- source,
119
- label: label.trim(),
120
- content: content.trim(),
121
- path: path?.trim(),
122
- timestamp: new Date().toISOString(),
123
- }
124
-
125
- const updated: Investigation = {
126
- ...inv,
127
- evidence: [...inv.evidence, ev],
128
- status: inv.status === 'aberta' ? 'em_andamento' : inv.status,
129
- updated: new Date().toISOString(),
130
- }
131
- _investigations = _investigations.map((i) => i.id === inv.id ? updated : i)
132
- save()
133
- return ev
134
- }
135
-
136
- export function addFinding(
137
- investigationRef: string,
138
- severity: Finding['severity'],
139
- title: string,
140
- description: string,
141
- evidenceIds: string[] = [],
142
- ): Finding | null {
143
- const inv = findInvestigation(investigationRef)
144
- if (!inv) return null
145
-
146
- // Validate evidence IDs exist
147
- const validIds = evidenceIds.filter((eid) =>
148
- inv.evidence.some((e) => e.id === eid),
149
- )
150
-
151
- const finding: Finding = {
152
- id: genId(),
153
- severity,
154
- title: title.trim(),
155
- description: description.trim(),
156
- evidence_ids: validIds,
157
- timestamp: new Date().toISOString(),
158
- }
159
-
160
- const updated: Investigation = {
161
- ...inv,
162
- findings: [...inv.findings, finding],
163
- updated: new Date().toISOString(),
164
- }
165
- _investigations = _investigations.map((i) => i.id === inv.id ? updated : i)
166
- save()
167
- return finding
168
- }
169
-
170
- export function closeInvestigation(
171
- investigationRef: string,
172
- summary: string,
173
- recommendations?: string,
174
- ): Investigation | null {
175
- const inv = findInvestigation(investigationRef)
176
- if (!inv) return null
177
-
178
- const updated: Investigation = {
179
- ...inv,
180
- status: 'concluida',
181
- summary: summary.trim(),
182
- recommendations: recommendations?.trim(),
183
- updated: new Date().toISOString(),
184
- }
185
- _investigations = _investigations.map((i) => i.id === inv.id ? updated : i)
186
- save()
187
- return updated
188
- }
189
-
190
- export function getInvestigation(ref: string): Investigation | null {
191
- return findInvestigation(ref)
192
- }
193
-
194
- export function listInvestigations(
195
- status?: InvestigationStatus,
196
- type?: InvestigationType,
197
- limit = 20,
198
- ): Investigation[] {
199
- let filtered = [..._investigations]
200
- if (status) filtered = filtered.filter((i) => i.status === status)
201
- if (type) filtered = filtered.filter((i) => i.type === type)
202
- return filtered
203
- .sort((a, b) => new Date(b.updated).getTime() - new Date(a.updated).getTime())
204
- .slice(0, limit)
205
- }
206
-
207
- export function searchInvestigations(query: string): Investigation[] {
208
- const lower = query.toLowerCase()
209
- return _investigations.filter((inv) =>
210
- inv.title.toLowerCase().includes(lower) ||
211
- inv.hypothesis?.toLowerCase().includes(lower) ||
212
- inv.tags.some((t) => t.includes(lower)) ||
213
- inv.findings.some((f) => f.title.toLowerCase().includes(lower)) ||
214
- inv.summary?.toLowerCase().includes(lower),
215
- ).sort((a, b) => new Date(b.updated).getTime() - new Date(a.updated).getTime())
216
- }
217
-
218
- // ─── Report Generation ──────────────────────────────────────
219
-
220
- export function generateReport(investigationRef: string): string | null {
221
- const inv = findInvestigation(investigationRef)
222
- if (!inv) return null
223
-
224
- const lines: string[] = []
225
- const date = (iso: string) => new Date(iso).toLocaleDateString('pt-BR', {
226
- day: '2-digit', month: '2-digit', year: 'numeric',
227
- hour: '2-digit', minute: '2-digit',
228
- })
229
- const typeLabels: Record<InvestigationType, string> = {
230
- bug: 'Bug / Mal funcionamento',
231
- feature: 'Construcao de funcionalidade',
232
- test: 'Material para testes',
233
- audit: 'Auditoria',
234
- incident: 'Incidente',
235
- }
236
- const severityOrder: Record<string, number> = {
237
- critical: 0, high: 1, medium: 2, low: 3, info: 4,
238
- }
239
-
240
- lines.push(`# Investigacao: ${inv.title}`)
241
- lines.push('')
242
- lines.push(`**Tipo:** ${typeLabels[inv.type]}`)
243
- lines.push(`**Status:** ${inv.status}`)
244
- lines.push(`**Abertura:** ${date(inv.created)}`)
245
- lines.push(`**Ultima atualizacao:** ${date(inv.updated)}`)
246
- if (inv.tags.length) lines.push(`**Tags:** ${inv.tags.join(', ')}`)
247
- lines.push(`**ID:** ${inv.id}`)
248
-
249
- if (inv.hypothesis) {
250
- lines.push('')
251
- lines.push(`## Hipotese`)
252
- lines.push(inv.hypothesis)
253
- }
254
-
255
- // Evidence
256
- if (inv.evidence.length > 0) {
257
- lines.push('')
258
- lines.push(`## Evidencias (${inv.evidence.length})`)
259
- for (const ev of inv.evidence) {
260
- const ts = date(ev.timestamp)
261
- lines.push('')
262
- lines.push(`### [${ev.id}] ${ev.label}`)
263
- lines.push(`- Fonte: ${ev.source}${ev.path ? ` (${ev.path})` : ''}`)
264
- lines.push(`- Coletada: ${ts}`)
265
- // Show content, truncated if very large
266
- const content = ev.content.length > 2000
267
- ? ev.content.slice(0, 2000) + '\n... (truncado)'
268
- : ev.content
269
- lines.push('```')
270
- lines.push(content)
271
- lines.push('```')
272
- }
273
- }
274
-
275
- // Findings
276
- if (inv.findings.length > 0) {
277
- const sorted = [...inv.findings].sort((a, b) =>
278
- (severityOrder[a.severity] ?? 4) - (severityOrder[b.severity] ?? 4),
279
- )
280
- lines.push('')
281
- lines.push(`## Conclusoes (${inv.findings.length})`)
282
- for (const f of sorted) {
283
- const badge = severityBadge(f.severity)
284
- lines.push('')
285
- lines.push(`### ${badge} ${f.title}`)
286
- lines.push(f.description)
287
- if (f.evidence_ids.length > 0) {
288
- lines.push(`- Evidencias: ${f.evidence_ids.join(', ')}`)
289
- }
290
- }
291
- }
292
-
293
- // Summary & recommendations
294
- if (inv.summary) {
295
- lines.push('')
296
- lines.push(`## Resumo`)
297
- lines.push(inv.summary)
298
- }
299
- if (inv.recommendations) {
300
- lines.push('')
301
- lines.push(`## Recomendacoes`)
302
- lines.push(inv.recommendations)
303
- }
304
-
305
- return lines.join('\n')
306
- }
307
-
308
- // ─── Formatting (for TUI display) ──────────────────────────
309
-
310
- export function formatInvestigationList(investigations: Investigation[]): string {
311
- if (investigations.length === 0) return 'Nenhuma investigacao encontrada.'
312
-
313
- const lines = investigations.map((inv) => {
314
- const date = new Date(inv.updated).toLocaleDateString('pt-BR', {
315
- day: '2-digit', month: '2-digit',
316
- })
317
- const status = statusBadge(inv.status)
318
- const evCount = inv.evidence.length
319
- const fCount = inv.findings.length
320
- const tags = inv.tags.length > 0 ? ` [${inv.tags.join(', ')}]` : ''
321
- return ` ${status} [${date}] ${inv.title} (${inv.type}) — ${evCount} ev, ${fCount} concl${tags} {${inv.id}}`
322
- })
323
-
324
- return `Investigacoes (${investigations.length}):\n${lines.join('\n')}`
325
- }
326
-
327
- export function formatInvestigationDetail(inv: Investigation): string {
328
- const date = (iso: string) => new Date(iso).toLocaleDateString('pt-BR')
329
- const status = statusBadge(inv.status)
330
- const lines = [
331
- `--- Investigacao {${inv.id}} ---`,
332
- `Titulo: ${inv.title}`,
333
- `Tipo: ${inv.type} | Status: ${status}`,
334
- `Criada: ${date(inv.created)} | Atualizada: ${date(inv.updated)}`,
335
- ]
336
-
337
- if (inv.hypothesis) lines.push(`Hipotese: ${inv.hypothesis}`)
338
- if (inv.tags.length) lines.push(`Tags: ${inv.tags.join(', ')}`)
339
-
340
- lines.push(`\nEvidencias: ${inv.evidence.length}`)
341
- for (const ev of inv.evidence.slice(-5)) {
342
- const preview = ev.content.slice(0, 80).replace(/\n/g, ' ')
343
- lines.push(` [${ev.id}] ${ev.source}: ${ev.label} — "${preview}..."`)
344
- }
345
- if (inv.evidence.length > 5) {
346
- lines.push(` ... (${inv.evidence.length - 5} mais)`)
347
- }
348
-
349
- lines.push(`\nConclusoes: ${inv.findings.length}`)
350
- for (const f of inv.findings) {
351
- lines.push(` ${severityBadge(f.severity)} ${f.title}`)
352
- }
353
-
354
- if (inv.summary) lines.push(`\nResumo: ${inv.summary}`)
355
- if (inv.recommendations) lines.push(`Recomendacoes: ${inv.recommendations}`)
356
-
357
- return lines.join('\n')
358
- }
359
-
360
- export function formatEvidenceDetail(ev: Evidence): string {
361
- const ts = new Date(ev.timestamp).toLocaleDateString('pt-BR', {
362
- day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit',
363
- })
364
- const lines = [
365
- `--- Evidencia {${ev.id}} ---`,
366
- `Label: ${ev.label}`,
367
- `Fonte: ${ev.source}${ev.path ? ` (${ev.path})` : ''}`,
368
- `Coletada: ${ts}`,
369
- '',
370
- ev.content.length > 3000
371
- ? ev.content.slice(0, 3000) + '\n... (truncado)'
372
- : ev.content,
373
- ]
374
- return lines.join('\n')
375
- }
376
-
377
- // ─── Helpers ────────────────────────────────────────────────
378
-
379
- function findInvestigation(ref: string): Investigation | null {
380
- const lower = ref.toLowerCase().trim()
381
- // Exact ID match
382
- const byId = _investigations.find((i) => i.id === lower)
383
- if (byId) return byId
384
- // Partial title match (most recent)
385
- const byTitle = _investigations
386
- .filter((i) => i.title.toLowerCase().includes(lower))
387
- .sort((a, b) => new Date(b.updated).getTime() - new Date(a.updated).getTime())
388
- return byTitle[0] || null
389
- }
390
-
391
- function statusBadge(status: InvestigationStatus): string {
392
- switch (status) {
393
- case 'aberta': return '○'
394
- case 'em_andamento': return '◉'
395
- case 'concluida': return '●'
396
- case 'arquivada': return '◌'
397
- }
398
- }
399
-
400
- function severityBadge(severity: Finding['severity']): string {
401
- switch (severity) {
402
- case 'critical': return '[CRITICO]'
403
- case 'high': return '[ALTO]'
404
- case 'medium': return '[MEDIO]'
405
- case 'low': return '[BAIXO]'
406
- case 'info': return '[INFO]'
407
- }
408
- }
409
-
410
- function genId(): string {
411
- const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
412
- let id = ''
413
- for (let i = 0; i < 6; i++) id += chars[Math.floor(Math.random() * chars.length)]
414
- return id
415
- }
package/src/markdown.ts DELETED
@@ -1,125 +0,0 @@
1
- import { A, C } from './ansi'
2
-
3
- /**
4
- * Render markdown text to ANSI-formatted terminal lines.
5
- * Handles: headers, bold, italic, inline code, code blocks,
6
- * bullet/numbered lists, blockquotes, and links.
7
- */
8
- export function renderMarkdown(text: string): string[] {
9
- const lines = text.split('\n')
10
- const output: string[] = []
11
- let inCodeBlock = false
12
- let codeLang = ''
13
-
14
- for (let i = 0; i < lines.length; i++) {
15
- const line = lines[i]
16
-
17
- // ── Code block toggle ──
18
- if (line.trimStart().startsWith('```')) {
19
- if (!inCodeBlock) {
20
- inCodeBlock = true
21
- codeLang = line.trimStart().slice(3).trim()
22
- const label = codeLang ? ` ${codeLang}` : ''
23
- output.push(` ${A.dim}┌──${label}${'─'.repeat(Math.max(1, 40 - label.length))}${A.reset}`)
24
- } else {
25
- inCodeBlock = false
26
- codeLang = ''
27
- output.push(` ${A.dim}└${'─'.repeat(42)}${A.reset}`)
28
- }
29
- continue
30
- }
31
-
32
- // ── Inside code block ──
33
- if (inCodeBlock) {
34
- output.push(` ${A.dim}│${A.reset} ${C.code}${line}${A.reset}`)
35
- continue
36
- }
37
-
38
- // ── Blank line ──
39
- if (!line.trim()) {
40
- output.push('')
41
- continue
42
- }
43
-
44
- // ── Headers ──
45
- const headerMatch = line.match(/^(#{1,3})\s+(.+)/)
46
- if (headerMatch) {
47
- const level = headerMatch[1].length
48
- const text = headerMatch[2]
49
- const prefix = level === 1 ? '━' : level === 2 ? '─' : '·'
50
- output.push(` ${C.heading}${A.bold}${prefix} ${renderInline(text)}${A.reset}`)
51
- continue
52
- }
53
-
54
- // ── Blockquote ──
55
- if (line.trimStart().startsWith('>')) {
56
- const content = line.replace(/^\s*>\s?/, '')
57
- output.push(` ${C.quote}│ ${renderInline(content)}${A.reset}`)
58
- continue
59
- }
60
-
61
- // ── Bullet list ──
62
- const bulletMatch = line.match(/^(\s*)([-*+])\s+(.+)/)
63
- if (bulletMatch) {
64
- const indent = Math.floor(bulletMatch[1].length / 2)
65
- const content = bulletMatch[3]
66
- const pad = ' '.repeat(indent)
67
- output.push(` ${pad}${A.dim}•${A.reset} ${renderInline(content)}`)
68
- continue
69
- }
70
-
71
- // ── Numbered list ──
72
- const numMatch = line.match(/^(\s*)(\d+)[.)]\s+(.+)/)
73
- if (numMatch) {
74
- const indent = Math.floor(numMatch[1].length / 2)
75
- const num = numMatch[2]
76
- const content = numMatch[3]
77
- const pad = ' '.repeat(indent)
78
- output.push(` ${pad}${A.dim}${num}.${A.reset} ${renderInline(content)}`)
79
- continue
80
- }
81
-
82
- // ── Horizontal rule ──
83
- if (/^[-*_]{3,}\s*$/.test(line.trim())) {
84
- output.push(` ${A.dim}${'─'.repeat(40)}${A.reset}`)
85
- continue
86
- }
87
-
88
- // ── Regular paragraph line ──
89
- output.push(` ${renderInline(line)}`)
90
- }
91
-
92
- // Close unclosed code block
93
- if (inCodeBlock) {
94
- output.push(` ${A.dim}└${'─'.repeat(42)}${A.reset}`)
95
- }
96
-
97
- return output
98
- }
99
-
100
- /**
101
- * Apply inline markdown formatting (bold, italic, code, links).
102
- */
103
- function renderInline(text: string): string {
104
- let result = text
105
-
106
- // Inline code (must come before bold/italic to avoid conflicts)
107
- result = result.replace(/`([^`]+)`/g, `${A.inv} $1 ${A.reset}`)
108
-
109
- // Bold + italic
110
- result = result.replace(/\*\*\*(.+?)\*\*\*/g, `${A.bold}${A.italic}$1${A.reset}`)
111
-
112
- // Bold
113
- result = result.replace(/\*\*(.+?)\*\*/g, `${A.bold}$1${A.reset}`)
114
- result = result.replace(/__(.+?)__/g, `${A.bold}$1${A.reset}`)
115
-
116
- // Italic
117
- result = result.replace(/\*(.+?)\*/g, `${A.italic}$1${A.reset}`)
118
- result = result.replace(/_(.+?)_/g, `${A.italic}$1${A.reset}`)
119
-
120
- // Links [text](url)
121
- result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g,
122
- `${A.underline}$1${A.reset} ${C.link}($2)${A.reset}`)
123
-
124
- return result
125
- }