Skip to content

Commit 9d2b676

Browse files
committed
Simplify /note to uv-run CLI tool
- Replace TypeScript tool with simple Python script - Location: .opencode/bin/note (executable) - Commands: create, save, list, search - Output: JSON for model to present - No dependencies, no pyproject.toml changes
1 parent 425d9a5 commit 9d2b676

5 files changed

Lines changed: 314 additions & 432 deletions

File tree

.opencode/bin/note

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#!/usr/bin/env python3
2+
"""note - Simple CLI for structured notes.
3+
4+
Usage:
5+
uv run note create --content "..."
6+
uv run note save --content "..." --filename "name"
7+
uv run note list
8+
uv run note search --query "..."
9+
"""
10+
import json
11+
import os
12+
import re
13+
import sys
14+
from datetime import datetime
15+
from pathlib import Path
16+
17+
NOTES_DIR = Path(".knowledge/notes")
18+
19+
20+
def generate_id():
21+
date = datetime.now().strftime("%Y-%m-%d")
22+
random = os.urandom(3).hex()[:6]
23+
return f"{date}-{random}"
24+
25+
26+
def slugify(title: str) -> str:
27+
return re.sub(r"[^a-z0-9]+", "-", title.lower()).strip("-")
28+
29+
30+
def suggest_tags(content: str) -> list[str]:
31+
content_lower = content.lower()
32+
tags = set()
33+
patterns = {
34+
"auth": ["auth", "oauth", "login"],
35+
"api": ["api", "endpoint"],
36+
"design": ["design", "architecture"],
37+
"bug": ["bug", "error", "fix"],
38+
}
39+
for tag, keywords in patterns.items():
40+
if any(kw in content_lower for kw in keywords):
41+
tags.add(tag)
42+
return sorted(tags)
43+
44+
45+
def create(args):
46+
content = args.get("--content") or args.get("-c")
47+
title = args.get("--title") or args.get("-t")
48+
49+
if not content:
50+
print("Error: --content required", file=sys.stderr)
51+
sys.exit(1)
52+
53+
if not title:
54+
first = content.split("\n")[0].strip()
55+
title = first if len(first) < 80 else " ".join(content.split()[:5]) + "..."
56+
57+
tags = suggest_tags(content)
58+
note_id = generate_id()
59+
filename = slugify(title) or f"note-{note_id}"
60+
61+
result = {
62+
"id": note_id,
63+
"title": title,
64+
"created": datetime.now().strftime("%Y-%m-%d"),
65+
"type": "note",
66+
"tags": tags,
67+
"filename": filename,
68+
}
69+
70+
print(json.dumps(result, indent=2))
71+
72+
73+
def save(args):
74+
content = args.get("--content") or args.get("-c")
75+
filename = args.get("--filename") or args.get("-f")
76+
77+
if not content or not filename:
78+
print("Error: --content and --filename required", file=sys.stderr)
79+
sys.exit(1)
80+
81+
NOTES_DIR.mkdir(parents=True, exist_ok=True)
82+
filepath = NOTES_DIR / f"{filename}.md"
83+
84+
if filepath.exists():
85+
print(f'{{"error": "File exists: {filepath}"}}')
86+
sys.exit(1)
87+
88+
filepath.write_text(content)
89+
print(json.dumps({"saved": str(filepath)}))
90+
91+
92+
def list_notes(args):
93+
if not NOTES_DIR.exists():
94+
print(json.dumps({"notes": [], "count": 0}))
95+
return
96+
97+
notes = sorted([f.name for f in NOTES_DIR.glob("*.md")], reverse=True)
98+
print(json.dumps({"notes": notes, "count": len(notes)}))
99+
100+
101+
def search(args):
102+
query = args.get("--query") or args.get("-q")
103+
if not query:
104+
print("Error: --query required", file=sys.stderr)
105+
sys.exit(1)
106+
107+
results = []
108+
if NOTES_DIR.exists():
109+
for f in NOTES_DIR.glob("*.md"):
110+
if query.lower() in f.read_text().lower():
111+
results.append(f.name)
112+
113+
print(json.dumps({"results": sorted(results, reverse=True)}))
114+
115+
116+
def parse_args():
117+
args = {}
118+
i = 1
119+
while i < len(sys.argv):
120+
if sys.argv[i].startswith("--") or sys.argv[i].startswith("-"):
121+
key = sys.argv[i]
122+
if i + 1 < len(sys.argv) and not sys.argv[i + 1].startswith("-"):
123+
args[key] = sys.argv[i + 1]
124+
i += 2
125+
else:
126+
args[key] = True
127+
i += 1
128+
else:
129+
args["_command"] = sys.argv[i]
130+
i += 1
131+
return args
132+
133+
134+
def main():
135+
if len(sys.argv) < 2:
136+
print(__doc__)
137+
sys.exit(1)
138+
139+
command = sys.argv[1]
140+
args = parse_args()
141+
142+
commands = {
143+
"create": create,
144+
"save": save,
145+
"list": list_notes,
146+
"search": search,
147+
}
148+
149+
if command not in commands:
150+
print(f"Unknown command: {command}")
151+
print(__doc__)
152+
sys.exit(1)
153+
154+
commands[command](args)
155+
156+
157+
if __name__ == "__main__":
158+
main()

.opencode/commands/note.md

Lines changed: 36 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,64 @@
11
# /note Command
22

3-
Transform raw thoughts into structured, Obsidian-compatible notes.
3+
Create structured notes from raw thoughts.
44

55
## Usage
66

77
```
88
/note <your raw thoughts>
99
```
1010

11-
## Flow
11+
## How It Works
1212

13-
1. **Create** - User provides raw content
14-
2. **Structure** - Tool structures into YAML + Markdown with tags and [[links]]
15-
3. **Review** - Model presents formatted note
16-
4. **Iterate** - User can request modifications (re-runs tool)
17-
5. **Save** - User says "save" → Tool writes to file
13+
The model runs the `note` CLI tool:
1814

19-
## Tool Actions
15+
1. **Create**: `uv run note create --content "..."`
16+
2. **Review**: Model presents structured note
17+
3. **Iterate**: Re-run with modifications if needed
18+
4. **Save**: `uv run note save --content "..." --filename "name"`
2019

21-
### `create` - Structure content
20+
## CLI Tool
2221

23-
```typescript
24-
note_tool(
25-
action: "create",
26-
content: "your raw thoughts...",
27-
mode: "draft", // Returns formatted note for review
28-
title?: string, // Optional title override
29-
tags?: string, // Comma-separated tags
30-
modifications?: string // Instructions for changes
31-
)
32-
```
22+
**Location**: `.opencode/bin/note`
3323

34-
Returns:
35-
- `frontmatter` - YAML metadata (id, title, created, type, tags)
36-
- `body` - Structured markdown body
37-
- `suggested_tags` - Auto-detected tags
38-
- `suggested_links` - Detected [[WikiLinks]]
39-
- `filename_suggestion` - Auto-generated filename
40-
41-
### `create` with `mode: "finalize"` - Save directly
42-
43-
```typescript
44-
note_tool(
45-
action: "create",
46-
content: "...",
47-
mode: "finalize",
48-
filename?: string // Optional, auto-generated if not provided
49-
)
50-
```
24+
**Commands**:
5125

52-
### Other actions
26+
```bash
27+
# Structure content into note
28+
uv run note create --content "Your thoughts..."
5329

54-
- `save` - Write pre-formatted content directly
55-
- `list` - Show existing notes
56-
- `search` - Search note contents
30+
# Save to file
31+
uv run note save --content "..." --filename "name"
5732

58-
## Example Session
33+
# List existing notes
34+
uv run note list
5935

36+
# Search notes
37+
uv run note search --query "auth"
6038
```
61-
User: /note "I was thinking about the auth flow. We need to handle OAuth2
62-
and API keys. Error handling is important but I'm not sure
63-
where it fits."
64-
65-
Model: [Calls note_tool(action="create", content="...", mode="draft")]
66-
67-
---
68-
id: 2026-03-27-a1b2c3
69-
title: Auth Flow Design Thoughts
70-
created: 2026-03-27
71-
type: note
72-
tags:
73-
- auth
74-
- oauth
75-
- api
76-
---
77-
78-
## Auth Flow Design Thoughts
79-
80-
## Authentication Methods
81-
We need to handle OAuth2 and API keys for the new feature.
82-
83-
## Error Handling Questions
84-
Error handling is important but I'm not sure where it fits.
85-
86-
**Suggested Tags:** auth, oauth, api
87-
**Suggested Links:** [[OAuth2]], [[API Keys]], [[Error Handling]]
88-
**Suggested Filename:** auth-flow-design-thoughts
8939

90-
Want me to save this, or would you like modifications?
40+
## Example
9141

92-
User: Add a section about OAuth2 implementation details
93-
94-
Model: [Calls note_tool(action="create", content="...", mode="draft",
95-
modifications="Add section about OAuth2 implementation")]
96-
97-
[Returns updated note with new section]
98-
99-
User: Save as auth-design
100-
101-
Model: [Calls note_tool(action="create", content="...", mode="finalize",
102-
filename="auth-design")]
103-
104-
✅ Saved to .knowledge/notes/auth-design.md
10542
```
43+
User: /note "I was thinking about auth flow. We need OAuth2."
10644
107-
## How It Works
108-
109-
The **tool** does all the work:
110-
- Parses raw content
111-
- Extracts/generates title
112-
- Structures into sections (## headings)
113-
- Generates YAML frontmatter
114-
- Suggests tags based on keywords
115-
- Identifies [[WikiLinks]]
116-
- Formats Obsidian-compatible output
45+
Model: uv run note create --content "I was thinking about auth flow..."
11746
118-
The **model** just:
119-
- Calls the tool
120-
- Presents the result
121-
- Handles user feedback
122-
- Re-calls tool for modifications
47+
Output:
48+
{
49+
"id": "2026-03-27-abc123",
50+
"title": "Auth Flow Thoughts",
51+
"created": "2026-03-27",
52+
"type": "note",
53+
"tags": ["auth"],
54+
"filename": "auth-flow-thoughts"
55+
}
12356
124-
## Output Location
57+
Model: Here's your note with tags: auth. Save as "auth-flow-thoughts"?
12558
126-
All notes saved to: `.knowledge/notes/<filename>.md`
59+
User: Yes
12760
128-
## Related
61+
Model: uv run note save --content "..." --filename "auth-flow-thoughts"
62+
```
12963

130-
- [[.knowledge]] - Knowledge directory
131-
- [[/research]] - Deep research (requires workflow state)
132-
- [[/plan]] - Structured planning (requires workflow state)
64+
Notes saved to: `.knowledge/notes/<filename>.md`

0 commit comments

Comments
 (0)