codealmanac 0.1.0.dev0__py3-none-any.whl
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.
- codealmanac/__init__.py +13 -0
- codealmanac/app.py +175 -0
- codealmanac/cli/__init__.py +1 -0
- codealmanac/cli/dispatch/__init__.py +0 -0
- codealmanac/cli/dispatch/admin.py +124 -0
- codealmanac/cli/dispatch/config.py +50 -0
- codealmanac/cli/dispatch/root.py +328 -0
- codealmanac/cli/main.py +28 -0
- codealmanac/cli/parser/__init__.py +0 -0
- codealmanac/cli/parser/admin.py +81 -0
- codealmanac/cli/parser/lifecycle.py +57 -0
- codealmanac/cli/parser/root.py +19 -0
- codealmanac/cli/parser/wiki.py +87 -0
- codealmanac/cli/render/__init__.py +0 -0
- codealmanac/cli/render/admin.py +191 -0
- codealmanac/cli/render/root.py +290 -0
- codealmanac/core/__init__.py +1 -0
- codealmanac/core/errors.py +45 -0
- codealmanac/core/models.py +14 -0
- codealmanac/core/paths.py +25 -0
- codealmanac/core/slug.py +7 -0
- codealmanac/core/text.py +5 -0
- codealmanac/database/__init__.py +15 -0
- codealmanac/database/sqlite.py +54 -0
- codealmanac/integrations/__init__.py +1 -0
- codealmanac/integrations/automation/__init__.py +3 -0
- codealmanac/integrations/automation/scheduler/__init__.py +5 -0
- codealmanac/integrations/automation/scheduler/launchd.py +163 -0
- codealmanac/integrations/command.py +56 -0
- codealmanac/integrations/harnesses/__init__.py +7 -0
- codealmanac/integrations/harnesses/claude/__init__.py +1 -0
- codealmanac/integrations/harnesses/claude/adapter.py +217 -0
- codealmanac/integrations/harnesses/codex/__init__.py +3 -0
- codealmanac/integrations/harnesses/codex/adapter.py +221 -0
- codealmanac/integrations/harnesses/git_status.py +49 -0
- codealmanac/integrations/sources/__init__.py +29 -0
- codealmanac/integrations/sources/filesystem/__init__.py +5 -0
- codealmanac/integrations/sources/filesystem/adapter.py +685 -0
- codealmanac/integrations/sources/filesystem/selection.py +209 -0
- codealmanac/integrations/sources/git/__init__.py +3 -0
- codealmanac/integrations/sources/git/adapter.py +132 -0
- codealmanac/integrations/sources/github/__init__.py +3 -0
- codealmanac/integrations/sources/github/adapter.py +413 -0
- codealmanac/integrations/sources/runtime.py +22 -0
- codealmanac/integrations/sources/transcripts/__init__.py +33 -0
- codealmanac/integrations/sources/transcripts/claude.py +61 -0
- codealmanac/integrations/sources/transcripts/codex.py +69 -0
- codealmanac/integrations/sources/transcripts/jsonl.py +84 -0
- codealmanac/integrations/sources/transcripts/runtime.py +387 -0
- codealmanac/integrations/sources/web/__init__.py +3 -0
- codealmanac/integrations/sources/web/adapter.py +303 -0
- codealmanac/integrations/updates/__init__.py +7 -0
- codealmanac/integrations/updates/package.py +85 -0
- codealmanac/integrations/workspaces/__init__.py +1 -0
- codealmanac/integrations/workspaces/git/__init__.py +3 -0
- codealmanac/integrations/workspaces/git/probe.py +128 -0
- codealmanac/manual/README.md +24 -0
- codealmanac/manual/__init__.py +19 -0
- codealmanac/manual/build.md +20 -0
- codealmanac/manual/evidence.md +23 -0
- codealmanac/manual/garden.md +20 -0
- codealmanac/manual/ingest.md +17 -0
- codealmanac/manual/library.py +84 -0
- codealmanac/manual/models.py +83 -0
- codealmanac/manual/pages.md +28 -0
- codealmanac/manual/requests.py +6 -0
- codealmanac/manual/sources.md +18 -0
- codealmanac/manual/style.md +19 -0
- codealmanac/prompts/__init__.py +5 -0
- codealmanac/prompts/base/notability.md +14 -0
- codealmanac/prompts/base/purpose.md +23 -0
- codealmanac/prompts/base/syntax.md +19 -0
- codealmanac/prompts/models.py +9 -0
- codealmanac/prompts/operations/garden.md +26 -0
- codealmanac/prompts/operations/ingest.md +18 -0
- codealmanac/prompts/renderer.py +24 -0
- codealmanac/prompts/requests.py +22 -0
- codealmanac/server/__init__.py +1 -0
- codealmanac/server/app.py +202 -0
- codealmanac/server/assets/__init__.py +1 -0
- codealmanac/server/assets/app.css +865 -0
- codealmanac/server/assets/app.js +3 -0
- codealmanac/server/assets/index.html +80 -0
- codealmanac/server/assets/viewer/api.js +30 -0
- codealmanac/server/assets/viewer/components.js +197 -0
- codealmanac/server/assets/viewer/main.js +126 -0
- codealmanac/server/assets/viewer/renderers.js +122 -0
- codealmanac/server/assets/viewer/routes.js +36 -0
- codealmanac/services/__init__.py +1 -0
- codealmanac/services/automation/__init__.py +3 -0
- codealmanac/services/automation/models.py +83 -0
- codealmanac/services/automation/ports.py +14 -0
- codealmanac/services/automation/requests.py +40 -0
- codealmanac/services/automation/service.py +294 -0
- codealmanac/services/config/__init__.py +17 -0
- codealmanac/services/config/models.py +61 -0
- codealmanac/services/config/requests.py +21 -0
- codealmanac/services/config/service.py +55 -0
- codealmanac/services/config/store.py +26 -0
- codealmanac/services/diagnostics/__init__.py +1 -0
- codealmanac/services/diagnostics/models.py +22 -0
- codealmanac/services/diagnostics/requests.py +8 -0
- codealmanac/services/diagnostics/service.py +283 -0
- codealmanac/services/harnesses/__init__.py +1 -0
- codealmanac/services/harnesses/models.py +104 -0
- codealmanac/services/harnesses/ports.py +18 -0
- codealmanac/services/harnesses/requests.py +19 -0
- codealmanac/services/harnesses/service.py +38 -0
- codealmanac/services/health/__init__.py +1 -0
- codealmanac/services/health/requests.py +8 -0
- codealmanac/services/health/service.py +20 -0
- codealmanac/services/index/__init__.py +1 -0
- codealmanac/services/index/models.py +135 -0
- codealmanac/services/index/requests.py +26 -0
- codealmanac/services/index/service.py +86 -0
- codealmanac/services/index/store.py +411 -0
- codealmanac/services/index/views.py +524 -0
- codealmanac/services/pages/__init__.py +1 -0
- codealmanac/services/pages/requests.py +17 -0
- codealmanac/services/pages/service.py +26 -0
- codealmanac/services/runs/__init__.py +1 -0
- codealmanac/services/runs/models.py +91 -0
- codealmanac/services/runs/requests.py +76 -0
- codealmanac/services/runs/service.py +86 -0
- codealmanac/services/runs/store.py +256 -0
- codealmanac/services/search/__init__.py +1 -0
- codealmanac/services/search/requests.py +23 -0
- codealmanac/services/search/service.py +31 -0
- codealmanac/services/sources/__init__.py +1 -0
- codealmanac/services/sources/models.py +126 -0
- codealmanac/services/sources/ports.py +30 -0
- codealmanac/services/sources/requests.py +76 -0
- codealmanac/services/sources/service.py +351 -0
- codealmanac/services/tagging/__init__.py +1 -0
- codealmanac/services/tagging/models.py +9 -0
- codealmanac/services/tagging/requests.py +35 -0
- codealmanac/services/tagging/service.py +43 -0
- codealmanac/services/topics/__init__.py +1 -0
- codealmanac/services/topics/models.py +36 -0
- codealmanac/services/topics/requests.py +115 -0
- codealmanac/services/topics/service.py +297 -0
- codealmanac/services/updates/__init__.py +4 -0
- codealmanac/services/updates/models.py +83 -0
- codealmanac/services/updates/ports.py +17 -0
- codealmanac/services/updates/requests.py +10 -0
- codealmanac/services/updates/service.py +113 -0
- codealmanac/services/viewer/__init__.py +1 -0
- codealmanac/services/viewer/models.py +80 -0
- codealmanac/services/viewer/renderer.py +89 -0
- codealmanac/services/viewer/requests.py +86 -0
- codealmanac/services/viewer/service.py +211 -0
- codealmanac/services/wiki/__init__.py +1 -0
- codealmanac/services/wiki/documents.py +83 -0
- codealmanac/services/wiki/frontmatter.py +94 -0
- codealmanac/services/wiki/frontmatter_rewrite.py +142 -0
- codealmanac/services/wiki/models.py +69 -0
- codealmanac/services/wiki/paths.py +42 -0
- codealmanac/services/wiki/service.py +57 -0
- codealmanac/services/wiki/templates.py +73 -0
- codealmanac/services/wiki/topics.py +266 -0
- codealmanac/services/wiki/wikilinks.py +58 -0
- codealmanac/services/workspaces/__init__.py +1 -0
- codealmanac/services/workspaces/models.py +124 -0
- codealmanac/services/workspaces/ports.py +9 -0
- codealmanac/services/workspaces/requests.py +82 -0
- codealmanac/services/workspaces/roots.py +74 -0
- codealmanac/services/workspaces/service.py +303 -0
- codealmanac/services/workspaces/store.py +127 -0
- codealmanac/workflows/__init__.py +1 -0
- codealmanac/workflows/build/__init__.py +1 -0
- codealmanac/workflows/build/models.py +8 -0
- codealmanac/workflows/build/service.py +45 -0
- codealmanac/workflows/garden/__init__.py +3 -0
- codealmanac/workflows/garden/models.py +30 -0
- codealmanac/workflows/garden/requests.py +22 -0
- codealmanac/workflows/garden/service.py +239 -0
- codealmanac/workflows/ingest/__init__.py +1 -0
- codealmanac/workflows/ingest/models.py +26 -0
- codealmanac/workflows/ingest/requests.py +39 -0
- codealmanac/workflows/ingest/service.py +302 -0
- codealmanac/workflows/lifecycle.py +197 -0
- codealmanac/workflows/sync/__init__.py +3 -0
- codealmanac/workflows/sync/models.py +157 -0
- codealmanac/workflows/sync/requests.py +63 -0
- codealmanac/workflows/sync/service.py +651 -0
- codealmanac/workflows/sync/store.py +51 -0
- codealmanac-0.1.0.dev0.dist-info/METADATA +248 -0
- codealmanac-0.1.0.dev0.dist-info/RECORD +192 -0
- codealmanac-0.1.0.dev0.dist-info/WHEEL +5 -0
- codealmanac-0.1.0.dev0.dist-info/entry_points.txt +2 -0
- codealmanac-0.1.0.dev0.dist-info/licenses/LICENSE.md +201 -0
- codealmanac-0.1.0.dev0.dist-info/top_level.txt +1 -0
codealmanac/cli/main.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
|
|
5
|
+
from pydantic import ValidationError
|
|
6
|
+
|
|
7
|
+
from codealmanac.app import create_app
|
|
8
|
+
from codealmanac.cli.dispatch.root import dispatch as dispatch_app
|
|
9
|
+
from codealmanac.cli.parser.root import build_parser
|
|
10
|
+
from codealmanac.core.errors import CodeAlmanacError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
14
|
+
parser = build_parser()
|
|
15
|
+
args = parser.parse_args(argv)
|
|
16
|
+
try:
|
|
17
|
+
return dispatch(args)
|
|
18
|
+
except (CodeAlmanacError, ValidationError) as error:
|
|
19
|
+
print(f"codealmanac: {error}", file=sys.stderr)
|
|
20
|
+
return 1
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def dispatch(args: argparse.Namespace) -> int:
|
|
24
|
+
return dispatch_app(args, create_app())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
raise SystemExit(main())
|
|
File without changes
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from codealmanac.services.automation.models import AutomationTask
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def add_admin_commands(subcommands: argparse._SubParsersAction) -> None:
|
|
7
|
+
doctor = subcommands.add_parser("doctor", help="check local install and wiki")
|
|
8
|
+
doctor.add_argument("--wiki")
|
|
9
|
+
doctor.add_argument("--json", action="store_true")
|
|
10
|
+
|
|
11
|
+
update = subcommands.add_parser("update", help="update the local CLI")
|
|
12
|
+
update.add_argument("--check", action="store_true")
|
|
13
|
+
update.add_argument("--json", action="store_true")
|
|
14
|
+
|
|
15
|
+
jobs = subcommands.add_parser("jobs", help="inspect local lifecycle jobs")
|
|
16
|
+
jobs.add_argument("--wiki")
|
|
17
|
+
jobs.add_argument("--limit", type=int)
|
|
18
|
+
jobs.add_argument("--json", action="store_true")
|
|
19
|
+
job_subcommands = jobs.add_subparsers(dest="jobs_command")
|
|
20
|
+
jobs_show = job_subcommands.add_parser("show", help="show one job record")
|
|
21
|
+
jobs_show.add_argument("run_id")
|
|
22
|
+
jobs_show.add_argument("--json", action="store_true")
|
|
23
|
+
jobs_logs = job_subcommands.add_parser("logs", help="show one job log")
|
|
24
|
+
jobs_logs.add_argument("run_id")
|
|
25
|
+
jobs_logs.add_argument("--json", action="store_true")
|
|
26
|
+
|
|
27
|
+
automation = subcommands.add_parser(
|
|
28
|
+
"automation",
|
|
29
|
+
help="manage local scheduled automation",
|
|
30
|
+
)
|
|
31
|
+
automation_subcommands = automation.add_subparsers(
|
|
32
|
+
dest="automation_command",
|
|
33
|
+
required=True,
|
|
34
|
+
)
|
|
35
|
+
automation_install = automation_subcommands.add_parser(
|
|
36
|
+
"install",
|
|
37
|
+
help="install scheduled sync and garden jobs",
|
|
38
|
+
)
|
|
39
|
+
automation_install.add_argument(
|
|
40
|
+
"tasks",
|
|
41
|
+
nargs="*",
|
|
42
|
+
choices=tuple(task.value for task in AutomationTask),
|
|
43
|
+
)
|
|
44
|
+
automation_install.add_argument(
|
|
45
|
+
"--every",
|
|
46
|
+
help="run interval for sync or one selected task",
|
|
47
|
+
)
|
|
48
|
+
automation_install.add_argument(
|
|
49
|
+
"--quiet",
|
|
50
|
+
help="minimum quiet time before sync",
|
|
51
|
+
)
|
|
52
|
+
automation_install.add_argument(
|
|
53
|
+
"--garden-every",
|
|
54
|
+
help="Garden run interval (default: 4h)",
|
|
55
|
+
)
|
|
56
|
+
automation_install.add_argument(
|
|
57
|
+
"--garden-off",
|
|
58
|
+
action="store_true",
|
|
59
|
+
help="disable scheduled Garden automation",
|
|
60
|
+
)
|
|
61
|
+
automation_install.add_argument("--json", action="store_true")
|
|
62
|
+
automation_uninstall = automation_subcommands.add_parser(
|
|
63
|
+
"uninstall",
|
|
64
|
+
help="remove scheduled jobs",
|
|
65
|
+
)
|
|
66
|
+
automation_uninstall.add_argument(
|
|
67
|
+
"tasks",
|
|
68
|
+
nargs="*",
|
|
69
|
+
choices=tuple(task.value for task in AutomationTask),
|
|
70
|
+
)
|
|
71
|
+
automation_uninstall.add_argument("--json", action="store_true")
|
|
72
|
+
automation_status = automation_subcommands.add_parser(
|
|
73
|
+
"status",
|
|
74
|
+
help="show scheduled automation status",
|
|
75
|
+
)
|
|
76
|
+
automation_status.add_argument(
|
|
77
|
+
"tasks",
|
|
78
|
+
nargs="*",
|
|
79
|
+
choices=tuple(task.value for task in AutomationTask),
|
|
80
|
+
)
|
|
81
|
+
automation_status.add_argument("--json", action="store_true")
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from codealmanac.services.harnesses.models import HarnessKind
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def add_lifecycle_commands(subcommands: argparse._SubParsersAction) -> None:
|
|
7
|
+
init = subcommands.add_parser("init", help="initialize a local Almanac wiki")
|
|
8
|
+
init.add_argument("path", nargs="?", default=".")
|
|
9
|
+
init.add_argument("--root")
|
|
10
|
+
init.add_argument("--name")
|
|
11
|
+
init.add_argument("--description", default="")
|
|
12
|
+
|
|
13
|
+
build = subcommands.add_parser("build", help="build or refresh a local wiki")
|
|
14
|
+
build.add_argument("path", nargs="?", default=".")
|
|
15
|
+
build.add_argument("--root")
|
|
16
|
+
build.add_argument("--name")
|
|
17
|
+
build.add_argument("--description", default="")
|
|
18
|
+
|
|
19
|
+
ingest = subcommands.add_parser("ingest", help="ingest local material")
|
|
20
|
+
ingest.add_argument("inputs", nargs="+")
|
|
21
|
+
ingest.add_argument("--wiki")
|
|
22
|
+
ingest.add_argument(
|
|
23
|
+
"--using",
|
|
24
|
+
choices=tuple(kind.value for kind in HarnessKind),
|
|
25
|
+
)
|
|
26
|
+
ingest.add_argument("--title")
|
|
27
|
+
ingest.add_argument("--guidance")
|
|
28
|
+
|
|
29
|
+
garden = subcommands.add_parser("garden", help="garden the local wiki")
|
|
30
|
+
garden.add_argument("--wiki")
|
|
31
|
+
garden.add_argument(
|
|
32
|
+
"--using",
|
|
33
|
+
choices=tuple(kind.value for kind in HarnessKind),
|
|
34
|
+
)
|
|
35
|
+
garden.add_argument("--title")
|
|
36
|
+
garden.add_argument("--guidance")
|
|
37
|
+
|
|
38
|
+
sync = subcommands.add_parser("sync", help="sync quiet local transcripts")
|
|
39
|
+
sync.add_argument("--wiki")
|
|
40
|
+
sync.add_argument("--from", dest="source_apps")
|
|
41
|
+
sync.add_argument("--quiet")
|
|
42
|
+
sync.add_argument("--pending-timeout")
|
|
43
|
+
sync.add_argument("--max-failed-attempts", type=int)
|
|
44
|
+
sync.add_argument("--claim-owner")
|
|
45
|
+
sync.add_argument(
|
|
46
|
+
"--using",
|
|
47
|
+
choices=tuple(kind.value for kind in HarnessKind),
|
|
48
|
+
)
|
|
49
|
+
sync.add_argument("--json", action="store_true")
|
|
50
|
+
sync_subcommands = sync.add_subparsers(dest="sync_command")
|
|
51
|
+
sync_status = sync_subcommands.add_parser("status", help="show sync readiness")
|
|
52
|
+
sync_status.add_argument("--wiki")
|
|
53
|
+
sync_status.add_argument("--from", dest="source_apps")
|
|
54
|
+
sync_status.add_argument("--quiet")
|
|
55
|
+
sync_status.add_argument("--pending-timeout")
|
|
56
|
+
sync_status.add_argument("--max-failed-attempts", type=int)
|
|
57
|
+
sync_status.add_argument("--json", action="store_true")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from codealmanac import __version__
|
|
4
|
+
from codealmanac.cli.parser.admin import add_admin_commands
|
|
5
|
+
from codealmanac.cli.parser.lifecycle import add_lifecycle_commands
|
|
6
|
+
from codealmanac.cli.parser.wiki import add_wiki_commands
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
10
|
+
parser = argparse.ArgumentParser(
|
|
11
|
+
prog="codealmanac",
|
|
12
|
+
description="Maintain a local Almanac wiki for a codebase.",
|
|
13
|
+
)
|
|
14
|
+
parser.add_argument("--version", action="version", version=__version__)
|
|
15
|
+
subcommands = parser.add_subparsers(dest="command", required=True)
|
|
16
|
+
add_lifecycle_commands(subcommands)
|
|
17
|
+
add_wiki_commands(subcommands)
|
|
18
|
+
add_admin_commands(subcommands)
|
|
19
|
+
return parser
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
DEFAULT_VIEWER_HOST = "127.0.0.1"
|
|
4
|
+
DEFAULT_VIEWER_PORT = 3927
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def add_wiki_commands(subcommands: argparse._SubParsersAction) -> None:
|
|
8
|
+
list_parser = subcommands.add_parser("list", help="list registered local wikis")
|
|
9
|
+
list_parser.add_argument("--json", action="store_true")
|
|
10
|
+
list_actions = list_parser.add_mutually_exclusive_group()
|
|
11
|
+
list_actions.add_argument("--drop")
|
|
12
|
+
list_actions.add_argument("--drop-missing", action="store_true")
|
|
13
|
+
|
|
14
|
+
search = subcommands.add_parser("search", help="search the local wiki")
|
|
15
|
+
search.add_argument("query", nargs="?")
|
|
16
|
+
search.add_argument("--wiki")
|
|
17
|
+
search.add_argument("--topic", action="append", default=[])
|
|
18
|
+
search.add_argument("--mentions")
|
|
19
|
+
search.add_argument("--include-archive", action="store_true")
|
|
20
|
+
search.add_argument("--archived", action="store_true")
|
|
21
|
+
search.add_argument("--limit", type=int)
|
|
22
|
+
search.add_argument("--json", action="store_true")
|
|
23
|
+
|
|
24
|
+
show = subcommands.add_parser("show", help="show a wiki page")
|
|
25
|
+
show.add_argument("slug")
|
|
26
|
+
show.add_argument("--wiki")
|
|
27
|
+
show.add_argument("--json", action="store_true")
|
|
28
|
+
show.add_argument("--body", action="store_true")
|
|
29
|
+
show.add_argument("--meta", action="store_true")
|
|
30
|
+
show.add_argument("--lead", action="store_true")
|
|
31
|
+
show.add_argument("--links", action="store_true")
|
|
32
|
+
show.add_argument("--backlinks", action="store_true")
|
|
33
|
+
show.add_argument("--files", action="store_true")
|
|
34
|
+
show.add_argument("--topics", action="store_true")
|
|
35
|
+
|
|
36
|
+
topics = subcommands.add_parser("topics", help="list or inspect topics")
|
|
37
|
+
topics.add_argument("--wiki")
|
|
38
|
+
topic_subcommands = topics.add_subparsers(dest="topic_command")
|
|
39
|
+
topic_show = topic_subcommands.add_parser("show", help="show a topic")
|
|
40
|
+
topic_show.add_argument("slug")
|
|
41
|
+
topic_show.add_argument("--descendants", action="store_true")
|
|
42
|
+
topic_create = topic_subcommands.add_parser("create", help="create a topic")
|
|
43
|
+
topic_create.add_argument("name")
|
|
44
|
+
topic_create.add_argument("--parent", action="append", default=[])
|
|
45
|
+
topic_describe = topic_subcommands.add_parser(
|
|
46
|
+
"describe",
|
|
47
|
+
help="set a topic description",
|
|
48
|
+
)
|
|
49
|
+
topic_describe.add_argument("slug")
|
|
50
|
+
topic_describe.add_argument("description")
|
|
51
|
+
topic_link = topic_subcommands.add_parser("link", help="link topic to parent")
|
|
52
|
+
topic_link.add_argument("child")
|
|
53
|
+
topic_link.add_argument("parent")
|
|
54
|
+
topic_unlink = topic_subcommands.add_parser(
|
|
55
|
+
"unlink",
|
|
56
|
+
help="unlink topic from parent",
|
|
57
|
+
)
|
|
58
|
+
topic_unlink.add_argument("child")
|
|
59
|
+
topic_unlink.add_argument("parent")
|
|
60
|
+
topic_rename = topic_subcommands.add_parser("rename", help="rename a topic")
|
|
61
|
+
topic_rename.add_argument("old_slug")
|
|
62
|
+
topic_rename.add_argument("new_slug")
|
|
63
|
+
topic_delete = topic_subcommands.add_parser("delete", help="delete a topic")
|
|
64
|
+
topic_delete.add_argument("slug")
|
|
65
|
+
|
|
66
|
+
health = subcommands.add_parser("health", help="check wiki health")
|
|
67
|
+
health.add_argument("--wiki")
|
|
68
|
+
health.add_argument("--json", action="store_true")
|
|
69
|
+
|
|
70
|
+
reindex = subcommands.add_parser("reindex", help="force a full index rebuild")
|
|
71
|
+
reindex.add_argument("--wiki")
|
|
72
|
+
reindex.add_argument("--json", action="store_true")
|
|
73
|
+
|
|
74
|
+
serve = subcommands.add_parser("serve", help="serve the local wiki viewer")
|
|
75
|
+
serve.add_argument("--wiki")
|
|
76
|
+
serve.add_argument("--host", default=DEFAULT_VIEWER_HOST)
|
|
77
|
+
serve.add_argument("--port", type=int, default=DEFAULT_VIEWER_PORT)
|
|
78
|
+
|
|
79
|
+
tag = subcommands.add_parser("tag", help="add topics to a page")
|
|
80
|
+
tag.add_argument("slug")
|
|
81
|
+
tag.add_argument("topics", nargs="+")
|
|
82
|
+
tag.add_argument("--wiki")
|
|
83
|
+
|
|
84
|
+
untag = subcommands.add_parser("untag", help="remove topics from a page")
|
|
85
|
+
untag.add_argument("slug")
|
|
86
|
+
untag.add_argument("topics", nargs="+")
|
|
87
|
+
untag.add_argument("--wiki")
|
|
File without changes
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import shlex
|
|
3
|
+
import sys
|
|
4
|
+
from datetime import timedelta
|
|
5
|
+
|
|
6
|
+
from codealmanac.services.automation.models import (
|
|
7
|
+
AutomationInstallResult,
|
|
8
|
+
AutomationStatusReport,
|
|
9
|
+
AutomationTask,
|
|
10
|
+
AutomationUninstallResult,
|
|
11
|
+
ScheduledJob,
|
|
12
|
+
ScheduledJobStatus,
|
|
13
|
+
)
|
|
14
|
+
from codealmanac.services.diagnostics.models import DoctorCheck, DoctorReport
|
|
15
|
+
from codealmanac.services.runs.models import RunLogEvent, RunRecord
|
|
16
|
+
from codealmanac.services.updates.models import UpdatePlan, UpdateResult
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def render_automation_install(
|
|
20
|
+
result: AutomationInstallResult,
|
|
21
|
+
json_output: bool,
|
|
22
|
+
) -> None:
|
|
23
|
+
if json_output:
|
|
24
|
+
print(json.dumps(result.model_dump(mode="json"), indent=2))
|
|
25
|
+
return
|
|
26
|
+
print("automation installed")
|
|
27
|
+
for job in result.jobs:
|
|
28
|
+
print_automation_job(job)
|
|
29
|
+
for job in result.disabled:
|
|
30
|
+
print(f" {job.task.value}: disabled")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def render_automation_uninstall(
|
|
34
|
+
result: AutomationUninstallResult,
|
|
35
|
+
json_output: bool,
|
|
36
|
+
) -> None:
|
|
37
|
+
if json_output:
|
|
38
|
+
print(json.dumps(result.model_dump(mode="json"), indent=2))
|
|
39
|
+
return
|
|
40
|
+
if len(result.removed) == 0:
|
|
41
|
+
print("automation not installed")
|
|
42
|
+
return
|
|
43
|
+
print("automation removed")
|
|
44
|
+
for path in result.removed:
|
|
45
|
+
print(f" plist: {path}")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def render_automation_status(
|
|
49
|
+
report: AutomationStatusReport,
|
|
50
|
+
json_output: bool,
|
|
51
|
+
) -> None:
|
|
52
|
+
if json_output:
|
|
53
|
+
print(json.dumps(report.model_dump(mode="json"), indent=2))
|
|
54
|
+
return
|
|
55
|
+
for status in report.statuses:
|
|
56
|
+
render_automation_job_status(status)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def print_automation_job(job: ScheduledJob) -> None:
|
|
60
|
+
print(f" {job.task.value} interval: {duration_label(job.interval)}")
|
|
61
|
+
if job.task == AutomationTask.SYNC:
|
|
62
|
+
quiet = job.program_arguments[job.program_arguments.index("--quiet") + 1]
|
|
63
|
+
print(f" sync quiet: {quiet}")
|
|
64
|
+
print(f" {job.task.value} command: {' '.join(job.program_arguments)}")
|
|
65
|
+
if job.working_directory is not None:
|
|
66
|
+
print(f" {job.task.value} cwd: {job.working_directory}")
|
|
67
|
+
print(f" {job.task.value} plist: {job.plist_path}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def render_automation_job_status(status: ScheduledJobStatus) -> None:
|
|
71
|
+
label = f"{status.task.value} automation"
|
|
72
|
+
if not status.installed:
|
|
73
|
+
print(f"{label}: not installed")
|
|
74
|
+
return
|
|
75
|
+
print(f"{label}: installed")
|
|
76
|
+
print(f" plist: {status.plist_path}")
|
|
77
|
+
print(f" launchd loaded: {'yes' if status.loaded else 'no'}")
|
|
78
|
+
if status.interval is not None:
|
|
79
|
+
print(f" interval: {duration_label(status.interval)}")
|
|
80
|
+
if status.quiet is not None:
|
|
81
|
+
print(f" quiet: {duration_label(status.quiet)}")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def duration_label(value: timedelta) -> str:
|
|
85
|
+
seconds = int(value.total_seconds())
|
|
86
|
+
return f"{seconds}s"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def render_doctor(report: DoctorReport, json_output: bool) -> None:
|
|
90
|
+
if json_output:
|
|
91
|
+
print(json.dumps(report.model_dump(mode="json"), indent=2))
|
|
92
|
+
return
|
|
93
|
+
print(f"codealmanac v{report.version}")
|
|
94
|
+
print("")
|
|
95
|
+
render_doctor_section("Install", report.install)
|
|
96
|
+
render_doctor_section("Current wiki", report.wiki)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def render_doctor_section(title: str, checks: tuple[DoctorCheck, ...]) -> None:
|
|
100
|
+
if len(checks) == 0:
|
|
101
|
+
return
|
|
102
|
+
print(f"## {title}")
|
|
103
|
+
for check in checks:
|
|
104
|
+
print(f" {check.status.value} {check.message}")
|
|
105
|
+
if check.fix is not None:
|
|
106
|
+
print(f" {check.fix}")
|
|
107
|
+
print("")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def render_update_plan(plan: UpdatePlan, json_output: bool) -> None:
|
|
111
|
+
if json_output:
|
|
112
|
+
print(json.dumps(plan.model_dump(mode="json"), indent=2))
|
|
113
|
+
return
|
|
114
|
+
print(f"codealmanac {plan.installed_version}")
|
|
115
|
+
print(f"update status: {plan.status.value}")
|
|
116
|
+
print(f"install method: {plan.method.value}")
|
|
117
|
+
print(f"message: {plan.message}")
|
|
118
|
+
if plan.command:
|
|
119
|
+
print(f"command: {shell_command(plan.command)}")
|
|
120
|
+
if plan.fix is not None:
|
|
121
|
+
print(plan.fix)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def render_update_result(result: UpdateResult, json_output: bool) -> None:
|
|
125
|
+
if json_output:
|
|
126
|
+
print(json.dumps(result.model_dump(mode="json"), indent=2))
|
|
127
|
+
return
|
|
128
|
+
render_update_plan(result.plan, json_output=False)
|
|
129
|
+
if result.exit_code is not None:
|
|
130
|
+
print(f"exit_code: {result.exit_code}")
|
|
131
|
+
if result.stdout:
|
|
132
|
+
print(result.stdout, end="" if result.stdout.endswith("\n") else "\n")
|
|
133
|
+
if result.stderr:
|
|
134
|
+
print(result.stderr, end="" if result.stderr.endswith("\n") else "\n")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def shell_command(command: tuple[str, ...]) -> str:
|
|
138
|
+
return shlex.join(command)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def render_runs(records: tuple[RunRecord, ...], json_output: bool) -> None:
|
|
142
|
+
if json_output:
|
|
143
|
+
data = [record.model_dump(mode="json") for record in records]
|
|
144
|
+
print(json.dumps(data, indent=2))
|
|
145
|
+
return
|
|
146
|
+
if len(records) == 0:
|
|
147
|
+
print("# 0 jobs", file=sys.stderr)
|
|
148
|
+
return
|
|
149
|
+
for record in records:
|
|
150
|
+
title = record.title or ""
|
|
151
|
+
print(
|
|
152
|
+
f"{record.run_id}\t{record.status.value}\t"
|
|
153
|
+
f"{record.operation.value}\t{title}"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def render_run(record: RunRecord, json_output: bool) -> None:
|
|
158
|
+
if json_output:
|
|
159
|
+
print(json.dumps(record.model_dump(mode="json"), indent=2))
|
|
160
|
+
return
|
|
161
|
+
print(f"id: {record.run_id}")
|
|
162
|
+
print(f"operation: {record.operation.value}")
|
|
163
|
+
print(f"status: {record.status.value}")
|
|
164
|
+
if record.title is not None:
|
|
165
|
+
print(f"title: {record.title}")
|
|
166
|
+
if record.summary is not None:
|
|
167
|
+
print(f"summary: {record.summary}")
|
|
168
|
+
if record.error is not None:
|
|
169
|
+
print(f"error: {record.error}")
|
|
170
|
+
if record.harness_transcript is not None:
|
|
171
|
+
print(
|
|
172
|
+
"harness_transcript: "
|
|
173
|
+
f"{record.harness_transcript.kind.value} "
|
|
174
|
+
f"{record.harness_transcript.session_id}"
|
|
175
|
+
)
|
|
176
|
+
if record.harness_transcript.transcript_path is not None:
|
|
177
|
+
print(
|
|
178
|
+
"harness_transcript_path: "
|
|
179
|
+
f"{record.harness_transcript.transcript_path}"
|
|
180
|
+
)
|
|
181
|
+
print(f"created_at: {record.created_at.isoformat()}")
|
|
182
|
+
print(f"updated_at: {record.updated_at.isoformat()}")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def render_run_log(events: tuple[RunLogEvent, ...], json_output: bool) -> None:
|
|
186
|
+
if json_output:
|
|
187
|
+
data = [event.model_dump(mode="json") for event in events]
|
|
188
|
+
print(json.dumps(data, indent=2))
|
|
189
|
+
return
|
|
190
|
+
for event in events:
|
|
191
|
+
print(f"{event.sequence}\t{event.kind.value}\t{event.message}")
|