kimaki 0.4.21 → 0.4.22
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/cli.js +1 -1
- package/dist/discordBot.js +60 -31
- package/dist/format-tables.js +93 -0
- package/dist/format-tables.test.js +418 -0
- package/dist/markdown.js +3 -3
- package/dist/tools.js +2 -4
- package/dist/utils.js +31 -0
- package/package.json +1 -2
- package/src/cli.ts +1 -1
- package/src/discordBot.ts +64 -36
- package/src/format-tables.test.ts +440 -0
- package/src/format-tables.ts +106 -0
- package/src/markdown.ts +3 -3
- package/src/tools.ts +2 -4
- package/src/utils.ts +37 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Lexer, type Token, type Tokens } from 'marked'
|
|
2
|
+
|
|
3
|
+
export function formatMarkdownTables(markdown: string): string {
|
|
4
|
+
const lexer = new Lexer()
|
|
5
|
+
const tokens = lexer.lex(markdown)
|
|
6
|
+
|
|
7
|
+
let result = ''
|
|
8
|
+
for (const token of tokens) {
|
|
9
|
+
if (token.type === 'table') {
|
|
10
|
+
result += formatTableToken(token as Tokens.Table)
|
|
11
|
+
} else {
|
|
12
|
+
result += token.raw
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return result
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatTableToken(table: Tokens.Table): string {
|
|
19
|
+
const headers = table.header.map((cell) => {
|
|
20
|
+
return extractCellText(cell.tokens)
|
|
21
|
+
})
|
|
22
|
+
const rows = table.rows.map((row) => {
|
|
23
|
+
return row.map((cell) => {
|
|
24
|
+
return extractCellText(cell.tokens)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const columnWidths = calculateColumnWidths(headers, rows)
|
|
29
|
+
const lines: string[] = []
|
|
30
|
+
|
|
31
|
+
lines.push(formatRow(headers, columnWidths))
|
|
32
|
+
lines.push(formatSeparator(columnWidths))
|
|
33
|
+
for (const row of rows) {
|
|
34
|
+
lines.push(formatRow(row, columnWidths))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return '```\n' + lines.join('\n') + '\n```\n'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function extractCellText(tokens: Token[]): string {
|
|
41
|
+
const parts: string[] = []
|
|
42
|
+
for (const token of tokens) {
|
|
43
|
+
parts.push(extractTokenText(token))
|
|
44
|
+
}
|
|
45
|
+
return parts.join('').trim()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function extractTokenText(token: Token): string {
|
|
49
|
+
switch (token.type) {
|
|
50
|
+
case 'text':
|
|
51
|
+
case 'codespan':
|
|
52
|
+
case 'escape':
|
|
53
|
+
return token.text
|
|
54
|
+
case 'link':
|
|
55
|
+
return token.href
|
|
56
|
+
case 'image':
|
|
57
|
+
return token.href
|
|
58
|
+
case 'strong':
|
|
59
|
+
case 'em':
|
|
60
|
+
case 'del':
|
|
61
|
+
return token.tokens ? extractCellText(token.tokens) : token.text
|
|
62
|
+
case 'br':
|
|
63
|
+
return ' '
|
|
64
|
+
default: {
|
|
65
|
+
const tokenAny = token as { tokens?: Token[]; text?: string }
|
|
66
|
+
if (tokenAny.tokens && Array.isArray(tokenAny.tokens)) {
|
|
67
|
+
return extractCellText(tokenAny.tokens)
|
|
68
|
+
}
|
|
69
|
+
if (typeof tokenAny.text === 'string') {
|
|
70
|
+
return tokenAny.text
|
|
71
|
+
}
|
|
72
|
+
return ''
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function calculateColumnWidths(
|
|
78
|
+
headers: string[],
|
|
79
|
+
rows: string[][],
|
|
80
|
+
): number[] {
|
|
81
|
+
const widths = headers.map((h) => {
|
|
82
|
+
return h.length
|
|
83
|
+
})
|
|
84
|
+
for (const row of rows) {
|
|
85
|
+
for (let i = 0; i < row.length; i++) {
|
|
86
|
+
const cell = row[i] ?? ''
|
|
87
|
+
widths[i] = Math.max(widths[i] ?? 0, cell.length)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return widths
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function formatRow(cells: string[], widths: number[]): string {
|
|
94
|
+
const paddedCells = cells.map((cell, i) => {
|
|
95
|
+
return cell.padEnd(widths[i] ?? 0)
|
|
96
|
+
})
|
|
97
|
+
return paddedCells.join(' ')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function formatSeparator(widths: number[]): string {
|
|
101
|
+
return widths
|
|
102
|
+
.map((w) => {
|
|
103
|
+
return '-'.repeat(w)
|
|
104
|
+
})
|
|
105
|
+
.join(' ')
|
|
106
|
+
}
|
package/src/markdown.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { OpencodeClient } from '@opencode-ai/sdk'
|
|
2
|
-
import { format } from 'date-fns'
|
|
3
2
|
import * as yaml from 'js-yaml'
|
|
3
|
+
import { formatDateTime } from './utils.js'
|
|
4
4
|
import { extractNonXmlContent } from './xml.js'
|
|
5
5
|
|
|
6
6
|
export class ShareMarkdown {
|
|
@@ -62,10 +62,10 @@ export class ShareMarkdown {
|
|
|
62
62
|
lines.push('## Session Information')
|
|
63
63
|
lines.push('')
|
|
64
64
|
lines.push(
|
|
65
|
-
`- **Created**: ${
|
|
65
|
+
`- **Created**: ${formatDateTime(new Date(session.time.created))}`,
|
|
66
66
|
)
|
|
67
67
|
lines.push(
|
|
68
|
-
`- **Updated**: ${
|
|
68
|
+
`- **Updated**: ${formatDateTime(new Date(session.time.updated))}`,
|
|
69
69
|
)
|
|
70
70
|
if (session.version) {
|
|
71
71
|
lines.push(`- **OpenCode Version**: v${session.version}`)
|
package/src/tools.ts
CHANGED
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
import { createLogger } from './logger.js'
|
|
12
12
|
|
|
13
13
|
const toolsLogger = createLogger('TOOLS')
|
|
14
|
-
import { formatDistanceToNow } from 'date-fns'
|
|
15
14
|
|
|
16
15
|
import { ShareMarkdown } from './markdown.js'
|
|
16
|
+
import { formatDistanceToNow } from './utils.js'
|
|
17
17
|
import pc from 'picocolors'
|
|
18
18
|
import {
|
|
19
19
|
initializeOpencodeForDirectory,
|
|
@@ -232,9 +232,7 @@ export async function getTools({
|
|
|
232
232
|
id: session.id,
|
|
233
233
|
folder: session.directory,
|
|
234
234
|
status,
|
|
235
|
-
finishedAt: formatDistanceToNow(new Date(finishedAt),
|
|
236
|
-
addSuffix: true,
|
|
237
|
-
}),
|
|
235
|
+
finishedAt: formatDistanceToNow(new Date(finishedAt)),
|
|
238
236
|
title: session.title,
|
|
239
237
|
prompt: session.title,
|
|
240
238
|
}
|
package/src/utils.ts
CHANGED
|
@@ -75,3 +75,40 @@ export function isAbortError(
|
|
|
75
75
|
(error instanceof DOMException && error.name === 'AbortError')
|
|
76
76
|
)
|
|
77
77
|
}
|
|
78
|
+
|
|
79
|
+
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
|
|
80
|
+
|
|
81
|
+
const TIME_DIVISIONS: Array<{ amount: number; name: Intl.RelativeTimeFormatUnit }> = [
|
|
82
|
+
{ amount: 60, name: 'seconds' },
|
|
83
|
+
{ amount: 60, name: 'minutes' },
|
|
84
|
+
{ amount: 24, name: 'hours' },
|
|
85
|
+
{ amount: 7, name: 'days' },
|
|
86
|
+
{ amount: 4.34524, name: 'weeks' },
|
|
87
|
+
{ amount: 12, name: 'months' },
|
|
88
|
+
{ amount: Number.POSITIVE_INFINITY, name: 'years' },
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
export function formatDistanceToNow(date: Date): string {
|
|
92
|
+
let duration = (date.getTime() - Date.now()) / 1000
|
|
93
|
+
|
|
94
|
+
for (const division of TIME_DIVISIONS) {
|
|
95
|
+
if (Math.abs(duration) < division.amount) {
|
|
96
|
+
return rtf.format(Math.round(duration), division.name)
|
|
97
|
+
}
|
|
98
|
+
duration /= division.amount
|
|
99
|
+
}
|
|
100
|
+
return rtf.format(Math.round(duration), 'years')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const dtf = new Intl.DateTimeFormat('en-US', {
|
|
104
|
+
month: 'short',
|
|
105
|
+
day: 'numeric',
|
|
106
|
+
year: 'numeric',
|
|
107
|
+
hour: 'numeric',
|
|
108
|
+
minute: '2-digit',
|
|
109
|
+
hour12: true,
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
export function formatDateTime(date: Date): string {
|
|
113
|
+
return dtf.format(date)
|
|
114
|
+
}
|