@devoplus/one-sdk 0.0.0-stage → 0.2.0-next.112

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 (139) hide show
  1. package/CHANGELOG.md +268 -0
  2. package/README.md +894 -2
  3. package/bin/accessRequest.d.mts +96 -0
  4. package/bin/accessRequest.mjs +340 -0
  5. package/bin/appOps.d.mts +97 -0
  6. package/bin/appOps.mjs +336 -0
  7. package/bin/dataCommand.mjs +131 -0
  8. package/bin/devConfig.mjs +132 -0
  9. package/bin/one.mjs +341 -0
  10. package/bin/session.mjs +239 -0
  11. package/dist/agentInput.d.ts +9 -0
  12. package/dist/agentInput.d.ts.map +1 -0
  13. package/dist/agentInput.js +102 -0
  14. package/dist/agentInput.js.map +1 -0
  15. package/dist/agentTools.d.ts +83 -0
  16. package/dist/agentTools.d.ts.map +1 -0
  17. package/dist/agentTools.js +164 -0
  18. package/dist/agentTools.js.map +1 -0
  19. package/dist/ai.d.ts +67 -0
  20. package/dist/ai.d.ts.map +1 -0
  21. package/dist/ai.js +129 -0
  22. package/dist/ai.js.map +1 -0
  23. package/dist/audit.d.ts +21 -0
  24. package/dist/audit.d.ts.map +1 -0
  25. package/dist/audit.js +58 -0
  26. package/dist/audit.js.map +1 -0
  27. package/dist/backend.d.ts +27 -0
  28. package/dist/backend.d.ts.map +1 -0
  29. package/dist/backend.js +63 -0
  30. package/dist/backend.js.map +1 -0
  31. package/dist/cache.d.ts +20 -0
  32. package/dist/cache.d.ts.map +1 -0
  33. package/dist/cache.js +84 -0
  34. package/dist/cache.js.map +1 -0
  35. package/dist/client.d.ts +147 -0
  36. package/dist/client.d.ts.map +1 -0
  37. package/dist/client.js +469 -0
  38. package/dist/client.js.map +1 -0
  39. package/dist/components.d.ts +235 -0
  40. package/dist/components.d.ts.map +1 -0
  41. package/dist/components.js +1491 -0
  42. package/dist/components.js.map +1 -0
  43. package/dist/context.d.ts +110 -0
  44. package/dist/context.d.ts.map +1 -0
  45. package/dist/context.js +126 -0
  46. package/dist/context.js.map +1 -0
  47. package/dist/data/crud.d.ts +74 -0
  48. package/dist/data/crud.d.ts.map +1 -0
  49. package/dist/data/crud.js +614 -0
  50. package/dist/data/crud.js.map +1 -0
  51. package/dist/data/ddl.d.ts +76 -0
  52. package/dist/data/ddl.d.ts.map +1 -0
  53. package/dist/data/ddl.js +362 -0
  54. package/dist/data/ddl.js.map +1 -0
  55. package/dist/data/fold.d.ts +14 -0
  56. package/dist/data/fold.d.ts.map +1 -0
  57. package/dist/data/fold.js +76 -0
  58. package/dist/data/fold.js.map +1 -0
  59. package/dist/data/model.d.ts +113 -0
  60. package/dist/data/model.d.ts.map +1 -0
  61. package/dist/data/model.js +408 -0
  62. package/dist/data/model.js.map +1 -0
  63. package/dist/data/values.d.ts +29 -0
  64. package/dist/data/values.d.ts.map +1 -0
  65. package/dist/data/values.js +152 -0
  66. package/dist/data/values.js.map +1 -0
  67. package/dist/dexa.d.ts +44 -0
  68. package/dist/dexa.d.ts.map +1 -0
  69. package/dist/dexa.js +74 -0
  70. package/dist/dexa.js.map +1 -0
  71. package/dist/dexaCommand.d.ts +116 -0
  72. package/dist/dexaCommand.d.ts.map +1 -0
  73. package/dist/dexaCommand.js +226 -0
  74. package/dist/dexaCommand.js.map +1 -0
  75. package/dist/export/columns.d.ts +62 -0
  76. package/dist/export/columns.d.ts.map +1 -0
  77. package/dist/export/columns.js +127 -0
  78. package/dist/export/columns.js.map +1 -0
  79. package/dist/export/csv.d.ts +10 -0
  80. package/dist/export/csv.d.ts.map +1 -0
  81. package/dist/export/csv.js +30 -0
  82. package/dist/export/csv.js.map +1 -0
  83. package/dist/export/report.d.ts +19 -0
  84. package/dist/export/report.d.ts.map +1 -0
  85. package/dist/export/report.js +91 -0
  86. package/dist/export/report.js.map +1 -0
  87. package/dist/export/response.d.ts +14 -0
  88. package/dist/export/response.d.ts.map +1 -0
  89. package/dist/export/response.js +70 -0
  90. package/dist/export/response.js.map +1 -0
  91. package/dist/export/xlsx.d.ts +15 -0
  92. package/dist/export/xlsx.d.ts.map +1 -0
  93. package/dist/export/xlsx.js +167 -0
  94. package/dist/export/xlsx.js.map +1 -0
  95. package/dist/export/zip.d.ts +7 -0
  96. package/dist/export/zip.d.ts.map +1 -0
  97. package/dist/export/zip.js +83 -0
  98. package/dist/export/zip.js.map +1 -0
  99. package/dist/files.d.ts +52 -0
  100. package/dist/files.d.ts.map +1 -0
  101. package/dist/files.js +137 -0
  102. package/dist/files.js.map +1 -0
  103. package/dist/index.d.ts +31 -0
  104. package/dist/index.d.ts.map +1 -0
  105. package/dist/index.js +35 -0
  106. package/dist/index.js.map +1 -0
  107. package/dist/jobs.d.ts +57 -0
  108. package/dist/jobs.d.ts.map +1 -0
  109. package/dist/jobs.js +157 -0
  110. package/dist/jobs.js.map +1 -0
  111. package/dist/log.d.ts +22 -0
  112. package/dist/log.d.ts.map +1 -0
  113. package/dist/log.js +107 -0
  114. package/dist/log.js.map +1 -0
  115. package/dist/notify.d.ts +41 -0
  116. package/dist/notify.d.ts.map +1 -0
  117. package/dist/notify.js +11 -0
  118. package/dist/notify.js.map +1 -0
  119. package/dist/org.d.ts +184 -0
  120. package/dist/org.d.ts.map +1 -0
  121. package/dist/org.js +114 -0
  122. package/dist/org.js.map +1 -0
  123. package/dist/people.d.ts +39 -0
  124. package/dist/people.d.ts.map +1 -0
  125. package/dist/people.js +88 -0
  126. package/dist/people.js.map +1 -0
  127. package/dist/problem.d.ts +12 -0
  128. package/dist/problem.d.ts.map +1 -0
  129. package/dist/problem.js +33 -0
  130. package/dist/problem.js.map +1 -0
  131. package/dist/runtime.d.ts +92 -0
  132. package/dist/runtime.d.ts.map +1 -0
  133. package/dist/runtime.js +8 -0
  134. package/dist/runtime.js.map +1 -0
  135. package/dist/scenarios.d.ts +125 -0
  136. package/dist/scenarios.d.ts.map +1 -0
  137. package/dist/scenarios.js +251 -0
  138. package/dist/scenarios.js.map +1 -0
  139. package/package.json +55 -3
package/bin/one.mjs ADDED
@@ -0,0 +1,341 @@
1
+ #!/usr/bin/env node
2
+ // `one` — Devoplus One yerel geliştirme komutu (D-72). Uygulama deposu çalışma ortamı aracını ve onun yapılandırma
3
+ // dosyasını görmez: yapılandırma manifest'ten `.one/dev.jsonc`'a üretilir, araç arkada çalıştırılır.
4
+ import { spawn } from 'node:child_process'
5
+ import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
6
+ import { createRequire } from 'node:module'
7
+ import { homedir } from 'node:os'
8
+ import { dirname, join, resolve } from 'node:path'
9
+ import process from 'node:process'
10
+ import {
11
+ AccessRequestError,
12
+ KINDS,
13
+ buildAccessRequest,
14
+ describeAccessRequest,
15
+ describePortalContext,
16
+ getAccessRequest,
17
+ getPortalContext,
18
+ listMyAccessRequests,
19
+ parsePermissionOptions,
20
+ submitAccessRequest,
21
+ } from './accessRequest.mjs'
22
+ import { DataCommandError, runDataCommand } from './dataCommand.mjs'
23
+ import {
24
+ AppOpsError,
25
+ COMMAND_ACTIONS,
26
+ ROLLBACK_WARNING,
27
+ checkSlug,
28
+ describeDeployment,
29
+ describeDeploymentResult,
30
+ describeInsights,
31
+ getInsights,
32
+ listDeployments,
33
+ parseOpsArgs,
34
+ requestDeployment,
35
+ } from './appOps.mjs'
36
+ import { DEV_CONFIG_FILE, DEV_DIR, OneCliError, USAGE, assertCommandFits, commandArgs, devConfig, readDevManifest } from './devConfig.mjs'
37
+ import {
38
+ SessionError,
39
+ clientsFilePath,
40
+ deviceLogin,
41
+ ensureFresh,
42
+ registerCliClient,
43
+ resolveTarget,
44
+ revokeSession,
45
+ sessionFilePath,
46
+ } from './session.mjs'
47
+
48
+ function option(name) {
49
+ const i = process.argv.indexOf(`--${name}`)
50
+ return i >= 0 ? process.argv[i + 1] : undefined
51
+ }
52
+
53
+ /** Tekrar edilebilir seçeneğin bütün değerleri (`--permission a=b --permission c=d`). */
54
+ function optionAll(name) {
55
+ const values = []
56
+ for (let i = 0; i < process.argv.length; i++) if (process.argv[i] === `--${name}` && i + 1 < process.argv.length) values.push(process.argv[i + 1])
57
+ return values
58
+ }
59
+
60
+ /** Çalışma ortamı aracı önce uygulamanın, sonra SDK'nın bağımlılıklarından çözülür. */
61
+ function runtimeToolBin(appDir) {
62
+ for (const from of [join(appDir, 'package.json'), import.meta.url]) {
63
+ try {
64
+ const pkgPath = createRequire(from).resolve('wrangler/package.json')
65
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
66
+ const bin = typeof pkg.bin === 'string' ? pkg.bin : pkg.bin?.wrangler
67
+ if (bin) return join(dirname(pkgPath), bin)
68
+ } catch {
69
+ // sonraki konum
70
+ }
71
+ }
72
+ throw new OneCliError('Devoplus One çalışma ortamı bulunamadı; "pnpm install" çalıştırın.')
73
+ }
74
+
75
+ const SESSION_COMMANDS = ['login', 'logout', 'request', 'requests', 'context']
76
+ /** Yayın ve kullanım panosu komutları (D-86 · D-87) — `one login` oturumuyla backend'e gider. */
77
+ const OPS_COMMANDS = ['publish', 'promote', 'rollback', 'deployments', 'status']
78
+
79
+ function sessionFile() {
80
+ return sessionFilePath(process.env, homedir())
81
+ }
82
+
83
+ function readSession() {
84
+ try {
85
+ return JSON.parse(readFileSync(sessionFile(), 'utf8'))
86
+ } catch {
87
+ return undefined
88
+ }
89
+ }
90
+
91
+ /** Oturum dosyası yalnız kullanıcının okuyabileceği izinle yazılır (dizin 0700, dosya 0600). */
92
+ function writeSession(session) {
93
+ const file = sessionFile()
94
+ mkdirSync(dirname(file), { recursive: true, mode: 0o700 })
95
+ writeFileSync(file, JSON.stringify(session, null, 2) + '\n', { mode: 0o600 })
96
+ chmodSync(file, 0o600)
97
+ }
98
+
99
+ const sleep = (ms) => new Promise((done) => setTimeout(done, ms))
100
+
101
+ function readClients() {
102
+ try {
103
+ return JSON.parse(readFileSync(clientsFilePath(process.env, homedir()), 'utf8'))
104
+ } catch {
105
+ return {}
106
+ }
107
+ }
108
+
109
+ function writeClients(clients) {
110
+ const file = clientsFilePath(process.env, homedir())
111
+ mkdirSync(dirname(file), { recursive: true, mode: 0o700 })
112
+ writeFileSync(file, JSON.stringify(clients, null, 2) + '\n', { mode: 0o600 })
113
+ chmodSync(file, 0o600)
114
+ }
115
+
116
+ /**
117
+ * İstemci kimliği verilmediyse (D-78 d) bu kimlik sağlayıcı için daha önce kaydedilmiş olanı kullanır, yoksa dinamik
118
+ * kayıtla yenisini alıp saklar. `fresh: true` saklananı yok sayar (kayıt silinmiş/geçersizse bir kez yenilenir).
119
+ */
120
+ async function ensureClientId(target, { fresh = false } = {}) {
121
+ if (target.clientId) return { clientId: target.clientId, registered: false }
122
+ const clients = readClients()
123
+ if (!fresh && typeof clients[target.issuer] === 'string') return { clientId: clients[target.issuer], registered: false }
124
+ const clientId = await registerCliClient(target.issuer, { fetch })
125
+ writeClients({ ...clients, [target.issuer]: clientId })
126
+ return { clientId, registered: true }
127
+ }
128
+
129
+ async function freshSession() {
130
+ const { session, changed } = await ensureFresh(readSession(), { fetch })
131
+ if (changed) writeSession(session)
132
+ return session
133
+ }
134
+
135
+ /** Uygulamanın kısa adı: önce --app, sonra bu dizindeki one.manifest.json. */
136
+ function appSlugFromContext(explicit = option('app')) {
137
+ if (explicit) return explicit
138
+ try {
139
+ return readDevManifest(readFileSync(join(process.cwd(), 'one.manifest.json'), 'utf8')).slug
140
+ } catch {
141
+ return undefined
142
+ }
143
+ }
144
+
145
+ async function sessionCommand(command) {
146
+ if (command === 'login') {
147
+ const explicit = resolveTarget(
148
+ { env: option('env'), clientId: option('client-id'), issuer: option('auth'), apiBase: option('api-base') },
149
+ process.env,
150
+ )
151
+ const io = {
152
+ fetch,
153
+ sleep,
154
+ scope: option('scope'),
155
+ onPrompt: ({ verificationUri, verificationUriComplete, userCode, expiresIn }) => {
156
+ console.log(`Tarayıcıda şu adresi açın: ${verificationUriComplete ?? verificationUri}`)
157
+ console.log(`Kodu girin: ${userCode} (${Math.round(expiresIn / 60)} dakika geçerli)`)
158
+ console.log('Giriş ve portal seçimi tarayıcıda tamamlanınca burası kendiliğinden devam eder...')
159
+ },
160
+ }
161
+ let client = await ensureClientId(explicit)
162
+ let session
163
+ try {
164
+ session = await deviceLogin({ ...explicit, clientId: client.clientId }, io)
165
+ } catch (err) {
166
+ // Saklanan kayıt kimlik sağlayıcıda artık yoksa bir kez yeniden kaydolunur; açıkça verilen kimlik değiştirilmez.
167
+ if (!(err instanceof SessionError) || err.code !== 'invalid_client' || explicit.clientId || client.registered) throw err
168
+ client = await ensureClientId(explicit, { fresh: true })
169
+ session = await deviceLogin({ ...explicit, clientId: client.clientId }, io)
170
+ }
171
+ writeSession(session)
172
+ console.log(`Giriş tamamlandı (${session.env}). Oturum: ${sessionFile()}`)
173
+ return 0
174
+ }
175
+ if (command === 'logout') {
176
+ const session = readSession()
177
+ if (session) await revokeSession(session, { fetch })
178
+ rmSync(sessionFile(), { force: true })
179
+ console.log('Bu bilgisayardaki oturum silindi.')
180
+ return 0
181
+ }
182
+ if (command === 'requests') {
183
+ const session = await freshSession()
184
+ const options = { apiBase: session.apiBase, accessToken: session.accessToken }
185
+ const id = process.argv[3]
186
+ const rows = id && !id.startsWith('--') ? [await getAccessRequest(options, id)] : await listMyAccessRequests(options)
187
+ if (process.argv.includes('--json')) console.log(JSON.stringify(rows, null, 2))
188
+ else if (rows.length === 0) console.log('Talebiniz yok.')
189
+ else for (const row of rows) console.log(describeAccessRequest(row))
190
+ return 0
191
+ }
192
+ if (command === 'context') {
193
+ const session = await freshSession()
194
+ const context = await getPortalContext({ apiBase: session.apiBase, accessToken: session.accessToken })
195
+ console.log(process.argv.includes('--json') ? JSON.stringify(context, null, 2) : describePortalContext(context))
196
+ return 0
197
+ }
198
+ // request
199
+ const kind = process.argv[3]
200
+ if (!kind || kind.startsWith('--')) {
201
+ console.log(REQUEST_USAGE)
202
+ return kind ? 1 : 0
203
+ }
204
+ const sqlFile = option('sql-file')
205
+ const body = buildAccessRequest(kind, {
206
+ appSlug: appSlugFromContext(),
207
+ justification: option('reason'),
208
+ titleTr: option('title'),
209
+ titleEn: option('title-en'),
210
+ additionalPermissions: parsePermissionOptions(optionAll('permission'), optionAll('permission-en')),
211
+ portalKey: option('portal'),
212
+ queryKey: option('query'),
213
+ permissionCode: option('code'),
214
+ permissionNameTr: option('name'),
215
+ permissionNameEn: option('name-en'),
216
+ userGroupId: option('group'),
217
+ scope: option('scope'),
218
+ description: option('description'),
219
+ draftSql: sqlFile ? readFileSync(resolve(process.cwd(), sqlFile), 'utf8') : undefined,
220
+ })
221
+ const session = await freshSession()
222
+ const row = await submitAccessRequest({ apiBase: session.apiBase, accessToken: session.accessToken }, body)
223
+ if (process.argv.includes('--json')) console.log(JSON.stringify(row, null, 2))
224
+ else {
225
+ console.log('Talebiniz gönderildi; yönetici onaylayınca e-postayla bildirilir.')
226
+ console.log(describeAccessRequest(row))
227
+ console.log(`Durumu: one requests ${row.id}`)
228
+ }
229
+ return 0
230
+ }
231
+
232
+ /**
233
+ * `one publish|promote|rollback|deployments|status` (D-86 · D-87). Argümanlar ağa çıkmadan denetlenir; geri alma
234
+ * `--yes` olmadan hiçbir istek göndermez (veri kaybı olabilir).
235
+ */
236
+ async function opsCommand(command) {
237
+ const args = parseOpsArgs(command, process.argv.slice(3))
238
+ const found = appSlugFromContext(args.app)
239
+ if (!found) throw new AppOpsError('Uygulamanın kısa adı bulunamadı: komutu uygulamanın kök dizininde çalıştırın ya da --app verin.')
240
+ // Oturuma ve ağa dokunmadan önce: kısa ad adrese girer.
241
+ const slug = checkSlug(found)
242
+ if (command === 'rollback' && !args.yes) {
243
+ console.error(ROLLBACK_WARNING)
244
+ console.error(`Onaylıyorsanız komutu --yes ile yeniden çalıştırın: one rollback${args.portalKey ? ` --portal ${args.portalKey}` : ''}${args.toVersion ? ` --to ${args.toVersion}` : ''} --yes`)
245
+ return 1
246
+ }
247
+ const session = await freshSession()
248
+ const options = { apiBase: session.apiBase, accessToken: session.accessToken }
249
+ if (command === 'deployments') {
250
+ const rows = await listDeployments(options, slug, args.portalKey)
251
+ if (args.json) console.log(JSON.stringify(rows, null, 2))
252
+ else if (!Array.isArray(rows) || rows.length === 0) console.log('Yayın isteği yok.')
253
+ else for (const row of rows) console.log(describeDeployment(row))
254
+ return 0
255
+ }
256
+ if (command === 'status') {
257
+ const insights = await getInsights(options, slug, { portalKey: args.portalKey, days: args.days })
258
+ console.log(args.json ? JSON.stringify(insights, null, 2) : describeInsights(insights))
259
+ return 0
260
+ }
261
+ const action = COMMAND_ACTIONS[command]
262
+ const result = await requestDeployment(options, slug, { action, portalKey: args.portalKey, toVersion: args.toVersion })
263
+ if (args.json) console.log(JSON.stringify(result, null, 2))
264
+ else console.log(describeDeploymentResult(result, { action, slug, portalKey: args.portalKey }))
265
+ return 0
266
+ }
267
+
268
+ const REQUEST_USAGE = `kullanım: one request <tür> [seçenekler]
269
+
270
+ Türler:
271
+ ${Object.entries(KINDS)
272
+ .map(([name, kind]) => ` ${name.padEnd(15)} ${kind.label}${kind.audience === 'platform' ? ' (platform yöneticisine)' : ''}`)
273
+ .join('\n')}
274
+
275
+ Ortak: --app <kısa ad> (varsayılan: one.manifest.json dosyasındaki app.slug), --reason "<gerekçe>", --json
276
+ registration --title "<Türkçe ad>" [--title-en "<İngilizce ad>"]
277
+ [--permission <One.Uygulama.Eylem>="<Türkçe ad>"]... [--permission-en <One.Uygulama.Eylem>="<İngilizce ad>"]...
278
+ (uygulama içi izinler kayıtla birlikte istenir, en çok 16; sonradan eklemek platform yöneticisini ister)
279
+ installation (portal anahtarını sunucu portalın adından belirler, elle seçilemez; --portal yok sayılır;
280
+ portalın adı ve var olan anahtarı: one context)
281
+ dexa-query --query <sorgu anahtarı>
282
+ dexa-command --query <işlem anahtarı>
283
+ app-permission --code <One.Uygulama.Eylem> [--name "<Türkçe ad>"] [--name-en "<İngilizce ad>"] [--group <grup kimliği>]
284
+ (kod uygulamanın kaydında tanımlı olmalı; değilse talep reddedilir)
285
+ scope --scope <modül.read|write|manage|execute> ya da ai.use
286
+ new-query --description "<ihtiyaç>" [--sql-file <taslak.sql>] (taslak hiçbir yerde ÇALIŞTIRILMAZ)
287
+
288
+ Talep yalnız uygulamanın sahibi tarafından gönderilir (kayıt talebi hariç); portal ve kimlik oturumdan gelir.
289
+ Kayıt, kurulum, sorgu ve izin taleplerini portalın şirket yöneticisi, yetki alanı taleplerini platform yöneticisi onaylar.`
290
+
291
+ function main() {
292
+ const command = process.argv[2]
293
+ if (!command || command === '--help' || command === '-h') {
294
+ console.log(USAGE)
295
+ return 0
296
+ }
297
+ if (SESSION_COMMANDS.includes(command)) return sessionCommand(command)
298
+ if (command === 'data') return runDataCommand({ cwd: process.cwd(), args: process.argv.slice(3) })
299
+ if (OPS_COMMANDS.includes(command)) return opsCommand(command)
300
+ const appDir = process.cwd()
301
+ let raw
302
+ try {
303
+ raw = readFileSync(join(appDir, 'one.manifest.json'), 'utf8')
304
+ } catch {
305
+ throw new OneCliError('Bu dizinde one.manifest.json yok; komutu uygulamanın kök dizininde çalıştırın.')
306
+ }
307
+ const manifest = readDevManifest(raw)
308
+ assertCommandFits(command, manifest)
309
+ const args = commandArgs(command, { port: option('port'), outDir: resolve(appDir, DEV_DIR, 'dist') })
310
+ const config = devConfig(manifest, { apiBase: option('api-base') ?? process.env.ONE_API_BASE })
311
+ mkdirSync(resolve(appDir, DEV_DIR), { recursive: true })
312
+ writeFileSync(
313
+ resolve(appDir, DEV_DIR, DEV_CONFIG_FILE),
314
+ '// one tarafından üretilir; elle düzenlemeyin, her komutta one.manifest.json\'dan yeniden yazılır.\n' +
315
+ JSON.stringify(config, null, 2) +
316
+ '\n',
317
+ )
318
+ const child = spawn(process.execPath, [runtimeToolBin(appDir), ...args], {
319
+ cwd: appDir,
320
+ stdio: 'inherit',
321
+ env: { ...process.env, WRANGLER_SEND_METRICS: 'false' },
322
+ })
323
+ child.on('exit', (code, signal) => process.exit(signal ? 1 : (code ?? 1)))
324
+ return undefined
325
+ }
326
+
327
+ function fail(err) {
328
+ if (err instanceof OneCliError || err instanceof SessionError || err instanceof AccessRequestError || err instanceof DataCommandError || err instanceof AppOpsError) {
329
+ console.error(`one: ${err.message}`)
330
+ process.exit(1)
331
+ }
332
+ throw err
333
+ }
334
+
335
+ try {
336
+ const code = main()
337
+ if (code instanceof Promise) code.then((c) => process.exit(c), fail)
338
+ else if (code !== undefined) process.exit(code)
339
+ } catch (err) {
340
+ fail(err)
341
+ }
@@ -0,0 +1,239 @@
1
+ // `one login` oturumu (D-75): tarayıcısız giriş (cihaz kodu, RFC 8628) + belirteç yenileme + yerel saklama.
2
+ //
3
+ // Ağ ve dosya sistemi ÇAĞIRANDAN gelir (`fetch`, `sleep`, `now`) → testlenir (session.test.ts). Komutların kendisi
4
+ // one.mjs'tedir.
5
+ //
6
+ // ⛔ Belirteç yalnız kullanıcının kendi makinesinde, yalnız kullanıcının okuyabildiği dosyada (0600) durur ve yalnız
7
+ // kimlik sağlayıcıya ve Devoplus API'ye gider. Ekrana, günlüğe ya da hata metnine YAZILMAZ.
8
+
9
+ /** `https://…/` ya da yerel `http://localhost|127.0.0.1[:port]/…/` — belirteç taşıyan her taban bu kuralla denetlenir. */
10
+ export function isSecureBase(base) {
11
+ return (
12
+ /^https:\/\/[^\s/]+(\/[^\s]*)?\/$/.test(base ?? '') ||
13
+ /^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?\/([^\s]*\/)?$/.test(base ?? '')
14
+ )
15
+ }
16
+
17
+ export class SessionError extends Error {
18
+ /** Kimlik sağlayıcının OAuth hata kodu (`invalid_client` …), varsa. */
19
+ constructor(message, code) {
20
+ super(message)
21
+ this.code = code
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Ortamlar — kimlik sağlayıcı ve API adresleri (workspace "Alan adları" tablosu). Komut satırı istemcisinin kimliği
27
+ * sabit DEĞİLDİR (D-78 d): `--client-id` / `ONE_CLIENT_ID` verilmezse `one login` kimlik sağlayıcıya dinamik kayıtla
28
+ * (RFC 7591) kendini kaydeder ve kimliği bu bilgisayarda saklar.
29
+ */
30
+ export const ENVIRONMENTS = {
31
+ test: { issuer: 'https://test-auth.devoplus.dev', apiBase: 'https://test-api.devoplus.dev/' },
32
+ uat: { issuer: 'https://uat-auth.devoplus.dev', apiBase: 'https://uat-api.devoplus.dev/' },
33
+ prod: { issuer: 'https://auth.devoplus.com.tr', apiBase: 'https://api.devoplus.com.tr/' },
34
+ }
35
+
36
+ export const DEFAULT_ENVIRONMENT = 'test'
37
+ /**
38
+ * Dar ajan kapsamı (D-78 d): dinamik kaydın kendiliğinden etkin olmasının koşulu bu kümenin alt kümesi + `onedev.write`.
39
+ * Belirteç backend'de yalnız erişim talebi uçlarına ve uygulamanın yayın/pano uçlarına (D-86 · D-87, C5) geçer; portal verisine erişmez.
40
+ */
41
+ export const AGENT_SCOPE = 'openid profile email offline_access onedev.write'
42
+ export const DEFAULT_SCOPE = AGENT_SCOPE
43
+ export const CLI_CLIENT_NAME = 'Devoplus One komut satırı'
44
+ const DEVICE_GRANT = 'urn:ietf:params:oauth:grant-type:device_code'
45
+ /** Süresi dolmadan bu kadar saniye önce yenilenir (istek yoldayken dolmasın). */
46
+ const REFRESH_MARGIN_SECONDS = 60
47
+
48
+ /**
49
+ * Ortamı ve istemci kimliğini çözer. Öncelik: komut satırı → ortam değişkeni → ortam tablosu.
50
+ * @param {{ env?: string, clientId?: string, issuer?: string, apiBase?: string }} options
51
+ * @param {Record<string, string | undefined>} vars `process.env`
52
+ */
53
+ export function resolveTarget(options = {}, vars = {}) {
54
+ const name = options.env ?? vars.ONE_ENV ?? DEFAULT_ENVIRONMENT
55
+ const preset = ENVIRONMENTS[name]
56
+ if (!preset) throw new SessionError(`ortam tanınmadı: ${name} (${Object.keys(ENVIRONMENTS).join(', ')})`)
57
+ const issuer = (options.issuer ?? vars.ONE_AUTH_ISSUER ?? preset.issuer).replace(/\/+$/, '')
58
+ const apiBase = options.apiBase ?? vars.ONE_API_BASE ?? preset.apiBase
59
+ const clientId = options.clientId ?? vars.ONE_CLIENT_ID ?? undefined
60
+ if (!/^https:\/\/[^\s/]+$/.test(issuer) && !/^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(issuer))
61
+ throw new SessionError(`kimlik sağlayıcı adresi "https://..." biçiminde olmalı: ${issuer}`)
62
+ // Belirteç bu adrese gider: düz http yalnız yerel geliştirme adresinde kabul edilir (RFC 6750 §5.3).
63
+ if (!isSecureBase(apiBase)) throw new SessionError(`API tabanı "https://.../" biçiminde ve "/" ile bitmeli (http yalnız localhost): ${apiBase}`)
64
+ return { env: name, issuer, apiBase, clientId: clientId || undefined }
65
+ }
66
+
67
+ async function postForm(fetchImpl, url, form) {
68
+ let response
69
+ try {
70
+ response = await fetchImpl(url, {
71
+ method: 'POST',
72
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
73
+ body: new URLSearchParams(form).toString(),
74
+ })
75
+ } catch {
76
+ throw new SessionError(`kimlik sağlayıcıya ulaşılamadı: ${new URL(url).origin}`)
77
+ }
78
+ let body = {}
79
+ try {
80
+ body = await response.json()
81
+ } catch {
82
+ // gövdesiz yanıt
83
+ }
84
+ return { status: response.status, ok: response.ok, body }
85
+ }
86
+
87
+ function tokenSession(target, body, now) {
88
+ if (typeof body.access_token !== 'string' || !body.access_token)
89
+ throw new SessionError('kimlik sağlayıcı erişim belirteci döndürmedi.')
90
+ const expiresIn = Number.isFinite(body.expires_in) ? body.expires_in : 0
91
+ return {
92
+ env: target.env,
93
+ issuer: target.issuer,
94
+ apiBase: target.apiBase,
95
+ clientId: target.clientId,
96
+ accessToken: body.access_token,
97
+ refreshToken: typeof body.refresh_token === 'string' ? body.refresh_token : undefined,
98
+ expiresAt: Math.floor(now() / 1000) + expiresIn,
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Komut satırı istemcisini kimlik sağlayıcıya dinamik kayıtla (RFC 7591) kaydeder ve `client_id` döndürür.
104
+ * Public istemcidir (sır yok); yalnız cihaz kodu + yenileme grant'ı ve dar ajan kapsamını ister (D-78 d).
105
+ */
106
+ export async function registerCliClient(issuer, io) {
107
+ let response
108
+ try {
109
+ response = await io.fetch(`${issuer}/register`, {
110
+ method: 'POST',
111
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
112
+ body: JSON.stringify({
113
+ client_name: CLI_CLIENT_NAME,
114
+ grant_types: [DEVICE_GRANT, 'refresh_token'],
115
+ token_endpoint_auth_method: 'none',
116
+ scope: AGENT_SCOPE,
117
+ }),
118
+ })
119
+ } catch {
120
+ throw new SessionError(`kimlik sağlayıcıya ulaşılamadı: ${new URL(issuer).origin}`)
121
+ }
122
+ let body = {}
123
+ try {
124
+ body = await response.json()
125
+ } catch {
126
+ // gövdesiz yanıt
127
+ }
128
+ if (!response.ok || typeof body.client_id !== 'string' || !body.client_id)
129
+ throw new SessionError(oauthMessage(body, 'komut satırı istemcisi kaydedilemedi'), body?.error)
130
+ return body.client_id
131
+ }
132
+
133
+ /**
134
+ * Cihaz kodu girişi. `onPrompt` kullanıcıya adresi ve kodu gösterir; ardından onay beklenir.
135
+ * @param {ReturnType<typeof resolveTarget>} target
136
+ * @param {{ fetch: typeof fetch, sleep: (ms: number) => Promise<void>, now?: () => number,
137
+ * onPrompt: (p: { verificationUri: string, verificationUriComplete?: string, userCode: string, expiresIn: number }) => void,
138
+ * scope?: string }} io
139
+ */
140
+ export async function deviceLogin(target, io) {
141
+ const now = io.now ?? Date.now
142
+ const start = await postForm(io.fetch, `${target.issuer}/device_authorization`, {
143
+ client_id: target.clientId,
144
+ scope: io.scope ?? DEFAULT_SCOPE,
145
+ })
146
+ if (!start.ok) throw new SessionError(oauthMessage(start.body, 'giriş başlatılamadı'), start.body?.error)
147
+ const { device_code: deviceCode, user_code: userCode, verification_uri: verificationUri } = start.body
148
+ if (typeof deviceCode !== 'string' || typeof userCode !== 'string' || typeof verificationUri !== 'string')
149
+ throw new SessionError('kimlik sağlayıcının cihaz kodu yanıtı eksik.')
150
+ const expiresIn = Number(start.body.expires_in) || 900
151
+ let interval = Math.max(Number(start.body.interval) || 5, 1)
152
+ io.onPrompt({
153
+ verificationUri,
154
+ verificationUriComplete:
155
+ typeof start.body.verification_uri_complete === 'string' ? start.body.verification_uri_complete : undefined,
156
+ userCode,
157
+ expiresIn,
158
+ })
159
+
160
+ const deadline = now() + expiresIn * 1000
161
+ while (now() < deadline) {
162
+ await io.sleep(interval * 1000)
163
+ const poll = await postForm(io.fetch, `${target.issuer}/token`, {
164
+ grant_type: DEVICE_GRANT,
165
+ device_code: deviceCode,
166
+ client_id: target.clientId,
167
+ })
168
+ if (poll.ok) return tokenSession(target, poll.body, now)
169
+ const error = poll.body?.error
170
+ if (error === 'authorization_pending') continue
171
+ if (error === 'slow_down') {
172
+ interval += 5
173
+ continue
174
+ }
175
+ if (error === 'access_denied') throw new SessionError('giriş tarayıcıda reddedildi.')
176
+ if (error === 'expired_token') break
177
+ throw new SessionError(oauthMessage(poll.body, 'giriş tamamlanamadı'))
178
+ }
179
+ throw new SessionError('giriş kodunun süresi doldu; "one login" komutunu yeniden çalıştırın.')
180
+ }
181
+
182
+ /**
183
+ * Geçerli bir erişim belirteci döndürür; süresi dolmak üzereyse yenileme belirteciyle yeniler. Yenilenemezse
184
+ * `SessionError` (yeniden giriş gerekir). Dönüşte `changed: true` ise çağıran yeni oturumu kaydeder (yenileme
185
+ * belirteci her kullanımda değişir — eskisi bir daha kabul edilmez).
186
+ */
187
+ export async function ensureFresh(session, io) {
188
+ const now = io.now ?? Date.now
189
+ if (!session?.accessToken) throw new SessionError('oturum yok; önce "one login" çalıştırın.')
190
+ if (session.expiresAt - REFRESH_MARGIN_SECONDS > Math.floor(now() / 1000)) return { session, changed: false }
191
+ if (!session.refreshToken) throw new SessionError('oturumun süresi doldu; "one login" ile yeniden giriş yapın.')
192
+ const result = await postForm(io.fetch, `${session.issuer}/token`, {
193
+ grant_type: 'refresh_token',
194
+ refresh_token: session.refreshToken,
195
+ client_id: session.clientId,
196
+ })
197
+ if (!result.ok) throw new SessionError('oturum yenilenemedi; "one login" ile yeniden giriş yapın.')
198
+ const next = tokenSession(session, result.body, now)
199
+ // Yanıt yeni yenileme belirteci taşımıyorsa eskisi korunur (RFC 6749 §6: sunucu yenisini vermeyebilir).
200
+ // Döndürme açık sağlayıcıda yanıt her zaman yenisini taşır; o zaman eskisi zaten geçersizdir ve kullanılmaz.
201
+ if (next.refreshToken === undefined && session.refreshToken) next.refreshToken = session.refreshToken
202
+ return { session: next, changed: true }
203
+ }
204
+
205
+ /** Yenileme belirtecini kimlik sağlayıcıda iptal eder (RFC 7009). Başarısızlık sessizdir — yerel kayıt yine silinir. */
206
+ export async function revokeSession(session, io) {
207
+ if (!session?.refreshToken) return false
208
+ try {
209
+ const result = await postForm(io.fetch, `${session.issuer}/revoke`, {
210
+ token: session.refreshToken,
211
+ token_type_hint: 'refresh_token',
212
+ client_id: session.clientId,
213
+ })
214
+ return result.ok
215
+ } catch {
216
+ return false
217
+ }
218
+ }
219
+
220
+ function oauthMessage(body, prefix) {
221
+ const description = typeof body?.error_description === 'string' ? body.error_description : undefined
222
+ const code = typeof body?.error === 'string' ? body.error : undefined
223
+ return description ? `${prefix}: ${description}` : code ? `${prefix} (${code})` : prefix
224
+ }
225
+
226
+ function configDir(vars, home) {
227
+ const base = vars.XDG_CONFIG_HOME && vars.XDG_CONFIG_HOME.trim() ? vars.XDG_CONFIG_HOME : `${home}/.config`
228
+ return `${base.replace(/\/+$/, '')}/devoplus-one`
229
+ }
230
+
231
+ /** Oturum dosyasının yolu: `$XDG_CONFIG_HOME/devoplus-one/session.json`, yoksa `~/.config/devoplus-one/session.json`. */
232
+ export function sessionFilePath(vars, home) {
233
+ return `${configDir(vars, home)}/session.json`
234
+ }
235
+
236
+ /** Dinamik kayıtla alınmış istemci kimlikleri (kimlik sağlayıcı adresi → `client_id`); oturumdan ayrı, çıkışta silinmez. */
237
+ export function clientsFilePath(vars, home) {
238
+ return `${configDir(vars, home)}/clients.json`
239
+ }
@@ -0,0 +1,9 @@
1
+ export declare const AGENT_INPUT_KEYWORDS: readonly ['type', 'properties', 'required', 'enum', 'minimum', 'maximum', 'minLength', 'maxLength', 'items', 'description'];
2
+ /** Bir alan hatası: `path` girdi içindeki yol (`limit`, `tags.2`); kök için `(kök)`. */
3
+ export interface AgentInputError {
4
+ path: string;
5
+ message: string;
6
+ }
7
+ /** Girdiyi şemaya göre denetler; boş dizi = geçerli. */
8
+ export declare function validateAgentInput(schema: unknown, input: unknown): AgentInputError[];
9
+ //# sourceMappingURL=agentInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentInput.d.ts","sourceRoot":"","sources":["../src/agentInput.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,oBAAoB,YAAI,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,CAAU,CAAA;AAE/J,wFAAwF;AACxF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAoBD,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,eAAe,EAAE,CAIrF"}