diff --git a/chrome-extension/src/background.js b/chrome-extension/src/background.js
index 0a0926a..cb109d1 100644
--- a/chrome-extension/src/background.js
+++ b/chrome-extension/src/background.js
@@ -101,7 +101,7 @@ async function initOllamaClient() {
// Default to local Ollama instance and a general model if not configured
const endpoint = ollamaEndpoint || "http://localhost:11434";
- const model = ollamaModel || "gemma:7b";
+ const model = ollamaModel || "lukasmwerner/mark-tagger:1b";
ollamaClient = new Ollama({
host: endpoint,
@@ -157,7 +157,7 @@ async function generateTagsWithOllama(url, title, description) {
"ollamaModel",
"ollamaPrompt",
]);
- const model = ollamaModel || "gemma:7b";
+ const model = ollamaModel || "lukasmwerner/mark-tagger:1b";
// Use custom prompt if available, otherwise use default
let promptTemplate = ollamaPrompt ||
diff --git a/chrome-extension/src/options.html b/chrome-extension/src/options.html
index eaa1ee8..9c4cc8d 100644
--- a/chrome-extension/src/options.html
+++ b/chrome-extension/src/options.html
@@ -89,7 +89,7 @@
diff --git a/chrome-extension/src/options.js b/chrome-extension/src/options.js
index 067d53b..bb9bb7f 100644
--- a/chrome-extension/src/options.js
+++ b/chrome-extension/src/options.js
@@ -1,7 +1,7 @@
import { Ollama } from "ollama/browser";
import { ollama_schema } from "./schema";
-document.addEventListener("DOMContentLoaded", function () {
+document.addEventListener("DOMContentLoaded", function() {
// Load saved settings
chrome.storage.sync.get(
[
@@ -12,7 +12,7 @@ document.addEventListener("DOMContentLoaded", function () {
"ollamaTemperature",
"enableAutoTagging",
],
- function (result) {
+ function(result) {
console.log(result);
if (result.apiToken) {
document.getElementById("tokenInput").value = result.apiToken;
@@ -55,13 +55,13 @@ Description: {{description}}`;
// Save settings
document
.getElementById("saveSettings")
- .addEventListener("click", function () {
+ .addEventListener("click", function() {
const token = document.getElementById("tokenInput").value;
const ollamaEndpoint =
document.getElementById("ollamaEndpoint").value ||
"http://localhost:11434";
const ollamaModel = document.getElementById("ollamaModel").value ||
- "gemma:7b";
+ "lukasmwerner/mark-tagger:1b";
const ollamaPrompt =
document.getElementById("ollamaPrompt").value ||
`Generate 3-5 relevant tags for this content. Return only the tags as a JSON array of strings. No additional explanation needed.
@@ -84,7 +84,7 @@ Description: {{description}}`;
ollamaTemperature: ollamaTemperature.valueOf(),
enableAutoTagging: enableAutoTagging,
},
- function () {
+ function() {
updateStatus("Settings saved successfully!");
},
);
@@ -93,7 +93,7 @@ Description: {{description}}`;
// Test Ollama connection
document
.getElementById("testOllama")
- .addEventListener("click", async function () {
+ .addEventListener("click", async function() {
try {
updateStatus("Testing Ollama connection...");
@@ -101,7 +101,7 @@ Description: {{description}}`;
document.getElementById("ollamaEndpoint").value ||
"http://localhost:11434";
const model = document.getElementById("ollamaModel").value ||
- "gemma:7b";
+ "lukasmwerner/mark-tagger:1b";
const ollama = new Ollama({
host: endpoint,
@@ -119,8 +119,7 @@ Description: {{description}}`;
);
} else {
updateStatus(
- `Connection successful, but model "${model}" was not found. Available models: ${
- models.models.map((m) => m.name).join(", ")
+ `Connection successful, but model "${model}" was not found. Available models: ${models.models.map((m) => m.name).join(", ")
}`,
);
}
@@ -133,7 +132,7 @@ Description: {{description}}`;
// Test prompt for tag generation
document
.getElementById("testPrompt")
- .addEventListener("click", async function () {
+ .addEventListener("click", async function() {
try {
updateStatus("Testing tag generation...");
diff --git a/chrome-extension/src/popup.js b/chrome-extension/src/popup.js
index 821b01f..58e5dc5 100644
--- a/chrome-extension/src/popup.js
+++ b/chrome-extension/src/popup.js
@@ -54,7 +54,7 @@ async function generateTagsWithOllama(url, title, description) {
]);
const endpoint = ollamaEndpoint || "http://localhost:11434";
- const model = ollamaModel || "gemma:7b";
+ const model = ollamaModel || "lukasmwerner/mark-tagger:1b";
// Use custom prompt if available, otherwise use default
let promptTemplate = ollamaPrompt ||
@@ -91,6 +91,7 @@ async function generateTagsWithOllama(url, title, description) {
console.log(response);
let tags = JSON.parse(response.response).tags;
+ tags = tags.map(v => v.toLowerCase())
return tags;
} catch (error) {
// Hide loading indicator
diff --git a/cmd/delete.go b/cmd/delete.go
new file mode 100644
index 0000000..3427c88
--- /dev/null
+++ b/cmd/delete.go
@@ -0,0 +1,72 @@
+/*
+Copyright © 2025 Lukas Werner
+*/
+package cmd
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/charmbracelet/huh"
+ "github.com/lukasmwerner/mark/store"
+ "github.com/spf13/cobra"
+)
+
+// deleteCmd represents the delete command
+var deleteCmd = &cobra.Command{
+ Use: "delete",
+ Short: "deletes bookmarks based on given search query",
+ Args: cobra.MinimumNArgs(1),
+ Run: func(cmd *cobra.Command, args []string) {
+
+ db, err := store.Open()
+ if err != nil {
+ fmt.Println("unable to open database", err.Error())
+ return
+ }
+ defer db.Close()
+
+ searchQuery := strings.Join(args, " ")
+
+ bookmarks, err := store.SearchBookmarks(db, searchQuery)
+ if err != nil {
+ fmt.Println("unable to search bookmarks", err.Error())
+ return
+ }
+ for _, bm := range bookmarks {
+ var affirmative bool
+ err := huh.NewConfirm().Title("Delete?").
+ Description(prettyOutput(bm)).
+ Affirmative("Delete!").
+ Negative("No, Keep").
+ Value(&affirmative).Run()
+ if err != nil {
+ return
+ }
+ if !affirmative {
+ fmt.Println("Skipped.")
+ continue
+ }
+ err = store.DeleteBookmark(db, bm)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ fmt.Println("Deleted.")
+ }
+ },
+}
+
+func init() {
+ rootCmd.AddCommand(deleteCmd)
+
+ // Here you will define your flags and configuration settings.
+
+ // Cobra supports Persistent Flags which will work for this command
+ // and all subcommands, e.g.:
+ // deleteCmd.PersistentFlags().String("foo", "", "A help for foo")
+
+ // Cobra supports local flags which will only run when this command
+ // is called directly, e.g.:
+ // deleteCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}
diff --git a/cmd/dump.go b/cmd/dump.go
new file mode 100644
index 0000000..e692248
--- /dev/null
+++ b/cmd/dump.go
@@ -0,0 +1,81 @@
+/*
+Copyright © 2025 Lukas Werner
+*/
+package cmd
+
+import (
+ "database/sql"
+ "fmt"
+
+ "github.com/lukasmwerner/mark/store"
+ "github.com/spf13/cobra"
+)
+
+// dumpCmd represents the dump command
+var dumpCmd = &cobra.Command{
+ Use: "dump",
+ Short: "dumps the database to a sqlite3 file",
+ Args: cobra.MinimumNArgs(1),
+ Run: func(cmd *cobra.Command, args []string) {
+ db, err := store.Open()
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ defer db.Close()
+
+ dumpDB, err := sql.Open("sqlite3", args[0])
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ defer dumpDB.Close()
+
+ dumpDB.Exec(`CREATE TABLE bookmarks (
+ id INTEGER PRIMARY KEY NOT NULL,
+ url TEXT,
+ title TEXT,
+ description TEXT,
+ tags TEXT
+);`)
+
+ rows, err := db.QueryContext(cmd.Context(), "SELECT url, title, description, tags FROM Bookmarks;")
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+
+ defer rows.Close()
+
+ for rows.Next() {
+ var b store.Bookmark
+ var tags string
+ err := rows.Scan(&b.Url, &b.Title, &b.Description, &tags)
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ _, err = dumpDB.Exec(`INSERT INTO bookmarks (url, title, description, tags) VALUES (?, ?, ?, ?)`,
+ b.Url, b.Title, b.Description, tags)
+ if err != nil {
+ fmt.Println(err.Error())
+ return
+ }
+ }
+
+ },
+}
+
+func init() {
+ rootCmd.AddCommand(dumpCmd)
+
+ // Here you will define your flags and configuration settings.
+
+ // Cobra supports Persistent Flags which will work for this command
+ // and all subcommands, e.g.:
+ // dumpCmd.PersistentFlags().String("foo", "", "A help for foo")
+
+ // Cobra supports local flags which will only run when this command
+ // is called directly, e.g.:
+ // dumpCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}
diff --git a/cmd/show.go b/cmd/show.go
index 90550a5..391bf02 100644
--- a/cmd/show.go
+++ b/cmd/show.go
@@ -11,6 +11,7 @@ import (
"strings"
"github.com/charmbracelet/huh"
+ "github.com/charmbracelet/lipgloss"
"github.com/lukasmwerner/mark/store"
"github.com/spf13/cobra"
)
@@ -77,7 +78,7 @@ func init() {
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// showCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
- showCmd.Flags().StringVarP(&outputMode, "mode", "m", "json", "Output mode: json,csv")
+ showCmd.Flags().StringVarP(&outputMode, "mode", "m", "pretty", "Output mode: json,csv,pretty")
}
func outputBookmark(mode string, bookmark store.Bookmark) {
@@ -90,6 +91,18 @@ func outputBookmark(mode string, bookmark store.Bookmark) {
w.Write([]string{"Title", "Description", "Tags", "URL"})
w.Write([]string{bookmark.Title, bookmark.Description, strings.Join(bookmark.Tags, ","), bookmark.Url})
w.Flush()
-
+ case "pretty":
+ fmt.Print(prettyOutput(bookmark))
}
}
+
+var categoryColorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#34A77C"))
+
+func prettyOutput(bm store.Bookmark) string {
+ out := ""
+ out += categoryColorStyle.Render("Title: ") + bm.Title + "\n"
+ out += categoryColorStyle.Render("URL: ") + bm.Url + "\n"
+ out += categoryColorStyle.Render("Tags: ") + strings.Join(bm.Tags, ", ") + "\n"
+ out += categoryColorStyle.Render("Description: ") + bm.Description + "\n"
+ return out
+}
diff --git a/store/database.go b/store/database.go
index fc1d206..aa7535c 100644
--- a/store/database.go
+++ b/store/database.go
@@ -278,6 +278,21 @@ func UpdateBookmark(db *DB, original Bookmark, updated Bookmark) error {
return err
}
+func DeleteBookmark(db *DB, bookmark Bookmark) error {
+ _, err := db.Exec(`DELETE FROM Bookmarks
+ WHERE
+ url = ? AND
+ title = ? AND
+ description = ? AND
+ tags = ?;`,
+ bookmark.Url,
+ bookmark.Title,
+ bookmark.Description,
+ strings.Join(bookmark.Tags, ", "),
+ )
+ return err
+}
+
func AddKey(db *DB, key string) error {
_, err := db.Exec("INSERT INTO Server_Keys (key) VALUES (?)", key)
if err != nil {