rubik 0.1.0
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.
- checksums.yaml +7 -0
- data/ATTRIBUTION.md +139 -0
- data/CHANGELOG.md +236 -0
- data/LICENSE +21 -0
- data/README.md +269 -0
- data/bin/rubik +82 -0
- data/lib/rubik/adapter/base.rb +195 -0
- data/lib/rubik/adapter/c.rb +36 -0
- data/lib/rubik/adapter/crystal.rb +78 -0
- data/lib/rubik/adapter/generic.rb +86 -0
- data/lib/rubik/adapter/go.rb +36 -0
- data/lib/rubik/adapter/node.rb +146 -0
- data/lib/rubik/adapter/process.rb +78 -0
- data/lib/rubik/adapter/python.rb +30 -0
- data/lib/rubik/adapter/registry.rb +81 -0
- data/lib/rubik/adapter/ruby.rb +30 -0
- data/lib/rubik/adapter/rust.rb +36 -0
- data/lib/rubik/adapter/zig.rb +36 -0
- data/lib/rubik/adapter.rb +18 -0
- data/lib/rubik/ast/body_loader.rb +19 -0
- data/lib/rubik/ast/c_extractor.rb +114 -0
- data/lib/rubik/ast/cache_store.rb +191 -0
- data/lib/rubik/ast/extractor.rb +59 -0
- data/lib/rubik/ast/fallback_cleaner.rb +145 -0
- data/lib/rubik/ast/go_extractor.rb +148 -0
- data/lib/rubik/ast/graph.rb +272 -0
- data/lib/rubik/ast/python_extractor.rb +120 -0
- data/lib/rubik/ast/ruby_extractor.rb +260 -0
- data/lib/rubik/ast/rust_extractor.rb +127 -0
- data/lib/rubik/ast/symbol.rb +103 -0
- data/lib/rubik/ast/treesitter/base_walker.rb +109 -0
- data/lib/rubik/ast/treesitter/c_walker.rb +122 -0
- data/lib/rubik/ast/treesitter/go_walker.rb +120 -0
- data/lib/rubik/ast/treesitter/python_walker.rb +107 -0
- data/lib/rubik/ast/treesitter/rust_walker.rb +130 -0
- data/lib/rubik/ast/treesitter/typescript_walker.rb +182 -0
- data/lib/rubik/ast/treesitter/zig_walker.rb +109 -0
- data/lib/rubik/ast/treesitter_extractor.rb +93 -0
- data/lib/rubik/ast/typescript_extractor.rb +283 -0
- data/lib/rubik/ast/zig_extractor.rb +116 -0
- data/lib/rubik/ast.rb +13 -0
- data/lib/rubik/benchmark/runner.rb +224 -0
- data/lib/rubik/benchmark/synth_repo.rb +59 -0
- data/lib/rubik/benchmark.rb +9 -0
- data/lib/rubik/buffer/diff.rb +43 -0
- data/lib/rubik/buffer/manager.rb +427 -0
- data/lib/rubik/buffer/path_validator.rb +60 -0
- data/lib/rubik/buffer/snapshot.rb +99 -0
- data/lib/rubik/buffer.rb +10 -0
- data/lib/rubik/coeffects.rb +129 -0
- data/lib/rubik/concurrency/boundary.rb +74 -0
- data/lib/rubik/concurrency/dns_pool.rb +68 -0
- data/lib/rubik/concurrency/errors.rb +17 -0
- data/lib/rubik/concurrency/event_loop.rb +441 -0
- data/lib/rubik/concurrency/join.rb +55 -0
- data/lib/rubik/concurrency/process_waiter.rb +55 -0
- data/lib/rubik/concurrency/scheduler.rb +84 -0
- data/lib/rubik/concurrency/timers.rb +109 -0
- data/lib/rubik/concurrency.rb +132 -0
- data/lib/rubik/config/deep_ops.rb +80 -0
- data/lib/rubik/config/defaults.rb +248 -0
- data/lib/rubik/config/model_catalog.rb +93 -0
- data/lib/rubik/config/store.rb +155 -0
- data/lib/rubik/config.rb +216 -0
- data/lib/rubik/context/git_info.rb +84 -0
- data/lib/rubik/context/git_lister.rb +37 -0
- data/lib/rubik/context/index.rb +134 -0
- data/lib/rubik/context/repo_context_reader.rb +121 -0
- data/lib/rubik/context/scanner.rb +439 -0
- data/lib/rubik/context.rb +11 -0
- data/lib/rubik/diagnostics/collector.rb +200 -0
- data/lib/rubik/diagnostics/entry.rb +60 -0
- data/lib/rubik/diagnostics/parser.rb +107 -0
- data/lib/rubik/diagnostics.rb +10 -0
- data/lib/rubik/event_bus.rb +56 -0
- data/lib/rubik/kernel/dispatcher.rb +171 -0
- data/lib/rubik/kernel/dmesg.rb +116 -0
- data/lib/rubik/kernel/init.rb +177 -0
- data/lib/rubik/kernel/record.rb +85 -0
- data/lib/rubik/kernel/unit.rb +160 -0
- data/lib/rubik/llm/anthropic.rb +202 -0
- data/lib/rubik/llm/chunk_assembler.rb +285 -0
- data/lib/rubik/llm/client.rb +526 -0
- data/lib/rubik/llm/formatter.rb +450 -0
- data/lib/rubik/llm/message_sanitizer.rb +87 -0
- data/lib/rubik/llm/ollama.rb +167 -0
- data/lib/rubik/llm/openai.rb +21 -0
- data/lib/rubik/llm/openai_compatible.rb +216 -0
- data/lib/rubik/llm/provider.rb +125 -0
- data/lib/rubik/llm/registry.rb +336 -0
- data/lib/rubik/llm/router.rb +100 -0
- data/lib/rubik/llm.rb +18 -0
- data/lib/rubik/log.rb +38 -0
- data/lib/rubik/lsp/client.rb +475 -0
- data/lib/rubik/lsp/manager.rb +366 -0
- data/lib/rubik/lsp.rb +9 -0
- data/lib/rubik/mcp/boot_lifecycle.rb +114 -0
- data/lib/rubik/mcp/client.rb +169 -0
- data/lib/rubik/mcp/errors.rb +11 -0
- data/lib/rubik/mcp/manager.rb +619 -0
- data/lib/rubik/mcp/proxy_tool.rb +104 -0
- data/lib/rubik/mcp/transport/stdio.rb +255 -0
- data/lib/rubik/mcp.rb +12 -0
- data/lib/rubik/orchestrator/activity_log.rb +75 -0
- data/lib/rubik/orchestrator/auto_apply.rb +166 -0
- data/lib/rubik/orchestrator/budget_manager.rb +97 -0
- data/lib/rubik/orchestrator/continuation_manager.rb +161 -0
- data/lib/rubik/orchestrator/finish_message_selector.rb +55 -0
- data/lib/rubik/orchestrator/goal_ledger/drift.rb +71 -0
- data/lib/rubik/orchestrator/goal_ledger/ledger.rb +399 -0
- data/lib/rubik/orchestrator/goal_ledger/observation_projection.rb +179 -0
- data/lib/rubik/orchestrator/goal_ledger/patch.rb +268 -0
- data/lib/rubik/orchestrator/goal_ledger/schema.rb +123 -0
- data/lib/rubik/orchestrator/goal_ledger/validator.rb +361 -0
- data/lib/rubik/orchestrator/goal_ledger/verifier.rb +129 -0
- data/lib/rubik/orchestrator/goal_ledger/verify_spec.rb +50 -0
- data/lib/rubik/orchestrator/goal_ledger.rb +65 -0
- data/lib/rubik/orchestrator/goal_state.rb +184 -0
- data/lib/rubik/orchestrator/goal_wait.rb +125 -0
- data/lib/rubik/orchestrator/loop.rb +388 -0
- data/lib/rubik/orchestrator/lsp_feedback.rb +55 -0
- data/lib/rubik/orchestrator/maestro_gate.rb +65 -0
- data/lib/rubik/orchestrator/provider_recovery.rb +90 -0
- data/lib/rubik/orchestrator/provider_resolver.rb +69 -0
- data/lib/rubik/orchestrator/shell_deps.rb +85 -0
- data/lib/rubik/orchestrator/step_executor.rb +328 -0
- data/lib/rubik/orchestrator/stream_manager.rb +118 -0
- data/lib/rubik/orchestrator/subagent/activation.rb +65 -0
- data/lib/rubik/orchestrator/subagent/budget.rb +108 -0
- data/lib/rubik/orchestrator/subagent/completion.rb +61 -0
- data/lib/rubik/orchestrator/subagent/concurrency_gate.rb +37 -0
- data/lib/rubik/orchestrator/subagent/conversation_id.rb +67 -0
- data/lib/rubik/orchestrator/subagent/dependency.rb +398 -0
- data/lib/rubik/orchestrator/subagent/instance.rb +384 -0
- data/lib/rubik/orchestrator/subagent/lifecycle.rb +118 -0
- data/lib/rubik/orchestrator/subagent/messaging.rb +61 -0
- data/lib/rubik/orchestrator/subagent/narration.rb +125 -0
- data/lib/rubik/orchestrator/subagent/notices.rb +209 -0
- data/lib/rubik/orchestrator/subagent/questioner.rb +80 -0
- data/lib/rubik/orchestrator/subagent/reaper.rb +98 -0
- data/lib/rubik/orchestrator/subagent/resilience.rb +70 -0
- data/lib/rubik/orchestrator/subagent/reuse.rb +127 -0
- data/lib/rubik/orchestrator/subagent/scoped_registry.rb +79 -0
- data/lib/rubik/orchestrator/subagent/step_runner.rb +240 -0
- data/lib/rubik/orchestrator/subagent/storage_id.rb +26 -0
- data/lib/rubik/orchestrator/subagent/supervisor.rb +327 -0
- data/lib/rubik/orchestrator/subagent/transcript_formatter.rb +141 -0
- data/lib/rubik/orchestrator/subagent/type_registry.rb +199 -0
- data/lib/rubik/orchestrator/subagent_notice_inbox.rb +80 -0
- data/lib/rubik/orchestrator/tool_call_id.rb +47 -0
- data/lib/rubik/orchestrator/tool_circuit_breaker.rb +91 -0
- data/lib/rubik/orchestrator/tool_history.rb +39 -0
- data/lib/rubik/orchestrator/turn_executor/subagent_channel.rb +102 -0
- data/lib/rubik/orchestrator/turn_executor/user_channel.rb +172 -0
- data/lib/rubik/orchestrator/turn_executor.rb +376 -0
- data/lib/rubik/orchestrator.rb +11 -0
- data/lib/rubik/platform/filesystem.rb +64 -0
- data/lib/rubik/platform/io_drain.rb +53 -0
- data/lib/rubik/platform/path.rb +210 -0
- data/lib/rubik/platform/process.rb +38 -0
- data/lib/rubik/platform/sanitize.rb +157 -0
- data/lib/rubik/platform.rb +113 -0
- data/lib/rubik/plugin/dsl.rb +51 -0
- data/lib/rubik/plugin/manager.rb +247 -0
- data/lib/rubik/plugin/storage.rb +195 -0
- data/lib/rubik/plugin.rb +10 -0
- data/lib/rubik/prompt/context_builder.rb +377 -0
- data/lib/rubik/prompt/context_compactor.rb +326 -0
- data/lib/rubik/prompt/conversation_rows.rb +74 -0
- data/lib/rubik/prompt/doctrine.rb +160 -0
- data/lib/rubik/prompt/emergency_compactor.rb +81 -0
- data/lib/rubik/prompt/services.rb +60 -0
- data/lib/rubik/prompt/token_estimator.rb +63 -0
- data/lib/rubik/prompt/tool_teachings.rb +78 -0
- data/lib/rubik/prompt.rb +13 -0
- data/lib/rubik/repair/loop.rb +95 -0
- data/lib/rubik/repair.rb +8 -0
- data/lib/rubik/repl/ast_warmup.rb +129 -0
- data/lib/rubik/repl/command_context.rb +214 -0
- data/lib/rubik/repl/command_parser.rb +48 -0
- data/lib/rubik/repl/commands/agent.rb +30 -0
- data/lib/rubik/repl/commands/agent_context.rb +289 -0
- data/lib/rubik/repl/commands/agent_execution.rb +342 -0
- data/lib/rubik/repl/commands/agent_models.rb +212 -0
- data/lib/rubik/repl/commands/agent_providers.rb +232 -0
- data/lib/rubik/repl/commands/base.rb +55 -0
- data/lib/rubik/repl/commands/buffer.rb +124 -0
- data/lib/rubik/repl/commands/dmesg.rb +87 -0
- data/lib/rubik/repl/commands/lsp.rb +235 -0
- data/lib/rubik/repl/commands/mcp.rb +194 -0
- data/lib/rubik/repl/commands/plugins.rb +208 -0
- data/lib/rubik/repl/commands/pruner.rb +71 -0
- data/lib/rubik/repl/commands/session.rb +183 -0
- data/lib/rubik/repl/commands/skills.rb +111 -0
- data/lib/rubik/repl/commands/storage.rb +85 -0
- data/lib/rubik/repl/commands/subagents.rb +195 -0
- data/lib/rubik/repl/commands/system.rb +85 -0
- data/lib/rubik/repl/commands/tasks.rb +120 -0
- data/lib/rubik/repl/commands/workspace.rb +443 -0
- data/lib/rubik/repl/commands.rb +44 -0
- data/lib/rubik/repl/composition.rb +32 -0
- data/lib/rubik/repl/config_wiring.rb +142 -0
- data/lib/rubik/repl/confirmations.rb +56 -0
- data/lib/rubik/repl/hooks.rb +47 -0
- data/lib/rubik/repl/input_execution.rb +323 -0
- data/lib/rubik/repl/input_loop.rb +213 -0
- data/lib/rubik/repl/interrupt_handler.rb +217 -0
- data/lib/rubik/repl/lifecycle.rb +410 -0
- data/lib/rubik/repl/paste_support.rb +88 -0
- data/lib/rubik/repl/pending_notice_queue.rb +60 -0
- data/lib/rubik/repl/presentation.rb +132 -0
- data/lib/rubik/repl/pruner/brief.rb +101 -0
- data/lib/rubik/repl/pruner/gate.rb +279 -0
- data/lib/rubik/repl/pruner/harness.rb +236 -0
- data/lib/rubik/repl/pruner/rules.rb +316 -0
- data/lib/rubik/repl/pruner/service.rb +82 -0
- data/lib/rubik/repl/pruner.rb +39 -0
- data/lib/rubik/repl/service_catalog.rb +374 -0
- data/lib/rubik/repl/setup_factories.rb +66 -0
- data/lib/rubik/repl/shell.rb +231 -0
- data/lib/rubik/repl/step_formatter.rb +324 -0
- data/lib/rubik/repl/tee_io.rb +105 -0
- data/lib/rubik/repl/terminal_guard.rb +157 -0
- data/lib/rubik/repl/turn_recorder.rb +234 -0
- data/lib/rubik/repl.rb +14 -0
- data/lib/rubik/runtime/ast_service.rb +78 -0
- data/lib/rubik/runtime/context.rb +335 -0
- data/lib/rubik/runtime/diagnostic_engine.rb +188 -0
- data/lib/rubik/runtime/discovery.rb +342 -0
- data/lib/rubik/runtime/fiber.rb +142 -0
- data/lib/rubik/runtime/fiber_ordering.rb +52 -0
- data/lib/rubik/runtime/fiber_retry_gate.rb +84 -0
- data/lib/rubik/runtime/index_service.rb +58 -0
- data/lib/rubik/runtime/loop_service.rb +73 -0
- data/lib/rubik/runtime/manager.rb +241 -0
- data/lib/rubik/runtime/overlay_storage.rb +481 -0
- data/lib/rubik/runtime/service_definition.rb +54 -0
- data/lib/rubik/runtime/service_registration.rb +33 -0
- data/lib/rubik/runtime/task_manager.rb +302 -0
- data/lib/rubik/runtime.rb +19 -0
- data/lib/rubik/sandbox/driver/base.rb +30 -0
- data/lib/rubik/sandbox/driver/bubblewrap.rb +111 -0
- data/lib/rubik/sandbox/driver/docker.rb +113 -0
- data/lib/rubik/sandbox/driver/podman.rb +98 -0
- data/lib/rubik/sandbox/driver/process.rb +190 -0
- data/lib/rubik/sandbox/driver.rb +91 -0
- data/lib/rubik/sandbox/runner.rb +218 -0
- data/lib/rubik/sandbox/shell_resolver.rb +178 -0
- data/lib/rubik/sandbox.rb +8 -0
- data/lib/rubik/security/blocked_guidance.rb +187 -0
- data/lib/rubik/security/classifier/classifier.rb +440 -0
- data/lib/rubik/security/classifier/commands/agent-browser.yaml +9 -0
- data/lib/rubik/security/classifier/commands/apt.yaml +25 -0
- data/lib/rubik/security/classifier/commands/awk.yaml +10 -0
- data/lib/rubik/security/classifier/commands/basename.yaml +7 -0
- data/lib/rubik/security/classifier/commands/bash-classify.yaml +8 -0
- data/lib/rubik/security/classifier/commands/bash.yaml +20 -0
- data/lib/rubik/security/classifier/commands/black.yaml +12 -0
- data/lib/rubik/security/classifier/commands/brew.yaml +31 -0
- data/lib/rubik/security/classifier/commands/builtin.yaml +8 -0
- data/lib/rubik/security/classifier/commands/cargo.yaml +114 -0
- data/lib/rubik/security/classifier/commands/cat.yaml +7 -0
- data/lib/rubik/security/classifier/commands/chgrp.yaml +10 -0
- data/lib/rubik/security/classifier/commands/chmod.yaml +10 -0
- data/lib/rubik/security/classifier/commands/chown.yaml +10 -0
- data/lib/rubik/security/classifier/commands/column.yaml +7 -0
- data/lib/rubik/security/classifier/commands/comm.yaml +7 -0
- data/lib/rubik/security/classifier/commands/command.yaml +12 -0
- data/lib/rubik/security/classifier/commands/cp.yaml +7 -0
- data/lib/rubik/security/classifier/commands/crystal.yaml +63 -0
- data/lib/rubik/security/classifier/commands/curl.yaml +30 -0
- data/lib/rubik/security/classifier/commands/cut.yaml +7 -0
- data/lib/rubik/security/classifier/commands/date.yaml +10 -0
- data/lib/rubik/security/classifier/commands/df.yaml +7 -0
- data/lib/rubik/security/classifier/commands/diff.yaml +7 -0
- data/lib/rubik/security/classifier/commands/dig.yaml +7 -0
- data/lib/rubik/security/classifier/commands/dirname.yaml +7 -0
- data/lib/rubik/security/classifier/commands/docker.yaml +177 -0
- data/lib/rubik/security/classifier/commands/du.yaml +7 -0
- data/lib/rubik/security/classifier/commands/echo.yaml +7 -0
- data/lib/rubik/security/classifier/commands/env.yaml +15 -0
- data/lib/rubik/security/classifier/commands/eslint.yaml +12 -0
- data/lib/rubik/security/classifier/commands/eval.yaml +8 -0
- data/lib/rubik/security/classifier/commands/exec.yaml +12 -0
- data/lib/rubik/security/classifier/commands/expand.yaml +7 -0
- data/lib/rubik/security/classifier/commands/false.yaml +7 -0
- data/lib/rubik/security/classifier/commands/file.yaml +7 -0
- data/lib/rubik/security/classifier/commands/find.yaml +25 -0
- data/lib/rubik/security/classifier/commands/fmt.yaml +7 -0
- data/lib/rubik/security/classifier/commands/fold.yaml +7 -0
- data/lib/rubik/security/classifier/commands/free.yaml +7 -0
- data/lib/rubik/security/classifier/commands/gawk.yaml +10 -0
- data/lib/rubik/security/classifier/commands/gcloud.yaml +979 -0
- data/lib/rubik/security/classifier/commands/gh.yaml +615 -0
- data/lib/rubik/security/classifier/commands/git.yaml +524 -0
- data/lib/rubik/security/classifier/commands/glab-discussion.yaml +21 -0
- data/lib/rubik/security/classifier/commands/glab.yaml +807 -0
- data/lib/rubik/security/classifier/commands/go.yaml +44 -0
- data/lib/rubik/security/classifier/commands/gog.yaml +983 -0
- data/lib/rubik/security/classifier/commands/gradle.yaml +92 -0
- data/lib/rubik/security/classifier/commands/gradlew.yaml +92 -0
- data/lib/rubik/security/classifier/commands/grep.yaml +7 -0
- data/lib/rubik/security/classifier/commands/groups.yaml +7 -0
- data/lib/rubik/security/classifier/commands/gunzip.yaml +8 -0
- data/lib/rubik/security/classifier/commands/gws.yaml +211 -0
- data/lib/rubik/security/classifier/commands/gzip.yaml +7 -0
- data/lib/rubik/security/classifier/commands/hadolint.yaml +9 -0
- data/lib/rubik/security/classifier/commands/halt.yaml +8 -0
- data/lib/rubik/security/classifier/commands/head.yaml +7 -0
- data/lib/rubik/security/classifier/commands/helm.yaml +129 -0
- data/lib/rubik/security/classifier/commands/hostname.yaml +7 -0
- data/lib/rubik/security/classifier/commands/id.yaml +7 -0
- data/lib/rubik/security/classifier/commands/install.yaml +7 -0
- data/lib/rubik/security/classifier/commands/isort.yaml +14 -0
- data/lib/rubik/security/classifier/commands/java.yaml +35 -0
- data/lib/rubik/security/classifier/commands/javac.yaml +39 -0
- data/lib/rubik/security/classifier/commands/join.yaml +7 -0
- data/lib/rubik/security/classifier/commands/journalctl.yaml +14 -0
- data/lib/rubik/security/classifier/commands/jq.yaml +7 -0
- data/lib/rubik/security/classifier/commands/kill.yaml +7 -0
- data/lib/rubik/security/classifier/commands/killall.yaml +7 -0
- data/lib/rubik/security/classifier/commands/kubectl.yaml +211 -0
- data/lib/rubik/security/classifier/commands/less.yaml +7 -0
- data/lib/rubik/security/classifier/commands/ln.yaml +7 -0
- data/lib/rubik/security/classifier/commands/ls.yaml +7 -0
- data/lib/rubik/security/classifier/commands/make.yaml +21 -0
- data/lib/rubik/security/classifier/commands/md5sum.yaml +7 -0
- data/lib/rubik/security/classifier/commands/mise.yaml +579 -0
- data/lib/rubik/security/classifier/commands/mkdir.yaml +8 -0
- data/lib/rubik/security/classifier/commands/more.yaml +7 -0
- data/lib/rubik/security/classifier/commands/mv.yaml +7 -0
- data/lib/rubik/security/classifier/commands/mvn.yaml +83 -0
- data/lib/rubik/security/classifier/commands/mvnw.yaml +83 -0
- data/lib/rubik/security/classifier/commands/mypy.yaml +9 -0
- data/lib/rubik/security/classifier/commands/netstat.yaml +7 -0
- data/lib/rubik/security/classifier/commands/nice.yaml +11 -0
- data/lib/rubik/security/classifier/commands/nl.yaml +7 -0
- data/lib/rubik/security/classifier/commands/node.yaml +20 -0
- data/lib/rubik/security/classifier/commands/nohup.yaml +9 -0
- data/lib/rubik/security/classifier/commands/npm.yaml +71 -0
- data/lib/rubik/security/classifier/commands/npx.yaml +42 -0
- data/lib/rubik/security/classifier/commands/nslookup.yaml +7 -0
- data/lib/rubik/security/classifier/commands/paste.yaml +7 -0
- data/lib/rubik/security/classifier/commands/ping.yaml +7 -0
- data/lib/rubik/security/classifier/commands/pip.yaml +33 -0
- data/lib/rubik/security/classifier/commands/pip3.yaml +33 -0
- data/lib/rubik/security/classifier/commands/pipx.yaml +33 -0
- data/lib/rubik/security/classifier/commands/pkill.yaml +7 -0
- data/lib/rubik/security/classifier/commands/pnpm.yaml +346 -0
- data/lib/rubik/security/classifier/commands/poetry.yaml +97 -0
- data/lib/rubik/security/classifier/commands/poweroff.yaml +8 -0
- data/lib/rubik/security/classifier/commands/prettier.yaml +13 -0
- data/lib/rubik/security/classifier/commands/printenv.yaml +7 -0
- data/lib/rubik/security/classifier/commands/printf.yaml +7 -0
- data/lib/rubik/security/classifier/commands/ps.yaml +7 -0
- data/lib/rubik/security/classifier/commands/pup.yaml +882 -0
- data/lib/rubik/security/classifier/commands/pwd.yaml +7 -0
- data/lib/rubik/security/classifier/commands/pytest.yaml +7 -0
- data/lib/rubik/security/classifier/commands/python.yaml +9 -0
- data/lib/rubik/security/classifier/commands/python3.yaml +9 -0
- data/lib/rubik/security/classifier/commands/read.yaml +7 -0
- data/lib/rubik/security/classifier/commands/readlink.yaml +7 -0
- data/lib/rubik/security/classifier/commands/realpath.yaml +7 -0
- data/lib/rubik/security/classifier/commands/reboot.yaml +8 -0
- data/lib/rubik/security/classifier/commands/rev.yaml +7 -0
- data/lib/rubik/security/classifier/commands/rg.yaml +7 -0
- data/lib/rubik/security/classifier/commands/rm.yaml +8 -0
- data/lib/rubik/security/classifier/commands/rsync.yaml +12 -0
- data/lib/rubik/security/classifier/commands/rtk.yaml +140 -0
- data/lib/rubik/security/classifier/commands/ruff.yaml +29 -0
- data/lib/rubik/security/classifier/commands/rustfmt.yaml +14 -0
- data/lib/rubik/security/classifier/commands/scp.yaml +7 -0
- data/lib/rubik/security/classifier/commands/sed.yaml +14 -0
- data/lib/rubik/security/classifier/commands/seq.yaml +7 -0
- data/lib/rubik/security/classifier/commands/sh.yaml +14 -0
- data/lib/rubik/security/classifier/commands/sha1sum.yaml +7 -0
- data/lib/rubik/security/classifier/commands/sha256sum.yaml +7 -0
- data/lib/rubik/security/classifier/commands/shellcheck.yaml +10 -0
- data/lib/rubik/security/classifier/commands/shutdown.yaml +8 -0
- data/lib/rubik/security/classifier/commands/slack.yaml +113 -0
- data/lib/rubik/security/classifier/commands/sleep.yaml +8 -0
- data/lib/rubik/security/classifier/commands/snip.yaml +79 -0
- data/lib/rubik/security/classifier/commands/sort.yaml +10 -0
- data/lib/rubik/security/classifier/commands/ss.yaml +7 -0
- data/lib/rubik/security/classifier/commands/ssh.yaml +41 -0
- data/lib/rubik/security/classifier/commands/stat.yaml +7 -0
- data/lib/rubik/security/classifier/commands/stdbuf.yaml +15 -0
- data/lib/rubik/security/classifier/commands/strace.yaml +20 -0
- data/lib/rubik/security/classifier/commands/strings.yaml +11 -0
- data/lib/rubik/security/classifier/commands/sudo.yaml +24 -0
- data/lib/rubik/security/classifier/commands/systemctl.yaml +58 -0
- data/lib/rubik/security/classifier/commands/systemd-run.yaml +30 -0
- data/lib/rubik/security/classifier/commands/tac.yaml +7 -0
- data/lib/rubik/security/classifier/commands/tail.yaml +7 -0
- data/lib/rubik/security/classifier/commands/tar.yaml +25 -0
- data/lib/rubik/security/classifier/commands/tee.yaml +8 -0
- data/lib/rubik/security/classifier/commands/terraform.yaml +70 -0
- data/lib/rubik/security/classifier/commands/test.yaml +7 -0
- data/lib/rubik/security/classifier/commands/time.yaml +8 -0
- data/lib/rubik/security/classifier/commands/timeout.yaml +18 -0
- data/lib/rubik/security/classifier/commands/top.yaml +7 -0
- data/lib/rubik/security/classifier/commands/touch.yaml +8 -0
- data/lib/rubik/security/classifier/commands/tr.yaml +7 -0
- data/lib/rubik/security/classifier/commands/traceroute.yaml +7 -0
- data/lib/rubik/security/classifier/commands/true.yaml +7 -0
- data/lib/rubik/security/classifier/commands/tsc.yaml +16 -0
- data/lib/rubik/security/classifier/commands/type.yaml +7 -0
- data/lib/rubik/security/classifier/commands/uname.yaml +7 -0
- data/lib/rubik/security/classifier/commands/unexpand.yaml +7 -0
- data/lib/rubik/security/classifier/commands/uniq.yaml +7 -0
- data/lib/rubik/security/classifier/commands/unzip.yaml +10 -0
- data/lib/rubik/security/classifier/commands/uptime.yaml +7 -0
- data/lib/rubik/security/classifier/commands/uv.yaml +156 -0
- data/lib/rubik/security/classifier/commands/wait.yaml +11 -0
- data/lib/rubik/security/classifier/commands/wc.yaml +7 -0
- data/lib/rubik/security/classifier/commands/wget.yaml +7 -0
- data/lib/rubik/security/classifier/commands/whereis.yaml +7 -0
- data/lib/rubik/security/classifier/commands/which.yaml +7 -0
- data/lib/rubik/security/classifier/commands/whoami.yaml +7 -0
- data/lib/rubik/security/classifier/commands/xargs.yaml +26 -0
- data/lib/rubik/security/classifier/commands/xmllint.yaml +7 -0
- data/lib/rubik/security/classifier/commands/yarn.yaml +72 -0
- data/lib/rubik/security/classifier/commands/yes.yaml +7 -0
- data/lib/rubik/security/classifier/commands/yq.yaml +10 -0
- data/lib/rubik/security/classifier/commands/zip.yaml +8 -0
- data/lib/rubik/security/classifier/commands/zsh.yaml +14 -0
- data/lib/rubik/security/classifier/database.rb +216 -0
- data/lib/rubik/security/classifier/matcher/delegation.rb +190 -0
- data/lib/rubik/security/classifier/matcher/options.rb +245 -0
- data/lib/rubik/security/classifier/matcher/subcommands.rb +103 -0
- data/lib/rubik/security/classifier/matcher.rb +249 -0
- data/lib/rubik/security/classifier/models.rb +170 -0
- data/lib/rubik/security/classifier/parser.rb +23 -0
- data/lib/rubik/security/classifier/parsers/bash.rb +280 -0
- data/lib/rubik/security/classifier/parsers/bash_extractors.rb +221 -0
- data/lib/rubik/security/classifier/parsers/powershell.rb +185 -0
- data/lib/rubik/security/classifier/pwsh_rules.rb +224 -0
- data/lib/rubik/security/classifier.rb +25 -0
- data/lib/rubik/security/errors.rb +39 -0
- data/lib/rubik/security/guard.rb +954 -0
- data/lib/rubik/security/redactor.rb +89 -0
- data/lib/rubik/security.rb +11 -0
- data/lib/rubik/session/manager.rb +331 -0
- data/lib/rubik/session/recorder.rb +149 -0
- data/lib/rubik/session/replayer.rb +65 -0
- data/lib/rubik/session.rb +10 -0
- data/lib/rubik/skills/frontmatter.rb +41 -0
- data/lib/rubik/skills/manager.rb +223 -0
- data/lib/rubik/skills/registry.rb +64 -0
- data/lib/rubik/skills/skill.rb +33 -0
- data/lib/rubik/skills/storage.rb +77 -0
- data/lib/rubik/skills/validator.rb +116 -0
- data/lib/rubik/skills.rb +20 -0
- data/lib/rubik/storage/folding.rb +23 -0
- data/lib/rubik/storage/layout.rb +145 -0
- data/lib/rubik/storage/render.rb +97 -0
- data/lib/rubik/storage/service.rb +42 -0
- data/lib/rubik/storage/store.rb +514 -0
- data/lib/rubik/storage/transcript_indexer.rb +116 -0
- data/lib/rubik/storage/uri_reader.rb +168 -0
- data/lib/rubik/storage/vakanuvis/annal_writer.rb +92 -0
- data/lib/rubik/storage/vakanuvis/distill_gate.rb +323 -0
- data/lib/rubik/storage/vakanuvis/gate.rb +250 -0
- data/lib/rubik/storage/vakanuvis/harness.rb +421 -0
- data/lib/rubik/storage/vakanuvis/memory_steward.rb +163 -0
- data/lib/rubik/storage/vakanuvis/service.rb +69 -0
- data/lib/rubik/storage/vakanuvis/vocabulary.rb +99 -0
- data/lib/rubik/storage/vakanuvis/wiki_skeleton.rb +67 -0
- data/lib/rubik/storage/vakanuvis.rb +77 -0
- data/lib/rubik/storage.rb +29 -0
- data/lib/rubik/tools/base.rb +107 -0
- data/lib/rubik/tools/builtins/dmesg.rb +74 -0
- data/lib/rubik/tools/builtins/exec.rb +293 -0
- data/lib/rubik/tools/builtins/files.rb +354 -0
- data/lib/rubik/tools/builtins/goal_ledger.rb +89 -0
- data/lib/rubik/tools/builtins/grep.rb +281 -0
- data/lib/rubik/tools/builtins/interactive.rb +175 -0
- data/lib/rubik/tools/builtins/lifecycle.rb +359 -0
- data/lib/rubik/tools/builtins/lsp.rb +283 -0
- data/lib/rubik/tools/builtins/pruner.rb +88 -0
- data/lib/rubik/tools/builtins/runtime.rb +120 -0
- data/lib/rubik/tools/builtins/send_message.rb +109 -0
- data/lib/rubik/tools/builtins/show_file.rb +84 -0
- data/lib/rubik/tools/builtins/skill.rb +72 -0
- data/lib/rubik/tools/builtins/storage.rb +232 -0
- data/lib/rubik/tools/builtins/subagent.rb +356 -0
- data/lib/rubik/tools/builtins/subagent_lifecycle.rb +72 -0
- data/lib/rubik/tools/builtins/symbols.rb +209 -0
- data/lib/rubik/tools/builtins/synthesis.rb +314 -0
- data/lib/rubik/tools/builtins/task.rb +168 -0
- data/lib/rubik/tools/builtins/unsynthesize.rb +111 -0
- data/lib/rubik/tools/builtins/vakanuvis.rb +161 -0
- data/lib/rubik/tools/builtins.rb +30 -0
- data/lib/rubik/tools/catalog.rb +70 -0
- data/lib/rubik/tools/declaration.rb +90 -0
- data/lib/rubik/tools/diagnostic_reporter.rb +283 -0
- data/lib/rubik/tools/fuzzy_matcher.rb +172 -0
- data/lib/rubik/tools/native_search.rb +180 -0
- data/lib/rubik/tools/output.rb +54 -0
- data/lib/rubik/tools/preflight_runner.rb +209 -0
- data/lib/rubik/tools/registry.rb +247 -0
- data/lib/rubik/tools/result.rb +71 -0
- data/lib/rubik/tools/services.rb +96 -0
- data/lib/rubik/tools/truncator.rb +78 -0
- data/lib/rubik/tools/uri_resolver.rb +195 -0
- data/lib/rubik/tools.rb +14 -0
- data/lib/rubik/ui/approvals.rb +52 -0
- data/lib/rubik/ui/banner.rb +43 -0
- data/lib/rubik/ui/capability.rb +122 -0
- data/lib/rubik/ui/dialog.rb +106 -0
- data/lib/rubik/ui/diff_viewer.rb +78 -0
- data/lib/rubik/ui/frame.rb +95 -0
- data/lib/rubik/ui/markdown_stream.rb +107 -0
- data/lib/rubik/ui/panel.rb +98 -0
- data/lib/rubik/ui/renderer.rb +206 -0
- data/lib/rubik/ui/spinner.rb +120 -0
- data/lib/rubik/ui/statusline.rb +134 -0
- data/lib/rubik/ui/theme.rb +118 -0
- data/lib/rubik/ui.rb +57 -0
- data/lib/rubik/uris.rb +101 -0
- data/lib/rubik/version.rb +5 -0
- data/lib/rubik.rb +65 -0
- metadata +770 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module Tools
|
|
5
|
+
module Truncator
|
|
6
|
+
DEFAULT_MAX_CHARS = 12_000
|
|
7
|
+
DEFAULT_MAX_LINES = 300
|
|
8
|
+
|
|
9
|
+
def self.truncate(text, max_chars: DEFAULT_MAX_CHARS, max_lines: DEFAULT_MAX_LINES, strategy: :head_tail)
|
|
10
|
+
str = text.to_s
|
|
11
|
+
# byteslice truncation below cuts on byte boundaries and can split a
|
|
12
|
+
# multibyte character, producing invalid UTF-8 that then poisons the
|
|
13
|
+
# whole downstream pipeline (JSON generation, regex work, token
|
|
14
|
+
# estimation). Scrub exactly once, after the last byteslice.
|
|
15
|
+
str = str.scrub("") unless str.valid_encoding?
|
|
16
|
+
lines = str.lines
|
|
17
|
+
|
|
18
|
+
max_c = (max_chars || DEFAULT_MAX_CHARS).to_i
|
|
19
|
+
max_l = (max_lines || DEFAULT_MAX_LINES).to_i
|
|
20
|
+
|
|
21
|
+
return { text: str, truncated: false, omitted_lines: 0, omitted_chars: 0 } if str.bytesize <= max_c && lines.size <= max_l
|
|
22
|
+
|
|
23
|
+
strategy_sym = strategy.to_s.downcase.to_sym
|
|
24
|
+
|
|
25
|
+
case strategy_sym
|
|
26
|
+
when :head_only
|
|
27
|
+
kept_lines = lines.first(max_l)
|
|
28
|
+
kept_str = kept_lines.join
|
|
29
|
+
kept_str = kept_str.byteslice(0, max_c) if kept_str.bytesize > max_c
|
|
30
|
+
omitted_lines = [lines.size - kept_lines.size, 0].max
|
|
31
|
+
omitted_chars = [str.bytesize - kept_str.bytesize, 0].max
|
|
32
|
+
notice = "\n... [Truncated: omitted #{omitted_lines} lines / #{omitted_chars} chars] ...\n"
|
|
33
|
+
{ text: finalize(kept_str, notice, prefix: true), truncated: true,
|
|
34
|
+
omitted_lines: omitted_lines, omitted_chars: omitted_chars }
|
|
35
|
+
when :tail_only
|
|
36
|
+
kept_lines = lines.last(max_l)
|
|
37
|
+
kept_str = kept_lines.join
|
|
38
|
+
kept_str = kept_str.byteslice(-max_c, max_c) if kept_str.bytesize > max_c
|
|
39
|
+
omitted_lines = [lines.size - kept_lines.size, 0].max
|
|
40
|
+
omitted_chars = [str.bytesize - kept_str.bytesize, 0].max
|
|
41
|
+
notice = "\n... [Truncated: omitted #{omitted_lines} lines / #{omitted_chars} chars] ...\n"
|
|
42
|
+
{ text: finalize(kept_str, notice, prefix: false), truncated: true,
|
|
43
|
+
omitted_lines: omitted_lines, omitted_chars: omitted_chars }
|
|
44
|
+
else # :head_tail
|
|
45
|
+
half_lines = (max_l / 2).clamp(1, 150)
|
|
46
|
+
half_chars = (max_c / 2).clamp(1, 6000)
|
|
47
|
+
|
|
48
|
+
head_lines = lines.first(half_lines)
|
|
49
|
+
tail_lines = lines.last(half_lines)
|
|
50
|
+
|
|
51
|
+
head_str = head_lines.join
|
|
52
|
+
tail_str = tail_lines.join
|
|
53
|
+
|
|
54
|
+
head_str = head_str.byteslice(0, half_chars) if head_str.bytesize > half_chars
|
|
55
|
+
tail_str = tail_str.byteslice(-half_chars, half_chars) if tail_str.bytesize > half_chars
|
|
56
|
+
|
|
57
|
+
omitted_lines = [lines.size - (head_lines.size + tail_lines.size), 0].max
|
|
58
|
+
omitted_chars = [str.bytesize - (head_str.bytesize + tail_str.bytesize), 0].max
|
|
59
|
+
notice = "\n\n... [Truncated: #{omitted_lines} lines / #{omitted_chars} chars omitted to preserve token budget] ...\n\n"
|
|
60
|
+
|
|
61
|
+
{ text: finalize(tail_str, finalize(head_str, notice, prefix: true), prefix: false), truncated: true,
|
|
62
|
+
omitted_lines: omitted_lines, omitted_chars: omitted_chars }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Post-byteslice UTF-8 guard: a byte-boundary cut can split a multibyte
|
|
67
|
+
# character at either edge of the kept region. Dropping the partial
|
|
68
|
+
# sequence keeps the byte budget honest while the rendered output stays
|
|
69
|
+
# valid UTF-8 — the property every consumer below this point (JSON
|
|
70
|
+
# payload envelopes, regex-based token estimation, history joins)
|
|
71
|
+
# silently assumes.
|
|
72
|
+
def self.finalize(str, notice, prefix:)
|
|
73
|
+
str = str.scrub("") unless str.valid_encoding?
|
|
74
|
+
prefix ? "#{str}#{notice}" : "#{notice}#{str}"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "monitor"
|
|
4
|
+
require_relative "../uris"
|
|
5
|
+
require_relative "../coeffects"
|
|
6
|
+
require_relative "../orchestrator/subagent/transcript_formatter"
|
|
7
|
+
require_relative "output"
|
|
8
|
+
require_relative "diagnostic_reporter"
|
|
9
|
+
require_relative "../storage"
|
|
10
|
+
|
|
11
|
+
module Rubik
|
|
12
|
+
module Tools
|
|
13
|
+
# rubik:// şema sevk yeri (uri-resolver-seam planı 2026-10-01).
|
|
14
|
+
# read_file ve show_file sanal URI'leri buradan geçirir; dosya
|
|
15
|
+
# düzlemi (file:// ve düz yollar) ReadFileTool'da kalır.
|
|
16
|
+
#
|
|
17
|
+
# Kayıt modeli Sandbox::Driver'ınkinin aynısı (AGENTS.md §4.D):
|
|
18
|
+
# Monitor korumalı şema→adapter haritası; ctx verilirse önceki değer
|
|
19
|
+
# anlık görüntüsü alınır ve kimlik-korumalı geri-yükleme lambda'sı
|
|
20
|
+
# ctx.on_dispose'a LIFO için itilir — dispose yalnızca yuvada HÂLÂ
|
|
21
|
+
# bizim adapter varsa geri yükler, böylece daha yeni bir kayıt eski
|
|
22
|
+
# bağlamın kapanışından sağ çıkar. Varsayılan adapterler dosya sonunda
|
|
23
|
+
# ctx'siz tohumlanır (süreç ömrü) — bare kurulumlu testler için diki
|
|
24
|
+
# kök gerekmez.
|
|
25
|
+
module UriResolver
|
|
26
|
+
@adapters = {}
|
|
27
|
+
@mutex = Monitor.new
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
def register(scheme, adapter, ctx: nil)
|
|
31
|
+
key = scheme.to_s.downcase
|
|
32
|
+
restore = @mutex.synchronize do
|
|
33
|
+
prior = @adapters[key] # snapshot BEFORE the overwrite
|
|
34
|
+
@adapters[key] = adapter
|
|
35
|
+
lambda do
|
|
36
|
+
@mutex.synchronize do
|
|
37
|
+
next unless @adapters.key?(key) && @adapters[key].equal?(adapter)
|
|
38
|
+
|
|
39
|
+
prior ? @adapters[key] = prior : @adapters.delete(key)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
ctx&.on_dispose(&restore)
|
|
44
|
+
adapter
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def unregister(scheme)
|
|
48
|
+
@mutex.synchronize { @adapters.delete(scheme.to_s.downcase) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def registered_schemes
|
|
52
|
+
@mutex.synchronize { @adapters.keys.sort }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Sevk. Adapterden String (başarı) ya da ToolOutput (dürüst hata)
|
|
56
|
+
# döner; bu çözümleyici URI'ye hizmet etmiyorsa nil:
|
|
57
|
+
# - URI rubik:// değil
|
|
58
|
+
# - şemaya kayıtlı adapter yok
|
|
59
|
+
# - adapterın tam grameri (match?) URI'yi reddetmesi
|
|
60
|
+
# - `only:` verilmiş ve URI'nin şemesi farklıysa (show_file
|
|
61
|
+
# yalnız "storage" ister)
|
|
62
|
+
# Çağıranlar nil'i "kendi doğal düzlemime düş" olarak yorumlar —
|
|
63
|
+
# bilinmeyen şemalar buffer düzlemine düşer (bugünkü davranış).
|
|
64
|
+
def resolve(uri, ctx: nil, buffer_manager: nil, only: nil)
|
|
65
|
+
scheme = Rubik::Uris.scheme_of(uri)
|
|
66
|
+
return nil unless scheme
|
|
67
|
+
return nil if only && scheme != only.to_s.downcase
|
|
68
|
+
|
|
69
|
+
adapter = @mutex.synchronize { @adapters[scheme] }
|
|
70
|
+
return nil unless adapter
|
|
71
|
+
return nil unless adapter.match?(uri)
|
|
72
|
+
|
|
73
|
+
result = adapter.read(uri, ctx: ctx, buffer_manager: buffer_manager)
|
|
74
|
+
return result unless result.nil?
|
|
75
|
+
|
|
76
|
+
# Sözleşme ihlali (adapter read ASLA nil döndürmez) — gramer
|
|
77
|
+
# reddinden AYRT edilir: nil'i dosya düzlemine düşürmek yanlış
|
|
78
|
+
# yönlendiren bir dosya-not-found üretirdi; dürüst hata basılır.
|
|
79
|
+
warn "[UriResolver] '#{scheme}' adapter violated the read contract (returned nil); " \
|
|
80
|
+
"refusing to fall through to the file plane"
|
|
81
|
+
ToolOutput.error("rubik://#{scheme} read failed: the '#{scheme}' adapter returned no content " \
|
|
82
|
+
"(contract violation — see runtime log).")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Şema kayıtlı VE adapterın grameri URI'yi tanıyor mu — yalnızca
|
|
86
|
+
# belgelendirme/bekçi sorguları için; sevk yolu resolve'dur.
|
|
87
|
+
def handles?(uri)
|
|
88
|
+
scheme = Rubik::Uris.scheme_of(uri)
|
|
89
|
+
return false unless scheme
|
|
90
|
+
|
|
91
|
+
adapter = @mutex.synchronize { @adapters[scheme] }
|
|
92
|
+
!adapter.nil? && adapter.match?(uri)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Adapter sözleşmesi: durumdan yoksuz strateji sınıfları (repo
|
|
97
|
+
# kuralı: ilgili sınıflar aynı dosyada, files.rb gibi). match? gramer
|
|
98
|
+
# kapısıdır — başarısızlığı sevk yerinin nil düşüşüdür. read ASLA nil
|
|
99
|
+
# döndürmez ve ASLA yükselmez; hata yükü kendi ToolOutput'undadır
|
|
100
|
+
# (kendi rescue StandardError'ıyla sarılır — StorageAdapter'da bu
|
|
101
|
+
# ödev UriReader.read'in içindeki rescue'ya delege edilmiştir).
|
|
102
|
+
# Çağrı anı durumu (ctx, buffer_manager) resolve argümanlarından
|
|
103
|
+
# akar — adapter ctx-provided servis DEĞİLDİR.
|
|
104
|
+
|
|
105
|
+
# rubik://storage/... — FS-DB okuma düzlemi. Alt-gramer (index/
|
|
106
|
+
# proje/tip-liste/varlık/refs, .json eki) UriReader'ındır; hata
|
|
107
|
+
# yükleri de orada yaşar.
|
|
108
|
+
class StorageAdapter
|
|
109
|
+
def match?(uri)
|
|
110
|
+
Rubik::Uris::STORAGE_PREFIX.match?(uri)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def read(uri, ctx:, **_)
|
|
114
|
+
Rubik::Storage::UriReader.read(uri, ctx: ctx)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# rubik://subagent/<storage-id>/transcript reads the transcript FILE
|
|
119
|
+
# directly — never through the buffer manager. The URL must carry the
|
|
120
|
+
# unique storage dir name: the absolute path advertised by
|
|
121
|
+
# manage_subagents status and Instance#to_h. A bare display id
|
|
122
|
+
# ("subagent-1") resolves to a nonexistent dir and returns an honest
|
|
123
|
+
# not-found — deliberately no glob-newest fallback, which would serve
|
|
124
|
+
# a previous session's transcript while a live unsaved instance may
|
|
125
|
+
# exist (the stale-evidence failure mode of 2026-09-27). One route,
|
|
126
|
+
# one truth. The sandbox workspace gate does not apply: the id is
|
|
127
|
+
# charset-validated and the path is an internal artifact under the
|
|
128
|
+
# transcript root, not a user-supplied filesystem location.
|
|
129
|
+
class SubagentTranscriptAdapter
|
|
130
|
+
def match?(uri)
|
|
131
|
+
Rubik::Uris::SUBAGENT_TRANSCRIPT.match?(uri)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def read(uri, ctx:, buffer_manager:)
|
|
135
|
+
sub_id = uri[Rubik::Uris::SUBAGENT_TRANSCRIPT, 1]
|
|
136
|
+
unless sub_id.match?(/\A[a-zA-Z0-9_-]+\z/)
|
|
137
|
+
return ToolOutput.error("Invalid subagent id: #{sub_id} (allowed: letters, digits, _ and -). Use the " \
|
|
138
|
+
"storage-id dir name from the absolute 'transcript' path in manage_subagents status.")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
storage = Rubik::Coeffects.get(ctx, Rubik::Coeffects::STORAGE_STORE)
|
|
142
|
+
file = Rubik::Orchestrator::Subagent::TranscriptFormatter.resolve(buffer_manager.root_dir, sub_id, storage: storage)
|
|
143
|
+
return File.read(file) if File.exist?(file)
|
|
144
|
+
|
|
145
|
+
ToolOutput.error(DiagnosticReporter.render_file_not_found(file.to_s, candidates: []) +
|
|
146
|
+
"\nHint: for a running subagent the live copy is manage_subagents action 'transcript' " \
|
|
147
|
+
"(in-memory); the on-disk file appears only after the subagent's first save.")
|
|
148
|
+
rescue StandardError => e
|
|
149
|
+
ToolOutput.error("Transcript read failed: #{e.class}: #{e.message}")
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# rubik://goal/state + rubik://goal/ledger: canlı !goal anlık
|
|
154
|
+
# görüntüleri (GoalState#to_xml / GoalLedger#to_xml) — coeffect
|
|
155
|
+
# üzerinden çözülür; bunlar dosya değildir, buffer manager devre
|
|
156
|
+
# dışıdır. Bağlam/yoksa dürüst not-found (subagent transcript
|
|
157
|
+
# stilinde); glob/fallback yok — yanlış koşunun durumu servis
|
|
158
|
+
# edilmez. goal şeması TEK adapter slotudur: state/ledger ayrımı
|
|
159
|
+
# İÇ dispatch'tir (ikinci kayıt mevcut adapter'ı gölgelerdi).
|
|
160
|
+
class GoalStateAdapter
|
|
161
|
+
def match?(uri)
|
|
162
|
+
Rubik::Uris::GOAL_STATE.match?(uri) || Rubik::Uris::GOAL_LEDGER.match?(uri)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def read(uri, ctx:, **_)
|
|
166
|
+
if Rubik::Uris::GOAL_LEDGER.match?(uri)
|
|
167
|
+
ledger = Rubik::Coeffects.get(ctx, Rubik::Coeffects::GOAL_LEDGER)
|
|
168
|
+
return ledger.to_xml if ledger
|
|
169
|
+
|
|
170
|
+
return ToolOutput.error("No goal ledger available: no !goal run is active (or goal.ledger.enabled=false). " \
|
|
171
|
+
"The ledger exists only while a goal loop runs; rubik://goal/state may still " \
|
|
172
|
+
"hold the last run's snapshot.")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
state = Rubik::Coeffects.get(ctx, Rubik::Coeffects::GOAL_STATE)
|
|
176
|
+
return state.to_xml if state
|
|
177
|
+
|
|
178
|
+
ToolOutput.error("No goal state available: no !goal run has started in this session. " \
|
|
179
|
+
"Goal state exists only while (or after) the operator runs !goal; " \
|
|
180
|
+
"for subagent work use manage_subagents action: 'status'.")
|
|
181
|
+
rescue StandardError => e
|
|
182
|
+
ToolOutput.error("Goal state read failed: #{e.class}: #{e.message}")
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
DEFAULT_STORAGE_ADAPTER = StorageAdapter.new
|
|
187
|
+
DEFAULT_SUBAGENT_TRANSCRIPT_ADAPTER = SubagentTranscriptAdapter.new
|
|
188
|
+
DEFAULT_GOAL_STATE_ADAPTER = GoalStateAdapter.new
|
|
189
|
+
|
|
190
|
+
register(Rubik::Uris::STORAGE, DEFAULT_STORAGE_ADAPTER)
|
|
191
|
+
register(Rubik::Uris::SUBAGENT, DEFAULT_SUBAGENT_TRANSCRIPT_ADAPTER)
|
|
192
|
+
register(Rubik::Uris::GOAL, DEFAULT_GOAL_STATE_ADAPTER)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
data/lib/rubik/tools.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "tools/base"
|
|
4
|
+
require_relative "tools/output"
|
|
5
|
+
require_relative "tools/result"
|
|
6
|
+
require_relative "tools/truncator"
|
|
7
|
+
require_relative "tools/registry"
|
|
8
|
+
require_relative "tools/builtins"
|
|
9
|
+
require_relative "tools/catalog"
|
|
10
|
+
|
|
11
|
+
module Rubik
|
|
12
|
+
module Tools
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-prompt"
|
|
4
|
+
|
|
5
|
+
module Rubik
|
|
6
|
+
module UI
|
|
7
|
+
# Fast-path approval surfaces (Antigravity-style): a warning card drawn
|
|
8
|
+
# above the prompt plus single-keypress answers.
|
|
9
|
+
#
|
|
10
|
+
# History note: an adaptive sandbox-network approval card
|
|
11
|
+
# (confirm_action/map_action, returning :yes_network/:yes_sandbox) lived
|
|
12
|
+
# here but never had a production caller — a phantom security control
|
|
13
|
+
# (Plan 111, F5). Sandbox network policy is config-only
|
|
14
|
+
# (`sandbox.allow_network`); if an approval gate is ever wired in, its
|
|
15
|
+
# design intent is preserved in _plan/80-sandbox-mimari-refaktoru…md and
|
|
16
|
+
# git history.
|
|
17
|
+
module Approvals
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# Single-key yes/no. Returns the downcased answer String ("y"/"n"/...)
|
|
21
|
+
# exactly like Confirmations#ask_yes_no so call sites keep their word
|
|
22
|
+
# matching. Enter/Esc/EOF/Ctrl+C resolve to the default answer.
|
|
23
|
+
def keypress_yes_no(shell, question, theme:, default: "n")
|
|
24
|
+
default_answer = default.to_s.downcase
|
|
25
|
+
return default_answer unless Capability.interactive?(input: shell.input, output: shell.output)
|
|
26
|
+
|
|
27
|
+
hint = default_answer.start_with?("y") ? "Y/n" : "y/N"
|
|
28
|
+
prompt = build_prompt(shell)
|
|
29
|
+
|
|
30
|
+
answer = prompt.keypress("#{question} #{theme.paint("[#{hint}]", :dim)} ")
|
|
31
|
+
normalize_answer(answer, default_answer)
|
|
32
|
+
rescue Interrupt
|
|
33
|
+
Rubik::REPL::InterruptHandler.restore_console_after_interrupt
|
|
34
|
+
default_answer
|
|
35
|
+
rescue StandardError
|
|
36
|
+
default_answer
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def normalize_answer(answer, default_answer)
|
|
40
|
+
answer = answer.is_a?(String) ? answer.strip.downcase : ""
|
|
41
|
+
answer.empty? || answer == "\r" || answer == "\e" ? default_answer : answer
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# TTY::Prompt must talk to the real terminal: unwrap TeeIO capture the
|
|
45
|
+
# same way Confirmations#interactive_input? does.
|
|
46
|
+
def build_prompt(shell)
|
|
47
|
+
out = Capability.unwrap_tee(shell.output) || $stdout
|
|
48
|
+
TTY::Prompt.new(input: shell.input || $stdin, output: out)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../platform"
|
|
4
|
+
|
|
5
|
+
module Rubik
|
|
6
|
+
module UI
|
|
7
|
+
# Startup banner: brand, version, active model and quick hints inside a
|
|
8
|
+
# rounded frame. Only rendered for interactive TTY sessions honoring
|
|
9
|
+
# `ui.banner`; piped/scripted runs stay silent for clean automation.
|
|
10
|
+
module Banner
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def render(shell, theme)
|
|
14
|
+
return nil unless shell.config.fetch("ui.banner", true)
|
|
15
|
+
return nil unless Capability.interactive?(input: shell.input, output: shell.output)
|
|
16
|
+
|
|
17
|
+
routed = Statusline.current_route(shell)
|
|
18
|
+
model = routed ? Statusline.model_label(routed) : "model yapılandırılmamış"
|
|
19
|
+
project = File.basename(shell.buffer_manager&.root_dir || Dir.pwd)
|
|
20
|
+
branch = shell.detect_git_branch || "no-git"
|
|
21
|
+
|
|
22
|
+
lines = [
|
|
23
|
+
"🎲 Rubik v#{Rubik::VERSION}",
|
|
24
|
+
theme.paint(model, :info),
|
|
25
|
+
theme.paint("#{project} · #{branch}", :dim),
|
|
26
|
+
theme.paint("!help komutlar · ctrl+c kes · !config ayarlar", :dim)
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
if Rubik::Platform.linux? && Rubik::Platform.which("bwrap") &&
|
|
30
|
+
!shell.config.fetch("sandbox.enabled", false) &&
|
|
31
|
+
shell.config.fetch("sandbox.prompt_on_startup", true)
|
|
32
|
+
lines << theme.paint("💡 Linux & bwrap algılandı: '!sandbox on' ile donanımsal OS izolasyonu açılabilir", :warn)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
Frame.draw(
|
|
36
|
+
lines,
|
|
37
|
+
theme: theme,
|
|
38
|
+
title: theme.paint("rubik", :accent)
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "strings-ansi"
|
|
4
|
+
|
|
5
|
+
module Rubik
|
|
6
|
+
module UI
|
|
7
|
+
# Terminal capability detection: decides whether ANSI color, box-drawing
|
|
8
|
+
# and live spinner surfaces may be used for a given IO target.
|
|
9
|
+
#
|
|
10
|
+
# Resolution order:
|
|
11
|
+
# 1. RUBIK_TEST=1 -> never (deterministic test output)
|
|
12
|
+
# 2. NO_COLOR set -> never (https://no-color.org)
|
|
13
|
+
# 3. CLICOLOR_FORCE=1 -> always (CI with color-capable logs)
|
|
14
|
+
# 4. ui.color config: always|never -> explicit override
|
|
15
|
+
# 5. output not a TTY -> never (pipe/redirect safety)
|
|
16
|
+
# 6. TERM=dumb -> never
|
|
17
|
+
# 7. Windows legacy conhost -> try enabling VT, else never
|
|
18
|
+
module Capability
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
# Strips ANSI SGR escape sequences from a string. Used on the recording
|
|
22
|
+
# path (TeeIO -> StringIO) so session files and LLM context stay clean.
|
|
23
|
+
def strip_ansi(str)
|
|
24
|
+
return str unless str.is_a?(String) && str.include?("\e[")
|
|
25
|
+
|
|
26
|
+
Strings::ANSI.sanitize(str)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# True when ANSI color output is allowed for `io` under `config`.
|
|
30
|
+
def color?(io, config = nil)
|
|
31
|
+
case resolve_mode(io, config)
|
|
32
|
+
when :always then true
|
|
33
|
+
when :never then false
|
|
34
|
+
else io.respond_to?(:tty?) ? io.tty? : false
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# True when the target can animate a spinner line (real terminal only).
|
|
39
|
+
def spinner?(io, config = nil)
|
|
40
|
+
color?(io, config) && io.respond_to?(:tty?) && io.tty? && interactive_env?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def interactive_env?
|
|
44
|
+
ENV["RUBIK_TEST"] != "1"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# True when both the input and the *real* output are interactive TTYs.
|
|
48
|
+
# Single canonical interactivity predicate for UI surfaces: a TeeIO
|
|
49
|
+
# wrapping a real terminal (REPL command capture) still counts as
|
|
50
|
+
# interactive; pure StringIO captures and pipes never do.
|
|
51
|
+
def interactive?(input:, output:)
|
|
52
|
+
return false unless interactive_env?
|
|
53
|
+
|
|
54
|
+
out_io = unwrap_tee(output)
|
|
55
|
+
return false unless out_io
|
|
56
|
+
|
|
57
|
+
input.respond_to?(:tty?) && input.tty? &&
|
|
58
|
+
out_io.respond_to?(:tty?) && out_io.tty?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Resolves the real terminal behind a TeeIO (first target that is a
|
|
62
|
+
# live TTY); returns nil when every target is a capture buffer
|
|
63
|
+
# (StringIO#tty? is defined but false), and the IO itself when it is
|
|
64
|
+
# not a TeeIO.
|
|
65
|
+
def unwrap_tee(io)
|
|
66
|
+
return io unless io.is_a?(Rubik::REPL::TeeIO)
|
|
67
|
+
|
|
68
|
+
io.targets.compact.find(&:tty?)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def resolve_mode(io, config)
|
|
72
|
+
return :never if ENV["RUBIK_TEST"] == "1"
|
|
73
|
+
return :never if ENV["NO_COLOR"] && !ENV["NO_COLOR"].to_s.empty?
|
|
74
|
+
return :always if ENV["CLICOLOR_FORCE"] && ENV["CLICOLOR_FORCE"].to_s == "1"
|
|
75
|
+
|
|
76
|
+
cfg_mode = config&.fetch("ui.color", "auto").to_s.downcase if config
|
|
77
|
+
return :always if cfg_mode == "always"
|
|
78
|
+
return :never if cfg_mode == "never"
|
|
79
|
+
|
|
80
|
+
return :never if ENV["TERM"] && ENV["TERM"].downcase == "dumb"
|
|
81
|
+
return :never unless io.respond_to?(:tty?) && io.tty?
|
|
82
|
+
|
|
83
|
+
enable_windows_virtual_terminal_if_needed
|
|
84
|
+
:auto
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# On legacy Windows consoles ANSI sequences are only rendered after
|
|
88
|
+
# ENABLE_VIRTUAL_TERMINAL_PROCESSING is set on the output handle.
|
|
89
|
+
# Modern terminals (Windows Terminal, WezTerm, Alacritty) already have
|
|
90
|
+
# it on; this is a best-effort, failure-tolerant probe.
|
|
91
|
+
def enable_windows_virtual_terminal_if_needed
|
|
92
|
+
return unless Gem.win_platform?
|
|
93
|
+
return if @vt_enabled
|
|
94
|
+
|
|
95
|
+
@vt_enabled = begin
|
|
96
|
+
require "fiddle"
|
|
97
|
+
kernel32 = Fiddle.dlopen("kernel32.dll")
|
|
98
|
+
get_std_handle = Fiddle::Function.new(kernel32["GetStdHandle"], [Fiddle::TYPE_LONG], Fiddle::TYPE_LONG)
|
|
99
|
+
get_console_mode = Fiddle::Function.new(kernel32["GetConsoleMode"],
|
|
100
|
+
[Fiddle::TYPE_LONG, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
|
|
101
|
+
set_console_mode = Fiddle::Function.new(kernel32["SetConsoleMode"],
|
|
102
|
+
[Fiddle::TYPE_LONG, Fiddle::TYPE_LONG], Fiddle::TYPE_INT)
|
|
103
|
+
handle = get_std_handle.call(-11) # STD_OUTPUT_HANDLE
|
|
104
|
+
mode_buf = [0].pack("L")
|
|
105
|
+
ok = get_console_mode.call(handle, mode_buf)
|
|
106
|
+
if ok != 0
|
|
107
|
+
mode = mode_buf.unpack1("L")
|
|
108
|
+
vt_flag = 0x0004 # ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
|
109
|
+
set_console_mode.call(handle, mode | vt_flag) != 0 if mode.nobits?(vt_flag)
|
|
110
|
+
end
|
|
111
|
+
true
|
|
112
|
+
rescue StandardError
|
|
113
|
+
false
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def reset_memoization!
|
|
118
|
+
@vt_enabled = nil
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-prompt"
|
|
4
|
+
require_relative "capability"
|
|
5
|
+
require_relative "frame"
|
|
6
|
+
require_relative "theme"
|
|
7
|
+
require_relative "../repl/interrupt_handler"
|
|
8
|
+
|
|
9
|
+
module Rubik
|
|
10
|
+
module UI
|
|
11
|
+
# Interactive question and decision dialog for the human operator (Plan 92).
|
|
12
|
+
# Presents single-choice and multi-choice menus with clean ANSI/VT100 cards
|
|
13
|
+
# and handles keyboard interrupts gracefully.
|
|
14
|
+
class Dialog
|
|
15
|
+
attr_reader :shell, :theme
|
|
16
|
+
|
|
17
|
+
def initialize(shell: nil, theme: nil, prompt: nil)
|
|
18
|
+
@shell = shell
|
|
19
|
+
@theme = theme || Theme.for(shell&.output || $stdout, shell&.config)
|
|
20
|
+
@prompt = prompt
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Prompts the user with a single-choice question.
|
|
24
|
+
#
|
|
25
|
+
# @param question [String] Question text
|
|
26
|
+
# @param options [Array<String>] List of selectable options
|
|
27
|
+
# @param allow_custom [Boolean] Whether to provide a free-text write-in option
|
|
28
|
+
# @return [String, Symbol] Selected answer, custom string, or :cancelled
|
|
29
|
+
def ask_single(question, options, allow_custom: true)
|
|
30
|
+
return :non_interactive unless interactive?
|
|
31
|
+
|
|
32
|
+
render_card(question, multi: false)
|
|
33
|
+
p = build_prompt
|
|
34
|
+
choices = options.uniq.dup
|
|
35
|
+
choices << "Other (write-in response)" if allow_custom
|
|
36
|
+
|
|
37
|
+
answer = p.select("Seçiminizi yapın / Select an option:", choices, filter: true, per_page: 10)
|
|
38
|
+
if answer == "Other (write-in response)"
|
|
39
|
+
custom = p.ask("Yanıtınız / Your answer:")
|
|
40
|
+
custom.to_s.strip.empty? ? options.first : "Other (write-in): #{custom.strip}"
|
|
41
|
+
else
|
|
42
|
+
answer
|
|
43
|
+
end
|
|
44
|
+
rescue Interrupt, StandardError
|
|
45
|
+
:cancelled
|
|
46
|
+
ensure
|
|
47
|
+
restore_console
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Prompts the user with a multi-choice question.
|
|
51
|
+
#
|
|
52
|
+
# @param question [String] Question text
|
|
53
|
+
# @param options [Array<String>] List of selectable options
|
|
54
|
+
# @return [Array<String>, Symbol] Array of selected answers, or :cancelled
|
|
55
|
+
def ask_multi(question, options)
|
|
56
|
+
return :non_interactive unless interactive?
|
|
57
|
+
|
|
58
|
+
render_card(question, multi: true)
|
|
59
|
+
p = build_prompt
|
|
60
|
+
answers = p.multi_select("Seçenekleri belirleyin / Select options (Space: toggle, Enter: confirm):",
|
|
61
|
+
options.uniq, filter: true, per_page: 10)
|
|
62
|
+
answers.empty? ? [options.first] : answers
|
|
63
|
+
rescue Interrupt, StandardError
|
|
64
|
+
:cancelled
|
|
65
|
+
ensure
|
|
66
|
+
restore_console
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# True when the input/output streams are interactive TTYs.
|
|
70
|
+
def interactive?
|
|
71
|
+
return true if @prompt # Injected prompt for test mockability
|
|
72
|
+
|
|
73
|
+
inp = @shell&.input || $stdin
|
|
74
|
+
out = @shell&.output || $stdout
|
|
75
|
+
Capability.interactive?(input: inp, output: out)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def render_card(question, multi: false)
|
|
81
|
+
return unless @shell&.output
|
|
82
|
+
|
|
83
|
+
subtitle = multi ? "Çoklu Seçim (Boşluk: Seç/Kaldır · Enter: Onayla)" : "Tekli Seçim (Ok tuşları: Gezin · Enter: Onayla)"
|
|
84
|
+
card = Frame.draw(
|
|
85
|
+
[@theme.paint(question, :bold), @theme.paint(subtitle, :dim)],
|
|
86
|
+
theme: @theme,
|
|
87
|
+
title: @theme.paint("#{@theme.glyph(:action)} AJAN SORUSU", :accent)
|
|
88
|
+
)
|
|
89
|
+
@shell.output.puts(card)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def build_prompt
|
|
93
|
+
return @prompt if @prompt
|
|
94
|
+
|
|
95
|
+
out = Capability.unwrap_tee(@shell&.output) || $stdout
|
|
96
|
+
TTY::Prompt.new(input: @shell&.input || $stdin, output: out)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def restore_console
|
|
100
|
+
Rubik::REPL::InterruptHandler.restore_console_after_interrupt
|
|
101
|
+
rescue StandardError
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|