datagrok-tools 6.1.10 → 6.1.11

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.
@@ -12,11 +12,14 @@ var color = _interopRequireWildcard(require("../utils/color-utils"));
12
12
  var _testUtils = require("../utils/test-utils");
13
13
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
14
14
  const fetch = require('node-fetch');
15
+ const AdmZip = require('adm-zip');
15
16
  async function report(args) {
16
17
  const subcommand = args._[1];
17
18
  switch (subcommand) {
18
19
  case 'fetch':
19
20
  return await handleFetch(args);
21
+ case 'read':
22
+ return await handleRead(args);
20
23
  case 'resolve':
21
24
  return await handleResolve(args);
22
25
  case 'ticket':
@@ -25,6 +28,53 @@ async function report(args) {
25
28
  return false;
26
29
  }
27
30
  }
31
+ async function fetchReportMeta(url, token, number) {
32
+ const resp = await fetch(`${url}/reports?text=number%3D${encodeURIComponent(number)}`, {
33
+ headers: {
34
+ Authorization: token
35
+ }
36
+ });
37
+ if (!resp.ok) return null;
38
+ const arr = await resp.json();
39
+ if (!Array.isArray(arr) || arr.length === 0) return null;
40
+ return arr[0];
41
+ }
42
+ async function fetchReportBundle(instance, number) {
43
+ const {
44
+ url,
45
+ key
46
+ } = (0, _testUtils.getDevKey)(instance);
47
+ const token = await (0, _testUtils.getToken)(url, key);
48
+ const byNumberResp = await fetch(`${url}/reports/by-number/${encodeURIComponent(number)}/zip`, {
49
+ headers: {
50
+ Authorization: token
51
+ }
52
+ });
53
+ if (byNumberResp.ok) {
54
+ const zipBuffer = await byNumberResp.buffer();
55
+ const metaJson = await fetchReportMeta(url, token, number);
56
+ return {
57
+ zipBuffer,
58
+ metaJson
59
+ };
60
+ }
61
+ if (byNumberResp.status !== 404 && byNumberResp.status !== 405) throw new Error(`HTTP ${byNumberResp.status} from /reports/by-number/${number}/zip`);
62
+ const metaJson = await fetchReportMeta(url, token, number);
63
+ if (metaJson == null) throw new Error(`Report #${number} not found`);
64
+ const reportId = metaJson.id || metaJson.Id;
65
+ if (!reportId) throw new Error('Report found but has no id field');
66
+ const downloadResp = await fetch(`${url}/reports/${reportId}/zip`, {
67
+ headers: {
68
+ Authorization: token
69
+ }
70
+ });
71
+ if (!downloadResp.ok) throw new Error(`Report download failed (HTTP ${downloadResp.status})`);
72
+ const zipBuffer = await downloadResp.buffer();
73
+ return {
74
+ zipBuffer,
75
+ metaJson
76
+ };
77
+ }
28
78
  async function handleFetch(args) {
29
79
  const instance = args._[2];
30
80
  const number = args._[3];
@@ -33,49 +83,194 @@ async function handleFetch(args) {
33
83
  return false;
34
84
  }
35
85
  try {
86
+ console.log(`Fetching report #${number}...`);
36
87
  const {
37
- url,
38
- key
39
- } = (0, _testUtils.getDevKey)(instance);
40
- const token = await (0, _testUtils.getToken)(url, key);
41
- console.log(`Searching for report #${number}...`);
42
- const searchResp = await fetch(`${url}/reports?text=number%3D${encodeURIComponent(number)}`, {
43
- headers: {
44
- Authorization: token
45
- }
46
- });
47
- if (!searchResp.ok) {
48
- color.error(`Report search failed (HTTP ${searchResp.status})`);
49
- return false;
88
+ zipBuffer,
89
+ metaJson
90
+ } = await fetchReportBundle(instance, number);
91
+ const outputPath = _path.default.join(_os.default.tmpdir(), `report_${instance}_${number}.zip`);
92
+ _fs.default.writeFileSync(outputPath, zipBuffer);
93
+ if (metaJson != null) {
94
+ const metaPath = outputPath.replace('.zip', '_meta.json');
95
+ _fs.default.writeFileSync(metaPath, JSON.stringify(metaJson, null, 2));
50
96
  }
51
- const results = await searchResp.json();
52
- if (!Array.isArray(results) || results.length === 0) {
53
- color.error(`Report #${number} not found`);
54
- return false;
97
+ color.success(`Report saved to: ${outputPath}`);
98
+ console.log(outputPath);
99
+ return true;
100
+ } catch (err) {
101
+ color.error(`Error: ${err.message}`);
102
+ return false;
103
+ }
104
+ }
105
+ function looksLikePath(s) {
106
+ return s.includes('/') || s.includes('\\') || /\.(zip|json)$/i.test(s) || _fs.default.existsSync(s);
107
+ }
108
+ function loadFromZip(z) {
109
+ const entries = z.getEntries();
110
+ const reportEntry = entries.find(e => e.entryName.toLowerCase().endsWith('report.json'));
111
+ if (reportEntry == null) {
112
+ const names = entries.map(e => e.entryName).join(', ');
113
+ throw new Error(`report.json not found in zip. Contents: ${names}`);
114
+ }
115
+ const data = JSON.parse(reportEntry.getData().toString('utf-8'));
116
+ const d42Names = entries.map(e => e.entryName).filter(n => n.toLowerCase().endsWith('.d42'));
117
+ return {
118
+ data,
119
+ zip: z,
120
+ d42Names
121
+ };
122
+ }
123
+ function loadFromBuffer(buf) {
124
+ try {
125
+ const z = new AdmZip(buf);
126
+ return loadFromZip(z);
127
+ } catch {
128
+ const text = buf.toString('utf-8');
129
+ return {
130
+ data: JSON.parse(text),
131
+ zip: null,
132
+ d42Names: []
133
+ };
134
+ }
135
+ }
136
+ function loadFromPath(p) {
137
+ if (p.toLowerCase().endsWith('.json')) {
138
+ const text = _fs.default.readFileSync(p, 'utf-8');
139
+ return {
140
+ data: JSON.parse(text),
141
+ zip: null,
142
+ d42Names: []
143
+ };
144
+ }
145
+ return loadFromBuffer(_fs.default.readFileSync(p));
146
+ }
147
+ function unwrapEnvelope(data) {
148
+ if (data && typeof data === 'object' && '#type' in data && 'data' in data && typeof data.data === 'object' && data.data != null) {
149
+ const meta = {};
150
+ if (data.id != null) meta.id = data.id;
151
+ if (data.createdOn != null) meta.createdOn = data.createdOn;
152
+ if (data['#type'] != null) meta['#type'] = data['#type'];
153
+ return {
154
+ meta,
155
+ body: data.data
156
+ };
157
+ }
158
+ return {
159
+ meta: {},
160
+ body: data
161
+ };
162
+ }
163
+ function loadSidecar(inputPath) {
164
+ const stem = inputPath.replace(/\.[^./\\]+$/, '');
165
+ const sidecar = `${stem}_meta.json`;
166
+ if (sidecar !== inputPath && _fs.default.existsSync(sidecar)) return JSON.parse(_fs.default.readFileSync(sidecar, 'utf-8'));
167
+ return null;
168
+ }
169
+ function ensureParentDir(p) {
170
+ const dir = _path.default.dirname(p);
171
+ if (dir && dir !== '.' && !_fs.default.existsSync(dir)) _fs.default.mkdirSync(dir, {
172
+ recursive: true
173
+ });
174
+ }
175
+ function extractScreenshot(zip, body, outPath) {
176
+ if (zip != null) {
177
+ const entries = zip.getEntries();
178
+ const e = entries.find(x => x.entryName.toLowerCase().endsWith('screenshot.png'));
179
+ if (e != null) {
180
+ ensureParentDir(outPath);
181
+ _fs.default.writeFileSync(outPath, e.getData());
182
+ return true;
55
183
  }
56
- const reportData = results[0];
57
- const reportId = reportData.id || reportData.Id;
58
- if (!reportId) {
59
- color.error('Report found but has no id field');
184
+ }
185
+ const b64 = body && body.screenshot;
186
+ if (typeof b64 === 'string' && b64.length > 0) {
187
+ const stripped = b64.includes(',') ? b64.slice(b64.indexOf(',') + 1) : b64;
188
+ try {
189
+ const buf = Buffer.from(stripped, 'base64');
190
+ ensureParentDir(outPath);
191
+ _fs.default.writeFileSync(outPath, buf);
192
+ return true;
193
+ } catch {
60
194
  return false;
61
195
  }
62
- console.log(`Downloading report ${reportId}...`);
63
- const downloadResp = await fetch(`${url}/reports/${reportId}/zip`, {
64
- headers: {
65
- Authorization: token
196
+ }
197
+ return false;
198
+ }
199
+ function extractD42(zip, names, outDir) {
200
+ if (zip == null || names.length === 0) return [];
201
+ _fs.default.mkdirSync(outDir, {
202
+ recursive: true
203
+ });
204
+ const written = [];
205
+ for (var name of names) {
206
+ const entry = zip.getEntry(name);
207
+ if (entry == null) continue;
208
+ const out = _path.default.join(outDir, _path.default.basename(name));
209
+ _fs.default.writeFileSync(out, entry.getData());
210
+ written.push(out);
211
+ }
212
+ return written;
213
+ }
214
+ async function handleRead(args) {
215
+ const positional = args._.slice(2);
216
+ if (positional.length === 0) {
217
+ color.error('Usage: grok report read <path> | <instance> <number>');
218
+ return false;
219
+ }
220
+ const screenshotOut = args['extract-screenshot'];
221
+ const d42Dir = args['extract-d42'];
222
+ const extractActions = args['extract-actions'] === true;
223
+ try {
224
+ let loaded;
225
+ let sidecarMeta = null;
226
+ let inputPath = null;
227
+ let networkBase = null;
228
+ if (positional.length >= 2 && !looksLikePath(positional[0])) {
229
+ const [instance, number] = positional;
230
+ const bundle = await fetchReportBundle(instance, number);
231
+ loaded = loadFromBuffer(bundle.zipBuffer);
232
+ sidecarMeta = bundle.metaJson;
233
+ networkBase = _path.default.join(_os.default.tmpdir(), `report_${instance}_${number}`);
234
+ } else {
235
+ inputPath = _path.default.resolve(positional[0]);
236
+ if (!_fs.default.existsSync(inputPath)) {
237
+ color.error(`File not found: ${inputPath}`);
238
+ return false;
66
239
  }
67
- });
68
- if (!downloadResp.ok) {
69
- color.error(`Report download failed (HTTP ${downloadResp.status})`);
70
- return false;
240
+ loaded = loadFromPath(inputPath);
241
+ sidecarMeta = loadSidecar(inputPath);
71
242
  }
72
- const buffer = await downloadResp.buffer();
73
- const outputPath = _path.default.join(_os.default.tmpdir(), `report_${instance}_${number}.zip`);
74
- _fs.default.writeFileSync(outputPath, buffer);
75
- const metaPath = outputPath.replace('.zip', '_meta.json');
76
- _fs.default.writeFileSync(metaPath, JSON.stringify(reportData, null, 2));
77
- color.success(`Report saved to: ${outputPath}`);
78
- console.log(outputPath);
243
+ const {
244
+ meta: envelopeMeta,
245
+ body
246
+ } = unwrapEnvelope(loaded.data);
247
+ const meta = Object.assign({}, envelopeMeta, sidecarMeta || {});
248
+ const output = {
249
+ meta,
250
+ ...body
251
+ };
252
+ const files = {};
253
+ if (screenshotOut) {
254
+ if (extractScreenshot(loaded.zip, body, screenshotOut)) {
255
+ files.screenshot = screenshotOut;
256
+ output.screenshot = screenshotOut;
257
+ }
258
+ }
259
+ if (d42Dir) {
260
+ const written = extractD42(loaded.zip, loaded.d42Names, d42Dir);
261
+ if (written.length > 0) files.d42 = written;
262
+ }
263
+ if (extractActions && Array.isArray(body && body.actions)) {
264
+ const stem = inputPath != null ? inputPath.replace(/\.[^./\\]+$/, '') : networkBase;
265
+ if (stem != null) {
266
+ const actionsPath = `${stem}_actions.json`;
267
+ _fs.default.writeFileSync(actionsPath, JSON.stringify(body.actions, null, 2));
268
+ files.actions = actionsPath;
269
+ }
270
+ }
271
+ if (Object.keys(files).length > 0) output.files = files;
272
+ process.stdout.write(JSON.stringify(output));
273
+ process.stdout.write('\n');
79
274
  return true;
80
275
  } catch (err) {
81
276
  color.error(`Error: ${err.message}`);
@@ -14,7 +14,7 @@ var _serverOutput = require("../utils/server-output");
14
14
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
15
15
  /// Docs: [Grok Dapi](/docs/plans/grok-dapi/)
16
16
 
17
- const ENTITIES = ['users', 'groups', 'functions', 'connections', 'queries', 'scripts', 'packages', 'reports', 'files'];
17
+ const ENTITIES = ['users', 'groups', 'functions', 'connections', 'queries', 'scripts', 'packages', 'reports', 'files', 'tables'];
18
18
  const VERBS = ['list', 'get', 'delete'];
19
19
  async function server(argv) {
20
20
  const args = argv['_'].slice(1);
@@ -43,7 +43,9 @@ async function server(argv) {
43
43
  if (entity === 'batch') return handleBatch(dapi, argv, verb, rest, output);
44
44
  if (entity === 'raw') return handleRaw(dapi, verb, rest, output);
45
45
  if (entity === 'describe') return handleDescribe(dapi, verb ?? rest[0], output);
46
+ if (entity === 'healthcheck') return handleHealthcheck(dapi, argv, output);
46
47
  if (entity === 'functions' && verb === 'run') return handleFuncRun(dapi, rest, argv, output);
48
+ if (entity === 'functions' && verb === 'list') return handleFunctionsList(dapi, argv, limit, offset, filter, output);
47
49
  if (entity === 'files' && verb === 'list') {
48
50
  const path = rest[0] ?? '';
49
51
  const result = await dapi.files.list(path, recursive);
@@ -60,6 +62,7 @@ async function server(argv) {
60
62
  if (output !== 'quiet') console.log(`Deleted ${rest[0]}`);
61
63
  return true;
62
64
  }
65
+ if (entity === 'files' && verb === 'put') return handleFilesPut(dapi, rest, output);
63
66
  if (entity === 'shares' && verb === 'add') return handleSharesAdd(dapi, rest, argv, output);
64
67
  if (entity === 'shares' && verb === 'list') return handleSharesList(dapi, rest, output);
65
68
  if (entity === 'users' && verb === 'save') return handleUserSave(dapi, argv, output);
@@ -70,6 +73,10 @@ async function server(argv) {
70
73
  if (entity === 'groups' && verb === 'remove-members') return handleGroupRemoveMembers(dapi, rest, argv, output);
71
74
  if (entity === 'groups' && verb === 'list-members') return handleGroupListMembers(dapi, rest, argv, output);
72
75
  if (entity === 'groups' && verb === 'list-memberships') return handleGroupListMemberships(dapi, rest, argv, output);
76
+ if (entity === 'users' && verb === 'block') return handleUserBlock(dapi, rest, output);
77
+ if (entity === 'users' && verb === 'unblock') return handleUserUnblock(dapi, rest, output);
78
+ if (entity === 'tables' && verb === 'download') return handleTablesDownload(dapi, rest, argv, output);
79
+ if (entity === 'tables' && verb === 'upload') return handleTablesUpload(dapi, rest, output);
73
80
  const source = dapi[entity];
74
81
  if (!source || !ENTITIES.includes(entity)) {
75
82
  (0, _serverOutput.printError)(new Error(`Unknown entity type: '${entity}'. Valid: ${ENTITIES.join(', ')}`));
@@ -110,6 +117,112 @@ async function server(argv) {
110
117
  return false;
111
118
  }
112
119
  }
120
+ async function handleFilesPut(dapi, rest, output) {
121
+ const [localPath, remotePath] = rest;
122
+ if (!localPath || !remotePath) {
123
+ (0, _serverOutput.printError)(new Error('Usage: grok s files put <local-path> <remote-path>\n e.g. grok s files put ./smiles.csv "System:DemoFiles/smiles.csv"'));
124
+ return false;
125
+ }
126
+ if (!fs.existsSync(localPath)) {
127
+ (0, _serverOutput.printError)(new Error(`Local file not found: ${localPath}`));
128
+ return false;
129
+ }
130
+ const result = await dapi.files.put(localPath, remotePath);
131
+ if (output === 'quiet') return true;
132
+ if (output === 'json') (0, _serverOutput.printOutput)(result, output);else console.log(`Uploaded ${result.size} bytes to ${result.path}`);
133
+ return true;
134
+ }
135
+ async function handleUserBlock(dapi, rest, output) {
136
+ if (!rest[0]) {
137
+ (0, _serverOutput.printError)(new Error('Usage: grok s users block <id-or-login>'));
138
+ return false;
139
+ }
140
+ const user = await dapi.users.find(rest[0]);
141
+ await dapi.users.block(user);
142
+ if (output !== 'quiet') console.log(`Blocked ${user?.login ?? rest[0]}`);
143
+ return true;
144
+ }
145
+ async function handleUserUnblock(dapi, rest, output) {
146
+ if (!rest[0]) {
147
+ (0, _serverOutput.printError)(new Error('Usage: grok s users unblock <id-or-login>'));
148
+ return false;
149
+ }
150
+ const user = await dapi.users.find(rest[0]);
151
+ await dapi.users.unblock(user);
152
+ if (output !== 'quiet') console.log(`Unblocked ${user?.login ?? rest[0]}`);
153
+ return true;
154
+ }
155
+ async function handleTablesDownload(dapi, rest, argv, output) {
156
+ const name = rest[0];
157
+ if (!name) {
158
+ (0, _serverOutput.printError)(new Error('Usage: grok s tables download <name-or-id> [-O <file>]'));
159
+ return false;
160
+ }
161
+ const csv = await dapi.tables.download(name);
162
+ const outFile = argv['output-file'] ?? argv.O;
163
+ if (outFile) {
164
+ fs.writeFileSync(outFile, csv);
165
+ if (output !== 'quiet') console.log(`Wrote ${csv.length} bytes to ${outFile}`);
166
+ } else process.stdout.write(csv);
167
+ return true;
168
+ }
169
+ async function handleTablesUpload(dapi, rest, output) {
170
+ const [name, localPath] = rest;
171
+ if (!name || !localPath) {
172
+ (0, _serverOutput.printError)(new Error('Usage: grok s tables upload <name> <file.csv>'));
173
+ return false;
174
+ }
175
+ if (!fs.existsSync(localPath)) {
176
+ (0, _serverOutput.printError)(new Error(`Local file not found: ${localPath}`));
177
+ return false;
178
+ }
179
+ const result = await dapi.tables.upload(name, localPath);
180
+ if (output === 'quiet') console.log(result?.ID ?? result?.id ?? '');else (0, _serverOutput.printOutput)(result, output);
181
+ return true;
182
+ }
183
+
184
+ /**
185
+ * Normalize common type aliases to the server's `source` field. The server uses
186
+ * four discriminators: `function` (standalone), `function-package` (bundled in a
187
+ * plugin package), `script`, `data-query`. `--type function` broadens to both
188
+ * standalone and package funcs since that matches user intent.
189
+ */
190
+ function funcTypeClause(t) {
191
+ const v = t.toLowerCase();
192
+ if (v === 'query' || v === 'data-query' || v === 'dataquery') return 'source="data-query"';
193
+ if (v === 'script') return 'source="script"';
194
+ if (v === 'package' || v === 'package-function' || v === 'packagefunc') return 'source="function-package"';
195
+ if (v === 'function' || v === 'func') return '(source="function" or source="function-package")';
196
+ return `source="${v}"`;
197
+ }
198
+ async function handleFunctionsList(dapi, argv, limit, offset, userFilter, output) {
199
+ const typeArg = argv.type;
200
+ const language = argv.language;
201
+ const pkg = argv.package;
202
+
203
+ // --language only makes sense for scripts; if the user left --type off, imply 'script'.
204
+ const effectiveType = typeArg ?? (language ? 'script' : undefined);
205
+ const clauses = [];
206
+ if (effectiveType) clauses.push(funcTypeClause(effectiveType));
207
+ if (pkg) clauses.push(`package.shortName="${pkg}"`);
208
+ if (userFilter) clauses.push(`(${userFilter})`);
209
+ const composed = clauses.join(' and ');
210
+
211
+ // Pull a generous batch; the server's public functions endpoint ignores limit/page
212
+ // today, so pagination is done client-side below.
213
+ let results = await dapi.functions.filter(composed).by(10000).list();
214
+
215
+ // `language` lives on the Script subclass and isn't queryable via smart filter —
216
+ // post-filter in Node. Same for any other subclass-only attribute we add later.
217
+ if (language) {
218
+ const want = language.toLowerCase();
219
+ results = results.filter(f => (f?.language ?? '').toLowerCase() === want);
220
+ }
221
+ if (offset > 0) results = results.slice(offset);
222
+ if (limit > 0) results = results.slice(0, limit);
223
+ (0, _serverOutput.printOutput)(results, output);
224
+ return true;
225
+ }
113
226
  async function handleRaw(dapi, method, rest, output) {
114
227
  if (!method || !rest[0]) {
115
228
  (0, _serverOutput.printError)(new Error('Usage: grok s raw <METHOD> <path>'));
@@ -120,6 +233,24 @@ async function handleRaw(dapi, method, rest, output) {
120
233
  (0, _serverOutput.printOutput)(result, output);
121
234
  return true;
122
235
  }
236
+ async function handleHealthcheck(dapi, argv, output) {
237
+ const module = argv.module;
238
+ const path = module ? `/api/public/v1/healthcheck?module=${encodeURIComponent(module)}` : '/api/public/v1/healthcheck';
239
+ const result = await dapi.raw('GET', path);
240
+ if (output === 'json') {
241
+ (0, _serverOutput.printOutput)(result, output);
242
+ return true;
243
+ }
244
+ if (output !== 'quiet') {
245
+ console.log(`status: ${result?.status ?? ''}`);
246
+ console.log(`server: ${result?.server ?? ''}`);
247
+ console.log(`version: ${result?.version ?? ''}`);
248
+ console.log(`time: ${result?.time ?? ''}`);
249
+ console.log('');
250
+ }
251
+ (0, _serverOutput.printOutput)(result?.services ?? [], output);
252
+ return true;
253
+ }
123
254
  async function handleDescribe(dapi, entityType, output) {
124
255
  if (!entityType) {
125
256
  (0, _serverOutput.printError)(new Error('Usage: grok s describe <entity-type>'));
@@ -445,7 +576,7 @@ Usage: grok server <entity> <verb> [args] [options]
445
576
  Manage a Datagrok server from the command line.
446
577
 
447
578
  Entities:
448
- users, groups, functions, connections, queries, scripts, packages, reports, files
579
+ users, groups, functions, connections, queries, scripts, packages, reports, files, tables
449
580
 
450
581
  Verbs:
451
582
  list List entities
@@ -454,9 +585,16 @@ Verbs:
454
585
 
455
586
  Special commands:
456
587
  grok s functions run <Name:func(args)> Call a function
457
- grok s files list [path] [-r] List files (recursive with -r)
588
+ grok s functions list [--type <t>] [--language <l>] [--package <p>] [--filter <expr>]
589
+ Type: script|query|function|package
590
+ Language applies to scripts (python, r, julia, nodejs, octave, grok)
591
+ grok s files list <path> [-r] List files (recursive with -r)
592
+ grok s files get <path> Download a file (returns bytes)
593
+ grok s files delete <path> Delete a file
594
+ grok s files put <local> <remote> Upload a local file
458
595
  grok s raw <METHOD> <path> Hit any API endpoint
459
596
  grok s describe <entity-type> Show entity JSON schema
597
+ grok s healthcheck [--module <name>] Check server + per-module health
460
598
  grok s shares add <entity> <group>[,<group>...] [--access View|Edit]
461
599
  Share an entity with one or more groups
462
600
  grok s shares list <entity-id> List who an entity (UUID) is shared with
@@ -471,6 +609,10 @@ Special commands:
471
609
  grok s groups remove-members <group> <m>... Remove members (no-op if not a member)
472
610
  grok s groups list-members <group> [--admin] List members (optionally filter by admin)
473
611
  grok s groups list-memberships <group> [--admin] List parent groups
612
+ grok s users block <id-or-login> Block a user from the platform
613
+ grok s users unblock <id-or-login> Unblock a previously blocked user
614
+ grok s tables upload <name> <file.csv> Upload a CSV as a Datagrok table
615
+ grok s tables download <name-or-id> [-O <file>] Download a table as CSV (stdout by default)
474
616
  grok s batch <entity> <verb> arg1 [arg2 ...] Batch operation (one round-trip)
475
617
  grok s batch <entity> <verb> --json params.json Batch from JSON array
476
618
  grok s batch manifest.json Run a workflow manifest
@@ -483,6 +625,10 @@ Options:
483
625
  --offset <n> Start offset (default: 0)
484
626
  -r, --recursive Recursive (for files list)
485
627
  --json <file> Read function parameters or batch params from JSON file
628
+ -O, --output-file Write table download to a file instead of stdout
629
+ --type <t> Function discriminator: script | query | function | package
630
+ --language <lang> Script language: python, r, julia, nodejs, octave, grok
631
+ --package <name> Restrict to functions belonging to a package (by short name)
486
632
 
487
633
  Batch manifest options (in manifest.json):
488
634
  stopOnError Stop on first failure (default: true)
@@ -504,7 +650,11 @@ Examples:
504
650
  grok s connections test --json conn.json
505
651
  grok s functions run 'Chem:smilesToMw("ccc")'
506
652
  grok s functions run Chem:test --json params.json
653
+ grok s functions list --type script --language python --limit 20
654
+ grok s functions list --package Chem --type query
655
+ grok s functions list --package PowerPack --type package --filter 'name contains "grid"'
507
656
  grok s files list "System:AppData" -r
657
+ grok s files put ./smiles.csv "System:DemoFiles/smiles.csv"
508
658
  grok s raw GET /api/users/current
509
659
  grok s describe connections
510
660
  grok s users list --host dev
@@ -513,8 +663,8 @@ Examples:
513
663
  grok s groups remove-members Admins alice
514
664
  grok s groups list-members Admins --admin
515
665
  grok s groups list-memberships alice
516
- grok s batch files delete "System:AppData/old.txt" "System:AppData/tmp.txt"
666
+ grok s batch files delete "System:AppData/old.txt" "System:DemoFiles/tmp.txt"
517
667
  grok s batch users create --json users.json
518
668
  grok s batch manifest.json
519
- grok s batch files delete "System:AppData/a" "System:AppData/b" --output json
669
+ grok s batch files delete "System:AppData/a" "System:DemoFiles/b" --output json
520
670
  `;