cu-cli-core 0.1.0b1__tar.gz → 0.1.0b2__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 (71) hide show
  1. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/PKG-INFO +1 -1
  2. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/pyproject.toml +8 -1
  3. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/analysis.py +113 -0
  4. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/command_spec.py +136 -8
  5. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/contracts.py +25 -5
  6. cu_cli_core-0.1.0b2/src/cu_cli_core/doctor.py +253 -0
  7. cu_cli_core-0.1.0b2/src/cu_cli_core/infra.py +317 -0
  8. cu_cli_core-0.1.0b2/src/cu_cli_core/infra_models.py +220 -0
  9. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/input_planning.py +231 -15
  10. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/operations/analysis.py +4 -2
  11. cu_cli_core-0.1.0b2/src/cu_cli_core/postprovision.py +262 -0
  12. cu_cli_core-0.1.0b2/src/cu_cli_core/reporting.py +65 -0
  13. cu_cli_core-0.1.0b2/src/cu_cli_core/resources/azd_template/README.md +196 -0
  14. cu_cli_core-0.1.0b2/src/cu_cli_core/resources/azd_template/azure.yaml +27 -0
  15. cu_cli_core-0.1.0b2/src/cu_cli_core/resources/azd_template/hooks/postprovision.ps1 +25 -0
  16. cu_cli_core-0.1.0b2/src/cu_cli_core/resources/azd_template/hooks/postprovision.sh +22 -0
  17. cu_cli_core-0.1.0b2/src/cu_cli_core/resources/azd_template/infra/main.bicep +115 -0
  18. cu_cli_core-0.1.0b2/src/cu_cli_core/resources/azd_template/infra/main.parameters.json +30 -0
  19. cu_cli_core-0.1.0b2/src/cu_cli_core/resources/azd_template/infra/models.json +1 -0
  20. cu_cli_core-0.1.0b2/src/cu_cli_core/resources/azd_template/infra/modules/foundry.bicep +122 -0
  21. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/service_options.py +19 -3
  22. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core.egg-info/PKG-INFO +1 -1
  23. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core.egg-info/SOURCES.txt +18 -0
  24. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/tests/test_analysis.py +108 -0
  25. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/tests/test_command_spec.py +36 -5
  26. cu_cli_core-0.1.0b2/tests/test_contracts.py +75 -0
  27. cu_cli_core-0.1.0b2/tests/test_doctor.py +149 -0
  28. cu_cli_core-0.1.0b2/tests/test_infra.py +116 -0
  29. cu_cli_core-0.1.0b2/tests/test_infra_models.py +97 -0
  30. cu_cli_core-0.1.0b2/tests/test_input_planning.py +795 -0
  31. cu_cli_core-0.1.0b2/tests/test_postprovision.py +201 -0
  32. cu_cli_core-0.1.0b2/tests/test_postprovision_launchers.py +117 -0
  33. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/tests/test_service_options.py +4 -3
  34. cu_cli_core-0.1.0b1/tests/test_contracts.py +0 -36
  35. cu_cli_core-0.1.0b1/tests/test_input_planning.py +0 -348
  36. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/README.md +0 -0
  37. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/setup.cfg +0 -0
  38. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/__init__.py +0 -0
  39. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/client.py +0 -0
  40. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/defaults.py +0 -0
  41. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/environment.py +0 -0
  42. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/errors.py +0 -0
  43. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/operations/__init__.py +0 -0
  44. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/operations/analyzer_copy.py +0 -0
  45. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/operations/analyzers.py +0 -0
  46. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/operations/profiles.py +0 -0
  47. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/operations/schema.py +0 -0
  48. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/operations/validation.py +0 -0
  49. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/profiles.py +0 -0
  50. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/py.typed +0 -0
  51. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/resources/openapi/2025-11-01/ContentUnderstanding.json +0 -0
  52. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/resources/openapi/2026-06-01-preview/ContentUnderstanding.json +0 -0
  53. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/schema.py +0 -0
  54. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/schema_validation.py +0 -0
  55. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/serialization.py +0 -0
  56. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core/spec_validation.py +0 -0
  57. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core.egg-info/dependency_links.txt +0 -0
  58. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core.egg-info/requires.txt +0 -0
  59. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/src/cu_cli_core.egg-info/top_level.txt +0 -0
  60. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/tests/test_client.py +0 -0
  61. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/tests/test_defaults.py +0 -0
  62. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/tests/test_environment.py +0 -0
  63. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/tests/test_errors.py +0 -0
  64. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/tests/test_operations.py +0 -0
  65. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/tests/test_package_metadata.py +0 -0
  66. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/tests/test_profile_operations.py +0 -0
  67. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/tests/test_profiles.py +0 -0
  68. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/tests/test_schema.py +0 -0
  69. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/tests/test_schema_validation.py +0 -0
  70. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/tests/test_serialization.py +0 -0
  71. {cu_cli_core-0.1.0b1 → cu_cli_core-0.1.0b2}/tests/test_validation_operation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cu-cli-core
3
- Version: 0.1.0b1
3
+ Version: 0.1.0b2
4
4
  Summary: Framework-neutral command contracts and operations for Azure Content Understanding CLIs.
5
5
  Author: Microsoft Corporation
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cu-cli-core"
7
- version = "0.1.0b1"
7
+ version = "0.1.0b2"
8
8
  description = "Framework-neutral command contracts and operations for Azure Content Understanding CLIs."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -34,6 +34,13 @@ where = ["src"]
34
34
  "cu_cli_core" = [
35
35
  "py.typed",
36
36
  "resources/openapi/*/*.json",
37
+ "resources/azd_template/*.md",
38
+ "resources/azd_template/*.yaml",
39
+ "resources/azd_template/hooks/*.ps1",
40
+ "resources/azd_template/hooks/*.sh",
41
+ "resources/azd_template/infra/*.bicep",
42
+ "resources/azd_template/infra/*.json",
43
+ "resources/azd_template/infra/modules/*.bicep",
37
44
  ]
38
45
 
39
46
  [tool.pytest.ini_options]
@@ -45,6 +45,7 @@ class AnalyzeJob:
45
45
  analyzer_id: str
46
46
  out_path: Optional[Path] = None # None => caller streams to stdout
47
47
  output_format: str = "markdown"
48
+ input_url: Optional[str] = None
48
49
 
49
50
 
50
51
  @dataclass
@@ -249,6 +250,51 @@ def analyze_bytes_with_usage(
249
250
  return AnalyzeResponse(result=result, usage=getattr(poller, "usage", None))
250
251
 
251
252
 
253
+ def _analysis_url_input(url: str) -> Any:
254
+ from azure.ai.contentunderstanding.models import AnalysisInput
255
+
256
+ return AnalysisInput(url=url)
257
+
258
+
259
+ def analyze_url(
260
+ client: Any,
261
+ analyzer_id: str,
262
+ url: str,
263
+ *,
264
+ raw_json: bool = False,
265
+ ) -> Any:
266
+ """Analyze an HTTPS input reference and return the completed result."""
267
+ return analyze_url_with_usage(
268
+ client,
269
+ analyzer_id,
270
+ url,
271
+ raw_json=raw_json,
272
+ ).result
273
+
274
+
275
+ def analyze_url_with_usage(
276
+ client: Any,
277
+ analyzer_id: str,
278
+ url: str,
279
+ *,
280
+ raw_json: bool = False,
281
+ ) -> AnalyzeResponse:
282
+ """Analyze an HTTPS input reference while retaining usage metadata."""
283
+ kwargs = {"cls": _capture_raw_response} if raw_json else {}
284
+ poller = client.begin_analyze(
285
+ analyzer_id=analyzer_id,
286
+ inputs=[_analysis_url_input(url)],
287
+ **kwargs,
288
+ )
289
+ completed = poller.result()
290
+ if raw_json:
291
+ _, raw_response = completed
292
+ result = raw_response.json()
293
+ else:
294
+ result = completed
295
+ return AnalyzeResponse(result=result, usage=getattr(poller, "usage", None))
296
+
297
+
252
298
  def analyze_bytes_inline(
253
299
  client: Any,
254
300
  analyzer_id: str,
@@ -288,8 +334,54 @@ def analyze_bytes_inline_with_usage(
288
334
  return AnalyzeResponse(result=result, usage=getattr(response, "usage", None))
289
335
 
290
336
 
337
+ def analyze_url_inline(
338
+ client: Any,
339
+ analyzer_id: str,
340
+ url: str,
341
+ *,
342
+ raw_json: bool = False,
343
+ ) -> Any:
344
+ """Analyze an HTTPS input reference synchronously."""
345
+ return analyze_url_inline_with_usage(
346
+ client,
347
+ analyzer_id,
348
+ url,
349
+ raw_json=raw_json,
350
+ ).result
351
+
352
+
353
+ def analyze_url_inline_with_usage(
354
+ client: Any,
355
+ analyzer_id: str,
356
+ url: str,
357
+ *,
358
+ raw_json: bool = False,
359
+ ) -> AnalyzeResponse:
360
+ """Analyze an HTTPS input reference synchronously with usage metadata."""
361
+ kwargs = {"cls": _capture_raw_response} if raw_json else {}
362
+ completed = client.analyze_inline(
363
+ analyzer_id=analyzer_id,
364
+ inputs=[_analysis_url_input(url)],
365
+ **kwargs,
366
+ )
367
+ if raw_json:
368
+ response, raw_response = completed
369
+ result = raw_response.json()
370
+ else:
371
+ response = completed
372
+ result = response.result
373
+ return AnalyzeResponse(result=result, usage=getattr(response, "usage", None))
374
+
375
+
291
376
  def analyze_one(client: Any, job: AnalyzeJob) -> Any:
292
377
  """Run a single :class:`AnalyzeJob`, returning the completed SDK result."""
378
+ if job.input_url is not None:
379
+ return analyze_url(
380
+ client,
381
+ job.analyzer_id,
382
+ job.input_url,
383
+ raw_json=job.output_format == "json",
384
+ )
293
385
  data = Path(job.input_ref).read_bytes()
294
386
  return analyze_bytes(
295
387
  client,
@@ -301,6 +393,13 @@ def analyze_one(client: Any, job: AnalyzeJob) -> Any:
301
393
 
302
394
  def analyze_one_inline(client: Any, job: AnalyzeJob) -> Any:
303
395
  """Run a single job synchronously through the inline analyze API."""
396
+ if job.input_url is not None:
397
+ return analyze_url_inline(
398
+ client,
399
+ job.analyzer_id,
400
+ job.input_url,
401
+ raw_json=job.output_format == "json",
402
+ )
304
403
  data = Path(job.input_ref).read_bytes()
305
404
  return analyze_bytes_inline(
306
405
  client,
@@ -312,6 +411,13 @@ def analyze_one_inline(client: Any, job: AnalyzeJob) -> Any:
312
411
 
313
412
  def analyze_one_with_usage(client: Any, job: AnalyzeJob) -> AnalyzeResponse:
314
413
  """Run one long-running analysis and retain its usage metadata."""
414
+ if job.input_url is not None:
415
+ return analyze_url_with_usage(
416
+ client,
417
+ job.analyzer_id,
418
+ job.input_url,
419
+ raw_json=job.output_format == "json",
420
+ )
315
421
  data = Path(job.input_ref).read_bytes()
316
422
  return analyze_bytes_with_usage(
317
423
  client,
@@ -323,6 +429,13 @@ def analyze_one_with_usage(client: Any, job: AnalyzeJob) -> AnalyzeResponse:
323
429
 
324
430
  def analyze_one_inline_with_usage(client: Any, job: AnalyzeJob) -> AnalyzeResponse:
325
431
  """Run one inline analysis and retain its usage metadata."""
432
+ if job.input_url is not None:
433
+ return analyze_url_inline_with_usage(
434
+ client,
435
+ job.analyzer_id,
436
+ job.input_url,
437
+ raw_json=job.output_format == "json",
438
+ )
326
439
  data = Path(job.input_ref).read_bytes()
327
440
  return analyze_bytes_inline_with_usage(
328
441
  client,
@@ -18,8 +18,11 @@ class SurfaceClassification(str, Enum):
18
18
  COMMON = "common"
19
19
  SHARED_ALIAS = "shared-alias"
20
20
  STANDALONE_SHORTCUT = "standalone-shortcut"
21
+ STANDALONE_ONLY = "standalone-only"
22
+ AZURE_ONLY = "azure-only"
21
23
  AZURE_HOST_GLOBAL = "azure-host-global"
22
24
  FRONTEND_PRESENTATION = "frontend-presentation"
25
+ INTERNAL = "internal"
23
26
 
24
27
 
25
28
  class ArgumentValueType(str, Enum):
@@ -27,6 +30,7 @@ class ArgumentValueType(str, Enum):
27
30
 
28
31
  STRING = "string"
29
32
  BOOLEAN = "boolean"
33
+ TRISTATE_BOOLEAN = "tristate-boolean"
30
34
  INTEGER = "integer"
31
35
  PATH = "path"
32
36
 
@@ -64,8 +68,8 @@ class CommandSpec:
64
68
 
65
69
  path: tuple[str, ...]
66
70
  help: str
67
- operation: str
68
- request_type: str
71
+ operation: str | None = None
72
+ request_type: str | None = None
69
73
  arguments: tuple[ArgumentSpec, ...] = ()
70
74
  service_options: tuple[str, ...] = ()
71
75
  classification: SurfaceClassification = SurfaceClassification.COMMON
@@ -127,6 +131,8 @@ def bind_command_arguments(
127
131
  def build_request(spec: CommandSpec, parsed: Mapping[str, Any]) -> Any:
128
132
  """Bind frontend values and instantiate the spec's lazy request type."""
129
133
 
134
+ if spec.request_type is None:
135
+ raise CommandBindingError(f"{' '.join(spec.path)} does not define a shared request type")
130
136
  request_type = resolve_identifier(spec.request_type)
131
137
  try:
132
138
  return request_type(**bind_command_arguments(spec, parsed))
@@ -136,7 +142,11 @@ def build_request(spec: CommandSpec, parsed: Mapping[str, Any]) -> Any:
136
142
  raise CommandBindingError(str(exc)) from exc
137
143
 
138
144
 
139
- _SERVICE_OPTIONS = ("endpoint", "api-version", "auth-mode", "api-key")
145
+ _SERVICE_OPTIONS = ("endpoint", "api-version", "auth-mode", "api-key", "profile")
146
+ _API_VERSION_HELP = (
147
+ "Content Understanding API version. Known versions: 2025-11-01 (GA) and "
148
+ "2026-06-01-preview (preview); any YYYY-MM-DD-preview version is also accepted."
149
+ )
140
150
 
141
151
 
142
152
  def _profile_name_arguments(option_help: str) -> tuple[ArgumentSpec, ...]:
@@ -223,14 +233,33 @@ _INPUT_ARGUMENTS = (
223
233
  ),
224
234
  )
225
235
 
236
+ _ANALYZE_INPUT_ARGUMENTS = (
237
+ ArgumentSpec(
238
+ "INPUTS",
239
+ field="positional_inputs",
240
+ parser_name="inputs",
241
+ help="Standalone local file, directory, or HTTPS URL shortcuts.",
242
+ repeatable=True,
243
+ classification=SurfaceClassification.STANDALONE_SHORTCUT,
244
+ ),
245
+ *_INPUT_ARGUMENTS[1:],
246
+ ArgumentSpec(
247
+ "--url",
248
+ field="urls",
249
+ parser_name="urls",
250
+ help="HTTPS or Azure Blob SAS URL. Repeat for multiple URLs.",
251
+ repeatable=True,
252
+ ),
253
+ )
254
+
226
255
 
227
256
  ANALYZE = CommandSpec(
228
257
  path=("analyze",),
229
- help="Process local files with an analyzer and return analyzer results.",
258
+ help="Process local files or HTTPS URLs and return analyzer results.",
230
259
  operation="cu_cli_core.operations.analysis#execute_analyze",
231
260
  request_type="cu_cli_core.contracts#AnalyzeRequest",
232
261
  arguments=(
233
- *_INPUT_ARGUMENTS,
262
+ *_ANALYZE_INPUT_ARGUMENTS,
234
263
  ArgumentSpec(
235
264
  "--analyzer",
236
265
  aliases=("-a",),
@@ -317,7 +346,6 @@ ANALYZE = CommandSpec(
317
346
  value_type=ArgumentValueType.PATH,
318
347
  file_okay=True,
319
348
  dir_okay=False,
320
- classification=SurfaceClassification.FRONTEND_PRESENTATION,
321
349
  ),
322
350
  ArgumentSpec(
323
351
  "--concurrency",
@@ -824,7 +852,6 @@ PROFILE_SHOW = CommandSpec(
824
852
  parser_name="deployments",
825
853
  help="Also list live Foundry model deployments.",
826
854
  value_type=ArgumentValueType.BOOLEAN,
827
- classification=SurfaceClassification.FRONTEND_PRESENTATION,
828
855
  ),
829
856
  ),
830
857
  )
@@ -935,7 +962,17 @@ PROFILE_DELETE = CommandSpec(
935
962
  help="Delete an inactive named CU CLI profile.",
936
963
  operation="cu_cli_core.operations.profiles#delete_profile",
937
964
  request_type="cu_cli_core.contracts#ProfileDeleteRequest",
938
- arguments=_profile_name_arguments("Existing inactive profile to delete."),
965
+ arguments=(
966
+ *_profile_name_arguments("Existing inactive profile to delete."),
967
+ ArgumentSpec(
968
+ "--yes",
969
+ aliases=("-y",),
970
+ field="yes",
971
+ parser_name="yes",
972
+ help="Delete without prompting for confirmation.",
973
+ value_type=ArgumentValueType.BOOLEAN,
974
+ ),
975
+ ),
939
976
  )
940
977
 
941
978
  PROFILE_COPY = CommandSpec(
@@ -1061,6 +1098,93 @@ ENV_VAR_LIST = CommandSpec(
1061
1098
  ),
1062
1099
  )
1063
1100
 
1101
+ DOCTOR = CommandSpec(
1102
+ path=("doctor",),
1103
+ help="Verify a Microsoft Foundry resource connection and Content Understanding defaults.",
1104
+ arguments=(
1105
+ ArgumentSpec(
1106
+ "--profile",
1107
+ aliases=("-p",),
1108
+ field="profile_name",
1109
+ parser_name="profile_name",
1110
+ help="Named CU CLI profile to use.",
1111
+ ),
1112
+ ArgumentSpec(
1113
+ "--endpoint",
1114
+ field="endpoint",
1115
+ parser_name="endpoint",
1116
+ help="Override the configured Microsoft Foundry resource endpoint.",
1117
+ ),
1118
+ ArgumentSpec(
1119
+ "--api-version",
1120
+ field="api_version",
1121
+ parser_name="api_version",
1122
+ help=_API_VERSION_HELP,
1123
+ ),
1124
+ ArgumentSpec(
1125
+ "--fix-defaults",
1126
+ field="fix_defaults",
1127
+ parser_name="fix_defaults",
1128
+ help="Configure Content Understanding defaults from profile model mappings.",
1129
+ value_type=ArgumentValueType.BOOLEAN,
1130
+ ),
1131
+ ArgumentSpec(
1132
+ "--auth-mode",
1133
+ field="auth_mode",
1134
+ parser_name="auth_mode",
1135
+ help="Authentication mode; defaults to the selected CU CLI profile.",
1136
+ choices=("login", "key"),
1137
+ ),
1138
+ ArgumentSpec(
1139
+ "--api-key",
1140
+ field="api_key",
1141
+ parser_name="api_key",
1142
+ help="Override the configured API key.",
1143
+ ),
1144
+ ),
1145
+ )
1146
+
1147
+ INFRA_GENERATE = CommandSpec(
1148
+ path=("infra", "generate"),
1149
+ help="Generate an azd/Bicep project for Content Understanding.",
1150
+ arguments=(
1151
+ ArgumentSpec("--output-dir", aliases=("-d",), field="output_dir", parser_name="output_dir", help="Directory where the azd template is generated.", value_type=ArgumentValueType.PATH, default="provision", file_okay=False),
1152
+ ArgumentSpec("--environment", aliases=("-e",), field="environment", parser_name="environment", help="azd environment name."),
1153
+ ArgumentSpec("--location", aliases=("-l",), field="location", parser_name="location", help="Azure region."),
1154
+ ArgumentSpec("--subscription", field="subscription", parser_name="subscription", help="Azure subscription name or ID.", classification=SurfaceClassification.STANDALONE_ONLY),
1155
+ ArgumentSpec("--api-version", field="api_version", parser_name="api_version", help=_API_VERSION_HELP),
1156
+ ArgumentSpec("--models", field="models", parser_name="models", help="Comma-separated model names, 'recommended', or 'none'."),
1157
+ ArgumentSpec("--foundry-endpoint", field="foundry_endpoint", parser_name="foundry_endpoint", help="Existing Microsoft Foundry resource endpoint."),
1158
+ ArgumentSpec("--foundry-prefix", field="foundry_prefix", parser_name="foundry_prefix", help="Prefix for a new Microsoft Foundry resource name."),
1159
+ ArgumentSpec("--assign-roles", field="assign_roles", parser_name="assign_roles", help="Whether to configure RBAC role assignments; omit to use interactive or default behavior.", value_type=ArgumentValueType.TRISTATE_BOOLEAN),
1160
+ ArgumentSpec("--force", field="force", parser_name="force", help="Overwrite an existing generated template.", value_type=ArgumentValueType.BOOLEAN),
1161
+ ),
1162
+ )
1163
+
1164
+ INFRA_MODELS = CommandSpec(
1165
+ path=("_infra-models",),
1166
+ help="Configure model deployments for a generated infrastructure project.",
1167
+ classification=SurfaceClassification.INTERNAL,
1168
+ arguments=(
1169
+ ArgumentSpec("--resource-group", field="resource_group", parser_name="resource_group", help="Resource group containing the Foundry account.", required=True),
1170
+ ArgumentSpec("--account", field="account_name", parser_name="account_name", help="Foundry account name.", required=True),
1171
+ ArgumentSpec("--subscription", field="subscription_id", parser_name="subscription_id", help="Azure subscription ID.", required=True, classification=SurfaceClassification.STANDALONE_ONLY),
1172
+ ArgumentSpec("--selection", field="selection", parser_name="selection", help="prompt, recommended, none, or comma-separated model selectors.", required=True),
1173
+ ArgumentSpec("--out", field="out_path", parser_name="out_path", help="Output Bicep model file.", value_type=ArgumentValueType.PATH, required=True),
1174
+ ArgumentSpec("--deploy/--no-deploy", field="deploy", parser_name="deploy", help="Deploy selected models before writing the model file.", value_type=ArgumentValueType.BOOLEAN, default=True, classification=SurfaceClassification.STANDALONE_ONLY),
1175
+ ArgumentSpec("--deploy", field="deploy", parser_name="deploy", help="Deploy selected models before writing the model file.", value_type=ArgumentValueType.BOOLEAN, default=True, classification=SurfaceClassification.AZURE_ONLY),
1176
+ ArgumentSpec("--endpoint", field="endpoint", parser_name="endpoint", help="Microsoft Foundry resource endpoint.", required=True, classification=SurfaceClassification.AZURE_ONLY),
1177
+ ArgumentSpec("--api-version", field="api_version", parser_name="api_version", help=_API_VERSION_HELP, classification=SurfaceClassification.AZURE_ONLY),
1178
+ ArgumentSpec("--use-key", field="use_key", parser_name="use_key", help="Use an account key for the data-plane request.", value_type=ArgumentValueType.BOOLEAN, classification=SurfaceClassification.AZURE_ONLY),
1179
+ ),
1180
+ )
1181
+
1182
+ INFRA_POSTPROVISION_V1 = CommandSpec(
1183
+ path=("_infra-postprovision-v1",),
1184
+ help="Complete generated-project setup after azd provisioning.",
1185
+ classification=SurfaceClassification.INTERNAL,
1186
+ )
1187
+
1064
1188
 
1065
1189
  COMMAND_SPECS: tuple[CommandSpec, ...] = (
1066
1190
  ANALYZE,
@@ -1086,6 +1210,10 @@ COMMAND_SPECS: tuple[CommandSpec, ...] = (
1086
1210
  PROFILE_SET_ACTIVE,
1087
1211
  PROFILE_SYNC_DEFAULTS,
1088
1212
  ENV_VAR_LIST,
1213
+ DOCTOR,
1214
+ INFRA_GENERATE,
1215
+ INFRA_MODELS,
1216
+ INFRA_POSTPROVISION_V1,
1089
1217
  )
1090
1218
  _COMMAND_SPECS_BY_PATH = {spec.path: spec for spec in COMMAND_SPECS}
1091
1219
 
@@ -20,13 +20,16 @@ class SelectionMode(str, Enum):
20
20
  POSITIONAL = "positional"
21
21
  NAMED_FILES = "named-files"
22
22
  NAMED_SOURCES = "named-sources"
23
+ NAMED_URLS = "named-urls"
23
24
 
24
25
 
25
26
  class InputOrigin(str, Enum):
26
27
  POSITIONAL_FILE = "positional-file"
27
28
  POSITIONAL_SOURCE = "positional-source"
29
+ POSITIONAL_URL = "positional-url"
28
30
  NAMED_FILE = "named-file"
29
31
  NAMED_SOURCE = "named-source"
32
+ NAMED_URL = "named-url"
30
33
 
31
34
 
32
35
  class ExistingResultPolicy(str, Enum):
@@ -49,6 +52,7 @@ class OutcomeStatus(str, Enum):
49
52
  @dataclass(frozen=True)
50
53
  class ProfileShowRequest:
51
54
  name: str | None = None
55
+ deployments: bool = False
52
56
 
53
57
 
54
58
  @dataclass(frozen=True)
@@ -83,6 +87,7 @@ class ProfileCreateRequest:
83
87
  @dataclass(frozen=True)
84
88
  class ProfileDeleteRequest:
85
89
  name: str
90
+ yes: bool = False
86
91
 
87
92
 
88
93
  @dataclass(frozen=True)
@@ -159,7 +164,7 @@ class AnalyzerDeleteRequest:
159
164
 
160
165
  @dataclass(frozen=True)
161
166
  class AnalyzeRequest:
162
- positional_inputs: tuple[Path, ...] = ()
167
+ positional_inputs: tuple[str, ...] = ()
163
168
  files: tuple[Path, ...] = ()
164
169
  sources: tuple[Path, ...] = ()
165
170
  pattern: str | None = None
@@ -175,15 +180,17 @@ class AnalyzeRequest:
175
180
  yes: bool = False
176
181
  report_file: Path | None = None
177
182
  concurrency: int = 4
183
+ urls: tuple[str, ...] = ()
178
184
 
179
185
  def __post_init__(self) -> None:
180
186
  object.__setattr__(
181
187
  self,
182
188
  "positional_inputs",
183
- tuple(Path(path) for path in self.positional_inputs),
189
+ tuple(str(value) for value in self.positional_inputs),
184
190
  )
185
191
  object.__setattr__(self, "files", tuple(Path(path) for path in self.files))
186
192
  object.__setattr__(self, "sources", tuple(Path(path) for path in self.sources))
193
+ object.__setattr__(self, "urls", tuple(str(value) for value in self.urls))
187
194
  if self.output_file is not None:
188
195
  object.__setattr__(self, "output_file", Path(self.output_file))
189
196
  if self.output_dir is not None:
@@ -329,11 +336,24 @@ class EnvironmentVariableListRequest:
329
336
 
330
337
  @dataclass(frozen=True)
331
338
  class PlannedInput:
332
- path: Path
333
- source_root: Path
339
+ path: Path | None
340
+ source_root: Path | None
334
341
  relative_path: Path
335
342
  origin: InputOrigin
336
- size_bytes: int
343
+ size_bytes: int | None
344
+ url: str | None = None
345
+
346
+ @property
347
+ def reference(self) -> str:
348
+ if self.url is not None:
349
+ return self.url
350
+ if self.path is None:
351
+ raise ValueError("planned input must have either a local path or URL.")
352
+ return str(self.path)
353
+
354
+ @property
355
+ def is_remote(self) -> bool:
356
+ return self.url is not None
337
357
 
338
358
 
339
359
  @dataclass(frozen=True)