Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d813c90ae | ||
|
|
456919df43 | ||
|
|
52f792f00f |
No files matched your search
@@ -2,7 +2,7 @@
|
||||
"name": "aiturk-ide",
|
||||
"productName": "AITURK IDE",
|
||||
"private": true,
|
||||
"version": "1.0.0-beta.13",
|
||||
"version": "1.0.0-beta.12",
|
||||
"description": "TurkServis resmi yapay zekâ geliştirme ortamı. Hermes Agent tabanlıdır.",
|
||||
"author": "AITURK / TurkServis",
|
||||
"repository": {
|
||||
|
||||
@@ -53,6 +53,44 @@ npm run android:sync --workspaces=false
|
||||
üretim imzası içermez ve kurulabilir sürüm olarak paylaşılmamalıdır. Özel üretim
|
||||
imza anahtarı oluşturulmadı; mağaza hesabı veya ücretli hizmet açılmadı.
|
||||
|
||||
Yayın adayı için sürüm ve Android sürüm kodu birlikte açıkça verilir:
|
||||
|
||||
```powershell
|
||||
.\android\gradlew.bat -p android assembleRelease '-PaiturkReleaseVersion=0.1.4-beta.1' '-PaiturkReleaseVersionCode=6'
|
||||
```
|
||||
|
||||
Bu komut yine **imzasız** APK üretir; üretim imzasını veya cihaz kabulünü atlamaz.
|
||||
İki parametreden yalnız biri, `-dev` etiketi veya 5 ve daha küçük sürüm kodu
|
||||
verilirse derleme durur. Parametresiz geliştirici derlemesi `0.1.4-dev` / 5 kalır.
|
||||
Sonraki yayınlarda sürüm kodunu son dağıtılmış APK'dan daha büyük seçin; buradaki
|
||||
alt sınır yalnız mevcut geliştirme sürümünü korur, uzak yayın envanterini sorgulamaz.
|
||||
|
||||
12 Eylül 2026: gerçek Gradle `assembleRelease` çalışması bu parametrelerle 55
|
||||
saniyede tamamlandı; `aapt2` uygulama kimliği, `0.1.4-beta.1` / 6 ve debug
|
||||
bayrağının olmadığını doğruladı. Test çıktısının SHA-256 değeri
|
||||
`a93b93b3080ec484cc998ef316528ff7b947fa366b2ff5de375386acc66fffc4`.
|
||||
`apksigner verify` imzasız çıktıyı reddetti (beklenen sonuç). Ayrı gerçek Gradle
|
||||
çalışmaları eksik sürüm kodunu, `0.1.4-dev` yayın etiketini ve sürüm kodu 5'i
|
||||
reddetti. Bu çıktı yayımlanmadı; imza ve cihaz kabulü değildir.
|
||||
|
||||
### Dağıtım APK'sının doğrulanması
|
||||
|
||||
`scripts/verify-android-release.mjs` dağıtım öncesi salt okunur APK kontrolüdür.
|
||||
`--apk`, `--java`, `--apksigner` (Android SDK içindeki apksigner.jar), `--aapt2`,
|
||||
`--certificate` (bağımsız olarak belirlenen üretim sertifikası SHA-256 değeri),
|
||||
`--version` ve `--version-code` parametrelerinin tamamını ister. Sertifika pinini
|
||||
kontrol edilen APK'dan otomatik türetmeyin. Bu araç anahtar üretmez veya APK imzalamaz.
|
||||
|
||||
Gerçek apksigner doğrulaması, v2/v3 imza, tek beklenen sertifika, uygulama kimliği
|
||||
`online.turkservis.aiturk.mobile`, tam sürüm eşleşmesi ve debug bayrağı kontrol edilir.
|
||||
Dosya doğrulama boyunca değişirse sonuç reddedilir. Debug sertifikası ve `-dev`
|
||||
sürümü dağıtım için kabul edilmez. Başarılı rapor APK hash'ini verir fakat açıkça
|
||||
`deviceAcceptanceVerified: false` içerir; fiziksel telefon kabulünün yerine geçmez.
|
||||
|
||||
12 Eylül 2026: mevcut mutual-identity geliştirici APK'sı gerçek SDK araçlarıyla
|
||||
kontrol edildi ve beklenildiği gibi `debug-signing-certificate` ile reddedildi.
|
||||
Üretim sertifikalı pozitif APK doğrulaması henüz yapılmadı.
|
||||
|
||||
## Uyumlu IDE ve bağlantı servisi
|
||||
|
||||
Bu kaynak ağacındaki masaüstü paketini normal masaüstü build süreciyle derleyin.
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
// Release identity is explicit; ordinary developer builds retain the -dev label.
|
||||
def releaseVersion = providers.gradleProperty('aiturkReleaseVersion').orNull
|
||||
def releaseCode = providers.gradleProperty('aiturkReleaseVersionCode').orNull
|
||||
if ((releaseVersion == null) != (releaseCode == null)) {
|
||||
throw new GradleException('Both aiturkReleaseVersion and aiturkReleaseVersionCode are required')
|
||||
}
|
||||
def selectedVersionCode = 5
|
||||
if (releaseVersion != null) {
|
||||
if (!(releaseVersion ==~ /\d+\.\d+\.\d+(?:-beta\.\d+)?/)) {
|
||||
throw new GradleException('Release version must be X.Y.Z or X.Y.Z-beta.N; -dev is not a release')
|
||||
}
|
||||
if (!(releaseCode ==~ /[1-9]\d{0,9}/) || releaseCode.toLong() <= 5 || releaseCode.toLong() > 2100000000L) {
|
||||
throw new GradleException('Release version code must be greater than 5 and at most 2100000000')
|
||||
}
|
||||
selectedVersionCode = releaseCode.toInteger()
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "online.turkservis.aiturk.mobile"
|
||||
compileSdk = rootProject.ext.compileSdkVersion
|
||||
@@ -7,8 +24,8 @@ android {
|
||||
applicationId "online.turkservis.aiturk.mobile"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 5
|
||||
versionName "0.1.4-dev"
|
||||
versionCode selectedVersionCode
|
||||
versionName releaseVersion ?: "0.1.4-dev"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { createReadStream } from 'node:fs'
|
||||
import { lstat } from 'node:fs/promises'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { parseArgs } from 'node:util'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
|
||||
export function validateAndroidRelease({ signature, badging, certificate, version, versionCode }) {
|
||||
const expected = certificate.trim().replaceAll(':', '').toLowerCase()
|
||||
if (!/^[a-f0-9]{64}$/.test(expected)) throw new Error('certificate-pin-invalid')
|
||||
if (!/^\d+\.\d+\.\d+(?:-beta\.\d+)?$/.test(version)) throw new Error('release-version-invalid')
|
||||
if (!Number.isSafeInteger(versionCode) || versionCode <= 0) throw new Error('version-code-invalid')
|
||||
const signers = [...signature.matchAll(/^Signer #\d+ certificate SHA-256 digest: ([a-fA-F0-9]{64})\s*$/gm)]
|
||||
if (signers.length !== 1 || signers[0][1].toLowerCase() !== expected) throw new Error('signing-certificate-mismatch')
|
||||
if (/^Signer #\d+ certificate DN:.*\bCN=Android Debug\b/im.test(signature)) throw new Error('debug-signing-certificate')
|
||||
if (!/^Verified using v[23](?:\.\d+)? scheme[^\r\n]*: true\s*$/m.test(signature)) throw new Error('modern-apk-signature-required')
|
||||
const packages = [...badging.matchAll(/^package: name='([^']+)' versionCode='([^']+)' versionName='([^']+)'/gm)]
|
||||
if (packages.length !== 1 || packages[0][1] !== 'online.turkservis.aiturk.mobile') throw new Error('application-id-mismatch')
|
||||
if (packages[0][2] !== String(versionCode) || packages[0][3] !== version) throw new Error('apk-version-mismatch')
|
||||
if (/^application-debuggable\b/m.test(badging)) throw new Error('debuggable-apk')
|
||||
return { applicationId: packages[0][1], version, versionCode, certificateSha256: expected }
|
||||
}
|
||||
|
||||
async function regularFile(path) {
|
||||
let current = path
|
||||
while (true) {
|
||||
const info = await lstat(current)
|
||||
if (info.isSymbolicLink()) throw new Error('apk-reparse-path')
|
||||
if (current === path && !info.isFile()) throw new Error('apk-not-regular')
|
||||
const parent = dirname(current)
|
||||
if (parent === current) return
|
||||
current = parent
|
||||
}
|
||||
}
|
||||
|
||||
async function digest(path) {
|
||||
const hash = createHash('sha256')
|
||||
for await (const chunk of createReadStream(path)) hash.update(chunk)
|
||||
return hash.digest('hex')
|
||||
}
|
||||
|
||||
function tool(executable, args) {
|
||||
const env = { ...process.env }
|
||||
delete env.JAVA_TOOL_OPTIONS
|
||||
delete env._JAVA_OPTIONS
|
||||
delete env.JDK_JAVA_OPTIONS
|
||||
const result = spawnSync(executable, args, { env, encoding: 'utf8', windowsHide: true, timeout: 60_000, maxBuffer: 1024 * 1024 })
|
||||
if (result.error || result.status !== 0) throw new Error('android-verification-tool-failed')
|
||||
return result.stdout
|
||||
}
|
||||
|
||||
export async function verifyAndroidRelease(options) {
|
||||
const apk = resolve(options.apk)
|
||||
await regularFile(apk)
|
||||
const before = await lstat(apk)
|
||||
const sha256 = await digest(apk)
|
||||
const signature = tool(resolve(options.java), ['-jar', resolve(options.apksigner), 'verify', '--verbose', '--print-certs', apk])
|
||||
const badging = tool(resolve(options.aapt2), ['dump', 'badging', apk])
|
||||
const identity = validateAndroidRelease({ ...options, signature, badging })
|
||||
await regularFile(apk)
|
||||
const after = await lstat(apk)
|
||||
if (before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs || await digest(apk) !== sha256) throw new Error('apk-changed-during-verification')
|
||||
return { verified: true, ...identity, size: after.size, sha256, deviceAcceptanceVerified: false }
|
||||
}
|
||||
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
||||
const { values } = parseArgs({ options: Object.fromEntries(['apk', 'java', 'apksigner', 'aapt2', 'certificate', 'version', 'version-code'].map(name => [name, { type: 'string' }])) })
|
||||
try {
|
||||
if (Object.keys(values).length !== 7) throw new Error('all-release-verification-arguments-required')
|
||||
console.log(JSON.stringify(await verifyAndroidRelease({ ...values, versionCode: Number(values['version-code']) })))
|
||||
} catch (error) {
|
||||
const code = /^[a-z][a-z0-9-]{1,95}$/.test(error.message) ? error.message : 'android-release-verification-failed'
|
||||
console.error(JSON.stringify({ verified: false, code }))
|
||||
process.exitCode = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import { validateAndroidRelease } from '../scripts/verify-android-release.mjs'
|
||||
|
||||
const certificate = 'a'.repeat(64)
|
||||
const fixture = {
|
||||
certificate, version: '0.1.4-beta.1', versionCode: 6,
|
||||
signature: `Verified using v2 scheme (APK Signature Scheme v2): true\nSigner #1 certificate DN: CN=AITURK Release\nSigner #1 certificate SHA-256 digest: ${certificate}\n`,
|
||||
badging: "package: name='online.turkservis.aiturk.mobile' versionCode='6' versionName='0.1.4-beta.1'\n",
|
||||
}
|
||||
|
||||
test('APK release identity requires a pinned certificate and exact version', () => {
|
||||
assert.equal(validateAndroidRelease(fixture).certificateSha256, certificate)
|
||||
assert.throws(() => validateAndroidRelease({ ...fixture, certificate: 'b'.repeat(64) }), /certificate-mismatch/)
|
||||
assert.throws(() => validateAndroidRelease({ ...fixture, versionCode: 7 }), /version-mismatch/)
|
||||
assert.throws(() => validateAndroidRelease({ ...fixture, version: '0.1.4-dev' }), /release-version-invalid/)
|
||||
})
|
||||
|
||||
test('debug and ambiguous APK identities are never release-ready', () => {
|
||||
assert.throws(() => validateAndroidRelease({ ...fixture, signature: fixture.signature.replace('AITURK Release', 'Android Debug') }), /debug-signing/)
|
||||
assert.throws(() => validateAndroidRelease({ ...fixture, badging: fixture.badging + 'application-debuggable\n' }), /debuggable-apk/)
|
||||
assert.throws(() => validateAndroidRelease({ ...fixture, badging: fixture.badging.replace('online.turkservis.aiturk.mobile', 'other.application') }), /application-id/)
|
||||
assert.throws(() => validateAndroidRelease({ ...fixture, signature: fixture.signature + `Signer #2 certificate SHA-256 digest: ${certificate}\n` }), /certificate-mismatch/)
|
||||
assert.throws(() => validateAndroidRelease({ ...fixture, signature: fixture.signature.replace(': true', ': false') }), /modern-apk-signature/)
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
"""Connect the AITURK distribution's media tools to its existing member key."""
|
||||
|
||||
|
||||
def configure_existing_media(cfg: dict) -> bool:
|
||||
"""Migrate an existing member endpoint when the upgraded backend starts."""
|
||||
providers = cfg.get("providers") or {}
|
||||
entries = providers.values() if isinstance(providers, dict) else []
|
||||
for entry in entries:
|
||||
if isinstance(entry, dict) and configure_media(cfg, entry):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def configure_media(cfg: dict, entry: dict) -> bool:
|
||||
if entry.get("base_url", "").rstrip("/") != "https://ai.turkservis.online/v1":
|
||||
return False
|
||||
key_env = entry.get("key_env")
|
||||
if not isinstance(key_env, str) or not key_env:
|
||||
return False
|
||||
servers = cfg.get("mcp_servers")
|
||||
if servers is None:
|
||||
servers = cfg["mcp_servers"] = {}
|
||||
if not isinstance(servers, dict):
|
||||
return False
|
||||
name = "turkservis-media"
|
||||
existing = servers.get(name, {})
|
||||
if not isinstance(existing, dict):
|
||||
return False
|
||||
if existing and existing.get("url") != "https://ai.turkservis.online/mcp":
|
||||
return False
|
||||
updated = {
|
||||
**existing,
|
||||
"url": "https://ai.turkservis.online/mcp",
|
||||
"headers": {"Authorization": "Bearer ${" + key_env + "}"},
|
||||
"timeout": 300,
|
||||
"tools": {"include": ["list_capabilities", "generate_media"]},
|
||||
}
|
||||
if updated == existing:
|
||||
return False
|
||||
servers[name] = updated
|
||||
return True
|
||||
@@ -405,6 +405,12 @@ def _eager_reconcile_own_session_db() -> None:
|
||||
|
||||
@asynccontextmanager
|
||||
async def _lifespan(app: "FastAPI"):
|
||||
from hermes_cli.aiturk_media import configure_existing_media
|
||||
from hermes_cli.config import read_raw_config, save_config
|
||||
with _CONFIG_MUTATION_LOCK:
|
||||
media_cfg = read_raw_config()
|
||||
if configure_existing_media(media_cfg):
|
||||
save_config(media_cfg)
|
||||
app.state.event_channels = {} # dict[str, set]
|
||||
app.state.event_lock = asyncio.Lock()
|
||||
app.state.pty_active_session_files = {} # dict[str, Path]
|
||||
@@ -8823,6 +8829,9 @@ def _write_custom_endpoint(cfg: Dict[str, Any], body: CustomEndpointUpdate) -> T
|
||||
providers[endpoint_id] = entry
|
||||
cfg["providers"] = providers
|
||||
|
||||
from hermes_cli.aiturk_media import configure_media
|
||||
configure_media(cfg, entry)
|
||||
|
||||
if body.make_default:
|
||||
cfg["model"] = _apply_main_model_assignment(
|
||||
cfg.get("model", {}), endpoint_id, model, base_url
|
||||
|
||||
Generated
+1
-1
@@ -65,7 +65,7 @@
|
||||
},
|
||||
"apps/desktop": {
|
||||
"name": "aiturk-ide",
|
||||
"version": "1.0.0-beta.13",
|
||||
"version": "1.0.0-beta.12",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@assistant-ui/core": "0.2.23",
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
---
|
||||
name: aiturk-media
|
||||
description: "Generate images, video and speech or transcribe audio with the user's TurkServis account through the AITURK CLI (`aiturk media`). Use whenever the user asks for an image, video, voice-over or transcript."
|
||||
version: 1.0.0
|
||||
author: AITURK / TurkServis
|
||||
license: MIT
|
||||
platforms: [windows]
|
||||
prerequisites:
|
||||
commands: [aiturk]
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [Media, Image, Video, TTS, STT, AITURK, TurkServis]
|
||||
---
|
||||
|
||||
# AITURK media (image, video, speech, transcription)
|
||||
|
||||
AITURK IDE produces media with the AITURK CLI that is installed next to it. The
|
||||
CLI uses the same TurkServis account; usage is billed to the member's balance.
|
||||
Do not add or enable an MCP media server for this.
|
||||
|
||||
## When to use
|
||||
|
||||
The user asks for an image, a short video, spoken audio from text, or a
|
||||
transcript of an audio file.
|
||||
|
||||
## Steps
|
||||
|
||||
The launcher is `aiturk.cmd`. In PowerShell or cmd type `aiturk`; in a bash-style
|
||||
terminal (Git Bash) type `aiturk.cmd` instead, with the same arguments.
|
||||
|
||||
1. List what the account can use right now. Never rely on a remembered model list.
|
||||
|
||||
```shell
|
||||
aiturk media list --json
|
||||
```
|
||||
|
||||
Use a capability only when its `status` is `available`. `unknown` is an
|
||||
access error; `unavailable` means no usable model. Pick the model the user
|
||||
named, or a fitting `id` from the list. Never invent a model id.
|
||||
|
||||
2. Run one generation. Choose an output file name that does not exist yet.
|
||||
|
||||
```shell
|
||||
aiturk media run image --model <model-id> --text "<what the user asked for>" --output image.png
|
||||
aiturk media run video --model <model-id> --text "<prompt>" --seconds 5 --output clip.mp4
|
||||
aiturk media run tts --model <model-id> --text "<text>" --voice <voice> --output speech.mp3
|
||||
aiturk media run stt --model <model-id> --audio <audio-file> --output transcript.txt
|
||||
```
|
||||
|
||||
3. Confirm the output file exists and has the expected type, then show or link
|
||||
it to the user. Do not retry a failed or interrupted generation automatically;
|
||||
each run is billed.
|
||||
|
||||
## If the command is unavailable
|
||||
|
||||
- `aiturk` and `aiturk.cmd` both not found: try `%LOCALAPPDATA%\TurkServis\AITURK\program\bin\aiturk.cmd`.
|
||||
If that is missing too, tell the user to install the AITURK CLI from
|
||||
https://turkservis.online/indir.
|
||||
- Not signed in or an authentication error: ask the user to run `aiturk login`
|
||||
once, then `aiturk doctor`.
|
||||
- Only while the CLI cannot be used at all may an already configured TurkServis
|
||||
media connection be used instead; say that you did and why.
|
||||
|
||||
Never print API keys, tokens or authorization headers. Without a generation
|
||||
request, list only; do not start a paid trial generation.
|
||||
@@ -1,52 +1,35 @@
|
||||
"""AITURK IDE produces media through the AITURK CLI, never by adding an MCP server."""
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SKILL = ROOT / "skills" / "media" / "aiturk-media" / "SKILL.md"
|
||||
from hermes_cli.aiturk_media import configure_media, configure_existing_media
|
||||
|
||||
|
||||
def test_backend_never_registers_a_turkservis_media_mcp_server():
|
||||
assert importlib.util.find_spec("hermes_cli.aiturk_media") is None
|
||||
source = (ROOT / "hermes_cli" / "web_server.py").read_text(encoding="utf-8")
|
||||
for needle in ("aiturk_media", "turkservis-media", "generate_media", "ai.turkservis.online/mcp"):
|
||||
assert needle not in source
|
||||
def test_existing_member_profile_migrates_once_without_changing_provider():
|
||||
entry = {"base_url": "https://ai.turkservis.online/v1", "key_env": "MEMBER_KEY"}
|
||||
cfg = {"providers": {"turkservis": dict(entry)}, "model": {"default": "kept"}}
|
||||
assert configure_existing_media(cfg)
|
||||
assert cfg["providers"]["turkservis"] == entry
|
||||
assert cfg["model"] == {"default": "kept"}
|
||||
assert not configure_existing_media(cfg)
|
||||
|
||||
|
||||
def test_no_backend_module_writes_the_turkservis_mcp_endpoint_into_config():
|
||||
offenders = [
|
||||
path.relative_to(ROOT).as_posix()
|
||||
for package in ("hermes_cli", "agent", "tools", "gateway", "tui_gateway")
|
||||
for path in (ROOT / package).rglob("*.py")
|
||||
if "ai.turkservis.online/mcp" in path.read_text(encoding="utf-8", errors="ignore")
|
||||
]
|
||||
assert offenders == []
|
||||
def test_member_key_is_referenced_without_copying_or_overwriting_other_servers():
|
||||
cfg = {"mcp_servers": {"other": {"url": "https://example.org/mcp"}}}
|
||||
entry = {"base_url": "https://ai.turkservis.online/v1", "key_env": "HERMES_CUSTOM_TURKSERVIS_API_KEY"}
|
||||
assert configure_media(cfg, entry)
|
||||
assert cfg["mcp_servers"]["other"] == {"url": "https://example.org/mcp"}
|
||||
assert cfg["mcp_servers"]["turkservis-media"]["headers"]["Authorization"] == "Bearer ${HERMES_CUSTOM_TURKSERVIS_API_KEY}"
|
||||
assert not configure_media(cfg, entry)
|
||||
entry["key_env"] = "ROTATED_MEMBER_KEY"
|
||||
assert configure_media(cfg, entry)
|
||||
assert "ROTATED_MEMBER_KEY" in cfg["mcp_servers"]["turkservis-media"]["headers"]["Authorization"]
|
||||
|
||||
|
||||
def test_existing_user_mcp_entries_are_left_exactly_as_they_are():
|
||||
# Startup used to rewrite config.yaml; with the writer gone, a saved config
|
||||
# round-trips untouched (including a disabled turkservis-media entry).
|
||||
before = {
|
||||
"providers": {"turkservis": {"base_url": "https://ai.turkservis.online/v1", "key_env": "MEMBER_KEY"}},
|
||||
"mcp_servers": {
|
||||
"turkservis-media": {"url": "https://ai.turkservis.online/mcp", "enabled": False},
|
||||
"other": {"url": "https://example.org/mcp"},
|
||||
},
|
||||
}
|
||||
source = (ROOT / "hermes_cli" / "web_server.py").read_text(encoding="utf-8")
|
||||
lifespan = source[source.index("async def _lifespan("):]
|
||||
lifespan = lifespan[:lifespan.index("yield")]
|
||||
assert "save_config" not in lifespan and "mcp_servers" not in lifespan
|
||||
assert yaml.safe_load(yaml.safe_dump(before)) == before
|
||||
def test_other_provider_and_missing_key_do_not_enable_media():
|
||||
for entry in ({"base_url": "https://example.com/v1", "key_env": "SECRET"}, {"base_url": "https://ai.turkservis.online/v1"}):
|
||||
cfg = {}
|
||||
assert not configure_media(cfg, entry)
|
||||
assert cfg == {}
|
||||
|
||||
|
||||
def test_bundled_skill_teaches_aiturk_media():
|
||||
text = SKILL.read_text(encoding="utf-8")
|
||||
meta = yaml.safe_load(text.split("---")[1])
|
||||
assert meta["name"] == "aiturk-media"
|
||||
for phrase in ("aiturk media list --json", "aiturk media run image", "aiturk media run video",
|
||||
"aiturk media run tts", "aiturk media run stt", "aiturk login"):
|
||||
assert phrase in text
|
||||
assert "Do not add or enable an MCP media server" in text
|
||||
def test_explicitly_disabled_connection_stays_disabled():
|
||||
cfg = {"mcp_servers": {"turkservis-media": {"url": "https://ai.turkservis.online/mcp", "enabled": False}}}
|
||||
configure_media(cfg, {"base_url": "https://ai.turkservis.online/v1", "key_env": "KEY"})
|
||||
assert cfg["mcp_servers"]["turkservis-media"]["enabled"] is False
|
||||
Reference in new issue
Block a user