@softspark/ai-toolkit 1.3.5 → 1.3.7
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.
- package/CHANGELOG.md +14 -0
- package/app/hooks/session-start.sh +8 -1
- package/kb/reference/benchmark-config.md +3 -3
- package/kb/reference/competitive-features-implementation.md +1 -1
- package/kb/reference/manifest-install.md +1 -1
- package/llms-full.txt +13 -7
- package/package.json +1 -1
- package/scripts/install_steps/ai_tools.py +38 -21
- package/scripts/install_steps/install_state.py +15 -0
- package/scripts/version_check.py +154 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v1.3.7 — Update Notifications (2026-04-07)
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Update notifications**: `session-start.sh` hook checks npm for newer versions (cached 24h, non-blocking). `ai-toolkit status` shows installed vs latest version with upgrade command.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## v1.3.6 — Patch (2026-04-07)
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **Tests updated**: All hook tests now pass stdin JSON instead of env vars (matching hook changes from v1.3.5). 327/327 tests pass.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
10
24
|
## v1.3.5 — Patch (2026-04-07)
|
|
11
25
|
|
|
12
26
|
### Fixed
|
|
@@ -8,7 +8,14 @@
|
|
|
8
8
|
echo "MANDATORY: Before answering ANY technical question, apply ALL rules from your CLAUDE.md files (global + project). Follow the exact order of operations defined there. Do NOT skip mandatory steps even if you think you already know the answer."
|
|
9
9
|
echo "REMINDER: When writing features or fixing bugs, ensure tests cover the changes. When modifying API, config, or setup, update relevant documentation. Propose these steps to the user — do not silently skip them."
|
|
10
10
|
|
|
11
|
-
# 2.
|
|
11
|
+
# 2. Check for updates (cached, max once per 24h, non-blocking)
|
|
12
|
+
TOOLKIT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
13
|
+
VERSION_MSG=$(python3 "$TOOLKIT_DIR/scripts/version_check.py" 2>/dev/null)
|
|
14
|
+
if [ -n "$VERSION_MSG" ]; then
|
|
15
|
+
echo "$VERSION_MSG"
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# 3. Load session context (if available)
|
|
12
19
|
SESSION_FILE=".claude/session-context.md"
|
|
13
20
|
if [ -f "$SESSION_FILE" ]; then
|
|
14
21
|
echo "=== Session Context ==="
|
|
@@ -32,17 +32,17 @@ AI Toolkit Config Benchmark
|
|
|
32
32
|
========================
|
|
33
33
|
|
|
34
34
|
## Your Configuration (~/.claude/)
|
|
35
|
-
Agents:
|
|
35
|
+
Agents: 44
|
|
36
36
|
Skills: 80
|
|
37
37
|
Hooks: 12
|
|
38
38
|
|
|
39
39
|
## Toolkit Totals
|
|
40
|
-
Agents:
|
|
40
|
+
Agents: 44
|
|
41
41
|
Skills: 80
|
|
42
42
|
Hooks: 12
|
|
43
43
|
|
|
44
44
|
## Coverage
|
|
45
|
-
Agents: 100% (
|
|
45
|
+
Agents: 100% (44 / 44)
|
|
46
46
|
Skills: 100% (80 / 80)
|
|
47
47
|
Hooks: 100% (12 / 12)
|
|
48
48
|
|
|
@@ -384,7 +384,7 @@ All entries are tagged with "_source": "<source-name>" for idempotent updates.
|
|
|
384
384
|
"required": true
|
|
385
385
|
},
|
|
386
386
|
"agents": {
|
|
387
|
-
"description": "
|
|
387
|
+
"description": "44 specialized agents",
|
|
388
388
|
"files": ["app/agents/*.md"],
|
|
389
389
|
"required": false,
|
|
390
390
|
"default": true
|
|
@@ -24,7 +24,7 @@ Modules are defined in `manifest.json` at the repository root. There are 17 modu
|
|
|
24
24
|
| Module | Description | In Profile |
|
|
25
25
|
|--------|-------------|-----------|
|
|
26
26
|
| `core` | Core hooks and essential skills | minimal, standard, strict, full |
|
|
27
|
-
| `agents` |
|
|
27
|
+
| `agents` | 44 specialized agents | standard, strict, full |
|
|
28
28
|
| `skills` | 90 skills (task, hybrid, knowledge) | standard, strict, full |
|
|
29
29
|
| `rules-common` | Common coding rules (5 files) | standard, strict, full |
|
|
30
30
|
| `rules-typescript` | TypeScript-specific rules (5 files) | auto-detect |
|
package/llms-full.txt
CHANGED
|
@@ -1199,17 +1199,17 @@ AI Toolkit Config Benchmark
|
|
|
1199
1199
|
========================
|
|
1200
1200
|
|
|
1201
1201
|
## Your Configuration (~/.claude/)
|
|
1202
|
-
Agents:
|
|
1202
|
+
Agents: 44
|
|
1203
1203
|
Skills: 80
|
|
1204
1204
|
Hooks: 12
|
|
1205
1205
|
|
|
1206
1206
|
## Toolkit Totals
|
|
1207
|
-
Agents:
|
|
1207
|
+
Agents: 44
|
|
1208
1208
|
Skills: 80
|
|
1209
1209
|
Hooks: 12
|
|
1210
1210
|
|
|
1211
1211
|
## Coverage
|
|
1212
|
-
Agents: 100% (
|
|
1212
|
+
Agents: 100% (44 / 44)
|
|
1213
1213
|
Skills: 100% (80 / 80)
|
|
1214
1214
|
Hooks: 100% (12 / 12)
|
|
1215
1215
|
|
|
@@ -1907,7 +1907,7 @@ All entries are tagged with "_source": "<source-name>" for idempotent updates.
|
|
|
1907
1907
|
"required": true
|
|
1908
1908
|
},
|
|
1909
1909
|
"agents": {
|
|
1910
|
-
"description": "
|
|
1910
|
+
"description": "44 specialized agents",
|
|
1911
1911
|
"files": ["app/agents/*.md"],
|
|
1912
1912
|
"required": false,
|
|
1913
1913
|
"default": true
|
|
@@ -3454,7 +3454,7 @@ Modules are defined in `manifest.json` at the repository root. There are 17 modu
|
|
|
3454
3454
|
| Module | Description | In Profile |
|
|
3455
3455
|
|--------|-------------|-----------|
|
|
3456
3456
|
| `core` | Core hooks and essential skills | minimal, standard, strict, full |
|
|
3457
|
-
| `agents` |
|
|
3457
|
+
| `agents` | 44 specialized agents | standard, strict, full |
|
|
3458
3458
|
| `skills` | 90 skills (task, hybrid, knowledge) | standard, strict, full |
|
|
3459
3459
|
| `rules-common` | Common coding rules (5 files) | standard, strict, full |
|
|
3460
3460
|
| `rules-typescript` | TypeScript-specific rules (5 files) | auto-detect |
|
|
@@ -4067,7 +4067,7 @@ title: "AI Toolkit - Skills Catalog"
|
|
|
4067
4067
|
category: reference
|
|
4068
4068
|
service: ai-toolkit
|
|
4069
4069
|
tags: [skills, domain-knowledge, catalog, task-skills, hybrid-skills]
|
|
4070
|
-
version: "1.3.
|
|
4070
|
+
version: "1.3.3"
|
|
4071
4071
|
created: "2026-03-23"
|
|
4072
4072
|
last_updated: "2026-04-07"
|
|
4073
4073
|
description: "Complete catalog of 90 skills: 28 task, 30 hybrid, 32 knowledge. Includes effort levels, skill-scoped hooks, executable scripts, security auditor, and persona presets."
|
|
@@ -4177,7 +4177,7 @@ Hybrid skills combine slash-command invocation with domain knowledge that agents
|
|
|
4177
4177
|
| `application-deploy` | 3 | Deploy → smoke test → release notes |
|
|
4178
4178
|
| `proactive-troubleshooting` | 4 | Investigate → check perf → preventive fix → docs |
|
|
4179
4179
|
|
|
4180
|
-
## Knowledge Skills - Development (
|
|
4180
|
+
## Knowledge Skills - Development (16)
|
|
4181
4181
|
|
|
4182
4182
|
| Skill | Directory | Domain |
|
|
4183
4183
|
|-------|-----------|--------|
|
|
@@ -4188,6 +4188,12 @@ Hybrid skills combine slash-command invocation with domain knowledge that agents
|
|
|
4188
4188
|
| **ecommerce-patterns** | `skills/ecommerce-patterns/` | E-commerce: catalog, cart, checkout, payments |
|
|
4189
4189
|
| **clean-code** | `skills/clean-code/` | Multi-language code quality: Python, TS, PHP, Go, Dart |
|
|
4190
4190
|
| **typescript-patterns** | `skills/typescript-patterns/` | TypeScript/JavaScript patterns for frontend and backend |
|
|
4191
|
+
| **rust-patterns** | `skills/rust-patterns/` | Ownership, borrowing, error handling, Cargo, tokio, serde |
|
|
4192
|
+
| **java-patterns** | `skills/java-patterns/` | Records, sealed classes, Stream API, Spring Boot, JUnit 5 |
|
|
4193
|
+
| **csharp-patterns** | `skills/csharp-patterns/` | Nullable refs, async/await, ASP.NET Core, EF Core |
|
|
4194
|
+
| **kotlin-patterns** | `skills/kotlin-patterns/` | Coroutines, DSLs, sealed classes, Ktor, MockK |
|
|
4195
|
+
| **swift-patterns** | `skills/swift-patterns/` | Protocol-oriented, SwiftUI, async/await, SPM |
|
|
4196
|
+
| **ruby-patterns** | `skills/ruby-patterns/` | Blocks, Rails conventions, RSpec, ActiveRecord |
|
|
4191
4197
|
| **design-engineering** | `skills/design-engineering/` | UI polish, animation craft, easing, transforms, accessibility |
|
|
4192
4198
|
| **documentation-standards** | `skills/documentation-standards/` | KB document conventions, frontmatter validation, category taxonomy |
|
|
4193
4199
|
| **brand-voice** | `skills/brand-voice/` | Anti-trope list, voice principles, LLM rhetoric prevention |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softspark/ai-toolkit",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.7",
|
|
4
4
|
"description": "Professional-grade AI coding toolkit: 90 skills, 44 agents, multi-platform support (Claude, Cursor, Windsurf, Copilot, Gemini, Cline, Roo Code, Aider, Augment), machine-enforced safety constitution, persona presets, skill security auditor, expanded lifecycle hooks, 11 plugin packs, and benchmark tooling.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -166,11 +166,11 @@ def install_local_project(rules_dir: Path, dry_run: bool, reset: bool,
|
|
|
166
166
|
|
|
167
167
|
|
|
168
168
|
def _inject_language_rules(cwd: Path, language_modules: list[str] | None) -> None:
|
|
169
|
-
"""Inject language-specific
|
|
169
|
+
"""Inject language-specific rule summary into project's .claude/CLAUDE.md.
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
Instead of injecting full rule content (hundreds of lines), injects a
|
|
172
|
+
compact summary with the key rules per category. Full rules are available
|
|
173
|
+
as knowledge skills that Claude auto-loads contextually.
|
|
174
174
|
"""
|
|
175
175
|
if not language_modules:
|
|
176
176
|
return
|
|
@@ -179,19 +179,35 @@ def _inject_language_rules(cwd: Path, language_modules: list[str] | None) -> Non
|
|
|
179
179
|
if not rules_src.is_dir():
|
|
180
180
|
return
|
|
181
181
|
|
|
182
|
-
#
|
|
183
|
-
|
|
182
|
+
# Detect language names
|
|
183
|
+
langs: list[str] = []
|
|
184
184
|
for mod in language_modules:
|
|
185
|
-
# module name format: "rules-typescript" → dir "typescript"
|
|
186
185
|
if mod.startswith("rules-"):
|
|
187
|
-
|
|
186
|
+
langs.append(mod[6:])
|
|
188
187
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
if not langs:
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
# Build compact summary: first 5 key rules from each category
|
|
192
|
+
lines: list[str] = ["# Language Rules (auto-detected)", ""]
|
|
193
|
+
lines.append(f"Detected: **{', '.join(langs)}** + common rules.")
|
|
194
|
+
lines.append("")
|
|
195
|
+
lines.append("Detailed rules available as knowledge skills (auto-loaded by Claude).")
|
|
196
|
+
lines.append("")
|
|
197
|
+
|
|
198
|
+
# Deduplicate: common + unique language dirs
|
|
199
|
+
all_dirs: list[str] = ["common"]
|
|
200
|
+
for l in langs:
|
|
201
|
+
if l not in all_dirs:
|
|
202
|
+
all_dirs.append(l)
|
|
203
|
+
|
|
204
|
+
for lang_dir in all_dirs:
|
|
192
205
|
lang_path = rules_src / lang_dir
|
|
193
206
|
if not lang_path.is_dir():
|
|
194
207
|
continue
|
|
208
|
+
lang_label = lang_dir.capitalize() if lang_dir != "common" else "Common"
|
|
209
|
+
lines.append(f"## {lang_label} Rules")
|
|
210
|
+
lines.append("")
|
|
195
211
|
for rule_file in sorted(lang_path.glob("*.md")):
|
|
196
212
|
content = rule_file.read_text(encoding="utf-8").strip()
|
|
197
213
|
# Strip YAML frontmatter
|
|
@@ -199,15 +215,16 @@ def _inject_language_rules(cwd: Path, language_modules: list[str] | None) -> Non
|
|
|
199
215
|
end = content.find("---", 3)
|
|
200
216
|
if end != -1:
|
|
201
217
|
content = content[end + 3:].strip()
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
218
|
+
# Extract first 3 bullet points as key rules
|
|
219
|
+
bullets = [l.strip() for l in content.splitlines() if l.strip().startswith("- ")][:3]
|
|
220
|
+
if bullets:
|
|
221
|
+
category = rule_file.stem.replace("-", " ").title()
|
|
222
|
+
lines.append(f"**{category}:** {' | '.join(b.lstrip('- ') for b in bullets)}")
|
|
223
|
+
lines.append("")
|
|
224
|
+
|
|
225
|
+
# Write summary to temp file, then inject as section
|
|
209
226
|
import tempfile
|
|
210
|
-
combined = "\n
|
|
227
|
+
combined = "\n".join(lines)
|
|
211
228
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False,
|
|
212
229
|
encoding="utf-8") as tmp:
|
|
213
230
|
tmp.write(combined)
|
|
@@ -215,8 +232,8 @@ def _inject_language_rules(cwd: Path, language_modules: list[str] | None) -> Non
|
|
|
215
232
|
|
|
216
233
|
try:
|
|
217
234
|
inject_section(tmp_path, cwd / ".claude" / "CLAUDE.md", "language-rules")
|
|
218
|
-
|
|
219
|
-
print(f" Injected: language rules (common + {
|
|
235
|
+
lang_names = [l for l in langs if l != "common"]
|
|
236
|
+
print(f" Injected: language rules summary (common + {', '.join(lang_names)})")
|
|
220
237
|
finally:
|
|
221
238
|
tmp_path.unlink(missing_ok=True)
|
|
222
239
|
|
|
@@ -115,3 +115,18 @@ def print_status() -> None:
|
|
|
115
115
|
# Strip "rules-" prefix for readability
|
|
116
116
|
langs = [m.replace("rules-", "") for m in detected]
|
|
117
117
|
print(f" Detected: {', '.join(langs)}")
|
|
118
|
+
|
|
119
|
+
# Check for updates
|
|
120
|
+
try:
|
|
121
|
+
import sys as _sys
|
|
122
|
+
_sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
123
|
+
from version_check import check
|
|
124
|
+
result = check()
|
|
125
|
+
if result["update_available"]:
|
|
126
|
+
print()
|
|
127
|
+
print(f" Update available: {result['installed']} -> {result['latest']}")
|
|
128
|
+
print(f" Run: npm install -g @softspark/ai-toolkit@latest && ai-toolkit update")
|
|
129
|
+
else:
|
|
130
|
+
print(f" Latest: {result['latest']} (up to date)")
|
|
131
|
+
except Exception:
|
|
132
|
+
pass # version check is optional
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Check if a newer version of ai-toolkit is available on npm.
|
|
3
|
+
|
|
4
|
+
Caches the result for 24 hours to avoid hitting npm on every session.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
version_check.py # Print update notice if available (for hooks)
|
|
8
|
+
version_check.py --status # Print detailed version info (for ai-toolkit status)
|
|
9
|
+
version_check.py --force # Force re-check (ignore cache)
|
|
10
|
+
|
|
11
|
+
Exit codes:
|
|
12
|
+
0 Up to date (or check skipped/failed)
|
|
13
|
+
1 Update available
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
import time
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
CACHE_FILE = Path.home() / ".ai-toolkit" / "version-check.json"
|
|
25
|
+
CACHE_TTL = 86400 # 24 hours
|
|
26
|
+
PACKAGE_NAME = "@softspark/ai-toolkit"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _get_installed_version() -> str:
|
|
30
|
+
"""Read version from the toolkit's own package.json."""
|
|
31
|
+
pkg = Path(__file__).resolve().parent.parent / "package.json"
|
|
32
|
+
if pkg.is_file():
|
|
33
|
+
with open(pkg, encoding="utf-8") as f:
|
|
34
|
+
return json.load(f).get("version", "unknown")
|
|
35
|
+
return "unknown"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _get_cached() -> dict | None:
|
|
39
|
+
"""Read cached version check result."""
|
|
40
|
+
if not CACHE_FILE.is_file():
|
|
41
|
+
return None
|
|
42
|
+
try:
|
|
43
|
+
with open(CACHE_FILE, encoding="utf-8") as f:
|
|
44
|
+
data = json.load(f)
|
|
45
|
+
checked_at = data.get("checked_at", 0)
|
|
46
|
+
if time.time() - checked_at > CACHE_TTL:
|
|
47
|
+
return None # expired
|
|
48
|
+
return data
|
|
49
|
+
except (json.JSONDecodeError, OSError):
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _save_cache(installed: str, latest: str) -> None:
|
|
54
|
+
"""Save version check result to cache."""
|
|
55
|
+
CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
56
|
+
data = {
|
|
57
|
+
"installed": installed,
|
|
58
|
+
"latest": latest,
|
|
59
|
+
"checked_at": time.time(),
|
|
60
|
+
}
|
|
61
|
+
tmp = str(CACHE_FILE) + ".tmp"
|
|
62
|
+
with open(tmp, "w", encoding="utf-8") as f:
|
|
63
|
+
json.dump(data, f)
|
|
64
|
+
os.replace(tmp, str(CACHE_FILE))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _fetch_latest_version() -> str | None:
|
|
68
|
+
"""Fetch latest version from npm registry (2s timeout)."""
|
|
69
|
+
try:
|
|
70
|
+
result = subprocess.run(
|
|
71
|
+
["npm", "view", PACKAGE_NAME, "version"],
|
|
72
|
+
capture_output=True, text=True, timeout=5,
|
|
73
|
+
)
|
|
74
|
+
if result.returncode == 0:
|
|
75
|
+
return result.stdout.strip()
|
|
76
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
77
|
+
pass
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _parse_semver(version: str) -> tuple[int, ...]:
|
|
82
|
+
"""Parse version string to comparable tuple."""
|
|
83
|
+
try:
|
|
84
|
+
return tuple(int(x) for x in version.split("."))
|
|
85
|
+
except (ValueError, AttributeError):
|
|
86
|
+
return (0, 0, 0)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _is_newer(latest: str, installed: str) -> bool:
|
|
90
|
+
"""Check if latest is newer than installed."""
|
|
91
|
+
return _parse_semver(latest) > _parse_semver(installed)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def check(force: bool = False) -> dict:
|
|
95
|
+
"""Check for updates. Returns {installed, latest, update_available}."""
|
|
96
|
+
installed = _get_installed_version()
|
|
97
|
+
|
|
98
|
+
if not force:
|
|
99
|
+
cached = _get_cached()
|
|
100
|
+
if cached:
|
|
101
|
+
return {
|
|
102
|
+
"installed": installed,
|
|
103
|
+
"latest": cached.get("latest", installed),
|
|
104
|
+
"update_available": _is_newer(cached.get("latest", ""), installed),
|
|
105
|
+
"cached": True,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
latest = _fetch_latest_version()
|
|
109
|
+
if latest:
|
|
110
|
+
_save_cache(installed, latest)
|
|
111
|
+
return {
|
|
112
|
+
"installed": installed,
|
|
113
|
+
"latest": latest,
|
|
114
|
+
"update_available": _is_newer(latest, installed),
|
|
115
|
+
"cached": False,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
"installed": installed,
|
|
120
|
+
"latest": "unknown",
|
|
121
|
+
"update_available": False,
|
|
122
|
+
"cached": False,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def main() -> None:
|
|
127
|
+
force = "--force" in sys.argv
|
|
128
|
+
status_mode = "--status" in sys.argv
|
|
129
|
+
|
|
130
|
+
result = check(force=force)
|
|
131
|
+
|
|
132
|
+
if status_mode:
|
|
133
|
+
print(f" Installed: {result['installed']}")
|
|
134
|
+
print(f" Latest: {result['latest']}")
|
|
135
|
+
if result["update_available"]:
|
|
136
|
+
print(f" Update: {result['installed']} -> {result['latest']}")
|
|
137
|
+
print(f" Run: npm install -g {PACKAGE_NAME}@latest && ai-toolkit update")
|
|
138
|
+
else:
|
|
139
|
+
print(" Update: up to date")
|
|
140
|
+
sys.exit(1 if result["update_available"] else 0)
|
|
141
|
+
|
|
142
|
+
# Hook mode: only print if update available
|
|
143
|
+
if result["update_available"]:
|
|
144
|
+
print(
|
|
145
|
+
f"ai-toolkit update available: {result['installed']} -> {result['latest']} "
|
|
146
|
+
f"(npm install -g {PACKAGE_NAME}@latest)"
|
|
147
|
+
)
|
|
148
|
+
sys.exit(1)
|
|
149
|
+
|
|
150
|
+
sys.exit(0)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if __name__ == "__main__":
|
|
154
|
+
main()
|