Skip to content

Commit dc0ba2c

Browse files
author
Kevin Allioli
committed
feat(powershell): add Remove-GhostbitPaste (gbd) and Get-GhostbitHistory (gbh)
- Remove-GhostbitPaste — reads delete token from URL fragment, sends DELETE /api/v1/pastes/{id} with X-Delete-Token header - Get-GhostbitHistory — tabular display of local paste history (ID, language, age, expiry, full URL) - Get-GhostbitHistory -Clear — wipes history file - New-GhostbitPaste now appends each created paste to local history (best-effort, never blocks; Windows: %LOCALAPPDATA%\ghostbit\history.jsonl macOS/Linux: ~/.local/share/ghostbit/history.jsonl)
1 parent 084cffc commit dc0ba2c

3 files changed

Lines changed: 192 additions & 3 deletions

File tree

cli/powershell/Ghostbit.psd1

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
FunctionsToExport = @(
1111
'New-GhostbitPaste'
1212
'Get-GhostbitPaste'
13+
'Remove-GhostbitPaste'
14+
'Get-GhostbitHistory'
1315
'Invoke-GhostbitConfig'
1416
)
15-
AliasesToExport = @('gb', 'gbv')
17+
AliasesToExport = @('gb', 'gbv', 'gbd', 'gbh')
1618
CmdletsToExport = @()
1719
VariablesToExport = @()
1820

cli/powershell/Ghostbit.psm1

Lines changed: 157 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ $script:ConfigPath = if ($IsWindows) {
2929
} else {
3030
Join-Path $HOME '.config/ghostbit.toml'
3131
}
32+
$script:HistoryPath = if ($IsWindows) {
33+
Join-Path $env:LOCALAPPDATA 'ghostbit\history.jsonl'
34+
} else {
35+
Join-Path $HOME '.local/share/ghostbit/history.jsonl'
36+
}
3237

3338
# ── Config ────────────────────────────────────────────────────────────────────
3439

@@ -302,6 +307,21 @@ function New-GhostbitPaste {
302307
}
303308
$fullUrl = "$($response.url)#$fragment"
304309

310+
# ── Append to local history (best-effort, never blocks) ──
311+
try {
312+
$dir = Split-Path $script:HistoryPath -Parent
313+
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
314+
$entry = [ordered]@{
315+
id = $response.id
316+
url = $response.url
317+
full_url = $fullUrl
318+
created_at = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
319+
language = if ($Language) { $Language } else { $null }
320+
expires_at = $response.expires_at
321+
}
322+
Add-Content -Path $script:HistoryPath -Value ($entry | ConvertTo-Json -Compress) -Encoding UTF8
323+
} catch { }
324+
305325
if ($AsJson) {
306326
$response | Add-Member -NotePropertyName full_url -NotePropertyValue $fullUrl -Force
307327
$response | ConvertTo-Json
@@ -476,7 +496,142 @@ function Invoke-GhostbitConfig {
476496
}
477497
}
478498

499+
# ── Remove-GhostbitPaste ─────────────────────────────────────────────────────
500+
501+
function Remove-GhostbitPaste {
502+
<#
503+
.SYNOPSIS
504+
Delete a paste using the delete token embedded in its URL.
505+
506+
.PARAMETER Url
507+
Full paste URL including the #fragment (KEY~DELETE_TOKEN or ~DELETE_TOKEN).
508+
509+
.EXAMPLE
510+
Remove-GhostbitPaste "https://paste.example.com/abc123#KEY~TOKEN"
511+
gbd "https://paste.example.com/abc123#KEY~TOKEN"
512+
#>
513+
[CmdletBinding()]
514+
[Alias('gbd')]
515+
param(
516+
[Parameter(Mandatory, Position = 0)]
517+
[string]$Url
518+
)
519+
520+
$uri = [System.Uri]$Url
521+
$fragment = $uri.Fragment.TrimStart('#')
522+
$serverUrl = "$($uri.Scheme)://$($uri.Authority)"
523+
$pasteId = $uri.AbsolutePath.Trim('/')
524+
$deleteToken = $fragment.Split('~', 2)[1]
525+
526+
if ([string]::IsNullOrEmpty($deleteToken)) {
527+
Write-Error 'Delete token missing from URL fragment (expected KEY~TOKEN or ~TOKEN).'
528+
return
529+
}
530+
531+
$apiUrl = "$serverUrl/api/v1/pastes/$pasteId"
532+
try {
533+
Invoke-RestMethod -Uri $apiUrl -Method Delete `
534+
-Headers @{ 'User-Agent' = $script:UserAgent; 'X-Delete-Token' = $deleteToken } | Out-Null
535+
Write-Host "Deleted $pasteId."
536+
} catch {
537+
$code = $_.Exception.Response.StatusCode.value__
538+
switch ($code) {
539+
403 { Write-Error 'Invalid delete token.' }
540+
404 { Write-Error 'Paste not found (already deleted or expired).' }
541+
default { Write-Error "Error $code`: $($_.ErrorDetails.Message ?? $_.Exception.Message)" }
542+
}
543+
}
544+
}
545+
546+
# ── Get-GhostbitHistory ───────────────────────────────────────────────────────
547+
548+
function Get-GhostbitHistory {
549+
<#
550+
.SYNOPSIS
551+
List pastes created on this machine, or clear the local history.
552+
553+
.DESCRIPTION
554+
History is stored locally at:
555+
Windows : %LOCALAPPDATA%\ghostbit\history.jsonl
556+
macOS/Linux : ~/.local/share/ghostbit/history.jsonl
557+
558+
Nothing is sent to the server — this file stays on your machine only.
559+
560+
.PARAMETER Clear
561+
Wipe the local history file.
562+
563+
.EXAMPLE
564+
Get-GhostbitHistory
565+
Get-GhostbitHistory -Clear
566+
gbh
567+
#>
568+
[CmdletBinding()]
569+
[Alias('gbh')]
570+
param(
571+
[switch]$Clear
572+
)
573+
574+
if ($Clear) {
575+
if (Test-Path $script:HistoryPath) {
576+
Remove-Item $script:HistoryPath -Force
577+
Write-Host 'History cleared.'
578+
} else {
579+
Write-Host 'No history file found.'
580+
}
581+
return
582+
}
583+
584+
if (-not (Test-Path $script:HistoryPath)) {
585+
Write-Host 'No pastes in local history.' -ForegroundColor DarkGray
586+
Write-Host " History file: $script:HistoryPath" -ForegroundColor DarkGray
587+
return
588+
}
589+
590+
$entries = Get-Content $script:HistoryPath -Encoding UTF8 |
591+
Where-Object { $_ -match '\S' } |
592+
ForEach-Object { $_ | ConvertFrom-Json }
593+
594+
if (-not $entries) {
595+
Write-Host 'No pastes in local history.' -ForegroundColor DarkGray
596+
return
597+
}
598+
599+
$now = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
600+
601+
function Format-Age([long]$ts) {
602+
$d = $now - $ts
603+
if ($d -lt 120) { return 'just now' }
604+
if ($d -lt 3600) { return "$([int]($d/60))m ago" }
605+
if ($d -lt 86400) { return "$([int]($d/3600))h ago" }
606+
return "$([int]($d/86400))d ago"
607+
}
608+
609+
function Format-Expiry($exp) {
610+
if (-not $exp) { return 'never' }
611+
$d = $exp - $now
612+
if ($d -le 0) { return 'expired' }
613+
if ($d -lt 3600) { return "in $([int]($d/60))m" }
614+
if ($d -lt 86400) { return "in $([int]($d/3600))h" }
615+
return "in $([int]($d/86400))d"
616+
}
617+
618+
$header = '{0,-12} {1,-14} {2,-12} {3,-10} {4}' -f 'ID', 'Lang', 'Created', 'Expires', 'URL'
619+
Write-Host $header
620+
Write-Host ('-' * 80)
621+
622+
[array]::Reverse(($entries = @($entries)))
623+
foreach ($e in $entries) {
624+
$id = ([string]$e.id).PadRight(12).Substring(0, [Math]::Min(12, ([string]$e.id).Length)).PadRight(12)
625+
$lang = ([string]($e.language ?? 'plain')).PadRight(14).Substring(0, [Math]::Min(14, ([string]($e.language ?? 'plain')).Length)).PadRight(14)
626+
$created = (Format-Age $e.created_at).PadRight(12)
627+
$expires = (Format-Expiry $e.expires_at).PadRight(10)
628+
$url = $e.full_url ?? $e.url
629+
Write-Host ('{0} {1} {2} {3} {4}' -f $id, $lang, $created, $expires, $url)
630+
}
631+
}
632+
479633
# ── Exports ───────────────────────────────────────────────────────────────────
480634

481-
Export-ModuleMember -Function New-GhostbitPaste, Get-GhostbitPaste, Invoke-GhostbitConfig `
482-
-Alias gb, gbv
635+
Export-ModuleMember -Function New-GhostbitPaste, Get-GhostbitPaste, Remove-GhostbitPaste, `
636+
Get-GhostbitHistory, Invoke-GhostbitConfig `
637+
-Alias gb, gbv, gbd, gbh

docs/cli-powershell.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,38 @@ gbv "https://paste.example.com/abc123#KEY~TOKEN" | more
102102

103103
---
104104

105+
## Delete a paste
106+
107+
```powershell
108+
Remove-GhostbitPaste "https://paste.example.com/abc123#KEY~TOKEN"
109+
gbd "https://paste.example.com/abc123#KEY~TOKEN"
110+
```
111+
112+
The delete token is read from the URL fragment (after `~`).
113+
114+
---
115+
116+
## Paste history
117+
118+
All created pastes are saved locally. Nothing is sent to the server.
119+
120+
| Platform | Path |
121+
|----------|------|
122+
| Windows | `%LOCALAPPDATA%\ghostbit\history.jsonl` |
123+
| macOS / Linux | `~/.local/share/ghostbit/history.jsonl` |
124+
125+
```powershell
126+
# List recent pastes
127+
Get-GhostbitHistory
128+
gbh
129+
130+
# Wipe local history
131+
Get-GhostbitHistory -Clear
132+
gbh -Clear
133+
```
134+
135+
---
136+
105137
## Examples
106138

107139
```powershell

0 commit comments

Comments
 (0)