@fnndsc/chili 3.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +113 -0
  3. package/dist/chefs/chefs.d.ts +15 -0
  4. package/dist/chefs/chefs.js +195 -0
  5. package/dist/commands/compute/fields.d.ts +6 -0
  6. package/dist/commands/compute/fields.js +13 -0
  7. package/dist/commands/compute/list.d.ts +19 -0
  8. package/dist/commands/compute/list.js +20 -0
  9. package/dist/commands/connect/login.d.ts +16 -0
  10. package/dist/commands/connect/login.js +18 -0
  11. package/dist/commands/connect/logout.d.ts +6 -0
  12. package/dist/commands/connect/logout.js +16 -0
  13. package/dist/commands/feed/comments.d.ts +41 -0
  14. package/dist/commands/feed/comments.js +45 -0
  15. package/dist/commands/feed/create.d.ts +13 -0
  16. package/dist/commands/feed/create.js +40 -0
  17. package/dist/commands/feed/note.d.ts +21 -0
  18. package/dist/commands/feed/note.js +24 -0
  19. package/dist/commands/feeds/delete.d.ts +14 -0
  20. package/dist/commands/feeds/delete.js +34 -0
  21. package/dist/commands/feeds/fields.d.ts +6 -0
  22. package/dist/commands/feeds/fields.js +17 -0
  23. package/dist/commands/feeds/list.d.ts +17 -0
  24. package/dist/commands/feeds/list.js +38 -0
  25. package/dist/commands/feeds/share.d.ts +17 -0
  26. package/dist/commands/feeds/share.js +19 -0
  27. package/dist/commands/file/view.d.ts +8 -0
  28. package/dist/commands/file/view.js +25 -0
  29. package/dist/commands/files/delete.d.ts +16 -0
  30. package/dist/commands/files/delete.js +36 -0
  31. package/dist/commands/files/fields.d.ts +7 -0
  32. package/dist/commands/files/fields.js +18 -0
  33. package/dist/commands/files/list.d.ts +11 -0
  34. package/dist/commands/files/list.js +23 -0
  35. package/dist/commands/fs/cat.d.ts +15 -0
  36. package/dist/commands/fs/cat.js +26 -0
  37. package/dist/commands/fs/cp.d.ts +17 -0
  38. package/dist/commands/fs/cp.js +26 -0
  39. package/dist/commands/fs/create.d.ts +15 -0
  40. package/dist/commands/fs/create.js +83 -0
  41. package/dist/commands/fs/download.d.ts +25 -0
  42. package/dist/commands/fs/download.js +235 -0
  43. package/dist/commands/fs/edit.d.ts +16 -0
  44. package/dist/commands/fs/edit.js +25 -0
  45. package/dist/commands/fs/ls.d.ts +18 -0
  46. package/dist/commands/fs/ls.js +142 -0
  47. package/dist/commands/fs/mkdir.d.ts +8 -0
  48. package/dist/commands/fs/mkdir.js +21 -0
  49. package/dist/commands/fs/mv.d.ts +13 -0
  50. package/dist/commands/fs/mv.js +73 -0
  51. package/dist/commands/fs/rm.d.ts +24 -0
  52. package/dist/commands/fs/rm.js +108 -0
  53. package/dist/commands/fs/touch.d.ts +16 -0
  54. package/dist/commands/fs/touch.js +62 -0
  55. package/dist/commands/fs/upload.d.ts +55 -0
  56. package/dist/commands/fs/upload.js +221 -0
  57. package/dist/commands/groups/fields.d.ts +6 -0
  58. package/dist/commands/groups/fields.js +13 -0
  59. package/dist/commands/groups/list.d.ts +16 -0
  60. package/dist/commands/groups/list.js +26 -0
  61. package/dist/commands/man/doc.d.ts +16 -0
  62. package/dist/commands/man/doc.js +32 -0
  63. package/dist/commands/man/topics.d.ts +6 -0
  64. package/dist/commands/man/topics.js +27 -0
  65. package/dist/commands/pipeline/fields.d.ts +6 -0
  66. package/dist/commands/pipeline/fields.js +13 -0
  67. package/dist/commands/pipeline/run.d.ts +18 -0
  68. package/dist/commands/pipeline/run.js +33 -0
  69. package/dist/commands/plugin/readme.d.ts +14 -0
  70. package/dist/commands/plugin/readme.js +49 -0
  71. package/dist/commands/plugin/run.d.ts +10 -0
  72. package/dist/commands/plugin/run.js +29 -0
  73. package/dist/commands/plugin/search.d.ts +7 -0
  74. package/dist/commands/plugin/search.js +18 -0
  75. package/dist/commands/plugininstances/fields.d.ts +6 -0
  76. package/dist/commands/plugininstances/fields.js +13 -0
  77. package/dist/commands/plugininstances/list.d.ts +16 -0
  78. package/dist/commands/plugininstances/list.js +26 -0
  79. package/dist/commands/pluginmetas/fields.d.ts +6 -0
  80. package/dist/commands/pluginmetas/fields.js +13 -0
  81. package/dist/commands/pluginmetas/list.d.ts +16 -0
  82. package/dist/commands/pluginmetas/list.js +26 -0
  83. package/dist/commands/plugins/add.d.ts +34 -0
  84. package/dist/commands/plugins/add.js +374 -0
  85. package/dist/commands/plugins/delete.d.ts +14 -0
  86. package/dist/commands/plugins/delete.js +34 -0
  87. package/dist/commands/plugins/fields.d.ts +6 -0
  88. package/dist/commands/plugins/fields.js +17 -0
  89. package/dist/commands/plugins/list.d.ts +17 -0
  90. package/dist/commands/plugins/list.js +40 -0
  91. package/dist/commands/plugins/overview.d.ts +6 -0
  92. package/dist/commands/plugins/overview.js +17 -0
  93. package/dist/commands/store/list.d.ts +23 -0
  94. package/dist/commands/store/list.js +48 -0
  95. package/dist/commands/tags/fields.d.ts +6 -0
  96. package/dist/commands/tags/fields.js +13 -0
  97. package/dist/commands/tags/list.d.ts +16 -0
  98. package/dist/commands/tags/list.js +26 -0
  99. package/dist/commands/workflows/fields.d.ts +6 -0
  100. package/dist/commands/workflows/fields.js +13 -0
  101. package/dist/commands/workflows/list.d.ts +16 -0
  102. package/dist/commands/workflows/list.js +26 -0
  103. package/dist/config/colorConfig.d.ts +56 -0
  104. package/dist/config/colorConfig.js +161 -0
  105. package/dist/connect/connectHandler.d.ts +15 -0
  106. package/dist/connect/connectHandler.js +48 -0
  107. package/dist/context/contextCommand.d.ts +13 -0
  108. package/dist/context/contextCommand.js +193 -0
  109. package/dist/controllers/baseController.d.ts +43 -0
  110. package/dist/controllers/baseController.js +67 -0
  111. package/dist/controllers/feedController.d.ts +35 -0
  112. package/dist/controllers/feedController.js +53 -0
  113. package/dist/controllers/fileController.d.ts +56 -0
  114. package/dist/controllers/fileController.js +93 -0
  115. package/dist/controllers/pluginContextController.d.ts +24 -0
  116. package/dist/controllers/pluginContextController.js +49 -0
  117. package/dist/controllers/pluginController.d.ts +70 -0
  118. package/dist/controllers/pluginController.js +98 -0
  119. package/dist/controllers/pluginMetaController.d.ts +41 -0
  120. package/dist/controllers/pluginMetaController.js +52 -0
  121. package/dist/feeds/feedHandler.d.ts +64 -0
  122. package/dist/feeds/feedHandler.js +203 -0
  123. package/dist/filesystem/fileGroupHandler.d.ts +92 -0
  124. package/dist/filesystem/fileGroupHandler.js +318 -0
  125. package/dist/filesystem/filesystemHandler.d.ts +12 -0
  126. package/dist/filesystem/filesystemHandler.js +135 -0
  127. package/dist/filesystem/inodeCommand.d.ts +12 -0
  128. package/dist/filesystem/inodeCommand.js +37 -0
  129. package/dist/handlers/baseGroupHandler.d.ts +103 -0
  130. package/dist/handlers/baseGroupHandler.js +342 -0
  131. package/dist/index.d.ts +7 -0
  132. package/dist/index.js +239 -0
  133. package/dist/lfs/lfs.d.ts +25 -0
  134. package/dist/lfs/lfs.js +74 -0
  135. package/dist/man/man.d.ts +15 -0
  136. package/dist/man/man.js +31 -0
  137. package/dist/man/renderer.d.ts +26 -0
  138. package/dist/man/renderer.js +187 -0
  139. package/dist/models/feed.d.ts +18 -0
  140. package/dist/models/feed.js +8 -0
  141. package/dist/models/listing.d.ts +33 -0
  142. package/dist/models/listing.js +9 -0
  143. package/dist/models/plugin.d.ts +27 -0
  144. package/dist/models/plugin.js +1 -0
  145. package/dist/models/resource.d.ts +39 -0
  146. package/dist/models/resource.js +9 -0
  147. package/dist/pacs/pacsQueryHandler.d.ts +55 -0
  148. package/dist/pacs/pacsQueryHandler.js +196 -0
  149. package/dist/pacs/pacsQueryPayload.d.ts +19 -0
  150. package/dist/pacs/pacsQueryPayload.js +44 -0
  151. package/dist/pacs/pacsResultRender.d.ts +20 -0
  152. package/dist/pacs/pacsResultRender.js +141 -0
  153. package/dist/pacs/pacsRetrieveHandler.d.ts +87 -0
  154. package/dist/pacs/pacsRetrieveHandler.js +272 -0
  155. package/dist/pacs/pacsServerHandler.d.ts +15 -0
  156. package/dist/pacs/pacsServerHandler.js +32 -0
  157. package/dist/path/pathCommand.d.ts +64 -0
  158. package/dist/path/pathCommand.js +805 -0
  159. package/dist/path/pathMapper.d.ts +189 -0
  160. package/dist/path/pathMapper.js +382 -0
  161. package/dist/plugins/pluginGroupHandler.d.ts +42 -0
  162. package/dist/plugins/pluginGroupHandler.js +90 -0
  163. package/dist/plugins/pluginHandler.d.ts +87 -0
  164. package/dist/plugins/pluginHandler.js +303 -0
  165. package/dist/plugins/pluginMetaHandler.d.ts +21 -0
  166. package/dist/plugins/pluginMetaHandler.js +27 -0
  167. package/dist/screen/screen.d.ts +214 -0
  168. package/dist/screen/screen.js +478 -0
  169. package/dist/utils/admin_prompt.d.ts +42 -0
  170. package/dist/utils/admin_prompt.js +105 -0
  171. package/dist/utils/cli.d.ts +67 -0
  172. package/dist/utils/cli.js +112 -0
  173. package/dist/utils/docker.d.ts +74 -0
  174. package/dist/utils/docker.js +155 -0
  175. package/dist/utils/input_format.d.ts +77 -0
  176. package/dist/utils/input_format.js +135 -0
  177. package/dist/utils/sort.d.ts +29 -0
  178. package/dist/utils/sort.js +63 -0
  179. package/dist/utils/ui.d.ts +7 -0
  180. package/dist/utils/ui.js +26 -0
  181. package/dist/utils.d.ts +15 -0
  182. package/dist/utils.js +14 -0
  183. package/dist/views/compute.d.ts +17 -0
  184. package/dist/views/compute.js +40 -0
  185. package/dist/views/connect.d.ts +13 -0
  186. package/dist/views/connect.js +35 -0
  187. package/dist/views/feed.d.ts +44 -0
  188. package/dist/views/feed.js +110 -0
  189. package/dist/views/file.d.ts +25 -0
  190. package/dist/views/file.js +71 -0
  191. package/dist/views/fs.d.ts +51 -0
  192. package/dist/views/fs.js +105 -0
  193. package/dist/views/ls.d.ts +37 -0
  194. package/dist/views/ls.js +190 -0
  195. package/dist/views/plugin.d.ts +28 -0
  196. package/dist/views/plugin.js +71 -0
  197. package/dist/views/pluginParameters.d.ts +17 -0
  198. package/dist/views/pluginParameters.js +79 -0
  199. package/docs/00_intro.adoc +25 -0
  200. package/docs/01_setup.adoc +63 -0
  201. package/docs/02_context.adoc +128 -0
  202. package/docs/03_searchable.adoc +134 -0
  203. package/docs/10_everyday_examples.adoc +9 -0
  204. package/docs/11_upload_data.adoc +146 -0
  205. package/docs/12_create_a_feed.adoc +9 -0
  206. package/docs/13_run_a_plugin.adoc +271 -0
  207. package/docs/20_plugins.adoc +16 -0
  208. package/docs/21_pluginReadme.adoc +123 -0
  209. package/docs/30_filesystem.adoc +93 -0
  210. package/docs/31_fileops.adoc +51 -0
  211. package/docs/32_file_content.adoc +471 -0
  212. package/docs/_architecture.adoc +48 -0
  213. package/docs/_deepContext.adoc +84 -0
  214. package/docs/_roadmap.adoc +42 -0
  215. package/docs/_searchable.adoc +187 -0
  216. package/docs/_state.adoc +68 -0
  217. package/docs/pacs.adoc +101 -0
  218. package/docs/pathMapper.md +246 -0
  219. package/package.json +92 -0
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @file Implements the core logic for the `rm` command in the ChRIS CLI.
3
+ *
4
+ * This module provides functionality to delete files, directories, and links
5
+ * from the ChRIS file system. It determines the type of the target and calls
6
+ * the appropriate delete function.
7
+ *
8
+ * @module
9
+ */
10
+ import { files_listAll } from "@fnndsc/salsa";
11
+ import { files_delete as salsaFiles_delete } from "@fnndsc/salsa";
12
+ import { path_resolveChrisFs } from "../../utils/cli.js";
13
+ /**
14
+ * Finds a file/dir/link by path and returns its type and ID.
15
+ *
16
+ * @param targetPath - The resolved path to search for.
17
+ * @returns An object with type, id, and name, or null if not found.
18
+ */
19
+ async function pathInfo_find(targetPath) {
20
+ const parts = targetPath.split('/').filter((p) => p);
21
+ const basename = parts.pop() || '';
22
+ const parentPath = '/' + parts.join('/');
23
+ const fetchOpts = { limit: 1000, offset: 0 };
24
+ // Search in all three asset types within the parent directory
25
+ const [dirs, files, links] = await Promise.all([
26
+ files_listAll(fetchOpts, 'dirs', parentPath),
27
+ files_listAll(fetchOpts, 'files', parentPath),
28
+ files_listAll(fetchOpts, 'links', parentPath)
29
+ ]);
30
+ // Helper to extract name from raw object
31
+ const extractName = (raw) => {
32
+ let name = raw.fname || raw.path || "";
33
+ if (name.includes('/')) {
34
+ name = name.split('/').pop() || name;
35
+ }
36
+ return name;
37
+ };
38
+ if (dirs && dirs.tableData) {
39
+ for (const d of dirs.tableData) {
40
+ if (extractName(d) === basename && d.id) {
41
+ return { type: 'dir', id: Number(d.id), name: basename };
42
+ }
43
+ }
44
+ }
45
+ if (files && files.tableData) {
46
+ for (const f of files.tableData) {
47
+ if (extractName(f) === basename && f.id) {
48
+ return { type: 'file', id: Number(f.id), name: basename };
49
+ }
50
+ }
51
+ }
52
+ if (links && links.tableData) {
53
+ for (const l of links.tableData) {
54
+ if (extractName(l) === basename && l.id) {
55
+ return { type: 'link', id: Number(l.id), name: basename };
56
+ }
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+ /**
62
+ * Core logic for the 'rm' command.
63
+ *
64
+ * @param targetPath - The path to delete.
65
+ * @param options - Options including recursive and force flags.
66
+ * @returns A Promise resolving to RmResult.
67
+ */
68
+ export async function files_rm(targetPath, options = {}) {
69
+ try {
70
+ const resolvedPath = await path_resolveChrisFs(targetPath, {});
71
+ const info = await pathInfo_find(resolvedPath);
72
+ if (!info) {
73
+ return {
74
+ success: false,
75
+ path: resolvedPath,
76
+ type: null,
77
+ error: `No such file or directory: ${resolvedPath}`
78
+ };
79
+ }
80
+ // Check if it's a directory and recursive flag is not set
81
+ if (info.type === 'dir' && !options.recursive) {
82
+ return {
83
+ success: false,
84
+ path: resolvedPath,
85
+ type: info.type,
86
+ error: `Cannot remove directory '${resolvedPath}': is a directory (use -r for recursive delete)`
87
+ };
88
+ }
89
+ // Delete the resource
90
+ const assetName = info.type === 'dir' ? 'dirs' : info.type === 'link' ? 'links' : 'files';
91
+ const deleted = await salsaFiles_delete(info.id, assetName);
92
+ return {
93
+ success: deleted,
94
+ path: resolvedPath,
95
+ type: info.type,
96
+ error: deleted ? undefined : `Failed to delete ${info.type}: ${resolvedPath}`
97
+ };
98
+ }
99
+ catch (error) {
100
+ const errorMsg = error instanceof Error ? error.message : String(error);
101
+ return {
102
+ success: false,
103
+ path: targetPath,
104
+ type: null,
105
+ error: errorMsg
106
+ };
107
+ }
108
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Options for the touch command.
3
+ */
4
+ export interface TouchOptions {
5
+ withContents?: string;
6
+ withContentsFromFile?: string;
7
+ }
8
+ /**
9
+ * Core logic for the 'touch' command.
10
+ * This function does not perform any console output.
11
+ *
12
+ * @param filePath - The full or relative ChRIS path for the new file.
13
+ * @param options - Optional parameters for content injection.
14
+ * @returns A Promise resolving to true on success, false on failure.
15
+ */
16
+ export declare function files_touch(filePath: string, options?: TouchOptions): Promise<boolean>;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @file Implements the core logic for the `touch` command in the ChRIS CLI.
3
+ *
4
+ * This module provides functionality to create empty files or files with content
5
+ * within the ChRIS file system. It interacts with the `@fnndsc/salsa` library.
6
+ *
7
+ * @module
8
+ */
9
+ import fs from 'fs';
10
+ import { files_touch as salsaFiles_touch } from "@fnndsc/salsa";
11
+ import { path_resolveChrisFs } from "../../utils/cli.js";
12
+ import { errorStack, Ok, Err } from "@fnndsc/cumin";
13
+ /**
14
+ * Reads content from a local file with proper error handling.
15
+ *
16
+ * @param localFilePath - Path to the local file on the host system.
17
+ * @returns Result<string> containing file contents or error.
18
+ */
19
+ function fileContents_read(localFilePath) {
20
+ try {
21
+ if (!fs.existsSync(localFilePath)) {
22
+ errorStack.stack_push("error", `Local file not found: ${localFilePath}`);
23
+ return Err();
24
+ }
25
+ const content = fs.readFileSync(localFilePath, 'utf-8');
26
+ return Ok(content);
27
+ }
28
+ catch (error) {
29
+ const msg = error instanceof Error ? error.message : String(error);
30
+ errorStack.stack_push("error", `Failed to read local file ${localFilePath}: ${msg}`);
31
+ return Err();
32
+ }
33
+ }
34
+ /**
35
+ * Core logic for the 'touch' command.
36
+ * This function does not perform any console output.
37
+ *
38
+ * @param filePath - The full or relative ChRIS path for the new file.
39
+ * @param options - Optional parameters for content injection.
40
+ * @returns A Promise resolving to true on success, false on failure.
41
+ */
42
+ export async function files_touch(filePath, options = {}) {
43
+ const resolvedPath = await path_resolveChrisFs(filePath, {});
44
+ // Determine content to write
45
+ let content;
46
+ if (options.withContentsFromFile) {
47
+ const result = fileContents_read(options.withContentsFromFile);
48
+ if (!result.ok) {
49
+ return false;
50
+ }
51
+ content = result.value;
52
+ }
53
+ else if (options.withContents !== undefined) {
54
+ content = options.withContents;
55
+ }
56
+ if (content !== undefined) {
57
+ return await salsaFiles_touch(resolvedPath, content);
58
+ }
59
+ else {
60
+ return await salsaFiles_touch(resolvedPath);
61
+ }
62
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * File information for upload tracking.
3
+ */
4
+ export interface UploadFileInfo {
5
+ hostPath: string;
6
+ chrisPath: string;
7
+ size: number;
8
+ }
9
+ /**
10
+ * Upload summary statistics.
11
+ */
12
+ export interface UploadSummary {
13
+ startTime: number;
14
+ endTime: number;
15
+ totalFiles: number;
16
+ transferredCount: number;
17
+ failedCount: number;
18
+ transferSize: number;
19
+ duration: number;
20
+ speed: number;
21
+ actualTargetPath: string;
22
+ }
23
+ /**
24
+ * Formats bytes into human-readable format.
25
+ * @param bytes - Number of bytes.
26
+ * @returns Formatted string (e.g., "1.23 MB").
27
+ */
28
+ export declare function bytes_format(bytes: number): string;
29
+ /**
30
+ * Formats seconds into a human-friendly ETA string.
31
+ * @param seconds - Remaining seconds.
32
+ */
33
+ export declare function eta_format(seconds: number | null): string;
34
+ /**
35
+ * Formats a bytes-per-second rate into human-readable string.
36
+ * @param rateBytesPerSec - Rate in bytes per second.
37
+ */
38
+ export declare function rate_format(rateBytesPerSec: number): string;
39
+ /**
40
+ * Uploads files with a progress bar display.
41
+ * @param localPath - Local path (file or directory).
42
+ * @param remotePath - Remote ChRIS path.
43
+ * @returns Promise<UploadSummary> with upload statistics.
44
+ */
45
+ export declare function files_uploadWithProgress(localPath: string, remotePath: string, options?: {
46
+ force?: boolean;
47
+ }): Promise<UploadSummary>;
48
+ /**
49
+ * Uploads a local file or directory to ChRIS.
50
+ *
51
+ * @param localPath - Local path.
52
+ * @param remotePath - Remote ChRIS path.
53
+ * @returns Promise<boolean> true if successful.
54
+ */
55
+ export declare function files_upload(localPath: string, remotePath: string): Promise<boolean>;
@@ -0,0 +1,221 @@
1
+ /**
2
+ * @file Upload command implementation.
3
+ * @module
4
+ */
5
+ import { files_uploadPath } from "@fnndsc/salsa";
6
+ import { chrisIO } from "@fnndsc/cumin";
7
+ import { path_resolveChrisFs } from "../../utils/cli.js";
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import cliProgress from "cli-progress";
11
+ import chalk from "chalk";
12
+ import { prompt_confirmOrThrow } from "../../utils/input_format.js";
13
+ /**
14
+ * Formats bytes into human-readable format.
15
+ * @param bytes - Number of bytes.
16
+ * @returns Formatted string (e.g., "1.23 MB").
17
+ */
18
+ export function bytes_format(bytes) {
19
+ if (bytes === 0)
20
+ return "0 B";
21
+ const k = 1024;
22
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
23
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
24
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
25
+ }
26
+ /**
27
+ * Formats seconds into a human-friendly ETA string.
28
+ * @param seconds - Remaining seconds.
29
+ */
30
+ export function eta_format(seconds) {
31
+ if (seconds === null || !Number.isFinite(seconds) || seconds < 0)
32
+ return "--";
33
+ if (seconds < 60)
34
+ return `${Math.round(seconds)}s`;
35
+ const hrs = Math.floor(seconds / 3600);
36
+ const mins = Math.floor((seconds % 3600) / 60);
37
+ const secs = Math.floor(seconds % 60);
38
+ if (hrs > 0) {
39
+ return `${hrs}h ${mins}m ${secs}s`;
40
+ }
41
+ return `${mins}m ${secs}s`;
42
+ }
43
+ /**
44
+ * Formats a bytes-per-second rate into human-readable string.
45
+ * @param rateBytesPerSec - Rate in bytes per second.
46
+ */
47
+ export function rate_format(rateBytesPerSec) {
48
+ if (!Number.isFinite(rateBytesPerSec) || rateBytesPerSec <= 0)
49
+ return "--";
50
+ return `${bytes_format(rateBytesPerSec)}/s`;
51
+ }
52
+ /**
53
+ * Recursively scans a local directory to get all files for upload.
54
+ * @param localPath - The local filesystem path.
55
+ * @param remotePath - The target ChRIS path.
56
+ * @returns Array of UploadFileInfo objects.
57
+ */
58
+ async function localFiles_scan(localPath, remotePath) {
59
+ const files = [];
60
+ async function walkDir(currentLocal, currentRemote) {
61
+ const entries = await fs.promises.readdir(currentLocal, { withFileTypes: true });
62
+ for (const entry of entries) {
63
+ const localFilePath = path.join(currentLocal, entry.name);
64
+ const remoteFilePath = currentRemote.endsWith('/')
65
+ ? currentRemote + entry.name
66
+ : currentRemote + '/' + entry.name;
67
+ if (entry.isDirectory()) {
68
+ await walkDir(localFilePath, remoteFilePath);
69
+ }
70
+ else {
71
+ const stats = await fs.promises.stat(localFilePath);
72
+ files.push({
73
+ hostPath: localFilePath,
74
+ chrisPath: remoteFilePath,
75
+ size: stats.size,
76
+ });
77
+ }
78
+ }
79
+ }
80
+ const stats = await fs.promises.stat(localPath);
81
+ if (stats.isDirectory()) {
82
+ // Preserve directory basename in remote path (Unix cp semantics)
83
+ const basename = path.basename(localPath);
84
+ const targetDir = remotePath.endsWith('/')
85
+ ? remotePath + basename
86
+ : remotePath + '/' + basename;
87
+ await walkDir(localPath, targetDir);
88
+ }
89
+ else {
90
+ // Single file upload
91
+ const filename = path.basename(localPath);
92
+ const targetPath = remotePath.endsWith('/')
93
+ ? remotePath + filename
94
+ : remotePath + '/' + filename;
95
+ files.push({
96
+ hostPath: localPath,
97
+ chrisPath: targetPath,
98
+ size: stats.size,
99
+ });
100
+ }
101
+ return files;
102
+ }
103
+ /**
104
+ * Uploads files with a progress bar display.
105
+ * @param localPath - Local path (file or directory).
106
+ * @param remotePath - Remote ChRIS path.
107
+ * @returns Promise<UploadSummary> with upload statistics.
108
+ */
109
+ export async function files_uploadWithProgress(localPath, remotePath, options = {}) {
110
+ const resolvedRemote = await path_resolveChrisFs(remotePath, {});
111
+ // Scan files
112
+ console.log(chalk.cyan("Scanning files to upload..."));
113
+ const fileList = await localFiles_scan(localPath, resolvedRemote);
114
+ const totalSize = fileList.reduce((sum, f) => sum + f.size, 0);
115
+ // Determine actual target path (where files will be uploaded)
116
+ const stats = await fs.promises.stat(localPath);
117
+ let actualTarget = resolvedRemote;
118
+ if (stats.isDirectory()) {
119
+ const basename = path.basename(localPath);
120
+ actualTarget = resolvedRemote.endsWith('/')
121
+ ? resolvedRemote + basename
122
+ : resolvedRemote + '/' + basename;
123
+ }
124
+ // Detect existing target and require confirmation unless force is set
125
+ if (!options.force) {
126
+ try {
127
+ const client = await chrisIO.client_get();
128
+ if (client) {
129
+ const folderList = await client.getFileBrowserFolders({ path: actualTarget });
130
+ const items = await folderList.getItems();
131
+ if (items && items.length > 0) {
132
+ await prompt_confirmOrThrow(`Target '${actualTarget}' already exists in ChRIS. Merge/overwrite? (y/N)`);
133
+ }
134
+ }
135
+ }
136
+ catch {
137
+ // If listing fails (path absent or error), continue.
138
+ }
139
+ }
140
+ else {
141
+ // Force mode: if target exists and is a directory, no prompt. If it's a file and we're uploading a single file, proceed.
142
+ }
143
+ const showProgress = !!process.stdout.isTTY;
144
+ const progressBar = showProgress
145
+ ? new cliProgress.SingleBar({
146
+ format: "Transferring [{bar}] {percentage}% | ETA: {etaHuman} | Rate: {rate} | {value}/{total} files | {bytes}/{totalBytes}",
147
+ }, cliProgress.Presets.shades_classic)
148
+ : null;
149
+ if (progressBar) {
150
+ progressBar.start(fileList.length, 0, {
151
+ bytes: "0 B",
152
+ totalBytes: bytes_format(totalSize),
153
+ etaHuman: "--",
154
+ rate: "--",
155
+ });
156
+ }
157
+ // Upload files
158
+ const summary = {
159
+ startTime: Date.now(),
160
+ endTime: 0,
161
+ totalFiles: fileList.length,
162
+ transferredCount: 0,
163
+ failedCount: 0,
164
+ transferSize: 0,
165
+ duration: 0,
166
+ speed: 0,
167
+ actualTargetPath: actualTarget,
168
+ };
169
+ for (const [index, file] of fileList.entries()) {
170
+ try {
171
+ const fileContent = await fs.promises.readFile(file.hostPath);
172
+ const fileBlob = new Blob([fileContent]);
173
+ // Split chrisPath into dir and filename for the new API
174
+ const lastSlash = file.chrisPath.lastIndexOf('/');
175
+ const dir = file.chrisPath.substring(0, lastSlash) || '/';
176
+ const filename = file.chrisPath.substring(lastSlash + 1);
177
+ const uploadResult = await chrisIO.file_upload(fileBlob, dir, filename);
178
+ if (uploadResult) {
179
+ summary.transferredCount++;
180
+ summary.transferSize += fileContent.length;
181
+ }
182
+ else {
183
+ summary.failedCount++;
184
+ console.log(chalk.yellow(`Failed to upload: ${file.hostPath}`));
185
+ }
186
+ }
187
+ catch (error) {
188
+ summary.failedCount++;
189
+ console.log(chalk.red(`Error uploading ${file.hostPath}: ${error instanceof Error ? error.message : String(error)}`));
190
+ }
191
+ const elapsedSeconds = Math.max(1e-6, (Date.now() - summary.startTime) / 1000);
192
+ const rateCurrent = summary.transferSize > 0 ? summary.transferSize / elapsedSeconds : 0;
193
+ const remainingBytes = Math.max(0, totalSize - summary.transferSize);
194
+ const etaSeconds = rateCurrent > 0 ? remainingBytes / rateCurrent : null;
195
+ if (progressBar) {
196
+ progressBar.update(index + 1, {
197
+ bytes: bytes_format(summary.transferSize),
198
+ etaHuman: eta_format(etaSeconds),
199
+ rate: rate_format(rateCurrent),
200
+ });
201
+ }
202
+ }
203
+ if (progressBar) {
204
+ progressBar.stop();
205
+ }
206
+ summary.endTime = Date.now();
207
+ summary.duration = (summary.endTime - summary.startTime) / 1000; // seconds
208
+ summary.speed = summary.transferSize / summary.duration; // bytes per second
209
+ return summary;
210
+ }
211
+ /**
212
+ * Uploads a local file or directory to ChRIS.
213
+ *
214
+ * @param localPath - Local path.
215
+ * @param remotePath - Remote ChRIS path.
216
+ * @returns Promise<boolean> true if successful.
217
+ */
218
+ export async function files_upload(localPath, remotePath) {
219
+ const resolvedRemote = await path_resolveChrisFs(remotePath, {});
220
+ return await files_uploadPath(localPath, resolvedRemote);
221
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Fetches the available group field names.
3
+ *
4
+ * @returns The group field names, or null on failure.
5
+ */
6
+ export declare function groupFields_fetch(): Promise<string[] | null>;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @file Fetches available fields for group resources.
3
+ * @module
4
+ */
5
+ import { groupFields_get } from '@fnndsc/salsa';
6
+ /**
7
+ * Fetches the available group field names.
8
+ *
9
+ * @returns The group field names, or null on failure.
10
+ */
11
+ export async function groupFields_fetch() {
12
+ return await groupFields_get();
13
+ }
@@ -0,0 +1,16 @@
1
+ import { CLIoptions } from '../../utils/cli.js';
2
+ /**
3
+ * Result of listing groups (table data plus selected fields).
4
+ */
5
+ export interface GroupListResult {
6
+ groups: Record<string, unknown>[];
7
+ selectedFields: string[];
8
+ totalCount?: number;
9
+ }
10
+ /**
11
+ * Fetches a list of groups.
12
+ *
13
+ * @param options - List and filter options.
14
+ * @returns The groups list result.
15
+ */
16
+ export declare function groups_fetchList(options: CLIoptions): Promise<GroupListResult>;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @file Implements listing ChRIS groups.
3
+ * @module
4
+ */
5
+ import { groups_list as salsaGroups_list, groups_listAll as salsaGroups_listAll } from '@fnndsc/salsa';
6
+ import { options_toParams } from '../../utils/cli.js';
7
+ import { list_applySort } from '../../utils/sort.js';
8
+ /**
9
+ * Fetches a list of groups.
10
+ *
11
+ * @param options - List and filter options.
12
+ * @returns The groups list result.
13
+ */
14
+ export async function groups_fetchList(options) {
15
+ const params = options_toParams(options);
16
+ const result = options.all
17
+ ? await salsaGroups_listAll(params)
18
+ : await salsaGroups_list(params);
19
+ if (result && result.tableData) {
20
+ let groups = result.tableData;
21
+ if (options.sort)
22
+ groups = list_applySort(groups, options.sort, options.reverse);
23
+ return { groups, selectedFields: result.selectedFields || [], totalCount: result.totalCount };
24
+ }
25
+ return { groups: [], selectedFields: [] };
26
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Options for rendering a man page.
3
+ */
4
+ export interface ManPageOptions {
5
+ topic: string;
6
+ browser?: boolean;
7
+ style: "figlet" | "ascii";
8
+ width?: number;
9
+ }
10
+ /**
11
+ * Displays a documentation page for a specific topic.
12
+ *
13
+ * @param options - Options defining the topic, output format (browser vs console), and style.
14
+ * @returns A Promise resolving to `void`.
15
+ */
16
+ export declare function manPage_display(options: ManPageOptions): Promise<void>;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @file Implements the logic for displaying documentation topics.
3
+ *
4
+ * This module provides functionality to render and display documentation
5
+ * pages either in the terminal or a browser.
6
+ *
7
+ * @module
8
+ */
9
+ import fs from "fs";
10
+ import path from "path";
11
+ import { projectDir_get, browser_open, asciidoc_render } from "../../man/renderer.js";
12
+ /**
13
+ * Displays a documentation page for a specific topic.
14
+ *
15
+ * @param options - Options defining the topic, output format (browser vs console), and style.
16
+ * @returns A Promise resolving to `void`.
17
+ */
18
+ export async function manPage_display(options) {
19
+ const docDir = path.join(projectDir_get(), "doc");
20
+ const docPath = path.join(docDir, `${options.topic}.adoc`);
21
+ if (!fs.existsSync(docPath)) {
22
+ console.error(`Documentation for '${options.topic}' not found.`);
23
+ return;
24
+ }
25
+ if (options.browser) {
26
+ browser_open(docPath);
27
+ }
28
+ else {
29
+ const content = fs.readFileSync(docPath, "utf-8");
30
+ console.log(await asciidoc_render(content, options.style, options.width));
31
+ }
32
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Lists the available documentation topics.
3
+ *
4
+ * @returns A Promise resolving to an array of topic strings (filenames without extension).
5
+ */
6
+ export declare function topics_list(): Promise<string[]>;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @file Implements the logic for listing available documentation topics.
3
+ *
4
+ * This module scans the documentation directory and returns a list
5
+ * of available topics (Asciidoc files).
6
+ *
7
+ * @module
8
+ */
9
+ import fs from "fs";
10
+ import path from "path";
11
+ import { projectDir_get } from "../../man/renderer.js";
12
+ import { library } from "@fortawesome/fontawesome-svg-core";
13
+ import { faBook } from "@fortawesome/free-solid-svg-icons";
14
+ /**
15
+ * Lists the available documentation topics.
16
+ *
17
+ * @returns A Promise resolving to an array of topic strings (filenames without extension).
18
+ */
19
+ export async function topics_list() {
20
+ const docDir = path.join(projectDir_get(), "doc");
21
+ library.add(faBook);
22
+ const bookIcon = "";
23
+ const files = fs.readdirSync(docDir);
24
+ const adocFiles = files.filter((file) => path.extname(file) === ".adoc");
25
+ const formattedOutput = adocFiles.map((file) => `${bookIcon} ${path.basename(file, ".adoc")}`);
26
+ return formattedOutput;
27
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Fetches the available pipeline field names.
3
+ *
4
+ * @returns The pipeline field names, or null on failure.
5
+ */
6
+ export declare function pipelineFields_fetch(): Promise<string[] | null>;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @file Fetches available fields for pipeline resources.
3
+ * @module
4
+ */
5
+ import { pipelineFields_get } from '@fnndsc/salsa';
6
+ /**
7
+ * Fetches the available pipeline field names.
8
+ *
9
+ * @returns The pipeline field names, or null on failure.
10
+ */
11
+ export async function pipelineFields_fetch() {
12
+ return await pipelineFields_get();
13
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @file Pipeline run command — creates a workflow from a registered pipeline.
3
+ * @module
4
+ */
5
+ import { Result } from "@fnndsc/cumin";
6
+ import { WorkflowResult } from "@fnndsc/salsa";
7
+ /**
8
+ * Runs a pipeline by name or ID, attaching it to the current plugin context node
9
+ * or an explicitly supplied previous instance ID.
10
+ *
11
+ * @param nameOrId - Pipeline name or numeric ID.
12
+ * @param options - Optional compute override and explicit previous instance ID.
13
+ * @returns Result containing workflow ID and created plugin instance IDs.
14
+ */
15
+ export declare function pipeline_execute(nameOrId: string, options?: {
16
+ compute?: string;
17
+ previousInstId?: number;
18
+ }): Promise<Result<WorkflowResult>>;