@tyroneross/build-loop 0.30.3
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.
- package/.agents/plugins/marketplace.json +10 -0
- package/.claude-plugin/marketplace.json +38 -0
- package/.claude-plugin/plugin.json +30 -0
- package/.codex-plugin/plugin.json +41 -0
- package/.cursor/rules/build-loop-surface.mdc +22 -0
- package/AGENTS.md +498 -0
- package/LICENSE +202 -0
- package/README.md +403 -0
- package/agents/alignment-checker.md +124 -0
- package/agents/api-assessor.md +136 -0
- package/agents/architecture-scout.md +185 -0
- package/agents/assessment-orchestrator.md +163 -0
- package/agents/build-orchestrator.md +198 -0
- package/agents/database-assessor.md +133 -0
- package/agents/design-contract-specialist.md +239 -0
- package/agents/fact-checker.md +69 -0
- package/agents/fix-critique.md +161 -0
- package/agents/frontend-assessor.md +136 -0
- package/agents/implementer.md +185 -0
- package/agents/independent-auditor.md +143 -0
- package/agents/mock-scanner.md +78 -0
- package/agents/optimize-runner.md +112 -0
- package/agents/overfitting-reviewer.md +96 -0
- package/agents/performance-assessor.md +159 -0
- package/agents/plan-critic.md +85 -0
- package/agents/promotion-reviewer.md +121 -0
- package/agents/recurring-pattern-detector.md +248 -0
- package/agents/retrospective-synthesizer.md +109 -0
- package/agents/root-cause-investigator.md +285 -0
- package/agents/scope-auditor.md +121 -0
- package/agents/security-reviewer.md +151 -0
- package/agents/self-improvement-architect.md +125 -0
- package/agents/synthesis-critic.md +99 -0
- package/agents/transcript-pattern-miner.md +165 -0
- package/agents/ui-validator.md +193 -0
- package/codex-skills/build-loop/SKILL.md +20 -0
- package/codex-skills/debug-loop/SKILL.md +18 -0
- package/codex-skills/handoff/SKILL.md +22 -0
- package/codex-skills/knowledge/SKILL.md +21 -0
- package/codex-skills/optimize/SKILL.md +18 -0
- package/codex-skills/research/SKILL.md +18 -0
- package/commands/agent-rally-point.md +301 -0
- package/commands/assess.md +41 -0
- package/commands/debug.md +20 -0
- package/commands/debugger-detail.md +34 -0
- package/commands/debugger-scan.md +15 -0
- package/commands/debugger-status.md +20 -0
- package/commands/debugger.md +32 -0
- package/commands/handoff.md +113 -0
- package/commands/knowledge-review.md +16 -0
- package/commands/optimize.md +31 -0
- package/commands/plan-verify.md +39 -0
- package/commands/promote-experiment.md +129 -0
- package/commands/research.md +22 -0
- package/commands/run.md +12 -0
- package/commands/self-review.md +51 -0
- package/commands/start-prd.md +57 -0
- package/commands/test.md +34 -0
- package/dist/src/assessment-orchestrator.d.ts +78 -0
- package/dist/src/assessment-orchestrator.d.ts.map +1 -0
- package/dist/src/assessment-orchestrator.js +304 -0
- package/dist/src/assessment-orchestrator.js.map +1 -0
- package/dist/src/audit-miner.d.ts +21 -0
- package/dist/src/audit-miner.d.ts.map +1 -0
- package/dist/src/audit-miner.js +421 -0
- package/dist/src/audit-miner.js.map +1 -0
- package/dist/src/batch-operations.d.ts +30 -0
- package/dist/src/batch-operations.d.ts.map +1 -0
- package/dist/src/batch-operations.js +339 -0
- package/dist/src/batch-operations.js.map +1 -0
- package/dist/src/config.d.ts +83 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +176 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/context-engine.d.ts +45 -0
- package/dist/src/context-engine.d.ts.map +1 -0
- package/dist/src/context-engine.js +135 -0
- package/dist/src/context-engine.js.map +1 -0
- package/dist/src/debug-wrapper.d.ts +58 -0
- package/dist/src/debug-wrapper.d.ts.map +1 -0
- package/dist/src/debug-wrapper.js +285 -0
- package/dist/src/debug-wrapper.js.map +1 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +149 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interactive-verifier.d.ts +26 -0
- package/dist/src/interactive-verifier.d.ts.map +1 -0
- package/dist/src/interactive-verifier.js +435 -0
- package/dist/src/interactive-verifier.js.map +1 -0
- package/dist/src/lessons.d.ts +59 -0
- package/dist/src/lessons.d.ts.map +1 -0
- package/dist/src/lessons.js +172 -0
- package/dist/src/lessons.js.map +1 -0
- package/dist/src/log-reader.d.ts +39 -0
- package/dist/src/log-reader.d.ts.map +1 -0
- package/dist/src/log-reader.js +291 -0
- package/dist/src/log-reader.js.map +1 -0
- package/dist/src/logger.d.ts +59 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +328 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/parallel-retrieval.d.ts +52 -0
- package/dist/src/parallel-retrieval.d.ts.map +1 -0
- package/dist/src/parallel-retrieval.js +309 -0
- package/dist/src/parallel-retrieval.js.map +1 -0
- package/dist/src/pattern-extractor.d.ts +30 -0
- package/dist/src/pattern-extractor.d.ts.map +1 -0
- package/dist/src/pattern-extractor.js +386 -0
- package/dist/src/pattern-extractor.js.map +1 -0
- package/dist/src/result-aggregator.d.ts +44 -0
- package/dist/src/result-aggregator.d.ts.map +1 -0
- package/dist/src/result-aggregator.js +315 -0
- package/dist/src/result-aggregator.js.map +1 -0
- package/dist/src/retrieval.d.ts +139 -0
- package/dist/src/retrieval.d.ts.map +1 -0
- package/dist/src/retrieval.js +724 -0
- package/dist/src/retrieval.js.map +1 -0
- package/dist/src/setup/auto-setup.d.ts +15 -0
- package/dist/src/setup/auto-setup.d.ts.map +1 -0
- package/dist/src/setup/auto-setup.js +157 -0
- package/dist/src/setup/auto-setup.js.map +1 -0
- package/dist/src/setup/configure-hooks.d.ts +8 -0
- package/dist/src/setup/configure-hooks.d.ts.map +1 -0
- package/dist/src/setup/configure-hooks.js +16 -0
- package/dist/src/setup/configure-hooks.js.map +1 -0
- package/dist/src/setup/create-slash-commands.d.ts +8 -0
- package/dist/src/setup/create-slash-commands.d.ts.map +1 -0
- package/dist/src/setup/create-slash-commands.js +101 -0
- package/dist/src/setup/create-slash-commands.js.map +1 -0
- package/dist/src/setup/index.d.ts +8 -0
- package/dist/src/setup/index.d.ts.map +1 -0
- package/dist/src/setup/index.js +17 -0
- package/dist/src/setup/index.js.map +1 -0
- package/dist/src/setup/inject-claude-md.d.ts +10 -0
- package/dist/src/setup/inject-claude-md.d.ts.map +1 -0
- package/dist/src/setup/inject-claude-md.js +150 -0
- package/dist/src/setup/inject-claude-md.js.map +1 -0
- package/dist/src/setup/uninstall.d.ts +22 -0
- package/dist/src/setup/uninstall.d.ts.map +1 -0
- package/dist/src/setup/uninstall.js +235 -0
- package/dist/src/setup/uninstall.js.map +1 -0
- package/dist/src/storage.d.ts +192 -0
- package/dist/src/storage.d.ts.map +1 -0
- package/dist/src/storage.js +1037 -0
- package/dist/src/storage.js.map +1 -0
- package/dist/src/string-similarity.d.ts +8 -0
- package/dist/src/string-similarity.d.ts.map +1 -0
- package/dist/src/string-similarity.js +57 -0
- package/dist/src/string-similarity.js.map +1 -0
- package/dist/src/traces/adapters/browser.d.ts +69 -0
- package/dist/src/traces/adapters/browser.d.ts.map +1 -0
- package/dist/src/traces/adapters/browser.js +280 -0
- package/dist/src/traces/adapters/browser.js.map +1 -0
- package/dist/src/traces/adapters/index.d.ts +23 -0
- package/dist/src/traces/adapters/index.d.ts.map +1 -0
- package/dist/src/traces/adapters/index.js +67 -0
- package/dist/src/traces/adapters/index.js.map +1 -0
- package/dist/src/traces/adapters/langchain.d.ts +42 -0
- package/dist/src/traces/adapters/langchain.d.ts.map +1 -0
- package/dist/src/traces/adapters/langchain.js +151 -0
- package/dist/src/traces/adapters/langchain.js.map +1 -0
- package/dist/src/traces/adapters/opentelemetry.d.ts +46 -0
- package/dist/src/traces/adapters/opentelemetry.d.ts.map +1 -0
- package/dist/src/traces/adapters/opentelemetry.js +164 -0
- package/dist/src/traces/adapters/opentelemetry.js.map +1 -0
- package/dist/src/traces/adapters/sentry.d.ts +58 -0
- package/dist/src/traces/adapters/sentry.d.ts.map +1 -0
- package/dist/src/traces/adapters/sentry.js +212 -0
- package/dist/src/traces/adapters/sentry.js.map +1 -0
- package/dist/src/traces/index.d.ts +10 -0
- package/dist/src/traces/index.d.ts.map +1 -0
- package/dist/src/traces/index.js +45 -0
- package/dist/src/traces/index.js.map +1 -0
- package/dist/src/traces/storage.d.ts +78 -0
- package/dist/src/traces/storage.d.ts.map +1 -0
- package/dist/src/traces/storage.js +399 -0
- package/dist/src/traces/storage.js.map +1 -0
- package/dist/src/traces/summarizer.d.ts +83 -0
- package/dist/src/traces/summarizer.d.ts.map +1 -0
- package/dist/src/traces/summarizer.js +247 -0
- package/dist/src/traces/summarizer.js.map +1 -0
- package/dist/src/traces/types.d.ts +365 -0
- package/dist/src/traces/types.d.ts.map +1 -0
- package/dist/src/traces/types.js +14 -0
- package/dist/src/traces/types.js.map +1 -0
- package/dist/src/types.d.ts +495 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +27 -0
- package/dist/src/types.js.map +1 -0
- package/docs/agent-surface-policy.md +45 -0
- package/docs/memory-setup.md +226 -0
- package/hooks/_arch_scan_bg.py +88 -0
- package/hooks/_plugin_drift_check_bg.py +85 -0
- package/hooks/_session_start_lib.sh +42 -0
- package/hooks/git/pre-push +141 -0
- package/hooks/hooks.json +126 -0
- package/hooks/post-commit +30 -0
- package/hooks/pre-commit +29 -0
- package/hooks/pre-edit-apppulse.sh +6 -0
- package/hooks/pre-edit-architecture.sh +106 -0
- package/hooks/pre-edit-rally-point.sh +92 -0
- package/hooks/session-start-apppulse.sh +6 -0
- package/hooks/session-start-architecture.sh +10 -0
- package/hooks/session-start-git-hooks.sh +12 -0
- package/hooks/session-start-plugin-drift.sh +27 -0
- package/hooks/session-start-rally-point.sh +44 -0
- package/hooks/session-start-retrieval.sh +24 -0
- package/hooks/session-start-worktree-gc.sh +105 -0
- package/hooks/test_apppulse_hooks.sh +6 -0
- package/hooks/test_rally_point_hooks.sh +388 -0
- package/hooks/test_sec008_hooks.sh +66 -0
- package/package.json +111 -0
- package/scripts/README.md +301 -0
- package/scripts/_db_url.py +61 -0
- package/scripts/_paths.py +445 -0
- package/scripts/blm.py +162 -0
- package/scripts/blm_api.py +263 -0
- package/scripts/check_cache_sync.py +490 -0
- package/scripts/embed_backend.py +475 -0
- package/scripts/install_memory.py +556 -0
- package/scripts/lessons_index/__init__.py +77 -0
- package/scripts/lessons_index/__main__.py +135 -0
- package/scripts/lessons_index/ingest.py +288 -0
- package/scripts/lessons_index/query.py +285 -0
- package/scripts/lessons_index/schema.py +114 -0
- package/scripts/memory_context/__init__.py +610 -0
- package/scripts/memory_graph/__init__.py +393 -0
- package/scripts/project_resolver.py +172 -0
- package/scripts/prune_codex_plugin_cache.py +23 -0
- package/scripts/prune_plugin_cache.py +359 -0
- package/scripts/semantic_index/__init__.py +482 -0
- package/scripts/semantic_index/_bench_hybrid.py +175 -0
- package/scripts/semantic_index/backfill.py +263 -0
- package/scripts/semantic_index/hybrid.py +174 -0
- package/scripts/semantic_index/test_backfill.py +292 -0
- package/scripts/semantic_index/test_hybrid.py +541 -0
- package/scripts/sync_navgator_lessons.py +666 -0
- package/scripts/sync_plugin_cache.py +388 -0
- package/skills/agent-rally-point/SKILL.md +165 -0
- package/skills/agent-rally-watcher/SKILL.md +83 -0
- package/skills/api-registry-bridge/SKILL.md +59 -0
- package/skills/architecture/dead/SKILL.md +56 -0
- package/skills/architecture/impact/SKILL.md +88 -0
- package/skills/architecture/review/SKILL.md +227 -0
- package/skills/architecture/rules/SKILL.md +72 -0
- package/skills/architecture/scan/SKILL.md +79 -0
- package/skills/architecture/trace/SKILL.md +65 -0
- package/skills/attribution-standard/SKILL.md +122 -0
- package/skills/authentication/SKILL.md +81 -0
- package/skills/authentication/references/better-auth-magic-link.md +135 -0
- package/skills/authentication/references/better-auth-setup.md +224 -0
- package/skills/authentication/references/google-calendar-sync.md +278 -0
- package/skills/authentication/references/google-cloud-console.md +159 -0
- package/skills/authentication/references/google-geocoding-directions.md +126 -0
- package/skills/authentication/references/google-lessons-example-web-app.md +177 -0
- package/skills/authentication/references/google-maps.md +162 -0
- package/skills/authentication/references/google-oauth-setup.md +343 -0
- package/skills/authentication/references/google-places.md +173 -0
- package/skills/authentication/references/lessons-example-web-app-better-auth.md +126 -0
- package/skills/authentication/references/resend-email.md +227 -0
- package/skills/authentication/references/resend-otp-magic-link.md +153 -0
- package/skills/authentication/references/supabase-auth.md +172 -0
- package/skills/auto-decision-capture/SKILL.md +419 -0
- package/skills/build-loop/SKILL.md +516 -0
- package/skills/build-loop/detect-plugins.mjs +82 -0
- package/skills/build-loop/eval-guide.md +65 -0
- package/skills/build-loop/fallbacks.md +549 -0
- package/skills/build-loop/phases/fact-check.md +42 -0
- package/skills/build-loop/phases/ui-validation.md +267 -0
- package/skills/build-loop/references/apple-native-planning.md +439 -0
- package/skills/build-loop/references/capability-routing.md +181 -0
- package/skills/build-loop/references/codex-subagents.md +98 -0
- package/skills/build-loop/references/coordination.md +161 -0
- package/skills/build-loop/references/correction-aware-capture.md +177 -0
- package/skills/build-loop/references/independent-auditor.md +72 -0
- package/skills/build-loop/references/intent-capability-pack.md +170 -0
- package/skills/build-loop/references/intent-exploration-prompts.md +96 -0
- package/skills/build-loop/references/leadership.md +72 -0
- package/skills/build-loop/references/memory.md +284 -0
- package/skills/build-loop/references/modular-systems-pack.md +96 -0
- package/skills/build-loop/references/output-style.md +129 -0
- package/skills/build-loop/references/pay-it-forward-arch.md +98 -0
- package/skills/build-loop/references/phase-1-assess.md +218 -0
- package/skills/build-loop/references/phase-2-plan.md +77 -0
- package/skills/build-loop/references/phase-3-execute.md +47 -0
- package/skills/build-loop/references/phase-4-review.md +310 -0
- package/skills/build-loop/references/phase-5-iterate.md +71 -0
- package/skills/build-loop/references/phase-6-learn.md +56 -0
- package/skills/build-loop/references/recent-design-structures.md +274 -0
- package/skills/build-loop/references/refactor-history/ASSESSMENT.md +85 -0
- package/skills/build-loop/references/refactor-history/STANDALONE_TEST_RUN.md +149 -0
- package/skills/build-loop/references/refactor-history/scenarios/01-simple-bugfix.md +32 -0
- package/skills/build-loop/references/refactor-history/scenarios/02-ui-build-with-iteration.md +48 -0
- package/skills/build-loop/references/refactor-history/scenarios/03-multi-failure-escalation.md +60 -0
- package/skills/build-loop/references/refactor-history/scenarios/04-ui-build-ibr-absent.md +51 -0
- package/skills/build-loop/references/refactor-history/scenarios/05-refactor-navgator-absent.md +71 -0
- package/skills/build-loop/references/refactor-history/scenarios/06-recurring-bug-debugger-absent.md +52 -0
- package/skills/build-loop/references/refactor-history/trace-comparison.md +202 -0
- package/skills/build-loop/references/self-recursive-dev.md +77 -0
- package/skills/build-loop/references/self-review.md +232 -0
- package/skills/build-loop/references/ui-io-contract.md +116 -0
- package/skills/build-loop/scanners/audit-design-rules.mjs +476 -0
- package/skills/build-loop/scanners/require-visual-evidence.mjs +239 -0
- package/skills/build-loop/templates/backlog-item.md +35 -0
- package/skills/build-loop/templates/codex-worker-prompt.md +89 -0
- package/skills/build-loop/templates/ui-subagent-prompt.md +179 -0
- package/skills/build-loop/templates/ux-fix-plan.md +40 -0
- package/skills/building-with-deepagents/SKILL.md +195 -0
- package/skills/building-with-deepagents/references/anti-patterns.md +141 -0
- package/skills/building-with-deepagents/references/api-quick-reference.md +184 -0
- package/skills/capabilities/SKILL.md +91 -0
- package/skills/debug-loop/SKILL.md +278 -0
- package/skills/debug-loop/references/convergence-rules.md +145 -0
- package/skills/debug-loop/references/swiftui-macos-clunky-ui-debugger.html +823 -0
- package/skills/debugging/assess/SKILL.md +111 -0
- package/skills/debugging/debug-loop/SKILL.md +231 -0
- package/skills/debugging/memory/SKILL.md +147 -0
- package/skills/debugging/store/SKILL.md +153 -0
- package/skills/debugging-memory/SKILL.md +440 -0
- package/skills/debugging-memory/examples/incident-example.json +67 -0
- package/skills/debugging-memory/references/incident-documentation.md +280 -0
- package/skills/debugging-memory/references/ios-notification-alarm-playbook.md +300 -0
- package/skills/debugging-memory/references/pattern-extraction.md +163 -0
- package/skills/debugging-memory/references/subagent-integration.md +312 -0
- package/skills/defenseclaw-bridge/SKILL.md +156 -0
- package/skills/defenseclaw-bridge/references/dc-config-mapping.md +162 -0
- package/skills/defenseclaw-bridge/references/output-format.md +108 -0
- package/skills/handoff/SKILL.md +94 -0
- package/skills/ibr-bridge/SKILL.md +68 -0
- package/skills/knowledge/SKILL.md +127 -0
- package/skills/knowledge/references/recall-integration.md +83 -0
- package/skills/knowledge/templates/madr-minimal.md +56 -0
- package/skills/knowledge-review/SKILL.md +112 -0
- package/skills/logging-tracer/SKILL.md +202 -0
- package/skills/logging-tracer/references/log-analysis.md +128 -0
- package/skills/logging-tracer/references/stack-templates.md +274 -0
- package/skills/logging-tracer-bridge/SKILL.md +70 -0
- package/skills/mcp-builder/SKILL.md +278 -0
- package/skills/mcp-builder/references/mcp-security.md +106 -0
- package/skills/model-tiering/SKILL.md +216 -0
- package/skills/native-ax-driver/SKILL.md +220 -0
- package/skills/native-ax-driver/scripts/layout_fill.py +222 -0
- package/skills/native-ax-driver/scripts/native_driver.py +495 -0
- package/skills/native-ax-driver/swift/bl-ax-driver/Package.swift +20 -0
- package/skills/native-ax-driver/swift/bl-ax-driver/Sources/main.swift +542 -0
- package/skills/optimize/SKILL.md +202 -0
- package/skills/optimize/profiles.md +61 -0
- package/skills/plan-verify/SKILL.md +115 -0
- package/skills/plan-verify/test-fixtures/example-app-v20-findings.json +27 -0
- package/skills/plan-verify/test-fixtures/example-app-v20.md +42 -0
- package/skills/plan-verify/test-fixtures/example-app-v22-findings.json +15 -0
- package/skills/plan-verify/test-fixtures/example-app-v22.md +1608 -0
- package/skills/plan-verify/test-fixtures/unrelated-good-plan-findings.json +15 -0
- package/skills/plan-verify/test-fixtures/unrelated-good-plan.md +55 -0
- package/skills/plugin-builder/SKILL.md +329 -0
- package/skills/plugin-builder/references/authoritative-sources.md +104 -0
- package/skills/plugin-builder/references/build-loop-phase-guidance.md +94 -0
- package/skills/plugin-builder/references/components-guide.md +221 -0
- package/skills/plugin-builder/references/distribution.md +317 -0
- package/skills/plugin-builder/references/dual-host-claude-codex.md +220 -0
- package/skills/plugin-builder/references/hooks-reference.md +210 -0
- package/skills/plugin-builder/references/manifest-schema.md +111 -0
- package/skills/plugin-builder/references/plugin-hygiene-lessons.md +307 -0
- package/skills/plugin-tests/SKILL.md +94 -0
- package/skills/prd-bridge/SKILL.md +135 -0
- package/skills/research/SKILL.md +80 -0
- package/skills/security-methodology/SKILL.md +105 -0
- package/skills/security-methodology/references/cross-source-matrix.md +77 -0
- package/skills/security-methodology/references/mitre-atlas-starter.md +83 -0
- package/skills/security-methodology/references/nist-600-1-mapping.md +119 -0
- package/skills/security-methodology/references/owasp-agentic-top-10.md +157 -0
- package/skills/security-methodology/references/owasp-llm-top-10.md +144 -0
- package/skills/security-methodology/references/owasp-web-top-10.md +65 -0
- package/skills/self-improve/SKILL.md +298 -0
- package/skills/spec-writing/SKILL.md +548 -0
- package/skills/spec-writing/scripts/check_checklist.py +478 -0
- package/skills/sync-skills/SKILL.md +132 -0
- package/skills/telemetry/SKILL.md +66 -0
- package/skills/ui-design/SKILL.md +75 -0
- package/skills/ui-design/references/design-patterns-multi.md +165 -0
- package/skills/ui-design/references/design-preferences-from-owned-apps.md +150 -0
- package/skills/ui-design/references/evidence-capture-policy.md +55 -0
- package/skills/ui-design/references/ui-guidance-sources.md +330 -0
- package/skills/ui-design/references/universal-design-principles.alt.md +182 -0
- package/skills/ui-design/references/universal-design-principles.md +419 -0
- package/templates/memory/MEMORY.md.template +55 -0
- package/templates/memory/constitution.md.template +64 -0
- package/templates/memory/manifest.json +47 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com>
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
// Build-loop native AX driver
|
|
4
|
+
//
|
|
5
|
+
// Reads a running macOS app's accessibility tree and dispatches AX actions
|
|
6
|
+
// (AXPress, AXShowMenu, AXSetValue, AXFocus, AXIncrement, AXDecrement,
|
|
7
|
+
// AXConfirm, AXCancel, AXScrollToVisible) on individual elements.
|
|
8
|
+
//
|
|
9
|
+
// Provenance: ported from interface-built-right's ibr-ax-extract
|
|
10
|
+
// (src/native/swift/ibr-ax-extract/Sources/main.swift). Kept binary-compatible
|
|
11
|
+
// with that tool's output schema so existing parsers work unchanged. Build-loop
|
|
12
|
+
// vendors its own copy so this capability is built in, not bridged.
|
|
13
|
+
//
|
|
14
|
+
// All actions go through AXUIElement APIs. The driver does NOT post CGEvent
|
|
15
|
+
// mouse/keyboard events — the user's hardware cursor is never moved.
|
|
16
|
+
|
|
17
|
+
import AppKit
|
|
18
|
+
import Foundation
|
|
19
|
+
|
|
20
|
+
// MARK: - Element Model
|
|
21
|
+
|
|
22
|
+
struct AXExtractedElement: Codable {
|
|
23
|
+
let role: String
|
|
24
|
+
let subrole: String?
|
|
25
|
+
let title: String?
|
|
26
|
+
let description: String?
|
|
27
|
+
let identifier: String?
|
|
28
|
+
let value: String?
|
|
29
|
+
let enabled: Bool
|
|
30
|
+
let focused: Bool
|
|
31
|
+
let actions: [String]
|
|
32
|
+
let position: Position?
|
|
33
|
+
let size: Size?
|
|
34
|
+
let children: [AXExtractedElement]
|
|
35
|
+
let path: [Int] // Index path from app root: [0, 2, 1] = 1st child → 3rd child → 2nd child
|
|
36
|
+
|
|
37
|
+
struct Position: Codable {
|
|
38
|
+
let x: Double
|
|
39
|
+
let y: Double
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
struct Size: Codable {
|
|
43
|
+
let width: Double
|
|
44
|
+
let height: Double
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Legacy output format for simulator scanning (backwards compatible)
|
|
49
|
+
struct LegacyElement: Codable {
|
|
50
|
+
let identifier: String
|
|
51
|
+
let label: String
|
|
52
|
+
let role: String
|
|
53
|
+
let traits: [String]
|
|
54
|
+
let frame: Frame
|
|
55
|
+
let isEnabled: Bool
|
|
56
|
+
let value: String?
|
|
57
|
+
let path: [Int]
|
|
58
|
+
let children: [LegacyElement]
|
|
59
|
+
|
|
60
|
+
struct Frame: Codable {
|
|
61
|
+
let x: Double
|
|
62
|
+
let y: Double
|
|
63
|
+
let width: Double
|
|
64
|
+
let height: Double
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// MARK: - AX Attribute Helpers
|
|
69
|
+
|
|
70
|
+
func getStringAttribute(_ element: AXUIElement, _ attribute: String) -> String? {
|
|
71
|
+
var value: AnyObject?
|
|
72
|
+
let result = AXUIElementCopyAttributeValue(element, attribute as CFString, &value)
|
|
73
|
+
guard result == .success, let str = value as? String else { return nil }
|
|
74
|
+
return str
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
func getBoolAttribute(_ element: AXUIElement, _ attribute: String) -> Bool {
|
|
78
|
+
var value: AnyObject?
|
|
79
|
+
let result = AXUIElementCopyAttributeValue(element, attribute as CFString, &value)
|
|
80
|
+
guard result == .success else { return false }
|
|
81
|
+
if let num = value as? NSNumber { return num.boolValue }
|
|
82
|
+
return false
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func getPosition(_ element: AXUIElement) -> AXExtractedElement.Position? {
|
|
86
|
+
var posValue: AnyObject?
|
|
87
|
+
let result = AXUIElementCopyAttributeValue(element, kAXPositionAttribute as CFString, &posValue)
|
|
88
|
+
guard result == .success, let val = posValue else { return nil }
|
|
89
|
+
var position = CGPoint.zero
|
|
90
|
+
AXValueGetValue(val as! AXValue, .cgPoint, &position)
|
|
91
|
+
return AXExtractedElement.Position(x: Double(position.x), y: Double(position.y))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
func getSize(_ element: AXUIElement) -> AXExtractedElement.Size? {
|
|
95
|
+
var sizeValue: AnyObject?
|
|
96
|
+
let result = AXUIElementCopyAttributeValue(element, kAXSizeAttribute as CFString, &sizeValue)
|
|
97
|
+
guard result == .success, let val = sizeValue else { return nil }
|
|
98
|
+
var size = CGSize.zero
|
|
99
|
+
AXValueGetValue(val as! AXValue, .cgSize, &size)
|
|
100
|
+
return AXExtractedElement.Size(width: Double(size.width), height: Double(size.height))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
func getActions(_ element: AXUIElement) -> [String] {
|
|
104
|
+
var actionsValue: CFArray?
|
|
105
|
+
let result = AXUIElementCopyActionNames(element, &actionsValue)
|
|
106
|
+
guard result == .success, let actions = actionsValue as? [String] else { return [] }
|
|
107
|
+
return actions
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
func getChildren(_ element: AXUIElement) -> [AXUIElement] {
|
|
111
|
+
var value: AnyObject?
|
|
112
|
+
let result = AXUIElementCopyAttributeValue(element, kAXChildrenAttribute as CFString, &value)
|
|
113
|
+
guard result == .success, let children = value as? [AXUIElement] else { return [] }
|
|
114
|
+
return children
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
func getFrame(_ element: AXUIElement) -> LegacyElement.Frame {
|
|
118
|
+
let pos = getPosition(element)
|
|
119
|
+
let sz = getSize(element)
|
|
120
|
+
return LegacyElement.Frame(
|
|
121
|
+
x: pos?.x ?? 0, y: pos?.y ?? 0,
|
|
122
|
+
width: sz?.width ?? 0, height: sz?.height ?? 0
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// MARK: - AX Tree Walkers
|
|
127
|
+
|
|
128
|
+
/// Walk element tree — full AX attributes, tracking index path
|
|
129
|
+
func walkElementFull(_ element: AXUIElement, depth: Int = 0, maxDepth: Int = 15, currentPath: [Int] = []) -> AXExtractedElement? {
|
|
130
|
+
guard depth < maxDepth else { return nil }
|
|
131
|
+
|
|
132
|
+
let role = getStringAttribute(element, kAXRoleAttribute) ?? "AXUnknown"
|
|
133
|
+
let subrole = getStringAttribute(element, kAXSubroleAttribute)
|
|
134
|
+
let title = getStringAttribute(element, kAXTitleAttribute)
|
|
135
|
+
let descriptionAttr = getStringAttribute(element, kAXDescriptionAttribute)
|
|
136
|
+
let identifier = getStringAttribute(element, "AXIdentifier")
|
|
137
|
+
let value = getStringAttribute(element, kAXValueAttribute as String)
|
|
138
|
+
let enabled = getBoolAttribute(element, kAXEnabledAttribute)
|
|
139
|
+
let focused = getBoolAttribute(element, kAXFocusedAttribute)
|
|
140
|
+
let position = getPosition(element)
|
|
141
|
+
let size = getSize(element)
|
|
142
|
+
let actions = getActions(element)
|
|
143
|
+
|
|
144
|
+
if let sz = size, sz.width <= 0 && sz.height <= 0 && depth > 2 {
|
|
145
|
+
return nil
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let axChildren = getChildren(element)
|
|
149
|
+
var childElements: [AXExtractedElement] = []
|
|
150
|
+
for (index, child) in axChildren.enumerated() {
|
|
151
|
+
let childPath = currentPath + [index]
|
|
152
|
+
if let childEl = walkElementFull(child, depth: depth + 1, maxDepth: maxDepth, currentPath: childPath) {
|
|
153
|
+
childElements.append(childEl)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return AXExtractedElement(
|
|
158
|
+
role: role,
|
|
159
|
+
subrole: subrole,
|
|
160
|
+
title: title,
|
|
161
|
+
description: descriptionAttr,
|
|
162
|
+
identifier: identifier,
|
|
163
|
+
value: value,
|
|
164
|
+
enabled: enabled,
|
|
165
|
+
focused: focused,
|
|
166
|
+
actions: actions,
|
|
167
|
+
position: position,
|
|
168
|
+
size: size,
|
|
169
|
+
children: childElements,
|
|
170
|
+
path: currentPath
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// Walk element tree — legacy format for simulator compatibility
|
|
175
|
+
func walkElementLegacy(_ element: AXUIElement, depth: Int = 0, maxDepth: Int = 15, currentPath: [Int] = []) -> LegacyElement? {
|
|
176
|
+
guard depth < maxDepth else { return nil }
|
|
177
|
+
|
|
178
|
+
let role = getStringAttribute(element, kAXRoleAttribute) ?? "AXUnknown"
|
|
179
|
+
let label = getStringAttribute(element, kAXTitleAttribute)
|
|
180
|
+
?? getStringAttribute(element, kAXDescriptionAttribute)
|
|
181
|
+
?? getStringAttribute(element, kAXValueAttribute as String)
|
|
182
|
+
?? ""
|
|
183
|
+
let identifier = getStringAttribute(element, "AXIdentifier") ?? ""
|
|
184
|
+
let isEnabled = getBoolAttribute(element, kAXEnabledAttribute)
|
|
185
|
+
let value = getStringAttribute(element, kAXValueAttribute as String)
|
|
186
|
+
let frame = getFrame(element)
|
|
187
|
+
|
|
188
|
+
if frame.width <= 0 && frame.height <= 0 && depth > 2 {
|
|
189
|
+
return nil
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let axChildren = getChildren(element)
|
|
193
|
+
var childElements: [LegacyElement] = []
|
|
194
|
+
for (index, child) in axChildren.enumerated() {
|
|
195
|
+
let childPath = currentPath + [index]
|
|
196
|
+
if let childEl = walkElementLegacy(child, depth: depth + 1, maxDepth: maxDepth, currentPath: childPath) {
|
|
197
|
+
childElements.append(childEl)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
var traits: [String] = []
|
|
202
|
+
if role == "AXButton" { traits.append("button") }
|
|
203
|
+
if role == "AXLink" { traits.append("link") }
|
|
204
|
+
if role == "AXStaticText" { traits.append("staticText") }
|
|
205
|
+
if role == "AXImage" { traits.append("image") }
|
|
206
|
+
if !isEnabled { traits.append("disabled") }
|
|
207
|
+
|
|
208
|
+
return LegacyElement(
|
|
209
|
+
identifier: identifier,
|
|
210
|
+
label: label,
|
|
211
|
+
role: role,
|
|
212
|
+
traits: traits,
|
|
213
|
+
frame: frame,
|
|
214
|
+
isEnabled: isEnabled,
|
|
215
|
+
value: value,
|
|
216
|
+
path: currentPath,
|
|
217
|
+
children: childElements
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// MARK: - App Finders
|
|
222
|
+
|
|
223
|
+
func findSimulatorApp() -> NSRunningApplication? {
|
|
224
|
+
let apps = NSWorkspace.shared.runningApplications
|
|
225
|
+
return apps.first { $0.bundleIdentifier == "com.apple.iphonesimulator" }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
func findSimulatorWindow(app: NSRunningApplication, deviceName: String?) -> AXUIElement? {
|
|
229
|
+
let appElement = AXUIElementCreateApplication(app.processIdentifier)
|
|
230
|
+
|
|
231
|
+
var windowsValue: AnyObject?
|
|
232
|
+
let result = AXUIElementCopyAttributeValue(appElement, kAXWindowsAttribute as CFString, &windowsValue)
|
|
233
|
+
guard result == .success, let windows = windowsValue as? [AXUIElement] else { return nil }
|
|
234
|
+
|
|
235
|
+
if let deviceName = deviceName {
|
|
236
|
+
for window in windows {
|
|
237
|
+
let title = getStringAttribute(window, kAXTitleAttribute) ?? ""
|
|
238
|
+
if title.localizedCaseInsensitiveContains(deviceName) {
|
|
239
|
+
return window
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return windows.first
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// Find a running app by name (case-insensitive substring match)
|
|
248
|
+
func findAppByName(_ name: String) -> NSRunningApplication? {
|
|
249
|
+
let apps = NSWorkspace.shared.runningApplications
|
|
250
|
+
let lowered = name.lowercased()
|
|
251
|
+
return apps.first { app in
|
|
252
|
+
let appName = app.localizedName?.lowercased() ?? ""
|
|
253
|
+
let bundleId = app.bundleIdentifier?.lowercased() ?? ""
|
|
254
|
+
return appName.contains(lowered) || bundleId.contains(lowered)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
func findAppByPid(_ pid: pid_t) -> NSRunningApplication? {
|
|
259
|
+
return NSWorkspace.shared.runningApplications.first { $0.processIdentifier == pid }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/// Find the frontmost/main window for a given app
|
|
263
|
+
func findMainWindow(pid: pid_t) -> (window: AXUIElement, id: CGWindowID, title: String, size: CGSize)? {
|
|
264
|
+
let appElement = AXUIElementCreateApplication(pid)
|
|
265
|
+
|
|
266
|
+
var windowsValue: AnyObject?
|
|
267
|
+
let result = AXUIElementCopyAttributeValue(appElement, kAXWindowsAttribute as CFString, &windowsValue)
|
|
268
|
+
guard result == .success, let windows = windowsValue as? [AXUIElement], !windows.isEmpty else {
|
|
269
|
+
return nil
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let window: AXUIElement
|
|
273
|
+
var mainWindow: AnyObject?
|
|
274
|
+
let mainResult = AXUIElementCopyAttributeValue(appElement, kAXMainWindowAttribute as CFString, &mainWindow)
|
|
275
|
+
if mainResult == .success, let mw = mainWindow {
|
|
276
|
+
window = (mw as! AXUIElement)
|
|
277
|
+
} else {
|
|
278
|
+
window = windows[0]
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let title = getStringAttribute(window, kAXTitleAttribute) ?? "Untitled"
|
|
282
|
+
let sz = getSize(window) ?? AXExtractedElement.Size(width: 800, height: 600)
|
|
283
|
+
|
|
284
|
+
var windowId: CGWindowID = 0
|
|
285
|
+
let windowList = CGWindowListCopyWindowInfo([.optionOnScreenOnly, .excludeDesktopElements], kCGNullWindowID) as? [[String: Any]] ?? []
|
|
286
|
+
for info in windowList {
|
|
287
|
+
guard let ownerPid = info[kCGWindowOwnerPID as String] as? Int32,
|
|
288
|
+
let wid = info[kCGWindowNumber as String] as? CGWindowID,
|
|
289
|
+
ownerPid == pid else { continue }
|
|
290
|
+
let wName = info[kCGWindowName as String] as? String ?? ""
|
|
291
|
+
if wName == title || windowId == 0 {
|
|
292
|
+
windowId = wid
|
|
293
|
+
if wName == title { break }
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return (window: window, id: windowId, title: title,
|
|
298
|
+
size: CGSize(width: sz.width, height: sz.height))
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// MARK: - Action Execution
|
|
302
|
+
|
|
303
|
+
/// Traverse the AX tree by numeric index path from the app root
|
|
304
|
+
func navigateToElement(root: AXUIElement, path: [Int]) -> AXUIElement? {
|
|
305
|
+
var current = root
|
|
306
|
+
for index in path {
|
|
307
|
+
var childrenRef: CFTypeRef?
|
|
308
|
+
guard AXUIElementCopyAttributeValue(current, kAXChildrenAttribute as CFString, &childrenRef) == .success,
|
|
309
|
+
let children = childrenRef as? [AXUIElement],
|
|
310
|
+
index < children.count else { return nil }
|
|
311
|
+
current = children[index]
|
|
312
|
+
}
|
|
313
|
+
return current
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/// Execute an accessibility action on the element at the given path
|
|
317
|
+
func performAction(pid: pid_t, deviceName: String?, elementPath: [Int], action: String, value: String?) -> (Bool, String?) {
|
|
318
|
+
let root: AXUIElement?
|
|
319
|
+
if let app = findAppByPid(pid),
|
|
320
|
+
app.bundleIdentifier == "com.apple.iphonesimulator",
|
|
321
|
+
let simulatorWindow = findSimulatorWindow(app: app, deviceName: deviceName) {
|
|
322
|
+
root = simulatorWindow
|
|
323
|
+
} else {
|
|
324
|
+
root = findMainWindow(pid: pid)?.window
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
guard let actionRoot = root else {
|
|
328
|
+
return (false, "No action root window found for pid \(pid)")
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
guard let element = navigateToElement(root: actionRoot, path: elementPath) else {
|
|
332
|
+
return (false, "Element not found at path")
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
switch action {
|
|
336
|
+
case "press":
|
|
337
|
+
let ok = AXUIElementPerformAction(element, kAXPressAction as CFString) == .success
|
|
338
|
+
return (ok, ok ? nil : "AXPress failed")
|
|
339
|
+
case "setValue":
|
|
340
|
+
guard let value = value else {
|
|
341
|
+
return (false, "setValue requires --value")
|
|
342
|
+
}
|
|
343
|
+
let ok = AXUIElementSetAttributeValue(element, kAXValueAttribute as CFString, value as CFTypeRef) == .success
|
|
344
|
+
return (ok, ok ? nil : "AXSetValue failed")
|
|
345
|
+
case "increment":
|
|
346
|
+
let ok = AXUIElementPerformAction(element, kAXIncrementAction as CFString) == .success
|
|
347
|
+
return (ok, ok ? nil : "AXIncrement failed")
|
|
348
|
+
case "decrement":
|
|
349
|
+
let ok = AXUIElementPerformAction(element, kAXDecrementAction as CFString) == .success
|
|
350
|
+
return (ok, ok ? nil : "AXDecrement failed")
|
|
351
|
+
case "showMenu":
|
|
352
|
+
let ok = AXUIElementPerformAction(element, kAXShowMenuAction as CFString) == .success
|
|
353
|
+
return (ok, ok ? nil : "AXShowMenu failed")
|
|
354
|
+
case "confirm":
|
|
355
|
+
let ok = AXUIElementPerformAction(element, kAXConfirmAction as CFString) == .success
|
|
356
|
+
return (ok, ok ? nil : "AXConfirm failed")
|
|
357
|
+
case "cancel":
|
|
358
|
+
let ok = AXUIElementPerformAction(element, kAXCancelAction as CFString) == .success
|
|
359
|
+
return (ok, ok ? nil : "AXCancel failed")
|
|
360
|
+
case "focus":
|
|
361
|
+
let ok = AXUIElementSetAttributeValue(element, kAXFocusedAttribute as CFString, true as CFTypeRef) == .success
|
|
362
|
+
return (ok, ok ? nil : "AXFocus failed")
|
|
363
|
+
case "scrollToVisible":
|
|
364
|
+
let ok = AXUIElementPerformAction(element, "AXScrollToVisible" as CFString) == .success
|
|
365
|
+
return (ok, ok ? nil : "AXScrollToVisible failed")
|
|
366
|
+
default:
|
|
367
|
+
return (false, "Unknown action: \(action)")
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// MARK: - Main
|
|
372
|
+
|
|
373
|
+
let args = CommandLine.arguments
|
|
374
|
+
|
|
375
|
+
var deviceName: String? = nil
|
|
376
|
+
var targetPid: pid_t? = nil
|
|
377
|
+
var targetApp: String? = nil
|
|
378
|
+
var resolveApp: String? = nil
|
|
379
|
+
var outputMode: String = "auto"
|
|
380
|
+
var actionName: String? = nil
|
|
381
|
+
var elementPathStr: String? = nil
|
|
382
|
+
var actionValue: String? = nil
|
|
383
|
+
|
|
384
|
+
var i = 1
|
|
385
|
+
while i < args.count {
|
|
386
|
+
switch args[i] {
|
|
387
|
+
case "--device-name":
|
|
388
|
+
i += 1
|
|
389
|
+
if i < args.count { deviceName = args[i] }
|
|
390
|
+
case "--pid":
|
|
391
|
+
i += 1
|
|
392
|
+
if i < args.count { targetPid = pid_t(args[i]) }
|
|
393
|
+
case "--app":
|
|
394
|
+
i += 1
|
|
395
|
+
if i < args.count { targetApp = args[i] }
|
|
396
|
+
case "--resolve-app":
|
|
397
|
+
i += 1
|
|
398
|
+
if i < args.count { resolveApp = args[i] }
|
|
399
|
+
case "--format":
|
|
400
|
+
i += 1
|
|
401
|
+
if i < args.count { outputMode = args[i] }
|
|
402
|
+
case "--action":
|
|
403
|
+
i += 1
|
|
404
|
+
if i < args.count { actionName = args[i] }
|
|
405
|
+
case "--element-path":
|
|
406
|
+
i += 1
|
|
407
|
+
if i < args.count { elementPathStr = args[i] } // comma-separated: "0,2,1"
|
|
408
|
+
case "--value":
|
|
409
|
+
i += 1
|
|
410
|
+
if i < args.count { actionValue = args[i] }
|
|
411
|
+
default:
|
|
412
|
+
break
|
|
413
|
+
}
|
|
414
|
+
i += 1
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// --- Mode: Resolve a running macOS app without AX permission ---
|
|
418
|
+
if let appName = resolveApp {
|
|
419
|
+
guard let app = findAppByName(appName) else {
|
|
420
|
+
fputs("Error: No running app found matching \"\(appName)\"\n", stderr)
|
|
421
|
+
exit(1)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
let payload: [String: Any] = [
|
|
425
|
+
"pid": Int(app.processIdentifier),
|
|
426
|
+
"name": app.localizedName ?? "",
|
|
427
|
+
"bundleIdentifier": app.bundleIdentifier ?? ""
|
|
428
|
+
]
|
|
429
|
+
if let data = try? JSONSerialization.data(withJSONObject: payload),
|
|
430
|
+
let str = String(data: data, encoding: .utf8) {
|
|
431
|
+
print(str)
|
|
432
|
+
exit(0)
|
|
433
|
+
}
|
|
434
|
+
fputs("Error: Failed to encode app resolution JSON\n", stderr)
|
|
435
|
+
exit(1)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Check accessibility permission
|
|
439
|
+
let checkOpts = [kAXTrustedCheckOptionPrompt.takeUnretainedValue(): true] as CFDictionary
|
|
440
|
+
guard AXIsProcessTrustedWithOptions(checkOpts) else {
|
|
441
|
+
fputs("Error: Accessibility permission required. Grant access in System Settings > Privacy & Security > Accessibility\n", stderr)
|
|
442
|
+
exit(1)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
let encoder = JSONEncoder()
|
|
446
|
+
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
|
|
447
|
+
|
|
448
|
+
// --- Mode: Action execution (--action --pid --element-path) ---
|
|
449
|
+
if let actionName = actionName, let pathStr = elementPathStr {
|
|
450
|
+
guard let pid = targetPid else {
|
|
451
|
+
fputs("{\"error\":\"--pid is required for action mode\"}\n", stderr)
|
|
452
|
+
exit(1)
|
|
453
|
+
}
|
|
454
|
+
let path = pathStr.split(separator: ",").compactMap { Int($0) }
|
|
455
|
+
let (success, error) = performAction(pid: pid, deviceName: deviceName, elementPath: path, action: actionName, value: actionValue)
|
|
456
|
+
var result: [String: Any] = ["success": success, "action": actionName]
|
|
457
|
+
if let error = error {
|
|
458
|
+
result["error"] = error
|
|
459
|
+
}
|
|
460
|
+
if let data = try? JSONSerialization.data(withJSONObject: result),
|
|
461
|
+
let str = String(data: data, encoding: .utf8) {
|
|
462
|
+
print(str)
|
|
463
|
+
}
|
|
464
|
+
exit(success ? 0 : 1)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// --- Mode 1: macOS app scanning (--pid or --app) ---
|
|
468
|
+
if targetPid != nil || targetApp != nil {
|
|
469
|
+
let pid: pid_t
|
|
470
|
+
if let p = targetPid {
|
|
471
|
+
pid = p
|
|
472
|
+
} else if let appName = targetApp {
|
|
473
|
+
guard let app = findAppByName(appName) else {
|
|
474
|
+
fputs("Error: No running app found matching \"\(appName)\"\n", stderr)
|
|
475
|
+
fputs("Running apps:\n", stderr)
|
|
476
|
+
for app in NSWorkspace.shared.runningApplications where app.activationPolicy == .regular {
|
|
477
|
+
let name = app.localizedName ?? "Unknown"
|
|
478
|
+
let bundle = app.bundleIdentifier ?? "unknown"
|
|
479
|
+
fputs(" \(name) (\(bundle)) pid=\(app.processIdentifier)\n", stderr)
|
|
480
|
+
}
|
|
481
|
+
exit(1)
|
|
482
|
+
}
|
|
483
|
+
pid = app.processIdentifier
|
|
484
|
+
} else {
|
|
485
|
+
fputs("Error: No target specified\n", stderr)
|
|
486
|
+
exit(1)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
guard let windowInfo = findMainWindow(pid: pid) else {
|
|
490
|
+
fputs("Error: No windows found for pid \(pid)\n", stderr)
|
|
491
|
+
exit(1)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
let widthInt = Int(windowInfo.size.width)
|
|
495
|
+
let heightInt = Int(windowInfo.size.height)
|
|
496
|
+
print("WINDOW:\(windowInfo.id):\(widthInt)x\(heightInt):\(windowInfo.title)")
|
|
497
|
+
|
|
498
|
+
guard let rootElement = walkElementFull(windowInfo.window, currentPath: []) else {
|
|
499
|
+
fputs("Error: Failed to walk accessibility tree\n", stderr)
|
|
500
|
+
exit(1)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
let output = rootElement.children
|
|
504
|
+
let data = try encoder.encode(output)
|
|
505
|
+
if let json = String(data: data, encoding: .utf8) {
|
|
506
|
+
print(json)
|
|
507
|
+
} else {
|
|
508
|
+
fputs("Error: Failed to encode JSON\n", stderr)
|
|
509
|
+
exit(1)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
exit(0)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// --- Mode 2: Simulator scanning (--device-name or default) ---
|
|
516
|
+
guard let simApp = findSimulatorApp() else {
|
|
517
|
+
fputs("Error: Simulator.app is not running\n", stderr)
|
|
518
|
+
exit(1)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
guard let window = findSimulatorWindow(app: simApp, deviceName: deviceName) else {
|
|
522
|
+
let msg = deviceName != nil
|
|
523
|
+
? "Error: No simulator window found for device: \(deviceName!)"
|
|
524
|
+
: "Error: No simulator windows found"
|
|
525
|
+
fputs("\(msg)\n", stderr)
|
|
526
|
+
exit(1)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
guard let rootElement = walkElementLegacy(window, currentPath: []) else {
|
|
530
|
+
fputs("Error: Failed to walk accessibility tree\n", stderr)
|
|
531
|
+
exit(1)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
let output = rootElement.children
|
|
535
|
+
let data = try encoder.encode(output)
|
|
536
|
+
|
|
537
|
+
if let json = String(data: data, encoding: .utf8) {
|
|
538
|
+
print(json)
|
|
539
|
+
} else {
|
|
540
|
+
fputs("Error: Failed to encode JSON\n", stderr)
|
|
541
|
+
exit(1)
|
|
542
|
+
}
|