@kitlangton/tailcode 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -88,4 +88,5 @@ TAILCODE_PORT=4096 TAILCODE_PASSWORD=secret bun run start
88
88
 
89
89
  - The published URL is only reachable from devices on your Tailscale tailnet
90
90
  - OpenCode is bound to localhost to avoid exposing it on your LAN
91
+ - Running `tailcode` again will auto-attach if OpenCode is already running locally
91
92
  - TailCode shows a local attach command after setup: `opencode attach http://127.0.0.1:4096`
package/bin/tailcode.ts CHANGED
@@ -1,3 +1,83 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import "../src/main.tsx"
3
+ export {}
4
+
5
+ const DEFAULT_PORT = 4096
6
+
7
+ function printHelp() {
8
+ process.stdout.write(
9
+ `tailcode\n\nUsage:\n tailcode [--wizard] [--attach] [--help]\n\nOptions:\n --wizard Always open the TailCode setup wizard\n --attach Attach to an already-running local OpenCode server\n --help Show this help\n`,
10
+ )
11
+ }
12
+
13
+ function resolvePort() {
14
+ const raw = process.env.TAILCODE_PORT
15
+ if (!raw) return DEFAULT_PORT
16
+ const parsed = Number.parseInt(raw, 10)
17
+ return Number.isInteger(parsed) && parsed > 0 && parsed <= 65535 ? parsed : DEFAULT_PORT
18
+ }
19
+
20
+ async function isHealthy(port: number) {
21
+ const controller = new AbortController()
22
+ const timer = setTimeout(() => controller.abort(), 600)
23
+ try {
24
+ const response = await fetch(`http://127.0.0.1:${port}/global/health`, {
25
+ signal: controller.signal,
26
+ })
27
+ return response.ok
28
+ } catch {
29
+ return false
30
+ } finally {
31
+ clearTimeout(timer)
32
+ }
33
+ }
34
+
35
+ async function runAttach(port: number) {
36
+ const bin = Bun.which("opencode")
37
+ if (!bin) {
38
+ process.stderr.write("tailcode: 'opencode' is not installed (launching wizard instead)\n")
39
+ return false
40
+ }
41
+
42
+ const target = `http://127.0.0.1:${port}`
43
+ process.stdout.write(`tailcode: attaching to ${target}\n`)
44
+
45
+ const child = Bun.spawn([bin, "attach", target], {
46
+ stdin: "inherit",
47
+ stdout: "inherit",
48
+ stderr: "inherit",
49
+ })
50
+
51
+ process.exit(await child.exited)
52
+ }
53
+
54
+ const args = process.argv.slice(2)
55
+ const forceWizard = args.includes("--wizard")
56
+ const forceAttach = args.includes("--attach")
57
+
58
+ if (args.includes("--help") || args.includes("-h")) {
59
+ printHelp()
60
+ process.exit(0)
61
+ }
62
+
63
+ if (forceWizard && forceAttach) {
64
+ process.stderr.write("tailcode: use either --wizard or --attach, not both\n")
65
+ process.exit(1)
66
+ }
67
+
68
+ const port = resolvePort()
69
+
70
+ if (!forceWizard) {
71
+ const healthy = await isHealthy(port)
72
+ if (healthy) {
73
+ await runAttach(port)
74
+ }
75
+
76
+ if (forceAttach) {
77
+ process.stderr.write(`tailcode: OpenCode is not running on http://127.0.0.1:${port}\n`)
78
+ process.stderr.write("tailcode: run without --attach to start the setup wizard\n")
79
+ process.exit(1)
80
+ }
81
+ }
82
+
83
+ await import("../src/main.tsx")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitlangton/tailcode",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Terminal wizard for publishing OpenCode to your Tailscale tailnet",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -45,10 +45,12 @@
45
45
  "showcase": "bun run --conditions=browser --preserve-symlinks src/showcase.tsx",
46
46
  "showcase:dev": "bun run --conditions=browser --preserve-symlinks --hot src/showcase.tsx",
47
47
  "typecheck": "tsc -p tsconfig.json --noEmit",
48
- "fmt": "oxfmt --write src",
49
- "fmt:check": "oxfmt --check src",
50
- "lint": "oxlint src",
51
- "check": "bun run typecheck && bun run lint && bun run fmt:check"
48
+ "fmt": "oxfmt --write src bin",
49
+ "fmt:check": "oxfmt --check src bin",
50
+ "lint": "oxlint src bin",
51
+ "check": "bun run typecheck && bun run lint && bun run fmt:check",
52
+ "publish:dry": "bun run check && bun publish --dry-run",
53
+ "publish:npm": "bun run check && bun publish"
52
54
  },
53
55
  "dependencies": {
54
56
  "@effect/atom-solid": "4.0.0-beta.11",
package/src/app.tsx CHANGED
@@ -409,7 +409,7 @@ export function App(props: AppProps = {}) {
409
409
  }
410
410
 
411
411
  const stageRow = () => (
412
- <box flexDirection={compact() ? "column" : "row"} gap={0}>
412
+ <box flexDirection="row" gap={0}>
413
413
  {stageBadge("tailscale", "Tailscale")}
414
414
  {stageBadge("opencode", "OpenCode")}
415
415
  {stageBadge("publish", "Publish")}