diff --git a/.claude/skills/cf-edit/SKILL.md b/.claude/skills/cf-edit/SKILL.md index e2c0e395..fd4f1141 100644 --- a/.claude/skills/cf-edit/SKILL.md +++ b/.claude/skills/cf-edit/SKILL.md @@ -1,6 +1,6 @@ --- name: cf-edit -description: Точечное редактирование конфигурации 1С. Используй когда нужно изменить свойства конфигурации, добавить или удалить объект из состава, настроить роли по умолчанию +description: Точечное редактирование конфигурации 1С. Используй когда нужно изменить свойства конфигурации, добавить или удалить объект из состава, настроить роли по умолчанию, поменять раскладку панелей argument-hint: -ConfigPath -Operation -Value allowed-tools: - Bash @@ -37,6 +37,7 @@ powershell.exe -NoProfile -File .claude/skills/cf-edit/scripts/cf-edit.ps1 -Conf | `add-defaultRole` | `Role.Name` или `Name` | Добавить роль по умолчанию | | `remove-defaultRole` | `Role.Name` или `Name` | Удалить роль по умолчанию | | `set-defaultRoles` | Имена через `;;` | Заменить список ролей по умолчанию | +| `set-panels` | JSON-объект (см. [reference.md](reference.md)) | Перезаписать `Ext/ClientApplicationInterface.xml` (раскладка панелей) | Допустимые значения свойств, формат DefinitionFile (JSON), каноничный порядок: [reference.md](reference.md) diff --git a/.claude/skills/cf-edit/reference.md b/.claude/skills/cf-edit/reference.md index 57a8c0f9..f394237f 100644 --- a/.claude/skills/cf-edit/reference.md +++ b/.claude/skills/cf-edit/reference.md @@ -52,6 +52,52 @@ Batch: `"Catalog.Товары ;; Document.Заказ ;; Enum.ВидыОплат" `set-defaultRoles` полностью заменяет список ролей. +## set-panels + +Перезаписывает `Ext/ClientApplicationInterface.xml` — раскладку панелей рабочего пространства Taxi. Файл создаётся с нуля; то, что не упомянуто в `value`, отсутствует на экране. + +`value` — объект с ключами `top`, `left`, `right`, `bottom`. Каждый ключ — массив записей. Ключ можно опустить (= пустая сторона). + +**Запись** — одна из: +- Строка-алиас (одна панель в этом слоте) +- Объект `{"group": [...]}` (стек: панели/подгруппы внутри располагаются друг под другом) + +**Алиасы панелей:** + +| Алиас | Панель | +|-------|--------| +| `sections` | Панель разделов | +| `open` | Панель открытых | +| `favorites` | Панель избранного | +| `history` | Панель истории | +| `functions` | Панель функций текущего раздела | + +**Семантика:** +- Несколько записей в одной стороне → отдельные слоты «рядом» (несколько тегов ``/...) +- `{"group":[...]}` → один тег с ``-обёрткой, элементы внутри идут стеком + +**Пример** (DefinitionFile): +```json +[ + { + "operation": "set-panels", + "value": { + "top": ["open"], + "left": ["sections"], + "right": [{ "group": ["favorites", "history"] }], + "bottom": ["functions"] + } + } +] +``` + +**Через `-Value`** (CLI): передавай тот же объект как JSON-строку: +```powershell +... -Operation set-panels -Value '{"top":["open"],"left":["sections"]}' +``` + +`` для всех 5 панелей пишется автоматически — они всегда доступны пользователю через «Вид → Настройка панелей», даже если не размещены по умолчанию. + ## DefinitionFile (JSON) ```json diff --git a/.claude/skills/cf-edit/scripts/cf-edit.ps1 b/.claude/skills/cf-edit/scripts/cf-edit.ps1 index 72e48a24..7b5c244b 100644 --- a/.claude/skills/cf-edit/scripts/cf-edit.ps1 +++ b/.claude/skills/cf-edit/scripts/cf-edit.ps1 @@ -1,9 +1,9 @@ -# cf-edit v1.1 — Edit 1C configuration root (Configuration.xml) +# cf-edit v1.2 — Edit 1C configuration root (Configuration.xml) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)][Alias('Path')][string]$ConfigPath, [string]$DefinitionFile, - [ValidateSet("modify-property","add-childObject","remove-childObject","add-defaultRole","remove-defaultRole","set-defaultRoles")] + [ValidateSet("modify-property","add-childObject","remove-childObject","add-defaultRole","remove-defaultRole","set-defaultRoles","set-panels")] [string]$Operation, [string]$Value, [switch]$NoValidate @@ -444,6 +444,108 @@ function Do-RemoveDefaultRole([string]$batchVal) { } } +# --- Operation: set-panels --- +$script:panelUuids = @{ + "sections" = "b553047f-c9aa-4157-978d-448ecad24248" + "open" = "cbab57f2-a0f3-4f0a-89ea-4cb19570ab75" + "favorites" = "13322b22-3960-4d68-93a6-fe2dd7f28ca3" + "history" = "c933ac92-92cd-459d-81cc-e0c8a83ced99" + "functions" = "b2735bd3-d822-4430-ba59-c9e869693b24" +} + +function Build-PanelEntryXml($entry, [string]$indent) { + # String alias -> ... + if ($entry -is [string]) { + if (-not $script:panelUuids.ContainsKey($entry)) { + Write-Error "Unknown panel alias '$entry'. Allowed: $(($script:panelUuids.Keys | Sort-Object) -join ', ')" + exit 1 + } + $u = $script:panelUuids[$entry] + $instId = [guid]::NewGuid().ToString() + return "$indent`r`n$indent`t$u`r`n$indent" + } + # Object {group: [...]} -> ... (stack) + if ($entry.PSObject.Properties['group']) { + $children = $entry.group + if (-not $children -or $children.Count -eq 0) { + Write-Error "group must contain at least one entry" + exit 1 + } + $gid = [guid]::NewGuid().ToString() + $inner = "" + foreach ($child in $children) { + $childXml = Build-PanelEntryXml $child "$indent`t`t" + $inner += "$indent`t`r`n$childXml`r`n$indent`t`r`n" + } + return "$indent`r`n$inner$indent" + } + Write-Error "Panel entry must be a string alias or object {group:[...]}, got: $($entry | ConvertTo-Json -Compress)" + exit 1 +} + +function Do-SetPanels($valArg) { + # Accept string (JSON), PSCustomObject, or hashtable + $layout = $valArg + if ($layout -is [string]) { + try { $layout = $layout | ConvertFrom-Json } catch { + Write-Error "set-panels value must be valid JSON object, got: $valArg" + exit 1 + } + } + if (-not $layout) { + Write-Error "set-panels value is empty" + exit 1 + } + + $sides = @("top","left","right","bottom") + $bodyParts = @() + foreach ($side in $sides) { + $entries = $null + if ($layout.PSObject.Properties[$side]) { $entries = $layout.$side } + if ($null -eq $entries) { continue } + # Normalize to array + if ($entries -isnot [System.Array] -and $entries -isnot [System.Collections.IList]) { + $entries = @($entries) + } + foreach ($entry in $entries) { + $entryXml = Build-PanelEntryXml $entry "`t`t" + $bodyParts += "`t<$side>`r`n$entryXml`r`n`t" + } + } + + # Reject unknown side keys (catches typos like "Top" vs "top") + foreach ($prop in $layout.PSObject.Properties) { + if ($sides -notcontains $prop.Name) { + Write-Error "Unknown side '$($prop.Name)'. Allowed: $($sides -join ', ')" + exit 1 + } + } + + $body = $bodyParts -join "`r`n" + $declarations = @" + + + + + +"@ + $bodyBlock = if ($body) { "$body`r`n" } else { "" } + $caiXml = @" + + +$bodyBlock$declarations + +"@ + + $extDir = Join-Path $script:configDir "Ext" + if (-not (Test-Path $extDir)) { New-Item -ItemType Directory -Path $extDir -Force | Out-Null } + $caiPath = Join-Path $extDir "ClientApplicationInterface.xml" + $utf8Bom = New-Object System.Text.UTF8Encoding($true) + [System.IO.File]::WriteAllText($caiPath, $caiXml, $utf8Bom) + $script:modifyCount++ + Info "Wrote panel layout: $caiPath" +} + # --- Operation: set-defaultRoles --- function Do-SetDefaultRoles([string]$batchVal) { $items = Parse-BatchValue $batchVal @@ -508,15 +610,18 @@ if ($DefinitionFile) { foreach ($op in $operations) { $opName = if ($op.operation) { "$($op.operation)" } else { "$Operation" } - $opValue = if ($op.value) { "$($op.value)" } else { "$Value" } + # Pass value through as-is (object or string); set-panels needs object form + $opValue = if ($null -ne $op.value) { $op.value } else { $Value } + $opValueStr = if ($opValue -is [string]) { $opValue } else { "$opValue" } switch ($opName) { - "modify-property" { Do-ModifyProperty $opValue } - "add-childObject" { Do-AddChildObject $opValue } - "remove-childObject" { Do-RemoveChildObject $opValue } - "add-defaultRole" { Do-AddDefaultRole $opValue } - "remove-defaultRole" { Do-RemoveDefaultRole $opValue } - "set-defaultRoles" { Do-SetDefaultRoles $opValue } + "modify-property" { Do-ModifyProperty $opValueStr } + "add-childObject" { Do-AddChildObject $opValueStr } + "remove-childObject" { Do-RemoveChildObject $opValueStr } + "add-defaultRole" { Do-AddDefaultRole $opValueStr } + "remove-defaultRole" { Do-RemoveDefaultRole $opValueStr } + "set-defaultRoles" { Do-SetDefaultRoles $opValueStr } + "set-panels" { Do-SetPanels $opValue } default { Write-Error "Unknown operation: $opName"; exit 1 } } } diff --git a/.claude/skills/cf-edit/scripts/cf-edit.py b/.claude/skills/cf-edit/scripts/cf-edit.py index 614902e8..6cdd2591 100644 --- a/.claude/skills/cf-edit/scripts/cf-edit.py +++ b/.claude/skills/cf-edit/scripts/cf-edit.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# cf-edit v1.1 — Edit 1C configuration root (Configuration.xml) +# cf-edit v1.2 — Edit 1C configuration root (Configuration.xml) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -7,6 +7,7 @@ import json import os import subprocess import sys +import uuid as _uuid from html import escape as html_escape from lxml import etree @@ -161,7 +162,7 @@ def main(): parser = argparse.ArgumentParser(description="Edit 1C configuration root (Configuration.xml)", allow_abbrev=False) parser.add_argument("-ConfigPath", "-Path", required=True) parser.add_argument("-DefinitionFile", default=None) - parser.add_argument("-Operation", default=None, choices=["modify-property", "add-childObject", "remove-childObject", "add-defaultRole", "remove-defaultRole", "set-defaultRoles"]) + parser.add_argument("-Operation", default=None, choices=["modify-property", "add-childObject", "remove-childObject", "add-defaultRole", "remove-defaultRole", "set-defaultRoles", "set-panels"]) parser.add_argument("-Value", default=None) parser.add_argument("-NoValidate", action="store_true") args = parser.parse_args() @@ -493,6 +494,93 @@ def main(): modify_count += 1 info(f"Set DefaultRoles: {len(items)} roles") + # --- set-panels (writes Ext/ClientApplicationInterface.xml from scratch) --- + PANEL_UUIDS = { + "sections": "b553047f-c9aa-4157-978d-448ecad24248", + "open": "cbab57f2-a0f3-4f0a-89ea-4cb19570ab75", + "favorites": "13322b22-3960-4d68-93a6-fe2dd7f28ca3", + "history": "c933ac92-92cd-459d-81cc-e0c8a83ced99", + "functions": "b2735bd3-d822-4430-ba59-c9e869693b24", + } + + def build_panel_entry_xml(entry, indent): + if isinstance(entry, str): + if entry not in PANEL_UUIDS: + allowed = ", ".join(sorted(PANEL_UUIDS.keys())) + print(f"Unknown panel alias '{entry}'. Allowed: {allowed}", file=sys.stderr) + sys.exit(1) + inst = str(_uuid.uuid4()) + return f'{indent}\r\n{indent}\t{PANEL_UUIDS[entry]}\r\n{indent}' + if isinstance(entry, dict) and "group" in entry: + children = entry["group"] + if not children: + print("group must contain at least one entry", file=sys.stderr) + sys.exit(1) + gid = str(_uuid.uuid4()) + inner = "" + for child in children: + child_xml = build_panel_entry_xml(child, indent + "\t\t") + inner += f"{indent}\t\r\n{child_xml}\r\n{indent}\t\r\n" + return f'{indent}\r\n{inner}{indent}' + print(f"Panel entry must be string alias or {{group:[...]}}, got: {entry!r}", file=sys.stderr) + sys.exit(1) + + def do_set_panels(value): + nonlocal modify_count + layout = value + if isinstance(layout, str): + try: + layout = json.loads(layout) + except json.JSONDecodeError: + print(f"set-panels value must be valid JSON object", file=sys.stderr) + sys.exit(1) + if not isinstance(layout, dict) or not layout: + print("set-panels value must be non-empty object", file=sys.stderr) + sys.exit(1) + + sides = ("top", "left", "right", "bottom") + # Reject unknown side keys + for k in layout.keys(): + if k not in sides: + print(f"Unknown side '{k}'. Allowed: {', '.join(sides)}", file=sys.stderr) + sys.exit(1) + + body_parts = [] + for side in sides: + entries = layout.get(side) + if entries is None: + continue + if not isinstance(entries, list): + entries = [entries] + for entry in entries: + entry_xml = build_panel_entry_xml(entry, "\t\t") + body_parts.append(f"\t<{side}>\r\n{entry_xml}\r\n\t") + body = "\r\n".join(body_parts) + body_block = body + "\r\n" if body else "" + declarations = ( + '\t\r\n' + '\t\r\n' + '\t\r\n' + '\t\r\n' + '\t' + ) + cai_xml = ( + '\r\n' + '\r\n' + f'{body_block}{declarations}\r\n' + '' + ) + ext_dir = os.path.join(config_dir, "Ext") + os.makedirs(ext_dir, exist_ok=True) + cai_path = os.path.join(ext_dir, "ClientApplicationInterface.xml") + with open(cai_path, "w", encoding="utf-8-sig", newline="") as fh: + fh.write(cai_xml) + modify_count += 1 + info(f"Wrote panel layout: {cai_path}") + # --- Execute operations --- operations = [] if args.DefinitionFile: @@ -513,17 +601,19 @@ def main(): op_value = op.get("value", args.Value or "") if op_name == "modify-property": - do_modify_property(op_value) + do_modify_property(op_value if isinstance(op_value, str) else str(op_value)) elif op_name == "add-childObject": - do_add_child_object(op_value) + do_add_child_object(op_value if isinstance(op_value, str) else str(op_value)) elif op_name == "remove-childObject": - do_remove_child_object(op_value) + do_remove_child_object(op_value if isinstance(op_value, str) else str(op_value)) elif op_name == "add-defaultRole": - do_add_default_role(op_value) + do_add_default_role(op_value if isinstance(op_value, str) else str(op_value)) elif op_name == "remove-defaultRole": - do_remove_default_role(op_value) + do_remove_default_role(op_value if isinstance(op_value, str) else str(op_value)) elif op_name == "set-defaultRoles": - do_set_default_roles(op_value) + do_set_default_roles(op_value if isinstance(op_value, str) else str(op_value)) + elif op_name == "set-panels": + do_set_panels(op_value) else: print(f"Unknown operation: {op_name}", file=sys.stderr) sys.exit(1) diff --git a/tests/skills/cases/cf-edit/set-panels.json b/tests/skills/cases/cf-edit/set-panels.json new file mode 100644 index 00000000..c3fba247 --- /dev/null +++ b/tests/skills/cases/cf-edit/set-panels.json @@ -0,0 +1,14 @@ +{ + "name": "Установить раскладку панелей (top/left/right стек/bottom)", + "input": [ + { + "operation": "set-panels", + "value": { + "top": ["open"], + "left": ["sections"], + "right": [{ "group": ["favorites", "history"] }], + "bottom": ["functions"] + } + } + ] +} diff --git a/tests/skills/cases/cf-edit/snapshots/set-panels/Configuration.xml b/tests/skills/cases/cf-edit/snapshots/set-panels/Configuration.xml new file mode 100644 index 00000000..ae8620a1 --- /dev/null +++ b/tests/skills/cases/cf-edit/snapshots/set-panels/Configuration.xml @@ -0,0 +1,251 @@ + + + + + + UUID-002 + UUID-003 + + + UUID-004 + UUID-005 + + + UUID-006 + UUID-007 + + + UUID-008 + UUID-009 + + + UUID-010 + UUID-011 + + + UUID-012 + UUID-013 + + + UUID-014 + UUID-015 + + + + TestConfig + + + ru + TestConfig + + + + + Version8_3_24 + ManagedApplication + + PlatformApplication + + Russian + + + + + false + false + false + + + + + + + + + + + + + + + + + + + + + + Biometrics + true + + + Location + false + + + BackgroundLocation + false + + + BluetoothPrinters + false + + + WiFiPrinters + false + + + Contacts + false + + + Calendars + false + + + PushNotifications + false + + + LocalNotifications + false + + + InAppPurchases + false + + + PersonalComputerFileExchange + false + + + Ads + false + + + NumberDialing + false + + + CallProcessing + false + + + CallLog + false + + + AutoSendSMS + false + + + ReceiveSMS + false + + + SMSLog + false + + + Camera + false + + + Microphone + false + + + MusicLibrary + false + + + PictureAndVideoLibraries + false + + + AudioPlaybackAndVibration + false + + + BackgroundAudioPlaybackAndVibration + false + + + InstallPackages + false + + + OSBackup + true + + + ApplicationUsageStatistics + false + + + BarcodeScanning + false + + + BackgroundAudioRecording + false + + + AllFilesAccess + false + + + Videoconferences + false + + + NFC + false + + + DocumentScanning + false + + + SpeechToText + false + + + Geofences + false + + + IncomingShareRequests + false + + + AllIncomingShareRequestsTypesProcessing + false + + + + + + Normal + + + Language.Русский + + + + + + Managed + NotAutoFree + DontUse + DontUse + TaxiEnableVersion8_2 + DontUse + Version8_3_24 + + + + Русский + + + \ No newline at end of file diff --git a/tests/skills/cases/cf-edit/snapshots/set-panels/Ext/ClientApplicationInterface.xml b/tests/skills/cases/cf-edit/snapshots/set-panels/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..ec0cb324 --- /dev/null +++ b/tests/skills/cases/cf-edit/snapshots/set-panels/Ext/ClientApplicationInterface.xml @@ -0,0 +1,37 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + UUID-007 + + + + + UUID-009 + + + + + + + UUID-011 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/cf-edit/snapshots/set-panels/Languages/Русский.xml b/tests/skills/cases/cf-edit/snapshots/set-panels/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/cf-edit/snapshots/set-panels/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file