From 7cbb49134a3a838de42b3644fdff6c56d33e7192 Mon Sep 17 00:00:00 2001 From: "david.delagneau" Date: Thu, 21 May 2026 10:43:44 -0600 Subject: [PATCH] feat: update profile path resolution and enhance scripts for improved project adaptability --- scripts/README.md | 4 +-- scripts/aiw/README.md | 4 +-- scripts/aiw/profile.py | 35 +++++++++++++++++++ scripts/aiw/test_profile.py | 6 ++++ scripts/iphone-photo-inbox/README.md | 9 ++--- scripts/mattermost-proxy/.env.example | 4 ++- scripts/mattermost-proxy/README.md | 12 +++---- scripts/mattermost-proxy/mattermost_mirror.py | 4 +-- scripts/mattermost-proxy/read-context.py | 18 ++++++++-- scripts/mattermost-proxy/run-mirror.sh | 4 ++- scripts/mattermost/README.md | 4 +-- scripts/mcp/aiw-context-mcp/README.md | 4 +-- scripts/memory/README.md | 7 ++-- scripts/memory/memory.sh | 3 +- scripts/obsidian/cli.sh | 3 +- scripts/obsidian/daily.sh | 3 +- scripts/obsidian/open.sh | 3 +- scripts/obsidian/search.sh | 3 +- 18 files changed, 98 insertions(+), 32 deletions(-) diff --git a/scripts/README.md b/scripts/README.md index 4701497..ca47674 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -41,7 +41,7 @@ bash scripts/memory/memory.sh search "PDIAP-15765" bash scripts/memory/memory.sh create work-item pdiap-15999 "Example title" ``` -This interface defaults to Markdown files under `project-knowledge/`, uses Obsidian CLI when useful and available, and falls back to direct file operations. +This interface resolves the canonical Markdown directory from `profiles//workspace.json`, uses Obsidian CLI when useful and available, and falls back to direct file operations. The default workspace Mattermost extractor now lives in: @@ -79,7 +79,7 @@ Expected behavior: - avoid interactive prompts - return a non-zero exit code on failure -OpenCode can then use that output to refresh `ai/inbox/mattermost-latest.md` proactively. When the local Mattermost proxy mirror is running, commands should prefer `ai/inbox/mattermost-mirror/` through `scripts/mattermost-proxy/read-context.py` and use legacy sync output as fallback evidence. +OpenCode can then use that output to refresh the active profile inbox proactively. When the local Mattermost proxy mirror is running, commands should prefer `/mattermost-mirror/` through `scripts/mattermost-proxy/read-context.py --profile ` and use legacy sync output only as fallback evidence. Historical Slack exports can also be imported through: diff --git a/scripts/aiw/README.md b/scripts/aiw/README.md index 5c70f99..41fb214 100644 --- a/scripts/aiw/README.md +++ b/scripts/aiw/README.md @@ -34,7 +34,7 @@ The service manager unifies startup and status. It does not move capture behavio ## Local project-knowledge index -The workspace includes a dependency-free local indexer for canonical Markdown memory. The index is derived from `project-knowledge/` and written under `.aiw/indexes//`; it is safe to delete and rebuild. +The workspace includes a dependency-free local indexer for canonical Markdown memory. The index is derived from the profile's configured `knowledge_dir` and written under the profile's configured `index_dir`; it is safe to delete and rebuild. ```bash python3 scripts/aiw/indexer.py build --profile fidelity @@ -62,7 +62,7 @@ Current fields: } ``` -Use `scripts/aiw/profile.py` from new scripts instead of hardcoding root-level `project-knowledge/` or `ai/inbox/` paths. +Use `scripts/aiw/profile.py` from new scripts instead of hardcoding root-level project memory or inbox paths. ## Robustness features diff --git a/scripts/aiw/profile.py b/scripts/aiw/profile.py index 5a93b53..feaa6a9 100644 --- a/scripts/aiw/profile.py +++ b/scripts/aiw/profile.py @@ -8,6 +8,7 @@ instead of hardcoding root-level project paths. from __future__ import annotations import json +import argparse from pathlib import Path from typing import Any @@ -70,3 +71,37 @@ def relative_to_root(path: Path, root: Path | None = None) -> str: return str(path.relative_to(base)) except ValueError: return str(path) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + subparsers = parser.add_subparsers(dest="command", required=True) + + path_parser = subparsers.add_parser("path", help="Print a resolved profile path.") + path_parser.add_argument("kind", choices=["knowledge", "inbox", "index"]) + path_parser.add_argument("--profile", default="fidelity") + + config_parser = subparsers.add_parser("config", help="Print resolved workspace configuration as JSON.") + config_parser.add_argument("--profile", default="fidelity") + + args = parser.parse_args() + if args.command == "path": + if args.kind == "knowledge": + print(knowledge_dir(args.profile)) + elif args.kind == "inbox": + print(inbox_dir(args.profile)) + else: + print(index_dir(args.profile)) + return + + config = load_workspace_config(args.profile) + config["resolved"] = { + "knowledge_dir": str(knowledge_dir(args.profile)), + "inbox_dir": str(inbox_dir(args.profile)), + "index_dir": str(index_dir(args.profile)), + } + print(json.dumps(config, ensure_ascii=False, indent=2, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/scripts/aiw/test_profile.py b/scripts/aiw/test_profile.py index 018e0fe..6442ef8 100644 --- a/scripts/aiw/test_profile.py +++ b/scripts/aiw/test_profile.py @@ -42,6 +42,12 @@ class ProfileTests(unittest.TestCase): self.assertEqual(profile.inbox_dir("missing", root=root), root / "ai" / "inbox") self.assertEqual(profile.index_dir("missing", root=root), root / ".aiw" / "indexes" / "missing") + def test_relative_to_root_handles_external_paths(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.assertEqual(profile.relative_to_root(root / "a" / "b", root=root), "a/b") + self.assertEqual(profile.relative_to_root(Path("/external/path"), root=root), "/external/path") + if __name__ == "__main__": unittest.main() diff --git a/scripts/iphone-photo-inbox/README.md b/scripts/iphone-photo-inbox/README.md index acc4f7a..c1fd7c8 100644 --- a/scripts/iphone-photo-inbox/README.md +++ b/scripts/iphone-photo-inbox/README.md @@ -152,17 +152,18 @@ Keep this utility as an isolated image mailbox. If a project wants easy access, link the project inbox to the mailbox instead of making this utility know about the project. -Example: +Example using the active profile inbox: ```bash -mkdir -p ai/inbox -ln -s "$HOME/Pictures/Photo Inbox" ai/inbox/photos +PROFILE_INBOX="$(python3 scripts/aiw/profile.py path inbox --profile fidelity)" +mkdir -p "$PROFILE_INBOX" +ln -s "$HOME/Pictures/Photo Inbox" "$PROFILE_INBOX/photos" ``` Or point the receiver at a project-owned folder from `.env`: ```bash -PHOTO_INBOX_DIR=/absolute/path/to/project/ai/inbox/photos +PHOTO_INBOX_DIR=/absolute/path/to/profile/inbox/photos ``` The symlink approach keeps this utility reusable across projects and devices. diff --git a/scripts/mattermost-proxy/.env.example b/scripts/mattermost-proxy/.env.example index 5fb8dd2..fcf1b2e 100644 --- a/scripts/mattermost-proxy/.env.example +++ b/scripts/mattermost-proxy/.env.example @@ -7,7 +7,9 @@ MATTERMOST_MIRROR_HOST_ALLOW= # Output directory for raw evidence and normalized AI-readable context. -MATTERMOST_MIRROR_DIR=ai/inbox/mattermost-mirror +# Optional. If omitted, run-mirror.sh writes to the active profile inbox: +# /workspace.json>/mattermost-mirror +# MATTERMOST_MIRROR_DIR=/absolute/path/to/mattermost-mirror # mitmproxy listener used by launch-mattermost.sh. MATTERMOST_MIRROR_LISTEN_HOST=127.0.0.1 diff --git a/scripts/mattermost-proxy/README.md b/scripts/mattermost-proxy/README.md index 39d7983..4cdde70 100644 --- a/scripts/mattermost-proxy/README.md +++ b/scripts/mattermost-proxy/README.md @@ -2,7 +2,7 @@ Local read-only Mattermost Desktop mirror for AI workspace context. -This is for **raw evidence only**. It writes under `ai/inbox/mattermost-mirror/`; durable project memory still belongs in `project-knowledge/` after normal promotion rules. +This is for **raw evidence only**. By default it writes under the active profile inbox at `/mattermost-mirror/`; durable project memory still belongs in the profile's canonical project knowledge vault after normal promotion rules. ## Why this exists @@ -48,7 +48,7 @@ missing. ## Output layout ```text -ai/inbox/mattermost-mirror/ +/mattermost-mirror/ latest.jsonl # bounded AI-readable window latest.md # bounded Markdown view state.json # last seen by channel and user cache @@ -138,7 +138,7 @@ Each line in the normalized JSONL contains: ## Useful environment variables - `MATTERMOST_MIRROR_HOST_ALLOW`: exact host or parent domain to capture. -- `MATTERMOST_MIRROR_DIR`: output directory, default `ai/inbox/mattermost-mirror`. +- `MATTERMOST_MIRROR_DIR`: optional output directory. If omitted, `run-mirror.sh` uses `/workspace.json>/mattermost-mirror`. - `MATTERMOST_MIRROR_LATEST_LIMIT`: number of messages in `latest.*`, default `200`. - `MATTERMOST_MIRROR_CHANNEL_IDS`: optional comma-separated channel ID allowlist. - `MATTERMOST_MIRROR_WRITE_RAW`: set to `1` to save compact raw REST/WebSocket evidence. @@ -170,7 +170,7 @@ do not create message files. Open a channel, open a thread, scroll slightly in history, or wait for/send a new message. Then check: ```text -ai/inbox/mattermost-mirror/latest.md -ai/inbox/mattermost-mirror/channels//YYYY/MM/YYYY-MM-DD.jsonl -ai/inbox/mattermost-mirror/by-date/YYYY/MM/YYYY-MM-DD.jsonl +/mattermost-mirror/latest.md +/mattermost-mirror/channels//YYYY/MM/YYYY-MM-DD.jsonl +/mattermost-mirror/by-date/YYYY/MM/YYYY-MM-DD.jsonl ``` diff --git a/scripts/mattermost-proxy/mattermost_mirror.py b/scripts/mattermost-proxy/mattermost_mirror.py index 1093969..2593f0e 100644 --- a/scripts/mattermost-proxy/mattermost_mirror.py +++ b/scripts/mattermost-proxy/mattermost_mirror.py @@ -6,7 +6,7 @@ This addon is intentionally narrow: - redact secrets - normalize posts into date-rotated JSONL files for AI context -The output under ai/inbox/ is raw evidence, not canonical project memory. +The output under the profile inbox is raw evidence, not canonical project memory. """ from __future__ import annotations @@ -23,7 +23,7 @@ from urllib.parse import urlparse from mitmproxy import http -DEFAULT_OUT_DIR = "ai/inbox/mattermost-mirror" +DEFAULT_OUT_DIR = "mattermost-mirror" POST_ID_RE = re.compile(r"^[a-z0-9]{26}$") SAFE_NAME_RE = re.compile(r"[^a-zA-Z0-9._-]+") diff --git a/scripts/mattermost-proxy/read-context.py b/scripts/mattermost-proxy/read-context.py index 0faaf57..75c90d9 100755 --- a/scripts/mattermost-proxy/read-context.py +++ b/scripts/mattermost-proxy/read-context.py @@ -13,17 +13,29 @@ import argparse import json import os import shlex +import sys from datetime import date, datetime, timedelta from pathlib import Path ROOT = Path(__file__).resolve().parents[2] -MIRROR_DIR = ROOT / "ai" / "inbox" / "mattermost-mirror" -LEGACY_LATEST = ROOT / "ai" / "inbox" / "mattermost-latest.md" +sys.path.insert(0, str(ROOT / "scripts" / "aiw")) +import profile as aiw_profile # noqa: E402 + +DEFAULT_PROFILE = os.getenv("AIW_PROJECT_PROFILE", "fidelity") +MIRROR_DIR = aiw_profile.inbox_dir(DEFAULT_PROFILE, root=ROOT) / "mattermost-mirror" +LEGACY_LATEST = aiw_profile.inbox_dir(DEFAULT_PROFILE, root=ROOT) / "mattermost-latest.md" LEGACY_GENERATED = ROOT / "scripts" / "mattermost" / "generated" / "mattermost_context.jsonl" LOCAL_ENV = Path(__file__).resolve().parent / ".env" +def configure_profile_paths(profile: str) -> None: + global MIRROR_DIR, LEGACY_LATEST + inbox = aiw_profile.inbox_dir(profile, root=ROOT) + MIRROR_DIR = inbox / "mattermost-mirror" + LEGACY_LATEST = inbox / "mattermost-latest.md" + + def load_local_env(path: Path = LOCAL_ENV) -> None: """Load simple KEY=VALUE pairs from the connector-local .env. @@ -201,6 +213,7 @@ def main() -> None: load_local_env() parser = argparse.ArgumentParser() + parser.add_argument("--profile", default=DEFAULT_PROFILE) parser.add_argument("--mode", choices=["latest", "previous-workday", "standup", "focused"], default="latest") parser.add_argument("--today", default="") parser.add_argument( @@ -210,6 +223,7 @@ def main() -> None: ) parser.add_argument("--limit", type=int, default=80, help="Max records per section; use 0 for no limit.") args = parser.parse_args() + configure_profile_paths(args.profile) channels = parse_channels(args.channels or None) limit = args.limit if args.limit > 0 else None diff --git a/scripts/mattermost-proxy/run-mirror.sh b/scripts/mattermost-proxy/run-mirror.sh index c91cd32..fe4714e 100755 --- a/scripts/mattermost-proxy/run-mirror.sh +++ b/scripts/mattermost-proxy/run-mirror.sh @@ -11,7 +11,9 @@ if [ -f "$SCRIPT_DIR/.env" ]; then set +a fi -export MATTERMOST_MIRROR_DIR="${MATTERMOST_MIRROR_DIR:-$WORKSPACE_ROOT/ai/inbox/mattermost-mirror}" +PROFILE="${AIW_PROJECT_PROFILE:-fidelity}" +PROFILE_INBOX_DIR="$(python3 "$WORKSPACE_ROOT/scripts/aiw/profile.py" path inbox --profile "$PROFILE")" +export MATTERMOST_MIRROR_DIR="${MATTERMOST_MIRROR_DIR:-$PROFILE_INBOX_DIR/mattermost-mirror}" export MATTERMOST_MIRROR_LISTEN_HOST="${MATTERMOST_MIRROR_LISTEN_HOST:-127.0.0.1}" export MATTERMOST_MIRROR_LISTEN_PORT="${MATTERMOST_MIRROR_LISTEN_PORT:-8080}" diff --git a/scripts/mattermost/README.md b/scripts/mattermost/README.md index 4436a72..704ae8d 100644 --- a/scripts/mattermost/README.md +++ b/scripts/mattermost/README.md @@ -3,7 +3,7 @@ This directory contains the workspace-local Mattermost extractor used by OpenCode to refresh communication context. The preferred live Mattermost evidence source is now the proxy mirror under -`ai/inbox/mattermost-mirror/` when it is running. This legacy extractor remains +the active profile inbox, `/mattermost-mirror/`, when it is running. This legacy extractor remains the fallback and explicit refresh path for commands that need a fresh pull from the Mattermost API. @@ -72,7 +72,7 @@ Generic workspace variables are preferred for reusable projects: - `AIW_MATTERMOST_SYNC_CMD` - `AIW_MATTERMOST_SYNC_INTERVAL_MINUTES` -The older `FIDELITY_*` variables remain supported for this project profile. +Profile-specific compatibility variables may exist for older project setups, but new reusable workflows should prefer the generic `AIW_*` variables. Previous workday mode for standups: diff --git a/scripts/mcp/aiw-context-mcp/README.md b/scripts/mcp/aiw-context-mcp/README.md index 6496614..45a2ba7 100644 --- a/scripts/mcp/aiw-context-mcp/README.md +++ b/scripts/mcp/aiw-context-mcp/README.md @@ -45,7 +45,7 @@ python3 scripts/mcp/aiw-context-mcp/server.py --transport stdio - `memory_hybrid_search` - `photos_latest` -All tools are read-only. Mattermost tools read `ai/inbox/mattermost-mirror/`; photo tools list local Photo Inbox files without embedding image data; project tools read canonical Markdown under `project-knowledge/`. +All tools are read-only. Mattermost tools read the active profile's `mattermost-mirror/` inbox; photo tools list local Photo Inbox files without embedding image data; project tools read canonical Markdown from the profile's configured `knowledge_dir`. `memory_hybrid_search` reads the derived local index built by: @@ -53,7 +53,7 @@ All tools are read-only. Mattermost tools read `ai/inbox/mattermost-mirror/`; ph python3 scripts/aiw/indexer.py build --profile fidelity ``` -If the index is missing, it falls back to bounded live Markdown search over `project-knowledge/`. The index is not canonical memory; `project-knowledge/` remains the source of truth. +If the index is missing, it falls back to bounded live Markdown search over the profile's configured knowledge directory. The index is not canonical memory; Markdown remains the source of truth. Mattermost latest/date/standup tools filter to the active profile's context channels by default. For Fidelity, that list lives in `profiles/fidelity/context-sources.json`. Pass explicit `channels` to override the profile list, or `include_all_channels: true` when broad unfiltered mirror evidence is intentionally needed. diff --git a/scripts/memory/README.md b/scripts/memory/README.md index 0fd4587..a80b370 100644 --- a/scripts/memory/README.md +++ b/scripts/memory/README.md @@ -2,12 +2,13 @@ This directory exposes a project-agnostic interface for canonical project knowledge. -The current implementation uses Markdown files under `project-knowledge/` and can optionally delegate to the Obsidian CLI when it is available. The agent should depend on this memory interface, not on Obsidian-specific behavior, so the backing tool can be replaced later. +The current implementation resolves the canonical Markdown directory from `profiles//workspace.json` and can optionally delegate to the Obsidian CLI when it is available. The agent should depend on this memory interface, not on Obsidian-specific behavior, so the backing tool can be replaced later. ## Backend Model -- `AIW_PROJECT_KNOWLEDGE_DIR` points to the canonical Markdown project knowledge directory. -- `AIW_MEMORY_VAULT_DIR` and `AIW_OBSIDIAN_VAULT_DIR` are transition aliases. +- `AIW_PROJECT_PROFILE` selects the profile, defaulting to `fidelity` in this repository. +- `profiles//workspace.json` defines the canonical Markdown project knowledge directory. +- `AIW_PROJECT_KNOWLEDGE_DIR` can override the resolved directory for local experiments. - `AIW_MEMORY_BACKEND` defaults to `auto`. - `auto` uses Obsidian CLI when it is useful and available, then falls back to direct Markdown operations. - `files` forces direct Markdown operations. diff --git a/scripts/memory/memory.sh b/scripts/memory/memory.sh index e5c19f4..7b5b125 100755 --- a/scripts/memory/memory.sh +++ b/scripts/memory/memory.sh @@ -4,7 +4,8 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -PROJECT_KNOWLEDGE_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-${AIW_MEMORY_VAULT_DIR:-${AIW_OBSIDIAN_VAULT_DIR:-$WORKSPACE_ROOT/project-knowledge}}}" +PROFILE="${AIW_PROJECT_PROFILE:-fidelity}" +PROJECT_KNOWLEDGE_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-$(python3 "$WORKSPACE_ROOT/scripts/aiw/profile.py" path knowledge --profile "$PROFILE")}" VAULT_DIR="$PROJECT_KNOWLEDGE_DIR" BACKEND="${AIW_MEMORY_BACKEND:-auto}" OBSIDIAN_CLI="$WORKSPACE_ROOT/scripts/obsidian/cli.sh" diff --git a/scripts/obsidian/cli.sh b/scripts/obsidian/cli.sh index 2c3e469..087ceae 100755 --- a/scripts/obsidian/cli.sh +++ b/scripts/obsidian/cli.sh @@ -4,7 +4,8 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -VAULT_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-${AIW_MEMORY_VAULT_DIR:-${AIW_OBSIDIAN_VAULT_DIR:-$WORKSPACE_ROOT/project-knowledge}}}" +PROFILE="${AIW_PROJECT_PROFILE:-fidelity}" +VAULT_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-$(python3 "$WORKSPACE_ROOT/scripts/aiw/profile.py" path knowledge --profile "$PROFILE")}" if ! command -v obsidian >/dev/null 2>&1; then echo "obsidian CLI is not installed or not in PATH" >&2 diff --git a/scripts/obsidian/daily.sh b/scripts/obsidian/daily.sh index 9013457..969a4e8 100755 --- a/scripts/obsidian/daily.sh +++ b/scripts/obsidian/daily.sh @@ -4,7 +4,8 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -VAULT_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-${AIW_MEMORY_VAULT_DIR:-${AIW_OBSIDIAN_VAULT_DIR:-$WORKSPACE_ROOT/project-knowledge}}}" +PROFILE="${AIW_PROJECT_PROFILE:-fidelity}" +VAULT_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-$(python3 "$WORKSPACE_ROOT/scripts/aiw/profile.py" path knowledge --profile "$PROFILE")}" VAULT_NAME="${AIW_OBSIDIAN_VAULT_NAME:-$(basename "$VAULT_DIR")}" URI="$("$SCRIPT_DIR/uri.sh" daily "vault=$VAULT_NAME")" diff --git a/scripts/obsidian/open.sh b/scripts/obsidian/open.sh index f7a4e42..d8c35b3 100755 --- a/scripts/obsidian/open.sh +++ b/scripts/obsidian/open.sh @@ -4,7 +4,8 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -VAULT_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-${AIW_MEMORY_VAULT_DIR:-${AIW_OBSIDIAN_VAULT_DIR:-$WORKSPACE_ROOT/project-knowledge}}}" +PROFILE="${AIW_PROJECT_PROFILE:-fidelity}" +VAULT_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-$(python3 "$WORKSPACE_ROOT/scripts/aiw/profile.py" path knowledge --profile "$PROFILE")}" VAULT_NAME="${AIW_OBSIDIAN_VAULT_NAME:-$(basename "$VAULT_DIR")}" if [[ $# -lt 1 ]]; then diff --git a/scripts/obsidian/search.sh b/scripts/obsidian/search.sh index 359f6b7..bf91b13 100755 --- a/scripts/obsidian/search.sh +++ b/scripts/obsidian/search.sh @@ -4,7 +4,8 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -VAULT_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-${AIW_MEMORY_VAULT_DIR:-${AIW_OBSIDIAN_VAULT_DIR:-$WORKSPACE_ROOT/project-knowledge}}}" +PROFILE="${AIW_PROJECT_PROFILE:-fidelity}" +VAULT_DIR="${AIW_PROJECT_KNOWLEDGE_DIR:-$(python3 "$WORKSPACE_ROOT/scripts/aiw/profile.py" path knowledge --profile "$PROFILE")}" VAULT_NAME="${AIW_OBSIDIAN_VAULT_NAME:-$(basename "$VAULT_DIR")}" if [[ $# -lt 1 ]]; then