+78
-14
public/editor/tabs/CLITab.tsx
+78
-14
public/editor/tabs/CLITab.tsx
···
16
16
<CardHeader>
17
17
<div className="flex items-center gap-2 mb-2">
18
18
<CardTitle>Wisp CLI Tool</CardTitle>
19
-
<Badge variant="secondary" className="text-xs">v0.1.0</Badge>
19
+
<Badge variant="secondary" className="text-xs">v0.2.0</Badge>
20
20
<Badge variant="outline" className="text-xs">Alpha</Badge>
21
21
</div>
22
22
<CardDescription>
···
32
32
</div>
33
33
34
34
<div className="space-y-3">
35
-
<h3 className="text-sm font-semibold">Download CLI</h3>
35
+
<h3 className="text-sm font-semibold">Features</h3>
36
+
<ul className="text-sm text-muted-foreground space-y-2 list-disc list-inside">
37
+
<li><strong>Deploy:</strong> Push static sites directly from your terminal</li>
38
+
<li><strong>Pull:</strong> Download sites from the PDS for development or backup</li>
39
+
<li><strong>Serve:</strong> Run a local server with real-time firehose updates</li>
40
+
</ul>
41
+
</div>
42
+
43
+
<div className="space-y-3">
44
+
<h3 className="text-sm font-semibold">Download v0.2.0</h3>
36
45
<div className="grid gap-2">
37
46
<div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border">
38
47
<a
39
-
href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-macos-arm64"
48
+
href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-darwin"
40
49
target="_blank"
41
50
rel="noopener noreferrer"
42
51
className="flex items-center justify-between mb-2"
···
45
54
<ExternalLink className="w-4 h-4 text-muted-foreground" />
46
55
</a>
47
56
<div className="text-xs text-muted-foreground">
48
-
<span className="font-mono">SHA256: 637e325d9668ca745e01493d80dfc72447ef0a889b313e28913ca65c94c7aaae</span>
57
+
<span className="font-mono">SHA-1: a8c27ea41c5e2672bfecb3476ece1c801741d759</span>
49
58
</div>
50
59
</div>
51
60
<div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border">
···
59
68
<ExternalLink className="w-4 h-4 text-muted-foreground" />
60
69
</a>
61
70
<div className="text-xs text-muted-foreground">
62
-
<span className="font-mono">SHA256: 01561656b64826f95b39f13c65c97da8bcc63ecd9f4d7e4e369c8ba8c903c22a</span>
71
+
<span className="font-mono">SHA-1: fd7ee689c7600fc953179ea755b0357c8481a622</span>
63
72
</div>
64
73
</div>
65
74
<div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border">
···
73
82
<ExternalLink className="w-4 h-4 text-muted-foreground" />
74
83
</a>
75
84
<div className="text-xs text-muted-foreground">
76
-
<span className="font-mono">SHA256: 1ff485b9bcf89bc5721a862863c4843cf4530cbcd2489cf200cb24a44f7865a2</span>
85
+
<span className="font-mono">SHA-1: 8bca6992559e19e1d29ab3d2fcc6d09b28e5a485</span>
86
+
</div>
87
+
</div>
88
+
<div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border">
89
+
<a
90
+
href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-windows.exe"
91
+
target="_blank"
92
+
rel="noopener noreferrer"
93
+
className="flex items-center justify-between mb-2"
94
+
>
95
+
<span className="font-mono text-sm">Windows (x86_64)</span>
96
+
<ExternalLink className="w-4 h-4 text-muted-foreground" />
97
+
</a>
98
+
<div className="text-xs text-muted-foreground">
99
+
<span className="font-mono">SHA-1: 90ea3987a06597fa6c42e1df9009e9758e92dd54</span>
77
100
</div>
78
101
</div>
79
102
</div>
80
103
</div>
81
104
82
105
<div className="space-y-3">
83
-
<h3 className="text-sm font-semibold">Basic Usage</h3>
106
+
<h3 className="text-sm font-semibold">Deploy a Site</h3>
84
107
<CodeBlock
85
108
code={`# Download and make executable
86
-
curl -O https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-macos-arm64
87
-
chmod +x wisp-cli-macos-arm64
109
+
curl -O https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-darwin
110
+
chmod +x wisp-cli-aarch64-darwin
88
111
89
-
# Deploy your site (will use OAuth)
90
-
./wisp-cli-macos-arm64 your-handle.bsky.social \\
112
+
# Deploy your site
113
+
./wisp-cli-aarch64-darwin deploy your-handle.bsky.social \\
91
114
--path ./dist \\
92
-
--site my-site
115
+
--site my-site \\
116
+
--password your-app-password
93
117
94
118
# Your site will be available at:
95
119
# https://sites.wisp.place/your-handle/my-site`}
···
98
122
</div>
99
123
100
124
<div className="space-y-3">
125
+
<h3 className="text-sm font-semibold">Pull a Site from PDS</h3>
126
+
<p className="text-xs text-muted-foreground">
127
+
Download a site from the PDS to your local machine (uses OAuth authentication):
128
+
</p>
129
+
<CodeBlock
130
+
code={`# Pull a site to a specific directory
131
+
wisp-cli pull your-handle.bsky.social \\
132
+
--site my-site \\
133
+
--output ./my-site
134
+
135
+
# Pull to current directory
136
+
wisp-cli pull your-handle.bsky.social \\
137
+
--site my-site
138
+
139
+
# Opens browser for OAuth authentication on first run`}
140
+
language="bash"
141
+
/>
142
+
</div>
143
+
144
+
<div className="space-y-3">
145
+
<h3 className="text-sm font-semibold">Serve a Site Locally with Real-Time Updates</h3>
146
+
<p className="text-xs text-muted-foreground">
147
+
Run a local server that monitors the firehose for real-time updates (uses OAuth authentication):
148
+
</p>
149
+
<CodeBlock
150
+
code={`# Serve on http://localhost:8080 (default)
151
+
wisp-cli serve your-handle.bsky.social \\
152
+
--site my-site
153
+
154
+
# Serve on a custom port
155
+
wisp-cli serve your-handle.bsky.social \\
156
+
--site my-site \\
157
+
--port 3000
158
+
159
+
# Downloads site, serves it, and watches firehose for live updates!`}
160
+
language="bash"
161
+
/>
162
+
</div>
163
+
164
+
<div className="space-y-3">
101
165
<h3 className="text-sm font-semibold">CI/CD with Tangled Spindle</h3>
102
166
<p className="text-xs text-muted-foreground">
103
167
Deploy automatically on every push using{' '}
···
147
211
chmod +x wisp-cli
148
212
149
213
# Deploy to Wisp
150
-
./wisp-cli \\
214
+
./wisp-cli deploy \\
151
215
"$WISP_HANDLE" \\
152
216
--path "$SITE_PATH" \\
153
217
--site "$SITE_NAME" \\
···
210
274
chmod +x wisp-cli
211
275
212
276
# Deploy to Wisp
213
-
./wisp-cli \\
277
+
./wisp-cli deploy \\
214
278
"$WISP_HANDLE" \\
215
279
--path "$SITE_PATH" \\
216
280
--site "$SITE_NAME" \\
+5
-5
src/index.ts
+5
-5
src/index.ts
···
70
70
},
71
71
cookie: {
72
72
secrets: cookieSecret,
73
-
sign: true
73
+
sign: ['did']
74
74
}
75
75
})
76
76
// Observability middleware
···
105
105
.onError(observabilityMiddleware('main-app').onError)
106
106
.use(csrfProtection())
107
107
.use(authRoutes(client, cookieSecret))
108
-
.use(wispRoutes(client))
109
-
.use(domainRoutes(client))
110
-
.use(userRoutes(client))
111
-
.use(siteRoutes(client))
108
+
.use(wispRoutes(client, cookieSecret))
109
+
.use(domainRoutes(client, cookieSecret))
110
+
.use(userRoutes(client, cookieSecret))
111
+
.use(siteRoutes(client, cookieSecret))
112
112
.use(adminRoutes(cookieSecret))
113
113
.use(
114
114
await staticPlugin({
+6
-22
src/routes/auth.ts
+6
-22
src/routes/auth.ts
···
5
5
import { authenticateRequest } from '../lib/wisp-auth'
6
6
import { logger } from '../lib/observability'
7
7
8
-
export const authRoutes = (client: NodeOAuthClient, cookieSecret: string) => new Elysia()
8
+
export const authRoutes = (client: NodeOAuthClient, cookieSecret: string) => new Elysia({
9
+
cookie: {
10
+
secrets: cookieSecret,
11
+
sign: ['did']
12
+
}
13
+
})
9
14
.post('/api/auth/signin', async (c) => {
10
15
let handle = 'unknown'
11
16
try {
···
74
79
c.cookie.did.remove()
75
80
return c.redirect('/?error=auth_failed')
76
81
}
77
-
}, {
78
-
cookie: t.Cookie({
79
-
did: t.Optional(t.String())
80
-
}, {
81
-
secrets: cookieSecret,
82
-
sign: ['did']
83
-
})
84
82
})
85
83
.post('/api/auth/logout', async (c) => {
86
84
try {
···
106
104
logger.error('[Auth] Logout error', err)
107
105
return { error: 'Logout failed' }
108
106
}
109
-
}, {
110
-
cookie: t.Cookie({
111
-
did: t.Optional(t.String())
112
-
}, {
113
-
secrets: cookieSecret,
114
-
sign: ['did']
115
-
})
116
107
})
117
108
.get('/api/auth/status', async (c) => {
118
109
try {
···
132
123
c.cookie.did.remove()
133
124
return { authenticated: false }
134
125
}
135
-
}, {
136
-
cookie: t.Cookie({
137
-
did: t.Optional(t.String())
138
-
}, {
139
-
secrets: cookieSecret,
140
-
sign: ['did']
141
-
})
142
126
})
+8
-2
src/routes/domain.ts
+8
-2
src/routes/domain.ts
···
24
24
import { verifyCustomDomain } from '../lib/dns-verify'
25
25
import { logger } from '../lib/logger'
26
26
27
-
export const domainRoutes = (client: NodeOAuthClient) =>
28
-
new Elysia({ prefix: '/api/domain' })
27
+
export const domainRoutes = (client: NodeOAuthClient, cookieSecret: string) =>
28
+
new Elysia({
29
+
prefix: '/api/domain',
30
+
cookie: {
31
+
secrets: cookieSecret,
32
+
sign: ['did']
33
+
}
34
+
})
29
35
// Public endpoints (no auth required)
30
36
.get('/check', async ({ query }) => {
31
37
try {
+8
-2
src/routes/site.ts
+8
-2
src/routes/site.ts
···
5
5
import { deleteSite } from '../lib/db'
6
6
import { logger } from '../lib/logger'
7
7
8
-
export const siteRoutes = (client: NodeOAuthClient) =>
9
-
new Elysia({ prefix: '/api/site' })
8
+
export const siteRoutes = (client: NodeOAuthClient, cookieSecret: string) =>
9
+
new Elysia({
10
+
prefix: '/api/site',
11
+
cookie: {
12
+
secrets: cookieSecret,
13
+
sign: ['did']
14
+
}
15
+
})
10
16
.derive(async ({ cookie }) => {
11
17
const auth = await requireAuth(client, cookie)
12
18
return { auth }
+9
-3
src/routes/user.ts
+9
-3
src/routes/user.ts
···
1
-
import { Elysia } from 'elysia'
1
+
import { Elysia, t } from 'elysia'
2
2
import { requireAuth } from '../lib/wisp-auth'
3
3
import { NodeOAuthClient } from '@atproto/oauth-client-node'
4
4
import { Agent } from '@atproto/api'
···
6
6
import { syncSitesFromPDS } from '../lib/sync-sites'
7
7
import { logger } from '../lib/logger'
8
8
9
-
export const userRoutes = (client: NodeOAuthClient) =>
10
-
new Elysia({ prefix: '/api/user' })
9
+
export const userRoutes = (client: NodeOAuthClient, cookieSecret: string) =>
10
+
new Elysia({
11
+
prefix: '/api/user',
12
+
cookie: {
13
+
secrets: cookieSecret,
14
+
sign: ['did']
15
+
}
16
+
})
11
17
.derive(async ({ cookie }) => {
12
18
const auth = await requireAuth(client, cookie)
13
19
return { auth }
+8
-2
src/routes/wisp.ts
+8
-2
src/routes/wisp.ts
···
37
37
return true;
38
38
}
39
39
40
-
export const wispRoutes = (client: NodeOAuthClient) =>
41
-
new Elysia({ prefix: '/wisp' })
40
+
export const wispRoutes = (client: NodeOAuthClient, cookieSecret: string) =>
41
+
new Elysia({
42
+
prefix: '/wisp',
43
+
cookie: {
44
+
secrets: cookieSecret,
45
+
sign: ['did']
46
+
}
47
+
})
42
48
.derive(async ({ cookie }) => {
43
49
const auth = await requireAuth(client, cookie)
44
50
return { auth }