atlas-vault 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ryan Mattson
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.
@@ -0,0 +1,183 @@
1
+ Metadata-Version: 2.4
2
+ Name: atlas-vault
3
+ Version: 0.1.0
4
+ Summary: A command-line tool for managing and validating Markdown-based worldbuilding vaults.
5
+ Author-email: Ryan Mattson <ramattson5@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/you/atlas
8
+ Project-URL: Issues, https://github.com/you/atlas/issues
9
+ Keywords: worldbuilding,markdown,cli,vault
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Environment :: Console
12
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
13
+ Requires-Python: >=3.11
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: pyyaml>=6.0
17
+ Dynamic: license-file
18
+
19
+ # Atlas
20
+
21
+ A command-line tool for managing Markdown worldbuilding vaults, built with Obsidian in mind.
22
+
23
+ Atlas creates, moves, deletes, lists, and searches notes in a vault of plain Markdown files. It keeps note ids and `[[wiki links]]` consistent when you reorganize, so renaming a note never leaves broken links behind.
24
+
25
+ ## Features
26
+
27
+ - Create notes from templates, with frontmatter written for you
28
+ - Move or rename notes and automatically update every wiki link that points at them
29
+ - Safe deletes: notes go to a trash folder instead of being erased
30
+ - Tree view of your vault and title search
31
+ - Works on plain `.md` files, so your vault stays usable in Obsidian or any other editor
32
+
33
+ ## Requirements
34
+
35
+ - Python 3.11 or newer
36
+
37
+ ## Installation
38
+
39
+
40
+
41
+ ## Quick start
42
+
43
+ ```bash
44
+ cd ~/Documents/MyWorld
45
+ atlas init
46
+ atlas create "Nations/Republic of Valora" --type Nation
47
+ atlas list
48
+ ```
49
+
50
+ `atlas init` marks a folder as an Atlas vault by creating a `.atlas` directory inside it. Every other command needs that directory to exist.
51
+
52
+ ## Finding your vault
53
+
54
+ Atlas looks for a vault in this order:
55
+
56
+ 1. The path given with `-v` / `--vault`
57
+ 2. The current directory, then up to five parent directories, stopping at the first one that contains `.atlas`
58
+
59
+ So you can run commands from any subfolder of your vault without passing a path. Global options go before the command:
60
+
61
+ ```bash
62
+ atlas --vault ~/Documents/MyWorld list
63
+ atlas --quiet create "Characters/Aria"
64
+ ```
65
+
66
+ | Option | Description |
67
+ | --- | --- |
68
+ | `-v`, `--vault PATH` | Path to the vault. |
69
+ | `--quiet` | Silence messages from Atlas. |
70
+ | `--version` | Show the Atlas version. |
71
+
72
+ ## Commands
73
+
74
+ Note names are paths relative to the vault root. The `.md` extension is optional.
75
+
76
+ ### `atlas init`
77
+
78
+ Initializes the current directory (or `--vault`) as an Atlas vault. This creates `.atlas/` directory.
79
+
80
+ ### `atlas create NAME [NAME ...]`
81
+
82
+ Creates one or more notes. Missing folders are created for you. Each new note gets an `id` in its frontmatter, generated from its path, and a heading with its title.
83
+
84
+ ```bash
85
+ atlas create "Nations/Republic of Valora"
86
+ atlas create "Characters/Aria" "Characters/Bram" --type character
87
+ atlas create "Places/Old Harbor" --template location --open
88
+ ```
89
+
90
+ | Option | Description |
91
+ | --- | --- |
92
+ | `--type TYPE` | Sets `type` in the note's frontmatter, lowercased. |
93
+ | `-t`, `--template NAME` | Starts the note from a template in `.atlas/templates`. |
94
+ | `--open` | Opens the last created note in `$EDITOR`. |
95
+
96
+ ### `atlas list`
97
+
98
+ Prints a tree of every Markdown file in the vault. Hidden files and folders are skipped unless you pass `-a` / `--all`.
99
+
100
+ ### `atlas search QUERY`
101
+
102
+ Prints the vault-relative path of every note whose title contains the query. The match is case-insensitive and only looks at titles, not folders or note contents. Use `-a` / `--all` to include hidden directories such as the trash.
103
+
104
+ ```bash
105
+ atlas search valora
106
+ ```
107
+
108
+ ### `atlas move SOURCE [SOURCE ...] DESTINATION`
109
+
110
+ Moves or renames notes.
111
+
112
+ - With one source, the destination is the new path of the note.
113
+ - With several sources, the destination must be an existing folder.
114
+
115
+ ```bash
116
+ atlas move "Nations/Valora" "Nations/Republic of Valora"
117
+ atlas move "Characters/Aria" "Characters/Bram" Archive
118
+ ```
119
+
120
+ When a note moves, Atlas:
121
+
122
+ - Rewrites the note's frontmatter `id` to match its new path
123
+ - Updates wiki links in every note in the vault, including aliases (`[[Note|alias]]`), headings (`[[Note#Heading]]`), and embeds (`![[Note]]`)
124
+ - Leaves links inside code blocks and inline code untouched
125
+ - Leaves a bare-title link alone when several notes share that title, since it is unclear which one was meant
126
+
127
+ All moves are checked before anything changes, so a bad argument never leaves the vault half moved. Pass `--no-links` to skip the link updates.
128
+
129
+ ### `atlas delete NAME [NAME ...]`
130
+
131
+ Moves notes to the vault's `.trash` folder instead of erasing them. A timestamp is added to the file name, so deleting a note with the same name twice never overwrites the earlier copy.
132
+
133
+ ## Templates
134
+
135
+ Templates are Markdown files in `.atlas/templates`. Pass the name without the `.md` suffix:
136
+
137
+ ```bash
138
+ atlas create "Places/Old Harbor" --template location
139
+ ```
140
+
141
+ Templates can have frontmatter and a body, and `{{placeholders}}` are filled in when the note is created:
142
+
143
+ | Placeholder | Value |
144
+ | --- | --- |
145
+ | `{{title}}` | The note title (its file name without `.md`). |
146
+ | `{{id}}` | The generated note id. |
147
+ | `{{date}}` | Today's date as `YYYY-MM-DD`. |
148
+
149
+ Unknown placeholders are left as they are. Example `.atlas/templates/location.md`:
150
+
151
+ ```markdown
152
+ ---
153
+ type: location
154
+ created: "{{date}}"
155
+ tags: []
156
+ ---
157
+
158
+ # {{title}}
159
+
160
+ ## Description
161
+
162
+ ## Notable people
163
+ ```
164
+
165
+ The note `id` is always generated by Atlas, and a `--type` given on the command line overrides a `type` set in the template.
166
+
167
+ ## Vault layout
168
+
169
+ ```text
170
+ MyWorld/
171
+ ├── .atlas/
172
+ │ ├── config.toml
173
+ │ └── templates/
174
+ ├── .trash/
175
+ ├── Nations/
176
+ │ └── Republic of Valora.md
177
+ └── Characters/
178
+ └── Aria.md
179
+ ```
180
+
181
+ ## License
182
+
183
+ MIT
@@ -0,0 +1,165 @@
1
+ # Atlas
2
+
3
+ A command-line tool for managing Markdown worldbuilding vaults, built with Obsidian in mind.
4
+
5
+ Atlas creates, moves, deletes, lists, and searches notes in a vault of plain Markdown files. It keeps note ids and `[[wiki links]]` consistent when you reorganize, so renaming a note never leaves broken links behind.
6
+
7
+ ## Features
8
+
9
+ - Create notes from templates, with frontmatter written for you
10
+ - Move or rename notes and automatically update every wiki link that points at them
11
+ - Safe deletes: notes go to a trash folder instead of being erased
12
+ - Tree view of your vault and title search
13
+ - Works on plain `.md` files, so your vault stays usable in Obsidian or any other editor
14
+
15
+ ## Requirements
16
+
17
+ - Python 3.11 or newer
18
+
19
+ ## Installation
20
+
21
+
22
+
23
+ ## Quick start
24
+
25
+ ```bash
26
+ cd ~/Documents/MyWorld
27
+ atlas init
28
+ atlas create "Nations/Republic of Valora" --type Nation
29
+ atlas list
30
+ ```
31
+
32
+ `atlas init` marks a folder as an Atlas vault by creating a `.atlas` directory inside it. Every other command needs that directory to exist.
33
+
34
+ ## Finding your vault
35
+
36
+ Atlas looks for a vault in this order:
37
+
38
+ 1. The path given with `-v` / `--vault`
39
+ 2. The current directory, then up to five parent directories, stopping at the first one that contains `.atlas`
40
+
41
+ So you can run commands from any subfolder of your vault without passing a path. Global options go before the command:
42
+
43
+ ```bash
44
+ atlas --vault ~/Documents/MyWorld list
45
+ atlas --quiet create "Characters/Aria"
46
+ ```
47
+
48
+ | Option | Description |
49
+ | --- | --- |
50
+ | `-v`, `--vault PATH` | Path to the vault. |
51
+ | `--quiet` | Silence messages from Atlas. |
52
+ | `--version` | Show the Atlas version. |
53
+
54
+ ## Commands
55
+
56
+ Note names are paths relative to the vault root. The `.md` extension is optional.
57
+
58
+ ### `atlas init`
59
+
60
+ Initializes the current directory (or `--vault`) as an Atlas vault. This creates `.atlas/` directory.
61
+
62
+ ### `atlas create NAME [NAME ...]`
63
+
64
+ Creates one or more notes. Missing folders are created for you. Each new note gets an `id` in its frontmatter, generated from its path, and a heading with its title.
65
+
66
+ ```bash
67
+ atlas create "Nations/Republic of Valora"
68
+ atlas create "Characters/Aria" "Characters/Bram" --type character
69
+ atlas create "Places/Old Harbor" --template location --open
70
+ ```
71
+
72
+ | Option | Description |
73
+ | --- | --- |
74
+ | `--type TYPE` | Sets `type` in the note's frontmatter, lowercased. |
75
+ | `-t`, `--template NAME` | Starts the note from a template in `.atlas/templates`. |
76
+ | `--open` | Opens the last created note in `$EDITOR`. |
77
+
78
+ ### `atlas list`
79
+
80
+ Prints a tree of every Markdown file in the vault. Hidden files and folders are skipped unless you pass `-a` / `--all`.
81
+
82
+ ### `atlas search QUERY`
83
+
84
+ Prints the vault-relative path of every note whose title contains the query. The match is case-insensitive and only looks at titles, not folders or note contents. Use `-a` / `--all` to include hidden directories such as the trash.
85
+
86
+ ```bash
87
+ atlas search valora
88
+ ```
89
+
90
+ ### `atlas move SOURCE [SOURCE ...] DESTINATION`
91
+
92
+ Moves or renames notes.
93
+
94
+ - With one source, the destination is the new path of the note.
95
+ - With several sources, the destination must be an existing folder.
96
+
97
+ ```bash
98
+ atlas move "Nations/Valora" "Nations/Republic of Valora"
99
+ atlas move "Characters/Aria" "Characters/Bram" Archive
100
+ ```
101
+
102
+ When a note moves, Atlas:
103
+
104
+ - Rewrites the note's frontmatter `id` to match its new path
105
+ - Updates wiki links in every note in the vault, including aliases (`[[Note|alias]]`), headings (`[[Note#Heading]]`), and embeds (`![[Note]]`)
106
+ - Leaves links inside code blocks and inline code untouched
107
+ - Leaves a bare-title link alone when several notes share that title, since it is unclear which one was meant
108
+
109
+ All moves are checked before anything changes, so a bad argument never leaves the vault half moved. Pass `--no-links` to skip the link updates.
110
+
111
+ ### `atlas delete NAME [NAME ...]`
112
+
113
+ Moves notes to the vault's `.trash` folder instead of erasing them. A timestamp is added to the file name, so deleting a note with the same name twice never overwrites the earlier copy.
114
+
115
+ ## Templates
116
+
117
+ Templates are Markdown files in `.atlas/templates`. Pass the name without the `.md` suffix:
118
+
119
+ ```bash
120
+ atlas create "Places/Old Harbor" --template location
121
+ ```
122
+
123
+ Templates can have frontmatter and a body, and `{{placeholders}}` are filled in when the note is created:
124
+
125
+ | Placeholder | Value |
126
+ | --- | --- |
127
+ | `{{title}}` | The note title (its file name without `.md`). |
128
+ | `{{id}}` | The generated note id. |
129
+ | `{{date}}` | Today's date as `YYYY-MM-DD`. |
130
+
131
+ Unknown placeholders are left as they are. Example `.atlas/templates/location.md`:
132
+
133
+ ```markdown
134
+ ---
135
+ type: location
136
+ created: "{{date}}"
137
+ tags: []
138
+ ---
139
+
140
+ # {{title}}
141
+
142
+ ## Description
143
+
144
+ ## Notable people
145
+ ```
146
+
147
+ The note `id` is always generated by Atlas, and a `--type` given on the command line overrides a `type` set in the template.
148
+
149
+ ## Vault layout
150
+
151
+ ```text
152
+ MyWorld/
153
+ ├── .atlas/
154
+ │ ├── config.toml
155
+ │ └── templates/
156
+ ├── .trash/
157
+ ├── Nations/
158
+ │ └── Republic of Valora.md
159
+ └── Characters/
160
+ └── Aria.md
161
+ ```
162
+
163
+ ## License
164
+
165
+ MIT
@@ -0,0 +1,18 @@
1
+ # atlas/__init__.py
2
+
3
+ """
4
+ Atlas: a command-line tool for managing and validating worldbuilding vaults.
5
+
6
+ Attributes:
7
+ __version__: The installed version of Atlas, read from package metadata.
8
+ Falls back to a development version when Atlas is not installed.
9
+ """
10
+
11
+ from importlib.metadata import PackageNotFoundError, version
12
+
13
+ try:
14
+ __version__: str = version("atlas-vault")
15
+ except PackageNotFoundError:
16
+ __version__: str = "0.1.0-dev"
17
+
18
+ __all__ = ["__version__"]
@@ -0,0 +1,10 @@
1
+ # atlas/__main__.py
2
+
3
+ """Enables running Atlas with ``python -m atlas``."""
4
+
5
+ import sys
6
+
7
+ from atlas.main import main
8
+
9
+ if __name__ == "__main__":
10
+ sys.exit(main())
@@ -0,0 +1,21 @@
1
+ # atlas/commands/__init__.py
2
+
3
+ """Atlas subcommands."""
4
+
5
+ from atlas.commands import create_cmd, delete_cmd, init_cmd, list_cmd, move_cmd, search_cmd
6
+ from atlas.commands._types import SubParsers
7
+
8
+
9
+ def register_all(subparsers: SubParsers) -> None:
10
+ """
11
+ Registers every Atlas subcommand.
12
+
13
+ Args:
14
+ subparsers: The subparsers action to add the commands to.
15
+ """
16
+ init_cmd.register(subparsers)
17
+ create_cmd.register(subparsers)
18
+ list_cmd.register(subparsers)
19
+ delete_cmd.register(subparsers)
20
+ move_cmd.register(subparsers)
21
+ search_cmd.register(subparsers)
@@ -0,0 +1,82 @@
1
+ # atlas/commands/_types.py
2
+
3
+ """Shared typing helpers for Atlas subcommands."""
4
+
5
+ import argparse
6
+ from typing import Any, Protocol
7
+
8
+ import yaml
9
+
10
+
11
+ class SubParsers(Protocol):
12
+ """
13
+ The part of an argparse subparsers action that commands rely on.
14
+
15
+ This mirrors ``argparse._SubParsersAction`` without importing the private
16
+ class. Anything with a compatible ``add_parser`` method satisfies it.
17
+ """
18
+
19
+ def add_parser(
20
+ self,
21
+ name: str,
22
+ *,
23
+ help: str | None = None, # noqa: A002
24
+ ) -> argparse.ArgumentParser:
25
+ """Adds a named subparser and returns it."""
26
+ ...
27
+
28
+
29
+ def non_empty(value: str) -> str:
30
+ """
31
+ Strips a command-line value and rejects it if nothing is left.
32
+
33
+ Args:
34
+ value: The raw value from the command line.
35
+
36
+ Returns:
37
+ The stripped value.
38
+
39
+ Raises:
40
+ argparse.ArgumentTypeError: If the value is empty or only whitespace.
41
+ """
42
+ stripped: str = value.strip()
43
+ if not stripped:
44
+ raise argparse.ArgumentTypeError("must not be empty")
45
+ return stripped
46
+
47
+
48
+ def key_value(value: str) -> tuple[str, Any]:
49
+ """
50
+ Parses a ``KEY=VALUE`` command-line argument.
51
+
52
+ The text is split at the first ``=``. The key is stripped of surrounding
53
+ whitespace. The value is parsed as YAML, so ``2000000`` becomes an int,
54
+ ``true`` a bool, ``[a, b]`` a list, and ``draft`` stays a string. Quote
55
+ the value inside the argument to force a string, as in ``name="'123'"``.
56
+ An empty value becomes ``None``.
57
+
58
+ Args:
59
+ value: The raw ``KEY=VALUE`` argument.
60
+
61
+ Returns:
62
+ The key and the parsed value.
63
+
64
+ Raises:
65
+ argparse.ArgumentTypeError: If there is no ``=``, the key is empty
66
+ or is ``id``, or the value is not valid YAML.
67
+ """
68
+ key, separator, raw_value = value.partition("=")
69
+ key = key.strip()
70
+ if not separator or not key:
71
+ raise argparse.ArgumentTypeError(f"expected KEY=VALUE, got {value!r}")
72
+ if key == "id":
73
+ raise argparse.ArgumentTypeError("'id' is set from the note path and cannot be changed")
74
+
75
+ try:
76
+ parsed: Any = yaml.safe_load(raw_value)
77
+ except yaml.YAMLError as error:
78
+ problem: str = getattr(error, "problem", None) or "invalid YAML"
79
+ raise argparse.ArgumentTypeError(
80
+ f"value for {key!r} is not valid YAML ({problem}); quote it to use it as text"
81
+ ) from error
82
+ return key, parsed
@@ -0,0 +1,187 @@
1
+ # atlas/commands/create_cmd.py
2
+
3
+ """The ``atlas create`` command."""
4
+
5
+ import argparse
6
+ import os
7
+ import shlex
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ from atlas import frontmatter
14
+ from atlas.commands._types import SubParsers, non_empty
15
+ from atlas.errors import FileReadError, InvalidNoteIdError
16
+ from atlas.vault import Vault
17
+
18
+ if TYPE_CHECKING:
19
+ from atlas.note import Note
20
+
21
+
22
+ def register(subparsers: SubParsers) -> None:
23
+ """
24
+ Registers the ``create`` subcommand.
25
+
26
+ Args:
27
+ subparsers: The subparsers action to add the command to.
28
+ """
29
+ parser: argparse.ArgumentParser = subparsers.add_parser(
30
+ "create", help="Create a new Atlas Object."
31
+ )
32
+
33
+ parser.add_argument(
34
+ "names",
35
+ nargs="+",
36
+ type=str,
37
+ help=(
38
+ "Path(s) of the new item(s) relative to the vault root, e.g. "
39
+ "'Nations/Republic of Valora'. The '.md' extension is optional."
40
+ ),
41
+ )
42
+
43
+ parser.add_argument(
44
+ "--type",
45
+ type=non_empty,
46
+ metavar="TYPE",
47
+ help=(
48
+ "Set the type of the new note, e.g. 'Nation'. Written to its frontmatter. "
49
+ "Shorthand for --set type=<lowercased TYPE>."
50
+ ),
51
+ )
52
+
53
+ parser.add_argument(
54
+ "--set",
55
+ dest="properties",
56
+ action="append",
57
+ metavar="KEY=VALUE",
58
+ help=(
59
+ "Set a frontmatter property, e.g. --set status=draft. The value is parsed "
60
+ "as YAML, so numbers, booleans and lists work: --set population=2000000 "
61
+ "--set 'tags=[nation, active]'. Repeatable. Overrides --type and the template."
62
+ ),
63
+ )
64
+
65
+ parser.add_argument(
66
+ "--id",
67
+ type=non_empty,
68
+ metavar="ID",
69
+ dest="note_id",
70
+ help=(
71
+ "Use this ID instead of the one derived from the note's path. "
72
+ "Only valid when creating a single note."
73
+ ),
74
+ )
75
+
76
+ parser.add_argument(
77
+ "-b",
78
+ "--body",
79
+ metavar="TEXT",
80
+ help=(
81
+ "Seed the note body with this text instead of the default heading or the "
82
+ "template body. Use '-' to read the body from standard input. When creating "
83
+ "several notes, every note gets the same body."
84
+ ),
85
+ )
86
+
87
+ parser.add_argument(
88
+ "-t",
89
+ "--template",
90
+ type=non_empty,
91
+ metavar="NAME",
92
+ help="Name of a template in .atlas/templates to start the note from.",
93
+ )
94
+
95
+ parser.add_argument(
96
+ "--open",
97
+ action="store_true",
98
+ help="Open the note (or last note) in the users $EDITOR. Does not open if no $EDITOR.",
99
+ )
100
+
101
+ parser.set_defaults(handler=run)
102
+
103
+
104
+ def _read_stdin() -> str:
105
+ """
106
+ Reads all of standard input as text.
107
+
108
+ Returns:
109
+ The text that was piped in.
110
+
111
+ Raises:
112
+ FileReadError: If standard input cannot be read or is not valid text.
113
+ """
114
+ try:
115
+ return sys.stdin.read()
116
+ except (OSError, UnicodeDecodeError) as error:
117
+ reason: str = getattr(error, "strerror", None) or str(error)
118
+ raise FileReadError(Path("<stdin>"), reason) from error
119
+
120
+
121
+ def run(args: argparse.Namespace) -> int:
122
+ """
123
+ Runs the ``create`` command.
124
+
125
+ Args:
126
+ args: The parsed command-line arguments.
127
+
128
+ Returns:
129
+ The process exit code. Zero indicates success.
130
+
131
+ Raises:
132
+ VaultNotFoundError: If the vault path does not exist.
133
+ VaultNotInitializedError: If the vault has no ``.atlas`` directory.
134
+ InvalidPropertyError: If a ``--set`` property is not valid.
135
+ InvalidNoteNameError: If the note name is not valid.
136
+ InvalidTemplateNameError: If the template name is not valid.
137
+ TemplateNotFoundError: If the template does not exist.
138
+ FileReadError: If the template cannot be read.
139
+ InvalidFrontmatterError: If the template's frontmatter is not valid.
140
+ NoteExistsError: If the note already exists.
141
+ FileCreationError: If the note or its parent folders cannot be created.
142
+ """
143
+ vault: Vault = Vault.open(args.vault)
144
+
145
+ if args.note_id is not None and len(args.names) > 1:
146
+ raise InvalidNoteIdError(args.note_id, "--id can only be used when creating one note")
147
+
148
+ properties: dict[str, Any] = {}
149
+ if args.type is not None:
150
+ properties["type"] = args.type.lower()
151
+
152
+ raw_properties: list[str] = args.properties or []
153
+ for assignment in raw_properties:
154
+ key, value = frontmatter.parse_property(assignment)
155
+ properties[key] = value
156
+
157
+ body: str | None = args.body
158
+ if body == "-":
159
+ body = _read_stdin()
160
+
161
+ last_note: Note | None = None
162
+
163
+ for name in args.names:
164
+ new_note: Note = vault.resolve_note(name)
165
+ vault.create(
166
+ new_note,
167
+ properties=properties,
168
+ template=args.template,
169
+ note_id=args.note_id,
170
+ body=body,
171
+ )
172
+ last_note = new_note
173
+
174
+ if not args.quiet:
175
+ print("Created Note:", new_note.path)
176
+
177
+ if args.open and last_note is not None:
178
+ editor: str | None = os.environ.get("EDITOR", None)
179
+ if editor:
180
+ try:
181
+ cmd: list[str] = [*shlex.split(editor), str(last_note.path)]
182
+ subprocess.call(cmd) # noqa: S603
183
+ except FileNotFoundError:
184
+ print(f"Editor '{editor}' not found.")
185
+ return 0
186
+
187
+ return 0