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.
- package/dist/README.md +159 -0
- package/package.json +11 -3
- package/.github/workflows/ci.yml +0 -30
- package/.github/workflows/release.yml +0 -67
- package/bun.lock +0 -33
- package/install.ps1 +0 -119
- package/skills/business.md +0 -77
- package/skills/default.md +0 -77
- package/src/ansi.ts +0 -164
- package/src/approval.ts +0 -74
- package/src/auth.ts +0 -125
- package/src/briefing.ts +0 -52
- package/src/claude.ts +0 -267
- package/src/cli.ts +0 -137
- package/src/clipboard.ts +0 -27
- package/src/config.ts +0 -87
- package/src/context-window.ts +0 -190
- package/src/context.ts +0 -125
- package/src/decisions.ts +0 -122
- package/src/email.ts +0 -123
- package/src/errors.ts +0 -78
- package/src/export.ts +0 -82
- package/src/finance.ts +0 -148
- package/src/git.ts +0 -62
- package/src/history.ts +0 -100
- package/src/images.ts +0 -68
- package/src/index.ts +0 -1431
- package/src/investigate.ts +0 -415
- package/src/markdown.ts +0 -125
- package/src/memos.ts +0 -191
- package/src/models.ts +0 -94
- package/src/monitor.ts +0 -169
- package/src/morning.ts +0 -108
- package/src/news.ts +0 -329
- package/src/openai-provider.ts +0 -127
- package/src/people.ts +0 -472
- package/src/personas.ts +0 -99
- package/src/platform.ts +0 -84
- package/src/plugins.ts +0 -125
- package/src/pomodoro.ts +0 -169
- package/src/providers.ts +0 -70
- package/src/retry.ts +0 -108
- package/src/session.ts +0 -128
- package/src/skills.ts +0 -102
- package/src/tasks.ts +0 -418
- package/src/tokens.ts +0 -102
- package/src/tool-safety.ts +0 -100
- package/src/tools.ts +0 -1479
- package/src/tui.ts +0 -693
- package/src/types.ts +0 -55
- package/src/undo.ts +0 -83
- package/src/windows.ts +0 -299
- package/src/workflows.ts +0 -197
- package/tests/ansi.test.ts +0 -58
- package/tests/approval.test.ts +0 -43
- package/tests/briefing.test.ts +0 -10
- package/tests/cli.test.ts +0 -53
- package/tests/context-window.test.ts +0 -83
- package/tests/images.test.ts +0 -28
- package/tests/memos.test.ts +0 -116
- package/tests/models.test.ts +0 -34
- package/tests/news.test.ts +0 -13
- package/tests/path-guard.test.ts +0 -37
- package/tests/people.test.ts +0 -204
- package/tests/skills.test.ts +0 -35
- package/tests/ssrf.test.ts +0 -80
- package/tests/tasks.test.ts +0 -152
- package/tests/tokens.test.ts +0 -44
- package/tests/tool-safety.test.ts +0 -55
- package/tests/windows-security.test.ts +0 -59
- package/tests/windows.test.ts +0 -20
- package/tsconfig.json +0 -19
package/src/people.ts
DELETED
|
@@ -1,472 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* People management — personal CRM for team, family, and contacts.
|
|
3
|
-
* Stores people, interaction logs, delegated tasks, and follow-up reminders.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
7
|
-
import { join } from 'node:path'
|
|
8
|
-
|
|
9
|
-
// ─── Types ──────────────────────────────────────────────────
|
|
10
|
-
|
|
11
|
-
export type PersonGroup = 'equipe' | 'familia' | 'contato'
|
|
12
|
-
|
|
13
|
-
export interface Person {
|
|
14
|
-
id: string
|
|
15
|
-
name: string
|
|
16
|
-
group: PersonGroup
|
|
17
|
-
role?: string // "dev frontend", "esposa", "filho", "fornecedor"
|
|
18
|
-
contact?: string // phone, email, etc.
|
|
19
|
-
notes?: string // free-text notes
|
|
20
|
-
createdAt: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface Interaction {
|
|
24
|
-
id: string
|
|
25
|
-
personId: string
|
|
26
|
-
date: string // ISO date
|
|
27
|
-
type: InteractionType
|
|
28
|
-
summary: string
|
|
29
|
-
followUpDate?: string // ISO date — when to follow up
|
|
30
|
-
followUpDone: boolean
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export type InteractionType = 'conversa' | 'email' | 'reuniao' | 'ligacao' | 'mensagem' | 'delegacao' | 'entrega' | 'outro'
|
|
34
|
-
|
|
35
|
-
export interface Delegation {
|
|
36
|
-
id: string
|
|
37
|
-
personId: string
|
|
38
|
-
task: string
|
|
39
|
-
assignedAt: string
|
|
40
|
-
dueDate?: string // ISO date
|
|
41
|
-
status: 'pendente' | 'em_andamento' | 'concluido' | 'atrasado'
|
|
42
|
-
notes?: string
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface PeopleData {
|
|
46
|
-
people: Person[]
|
|
47
|
-
interactions: Interaction[]
|
|
48
|
-
delegations: Delegation[]
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ─── Storage ────────────────────────────────────────────────
|
|
52
|
-
|
|
53
|
-
let _dataDir = ''
|
|
54
|
-
let _data: PeopleData = { people: [], interactions: [], delegations: [] }
|
|
55
|
-
|
|
56
|
-
const DATA_FILE = () => join(_dataDir, 'people.json')
|
|
57
|
-
|
|
58
|
-
function save(): void {
|
|
59
|
-
writeFileSync(DATA_FILE(), JSON.stringify(_data, null, 2))
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function load(): void {
|
|
63
|
-
const file = DATA_FILE()
|
|
64
|
-
if (!existsSync(file)) {
|
|
65
|
-
_data = { people: [], interactions: [], delegations: [] }
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
try {
|
|
69
|
-
const raw = JSON.parse(readFileSync(file, 'utf-8'))
|
|
70
|
-
_data = {
|
|
71
|
-
people: raw.people || [],
|
|
72
|
-
interactions: raw.interactions || [],
|
|
73
|
-
delegations: raw.delegations || [],
|
|
74
|
-
}
|
|
75
|
-
} catch {
|
|
76
|
-
_data = { people: [], interactions: [], delegations: [] }
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ─── Init ───────────────────────────────────────────────────
|
|
81
|
-
|
|
82
|
-
export function initPeople(dataDir: string): void {
|
|
83
|
-
_dataDir = dataDir
|
|
84
|
-
if (!existsSync(dataDir)) mkdirSync(dataDir, { recursive: true })
|
|
85
|
-
load()
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ─── People CRUD ────────────────────────────────────────────
|
|
89
|
-
|
|
90
|
-
export function addPerson(name: string, group: PersonGroup, role?: string, contact?: string): Person {
|
|
91
|
-
const person: Person = {
|
|
92
|
-
id: genId(),
|
|
93
|
-
name: name.trim(),
|
|
94
|
-
group,
|
|
95
|
-
role: role?.trim(),
|
|
96
|
-
contact: contact?.trim(),
|
|
97
|
-
createdAt: new Date().toISOString(),
|
|
98
|
-
}
|
|
99
|
-
_data = { ..._data, people: [..._data.people, person] }
|
|
100
|
-
save()
|
|
101
|
-
return person
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function updatePerson(idOrName: string, updates: Partial<Pick<Person, 'name' | 'group' | 'role' | 'contact' | 'notes'>>): Person | null {
|
|
105
|
-
const person = findPerson(idOrName)
|
|
106
|
-
if (!person) return null
|
|
107
|
-
|
|
108
|
-
_data = {
|
|
109
|
-
..._data,
|
|
110
|
-
people: _data.people.map((p) =>
|
|
111
|
-
p.id === person.id ? { ...p, ...stripUndefined(updates) } : p,
|
|
112
|
-
),
|
|
113
|
-
}
|
|
114
|
-
save()
|
|
115
|
-
return _data.people.find((p) => p.id === person.id) || null
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function removePerson(idOrName: string): boolean {
|
|
119
|
-
const person = findPerson(idOrName)
|
|
120
|
-
if (!person) return false
|
|
121
|
-
|
|
122
|
-
_data = {
|
|
123
|
-
..._data,
|
|
124
|
-
people: _data.people.filter((p) => p.id !== person.id),
|
|
125
|
-
interactions: _data.interactions.filter((i) => i.personId !== person.id),
|
|
126
|
-
delegations: _data.delegations.filter((d) => d.personId !== person.id),
|
|
127
|
-
}
|
|
128
|
-
save()
|
|
129
|
-
return true
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function findPerson(idOrName: string): Person | null {
|
|
133
|
-
const lower = idOrName.toLowerCase()
|
|
134
|
-
return _data.people.find(
|
|
135
|
-
(p) => p.id === idOrName || p.name.toLowerCase().includes(lower),
|
|
136
|
-
) || null
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export function listPeople(group?: PersonGroup): Person[] {
|
|
140
|
-
if (group) return _data.people.filter((p) => p.group === group)
|
|
141
|
-
return [..._data.people]
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ─── Interactions ───────────────────────────────────────────
|
|
145
|
-
|
|
146
|
-
export function logInteraction(
|
|
147
|
-
personIdOrName: string,
|
|
148
|
-
type: InteractionType,
|
|
149
|
-
summary: string,
|
|
150
|
-
followUpDate?: Date,
|
|
151
|
-
): Interaction | null {
|
|
152
|
-
const person = findPerson(personIdOrName)
|
|
153
|
-
if (!person) return null
|
|
154
|
-
|
|
155
|
-
const interaction: Interaction = {
|
|
156
|
-
id: genId(),
|
|
157
|
-
personId: person.id,
|
|
158
|
-
date: new Date().toISOString(),
|
|
159
|
-
type,
|
|
160
|
-
summary: summary.trim(),
|
|
161
|
-
followUpDate: followUpDate?.toISOString(),
|
|
162
|
-
followUpDone: false,
|
|
163
|
-
}
|
|
164
|
-
_data = { ..._data, interactions: [..._data.interactions, interaction] }
|
|
165
|
-
save()
|
|
166
|
-
return interaction
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export function getInteractions(personIdOrName: string, limit = 10): Interaction[] {
|
|
170
|
-
const person = findPerson(personIdOrName)
|
|
171
|
-
if (!person) return []
|
|
172
|
-
|
|
173
|
-
return _data.interactions
|
|
174
|
-
.filter((i) => i.personId === person.id)
|
|
175
|
-
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
|
176
|
-
.slice(0, limit)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export function getPendingFollowUps(): Array<{ person: Person; interaction: Interaction }> {
|
|
180
|
-
const now = new Date()
|
|
181
|
-
const results: Array<{ person: Person; interaction: Interaction }> = []
|
|
182
|
-
|
|
183
|
-
for (const interaction of _data.interactions) {
|
|
184
|
-
if (interaction.followUpDone || !interaction.followUpDate) continue
|
|
185
|
-
const followUp = new Date(interaction.followUpDate)
|
|
186
|
-
if (isNaN(followUp.getTime())) continue
|
|
187
|
-
if (followUp <= now) {
|
|
188
|
-
const person = _data.people.find((p) => p.id === interaction.personId)
|
|
189
|
-
if (person) results.push({ person, interaction })
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return results.sort((a, b) =>
|
|
194
|
-
new Date(a.interaction.followUpDate!).getTime() - new Date(b.interaction.followUpDate!).getTime(),
|
|
195
|
-
)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export function markFollowUpDone(interactionId: string): boolean {
|
|
199
|
-
const found = _data.interactions.find((i) => i.id === interactionId)
|
|
200
|
-
if (!found) return false
|
|
201
|
-
|
|
202
|
-
_data = {
|
|
203
|
-
..._data,
|
|
204
|
-
interactions: _data.interactions.map((i) =>
|
|
205
|
-
i.id === interactionId ? { ...i, followUpDone: true } : i,
|
|
206
|
-
),
|
|
207
|
-
}
|
|
208
|
-
save()
|
|
209
|
-
return true
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// ─── Delegations ────────────────────────────────────────────
|
|
213
|
-
|
|
214
|
-
export function delegateTask(
|
|
215
|
-
personIdOrName: string,
|
|
216
|
-
task: string,
|
|
217
|
-
dueDate?: Date,
|
|
218
|
-
): Delegation | null {
|
|
219
|
-
const person = findPerson(personIdOrName)
|
|
220
|
-
if (!person) return null
|
|
221
|
-
|
|
222
|
-
const delegation: Delegation = {
|
|
223
|
-
id: genId(),
|
|
224
|
-
personId: person.id,
|
|
225
|
-
task: task.trim(),
|
|
226
|
-
assignedAt: new Date().toISOString(),
|
|
227
|
-
dueDate: dueDate?.toISOString(),
|
|
228
|
-
status: 'pendente',
|
|
229
|
-
}
|
|
230
|
-
_data = { ..._data, delegations: [..._data.delegations, delegation] }
|
|
231
|
-
save()
|
|
232
|
-
return delegation
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
export function updateDelegation(
|
|
236
|
-
delegationId: string,
|
|
237
|
-
status: Delegation['status'],
|
|
238
|
-
notes?: string,
|
|
239
|
-
): Delegation | null {
|
|
240
|
-
const found = _data.delegations.find((d) => d.id === delegationId)
|
|
241
|
-
if (!found) return null
|
|
242
|
-
|
|
243
|
-
_data = {
|
|
244
|
-
..._data,
|
|
245
|
-
delegations: _data.delegations.map((d) =>
|
|
246
|
-
d.id === delegationId ? { ...d, status, notes: notes || d.notes } : d,
|
|
247
|
-
),
|
|
248
|
-
}
|
|
249
|
-
save()
|
|
250
|
-
return _data.delegations.find((d) => d.id === delegationId) || null
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export function getDelegations(personIdOrName?: string, onlyPending = true): Delegation[] {
|
|
254
|
-
let results = [..._data.delegations]
|
|
255
|
-
|
|
256
|
-
if (personIdOrName) {
|
|
257
|
-
const person = findPerson(personIdOrName)
|
|
258
|
-
if (!person) return []
|
|
259
|
-
results = results.filter((d) => d.personId === person.id)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (onlyPending) {
|
|
263
|
-
results = results.filter((d) => d.status !== 'concluido')
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Mark overdue delegations
|
|
267
|
-
const now = new Date()
|
|
268
|
-
results = results.map((d) => {
|
|
269
|
-
if (d.status === 'pendente' && d.dueDate) {
|
|
270
|
-
const due = new Date(d.dueDate)
|
|
271
|
-
if (!isNaN(due.getTime()) && due < now) {
|
|
272
|
-
return { ...d, status: 'atrasado' as const }
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return d
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
return results.sort((a, b) => {
|
|
279
|
-
// Overdue first, then by due date
|
|
280
|
-
if (a.status === 'atrasado' && b.status !== 'atrasado') return -1
|
|
281
|
-
if (b.status === 'atrasado' && a.status !== 'atrasado') return 1
|
|
282
|
-
const da = a.dueDate ? new Date(a.dueDate).getTime() : Infinity
|
|
283
|
-
const db = b.dueDate ? new Date(b.dueDate).getTime() : Infinity
|
|
284
|
-
return da - db
|
|
285
|
-
})
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// ─── Formatting ─────────────────────────────────────────────
|
|
289
|
-
|
|
290
|
-
const GROUP_LABELS: Record<PersonGroup, string> = {
|
|
291
|
-
equipe: 'Equipe',
|
|
292
|
-
familia: 'Familia',
|
|
293
|
-
contato: 'Contato',
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
export function formatPeopleList(people: Person[]): string {
|
|
297
|
-
if (people.length === 0) return 'Nenhuma pessoa cadastrada.'
|
|
298
|
-
|
|
299
|
-
const grouped = new Map<PersonGroup, Person[]>()
|
|
300
|
-
for (const p of people) {
|
|
301
|
-
const list = grouped.get(p.group) || []
|
|
302
|
-
grouped.set(p.group, [...list, p])
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const sections: string[] = []
|
|
306
|
-
const order: PersonGroup[] = ['equipe', 'familia', 'contato']
|
|
307
|
-
|
|
308
|
-
for (const group of order) {
|
|
309
|
-
const groupPeople = grouped.get(group)
|
|
310
|
-
if (!groupPeople?.length) continue
|
|
311
|
-
|
|
312
|
-
const lines = groupPeople.map((p) => {
|
|
313
|
-
const role = p.role ? ` (${p.role})` : ''
|
|
314
|
-
const contact = p.contact ? ` — ${p.contact}` : ''
|
|
315
|
-
return ` ${p.name}${role}${contact} [${p.id}]`
|
|
316
|
-
})
|
|
317
|
-
sections.push(`--- ${GROUP_LABELS[group]} ---\n${lines.join('\n')}`)
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return sections.join('\n\n')
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
export function formatPersonDetail(person: Person): string {
|
|
324
|
-
const lines: string[] = []
|
|
325
|
-
lines.push(`${person.name} [${person.id}]`)
|
|
326
|
-
lines.push(`Grupo: ${GROUP_LABELS[person.group]}`)
|
|
327
|
-
if (person.role) lines.push(`Papel: ${person.role}`)
|
|
328
|
-
if (person.contact) lines.push(`Contato: ${person.contact}`)
|
|
329
|
-
if (person.notes) lines.push(`Notas: ${person.notes}`)
|
|
330
|
-
|
|
331
|
-
// Recent interactions
|
|
332
|
-
const interactions = getInteractions(person.id, 5)
|
|
333
|
-
if (interactions.length > 0) {
|
|
334
|
-
lines.push('\nInteracoes recentes:')
|
|
335
|
-
for (const i of interactions) {
|
|
336
|
-
const date = new Date(i.date).toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' })
|
|
337
|
-
lines.push(` [${date}] ${i.type}: ${i.summary}`)
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Pending delegations
|
|
342
|
-
const delegations = getDelegations(person.id)
|
|
343
|
-
if (delegations.length > 0) {
|
|
344
|
-
lines.push('\nTarefas delegadas:')
|
|
345
|
-
for (const d of delegations) {
|
|
346
|
-
const statusIcon = d.status === 'atrasado' ? '!!' : d.status === 'em_andamento' ? '>>' : ' '
|
|
347
|
-
const due = d.dueDate ? ` (${formatDate(d.dueDate)})` : ''
|
|
348
|
-
lines.push(` ${statusIcon} ${d.task}${due} [${d.status}]`)
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return lines.join('\n')
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
export function formatDelegationList(delegations: Delegation[]): string {
|
|
356
|
-
if (delegations.length === 0) return 'Nenhuma tarefa delegada pendente.'
|
|
357
|
-
|
|
358
|
-
const lines = delegations.map((d) => {
|
|
359
|
-
const person = _data.people.find((p) => p.id === d.personId)
|
|
360
|
-
const name = person?.name || '?'
|
|
361
|
-
const statusIcon = d.status === 'atrasado' ? '!! ' : d.status === 'em_andamento' ? '>> ' : ' '
|
|
362
|
-
const due = d.dueDate ? ` (${formatDate(d.dueDate)})` : ''
|
|
363
|
-
return `${statusIcon}${name}: ${d.task}${due} [${d.status}] [${d.id}]`
|
|
364
|
-
})
|
|
365
|
-
|
|
366
|
-
return `Delegacoes (${delegations.length}):\n${lines.join('\n')}`
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
export function formatFollowUps(items: Array<{ person: Person; interaction: Interaction }>): string {
|
|
370
|
-
if (items.length === 0) return 'Nenhum follow-up pendente.'
|
|
371
|
-
|
|
372
|
-
const lines = items.map(({ person, interaction }) => {
|
|
373
|
-
const date = formatDate(interaction.followUpDate!)
|
|
374
|
-
return ` [${date}] ${person.name}: ${interaction.summary} [${interaction.id}]`
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
return `Follow-ups pendentes (${items.length}):\n${lines.join('\n')}`
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// ─── Dashboard ──────────────────────────────────────────────
|
|
381
|
-
|
|
382
|
-
export function generatePeopleDashboard(): string {
|
|
383
|
-
const sections: string[] = []
|
|
384
|
-
sections.push('=== PAINEL DE PESSOAS ===\n')
|
|
385
|
-
|
|
386
|
-
// Summary counts
|
|
387
|
-
const teamCount = _data.people.filter((p) => p.group === 'equipe').length
|
|
388
|
-
const familyCount = _data.people.filter((p) => p.group === 'familia').length
|
|
389
|
-
const contactCount = _data.people.filter((p) => p.group === 'contato').length
|
|
390
|
-
sections.push(`Equipe: ${teamCount} | Familia: ${familyCount} | Contatos: ${contactCount}`)
|
|
391
|
-
|
|
392
|
-
// Overdue follow-ups
|
|
393
|
-
const followUps = getPendingFollowUps()
|
|
394
|
-
if (followUps.length > 0) {
|
|
395
|
-
sections.push(`\n!! ${followUps.length} follow-up(s) pendente(s):`)
|
|
396
|
-
for (const { person, interaction } of followUps.slice(0, 5)) {
|
|
397
|
-
sections.push(` ${person.name}: ${interaction.summary}`)
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Overdue/pending delegations
|
|
402
|
-
const allDelegations = getDelegations()
|
|
403
|
-
const overdue = allDelegations.filter((d) => d.status === 'atrasado')
|
|
404
|
-
const pending = allDelegations.filter((d) => d.status === 'pendente' || d.status === 'em_andamento')
|
|
405
|
-
|
|
406
|
-
if (overdue.length > 0) {
|
|
407
|
-
sections.push(`\n!! ${overdue.length} delegacao(oes) atrasada(s):`)
|
|
408
|
-
for (const d of overdue.slice(0, 5)) {
|
|
409
|
-
const person = _data.people.find((p) => p.id === d.personId)
|
|
410
|
-
sections.push(` ${person?.name}: ${d.task}`)
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (pending.length > 0) {
|
|
415
|
-
sections.push(`\n${pending.length} delegacao(oes) em andamento`)
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Recent interactions (last 3)
|
|
419
|
-
const recent = [..._data.interactions]
|
|
420
|
-
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
|
421
|
-
.slice(0, 3)
|
|
422
|
-
|
|
423
|
-
if (recent.length > 0) {
|
|
424
|
-
sections.push('\nUltimas interacoes:')
|
|
425
|
-
for (const i of recent) {
|
|
426
|
-
const person = _data.people.find((p) => p.id === i.personId)
|
|
427
|
-
const date = new Date(i.date).toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' })
|
|
428
|
-
sections.push(` [${date}] ${person?.name}: ${i.summary}`)
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
sections.push('\n========================')
|
|
433
|
-
return sections.join('\n')
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// ─── Helpers ────────────────────────────────────────────────
|
|
437
|
-
|
|
438
|
-
function genId(): string {
|
|
439
|
-
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
|
440
|
-
let id = ''
|
|
441
|
-
for (let i = 0; i < 6; i++) {
|
|
442
|
-
id += chars[Math.floor(Math.random() * chars.length)]
|
|
443
|
-
}
|
|
444
|
-
return id
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function formatDate(isoDate: string): string {
|
|
448
|
-
const d = new Date(isoDate)
|
|
449
|
-
if (isNaN(d.getTime())) return '?'
|
|
450
|
-
const now = new Date()
|
|
451
|
-
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
|
452
|
-
const target = new Date(d.getFullYear(), d.getMonth(), d.getDate())
|
|
453
|
-
|
|
454
|
-
if (target.getTime() === today.getTime()) return 'hoje'
|
|
455
|
-
const tomorrow = new Date(today)
|
|
456
|
-
tomorrow.setDate(tomorrow.getDate() + 1)
|
|
457
|
-
if (target.getTime() === tomorrow.getTime()) return 'amanha'
|
|
458
|
-
|
|
459
|
-
const diff = Math.floor((target.getTime() - today.getTime()) / 86_400_000)
|
|
460
|
-
if (diff < 0) return `${Math.abs(diff)}d atras`
|
|
461
|
-
if (diff <= 7) return `em ${diff}d`
|
|
462
|
-
|
|
463
|
-
return d.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' })
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function stripUndefined(obj: Record<string, unknown>): Record<string, unknown> {
|
|
467
|
-
const result: Record<string, unknown> = {}
|
|
468
|
-
for (const [key, val] of Object.entries(obj)) {
|
|
469
|
-
if (val !== undefined) result[key] = val
|
|
470
|
-
}
|
|
471
|
-
return result
|
|
472
|
-
}
|
package/src/personas.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prompt personas — switchable assistant modes.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface Persona {
|
|
6
|
-
name: string
|
|
7
|
-
description: string
|
|
8
|
-
systemPrompt: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const PERSONAS: Record<string, Persona> = {
|
|
12
|
-
default: {
|
|
13
|
-
name: 'default',
|
|
14
|
-
description: 'Versatile assistant (general + coding)',
|
|
15
|
-
systemPrompt: '', // Uses the default skill
|
|
16
|
-
},
|
|
17
|
-
|
|
18
|
-
coder: {
|
|
19
|
-
name: 'coder',
|
|
20
|
-
description: 'Focused software engineer',
|
|
21
|
-
systemPrompt: `You are a senior software engineer. Focus exclusively on code quality, architecture, and implementation.
|
|
22
|
-
|
|
23
|
-
Behavior:
|
|
24
|
-
- Write clean, production-grade code. No shortcuts.
|
|
25
|
-
- Always read files before editing. Use edit_file for modifications.
|
|
26
|
-
- Run tests after changes. Check types. Verify builds.
|
|
27
|
-
- Match the project's existing patterns and conventions.
|
|
28
|
-
- Be terse. Code speaks louder than explanations.`,
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
writer: {
|
|
32
|
-
name: 'writer',
|
|
33
|
-
description: 'Technical and creative writer',
|
|
34
|
-
systemPrompt: `You are a skilled writer who adapts tone and style to the task.
|
|
35
|
-
|
|
36
|
-
Behavior:
|
|
37
|
-
- For technical writing: clear, structured, precise. Use headings, lists, examples.
|
|
38
|
-
- For creative writing: vivid, engaging, varied sentence structure.
|
|
39
|
-
- For emails/messages: concise, professional, direct.
|
|
40
|
-
- Always match the language the user writes in.
|
|
41
|
-
- Use fetch_url to research topics when needed.
|
|
42
|
-
- Never pad with filler. Every sentence should carry weight.`,
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
researcher: {
|
|
46
|
-
name: 'researcher',
|
|
47
|
-
description: 'Deep research and analysis',
|
|
48
|
-
systemPrompt: `You are a research analyst. Thorough, evidence-based, skeptical.
|
|
49
|
-
|
|
50
|
-
Behavior:
|
|
51
|
-
- Use fetch_url extensively to gather information from multiple sources.
|
|
52
|
-
- Cross-reference claims. Note when sources conflict.
|
|
53
|
-
- Structure findings with clear sections: Summary, Key Findings, Sources, Caveats.
|
|
54
|
-
- Distinguish between facts, opinions, and speculation.
|
|
55
|
-
- Always cite where you found information.
|
|
56
|
-
- If you can't verify something, say so explicitly.`,
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
reviewer: {
|
|
60
|
-
name: 'reviewer',
|
|
61
|
-
description: 'Code review specialist',
|
|
62
|
-
systemPrompt: `You are a meticulous code reviewer focused on quality, security, and maintainability.
|
|
63
|
-
|
|
64
|
-
Behavior:
|
|
65
|
-
- Read the entire file/diff before commenting.
|
|
66
|
-
- Categorize issues: CRITICAL (bugs, security), WARNING (code smell, performance), SUGGESTION (style, readability).
|
|
67
|
-
- Be specific. Show the line, explain the problem, suggest the fix.
|
|
68
|
-
- Check for: error handling, input validation, edge cases, naming, complexity.
|
|
69
|
-
- Don't nitpick formatting unless it affects readability.
|
|
70
|
-
- Praise good patterns when you see them.`,
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
business: {
|
|
74
|
-
name: 'business',
|
|
75
|
-
description: 'Personal business assistant (Windows-focused)',
|
|
76
|
-
systemPrompt: '', // Uses the business skill
|
|
77
|
-
},
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Get a persona by name (case-insensitive).
|
|
82
|
-
*/
|
|
83
|
-
export function getPersona(name: string): Persona | null {
|
|
84
|
-
return PERSONAS[name.toLowerCase()] || null
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Format persona list for display.
|
|
89
|
-
*/
|
|
90
|
-
export function formatPersonaList(current: string): string {
|
|
91
|
-
const lines = ['Personas:']
|
|
92
|
-
for (const [key, p] of Object.entries(PERSONAS)) {
|
|
93
|
-
const marker = key === current ? ' *' : ' '
|
|
94
|
-
lines.push(`${marker} ${key.padEnd(12)} ${p.description}`)
|
|
95
|
-
}
|
|
96
|
-
lines.push('')
|
|
97
|
-
lines.push('Use: /persona <name>')
|
|
98
|
-
return lines.join('\n')
|
|
99
|
-
}
|
package/src/platform.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs'
|
|
2
|
-
|
|
3
|
-
export const IS_WINDOWS = process.platform === 'win32'
|
|
4
|
-
export const IS_MAC = process.platform === 'darwin'
|
|
5
|
-
export const IS_LINUX = process.platform === 'linux'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Returns the shell command prefix for spawning subprocesses.
|
|
9
|
-
* Windows: powershell, Unix: bash with sh fallback.
|
|
10
|
-
*/
|
|
11
|
-
export function getShell(): [string, ...string[]] {
|
|
12
|
-
if (IS_WINDOWS) {
|
|
13
|
-
return ['powershell', '-NoProfile', '-NonInteractive', '-Command']
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Prefer user's SHELL, then bash, then sh
|
|
17
|
-
const userShell = process.env.SHELL
|
|
18
|
-
if (userShell && existsSync(userShell)) {
|
|
19
|
-
return [userShell, '-c']
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return ['bash', '-c']
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Returns a human-readable shell name for the system prompt.
|
|
27
|
-
*/
|
|
28
|
-
export function getShellName(): string {
|
|
29
|
-
if (IS_WINDOWS) return 'powershell'
|
|
30
|
-
const shell = process.env.SHELL || '/bin/bash'
|
|
31
|
-
const name = shell.split('/').pop() || 'bash'
|
|
32
|
-
return name
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Check if a command is available on the system.
|
|
37
|
-
*/
|
|
38
|
-
export async function commandExists(cmd: string): Promise<boolean> {
|
|
39
|
-
try {
|
|
40
|
-
const args = IS_WINDOWS
|
|
41
|
-
? ['powershell', '-NoProfile', '-Command', `Get-Command ${cmd} -ErrorAction SilentlyContinue`]
|
|
42
|
-
: ['which', cmd]
|
|
43
|
-
|
|
44
|
-
const proc = Bun.spawn(args, { stdout: 'pipe', stderr: 'pipe' })
|
|
45
|
-
await proc.exited
|
|
46
|
-
return proc.exitCode === 0
|
|
47
|
-
} catch {
|
|
48
|
-
return false
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Cache ripgrep availability at startup
|
|
53
|
-
let _hasRg: boolean | null = null
|
|
54
|
-
|
|
55
|
-
export async function hasRipgrep(): Promise<boolean> {
|
|
56
|
-
if (_hasRg !== null) return _hasRg
|
|
57
|
-
_hasRg = await commandExists('rg')
|
|
58
|
-
return _hasRg
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Directories to exclude from file searches.
|
|
63
|
-
*/
|
|
64
|
-
export const SEARCH_EXCLUDES = [
|
|
65
|
-
'node_modules',
|
|
66
|
-
'.git',
|
|
67
|
-
'dist',
|
|
68
|
-
'build',
|
|
69
|
-
'.next',
|
|
70
|
-
'__pycache__',
|
|
71
|
-
'.venv',
|
|
72
|
-
'target',
|
|
73
|
-
'.cache',
|
|
74
|
-
]
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Check if a path should be excluded from search results.
|
|
78
|
-
*/
|
|
79
|
-
export function shouldExclude(path: string): boolean {
|
|
80
|
-
const normalized = path.replace(/\\/g, '/')
|
|
81
|
-
return SEARCH_EXCLUDES.some(
|
|
82
|
-
(ex) => normalized.includes(`/${ex}/`) || normalized.startsWith(`${ex}/`),
|
|
83
|
-
)
|
|
84
|
-
}
|