kd-cli 2.1.4__tar.gz → 2.1.5__tar.gz

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 (74) hide show
  1. {kd_cli-2.1.4/kd_cli.egg-info → kd_cli-2.1.5}/PKG-INFO +1 -1
  2. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/core/command_base.py +0 -2
  3. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/core/option_registry.py +1 -1
  4. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/main.py +1 -8
  5. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/namespaces/primer/__init__.py +50 -9
  6. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/output.py +23 -6
  7. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/resources/CHANGELOG.md +10 -0
  8. {kd_cli-2.1.4 → kd_cli-2.1.5/kd_cli.egg-info}/PKG-INFO +1 -1
  9. {kd_cli-2.1.4 → kd_cli-2.1.5}/pyproject.toml +1 -1
  10. {kd_cli-2.1.4 → kd_cli-2.1.5}/LICENSE +0 -0
  11. {kd_cli-2.1.4 → kd_cli-2.1.5}/MANIFEST.in +0 -0
  12. {kd_cli-2.1.4 → kd_cli-2.1.5}/README.md +0 -0
  13. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/__init__.py +0 -0
  14. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/api/__init__.py +0 -0
  15. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/api/articles.py +0 -0
  16. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/api/http.py +0 -0
  17. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/api/hub.py +0 -0
  18. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/api/saved_queries.py +0 -0
  19. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/api/tags.py +0 -0
  20. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/api/youtrack.py +0 -0
  21. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/article_marker.py +0 -0
  22. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/auth.py +0 -0
  23. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/__init__.py +0 -0
  24. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/core/__init__.py +0 -0
  25. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/core/command_parser.py +0 -0
  26. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/core/namespaces.py +0 -0
  27. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/input.py +0 -0
  28. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/namespaces/__init__.py +0 -0
  29. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/namespaces/alias/__init__.py +0 -0
  30. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/namespaces/article/__init__.py +0 -0
  31. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/namespaces/init.py +0 -0
  32. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/namespaces/issue/__init__.py +0 -0
  33. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/namespaces/project/__init__.py +0 -0
  34. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/namespaces/query/__init__.py +0 -0
  35. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/namespaces/release_notes.py +0 -0
  36. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/namespaces/search/__init__.py +0 -0
  37. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/namespaces/system/__init__.py +0 -0
  38. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/namespaces/tag/__init__.py +0 -0
  39. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/namespaces/time/__init__.py +0 -0
  40. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/namespaces/user/__init__.py +0 -0
  41. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/cli/tree.py +0 -0
  42. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/client.py +0 -0
  43. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/config.py +0 -0
  44. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/config_schema.py +0 -0
  45. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/connection.py +0 -0
  46. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/debug.py +0 -0
  47. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/exceptions.py +0 -0
  48. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/history.py +0 -0
  49. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/models/__init__.py +0 -0
  50. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/models/article.py +0 -0
  51. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/models/assist.py +0 -0
  52. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/models/attachment.py +0 -0
  53. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/models/comment.py +0 -0
  54. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/models/common.py +0 -0
  55. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/models/issue.py +0 -0
  56. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/models/project.py +0 -0
  57. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/models/saved_query.py +0 -0
  58. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/models/search.py +0 -0
  59. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/models/tag.py +0 -0
  60. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/models/user.py +0 -0
  61. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/models/work_item.py +0 -0
  62. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/primer.py +0 -0
  63. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/query_parser.py +0 -0
  64. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/release_notes.py +0 -0
  65. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/resources/__init__.py +0 -0
  66. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/resources/command-synonyms.yaml +0 -0
  67. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/resources/config-template.yaml +0 -0
  68. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd/resources/error-messages.yaml +0 -0
  69. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd_cli.egg-info/SOURCES.txt +0 -0
  70. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd_cli.egg-info/dependency_links.txt +0 -0
  71. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd_cli.egg-info/entry_points.txt +0 -0
  72. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd_cli.egg-info/requires.txt +0 -0
  73. {kd_cli-2.1.4 → kd_cli-2.1.5}/kd_cli.egg-info/top_level.txt +0 -0
  74. {kd_cli-2.1.4 → kd_cli-2.1.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kd-cli
3
- Version: 2.1.4
3
+ Version: 2.1.5
4
4
  Summary: Knowledge Dispatcher for issue trackers and knowledge bases
5
5
  Author: Sergiy Gladkyy
6
6
  License-Expression: MIT
@@ -114,8 +114,6 @@ class Command(ABC):
114
114
  if opt.aliases:
115
115
  alias_str = ", ".join(f"--{a}" for a in opt.aliases)
116
116
  flag += f" (alias: {alias_str})"
117
- if opt.choices:
118
- flag += f" ({', '.join(opt.choices)})"
119
117
  if opt.help_text:
120
118
  flag += f" {opt.help_text}"
121
119
  lines.append(flag)
@@ -253,7 +253,7 @@ OUTPUT_OPTIONS: list[OptionDeclaration] = [
253
253
  layer=OptionLayer.OUTPUT,
254
254
  type=str,
255
255
  choices=["table", "json", "yaml", "md"],
256
- help_text="Output format (table, json, yaml, md)",
256
+ help_text="Output format: table|json|yaml|md (default: table)",
257
257
  ),
258
258
  OptionDeclaration(
259
259
  name="fields",
@@ -503,8 +503,6 @@ def _show_reference(
503
503
  flag += f"/-{decl.short_name}"
504
504
  if decl.type is not bool:
505
505
  flag += f" {decl.name.upper()}"
506
- if decl.choices:
507
- flag += f" ({'/'.join(decl.choices)})"
508
506
  parts.append(flag)
509
507
  print(f"Global options: {', '.join(parts)}")
510
508
  print()
@@ -539,8 +537,6 @@ def _print_namespace_reference(ns: Namespace, depth: int = 0) -> None:
539
537
  flag = f"--{opt.name}"
540
538
  if opt.short_name:
541
539
  flag += f"/-{opt.short_name}"
542
- if opt.choices:
543
- flag += f" ({', '.join(opt.choices)})"
544
540
  line = f" {flag}"
545
541
  if opt.help_text:
546
542
  line += f" {opt.help_text}"
@@ -621,8 +617,5 @@ def _show_discovery(namespace: Namespace, *, file: Any = None) -> None:
621
617
  flag = f"--{decl.name}"
622
618
  if decl.short_name:
623
619
  flag += f", -{decl.short_name}"
624
- extra = ""
625
- if decl.choices:
626
- extra = f" ({', '.join(decl.choices)})"
627
- print(f" {flag:<16}{decl.help_text}{extra}", file=file)
620
+ print(f" {flag:<16}{decl.help_text}", file=file)
628
621
  print(file=file)
@@ -10,7 +10,12 @@ from typing import Any
10
10
 
11
11
  from kd.cli.core.command_base import Command, CommandResult
12
12
  from kd.cli.core.namespaces import Namespace
13
- from kd.cli.core.option_registry import OptionDeclaration, OptionLayer, ResolvedOptions
13
+ from kd.cli.core.option_registry import (
14
+ OptionDeclaration,
15
+ OptionLayer,
16
+ ResolvedOptions,
17
+ project_option,
18
+ )
14
19
  from kd.cli.input import resolve_text_input
15
20
  from kd.exceptions import ValidationError
16
21
  from kd.primer import (
@@ -25,6 +30,26 @@ from kd.primer import (
25
30
  )
26
31
 
27
32
 
33
+ def _assert_workspace_project(command: Command, options: ResolvedOptions) -> None:
34
+ """Enforce the ``--project`` assertion on workspace-scoped primer commands.
35
+
36
+ The primer lives in ``.kd/primer.yaml`` and has no project dimension.
37
+ ``--project`` is accepted as an assertion of which workspace's primer the
38
+ caller thinks it's editing: matches ``default_project`` → silent no-op;
39
+ mismatches → raise so an agent running from the wrong workspace fails loud.
40
+ """
41
+ asserted = options.command.get("project")
42
+ if not asserted:
43
+ return
44
+ current = command.config.default_project
45
+ if current and asserted != current:
46
+ raise ValidationError(
47
+ f"Primer is workspace-scoped. --project={asserted} does not match "
48
+ f"this workspace's default_project={current}. To edit {asserted}'s "
49
+ f"primer, run from that workspace."
50
+ )
51
+
52
+
28
53
  # ---------------------------------------------------------------------------
29
54
  # /primer show (root default)
30
55
  # ---------------------------------------------------------------------------
@@ -46,19 +71,16 @@ class PrimerShowCommand(Command):
46
71
  ],
47
72
  see_also=["/primer/note list", "/primer/article list"],
48
73
  )
74
+ self.display_hint = "primer_show"
49
75
 
50
76
  def execute(
51
77
  self, args: list[str], options: ResolvedOptions, client: Any = None
52
78
  ) -> CommandResult:
53
79
  data = load_primer()
54
- if not data.notes and not data.articles:
55
- return None
56
- result: dict[str, Any] = {}
57
- if data.notes:
58
- result["notes"] = [{"#": i + 1, "note": n} for i, n in enumerate(data.notes)]
59
- if data.articles:
60
- result["articles"] = [{"id": a.id, "summary": a.summary or ""} for a in data.articles]
61
- return result
80
+ return {
81
+ "notes": [{"#": i + 1, "note": n} for i, n in enumerate(data.notes)],
82
+ "articles": [{"id": a.id, "summary": a.summary or ""} for a in data.articles],
83
+ }
62
84
 
63
85
 
64
86
  # ---------------------------------------------------------------------------
@@ -93,6 +115,7 @@ class PrimerNoteAddCommand(Command):
93
115
  super().__init__(
94
116
  "add",
95
117
  "Add a primer note",
118
+ requires_config=True,
96
119
  usage="kd /primer/note add TEXT",
97
120
  long_description="Appends a note to the project primer. TEXT can be inline, "
98
121
  "@file to read from a file, or - to read from stdin.",
@@ -103,9 +126,13 @@ class PrimerNoteAddCommand(Command):
103
126
  see_also=["/primer/note list", "/primer/note remove"],
104
127
  )
105
128
 
129
+ def _register_options(self) -> None:
130
+ self.register_option(project_option(help_text="Assert which project's primer this edits"))
131
+
106
132
  def execute(
107
133
  self, args: list[str], options: ResolvedOptions, client: Any = None
108
134
  ) -> CommandResult:
135
+ _assert_workspace_project(self, options)
109
136
  if not args:
110
137
  raise ValidationError("Usage: kd /primer/note add TEXT")
111
138
  text = resolve_text_input(" ".join(args))
@@ -122,6 +149,7 @@ class PrimerNoteRemoveCommand(Command):
122
149
  super().__init__(
123
150
  "remove",
124
151
  "Remove a primer note by index or all notes",
152
+ requires_config=True,
125
153
  usage="kd /primer/note remove INDEX | --all",
126
154
  long_description="Removes the note at the given 1-based index, or all notes "
127
155
  "if --all is passed. Use '/primer/note list' to see current indices.",
@@ -142,10 +170,12 @@ class PrimerNoteRemoveCommand(Command):
142
170
  help_text="Remove all notes",
143
171
  )
144
172
  )
173
+ self.register_option(project_option(help_text="Assert which project's primer this edits"))
145
174
 
146
175
  def execute(
147
176
  self, args: list[str], options: ResolvedOptions, client: Any = None
148
177
  ) -> CommandResult:
178
+ _assert_workspace_project(self, options)
149
179
  if options.command.get("all"):
150
180
  count = clear_notes()
151
181
  return {"ok": True, "action": "clear", "removed": count}
@@ -166,6 +196,7 @@ class PrimerNoteUpdateCommand(Command):
166
196
  super().__init__(
167
197
  "update",
168
198
  "Replace a primer note by index",
199
+ requires_config=True,
169
200
  usage="kd /primer/note update INDEX TEXT",
170
201
  long_description="Replaces the note at the given 1-based index with new text. "
171
202
  "TEXT can be inline, @file to read from a file, or - to read from stdin. "
@@ -177,9 +208,13 @@ class PrimerNoteUpdateCommand(Command):
177
208
  see_also=["/primer/note list", "/primer/note remove"],
178
209
  )
179
210
 
211
+ def _register_options(self) -> None:
212
+ self.register_option(project_option(help_text="Assert which project's primer this edits"))
213
+
180
214
  def execute(
181
215
  self, args: list[str], options: ResolvedOptions, client: Any = None
182
216
  ) -> CommandResult:
217
+ _assert_workspace_project(self, options)
183
218
  if not args:
184
219
  raise ValidationError("Usage: kd /primer/note update INDEX TEXT")
185
220
  try:
@@ -227,6 +262,7 @@ class PrimerArticleAddCommand(Command):
227
262
  super().__init__(
228
263
  "add",
229
264
  "Pin an article to the primer",
265
+ requires_config=True,
230
266
  usage="kd /primer/article add ARTICLE_ID [--summary S]",
231
267
  long_description="Adds a YouTrack article reference to the project primer. "
232
268
  "Use --summary to store a description alongside the ID.",
@@ -246,10 +282,12 @@ class PrimerArticleAddCommand(Command):
246
282
  help_text="Description to store with the article reference",
247
283
  )
248
284
  )
285
+ self.register_option(project_option(help_text="Assert which project's primer this edits"))
249
286
 
250
287
  def execute(
251
288
  self, args: list[str], options: ResolvedOptions, client: Any = None
252
289
  ) -> CommandResult:
290
+ _assert_workspace_project(self, options)
253
291
  if not args:
254
292
  raise ValidationError("Usage: kd /primer/article add ARTICLE_ID")
255
293
  article_id = args[0]
@@ -268,6 +306,7 @@ class PrimerArticleRemoveCommand(Command):
268
306
  super().__init__(
269
307
  "remove",
270
308
  "Unpin an article or all articles",
309
+ requires_config=True,
271
310
  usage="kd /primer/article remove ARTICLE_ID | --all",
272
311
  long_description="Removes the article with the given ID from the primer, "
273
312
  "or all pinned articles if --all is passed.",
@@ -288,10 +327,12 @@ class PrimerArticleRemoveCommand(Command):
288
327
  help_text="Remove all pinned articles",
289
328
  )
290
329
  )
330
+ self.register_option(project_option(help_text="Assert which project's primer this edits"))
291
331
 
292
332
  def execute(
293
333
  self, args: list[str], options: ResolvedOptions, client: Any = None
294
334
  ) -> CommandResult:
335
+ _assert_workspace_project(self, options)
295
336
  if options.command.get("all"):
296
337
  count = clear_articles()
297
338
  return {"ok": True, "action": "clear", "removed": count}
@@ -1,13 +1,14 @@
1
1
  """Output formatting.
2
2
 
3
3
  Renders command results in multiple formats:
4
- - table: human-readable tabular output (default for TTY)
4
+ - table: human-readable tabular output (unconditional default)
5
5
  - json: JSON output for piping to jq
6
6
  - yaml: YAML output
7
7
  - md: Markdown table
8
8
 
9
- Auto-detects TTY vs pipe: uses table format for interactive terminals,
10
- falls back to json when piped.
9
+ Default is ``table`` regardless of stdout context; TTY state does not
10
+ influence format (ADR-006 — TTY detection stays config-first). Consumers
11
+ that want JSON or YAML pass ``-o json`` / ``-o yaml`` explicitly.
11
12
 
12
13
  Value normalization runs before serialization: epoch-ms timestamps become
13
14
  ISO 8601, enums become their ``.value``, and nested collections render
@@ -17,7 +18,6 @@ cleanly in every format.
17
18
  from __future__ import annotations
18
19
 
19
20
  import json
20
- import sys
21
21
  from datetime import datetime, timezone
22
22
  from typing import Any
23
23
 
@@ -75,7 +75,8 @@ class OutputFormatter:
75
75
 
76
76
  Args:
77
77
  data: Structured data from a command.
78
- fmt: Output format. Auto-detected if None (TTY->table, pipe->json).
78
+ fmt: Output format. Defaults to ``"table"`` when None; TTY state
79
+ does not influence the default (ADR-006).
79
80
  fields: Comma-separated field names to include.
80
81
  display_hint: Rendering hint from the command (e.g. ``"tree"``).
81
82
  """
@@ -87,12 +88,28 @@ class OutputFormatter:
87
88
  normalized: list[dict[str, Any]] | dict[str, Any] = self._normalize(data)
88
89
 
89
90
  if fmt is None:
90
- fmt = "table" if sys.stdout.isatty() else "json"
91
+ fmt = "table"
91
92
 
92
93
  # Tree display: table/md render as pretty tree, json/yaml serialize structure.
93
94
  if display_hint == "tree" and isinstance(normalized, dict) and fmt in ("table", "md"):
94
95
  return self._render_tree_display(normalized)
95
96
 
97
+ # Primer show: table/md formats drop empty sections so a notes-only
98
+ # primer renders without an empty "articles" row. When both sections
99
+ # are empty, show a populate hint. json/yaml pass through unchanged.
100
+ if (
101
+ display_hint == "primer_show"
102
+ and fmt in ("table", "md")
103
+ and isinstance(normalized, dict)
104
+ ):
105
+ if not normalized.get("notes") and not normalized.get("articles"):
106
+ return (
107
+ "Primer is empty.\n"
108
+ ' Populate with: kd /primer/note add "<text>"\n'
109
+ " kd /primer/article add <ARTICLE_ID>"
110
+ )
111
+ normalized = {k: v for k, v in normalized.items() if v}
112
+
96
113
  field_list = [f.strip() for f in fields.split(",")] if fields else None
97
114
 
98
115
  match fmt:
@@ -4,6 +4,16 @@ All notable changes to kd-cli are recorded here. The project follows [Semantic V
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.1.5] — 2026-04-17
8
+
9
+ Milestone 005 — CLI friction audit, session S1. Four first-time-user surface fixes on the `--help` / `--output` axis.
10
+
11
+ ### Fixed
12
+ - `--help`, `--reference`, and per-command help now render the `--output` choices list exactly once. The literal help text carries the list and the default (`Output format: table|json|yaml|md (default: table)`); four renderer sites that previously auto-appended `(table, json, yaml, md)` a second time stopped doing so (KDCLI-73).
13
+ - `kd` no longer flips the default output format to JSON when stdout is a pipe. `table` is the unconditional default across TTY, pipes, and subprocess contexts, per [ADR-006](../../docs/design/ADR-006-tty-detection-config-first.md). Consumers wanting JSON or YAML pass `-o json` / `-o yaml` explicitly (KDCLI-74).
14
+ - `kd /primer show` on an empty primer now prints a populate hint in `table` and `md` output, instead of rendering zero bytes. JSON and YAML continue to return the canonical empty container `{"notes": [], "articles": []}` (KDCLI-76).
15
+ - `kd /primer/note add`, `kd /primer/article add`, and their sibling `remove` / `update` commands accept `--project/-p` as a workspace assertion. A match against the active connection's `default_project` is a silent no-op; a mismatch raises a `ValidationError` naming both the asserted and resolved keys so an agent running from the wrong workspace fails loud instead of silently writing to the wrong primer (KDCLI-70).
16
+
7
17
  ## [2.1.4] — 2026-04-15
8
18
 
9
19
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kd-cli
3
- Version: 2.1.4
3
+ Version: 2.1.5
4
4
  Summary: Knowledge Dispatcher for issue trackers and knowledge bases
5
5
  Author: Sergiy Gladkyy
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "kd-cli"
7
- version = "2.1.4"
7
+ version = "2.1.5"
8
8
  description = "Knowledge Dispatcher for issue trackers and knowledge bases"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes