dotdo 0.0.1 → 0.0.2
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/LICENSE +1 -1
- package/README.md +446 -315
- package/dist/ai/index.js +19 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/template-literals.js +852 -0
- package/dist/ai/template-literals.js.map +1 -0
- package/dist/api/analytics/router.js +601 -0
- package/dist/api/analytics/router.js.map +1 -0
- package/dist/api/index.js +158 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/middleware/auth-federation.js +573 -0
- package/dist/api/middleware/auth-federation.js.map +1 -0
- package/dist/api/middleware/auth.js +544 -0
- package/dist/api/middleware/auth.js.map +1 -0
- package/dist/api/middleware/error-handling.js +176 -0
- package/dist/api/middleware/error-handling.js.map +1 -0
- package/dist/api/middleware/request-id.js +21 -0
- package/dist/api/middleware/request-id.js.map +1 -0
- package/dist/api/pages.js +1180 -0
- package/dist/api/pages.js.map +1 -0
- package/dist/api/routes/api.js +612 -0
- package/dist/api/routes/api.js.map +1 -0
- package/dist/api/routes/browsers.js +471 -0
- package/dist/api/routes/browsers.js.map +1 -0
- package/dist/api/routes/do.js +188 -0
- package/dist/api/routes/do.js.map +1 -0
- package/dist/api/routes/mcp.js +459 -0
- package/dist/api/routes/mcp.js.map +1 -0
- package/dist/api/routes/obs.js +445 -0
- package/dist/api/routes/obs.js.map +1 -0
- package/dist/api/routes/openapi.js +794 -0
- package/dist/api/routes/openapi.js.map +1 -0
- package/dist/api/routes/rpc.js +1103 -0
- package/dist/api/routes/rpc.js.map +1 -0
- package/dist/api/routes/sandboxes.js +389 -0
- package/dist/api/routes/sandboxes.js.map +1 -0
- package/dist/api/test-do.js +38 -0
- package/dist/api/test-do.js.map +1 -0
- package/dist/api/types.js +11 -0
- package/dist/api/types.js.map +1 -0
- package/dist/cli/bin.js +2 -0
- package/dist/cli/main.js +52342 -0
- package/dist/db/actions.js +212 -0
- package/dist/db/actions.js.map +1 -0
- package/dist/db/auth.js +506 -0
- package/dist/db/auth.js.map +1 -0
- package/dist/db/branches.js +65 -0
- package/dist/db/branches.js.map +1 -0
- package/dist/db/clickhouse.js +1074 -0
- package/dist/db/clickhouse.js.map +1 -0
- package/dist/db/dlq.js +39 -0
- package/dist/db/dlq.js.map +1 -0
- package/dist/db/events.js +28 -0
- package/dist/db/events.js.map +1 -0
- package/dist/db/exec.js +64 -0
- package/dist/db/exec.js.map +1 -0
- package/dist/db/files.js +85 -0
- package/dist/db/files.js.map +1 -0
- package/dist/db/flags.js +24 -0
- package/dist/db/flags.js.map +1 -0
- package/dist/db/git.js +116 -0
- package/dist/db/git.js.map +1 -0
- package/dist/db/iceberg/inverted-index.js +862 -0
- package/dist/db/iceberg/inverted-index.js.map +1 -0
- package/dist/db/iceberg/puffin.js +878 -0
- package/dist/db/iceberg/puffin.js.map +1 -0
- package/dist/db/iceberg/search-manifest.js +422 -0
- package/dist/db/iceberg/search-manifest.js.map +1 -0
- package/dist/db/iceberg/types.js +8 -0
- package/dist/db/iceberg/types.js.map +1 -0
- package/dist/db/index.js +121 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/integrations.js +368 -0
- package/dist/db/integrations.js.map +1 -0
- package/dist/db/json-indexes.js +332 -0
- package/dist/db/json-indexes.js.map +1 -0
- package/dist/db/linked-accounts.js +287 -0
- package/dist/db/linked-accounts.js.map +1 -0
- package/dist/db/nouns.js +183 -0
- package/dist/db/nouns.js.map +1 -0
- package/dist/db/objects.js +170 -0
- package/dist/db/objects.js.map +1 -0
- package/dist/db/primitives/dag-scheduler/index.js +869 -0
- package/dist/db/primitives/dag-scheduler/index.js.map +1 -0
- package/dist/db/primitives/exactly-once-context.js +237 -0
- package/dist/db/primitives/exactly-once-context.js.map +1 -0
- package/dist/db/primitives/index.js +62 -0
- package/dist/db/primitives/index.js.map +1 -0
- package/dist/db/primitives/keyed-router.js +145 -0
- package/dist/db/primitives/keyed-router.js.map +1 -0
- package/dist/db/primitives/observability.js +162 -0
- package/dist/db/primitives/observability.js.map +1 -0
- package/dist/db/primitives/schema-evolution.js +643 -0
- package/dist/db/primitives/schema-evolution.js.map +1 -0
- package/dist/db/primitives/stateful-operator/index.js +770 -0
- package/dist/db/primitives/stateful-operator/index.js.map +1 -0
- package/dist/db/primitives/temporal-store.js +306 -0
- package/dist/db/primitives/temporal-store.js.map +1 -0
- package/dist/db/primitives/typed-column-store.js +1229 -0
- package/dist/db/primitives/typed-column-store.js.map +1 -0
- package/dist/db/primitives/utils/duration.js +162 -0
- package/dist/db/primitives/utils/duration.js.map +1 -0
- package/dist/db/primitives/utils/murmur3.js +118 -0
- package/dist/db/primitives/utils/murmur3.js.map +1 -0
- package/dist/db/primitives/watermark-service.js +136 -0
- package/dist/db/primitives/watermark-service.js.map +1 -0
- package/dist/db/primitives/window-manager.js +764 -0
- package/dist/db/primitives/window-manager.js.map +1 -0
- package/dist/db/relationships.js +66 -0
- package/dist/db/relationships.js.map +1 -0
- package/dist/db/schema-minimal.js +61 -0
- package/dist/db/schema-minimal.js.map +1 -0
- package/dist/db/search.js +28 -0
- package/dist/db/search.js.map +1 -0
- package/dist/db/stores.js +1665 -0
- package/dist/db/stores.js.map +1 -0
- package/dist/db/things.js +297 -0
- package/dist/db/things.js.map +1 -0
- package/dist/db/vault.js +171 -0
- package/dist/db/vault.js.map +1 -0
- package/dist/db/verbs.js +102 -0
- package/dist/db/verbs.js.map +1 -0
- package/dist/do/base.js +48 -0
- package/dist/do/base.js.map +1 -0
- package/dist/do/bash.js +35 -0
- package/dist/do/bash.js.map +1 -0
- package/dist/do/fs.js +25 -0
- package/dist/do/fs.js.map +1 -0
- package/dist/do/full.js +61 -0
- package/dist/do/full.js.map +1 -0
- package/dist/do/git.js +28 -0
- package/dist/do/git.js.map +1 -0
- package/dist/do/index.js +52 -0
- package/dist/do/index.js.map +1 -0
- package/dist/do/tiny.js +31 -0
- package/dist/do/tiny.js.map +1 -0
- package/dist/lib/DOAuth.js +261 -0
- package/dist/lib/DOAuth.js.map +1 -0
- package/dist/lib/DODispatcher.js +72 -0
- package/dist/lib/DODispatcher.js.map +1 -0
- package/dist/lib/Modifier.js +189 -0
- package/dist/lib/Modifier.js.map +1 -0
- package/dist/lib/StateStorage.js +403 -0
- package/dist/lib/StateStorage.js.map +1 -0
- package/dist/lib/TypeRegistry.js +122 -0
- package/dist/lib/TypeRegistry.js.map +1 -0
- package/dist/lib/agent/tools/bash.js +336 -0
- package/dist/lib/agent/tools/bash.js.map +1 -0
- package/dist/lib/agent/tools/edit.js +157 -0
- package/dist/lib/agent/tools/edit.js.map +1 -0
- package/dist/lib/agent/tools/glob.js +137 -0
- package/dist/lib/agent/tools/glob.js.map +1 -0
- package/dist/lib/agent/tools/grep.js +315 -0
- package/dist/lib/agent/tools/grep.js.map +1 -0
- package/dist/lib/agent/tools/index.js +71 -0
- package/dist/lib/agent/tools/index.js.map +1 -0
- package/dist/lib/agent/tools/read.js +212 -0
- package/dist/lib/agent/tools/read.js.map +1 -0
- package/dist/lib/agent/tools/types.js +197 -0
- package/dist/lib/agent/tools/types.js.map +1 -0
- package/dist/lib/agent/tools/write.js +159 -0
- package/dist/lib/agent/tools/write.js.map +1 -0
- package/dist/lib/ai/gateway.js +247 -0
- package/dist/lib/ai/gateway.js.map +1 -0
- package/dist/lib/ai/tool-loop-agent.js +591 -0
- package/dist/lib/ai/tool-loop-agent.js.map +1 -0
- package/dist/lib/auto-wiring.js +439 -0
- package/dist/lib/auto-wiring.js.map +1 -0
- package/dist/lib/browse/browserbase.js +163 -0
- package/dist/lib/browse/browserbase.js.map +1 -0
- package/dist/lib/browse/cloudflare.js +144 -0
- package/dist/lib/browse/cloudflare.js.map +1 -0
- package/dist/lib/browse/index.js +62 -0
- package/dist/lib/browse/index.js.map +1 -0
- package/dist/lib/browse/types.js +13 -0
- package/dist/lib/browse/types.js.map +1 -0
- package/dist/lib/cache/index.js +37 -0
- package/dist/lib/cache/index.js.map +1 -0
- package/dist/lib/cache/visibility.js +638 -0
- package/dist/lib/cache/visibility.js.map +1 -0
- package/dist/lib/capabilities.js +268 -0
- package/dist/lib/capabilities.js.map +1 -0
- package/dist/lib/channels/base.js +106 -0
- package/dist/lib/channels/base.js.map +1 -0
- package/dist/lib/channels/discord.js +94 -0
- package/dist/lib/channels/discord.js.map +1 -0
- package/dist/lib/channels/email.js +204 -0
- package/dist/lib/channels/email.js.map +1 -0
- package/dist/lib/channels/index.js +90 -0
- package/dist/lib/channels/index.js.map +1 -0
- package/dist/lib/channels/mdxui-chat.js +95 -0
- package/dist/lib/channels/mdxui-chat.js.map +1 -0
- package/dist/lib/channels/slack-blockkit.js +121 -0
- package/dist/lib/channels/slack-blockkit.js.map +1 -0
- package/dist/lib/channels/types.js +7 -0
- package/dist/lib/channels/types.js.map +1 -0
- package/dist/lib/cloudflare/ai.js +654 -0
- package/dist/lib/cloudflare/ai.js.map +1 -0
- package/dist/lib/cloudflare/index.js +88 -0
- package/dist/lib/cloudflare/index.js.map +1 -0
- package/dist/lib/cloudflare/kv.js +342 -0
- package/dist/lib/cloudflare/kv.js.map +1 -0
- package/dist/lib/cloudflare/queues.js +434 -0
- package/dist/lib/cloudflare/queues.js.map +1 -0
- package/dist/lib/cloudflare/r2.js +604 -0
- package/dist/lib/cloudflare/r2.js.map +1 -0
- package/dist/lib/cloudflare/vectorize.js +494 -0
- package/dist/lib/cloudflare/vectorize.js.map +1 -0
- package/dist/lib/cloudflare/workflows.js +569 -0
- package/dist/lib/cloudflare/workflows.js.map +1 -0
- package/dist/lib/colo/caching.js +196 -0
- package/dist/lib/colo/caching.js.map +1 -0
- package/dist/lib/colo/detection.js +194 -0
- package/dist/lib/colo/detection.js.map +1 -0
- package/dist/lib/colo/external-data.js +219 -0
- package/dist/lib/colo/external-data.js.map +1 -0
- package/dist/lib/colo/globe-data.js +179 -0
- package/dist/lib/colo/globe-data.js.map +1 -0
- package/dist/lib/colo/index.js +16 -0
- package/dist/lib/colo/index.js.map +1 -0
- package/dist/lib/decorators.js +37 -0
- package/dist/lib/decorators.js.map +1 -0
- package/dist/lib/discovery.js +81 -0
- package/dist/lib/discovery.js.map +1 -0
- package/dist/lib/executors/AgenticFunctionExecutor.js +619 -0
- package/dist/lib/executors/AgenticFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/BaseFunctionExecutor.js +328 -0
- package/dist/lib/executors/BaseFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/CascadeExecutor.js +418 -0
- package/dist/lib/executors/CascadeExecutor.js.map +1 -0
- package/dist/lib/executors/CodeFunctionExecutor.js +904 -0
- package/dist/lib/executors/CodeFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/GenerativeFunctionExecutor.js +904 -0
- package/dist/lib/executors/GenerativeFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/HumanFunctionExecutor.js +884 -0
- package/dist/lib/executors/HumanFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/ParallelStepExecutor.js +308 -0
- package/dist/lib/executors/ParallelStepExecutor.js.map +1 -0
- package/dist/lib/executors/types.js +12 -0
- package/dist/lib/executors/types.js.map +1 -0
- package/dist/lib/experiments.js +89 -0
- package/dist/lib/experiments.js.map +1 -0
- package/dist/lib/flags/store.js +262 -0
- package/dist/lib/flags/store.js.map +1 -0
- package/dist/lib/functions/FunctionComposition.js +467 -0
- package/dist/lib/functions/FunctionComposition.js.map +1 -0
- package/dist/lib/functions/FunctionMiddleware.js +457 -0
- package/dist/lib/functions/FunctionMiddleware.js.map +1 -0
- package/dist/lib/functions/FunctionRegistry.js +426 -0
- package/dist/lib/functions/FunctionRegistry.js.map +1 -0
- package/dist/lib/functions/createFunction.js +1048 -0
- package/dist/lib/functions/createFunction.js.map +1 -0
- package/dist/lib/humans/index.js +68 -0
- package/dist/lib/humans/index.js.map +1 -0
- package/dist/lib/humans/templates.js +117 -0
- package/dist/lib/humans/templates.js.map +1 -0
- package/dist/lib/identity.js +98 -0
- package/dist/lib/identity.js.map +1 -0
- package/dist/lib/index.js +9 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/logging/error-logger.js +163 -0
- package/dist/lib/logging/error-logger.js.map +1 -0
- package/dist/lib/logging/index.js +160 -0
- package/dist/lib/logging/index.js.map +1 -0
- package/dist/lib/mixins/bash.js +825 -0
- package/dist/lib/mixins/bash.js.map +1 -0
- package/dist/lib/mixins/fs.js +648 -0
- package/dist/lib/mixins/fs.js.map +1 -0
- package/dist/lib/mixins/git.js +1011 -0
- package/dist/lib/mixins/git.js.map +1 -0
- package/dist/lib/mixins/index.js +29 -0
- package/dist/lib/mixins/index.js.map +1 -0
- package/dist/lib/mixins/npm.js +662 -0
- package/dist/lib/mixins/npm.js.map +1 -0
- package/dist/lib/noun-id.js +278 -0
- package/dist/lib/noun-id.js.map +1 -0
- package/dist/lib/rate-limit/sliding-window.js +148 -0
- package/dist/lib/rate-limit/sliding-window.js.map +1 -0
- package/dist/lib/rate-limit.js +110 -0
- package/dist/lib/rate-limit.js.map +1 -0
- package/dist/lib/rpc/bindings.js +548 -0
- package/dist/lib/rpc/bindings.js.map +1 -0
- package/dist/lib/rpc/index.js +64 -0
- package/dist/lib/rpc/index.js.map +1 -0
- package/dist/lib/safe-stringify.js +223 -0
- package/dist/lib/safe-stringify.js.map +1 -0
- package/dist/lib/sandbox/miniflare-sandbox.js +1007 -0
- package/dist/lib/sandbox/miniflare-sandbox.js.map +1 -0
- package/dist/lib/sqids.js +110 -0
- package/dist/lib/sqids.js.map +1 -0
- package/dist/lib/sql/adapters/index.js +10 -0
- package/dist/lib/sql/adapters/index.js.map +1 -0
- package/dist/lib/sql/adapters/node-sql-parser.js +552 -0
- package/dist/lib/sql/adapters/node-sql-parser.js.map +1 -0
- package/dist/lib/sql/adapters/pgsql-parser.js +1189 -0
- package/dist/lib/sql/adapters/pgsql-parser.js.map +1 -0
- package/dist/lib/sql/index.js +277 -0
- package/dist/lib/sql/index.js.map +1 -0
- package/dist/lib/sql/types.js +56 -0
- package/dist/lib/sql/types.js.map +1 -0
- package/dist/lib/type-classifier.js +126 -0
- package/dist/lib/type-classifier.js.map +1 -0
- package/dist/lib/utils/html.js +47 -0
- package/dist/lib/utils/html.js.map +1 -0
- package/dist/lib/validation.js +48 -0
- package/dist/lib/validation.js.map +1 -0
- package/dist/lib/vault/store.js +411 -0
- package/dist/lib/vault/store.js.map +1 -0
- package/dist/metrics/hunch.js +739 -0
- package/dist/metrics/hunch.js.map +1 -0
- package/dist/objects/API.js +302 -0
- package/dist/objects/API.js.map +1 -0
- package/dist/objects/Agent.js +179 -0
- package/dist/objects/Agent.js.map +1 -0
- package/dist/objects/AgenticFunctionExecutor.js +8 -0
- package/dist/objects/AgenticFunctionExecutor.js.map +1 -0
- package/dist/objects/App.js +83 -0
- package/dist/objects/App.js.map +1 -0
- package/dist/objects/Browser.js +884 -0
- package/dist/objects/Browser.js.map +1 -0
- package/dist/objects/Business.js +107 -0
- package/dist/objects/Business.js.map +1 -0
- package/dist/objects/CLI.js +221 -0
- package/dist/objects/CLI.js.map +1 -0
- package/dist/objects/CodeFunctionExecutor.js +8 -0
- package/dist/objects/CodeFunctionExecutor.js.map +1 -0
- package/dist/objects/Collection.js +161 -0
- package/dist/objects/Collection.js.map +1 -0
- package/dist/objects/DO.js +41 -0
- package/dist/objects/DO.js.map +1 -0
- package/dist/objects/DOBase.js +2309 -0
- package/dist/objects/DOBase.js.map +1 -0
- package/dist/objects/DOFull.js +1676 -0
- package/dist/objects/DOFull.js.map +1 -0
- package/dist/objects/DOTiny.js +207 -0
- package/dist/objects/DOTiny.js.map +1 -0
- package/dist/objects/Directory.js +199 -0
- package/dist/objects/Directory.js.map +1 -0
- package/dist/objects/Entity.js +413 -0
- package/dist/objects/Entity.js.map +1 -0
- package/dist/objects/Function.js +116 -0
- package/dist/objects/Function.js.map +1 -0
- package/dist/objects/Human.js +231 -0
- package/dist/objects/Human.js.map +1 -0
- package/dist/objects/HumanFunctionExecutor.js +8 -0
- package/dist/objects/HumanFunctionExecutor.js.map +1 -0
- package/dist/objects/IcebergMetadataDO.js +938 -0
- package/dist/objects/IcebergMetadataDO.js.map +1 -0
- package/dist/objects/IntegrationsDO.js +1174 -0
- package/dist/objects/IntegrationsDO.js.map +1 -0
- package/dist/objects/ObservabilityBroadcaster.js +149 -0
- package/dist/objects/ObservabilityBroadcaster.js.map +1 -0
- package/dist/objects/Package.js +154 -0
- package/dist/objects/Package.js.map +1 -0
- package/dist/objects/Product.js +193 -0
- package/dist/objects/Product.js.map +1 -0
- package/dist/objects/SDK.js +152 -0
- package/dist/objects/SDK.js.map +1 -0
- package/dist/objects/SaaS.js +235 -0
- package/dist/objects/SaaS.js.map +1 -0
- package/dist/objects/SandboxDO.js +759 -0
- package/dist/objects/SandboxDO.js.map +1 -0
- package/dist/objects/Service.js +337 -0
- package/dist/objects/Service.js.map +1 -0
- package/dist/objects/Site.js +80 -0
- package/dist/objects/Site.js.map +1 -0
- package/dist/objects/Startup.js +479 -0
- package/dist/objects/Startup.js.map +1 -0
- package/dist/objects/ThingsDO.js +170 -0
- package/dist/objects/ThingsDO.js.map +1 -0
- package/dist/objects/VectorShardDO.js +648 -0
- package/dist/objects/VectorShardDO.js.map +1 -0
- package/dist/objects/Worker.js +144 -0
- package/dist/objects/Worker.js.map +1 -0
- package/dist/objects/Workflow.js +196 -0
- package/dist/objects/Workflow.js.map +1 -0
- package/dist/objects/WorkflowFactory.js +313 -0
- package/dist/objects/WorkflowFactory.js.map +1 -0
- package/dist/objects/WorkflowRuntime.js +863 -0
- package/dist/objects/WorkflowRuntime.js.map +1 -0
- package/dist/objects/circuit-breaker-bulkhead.js +178 -0
- package/dist/objects/circuit-breaker-bulkhead.js.map +1 -0
- package/dist/objects/createFunction.js +934 -0
- package/dist/objects/createFunction.js.map +1 -0
- package/dist/objects/index.js +80 -0
- package/dist/objects/index.js.map +1 -0
- package/dist/objects/lifecycle/Branch.js +275 -0
- package/dist/objects/lifecycle/Branch.js.map +1 -0
- package/dist/objects/lifecycle/Clone.js +1499 -0
- package/dist/objects/lifecycle/Clone.js.map +1 -0
- package/dist/objects/lifecycle/Compact.js +237 -0
- package/dist/objects/lifecycle/Compact.js.map +1 -0
- package/dist/objects/lifecycle/Promote.js +476 -0
- package/dist/objects/lifecycle/Promote.js.map +1 -0
- package/dist/objects/lifecycle/Shard.js +560 -0
- package/dist/objects/lifecycle/Shard.js.map +1 -0
- package/dist/objects/lifecycle/index.js +15 -0
- package/dist/objects/lifecycle/index.js.map +1 -0
- package/dist/objects/lifecycle/types.js +33 -0
- package/dist/objects/lifecycle/types.js.map +1 -0
- package/dist/objects/mixins/infrastructure.js +171 -0
- package/dist/objects/mixins/infrastructure.js.map +1 -0
- package/dist/objects/modules/StoresModule.js +153 -0
- package/dist/objects/modules/StoresModule.js.map +1 -0
- package/dist/objects/persistence/checkpoint-manager.js +606 -0
- package/dist/objects/persistence/checkpoint-manager.js.map +1 -0
- package/dist/objects/persistence/index.js +72 -0
- package/dist/objects/persistence/index.js.map +1 -0
- package/dist/objects/persistence/migration-runner.js +562 -0
- package/dist/objects/persistence/migration-runner.js.map +1 -0
- package/dist/objects/persistence/replication-manager.js +501 -0
- package/dist/objects/persistence/replication-manager.js.map +1 -0
- package/dist/objects/persistence/tiered-storage-manager.js +595 -0
- package/dist/objects/persistence/tiered-storage-manager.js.map +1 -0
- package/dist/objects/persistence/types.js +14 -0
- package/dist/objects/persistence/types.js.map +1 -0
- package/dist/objects/persistence/wal-manager.js +653 -0
- package/dist/objects/persistence/wal-manager.js.map +1 -0
- package/dist/objects/presets/index.js +20 -0
- package/dist/objects/presets/index.js.map +1 -0
- package/dist/objects/presets/primitives.js +188 -0
- package/dist/objects/presets/primitives.js.map +1 -0
- package/dist/objects/primitives/alarm-adapter.js +141 -0
- package/dist/objects/primitives/alarm-adapter.js.map +1 -0
- package/dist/objects/primitives/index.js +337 -0
- package/dist/objects/primitives/index.js.map +1 -0
- package/dist/objects/primitives/storage-adapter.js +182 -0
- package/dist/objects/primitives/storage-adapter.js.map +1 -0
- package/dist/objects/primitives/with-primitives.js +102 -0
- package/dist/objects/primitives/with-primitives.js.map +1 -0
- package/dist/objects/services/StoreManager.js +227 -0
- package/dist/objects/services/StoreManager.js.map +1 -0
- package/dist/objects/services/index.js +13 -0
- package/dist/objects/services/index.js.map +1 -0
- package/dist/objects/transport/auth-layer.js +1451 -0
- package/dist/objects/transport/auth-layer.js.map +1 -0
- package/dist/objects/transport/capnweb-target.js +355 -0
- package/dist/objects/transport/capnweb-target.js.map +1 -0
- package/dist/objects/transport/chain.js +441 -0
- package/dist/objects/transport/chain.js.map +1 -0
- package/dist/objects/transport/handler.js +58 -0
- package/dist/objects/transport/handler.js.map +1 -0
- package/dist/objects/transport/index.js +53 -0
- package/dist/objects/transport/index.js.map +1 -0
- package/dist/objects/transport/mcp-server.js +690 -0
- package/dist/objects/transport/mcp-server.js.map +1 -0
- package/dist/objects/transport/rest-autowire.js +1507 -0
- package/dist/objects/transport/rest-autowire.js.map +1 -0
- package/dist/objects/transport/rest-router.js +440 -0
- package/dist/objects/transport/rest-router.js.map +1 -0
- package/dist/objects/transport/rpc-server.js +1536 -0
- package/dist/objects/transport/rpc-server.js.map +1 -0
- package/dist/objects/transport/shared.js +575 -0
- package/dist/objects/transport/shared.js.map +1 -0
- package/dist/objects/transport/sync-engine.js +291 -0
- package/dist/objects/transport/sync-engine.js.map +1 -0
- package/dist/objects/transport/types.js +8 -0
- package/dist/objects/transport/types.js.map +1 -0
- package/dist/primitives/bashx/src/ast/analyze.js +1472 -0
- package/dist/primitives/bashx/src/ast/analyze.js.map +1 -0
- package/dist/primitives/bashx/src/ast/parser.js +1488 -0
- package/dist/primitives/bashx/src/ast/parser.js.map +1 -0
- package/dist/primitives/bashx/src/do/commands/crypto.js +1954 -0
- package/dist/primitives/bashx/src/do/commands/crypto.js.map +1 -0
- package/dist/primitives/bashx/src/do/commands/data-processing.js +1812 -0
- package/dist/primitives/bashx/src/do/commands/data-processing.js.map +1 -0
- package/dist/primitives/bashx/src/do/commands/extended-utils.js +804 -0
- package/dist/primitives/bashx/src/do/commands/extended-utils.js.map +1 -0
- package/dist/primitives/bashx/src/do/commands/math-control.js +1122 -0
- package/dist/primitives/bashx/src/do/commands/math-control.js.map +1 -0
- package/dist/primitives/bashx/src/do/commands/posix-utils.js +1015 -0
- package/dist/primitives/bashx/src/do/commands/posix-utils.js.map +1 -0
- package/dist/primitives/bashx/src/do/commands/system-utils.js +687 -0
- package/dist/primitives/bashx/src/do/commands/system-utils.js.map +1 -0
- package/dist/primitives/bashx/src/do/commands/test-command.js +523 -0
- package/dist/primitives/bashx/src/do/commands/test-command.js.map +1 -0
- package/dist/primitives/bashx/src/do/commands/text-processing.js +1550 -0
- package/dist/primitives/bashx/src/do/commands/text-processing.js.map +1 -0
- package/dist/primitives/bashx/src/do/container-executor.js +429 -0
- package/dist/primitives/bashx/src/do/container-executor.js.map +1 -0
- package/dist/primitives/bashx/src/do/index.js +668 -0
- package/dist/primitives/bashx/src/do/index.js.map +1 -0
- package/dist/primitives/bashx/src/do/tiered-executor.js +2647 -0
- package/dist/primitives/bashx/src/do/tiered-executor.js.map +1 -0
- package/dist/primitives/bashx/src/do/worker.js +352 -0
- package/dist/primitives/bashx/src/do/worker.js.map +1 -0
- package/dist/primitives/bashx/src/types.js +10 -0
- package/dist/primitives/bashx/src/types.js.map +1 -0
- package/dist/primitives/fsx/core/backend.js +480 -0
- package/dist/primitives/fsx/core/backend.js.map +1 -0
- package/dist/primitives/fsx/core/constants.js +140 -0
- package/dist/primitives/fsx/core/constants.js.map +1 -0
- package/dist/primitives/fsx/core/fsx.js +1184 -0
- package/dist/primitives/fsx/core/fsx.js.map +1 -0
- package/dist/primitives/fsx/core/glob/glob.js +438 -0
- package/dist/primitives/fsx/core/glob/glob.js.map +1 -0
- package/dist/primitives/fsx/core/glob/index.js +8 -0
- package/dist/primitives/fsx/core/glob/index.js.map +1 -0
- package/dist/primitives/fsx/core/glob/match.js +392 -0
- package/dist/primitives/fsx/core/glob/match.js.map +1 -0
- package/dist/primitives/fsx/core/types.js +307 -0
- package/dist/primitives/fsx/core/types.js.map +1 -0
- package/dist/sandbox/index.js +258 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sdk/capnweb-compat.js +42 -0
- package/dist/sdk/capnweb-compat.js.map +1 -0
- package/dist/sdk/client.js +20 -0
- package/dist/sdk/client.js.map +1 -0
- package/dist/sdk/index.js +17 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/snippets/artifacts-config.js +241 -0
- package/dist/snippets/artifacts-config.js.map +1 -0
- package/dist/snippets/artifacts-ingest.js +832 -0
- package/dist/snippets/artifacts-ingest.js.map +1 -0
- package/dist/snippets/artifacts-serve.js +1035 -0
- package/dist/snippets/artifacts-serve.js.map +1 -0
- package/dist/snippets/artifacts-types.js +161 -0
- package/dist/snippets/artifacts-types.js.map +1 -0
- package/dist/snippets/cache-probe.js +376 -0
- package/dist/snippets/cache-probe.js.map +1 -0
- package/dist/snippets/cache.js +10 -0
- package/dist/snippets/cache.js.map +1 -0
- package/dist/snippets/events.js +469 -0
- package/dist/snippets/events.js.map +1 -0
- package/dist/snippets/index.js +7 -0
- package/dist/snippets/index.js.map +1 -0
- package/dist/snippets/proxy.js +495 -0
- package/dist/snippets/proxy.js.map +1 -0
- package/dist/snippets/search.js +1759 -0
- package/dist/snippets/search.js.map +1 -0
- package/dist/streams/index.js +30 -0
- package/dist/streams/index.js.map +1 -0
- package/dist/streams/observability.js +68 -0
- package/dist/streams/observability.js.map +1 -0
- package/dist/types/AI.js +92 -0
- package/dist/types/AI.js.map +1 -0
- package/dist/types/AIFunction.js +171 -0
- package/dist/types/AIFunction.js.map +1 -0
- package/dist/types/BrowseVerb.js +89 -0
- package/dist/types/BrowseVerb.js.map +1 -0
- package/dist/types/Browser.js +31 -0
- package/dist/types/Browser.js.map +1 -0
- package/dist/types/Chaos.js +15 -0
- package/dist/types/Chaos.js.map +1 -0
- package/dist/types/CloudflareBindings.js +109 -0
- package/dist/types/CloudflareBindings.js.map +1 -0
- package/dist/types/Collection.js +50 -0
- package/dist/types/Collection.js.map +1 -0
- package/dist/types/DO.js +2 -0
- package/dist/types/DO.js.map +1 -0
- package/dist/types/DOLocation.js +63 -0
- package/dist/types/DOLocation.js.map +1 -0
- package/dist/types/EventHandler.js +57 -0
- package/dist/types/EventHandler.js.map +1 -0
- package/dist/types/Experiment.js +33 -0
- package/dist/types/Experiment.js.map +1 -0
- package/dist/types/Flag.js +57 -0
- package/dist/types/Flag.js.map +1 -0
- package/dist/types/Lifecycle.js +13 -0
- package/dist/types/Lifecycle.js.map +1 -0
- package/dist/types/Location.js +169 -0
- package/dist/types/Location.js.map +1 -0
- package/dist/types/Noun.js +66 -0
- package/dist/types/Noun.js.map +1 -0
- package/dist/types/SessionEvent.js +194 -0
- package/dist/types/SessionEvent.js.map +1 -0
- package/dist/types/Thing.js +55 -0
- package/dist/types/Thing.js.map +1 -0
- package/dist/types/ThingDO.js +153 -0
- package/dist/types/ThingDO.js.map +1 -0
- package/dist/types/Things.js +2 -0
- package/dist/types/Things.js.map +1 -0
- package/dist/types/Verb.js +119 -0
- package/dist/types/Verb.js.map +1 -0
- package/dist/types/WorkflowContext.js +70 -0
- package/dist/types/WorkflowContext.js.map +1 -0
- package/dist/types/analytics-api.js +13 -0
- package/dist/types/analytics-api.js.map +1 -0
- package/dist/types/capabilities.js +135 -0
- package/dist/types/capabilities.js.map +1 -0
- package/dist/types/drizzle.js +12 -0
- package/dist/types/drizzle.js.map +1 -0
- package/dist/types/event.js +201 -0
- package/dist/types/event.js.map +1 -0
- package/dist/types/fn.js +12 -0
- package/dist/types/fn.js.map +1 -0
- package/dist/types/iceberg.js +48 -0
- package/dist/types/iceberg.js.map +1 -0
- package/dist/types/ids.js +170 -0
- package/dist/types/ids.js.map +1 -0
- package/dist/types/index.js +41 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/introspect.js +54 -0
- package/dist/types/introspect.js.map +1 -0
- package/dist/types/observability.js +124 -0
- package/dist/types/observability.js.map +1 -0
- package/dist/types/sync-protocol.js +175 -0
- package/dist/types/sync-protocol.js.map +1 -0
- package/dist/types/vector.js +13 -0
- package/dist/types/vector.js.map +1 -0
- package/dist/workflows/ScheduleManager.js +473 -0
- package/dist/workflows/ScheduleManager.js.map +1 -0
- package/dist/workflows/StepDOBridge.js +149 -0
- package/dist/workflows/StepDOBridge.js.map +1 -0
- package/dist/workflows/StepResultStorage.js +232 -0
- package/dist/workflows/StepResultStorage.js.map +1 -0
- package/dist/workflows/WaitForEventManager.js +461 -0
- package/dist/workflows/WaitForEventManager.js.map +1 -0
- package/dist/workflows/analyzer.js +332 -0
- package/dist/workflows/analyzer.js.map +1 -0
- package/dist/workflows/compat/activity-router.js +484 -0
- package/dist/workflows/compat/activity-router.js.map +1 -0
- package/dist/workflows/compat/backends/cloudflare-workflows.js +431 -0
- package/dist/workflows/compat/backends/cloudflare-workflows.js.map +1 -0
- package/dist/workflows/compat/backends/index.js +14 -0
- package/dist/workflows/compat/backends/index.js.map +1 -0
- package/dist/workflows/compat/errors/index.js +375 -0
- package/dist/workflows/compat/errors/index.js.map +1 -0
- package/dist/workflows/compat/index.js +79 -0
- package/dist/workflows/compat/index.js.map +1 -0
- package/dist/workflows/compat/inngest/index.js +989 -0
- package/dist/workflows/compat/inngest/index.js.map +1 -0
- package/dist/workflows/compat/qstash/index.js +1263 -0
- package/dist/workflows/compat/qstash/index.js.map +1 -0
- package/dist/workflows/compat/temporal/activities.js +739 -0
- package/dist/workflows/compat/temporal/activities.js.map +1 -0
- package/dist/workflows/compat/temporal/child-workflows.js +154 -0
- package/dist/workflows/compat/temporal/child-workflows.js.map +1 -0
- package/dist/workflows/compat/temporal/client.js +381 -0
- package/dist/workflows/compat/temporal/client.js.map +1 -0
- package/dist/workflows/compat/temporal/context.js +309 -0
- package/dist/workflows/compat/temporal/context.js.map +1 -0
- package/dist/workflows/compat/temporal/determinism.js +216 -0
- package/dist/workflows/compat/temporal/determinism.js.map +1 -0
- package/dist/workflows/compat/temporal/errors.js +128 -0
- package/dist/workflows/compat/temporal/errors.js.map +1 -0
- package/dist/workflows/compat/temporal/index.js +2464 -0
- package/dist/workflows/compat/temporal/index.js.map +1 -0
- package/dist/workflows/compat/temporal/saga.js +504 -0
- package/dist/workflows/compat/temporal/saga.js.map +1 -0
- package/dist/workflows/compat/temporal/signals.js +364 -0
- package/dist/workflows/compat/temporal/signals.js.map +1 -0
- package/dist/workflows/compat/temporal/storage.js +271 -0
- package/dist/workflows/compat/temporal/storage.js.map +1 -0
- package/dist/workflows/compat/temporal/timers.js +347 -0
- package/dist/workflows/compat/temporal/timers.js.map +1 -0
- package/dist/workflows/compat/temporal/types.js +7 -0
- package/dist/workflows/compat/temporal/types.js.map +1 -0
- package/dist/workflows/compat/temporal/unified-primitives.js +339 -0
- package/dist/workflows/compat/temporal/unified-primitives.js.map +1 -0
- package/dist/workflows/compat/trigger/index.js +468 -0
- package/dist/workflows/compat/trigger/index.js.map +1 -0
- package/dist/workflows/compat/utils/index.js +69 -0
- package/dist/workflows/compat/utils/index.js.map +1 -0
- package/dist/workflows/context/correlation-capability.js +266 -0
- package/dist/workflows/context/correlation-capability.js.map +1 -0
- package/dist/workflows/context/correlation.js +484 -0
- package/dist/workflows/context/correlation.js.map +1 -0
- package/dist/workflows/context/experiment.js +289 -0
- package/dist/workflows/context/experiment.js.map +1 -0
- package/dist/workflows/context/flag.js +244 -0
- package/dist/workflows/context/flag.js.map +1 -0
- package/dist/workflows/context/foundation.js +648 -0
- package/dist/workflows/context/foundation.js.map +1 -0
- package/dist/workflows/context/human-base.js +106 -0
- package/dist/workflows/context/human-base.js.map +1 -0
- package/dist/workflows/context/human.js +368 -0
- package/dist/workflows/context/human.js.map +1 -0
- package/dist/workflows/context/measure.js +354 -0
- package/dist/workflows/context/measure.js.map +1 -0
- package/dist/workflows/context/rate-limit.js +358 -0
- package/dist/workflows/context/rate-limit.js.map +1 -0
- package/dist/workflows/context/user.js +117 -0
- package/dist/workflows/context/user.js.map +1 -0
- package/dist/workflows/context/vault.js +360 -0
- package/dist/workflows/context/vault.js.map +1 -0
- package/dist/workflows/data/entity-events/entity-events.js +489 -0
- package/dist/workflows/data/entity-events/entity-events.js.map +1 -0
- package/dist/workflows/data/experiment/index.js +599 -0
- package/dist/workflows/data/experiment/index.js.map +1 -0
- package/dist/workflows/data/goal/context.js +558 -0
- package/dist/workflows/data/goal/context.js.map +1 -0
- package/dist/workflows/data/goal/index.js +32 -0
- package/dist/workflows/data/goal/index.js.map +1 -0
- package/dist/workflows/data/measure/index.js +840 -0
- package/dist/workflows/data/measure/index.js.map +1 -0
- package/dist/workflows/data/stream/index.js +1215 -0
- package/dist/workflows/data/stream/index.js.map +1 -0
- package/dist/workflows/data/track/context.js +883 -0
- package/dist/workflows/data/track/context.js.map +1 -0
- package/dist/workflows/data/track/index.js +15 -0
- package/dist/workflows/data/track/index.js.map +1 -0
- package/dist/workflows/data/view/context.js +864 -0
- package/dist/workflows/data/view/context.js.map +1 -0
- package/dist/workflows/domain.js +93 -0
- package/dist/workflows/domain.js.map +1 -0
- package/dist/workflows/flag.js +176 -0
- package/dist/workflows/flag.js.map +1 -0
- package/dist/workflows/flags.js +217 -0
- package/dist/workflows/flags.js.map +1 -0
- package/dist/workflows/hash.js +209 -0
- package/dist/workflows/hash.js.map +1 -0
- package/dist/workflows/index.js +50 -0
- package/dist/workflows/index.js.map +1 -0
- package/dist/workflows/on.js +378 -0
- package/dist/workflows/on.js.map +1 -0
- package/dist/workflows/pipeline-promise.js +481 -0
- package/dist/workflows/pipeline-promise.js.map +1 -0
- package/dist/workflows/pipeline-types.js +20 -0
- package/dist/workflows/pipeline-types.js.map +1 -0
- package/dist/workflows/proxy.js +76 -0
- package/dist/workflows/proxy.js.map +1 -0
- package/dist/workflows/runtime.js +310 -0
- package/dist/workflows/runtime.js.map +1 -0
- package/dist/workflows/schedule-builder.js +327 -0
- package/dist/workflows/schedule-builder.js.map +1 -0
- package/dist/workflows/visibility/index.js +148 -0
- package/dist/workflows/visibility/index.js.map +1 -0
- package/dist/workflows/visibility/query-parser.js +150 -0
- package/dist/workflows/visibility/query-parser.js.map +1 -0
- package/dist/workflows/visibility/store.js +223 -0
- package/dist/workflows/visibility/store.js.map +1 -0
- package/dist/workflows/visibility/types.js +30 -0
- package/dist/workflows/visibility/types.js.map +1 -0
- package/dist/workflows/workflow.js +53 -0
- package/dist/workflows/workflow.js.map +1 -0
- package/package.json +279 -46
|
@@ -0,0 +1,2647 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiered Execution System
|
|
3
|
+
*
|
|
4
|
+
* Implements a 4-tier execution model for bash commands:
|
|
5
|
+
*
|
|
6
|
+
* - Tier 1: Native in-Worker via nodejs_compat_v2/ofetch (fastest, most limited)
|
|
7
|
+
* - Tier 2: RPC bindings for jq.do/npm.do (fast, specific tools)
|
|
8
|
+
* - Tier 3: worker_loaders for dynamic npm (flexible, dynamic loading)
|
|
9
|
+
* - Tier 4: Sandbox SDK for true Linux needs (slowest, full capability)
|
|
10
|
+
*
|
|
11
|
+
* The executor auto-detects which tier to use based on command analysis
|
|
12
|
+
* and available bindings.
|
|
13
|
+
*
|
|
14
|
+
* @module bashx/do/tiered-executor
|
|
15
|
+
*/
|
|
16
|
+
import { executeBc, executeExpr, executeSeq, executeShuf, executeSleep, executeTimeout, timeoutCommandNotFound, } from './commands/math-control.js';
|
|
17
|
+
import { executeJq, executeYq, executeBase64, executeEnvsubst, parseJqArgs, parseYqArgs, parseBase64Args, parseEnvsubstArgs, JqError, Base64Error, EnvsubstError, } from './commands/data-processing.js';
|
|
18
|
+
import { executeCryptoCommand, } from './commands/crypto.js';
|
|
19
|
+
import { executeSed, executeAwk, executeDiff, executePatch, executeTee, executeXargs, } from './commands/text-processing.js';
|
|
20
|
+
import { executeCut, executeSort, executeTr, executeUniq, executeWc, executeBasename, executeDirname, executeEcho, executePrintf, executeDate, executeDd, executeOd, } from './commands/posix-utils.js';
|
|
21
|
+
import { executeYes, executeWhoami, executeHostname, executePrintenv, } from './commands/system-utils.js';
|
|
22
|
+
import { parseEnvArgs, executeEnv, formatEnv, parseIdArgs, executeId, DEFAULT_WORKER_IDENTITY, parseUnameArgs, executeUname, DEFAULT_WORKER_SYSINFO, parseTacArgs, executeTac, } from './commands/extended-utils.js';
|
|
23
|
+
import { executeTest, createFileInfoProvider, } from './commands/test-command.js';
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// TIER 1: NATIVE COMMANDS
|
|
26
|
+
// ============================================================================
|
|
27
|
+
/**
|
|
28
|
+
* Commands that can be executed natively in-Worker via nodejs_compat_v2
|
|
29
|
+
*/
|
|
30
|
+
const TIER_1_NATIVE_COMMANDS = new Set([
|
|
31
|
+
// Basic file operations (read)
|
|
32
|
+
'cat', 'head', 'tail', 'ls', 'test', '[', 'stat', 'readlink', 'find', 'grep',
|
|
33
|
+
// File operations (write)
|
|
34
|
+
'mkdir', 'rmdir', 'rm', 'cp', 'mv', 'touch', 'truncate', 'ln',
|
|
35
|
+
// Permission operations
|
|
36
|
+
'chmod', 'chown',
|
|
37
|
+
// Echo/print (pure computation)
|
|
38
|
+
'echo', 'printf',
|
|
39
|
+
// Path operations
|
|
40
|
+
'pwd', 'dirname', 'basename',
|
|
41
|
+
// String/text processing (pure)
|
|
42
|
+
'wc', 'sort', 'uniq', 'tr', 'cut', 'rev',
|
|
43
|
+
// Date/time (pure)
|
|
44
|
+
'date',
|
|
45
|
+
// True/false (pure)
|
|
46
|
+
'true', 'false',
|
|
47
|
+
// HTTP operations (via fetch API)
|
|
48
|
+
'curl', 'wget',
|
|
49
|
+
// Math & control commands
|
|
50
|
+
'bc', 'expr', 'seq', 'shuf', 'sleep', 'timeout',
|
|
51
|
+
// Compression commands (native via pako/fflate)
|
|
52
|
+
'gzip', 'gunzip', 'zcat', 'tar', 'zip', 'unzip',
|
|
53
|
+
// Data processing commands (native implementations)
|
|
54
|
+
'jq', 'yq', 'base64', 'envsubst',
|
|
55
|
+
// Crypto commands (native via Web Crypto API)
|
|
56
|
+
'sha256sum', 'sha1sum', 'sha512sum', 'sha384sum', 'md5sum',
|
|
57
|
+
'uuidgen', 'uuid', 'cksum', 'sum', 'openssl',
|
|
58
|
+
// Text processing commands (native implementations)
|
|
59
|
+
'sed', 'awk', 'diff', 'patch', 'tee', 'xargs',
|
|
60
|
+
// System utility commands (native implementations)
|
|
61
|
+
'yes', 'whoami', 'hostname', 'printenv',
|
|
62
|
+
// Extended utility commands (native implementations)
|
|
63
|
+
'env', 'id', 'uname', 'tac',
|
|
64
|
+
]);
|
|
65
|
+
/**
|
|
66
|
+
* Commands that specifically require filesystem access for Tier 1
|
|
67
|
+
*/
|
|
68
|
+
const TIER_1_FS_COMMANDS = new Set([
|
|
69
|
+
// Read operations
|
|
70
|
+
'cat', 'head', 'tail', 'ls', 'test', 'stat', 'readlink', 'find', 'grep',
|
|
71
|
+
// Write operations
|
|
72
|
+
'mkdir', 'rmdir', 'rm', 'cp', 'mv', 'touch', 'truncate', 'ln',
|
|
73
|
+
// Permission operations
|
|
74
|
+
'chmod', 'chown',
|
|
75
|
+
// Compression commands (need filesystem access)
|
|
76
|
+
'gzip', 'gunzip', 'zcat', 'tar', 'zip', 'unzip',
|
|
77
|
+
]);
|
|
78
|
+
/**
|
|
79
|
+
* Commands that use native HTTP (fetch API) for Tier 1
|
|
80
|
+
*/
|
|
81
|
+
const TIER_1_HTTP_COMMANDS = new Set(['curl', 'wget']);
|
|
82
|
+
/**
|
|
83
|
+
* Data processing commands with native implementations
|
|
84
|
+
*/
|
|
85
|
+
const TIER_1_DATA_COMMANDS = new Set(['jq', 'yq', 'base64', 'envsubst']);
|
|
86
|
+
/**
|
|
87
|
+
* Crypto commands with native Web Crypto API implementations
|
|
88
|
+
*/
|
|
89
|
+
const TIER_1_CRYPTO_COMMANDS = new Set([
|
|
90
|
+
'sha256sum', 'sha1sum', 'sha512sum', 'sha384sum', 'md5sum',
|
|
91
|
+
'uuidgen', 'uuid', 'cksum', 'sum', 'openssl',
|
|
92
|
+
]);
|
|
93
|
+
/**
|
|
94
|
+
* Text processing commands with native implementations
|
|
95
|
+
*/
|
|
96
|
+
const TIER_1_TEXT_PROCESSING_COMMANDS = new Set([
|
|
97
|
+
'sed', 'awk', 'diff', 'patch', 'tee', 'xargs',
|
|
98
|
+
]);
|
|
99
|
+
/**
|
|
100
|
+
* POSIX utility commands with native implementations
|
|
101
|
+
* These include: cut, sort, tr, uniq, wc, basename, dirname, echo, printf, date, dd, od
|
|
102
|
+
*/
|
|
103
|
+
const TIER_1_POSIX_UTILS_COMMANDS = new Set([
|
|
104
|
+
'cut', 'sort', 'tr', 'uniq', 'wc',
|
|
105
|
+
'basename', 'dirname', 'echo', 'printf',
|
|
106
|
+
'date', 'dd', 'od',
|
|
107
|
+
]);
|
|
108
|
+
/**
|
|
109
|
+
* System utility commands with native implementations
|
|
110
|
+
* These include: yes, whoami, hostname, printenv
|
|
111
|
+
* (sleep, seq, pwd are already handled in other command groups)
|
|
112
|
+
*/
|
|
113
|
+
const TIER_1_SYSTEM_UTILS_COMMANDS = new Set([
|
|
114
|
+
'yes', 'whoami', 'hostname', 'printenv',
|
|
115
|
+
]);
|
|
116
|
+
/**
|
|
117
|
+
* Extended utility commands with native implementations
|
|
118
|
+
* These include: env, id, uname, timeout, tac, shuf
|
|
119
|
+
* Note: timeout and shuf are also in math-control, but extended-utils provides more options
|
|
120
|
+
*/
|
|
121
|
+
const TIER_1_EXTENDED_UTILS_COMMANDS = new Set([
|
|
122
|
+
'env', 'id', 'uname', 'tac',
|
|
123
|
+
// Note: timeout and shuf have extended implementations but also exist in math-control
|
|
124
|
+
]);
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// TIER 2: RPC COMMANDS
|
|
127
|
+
// ============================================================================
|
|
128
|
+
/**
|
|
129
|
+
* Default RPC services and their commands
|
|
130
|
+
*/
|
|
131
|
+
const DEFAULT_RPC_SERVICES = {
|
|
132
|
+
jq: {
|
|
133
|
+
commands: ['jq'],
|
|
134
|
+
endpoint: 'https://jq.do',
|
|
135
|
+
},
|
|
136
|
+
npm: {
|
|
137
|
+
commands: ['npm', 'npx', 'pnpm', 'yarn', 'bun'],
|
|
138
|
+
endpoint: 'https://npm.do',
|
|
139
|
+
},
|
|
140
|
+
git: {
|
|
141
|
+
commands: ['git'],
|
|
142
|
+
endpoint: 'https://git.do',
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
// ============================================================================
|
|
146
|
+
// TIER 3: WORKER LOADER MODULES
|
|
147
|
+
// ============================================================================
|
|
148
|
+
/**
|
|
149
|
+
* NPM packages that can be dynamically loaded in Workers
|
|
150
|
+
*/
|
|
151
|
+
const TIER_3_LOADABLE_MODULES = new Set([
|
|
152
|
+
// JavaScript/TypeScript tools
|
|
153
|
+
'esbuild', 'typescript', 'prettier', 'eslint',
|
|
154
|
+
// Data processing
|
|
155
|
+
'zod', 'ajv', 'yaml', 'toml',
|
|
156
|
+
// Crypto
|
|
157
|
+
'crypto-js', 'jose',
|
|
158
|
+
// Utility
|
|
159
|
+
'lodash', 'date-fns', 'uuid',
|
|
160
|
+
]);
|
|
161
|
+
// ============================================================================
|
|
162
|
+
// TIER 4: SANDBOX-ONLY COMMANDS
|
|
163
|
+
// ============================================================================
|
|
164
|
+
/**
|
|
165
|
+
* Commands that require true Linux sandbox execution
|
|
166
|
+
*/
|
|
167
|
+
const TIER_4_SANDBOX_COMMANDS = new Set([
|
|
168
|
+
// System/process management
|
|
169
|
+
'ps', 'kill', 'killall', 'top', 'htop',
|
|
170
|
+
// Network tools (curl/wget moved to Tier 1 via native fetch)
|
|
171
|
+
'ping', 'ssh', 'scp', 'nc', 'netstat',
|
|
172
|
+
// Package managers (when not via RPC)
|
|
173
|
+
'apt', 'apt-get', 'yum', 'dnf', 'brew',
|
|
174
|
+
// Containers
|
|
175
|
+
'docker', 'docker-compose', 'podman', 'kubectl',
|
|
176
|
+
// Compilers and runtimes
|
|
177
|
+
'gcc', 'g++', 'clang', 'rustc', 'cargo', 'go', 'python', 'python3', 'ruby', 'perl',
|
|
178
|
+
// System utilities (chmod/chown moved to Tier 1 via FsCapability)
|
|
179
|
+
'sudo', 'su', 'chgrp',
|
|
180
|
+
// Archive (gzip, tar, zip moved to Tier 1 via pako/fflate)
|
|
181
|
+
// Process substitution, pipes, complex shell features
|
|
182
|
+
'bash', 'sh', 'zsh',
|
|
183
|
+
]);
|
|
184
|
+
// ============================================================================
|
|
185
|
+
// TIERED EXECUTOR CLASS
|
|
186
|
+
// ============================================================================
|
|
187
|
+
/**
|
|
188
|
+
* TieredExecutor - Smart executor that routes commands to the appropriate tier.
|
|
189
|
+
*
|
|
190
|
+
* The executor analyzes each command and determines the optimal execution tier:
|
|
191
|
+
* - Tier 1: Fast, in-Worker native operations (cat, ls, echo, etc.)
|
|
192
|
+
* - Tier 2: RPC calls to external services (jq.do, npm.do, git.do)
|
|
193
|
+
* - Tier 3: Dynamic npm module loading (esbuild, prettier, etc.)
|
|
194
|
+
* - Tier 4: Full sandbox execution for Linux-specific commands
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```typescript
|
|
198
|
+
* const executor = new TieredExecutor({
|
|
199
|
+
* fs: fsCapability,
|
|
200
|
+
* rpcBindings: {
|
|
201
|
+
* jq: { endpoint: 'https://jq.do', commands: ['jq'] },
|
|
202
|
+
* },
|
|
203
|
+
* sandbox: sandboxBinding,
|
|
204
|
+
* })
|
|
205
|
+
*
|
|
206
|
+
* // Auto-routed to Tier 1 (native)
|
|
207
|
+
* await executor.execute('cat file.txt')
|
|
208
|
+
*
|
|
209
|
+
* // Auto-routed to Tier 2 (RPC)
|
|
210
|
+
* await executor.execute('jq .name package.json')
|
|
211
|
+
*
|
|
212
|
+
* // Auto-routed to Tier 4 (sandbox)
|
|
213
|
+
* await executor.execute('docker ps')
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
export class TieredExecutor {
|
|
217
|
+
fs;
|
|
218
|
+
rpcBindings;
|
|
219
|
+
workerLoaders;
|
|
220
|
+
sandbox;
|
|
221
|
+
defaultTimeout;
|
|
222
|
+
/** @internal Reserved for future optimization strategy selection */
|
|
223
|
+
preferFaster;
|
|
224
|
+
constructor(config) {
|
|
225
|
+
this.fs = config.fs;
|
|
226
|
+
this.rpcBindings = config.rpcBindings ?? {};
|
|
227
|
+
this.workerLoaders = config.workerLoaders ?? {};
|
|
228
|
+
this.sandbox = config.sandbox;
|
|
229
|
+
this.defaultTimeout = config.defaultTimeout ?? 30000;
|
|
230
|
+
this.preferFaster = config.preferFaster ?? true;
|
|
231
|
+
// Merge default RPC services with provided bindings
|
|
232
|
+
for (const [name, service] of Object.entries(DEFAULT_RPC_SERVICES)) {
|
|
233
|
+
if (!this.rpcBindings[name]) {
|
|
234
|
+
this.rpcBindings[name] = {
|
|
235
|
+
name,
|
|
236
|
+
endpoint: service.endpoint,
|
|
237
|
+
commands: service.commands,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Classify a command to determine which tier should execute it.
|
|
244
|
+
*
|
|
245
|
+
* @param command - The command to classify
|
|
246
|
+
* @returns TierClassification with tier level and handler info
|
|
247
|
+
*/
|
|
248
|
+
classifyCommand(command) {
|
|
249
|
+
const cmd = this.extractCommandName(command);
|
|
250
|
+
// Tier 1: Native in-Worker commands
|
|
251
|
+
if (TIER_1_NATIVE_COMMANDS.has(cmd)) {
|
|
252
|
+
// If it's a filesystem command, check if fs capability is available
|
|
253
|
+
if (TIER_1_FS_COMMANDS.has(cmd)) {
|
|
254
|
+
if (this.fs) {
|
|
255
|
+
return {
|
|
256
|
+
tier: 1,
|
|
257
|
+
reason: `Native filesystem operation via FsCapability`,
|
|
258
|
+
handler: 'native',
|
|
259
|
+
capability: 'fs',
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
// Fall through to sandbox if no fs capability
|
|
263
|
+
}
|
|
264
|
+
else if (TIER_1_HTTP_COMMANDS.has(cmd)) {
|
|
265
|
+
// HTTP commands via native fetch API
|
|
266
|
+
return {
|
|
267
|
+
tier: 1,
|
|
268
|
+
reason: `Native HTTP operation via fetch API (${cmd})`,
|
|
269
|
+
handler: 'native',
|
|
270
|
+
capability: 'http',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
else if (TIER_1_DATA_COMMANDS.has(cmd)) {
|
|
274
|
+
// Data processing commands (jq, yq, base64, envsubst)
|
|
275
|
+
return {
|
|
276
|
+
tier: 1,
|
|
277
|
+
reason: `Native data processing command (${cmd})`,
|
|
278
|
+
handler: 'native',
|
|
279
|
+
capability: cmd, // Use command name as capability
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
else if (TIER_1_CRYPTO_COMMANDS.has(cmd)) {
|
|
283
|
+
// Crypto commands via Web Crypto API
|
|
284
|
+
return {
|
|
285
|
+
tier: 1,
|
|
286
|
+
reason: `Native crypto command via Web Crypto API (${cmd})`,
|
|
287
|
+
handler: 'native',
|
|
288
|
+
capability: 'crypto',
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
else if (TIER_1_TEXT_PROCESSING_COMMANDS.has(cmd)) {
|
|
292
|
+
// Text processing commands (sed, awk, diff, patch, tee, xargs)
|
|
293
|
+
return {
|
|
294
|
+
tier: 1,
|
|
295
|
+
reason: `Native text processing command (${cmd})`,
|
|
296
|
+
handler: 'native',
|
|
297
|
+
capability: 'text',
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
else if (TIER_1_POSIX_UTILS_COMMANDS.has(cmd)) {
|
|
301
|
+
// POSIX utility commands (cut, sort, tr, uniq, wc, basename, dirname, echo, printf, date, dd, od)
|
|
302
|
+
return {
|
|
303
|
+
tier: 1,
|
|
304
|
+
reason: `Native POSIX utility command (${cmd})`,
|
|
305
|
+
handler: 'native',
|
|
306
|
+
capability: 'posix',
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
else if (TIER_1_SYSTEM_UTILS_COMMANDS.has(cmd)) {
|
|
310
|
+
// System utility commands (yes, whoami, hostname, printenv)
|
|
311
|
+
return {
|
|
312
|
+
tier: 1,
|
|
313
|
+
reason: `Native system utility command (${cmd})`,
|
|
314
|
+
handler: 'native',
|
|
315
|
+
capability: 'system',
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
else if (TIER_1_EXTENDED_UTILS_COMMANDS.has(cmd)) {
|
|
319
|
+
// Extended utility commands (env, id, uname, tac)
|
|
320
|
+
return {
|
|
321
|
+
tier: 1,
|
|
322
|
+
reason: `Native extended utility command (${cmd})`,
|
|
323
|
+
handler: 'native',
|
|
324
|
+
capability: 'extended',
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
// Pure computation commands
|
|
329
|
+
return {
|
|
330
|
+
tier: 1,
|
|
331
|
+
reason: `Pure computation command (${cmd})`,
|
|
332
|
+
handler: 'native',
|
|
333
|
+
capability: 'compute',
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Tier 2: RPC service commands
|
|
338
|
+
for (const [serviceName, binding] of Object.entries(this.rpcBindings)) {
|
|
339
|
+
if (binding.commands.includes(cmd)) {
|
|
340
|
+
return {
|
|
341
|
+
tier: 2,
|
|
342
|
+
reason: `RPC service available (${serviceName})`,
|
|
343
|
+
handler: 'rpc',
|
|
344
|
+
capability: serviceName,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// Tier 3: Check for commands that can use dynamically loaded modules
|
|
349
|
+
// This is for Node.js tools that can run in Workers with worker_loaders
|
|
350
|
+
const workerLoaderMatch = this.matchWorkerLoader(command);
|
|
351
|
+
if (workerLoaderMatch) {
|
|
352
|
+
return {
|
|
353
|
+
tier: 3,
|
|
354
|
+
reason: `Dynamic npm module available (${workerLoaderMatch})`,
|
|
355
|
+
handler: 'loader',
|
|
356
|
+
capability: workerLoaderMatch,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
// Tier 4: Sandbox for everything else
|
|
360
|
+
return {
|
|
361
|
+
tier: 4,
|
|
362
|
+
reason: TIER_4_SANDBOX_COMMANDS.has(cmd)
|
|
363
|
+
? `Requires Linux sandbox (${cmd})`
|
|
364
|
+
: 'No higher tier available for this command',
|
|
365
|
+
handler: 'sandbox',
|
|
366
|
+
capability: 'container',
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Execute a command, automatically routing to the appropriate tier.
|
|
371
|
+
*
|
|
372
|
+
* @param command - The command to execute
|
|
373
|
+
* @param options - Optional execution options
|
|
374
|
+
* @returns Promise resolving to the execution result
|
|
375
|
+
*/
|
|
376
|
+
async execute(command, options) {
|
|
377
|
+
// Handle input redirection (< filename)
|
|
378
|
+
const redirectMatch = command.match(/^(.+?)\s*<\s*(\S+)\s*$/);
|
|
379
|
+
if (redirectMatch && this.fs) {
|
|
380
|
+
const actualCommand = redirectMatch[1].trim();
|
|
381
|
+
const inputFile = redirectMatch[2];
|
|
382
|
+
try {
|
|
383
|
+
const inputContent = await this.fs.read(inputFile, { encoding: 'utf-8' });
|
|
384
|
+
return this.execute(actualCommand, { ...options, stdin: inputContent });
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
388
|
+
return this.createResult(command, '', message, 1, 1);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// Handle pipelines - split by | and execute in sequence
|
|
392
|
+
const pipelineSegments = this.splitPipeline(command);
|
|
393
|
+
if (pipelineSegments.length > 1) {
|
|
394
|
+
return this.executePipeline(pipelineSegments, options);
|
|
395
|
+
}
|
|
396
|
+
const classification = this.classifyCommand(command);
|
|
397
|
+
try {
|
|
398
|
+
switch (classification.tier) {
|
|
399
|
+
case 1:
|
|
400
|
+
return await this.executeTier1(command, classification, options);
|
|
401
|
+
case 2:
|
|
402
|
+
return await this.executeTier2(command, classification, options);
|
|
403
|
+
case 3:
|
|
404
|
+
return await this.executeTier3(command, classification, options);
|
|
405
|
+
case 4:
|
|
406
|
+
return await this.executeTier4(command, classification, options);
|
|
407
|
+
default:
|
|
408
|
+
throw new Error(`Unknown tier: ${classification.tier}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
// If a higher tier fails, try falling back to a lower tier
|
|
413
|
+
if (classification.tier < 4 && this.sandbox) {
|
|
414
|
+
console.warn(`Tier ${classification.tier} failed for "${command}", falling back to sandbox:`, error);
|
|
415
|
+
return this.executeTier4(command, { ...classification, tier: 4 }, options);
|
|
416
|
+
}
|
|
417
|
+
throw error;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Spawn a streaming process. Routes to sandbox for full streaming support.
|
|
422
|
+
*/
|
|
423
|
+
async spawn(command, args, options) {
|
|
424
|
+
if (!this.sandbox?.spawn) {
|
|
425
|
+
throw new Error('Spawn requires sandbox with spawn support');
|
|
426
|
+
}
|
|
427
|
+
return this.sandbox.spawn(command, args, options);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Split a command into pipeline segments, respecting quotes
|
|
431
|
+
*/
|
|
432
|
+
splitPipeline(command) {
|
|
433
|
+
const segments = [];
|
|
434
|
+
let current = '';
|
|
435
|
+
let inSingleQuote = false;
|
|
436
|
+
let inDoubleQuote = false;
|
|
437
|
+
for (let i = 0; i < command.length; i++) {
|
|
438
|
+
const char = command[i];
|
|
439
|
+
if (char === "'" && !inDoubleQuote) {
|
|
440
|
+
inSingleQuote = !inSingleQuote;
|
|
441
|
+
current += char;
|
|
442
|
+
}
|
|
443
|
+
else if (char === '"' && !inSingleQuote) {
|
|
444
|
+
inDoubleQuote = !inDoubleQuote;
|
|
445
|
+
current += char;
|
|
446
|
+
}
|
|
447
|
+
else if (char === '\\' && !inSingleQuote && command[i + 1] === '|') {
|
|
448
|
+
// Escaped pipe - keep it as \|, don't split
|
|
449
|
+
current += '\\|';
|
|
450
|
+
i++;
|
|
451
|
+
}
|
|
452
|
+
else if (char === '|' && !inSingleQuote && !inDoubleQuote) {
|
|
453
|
+
// Check if it's || (logical OR) - skip if so
|
|
454
|
+
if (command[i + 1] === '|') {
|
|
455
|
+
current += '||';
|
|
456
|
+
i++;
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
segments.push(current.trim());
|
|
460
|
+
current = '';
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
current += char;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (current.trim()) {
|
|
468
|
+
segments.push(current.trim());
|
|
469
|
+
}
|
|
470
|
+
return segments;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Execute a pipeline of commands, passing stdout of each to stdin of next
|
|
474
|
+
*/
|
|
475
|
+
async executePipeline(segments, options) {
|
|
476
|
+
let stdin = options?.stdin || '';
|
|
477
|
+
let lastResult = null;
|
|
478
|
+
for (const segment of segments) {
|
|
479
|
+
const segmentOptions = {
|
|
480
|
+
...options,
|
|
481
|
+
stdin,
|
|
482
|
+
};
|
|
483
|
+
lastResult = await this.execute(segment, segmentOptions);
|
|
484
|
+
// If command failed, stop the pipeline
|
|
485
|
+
if (lastResult.exitCode !== 0) {
|
|
486
|
+
return lastResult;
|
|
487
|
+
}
|
|
488
|
+
// Pass stdout to next command's stdin
|
|
489
|
+
stdin = lastResult.stdout;
|
|
490
|
+
}
|
|
491
|
+
return lastResult;
|
|
492
|
+
}
|
|
493
|
+
// ============================================================================
|
|
494
|
+
// TIER EXECUTION METHODS
|
|
495
|
+
// ============================================================================
|
|
496
|
+
/**
|
|
497
|
+
* Tier 1: Execute command natively in-Worker
|
|
498
|
+
*/
|
|
499
|
+
async executeTier1(command, classification, options) {
|
|
500
|
+
const cmd = this.extractCommandName(command);
|
|
501
|
+
const args = this.extractArgs(command);
|
|
502
|
+
// Handle filesystem commands via FsCapability
|
|
503
|
+
if (classification.capability === 'fs' && this.fs) {
|
|
504
|
+
return this.executeNativeFs(cmd, args, options);
|
|
505
|
+
}
|
|
506
|
+
// Handle HTTP commands via native fetch API
|
|
507
|
+
if (classification.capability === 'http') {
|
|
508
|
+
if (cmd === 'curl') {
|
|
509
|
+
return this.executeCurl(args);
|
|
510
|
+
}
|
|
511
|
+
else if (cmd === 'wget') {
|
|
512
|
+
return this.executeWget(args);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// Handle data processing commands
|
|
516
|
+
if (TIER_1_DATA_COMMANDS.has(cmd)) {
|
|
517
|
+
return this.executeDataProcessing(cmd, args, command, options);
|
|
518
|
+
}
|
|
519
|
+
// Handle crypto commands via Web Crypto API
|
|
520
|
+
if (TIER_1_CRYPTO_COMMANDS.has(cmd)) {
|
|
521
|
+
return this.executeNativeCrypto(cmd, args, options);
|
|
522
|
+
}
|
|
523
|
+
// Handle text processing commands (sed, awk, diff, patch, tee, xargs)
|
|
524
|
+
if (TIER_1_TEXT_PROCESSING_COMMANDS.has(cmd)) {
|
|
525
|
+
return this.executeTextProcessing(cmd, args, command, options);
|
|
526
|
+
}
|
|
527
|
+
// Handle POSIX utility commands (cut, sort, tr, uniq, wc, basename, dirname, echo, printf, date, dd, od)
|
|
528
|
+
if (TIER_1_POSIX_UTILS_COMMANDS.has(cmd)) {
|
|
529
|
+
return this.executePosixUtils(cmd, args, command, options);
|
|
530
|
+
}
|
|
531
|
+
// Handle system utility commands (yes, whoami, hostname, printenv)
|
|
532
|
+
if (TIER_1_SYSTEM_UTILS_COMMANDS.has(cmd)) {
|
|
533
|
+
return this.executeSystemUtilsCommands(cmd, args, command, options);
|
|
534
|
+
}
|
|
535
|
+
// Handle extended utility commands (env, id, uname, tac)
|
|
536
|
+
if (TIER_1_EXTENDED_UTILS_COMMANDS.has(cmd)) {
|
|
537
|
+
return this.executeExtendedUtilsCommands(cmd, args, command, options);
|
|
538
|
+
}
|
|
539
|
+
// Handle pure computation commands
|
|
540
|
+
return this.executeNativeCompute(cmd, args, options);
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Execute native filesystem operations via FsCapability (from fsx.do)
|
|
544
|
+
*
|
|
545
|
+
* fsx.do provides a comprehensive POSIX-like filesystem API. This method
|
|
546
|
+
* uses the following fsx.do methods:
|
|
547
|
+
* - read(path, { encoding: 'utf-8' }) - returns string for text operations
|
|
548
|
+
* - list(path, { withFileTypes: true }) - returns Dirent[] for directory listing
|
|
549
|
+
* - exists(path) - returns boolean for existence checks
|
|
550
|
+
* - stat(path) - returns Stats with isFile()/isDirectory() methods
|
|
551
|
+
*/
|
|
552
|
+
async executeNativeFs(cmd, args, options) {
|
|
553
|
+
if (!this.fs) {
|
|
554
|
+
throw new Error('FsCapability not available');
|
|
555
|
+
}
|
|
556
|
+
try {
|
|
557
|
+
let stdout = '';
|
|
558
|
+
let stderr = '';
|
|
559
|
+
let exitCode = 0;
|
|
560
|
+
switch (cmd) {
|
|
561
|
+
case 'cat': {
|
|
562
|
+
if (args.length === 0) {
|
|
563
|
+
return this.createResult(`cat ${args.join(' ')}`, '', 'cat: missing operand', 1, 1);
|
|
564
|
+
}
|
|
565
|
+
// Use encoding: 'utf-8' to ensure we get strings back from fsx.do
|
|
566
|
+
const contents = await Promise.all(args.filter(a => !a.startsWith('-')).map(f => this.fs.read(f, { encoding: 'utf-8' })));
|
|
567
|
+
stdout = contents.join('');
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
case 'ls': {
|
|
571
|
+
const path = args.find(a => !a.startsWith('-')) || '.';
|
|
572
|
+
// Use withFileTypes: true to get Dirent objects from fsx.do
|
|
573
|
+
const entries = await this.fs.list(path, { withFileTypes: true });
|
|
574
|
+
// fsx.do returns Dirent[] when withFileTypes is true
|
|
575
|
+
// Dirent has isDirectory() as a method
|
|
576
|
+
stdout = entries
|
|
577
|
+
.map(e => e.isDirectory() ? `${e.name}/` : e.name)
|
|
578
|
+
.join('\n') + '\n';
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
case 'head': {
|
|
582
|
+
// Parse options: -n N, -n -N (exclude last N), -q (quiet, no headers)
|
|
583
|
+
let headQuiet = false;
|
|
584
|
+
let headLines = 10;
|
|
585
|
+
let headExcludeLast = false;
|
|
586
|
+
const headFiles = [];
|
|
587
|
+
for (let i = 0; i < args.length; i++) {
|
|
588
|
+
const arg = args[i];
|
|
589
|
+
if (arg === '-q' || arg === '--quiet' || arg === '--silent') {
|
|
590
|
+
headQuiet = true;
|
|
591
|
+
}
|
|
592
|
+
else if (arg === '-n' && args[i + 1]) {
|
|
593
|
+
const nArg = args[++i];
|
|
594
|
+
if (nArg.startsWith('-')) {
|
|
595
|
+
// -n -N means exclude last N lines
|
|
596
|
+
headExcludeLast = true;
|
|
597
|
+
headLines = parseInt(nArg.slice(1), 10);
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
headLines = parseInt(nArg, 10);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
else if (arg.startsWith('-n')) {
|
|
604
|
+
const nArg = arg.slice(2);
|
|
605
|
+
if (nArg.startsWith('-')) {
|
|
606
|
+
headExcludeLast = true;
|
|
607
|
+
headLines = parseInt(nArg.slice(1), 10);
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
headLines = parseInt(nArg, 10);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
else if (!arg.startsWith('-')) {
|
|
614
|
+
headFiles.push(arg);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
if (headFiles.length === 0) {
|
|
618
|
+
// Read from stdin
|
|
619
|
+
const stdinContent = options?.stdin || '';
|
|
620
|
+
const stdinLines = stdinContent.split('\n');
|
|
621
|
+
if (headExcludeLast) {
|
|
622
|
+
// Exclude last N lines
|
|
623
|
+
stdout = stdinLines.slice(0, -headLines).join('\n') + '\n';
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
stdout = stdinLines.slice(0, headLines).join('\n') + '\n';
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
const results = [];
|
|
631
|
+
for (let fi = 0; fi < headFiles.length; fi++) {
|
|
632
|
+
const file = headFiles[fi];
|
|
633
|
+
const content = await this.fs.read(file, { encoding: 'utf-8' });
|
|
634
|
+
const fileLines = content.split('\n');
|
|
635
|
+
let selectedLines;
|
|
636
|
+
if (headExcludeLast) {
|
|
637
|
+
selectedLines = fileLines.slice(0, -headLines);
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
selectedLines = fileLines.slice(0, headLines);
|
|
641
|
+
}
|
|
642
|
+
// Add header if multiple files and not quiet
|
|
643
|
+
if (headFiles.length > 1 && !headQuiet) {
|
|
644
|
+
if (fi > 0)
|
|
645
|
+
results.push('');
|
|
646
|
+
results.push(`==> ${file} <==`);
|
|
647
|
+
}
|
|
648
|
+
results.push(...selectedLines);
|
|
649
|
+
}
|
|
650
|
+
stdout = results.join('\n') + '\n';
|
|
651
|
+
}
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
case 'tail': {
|
|
655
|
+
// Parse options: -n N, -n +N (start from line N), -q (quiet, no headers)
|
|
656
|
+
let tailQuiet = false;
|
|
657
|
+
let tailLines = 10;
|
|
658
|
+
let tailStartFrom = false; // +N means start from line N
|
|
659
|
+
const tailFiles = [];
|
|
660
|
+
for (let i = 0; i < args.length; i++) {
|
|
661
|
+
const arg = args[i];
|
|
662
|
+
if (arg === '-q' || arg === '--quiet' || arg === '--silent') {
|
|
663
|
+
tailQuiet = true;
|
|
664
|
+
}
|
|
665
|
+
else if (arg === '-n' && args[i + 1]) {
|
|
666
|
+
const nArg = args[++i];
|
|
667
|
+
if (nArg.startsWith('+')) {
|
|
668
|
+
// -n +N means start from line N (1-indexed)
|
|
669
|
+
tailStartFrom = true;
|
|
670
|
+
tailLines = parseInt(nArg.slice(1), 10);
|
|
671
|
+
}
|
|
672
|
+
else {
|
|
673
|
+
tailLines = parseInt(nArg, 10);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
else if (arg.startsWith('-n')) {
|
|
677
|
+
const nArg = arg.slice(2);
|
|
678
|
+
if (nArg.startsWith('+')) {
|
|
679
|
+
tailStartFrom = true;
|
|
680
|
+
tailLines = parseInt(nArg.slice(1), 10);
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
tailLines = parseInt(nArg, 10);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
else if (!arg.startsWith('-')) {
|
|
687
|
+
tailFiles.push(arg);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (tailFiles.length === 0) {
|
|
691
|
+
// Read from stdin
|
|
692
|
+
const stdinContent = options?.stdin || '';
|
|
693
|
+
const stdinLines = stdinContent.split('\n');
|
|
694
|
+
const effectiveStdinLines = stdinLines[stdinLines.length - 1] === '' ? stdinLines.slice(0, -1) : stdinLines;
|
|
695
|
+
if (tailStartFrom) {
|
|
696
|
+
// +N means output starting from line N (1-indexed)
|
|
697
|
+
stdout = effectiveStdinLines.slice(tailLines - 1).join('\n') + '\n';
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
stdout = effectiveStdinLines.slice(-tailLines).join('\n') + '\n';
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
const results = [];
|
|
705
|
+
for (let fi = 0; fi < tailFiles.length; fi++) {
|
|
706
|
+
const file = tailFiles[fi];
|
|
707
|
+
const content = await this.fs.read(file, { encoding: 'utf-8' });
|
|
708
|
+
const allLines = content.split('\n');
|
|
709
|
+
const effectiveLines = allLines[allLines.length - 1] === '' ? allLines.slice(0, -1) : allLines;
|
|
710
|
+
let selectedLines;
|
|
711
|
+
if (tailStartFrom) {
|
|
712
|
+
selectedLines = effectiveLines.slice(tailLines - 1);
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
selectedLines = effectiveLines.slice(-tailLines);
|
|
716
|
+
}
|
|
717
|
+
// Add header if multiple files and not quiet
|
|
718
|
+
if (tailFiles.length > 1 && !tailQuiet) {
|
|
719
|
+
if (fi > 0)
|
|
720
|
+
results.push('');
|
|
721
|
+
results.push(`==> ${file} <==`);
|
|
722
|
+
}
|
|
723
|
+
results.push(...selectedLines);
|
|
724
|
+
}
|
|
725
|
+
stdout = results.join('\n') + '\n';
|
|
726
|
+
}
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
case 'test':
|
|
730
|
+
case '[': {
|
|
731
|
+
// Full test/[ implementation with all POSIX operators
|
|
732
|
+
const fileInfoProvider = this.fs ? createFileInfoProvider(this.fs) : undefined;
|
|
733
|
+
const result = await executeTest(args, fileInfoProvider);
|
|
734
|
+
exitCode = result.exitCode;
|
|
735
|
+
if (result.stderr) {
|
|
736
|
+
stderr = result.stderr;
|
|
737
|
+
}
|
|
738
|
+
break;
|
|
739
|
+
}
|
|
740
|
+
// ========================================================================
|
|
741
|
+
// NEW FILESYSTEM COMMANDS
|
|
742
|
+
// ========================================================================
|
|
743
|
+
case 'mkdir': {
|
|
744
|
+
const mkdirRecursive = args.includes('-p');
|
|
745
|
+
const mkdirPath = args.filter(a => !a.startsWith('-'))[0];
|
|
746
|
+
if (!mkdirPath) {
|
|
747
|
+
return this.createResult('mkdir', '', 'mkdir: missing operand', 1, 1);
|
|
748
|
+
}
|
|
749
|
+
await this.fs.mkdir(mkdirPath, { recursive: mkdirRecursive });
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
case 'rmdir': {
|
|
753
|
+
const rmdirPath = args.filter(a => !a.startsWith('-'))[0];
|
|
754
|
+
if (!rmdirPath) {
|
|
755
|
+
return this.createResult('rmdir', '', 'rmdir: missing operand', 1, 1);
|
|
756
|
+
}
|
|
757
|
+
await this.fs.rmdir(rmdirPath);
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
case 'rm': {
|
|
761
|
+
const rmRecursive = args.includes('-r') || args.includes('-rf') || args.includes('-R');
|
|
762
|
+
const rmForce = args.includes('-f') || args.includes('-rf');
|
|
763
|
+
const rmPaths = args.filter(a => !a.startsWith('-'));
|
|
764
|
+
if (rmPaths.length === 0) {
|
|
765
|
+
return this.createResult('rm', '', 'rm: missing operand', 1, 1);
|
|
766
|
+
}
|
|
767
|
+
for (const p of rmPaths) {
|
|
768
|
+
await this.fs.rm(p, { recursive: rmRecursive, force: rmForce });
|
|
769
|
+
}
|
|
770
|
+
break;
|
|
771
|
+
}
|
|
772
|
+
case 'cp': {
|
|
773
|
+
const cpRecursive = args.includes('-r') || args.includes('-R');
|
|
774
|
+
const cpPaths = args.filter(a => !a.startsWith('-'));
|
|
775
|
+
if (cpPaths.length < 2) {
|
|
776
|
+
return this.createResult('cp', '', 'cp: missing destination file operand', 1, 1);
|
|
777
|
+
}
|
|
778
|
+
const [cpSrc, cpDest] = cpPaths;
|
|
779
|
+
await this.fs.copyFile(cpSrc, cpDest, { recursive: cpRecursive });
|
|
780
|
+
break;
|
|
781
|
+
}
|
|
782
|
+
case 'mv': {
|
|
783
|
+
const mvPaths = args.filter(a => !a.startsWith('-'));
|
|
784
|
+
if (mvPaths.length < 2) {
|
|
785
|
+
return this.createResult('mv', '', 'mv: missing destination file operand', 1, 1);
|
|
786
|
+
}
|
|
787
|
+
const [mvSrc, mvDest] = mvPaths;
|
|
788
|
+
await this.fs.rename(mvSrc, mvDest);
|
|
789
|
+
break;
|
|
790
|
+
}
|
|
791
|
+
case 'touch': {
|
|
792
|
+
const touchPath = args.filter(a => !a.startsWith('-'))[0];
|
|
793
|
+
if (!touchPath) {
|
|
794
|
+
return this.createResult('touch', '', 'touch: missing file operand', 1, 1);
|
|
795
|
+
}
|
|
796
|
+
const touchNow = new Date();
|
|
797
|
+
try {
|
|
798
|
+
await this.fs.utimes(touchPath, touchNow, touchNow);
|
|
799
|
+
}
|
|
800
|
+
catch {
|
|
801
|
+
await this.fs.write(touchPath, '');
|
|
802
|
+
}
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
case 'truncate': {
|
|
806
|
+
let truncSize = 0;
|
|
807
|
+
let truncPath = '';
|
|
808
|
+
for (let i = 0; i < args.length; i++) {
|
|
809
|
+
if (args[i] === '-s' && args[i + 1]) {
|
|
810
|
+
truncSize = parseInt(args[++i], 10);
|
|
811
|
+
}
|
|
812
|
+
else if (!args[i].startsWith('-')) {
|
|
813
|
+
truncPath = args[i];
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (!truncPath) {
|
|
817
|
+
return this.createResult('truncate', '', 'truncate: missing file operand', 1, 1);
|
|
818
|
+
}
|
|
819
|
+
await this.fs.truncate(truncPath, truncSize);
|
|
820
|
+
break;
|
|
821
|
+
}
|
|
822
|
+
case 'stat': {
|
|
823
|
+
const statPath = args.filter(a => !a.startsWith('-'))[0];
|
|
824
|
+
if (!statPath) {
|
|
825
|
+
return this.createResult('stat', '', 'stat: missing operand', 1, 1);
|
|
826
|
+
}
|
|
827
|
+
const fileStat = await this.fs.stat(statPath);
|
|
828
|
+
const statLines = [
|
|
829
|
+
` File: ${statPath}`,
|
|
830
|
+
` Size: ${fileStat.size}`,
|
|
831
|
+
` Mode: ${fileStat.mode.toString(8)}`,
|
|
832
|
+
`Access: ${fileStat.atime}`,
|
|
833
|
+
`Modify: ${fileStat.mtime}`,
|
|
834
|
+
`Change: ${fileStat.ctime}`,
|
|
835
|
+
];
|
|
836
|
+
stdout = statLines.join('\n') + '\n';
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
case 'readlink': {
|
|
840
|
+
const readlinkPath = args.filter(a => !a.startsWith('-'))[0];
|
|
841
|
+
if (!readlinkPath) {
|
|
842
|
+
return this.createResult('readlink', '', 'readlink: missing operand', 1, 1);
|
|
843
|
+
}
|
|
844
|
+
const linkTarget = await this.fs.readlink(readlinkPath);
|
|
845
|
+
stdout = linkTarget + '\n';
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
case 'ln': {
|
|
849
|
+
const lnSymbolic = args.includes('-s');
|
|
850
|
+
const lnPaths = args.filter(a => !a.startsWith('-'));
|
|
851
|
+
if (lnPaths.length < 2) {
|
|
852
|
+
return this.createResult('ln', '', 'ln: missing file operand', 1, 1);
|
|
853
|
+
}
|
|
854
|
+
const [lnTarget, lnPath] = lnPaths;
|
|
855
|
+
if (lnSymbolic) {
|
|
856
|
+
await this.fs.symlink(lnTarget, lnPath);
|
|
857
|
+
}
|
|
858
|
+
else {
|
|
859
|
+
await this.fs.link(lnTarget, lnPath);
|
|
860
|
+
}
|
|
861
|
+
break;
|
|
862
|
+
}
|
|
863
|
+
case 'chmod': {
|
|
864
|
+
const chmodNonFlags = args.filter(a => !a.startsWith('-'));
|
|
865
|
+
if (chmodNonFlags.length < 2) {
|
|
866
|
+
return this.createResult('chmod', '', 'chmod: missing operand', 1, 1);
|
|
867
|
+
}
|
|
868
|
+
const [chmodModeStr, chmodPath] = chmodNonFlags;
|
|
869
|
+
let chmodMode;
|
|
870
|
+
if (/^[0-7]+$/.test(chmodModeStr)) {
|
|
871
|
+
chmodMode = parseInt(chmodModeStr, 8);
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
return this.createResult('chmod', '', 'chmod: symbolic modes not yet supported', 1, 1);
|
|
875
|
+
}
|
|
876
|
+
await this.fs.chmod(chmodPath, chmodMode);
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
case 'chown': {
|
|
880
|
+
const chownNonFlags = args.filter(a => !a.startsWith('-'));
|
|
881
|
+
if (chownNonFlags.length < 2) {
|
|
882
|
+
return this.createResult('chown', '', 'chown: missing operand', 1, 1);
|
|
883
|
+
}
|
|
884
|
+
const [chownOwnerGroup, chownPath] = chownNonFlags;
|
|
885
|
+
const [chownUidStr, chownGidStr] = chownOwnerGroup.split(':');
|
|
886
|
+
const chownUid = parseInt(chownUidStr, 10) || 0;
|
|
887
|
+
const chownGid = chownGidStr ? parseInt(chownGidStr, 10) || 0 : chownUid;
|
|
888
|
+
await this.fs.chown(chownPath, chownUid, chownGid);
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
891
|
+
case 'find': {
|
|
892
|
+
let findSearchPath = '.';
|
|
893
|
+
let findNamePattern = null;
|
|
894
|
+
let findTypeFilter = null;
|
|
895
|
+
for (let i = 0; i < args.length; i++) {
|
|
896
|
+
const arg = args[i];
|
|
897
|
+
if (arg === '-name' && args[i + 1]) {
|
|
898
|
+
findNamePattern = args[++i];
|
|
899
|
+
}
|
|
900
|
+
else if (arg === '-type' && args[i + 1]) {
|
|
901
|
+
findTypeFilter = args[++i];
|
|
902
|
+
}
|
|
903
|
+
else if (!arg.startsWith('-')) {
|
|
904
|
+
findSearchPath = arg;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
const findEntries = await this.fs.list(findSearchPath, { recursive: true, withFileTypes: true });
|
|
908
|
+
const findResults = [];
|
|
909
|
+
for (const entry of findEntries) {
|
|
910
|
+
if (findTypeFilter === 'f' && !entry.isFile())
|
|
911
|
+
continue;
|
|
912
|
+
if (findTypeFilter === 'd' && !entry.isDirectory())
|
|
913
|
+
continue;
|
|
914
|
+
if (findNamePattern) {
|
|
915
|
+
const findGlobPattern = findNamePattern.replace(/\./g, '\\.').replace(/\*/g, '.*').replace(/\?/g, '.');
|
|
916
|
+
const findRegex = new RegExp(`^${findGlobPattern}$`);
|
|
917
|
+
if (!findRegex.test(entry.name))
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
findResults.push(entry.path || `${findSearchPath}/${entry.name}`);
|
|
921
|
+
}
|
|
922
|
+
stdout = findResults.join('\n') + (findResults.length ? '\n' : '');
|
|
923
|
+
break;
|
|
924
|
+
}
|
|
925
|
+
case 'grep': {
|
|
926
|
+
let grepIgnoreCase = false;
|
|
927
|
+
let grepShowLineNumbers = false;
|
|
928
|
+
let grepInvertMatch = false;
|
|
929
|
+
let grepRecursive = false;
|
|
930
|
+
let grepPerlRegex = false; // -P flag: use JavaScript regex (supports lookahead, non-greedy, etc.)
|
|
931
|
+
let grepPattern = '';
|
|
932
|
+
const grepFiles = [];
|
|
933
|
+
for (let i = 0; i < args.length; i++) {
|
|
934
|
+
const arg = args[i];
|
|
935
|
+
if (arg === '-i')
|
|
936
|
+
grepIgnoreCase = true;
|
|
937
|
+
else if (arg === '-n')
|
|
938
|
+
grepShowLineNumbers = true;
|
|
939
|
+
else if (arg === '-v')
|
|
940
|
+
grepInvertMatch = true;
|
|
941
|
+
else if (arg === '-r' || arg === '-R')
|
|
942
|
+
grepRecursive = true;
|
|
943
|
+
else if (arg === '-P')
|
|
944
|
+
grepPerlRegex = true; // Enable Perl-compatible regex (JavaScript regex)
|
|
945
|
+
else if (!arg.startsWith('-')) {
|
|
946
|
+
if (!grepPattern)
|
|
947
|
+
grepPattern = arg;
|
|
948
|
+
else
|
|
949
|
+
grepFiles.push(arg);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
if (!grepPattern) {
|
|
953
|
+
return this.createResult('grep', '', 'grep: missing pattern', 1, 1);
|
|
954
|
+
}
|
|
955
|
+
// When -P is used, pattern is already JavaScript regex syntax
|
|
956
|
+
// When -P is not used, convert basic grep pattern to JavaScript regex
|
|
957
|
+
let grepRegexPattern = grepPattern;
|
|
958
|
+
if (!grepPerlRegex) {
|
|
959
|
+
// Basic grep uses BRE (Basic Regular Expression)
|
|
960
|
+
// In BRE, () {} + ? | need to be escaped to be special
|
|
961
|
+
// For simplicity, we treat the pattern as-is since JavaScript regex is close to ERE
|
|
962
|
+
grepRegexPattern = grepPattern;
|
|
963
|
+
}
|
|
964
|
+
const grepRegex = new RegExp(grepRegexPattern, grepIgnoreCase ? 'i' : '');
|
|
965
|
+
const grepResults = [];
|
|
966
|
+
let grepMatchFound = false;
|
|
967
|
+
const grepProcessFile = async (filePath) => {
|
|
968
|
+
const grepContent = await this.fs.read(filePath, { encoding: 'utf-8' });
|
|
969
|
+
const grepFileLines = grepContent.split('\n');
|
|
970
|
+
for (let lineNum = 0; lineNum < grepFileLines.length; lineNum++) {
|
|
971
|
+
const line = grepFileLines[lineNum];
|
|
972
|
+
const matches = grepRegex.test(line);
|
|
973
|
+
if (matches !== grepInvertMatch) {
|
|
974
|
+
grepMatchFound = true;
|
|
975
|
+
let output = '';
|
|
976
|
+
if (grepFiles.length > 1)
|
|
977
|
+
output += `${filePath}:`;
|
|
978
|
+
if (grepShowLineNumbers)
|
|
979
|
+
output += `${lineNum + 1}:`;
|
|
980
|
+
output += line;
|
|
981
|
+
grepResults.push(output);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
if (grepFiles.length === 0) {
|
|
986
|
+
const grepInput = options?.stdin || '';
|
|
987
|
+
const grepInputLines = grepInput.split('\n');
|
|
988
|
+
for (let lineNum = 0; lineNum < grepInputLines.length; lineNum++) {
|
|
989
|
+
const line = grepInputLines[lineNum];
|
|
990
|
+
const matches = grepRegex.test(line);
|
|
991
|
+
if (matches !== grepInvertMatch) {
|
|
992
|
+
grepMatchFound = true;
|
|
993
|
+
let output = '';
|
|
994
|
+
if (grepShowLineNumbers)
|
|
995
|
+
output += `${lineNum + 1}:`;
|
|
996
|
+
output += line;
|
|
997
|
+
grepResults.push(output);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
for (const file of grepFiles) {
|
|
1003
|
+
if (grepRecursive) {
|
|
1004
|
+
const grepFileStat = await this.fs.stat(file);
|
|
1005
|
+
if (grepFileStat.isDirectory()) {
|
|
1006
|
+
const grepDirEntries = await this.fs.list(file, { recursive: true, withFileTypes: true });
|
|
1007
|
+
for (const entry of grepDirEntries) {
|
|
1008
|
+
if (entry.isFile())
|
|
1009
|
+
await grepProcessFile(entry.path || `${file}/${entry.name}`);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
else {
|
|
1013
|
+
await grepProcessFile(file);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
await grepProcessFile(file);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
stdout = grepResults.join('\n') + (grepResults.length ? '\n' : '');
|
|
1022
|
+
exitCode = grepMatchFound ? 0 : 1;
|
|
1023
|
+
break;
|
|
1024
|
+
}
|
|
1025
|
+
default:
|
|
1026
|
+
throw new Error(`Unsupported native fs command: ${cmd}`);
|
|
1027
|
+
}
|
|
1028
|
+
return this.createResult(`${cmd} ${args.join(' ')}`, stdout, stderr, exitCode, 1);
|
|
1029
|
+
}
|
|
1030
|
+
catch (error) {
|
|
1031
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1032
|
+
return this.createResult(`${cmd} ${args.join(' ')}`, '', message, 1, 1);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Execute pure computation commands natively
|
|
1037
|
+
*/
|
|
1038
|
+
async executeNativeCompute(cmd, args, options) {
|
|
1039
|
+
let stdout = '';
|
|
1040
|
+
let exitCode = 0;
|
|
1041
|
+
switch (cmd) {
|
|
1042
|
+
case 'echo': {
|
|
1043
|
+
// Handle echo flags: -e (enable escapes), -n (no newline), -E (disable escapes)
|
|
1044
|
+
let enableEscapes = false;
|
|
1045
|
+
let addNewline = true;
|
|
1046
|
+
let startIdx = 0;
|
|
1047
|
+
// Parse flags
|
|
1048
|
+
for (let i = 0; i < args.length; i++) {
|
|
1049
|
+
if (args[i] === '-e') {
|
|
1050
|
+
enableEscapes = true;
|
|
1051
|
+
startIdx = i + 1;
|
|
1052
|
+
}
|
|
1053
|
+
else if (args[i] === '-n') {
|
|
1054
|
+
addNewline = false;
|
|
1055
|
+
startIdx = i + 1;
|
|
1056
|
+
}
|
|
1057
|
+
else if (args[i] === '-E') {
|
|
1058
|
+
enableEscapes = false;
|
|
1059
|
+
startIdx = i + 1;
|
|
1060
|
+
}
|
|
1061
|
+
else if (args[i] === '-en' || args[i] === '-ne') {
|
|
1062
|
+
enableEscapes = true;
|
|
1063
|
+
addNewline = false;
|
|
1064
|
+
startIdx = i + 1;
|
|
1065
|
+
}
|
|
1066
|
+
else {
|
|
1067
|
+
break;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
let output = args.slice(startIdx).join(' ');
|
|
1071
|
+
if (enableEscapes) {
|
|
1072
|
+
output = output
|
|
1073
|
+
.replace(/\\n/g, '\n')
|
|
1074
|
+
.replace(/\\t/g, '\t')
|
|
1075
|
+
.replace(/\\r/g, '\r')
|
|
1076
|
+
.replace(/\\a/g, '\x07')
|
|
1077
|
+
.replace(/\\b/g, '\b')
|
|
1078
|
+
.replace(/\\f/g, '\f')
|
|
1079
|
+
.replace(/\\v/g, '\v')
|
|
1080
|
+
.replace(/\\\\/g, '\\');
|
|
1081
|
+
}
|
|
1082
|
+
stdout = output + (addNewline ? '\n' : '');
|
|
1083
|
+
break;
|
|
1084
|
+
}
|
|
1085
|
+
case 'printf':
|
|
1086
|
+
// Simple printf implementation
|
|
1087
|
+
stdout = args.join(' ')
|
|
1088
|
+
.replace(/\\n/g, '\n')
|
|
1089
|
+
.replace(/\\t/g, '\t')
|
|
1090
|
+
.replace(/\\0/g, '\0') // Handle null characters
|
|
1091
|
+
.replace(/\\r/g, '\r');
|
|
1092
|
+
break;
|
|
1093
|
+
case 'pwd':
|
|
1094
|
+
stdout = (options?.cwd || process.cwd?.() || '/') + '\n';
|
|
1095
|
+
break;
|
|
1096
|
+
case 'date':
|
|
1097
|
+
stdout = new Date().toString() + '\n';
|
|
1098
|
+
break;
|
|
1099
|
+
case 'true':
|
|
1100
|
+
exitCode = 0;
|
|
1101
|
+
break;
|
|
1102
|
+
case 'false':
|
|
1103
|
+
exitCode = 1;
|
|
1104
|
+
break;
|
|
1105
|
+
case 'basename': {
|
|
1106
|
+
const path = args[0] || '';
|
|
1107
|
+
stdout = path.split('/').pop() || '';
|
|
1108
|
+
if (stdout)
|
|
1109
|
+
stdout += '\n';
|
|
1110
|
+
break;
|
|
1111
|
+
}
|
|
1112
|
+
case 'dirname': {
|
|
1113
|
+
const path = args[0] || '';
|
|
1114
|
+
const parts = path.split('/');
|
|
1115
|
+
parts.pop();
|
|
1116
|
+
stdout = (parts.join('/') || (path.startsWith('/') ? '/' : '.')) + '\n';
|
|
1117
|
+
break;
|
|
1118
|
+
}
|
|
1119
|
+
case 'wc': {
|
|
1120
|
+
// Count lines/words/chars from stdin or return empty
|
|
1121
|
+
const input = options?.stdin || '';
|
|
1122
|
+
const lines = input.split('\n').length - (input.endsWith('\n') ? 1 : 0);
|
|
1123
|
+
const words = input.split(/\s+/).filter(Boolean).length;
|
|
1124
|
+
const chars = input.length;
|
|
1125
|
+
if (args.includes('-l')) {
|
|
1126
|
+
stdout = `${lines}\n`;
|
|
1127
|
+
}
|
|
1128
|
+
else if (args.includes('-w')) {
|
|
1129
|
+
stdout = `${words}\n`;
|
|
1130
|
+
}
|
|
1131
|
+
else if (args.includes('-c')) {
|
|
1132
|
+
stdout = `${chars}\n`;
|
|
1133
|
+
}
|
|
1134
|
+
else {
|
|
1135
|
+
stdout = `${lines} ${words} ${chars}\n`;
|
|
1136
|
+
}
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1139
|
+
case 'sort': {
|
|
1140
|
+
const input = options?.stdin || '';
|
|
1141
|
+
const lines = input.split('\n').filter(Boolean);
|
|
1142
|
+
lines.sort();
|
|
1143
|
+
if (args.includes('-r'))
|
|
1144
|
+
lines.reverse();
|
|
1145
|
+
stdout = lines.join('\n') + (lines.length ? '\n' : '');
|
|
1146
|
+
break;
|
|
1147
|
+
}
|
|
1148
|
+
case 'uniq': {
|
|
1149
|
+
const input = options?.stdin || '';
|
|
1150
|
+
const lines = input.split('\n');
|
|
1151
|
+
const unique = lines.filter((line, i) => i === 0 || line !== lines[i - 1]);
|
|
1152
|
+
stdout = unique.join('\n');
|
|
1153
|
+
break;
|
|
1154
|
+
}
|
|
1155
|
+
case 'tr': {
|
|
1156
|
+
const input = options?.stdin || '';
|
|
1157
|
+
const set1 = args[0] || '';
|
|
1158
|
+
const set2 = args[1] || '';
|
|
1159
|
+
let result = input;
|
|
1160
|
+
for (let i = 0; i < set1.length && i < set2.length; i++) {
|
|
1161
|
+
result = result.replace(new RegExp(set1[i], 'g'), set2[i]);
|
|
1162
|
+
}
|
|
1163
|
+
stdout = result;
|
|
1164
|
+
break;
|
|
1165
|
+
}
|
|
1166
|
+
case 'rev': {
|
|
1167
|
+
const input = options?.stdin || '';
|
|
1168
|
+
stdout = input.split('\n').map(line => line.split('').reverse().join('')).join('\n');
|
|
1169
|
+
break;
|
|
1170
|
+
}
|
|
1171
|
+
case 'cut': {
|
|
1172
|
+
const input = options?.stdin || '';
|
|
1173
|
+
const delimiter = args.includes('-d') ? args[args.indexOf('-d') + 1] : '\t';
|
|
1174
|
+
const fieldArg = args.includes('-f') ? args[args.indexOf('-f') + 1] : '1';
|
|
1175
|
+
const field = parseInt(fieldArg, 10) - 1;
|
|
1176
|
+
stdout = input.split('\n').map(line => {
|
|
1177
|
+
const parts = line.split(delimiter);
|
|
1178
|
+
return parts[field] || '';
|
|
1179
|
+
}).join('\n');
|
|
1180
|
+
break;
|
|
1181
|
+
}
|
|
1182
|
+
// ========================================================================
|
|
1183
|
+
// MATH & CONTROL COMMANDS
|
|
1184
|
+
// ========================================================================
|
|
1185
|
+
case 'bc': {
|
|
1186
|
+
// bc receives expression from stdin (piped) or as a file/expression arg
|
|
1187
|
+
const input = options?.stdin || args.join(' ');
|
|
1188
|
+
const mathLib = args.includes('-l');
|
|
1189
|
+
const result = executeBc(input, { mathLib });
|
|
1190
|
+
stdout = result.result ? result.result + '\n' : '';
|
|
1191
|
+
exitCode = result.exitCode;
|
|
1192
|
+
if (result.stderr) {
|
|
1193
|
+
return this.createResult(`${cmd} ${args.join(' ')}`, stdout, result.stderr, exitCode, 1);
|
|
1194
|
+
}
|
|
1195
|
+
break;
|
|
1196
|
+
}
|
|
1197
|
+
case 'expr': {
|
|
1198
|
+
const result = executeExpr(args);
|
|
1199
|
+
stdout = result.result ? result.result + '\n' : '';
|
|
1200
|
+
exitCode = result.exitCode;
|
|
1201
|
+
if (result.stderr) {
|
|
1202
|
+
return this.createResult(`${cmd} ${args.join(' ')}`, stdout, result.stderr, exitCode, 1);
|
|
1203
|
+
}
|
|
1204
|
+
break;
|
|
1205
|
+
}
|
|
1206
|
+
case 'seq': {
|
|
1207
|
+
// Parse seq options: -s separator, -w equal-width, -f format
|
|
1208
|
+
const seqOptions = {};
|
|
1209
|
+
const numArgs = [];
|
|
1210
|
+
for (let i = 0; i < args.length; i++) {
|
|
1211
|
+
const arg = args[i];
|
|
1212
|
+
if (arg === '-s' && args[i + 1] !== undefined) {
|
|
1213
|
+
seqOptions.separator = args[++i].replace(/\\t/g, '\t').replace(/\\n/g, '\n');
|
|
1214
|
+
}
|
|
1215
|
+
else if (arg === '-w') {
|
|
1216
|
+
seqOptions.equalWidth = true;
|
|
1217
|
+
}
|
|
1218
|
+
else if (arg === '-f' && args[i + 1] !== undefined) {
|
|
1219
|
+
seqOptions.format = args[++i];
|
|
1220
|
+
}
|
|
1221
|
+
else if (!arg.startsWith('-') || /^-?\d/.test(arg)) {
|
|
1222
|
+
numArgs.push(parseFloat(arg));
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
const result = executeSeq(numArgs, seqOptions);
|
|
1226
|
+
stdout = result.result ? result.result + '\n' : '';
|
|
1227
|
+
exitCode = result.exitCode;
|
|
1228
|
+
break;
|
|
1229
|
+
}
|
|
1230
|
+
case 'shuf': {
|
|
1231
|
+
// Parse shuf options
|
|
1232
|
+
const shufOptions = {};
|
|
1233
|
+
const input = options?.stdin || '';
|
|
1234
|
+
let inputLines = input.split('\n').filter(l => l.length > 0);
|
|
1235
|
+
for (let i = 0; i < args.length; i++) {
|
|
1236
|
+
const arg = args[i];
|
|
1237
|
+
if ((arg === '-n' || arg === '--head-count') && args[i + 1] !== undefined) {
|
|
1238
|
+
shufOptions.count = parseInt(args[++i], 10);
|
|
1239
|
+
}
|
|
1240
|
+
else if (arg.startsWith('--head-count=')) {
|
|
1241
|
+
shufOptions.count = parseInt(arg.slice(13), 10);
|
|
1242
|
+
}
|
|
1243
|
+
else if (arg === '-r' || arg === '--repeat') {
|
|
1244
|
+
shufOptions.replacement = true;
|
|
1245
|
+
}
|
|
1246
|
+
else if (arg === '-e' || arg === '--echo') {
|
|
1247
|
+
// Collect remaining args as echo args
|
|
1248
|
+
shufOptions.echoArgs = args.slice(i + 1).filter(a => !a.startsWith('-') || /^-?\d/.test(a));
|
|
1249
|
+
}
|
|
1250
|
+
else if (arg === '-i' && args[i + 1] !== undefined) {
|
|
1251
|
+
const range = args[++i];
|
|
1252
|
+
const [start, end] = range.split('-').map(Number);
|
|
1253
|
+
shufOptions.inputRange = { start, end };
|
|
1254
|
+
}
|
|
1255
|
+
else if (arg === '-o' && args[i + 1] !== undefined) {
|
|
1256
|
+
shufOptions.outputFile = args[++i];
|
|
1257
|
+
}
|
|
1258
|
+
else if (arg.startsWith('--random-source=') || arg === '--random-source') {
|
|
1259
|
+
shufOptions.randomSource = arg.includes('=') ? arg.split('=')[1] : args[++i];
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
const result = executeShuf(inputLines, shufOptions);
|
|
1263
|
+
stdout = result.result ? result.result + '\n' : '';
|
|
1264
|
+
exitCode = result.exitCode;
|
|
1265
|
+
break;
|
|
1266
|
+
}
|
|
1267
|
+
case 'sleep': {
|
|
1268
|
+
if (args.length === 0) {
|
|
1269
|
+
return this.createResult('sleep', '', 'sleep: missing operand', 1, 1);
|
|
1270
|
+
}
|
|
1271
|
+
const result = await executeSleep(args);
|
|
1272
|
+
exitCode = result.exitCode;
|
|
1273
|
+
if (result.stderr) {
|
|
1274
|
+
return this.createResult(`${cmd} ${args.join(' ')}`, '', result.stderr, exitCode, 1);
|
|
1275
|
+
}
|
|
1276
|
+
break;
|
|
1277
|
+
}
|
|
1278
|
+
case 'timeout': {
|
|
1279
|
+
// Parse timeout options
|
|
1280
|
+
const timeoutOptions = { duration: '' };
|
|
1281
|
+
let commandStartIndex = -1;
|
|
1282
|
+
for (let i = 0; i < args.length; i++) {
|
|
1283
|
+
const arg = args[i];
|
|
1284
|
+
if (arg === '-k' && args[i + 1] !== undefined) {
|
|
1285
|
+
timeoutOptions.killAfter = args[++i];
|
|
1286
|
+
}
|
|
1287
|
+
else if (arg.startsWith('--kill-after=')) {
|
|
1288
|
+
timeoutOptions.killAfter = arg.slice(13);
|
|
1289
|
+
}
|
|
1290
|
+
else if (arg === '-s' && args[i + 1] !== undefined) {
|
|
1291
|
+
timeoutOptions.signal = args[++i];
|
|
1292
|
+
}
|
|
1293
|
+
else if (arg.startsWith('--signal=')) {
|
|
1294
|
+
timeoutOptions.signal = arg.slice(9);
|
|
1295
|
+
}
|
|
1296
|
+
else if (arg === '--preserve-status') {
|
|
1297
|
+
timeoutOptions.preserveStatus = true;
|
|
1298
|
+
}
|
|
1299
|
+
else if (arg === '--foreground') {
|
|
1300
|
+
timeoutOptions.foreground = true;
|
|
1301
|
+
}
|
|
1302
|
+
else if (arg === '-v' || arg === '--verbose') {
|
|
1303
|
+
timeoutOptions.verbose = true;
|
|
1304
|
+
}
|
|
1305
|
+
else if (!arg.startsWith('-') || /^[0-9.]/.test(arg)) {
|
|
1306
|
+
if (timeoutOptions.duration === '') {
|
|
1307
|
+
timeoutOptions.duration = arg;
|
|
1308
|
+
}
|
|
1309
|
+
else {
|
|
1310
|
+
commandStartIndex = i;
|
|
1311
|
+
break;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
if (timeoutOptions.duration === '' || commandStartIndex < 0) {
|
|
1316
|
+
return this.createResult('timeout', '', 'timeout: missing operand', 125, 1);
|
|
1317
|
+
}
|
|
1318
|
+
const subCommand = args.slice(commandStartIndex).join(' ');
|
|
1319
|
+
// Check if the sub-command is a path to a non-existent command
|
|
1320
|
+
// (paths like /nonexistent/cmd are not supported natively)
|
|
1321
|
+
const firstWord = subCommand.trim().split(/\s+/)[0];
|
|
1322
|
+
if (firstWord.startsWith('/')) {
|
|
1323
|
+
// Full path command - not supported natively
|
|
1324
|
+
const notFoundResult = timeoutCommandNotFound(firstWord);
|
|
1325
|
+
return this.createResult(`timeout ${args.join(' ')}`, '', notFoundResult.stderr, notFoundResult.exitCode, 1);
|
|
1326
|
+
}
|
|
1327
|
+
// Execute with timeout
|
|
1328
|
+
const result = await executeTimeout(timeoutOptions, subCommand, async (cmd) => {
|
|
1329
|
+
const subResult = await this.execute(cmd, options);
|
|
1330
|
+
return {
|
|
1331
|
+
exitCode: subResult.exitCode,
|
|
1332
|
+
stdout: subResult.stdout,
|
|
1333
|
+
stderr: subResult.stderr,
|
|
1334
|
+
};
|
|
1335
|
+
});
|
|
1336
|
+
return this.createResult(`timeout ${args.join(' ')}`, result.stdout, result.stderr, result.exitCode, 1);
|
|
1337
|
+
}
|
|
1338
|
+
default:
|
|
1339
|
+
throw new Error(`Unsupported native compute command: ${cmd}`);
|
|
1340
|
+
}
|
|
1341
|
+
return this.createResult(`${cmd} ${args.join(' ')}`, stdout, '', exitCode, 1);
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Execute data processing commands (jq, yq, base64, envsubst)
|
|
1345
|
+
*/
|
|
1346
|
+
async executeDataProcessing(cmd, args, fullCommand, options) {
|
|
1347
|
+
try {
|
|
1348
|
+
let stdout = '';
|
|
1349
|
+
const stderr = '';
|
|
1350
|
+
const exitCode = 0;
|
|
1351
|
+
switch (cmd) {
|
|
1352
|
+
case 'jq': {
|
|
1353
|
+
const { query, file, options: jqOptions } = parseJqArgs(args);
|
|
1354
|
+
// Get input from file or stdin
|
|
1355
|
+
let input;
|
|
1356
|
+
if (file) {
|
|
1357
|
+
if (this.fs) {
|
|
1358
|
+
input = (await this.fs.read(file, { encoding: 'utf-8' }));
|
|
1359
|
+
}
|
|
1360
|
+
else {
|
|
1361
|
+
throw new Error(`ENOENT: no such file: ${file}`);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
else if (options?.stdin) {
|
|
1365
|
+
input = options.stdin;
|
|
1366
|
+
}
|
|
1367
|
+
else {
|
|
1368
|
+
return this.createResult(fullCommand, '', 'jq: no input', 1, 1);
|
|
1369
|
+
}
|
|
1370
|
+
stdout = executeJq(query || '.', input, jqOptions);
|
|
1371
|
+
break;
|
|
1372
|
+
}
|
|
1373
|
+
case 'yq': {
|
|
1374
|
+
const { query, file, options: yqOptions } = parseYqArgs(args);
|
|
1375
|
+
// Get input from file or stdin
|
|
1376
|
+
let input;
|
|
1377
|
+
if (file) {
|
|
1378
|
+
if (this.fs) {
|
|
1379
|
+
input = (await this.fs.read(file, { encoding: 'utf-8' }));
|
|
1380
|
+
}
|
|
1381
|
+
else {
|
|
1382
|
+
throw new Error(`ENOENT: no such file: ${file}`);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
else if (options?.stdin) {
|
|
1386
|
+
input = options.stdin;
|
|
1387
|
+
}
|
|
1388
|
+
else {
|
|
1389
|
+
return this.createResult(fullCommand, '', 'yq: no input', 1, 1);
|
|
1390
|
+
}
|
|
1391
|
+
stdout = executeYq(query, input, yqOptions);
|
|
1392
|
+
break;
|
|
1393
|
+
}
|
|
1394
|
+
case 'base64': {
|
|
1395
|
+
const { file, options: b64Options } = parseBase64Args(args);
|
|
1396
|
+
// Get input from file or stdin
|
|
1397
|
+
let input;
|
|
1398
|
+
if (file) {
|
|
1399
|
+
if (this.fs) {
|
|
1400
|
+
input = (await this.fs.read(file, { encoding: 'utf-8' }));
|
|
1401
|
+
}
|
|
1402
|
+
else {
|
|
1403
|
+
throw new Error(`ENOENT: no such file: ${file}`);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
else if (options?.stdin !== undefined) {
|
|
1407
|
+
input = options.stdin;
|
|
1408
|
+
}
|
|
1409
|
+
else {
|
|
1410
|
+
// Default to empty for encode, error for decode
|
|
1411
|
+
if (b64Options.decode) {
|
|
1412
|
+
return this.createResult(fullCommand, '', 'base64: no input', 1, 1);
|
|
1413
|
+
}
|
|
1414
|
+
input = '';
|
|
1415
|
+
}
|
|
1416
|
+
stdout = executeBase64(input, b64Options);
|
|
1417
|
+
break;
|
|
1418
|
+
}
|
|
1419
|
+
case 'envsubst': {
|
|
1420
|
+
// Parse args and handle input redirect
|
|
1421
|
+
const { options: envOptions, inputRedirect } = parseEnvsubstArgs(args, options?.env || {});
|
|
1422
|
+
// Get input from redirect, stdin, or empty
|
|
1423
|
+
let input;
|
|
1424
|
+
if (inputRedirect) {
|
|
1425
|
+
if (this.fs) {
|
|
1426
|
+
input = (await this.fs.read(inputRedirect, { encoding: 'utf-8' }));
|
|
1427
|
+
}
|
|
1428
|
+
else {
|
|
1429
|
+
throw new Error(`ENOENT: no such file: ${inputRedirect}`);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
else if (options?.stdin !== undefined) {
|
|
1433
|
+
input = options.stdin;
|
|
1434
|
+
}
|
|
1435
|
+
else {
|
|
1436
|
+
input = '';
|
|
1437
|
+
}
|
|
1438
|
+
stdout = executeEnvsubst(input, envOptions);
|
|
1439
|
+
break;
|
|
1440
|
+
}
|
|
1441
|
+
default:
|
|
1442
|
+
throw new Error(`Unknown data processing command: ${cmd}`);
|
|
1443
|
+
}
|
|
1444
|
+
return this.createResult(fullCommand, stdout, stderr, exitCode, 1);
|
|
1445
|
+
}
|
|
1446
|
+
catch (error) {
|
|
1447
|
+
if (error instanceof JqError) {
|
|
1448
|
+
return this.createResult(fullCommand, '', `jq: ${error.message}`, error.exitCode, 1);
|
|
1449
|
+
}
|
|
1450
|
+
if (error instanceof Base64Error) {
|
|
1451
|
+
return this.createResult(fullCommand, '', `base64: ${error.message}`, 1, 1);
|
|
1452
|
+
}
|
|
1453
|
+
if (error instanceof EnvsubstError) {
|
|
1454
|
+
return this.createResult(fullCommand, '', `envsubst: ${error.message}`, 1, 1);
|
|
1455
|
+
}
|
|
1456
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1457
|
+
return this.createResult(fullCommand, '', message, 1, 1);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Execute curl command via native fetch() API
|
|
1462
|
+
*
|
|
1463
|
+
* Supports common curl flags:
|
|
1464
|
+
* - -X METHOD: HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
1465
|
+
* - -H "Header: Value": Custom headers
|
|
1466
|
+
* - -d "data": Request body (implies POST if no -X specified)
|
|
1467
|
+
* - -o filename: Output to file
|
|
1468
|
+
* - -s: Silent mode (suppress progress)
|
|
1469
|
+
* - -L: Follow redirects
|
|
1470
|
+
* - -u user:pass: Basic authentication
|
|
1471
|
+
* - -I: HEAD request (headers only)
|
|
1472
|
+
* - --data-raw "data": Raw data without processing
|
|
1473
|
+
*/
|
|
1474
|
+
async executeCurl(args) {
|
|
1475
|
+
let method = 'GET';
|
|
1476
|
+
let url = '';
|
|
1477
|
+
const headers = {};
|
|
1478
|
+
let body;
|
|
1479
|
+
let outputFile;
|
|
1480
|
+
let silent = false;
|
|
1481
|
+
let followRedirects = false;
|
|
1482
|
+
let headersOnly = false;
|
|
1483
|
+
let includeHeaders = false;
|
|
1484
|
+
for (let i = 0; i < args.length; i++) {
|
|
1485
|
+
const arg = args[i];
|
|
1486
|
+
if (arg === '-X' && args[i + 1]) {
|
|
1487
|
+
method = args[++i];
|
|
1488
|
+
}
|
|
1489
|
+
else if (arg === '-H' && args[i + 1]) {
|
|
1490
|
+
const headerValue = args[++i];
|
|
1491
|
+
const colonIndex = headerValue.indexOf(':');
|
|
1492
|
+
if (colonIndex > 0) {
|
|
1493
|
+
const key = headerValue.slice(0, colonIndex).trim();
|
|
1494
|
+
const val = headerValue.slice(colonIndex + 1).trim();
|
|
1495
|
+
headers[key] = val;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
else if ((arg === '-d' || arg === '--data' || arg === '--data-raw') && args[i + 1]) {
|
|
1499
|
+
body = args[++i];
|
|
1500
|
+
// -d implies POST if method wasn't explicitly set
|
|
1501
|
+
if (method === 'GET')
|
|
1502
|
+
method = 'POST';
|
|
1503
|
+
}
|
|
1504
|
+
else if (arg === '-o' && args[i + 1]) {
|
|
1505
|
+
outputFile = args[++i];
|
|
1506
|
+
}
|
|
1507
|
+
else if (arg === '-s' || arg === '--silent') {
|
|
1508
|
+
silent = true;
|
|
1509
|
+
}
|
|
1510
|
+
else if (arg === '-L' || arg === '--location') {
|
|
1511
|
+
followRedirects = true;
|
|
1512
|
+
}
|
|
1513
|
+
else if (arg === '-I' || arg === '--head') {
|
|
1514
|
+
headersOnly = true;
|
|
1515
|
+
method = 'HEAD';
|
|
1516
|
+
}
|
|
1517
|
+
else if (arg === '-i' || arg === '--include') {
|
|
1518
|
+
includeHeaders = true;
|
|
1519
|
+
}
|
|
1520
|
+
else if (arg === '-u' && args[i + 1]) {
|
|
1521
|
+
const credentials = args[++i];
|
|
1522
|
+
const encoded = btoa(credentials);
|
|
1523
|
+
headers['Authorization'] = `Basic ${encoded}`;
|
|
1524
|
+
}
|
|
1525
|
+
else if (!arg.startsWith('-') && !url) {
|
|
1526
|
+
url = arg;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
if (!url) {
|
|
1530
|
+
return this.createResult('curl', '', 'curl: no URL specified', 1, 1);
|
|
1531
|
+
}
|
|
1532
|
+
// Ensure URL has protocol
|
|
1533
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
1534
|
+
url = 'https://' + url;
|
|
1535
|
+
}
|
|
1536
|
+
try {
|
|
1537
|
+
const response = await fetch(url, {
|
|
1538
|
+
method,
|
|
1539
|
+
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
1540
|
+
body,
|
|
1541
|
+
redirect: followRedirects ? 'follow' : 'manual',
|
|
1542
|
+
});
|
|
1543
|
+
let output = '';
|
|
1544
|
+
// Build headers string if needed
|
|
1545
|
+
if (headersOnly || includeHeaders) {
|
|
1546
|
+
const headerLines = [`HTTP/1.1 ${response.status} ${response.statusText}`];
|
|
1547
|
+
response.headers.forEach((value, key) => {
|
|
1548
|
+
headerLines.push(`${key}: ${value}`);
|
|
1549
|
+
});
|
|
1550
|
+
headerLines.push('');
|
|
1551
|
+
output = headerLines.join('\r\n');
|
|
1552
|
+
}
|
|
1553
|
+
// Get body content (unless HEAD request)
|
|
1554
|
+
if (!headersOnly) {
|
|
1555
|
+
const content = await response.text();
|
|
1556
|
+
output += content;
|
|
1557
|
+
}
|
|
1558
|
+
// Write to file if -o specified
|
|
1559
|
+
if (outputFile && this.fs) {
|
|
1560
|
+
await this.fs.write(outputFile, output);
|
|
1561
|
+
return this.createResult(`curl ${args.join(' ')}`, silent ? '' : '', '', 0, 1);
|
|
1562
|
+
}
|
|
1563
|
+
return this.createResult(`curl ${args.join(' ')}`, output, '', response.ok ? 0 : 1, 1);
|
|
1564
|
+
}
|
|
1565
|
+
catch (error) {
|
|
1566
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1567
|
+
return this.createResult(`curl ${args.join(' ')}`, '', `curl: ${message}`, 1, 1);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Execute wget command via native fetch() API
|
|
1572
|
+
*
|
|
1573
|
+
* Supports common wget flags:
|
|
1574
|
+
* - -O filename: Output to file (use "-" for stdout)
|
|
1575
|
+
* - -q: Quiet mode
|
|
1576
|
+
* - --header "Header: Value": Custom headers
|
|
1577
|
+
* - -S: Print server response headers
|
|
1578
|
+
* - --no-check-certificate: Skip SSL verification (ignored in fetch)
|
|
1579
|
+
*/
|
|
1580
|
+
async executeWget(args) {
|
|
1581
|
+
let url = '';
|
|
1582
|
+
const headers = {};
|
|
1583
|
+
let outputFile;
|
|
1584
|
+
let quiet = false;
|
|
1585
|
+
let printHeaders = false;
|
|
1586
|
+
for (let i = 0; i < args.length; i++) {
|
|
1587
|
+
const arg = args[i];
|
|
1588
|
+
if (arg === '-O' && args[i + 1]) {
|
|
1589
|
+
outputFile = args[++i];
|
|
1590
|
+
}
|
|
1591
|
+
else if (arg === '-q' || arg === '--quiet') {
|
|
1592
|
+
quiet = true;
|
|
1593
|
+
}
|
|
1594
|
+
else if (arg === '--header' && args[i + 1]) {
|
|
1595
|
+
const headerValue = args[++i];
|
|
1596
|
+
const colonIndex = headerValue.indexOf(':');
|
|
1597
|
+
if (colonIndex > 0) {
|
|
1598
|
+
const key = headerValue.slice(0, colonIndex).trim();
|
|
1599
|
+
const val = headerValue.slice(colonIndex + 1).trim();
|
|
1600
|
+
headers[key] = val;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
else if (arg === '-S' || arg === '--server-response') {
|
|
1604
|
+
printHeaders = true;
|
|
1605
|
+
}
|
|
1606
|
+
else if (arg === '--no-check-certificate') {
|
|
1607
|
+
// Ignored - fetch handles SSL automatically
|
|
1608
|
+
}
|
|
1609
|
+
else if (!arg.startsWith('-') && !url) {
|
|
1610
|
+
url = arg;
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
if (!url) {
|
|
1614
|
+
return this.createResult('wget', '', 'wget: missing URL', 1, 1);
|
|
1615
|
+
}
|
|
1616
|
+
// Ensure URL has protocol
|
|
1617
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
1618
|
+
url = 'https://' + url;
|
|
1619
|
+
}
|
|
1620
|
+
try {
|
|
1621
|
+
const response = await fetch(url, {
|
|
1622
|
+
method: 'GET',
|
|
1623
|
+
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
1624
|
+
redirect: 'follow',
|
|
1625
|
+
});
|
|
1626
|
+
let stderr = '';
|
|
1627
|
+
// Print server response headers if -S
|
|
1628
|
+
if (printHeaders && !quiet) {
|
|
1629
|
+
const headerLines = [` HTTP/1.1 ${response.status} ${response.statusText}`];
|
|
1630
|
+
response.headers.forEach((value, key) => {
|
|
1631
|
+
headerLines.push(` ${key}: ${value}`);
|
|
1632
|
+
});
|
|
1633
|
+
stderr = headerLines.join('\n') + '\n';
|
|
1634
|
+
}
|
|
1635
|
+
const content = await response.text();
|
|
1636
|
+
// Determine output filename if not specified
|
|
1637
|
+
if (!outputFile) {
|
|
1638
|
+
// Extract filename from URL path
|
|
1639
|
+
const urlObj = new URL(url);
|
|
1640
|
+
const pathParts = urlObj.pathname.split('/');
|
|
1641
|
+
outputFile = pathParts[pathParts.length - 1] || 'index.html';
|
|
1642
|
+
}
|
|
1643
|
+
// Output to stdout if "-"
|
|
1644
|
+
if (outputFile === '-') {
|
|
1645
|
+
return this.createResult(`wget ${args.join(' ')}`, content, stderr, response.ok ? 0 : 1, 1);
|
|
1646
|
+
}
|
|
1647
|
+
// Write to file
|
|
1648
|
+
if (this.fs) {
|
|
1649
|
+
await this.fs.write(outputFile, content);
|
|
1650
|
+
const successMsg = quiet ? '' : `'${outputFile}' saved\n`;
|
|
1651
|
+
return this.createResult(`wget ${args.join(' ')}`, '', stderr + successMsg, 0, 1);
|
|
1652
|
+
}
|
|
1653
|
+
// No fs available, output to stdout
|
|
1654
|
+
return this.createResult(`wget ${args.join(' ')}`, content, stderr, response.ok ? 0 : 1, 1);
|
|
1655
|
+
}
|
|
1656
|
+
catch (error) {
|
|
1657
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1658
|
+
return this.createResult(`wget ${args.join(' ')}`, '', `wget: ${message}`, 1, 1);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Execute crypto commands via Web Crypto API (sha256sum, md5sum, uuidgen, etc.)
|
|
1663
|
+
*/
|
|
1664
|
+
async executeNativeCrypto(cmd, args, options) {
|
|
1665
|
+
try {
|
|
1666
|
+
const result = await executeCryptoCommand(cmd, args, {
|
|
1667
|
+
fs: this.fs,
|
|
1668
|
+
stdin: options?.stdin,
|
|
1669
|
+
});
|
|
1670
|
+
return this.createResult(`${cmd} ${args.join(' ')}`, result.stdout, result.stderr, result.exitCode, 1);
|
|
1671
|
+
}
|
|
1672
|
+
catch (error) {
|
|
1673
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1674
|
+
return this.createResult(`${cmd} ${args.join(' ')}`, '', message, 1, 1);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
/**
|
|
1678
|
+
* Execute text processing commands (sed, awk, diff, patch, tee, xargs)
|
|
1679
|
+
*/
|
|
1680
|
+
async executeTextProcessing(cmd, args, fullCommand, options) {
|
|
1681
|
+
try {
|
|
1682
|
+
const input = options?.stdin || '';
|
|
1683
|
+
switch (cmd) {
|
|
1684
|
+
case 'sed': {
|
|
1685
|
+
// For sed, we need to get file content if a file is specified
|
|
1686
|
+
const files = args.filter(a => !a.startsWith('-') && !a.startsWith('s/') && !a.startsWith("'") && !a.startsWith('"'));
|
|
1687
|
+
let content = input;
|
|
1688
|
+
if (files.length > 0 && this.fs) {
|
|
1689
|
+
content = await this.fs.read(files[files.length - 1], { encoding: 'utf-8' });
|
|
1690
|
+
}
|
|
1691
|
+
const result = executeSed(args, content, this.fs);
|
|
1692
|
+
return this.createResult(fullCommand, result.stdout, result.stderr, result.exitCode, 1);
|
|
1693
|
+
}
|
|
1694
|
+
case 'awk': {
|
|
1695
|
+
// For awk, get file content if specified
|
|
1696
|
+
let content = input;
|
|
1697
|
+
// Skip the first non-flag arg which is usually the program
|
|
1698
|
+
const nonFlagArgs = args.filter(a => !a.startsWith('-'));
|
|
1699
|
+
if (nonFlagArgs.length > 1 && this.fs) {
|
|
1700
|
+
// Last arg is file
|
|
1701
|
+
content = await this.fs.read(nonFlagArgs[nonFlagArgs.length - 1], { encoding: 'utf-8' });
|
|
1702
|
+
}
|
|
1703
|
+
const result = executeAwk(args, content);
|
|
1704
|
+
return this.createResult(fullCommand, result.stdout, result.stderr, result.exitCode, 1);
|
|
1705
|
+
}
|
|
1706
|
+
case 'diff': {
|
|
1707
|
+
// diff requires two file contents
|
|
1708
|
+
const diffFiles = args.filter(a => !a.startsWith('-'));
|
|
1709
|
+
if (diffFiles.length >= 2 && this.fs) {
|
|
1710
|
+
const file1Content = await this.fs.read(diffFiles[0], { encoding: 'utf-8' });
|
|
1711
|
+
const file2Content = await this.fs.read(diffFiles[1], { encoding: 'utf-8' });
|
|
1712
|
+
const unified = args.includes('-u') || args.includes('--unified');
|
|
1713
|
+
const context = args.includes('-c') || args.includes('--context');
|
|
1714
|
+
const result = executeDiff(file1Content, file2Content, diffFiles[0], diffFiles[1], { unified, context });
|
|
1715
|
+
return this.createResult(fullCommand, result.stdout, result.stderr, result.exitCode, 1);
|
|
1716
|
+
}
|
|
1717
|
+
return this.createResult(fullCommand, '', 'diff: missing operand', 1, 1);
|
|
1718
|
+
}
|
|
1719
|
+
case 'patch': {
|
|
1720
|
+
// patch applies a diff to a file
|
|
1721
|
+
const reverse = args.includes('-R') || args.includes('--reverse');
|
|
1722
|
+
const dryRun = args.includes('--dry-run');
|
|
1723
|
+
let stripLevel = 0;
|
|
1724
|
+
const stripArg = args.find(a => a.startsWith('-p'));
|
|
1725
|
+
if (stripArg) {
|
|
1726
|
+
stripLevel = parseInt(stripArg.slice(2), 10);
|
|
1727
|
+
}
|
|
1728
|
+
// Input is the patch content - parse it to find target file
|
|
1729
|
+
const patchContent = input;
|
|
1730
|
+
const fileMatch = patchContent.match(/^---\s+(\S+)/m);
|
|
1731
|
+
const newFileMatch = patchContent.match(/^\+\+\+\s+(\S+)/m);
|
|
1732
|
+
let targetFile = newFileMatch?.[1] || fileMatch?.[1] || '';
|
|
1733
|
+
// Strip prefix from target file
|
|
1734
|
+
if (stripLevel > 0 && targetFile) {
|
|
1735
|
+
const parts = targetFile.split('/');
|
|
1736
|
+
targetFile = parts.slice(stripLevel).join('/');
|
|
1737
|
+
}
|
|
1738
|
+
// Resolve relative path against cwd if not absolute
|
|
1739
|
+
const cwd = options?.cwd || '/test';
|
|
1740
|
+
if (!targetFile.startsWith('/') && cwd) {
|
|
1741
|
+
targetFile = `${cwd.replace(/\/$/, '')}/${targetFile}`;
|
|
1742
|
+
}
|
|
1743
|
+
// Read original file content
|
|
1744
|
+
let originalContent = '';
|
|
1745
|
+
if (targetFile && this.fs) {
|
|
1746
|
+
try {
|
|
1747
|
+
originalContent = await this.fs.read(targetFile, { encoding: 'utf-8' });
|
|
1748
|
+
}
|
|
1749
|
+
catch {
|
|
1750
|
+
// File might not exist, continue with empty
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
const result = executePatch(originalContent, patchContent, { reverse, dryRun, stripLevel });
|
|
1754
|
+
// Write result back to file if not dry run and successful
|
|
1755
|
+
if (!dryRun && result.exitCode === 0 && result.result && targetFile && this.fs) {
|
|
1756
|
+
await this.fs.write(targetFile, result.result);
|
|
1757
|
+
}
|
|
1758
|
+
return this.createResult(fullCommand, result.stdout, result.stderr, result.exitCode, 1);
|
|
1759
|
+
}
|
|
1760
|
+
case 'tee': {
|
|
1761
|
+
const result = await executeTee(input, args, this.fs);
|
|
1762
|
+
return this.createResult(fullCommand, result.stdout, result.stderr, result.exitCode, 1);
|
|
1763
|
+
}
|
|
1764
|
+
case 'xargs': {
|
|
1765
|
+
const result = await executeXargs(input, args, async (cmd) => {
|
|
1766
|
+
const subResult = await this.execute(cmd, options);
|
|
1767
|
+
return {
|
|
1768
|
+
stdout: subResult.stdout,
|
|
1769
|
+
stderr: subResult.stderr,
|
|
1770
|
+
exitCode: subResult.exitCode,
|
|
1771
|
+
};
|
|
1772
|
+
});
|
|
1773
|
+
return this.createResult(fullCommand, result.stdout, result.stderr, result.exitCode, 1);
|
|
1774
|
+
}
|
|
1775
|
+
default:
|
|
1776
|
+
throw new Error(`Unsupported text processing command: ${cmd}`);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
catch (error) {
|
|
1780
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1781
|
+
return this.createResult(fullCommand, '', message, 1, 1);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
/**
|
|
1785
|
+
* Execute POSIX utility commands (cut, sort, tr, uniq, wc, basename, dirname, echo, printf, date, dd, od)
|
|
1786
|
+
*/
|
|
1787
|
+
async executePosixUtils(cmd, args, fullCommand, options) {
|
|
1788
|
+
try {
|
|
1789
|
+
const input = options?.stdin || '';
|
|
1790
|
+
switch (cmd) {
|
|
1791
|
+
case 'cut': {
|
|
1792
|
+
// Parse cut options
|
|
1793
|
+
const cutOptions = {};
|
|
1794
|
+
for (let i = 0; i < args.length; i++) {
|
|
1795
|
+
const arg = args[i];
|
|
1796
|
+
if (arg === '-b' && args[i + 1]) {
|
|
1797
|
+
cutOptions.bytes = args[++i];
|
|
1798
|
+
}
|
|
1799
|
+
else if (arg.startsWith('-b')) {
|
|
1800
|
+
cutOptions.bytes = arg.slice(2);
|
|
1801
|
+
}
|
|
1802
|
+
else if (arg === '-c' && args[i + 1]) {
|
|
1803
|
+
cutOptions.chars = args[++i];
|
|
1804
|
+
}
|
|
1805
|
+
else if (arg.startsWith('-c')) {
|
|
1806
|
+
cutOptions.chars = arg.slice(2);
|
|
1807
|
+
}
|
|
1808
|
+
else if (arg === '-f' && args[i + 1]) {
|
|
1809
|
+
cutOptions.fields = args[++i];
|
|
1810
|
+
}
|
|
1811
|
+
else if (arg.startsWith('-f')) {
|
|
1812
|
+
cutOptions.fields = arg.slice(2);
|
|
1813
|
+
}
|
|
1814
|
+
else if (arg === '-d' && args[i + 1]) {
|
|
1815
|
+
cutOptions.delimiter = args[++i];
|
|
1816
|
+
}
|
|
1817
|
+
else if (arg.startsWith('-d')) {
|
|
1818
|
+
cutOptions.delimiter = arg.slice(2);
|
|
1819
|
+
}
|
|
1820
|
+
else if (arg === '--output-delimiter' && args[i + 1]) {
|
|
1821
|
+
cutOptions.outputDelimiter = args[++i];
|
|
1822
|
+
}
|
|
1823
|
+
else if (arg === '-s' || arg === '--only-delimited') {
|
|
1824
|
+
cutOptions.onlyDelimited = true;
|
|
1825
|
+
}
|
|
1826
|
+
else if (arg === '--complement') {
|
|
1827
|
+
cutOptions.complement = true;
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
const stdout = executeCut(input, cutOptions);
|
|
1831
|
+
return this.createResult(fullCommand, stdout, '', 0, 1);
|
|
1832
|
+
}
|
|
1833
|
+
case 'sort': {
|
|
1834
|
+
// Parse sort options
|
|
1835
|
+
const sortOptions = {};
|
|
1836
|
+
for (let i = 0; i < args.length; i++) {
|
|
1837
|
+
const arg = args[i];
|
|
1838
|
+
if (arg === '-n' || arg === '--numeric-sort') {
|
|
1839
|
+
sortOptions.numeric = true;
|
|
1840
|
+
}
|
|
1841
|
+
else if (arg === '-r' || arg === '--reverse') {
|
|
1842
|
+
sortOptions.reverse = true;
|
|
1843
|
+
}
|
|
1844
|
+
else if (arg === '-u' || arg === '--unique') {
|
|
1845
|
+
sortOptions.unique = true;
|
|
1846
|
+
}
|
|
1847
|
+
else if (arg === '-f' || arg === '--ignore-case') {
|
|
1848
|
+
sortOptions.ignoreCase = true;
|
|
1849
|
+
}
|
|
1850
|
+
else if (arg === '-b' || arg === '--ignore-leading-blanks') {
|
|
1851
|
+
sortOptions.ignoreLeadingBlanks = true;
|
|
1852
|
+
}
|
|
1853
|
+
else if (arg === '-h' || arg === '--human-numeric-sort') {
|
|
1854
|
+
sortOptions.humanNumeric = true;
|
|
1855
|
+
}
|
|
1856
|
+
else if (arg === '-c' || arg === '--check') {
|
|
1857
|
+
sortOptions.check = true;
|
|
1858
|
+
}
|
|
1859
|
+
else if (arg === '-k' && args[i + 1]) {
|
|
1860
|
+
sortOptions.key = args[++i];
|
|
1861
|
+
}
|
|
1862
|
+
else if (arg.startsWith('-k')) {
|
|
1863
|
+
sortOptions.key = arg.slice(2);
|
|
1864
|
+
}
|
|
1865
|
+
else if (arg === '-t' && args[i + 1]) {
|
|
1866
|
+
sortOptions.separator = args[++i];
|
|
1867
|
+
}
|
|
1868
|
+
else if (arg.startsWith('-t')) {
|
|
1869
|
+
sortOptions.separator = arg.slice(2);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
const lines = input.split('\n').filter((l, i, arr) => i < arr.length - 1 || l !== '');
|
|
1873
|
+
const sorted = executeSort(lines, sortOptions);
|
|
1874
|
+
const stdout = sorted.join('\n') + (sorted.length > 0 ? '\n' : '');
|
|
1875
|
+
return this.createResult(fullCommand, stdout, '', 0, 1);
|
|
1876
|
+
}
|
|
1877
|
+
case 'tr': {
|
|
1878
|
+
// Parse tr options
|
|
1879
|
+
const trOptions = {};
|
|
1880
|
+
let set1 = '';
|
|
1881
|
+
let set2;
|
|
1882
|
+
const nonFlagArgs = [];
|
|
1883
|
+
for (let i = 0; i < args.length; i++) {
|
|
1884
|
+
const arg = args[i];
|
|
1885
|
+
if (arg === '-d' || arg === '--delete') {
|
|
1886
|
+
trOptions.delete = true;
|
|
1887
|
+
}
|
|
1888
|
+
else if (arg === '-s' || arg === '--squeeze-repeats') {
|
|
1889
|
+
trOptions.squeeze = true;
|
|
1890
|
+
}
|
|
1891
|
+
else if (arg === '-c' || arg === '-C' || arg === '--complement') {
|
|
1892
|
+
trOptions.complement = true;
|
|
1893
|
+
}
|
|
1894
|
+
else if (!arg.startsWith('-')) {
|
|
1895
|
+
nonFlagArgs.push(arg);
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
set1 = nonFlagArgs[0] || '';
|
|
1899
|
+
set2 = nonFlagArgs[1];
|
|
1900
|
+
const stdout = executeTr(input, set1, set2, trOptions);
|
|
1901
|
+
return this.createResult(fullCommand, stdout, '', 0, 1);
|
|
1902
|
+
}
|
|
1903
|
+
case 'uniq': {
|
|
1904
|
+
// Parse uniq options
|
|
1905
|
+
const uniqOptions = {};
|
|
1906
|
+
for (let i = 0; i < args.length; i++) {
|
|
1907
|
+
const arg = args[i];
|
|
1908
|
+
if (arg === '-c' || arg === '--count') {
|
|
1909
|
+
uniqOptions.count = true;
|
|
1910
|
+
}
|
|
1911
|
+
else if (arg === '-d' || arg === '--repeated') {
|
|
1912
|
+
uniqOptions.repeated = true;
|
|
1913
|
+
}
|
|
1914
|
+
else if (arg === '-u' || arg === '--unique') {
|
|
1915
|
+
uniqOptions.unique = true;
|
|
1916
|
+
}
|
|
1917
|
+
else if (arg === '-i' || arg === '--ignore-case') {
|
|
1918
|
+
uniqOptions.ignoreCase = true;
|
|
1919
|
+
}
|
|
1920
|
+
else if (arg === '-f' && args[i + 1]) {
|
|
1921
|
+
uniqOptions.skipFields = parseInt(args[++i], 10);
|
|
1922
|
+
}
|
|
1923
|
+
else if (arg.startsWith('-f')) {
|
|
1924
|
+
uniqOptions.skipFields = parseInt(arg.slice(2), 10);
|
|
1925
|
+
}
|
|
1926
|
+
else if (arg === '-s' && args[i + 1]) {
|
|
1927
|
+
uniqOptions.skipChars = parseInt(args[++i], 10);
|
|
1928
|
+
}
|
|
1929
|
+
else if (arg.startsWith('-s')) {
|
|
1930
|
+
uniqOptions.skipChars = parseInt(arg.slice(2), 10);
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
const lines = input.split('\n').filter((l, i, arr) => i < arr.length - 1 || l !== '');
|
|
1934
|
+
const unique = executeUniq(lines, uniqOptions);
|
|
1935
|
+
const stdout = unique.join('\n') + (unique.length > 0 ? '\n' : '');
|
|
1936
|
+
return this.createResult(fullCommand, stdout, '', 0, 1);
|
|
1937
|
+
}
|
|
1938
|
+
case 'wc': {
|
|
1939
|
+
// Parse wc options
|
|
1940
|
+
const wcOptions = {};
|
|
1941
|
+
let hasOptions = false;
|
|
1942
|
+
for (const arg of args) {
|
|
1943
|
+
if (arg === '-l' || arg === '--lines') {
|
|
1944
|
+
wcOptions.lines = true;
|
|
1945
|
+
hasOptions = true;
|
|
1946
|
+
}
|
|
1947
|
+
else if (arg === '-w' || arg === '--words') {
|
|
1948
|
+
wcOptions.words = true;
|
|
1949
|
+
hasOptions = true;
|
|
1950
|
+
}
|
|
1951
|
+
else if (arg === '-c' || arg === '--bytes') {
|
|
1952
|
+
wcOptions.bytes = true;
|
|
1953
|
+
hasOptions = true;
|
|
1954
|
+
}
|
|
1955
|
+
else if (arg === '-m' || arg === '--chars') {
|
|
1956
|
+
wcOptions.chars = true;
|
|
1957
|
+
hasOptions = true;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
const result = executeWc(input, wcOptions);
|
|
1961
|
+
// Format output like GNU wc
|
|
1962
|
+
let stdout = '';
|
|
1963
|
+
if (!hasOptions) {
|
|
1964
|
+
// Default: lines, words, bytes
|
|
1965
|
+
stdout = `${result.lines} ${result.words} ${result.bytes}\n`;
|
|
1966
|
+
}
|
|
1967
|
+
else {
|
|
1968
|
+
const parts = [];
|
|
1969
|
+
if (wcOptions.lines)
|
|
1970
|
+
parts.push(result.lines);
|
|
1971
|
+
if (wcOptions.words)
|
|
1972
|
+
parts.push(result.words);
|
|
1973
|
+
if (wcOptions.bytes)
|
|
1974
|
+
parts.push(result.bytes);
|
|
1975
|
+
if (wcOptions.chars)
|
|
1976
|
+
parts.push(result.chars);
|
|
1977
|
+
stdout = parts.join(' ') + '\n';
|
|
1978
|
+
}
|
|
1979
|
+
return this.createResult(fullCommand, stdout, '', 0, 1);
|
|
1980
|
+
}
|
|
1981
|
+
case 'basename': {
|
|
1982
|
+
const path = args.find(a => !a.startsWith('-')) || '';
|
|
1983
|
+
const suffix = args.filter(a => !a.startsWith('-'))[1];
|
|
1984
|
+
const stdout = executeBasename(path, suffix) + '\n';
|
|
1985
|
+
return this.createResult(fullCommand, stdout, '', 0, 1);
|
|
1986
|
+
}
|
|
1987
|
+
case 'dirname': {
|
|
1988
|
+
const path = args.find(a => !a.startsWith('-')) || '';
|
|
1989
|
+
const stdout = executeDirname(path) + '\n';
|
|
1990
|
+
return this.createResult(fullCommand, stdout, '', 0, 1);
|
|
1991
|
+
}
|
|
1992
|
+
case 'echo': {
|
|
1993
|
+
// Parse echo options
|
|
1994
|
+
const echoOptions = {};
|
|
1995
|
+
let startIdx = 0;
|
|
1996
|
+
for (let i = 0; i < args.length; i++) {
|
|
1997
|
+
if (args[i] === '-n') {
|
|
1998
|
+
echoOptions.noNewline = true;
|
|
1999
|
+
startIdx = i + 1;
|
|
2000
|
+
}
|
|
2001
|
+
else if (args[i] === '-e') {
|
|
2002
|
+
echoOptions.interpretEscapes = true;
|
|
2003
|
+
startIdx = i + 1;
|
|
2004
|
+
}
|
|
2005
|
+
else if (args[i] === '-E') {
|
|
2006
|
+
echoOptions.interpretEscapes = false;
|
|
2007
|
+
startIdx = i + 1;
|
|
2008
|
+
}
|
|
2009
|
+
else if (args[i] === '-en' || args[i] === '-ne') {
|
|
2010
|
+
echoOptions.noNewline = true;
|
|
2011
|
+
echoOptions.interpretEscapes = true;
|
|
2012
|
+
startIdx = i + 1;
|
|
2013
|
+
}
|
|
2014
|
+
else {
|
|
2015
|
+
break;
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
const stdout = executeEcho(args.slice(startIdx), echoOptions);
|
|
2019
|
+
return this.createResult(fullCommand, stdout, '', 0, 1);
|
|
2020
|
+
}
|
|
2021
|
+
case 'printf': {
|
|
2022
|
+
if (args.length === 0) {
|
|
2023
|
+
return this.createResult(fullCommand, '', '', 0, 1);
|
|
2024
|
+
}
|
|
2025
|
+
const format = args[0];
|
|
2026
|
+
const formatArgs = args.slice(1);
|
|
2027
|
+
const stdout = executePrintf(format, formatArgs);
|
|
2028
|
+
return this.createResult(fullCommand, stdout, '', 0, 1);
|
|
2029
|
+
}
|
|
2030
|
+
case 'date': {
|
|
2031
|
+
// Parse date options
|
|
2032
|
+
const dateOptions = {};
|
|
2033
|
+
let format;
|
|
2034
|
+
for (let i = 0; i < args.length; i++) {
|
|
2035
|
+
const arg = args[i];
|
|
2036
|
+
if (arg === '-u' || arg === '--utc' || arg === '--universal') {
|
|
2037
|
+
dateOptions.utc = true;
|
|
2038
|
+
}
|
|
2039
|
+
else if (arg === '-d' && args[i + 1]) {
|
|
2040
|
+
dateOptions.date = args[++i];
|
|
2041
|
+
}
|
|
2042
|
+
else if (arg.startsWith('--date=')) {
|
|
2043
|
+
dateOptions.date = arg.slice(7);
|
|
2044
|
+
}
|
|
2045
|
+
else if (arg.startsWith('+')) {
|
|
2046
|
+
format = arg;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
const stdout = executeDate(format, dateOptions) + '\n';
|
|
2050
|
+
return this.createResult(fullCommand, stdout, '', 0, 1);
|
|
2051
|
+
}
|
|
2052
|
+
case 'dd': {
|
|
2053
|
+
// Parse dd options (dd uses operand=value format)
|
|
2054
|
+
const ddOptions = {};
|
|
2055
|
+
let inputFile;
|
|
2056
|
+
let outputFile;
|
|
2057
|
+
let convOptions = [];
|
|
2058
|
+
for (const arg of args) {
|
|
2059
|
+
if (arg.startsWith('bs=')) {
|
|
2060
|
+
ddOptions.bs = parseInt(arg.slice(3), 10);
|
|
2061
|
+
}
|
|
2062
|
+
else if (arg.startsWith('count=')) {
|
|
2063
|
+
ddOptions.count = parseInt(arg.slice(6), 10);
|
|
2064
|
+
}
|
|
2065
|
+
else if (arg.startsWith('skip=')) {
|
|
2066
|
+
ddOptions.skip = parseInt(arg.slice(5), 10);
|
|
2067
|
+
}
|
|
2068
|
+
else if (arg.startsWith('seek=')) {
|
|
2069
|
+
ddOptions.seek = parseInt(arg.slice(5), 10);
|
|
2070
|
+
}
|
|
2071
|
+
else if (arg.startsWith('ibs=')) {
|
|
2072
|
+
ddOptions.ibs = parseInt(arg.slice(4), 10);
|
|
2073
|
+
}
|
|
2074
|
+
else if (arg.startsWith('obs=')) {
|
|
2075
|
+
ddOptions.obs = parseInt(arg.slice(4), 10);
|
|
2076
|
+
}
|
|
2077
|
+
else if (arg.startsWith('if=')) {
|
|
2078
|
+
inputFile = arg.slice(3);
|
|
2079
|
+
}
|
|
2080
|
+
else if (arg.startsWith('of=')) {
|
|
2081
|
+
outputFile = arg.slice(3);
|
|
2082
|
+
}
|
|
2083
|
+
else if (arg.startsWith('conv=')) {
|
|
2084
|
+
convOptions = arg.slice(5).split(',');
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
// Get input data from file or stdin
|
|
2088
|
+
let inputData;
|
|
2089
|
+
if (inputFile) {
|
|
2090
|
+
if (!this.fs) {
|
|
2091
|
+
return this.createResult(fullCommand, '', 'dd: no filesystem capability for if=', 1, 1);
|
|
2092
|
+
}
|
|
2093
|
+
try {
|
|
2094
|
+
const content = await this.fs.read(inputFile);
|
|
2095
|
+
inputData = typeof content === 'string' ? new TextEncoder().encode(content) : content;
|
|
2096
|
+
}
|
|
2097
|
+
catch (e) {
|
|
2098
|
+
return this.createResult(fullCommand, '', `dd: ${inputFile}: No such file or directory`, 1, 1);
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
else {
|
|
2102
|
+
inputData = new TextEncoder().encode(input);
|
|
2103
|
+
}
|
|
2104
|
+
// Execute dd
|
|
2105
|
+
let outputData = executeDd(inputData, ddOptions);
|
|
2106
|
+
// Apply conv options
|
|
2107
|
+
if (convOptions.includes('ucase')) {
|
|
2108
|
+
const text = new TextDecoder().decode(outputData);
|
|
2109
|
+
outputData = new TextEncoder().encode(text.toUpperCase());
|
|
2110
|
+
}
|
|
2111
|
+
if (convOptions.includes('lcase')) {
|
|
2112
|
+
const text = new TextDecoder().decode(outputData);
|
|
2113
|
+
outputData = new TextEncoder().encode(text.toLowerCase());
|
|
2114
|
+
}
|
|
2115
|
+
// Calculate stats for stderr
|
|
2116
|
+
const bs = ddOptions.bs || 512;
|
|
2117
|
+
const recordsIn = Math.ceil(inputData.length / bs);
|
|
2118
|
+
const recordsOut = Math.ceil(outputData.length / bs);
|
|
2119
|
+
const stderr = `${recordsIn}+0 records in\n${recordsOut}+0 records out\n${outputData.length} bytes copied`;
|
|
2120
|
+
// Write output to file or stdout
|
|
2121
|
+
if (outputFile) {
|
|
2122
|
+
if (!this.fs) {
|
|
2123
|
+
return this.createResult(fullCommand, '', 'dd: no filesystem capability for of=', 1, 1);
|
|
2124
|
+
}
|
|
2125
|
+
await this.fs.write(outputFile, outputData);
|
|
2126
|
+
return this.createResult(fullCommand, '', stderr, 0, 1);
|
|
2127
|
+
}
|
|
2128
|
+
const stdout = new TextDecoder().decode(outputData);
|
|
2129
|
+
return this.createResult(fullCommand, stdout, stderr, 0, 1);
|
|
2130
|
+
}
|
|
2131
|
+
case 'od': {
|
|
2132
|
+
// Parse od options
|
|
2133
|
+
const odOptions = {};
|
|
2134
|
+
for (let i = 0; i < args.length; i++) {
|
|
2135
|
+
const arg = args[i];
|
|
2136
|
+
if (arg === '-A' && args[i + 1]) {
|
|
2137
|
+
odOptions.addressFormat = args[++i];
|
|
2138
|
+
}
|
|
2139
|
+
else if (arg.startsWith('-A')) {
|
|
2140
|
+
odOptions.addressFormat = arg.slice(2);
|
|
2141
|
+
}
|
|
2142
|
+
else if (arg === '-t' && args[i + 1]) {
|
|
2143
|
+
odOptions.format = args[++i];
|
|
2144
|
+
}
|
|
2145
|
+
else if (arg.startsWith('-t')) {
|
|
2146
|
+
odOptions.format = arg.slice(2);
|
|
2147
|
+
}
|
|
2148
|
+
else if (arg === '-x') {
|
|
2149
|
+
odOptions.format = 'x';
|
|
2150
|
+
}
|
|
2151
|
+
else if (arg === '-c') {
|
|
2152
|
+
odOptions.format = 'c';
|
|
2153
|
+
}
|
|
2154
|
+
else if (arg === '-d') {
|
|
2155
|
+
odOptions.format = 'd';
|
|
2156
|
+
}
|
|
2157
|
+
else if (arg === '-o') {
|
|
2158
|
+
odOptions.format = 'o';
|
|
2159
|
+
}
|
|
2160
|
+
else if (arg === '-w' && args[i + 1]) {
|
|
2161
|
+
odOptions.width = parseInt(args[++i], 10);
|
|
2162
|
+
}
|
|
2163
|
+
else if (arg.startsWith('-w')) {
|
|
2164
|
+
odOptions.width = parseInt(arg.slice(2), 10);
|
|
2165
|
+
}
|
|
2166
|
+
else if (arg === '-j' && args[i + 1]) {
|
|
2167
|
+
odOptions.skip = parseInt(args[++i], 10);
|
|
2168
|
+
}
|
|
2169
|
+
else if (arg.startsWith('-j')) {
|
|
2170
|
+
odOptions.skip = parseInt(arg.slice(2), 10);
|
|
2171
|
+
}
|
|
2172
|
+
else if (arg === '-N' && args[i + 1]) {
|
|
2173
|
+
odOptions.count = parseInt(args[++i], 10);
|
|
2174
|
+
}
|
|
2175
|
+
else if (arg.startsWith('-N')) {
|
|
2176
|
+
odOptions.count = parseInt(arg.slice(2), 10);
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
const inputData = new TextEncoder().encode(input);
|
|
2180
|
+
const stdout = executeOd(inputData, odOptions);
|
|
2181
|
+
return this.createResult(fullCommand, stdout, '', 0, 1);
|
|
2182
|
+
}
|
|
2183
|
+
default:
|
|
2184
|
+
throw new Error(`Unsupported POSIX utility command: ${cmd}`);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
catch (error) {
|
|
2188
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2189
|
+
return this.createResult(fullCommand, '', message, 1, 1);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
/**
|
|
2193
|
+
* Execute system utility commands (yes, whoami, hostname, printenv)
|
|
2194
|
+
*/
|
|
2195
|
+
async executeSystemUtilsCommands(cmd, args, fullCommand, options) {
|
|
2196
|
+
try {
|
|
2197
|
+
// Build context from execution options
|
|
2198
|
+
const context = {
|
|
2199
|
+
cwd: options?.cwd,
|
|
2200
|
+
env: options?.env,
|
|
2201
|
+
stdin: options?.stdin,
|
|
2202
|
+
};
|
|
2203
|
+
switch (cmd) {
|
|
2204
|
+
case 'yes': {
|
|
2205
|
+
// Parse yes options (there are none in standard yes)
|
|
2206
|
+
// Limit to 1000 lines for safety in non-streaming environment
|
|
2207
|
+
const result = executeYes(args, { maxLines: 1000 });
|
|
2208
|
+
return this.createResult(fullCommand, result.stdout, result.stderr, result.exitCode, 1);
|
|
2209
|
+
}
|
|
2210
|
+
case 'whoami': {
|
|
2211
|
+
const result = executeWhoami(args, context);
|
|
2212
|
+
return this.createResult(fullCommand, result.stdout, result.stderr, result.exitCode, 1);
|
|
2213
|
+
}
|
|
2214
|
+
case 'hostname': {
|
|
2215
|
+
const result = executeHostname(args, context);
|
|
2216
|
+
return this.createResult(fullCommand, result.stdout, result.stderr, result.exitCode, 1);
|
|
2217
|
+
}
|
|
2218
|
+
case 'printenv': {
|
|
2219
|
+
// Parse printenv options
|
|
2220
|
+
const printenvOpts = {
|
|
2221
|
+
null: args.includes('-0') || args.includes('--null'),
|
|
2222
|
+
};
|
|
2223
|
+
const varArgs = args.filter(a => a !== '-0' && a !== '--null');
|
|
2224
|
+
const result = executePrintenv(varArgs, context, printenvOpts);
|
|
2225
|
+
return this.createResult(fullCommand, result.stdout, result.stderr, result.exitCode, 1);
|
|
2226
|
+
}
|
|
2227
|
+
default:
|
|
2228
|
+
throw new Error(`Unsupported system utility command: ${cmd}`);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
catch (error) {
|
|
2232
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2233
|
+
return this.createResult(fullCommand, '', message, 1, 1);
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Execute extended utility commands (env, id, uname, tac)
|
|
2238
|
+
*/
|
|
2239
|
+
async executeExtendedUtilsCommands(cmd, args, fullCommand, options) {
|
|
2240
|
+
try {
|
|
2241
|
+
switch (cmd) {
|
|
2242
|
+
case 'env': {
|
|
2243
|
+
const envArgs = parseEnvArgs(args);
|
|
2244
|
+
const baseEnv = options?.env ?? {};
|
|
2245
|
+
const result = executeEnv(baseEnv, envArgs);
|
|
2246
|
+
if (result.command && result.command.length > 0) {
|
|
2247
|
+
// Execute the command with the modified environment
|
|
2248
|
+
const cmdToRun = result.command.join(' ');
|
|
2249
|
+
return this.execute(cmdToRun, { ...options, env: result.env });
|
|
2250
|
+
}
|
|
2251
|
+
// No command - print the environment
|
|
2252
|
+
const output = formatEnv(result.env);
|
|
2253
|
+
return this.createResult(fullCommand, output, '', 0, 1);
|
|
2254
|
+
}
|
|
2255
|
+
case 'id': {
|
|
2256
|
+
const idArgs = parseIdArgs(args);
|
|
2257
|
+
// Use worker identity (could be configurable in future)
|
|
2258
|
+
const output = executeId(DEFAULT_WORKER_IDENTITY, idArgs);
|
|
2259
|
+
return this.createResult(fullCommand, output + '\n', '', 0, 1);
|
|
2260
|
+
}
|
|
2261
|
+
case 'uname': {
|
|
2262
|
+
const unameArgs = parseUnameArgs(args);
|
|
2263
|
+
// Use worker system info (could be configurable in future)
|
|
2264
|
+
const output = executeUname(DEFAULT_WORKER_SYSINFO, unameArgs);
|
|
2265
|
+
return this.createResult(fullCommand, output + '\n', '', 0, 1);
|
|
2266
|
+
}
|
|
2267
|
+
case 'tac': {
|
|
2268
|
+
const { options: tacOptions, files } = parseTacArgs(args);
|
|
2269
|
+
// Get input - from files or stdin
|
|
2270
|
+
let input;
|
|
2271
|
+
if (files.length > 0 && this.fs) {
|
|
2272
|
+
// Read from files
|
|
2273
|
+
const contents = [];
|
|
2274
|
+
for (const file of files) {
|
|
2275
|
+
const content = await this.fs.read(file, { encoding: 'utf-8' });
|
|
2276
|
+
contents.push(content);
|
|
2277
|
+
}
|
|
2278
|
+
input = contents.join('');
|
|
2279
|
+
}
|
|
2280
|
+
else {
|
|
2281
|
+
// Use stdin
|
|
2282
|
+
input = options?.stdin ?? '';
|
|
2283
|
+
}
|
|
2284
|
+
const output = executeTac(input, tacOptions);
|
|
2285
|
+
return this.createResult(fullCommand, output, '', 0, 1);
|
|
2286
|
+
}
|
|
2287
|
+
default:
|
|
2288
|
+
throw new Error(`Unsupported extended utility command: ${cmd}`);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
catch (error) {
|
|
2292
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2293
|
+
return this.createResult(fullCommand, '', message, 1, 1);
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Tier 2: Execute command via RPC service
|
|
2298
|
+
*/
|
|
2299
|
+
async executeTier2(command, classification, options) {
|
|
2300
|
+
const serviceName = classification.capability;
|
|
2301
|
+
if (!serviceName) {
|
|
2302
|
+
throw new Error('No RPC service specified for Tier 2 execution');
|
|
2303
|
+
}
|
|
2304
|
+
const binding = this.rpcBindings[serviceName];
|
|
2305
|
+
if (!binding) {
|
|
2306
|
+
throw new Error(`RPC binding not found: ${serviceName}`);
|
|
2307
|
+
}
|
|
2308
|
+
try {
|
|
2309
|
+
const endpoint = typeof binding.endpoint === 'string'
|
|
2310
|
+
? binding.endpoint
|
|
2311
|
+
: null;
|
|
2312
|
+
if (!endpoint) {
|
|
2313
|
+
// Use the binding's fetch method directly
|
|
2314
|
+
const fetcher = binding.endpoint;
|
|
2315
|
+
const response = await fetcher.fetch('/', {
|
|
2316
|
+
method: 'POST',
|
|
2317
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2318
|
+
body: JSON.stringify({
|
|
2319
|
+
command,
|
|
2320
|
+
cwd: options?.cwd,
|
|
2321
|
+
env: options?.env,
|
|
2322
|
+
timeout: options?.timeout ?? this.defaultTimeout,
|
|
2323
|
+
}),
|
|
2324
|
+
});
|
|
2325
|
+
if (!response.ok) {
|
|
2326
|
+
const errorText = await response.text();
|
|
2327
|
+
return this.createResult(command, '', `RPC error: ${errorText}`, 1, 2);
|
|
2328
|
+
}
|
|
2329
|
+
const result = await response.json();
|
|
2330
|
+
return this.createResult(command, result.stdout, result.stderr, result.exitCode, 2);
|
|
2331
|
+
}
|
|
2332
|
+
// Make HTTP request to RPC endpoint
|
|
2333
|
+
const response = await fetch(`${endpoint}/execute`, {
|
|
2334
|
+
method: 'POST',
|
|
2335
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2336
|
+
body: JSON.stringify({
|
|
2337
|
+
command,
|
|
2338
|
+
cwd: options?.cwd,
|
|
2339
|
+
env: options?.env,
|
|
2340
|
+
timeout: options?.timeout ?? this.defaultTimeout,
|
|
2341
|
+
}),
|
|
2342
|
+
});
|
|
2343
|
+
if (!response.ok) {
|
|
2344
|
+
const errorText = await response.text();
|
|
2345
|
+
return this.createResult(command, '', `RPC error: ${errorText}`, 1, 2);
|
|
2346
|
+
}
|
|
2347
|
+
const result = await response.json();
|
|
2348
|
+
return this.createResult(command, result.stdout, result.stderr, result.exitCode, 2);
|
|
2349
|
+
}
|
|
2350
|
+
catch (error) {
|
|
2351
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2352
|
+
throw new Error(`Tier 2 RPC execution failed: ${message}`);
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
/**
|
|
2356
|
+
* Tier 3: Execute via worker_loaders (dynamic npm modules)
|
|
2357
|
+
*/
|
|
2358
|
+
async executeTier3(command, classification, options) {
|
|
2359
|
+
const moduleName = classification.capability;
|
|
2360
|
+
if (!moduleName) {
|
|
2361
|
+
throw new Error('No module specified for Tier 3 execution');
|
|
2362
|
+
}
|
|
2363
|
+
const loader = this.workerLoaders[moduleName];
|
|
2364
|
+
if (!loader) {
|
|
2365
|
+
throw new Error(`Worker loader not found: ${moduleName}`);
|
|
2366
|
+
}
|
|
2367
|
+
try {
|
|
2368
|
+
// Load the module dynamically
|
|
2369
|
+
const module = await loader.load(moduleName);
|
|
2370
|
+
// Execute command based on module type
|
|
2371
|
+
// This is a simplified implementation - real implementation would
|
|
2372
|
+
// need to know how to invoke each module's CLI
|
|
2373
|
+
const result = await this.executeLoadedModule(module, command, options);
|
|
2374
|
+
return this.createResult(command, result.stdout, result.stderr, result.exitCode, 3);
|
|
2375
|
+
}
|
|
2376
|
+
catch (error) {
|
|
2377
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2378
|
+
throw new Error(`Tier 3 worker loader execution failed: ${message}`);
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
/**
|
|
2382
|
+
* Execute a dynamically loaded module
|
|
2383
|
+
*/
|
|
2384
|
+
async executeLoadedModule(module, command, _options) {
|
|
2385
|
+
// This is a placeholder for module-specific execution logic
|
|
2386
|
+
// Real implementation would handle each module type appropriately
|
|
2387
|
+
const cmd = this.extractCommandName(command);
|
|
2388
|
+
const args = this.extractArgs(command);
|
|
2389
|
+
// Check if module has a CLI-like interface
|
|
2390
|
+
const mod = module;
|
|
2391
|
+
// Try common patterns for CLI modules
|
|
2392
|
+
if (typeof mod.run === 'function') {
|
|
2393
|
+
const result = await mod.run(args);
|
|
2394
|
+
return { stdout: String(result), stderr: '', exitCode: 0 };
|
|
2395
|
+
}
|
|
2396
|
+
if (typeof mod.main === 'function') {
|
|
2397
|
+
const result = await mod.main(args);
|
|
2398
|
+
return { stdout: String(result), stderr: '', exitCode: 0 };
|
|
2399
|
+
}
|
|
2400
|
+
if (typeof mod.default === 'function') {
|
|
2401
|
+
const result = await mod.default(args);
|
|
2402
|
+
return { stdout: String(result), stderr: '', exitCode: 0 };
|
|
2403
|
+
}
|
|
2404
|
+
throw new Error(`Module ${cmd} does not have a callable interface`);
|
|
2405
|
+
}
|
|
2406
|
+
/**
|
|
2407
|
+
* Tier 4: Execute via Sandbox SDK
|
|
2408
|
+
*/
|
|
2409
|
+
async executeTier4(command, _classification, options) {
|
|
2410
|
+
if (!this.sandbox) {
|
|
2411
|
+
throw new Error('Sandbox not configured. Tier 4 execution requires a sandbox binding.');
|
|
2412
|
+
}
|
|
2413
|
+
const result = await this.sandbox.execute(command, options);
|
|
2414
|
+
// Add tier info to result
|
|
2415
|
+
return {
|
|
2416
|
+
...result,
|
|
2417
|
+
classification: {
|
|
2418
|
+
...result.classification,
|
|
2419
|
+
reason: `${result.classification.reason} (Tier 4: Sandbox)`,
|
|
2420
|
+
},
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
// ============================================================================
|
|
2424
|
+
// HELPER METHODS
|
|
2425
|
+
// ============================================================================
|
|
2426
|
+
/**
|
|
2427
|
+
* Extract command name from a full command string
|
|
2428
|
+
*/
|
|
2429
|
+
extractCommandName(command) {
|
|
2430
|
+
const trimmed = command.trim();
|
|
2431
|
+
// Handle env vars prefix: VAR=value cmd
|
|
2432
|
+
const withoutEnvVars = trimmed.replace(/^(\w+=\S+\s+)+/, '');
|
|
2433
|
+
// Get first word
|
|
2434
|
+
const match = withoutEnvVars.match(/^[\w\-\.\/]+/);
|
|
2435
|
+
if (!match)
|
|
2436
|
+
return '';
|
|
2437
|
+
// Handle path: /usr/bin/cmd -> cmd
|
|
2438
|
+
const name = match[0].split('/').pop() || '';
|
|
2439
|
+
return name;
|
|
2440
|
+
}
|
|
2441
|
+
/**
|
|
2442
|
+
* Extract arguments from a full command string
|
|
2443
|
+
*/
|
|
2444
|
+
extractArgs(command) {
|
|
2445
|
+
const trimmed = command.trim();
|
|
2446
|
+
// Remove env vars prefix
|
|
2447
|
+
const withoutEnvVars = trimmed.replace(/^(\w+=\S+\s+)+/, '');
|
|
2448
|
+
// Split by whitespace, respecting quotes
|
|
2449
|
+
const parts = this.tokenize(withoutEnvVars);
|
|
2450
|
+
// Skip the command name
|
|
2451
|
+
return parts.slice(1);
|
|
2452
|
+
}
|
|
2453
|
+
/**
|
|
2454
|
+
* Tokenize command respecting quotes and escape sequences
|
|
2455
|
+
*/
|
|
2456
|
+
tokenize(input) {
|
|
2457
|
+
const tokens = [];
|
|
2458
|
+
let current = '';
|
|
2459
|
+
let inSingleQuote = false;
|
|
2460
|
+
let inDoubleQuote = false;
|
|
2461
|
+
for (let i = 0; i < input.length; i++) {
|
|
2462
|
+
const char = input[i];
|
|
2463
|
+
// Handle escape sequences in double quotes
|
|
2464
|
+
if (char === '\\' && inDoubleQuote && i + 1 < input.length) {
|
|
2465
|
+
const nextChar = input[i + 1];
|
|
2466
|
+
// Keep the escape sequence for later processing
|
|
2467
|
+
current += char + nextChar;
|
|
2468
|
+
i++; // Skip the escaped character
|
|
2469
|
+
continue;
|
|
2470
|
+
}
|
|
2471
|
+
if (char === "'" && !inDoubleQuote) {
|
|
2472
|
+
inSingleQuote = !inSingleQuote;
|
|
2473
|
+
current += char;
|
|
2474
|
+
}
|
|
2475
|
+
else if (char === '"' && !inSingleQuote) {
|
|
2476
|
+
inDoubleQuote = !inDoubleQuote;
|
|
2477
|
+
current += char;
|
|
2478
|
+
}
|
|
2479
|
+
else if (/\s/.test(char) && !inSingleQuote && !inDoubleQuote) {
|
|
2480
|
+
if (current) {
|
|
2481
|
+
tokens.push(this.stripQuotes(current));
|
|
2482
|
+
current = '';
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
else {
|
|
2486
|
+
current += char;
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
if (current) {
|
|
2490
|
+
tokens.push(this.stripQuotes(current));
|
|
2491
|
+
}
|
|
2492
|
+
return tokens;
|
|
2493
|
+
}
|
|
2494
|
+
/**
|
|
2495
|
+
* Strip outer quotes from a string and unescape inner quotes
|
|
2496
|
+
*/
|
|
2497
|
+
stripQuotes(s) {
|
|
2498
|
+
if (s.startsWith('"') && s.endsWith('"')) {
|
|
2499
|
+
// Remove outer double quotes and unescape inner escaped quotes
|
|
2500
|
+
return s.slice(1, -1).replace(/\\"/g, '"');
|
|
2501
|
+
}
|
|
2502
|
+
if (s.startsWith("'") && s.endsWith("'")) {
|
|
2503
|
+
// Remove outer single quotes (no escaping in single quotes)
|
|
2504
|
+
return s.slice(1, -1);
|
|
2505
|
+
}
|
|
2506
|
+
return s;
|
|
2507
|
+
}
|
|
2508
|
+
/**
|
|
2509
|
+
* Check if a command can use a worker loader
|
|
2510
|
+
*/
|
|
2511
|
+
matchWorkerLoader(command) {
|
|
2512
|
+
const cmd = this.extractCommandName(command);
|
|
2513
|
+
// Check if we have a loader for this command
|
|
2514
|
+
for (const [name, loader] of Object.entries(this.workerLoaders)) {
|
|
2515
|
+
if (loader.modules.includes(cmd)) {
|
|
2516
|
+
return name;
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
// Check if it's a known loadable module
|
|
2520
|
+
if (TIER_3_LOADABLE_MODULES.has(cmd)) {
|
|
2521
|
+
return cmd;
|
|
2522
|
+
}
|
|
2523
|
+
return null;
|
|
2524
|
+
}
|
|
2525
|
+
/**
|
|
2526
|
+
* Create a BashResult with tier information
|
|
2527
|
+
*/
|
|
2528
|
+
createResult(command, stdout, stderr, exitCode, tier) {
|
|
2529
|
+
return {
|
|
2530
|
+
input: command,
|
|
2531
|
+
command,
|
|
2532
|
+
valid: true,
|
|
2533
|
+
generated: false,
|
|
2534
|
+
stdout,
|
|
2535
|
+
stderr,
|
|
2536
|
+
exitCode,
|
|
2537
|
+
intent: {
|
|
2538
|
+
commands: [this.extractCommandName(command)],
|
|
2539
|
+
reads: [],
|
|
2540
|
+
writes: [],
|
|
2541
|
+
deletes: [],
|
|
2542
|
+
network: false,
|
|
2543
|
+
elevated: false,
|
|
2544
|
+
},
|
|
2545
|
+
classification: {
|
|
2546
|
+
type: 'execute',
|
|
2547
|
+
impact: 'none',
|
|
2548
|
+
reversible: true,
|
|
2549
|
+
reason: `Executed via Tier ${tier}`,
|
|
2550
|
+
},
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
// ============================================================================
|
|
2554
|
+
// UTILITY METHODS
|
|
2555
|
+
// ============================================================================
|
|
2556
|
+
/**
|
|
2557
|
+
* Get tier capabilities summary
|
|
2558
|
+
*/
|
|
2559
|
+
getCapabilities() {
|
|
2560
|
+
return {
|
|
2561
|
+
tier1: {
|
|
2562
|
+
available: true, // Always available for pure compute
|
|
2563
|
+
commands: Array.from(TIER_1_NATIVE_COMMANDS),
|
|
2564
|
+
},
|
|
2565
|
+
tier2: {
|
|
2566
|
+
available: Object.keys(this.rpcBindings).length > 0,
|
|
2567
|
+
services: Object.keys(this.rpcBindings),
|
|
2568
|
+
},
|
|
2569
|
+
tier3: {
|
|
2570
|
+
available: Object.keys(this.workerLoaders).length > 0,
|
|
2571
|
+
loaders: Object.keys(this.workerLoaders),
|
|
2572
|
+
},
|
|
2573
|
+
tier4: {
|
|
2574
|
+
available: this.sandbox !== undefined,
|
|
2575
|
+
},
|
|
2576
|
+
};
|
|
2577
|
+
}
|
|
2578
|
+
/**
|
|
2579
|
+
* Check if a specific tier is available for a command
|
|
2580
|
+
*/
|
|
2581
|
+
isTierAvailable(tier, command) {
|
|
2582
|
+
switch (tier) {
|
|
2583
|
+
case 1:
|
|
2584
|
+
if (!command)
|
|
2585
|
+
return true;
|
|
2586
|
+
const cmd1 = this.extractCommandName(command);
|
|
2587
|
+
if (TIER_1_FS_COMMANDS.has(cmd1))
|
|
2588
|
+
return this.fs !== undefined;
|
|
2589
|
+
return TIER_1_NATIVE_COMMANDS.has(cmd1);
|
|
2590
|
+
case 2:
|
|
2591
|
+
if (!command)
|
|
2592
|
+
return Object.keys(this.rpcBindings).length > 0;
|
|
2593
|
+
const cmd2 = this.extractCommandName(command);
|
|
2594
|
+
return Object.values(this.rpcBindings).some(b => b.commands.includes(cmd2));
|
|
2595
|
+
case 3:
|
|
2596
|
+
if (!command)
|
|
2597
|
+
return Object.keys(this.workerLoaders).length > 0;
|
|
2598
|
+
return this.matchWorkerLoader(command) !== null;
|
|
2599
|
+
case 4:
|
|
2600
|
+
return this.sandbox !== undefined;
|
|
2601
|
+
default:
|
|
2602
|
+
return false;
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
// ============================================================================
|
|
2607
|
+
// FACTORY FUNCTIONS
|
|
2608
|
+
// ============================================================================
|
|
2609
|
+
/**
|
|
2610
|
+
* Create a TieredExecutor from environment bindings.
|
|
2611
|
+
*
|
|
2612
|
+
* @param env - Worker environment with bindings
|
|
2613
|
+
* @param options - Additional configuration options
|
|
2614
|
+
* @returns Configured TieredExecutor
|
|
2615
|
+
*
|
|
2616
|
+
* @example
|
|
2617
|
+
* ```typescript
|
|
2618
|
+
* // In your Worker
|
|
2619
|
+
* export default {
|
|
2620
|
+
* async fetch(request: Request, env: Env) {
|
|
2621
|
+
* const executor = createTieredExecutor(env, {
|
|
2622
|
+
* rpcBindings: {
|
|
2623
|
+
* jq: { endpoint: env.JQ_SERVICE, commands: ['jq'] },
|
|
2624
|
+
* },
|
|
2625
|
+
* sandbox: {
|
|
2626
|
+
* execute: async (cmd, opts) => containerExecutor.run(cmd, opts),
|
|
2627
|
+
* },
|
|
2628
|
+
* })
|
|
2629
|
+
*
|
|
2630
|
+
* // Commands are auto-routed to the best tier
|
|
2631
|
+
* const result = await executor.execute('echo hello')
|
|
2632
|
+
* return new Response(result.stdout)
|
|
2633
|
+
* }
|
|
2634
|
+
* }
|
|
2635
|
+
* ```
|
|
2636
|
+
*/
|
|
2637
|
+
export function createTieredExecutor(_env, options) {
|
|
2638
|
+
return new TieredExecutor({
|
|
2639
|
+
fs: options?.fs,
|
|
2640
|
+
rpcBindings: options?.rpcBindings,
|
|
2641
|
+
workerLoaders: options?.workerLoaders,
|
|
2642
|
+
sandbox: options?.sandbox,
|
|
2643
|
+
defaultTimeout: options?.defaultTimeout,
|
|
2644
|
+
preferFaster: options?.preferFaster,
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
//# sourceMappingURL=tiered-executor.js.map
|