vincs 0.1.0__tar.gz

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 (134) hide show
  1. vincs-0.1.0/.gitignore +52 -0
  2. vincs-0.1.0/PKG-INFO +79 -0
  3. vincs-0.1.0/README.md +52 -0
  4. vincs-0.1.0/hatch_build.py +50 -0
  5. vincs-0.1.0/pyproject.toml +38 -0
  6. vincs-0.1.0/src/vinc/__init__.py +8 -0
  7. vincs-0.1.0/src/vinc/__main__.py +3 -0
  8. vincs-0.1.0/src/vinc/_runtime/api/__init__.py +16 -0
  9. vincs-0.1.0/src/vinc/_runtime/api/activate.py +226 -0
  10. vincs-0.1.0/src/vinc/_runtime/api/auth.py +119 -0
  11. vincs-0.1.0/src/vinc/_runtime/api/backup.py +50 -0
  12. vincs-0.1.0/src/vinc/_runtime/api/changes.py +127 -0
  13. vincs-0.1.0/src/vinc/_runtime/api/cli.py +1035 -0
  14. vincs-0.1.0/src/vinc/_runtime/api/cli_account.py +30 -0
  15. vincs-0.1.0/src/vinc/_runtime/api/cli_output.py +177 -0
  16. vincs-0.1.0/src/vinc/_runtime/api/cli_target.py +48 -0
  17. vincs-0.1.0/src/vinc/_runtime/api/cli_tools.py +230 -0
  18. vincs-0.1.0/src/vinc/_runtime/api/cli_transport.py +122 -0
  19. vincs-0.1.0/src/vinc/_runtime/api/docstore.py +2350 -0
  20. vincs-0.1.0/src/vinc/_runtime/api/docsync.py +163 -0
  21. vincs-0.1.0/src/vinc/_runtime/api/export.py +217 -0
  22. vincs-0.1.0/src/vinc/_runtime/api/fragment.py +1299 -0
  23. vincs-0.1.0/src/vinc/_runtime/api/graph_algo.py +417 -0
  24. vincs-0.1.0/src/vinc/_runtime/api/graphmd.py +620 -0
  25. vincs-0.1.0/src/vinc/_runtime/api/jobs.py +238 -0
  26. vincs-0.1.0/src/vinc/_runtime/api/localmode.py +168 -0
  27. vincs-0.1.0/src/vinc/_runtime/api/mirror_provenance.py +147 -0
  28. vincs-0.1.0/src/vinc/_runtime/api/mirror_status.py +46 -0
  29. vincs-0.1.0/src/vinc/_runtime/api/node_mirror.py +187 -0
  30. vincs-0.1.0/src/vinc/_runtime/api/owner_fold.py +160 -0
  31. vincs-0.1.0/src/vinc/_runtime/api/package_contract.py +50 -0
  32. vincs-0.1.0/src/vinc/_runtime/api/packfile.py +127 -0
  33. vincs-0.1.0/src/vinc/_runtime/api/preference.py +137 -0
  34. vincs-0.1.0/src/vinc/_runtime/api/procutil.py +18 -0
  35. vincs-0.1.0/src/vinc/_runtime/api/query.py +7016 -0
  36. vincs-0.1.0/src/vinc/_runtime/api/refs.py +249 -0
  37. vincs-0.1.0/src/vinc/_runtime/api/remote_client.py +169 -0
  38. vincs-0.1.0/src/vinc/_runtime/api/revstamp.py +141 -0
  39. vincs-0.1.0/src/vinc/_runtime/api/role_contract.py +132 -0
  40. vincs-0.1.0/src/vinc/_runtime/api/role_gate_contract.py +57 -0
  41. vincs-0.1.0/src/vinc/_runtime/api/roles.py +64 -0
  42. vincs-0.1.0/src/vinc/_runtime/api/server.py +2627 -0
  43. vincs-0.1.0/src/vinc/_runtime/api/share.py +566 -0
  44. vincs-0.1.0/src/vinc/_runtime/api/spaceproxy.py +159 -0
  45. vincs-0.1.0/src/vinc/_runtime/api/sync.py +1324 -0
  46. vincs-0.1.0/src/vinc/_runtime/api/syncmerge.py +110 -0
  47. vincs-0.1.0/src/vinc/_runtime/api/textextract.py +167 -0
  48. vincs-0.1.0/src/vinc/_runtime/engines/__init__.py +0 -0
  49. vincs-0.1.0/src/vinc/_runtime/engines/body_candidates.py +94 -0
  50. vincs-0.1.0/src/vinc/_runtime/engines/domain_hierarchy.py +297 -0
  51. vincs-0.1.0/src/vinc/_runtime/engines/embed_pipeline.py +419 -0
  52. vincs-0.1.0/src/vinc/_runtime/engines/fragment_ingest.py +246 -0
  53. vincs-0.1.0/src/vinc/_runtime/engines/mentions_link.py +401 -0
  54. vincs-0.1.0/src/vinc/_runtime/engines/recall_ingest.py +429 -0
  55. vincs-0.1.0/src/vinc/_runtime/store/__init__.py +14 -0
  56. vincs-0.1.0/src/vinc/_runtime/store/backend.py +58 -0
  57. vincs-0.1.0/src/vinc/_runtime/store/chunking.py +162 -0
  58. vincs-0.1.0/src/vinc/_runtime/store/config.py +228 -0
  59. vincs-0.1.0/src/vinc/_runtime/store/diagnostics.py +36 -0
  60. vincs-0.1.0/src/vinc/_runtime/store/durable_generation.py +326 -0
  61. vincs-0.1.0/src/vinc/_runtime/store/embedded_backend.py +2435 -0
  62. vincs-0.1.0/src/vinc/_runtime/store/embedded_schema.py +685 -0
  63. vincs-0.1.0/src/vinc/_runtime/store/encoder_runtime.py +20 -0
  64. vincs-0.1.0/src/vinc/_runtime/store/lexical.py +102 -0
  65. vincs-0.1.0/src/vinc/_runtime/store/migrate.py +522 -0
  66. vincs-0.1.0/src/vinc/_runtime/store/migrate_themes.py +136 -0
  67. vincs-0.1.0/src/vinc/_runtime/store/open_diagnostics.py +5 -0
  68. vincs-0.1.0/src/vinc/_runtime/store/persistence.py +637 -0
  69. vincs-0.1.0/src/vinc/_runtime/store/pkns.py +192 -0
  70. vincs-0.1.0/src/vinc/_runtime/store/procutil.py +68 -0
  71. vincs-0.1.0/src/vinc/_runtime/store/provision.py +83 -0
  72. vincs-0.1.0/src/vinc/_runtime/store/querybase.py +495 -0
  73. vincs-0.1.0/src/vinc/_runtime/store/request_timing.py +130 -0
  74. vincs-0.1.0/src/vinc/_runtime/store/theme_store.py +288 -0
  75. vincs-0.1.0/src/vinc/_runtime/store/wal_guard.py +115 -0
  76. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/__init__.py +22 -0
  77. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/bridge.py +290 -0
  78. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/edverify.py +222 -0
  79. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/entitlement.py +359 -0
  80. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/http_server.py +1655 -0
  81. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/install.py +292 -0
  82. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/keychain.py +88 -0
  83. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/oauth.py +323 -0
  84. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/openapi_v1.json +2227 -0
  85. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/openapi_v1.py +77 -0
  86. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/proxy.py +205 -0
  87. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/remote.py +70 -0
  88. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/rest_api.py +330 -0
  89. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/rest_routes.py +114 -0
  90. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/role_output_gate.py +357 -0
  91. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/roles.py +547 -0
  92. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/rpc.py +291 -0
  93. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/stdio.py +36 -0
  94. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/store.py +579 -0
  95. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/syncclient.py +108 -0
  96. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/toolcall.py +79 -0
  97. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/tools.py +2306 -0
  98. vincs-0.1.0/src/vinc/_runtime/vinc_mcp/viewurl.py +139 -0
  99. vincs-0.1.0/src/vinc/_runtime/vinc_rag/__init__.py +6 -0
  100. vincs-0.1.0/src/vinc/_runtime/vinc_rag/embed/__init__.py +5 -0
  101. vincs-0.1.0/src/vinc/_runtime/vinc_rag/embed/encoder.py +254 -0
  102. vincs-0.1.0/src/vinc/_runtime/vinc_rag/embed/worker.py +987 -0
  103. vincs-0.1.0/src/vinc/_runtime/vinc_rag/rerank.py +397 -0
  104. vincs-0.1.0/src/vinc/_runtime/vinc_rag/retrieval.py +1429 -0
  105. vincs-0.1.0/src/vinc/_runtime/vinc_rag/search_relevance.py +83 -0
  106. vincs-0.1.0/src/vinc/cli.py +22 -0
  107. vincs-0.1.0/src/vinc/integrations/__init__.py +1 -0
  108. vincs-0.1.0/src/vinc/integrations/agent_framework/__init__.py +16 -0
  109. vincs-0.1.0/src/vinc/integrations/agent_framework/approval.py +39 -0
  110. vincs-0.1.0/src/vinc/integrations/agent_framework/output_gate.py +39 -0
  111. vincs-0.1.0/src/vinc/integrations/agent_framework/provider.py +196 -0
  112. vincs-0.1.0/src/vinc/integrations/agent_framework/py.typed +0 -0
  113. vincs-0.1.0/src/vinc/integrations/agent_framework/review.py +42 -0
  114. vincs-0.1.0/src/vinc/integrations/langgraph/__init__.py +41 -0
  115. vincs-0.1.0/src/vinc/integrations/langgraph/approval.py +29 -0
  116. vincs-0.1.0/src/vinc/integrations/langgraph/helpers.py +199 -0
  117. vincs-0.1.0/src/vinc/integrations/langgraph/middleware.py +202 -0
  118. vincs-0.1.0/src/vinc/integrations/langgraph/output_gate.py +71 -0
  119. vincs-0.1.0/src/vinc/integrations/langgraph/py.typed +0 -0
  120. vincs-0.1.0/src/vinc/integrations/langgraph/review.py +64 -0
  121. vincs-0.1.0/src/vinc/sdk/__init__.py +31 -0
  122. vincs-0.1.0/src/vinc/sdk/approval.py +118 -0
  123. vincs-0.1.0/src/vinc/sdk/client.py +629 -0
  124. vincs-0.1.0/src/vinc/sdk/context.py +211 -0
  125. vincs-0.1.0/src/vinc/sdk/episode.py +65 -0
  126. vincs-0.1.0/src/vinc/sdk/errors.py +77 -0
  127. vincs-0.1.0/src/vinc/sdk/handoff.py +34 -0
  128. vincs-0.1.0/src/vinc/sdk/output_gate.py +357 -0
  129. vincs-0.1.0/src/vinc/sdk/package.py +120 -0
  130. vincs-0.1.0/src/vinc/sdk/prefetch.py +91 -0
  131. vincs-0.1.0/src/vinc/sdk/py.typed +0 -0
  132. vincs-0.1.0/src/vinc/sdk/review.py +115 -0
  133. vincs-0.1.0/src/vinc/sdk/tools.py +74 -0
  134. vincs-0.1.0/tests/test_install.py +103 -0
vincs-0.1.0/.gitignore ADDED
@@ -0,0 +1,52 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ venv/
5
+ dist/
6
+ # 예외: 디자인 허브 산출물은 **계약**이라 추적한다. 소비 리포의 사본
7
+ # (app/src/tokens.*.css·theme.ts)이 커밋돼 있으므로 그 비교 대상도 리포에 있어야
8
+ # `sync_out.py --check`(CI 드리프트 게이트)가 성립한다. 없으면 게이트가
9
+ # "[MISSING·허브]" 로 죽는다 — 2026-08-08 CI 실패의 원인.
10
+ !design-hub/dist/
11
+ # 단, Claude Design 푸시 번들은 제외 — 업로드 직전에 만드는 임시 산출물이고
12
+ # 대조할 커밋된 소비 사본이 없다(그쪽은 claude.ai 프로젝트에 산다).
13
+ design-hub/dist/claude-design/
14
+ build/
15
+ *.egg-info/
16
+ models/
17
+ vinc_store
18
+ *.lbug
19
+ vinc_e2e/
20
+ node_modules/
21
+ app/src-tauri/target/
22
+ app/src-tauri/binaries/
23
+ app/src-tauri/gen/
24
+ # .claude/ 는 기기별 설정(launch.json·ui-events.jsonl·worktrees)이라 **어느 깊이에서도**
25
+ # 추적하지 않는다. 예외는 이제 없다. 역할 껍데기는 `agents/shells/` 에서 추적되고
26
+ # (`agents_sync.py --check` 가 CI 에서 대조), 하네스가 읽는 사본은 리포 밖 워크스페이스
27
+ # 루트에 `--install` 이 쓴다. 리포 안 `.claude/agents` 는 체크아웃의 브랜치를 따라
28
+ # 수가 달라지는 두 번째 로스터였다.
29
+ # ⚠ 전에는 루트의 `.claude/agents/` 만 되살리려고 네 줄을 겹쳐 썼다(한 줄로는 예외가
30
+ # 사문이 되거나 `_migration/**/.claude/` 중첩본이 새어 나갔다, 2026-08-23 PR #69).
31
+ # 예외가 사라졌으니 모든 깊이를 잡는 이 한 줄이면 된다.
32
+ .claude/
33
+ # 서명·접속 키 — 커밋되면 되돌릴 수 없다. 지금 키들은 리포 **바깥**(워크스페이스
34
+ # 루트)에 있어 커밋된 적이 없지만, 누가 안으로 옮겨도 막히도록 패턴을 박아 둔다.
35
+ # 업데이터 개인키를 잃으면 배포된 전 사용자가 자동 업데이트에서 영구 이탈한다
36
+ # (복구 경로 없음 — DEPLOYMENT.md §1.6 백업 절차 참조).
37
+ *.key
38
+ *.pem
39
+ *.p12
40
+ *.pfx
41
+
42
+ # uv 캐시 락파일 — 로컬 테스트 실행(`uv run`)이 만든다. 정본 의존성은
43
+ # pyproject.toml 이고, 이 락은 기기별 파생물이라 추적하지 않는다.
44
+ uv.lock
45
+
46
+ # 평가·리허설 부산물 — 모델 가중치 수백 MB 가 여기 앉는다.
47
+ # 이 워킹트리는 세션 공유라 남의 `git add -A` 가 이것을 집는다.
48
+ .cache/
49
+ work/
50
+
51
+ # design-hub capture PNGs are build products of scripts/capture_*.mjs; the manifests are committed
52
+ design-hub/reports/captures/**/*.png
vincs-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.5
2
+ Name: vincs
3
+ Version: 0.1.0
4
+ Summary: Vinc Python SDK, framework integrations and terminal CLI
5
+ Project-URL: Homepage, https://vincs.io
6
+ Project-URL: Documentation, https://vincs.io/docs/
7
+ Author: Vinculums
8
+ License-Expression: MIT
9
+ Requires-Python: >=3.11
10
+ Requires-Dist: httpx<1,>=0.27
11
+ Requires-Dist: pydantic<3,>=2.7
12
+ Provides-Extra: agent-framework
13
+ Requires-Dist: agent-framework-core>=1.8.1; extra == 'agent-framework'
14
+ Provides-Extra: all
15
+ Requires-Dist: agent-framework-core>=1.8.1; extra == 'all'
16
+ Requires-Dist: langchain-core>=1.0; extra == 'all'
17
+ Requires-Dist: langchain>=1.0; extra == 'all'
18
+ Requires-Dist: langgraph>=1.0; extra == 'all'
19
+ Provides-Extra: langgraph
20
+ Requires-Dist: langchain-core>=1.0; extra == 'langgraph'
21
+ Requires-Dist: langchain>=1.0; extra == 'langgraph'
22
+ Requires-Dist: langgraph>=1.0; extra == 'langgraph'
23
+ Provides-Extra: test
24
+ Requires-Dist: anyio>=4; extra == 'test'
25
+ Requires-Dist: pytest>=8; extra == 'test'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # Vinc for Python and the terminal
29
+
30
+ Install the SDK and the `vinc` command together:
31
+
32
+ ```sh
33
+ pip install vincs
34
+ vinc --help
35
+ vinc --version
36
+ ```
37
+
38
+ Use the account graph from the terminal with a member key created in your Vinc
39
+ account. Set `VINC_API_KEY` in your environment, then run:
40
+
41
+ ```sh
42
+ vinc --target account whoami
43
+ vinc --target account search --query "release checklist"
44
+ ```
45
+
46
+ The account CLI uses the same commands and error handling as the desktop's CLI.
47
+ This Python install does not bundle the native local database, extensions or
48
+ embedding model. For a self-contained local CLI, use the signed terminal download
49
+ from [the CLI installation guide](https://vincs.io/docs/cli/#install).
50
+
51
+ ```python
52
+ from vinc import Vinc, AsyncVinc
53
+
54
+ client = Vinc() # reads VINC_API_KEY
55
+ answer = client.brief("release checklist")
56
+ ```
57
+
58
+ Choose integrations using one package name:
59
+
60
+ ```sh
61
+ pip install "vincs[langgraph]"
62
+ pip install "vincs[agent-framework]"
63
+ pip install "vincs[all]"
64
+ ```
65
+
66
+ ```python
67
+ from vinc.integrations.langgraph import get_vinc_context
68
+ from vinc.integrations.agent_framework import VincContextProvider
69
+ ```
70
+
71
+ These modules are built from the same source as `vinc-client`, `vinc-langgraph`
72
+ and `vinc-agent-framework`. Existing installations and imports keep working;
73
+ the unified package does not overwrite their files. Framework imports require
74
+ the corresponding extra. Importing the base SDK does not load a framework or
75
+ start a local database. No conversation is automatically written to Vinc.
76
+
77
+ The Python distribution version is independent of the product version reported
78
+ by `vinc --version`. See the [SDK guide](https://vincs.io/docs/frameworks/) for
79
+ key types, read-only use and explicit writes.
vincs-0.1.0/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # Vinc for Python and the terminal
2
+
3
+ Install the SDK and the `vinc` command together:
4
+
5
+ ```sh
6
+ pip install vincs
7
+ vinc --help
8
+ vinc --version
9
+ ```
10
+
11
+ Use the account graph from the terminal with a member key created in your Vinc
12
+ account. Set `VINC_API_KEY` in your environment, then run:
13
+
14
+ ```sh
15
+ vinc --target account whoami
16
+ vinc --target account search --query "release checklist"
17
+ ```
18
+
19
+ The account CLI uses the same commands and error handling as the desktop's CLI.
20
+ This Python install does not bundle the native local database, extensions or
21
+ embedding model. For a self-contained local CLI, use the signed terminal download
22
+ from [the CLI installation guide](https://vincs.io/docs/cli/#install).
23
+
24
+ ```python
25
+ from vinc import Vinc, AsyncVinc
26
+
27
+ client = Vinc() # reads VINC_API_KEY
28
+ answer = client.brief("release checklist")
29
+ ```
30
+
31
+ Choose integrations using one package name:
32
+
33
+ ```sh
34
+ pip install "vincs[langgraph]"
35
+ pip install "vincs[agent-framework]"
36
+ pip install "vincs[all]"
37
+ ```
38
+
39
+ ```python
40
+ from vinc.integrations.langgraph import get_vinc_context
41
+ from vinc.integrations.agent_framework import VincContextProvider
42
+ ```
43
+
44
+ These modules are built from the same source as `vinc-client`, `vinc-langgraph`
45
+ and `vinc-agent-framework`. Existing installations and imports keep working;
46
+ the unified package does not overwrite their files. Framework imports require
47
+ the corresponding extra. Importing the base SDK does not load a framework or
48
+ start a local database. No conversation is automatically written to Vinc.
49
+
50
+ The Python distribution version is independent of the product version reported
51
+ by `vinc --version`. See the [SDK guide](https://vincs.io/docs/frameworks/) for
52
+ key types, read-only use and explicit writes.
@@ -0,0 +1,50 @@
1
+ """Assemble the unified distribution from the canonical repository packages."""
2
+ from __future__ import annotations
3
+
4
+ import re
5
+ import tempfile
6
+ from pathlib import Path
7
+
8
+ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
9
+
10
+
11
+ class CustomBuildHook(BuildHookInterface):
12
+ def initialize(self, version, build_data):
13
+ root = Path(self.root)
14
+ # sdist 에는 이미 조립된 소스가 있다. 저장소 밖에서 다시 빌드할 수 있어야 한다.
15
+ if (root / "src/vinc/sdk/client.py").is_file():
16
+ return
17
+ repo = root.parents[2]
18
+ sources = {
19
+ "sdk": repo / "integrations/client/python/src/vinc_client",
20
+ "integrations/langgraph": repo / "integrations/langgraph/python/src/vinc_langgraph",
21
+ "integrations/agent_framework": repo / "integrations/agent-framework/python/src/vinc_agent_framework",
22
+ **{f"_runtime/{name}": repo / name
23
+ for name in ("api", "store", "engines", "vinc_mcp", "vinc_rag")},
24
+ }
25
+ self._stage = tempfile.TemporaryDirectory(prefix="vincs-build-")
26
+ stage = Path(self._stage.name)
27
+ prefix = "src/vinc" if self.target_name == "sdist" else "vinc"
28
+ for destination, source in sources.items():
29
+ if not source.is_dir():
30
+ raise RuntimeError(f"canonical package source missing: {source}")
31
+ for path in sorted(source.rglob("*")):
32
+ if not path.is_file() or "__pycache__" in path.parts:
33
+ continue
34
+ if path.suffix not in {".py", ".json"} and path.name != "py.typed":
35
+ continue
36
+ relative = Path(destination) / path.relative_to(source)
37
+ target = stage / relative
38
+ target.parent.mkdir(parents=True, exist_ok=True)
39
+ if path.suffix == ".py" and destination.startswith("integrations/"):
40
+ # 별도 배포판의 파일을 덮어쓰지 않고 같은 SDK 소스에 연결한다.
41
+ text = path.read_text(encoding="utf-8")
42
+ text = re.sub(r"(?m)^(from\s+)vinc_client(?=[.\s])", r"\1vinc.sdk", text)
43
+ target.write_text(text, encoding="utf-8")
44
+ else:
45
+ target.write_bytes(path.read_bytes())
46
+ build_data["force_include"][str(target)] = f"{prefix}/{relative.as_posix()}"
47
+
48
+ def finalize(self, version, build_data, artifact_path):
49
+ if hasattr(self, "_stage"):
50
+ self._stage.cleanup()
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "vincs"
7
+ version = "0.1.0"
8
+ description = "Vinc Python SDK, framework integrations and terminal CLI"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ authors = [{ name = "Vinculums" }]
13
+ dependencies = ["httpx>=0.27,<1", "pydantic>=2.7,<3"]
14
+
15
+ [project.optional-dependencies]
16
+ langgraph = ["langgraph>=1.0", "langchain-core>=1.0", "langchain>=1.0"]
17
+ agent-framework = ["agent-framework-core>=1.8.1"]
18
+ all = ["vincs[langgraph,agent-framework]"]
19
+ test = ["pytest>=8", "anyio>=4"]
20
+
21
+ [project.scripts]
22
+ vinc = "vinc.cli:main"
23
+
24
+ [project.urls]
25
+ Homepage = "https://vincs.io"
26
+ Documentation = "https://vincs.io/docs/"
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["src/vinc"]
30
+
31
+ [tool.hatch.build.targets.sdist]
32
+ include = ["src", "pyproject.toml", "README.md", "hatch_build.py", "tests"]
33
+
34
+ [tool.hatch.build.hooks.custom]
35
+ path = "hatch_build.py"
36
+
37
+ [tool.pytest.ini_options]
38
+ testpaths = ["tests"]
@@ -0,0 +1,8 @@
1
+ """Vinc SDK. Framework dependencies are loaded only by their integration."""
2
+ from .sdk import * # noqa: F403
3
+ from .sdk import AsyncVincClient, VincClient, __all__ as _sdk_exports
4
+
5
+ __version__ = "0.1.0"
6
+ Vinc = VincClient
7
+ AsyncVinc = AsyncVincClient
8
+ __all__ = [*_sdk_exports, "Vinc", "AsyncVinc"]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,16 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Query API — read-only entry points over the memory store.
3
+
4
+ Three entry points sit on top of the ingestion engines:
5
+ recall : episodic memory retrieval (Episode nodes, time-ordered)
6
+ search : hybrid vector + BM25 retrieval over Chunk nodes
7
+ decide : decision support — graph traversal over policy/concept relations
8
+
9
+ Each entry point builds a deterministic *query plan* (testable without a
10
+ database) and, when a Neo4j driver is available, executes it.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from .query import QueryPlan, decide, recall, search
15
+
16
+ __all__ = ["QueryPlan", "recall", "search", "decide"]
@@ -0,0 +1,226 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Activate — make a slice of owned memory *portable* to an external AI.
3
+
4
+ P3 of the memory IA ("내 AI로 이식"). Given recall rows (owner-scoped, optionally
5
+ preference-ranked), build a **context pack** in two portable shapes:
6
+
7
+ - a Markdown brief (``pack.md``) a human or any LLM can read directly;
8
+ - a structured JSON (``pack.json``) for programmatic ingestion;
9
+
10
+ plus an **MCP resource manifest** (``mcp.json``) — the connector contract an MCP
11
+ server (mounted by weg-9000-orchestration) serves so a foreign AI can pull these
12
+ episodes as ``memory://{owner}/{id}`` resources.
13
+
14
+ Builders are pure (no I/O, no wall clock — ``generated_at`` is injected) so the
15
+ pack is deterministic and testable. ``activate`` orchestrates recall → pack and
16
+ optionally writes the three files.
17
+
18
+ python -m api.cli activate --owner user:weg1456 --rank preference --out ./pack
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ from dataclasses import dataclass, field
24
+ from pathlib import Path
25
+
26
+ from . import query as q
27
+
28
+ MCP_SCHEMA_VERSION = "2024-11-05"
29
+ _URI_SCHEME = "memory"
30
+
31
+
32
+ def _uri(owner: str, episode_id: str) -> str:
33
+ return f"{_URI_SCHEME}://{owner}/{episode_id}"
34
+
35
+
36
+ @dataclass
37
+ class ContextPack:
38
+ owner: str
39
+ query: str
40
+ generated_at: str | None
41
+ episodes: list[dict]
42
+ learner_model: list[dict] = field(default_factory=list) # how this person learns (U-patterns)
43
+ theme_id: str | None = None
44
+ # 주제 어휘(선택) — **authored 만** 실린다. proposed(재가 전 잠정)는 팩·내보내기에
45
+ # 하중을 지지 않는다(THESIS §6 잠정상태 · D11-R2). activate() 가 필터해 넣어준다.
46
+ vocabulary: list[dict] = field(default_factory=list)
47
+
48
+ # ── JSON shape (programmatic ingestion) ──────────────────────────────────
49
+ def to_json(self) -> dict:
50
+ out = {
51
+ "kind": "weg-9000-memory/context-pack",
52
+ "owner": self.owner,
53
+ "query": self.query,
54
+ "generated_at": self.generated_at,
55
+ "episode_count": len(self.episodes),
56
+ "episodes": self.episodes,
57
+ "learner_model": self.learner_model,
58
+ }
59
+ if self.theme_id is not None:
60
+ out["theme"] = {"theme_id": self.theme_id, "vocabulary": self.vocabulary}
61
+ return out
62
+
63
+ def _learner_section(self) -> list[str]:
64
+ if not self.learner_model:
65
+ return []
66
+ out = [
67
+ "",
68
+ f"## 학습법 — how {self.owner} learns",
69
+ "",
70
+ "> 이 개인의 학습 방식(인지 지문). 외부 AI는 이 방식에 맞춰 나를 도와야 한다.",
71
+ "",
72
+ ]
73
+ for p in self.learner_model:
74
+ meta = []
75
+ if p.get("domain"):
76
+ meta.append(f"domain: {p['domain']}")
77
+ if p.get("observations") is not None:
78
+ meta.append(f"n={p['observations']}")
79
+ head = f"- **{p.get('code', '')} {p.get('name', '')}**".rstrip()
80
+ if meta:
81
+ head += f" ({' · '.join(meta)})"
82
+ out.append(head)
83
+ desc = (p.get("description") or "").strip()
84
+ if desc:
85
+ out.append(f" - {desc}")
86
+ return out
87
+
88
+ # ── Markdown brief (human / LLM-readable) ────────────────────────────────
89
+ def to_markdown(self) -> str:
90
+ parts = [
91
+ f"# Memory Context Pack — {self.owner}",
92
+ "",
93
+ f"> query: \"{self.query}\" · generated: {self.generated_at or 'n/a'} "
94
+ f"· {len(self.episodes)} episode(s) · {len(self.learner_model)} learning-pattern(s)",
95
+ "> Portable memory brief for ingestion into an external AI. "
96
+ "Source: weg-9000-memory.",
97
+ "",
98
+ ]
99
+ if not self.episodes:
100
+ parts.append("_(no episodes in scope)_")
101
+ for ep in self.episodes:
102
+ pin = " 📌" if ep.get("pinned") else ""
103
+ tags = ep.get("tags") or []
104
+ meta = [f"date: {ep.get('date', 'n/a')}"]
105
+ if ep.get("domain"):
106
+ meta.append(f"domain: {ep['domain']}")
107
+ if tags:
108
+ meta.append("tags: " + ", ".join(tags))
109
+ if ep.get("score") is not None:
110
+ meta.append(f"score: {ep['score']}")
111
+ parts.append(f"## {ep.get('title') or ep.get('id', 'untitled')}{pin}")
112
+ parts.append("- " + " · ".join(meta))
113
+ summary = (ep.get("summary") or "").strip()
114
+ if summary:
115
+ parts += ["", summary]
116
+ parts += ["", "---", ""]
117
+ parts += self._learner_section()
118
+ return "\n".join(parts) + "\n"
119
+
120
+ # ── MCP resource manifest (connector contract) ───────────────────────────
121
+ def to_mcp_manifest(self) -> dict:
122
+ resources = [
123
+ {
124
+ "uri": _uri(self.owner, ep.get("id", "")),
125
+ "name": ep.get("title") or ep.get("id", ""),
126
+ "mimeType": "text/markdown",
127
+ "description": (ep.get("summary") or "")[:120],
128
+ }
129
+ for ep in self.episodes
130
+ ]
131
+ if self.learner_model:
132
+ resources.append({
133
+ "uri": _uri(self.owner, "_learner"),
134
+ "name": f"learning method ({self.owner})",
135
+ "mimeType": "text/markdown",
136
+ "description": f"{len(self.learner_model)} learning pattern(s) — how this person learns",
137
+ })
138
+ return {
139
+ "schemaVersion": MCP_SCHEMA_VERSION,
140
+ "owner": self.owner,
141
+ "generated_at": self.generated_at,
142
+ "resources": resources,
143
+ "pack": {
144
+ "uri": _uri(self.owner, "_pack"),
145
+ "name": f"context pack ({self.owner})",
146
+ "mimeType": "application/json",
147
+ "description": f"{len(self.episodes)} episode(s), query=\"{self.query}\"",
148
+ },
149
+ }
150
+
151
+
152
+ def build_context_pack(rows: list[dict], *, owner: str, query: str = "",
153
+ generated_at: str | None = None,
154
+ learner_model: list[dict] | None = None,
155
+ theme_id: str | None = None,
156
+ vocabulary: list[dict] | None = None) -> ContextPack:
157
+ """Assemble a ContextPack from recall rows (pure). ``learner_model`` is the
158
+ person's LearnerPattern rows (how they learn) — carried so an export is "my
159
+ learning *and how I learn it*". ``vocabulary`` (authored 만) 은 주제 타입 어휘를
160
+ 함께 이전한다 — 어휘는 소유·이전 단위(THESIS T4·D11-R2 §8)."""
161
+ episodes = [dict(r) for r in rows]
162
+ return ContextPack(owner=owner, query=query, generated_at=generated_at,
163
+ episodes=episodes, learner_model=[dict(p) for p in (learner_model or [])],
164
+ theme_id=theme_id, vocabulary=[dict(t) for t in (vocabulary or [])])
165
+
166
+
167
+ def write_pack(pack: ContextPack, out_dir: str | Path) -> dict:
168
+ """Write pack.md / pack.json / mcp.json into ``out_dir``. Returns the paths."""
169
+ out = Path(out_dir)
170
+ out.mkdir(parents=True, exist_ok=True)
171
+ md_path = out / "pack.md"
172
+ json_path = out / "pack.json"
173
+ mcp_path = out / "mcp.json"
174
+ md_path.write_text(pack.to_markdown(), encoding="utf-8")
175
+ json_path.write_text(json.dumps(pack.to_json(), ensure_ascii=False, indent=2), encoding="utf-8")
176
+ mcp_path.write_text(json.dumps(pack.to_mcp_manifest(), ensure_ascii=False, indent=2), encoding="utf-8")
177
+ return {"markdown": str(md_path), "json": str(json_path), "mcp": str(mcp_path)}
178
+
179
+
180
+ # ── Public entry point ───────────────────────────────────────────────────────
181
+
182
+ def activate(query: str = "", *, owner: str, tenant: str | None = None,
183
+ domain: str | None = None, workspace: str | None = None,
184
+ tags: list[str] | None = None, rank: str = "preference", limit: int = 20,
185
+ include_model: bool = False,
186
+ generated_at: str | None = None, out_dir: str | Path | None = None,
187
+ theme_id: str | None = None, themes=None,
188
+ cfg=None, store=None) -> dict:
189
+ """Recall owner-scoped records and build a portable context pack.
190
+
191
+ ``domain`` (free, user-defined) optionally scopes the pack to one dataset
192
+ partition; omit to export across all of the owner's domains. Defaults to
193
+ ``rank="preference"`` — an export leads with the owner's preference signals.
194
+
195
+ ``include_model`` (**default False** — Vinc 기본 export 은 learner 섹션 부재:
196
+ 설계 §13 D6 · WO §3 F6, 학습 프레이밍 미승계 §1). 마이그레이션(A+2, §10)의
197
+ 무손실 이관 시에만 ``True`` 로 learner 신호(LearnerPattern)를 포함한다 — 이때만
198
+ pack 에 학습법 섹션이 실린다(D4 는 pack 이 신호를 담을 수 **있게** 하는 것이지
199
+ 기본 노출이 아니다). Preference 신호(pinned/tags/access_count/last_access)는
200
+ rank 무관 항상 수록된다(§10·D4). Writes three files when ``out_dir`` given."""
201
+ res = q.recall(query, owner=owner, tenant=tenant, domain=domain,
202
+ workspace=workspace, tags=tags, rank=rank, limit=limit, cfg=cfg, store=store)
203
+ model_rows = (q.learner_model(owner=owner, cfg=cfg, store=store).get("rows", [])
204
+ if include_model else [])
205
+ # 주제 어휘 동봉(선택) — **authored 만**. proposed(재가 전)는 팩에 실리지 않는다
206
+ # (THESIS §6 잠정상태 · D11-R2 수용기준). 단일 필터 라인.
207
+ vocabulary = ([t for t in themes.list_types(theme_id) if t.get("status") == "authored"]
208
+ if (theme_id and themes is not None) else None)
209
+ pack = build_context_pack(res.get("rows", []), owner=owner, query=query,
210
+ generated_at=generated_at, learner_model=model_rows,
211
+ theme_id=theme_id, vocabulary=vocabulary)
212
+ out: dict = {
213
+ "owner": owner,
214
+ "episode_count": len(pack.episodes),
215
+ "learner_pattern_count": len(pack.learner_model),
216
+ "executed": res.get("executed", False),
217
+ }
218
+ if out_dir is not None:
219
+ out["written"] = write_pack(pack, out_dir)
220
+ else:
221
+ out["pack"] = {
222
+ "markdown": pack.to_markdown(),
223
+ "json": pack.to_json(),
224
+ "mcp": pack.to_mcp_manifest(),
225
+ }
226
+ return out
@@ -0,0 +1,119 @@
1
+ # -*- coding: utf-8 -*-
2
+ """로컬 로그인 코어 — 온보딩 프로필 + 세션 토큰(설계 [[vinc.spec.architecture §4]], F4).
3
+
4
+ Vinc 는 단일 사용자·오프라인 앱이다. "로그인"은 외부 인증 서버 왕복이 아니라
5
+ **로컬 자격 파일에 대한 패스프레이즈 검증**이다(F4 — 네트워크 왕복 없음). 검증에
6
+ 성공하면 사이드카가 임의 **세션 토큰**을 발급하고, 데스크톱 셸(Tauri)이 이후 모든
7
+ HTTP 요청에 ``Authorization: Bearer <token>`` 로 실어 보낸다. 루프백 바인딩
8
+ (127.0.0.1) + 토큰이 이중 방어다(§3-1).
9
+
10
+ 자격 해시는 **argon2id**(설계 §3-1 명시)로, ``argon2-cffi`` 옵션 extra(``vinc[server]``)
11
+ 에서 lazy-import 한다 — 코어는 의존성 0(F5: 키 없이 기동). 미설치 환경에선 온보딩·
12
+ 패스프레이즈 검증만 불가하고(명확한 오류) 앱 기동·조회는 그대로 동작한다.
13
+
14
+ 토큰 발급·검증은 stdlib ``secrets`` 만 쓴다(argon2 불요) — 어떤 경로도 소켓을 열지
15
+ 않는다(F4 실측: 소켓 차단 하에서도 동작).
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import secrets
21
+ from pathlib import Path
22
+
23
+ # argon2id 파라미터(자격 파일 헤더에 함께 저장 — 재해시 없이 검증 가능).
24
+ # 대화형 로그인 1회 검증 기준의 보수적 기본값. (RFC 9106 2nd 권장 근방.)
25
+ _ARGON2_TIME_COST = 3
26
+ _ARGON2_MEMORY_COST = 64 * 1024 # 64 MiB
27
+ _ARGON2_PARALLELISM = 4
28
+
29
+ _CRED_KIND = "vinc/local-credential"
30
+
31
+
32
+ class AuthUnavailable(RuntimeError):
33
+ """argon2 extra 미설치 — 온보딩/패스프레이즈 검증 불가(``pip install vinc[server]``)."""
34
+
35
+
36
+ def _argon2():
37
+ """argon2 PasswordHasher 를 lazy-import(코어 의존성 0 유지 — F5). 미설치 시
38
+ :class:`AuthUnavailable`."""
39
+ try:
40
+ from argon2 import PasswordHasher, Type
41
+ except ImportError as exc: # pragma: no cover - 설치 여부에 따라
42
+ raise AuthUnavailable(
43
+ "argon2id 자격에는 'vinc[server]' extra 가 필요하다 "
44
+ "(pip install argon2-cffi). 코어·조회는 이것 없이 동작한다."
45
+ ) from exc
46
+ return PasswordHasher(time_cost=_ARGON2_TIME_COST, memory_cost=_ARGON2_MEMORY_COST,
47
+ parallelism=_ARGON2_PARALLELISM, type=Type.ID)
48
+
49
+
50
+ def cred_path(data_dir: str | Path) -> Path:
51
+ """자격 파일 경로. 데이터 디렉토리 옆 ``auth.json``(§3-2 — 데이터·설정 동거)."""
52
+ return Path(data_dir).parent / "auth.json"
53
+
54
+
55
+ # ── 온보딩 / 검증 ────────────────────────────────────────────────────────────
56
+
57
+ def create_profile(passphrase: str, *, path: str | Path, force: bool = False) -> dict:
58
+ """온보딩: 패스프레이즈를 argon2id 로 해시해 자격 파일을 만든다(1회). 이미
59
+ 있으면 ``force`` 없이 덮어쓰지 않는다(계정 유실 방지)."""
60
+ p = Path(path)
61
+ if p.exists() and not force:
62
+ raise FileExistsError(f"자격 파일 이미 존재: {p} (재설정은 force=True)")
63
+ if not passphrase:
64
+ raise ValueError("빈 패스프레이즈 금지")
65
+ ph = _argon2()
66
+ p.parent.mkdir(parents=True, exist_ok=True)
67
+ payload = {"kind": _CRED_KIND, "algo": "argon2id", "hash": ph.hash(passphrase)}
68
+ p.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
69
+ # 자격 파일은 소유자만 읽도록(단일 사용자 데스크톱 — §3-3).
70
+ try:
71
+ p.chmod(0o600)
72
+ except OSError:
73
+ pass
74
+ return {"created": str(p), "algo": "argon2id"}
75
+
76
+
77
+ def verify_passphrase(passphrase: str, *, path: str | Path) -> bool:
78
+ """패스프레이즈를 자격 파일과 대조(로컬만 — 네트워크 왕복 없음, F4). 파일
79
+ 부재·불일치 시 False. argon2 미설치 시 :class:`AuthUnavailable`."""
80
+ p = Path(path)
81
+ if not p.exists():
82
+ return False
83
+ from argon2.exceptions import VerifyMismatchError # lazy — extra 없으면 아래 _argon2 가 먼저 raise
84
+ ph = _argon2()
85
+ try:
86
+ stored = json.loads(p.read_text(encoding="utf-8")).get("hash", "")
87
+ except (OSError, ValueError):
88
+ return False
89
+ try:
90
+ return bool(ph.verify(stored, passphrase))
91
+ except VerifyMismatchError:
92
+ return False
93
+ except Exception: # noqa: BLE001 - 손상 해시 등은 인증 실패로 취급
94
+ return False
95
+
96
+
97
+ # ── 세션 토큰 ────────────────────────────────────────────────────────────────
98
+
99
+ def issue_token(nbytes: int = 32) -> str:
100
+ """새 세션 토큰 발급(stdlib secrets — 소켓 없음, F4). 프로세스 수명 동안만 유효."""
101
+ return secrets.token_urlsafe(nbytes)
102
+
103
+
104
+ class SessionAuth:
105
+ """사이드카 세션 토큰 보관·검증. 기동 시 1개 발급, 셸에 전달. 모든 요청의
106
+ ``Authorization: Bearer`` 헤더를 상수시간 비교한다."""
107
+
108
+ def __init__(self, token: str | None = None):
109
+ self.token = token or issue_token()
110
+
111
+ def check_header(self, header_value: str | None) -> bool:
112
+ """``Authorization`` 헤더 값이 세션 토큰과 일치하는지(Bearer 스킴). 상수시간
113
+ 비교(secrets.compare_digest)로 타이밍 누출 방지."""
114
+ if not header_value:
115
+ return False
116
+ parts = header_value.split(None, 1)
117
+ if len(parts) != 2 or parts[0].lower() != "bearer":
118
+ return False
119
+ return secrets.compare_digest(parts[1], self.token)