@eyeauras/youtrack-cli 0.1.0

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 (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +349 -0
  3. package/dist/src/agile-commands.d.ts +2 -0
  4. package/dist/src/agile-commands.d.ts.map +1 -0
  5. package/dist/src/agile-commands.js +18 -0
  6. package/dist/src/agile-commands.js.map +1 -0
  7. package/dist/src/agile.d.ts +8 -0
  8. package/dist/src/agile.d.ts.map +1 -0
  9. package/dist/src/agile.js +64 -0
  10. package/dist/src/agile.js.map +1 -0
  11. package/dist/src/article-extras-commands.d.ts +2 -0
  12. package/dist/src/article-extras-commands.d.ts.map +1 -0
  13. package/dist/src/article-extras-commands.js +27 -0
  14. package/dist/src/article-extras-commands.js.map +1 -0
  15. package/dist/src/article-extras.d.ts +8 -0
  16. package/dist/src/article-extras.d.ts.map +1 -0
  17. package/dist/src/article-extras.js +22 -0
  18. package/dist/src/article-extras.js.map +1 -0
  19. package/dist/src/articles-commands.d.ts +3 -0
  20. package/dist/src/articles-commands.d.ts.map +1 -0
  21. package/dist/src/articles-commands.js +25 -0
  22. package/dist/src/articles-commands.js.map +1 -0
  23. package/dist/src/articles.d.ts +11 -0
  24. package/dist/src/articles.d.ts.map +1 -0
  25. package/dist/src/articles.js +41 -0
  26. package/dist/src/articles.js.map +1 -0
  27. package/dist/src/attachment-download-commands.d.ts +2 -0
  28. package/dist/src/attachment-download-commands.d.ts.map +1 -0
  29. package/dist/src/attachment-download-commands.js +19 -0
  30. package/dist/src/attachment-download-commands.js.map +1 -0
  31. package/dist/src/attachment-download.d.ts +16 -0
  32. package/dist/src/attachment-download.d.ts.map +1 -0
  33. package/dist/src/attachment-download.js +127 -0
  34. package/dist/src/attachment-download.js.map +1 -0
  35. package/dist/src/attachment-form.d.ts +2 -0
  36. package/dist/src/attachment-form.d.ts.map +1 -0
  37. package/dist/src/attachment-form.js +20 -0
  38. package/dist/src/attachment-form.js.map +1 -0
  39. package/dist/src/bin.d.ts +3 -0
  40. package/dist/src/bin.d.ts.map +1 -0
  41. package/dist/src/bin.js +10 -0
  42. package/dist/src/bin.js.map +1 -0
  43. package/dist/src/bundle-values-commands.d.ts +3 -0
  44. package/dist/src/bundle-values-commands.d.ts.map +1 -0
  45. package/dist/src/bundle-values-commands.js +30 -0
  46. package/dist/src/bundle-values-commands.js.map +1 -0
  47. package/dist/src/bundle-values.d.ts +13 -0
  48. package/dist/src/bundle-values.d.ts.map +1 -0
  49. package/dist/src/bundle-values.js +24 -0
  50. package/dist/src/bundle-values.js.map +1 -0
  51. package/dist/src/cli-support.d.ts +19 -0
  52. package/dist/src/cli-support.d.ts.map +1 -0
  53. package/dist/src/cli-support.js +81 -0
  54. package/dist/src/cli-support.js.map +1 -0
  55. package/dist/src/cli.d.ts +3 -0
  56. package/dist/src/cli.d.ts.map +1 -0
  57. package/dist/src/cli.js +99 -0
  58. package/dist/src/cli.js.map +1 -0
  59. package/dist/src/client.d.ts +62 -0
  60. package/dist/src/client.d.ts.map +1 -0
  61. package/dist/src/client.js +348 -0
  62. package/dist/src/client.js.map +1 -0
  63. package/dist/src/field-catalog-commands.d.ts +4 -0
  64. package/dist/src/field-catalog-commands.d.ts.map +1 -0
  65. package/dist/src/field-catalog-commands.js +31 -0
  66. package/dist/src/field-catalog-commands.js.map +1 -0
  67. package/dist/src/field-catalog.d.ts +12 -0
  68. package/dist/src/field-catalog.d.ts.map +1 -0
  69. package/dist/src/field-catalog.js +22 -0
  70. package/dist/src/field-catalog.js.map +1 -0
  71. package/dist/src/group-directory-commands.d.ts +3 -0
  72. package/dist/src/group-directory-commands.d.ts.map +1 -0
  73. package/dist/src/group-directory-commands.js +37 -0
  74. package/dist/src/group-directory-commands.js.map +1 -0
  75. package/dist/src/group-directory.d.ts +12 -0
  76. package/dist/src/group-directory.d.ts.map +1 -0
  77. package/dist/src/group-directory.js +23 -0
  78. package/dist/src/group-directory.js.map +1 -0
  79. package/dist/src/index.d.ts +18 -0
  80. package/dist/src/index.d.ts.map +1 -0
  81. package/dist/src/index.js +18 -0
  82. package/dist/src/index.js.map +1 -0
  83. package/dist/src/issue-attachments-commands.d.ts +2 -0
  84. package/dist/src/issue-attachments-commands.d.ts.map +1 -0
  85. package/dist/src/issue-attachments-commands.js +21 -0
  86. package/dist/src/issue-attachments-commands.js.map +1 -0
  87. package/dist/src/issue-attachments.d.ts +5 -0
  88. package/dist/src/issue-attachments.d.ts.map +1 -0
  89. package/dist/src/issue-attachments.js +11 -0
  90. package/dist/src/issue-attachments.js.map +1 -0
  91. package/dist/src/issue-context-commands.d.ts +4 -0
  92. package/dist/src/issue-context-commands.d.ts.map +1 -0
  93. package/dist/src/issue-context-commands.js +47 -0
  94. package/dist/src/issue-context-commands.js.map +1 -0
  95. package/dist/src/issue-context.d.ts +15 -0
  96. package/dist/src/issue-context.d.ts.map +1 -0
  97. package/dist/src/issue-context.js +60 -0
  98. package/dist/src/issue-context.js.map +1 -0
  99. package/dist/src/issue-fields-commands.d.ts +5 -0
  100. package/dist/src/issue-fields-commands.d.ts.map +1 -0
  101. package/dist/src/issue-fields-commands.js +21 -0
  102. package/dist/src/issue-fields-commands.js.map +1 -0
  103. package/dist/src/issue-fields.d.ts +9 -0
  104. package/dist/src/issue-fields.d.ts.map +1 -0
  105. package/dist/src/issue-fields.js +101 -0
  106. package/dist/src/issue-fields.js.map +1 -0
  107. package/dist/src/issue-query-commands.d.ts +3 -0
  108. package/dist/src/issue-query-commands.d.ts.map +1 -0
  109. package/dist/src/issue-query-commands.js +58 -0
  110. package/dist/src/issue-query-commands.js.map +1 -0
  111. package/dist/src/issue-query.d.ts +16 -0
  112. package/dist/src/issue-query.d.ts.map +1 -0
  113. package/dist/src/issue-query.js +75 -0
  114. package/dist/src/issue-query.js.map +1 -0
  115. package/dist/src/issue-relations-commands.d.ts +3 -0
  116. package/dist/src/issue-relations-commands.d.ts.map +1 -0
  117. package/dist/src/issue-relations-commands.js +28 -0
  118. package/dist/src/issue-relations-commands.js.map +1 -0
  119. package/dist/src/issue-relations.d.ts +14 -0
  120. package/dist/src/issue-relations.d.ts.map +1 -0
  121. package/dist/src/issue-relations.js +35 -0
  122. package/dist/src/issue-relations.js.map +1 -0
  123. package/dist/src/issue-time-commands.d.ts +3 -0
  124. package/dist/src/issue-time-commands.d.ts.map +1 -0
  125. package/dist/src/issue-time-commands.js +24 -0
  126. package/dist/src/issue-time-commands.js.map +1 -0
  127. package/dist/src/issue-time.d.ts +9 -0
  128. package/dist/src/issue-time.d.ts.map +1 -0
  129. package/dist/src/issue-time.js +88 -0
  130. package/dist/src/issue-time.js.map +1 -0
  131. package/dist/src/time-settings-commands.d.ts +3 -0
  132. package/dist/src/time-settings-commands.d.ts.map +1 -0
  133. package/dist/src/time-settings-commands.js +27 -0
  134. package/dist/src/time-settings-commands.js.map +1 -0
  135. package/dist/src/time-settings.d.ts +8 -0
  136. package/dist/src/time-settings.d.ts.map +1 -0
  137. package/dist/src/time-settings.js +14 -0
  138. package/dist/src/time-settings.js.map +1 -0
  139. package/dist/src/user-directory-commands.d.ts +3 -0
  140. package/dist/src/user-directory-commands.d.ts.map +1 -0
  141. package/dist/src/user-directory-commands.js +24 -0
  142. package/dist/src/user-directory-commands.js.map +1 -0
  143. package/dist/src/user-directory.d.ts +9 -0
  144. package/dist/src/user-directory.d.ts.map +1 -0
  145. package/dist/src/user-directory.js +15 -0
  146. package/dist/src/user-directory.js.map +1 -0
  147. package/package.json +45 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AI CLI Factory contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,349 @@
1
+ # YouTrack CLI
2
+
3
+ The scoped package provides the `youtrack-cli` command:
4
+
5
+ ```sh
6
+ npm install --global @eyeauras/youtrack-cli
7
+ youtrack-cli --help
8
+ youtrack-cli profile configure
9
+ ```
10
+
11
+ Requires Node.js 22+ and npm. Compiled JavaScript and the Core dependency are installed
12
+ automatically; no repository checkout, .NET, TypeScript compiler or browser is needed.
13
+ Interactive profile configuration guides endpoint/authentication setup; secrets use the
14
+ OS credential store. Existing `youtrack-cli` profiles and permissions are unchanged.
15
+ Licensed under MIT. See the [release guide](https://github.com/iXab3r/EyeAuras.CliFactory/blob/main/docs/npm-release.md).
16
+
17
+ YouTrack uses the same `@eyeauras/cli-factory` workspace as TeamCity. The CLI
18
+ provides standard profiles/auth/permissions and 118 REST operations: 98 ReadOnly and 20 Update.
19
+ A separate ReadOnly command downloads one selected issue attachment; it is not REST operation 119.
20
+ There are 117 service leaves: 116 for REST operations plus download. Two `--direct` selectors
21
+ each select a second REST endpoint without adding another leaf.
22
+ The foundational read projections are:
23
+
24
+ | Command | Default fields |
25
+ |---|---|
26
+ | `user me` | `id,login` |
27
+ | `project list` | `id,name,shortName` |
28
+ | `issues list [--query <query>]` | `id,idReadable,summary,project(id,name,shortName),updated,resolved` |
29
+ | `issues get <issueID>` | Issue-list fields plus `description,created` |
30
+ | `issues comments list <issueID>` | `id,text,author(id,login),created,updated` |
31
+
32
+ REST reads support explicit `--fields <projection>`. Projection results retain the server's
33
+ field names, `$type` and nullable values; authentication always validates fixed `id,login`.
34
+ Offset collections use `--top 50 --skip 0` by default; top is 1–100 and skip a nonnegative integer.
35
+ Paging accepts unsigned decimal digits, including leading zeros; signs (including `-0`), whitespace,
36
+ fractions, exponents and unsafe integers reject. Invalid syntax, range and overflow now fail before
37
+ onboarding or credential access on CLI, execute and RPC. The static errors are
38
+ `YouTrack top must be a decimal integer between 1 and 100.` and
39
+ `YouTrack skip must be a nonnegative safe decimal integer.`; they never include the supplied input.
40
+ Directly callable service methods retain their own range validation.
41
+ Each collection command makes one request, and rejects an oversized server page. No `--all`
42
+ or implicit nested follow-up requests are provided. Issue IDs are encoded as opaque path
43
+ segments, including readable IDs such as `DEMO-1`.
44
+
45
+ ```powershell
46
+ npm run youtrack -- project list --top 3 --profile youtrack-dev
47
+ npm run youtrack -- issues list --query "project: DEMO #Unresolved" --profile youtrack-dev
48
+ npm run youtrack -- issues get DEMO-1 --fields "id,summary,description" --profile youtrack-dev
49
+ npm run youtrack -- issues comments list DEMO-1 --top 3 --profile youtrack-dev
50
+ ```
51
+
52
+ Handlers return domain values for human, `--json` and persistent `--json-rpc` output.
53
+ Empty collections are `[]`. Signed/credential-bearing URLs and the active bearer token are
54
+ scrubbed recursively, including explicitly projected nested fields. Unsigned URLs remain
55
+ unchanged. Failures expose HTTP status and safe Retry-After information, never raw server
56
+ errors or authentication material. Remote mutations require the Update gate, described below.
57
+ The ReadOnly download command writes only its explicitly requested local file beneath profile AppData.
58
+
59
+ JSON responses now have an 8 MiB limit on actual decoded bytes, including responses without a
60
+ Content-Length header. This allows room for the existing bounded pages and text-rich projections;
61
+ it does not guarantee every requested projection will fit. Narrow `--fields` or reduce `--top` if
62
+ needed. Invalid/truncated identity transfer lengths, overflow, stream failure or cancellation fail
63
+ with `YouTrack response stream failed, exceeded 8 MiB, or was cancelled.` without response content.
64
+ Compressed wire length is syntax-checked but not compared with decoded size. Existing HTTP status,
65
+ Retry-After, empty/null mutation and UTF-8 BOM behavior are unchanged. Attachment-download limits
66
+ remain separate; no request is retried automatically.
67
+
68
+ ## Sign in locally
69
+
70
+ Build from the repository root with `npm run build`, then use your current terminal and
71
+ the normal CLI pipeline; no wrapper or auxiliary terminal window is required:
72
+
73
+ ```powershell
74
+ npm run youtrack -- profile configure youtrack-dev
75
+ ```
76
+
77
+ Only if this terminal can see the existing configured profile, resume token login without changing it:
78
+
79
+ ```powershell
80
+ npm run youtrack -- auth login --profile youtrack-dev
81
+ ```
82
+
83
+ If login reports a missing profile, run `profile configure` first in that same terminal; do not assume
84
+ profile visibility is identical across terminal environments.
85
+
86
+ Enter the YouTrack server URL when configuring a new profile, then enter your permanent token at the
87
+ factory's hidden token prompt. Never put a token in chat or command-line arguments.
88
+ Use the server origin and any context path (for example `https://youtrack.example.com/track`),
89
+ without `/api`, credentials, query or fragment. HTTPS is required; HTTP is accepted only
90
+ for explicit `localhost`, `127.0.0.1` or `[::1]` development addresses. Redirects are refused.
91
+
92
+ Create a token in your YouTrack profile with the YouTrack service scope and only the
93
+ account permissions you need. See JetBrains' [permanent token documentation](https://www.jetbrains.com/help/youtrack/devportal/authentication-with-permanent-token.html).
94
+ The identity fields follow [My User Profile](https://www.jetbrains.com/help/youtrack/devportal/resource-api-users-me.html).
95
+
96
+ The candidate is validated before storage in the operating-system credential store.
97
+ Non-secret profiles live in current-user AppData for application `youtrack-cli`; the
98
+ credential namespace is separate from TeamCity and every YouTrack profile is isolated.
99
+ `ReadOnly` starts enabled and `Update` disabled. Every mutation requires an explicitly enabled Update category.
100
+
101
+ For automation, supply token input from a trusted secret source on stdin:
102
+
103
+ ```powershell
104
+ npm run youtrack -- auth login --profile youtrack-dev --token-stdin
105
+ ```
106
+
107
+ The factory selects a new candidate from explicit `--token-stdin`, then `YOUTRACK_TOKEN`,
108
+ then the hidden prompt in an ordinary interactive CLI with all three standard streams
109
+ attached to TTYs. JSON, JSON-RPC, programmatic calls and redirected streams never prompt.
110
+ `--token-stdin` is unavailable in JSON-RPC/programmatic execution because stdin belongs to
111
+ the transport; provide the configured environment candidate instead.
112
+ Stored credentials remain available for service calls but are never configure/login candidates.
113
+ Use secret-manager stdin without putting the token in shell history; there is no token flag
114
+ or plaintext fallback.
115
+
116
+ Configure validates the proposed settings and candidate before changing an existing profile or
117
+ credential. Missing or rejected input leaves the existing pair unchanged; a new profile is not
118
+ created. After successful validation, configure removes the old credential, saves configuration,
119
+ then stores the new credential. A storage failure may leave an unauthenticated profile; no old
120
+ credential is restored against changed settings. Repeat `profile configure <name> --token-stdin`
121
+ after repairing local storage. Errors never include backend credential diagnostics.
122
+ `auth login` replaces credentials without changing endpoint settings. These rules govern the
123
+ authentication lifecycle; ordinary non-authentication `profile set` remains a separate operation.
124
+
125
+ ## Controlled mutations
126
+
127
+ `Update` is disabled by default and isolated per profile. Enable it only when you deliberately
128
+ intend to write, using the standard `permissions grant Update --profile <name>` command.
129
+ This documentation does not enable it automatically. The local proof remains ReadOnly.
130
+
131
+ | Command | Required `--body` JSON object |
132
+ |---|---|
133
+ | `issues create` | Nonempty `project.id` and `summary`; optional string or null `description` |
134
+ | `issues update <issueID>` | Nonempty subset of `summary` and `description`; summary must be nonempty |
135
+ | `issues comments add <issueID>` | Nonempty `text` only |
136
+
137
+ Missing `--body` and malformed JSON fail before profile onboarding or credential access.
138
+ Description and comment text preserve multiline Markdown. Omitted fields remain unchanged;
139
+ `description: null` clears the description. Unknown fields (including nested project properties)
140
+ are rejected locally. The `issues create` and `issues update` bodies do not accept custom fields,
141
+ state/assignee, visibility or notification controls. Use the separate `issues fields set` command
142
+ for supported custom-field changes described below. A project may require custom fields without
143
+ defaults; creation then fails safely with the server's status rather than guessing missing values.
144
+
145
+ For Windows **CMD**, replace the example project ID before deliberately running a write:
146
+
147
+ ```cmd
148
+ npm run youtrack -- issues create --body "{\"project\":{\"id\":\"PROJECT_ID\"},\"summary\":\"Example issue\"}" --profile youtrack-dev
149
+ ```
150
+
151
+ The equivalent **PowerShell 7** command uses ordinary single quotes around JSON:
152
+
153
+ ```powershell
154
+ npm run youtrack -- issues create --body '{"project":{"id":"PROJECT_ID"},"summary":"Example issue"}' --profile youtrack-dev
155
+ ```
156
+
157
+ Windows **PowerShell 5.1** requires escaping the inner quotes through the npm/CMD pipeline:
158
+
159
+ ```powershell
160
+ npm run youtrack -- issues create --body '{\"project\":{\"id\":\"PROJECT_ID\"},\"summary\":\"Example issue\"}' --profile youtrack-dev
161
+ ```
162
+
163
+ Updates return `id,idReadable,summary,updated`; comment creation returns the same finite fields
164
+ as comment reads. An empty successful mutation response becomes JSON `null`. No mutation retries,
165
+ implicit follow-up reads or automatic permission grants occur. Malformed bodies and remote errors
166
+ use safe diagnostics without echoing submitted JSON or server response text.
167
+
168
+ ## Additional commands through operation 50
169
+
170
+ The same tree is available through normal npm commands, JSON and JSON-RPC. Use leaf `--help`
171
+ for exact arguments and permission categories; no endpoint, header or arbitrary HTTP escape hatch exists.
172
+
173
+ | Family | Additional commands | Operations |
174
+ |---|---|---:|
175
+ | Queries and commands | `commands assist/apply`, `search assist`, `issues count`, `saved-queries list/get` | 6 |
176
+ | Project, users and fields | `project get`, `project field list/get`, `user list`, `issues fields list/get/set` | 7 |
177
+ | Links and tags | `link-types list/get`, `tags list/get`, `issues links list/get/issues/add/remove`, `issues tags list/add/remove` | 12 |
178
+ | Activity and context | `activities page`, `issues activity page`, `issues comments get/update`, `issues vcs-changes list/get`, `issues sprints list` | 7 |
179
+ | Work time | `issues time-tracking get`, `issues work-items list/get/add/update`, `work-items list/get` | 7 |
180
+ | Attachments | `issues attachments list/get/upload` | 3 |
181
+
182
+ `commands assist`, `search assist` and `issues count` use POST on the wire but remain ReadOnly:
183
+ they compute suggestions or counts without applying changes. Assist is not a dry run or a guarantee
184
+ that a later command will succeed. Required `--query` is preserved exactly; optional `--caret` is
185
+ between zero and its length. `commands apply` requires 1–20 comma-separated explicit issue IDs via
186
+ `--issues`, never an implicit search expansion. It has no run-as or silent mode. Count returns the
187
+ server's `count`, including `-1` (pending) or `null`; the CLI never polls automatically.
188
+
189
+ Activity commands require `--categories <categories>` and support `--cursor`, `--reverse` and
190
+ `--fields`. They return one server-defined page with `activities`, before/after cursors and flags.
191
+ They do not support offset or page-size flags and do not fetch a next page automatically.
192
+ Activity time, author and issue-query filters are outside this block.
193
+
194
+ Project fields expose field types and settings without following bundle references. Issue fields
195
+ preserve polymorphic values, including scalar, null and array values. `issues fields set` accepts
196
+ an explicit `$type` and `value`: single/multi Enum, Build, Version, Owned, Group and User types;
197
+ State, Simple, Date, Period and Text types. References use explicit identity selectors (`id`,
198
+ `name`, and `login` for users); the CLI performs no name lookup. Single values can use `null`,
199
+ multi values use `[]` to clear. Period values accept nonnegative 32-bit integer `minutes`
200
+ and/or `presentation`; dates use
201
+ integer Unix milliseconds. Text values use `{ "text": "..." }`. StateMachine fields instead
202
+ require `$type` and `{ "event": { "id": "..." } }`; inspect `possibleEvents` explicitly with
203
+ `--fields` before selecting a transition. Unknown types and body fields fail locally.
204
+
205
+ Relationship additions accept only `{ "id": "TARGET_ID" }`. Link IDs include the explicit
206
+ direction returned by YouTrack. `issues links remove <issueID> <linkID> <targetIssueID>` unlinks
207
+ the target; `issues tags remove <issueID> <tagID>` unassigns the tag. Neither deletes the issue or
208
+ tag itself. Comment updates accept only nonempty `text`, preserving multiline Markdown.
209
+
210
+ Work-item creation requires `duration.minutes` (nonnegative 32-bit integer) and/or nonempty
211
+ `duration.presentation`. Both may be supplied; YouTrack interprets presentation text. Writable
212
+ fields are `duration`, `date`, `author`, `type`, `text`, `created` and `updated`. Dates/timestamps
213
+ use integer Unix milliseconds; `author` and `type` use `{ "id": "..." }` or `null`. Text may be
214
+ empty, multiline or null; `updated` may also be null. Updates require at least one field and
215
+ preserve omitted fields. No client-side date, user or duration conversion is guessed. Work-item
216
+ writes accept `--fields`; only global `work-items list` supports `--query`. Other date/author
217
+ filters are outside this block. Time-tracking inspection defaults to `id,enabled`, without
218
+ expanding work items.
219
+
220
+ Attachment reads default to `id,name,size,mimeType`; explicit read projections still scrub signed
221
+ URLs. Upload requires one explicit `--file <path>` and sends one native multipart request, returning
222
+ metadata only. Missing/blank/invalid path syntax fails before onboarding. Regular-file and read
223
+ checks occur inside the Update handler before HTTP, so denied uploads do not inspect files.
224
+ Only the basename becomes the multipart filename; errors never echo local paths. No attachment
225
+ binary download or signed-URL follow-up was included in the first 50 operations; the separate
226
+ download capability is documented below.
227
+
228
+ ## Additional commands through operation 100
229
+
230
+ These add fifty REST operations. Two `--direct` switches each select between two documented
231
+ read endpoints, so at this stage the tree had 98 service leaves for 100 operations.
232
+
233
+ | Family | Additional commands | Operations |
234
+ |---|---|---:|
235
+ | Global fields and choices | `field list/get`, `field type list`, `bundle enum/state list/get`, `bundle enum/state value list/get` | 11 |
236
+ | User directory and bundles | `user get`, `bundle user list/get`, `bundle user member list`, `bundle user group list/get`, `bundle user individual list/get` | 8 |
237
+ | Groups and project teams | `group list/get`, `group member list [--direct]`, `group subgroup list`, `project team get`, `project team group list`, `project team user list [--direct]` | 9 |
238
+ | Time metadata | `time-tracking settings get`, `time-tracking work-time get`, `work-item-type list/get`, `project time-tracking get`, `project work-item-type list/get` | 7 |
239
+ | Boards and sprints | `agile list/get`, `sprint list/get/create/update` | 6 |
240
+ | Articles | `article list/get/create/update`, `article comment list/get/add/update`, `project article list` | 9 |
241
+
242
+ Every new read supports `--fields`. Offset lists retain one-page `--top`/`--skip` behavior;
243
+ details and settings reads have no paging flags. Bundle defaults omit nested values and members:
244
+ use the matching paginated child command. Archived enum/state values are preserved; state
245
+ `isResolved` is a resolution classification, not a list of workflow transition events.
246
+ Global field, bundle and value detail IDs are opaque database IDs; no name lookup is performed.
247
+
248
+ User-bundle `member list` includes membership supplied by groups and directly added users.
249
+ `individual list/get` covers only users added directly, and `group list/get` covers attached
250
+ groups. `group member list` and `project team user list` return all visible members by default;
251
+ `--direct` selects the server's direct-membership endpoint. The CLI does not crawl groups or
252
+ infer membership locally. Project-team endpoints require YouTrack 2026.1 or later; unavailable
253
+ or denied routes remain errors, with no alternate Hub route or permission changes.
254
+
255
+ Time metadata exposes the server's work schedule and available work-item types without changing
256
+ settings or synthesizing locale/calendar defaults. Global/project type lists are paginated;
257
+ other metadata reads return one object. No new date, author, search or archive filters are added
258
+ by these directory commands.
259
+
260
+ Sprint reads use an explicit board ID; the literal sprint ID `current` goes directly to the
261
+ same documented detail/update route. Create requires a nonempty single-line `name`; update
262
+ requires at least one supported field. Writable fields are `name`, `goal`, `start`, `finish`,
263
+ `archived` and `isDefault`. Goal preserves empty/multiline text or null; dates are safe integer
264
+ Unix milliseconds or null; flags are booleans. Create additionally accepts
265
+ `previousSprint: { "id": "..." }`, which explicitly moves unresolved issues from that sprint.
266
+ It is never inferred and is rejected on update. `isDefault: true` affects where matching new
267
+ board issues go. Sprint issue-membership payloads, agile mutations and deletion remain outside
268
+ this block. Reads and writes support `--fields`; there is no carryover lookup or polling.
269
+
270
+ Article create accepts nonempty `project.id` and `summary`, with optional string/null `content`.
271
+ Article update accepts a nonempty subset of `summary` and `content`; comments accept only
272
+ nonempty `text`, preserving multiline Markdown. Omission keeps fields unchanged; content null
273
+ clears it. These narrow bodies exclude parent/hierarchy, visibility, reporter, stars, tags,
274
+ nested attachments/comments and comment pinning/reactions. The REST entities have additional
275
+ writable properties, but this CLI does not forward them. Article and comment reads/writes
276
+ support `--fields`; article lists have no query flag. Article attachment transfer and hierarchy
277
+ reads are documented below. Hierarchy writes, draft publishing, notification suppression and
278
+ deletion remain unsupported.
279
+
280
+ ## Remaining bundles, article hierarchy and attachment transfer
281
+
282
+ `bundle build/owned/version list/get` and their `value list/get` children expose twelve
283
+ ReadOnly operations. Lists use the normal bounded page; bundle defaults do not expand values.
284
+ Archived values and nullable owners are preserved without local lookup or filtering. Version
285
+ value `startDate` requires YouTrack 2023.1 or later; the CLI does not substitute another field.
286
+
287
+ `article attachment list/get/upload` and `article child list/get` plus `article parent get`
288
+ add six operations. The hierarchy commands read only the selected relationship and never
289
+ recursively traverse descendants. A literal JSON null parent remains null; an empty body,
290
+ 404 or malformed response is still an error. Article attachment upload requires one explicit
291
+ `--file`, the Update gate, and uses native multipart after checking that the file is regular.
292
+ It supports `--fields` for the response. No implicit file discovery or article binary download
293
+ is provided, and upload never grants permissions.
294
+
295
+ ```powershell
296
+ npm run youtrack -- issues attachments download DEMO-1 1-1 --profile youtrack-dev
297
+ npm run youtrack -- issues attachments download DEMO-1 1-1 --name report.txt --max-bytes 1048576 --profile youtrack-dev
298
+ ```
299
+
300
+ Issue attachment download first reads the exact attachment's fixed metadata, then requests only
301
+ its returned URL. The file request sends neither Authorization nor cookies, follows no redirects,
302
+ and accepts only the configured origin and documented attachment path beneath its context.
303
+ External/CDN URLs fail rather than relaxing this policy. Signatures stay in memory and never
304
+ appear in metadata output, errors or the download result.
305
+
306
+ Files go beneath the selected profile's `AppDataDirectory/downloads`. `--name` must be a safe
307
+ single basename; the default prefixes the sanitized attachment name with its ID. Existing
308
+ filenames are never overwritten. The default limit is 25 MiB; `--max-bytes` accepts 1–104857600
309
+ and is enforced while streaming as well as against Content-Length. Core stages bytes in a fresh
310
+ private directory under this profile's `temp`, then hard-links only the complete identity-checked
311
+ file into `downloads`. Partial files are removed when their identities remain trusted. Output
312
+ contains only sanitized ID/name, local path, byte count and content type.
313
+ No binary data is printed. Publication uses an exclusive hard link; unsupported filesystems
314
+ fail without a copy/rename fallback. Existing links or junctions in the directory chain and detectable directory/staged/destination
315
+ replacement are rejected. Unknown replacement files are never removed. This protects
316
+ current-user-owned AppData; it cannot prevent every malicious same-account replacement after the
317
+ last identity check. Errors report whether publication occurred and whether private staging cleanup
318
+ failed, without raw filesystem/service details. The local proof never invokes download or upload.
319
+
320
+ ## Local proof and offline tests
321
+
322
+ After configuring a profile, explicitly run:
323
+
324
+ ```powershell
325
+ npm run test:integration --workspace @eyeauras/youtrack-cli -- --profile youtrack-dev
326
+ ```
327
+
328
+ The explicit local proof uses twenty-four fixed ReadOnly rows through the compiled CLI, named profile
329
+ and OS keyring: current user; projects and issues (up to three each); one selected issue and its
330
+ comments; one selected project and its custom fields; users; and the selected issue's custom
331
+ fields, attachments, tags, links and work items. It also reads global fields, user bundles,
332
+ groups, the selected project's team/time settings, global work-item types, agiles and articles.
333
+ It also lists build, owned-field and version bundles. No bundle values or article descendants
334
+ are followed, and no binary download is invoked.
335
+ Every collection is capped at three. Projects
336
+ exercise their default projection; all added reads request only `id`. IDs remain in memory.
337
+ Output contains only static PASS/FAIL/SKIP, endpoint templates and counts. Denied or malformed
338
+ attempted reads fail. Reads without a usable project/issue prerequisite are skipped; skipped rows
339
+ are not availability evidence, and failed prerequisites still fail the overall proof. No real
340
+ writes, automatic pagination or further resource discovery occur. CI and arbitrary URL/token/
341
+ command arguments are refused; inherited `YOUTRACK_TOKEN` is removed case-insensitively.
342
+ The shared Core proof invoker keeps the 30-second timeout and separate 64-KiB stdout/stderr bounds;
343
+ see the [shared CI and process contract](../../docs/testing.md).
344
+ Do not run it in CI or add it to generic test commands. A failed proof requires local
345
+ configuration/TLS/permission investigation; its payloads are deliberately not logged.
346
+
347
+ `npm test` builds and runs deterministic offline tests, including MSW against the actual
348
+ client and declaration, synthetic profile/credential isolation, JSON-RPC and proof safety.
349
+ Tests inject `AppArguments` rather than redirecting the user's data directories.
@@ -0,0 +1,2 @@
1
+ export declare const agileRootCommands: import("@eyeauras/cli-factory").CommandDefinition[];
2
+ //# sourceMappingURL=agile-commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agile-commands.d.ts","sourceRoot":"","sources":["../../src/agile-commands.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,iBAAiB,qDA6B7B,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { command } from "@eyeauras/cli-factory";
2
+ import { pagedRead, projectedBodyUpdate, projectedRead } from "./cli-support.js";
3
+ import { createSprint, getAgile, getSprint, listAgiles, listSprints, updateSprint } from "./agile.js";
4
+ export const agileRootCommands = [
5
+ command("agile", "Inspect agile boards", [
6
+ pagedRead("list", "List one page of accessible agile boards", listAgiles),
7
+ projectedRead("get <agile>", "Read an agile board without expanding its sprints or projects", getAgile),
8
+ ]),
9
+ command("sprint", "Inspect and explicitly manage sprint plans", [
10
+ pagedRead("list <agile>", "List one page of a board's sprints", listSprints),
11
+ projectedRead("get <agile> <sprint>", "Read a sprint; use current for the board's current sprint", getSprint),
12
+ projectedBodyUpdate("create <agile>", "Create with name; optional previousSprint.id moves unresolved issues from that sprint. " +
13
+ "isDefault: true automatically adds matching new issues. Neither setting is inferred.", createSprint),
14
+ projectedBodyUpdate("update <agile> <sprint>", "Update supplied name/goal/start/finish/archived/isDefault; use current for the current sprint. " +
15
+ "isDefault: true automatically adds matching new issues; previousSprint is creation-only.", updateSprint),
16
+ ]),
17
+ ];
18
+ //# sourceMappingURL=agile-commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agile-commands.js","sourceRoot":"","sources":["../../src/agile-commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtG,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,OAAO,CAAC,OAAO,EAAE,sBAAsB,EAAE;QACvC,SAAS,CAAC,MAAM,EAAE,0CAA0C,EAAE,UAAU,CAAC;QACzE,aAAa,CACX,aAAa,EACb,+DAA+D,EAC/D,QAAQ,CACT;KACF,CAAC;IACF,OAAO,CAAC,QAAQ,EAAE,4CAA4C,EAAE;QAC9D,SAAS,CAAC,cAAc,EAAE,oCAAoC,EAAE,WAAW,CAAC;QAC5E,aAAa,CACX,sBAAsB,EACtB,2DAA2D,EAC3D,SAAS,CACV;QACD,mBAAmB,CACjB,gBAAgB,EAChB,yFAAyF;YACvF,sFAAsF,EACxF,YAAY,CACb;QACD,mBAAmB,CACjB,yBAAyB,EACzB,iGAAiG;YAC/F,0FAA0F,EAC5F,YAAY,CACb;KACF,CAAC;CACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type Connection, type ProjectionOptions, type YouTrackObject } from "./client.js";
2
+ export declare const listAgiles: (connection: Connection, options?: import("./client.js").PageOptions | undefined) => Promise<YouTrackObject[]>;
3
+ export declare const getAgile: (connection: Connection, agile: string, options?: ProjectionOptions | undefined) => Promise<YouTrackObject>;
4
+ export declare const listSprints: (connection: Connection, agile: string, options?: import("./client.js").PageOptions | undefined) => Promise<YouTrackObject[]>;
5
+ export declare const getSprint: (connection: Connection, agile: string, sprint: string, options?: ProjectionOptions | undefined) => Promise<YouTrackObject>;
6
+ export declare function createSprint(connection: Connection, agile: string, input: unknown, options?: ProjectionOptions): Promise<YouTrackObject | null>;
7
+ export declare function updateSprint(connection: Connection, agile: string, sprint: string, input: unknown, options?: ProjectionOptions): Promise<YouTrackObject | null>;
8
+ //# sourceMappingURL=agile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agile.d.ts","sourceRoot":"","sources":["../../src/agile.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AASrB,eAAO,MAAM,UAAU,gHAA8C,CAAC;AACtE,eAAO,MAAM,QAAQ,6GAAuC,CAAC;AAC7D,eAAO,MAAM,WAAW,+HAGvB,CAAC;AACF,eAAO,MAAM,SAAS,6HAIrB,CAAC;AAkDF,wBAAsB,YAAY,CAChC,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,iBAAsB,kCAQhC;AAED,wBAAsB,YAAY,CAChC,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,iBAAsB,kCAQhC"}
@@ -0,0 +1,64 @@
1
+ import { encodedID, fields, mutate, mutationBody, nullableText, readCollectionAt, readObjectAt, requiredText, } from "./client.js";
2
+ const agileFields = "id,name,currentSprint(id,name,start,finish,archived),status(valid,hasJobs)";
3
+ const sprintFields = "id,name,goal,start,finish,archived,isDefault,agile(id,name)";
4
+ function agilePath(agile) {
5
+ return `api/agiles/${encodedID(agile, "agile ID")}`;
6
+ }
7
+ export const listAgiles = readCollectionAt("api/agiles", agileFields);
8
+ export const getAgile = readObjectAt(agilePath, agileFields);
9
+ export const listSprints = readCollectionAt((agile) => `${agilePath(agile)}/sprints`, sprintFields);
10
+ export const getSprint = readObjectAt((agile, sprint) => `${agilePath(agile)}/sprints/${encodedID(sprint, "sprint ID")}`, sprintFields);
11
+ function sprintBody(input, creating) {
12
+ const body = mutationBody(input, [
13
+ "name", "goal", "start", "finish", "archived", "isDefault",
14
+ ...(creating ? ["previousSprint"] : []),
15
+ ]);
16
+ if (creating && !Object.hasOwn(body, "name")) {
17
+ throw new Error("YouTrack sprint creation requires name.");
18
+ }
19
+ if (Object.keys(body).length === 0) {
20
+ throw new Error("YouTrack sprint update requires at least one writable field.");
21
+ }
22
+ const result = {};
23
+ if (Object.hasOwn(body, "name")) {
24
+ if (typeof body.name !== "string") {
25
+ throw new Error("YouTrack sprint name must be nonempty single-line text.");
26
+ }
27
+ result.name = requiredText(body.name, "sprint name");
28
+ }
29
+ if (Object.hasOwn(body, "goal")) {
30
+ result.goal = nullableText(body.goal, "sprint goal");
31
+ }
32
+ for (const key of ["start", "finish"]) {
33
+ if (Object.hasOwn(body, key)) {
34
+ const value = body[key];
35
+ if (value !== null && (typeof value !== "number" || !Number.isSafeInteger(value))) {
36
+ throw new Error(`YouTrack sprint ${key} must be a safe integer UTC timestamp in milliseconds or null.`);
37
+ }
38
+ result[key] = value;
39
+ }
40
+ }
41
+ for (const key of ["archived", "isDefault"]) {
42
+ if (Object.hasOwn(body, key)) {
43
+ if (typeof body[key] !== "boolean") {
44
+ throw new Error(`YouTrack sprint ${key} must be boolean.`);
45
+ }
46
+ result[key] = body[key];
47
+ }
48
+ }
49
+ if (Object.hasOwn(body, "previousSprint")) {
50
+ const previous = mutationBody(body.previousSprint, ["id"]);
51
+ if (typeof previous.id !== "string") {
52
+ throw new Error("YouTrack previousSprint.id must be nonempty text.");
53
+ }
54
+ result.previousSprint = { id: requiredText(previous.id, "previousSprint.id") };
55
+ }
56
+ return result;
57
+ }
58
+ export async function createSprint(connection, agile, input, options = {}) {
59
+ return mutate(connection, `${agilePath(agile)}/sprints`, sprintBody(input, true), fields(options, sprintFields));
60
+ }
61
+ export async function updateSprint(connection, agile, sprint, input, options = {}) {
62
+ return mutate(connection, `${agilePath(agile)}/sprints/${encodedID(sprint, "sprint ID")}`, sprintBody(input, false), fields(options, sprintFields));
63
+ }
64
+ //# sourceMappingURL=agile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agile.js","sourceRoot":"","sources":["../../src/agile.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,MAAM,EACN,MAAM,EACN,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,YAAY,GAIb,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,GAAG,4EAA4E,CAAC;AACjG,MAAM,YAAY,GAAG,6DAA6D,CAAC;AAEnF,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,cAAc,SAAS,CAAC,KAAK,EAAE,UAAU,CAAC,EAAE,CAAC;AACtD,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,gBAAgB,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;AACtE,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;AAC7D,MAAM,CAAC,MAAM,WAAW,GAAG,gBAAgB,CACzC,CAAC,KAAa,EAAE,EAAE,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,UAAU,EAChD,YAAY,CACb,CAAC;AACF,MAAM,CAAC,MAAM,SAAS,GAAG,YAAY,CACnC,CAAC,KAAa,EAAE,MAAc,EAAE,EAAE,CAChC,GAAG,SAAS,CAAC,KAAK,CAAC,YAAY,SAAS,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,EACjE,YAAY,CACb,CAAC;AAEF,SAAS,UAAU,CAAC,KAAc,EAAE,QAAiB;IACnD,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,EAAE;QAC/B,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW;QAC1D,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KACxC,CAAC,CAAC;IACH,IAAI,QAAQ,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;IAClF,CAAC;IACD,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;QAChC,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;QAC7E,CAAC;QACD,MAAM,CAAC,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IACvD,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;QAChC,MAAM,CAAC,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IACvD,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAU,EAAE,CAAC;QAC/C,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;YACxB,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBAClF,MAAM,IAAI,KAAK,CAAC,mBAAmB,GAAG,gEAAgE,CAAC,CAAC;YAC1G,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACtB,CAAC;IACH,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,CAAU,EAAE,CAAC;QACrD,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;YAC7B,IAAI,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC;gBACnC,MAAM,IAAI,KAAK,CAAC,mBAAmB,GAAG,mBAAmB,CAAC,CAAC;YAC7D,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,CAAC,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3D,IAAI,OAAO,QAAQ,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACvE,CAAC;QACD,MAAM,CAAC,cAAc,GAAG,EAAE,EAAE,EAAE,YAAY,CAAC,QAAQ,CAAC,EAAE,EAAE,mBAAmB,CAAC,EAAE,CAAC;IACjF,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,UAAsB,EACtB,KAAa,EACb,KAAc,EACd,OAAO,GAAsB,EAAE;IAE/B,OAAO,MAAM,CACX,UAAU,EACV,GAAG,SAAS,CAAC,KAAK,CAAC,UAAU,EAC7B,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,EACvB,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC,CAC9B,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,UAAsB,EACtB,KAAa,EACb,MAAc,EACd,KAAc,EACd,OAAO,GAAsB,EAAE;IAE/B,OAAO,MAAM,CACX,UAAU,EACV,GAAG,SAAS,CAAC,KAAK,CAAC,YAAY,SAAS,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,EAC/D,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,EACxB,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC,CAC9B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const articlesExtraChildren: import("@eyeauras/cli-factory").CommandDefinition[];
2
+ //# sourceMappingURL=article-extras-commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"article-extras-commands.d.ts","sourceRoot":"","sources":["../../src/article-extras-commands.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,qBAAqB,qDAgDjC,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { command } from "@eyeauras/cli-factory";
2
+ import { requiredText } from "./client.js";
3
+ import { pagedRead, projectedRead, updateCommand, projectionOptions, readOptions } from "./cli-support.js";
4
+ import { getArticleAttachment, getChildArticle, getParentArticle, listArticleAttachments, listChildArticles, uploadArticleAttachment, } from "./article-extras.js";
5
+ export const articlesExtraChildren = [
6
+ command("attachment", "Inspect article attachment metadata and upload one file", [
7
+ pagedRead("list <article>", "List one page of attachment metadata without downloading files", listArticleAttachments),
8
+ projectedRead("get <article> <attachment>", "Read attachment metadata without downloading its contents", getArticleAttachment),
9
+ updateCommand("upload <article>", "Upload one selected regular file to an existing article", async (connection, { args, options }, context) => uploadArticleAttachment(connection, args.article, String(options.file), readOptions(options)), [
10
+ ...projectionOptions,
11
+ {
12
+ flags: "--file <path>",
13
+ description: "Required explicit local input file; no copies or recursion",
14
+ required: true,
15
+ parse: (value) => requiredText(value, "file path"),
16
+ },
17
+ ]),
18
+ ]),
19
+ command("child", "Inspect immediate child articles without recursive traversal", [
20
+ pagedRead("list <article>", "List one page of immediate child articles", listChildArticles),
21
+ projectedRead("get <article> <child>", "Read the specified child of the parent article", getChildArticle),
22
+ ]),
23
+ command("parent", "Inspect an article's parent", [
24
+ projectedRead("get <article>", "Read the parent; preserve a successful JSON null for no parent", getParentArticle),
25
+ ]),
26
+ ];
27
+ //# sourceMappingURL=article-extras-commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"article-extras-commands.js","sourceRoot":"","sources":["../../src/article-extras-commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC3G,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,OAAO,CAAC,YAAY,EAAE,yDAAyD,EAAE;QAC/E,SAAS,CACP,gBAAgB,EAChB,gEAAgE,EAChE,sBAAsB,CACvB;QACD,aAAa,CACX,4BAA4B,EAC5B,2DAA2D,EAC3D,oBAAoB,CACrB;QACD,aAAa,CACX,kBAAkB,EAClB,yDAAyD,EACzD,KAAK,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,CAC/C,uBAAuB,CACrB,UAAU,EACV,IAAI,CAAC,OAAO,EACZ,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EACpB,WAAW,CAAC,OAAO,CAAC,CACrB,EACH;YACI,GAAG,iBAAiB;YACpB;gBACE,KAAK,EAAE,eAAe;gBACtB,WAAW,EAAE,4DAA4D;gBACzE,QAAQ,EAAE,IAAI;gBACd,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,WAAW,CAAC;aACnD;SACF,CACJ;KACF,CAAC;IACF,OAAO,CAAC,OAAO,EAAE,8DAA8D,EAAE;QAC/E,SAAS,CAAC,gBAAgB,EAAE,2CAA2C,EAAE,iBAAiB,CAAC;QAC3F,aAAa,CACX,uBAAuB,EACvB,gDAAgD,EAChD,eAAe,CAChB;KACF,CAAC;IACF,OAAO,CAAC,QAAQ,EAAE,6BAA6B,EAAE;QAC/C,aAAa,CACX,eAAe,EACf,gEAAgE,EAChE,gBAAgB,CACjB;KACF,CAAC;CACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type Connection, type PageOptions, type ProjectionOptions, type YouTrackObject } from "./client.js";
2
+ export declare const listArticleAttachments: (connection: Connection, articleID: string, options?: PageOptions | undefined) => Promise<YouTrackObject[]>;
3
+ export declare const getArticleAttachment: (connection: Connection, articleID: string, attachmentID: string, options?: ProjectionOptions | undefined) => Promise<YouTrackObject>;
4
+ export declare function uploadArticleAttachment(connection: Connection, articleID: string, filePath: string, options?: ProjectionOptions): Promise<YouTrackObject[] | null>;
5
+ export declare const listChildArticles: (connection: Connection, articleID: string, options?: PageOptions | undefined) => Promise<YouTrackObject[]>;
6
+ export declare const getChildArticle: (connection: Connection, articleID: string, childID: string, options?: ProjectionOptions | undefined) => Promise<YouTrackObject>;
7
+ export declare function getParentArticle(connection: Connection, articleID: string, options?: ProjectionOptions): Promise<YouTrackObject | null>;
8
+ //# sourceMappingURL=article-extras.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"article-extras.d.ts","sourceRoot":"","sources":["../../src/article-extras.ts"],"names":[],"mappings":"AACA,OAAO,EAOL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAUrB,eAAO,MAAM,sBAAsB,6GAGlC,CAAC;AACF,eAAO,MAAM,oBAAoB,uIAIhC,CAAC;AAEF,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,cAAc,EAAE,GAAG,IAAI,CAAC,CAKlC;AAED,eAAO,MAAM,iBAAiB,6GAG7B,CAAC;AACF,eAAO,MAAM,eAAe,kIAI3B,CAAC;AAEF,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAMhC"}
@@ -0,0 +1,22 @@
1
+ import { attachmentForm } from "./attachment-form.js";
2
+ import { encodedID, fields, readCollectionAt, readNullableObject, readObjectAt, uploadObjectCollection, } from "./client.js";
3
+ const attachmentFields = "id,name,size,mimeType";
4
+ const articleListFields = "id,idReadable,summary,project(id,shortName),updated";
5
+ const articleDetailFields = `${articleListFields},content,parentArticle(id,idReadable),created`;
6
+ function articlePath(articleID) {
7
+ return `api/articles/${encodedID(articleID, "article ID")}`;
8
+ }
9
+ export const listArticleAttachments = readCollectionAt((articleID) => `${articlePath(articleID)}/attachments`, attachmentFields);
10
+ export const getArticleAttachment = readObjectAt((articleID, attachmentID) => `${articlePath(articleID)}/attachments/${encodedID(attachmentID, "attachment ID")}`, attachmentFields);
11
+ export async function uploadArticleAttachment(connection, articleID, filePath, options = {}) {
12
+ const path = `${articlePath(articleID)}/attachments`;
13
+ const projection = fields(options, attachmentFields);
14
+ const form = await attachmentForm(filePath);
15
+ return uploadObjectCollection(connection, path, form, projection);
16
+ }
17
+ export const listChildArticles = readCollectionAt((articleID) => `${articlePath(articleID)}/childArticles`, articleListFields);
18
+ export const getChildArticle = readObjectAt((articleID, childID) => `${articlePath(articleID)}/childArticles/${encodedID(childID, "child article ID")}`, articleDetailFields);
19
+ export async function getParentArticle(connection, articleID, options = {}) {
20
+ return readNullableObject(connection, `${articlePath(articleID)}/parentArticle`, { fields: fields(options, articleDetailFields) });
21
+ }
22
+ //# sourceMappingURL=article-extras.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"article-extras.js","sourceRoot":"","sources":["../../src/article-extras.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EACL,SAAS,EACT,MAAM,EACN,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,GAKvB,MAAM,aAAa,CAAC;AAErB,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AACjD,MAAM,iBAAiB,GAAG,qDAAqD,CAAC;AAChF,MAAM,mBAAmB,GAAG,GAAG,iBAAiB,+CAA+C,CAAC;AAEhG,SAAS,WAAW,CAAC,SAAiB;IACpC,OAAO,gBAAgB,SAAS,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,CAAC;AAC9D,CAAC;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,gBAAgB,CACpD,CAAC,SAAiB,EAAE,EAAE,CAAC,GAAG,WAAW,CAAC,SAAS,CAAC,cAAc,EAC9D,gBAAgB,CACjB,CAAC;AACF,MAAM,CAAC,MAAM,oBAAoB,GAAG,YAAY,CAC9C,CAAC,SAAiB,EAAE,YAAoB,EAAE,EAAE,CAC1C,GAAG,WAAW,CAAC,SAAS,CAAC,gBAAgB,SAAS,CAAC,YAAY,EAAE,eAAe,CAAC,EAAE,EACrF,gBAAgB,CACjB,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,UAAsB,EACtB,SAAiB,EACjB,QAAgB,EAChB,OAAO,GAAsB,EAAE;IAE/B,MAAM,IAAI,GAAG,GAAG,WAAW,CAAC,SAAS,CAAC,cAAc,CAAC;IACrD,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;IACrD,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC5C,OAAO,sBAAsB,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG,gBAAgB,CAC/C,CAAC,SAAiB,EAAE,EAAE,CAAC,GAAG,WAAW,CAAC,SAAS,CAAC,gBAAgB,EAChE,iBAAiB,CAClB,CAAC;AACF,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CACzC,CAAC,SAAiB,EAAE,OAAe,EAAE,EAAE,CACrC,GAAG,WAAW,CAAC,SAAS,CAAC,kBAAkB,SAAS,CAAC,OAAO,EAAE,kBAAkB,CAAC,EAAE,EACrF,mBAAmB,CACpB,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,UAAsB,EACtB,SAAiB,EACjB,OAAO,GAAsB,EAAE;IAE/B,OAAO,kBAAkB,CACvB,UAAU,EACV,GAAG,WAAW,CAAC,SAAS,CAAC,gBAAgB,EACzC,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,mBAAmB,CAAC,EAAE,CACjD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare const articlesRootCommands: import("@eyeauras/cli-factory").CommandDefinition[];
2
+ export declare const articlesProjectChildren: import("@eyeauras/cli-factory").CommandDefinition[];
3
+ //# sourceMappingURL=articles-commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"articles-commands.d.ts","sourceRoot":"","sources":["../../src/articles-commands.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,oBAAoB,qDA8BhC,CAAC;AAEF,eAAO,MAAM,uBAAuB,qDAInC,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { command } from "@eyeauras/cli-factory";
2
+ import { articlesExtraChildren } from "./article-extras-commands.js";
3
+ import { pagedRead, projectedBodyUpdate, projectedRead } from "./cli-support.js";
4
+ import { addArticleComment, createArticle, getArticle, getArticleComment, listArticleComments, listArticles, listProjectArticles, updateArticle, updateArticleComment, } from "./articles.js";
5
+ export const articlesRootCommands = [
6
+ command("article", "Read and write knowledge-base articles", [
7
+ ...articlesExtraChildren,
8
+ pagedRead("list", "List one page of accessible articles", listArticles),
9
+ projectedRead("get <article>", "Read article content and parent identity", getArticle),
10
+ projectedBodyUpdate("create", "Create an article with project.id, summary and optional content", createArticle),
11
+ projectedBodyUpdate("update <article>", "Update summary/content; null content clears it and omitted fields stay unchanged", updateArticle),
12
+ command("comment", "Read and write article comments", [
13
+ pagedRead("list <article>", "List one page of comments", listArticleComments),
14
+ projectedRead("get <article> <comment>", "Read an article comment", getArticleComment),
15
+ projectedBodyUpdate("add <article>", "Add an article comment with nonempty text", addArticleComment),
16
+ projectedBodyUpdate("update <article> <comment>", "Replace an article comment's text", updateArticleComment),
17
+ ]),
18
+ ]),
19
+ ];
20
+ export const articlesProjectChildren = [
21
+ command("article", "Inspect project knowledge-base articles", [
22
+ pagedRead("list <project>", "List one page of articles in a project", listProjectArticles),
23
+ ]),
24
+ ];
25
+ //# sourceMappingURL=articles-commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"articles-commands.js","sourceRoot":"","sources":["../../src/articles-commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,oBAAoB,GACrB,MAAM,eAAe,CAAC;AAEvB,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,OAAO,CAAC,SAAS,EAAE,wCAAwC,EAAE;QAC3D,GAAG,qBAAqB;QACxB,SAAS,CAAC,MAAM,EAAE,sCAAsC,EAAE,YAAY,CAAC;QACvE,aAAa,CAAC,eAAe,EAAE,0CAA0C,EAAE,UAAU,CAAC;QACtF,mBAAmB,CACjB,QAAQ,EACR,iEAAiE,EACjE,aAAa,CACd;QACD,mBAAmB,CACjB,kBAAkB,EAClB,kFAAkF,EAClF,aAAa,CACd;QACD,OAAO,CAAC,SAAS,EAAE,iCAAiC,EAAE;YACpD,SAAS,CAAC,gBAAgB,EAAE,2BAA2B,EAAE,mBAAmB,CAAC;YAC7E,aAAa,CAAC,yBAAyB,EAAE,yBAAyB,EAAE,iBAAiB,CAAC;YACtF,mBAAmB,CACjB,eAAe,EACf,2CAA2C,EAC3C,iBAAiB,CAClB;YACD,mBAAmB,CACjB,4BAA4B,EAC5B,mCAAmC,EACnC,oBAAoB,CACrB;SACF,CAAC;KACH,CAAC;CACH,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,OAAO,CAAC,SAAS,EAAE,yCAAyC,EAAE;QAC5D,SAAS,CAAC,gBAAgB,EAAE,wCAAwC,EAAE,mBAAmB,CAAC;KAC3F,CAAC;CACH,CAAC"}