@appaflytech/zeyrek-ai-mcp 0.0.35 → 0.0.37

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 (210) hide show
  1. package/.env.example +15 -0
  2. package/README.md +5 -5
  3. package/dist/config.js +37 -0
  4. package/dist/index.js +2 -0
  5. package/dist/knowledge/access.js +802 -0
  6. package/dist/knowledge/backendConstraints.js +18 -1
  7. package/dist/knowledge/case.js +41 -0
  8. package/dist/knowledge/componentCatalog.js +655 -424
  9. package/dist/knowledge/componentEvents.js +140 -23
  10. package/dist/knowledge/connectDataBind.js +843 -0
  11. package/dist/knowledge/connectDataDefinition.js +344 -0
  12. package/dist/knowledge/connectDataExecute.js +93 -0
  13. package/dist/knowledge/connectDataRules.js +128 -0
  14. package/dist/knowledge/decision.js +121 -0
  15. package/dist/knowledge/expressionPrelude.js +59 -0
  16. package/dist/knowledge/layoutSummary.js +140 -2
  17. package/dist/knowledge/microserviceParameters.js +58 -0
  18. package/dist/knowledge/mining.js +94 -0
  19. package/dist/knowledge/mobileComponentCatalog.js +864 -0
  20. package/dist/knowledge/mobileComponentEvents.js +239 -0
  21. package/dist/knowledge/mobileStore.js +321 -0
  22. package/dist/knowledge/normalize.js +235 -84
  23. package/dist/knowledge/outlines.js +36 -0
  24. package/dist/knowledge/platform/lint.js +385 -0
  25. package/dist/knowledge/platform/mobile.js +142 -0
  26. package/dist/knowledge/platform/mobileLayoutLint.js +324 -0
  27. package/dist/knowledge/platform/mobileRules.js +231 -0
  28. package/dist/knowledge/platform/mobileSpacing.js +82 -0
  29. package/dist/knowledge/platform/mobileTabs.js +108 -0
  30. package/dist/knowledge/platform/nativeBoxModel.js +44 -0
  31. package/dist/knowledge/platform/profile.js +25 -0
  32. package/dist/knowledge/platform/resolve.js +122 -0
  33. package/dist/knowledge/platform/web.js +39 -0
  34. package/dist/knowledge/policy.js +18 -4
  35. package/dist/knowledge/process.js +510 -0
  36. package/dist/knowledge/reservedTables.js +79 -0
  37. package/dist/knowledge/rowFilter.js +43 -0
  38. package/dist/knowledge/rules.js +423 -37
  39. package/dist/knowledge/schemaGuard.js +11 -0
  40. package/dist/knowledge/styleResolve.js +53 -19
  41. package/dist/knowledge/styleResolveNative.js +527 -0
  42. package/dist/knowledge/themeResolve.js +4 -4
  43. package/dist/knowledge/themeSchema.js +1 -2
  44. package/dist/observability/trace.js +6 -3
  45. package/dist/platform/argumentAliases.js +128 -0
  46. package/dist/platform/bodyFile.js +103 -0
  47. package/dist/platform/capture/actions.js +398 -0
  48. package/dist/platform/capture/browser.js +1 -0
  49. package/dist/platform/capture/capture.js +683 -0
  50. package/dist/platform/capture/console-log.js +122 -0
  51. package/dist/platform/capture/index.js +11 -0
  52. package/dist/platform/capture/readiness.js +103 -0
  53. package/dist/platform/capture/summary.js +77 -0
  54. package/dist/platform/capture/target.js +5 -0
  55. package/dist/platform/capture/targets/studio-mobile.js +15 -0
  56. package/dist/platform/capture/targets/studio-web.js +5 -0
  57. package/dist/platform/diagnostics.js +10 -4
  58. package/dist/platform/domSummary.js +1 -1
  59. package/dist/platform/errors.js +163 -10
  60. package/dist/platform/http.js +29 -8
  61. package/dist/platform/impersonation.js +45 -0
  62. package/dist/platform/jsonArray.js +24 -0
  63. package/dist/platform/responses.js +39 -5
  64. package/dist/platform/schemaReadMemory.js +60 -0
  65. package/dist/platform/secrets.js +19 -1
  66. package/dist/platform/strictArguments.js +68 -12
  67. package/dist/platform/tokenVault.js +187 -4
  68. package/dist/resources/examples/bpm-gorev-kontrol.json +294 -0
  69. package/dist/resources/examples/bpm-iso-dokuman.json +228 -0
  70. package/dist/resources/examples/bpm-satinalma.json +376 -0
  71. package/dist/resources/examples/connect-data-convert-verify.json +107 -0
  72. package/dist/resources/examples/connect-data-dropdown-roles.json +66 -0
  73. package/dist/resources/examples/connect-data-edit-m2m.json +73 -0
  74. package/dist/resources/examples/connect-data-query-params.json +50 -0
  75. package/dist/resources/examples/connect-data-table-entity.json +147 -0
  76. package/dist/resources/examples/connect-data-workflow-microservice.json +65 -0
  77. package/dist/resources/examples/middleware-authenticate.json +1 -1
  78. package/dist/resources/examples/mobile-app-shell-drawer.json +244 -0
  79. package/dist/resources/examples/mobile-app-shell-stack.json +157 -0
  80. package/dist/resources/examples/mobile-app-shell-tab.json +208 -0
  81. package/dist/resources/examples/mobile-form-screen.json +365 -0
  82. package/dist/resources/examples/mobile-list-detail-form.json +703 -0
  83. package/dist/resources/examples/page-detail-profile.json +2 -1
  84. package/dist/resources/examples/page-dynamic-form.json +3 -2
  85. package/dist/resources/examples/page-flex-sections.json +1 -1
  86. package/dist/resources/examples/page-html-embed.json +1 -1
  87. package/dist/resources/examples/workflow-basic.json +8 -2
  88. package/dist/resources/guides/access.md +810 -0
  89. package/dist/resources/guides/bpm.md +691 -0
  90. package/dist/resources/guides/case.md +153 -0
  91. package/dist/resources/guides/component.md +93 -6
  92. package/dist/resources/guides/decision.md +185 -0
  93. package/dist/resources/guides/entity.md +50 -8
  94. package/dist/resources/guides/expressions.md +14 -5
  95. package/dist/resources/guides/functions.md +1 -1
  96. package/dist/resources/guides/getting-started.md +38 -1
  97. package/dist/resources/guides/layout.md +11 -7
  98. package/dist/resources/guides/merge.md +37 -3
  99. package/dist/resources/guides/microservice.md +87 -2
  100. package/dist/resources/guides/middleware.md +68 -4
  101. package/dist/resources/guides/mobile-layout.md +193 -0
  102. package/dist/resources/guides/mobile-publish.md +124 -0
  103. package/dist/resources/guides/mobile.md +549 -0
  104. package/dist/resources/guides/page.md +463 -83
  105. package/dist/resources/guides/theme.md +8 -3
  106. package/dist/resources/guides/translation.md +1 -1
  107. package/dist/resources/guides/workflow.md +58 -1
  108. package/dist/resources/read.js +41 -0
  109. package/dist/resources/rules.js +11 -5
  110. package/dist/resources/schema-fingerprint.json +7 -5
  111. package/dist/resources/schemas/AccessExplanationDto.json +144 -0
  112. package/dist/resources/schemas/AccessMatrixDto.json +170 -0
  113. package/dist/resources/schemas/AccessPermissionDto.json +57 -0
  114. package/dist/resources/schemas/AccessPolicyDto.json +223 -0
  115. package/dist/resources/schemas/AccessResourceDto.json +86 -0
  116. package/dist/resources/schemas/AppSessionDto.json +53 -0
  117. package/dist/resources/schemas/ConnectDataDescribeDto.json +344 -0
  118. package/dist/resources/schemas/ConnectDataRequestModel.json +202 -0
  119. package/dist/resources/schemas/ConnectDataResultDto.json +421 -0
  120. package/dist/resources/schemas/ConnectorCredentialDto.json +70 -0
  121. package/dist/resources/schemas/ConvertConnectDataToMicroserviceCommand.json +37 -0
  122. package/dist/resources/schemas/CreateAccessPolicyCommand.json +209 -0
  123. package/dist/resources/schemas/CreateDecisionCommand.json +221 -0
  124. package/dist/resources/schemas/CreateEntityCommand.json +118 -0
  125. package/dist/resources/schemas/CreateMicroserviceCommand.json +13 -1
  126. package/dist/resources/schemas/CreateProcessDefinitionCommand.json +1337 -0
  127. package/dist/resources/schemas/CreateQueryCommand.json +4 -0
  128. package/dist/resources/schemas/CreateWorkflowCommand.json +146 -1
  129. package/dist/resources/schemas/DataJobDto.json +6 -1
  130. package/dist/resources/schemas/DecisionDto.json +302 -0
  131. package/dist/resources/schemas/DetachConnectDataMicroserviceCommand.json +28 -0
  132. package/dist/resources/schemas/ExplainAccessQuery.json +33 -0
  133. package/dist/resources/schemas/GenerateProcessDocumentCommand.json +116 -0
  134. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +25 -1
  135. package/dist/resources/schemas/MicroserviceDto.json +15 -3
  136. package/dist/resources/schemas/OrganizationProjectDto.json +335 -0
  137. package/dist/resources/schemas/OrganizationSchemaDto.json +196 -0
  138. package/dist/resources/schemas/OrganizationSchemaVersionDto.json +207 -0
  139. package/dist/resources/schemas/PatchProcessDefinitionCommand.json +76 -0
  140. package/dist/resources/schemas/PreviewMicroserviceCommand.json +10 -1
  141. package/dist/resources/schemas/PreviewWorkflowCommand.json +25 -1
  142. package/dist/resources/schemas/ProcessDefinitionDto.json +1480 -0
  143. package/dist/resources/schemas/ProcessSimulationDto.json +96 -0
  144. package/dist/resources/schemas/SaveConnectorCredentialCommand.json +90 -0
  145. package/dist/resources/schemas/SaveProcessEventSourceCommand.json +61 -0
  146. package/dist/resources/schemas/SaveProcessMailSourceCommand.json +89 -0
  147. package/dist/resources/schemas/SignProcessTaskCommand.json +73 -0
  148. package/dist/resources/schemas/SimulateProcessDefinitionQuery.json +92 -0
  149. package/dist/resources/schemas/StartMobileBuildCommand.json +54 -0
  150. package/dist/resources/schemas/StepDto.json +25 -1
  151. package/dist/resources/schemas/UpdateAccessPolicyCommand.json +209 -0
  152. package/dist/resources/schemas/UpdateDecisionCommand.json +216 -0
  153. package/dist/resources/schemas/UpdateEntityCommand.json +118 -0
  154. package/dist/resources/schemas/UpdateMicroserviceCommand.json +13 -1
  155. package/dist/resources/schemas/UpdateProcessDefinitionCommand.json +1357 -0
  156. package/dist/resources/schemas/UpdateQueryCommand.json +4 -0
  157. package/dist/resources/schemas/UpdateViewCommand.json +0 -2
  158. package/dist/resources/schemas/UpdateWorkflowCommand.json +146 -1
  159. package/dist/resources/schemas/UpsertMobileStoreSettingsCommand.json +274 -0
  160. package/dist/resources/schemas/ValidateProcessDefinitionQuery.json +1339 -0
  161. package/dist/server.js +31 -1
  162. package/dist/tools/access.js +369 -2
  163. package/dist/tools/accessOperations.js +135 -0
  164. package/dist/tools/accessPolicies.js +170 -0
  165. package/dist/tools/accessRuntime.js +203 -0
  166. package/dist/tools/applications.js +60 -3
  167. package/dist/tools/bpm.js +343 -0
  168. package/dist/tools/bpmGovernance.js +266 -0
  169. package/dist/tools/bpmOperations.js +230 -0
  170. package/dist/tools/bpmReporting.js +154 -0
  171. package/dist/tools/bpmRouting.js +150 -0
  172. package/dist/tools/bpmRuntime.js +396 -0
  173. package/dist/tools/case.js +178 -0
  174. package/dist/tools/caseRuntime.js +89 -0
  175. package/dist/tools/common.js +10 -1
  176. package/dist/tools/components.js +118 -14
  177. package/dist/tools/connectData.js +450 -0
  178. package/dist/tools/connectors.js +193 -0
  179. package/dist/tools/datasources.js +3 -3
  180. package/dist/tools/decisions.js +206 -0
  181. package/dist/tools/dependencies.js +1 -1
  182. package/dist/tools/deployments.js +38 -3
  183. package/dist/tools/designSchemaPatch.js +34 -9
  184. package/dist/tools/dtos.js +36 -5
  185. package/dist/tools/entities.js +79 -12
  186. package/dist/tools/externalComponents.js +26 -5
  187. package/dist/tools/flows.js +119 -17
  188. package/dist/tools/folders.js +1 -1
  189. package/dist/tools/imports.js +5 -2
  190. package/dist/tools/layouts.js +50 -17
  191. package/dist/tools/merges.js +2 -2
  192. package/dist/tools/microservices.js +77 -25
  193. package/dist/tools/mobile.js +280 -0
  194. package/dist/tools/mobileStore.js +556 -0
  195. package/dist/tools/modules.js +48 -0
  196. package/dist/tools/organizationStructure.js +137 -0
  197. package/dist/tools/pages.js +465 -98
  198. package/dist/tools/platformInfo.js +28 -8
  199. package/dist/tools/profileWrite.js +92 -0
  200. package/dist/tools/projects.js +71 -9
  201. package/dist/tools/rows.js +69 -13
  202. package/dist/tools/schemaOps.js +61 -1
  203. package/dist/tools/screenWrite.js +36 -7
  204. package/dist/tools/screens.js +95 -5
  205. package/dist/tools/templates.js +26 -1
  206. package/dist/tools/translations.js +1 -1
  207. package/dist/tools/variables.js +11 -5
  208. package/dist/tools/workflows.js +10 -5
  209. package/package.json +2 -2
  210. package/dist/platform/screenshot.js +0 -1162
@@ -0,0 +1,556 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { basename, resolve } from 'node:path';
3
+ import { z } from 'zod';
4
+ import { destructive, environmentParam, guid, projectParam, readOnly, updates, writes } from './common.js';
5
+ import { unwrap } from './screens.js';
6
+ import { PlatformApiError } from '../platform/errors.js';
7
+ import { handle } from '../platform/responses.js';
8
+ import { attachFindings, enforcePolicy, evaluatePolicy } from '../knowledge/policy.js';
9
+ import { policyRules } from '../knowledge/rules.js';
10
+ import { ASSET_KINDS, BUILD_ASSET_KINDS, DECLARATION_KEYS, DEVICE_CLASSES, MAX_ASSET_BYTES, ORIENTATIONS, RELEASE_STATUSES, SCREENSHOT_CAPTURE_DEVICES, SPLASH_RESIZE_MODES, USER_INTERFACE_STYLES, assetSlotProblem, changedStoreFields, mergeStoreSettings, permissionLaunchShellWarnings, permissionStoreBuildWarnings } from '../knowledge/mobileStore.js';
11
+ /**
12
+ * Mobil mağaza yayınının ajan yolu (`plans/mobil-magaza-yayini` g-11, `plans/mobil-yayin-mcp` g-01):
13
+ * gizli olmayan mağaza ayarı, mağaza görselleri, beyanlar ve derleme işi. Gizli bilgi (Apple/Google
14
+ * hesabı, sertifika, keystore, profil, demo parolası) ve runner kaydı bilinçli olarak YOK — Studio ›
15
+ * Yayın'dan insan yapar (`docs/adr/0002-mobil-yayin-gorsel-ve-beyan.md`). Uçlar Core DB'dedir (ortam
16
+ * kavramı yok); derlenen uygulama her zaman production ortamını okur.
17
+ */
18
+ const applicationId = guid.describe("Mobil uygulamanın id'si (application_list / mobile_app_create çıktısı). Slug verilirse DEFAULT_PROJECT içinde çözülür");
19
+ const buildId = guid.describe("Derleme işinin id'si (mobile_build_start ya da mobile_build_status { applicationId } çıktısı)");
20
+ const mobilePlatform = z.enum(['ios', 'android']);
21
+ const buildTarget = z.enum(['build', 'testflight', 'internal'])
22
+ .describe("build: yalnız derler (imzasız/imzalı çıktı, mağazaya gitmez) · testflight: iOS'ta TestFlight'a yükler · internal: Android'de Play iç test kanalına yükler. Varsayılan build");
23
+ const listingSchema = z.object({
24
+ title: z.string().optional().describe('≤ 30'),
25
+ subtitle: z.string().optional().describe('≤ 30 (iOS)'),
26
+ shortDescription: z.string().optional().describe('≤ 80 (Play)'),
27
+ description: z.string().optional().describe('≤ 4000'),
28
+ keywords: z.array(z.string()).optional().describe('virgülle birleşik ≤ 100 (iOS)'),
29
+ promoText: z.string().optional().describe('≤ 170 (iOS)'),
30
+ releaseNotes: z.string().optional().describe('≤ 4000')
31
+ }).strict();
32
+ const settingsPatch = z.object({
33
+ displayName: z.string().optional().describe('Cihazda görünen ad, ≤ 30'),
34
+ bundleIdentifier: z.string().optional().describe('iOS kimliği (com.firma.uygulama); ilk derlemeden sonra KİLİTLİ'),
35
+ androidPackage: z.string().optional().describe('Android paket adı; segment Java anahtar sözcüğü olamaz; ilk derlemeden sonra KİLİTLİ'),
36
+ scheme: z.string().optional().describe("Derin bağlantı şeması, ^[a-z][a-z0-9+.-]{2,}$, 'zeyrek' yasak; ilk derlemeden sonra KİLİTLİ"),
37
+ version: z.string().optional().describe('MAJOR.MINOR.PATCH (öndeki sıfır yok)'),
38
+ iosBuildNumber: z.number().int().nullable().optional().describe('SON ayrılan iOS derleme numarası (sıradaki +1). Yalnız mevcut mağaza uygulaması taşınırken ve ilk derlemeden ÖNCE yazılır; null = değiştirme'),
39
+ androidVersionCode: z.number().int().nullable().optional().describe('SON ayrılan Android versionCode; iosBuildNumber ile aynı kural'),
40
+ orientation: z.enum(ORIENTATIONS).optional(),
41
+ supportsTablet: z.boolean().optional(),
42
+ userInterfaceStyle: z.enum(USER_INTERFACE_STYLES).optional(),
43
+ adaptiveIconBackground: z.string().optional().describe('#RRGGBB'),
44
+ splashBackgroundColor: z.string().optional().describe('#RRGGBB'),
45
+ splashResizeMode: z.enum(SPLASH_RESIZE_MODES).optional(),
46
+ permissions: z.record(z.string().nullable()).optional().describe('Modül → izin açıklaması (1–500); anahtar ^[a-z][A-Za-z0-9]*$ (ör. camera)'),
47
+ permissionModes: z.record(z.string()).optional().describe("İzin anahtarı → mağaza derlemesindeki kip: auto (uygulama kullanıyorsa dahil; varsayılan) | on (her zaman dahil) | off (dahil etme — o izni kullanan ekran 'unavailable' alır, production kullanıyorsa ön kontrol P-12 derlemeyi durdurur). Yalnız çıkarılabilir izinler yazılır (backend 422); anahtarlar ve bugünkü durum: mobile_store_get { detail: ['permissions'] }. Yama kayıttaki kiplerin üstüne birleşir"),
48
+ launchPermissions: z.array(z.string()).optional().describe("Uygulama açılınca bir kez sorulacak izinlerin anahtarları (mağaza sürümünde; yalnız hiç sorulmamışsa sorulur; soruş sırası dizinin değil kataloğun sırasıdır). Liste BÜTÜN olarak yazılır: verilen dizi kayıttakinin yerine geçer, [] hepsini kaldırır. Açılışta sormak bir kullanımdır: izin derlemeye girer ve bir sonraki mağaza derlemesiyle etkili olur. Kipi off olan izin listeye yazılamaz (derlemenin taşımadığı izin sorulamaz): önce permissionModes.<key> auto ya da on yapılır. Anahtarlar: mobile_store_get { detail: ['permissions'] }"),
49
+ listing: z.record(listingSchema).optional().describe("Dil kodu ('tr', 'en-US') → mağaza metni; verilen dil alan alan birleşir. Dil SİLİNMEZ (alanları '' yapılır; dili kaldırmak Studio › Yayın)"),
50
+ urls: z.object({
51
+ support: z.string().optional(),
52
+ marketing: z.string().optional(),
53
+ privacyPolicy: z.string().optional().describe('Yüklemede zorunlu'),
54
+ privacyChoices: z.string().optional()
55
+ }).strict().optional().describe('Boş ya da mutlak https:// adres'),
56
+ apple: z.object({
57
+ ascAppId: z.string().optional(),
58
+ categories: z.object({ primary: z.string().optional(), secondary: z.string().optional() }).strict().optional(),
59
+ advisory: z.record(z.string()).optional(),
60
+ copyright: z.string().optional().describe('≤ 200; TestFlight yüklemesinde zorunlu')
61
+ }).strict().optional(),
62
+ play: z.object({
63
+ developerId: z.string().optional(),
64
+ contactEmail: z.string().optional().describe('Play yüklemesinde zorunlu'),
65
+ releaseStatus: z.enum(RELEASE_STATUSES).optional().describe('Play uygulaması taslakken yalnız draft kabul edilir')
66
+ }).strict().optional(),
67
+ review: z.object({
68
+ firstName: z.string().optional(),
69
+ lastName: z.string().optional(),
70
+ phone: z.string().optional(),
71
+ email: z.string().optional(),
72
+ notes: z.string().optional()
73
+ }).strict().optional().describe('App Review iletişim bilgisi. Demo hesabı parolası burada YOK — gizli bilgidir, Studio › Yayın › Hesaplar')
74
+ }).strict();
75
+ /** Backend bir çağrıda en çok bu kadar satır döner (`MobileBuildService.LogPageLimit`); dolu sayfa = devamı var */
76
+ const LOG_PAGE_LIMIT = 2000;
77
+ const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
78
+ // Bölüm ve alan adları `secretKeyPattern`'e (credential|secret|token…) takılmamalı: takılan anahtarın
79
+ // değeri yanıtta `***` olur (GW-001). Bu yüzden kimlik bilgisi bölümü `signing`, olay türü `subject`.
80
+ const DETAIL_SECTIONS = ['assets', 'signing', 'runners', 'audit', 'permissions'];
81
+ /** Son kaç denetim kaydı döner; Studio aynı listeyi sayfalamadan gösterir, ajanın son olaylar yeter */
82
+ const AUDIT_LIMIT = 20;
83
+ /** İzin başına dönen kullanım yeri; toplam `usageCount`'tadır (bir izni 200 sayfa kullanabilir) */
84
+ const PERMISSION_USAGE_LIMIT = 20;
85
+ const slotOf = (asset) => [asset.kind, asset.locale, asset.deviceClass].filter(Boolean).join('/');
86
+ const assetSummary = (asset) => ({
87
+ id: asset.id,
88
+ slot: slotOf(asset),
89
+ sortOrder: asset.sortOrder,
90
+ contentType: asset.contentType,
91
+ width: asset.width,
92
+ height: asset.height,
93
+ size: asset.size,
94
+ createdAt: asset.createdAt
95
+ });
96
+ /**
97
+ * Görsel listesi varsayılan olarak yuva başına sayıya indirgenir; `assets` ayrıntısı istenirse id'li
98
+ * liste döner (sıralama ve silme id ister). İçerik (bayt, url) hiç dönmez.
99
+ */
100
+ function storeSummary(store, detailAssets = false) {
101
+ const assets = Array.isArray(store.assets) ? store.assets.filter(isObject) : [];
102
+ const slots = new Map();
103
+ for (const asset of assets)
104
+ slots.set(slotOf(asset), (slots.get(slotOf(asset)) ?? 0) + 1);
105
+ const declarations = isObject(store.declarations) ? store.declarations : {};
106
+ return {
107
+ applicationId: store.applicationId,
108
+ persisted: store.persisted,
109
+ settings: store.settings,
110
+ lockedFields: store.lockedFields,
111
+ updatedAt: store.updatedAt,
112
+ updatedBy: store.updatedBy,
113
+ assets: detailAssets ? assets.map(assetSummary) : Object.fromEntries(slots),
114
+ declarations: Object.fromEntries(Object.entries(declarations).map(([key, value]) => [key, isObject(value) && value.checked === true]))
115
+ };
116
+ }
117
+ const list = (payload) => {
118
+ const data = isObject(payload) && 'data' in payload ? payload.data : payload;
119
+ return Array.isArray(data) ? data.filter(isObject) : [];
120
+ };
121
+ const secretSummary = (secret) => ({ id: secret.id, kind: secret.kind, maskedId: secret.maskedId, meta: secret.meta, createdAt: secret.createdAt });
122
+ /**
123
+ * Yayın hazırlığının salt okunur bölümleri. Gizli değer bu uçların hiçbirinde yoktur (backend yalnız
124
+ * maskeli id + meta döner, `MobileStoreSecretDtos.cs`); her bölüm ayrı okunur ve düşen bölüm çağrıyı
125
+ * düşürmez (uç hatası, 404/5xx). Bölümler taban okumayla aynı `mobile.store.read` iznini ister; izni olmayan
126
+ * kullanıcı zaten taban GET'te düşer.
127
+ */
128
+ async function readDetail(platform, appId, section, scope) {
129
+ try {
130
+ if (section === 'permissions') {
131
+ const environment = platform.resolveEnvironment(scope.environment);
132
+ const view = unwrap(await platform.org('GET', `/mobile-store/${appId}/permissions`, { query: { environment } }));
133
+ // Cihaz sayıları tenant'tadır ve yoklukları görünümü düşürmez (push kapalı, proje çözülemedi).
134
+ const devices = await platform.tenant('GET', scope.project, `/push/devices/${appId}/permission-stats`, { environment })
135
+ .then(unwrap, (error) => {
136
+ if (!(error instanceof PlatformApiError))
137
+ throw error;
138
+ return error;
139
+ });
140
+ const counts = !(devices instanceof PlatformApiError) && isObject(devices.byPermission) ? devices.byPermission : {};
141
+ const entries = Array.isArray(view.permissions) ? view.permissions.filter(isObject) : [];
142
+ const warnings = [...permissionStoreBuildWarnings(entries, String(view.environment)), ...permissionLaunchShellWarnings(entries, view.shell)];
143
+ return {
144
+ permissions: {
145
+ catalogVersion: view.catalogVersion,
146
+ environment: view.environment,
147
+ usageReadable: view.usageReadable,
148
+ shell: view.shell,
149
+ ...(devices instanceof PlatformApiError ? {} : { devicesTotal: devices.total }),
150
+ permissions: entries.map((entry) => {
151
+ const usage = Array.isArray(entry.usage) ? entry.usage : [];
152
+ const key = String(entry.key);
153
+ return { ...entry, usageCount: usage.length, usage: usage.slice(0, PERMISSION_USAGE_LIMIT), ...(isObject(counts[key]) ? { devices: counts[key] } : {}) };
154
+ })
155
+ },
156
+ ...(warnings.length > 0 ? { permissionWarnings: warnings } : {}),
157
+ ...(devices instanceof PlatformApiError ? { permissionDevicesError: devices.message } : {})
158
+ };
159
+ }
160
+ if (section === 'signing') {
161
+ const [credentials, accounts] = await Promise.all([
162
+ platform.org('GET', `/mobile-store/${appId}/credentials`),
163
+ platform.org('GET', '/mobile-store/accounts')
164
+ ]);
165
+ return {
166
+ signing: {
167
+ app: list(credentials).map(credential => ({ ...secretSummary(credential), exportable: credential.exportable })),
168
+ accounts: list(accounts).map(account => ({
169
+ ...secretSummary(account),
170
+ label: account.label,
171
+ lastUsedAt: account.lastUsedAt,
172
+ certificates: Array.isArray(account.certificates) ? account.certificates.filter(isObject).map(secretSummary) : []
173
+ }))
174
+ }
175
+ };
176
+ }
177
+ if (section === 'runners') {
178
+ const runners = unwrap(await platform.org('GET', '/mobile-runners'));
179
+ return {
180
+ runners: {
181
+ sharedOnline: runners.sharedOnline,
182
+ dedicated: (Array.isArray(runners.runners) ? runners.runners.filter(isObject) : []).map(runner => ({
183
+ id: runner.id, name: runner.name, shared: runner.shared, platforms: runner.platforms, online: runner.online, registered: runner.registered, lastSeenAt: runner.lastSeenAt
184
+ }))
185
+ }
186
+ };
187
+ }
188
+ const audits = list(await platform.org('GET', '/mobile-store/secret-audits', { query: { applicationId: appId } }));
189
+ return { audit: audits.slice(0, AUDIT_LIMIT).map(entry => ({ kind: entry.kind, subject: entry.secretKind, maskedId: entry.maskedId, jobId: entry.jobId, actor: entry.actor, occurredAt: entry.occurredAt })) };
190
+ }
191
+ catch (error) {
192
+ if (!(error instanceof PlatformApiError))
193
+ throw error;
194
+ return { [`${section}Error`]: error.message };
195
+ }
196
+ }
197
+ /**
198
+ * Ön kontrol P-09 dedicated YA DA shared çevrimiçiyse geçer (`MobileStorePreflightService.cs` P-09) ve
199
+ * istenen kapsama bakmaz; kapsam verilip o kapsamda runner yoksa iş kuyrukta `runner_lost` ile düşer.
200
+ */
201
+ async function runnerScopeProblem(platform, mobile, scope) {
202
+ const runners = unwrap(await platform.org('GET', '/mobile-runners'));
203
+ const shared = isObject(runners.sharedOnline) && runners.sharedOnline[mobile] === true;
204
+ const dedicated = (Array.isArray(runners.runners) ? runners.runners.filter(isObject) : [])
205
+ .some(runner => runner.shared !== true && runner.online === true && Array.isArray(runner.platforms) && runner.platforms.includes(mobile));
206
+ const online = scope === 'shared' ? shared : dedicated;
207
+ if (online)
208
+ return undefined;
209
+ const other = scope === 'shared' ? dedicated : shared;
210
+ return `runnerScope '${scope}' için çevrimiçi ${mobile} runner'ı yok — iş hiç alınmaz ve derleme numarası yanardı. ${other ? `'${scope === 'shared' ? 'dedicated' : 'shared'}' kapsamında çevrimiçi runner var: o kapsamı verin ya da runnerScope'u bırakın (backend seçer).` : 'Hiçbir kapsamda çevrimiçi runner yok: runner Mac\'te zeyrek-mobile-runner run.'}`;
211
+ }
212
+ const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47];
213
+ const JPEG_SIGNATURE = [0xff, 0xd8, 0xff];
214
+ const startsWith = (bytes, signature) => signature.every((byte, index) => bytes[index] === byte);
215
+ /**
216
+ * Görsel baytını `path` ya da base64 `content`'ten okur ve biçimi imzadan çıkarır. Backend yalnız PNG/JPEG
217
+ * kabul eder, derleme görseli yalnız PNG (`MobileStoreAssetValidator`); ölçü ve alfa denetimi backend'de.
218
+ */
219
+ async function readAsset(kind, path, content, filename) {
220
+ if ((path === undefined) === (content === undefined)) {
221
+ throw new PlatformApiError("mobile_asset_create: `path` ya da `content`'ten TAM OLARAK biri verilir — `path` gateway'in okuyabildiği yerel dosya (page_screenshot outPath'i), `content` base64 içerik (filename ile).", 400);
222
+ }
223
+ let bytes;
224
+ if (path !== undefined) {
225
+ try {
226
+ bytes = await readFile(resolve(path));
227
+ }
228
+ catch (error) {
229
+ throw new PlatformApiError(`mobile_asset_create: dosya okunamadı — ${resolve(path)} (${error instanceof Error ? error.message : String(error)}). Yol gateway sürecinin çalıştığı makineye göre çözülür.`, 400);
230
+ }
231
+ }
232
+ else {
233
+ bytes = Buffer.from(content, 'base64');
234
+ }
235
+ if (bytes.byteLength === 0 || bytes.byteLength > MAX_ASSET_BYTES) {
236
+ throw new PlatformApiError(`mobile_asset_create: görsel ${bytes.byteLength} B — 1 B ile ${MAX_ASSET_BYTES} B (10 MB) arasında olmalı. Hiçbir şey yüklenmedi.`, 400);
237
+ }
238
+ const png = startsWith(bytes, PNG_SIGNATURE);
239
+ const jpeg = startsWith(bytes, JPEG_SIGNATURE);
240
+ const buildKind = BUILD_ASSET_KINDS.includes(kind);
241
+ if (!png && !(jpeg && !buildKind)) {
242
+ throw new PlatformApiError(`mobile_asset_create: '${kind}' ${buildKind ? 'yalnız PNG' : 'PNG ya da JPEG'} kabul eder, dosya ${jpeg ? 'JPEG' : 'ikisi de değil'}. Hiçbir şey yüklenmedi.`, 400);
243
+ }
244
+ const extension = png ? 'png' : 'jpg';
245
+ return { bytes, name: filename ?? (path !== undefined ? basename(path) : `${kind}.${extension}`), contentType: png ? 'image/png' : 'image/jpeg' };
246
+ }
247
+ const captureTable = DEVICE_CLASSES.map(deviceClass => {
248
+ const device = SCREENSHOT_CAPTURE_DEVICES[deviceClass];
249
+ return `${deviceClass} → device { width: ${device.width}, height: ${device.height}, scale: ${device.scale} } (${device.width * device.scale}×${device.height * device.scale})`;
250
+ }).join(' · ');
251
+ /**
252
+ * Ayar yaması: kaydı okur, yamayı birleştirir, MOB-022'yi uygular, tam gövdeyi yazar (g-11). Değişiklik
253
+ * yoksa yazmaz. `declarations` / `registerAppleBundleId` ile aynı çağrıda ilk adımdır — bundle ID kaydı
254
+ * yeni yazılan `bundleIdentifier`'ı kullanır.
255
+ */
256
+ async function writeSettings(platform, config, appId, requested, policyOverride) {
257
+ const settings = Object.fromEntries(Object.entries(requested).filter(([, value]) => value !== null));
258
+ const current = unwrap(await platform.org('GET', `/mobile-store/${appId}`));
259
+ const before = isObject(current.settings) ? current.settings : {};
260
+ const changed = changedStoreFields(before, settings);
261
+ if (changed.length === 0)
262
+ return { ...storeSummary(current), changed, note: 'Yama kayıttaki değerlerle aynı — hiçbir ayar yazılmadı.' };
263
+ const locked = changed.filter(field => Array.isArray(current.lockedFields) && current.lockedFields.includes(field));
264
+ if (locked.length > 0) {
265
+ throw new PlatformApiError(`mobile_store_update: ${locked.join(', ')} ilk derlemeden sonra KİLİTLİ — mağaza bu kimliği/numarayı uygulamanın kendisi sayar, değişen kimlik yeni bir mağaza uygulaması demektir. Hiçbir alan yazılmadı. Kilitli alanı yamadan çıkarın; farklı kimlik gerçekten gerekiyorsa yeni bir mobil uygulama açın (mobile_app_create).`, 422, 'MobileStoreFieldLockedException');
266
+ }
267
+ const body = mergeStoreSettings(before, settings);
268
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'mobileStore', body, { operation: 'update' }, policyOverride));
269
+ const saved = unwrap(await platform.org('PUT', `/mobile-store/${appId}`, { body: { ...body, expectedUpdatedAt: current.updatedAt ?? null } }));
270
+ return attachFindings({ ...storeSummary(saved), changed }, findings);
271
+ }
272
+ const seconds = (from, to) => {
273
+ const start = typeof from === 'string' ? Date.parse(from) : NaN;
274
+ const end = typeof to === 'string' ? Date.parse(to) : NaN;
275
+ return Number.isFinite(start) && Number.isFinite(end) ? Math.max(0, Math.round((end - start) / 1000)) : undefined;
276
+ };
277
+ /** Aşama süresi: bir aşama bir sonrakinin başladığı anda, sonuncusu iş bittiğinde (sürüyorsa şimdi) biter. */
278
+ function buildSummary(job, now) {
279
+ const stages = Array.isArray(job.stages) ? job.stages.filter(isObject) : [];
280
+ const active = job.status === 'queued' || job.status === 'running';
281
+ const end = typeof job.finishedAt === 'string' ? job.finishedAt : now;
282
+ return {
283
+ id: job.id,
284
+ applicationId: job.applicationId,
285
+ platform: job.platform,
286
+ target: job.target,
287
+ status: job.status,
288
+ stage: job.stage,
289
+ stages: stages.map((entry, index) => ({
290
+ stage: entry.stage,
291
+ startedAt: entry.startedAt,
292
+ seconds: seconds(entry.startedAt, stages[index + 1]?.startedAt ?? end)
293
+ })),
294
+ elapsedSeconds: seconds(job.startedAt ?? job.createdAt, end),
295
+ version: job.version,
296
+ buildNumber: job.buildNumber,
297
+ runnerScope: job.runnerScope,
298
+ cancelRequested: job.cancelRequested,
299
+ awaitingStore: job.status === 'running' && job.stage === 'store_processing',
300
+ error: job.error,
301
+ storeResult: job.storeResult,
302
+ hasArtifact: job.hasArtifact,
303
+ createdAt: job.createdAt,
304
+ finishedAt: job.finishedAt,
305
+ ...(active ? { next: 'İş sürüyor: mobile_build_status ile yeniden okuyun (aşamalar dakikalar sürer), günlük için mobile_build_log { after }' } : {}),
306
+ ...(isObject(job.error) ? { next: `Hata kodu → öneri: platform_guide('mobile-publish', 'hata-kodlari'); ayrıntı mobile_build_log` } : {})
307
+ };
308
+ }
309
+ export function registerMobileStoreTools(server, platform) {
310
+ const config = platform.config;
311
+ server.registerTool('mobile_store_get', {
312
+ title: 'Mobil mağaza ayarını oku',
313
+ description: "Mobil uygulamanın gizli olmayan mağaza ayarını döner (GET mobile-store/{appId}): kimlik (bundleIdentifier, androidPackage, scheme), sürüm ve sayaçlar, görünüm, izin metinleri, kipleri ve açılışta sorulanlar (permissions, permissionModes, launchPermissions), listing (dil başına mağaza metni), urls, apple, play, review; lockedFields (ilk derlemeden sonra değişmeyen alanlar), görsel yuvası başına sayı (assets: 'screenshot/tr/iphone69': 3) ve beyanlar (declarations: anahtar → işaretli mi). Kayıt hiç yazılmamışsa persisted:false ve VARSAYILANLAR döner (kaydedilmez). manifest:true derlemeye gidecek manifesti de ekler (manifest-preview; sayaçlar SIRADAKİ numara); üretilemiyorsa manifestError alan adıyla döner. detail ayrıntı bölümleri ekler: 'assets' görselleri id'li liste olarak döner (mobile_asset_update / mobile_asset_delete id ister), 'signing' uygulamanın kimlik bilgilerini (signing.app: iOS profili, Android keystore, harita anahtarı, demo hesabı) ve organizasyonun Apple/Google hesaplarını + sertifikalarını (signing.accounts), 'runners' çevrimiçi runner'ları, 'audit' son 20 gizli bilgi olayını (subject: hangi tür, kim, ne zaman, hangi iş), 'permissions' izin görünümünü (permissions.permissions[]: uygulamanın isteyebileceği her izin için mode, text/defaultText, usage — izni kullanan sayfa·bileşen ya da akış adımı (setter ile yazılan tetikleyici prop dahil), ilk 20 yer + usageCount, çağrının environment'ından; usageReadable false ise o ortam okunamamıştır ve boş usage 'kullanılmıyor' demek değildir —, build — mağaza derlemesi taşıyor mu ve neden; HER ZAMAN production'dan: build.usageCount production'daki kullanım, build.staticUsageCount onun yazılı (ifadesiz) kısmı —, devices — aktif cihazların izin durumu sayıları; missingFromStoreBuilds — o izni taşımayan son mağaza derlemeleri; shell.carriesPermissions false ise kipler ve kabuğun henüz taşımadığı izinler bir sonraki kabuk sürümüyle etkili olur; shell.carriesLaunchPermissions false ise açılışta sorulan izinler (askOnLaunch) o kabukta sorulmaz — permissionWarnings söyler). Gizli DEĞER hiçbir bölümde yoktur — yalnız tür, maskeli id ve meta (ekip, son kullanma); bir bölüm okunamazsa (uç hatası) <bölüm>Error döner, çağrı düşmez. Gizli bilgiyi EKLEMEK/DEĞİŞTİRMEK MCP'den yapılmaz — Studio › Yayın › Hesaplar.",
314
+ inputSchema: {
315
+ applicationId,
316
+ manifest: z.boolean().optional().describe('true: derleme manifestinin önizlemesini de ekler'),
317
+ detail: z.array(z.enum(DETAIL_SECTIONS)).optional().describe("Ayrıntı bölümleri: 'assets' (id'li görsel listesi), 'signing' (kimlik bilgisi ve hesap durumu, değersiz), 'runners', 'audit', 'permissions' (izin görünümü; kullanılan bir izin mağazadaki son derlemede yoksa ya da açılışta sorulan izin varken yayındaki kabuk açılışta sormuyorsa permissionWarnings)"),
318
+ project: projectParam.describe("Yalnız detail: ['permissions'] için: cihaz sayılarının okunacağı proje; boşsa DEFAULT_PROJECT"),
319
+ environment: environmentParam.describe("Yalnız detail: ['permissions'] için: KULLANIMIN okunacağı ortam (boşsa DEFAULT_ENVIRONMENT). build her zaman production'dan hesaplanır; yalnız okumadır")
320
+ },
321
+ annotations: readOnly
322
+ }, handle(config, async ({ applicationId: appId, manifest, detail, project, environment }) => {
323
+ const sections = new Set(detail ?? []);
324
+ const store = storeSummary(unwrap(await platform.org('GET', `/mobile-store/${appId}`)), sections.has('assets'));
325
+ const details = await Promise.all(DETAIL_SECTIONS.filter(section => section !== 'assets' && sections.has(section)).map(section => readDetail(platform, appId, section, { ...(project ? { project } : {}), ...(environment ? { environment } : {}) })));
326
+ const result = Object.assign(store, ...details);
327
+ if (!manifest)
328
+ return result;
329
+ try {
330
+ return { ...result, manifest: unwrap(await platform.org('GET', `/mobile-store/${appId}/manifest-preview`)) };
331
+ }
332
+ catch (error) {
333
+ if (!(error instanceof PlatformApiError))
334
+ throw error;
335
+ return { ...result, manifestError: error.message };
336
+ }
337
+ }));
338
+ server.registerTool('mobile_store_update', {
339
+ title: 'Mobil mağaza ayarını güncelle',
340
+ description: "Mobil uygulamanın mağaza kaydını yazar; üç parçadan en az biri verilir ve sırayla uygulanır: settings → declarations → registerAppleBundleId. (1) settings gizli olmayan ayarın YAMASIDIR (PUT mobile-store/{appId}): gateway kaydı okur, verilen alanları üstüne birleştirir (iç nesneler alan alan, listing dil dil; dizi ve skaler yerine geçer) ve tam gövdeyi yazar — backend PUT tam satırı yazdığı için yamasız gönderilen gövde öteki alanları varsayılana döndürürdü. Sayaçlar (iosBuildNumber, androidVersionCode) yalnız yamada varsa gider. Yazmadan önce MOB-022: biçimsiz alan (paket adı, şema, semver, renk, listing sınırları, https adres, e-posta) backend'in 422'siyle aynı gerekçeyle isteği DURDURUR (policyOverride:true ile gönderilir, backend yine reddeder); mağazaya yükleme için boş kalan alanlar uyarıdır, kayıt taslak olarak yazılır. Tuzak: bundleIdentifier, androidPackage, scheme ve sayaçlar İLK DERLEMEDEN SONRA KİLİTLİDİR (lockedFields) — değişimi 422 MobileStoreFieldLockedException. Eşzamanlılık: PUT okunan kaydın updatedAt değerini expectedUpdatedAt olarak taşır; arada Studio'dan ya da başka bir çağrıdan yazılmışsa 409 MobileStoreSettingsStaleException döner ve hiçbir alan yazılmaz — aynı yamayla aracı yeniden çağırın. (2) declarations mağaza beyanlarını işaretler (PUT mobile-store/{appId}/declarations): appPrivacy (iOS App Privacy), dataSafety, contentRating, targetAudience, appAccess (Play Console). BEYAN KULLANICININDIR: true, kullanıcının o formu App Store Connect / Play Console'da doldurduğunu onayladığı anlamına gelir ve ön kontrol (P-10) bunu doğru kabul eder — kullanıcı açıkça onaylamadan true YAZMAYIN; false işareti kaldırır. Verilmeyen anahtar değişmez. (3) registerAppleBundleId:true kayıttaki bundleIdentifier'ı App Store Connect'e kaydeder (POST mobile-store/{appId}/ios/bundle-id; ön kontrol P-04 asc_bundle_id_missing'in düzeltmesi). Organizasyonda Apple hesabı ve mobile.secrets.write izni ister; kayıtlıysa yeniden oluşturmaz (bundleId.created:false). Kayıt hiç yazılmamışsa (persisted:false) REDDEDİLİR — kimlik otomatik varsayılandır ve Apple'daki kayıt silinemez; aynı çağrıda settings.bundleIdentifier verin. İşlem yoktur: sonraki adım düşerse önceki adımlar yazılmış kalır ve yanıt <adım>Error + note taşır. YAPMAZ: görsel yüklemez (mobile_asset_create), gizli bilgi (Apple/Google hesabı, sertifika, keystore, demo parolası) yazmaz — Studio › Yayın › Hesaplar. Yanıt: güncel kayıt + changed (değişen ayar yolları) + declared (gönderilen beyanlar) + bundleId.",
341
+ inputSchema: {
342
+ applicationId,
343
+ settings: settingsPatch.optional().describe("Değişecek alanlar; ör. { bundleIdentifier: 'com.firma.app', listing: { tr: { title: 'Uygulama' } } }"),
344
+ declarations: z.object(Object.fromEntries(DECLARATION_KEYS.map(key => [key, z.boolean().optional()]))).strict().optional()
345
+ .describe('Beyan anahtarı → işaretli mi. YALNIZ kullanıcının onayıyla true; ör. { appPrivacy: true }'),
346
+ registerAppleBundleId: z.literal(true).optional().describe("true: bundleIdentifier'ı App Store Connect'e kaydeder (P-04)"),
347
+ policyOverride: z.boolean().optional().describe('MOB-022 block bulgusunu uyarıya indirir; backend kuralı yine uygular')
348
+ },
349
+ annotations: updates
350
+ }, handle(config, async ({ applicationId: appId, settings: requested, declarations, registerAppleBundleId, policyOverride }) => {
351
+ const declared = Object.fromEntries(Object.entries(declarations ?? {}).filter(([, value]) => typeof value === 'boolean'));
352
+ if (requested === undefined && Object.keys(declared).length === 0 && !registerAppleBundleId) {
353
+ throw new PlatformApiError('mobile_store_update: settings, declarations ya da registerAppleBundleId\'den en az biri verilir — hiçbir şey yazılmadı. Ayar için settings yaması, beyan için declarations ({ appPrivacy: true }), Apple kaydı için registerAppleBundleId: true.', 400);
354
+ }
355
+ // Kayıt hiç yazılmamışsa backend varsayılan kimliği (`MobileStoreSettings.CreateDefault`) Apple'a
356
+ // kaydeder (`MobileStoreSecretService.RegisterBundleIdAsync`) — API'den silinemeyen dış kayıt.
357
+ if (registerAppleBundleId && requested?.bundleIdentifier === undefined) {
358
+ const before = unwrap(await platform.org('GET', `/mobile-store/${appId}`));
359
+ if (before.persisted !== true) {
360
+ throw new PlatformApiError(`mobile_store_update: registerAppleBundleId reddedildi — mağaza kaydı hiç yazılmamış, kayıttaki bundleIdentifier ('${String(isObject(before.settings) ? before.settings.bundleIdentifier : '')}') otomatik VARSAYILANDIR ve Apple'daki kayıt API'den silinemez. Kimliği önce onaylayın: aynı çağrıya settings: { bundleIdentifier } ekleyin ya da önce settings yazın. Hiçbir şey yazılmadı.`, 400);
361
+ }
362
+ }
363
+ // Adımlar ayrı uçlardır, işlem yok: sonraki adım düşerse yazılmış olanlar yanıtta kalır, hata
364
+ // `<adım>Error` ile döner (41 §9 — "hiçbir şey yazılmadı" sanılmasın).
365
+ const written = [];
366
+ let result = requested === undefined ? {} : await writeSettings(platform, config, appId, requested, policyOverride);
367
+ if (requested !== undefined)
368
+ written.push('settings');
369
+ const steps = [
370
+ ...(Object.keys(declared).length > 0
371
+ ? [['declarations', async () => ({ ...storeSummary(unwrap(await platform.org('PUT', `/mobile-store/${appId}/declarations`, { body: { declarations: declared } }))), declared })]]
372
+ : []),
373
+ ...(registerAppleBundleId
374
+ ? [['bundleId', async () => ({ bundleId: unwrap(await platform.org('POST', `/mobile-store/${appId}/ios/bundle-id`)) })]]
375
+ : [])
376
+ ];
377
+ for (const [step, run] of steps) {
378
+ try {
379
+ result = { ...result, ...await run() };
380
+ written.push(step);
381
+ }
382
+ catch (error) {
383
+ if (written.length === 0 || !(error instanceof PlatformApiError))
384
+ throw error;
385
+ return { ...result, [`${step}Error`]: error.message, note: `${written.join(' + ')} YAZILDI; ${step} yazılamadı (ayrıntı ${step}Error) ve sonraki adımlar denenmedi. Yalnız kalan parçayla yeniden çağırın.` };
386
+ }
387
+ }
388
+ return result;
389
+ }));
390
+ server.registerTool('mobile_build_start', {
391
+ title: 'Mobil derleme başlat',
392
+ description: "Mobil uygulama için derleme işi açar (POST mobile-builds { platform, target }). Önce ön kontrolü okur (GET mobile-store/{appId}/preflight — Studio'nun Yayımla düğmesiyle aynı liste: ayar, görsel, gizli bilgi, App Store Connect / Play erişimi, production sayfası, etkin iş, çevrimiçi runner, beyanlar); bir satır bile 'fail' ise iş BAŞLATILMAZ ve fail satırları kod + öneriyle hata olarak döner. dryRun:true yalnız ön kontrolü döner, iş açmaz — yayından önce bununla bakın. Hedef–platform: build her ikisinde, testflight yalnız ios, internal yalnız android. Tuzak: her başlatma derleme numarasını AYIRIR ve ilk başlatma kimlik alanlarını kilitler; iptal ya da hata numarayı geri vermez. Aynı platformda etkin iş varsa 409. İş asenkrondur: mobile_build_status ile izleyin. Derlenen uygulama production ortamını okur — yükleme hedeflerinde açılış sayfası production'a dağıtılmış olmalı (P-06 fail); yalın build'de P-06 yalnız uyarıdır.",
393
+ inputSchema: {
394
+ applicationId,
395
+ platform: mobilePlatform,
396
+ target: buildTarget.optional(),
397
+ runnerScope: z.enum(['dedicated', 'shared']).optional()
398
+ .describe("dedicated: organizasyonun kendi runner'ı · shared: platformun ortak runner'ı. Verilmezse backend seçer (çevrimiçi dedicated varsa o, yoksa shared). Ön kontrol P-09 kapsamdan bağımsızdır; verilen kapsamda çevrimiçi runner yoksa iş hiç alınmaz ve numara yanardı — gateway bunu POST'tan önce durdurur. Durum: mobile_store_get { detail: ['runners'] }"),
399
+ dryRun: z.boolean().optional().describe('true: yalnız ön kontrol; iş açılmaz, numara ayrılmaz')
400
+ },
401
+ annotations: writes
402
+ }, handle(config, async ({ applicationId: appId, platform: mobile, target, runnerScope, dryRun }) => {
403
+ const resolvedTarget = target ?? 'build';
404
+ const allowed = { ios: ['build', 'testflight'], android: ['build', 'internal'] }[mobile];
405
+ if (!allowed.includes(resolvedTarget)) {
406
+ throw new PlatformApiError(`mobile_build_start: '${resolvedTarget}' hedefi ${mobile} için yok — ${mobile} yalnız ${allowed.join(' | ')} alır (testflight iOS'un, internal Android'in yükleme hedefidir). Hiçbir iş açılmadı.`, 400);
407
+ }
408
+ const preflight = unwrap(await platform.org('GET', `/mobile-store/${appId}/preflight`, { query: { platform: mobile, target: resolvedTarget } }));
409
+ const checks = Array.isArray(preflight.checks) ? preflight.checks.filter(isObject) : [];
410
+ const open = checks.filter(check => check.status !== 'ok');
411
+ const scopeProblem = runnerScope ? await runnerScopeProblem(platform, mobile, runnerScope) : undefined;
412
+ if (dryRun) {
413
+ return { dryRun: true, platform: mobile, target: resolvedTarget, ready: preflight.ready, blocked: preflight.blocked, checks: open, okCount: checks.length - open.length, ...(scopeProblem ? { runnerScopeProblem: scopeProblem } : {}) };
414
+ }
415
+ if (scopeProblem)
416
+ throw new PlatformApiError(`mobile_build_start: ${scopeProblem} İş BAŞLATILMADI, derleme numarası ayrılmadı.`, 422, 'runner_scope_offline');
417
+ const failed = checks.filter(check => check.status === 'fail');
418
+ if (failed.length > 0) {
419
+ throw new PlatformApiError([
420
+ `mobile_build_start: ön kontrol ${failed.length} satırda 'fail' — iş BAŞLATILMADI, derleme numarası ayrılmadı (Studio'nun Yayımla düğmesiyle aynı kapı):`,
421
+ ...failed.map(check => `- ${String(check.id)} ${String(check.code)}: ${String(check.message ?? '')}${check.fixHint ? ` → ${String(check.fixHint)}` : ''}`),
422
+ "Ayar satırları ve beyanlar mobile_store_update ile, görseller mobile_asset_create ile, Apple bundle ID'si (P-04) mobile_store_update { registerAppleBundleId: true } ile; gizli bilgi satırları Studio › Yayın › Hesaplar'da düzeltilir. Tam liste: mobile_build_start { dryRun: true }."
423
+ ].join('\n'), 422, 'preflight_failed');
424
+ }
425
+ const job = unwrap(await platform.org('POST', '/mobile-builds', { body: { applicationId: appId, platform: mobile, target: resolvedTarget, ...(runnerScope ? { runnerScope } : {}) } }));
426
+ const warnings = checks.filter(check => check.status === 'warn' || check.status === 'manual');
427
+ return {
428
+ ...buildSummary(job, new Date().toISOString()),
429
+ ...(warnings.length > 0 ? { preflightWarnings: warnings.map(check => `${String(check.id)} ${String(check.code)}: ${String(check.message ?? '')}`) } : {})
430
+ };
431
+ }));
432
+ server.registerTool('mobile_build_status', {
433
+ title: 'Mobil derleme durumu',
434
+ description: "buildId verilirse o işin durumunu döner (GET mobile-builds/{id}): status (queued | running | completed | failed | cancelled), stage (claimed → preparing → building → signing → uploading → store_processing), aşama başına süre (seconds), toplam süre, version + buildNumber, error { code, message, hint }, storeResult (testflight: testflightBuildId, processingState, listingError + listingMessage — derleme TestFlight'ta ama mağaza metni/görselleri reddedildi, iş yine tamamlanır; internal: versionCode, releaseStatus) ve awaitingStore (TestFlight işleme yoklaması — runner işten çıkmıştır, iş Apple VALID deyince completed olur, en çok 60 dk). Yalnız applicationId verilirse uygulamanın son işlerini özetle listeler (en yeni önce, ≤ 50). Hata kodunun önerisi platform_guide('mobile-publish', 'hata-kodlari') bölümündedir.",
435
+ inputSchema: {
436
+ buildId: buildId.optional(),
437
+ applicationId: applicationId.optional()
438
+ },
439
+ annotations: readOnly
440
+ }, handle(config, async ({ buildId: id, applicationId: appId }) => {
441
+ if ((id === undefined) === (appId === undefined)) {
442
+ throw new PlatformApiError("mobile_build_status: buildId ya da applicationId'den TAM OLARAK biri verilir — buildId tek işin ayrıntısını, applicationId son işlerin listesini döner.", 400);
443
+ }
444
+ const now = new Date().toISOString();
445
+ if (id)
446
+ return buildSummary(unwrap(await platform.org('GET', `/mobile-builds/${id}`)), now);
447
+ const payload = await platform.org('GET', '/mobile-builds', { query: { applicationId: appId } });
448
+ const jobs = Array.isArray(payload?.data) ? payload.data.filter(isObject) : [];
449
+ return {
450
+ applicationId: appId,
451
+ count: jobs.length,
452
+ builds: jobs.map(job => ({ id: job.id, platform: job.platform, target: job.target, status: job.status, stage: job.stage, buildNumber: job.buildNumber, version: job.version, errorCode: isObject(job.error) ? job.error.code : undefined, createdAt: job.createdAt, finishedAt: job.finishedAt })),
453
+ ...(jobs.length === 0 ? { note: 'Bu uygulamada hiç derleme işi yok — çağrı BAŞARILI, sonuç boş. Başlatmak için mobile_build_start.' } : {})
454
+ };
455
+ }));
456
+ server.registerTool('mobile_build_log', {
457
+ title: 'Mobil derleme günlüğü',
458
+ description: "Derleme işinin günlüğünü artımlı okur (GET mobile-builds/{id}/log?after=). after verilen sıra numarasından SONRAKİ satırlar gelir (ilk çağrıda 0); yanıttaki nextAfter bir sonraki çağrının after'ıdır, more:true ise okunmamış satır vardır. Satır biçimi '<seq> [<aşama>] <metin>'. Gizli değerler runner'da maskelenmiştir. Hata ayıklarken önce mobile_build_status'un error alanı okunur; günlük ilk hata satırını bulmak içindir.",
459
+ inputSchema: {
460
+ buildId,
461
+ after: z.number().int().min(0).optional().describe('Bu sıra numarasından sonraki satırlar; varsayılan 0 (baştan)'),
462
+ limit: z.number().int().min(1).max(2000).optional().describe('En çok kaç satır; varsayılan 200')
463
+ },
464
+ annotations: readOnly
465
+ }, handle(config, async ({ buildId: id, after, limit }) => {
466
+ const from = after ?? 0;
467
+ const max = limit ?? 200;
468
+ const log = unwrap(await platform.org('GET', `/mobile-builds/${id}/log`, { query: { after: from } }));
469
+ const lines = Array.isArray(log.lines) ? log.lines.filter(isObject) : [];
470
+ const shown = lines.slice(0, max);
471
+ const last = shown.at(-1);
472
+ return {
473
+ buildId: id,
474
+ lines: shown.map(line => `${String(line.seq)} [${String(line.stage ?? '-')}] ${String(line.line ?? '')}`),
475
+ nextAfter: last ? Number(last.seq) : from,
476
+ more: lines.length > shown.length || lines.length >= LOG_PAGE_LIMIT
477
+ };
478
+ }));
479
+ server.registerTool('mobile_build_cancel', {
480
+ title: 'Mobil derlemeyi iptal et',
481
+ description: "Derleme işini iptal eder (POST mobile-builds/{id}/cancel). queued iş hemen cancelled olur; running işte cancelRequested işaretlenir ve runner bir sonraki kalp atışında (≤ 30 sn) durur — sonucu mobile_build_status ile okuyun. Geri alınamaz: ayrılan derleme numarası yanar (mağaza aynı numarayı ikinci kez kabul etmez). TestFlight işleme yoklamasındaki (awaitingStore) ya da bitmiş iş iptal edilemez: 409.",
482
+ inputSchema: { buildId },
483
+ annotations: destructive
484
+ }, handle(config, async ({ buildId: id }) => buildSummary(unwrap(await platform.org('POST', `/mobile-builds/${id}/cancel`)), new Date().toISOString())));
485
+ server.registerTool('mobile_asset_create', {
486
+ title: 'Mağaza görseli yükle',
487
+ description: `Mağaza görselini yükler (POST mobile-store/{appId}/assets, multipart). Kaynak path (gateway'in okuyabildiği yerel dosya) ya da base64 content'tir — dosya ajanın bağlamına girmez. Yuvalar: derleme görselleri icon (1024×1024 PNG, alfasız), adaptiveForeground / adaptiveBackground / adaptiveMonochrome (1024×1024 PNG), splash (≥ 1242×2436 PNG) — dilsizdir, locale verilmez; mağaza görselleri featureGraphic (1024×500, alfasız), promo, screenshot (deviceClass zorunlu, locale önerilir: 'screenshot/tr/iphone69'). Tuzak: screenshot ve promo dışındaki türler TEKLİDİR — yükleme yuvadaki önceki görselin YERİNE geçer ve eskisi silinir. Yuva başına en çok 10 ekran görüntüsü, dosya ≤ 10 MB, PNG ya da JPEG (derleme görseli yalnız PNG). Ölçü, biçim ve alfa backend'de denetlenir; 422 hangi ölçünün beklendiğini söyler. EKRAN GÖRÜNTÜSÜ uygulamanın kendi sayfasından üretilir: önce page_screenshot { environment: 'production', applicationSlug, pageSlug, device, format: 'jpeg', quality: 92, language, colorScheme: 'light', outPath }, sonra bu araç { path: <outPath>, kind: 'screenshot', deviceClass, locale }. Cihaz sınıfı → device: ${captureTable}. JPEG şarttır: iPhone ve iPad ekran görüntüsünde alfa kanalı reddedilir. Giriş/splash ekranını çekmeyin (App Review 2.3.3: uygulama kullanımda gösterilir). Sıra: mobile_asset_update; silme: mobile_asset_delete; liste: mobile_store_get { detail: ['assets'] }.`,
488
+ inputSchema: {
489
+ applicationId,
490
+ path: z.string().min(1).optional().describe("Gateway sürecinin okuyabildiği yerel PNG/JPEG yolu (page_screenshot outPath'i); content ile birlikte verilmez"),
491
+ content: z.string().min(1).optional().describe('Base64 görsel içeriği; path ile birlikte verilmez'),
492
+ filename: z.string().min(1).optional().describe("Dosya adı; verilmezse path'in adı ya da '<kind>.png|jpg'"),
493
+ kind: z.enum(ASSET_KINDS),
494
+ locale: z.string().optional().describe("Mağaza görselinin dili ('tr', 'en-US'); derleme görselinde verilmez"),
495
+ deviceClass: z.enum(DEVICE_CLASSES).optional().describe("Yalnız screenshot: iphone69 (iPhone 6.9\"), ipad13 (iPad 13\"), phone, tablet7, tablet10 (Play)")
496
+ },
497
+ // Tekli yuvada önceki görsel ve dosyası silinir (`MobileStoreService.UploadAssetAsync`): geri alınamaz.
498
+ // Ekran görüntüsü eklendiği için idempotent değildir.
499
+ annotations: { ...writes, destructiveHint: true }
500
+ }, handle(config, async ({ applicationId: appId, path, content, filename, kind, locale, deviceClass }) => {
501
+ // Backend boş dili null sayar (`MobileStoreService.UploadAssetAsync`); data URL öneki base64 değildir.
502
+ const slotLocale = locale?.trim() ? locale.trim() : undefined;
503
+ const problem = assetSlotProblem(kind, slotLocale, deviceClass);
504
+ if (problem)
505
+ throw new PlatformApiError(`mobile_asset_create: ${problem}. Hiçbir şey yüklenmedi.`, 400);
506
+ const file = await readAsset(kind, path, content?.replace(/^data:[^,]*;base64,/, ''), filename);
507
+ const asset = unwrap(await platform.org('POST', `/mobile-store/${appId}/assets`, {
508
+ multipart: [
509
+ { name: 'file', filename: file.name, contentType: file.contentType, content: file.bytes },
510
+ { name: 'kind', value: kind },
511
+ ...(slotLocale === undefined ? [] : [{ name: 'locale', value: slotLocale }]),
512
+ ...(deviceClass === undefined ? [] : [{ name: 'deviceClass', value: deviceClass }])
513
+ ]
514
+ }));
515
+ return { ...assetSummary(asset), ...(kind === 'screenshot' || kind === 'promo' ? {} : { note: `'${kind}' tekli yuvadır: varsa önceki görsel silindi.` }) };
516
+ }));
517
+ server.registerTool('mobile_asset_update', {
518
+ title: 'Mağaza görsellerini sırala',
519
+ description: "Bir yuvadaki görsellerin sırasını yazar (PUT mobile-store/{appId}/assets/order): dizideki sıra mağazadaki gösterim sırası olur (ilk ekran görüntüsü mağazada önce görünür). assetIds TEK yuvanın (ör. 'screenshot/tr/iphone69') BÜTÜN görsellerini içerir — backend her id'ye dizideki indeksini yazar ve yuvayı denetlemez; eksik liste ya da iki yuvanın karışımı sessizce çakışan sıra üretirdi, bu yüzden gateway istek gitmeden kaydı okur ve bunları durdurur. Id'ler: mobile_store_get { detail: ['assets'] }.",
520
+ inputSchema: {
521
+ applicationId,
522
+ assetIds: z.array(guid).min(1).describe('Yuvanın bütün görsel id\'leri, istenen sırada')
523
+ },
524
+ annotations: updates
525
+ }, handle(config, async ({ applicationId: appId, assetIds }) => {
526
+ const current = unwrap(await platform.org('GET', `/mobile-store/${appId}`));
527
+ const assets = Array.isArray(current.assets) ? current.assets.filter(isObject) : [];
528
+ const unknown = assetIds.filter(id => !assets.some(asset => asset.id === id));
529
+ const slots = [...new Set(assets.filter(asset => assetIds.includes(String(asset.id))).map(slotOf))];
530
+ const missing = slots.length === 1 ? assets.filter(asset => slotOf(asset) === slots[0] && !assetIds.includes(String(asset.id))).map(asset => asset.id) : [];
531
+ if (unknown.length > 0 || slots.length !== 1 || missing.length > 0 || new Set(assetIds).size !== assetIds.length) {
532
+ throw new PlatformApiError([
533
+ 'mobile_asset_update: sıra yazılmadı —',
534
+ unknown.length > 0 ? `uygulamada olmayan id: ${unknown.join(', ')};` : '',
535
+ slots.length > 1 ? `id'ler ${slots.length} yuvaya dağılıyor (${slots.join(', ')}) — tek yuva verin;` : '',
536
+ missing.length > 0 ? `'${slots[0]}' yuvasının şu görselleri eksik: ${missing.join(', ')} — yuvanın tamamı verilir;` : '',
537
+ new Set(assetIds).size !== assetIds.length ? 'aynı id iki kez verilmiş;' : '',
538
+ "güncel liste: mobile_store_get { detail: ['assets'] }."
539
+ ].filter(Boolean).join(' '), 400);
540
+ }
541
+ const ordered = list(await platform.org('PUT', `/mobile-store/${appId}/assets/order`, { body: { assetIds } }));
542
+ return { applicationId: appId, slot: slots[0], assets: ordered.filter(asset => slotOf(asset) === slots[0]).map(assetSummary) };
543
+ }));
544
+ server.registerTool('mobile_asset_delete', {
545
+ title: 'Mağaza görselini sil',
546
+ description: "Mağaza görselini siler (DELETE mobile-store/{appId}/assets/{assetId}); dosya depodan da silinir, geri alınamaz. Derleme görseli (icon, splash, adaptive*) silinirse ön kontrol ilgili satırı fail'e çevirir. Tekli yuvadaki görseli değiştirmek için silmek gerekmez — mobile_asset_create yerine geçer. Id: mobile_store_get { detail: ['assets'] }.",
547
+ inputSchema: {
548
+ applicationId,
549
+ assetId: guid.describe("Görselin id'si (mobile_store_get { detail: ['assets'] })")
550
+ },
551
+ annotations: destructive
552
+ }, handle(config, async ({ applicationId: appId, assetId }) => {
553
+ await platform.org('DELETE', `/mobile-store/${appId}/assets/${assetId}`);
554
+ return { applicationId: appId, deleted: assetId };
555
+ }));
556
+ }
@@ -0,0 +1,48 @@
1
+ import { guid, projectParam, environmentParam, readOnly, writes } from './common.js';
2
+ import { PolicyBlockError } from '../knowledge/policy.js';
3
+ import { handle } from '../platform/responses.js';
4
+ import { z } from 'zod';
5
+ export function registerModuleTools(server, platform) {
6
+ const config = platform.config;
7
+ server.registerTool('module_list', {
8
+ title: 'Sistem modüllerini listele',
9
+ description: "Kurulabilir sistem modülleri (catalog: slug, name, version, requires) ve bu ortamdaki kurulumlar (installations: module, version, catalogVersion, applicationId, receiveUpdates, isOutdated). Katalogda iki modül var: access (yetki modülü, kompleks proje ister) ve bpm-management (süreç yönetimi: süreç sayfaları ve iş takvimi). requires dolu bir modül, gerektirdiği modül aynı application'a kurulmadan kurulamaz.",
10
+ inputSchema: {
11
+ project: projectParam,
12
+ environment: environmentParam,
13
+ module: z.string().min(1).max(100).optional().describe('Yalnız bu modülün kurulumları')
14
+ },
15
+ annotations: readOnly
16
+ }, handle(config, async ({ project, environment, module }) => {
17
+ const [catalog, installations] = await Promise.all([
18
+ platform.tenant('GET', project, '/modules/catalog', { environment }),
19
+ platform.tenant('GET', project, '/modules', { environment, query: module ? { module } : undefined })
20
+ ]);
21
+ return {
22
+ catalog: catalog?.data ?? catalog,
23
+ installations: installations?.data ?? installations
24
+ };
25
+ }));
26
+ server.registerTool('module_update', {
27
+ title: 'Sistem modülünü kur, güncelle ya da ayır',
28
+ description: "operation install: modülü application'a kurar — süreç paketi tablolarını (yoksa), iş takvimlerini ve süreç tanımlarını getirir, süreçler o application'da aktif yayınlanır; gereken modül kurulu değilse 409 ModuleRequiresModule, ikinci kurulum 409 ModuleAlreadyInstalled. operation upgrade: katalog sürümü yeniyse içeriği yeniden kurar; değişen süreç yeni sürüm olarak yayınlanır ve müşterinin uyarlaması yeni sürüme taşınır (taşınamayan adım çakışma bildirimi olur). Var olan tablo ve verisine dokunulmaz — yeni paket sürümünün bir tabloya eklediği alan var olan tabloya eklenmez. operation detach: güncellemeleri keser, içerik ve çalışan örnekler yerinde kalır; ayırma geri alınmaz (ardından install 409 ModuleAlreadyInstalled, upgrade 400 döner). access modülünün kurulumu yalnız kaydı tutar, içerik getirmez (TB-92). production'a kurulum gateway'de durur (BPM-014): development'ta kurup deployment ile taşıyın.",
29
+ inputSchema: {
30
+ project: projectParam,
31
+ environment: environmentParam,
32
+ operation: z.enum(['install', 'upgrade', 'detach']),
33
+ module: z.string().min(1).max(100),
34
+ applicationId: guid
35
+ },
36
+ annotations: writes
37
+ }, handle(config, async ({ project, environment, operation, module, applicationId }) => {
38
+ if ((environment ?? config.defaultEnvironment) === 'production') {
39
+ throw new PolicyBlockError([{
40
+ ruleId: 'BPM-014',
41
+ severity: 'block',
42
+ message: `module_update: modül ${operation} production'a gönderilmez. Modülü development'ta kurun; production'a taşıma insan onaylı deployment işidir (tanım ve takvim taşınır, uyarlama taşınmaz).`,
43
+ guide: 'ngapps://guides/bpm'
44
+ }], false);
45
+ }
46
+ return platform.tenant('POST', project, `/modules/${encodeURIComponent(module)}/${operation}`, { environment, body: { applicationId } });
47
+ }));
48
+ }