skillstate-kit 0.1.1__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.
skillstate/store.py ADDED
@@ -0,0 +1,332 @@
1
+ """Transactional checkpoints and a durable operation journal.
2
+
3
+ SQLite transactions never span model or tool calls. A reserved operation prevents
4
+ other writers from advancing its run until its outcome is recorded or reconciled.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import sqlite3
10
+ import threading
11
+ import time
12
+ import uuid
13
+ from contextlib import contextmanager
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ from .errors import ConflictError, NotFoundError, ValidationError
18
+ from .jsonio import dumps, loads
19
+ from .models import Skill
20
+ from .schema import apply_patch, validate
21
+
22
+
23
+ def _identifier(value: str) -> str:
24
+ if type(value) is not str or not value or len(value) > 128:
25
+ raise ValidationError("Identifiers must be nonempty strings up to 128 characters")
26
+ return value
27
+
28
+
29
+ class SQLiteStore:
30
+ """One local database, multiple processes, optimistic revision checks.
31
+
32
+ Owner identifiers coordinate cooperating clients; they are not authentication.
33
+ Keep this file private to the local project/user. Do not place it on network FS.
34
+ """
35
+
36
+ def __init__(self, path: str | Path = ":memory:"):
37
+ self.path = str(path)
38
+ if self.path != ":memory:":
39
+ Path(path).parent.mkdir(parents=True, exist_ok=True)
40
+ self._lock = threading.RLock()
41
+ self._db = sqlite3.connect(
42
+ self.path, timeout=10, isolation_level=None, check_same_thread=False
43
+ )
44
+ self._db.row_factory = sqlite3.Row
45
+ self._db.execute("PRAGMA foreign_keys=ON")
46
+ self._db.execute("PRAGMA busy_timeout=10000")
47
+ self._db.execute("PRAGMA synchronous=FULL")
48
+ version = self._db.execute("PRAGMA user_version").fetchone()[0]
49
+ if version not in (0, 1):
50
+ self._db.close()
51
+ raise ValidationError(f"Unsupported database version: {version}")
52
+ self._db.execute("PRAGMA journal_mode=WAL")
53
+ self._db.executescript("""
54
+ CREATE TABLE IF NOT EXISTS runs (
55
+ id TEXT PRIMARY KEY, skill TEXT NOT NULL, fingerprint TEXT NOT NULL,
56
+ state TEXT NOT NULL, observation TEXT NOT NULL, revision INTEGER NOT NULL,
57
+ status TEXT NOT NULL, owner TEXT NOT NULL, pending TEXT,
58
+ created REAL NOT NULL, updated REAL NOT NULL
59
+ );
60
+ CREATE TABLE IF NOT EXISTS operations (
61
+ id TEXT PRIMARY KEY, run_id TEXT NOT NULL REFERENCES runs(id),
62
+ action TEXT NOT NULL, candidate TEXT NOT NULL, status TEXT NOT NULL,
63
+ result TEXT, created REAL NOT NULL
64
+ );
65
+ CREATE TABLE IF NOT EXISTS events (
66
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT,
67
+ run_id TEXT NOT NULL REFERENCES runs(id), kind TEXT NOT NULL,
68
+ payload TEXT NOT NULL, created REAL NOT NULL
69
+ );
70
+ PRAGMA user_version=1;
71
+ """)
72
+
73
+ def close(self) -> None:
74
+ with self._lock:
75
+ self._db.close()
76
+
77
+ def __enter__(self) -> SQLiteStore:
78
+ return self
79
+
80
+ def __exit__(self, *args) -> None:
81
+ self.close()
82
+
83
+ @contextmanager
84
+ def _transaction(self):
85
+ with self._lock:
86
+ self._db.execute("BEGIN IMMEDIATE")
87
+ try:
88
+ yield
89
+ self._db.execute("COMMIT")
90
+ except BaseException:
91
+ self._db.execute("ROLLBACK")
92
+ raise
93
+
94
+ def _event(self, run_id: str, kind: str, payload: Any) -> None:
95
+ self._db.execute(
96
+ "INSERT INTO events(run_id,kind,payload,created) VALUES(?,?,?,?)",
97
+ (run_id, kind, dumps(payload), time.time()),
98
+ )
99
+
100
+ def _row(self, run_id: str) -> sqlite3.Row:
101
+ row = self._db.execute("SELECT * FROM runs WHERE id=?", (run_id,)).fetchone()
102
+ if row is None:
103
+ raise NotFoundError(f"Run not found: {run_id}")
104
+ return row
105
+
106
+ def _guard(
107
+ self, row: sqlite3.Row, owner: str, revision: int | None = None, pending: bool = False
108
+ ) -> None:
109
+ if row["owner"] != owner:
110
+ raise ConflictError("Run belongs to a different owner; use handoff")
111
+ if revision is not None and (type(revision) is not int or row["revision"] != revision):
112
+ raise ConflictError("Stale revision; reload context and reconsider the decision")
113
+ if not pending and row["pending"] is not None:
114
+ raise ConflictError("An operation is pending or unknown; reconcile it first")
115
+ if row["status"] in ("completed", "cancelled"):
116
+ raise ConflictError("Run is already terminal")
117
+
118
+ @staticmethod
119
+ def _snapshot(row: sqlite3.Row) -> dict:
120
+ return {
121
+ "run_id": row["id"],
122
+ "skill": loads(row["skill"]),
123
+ "state": loads(row["state"]),
124
+ "observation": loads(row["observation"]),
125
+ "revision": row["revision"],
126
+ "status": row["status"],
127
+ "owner": row["owner"],
128
+ "pending_operation": row["pending"],
129
+ }
130
+
131
+ def get(self, run_id: str) -> dict:
132
+ with self._lock:
133
+ return self._snapshot(self._row(run_id))
134
+
135
+ def list_runs(self, limit: int = 100) -> list[dict]:
136
+ if type(limit) is not int or not 1 <= limit <= 1000:
137
+ raise ValidationError("Run limit must be between 1 and 1000")
138
+ with self._lock:
139
+ rows = self._db.execute(
140
+ "SELECT id,revision,status,owner,pending FROM runs ORDER BY updated DESC LIMIT ?",
141
+ (limit,),
142
+ ).fetchall()
143
+ return [dict(row) for row in rows]
144
+
145
+ def create(self, run_id: str, skill: Skill, owner: str, observation: Any = None) -> dict:
146
+ _identifier(run_id)
147
+ _identifier(owner)
148
+ skill = Skill.from_dict(skill.to_dict())
149
+ obs = dumps(observation, skill.limits.observation_bytes)
150
+ with self._transaction():
151
+ if self._db.execute("SELECT 1 FROM runs WHERE id=?", (run_id,)).fetchone():
152
+ raise ConflictError("Run already exists; resume it without resetting its state")
153
+ now = time.time()
154
+ self._db.execute(
155
+ "INSERT INTO runs VALUES(?,?,?,?,?,0,'ready',?,NULL,?,?)",
156
+ (
157
+ run_id,
158
+ dumps(skill.to_dict()),
159
+ skill.fingerprint,
160
+ dumps(skill.initial_state),
161
+ obs,
162
+ owner,
163
+ now,
164
+ now,
165
+ ),
166
+ )
167
+ self._event(run_id, "created", {"skill": skill.name, "owner": owner})
168
+ return self.get(run_id)
169
+
170
+ def _candidate(self, row, patch: list[dict]) -> dict:
171
+ skill = Skill.from_dict(loads(row["skill"]))
172
+ candidate = apply_patch(loads(row["state"]), patch)
173
+ validate(skill.state_schema, candidate)
174
+ dumps(candidate, skill.limits.state_bytes)
175
+ return candidate
176
+
177
+ def update(
178
+ self,
179
+ run_id: str,
180
+ owner: str,
181
+ revision: int,
182
+ patch: list[dict],
183
+ observation: Any = None,
184
+ *,
185
+ done: bool = False,
186
+ ) -> dict:
187
+ """Record agent-reported facts. Does not certify external business truth."""
188
+ if type(done) is not bool:
189
+ raise ValidationError("done must be boolean")
190
+ with self._transaction():
191
+ row = self._row(run_id)
192
+ self._guard(row, owner, revision)
193
+ skill = Skill.from_dict(loads(row["skill"]))
194
+ candidate = self._candidate(row, patch)
195
+ obs = dumps(observation, skill.limits.observation_bytes)
196
+ self._db.execute(
197
+ "UPDATE runs SET state=?,observation=?,revision=revision+1,status=?,updated=? WHERE id=?",
198
+ (dumps(candidate), obs, "completed" if done else "ready", time.time(), run_id),
199
+ )
200
+ self._event(
201
+ run_id,
202
+ "completed" if done else "updated",
203
+ {"patch": patch, "origin": "agent_reported"},
204
+ )
205
+ return self.get(run_id)
206
+
207
+ def reserve(
208
+ self, run_id: str, owner: str, revision: int, action: dict, patch: list[dict]
209
+ ) -> dict:
210
+ dumps(action, 32_000)
211
+ if (
212
+ type(action) is not dict
213
+ or set(action) != {"name", "arguments"}
214
+ or type(action["name"]) is not str
215
+ or not action["name"]
216
+ or type(action["arguments"]) is not dict
217
+ ):
218
+ raise ValidationError("Invalid action contract")
219
+ with self._transaction():
220
+ row = self._row(run_id)
221
+ self._guard(row, owner, revision)
222
+ candidate = self._candidate(row, patch)
223
+ operation_id = uuid.uuid4().hex
224
+ self._db.execute(
225
+ "INSERT INTO operations VALUES(?,?,?,?,'pending',NULL,?)",
226
+ (operation_id, run_id, dumps(action), dumps(candidate), time.time()),
227
+ )
228
+ self._db.execute(
229
+ "UPDATE runs SET pending=?,status='running',revision=revision+1,updated=? WHERE id=?",
230
+ (operation_id, time.time(), run_id),
231
+ )
232
+ self._event(run_id, "reserved", {"operation_id": operation_id, "action": action})
233
+ return {"operation_id": operation_id, "run": self.get(run_id)}
234
+
235
+ def operation(self, operation_id: str) -> dict:
236
+ with self._lock:
237
+ row = self._db.execute(
238
+ "SELECT * FROM operations WHERE id=?", (operation_id,)
239
+ ).fetchone()
240
+ if row is None:
241
+ raise NotFoundError("Operation not found")
242
+ return {
243
+ "operation_id": row["id"],
244
+ "run_id": row["run_id"],
245
+ "action": loads(row["action"]),
246
+ "status": row["status"],
247
+ "result": loads(row["result"]) if row["result"] else None,
248
+ }
249
+
250
+ def mark_unknown(self, operation_id: str, owner: str, reason: str) -> dict:
251
+ reason = str(reason)[:1000]
252
+ with self._transaction():
253
+ op = self.operation(operation_id)
254
+ row = self._row(op["run_id"])
255
+ self._guard(row, owner, pending=True)
256
+ if row["pending"] != operation_id or op["status"] not in ("pending", "unknown"):
257
+ raise ConflictError("Operation is not outstanding")
258
+ self._db.execute("UPDATE operations SET status='unknown' WHERE id=?", (operation_id,))
259
+ self._db.execute(
260
+ "UPDATE runs SET status='unknown',revision=revision+1,updated=? WHERE id=?",
261
+ (time.time(), op["run_id"]),
262
+ )
263
+ self._event(op["run_id"], "unknown", {"operation_id": operation_id, "reason": reason})
264
+ return self.get(op["run_id"])
265
+
266
+ def record_result(
267
+ self,
268
+ operation_id: str,
269
+ owner: str,
270
+ success: bool,
271
+ observation: Any,
272
+ *,
273
+ reconcile: bool = False,
274
+ origin: str = "agent_reported",
275
+ ) -> dict:
276
+ if type(success) is not bool or type(reconcile) is not bool:
277
+ raise ValidationError("success and reconcile must be booleans")
278
+ if origin not in ("agent_reported", "runtime_observed"):
279
+ raise ValidationError("Invalid result origin")
280
+ with self._transaction():
281
+ op = self.operation(operation_id)
282
+ row = self._row(op["run_id"])
283
+ if row["owner"] != owner:
284
+ raise ConflictError("Run owner mismatch")
285
+ skill = Skill.from_dict(loads(row["skill"]))
286
+ obs = dumps(observation, skill.limits.observation_bytes)
287
+ result = {"success": success, "observation": loads(obs), "origin": origin}
288
+ if op["status"] in ("succeeded", "failed"):
289
+ if op["result"] != result:
290
+ raise ConflictError("Operation already has a different result")
291
+ return self._snapshot(row)
292
+ if row["pending"] != operation_id:
293
+ raise ConflictError("Operation does not match run's pending operation")
294
+ if op["status"] == "unknown" and not reconcile:
295
+ raise ConflictError("Unknown result requires explicit reconciliation")
296
+ candidate_row = self._db.execute(
297
+ "SELECT candidate FROM operations WHERE id=?", (operation_id,)
298
+ ).fetchone()
299
+ next_state = candidate_row[0] if success else row["state"]
300
+ self._db.execute(
301
+ "UPDATE operations SET status=?,result=? WHERE id=?",
302
+ ("succeeded" if success else "failed", dumps(result), operation_id),
303
+ )
304
+ self._db.execute(
305
+ "UPDATE runs SET state=?,observation=?,pending=NULL,status='ready',revision=revision+1,updated=? WHERE id=?",
306
+ (next_state, obs, time.time(), op["run_id"]),
307
+ )
308
+ self._event(op["run_id"], "result", {"operation_id": operation_id, **result})
309
+ return self.get(op["run_id"])
310
+
311
+ def handoff(self, run_id: str, owner: str, revision: int, target: str) -> dict:
312
+ _identifier(target)
313
+ with self._transaction():
314
+ row = self._row(run_id)
315
+ self._guard(row, owner, revision)
316
+ self._db.execute(
317
+ "UPDATE runs SET owner=?,revision=revision+1,updated=? WHERE id=?",
318
+ (target, time.time(), run_id),
319
+ )
320
+ self._event(run_id, "handoff", {"from": owner, "to": target})
321
+ return self.get(run_id)
322
+
323
+ def events(self, run_id: str, after: int = 0, limit: int = 100) -> list[dict]:
324
+ if type(after) is not int or after < 0 or type(limit) is not int or not 1 <= limit <= 1000:
325
+ raise ValidationError("Invalid event pagination")
326
+ with self._lock:
327
+ self._row(run_id)
328
+ rows = self._db.execute(
329
+ "SELECT sequence,kind,payload FROM events WHERE run_id=? AND sequence>? ORDER BY sequence LIMIT ?",
330
+ (run_id, after, limit),
331
+ ).fetchall()
332
+ return [{"sequence": r[0], "kind": r[1], "payload": loads(r[2])} for r in rows]
@@ -0,0 +1,157 @@
1
+ Metadata-Version: 2.4
2
+ Name: skillstate-kit
3
+ Version: 0.1.1
4
+ Summary: Compile existing agent skills into portable, validated execution state.
5
+ Project-URL: Homepage, https://github.com/Atakan-Emre/skillstate-kit
6
+ Project-URL: Repository, https://github.com/Atakan-Emre/skillstate-kit
7
+ Project-URL: Documentation, https://github.com/Atakan-Emre/skillstate-kit/tree/main/docs
8
+ Project-URL: Issues, https://github.com/Atakan-Emre/skillstate-kit/issues
9
+ Author: Atakan Emre
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,claude,codex,mcp,skills,state
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: jsonschema<5,>=4.23
22
+ Requires-Dist: pathspec<1,>=0.12
23
+ Requires-Dist: pyyaml<7,>=6.0.2
24
+ Requires-Dist: tomlkit<1,>=0.13
25
+ Provides-Extra: dev
26
+ Requires-Dist: build<2,>=1.2; extra == 'dev'
27
+ Requires-Dist: httpx<1,>=0.28; extra == 'dev'
28
+ Requires-Dist: hypothesis<7,>=6.120; extra == 'dev'
29
+ Requires-Dist: mcp<2,>=1.20; extra == 'dev'
30
+ Requires-Dist: pytest-cov<7,>=6; extra == 'dev'
31
+ Requires-Dist: pytest<9,>=8; extra == 'dev'
32
+ Requires-Dist: ruff<1,>=0.11; extra == 'dev'
33
+ Requires-Dist: twine<7,>=6; extra == 'dev'
34
+ Provides-Extra: http
35
+ Requires-Dist: httpx<1,>=0.28; extra == 'http'
36
+ Provides-Extra: mcp
37
+ Requires-Dist: mcp<2,>=1.20; extra == 'mcp'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # skillstate-kit
41
+
42
+ **Portable, validated execution state for agent skills.**
43
+
44
+ Convert an existing `SKILL.md` into a state-backed skill, retain durable checkpoints, and integrate the same execution engine into a Python application.
45
+
46
+ Python 3.11+ · MIT license · Initial alpha
47
+
48
+ ## Install
49
+
50
+ ```bash
51
+ python -m pip install "skillstate-kit[mcp,http]"
52
+ skillstate demo
53
+ ```
54
+
55
+ The core package requires no model account. The demo uses an explicitly scripted model and local tools; it is not a benchmark of real-model performance.
56
+
57
+ Optional extras:
58
+
59
+ - `mcp`: a project-scoped STDIO MCP server and connection diagnostics.
60
+ - `http`: a stateless adapter for a configured JSON-compatible chat-completions endpoint.
61
+
62
+ ## Use an existing skill
63
+
64
+ Run these commands in your target project. Replace `skills/qa/SKILL.md` with your existing skill file:
65
+
66
+ ```bash
67
+ skillstate init --host codex --host claude-code --host antigravity --mcp
68
+ skillstate generate skills/qa/SKILL.md --name qa-state --install
69
+ skillstate validate qa-state
70
+ skillstate doctor --mcp
71
+ ```
72
+
73
+ After your host discovers the installed `generate-skill-state` skill, ask it to generate state from an existing skill and install the result. Invoke the generated `qa-state` skill to run the procedure with checkpoints. Generating a definition does not execute the task.
74
+
75
+ `generate` preserves the source procedure and adds bounded progress fields. Domain-specific generation uses the active agent's proposal:
76
+
77
+ ```bash
78
+ skillstate generate skills/qa/SKILL.md --prepare
79
+ skillstate generate skills/qa/SKILL.md --proposal proposal.json --source-hash HASH_FROM_PREPARE --install
80
+ ```
81
+
82
+ The preparation response contains source text, its fingerprint and the proposal schema. The agent writes `proposal.json`; the library validates it and refuses stale source fingerprints. Structural validation is not proof that every business rule is correct.
83
+
84
+ You can also supply an explicitly configured endpoint with `--base-url` and `--model`. Terminal commands do not borrow your IDE's model credentials.
85
+
86
+ ## Python integration
87
+
88
+ This complete example uses a scripted model. Replace `model` and `record` with your own model and service functions:
89
+
90
+ ```python
91
+ import asyncio
92
+ from skillstate import Skill, SkillRuntime, SQLiteStore, Tool, ToolResult
93
+
94
+ skill = Skill(
95
+ name="record-job",
96
+ instructions="Record the job, then finish after its result is confirmed.",
97
+ state_schema={
98
+ "type": "object",
99
+ "properties": {"recorded": {"type": "boolean"}},
100
+ "required": ["recorded"],
101
+ "additionalProperties": False,
102
+ },
103
+ initial_state={"recorded": False},
104
+ )
105
+
106
+ def model(context):
107
+ if context["state"]["recorded"]:
108
+ return {"patch": [], "action": None, "done": True}
109
+ return {
110
+ "patch": [{"op": "set", "path": "/recorded", "value": True}],
111
+ "action": {"name": "record", "arguments": {}},
112
+ "done": False,
113
+ }
114
+
115
+ def record(arguments, operation_id):
116
+ # For a real API, forward operation_id to its idempotency facility when
117
+ # supported and inspect the actual response before reporting success.
118
+ return ToolResult(True, {"recorded": True})
119
+
120
+ async def main():
121
+ with SQLiteStore() as store: # Pass a local file path for durable storage.
122
+ store.create("job-001", skill, "worker")
123
+ tool = Tool("record", "Record a job",
124
+ {"type": "object", "additionalProperties": False}, record)
125
+ runtime = SkillRuntime(store, model, [tool],
126
+ completion_check=lambda state: state["recorded"])
127
+ result = await runtime.run("job-001", "worker", max_steps=10)
128
+ print(result["status"], result["state"])
129
+
130
+ asyncio.run(main())
131
+ ```
132
+
133
+ For persistent storage, pass a database path to `SQLiteStore`. Reopen the same database and run ID to resume; creating a duplicate run is rejected.
134
+
135
+ ## Execution contract
136
+
137
+ - Explicit `set`/`delete` patches with JSON Pointer paths, inline JSON Schema and UTF-8 size limits.
138
+ - State and tool arguments validated before a managed tool executes.
139
+ - Persistent operation intent before tool execution; atomic state, result and event commit afterward.
140
+ - Failed tools retain the prior state. Exceptions, timeouts and ambiguous results block replay until reconciliation.
141
+ - Revision checks prevent competing clients from silently overwriting each other.
142
+ - Immutable skill definitions, source drift detection and sequential owner handoff.
143
+ - Large text artifacts kept outside the active context.
144
+
145
+ Native host skills provide state and checkpoints. They do **not** replace host conversation history or intercept every native tool. Use the managed Python runtime with a stateless model for history-free model inputs.
146
+
147
+ SQLite does not make external APIs transactional. Owner names coordinate local clients; they are not authentication. The library validates reported results but cannot independently prove external business truth. Keep local state private and test recovery behavior for your application.
148
+
149
+ ## Compatibility and documentation
150
+
151
+ Adapters generate project skill files and optional MCP configuration for Codex, Claude Code and Antigravity. They target documented integration surfaces. MCP protocol tests and configuration discovery do not certify every IDE version or live task.
152
+
153
+ The source distribution includes `docs/cli.md`, `docs/architecture.md`, `docs/compatibility.md`, `docs/validation.md`, `docs/README_TR.md` and runnable examples. Download the source archive from this package's files to read them offline. The development repository currently requires collaborator access.
154
+
155
+ ## Research
156
+
157
+ An independent implementation inspired by [SKILL.state: Scalable Long-Horizon Agent Skills](https://arxiv.org/abs/2608.26263). Not affiliated with the paper's authors. The compiler, host adapters and operation journal are engineering extensions; no reproduction of the paper's benchmark scores or token savings is claimed.
@@ -0,0 +1,22 @@
1
+ skillstate/__init__.py,sha256=HafjazXaBSNs7d092K4h4JenqfjKkWBiVAAxmGnxVr0,757
2
+ skillstate/__main__.py,sha256=k1ocEWawweo1qCJWNFAAvyxz3tcY13dzvCenHszij30,48
3
+ skillstate/artifacts.py,sha256=nJXt1Wmv1-Drja2RcDdVnNdbB7Jbz1OkPrnjSpqeY2A,2083
4
+ skillstate/cli.py,sha256=33XyLwAFTsTdzEtQMKDCXmhiaz2BSHTW4vvic3gADmk,10719
5
+ skillstate/compiler.py,sha256=IYzkzffbRdcQQXCmdpQshpxhTnxI35w8DDQCVvtPdqY,14978
6
+ skillstate/demo.py,sha256=GMbpROEginms1mdaUAFnVqx5lXHR3m2v7_Y9dPKqVQA,2193
7
+ skillstate/errors.py,sha256=5BfKLGTARdABa22bw70KVkmm7d8ZKivP4Bmqe5HPPsw,598
8
+ skillstate/hosts.py,sha256=v8kqZbYQ7SUOlYVpKEwplh5JJsKKvGZDjuoA2MuDy3Q,13784
9
+ skillstate/jsonio.py,sha256=cfvRVds7UK9Nzedl5S6qcPjX_JBpX6KZr3smaBXXjXA,4927
10
+ skillstate/mcp_server.py,sha256=1HC8hJ3U8_Ab33yK95y3U0pqvtH4fqzhF8wlw7pWWk8,5592
11
+ skillstate/models.py,sha256=INE9zYIru0ae4pbWatcpCFRzyM9uCee5R_1vfDLg-bc,2618
12
+ skillstate/providers.py,sha256=dN7RuP-5w0-PNewginE1jyt2IM4PbUk8QEVtfrJFb_o,3650
13
+ skillstate/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ skillstate/runtime.py,sha256=0shW9knRnVu9bxFlPWncq_e0CZ4HTJhl7dXTMzmjdA0,7768
15
+ skillstate/schema.py,sha256=zfbm_euLdCVK8ihM2fO6_67gcXLxL1mdt-M43rPGP4w,3967
16
+ skillstate/service.py,sha256=bRNYgaRSXGIQ0WX6xBpgMm2Ao_BBfR8MybsjfGjLLk0,2233
17
+ skillstate/store.py,sha256=1SQ-lAzWP8cB4YgYwQVA__3CUfu6WNM0I-mLtG_mrEw,14060
18
+ skillstate_kit-0.1.1.dist-info/METADATA,sha256=VRdG7gmSA305HCJLnNhvTpLgJSjHXIe0TQkkaNNm834,7481
19
+ skillstate_kit-0.1.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
20
+ skillstate_kit-0.1.1.dist-info/entry_points.txt,sha256=96xGFcS705trwVFkVCR42YJCwYDZjmD8ajaKCOKGx9g,51
21
+ skillstate_kit-0.1.1.dist-info/licenses/LICENSE,sha256=7GRdWqwPbYWWkOBJqMbsMtHkZR_IvUbHoyixEnwXQPI,1068
22
+ skillstate_kit-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ skillstate = skillstate.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Atakan Emre
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.