rpr-cli 0.1.1__py3-none-any.whl
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.
- rpr/__init__.py +1 -0
- rpr/agent/__init__.py +29 -0
- rpr/agent/approval.py +19 -0
- rpr/agent/bootstrap.py +60 -0
- rpr/agent/client.py +115 -0
- rpr/agent/control.py +16 -0
- rpr/agent/mock.py +158 -0
- rpr/agent/runtime.py +291 -0
- rpr/agent/session.py +91 -0
- rpr/agent/tools/__init__.py +10 -0
- rpr/agent/tools/base.py +62 -0
- rpr/agent/tools/mutating.py +47 -0
- rpr/agent/tools/readonly.py +147 -0
- rpr/agent/tools/registry.py +30 -0
- rpr/application/__init__.py +1 -0
- rpr/application/catalog.py +427 -0
- rpr/application/chat_service.py +413 -0
- rpr/application/checks.py +49 -0
- rpr/application/cli_adapter.py +36 -0
- rpr/application/completer.py +163 -0
- rpr/application/conversation_service.py +92 -0
- rpr/application/prompt_service.py +85 -0
- rpr/application/selector.py +240 -0
- rpr/application/shell.py +543 -0
- rpr/checks/__init__.py +0 -0
- rpr/checks/base.py +13 -0
- rpr/checks/instructions.py +89 -0
- rpr/checks/packages.py +619 -0
- rpr/checks/workspace.py +170 -0
- rpr/cli.py +78 -0
- rpr/commands/__init__.py +0 -0
- rpr/commands/add.py +166 -0
- rpr/commands/chat.py +48 -0
- rpr/commands/check.py +53 -0
- rpr/commands/generate/__init__.py +0 -0
- rpr/commands/generate/api.py +228 -0
- rpr/commands/generate/domain.py +383 -0
- rpr/commands/generate/engine.py +148 -0
- rpr/commands/generate/storybook.py +442 -0
- rpr/commands/generate/ui.py +414 -0
- rpr/commands/init.py +822 -0
- rpr/commands/map.py +113 -0
- rpr/commands/settings.py +102 -0
- rpr/commands/sync.py +97 -0
- rpr/context.py +203 -0
- rpr/generators/__init__.py +0 -0
- rpr/generators/base.py +110 -0
- rpr/generators/claude.py +33 -0
- rpr/generators/copilot.py +36 -0
- rpr/generators/cursor.py +38 -0
- rpr/generators/gemini.py +33 -0
- rpr/map/__init__.py +0 -0
- rpr/map/architecture.py +495 -0
- rpr/map/chains.py +317 -0
- rpr/map/classifier.py +170 -0
- rpr/map/coverage.py +200 -0
- rpr/map/dependencies.py +243 -0
- rpr/map/extractor.py +223 -0
- rpr/map/graph.py +318 -0
- rpr/map/output.py +1030 -0
- rpr/map/responsibility.py +345 -0
- rpr/map/topology.py +327 -0
- rpr/map/walker.py +151 -0
- rpr/scaffolds/domain/base_entity.md +30 -0
- rpr/scaffolds/domain/base_repo.md +48 -0
- rpr/scaffolds/domain/container.md +76 -0
- rpr/scaffolds/domain/settings.md +57 -0
- rpr/scaffolds/instructions/all.instructions.md +50 -0
- rpr/scaffolds/instructions/api.instructions.md +42 -0
- rpr/scaffolds/instructions/domain.instructions.md +93 -0
- rpr/scaffolds/instructions/frontend.instructions.md +97 -0
- rpr/scaffolds/instructions/rust-engine.instructions.md +40 -0
- rpr/scaffolds/instructions/setup-guide.instructions.md +86 -0
- rpr/scaffolds/instructions/tooling-setup.instructions.md +97 -0
- rpr/scaffolds/instructions/tooling.instructions.md +42 -0
- rpr/scaffolds/js_special_files/fetch.service.md +222 -0
- rpr/scaffolds/js_special_files/sticky-navigation.md +164 -0
- rpr/scaffolds/special_files/domain_container.md +76 -0
- rpr/scaffolds/special_files/domain_settings.md +57 -0
- rpr/scaffolds/special_files/dto_util.md +62 -0
- rpr/scaffolds/special_files/encrypted_column.md +98 -0
- rpr/scaffolds/special_files/mapper_util.md +159 -0
- rpr/scaffolds/special_files/partial_update.md +61 -0
- rpr/templates/__init__.py +0 -0
- rpr/templates/registry.py +81 -0
- rpr/ui/__init__.py +0 -0
- rpr/ui/console.py +32 -0
- rpr/ui/markdown.py +59 -0
- rpr/ui/prompt_session.py +430 -0
- rpr/ui/renderers.py +167 -0
- rpr/ui/theme.py +286 -0
- rpr/workspace.py +131 -0
- rpr_cli-0.1.1.dist-info/METADATA +201 -0
- rpr_cli-0.1.1.dist-info/RECORD +97 -0
- rpr_cli-0.1.1.dist-info/WHEEL +4 -0
- rpr_cli-0.1.1.dist-info/entry_points.txt +2 -0
- rpr_cli-0.1.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(slots=True, frozen=True)
|
|
8
|
+
class CommandOption:
|
|
9
|
+
long_name: str = field(
|
|
10
|
+
metadata={
|
|
11
|
+
"description": "Long option name rendered and invoked as --<long-name>."
|
|
12
|
+
}
|
|
13
|
+
)
|
|
14
|
+
description: str = field(
|
|
15
|
+
metadata={"description": "Help text shown in discovery and help output."}
|
|
16
|
+
)
|
|
17
|
+
kind: Literal["flag", "value"] = field(
|
|
18
|
+
metadata={
|
|
19
|
+
"description": "Whether the option is a boolean flag or requires a value."
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
short_name: str | None = field(
|
|
23
|
+
default=None,
|
|
24
|
+
metadata={
|
|
25
|
+
"description": "Optional short alias rendered and invoked as -<short-name>."
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
value_name: str | None = field(
|
|
29
|
+
default=None,
|
|
30
|
+
metadata={
|
|
31
|
+
"description": "Placeholder shown when the option expects a value, e.g. MODEL or PATH."
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(slots=True, frozen=True)
|
|
37
|
+
class CommandArgument:
|
|
38
|
+
name: str = field(
|
|
39
|
+
metadata={"description": "Positional argument name used in usage text."}
|
|
40
|
+
)
|
|
41
|
+
description: str = field(
|
|
42
|
+
metadata={"description": "Meaning of the argument for help and picker UIs."}
|
|
43
|
+
)
|
|
44
|
+
required: bool = field(
|
|
45
|
+
default=True,
|
|
46
|
+
metadata={"description": "Whether the argument is required."},
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(slots=True, frozen=True)
|
|
51
|
+
class CommandDefinition:
|
|
52
|
+
name: str = field(
|
|
53
|
+
metadata={
|
|
54
|
+
"description": "Canonical command token shared by CLI and slash-command adapters."
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
description: str = field(
|
|
58
|
+
metadata={"description": "Short description shown in help and command pickers."}
|
|
59
|
+
)
|
|
60
|
+
action_id: str | None = field(
|
|
61
|
+
default=None,
|
|
62
|
+
metadata={
|
|
63
|
+
"description": "Stable application action identifier used for leaf-command dispatch."
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
aliases: tuple[str, ...] = field(
|
|
67
|
+
default_factory=tuple,
|
|
68
|
+
metadata={
|
|
69
|
+
"description": "Alternative tokens that resolve to the same command."
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
arguments: tuple[CommandArgument, ...] = field(
|
|
73
|
+
default_factory=tuple,
|
|
74
|
+
metadata={"description": "Positional arguments accepted by this command."},
|
|
75
|
+
)
|
|
76
|
+
options: tuple[CommandOption, ...] = field(
|
|
77
|
+
default_factory=tuple,
|
|
78
|
+
metadata={"description": "Supported flags and value options."},
|
|
79
|
+
)
|
|
80
|
+
children: tuple[CommandDefinition, ...] = field(
|
|
81
|
+
default_factory=tuple,
|
|
82
|
+
metadata={
|
|
83
|
+
"description": "Nested subcommands for grouped commands such as generate or sync."
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
chat_enabled: bool = field(
|
|
87
|
+
default=True,
|
|
88
|
+
metadata={
|
|
89
|
+
"description": "Whether the command is available as a slash command in the chat shell."
|
|
90
|
+
},
|
|
91
|
+
)
|
|
92
|
+
cli_enabled: bool = field(
|
|
93
|
+
default=True,
|
|
94
|
+
metadata={
|
|
95
|
+
"description": "Whether the command is available from the top-level CLI."
|
|
96
|
+
},
|
|
97
|
+
)
|
|
98
|
+
interactive: bool = field(
|
|
99
|
+
default=False,
|
|
100
|
+
metadata={
|
|
101
|
+
"description": "Whether the command opens a follow-up interactive flow."
|
|
102
|
+
},
|
|
103
|
+
)
|
|
104
|
+
default_root_command: bool = field(
|
|
105
|
+
default=False,
|
|
106
|
+
metadata={
|
|
107
|
+
"description": "Marks the command that runs when rpr is invoked without a subcommand."
|
|
108
|
+
},
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
COMMAND_CATALOG: tuple[CommandDefinition, ...] = (
|
|
113
|
+
CommandDefinition(
|
|
114
|
+
name="chat",
|
|
115
|
+
description="Start the primary interactive chat shell.",
|
|
116
|
+
action_id="chat.start",
|
|
117
|
+
interactive=True,
|
|
118
|
+
default_root_command=True,
|
|
119
|
+
options=(
|
|
120
|
+
CommandOption(
|
|
121
|
+
"--provider",
|
|
122
|
+
"Override the AI provider.",
|
|
123
|
+
kind="value",
|
|
124
|
+
value_name="PROVIDER",
|
|
125
|
+
),
|
|
126
|
+
CommandOption(
|
|
127
|
+
"--model", "Override the model name.", kind="value", value_name="MODEL"
|
|
128
|
+
),
|
|
129
|
+
CommandOption(
|
|
130
|
+
"--approval-mode",
|
|
131
|
+
"Override the default approval mode.",
|
|
132
|
+
kind="value",
|
|
133
|
+
value_name="MODE",
|
|
134
|
+
),
|
|
135
|
+
),
|
|
136
|
+
),
|
|
137
|
+
CommandDefinition(
|
|
138
|
+
name="init",
|
|
139
|
+
description="Bootstrap an NX workspace with UV workspace configuration.",
|
|
140
|
+
action_id="init.run",
|
|
141
|
+
arguments=(
|
|
142
|
+
CommandArgument(
|
|
143
|
+
"project-name", "Name of the workspace to create.", required=False
|
|
144
|
+
),
|
|
145
|
+
),
|
|
146
|
+
options=(
|
|
147
|
+
CommandOption(
|
|
148
|
+
"--python", "Python version to use.", kind="value", value_name="VERSION"
|
|
149
|
+
),
|
|
150
|
+
CommandOption("--dry-run", "Preview changes without writing.", kind="flag"),
|
|
151
|
+
CommandOption(
|
|
152
|
+
"--integrated", "Use integrated NX workspace style.", kind="flag"
|
|
153
|
+
),
|
|
154
|
+
),
|
|
155
|
+
),
|
|
156
|
+
CommandDefinition(
|
|
157
|
+
name="generate",
|
|
158
|
+
description="Run an existing scaffolding command from chat.",
|
|
159
|
+
action_id="generate.dispatch",
|
|
160
|
+
children=(
|
|
161
|
+
CommandDefinition(
|
|
162
|
+
name="domain",
|
|
163
|
+
description="Generate a domain package.",
|
|
164
|
+
action_id="generate.domain",
|
|
165
|
+
arguments=(CommandArgument("name", "Package name."),),
|
|
166
|
+
options=(
|
|
167
|
+
CommandOption(
|
|
168
|
+
"--dry-run", "Preview changes without writing.", kind="flag"
|
|
169
|
+
),
|
|
170
|
+
),
|
|
171
|
+
),
|
|
172
|
+
CommandDefinition(
|
|
173
|
+
name="api",
|
|
174
|
+
description="Generate an API app.",
|
|
175
|
+
action_id="generate.api",
|
|
176
|
+
arguments=(CommandArgument("name", "App name."),),
|
|
177
|
+
options=(
|
|
178
|
+
CommandOption(
|
|
179
|
+
"--dry-run", "Preview changes without writing.", kind="flag"
|
|
180
|
+
),
|
|
181
|
+
),
|
|
182
|
+
),
|
|
183
|
+
CommandDefinition(
|
|
184
|
+
name="ui",
|
|
185
|
+
description="Generate a UI package.",
|
|
186
|
+
action_id="generate.ui",
|
|
187
|
+
arguments=(CommandArgument("name", "Package name."),),
|
|
188
|
+
options=(
|
|
189
|
+
CommandOption(
|
|
190
|
+
"--dry-run", "Preview changes without writing.", kind="flag"
|
|
191
|
+
),
|
|
192
|
+
),
|
|
193
|
+
),
|
|
194
|
+
CommandDefinition(
|
|
195
|
+
name="engine",
|
|
196
|
+
description="Generate a Rust engine package.",
|
|
197
|
+
action_id="generate.engine",
|
|
198
|
+
arguments=(CommandArgument("name", "Package name."),),
|
|
199
|
+
options=(
|
|
200
|
+
CommandOption(
|
|
201
|
+
"--dry-run", "Preview changes without writing.", kind="flag"
|
|
202
|
+
),
|
|
203
|
+
),
|
|
204
|
+
),
|
|
205
|
+
CommandDefinition(
|
|
206
|
+
name="storybook",
|
|
207
|
+
description="Generate Storybook configuration for a UI package.",
|
|
208
|
+
action_id="generate.storybook",
|
|
209
|
+
options=(
|
|
210
|
+
CommandOption(
|
|
211
|
+
"--ui-package",
|
|
212
|
+
"Target UI package.",
|
|
213
|
+
kind="value",
|
|
214
|
+
value_name="PACKAGE",
|
|
215
|
+
),
|
|
216
|
+
CommandOption(
|
|
217
|
+
"--dry-run", "Preview changes without writing.", kind="flag"
|
|
218
|
+
),
|
|
219
|
+
),
|
|
220
|
+
),
|
|
221
|
+
),
|
|
222
|
+
),
|
|
223
|
+
CommandDefinition(
|
|
224
|
+
name="sync",
|
|
225
|
+
description="Sync AI instructions through the existing generators.",
|
|
226
|
+
action_id="sync.dispatch",
|
|
227
|
+
children=(
|
|
228
|
+
CommandDefinition(
|
|
229
|
+
name="rules",
|
|
230
|
+
description="Read instructions and generate AI tool config files.",
|
|
231
|
+
action_id="sync.rules",
|
|
232
|
+
options=(
|
|
233
|
+
CommandOption(
|
|
234
|
+
"--target",
|
|
235
|
+
"Limit sync to one generator.",
|
|
236
|
+
kind="value",
|
|
237
|
+
short_name="-t",
|
|
238
|
+
value_name="GENERATOR",
|
|
239
|
+
),
|
|
240
|
+
CommandOption(
|
|
241
|
+
"--dry-run", "Preview changes without writing.", kind="flag"
|
|
242
|
+
),
|
|
243
|
+
),
|
|
244
|
+
),
|
|
245
|
+
),
|
|
246
|
+
),
|
|
247
|
+
CommandDefinition(
|
|
248
|
+
name="add",
|
|
249
|
+
description="Write a registered template into the current project.",
|
|
250
|
+
action_id="add.dispatch",
|
|
251
|
+
children=(
|
|
252
|
+
CommandDefinition(
|
|
253
|
+
name="template",
|
|
254
|
+
description="Apply a registered code template to the project.",
|
|
255
|
+
action_id="add.template",
|
|
256
|
+
arguments=(
|
|
257
|
+
CommandArgument(
|
|
258
|
+
"template-id", "Template identifier to apply.", required=False
|
|
259
|
+
),
|
|
260
|
+
),
|
|
261
|
+
options=(
|
|
262
|
+
CommandOption(
|
|
263
|
+
"--list",
|
|
264
|
+
"List all available templates.",
|
|
265
|
+
kind="flag",
|
|
266
|
+
short_name="-l",
|
|
267
|
+
),
|
|
268
|
+
CommandOption(
|
|
269
|
+
"--name",
|
|
270
|
+
"App or package name for variable interpolation.",
|
|
271
|
+
kind="value",
|
|
272
|
+
short_name="-n",
|
|
273
|
+
value_name="NAME",
|
|
274
|
+
),
|
|
275
|
+
CommandOption(
|
|
276
|
+
"--force",
|
|
277
|
+
"Overwrite existing files.",
|
|
278
|
+
kind="flag",
|
|
279
|
+
short_name="-f",
|
|
280
|
+
),
|
|
281
|
+
CommandOption(
|
|
282
|
+
"--source",
|
|
283
|
+
"Path to the rules source directory.",
|
|
284
|
+
kind="value",
|
|
285
|
+
value_name="PATH",
|
|
286
|
+
),
|
|
287
|
+
CommandOption(
|
|
288
|
+
"--dry-run", "Preview changes without writing.", kind="flag"
|
|
289
|
+
),
|
|
290
|
+
),
|
|
291
|
+
),
|
|
292
|
+
),
|
|
293
|
+
),
|
|
294
|
+
CommandDefinition(
|
|
295
|
+
name="check",
|
|
296
|
+
description="Run workspace health checks through the shared application service.",
|
|
297
|
+
action_id="check.run",
|
|
298
|
+
options=(
|
|
299
|
+
CommandOption(
|
|
300
|
+
"--group",
|
|
301
|
+
"Limit checks to one group.",
|
|
302
|
+
kind="value",
|
|
303
|
+
value_name="GROUP",
|
|
304
|
+
),
|
|
305
|
+
),
|
|
306
|
+
),
|
|
307
|
+
CommandDefinition(
|
|
308
|
+
name="map",
|
|
309
|
+
description="Generate the current code-map output from chat.",
|
|
310
|
+
action_id="map.run",
|
|
311
|
+
options=(
|
|
312
|
+
CommandOption(
|
|
313
|
+
"--root", "Root directory to scan.", kind="value", value_name="PATH"
|
|
314
|
+
),
|
|
315
|
+
CommandOption(
|
|
316
|
+
"--output", "Output file path.", kind="value", value_name="PATH"
|
|
317
|
+
),
|
|
318
|
+
CommandOption("--dry-run", "Preview changes without writing.", kind="flag"),
|
|
319
|
+
),
|
|
320
|
+
),
|
|
321
|
+
CommandDefinition(
|
|
322
|
+
name="settings",
|
|
323
|
+
description="Inspect or mutate persisted shell settings.",
|
|
324
|
+
action_id="settings.dispatch",
|
|
325
|
+
children=(
|
|
326
|
+
CommandDefinition(
|
|
327
|
+
name="list",
|
|
328
|
+
description="List all settings.",
|
|
329
|
+
action_id="settings.list",
|
|
330
|
+
),
|
|
331
|
+
CommandDefinition(
|
|
332
|
+
name="get",
|
|
333
|
+
description="Read one setting value.",
|
|
334
|
+
action_id="settings.get",
|
|
335
|
+
arguments=(CommandArgument("key", "Setting key to inspect."),),
|
|
336
|
+
),
|
|
337
|
+
CommandDefinition(
|
|
338
|
+
name="set",
|
|
339
|
+
description="Update one setting value.",
|
|
340
|
+
action_id="settings.set",
|
|
341
|
+
arguments=(
|
|
342
|
+
CommandArgument("key", "Setting key to update."),
|
|
343
|
+
CommandArgument("value", "New value to persist."),
|
|
344
|
+
),
|
|
345
|
+
),
|
|
346
|
+
),
|
|
347
|
+
),
|
|
348
|
+
CommandDefinition(
|
|
349
|
+
name="help",
|
|
350
|
+
description="List slash commands and usage.",
|
|
351
|
+
action_id="help.show",
|
|
352
|
+
cli_enabled=False,
|
|
353
|
+
),
|
|
354
|
+
CommandDefinition(
|
|
355
|
+
name="exit",
|
|
356
|
+
description="End the interactive session.",
|
|
357
|
+
action_id="chat.exit",
|
|
358
|
+
cli_enabled=False,
|
|
359
|
+
),
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def find_command(
|
|
364
|
+
catalog: tuple[CommandDefinition, ...],
|
|
365
|
+
tokens: list[str],
|
|
366
|
+
) -> CommandDefinition | None:
|
|
367
|
+
"""Recursively find a command by token path.
|
|
368
|
+
|
|
369
|
+
Examples::
|
|
370
|
+
|
|
371
|
+
find_command(COMMAND_CATALOG, ["generate", "ui"]) # → CommandDefinition(name="ui")
|
|
372
|
+
find_command(COMMAND_CATALOG, ["init"]) # → CommandDefinition(name="init")
|
|
373
|
+
find_command(COMMAND_CATALOG, ["unknown"]) # → None
|
|
374
|
+
"""
|
|
375
|
+
if not tokens:
|
|
376
|
+
return None
|
|
377
|
+
head, *tail = tokens
|
|
378
|
+
for cmd in catalog:
|
|
379
|
+
if cmd.name == head or head in cmd.aliases:
|
|
380
|
+
if not tail:
|
|
381
|
+
return cmd
|
|
382
|
+
if cmd.children:
|
|
383
|
+
return find_command(cmd.children, tail)
|
|
384
|
+
return None
|
|
385
|
+
return None
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def chat_commands(
|
|
389
|
+
catalog: tuple[CommandDefinition, ...],
|
|
390
|
+
) -> tuple[CommandDefinition, ...]:
|
|
391
|
+
"""Return top-level commands available in the chat shell (chat_enabled=True)."""
|
|
392
|
+
return tuple(cmd for cmd in catalog if cmd.chat_enabled)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def cli_commands(
|
|
396
|
+
catalog: tuple[CommandDefinition, ...],
|
|
397
|
+
) -> tuple[CommandDefinition, ...]:
|
|
398
|
+
"""Return top-level commands available from the CLI (cli_enabled=True)."""
|
|
399
|
+
return tuple(cmd for cmd in catalog if cmd.cli_enabled)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def usage_string(cmd: CommandDefinition) -> str:
|
|
403
|
+
"""Generate a usage string from a CommandDefinition.
|
|
404
|
+
|
|
405
|
+
Group commands show pipe-separated child names::
|
|
406
|
+
|
|
407
|
+
/generate <domain|api|ui|engine|storybook>
|
|
408
|
+
|
|
409
|
+
Leaf commands show arguments and options::
|
|
410
|
+
|
|
411
|
+
/init [project-name] [--python VERSION] [--dry-run]
|
|
412
|
+
"""
|
|
413
|
+
if cmd.children:
|
|
414
|
+
child_names = "|".join(c.name for c in cmd.children)
|
|
415
|
+
return f"/{cmd.name} <{child_names}>"
|
|
416
|
+
|
|
417
|
+
parts: list[str] = [f"/{cmd.name}"]
|
|
418
|
+
for arg in cmd.arguments:
|
|
419
|
+
bracket = f"<{arg.name}>" if arg.required else f"[{arg.name}]"
|
|
420
|
+
parts.append(bracket)
|
|
421
|
+
for opt in cmd.options:
|
|
422
|
+
if opt.kind == "flag":
|
|
423
|
+
parts.append(f"[{opt.long_name}]")
|
|
424
|
+
else:
|
|
425
|
+
placeholder = opt.value_name or opt.long_name.lstrip("-").upper()
|
|
426
|
+
parts.append(f"[{opt.long_name} {placeholder}]")
|
|
427
|
+
return " ".join(parts)
|