Add timezone date fix for blog-backup

This commit is contained in:
OpenClaw Bot 2026-02-26 16:56:16 -06:00
parent 6185f2bf6b
commit dba7cfe57e
14 changed files with 846 additions and 14 deletions

View File

@ -1,3 +1,5 @@
[
"obsidian-git"
"obsidian-git",
"obsidian42-brat",
"qmd-search"
]

View File

@ -0,0 +1,5 @@
{
"appliedMigrations": [
"tokens-to-secretstorage-v1"
]
}

View File

@ -0,0 +1,24 @@
{
"pluginList": [
"achekulaev/obsidian-qmd"
],
"pluginSubListFrozenVersion": [
{
"repo": "achekulaev/obsidian-qmd",
"version": ""
}
],
"themesList": [],
"updateAtStartup": true,
"updateThemesAtStartup": true,
"enableAfterInstall": true,
"loggingEnabled": false,
"loggingPath": "BRAT-log",
"loggingVerboseEnabled": false,
"debuggingMode": false,
"notificationsEnabled": true,
"globalTokenName": "",
"personalAccessToken": "",
"selectLatestPluginVersionByDefault": false,
"allowIncompatiblePlugins": false
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,14 @@
{
"id": "obsidian42-brat",
"name": "BRAT",
"version": "2.0.2",
"minAppVersion": "1.11.4",
"description": "Easily install a beta version of a plugin for testing.",
"author": "TfTHacker",
"authorUrl": "https://github.com/TfTHacker/obsidian42-brat",
"helpUrl": "https://tfthacker.com/BRAT",
"isDesktopOnly": false,
"fundingUrl": {
"Visit my site": "https://tfthacker.com"
}
}

View File

@ -0,0 +1,152 @@
.brat-modal .modal-button-container {
margin-top: 5px;
}
.brat-modal .disabled-setting {
opacity: 0.5;
}
.brat-modal .disabled-setting:hover {
cursor: not-allowed;
}
/* Input validation styles */
.brat-settings .valid-input,
.brat-modal .valid-repository {
border-color: var(--color-green);
}
.brat-settings .invalid-input,
.brat-modal .invalid-repository {
border-color: var(--color-red);
}
.brat-settings .validation-error,
.brat-modal .validation-error {
border-color: var(--color-orange);
}
/* Version selector */
.brat-version-selector {
width: 100%;
max-width: 400px;
justify-content: left;
}
.brat-token-input {
min-width: 33%;
}
/* Token info container styles */
.brat-token-info {
margin-top: 8px;
font-size: 0.8em;
padding: 8px;
border-radius: 4px;
background-color: var(--background-secondary);
}
/* Token status indicators */
.brat-token-info.valid,
.brat-token-status.valid {
color: var(--color-green);
}
.brat-token-info.invalid,
.brat-token-status.invalid {
color: var(--color-red);
}
.brat-token-info.valid {
border-left: 3px solid var(--color-green);
}
.brat-token-info.invalid {
border-left: 3px solid var(--color-red);
}
/* Token details and status */
.brat-token-status {
margin-bottom: 4px;
}
.brat-token-details {
margin-top: 4px;
color: var(--text-muted);
}
/* Token warnings */
.brat-token-warning {
color: var(--color-orange);
margin-top: 4px;
}
/* Token additional info */
.brat-token-scopes,
.brat-token-rate {
color: var(--text-muted);
margin-top: 2px;
}
/* Flex break utility */
.brat-modal .break {
flex-basis: 100%;
height: 0;
}
/* Validation status */
.brat-modal .validation-status-error {
color: var(--text-error);
}
.brat-modal .validation-status {
margin-top: 0.5em;
margin-bottom: 0.5em;
font-size: 0.8em;
text-align: left;
}
.confirm-modal .ok-button {
margin-right: 10px;
margin-top: 20px;
}
/* Hide filtered plugin items */
.brat-plugin-item[hidden] {
display: none !important;
}
/* Hide filtered theme items */
.brat-theme-item[hidden] {
display: none !important;
}
/* Filter and button layout */
.brat-filter-and-button {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin: 0.75em 0;
}
.brat-filter-input {
max-width: 300px;
padding: 4px 8px;
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
background-color: var(--background-secondary);
color: var(--text-normal);
}
.brat-filter-input:focus {
outline: none;
border-color: var(--interactive-accent);
}
.brat-filter-and-button .setting-item {
border: none;
padding: 0;
}
.brat-filter-and-button .setting-item-control {
justify-content: flex-end;
}

21
.obsidian/plugins/qmd-search/data.json vendored Normal file
View File

@ -0,0 +1,21 @@
{
"qmdBinaryPath": "/opt/homebrew/bin/qmd",
"collectionName": "",
"indexName": null,
"fileMask": "**/*.md",
"debounceMs": 45000,
"enablePeriodicUpdates": true,
"periodicUpdateMinutes": 15,
"defaultSearchMode": "semantic",
"fallbackOnSemanticFailure": true,
"fallbackOnZeroResults": false,
"showEmbeddingsBanner": true,
"autoGenerateEmbeddings": true,
"enableRibbonIcon": true,
"enableSearchPane": false,
"showScoresInResults": true,
"lastIndexUpdateTime": "2026-02-26T22:48:31.431Z",
"lastEmbeddingRunTime": "2026-02-26T22:48:32.734Z",
"lastSearchMode": "semantic",
"lastError": null
}

8
.obsidian/plugins/qmd-search/main.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"id":"qmd-search","name":"QMD Semantic Search","version":"1.1.3","minAppVersion":"1.11.0","description":"Semantic-first search for your vault using QMD (Quick Markdown Search). Provides vector-based semantic search with keyword fallback.","author":"Oleksii Chekulaiev","authorUrl":"https://github.com/achekulaev","isDesktopOnly":true}

312
.obsidian/plugins/qmd-search/styles.css vendored Normal file
View File

@ -0,0 +1,312 @@
/**
* QMD Semantic Search - Obsidian Plugin Styles
*
* These styles are loaded by Obsidian automatically when the plugin is enabled.
*/
/* Utility */
.qmd-hidden {
display: none !important;
}
/* Search Modal - Input */
.qmd-input-with-pill {
padding-right: 120px !important;
}
/* Search Modal - Results */
.qmd-search-result {
padding: 8px 12px;
}
.qmd-search-result-title {
display: flex;
align-items: baseline;
font-weight: 500;
margin-bottom: 2px;
}
.qmd-search-result-name {
color: var(--text-normal);
}
.qmd-search-result-path {
font-size: 0.85em;
color: var(--text-muted);
margin-bottom: 4px;
}
.qmd-search-result-snippet {
font-size: 0.9em;
color: var(--text-muted);
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.qmd-search-result-score {
font-weight: normal;
font-style: italic;
font-size: 0.75em;
color: var(--text-faint);
}
.qmd-search-result-meta {
display: flex;
gap: 12px;
font-size: 0.8em;
color: var(--text-faint);
}
.qmd-search-result-mode {
padding: 1px 6px;
border-radius: 3px;
background: var(--background-modifier-hover);
}
.qmd-mode-semantic {
color: var(--text-accent);
}
.qmd-mode-keyword {
color: var(--text-muted);
}
/* Search mode pill inside input field */
.qmd-search-mode-indicator {
position: absolute;
right: 44px;
top: 50%;
transform: translateY(-50%);
padding: 2px 8px;
font-size: 0.75em;
color: rgba(255, 255, 255, 0.85);
background: rgba(var(--interactive-accent-rgb, 124, 77, 255), 0.5);
border-radius: 10px;
pointer-events: none;
white-space: nowrap;
}
/* Search Modal - Error display */
.qmd-search-error {
padding: 12px;
background: var(--background-modifier-error);
border-radius: 4px;
cursor: default;
}
.qmd-search-error-title {
color: var(--text-error);
font-weight: 500;
}
.qmd-search-error-hint {
font-size: 0.9em;
color: var(--text-muted);
margin-top: 4px;
}
/* Search Modal - Progress bar */
.qmd-progress-container {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
background: var(--background-secondary);
border-bottom: 1px solid var(--background-modifier-border);
}
.qmd-progress-bar {
flex: 1;
height: 3px;
background: var(--background-modifier-border);
border-radius: 2px;
overflow: hidden;
}
.qmd-progress-bar-fill {
height: 100%;
width: 30%;
background: var(--text-accent);
border-radius: 2px;
animation: qmd-progress-slide 1s ease-in-out infinite;
}
@keyframes qmd-progress-slide {
0% { transform: translateX(-100%); }
50% { transform: translateX(333%); }
100% { transform: translateX(-100%); }
}
.qmd-progress-text {
font-size: 0.8em;
color: var(--text-muted);
white-space: nowrap;
}
/* Search Pane */
.qmd-search-pane {
display: flex;
flex-direction: column;
height: 100%;
padding: 8px;
}
.qmd-search-input-container {
margin-bottom: 8px;
}
.qmd-search-input-wrapper {
display: flex;
align-items: center;
background: var(--background-modifier-form-field);
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
padding: 4px 8px;
}
.qmd-search-icon {
color: var(--text-muted);
margin-right: 8px;
}
.qmd-search-input {
flex: 1;
border: none;
background: transparent;
outline: none;
color: var(--text-normal);
}
.qmd-search-status {
padding: 4px 8px;
font-size: 0.85em;
margin-bottom: 8px;
border-radius: 4px;
}
.qmd-status-loading {
color: var(--text-muted);
background: var(--background-modifier-hover);
}
.qmd-status-success {
color: var(--text-success);
}
.qmd-status-error {
color: var(--text-error);
background: var(--background-modifier-error);
}
.qmd-search-results {
flex: 1;
overflow-y: auto;
}
.qmd-search-result-item {
padding: 8px;
margin-bottom: 4px;
border-radius: 4px;
cursor: pointer;
background: var(--background-secondary);
}
.qmd-search-result-item:hover {
background: var(--background-modifier-hover);
}
.qmd-result-title {
font-weight: 500;
color: var(--text-normal);
margin-bottom: 2px;
}
.qmd-result-path {
font-size: 0.85em;
color: var(--text-muted);
margin-bottom: 4px;
}
.qmd-result-snippet {
font-size: 0.9em;
color: var(--text-muted);
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.qmd-result-score {
font-size: 0.8em;
color: var(--text-faint);
}
.qmd-search-no-results {
padding: 16px;
text-align: center;
color: var(--text-muted);
}
/* Search Pane - Loading */
.qmd-pane-loading-row {
display: flex;
align-items: center;
gap: 8px;
}
.qmd-pane-spinner {
width: 12px;
height: 12px;
border: 2px solid var(--background-modifier-border);
border-top-color: var(--text-accent);
border-radius: 50%;
animation: qmd-pane-spin 0.8s linear infinite;
}
@keyframes qmd-pane-spin {
to { transform: rotate(360deg); }
}
/* Settings Tab */
.qmd-diagnostics {
padding: 12px;
background: var(--background-secondary);
border-radius: 4px;
margin-bottom: 16px;
}
.qmd-diagnostic-row {
display: flex;
justify-content: space-between;
padding: 4px 0;
}
.qmd-diagnostic-label {
font-weight: 500;
color: var(--text-muted);
}
.qmd-diagnostic-value {
color: var(--text-normal);
font-family: var(--font-monospace);
font-size: 0.9em;
}
/* Loading spinner animation */
@keyframes qmd-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.qmd-loading-spinner {
width: 16px;
height: 16px;
border: 2px solid var(--background-modifier-border);
border-top-color: var(--text-accent);
border-radius: 50%;
animation: qmd-spin 0.8s linear infinite;
}

View File

@ -13,12 +13,12 @@
"state": {
"type": "markdown",
"state": {
"file": "TOOLS.md",
"file": "memory/2026-02-26.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "TOOLS"
"title": "2026-02-26"
}
}
]
@ -95,7 +95,7 @@
"state": {
"type": "backlink",
"state": {
"file": "TOOLS.md",
"file": "memory/2026-02-26-alice-debug.md",
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical",
@ -105,7 +105,7 @@
"unlinkedCollapsed": true
},
"icon": "links-coming-in",
"title": "Backlinks for TOOLS"
"title": "Backlinks for 2026-02-26-alice-debug"
}
},
{
@ -114,12 +114,12 @@
"state": {
"type": "outgoing-link",
"state": {
"file": "TOOLS.md",
"file": "memory/2026-02-26-alice-debug.md",
"linksCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-going-out",
"title": "Outgoing links from TOOLS"
"title": "Outgoing links from 2026-02-26-alice-debug"
}
},
{
@ -157,16 +157,27 @@
"state": {
"type": "outline",
"state": {
"file": "TOOLS.md",
"file": "memory/2026-02-26-alice-debug.md",
"followCursor": false,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-list",
"title": "Outline of TOOLS"
"title": "Outline of 2026-02-26-alice-debug"
}
},
{
"id": "353be6f48ab39071",
"type": "leaf",
"state": {
"type": "git-view",
"state": {},
"icon": "git-pull-request",
"title": "Source Control"
}
}
]
],
"currentTab": 5
}
],
"direction": "horizontal",
@ -181,15 +192,26 @@
"daily-notes:Open today's daily note": false,
"templates:Insert template": false,
"command-palette:Open command palette": false,
"bases:Create new base": false
"bases:Create new base": false,
"obsidian-git:Open Git source control": false,
"obsidian42-brat:BRAT": false,
"qmd-search:QMD search": false
}
},
"active": "1914b3cb53aaf523",
"active": "09c562b295be8994",
"lastOpenFiles": [
"MEMORY.md",
"USER.md",
"scripts/daily-digest.sh",
"memory/2026-02-26-blog-fix.md",
"memory/2026-02-26-blog-api-fix.md",
"memory/2026-02-26-digest-failed.md",
"scripts/skrybe",
"scripts/skrybe.swift",
"AGENTS.md",
"HEARTBEAT.md",
"MEMORY.md",
"memory/2026-02-26-alice-debug.md",
"TOOLS.md",
"USER.md",
"Untitled 2.base",
"Untitled 1.base",
"Untitled.base"

View File

@ -11,6 +11,30 @@ Morning: Gantt Board for hardening (test if complete), git commit workspace. Bui
---
## [2026-02-26 16:46] ✅ Created daily-digest.sh script
### What was requested
Matt wanted a shell script to generate the daily digest reliably (to fix the broken cron job).
### What was done
- Created `/Users/mattbruce/.openclaw/workspace/scripts/daily-digest.sh`
- Script handles JSON escaping properly using `jq`
- Uses temp files to avoid argument length issues
- Checks for existing digest before creating
- Generates proper format with emojis and `[Read more →](URL)` links
- Can be run manually: `./daily-digest.sh [YYYY-MM-DD]`
### Usage
```bash
# Run for today
./scripts/daily-digest.sh
# Run for specific date
./scripts/daily-digest.sh 2026-02-26
```
---
## [2026-02-26 15:52] ✅ Fixed blog-backup skill to match mission-control-docs pattern
### What was requested

BIN
scripts/skrybe Executable file

Binary file not shown.

202
scripts/skrybe.swift Normal file
View File

@ -0,0 +1,202 @@
import Foundation
import AppKit
import CoreGraphics
import ApplicationServices
enum SkrybeError: LocalizedError {
case invalidUsage(String)
case unsupportedSubcommand(String)
case unsupportedQuotesOption(String)
case apiURLInvalid
case apiUnreachable(String)
case invalidHTTPStatus(Int)
case invalidResponse
case pasteboardWriteFailed
case accessibilityPermissionMissing
case eventCreationFailed
var errorDescription: String? {
switch self {
case .invalidUsage(let message):
return message
case .unsupportedSubcommand(let command):
return "Unsupported subcommand: \(command)"
case .unsupportedQuotesOption(let option):
return "Unsupported quotes option: \(option)"
case .apiURLInvalid:
return "Invalid quotes API URL."
case .apiUnreachable(let reason):
return "Unable to reach quotes API: \(reason)"
case .invalidHTTPStatus(let status):
return "Quotes API returned HTTP status \(status)."
case .invalidResponse:
return "Quotes API returned an invalid response format."
case .pasteboardWriteFailed:
return "Failed to write quote text to the macOS pasteboard."
case .accessibilityPermissionMissing:
return "Accessibility permission is required to simulate paste (Cmd+V). Enable it in System Settings > Privacy & Security > Accessibility."
case .eventCreationFailed:
return "Unable to create keyboard events for paste operation."
}
}
}
struct QuoteResponse: Decodable {
let quote: String
let author: String
}
struct QuoteService {
private let endpoint = "http://localhost:3001/quote"
func fetchRandomQuote(timeout: TimeInterval = 5.0) throws -> QuoteResponse {
guard let url = URL(string: endpoint) else {
throw SkrybeError.apiURLInvalid
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.timeoutInterval = timeout
let semaphore = DispatchSemaphore(value: 0)
var responseData: Data?
var response: URLResponse?
var requestError: Error?
let task = URLSession.shared.dataTask(with: request) { data, urlResponse, error in
responseData = data
response = urlResponse
requestError = error
semaphore.signal()
}
task.resume()
_ = semaphore.wait(timeout: .now() + timeout + 1.0)
if let requestError {
throw SkrybeError.apiUnreachable(requestError.localizedDescription)
}
guard let http = response as? HTTPURLResponse else {
throw SkrybeError.invalidResponse
}
guard (200...299).contains(http.statusCode) else {
throw SkrybeError.invalidHTTPStatus(http.statusCode)
}
guard let responseData else {
throw SkrybeError.invalidResponse
}
do {
let decoded = try JSONDecoder().decode(QuoteResponse.self, from: responseData)
return decoded
} catch {
throw SkrybeError.invalidResponse
}
}
}
struct QuotePaster {
func paste(_ text: String) throws {
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
guard pasteboard.setString(text, forType: .string) else {
throw SkrybeError.pasteboardWriteFailed
}
guard AXIsProcessTrusted() else {
throw SkrybeError.accessibilityPermissionMissing
}
try sendCommandV()
}
private func sendCommandV() throws {
guard
let source = CGEventSource(stateID: .hidSystemState),
let keyDown = CGEvent(keyboardEventSource: source, virtualKey: 9, keyDown: true), // 9 = 'v'
let keyUp = CGEvent(keyboardEventSource: source, virtualKey: 9, keyDown: false)
else {
throw SkrybeError.eventCreationFailed
}
keyDown.flags = .maskCommand
keyUp.flags = .maskCommand
keyDown.post(tap: .cghidEventTap)
keyUp.post(tap: .cghidEventTap)
}
}
struct SkrybeCLI {
private let quoteService = QuoteService()
private let quotePaster = QuotePaster()
func run(arguments: [String]) throws {
guard arguments.count >= 2 else {
throw SkrybeError.invalidUsage(usage())
}
let command = arguments[1]
switch command {
case "quotes":
try runQuotes(arguments: Array(arguments.dropFirst(2)))
case "--help", "-h", "help":
print(usage())
default:
throw SkrybeError.unsupportedSubcommand(command)
}
}
private func runQuotes(arguments: [String]) throws {
guard let option = arguments.first else {
throw SkrybeError.invalidUsage(quotesUsage())
}
switch option {
case "--paste":
let quote = try quoteService.fetchRandomQuote()
let rendered = "\(quote.quote)\(quote.author)"
try quotePaster.paste(rendered)
print("✅ Pasted quote at cursor position")
case "--list", "--add":
throw SkrybeError.invalidUsage("\(option) is not implemented yet. Use `skrybe quotes --paste`.")
case "--help", "-h", "help":
print(quotesUsage())
default:
throw SkrybeError.unsupportedQuotesOption(option)
}
}
private func usage() -> String {
return """
Usage:
skrybe quotes --paste
skrybe quotes --list (planned)
skrybe quotes --add (planned)
"""
}
private func quotesUsage() -> String {
return """
Quotes subcommand:
skrybe quotes --paste Fetch random quote from localhost:3001/quote and paste at current cursor
skrybe quotes --list Planned
skrybe quotes --add Planned
"""
}
}
do {
try SkrybeCLI().run(arguments: CommandLine.arguments)
} catch {
if let localized = error as? LocalizedError, let message = localized.errorDescription {
fputs("\(message)\n", stderr)
} else {
fputs("\(error.localizedDescription)\n", stderr)
}
exit(EXIT_FAILURE)
}