holoscript-holorepo 0.2.0__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.
- holoscript_holorepo/__init__.py +225 -0
- holoscript_holorepo/cli.py +58 -0
- holoscript_holorepo-0.2.0.dist-info/METADATA +69 -0
- holoscript_holorepo-0.2.0.dist-info/RECORD +8 -0
- holoscript_holorepo-0.2.0.dist-info/WHEEL +5 -0
- holoscript_holorepo-0.2.0.dist-info/entry_points.txt +2 -0
- holoscript_holorepo-0.2.0.dist-info/licenses/LICENSE +21 -0
- holoscript_holorepo-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""holoscript-holorepo: Python companion for HoloRepo.
|
|
2
|
+
|
|
3
|
+
The client is config/env driven. It can read any Postgres reachable by `psql`,
|
|
4
|
+
or an explicitly configured owned node over SSH + docker exec. No host, IP, or
|
|
5
|
+
container is assumed by default.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import subprocess
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from urllib.parse import urlsplit, urlunsplit
|
|
16
|
+
|
|
17
|
+
__version__ = "0.2.0"
|
|
18
|
+
RELEASE_LANE = "v0-preview"
|
|
19
|
+
CONFIG_SCHEMA = "holorepo.config.v1"
|
|
20
|
+
|
|
21
|
+
_MUTATION = re.compile(r"\b(insert|update|delete|drop|alter|create|truncate|grant|revoke|copy|merge|call)\b", re.I)
|
|
22
|
+
_READ_HEAD = re.compile(r"^(select|with|explain|show|table)\b", re.I)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def default_config() -> dict:
|
|
26
|
+
return {
|
|
27
|
+
"schema": CONFIG_SCHEMA,
|
|
28
|
+
"productName": "HoloRepo",
|
|
29
|
+
"activeProfile": "local",
|
|
30
|
+
"storage": {"kind": "filesystem", "root": ".holorepo/storage"},
|
|
31
|
+
"git": {"backend": "local-bare", "root": ".holorepo/git"},
|
|
32
|
+
"database": {"transport": "direct-psql", "connectionEnv": "HOLOREPO_DATABASE_URL"},
|
|
33
|
+
"knowledgeStore": {"database": "knowledge", "table": "memory_entries"},
|
|
34
|
+
"agents": {"maxConcurrentWriters": 100},
|
|
35
|
+
"profiles": {
|
|
36
|
+
"jetsonExample": {
|
|
37
|
+
"note": "Example only. HoloRepo no longer assumes this host, container, or path.",
|
|
38
|
+
"database": {
|
|
39
|
+
"transport": "ssh-docker-psql",
|
|
40
|
+
"ssh": {"hostEnv": "HOLOREPO_SSH", "keyEnv": "HOLOREPO_SSH_KEY"},
|
|
41
|
+
"docker": {"containerEnv": "HOLOREPO_POSTGRES_CONTAINER"},
|
|
42
|
+
"userEnv": "HOLOREPO_DATABASE_USER",
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _merge(a: dict, b: dict) -> dict:
|
|
50
|
+
out = dict(a)
|
|
51
|
+
for key, value in (b or {}).items():
|
|
52
|
+
if isinstance(value, dict) and isinstance(out.get(key), dict):
|
|
53
|
+
out[key] = _merge(out[key], value)
|
|
54
|
+
else:
|
|
55
|
+
out[key] = value
|
|
56
|
+
return out
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def load_config(path: str | None = None, env: dict | None = None) -> dict:
|
|
60
|
+
env = os.environ if env is None else env
|
|
61
|
+
target = Path(path or env.get("HOLOREPO_CONFIG", "holorepo.config.json"))
|
|
62
|
+
cfg = json.loads(target.read_text()) if target.exists() else default_config()
|
|
63
|
+
if "$schema" in cfg and "schema" not in cfg:
|
|
64
|
+
cfg["schema"] = cfg["$schema"]
|
|
65
|
+
profile = env.get("HOLOREPO_CONFIG_PROFILE") or cfg.get("activeProfile") or cfg.get("profile")
|
|
66
|
+
if profile and cfg.get("profiles", {}).get(profile):
|
|
67
|
+
cfg = _merge(cfg, cfg["profiles"][profile])
|
|
68
|
+
cfg["activeProfile"] = profile
|
|
69
|
+
if env.get("HOLOREPO_DATABASE_URL"):
|
|
70
|
+
cfg["database"] = _merge(cfg.get("database", {}), {"transport": "direct-psql", "connectionEnv": "HOLOREPO_DATABASE_URL"})
|
|
71
|
+
elif env.get("DATABASE_URL"):
|
|
72
|
+
cfg["database"] = _merge(cfg.get("database", {}), {"transport": "direct-psql", "connectionEnv": "DATABASE_URL"})
|
|
73
|
+
return cfg
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _sha256(text: str) -> str:
|
|
77
|
+
return "sha256:" + hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def is_read_only_sql(sql: str) -> bool:
|
|
81
|
+
s = (sql or "").strip().rstrip(";").strip()
|
|
82
|
+
if not s:
|
|
83
|
+
return False
|
|
84
|
+
if ";" in s:
|
|
85
|
+
return False
|
|
86
|
+
if s.startswith("\\"):
|
|
87
|
+
return True
|
|
88
|
+
return bool(_READ_HEAD.match(s)) and not _MUTATION.search(s)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def sanitize_term(term: str) -> str:
|
|
92
|
+
return re.sub(r"\s+", " ", re.sub(r"[%_\\']", " ", term or "")).strip()[:120]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def safe_table(name: str = "memory_entries") -> str:
|
|
96
|
+
if not re.match(r"^[a-z_][a-z0-9_]*$", name or "", re.I):
|
|
97
|
+
raise ValueError(f"unsafe SQL identifier: {name}")
|
|
98
|
+
return name
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def build_ks_query(term: str, domain: str | None = None, limit: int = 10, table: str = "memory_entries") -> str:
|
|
102
|
+
t = sanitize_term(term)
|
|
103
|
+
tbl = safe_table(table)
|
|
104
|
+
dom = f" and domain = '{sanitize_term(domain)}'" if domain else ""
|
|
105
|
+
lim = max(1, min(50, int(limit) if str(limit).isdigit() else 10))
|
|
106
|
+
return (
|
|
107
|
+
"select id, domain, type, coalesce(confidence,0), "
|
|
108
|
+
"left(regexp_replace(content, '\\s+', ' ', 'g'), 150) "
|
|
109
|
+
f"from {tbl} where content ILIKE '%{t}%'{dom} and coalesce(is_stale,false)=false "
|
|
110
|
+
f"order by coalesce(reuse_count,0) desc, updated_at desc limit {lim}"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def redact_connection_string(value: str | None) -> str | None:
|
|
115
|
+
if not value:
|
|
116
|
+
return value
|
|
117
|
+
try:
|
|
118
|
+
parts = urlsplit(value)
|
|
119
|
+
host = parts.hostname or "host"
|
|
120
|
+
port = f":{parts.port}" if parts.port else ""
|
|
121
|
+
user = "redacted@" if parts.username else ""
|
|
122
|
+
return urlunsplit((parts.scheme, f"{user}{host}{port}", parts.path, parts.query, parts.fragment))
|
|
123
|
+
except Exception:
|
|
124
|
+
return re.sub(r"//[^:@/\s]+:[^@/\s]+@", "//redacted:redacted@", value)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _split_password_from_url(value: str):
|
|
128
|
+
if not value:
|
|
129
|
+
return value, None
|
|
130
|
+
try:
|
|
131
|
+
parts = urlsplit(value)
|
|
132
|
+
if not parts.password:
|
|
133
|
+
return value, None
|
|
134
|
+
host = parts.hostname or ""
|
|
135
|
+
port = f":{parts.port}" if parts.port else ""
|
|
136
|
+
netloc = f"{parts.username}@{host}{port}" if parts.username else f"{host}{port}"
|
|
137
|
+
return urlunsplit((parts.scheme, netloc, parts.path, parts.query, parts.fragment)), parts.password
|
|
138
|
+
except Exception:
|
|
139
|
+
return value, None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def redact_db_summary(db: str, config: dict | None = None, env: dict | None = None) -> str:
|
|
143
|
+
env = os.environ if env is None else env
|
|
144
|
+
config = load_config(env=env) if config is None else config
|
|
145
|
+
db_cfg = config.get("database", {})
|
|
146
|
+
transport = db_cfg.get("transport", "direct-psql")
|
|
147
|
+
env_name = db_cfg.get("connectionEnv")
|
|
148
|
+
if env_name and env.get(env_name):
|
|
149
|
+
return f"{redact_connection_string(env[env_name])} db={db} ({transport}, redacted)"
|
|
150
|
+
return f"postgres://<{env_name or 'HOLOREPO_DATABASE_URL'}>/{db} ({transport}, redacted)"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _run_psql(db: str, sql: str, meta: bool = False, config: dict | None = None, env: dict | None = None):
|
|
154
|
+
env = os.environ if env is None else env
|
|
155
|
+
config = load_config(env=env) if config is None else config
|
|
156
|
+
db_cfg = config.get("database", {})
|
|
157
|
+
transport = db_cfg.get("transport", "direct-psql")
|
|
158
|
+
password = None
|
|
159
|
+
if transport == "direct-psql":
|
|
160
|
+
env_name = db_cfg.get("connectionEnv", "HOLOREPO_DATABASE_URL")
|
|
161
|
+
conn = env.get(env_name)
|
|
162
|
+
if not conn:
|
|
163
|
+
return False, "", f"direct-psql needs {env_name}"
|
|
164
|
+
conn, password = _split_password_from_url(conn)
|
|
165
|
+
args = ["psql", "-X", conn, "-c" if meta else "-tAc", sql]
|
|
166
|
+
elif transport == "ssh-docker-psql":
|
|
167
|
+
ssh = db_cfg.get("ssh", {})
|
|
168
|
+
docker = db_cfg.get("docker", {})
|
|
169
|
+
host = ssh.get("host") or env.get(ssh.get("hostEnv", "HOLOREPO_SSH"))
|
|
170
|
+
key = ssh.get("keyPath") or env.get(ssh.get("keyEnv", "HOLOREPO_SSH_KEY"))
|
|
171
|
+
container = docker.get("container") or env.get(docker.get("containerEnv", "HOLOREPO_POSTGRES_CONTAINER"))
|
|
172
|
+
user = db_cfg.get("user") or env.get(db_cfg.get("userEnv", "HOLOREPO_DATABASE_USER"), "postgres")
|
|
173
|
+
if not host:
|
|
174
|
+
return False, "", "ssh-docker-psql needs database.ssh.host or hostEnv"
|
|
175
|
+
if not container:
|
|
176
|
+
return False, "", "ssh-docker-psql needs database.docker.container or containerEnv"
|
|
177
|
+
esc = sql.replace("'", "'\\''")
|
|
178
|
+
flag = "" if meta else "-tA"
|
|
179
|
+
remote = f"sudo docker exec '{container}' psql -U '{user}' -d '{db}' {flag} -c '{esc}'"
|
|
180
|
+
args = ["ssh", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=15"]
|
|
181
|
+
if key:
|
|
182
|
+
args.extend(["-i", os.path.expanduser(key)])
|
|
183
|
+
args.extend([host, remote])
|
|
184
|
+
else:
|
|
185
|
+
return False, "", f"unsupported database transport: {transport}"
|
|
186
|
+
child_env = dict(os.environ)
|
|
187
|
+
child_env.update(env)
|
|
188
|
+
if transport == "direct-psql" and password and not child_env.get("PGPASSWORD"):
|
|
189
|
+
child_env["PGPASSWORD"] = password
|
|
190
|
+
proc = subprocess.run(args, capture_output=True, text=True, timeout=30, env=child_env)
|
|
191
|
+
return proc.returncode == 0, proc.stdout.strip(), proc.stderr.strip()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def read_db(db: str, sql: str, config: dict | None = None, env: dict | None = None) -> dict:
|
|
195
|
+
if not is_read_only_sql(sql):
|
|
196
|
+
return {"ok": False, "refused": True}
|
|
197
|
+
ok, out, err = _run_psql(db, sql, meta=sql.strip().startswith("\\"), config=config, env=env)
|
|
198
|
+
if not ok:
|
|
199
|
+
return {"ok": False, "err": err}
|
|
200
|
+
rows = out.split("\n") if out else []
|
|
201
|
+
receipt = {
|
|
202
|
+
"schema": "holorepo.db-read-receipt.v1",
|
|
203
|
+
"backend": (config or load_config(env=env)).get("database", {}).get("transport", "direct-psql"),
|
|
204
|
+
"db": db,
|
|
205
|
+
"connection": redact_db_summary(db, config=config, env=env),
|
|
206
|
+
"query_digest": _sha256(sql),
|
|
207
|
+
"result_digest": _sha256(out),
|
|
208
|
+
"row_count": len(rows),
|
|
209
|
+
"read_only": True,
|
|
210
|
+
}
|
|
211
|
+
return {"ok": True, "out": out, "rows": len(rows), "receipt": receipt}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def query_ks(term: str, domain: str | None = None, limit: int = 10, config: dict | None = None, env: dict | None = None) -> dict:
|
|
215
|
+
config = load_config(env=env) if config is None else config
|
|
216
|
+
ks = config.get("knowledgeStore", {})
|
|
217
|
+
ok, out, err = _run_psql(ks.get("database", "knowledge"), build_ks_query(term, domain, limit, ks.get("table", "memory_entries")), config=config, env=env)
|
|
218
|
+
if not ok:
|
|
219
|
+
return {"ok": False, "err": err}
|
|
220
|
+
rows = []
|
|
221
|
+
for line in filter(None, out.split("\n")):
|
|
222
|
+
parts = line.split("|")
|
|
223
|
+
if len(parts) >= 5:
|
|
224
|
+
rows.append({"id": parts[0], "domain": parts[1], "type": parts[2], "confidence": parts[3], "snippet": parts[4]})
|
|
225
|
+
return {"ok": True, "term": sanitize_term(term), "rows": rows}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""holorepo-py CLI."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from . import default_config, load_config, query_ks, read_db
|
|
8
|
+
|
|
9
|
+
HELP = """holorepo-py - Python companion for HoloRepo
|
|
10
|
+
|
|
11
|
+
holorepo-py config print resolved config
|
|
12
|
+
holorepo-py db read <db> "<sql>" guest read (SELECT/meta only) + receipt
|
|
13
|
+
holorepo-py ks query "<text>" [limit] configured HoloScript KS read
|
|
14
|
+
|
|
15
|
+
Config: holorepo.config.json + env. No host, IP, or container is assumed.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main(argv=None) -> int:
|
|
20
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
21
|
+
if not argv or argv[0] in ("-h", "--help"):
|
|
22
|
+
print(HELP)
|
|
23
|
+
return 0
|
|
24
|
+
group = argv[0]
|
|
25
|
+
if group == "config":
|
|
26
|
+
print(json.dumps(load_config(), indent=2))
|
|
27
|
+
return 0
|
|
28
|
+
if group == "sample":
|
|
29
|
+
print(json.dumps(default_config(), indent=2))
|
|
30
|
+
return 0
|
|
31
|
+
if group == "db" and len(argv) >= 4 and argv[1] == "read":
|
|
32
|
+
result = read_db(argv[2], argv[3])
|
|
33
|
+
if result.get("refused"):
|
|
34
|
+
print("[holorepo-py] REFUSED: read accepts SELECT/meta only; mutations go through stage-write.", file=sys.stderr)
|
|
35
|
+
return 3
|
|
36
|
+
if not result["ok"]:
|
|
37
|
+
print(f"[holorepo-py] query failed: {result['err'][:200]}", file=sys.stderr)
|
|
38
|
+
return 1
|
|
39
|
+
print(f"read {argv[2]} ({result['rows']} rows) {result['receipt']['result_digest'][:24]}")
|
|
40
|
+
print(result["out"][:1600])
|
|
41
|
+
return 0
|
|
42
|
+
if group == "ks" and len(argv) >= 3 and argv[1] == "query":
|
|
43
|
+
limit = int(argv[3]) if len(argv) > 3 and argv[3].isdigit() else 10
|
|
44
|
+
result = query_ks(argv[2], limit=limit)
|
|
45
|
+
if not result["ok"]:
|
|
46
|
+
print(f"[holorepo-py] query failed: {result['err'][:200]}", file=sys.stderr)
|
|
47
|
+
return 1
|
|
48
|
+
print(f'"{result["term"]}" - {len(result["rows"])} hit(s)')
|
|
49
|
+
for row in result["rows"]:
|
|
50
|
+
print(f" [{row['domain']}/{row['type']}] {row['snippet']}")
|
|
51
|
+
print(f" {row['id']} conf={row['confidence']}")
|
|
52
|
+
return 0
|
|
53
|
+
print(HELP, file=sys.stderr)
|
|
54
|
+
return 2
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: holoscript-holorepo
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python companion for HoloRepo: plug-and-play sovereign DB + HoloScript KS for agent frameworks.
|
|
5
|
+
Author: HoloScript ecosystem
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/brianonbased-dev/ai-ecosystem/tree/main/packages/holorepo-py
|
|
8
|
+
Project-URL: Source, https://github.com/brianonbased-dev/ai-ecosystem
|
|
9
|
+
Project-URL: Doctrine, https://github.com/brianonbased-dev/ai-ecosystem/blob/main/docs/handbooks/holorepo-operating-model.md
|
|
10
|
+
Keywords: holoscript,holorepo,sovereign,postgres,knowledge-store,agent-framework
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
15
|
+
Classifier: Topic :: Database
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# holoscript-holorepo
|
|
22
|
+
|
|
23
|
+
Python companion for HoloRepo: a plug-and-play sovereign GitHub + database +
|
|
24
|
+
HoloScript knowledge store for high-volume agent frameworks.
|
|
25
|
+
|
|
26
|
+
The Python client covers the DB/KS guest surface. It reads any Postgres reachable
|
|
27
|
+
by `psql`, or an explicitly configured owned node over SSH + Docker. No host, IP,
|
|
28
|
+
or container is assumed by default.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install holoscript-holorepo
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Config
|
|
37
|
+
|
|
38
|
+
Use `holorepo.config.json` or env:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
export HOLOREPO_DATABASE_URL="postgres://user@host:5432/app"
|
|
42
|
+
export PGPASSWORD="..."
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For an owned node over SSH, use the `jetsonExample` profile as an example and
|
|
46
|
+
set `HOLOREPO_SSH`, `HOLOREPO_SSH_KEY`, and `HOLOREPO_POSTGRES_CONTAINER`.
|
|
47
|
+
|
|
48
|
+
## Use
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
holorepo-py db read knowledge "select count(*) from memory_entries"
|
|
52
|
+
holorepo-py ks query "storage lanes" 8
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from holoscript_holorepo import query_ks, read_db
|
|
57
|
+
|
|
58
|
+
hits = query_ks("sovereign backbone", limit=5)["rows"]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Reads refuse mutations. Writes are staged by the npm client as
|
|
62
|
+
`holorepo.storage-proof.v1` and admitted by the configured operator route.
|
|
63
|
+
|
|
64
|
+
## Release Boundary
|
|
65
|
+
|
|
66
|
+
`holoscript-holorepo` is distributed under the MIT license. The compatibility
|
|
67
|
+
label is still `v0-preview`: the Python companion supports config/env-driven
|
|
68
|
+
DB/KS reads for agent frameworks, while write admission remains in the npm
|
|
69
|
+
client and HoloGate/HoloKey operator lane.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
holoscript_holorepo/__init__.py,sha256=ifTEeT6guzDgKAvQLc7bdqtgkVSw373eym6JSX52Hho,9667
|
|
2
|
+
holoscript_holorepo/cli.py,sha256=KohNz3zb9wMdERAa5Um4bC8L85NqChPxGS2nIe4Lzsg,2143
|
|
3
|
+
holoscript_holorepo-0.2.0.dist-info/licenses/LICENSE,sha256=b7rhi2piGUvTWuK-gR-z0NMuHUqvbVYE-es4fy51I7Y,1096
|
|
4
|
+
holoscript_holorepo-0.2.0.dist-info/METADATA,sha256=tmFchkMYVVGuZgHClAPv_qhT8fCzlFA5i2kug8Pj-B8,2415
|
|
5
|
+
holoscript_holorepo-0.2.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
6
|
+
holoscript_holorepo-0.2.0.dist-info/entry_points.txt,sha256=0-2F2urCUEDKi7lDERYKHkJjV6qmUDiiB6z0enSqn7E,61
|
|
7
|
+
holoscript_holorepo-0.2.0.dist-info/top_level.txt,sha256=FBRsbCcrCu5BHVU2Jg1NBEZauG8uslbOFpk05CGC5Ng,20
|
|
8
|
+
holoscript_holorepo-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HoloScript ecosystem (Joseph, founder).
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
holoscript_holorepo
|