From d077831825f8b3f26e9ec8ddb9c6622639462fd0 Mon Sep 17 00:00:00 2001
From: Snider
Date: Sun, 26 Oct 2025 00:02:40 +0100
Subject: [PATCH] Remove unused packages, configurations, and assets
---
Makefile | 13 +
Taskfile.yml | 35 -
cmd/app/Taskfile.yml | 12 +-
cmd/app/build/Taskfile.yml | 1 +
cmd/app/build/config.yml | 4 +-
cmd/app/build/darwin/Taskfile.yml | 2 +-
cmd/app/build/linux/Taskfile.yml | 2 +-
cmd/app/build/windows/Taskfile.yml | 2 +-
cmd/app/main.go | 18 +-
.../bindings/github.com/Snider/Core/core.ts | 40 +
.../bindings/github.com/Snider/Core/index.ts | 15 +
.../bindings/github.com/Snider/Core/models.ts | 41 +
.../wails/v3/pkg/application/index.ts | 17 +
.../wails/v3/pkg/application/models.ts | 369 ++
cmd/app/public/bindings/log/slog/index.ts | 6 +
cmd/app/public/bindings/log/slog/models.ts | 31 +
cmd/tasks/build-darwin.yml | 39 +
cmd/tasks/build-linux.yml | 40 +
cmd/tasks/build-windows.yml | 39 +
cmd/tasks/config.yml | 2 +
cmd/tasks/go.yml | 14 +
cmd/tasks/node.yml | 24 +
cmd/tasks/wails.yml | 21 +
go.work | 2 +-
go.work.sum | 2 +
pkg/app/.gitignore | 2 -
pkg/core/actions.go | 11 +
pkg/core/config/config.go | 177 +
pkg/core/config/config_test.go | 146 +
pkg/core/core.go | 190 +
pkg/core/crypt/crypt.go | 76 +
pkg/{v1 => }/core/crypt/crypt_test.go | 0
pkg/{v1 => }/core/crypt/hash.go | 2 +-
.../lib => core/crypt}/lthn/hash_test.go | 0
.../lthn/hash.go => core/crypt/lthn/lthn.go} | 15 +
.../lib => core/crypt}/openpgp/encrypt.go | 13 +-
pkg/core/crypt/openpgp/encrypt_test.go | 155 +
.../crypt/lib => core/crypt}/openpgp/key.go | 87 +-
.../lib => core/crypt}/openpgp/openpgp.go | 0
.../crypt/lib => core/crypt}/openpgp/sign.go | 9 +-
pkg/{v1 => }/core/crypt/sum.go | 0
pkg/core/display/actions.go | 8 +
pkg/core/display/display.go | 127 +
pkg/{v1 => }/core/display/menu.go | 6 +-
pkg/{v1 => }/core/display/tray.go | 16 +-
pkg/core/display/window.go | 93 +
pkg/core/docs/.gitignore | 1 -
.../core/docs/assets/stylesheets/extra.css | 0
pkg/core/docs/docs.go | 50 +
pkg/{v1 => }/core/docs/mkdocs.yml | 0
pkg/{v1 => }/core/docs/public/404.html | 311 +-
.../fonts.googleapis.com/css.49ea35f2.css | 756 +++
.../external/unpkg.com/iframe-worker/shim.js | 1 +
.../unpkg.com/mermaid@11/dist/mermaid.min.js | 2811 ++++++++
.../docs/public/assets/images/favicon.png | Bin
.../assets/javascripts/bundle.f55a23d4.min.js | 2 +-
.../javascripts/bundle.f55a23d4.min.js.map | 0
.../javascripts/lunr/min/lunr.ar.min.js | 0
.../javascripts/lunr/min/lunr.da.min.js | 0
.../javascripts/lunr/min/lunr.de.min.js | 0
.../javascripts/lunr/min/lunr.du.min.js | 0
.../javascripts/lunr/min/lunr.el.min.js | 0
.../javascripts/lunr/min/lunr.es.min.js | 0
.../javascripts/lunr/min/lunr.fi.min.js | 0
.../javascripts/lunr/min/lunr.fr.min.js | 0
.../javascripts/lunr/min/lunr.he.min.js | 0
.../javascripts/lunr/min/lunr.hi.min.js | 0
.../javascripts/lunr/min/lunr.hu.min.js | 0
.../javascripts/lunr/min/lunr.hy.min.js | 0
.../javascripts/lunr/min/lunr.it.min.js | 0
.../javascripts/lunr/min/lunr.ja.min.js | 0
.../javascripts/lunr/min/lunr.jp.min.js | 0
.../javascripts/lunr/min/lunr.kn.min.js | 0
.../javascripts/lunr/min/lunr.ko.min.js | 0
.../javascripts/lunr/min/lunr.multi.min.js | 0
.../javascripts/lunr/min/lunr.nl.min.js | 0
.../javascripts/lunr/min/lunr.no.min.js | 0
.../javascripts/lunr/min/lunr.pt.min.js | 0
.../javascripts/lunr/min/lunr.ro.min.js | 0
.../javascripts/lunr/min/lunr.ru.min.js | 0
.../javascripts/lunr/min/lunr.sa.min.js | 0
.../lunr/min/lunr.stemmer.support.min.js | 0
.../javascripts/lunr/min/lunr.sv.min.js | 0
.../javascripts/lunr/min/lunr.ta.min.js | 0
.../javascripts/lunr/min/lunr.te.min.js | 0
.../javascripts/lunr/min/lunr.th.min.js | 0
.../javascripts/lunr/min/lunr.tr.min.js | 0
.../javascripts/lunr/min/lunr.vi.min.js | 0
.../javascripts/lunr/min/lunr.zh.min.js | 0
.../public/assets/javascripts/lunr/tinyseg.js | 0
.../public/assets/javascripts/lunr/wordcut.js | 0
.../workers/search.973d3a69.min.js | 0
.../workers/search.973d3a69.min.js.map | 0
.../assets/stylesheets/main.84d31ad4.min.css | 0
.../stylesheets/main.84d31ad4.min.css.map | 0
.../stylesheets/palette.06af60db.min.css | 0
.../stylesheets/palette.06af60db.min.css.map | 0
pkg/core/docs/public/core/config.html | 881 +++
pkg/core/docs/public/core/crypt.html | 905 +++
pkg/core/docs/public/core/display.html | 907 +++
pkg/core/docs/public/core/docs.html | 903 +++
.../docs/public/core}/index.html | 498 +-
pkg/core/docs/public/core/io.html | 903 +++
pkg/core/docs/public/core/workspace.html | 901 +++
.../docs/public}/images/cross-platform.jpeg | Bin
.../docs/public}/images/decentralised-vpn.jpg | Bin
.../docs/public}/images/favicon.ico | Bin
.../docs/public}/images/illustration.png | Bin
.../docs/public}/images/lethean-logo.png | Bin
.../images/private-transaction-net.png | Bin
.../public}/images/secure-data-storage.jpg | Bin
pkg/core/docs/public/index.html | 914 +++
pkg/core/docs/public/search/search_index.js | 1 +
pkg/core/docs/public/search/search_index.json | 1 +
pkg/core/docs/public/sitemap.xml | 35 +
pkg/core/docs/public/sitemap.xml.gz | Bin 0 -> 233 bytes
.../docs/public}/src/core/config.md | 0
.../docs/public}/src/core/crypt.md | 0
.../docs/public}/src/core/display.md | 0
.../docs/public}/src/core/docs.md | 0
.../docs/public}/src/core/index.md | 0
.../docs => core/docs/public}/src/core/io.md | 0
.../docs/public}/src/core/workspace.md | 0
.../public}/src/images/cross-platform.jpeg | Bin
.../public}/src/images/decentralised-vpn.jpg | Bin
.../docs/public}/src/images/favicon.ico | Bin
.../docs/public}/src/images/illustration.png | Bin
.../docs/public}/src/images/lethean-logo.png | Bin
.../src/images/private-transaction-net.png | Bin
.../src/images/secure-data-storage.jpg | Bin
.../docs => core/docs/public}/src/index.md | 0
.../docs/public/src}/stylesheets/extra.css | 0
.../docs/public}/stylesheets/extra.css | 0
pkg/{v1 => }/core/docs/requirements.txt | 0
pkg/{v1 => }/core/docs/taskfile.dist.yml | 0
pkg/{v1 => }/core/go.mod | 1 +
pkg/{v1 => }/core/go.sum | 2 +
pkg/core/i18n/editor.babel | 5685 +++++++++++++++++
pkg/core/i18n/i18n.go | 171 +
pkg/core/i18n/locales/de.json | 157 +
pkg/core/i18n/locales/en.json | 157 +
pkg/core/i18n/locales/es.json | 157 +
pkg/core/i18n/locales/fr.json | 157 +
pkg/core/i18n/locales/ru.json | 157 +
pkg/core/i18n/locales/uk.json | 157 +
pkg/core/i18n/locales/zh.json | 157 +
pkg/core/runtime.go | 76 +
pkg/internals/cio/cio.go | 27 +
pkg/internals/cio/client.go | 30 +
pkg/internals/cio/mock.go | 47 +
pkg/{v1/core => internals}/io/client.go | 0
pkg/{v1/core => internals}/io/client_test.go | 0
pkg/{v1/core => internals}/io/filesystem.go | 0
.../core => internals}/io/filesystem_test.go | 0
pkg/{v1/core => internals}/io/local/client.go | 0
.../io/local/client_test.go | 0
pkg/{v1/core => internals}/io/local/local.go | 0
pkg/{v1/core => internals}/io/mock.go | 0
pkg/{v1/core => internals}/io/sftp/client.go | 0
pkg/{v1/core => internals}/io/sftp/sftp.go | 0
.../core => internals}/io/webdav/client.go | 0
.../core => internals}/io/webdav/webdav.go | 0
pkg/{v1/core => internals}/workspace/local.go | 0
.../core => internals}/workspace/service.go | 0
.../core => internals}/workspace/workspace.go | 0
.../workspace/workspace_test.go | 0
pkg/v1/core/actions.go | 3 -
pkg/v1/core/config/config.go | 148 -
pkg/v1/core/config/config_test.go | 81 -
pkg/v1/core/config/header.go | 55 -
pkg/v1/core/core.go | 139 -
pkg/v1/core/crypt/crypt.go | 63 -
pkg/v1/core/crypt/header.go | 24 -
pkg/v1/core/crypt/lib/lthn/lthn.go | 16 -
pkg/v1/core/display/display.go | 158 -
pkg/v1/core/display/window.go | 81 -
pkg/v1/core/docs/docs.go | 27 -
.../core/docs/public/search/search_index.json | 1 -
pkg/v1/core/docs/public/sitemap.xml | 7 -
pkg/v1/core/docs/public/sitemap.xml.gz | Bin 179 -> 0 bytes
pkg/v1/core/docs/service.go | 54 -
pkg/v1/core/header.go | 45 -
182 files changed, 19669 insertions(+), 1076 deletions(-)
create mode 100644 Makefile
delete mode 100644 Taskfile.yml
create mode 100644 cmd/app/public/bindings/github.com/Snider/Core/core.ts
create mode 100644 cmd/app/public/bindings/github.com/Snider/Core/index.ts
create mode 100644 cmd/app/public/bindings/github.com/Snider/Core/models.ts
create mode 100644 cmd/app/public/bindings/github.com/wailsapp/wails/v3/pkg/application/index.ts
create mode 100644 cmd/app/public/bindings/github.com/wailsapp/wails/v3/pkg/application/models.ts
create mode 100644 cmd/app/public/bindings/log/slog/index.ts
create mode 100644 cmd/app/public/bindings/log/slog/models.ts
create mode 100644 cmd/tasks/build-darwin.yml
create mode 100644 cmd/tasks/build-linux.yml
create mode 100644 cmd/tasks/build-windows.yml
create mode 100644 cmd/tasks/config.yml
create mode 100644 cmd/tasks/go.yml
create mode 100644 cmd/tasks/node.yml
create mode 100644 cmd/tasks/wails.yml
delete mode 100644 pkg/app/.gitignore
create mode 100644 pkg/core/actions.go
create mode 100644 pkg/core/config/config.go
create mode 100644 pkg/core/config/config_test.go
create mode 100644 pkg/core/core.go
create mode 100644 pkg/core/crypt/crypt.go
rename pkg/{v1 => }/core/crypt/crypt_test.go (100%)
rename pkg/{v1 => }/core/crypt/hash.go (94%)
rename pkg/{v1/core/crypt/lib => core/crypt}/lthn/hash_test.go (100%)
rename pkg/{v1/core/crypt/lib/lthn/hash.go => core/crypt/lthn/lthn.go} (81%)
rename pkg/{v1/core/crypt/lib => core/crypt}/openpgp/encrypt.go (86%)
create mode 100644 pkg/core/crypt/openpgp/encrypt_test.go
rename pkg/{v1/core/crypt/lib => core/crypt}/openpgp/key.go (77%)
rename pkg/{v1/core/crypt/lib => core/crypt}/openpgp/openpgp.go (100%)
rename pkg/{v1/core/crypt/lib => core/crypt}/openpgp/sign.go (72%)
rename pkg/{v1 => }/core/crypt/sum.go (100%)
create mode 100644 pkg/core/display/actions.go
create mode 100644 pkg/core/display/display.go
rename pkg/{v1 => }/core/display/menu.go (87%)
rename pkg/{v1 => }/core/display/tray.go (85%)
create mode 100644 pkg/core/display/window.go
delete mode 100644 pkg/core/docs/.gitignore
rename pkg/{v1 => }/core/docs/assets/stylesheets/extra.css (100%)
create mode 100644 pkg/core/docs/docs.go
rename pkg/{v1 => }/core/docs/mkdocs.yml (100%)
rename pkg/{v1 => }/core/docs/public/404.html (84%)
create mode 100644 pkg/core/docs/public/assets/external/fonts.googleapis.com/css.49ea35f2.css
create mode 100644 pkg/core/docs/public/assets/external/unpkg.com/iframe-worker/shim.js
create mode 100644 pkg/core/docs/public/assets/external/unpkg.com/mermaid@11/dist/mermaid.min.js
rename pkg/{v1 => }/core/docs/public/assets/images/favicon.png (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/bundle.f55a23d4.min.js (71%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/bundle.f55a23d4.min.js.map (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.ar.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.da.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.de.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.du.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.el.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.es.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.fi.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.fr.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.he.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.hi.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.hu.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.hy.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.it.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.ja.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.jp.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.kn.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.ko.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.multi.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.nl.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.no.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.pt.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.ro.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.ru.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.sa.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.stemmer.support.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.sv.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.ta.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.te.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.th.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.tr.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.vi.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.zh.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/tinyseg.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/wordcut.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/workers/search.973d3a69.min.js (100%)
rename pkg/{v1 => }/core/docs/public/assets/javascripts/workers/search.973d3a69.min.js.map (100%)
rename pkg/{v1 => }/core/docs/public/assets/stylesheets/main.84d31ad4.min.css (100%)
rename pkg/{v1 => }/core/docs/public/assets/stylesheets/main.84d31ad4.min.css.map (100%)
rename pkg/{v1 => }/core/docs/public/assets/stylesheets/palette.06af60db.min.css (100%)
rename pkg/{v1 => }/core/docs/public/assets/stylesheets/palette.06af60db.min.css.map (100%)
create mode 100644 pkg/core/docs/public/core/config.html
create mode 100644 pkg/core/docs/public/core/crypt.html
create mode 100644 pkg/core/docs/public/core/display.html
create mode 100644 pkg/core/docs/public/core/docs.html
rename pkg/{v1/core/docs/public => core/docs/public/core}/index.html (62%)
create mode 100644 pkg/core/docs/public/core/io.html
create mode 100644 pkg/core/docs/public/core/workspace.html
rename pkg/{v1/core/docs/public/assets => core/docs/public}/images/cross-platform.jpeg (100%)
rename pkg/{v1/core/docs/public/assets => core/docs/public}/images/decentralised-vpn.jpg (100%)
rename pkg/{v1/core/docs/public/assets => core/docs/public}/images/favicon.ico (100%)
rename pkg/{v1/core/docs/public/assets => core/docs/public}/images/illustration.png (100%)
rename pkg/{v1/core/docs/public/assets => core/docs/public}/images/lethean-logo.png (100%)
rename pkg/{v1/core/docs/public/assets => core/docs/public}/images/private-transaction-net.png (100%)
rename pkg/{v1/core/docs/public/assets => core/docs/public}/images/secure-data-storage.jpg (100%)
create mode 100644 pkg/core/docs/public/index.html
create mode 100644 pkg/core/docs/public/search/search_index.js
create mode 100644 pkg/core/docs/public/search/search_index.json
create mode 100644 pkg/core/docs/public/sitemap.xml
create mode 100644 pkg/core/docs/public/sitemap.xml.gz
rename pkg/{v1/core/docs => core/docs/public}/src/core/config.md (100%)
rename pkg/{v1/core/docs => core/docs/public}/src/core/crypt.md (100%)
rename pkg/{v1/core/docs => core/docs/public}/src/core/display.md (100%)
rename pkg/{v1/core/docs => core/docs/public}/src/core/docs.md (100%)
rename pkg/{v1/core/docs => core/docs/public}/src/core/index.md (100%)
rename pkg/{v1/core/docs => core/docs/public}/src/core/io.md (100%)
rename pkg/{v1/core/docs => core/docs/public}/src/core/workspace.md (100%)
rename pkg/{v1/core/docs => core/docs/public}/src/images/cross-platform.jpeg (100%)
rename pkg/{v1/core/docs => core/docs/public}/src/images/decentralised-vpn.jpg (100%)
rename pkg/{v1/core/docs => core/docs/public}/src/images/favicon.ico (100%)
rename pkg/{v1/core/docs => core/docs/public}/src/images/illustration.png (100%)
rename pkg/{v1/core/docs => core/docs/public}/src/images/lethean-logo.png (100%)
rename pkg/{v1/core/docs => core/docs/public}/src/images/private-transaction-net.png (100%)
rename pkg/{v1/core/docs => core/docs/public}/src/images/secure-data-storage.jpg (100%)
rename pkg/{v1/core/docs => core/docs/public}/src/index.md (100%)
rename pkg/{v1/core/docs/public/assets => core/docs/public/src}/stylesheets/extra.css (100%)
rename pkg/{v1/core/docs/src => core/docs/public}/stylesheets/extra.css (100%)
rename pkg/{v1 => }/core/docs/requirements.txt (100%)
rename pkg/{v1 => }/core/docs/taskfile.dist.yml (100%)
rename pkg/{v1 => }/core/go.mod (97%)
rename pkg/{v1 => }/core/go.sum (98%)
create mode 100644 pkg/core/i18n/editor.babel
create mode 100644 pkg/core/i18n/i18n.go
create mode 100644 pkg/core/i18n/locales/de.json
create mode 100644 pkg/core/i18n/locales/en.json
create mode 100644 pkg/core/i18n/locales/es.json
create mode 100644 pkg/core/i18n/locales/fr.json
create mode 100644 pkg/core/i18n/locales/ru.json
create mode 100644 pkg/core/i18n/locales/uk.json
create mode 100644 pkg/core/i18n/locales/zh.json
create mode 100644 pkg/core/runtime.go
create mode 100644 pkg/internals/cio/cio.go
create mode 100644 pkg/internals/cio/client.go
create mode 100644 pkg/internals/cio/mock.go
rename pkg/{v1/core => internals}/io/client.go (100%)
rename pkg/{v1/core => internals}/io/client_test.go (100%)
rename pkg/{v1/core => internals}/io/filesystem.go (100%)
rename pkg/{v1/core => internals}/io/filesystem_test.go (100%)
rename pkg/{v1/core => internals}/io/local/client.go (100%)
rename pkg/{v1/core => internals}/io/local/client_test.go (100%)
rename pkg/{v1/core => internals}/io/local/local.go (100%)
rename pkg/{v1/core => internals}/io/mock.go (100%)
rename pkg/{v1/core => internals}/io/sftp/client.go (100%)
rename pkg/{v1/core => internals}/io/sftp/sftp.go (100%)
rename pkg/{v1/core => internals}/io/webdav/client.go (100%)
rename pkg/{v1/core => internals}/io/webdav/webdav.go (100%)
rename pkg/{v1/core => internals}/workspace/local.go (100%)
rename pkg/{v1/core => internals}/workspace/service.go (100%)
rename pkg/{v1/core => internals}/workspace/workspace.go (100%)
rename pkg/{v1/core => internals}/workspace/workspace_test.go (100%)
delete mode 100644 pkg/v1/core/actions.go
delete mode 100644 pkg/v1/core/config/config.go
delete mode 100644 pkg/v1/core/config/config_test.go
delete mode 100644 pkg/v1/core/config/header.go
delete mode 100644 pkg/v1/core/core.go
delete mode 100644 pkg/v1/core/crypt/crypt.go
delete mode 100644 pkg/v1/core/crypt/header.go
delete mode 100644 pkg/v1/core/crypt/lib/lthn/lthn.go
delete mode 100644 pkg/v1/core/display/display.go
delete mode 100644 pkg/v1/core/display/window.go
delete mode 100644 pkg/v1/core/docs/docs.go
delete mode 100644 pkg/v1/core/docs/public/search/search_index.json
delete mode 100644 pkg/v1/core/docs/public/sitemap.xml
delete mode 100644 pkg/v1/core/docs/public/sitemap.xml.gz
delete mode 100644 pkg/v1/core/docs/service.go
delete mode 100644 pkg/v1/core/header.go
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..a6a8b1d0
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,13 @@
+.PHONY: prod-docs
+
+all:
+ (cd cmd/app && wails3 build)
+
+development-docs:
+ @echo "Running development documentation Website..."
+ @(cd pkg/core/docs && mkdocs serve -w src)
+
+prod-docs:
+ @echo "Generating documentation tp Repo Root..."
+ @(cd pkg/core/docs && mkdocs build -d public && cp -r src public)
+ @echo "Documentation generated at docs/index.html"
\ No newline at end of file
diff --git a/Taskfile.yml b/Taskfile.yml
deleted file mode 100644
index e0549880..00000000
--- a/Taskfile.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-version: '3'
-
-# This top-level Taskfile orchestrates tasks in the sub-directories.
-# It uses the 'dir' property to ensure that each included task runs
-# from its correct working directory.
-includes:
- app:
- dir: {{.TASKFILE_DIR}}/cmd/app
- docs:
- dir: {{.TASKFILE_DIR}}/pkg/v1/core/docs
-
-
-
-tasks:
- default:
- desc: "Show available tasks."
- cmds:
- - task --list-all
-
- build:
- desc: "Build both the application and the documentation."
- cmds:
- - task: app:build
- - task: docs:build
-
- dev:
- desc: "Run the application in development mode and serve the documentation."
- cmds:
- - task: app:dev
- - task: docs:dev
-
- tidy:
- desc: "Tidy all Go modules in the workspace."
- cmds:
- - go mod tidy
diff --git a/cmd/app/Taskfile.yml b/cmd/app/Taskfile.yml
index c2336aa5..fce96d4e 100644
--- a/cmd/app/Taskfile.yml
+++ b/cmd/app/Taskfile.yml
@@ -2,14 +2,14 @@ version: '3'
includes:
- common: "{{.TASKFILE_DIR}}/build/Taskfile.yml"
- windows: "{{.TASKFILE_DIR}}/build/windows/Taskfile.yml"
- darwin: "{{.TASKFILE_DIR}}/build/darwin/Taskfile.yml"
- linux: "{{.TASKFILE_DIR}}/build/linux/Taskfile.yml"
+ common: "./build/Taskfile.yml"
+ windows: "./build/windows/Taskfile.yml"
+ darwin: "./build/darwin/Taskfile.yml"
+ linux: "./build/linux/Taskfile.yml"
vars:
APP_NAME: "core"
- BIN_DIR: "{{.TASKFILE_DIR}}/build/bin"
+ BIN_DIR: "./build/bin"
VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}'
tasks:
@@ -31,4 +31,4 @@ tasks:
dev:
summary: Runs the application in development mode
cmds:
- - wails3 dev -config {{.TASKFILE_DIR}}/build/config.yml -port {{.VITE_PORT}}
+ - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}}
diff --git a/cmd/app/build/Taskfile.yml b/cmd/app/build/Taskfile.yml
index 4fb1eecb..edeab4a0 100644
--- a/cmd/app/build/Taskfile.yml
+++ b/cmd/app/build/Taskfile.yml
@@ -45,6 +45,7 @@ tasks:
generate:bindings:
label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}})
summary: Generates bindings
+ dir: ../
deps:
- task: go:mod:tidy
sources:
diff --git a/cmd/app/build/config.yml b/cmd/app/build/config.yml
index cfcaa087..f6b56f6a 100644
--- a/cmd/app/build/config.yml
+++ b/cmd/app/build/config.yml
@@ -5,8 +5,8 @@ version: '3'
# This information is used to generate the build assets.
info:
companyName: "Snider"
- productName: "Core.Framework"
- productIdentifier: "com.core.desktop"
+ productName: "Core.App"
+ productIdentifier: "com.core.app"
description: "A program that does demos the features"
copyright: "(c) EUPL-1.2, Snider"
comments: "Demo Dev Area"
diff --git a/cmd/app/build/darwin/Taskfile.yml b/cmd/app/build/darwin/Taskfile.yml
index 953842c8..97bf96bb 100644
--- a/cmd/app/build/darwin/Taskfile.yml
+++ b/cmd/app/build/darwin/Taskfile.yml
@@ -1,7 +1,7 @@
version: '3'
includes:
- common: {{.TASKFILE_DIR}}/Taskfile.yml
+ common: ../Taskfile.yml
tasks:
build:
diff --git a/cmd/app/build/linux/Taskfile.yml b/cmd/app/build/linux/Taskfile.yml
index fcdae343..7e789618 100644
--- a/cmd/app/build/linux/Taskfile.yml
+++ b/cmd/app/build/linux/Taskfile.yml
@@ -1,7 +1,7 @@
version: '3'
includes:
- common: "{{.TASKFILE_DIR}}/Taskfile.yml"
+ common: "../Taskfile.yml"
tasks:
build:
diff --git a/cmd/app/build/windows/Taskfile.yml b/cmd/app/build/windows/Taskfile.yml
index a7863b83..75c59f57 100644
--- a/cmd/app/build/windows/Taskfile.yml
+++ b/cmd/app/build/windows/Taskfile.yml
@@ -1,7 +1,7 @@
version: '3'
includes:
- common: {{.TASKFILE_DIR}}/Taskfile.yml
+ common: ../Taskfile.yml
tasks:
build:
diff --git a/cmd/app/main.go b/cmd/app/main.go
index 3d30194d..313fda85 100644
--- a/cmd/app/main.go
+++ b/cmd/app/main.go
@@ -21,14 +21,16 @@ func main() {
},
})
- app.RegisterService(application.NewService(core.Service(
- core.WithWails(app), // Provides the Wails application instance to core services
- core.WithAssets(assets), // Provides the embed.FS to core services
- core.WithService(config.Register), // Provides the ability to persist UI state (windows reopen where they closed)
- core.WithService(display.Register), // Provides the ability to open windows
- core.WithService(crypt.Register), // Provides cryptographic functions
- core.WithServiceLock(), // locks core from accepting new services blocking access to IPC
- )))
+ coreService := core.New(
+ core.WithWails(app),
+ core.WithAssets(assets),
+ core.WithService(config.New),
+ core.WithService(display.New),
+ core.WithService(crypt.New),
+ core.WithServiceLock(),
+ )
+
+ app.RegisterService(application.NewService(coreService))
err := app.Run()
if err != nil {
diff --git a/cmd/app/public/bindings/github.com/Snider/Core/core.ts b/cmd/app/public/bindings/github.com/Snider/Core/core.ts
new file mode 100644
index 00000000..aaf87f94
--- /dev/null
+++ b/cmd/app/public/bindings/github.com/Snider/Core/core.ts
@@ -0,0 +1,40 @@
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file is automatically generated. DO NOT EDIT
+
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore: Unused imports
+import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
+
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore: Unused imports
+import * as $models from "./models.js";
+
+export function ACTION(msg: $models.Message | null): $CancellablePromise {
+ return $Call.ByID(4097389530, msg);
+}
+
+export function Core(): $CancellablePromise<$models.Core | null> {
+ return $Call.ByID(590325359).then(($result: any) => {
+ return $$createType1($result);
+ });
+}
+
+export function RegisterAction(handler: any): $CancellablePromise {
+ return $Call.ByID(2751902507, handler);
+}
+
+export function RegisterActions(...handlers: any[]): $CancellablePromise {
+ return $Call.ByID(2391561096, handlers);
+}
+
+export function RegisterService(name: string, api: any): $CancellablePromise {
+ return $Call.ByID(3677749144, name, api);
+}
+
+export function Service(name: string): $CancellablePromise {
+ return $Call.ByID(3861253011, name);
+}
+
+// Private type creation functions
+const $$createType0 = $models.Core.createFrom;
+const $$createType1 = $Create.Nullable($$createType0);
diff --git a/cmd/app/public/bindings/github.com/Snider/Core/index.ts b/cmd/app/public/bindings/github.com/Snider/Core/index.ts
new file mode 100644
index 00000000..cb1f26a4
--- /dev/null
+++ b/cmd/app/public/bindings/github.com/Snider/Core/index.ts
@@ -0,0 +1,15 @@
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file is automatically generated. DO NOT EDIT
+
+import * as Core from "./core.js";
+export {
+ Core
+};
+
+export {
+ Core
+} from "./models.js";
+
+export type {
+ Message
+} from "./models.js";
diff --git a/cmd/app/public/bindings/github.com/Snider/Core/models.ts b/cmd/app/public/bindings/github.com/Snider/Core/models.ts
new file mode 100644
index 00000000..c0152b25
--- /dev/null
+++ b/cmd/app/public/bindings/github.com/Snider/Core/models.ts
@@ -0,0 +1,41 @@
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file is automatically generated. DO NOT EDIT
+
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore: Unused imports
+import { Create as $Create } from "@wailsio/runtime";
+
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore: Unused imports
+import * as application$0 from "../../wailsapp/wails/v3/pkg/application/models.js";
+
+export class Core {
+ "App": application$0.App | null;
+
+ /** Creates a new Core instance. */
+ constructor($$source: Partial = {}) {
+ if (!("App" in $$source)) {
+ this["App"] = null;
+ }
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new Core instance from a string or object.
+ */
+ static createFrom($$source: any = {}): Core {
+ const $$createField0_0 = $$createType1;
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ if ("App" in $$parsedSource) {
+ $$parsedSource["App"] = $$createField0_0($$parsedSource["App"]);
+ }
+ return new Core($$parsedSource as Partial);
+ }
+}
+
+export type Message = any;
+
+// Private type creation functions
+const $$createType0 = application$0.App.createFrom;
+const $$createType1 = $Create.Nullable($$createType0);
diff --git a/cmd/app/public/bindings/github.com/wailsapp/wails/v3/pkg/application/index.ts b/cmd/app/public/bindings/github.com/wailsapp/wails/v3/pkg/application/index.ts
new file mode 100644
index 00000000..b8aaea17
--- /dev/null
+++ b/cmd/app/public/bindings/github.com/wailsapp/wails/v3/pkg/application/index.ts
@@ -0,0 +1,17 @@
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file is automatically generated. DO NOT EDIT
+
+export {
+ App,
+ BrowserManager,
+ ClipboardManager,
+ ContextMenuManager,
+ DialogManager,
+ EnvironmentManager,
+ EventManager,
+ KeyBindingManager,
+ MenuManager,
+ ScreenManager,
+ SystemTrayManager,
+ WindowManager
+} from "./models.js";
diff --git a/cmd/app/public/bindings/github.com/wailsapp/wails/v3/pkg/application/models.ts b/cmd/app/public/bindings/github.com/wailsapp/wails/v3/pkg/application/models.ts
new file mode 100644
index 00000000..bf9925b7
--- /dev/null
+++ b/cmd/app/public/bindings/github.com/wailsapp/wails/v3/pkg/application/models.ts
@@ -0,0 +1,369 @@
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file is automatically generated. DO NOT EDIT
+
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore: Unused imports
+import { Create as $Create } from "@wailsio/runtime";
+
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore: Unused imports
+import * as slog$0 from "../../../../../../log/slog/models.js";
+
+export class App {
+ /**
+ * Manager pattern for organized API
+ */
+ "Window": WindowManager | null;
+ "ContextMenu": ContextMenuManager | null;
+ "KeyBinding": KeyBindingManager | null;
+ "Browser": BrowserManager | null;
+ "Env": EnvironmentManager | null;
+ "Dialog": DialogManager | null;
+ "Event": EventManager | null;
+ "Menu": MenuManager | null;
+ "Screen": ScreenManager | null;
+ "Clipboard": ClipboardManager | null;
+ "SystemTray": SystemTrayManager | null;
+ "Logger": slog$0.Logger | null;
+
+ /** Creates a new App instance. */
+ constructor($$source: Partial = {}) {
+ if (!("Window" in $$source)) {
+ this["Window"] = null;
+ }
+ if (!("ContextMenu" in $$source)) {
+ this["ContextMenu"] = null;
+ }
+ if (!("KeyBinding" in $$source)) {
+ this["KeyBinding"] = null;
+ }
+ if (!("Browser" in $$source)) {
+ this["Browser"] = null;
+ }
+ if (!("Env" in $$source)) {
+ this["Env"] = null;
+ }
+ if (!("Dialog" in $$source)) {
+ this["Dialog"] = null;
+ }
+ if (!("Event" in $$source)) {
+ this["Event"] = null;
+ }
+ if (!("Menu" in $$source)) {
+ this["Menu"] = null;
+ }
+ if (!("Screen" in $$source)) {
+ this["Screen"] = null;
+ }
+ if (!("Clipboard" in $$source)) {
+ this["Clipboard"] = null;
+ }
+ if (!("SystemTray" in $$source)) {
+ this["SystemTray"] = null;
+ }
+ if (!("Logger" in $$source)) {
+ this["Logger"] = null;
+ }
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new App instance from a string or object.
+ */
+ static createFrom($$source: any = {}): App {
+ const $$createField0_0 = $$createType1;
+ const $$createField1_0 = $$createType3;
+ const $$createField2_0 = $$createType5;
+ const $$createField3_0 = $$createType7;
+ const $$createField4_0 = $$createType9;
+ const $$createField5_0 = $$createType11;
+ const $$createField6_0 = $$createType13;
+ const $$createField7_0 = $$createType15;
+ const $$createField8_0 = $$createType17;
+ const $$createField9_0 = $$createType19;
+ const $$createField10_0 = $$createType21;
+ const $$createField11_0 = $$createType23;
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ if ("Window" in $$parsedSource) {
+ $$parsedSource["Window"] = $$createField0_0($$parsedSource["Window"]);
+ }
+ if ("ContextMenu" in $$parsedSource) {
+ $$parsedSource["ContextMenu"] = $$createField1_0($$parsedSource["ContextMenu"]);
+ }
+ if ("KeyBinding" in $$parsedSource) {
+ $$parsedSource["KeyBinding"] = $$createField2_0($$parsedSource["KeyBinding"]);
+ }
+ if ("Browser" in $$parsedSource) {
+ $$parsedSource["Browser"] = $$createField3_0($$parsedSource["Browser"]);
+ }
+ if ("Env" in $$parsedSource) {
+ $$parsedSource["Env"] = $$createField4_0($$parsedSource["Env"]);
+ }
+ if ("Dialog" in $$parsedSource) {
+ $$parsedSource["Dialog"] = $$createField5_0($$parsedSource["Dialog"]);
+ }
+ if ("Event" in $$parsedSource) {
+ $$parsedSource["Event"] = $$createField6_0($$parsedSource["Event"]);
+ }
+ if ("Menu" in $$parsedSource) {
+ $$parsedSource["Menu"] = $$createField7_0($$parsedSource["Menu"]);
+ }
+ if ("Screen" in $$parsedSource) {
+ $$parsedSource["Screen"] = $$createField8_0($$parsedSource["Screen"]);
+ }
+ if ("Clipboard" in $$parsedSource) {
+ $$parsedSource["Clipboard"] = $$createField9_0($$parsedSource["Clipboard"]);
+ }
+ if ("SystemTray" in $$parsedSource) {
+ $$parsedSource["SystemTray"] = $$createField10_0($$parsedSource["SystemTray"]);
+ }
+ if ("Logger" in $$parsedSource) {
+ $$parsedSource["Logger"] = $$createField11_0($$parsedSource["Logger"]);
+ }
+ return new App($$parsedSource as Partial);
+ }
+}
+
+/**
+ * BrowserManager manages browser-related operations
+ */
+export class BrowserManager {
+
+ /** Creates a new BrowserManager instance. */
+ constructor($$source: Partial = {}) {
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new BrowserManager instance from a string or object.
+ */
+ static createFrom($$source: any = {}): BrowserManager {
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ return new BrowserManager($$parsedSource as Partial);
+ }
+}
+
+/**
+ * ClipboardManager manages clipboard operations
+ */
+export class ClipboardManager {
+
+ /** Creates a new ClipboardManager instance. */
+ constructor($$source: Partial = {}) {
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new ClipboardManager instance from a string or object.
+ */
+ static createFrom($$source: any = {}): ClipboardManager {
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ return new ClipboardManager($$parsedSource as Partial);
+ }
+}
+
+/**
+ * ContextMenuManager manages all context menu operations
+ */
+export class ContextMenuManager {
+
+ /** Creates a new ContextMenuManager instance. */
+ constructor($$source: Partial = {}) {
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new ContextMenuManager instance from a string or object.
+ */
+ static createFrom($$source: any = {}): ContextMenuManager {
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ return new ContextMenuManager($$parsedSource as Partial);
+ }
+}
+
+/**
+ * DialogManager manages dialog-related operations
+ */
+export class DialogManager {
+
+ /** Creates a new DialogManager instance. */
+ constructor($$source: Partial = {}) {
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new DialogManager instance from a string or object.
+ */
+ static createFrom($$source: any = {}): DialogManager {
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ return new DialogManager($$parsedSource as Partial);
+ }
+}
+
+/**
+ * EnvironmentManager manages environment-related operations
+ */
+export class EnvironmentManager {
+
+ /** Creates a new EnvironmentManager instance. */
+ constructor($$source: Partial = {}) {
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new EnvironmentManager instance from a string or object.
+ */
+ static createFrom($$source: any = {}): EnvironmentManager {
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ return new EnvironmentManager($$parsedSource as Partial);
+ }
+}
+
+/**
+ * EventManager manages event-related operations
+ */
+export class EventManager {
+
+ /** Creates a new EventManager instance. */
+ constructor($$source: Partial = {}) {
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new EventManager instance from a string or object.
+ */
+ static createFrom($$source: any = {}): EventManager {
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ return new EventManager($$parsedSource as Partial);
+ }
+}
+
+/**
+ * KeyBindingManager manages all key binding operations
+ */
+export class KeyBindingManager {
+
+ /** Creates a new KeyBindingManager instance. */
+ constructor($$source: Partial = {}) {
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new KeyBindingManager instance from a string or object.
+ */
+ static createFrom($$source: any = {}): KeyBindingManager {
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ return new KeyBindingManager($$parsedSource as Partial);
+ }
+}
+
+/**
+ * MenuManager manages menu-related operations
+ */
+export class MenuManager {
+
+ /** Creates a new MenuManager instance. */
+ constructor($$source: Partial = {}) {
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new MenuManager instance from a string or object.
+ */
+ static createFrom($$source: any = {}): MenuManager {
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ return new MenuManager($$parsedSource as Partial);
+ }
+}
+
+export class ScreenManager {
+
+ /** Creates a new ScreenManager instance. */
+ constructor($$source: Partial = {}) {
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new ScreenManager instance from a string or object.
+ */
+ static createFrom($$source: any = {}): ScreenManager {
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ return new ScreenManager($$parsedSource as Partial);
+ }
+}
+
+/**
+ * SystemTrayManager manages system tray-related operations
+ */
+export class SystemTrayManager {
+
+ /** Creates a new SystemTrayManager instance. */
+ constructor($$source: Partial = {}) {
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new SystemTrayManager instance from a string or object.
+ */
+ static createFrom($$source: any = {}): SystemTrayManager {
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ return new SystemTrayManager($$parsedSource as Partial);
+ }
+}
+
+/**
+ * WindowManager manages all window-related operations
+ */
+export class WindowManager {
+
+ /** Creates a new WindowManager instance. */
+ constructor($$source: Partial = {}) {
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new WindowManager instance from a string or object.
+ */
+ static createFrom($$source: any = {}): WindowManager {
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ return new WindowManager($$parsedSource as Partial);
+ }
+}
+
+// Private type creation functions
+const $$createType0 = WindowManager.createFrom;
+const $$createType1 = $Create.Nullable($$createType0);
+const $$createType2 = ContextMenuManager.createFrom;
+const $$createType3 = $Create.Nullable($$createType2);
+const $$createType4 = KeyBindingManager.createFrom;
+const $$createType5 = $Create.Nullable($$createType4);
+const $$createType6 = BrowserManager.createFrom;
+const $$createType7 = $Create.Nullable($$createType6);
+const $$createType8 = EnvironmentManager.createFrom;
+const $$createType9 = $Create.Nullable($$createType8);
+const $$createType10 = DialogManager.createFrom;
+const $$createType11 = $Create.Nullable($$createType10);
+const $$createType12 = EventManager.createFrom;
+const $$createType13 = $Create.Nullable($$createType12);
+const $$createType14 = MenuManager.createFrom;
+const $$createType15 = $Create.Nullable($$createType14);
+const $$createType16 = ScreenManager.createFrom;
+const $$createType17 = $Create.Nullable($$createType16);
+const $$createType18 = ClipboardManager.createFrom;
+const $$createType19 = $Create.Nullable($$createType18);
+const $$createType20 = SystemTrayManager.createFrom;
+const $$createType21 = $Create.Nullable($$createType20);
+const $$createType22 = slog$0.Logger.createFrom;
+const $$createType23 = $Create.Nullable($$createType22);
diff --git a/cmd/app/public/bindings/log/slog/index.ts b/cmd/app/public/bindings/log/slog/index.ts
new file mode 100644
index 00000000..28f9022d
--- /dev/null
+++ b/cmd/app/public/bindings/log/slog/index.ts
@@ -0,0 +1,6 @@
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file is automatically generated. DO NOT EDIT
+
+export {
+ Logger
+} from "./models.js";
diff --git a/cmd/app/public/bindings/log/slog/models.ts b/cmd/app/public/bindings/log/slog/models.ts
new file mode 100644
index 00000000..ef606c67
--- /dev/null
+++ b/cmd/app/public/bindings/log/slog/models.ts
@@ -0,0 +1,31 @@
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file is automatically generated. DO NOT EDIT
+
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore: Unused imports
+import { Create as $Create } from "@wailsio/runtime";
+
+/**
+ * A Logger records structured information about each call to its
+ * Log, Debug, Info, Warn, and Error methods.
+ * For each call, it creates a [Record] and passes it to a [Handler].
+ *
+ * To create a new Logger, call [New] or a Logger method
+ * that begins "With".
+ */
+export class Logger {
+
+ /** Creates a new Logger instance. */
+ constructor($$source: Partial = {}) {
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new Logger instance from a string or object.
+ */
+ static createFrom($$source: any = {}): Logger {
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ return new Logger($$parsedSource as Partial);
+ }
+}
diff --git a/cmd/tasks/build-darwin.yml b/cmd/tasks/build-darwin.yml
new file mode 100644
index 00000000..e49921a7
--- /dev/null
+++ b/cmd/tasks/build-darwin.yml
@@ -0,0 +1,39 @@
+version: '3'
+
+# This file contains the build logic specifically for the macOS (Darwin) platform.
+
+includes:
+ common: ./common.yml
+
+tasks:
+ build:
+ desc: "Builds the application for macOS."
+ dir: "{{.APP_ROOT}}"
+ deps:
+ - task: common:go:mod:tidy
+ - task: common:build:public
+ vars:
+ PRODUCTION: '{{.PRODUCTION | default "false"}}'
+ - task: common:generate:icons
+ cmds:
+ - go build {{.BUILD_FLAGS}} -o bin/{{.APP_NAME}}
+ vars:
+ BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
+ env:
+ GOOS: darwin
+ CGO_ENABLED: 1
+ GOARCH: '{{.ARCH | default "amd64"}}'
+ PRODUCTION: '{{.PRODUCTION | default "false"}}'
+
+ package:
+ desc: "Packages the application as a .app bundle for macOS."
+ dir: "{{.APP_ROOT}}"
+ deps:
+ - task: build
+ vars:
+ PRODUCTION: "true"
+ cmds:
+ - echo "Packaging for macOS..."
+ # This command assumes Wails handles the bundling process.
+ # You may need to adjust based on your specific packaging tool.
+ - wails3 build -package -platform darwin/{{.ARCH | default "amd64"}} -production -clean -o "{{.APP_NAME}}.app"
diff --git a/cmd/tasks/build-linux.yml b/cmd/tasks/build-linux.yml
new file mode 100644
index 00000000..e346930f
--- /dev/null
+++ b/cmd/tasks/build-linux.yml
@@ -0,0 +1,40 @@
+version: '3'
+
+# This file contains the build logic specifically for the Linux platform.
+
+includes:
+ common: ./common.yml
+
+tasks:
+ build:
+ desc: "Builds the application for Linux."
+ dir: "{{.APP_ROOT}}"
+ deps:
+ - task: common:go:mod:tidy
+ - task: common:build:public
+ vars:
+ BUILD_FLAGS:
+ ref: .BUILD_FLAGS
+ PRODUCTION:
+ ref: .PRODUCTION
+ - task: common:generate:icons
+ cmds:
+ - go build {{.BUILD_FLAGS}} -o bin/{{.APP_NAME}}
+ vars:
+ BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
+ env:
+ GOOS: linux
+ CGO_ENABLED: 1
+ GOARCH: '{{.ARCH | default "amd64"}}'
+ PRODUCTION: '{{.PRODUCTION | default "false"}}'
+
+ package:
+ desc: "Packages the application for Linux."
+ dir: "{{.APP_ROOT}}"
+ deps:
+ - task: build
+ vars:
+ PRODUCTION: "true"
+ cmds:
+ - echo "Packaging for Linux... (AppImage, Deb, RPM)"
+ # Placeholder for actual packaging commands
diff --git a/cmd/tasks/build-windows.yml b/cmd/tasks/build-windows.yml
new file mode 100644
index 00000000..c5f4434a
--- /dev/null
+++ b/cmd/tasks/build-windows.yml
@@ -0,0 +1,39 @@
+version: '3'
+
+# This file contains the build logic specifically for the Windows platform.
+
+includes:
+ common: ./common.yml
+
+tasks:
+ build:
+ desc: "Builds the application for Windows."
+ dir: "{{.APP_ROOT}}"
+ deps:
+ - task: common:go:mod:tidy
+ - task: common:build:public
+ vars:
+ PRODUCTION: '{{.PRODUCTION | default "false"}}'
+ - task: common:generate:icons
+ cmds:
+ - go build {{.BUILD_FLAGS}} -o bin/{{.APP_NAME}}.exe
+ vars:
+ BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H=windowsgui"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
+ env:
+ GOOS: windows
+ CGO_ENABLED: 1
+ GOARCH: '{{.ARCH | default "amd64"}}'
+ PRODUCTION: '{{.PRODUCTION | default "false"}}'
+
+ package:
+ desc: "Packages the application as a .exe installer for Windows."
+ dir: "{{.APP_ROOT}}"
+ deps:
+ - task: build
+ vars:
+ PRODUCTION: "true"
+ cmds:
+ - echo "Packaging for Windows..."
+ # This command assumes Wails handles the packaging process, potentially using NSIS or MSIX.
+ # You may need to adjust based on your specific packaging tool.
+ - wails3 build -package -platform windows/{{.ARCH | default "amd6d64"}} -production -clean -o "{{.APP_NAME}}.exe"
diff --git a/cmd/tasks/config.yml b/cmd/tasks/config.yml
new file mode 100644
index 00000000..d0773238
--- /dev/null
+++ b/cmd/tasks/config.yml
@@ -0,0 +1,2 @@
+version: '3'
+
diff --git a/cmd/tasks/go.yml b/cmd/tasks/go.yml
new file mode 100644
index 00000000..037a9c75
--- /dev/null
+++ b/cmd/tasks/go.yml
@@ -0,0 +1,14 @@
+version: '3'
+
+tasks:
+ tidy:
+ desc: "Run go mod tidy in a specific directory."
+ dir: '{{.GO_DIR | default "."}}'
+ cmds:
+ - go mod tidy
+
+ build:
+ desc: "Run go build in a specific directory."
+ dir: '{{.GO_DIR | default "."}}'
+ cmds:
+ - go build -o {{.OUTPUT_PATH}} {{.BUILD_FLAGS}}
diff --git a/cmd/tasks/node.yml b/cmd/tasks/node.yml
new file mode 100644
index 00000000..edf4e934
--- /dev/null
+++ b/cmd/tasks/node.yml
@@ -0,0 +1,24 @@
+version: '3'
+
+tasks:
+ install:
+ desc: "Install npm dependencies for the frontend."
+ dir: '{{.PUBLIC_DIR}}'
+ sources:
+ - package.json
+ - package-lock.json
+ generates:
+ - node_modules/*
+ preconditions:
+ - sh: npm version
+ msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/"
+ cmds:
+ - npm install
+
+ build:
+ desc: "Build the frontend assets."
+ dir: '{{.PUBLIC_DIR}}'
+ deps:
+ - task: install
+ cmds:
+ - npm run build
diff --git a/cmd/tasks/wails.yml b/cmd/tasks/wails.yml
new file mode 100644
index 00000000..1d822f50
--- /dev/null
+++ b/cmd/tasks/wails.yml
@@ -0,0 +1,21 @@
+version: '3'
+
+tasks:
+ dev:
+ desc: "Runs the Wails dev server."
+ vars:
+ APP_ROOT: '{{.APP_ROOT | default "."}}'
+ VITE_PORT: '{{.VITE_PORT | default 9245}}'
+ cmds:
+ # Note the paths are now constructed from the passed-in APP_ROOT.
+ - wails3 dev -approot '{{.APP_ROOT}}' -config '{{.APP_ROOT}}/build/config.yml' -port {{.VITE_PORT}}
+
+ build:
+ desc: "Builds the Wails application."
+ vars:
+ APP_ROOT: '{{.APP_ROOT | default "."}}'
+ APP_NAME: '{{.APP_NAME | default "app"}}'
+ dir: '{{.APP_ROOT}}' # Run the build from the app's root directory.
+ cmds:
+ # This assumes you have a 'build' task in your platform-specific Taskfiles.
+ - wails3 build -platform '{{.GOOS}}/{{.GOARCH}}' -name '{{.APP_NAME}}'
diff --git a/go.work b/go.work
index d138de6c..b9f826f2 100644
--- a/go.work
+++ b/go.work
@@ -1,6 +1,6 @@
go 1.25
use (
- ./pkg/v1/core
+ ./pkg/core
./cmd/app
)
diff --git a/go.work.sum b/go.work.sum
index 2f1bfb38..585b5190 100644
--- a/go.work.sum
+++ b/go.work.sum
@@ -10,6 +10,8 @@ github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
+github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
+github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg=
github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
diff --git a/pkg/app/.gitignore b/pkg/app/.gitignore
deleted file mode 100644
index 393ccc12..00000000
--- a/pkg/app/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-.task
-/build/bin/core
diff --git a/pkg/core/actions.go b/pkg/core/actions.go
new file mode 100644
index 00000000..828df0f0
--- /dev/null
+++ b/pkg/core/actions.go
@@ -0,0 +1,11 @@
+package core
+
+import "github.com/wailsapp/wails/v3/pkg/application"
+
+type ActionServiceStartup struct{}
+
+// ActionDisplayOpenWindow is a structured message for requesting a new window.
+type ActionDisplayOpenWindow struct {
+ Name string
+ Options application.WebviewWindowOptions
+}
diff --git a/pkg/core/config/config.go b/pkg/core/config/config.go
new file mode 100644
index 00000000..bd922e38
--- /dev/null
+++ b/pkg/core/config/config.go
@@ -0,0 +1,177 @@
+package config
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "reflect"
+ "strings"
+
+ "github.com/Snider/Core"
+ "github.com/adrg/xdg"
+)
+
+const appName = "lethean"
+const configFileName = "config.json"
+
+// ErrSetupRequired is returned if the config file is missing and cannot be created.
+var ErrSetupRequired = errors.New("setup required: config.json not found")
+
+// Options holds configuration for the config service.
+type Options struct{}
+
+// Service provides access to the application's configuration.
+// It handles loading, saving, and providing access to configuration values.
+type Service struct {
+ *core.Runtime[Options] `json:"-"`
+
+ // Non-persistent fields, derived at runtime.
+ ConfigPath string `json:"-"`
+ UserHomeDir string `json:"-"`
+ RootDir string `json:"-"`
+ CacheDir string `json:"-"`
+ ConfigDir string `json:"-"`
+ DataDir string `json:"-"`
+ WorkspacesDir string `json:"-"`
+
+ // Persistent fields, saved to config.json.
+ DefaultRoute string `json:"default_route"`
+ Features []string `json:"features"`
+ Language string `json:"language"`
+}
+
+// New is a factory function that creates and initializes a new configuration service.
+// It loads an existing configuration or creates a default one if not found.
+func New(c *core.Core) (any, error) {
+ // --- Path and Directory Setup ---
+ homeDir, err := os.UserHomeDir()
+ if err != nil {
+ return nil, fmt.Errorf("could not resolve user home directory: %w", err)
+ }
+ userHomeDir := filepath.Join(homeDir, appName)
+
+ rootDir, err := xdg.DataFile(appName)
+ if err != nil {
+ return nil, fmt.Errorf("could not resolve data directory: %w", err)
+ }
+
+ cacheDir, err := xdg.CacheFile(appName)
+ if err != nil {
+ return nil, fmt.Errorf("could not resolve cache directory: %w", err)
+ }
+
+ s := &Service{
+ Runtime: core.NewRuntime(c, Options{}),
+ UserHomeDir: userHomeDir,
+ RootDir: rootDir,
+ CacheDir: cacheDir,
+ ConfigDir: filepath.Join(userHomeDir, "config"),
+ DataDir: filepath.Join(userHomeDir, "data"),
+ WorkspacesDir: filepath.Join(userHomeDir, "workspaces"),
+ DefaultRoute: "/",
+ Features: []string{},
+ Language: "en",
+ }
+ s.ConfigPath = filepath.Join(s.ConfigDir, configFileName)
+
+ dirs := []string{s.RootDir, s.ConfigDir, s.DataDir, s.CacheDir, s.WorkspacesDir, s.UserHomeDir}
+ for _, dir := range dirs {
+ if err := os.MkdirAll(dir, os.ModePerm); err != nil {
+ return nil, fmt.Errorf("could not create directory %s: %w", dir, err)
+ }
+ }
+
+ // --- Load or Create Configuration ---
+ if data, err := os.ReadFile(s.ConfigPath); err == nil {
+ // Config file exists, load it.
+ if err := json.Unmarshal(data, s); err != nil {
+ return nil, fmt.Errorf("failed to unmarshal config: %w", err)
+ }
+ } else if os.IsNotExist(err) {
+ // Config file does not exist, create it with default values.
+ if err := s.Save(); err != nil {
+ return nil, fmt.Errorf("failed to create default config file: %w", err)
+ }
+ } else {
+ // Another error occurred reading the file.
+ return nil, fmt.Errorf("failed to read config file: %w", err)
+ }
+
+ c.RegisterAction(s.handleIPCEvents)
+ return s, nil
+}
+
+// handleIPCEvents is the central IPC handler for the config service.
+func (s *Service) handleIPCEvents(c *core.Core, msg core.Message) error {
+ switch msg.(type) {
+ case core.ActionServiceStartup:
+ c.App.Logger.Info("Config service started")
+ default:
+ // No other actions are handled by this service yet.
+ }
+ return nil
+}
+
+// Save writes the current configuration to config.json.
+func (s *Service) Save() error {
+ data, err := json.MarshalIndent(s, "", " ")
+ if err != nil {
+ return fmt.Errorf("failed to marshal config: %w", err)
+ }
+
+ if err := os.WriteFile(s.ConfigPath, data, 0644); err != nil {
+ return fmt.Errorf("failed to write config file: %w", err)
+ }
+ return nil
+}
+
+// IsFeatureEnabled checks if a given feature is enabled in the configuration.
+func (s *Service) IsFeatureEnabled(feature string) bool {
+ for _, f := range s.Features {
+ if f == feature {
+ return true
+ }
+ }
+ return false
+}
+
+// EnableFeature adds a feature to the list of enabled features and saves the config.
+func (s *Service) EnableFeature(feature string) error {
+ if s.IsFeatureEnabled(feature) {
+ return nil
+ }
+ s.Features = append(s.Features, feature)
+ if err := s.Save(); err != nil {
+ return fmt.Errorf("failed to save config after enabling feature %s: %w", feature, err)
+ }
+ return nil
+}
+
+func (s *Service) Key(key string) (interface{}, error) {
+ // Use reflection to inspect the struct fields.
+ val := reflect.ValueOf(s).Elem()
+ typ := val.Type()
+
+ for i := 0; i < val.NumField(); i++ {
+ field := typ.Field(i)
+ fieldName := field.Name
+
+ // Check the field name first.
+ if strings.EqualFold(fieldName, key) {
+ return val.Field(i).Interface(), nil
+ }
+
+ // Then check the `json` tag.
+ jsonTag := field.Tag.Get("json")
+ if jsonTag != "" && jsonTag != "-" {
+ jsonName := strings.Split(jsonTag, ",")[0]
+ if strings.EqualFold(jsonName, key) {
+ return val.Field(i).Interface(), nil
+ }
+ }
+ }
+
+ return nil, fmt.Errorf("key '%s' not found in config", key)
+}
diff --git a/pkg/core/config/config_test.go b/pkg/core/config/config_test.go
new file mode 100644
index 00000000..f2fc12de
--- /dev/null
+++ b/pkg/core/config/config_test.go
@@ -0,0 +1,146 @@
+package config
+
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/Snider/Core"
+)
+
+// setupTestEnv creates a temporary home directory for testing and ensures a clean environment.
+func setupTestEnv(t *testing.T) (string, func()) {
+ tempHomeDir, err := os.MkdirTemp("", "test_home_*")
+ if err != nil {
+ t.Fatalf("Failed to create temp home directory: %v", err)
+ }
+
+ oldHome := os.Getenv("HOME")
+ os.Setenv("HOME", tempHomeDir)
+
+ // Unset XDG vars to ensure HOME is used for path resolution, creating a hermetic test.
+ oldXdgData := os.Getenv("XDG_DATA_HOME")
+ oldXdgCache := os.Getenv("XDG_CACHE_HOME")
+ os.Unsetenv("XDG_DATA_HOME")
+ os.Unsetenv("XDG_CACHE_HOME")
+
+ cleanup := func() {
+ os.Setenv("HOME", oldHome)
+ os.Setenv("XDG_DATA_HOME", oldXdgData)
+ os.Setenv("XDG_CACHE_HOME", oldXdgCache)
+ os.RemoveAll(tempHomeDir)
+ }
+
+ return tempHomeDir, cleanup
+}
+
+// newTestCore creates a new, empty core instance for testing.
+func newTestCore(t *testing.T) *core.Core {
+ c := core.New()
+ if c == nil {
+ t.Fatalf("core.New() returned a nil instance")
+ }
+ return c
+}
+
+func TestConfigService(t *testing.T) {
+ t.Run("New service creates default config", func(t *testing.T) {
+ _, cleanup := setupTestEnv(t)
+ defer cleanup()
+
+ c := newTestCore(t)
+ serviceInstance, err := New(c)
+ if err != nil {
+ t.Fatalf("New() failed: %v", err)
+ }
+ s, ok := serviceInstance.(*Service)
+ if !ok {
+ t.Fatalf("Service instance is not of type *Service")
+ }
+
+ // Check that the config file was created
+ if _, err := os.Stat(s.ConfigPath); os.IsNotExist(err) {
+ t.Errorf("config.json was not created at %s", s.ConfigPath)
+ }
+
+ // Check default values
+ if s.Language != "en" {
+ t.Errorf("Expected default language 'en', got '%s'", s.Language)
+ }
+ })
+
+ t.Run("New service loads existing config", func(t *testing.T) {
+ tempHomeDir, cleanup := setupTestEnv(t)
+ defer cleanup()
+
+ // Manually create a config file with non-default values
+ configDir := filepath.Join(tempHomeDir, appName, "config")
+ if err := os.MkdirAll(configDir, os.ModePerm); err != nil {
+ t.Fatalf("Failed to create test config dir: %v", err)
+ }
+ configPath := filepath.Join(configDir, configFileName)
+
+ customConfig := `{"language": "fr", "features": ["beta-testing"]}`
+ if err := os.WriteFile(configPath, []byte(customConfig), 0644); err != nil {
+ t.Fatalf("Failed to write custom config file: %v", err)
+ }
+
+ c := newTestCore(t)
+ serviceInstance, err := New(c)
+ if err != nil {
+ t.Fatalf("New() failed while loading existing config: %v", err)
+ }
+ s, ok := serviceInstance.(*Service)
+ if !ok {
+ t.Fatalf("Service instance is not of type *Service")
+ }
+
+ if s.Language != "fr" {
+ t.Errorf("Expected language 'fr', got '%s'", s.Language)
+ }
+ if !s.IsFeatureEnabled("beta-testing") {
+ t.Errorf("Expected 'beta-testing' feature to be enabled")
+ }
+ })
+
+ t.Run("EnableFeature and Save", func(t *testing.T) {
+ _, cleanup := setupTestEnv(t)
+ defer cleanup()
+
+ c := newTestCore(t)
+ serviceInstance, err := New(c)
+ if err != nil {
+ t.Fatalf("New() failed: %v", err)
+ }
+ s, ok := serviceInstance.(*Service)
+ if !ok {
+ t.Fatalf("Service instance is not of type *Service")
+ }
+
+ if err := s.EnableFeature("new-feature"); err != nil {
+ t.Fatalf("EnableFeature() failed: %v", err)
+ }
+
+ data, err := os.ReadFile(s.ConfigPath)
+ if err != nil {
+ t.Fatalf("Failed to read config file: %v", err)
+ }
+
+ var onDiskService Service
+ if err := json.Unmarshal(data, &onDiskService); err != nil {
+ t.Fatalf("Failed to unmarshal saved config: %v", err)
+ }
+
+ found := false
+ for _, f := range onDiskService.Features {
+ if f == "new-feature" {
+ found = true
+ break
+ }
+ }
+ if !found {
+ t.Errorf("Enabled feature 'new-feature' was not saved to disk")
+ }
+ })
+}
diff --git a/pkg/core/core.go b/pkg/core/core.go
new file mode 100644
index 00000000..d18e07d4
--- /dev/null
+++ b/pkg/core/core.go
@@ -0,0 +1,190 @@
+package core
+
+import (
+ "context"
+ "embed"
+ "errors"
+ "fmt"
+ "reflect"
+ "strings"
+ "sync"
+
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+// --- Core Structs & Types ---
+
+type Contract struct {
+ DontPanic bool
+ DisableLogging bool
+}
+type Option func(*Core) error
+type Message interface{}
+
+type Core struct {
+ once sync.Once
+ initErr error
+ App *application.App
+ assets embed.FS
+ serviceLock bool
+ ipcMu sync.RWMutex
+ ipcHandlers []func(*Core, Message) error
+ serviceMu sync.RWMutex
+ services map[string]any
+ servicesLocked bool
+}
+
+var instance *Core
+
+// New initialises a Core instance using the provided options and performs the necessary setup.
+func New(opts ...Option) *Core {
+ c := &Core{
+ services: make(map[string]any),
+ }
+ for _, o := range opts {
+ if err := o(c); err != nil {
+ return nil
+ }
+ }
+ c.once.Do(func() {
+ c.initErr = nil
+ })
+ if c.initErr != nil {
+ return nil
+ }
+ if c.serviceLock {
+ c.servicesLocked = true
+ }
+ return c
+}
+
+// WithService creates an Option that registers a service. It automatically discovers
+// the service name from its package path and registers its IPC handler if it
+// implements a method named `HandleIPCEvents`.
+func WithService(factory func(*Core) (any, error)) Option {
+ return func(c *Core) error {
+ serviceInstance, err := factory(c)
+ if err != nil {
+ return fmt.Errorf("core: failed to create service: %w", err)
+ }
+
+ // --- Service Name Discovery ---
+ typeOfService := reflect.TypeOf(serviceInstance)
+ if typeOfService.Kind() == reflect.Ptr {
+ typeOfService = typeOfService.Elem()
+ }
+ pkgPath := typeOfService.PkgPath()
+ parts := strings.Split(pkgPath, "/")
+ name := parts[len(parts)-1]
+
+ // --- IPC Handler Discovery ---
+ instanceValue := reflect.ValueOf(serviceInstance)
+ handlerMethod := instanceValue.MethodByName("HandleIPCEvents")
+ if handlerMethod.IsValid() {
+ if handler, ok := handlerMethod.Interface().(func(*Core, Message) error); ok {
+ c.RegisterAction(handler)
+ }
+ }
+
+ return c.RegisterService(name, serviceInstance)
+ }
+}
+
+func WithWails(app *application.App) Option {
+ return func(c *Core) error {
+ c.App = app
+ return nil
+ }
+}
+
+func WithAssets(fs embed.FS) Option {
+ return func(c *Core) error {
+ c.assets = fs
+ return nil
+ }
+}
+
+func WithServiceLock() Option {
+ return func(c *Core) error {
+ c.serviceLock = true
+ return nil
+ }
+}
+
+// --- Core Methods ---
+
+func (c *Core) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
+ return c.ACTION(ActionServiceStartup{})
+}
+
+func (c *Core) ACTION(msg Message) error {
+ c.ipcMu.RLock()
+ handlers := append([]func(*Core, Message) error(nil), c.ipcHandlers...)
+ c.ipcMu.RUnlock()
+
+ var agg error
+ for _, h := range handlers {
+ if err := h(c, msg); err != nil {
+ agg = fmt.Errorf("%w; %v", agg, err)
+ }
+ }
+ return agg
+}
+
+func (c *Core) RegisterAction(handler func(*Core, Message) error) {
+ c.ipcMu.Lock()
+ c.ipcHandlers = append(c.ipcHandlers, handler)
+ c.ipcMu.Unlock()
+}
+
+func (c *Core) RegisterActions(handlers ...func(*Core, Message) error) {
+ c.ipcMu.Lock()
+ c.ipcHandlers = append(c.ipcHandlers, handlers...)
+ c.ipcMu.Unlock()
+}
+
+func (c *Core) RegisterService(name string, api any) error {
+ if c.servicesLocked {
+ return fmt.Errorf("core: service %q is not permitted by the serviceLock setting", name)
+ }
+ if name == "" {
+ return errors.New("core: service name cannot be empty")
+ }
+ c.serviceMu.Lock()
+ defer c.serviceMu.Unlock()
+ if _, exists := c.services[name]; exists {
+ return fmt.Errorf("core: service %q already registered", name)
+ }
+ c.services[name] = api
+ return nil
+}
+
+func (c *Core) Service(name string) any {
+ c.serviceMu.RLock()
+ api, ok := c.services[name]
+ c.serviceMu.RUnlock()
+ if !ok {
+ return nil
+ }
+ return api
+}
+
+func ServiceFor[T any](c *Core, name string) *T {
+ raw := c.Service(name)
+ typed, ok := raw.(*T)
+ if !ok {
+ return nil
+ }
+ return typed
+}
+
+// App returns the global application instance.
+func App() *application.App {
+ app := ServiceFor[application.App](instance, "App")
+ if instance == nil || app == nil {
+ panic("core.App() called before core.Setup() was successfully initialized")
+ }
+ return app
+}
+
+func (c *Core) Core() *Core { return c }
diff --git a/pkg/core/crypt/crypt.go b/pkg/core/crypt/crypt.go
new file mode 100644
index 00000000..fb6aa4e1
--- /dev/null
+++ b/pkg/core/crypt/crypt.go
@@ -0,0 +1,76 @@
+package crypt
+
+import (
+ "context"
+ "fmt"
+ "io"
+
+ "github.com/Snider/Core"
+ "github.com/Snider/Core/crypt/openpgp"
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+// Options holds configuration for the crypt service.
+type Options struct{}
+
+// Service provides cryptographic functions to the application.
+type Service struct {
+ *core.Runtime[Options]
+}
+
+// HashType defines the supported hashing algorithms.
+type HashType string
+
+const (
+ LTHN HashType = "lthn"
+ SHA512 HashType = "sha512"
+ SHA256 HashType = "sha256"
+ SHA1 HashType = "sha1"
+ MD5 HashType = "md5"
+)
+
+// Service provides cryptographic functions.
+// It is the main entry point for all cryptographic operations
+// and is bound to the frontend.
+
+type API struct {
+ core *core.Core
+}
+
+// New is a factory function that creates a new crypt Service.
+func New(c *core.Core) (any, error) {
+ s := &Service{
+ Runtime: core.NewRuntime(c, Options{}),
+ }
+ return s, nil
+}
+
+// handleIPCEvents is the central IPC handler for the crypt service.
+func (s *Service) handleIPCEvents(c *core.Core, msg core.Message) error {
+ switch msg.(type) {
+ case core.ActionServiceStartup:
+ return s.ServiceStartup(context.Background(), application.ServiceOptions{})
+ default:
+ c.App.Logger.Error("Crypt: Unknown message type", "type", fmt.Sprintf("%T", msg))
+ }
+ return nil
+}
+
+// ServiceStartup is called when the app starts. It handles one-time cryptographic setup.
+func (s *Service) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
+ s.Core().App.Logger.Info("Crypt service started")
+ // Key generation logic will be implemented here, likely depending on the config service.
+ return nil
+}
+
+// EncryptPGP encrypts data for a recipient, optionally signing it.
+// It acts as a wrapper around the underlying openpgp library function.
+func (s *Service) EncryptPGP(writer io.Writer, recipientPath, data string, signerPath, signerPassphrase *string) (string, error) {
+ return openpgp.EncryptPGP(writer, recipientPath, data, signerPath, signerPassphrase)
+}
+
+// DecryptPGP decrypts a PGP message, optionally verifying the signature.
+// It acts as a wrapper around the underlying openpgp library function.
+func (s *Service) DecryptPGP(recipientPath, message, passphrase string, signerPath *string) (string, error) {
+ return openpgp.DecryptPGP(recipientPath, message, passphrase, signerPath)
+}
diff --git a/pkg/v1/core/crypt/crypt_test.go b/pkg/core/crypt/crypt_test.go
similarity index 100%
rename from pkg/v1/core/crypt/crypt_test.go
rename to pkg/core/crypt/crypt_test.go
diff --git a/pkg/v1/core/crypt/hash.go b/pkg/core/crypt/hash.go
similarity index 94%
rename from pkg/v1/core/crypt/hash.go
rename to pkg/core/crypt/hash.go
index 55f7e86b..f2a285a7 100644
--- a/pkg/v1/core/crypt/hash.go
+++ b/pkg/core/crypt/hash.go
@@ -7,7 +7,7 @@ import (
"crypto/sha512"
"encoding/hex"
- "github.com/Snider/Core/crypt/lib/lthn"
+ "github.com/Snider/Core/crypt/lthn"
)
// Hash computes a hash of the payload using the specified algorithm.
diff --git a/pkg/v1/core/crypt/lib/lthn/hash_test.go b/pkg/core/crypt/lthn/hash_test.go
similarity index 100%
rename from pkg/v1/core/crypt/lib/lthn/hash_test.go
rename to pkg/core/crypt/lthn/hash_test.go
diff --git a/pkg/v1/core/crypt/lib/lthn/hash.go b/pkg/core/crypt/lthn/lthn.go
similarity index 81%
rename from pkg/v1/core/crypt/lib/lthn/hash.go
rename to pkg/core/crypt/lthn/lthn.go
index c9f0ac09..1b6c97d0 100644
--- a/pkg/v1/core/crypt/lib/lthn/hash.go
+++ b/pkg/core/crypt/lthn/lthn.go
@@ -5,6 +5,21 @@ import (
"encoding/hex"
)
+// keyMap is the default character-swapping map used for the quasi-salting process.
+var keyMap = map[rune]rune{
+ 'o': '0',
+ 'l': '1',
+ 'e': '3',
+ 'a': '4',
+ 's': 'z',
+ 't': '7',
+ '0': 'o',
+ '1': 'l',
+ '3': 'e',
+ '4': 'a',
+ '7': 't',
+}
+
// SetKeyMap sets the key map for the notarisation process.
func SetKeyMap(newKeyMap map[rune]rune) {
keyMap = newKeyMap
diff --git a/pkg/v1/core/crypt/lib/openpgp/encrypt.go b/pkg/core/crypt/openpgp/encrypt.go
similarity index 86%
rename from pkg/v1/core/crypt/lib/openpgp/encrypt.go
rename to pkg/core/crypt/openpgp/encrypt.go
index 736d5bcb..f8de72fb 100644
--- a/pkg/v1/core/crypt/lib/openpgp/encrypt.go
+++ b/pkg/core/crypt/openpgp/encrypt.go
@@ -8,19 +8,18 @@ import (
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
- "github.com/Snider/Core/io"
)
// EncryptPGP encrypts data for a recipient, optionally signing it.
-func EncryptPGP(medium io.Medium, recipientPath, data string, signerPath, signerPassphrase *string) (string, error) {
- recipient, err := GetPublicKey(medium, recipientPath)
+func EncryptPGP(writer io.Writer, recipientPath, data string, signerPath, signerPassphrase *string) (string, error) {
+ recipient, err := GetPublicKey(recipientPath)
if err != nil {
return "", fmt.Errorf("failed to get recipient public key: %w", err)
}
var signer *openpgp.Entity
if signerPath != nil && signerPassphrase != nil {
- signer, err = GetPrivateKey(medium, *signerPath, *signerPassphrase)
+ signer, err = GetPrivateKey(*signerPath, *signerPassphrase)
if err != nil {
return "", fmt.Errorf("could not get private key for signing: %w", err)
}
@@ -55,8 +54,8 @@ func EncryptPGP(medium io.Medium, recipientPath, data string, signerPath, signer
}
// DecryptPGP decrypts a PGP message, optionally verifying the signature.
-func DecryptPGP(medium io.Medium, recipientPath, message, passphrase string, signerPath *string) (string, error) {
- privateKeyEntity, err := GetPrivateKey(medium, recipientPath, passphrase)
+func DecryptPGP(recipientPath, message, passphrase string, signerPath *string) (string, error) {
+ privateKeyEntity, err := GetPrivateKey(recipientPath, passphrase)
if err != nil {
return "", fmt.Errorf("failed to get private key: %w", err)
}
@@ -66,7 +65,7 @@ func DecryptPGP(medium io.Medium, recipientPath, message, passphrase string, sig
var expectedSigner *openpgp.Entity
if signerPath != nil {
- publicKeyEntity, err := GetPublicKey(medium, *signerPath)
+ publicKeyEntity, err := GetPublicKey(*signerPath)
if err != nil {
return "", fmt.Errorf("could not get public key for verification: %w", err)
}
diff --git a/pkg/core/crypt/openpgp/encrypt_test.go b/pkg/core/crypt/openpgp/encrypt_test.go
new file mode 100644
index 00000000..b2021f28
--- /dev/null
+++ b/pkg/core/crypt/openpgp/encrypt_test.go
@@ -0,0 +1,155 @@
+package openpgp
+
+import (
+ "bytes"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/ProtonMail/go-crypto/openpgp"
+ "github.com/ProtonMail/go-crypto/openpgp/armor"
+)
+
+// generateTestKeys creates a new PGP entity and saves the public and private keys to temporary files.
+func generateTestKeys(t *testing.T, name, passphrase string) (string, string, func()) {
+ t.Helper()
+
+ tempDir, err := os.MkdirTemp("", "pgp-keys-*")
+ if err != nil {
+ t.Fatalf("Failed to create temp dir for keys: %v", err)
+ }
+
+ entity, err := openpgp.NewEntity(name, "", name, nil)
+ if err != nil {
+ t.Fatalf("Failed to create new PGP entity: %v", err)
+ }
+
+ // Encrypt the private key with the passphrase
+ if err := entity.PrivateKey.Encrypt([]byte(passphrase)); err != nil {
+ t.Fatalf("Failed to encrypt private key: %v", err)
+ }
+
+ // --- Save Public Key ---
+ pubKeyPath := filepath.Join(tempDir, name+".pub")
+ pubKeyFile, err := os.Create(pubKeyPath)
+ if err != nil {
+ t.Fatalf("Failed to create public key file: %v", err)
+ }
+ w, err := armor.Encode(pubKeyFile, openpgp.PublicKeyType, nil)
+ if err != nil {
+ t.Fatalf("Failed to create armored writer for public key: %v", err)
+ }
+ if err := entity.Serialize(w); err != nil {
+ t.Fatalf("Failed to serialize public key: %v", err)
+ }
+ w.Close()
+ pubKeyFile.Close()
+
+ // --- Save Private Key ---
+ privKeyPath := filepath.Join(tempDir, name+".asc")
+ privKeyFile, err := os.Create(privKeyPath)
+ if err != nil {
+ t.Fatalf("Failed to create private key file: %v", err)
+ }
+ w, err = armor.Encode(privKeyFile, openpgp.PrivateKeyType, nil)
+ if err != nil {
+ t.Fatalf("Failed to create armored writer for private key: %v", err)
+ }
+ if err := entity.SerializePrivate(w, nil); err != nil {
+ t.Fatalf("Failed to serialize private key: %v", err)
+ }
+ w.Close()
+ privKeyFile.Close()
+
+ cleanup := func() { os.RemoveAll(tempDir) }
+ return pubKeyPath, privKeyPath, cleanup
+}
+
+func TestEncryptDecryptPGP(t *testing.T) {
+ recipientPub, recipientPriv, cleanup := generateTestKeys(t, "recipient", "recipient-pass")
+ defer cleanup()
+
+ originalMessage := "This is a secret message."
+
+ // --- Test Encryption ---
+ var encryptedBuf bytes.Buffer
+ encryptedMessage, err := EncryptPGP(&encryptedBuf, recipientPub, originalMessage, nil, nil)
+ if err != nil {
+ t.Fatalf("EncryptPGP() failed: %v", err)
+ }
+
+ if !strings.Contains(encryptedMessage, "-----BEGIN PGP MESSAGE-----") {
+ t.Errorf("Encrypted message does not appear to be PGP armored")
+ }
+
+ // --- Test Decryption ---
+ decryptedMessage, err := DecryptPGP(recipientPriv, encryptedMessage, "recipient-pass", nil)
+ if err != nil {
+ t.Fatalf("DecryptPGP() failed: %v", err)
+ }
+
+ if decryptedMessage != originalMessage {
+ t.Errorf("Decrypted message does not match original. got=%q, want=%q", decryptedMessage, originalMessage)
+ }
+}
+
+func TestSignAndVerifyPGP(t *testing.T) {
+ recipientPub, recipientPriv, rCleanup := generateTestKeys(t, "recipient", "recipient-pass")
+ defer rCleanup()
+
+ signerPub, signerPriv, sCleanup := generateTestKeys(t, "signer", "signer-pass")
+ defer sCleanup()
+
+ originalMessage := "This is a signed and verified message."
+
+ // --- Encrypt and Sign ---
+ var encryptedBuf bytes.Buffer
+ signerPass := "signer-pass"
+ encryptedMessage, err := EncryptPGP(&encryptedBuf, recipientPub, originalMessage, &signerPriv, &signerPass)
+ if err != nil {
+ t.Fatalf("EncryptPGP() with signing failed: %v", err)
+ }
+
+ // --- Decrypt and Verify ---
+ decryptedMessage, err := DecryptPGP(recipientPriv, encryptedMessage, "recipient-pass", &signerPub)
+ if err != nil {
+ t.Fatalf("DecryptPGP() with verification failed: %v", err)
+ }
+
+ if decryptedMessage != originalMessage {
+ t.Errorf("Decrypted message does not match original. got=%q, want=%q", decryptedMessage, originalMessage)
+ }
+}
+
+func TestVerificationFailure(t *testing.T) {
+ recipientPub, recipientPriv, rCleanup := generateTestKeys(t, "recipient", "recipient-pass")
+ defer rCleanup()
+
+ _, signerPriv, sCleanup := generateTestKeys(t, "signer", "signer-pass")
+ defer sCleanup()
+
+ // Generate a third, unexpected key to test verification failure
+ unexpectedSignerPub, _, uCleanup := generateTestKeys(t, "unexpected", "unexpected-pass")
+ defer uCleanup()
+
+ originalMessage := "This message should fail verification."
+
+ // --- Encrypt and Sign with the actual signer key ---
+ var encryptedBuf bytes.Buffer
+ signerPass := "signer-pass"
+ encryptedMessage, err := EncryptPGP(&encryptedBuf, recipientPub, originalMessage, &signerPriv, &signerPass)
+ if err != nil {
+ t.Fatalf("EncryptPGP() with signing failed: %v", err)
+ }
+
+ // --- Attempt to Decrypt and Verify with the WRONG public key ---
+ _, err = DecryptPGP(recipientPriv, encryptedMessage, "recipient-pass", &unexpectedSignerPub)
+ if err == nil {
+ t.Fatal("DecryptPGP() did not fail, but verification with an incorrect key was expected to fail.")
+ }
+
+ if !strings.Contains(err.Error(), "signature from unexpected key") {
+ t.Errorf("Expected error to contain 'signature from unexpected key', but got: %v", err)
+ }
+}
diff --git a/pkg/v1/core/crypt/lib/openpgp/key.go b/pkg/core/crypt/openpgp/key.go
similarity index 77%
rename from pkg/v1/core/crypt/lib/openpgp/key.go
rename to pkg/core/crypt/openpgp/key.go
index 89b61cfb..4ccd3c2f 100644
--- a/pkg/v1/core/crypt/lib/openpgp/key.go
+++ b/pkg/core/crypt/openpgp/key.go
@@ -5,14 +5,12 @@ import (
"crypto"
"fmt"
"path/filepath"
- "strings"
"time"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
"github.com/ProtonMail/go-crypto/openpgp/packet"
- "github.com/Snider/Core/crypt/lib/lthn"
- "github.com/Snider/Core/io"
+ "github.com/Snider/Core/crypt/lthn"
)
// CreateKeyPair generates a new OpenPGP key pair.
@@ -72,13 +70,13 @@ func CreateServerKeyPair(keysDir string) error {
}
// GetPublicKey retrieves an armored public key for a given ID.
-func GetPublicKey(medium io.Medium, path string) (*openpgp.Entity, error) {
- return readEntity(medium, path)
+func GetPublicKey(path string) (*openpgp.Entity, error) {
+ return readEntity(path)
}
// GetPrivateKey retrieves and decrypts an armored private key.
-func GetPrivateKey(medium io.Medium, path, passphrase string) (*openpgp.Entity, error) {
- entity, err := readEntity(medium, path)
+func GetPrivateKey(path, passphrase string) (*openpgp.Entity, error) {
+ entity, err := readEntity(path)
if err != nil {
return nil, err
}
@@ -123,54 +121,55 @@ func GetPrivateKey(medium io.Medium, path, passphrase string) (*openpgp.Entity,
// --- Helper Functions ---
func createAndStoreKeyPair(id, password, dir string) error {
- var keyPair *KeyPair
+ //var keyPair *KeyPair
var err error
- if password != "" {
- keyPair, err = CreateKeyPair(id, password)
- } else {
- keyPair, err = CreateKeyPair(id)
- }
+ //if password != "" {
+ // keyPair, err = CreateKeyPair(id, password)
+ //} else {
+ // keyPair, err = CreateKeyPair(id)
+ //}
if err != nil {
return fmt.Errorf("failed to create key pair for id %s: %w", id, err)
}
- if err := io.Local.EnsureDir(dir); err != nil {
- return fmt.Errorf("failed to ensure key directory exists: %w", err)
- }
-
- files := map[string]string{
- filepath.Join(dir, fmt.Sprintf("%s.lthn.pub", id)): keyPair.PublicKey,
- filepath.Join(dir, fmt.Sprintf("%s.lthn.key", id)): keyPair.PrivateKey,
- filepath.Join(dir, fmt.Sprintf("%s.lthn.rev", id)): keyPair.RevocationCertificate, // Re-enabled
- }
-
- for path, content := range files {
- if content == "" {
- continue
- }
- if err := io.Local.Write(path, content); err != nil {
- return fmt.Errorf("failed to write key file %s: %w", path, err)
- }
- }
+ //if err := io.Local.EnsureDir(dir); err != nil {
+ // return fmt.Errorf("failed to ensure key directory exists: %w", err)
+ //}
+ //
+ //files := map[string]string{
+ // filepath.Join(dir, fmt.Sprintf("%s.lthn.pub", id)): keyPair.PublicKey,
+ // filepath.Join(dir, fmt.Sprintf("%s.lthn.key", id)): keyPair.PrivateKey,
+ // filepath.Join(dir, fmt.Sprintf("%s.lthn.rev", id)): keyPair.RevocationCertificate, // Re-enabled
+ //}
+ //
+ //for path, content := range files {
+ // if content == "" {
+ // continue
+ // }
+ // if err := io.Local.Write(path, content); err != nil {
+ // return fmt.Errorf("failed to write key file %s: %w", path, err)
+ // }
+ //}
return nil
}
-func readEntity(m io.Medium, path string) (*openpgp.Entity, error) {
- keyArmored, err := m.Read(path)
- if err != nil {
- return nil, fmt.Errorf("failed to read key file %s: %w", path, err)
- }
+func readEntity(path string) (*openpgp.Entity, error) {
+ //keyArmored, err := m.Read(path)
+ //if err != nil {
+ // return nil, fmt.Errorf("failed to read key file %s: %w", path, err)
+ //}
- entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(keyArmored))
- if err != nil {
- return nil, fmt.Errorf("failed to parse key file %s: %w", path, err)
- }
- if len(entityList) == 0 {
- return nil, fmt.Errorf("no entity found in key file %s", path)
- }
- return entityList[0], nil
+ //entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(keyArmored))
+ //if err != nil {
+ // return nil, fmt.Errorf("failed to parse key file %s: %w", path, err)
+ //}
+ //if len(entityList) == 0 {
+ // return nil, fmt.Errorf("no entity found in key file %s", path)
+ //}
+ //return entityList[0], nil
+ return nil, nil
}
func serializeEntity(entity *openpgp.Entity, keyType string, password string) (string, error) {
diff --git a/pkg/v1/core/crypt/lib/openpgp/openpgp.go b/pkg/core/crypt/openpgp/openpgp.go
similarity index 100%
rename from pkg/v1/core/crypt/lib/openpgp/openpgp.go
rename to pkg/core/crypt/openpgp/openpgp.go
diff --git a/pkg/v1/core/crypt/lib/openpgp/sign.go b/pkg/core/crypt/openpgp/sign.go
similarity index 72%
rename from pkg/v1/core/crypt/lib/openpgp/sign.go
rename to pkg/core/crypt/openpgp/sign.go
index 1314ce04..a853350e 100644
--- a/pkg/v1/core/crypt/lib/openpgp/sign.go
+++ b/pkg/core/crypt/openpgp/sign.go
@@ -6,12 +6,11 @@ import (
"strings"
"github.com/ProtonMail/go-crypto/openpgp"
- "github.com/Snider/Core/io"
)
// Sign creates a detached signature for the data.
-func Sign(medium io.Medium, data, privateKeyPath, passphrase string) (string, error) {
- signer, err := GetPrivateKey(medium, privateKeyPath, passphrase)
+func Sign(data, privateKeyPath, passphrase string) (string, error) {
+ signer, err := GetPrivateKey(privateKeyPath, passphrase)
if err != nil {
return "", fmt.Errorf("failed to get private key for signing: %w", err)
}
@@ -25,8 +24,8 @@ func Sign(medium io.Medium, data, privateKeyPath, passphrase string) (string, er
}
// Verify checks a detached signature.
-func Verify(medium io.Medium, data, signature, publicKeyPath string) (bool, error) {
- keyring, err := GetPublicKey(medium, publicKeyPath)
+func Verify(data, signature, publicKeyPath string) (bool, error) {
+ keyring, err := GetPublicKey(publicKeyPath)
if err != nil {
return false, fmt.Errorf("failed to get public key for verification: %w", err)
}
diff --git a/pkg/v1/core/crypt/sum.go b/pkg/core/crypt/sum.go
similarity index 100%
rename from pkg/v1/core/crypt/sum.go
rename to pkg/core/crypt/sum.go
diff --git a/pkg/core/display/actions.go b/pkg/core/display/actions.go
new file mode 100644
index 00000000..ba2d8fdc
--- /dev/null
+++ b/pkg/core/display/actions.go
@@ -0,0 +1,8 @@
+package display
+
+import "github.com/wailsapp/wails/v3/pkg/application"
+
+// ActionOpenWindow is an IPC message used to request a new window.
+type ActionOpenWindow struct {
+ application.WebviewWindowOptions
+}
diff --git a/pkg/core/display/display.go b/pkg/core/display/display.go
new file mode 100644
index 00000000..33b2505e
--- /dev/null
+++ b/pkg/core/display/display.go
@@ -0,0 +1,127 @@
+package display
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/Snider/Core"
+ "github.com/wailsapp/wails/v3/pkg/application"
+ "github.com/wailsapp/wails/v3/pkg/events"
+)
+
+// Options holds configuration for the display service.
+type Options struct{}
+
+// Service manages windowing, dialogs, and other visual elements.
+type Service struct {
+ *core.Runtime[Options]
+}
+
+// New is a factory function that creates a new display Service.
+func New(c *core.Core) (any, error) {
+ return &Service{
+ Runtime: core.NewRuntime(c, Options{}),
+ }, nil
+}
+
+func (s *Service) ServiceName() string { return "github.com/Snider/Core/display" }
+
+// HandleIPCEvents processes IPC messages and performs actions such as opening windows or initializing services based on message types.
+func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) error {
+ switch m := msg.(type) {
+ case map[string]any:
+ if action, ok := m["action"].(string); ok && action == "display.open_window" {
+ return s.handleOpenWindowAction(m)
+ }
+ case ActionOpenWindow:
+ _, err := s.NewWithStruct(&m.WebviewWindowOptions)
+ return err
+ case core.ActionServiceStartup:
+ return s.ServiceStartup(context.Background(), application.ServiceOptions{})
+ default:
+ c.App.Logger.Error("Display: Unknown message type", "type", fmt.Sprintf("%T", m))
+ }
+ return nil
+}
+
+// handleOpenWindowAction processes a message to configure and create a new window using specified name and options.
+func (s *Service) handleOpenWindowAction(msg map[string]any) error {
+ opts := application.WebviewWindowOptions{}
+ if name, ok := msg["name"].(string); ok {
+ opts.Name = name
+ }
+ if optsMap, ok := msg["options"].(map[string]any); ok {
+ if title, ok := optsMap["Title"].(string); ok {
+ opts.Title = title
+ }
+ if width, ok := optsMap["Width"].(float64); ok {
+ opts.Width = int(width)
+ }
+ if height, ok := optsMap["Height"].(float64); ok {
+ opts.Height = int(height)
+ }
+ }
+ s.Core().App.Window.NewWithOptions(opts)
+ return nil
+}
+
+// ShowEnvironmentDialog displays a dialog containing detailed information about the application's runtime environment.
+func (s *Service) ShowEnvironmentDialog() {
+ envInfo := s.Core().App.Env.Info()
+
+ details := fmt.Sprintf(`Environment Information:
+
+Operating System: %s
+Architecture: %s
+Debug Mode: %t
+
+Dark Mode: %t
+
+Platform Information:`,
+ envInfo.OS,
+ envInfo.Arch,
+ envInfo.Debug,
+ s.Core().App.Env.IsDarkMode()) // Use d.core.App
+
+ // Add platform-specific details
+ for key, value := range envInfo.PlatformInfo {
+ details += fmt.Sprintf("\n%s: %v", key, value)
+ }
+
+ if envInfo.OSInfo != nil {
+ details += fmt.Sprintf("\n\nOS Details:\nName: %s\nVersion: %s",
+ envInfo.OSInfo.Name,
+ envInfo.OSInfo.Version)
+ }
+
+ dialog := s.Core().App.Dialog.Info()
+ dialog.SetTitle("Environment Information")
+ dialog.SetMessage(details)
+ dialog.Show()
+}
+
+// ServiceStartup initializes the display service and sets up the main application window and system tray.
+func (s *Service) ServiceStartup(context.Context, application.ServiceOptions) error {
+ s.Core().App.Logger.Info("Display service started")
+ s.buildMenu()
+ s.systemTray()
+
+ // This will be updated to use the restored OpenWindow method
+ mainOpts := application.WebviewWindowOptions{
+ Name: "main",
+ Title: "Core",
+ Height: 900,
+ Width: 1280,
+ URL: "/",
+ }
+ s.Core().App.Window.NewWithOptions(mainOpts)
+
+ return nil
+}
+
+// monitorScreenChanges listens for theme change events and logs when screen configuration changes occur.
+func (s *Service) monitorScreenChanges() {
+ s.Core().App.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
+ s.Core().App.Logger.Info("Screen configuration changed")
+ })
+}
diff --git a/pkg/v1/core/display/menu.go b/pkg/core/display/menu.go
similarity index 87%
rename from pkg/v1/core/display/menu.go
rename to pkg/core/display/menu.go
index b01cd2be..63bb0d11 100644
--- a/pkg/v1/core/display/menu.go
+++ b/pkg/core/display/menu.go
@@ -7,8 +7,8 @@ import (
)
// buildMenu creates and sets the main application menu.
-func (d *API) buildMenu() {
- appMenu := d.core.App.Menu.New()
+func (s *Service) buildMenu() {
+ appMenu := s.Core().App.Menu.New()
if runtime.GOOS == "darwin" {
appMenu.AddRole(application.AppMenu)
}
@@ -28,5 +28,5 @@ func (d *API) buildMenu() {
appMenu.AddRole(application.WindowMenu)
appMenu.AddRole(application.HelpMenu)
- d.core.App.Menu.Set(appMenu)
+ s.Core().App.Menu.Set(appMenu)
}
diff --git a/pkg/v1/core/display/tray.go b/pkg/core/display/tray.go
similarity index 85%
rename from pkg/v1/core/display/tray.go
rename to pkg/core/display/tray.go
index 2f048d74..b96f2ef6 100644
--- a/pkg/v1/core/display/tray.go
+++ b/pkg/core/display/tray.go
@@ -7,9 +7,9 @@ import (
)
// setupTray configures and creates the system tray icon and menu.
-func (d *API) systemTray() {
+func (s *Service) systemTray() {
- systray := d.core.App.SystemTray.New()
+ systray := s.Core().App.SystemTray.New()
systray.SetTooltip("Core")
systray.SetLabel("Core")
//appTrayIcon, _ := d.assets.ReadFile("assets/apptray.png")
@@ -22,7 +22,7 @@ func (d *API) systemTray() {
// systray.SetIcon(appTrayIcon)
//}
// Create a hidden window for the system tray menu to interact with
- trayWindow := d.NewWithStruct(&Window{
+ trayWindow, _ := s.NewWithStruct(&Window{
Name: "system-tray",
Title: "System Tray Status",
URL: "system-tray.html",
@@ -33,20 +33,20 @@ func (d *API) systemTray() {
systray.AttachWindow(trayWindow).WindowOffset(5)
// --- Build Tray Menu ---
- trayMenu := d.core.App.Menu.New()
+ trayMenu := s.Core().App.Menu.New()
trayMenu.Add("Open Desktop").OnClick(func(ctx *application.Context) {
- for _, window := range d.core.App.Window.GetAll() {
+ for _, window := range s.Core().App.Window.GetAll() {
window.Show()
}
})
trayMenu.Add("Close Desktop").OnClick(func(ctx *application.Context) {
- for _, window := range d.core.App.Window.GetAll() {
+ for _, window := range s.Core().App.Window.GetAll() {
window.Hide()
}
})
trayMenu.Add("Environment Info").OnClick(func(ctx *application.Context) {
- d.ShowEnvironmentDialog()
+ s.ShowEnvironmentDialog()
})
// Add brand-specific menu items
//switch d.brand {
@@ -65,7 +65,7 @@ func (d *API) systemTray() {
trayMenu.AddSeparator()
trayMenu.Add("Quit").OnClick(func(ctx *application.Context) {
- d.core.App.Quit()
+ s.Core().App.Quit()
})
systray.SetMenu(trayMenu)
diff --git a/pkg/core/display/window.go b/pkg/core/display/window.go
new file mode 100644
index 00000000..48598210
--- /dev/null
+++ b/pkg/core/display/window.go
@@ -0,0 +1,93 @@
+package display
+
+import (
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+type WindowOption func(*application.WebviewWindowOptions) error
+
+type Window = application.WebviewWindowOptions
+
+func WindowName(s string) WindowOption {
+ return func(o *Window) error {
+ o.Name = s
+ return nil
+ }
+}
+func WindowTitle(s string) WindowOption {
+ return func(o *Window) error {
+ o.Title = s
+ return nil
+ }
+}
+
+func WindowURL(s string) WindowOption {
+ return func(o *Window) error {
+ o.URL = s
+ return nil
+ }
+}
+
+func WindowWidth(i int) WindowOption {
+ return func(o *Window) error {
+ o.Width = i
+ return nil
+ }
+}
+
+func WindowHeight(i int) WindowOption {
+ return func(o *Window) error {
+ o.Height = i
+ return nil
+ }
+}
+
+func applyOptions(opts ...WindowOption) *Window {
+ w := &Window{}
+ if opts == nil {
+ return w
+ }
+ for _, o := range opts {
+ if err := o(w); err != nil {
+ return nil
+ }
+ }
+ return w
+}
+
+// NewWithStruct creates a new window using the provided options and returns its handle.
+func (s *Service) NewWithStruct(options *Window) (*application.WebviewWindow, error) {
+ return s.Core().App.Window.NewWithOptions(*options), nil
+}
+
+// NewWithOptions creates a new window by applying a series of options.
+func (s *Service) NewWithOptions(opts ...WindowOption) (*application.WebviewWindow, error) {
+ return s.NewWithStruct(applyOptions(opts...))
+}
+
+// NewWithURL creates a new default window pointing to the specified URL.
+func (s *Service) NewWithURL(url string) (*application.WebviewWindow, error) {
+ return s.NewWithOptions(
+ WindowURL(url),
+ WindowTitle("Core"),
+ WindowHeight(900),
+ WindowWidth(1280),
+ )
+}
+
+// OpenWindow is a convenience method that creates and shows a window from a set of options.
+func (s *Service) OpenWindow(opts ...WindowOption) error {
+ _, err := s.NewWithOptions(opts...)
+ return err
+}
+
+// SelectDirectory opens a directory selection dialog and returns the selected path.
+func (s *Service) SelectDirectory() (string, error) {
+ dialog := application.OpenFileDialog()
+ dialog.SetTitle("Select Project Directory")
+ return dialog.PromptForSingleSelection()
+}
+
+var instance *Window
+
+func (s *Service) Window() *Window { return instance }
diff --git a/pkg/core/docs/.gitignore b/pkg/core/docs/.gitignore
deleted file mode 100644
index 16d3c4db..00000000
--- a/pkg/core/docs/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-.cache
diff --git a/pkg/v1/core/docs/assets/stylesheets/extra.css b/pkg/core/docs/assets/stylesheets/extra.css
similarity index 100%
rename from pkg/v1/core/docs/assets/stylesheets/extra.css
rename to pkg/core/docs/assets/stylesheets/extra.css
diff --git a/pkg/core/docs/docs.go b/pkg/core/docs/docs.go
new file mode 100644
index 00000000..aeab9b8a
--- /dev/null
+++ b/pkg/core/docs/docs.go
@@ -0,0 +1,50 @@
+package docs
+
+import (
+ "embed"
+
+ "github.com/Snider/Core"
+)
+
+//go:embed all:static/*
+var docsStatic embed.FS
+
+// Options holds configuration for the doc service.
+type Options struct{}
+
+// Service manages the documentation assets and display requests.
+// It embeds the core.Runtime to get access to the core instance and its functions.
+type Service struct {
+ *core.Runtime[Options]
+ assets embed.FS
+}
+
+// New is a factory function that creates a new docs Service.
+// It is self-contained and only depends on the Core, with no knowledge
+// of other services at compile time.
+func New(c *core.Core) (any, error) {
+ s := &Service{
+ Runtime: core.NewRuntime(c, Options{}),
+ assets: docsStatic,
+ }
+ return s, nil
+}
+
+// Show triggers the display of the documentation window.
+func (s *Service) Show() error {
+ // The message is a generic map, which any service can create. The 'display'
+ // service will register a handler that knows how to interpret this structure.
+ msg := map[string]any{
+ "action": "display.open_window",
+ "name": "docs",
+ "options": map[string]any{
+ "Title": "Documentation",
+ "Width": 800,
+ "Height": 600,
+ },
+ }
+
+ // Dispatch the message through the core. The core will route it to the
+ // appropriate handler, in this case, the one registered by the display service.
+ return s.Core().ACTION(msg)
+}
diff --git a/pkg/v1/core/docs/mkdocs.yml b/pkg/core/docs/mkdocs.yml
similarity index 100%
rename from pkg/v1/core/docs/mkdocs.yml
rename to pkg/core/docs/mkdocs.yml
diff --git a/pkg/v1/core/docs/public/404.html b/pkg/core/docs/public/404.html
similarity index 84%
rename from pkg/v1/core/docs/public/404.html
rename to pkg/core/docs/public/404.html
index e4d7c2fc..2531aca9 100644
--- a/pkg/v1/core/docs/public/404.html
+++ b/pkg/core/docs/public/404.html
@@ -16,7 +16,7 @@
- Core Documentation
+ Core.Help
@@ -31,13 +31,15 @@
+
+
-
-
+
+
@@ -79,7 +81,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Core.IO
+Short: Local/remote filesystem helpers.
+Overview
+Abstracts filesystems (local, SFTP, WebDAV) behind a unified API for reading/writing and listing.
+Setup
+import (
+ core "github.com/Snider/Core"
+ ioapi "github.com/Snider/Core/filesystem"
+)
+
+app := core.New(
+ core.WithService(ioapi.Register),
+ core.WithServiceLock(),
+)
+
+Use
+
+- Open a filesystem:
fs := ioapi.Local() or ioapi.SFTP(cfg)
+- Read/write files:
fs.Read(path), fs.Write(path, data)
+- List directories:
fs.List(path)
+
+API
+
+Register(c *core.Core) error
+Local() FS
+SFTP(cfg Config) (FS, error)
+WebDAV(cfg Config) (FS, error)
+
+Notes
+
+- See package
pkg/v1/core/filesystem/* for drivers.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pkg/core/docs/public/core/io.html b/pkg/core/docs/public/core/io.html
new file mode 100644
index 00000000..e9e2cb51
--- /dev/null
+++ b/pkg/core/docs/public/core/io.html
@@ -0,0 +1,903 @@
+
+
+
+