async_linkedinlib 0.2.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 (111) hide show
  1. async_linkedinlib-0.2.0/.github/ISSUE_TEMPLATE/bug_report.md +22 -0
  2. async_linkedinlib-0.2.0/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
  3. async_linkedinlib-0.2.0/.github/pull_request_template.md +10 -0
  4. async_linkedinlib-0.2.0/.github/workflows/ci.yml +33 -0
  5. async_linkedinlib-0.2.0/.github/workflows/release.yml +83 -0
  6. async_linkedinlib-0.2.0/.gitignore +20 -0
  7. async_linkedinlib-0.2.0/AGENTS.md +123 -0
  8. async_linkedinlib-0.2.0/CLAUDE.md +4 -0
  9. async_linkedinlib-0.2.0/LICENSE +21 -0
  10. async_linkedinlib-0.2.0/PKG-INFO +478 -0
  11. async_linkedinlib-0.2.0/README.md +457 -0
  12. async_linkedinlib-0.2.0/assets/banner.png +0 -0
  13. async_linkedinlib-0.2.0/examples/account.py +20 -0
  14. async_linkedinlib-0.2.0/examples/comments.py +42 -0
  15. async_linkedinlib-0.2.0/examples/feed.py +48 -0
  16. async_linkedinlib-0.2.0/examples/jobs.py +60 -0
  17. async_linkedinlib-0.2.0/examples/posts.py +42 -0
  18. async_linkedinlib-0.2.0/examples/search.py +59 -0
  19. async_linkedinlib-0.2.0/pyproject.toml +61 -0
  20. async_linkedinlib-0.2.0/src/async_linkedinlib/__init__.py +187 -0
  21. async_linkedinlib-0.2.0/src/async_linkedinlib/__main__.py +3 -0
  22. async_linkedinlib-0.2.0/src/async_linkedinlib/auth/__init__.py +5 -0
  23. async_linkedinlib-0.2.0/src/async_linkedinlib/auth/service.py +77 -0
  24. async_linkedinlib-0.2.0/src/async_linkedinlib/cli.py +37 -0
  25. async_linkedinlib-0.2.0/src/async_linkedinlib/client.py +93 -0
  26. async_linkedinlib-0.2.0/src/async_linkedinlib/config/__init__.py +6 -0
  27. async_linkedinlib-0.2.0/src/async_linkedinlib/config/options.py +15 -0
  28. async_linkedinlib-0.2.0/src/async_linkedinlib/config/profile.py +56 -0
  29. async_linkedinlib-0.2.0/src/async_linkedinlib/exceptions.py +75 -0
  30. async_linkedinlib-0.2.0/src/async_linkedinlib/models/__init__.py +155 -0
  31. async_linkedinlib-0.2.0/src/async_linkedinlib/models/_json.py +65 -0
  32. async_linkedinlib-0.2.0/src/async_linkedinlib/models/comments.py +145 -0
  33. async_linkedinlib-0.2.0/src/async_linkedinlib/models/content.py +207 -0
  34. async_linkedinlib-0.2.0/src/async_linkedinlib/models/feed.py +124 -0
  35. async_linkedinlib-0.2.0/src/async_linkedinlib/models/jobs.py +215 -0
  36. async_linkedinlib-0.2.0/src/async_linkedinlib/models/jobs_apply.py +424 -0
  37. async_linkedinlib-0.2.0/src/async_linkedinlib/models/member.py +14 -0
  38. async_linkedinlib-0.2.0/src/async_linkedinlib/models/pagination.py +52 -0
  39. async_linkedinlib-0.2.0/src/async_linkedinlib/models/posts.py +141 -0
  40. async_linkedinlib-0.2.0/src/async_linkedinlib/models/posts_results.py +120 -0
  41. async_linkedinlib-0.2.0/src/async_linkedinlib/models/search.py +307 -0
  42. async_linkedinlib-0.2.0/src/async_linkedinlib/models/search_filters.py +339 -0
  43. async_linkedinlib-0.2.0/src/async_linkedinlib/models/search_jobs.py +213 -0
  44. async_linkedinlib-0.2.0/src/async_linkedinlib/py.typed +0 -0
  45. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/__init__.py +12 -0
  46. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/account.py +35 -0
  47. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/comments/__init__.py +5 -0
  48. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/comments/resource.py +227 -0
  49. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/comments/urns.py +43 -0
  50. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/feed/__init__.py +5 -0
  51. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/feed/queries.py +57 -0
  52. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/feed/resource.py +225 -0
  53. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/feed/saved.py +49 -0
  54. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/feed/urns.py +68 -0
  55. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/jobs/__init__.py +5 -0
  56. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/jobs/answers.py +239 -0
  57. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/jobs/resource.py +212 -0
  58. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/jobs/upload.py +78 -0
  59. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/mentions.py +58 -0
  60. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/paging.py +88 -0
  61. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/posts/__init__.py +6 -0
  62. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/posts/media.py +173 -0
  63. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/posts/payloads.py +98 -0
  64. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/posts/resource.py +254 -0
  65. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/reactions.py +89 -0
  66. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/search/__init__.py +5 -0
  67. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/search/queries.py +79 -0
  68. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/search/results.py +85 -0
  69. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/search/search.py +284 -0
  70. async_linkedinlib-0.2.0/src/async_linkedinlib/resources/threads.py +30 -0
  71. async_linkedinlib-0.2.0/src/async_linkedinlib/session/__init__.py +5 -0
  72. async_linkedinlib-0.2.0/src/async_linkedinlib/session/cookies.py +52 -0
  73. async_linkedinlib-0.2.0/src/async_linkedinlib/session/lease.py +30 -0
  74. async_linkedinlib-0.2.0/src/async_linkedinlib/session/state.py +129 -0
  75. async_linkedinlib-0.2.0/src/async_linkedinlib/transport/__init__.py +6 -0
  76. async_linkedinlib-0.2.0/src/async_linkedinlib/transport/http.py +183 -0
  77. async_linkedinlib-0.2.0/src/async_linkedinlib/transport/restli.py +46 -0
  78. async_linkedinlib-0.2.0/src/async_linkedinlib/transport/voyager.py +151 -0
  79. async_linkedinlib-0.2.0/tests/auth/test_google.py +145 -0
  80. async_linkedinlib-0.2.0/tests/comments/conftest.py +114 -0
  81. async_linkedinlib-0.2.0/tests/comments/test_models.py +55 -0
  82. async_linkedinlib-0.2.0/tests/comments/test_reads.py +213 -0
  83. async_linkedinlib-0.2.0/tests/comments/test_writes.py +209 -0
  84. async_linkedinlib-0.2.0/tests/conftest.py +20 -0
  85. async_linkedinlib-0.2.0/tests/feed/conftest.py +141 -0
  86. async_linkedinlib-0.2.0/tests/feed/test_models.py +143 -0
  87. async_linkedinlib-0.2.0/tests/feed/test_reads.py +219 -0
  88. async_linkedinlib-0.2.0/tests/feed/test_writes.py +179 -0
  89. async_linkedinlib-0.2.0/tests/integration/test_loopback.py +100 -0
  90. async_linkedinlib-0.2.0/tests/jobs/conftest.py +242 -0
  91. async_linkedinlib-0.2.0/tests/jobs/test_application.py +224 -0
  92. async_linkedinlib-0.2.0/tests/jobs/test_details.py +296 -0
  93. async_linkedinlib-0.2.0/tests/jobs/test_submission.py +248 -0
  94. async_linkedinlib-0.2.0/tests/posts/conftest.py +92 -0
  95. async_linkedinlib-0.2.0/tests/posts/test_attachments.py +200 -0
  96. async_linkedinlib-0.2.0/tests/posts/test_create.py +235 -0
  97. async_linkedinlib-0.2.0/tests/posts/test_manage.py +154 -0
  98. async_linkedinlib-0.2.0/tests/posts/test_media.py +153 -0
  99. async_linkedinlib-0.2.0/tests/posts/test_models.py +35 -0
  100. async_linkedinlib-0.2.0/tests/restli_decode.py +48 -0
  101. async_linkedinlib-0.2.0/tests/search/conftest.py +146 -0
  102. async_linkedinlib-0.2.0/tests/search/test_filters.py +206 -0
  103. async_linkedinlib-0.2.0/tests/search/test_jobs.py +234 -0
  104. async_linkedinlib-0.2.0/tests/search/test_typeahead.py +125 -0
  105. async_linkedinlib-0.2.0/tests/search/test_verticals.py +296 -0
  106. async_linkedinlib-0.2.0/tests/session/test_state.py +88 -0
  107. async_linkedinlib-0.2.0/tests/transport/test_http.py +158 -0
  108. async_linkedinlib-0.2.0/tests/voyager/test_paging.py +88 -0
  109. async_linkedinlib-0.2.0/tests/voyager/test_restli.py +39 -0
  110. async_linkedinlib-0.2.0/tests/voyager/test_voyager.py +271 -0
  111. async_linkedinlib-0.2.0/uv.lock +504 -0
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: Bug report
3
+ about: Report a reproducible problem with the library
4
+ ---
5
+
6
+ ### What happened?
7
+
8
+ Describe the actual and expected behavior.
9
+
10
+ ### Minimal reproduction
11
+
12
+ Use synthetic inputs. Remove credentials, session data and private account details.
13
+
14
+ ### Environment
15
+
16
+ - Package version or commit:
17
+ - Python version:
18
+ - Operating system:
19
+
20
+ ### Redacted error
21
+
22
+ Include the exception type and relevant traceback, without raw response bodies.
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: Feature request
3
+ about: Describe a missing capability or improvement
4
+ ---
5
+
6
+ ### Use case
7
+
8
+ What are you trying to accomplish?
9
+
10
+ ### Proposed behavior
11
+
12
+ Describe the public method or workflow you would like.
13
+
14
+ ### Evidence and constraints
15
+
16
+ Link public documentation or describe observed behavior. Do not attach credentials,
17
+ private captures or account data.
@@ -0,0 +1,10 @@
1
+ ## Change
2
+
3
+ Describe the problem and resulting behavior.
4
+
5
+ ## Verification
6
+
7
+ List checks run and distinguish synthetic tests from live verification.
8
+
9
+ - [ ] Public behavior changes are documented in README.md.
10
+ - [ ] Tests and examples contain only synthetic data.
@@ -0,0 +1,33 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ pull_request:
5
+ workflow_dispatch:
6
+ workflow_call:
7
+ permissions:
8
+ contents: read
9
+ concurrency:
10
+ group: ci-${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+ jobs:
13
+ check:
14
+ name: ${{ matrix.os }} / Python ${{ matrix.python }}
15
+ runs-on: ${{ matrix.os }}
16
+ timeout-minutes: 15
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ os: [ubuntu-latest, macos-latest]
21
+ python: ['3.11', '3.13']
22
+ steps:
23
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
24
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
25
+ with:
26
+ python-version: ${{ matrix.python }}
27
+ enable-cache: true
28
+ - run: uv sync --locked
29
+ - run: uv run ruff check src tests examples
30
+ - run: uv run ruff format --check src tests examples
31
+ - run: uv run mypy src
32
+ - run: uv run pytest -q
33
+ - run: uv build
@@ -0,0 +1,83 @@
1
+ name: Publish to PyPI
2
+ on:
3
+ release:
4
+ types: [published]
5
+ permissions:
6
+ contents: read
7
+ concurrency:
8
+ group: pypi-${{ github.repository }}
9
+ cancel-in-progress: false
10
+ jobs:
11
+ checks:
12
+ uses: ./.github/workflows/ci.yml
13
+ build:
14
+ needs: checks
15
+ runs-on: ubuntu-latest
16
+ timeout-minutes: 10
17
+ steps:
18
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
19
+ with:
20
+ fetch-depth: 0
21
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
22
+ with:
23
+ python-version: '3.11'
24
+ - name: Validate release tag and source
25
+ env:
26
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
27
+ run: |
28
+ git merge-base --is-ancestor HEAD origin/main
29
+ uv run --no-project python - <<'PYTHON'
30
+ import os, tomllib
31
+ from pathlib import Path
32
+ version = tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"]
33
+ if os.environ["RELEASE_TAG"] != f"v{version}":
34
+ raise SystemExit(f"Release tag must be v{version}")
35
+ PYTHON
36
+ - run: uv build
37
+ - run: uvx twine check --strict dist/*
38
+ - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
39
+ with:
40
+ name: distributions
41
+ path: dist/*
42
+ if-no-files-found: error
43
+ retention-days: 7
44
+ publish:
45
+ needs: build
46
+ runs-on: ubuntu-latest
47
+ timeout-minutes: 10
48
+ environment:
49
+ name: pypi
50
+ url: https://pypi.org/p/async_linkedinlib
51
+ permissions:
52
+ id-token: write
53
+ steps:
54
+ - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
55
+ with:
56
+ name: distributions
57
+ path: dist/
58
+ - name: Verify any previously published files
59
+ run: |
60
+ python - <<'PYTHON'
61
+ import hashlib, json, urllib.error, urllib.request
62
+ from pathlib import Path
63
+ try:
64
+ with urllib.request.urlopen("https://pypi.org/pypi/async_linkedinlib/json") as response:
65
+ project = json.load(response)
66
+ except urllib.error.HTTPError as error:
67
+ if error.code != 404:
68
+ raise
69
+ project = {"releases": {}}
70
+ published = {
71
+ item["filename"]: item["digests"]["sha256"]
72
+ for release in project["releases"].values() for item in release
73
+ }
74
+ for artifact in Path("dist").iterdir():
75
+ expected = published.get(artifact.name)
76
+ actual = hashlib.sha256(artifact.read_bytes()).hexdigest()
77
+ if expected is not None and expected != actual:
78
+ raise SystemExit(f"Existing PyPI file differs: {artifact.name}")
79
+ PYTHON
80
+ - name: Publish distributions
81
+ uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1
82
+ with:
83
+ skip-existing: true
@@ -0,0 +1,20 @@
1
+ .venv/
2
+ __pycache__/
3
+ .pytest_cache/
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ dist/
7
+ *.session.json
8
+ *.session.json.lock
9
+ .DS_Store
10
+ *.py[cod]
11
+ build/
12
+ *.egg-info/
13
+ .env
14
+ .env.*
15
+ *.har
16
+ *.mitm
17
+ *.pem
18
+ *.key
19
+ .coverage
20
+ htmlcov/
@@ -0,0 +1,123 @@
1
+ # Rules for async_linkedinlib
2
+
3
+ Follow these standalone library rules.
4
+ Read this package's [README.md](README.md), the relevant source, and its tests before editing.
5
+ Keep this a standalone Python package; do not import the backend or `async_instalib`.
6
+
7
+ ## Structure and ownership
8
+
9
+ - `client.py` owns `LinkedIn`, the shared session/transport, resource construction,
10
+ async lifecycle and session-file ownership. Add an explicit attribute for each
11
+ implemented resource; keep endpoint logic out of this class.
12
+ - `auth/` owns login orchestration, fresh login context and supported challenge
13
+ handling. It may reuse the account resource to verify a completed login.
14
+ - `session/` owns account/device state, scoped cookies, persistence and file locks.
15
+ It performs no HTTP requests. Do not store passwords or Google ID tokens here.
16
+ - `transport/` is the single network boundary. `http.py` owns the HTTPX pool,
17
+ authentication headers, cookie updates, timeouts, HTTP error translation and cooldowns.
18
+ `voyager.py` runs persisted GraphQL operations and rest.li calls over it, and `restli.py`
19
+ encodes variables exactly as the app's `DataEncoder` does.
20
+ - `resources/` owns feature operations and provider-response decoding. Every
21
+ resource receives the existing transport; never create a separate session or
22
+ HTTP client per feature.
23
+ - `models/` owns typed public results. Models perform no network or persistence work.
24
+ - `config/` owns caller options and versioned app metadata, separate from credentials.
25
+ - `exceptions.py` owns public errors; `cli.py` owns interactive commands and credential
26
+ input. Put runnable usage examples in `examples/`.
27
+
28
+ Group methods around a user's feature: account, feed, posts, comments, search, jobs, and
29
+ later connections and messages. Shared by several features: `resources/reactions.py`
30
+ (react and list reactions), `resources/paging.py` (every list's pages and cursors; follow the
31
+ app's pager, never write another), `resources/threads.py` (thread URNs) and
32
+ `resources/mentions.py` (text mentions). `models/content.py` holds the shared `Post`, `Author` and
33
+ `SocialCounts`; `models/pagination.py` holds `Page` and `Cursor`.
34
+ A small feature can start as `resources/<feature>.py`. Split a substantial feature
35
+ into `resources/<feature>/` when its real responsibilities justify that split.
36
+ Matching result models belong in `models/<feature>.py`; tests belong under that
37
+ feature in `tests/`. Do not pre-create empty resources, generic CRUD methods, plugin
38
+ registries, base resource classes or speculative interfaces.
39
+
40
+ Use cohesive classes and explicit dependencies. Keep public imports stable:
41
+ `from async_linkedinlib import LinkedIn, Session, Member` must continue working.
42
+ Keep `__init__.py` files declarative, with `__all__` and a one-line purpose comment
43
+ on each re-export. Annotate public APIs, use familiar names, follow Ruff's 100-column
44
+ format and a 500-line source file limit.
45
+
46
+ ## Adding a LinkedIn feature
47
+
48
+ 1. Establish the operation from a captured exchange or from the shipped app's own request
49
+ code (decompiled Voyager GraphQL clients, routes and builders). App 4.1.1267 draws these
50
+ screens with server-driven protobuf (SDUI); the SDK does not parse SDUI UI trees and uses
51
+ the Voyager operations the same APK ships, over JSON. Take variable names, enums and
52
+ bodies from app code: the server silently ignores unknown variables. Record the app
53
+ version and evidence in the README. Distinguish observed behavior, locally tested
54
+ behavior and live SDK verification.
55
+ 2. Define the smallest useful public method, typed result and expected failures.
56
+ Keep IDs as strings. Preserve unknown versus absent fields rather than guessing.
57
+ 3. Implement request construction and decoding inside the feature, using the shared
58
+ transport. Do not spread provider payloads and URL construction through `client.py`.
59
+ 4. Test the request contract and meaningful failures with synthetic data. Use a real
60
+ loopback server when serialization, cookies, streaming or lifecycle matters.
61
+ 5. Update the README's feature coverage, usage and limits. Add root exports only for
62
+ supported public types. An observed endpoint does not establish every capability
63
+ or variation of that feature.
64
+
65
+ Intended future groups are `connections` and `messages`. These names describe extension
66
+ boundaries, not implemented functionality. Implement only the feature requested.
67
+
68
+ ## Protocol and account state
69
+
70
+ - `li_at` is the primary session credential; preserve `JSESSIONID` and its CSRF role.
71
+ Retain cookie domain, path, security and expiry when available. Never flatten the
72
+ entire jar into a global credential header or send it to arbitrary hosts.
73
+ - Each client owns one account and one connection pool. New features must use the
74
+ same cookie rotation and cooldown behavior. Preserve saved-session compatibility;
75
+ version persisted schema changes explicitly.
76
+ - Google sign-in currently means exchanging a caller-supplied fresh ID token for
77
+ LinkedIn's Android flow. Do not imply that the SDK acquires Google tokens or accepts
78
+ tokens issued for unrelated OAuth clients. Do not replay a captured login token.
79
+ - Do not invent endpoints, GraphQL IDs, token refresh, OTP, password login, challenge
80
+ continuation or anti-abuse telemetry. Unsupported server flows fail explicitly.
81
+ - Keep pagination explicit. Preserve opaque server cursors and their account/query
82
+ context (`Cursor` carries the operation, query scope and owning session); do not
83
+ silently fetch every page or invent continuation values.
84
+ - A write whose 2xx reply cannot be read raises `UnconfirmedWrite`, never a plain
85
+ `ProtocolError`, so callers know the change may exist.
86
+ - Reads must not implicitly send messages, mark conversations read, follow, connect,
87
+ apply for jobs, publish, delete or acknowledge content. Expose those actions as
88
+ separate, clearly named operations when supported by evidence.
89
+ - Never silently retry a write or credential exchange. A timeout can occur after the
90
+ server accepted it. Do not assume an idempotency mechanism exists.
91
+ - Keep TLS verification enabled, redirects explicit, and destination hosts constrained
92
+ to the documented operation. Uploads observed so far go to www.linkedin.com; validate an
93
+ upload URL's host before sending. Add another host only when its flow requires it, with
94
+ credentials scoped to that host.
95
+ - Accept GraphQL replies with errors only through `Voyager.query(...,
96
+ ignore_null_field_errors=True)`, and only for finders known to report harmless nulls.
97
+
98
+ ## Credentials and verification
99
+
100
+ Keep raw captures, cookies, passwords, ID tokens and real account data outside Git.
101
+ Use hidden prompts or caller-provided secret inputs, never credential arguments.
102
+ Session files remain atomic and mode 0600; preserve exclusive file ownership.
103
+ Logs, exception messages and object reprs must not expose secrets or raw responses.
104
+ Challenge URLs can carry private state and must not appear in ordinary error text.
105
+
106
+ Use synthetic credentials in tests. Live reads must use the user's authorized
107
+ session. Live writes require an authorized action and target; preserve that
108
+ authorization across turns rather than asking repeatedly. Delete test-created remote data, verify cleanup, and report any effects that cannot be undone.
109
+
110
+ Run checks from this directory:
111
+
112
+ ```sh
113
+ uv run pytest -q
114
+ uv run ruff check src tests examples
115
+ uv run ruff format --check src tests examples
116
+ uv run mypy src
117
+ uv build
118
+ ```
119
+
120
+ Use focused tests while developing, then the full package suite for changes to
121
+ auth, transport, sessions or public imports. Do not contact LinkedIn in the normal
122
+ test suite. Report live and simulated verification separately. Keep lasting
123
+ documentation in this README and these rules; plans belong in `tasks/`. Do not create a separate documentation site.
@@ -0,0 +1,4 @@
1
+ # Library instructions
2
+
3
+ Read and follow [AGENTS.md](AGENTS.md). It is the single source of library-specific
4
+ structure, protocol, credential-handling and verification rules.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anandesh Sharma
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.