diff --git a/OP/.gitignore b/OP/.gitignore
new file mode 100644
index 0000000..6303abf
--- /dev/null
+++ b/OP/.gitignore
@@ -0,0 +1,270 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+publish_release/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+**/.DS_Store
+
+**/.vs/
+.DS_Store
+
+
+publish_release/
\ No newline at end of file
diff --git a/OP/Build/Build.csproj b/OP/Build/Build.csproj
new file mode 100644
index 0000000..6c1dc92
--- /dev/null
+++ b/OP/Build/Build.csproj
@@ -0,0 +1,10 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+
+
+
diff --git a/OP/Build/Program.cs b/OP/Build/Program.cs
new file mode 100644
index 0000000..1e8db2d
--- /dev/null
+++ b/OP/Build/Program.cs
@@ -0,0 +1,304 @@
+using System.Diagnostics;
+using System.Text.Json;
+using System.Text.RegularExpressions;
+
+const string DocsFolderName = "op.docs";
+const string PluginSourceFolder = "OP_plugin";
+const string PluginManifestFileName = "manifest.json";
+
+try
+{
+ var buildProjectDir = GetBuildProjectDirectory();
+ var workspaceRoot = Path.GetFullPath(Path.Combine(buildProjectDir, ".."));
+ var docsRoot = Path.GetFullPath(Path.Combine(workspaceRoot, "..", DocsFolderName));
+ var pluginSourceRoot = Path.GetFullPath(Path.Combine(workspaceRoot, "..", PluginSourceFolder));
+
+ Console.WriteLine($"Workspace root: {workspaceRoot}");
+ Console.WriteLine($"Docs root: {docsRoot}");
+ Console.WriteLine($"Plugin source: {pluginSourceRoot}");
+
+ EnsureDirectoryExists(docsRoot, "Docs folder not found. Make sure op.docs exists in the workspace root.");
+ EnsureDirectoryExists(pluginSourceRoot, "Plugin source folder not found. Make sure OP_plugin exists in the workspace root.");
+
+ var manifestPath = Path.Combine(pluginSourceRoot, PluginManifestFileName);
+ if (!File.Exists(manifestPath))
+ throw new InvalidOperationException($"Plugin manifest not found at {manifestPath}");
+
+ BuildPlugin(pluginSourceRoot);
+
+ var pluginId = ReadPluginId(manifestPath);
+ Console.WriteLine($"Detected plugin id: {pluginId}");
+
+ var userRoot = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+ var docsTargets = ScanDocTargets(docsRoot);
+
+ if (!docsTargets.Any())
+ {
+ Console.WriteLine("No docs files with 'category: docs' were found.");
+ return;
+ }
+
+ var uniqueTargets = docsTargets.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
+
+ Console.WriteLine($"Found {uniqueTargets.Count} unique docs target(s):");
+ foreach (var target in uniqueTargets)
+ {
+ Console.WriteLine($" - {target}");
+ }
+
+ foreach (var targetPath in uniqueTargets)
+ {
+ var targetRoot = BuildAbsolutePathFromUserRoot(userRoot, targetPath);
+ var pluginsRoot = Path.Combine(targetRoot, ".obsidian", "plugins");
+ var pluginDestination = Path.Combine(pluginsRoot, pluginId);
+
+ Console.WriteLine();
+ Console.WriteLine($"Installing plugin to: {pluginDestination}");
+
+ if (Directory.Exists(pluginDestination))
+ {
+ Console.WriteLine("Existing plugin folder found. Deleting previous version...");
+ Directory.Delete(pluginDestination, recursive: true);
+ }
+
+ Directory.CreateDirectory(pluginsRoot);
+ CopyDirectory(pluginSourceRoot, pluginDestination, GetCopyExcludes());
+ Console.WriteLine("Plugin copied successfully.");
+ }
+}
+catch (Exception ex)
+{
+ Console.Error.WriteLine("ERROR: " + ex.Message);
+ Environment.Exit(1);
+}
+
+static string GetBuildProjectDirectory()
+{
+ var baseDir = AppContext.BaseDirectory;
+ // Current baseDir is Build/bin/Debug/net10.0/
+ // We want to go up to the project root (where Build/ and OP.sln are)
+ return Path.GetFullPath(Path.Combine(baseDir, "..", "..", ".."));
+}
+
+static void EnsureDirectoryExists(string path, string message)
+{
+ if (!Directory.Exists(path))
+ throw new DirectoryNotFoundException(message);
+}
+
+static IReadOnlyCollection ScanDocTargets(string docsRoot)
+{
+ var docsTargetPaths = new List();
+ var markdownFiles = Directory.EnumerateFiles(docsRoot, "*.md", SearchOption.AllDirectories);
+
+ foreach (var filePath in markdownFiles)
+ {
+ var content = File.ReadAllText(filePath);
+ var frontMatter = ExtractFrontMatter(content);
+ if (frontMatter is null)
+ continue;
+
+ if (!frontMatter.TryGetValue("category", out var categoryValue) ||
+ !string.Equals(categoryValue.Trim(), "docs", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ if (!frontMatter.TryGetValue("path", out var pathValue) || string.IsNullOrWhiteSpace(pathValue))
+ {
+ Console.WriteLine($"WARNING: File '{filePath}' has category: docs but missing path.");
+ continue;
+ }
+
+ docsTargetPaths.Add(pathValue.Trim());
+ }
+
+ return docsTargetPaths;
+}
+
+static Dictionary? ExtractFrontMatter(string content)
+{
+ var match = Regex.Match(content, @"\A---\r?\n(.*?)\r?\n---", RegexOptions.Singleline);
+ if (!match.Success)
+ return null;
+
+ var frontMatter = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ var body = match.Groups[1].Value;
+
+ foreach (var rawLine in body.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries))
+ {
+ var line = rawLine.Trim();
+ if (line.Length == 0 || line.StartsWith("#"))
+ continue;
+
+ var separatorIndex = line.IndexOf(':');
+ if (separatorIndex < 0)
+ continue;
+
+ var key = line.Substring(0, separatorIndex).Trim();
+ var value = line.Substring(separatorIndex + 1).Trim().Trim('"').Trim();
+ frontMatter[key] = value;
+ }
+
+ return frontMatter;
+}
+
+static string BuildAbsolutePathFromUserRoot(string userRoot, string relativePath)
+{
+ if (Path.IsPathRooted(relativePath))
+ {
+ return Path.GetFullPath(relativePath);
+ }
+
+ var segments = relativePath
+ .Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(segment => segment.Trim())
+ .Where(segment => segment.Length > 0)
+ .ToArray();
+
+ if (segments.Length == 0)
+ throw new InvalidOperationException($"Invalid path value: '{relativePath}'");
+
+ return Path.GetFullPath(Path.Combine(new[] { userRoot }.Concat(segments).ToArray()));
+}
+
+static string ReadPluginId(string manifestPath)
+{
+ using var stream = File.OpenRead(manifestPath);
+ using var doc = JsonDocument.Parse(stream);
+ if (!doc.RootElement.TryGetProperty("id", out var idElement))
+ throw new InvalidOperationException("Plugin manifest does not contain an 'id' property.");
+
+ var pluginId = idElement.GetString();
+ if (string.IsNullOrWhiteSpace(pluginId))
+ throw new InvalidOperationException("Plugin id is empty.");
+
+ return pluginId.Trim();
+}
+
+static void BuildPlugin(string pluginSourceRoot)
+{
+ Console.WriteLine("Building plugin in OP_plugin...");
+ var npmExecutable = FindNpmExecutable();
+ if (npmExecutable is null)
+ {
+ throw new InvalidOperationException("Could not find npm on PATH. Install Node.js/npm or add npm to your PATH.");
+ }
+
+ var processStartInfo = new ProcessStartInfo(npmExecutable)
+ {
+ WorkingDirectory = pluginSourceRoot,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ };
+
+ processStartInfo.FileName = "cmd";
+ processStartInfo.ArgumentList.Add("/c");
+ processStartInfo.ArgumentList.Add(npmExecutable);
+ processStartInfo.ArgumentList.Add("run");
+ processStartInfo.ArgumentList.Add("build");
+
+ using var process = Process.Start(processStartInfo) ?? throw new InvalidOperationException($"Failed to start 'cmd /c {npmExecutable}'.");
+ var stdout = process.StandardOutput.ReadToEnd();
+ var stderr = process.StandardError.ReadToEnd();
+ process.WaitForExit();
+
+ Console.WriteLine(stdout);
+ if (!string.IsNullOrWhiteSpace(stderr))
+ Console.Error.WriteLine(stderr);
+
+ if (process.ExitCode != 0)
+ throw new InvalidOperationException($"Plugin build failed with exit code {process.ExitCode}.");
+
+ Console.WriteLine("Plugin build completed successfully.");
+}
+
+static string? FindNpmExecutable()
+{
+ var candidates = new[] { "npm.cmd", "npm.exe", "npm" };
+ foreach (var candidate in candidates)
+ {
+ try
+ {
+ var processStartInfo = new ProcessStartInfo("cmd", $"/c {candidate} --version")
+ {
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ };
+ using var process = Process.Start(processStartInfo);
+ if (process is null)
+ continue;
+
+ bool exited = process.WaitForExit(5000);
+ if (!exited)
+ {
+ process.Kill();
+ continue;
+ }
+
+ if (process.ExitCode == 0)
+ return candidate;
+ }
+ catch
+ {
+ continue;
+ }
+ }
+
+ return null;
+}
+
+static HashSet GetCopyExcludes()
+{
+ return new(StringComparer.OrdinalIgnoreCase)
+ {
+ "node_modules",
+ "src",
+ "obj",
+ "bin",
+ ".git",
+ ".idea",
+ ".vscode",
+ ".editorconfig",
+ ".npmrc",
+ "package.json",
+ "package-lock.json",
+ "tsconfig.json",
+ "esbuild.config.mjs",
+ "eslint.config.mts",
+ "version-bump.mjs",
+ "versions.json",
+ "data.json",
+ "README.md",
+ "AGENTS.md",
+ "LICENSE"
+ };
+}
+
+static void CopyDirectory(string sourceDir, string destinationDir, HashSet excludes)
+{
+ Directory.CreateDirectory(destinationDir);
+
+ foreach (var filePath in Directory.GetFiles(sourceDir))
+ {
+ var fileName = Path.GetFileName(filePath);
+ if (excludes.Contains(fileName))
+ continue;
+
+ var destinationFile = Path.Combine(destinationDir, fileName);
+ File.Copy(filePath, destinationFile, overwrite: true);
+ }
+
+ foreach (var directoryPath in Directory.GetDirectories(sourceDir))
+ {
+ var dirName = Path.GetFileName(directoryPath);
+ if (excludes.Contains(dirName))
+ continue;
+
+ CopyDirectory(directoryPath, Path.Combine(destinationDir, dirName), excludes);
+ }
+}
diff --git a/OP/OP.sln b/OP/OP.sln
new file mode 100644
index 0000000..497efb2
--- /dev/null
+++ b/OP/OP.sln
@@ -0,0 +1,16 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Build", "Build\Build.csproj", "{57A2E434-08CD-47BF-9F3D-E1BF74C34EE7}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {57A2E434-08CD-47BF-9F3D-E1BF74C34EE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {57A2E434-08CD-47BF-9F3D-E1BF74C34EE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {57A2E434-08CD-47BF-9F3D-E1BF74C34EE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {57A2E434-08CD-47BF-9F3D-E1BF74C34EE7}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/OP/.editorconfig b/OP_plugin/.editorconfig
similarity index 100%
rename from OP/.editorconfig
rename to OP_plugin/.editorconfig
diff --git a/OP/.npmrc b/OP_plugin/.npmrc
similarity index 100%
rename from OP/.npmrc
rename to OP_plugin/.npmrc
diff --git a/OP/AGENTS.md b/OP_plugin/AGENTS.md
similarity index 100%
rename from OP/AGENTS.md
rename to OP_plugin/AGENTS.md
diff --git a/OP/LICENSE b/OP_plugin/LICENSE
similarity index 100%
rename from OP/LICENSE
rename to OP_plugin/LICENSE
diff --git a/OP/README.md b/OP_plugin/README.md
similarity index 100%
rename from OP/README.md
rename to OP_plugin/README.md
diff --git a/OP/esbuild.config.mjs b/OP_plugin/esbuild.config.mjs
similarity index 100%
rename from OP/esbuild.config.mjs
rename to OP_plugin/esbuild.config.mjs
diff --git a/OP/eslint.config.mts b/OP_plugin/eslint.config.mts
similarity index 100%
rename from OP/eslint.config.mts
rename to OP_plugin/eslint.config.mts
diff --git a/OP/manifest.json b/OP_plugin/manifest.json
similarity index 100%
rename from OP/manifest.json
rename to OP_plugin/manifest.json
diff --git a/OP/package-lock.json b/OP_plugin/package-lock.json
similarity index 100%
rename from OP/package-lock.json
rename to OP_plugin/package-lock.json
diff --git a/OP/package.json b/OP_plugin/package.json
similarity index 100%
rename from OP/package.json
rename to OP_plugin/package.json
diff --git a/OP/src/main.ts b/OP_plugin/src/main.ts
similarity index 100%
rename from OP/src/main.ts
rename to OP_plugin/src/main.ts
diff --git a/OP/src/settings.ts b/OP_plugin/src/settings.ts
similarity index 100%
rename from OP/src/settings.ts
rename to OP_plugin/src/settings.ts
diff --git a/OP/styles.css b/OP_plugin/styles.css
similarity index 100%
rename from OP/styles.css
rename to OP_plugin/styles.css
diff --git a/OP/tsconfig.json b/OP_plugin/tsconfig.json
similarity index 100%
rename from OP/tsconfig.json
rename to OP_plugin/tsconfig.json
diff --git a/OP/version-bump.mjs b/OP_plugin/version-bump.mjs
similarity index 100%
rename from OP/version-bump.mjs
rename to OP_plugin/version-bump.mjs
diff --git a/OP/versions.json b/OP_plugin/versions.json
similarity index 100%
rename from OP/versions.json
rename to OP_plugin/versions.json
diff --git a/op.docs/.obsidian/app.json b/op.docs/.obsidian/app.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/op.docs/.obsidian/app.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/op.docs/.obsidian/appearance.json b/op.docs/.obsidian/appearance.json
new file mode 100644
index 0000000..4be7969
--- /dev/null
+++ b/op.docs/.obsidian/appearance.json
@@ -0,0 +1,3 @@
+{
+ "theme": "obsidian"
+}
\ No newline at end of file
diff --git a/op.docs/.obsidian/community-plugins.json b/op.docs/.obsidian/community-plugins.json
new file mode 100644
index 0000000..a4b4a08
--- /dev/null
+++ b/op.docs/.obsidian/community-plugins.json
@@ -0,0 +1,3 @@
+[
+ "frontmatter-folder-organizer"
+]
\ No newline at end of file
diff --git a/op.docs/.obsidian/core-plugins.json b/op.docs/.obsidian/core-plugins.json
new file mode 100644
index 0000000..639b90d
--- /dev/null
+++ b/op.docs/.obsidian/core-plugins.json
@@ -0,0 +1,33 @@
+{
+ "file-explorer": true,
+ "global-search": true,
+ "switcher": true,
+ "graph": true,
+ "backlink": true,
+ "canvas": true,
+ "outgoing-link": true,
+ "tag-pane": true,
+ "footnotes": false,
+ "properties": true,
+ "page-preview": true,
+ "daily-notes": true,
+ "templates": true,
+ "note-composer": true,
+ "command-palette": true,
+ "slash-command": false,
+ "editor-status": true,
+ "bookmarks": true,
+ "markdown-importer": false,
+ "zk-prefixer": false,
+ "random-note": false,
+ "outline": true,
+ "word-count": true,
+ "slides": false,
+ "audio-recorder": false,
+ "workspaces": false,
+ "file-recovery": true,
+ "publish": false,
+ "sync": true,
+ "bases": true,
+ "webviewer": false
+}
\ No newline at end of file
diff --git a/op.docs/.obsidian/plugins/frontmatter-folder-organizer/manifest.json b/op.docs/.obsidian/plugins/frontmatter-folder-organizer/manifest.json
new file mode 100644
index 0000000..a6f3782
--- /dev/null
+++ b/op.docs/.obsidian/plugins/frontmatter-folder-organizer/manifest.json
@@ -0,0 +1,10 @@
+{
+ "id": "frontmatter-folder-organizer",
+ "name": "Frontmatter Folder Organizer",
+ "version": "1.0.0",
+ "minAppVersion": "1.0.0",
+ "description": "Organize markdown files into folders based on frontmatter hierarchy.",
+ "author": "Obsidian",
+ "authorUrl": "https://obsidian.md",
+ "isDesktopOnly": false
+}
diff --git a/op.docs/.obsidian/plugins/frontmatter-folder-organizer/styles.css b/op.docs/.obsidian/plugins/frontmatter-folder-organizer/styles.css
new file mode 100644
index 0000000..71cc60f
--- /dev/null
+++ b/op.docs/.obsidian/plugins/frontmatter-folder-organizer/styles.css
@@ -0,0 +1,8 @@
+/*
+
+This CSS file will be included with your plugin, and
+available in the app when your plugin is enabled.
+
+If your plugin does not need CSS, delete this file.
+
+*/
diff --git a/op.docs/.obsidian/types.json b/op.docs/.obsidian/types.json
new file mode 100644
index 0000000..be6e75f
--- /dev/null
+++ b/op.docs/.obsidian/types.json
@@ -0,0 +1,8 @@
+{
+ "types": {
+ "aliases": "aliases",
+ "cssclasses": "multitext",
+ "tags": "tags",
+ "shouldInstallPlugins": "checkbox"
+ }
+}
\ No newline at end of file
diff --git a/op.docs/.obsidian/workspace.json b/op.docs/.obsidian/workspace.json
new file mode 100644
index 0000000..8d4e22e
--- /dev/null
+++ b/op.docs/.obsidian/workspace.json
@@ -0,0 +1,194 @@
+{
+ "main": {
+ "id": "eeb36d4e48f0ca25",
+ "type": "split",
+ "children": [
+ {
+ "id": "4fe61a13d63c2732",
+ "type": "tabs",
+ "children": [
+ {
+ "id": "362018eb264ab766",
+ "type": "leaf",
+ "state": {
+ "type": "markdown",
+ "state": {
+ "file": "docs/op.docs.md",
+ "mode": "source",
+ "source": false
+ },
+ "icon": "lucide-file",
+ "title": "op.docs"
+ }
+ }
+ ]
+ }
+ ],
+ "direction": "vertical"
+ },
+ "left": {
+ "id": "b9bf87c6cad06498",
+ "type": "split",
+ "children": [
+ {
+ "id": "6685cdba50e4a0a7",
+ "type": "tabs",
+ "children": [
+ {
+ "id": "6ef95b00a28c418d",
+ "type": "leaf",
+ "state": {
+ "type": "file-explorer",
+ "state": {
+ "sortOrder": "alphabetical",
+ "autoReveal": false
+ },
+ "icon": "lucide-folder-closed",
+ "title": "Files"
+ }
+ },
+ {
+ "id": "c5043b22e38e990c",
+ "type": "leaf",
+ "state": {
+ "type": "search",
+ "state": {
+ "query": "",
+ "matchingCase": false,
+ "explainSearch": false,
+ "collapseAll": false,
+ "extraContext": false,
+ "sortOrder": "alphabetical"
+ },
+ "icon": "lucide-search",
+ "title": "Search"
+ }
+ },
+ {
+ "id": "b159963fcca61ebc",
+ "type": "leaf",
+ "state": {
+ "type": "bookmarks",
+ "state": {},
+ "icon": "lucide-bookmark",
+ "title": "Bookmarks"
+ }
+ }
+ ]
+ }
+ ],
+ "direction": "horizontal",
+ "width": 300
+ },
+ "right": {
+ "id": "c5e47ea76d041492",
+ "type": "split",
+ "children": [
+ {
+ "id": "897865c856161773",
+ "type": "tabs",
+ "children": [
+ {
+ "id": "379d0b312b9d30c9",
+ "type": "leaf",
+ "state": {
+ "type": "backlink",
+ "state": {
+ "file": "doc.zs.md",
+ "collapseAll": false,
+ "extraContext": false,
+ "sortOrder": "alphabetical",
+ "showSearch": false,
+ "searchQuery": "",
+ "backlinkCollapsed": false,
+ "unlinkedCollapsed": true
+ },
+ "icon": "links-coming-in",
+ "title": "Backlinks for doc.zs"
+ }
+ },
+ {
+ "id": "a8f559c72a82e929",
+ "type": "leaf",
+ "state": {
+ "type": "outgoing-link",
+ "state": {
+ "file": "doc.zs.md",
+ "linksCollapsed": false,
+ "unlinkedCollapsed": true
+ },
+ "icon": "links-going-out",
+ "title": "Outgoing links from doc.zs"
+ }
+ },
+ {
+ "id": "e1552c44ca209983",
+ "type": "leaf",
+ "state": {
+ "type": "tag",
+ "state": {
+ "sortOrder": "frequency",
+ "useHierarchy": true,
+ "showSearch": false,
+ "searchQuery": ""
+ },
+ "icon": "lucide-tags",
+ "title": "Tags"
+ }
+ },
+ {
+ "id": "2d2defa7638fb5a3",
+ "type": "leaf",
+ "state": {
+ "type": "all-properties",
+ "state": {
+ "sortOrder": "frequency",
+ "showSearch": false,
+ "searchQuery": ""
+ },
+ "icon": "lucide-archive",
+ "title": "All properties"
+ }
+ },
+ {
+ "id": "82b06aefc5b6df13",
+ "type": "leaf",
+ "state": {
+ "type": "outline",
+ "state": {
+ "file": "doc.zs.md",
+ "followCursor": false,
+ "showSearch": false,
+ "searchQuery": ""
+ },
+ "icon": "lucide-list",
+ "title": "Outline of doc.zs"
+ }
+ }
+ ]
+ }
+ ],
+ "direction": "horizontal",
+ "width": 300,
+ "collapsed": true
+ },
+ "left-ribbon": {
+ "hiddenItems": {
+ "switcher:Open quick switcher": false,
+ "graph:Open graph view": false,
+ "canvas:Create new canvas": false,
+ "daily-notes:Open today's daily note": false,
+ "templates:Insert template": false,
+ "command-palette:Open command palette": false,
+ "bases:Create new base": false
+ }
+ },
+ "active": "362018eb264ab766",
+ "lastOpenFiles": [
+ "docs/doc.zs.md",
+ "docs/op.docs.md",
+ "docs",
+ "os.docs.md",
+ "PLUGIN_SETTINGS_UI_GUIDE.md"
+ ]
+}
\ No newline at end of file
diff --git a/op.docs/PLUGIN_SETTINGS_UI_GUIDE.md b/op.docs/PLUGIN_SETTINGS_UI_GUIDE.md
new file mode 100644
index 0000000..9cbb050
--- /dev/null
+++ b/op.docs/PLUGIN_SETTINGS_UI_GUIDE.md
@@ -0,0 +1,604 @@
+# Plugin Settings UI Guide
+
+This guide explains how to structure and format plugin settings UI in Obsidian, with examples for building multiple plugins with distinct features.
+
+## Table of Contents
+
+1. [Core Concepts](#core-concepts)
+2. [Setting Types](#setting-types)
+3. [UI Organization Patterns](#ui-organization-patterns)
+4. [Multi-Plugin Architecture](#multi-plugin-architecture)
+5. [Plugin Examples](#plugin-examples)
+
+## Core Concepts
+
+### Settings Interface & Settings Tab
+
+Every plugin has:
+
+1. **Settings Interface** - TypeScript interface defining your data structure
+2. **Settings Tab Class** - Extends `PluginSettingTab` and defines the UI
+
+```typescript
+// Define the data structure
+export interface MyPluginSettings {
+ settingName: string;
+ settingValue: boolean;
+}
+
+// Default values
+export const DEFAULT_SETTINGS: MyPluginSettings = {
+ settingName: 'default',
+ settingValue: false,
+};
+
+// UI definition
+export class MySettingTab extends PluginSettingTab {
+ plugin: MyPlugin;
+
+ constructor(app: App, plugin: MyPlugin) {
+ super(app, plugin);
+ this.plugin = plugin;
+ }
+
+ display(): void {
+ const { containerEl } = this;
+ containerEl.empty(); // Clear previous UI
+
+ // Add UI elements here
+ }
+}
+```
+
+### Container Structure
+
+The `containerEl` is where you build your UI. Always start with `containerEl.empty()` to clear old content when the settings tab reopens.
+
+```typescript
+display(): void {
+ const { containerEl } = this;
+ containerEl.empty();
+
+ // Add heading
+ containerEl.createEl('h2', { text: 'Plugin Name' });
+
+ // Add settings
+ new Setting(containerEl)
+ .setName('Setting Name')
+ .setDesc('Description of what this does')
+ .addText((text) => { /* ... */ });
+}
+```
+
+## Setting Types
+
+### Text Input
+
+Single-line text input:
+
+```typescript
+new Setting(containerEl)
+ .setName('Plugin Name')
+ .setDesc('The name of your plugin')
+ .addText((text) =>
+ text
+ .setPlaceholder('Enter name')
+ .setValue(this.plugin.settings.pluginName)
+ .onChange(async (value) => {
+ this.plugin.settings.pluginName = value;
+ await this.plugin.saveSettings();
+ })
+ );
+```
+
+### Text Area
+
+Multi-line text input:
+
+```typescript
+new Setting(containerEl)
+ .setName('Configuration')
+ .setDesc('Multi-line configuration')
+ .addTextArea((text) =>
+ text
+ .setPlaceholder('Enter configuration')
+ .setValue(this.plugin.settings.config)
+ .onChange(async (value) => {
+ this.plugin.settings.config = value;
+ await this.plugin.saveSettings();
+ })
+ );
+```
+
+### Toggle
+
+Boolean switch:
+
+```typescript
+new Setting(containerEl)
+ .setName('Enable Feature')
+ .setDesc('Turn this feature on or off')
+ .addToggle((toggle) =>
+ toggle
+ .setValue(this.plugin.settings.enabled)
+ .onChange(async (value) => {
+ this.plugin.settings.enabled = value;
+ await this.plugin.saveSettings();
+ })
+ );
+```
+
+### Dropdown
+
+Select from predefined options:
+
+```typescript
+new Setting(containerEl)
+ .setName('Theme')
+ .setDesc('Choose a color theme')
+ .addDropdown((dropdown) =>
+ dropdown
+ .addOption('light', 'Light')
+ .addOption('dark', 'Dark')
+ .addOption('auto', 'Auto')
+ .setValue(this.plugin.settings.theme)
+ .onChange(async (value) => {
+ this.plugin.settings.theme = value;
+ await this.plugin.saveSettings();
+ })
+ );
+```
+
+### Slider
+
+Numeric input with range:
+
+```typescript
+new Setting(containerEl)
+ .setName('Search Depth')
+ .setDesc('How deep to search in folders (1-10)')
+ .addSlider((slider) =>
+ slider
+ .setMin(1)
+ .setMax(10)
+ .setStep(1)
+ .setValue(this.plugin.settings.searchDepth)
+ .onChange(async (value) => {
+ this.plugin.settings.searchDepth = value;
+ await this.plugin.saveSettings();
+ })
+ );
+```
+
+### Button
+
+Trigger an action:
+
+```typescript
+new Setting(containerEl)
+ .setName('Process Files')
+ .setDesc('Start processing all files')
+ .addButton((button) =>
+ button
+ .setButtonText('Process Now')
+ .onClick(async () => {
+ await this.plugin.processAllFiles();
+ new Notice('Processing complete!');
+ })
+ );
+```
+
+### Color Picker
+
+Select a color:
+
+```typescript
+new Setting(containerEl)
+ .setName('Tag Color')
+ .setDesc('Color for tags')
+ .addColorPicker((color) =>
+ color
+ .setValue(this.plugin.settings.tagColor)
+ .onChange(async (value) => {
+ this.plugin.settings.tagColor = value;
+ await this.plugin.saveSettings();
+ })
+ );
+```
+
+## UI Organization Patterns
+
+### Section Headers
+
+Organize settings into logical sections:
+
+```typescript
+display(): void {
+ const { containerEl } = this;
+ containerEl.empty();
+
+ // Main heading
+ containerEl.createEl('h2', { text: 'Plugin Settings' });
+
+ // Section 1
+ containerEl.createEl('h3', { text: 'Basic Configuration' });
+ new Setting(containerEl).setName('Setting 1')...
+ new Setting(containerEl).setName('Setting 2')...
+
+ // Section 2
+ containerEl.createEl('h3', { text: 'Advanced Options' });
+ new Setting(containerEl).setName('Setting 3')...
+ new Setting(containerEl).setName('Setting 4')...
+}
+```
+
+### Conditional Display
+
+Show/hide settings based on other settings:
+
+```typescript
+display(): void {
+ const { containerEl } = this;
+ containerEl.empty();
+
+ new Setting(containerEl)
+ .setName('Enable Advanced')
+ .addToggle((toggle) =>
+ toggle
+ .setValue(this.plugin.settings.advancedMode)
+ .onChange(async (value) => {
+ this.plugin.settings.advancedMode = value;
+ await this.plugin.saveSettings();
+ this.display(); // Redraw to show/hide advanced settings
+ })
+ );
+
+ if (this.plugin.settings.advancedMode) {
+ new Setting(containerEl)
+ .setName('Advanced Option')
+ .setDesc('This only shows when advanced mode is on')
+ .addText((text) => { /* ... */ });
+ }
+}
+```
+
+### Grouped Controls
+
+Multiple controls in one setting:
+
+```typescript
+new Setting(containerEl)
+ .setName('Color')
+ .setDesc('Choose color and opacity')
+ .addColorPicker((color) =>
+ color
+ .setValue(this.plugin.settings.color)
+ .onChange(async (value) => {
+ this.plugin.settings.color = value;
+ await this.plugin.saveSettings();
+ })
+ )
+ .addSlider((slider) =>
+ slider
+ .setMin(0)
+ .setMax(100)
+ .setValue(this.plugin.settings.opacity)
+ .onChange(async (value) => {
+ this.plugin.settings.opacity = value;
+ await this.plugin.saveSettings();
+ })
+ );
+```
+
+## Multi-Plugin Architecture
+
+When building multiple plugins within one codebase, consider this structure:
+
+### Folder Structure
+
+```
+src/
+ main.ts (Plugin entry point)
+ settings.ts (Shared settings)
+ plugins/
+ folder/
+ folderPlugin.ts
+ folderSettings.ts
+ markdown/
+ markdownPlugin.ts
+ markdownSettings.ts
+ kanban/
+ kanbanPlugin.ts
+ kanbanSettings.ts
+```
+
+### Unified Settings Interface
+
+```typescript
+// settings.ts
+export interface PluginConfig {
+ // Folder Plugin settings
+ folderPlugin: {
+ enabled: boolean;
+ organizationHierarchy: string;
+ };
+
+ // Markdown Plugin settings
+ markdownPlugin: {
+ enabled: boolean;
+ autoCreatePath: string;
+ template: string;
+ };
+
+ // Kanban Plugin settings
+ kanbanPlugin: {
+ enabled: boolean;
+ boardTemplate: string;
+ defaultLayout: string;
+ };
+}
+
+export const DEFAULT_SETTINGS: PluginConfig = {
+ folderPlugin: {
+ enabled: true,
+ organizationHierarchy: 'Category, Status',
+ },
+ markdownPlugin: {
+ enabled: true,
+ autoCreatePath: 'Notes',
+ template: 'default',
+ },
+ kanbanPlugin: {
+ enabled: false,
+ boardTemplate: 'default',
+ defaultLayout: 'columns',
+ },
+};
+```
+
+### Unified Settings Tab
+
+```typescript
+// settings.ts
+export class UnifiedSettingTab extends PluginSettingTab {
+ plugin: MultiPluginBase;
+
+ display(): void {
+ const { containerEl } = this;
+ containerEl.empty();
+
+ containerEl.createEl('h2', { text: 'Plugin Suite Settings' });
+
+ // Folder Plugin Section
+ this.displayFolderPluginSettings(containerEl);
+
+ // Markdown Plugin Section
+ this.displayMarkdownPluginSettings(containerEl);
+
+ // Kanban Plugin Section
+ this.displayKanbanPluginSettings(containerEl);
+ }
+
+ displayFolderPluginSettings(containerEl: HTMLElement): void {
+ containerEl.createEl('h3', { text: 'Folder Organization Plugin' });
+
+ new Setting(containerEl)
+ .setName('Enable Folder Plugin')
+ .addToggle((toggle) =>
+ toggle
+ .setValue(this.plugin.settings.folderPlugin.enabled)
+ .onChange(async (value) => {
+ this.plugin.settings.folderPlugin.enabled = value;
+ await this.plugin.saveSettings();
+ })
+ );
+
+ if (this.plugin.settings.folderPlugin.enabled) {
+ new Setting(containerEl)
+ .setName('Organization Hierarchy')
+ .setDesc('Comma-separated frontmatter fields')
+ .addTextArea((text) =>
+ text
+ .setValue(this.plugin.settings.folderPlugin.organizationHierarchy)
+ .onChange(async (value) => {
+ this.plugin.settings.folderPlugin.organizationHierarchy = value;
+ await this.plugin.saveSettings();
+ })
+ );
+ }
+ }
+
+ displayMarkdownPluginSettings(containerEl: HTMLElement): void {
+ containerEl.createEl('h3', { text: 'Markdown Auto-Create Plugin' });
+
+ new Setting(containerEl)
+ .setName('Enable Markdown Plugin')
+ .addToggle((toggle) =>
+ toggle
+ .setValue(this.plugin.settings.markdownPlugin.enabled)
+ .onChange(async (value) => {
+ this.plugin.settings.markdownPlugin.enabled = value;
+ await this.plugin.saveSettings();
+ })
+ );
+
+ if (this.plugin.settings.markdownPlugin.enabled) {
+ new Setting(containerEl)
+ .setName('Auto-Create Path')
+ .setDesc('Default folder for auto-created files')
+ .addText((text) =>
+ text
+ .setPlaceholder('Notes')
+ .setValue(this.plugin.settings.markdownPlugin.autoCreatePath)
+ .onChange(async (value) => {
+ this.plugin.settings.markdownPlugin.autoCreatePath = value;
+ await this.plugin.saveSettings();
+ })
+ );
+
+ new Setting(containerEl)
+ .setName('Template')
+ .setDesc('Template to use for new files')
+ .addDropdown((dropdown) =>
+ dropdown
+ .addOption('default', 'Default')
+ .addOption('minimal', 'Minimal')
+ .addOption('detailed', 'Detailed')
+ .setValue(this.plugin.settings.markdownPlugin.template)
+ .onChange(async (value) => {
+ this.plugin.settings.markdownPlugin.template = value;
+ await this.plugin.saveSettings();
+ })
+ );
+ }
+ }
+
+ displayKanbanPluginSettings(containerEl: HTMLElement): void {
+ containerEl.createEl('h3', { text: 'Kanban Board Plugin' });
+
+ new Setting(containerEl)
+ .setName('Enable Kanban Plugin')
+ .addToggle((toggle) =>
+ toggle
+ .setValue(this.plugin.settings.kanbanPlugin.enabled)
+ .onChange(async (value) => {
+ this.plugin.settings.kanbanPlugin.enabled = value;
+ await this.plugin.saveSettings();
+ })
+ );
+
+ if (this.plugin.settings.kanbanPlugin.enabled) {
+ new Setting(containerEl)
+ .setName('Board Template')
+ .setDesc('Default board template')
+ .addDropdown((dropdown) =>
+ dropdown
+ .addOption('default', 'Default')
+ .addOption('agile', 'Agile')
+ .addOption('gtd', 'Getting Things Done')
+ .setValue(this.plugin.settings.kanbanPlugin.boardTemplate)
+ .onChange(async (value) => {
+ this.plugin.settings.kanbanPlugin.boardTemplate = value;
+ await this.plugin.saveSettings();
+ })
+ );
+
+ new Setting(containerEl)
+ .setName('Default Layout')
+ .setDesc('How boards are displayed by default')
+ .addDropdown((dropdown) =>
+ dropdown
+ .addOption('columns', 'Columns')
+ .addOption('rows', 'Rows')
+ .addOption('grid', 'Grid')
+ .setValue(this.plugin.settings.kanbanPlugin.defaultLayout)
+ .onChange(async (value) => {
+ this.plugin.settings.kanbanPlugin.defaultLayout = value;
+ await this.plugin.saveSettings();
+ })
+ );
+ }
+ }
+}
+```
+
+## Plugin Examples
+
+### Example 1: Folder Organization Plugin
+
+**Settings Interface:**
+```typescript
+export interface FolderOrganizerSettings {
+ enabled: boolean;
+ organizationHierarchy: string;
+ createMissingFolders: boolean;
+ overwriteExisting: boolean;
+ excludeFolders: string[];
+}
+```
+
+**Key UI Elements:**
+- Toggle to enable/disable
+- Text area for hierarchy configuration
+- Toggle for auto-create folders
+- Toggle for overwrite confirmation
+- Multi-select or list for excluded folders
+- Action button to organize files
+
+### Example 2: Markdown Auto-Create Plugin
+
+**Settings Interface:**
+```typescript
+export interface MarkdownAutoCreateSettings {
+ enabled: boolean;
+ defaultPath: string;
+ template: 'default' | 'minimal' | 'detailed';
+ autoOpenNewFile: boolean;
+ defaultFrontmatter: string;
+ hotkey: string;
+}
+```
+
+**Key UI Elements:**
+- Toggle to enable/disable
+- Text input for default path
+- Dropdown for template selection
+- Toggle for auto-open
+- Text area for frontmatter template
+- Display current hotkey (or hotkey picker if available)
+
+### Example 3: Kanban Board Plugin
+
+**Settings Interface:**
+```typescript
+export interface KanbanBoardSettings {
+ enabled: boolean;
+ boardTemplate: 'default' | 'agile' | 'gtd';
+ defaultLayout: 'columns' | 'rows' | 'grid';
+ cardWidth: number;
+ showDates: boolean;
+ showTags: boolean;
+ defaultColumns: string[];
+ darkMode: boolean;
+}
+```
+
+**Key UI Elements:**
+- Toggle to enable/disable
+- Dropdown for board templates
+- Dropdown for default layout
+- Slider for card width
+- Toggles for feature flags
+- Text area for default columns (comma-separated)
+- Color picker for theme customization
+
+## Best Practices
+
+1. **Always save settings** - Call `await this.plugin.saveSettings()` after any change
+2. **Use descriptive names** - Users should understand what each setting does
+3. **Group related settings** - Use section headers (h3) to organize
+4. **Provide defaults** - Always have sensible defaults in `DEFAULT_SETTINGS`
+5. **Validate input** - Check user input before saving (trim, validate paths, etc.)
+6. **Use conditionals wisely** - Show/hide advanced options based on basic settings
+7. **Provide feedback** - Use `new Notice()` to confirm actions
+8. **Redraw on enable/disable** - Call `this.display()` to update UI when features toggle
+9. **Document complex settings** - Use `.setDesc()` liberally
+10. **Test multi-step workflows** - Ensure settings changes work with all plugins enabled
+
+## Testing Your Settings UI
+
+```typescript
+// In your test file
+const plugin = new MyPlugin(app, manifest);
+plugin.loadSettings();
+plugin.registerSettingTab(new UnifiedSettingTab(app, plugin));
+
+// Settings should load without errors
+// Check that all toggles/inputs work
+// Verify settings persist after reload
+// Test conditional displays
+```
+
+---
+
+This guide provides a foundation for building clean, organized, and user-friendly plugin settings. Adapt these patterns to your specific needs!
diff --git a/op.docs/docs/doc.zs.md b/op.docs/docs/doc.zs.md
new file mode 100644
index 0000000..649b603
--- /dev/null
+++ b/op.docs/docs/doc.zs.md
@@ -0,0 +1,5 @@
+---
+category: docs
+path: ZeroSpace\zs.docs
+shouldInstallPlugins: true
+---
diff --git a/op.docs/docs/op.docs.md b/op.docs/docs/op.docs.md
new file mode 100644
index 0000000..2a41495
--- /dev/null
+++ b/op.docs/docs/op.docs.md
@@ -0,0 +1,4 @@
+---
+category: docs
+path: Obsidian-Plugins\op.docs
+---
diff --git a/os.tasks/.obsidian/app.json b/os.tasks/.obsidian/app.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/os.tasks/.obsidian/app.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/os.tasks/.obsidian/appearance.json b/os.tasks/.obsidian/appearance.json
new file mode 100644
index 0000000..4be7969
--- /dev/null
+++ b/os.tasks/.obsidian/appearance.json
@@ -0,0 +1,3 @@
+{
+ "theme": "obsidian"
+}
\ No newline at end of file
diff --git a/os.tasks/.obsidian/core-plugins.json b/os.tasks/.obsidian/core-plugins.json
new file mode 100644
index 0000000..639b90d
--- /dev/null
+++ b/os.tasks/.obsidian/core-plugins.json
@@ -0,0 +1,33 @@
+{
+ "file-explorer": true,
+ "global-search": true,
+ "switcher": true,
+ "graph": true,
+ "backlink": true,
+ "canvas": true,
+ "outgoing-link": true,
+ "tag-pane": true,
+ "footnotes": false,
+ "properties": true,
+ "page-preview": true,
+ "daily-notes": true,
+ "templates": true,
+ "note-composer": true,
+ "command-palette": true,
+ "slash-command": false,
+ "editor-status": true,
+ "bookmarks": true,
+ "markdown-importer": false,
+ "zk-prefixer": false,
+ "random-note": false,
+ "outline": true,
+ "word-count": true,
+ "slides": false,
+ "audio-recorder": false,
+ "workspaces": false,
+ "file-recovery": true,
+ "publish": false,
+ "sync": true,
+ "bases": true,
+ "webviewer": false
+}
\ No newline at end of file
diff --git a/os.tasks/.obsidian/workspace.json b/os.tasks/.obsidian/workspace.json
new file mode 100644
index 0000000..58509fe
--- /dev/null
+++ b/os.tasks/.obsidian/workspace.json
@@ -0,0 +1,190 @@
+{
+ "main": {
+ "id": "5aefe3eaa01298aa",
+ "type": "split",
+ "children": [
+ {
+ "id": "8e01d97cdc20d278",
+ "type": "tabs",
+ "children": [
+ {
+ "id": "46c2d403c5136d23",
+ "type": "leaf",
+ "state": {
+ "type": "markdown",
+ "state": {
+ "file": "Generate Build Script Project.md",
+ "mode": "source",
+ "source": false
+ },
+ "icon": "lucide-file",
+ "title": "Generate Build Script Project"
+ }
+ }
+ ]
+ }
+ ],
+ "direction": "vertical"
+ },
+ "left": {
+ "id": "97c9b935bff66d1d",
+ "type": "split",
+ "children": [
+ {
+ "id": "1ecf357cfe02325f",
+ "type": "tabs",
+ "children": [
+ {
+ "id": "19ac1b441316dcfc",
+ "type": "leaf",
+ "state": {
+ "type": "file-explorer",
+ "state": {
+ "sortOrder": "alphabetical",
+ "autoReveal": false
+ },
+ "icon": "lucide-folder-closed",
+ "title": "Files"
+ }
+ },
+ {
+ "id": "99feed5ad5a90deb",
+ "type": "leaf",
+ "state": {
+ "type": "search",
+ "state": {
+ "query": "",
+ "matchingCase": false,
+ "explainSearch": false,
+ "collapseAll": false,
+ "extraContext": false,
+ "sortOrder": "alphabetical"
+ },
+ "icon": "lucide-search",
+ "title": "Search"
+ }
+ },
+ {
+ "id": "de452041497e4cd8",
+ "type": "leaf",
+ "state": {
+ "type": "bookmarks",
+ "state": {},
+ "icon": "lucide-bookmark",
+ "title": "Bookmarks"
+ }
+ }
+ ]
+ }
+ ],
+ "direction": "horizontal",
+ "width": 300
+ },
+ "right": {
+ "id": "a984cdfdcdf28365",
+ "type": "split",
+ "children": [
+ {
+ "id": "a63d615d35bc9daf",
+ "type": "tabs",
+ "children": [
+ {
+ "id": "4b86dc95dc16032b",
+ "type": "leaf",
+ "state": {
+ "type": "backlink",
+ "state": {
+ "file": "Generate Build Script Project.md",
+ "collapseAll": false,
+ "extraContext": false,
+ "sortOrder": "alphabetical",
+ "showSearch": false,
+ "searchQuery": "",
+ "backlinkCollapsed": false,
+ "unlinkedCollapsed": true
+ },
+ "icon": "links-coming-in",
+ "title": "Backlinks for Generate Build Script Project"
+ }
+ },
+ {
+ "id": "4c3dbdb8f44e7955",
+ "type": "leaf",
+ "state": {
+ "type": "outgoing-link",
+ "state": {
+ "file": "Generate Build Script Project.md",
+ "linksCollapsed": false,
+ "unlinkedCollapsed": true
+ },
+ "icon": "links-going-out",
+ "title": "Outgoing links from Generate Build Script Project"
+ }
+ },
+ {
+ "id": "da94bd2266074d5a",
+ "type": "leaf",
+ "state": {
+ "type": "tag",
+ "state": {
+ "sortOrder": "frequency",
+ "useHierarchy": true,
+ "showSearch": false,
+ "searchQuery": ""
+ },
+ "icon": "lucide-tags",
+ "title": "Tags"
+ }
+ },
+ {
+ "id": "46b74855b4b336d7",
+ "type": "leaf",
+ "state": {
+ "type": "all-properties",
+ "state": {
+ "sortOrder": "frequency",
+ "showSearch": false,
+ "searchQuery": ""
+ },
+ "icon": "lucide-archive",
+ "title": "All properties"
+ }
+ },
+ {
+ "id": "ae88324a14179265",
+ "type": "leaf",
+ "state": {
+ "type": "outline",
+ "state": {
+ "file": "Generate Build Script Project.md",
+ "followCursor": false,
+ "showSearch": false,
+ "searchQuery": ""
+ },
+ "icon": "lucide-list",
+ "title": "Outline of Generate Build Script Project"
+ }
+ }
+ ]
+ }
+ ],
+ "direction": "horizontal",
+ "width": 300,
+ "collapsed": true
+ },
+ "left-ribbon": {
+ "hiddenItems": {
+ "switcher:Open quick switcher": false,
+ "graph:Open graph view": false,
+ "canvas:Create new canvas": false,
+ "daily-notes:Open today's daily note": false,
+ "templates:Insert template": false,
+ "command-palette:Open command palette": false,
+ "bases:Create new base": false
+ }
+ },
+ "active": "46c2d403c5136d23",
+ "lastOpenFiles": [
+ "Generate Build Script Project.md"
+ ]
+}
\ No newline at end of file
diff --git a/os.tasks/Generate Build Script Project.md b/os.tasks/Generate Build Script Project.md
new file mode 100644
index 0000000..f1d5f40
--- /dev/null
+++ b/os.tasks/Generate Build Script Project.md
@@ -0,0 +1,17 @@
+---
+category: AI Task
+---
+
+
+In OP/Build project, create a program that scans the markdown files in op.docs.
+
+It will grab files with the frontmatter of catergory: docs.
+
+For this doc files, get the frontmatter path. This will tell you the location of the docs relative to the current user root. Such as user/Obsidian-Plugins/op.docs.
+
+You will want to build the Obsidian plugin located in OP_plugin.
+
+Once it's built, you copy the contents of OP_plugin and move it to the relative plugin folders, such as ZeroSpace\zs.docs\.obsidian\plugins\
+
+Delete the previous plugin obviously, if the location is already occupied.
+