@stacksjs/ts-cloud-core 0.1.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/LICENSE.md +21 -0
- package/README.md +321 -0
- package/package.json +31 -0
- package/src/advanced-features.test.ts +465 -0
- package/src/aws/cloudformation.ts +421 -0
- package/src/aws/cloudfront.ts +158 -0
- package/src/aws/credentials.test.ts +132 -0
- package/src/aws/credentials.ts +545 -0
- package/src/aws/index.ts +87 -0
- package/src/aws/s3.test.ts +188 -0
- package/src/aws/s3.ts +1088 -0
- package/src/aws/signature.test.ts +670 -0
- package/src/aws/signature.ts +1155 -0
- package/src/backup/disaster-recovery.test.ts +726 -0
- package/src/backup/disaster-recovery.ts +500 -0
- package/src/backup/index.ts +34 -0
- package/src/backup/manager.test.ts +498 -0
- package/src/backup/manager.ts +432 -0
- package/src/cicd/circleci.ts +430 -0
- package/src/cicd/github-actions.ts +424 -0
- package/src/cicd/gitlab-ci.ts +255 -0
- package/src/cicd/index.ts +8 -0
- package/src/cli/history.ts +396 -0
- package/src/cli/index.ts +10 -0
- package/src/cli/progress.ts +458 -0
- package/src/cli/repl.ts +454 -0
- package/src/cli/suggestions.ts +327 -0
- package/src/cli/table.test.ts +319 -0
- package/src/cli/table.ts +332 -0
- package/src/cloudformation/builder.test.ts +327 -0
- package/src/cloudformation/builder.ts +378 -0
- package/src/cloudformation/builders/api-gateway.ts +449 -0
- package/src/cloudformation/builders/cache.ts +334 -0
- package/src/cloudformation/builders/cdn.ts +278 -0
- package/src/cloudformation/builders/compute.ts +485 -0
- package/src/cloudformation/builders/database.ts +392 -0
- package/src/cloudformation/builders/functions.ts +343 -0
- package/src/cloudformation/builders/messaging.ts +140 -0
- package/src/cloudformation/builders/monitoring.ts +300 -0
- package/src/cloudformation/builders/network.ts +264 -0
- package/src/cloudformation/builders/queue.ts +147 -0
- package/src/cloudformation/builders/security.ts +399 -0
- package/src/cloudformation/builders/storage.ts +285 -0
- package/src/cloudformation/index.ts +30 -0
- package/src/cloudformation/types.ts +173 -0
- package/src/compliance/aws-config.ts +543 -0
- package/src/compliance/cloudtrail.ts +376 -0
- package/src/compliance/compliance.test.ts +423 -0
- package/src/compliance/guardduty.ts +446 -0
- package/src/compliance/index.ts +66 -0
- package/src/compliance/security-hub.ts +456 -0
- package/src/containers/build-optimization.ts +416 -0
- package/src/containers/containers.test.ts +508 -0
- package/src/containers/image-scanning.ts +360 -0
- package/src/containers/index.ts +9 -0
- package/src/containers/registry.ts +293 -0
- package/src/containers/service-mesh.ts +520 -0
- package/src/database/database.test.ts +762 -0
- package/src/database/index.ts +9 -0
- package/src/database/migrations.ts +444 -0
- package/src/database/performance.ts +528 -0
- package/src/database/replicas.ts +534 -0
- package/src/database/users.ts +494 -0
- package/src/dependency-graph.ts +143 -0
- package/src/deployment/ab-testing.ts +582 -0
- package/src/deployment/blue-green.ts +452 -0
- package/src/deployment/canary.ts +500 -0
- package/src/deployment/deployment.test.ts +526 -0
- package/src/deployment/index.ts +61 -0
- package/src/deployment/progressive.ts +62 -0
- package/src/dns/dns.test.ts +641 -0
- package/src/dns/dnssec.ts +315 -0
- package/src/dns/index.ts +8 -0
- package/src/dns/resolver.ts +496 -0
- package/src/dns/routing.ts +593 -0
- package/src/email/advanced/analytics.ts +445 -0
- package/src/email/advanced/index.ts +11 -0
- package/src/email/advanced/rules.ts +465 -0
- package/src/email/advanced/scheduling.ts +352 -0
- package/src/email/advanced/search.ts +412 -0
- package/src/email/advanced/shared-mailboxes.ts +404 -0
- package/src/email/advanced/templates.ts +455 -0
- package/src/email/advanced/threading.ts +281 -0
- package/src/email/analytics.ts +467 -0
- package/src/email/bounce-handling.ts +425 -0
- package/src/email/email.test.ts +431 -0
- package/src/email/handlers/__tests__/inbound.test.ts +38 -0
- package/src/email/handlers/__tests__/outbound.test.ts +37 -0
- package/src/email/handlers/converter.ts +227 -0
- package/src/email/handlers/feedback.ts +228 -0
- package/src/email/handlers/inbound.ts +169 -0
- package/src/email/handlers/outbound.ts +178 -0
- package/src/email/index.ts +15 -0
- package/src/email/reputation.ts +303 -0
- package/src/email/templates.ts +352 -0
- package/src/errors/index.test.ts +434 -0
- package/src/errors/index.ts +416 -0
- package/src/health-checks/index.ts +40 -0
- package/src/index.ts +360 -0
- package/src/intrinsic-functions.ts +118 -0
- package/src/lambda/concurrency.ts +330 -0
- package/src/lambda/destinations.ts +345 -0
- package/src/lambda/dlq.ts +425 -0
- package/src/lambda/index.ts +11 -0
- package/src/lambda/lambda.test.ts +840 -0
- package/src/lambda/layers.ts +263 -0
- package/src/lambda/versions.ts +376 -0
- package/src/lambda/vpc.ts +399 -0
- package/src/local/config.ts +114 -0
- package/src/local/index.ts +6 -0
- package/src/local/mock-aws.ts +351 -0
- package/src/modules/ai.ts +340 -0
- package/src/modules/api.ts +478 -0
- package/src/modules/auth.ts +805 -0
- package/src/modules/cache.ts +417 -0
- package/src/modules/cdn.ts +1062 -0
- package/src/modules/communication.ts +1094 -0
- package/src/modules/compute.ts +3348 -0
- package/src/modules/database.ts +554 -0
- package/src/modules/deployment.ts +1079 -0
- package/src/modules/dns.ts +337 -0
- package/src/modules/email.ts +1538 -0
- package/src/modules/filesystem.ts +515 -0
- package/src/modules/index.ts +32 -0
- package/src/modules/messaging.ts +486 -0
- package/src/modules/monitoring.ts +2086 -0
- package/src/modules/network.ts +664 -0
- package/src/modules/parameter-store.ts +325 -0
- package/src/modules/permissions.ts +1081 -0
- package/src/modules/phone.ts +494 -0
- package/src/modules/queue.ts +1260 -0
- package/src/modules/redirects.ts +464 -0
- package/src/modules/registry.ts +699 -0
- package/src/modules/search.ts +401 -0
- package/src/modules/secrets.ts +416 -0
- package/src/modules/security.ts +731 -0
- package/src/modules/sms.ts +389 -0
- package/src/modules/storage.ts +1120 -0
- package/src/modules/workflow.ts +680 -0
- package/src/multi-account/config.ts +521 -0
- package/src/multi-account/index.ts +7 -0
- package/src/multi-account/manager.ts +427 -0
- package/src/multi-region/cross-region.ts +410 -0
- package/src/multi-region/index.ts +8 -0
- package/src/multi-region/manager.ts +483 -0
- package/src/multi-region/regions.ts +435 -0
- package/src/network-security/index.ts +48 -0
- package/src/observability/index.ts +9 -0
- package/src/observability/logs.ts +522 -0
- package/src/observability/metrics.ts +460 -0
- package/src/observability/observability.test.ts +782 -0
- package/src/observability/synthetics.ts +568 -0
- package/src/observability/xray.ts +358 -0
- package/src/phone/advanced/analytics.ts +349 -0
- package/src/phone/advanced/callbacks.ts +428 -0
- package/src/phone/advanced/index.ts +8 -0
- package/src/phone/advanced/ivr-builder.ts +504 -0
- package/src/phone/advanced/recording.ts +310 -0
- package/src/phone/handlers/__tests__/incoming-call.test.ts +40 -0
- package/src/phone/handlers/incoming-call.ts +117 -0
- package/src/phone/handlers/missed-call.ts +116 -0
- package/src/phone/handlers/voicemail.ts +179 -0
- package/src/phone/index.ts +9 -0
- package/src/presets/api-backend.ts +134 -0
- package/src/presets/data-pipeline.ts +204 -0
- package/src/presets/extend.test.ts +295 -0
- package/src/presets/extend.ts +297 -0
- package/src/presets/fullstack-app.ts +144 -0
- package/src/presets/index.ts +27 -0
- package/src/presets/jamstack.ts +135 -0
- package/src/presets/microservices.ts +167 -0
- package/src/presets/ml-api.ts +208 -0
- package/src/presets/nodejs-server.ts +104 -0
- package/src/presets/nodejs-serverless.ts +114 -0
- package/src/presets/realtime-app.ts +184 -0
- package/src/presets/static-site.ts +64 -0
- package/src/presets/traditional-web-app.ts +339 -0
- package/src/presets/wordpress.ts +138 -0
- package/src/preview/github.test.ts +249 -0
- package/src/preview/github.ts +297 -0
- package/src/preview/index.ts +37 -0
- package/src/preview/manager.test.ts +440 -0
- package/src/preview/manager.ts +326 -0
- package/src/preview/notifications.test.ts +582 -0
- package/src/preview/notifications.ts +341 -0
- package/src/queue/batch-processing.ts +402 -0
- package/src/queue/dlq-monitoring.ts +402 -0
- package/src/queue/fifo.ts +342 -0
- package/src/queue/index.ts +9 -0
- package/src/queue/management.ts +428 -0
- package/src/queue/queue.test.ts +429 -0
- package/src/resource-mgmt/index.ts +39 -0
- package/src/resource-naming.ts +62 -0
- package/src/s3/index.ts +523 -0
- package/src/schema/cloud-config.schema.json +554 -0
- package/src/schema/index.ts +68 -0
- package/src/security/certificate-manager.ts +492 -0
- package/src/security/index.ts +9 -0
- package/src/security/scanning.ts +545 -0
- package/src/security/secrets-manager.ts +476 -0
- package/src/security/secrets-rotation.ts +456 -0
- package/src/security/security.test.ts +738 -0
- package/src/sms/advanced/ab-testing.ts +389 -0
- package/src/sms/advanced/analytics.ts +336 -0
- package/src/sms/advanced/campaigns.ts +523 -0
- package/src/sms/advanced/chatbot.ts +224 -0
- package/src/sms/advanced/index.ts +10 -0
- package/src/sms/advanced/link-tracking.ts +248 -0
- package/src/sms/advanced/mms.ts +308 -0
- package/src/sms/handlers/__tests__/send.test.ts +40 -0
- package/src/sms/handlers/delivery-status.ts +133 -0
- package/src/sms/handlers/receive.ts +162 -0
- package/src/sms/handlers/send.ts +174 -0
- package/src/sms/index.ts +9 -0
- package/src/stack-diff.ts +389 -0
- package/src/static-site/index.ts +85 -0
- package/src/template-builder.ts +110 -0
- package/src/template-validator.ts +574 -0
- package/src/utils/cache.ts +291 -0
- package/src/utils/diff.ts +269 -0
- package/src/utils/hash.ts +227 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/parallel.ts +294 -0
- package/src/validators/credentials.test.ts +274 -0
- package/src/validators/credentials.ts +233 -0
- package/src/validators/quotas.test.ts +434 -0
- package/src/validators/quotas.ts +217 -0
- package/test/ai.test.ts +327 -0
- package/test/api.test.ts +511 -0
- package/test/auth.test.ts +632 -0
- package/test/cache.test.ts +406 -0
- package/test/cdn.test.ts +247 -0
- package/test/compute.test.ts +861 -0
- package/test/database.test.ts +523 -0
- package/test/deployment.test.ts +499 -0
- package/test/dns.test.ts +270 -0
- package/test/email.test.ts +439 -0
- package/test/filesystem.test.ts +382 -0
- package/test/integration.test.ts +350 -0
- package/test/messaging.test.ts +514 -0
- package/test/monitoring.test.ts +634 -0
- package/test/network.test.ts +425 -0
- package/test/permissions.test.ts +488 -0
- package/test/queue.test.ts +484 -0
- package/test/registry.test.ts +306 -0
- package/test/security.test.ts +462 -0
- package/test/storage.test.ts +463 -0
- package/test/template-validator.test.ts +559 -0
- package/test/workflow.test.ts +592 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command history with search and persistence
|
|
3
|
+
* Tracks executed commands and provides search/replay functionality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface HistoryEntry {
|
|
7
|
+
command: string
|
|
8
|
+
timestamp: Date
|
|
9
|
+
success: boolean
|
|
10
|
+
duration?: number
|
|
11
|
+
output?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface HistoryOptions {
|
|
15
|
+
maxSize?: number
|
|
16
|
+
persistFile?: string
|
|
17
|
+
trackOutput?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Command history manager
|
|
22
|
+
*/
|
|
23
|
+
export class CommandHistory {
|
|
24
|
+
private entries: HistoryEntry[] = []
|
|
25
|
+
private maxSize: number
|
|
26
|
+
private persistFile?: string
|
|
27
|
+
private trackOutput: boolean
|
|
28
|
+
|
|
29
|
+
constructor(options: HistoryOptions = {}) {
|
|
30
|
+
this.maxSize = options.maxSize || 1000
|
|
31
|
+
this.persistFile = options.persistFile
|
|
32
|
+
this.trackOutput = options.trackOutput || false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Add command to history
|
|
37
|
+
*/
|
|
38
|
+
add(entry: Omit<HistoryEntry, 'timestamp'>): void {
|
|
39
|
+
this.entries.push({
|
|
40
|
+
...entry,
|
|
41
|
+
timestamp: new Date(),
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// Trim if exceeds max size
|
|
45
|
+
if (this.entries.length > this.maxSize) {
|
|
46
|
+
this.entries = this.entries.slice(-this.maxSize)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get all history entries
|
|
52
|
+
*/
|
|
53
|
+
getAll(): HistoryEntry[] {
|
|
54
|
+
return [...this.entries]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get recent entries
|
|
59
|
+
*/
|
|
60
|
+
getRecent(count: number = 10): HistoryEntry[] {
|
|
61
|
+
return this.entries.slice(-count)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Search history by command text
|
|
66
|
+
*/
|
|
67
|
+
search(query: string): HistoryEntry[] {
|
|
68
|
+
const lowerQuery = query.toLowerCase()
|
|
69
|
+
|
|
70
|
+
return this.entries.filter(entry =>
|
|
71
|
+
entry.command.toLowerCase().includes(lowerQuery),
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Search history by date range
|
|
77
|
+
*/
|
|
78
|
+
searchByDate(startDate: Date, endDate: Date): HistoryEntry[] {
|
|
79
|
+
return this.entries.filter(
|
|
80
|
+
entry => entry.timestamp >= startDate && entry.timestamp <= endDate,
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get successful commands
|
|
86
|
+
*/
|
|
87
|
+
getSuccessful(): HistoryEntry[] {
|
|
88
|
+
return this.entries.filter(entry => entry.success)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get failed commands
|
|
93
|
+
*/
|
|
94
|
+
getFailed(): HistoryEntry[] {
|
|
95
|
+
return this.entries.filter(entry => !entry.success)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get most used commands
|
|
100
|
+
*/
|
|
101
|
+
getMostUsed(count: number = 10): Array<{ command: string; count: number }> {
|
|
102
|
+
const commandCounts: Map<string, number> = new Map()
|
|
103
|
+
|
|
104
|
+
for (const entry of this.entries) {
|
|
105
|
+
const baseCommand = entry.command.split(' ')[0]
|
|
106
|
+
commandCounts.set(baseCommand, (commandCounts.get(baseCommand) || 0) + 1)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return Array.from(commandCounts.entries())
|
|
110
|
+
.map(([command, count]) => ({ command, count }))
|
|
111
|
+
.sort((a, b) => b.count - a.count)
|
|
112
|
+
.slice(0, count)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get statistics
|
|
117
|
+
*/
|
|
118
|
+
getStats(): {
|
|
119
|
+
total: number
|
|
120
|
+
successful: number
|
|
121
|
+
failed: number
|
|
122
|
+
averageDuration: number
|
|
123
|
+
mostUsed: string
|
|
124
|
+
} {
|
|
125
|
+
const total = this.entries.length
|
|
126
|
+
const successful = this.entries.filter(e => e.success).length
|
|
127
|
+
const failed = this.entries.filter(e => !e.success).length
|
|
128
|
+
|
|
129
|
+
const durationsWithValue = this.entries.filter(e => e.duration !== undefined)
|
|
130
|
+
const averageDuration = durationsWithValue.length > 0
|
|
131
|
+
? durationsWithValue.reduce((sum, e) => sum + (e.duration || 0), 0) / durationsWithValue.length
|
|
132
|
+
: 0
|
|
133
|
+
|
|
134
|
+
const mostUsedList = this.getMostUsed(1)
|
|
135
|
+
const mostUsed = mostUsedList.length > 0 ? mostUsedList[0].command : 'N/A'
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
total,
|
|
139
|
+
successful,
|
|
140
|
+
failed,
|
|
141
|
+
averageDuration,
|
|
142
|
+
mostUsed,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Clear history
|
|
148
|
+
*/
|
|
149
|
+
clear(): void {
|
|
150
|
+
this.entries = []
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get entry by index
|
|
155
|
+
*/
|
|
156
|
+
getByIndex(index: number): HistoryEntry | undefined {
|
|
157
|
+
if (index < 0 || index >= this.entries.length) {
|
|
158
|
+
return undefined
|
|
159
|
+
}
|
|
160
|
+
return this.entries[index]
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Remove entry by index
|
|
165
|
+
*/
|
|
166
|
+
removeByIndex(index: number): void {
|
|
167
|
+
if (index >= 0 && index < this.entries.length) {
|
|
168
|
+
this.entries.splice(index, 1)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Save history to file
|
|
174
|
+
*/
|
|
175
|
+
async save(): Promise<void> {
|
|
176
|
+
if (!this.persistFile) return
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const fs = await import('node:fs/promises')
|
|
180
|
+
const data = JSON.stringify(this.entries, null, 2)
|
|
181
|
+
await fs.writeFile(this.persistFile, data, 'utf-8')
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
throw new Error(`Failed to save history: ${error}`)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Load history from file
|
|
190
|
+
*/
|
|
191
|
+
async load(): Promise<void> {
|
|
192
|
+
if (!this.persistFile) return
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const fs = await import('node:fs/promises')
|
|
196
|
+
const data = await fs.readFile(this.persistFile, 'utf-8')
|
|
197
|
+
const parsed = JSON.parse(data)
|
|
198
|
+
|
|
199
|
+
this.entries = parsed.map((entry: any) => ({
|
|
200
|
+
...entry,
|
|
201
|
+
timestamp: new Date(entry.timestamp),
|
|
202
|
+
}))
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// File doesn't exist or can't be read - that's ok
|
|
206
|
+
this.entries = []
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Export history as JSON
|
|
212
|
+
*/
|
|
213
|
+
exportJSON(): string {
|
|
214
|
+
return JSON.stringify(this.entries, null, 2)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Export history as CSV
|
|
219
|
+
*/
|
|
220
|
+
exportCSV(): string {
|
|
221
|
+
const header = 'Timestamp,Command,Success,Duration'
|
|
222
|
+
const rows = this.entries.map(entry =>
|
|
223
|
+
[
|
|
224
|
+
entry.timestamp.toISOString(),
|
|
225
|
+
`"${entry.command.replace(/"/g, '""')}"`, // Escape quotes
|
|
226
|
+
entry.success,
|
|
227
|
+
entry.duration || '',
|
|
228
|
+
].join(','),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
return [header, ...rows].join('\n')
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Import history from JSON
|
|
236
|
+
*/
|
|
237
|
+
importJSON(json: string): void {
|
|
238
|
+
const parsed = JSON.parse(json)
|
|
239
|
+
|
|
240
|
+
const imported = parsed.map((entry: any) => ({
|
|
241
|
+
...entry,
|
|
242
|
+
timestamp: new Date(entry.timestamp),
|
|
243
|
+
}))
|
|
244
|
+
|
|
245
|
+
this.entries.push(...imported)
|
|
246
|
+
|
|
247
|
+
// Trim if exceeds max size
|
|
248
|
+
if (this.entries.length > this.maxSize) {
|
|
249
|
+
this.entries = this.entries.slice(-this.maxSize)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get history grouped by date
|
|
255
|
+
*/
|
|
256
|
+
groupByDate(): Map<string, HistoryEntry[]> {
|
|
257
|
+
const grouped: Map<string, HistoryEntry[]> = new Map()
|
|
258
|
+
|
|
259
|
+
for (const entry of this.entries) {
|
|
260
|
+
const dateKey = entry.timestamp.toISOString().split('T')[0]
|
|
261
|
+
|
|
262
|
+
if (!grouped.has(dateKey)) {
|
|
263
|
+
grouped.set(dateKey, [])
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
grouped.get(dateKey)!.push(entry)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return grouped
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Get history grouped by command
|
|
274
|
+
*/
|
|
275
|
+
groupByCommand(): Map<string, HistoryEntry[]> {
|
|
276
|
+
const grouped: Map<string, HistoryEntry[]> = new Map()
|
|
277
|
+
|
|
278
|
+
for (const entry of this.entries) {
|
|
279
|
+
const baseCommand = entry.command.split(' ')[0]
|
|
280
|
+
|
|
281
|
+
if (!grouped.has(baseCommand)) {
|
|
282
|
+
grouped.set(baseCommand, [])
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
grouped.get(baseCommand)!.push(entry)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return grouped
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Replay a command from history
|
|
293
|
+
*/
|
|
294
|
+
replay(index: number): string | undefined {
|
|
295
|
+
const entry = this.getByIndex(index)
|
|
296
|
+
return entry?.command
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get suggestions based on partial input
|
|
301
|
+
*/
|
|
302
|
+
getSuggestions(partial: string, limit: number = 5): string[] {
|
|
303
|
+
const lowerPartial = partial.toLowerCase()
|
|
304
|
+
|
|
305
|
+
// Find commands that start with the partial
|
|
306
|
+
const matches = this.entries
|
|
307
|
+
.filter(entry => entry.command.toLowerCase().startsWith(lowerPartial))
|
|
308
|
+
.map(entry => entry.command)
|
|
309
|
+
|
|
310
|
+
// Remove duplicates and limit
|
|
311
|
+
return [...new Set(matches)].slice(0, limit)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Analyze command patterns
|
|
316
|
+
*/
|
|
317
|
+
analyzePatterns(): {
|
|
318
|
+
timeOfDay: Map<number, number> // hour -> count
|
|
319
|
+
dayOfWeek: Map<number, number> // day -> count
|
|
320
|
+
successRate: number
|
|
321
|
+
} {
|
|
322
|
+
const timeOfDay: Map<number, number> = new Map()
|
|
323
|
+
const dayOfWeek: Map<number, number> = new Map()
|
|
324
|
+
|
|
325
|
+
for (const entry of this.entries) {
|
|
326
|
+
const hour = entry.timestamp.getHours()
|
|
327
|
+
const day = entry.timestamp.getDay()
|
|
328
|
+
|
|
329
|
+
timeOfDay.set(hour, (timeOfDay.get(hour) || 0) + 1)
|
|
330
|
+
dayOfWeek.set(day, (dayOfWeek.get(day) || 0) + 1)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const successRate = this.entries.length > 0
|
|
334
|
+
? this.getSuccessful().length / this.entries.length
|
|
335
|
+
: 0
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
timeOfDay,
|
|
339
|
+
dayOfWeek,
|
|
340
|
+
successRate,
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Format history for display
|
|
347
|
+
*/
|
|
348
|
+
export function formatHistory(entries: HistoryEntry[], options: { maxWidth?: number } = {}): string {
|
|
349
|
+
const { maxWidth = 100 } = options
|
|
350
|
+
|
|
351
|
+
if (entries.length === 0) {
|
|
352
|
+
return 'No command history'
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const lines: string[] = []
|
|
356
|
+
|
|
357
|
+
for (let i = 0; i < entries.length; i++) {
|
|
358
|
+
const entry = entries[i]
|
|
359
|
+
const index = (i + 1).toString().padStart(4)
|
|
360
|
+
const timestamp = entry.timestamp.toLocaleString()
|
|
361
|
+
const status = entry.success ? '✓' : '✗'
|
|
362
|
+
|
|
363
|
+
let command = entry.command
|
|
364
|
+
if (command.length > maxWidth - 30) {
|
|
365
|
+
command = command.substring(0, maxWidth - 33) + '...'
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
let line = `${index} ${timestamp} ${status} ${command}`
|
|
369
|
+
|
|
370
|
+
if (entry.duration) {
|
|
371
|
+
line += ` (${entry.duration}ms)`
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
lines.push(line)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return lines.join('\n')
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Format history statistics
|
|
382
|
+
*/
|
|
383
|
+
export function formatHistoryStats(stats: ReturnType<CommandHistory['getStats']>): string {
|
|
384
|
+
const lines: string[] = []
|
|
385
|
+
|
|
386
|
+
lines.push('Command History Statistics')
|
|
387
|
+
lines.push('─'.repeat(30))
|
|
388
|
+
lines.push(`Total commands: ${stats.total}`)
|
|
389
|
+
lines.push(`Successful: ${stats.successful}`)
|
|
390
|
+
lines.push(`Failed: ${stats.failed}`)
|
|
391
|
+
lines.push(`Success rate: ${((stats.successful / stats.total) * 100).toFixed(1)}%`)
|
|
392
|
+
lines.push(`Average duration: ${stats.averageDuration.toFixed(0)}ms`)
|
|
393
|
+
lines.push(`Most used: ${stats.mostUsed}`)
|
|
394
|
+
|
|
395
|
+
return lines.join('\n')
|
|
396
|
+
}
|
package/src/cli/index.ts
ADDED