Skip to content

Commit 07a3610

Browse files
committed
local devhook
1 parent a3e26c9 commit 07a3610

11 files changed

Lines changed: 105 additions & 39 deletions

bun.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/blink/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "blink",
3-
"version": "1.1.36",
3+
"version": "1.1.37",
44
"description": "Blink is a tool for building and deploying AI agents.",
55
"type": "module",
66
"bin": {
@@ -93,7 +93,7 @@
9393
"@ai-sdk/xai": "^2.0.16",
9494
"@blink-sdk/compute": "^0.0.15",
9595
"@blink-sdk/events": "workspace:*",
96-
"@blink.so/api": "^1.0.0",
96+
"@blink.so/api": "^1.1.0",
9797
"@clack/prompts": "^0.11.0",
9898
"@hono/node-server": "^1.19.3",
9999
"@jaaydenh/gemini-cli": "0.11.0-nightly-20251022-2",

packages/blink/src/cli/deploy.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,10 +327,26 @@ export default async function deploy(
327327
chalk.dim(" Skipping webhook tunnel migration in CI mode")
328328
);
329329
} else {
330-
const productionUrl = `https://${devhookID}.blink.host`;
330+
let productionUrl: string | undefined;
331+
try {
332+
productionUrl = await client.devhook.getUrl(devhookID);
333+
} catch (error) {
334+
const message =
335+
error instanceof Error ? error.message : "Unknown error";
336+
console.log(
337+
chalk.yellow(
338+
`Warning: unable to determine webhook tunnel URL (${message})`
339+
)
340+
);
341+
}
331342
console.log("\n" + chalk.cyan("Webhook Tunnel"));
332-
console.log(chalk.dim(` Current: ${productionUrl} → local dev`));
333-
console.log(chalk.dim(` After: ${productionUrl} → production`));
343+
if (productionUrl) {
344+
console.log(chalk.dim(` Current: ${productionUrl} → local dev`));
345+
console.log(chalk.dim(` After: ${productionUrl} → production`));
346+
} else {
347+
console.log(chalk.dim(" Current: (unknown URL) → local dev"));
348+
console.log(chalk.dim(" After: (unknown URL) → production"));
349+
}
334350
console.log(
335351
chalk.dim(" Migrating will keep your webhooks working in production")
336352
);

packages/blink/src/cli/setup-github-app.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ GITHUB_CLIENT_ID=active_client_id
146146
describe("setup github-app command", () => {
147147
function callSetupGithubApp(directory: string) {
148148
const client = createMockClient();
149+
client.devhook.getUrl.mockResolvedValue("https://test.blink.so/devhook");
149150
return setupGithubApp(directory, {
150151
_deps: {
151152
authenticate: async () => {},

packages/blink/src/cli/setup-github-app.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { access, readFile, writeFile } from "node:fs/promises";
22
import { basename, join } from "node:path";
3-
import type Client from "@blink.so/api";
3+
import Client from "@blink.so/api";
44
import {
55
confirm,
66
intro,
@@ -133,12 +133,17 @@ export async function setupGithubApp(
133133
if (!devhookId) {
134134
throw new Error("Failed to obtain devhook ID");
135135
}
136-
const webhookUrl = `https://${devhookId}.blink.host`;
137136

138137
const host = getHostFn();
139138
if (!host) {
140139
throw new Error("No Blink host configured.");
141140
}
141+
const client =
142+
options?._deps?.client ??
143+
new Client({
144+
baseURL: host,
145+
});
146+
const webhookUrl = await client.devhook.getUrl(devhookId);
142147

143148
// Create manifest with sensible defaults for a typical GitHub App
144149
const manifest = {

packages/blink/src/cli/setup-slack-app.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,10 @@ SLACK_SIGNING_SECRET=active_secret
248248
describe("setup slack-app command", () => {
249249
function callSetupSlackApp(directory: string) {
250250
const client = createMockClient();
251+
client.devhook.getUrl.mockResolvedValue("https://test.blink.so/devhook");
251252
// Mock devhook.listen to avoid WebSocket connections
252253
client.devhook.listen.mockImplementation(
253-
({ onConnect }: { onConnect?: () => void }) => {
254-
setTimeout(() => onConnect?.(), 0);
254+
() => {
255255
return { dispose: () => {}, [Symbol.dispose]: () => {} };
256256
}
257257
);

packages/blink/src/cli/setup-slack-app.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -218,22 +218,10 @@ export async function setupSlackApp(
218218
const devhookId = hasDevhook(directory)
219219
? getDevhookID(directory)
220220
: createDevhookID(directory);
221-
const webhookUrl = `https://${devhookId}.blink.host`;
222221
if (!devhookId) {
223222
throw new Error("Failed to obtain devhook ID");
224223
}
225224

226-
log.info("Starting webhook listener...");
227-
228-
// State for handling Slack events
229-
let signingSecret = "";
230-
let botToken = "";
231-
let dmReceived = false;
232-
let dmChannel = "";
233-
let dmTimestamp = "";
234-
let signatureFailureDetected = false;
235-
let lastFailedChannel: string | undefined;
236-
237225
const host = getHostFn();
238226
if (!host) {
239227
throw new Error(
@@ -245,6 +233,18 @@ export async function setupSlackApp(
245233
new Client({
246234
baseURL: host,
247235
});
236+
const webhookUrl = await client.devhook.getUrl(devhookId);
237+
238+
log.info("Starting webhook listener...");
239+
240+
// State for handling Slack events
241+
let signingSecret = "";
242+
let botToken = "";
243+
let dmReceived = false;
244+
let dmChannel = "";
245+
let dmTimestamp = "";
246+
let signatureFailureDetected = false;
247+
let lastFailedChannel: string | undefined;
248248

249249
let resolveConnected = () => {};
250250
let rejectConnected = (_error: unknown) => {};

packages/blink/src/edit/agent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export interface EditAgent {
3636
export function createEditAgent(options: {
3737
directory: string;
3838
env: Record<string, string>;
39-
getDevhookUrl: () => string;
39+
getDevhookUrl: () => Promise<string>;
4040
}): EditAgent {
4141
const agent = new Agent();
4242

packages/blink/src/react/use-dev-mode.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { isToolOrDynamicToolUIPart } from "ai";
44
import { isToolApprovalOutput } from "../agent/tools";
55
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
66
import { join } from "path";
7-
import type { Client, CapabilitiesResponse } from "../agent/client";
7+
import Client from "@blink.so/api";
8+
import type { CapabilitiesResponse } from "../agent/client";
9+
import { getHost } from "../cli/lib/auth";
810
import { getDevhookID, createDevhookID, hasDevhook } from "../cli/lib/devhook";
911
import { createLocalServer, type LocalServer } from "../local/server";
1012
import { isLogMessage, isStoredMessageMetadata } from "../local/types";
@@ -230,10 +232,12 @@ export default function useDevMode(options: UseDevModeOptions): UseDevMode {
230232
directory,
231233
apiServerUrl: server.url,
232234
env,
233-
getDevhookUrl: useCallback(() => {
235+
getDevhookUrl: useCallback(async () => {
234236
const id = getDevhookID(directory) ?? createDevhookID(directory);
235237
setDevhookID(id);
236-
return `https://${id}.blink.host`;
238+
const host = getHost();
239+
const client = host ? new Client({ baseURL: host }) : new Client();
240+
return client.devhook.getUrl(id);
237241
}, [directory]),
238242
});
239243

packages/blink/src/react/use-devhook.ts

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from "react";
33
import { lock, getLockInfo } from "../local/lockfile";
44
import { join } from "node:path";
55
import chalk from "chalk";
6-
import { getDevhookID } from "../cli/lib/devhook";
6+
import { getHost } from "../cli/lib/auth";
77
import type { Logger } from "./use-logger";
88

99
export interface UseDevhookOptions {
@@ -25,6 +25,36 @@ export default function useDevhook(options: UseDevhookOptions) {
2525
const [status, setStatus] = useState<"connected" | "disconnected" | "error">(
2626
"disconnected"
2727
);
28+
const [publicUrl, setPublicUrl] = useState<string | undefined>(undefined);
29+
30+
useEffect(() => {
31+
if (!options.id) {
32+
setPublicUrl(undefined);
33+
return;
34+
}
35+
const host = getHost();
36+
if (!host) {
37+
// Skip URL lookup if not logged in
38+
setPublicUrl(undefined);
39+
return;
40+
}
41+
let cancelled = false;
42+
setPublicUrl(undefined);
43+
const client = new Client({ baseURL: host });
44+
void client.devhook
45+
.getUrl(options.id)
46+
.then((url) => {
47+
if (!cancelled) {
48+
setPublicUrl(url);
49+
}
50+
})
51+
.catch(() => {
52+
// Ignore lookup errors; listener will retry on connect.
53+
});
54+
return () => {
55+
cancelled = true;
56+
};
57+
}, [options.id]);
2858

2959
useEffect(() => {
3060
// Don't connect if disabled or no devhook ID exists
@@ -95,6 +125,16 @@ export default function useDevhook(options: UseDevhookOptions) {
95125
);
96126
}
97127

128+
// Check if user is logged in before connecting
129+
const host = getHost();
130+
if (!host) {
131+
options.logger.log(
132+
"system",
133+
`Run ${chalk.bold("blink login")} to send webhooks to your agent from anywhere`
134+
);
135+
return;
136+
}
137+
98138
// Lock acquired, now connect
99139
const connect = () => {
100140
if (disposed || isConnecting) return;
@@ -112,19 +152,19 @@ export default function useDevhook(options: UseDevhookOptions) {
112152
}
113153

114154
// No authentication needed for devhooks.
115-
const client = new Client({
116-
// TODO: This shouldn't be hardcoded but our @blink.so/api package
117-
// currently uses `BLINK_API_URL` which this does too 🤦🤦🤦.
118-
baseURL: "https://blink.coder.com",
119-
});
155+
const client = new Client({ baseURL: host });
120156
currentListener = client.devhook.listen({
121157
id: options.id!,
122158
onRequest: async (request) => {
123159
return onRequestRef.current(request);
124160
},
125161
onConnect: () => {
126-
isConnecting = false;
127-
setStatus("connected");
162+
void (async () => {
163+
const url = await client.devhook.getUrl(options.id!);
164+
isConnecting = false;
165+
setStatus("connected");
166+
setPublicUrl(url);
167+
})();
128168
},
129169
onDisconnect: () => {
130170
isConnecting = false;
@@ -181,11 +221,11 @@ export default function useDevhook(options: UseDevhookOptions) {
181221
}
182222
}
183223
};
184-
}, [options.disabled, options.directory]);
224+
}, [options.disabled, options.directory, options.id]);
185225

186226
return {
187227
id: options.id,
188-
url: options.id ? `https://${options.id}.blink.host` : undefined,
228+
url: publicUrl,
189229
status,
190230
};
191231
}

0 commit comments

Comments
 (0)