dotdo 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/README.md +238 -0
- package/cli/agent.ts +72 -0
- package/cli/bin.js +44 -0
- package/cli/bin.ts +38 -0
- package/cli/build.ts +157 -0
- package/cli/commands/auth/login.ts +14 -0
- package/cli/commands/auth/logout.ts +6 -0
- package/cli/commands/auth/whoami.ts +16 -0
- package/cli/commands/deploy-multi.ts +245 -0
- package/cli/commands/dev/deploy.ts +100 -0
- package/cli/commands/dev/dev.ts +95 -0
- package/cli/commands/dev/logs.ts +91 -0
- package/cli/commands/dev-local.ts +88 -0
- package/cli/commands/do-ops.ts +314 -0
- package/cli/commands/index.ts +100 -0
- package/cli/commands/init.ts +247 -0
- package/cli/commands/introspect/emitter.ts +315 -0
- package/cli/commands/introspect/index.ts +193 -0
- package/cli/commands/link.ts +598 -0
- package/cli/commands/snippets.ts +415 -0
- package/cli/commands/tunnel.ts +239 -0
- package/cli/device-auth.ts +289 -0
- package/cli/fallback.ts +12 -0
- package/cli/index.ts +121 -0
- package/cli/main.ts +246 -0
- package/cli/mcp-stdio.ts +790 -0
- package/cli/package.json +62 -0
- package/cli/runtime/do-registry.ts +193 -0
- package/cli/runtime/embedded-db.ts +344 -0
- package/cli/runtime/index.ts +9 -0
- package/cli/runtime/miniflare-adapter.ts +162 -0
- package/cli/sandbox.ts +82 -0
- package/cli/src/args.ts +174 -0
- package/cli/src/auth.ts +55 -0
- package/cli/src/commands/call.ts +84 -0
- package/cli/src/commands/charge.ts +96 -0
- package/cli/src/commands/config.ts +115 -0
- package/cli/src/commands/email.ts +112 -0
- package/cli/src/commands/llm.ts +115 -0
- package/cli/src/commands/queue.ts +134 -0
- package/cli/src/commands/text.ts +86 -0
- package/cli/src/config.ts +185 -0
- package/cli/src/output.ts +246 -0
- package/cli/src/rpc.ts +192 -0
- package/cli/utils/config.ts +282 -0
- package/cli/utils/detect.ts +73 -0
- package/cli/utils/index.ts +15 -0
- package/cli/utils/logger.ts +232 -0
- package/dist/ai/template-literals.js +2 -2
- package/dist/ai/template-literals.js.map +1 -1
- package/dist/api/middleware/auth.js +3 -2
- package/dist/api/middleware/auth.js.map +1 -1
- package/dist/db/iceberg/inverted-index.js +1 -1
- package/dist/db/iceberg/inverted-index.js.map +1 -1
- package/dist/db/iceberg/puffin.js.map +1 -1
- package/dist/db/json-indexes.js.map +1 -1
- package/dist/db/objects.js.map +1 -1
- package/dist/db/primitives/dag-scheduler/index.js +1 -1
- package/dist/db/primitives/dag-scheduler/index.js.map +1 -1
- package/dist/db/primitives/observability.js.map +1 -1
- package/dist/db/primitives/schema-evolution.js.map +1 -1
- package/dist/db/primitives/temporal-store.js.map +1 -1
- package/dist/db/primitives/typed-column-store.js.map +1 -1
- package/dist/db/primitives/utils/duration.js.map +1 -1
- package/dist/db/primitives/utils/murmur3.js +12 -14
- package/dist/db/primitives/utils/murmur3.js.map +1 -1
- package/dist/db/primitives/window-manager.js.map +1 -1
- package/dist/db/stores.js.map +1 -1
- package/dist/db/things.js.map +1 -1
- package/dist/lib/DODispatcher.js +2 -2
- package/dist/lib/DODispatcher.js.map +1 -1
- package/dist/lib/auto-wiring.js.map +1 -1
- package/dist/lib/channels/email.js +1 -1
- package/dist/lib/channels/email.js.map +1 -1
- package/dist/lib/channels/slack-blockkit.js.map +1 -1
- package/dist/lib/cloudflare/ai.js +1 -1
- package/dist/lib/cloudflare/ai.js.map +1 -1
- package/dist/lib/cloudflare/kv.js +1 -1
- package/dist/lib/cloudflare/kv.js.map +1 -1
- package/dist/lib/cloudflare/r2.js +3 -3
- package/dist/lib/cloudflare/r2.js.map +1 -1
- package/dist/lib/cloudflare/vectorize.js.map +1 -1
- package/dist/lib/cloudflare/workflows.js.map +1 -1
- package/dist/lib/executors/AgenticFunctionExecutor.js.map +1 -1
- package/dist/lib/executors/CodeFunctionExecutor.js.map +1 -1
- package/dist/lib/executors/GenerativeFunctionExecutor.js.map +1 -1
- package/dist/lib/executors/HumanFunctionExecutor.js +1 -1
- package/dist/lib/executors/HumanFunctionExecutor.js.map +1 -1
- package/dist/lib/executors/ParallelStepExecutor.js.map +1 -1
- package/dist/lib/experiments.js.map +1 -1
- package/dist/lib/flags/store.js.map +1 -1
- package/dist/lib/functions/FunctionComposition.js.map +1 -1
- package/dist/lib/functions/FunctionMiddleware.js.map +1 -1
- package/dist/lib/functions/FunctionRegistry.js.map +1 -1
- package/dist/lib/humans/templates.js.map +1 -1
- package/dist/lib/identity.js +2 -2
- package/dist/lib/identity.js.map +1 -1
- package/dist/lib/logging/index.js.map +1 -1
- package/dist/lib/mixins/bash.js +1 -73
- package/dist/lib/mixins/bash.js.map +1 -1
- package/dist/lib/mixins/git.js +0 -5
- package/dist/lib/mixins/git.js.map +1 -1
- package/dist/lib/mixins/npm.js.map +1 -1
- package/dist/lib/noun-id.js.map +1 -1
- package/dist/lib/rate-limit/sliding-window.js.map +1 -1
- package/dist/lib/rpc/bindings.js.map +1 -1
- package/dist/lib/safe-stringify.js.map +1 -1
- package/dist/lib/sandbox/miniflare-sandbox.js.map +1 -1
- package/dist/lib/sqids.js.map +1 -1
- package/dist/lib/sql/adapters/node-sql-parser.js.map +1 -1
- package/dist/lib/sql/adapters/pgsql-parser.js +19 -18
- package/dist/lib/sql/adapters/pgsql-parser.js.map +1 -1
- package/dist/metrics/hunch.js.map +1 -1
- package/dist/objects/API.js +1 -1
- package/dist/objects/API.js.map +1 -1
- package/dist/objects/Agent.js.map +1 -1
- package/dist/objects/Browser.js.map +1 -1
- package/dist/objects/CLI.js.map +1 -1
- package/dist/objects/DOBase.js.map +1 -1
- package/dist/objects/DOCache.js +153 -0
- package/dist/objects/DOCache.js.map +1 -0
- package/dist/objects/DOFull.js.map +1 -1
- package/dist/objects/Entity.js.map +1 -1
- package/dist/objects/Human.js.map +1 -1
- package/dist/objects/IcebergMetadataDO.js.map +1 -1
- package/dist/objects/IntegrationsDO.js.map +1 -1
- package/dist/objects/ObservabilityBroadcaster.js.map +1 -1
- package/dist/objects/Package.js.map +1 -1
- package/dist/objects/Product.js +1 -1
- package/dist/objects/Product.js.map +1 -1
- package/dist/objects/SaaS.js.map +1 -1
- package/dist/objects/SandboxDO.js.map +1 -1
- package/dist/objects/Service.js.map +1 -1
- package/dist/objects/VectorShardDO.js +9 -7
- package/dist/objects/VectorShardDO.js.map +1 -1
- package/dist/objects/Workflow.js.map +1 -1
- package/dist/objects/WorkflowFactory.js.map +1 -1
- package/dist/objects/WorkflowRuntime.js.map +1 -1
- package/dist/objects/lifecycle/Branch.js.map +1 -1
- package/dist/objects/lifecycle/Clone.js +1 -1
- package/dist/objects/lifecycle/Clone.js.map +1 -1
- package/dist/objects/lifecycle/Compact.js.map +1 -1
- package/dist/objects/lifecycle/Shard.js.map +1 -1
- package/dist/objects/persistence/checkpoint-manager.js.map +1 -1
- package/dist/objects/persistence/migration-runner.js.map +1 -1
- package/dist/objects/persistence/replication-manager.js +2 -2
- package/dist/objects/persistence/replication-manager.js.map +1 -1
- package/dist/objects/persistence/tiered-storage-manager.js.map +1 -1
- package/dist/objects/persistence/wal-manager.js.map +1 -1
- package/dist/objects/transport/auth-layer.js.map +1 -1
- package/dist/objects/transport/chain.js.map +1 -1
- package/dist/objects/transport/mcp-server.js +7 -6
- package/dist/objects/transport/mcp-server.js.map +1 -1
- package/dist/objects/transport/rest-autowire.js +3 -2
- package/dist/objects/transport/rest-autowire.js.map +1 -1
- package/dist/objects/transport/rest-router.js.map +1 -1
- package/dist/objects/transport/rpc-server.js +18 -15
- package/dist/objects/transport/rpc-server.js.map +1 -1
- package/dist/objects/transport/shared.js +2 -1
- package/dist/objects/transport/shared.js.map +1 -1
- package/dist/snippets/artifacts-ingest.js.map +1 -1
- package/dist/snippets/artifacts-serve.js.map +1 -1
- package/dist/snippets/search.js.map +1 -1
- package/dist/workflows/ScheduleManager.js.map +1 -1
- package/dist/workflows/StepResultStorage.js.map +1 -1
- package/dist/workflows/WaitForEventManager.js.map +1 -1
- package/dist/workflows/compat/backends/cloudflare-workflows.js.map +1 -1
- package/dist/workflows/compat/inngest/index.js.map +1 -1
- package/dist/workflows/compat/qstash/index.js.map +1 -1
- package/dist/workflows/compat/temporal/client.js.map +1 -1
- package/dist/workflows/compat/temporal/index.js.map +1 -1
- package/dist/workflows/compat/trigger/index.js.map +1 -1
- package/dist/workflows/compat/utils/index.js.map +1 -1
- package/dist/workflows/context/correlation.js +2 -2
- package/dist/workflows/context/correlation.js.map +1 -1
- package/dist/workflows/context/experiment.js +1 -1
- package/dist/workflows/context/experiment.js.map +1 -1
- package/dist/workflows/context/flag.js +1 -1
- package/dist/workflows/context/flag.js.map +1 -1
- package/dist/workflows/context/measure.js +1 -1
- package/dist/workflows/context/measure.js.map +1 -1
- package/dist/workflows/context/rate-limit.js.map +1 -1
- package/dist/workflows/data/entity-events/entity-events.js.map +1 -1
- package/dist/workflows/data/experiment/index.js.map +1 -1
- package/dist/workflows/data/goal/context.js +1 -1
- package/dist/workflows/data/goal/context.js.map +1 -1
- package/dist/workflows/data/measure/index.js +1 -1
- package/dist/workflows/data/measure/index.js.map +1 -1
- package/dist/workflows/data/stream/index.js +10 -76
- package/dist/workflows/data/stream/index.js.map +1 -1
- package/dist/workflows/data/track/context.js.map +1 -1
- package/dist/workflows/data/view/context.js.map +1 -1
- package/dist/workflows/domain.js.map +1 -1
- package/dist/workflows/flags.js +1 -1
- package/dist/workflows/flags.js.map +1 -1
- package/dist/workflows/hash.js.map +1 -1
- package/dist/workflows/on.js +1 -1
- package/dist/workflows/on.js.map +1 -1
- package/dist/workflows/schedule-builder.js.map +1 -1
- package/dist/workflows/visibility/index.js +0 -2
- package/dist/workflows/visibility/index.js.map +1 -1
- package/dist/workflows/visibility/query-parser.js.map +1 -1
- package/package.json +18 -3
- package/dist/api/analytics/router.js +0 -601
- package/dist/api/analytics/router.js.map +0 -1
- package/dist/api/index.js +0 -158
- package/dist/api/index.js.map +0 -1
- package/dist/api/middleware/error-handling.js +0 -176
- package/dist/api/middleware/error-handling.js.map +0 -1
- package/dist/api/middleware/request-id.js +0 -21
- package/dist/api/middleware/request-id.js.map +0 -1
- package/dist/api/pages.js +0 -1180
- package/dist/api/pages.js.map +0 -1
- package/dist/api/routes/api.js +0 -612
- package/dist/api/routes/api.js.map +0 -1
- package/dist/api/routes/browsers.js +0 -471
- package/dist/api/routes/browsers.js.map +0 -1
- package/dist/api/routes/do.js +0 -188
- package/dist/api/routes/do.js.map +0 -1
- package/dist/api/routes/mcp.js +0 -459
- package/dist/api/routes/mcp.js.map +0 -1
- package/dist/api/routes/obs.js +0 -445
- package/dist/api/routes/obs.js.map +0 -1
- package/dist/api/routes/openapi.js +0 -794
- package/dist/api/routes/openapi.js.map +0 -1
- package/dist/api/routes/rpc.js +0 -1103
- package/dist/api/routes/rpc.js.map +0 -1
- package/dist/api/routes/sandboxes.js +0 -389
- package/dist/api/routes/sandboxes.js.map +0 -1
- package/dist/api/test-do.js +0 -38
- package/dist/api/test-do.js.map +0 -1
- package/dist/api/types.js +0 -11
- package/dist/api/types.js.map +0 -1
- package/dist/cli/bin.js +0 -2
- package/dist/cli/main.js +0 -52342
- package/dist/do/bash.js +0 -35
- package/dist/do/bash.js.map +0 -1
- package/dist/do/fs.js +0 -25
- package/dist/do/fs.js.map +0 -1
- package/dist/do/full.js +0 -61
- package/dist/do/full.js.map +0 -1
- package/dist/do/git.js +0 -28
- package/dist/do/git.js.map +0 -1
- package/dist/do/index.js +0 -52
- package/dist/do/index.js.map +0 -1
- package/dist/lib/agent/tools/bash.js +0 -336
- package/dist/lib/agent/tools/bash.js.map +0 -1
- package/dist/lib/agent/tools/edit.js +0 -157
- package/dist/lib/agent/tools/edit.js.map +0 -1
- package/dist/lib/agent/tools/glob.js +0 -137
- package/dist/lib/agent/tools/glob.js.map +0 -1
- package/dist/lib/agent/tools/grep.js +0 -315
- package/dist/lib/agent/tools/grep.js.map +0 -1
- package/dist/lib/agent/tools/index.js +0 -71
- package/dist/lib/agent/tools/index.js.map +0 -1
- package/dist/lib/agent/tools/read.js +0 -212
- package/dist/lib/agent/tools/read.js.map +0 -1
- package/dist/lib/agent/tools/types.js +0 -197
- package/dist/lib/agent/tools/types.js.map +0 -1
- package/dist/lib/agent/tools/write.js +0 -159
- package/dist/lib/agent/tools/write.js.map +0 -1
- package/dist/lib/mixins/index.js +0 -29
- package/dist/lib/mixins/index.js.map +0 -1
- package/dist/primitives/bashx/src/ast/analyze.js +0 -1472
- package/dist/primitives/bashx/src/ast/analyze.js.map +0 -1
- package/dist/primitives/bashx/src/ast/parser.js +0 -1488
- package/dist/primitives/bashx/src/ast/parser.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/crypto.js +0 -1954
- package/dist/primitives/bashx/src/do/commands/crypto.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/data-processing.js +0 -1812
- package/dist/primitives/bashx/src/do/commands/data-processing.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/extended-utils.js +0 -804
- package/dist/primitives/bashx/src/do/commands/extended-utils.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/math-control.js +0 -1122
- package/dist/primitives/bashx/src/do/commands/math-control.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/posix-utils.js +0 -1015
- package/dist/primitives/bashx/src/do/commands/posix-utils.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/system-utils.js +0 -687
- package/dist/primitives/bashx/src/do/commands/system-utils.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/test-command.js +0 -523
- package/dist/primitives/bashx/src/do/commands/test-command.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/text-processing.js +0 -1550
- package/dist/primitives/bashx/src/do/commands/text-processing.js.map +0 -1
- package/dist/primitives/bashx/src/do/container-executor.js +0 -429
- package/dist/primitives/bashx/src/do/container-executor.js.map +0 -1
- package/dist/primitives/bashx/src/do/index.js +0 -668
- package/dist/primitives/bashx/src/do/index.js.map +0 -1
- package/dist/primitives/bashx/src/do/tiered-executor.js +0 -2647
- package/dist/primitives/bashx/src/do/tiered-executor.js.map +0 -1
- package/dist/primitives/bashx/src/do/worker.js +0 -352
- package/dist/primitives/bashx/src/do/worker.js.map +0 -1
- package/dist/primitives/bashx/src/types.js +0 -10
- package/dist/primitives/bashx/src/types.js.map +0 -1
- package/dist/primitives/fsx/core/backend.js +0 -480
- package/dist/primitives/fsx/core/backend.js.map +0 -1
- package/dist/primitives/fsx/core/constants.js +0 -140
- package/dist/primitives/fsx/core/constants.js.map +0 -1
- package/dist/primitives/fsx/core/fsx.js +0 -1184
- package/dist/primitives/fsx/core/fsx.js.map +0 -1
- package/dist/primitives/fsx/core/glob/glob.js +0 -438
- package/dist/primitives/fsx/core/glob/glob.js.map +0 -1
- package/dist/primitives/fsx/core/glob/index.js +0 -8
- package/dist/primitives/fsx/core/glob/index.js.map +0 -1
- package/dist/primitives/fsx/core/glob/match.js +0 -392
- package/dist/primitives/fsx/core/glob/match.js.map +0 -1
- package/dist/primitives/fsx/core/types.js +0 -307
- package/dist/primitives/fsx/core/types.js.map +0 -1
- package/dist/sdk/capnweb-compat.js +0 -42
- package/dist/sdk/capnweb-compat.js.map +0 -1
- package/dist/sdk/client.js +0 -20
- package/dist/sdk/client.js.map +0 -1
- package/dist/sdk/index.js +0 -17
- package/dist/sdk/index.js.map +0 -1
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Snippets CLI
|
|
3
|
+
*
|
|
4
|
+
* Deploy, list, and manage Cloudflare Snippets for a zone.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* do snippets list # List all snippets
|
|
8
|
+
* do snippets deploy <name> <file> # Deploy a snippet
|
|
9
|
+
* do snippets delete <name> # Delete a snippet
|
|
10
|
+
* do snippets rules <name> # Show rules for a snippet
|
|
11
|
+
* do snippets enable <name> <pattern> # Enable snippet on URL pattern
|
|
12
|
+
*
|
|
13
|
+
* Environment:
|
|
14
|
+
* CF_API_TOKEN - Cloudflare API token (required)
|
|
15
|
+
* CF_ZONE_ID - Default zone ID (or use --zone)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const name = 'snippets'
|
|
19
|
+
export const description = 'Manage Cloudflare Snippets'
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Types
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
interface SnippetMetadata {
|
|
26
|
+
main_module: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface Snippet {
|
|
30
|
+
snippet_name: string
|
|
31
|
+
created_on: string
|
|
32
|
+
modified_on: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface SnippetRule {
|
|
36
|
+
snippet_name: string
|
|
37
|
+
enabled: boolean
|
|
38
|
+
expression: string
|
|
39
|
+
description?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface CloudflareResponse<T> {
|
|
43
|
+
success: boolean
|
|
44
|
+
errors: Array<{ code: number; message: string }>
|
|
45
|
+
messages: Array<{ code: number; message: string }>
|
|
46
|
+
result: T
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface RunOptions {
|
|
50
|
+
fetch?: typeof fetch
|
|
51
|
+
env?: Record<string, string | undefined>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// API Client
|
|
56
|
+
// ============================================================================
|
|
57
|
+
|
|
58
|
+
class SnippetsAPI {
|
|
59
|
+
private baseUrl: string
|
|
60
|
+
private token: string
|
|
61
|
+
private fetchFn: typeof fetch
|
|
62
|
+
|
|
63
|
+
constructor(zoneId: string, token: string, fetchFn: typeof fetch = fetch) {
|
|
64
|
+
this.baseUrl = `https://api.cloudflare.com/client/v4/zones/${zoneId}/snippets`
|
|
65
|
+
this.token = token
|
|
66
|
+
this.fetchFn = fetchFn
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private async request<T>(
|
|
70
|
+
path: string,
|
|
71
|
+
options: RequestInit = {}
|
|
72
|
+
): Promise<CloudflareResponse<T>> {
|
|
73
|
+
const url = path ? `${this.baseUrl}/${path}` : this.baseUrl
|
|
74
|
+
const response = await this.fetchFn(url, {
|
|
75
|
+
...options,
|
|
76
|
+
headers: {
|
|
77
|
+
Authorization: `Bearer ${this.token}`,
|
|
78
|
+
...options.headers,
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const data = await response.json()
|
|
83
|
+
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
const errors = (data as CloudflareResponse<T>).errors
|
|
86
|
+
?.map((e) => e.message)
|
|
87
|
+
.join(', ')
|
|
88
|
+
throw new Error(`API error (${response.status}): ${errors || response.statusText}`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return data as CloudflareResponse<T>
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async list(): Promise<Snippet[]> {
|
|
95
|
+
const response = await this.request<Snippet[]>('')
|
|
96
|
+
return response.result
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async get(name: string): Promise<Snippet> {
|
|
100
|
+
const response = await this.request<Snippet>(name)
|
|
101
|
+
return response.result
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async deploy(name: string, code: string, mainModule = 'snippet.js'): Promise<Snippet> {
|
|
105
|
+
const metadata: SnippetMetadata = { main_module: mainModule }
|
|
106
|
+
|
|
107
|
+
const form = new FormData()
|
|
108
|
+
form.append('metadata', JSON.stringify(metadata))
|
|
109
|
+
form.append(mainModule, new Blob([code], { type: 'application/javascript' }), mainModule)
|
|
110
|
+
|
|
111
|
+
const response = await this.request<Snippet>(name, {
|
|
112
|
+
method: 'PUT',
|
|
113
|
+
body: form,
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
return response.result
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async delete(name: string): Promise<void> {
|
|
120
|
+
await this.request(name, { method: 'DELETE' })
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async getRules(name: string): Promise<SnippetRule[]> {
|
|
124
|
+
const response = await this.request<SnippetRule[]>(`${name}/rules`)
|
|
125
|
+
return response.result
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async setRules(name: string, rules: Omit<SnippetRule, 'snippet_name'>[]): Promise<SnippetRule[]> {
|
|
129
|
+
const response = await this.request<SnippetRule[]>(`${name}/rules`, {
|
|
130
|
+
method: 'PUT',
|
|
131
|
+
headers: { 'Content-Type': 'application/json' },
|
|
132
|
+
body: JSON.stringify(rules.map((r) => ({ ...r, snippet_name: name }))),
|
|
133
|
+
})
|
|
134
|
+
return response.result
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Zone-level rules endpoint
|
|
138
|
+
async getAllRules(): Promise<SnippetRule[]> {
|
|
139
|
+
const url = this.baseUrl.replace('/snippets', '/snippet_rules')
|
|
140
|
+
const response = await this.fetchFn(url, {
|
|
141
|
+
headers: { Authorization: `Bearer ${this.token}` },
|
|
142
|
+
})
|
|
143
|
+
const data = (await response.json()) as CloudflareResponse<SnippetRule[]>
|
|
144
|
+
return data.result
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ============================================================================
|
|
149
|
+
// Commands
|
|
150
|
+
// ============================================================================
|
|
151
|
+
|
|
152
|
+
async function listSnippets(api: SnippetsAPI): Promise<void> {
|
|
153
|
+
const snippets = await api.list()
|
|
154
|
+
|
|
155
|
+
if (snippets.length === 0) {
|
|
156
|
+
console.log('No snippets found')
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log('Snippets:')
|
|
161
|
+
for (const snippet of snippets) {
|
|
162
|
+
const modified = new Date(snippet.modified_on).toLocaleDateString()
|
|
163
|
+
console.log(` ${snippet.snippet_name} (modified: ${modified})`)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function deploySnippet(
|
|
168
|
+
api: SnippetsAPI,
|
|
169
|
+
name: string,
|
|
170
|
+
filePath: string,
|
|
171
|
+
readFile: (path: string) => Promise<string>
|
|
172
|
+
): Promise<void> {
|
|
173
|
+
console.log(`Deploying snippet "${name}" from ${filePath}...`)
|
|
174
|
+
|
|
175
|
+
const code = await readFile(filePath)
|
|
176
|
+
const result = await api.deploy(name, code)
|
|
177
|
+
|
|
178
|
+
console.log(`Deployed successfully`)
|
|
179
|
+
console.log(` Name: ${result.snippet_name}`)
|
|
180
|
+
console.log(` Modified: ${result.modified_on}`)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function deleteSnippet(api: SnippetsAPI, name: string): Promise<void> {
|
|
184
|
+
console.log(`Deleting snippet "${name}"...`)
|
|
185
|
+
await api.delete(name)
|
|
186
|
+
console.log('Deleted successfully')
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function showRules(api: SnippetsAPI, name: string): Promise<void> {
|
|
190
|
+
const rules = await api.getRules(name)
|
|
191
|
+
|
|
192
|
+
if (rules.length === 0) {
|
|
193
|
+
console.log(`No rules configured for "${name}"`)
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log(`Rules for "${name}":`)
|
|
198
|
+
for (const rule of rules) {
|
|
199
|
+
const status = rule.enabled ? '✓' : '✗'
|
|
200
|
+
console.log(` ${status} ${rule.expression}`)
|
|
201
|
+
if (rule.description) {
|
|
202
|
+
console.log(` ${rule.description}`)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function enableSnippet(
|
|
208
|
+
api: SnippetsAPI,
|
|
209
|
+
name: string,
|
|
210
|
+
pattern: string,
|
|
211
|
+
description?: string
|
|
212
|
+
): Promise<void> {
|
|
213
|
+
// Convert pattern to Cloudflare expression
|
|
214
|
+
// Supports:
|
|
215
|
+
// http.request.* -> raw Cloudflare expression (pass through)
|
|
216
|
+
// /path?param= -> path + query string match
|
|
217
|
+
// ?param= -> query string only
|
|
218
|
+
// /path/* -> path glob pattern
|
|
219
|
+
// $.search?q= -> exact path with query param
|
|
220
|
+
let expression: string
|
|
221
|
+
|
|
222
|
+
if (pattern.startsWith('http.')) {
|
|
223
|
+
// Already a Cloudflare expression
|
|
224
|
+
expression = pattern
|
|
225
|
+
} else if (pattern.includes('?')) {
|
|
226
|
+
// Query string pattern: /path?param= or ?param=
|
|
227
|
+
const [pathPart, queryPart] = pattern.split('?')
|
|
228
|
+
const conditions: string[] = []
|
|
229
|
+
|
|
230
|
+
if (pathPart) {
|
|
231
|
+
// Escape special chars for exact or glob match
|
|
232
|
+
if (pathPart.includes('*')) {
|
|
233
|
+
const regex = pathPart.replace(/\./g, '\\.').replace(/\$/g, '\\$').replace(/\*/g, '.*')
|
|
234
|
+
conditions.push(`http.request.uri.path matches "^${regex}$"`)
|
|
235
|
+
} else {
|
|
236
|
+
conditions.push(`http.request.uri.path eq "${pathPart}"`)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (queryPart) {
|
|
241
|
+
// Query param check: "q=" -> query contains "q="
|
|
242
|
+
conditions.push(`http.request.uri.query contains "${queryPart}"`)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
expression = conditions.join(' and ')
|
|
246
|
+
} else {
|
|
247
|
+
// Path-only glob pattern
|
|
248
|
+
const regex = pattern
|
|
249
|
+
.replace(/\./g, '\\.')
|
|
250
|
+
.replace(/\$/g, '\\$')
|
|
251
|
+
.replace(/\*/g, '.*')
|
|
252
|
+
|
|
253
|
+
expression = `http.request.uri.path matches "^${regex}$"`
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
console.log(`Enabling snippet "${name}" for pattern: ${pattern}`)
|
|
257
|
+
console.log(` Expression: ${expression}`)
|
|
258
|
+
|
|
259
|
+
const rules = await api.setRules(name, [
|
|
260
|
+
{
|
|
261
|
+
enabled: true,
|
|
262
|
+
expression,
|
|
263
|
+
description: description || `Enable ${name} for ${pattern}`,
|
|
264
|
+
},
|
|
265
|
+
])
|
|
266
|
+
|
|
267
|
+
console.log('Rule configured successfully')
|
|
268
|
+
console.log(` Enabled: ${rules[0].enabled}`)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ============================================================================
|
|
272
|
+
// Main Entry Point
|
|
273
|
+
// ============================================================================
|
|
274
|
+
|
|
275
|
+
export async function run(args: string[], options: RunOptions = {}): Promise<{ success: boolean }> {
|
|
276
|
+
const env = options.env ?? process.env
|
|
277
|
+
const fetchFn = options.fetch ?? fetch
|
|
278
|
+
|
|
279
|
+
// Parse arguments
|
|
280
|
+
const [subcommand, ...rest] = args
|
|
281
|
+
|
|
282
|
+
// Parse flags
|
|
283
|
+
let zoneId = env.CF_ZONE_ID
|
|
284
|
+
const flagArgs: string[] = []
|
|
285
|
+
|
|
286
|
+
for (let i = 0; i < rest.length; i++) {
|
|
287
|
+
if (rest[i] === '--zone' && rest[i + 1]) {
|
|
288
|
+
zoneId = rest[i + 1]
|
|
289
|
+
i++ // Skip next arg
|
|
290
|
+
} else if (!rest[i].startsWith('--')) {
|
|
291
|
+
flagArgs.push(rest[i])
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Validate environment
|
|
296
|
+
const token = env.CF_API_TOKEN
|
|
297
|
+
if (!token) {
|
|
298
|
+
console.error('Error: CF_API_TOKEN environment variable is required')
|
|
299
|
+
console.error('Get a token from: https://dash.cloudflare.com/profile/api-tokens')
|
|
300
|
+
return { success: false }
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!zoneId) {
|
|
304
|
+
console.error('Error: CF_ZONE_ID environment variable or --zone flag is required')
|
|
305
|
+
console.error('Find your zone ID in the Cloudflare dashboard Overview page')
|
|
306
|
+
return { success: false }
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const api = new SnippetsAPI(zoneId, token, fetchFn)
|
|
310
|
+
|
|
311
|
+
// File reader (can be overridden in tests)
|
|
312
|
+
const readFile = async (path: string): Promise<string> => {
|
|
313
|
+
const fs = await import('fs/promises')
|
|
314
|
+
return fs.readFile(path, 'utf-8')
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
switch (subcommand) {
|
|
319
|
+
case 'list':
|
|
320
|
+
case 'ls':
|
|
321
|
+
await listSnippets(api)
|
|
322
|
+
break
|
|
323
|
+
|
|
324
|
+
case 'deploy':
|
|
325
|
+
case 'push': {
|
|
326
|
+
const [name, filePath] = flagArgs
|
|
327
|
+
if (!name || !filePath) {
|
|
328
|
+
console.error('Usage: do snippets deploy <name> <file>')
|
|
329
|
+
return { success: false }
|
|
330
|
+
}
|
|
331
|
+
await deploySnippet(api, name, filePath, readFile)
|
|
332
|
+
break
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
case 'delete':
|
|
336
|
+
case 'rm': {
|
|
337
|
+
const [name] = flagArgs
|
|
338
|
+
if (!name) {
|
|
339
|
+
console.error('Usage: do snippets delete <name>')
|
|
340
|
+
return { success: false }
|
|
341
|
+
}
|
|
342
|
+
await deleteSnippet(api, name)
|
|
343
|
+
break
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
case 'rules': {
|
|
347
|
+
const [name] = flagArgs
|
|
348
|
+
if (!name) {
|
|
349
|
+
console.error('Usage: do snippets rules <name>')
|
|
350
|
+
return { success: false }
|
|
351
|
+
}
|
|
352
|
+
await showRules(api, name)
|
|
353
|
+
break
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
case 'enable': {
|
|
357
|
+
const [name, pattern, description] = flagArgs
|
|
358
|
+
if (!name || !pattern) {
|
|
359
|
+
console.error('Usage: do snippets enable <name> <pattern> [description]')
|
|
360
|
+
console.error('Examples:')
|
|
361
|
+
console.error(' do snippets enable proxy "/*"')
|
|
362
|
+
console.error(' do snippets enable api "/api/*"')
|
|
363
|
+
return { success: false }
|
|
364
|
+
}
|
|
365
|
+
await enableSnippet(api, name, pattern, description)
|
|
366
|
+
break
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
case undefined:
|
|
370
|
+
case 'help':
|
|
371
|
+
console.log(`
|
|
372
|
+
Cloudflare Snippets CLI
|
|
373
|
+
|
|
374
|
+
Usage:
|
|
375
|
+
do snippets list List all snippets
|
|
376
|
+
do snippets deploy <name> <file> Deploy a snippet
|
|
377
|
+
do snippets delete <name> Delete a snippet
|
|
378
|
+
do snippets rules <name> Show rules for a snippet
|
|
379
|
+
do snippets enable <name> <pattern> Enable snippet on URL pattern
|
|
380
|
+
|
|
381
|
+
Pattern Syntax:
|
|
382
|
+
/* All paths (glob)
|
|
383
|
+
/api/* Path prefix (glob)
|
|
384
|
+
/$.search?q= Exact path + query param
|
|
385
|
+
?q= Any path with query param
|
|
386
|
+
http.host eq X Raw Cloudflare expression
|
|
387
|
+
|
|
388
|
+
Options:
|
|
389
|
+
--zone <id> Override CF_ZONE_ID
|
|
390
|
+
|
|
391
|
+
Environment:
|
|
392
|
+
CF_API_TOKEN Cloudflare API token (required)
|
|
393
|
+
CF_ZONE_ID Default zone ID
|
|
394
|
+
|
|
395
|
+
Examples:
|
|
396
|
+
do snippets deploy query ./snippets/query.js
|
|
397
|
+
do snippets enable query "/$.search?q="
|
|
398
|
+
do snippets enable query "?q=" "Search queries only"
|
|
399
|
+
do snippets enable proxy "/api/*"
|
|
400
|
+
`)
|
|
401
|
+
break
|
|
402
|
+
|
|
403
|
+
default:
|
|
404
|
+
console.error(`Unknown subcommand: ${subcommand}`)
|
|
405
|
+
console.error('Run "do snippets help" for usage')
|
|
406
|
+
return { success: false }
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return { success: true }
|
|
410
|
+
} catch (error) {
|
|
411
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
412
|
+
console.error('Error:', message)
|
|
413
|
+
return { success: false }
|
|
414
|
+
}
|
|
415
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tunnel Command
|
|
3
|
+
*
|
|
4
|
+
* Exposes local development server via Cloudflare Tunnel.
|
|
5
|
+
* Supports both quick tunnels (temporary) and named tunnels (persistent).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Command } from 'commander'
|
|
9
|
+
import { createLogger, type Logger } from '../utils/logger'
|
|
10
|
+
import { spawn, type ChildProcess } from 'child_process'
|
|
11
|
+
import * as fs from 'fs'
|
|
12
|
+
import * as path from 'path'
|
|
13
|
+
import * as os from 'os'
|
|
14
|
+
|
|
15
|
+
const logger = createLogger('tunnel')
|
|
16
|
+
|
|
17
|
+
export interface TunnelOptions {
|
|
18
|
+
port: number
|
|
19
|
+
name?: string
|
|
20
|
+
configPath?: string
|
|
21
|
+
logger?: Logger
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TunnelResult {
|
|
25
|
+
url: string
|
|
26
|
+
stop: () => void
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Find cloudflared binary
|
|
31
|
+
*/
|
|
32
|
+
async function findCloudflared(): Promise<string | null> {
|
|
33
|
+
// Check common locations
|
|
34
|
+
const locations = [
|
|
35
|
+
// Global install
|
|
36
|
+
'/usr/local/bin/cloudflared',
|
|
37
|
+
'/opt/homebrew/bin/cloudflared',
|
|
38
|
+
// Windows
|
|
39
|
+
'C:\\Program Files\\cloudflared\\cloudflared.exe',
|
|
40
|
+
// Local .dotdo directory
|
|
41
|
+
path.join(process.cwd(), '.dotdo', 'bin', 'cloudflared'),
|
|
42
|
+
path.join(os.homedir(), '.dotdo', 'bin', 'cloudflared'),
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
for (const loc of locations) {
|
|
46
|
+
if (fs.existsSync(loc)) {
|
|
47
|
+
return loc
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Try to find in PATH
|
|
52
|
+
try {
|
|
53
|
+
const { execSync } = await import('child_process')
|
|
54
|
+
const result = execSync('which cloudflared', { encoding: 'utf-8' }).trim()
|
|
55
|
+
if (result) return result
|
|
56
|
+
} catch {
|
|
57
|
+
// Not in PATH
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Download cloudflared if not installed
|
|
65
|
+
*/
|
|
66
|
+
async function downloadCloudflared(): Promise<string> {
|
|
67
|
+
const platform = process.platform
|
|
68
|
+
const arch = process.arch
|
|
69
|
+
|
|
70
|
+
let downloadUrl: string
|
|
71
|
+
let binaryName = 'cloudflared'
|
|
72
|
+
|
|
73
|
+
if (platform === 'darwin') {
|
|
74
|
+
downloadUrl = arch === 'arm64'
|
|
75
|
+
? 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-arm64.tgz'
|
|
76
|
+
: 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-amd64.tgz'
|
|
77
|
+
} else if (platform === 'linux') {
|
|
78
|
+
downloadUrl = arch === 'arm64'
|
|
79
|
+
? 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64'
|
|
80
|
+
: 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64'
|
|
81
|
+
} else if (platform === 'win32') {
|
|
82
|
+
downloadUrl = 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe'
|
|
83
|
+
binaryName = 'cloudflared.exe'
|
|
84
|
+
} else {
|
|
85
|
+
throw new Error(`Unsupported platform: ${platform}`)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const binDir = path.join(os.homedir(), '.dotdo', 'bin')
|
|
89
|
+
const binaryPath = path.join(binDir, binaryName)
|
|
90
|
+
|
|
91
|
+
if (!fs.existsSync(binDir)) {
|
|
92
|
+
fs.mkdirSync(binDir, { recursive: true })
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
logger.info('Downloading cloudflared...')
|
|
96
|
+
|
|
97
|
+
// Download using fetch
|
|
98
|
+
const response = await fetch(downloadUrl)
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
throw new Error(`Failed to download cloudflared: ${response.status}`)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const buffer = await response.arrayBuffer()
|
|
104
|
+
|
|
105
|
+
// Handle tarball on macOS
|
|
106
|
+
if (downloadUrl.endsWith('.tgz')) {
|
|
107
|
+
// For simplicity, we'll use tar command
|
|
108
|
+
const tempPath = path.join(binDir, 'cloudflared.tgz')
|
|
109
|
+
fs.writeFileSync(tempPath, Buffer.from(buffer))
|
|
110
|
+
|
|
111
|
+
const { execSync } = await import('child_process')
|
|
112
|
+
execSync(`tar -xzf "${tempPath}" -C "${binDir}"`, { stdio: 'ignore' })
|
|
113
|
+
fs.unlinkSync(tempPath)
|
|
114
|
+
} else {
|
|
115
|
+
fs.writeFileSync(binaryPath, Buffer.from(buffer))
|
|
116
|
+
fs.chmodSync(binaryPath, 0o755)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
logger.success('cloudflared installed')
|
|
120
|
+
return binaryPath
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Start a Cloudflare Tunnel
|
|
125
|
+
*/
|
|
126
|
+
export async function startTunnel(options: TunnelOptions): Promise<string> {
|
|
127
|
+
const log = options.logger ?? logger
|
|
128
|
+
|
|
129
|
+
// Find or download cloudflared
|
|
130
|
+
let cloudflared = await findCloudflared()
|
|
131
|
+
if (!cloudflared) {
|
|
132
|
+
cloudflared = await downloadCloudflared()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
const args: string[] = ['tunnel']
|
|
137
|
+
|
|
138
|
+
if (options.name) {
|
|
139
|
+
// Named tunnel (requires auth)
|
|
140
|
+
args.push('--name', options.name)
|
|
141
|
+
args.push('run')
|
|
142
|
+
} else {
|
|
143
|
+
// Quick tunnel (no auth required)
|
|
144
|
+
args.push('--url', `http://localhost:${options.port}`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (options.configPath) {
|
|
148
|
+
args.push('--config', options.configPath)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
log.debug('Starting tunnel:', { cloudflared, args })
|
|
152
|
+
|
|
153
|
+
const proc = spawn(cloudflared, args, {
|
|
154
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
let tunnelUrl: string | null = null
|
|
158
|
+
|
|
159
|
+
proc.stdout?.on('data', (data: Buffer) => {
|
|
160
|
+
const output = data.toString()
|
|
161
|
+
log.debug('cloudflared stdout:', output)
|
|
162
|
+
|
|
163
|
+
// Parse tunnel URL from output
|
|
164
|
+
const urlMatch = output.match(/https:\/\/[^\s]+\.trycloudflare\.com/)
|
|
165
|
+
if (urlMatch && !tunnelUrl) {
|
|
166
|
+
tunnelUrl = urlMatch[0]
|
|
167
|
+
resolve(tunnelUrl)
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
proc.stderr?.on('data', (data: Buffer) => {
|
|
172
|
+
const output = data.toString()
|
|
173
|
+
log.debug('cloudflared stderr:', output)
|
|
174
|
+
|
|
175
|
+
// Also check stderr for URL (cloudflared logs there)
|
|
176
|
+
const urlMatch = output.match(/https:\/\/[^\s]+\.trycloudflare\.com/)
|
|
177
|
+
if (urlMatch && !tunnelUrl) {
|
|
178
|
+
tunnelUrl = urlMatch[0]
|
|
179
|
+
resolve(tunnelUrl)
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
proc.on('error', (error) => {
|
|
184
|
+
log.error('Tunnel error:', { error: error.message })
|
|
185
|
+
reject(error)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
proc.on('exit', (code) => {
|
|
189
|
+
if (!tunnelUrl) {
|
|
190
|
+
reject(new Error(`cloudflared exited with code ${code}`))
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// Timeout after 30 seconds
|
|
195
|
+
setTimeout(() => {
|
|
196
|
+
if (!tunnelUrl) {
|
|
197
|
+
proc.kill()
|
|
198
|
+
reject(new Error('Timeout waiting for tunnel URL'))
|
|
199
|
+
}
|
|
200
|
+
}, 30000)
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export const tunnelCommand = new Command('tunnel')
|
|
205
|
+
.description('Expose local server via Cloudflare Tunnel')
|
|
206
|
+
.option('-p, --port <port>', 'Local port to expose', '8787')
|
|
207
|
+
.option('-n, --name <name>', 'Tunnel name (requires auth)')
|
|
208
|
+
.option('-c, --config <path>', 'Path to tunnel config file')
|
|
209
|
+
.action(async (options) => {
|
|
210
|
+
const port = parseInt(options.port, 10)
|
|
211
|
+
|
|
212
|
+
logger.info(`Starting tunnel for localhost:${port}...`)
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const url = await startTunnel({
|
|
216
|
+
port,
|
|
217
|
+
name: options.name,
|
|
218
|
+
configPath: options.config,
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
console.log()
|
|
222
|
+
console.log('Tunnel is running!')
|
|
223
|
+
console.log()
|
|
224
|
+
console.log(` Local: http://localhost:${port}`)
|
|
225
|
+
console.log(` Public: ${url}`)
|
|
226
|
+
console.log()
|
|
227
|
+
console.log('Press Ctrl+C to stop')
|
|
228
|
+
|
|
229
|
+
// Keep process alive
|
|
230
|
+
await new Promise(() => {})
|
|
231
|
+
} catch (error) {
|
|
232
|
+
logger.error('Failed to start tunnel', {
|
|
233
|
+
error: error instanceof Error ? error.message : String(error),
|
|
234
|
+
})
|
|
235
|
+
process.exit(1)
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
export default tunnelCommand
|