···1-# vscode
2-.vscode
3-4-# Intellij
5-*.iml
6-.idea
7-8-# npm
9-node_modules
10-11-# Don't include the compiled main.js file in the repo.
12-# They should be uploaded to GitHub releases instead.
13-main.js
14-15-# Exclude sourcemaps
16-*.map
17-18-# obsidian
19-data.json
20-21-# Exclude macOS Finder (System Explorer) View States
22-.DS_Store
···1-# Obsidian community plugin
2-3-## Project overview
4-5-- Target: Obsidian Community Plugin (TypeScript โ bundled JavaScript).
6-- Entry point: `main.ts` compiled to `main.js` and loaded by Obsidian.
7-- Required release artifacts: `main.js`, `manifest.json`, and optional `styles.css`.
8-9-## Environment & tooling
10-11-- Node.js: use current LTS (Node 18+ recommended).
12-- **Package manager: npm** (required for this sample - `package.json` defines npm scripts and dependencies).
13-- **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`.
14-- Types: `obsidian` type definitions.
15-16-**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.
17-18-### Install
19-20-```bash
21-npm install
22-```
23-24-### Dev (watch)
25-26-```bash
27-npm run dev
28-```
29-30-### Production build
31-32-```bash
33-npm run build
34-```
35-36-## Linting
37-38-- To use eslint install eslint from terminal: `npm install -g eslint`
39-- To use eslint to analyze this project use this command: `eslint main.ts`
40-- eslint will then create a report with suggestions for code improvement by file and line number.
41-- 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/`
42-43-## File & folder conventions
44-45-- **Organize code into multiple files**: Split functionality across separate modules rather than putting everything in `main.ts`.
46-- Source lives in `src/`. Keep `main.ts` small and focused on plugin lifecycle (loading, unloading, registering commands).
47-- **Example file structure**:
48- ```
49- src/
50- main.ts # Plugin entry point, lifecycle management
51- settings.ts # Settings interface and defaults
52- commands/ # Command implementations
53- command1.ts
54- command2.ts
55- ui/ # UI components, modals, views
56- modal.ts
57- view.ts
58- utils/ # Utility functions, helpers
59- helpers.ts
60- constants.ts
61- types.ts # TypeScript interfaces and types
62- ```
63-- **Do not commit build artifacts**: Never commit `node_modules/`, `main.js`, or other generated files to version control.
64-- Keep the plugin small. Avoid large dependencies. Prefer browser-compatible packages.
65-- 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`).
66-67-## Manifest rules (`manifest.json`)
68-69-- Must include (non-exhaustive):
70- - `id` (plugin ID; for local dev it should match the folder name)
71- - `name`
72- - `version` (Semantic Versioning `x.y.z`)
73- - `minAppVersion`
74- - `description`
75- - `isDesktopOnly` (boolean)
76- - Optional: `author`, `authorUrl`, `fundingUrl` (string or map)
77-- Never change `id` after release. Treat it as stable API.
78-- Keep `minAppVersion` accurate when using newer APIs.
79-- Canonical requirements are coded here: https://github.com/obsidianmd/obsidian-releases/blob/master/.github/workflows/validate-plugin-entry.yml
80-81-## Testing
82-83-- Manual install for testing: copy `main.js`, `manifest.json`, `styles.css` (if any) to:
84- ```
85- <Vault>/.obsidian/plugins/<plugin-id>/
86- ```
87-- Reload Obsidian and enable the plugin in **Settings โ Community plugins**.
88-89-## Commands & settings
90-91-- Any user-facing commands should be added via `this.addCommand(...)`.
92-- If the plugin has configuration, provide a settings tab and sensible defaults.
93-- Persist settings using `this.loadData()` / `this.saveData()`.
94-- Use stable command IDs; avoid renaming once released.
95-96-## Versioning & releases
97-98-- Bump `version` in `manifest.json` (SemVer) and update `versions.json` to map plugin version โ minimum app version.
99-- Create a GitHub release whose tag exactly matches `manifest.json`'s `version`. Do not use a leading `v`.
100-- Attach `manifest.json`, `main.js`, and `styles.css` (if present) to the release as individual assets.
101-- After the initial release, follow the process to add/update your plugin in the community catalog as required.
102-103-## Security, privacy, and compliance
104-105-Follow Obsidian's **Developer Policies** and **Plugin Guidelines**. In particular:
106-107-- Default to local/offline operation. Only make network requests when essential to the feature.
108-- 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.
109-- Never execute remote code, fetch and eval scripts, or auto-update plugin code outside of normal releases.
110-- Minimize scope: read/write only what's necessary inside the vault. Do not access files outside the vault.
111-- Clearly disclose any external services used, data sent, and risks.
112-- Respect user privacy. Do not collect vault contents, filenames, or personal information unless absolutely necessary and explicitly consented.
113-- Avoid deceptive patterns, ads, or spammy notifications.
114-- Register and clean up all DOM, app, and interval listeners using the provided `register*` helpers so the plugin unloads safely.
115-116-## UX & copy guidelines (for UI text, commands, settings)
117-118-- Prefer sentence case for headings, buttons, and titles.
119-- Use clear, action-oriented imperatives in step-by-step copy.
120-- Use **bold** to indicate literal UI labels. Prefer "select" for interactions.
121-- Use arrow notation for navigation: **Settings โ Community plugins**.
122-- Keep in-app strings short, consistent, and free of jargon.
123-124-## Performance
125-126-- Keep startup light. Defer heavy work until needed.
127-- Avoid long-running tasks during `onload`; use lazy initialization.
128-- Batch disk access and avoid excessive vault scans.
129-- Debounce/throttle expensive operations in response to file system events.
130-131-## Coding conventions
132-133-- TypeScript with `"strict": true` preferred.
134-- **Keep `main.ts` minimal**: Focus only on plugin lifecycle (onload, onunload, addCommand calls). Delegate all feature logic to separate modules.
135-- **Split large files**: If any file exceeds ~200-300 lines, consider breaking it into smaller, focused modules.
136-- **Use clear module boundaries**: Each file should have a single, well-defined responsibility.
137-- Bundle everything into `main.js` (no unbundled runtime deps).
138-- Avoid Node/Electron APIs if you want mobile compatibility; set `isDesktopOnly` accordingly.
139-- Prefer `async/await` over promise chains; handle errors gracefully.
140-141-## Mobile
142-143-- Where feasible, test on iOS and Android.
144-- Don't assume desktop-only behavior unless `isDesktopOnly` is `true`.
145-- Avoid large in-memory structures; be mindful of memory and storage constraints.
146-147-## Agent do/don't
148-149-**Do**
150-- Add commands with stable IDs (don't rename once released).
151-- Provide defaults and validation in settings.
152-- Write idempotent code paths so reload/unload doesn't leak listeners or intervals.
153-- Use `this.register*` helpers for everything that needs cleanup.
154-155-**Don't**
156-- Introduce network calls without an obvious user-facing reason and documentation.
157-- Ship features that require cloud services without clear disclosure and explicit opt-in.
158-- Store or transmit vault contents unless essential and consented.
159-160-## Common tasks
161-162-### Organize code across multiple files
163-164-**main.ts** (minimal, lifecycle only):
165-```ts
166-import { Plugin } from "obsidian";
167-import { MySettings, DEFAULT_SETTINGS } from "./settings";
168-import { registerCommands } from "./commands";
169-170-export default class MyPlugin extends Plugin {
171- settings: MySettings;
172-173- async onload() {
174- this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
175- registerCommands(this);
176- }
177-}
178-```
179-180-**settings.ts**:
181-```ts
182-export interface MySettings {
183- enabled: boolean;
184- apiKey: string;
185-}
186-187-export const DEFAULT_SETTINGS: MySettings = {
188- enabled: true,
189- apiKey: "",
190-};
191-```
192-193-**commands/index.ts**:
194-```ts
195-import { Plugin } from "obsidian";
196-import { doSomething } from "./my-command";
197-198-export function registerCommands(plugin: Plugin) {
199- plugin.addCommand({
200- id: "do-something",
201- name: "Do something",
202- callback: () => doSomething(plugin),
203- });
204-}
205-```
206-207-### Add a command
208-209-```ts
210-this.addCommand({
211- id: "your-command-id",
212- name: "Do the thing",
213- callback: () => this.doTheThing(),
214-});
215-```
216-217-### Persist settings
218-219-```ts
220-interface MySettings { enabled: boolean }
221-const DEFAULT_SETTINGS: MySettings = { enabled: true };
222-223-async onload() {
224- this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
225- await this.saveData(this.settings);
226-}
227-```
228-229-### Register listeners safely
230-231-```ts
232-this.registerEvent(this.app.workspace.on("file-open", f => { /* ... */ }));
233-this.registerDomEvent(window, "resize", () => { /* ... */ });
234-this.registerInterval(window.setInterval(() => { /* ... */ }, 1000));
235-```
236-237-## Troubleshooting
238-239-- 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>/`.
240-- Build issues: if `main.js` is missing, run `npm run build` or `npm run dev` to compile your TypeScript source code.
241-- Commands not appearing: verify `addCommand` runs after `onload` and IDs are unique.
242-- Settings not persisting: ensure `loadData`/`saveData` are awaited and you re-render the UI after changes.
243-- Mobile-only issues: confirm you're not using desktop-only APIs; check `isDesktopOnly` and adjust.
244-245-## References
246-247-- Obsidian sample plugin: https://github.com/obsidianmd/obsidian-sample-plugin
248-- API documentation: https://docs.obsidian.md
249-- Developer policies: https://docs.obsidian.md/Developer+policies
250-- Plugin guidelines: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines
251-- Style guide: https://help.obsidian.md/style-guide
···1-MIT License
2-3-Copyright (c) 2025 treethought
4-5-Permission is hereby granted, free of charge, to any person obtaining a copy
6-of this software and associated documentation files (the "Software"), to deal
7-in the Software without restriction, including without limitation the rights
8-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9-copies of the Software, and to permit persons to whom the Software is
10-furnished to do so, subject to the following conditions:
11-12-The above copyright notice and this permission notice shall be included in all
13-copies or substantial portions of the Software.
14-15-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21-SOFTWARE.
···000000000000000000000
+2-40
README.md
···1-# ATmark
23Obsidian plugin for AT Protocol bookmarking platforms.
45-6-## Supported platforms
7-8-- **Semble** (`network.cosmik.*`) - Collections and cards
9-- **Bookmarks** (`community.lexicon.bookmarks.*`) - Community bookmarks lexicon with tag filtering (supports kipclip tags)
10-- **margin.at** (`at.margin.*`) - Bookmarks with collections and tags support
11-12-
13-
14-15-## Installation
16-17-Install via [BRAT](https://github.com/TfTHacker/obsidian42-brat):
18-19-1. Install the BRAT plugin from Community Plugins
20-2. Open BRAT settings
21-3. Click "Add Beta plugin"
22-4. Enter the GitHub URL: `https://github.com/treethought/obsidian-atmark`
23-5. Enable the plugin in Community Plugins
24-25-## Getting Started
26-27-### Authentication
28-29-1. Open Settings > ATmark
30-2. Enter your AT Protocol handle or DID
31-3. Create an app password in your AT Protocol client (Bluesky: Settings > Privacy and security > App passwords)
32-4. Enter the app password in the plugin settings
33-5. Save settings
34-35-The plugin will automatically connect using your credentials.
36-37-### Opening the View
38-39-Open the command palette (Ctrl/Cmd + P) and search for "ATmark: Open view". The view will show your bookmarks from all supported platforms.
40-41-## Network Use
42-43-This plugin connects to AT Protocol services to fetch and manage your bookmarks.
···1+# ATmark (archived)
23Obsidian plugin for AT Protocol bookmarking platforms.
45+This project has been renamed and moved to [obsidian-atmosphere](https://tangled.org/treethought.xyz/obsidian-atmosphere)
00000000000000000000000000000000000000
···1-{
2- "lexicon": 1,
3- "id": "network.cosmik.collectionLink",
4- "description": "A record that links a card to a collection.",
5- "defs": {
6- "main": {
7- "type": "record",
8- "description": "A record representing the relationship between a card and a collection.",
9- "key": "tid",
10- "record": {
11- "type": "object",
12- "required": ["collection", "card", "addedBy", "addedAt"],
13- "properties": {
14- "collection": {
15- "type": "ref",
16- "description": "Strong reference to the collection record.",
17- "ref": "com.atproto.repo.strongRef"
18- },
19- "card": {
20- "type": "ref",
21- "description": "Strong reference to the card record in the users library.",
22- "ref": "com.atproto.repo.strongRef"
23- },
24- "originalCard": {
25- "type": "ref",
26- "description": "Strong reference to the original card record (may be in another library).",
27- "ref": "com.atproto.repo.strongRef"
28- },
29- "addedBy": {
30- "type": "string",
31- "description": "DID of the user who added the card to the collection"
32- },
33- "addedAt": {
34- "type": "string",
35- "format": "datetime",
36- "description": "Timestamp when the card was added to the collection."
37- },
38- "createdAt": {
39- "type": "string",
40- "format": "datetime",
41- "description": "Timestamp when this link record was created (usually set by PDS)."
42- },
43- "provenance": {
44- "type": "ref",
45- "description": "Optional provenance information for this link.",
46- "ref": "network.cosmik.defs#provenance"
47- }
48- }
49- }
50- }
51- }
52-}
···1-export * as AtMarginAnnotation from "./types/at/margin/annotation.js";
2-export * as AtMarginBookmark from "./types/at/margin/bookmark.js";
3-export * as AtMarginCollection from "./types/at/margin/collection.js";
4-export * as AtMarginCollectionItem from "./types/at/margin/collectionItem.js";
5-export * as AtMarginHighlight from "./types/at/margin/highlight.js";
6-export * as AtMarginLike from "./types/at/margin/like.js";
7-export * as AtMarginProfile from "./types/at/margin/profile.js";
8-export * as AtMarginReply from "./types/at/margin/reply.js";
9-export * as ComAtprotoRepoStrongRef from "./types/com/atproto/repo/strongRef.js";
10-export * as NetworkCosmikCard from "./types/network/cosmik/card.js";
11-export * as NetworkCosmikCollection from "./types/network/cosmik/collection.js";
12-export * as NetworkCosmikCollectionLink from "./types/network/cosmik/collectionLink.js";
13-export * as NetworkCosmikDefs from "./types/network/cosmik/defs.js";
···1-import type {} from "@atcute/lexicons";
2-import * as v from "@atcute/lexicons/validations";
3-import type {} from "@atcute/lexicons/ambient";
4-import * as ComAtprotoRepoStrongRef from "../../com/atproto/repo/strongRef.js";
5-import * as NetworkCosmikDefs from "./defs.js";
6-7-const _mainSchema = /*#__PURE__*/ v.record(
8- /*#__PURE__*/ v.tidString(),
9- /*#__PURE__*/ v.object({
10- $type: /*#__PURE__*/ v.literal("network.cosmik.collectionLink"),
11- /**
12- * Timestamp when the card was added to the collection.
13- */
14- addedAt: /*#__PURE__*/ v.datetimeString(),
15- /**
16- * DID of the user who added the card to the collection
17- */
18- addedBy: /*#__PURE__*/ v.string(),
19- /**
20- * Strong reference to the card record in the users library.
21- */
22- get card() {
23- return ComAtprotoRepoStrongRef.mainSchema;
24- },
25- /**
26- * Strong reference to the collection record.
27- */
28- get collection() {
29- return ComAtprotoRepoStrongRef.mainSchema;
30- },
31- /**
32- * Timestamp when this link record was created (usually set by PDS).
33- */
34- createdAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()),
35- /**
36- * Strong reference to the original card record (may be in another library).
37- */
38- get originalCard() {
39- return /*#__PURE__*/ v.optional(ComAtprotoRepoStrongRef.mainSchema);
40- },
41- /**
42- * Optional provenance information for this link.
43- */
44- get provenance() {
45- return /*#__PURE__*/ v.optional(NetworkCosmikDefs.provenanceSchema);
46- },
47- }),
48-);
49-50-type main$schematype = typeof _mainSchema;
51-52-export interface mainSchema extends main$schematype {}
53-54-export const mainSchema = _mainSchema as mainSchema;
55-56-export interface Main extends v.InferInput<typeof mainSchema> {}
57-58-declare module "@atcute/lexicons/ambient" {
59- interface Records {
60- "network.cosmik.collectionLink": mainSchema;
61- }
62-}
···1-import { readFileSync, writeFileSync } from "fs";
2-3-const targetVersion = process.env.npm_package_version;
4-5-// read minAppVersion from manifest.json and bump version to target version
6-const manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
7-const { minAppVersion } = manifest;
8-manifest.version = targetVersion;
9-writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
10-11-// update versions.json with target version and minAppVersion from manifest.json
12-// but only if the target version is not already in versions.json
13-const versions = JSON.parse(readFileSync('versions.json', 'utf8'));
14-if (!Object.values(versions).includes(minAppVersion)) {
15- versions[targetVersion] = minAppVersion;
16- writeFileSync('versions.json', JSON.stringify(versions, null, '\t'));
17-}