feat: initial release of Claude Context plugin
Obsidian plugin to copy vault context files to clipboard for AI assistants. Features: - Copy vault context files to clipboard with one hotkey - Context generator with configurable conventions - Settings for folder, separator, preview, exclusions - Auto-detect vault folder structure - Include active note option - Ribbon icon for quick access - Full English UI and documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
commit
2d08546847
20 changed files with 2280 additions and 0 deletions
10
.editorconfig
Normal file
10
.editorconfig
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
28
.github/workflows/lint.yml
vendored
Normal file
28
.github/workflows/lint.yml
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
name: Node.js build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["**"]
|
||||
pull_request:
|
||||
branches: ["**"]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.x, 22.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "npm"
|
||||
- run: npm ci
|
||||
- run: npm run build --if-present
|
||||
- run: npm run lint
|
||||
|
||||
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# vscode
|
||||
.vscode
|
||||
|
||||
# Intellij
|
||||
*.iml
|
||||
.idea
|
||||
|
||||
# npm
|
||||
node_modules
|
||||
|
||||
# Don't include the compiled main.js file in the repo.
|
||||
# They should be uploaded to GitHub releases instead.
|
||||
main.js
|
||||
|
||||
# Exclude sourcemaps
|
||||
*.map
|
||||
|
||||
# obsidian
|
||||
data.json
|
||||
|
||||
# Exclude macOS Finder (System Explorer) View States
|
||||
.DS_Store
|
||||
1
.npmrc
Normal file
1
.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
tag-version-prefix=""
|
||||
251
AGENTS.md
Normal file
251
AGENTS.md
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
# Obsidian community plugin
|
||||
|
||||
## Project overview
|
||||
|
||||
- Target: Obsidian Community Plugin (TypeScript → bundled JavaScript).
|
||||
- Entry point: `main.ts` compiled to `main.js` and loaded by Obsidian.
|
||||
- Required release artifacts: `main.js`, `manifest.json`, and optional `styles.css`.
|
||||
|
||||
## Environment & tooling
|
||||
|
||||
- Node.js: use current LTS (Node 18+ recommended).
|
||||
- **Package manager: npm** (required for this sample - `package.json` defines npm scripts and dependencies).
|
||||
- **Bundler: esbuild** (required for this sample - `esbuild.config.mjs` and build scripts depend on it). Alternative bundlers like Rollup or webpack are acceptable for other projects if they bundle all external dependencies into `main.js`.
|
||||
- Types: `obsidian` type definitions.
|
||||
|
||||
**Note**: This sample project has specific technical dependencies on npm and esbuild. If you're creating a plugin from scratch, you can choose different tools, but you'll need to replace the build configuration accordingly.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### Dev (watch)
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Production build
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Linting
|
||||
|
||||
- To use eslint install eslint from terminal: `npm install -g eslint`
|
||||
- To use eslint to analyze this project use this command: `eslint main.ts`
|
||||
- eslint will then create a report with suggestions for code improvement by file and line number.
|
||||
- If your source code is in a folder, such as `src`, you can use eslint with this command to analyze all files in that folder: `eslint ./src/`
|
||||
|
||||
## File & folder conventions
|
||||
|
||||
- **Organize code into multiple files**: Split functionality across separate modules rather than putting everything in `main.ts`.
|
||||
- Source lives in `src/`. Keep `main.ts` small and focused on plugin lifecycle (loading, unloading, registering commands).
|
||||
- **Example file structure**:
|
||||
```
|
||||
src/
|
||||
main.ts # Plugin entry point, lifecycle management
|
||||
settings.ts # Settings interface and defaults
|
||||
commands/ # Command implementations
|
||||
command1.ts
|
||||
command2.ts
|
||||
ui/ # UI components, modals, views
|
||||
modal.ts
|
||||
view.ts
|
||||
utils/ # Utility functions, helpers
|
||||
helpers.ts
|
||||
constants.ts
|
||||
types.ts # TypeScript interfaces and types
|
||||
```
|
||||
- **Do not commit build artifacts**: Never commit `node_modules/`, `main.js`, or other generated files to version control.
|
||||
- Keep the plugin small. Avoid large dependencies. Prefer browser-compatible packages.
|
||||
- Generated output should be placed at the plugin root or `dist/` depending on your build setup. Release artifacts must end up at the top level of the plugin folder in the vault (`main.js`, `manifest.json`, `styles.css`).
|
||||
|
||||
## Manifest rules (`manifest.json`)
|
||||
|
||||
- Must include (non-exhaustive):
|
||||
- `id` (plugin ID; for local dev it should match the folder name)
|
||||
- `name`
|
||||
- `version` (Semantic Versioning `x.y.z`)
|
||||
- `minAppVersion`
|
||||
- `description`
|
||||
- `isDesktopOnly` (boolean)
|
||||
- Optional: `author`, `authorUrl`, `fundingUrl` (string or map)
|
||||
- Never change `id` after release. Treat it as stable API.
|
||||
- Keep `minAppVersion` accurate when using newer APIs.
|
||||
- Canonical requirements are coded here: https://github.com/obsidianmd/obsidian-releases/blob/master/.github/workflows/validate-plugin-entry.yml
|
||||
|
||||
## Testing
|
||||
|
||||
- Manual install for testing: copy `main.js`, `manifest.json`, `styles.css` (if any) to:
|
||||
```
|
||||
<Vault>/.obsidian/plugins/<plugin-id>/
|
||||
```
|
||||
- Reload Obsidian and enable the plugin in **Settings → Community plugins**.
|
||||
|
||||
## Commands & settings
|
||||
|
||||
- Any user-facing commands should be added via `this.addCommand(...)`.
|
||||
- If the plugin has configuration, provide a settings tab and sensible defaults.
|
||||
- Persist settings using `this.loadData()` / `this.saveData()`.
|
||||
- Use stable command IDs; avoid renaming once released.
|
||||
|
||||
## Versioning & releases
|
||||
|
||||
- Bump `version` in `manifest.json` (SemVer) and update `versions.json` to map plugin version → minimum app version.
|
||||
- Create a GitHub release whose tag exactly matches `manifest.json`'s `version`. Do not use a leading `v`.
|
||||
- Attach `manifest.json`, `main.js`, and `styles.css` (if present) to the release as individual assets.
|
||||
- After the initial release, follow the process to add/update your plugin in the community catalog as required.
|
||||
|
||||
## Security, privacy, and compliance
|
||||
|
||||
Follow Obsidian's **Developer Policies** and **Plugin Guidelines**. In particular:
|
||||
|
||||
- Default to local/offline operation. Only make network requests when essential to the feature.
|
||||
- No hidden telemetry. If you collect optional analytics or call third-party services, require explicit opt-in and document clearly in `README.md` and in settings.
|
||||
- Never execute remote code, fetch and eval scripts, or auto-update plugin code outside of normal releases.
|
||||
- Minimize scope: read/write only what's necessary inside the vault. Do not access files outside the vault.
|
||||
- Clearly disclose any external services used, data sent, and risks.
|
||||
- Respect user privacy. Do not collect vault contents, filenames, or personal information unless absolutely necessary and explicitly consented.
|
||||
- Avoid deceptive patterns, ads, or spammy notifications.
|
||||
- Register and clean up all DOM, app, and interval listeners using the provided `register*` helpers so the plugin unloads safely.
|
||||
|
||||
## UX & copy guidelines (for UI text, commands, settings)
|
||||
|
||||
- Prefer sentence case for headings, buttons, and titles.
|
||||
- Use clear, action-oriented imperatives in step-by-step copy.
|
||||
- Use **bold** to indicate literal UI labels. Prefer "select" for interactions.
|
||||
- Use arrow notation for navigation: **Settings → Community plugins**.
|
||||
- Keep in-app strings short, consistent, and free of jargon.
|
||||
|
||||
## Performance
|
||||
|
||||
- Keep startup light. Defer heavy work until needed.
|
||||
- Avoid long-running tasks during `onload`; use lazy initialization.
|
||||
- Batch disk access and avoid excessive vault scans.
|
||||
- Debounce/throttle expensive operations in response to file system events.
|
||||
|
||||
## Coding conventions
|
||||
|
||||
- TypeScript with `"strict": true` preferred.
|
||||
- **Keep `main.ts` minimal**: Focus only on plugin lifecycle (onload, onunload, addCommand calls). Delegate all feature logic to separate modules.
|
||||
- **Split large files**: If any file exceeds ~200-300 lines, consider breaking it into smaller, focused modules.
|
||||
- **Use clear module boundaries**: Each file should have a single, well-defined responsibility.
|
||||
- Bundle everything into `main.js` (no unbundled runtime deps).
|
||||
- Avoid Node/Electron APIs if you want mobile compatibility; set `isDesktopOnly` accordingly.
|
||||
- Prefer `async/await` over promise chains; handle errors gracefully.
|
||||
|
||||
## Mobile
|
||||
|
||||
- Where feasible, test on iOS and Android.
|
||||
- Don't assume desktop-only behavior unless `isDesktopOnly` is `true`.
|
||||
- Avoid large in-memory structures; be mindful of memory and storage constraints.
|
||||
|
||||
## Agent do/don't
|
||||
|
||||
**Do**
|
||||
- Add commands with stable IDs (don't rename once released).
|
||||
- Provide defaults and validation in settings.
|
||||
- Write idempotent code paths so reload/unload doesn't leak listeners or intervals.
|
||||
- Use `this.register*` helpers for everything that needs cleanup.
|
||||
|
||||
**Don't**
|
||||
- Introduce network calls without an obvious user-facing reason and documentation.
|
||||
- Ship features that require cloud services without clear disclosure and explicit opt-in.
|
||||
- Store or transmit vault contents unless essential and consented.
|
||||
|
||||
## Common tasks
|
||||
|
||||
### Organize code across multiple files
|
||||
|
||||
**main.ts** (minimal, lifecycle only):
|
||||
```ts
|
||||
import { Plugin } from "obsidian";
|
||||
import { MySettings, DEFAULT_SETTINGS } from "./settings";
|
||||
import { registerCommands } from "./commands";
|
||||
|
||||
export default class MyPlugin extends Plugin {
|
||||
settings: MySettings;
|
||||
|
||||
async onload() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
registerCommands(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**settings.ts**:
|
||||
```ts
|
||||
export interface MySettings {
|
||||
enabled: boolean;
|
||||
apiKey: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: MySettings = {
|
||||
enabled: true,
|
||||
apiKey: "",
|
||||
};
|
||||
```
|
||||
|
||||
**commands/index.ts**:
|
||||
```ts
|
||||
import { Plugin } from "obsidian";
|
||||
import { doSomething } from "./my-command";
|
||||
|
||||
export function registerCommands(plugin: Plugin) {
|
||||
plugin.addCommand({
|
||||
id: "do-something",
|
||||
name: "Do something",
|
||||
callback: () => doSomething(plugin),
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Add a command
|
||||
|
||||
```ts
|
||||
this.addCommand({
|
||||
id: "your-command-id",
|
||||
name: "Do the thing",
|
||||
callback: () => this.doTheThing(),
|
||||
});
|
||||
```
|
||||
|
||||
### Persist settings
|
||||
|
||||
```ts
|
||||
interface MySettings { enabled: boolean }
|
||||
const DEFAULT_SETTINGS: MySettings = { enabled: true };
|
||||
|
||||
async onload() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
```
|
||||
|
||||
### Register listeners safely
|
||||
|
||||
```ts
|
||||
this.registerEvent(this.app.workspace.on("file-open", f => { /* ... */ }));
|
||||
this.registerDomEvent(window, "resize", () => { /* ... */ });
|
||||
this.registerInterval(window.setInterval(() => { /* ... */ }, 1000));
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Plugin doesn't load after build: ensure `main.js` and `manifest.json` are at the top level of the plugin folder under `<Vault>/.obsidian/plugins/<plugin-id>/`.
|
||||
- Build issues: if `main.js` is missing, run `npm run build` or `npm run dev` to compile your TypeScript source code.
|
||||
- Commands not appearing: verify `addCommand` runs after `onload` and IDs are unique.
|
||||
- Settings not persisting: ensure `loadData`/`saveData` are awaited and you re-render the UI after changes.
|
||||
- Mobile-only issues: confirm you're not using desktop-only APIs; check `isDesktopOnly` and adjust.
|
||||
|
||||
## References
|
||||
|
||||
- Obsidian sample plugin: https://github.com/obsidianmd/obsidian-sample-plugin
|
||||
- API documentation: https://docs.obsidian.md
|
||||
- Developer policies: https://docs.obsidian.md/Developer+policies
|
||||
- Plugin guidelines: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines
|
||||
- Style guide: https://help.obsidian.md/style-guide
|
||||
5
LICENSE
Normal file
5
LICENSE
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
Copyright (C) 2020-2025 by Dynalist Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
285
README.md
Normal file
285
README.md
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
<div align="center">
|
||||
|
||||
# Claude Context
|
||||
|
||||
[](https://obsidian.md)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](LICENSE)
|
||||
|
||||
**Copy your vault context to clipboard with one hotkey.**
|
||||
|
||||
Give AI assistants (Claude, ChatGPT, etc.) instant understanding of your vault's conventions, structure, and workflows.
|
||||
|
||||
[Features](#features) • [Installation](#installation) • [Usage](#usage) • [Configuration](#configuration) • [Generator](#context-generator)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## The Problem
|
||||
|
||||
When working with AI assistants on your Obsidian vault, you constantly need to explain:
|
||||
- How you name files
|
||||
- Where things go
|
||||
- What link style you use
|
||||
- Your tag conventions
|
||||
- Your workflows
|
||||
|
||||
This is repetitive and error-prone. The AI forgets, you forget to mention something, and inconsistencies creep in.
|
||||
|
||||
---
|
||||
|
||||
## The Solution
|
||||
|
||||
**Claude Context** stores your vault's conventions in a dedicated folder (`_claude/`) and copies everything to your clipboard with a single hotkey. Paste it into any AI chat, and the assistant immediately understands how to work with your vault.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
## Features
|
||||
|
||||
</div>
|
||||
|
||||
### 📋 One-Click Context Copy
|
||||
|
||||
Press a hotkey or click the ribbon icon to copy all context files to your clipboard. The output is formatted with clear separators between files.
|
||||
|
||||
### ⚙️ Context Generator
|
||||
|
||||
Don't want to write context files manually? The built-in generator walks you through your preferences:
|
||||
|
||||
- **Language** - English or German
|
||||
- **File naming** - kebab-case, snake_case, camelCase, or free
|
||||
- **Link style** - Wikilinks or Markdown
|
||||
- **Tag style** - Hierarchical, flat, or none
|
||||
- **Frontmatter fields** - Customize required fields
|
||||
- **Custom rules** - Add your own conventions
|
||||
- **Folder purposes** - Auto-detects your folders, you describe their purpose
|
||||
- **Templates** - Define your note templates
|
||||
|
||||
Click "Generate" and your context files are created instantly.
|
||||
|
||||
### 📁 Auto-Detect Vault Structure
|
||||
|
||||
The generator automatically scans your vault's folder structure. Just fill in the purpose for each folder, and `structure.md` is generated with your actual directories.
|
||||
|
||||
### 🔧 Flexible Settings
|
||||
|
||||
- **Context folder** - Change from `_claude/` to any name
|
||||
- **Separator** - Customize the separator between files
|
||||
- **Include filenames** - Toggle `# === filename.md ===` headers
|
||||
- **Show preview** - Preview before copying
|
||||
- **Include active note** - Append the currently open note
|
||||
- **Excluded files** - Skip specific files from being copied
|
||||
|
||||
### 📝 Include Active Note
|
||||
|
||||
Working on a specific note and want context for it? Use "Copy context with current note" or enable "Include active note" in settings. The current note is appended to your context with an `ACTIVE:` prefix.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
## Installation
|
||||
|
||||
</div>
|
||||
|
||||
### From Source
|
||||
|
||||
1. Clone this repository into your vault's plugins folder:
|
||||
```bash
|
||||
cd /path/to/vault/.obsidian/plugins
|
||||
git clone https://github.com/yourusername/obsidian-claude-context.git claude-context
|
||||
```
|
||||
|
||||
2. Install dependencies and build:
|
||||
```bash
|
||||
cd claude-context
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
3. Enable the plugin in Obsidian:
|
||||
- Settings → Community Plugins → Enable "Claude Context"
|
||||
|
||||
### Development Setup
|
||||
|
||||
For development with hot reload:
|
||||
|
||||
```bash
|
||||
cd /path/to/vault/.obsidian/plugins/claude-context
|
||||
npm run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
## Usage
|
||||
|
||||
</div>
|
||||
|
||||
### Quick Start
|
||||
|
||||
1. **Generate context files**: `Ctrl+P` → "Claude Context: Generate context files"
|
||||
2. **Configure your preferences** in the generator modal
|
||||
3. **Click "Generate"** to create your context files
|
||||
4. **Copy context**: `Ctrl+P` → "Claude Context: Copy context to clipboard"
|
||||
5. **Paste** into ChatGPT, Claude, or any AI assistant
|
||||
|
||||
### Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| Copy context to clipboard | Copies all context files |
|
||||
| Copy context with current note | Copies context + active note |
|
||||
| Generate context files | Opens the context generator |
|
||||
|
||||
### Ribbon Icon
|
||||
|
||||
Click the clipboard icon in the left sidebar for quick access to "Copy context to clipboard".
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
## Configuration
|
||||
|
||||
</div>
|
||||
|
||||
### Settings
|
||||
|
||||
Access via Settings → Claude Context:
|
||||
|
||||
| Setting | Description | Default |
|
||||
|---------|-------------|---------|
|
||||
| Context folder | Folder containing context files | `_claude` |
|
||||
| Separator | Text between files | `---` |
|
||||
| Include filenames | Add filename headers | On |
|
||||
| Show preview | Preview before copying | Off |
|
||||
| Include active note | Always include open note | Off |
|
||||
| Excluded files | Files to skip (comma-separated) | Empty |
|
||||
|
||||
### Context Files
|
||||
|
||||
The plugin expects markdown files in your context folder:
|
||||
|
||||
```
|
||||
_claude/
|
||||
├── VAULT.md # Main entry point (always first)
|
||||
├── conventions.md # Naming and formatting rules
|
||||
├── structure.md # Folder organization
|
||||
├── workflows.md # How to do things
|
||||
├── templates.md # Note templates
|
||||
└── examples.md # Concrete examples
|
||||
```
|
||||
|
||||
`VAULT.md` is always copied first. Other files are sorted alphabetically.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
## Context Generator
|
||||
|
||||
</div>
|
||||
|
||||
The generator creates a complete context setup based on your preferences.
|
||||
|
||||
### Sections
|
||||
|
||||
**General**
|
||||
- Vault description (free text explaining your vault's purpose)
|
||||
- Language selection
|
||||
|
||||
**Formatting**
|
||||
- File naming convention
|
||||
- Link style (Wikilinks vs Markdown)
|
||||
- Maximum heading depth
|
||||
- Date format
|
||||
|
||||
**Tags**
|
||||
- Tag style (hierarchical, flat, or none)
|
||||
- Predefined tags for consistency
|
||||
|
||||
**Frontmatter**
|
||||
- Required fields for all notes
|
||||
|
||||
**Rules**
|
||||
- Custom rules for the AI to follow
|
||||
- Forbidden actions (folders/files the AI should never touch)
|
||||
|
||||
**Folder Structure**
|
||||
- Auto-detected folders with purpose fields
|
||||
- Generates "where does what go" documentation
|
||||
|
||||
**Note Templates**
|
||||
- Define template names, target folders, and tags
|
||||
- Generates template documentation
|
||||
|
||||
**Files to Generate**
|
||||
- Toggle which context files to create
|
||||
- Skip files you don't need
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
## Example Output
|
||||
|
||||
</div>
|
||||
|
||||
When you copy context, you get something like:
|
||||
|
||||
```markdown
|
||||
# === VAULT.md ===
|
||||
|
||||
# Vault Context for AI Assistants
|
||||
|
||||
Personal Zettelkasten for development and knowledge management.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
- Language: English
|
||||
- File naming: kebab-case
|
||||
- Links: [[wikilinks]]
|
||||
- Frontmatter: `date`, `tags`
|
||||
...
|
||||
|
||||
---
|
||||
|
||||
# === conventions.md ===
|
||||
|
||||
# Conventions
|
||||
|
||||
## Language
|
||||
|
||||
- Vault language: English
|
||||
...
|
||||
|
||||
---
|
||||
|
||||
# === structure.md ===
|
||||
...
|
||||
```
|
||||
|
||||
Paste this into any AI chat, and it immediately understands your vault.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
## License
|
||||
|
||||
</div>
|
||||
|
||||
MIT License - see [LICENSE](LICENSE) for details.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
Made with ♥ for the Obsidian community
|
||||
|
||||
</div>
|
||||
49
esbuild.config.mjs
Normal file
49
esbuild.config.mjs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import { builtinModules } from 'node:module';
|
||||
|
||||
const banner =
|
||||
`/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
if you want to view the source, please visit the github repository of this plugin
|
||||
*/
|
||||
`;
|
||||
|
||||
const prod = (process.argv[2] === "production");
|
||||
|
||||
const context = await esbuild.context({
|
||||
banner: {
|
||||
js: banner,
|
||||
},
|
||||
entryPoints: ["src/main.ts"],
|
||||
bundle: true,
|
||||
external: [
|
||||
"obsidian",
|
||||
"electron",
|
||||
"@codemirror/autocomplete",
|
||||
"@codemirror/collab",
|
||||
"@codemirror/commands",
|
||||
"@codemirror/language",
|
||||
"@codemirror/lint",
|
||||
"@codemirror/search",
|
||||
"@codemirror/state",
|
||||
"@codemirror/view",
|
||||
"@lezer/common",
|
||||
"@lezer/highlight",
|
||||
"@lezer/lr",
|
||||
...builtinModules],
|
||||
format: "cjs",
|
||||
target: "es2018",
|
||||
logLevel: "info",
|
||||
sourcemap: prod ? false : "inline",
|
||||
treeShaking: true,
|
||||
outfile: "main.js",
|
||||
minify: prod,
|
||||
});
|
||||
|
||||
if (prod) {
|
||||
await context.rebuild();
|
||||
process.exit(0);
|
||||
} else {
|
||||
await context.watch();
|
||||
}
|
||||
34
eslint.config.mts
Normal file
34
eslint.config.mts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import tseslint from 'typescript-eslint';
|
||||
import obsidianmd from "eslint-plugin-obsidianmd";
|
||||
import globals from "globals";
|
||||
import { globalIgnores } from "eslint/config";
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
parserOptions: {
|
||||
projectService: {
|
||||
allowDefaultProject: [
|
||||
'eslint.config.js',
|
||||
'manifest.json'
|
||||
]
|
||||
},
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
extraFileExtensions: ['.json']
|
||||
},
|
||||
},
|
||||
},
|
||||
...obsidianmd.configs.recommended,
|
||||
globalIgnores([
|
||||
"node_modules",
|
||||
"dist",
|
||||
"esbuild.config.mjs",
|
||||
"eslint.config.js",
|
||||
"version-bump.mjs",
|
||||
"versions.json",
|
||||
"main.js",
|
||||
]),
|
||||
);
|
||||
9
manifest.json
Normal file
9
manifest.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"id": "claude-context",
|
||||
"name": "Claude Context",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Copy .claude/ context files to clipboard with one hotkey.",
|
||||
"author": "Luca",
|
||||
"isDesktopOnly": true
|
||||
}
|
||||
614
package-lock.json
generated
Normal file
614
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,614 @@
|
|||
{
|
||||
"name": "obsidian-claude-context",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-claude-context",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"obsidian": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"esbuild": "0.25.5",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz",
|
||||
"integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.38.6",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz",
|
||||
"integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.5.0",
|
||||
"crelt": "^1.0.6",
|
||||
"style-mod": "^4.1.0",
|
||||
"w3c-keyname": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
|
||||
"integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
|
||||
"integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
|
||||
"integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
|
||||
"integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
|
||||
"integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
|
||||
"integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
|
||||
"integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
|
||||
"integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
|
||||
"integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
|
||||
"integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@marijn/find-cluster-break": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/codemirror": {
|
||||
"version": "5.60.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz",
|
||||
"integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/tern": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.18.126",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz",
|
||||
"integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/tern": {
|
||||
"version": "0.23.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz",
|
||||
"integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
|
||||
"integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.5",
|
||||
"@esbuild/android-arm": "0.25.5",
|
||||
"@esbuild/android-arm64": "0.25.5",
|
||||
"@esbuild/android-x64": "0.25.5",
|
||||
"@esbuild/darwin-arm64": "0.25.5",
|
||||
"@esbuild/darwin-x64": "0.25.5",
|
||||
"@esbuild/freebsd-arm64": "0.25.5",
|
||||
"@esbuild/freebsd-x64": "0.25.5",
|
||||
"@esbuild/linux-arm": "0.25.5",
|
||||
"@esbuild/linux-arm64": "0.25.5",
|
||||
"@esbuild/linux-ia32": "0.25.5",
|
||||
"@esbuild/linux-loong64": "0.25.5",
|
||||
"@esbuild/linux-mips64el": "0.25.5",
|
||||
"@esbuild/linux-ppc64": "0.25.5",
|
||||
"@esbuild/linux-riscv64": "0.25.5",
|
||||
"@esbuild/linux-s390x": "0.25.5",
|
||||
"@esbuild/linux-x64": "0.25.5",
|
||||
"@esbuild/netbsd-arm64": "0.25.5",
|
||||
"@esbuild/netbsd-x64": "0.25.5",
|
||||
"@esbuild/openbsd-arm64": "0.25.5",
|
||||
"@esbuild/openbsd-x64": "0.25.5",
|
||||
"@esbuild/sunos-x64": "0.25.5",
|
||||
"@esbuild/win32-arm64": "0.25.5",
|
||||
"@esbuild/win32-ia32": "0.25.5",
|
||||
"@esbuild/win32-x64": "0.25.5"
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/obsidian": {
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.10.3.tgz",
|
||||
"integrity": "sha512-VP+ZSxNMG7y6Z+sU9WqLvJAskCfkFrTz2kFHWmmzis+C+4+ELjk/sazwcTHrHXNZlgCeo8YOlM6SOrAFCynNew==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/codemirror": "5.60.8",
|
||||
"moment": "2.29.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@codemirror/state": "6.5.0",
|
||||
"@codemirror/view": "6.38.6"
|
||||
}
|
||||
},
|
||||
"node_modules/style-mod": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
|
||||
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-keyname": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
21
package.json
Normal file
21
package.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "obsidian-claude-context",
|
||||
"version": "1.0.0",
|
||||
"description": "Copy .claude/ context files to clipboard",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"esbuild": "0.25.5",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"obsidian": "latest"
|
||||
}
|
||||
}
|
||||
629
src/generator.ts
Normal file
629
src/generator.ts
Normal file
|
|
@ -0,0 +1,629 @@
|
|||
import { App, Modal, Setting, TFolder } from 'obsidian';
|
||||
import ClaudeContextPlugin from './main';
|
||||
|
||||
interface FolderConfig {
|
||||
name: string;
|
||||
purpose: string;
|
||||
}
|
||||
|
||||
interface TemplateConfig {
|
||||
name: string;
|
||||
folder: string;
|
||||
tag: string;
|
||||
}
|
||||
|
||||
interface ContextConfig {
|
||||
// Basic
|
||||
vaultDescription: string;
|
||||
language: string;
|
||||
|
||||
// Formatting
|
||||
fileNaming: string;
|
||||
linkStyle: string;
|
||||
headingDepth: string;
|
||||
dateFormat: string;
|
||||
|
||||
// Tags
|
||||
tagsStyle: string;
|
||||
customTags: string[];
|
||||
|
||||
// Frontmatter
|
||||
frontmatterFields: string[];
|
||||
|
||||
// Rules
|
||||
customRules: string[];
|
||||
forbiddenActions: string[];
|
||||
|
||||
// Structure
|
||||
folders: FolderConfig[];
|
||||
|
||||
// Templates
|
||||
templates: TemplateConfig[];
|
||||
|
||||
// Files to generate
|
||||
generateFiles: {
|
||||
conventions: boolean;
|
||||
structure: boolean;
|
||||
workflows: boolean;
|
||||
templates: boolean;
|
||||
examples: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: ContextConfig = {
|
||||
vaultDescription: '',
|
||||
language: 'english',
|
||||
fileNaming: 'kebab-case',
|
||||
linkStyle: 'wikilinks',
|
||||
headingDepth: 'h3',
|
||||
dateFormat: 'YYYY-MM-DD',
|
||||
tagsStyle: 'hierarchical',
|
||||
customTags: [],
|
||||
frontmatterFields: ['date', 'tags'],
|
||||
customRules: [],
|
||||
forbiddenActions: ['.obsidian/'],
|
||||
folders: [],
|
||||
templates: [],
|
||||
generateFiles: {
|
||||
conventions: true,
|
||||
structure: true,
|
||||
workflows: true,
|
||||
templates: true,
|
||||
examples: true,
|
||||
},
|
||||
};
|
||||
|
||||
export class ContextGeneratorModal extends Modal {
|
||||
plugin: ClaudeContextPlugin;
|
||||
config: ContextConfig;
|
||||
|
||||
constructor(app: App, plugin: ClaudeContextPlugin) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
this.config = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
|
||||
this.config.folders = this.scanVaultStructure().map(name => ({ name, purpose: '' }));
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
contentEl.addClass('claude-context-generator');
|
||||
contentEl.style.maxHeight = '80vh';
|
||||
contentEl.style.overflow = 'auto';
|
||||
|
||||
contentEl.createEl('h2', { text: 'Context Generator' });
|
||||
|
||||
// === BASIC SECTION ===
|
||||
contentEl.createEl('h3', { text: 'General' });
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Vault description')
|
||||
.setDesc('What is this vault used for?')
|
||||
.addTextArea(text => {
|
||||
text.setPlaceholder('e.g. Personal Zettelkasten for development and knowledge management')
|
||||
.setValue(this.config.vaultDescription)
|
||||
.onChange(v => this.config.vaultDescription = v);
|
||||
text.inputEl.rows = 2;
|
||||
text.inputEl.style.width = '100%';
|
||||
});
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Language')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('english', 'English')
|
||||
.addOption('german', 'Deutsch')
|
||||
.setValue(this.config.language)
|
||||
.onChange(v => this.config.language = v));
|
||||
|
||||
// === FORMATTING SECTION ===
|
||||
contentEl.createEl('h3', { text: 'Formatting' });
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('File naming')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('kebab-case', 'kebab-case')
|
||||
.addOption('snake_case', 'snake_case')
|
||||
.addOption('camelCase', 'camelCase')
|
||||
.addOption('free', 'Free / no convention')
|
||||
.setValue(this.config.fileNaming)
|
||||
.onChange(v => this.config.fileNaming = v));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Link style')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('wikilinks', '[[Wikilinks]]')
|
||||
.addOption('markdown', '[Markdown](links)')
|
||||
.setValue(this.config.linkStyle)
|
||||
.onChange(v => this.config.linkStyle = v));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Heading depth')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('h2', 'H1 - H2')
|
||||
.addOption('h3', 'H1 - H3')
|
||||
.addOption('h4', 'H1 - H4')
|
||||
.addOption('h6', 'Unlimited')
|
||||
.setValue(this.config.headingDepth)
|
||||
.onChange(v => this.config.headingDepth = v));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Date format')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('YYYY-MM-DD', 'YYYY-MM-DD (ISO)')
|
||||
.addOption('DD.MM.YYYY', 'DD.MM.YYYY')
|
||||
.addOption('MM/DD/YYYY', 'MM/DD/YYYY')
|
||||
.setValue(this.config.dateFormat)
|
||||
.onChange(v => this.config.dateFormat = v));
|
||||
|
||||
// === TAGS SECTION ===
|
||||
contentEl.createEl('h3', { text: 'Tags' });
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Tag style')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('hierarchical', 'Hierarchical (#area/tag)')
|
||||
.addOption('flat', 'Flat (#tag)')
|
||||
.addOption('none', 'No tags')
|
||||
.setValue(this.config.tagsStyle)
|
||||
.onChange(v => this.config.tagsStyle = v));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Predefined tags')
|
||||
.setDesc('Comma-separated (e.g. status/active, status/done, project)')
|
||||
.addText(text => text
|
||||
.setPlaceholder('tag1, tag2, area/tag3')
|
||||
.setValue(this.config.customTags.join(', '))
|
||||
.onChange(v => {
|
||||
this.config.customTags = v.split(',').map(s => s.trim()).filter(s => s);
|
||||
}));
|
||||
|
||||
// === FRONTMATTER SECTION ===
|
||||
contentEl.createEl('h3', { text: 'Frontmatter' });
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Frontmatter fields')
|
||||
.setDesc('Comma-separated (e.g. date, tags, aliases, status)')
|
||||
.addText(text => text
|
||||
.setValue(this.config.frontmatterFields.join(', '))
|
||||
.onChange(v => {
|
||||
this.config.frontmatterFields = v.split(',').map(s => s.trim()).filter(s => s);
|
||||
}));
|
||||
|
||||
// === RULES SECTION ===
|
||||
contentEl.createEl('h3', { text: 'Rules' });
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Custom rules')
|
||||
.setDesc('One rule per line')
|
||||
.addTextArea(text => {
|
||||
text.setPlaceholder('Proactively link related notes\nAlways fill frontmatter')
|
||||
.setValue(this.config.customRules.join('\n'))
|
||||
.onChange(v => {
|
||||
this.config.customRules = v.split('\n').map(s => s.trim()).filter(s => s);
|
||||
});
|
||||
text.inputEl.rows = 3;
|
||||
text.inputEl.style.width = '100%';
|
||||
});
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Forbidden actions')
|
||||
.setDesc('Comma-separated (e.g. .obsidian/, certain folders)')
|
||||
.addText(text => text
|
||||
.setValue(this.config.forbiddenActions.join(', '))
|
||||
.onChange(v => {
|
||||
this.config.forbiddenActions = v.split(',').map(s => s.trim()).filter(s => s);
|
||||
}));
|
||||
|
||||
// === STRUCTURE SECTION ===
|
||||
contentEl.createEl('h3', { text: 'Folder structure' });
|
||||
contentEl.createEl('p', {
|
||||
text: 'Describe the purpose of your folders:',
|
||||
cls: 'setting-item-description'
|
||||
});
|
||||
|
||||
const foldersContainer = contentEl.createDiv({ cls: 'folders-container' });
|
||||
this.renderFolders(foldersContainer);
|
||||
|
||||
// === TEMPLATES SECTION ===
|
||||
contentEl.createEl('h3', { text: 'Note templates' });
|
||||
|
||||
const templatesContainer = contentEl.createDiv({ cls: 'templates-container' });
|
||||
this.renderTemplates(templatesContainer);
|
||||
|
||||
new Setting(contentEl)
|
||||
.addButton(btn => btn
|
||||
.setButtonText('+ Add template')
|
||||
.onClick(() => {
|
||||
this.config.templates.push({ name: '', folder: '', tag: '' });
|
||||
this.renderTemplates(templatesContainer);
|
||||
}));
|
||||
|
||||
// === FILES TO GENERATE ===
|
||||
contentEl.createEl('h3', { text: 'Files to generate' });
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('conventions.md')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.config.generateFiles.conventions)
|
||||
.onChange(v => this.config.generateFiles.conventions = v));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('structure.md')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.config.generateFiles.structure)
|
||||
.onChange(v => this.config.generateFiles.structure = v));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('workflows.md')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.config.generateFiles.workflows)
|
||||
.onChange(v => this.config.generateFiles.workflows = v));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('templates.md')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.config.generateFiles.templates)
|
||||
.onChange(v => this.config.generateFiles.templates = v));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('examples.md')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.config.generateFiles.examples)
|
||||
.onChange(v => this.config.generateFiles.examples = v));
|
||||
|
||||
// === GENERATE BUTTON ===
|
||||
contentEl.createEl('hr');
|
||||
|
||||
new Setting(contentEl)
|
||||
.addButton(btn => btn
|
||||
.setButtonText('Generate')
|
||||
.setCta()
|
||||
.onClick(() => this.generate()));
|
||||
}
|
||||
|
||||
renderFolders(container: HTMLElement) {
|
||||
container.empty();
|
||||
for (const folder of this.config.folders) {
|
||||
const row = container.createDiv({ cls: 'folder-row' });
|
||||
row.style.display = 'flex';
|
||||
row.style.gap = '10px';
|
||||
row.style.marginBottom = '8px';
|
||||
row.style.alignItems = 'center';
|
||||
|
||||
const label = row.createEl('span', { text: `${folder.name}/` });
|
||||
label.style.minWidth = '120px';
|
||||
label.style.fontFamily = 'monospace';
|
||||
|
||||
const input = row.createEl('input', { type: 'text' });
|
||||
input.placeholder = 'Purpose...';
|
||||
input.value = folder.purpose;
|
||||
input.style.flex = '1';
|
||||
input.addEventListener('input', () => {
|
||||
folder.purpose = input.value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderTemplates(container: HTMLElement) {
|
||||
container.empty();
|
||||
this.config.templates.forEach((template, index) => {
|
||||
const row = container.createDiv({ cls: 'template-row' });
|
||||
row.style.display = 'flex';
|
||||
row.style.gap = '10px';
|
||||
row.style.marginBottom = '8px';
|
||||
row.style.alignItems = 'center';
|
||||
|
||||
const nameInput = row.createEl('input', { type: 'text' });
|
||||
nameInput.placeholder = 'Name (e.g. daily)';
|
||||
nameInput.value = template.name;
|
||||
nameInput.style.flex = '1';
|
||||
nameInput.addEventListener('input', () => {
|
||||
template.name = nameInput.value;
|
||||
});
|
||||
|
||||
const folderInput = row.createEl('input', { type: 'text' });
|
||||
folderInput.placeholder = 'Target folder';
|
||||
folderInput.value = template.folder;
|
||||
folderInput.style.flex = '1';
|
||||
folderInput.addEventListener('input', () => {
|
||||
template.folder = folderInput.value;
|
||||
});
|
||||
|
||||
const tagInput = row.createEl('input', { type: 'text' });
|
||||
tagInput.placeholder = 'Tag';
|
||||
tagInput.value = template.tag;
|
||||
tagInput.style.flex = '1';
|
||||
tagInput.addEventListener('input', () => {
|
||||
template.tag = tagInput.value;
|
||||
});
|
||||
|
||||
const removeBtn = row.createEl('button', { text: '✕' });
|
||||
removeBtn.addEventListener('click', () => {
|
||||
this.config.templates.splice(index, 1);
|
||||
this.renderTemplates(container);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
scanVaultStructure(): string[] {
|
||||
const root = this.app.vault.getRoot();
|
||||
const folders: string[] = [];
|
||||
|
||||
for (const child of root.children) {
|
||||
if (child instanceof TFolder && !child.name.startsWith('.')) {
|
||||
folders.push(child.name);
|
||||
}
|
||||
}
|
||||
|
||||
return folders.sort();
|
||||
}
|
||||
|
||||
async generate() {
|
||||
const folder = this.plugin.settings.contextFolder;
|
||||
|
||||
if (!this.app.vault.getAbstractFileByPath(folder)) {
|
||||
await this.app.vault.createFolder(folder);
|
||||
}
|
||||
|
||||
await this.createFile(folder, 'VAULT.md', this.generateVaultMd());
|
||||
|
||||
if (this.config.generateFiles.conventions) {
|
||||
await this.createFile(folder, 'conventions.md', this.generateConventionsMd());
|
||||
}
|
||||
if (this.config.generateFiles.structure) {
|
||||
await this.createFile(folder, 'structure.md', this.generateStructureMd());
|
||||
}
|
||||
if (this.config.generateFiles.workflows) {
|
||||
await this.createFile(folder, 'workflows.md', this.generateWorkflowsMd());
|
||||
}
|
||||
if (this.config.generateFiles.templates) {
|
||||
await this.createFile(folder, 'templates.md', this.generateTemplatesMd());
|
||||
}
|
||||
if (this.config.generateFiles.examples) {
|
||||
await this.createFile(folder, 'examples.md', this.generateExamplesMd());
|
||||
}
|
||||
|
||||
this.close();
|
||||
new (await import('obsidian')).Notice(`Context files generated in ${folder}/`);
|
||||
}
|
||||
|
||||
async createFile(folder: string, name: string, content: string) {
|
||||
const path = `${folder}/${name}`;
|
||||
const existing = this.app.vault.getAbstractFileByPath(path);
|
||||
if (existing) {
|
||||
await this.app.vault.modify(existing as any, content);
|
||||
} else {
|
||||
await this.app.vault.create(path, content);
|
||||
}
|
||||
}
|
||||
|
||||
generateVaultMd(): string {
|
||||
const lang = this.config.language === 'german' ? 'German' : 'English';
|
||||
const links = this.config.linkStyle === 'wikilinks' ? '[[wikilinks]]' : '[markdown](links)';
|
||||
const tags = this.config.tagsStyle === 'hierarchical' ? 'hierarchical (#area/tag)' :
|
||||
this.config.tagsStyle === 'flat' ? 'flat (#tag)' : 'none';
|
||||
const depth = this.config.headingDepth.toUpperCase();
|
||||
|
||||
const description = this.config.vaultDescription
|
||||
? `${this.config.vaultDescription}\n\n`
|
||||
: '';
|
||||
|
||||
const docLinks: string[] = [];
|
||||
if (this.config.generateFiles.conventions) docLinks.push('- [[conventions]] - Naming and formatting conventions');
|
||||
if (this.config.generateFiles.structure) docLinks.push('- [[structure]] - Folder structure and organization');
|
||||
if (this.config.generateFiles.workflows) docLinks.push('- [[workflows]] - Workflows and processes');
|
||||
if (this.config.generateFiles.templates) docLinks.push('- [[templates]] - Note templates');
|
||||
if (this.config.generateFiles.examples) docLinks.push('- [[examples]] - Concrete examples');
|
||||
|
||||
const defaultRules = [
|
||||
'Proactively link related notes',
|
||||
`Never delete \`${this.plugin.settings.contextFolder}/\` or \`.obsidian/\``,
|
||||
'Always fill frontmatter',
|
||||
];
|
||||
const allRules = [...defaultRules, ...this.config.customRules];
|
||||
const rulesText = allRules.map((r, i) => `${i + 1}. ${r}`).join('\n');
|
||||
|
||||
return `# Vault Context for AI Assistants
|
||||
|
||||
${description}## Quick Reference
|
||||
|
||||
- Language: ${lang}
|
||||
- File naming: ${this.config.fileNaming}
|
||||
- Links: ${links}
|
||||
- Frontmatter: \`${this.config.frontmatterFields.join('`, `')}\`
|
||||
- Date format: ${this.config.dateFormat}
|
||||
- Tags: ${tags}
|
||||
- Headings: H1 to ${depth}
|
||||
|
||||
## Documentation
|
||||
|
||||
${docLinks.join('\n')}
|
||||
|
||||
## Important Rules
|
||||
|
||||
${rulesText}
|
||||
`;
|
||||
}
|
||||
|
||||
generateConventionsMd(): string {
|
||||
const lang = this.config.language === 'german' ? 'German' : 'English';
|
||||
const depth = this.config.headingDepth.toUpperCase();
|
||||
const linkDesc = this.config.linkStyle === 'wikilinks'
|
||||
? '- Wikilinks: [[note-name]]\n- No Markdown links'
|
||||
: '- Markdown links: [text](path)\n- No Wikilinks';
|
||||
|
||||
let tagsSection = '';
|
||||
if (this.config.tagsStyle !== 'none') {
|
||||
const tagsDesc = this.config.tagsStyle === 'hierarchical'
|
||||
? '- Hierarchical: #area/subcategory'
|
||||
: '- Flat: #tagname';
|
||||
|
||||
let customTagsText = '';
|
||||
if (this.config.customTags.length > 0) {
|
||||
customTagsText = `\n- Predefined: ${this.config.customTags.map(t => `#${t}`).join(', ')}`;
|
||||
}
|
||||
|
||||
tagsSection = `\n## Tags\n\n${tagsDesc}${customTagsText}\n`;
|
||||
}
|
||||
|
||||
const frontmatterText = this.config.frontmatterFields.map(f => ` - ${f}`).join('\n');
|
||||
|
||||
return `# Conventions
|
||||
|
||||
## Language
|
||||
|
||||
- Vault language: ${lang}
|
||||
|
||||
## File Naming
|
||||
|
||||
- Format: ${this.config.fileNaming}
|
||||
- Purely semantic, no date prefixes
|
||||
|
||||
## Folder Naming
|
||||
|
||||
- Format: ${this.config.fileNaming}
|
||||
|
||||
## Formatting
|
||||
|
||||
- Headings: H1 to ${depth}
|
||||
- Date format: ${this.config.dateFormat}
|
||||
|
||||
## Linking
|
||||
|
||||
${linkDesc}
|
||||
${tagsSection}
|
||||
## Frontmatter
|
||||
|
||||
- Required fields in every note:
|
||||
${frontmatterText}
|
||||
`;
|
||||
}
|
||||
|
||||
generateStructureMd(): string {
|
||||
const contextFolder = this.plugin.settings.contextFolder;
|
||||
|
||||
const folderRows = this.config.folders
|
||||
.filter(f => f.name !== contextFolder)
|
||||
.map(f => `| \`${f.name}/\` | ${f.purpose || ''} |`)
|
||||
.join('\n');
|
||||
|
||||
const contextRow = `| \`${contextFolder}/\` | AI context (this documentation) |`;
|
||||
|
||||
const whereRows = this.config.folders
|
||||
.filter(f => f.name !== contextFolder && f.purpose)
|
||||
.map(f => `- **${f.purpose}?** → \`${f.name}/\``)
|
||||
.join('\n');
|
||||
|
||||
return `# Vault Structure
|
||||
|
||||
## Directories
|
||||
|
||||
| Folder | Purpose |
|
||||
|--------|---------|
|
||||
${folderRows}
|
||||
${contextRow}
|
||||
|
||||
## Where Does What Go?
|
||||
|
||||
${whereRows || '*Add examples here.*'}
|
||||
`;
|
||||
}
|
||||
|
||||
generateWorkflowsMd(): string {
|
||||
const linkStyle = this.config.linkStyle === 'wikilinks' ? '[[note-name]]' : '[text](path)';
|
||||
const forbidden = this.config.forbiddenActions.map(a => `- Never touch \`${a}\``).join('\n');
|
||||
|
||||
return `# Workflows
|
||||
|
||||
## General
|
||||
|
||||
- Unrestricted operation - all actions allowed
|
||||
- Proactively link related notes
|
||||
- Follow existing conventions
|
||||
|
||||
## Forbidden
|
||||
|
||||
${forbidden}
|
||||
|
||||
## Creating Notes
|
||||
|
||||
1. Place in the correct target folder
|
||||
2. Fill frontmatter (${this.config.frontmatterFields.join(', ')})
|
||||
3. Link relevant existing notes
|
||||
|
||||
## Linking
|
||||
|
||||
- When creating/editing: Search for related notes
|
||||
- Use links: ${linkStyle}
|
||||
- Think bidirectionally: Also backlink from target notes
|
||||
`;
|
||||
}
|
||||
|
||||
generateTemplatesMd(): string {
|
||||
if (this.config.templates.length === 0) {
|
||||
return `# Templates
|
||||
|
||||
*No templates configured. Add templates in the generator.*
|
||||
`;
|
||||
}
|
||||
|
||||
const rows = this.config.templates
|
||||
.map(t => `| ${t.name} | ${t.folder || '-'} | ${t.tag ? `#${t.tag}` : '-'} |`)
|
||||
.join('\n');
|
||||
|
||||
return `# Templates
|
||||
|
||||
## Available Templates
|
||||
|
||||
| Template | Target Folder | Tag |
|
||||
|----------|---------------|-----|
|
||||
${rows}
|
||||
|
||||
## Template Structure
|
||||
|
||||
Every template follows this structure:
|
||||
1. Frontmatter with \`${this.config.frontmatterFields.join('`, `')}\`
|
||||
2. H1 title
|
||||
3. H2 sections depending on type
|
||||
|
||||
## Variables
|
||||
|
||||
- \`{{date}}\` - Current date (${this.config.dateFormat})
|
||||
- \`{{title}}\` - Note title
|
||||
`;
|
||||
}
|
||||
|
||||
generateExamplesMd(): string {
|
||||
const frontmatter = this.config.frontmatterFields.includes('tags')
|
||||
? `---
|
||||
date: ${this.config.dateFormat === 'YYYY-MM-DD' ? '2025-02-05' : this.config.dateFormat === 'DD.MM.YYYY' ? '05.02.2025' : '02/05/2025'}
|
||||
tags:
|
||||
- example
|
||||
---`
|
||||
: `---
|
||||
date: ${this.config.dateFormat === 'YYYY-MM-DD' ? '2025-02-05' : this.config.dateFormat === 'DD.MM.YYYY' ? '05.02.2025' : '02/05/2025'}
|
||||
---`;
|
||||
|
||||
const link = this.config.linkStyle === 'wikilinks' ? '[[other-note]]' : '[other note](other-note.md)';
|
||||
|
||||
return `# Examples
|
||||
|
||||
## Simple Note
|
||||
|
||||
\`\`\`markdown
|
||||
${frontmatter}
|
||||
|
||||
# Example Title
|
||||
|
||||
## Content
|
||||
|
||||
Here is the content with a link to ${link}.
|
||||
\`\`\`
|
||||
`;
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
110
src/main.ts
Normal file
110
src/main.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
import { MarkdownView, Notice, Plugin, TFile, TFolder } from 'obsidian';
|
||||
import { ClaudeContextSettings, ClaudeContextSettingTab, DEFAULT_SETTINGS } from './settings';
|
||||
import { ContextGeneratorModal } from './generator';
|
||||
import { PreviewModal } from './preview';
|
||||
|
||||
export default class ClaudeContextPlugin extends Plugin {
|
||||
settings: ClaudeContextSettings;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
// Ribbon icon
|
||||
this.addRibbonIcon('clipboard-copy', 'Copy Claude context', () => {
|
||||
this.copyContextToClipboard();
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: 'copy-context',
|
||||
name: 'Copy context to clipboard',
|
||||
callback: () => this.copyContextToClipboard()
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: 'copy-context-with-note',
|
||||
name: 'Copy context with current note',
|
||||
callback: () => this.copyContextToClipboard(true)
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: 'generate-context',
|
||||
name: 'Generate context files',
|
||||
callback: () => new ContextGeneratorModal(this.app, this).open()
|
||||
});
|
||||
|
||||
this.addSettingTab(new ClaudeContextSettingTab(this.app, this));
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
async copyContextToClipboard(forceIncludeNote = false) {
|
||||
const folder = this.app.vault.getAbstractFileByPath(this.settings.contextFolder);
|
||||
|
||||
if (!folder || !(folder instanceof TFolder)) {
|
||||
new Notice(`Folder "${this.settings.contextFolder}" not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
const excludedFiles = this.settings.excludedFiles.map(f => f.toLowerCase());
|
||||
|
||||
const files = folder.children
|
||||
.filter((f): f is TFile =>
|
||||
f instanceof TFile &&
|
||||
f.extension === 'md' &&
|
||||
!excludedFiles.includes(f.name.toLowerCase())
|
||||
)
|
||||
.sort((a, b) => {
|
||||
if (a.basename === 'VAULT') return -1;
|
||||
if (b.basename === 'VAULT') return 1;
|
||||
return a.basename.localeCompare(b.basename);
|
||||
});
|
||||
|
||||
if (files.length === 0) {
|
||||
new Notice(`No markdown files in "${this.settings.contextFolder}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
const content = await this.app.vault.read(file);
|
||||
if (this.settings.includeFilenames) {
|
||||
parts.push(`# === ${file.name} ===\n\n${content}`);
|
||||
} else {
|
||||
parts.push(content);
|
||||
}
|
||||
}
|
||||
|
||||
// Include active note
|
||||
if (forceIncludeNote || this.settings.includeActiveNote) {
|
||||
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (activeView?.file) {
|
||||
const content = await this.app.vault.read(activeView.file);
|
||||
if (this.settings.includeFilenames) {
|
||||
parts.push(`# === ACTIVE: ${activeView.file.name} ===\n\n${content}`);
|
||||
} else {
|
||||
parts.push(`--- ACTIVE NOTE ---\n\n${content}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const combined = parts.join(`\n\n${this.settings.separator}\n\n`);
|
||||
const fileCount = files.length + (forceIncludeNote || this.settings.includeActiveNote ? 1 : 0);
|
||||
|
||||
if (this.settings.showPreview) {
|
||||
new PreviewModal(this.app, combined, fileCount, async () => {
|
||||
await navigator.clipboard.writeText(combined);
|
||||
new Notice(`Copied ${fileCount} files to clipboard`);
|
||||
}).open();
|
||||
} else {
|
||||
await navigator.clipboard.writeText(combined);
|
||||
new Notice(`Copied ${fileCount} files to clipboard`);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/preview.ts
Normal file
54
src/preview.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { App, Modal } from 'obsidian';
|
||||
|
||||
export class PreviewModal extends Modal {
|
||||
content: string;
|
||||
fileCount: number;
|
||||
onConfirm: () => void;
|
||||
|
||||
constructor(app: App, content: string, fileCount: number, onConfirm: () => void) {
|
||||
super(app);
|
||||
this.content = content;
|
||||
this.fileCount = fileCount;
|
||||
this.onConfirm = onConfirm;
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
contentEl.addClass('claude-context-preview');
|
||||
|
||||
contentEl.createEl('h2', { text: `Preview (${this.fileCount} files)` });
|
||||
|
||||
const previewContainer = contentEl.createDiv({ cls: 'preview-container' });
|
||||
previewContainer.style.maxHeight = '400px';
|
||||
previewContainer.style.overflow = 'auto';
|
||||
previewContainer.style.border = '1px solid var(--background-modifier-border)';
|
||||
previewContainer.style.borderRadius = '4px';
|
||||
previewContainer.style.padding = '10px';
|
||||
previewContainer.style.marginBottom = '15px';
|
||||
previewContainer.style.fontFamily = 'monospace';
|
||||
previewContainer.style.fontSize = '12px';
|
||||
previewContainer.style.whiteSpace = 'pre-wrap';
|
||||
|
||||
previewContainer.setText(this.content);
|
||||
|
||||
const buttonContainer = contentEl.createDiv({ cls: 'button-container' });
|
||||
buttonContainer.style.display = 'flex';
|
||||
buttonContainer.style.justifyContent = 'flex-end';
|
||||
buttonContainer.style.gap = '10px';
|
||||
|
||||
const cancelBtn = buttonContainer.createEl('button', { text: 'Cancel' });
|
||||
cancelBtn.addEventListener('click', () => this.close());
|
||||
|
||||
const copyBtn = buttonContainer.createEl('button', { text: 'Copy to Clipboard', cls: 'mod-cta' });
|
||||
copyBtn.addEventListener('click', () => {
|
||||
this.onConfirm();
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
100
src/settings.ts
Normal file
100
src/settings.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import { App, PluginSettingTab, Setting } from 'obsidian';
|
||||
import ClaudeContextPlugin from './main';
|
||||
|
||||
export interface ClaudeContextSettings {
|
||||
contextFolder: string;
|
||||
separator: string;
|
||||
includeFilenames: boolean;
|
||||
showPreview: boolean;
|
||||
includeActiveNote: boolean;
|
||||
excludedFiles: string[];
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: ClaudeContextSettings = {
|
||||
contextFolder: '_claude',
|
||||
separator: '---',
|
||||
includeFilenames: true,
|
||||
showPreview: false,
|
||||
includeActiveNote: false,
|
||||
excludedFiles: [],
|
||||
};
|
||||
|
||||
export class ClaudeContextSettingTab extends PluginSettingTab {
|
||||
plugin: ClaudeContextPlugin;
|
||||
|
||||
constructor(app: App, plugin: ClaudeContextPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Context folder')
|
||||
.setDesc('Folder containing your context files')
|
||||
.addText(text => text
|
||||
.setPlaceholder('_claude')
|
||||
.setValue(this.plugin.settings.contextFolder)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.contextFolder = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Separator')
|
||||
.setDesc('Text between files (e.g. "---" or "***")')
|
||||
.addText(text => text
|
||||
.setPlaceholder('---')
|
||||
.setValue(this.plugin.settings.separator)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.separator = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Include filenames')
|
||||
.setDesc('Add "# === filename.md ===" headers before each file')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.includeFilenames)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.includeFilenames = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Show preview')
|
||||
.setDesc('Show preview modal before copying')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.showPreview)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.showPreview = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Include active note')
|
||||
.setDesc('Append currently open note to context')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.includeActiveNote)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.includeActiveNote = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Excluded files')
|
||||
.setDesc('Comma-separated filenames to exclude (e.g. "examples.md, drafts.md")')
|
||||
.addText(text => text
|
||||
.setPlaceholder('file1.md, file2.md')
|
||||
.setValue(this.plugin.settings.excludedFiles.join(', '))
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.excludedFiles = value
|
||||
.split(',')
|
||||
.map(s => s.trim())
|
||||
.filter(s => s.length > 0);
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
}
|
||||
}
|
||||
8
styles.css
Normal file
8
styles.css
Normal file
|
|
@ -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.
|
||||
|
||||
*/
|
||||
30
tsconfig.json
Normal file
30
tsconfig.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"module": "ESNext",
|
||||
"target": "ES6",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitReturns": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"isolatedModules": true,
|
||||
"strictNullChecks": true,
|
||||
"strictBindCallApply": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ES5",
|
||||
"ES6",
|
||||
"ES7"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
17
version-bump.mjs
Normal file
17
version-bump.mjs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { readFileSync, writeFileSync } from "fs";
|
||||
|
||||
const targetVersion = process.env.npm_package_version;
|
||||
|
||||
// read minAppVersion from manifest.json and bump version to target version
|
||||
const manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
|
||||
const { minAppVersion } = manifest;
|
||||
manifest.version = targetVersion;
|
||||
writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
|
||||
|
||||
// update versions.json with target version and minAppVersion from manifest.json
|
||||
// but only if the target version is not already in versions.json
|
||||
const versions = JSON.parse(readFileSync('versions.json', 'utf8'));
|
||||
if (!Object.values(versions).includes(minAppVersion)) {
|
||||
versions[targetVersion] = minAppVersion;
|
||||
writeFileSync('versions.json', JSON.stringify(versions, null, '\t'));
|
||||
}
|
||||
3
versions.json
Normal file
3
versions.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"1.0.0": "0.15.0"
|
||||
}
|
||||
Loading…
Reference in a new issue