opencode-skills-collection 3.0.39 → 3.0.41

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.
Files changed (40) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +6 -1
  2. package/bundled-skills/2slides-ppt-generator/SKILL.md +12 -2
  3. package/bundled-skills/2slides-ppt-generator/requirements.txt +1 -0
  4. package/bundled-skills/2slides-ppt-generator/scripts/create_pdf_slides.py +1 -1
  5. package/bundled-skills/accesslint-diff/SKILL.md +4 -1
  6. package/bundled-skills/article-illustrations/SKILL.md +159 -0
  7. package/bundled-skills/cv-generator/SKILL.md +874 -0
  8. package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
  9. package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
  10. package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
  11. package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
  12. package/bundled-skills/docs/sources/sources.md +1 -0
  13. package/bundled-skills/docs/users/bundles.md +1 -1
  14. package/bundled-skills/docs/users/claude-code-skills.md +1 -1
  15. package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
  16. package/bundled-skills/docs/users/getting-started.md +1 -1
  17. package/bundled-skills/docs/users/kiro-integration.md +1 -1
  18. package/bundled-skills/docs/users/usage.md +4 -4
  19. package/bundled-skills/docs/users/visual-guide.md +4 -4
  20. package/bundled-skills/examprep-ai/SKILL.md +8 -0
  21. package/bundled-skills/hugging-face-cli/SKILL.md +2 -2
  22. package/bundled-skills/open-dynamic-workflows/SKILL.md +101 -0
  23. package/bundled-skills/permission-manager/README.md +1 -1
  24. package/bundled-skills/permission-manager/SKILL.md +2 -1
  25. package/bundled-skills/polis-protocol/SKILL.md +13 -4
  26. package/bundled-skills/runapi-cli/SKILL.md +140 -0
  27. package/bundled-skills/schema-markup-generator/SKILL.md +2 -1
  28. package/bundled-skills/smart-git-automation/SKILL.md +4 -2
  29. package/bundled-skills/user-thoughts/scripts/common.py +93 -6
  30. package/bundled-skills/user-thoughts/scripts/ignore_ops.py +20 -20
  31. package/bundled-skills/user-thoughts/scripts/init.py +3 -1
  32. package/bundled-skills/user-thoughts/scripts/show_mdbase.py +5 -5
  33. package/bundled-skills/user-thoughts/scripts/show_raw.py +3 -3
  34. package/bundled-skills/user-thoughts/scripts/sortin.py +37 -26
  35. package/bundled-skills/user-thoughts/scripts/status.py +8 -8
  36. package/bundled-skills/user-thoughts/scripts/write_raw.py +20 -11
  37. package/bundled-skills/vercel-cli-with-tokens/SKILL.md +15 -12
  38. package/bundled-skills/video-content-extractor/SKILL.md +103 -0
  39. package/package.json +1 -1
  40. package/skills_index.json +119 -7
@@ -3,13 +3,20 @@ import re
3
3
  from pathlib import Path
4
4
 
5
5
 
6
+ class UsthtSafetyError(RuntimeError):
7
+ """Raised when a runtime path would escape .ustht/."""
8
+
9
+
6
10
  def find_ustht() -> Path | None:
7
11
  """Find .ustht/ in the current directory or one of its parents."""
8
12
  cwd = Path.cwd()
9
13
  for d in [cwd, *cwd.parents]:
10
14
  ustht = d / ".ustht"
11
- if ustht.is_dir():
12
- return ustht
15
+ if ustht.exists():
16
+ if ustht.is_symlink():
17
+ raise UsthtSafetyError(f"Refusing symlinked runtime directory: {ustht}")
18
+ if ustht.is_dir():
19
+ return ustht.resolve()
13
20
  return None
14
21
 
15
22
 
@@ -28,7 +35,7 @@ def read_define_ini(ustht: Path) -> dict:
28
35
  if not ini.exists():
29
36
  return {}
30
37
  result = {}
31
- for line in ini.read_text(encoding="utf-8").splitlines():
38
+ for line in safe_read_text(ustht, ini).splitlines():
32
39
  line = line.strip()
33
40
  if "=" in line and not line.startswith("#"):
34
41
  k, v = line.split("=", 1)
@@ -40,15 +47,95 @@ def write_define_ini(ustht: Path, cfg: dict):
40
47
  """Replace define.ini with the provided key/value pairs."""
41
48
  ini = ustht / "define.ini"
42
49
  lines = [f"{k}={v}" for k, v in cfg.items()]
43
- ini.write_text("\n".join(lines) + "\n", encoding="utf-8")
50
+ safe_write_text(ustht, ini, "\n".join(lines) + "\n")
44
51
 
45
52
 
46
- def is_processed(filepath: Path) -> bool:
53
+ def is_processed(filepath: Path, ustht: Path | None = None) -> bool:
47
54
  """Return true when the first raw-file line is the processed marker."""
48
- first_line = filepath.read_text(encoding="utf-8").split("\n", 1)[0].strip()
55
+ content = safe_read_text(ustht, filepath) if ustht else filepath.read_text(encoding="utf-8")
56
+ first_line = content.split("\n", 1)[0].strip()
49
57
  return first_line == "<!-- processed -->"
50
58
 
51
59
 
60
+ def ensure_runtime_path(ustht: Path, path: Path, *, must_exist: bool = False) -> Path:
61
+ """Return a path only if its real location stays inside .ustht/."""
62
+ base = Path(ustht)
63
+ if base.is_symlink():
64
+ raise UsthtSafetyError(f"Refusing symlinked runtime directory: {base}")
65
+ base_real = base.resolve(strict=True)
66
+ target = Path(path)
67
+ if not target.is_absolute():
68
+ target = base_real / target
69
+
70
+ if target.exists() and target.is_symlink():
71
+ raise UsthtSafetyError(f"Refusing symlinked runtime path: {target}")
72
+ if must_exist and not target.exists():
73
+ raise UsthtSafetyError(f"Runtime path does not exist: {target}")
74
+
75
+ target_real = target.resolve(strict=must_exist)
76
+ try:
77
+ target_real.relative_to(base_real)
78
+ except ValueError as exc:
79
+ raise UsthtSafetyError(f"Runtime path escapes .ustht/: {target}") from exc
80
+
81
+ rel = target.relative_to(base_real)
82
+ current = base_real
83
+ for part in rel.parts:
84
+ current = current / part
85
+ if current.exists() and current.is_symlink():
86
+ raise UsthtSafetyError(f"Refusing symlinked runtime path: {current}")
87
+ return target
88
+
89
+
90
+ def ensure_runtime_dir(ustht: Path, path: Path, *, create: bool = False) -> Path:
91
+ """Return a safe runtime directory, creating it when requested."""
92
+ directory = ensure_runtime_path(ustht, path, must_exist=False)
93
+ if create:
94
+ directory.mkdir(parents=True, exist_ok=True)
95
+ if directory.exists() and not directory.is_dir():
96
+ raise UsthtSafetyError(f"Runtime path is not a directory: {directory}")
97
+ return directory
98
+
99
+
100
+ def safe_read_text(ustht: Path | None, path: Path) -> str:
101
+ """Read a runtime file after symlink and containment checks."""
102
+ safe_path = ensure_runtime_path(ustht, path, must_exist=True) if ustht else path
103
+ return safe_path.read_text(encoding="utf-8")
104
+
105
+
106
+ def safe_write_text(ustht: Path, path: Path, content: str):
107
+ """Write a runtime file after symlink and containment checks."""
108
+ safe_path = ensure_runtime_path(ustht, path, must_exist=False)
109
+ ensure_runtime_dir(ustht, safe_path.parent, create=True)
110
+ safe_path.write_text(content, encoding="utf-8")
111
+
112
+
113
+ def safe_markdown_files(ustht: Path, directory: Path, *, reverse: bool = False) -> list[Path]:
114
+ """List safe markdown files under one runtime directory."""
115
+ safe_dir = ensure_runtime_dir(ustht, directory)
116
+ if not safe_dir.exists():
117
+ return []
118
+ files = []
119
+ for file_path in safe_dir.glob("*.md"):
120
+ safe_path = ensure_runtime_path(ustht, file_path, must_exist=True)
121
+ if safe_path.is_file():
122
+ files.append(safe_path)
123
+ return sorted(files, reverse=reverse)
124
+
125
+
126
+ def safe_markdown_tree(ustht: Path, directory: Path) -> list[Path]:
127
+ """List safe markdown files recursively under one runtime directory."""
128
+ safe_dir = ensure_runtime_dir(ustht, directory)
129
+ if not safe_dir.exists():
130
+ return []
131
+ files = []
132
+ for file_path in safe_dir.rglob("*.md"):
133
+ safe_path = ensure_runtime_path(ustht, file_path, must_exist=True)
134
+ if safe_path.is_file():
135
+ files.append(safe_path)
136
+ return sorted(files)
137
+
138
+
52
139
  def validate_dim_name(dim: str) -> bool:
53
140
  """Validate a dimension path made of safe kebab-case segments."""
54
141
  reserved = {"raw", "ignored", "export", "define", "readme-ai"}
@@ -3,7 +3,7 @@ import sys
3
3
  from datetime import datetime
4
4
  from pathlib import Path
5
5
 
6
- from common import find_ustht
6
+ from common import ensure_runtime_dir, find_ustht, safe_markdown_files, safe_read_text, safe_write_text
7
7
 
8
8
  HELP = """Usage: python ignore_ops.py show|remove_last|add_suffix "text" [--help]
9
9
 
@@ -14,11 +14,11 @@ Subcommands:
14
14
  """
15
15
 
16
16
 
17
- def find_last_raw_entry(raw_dir: Path):
17
+ def find_last_raw_entry(ustht: Path, raw_dir: Path):
18
18
  """Return (file path, line index, entry text) for the latest raw entry."""
19
- files = sorted(raw_dir.glob("*.md"), reverse=True)
19
+ files = safe_markdown_files(ustht, raw_dir, reverse=True)
20
20
  for f in files:
21
- lines = f.read_text(encoding="utf-8").splitlines()
21
+ lines = safe_read_text(ustht, f).splitlines()
22
22
  if lines and lines[0].strip() == "<!-- processed -->":
23
23
  continue
24
24
  for idx in range(len(lines) - 1, -1, -1):
@@ -27,16 +27,16 @@ def find_last_raw_entry(raw_dir: Path):
27
27
  return None, None, None
28
28
 
29
29
 
30
- def remove_line(filepath: Path, idx: int):
30
+ def remove_line(ustht: Path, filepath: Path, idx: int):
31
31
  """Remove one line from a file."""
32
- lines = filepath.read_text(encoding="utf-8").splitlines()
32
+ lines = safe_read_text(ustht, filepath).splitlines()
33
33
  del lines[idx]
34
- filepath.write_text("\n".join(lines) + ("\n" if lines else ""), encoding="utf-8")
34
+ safe_write_text(ustht, filepath, "\n".join(lines) + ("\n" if lines else ""))
35
35
 
36
36
 
37
- def append_to_ignored(ignored_dir: Path, text: str, reason: str):
37
+ def append_to_ignored(ustht: Path, ignored_dir: Path, text: str, reason: str):
38
38
  """Append one ignored entry to today's ignored file."""
39
- ignored_dir.mkdir(exist_ok=True)
39
+ ignored_dir = ensure_runtime_dir(ustht, ignored_dir, create=True)
40
40
  today = datetime.now().strftime("%Y-%m-%d")
41
41
  now = datetime.now().strftime("%H:%M")
42
42
  f = ignored_dir / f"{today}.md"
@@ -45,23 +45,23 @@ def append_to_ignored(ignored_dir: Path, text: str, reason: str):
45
45
  clean = clean.rsplit(" | suggested-dim:", 1)[0]
46
46
  entry = f"- [{now}] {clean} ({reason})"
47
47
  if f.exists():
48
- content = f.read_text(encoding="utf-8").rstrip()
49
- f.write_text(f"{content}\n{entry}\n", encoding="utf-8")
48
+ content = safe_read_text(ustht, f).rstrip()
49
+ safe_write_text(ustht, f, f"{content}\n{entry}\n")
50
50
  else:
51
- f.write_text(f"{entry}\n", encoding="utf-8")
51
+ safe_write_text(ustht, f, f"{entry}\n")
52
52
 
53
53
 
54
- def show_ignored(ignored_dir: Path):
54
+ def show_ignored(ustht: Path, ignored_dir: Path):
55
55
  """Print all ignored entries."""
56
56
  if not ignored_dir.exists():
57
57
  print("No ignored entries.")
58
58
  return
59
- files = sorted(ignored_dir.glob("*.md"), reverse=True)
59
+ files = safe_markdown_files(ustht, ignored_dir, reverse=True)
60
60
  if not files:
61
61
  print("No ignored entries.")
62
62
  return
63
63
  for f in files:
64
- entries = [line for line in f.read_text(encoding="utf-8").splitlines() if line.strip().startswith("- [")]
64
+ entries = [line for line in safe_read_text(ustht, f).splitlines() if line.strip().startswith("- [")]
65
65
  if entries:
66
66
  print(f"#{f.name} ({len(entries)} entries):")
67
67
  for entry in entries:
@@ -73,12 +73,12 @@ def remove_last(ustht: Path):
73
73
  if not raw_dir.exists():
74
74
  print("No previous thought to ignore.")
75
75
  return
76
- filepath, idx, entry = find_last_raw_entry(raw_dir)
76
+ filepath, idx, entry = find_last_raw_entry(ustht, raw_dir)
77
77
  if filepath is None:
78
78
  print("No previous thought to ignore.")
79
79
  return
80
- remove_line(filepath, idx)
81
- append_to_ignored(ustht / "ignored", entry, "ignored with --last")
80
+ remove_line(ustht, filepath, idx)
81
+ append_to_ignored(ustht, ustht / "ignored", entry, "ignored with --last")
82
82
  display = entry
83
83
  if "] " in display:
84
84
  display = display.split("] ", 1)[1]
@@ -88,7 +88,7 @@ def remove_last(ustht: Path):
88
88
 
89
89
 
90
90
  def add_suffix(ustht: Path, text: str):
91
- append_to_ignored(ustht / "ignored", text, "ignored by suffix")
91
+ append_to_ignored(ustht, ustht / "ignored", text, "ignored by suffix")
92
92
  print("Ignored current message.")
93
93
 
94
94
 
@@ -108,7 +108,7 @@ def main():
108
108
 
109
109
  cmd = sys.argv[1]
110
110
  if cmd == "show":
111
- show_ignored(ustht / "ignored")
111
+ show_ignored(ustht, ustht / "ignored")
112
112
  elif cmd == "remove_last":
113
113
  remove_last(ustht)
114
114
  elif cmd == "add_suffix":
@@ -3,7 +3,7 @@ import shutil
3
3
  import sys
4
4
  from pathlib import Path
5
5
 
6
- from common import find_skill_dir
6
+ from common import UsthtSafetyError, find_skill_dir
7
7
 
8
8
  HELP = """Usage: python init.py [--help]
9
9
 
@@ -34,6 +34,8 @@ def main():
34
34
 
35
35
  target = Path.cwd() / ".ustht"
36
36
  if target.exists():
37
+ if target.is_symlink():
38
+ raise UsthtSafetyError(f"Refusing symlinked runtime directory: {target}")
37
39
  print("Already initialized; .ustht/ exists, skipping creation.")
38
40
  sys.exit(0)
39
41
 
@@ -2,7 +2,7 @@
2
2
  import sys
3
3
  from pathlib import Path
4
4
 
5
- from common import find_ustht, validate_dim_name
5
+ from common import find_ustht, safe_markdown_tree, safe_read_text, validate_dim_name
6
6
 
7
7
  HELP = """Usage: python show_mdbase.py show [--all|--dimension] [--help]
8
8
 
@@ -18,14 +18,14 @@ def show_index(mdbase: Path):
18
18
  if not index.exists():
19
19
  print("mdbase/README.ai.md does not exist.")
20
20
  return
21
- print(index.read_text(encoding="utf-8"))
21
+ print(safe_read_text(mdbase.parent, index))
22
22
 
23
23
 
24
24
  def list_dims(mdbase: Path):
25
25
  details = mdbase / "details"
26
26
  if not details.exists():
27
27
  return []
28
- return sorted(p.relative_to(details).with_suffix("").as_posix() for p in details.rglob("*.md"))
28
+ return sorted(p.relative_to(details).with_suffix("").as_posix() for p in safe_markdown_tree(mdbase.parent, details))
29
29
 
30
30
 
31
31
  def show_dim(mdbase: Path, dim: str):
@@ -39,7 +39,7 @@ def show_dim(mdbase: Path, dim: str):
39
39
  if not path.exists():
40
40
  print(f"mdbase/details/{dim}.md does not exist yet.")
41
41
  return
42
- print(path.read_text(encoding="utf-8"))
42
+ print(safe_read_text(mdbase.parent, path))
43
43
 
44
44
 
45
45
  def show_all(mdbase: Path):
@@ -54,7 +54,7 @@ def show_all(mdbase: Path):
54
54
  print(f"mdbase has {len(dims)} dimensions:")
55
55
  for dim in dims:
56
56
  path = details / f"{dim}.md"
57
- lines = [line for line in path.read_text(encoding="utf-8").splitlines() if line.strip().startswith("- ")]
57
+ lines = [line for line in safe_read_text(mdbase.parent, path).splitlines() if line.strip().startswith("- ")]
58
58
  print(f" {dim}.md: {len(lines)} entries")
59
59
 
60
60
 
@@ -2,7 +2,7 @@
2
2
  import sys
3
3
  from pathlib import Path
4
4
 
5
- from common import find_ustht, is_processed
5
+ from common import find_ustht, is_processed, safe_markdown_files, safe_read_text
6
6
 
7
7
  HELP = """Usage: python show_raw.py [--help]
8
8
 
@@ -25,13 +25,13 @@ def main():
25
25
  print("No unprocessed records.")
26
26
  return
27
27
 
28
- files = [f for f in sorted(raw_dir.glob("*.md"), reverse=True) if not is_processed(f)]
28
+ files = [f for f in safe_markdown_files(ustht, raw_dir, reverse=True) if not is_processed(f, ustht)]
29
29
  if not files:
30
30
  print("No unprocessed records. All raw files are marked processed.")
31
31
  return
32
32
 
33
33
  for f in files:
34
- content = f.read_text(encoding="utf-8").strip()
34
+ content = safe_read_text(ustht, f).strip()
35
35
  entry_count = sum(1 for line in content.splitlines() if line.strip().startswith("- ["))
36
36
  print(f"#{f.name} ({entry_count} unprocessed entries):")
37
37
  print(content)
@@ -5,7 +5,18 @@ from collections import defaultdict
5
5
  from datetime import datetime
6
6
  from pathlib import Path
7
7
 
8
- from common import find_ustht, read_define_ini, write_define_ini, is_processed, validate_dim_name
8
+ from common import (
9
+ ensure_runtime_dir,
10
+ safe_markdown_files,
11
+ safe_markdown_tree,
12
+ safe_read_text,
13
+ safe_write_text,
14
+ find_ustht,
15
+ read_define_ini,
16
+ write_define_ini,
17
+ is_processed,
18
+ validate_dim_name,
19
+ )
9
20
 
10
21
  HELP = """Usage: python sortin.py [--dry] [--help]
11
22
 
@@ -18,7 +29,7 @@ Options:
18
29
  """
19
30
 
20
31
 
21
- def parse_raw_file(filepath: Path):
32
+ def parse_raw_file(ustht: Path, filepath: Path):
22
33
  """Parse raw entries from one file."""
23
34
  entries = []
24
35
  date = filepath.stem.split("-", 3)
@@ -27,7 +38,7 @@ def parse_raw_file(filepath: Path):
27
38
  else:
28
39
  date = datetime.now().strftime("%Y-%m-%d")
29
40
 
30
- for line in filepath.read_text(encoding="utf-8").splitlines():
41
+ for line in safe_read_text(ustht, filepath).splitlines():
31
42
  line = line.strip()
32
43
  match = re.match(r"^- \[(\d{2}:\d{2})\] (.*)$", line)
33
44
  if not match:
@@ -51,31 +62,31 @@ def dim_path(mdbase: Path, dim: str) -> Path:
51
62
  return mdbase / "details" / f"{dim}.md"
52
63
 
53
64
 
54
- def count_entries(path: Path) -> int:
65
+ def count_entries(ustht: Path, path: Path) -> int:
55
66
  if not path.exists():
56
67
  return 0
57
- return sum(1 for line in path.read_text(encoding="utf-8").splitlines() if line.strip().startswith("- "))
68
+ return sum(1 for line in safe_read_text(ustht, path).splitlines() if line.strip().startswith("- "))
58
69
 
59
70
 
60
- def append_entries(path: Path, entries):
71
+ def append_entries(ustht: Path, path: Path, entries):
61
72
  """Append entries grouped by date to one dimension file."""
62
73
  by_date = defaultdict(list)
63
74
  for entry in entries:
64
75
  by_date[entry["date"]].append(entry)
65
76
 
66
- path.parent.mkdir(parents=True, exist_ok=True)
77
+ ensure_runtime_dir(ustht, path.parent, create=True)
67
78
  if not path.exists():
68
79
  title = path.stem.replace("-", " ").title()
69
- path.write_text(f"# {title}\n\n> Project memory for `{path.stem}`.\n\n", encoding="utf-8")
80
+ safe_write_text(ustht, path, f"# {title}\n\n> Project memory for `{path.stem}`.\n\n")
70
81
 
71
- content = path.read_text(encoding="utf-8").rstrip()
82
+ content = safe_read_text(ustht, path).rstrip()
72
83
  for date, date_entries in sorted(by_date.items()):
73
84
  lines = [f"- {entry['text']}" for entry in date_entries]
74
85
  block = "\n".join(lines)
75
86
  heading = f"## {date}"
76
- if heading in content:
77
- content_lines = content.splitlines()
78
- heading_idx = next(i for i, line in enumerate(content_lines) if line.strip() == heading)
87
+ content_lines = content.splitlines()
88
+ heading_idx = next((i for i, line in enumerate(content_lines) if line.strip() == heading), None)
89
+ if heading_idx is not None:
79
90
  insert_idx = len(content_lines)
80
91
  for i in range(heading_idx + 1, len(content_lines)):
81
92
  if content_lines[i].startswith("## "):
@@ -92,31 +103,31 @@ def append_entries(path: Path, entries):
92
103
  content = "\n".join(before).rstrip()
93
104
  else:
94
105
  content = f"{content}\n\n{heading}\n\n{block}".rstrip()
95
- path.write_text(content + "\n", encoding="utf-8")
106
+ safe_write_text(ustht, path, content + "\n")
96
107
 
97
108
 
98
- def mark_processed(filepath: Path):
109
+ def mark_processed(ustht: Path, filepath: Path):
99
110
  """Insert the processed marker at the top of a raw file."""
100
- content = filepath.read_text(encoding="utf-8")
111
+ content = safe_read_text(ustht, filepath)
101
112
  if content.split("\n", 1)[0].strip() != "<!-- processed -->":
102
- filepath.write_text("<!-- processed -->\n" + content, encoding="utf-8")
113
+ safe_write_text(ustht, filepath, "<!-- processed -->\n" + content)
103
114
 
104
115
 
105
- def update_index(mdbase: Path):
116
+ def update_index(ustht: Path, mdbase: Path):
106
117
  """Rebuild mdbase/README.ai.md with dimension counts."""
107
118
  now = datetime.now().strftime("%Y-%m-%d %H:%M")
108
119
  details = mdbase / "details"
109
120
  dims = []
110
121
  if details.exists():
111
- dims = sorted(p.relative_to(details).with_suffix("").as_posix() for p in details.rglob("*.md"))
122
+ dims = sorted(p.relative_to(details).with_suffix("").as_posix() for p in safe_markdown_tree(ustht, details))
112
123
 
113
124
  rows = ["| File | Dimension | Entries |", "|------|-----------|---------|"]
114
125
  backlog = mdbase / "backlog.md"
115
126
  if backlog.exists():
116
- rows.append(f"| [backlog.md](backlog.md) | backlog | {count_entries(backlog)} |")
127
+ rows.append(f"| [backlog.md](backlog.md) | backlog | {count_entries(ustht, backlog)} |")
117
128
  for dim in dims:
118
129
  path = details / f"{dim}.md"
119
- rows.append(f"| [details/{dim}.md](details/{dim}.md) | {dim} | {count_entries(path)} |")
130
+ rows.append(f"| [details/{dim}.md](details/{dim}.md) | {dim} | {count_entries(ustht, path)} |")
120
131
 
121
132
  content = "\n".join([
122
133
  "# user-thoughts mdbase Index",
@@ -137,7 +148,7 @@ def update_index(mdbase: Path):
137
148
  *rows,
138
149
  "",
139
150
  ])
140
- (mdbase / "README.ai.md").write_text(content, encoding="utf-8")
151
+ safe_write_text(ustht, mdbase / "README.ai.md", content)
141
152
 
142
153
 
143
154
  def main():
@@ -161,7 +172,7 @@ def main():
161
172
  print("No unprocessed records.")
162
173
  return
163
174
 
164
- raw_files = [f for f in sorted(raw_dir.glob("*.md")) if not is_processed(f)]
175
+ raw_files = [f for f in safe_markdown_files(ustht, raw_dir) if not is_processed(f, ustht)]
165
176
  if not raw_files:
166
177
  print("No unprocessed records. All raw files are marked processed.")
167
178
  return
@@ -169,7 +180,7 @@ def main():
169
180
  all_entries = []
170
181
  entries_by_file = {}
171
182
  for f in raw_files:
172
- entries = parse_raw_file(f)
183
+ entries = parse_raw_file(ustht, f)
173
184
  entries_by_file[f] = entries
174
185
  all_entries.extend(entries)
175
186
 
@@ -194,16 +205,16 @@ def main():
194
205
  return
195
206
 
196
207
  for dim, entries in grouped.items():
197
- append_entries(dim_path(mdbase, dim), entries)
208
+ append_entries(ustht, dim_path(mdbase, dim), entries)
198
209
 
199
210
  for f in raw_files:
200
211
  if entries_by_file.get(f):
201
- mark_processed(f)
212
+ mark_processed(ustht, f)
202
213
 
203
214
  now = datetime.now().strftime("%Y-%m-%d %H:%M")
204
215
  cfg["LAST_SORTIN"] = now
205
216
  write_define_ini(ustht, cfg)
206
- update_index(mdbase)
217
+ update_index(ustht, mdbase)
207
218
  print(f" LAST_SORTIN updated to {now}")
208
219
 
209
220
 
@@ -2,7 +2,7 @@
2
2
  import sys
3
3
  from pathlib import Path
4
4
 
5
- from common import find_ustht, read_define_ini, is_processed
5
+ from common import find_ustht, read_define_ini, is_processed, safe_markdown_files, safe_markdown_tree
6
6
 
7
7
  HELP = """Usage: python status.py [--help]
8
8
 
@@ -11,21 +11,21 @@ dimension counts.
11
11
  """
12
12
 
13
13
 
14
- def count_raw(raw_dir: Path):
14
+ def count_raw(ustht: Path, raw_dir: Path):
15
15
  """Return total and unprocessed raw file counts."""
16
16
  if not raw_dir.exists():
17
17
  return 0, 0
18
- files = list(raw_dir.glob("*.md"))
19
- unprocessed = sum(1 for f in files if not is_processed(f))
18
+ files = safe_markdown_files(ustht, raw_dir)
19
+ unprocessed = sum(1 for f in files if not is_processed(f, ustht))
20
20
  return len(files), unprocessed
21
21
 
22
22
 
23
- def count_dims(mdbase: Path):
23
+ def count_dims(ustht: Path, mdbase: Path):
24
24
  """Count dimension files under mdbase/details/."""
25
25
  details = mdbase / "details"
26
26
  if not details.exists():
27
27
  return 0
28
- return len(list(details.rglob("*.md")))
28
+ return len(safe_markdown_tree(ustht, details))
29
29
 
30
30
 
31
31
  def main():
@@ -42,8 +42,8 @@ def main():
42
42
  skill_status = cfg.get("SKILL_STATUS", "unknown")
43
43
  instant_status = cfg.get("INSTANT_STATUS", "unknown")
44
44
  last_sortin = cfg.get("LAST_SORTIN", "never") or "never"
45
- total_raw, unprocessed_raw = count_raw(ustht / "raw")
46
- dims = count_dims(ustht / "mdbase")
45
+ total_raw, unprocessed_raw = count_raw(ustht, ustht / "raw")
46
+ dims = count_dims(ustht, ustht / "mdbase")
47
47
 
48
48
  print(f"SKILL_STATUS={skill_status}")
49
49
  print(f"INSTANT_STATUS={instant_status}")
@@ -3,7 +3,15 @@ import sys
3
3
  from datetime import datetime
4
4
  from pathlib import Path
5
5
 
6
- from common import find_ustht, read_define_ini, validate_dim_name
6
+ from common import (
7
+ ensure_runtime_dir,
8
+ safe_markdown_files,
9
+ safe_read_text,
10
+ safe_write_text,
11
+ find_ustht,
12
+ read_define_ini,
13
+ validate_dim_name,
14
+ )
7
15
 
8
16
  HELP = """Usage: python write_raw.py "thought text" [--dim dimension] [--help]
9
17
 
@@ -21,12 +29,14 @@ Behavior:
21
29
  """
22
30
 
23
31
 
24
- def count_today_raw(raw_dir: Path) -> int:
32
+ def count_today_raw(ustht: Path, raw_dir: Path) -> int:
25
33
  """Count unprocessed entries across today's raw files."""
26
34
  today = datetime.now().strftime("%Y-%m-%d")
27
35
  count = 0
28
- for f in sorted(raw_dir.glob(f"{today}*.md")):
29
- content = f.read_text(encoding="utf-8")
36
+ for f in safe_markdown_files(ustht, raw_dir):
37
+ if not f.name.startswith(today):
38
+ continue
39
+ content = safe_read_text(ustht, f)
30
40
  first_line = content.split("\n", 1)[0].strip()
31
41
  if first_line == "<!-- processed -->":
32
42
  continue
@@ -72,15 +82,14 @@ def main():
72
82
  print(f"Invalid dimension name: {dim}. Use lowercase letters, digits, hyphens, and optional / subdirectories.")
73
83
  sys.exit(1)
74
84
 
75
- raw_dir = ustht / "raw"
76
- raw_dir.mkdir(exist_ok=True)
85
+ raw_dir = ensure_runtime_dir(ustht, ustht / "raw", create=True)
77
86
 
78
87
  today = datetime.now().strftime("%Y-%m-%d")
79
88
  now = datetime.now().strftime("%H:%M")
80
89
  raw_file = raw_dir / f"{today}.md"
81
90
 
82
91
  if raw_file.exists():
83
- first_line = raw_file.read_text(encoding="utf-8").split("\n", 1)[0].strip()
92
+ first_line = safe_read_text(ustht, raw_file).split("\n", 1)[0].strip()
84
93
  if first_line == "<!-- processed -->":
85
94
  seq = 2
86
95
  while (raw_dir / f"{today}-{seq}.md").exists():
@@ -92,12 +101,12 @@ def main():
92
101
  entry = f"- [{now}] {thought_clean}{suffix}"
93
102
 
94
103
  if raw_file.exists():
95
- content = raw_file.read_text(encoding="utf-8").rstrip()
96
- raw_file.write_text(f"{content}\n{entry}\n", encoding="utf-8")
104
+ content = safe_read_text(ustht, raw_file).rstrip()
105
+ safe_write_text(ustht, raw_file, f"{content}\n{entry}\n")
97
106
  else:
98
- raw_file.write_text(f"{entry}\n", encoding="utf-8")
107
+ safe_write_text(ustht, raw_file, f"{entry}\n")
99
108
 
100
- count = count_today_raw(raw_dir)
109
+ count = count_today_raw(ustht, raw_dir)
101
110
  if count > 5:
102
111
  print(f"Today has {count} recorded thoughts. Consider running /ustht sortin.")
103
112