@synapseone/cli 0.0.0-stage → 0.7.0-dev.41
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/README.md +42 -2
- package/bin/synapseone.js +973 -0
- package/package.json +40 -4
package/README.md
CHANGED
|
@@ -1,3 +1,43 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `synapseone` CLI — zero-dependency command line (Node 18+)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Apache-2.0. Part of [SynapseOne](../../README.md) Tier-1 PaaS. No `npm install` needed —
|
|
4
|
+
single file, only Node's built-in `fetch`.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
# one-line installer (installs @synapseone/cli@latest globally)
|
|
10
|
+
curl -fsSL https://synapseone.space/install.sh | sh
|
|
11
|
+
|
|
12
|
+
# ..or from the registry directly:
|
|
13
|
+
npm install -g @synapseone/cli@latest
|
|
14
|
+
|
|
15
|
+
# ..or run without installing anything:
|
|
16
|
+
npx -y @synapseone/cli@latest --help
|
|
17
|
+
|
|
18
|
+
# ..or from source:
|
|
19
|
+
node tools/cli/bin/synapseone.js --help
|
|
20
|
+
# or link globally:
|
|
21
|
+
npm link ./tools/cli # then call `synapseone ...` anywhere
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
export SYNAPSEONE_BASE_URL=http://localhost:8080
|
|
28
|
+
synapseone login --email demo@acme.local
|
|
29
|
+
synapseone orgs
|
|
30
|
+
synapseone accounts create prod Production
|
|
31
|
+
synapseone authz <user-id> workflow:Execute "*" --context team=etl
|
|
32
|
+
synapseone workflows create --template webhook-to-log --name demo
|
|
33
|
+
synapseone audit --limit 20
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Against the live demo: add `--base https://api.synapseone.space` (or
|
|
37
|
+
`export SYNAPSEONE_BASE_URL=https://api.synapseone.space`) and sign in with an API key
|
|
38
|
+
(`synapseone use-key <key>`).
|
|
39
|
+
|
|
40
|
+
## Full documentation
|
|
41
|
+
|
|
42
|
+
See **[docs/cli.md](../../docs/cli.md)** for the complete CLI reference —
|
|
43
|
+
configuration, global flags, all commands with usage, parameters, and examples.
|
|
@@ -0,0 +1,973 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* Copyright 2026 SynapseOne Contributors
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* synapseone CLI — zero dependencies, Node 18+ (global fetch).
|
|
6
|
+
*/
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const VERSION = (() => {
|
|
14
|
+
try {
|
|
15
|
+
return require('../package.json').version || '0.0.0';
|
|
16
|
+
} catch {
|
|
17
|
+
return '0.0.0';
|
|
18
|
+
}
|
|
19
|
+
})();
|
|
20
|
+
const CONF_DIR = path.join(os.homedir(), '.synapseone');
|
|
21
|
+
const CONF_FILE = path.join(CONF_DIR, 'config.json');
|
|
22
|
+
// Pre-rebrand config location; read-only fallback so existing logins survive.
|
|
23
|
+
const LEGACY_CONF_FILE = path.join(os.homedir(), '.synapsedx', 'config.json');
|
|
24
|
+
|
|
25
|
+
// ---------- config ----------
|
|
26
|
+
function loadConfig() {
|
|
27
|
+
for (const file of [CONF_FILE, LEGACY_CONF_FILE]) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
30
|
+
} catch {
|
|
31
|
+
/* try next location */
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
function saveConfig(cfg) {
|
|
37
|
+
fs.mkdirSync(CONF_DIR, { recursive: true, mode: 0o700 });
|
|
38
|
+
// Credentials on disk: owner-only (0600). Token/API key in this file is a
|
|
39
|
+
// bearer secret — never print it (see cmdConfig redaction below).
|
|
40
|
+
fs.writeFileSync(CONF_FILE, JSON.stringify(cfg, null, 2), { mode: 0o600 });
|
|
41
|
+
try { fs.chmodSync(CONF_FILE, 0o600); } catch { /* non-POSIX (win) */ }
|
|
42
|
+
}
|
|
43
|
+
function baseUrl(opts) {
|
|
44
|
+
return (opts.base || process.env.SYNAPSEONE_BASE_URL || process.env.SYNAPSEDX_BASE_URL || loadConfig().baseUrl || 'http://localhost:8080').replace(/\/$/, '');
|
|
45
|
+
}
|
|
46
|
+
function authHeaders(opts) {
|
|
47
|
+
const cfg = loadConfig();
|
|
48
|
+
const token = opts.token || process.env.SYNAPSEONE_TOKEN || process.env.SYNAPSEDX_TOKEN || cfg.token;
|
|
49
|
+
const apiKey = opts.apikey || process.env.SYNAPSEONE_API_KEY || process.env.SYNAPSEDX_API_KEY || cfg.apiKey;
|
|
50
|
+
const h = { 'Content-Type': 'application/json', Connection: 'close' };
|
|
51
|
+
if (token) h.Authorization = `Bearer ${token}`;
|
|
52
|
+
if (apiKey) h['X-API-Key'] = apiKey;
|
|
53
|
+
return h;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---------- http ----------
|
|
57
|
+
async function req(method, p, body, opts) {
|
|
58
|
+
let r;
|
|
59
|
+
try {
|
|
60
|
+
r = await fetch(baseUrl(opts) + p, {
|
|
61
|
+
method,
|
|
62
|
+
headers: authHeaders(opts),
|
|
63
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
64
|
+
});
|
|
65
|
+
} catch (e) {
|
|
66
|
+
throw new Error(`network ${p}: ${e.message || e}`);
|
|
67
|
+
}
|
|
68
|
+
const text = await r.text();
|
|
69
|
+
let data = null;
|
|
70
|
+
try {
|
|
71
|
+
data = text ? JSON.parse(text) : null;
|
|
72
|
+
} catch {
|
|
73
|
+
data = text;
|
|
74
|
+
}
|
|
75
|
+
if (!r.ok) throw httpError(r.status, p, data);
|
|
76
|
+
return data;
|
|
77
|
+
}
|
|
78
|
+
function httpError(status, p, data) {
|
|
79
|
+
// Clear messages for the two common LDS failures: reserved NGSI-LD
|
|
80
|
+
// attribute names (400 reserved_attribute) and missing resources (404).
|
|
81
|
+
const raw = typeof data === 'string' ? data : JSON.stringify(data);
|
|
82
|
+
let code = '';
|
|
83
|
+
let msg = '';
|
|
84
|
+
let reserved = [];
|
|
85
|
+
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
86
|
+
if (typeof data.code === 'string') code = data.code;
|
|
87
|
+
else if (typeof data.error === 'string') code = data.error;
|
|
88
|
+
if (data.error && typeof data.error === 'object') {
|
|
89
|
+
code = code || data.error.code || '';
|
|
90
|
+
msg = data.error.message || '';
|
|
91
|
+
}
|
|
92
|
+
msg = msg || (typeof data.message === 'string' ? data.message : '');
|
|
93
|
+
if (Array.isArray(data.reserved)) reserved = data.reserved.filter((x) => typeof x === 'string');
|
|
94
|
+
}
|
|
95
|
+
if (status === 400 && (code === 'reserved_attribute' || /reserved_attribute/i.test(raw || ''))) {
|
|
96
|
+
return new Error(
|
|
97
|
+
`HTTP 400 ${p}: reserved_attribute${msg ? ` — ${msg}` : ''}` +
|
|
98
|
+
`${reserved.length ? ` (reserved: ${reserved.join(', ')})` : ''}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (status === 404) {
|
|
102
|
+
return new Error(`HTTP 404 ${p}: not found${msg ? ` — ${msg}` : raw && raw !== 'null' ? ` — ${raw}` : ''}`);
|
|
103
|
+
}
|
|
104
|
+
return new Error(`HTTP ${status} ${p}: ${raw}`);
|
|
105
|
+
}
|
|
106
|
+
const GET = (p, o) => req('GET', p, undefined, o);
|
|
107
|
+
const POST = (p, b, o) => req('POST', p, b, o);
|
|
108
|
+
const DEL = (p, o) => req('DELETE', p, undefined, o);
|
|
109
|
+
|
|
110
|
+
// ---------- output ----------
|
|
111
|
+
function out(data, opts, pick) {
|
|
112
|
+
if (opts.json) {
|
|
113
|
+
console.log(JSON.stringify(data, null, 2));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (data === null || data === undefined) {
|
|
117
|
+
console.log('(empty)');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (Array.isArray(data)) {
|
|
121
|
+
if (!data.length) {
|
|
122
|
+
console.log('(empty)');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const cols = pick || Object.keys(data[0]).filter((k) => !/document|context/i.test(k));
|
|
126
|
+
console.log(cols.join('\t'));
|
|
127
|
+
for (const row of data) {
|
|
128
|
+
console.log(cols.map((c) => fmt(row[c])).join('\t'));
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (typeof data === 'object') {
|
|
133
|
+
for (const [k, v] of Object.entries(data)) console.log(`${k}: ${fmt(v)}`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
console.log(String(data));
|
|
137
|
+
}
|
|
138
|
+
function fmt(v) {
|
|
139
|
+
if (v === null || v === undefined) return '-';
|
|
140
|
+
if (typeof v === 'object') return JSON.stringify(v);
|
|
141
|
+
const s = String(v);
|
|
142
|
+
return s.length > 40 ? s.slice(0, 37) + '...' : s;
|
|
143
|
+
}
|
|
144
|
+
function need(v, name) {
|
|
145
|
+
if (!v) {
|
|
146
|
+
console.error(`missing <${name}>`);
|
|
147
|
+
process.exit(2);
|
|
148
|
+
}
|
|
149
|
+
return v;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ---------- args ----------
|
|
153
|
+
function parse(argv) {
|
|
154
|
+
const args = [];
|
|
155
|
+
const opts = { json: false };
|
|
156
|
+
for (let i = 0; i < argv.length; i++) {
|
|
157
|
+
const a = argv[i];
|
|
158
|
+
if (a === '--json') opts.json = true;
|
|
159
|
+
else if (a === '--base' || a === '--token' || a === '--apikey') opts[a.slice(2)] = argv[++i];
|
|
160
|
+
else if (a.startsWith('--base=')) opts.base = a.slice(7);
|
|
161
|
+
else if (a.startsWith('--token=')) opts.token = a.slice(8);
|
|
162
|
+
else if (a.startsWith('--apikey=')) opts.apikey = a.slice(9);
|
|
163
|
+
else if (a.startsWith('--context=')) contextOpt(opts, a.slice(10));
|
|
164
|
+
else if (a === '--context') {
|
|
165
|
+
const v = argv[++i];
|
|
166
|
+
if (!v) throw new Error('missing value for --context k=v');
|
|
167
|
+
contextOpt(opts, v);
|
|
168
|
+
}
|
|
169
|
+
else if (a === '--file') opts.file = argv[++i];
|
|
170
|
+
else if (a.startsWith('--file=')) opts.file = a.slice(7);
|
|
171
|
+
else if (a.startsWith('--limit=')) opts.limit = a.slice(8);
|
|
172
|
+
else if (a === '--limit') opts.limit = argv[++i];
|
|
173
|
+
else if (a.startsWith('--actor=')) opts.actor = a.slice(8);
|
|
174
|
+
else if (a === '--actor') opts.actor = argv[++i];
|
|
175
|
+
else if (a.startsWith('--name=')) opts.name = a.slice(7);
|
|
176
|
+
else if (a === '--name') opts.name = argv[++i];
|
|
177
|
+
else if (a.startsWith('--template=')) opts.template = a.slice(11);
|
|
178
|
+
else if (a === '--template') opts.template = argv[++i];
|
|
179
|
+
else if (a.startsWith('--email=')) opts.email = a.slice(8);
|
|
180
|
+
else if (a === '--email') opts.email = argv[++i];
|
|
181
|
+
else if (a.startsWith('--org=')) opts.org = a.slice(6);
|
|
182
|
+
else if (a === '--org') opts.org = argv[++i];
|
|
183
|
+
else if (a === '--label') opts.label = argv[++i];
|
|
184
|
+
else if (a.startsWith('--label=')) opts.label = a.slice(8);
|
|
185
|
+
else if (a === '--input') opts.input = argv[++i];
|
|
186
|
+
else if (a.startsWith('--input=')) opts.input = a.slice(8);
|
|
187
|
+
else if (a === '--data') opts.input = argv[++i];
|
|
188
|
+
else if (a.startsWith('--data=')) opts.input = a.slice(7);
|
|
189
|
+
else if (a === '--offset') opts.offset = argv[++i];
|
|
190
|
+
else if (a.startsWith('--offset=')) opts.offset = a.slice(9);
|
|
191
|
+
else if (a === '--status') opts.status = argv[++i];
|
|
192
|
+
else if (a.startsWith('--status=')) opts.status = a.slice(9);
|
|
193
|
+
else if (a === '--triggered-by') opts.triggeredBy = argv[++i];
|
|
194
|
+
else if (a.startsWith('--triggered-by=')) opts.triggeredBy = a.slice(15);
|
|
195
|
+
else if (a === '--workflow') opts.workflow = argv[++i];
|
|
196
|
+
else if (a.startsWith('--workflow=')) opts.workflow = a.slice(11);
|
|
197
|
+
else if (a === '--workflowId' || a === '--workflow-id') opts.workflow = argv[++i];
|
|
198
|
+
else if (a.startsWith('--workflowId=')) opts.workflow = a.slice(13);
|
|
199
|
+
else if (a.startsWith('--workflow-id=')) opts.workflow = a.slice(14);
|
|
200
|
+
else if (a === '--include-data' || a === '--includeData') opts.includeData = 'true';
|
|
201
|
+
else if (a.startsWith('--include-data=')) opts.includeData = a.slice(15);
|
|
202
|
+
else if (a.startsWith('--includeData=')) opts.includeData = a.slice(14);
|
|
203
|
+
else if (a === '--path') opts.path = argv[++i];
|
|
204
|
+
else if (a.startsWith('--path=')) opts.path = a.slice(7);
|
|
205
|
+
else if (a === '--method') opts.method = argv[++i];
|
|
206
|
+
else if (a.startsWith('--method=')) opts.method = a.slice(9);
|
|
207
|
+
else if (a === '--type') opts.credType = argv[++i];
|
|
208
|
+
else if (a.startsWith('--type=')) opts.credType = a.slice(7);
|
|
209
|
+
else if (a === '--secret') opts.secret = argv[++i];
|
|
210
|
+
else if (a.startsWith('--secret=')) opts.secret = a.slice(9);
|
|
211
|
+
else if (a === '--secret-file') opts.secretFile = argv[++i];
|
|
212
|
+
else if (a.startsWith('--secret-file=')) opts.secretFile = a.slice(14);
|
|
213
|
+
else if (a === '--expires-at') opts.expiresAt = argv[++i];
|
|
214
|
+
else if (a.startsWith('--expires-at=')) opts.expiresAt = a.slice(13);
|
|
215
|
+
else if (a.startsWith('--account=')) opts.account = a.slice(10);
|
|
216
|
+
else if (a === '--account') opts.account = argv[++i];
|
|
217
|
+
else if (a === '--entity-type' || a === '--entityType') opts.entityType = argv[++i];
|
|
218
|
+
else if (a.startsWith('--entity-type=')) opts.entityType = a.slice(14);
|
|
219
|
+
else if (a.startsWith('--entityType=')) opts.entityType = a.slice(13);
|
|
220
|
+
else if (a.startsWith('--license=')) opts.license = a.slice(10);
|
|
221
|
+
else if (a === '--license') opts.license = argv[++i];
|
|
222
|
+
else if (a.startsWith('--slug=')) opts.slug = a.slice(7);
|
|
223
|
+
else if (a === '--slug') opts.slug = argv[++i];
|
|
224
|
+
else if (a.startsWith('--visibility=')) opts.visibility = a.slice(13);
|
|
225
|
+
else if (a === '--visibility') opts.visibility = argv[++i];
|
|
226
|
+
else if (a.startsWith('--description=')) opts.description = a.slice(14);
|
|
227
|
+
else if (a === '--description') opts.description = argv[++i];
|
|
228
|
+
else if (a === '--vector') opts.vector = argv[++i];
|
|
229
|
+
else if (a.startsWith('--vector=')) opts.vector = a.slice(9);
|
|
230
|
+
else if (a === '--query') opts.query = argv[++i];
|
|
231
|
+
else if (a.startsWith('--query=')) opts.query = a.slice(8);
|
|
232
|
+
else if (a === '--metric') opts.metric = argv[++i];
|
|
233
|
+
else if (a.startsWith('--metric=')) opts.metric = a.slice(9);
|
|
234
|
+
else if (a === '--field') opts.field = argv[++i];
|
|
235
|
+
else if (a.startsWith('--field=')) opts.field = a.slice(8);
|
|
236
|
+
else if (a === '--where') opts.where = argv[++i];
|
|
237
|
+
else if (a.startsWith('--where=')) opts.where = a.slice(8);
|
|
238
|
+
else if (a === '--mapping') opts.mapping = argv[++i];
|
|
239
|
+
else if (a.startsWith('--mapping=')) opts.mapping = a.slice(10);
|
|
240
|
+
else if (a === '--as') opts.as = argv[++i];
|
|
241
|
+
else if (a.startsWith('--as=')) opts.as = a.slice(5);
|
|
242
|
+
else if (a === '--id') opts.docId = argv[++i];
|
|
243
|
+
else if (a.startsWith('--id=')) opts.docId = a.slice(5);
|
|
244
|
+
// Explicit aliases for NGSI-LD @context URLs (see contextOpt above).
|
|
245
|
+
else if (a.startsWith('--context-url=')) (opts.contextUrls ??= []).push(a.slice(14));
|
|
246
|
+
else if (a === '--context-url' || a === '--contexts') {
|
|
247
|
+
const v = argv[++i];
|
|
248
|
+
if (!v) throw new Error('missing value for --context-url <url>');
|
|
249
|
+
(opts.contextUrls ??= []).push(v);
|
|
250
|
+
}
|
|
251
|
+
else args.push(a);
|
|
252
|
+
}
|
|
253
|
+
return { args, opts };
|
|
254
|
+
}
|
|
255
|
+
function parseKV(s) {
|
|
256
|
+
const i = s.indexOf('=');
|
|
257
|
+
if (i < 0) throw new Error(`bad --context '${s}' (want k=v)`);
|
|
258
|
+
return { [s.slice(0, i)]: s.slice(i + 1) };
|
|
259
|
+
}
|
|
260
|
+
function contextOpt(o, v) {
|
|
261
|
+
// `--context https://...` is an NGSI-LD @context URL (repeatable, LDS);
|
|
262
|
+
// `--context key=value` keeps its existing authz/sim decision-context meaning.
|
|
263
|
+
if (/^https?:\/\//i.test(v)) (o.contextUrls ??= []).push(v);
|
|
264
|
+
else (o.context ??= {}), Object.assign(o.context, parseKV(v));
|
|
265
|
+
}
|
|
266
|
+
function readJson(file) {
|
|
267
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
268
|
+
}
|
|
269
|
+
function readJsonArg(o, what) {
|
|
270
|
+
// Inline JSON via --data (or --input), else a file via --file (workflows exec style).
|
|
271
|
+
if (o.input) {
|
|
272
|
+
try { return JSON.parse(o.input); }
|
|
273
|
+
catch { return readJson(o.input); }
|
|
274
|
+
}
|
|
275
|
+
if (o.file) return readJson(o.file);
|
|
276
|
+
console.error(`missing ${what} (use --file <json> or --data '<json>')`);
|
|
277
|
+
process.exit(2);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ---------- commands ----------
|
|
281
|
+
async function cmdLogin(o) {
|
|
282
|
+
const email = o.email || o._[0];
|
|
283
|
+
need(email, 'email');
|
|
284
|
+
const org = o.org || o._[1] || 'acme';
|
|
285
|
+
const r = await POST('/v1/auth/login', { email, orgSlug: org }, o);
|
|
286
|
+
const cfg = loadConfig();
|
|
287
|
+
cfg.baseUrl = baseUrl(o);
|
|
288
|
+
cfg.token = r.accessToken;
|
|
289
|
+
if (r.refreshToken) cfg.refreshToken = r.refreshToken;
|
|
290
|
+
delete cfg.apiKey;
|
|
291
|
+
saveConfig(cfg);
|
|
292
|
+
if (!o.json) console.log(`logged in as ${r.user.email} (token saved to ${CONF_FILE})`);
|
|
293
|
+
else console.log(JSON.stringify(r, null, 2));
|
|
294
|
+
}
|
|
295
|
+
async function cmdUseKey(o) {
|
|
296
|
+
const key = o._[0];
|
|
297
|
+
need(key, 'key');
|
|
298
|
+
const cfg = loadConfig();
|
|
299
|
+
cfg.baseUrl = baseUrl(o);
|
|
300
|
+
cfg.apiKey = key;
|
|
301
|
+
delete cfg.token;
|
|
302
|
+
saveConfig(cfg);
|
|
303
|
+
if (!o.json) console.log(`api key saved to ${CONF_FILE}`);
|
|
304
|
+
}
|
|
305
|
+
async function cmdMe(o) {
|
|
306
|
+
out(await GET('/v1/auth/me', o), o);
|
|
307
|
+
}
|
|
308
|
+
async function cmdOrgs(o) {
|
|
309
|
+
const [sub, ...rest] = o._;
|
|
310
|
+
if (!sub || sub === 'list') return out(await GET('/v1/orgs', o), o, ['slug', 'display_name', 'id']);
|
|
311
|
+
if (sub === 'create') {
|
|
312
|
+
const [slug, name] = rest;
|
|
313
|
+
need(slug, 'slug');
|
|
314
|
+
return out(await POST('/v1/orgs', { slug, displayName: name || slug }, o), o);
|
|
315
|
+
}
|
|
316
|
+
throw new Error(`unknown orgs subcommand '${sub}'`);
|
|
317
|
+
}
|
|
318
|
+
async function orgId(o, maybeId) {
|
|
319
|
+
if (maybeId) return maybeId;
|
|
320
|
+
const orgs = await GET('/v1/orgs', o);
|
|
321
|
+
if (!orgs.length) throw new Error('no organizations (login first?)');
|
|
322
|
+
const bySlug = orgs.find((x) => x.slug === (process.env.DEMO_ORG_SLUG || 'acme'));
|
|
323
|
+
return (bySlug || orgs[0]).id;
|
|
324
|
+
}
|
|
325
|
+
async function cmdAccounts(o) {
|
|
326
|
+
const [sub, ...rest] = o._;
|
|
327
|
+
if (sub === 'create') {
|
|
328
|
+
const org = rest.length > 1 ? rest[0] : await orgId(o);
|
|
329
|
+
const slug = rest.length > 1 ? rest[1] : rest[0];
|
|
330
|
+
const name = rest.length > 2 ? rest[2] : slug;
|
|
331
|
+
need(slug, 'slug');
|
|
332
|
+
return out(await POST(`/v1/orgs/${org}/accounts`, { slug, displayName: name }, o), o);
|
|
333
|
+
}
|
|
334
|
+
const org = sub && sub !== 'list' ? sub : await orgId(o);
|
|
335
|
+
return out(await GET(`/v1/orgs/${encodeURIComponent(org)}/accounts`, o), o, ['slug', 'display_name', 'id']);
|
|
336
|
+
}
|
|
337
|
+
async function cmdUsers(o) {
|
|
338
|
+
const [sub, ...rest] = o._;
|
|
339
|
+
if (sub === 'create') {
|
|
340
|
+
const [org, email, name] = rest;
|
|
341
|
+
need(org, 'org'); need(email, 'email');
|
|
342
|
+
return out(await POST(`/v1/orgs/${encodeURIComponent(org)}/users`, { email, displayName: name || email }, o), o);
|
|
343
|
+
}
|
|
344
|
+
if (sub === 'policies') {
|
|
345
|
+
need(rest[0], 'user-id');
|
|
346
|
+
return out(await GET(`/v1/users/${encodeURIComponent(rest[0])}/policies`, o), o);
|
|
347
|
+
}
|
|
348
|
+
const org = await orgId(o, sub && sub !== 'list' ? sub : undefined);
|
|
349
|
+
return out(await GET(`/v1/orgs/${encodeURIComponent(org)}/users`, o), o, ['email', 'display_name', 'id']);
|
|
350
|
+
}
|
|
351
|
+
async function cmdRefresh(o) {
|
|
352
|
+
const cfg = loadConfig();
|
|
353
|
+
const rt = o._[0] || (process.env.SYNAPSEONE_REFRESH_TOKEN || process.env.SYNAPSEDX_REFRESH_TOKEN) || cfg.refreshToken;
|
|
354
|
+
need(rt, 'refresh-token (arg | $SYNAPSEONE_REFRESH_TOKEN)');
|
|
355
|
+
const r = await POST('/v1/auth/refresh', { refreshToken: rt }, o);
|
|
356
|
+
cfg.baseUrl = baseUrl(o);
|
|
357
|
+
cfg.token = r.accessToken;
|
|
358
|
+
if (r.refreshToken) cfg.refreshToken = r.refreshToken;
|
|
359
|
+
delete cfg.apiKey;
|
|
360
|
+
saveConfig(cfg);
|
|
361
|
+
if (!o.json) console.log(`token rotated (saved to ${CONF_FILE})`);
|
|
362
|
+
else console.log(JSON.stringify(r, null, 2));
|
|
363
|
+
}
|
|
364
|
+
async function cmdLogoutAll(o) {
|
|
365
|
+
const cfg = loadConfig();
|
|
366
|
+
const rt = o._[0] || (process.env.SYNAPSEONE_REFRESH_TOKEN || process.env.SYNAPSEDX_REFRESH_TOKEN) || cfg.refreshToken;
|
|
367
|
+
need(rt, 'refresh-token (arg | $SYNAPSEONE_REFRESH_TOKEN)');
|
|
368
|
+
const r = await POST('/v1/auth/logout-all', { refreshToken: rt }, o);
|
|
369
|
+
delete cfg.refreshToken;
|
|
370
|
+
saveConfig(cfg);
|
|
371
|
+
out(r, o);
|
|
372
|
+
}
|
|
373
|
+
async function cmdVerify(o) {
|
|
374
|
+
out(await GET('/v1/auth/verify', o), o);
|
|
375
|
+
}
|
|
376
|
+
async function cmdRoles(o) {
|
|
377
|
+
const [sub, ...rest] = o._;
|
|
378
|
+
if (sub === 'create') {
|
|
379
|
+
const [account, slug] = rest;
|
|
380
|
+
need(account, 'account'); need(slug, 'slug');
|
|
381
|
+
return out(await POST(`/v1/accounts/${encodeURIComponent(account)}/roles`, { slug }, o), o);
|
|
382
|
+
}
|
|
383
|
+
if (sub === 'delete') {
|
|
384
|
+
need(rest[0], 'role-id');
|
|
385
|
+
return out(await DEL(`/v1/roles/${encodeURIComponent(rest[0])}`, o), o);
|
|
386
|
+
}
|
|
387
|
+
need(sub, 'account');
|
|
388
|
+
return out(await GET(`/v1/accounts/${encodeURIComponent(sub)}/roles`, o), o, ['slug', 'id']);
|
|
389
|
+
}
|
|
390
|
+
async function cmdPolicies(o) {
|
|
391
|
+
const [sub, ...rest] = o._;
|
|
392
|
+
if (sub === 'create') {
|
|
393
|
+
const [account, slug] = rest;
|
|
394
|
+
need(account, 'account'); need(slug, 'slug'); need(o.file, '--file policy.json');
|
|
395
|
+
return out(await POST(`/v1/accounts/${encodeURIComponent(account)}/policies`, { slug, document: readJson(o.file) }, o), o);
|
|
396
|
+
}
|
|
397
|
+
if (sub === 'delete') {
|
|
398
|
+
need(rest[0], 'policy-id');
|
|
399
|
+
return out(await DEL(`/v1/policies/${encodeURIComponent(rest[0])}`, o), o);
|
|
400
|
+
}
|
|
401
|
+
need(sub, 'account');
|
|
402
|
+
return out(await GET(`/v1/accounts/${encodeURIComponent(sub)}/policies`, o), o, ['slug', 'id']);
|
|
403
|
+
}
|
|
404
|
+
async function cmdAttach(o) {
|
|
405
|
+
need(o._[0], 'role-id'); need(o._[1], 'policy-id');
|
|
406
|
+
return out(await POST(`/v1/roles/${encodeURIComponent(o._[0])}/attach`, { policyId: o._[1] }, o), o);
|
|
407
|
+
}
|
|
408
|
+
async function cmdDetach(o) {
|
|
409
|
+
need(o._[0], 'role-id'); need(o._[1], 'policy-id');
|
|
410
|
+
return out(await DEL(`/v1/roles/${encodeURIComponent(o._[0])}/policies/${encodeURIComponent(o._[1])}`, o), o);
|
|
411
|
+
}
|
|
412
|
+
async function cmdAssign(o) {
|
|
413
|
+
need(o._[0], 'user-id'); need(o._[1], 'role-id');
|
|
414
|
+
return out(await POST(`/v1/users/${encodeURIComponent(o._[0])}/assign`, { roleId: o._[1] }, o), o);
|
|
415
|
+
}
|
|
416
|
+
async function cmdUnassign(o) {
|
|
417
|
+
need(o._[0], 'user-id'); need(o._[1], 'role-id');
|
|
418
|
+
return out(await DEL(`/v1/users/${encodeURIComponent(o._[0])}/roles/${encodeURIComponent(o._[1])}`, o), o);
|
|
419
|
+
}
|
|
420
|
+
async function cmdAuthz(o) {
|
|
421
|
+
const [user, action, resource] = o._;
|
|
422
|
+
need(user, 'user-id'); need(action, 'action'); need(resource, 'resource');
|
|
423
|
+
return out(await POST('/v1/authorize', { userId: user, action, resource, context: o.context }, o), o);
|
|
424
|
+
}
|
|
425
|
+
async function cmdSim(o) {
|
|
426
|
+
need(o.file, '--file policy.json');
|
|
427
|
+
const [action, resource] = o._;
|
|
428
|
+
need(action, 'action'); need(resource, 'resource');
|
|
429
|
+
const raw = readJson(o.file);
|
|
430
|
+
const docs = Array.isArray(raw) ? raw : [raw];
|
|
431
|
+
return out(await POST('/v1/authorize/dry-run', { documents: docs, action, resource, context: o.context }, o), o);
|
|
432
|
+
}
|
|
433
|
+
async function cmdAudit(o) {
|
|
434
|
+
const qs = `?limit=${o.limit || 50}${o.actor ? `&actor=${encodeURIComponent(o.actor)}` : ''}`;
|
|
435
|
+
return out(await GET(`/v1/audit${qs}`, o), o, ['created_at', 'actor_id', 'action', 'decision']);
|
|
436
|
+
}
|
|
437
|
+
async function cmdKeys(o) {
|
|
438
|
+
const [sub, ...rest] = o._;
|
|
439
|
+
if (sub === 'create') {
|
|
440
|
+
need(rest[0], 'account');
|
|
441
|
+
const r = await POST(`/v1/accounts/${encodeURIComponent(rest[0])}/keys`, { name: o.name || o.label || 'cli', scopes: ['*'] }, o);
|
|
442
|
+
if (!o.json) console.log(`SAVE THIS KEY (shown once):\n${r.apiKey}\nid: ${r.id}`);
|
|
443
|
+
else console.log(JSON.stringify(r, null, 2));
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (sub === 'revoke' || sub === 'delete') {
|
|
447
|
+
need(rest[0], 'key-id');
|
|
448
|
+
return out(await DEL(`/v1/keys/${encodeURIComponent(rest[0])}`, o), o);
|
|
449
|
+
}
|
|
450
|
+
if (sub === 'verify') {
|
|
451
|
+
need(rest[0] || o.apikey, 'api-key (arg or --apikey)');
|
|
452
|
+
return out(await POST('/v1/keys/verify', { apiKey: rest[0] || o.apikey }, o), o);
|
|
453
|
+
}
|
|
454
|
+
if (sub === 'list') {
|
|
455
|
+
need(rest[0], 'account');
|
|
456
|
+
const r = await GET(`/v1/accounts/${encodeURIComponent(rest[0])}/keys`, o);
|
|
457
|
+
return out(Array.isArray(r) ? r : [], o, ['id', 'name', 'prefix', 'revoked_at']);
|
|
458
|
+
}
|
|
459
|
+
throw new Error(`usage: keys create <account> [--name N] | keys list <account> | keys revoke <key-id> | keys verify <api-key>`);
|
|
460
|
+
}
|
|
461
|
+
async function cmdSa(o) {
|
|
462
|
+
const [sub, ...rest] = o._;
|
|
463
|
+
if (sub === 'create') {
|
|
464
|
+
need(rest[0], 'account'); need(rest[1], 'slug');
|
|
465
|
+
return out(await POST(`/v1/accounts/${encodeURIComponent(rest[0])}/service-accounts`, { slug: rest[1] }, o), o);
|
|
466
|
+
}
|
|
467
|
+
if (sub === 'token') {
|
|
468
|
+
need(rest[0], 'sa-id');
|
|
469
|
+
const r = await POST(`/v1/service-accounts/${encodeURIComponent(rest[0])}/token`, {}, o);
|
|
470
|
+
if (!o.json) console.log(`SAVE THIS TOKEN (short-lived):\n${r.accessToken}`);
|
|
471
|
+
else console.log(JSON.stringify(r, null, 2));
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
need(sub, 'account');
|
|
475
|
+
return out(await GET(`/v1/accounts/${encodeURIComponent(sub)}/service-accounts`, o), o, ['slug', 'id']);
|
|
476
|
+
}
|
|
477
|
+
async function cmdWorkflows(o) {
|
|
478
|
+
const [sub, ...rest] = o._;
|
|
479
|
+
if (!sub || sub === 'list') {
|
|
480
|
+
const r = await GET('/v1/workflows', o);
|
|
481
|
+
return out(Array.isArray(r) ? r : r.data || r, o, ['id', 'name', 'active']);
|
|
482
|
+
}
|
|
483
|
+
if (sub === 'create') {
|
|
484
|
+
if (o.template) {
|
|
485
|
+
return out(await POST('/v1/workflows', { template: o.template, name: o.name }, o), o);
|
|
486
|
+
}
|
|
487
|
+
if (o.file) {
|
|
488
|
+
const raw = readJson(o.file);
|
|
489
|
+
// Accept either a full body ({template|definition, name}) or a bare definition.
|
|
490
|
+
const body = raw && (raw.template || raw.definition) ? raw : { name: o.name || 'synapseone-workflow', definition: raw };
|
|
491
|
+
if (o.name && !body.name) body.name = o.name;
|
|
492
|
+
return out(await POST('/v1/workflows', body, o), o);
|
|
493
|
+
}
|
|
494
|
+
throw new Error(`workflows create needs --template T [--name N] or --file definition.json [--name N]`);
|
|
495
|
+
}
|
|
496
|
+
if (['get', 'activate', 'deactivate'].includes(sub)) {
|
|
497
|
+
need(rest[0], 'workflow-id');
|
|
498
|
+
const method = sub === 'get' ? 'GET' : 'POST';
|
|
499
|
+
const suf = sub === 'get' ? '' : `/${sub}`;
|
|
500
|
+
const id = encodeURIComponent(rest[0]);
|
|
501
|
+
if (method === 'GET') return out(await GET(`/v1/workflows/${id}`, o), o);
|
|
502
|
+
return out(await req('POST', `/v1/workflows/${id}${suf}`, {}, o), o);
|
|
503
|
+
}
|
|
504
|
+
if (sub === 'exec' || sub === 'execute') {
|
|
505
|
+
need(rest[0], 'workflow-id');
|
|
506
|
+
let payload = {};
|
|
507
|
+
if (o.input) {
|
|
508
|
+
try { payload = JSON.parse(o.input); }
|
|
509
|
+
catch { payload = readJson(o.input); }
|
|
510
|
+
} else if (o.file) {
|
|
511
|
+
const raw = readJson(o.file);
|
|
512
|
+
payload = raw && raw.input !== undefined ? raw.input : raw;
|
|
513
|
+
}
|
|
514
|
+
return out(await POST(`/v1/workflows/${encodeURIComponent(rest[0])}/execute`, payload, o), o);
|
|
515
|
+
}
|
|
516
|
+
if (sub === 'test-run' || sub === 'testrun' || sub === 'try') {
|
|
517
|
+
// Try a definition without saving: POST /v1/workflows/test-run {definition, input}.
|
|
518
|
+
// --file def.json (required): a bare {nodes, connections} definition or {definition, input}.
|
|
519
|
+
// --input input.json|'{"k":v}': extra run input (merged, --input wins on conflict).
|
|
520
|
+
// --workflow-id ID: attach the run to a saved workflow's history.
|
|
521
|
+
need(o.file, '--file def.json');
|
|
522
|
+
const raw = readJson(o.file);
|
|
523
|
+
const definition = raw && raw.definition ? raw.definition : raw;
|
|
524
|
+
let input = raw && raw.input !== undefined && !o.input ? raw.input : {};
|
|
525
|
+
if (o.input) {
|
|
526
|
+
try { input = JSON.parse(o.input); }
|
|
527
|
+
catch { input = readJson(o.input); }
|
|
528
|
+
}
|
|
529
|
+
const body = { definition, input };
|
|
530
|
+
if (o.workflow) body.workflowId = String(o.workflow);
|
|
531
|
+
return out(await POST('/v1/workflows/test-run', body, o), o);
|
|
532
|
+
}
|
|
533
|
+
if (sub === 'update') {
|
|
534
|
+
need(rest[0], 'workflow-id');
|
|
535
|
+
const patch = {};
|
|
536
|
+
if (o.name) patch.name = o.name;
|
|
537
|
+
if (o.file) {
|
|
538
|
+
const raw = readJson(o.file);
|
|
539
|
+
patch.definition = raw && raw.definition ? raw.definition : raw;
|
|
540
|
+
}
|
|
541
|
+
if (!patch.name && !patch.definition) {
|
|
542
|
+
throw new Error(`workflows update needs --name N and/or --file definition.json`);
|
|
543
|
+
}
|
|
544
|
+
return out(await req('PUT', `/v1/workflows/${encodeURIComponent(rest[0])}`, patch, o), o);
|
|
545
|
+
}
|
|
546
|
+
if (sub === 'delete' || sub === 'rm') {
|
|
547
|
+
need(rest[0], 'workflow-id');
|
|
548
|
+
return out(await DEL(`/v1/workflows/${encodeURIComponent(rest[0])}`, o), o);
|
|
549
|
+
}
|
|
550
|
+
throw new Error(`unknown workflows subcommand '${sub}'`);
|
|
551
|
+
}
|
|
552
|
+
async function cmdTemplates(o) {
|
|
553
|
+
const [sub, ...rest] = o._;
|
|
554
|
+
if (sub && sub !== 'list') {
|
|
555
|
+
return out(await GET(`/v1/templates/${encodeURIComponent(sub)}`, o), o);
|
|
556
|
+
}
|
|
557
|
+
if (sub === 'list' && rest[0]) {
|
|
558
|
+
return out(await GET(`/v1/templates/${encodeURIComponent(rest[0])}`, o), o);
|
|
559
|
+
}
|
|
560
|
+
return out(await GET('/v1/templates', o), o, ['name', 'description']);
|
|
561
|
+
}
|
|
562
|
+
async function cmdExecutions(o) {
|
|
563
|
+
const [sub, ...rest] = o._;
|
|
564
|
+
if (sub && sub !== 'list') {
|
|
565
|
+
const qs = o.includeData ? '?includeData=true' : '';
|
|
566
|
+
return out(await GET(`/v1/executions/${encodeURIComponent(sub)}${qs}`, o), o);
|
|
567
|
+
}
|
|
568
|
+
// Allowlist mirrors GET /v1/executions: limit/offset/workflowId/status/triggeredBy/includeData.
|
|
569
|
+
const qs = new URLSearchParams();
|
|
570
|
+
if (o.limit) qs.set('limit', String(o.limit));
|
|
571
|
+
if (o.offset) qs.set('offset', String(o.offset));
|
|
572
|
+
if (o.workflow) qs.set('workflowId', String(o.workflow));
|
|
573
|
+
if (o.status) qs.set('status', String(o.status));
|
|
574
|
+
if (o.triggeredBy) qs.set('triggeredBy', String(o.triggeredBy));
|
|
575
|
+
if (o.includeData) qs.set('includeData', String(o.includeData));
|
|
576
|
+
const s = qs.toString();
|
|
577
|
+
const r = await GET(`/v1/executions${s ? `?${s}` : ''}`, o);
|
|
578
|
+
return out(Array.isArray(r) ? r : r.data || r, o, ['id', 'status', 'workflowId']);
|
|
579
|
+
}
|
|
580
|
+
async function cmdWebhook(o) {
|
|
581
|
+
// Public inbound webhook: ANY /webhook/:path (no auth) triggers the ACTIVE
|
|
582
|
+
// workflow whose webhook node declares that path. NOTE: the gateway does not
|
|
583
|
+
// proxy /webhook/* — point --base at the workflow-service origin
|
|
584
|
+
// (e.g. http://localhost:4002), not the gateway.
|
|
585
|
+
const p = o._[0] || o.path;
|
|
586
|
+
need(p, 'path (arg or --path <p>)');
|
|
587
|
+
const clean = String(p).replace(/^\/+|\/+$/g, '').split('?')[0];
|
|
588
|
+
let payload;
|
|
589
|
+
if (o.input) {
|
|
590
|
+
try { payload = JSON.parse(o.input); }
|
|
591
|
+
catch { payload = readJson(o.input); }
|
|
592
|
+
} else if (o.file) {
|
|
593
|
+
payload = readJson(o.file);
|
|
594
|
+
}
|
|
595
|
+
const method = (o.method || (payload === undefined ? 'GET' : 'POST')).toUpperCase();
|
|
596
|
+
if (!['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'].includes(method)) {
|
|
597
|
+
throw new Error(`bad --method '${o.method}' (want GET|POST|PUT|PATCH|DELETE|HEAD)`);
|
|
598
|
+
}
|
|
599
|
+
const data = await req(method, `/webhook/${clean}`, payload, o);
|
|
600
|
+
return out(data, o);
|
|
601
|
+
}
|
|
602
|
+
async function cmdConfig() {
|
|
603
|
+
const cfg = loadConfig();
|
|
604
|
+
const redacted = { file: CONF_FILE, baseUrl: cfg.baseUrl };
|
|
605
|
+
if (cfg.token) redacted.token = '*** (saved)';
|
|
606
|
+
if (cfg.apiKey) redacted.apiKey = '*** (saved)';
|
|
607
|
+
if (cfg.refreshToken) redacted.refreshToken = '*** (saved)';
|
|
608
|
+
console.log(JSON.stringify(redacted, null, 2));
|
|
609
|
+
}
|
|
610
|
+
function credentialSecret(o) {
|
|
611
|
+
// Prefer --secret-file / env over --secret (flags leak via ps + shell history).
|
|
612
|
+
if (o.secretFile) return fs.readFileSync(o.secretFile, 'utf8').trim();
|
|
613
|
+
if (o.secret) return o.secret;
|
|
614
|
+
if (process.env.SYNAPSEONE_CREDENTIAL_SECRET) return process.env.SYNAPSEONE_CREDENTIAL_SECRET;
|
|
615
|
+
if (process.env.SYNAPSEDX_CREDENTIAL_SECRET) return process.env.SYNAPSEDX_CREDENTIAL_SECRET;
|
|
616
|
+
throw new Error('missing credential secret (use --secret-file <path>, --secret <S>, or $SYNAPSEONE_CREDENTIAL_SECRET)');
|
|
617
|
+
}
|
|
618
|
+
async function cmdCredentials(o) {
|
|
619
|
+
// Account-scoped secrets; PROPOSED contract (backend in progress): secrets are
|
|
620
|
+
// encrypted at rest and NEVER returned — list/get return metadata only
|
|
621
|
+
// {id, name, type, accountId, expiresAt, version}.
|
|
622
|
+
const [sub, ...rest] = o._;
|
|
623
|
+
if (sub === 'create') {
|
|
624
|
+
need(rest[0], 'account');
|
|
625
|
+
need(o.name, '--name <name>');
|
|
626
|
+
const type = o.credType || 'bearer';
|
|
627
|
+
if (!['bearer', 'basic', 'apiKey', 'oauth2'].includes(type)) {
|
|
628
|
+
throw new Error(`bad --type '${type}' (want bearer|basic|apiKey|oauth2)`);
|
|
629
|
+
}
|
|
630
|
+
const body = { name: o.name, type, secret: credentialSecret(o) };
|
|
631
|
+
if (o.expiresAt) body.expiresAt = o.expiresAt;
|
|
632
|
+
return out(await POST(`/v1/accounts/${encodeURIComponent(rest[0])}/credentials`, body, o), o);
|
|
633
|
+
}
|
|
634
|
+
if (!sub || sub === 'list') {
|
|
635
|
+
const account = sub === 'list' ? rest[0] : sub;
|
|
636
|
+
need(account, 'account');
|
|
637
|
+
const r = await GET(`/v1/accounts/${encodeURIComponent(account)}/credentials`, o);
|
|
638
|
+
return out(Array.isArray(r) ? r : r.data || r, o, ['id', 'name', 'type', 'version', 'expiresAt']);
|
|
639
|
+
}
|
|
640
|
+
if (sub === 'get') {
|
|
641
|
+
need(rest[0], 'credential-id');
|
|
642
|
+
return out(await GET(`/v1/credentials/${encodeURIComponent(rest[0])}`, o), o);
|
|
643
|
+
}
|
|
644
|
+
if (sub === 'delete' || sub === 'rm') {
|
|
645
|
+
need(rest[0], 'credential-id');
|
|
646
|
+
return out(await DEL(`/v1/credentials/${encodeURIComponent(rest[0])}`, o), o);
|
|
647
|
+
}
|
|
648
|
+
if (sub === 'rotate') {
|
|
649
|
+
need(rest[0], 'credential-id');
|
|
650
|
+
return out(await POST(`/v1/credentials/${encodeURIComponent(rest[0])}/rotate`, { secret: credentialSecret(o) }, o), o);
|
|
651
|
+
}
|
|
652
|
+
throw new Error(`unknown credentials subcommand '${sub}' (want create|list|get|delete|rotate)`);
|
|
653
|
+
}
|
|
654
|
+
async function cmdCall(o) {
|
|
655
|
+
// Generic escape hatch: call any gateway endpoint directly.
|
|
656
|
+
// Example: synapseone call GET /v1/accounts/acc123/credentials
|
|
657
|
+
const [method, rawPath, ...extra] = o._;
|
|
658
|
+
need(method, 'METHOD (GET|POST|PUT|PATCH|DELETE|HEAD)');
|
|
659
|
+
need(rawPath, 'path (e.g. /v1/workflows)');
|
|
660
|
+
if (extra.length) throw new Error(`unexpected args: ${extra.join(' ')}`);
|
|
661
|
+
const m = String(method).toUpperCase();
|
|
662
|
+
if (!['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'].includes(m)) {
|
|
663
|
+
throw new Error(`bad METHOD '${method}' (want GET|POST|PUT|PATCH|DELETE|HEAD)`);
|
|
664
|
+
}
|
|
665
|
+
if (!String(rawPath).startsWith('/')) throw new Error(`path must start with '/' (got '${rawPath}')`);
|
|
666
|
+
let body;
|
|
667
|
+
if (o.input) {
|
|
668
|
+
try { body = JSON.parse(o.input); }
|
|
669
|
+
catch { body = readJson(o.input); }
|
|
670
|
+
} else if (o.file) {
|
|
671
|
+
body = readJson(o.file);
|
|
672
|
+
}
|
|
673
|
+
return out(await req(m, rawPath, body, o), o);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// ---------- lds (SynapseLDS, /v1/lds) ----------
|
|
677
|
+
const LDS = '/v1/lds';
|
|
678
|
+
const LDS_DS_COLS = ['id', 'slug', 'name', 'entityType', 'license', 'defaultVisibility'];
|
|
679
|
+
const LDS_ATTR_COLS = ['name', 'ngsiLdType', 'visibility', 'description'];
|
|
680
|
+
|
|
681
|
+
function ldsVisibility(v, allowed) {
|
|
682
|
+
if (!allowed.split('|').includes(v)) throw new Error(`bad --visibility '${v}' (want ${allowed})`);
|
|
683
|
+
return v;
|
|
684
|
+
}
|
|
685
|
+
function ldsId(v, name) {
|
|
686
|
+
return encodeURIComponent(need(v, name));
|
|
687
|
+
}
|
|
688
|
+
async function cmdLds(o) {
|
|
689
|
+
const [sub, ...rest] = o._;
|
|
690
|
+
if (sub === 'licenses') {
|
|
691
|
+
const r = await GET(`${LDS}/licenses`, o);
|
|
692
|
+
return out(Array.isArray(r) ? r : r.data || r, o, ['id', 'name', 'openData']);
|
|
693
|
+
}
|
|
694
|
+
if (sub === 'datasources') return ldsDatasources(o, rest);
|
|
695
|
+
if (sub === 'attributes') return ldsAttributes(o, rest);
|
|
696
|
+
if (sub === 'ingest') return ldsIngest(o, rest);
|
|
697
|
+
if (sub === 'entities') return ldsEntities(o, rest);
|
|
698
|
+
if (sub === 'entity') return ldsEntity(o, rest);
|
|
699
|
+
if (sub === 'stats') return ldsStats(o, rest);
|
|
700
|
+
throw new Error(`usage: synapseone lds licenses | lds datasources [list|create|get|update|delete] | lds attributes [list|set] <id> | lds ingest <id> | lds entities <id> | lds entity <id> <entity-id> | lds stats <id>`);
|
|
701
|
+
}
|
|
702
|
+
async function ldsDatasources(o, args) {
|
|
703
|
+
const [sub, ...rest] = args;
|
|
704
|
+
if (!sub || sub === 'list') {
|
|
705
|
+
const qs = new URLSearchParams();
|
|
706
|
+
if (o.org) qs.set('orgId', String(o.org));
|
|
707
|
+
if (o.account) qs.set('accountId', String(o.account));
|
|
708
|
+
const s = qs.toString();
|
|
709
|
+
const r = await GET(`${LDS}/datasources${s ? `?${s}` : ''}`, o);
|
|
710
|
+
return out(Array.isArray(r) ? r : r.data || r, o, LDS_DS_COLS);
|
|
711
|
+
}
|
|
712
|
+
if (sub === 'create') {
|
|
713
|
+
need(o.name, '--name <name>');
|
|
714
|
+
need(o.entityType, '--entity-type <Type>');
|
|
715
|
+
need(o.license, '--license <SPDX>');
|
|
716
|
+
const body = { name: o.name, entityType: o.entityType, license: o.license };
|
|
717
|
+
if (o.slug) body.slug = o.slug;
|
|
718
|
+
if (o.contextUrls?.length) body.contexts = o.contextUrls;
|
|
719
|
+
if (o.visibility) body.defaultVisibility = ldsVisibility(o.visibility, 'public|internal');
|
|
720
|
+
if (o.description) body.description = o.description;
|
|
721
|
+
return out(await POST(`${LDS}/datasources`, body, o), o);
|
|
722
|
+
}
|
|
723
|
+
if (sub === 'get') {
|
|
724
|
+
return out(await GET(`${LDS}/datasources/${ldsId(rest[0], 'datasource-id')}`, o), o);
|
|
725
|
+
}
|
|
726
|
+
if (sub === 'update') {
|
|
727
|
+
const id = ldsId(rest[0], 'datasource-id');
|
|
728
|
+
const patch = {};
|
|
729
|
+
if (o.name) patch.name = o.name;
|
|
730
|
+
if (o.entityType) patch.entityType = o.entityType;
|
|
731
|
+
if (o.license) patch.license = o.license;
|
|
732
|
+
if (o.slug) patch.slug = o.slug;
|
|
733
|
+
if (o.contextUrls?.length) patch.contexts = o.contextUrls;
|
|
734
|
+
if (o.visibility) patch.defaultVisibility = ldsVisibility(o.visibility, 'public|internal');
|
|
735
|
+
if (o.description) patch.description = o.description;
|
|
736
|
+
if (!Object.keys(patch).length) {
|
|
737
|
+
throw new Error('lds datasources update needs at least one of --name|--entity-type|--license|--slug|--context-url|--visibility|--description');
|
|
738
|
+
}
|
|
739
|
+
return out(await req('PATCH', `${LDS}/datasources/${id}`, patch, o), o);
|
|
740
|
+
}
|
|
741
|
+
if (sub === 'delete' || sub === 'rm') {
|
|
742
|
+
return out(await DEL(`${LDS}/datasources/${ldsId(rest[0], 'datasource-id')}`, o), o);
|
|
743
|
+
}
|
|
744
|
+
throw new Error(`unknown lds datasources subcommand '${sub}' (want list|create|get|update|delete)`);
|
|
745
|
+
}
|
|
746
|
+
async function ldsAttributes(o, args) {
|
|
747
|
+
const [sub, ...rest] = args;
|
|
748
|
+
if (sub === 'set' || sub === 'put') {
|
|
749
|
+
const id = ldsId(rest[0], 'datasource-id');
|
|
750
|
+
const payload = readJsonArg(o, 'attributes JSON');
|
|
751
|
+
const attributes = Array.isArray(payload) ? payload : payload && payload.attributes;
|
|
752
|
+
if (!Array.isArray(attributes)) throw new Error('lds attributes set wants a JSON array or {"attributes":[...]}');
|
|
753
|
+
return out(await req('PUT', `${LDS}/datasources/${id}/attributes`, { attributes }, o), o);
|
|
754
|
+
}
|
|
755
|
+
if (!sub || sub === 'list') {
|
|
756
|
+
const r = await GET(`${LDS}/datasources/${ldsId(rest[0], 'datasource-id')}/attributes`, o);
|
|
757
|
+
return out(Array.isArray(r) ? r : r.attributes || r.data || r, o, LDS_ATTR_COLS);
|
|
758
|
+
}
|
|
759
|
+
throw new Error(`unknown lds attributes subcommand '${sub}' (want list|set)`);
|
|
760
|
+
}
|
|
761
|
+
async function ldsIngest(o, args) {
|
|
762
|
+
const id = ldsId(args[0], 'datasource-id');
|
|
763
|
+
const record = readJsonArg(o, 'record JSON');
|
|
764
|
+
return out(await POST(`${LDS}/datasources/${id}/records`, record, o), o);
|
|
765
|
+
}
|
|
766
|
+
async function ldsEntities(o, args) {
|
|
767
|
+
const id = ldsId(args[0], 'datasource-id');
|
|
768
|
+
const qs = new URLSearchParams();
|
|
769
|
+
if (o.limit) qs.set('limit', String(o.limit));
|
|
770
|
+
if (o.offset) qs.set('offset', String(o.offset));
|
|
771
|
+
if (o.visibility) qs.set('visibility', ldsVisibility(o.visibility, 'public|restricted'));
|
|
772
|
+
const s = qs.toString();
|
|
773
|
+
const r = await GET(`${LDS}/datasources/${id}/entities${s ? `?${s}` : ''}`, o);
|
|
774
|
+
// NGSI-LD documents: always JSON (table columns would mangle nested properties).
|
|
775
|
+
console.log(JSON.stringify(r, null, 2));
|
|
776
|
+
}
|
|
777
|
+
async function ldsEntity(o, args) {
|
|
778
|
+
const id = ldsId(args[0], 'datasource-id');
|
|
779
|
+
const r = await GET(`${LDS}/datasources/${id}/entities/${ldsId(args[1], 'entity-id')}`, o);
|
|
780
|
+
console.log(JSON.stringify(r, null, 2));
|
|
781
|
+
}
|
|
782
|
+
async function ldsStats(o, args) {
|
|
783
|
+
return out(await GET(`${LDS}/datasources/${ldsId(args[0], 'datasource-id')}/stats`, o), o);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// ---------- SynapseBase ----------
|
|
787
|
+
const BASE = '/v1/base';
|
|
788
|
+
const BASE_COL_COLS = ['id', 'name', 'description', 'updatedAt'];
|
|
789
|
+
const BASE_DOC_COLS = ['id', 'version', 'createdAt', 'updatedAt'];
|
|
790
|
+
const BASE_VEC_COLS = ['id', 'similarity', 'distance'];
|
|
791
|
+
|
|
792
|
+
async function cmdBase(o) {
|
|
793
|
+
const [sub, ...rest] = o._;
|
|
794
|
+
if (sub === 'collections') return baseCollections(o, rest);
|
|
795
|
+
if (sub === 'documents') return baseDocuments(o, rest);
|
|
796
|
+
if (sub === 'vector-search' || sub === 'vectorSearch') return baseVectorSearch(o, rest);
|
|
797
|
+
if (sub === 'query') return baseQuery(o, rest);
|
|
798
|
+
throw new Error(`usage: synapseone base collections [list|create|get|delete] | base documents [list|insert|get|delete] <collection> | base vector-search <collection> | base query <collection>`);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
async function baseCollections(o, args) {
|
|
802
|
+
const [sub, name] = args;
|
|
803
|
+
if (!sub || sub === 'list') {
|
|
804
|
+
const r = await GET(`${BASE}/collections`, o);
|
|
805
|
+
return out(Array.isArray(r) ? r : r.data || r, o, BASE_COL_COLS);
|
|
806
|
+
}
|
|
807
|
+
if (sub === 'create') {
|
|
808
|
+
const colName = name || o.name;
|
|
809
|
+
need(colName, 'collection name (arg or --name <name>)');
|
|
810
|
+
let mapping;
|
|
811
|
+
if (o.mapping) {
|
|
812
|
+
try { mapping = JSON.parse(o.mapping); } catch { mapping = readJson(o.mapping); }
|
|
813
|
+
} else if (o.file) {
|
|
814
|
+
mapping = readJson(o.file);
|
|
815
|
+
}
|
|
816
|
+
const body = { name: colName };
|
|
817
|
+
if (o.description) body.description = o.description;
|
|
818
|
+
if (mapping) body.mapping = mapping;
|
|
819
|
+
return out(await POST(`${BASE}/collections`, body, o), o);
|
|
820
|
+
}
|
|
821
|
+
if (sub === 'get') {
|
|
822
|
+
need(name, 'collection name');
|
|
823
|
+
return out(await GET(`${BASE}/collections/${encodeURIComponent(name)}`, o), o);
|
|
824
|
+
}
|
|
825
|
+
if (sub === 'delete' || sub === 'rm') {
|
|
826
|
+
need(name, 'collection name');
|
|
827
|
+
return out(await DEL(`${BASE}/collections/${encodeURIComponent(name)}`, o), o);
|
|
828
|
+
}
|
|
829
|
+
throw new Error(`unknown base collections subcommand '${sub}' (want list|create|get|delete)`);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
async function baseDocuments(o, args) {
|
|
833
|
+
const [sub, col, id] = args;
|
|
834
|
+
if (sub === 'list') {
|
|
835
|
+
need(col, 'collection name');
|
|
836
|
+
const qs = new URLSearchParams();
|
|
837
|
+
if (o.limit) qs.set('limit', String(o.limit));
|
|
838
|
+
if (o.offset) qs.set('offset', String(o.offset));
|
|
839
|
+
if (o.as) qs.set('as', String(o.as));
|
|
840
|
+
const s = qs.toString();
|
|
841
|
+
const r = await GET(`${BASE}/collections/${encodeURIComponent(col)}/documents${s ? `?${s}` : ''}`, o);
|
|
842
|
+
return out(o.json ? r : r.items || (Array.isArray(r) ? r : r.data || r), o, BASE_DOC_COLS);
|
|
843
|
+
}
|
|
844
|
+
if (sub === 'insert' || sub === 'create') {
|
|
845
|
+
need(col, 'collection name');
|
|
846
|
+
const payload = readJsonArg(o, 'document JSON');
|
|
847
|
+
const docId = id || o.docId || (payload && payload.id);
|
|
848
|
+
const body = { ...payload };
|
|
849
|
+
if (docId) body.id = docId;
|
|
850
|
+
return out(await POST(`${BASE}/collections/${encodeURIComponent(col)}/documents`, body, o), o);
|
|
851
|
+
}
|
|
852
|
+
if (sub === 'get') {
|
|
853
|
+
need(col, 'collection name');
|
|
854
|
+
need(id, 'document id');
|
|
855
|
+
const qs = o.as ? `?as=${encodeURIComponent(o.as)}` : '';
|
|
856
|
+
return out(await GET(`${BASE}/collections/${encodeURIComponent(col)}/documents/${encodeURIComponent(id)}${qs}`, o), o);
|
|
857
|
+
}
|
|
858
|
+
if (sub === 'delete' || sub === 'rm') {
|
|
859
|
+
need(col, 'collection name');
|
|
860
|
+
need(id, 'document id');
|
|
861
|
+
return out(await DEL(`${BASE}/collections/${encodeURIComponent(col)}/documents/${encodeURIComponent(id)}`, o), o);
|
|
862
|
+
}
|
|
863
|
+
throw new Error(`unknown base documents subcommand '${sub}' (want list|insert|get|delete)`);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
async function baseVectorSearch(o, args) {
|
|
867
|
+
const [col] = args;
|
|
868
|
+
need(col, 'collection name');
|
|
869
|
+
let vector;
|
|
870
|
+
if (o.vector) {
|
|
871
|
+
try { vector = JSON.parse(o.vector); } catch { vector = readJson(o.vector); }
|
|
872
|
+
} else if (o.query) {
|
|
873
|
+
const emb = await POST('/v1/inference/embeddings', { input: o.query }, o);
|
|
874
|
+
vector = emb?.data?.[0]?.embedding || emb?.embedding;
|
|
875
|
+
}
|
|
876
|
+
if (!vector || !Array.isArray(vector)) {
|
|
877
|
+
throw new Error('base vector-search requires --vector <json-array> or --query <text>');
|
|
878
|
+
}
|
|
879
|
+
const body = { vector };
|
|
880
|
+
if (o.limit) body.limit = Number(o.limit);
|
|
881
|
+
if (o.metric) body.metric = o.metric;
|
|
882
|
+
if (o.field) body.field = o.field;
|
|
883
|
+
if (o.as) body.as = o.as;
|
|
884
|
+
if (o.where) {
|
|
885
|
+
try { body.where = JSON.parse(o.where); } catch { body.where = readJson(o.where); }
|
|
886
|
+
}
|
|
887
|
+
const r = await POST(`${BASE}/collections/${encodeURIComponent(col)}/vector-search`, body, o);
|
|
888
|
+
if (o.json) {
|
|
889
|
+
console.log(JSON.stringify(r, null, 2));
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
return out(r.items || r, o, BASE_VEC_COLS);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
async function baseQuery(o, args) {
|
|
896
|
+
const [col] = args;
|
|
897
|
+
need(col, 'collection name');
|
|
898
|
+
const payload = readJsonArg(o, 'query JSON');
|
|
899
|
+
const r = await POST(`${BASE}/collections/${encodeURIComponent(col)}/query`, payload, o);
|
|
900
|
+
if (o.json) {
|
|
901
|
+
console.log(JSON.stringify(r, null, 2));
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
return out(r.items || r, o, ['id', 'similarity', 'version', 'createdAt']);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const COMMANDS = {
|
|
908
|
+
login: { fn: cmdLogin, use: 'login --email E [--org ORG] [--base URL]', doc: 'Dev login, save token + refresh' },
|
|
909
|
+
refresh: { fn: cmdRefresh, use: 'refresh [refresh-token]', doc: 'Rotate access token (atomic server-side)' },
|
|
910
|
+
'logout-all': { fn: cmdLogoutAll, use: 'logout-all [refresh-token]', doc: 'Revoke ALL sessions of token owner' },
|
|
911
|
+
verify: { fn: cmdVerify, use: 'verify', doc: 'Check current Bearer / API key' },
|
|
912
|
+
'use-key': { fn: cmdUseKey, use: 'use-key <api-key>', doc: 'Save API key instead of token' },
|
|
913
|
+
logout: { fn: async () => { saveConfig({ baseUrl: loadConfig().baseUrl }); console.log('logged out'); }, use: 'logout', doc: 'Clear saved credentials' },
|
|
914
|
+
config: { fn: cmdConfig, use: 'config', doc: 'Show saved config (secrets redacted)' },
|
|
915
|
+
whoami: { fn: cmdMe, use: 'whoami', doc: 'Current user (/v1/auth/me)' },
|
|
916
|
+
orgs: { fn: cmdOrgs, use: 'orgs [list|create <slug> [name]]', doc: 'Organizations' },
|
|
917
|
+
accounts: { fn: cmdAccounts, use: 'accounts [org] [list] | accounts create [org] <slug> [name]', doc: 'Accounts' },
|
|
918
|
+
users: { fn: cmdUsers, use: 'users [org] | users create <org> <email> [name] | users policies <user-id>', doc: 'Users + effective policies' },
|
|
919
|
+
roles: { fn: cmdRoles, use: 'roles <account> [list] | roles create <account> <slug> | roles delete <id>', doc: 'Roles' },
|
|
920
|
+
policies: { fn: cmdPolicies, use: 'policies <account> [list] | policies create <account> <slug> --file p.json | policies delete <id>', doc: 'Policies' },
|
|
921
|
+
attach: { fn: cmdAttach, use: 'attach <role-id> <policy-id>', doc: 'Attach policy to role' },
|
|
922
|
+
detach: { fn: cmdDetach, use: 'detach <role-id> <policy-id>', doc: 'Detach policy from role' },
|
|
923
|
+
assign: { fn: cmdAssign, use: 'assign <user-id> <role-id>', doc: 'Assign role to user' },
|
|
924
|
+
unassign: { fn: cmdUnassign, use: 'unassign <user-id> <role-id>', doc: 'Remove role from user' },
|
|
925
|
+
authz: { fn: cmdAuthz, use: 'authz <user> <action> <resource> [--context k=v]', doc: 'Authorize check' },
|
|
926
|
+
sim: { fn: cmdSim, use: 'sim --file policy.json <action> <resource> [--context k=v]', doc: 'Policy simulator' },
|
|
927
|
+
audit: { fn: cmdAudit, use: 'audit [--limit N] [--actor ID]', doc: 'Audit log' },
|
|
928
|
+
keys: { fn: cmdKeys, use: 'keys create <account> [--name N] | keys list <account> | keys revoke <key-id> | keys verify <api-key>', doc: 'API keys' },
|
|
929
|
+
sa: { fn: cmdSa, use: 'sa <account> [list] | sa create <account> <slug> | sa token <sa-id>', doc: 'Service accounts' },
|
|
930
|
+
credentials: { fn: cmdCredentials, use: 'credentials create <account> --name N --type bearer|basic|apiKey|oauth2 (--secret S | --secret-file F) [--expires-at ISO] | credentials [list] <account> | credentials get|delete <id> | credentials rotate <id> (--secret S | --secret-file F)', doc: 'Account credentials, secrets write-only (PROPOSED: backend in progress)' },
|
|
931
|
+
call: { fn: cmdCall, use: 'call <METHOD> <path> [--data \'{...}\'|body.json] [--file body.json]', doc: 'Generic gateway call (escape hatch for new endpoints)' },
|
|
932
|
+
workflows: { fn: cmdWorkflows, use: 'workflows [list] | workflows create --template T [--name N] | workflows create --file def.json [--name N] | workflows get|activate|deactivate <id> | workflows exec|execute <id> [--data \'{...}\'|input.json] [--file payload.json] | workflows update [--name N] [--file def.json] <id> | workflows delete <id> | workflows test-run --file def.json [--input input.json|\'{...}\'] [--workflow-id ID]', doc: 'Workflows (local engine)' },
|
|
933
|
+
executions: { fn: cmdExecutions, use: 'executions [list] [--limit N] [--offset N] [--workflow-id ID] [--status success|error|running] [--triggered-by manual|webhook|cron|test] [--include-data] | executions <id> [--include-data]', doc: 'Workflow executions ({data:[]} envelope)' },
|
|
934
|
+
webhook: { fn: cmdWebhook, use: 'webhook <path> [--data \'{...}\'|payload.json] [--file payload.json] [--method POST] [--base http://localhost:4002]', doc: 'Fire a public inbound webhook (no auth; --base = workflow origin)' },
|
|
935
|
+
templates: { fn: cmdTemplates, use: 'templates [list|<name>]', doc: 'Built-in templates' },
|
|
936
|
+
lds: { fn: cmdLds, use: 'lds licenses | lds datasources [list] [--org <orgId>] [--account <accountId>] | lds datasources create --name N --entity-type T --license SPDX [--slug S] [--context URL]... [--visibility public|internal] [--description D] | lds datasources get|delete <id> | lds datasources update <id> [--name N] [--entity-type T] [--license SPDX] [--slug S] [--context URL]... [--visibility public|internal] [--description D] | lds attributes list <id> | lds attributes set <id> (--file attrs.json | --data \'[...]\') | lds ingest <id> (--file record.json | --data \'{...}\') | lds entities <id> [--limit N] [--offset N] [--visibility public|restricted] | lds entity <id> <entity-id> | lds stats <id>', doc: 'SynapseLDS: licenses, datasources, attribute registry, ingest, NGSI-LD entities' },
|
|
937
|
+
base: { fn: cmdBase, use: 'base collections [list|create|get|delete] | base documents [list|insert|get|delete] <col> | base vector-search <col> (--vector \'[...]\'|--query \'...\') [--limit N] [--metric cosine|l2|ip] [--where \'{...}\'] | base query <col>', doc: 'SynapseBase: collections, document CRUD, vector similarity search, and hybrid queries' },
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
function help() {
|
|
941
|
+
console.log(`synapseone v${VERSION} — SynapseOne CLI (Apache-2.0)
|
|
942
|
+
|
|
943
|
+
Usage: synapseone [--base URL] [--token T | --apikey K] [--json] <command> [args]
|
|
944
|
+
|
|
945
|
+
Auth defaults: --base > $SYNAPSEONE_BASE_URL > ~/.synapseone/config.json > http://localhost:8080
|
|
946
|
+
`);
|
|
947
|
+
const names = Object.keys(COMMANDS);
|
|
948
|
+
const w = Math.max(...names.map((n) => n.length));
|
|
949
|
+
for (const n of names) console.log(` ${n.padEnd(w)} ${COMMANDS[n].doc}\n usage: synapseone ${COMMANDS[n].use}`);
|
|
950
|
+
console.log('\nExamples:\n synapseone login --email demo@acme.local\n synapseone orgs create acme2 "ACME 2"\n synapseone authz <uid> workflow:Execute "*" --context team=etl\n synapseone --json workflows list\n synapseone workflows create --template code-transform --name demo-transform\n synapseone workflows test-run --file def.json --input input.json\n synapseone workflows exec wf_abc123 --data \'{"value":21}\'\n synapseone executions list --workflow-id wf_abc123 --status success --limit 20\n synapseone webhook synapseone-event --data \'{"value":21}\' --base http://localhost:4002\n synapseone base collections create kb --mapping \'{"fields":{"vec":{"path":"embedding","type":"vector","dimension":384}}}\'\n synapseone base vector-search kb --query "authentication guide" --limit 5\n synapseone lds licenses\n synapseone lds datasources create --name buildings --entity-type Building --license CC-BY-4.0\n synapseone lds entities ds_123 --limit 20 --visibility public');
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
async function main() {
|
|
954
|
+
const { args, opts } = parse(process.argv.slice(2));
|
|
955
|
+
const [cmd, ...rest] = args;
|
|
956
|
+
if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h' || cmd === 'version' || cmd === '--version') {
|
|
957
|
+
if (cmd === 'version' || cmd === '--version') return console.log(`synapseone v${VERSION}`);
|
|
958
|
+
return help();
|
|
959
|
+
}
|
|
960
|
+
const c = COMMANDS[cmd];
|
|
961
|
+
if (!c) {
|
|
962
|
+
console.error(`unknown command '${cmd}' — try: synapseone help`);
|
|
963
|
+
process.exit(2);
|
|
964
|
+
}
|
|
965
|
+
try {
|
|
966
|
+
await c.fn({ ...opts, _: rest });
|
|
967
|
+
} catch (e) {
|
|
968
|
+
console.error(`error: ${e.message || e}`);
|
|
969
|
+
process.exit(1);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synapseone/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.7.0-dev.41",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"description": "synapseone CLI - zero-dependency command line for Tier-1 PaaS (Node 18+)",
|
|
6
|
+
"homepage": "https://synapseone.space",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/vku-open-source-2026/SynapseOne.git",
|
|
10
|
+
"directory": "tools/cli"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/vku-open-source-2026/SynapseOne/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"synapseone",
|
|
17
|
+
"cli",
|
|
18
|
+
"paas",
|
|
19
|
+
"iam",
|
|
20
|
+
"workflow",
|
|
21
|
+
"ngsi-ld",
|
|
22
|
+
"document-db",
|
|
23
|
+
"redis",
|
|
24
|
+
"queue"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"bin": {
|
|
30
|
+
"synapseone": "./bin/synapseone.js"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"bin",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"test": "node --test test/*.test.js"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
}
|
|
42
|
+
}
|