empuno 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +77 -2
- package/empuno.mjs +774 -0
- package/package.json +30 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lutfios LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,78 @@
|
|
|
1
|
-
#
|
|
1
|
+
# empuno CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[empuno](https://empuno.com) İK platformunun komut satırı. Yönetim panelinde
|
|
4
|
+
yapabildiğiniz **her işlemi** — izin onayı, bordro dönemi, vardiya planı,
|
|
5
|
+
zimmet, PDKS, masraf, işe alım, evrak — yapay zekâ ajanlarınıza ve
|
|
6
|
+
otomasyonlarınıza açar.
|
|
7
|
+
|
|
8
|
+
> Command-line client for the empuno HR platform (Turkey). Every admin-panel
|
|
9
|
+
> action is a command; output is always JSON. Docs: https://empuno.com/api-docs/agent
|
|
10
|
+
|
|
11
|
+
## Kurulum
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm i -g empuno # Node.js 18+
|
|
15
|
+
# ya da kurmadan:
|
|
16
|
+
npx empuno whoami
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Giriş
|
|
20
|
+
|
|
21
|
+
empuno panelinde **Firma Ayarları → API & Ajan → Yeni Anahtar → Tür: Ajan**
|
|
22
|
+
ile bir anahtar oluşturun ve modül yetkilerini seçin. Sonra:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
echo "emp_live_…" | empuno login --key-stdin # anahtar kabuk geçmişine düşmez
|
|
26
|
+
empuno whoami
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Ya da ortam değişkeniyle: `EMPUNO_API_KEY=emp_live_… empuno whoami`.
|
|
30
|
+
|
|
31
|
+
## Kullanım
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
empuno commands izin # komut ara (Türkçe başlıklarla)
|
|
35
|
+
empuno describe leave-requests.reject # yol parametreleri, alanlar, gereken yetki
|
|
36
|
+
empuno leave-requests.index --status pending
|
|
37
|
+
empuno leave-requests.approve 123
|
|
38
|
+
empuno leave-requests.reject 124 --rejection_reason "Yoğun dönem"
|
|
39
|
+
empuno employees.documents.store 45 --document_type is_sozlesmesi --file file=./sozlesme.pdf
|
|
40
|
+
empuno leave-reports.download 12 --output ./rapor.pdf
|
|
41
|
+
empuno call POST /manager/leave-requests/123/approve # ham çağrı
|
|
42
|
+
empuno api leaves --status approved # salt-okuma Company API
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Komutlar sunucudaki katalogdan okunur: panele yeni bir özellik geldiğinde
|
|
46
|
+
komutu CLI güncellemesi olmadan görünür.
|
|
47
|
+
|
|
48
|
+
- Yol parametreleri sırayla verilir; `--alan değer` bayrakları okuma
|
|
49
|
+
komutunda sorgu parametresi, yazma komutunda JSON gövdesi olur.
|
|
50
|
+
- `--alan @dosya` ya da `--file alan=dosya` → multipart yükleme.
|
|
51
|
+
- `--json '{…}'` / `--json @govde.json`, `--dry-run`, `--output yol`,
|
|
52
|
+
`--profile ad` (birden çok firma).
|
|
53
|
+
|
|
54
|
+
## Çıktı sözleşmesi
|
|
55
|
+
|
|
56
|
+
- stdout **her zaman JSON** (TTY'de girintili).
|
|
57
|
+
- Okuma: `{"ok": true, "page": "…", "data": {…}}`
|
|
58
|
+
- Yazma: `{"ok": true, "message": "…", "redirect": {"command": "…", "params": {…}}}`
|
|
59
|
+
- Hata: `{"ok": false, "error": "…", "message": "…", "errors": {…}}`
|
|
60
|
+
- Çıkış kodu: `0` başarı, `1` API hatası, `2` kullanım hatası, `3` ağ hatası.
|
|
61
|
+
|
|
62
|
+
## Ajanınıza verin
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
empuno İK sistemine `empuno` CLI ile erişimin var. Önce `empuno commands <anahtar kelime>`
|
|
66
|
+
ile komutu bul, `empuno describe <komut>` ile parametrelerini öğren, sonra çalıştır.
|
|
67
|
+
422 → `errors` alanlarını düzeltip tekrar dene; 403 → yetki yetmiyor, kullanıcıya
|
|
68
|
+
`required_permission`ı söyle. Geri alınamaz işlemlerden önce kullanıcıdan onay al.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Güvenlik
|
|
72
|
+
|
|
73
|
+
Ajan anahtarı, onu oluşturan kişinin adına ve seçilen modül yetkileriyle
|
|
74
|
+
çalışır; etkin yetki hiçbir zaman o kişinin yetkisini aşamaz. Her yazma işlemi
|
|
75
|
+
panelde anahtarın işlem günlüğünde görünür. Anahtar panelden anında iptal
|
|
76
|
+
edilir. Yerel yapılandırma `~/.config/empuno/config.json` (0600) dosyasındadır.
|
|
77
|
+
|
|
78
|
+
Belgeler: https://empuno.com/api-docs/agent · düz metin: https://empuno.com/api-docs/agent.txt
|
package/empuno.mjs
ADDED
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// empuno CLI — ajanlar (ve insanlar) için empuno komut satırı.
|
|
3
|
+
//
|
|
4
|
+
// Tek dosya, bağımlılıksız, Node >= 18. Komutlar sunucudaki katalogdan
|
|
5
|
+
// (GET /api/company/v1/catalog) okunur: panelde olan her yönetim işlemi
|
|
6
|
+
// burada da vardır; yeni özellikler CLI güncellemesi gerektirmeden görünür.
|
|
7
|
+
//
|
|
8
|
+
// Kurulum : npm i -g empuno (ya da kurmadan: npx empuno …)
|
|
9
|
+
// curl -fsSL https://empuno.com/cli/empuno.mjs -o ~/.local/bin/empuno && chmod +x ~/.local/bin/empuno
|
|
10
|
+
// Giriş : empuno login (anahtar: Firma Ayarları → API → Ajan anahtarı)
|
|
11
|
+
// Keşif : empuno commands izin → empuno describe leave-requests.approve
|
|
12
|
+
// Çalıştır: empuno leave-requests.approve 123
|
|
13
|
+
//
|
|
14
|
+
// Çıktı sözleşmesi: stdout HER ZAMAN JSON (TTY değilse tek satır, TTY'de
|
|
15
|
+
// girintili; --text ile insan okunur liste). Çıkış kodu: 0 başarı, 1 API
|
|
16
|
+
// hatası (4xx/5xx — gövde yine stdout'ta JSON), 2 kullanım hatası, 3 ağ hatası.
|
|
17
|
+
// Belgeler: https://empuno.com/api-docs/agent
|
|
18
|
+
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import os from 'node:os';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import crypto from 'node:crypto';
|
|
23
|
+
|
|
24
|
+
const VERSION = '1.0.1'; // EMPUNO_CLI_VERSION — config/agent_api.php cli_version + package.json ile aynı tutulur
|
|
25
|
+
const NPM_PACKAGE = 'empuno';
|
|
26
|
+
const DEFAULT_URL = 'https://empuno.com';
|
|
27
|
+
const CATALOG_TTL_MS = 10 * 60 * 1000;
|
|
28
|
+
|
|
29
|
+
// ─── yardımcılar ───────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
const isTTY = process.stdout.isTTY === true;
|
|
32
|
+
let outputMode = null; // 'json' | 'text' | null(otomatik)
|
|
33
|
+
let prettyFlag = null;
|
|
34
|
+
|
|
35
|
+
class UsageError extends Error {}
|
|
36
|
+
|
|
37
|
+
function print(value) {
|
|
38
|
+
const pretty = prettyFlag ?? isTTY;
|
|
39
|
+
process.stdout.write(JSON.stringify(value, null, pretty ? 2 : 0) + '\n');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function wantsText() {
|
|
43
|
+
if (outputMode) return outputMode === 'text';
|
|
44
|
+
return isTTY;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function warn(msg) {
|
|
48
|
+
process.stderr.write(`empuno: ${msg}\n`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function configDir() {
|
|
52
|
+
const base = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
53
|
+
return path.join(base, 'empuno');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function cacheDir() {
|
|
57
|
+
const base = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
|
|
58
|
+
return path.join(base, 'empuno');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function configPath() {
|
|
62
|
+
return process.env.EMPUNO_CONFIG || path.join(configDir(), 'config.json');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readConfig() {
|
|
66
|
+
try {
|
|
67
|
+
return JSON.parse(fs.readFileSync(configPath(), 'utf8'));
|
|
68
|
+
} catch {
|
|
69
|
+
return { default: 'default', profiles: {} };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function writeConfig(cfg) {
|
|
74
|
+
const file = configPath();
|
|
75
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
76
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
77
|
+
fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2) + '\n', { mode: 0o600 });
|
|
78
|
+
fs.renameSync(tmp, file);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function maskKey(key) {
|
|
82
|
+
return key ? `${key.slice(0, 13)}…${key.slice(-4)}` : null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Etkin profil: --key/--url bayrağı > EMPUNO_API_KEY/EMPUNO_URL > config dosyası. */
|
|
86
|
+
function resolveAuth(flags) {
|
|
87
|
+
const cfg = readConfig();
|
|
88
|
+
const profileName = flags.profile || process.env.EMPUNO_PROFILE || cfg.default || 'default';
|
|
89
|
+
const profile = cfg.profiles?.[profileName] || {};
|
|
90
|
+
const key = flags.key || process.env.EMPUNO_API_KEY || profile.key || null;
|
|
91
|
+
const url = (flags.url || process.env.EMPUNO_URL || profile.url || DEFAULT_URL).replace(/\/+$/, '');
|
|
92
|
+
return { key, url, profileName };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function requireKey(auth) {
|
|
96
|
+
if (!auth.key) {
|
|
97
|
+
throw new UsageError('API anahtarı yok. `empuno login` çalıştırın ya da EMPUNO_API_KEY ortam değişkenini ayarlayın. Anahtar: empuno paneli → Firma Ayarları → API → "Ajan" türünde anahtar.');
|
|
98
|
+
}
|
|
99
|
+
if (!auth.key.startsWith('emp_live_')) {
|
|
100
|
+
throw new UsageError('Geçersiz anahtar biçimi (emp_live_ ile başlamalı).');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── argüman ayrıştırma ────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
const GLOBAL_FLAGS = new Set(['key', 'url', 'profile', 'pretty', 'compact', 'text', 'json-out', 'dry-run', 'output', 'refresh', 'all', 'full', 'module', 'writes', 'reads', 'help', 'key-stdin', 'query', 'json', 'file', 'quiet']);
|
|
107
|
+
const BOOLEAN_FLAGS = new Set(['pretty', 'compact', 'text', 'json-out', 'dry-run', 'refresh', 'all', 'full', 'writes', 'reads', 'help', 'key-stdin', 'quiet']);
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* argv → { positional: [], flags: {global}, fields: [[name, rawValue]] }.
|
|
111
|
+
* `--ad değer`, `--ad=değer`, tekrar eden bayrak → dizi, `--` sonrası hep positional.
|
|
112
|
+
*/
|
|
113
|
+
function parseArgs(argv) {
|
|
114
|
+
const positional = [];
|
|
115
|
+
const flags = { query: [], file: [] };
|
|
116
|
+
const fields = [];
|
|
117
|
+
let i = 0;
|
|
118
|
+
let rest = false;
|
|
119
|
+
|
|
120
|
+
while (i < argv.length) {
|
|
121
|
+
const arg = argv[i];
|
|
122
|
+
if (rest || !arg.startsWith('--') || arg === '--') {
|
|
123
|
+
if (arg === '--' && !rest) { rest = true; i++; continue; }
|
|
124
|
+
positional.push(arg);
|
|
125
|
+
i++;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let name = arg.slice(2);
|
|
130
|
+
let value;
|
|
131
|
+
const eq = name.indexOf('=');
|
|
132
|
+
if (eq !== -1) {
|
|
133
|
+
value = name.slice(eq + 1);
|
|
134
|
+
name = name.slice(0, eq);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (GLOBAL_FLAGS.has(name) && BOOLEAN_FLAGS.has(name)) {
|
|
138
|
+
flags[name] = value === undefined ? true : !['0', 'false', 'no'].includes(value);
|
|
139
|
+
i++;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (value === undefined) {
|
|
144
|
+
const next = argv[i + 1];
|
|
145
|
+
if (next === undefined || (next.startsWith('--') && next.length > 2)) {
|
|
146
|
+
value = 'true'; // değersiz alan bayrağı → true
|
|
147
|
+
i++;
|
|
148
|
+
} else {
|
|
149
|
+
value = next;
|
|
150
|
+
i += 2;
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
i++;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (name === 'query' || name === 'file') {
|
|
157
|
+
flags[name].push(value);
|
|
158
|
+
} else if (GLOBAL_FLAGS.has(name)) {
|
|
159
|
+
flags[name] = value;
|
|
160
|
+
} else {
|
|
161
|
+
fields.push([name, value]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (flags.text) outputMode = 'text';
|
|
166
|
+
if (flags['json-out']) outputMode = 'json';
|
|
167
|
+
if (flags.pretty) prettyFlag = true;
|
|
168
|
+
if (flags.compact) prettyFlag = false;
|
|
169
|
+
|
|
170
|
+
return { positional, flags, fields };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** "true"/"12"/"[1,2]" → JSON değeri; baştaki sıfır ("0532…") ve diğerleri string kalır. */
|
|
174
|
+
function coerce(raw) {
|
|
175
|
+
if (typeof raw !== 'string') return raw;
|
|
176
|
+
if (raw === 'true') return true;
|
|
177
|
+
if (raw === 'false') return false;
|
|
178
|
+
if (raw === 'null') return null;
|
|
179
|
+
if (/^-?(0|[1-9]\d{0,14})(\.\d+)?$/.test(raw)) return Number(raw);
|
|
180
|
+
if ((raw.startsWith('{') && raw.endsWith('}')) || (raw.startsWith('[') && raw.endsWith(']'))) {
|
|
181
|
+
try { return JSON.parse(raw); } catch { /* düz metin */ }
|
|
182
|
+
}
|
|
183
|
+
return raw;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** a.b.0.c=x → {a:{b:[{c:x}]}} ; tekrar eden anahtar → dizi. */
|
|
187
|
+
function setDeep(target, dotted, value) {
|
|
188
|
+
const parts = dotted.replace(/\[\]$/, '').replace(/\[(\w*)\]/g, '.$1').split('.').filter((p) => p !== '');
|
|
189
|
+
let node = target;
|
|
190
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
191
|
+
const key = parts[i];
|
|
192
|
+
const nextIsIndex = /^\d+$/.test(parts[i + 1]);
|
|
193
|
+
if (node[key] === undefined || typeof node[key] !== 'object') node[key] = nextIsIndex ? [] : {};
|
|
194
|
+
node = node[key];
|
|
195
|
+
}
|
|
196
|
+
const last = parts[parts.length - 1];
|
|
197
|
+
const forceArray = dotted.endsWith('[]');
|
|
198
|
+
if (node[last] !== undefined && !Array.isArray(value)) {
|
|
199
|
+
node[last] = Array.isArray(node[last]) ? [...node[last], value] : [node[last], value];
|
|
200
|
+
} else {
|
|
201
|
+
node[last] = forceArray && !Array.isArray(value) ? [value] : value;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function readJsonArg(raw) {
|
|
206
|
+
const text = raw.startsWith('@') ? fs.readFileSync(raw.slice(1), 'utf8') : raw;
|
|
207
|
+
try {
|
|
208
|
+
return JSON.parse(text);
|
|
209
|
+
} catch (e) {
|
|
210
|
+
throw new UsageError(`--json geçerli JSON değil: ${e.message}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ─── HTTP ──────────────────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
let versionWarned = false;
|
|
217
|
+
|
|
218
|
+
function checkServerVersion(res) {
|
|
219
|
+
const latest = res.headers.get('x-empuno-cli-version');
|
|
220
|
+
if (!versionWarned && latest && compareVersions(latest, VERSION) > 0) {
|
|
221
|
+
versionWarned = true;
|
|
222
|
+
warn(`yeni CLI sürümü var (${VERSION} → ${latest}): ${installedViaNpm() ? `npm i -g ${NPM_PACKAGE}@latest` : 'empuno update'}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function compareVersions(a, b) {
|
|
227
|
+
const pa = a.split('.').map(Number);
|
|
228
|
+
const pb = b.split('.').map(Number);
|
|
229
|
+
for (let i = 0; i < 3; i++) {
|
|
230
|
+
if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0);
|
|
231
|
+
}
|
|
232
|
+
return 0;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function request(auth, method, urlPath, { query = {}, body = null, files = [], output = null, dryRun = false } = {}) {
|
|
236
|
+
const url = new URL(urlPath.startsWith('http') ? urlPath : auth.url + urlPath);
|
|
237
|
+
for (const [k, v] of Object.entries(query)) {
|
|
238
|
+
if (Array.isArray(v)) v.forEach((item) => url.searchParams.append(`${k}[]`, String(item)));
|
|
239
|
+
else if (v !== undefined && v !== null) url.searchParams.set(k, typeof v === 'object' ? JSON.stringify(v) : String(v));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const headers = {
|
|
243
|
+
Authorization: `Bearer ${auth.key}`,
|
|
244
|
+
Accept: 'application/json',
|
|
245
|
+
'User-Agent': `empuno-cli/${VERSION} node/${process.versions.node}`,
|
|
246
|
+
'X-Empuno-Cli': VERSION,
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
let sendMethod = method;
|
|
250
|
+
let payload;
|
|
251
|
+
|
|
252
|
+
if (files.length > 0) {
|
|
253
|
+
const form = new FormData();
|
|
254
|
+
appendForm(form, body || {});
|
|
255
|
+
for (const { field, file } of files) {
|
|
256
|
+
const buffer = fs.readFileSync(file);
|
|
257
|
+
form.append(field, new Blob([buffer]), path.basename(file));
|
|
258
|
+
}
|
|
259
|
+
// PHP, PUT/PATCH multipart gövdesini ayrıştırmaz → POST + _method.
|
|
260
|
+
if (!['GET', 'POST'].includes(method)) {
|
|
261
|
+
form.append('_method', method);
|
|
262
|
+
sendMethod = 'POST';
|
|
263
|
+
}
|
|
264
|
+
payload = form;
|
|
265
|
+
} else if (body && Object.keys(body).length > 0 && method !== 'GET') {
|
|
266
|
+
headers['Content-Type'] = 'application/json';
|
|
267
|
+
payload = JSON.stringify(body);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (dryRun) {
|
|
271
|
+
return {
|
|
272
|
+
dryRun: true,
|
|
273
|
+
result: {
|
|
274
|
+
method: sendMethod,
|
|
275
|
+
url: url.toString(),
|
|
276
|
+
headers: { ...headers, Authorization: `Bearer ${maskKey(auth.key)}` },
|
|
277
|
+
body: files.length ? { fields: body, files } : body,
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let res;
|
|
283
|
+
try {
|
|
284
|
+
res = await fetch(url, { method: sendMethod, headers, body: payload, redirect: 'manual' });
|
|
285
|
+
} catch (e) {
|
|
286
|
+
const err = new Error(`ağ hatası: ${e.cause?.message || e.message}`);
|
|
287
|
+
err.exitCode = 3;
|
|
288
|
+
throw err;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
checkServerVersion(res);
|
|
292
|
+
|
|
293
|
+
const type = res.headers.get('content-type') || '';
|
|
294
|
+
let result;
|
|
295
|
+
|
|
296
|
+
if (type.includes('application/json')) {
|
|
297
|
+
const text = await res.text();
|
|
298
|
+
try { result = JSON.parse(text); } catch { result = { ok: false, error: 'invalid_json', body: text.slice(0, 2000) }; }
|
|
299
|
+
} else if (res.status >= 300 && res.status < 400) {
|
|
300
|
+
result = { ok: false, error: 'unexpected_redirect', status: res.status, location: res.headers.get('location') };
|
|
301
|
+
} else if (res.ok && (output || isDownload(res, type))) {
|
|
302
|
+
result = await saveDownload(res, type, output);
|
|
303
|
+
} else {
|
|
304
|
+
const text = await res.text();
|
|
305
|
+
result = { ok: res.ok, status: res.status, content_type: type, body: text.slice(0, 4000) };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return { status: res.status, ok: res.ok, result };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function appendForm(form, value, prefix = '') {
|
|
312
|
+
if (value === null || value === undefined) return;
|
|
313
|
+
if (Array.isArray(value)) {
|
|
314
|
+
value.forEach((item, i) => appendForm(form, item, `${prefix}[${i}]`));
|
|
315
|
+
} else if (typeof value === 'object') {
|
|
316
|
+
for (const [k, v] of Object.entries(value)) appendForm(form, v, prefix ? `${prefix}[${k}]` : k);
|
|
317
|
+
} else {
|
|
318
|
+
form.append(prefix, typeof value === 'boolean' ? (value ? '1' : '0') : String(value));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function isDownload(res, type) {
|
|
323
|
+
const disposition = res.headers.get('content-disposition') || '';
|
|
324
|
+
return disposition.includes('attachment') || /pdf|octet-stream|spreadsheet|excel|zip|image\//.test(type);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function saveDownload(res, type, output) {
|
|
328
|
+
const disposition = res.headers.get('content-disposition') || '';
|
|
329
|
+
const match = /filename\*=UTF-8''([^;]+)|filename="?([^";]+)"?/i.exec(disposition);
|
|
330
|
+
const suggested = match ? decodeURIComponent(match[1] || match[2]) : `empuno-${Date.now()}${extensionFor(type)}`;
|
|
331
|
+
let target = output || suggested;
|
|
332
|
+
if (output && fs.existsSync(output) && fs.statSync(output).isDirectory()) target = path.join(output, path.basename(suggested));
|
|
333
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
334
|
+
fs.writeFileSync(target, buffer);
|
|
335
|
+
return { ok: true, saved: path.resolve(target), bytes: buffer.length, content_type: type };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function extensionFor(type) {
|
|
339
|
+
if (type.includes('pdf')) return '.pdf';
|
|
340
|
+
if (type.includes('spreadsheet') || type.includes('excel')) return '.xlsx';
|
|
341
|
+
if (type.includes('csv')) return '.csv';
|
|
342
|
+
if (type.includes('html')) return '.html';
|
|
343
|
+
if (type.includes('png')) return '.png';
|
|
344
|
+
if (type.includes('jpeg')) return '.jpg';
|
|
345
|
+
return '.bin';
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ─── katalog ───────────────────────────────────────────────────────────────
|
|
349
|
+
|
|
350
|
+
async function loadCatalog(auth, { refresh = false, all = false } = {}) {
|
|
351
|
+
const id = crypto.createHash('sha256').update(`${auth.url}|${auth.key}|${all}`).digest('hex').slice(0, 16);
|
|
352
|
+
const file = path.join(cacheDir(), `catalog-${id}.json`);
|
|
353
|
+
|
|
354
|
+
if (!refresh) {
|
|
355
|
+
try {
|
|
356
|
+
const stat = fs.statSync(file);
|
|
357
|
+
if (Date.now() - stat.mtimeMs < CATALOG_TTL_MS) return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
358
|
+
} catch { /* önbellek yok */ }
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const { ok, result } = await request(auth, 'GET', '/api/company/v1/catalog', { query: all ? { all: 1 } : {} });
|
|
362
|
+
if (!ok) {
|
|
363
|
+
const err = new Error(result?.message || 'katalog alınamadı');
|
|
364
|
+
err.payload = result;
|
|
365
|
+
err.exitCode = 1;
|
|
366
|
+
throw err;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
371
|
+
fs.writeFileSync(file, JSON.stringify(result), { mode: 0o600 });
|
|
372
|
+
} catch { /* önbellek yazılamazsa sorun değil */ }
|
|
373
|
+
|
|
374
|
+
return result;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async function findCommand(auth, name, flags) {
|
|
378
|
+
const catalog = await loadCatalog(auth, { refresh: flags.refresh });
|
|
379
|
+
let cmd = catalog.commands.find((c) => c.command === name);
|
|
380
|
+
if (cmd) return cmd;
|
|
381
|
+
|
|
382
|
+
if (catalog.access === 'read') {
|
|
383
|
+
const err = new UsageError('bu anahtar salt-okuma ("read") türünde: panel komutlarını çalıştıramaz, yalnız `empuno api …`. Firma Ayarları → API sekmesinden "Ajan" anahtarı oluşturun.');
|
|
384
|
+
err.payload = { ok: false, error: 'read_only_key' };
|
|
385
|
+
throw err;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Önbellek eskiyse bir kez tazele; yoksa kapsam dışı mı, hiç yok mu söyle.
|
|
389
|
+
const full = await loadCatalog(auth, { refresh: true, all: true });
|
|
390
|
+
cmd = full.commands.find((c) => c.command === name);
|
|
391
|
+
if (cmd && cmd.allowed) return cmd;
|
|
392
|
+
|
|
393
|
+
if (cmd) {
|
|
394
|
+
const need = cmd.requires_company_admin ? ['company_admin'] : cmd.permissions;
|
|
395
|
+
const err = new UsageError(`"${name}" bu anahtarın kapsamında değil. Gereken yetki: ${need.join(', ') || '—'}. Anahtar kapsamını panelden genişletin (Firma Ayarları → API).`);
|
|
396
|
+
err.payload = { ok: false, error: 'scope_required', command: name, required: need };
|
|
397
|
+
throw err;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const suggestions = full.commands
|
|
401
|
+
.map((c) => c.command)
|
|
402
|
+
.filter((c) => c.includes(name.split('.')[0]) || name.includes(c.split('.')[0]))
|
|
403
|
+
.slice(0, 8);
|
|
404
|
+
const err = new UsageError(`bilinmeyen komut "${name}".${suggestions.length ? ` Şunlardan biri mi: ${suggestions.join(', ')}` : ' `empuno commands` ile listeleyin.'}`);
|
|
405
|
+
err.payload = { ok: false, error: 'unknown_command', command: name, suggestions };
|
|
406
|
+
throw err;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ─── alt komutlar ──────────────────────────────────────────────────────────
|
|
410
|
+
|
|
411
|
+
async function cmdLogin(args) {
|
|
412
|
+
const { flags } = args;
|
|
413
|
+
let key = flags.key || process.env.EMPUNO_API_KEY || args.positional[0];
|
|
414
|
+
|
|
415
|
+
if (flags['key-stdin'] || (!key && !process.stdin.isTTY)) {
|
|
416
|
+
key = fs.readFileSync(0, 'utf8').trim();
|
|
417
|
+
} else if (!key) {
|
|
418
|
+
key = await prompt('API anahtarı (emp_live_…): ');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const url = (flags.url || process.env.EMPUNO_URL || DEFAULT_URL).replace(/\/+$/, '');
|
|
422
|
+
const profileName = flags.profile || 'default';
|
|
423
|
+
const auth = { key: key?.trim(), url, profileName };
|
|
424
|
+
requireKey(auth);
|
|
425
|
+
|
|
426
|
+
const { ok, result } = await request(auth, 'GET', '/api/company/v1/me');
|
|
427
|
+
if (!ok) {
|
|
428
|
+
print(result);
|
|
429
|
+
return 1;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const cfg = readConfig();
|
|
433
|
+
cfg.profiles = cfg.profiles || {};
|
|
434
|
+
cfg.profiles[profileName] = { url, key: auth.key };
|
|
435
|
+
if (!cfg.default || !cfg.profiles[cfg.default]) cfg.default = profileName;
|
|
436
|
+
writeConfig(cfg);
|
|
437
|
+
|
|
438
|
+
print({ ok: true, profile: profileName, config: configPath(), company: result.company, access: result.api_key?.access, acts_as: result.agent?.acts_as ?? null });
|
|
439
|
+
if (result.api_key?.access !== 'agent') {
|
|
440
|
+
warn('bu anahtar salt-okuma ("read") türünde: yalnız `empuno api …` uçları çalışır. Panel işlemleri için "Ajan" anahtarı oluşturun.');
|
|
441
|
+
}
|
|
442
|
+
return 0;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function prompt(question) {
|
|
446
|
+
return new Promise((resolve) => {
|
|
447
|
+
process.stderr.write(question);
|
|
448
|
+
let data = '';
|
|
449
|
+
process.stdin.setEncoding('utf8');
|
|
450
|
+
process.stdin.on('data', (chunk) => {
|
|
451
|
+
data += chunk;
|
|
452
|
+
if (data.includes('\n')) {
|
|
453
|
+
process.stdin.pause();
|
|
454
|
+
resolve(data.split('\n')[0]);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function cmdLogout(args) {
|
|
461
|
+
const cfg = readConfig();
|
|
462
|
+
const name = args.flags.profile || cfg.default || 'default';
|
|
463
|
+
const existed = Boolean(cfg.profiles?.[name]);
|
|
464
|
+
if (existed) delete cfg.profiles[name];
|
|
465
|
+
if (cfg.default === name) cfg.default = Object.keys(cfg.profiles || {})[0] || 'default';
|
|
466
|
+
writeConfig(cfg);
|
|
467
|
+
print({ ok: true, removed_profile: existed ? name : null, note: 'Anahtar sunucuda hâlâ geçerli; tamamen kapatmak için panelden iptal edin.' });
|
|
468
|
+
return 0;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function cmdWhoami(args) {
|
|
472
|
+
const auth = resolveAuth(args.flags);
|
|
473
|
+
requireKey(auth);
|
|
474
|
+
const { ok, result } = await request(auth, 'GET', '/api/company/v1/me');
|
|
475
|
+
print({ ...result, cli: { version: VERSION, profile: auth.profileName, url: auth.url, key: maskKey(auth.key) } });
|
|
476
|
+
return ok ? 0 : 1;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async function cmdCommands(args) {
|
|
480
|
+
const { flags, positional } = args;
|
|
481
|
+
const auth = resolveAuth(flags);
|
|
482
|
+
requireKey(auth);
|
|
483
|
+
const catalog = await loadCatalog(auth, { refresh: flags.refresh, all: flags.all });
|
|
484
|
+
const needle = positional.join(' ').toLocaleLowerCase('tr');
|
|
485
|
+
|
|
486
|
+
let list = catalog.commands;
|
|
487
|
+
if (flags.module) list = list.filter((c) => c.module === flags.module);
|
|
488
|
+
if (flags.writes) list = list.filter((c) => c.kind === 'write');
|
|
489
|
+
if (flags.reads) list = list.filter((c) => c.kind === 'read');
|
|
490
|
+
if (needle) {
|
|
491
|
+
list = list.filter((c) => `${c.command} ${c.title || ''} ${c.summary} ${c.description || ''} ${c.module} ${c.module_label || ''}`.toLocaleLowerCase('tr').includes(needle));
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (wantsText()) {
|
|
495
|
+
let currentModule = null;
|
|
496
|
+
for (const c of [...list].sort((a, b) => a.module.localeCompare(b.module) || a.command.localeCompare(b.command))) {
|
|
497
|
+
if (c.module !== currentModule) {
|
|
498
|
+
currentModule = c.module;
|
|
499
|
+
process.stdout.write(`\n[${c.module}] ${c.module_label || ''}\n`);
|
|
500
|
+
}
|
|
501
|
+
const lock = c.allowed === false ? ' (kapsam dışı)' : '';
|
|
502
|
+
process.stdout.write(` ${c.command.padEnd(44)} ${c.method.padEnd(6)} ${c.title || c.summary}${lock}\n`);
|
|
503
|
+
}
|
|
504
|
+
process.stdout.write(`\n${list.length} komut. Ayrıntı: empuno describe <komut>\n`);
|
|
505
|
+
return 0;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
print({
|
|
509
|
+
count: list.length,
|
|
510
|
+
commands: flags.full ? list : list.map((c) => ({ command: c.command, method: c.method, path: c.path, kind: c.kind, module: c.module, title: c.title, ...(c.summary !== c.title ? { summary: c.summary } : {}), ...(c.allowed === false ? { allowed: false } : {}) })),
|
|
511
|
+
hint: 'Ayrıntı (parametreler, alanlar, yetki): empuno describe <komut>',
|
|
512
|
+
});
|
|
513
|
+
return 0;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async function cmdDescribe(args) {
|
|
517
|
+
const name = args.positional[0];
|
|
518
|
+
if (!name) throw new UsageError('kullanım: empuno describe <komut>');
|
|
519
|
+
const auth = resolveAuth(args.flags);
|
|
520
|
+
requireKey(auth);
|
|
521
|
+
let cmd;
|
|
522
|
+
try {
|
|
523
|
+
cmd = await findCommand(auth, name, args.flags);
|
|
524
|
+
} catch (e) {
|
|
525
|
+
// Kapsam dışı komut da açıklanır: ajan, kullanıcıdan hangi yetkiyi isteyeceğini bilsin.
|
|
526
|
+
if (e.payload?.error !== 'scope_required') throw e;
|
|
527
|
+
const full = await loadCatalog(auth, { all: true });
|
|
528
|
+
cmd = full.commands.find((c) => c.command === name);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const example = ['empuno', cmd.command, ...cmd.path_params.map((p) => `<${p}>`)];
|
|
532
|
+
for (const [field, rule] of Object.entries(cmd.fields || {}).slice(0, 4)) {
|
|
533
|
+
if (field.includes('*')) continue;
|
|
534
|
+
example.push(`--${field}`, /file|image|mimes/.test(rule) ? '@./dosya.pdf' : '<değer>');
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
print({
|
|
538
|
+
...cmd,
|
|
539
|
+
usage: example.join(' '),
|
|
540
|
+
notes: [
|
|
541
|
+
cmd.allowed === false ? `Bu anahtar bu komutu çalıştıramaz; gereken yetki: ${(cmd.requires_company_admin ? ['company_admin'] : cmd.permissions).join(', ')}.` : null,
|
|
542
|
+
cmd.kind === 'read' ? 'Okuma komutu: alan bayrakları sorgu parametresi olarak gider (?per_page=…).' : 'Yazma komutu: alan bayrakları JSON gövdesi olarak gider; dosya için --alan @yol.',
|
|
543
|
+
cmd.fields ? 'fields: sunucunun doğrulama kuralları (best-effort).' : 'fields bilinmiyor: eksik alanlar 422 yanıtının `errors` nesnesinde listelenir.',
|
|
544
|
+
cmd.path_params.length ? `Yol parametreleri sırayla positional verilir: ${cmd.path_params.join(', ')}.` : null,
|
|
545
|
+
].filter(Boolean),
|
|
546
|
+
});
|
|
547
|
+
return 0;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async function cmdRun(args, name) {
|
|
551
|
+
const { flags, fields } = args;
|
|
552
|
+
const auth = resolveAuth(flags);
|
|
553
|
+
requireKey(auth);
|
|
554
|
+
const cmd = await findCommand(auth, name, flags);
|
|
555
|
+
|
|
556
|
+
// Yol parametreleri: önce positional, sonra aynı adlı bayrak.
|
|
557
|
+
const positional = [...args.positional];
|
|
558
|
+
const named = new Map();
|
|
559
|
+
const bodyFields = [];
|
|
560
|
+
for (const [k, v] of fields) {
|
|
561
|
+
if (cmd.path_params.includes(k)) named.set(k, v);
|
|
562
|
+
else bodyFields.push([k, v]);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
let urlPath = cmd.path;
|
|
566
|
+
for (const param of cmd.path_params) {
|
|
567
|
+
const value = named.get(param) ?? positional.shift();
|
|
568
|
+
if (value === undefined) throw new UsageError(`eksik yol parametresi: ${param} (kullanım: empuno describe ${cmd.command})`);
|
|
569
|
+
urlPath = urlPath.replace(new RegExp(`\\{${param}\\??\\}`), encodeURIComponent(value));
|
|
570
|
+
}
|
|
571
|
+
if (positional.length) throw new UsageError(`fazla argüman: ${positional.join(' ')}`);
|
|
572
|
+
|
|
573
|
+
return execute(auth, cmd.method, urlPath, flags, bodyFields);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
async function cmdCall(args) {
|
|
577
|
+
const [method, urlPath, ...extra] = args.positional;
|
|
578
|
+
if (!method || !urlPath || extra.length) throw new UsageError('kullanım: empuno call <METHOD> </manager/yol> [--alan değer …]');
|
|
579
|
+
const auth = resolveAuth(args.flags);
|
|
580
|
+
requireKey(auth);
|
|
581
|
+
return execute(auth, method.toUpperCase(), urlPath.startsWith('/') ? urlPath : `/${urlPath}`, args.flags, args.fields);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async function cmdApi(args) {
|
|
585
|
+
const [sub, ...extra] = args.positional;
|
|
586
|
+
if (!sub || extra.length) throw new UsageError('kullanım: empuno api <leaves|expenses|employees|…> [--status approved …]');
|
|
587
|
+
const auth = resolveAuth(args.flags);
|
|
588
|
+
requireKey(auth);
|
|
589
|
+
return execute(auth, 'GET', `/api/company/v1/${sub.replace(/^\/+/, '')}`, args.flags, args.fields);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
async function execute(auth, method, urlPath, flags, fieldPairs) {
|
|
593
|
+
let body = flags.json ? readJsonArg(flags.json) : {};
|
|
594
|
+
const files = [];
|
|
595
|
+
|
|
596
|
+
for (const raw of flags.file) {
|
|
597
|
+
const eq = raw.indexOf('=');
|
|
598
|
+
if (eq === -1) throw new UsageError('--file alan=yol biçiminde olmalı');
|
|
599
|
+
files.push({ field: raw.slice(0, eq), file: raw.slice(eq + 1).replace(/^@/, '') });
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
for (const [name, raw] of fieldPairs) {
|
|
603
|
+
if (typeof raw === 'string' && raw.startsWith('@') && raw.length > 1 && fs.existsSync(raw.slice(1))) {
|
|
604
|
+
files.push({ field: name, file: raw.slice(1) });
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
setDeep(body, name, coerce(raw));
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
for (const { file } of files) {
|
|
611
|
+
if (!fs.existsSync(file)) throw new UsageError(`dosya bulunamadı: ${file}`);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const query = {};
|
|
615
|
+
for (const raw of flags.query) {
|
|
616
|
+
const eq = raw.indexOf('=');
|
|
617
|
+
if (eq === -1) throw new UsageError('--query anahtar=değer biçiminde olmalı');
|
|
618
|
+
setDeep(query, raw.slice(0, eq), coerce(raw.slice(eq + 1)));
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// GET'te alanlar sorguya gider.
|
|
622
|
+
if (method === 'GET' && Object.keys(body).length) {
|
|
623
|
+
Object.assign(query, body);
|
|
624
|
+
body = {};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const response = await request(auth, method, urlPath, { query, body, files, output: flags.output, dryRun: flags['dry-run'] });
|
|
628
|
+
print(response.result);
|
|
629
|
+
if (response.dryRun) return 0;
|
|
630
|
+
return response.ok ? 0 : 1;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/** npm ile kurulduysa (global ya da npx önbelleği) dosya node_modules altındadır. */
|
|
634
|
+
function installedViaNpm() {
|
|
635
|
+
try {
|
|
636
|
+
return fs.realpathSync(process.argv[1]).split(path.sep).includes('node_modules');
|
|
637
|
+
} catch {
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async function cmdUpdate(args) {
|
|
643
|
+
if (installedViaNpm()) {
|
|
644
|
+
// npm'in yönettiği dosyanın üzerine yazmak sürüm kaydını bozar.
|
|
645
|
+
print({ ok: false, error: 'managed_by_npm', version: VERSION, hint: `npm i -g ${NPM_PACKAGE}@latest` });
|
|
646
|
+
return 1;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const auth = resolveAuth(args.flags);
|
|
650
|
+
const target = fs.realpathSync(process.argv[1]);
|
|
651
|
+
const res = await fetch(`${auth.url}/cli/empuno.mjs`, { headers: { 'User-Agent': `empuno-cli/${VERSION}` } });
|
|
652
|
+
if (!res.ok) {
|
|
653
|
+
print({ ok: false, error: 'download_failed', status: res.status });
|
|
654
|
+
return 1;
|
|
655
|
+
}
|
|
656
|
+
const text = await res.text();
|
|
657
|
+
const match = /const VERSION = '([^']+)'/.exec(text);
|
|
658
|
+
if (!text.startsWith('#!/usr/bin/env node') || !match) {
|
|
659
|
+
print({ ok: false, error: 'invalid_download' });
|
|
660
|
+
return 1;
|
|
661
|
+
}
|
|
662
|
+
if (compareVersions(match[1], VERSION) <= 0 && !args.flags.all) {
|
|
663
|
+
print({ ok: true, updated: false, version: VERSION, note: 'Zaten güncel.' });
|
|
664
|
+
return 0;
|
|
665
|
+
}
|
|
666
|
+
const mode = fs.statSync(target).mode;
|
|
667
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
668
|
+
fs.writeFileSync(tmp, text, { mode });
|
|
669
|
+
fs.renameSync(tmp, target);
|
|
670
|
+
print({ ok: true, updated: true, from: VERSION, to: match[1], path: target });
|
|
671
|
+
return 0;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const HELP = `empuno ${VERSION} — empuno İK sistemini komut satırından (ve ajanlardan) yönetin
|
|
675
|
+
|
|
676
|
+
KURULUM
|
|
677
|
+
npm i -g empuno # ya da kurmadan: npx empuno <komut>
|
|
678
|
+
curl -fsSL https://empuno.com/cli/empuno.mjs -o ~/.local/bin/empuno && chmod +x ~/.local/bin/empuno # npm'siz
|
|
679
|
+
empuno login # anahtar: panel → Firma Ayarları → API → "Ajan" anahtarı
|
|
680
|
+
echo "$KEY" | empuno login --key-stdin # geçmişe düşmeden
|
|
681
|
+
|
|
682
|
+
KEŞİF
|
|
683
|
+
empuno whoami # firma, anahtar türü, kimin adına çalıştığı
|
|
684
|
+
empuno commands [ara] [--module leaves] [--writes|--reads] [--all] [--full]
|
|
685
|
+
empuno describe <komut> # yöntem, yol, yol parametreleri, alanlar/kurallar, gereken yetki
|
|
686
|
+
|
|
687
|
+
ÇALIŞTIRMA (komut = panel rotası; listeden)
|
|
688
|
+
empuno leave-requests.index --status pending --per_page 50
|
|
689
|
+
empuno leave-requests.approve 123
|
|
690
|
+
empuno leave-requests.reject 123 --rejection_reason "Yoğun dönem"
|
|
691
|
+
empuno employees.documents.store 45 --document_type is_sozlesmesi --file file=./sozlesme.pdf
|
|
692
|
+
empuno leave-reports.download 12 --output ./rapor.pdf # dosya indir
|
|
693
|
+
empuno call POST /manager/leave-requests/123/approve # ham çağrı
|
|
694
|
+
empuno api leaves --status approved # salt-okuma Company API
|
|
695
|
+
|
|
696
|
+
ALANLAR
|
|
697
|
+
--ad değer | --ad=değer # true/false/null/sayı/JSON otomatik; baştaki sıfır string kalır
|
|
698
|
+
--dizi a --dizi b | --dizi[] a # dizi; --a.b.0.c x → iç içe nesne
|
|
699
|
+
--json '{"..."}' | --json @govde.json
|
|
700
|
+
--alan @./dosya.pdf | --file alan=./dosya.pdf # multipart yükleme
|
|
701
|
+
--query anahtar=değer # yazma isteğinde ek sorgu parametresi
|
|
702
|
+
|
|
703
|
+
GENEL BAYRAKLAR
|
|
704
|
+
--profile ad --key emp_live_… --url https://empuno.com --dry-run --output yol
|
|
705
|
+
--text | --json-out --pretty | --compact --refresh (katalog önbelleğini tazele)
|
|
706
|
+
|
|
707
|
+
ORTAM: EMPUNO_API_KEY, EMPUNO_URL, EMPUNO_PROFILE, EMPUNO_CONFIG
|
|
708
|
+
ÇIKTI: stdout her zaman JSON (TTY'de girintili). Çıkış kodu 0 başarı, 1 API hatası, 2 kullanım, 3 ağ.
|
|
709
|
+
YANIT: okuma → {ok, page, data}; yazma → {ok, message, redirect:{command, params}} veya 422 {ok:false, message, errors}
|
|
710
|
+
BELGE: https://empuno.com/api-docs/agent
|
|
711
|
+
`;
|
|
712
|
+
|
|
713
|
+
// ─── giriş noktası ─────────────────────────────────────────────────────────
|
|
714
|
+
|
|
715
|
+
async function main() {
|
|
716
|
+
const argv = process.argv.slice(2);
|
|
717
|
+
const args = parseArgs(argv);
|
|
718
|
+
const [sub, ...rest] = args.positional;
|
|
719
|
+
args.positional = rest;
|
|
720
|
+
|
|
721
|
+
if (!sub || sub === 'help' || (args.flags.help && !sub)) {
|
|
722
|
+
process.stdout.write(HELP);
|
|
723
|
+
return 0;
|
|
724
|
+
}
|
|
725
|
+
if (args.flags.help) {
|
|
726
|
+
args.positional = [sub];
|
|
727
|
+
return cmdDescribe(args);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
switch (sub) {
|
|
731
|
+
case 'version':
|
|
732
|
+
case '--version':
|
|
733
|
+
print({ version: VERSION, node: process.versions.node });
|
|
734
|
+
return 0;
|
|
735
|
+
case 'login':
|
|
736
|
+
return cmdLogin(args);
|
|
737
|
+
case 'logout':
|
|
738
|
+
return cmdLogout(args);
|
|
739
|
+
case 'whoami':
|
|
740
|
+
case 'me':
|
|
741
|
+
return cmdWhoami(args);
|
|
742
|
+
case 'commands':
|
|
743
|
+
case 'ls':
|
|
744
|
+
return cmdCommands(args);
|
|
745
|
+
case 'describe':
|
|
746
|
+
return cmdDescribe(args);
|
|
747
|
+
case 'call':
|
|
748
|
+
return cmdCall(args);
|
|
749
|
+
case 'api':
|
|
750
|
+
return cmdApi(args);
|
|
751
|
+
case 'update':
|
|
752
|
+
return cmdUpdate(args);
|
|
753
|
+
case 'run':
|
|
754
|
+
if (!args.positional[0]) throw new UsageError('kullanım: empuno run <komut> …');
|
|
755
|
+
return cmdRun({ ...args, positional: args.positional.slice(1) }, args.positional[0]);
|
|
756
|
+
default:
|
|
757
|
+
return cmdRun(args, sub);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
main()
|
|
762
|
+
.then((code) => { process.exitCode = code ?? 0; })
|
|
763
|
+
.catch((e) => {
|
|
764
|
+
if (e instanceof UsageError) {
|
|
765
|
+
if (e.payload) print(e.payload);
|
|
766
|
+
warn(e.message);
|
|
767
|
+
process.exitCode = 2;
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
if (e.payload) print(e.payload);
|
|
771
|
+
else print({ ok: false, error: e.exitCode === 3 ? 'network_error' : 'cli_error', message: e.message });
|
|
772
|
+
warn(e.message);
|
|
773
|
+
process.exitCode = e.exitCode || 1;
|
|
774
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "empuno",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "empuno İK platformunun komut satırı: yönetim panelindeki tüm işlemleri (izin, bordro, vardiya, PDKS, zimmet, masraf, işe alım) yapay zekâ ajanlarına ve otomasyonlara açar.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"empuno": "empuno.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"empuno.mjs",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"empuno",
|
|
19
|
+
"cli",
|
|
20
|
+
"ai-agent",
|
|
21
|
+
"hr",
|
|
22
|
+
"ik",
|
|
23
|
+
"insan-kaynaklari",
|
|
24
|
+
"bordro",
|
|
25
|
+
"izin",
|
|
26
|
+
"pdks",
|
|
27
|
+
"turkey"
|
|
28
|
+
],
|
|
29
|
+
"homepage": "https://empuno.com/api-docs/agent",
|
|
30
|
+
"author": "Lutfios LLC (https://empuno.com)",
|
|
31
|
+
"license": "MIT"
|
|
32
|
+
}
|