devin-devkit 0.1.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.
@@ -0,0 +1,262 @@
1
+ """Update installed tools against the remote devkit manifest.
2
+
3
+ The bundled ``manifest.json`` is a snapshot: every install spec is pinned to a
4
+ PyPI version or a GitHub tarball SHA at export time, so installs never move by
5
+ themselves. This module fetches the manifest published in the devin-devkit
6
+ repository (always the newest registry export) and computes a plan to bring
7
+ installed tools up to date.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import re
14
+ import shutil
15
+ import subprocess
16
+ import urllib.error
17
+ import urllib.request
18
+ from pathlib import Path
19
+ from typing import Any, Callable
20
+
21
+ from devin_devkit.installer import DevKitError
22
+
23
+ REMOTE_MANIFEST_URL = (
24
+ "https://raw.githubusercontent.com/Icaro0310/devin-devkit/main/"
25
+ "packages/devkit/src/devin_devkit/manifest.json"
26
+ )
27
+ FETCH_TIMEOUT = 15
28
+
29
+
30
+ def fetch_remote_manifest(
31
+ url: str = REMOTE_MANIFEST_URL,
32
+ *,
33
+ opener: Callable[..., Any] | None = None,
34
+ ) -> dict[str, Any]:
35
+ """Download and parse the published manifest; raise DevKitError on failure."""
36
+ open_fn = opener or urllib.request.urlopen
37
+ try:
38
+ with open_fn(url, timeout=FETCH_TIMEOUT) as response:
39
+ data = json.loads(response.read().decode("utf-8"))
40
+ except (OSError, urllib.error.URLError, json.JSONDecodeError) as exc:
41
+ raise DevKitError(f"cannot fetch remote manifest: {exc}") from exc
42
+ if data.get("schema") != "devin-devkit-manifest/0.1":
43
+ raise DevKitError("remote manifest has an unexpected schema")
44
+ return data
45
+
46
+
47
+ def installed_uv_tools(
48
+ runner: Callable[..., Any] = subprocess.run,
49
+ ) -> dict[str, str]:
50
+ """Return {package: version} from ``uv tool list``."""
51
+ try:
52
+ result = runner(
53
+ ["uv", "tool", "list"], capture_output=True, text=True, check=False
54
+ )
55
+ except OSError:
56
+ return {}
57
+ if result.returncode != 0:
58
+ return {}
59
+ installed: dict[str, str] = {}
60
+ for line in (result.stdout or "").splitlines():
61
+ if not line.strip() or line.startswith("-"):
62
+ continue
63
+ name, _, version = line.strip().partition(" ")
64
+ if name:
65
+ installed[name] = version.lstrip("v")
66
+ return installed
67
+
68
+
69
+ try:
70
+ import tomllib
71
+ except ImportError: # Python 3.10 — declared as a conditional dependency
72
+ import tomli as tomllib
73
+
74
+
75
+ def _uv_tools_dir() -> Path:
76
+ override = os.environ.get("UV_TOOL_DIR")
77
+ return (
78
+ Path(override)
79
+ if override
80
+ else Path.home() / ".local" / "share" / "uv" / "tools"
81
+ )
82
+
83
+
84
+ def installed_uv_specs(
85
+ tools_dir: str | Path | None = None,
86
+ ) -> dict[str, str]:
87
+ """Return {package: installed requirement} parsed from uv receipts.
88
+
89
+ ``uv tool list`` reports only name and version; the per-tool
90
+ ``uv-receipt.toml`` also records the requirement, including the
91
+ source URL for non-PyPI installs. That lets the updater detect a
92
+ channel migration (git archive -> PyPI) even when the version number
93
+ did not change.
94
+ """
95
+ root = Path(tools_dir) if tools_dir else _uv_tools_dir()
96
+ specs: dict[str, str] = {}
97
+ if not root.is_dir():
98
+ return specs
99
+ for receipt in root.glob("*/uv-receipt.toml"):
100
+ try:
101
+ data = tomllib.loads(receipt.read_text(encoding="utf-8"))
102
+ except (OSError, tomllib.TOMLDecodeError):
103
+ continue
104
+ for req in data.get("tool", {}).get("requirements", []):
105
+ if not isinstance(req, dict):
106
+ continue
107
+ name = req.get("name")
108
+ if not name:
109
+ continue
110
+ specs[name] = req.get("url") or f"{name}{req.get('specifier', '')}"
111
+ return specs
112
+
113
+
114
+ def _spec_channel(spec: str) -> str:
115
+ """Compare-key for install specs: PyPI installs collapse to 'pypi',
116
+ URL installs compare by exact URL."""
117
+ if re.match(r"(https?://|git\+)", spec):
118
+ return f"url:{spec}"
119
+ return "pypi"
120
+
121
+
122
+ def installed_npm_tools(
123
+ runner: Callable[..., Any] = subprocess.run,
124
+ ) -> dict[str, str]:
125
+ """Return {package: version} from ``npm ls -g --depth=0 --json``."""
126
+ try:
127
+ result = runner(
128
+ ["npm", "ls", "--global", "--depth=0", "--json"],
129
+ capture_output=True, text=True, check=False,
130
+ )
131
+ except OSError:
132
+ return {}
133
+ try:
134
+ data = json.loads(result.stdout or "{}")
135
+ except json.JSONDecodeError:
136
+ return {}
137
+ return {
138
+ name: meta.get("version", "")
139
+ for name, meta in data.get("dependencies", {}).items()
140
+ if isinstance(meta, dict)
141
+ }
142
+
143
+
144
+ def build_update_plan(
145
+ remote: dict[str, Any],
146
+ *,
147
+ installed: dict[str, str] | None = None,
148
+ installed_specs: dict[str, str] | None = None,
149
+ which: Callable[[str], str | None] = shutil.which,
150
+ runner: Callable[..., Any] = subprocess.run,
151
+ force: bool = False,
152
+ ) -> dict[str, Any]:
153
+ """Diff installed tools against the remote manifest.
154
+
155
+ ``installed`` maps package name to installed version; when None it is
156
+ discovered via ``uv tool list`` and ``npm ls -g``.
157
+ ``installed_specs`` maps package name to the requirement it was
158
+ installed from (uv receipts); when None it is discovered via
159
+ ``installed_uv_specs``. A same-version channel change (git archive
160
+ -> PyPI pin) plans a reinstall rather than reporting ``current``.
161
+ """
162
+ if installed is None:
163
+ installed = installed_uv_tools(runner)
164
+ installed.update(installed_npm_tools(runner))
165
+ if installed_specs is None:
166
+ installed_specs = installed_uv_specs()
167
+
168
+ actions: list[dict[str, Any]] = []
169
+ for tool in remote.get("tools", []):
170
+ tool_id = tool["id"]
171
+ manager = tool.get("manager")
172
+ if manager == "manual" or tool.get("status") == "manual":
173
+ continue
174
+ commands = tool.get("commands", [])
175
+ on_path = any(which(command) is not None for command in commands)
176
+ if tool["package"] not in installed and not on_path:
177
+ actions.append({"tool": tool_id, "action": "not-installed"})
178
+ continue
179
+ spec = tool.get("install_spec")
180
+ if not spec:
181
+ actions.append({"tool": tool_id, "action": "blocked", "detail": "missing install specification"})
182
+ continue
183
+ local_version = installed.get(tool["package"], "?")
184
+ remote_version = tool.get("version", "?")
185
+ if not force and local_version == remote_version:
186
+ installed_spec = installed_specs.get(tool["package"])
187
+ if (
188
+ manager == "uv"
189
+ and installed_spec is not None
190
+ and _spec_channel(installed_spec) != _spec_channel(spec)
191
+ ):
192
+ actions.append({
193
+ "tool": tool_id,
194
+ "action": "update",
195
+ "from": local_version,
196
+ "to": remote_version,
197
+ "reason": "install source changed",
198
+ "command": ["uv", "tool", "install", "--force", spec],
199
+ })
200
+ else:
201
+ actions.append({"tool": tool_id, "action": "current", "version": local_version})
202
+ continue
203
+ command = (
204
+ ["uv", "tool", "install", "--force", spec]
205
+ if manager == "uv"
206
+ else ["npm", "install", "--global", spec]
207
+ )
208
+ actions.append({
209
+ "tool": tool_id,
210
+ "action": "update",
211
+ "from": local_version,
212
+ "to": remote_version,
213
+ "command": command,
214
+ })
215
+ return {
216
+ "registry_version": remote.get("registry_version"),
217
+ "generated": remote.get("generated"),
218
+ "actions": actions,
219
+ }
220
+
221
+
222
+ def apply_update_plan(
223
+ plan: dict[str, Any],
224
+ runner: Callable[..., Any] = subprocess.run,
225
+ ) -> dict[str, Any]:
226
+ updated: list[str] = []
227
+ failed: list[str] = []
228
+ for action in plan["actions"]:
229
+ if action["action"] != "update":
230
+ continue
231
+ try:
232
+ result = runner(action["command"], check=False)
233
+ except OSError:
234
+ result = None
235
+ if result is None or result.returncode:
236
+ failed.append(action["tool"])
237
+ else:
238
+ updated.append(action["tool"])
239
+ return {"updated": updated, "failed": failed}
240
+
241
+
242
+ def freshness_hint(
243
+ manifest: dict[str, Any],
244
+ *,
245
+ opener: Callable[..., Any] | None = None,
246
+ ) -> str | None:
247
+ """One-line hint when the remote registry is newer than the bundled manifest.
248
+
249
+ Never raises: network or parse problems simply return None.
250
+ """
251
+ try:
252
+ remote = fetch_remote_manifest(opener=opener)
253
+ except DevKitError:
254
+ return None
255
+ local_version = manifest.get("registry_version", 0)
256
+ remote_version = remote.get("registry_version", 0)
257
+ if remote_version > local_version:
258
+ return (
259
+ f"registry v{remote_version} is available "
260
+ f"(bundled: v{local_version}); run 'devin-devkit update'"
261
+ )
262
+ return None
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.4
2
+ Name: devin-devkit
3
+ Version: 0.1.0
4
+ Summary: Install public Devin CLI profiles through uv
5
+ Author: Icaro0310
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: tomli>=2.0; python_version < "3.11"
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest>=8; extra == "dev"
12
+
13
+ <div align="center">
14
+
15
+ <a href="https://github.com/Icaro0310/devin-devkit/actions/workflows/test-devkit.yml"><img src="https://github.com/Icaro0310/devin-devkit/actions/workflows/test-devkit.yml/badge.svg" alt="ci"/></a>
16
+
17
+ <a href="https://scorecard.dev/viewer/?uri=github.com/Icaro0310/devin-devkit"><img src="https://api.scorecard.dev/projects/github.com/Icaro0310/devin-devkit/badge" alt="OpenSSF Scorecard"/></a>
18
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="License: MIT"/></a>
19
+ <a href="https://www.python.org/"><img src="https://img.shields.io/badge/python-3.10%2B-blue" alt="Python 3.10+"/></a>
20
+ <a href="https://github.com/Icaro0310/devin-devkit"><img src="https://img.shields.io/github/stars/Icaro0310/devin-devkit" alt="GitHub stars"/></a>
21
+ <a href="https://github.com/Icaro0310/devin-devkit/commits/main"><img src="https://img.shields.io/github/last-commit/Icaro0310/devin-devkit" alt="Last commit"/></a>
22
+ <a href="https://github.com/Icaro0310/awesome-devin"><img src="https://img.shields.io/badge/part%20of-devin--*-ecosystem-7c3aed" alt="devin-* ecosystem"/></a>
23
+ <a href="https://github.com/Icaro0310/devin-devkit/issues"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen" alt="PRs welcome"/></a>
24
+ </div>
25
+
26
+ # devin-devkit
27
+
28
+ > Unofficial community project. Not affiliated with, endorsed by, or sponsored by Cognition AI. Devin is a Cognition AI trademark.
29
+
30
+ **[Linux](README.linux.md)** · **[Personal Windows](README.windows.md)** · **[Corporate Windows](README.corporate-windows.md)** · **[Compatibility](COMPATIBILITY.md)** · **[macOS status](README.macos.md)**
31
+
32
+ Part of the [awesome-devin](https://github.com/Icaro0310/awesome-devin) ecosystem: the curated hub for the devin-* tools.
33
+
34
+ `devin-devkit` turns the public `devin-powerups` registry into selectable installation profiles. It is an **installer/distribution tool**, not a meta-package: each Python app gets its own isolated `uv` environment, and the Node bridge uses npm separately.
35
+
36
+ ![devin-devkit demo: list, outdated, doctor check](assets/demo.gif)
37
+
38
+ ## Source → tools → profiles
39
+
40
+ - `devin-powerups/registry.json` is the catalog and profile source of truth for the first-party `devin-*` tools, this separate DevKit distribution, the maintainer hub, and related artifacts.
41
+ - `tools/export_devkit_manifest.py` builds this repo's bundled manifest from the registry, including artifact, interface, audience, platform and environment metadata. The exporter rejects private or unregistered tools and verifies immutable Git commit references.
42
+ - `src/devin_devkit/cli.py` previews and installs the selected profile under an explicit execution environment. It does not edit Devin settings, inspect session databases, or install Slack/Obsidian/VM services.
43
+
44
+ ## Execution environments
45
+
46
+ One `devin-devkit` distribution supports three environments. Linux and Personal Windows use the extended runtime: local execution plus optional delegated workloads through Devin VM or QwenPaw when the selected tool supports them. Corporate Windows is intentionally self-contained: local execution only, with no VM, QwenPaw, Slack dependency, external compute, or workload delegation.
47
+
48
+ The installer defaults to `linux` on Linux and `personal-windows` on Windows. Corporate Windows is never inferred from the operating system; select it explicitly so restricted machines fail closed:
49
+
50
+ ```bash
51
+ devin-devkit install qa --environment linux
52
+ devin-devkit install qa --environment personal-windows
53
+ devin-devkit install qa --environment corporate-windows
54
+ ```
55
+
56
+ Environment entry points: [Linux](environments/linux/README.md) · [Personal Windows](environments/personal-windows/README.md) · [Corporate Windows](environments/corporate-windows/README.md).
57
+
58
+ The generated [compatibility matrix](COMPATIBILITY.md) is rendered from `registry.json`; unsupported entries carry an explicit registry reason rather than guessed compatibility.
59
+
60
+ ## Profiles
61
+
62
+ | Profile | Contents |
63
+ |---|---|
64
+ | `qa` | internals spec, QA pack, evals, metrics, doctor |
65
+ | `evaluation` | internals spec, evals, metrics, synthetic task fixtures |
66
+ | `security` | redact, bridge, janitor |
67
+ | `memory` / `agent` | memory, graph, search, poordjaevin |
68
+ | `operations` | backup, history, doctor, metrics, janitor, PM, orchestrator, switch and skill catalog |
69
+ | `full` / `all` | every automatically installable profile tool; `devin-office` remains a manual source-only service |
70
+
71
+ QwenPaw is an optional add-on and is not installed by these profiles. macOS is planned, not tested.
72
+
73
+ ## Use
74
+
75
+ Install the current GitHub source archive or use a local checkout; the package has no runtime dependencies:
76
+
77
+ ```bash
78
+ uv tool install "git+https://github.com/Icaro0310/devin-devkit.git#subdirectory=packages/devkit"
79
+ ```
80
+
81
+ From a local checkout, use `uv tool install .`. Then inspect the available profiles and preview before applying:
82
+
83
+ ```bash
84
+ devin-devkit profiles
85
+ devin-devkit list
86
+ devin-devkit install qa
87
+ devin-devkit install qa --apply
88
+ devin-devkit install full --apply
89
+ ```
90
+
91
+ Install specs are pinned (PyPI versions or GitHub archive SHAs), so an install never drifts on its own. To move forward:
92
+
93
+ ```bash
94
+ devin-devkit outdated # which installed tools have newer pins
95
+ devin-devkit update # preview the update plan
96
+ devin-devkit update --apply # reinstall outdated tools
97
+ devin-devkit update --force --apply # reinstall everything
98
+ ```
99
+
100
+ `outdated`/`update` read the manifest published in this repository, which a weekly workflow re-exports from the maintainer registry, so the pins are never more than about a week behind upstream.
101
+
102
+ To run from a checkout without installing the DevKit itself:
103
+
104
+ ```bash
105
+ uv run --project . python -m devin_devkit.cli install qa
106
+ ```
107
+
108
+ After a PyPI package release, the installer can also be run with `uvx devin-devkit ...`.
109
+
110
+ The manifest pins PyPI versions where available and GitHub source archives to immutable commit SHAs otherwise. Before it starts, the installer checks platform support, registry-declared environment compatibility, required managers, Node.js for `devin-bridge`, and existing command collisions. It never overwrites an existing command; commands already on `PATH` are reported as pre-existing but are not version-verified, and a partial collision blocks the profile before any installs run.
111
+
112
+ ## Runtime requirements
113
+
114
+ - Python tools: Python 3.10 or newer, installed in isolated environments managed by `uv`.
115
+ - `devin-bridge`: Node.js 20 or newer and npm. Its pinned GitHub archive is downloaded over HTTPS.
116
+ - Current installable profiles do not need Git. Git is needed only for manual source checkouts such as `devin-office`; package archives use HTTPS.
117
+ - Supported environments: Linux, Personal Windows and Corporate Windows. Linux and Personal Windows may use optional delegated runtime; Corporate Windows rejects registry entries that require external runtime, delegation or external integrations. macOS is planned but not claimed as tested.
118
+
119
+ ## Development and synchronization
120
+
121
+ ```bash
122
+ uv run --extra dev pytest
123
+ python ../devin-powerups/tools/validate_registry.py
124
+ python ../devin-powerups/tools/export_devkit_manifest.py
125
+ python ../devin-powerups/tools/export_devkit_manifest.py --check
126
+ python ../devin-powerups/tools/render_catalog.py --check ../Icaro0310/README.md
127
+ ```
128
+
129
+ The DevKit package has no runtime dependencies. New catalog entries and profiles belong in the registry; regenerate the manifest instead of hand-editing its generated copy. For local sibling development, install dependencies editably in a virtual environment (examples in the OS guides); keep relative file paths out of publishable package metadata.
@@ -0,0 +1,10 @@
1
+ devin_devkit/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
+ devin_devkit/cli.py,sha256=d47vn96QAuW-inihysQ6PZnC_YoWjP1WVKGnMaBMuJE,6348
3
+ devin_devkit/installer.py,sha256=4sD6jF4mhiXCRiHKHfR8MnS9igPMSow5ltRcpMGtvuc,9369
4
+ devin_devkit/manifest.json,sha256=dkoeo9v3lS7st2h1e6KBWY5GPDhJZ5uvU5ptCVFmnoQ,32670
5
+ devin_devkit/updater.py,sha256=Ula6hTPJul1LPd0pqcOzmd4iAKem3LMXHrrK_tC23TU,8916
6
+ devin_devkit-0.1.0.dist-info/METADATA,sha256=LYBZ5Hfw3tYHR7pFbUYzrJu6XmAgzPAARgywlL69ZYI,7963
7
+ devin_devkit-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
8
+ devin_devkit-0.1.0.dist-info/entry_points.txt,sha256=BGUJCma8kpI1LPWYzFy_I7IjFItBXV4BegYR_dWxOYg,55
9
+ devin_devkit-0.1.0.dist-info/top_level.txt,sha256=a-JvTFjEDz6sJz_SmzJLF5pxXiqBNNyd-RtD0QxQGdI,13
10
+ devin_devkit-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ devin-devkit = devin_devkit.cli:main
@@ -0,0 +1 @@
1
+ devin_devkit