opencode-manager 0.3.0 → 0.4.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/PROJECT-SUMMARY.md +104 -24
- package/README.md +335 -7
- package/bun.lock +17 -1
- package/manage_opencode_projects.py +71 -66
- package/package.json +6 -3
- package/src/bin/opencode-manager.ts +133 -3
- package/src/cli/backup.ts +324 -0
- package/src/cli/commands/chat.ts +322 -0
- package/src/cli/commands/projects.ts +222 -0
- package/src/cli/commands/sessions.ts +495 -0
- package/src/cli/commands/tokens.ts +168 -0
- package/src/cli/commands/tui.ts +36 -0
- package/src/cli/errors.ts +259 -0
- package/src/cli/formatters/json.ts +184 -0
- package/src/cli/formatters/ndjson.ts +71 -0
- package/src/cli/formatters/table.ts +837 -0
- package/src/cli/index.ts +169 -0
- package/src/cli/output.ts +661 -0
- package/src/cli/resolvers.ts +249 -0
- package/src/lib/clipboard.ts +37 -0
- package/src/lib/opencode-data.ts +380 -1
- package/src/lib/search.ts +170 -0
- package/src/{opencode-tui.tsx → tui/app.tsx} +739 -105
- package/src/tui/args.ts +92 -0
- package/src/tui/index.tsx +46 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fuzzy search utilities using fast-fuzzy.
|
|
3
|
+
* Extracted from TUI for reuse in CLI.
|
|
4
|
+
*/
|
|
5
|
+
import { Searcher, MatchData, FullOptions } from "fast-fuzzy"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A search candidate with an item and searchable text.
|
|
9
|
+
*/
|
|
10
|
+
export type SearchCandidate<T> = {
|
|
11
|
+
item: T
|
|
12
|
+
searchText: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A search result with the matched item and score.
|
|
17
|
+
*/
|
|
18
|
+
export type SearchResult<T> = {
|
|
19
|
+
item: T
|
|
20
|
+
score: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Options for fuzzy search.
|
|
25
|
+
*/
|
|
26
|
+
export type FuzzySearchOptions = {
|
|
27
|
+
/** Maximum number of results to return (default: 200) */
|
|
28
|
+
limit?: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Type for the searcher options with keySelector
|
|
32
|
+
type SearcherOptions<T> = FullOptions<SearchCandidate<T>> & {
|
|
33
|
+
keySelector: (c: SearchCandidate<T>) => string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates a fuzzy searcher for items with searchable text.
|
|
38
|
+
*
|
|
39
|
+
* @param candidates - Array of search candidates with items and search text
|
|
40
|
+
* @returns A Searcher instance configured for the candidates
|
|
41
|
+
*/
|
|
42
|
+
export function createSearcher<T>(
|
|
43
|
+
candidates: SearchCandidate<T>[]
|
|
44
|
+
): Searcher<SearchCandidate<T>, SearcherOptions<T>> {
|
|
45
|
+
return new Searcher(candidates, {
|
|
46
|
+
keySelector: (c: SearchCandidate<T>) => c.searchText,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Performs a fuzzy search on the given candidates.
|
|
52
|
+
*
|
|
53
|
+
* @param candidates - Array of search candidates
|
|
54
|
+
* @param query - The search query string
|
|
55
|
+
* @param options - Optional search options
|
|
56
|
+
* @returns Array of search results sorted by score (descending)
|
|
57
|
+
*/
|
|
58
|
+
export function fuzzySearch<T>(
|
|
59
|
+
candidates: SearchCandidate<T>[],
|
|
60
|
+
query: string,
|
|
61
|
+
options?: FuzzySearchOptions
|
|
62
|
+
): SearchResult<T>[] {
|
|
63
|
+
const q = query.trim()
|
|
64
|
+
if (!q) {
|
|
65
|
+
// No query - return all items with score 1
|
|
66
|
+
return candidates.map((c) => ({ item: c.item, score: 1 }))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const searcher = createSearcher(candidates)
|
|
70
|
+
const results = searcher.search(q, { returnMatchData: true }) as MatchData<SearchCandidate<T>>[]
|
|
71
|
+
|
|
72
|
+
const mapped: SearchResult<T>[] = results.map((match) => ({
|
|
73
|
+
item: match.item.item,
|
|
74
|
+
score: match.score,
|
|
75
|
+
}))
|
|
76
|
+
|
|
77
|
+
// Sort by score descending
|
|
78
|
+
mapped.sort((a, b) => b.score - a.score)
|
|
79
|
+
|
|
80
|
+
// Apply limit
|
|
81
|
+
const limit = options?.limit ?? 200
|
|
82
|
+
if (mapped.length > limit) {
|
|
83
|
+
return mapped.slice(0, limit)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return mapped
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Performs fuzzy search and returns only the matched items (not scores).
|
|
91
|
+
*
|
|
92
|
+
* @param candidates - Array of search candidates
|
|
93
|
+
* @param query - The search query string
|
|
94
|
+
* @param options - Optional search options
|
|
95
|
+
* @returns Array of matched items sorted by score (descending)
|
|
96
|
+
*/
|
|
97
|
+
export function fuzzySearchItems<T>(
|
|
98
|
+
candidates: SearchCandidate<T>[],
|
|
99
|
+
query: string,
|
|
100
|
+
options?: FuzzySearchOptions
|
|
101
|
+
): T[] {
|
|
102
|
+
return fuzzySearch(candidates, query, options).map((r) => r.item)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Builds a search text string from multiple fields.
|
|
107
|
+
* Joins all fields with spaces and normalizes whitespace.
|
|
108
|
+
*
|
|
109
|
+
* @param fields - Array of string fields to combine
|
|
110
|
+
* @returns A normalized search text string
|
|
111
|
+
*/
|
|
112
|
+
export function buildSearchText(...fields: (string | null | undefined)[]): string {
|
|
113
|
+
return fields
|
|
114
|
+
.filter((f): f is string => f != null && f !== "")
|
|
115
|
+
.join(" ")
|
|
116
|
+
.replace(/\s+/g, " ")
|
|
117
|
+
.trim()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Options for tokenized search.
|
|
122
|
+
*/
|
|
123
|
+
export type TokenizedSearchOptions = {
|
|
124
|
+
/** Maximum number of results to return (default: 200) */
|
|
125
|
+
limit?: number
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Performs tokenized substring search on items.
|
|
130
|
+
* Matches TUI project search semantics:
|
|
131
|
+
* - Query is split on whitespace into tokens
|
|
132
|
+
* - Each token must be found in at least one of the searchable fields
|
|
133
|
+
* - Matching is case-insensitive substring matching
|
|
134
|
+
*
|
|
135
|
+
* @param items - Array of items to search
|
|
136
|
+
* @param query - The search query string
|
|
137
|
+
* @param getFields - Function to extract searchable fields from an item
|
|
138
|
+
* @param options - Optional search options
|
|
139
|
+
* @returns Array of items that match all tokens
|
|
140
|
+
*/
|
|
141
|
+
export function tokenizedSearch<T>(
|
|
142
|
+
items: T[],
|
|
143
|
+
query: string,
|
|
144
|
+
getFields: (item: T) => (string | null | undefined)[],
|
|
145
|
+
options?: TokenizedSearchOptions
|
|
146
|
+
): T[] {
|
|
147
|
+
const q = query?.trim().toLowerCase() ?? ""
|
|
148
|
+
if (!q) {
|
|
149
|
+
const limit = options?.limit ?? 200
|
|
150
|
+
return items.slice(0, limit)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const tokens = q.split(/\s+/).filter(Boolean)
|
|
154
|
+
if (tokens.length === 0) {
|
|
155
|
+
const limit = options?.limit ?? 200
|
|
156
|
+
return items.slice(0, limit)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const matched = items.filter((item) => {
|
|
160
|
+
const fields = getFields(item).map((f) => (f || "").toLowerCase())
|
|
161
|
+
return tokens.every((tok) => fields.some((field) => field.includes(tok)))
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
const limit = options?.limit ?? 200
|
|
165
|
+
if (matched.length > limit) {
|
|
166
|
+
return matched.slice(0, limit)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return matched
|
|
170
|
+
}
|