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/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
2
|
+
|
|
3
|
+
__all__ = ["__version__"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _installed_version() -> str:
|
|
7
|
+
try:
|
|
8
|
+
return version("codealmanac")
|
|
9
|
+
except PackageNotFoundError:
|
|
10
|
+
return "0+unknown"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__version__ = _installed_version()
|
codealmanac/app.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
from codealmanac import __version__
|
|
5
|
+
from codealmanac.core.models import AppConfig
|
|
6
|
+
from codealmanac.integrations.automation import LaunchdSchedulerAdapter
|
|
7
|
+
from codealmanac.integrations.harnesses import default_harness_adapters
|
|
8
|
+
from codealmanac.integrations.sources import (
|
|
9
|
+
default_source_runtime_adapters,
|
|
10
|
+
default_transcript_discovery_adapters,
|
|
11
|
+
)
|
|
12
|
+
from codealmanac.integrations.updates import (
|
|
13
|
+
InstalledPackageMetadataProvider,
|
|
14
|
+
SubprocessPackageCommandRunner,
|
|
15
|
+
)
|
|
16
|
+
from codealmanac.integrations.workspaces.git import GitWorkspaceChangeProbe
|
|
17
|
+
from codealmanac.manual import ManualLibrary
|
|
18
|
+
from codealmanac.prompts import PromptRenderer
|
|
19
|
+
from codealmanac.services.automation.ports import SchedulerAdapter
|
|
20
|
+
from codealmanac.services.automation.service import AutomationService
|
|
21
|
+
from codealmanac.services.config.service import ConfigService
|
|
22
|
+
from codealmanac.services.config.store import ConfigStore
|
|
23
|
+
from codealmanac.services.diagnostics.service import DiagnosticsService
|
|
24
|
+
from codealmanac.services.harnesses.ports import HarnessAdapter
|
|
25
|
+
from codealmanac.services.harnesses.service import HarnessesService
|
|
26
|
+
from codealmanac.services.health.service import HealthService
|
|
27
|
+
from codealmanac.services.index.service import IndexService
|
|
28
|
+
from codealmanac.services.index.store import IndexStore
|
|
29
|
+
from codealmanac.services.pages.service import PagesService
|
|
30
|
+
from codealmanac.services.runs.service import RunsService
|
|
31
|
+
from codealmanac.services.runs.store import RunStore
|
|
32
|
+
from codealmanac.services.search.service import SearchService
|
|
33
|
+
from codealmanac.services.sources.ports import (
|
|
34
|
+
SourceRuntimeAdapter,
|
|
35
|
+
TranscriptDiscoveryAdapter,
|
|
36
|
+
)
|
|
37
|
+
from codealmanac.services.sources.service import SourcesService
|
|
38
|
+
from codealmanac.services.tagging.service import TaggingService
|
|
39
|
+
from codealmanac.services.topics.service import TopicsService
|
|
40
|
+
from codealmanac.services.updates.ports import (
|
|
41
|
+
PackageCommandRunner,
|
|
42
|
+
PackageInstallMetadataProvider,
|
|
43
|
+
)
|
|
44
|
+
from codealmanac.services.updates.service import UpdatesService
|
|
45
|
+
from codealmanac.services.viewer.renderer import MarkdownRenderer
|
|
46
|
+
from codealmanac.services.viewer.service import ViewerService
|
|
47
|
+
from codealmanac.services.wiki.service import WikiService
|
|
48
|
+
from codealmanac.services.workspaces.service import WorkspacesService
|
|
49
|
+
from codealmanac.services.workspaces.store import WorkspaceRegistryStore
|
|
50
|
+
from codealmanac.workflows.build.service import BuildWorkflow
|
|
51
|
+
from codealmanac.workflows.garden.service import GardenWorkflow
|
|
52
|
+
from codealmanac.workflows.ingest.service import IngestWorkflow
|
|
53
|
+
from codealmanac.workflows.lifecycle import LifecycleMutationPolicy
|
|
54
|
+
from codealmanac.workflows.sync.service import SyncWorkflow
|
|
55
|
+
from codealmanac.workflows.sync.store import SyncLedgerStore
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class CodeAlmanacWorkflows:
|
|
60
|
+
build: BuildWorkflow
|
|
61
|
+
ingest: IngestWorkflow
|
|
62
|
+
garden: GardenWorkflow
|
|
63
|
+
sync: SyncWorkflow
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True)
|
|
67
|
+
class CodeAlmanac:
|
|
68
|
+
automation: AutomationService
|
|
69
|
+
config: ConfigService
|
|
70
|
+
workspaces: WorkspacesService
|
|
71
|
+
wiki: WikiService
|
|
72
|
+
index: IndexService
|
|
73
|
+
search: SearchService
|
|
74
|
+
pages: PagesService
|
|
75
|
+
topics: TopicsService
|
|
76
|
+
health: HealthService
|
|
77
|
+
diagnostics: DiagnosticsService
|
|
78
|
+
tagging: TaggingService
|
|
79
|
+
updates: UpdatesService
|
|
80
|
+
viewer: ViewerService
|
|
81
|
+
runs: RunsService
|
|
82
|
+
sources: SourcesService
|
|
83
|
+
harnesses: HarnessesService
|
|
84
|
+
prompts: PromptRenderer
|
|
85
|
+
manual: ManualLibrary
|
|
86
|
+
workflows: CodeAlmanacWorkflows
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def create_app(
|
|
90
|
+
config: AppConfig | None = None,
|
|
91
|
+
harness_adapters: Sequence[HarnessAdapter] | None = None,
|
|
92
|
+
transcript_discovery_adapters: Sequence[TranscriptDiscoveryAdapter] | None = None,
|
|
93
|
+
source_runtime_adapters: Sequence[SourceRuntimeAdapter] | None = None,
|
|
94
|
+
scheduler: SchedulerAdapter | None = None,
|
|
95
|
+
update_metadata: PackageInstallMetadataProvider | None = None,
|
|
96
|
+
update_runner: PackageCommandRunner | None = None,
|
|
97
|
+
) -> CodeAlmanac:
|
|
98
|
+
app_config = config or AppConfig()
|
|
99
|
+
workspaces = WorkspacesService(WorkspaceRegistryStore(app_config.registry_path))
|
|
100
|
+
config_service = ConfigService(workspaces, ConfigStore(), app_config.config_path)
|
|
101
|
+
automation = AutomationService(workspaces, scheduler or LaunchdSchedulerAdapter())
|
|
102
|
+
manual = ManualLibrary()
|
|
103
|
+
wiki = WikiService(workspaces, manual)
|
|
104
|
+
index = IndexService(workspaces, IndexStore())
|
|
105
|
+
search = SearchService(workspaces, index)
|
|
106
|
+
pages = PagesService(workspaces, index)
|
|
107
|
+
topics = TopicsService(workspaces, index)
|
|
108
|
+
health = HealthService(workspaces, index)
|
|
109
|
+
diagnostics = DiagnosticsService(workspaces, index, manual, __version__)
|
|
110
|
+
tagging = TaggingService(pages)
|
|
111
|
+
updates = UpdatesService(
|
|
112
|
+
update_metadata or InstalledPackageMetadataProvider(),
|
|
113
|
+
update_runner or SubprocessPackageCommandRunner(),
|
|
114
|
+
)
|
|
115
|
+
viewer = ViewerService(workspaces, index, MarkdownRenderer())
|
|
116
|
+
runs = RunsService(workspaces, RunStore())
|
|
117
|
+
sources = SourcesService(
|
|
118
|
+
default_transcript_discovery_adapters()
|
|
119
|
+
if transcript_discovery_adapters is None
|
|
120
|
+
else transcript_discovery_adapters,
|
|
121
|
+
default_source_runtime_adapters()
|
|
122
|
+
if source_runtime_adapters is None
|
|
123
|
+
else source_runtime_adapters,
|
|
124
|
+
)
|
|
125
|
+
prompts = PromptRenderer()
|
|
126
|
+
harnesses = HarnessesService(
|
|
127
|
+
default_harness_adapters() if harness_adapters is None else harness_adapters
|
|
128
|
+
)
|
|
129
|
+
build = BuildWorkflow(workspaces, wiki, index)
|
|
130
|
+
ingest = IngestWorkflow(
|
|
131
|
+
workspaces,
|
|
132
|
+
sources,
|
|
133
|
+
harnesses,
|
|
134
|
+
runs,
|
|
135
|
+
index,
|
|
136
|
+
LifecycleMutationPolicy(GitWorkspaceChangeProbe(), operation="ingest"),
|
|
137
|
+
prompts,
|
|
138
|
+
)
|
|
139
|
+
garden = GardenWorkflow(
|
|
140
|
+
workspaces,
|
|
141
|
+
harnesses,
|
|
142
|
+
runs,
|
|
143
|
+
index,
|
|
144
|
+
health,
|
|
145
|
+
LifecycleMutationPolicy(GitWorkspaceChangeProbe(), operation="garden"),
|
|
146
|
+
prompts,
|
|
147
|
+
)
|
|
148
|
+
sync = SyncWorkflow(workspaces, sources, runs, ingest, SyncLedgerStore())
|
|
149
|
+
workflows = CodeAlmanacWorkflows(
|
|
150
|
+
build=build,
|
|
151
|
+
ingest=ingest,
|
|
152
|
+
garden=garden,
|
|
153
|
+
sync=sync,
|
|
154
|
+
)
|
|
155
|
+
return CodeAlmanac(
|
|
156
|
+
automation=automation,
|
|
157
|
+
config=config_service,
|
|
158
|
+
workspaces=workspaces,
|
|
159
|
+
wiki=wiki,
|
|
160
|
+
index=index,
|
|
161
|
+
search=search,
|
|
162
|
+
pages=pages,
|
|
163
|
+
topics=topics,
|
|
164
|
+
health=health,
|
|
165
|
+
diagnostics=diagnostics,
|
|
166
|
+
tagging=tagging,
|
|
167
|
+
updates=updates,
|
|
168
|
+
viewer=viewer,
|
|
169
|
+
runs=runs,
|
|
170
|
+
sources=sources,
|
|
171
|
+
harnesses=harnesses,
|
|
172
|
+
prompts=prompts,
|
|
173
|
+
manual=manual,
|
|
174
|
+
workflows=workflows,
|
|
175
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
File without changes
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from collections.abc import Sequence
|
|
3
|
+
from datetime import timedelta
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from codealmanac.app import CodeAlmanac
|
|
7
|
+
from codealmanac.cli.dispatch.config import (
|
|
8
|
+
load_cli_config,
|
|
9
|
+
parse_optional_duration,
|
|
10
|
+
)
|
|
11
|
+
from codealmanac.cli.render.admin import (
|
|
12
|
+
render_automation_install,
|
|
13
|
+
render_automation_status,
|
|
14
|
+
render_automation_uninstall,
|
|
15
|
+
render_doctor,
|
|
16
|
+
render_run,
|
|
17
|
+
render_run_log,
|
|
18
|
+
render_runs,
|
|
19
|
+
render_update_plan,
|
|
20
|
+
render_update_result,
|
|
21
|
+
)
|
|
22
|
+
from codealmanac.services.automation.models import AutomationTask
|
|
23
|
+
from codealmanac.services.automation.requests import (
|
|
24
|
+
AutomationStatusRequest,
|
|
25
|
+
InstallAutomationRequest,
|
|
26
|
+
UninstallAutomationRequest,
|
|
27
|
+
)
|
|
28
|
+
from codealmanac.services.config.models import CodeAlmanacConfig
|
|
29
|
+
from codealmanac.services.diagnostics.requests import DoctorRequest
|
|
30
|
+
from codealmanac.services.runs.requests import (
|
|
31
|
+
ListRunsRequest,
|
|
32
|
+
ReadRunLogRequest,
|
|
33
|
+
ShowRunRequest,
|
|
34
|
+
)
|
|
35
|
+
from codealmanac.services.updates.models import UpdateStatus
|
|
36
|
+
from codealmanac.services.updates.requests import CheckUpdateRequest, RunUpdateRequest
|
|
37
|
+
|
|
38
|
+
ADMIN_COMMANDS = frozenset(("automation", "doctor", "jobs", "update"))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def is_admin_command(command: str | None) -> bool:
|
|
42
|
+
return command in ADMIN_COMMANDS
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def dispatch_admin(args: argparse.Namespace, app: CodeAlmanac) -> int:
|
|
46
|
+
if args.command == "doctor":
|
|
47
|
+
report = app.diagnostics.check(DoctorRequest(cwd=Path.cwd(), wiki=args.wiki))
|
|
48
|
+
render_doctor(report, json_output=args.json)
|
|
49
|
+
return 0
|
|
50
|
+
if args.command == "update":
|
|
51
|
+
if args.check:
|
|
52
|
+
plan = app.updates.check(CheckUpdateRequest())
|
|
53
|
+
render_update_plan(plan, json_output=args.json)
|
|
54
|
+
return 0
|
|
55
|
+
result = app.updates.run(RunUpdateRequest())
|
|
56
|
+
render_update_result(result, json_output=args.json)
|
|
57
|
+
return 0 if result.status == UpdateStatus.COMPLETED else 1
|
|
58
|
+
if args.command == "jobs":
|
|
59
|
+
if args.jobs_command == "show":
|
|
60
|
+
record = app.runs.show(
|
|
61
|
+
ShowRunRequest(cwd=Path.cwd(), wiki=args.wiki, run_id=args.run_id)
|
|
62
|
+
)
|
|
63
|
+
render_run(record, json_output=args.json)
|
|
64
|
+
return 0
|
|
65
|
+
if args.jobs_command == "logs":
|
|
66
|
+
events = app.runs.log(
|
|
67
|
+
ReadRunLogRequest(cwd=Path.cwd(), wiki=args.wiki, run_id=args.run_id)
|
|
68
|
+
)
|
|
69
|
+
render_run_log(events, json_output=args.json)
|
|
70
|
+
return 0
|
|
71
|
+
records = app.runs.list(
|
|
72
|
+
ListRunsRequest(cwd=Path.cwd(), wiki=args.wiki, limit=args.limit)
|
|
73
|
+
)
|
|
74
|
+
render_runs(records, json_output=args.json)
|
|
75
|
+
return 0
|
|
76
|
+
if args.command == "automation":
|
|
77
|
+
tasks = parse_automation_tasks(args.tasks)
|
|
78
|
+
if args.automation_command == "install":
|
|
79
|
+
cli_config = load_cli_config(app, None)
|
|
80
|
+
result = app.automation.install(
|
|
81
|
+
InstallAutomationRequest(
|
|
82
|
+
cwd=Path.cwd(),
|
|
83
|
+
tasks=tasks,
|
|
84
|
+
every=parse_optional_duration(args.every, "--every"),
|
|
85
|
+
quiet=resolve_automation_quiet(args.quiet, cli_config),
|
|
86
|
+
garden_every=parse_optional_duration(
|
|
87
|
+
args.garden_every,
|
|
88
|
+
"--garden-every",
|
|
89
|
+
),
|
|
90
|
+
garden_off=args.garden_off,
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
render_automation_install(result, json_output=args.json)
|
|
94
|
+
return 0
|
|
95
|
+
if args.automation_command == "uninstall":
|
|
96
|
+
result = app.automation.uninstall(UninstallAutomationRequest(tasks=tasks))
|
|
97
|
+
render_automation_uninstall(result, json_output=args.json)
|
|
98
|
+
return 0
|
|
99
|
+
if args.automation_command == "status":
|
|
100
|
+
result = app.automation.status(AutomationStatusRequest(tasks=tasks))
|
|
101
|
+
render_automation_status(result, json_output=args.json)
|
|
102
|
+
return 0
|
|
103
|
+
raise AssertionError(f"unhandled admin command: {args.command}")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def resolve_automation_quiet(
|
|
107
|
+
value: str | None,
|
|
108
|
+
config: CodeAlmanacConfig,
|
|
109
|
+
) -> timedelta:
|
|
110
|
+
if value is None:
|
|
111
|
+
return config.sync.quiet
|
|
112
|
+
parsed = parse_optional_duration(value, "--quiet")
|
|
113
|
+
if parsed is None:
|
|
114
|
+
raise AssertionError("parsed automation quiet is unexpectedly empty")
|
|
115
|
+
return parsed
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def parse_automation_tasks(values: Sequence[str]) -> tuple[AutomationTask, ...]:
|
|
119
|
+
tasks: list[AutomationTask] = []
|
|
120
|
+
for value in values:
|
|
121
|
+
task = AutomationTask(value)
|
|
122
|
+
if task not in tasks:
|
|
123
|
+
tasks.append(task)
|
|
124
|
+
return tuple(tasks)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from humanfriendly import InvalidTimespan, parse_timespan
|
|
5
|
+
|
|
6
|
+
from codealmanac.app import CodeAlmanac
|
|
7
|
+
from codealmanac.core.errors import ValidationFailed
|
|
8
|
+
from codealmanac.services.config.models import CodeAlmanacConfig
|
|
9
|
+
from codealmanac.services.config.requests import LoadConfigRequest
|
|
10
|
+
from codealmanac.services.harnesses.models import HarnessKind
|
|
11
|
+
from codealmanac.workflows.sync.requests import DEFAULT_SYNC_PENDING_TIMEOUT
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def load_cli_config(app: CodeAlmanac, wiki: str | None) -> CodeAlmanacConfig:
|
|
15
|
+
return app.config.load(LoadConfigRequest(cwd=Path.cwd(), wiki=wiki))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def resolve_harness(value: str | None, config: CodeAlmanacConfig) -> HarnessKind:
|
|
19
|
+
if value is None:
|
|
20
|
+
return config.harness.default
|
|
21
|
+
return HarnessKind(value)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def resolve_quiet(value: str | None, config: CodeAlmanacConfig) -> timedelta:
|
|
25
|
+
if value is None:
|
|
26
|
+
return config.sync.quiet
|
|
27
|
+
return parse_quiet(value)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def resolve_pending_timeout(value: str | None) -> timedelta:
|
|
31
|
+
parsed = parse_optional_duration(value, "--pending-timeout")
|
|
32
|
+
return parsed or DEFAULT_SYNC_PENDING_TIMEOUT
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def parse_quiet(value: str) -> timedelta:
|
|
36
|
+
try:
|
|
37
|
+
seconds = parse_timespan(value)
|
|
38
|
+
except InvalidTimespan as error:
|
|
39
|
+
raise ValidationFailed(f"invalid --quiet value: {value}") from error
|
|
40
|
+
return timedelta(seconds=seconds)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def parse_optional_duration(value: str | None, flag: str) -> timedelta | None:
|
|
44
|
+
if value is None:
|
|
45
|
+
return None
|
|
46
|
+
try:
|
|
47
|
+
seconds = parse_timespan(value)
|
|
48
|
+
except InvalidTimespan as error:
|
|
49
|
+
raise ValidationFailed(f"invalid {flag} value: {value}") from error
|
|
50
|
+
return timedelta(seconds=seconds)
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from codealmanac.app import CodeAlmanac
|
|
6
|
+
from codealmanac.cli.dispatch.admin import dispatch_admin, is_admin_command
|
|
7
|
+
from codealmanac.cli.dispatch.config import (
|
|
8
|
+
load_cli_config,
|
|
9
|
+
resolve_harness,
|
|
10
|
+
resolve_pending_timeout,
|
|
11
|
+
resolve_quiet,
|
|
12
|
+
)
|
|
13
|
+
from codealmanac.cli.render.root import (
|
|
14
|
+
render_build,
|
|
15
|
+
render_garden,
|
|
16
|
+
render_health,
|
|
17
|
+
render_ingest,
|
|
18
|
+
render_page,
|
|
19
|
+
render_reindex,
|
|
20
|
+
render_search,
|
|
21
|
+
render_sync_status,
|
|
22
|
+
render_tagging,
|
|
23
|
+
render_topic,
|
|
24
|
+
render_topic_edge_mutation,
|
|
25
|
+
render_topic_mutation,
|
|
26
|
+
render_topic_rewrite_mutation,
|
|
27
|
+
render_topics,
|
|
28
|
+
render_workspace_drop,
|
|
29
|
+
render_workspace_list,
|
|
30
|
+
)
|
|
31
|
+
from codealmanac.core.errors import ValidationFailed
|
|
32
|
+
from codealmanac.services.health.requests import HealthCheckRequest
|
|
33
|
+
from codealmanac.services.index.requests import ReindexRequest
|
|
34
|
+
from codealmanac.services.pages.requests import ShowPageRequest
|
|
35
|
+
from codealmanac.services.search.requests import SearchPagesRequest
|
|
36
|
+
from codealmanac.services.sources.models import TranscriptApp
|
|
37
|
+
from codealmanac.services.tagging.requests import TagPageRequest, UntagPageRequest
|
|
38
|
+
from codealmanac.services.topics.requests import (
|
|
39
|
+
CreateTopicRequest,
|
|
40
|
+
DeleteTopicRequest,
|
|
41
|
+
DescribeTopicRequest,
|
|
42
|
+
LinkTopicRequest,
|
|
43
|
+
ListTopicsRequest,
|
|
44
|
+
RenameTopicRequest,
|
|
45
|
+
ShowTopicRequest,
|
|
46
|
+
UnlinkTopicRequest,
|
|
47
|
+
)
|
|
48
|
+
from codealmanac.services.workspaces.requests import (
|
|
49
|
+
DropWorkspaceRequest,
|
|
50
|
+
InitializeWorkspaceRequest,
|
|
51
|
+
)
|
|
52
|
+
from codealmanac.workflows.garden.requests import RunGardenRequest
|
|
53
|
+
from codealmanac.workflows.ingest.requests import RunIngestRequest
|
|
54
|
+
from codealmanac.workflows.sync.requests import (
|
|
55
|
+
DEFAULT_SYNC_MAX_FAILED_ATTEMPTS,
|
|
56
|
+
RunSyncRequest,
|
|
57
|
+
RunSyncStatusRequest,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def dispatch(args: argparse.Namespace, app: CodeAlmanac) -> int:
|
|
62
|
+
if args.command == "init":
|
|
63
|
+
workspace = app.workflows.build.initialize(
|
|
64
|
+
InitializeWorkspaceRequest(
|
|
65
|
+
path=Path(args.path),
|
|
66
|
+
almanac_root=Path(args.root) if args.root is not None else None,
|
|
67
|
+
name=args.name,
|
|
68
|
+
description=args.description,
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
print(workspace.name)
|
|
72
|
+
print(
|
|
73
|
+
f"initialized {workspace.almanac_path} "
|
|
74
|
+
f"(registry: {app.workspaces.store.path})",
|
|
75
|
+
file=sys.stderr,
|
|
76
|
+
)
|
|
77
|
+
return 0
|
|
78
|
+
if args.command == "build":
|
|
79
|
+
result = app.workflows.build.build(
|
|
80
|
+
InitializeWorkspaceRequest(
|
|
81
|
+
path=Path(args.path),
|
|
82
|
+
almanac_root=Path(args.root) if args.root is not None else None,
|
|
83
|
+
name=args.name,
|
|
84
|
+
description=args.description,
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
render_build(result.workspace.name, result.index)
|
|
88
|
+
return 0
|
|
89
|
+
if args.command == "ingest":
|
|
90
|
+
cli_config = load_cli_config(app, args.wiki)
|
|
91
|
+
result = app.workflows.ingest.run(
|
|
92
|
+
RunIngestRequest(
|
|
93
|
+
cwd=Path.cwd(),
|
|
94
|
+
wiki=args.wiki,
|
|
95
|
+
inputs=tuple(args.inputs),
|
|
96
|
+
harness=resolve_harness(args.using, cli_config),
|
|
97
|
+
title=args.title,
|
|
98
|
+
guidance=args.guidance,
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
render_ingest(result)
|
|
102
|
+
return 0
|
|
103
|
+
if args.command == "garden":
|
|
104
|
+
cli_config = load_cli_config(app, args.wiki)
|
|
105
|
+
result = app.workflows.garden.run(
|
|
106
|
+
RunGardenRequest(
|
|
107
|
+
cwd=Path.cwd(),
|
|
108
|
+
wiki=args.wiki,
|
|
109
|
+
harness=resolve_harness(args.using, cli_config),
|
|
110
|
+
title=args.title,
|
|
111
|
+
guidance=args.guidance,
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
render_garden(result)
|
|
115
|
+
return 0
|
|
116
|
+
if args.command == "sync" and args.sync_command == "status":
|
|
117
|
+
cli_config = load_cli_config(app, args.wiki)
|
|
118
|
+
result = app.workflows.sync.status(
|
|
119
|
+
RunSyncStatusRequest(
|
|
120
|
+
cwd=Path.cwd(),
|
|
121
|
+
wiki=args.wiki,
|
|
122
|
+
apps=parse_sync_apps(args.source_apps),
|
|
123
|
+
quiet=resolve_quiet(args.quiet, cli_config),
|
|
124
|
+
pending_timeout=resolve_pending_timeout(args.pending_timeout),
|
|
125
|
+
max_failed_attempts=args.max_failed_attempts
|
|
126
|
+
if args.max_failed_attempts is not None
|
|
127
|
+
else DEFAULT_SYNC_MAX_FAILED_ATTEMPTS,
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
render_sync_status(result, json_output=args.json)
|
|
131
|
+
return 0
|
|
132
|
+
if args.command == "sync":
|
|
133
|
+
cli_config = load_cli_config(app, args.wiki)
|
|
134
|
+
result = app.workflows.sync.run(
|
|
135
|
+
RunSyncRequest(
|
|
136
|
+
cwd=Path.cwd(),
|
|
137
|
+
wiki=args.wiki,
|
|
138
|
+
apps=parse_sync_apps(args.source_apps),
|
|
139
|
+
quiet=resolve_quiet(args.quiet, cli_config),
|
|
140
|
+
pending_timeout=resolve_pending_timeout(args.pending_timeout),
|
|
141
|
+
max_failed_attempts=args.max_failed_attempts
|
|
142
|
+
if args.max_failed_attempts is not None
|
|
143
|
+
else DEFAULT_SYNC_MAX_FAILED_ATTEMPTS,
|
|
144
|
+
harness=resolve_harness(args.using, cli_config),
|
|
145
|
+
claim_owner=args.claim_owner,
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
render_sync_status(result, json_output=args.json)
|
|
149
|
+
return 0
|
|
150
|
+
if args.command == "list":
|
|
151
|
+
if args.drop is not None:
|
|
152
|
+
result = app.workspaces.drop(
|
|
153
|
+
DropWorkspaceRequest(selector=args.drop, base_path=Path.cwd())
|
|
154
|
+
)
|
|
155
|
+
render_workspace_drop(result, json_output=args.json)
|
|
156
|
+
return 0
|
|
157
|
+
if args.drop_missing:
|
|
158
|
+
result = app.workspaces.drop_missing()
|
|
159
|
+
render_workspace_drop(result, json_output=args.json)
|
|
160
|
+
return 0
|
|
161
|
+
render_workspace_list(app.workspaces.list_registry(), json_output=args.json)
|
|
162
|
+
return 0
|
|
163
|
+
if args.command == "search":
|
|
164
|
+
rows = app.search.search(
|
|
165
|
+
SearchPagesRequest(
|
|
166
|
+
cwd=Path.cwd(),
|
|
167
|
+
wiki=args.wiki,
|
|
168
|
+
query=args.query,
|
|
169
|
+
topics=tuple(args.topic),
|
|
170
|
+
mentions=args.mentions,
|
|
171
|
+
include_archive=args.include_archive,
|
|
172
|
+
archived=args.archived,
|
|
173
|
+
limit=args.limit,
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
render_search(rows, json_output=args.json)
|
|
177
|
+
return 0
|
|
178
|
+
if args.command == "show":
|
|
179
|
+
page = app.pages.show(
|
|
180
|
+
ShowPageRequest(cwd=Path.cwd(), wiki=args.wiki, slug=args.slug)
|
|
181
|
+
)
|
|
182
|
+
render_page(page, args)
|
|
183
|
+
return 0
|
|
184
|
+
if args.command == "topics":
|
|
185
|
+
if args.topic_command == "show":
|
|
186
|
+
topic = app.topics.show(
|
|
187
|
+
ShowTopicRequest(
|
|
188
|
+
cwd=Path.cwd(),
|
|
189
|
+
wiki=args.wiki,
|
|
190
|
+
slug=args.slug,
|
|
191
|
+
include_descendants=args.descendants,
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
render_topic(topic)
|
|
195
|
+
return 0
|
|
196
|
+
if args.topic_command == "create":
|
|
197
|
+
result = app.topics.create(
|
|
198
|
+
CreateTopicRequest(
|
|
199
|
+
cwd=Path.cwd(),
|
|
200
|
+
wiki=args.wiki,
|
|
201
|
+
name=args.name,
|
|
202
|
+
parents=tuple(args.parent),
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
render_topic_mutation(result)
|
|
206
|
+
return 0
|
|
207
|
+
if args.topic_command == "describe":
|
|
208
|
+
result = app.topics.describe(
|
|
209
|
+
DescribeTopicRequest(
|
|
210
|
+
cwd=Path.cwd(),
|
|
211
|
+
wiki=args.wiki,
|
|
212
|
+
slug=args.slug,
|
|
213
|
+
description=args.description,
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
render_topic_mutation(result)
|
|
217
|
+
return 0
|
|
218
|
+
if args.topic_command == "link":
|
|
219
|
+
result = app.topics.link(
|
|
220
|
+
LinkTopicRequest(
|
|
221
|
+
cwd=Path.cwd(),
|
|
222
|
+
wiki=args.wiki,
|
|
223
|
+
child=args.child,
|
|
224
|
+
parent=args.parent,
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
render_topic_edge_mutation(result)
|
|
228
|
+
return 0
|
|
229
|
+
if args.topic_command == "unlink":
|
|
230
|
+
result = app.topics.unlink(
|
|
231
|
+
UnlinkTopicRequest(
|
|
232
|
+
cwd=Path.cwd(),
|
|
233
|
+
wiki=args.wiki,
|
|
234
|
+
child=args.child,
|
|
235
|
+
parent=args.parent,
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
render_topic_edge_mutation(result)
|
|
239
|
+
return 0
|
|
240
|
+
if args.topic_command == "rename":
|
|
241
|
+
result = app.topics.rename(
|
|
242
|
+
RenameTopicRequest(
|
|
243
|
+
cwd=Path.cwd(),
|
|
244
|
+
wiki=args.wiki,
|
|
245
|
+
old_slug=args.old_slug,
|
|
246
|
+
new_slug=args.new_slug,
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
render_topic_rewrite_mutation(result)
|
|
250
|
+
return 0
|
|
251
|
+
if args.topic_command == "delete":
|
|
252
|
+
result = app.topics.delete(
|
|
253
|
+
DeleteTopicRequest(
|
|
254
|
+
cwd=Path.cwd(),
|
|
255
|
+
wiki=args.wiki,
|
|
256
|
+
slug=args.slug,
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
render_topic_rewrite_mutation(result)
|
|
260
|
+
return 0
|
|
261
|
+
topics = app.topics.list(ListTopicsRequest(cwd=Path.cwd(), wiki=args.wiki))
|
|
262
|
+
render_topics(topics)
|
|
263
|
+
return 0
|
|
264
|
+
if args.command == "health":
|
|
265
|
+
report = app.health.check(HealthCheckRequest(cwd=Path.cwd(), wiki=args.wiki))
|
|
266
|
+
render_health(report, json_output=args.json)
|
|
267
|
+
return 0
|
|
268
|
+
if args.command == "reindex":
|
|
269
|
+
result = app.index.reindex(ReindexRequest(cwd=Path.cwd(), wiki=args.wiki))
|
|
270
|
+
render_reindex(result, json_output=args.json)
|
|
271
|
+
return 0
|
|
272
|
+
if is_admin_command(args.command):
|
|
273
|
+
return dispatch_admin(args, app)
|
|
274
|
+
if args.command == "serve":
|
|
275
|
+
return run_serve(app, args)
|
|
276
|
+
if args.command == "tag":
|
|
277
|
+
result = app.tagging.tag(
|
|
278
|
+
TagPageRequest(
|
|
279
|
+
cwd=Path.cwd(),
|
|
280
|
+
wiki=args.wiki,
|
|
281
|
+
slug=args.slug,
|
|
282
|
+
topics=tuple(args.topics),
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
render_tagging("tagged", "already tagged", result)
|
|
286
|
+
return 0
|
|
287
|
+
if args.command == "untag":
|
|
288
|
+
result = app.tagging.untag(
|
|
289
|
+
UntagPageRequest(
|
|
290
|
+
cwd=Path.cwd(),
|
|
291
|
+
wiki=args.wiki,
|
|
292
|
+
slug=args.slug,
|
|
293
|
+
topics=tuple(args.topics),
|
|
294
|
+
)
|
|
295
|
+
)
|
|
296
|
+
render_tagging("untagged", "not tagged", result)
|
|
297
|
+
return 0
|
|
298
|
+
raise AssertionError(f"unhandled command: {args.command}")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def parse_sync_apps(value: str | None) -> tuple[TranscriptApp, ...]:
|
|
302
|
+
if value is None or value.strip() == "":
|
|
303
|
+
return (TranscriptApp.CLAUDE, TranscriptApp.CODEX)
|
|
304
|
+
apps: list[TranscriptApp] = []
|
|
305
|
+
for raw in value.split(","):
|
|
306
|
+
item = raw.strip()
|
|
307
|
+
try:
|
|
308
|
+
app = TranscriptApp(item)
|
|
309
|
+
except ValueError as error:
|
|
310
|
+
raise ValidationFailed(
|
|
311
|
+
f'invalid --from "{value}" (expected claude,codex)'
|
|
312
|
+
) from error
|
|
313
|
+
if app not in apps:
|
|
314
|
+
apps.append(app)
|
|
315
|
+
if len(apps) == 0:
|
|
316
|
+
raise ValidationFailed("at least one sync source is required")
|
|
317
|
+
return tuple(apps)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def run_serve(app, args: argparse.Namespace) -> int:
|
|
321
|
+
import uvicorn
|
|
322
|
+
|
|
323
|
+
from codealmanac.server.app import create_server_app
|
|
324
|
+
|
|
325
|
+
server = create_server_app(app, Path.cwd(), args.wiki)
|
|
326
|
+
print(f"codealmanac viewer: http://{args.host}:{args.port}")
|
|
327
|
+
uvicorn.run(server, host=args.host, port=args.port, log_level="warning")
|
|
328
|
+
return 0
|