@blinq_ai/widget 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/dist/cli.js ADDED
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
5
+ import { homedir } from "os";
6
+ import { join } from "path";
7
+ var command = process.argv[2];
8
+ var DEFAULT_API_BASE_URL = process.env.BLINQ_API_BASE_URL ?? "https://api.blinq.kz";
9
+ var SESSION_DIR = join(homedir(), ".blinq");
10
+ var SESSION_PATH = join(SESSION_DIR, "widget-cli.json");
11
+ function printUsage() {
12
+ console.log("Usage:");
13
+ console.log(" npx @blinq_ai/widget login");
14
+ console.log(" npx @blinq_ai/widget init");
15
+ }
16
+ function normalizeBaseUrl(url) {
17
+ return url.replace(/\/$/, "");
18
+ }
19
+ function ensureSessionDir() {
20
+ mkdirSync(SESSION_DIR, { recursive: true });
21
+ }
22
+ function saveSession(session) {
23
+ ensureSessionDir();
24
+ writeFileSync(SESSION_PATH, `${JSON.stringify(session, null, 2)}
25
+ `, "utf-8");
26
+ }
27
+ function loadSession() {
28
+ if (!existsSync(SESSION_PATH)) {
29
+ return null;
30
+ }
31
+ return JSON.parse(readFileSync(SESSION_PATH, "utf-8"));
32
+ }
33
+ async function sleep(ms) {
34
+ await new Promise((resolve) => setTimeout(resolve, ms));
35
+ }
36
+ async function jsonFetch(url, options = {}, accessToken) {
37
+ const headers = new Headers(options.headers);
38
+ if (!headers.has("Content-Type") && options.body) {
39
+ headers.set("Content-Type", "application/json");
40
+ }
41
+ if (accessToken) {
42
+ headers.set("Authorization", `Bearer ${accessToken}`);
43
+ }
44
+ const response = await fetch(url, {
45
+ ...options,
46
+ headers
47
+ });
48
+ if (!response.ok) {
49
+ let message = `Request failed with status ${response.status}`;
50
+ try {
51
+ const payload = await response.json();
52
+ message = payload.detail ?? payload.message ?? message;
53
+ } catch {
54
+ }
55
+ throw new Error(message);
56
+ }
57
+ return await response.json();
58
+ }
59
+ async function login() {
60
+ const apiBaseUrl = normalizeBaseUrl(DEFAULT_API_BASE_URL);
61
+ const start = await jsonFetch(`${apiBaseUrl}/auth/device/start`, {
62
+ method: "POST"
63
+ });
64
+ console.log("Blinq CLI login started.");
65
+ console.log(`1. Open: ${start.verification_uri_complete}`);
66
+ console.log(`2. Confirm the request in your browser`);
67
+ console.log(`3. This terminal will finish automatically`);
68
+ console.log(`User code: ${start.user_code}`);
69
+ const deadline = Date.now() + start.expires_in * 1e3;
70
+ while (Date.now() < deadline) {
71
+ await sleep(start.interval * 1e3);
72
+ const poll = await jsonFetch(`${apiBaseUrl}/auth/device/poll`, {
73
+ method: "POST",
74
+ body: JSON.stringify({ device_code: start.device_code })
75
+ });
76
+ if (poll.status === "pending") {
77
+ continue;
78
+ }
79
+ if (poll.status === "expired" || !poll.access_token) {
80
+ throw new Error("Device login expired. Run `npx @blinq_ai/widget login` again.");
81
+ }
82
+ saveSession({
83
+ apiBaseUrl,
84
+ accessToken: poll.access_token
85
+ });
86
+ console.log(`Saved CLI session to ${SESSION_PATH}`);
87
+ return;
88
+ }
89
+ throw new Error("Device login timed out. Run `npx @blinq_ai/widget login` again.");
90
+ }
91
+ async function initProject() {
92
+ const session = loadSession();
93
+ if (!session) {
94
+ throw new Error("No CLI session found. Run `npx @blinq_ai/widget login` first.");
95
+ }
96
+ const cwd = process.cwd();
97
+ const configPath = join(cwd, "blinq-widget.config.json");
98
+ const snippetPath = join(cwd, "blinq-widget.snippet.html");
99
+ const examplePath = join(cwd, "blinq-widget.example.ts");
100
+ const snippetInfo = await jsonFetch(
101
+ `${session.apiBaseUrl}/api/widget/snippet`,
102
+ {},
103
+ session.accessToken
104
+ );
105
+ const rotatedToken = await jsonFetch(
106
+ `${session.apiBaseUrl}/api/widget/public-token`,
107
+ { method: "POST" },
108
+ session.accessToken
109
+ );
110
+ writeFileSync(
111
+ configPath,
112
+ `${JSON.stringify(
113
+ {
114
+ publicToken: rotatedToken.public_token,
115
+ apiBaseUrl: session.apiBaseUrl,
116
+ mcpEndpoint: snippetInfo.mcp_endpoint
117
+ },
118
+ null,
119
+ 2
120
+ )}
121
+ `,
122
+ "utf-8"
123
+ );
124
+ writeFileSync(snippetPath, `${rotatedToken.snippet}
125
+ `, "utf-8");
126
+ writeFileSync(
127
+ examplePath,
128
+ `import config from "./blinq-widget.config.json";
129
+ import { initFromConfig } from "@blinq_ai/widget";
130
+
131
+ void initFromConfig(config);
132
+ `,
133
+ "utf-8"
134
+ );
135
+ console.log("Blinq widget starter files created:");
136
+ console.log(`- ${configPath}`);
137
+ console.log(`- ${snippetPath}`);
138
+ console.log(`- ${examplePath}`);
139
+ console.log("");
140
+ console.log("Note: init rotates your public browser token so the generated files stay in sync.");
141
+ }
142
+ async function main() {
143
+ if (!command) {
144
+ printUsage();
145
+ process.exit(0);
146
+ }
147
+ if (command === "login") {
148
+ await login();
149
+ process.exit(0);
150
+ }
151
+ if (command === "init") {
152
+ await initProject();
153
+ process.exit(0);
154
+ }
155
+ printUsage();
156
+ process.exit(1);
157
+ }
158
+ void main().catch((error) => {
159
+ console.error(error instanceof Error ? error.message : String(error));
160
+ process.exit(1);
161
+ });