@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,187 @@
1
+ = Architecture of "Searchable" Strings
2
+ :toc: left
3
+ :toclevels: 3
4
+
5
+ The **"Searchable"** architecture is a core ChILI concept designed to unify resource identification and searching into a single, flexible string format. It allows users to specify resources by ID, Name, or complex query parameters seamlessly within CLI arguments and context strings.
6
+
7
+ As of version 0.4.1 of cumin, searchables are implemented as a formal `Searchable` class providing type safety, validation, and a unified API for all searchable operations.
8
+
9
+ == The Concept: "One String to Find Them All"
10
+
11
+ In a traditional REST API, fetching a resource by ID (`GET /plugins/14/`) is distinct from searching for resources (`GET /plugins/?name=pl-dircopy`). The Searchable concept blurs this line for the CLI user.
12
+
13
+ A "Searchable" string is an input argument that the system automatically interprets as either:
14
+ 1. **A Direct ID/Name**: `"14"`, `"pl-dircopy"`
15
+ 2. **An Explicit Query**: `"name_exact:pl-dircopy"`, `"version:1.0.2, min_creation_date:2023-01-01"`
16
+
17
+ == Syntax Specification
18
+
19
+ The explicit syntax is a comma-separated list of key-value pairs:
20
+
21
+ `"key1: value1, key2: value2, ..."`
22
+
23
+ * **Keys**: Correspond directly to the Django filter fields supported by the ChRIS API (e.g., `name`, `name_exact`, `id`, `min_creation_date`).
24
+ * **Values**: The values to search for.
25
+ * **Separator**: A colon (`:`) separates key and value. A comma (`,`) separates pairs.
26
+
27
+ === Batch Operations (`++` Separator)
28
+
29
+ For commands that support batch operations (such as `delete`), multiple searchables can be combined using the `++` separator:
30
+
31
+ `"searchable1++searchable2++searchable3"`
32
+
33
+ Each searchable is processed independently. For example:
34
+
35
+ [source,bash]
36
+ ----
37
+ chili plugins delete "id:77++id:33++name:pl-test"
38
+ ----
39
+
40
+ This command will:
41
+ 1. Resolve `id:77` and delete plugin 77
42
+ 2. Resolve `id:33` and delete plugin 33
43
+ 3. Resolve `name:pl-test` and delete matching plugin(s)
44
+
45
+ The implementation splits on `++` and iterates through each part (`pluginHandler.ts:83`).
46
+
47
+ == Implementation Mechanism
48
+
49
+ The logic resides in the `Searchable` class (`cumin/src/utils/searchable.ts`) which integrates with the existing parsing utilities (`cumin/src/utils/keypair.ts`) and ChRIS plugin operations (`cumin/src/plugins/chrisPlugins.ts`).
50
+
51
+ === The Searchable Class
52
+
53
+ The `Searchable` class encapsulates all searchable logic in a type-safe, testable module:
54
+
55
+ [source,typescript]
56
+ ----
57
+ import { Searchable } from '@fnndsc/cumin';
58
+
59
+ // Auto-detect type
60
+ const s1 = Searchable.from("pl-dircopy"); // simple
61
+ const s2 = Searchable.from("name:pl-dircopy"); // compound
62
+ const s3 = Searchable.from("id:77++id:33"); // batch
63
+
64
+ // Explicit constructors
65
+ const simple = Searchable.simple("pl-dircopy");
66
+ const compound = Searchable.compound("name:foo,version:1.0");
67
+ const batch = Searchable.batch("id:77++id:33");
68
+
69
+ // Type checking
70
+ if (s1.isSimple()) { /* ... */ }
71
+ if (s2.isCompound()) { /* ... */ }
72
+ if (s3.isBatch()) { /* ... */ }
73
+
74
+ // Convert to query parameters
75
+ const params = s2.toQueryParams(); // { name: "pl-dircopy" }
76
+
77
+ // Split batch into individual searchables
78
+ const parts = s3.toBatchSearchables(); // [Searchable, Searchable]
79
+
80
+ // Validate
81
+ if (s1.validate()) { /* searchable is valid */ }
82
+ ----
83
+
84
+ All ChRIS resource methods accept either strings or `Searchable` objects, providing backward compatibility while enabling explicit type safety when desired.
85
+
86
+ === 1. Type Detection (`Searchable.from`)
87
+
88
+ When a string is passed to `Searchable.from()`, the system automatically detects the type:
89
+
90
+ * **Batch Mode**: If the string contains `++`, it is treated as a batch searchable.
91
+ * Input: `"id:77++id:33"` -> Type: `batch`
92
+ * **Compound Mode**: If the string contains a colon (`:`) but no `++`, it is treated as a key-value search.
93
+ * Input: `"id:14"` -> Type: `compound`
94
+ * **Simple Mode**: If the string contains neither `:` nor `++`, it is assumed to be a plain name.
95
+ * Input: `"pl-dircopy"` -> Type: `simple`, normalized to `"name: pl-dircopy"`
96
+
97
+ For backward compatibility, `pluginString_makeSearchable()` still exists but is deprecated in favor of the `Searchable` class.
98
+
99
+ === 2. Parsing (`Searchable.toQueryParams`)
100
+
101
+ The Searchable instance provides parsed key-value pairs via `toQueryParams()`:
102
+
103
+ [source,typescript]
104
+ ----
105
+ const searchable = Searchable.from("name: foo, version: 1.0");
106
+ const params = searchable.toQueryParams();
107
+ // Result: { name: "foo", version: "1.0" }
108
+ ----
109
+
110
+ Internally, this uses the existing `keyPairString_parse()` utility for compound searchables.
111
+
112
+ === 3. Normalization (`Searchable.toNormalizedString`)
113
+
114
+ Before passing to the API, the searchable is normalized:
115
+
116
+ [source,typescript]
117
+ ----
118
+ const s1 = Searchable.simple("pl-dircopy");
119
+ s1.toNormalizedString(); // "name: pl-dircopy"
120
+
121
+ const s2 = Searchable.compound("name:foo,version:1.0");
122
+ s2.toNormalizedString(); // "name:foo,version:1.0" (unchanged)
123
+ ----
124
+
125
+ This normalized string is then passed through `options_toParams()`, merged with pagination options, and sent to the `@fnndsc/chrisapi` client as a standard HTTP Query String:
126
+
127
+ `GET /api/v1/endpoint/?name=foo&version=1.0`
128
+
129
+ === 4. Resolution
130
+
131
+ The API returns a list of matching objects. The system then extracts the relevant identifier (typically `id`) from the first result (or handles errors if multiple/zero results are found).
132
+
133
+ ==== Multiple Match Warning
134
+
135
+ When a searchable resolves to more than one result, the system:
136
+
137
+ 1. Issues a warning: `"Multiple plugins conformed to '<searchable>'"`
138
+ 2. Continues execution using the **first match**
139
+ 3. Pushes the warning to the error stack for user visibility
140
+
141
+ [source,typescript]
142
+ ----
143
+ // From chrisPlugins.ts:214-219
144
+ if (pluginList.hits.length > 1) {
145
+ errorStack.stack_push(
146
+ "warning",
147
+ `Multiple plugins conformed to "${searchable}.`
148
+ );
149
+ }
150
+ ----
151
+
152
+ This behavior helps users identify when their search criteria are too broad. Best practice is to use more specific searchables (e.g., `name_exact` instead of `name`, or include version constraints) to ensure deterministic results.
153
+
154
+ == Integration Points
155
+
156
+ The Searchable concept is integrated deeply into two main areas:
157
+
158
+ === Resource Commands
159
+
160
+ Commands that operate on resources accept searchable strings as arguments.
161
+
162
+ [source,bash]
163
+ ----
164
+ chili plugin run "name_exact:pl-dircopy, version:2.0.1"
165
+ ----
166
+
167
+ === Context Initialization
168
+
169
+ The Context parser (`chrisContextURL_parse` in `cumin`) supports searchables within the query string of a context URI.
170
+
171
+ [source,bash]
172
+ ----
173
+ chili "user@http://cube.org?plugin=name_exact:pl-dircopy" ...
174
+ ----
175
+
176
+ When the parser encounters a key like `plugin` with a value containing a colon (`:`), it triggers the full resolution logic (API call -> Search -> Resolve ID) *before* setting the persistent session state.
177
+
178
+ == Data Flow Summary
179
+
180
+ 1. **User Input**: `"name_exact:foo"`
181
+ 2. **CLI Parser**: Passes string to Resolver.
182
+ 3. **Resolver**: calls `keyPairString_parse("name_exact:foo")`.
183
+ 4. **Object**: `{ name_exact: "foo" }`.
184
+ 5. **API Client**: `GET .../?name_exact=foo`.
185
+ 6. **Response**: `[{ id: 42, name: "foo", ... }]`.
186
+ 7. **Resolution**: Returns ID `42`.
187
+ 8. **Action**: Command operates on Resource ID `42`.
@@ -0,0 +1,68 @@
1
+ = ChILI Context & State Management
2
+ :toc: left
3
+ :toclevels: 2
4
+
5
+ The ChRIS Interactive Line Interface (ChILI) implements a robust "Context" system to provide a stateful, shell-like experience over the stateless ChRIS REST API. This design allows the CLI to remember "who you are," "where you are," and "what you are doing" across multiple sessions, users, and CUBE instances.
6
+
7
+ == The Concept: "Where am I and Who am I?"
8
+
9
+ The "Context" is a state management mechanism that persists critical session information. It allows a user to navigate the ChRIS filesystem, switch between different user identities, or connect to different ChRIS backends (CUBEs) without needing to re-authenticate or re-specify parameters for every command.
10
+
11
+ It answers three fundamental questions:
12
+ 1. **Who is acting?** (Current User)
13
+ 2. **Where are they acting?** (Current ChRIS URL/CUBE instance)
14
+ 3. **What are they working on?** (Current Folder, Feed, or Plugin)
15
+
16
+ == Data Structure Hierarchy
17
+
18
+ The context is organized hierarchically to support multi-tenancy and multi-backend operations.
19
+
20
+ * **`FullContext`**: The root state object. It maintains a registry of all known users.
21
+ ** **`UserContext`**: For each specific user (e.g., "alice", "bob"), it stores a map of all CUBE URLs they have interacted with.
22
+ *** **`URLContext`**: The leaf node of the state tree. For a specific *User* at a specific *CUBE URL*, it persists the following session details:
23
+ **** `folder`: The current working directory path (e.g., `/home/alice/data`).
24
+ **** `feed`: The ID of the active feed.
25
+ **** `plugin`: The ID of the active plugin instance.
26
+ **** `token`: The active authentication token for this session.
27
+
28
+ == Persistence
29
+
30
+ This state is not ephemeral. It is persisted to the local filesystem (typically in `~/.chris/` or a similar configuration directory managed by `cumin`).
31
+
32
+ * **Session Continuity**: You can set a working directory (`chili context set --ChRISfolder /tmp`), close your terminal, open a new one days later, and `chili chefs ls` will still list the contents of `/tmp`.
33
+ * **Multi-Tasking**: You can seamlessly switch contexts. For example, you can work as user "dev" on a local testing CUBE, then switch context to user "prod_admin" on a remote production CUBE, and ChILI will instantly recall the correct tokens and working directories for each environment.
34
+
35
+ == CLI Usage
36
+
37
+ The context is managed via the `chili context` command group.
38
+
39
+ === Viewing State
40
+
41
+ [source,bash]
42
+ ----
43
+ # View the current active context (User, URL, Folder, etc.)
44
+ chili context get
45
+
46
+ # View details for a specific context aspect
47
+ chili context get --ChRISuser
48
+ chili context get --ChRISurl
49
+
50
+ # View the entire stored state tree (all users, all URLs)
51
+ chili context get --full
52
+ ----
53
+
54
+ === Modifying State
55
+
56
+ [source,bash]
57
+ ----
58
+ # Switch active user (if previously authenticated)
59
+ chili context set --ChRISuser alice
60
+
61
+ # Switch active CUBE instance
62
+ chili context set --ChRISurl https://chris.example.org/api/v1/
63
+
64
+ # Change current working directory (cd equivalent)
65
+ chili context set --ChRISfolder /home/alice/analysis/
66
+ ----
67
+
68
+ This architecture effectively turns the stateless ChRIS API into a stateful, interactive environment, significantly reducing the friction of repetitive tasks and complex workflows.
package/docs/pacs.adoc ADDED
@@ -0,0 +1,101 @@
1
+ = PACS Commands in Chili
2
+ :toc:
3
+ :toclevels: 2
4
+ :sectnums:
5
+
6
+ == Overview
7
+
8
+ `chili` is the CLI layer where PACS interactions become user-facing commands. It handles context management, tabular output, and decoding/presentation of PACS query results, while delegating business logic to `salsa`/`cumin`. The goal is to give an operator a concise, line-friendly workflow: pick a PACS server, launch a query, and view its structured results (studies with nested series) without touching raw JSON.
9
+
10
+ == PACS Servers
11
+
12
+ - `chili pacsservers list [--identifier <id>] [--limit <n>] [--offset <n>]`
13
+ - `chili pacsservers fieldslist`
14
+
15
+ == PACS Context
16
+
17
+ - `chili context set --pacsserver <id|identifier>` (stores PACS server target)
18
+ - `chili context get --pacsserver` (displays current PACS server)
19
+ - Inline override on commands: `--pacsserver <id|identifier>`
20
+
21
+ == PACS Queries
22
+
23
+ - **Create**: `chili pacsqueries create "<query>" [--title <title>] [--description <desc>] [--pacsserver <pacs>]`
24
+ - `<query>` can be JSON (`{"PatientID":"12345"}`) or comma-separated `key:value` pairs (`PatientID:12345,StudyDate:20251003`).
25
+ - On success prints: `Created PACS query id=<ID> status=<status> pacs=<pacs> title="<title>"`.
26
+ - **List**: `chili pacsqueries list [--pacsserver <pacs>] [--fields ...] [--table|--csv]`
27
+ - Filters to current/override PACS server. Use `fieldslist` to discover columns.
28
+ - **Fields**: `chili pacsqueries fieldslist`
29
+ - **Decode result**: `chili pacsqueries decode <queryId> [--raw]`
30
+ - Decodes the query `result` payload (base64 → zlib/gzip → JSON/text).
31
+ - Renders studies with nested series when JSON is present.
32
+ - `--raw` prints the decoded JSON prettified.
33
+
34
+ == PACS Retrieves
35
+
36
+ - **Pull**: `chili pacsretrieve pull <queryId>`
37
+ - Triggers a PACS retrieve to pull DICOM data from PACS to ChRIS.
38
+ - Creates a retrieve record and initiates the external service.
39
+ - Prints: `Created PACS retrieve id=<ID> query=<queryId> status=<status>`.
40
+ - **Report**: `chili pacsretrieve report <queryId>`
41
+ - Shows detailed status report with series-level progress.
42
+ - Displays studies, series descriptions, and file counts (`Pulling (X/Y images)`).
43
+ - Automatically detects completion when all series are pulled.
44
+ - Example output:
45
+ ```
46
+ Query ID: 123
47
+ Retrieve ID: 456
48
+ Retrieve Status: Retrieving / Completed
49
+
50
+ Study 1
51
+ Description: XR-Wrist 3+ Views Left
52
+ PA: Pulled (1 images)
53
+ OBLIQUE: Pulling (50/220 images)
54
+ LATERAL: Pending (0/180 images)
55
+ ```
56
+ - **Cancel**: `chili pacsretrieve cancel <retrieveId>`
57
+ - Cancels (deletes) an in-progress retrieve.
58
+ - Note: Partially-pulled data may remain in ChRIS.
59
+
60
+ == Typical Flow
61
+
62
+ === Query Only
63
+
64
+ ```bash
65
+ chili context set --pacsserver PACSDCM
66
+ chili pacsservers list --table
67
+ chili pacsqueries create "PatientID:4456554" --title "PID 4456554"
68
+ chili pacsqueries list --fields id,title,status,pacs_id,query
69
+ chili pacsqueries decode <id>
70
+ ```
71
+
72
+ === Query + Retrieve with Monitoring
73
+
74
+ ```bash
75
+ # 1. Set PACS context
76
+ chili context set --pacsserver PACSDCM
77
+
78
+ # 2. Create query
79
+ chili pacsqueries create "PatientID:4456554" --title "Patient scan"
80
+ # Returns: Created PACS query id=123
81
+
82
+ # 3. See what's available on PACS
83
+ chili pacsqueries decode 123
84
+
85
+ # 4. Pull DICOM data to ChRIS
86
+ chili pacsretrieve pull 123
87
+ # Returns: Created PACS retrieve id=456
88
+
89
+ # 5. Monitor progress
90
+ chili pacsretrieve report 123
91
+ # Shows series-level status: Pending / Pulling / Pulled
92
+
93
+ # 6. Cancel if needed
94
+ chili pacsretrieve cancel 456
95
+ ```
96
+
97
+ == Notes
98
+
99
+ - Commands rely on existing ChRIS auth/token; ensure `chili connect` is done.
100
+ - Decoding/parsing happens client-side; no changes to the PACS service.
101
+ - The CLI prints only at the presentation layer; underlying intents remain quiet for programmatic reuse.
@@ -0,0 +1,246 @@
1
+ # PathMapper Implementation
2
+
3
+ ## Overview
4
+
5
+ The `PathMapper` singleton class implements hierarchical path mapping from logical ChRIS paths to physical storage locations, with aggressive prefix caching to optimize repeated resolutions.
6
+
7
+ ## Architecture
8
+
9
+ ### Problem Solved
10
+
11
+ **Before:** Every path resolution walked the entire tree from root, causing redundant API calls:
12
+ ```
13
+ /home/user/public/feed_4 → 4 API calls
14
+ /home/user/public/feed_5 → 4 API calls (redundant!)
15
+ /home/user/public/feed_6 → 4 API calls (redundant!)
16
+ ```
17
+
18
+ **After:** Hierarchical prefix caching eliminates redundant operations:
19
+ ```
20
+ /home/user/public/feed_4 → 4 API calls (initial resolution)
21
+ /home/user/public/feed_5 → 1 API call (reuses cached prefix!)
22
+ /home/user/public/feed_6 → 1 API call (reuses cached prefix!)
23
+ ```
24
+
25
+ ### Key Design Decisions
26
+
27
+ 1. **Path-Level Caching**: Cache complete logical → physical mappings
28
+ 2. **Incremental Resolution**: Find longest cached prefix, resolve only the suffix
29
+ 3. **Intermediate Caching**: Cache every path segment for maximum reuse
30
+ 4. **TTL-Based Expiration**: 30-second TTL balances freshness vs performance
31
+ 5. **Singleton Pattern**: Single source of truth for all path mappings
32
+
33
+ ## API
34
+
35
+ ### Core Methods
36
+
37
+ ```typescript
38
+ import { pathMapper_get } from '@fnndsc/chili';
39
+
40
+ const mapper = pathMapper_get();
41
+
42
+ // Resolve logical path to physical location
43
+ const result = await mapper.logical_toPhysical('/home/user/public/feed_4');
44
+ if (result.ok) {
45
+ console.log(result.value); // '/SHARED/feed_4'
46
+ }
47
+
48
+ // Invalidate cache when links change
49
+ mapper.cache_invalidate('/home/user/public');
50
+
51
+ // Clear all cached mappings
52
+ mapper.cache_clear();
53
+
54
+ // Get performance statistics
55
+ const stats = mapper.stats_get();
56
+ console.log(`Hit rate: ${stats.hitRate * 100}%`);
57
+ ```
58
+
59
+ ### Statistics
60
+
61
+ ```typescript
62
+ interface CacheStats {
63
+ hits: number; // Number of cache hits
64
+ misses: number; // Number of cache misses
65
+ size: number; // Current cache size
66
+ hitRate: number; // Hit rate (0-1)
67
+ }
68
+ ```
69
+
70
+ ## Performance Characteristics
71
+
72
+ ### Time Complexity
73
+
74
+ | Operation | Without Cache | With Cache | Improvement |
75
+ |-----------|---------------|------------|-------------|
76
+ | First resolution | O(depth) | O(depth) | - |
77
+ | Repeated resolution | O(depth) | **O(1)** | ✅ |
78
+ | Prefix reuse | O(depth) | **O(suffix)** | ✅ |
79
+
80
+ ### Real-World Example
81
+
82
+ Resolving 1000 files in `/home/user/feeds/feed_123/files/`:
83
+
84
+ **Without caching:**
85
+ - 1000 × 6 components = 6,000 API calls
86
+
87
+ **With PathMapper:**
88
+ - First file: 6 API calls (cache miss)
89
+ - Remaining 999 files: 1 API call each (prefix reuse)
90
+ - **Total: 1,005 API calls (83% reduction)**
91
+
92
+ ## Implementation Details
93
+
94
+ ### Resolution Algorithm
95
+
96
+ ```
97
+ logical_toPhysical(logicalPath):
98
+ 1. Check exact cache hit → return if found
99
+ 2. Find longest cached prefix
100
+ 3. Resolve only the uncached suffix
101
+ 4. Cache all intermediate segments
102
+ 5. Return final physical path
103
+ ```
104
+
105
+ ### Example Resolution
106
+
107
+ ```
108
+ Input: '/home/user/public/feed_4'
109
+ Cache state: { '/home/user/public': '/SHARED' }
110
+
111
+ Step 1: Check exact cache for '/home/user/public/feed_4' → miss
112
+ Step 2: Find longest prefix:
113
+ - Check '/home/user/public/feed_4' → miss
114
+ - Check '/home/user/public' → HIT! ✓
115
+ Step 3: Resolve suffix 'feed_4' from '/SHARED':
116
+ - Check if '/SHARED/feed_4' is a link → no
117
+ Step 4: Cache '/home/user/public/feed_4' → '/SHARED/feed_4'
118
+ Step 5: Return '/SHARED/feed_4'
119
+ ```
120
+
121
+ ## Cache Management
122
+
123
+ ### When to Invalidate
124
+
125
+ Call `cache_invalidate()` when:
126
+ - Links are created: `mapper.cache_invalidate(parentDir)`
127
+ - Links are deleted: `mapper.cache_invalidate(parentDir)`
128
+ - Links are modified: `mapper.cache_invalidate(linkPath)`
129
+
130
+ ### TTL Behavior
131
+
132
+ - Default TTL: 30 seconds
133
+ - Expired entries are removed on access
134
+ - Balances freshness vs performance
135
+
136
+ ### Memory Considerations
137
+
138
+ - Cache grows with unique paths accessed
139
+ - Each entry: ~100 bytes (path strings + metadata)
140
+ - 10,000 paths ≈ 1 MB memory
141
+ - TTL ensures bounded growth
142
+
143
+ ## Testing
144
+
145
+ Comprehensive test suite with 31 tests covering:
146
+
147
+ ✅ Singleton pattern enforcement
148
+ ✅ Basic path resolution
149
+ ✅ Link resolution (single and multiple)
150
+ ✅ Caching behavior
151
+ ✅ **Prefix reuse optimization** (key feature)
152
+ ✅ Cache invalidation
153
+ ✅ TTL expiration
154
+ ✅ Error handling
155
+ ✅ Statistics tracking
156
+ ✅ Edge cases (malformed paths, deep nesting, etc.)
157
+
158
+ Run tests:
159
+ ```bash
160
+ npm test -- pathMapper.test.ts
161
+ ```
162
+
163
+ ## Integration
164
+
165
+ ### Automatic Usage
166
+
167
+ The existing `logical_toPhysical()` function now delegates to PathMapper:
168
+
169
+ ```typescript
170
+ // Old code continues to work unchanged
171
+ import { logical_toPhysical } from '@fnndsc/chili';
172
+
173
+ const result = await logical_toPhysical('/home/user/files');
174
+ // Automatically uses PathMapper under the hood
175
+ ```
176
+
177
+ ### Direct Usage
178
+
179
+ For advanced scenarios requiring cache control:
180
+
181
+ ```typescript
182
+ import { pathMapper_get } from '@fnndsc/chili';
183
+
184
+ const mapper = pathMapper_get();
185
+
186
+ // Batch operations with cache awareness
187
+ for (const file of manyFiles) {
188
+ const result = await mapper.logical_toPhysical(file.logicalPath);
189
+ // ...
190
+ }
191
+
192
+ // Check performance
193
+ const stats = mapper.stats_get();
194
+ console.log(`Resolved ${stats.hits + stats.misses} paths with ${stats.hitRate * 100}% hit rate`);
195
+ ```
196
+
197
+ ## Migration Notes
198
+
199
+ ### Backward Compatibility
200
+
201
+ ✅ **Fully backward compatible**
202
+ - Existing `logical_toPhysical()` calls work unchanged
203
+ - Same function signature and return type
204
+ - No breaking changes
205
+
206
+ ### Performance Impact
207
+
208
+ - **First access**: Identical to previous implementation
209
+ - **Subsequent accesses**: Up to 83% faster for common patterns
210
+ - **Memory overhead**: ~1 MB per 10,000 unique paths
211
+
212
+ ### Future Enhancements
213
+
214
+ Potential improvements:
215
+ 1. **LRU eviction**: Limit cache size with least-recently-used eviction
216
+ 2. **Persistent cache**: Serialize cache to disk across sessions
217
+ 3. **Metrics export**: Export detailed performance metrics
218
+ 4. **Link prefetching**: Proactively fetch links for common paths
219
+
220
+ ## Files
221
+
222
+ ```
223
+ chili/
224
+ ├── src/
225
+ │ ├── path/
226
+ │ │ └── pathMapper.ts # PathMapper implementation (new)
227
+ │ ├── utils/
228
+ │ │ └── cli.ts # Updated to use PathMapper
229
+ │ └── utils.ts # Exports PathMapper
230
+ ├── tests/
231
+ │ └── pathMapper.test.ts # Comprehensive test suite (new)
232
+ └── docs/
233
+ └── pathMapper.md # This document
234
+ ```
235
+
236
+ ## References
237
+
238
+ - Original implementation: `chili/src/utils/cli.ts` (lines 133-258, now replaced)
239
+ - Issue: Unacceptable lag in path resolution due to repeated tree walking
240
+ - Solution: Hierarchical prefix caching with singleton pattern
241
+
242
+ ---
243
+
244
+ **Author:** Claude + User collaboration
245
+ **Date:** 2025-01-31
246
+ **Status:** ✅ Implemented and tested
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@fnndsc/chili",
3
+ "version": "3.2.5",
4
+ "description": "ChILI handles Intelligent Line Interactions: A CLI and library for ChRIS, acting as the controller layer for business logic and user presentation.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/FNNDSC/chili.git"
8
+ },
9
+ "homepage": "https://github.com/FNNDSC/chili#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/FNNDSC/chili/issues"
12
+ },
13
+ "main": "dist/index.js",
14
+ "types": "dist/index.d.ts",
15
+ "type": "module",
16
+ "exports": {
17
+ ".": "./dist/index.js",
18
+ "./utils": "./dist/utils.js",
19
+ "./*": "./dist/*",
20
+ "./package.json": "./package.json"
21
+ },
22
+ "bin": {
23
+ "chili": "./dist/index.js"
24
+ },
25
+ "scripts": {
26
+ "build": "tsc && chmod +x ./dist/index.js",
27
+ "start": "node dist/index.js",
28
+ "test": "jest",
29
+ "test:coverage": "jest --coverage --coverageProvider=v8",
30
+ "clean": "rm -rf dist types fnndsc-chili*tgz",
31
+ "build:cumin": "cd ../cumin && npm run build",
32
+ "build:chili": "npm run clean && npm install && npm run build",
33
+ "build:all": "npm run build:cumin && npm run build:chili && npm run pack",
34
+ "pack": "npm pack",
35
+ "install:local": "npm i --ignore-scripts $(npm pack | tail -1)",
36
+ "deploy": "npm run build:all && npm run install:local"
37
+ },
38
+ "files": [
39
+ "dist/**/*",
40
+ "docs/**/*"
41
+ ],
42
+ "keywords": [
43
+ "chris",
44
+ "cli",
45
+ "repl"
46
+ ],
47
+ "author": "FNNDSC <dev@babyMRI.org>",
48
+ "license": "MIT",
49
+ "dependencies": {
50
+ "@fnndsc/cumin": "^3.2.4",
51
+ "@fnndsc/salsa": "^3.2.4",
52
+ "@fortawesome/fontawesome-free": "^6.6.0",
53
+ "@fortawesome/fontawesome-svg-core": "^6.6.0",
54
+ "@fortawesome/free-solid-svg-icons": "^6.6.0",
55
+ "@mermaid-js/mermaid-cli": "^11.12.0",
56
+ "archy": "^1.0.0",
57
+ "asciidoctor": "^3.0.4",
58
+ "axios": "^1.6.0",
59
+ "chalk": "^4.1.2",
60
+ "cli-highlight": "^2.1.11",
61
+ "cli-progress": "^3.12.0",
62
+ "cli-table3": "^0.6.3",
63
+ "commander": "^12.1.0",
64
+ "figlet": "^1.7.0",
65
+ "js-yaml": "^4.1.0",
66
+ "marked": "^9.1.0",
67
+ "marked-terminal": "^6.1.0",
68
+ "node-fetch": "^3.3.2",
69
+ "omelette": "^0.4.17",
70
+ "open": "^10.1.0",
71
+ "table": "^6.8.1"
72
+ },
73
+ "devDependencies": {
74
+ "@types/archy": "^0.0.36",
75
+ "@types/cli-progress": "^3.11.6",
76
+ "@types/figlet": "^1.5.8",
77
+ "@types/jest": "^30.0.0",
78
+ "@types/js-yaml": "^4.0.5",
79
+ "@types/marked-terminal": "^6.1.1",
80
+ "@types/node": "^24.10.1",
81
+ "@types/node-fetch": "^2.6.11",
82
+ "@types/omelette": "^0.4.4",
83
+ "jest": "^30.2.0",
84
+ "ts-jest": "^29.4.5",
85
+ "typescript": "^5.5.2"
86
+ },
87
+ "overrides": {
88
+ "glob": "^10.0.0",
89
+ "inflight": "npm:@aashutoshrathi/word-wrap@^1.2.6",
90
+ "node-domexception": "npm:undici-types@^6.19.8"
91
+ }
92
+ }