lindoai-cli 1.0.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/README.md +322 -0
- package/dist/index.cjs +2900 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +40 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +2875 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2900 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var http = require('http');
|
|
5
|
+
var fs3 = require('fs');
|
|
6
|
+
var path3 = require('path');
|
|
7
|
+
var os2 = require('os');
|
|
8
|
+
var commander = require('commander');
|
|
9
|
+
var lindoai = require('lindoai');
|
|
10
|
+
var child_process = require('child_process');
|
|
11
|
+
var url = require('url');
|
|
12
|
+
var crypto = require('crypto');
|
|
13
|
+
|
|
14
|
+
function _interopNamespace(e) {
|
|
15
|
+
if (e && e.__esModule) return e;
|
|
16
|
+
var n = Object.create(null);
|
|
17
|
+
if (e) {
|
|
18
|
+
Object.keys(e).forEach(function (k) {
|
|
19
|
+
if (k !== 'default') {
|
|
20
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
21
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
22
|
+
enumerable: true,
|
|
23
|
+
get: function () { return e[k]; }
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
n.default = e;
|
|
29
|
+
return Object.freeze(n);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
var http__namespace = /*#__PURE__*/_interopNamespace(http);
|
|
33
|
+
var fs3__namespace = /*#__PURE__*/_interopNamespace(fs3);
|
|
34
|
+
var path3__namespace = /*#__PURE__*/_interopNamespace(path3);
|
|
35
|
+
var os2__namespace = /*#__PURE__*/_interopNamespace(os2);
|
|
36
|
+
var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
|
|
37
|
+
|
|
38
|
+
var __defProp = Object.defineProperty;
|
|
39
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
40
|
+
var __esm = (fn, res) => function __init() {
|
|
41
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
42
|
+
};
|
|
43
|
+
var __export = (target, all) => {
|
|
44
|
+
for (var name in all)
|
|
45
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// src/server/live-preview-server.ts
|
|
49
|
+
var live_preview_server_exports = {};
|
|
50
|
+
__export(live_preview_server_exports, {
|
|
51
|
+
LIVE_RELOAD_SCRIPT: () => LIVE_RELOAD_SCRIPT,
|
|
52
|
+
injectLiveReload: () => injectLiveReload,
|
|
53
|
+
startLivePreviewServer: () => startLivePreviewServer
|
|
54
|
+
});
|
|
55
|
+
function injectLiveReload(html) {
|
|
56
|
+
const bodyCloseTagRegex = /<\/body>/i;
|
|
57
|
+
const match = html.match(bodyCloseTagRegex);
|
|
58
|
+
if (match && match.index !== void 0) {
|
|
59
|
+
return html.slice(0, match.index) + LIVE_RELOAD_SCRIPT + html.slice(match.index);
|
|
60
|
+
}
|
|
61
|
+
return html + LIVE_RELOAD_SCRIPT;
|
|
62
|
+
}
|
|
63
|
+
function startLivePreviewServer(filePath) {
|
|
64
|
+
return new Promise((resolve4, reject) => {
|
|
65
|
+
const absolutePath = path3__namespace.resolve(filePath);
|
|
66
|
+
const sseClients = /* @__PURE__ */ new Set();
|
|
67
|
+
let debounceTimer = null;
|
|
68
|
+
const DEBOUNCE_MS = 100;
|
|
69
|
+
function notifyClients() {
|
|
70
|
+
for (const client of sseClients) {
|
|
71
|
+
try {
|
|
72
|
+
client.write("data: reload\n\n");
|
|
73
|
+
} catch {
|
|
74
|
+
sseClients.delete(client);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function handleFileChange() {
|
|
79
|
+
if (debounceTimer) {
|
|
80
|
+
clearTimeout(debounceTimer);
|
|
81
|
+
}
|
|
82
|
+
debounceTimer = setTimeout(() => {
|
|
83
|
+
notifyClients();
|
|
84
|
+
debounceTimer = null;
|
|
85
|
+
}, DEBOUNCE_MS);
|
|
86
|
+
}
|
|
87
|
+
function handleRequest(req, res) {
|
|
88
|
+
const url = req.url || "/";
|
|
89
|
+
const method = req.method || "GET";
|
|
90
|
+
if (method !== "GET") {
|
|
91
|
+
res.writeHead(405, { "Content-Type": "text/plain" });
|
|
92
|
+
res.end("Method Not Allowed");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (url === "/__live-reload") {
|
|
96
|
+
res.writeHead(200, {
|
|
97
|
+
"Content-Type": "text/event-stream",
|
|
98
|
+
"Cache-Control": "no-cache",
|
|
99
|
+
"Connection": "keep-alive",
|
|
100
|
+
"Access-Control-Allow-Origin": "*"
|
|
101
|
+
});
|
|
102
|
+
res.write("data: connected\n\n");
|
|
103
|
+
sseClients.add(res);
|
|
104
|
+
req.on("close", () => {
|
|
105
|
+
sseClients.delete(res);
|
|
106
|
+
});
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (url === "/" || url === "/index.html") {
|
|
110
|
+
try {
|
|
111
|
+
const html = fs3__namespace.readFileSync(absolutePath, "utf-8");
|
|
112
|
+
const injectedHtml = injectLiveReload(html);
|
|
113
|
+
res.writeHead(200, {
|
|
114
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
115
|
+
"Cache-Control": "no-cache"
|
|
116
|
+
});
|
|
117
|
+
res.end(injectedHtml);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
const errorMessage = err instanceof Error ? err.message : "Unknown error";
|
|
120
|
+
res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
|
|
121
|
+
res.end(`<!DOCTYPE html>
|
|
122
|
+
<html>
|
|
123
|
+
<head><title>Error</title></head>
|
|
124
|
+
<body>
|
|
125
|
+
<h1>Error loading file</h1>
|
|
126
|
+
<p>${errorMessage}</p>
|
|
127
|
+
</body>
|
|
128
|
+
</html>`);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
|
|
133
|
+
res.end(`<!DOCTYPE html>
|
|
134
|
+
<html>
|
|
135
|
+
<head><title>Not Found</title></head>
|
|
136
|
+
<body>
|
|
137
|
+
<h1>404 Not Found</h1>
|
|
138
|
+
<p>The requested resource was not found.</p>
|
|
139
|
+
</body>
|
|
140
|
+
</html>`);
|
|
141
|
+
}
|
|
142
|
+
const server = http__namespace.createServer(handleRequest);
|
|
143
|
+
let watcher = null;
|
|
144
|
+
server.on("error", (err) => {
|
|
145
|
+
reject(new Error(`Failed to start preview server: ${err.message}`));
|
|
146
|
+
});
|
|
147
|
+
server.listen(0, "127.0.0.1", () => {
|
|
148
|
+
const address = server.address();
|
|
149
|
+
if (!address || typeof address === "string") {
|
|
150
|
+
reject(new Error("Failed to get server address"));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const port = address.port;
|
|
154
|
+
try {
|
|
155
|
+
watcher = fs3__namespace.watch(absolutePath, (eventType) => {
|
|
156
|
+
if (eventType === "change") {
|
|
157
|
+
handleFileChange();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
watcher.on("error", (err) => {
|
|
161
|
+
console.error(`[Live Preview] File watcher error: ${err.message}`);
|
|
162
|
+
});
|
|
163
|
+
} catch (err) {
|
|
164
|
+
const errorMessage = err instanceof Error ? err.message : "Unknown error";
|
|
165
|
+
console.error(`[Live Preview] Failed to watch file: ${errorMessage}`);
|
|
166
|
+
}
|
|
167
|
+
const handleShutdown = () => {
|
|
168
|
+
if (watcher) {
|
|
169
|
+
watcher.close();
|
|
170
|
+
watcher = null;
|
|
171
|
+
}
|
|
172
|
+
if (debounceTimer) {
|
|
173
|
+
clearTimeout(debounceTimer);
|
|
174
|
+
debounceTimer = null;
|
|
175
|
+
}
|
|
176
|
+
for (const client of sseClients) {
|
|
177
|
+
try {
|
|
178
|
+
client.end();
|
|
179
|
+
} catch {
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
sseClients.clear();
|
|
183
|
+
const pidFilePath = path3__namespace.join(os2__namespace.tmpdir(), "lindoai-pages-preview.pid");
|
|
184
|
+
const portFilePath = path3__namespace.join(os2__namespace.tmpdir(), "lindoai-pages-preview.port");
|
|
185
|
+
try {
|
|
186
|
+
if (fs3__namespace.existsSync(pidFilePath)) {
|
|
187
|
+
fs3__namespace.unlinkSync(pidFilePath);
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
if (fs3__namespace.existsSync(portFilePath)) {
|
|
193
|
+
fs3__namespace.unlinkSync(portFilePath);
|
|
194
|
+
}
|
|
195
|
+
} catch {
|
|
196
|
+
}
|
|
197
|
+
server.close(() => {
|
|
198
|
+
process.exit(0);
|
|
199
|
+
});
|
|
200
|
+
setTimeout(() => {
|
|
201
|
+
process.exit(0);
|
|
202
|
+
}, 1e3);
|
|
203
|
+
};
|
|
204
|
+
process.on("SIGTERM", handleShutdown);
|
|
205
|
+
process.on("SIGINT", handleShutdown);
|
|
206
|
+
resolve4(port);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
var LIVE_RELOAD_SCRIPT;
|
|
211
|
+
var init_live_preview_server = __esm({
|
|
212
|
+
"src/server/live-preview-server.ts"() {
|
|
213
|
+
LIVE_RELOAD_SCRIPT = `<script>
|
|
214
|
+
(function() {
|
|
215
|
+
var eventSource = new EventSource('/__live-reload');
|
|
216
|
+
eventSource.onmessage = function(event) {
|
|
217
|
+
if (event.data === 'reload') {
|
|
218
|
+
window.location.reload();
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
eventSource.onerror = function() {
|
|
222
|
+
console.log('[Live Reload] Connection lost, attempting to reconnect...');
|
|
223
|
+
};
|
|
224
|
+
})();
|
|
225
|
+
</script>`;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
var ENV_API_KEY = "LINDO_API_KEY";
|
|
229
|
+
var ENV_BASE_URL = "LINDO_BASE_URL";
|
|
230
|
+
var CONFIG_DIR = ".lindo";
|
|
231
|
+
var CONFIG_FILE = "config.json";
|
|
232
|
+
var DEFAULT_BASE_URL = "https://api.lindo.ai";
|
|
233
|
+
function getConfigDir() {
|
|
234
|
+
return path3__namespace.join(os2__namespace.homedir(), CONFIG_DIR);
|
|
235
|
+
}
|
|
236
|
+
function getConfigPath() {
|
|
237
|
+
return path3__namespace.join(getConfigDir(), CONFIG_FILE);
|
|
238
|
+
}
|
|
239
|
+
function readConfigFile() {
|
|
240
|
+
const configPath = getConfigPath();
|
|
241
|
+
try {
|
|
242
|
+
if (fs3__namespace.existsSync(configPath)) {
|
|
243
|
+
const content = fs3__namespace.readFileSync(configPath, "utf-8");
|
|
244
|
+
return JSON.parse(content);
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
}
|
|
248
|
+
return {};
|
|
249
|
+
}
|
|
250
|
+
function writeConfigFile(config) {
|
|
251
|
+
const configDir = getConfigDir();
|
|
252
|
+
const configPath = getConfigPath();
|
|
253
|
+
if (!fs3__namespace.existsSync(configDir)) {
|
|
254
|
+
fs3__namespace.mkdirSync(configDir, { recursive: true });
|
|
255
|
+
}
|
|
256
|
+
fs3__namespace.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
257
|
+
}
|
|
258
|
+
function loadConfig() {
|
|
259
|
+
const fileConfig = readConfigFile();
|
|
260
|
+
const apiKey = process.env[ENV_API_KEY] || fileConfig.apiKey;
|
|
261
|
+
const baseUrl = process.env[ENV_BASE_URL] || fileConfig.baseUrl || DEFAULT_BASE_URL;
|
|
262
|
+
return {
|
|
263
|
+
apiKey,
|
|
264
|
+
baseUrl
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function saveApiKey(apiKey) {
|
|
268
|
+
const config = readConfigFile();
|
|
269
|
+
config.apiKey = apiKey;
|
|
270
|
+
writeConfigFile(config);
|
|
271
|
+
}
|
|
272
|
+
function saveConfig(key, value) {
|
|
273
|
+
const config = readConfigFile();
|
|
274
|
+
switch (key) {
|
|
275
|
+
case "apiKey":
|
|
276
|
+
config.apiKey = value;
|
|
277
|
+
break;
|
|
278
|
+
case "baseUrl":
|
|
279
|
+
config.baseUrl = value;
|
|
280
|
+
break;
|
|
281
|
+
default:
|
|
282
|
+
throw new Error(`Unknown configuration key: ${key}`);
|
|
283
|
+
}
|
|
284
|
+
writeConfigFile(config);
|
|
285
|
+
}
|
|
286
|
+
function getConfigValue(key) {
|
|
287
|
+
const config = loadConfig();
|
|
288
|
+
switch (key) {
|
|
289
|
+
case "apiKey":
|
|
290
|
+
return config.apiKey;
|
|
291
|
+
case "baseUrl":
|
|
292
|
+
return config.baseUrl;
|
|
293
|
+
default:
|
|
294
|
+
return void 0;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function hasApiKey() {
|
|
298
|
+
const config = loadConfig();
|
|
299
|
+
return !!config.apiKey;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/output.ts
|
|
303
|
+
var colors = {
|
|
304
|
+
reset: "\x1B[0m",
|
|
305
|
+
red: "\x1B[31m",
|
|
306
|
+
green: "\x1B[32m",
|
|
307
|
+
blue: "\x1B[34m",
|
|
308
|
+
cyan: "\x1B[36m",
|
|
309
|
+
gray: "\x1B[90m",
|
|
310
|
+
bold: "\x1B[1m"
|
|
311
|
+
};
|
|
312
|
+
function useColors() {
|
|
313
|
+
return !process.env.NO_COLOR && process.stdout.isTTY !== false;
|
|
314
|
+
}
|
|
315
|
+
function colorize(text, color) {
|
|
316
|
+
if (!useColors()) {
|
|
317
|
+
return text;
|
|
318
|
+
}
|
|
319
|
+
return `${color}${text}${colors.reset}`;
|
|
320
|
+
}
|
|
321
|
+
function success(message) {
|
|
322
|
+
console.log(colorize(`\u2713 ${message}`, colors.green));
|
|
323
|
+
}
|
|
324
|
+
function error(message) {
|
|
325
|
+
console.error(colorize(`\u2717 ${message}`, colors.red));
|
|
326
|
+
}
|
|
327
|
+
function info(message) {
|
|
328
|
+
console.log(colorize(`\u2139 ${message}`, colors.blue));
|
|
329
|
+
}
|
|
330
|
+
function formatJson(data) {
|
|
331
|
+
return JSON.stringify(data, null, 2);
|
|
332
|
+
}
|
|
333
|
+
function formatTable(data) {
|
|
334
|
+
if (data === null || data === void 0) {
|
|
335
|
+
return "";
|
|
336
|
+
}
|
|
337
|
+
if (Array.isArray(data)) {
|
|
338
|
+
if (data.length === 0) {
|
|
339
|
+
return "No data";
|
|
340
|
+
}
|
|
341
|
+
const keys = /* @__PURE__ */ new Set();
|
|
342
|
+
for (const item of data) {
|
|
343
|
+
if (typeof item === "object" && item !== null) {
|
|
344
|
+
Object.keys(item).forEach((key) => keys.add(key));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (keys.size === 0) {
|
|
348
|
+
return data.map((item) => String(item)).join("\n");
|
|
349
|
+
}
|
|
350
|
+
const columns = Array.from(keys);
|
|
351
|
+
return formatTableFromRows(columns, data);
|
|
352
|
+
}
|
|
353
|
+
if (typeof data === "object") {
|
|
354
|
+
const obj = data;
|
|
355
|
+
const entries = Object.entries(obj);
|
|
356
|
+
if (entries.length === 0) {
|
|
357
|
+
return "No data";
|
|
358
|
+
}
|
|
359
|
+
const maxKeyLength = Math.max(...entries.map(([key]) => key.length));
|
|
360
|
+
return entries.map(([key, value]) => {
|
|
361
|
+
const paddedKey = key.padEnd(maxKeyLength);
|
|
362
|
+
const formattedValue = formatValue(value);
|
|
363
|
+
return `${colorize(paddedKey, colors.cyan)} ${formattedValue}`;
|
|
364
|
+
}).join("\n");
|
|
365
|
+
}
|
|
366
|
+
return String(data);
|
|
367
|
+
}
|
|
368
|
+
function formatTableFromRows(columns, rows) {
|
|
369
|
+
const widths = {};
|
|
370
|
+
for (const col of columns) {
|
|
371
|
+
widths[col] = col.length;
|
|
372
|
+
}
|
|
373
|
+
for (const row of rows) {
|
|
374
|
+
if (typeof row === "object" && row !== null) {
|
|
375
|
+
const obj = row;
|
|
376
|
+
for (const col of columns) {
|
|
377
|
+
const value = formatValue(obj[col]);
|
|
378
|
+
widths[col] = Math.max(widths[col], value.length);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const header = columns.map((col) => colorize(col.padEnd(widths[col]), colors.bold)).join(" ");
|
|
383
|
+
const separator = columns.map((col) => "-".repeat(widths[col])).join(" ");
|
|
384
|
+
const dataRows = rows.map((row) => {
|
|
385
|
+
if (typeof row === "object" && row !== null) {
|
|
386
|
+
const obj = row;
|
|
387
|
+
return columns.map((col) => formatValue(obj[col]).padEnd(widths[col])).join(" ");
|
|
388
|
+
}
|
|
389
|
+
return String(row);
|
|
390
|
+
});
|
|
391
|
+
return [header, separator, ...dataRows].join("\n");
|
|
392
|
+
}
|
|
393
|
+
function formatValue(value) {
|
|
394
|
+
if (value === null || value === void 0) {
|
|
395
|
+
return colorize("-", colors.gray);
|
|
396
|
+
}
|
|
397
|
+
if (typeof value === "boolean") {
|
|
398
|
+
return value ? colorize("true", colors.green) : colorize("false", colors.red);
|
|
399
|
+
}
|
|
400
|
+
if (typeof value === "number") {
|
|
401
|
+
return String(value);
|
|
402
|
+
}
|
|
403
|
+
if (typeof value === "object") {
|
|
404
|
+
if (Array.isArray(value)) {
|
|
405
|
+
return `[${value.length} items]`;
|
|
406
|
+
}
|
|
407
|
+
return JSON.stringify(value);
|
|
408
|
+
}
|
|
409
|
+
return String(value);
|
|
410
|
+
}
|
|
411
|
+
function output(data, format) {
|
|
412
|
+
if (format === "json") {
|
|
413
|
+
console.log(formatJson(data));
|
|
414
|
+
} else {
|
|
415
|
+
console.log(formatTable(data));
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/commands/config.ts
|
|
420
|
+
var VALID_KEYS = ["apiKey", "baseUrl"];
|
|
421
|
+
function createConfigCommand() {
|
|
422
|
+
const config = new commander.Command("config").description("Manage CLI configuration");
|
|
423
|
+
config.command("set <key> <value>").description("Set a configuration value").action((key, value) => {
|
|
424
|
+
if (!VALID_KEYS.includes(key)) {
|
|
425
|
+
error(`Invalid configuration key: ${key}`);
|
|
426
|
+
info(`Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
try {
|
|
430
|
+
saveConfig(key, value);
|
|
431
|
+
success(`Configuration saved: ${key}`);
|
|
432
|
+
info(`Config file: ${getConfigPath()}`);
|
|
433
|
+
} catch (err) {
|
|
434
|
+
error(`Failed to save configuration: ${err instanceof Error ? err.message : String(err)}`);
|
|
435
|
+
process.exit(1);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
config.command("get <key>").description("Get a configuration value").option("-f, --format <format>", "Output format (json, table)", "table").action((key, options) => {
|
|
439
|
+
const value = getConfigValue(key);
|
|
440
|
+
if (value === void 0) {
|
|
441
|
+
if (options.format === "json") {
|
|
442
|
+
output({ key, value: null }, options.format);
|
|
443
|
+
} else {
|
|
444
|
+
info(`Configuration key '${key}' is not set`);
|
|
445
|
+
}
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (options.format === "json") {
|
|
449
|
+
output({ key, value }, options.format);
|
|
450
|
+
} else {
|
|
451
|
+
const displayValue = key === "apiKey" ? maskApiKey(value) : value;
|
|
452
|
+
console.log(`${key}: ${displayValue}`);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
config.command("list").description("List all configuration values").option("-f, --format <format>", "Output format (json, table)", "table").action((options) => {
|
|
456
|
+
const resolvedConfig = loadConfig();
|
|
457
|
+
const configData = {
|
|
458
|
+
apiKey: resolvedConfig.apiKey ? maskApiKey(resolvedConfig.apiKey) : "(not set)",
|
|
459
|
+
baseUrl: resolvedConfig.baseUrl,
|
|
460
|
+
configFile: getConfigPath()
|
|
461
|
+
};
|
|
462
|
+
if (options.format === "json") {
|
|
463
|
+
output(
|
|
464
|
+
{
|
|
465
|
+
apiKey: resolvedConfig.apiKey ? maskApiKey(resolvedConfig.apiKey) : null,
|
|
466
|
+
baseUrl: resolvedConfig.baseUrl,
|
|
467
|
+
configFile: getConfigPath()
|
|
468
|
+
},
|
|
469
|
+
options.format
|
|
470
|
+
);
|
|
471
|
+
} else {
|
|
472
|
+
output(configData, options.format);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
config.command("path").description("Show the config file path").action(() => {
|
|
476
|
+
console.log(getConfigPath());
|
|
477
|
+
});
|
|
478
|
+
return config;
|
|
479
|
+
}
|
|
480
|
+
function maskApiKey(apiKey) {
|
|
481
|
+
if (apiKey.length <= 8) {
|
|
482
|
+
return "*".repeat(apiKey.length);
|
|
483
|
+
}
|
|
484
|
+
return `${apiKey.slice(0, 4)}${"*".repeat(apiKey.length - 8)}${apiKey.slice(-4)}`;
|
|
485
|
+
}
|
|
486
|
+
function createAgentsCommand() {
|
|
487
|
+
const agents = new commander.Command("agents").description("Run AI agents");
|
|
488
|
+
agents.command("run <agent-id>").description("Run an AI agent").option("-i, --input <json>", "Input data as JSON string", "{}").option("-s, --stream", "Stream the response", false).option("-f, --format <format>", "Output format (json, table)", "table").action(async (agentId, options) => {
|
|
489
|
+
if (!hasApiKey()) {
|
|
490
|
+
error("API key not configured");
|
|
491
|
+
info("Run: lindo config set apiKey <your-api-key>");
|
|
492
|
+
info("Or set the LINDO_API_KEY environment variable");
|
|
493
|
+
process.exit(1);
|
|
494
|
+
}
|
|
495
|
+
const config = loadConfig();
|
|
496
|
+
const client = new lindoai.LindoClient({
|
|
497
|
+
apiKey: config.apiKey,
|
|
498
|
+
baseUrl: config.baseUrl
|
|
499
|
+
});
|
|
500
|
+
let input;
|
|
501
|
+
try {
|
|
502
|
+
input = JSON.parse(options.input);
|
|
503
|
+
} catch {
|
|
504
|
+
error("Invalid JSON input");
|
|
505
|
+
info(`Example: --input '{"prompt": "Hello!"}'`);
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
try {
|
|
509
|
+
info(`Running agent: ${agentId}`);
|
|
510
|
+
const result = await client.agents.run({
|
|
511
|
+
agent_id: agentId,
|
|
512
|
+
input,
|
|
513
|
+
stream: options.stream
|
|
514
|
+
});
|
|
515
|
+
if (result.success) {
|
|
516
|
+
success("Agent run completed");
|
|
517
|
+
output(result, options.format);
|
|
518
|
+
} else {
|
|
519
|
+
error(`Agent run failed: ${result.error || "Unknown error"}`);
|
|
520
|
+
output(result, options.format);
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
} catch (err) {
|
|
524
|
+
handleError(err);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
return agents;
|
|
528
|
+
}
|
|
529
|
+
function handleError(err) {
|
|
530
|
+
if (err instanceof lindoai.AuthenticationError) {
|
|
531
|
+
error("Authentication failed");
|
|
532
|
+
info("Your API key may be invalid or expired");
|
|
533
|
+
info("Run: lindo config set apiKey <your-api-key>");
|
|
534
|
+
process.exit(1);
|
|
535
|
+
}
|
|
536
|
+
if (err instanceof Error) {
|
|
537
|
+
error(err.message);
|
|
538
|
+
} else {
|
|
539
|
+
error("An unexpected error occurred");
|
|
540
|
+
}
|
|
541
|
+
process.exit(1);
|
|
542
|
+
}
|
|
543
|
+
function createWorkflowsCommand() {
|
|
544
|
+
const workflows = new commander.Command("workflows").description("Manage workflows");
|
|
545
|
+
workflows.command("list").description("List workflow logs").option("-n, --name <name>", "Filter by workflow name").option("-s, --status <status>", "Filter by status").option("-w, --website <id>", "Filter by website ID").option("-c, --client <id>", "Filter by client ID").option("-l, --limit <number>", "Maximum number of results", "50").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
546
|
+
const client = getClient();
|
|
547
|
+
try {
|
|
548
|
+
const result = await client.workflows.list({
|
|
549
|
+
workflow_name: options.name,
|
|
550
|
+
status: options.status,
|
|
551
|
+
website_id: options.website,
|
|
552
|
+
client_id: options.client,
|
|
553
|
+
limit: parseInt(options.limit)
|
|
554
|
+
});
|
|
555
|
+
if (result.success) {
|
|
556
|
+
output(result.data, options.format);
|
|
557
|
+
} else {
|
|
558
|
+
error("Failed to list workflows");
|
|
559
|
+
process.exit(1);
|
|
560
|
+
}
|
|
561
|
+
} catch (err) {
|
|
562
|
+
handleError2(err);
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
workflows.command("start <workflow-name>").description("Start a workflow").option("-p, --params <json>", "Workflow parameters as JSON string", "{}").option("-f, --format <format>", "Output format (json, table)", "table").action(async (workflowName, options) => {
|
|
566
|
+
const client = getClient();
|
|
567
|
+
let params;
|
|
568
|
+
try {
|
|
569
|
+
params = JSON.parse(options.params);
|
|
570
|
+
} catch {
|
|
571
|
+
error("Invalid JSON params");
|
|
572
|
+
info(`Example: --params '{"page_id": "page-123"}'`);
|
|
573
|
+
process.exit(1);
|
|
574
|
+
}
|
|
575
|
+
try {
|
|
576
|
+
info(`Starting workflow: ${workflowName}`);
|
|
577
|
+
const result = await client.workflows.start({
|
|
578
|
+
workflow_name: workflowName,
|
|
579
|
+
params
|
|
580
|
+
});
|
|
581
|
+
if (result.success) {
|
|
582
|
+
success(`Workflow started: ${result.instance_id}`);
|
|
583
|
+
output(result, options.format);
|
|
584
|
+
} else {
|
|
585
|
+
error("Failed to start workflow");
|
|
586
|
+
output(result, options.format);
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
} catch (err) {
|
|
590
|
+
handleError2(err);
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
workflows.command("status <instance-id>").description("Get workflow status").option("-f, --format <format>", "Output format (json, table)", "table").action(async (instanceId, options) => {
|
|
594
|
+
const client = getClient();
|
|
595
|
+
try {
|
|
596
|
+
const status = await client.workflows.getStatus(instanceId);
|
|
597
|
+
output(status, options.format);
|
|
598
|
+
} catch (err) {
|
|
599
|
+
handleError2(err);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
workflows.command("pause <instance-id>").description("Pause a running workflow").option("-f, --format <format>", "Output format (json, table)", "table").action(async (instanceId, options) => {
|
|
603
|
+
const client = getClient();
|
|
604
|
+
try {
|
|
605
|
+
info(`Pausing workflow: ${instanceId}`);
|
|
606
|
+
const result = await client.workflows.pause(instanceId);
|
|
607
|
+
if (result.success) {
|
|
608
|
+
success(result.message);
|
|
609
|
+
} else {
|
|
610
|
+
error(result.message);
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
613
|
+
output(result, options.format);
|
|
614
|
+
} catch (err) {
|
|
615
|
+
handleError2(err);
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
workflows.command("resume <instance-id>").description("Resume a paused workflow").option("-f, --format <format>", "Output format (json, table)", "table").action(async (instanceId, options) => {
|
|
619
|
+
const client = getClient();
|
|
620
|
+
try {
|
|
621
|
+
info(`Resuming workflow: ${instanceId}`);
|
|
622
|
+
const result = await client.workflows.resume(instanceId);
|
|
623
|
+
if (result.success) {
|
|
624
|
+
success(result.message);
|
|
625
|
+
} else {
|
|
626
|
+
error(result.message);
|
|
627
|
+
process.exit(1);
|
|
628
|
+
}
|
|
629
|
+
output(result, options.format);
|
|
630
|
+
} catch (err) {
|
|
631
|
+
handleError2(err);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
workflows.command("terminate <instance-id>").description("Terminate a workflow").option("-f, --format <format>", "Output format (json, table)", "table").action(async (instanceId, options) => {
|
|
635
|
+
const client = getClient();
|
|
636
|
+
try {
|
|
637
|
+
info(`Terminating workflow: ${instanceId}`);
|
|
638
|
+
const result = await client.workflows.terminate(instanceId);
|
|
639
|
+
if (result.success) {
|
|
640
|
+
success(result.message);
|
|
641
|
+
} else {
|
|
642
|
+
error(result.message);
|
|
643
|
+
process.exit(1);
|
|
644
|
+
}
|
|
645
|
+
output(result, options.format);
|
|
646
|
+
} catch (err) {
|
|
647
|
+
handleError2(err);
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
return workflows;
|
|
651
|
+
}
|
|
652
|
+
function getClient() {
|
|
653
|
+
if (!hasApiKey()) {
|
|
654
|
+
error("API key not configured");
|
|
655
|
+
info("Run: lindo config set apiKey <your-api-key>");
|
|
656
|
+
info("Or set the LINDO_API_KEY environment variable");
|
|
657
|
+
process.exit(1);
|
|
658
|
+
}
|
|
659
|
+
const config = loadConfig();
|
|
660
|
+
return new lindoai.LindoClient({
|
|
661
|
+
apiKey: config.apiKey,
|
|
662
|
+
baseUrl: config.baseUrl
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
function handleError2(err) {
|
|
666
|
+
if (err instanceof lindoai.AuthenticationError) {
|
|
667
|
+
error("Authentication failed");
|
|
668
|
+
info("Your API key may be invalid or expired");
|
|
669
|
+
info("Run: lindo config set apiKey <your-api-key>");
|
|
670
|
+
process.exit(1);
|
|
671
|
+
}
|
|
672
|
+
if (err instanceof Error) {
|
|
673
|
+
error(err.message);
|
|
674
|
+
} else {
|
|
675
|
+
error("An unexpected error occurred");
|
|
676
|
+
}
|
|
677
|
+
process.exit(1);
|
|
678
|
+
}
|
|
679
|
+
function createWorkspaceCommand() {
|
|
680
|
+
const workspace = new commander.Command("workspace").description("Workspace operations");
|
|
681
|
+
workspace.command("get").description("Get workspace details").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
682
|
+
const client = getClient2();
|
|
683
|
+
try {
|
|
684
|
+
const response = await client.workspace.get();
|
|
685
|
+
output(response, options.format);
|
|
686
|
+
} catch (err) {
|
|
687
|
+
handleError3(err);
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
workspace.command("credits").description("Get workspace credit balance").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
691
|
+
const client = getClient2();
|
|
692
|
+
try {
|
|
693
|
+
const credits = await client.workspace.getCredits();
|
|
694
|
+
output(credits, options.format);
|
|
695
|
+
} catch (err) {
|
|
696
|
+
handleError3(err);
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
workspace.command("client-credits").description("Get credit balance for a specific client").requiredOption("-c, --client <id>", "Client ID").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
700
|
+
const client = getClient2();
|
|
701
|
+
try {
|
|
702
|
+
const credits = await client.workspace.getClientCredits(options.client);
|
|
703
|
+
output(credits, options.format);
|
|
704
|
+
} catch (err) {
|
|
705
|
+
handleError3(err);
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
workspace.command("update").description("Update workspace settings").option("-n, --name <name>", "Workspace name").option("-l, --language <lang>", "Workspace language").option("-w, --webhook <url>", "Webhook URL").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
709
|
+
const client = getClient2();
|
|
710
|
+
try {
|
|
711
|
+
const response = await client.workspace.update({
|
|
712
|
+
workspace_name: options.name,
|
|
713
|
+
workspace_language: options.language,
|
|
714
|
+
webhook_url: options.webhook
|
|
715
|
+
});
|
|
716
|
+
if (response.success) {
|
|
717
|
+
success("Workspace updated");
|
|
718
|
+
}
|
|
719
|
+
output(response, options.format);
|
|
720
|
+
} catch (err) {
|
|
721
|
+
handleError3(err);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
workspace.command("team-add").description("Add a team member to the workspace").requiredOption("-e, --email <email>", "Team member email").option("-r, --role <role>", "Role (Team)", "Team").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
725
|
+
const client = getClient2();
|
|
726
|
+
try {
|
|
727
|
+
const response = await client.workspace.addTeamMember(options.email, options.role);
|
|
728
|
+
if (response.success) {
|
|
729
|
+
success("Team member added");
|
|
730
|
+
}
|
|
731
|
+
output(response, options.format);
|
|
732
|
+
} catch (err) {
|
|
733
|
+
handleError3(err);
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
workspace.command("team-remove").description("Remove a team member from the workspace").requiredOption("-m, --member <id>", "Member ID").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
737
|
+
const client = getClient2();
|
|
738
|
+
try {
|
|
739
|
+
const response = await client.workspace.removeTeamMember(options.member);
|
|
740
|
+
if (response.success) {
|
|
741
|
+
success("Team member removed");
|
|
742
|
+
}
|
|
743
|
+
output(response, options.format);
|
|
744
|
+
} catch (err) {
|
|
745
|
+
handleError3(err);
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
workspace.command("integration-add").description("Add an integration to the workspace").requiredOption("-t, --type <type>", "Integration type (e.g., matomo)").requiredOption("-c, --config <json>", "Integration config as JSON").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
749
|
+
const client = getClient2();
|
|
750
|
+
let config;
|
|
751
|
+
try {
|
|
752
|
+
config = JSON.parse(options.config);
|
|
753
|
+
} catch {
|
|
754
|
+
error("Invalid JSON config");
|
|
755
|
+
process.exit(1);
|
|
756
|
+
}
|
|
757
|
+
try {
|
|
758
|
+
const response = await client.workspace.addIntegration({
|
|
759
|
+
integration_type: options.type,
|
|
760
|
+
config
|
|
761
|
+
});
|
|
762
|
+
if (response.success) {
|
|
763
|
+
success("Integration added");
|
|
764
|
+
}
|
|
765
|
+
output(response, options.format);
|
|
766
|
+
} catch (err) {
|
|
767
|
+
handleError3(err);
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
workspace.command("integration-remove").description("Remove an integration from the workspace").requiredOption("-t, --type <type>", "Integration type").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
771
|
+
const client = getClient2();
|
|
772
|
+
try {
|
|
773
|
+
const response = await client.workspace.removeIntegration(options.type);
|
|
774
|
+
if (response.success) {
|
|
775
|
+
success("Integration removed");
|
|
776
|
+
}
|
|
777
|
+
output(response, options.format);
|
|
778
|
+
} catch (err) {
|
|
779
|
+
handleError3(err);
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
workspace.command("whitelabel").description("Setup or update whitelabel settings").option("-d, --domain <domain>", "Custom domain").option("-s, --subdomain <domain>", "Subdomain domain").option("-e, --email-sender <email>", "Email sender address").option("--enable-register", "Enable client registration").option("--disable-register", "Disable client registration").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
783
|
+
const client = getClient2();
|
|
784
|
+
try {
|
|
785
|
+
const response = await client.workspace.setupWhitelabel({
|
|
786
|
+
domain: options.domain,
|
|
787
|
+
subdomain_domain: options.subdomain,
|
|
788
|
+
email_sender: options.emailSender,
|
|
789
|
+
wl_client_register: options.enableRegister ? true : options.disableRegister ? false : void 0
|
|
790
|
+
});
|
|
791
|
+
if (response.success) {
|
|
792
|
+
success("Whitelabel settings updated");
|
|
793
|
+
}
|
|
794
|
+
output(response, options.format);
|
|
795
|
+
} catch (err) {
|
|
796
|
+
handleError3(err);
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
workspace.command("appearance").description("Update workspace appearance settings").option("-p, --primary <color>", "Primary color (hex)").option("-s, --secondary <color>", "Secondary color (hex)").option("-m, --mode <mode>", "Theme mode (light/dark)").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
800
|
+
const client = getClient2();
|
|
801
|
+
try {
|
|
802
|
+
const response = await client.workspace.updateAppearance({
|
|
803
|
+
primary_color: options.primary,
|
|
804
|
+
secondary_color: options.secondary,
|
|
805
|
+
theme_mode: options.mode
|
|
806
|
+
});
|
|
807
|
+
if (response.success) {
|
|
808
|
+
success("Appearance settings updated");
|
|
809
|
+
}
|
|
810
|
+
output(response, options.format);
|
|
811
|
+
} catch (err) {
|
|
812
|
+
handleError3(err);
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
return workspace;
|
|
816
|
+
}
|
|
817
|
+
function getClient2() {
|
|
818
|
+
if (!hasApiKey()) {
|
|
819
|
+
error("API key not configured");
|
|
820
|
+
info("Run: lindo config set apiKey <your-api-key>");
|
|
821
|
+
info("Or set the LINDO_API_KEY environment variable");
|
|
822
|
+
process.exit(1);
|
|
823
|
+
}
|
|
824
|
+
const config = loadConfig();
|
|
825
|
+
return new lindoai.LindoClient({
|
|
826
|
+
apiKey: config.apiKey,
|
|
827
|
+
baseUrl: config.baseUrl
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
function handleError3(err) {
|
|
831
|
+
if (err instanceof lindoai.AuthenticationError) {
|
|
832
|
+
error("Authentication failed");
|
|
833
|
+
info("Your API key may be invalid or expired");
|
|
834
|
+
info("Run: lindo config set apiKey <your-api-key>");
|
|
835
|
+
process.exit(1);
|
|
836
|
+
}
|
|
837
|
+
if (err instanceof Error) {
|
|
838
|
+
error(err.message);
|
|
839
|
+
} else {
|
|
840
|
+
error("An unexpected error occurred");
|
|
841
|
+
}
|
|
842
|
+
process.exit(1);
|
|
843
|
+
}
|
|
844
|
+
function createAnalyticsCommand() {
|
|
845
|
+
const analytics = new commander.Command("analytics").description("Analytics operations");
|
|
846
|
+
analytics.command("workspace").description("Get workspace analytics").option("--from <date>", "Start date (ISO format)").option("--to <date>", "End date (ISO format)").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
847
|
+
const client = getClient3();
|
|
848
|
+
try {
|
|
849
|
+
const analytics2 = await client.analytics.getWorkspace({
|
|
850
|
+
from: options.from,
|
|
851
|
+
to: options.to
|
|
852
|
+
});
|
|
853
|
+
output(analytics2, options.format);
|
|
854
|
+
} catch (err) {
|
|
855
|
+
handleError4(err);
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
analytics.command("website").description("Get website analytics").requiredOption("-w, --website <id>", "Website ID (required)").option("--from <date>", "Start date (ISO format)").option("--to <date>", "End date (ISO format)").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
859
|
+
const client = getClient3();
|
|
860
|
+
try {
|
|
861
|
+
const analytics2 = await client.analytics.getWebsite({
|
|
862
|
+
website_id: options.website,
|
|
863
|
+
from: options.from,
|
|
864
|
+
to: options.to
|
|
865
|
+
});
|
|
866
|
+
output(analytics2, options.format);
|
|
867
|
+
} catch (err) {
|
|
868
|
+
handleError4(err);
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
return analytics;
|
|
872
|
+
}
|
|
873
|
+
function getClient3() {
|
|
874
|
+
if (!hasApiKey()) {
|
|
875
|
+
error("API key not configured");
|
|
876
|
+
info("Run: lindo config set apiKey <your-api-key>");
|
|
877
|
+
info("Or set the LINDO_API_KEY environment variable");
|
|
878
|
+
process.exit(1);
|
|
879
|
+
}
|
|
880
|
+
const config = loadConfig();
|
|
881
|
+
return new lindoai.LindoClient({
|
|
882
|
+
apiKey: config.apiKey,
|
|
883
|
+
baseUrl: config.baseUrl
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
function handleError4(err) {
|
|
887
|
+
if (err instanceof lindoai.AuthenticationError) {
|
|
888
|
+
error("Authentication failed");
|
|
889
|
+
info("Your API key may be invalid or expired");
|
|
890
|
+
info("Run: lindo config set apiKey <your-api-key>");
|
|
891
|
+
process.exit(1);
|
|
892
|
+
}
|
|
893
|
+
if (err instanceof Error) {
|
|
894
|
+
error(err.message);
|
|
895
|
+
} else {
|
|
896
|
+
error("An unexpected error occurred");
|
|
897
|
+
}
|
|
898
|
+
process.exit(1);
|
|
899
|
+
}
|
|
900
|
+
function createClientsCommand() {
|
|
901
|
+
const clients = new commander.Command("clients").description("Client management operations");
|
|
902
|
+
clients.command("list").description("List all workspace clients").option("-p, --page <page>", "Page number", "1").option("-s, --search <search>", "Search term").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
903
|
+
const client = getClient4();
|
|
904
|
+
try {
|
|
905
|
+
const response = await client.clients.list({
|
|
906
|
+
page: parseInt(options.page, 10),
|
|
907
|
+
search: options.search
|
|
908
|
+
});
|
|
909
|
+
if (options.format === "json") {
|
|
910
|
+
output(response, "json");
|
|
911
|
+
} else {
|
|
912
|
+
if (response.clients && response.clients.length > 0) {
|
|
913
|
+
console.log("\nClients:");
|
|
914
|
+
console.log("--------");
|
|
915
|
+
for (const c of response.clients) {
|
|
916
|
+
console.log(` ID: ${c.record_id}`);
|
|
917
|
+
console.log(` Email: ${c.email}`);
|
|
918
|
+
console.log(` Website Limit: ${c.website_limit ?? "N/A"}`);
|
|
919
|
+
console.log(` Suspended: ${c.suspended ?? false}`);
|
|
920
|
+
console.log("");
|
|
921
|
+
}
|
|
922
|
+
console.log(`Total: ${response.total ?? response.clients.length}`);
|
|
923
|
+
} else {
|
|
924
|
+
info("No clients found");
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
} catch (err) {
|
|
928
|
+
handleError5(err);
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
clients.command("create").description("Create a new workspace client").requiredOption("-e, --email <email>", "Client email address").option("-l, --limit <limit>", "Website limit", "5").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
932
|
+
const client = getClient4();
|
|
933
|
+
try {
|
|
934
|
+
const response = await client.clients.create({
|
|
935
|
+
email: options.email,
|
|
936
|
+
website_limit: parseInt(options.limit, 10)
|
|
937
|
+
});
|
|
938
|
+
if (response.success && response.client) {
|
|
939
|
+
success(`Client created: ${response.client.record_id}`);
|
|
940
|
+
output(response.client, options.format);
|
|
941
|
+
} else {
|
|
942
|
+
error("Failed to create client");
|
|
943
|
+
if (response.errors) {
|
|
944
|
+
for (const e of response.errors) {
|
|
945
|
+
error(` ${e}`);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
} catch (err) {
|
|
950
|
+
handleError5(err);
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
clients.command("update").description("Update a workspace client").requiredOption("-i, --id <id>", "Client ID").option("-l, --limit <limit>", "Website limit").option("--suspend", "Suspend the client").option("--unsuspend", "Unsuspend the client").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
954
|
+
const client = getClient4();
|
|
955
|
+
try {
|
|
956
|
+
const response = await client.clients.update({
|
|
957
|
+
client_id: options.id,
|
|
958
|
+
website_limit: options.limit ? parseInt(options.limit, 10) : void 0,
|
|
959
|
+
suspended: options.suspend ? true : options.unsuspend ? false : void 0
|
|
960
|
+
});
|
|
961
|
+
if (response.success) {
|
|
962
|
+
success("Client updated");
|
|
963
|
+
if (response.client) {
|
|
964
|
+
output(response.client, options.format);
|
|
965
|
+
}
|
|
966
|
+
} else {
|
|
967
|
+
error("Failed to update client");
|
|
968
|
+
if (response.errors) {
|
|
969
|
+
for (const e of response.errors) {
|
|
970
|
+
error(` ${e}`);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
} catch (err) {
|
|
975
|
+
handleError5(err);
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
clients.command("delete").description("Delete a workspace client").requiredOption("-i, --id <id>", "Client ID").action(async (options) => {
|
|
979
|
+
const client = getClient4();
|
|
980
|
+
try {
|
|
981
|
+
const response = await client.clients.delete(options.id);
|
|
982
|
+
if (response.success) {
|
|
983
|
+
success("Client deleted");
|
|
984
|
+
} else {
|
|
985
|
+
error("Failed to delete client");
|
|
986
|
+
if (response.errors) {
|
|
987
|
+
for (const e of response.errors) {
|
|
988
|
+
error(` ${e}`);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
} catch (err) {
|
|
993
|
+
handleError5(err);
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
clients.command("magic-link").description("Create a magic link for client authentication").requiredOption("-e, --email <email>", "Client email address").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
997
|
+
const client = getClient4();
|
|
998
|
+
try {
|
|
999
|
+
const response = await client.clients.createMagicLink(options.email);
|
|
1000
|
+
if (response.success) {
|
|
1001
|
+
success("Magic link created");
|
|
1002
|
+
output(response, options.format);
|
|
1003
|
+
} else {
|
|
1004
|
+
error("Failed to create magic link");
|
|
1005
|
+
if (response.errors) {
|
|
1006
|
+
for (const e of response.errors) {
|
|
1007
|
+
error(` ${e}`);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
handleError5(err);
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
return clients;
|
|
1016
|
+
}
|
|
1017
|
+
function getClient4() {
|
|
1018
|
+
if (!hasApiKey()) {
|
|
1019
|
+
error("API key not configured");
|
|
1020
|
+
info("Run: lindo config set apiKey <your-api-key>");
|
|
1021
|
+
info("Or set the LINDO_API_KEY environment variable");
|
|
1022
|
+
process.exit(1);
|
|
1023
|
+
}
|
|
1024
|
+
const config = loadConfig();
|
|
1025
|
+
return new lindoai.LindoClient({
|
|
1026
|
+
apiKey: config.apiKey,
|
|
1027
|
+
baseUrl: config.baseUrl
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
function handleError5(err) {
|
|
1031
|
+
if (err instanceof lindoai.AuthenticationError) {
|
|
1032
|
+
error("Authentication failed");
|
|
1033
|
+
info("Your API key may be invalid or expired");
|
|
1034
|
+
info("Run: lindo config set apiKey <your-api-key>");
|
|
1035
|
+
process.exit(1);
|
|
1036
|
+
}
|
|
1037
|
+
if (err instanceof Error) {
|
|
1038
|
+
error(err.message);
|
|
1039
|
+
} else {
|
|
1040
|
+
error("An unexpected error occurred");
|
|
1041
|
+
}
|
|
1042
|
+
process.exit(1);
|
|
1043
|
+
}
|
|
1044
|
+
function createWebsitesCommand() {
|
|
1045
|
+
const websites = new commander.Command("websites").description("Website management operations");
|
|
1046
|
+
websites.command("list").description("List all workspace websites").option("-p, --page <page>", "Page number", "1").option("-s, --search <search>", "Search term").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1047
|
+
const client = getClient5();
|
|
1048
|
+
try {
|
|
1049
|
+
const response = await client.websites.list({
|
|
1050
|
+
page: parseInt(options.page, 10),
|
|
1051
|
+
search: options.search
|
|
1052
|
+
});
|
|
1053
|
+
if (options.format === "json") {
|
|
1054
|
+
output(response, "json");
|
|
1055
|
+
} else {
|
|
1056
|
+
if (response.websites && response.websites.length > 0) {
|
|
1057
|
+
console.log("\nWebsites:");
|
|
1058
|
+
console.log("---------");
|
|
1059
|
+
for (const w of response.websites) {
|
|
1060
|
+
console.log(` ID: ${w.record_id}`);
|
|
1061
|
+
console.log(` Business Name: ${w.business_name ?? "N/A"}`);
|
|
1062
|
+
console.log(` Preview URL: ${w.preview_url ?? "N/A"}`);
|
|
1063
|
+
console.log(` Activated: ${w.activated ?? false}`);
|
|
1064
|
+
console.log("");
|
|
1065
|
+
}
|
|
1066
|
+
console.log(`Total: ${response.total ?? response.websites.length}`);
|
|
1067
|
+
} else {
|
|
1068
|
+
info("No websites found");
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
} catch (err) {
|
|
1072
|
+
handleError6(err);
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
websites.command("get").description("Get website details").requiredOption("-i, --id <id>", "Website ID").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1076
|
+
const client = getClient5();
|
|
1077
|
+
try {
|
|
1078
|
+
const response = await client.websites.getDetails(options.id);
|
|
1079
|
+
output(response, options.format);
|
|
1080
|
+
} catch (err) {
|
|
1081
|
+
handleError6(err);
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
websites.command("update").description("Update a website").requiredOption("-i, --id <id>", "Website ID").option("-n, --name <name>", "Business name").option("--activate", "Activate the website").option("--deactivate", "Deactivate the website").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1085
|
+
const client = getClient5();
|
|
1086
|
+
try {
|
|
1087
|
+
const response = await client.websites.update({
|
|
1088
|
+
website_id: options.id,
|
|
1089
|
+
business_name: options.name,
|
|
1090
|
+
activated: options.activate ? true : options.deactivate ? false : void 0
|
|
1091
|
+
});
|
|
1092
|
+
if (response.success) {
|
|
1093
|
+
success("Website updated");
|
|
1094
|
+
if (response.website) {
|
|
1095
|
+
output(response.website, options.format);
|
|
1096
|
+
}
|
|
1097
|
+
} else {
|
|
1098
|
+
error("Failed to update website");
|
|
1099
|
+
if (response.errors) {
|
|
1100
|
+
for (const e of response.errors) {
|
|
1101
|
+
error(` ${e}`);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
} catch (err) {
|
|
1106
|
+
handleError6(err);
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
websites.command("settings").description("Update website settings").requiredOption("-i, --id <id>", "Website ID").option("-n, --name <name>", "Business name").option("-l, --language <lang>", "Language").option("-d, --description <desc>", "Business description").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1110
|
+
const client = getClient5();
|
|
1111
|
+
try {
|
|
1112
|
+
const response = await client.websites.updateSettings(options.id, {
|
|
1113
|
+
business_name: options.name,
|
|
1114
|
+
language: options.language,
|
|
1115
|
+
business_description: options.description
|
|
1116
|
+
});
|
|
1117
|
+
if (response.success) {
|
|
1118
|
+
success("Website settings updated");
|
|
1119
|
+
}
|
|
1120
|
+
output(response, options.format);
|
|
1121
|
+
} catch (err) {
|
|
1122
|
+
handleError6(err);
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
websites.command("delete").description("Delete a website").requiredOption("-i, --id <id>", "Website ID").action(async (options) => {
|
|
1126
|
+
const client = getClient5();
|
|
1127
|
+
try {
|
|
1128
|
+
const response = await client.websites.delete(options.id);
|
|
1129
|
+
if (response.success) {
|
|
1130
|
+
success("Website deleted");
|
|
1131
|
+
} else {
|
|
1132
|
+
error("Failed to delete website");
|
|
1133
|
+
if (response.errors) {
|
|
1134
|
+
for (const e of response.errors) {
|
|
1135
|
+
error(` ${e}`);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
} catch (err) {
|
|
1140
|
+
handleError6(err);
|
|
1141
|
+
}
|
|
1142
|
+
});
|
|
1143
|
+
websites.command("assign").description("Assign a website to a client").requiredOption("-w, --website <id>", "Website ID").requiredOption("-c, --client <id>", "Client ID").action(async (options) => {
|
|
1144
|
+
const client = getClient5();
|
|
1145
|
+
try {
|
|
1146
|
+
const response = await client.websites.assign({
|
|
1147
|
+
website_id: options.website,
|
|
1148
|
+
client_id: options.client
|
|
1149
|
+
});
|
|
1150
|
+
if (response.success) {
|
|
1151
|
+
success("Website assigned to client");
|
|
1152
|
+
} else {
|
|
1153
|
+
error("Failed to assign website");
|
|
1154
|
+
if (response.errors) {
|
|
1155
|
+
for (const e of response.errors) {
|
|
1156
|
+
error(` ${e}`);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
} catch (err) {
|
|
1161
|
+
handleError6(err);
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
websites.command("domain-add").description("Add a custom domain to a website").requiredOption("-i, --id <id>", "Website ID").requiredOption("-d, --domain <domain>", "Custom domain").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1165
|
+
const client = getClient5();
|
|
1166
|
+
try {
|
|
1167
|
+
const response = await client.websites.addDomain(options.id, options.domain);
|
|
1168
|
+
if (response.success) {
|
|
1169
|
+
success("Domain added");
|
|
1170
|
+
if (response.result?.dns_records) {
|
|
1171
|
+
console.log("\nDNS Records to configure:");
|
|
1172
|
+
for (const record of response.result.dns_records) {
|
|
1173
|
+
console.log(` ${record.record_type} ${record.host} -> ${record.value}`);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
output(response, options.format);
|
|
1178
|
+
} catch (err) {
|
|
1179
|
+
handleError6(err);
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
websites.command("domain-remove").description("Remove a custom domain from a website").requiredOption("-i, --id <id>", "Website ID").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1183
|
+
const client = getClient5();
|
|
1184
|
+
try {
|
|
1185
|
+
const response = await client.websites.removeDomain(options.id);
|
|
1186
|
+
if (response.success) {
|
|
1187
|
+
success("Domain removed");
|
|
1188
|
+
}
|
|
1189
|
+
output(response, options.format);
|
|
1190
|
+
} catch (err) {
|
|
1191
|
+
handleError6(err);
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
websites.command("integration-add").description("Add an integration to a website").requiredOption("-i, --id <id>", "Website ID").requiredOption("-t, --type <type>", "Integration type (e.g., matomo)").requiredOption("-c, --config <json>", "Integration config as JSON").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1195
|
+
const client = getClient5();
|
|
1196
|
+
let config;
|
|
1197
|
+
try {
|
|
1198
|
+
config = JSON.parse(options.config);
|
|
1199
|
+
} catch {
|
|
1200
|
+
error("Invalid JSON config");
|
|
1201
|
+
process.exit(1);
|
|
1202
|
+
}
|
|
1203
|
+
try {
|
|
1204
|
+
const response = await client.websites.addIntegration(options.id, {
|
|
1205
|
+
integration_type: options.type,
|
|
1206
|
+
config
|
|
1207
|
+
});
|
|
1208
|
+
if (response.success) {
|
|
1209
|
+
success("Integration added");
|
|
1210
|
+
}
|
|
1211
|
+
output(response, options.format);
|
|
1212
|
+
} catch (err) {
|
|
1213
|
+
handleError6(err);
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
1216
|
+
websites.command("integration-remove").description("Remove an integration from a website").requiredOption("-i, --id <id>", "Website ID").requiredOption("-t, --type <type>", "Integration type").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1217
|
+
const client = getClient5();
|
|
1218
|
+
try {
|
|
1219
|
+
const response = await client.websites.removeIntegration(options.id, options.type);
|
|
1220
|
+
if (response.success) {
|
|
1221
|
+
success("Integration removed");
|
|
1222
|
+
}
|
|
1223
|
+
output(response, options.format);
|
|
1224
|
+
} catch (err) {
|
|
1225
|
+
handleError6(err);
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
websites.command("team-add").description("Add a team member to a website").requiredOption("-i, --id <id>", "Website ID").requiredOption("-e, --email <email>", "Team member email").requiredOption("-r, --role <role>", "Role (Editor or Commenter)").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1229
|
+
const client = getClient5();
|
|
1230
|
+
try {
|
|
1231
|
+
const response = await client.websites.addTeamMember(options.id, options.email, options.role);
|
|
1232
|
+
if (response.success) {
|
|
1233
|
+
success("Team member added");
|
|
1234
|
+
}
|
|
1235
|
+
output(response, options.format);
|
|
1236
|
+
} catch (err) {
|
|
1237
|
+
handleError6(err);
|
|
1238
|
+
}
|
|
1239
|
+
});
|
|
1240
|
+
websites.command("team-remove").description("Remove a team member from a website").requiredOption("-i, --id <id>", "Website ID").requiredOption("-m, --member <memberId>", "Member ID").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1241
|
+
const client = getClient5();
|
|
1242
|
+
try {
|
|
1243
|
+
const response = await client.websites.removeTeamMember(options.id, options.member);
|
|
1244
|
+
if (response.success) {
|
|
1245
|
+
success("Team member removed");
|
|
1246
|
+
}
|
|
1247
|
+
output(response, options.format);
|
|
1248
|
+
} catch (err) {
|
|
1249
|
+
handleError6(err);
|
|
1250
|
+
}
|
|
1251
|
+
});
|
|
1252
|
+
return websites;
|
|
1253
|
+
}
|
|
1254
|
+
function getClient5() {
|
|
1255
|
+
if (!hasApiKey()) {
|
|
1256
|
+
error("API key not configured");
|
|
1257
|
+
info("Run: lindo config set apiKey <your-api-key>");
|
|
1258
|
+
info("Or set the LINDO_API_KEY environment variable");
|
|
1259
|
+
process.exit(1);
|
|
1260
|
+
}
|
|
1261
|
+
const config = loadConfig();
|
|
1262
|
+
return new lindoai.LindoClient({
|
|
1263
|
+
apiKey: config.apiKey,
|
|
1264
|
+
baseUrl: config.baseUrl
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
function handleError6(err) {
|
|
1268
|
+
if (err instanceof lindoai.AuthenticationError) {
|
|
1269
|
+
error("Authentication failed");
|
|
1270
|
+
info("Your API key may be invalid or expired");
|
|
1271
|
+
info("Run: lindo config set apiKey <your-api-key>");
|
|
1272
|
+
process.exit(1);
|
|
1273
|
+
}
|
|
1274
|
+
if (err instanceof Error) {
|
|
1275
|
+
error(err.message);
|
|
1276
|
+
} else {
|
|
1277
|
+
error("An unexpected error occurred");
|
|
1278
|
+
}
|
|
1279
|
+
process.exit(1);
|
|
1280
|
+
}
|
|
1281
|
+
function openBrowser(url) {
|
|
1282
|
+
return new Promise((resolve4) => {
|
|
1283
|
+
const currentPlatform = os2.platform();
|
|
1284
|
+
let command;
|
|
1285
|
+
switch (currentPlatform) {
|
|
1286
|
+
case "darwin":
|
|
1287
|
+
command = `open "${url}"`;
|
|
1288
|
+
break;
|
|
1289
|
+
case "win32":
|
|
1290
|
+
command = `start "" "${url}"`;
|
|
1291
|
+
break;
|
|
1292
|
+
default:
|
|
1293
|
+
command = `xdg-open "${url}"`;
|
|
1294
|
+
break;
|
|
1295
|
+
}
|
|
1296
|
+
child_process.exec(command, (error2) => {
|
|
1297
|
+
if (error2) {
|
|
1298
|
+
resolve4(false);
|
|
1299
|
+
} else {
|
|
1300
|
+
resolve4(true);
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// src/commands/pages.ts
|
|
1307
|
+
var PID_FILE = path3__namespace.join(os2__namespace.tmpdir(), "lindoai-pages-preview.pid");
|
|
1308
|
+
var PORT_FILE = path3__namespace.join(os2__namespace.tmpdir(), "lindoai-pages-preview.port");
|
|
1309
|
+
function createPagesCommand() {
|
|
1310
|
+
const pages = new commander.Command("pages").description("Page management operations");
|
|
1311
|
+
pages.command("list").description("List all pages for a website").requiredOption("-w, --website <id>", "Website ID").option("-p, --page <page>", "Page number", "1").option("-s, --search <search>", "Search term").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1312
|
+
const client = getClient6();
|
|
1313
|
+
try {
|
|
1314
|
+
const response = await client.pages.list(options.website, {
|
|
1315
|
+
page: parseInt(options.page, 10),
|
|
1316
|
+
search: options.search
|
|
1317
|
+
});
|
|
1318
|
+
if (options.format === "json") {
|
|
1319
|
+
output(response, "json");
|
|
1320
|
+
} else {
|
|
1321
|
+
const result = response.result;
|
|
1322
|
+
if (result?.list && result.list.length > 0) {
|
|
1323
|
+
console.log("\nPages:");
|
|
1324
|
+
console.log("------");
|
|
1325
|
+
for (const p of result.list) {
|
|
1326
|
+
console.log(` ID: ${p.page_id}`);
|
|
1327
|
+
console.log(` Name: ${p.name ?? "N/A"}`);
|
|
1328
|
+
console.log(` Path: ${p.path ?? "N/A"}`);
|
|
1329
|
+
console.log(` Status: ${p.status ?? "N/A"}`);
|
|
1330
|
+
console.log(` Published: ${p.publish_date ? new Date(p.publish_date * 1e3).toISOString() : "No"}`);
|
|
1331
|
+
console.log("");
|
|
1332
|
+
}
|
|
1333
|
+
console.log(`Total: ${result.total ?? result.list.length}`);
|
|
1334
|
+
} else {
|
|
1335
|
+
info("No pages found");
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
} catch (err) {
|
|
1339
|
+
handleError7(err);
|
|
1340
|
+
}
|
|
1341
|
+
});
|
|
1342
|
+
pages.command("get").description("Get details of a specific page").requiredOption("-w, --website <id>", "Website ID").requiredOption("-i, --id <id>", "Page ID").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1343
|
+
const client = getClient6();
|
|
1344
|
+
try {
|
|
1345
|
+
const response = await client.pages.get(options.website, options.id);
|
|
1346
|
+
if (options.format === "json") {
|
|
1347
|
+
output(response, "json");
|
|
1348
|
+
} else {
|
|
1349
|
+
const result = response.result;
|
|
1350
|
+
if (result) {
|
|
1351
|
+
console.log("\nPage Details:");
|
|
1352
|
+
console.log("-------------");
|
|
1353
|
+
console.log(` ID: ${result.page_id}`);
|
|
1354
|
+
console.log(` Name: ${result.name ?? "N/A"}`);
|
|
1355
|
+
console.log(` Path: ${result.path ?? "N/A"}`);
|
|
1356
|
+
console.log(` Status: ${result.status ?? "N/A"}`);
|
|
1357
|
+
console.log(` Published: ${result.publish_date ? new Date(result.publish_date * 1e3).toISOString() : "No"}`);
|
|
1358
|
+
console.log(` Created: ${result.created_date ?? "N/A"}`);
|
|
1359
|
+
} else {
|
|
1360
|
+
error("Page not found");
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
} catch (err) {
|
|
1364
|
+
handleError7(err);
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
pages.command("publish").description("Publish a page").requiredOption("-w, --website <id>", "Website ID").requiredOption("-i, --id <id>", "Page ID").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1368
|
+
const client = getClient6();
|
|
1369
|
+
try {
|
|
1370
|
+
const response = await client.pages.publish(options.website, options.id);
|
|
1371
|
+
if (options.format === "json") {
|
|
1372
|
+
output(response, "json");
|
|
1373
|
+
} else {
|
|
1374
|
+
if (response.success) {
|
|
1375
|
+
success("Page published successfully");
|
|
1376
|
+
console.log(` Page ID: ${response.result?.page_id}`);
|
|
1377
|
+
console.log(` Published at: ${response.result?.publish_date ? new Date(response.result.publish_date * 1e3).toISOString() : "N/A"}`);
|
|
1378
|
+
} else {
|
|
1379
|
+
error("Failed to publish page");
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
} catch (err) {
|
|
1383
|
+
handleError7(err);
|
|
1384
|
+
}
|
|
1385
|
+
});
|
|
1386
|
+
pages.command("unpublish").description("Unpublish a page").requiredOption("-w, --website <id>", "Website ID").requiredOption("-i, --id <id>", "Page ID").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1387
|
+
const client = getClient6();
|
|
1388
|
+
try {
|
|
1389
|
+
const response = await client.pages.unpublish(options.website, options.id);
|
|
1390
|
+
if (options.format === "json") {
|
|
1391
|
+
output(response, "json");
|
|
1392
|
+
} else {
|
|
1393
|
+
if (response.success) {
|
|
1394
|
+
success("Page unpublished successfully");
|
|
1395
|
+
console.log(` Page ID: ${response.result?.page_id}`);
|
|
1396
|
+
} else {
|
|
1397
|
+
error("Failed to unpublish page");
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
} catch (err) {
|
|
1401
|
+
handleError7(err);
|
|
1402
|
+
}
|
|
1403
|
+
});
|
|
1404
|
+
pages.command("edit").description("Edit a page with live preview").argument("<website_id>", "Website ID").argument("<page_id>", "Page ID").option("--file <path>", "Output file path", "./page.html").option("--background", "Run preview server in background").action(async (websiteId, pageId, options) => {
|
|
1405
|
+
const client = getClient6();
|
|
1406
|
+
try {
|
|
1407
|
+
info("Fetching page details...");
|
|
1408
|
+
const pageResponse = await client.pages.get(websiteId, pageId);
|
|
1409
|
+
if (!pageResponse.result) {
|
|
1410
|
+
error("Page not found");
|
|
1411
|
+
process.exit(1);
|
|
1412
|
+
}
|
|
1413
|
+
const pagePath = pageResponse.result.path;
|
|
1414
|
+
const websiteResponse = await client.websites.getDetails(websiteId);
|
|
1415
|
+
if (!websiteResponse.result) {
|
|
1416
|
+
error("Website not found");
|
|
1417
|
+
process.exit(1);
|
|
1418
|
+
}
|
|
1419
|
+
const baseUrl = websiteResponse.result.custom_domain || websiteResponse.result.verified_domain || websiteResponse.result.preview_url;
|
|
1420
|
+
if (!baseUrl) {
|
|
1421
|
+
error("Could not determine website URL");
|
|
1422
|
+
process.exit(1);
|
|
1423
|
+
}
|
|
1424
|
+
const pageUrl = `https://${baseUrl}${pagePath}`;
|
|
1425
|
+
info(`Fetching HTML from ${pageUrl}...`);
|
|
1426
|
+
const htmlResponse = await fetch(pageUrl);
|
|
1427
|
+
if (!htmlResponse.ok) {
|
|
1428
|
+
error(`Failed to fetch page HTML: ${htmlResponse.status} ${htmlResponse.statusText}`);
|
|
1429
|
+
info("Make sure the page is published before editing");
|
|
1430
|
+
process.exit(1);
|
|
1431
|
+
}
|
|
1432
|
+
const html = await htmlResponse.text();
|
|
1433
|
+
const outputPath = path3__namespace.resolve(options.file);
|
|
1434
|
+
fs3__namespace.writeFileSync(outputPath, html, "utf-8");
|
|
1435
|
+
success(`HTML saved to ${outputPath}`);
|
|
1436
|
+
await terminateExistingPreviewServer();
|
|
1437
|
+
if (options.background) {
|
|
1438
|
+
await startBackgroundPreviewServer(outputPath);
|
|
1439
|
+
} else {
|
|
1440
|
+
await startForegroundPreviewServer(outputPath);
|
|
1441
|
+
}
|
|
1442
|
+
} catch (err) {
|
|
1443
|
+
handleError7(err);
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1446
|
+
pages.command("update").description("Update a page").argument("<website_id>", "Website ID").argument("<page_id>", "Page ID").option("--html-file <path>", "Path to local HTML file to upload").option("-f, --format <format>", "Output format (json, table)", "table").action(async (websiteId, pageId, options) => {
|
|
1447
|
+
const client = getClient6();
|
|
1448
|
+
const config = loadConfig();
|
|
1449
|
+
try {
|
|
1450
|
+
if (!options.htmlFile) {
|
|
1451
|
+
error("--html-file option is required");
|
|
1452
|
+
info("Usage: lindoai pages update <website_id> <page_id> --html-file <path>");
|
|
1453
|
+
process.exit(1);
|
|
1454
|
+
}
|
|
1455
|
+
const htmlPath = path3__namespace.resolve(options.htmlFile);
|
|
1456
|
+
if (!fs3__namespace.existsSync(htmlPath)) {
|
|
1457
|
+
error(`File not found: ${htmlPath}`);
|
|
1458
|
+
process.exit(1);
|
|
1459
|
+
}
|
|
1460
|
+
const html = fs3__namespace.readFileSync(htmlPath, "utf-8");
|
|
1461
|
+
info(`Read ${html.length} bytes from ${htmlPath}`);
|
|
1462
|
+
const pageResponse = await client.pages.get(websiteId, pageId);
|
|
1463
|
+
if (!pageResponse.result) {
|
|
1464
|
+
error("Page not found");
|
|
1465
|
+
process.exit(1);
|
|
1466
|
+
}
|
|
1467
|
+
const pagePath = pageResponse.result.path;
|
|
1468
|
+
info("Updating page...");
|
|
1469
|
+
const apiBaseUrl = config.baseUrl || "https://api.lindo.ai";
|
|
1470
|
+
const updateUrl = `${apiBaseUrl}/v1/workspace/website/${websiteId}/pages/${pageId}/update`;
|
|
1471
|
+
const response = await fetch(updateUrl, {
|
|
1472
|
+
method: "POST",
|
|
1473
|
+
headers: {
|
|
1474
|
+
"Content-Type": "application/json",
|
|
1475
|
+
"Authorization": `Bearer ${config.apiKey}`
|
|
1476
|
+
},
|
|
1477
|
+
body: JSON.stringify({
|
|
1478
|
+
html,
|
|
1479
|
+
path: pagePath
|
|
1480
|
+
})
|
|
1481
|
+
});
|
|
1482
|
+
const result = await response.json();
|
|
1483
|
+
if (options.format === "json") {
|
|
1484
|
+
output(result, "json");
|
|
1485
|
+
} else {
|
|
1486
|
+
if (result.success) {
|
|
1487
|
+
success("Page updated successfully");
|
|
1488
|
+
console.log(` Page ID: ${result.result?.page_id}`);
|
|
1489
|
+
if (result.result?.publish_date) {
|
|
1490
|
+
console.log(` Published at: ${new Date(result.result.publish_date * 1e3).toISOString()}`);
|
|
1491
|
+
}
|
|
1492
|
+
} else {
|
|
1493
|
+
error("Failed to update page");
|
|
1494
|
+
if (result.errors) {
|
|
1495
|
+
for (const err of result.errors) {
|
|
1496
|
+
console.log(` ${err}`);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
} catch (err) {
|
|
1502
|
+
handleError7(err);
|
|
1503
|
+
}
|
|
1504
|
+
});
|
|
1505
|
+
pages.command("stop-preview").description("Stop the background preview server").action(async () => {
|
|
1506
|
+
try {
|
|
1507
|
+
if (!fs3__namespace.existsSync(PID_FILE)) {
|
|
1508
|
+
info("No preview server is running");
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
const pid = parseInt(fs3__namespace.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
1512
|
+
if (isNaN(pid)) {
|
|
1513
|
+
error("Invalid PID file");
|
|
1514
|
+
cleanupPidFiles();
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
try {
|
|
1518
|
+
process.kill(pid, "SIGTERM");
|
|
1519
|
+
success(`Preview server (PID ${pid}) stopped`);
|
|
1520
|
+
} catch (killErr) {
|
|
1521
|
+
if (killErr.code === "ESRCH") {
|
|
1522
|
+
info("Preview server process not found (may have already stopped)");
|
|
1523
|
+
} else {
|
|
1524
|
+
error(`Failed to stop preview server: ${killErr.message}`);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
cleanupPidFiles();
|
|
1528
|
+
} catch (err) {
|
|
1529
|
+
if (err instanceof Error) {
|
|
1530
|
+
error(err.message);
|
|
1531
|
+
} else {
|
|
1532
|
+
error("An unexpected error occurred");
|
|
1533
|
+
}
|
|
1534
|
+
process.exit(1);
|
|
1535
|
+
}
|
|
1536
|
+
});
|
|
1537
|
+
return pages;
|
|
1538
|
+
}
|
|
1539
|
+
function getClient6() {
|
|
1540
|
+
if (!hasApiKey()) {
|
|
1541
|
+
error("API key not configured");
|
|
1542
|
+
info("Run: lindo config set apiKey <your-api-key>");
|
|
1543
|
+
info("Or set the LINDO_API_KEY environment variable");
|
|
1544
|
+
process.exit(1);
|
|
1545
|
+
}
|
|
1546
|
+
const config = loadConfig();
|
|
1547
|
+
return new lindoai.LindoClient({
|
|
1548
|
+
apiKey: config.apiKey,
|
|
1549
|
+
baseUrl: config.baseUrl
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
function handleError7(err) {
|
|
1553
|
+
if (err instanceof lindoai.AuthenticationError) {
|
|
1554
|
+
error("Authentication failed");
|
|
1555
|
+
info("Your API key may be invalid or expired");
|
|
1556
|
+
info("Run: lindo config set apiKey <your-api-key>");
|
|
1557
|
+
process.exit(1);
|
|
1558
|
+
}
|
|
1559
|
+
if (err instanceof Error) {
|
|
1560
|
+
error(err.message);
|
|
1561
|
+
} else {
|
|
1562
|
+
error("An unexpected error occurred");
|
|
1563
|
+
}
|
|
1564
|
+
process.exit(1);
|
|
1565
|
+
}
|
|
1566
|
+
function cleanupPidFiles() {
|
|
1567
|
+
try {
|
|
1568
|
+
if (fs3__namespace.existsSync(PID_FILE)) {
|
|
1569
|
+
fs3__namespace.unlinkSync(PID_FILE);
|
|
1570
|
+
}
|
|
1571
|
+
} catch {
|
|
1572
|
+
}
|
|
1573
|
+
try {
|
|
1574
|
+
if (fs3__namespace.existsSync(PORT_FILE)) {
|
|
1575
|
+
fs3__namespace.unlinkSync(PORT_FILE);
|
|
1576
|
+
}
|
|
1577
|
+
} catch {
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
async function terminateExistingPreviewServer() {
|
|
1581
|
+
if (!fs3__namespace.existsSync(PID_FILE)) {
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
try {
|
|
1585
|
+
const pid = parseInt(fs3__namespace.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
1586
|
+
if (!isNaN(pid)) {
|
|
1587
|
+
try {
|
|
1588
|
+
process.kill(pid, "SIGTERM");
|
|
1589
|
+
info(`Terminated existing preview server (PID ${pid})`);
|
|
1590
|
+
await new Promise((resolve4) => setTimeout(resolve4, 500));
|
|
1591
|
+
} catch {
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
} catch {
|
|
1595
|
+
}
|
|
1596
|
+
cleanupPidFiles();
|
|
1597
|
+
}
|
|
1598
|
+
async function startBackgroundPreviewServer(filePath) {
|
|
1599
|
+
const absolutePath = path3__namespace.resolve(filePath);
|
|
1600
|
+
const serverCode = `
|
|
1601
|
+
const http = require('node:http');
|
|
1602
|
+
const fs = require('node:fs');
|
|
1603
|
+
const path = require('node:path');
|
|
1604
|
+
const os = require('node:os');
|
|
1605
|
+
|
|
1606
|
+
const LIVE_RELOAD_SCRIPT = \`<script>
|
|
1607
|
+
(function() {
|
|
1608
|
+
var eventSource = new EventSource('/__live-reload');
|
|
1609
|
+
eventSource.onmessage = function(event) {
|
|
1610
|
+
if (event.data === 'reload') {
|
|
1611
|
+
window.location.reload();
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
eventSource.onerror = function() {
|
|
1615
|
+
console.log('[Live Reload] Connection lost, attempting to reconnect...');
|
|
1616
|
+
};
|
|
1617
|
+
})();
|
|
1618
|
+
</script>\`;
|
|
1619
|
+
|
|
1620
|
+
const filePath = ${JSON.stringify(absolutePath)};
|
|
1621
|
+
const pidFile = path.join(os.tmpdir(), 'lindoai-pages-preview.pid');
|
|
1622
|
+
const portFile = path.join(os.tmpdir(), 'lindoai-pages-preview.port');
|
|
1623
|
+
const sseClients = new Set();
|
|
1624
|
+
let debounceTimer = null;
|
|
1625
|
+
|
|
1626
|
+
function injectLiveReload(html) {
|
|
1627
|
+
const bodyCloseTagRegex = /<\\/body>/i;
|
|
1628
|
+
const match = html.match(bodyCloseTagRegex);
|
|
1629
|
+
if (match && match.index !== undefined) {
|
|
1630
|
+
return html.slice(0, match.index) + LIVE_RELOAD_SCRIPT + html.slice(match.index);
|
|
1631
|
+
}
|
|
1632
|
+
return html + LIVE_RELOAD_SCRIPT;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
function notifyClients() {
|
|
1636
|
+
for (const client of sseClients) {
|
|
1637
|
+
try {
|
|
1638
|
+
client.write('data: reload\\n\\n');
|
|
1639
|
+
} catch {
|
|
1640
|
+
sseClients.delete(client);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
function handleFileChange() {
|
|
1646
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
1647
|
+
debounceTimer = setTimeout(() => {
|
|
1648
|
+
notifyClients();
|
|
1649
|
+
debounceTimer = null;
|
|
1650
|
+
}, 100);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
const server = http.createServer((req, res) => {
|
|
1654
|
+
const url = req.url || '/';
|
|
1655
|
+
if (req.method !== 'GET') {
|
|
1656
|
+
res.writeHead(405, { 'Content-Type': 'text/plain' });
|
|
1657
|
+
res.end('Method Not Allowed');
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
if (url === '/__live-reload') {
|
|
1662
|
+
res.writeHead(200, {
|
|
1663
|
+
'Content-Type': 'text/event-stream',
|
|
1664
|
+
'Cache-Control': 'no-cache',
|
|
1665
|
+
'Connection': 'keep-alive',
|
|
1666
|
+
'Access-Control-Allow-Origin': '*',
|
|
1667
|
+
});
|
|
1668
|
+
res.write('data: connected\\n\\n');
|
|
1669
|
+
sseClients.add(res);
|
|
1670
|
+
req.on('close', () => sseClients.delete(res));
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
if (url === '/' || url === '/index.html') {
|
|
1675
|
+
try {
|
|
1676
|
+
const html = fs.readFileSync(filePath, 'utf-8');
|
|
1677
|
+
const injectedHtml = injectLiveReload(html);
|
|
1678
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' });
|
|
1679
|
+
res.end(injectedHtml);
|
|
1680
|
+
} catch (err) {
|
|
1681
|
+
res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
1682
|
+
res.end('<h1>Error loading file</h1><p>' + err.message + '</p>');
|
|
1683
|
+
}
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
1688
|
+
res.end('<h1>404 Not Found</h1>');
|
|
1689
|
+
});
|
|
1690
|
+
|
|
1691
|
+
let watcher = null;
|
|
1692
|
+
|
|
1693
|
+
const handleShutdown = () => {
|
|
1694
|
+
if (watcher) watcher.close();
|
|
1695
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
1696
|
+
for (const client of sseClients) {
|
|
1697
|
+
try { client.end(); } catch {}
|
|
1698
|
+
}
|
|
1699
|
+
sseClients.clear();
|
|
1700
|
+
try { if (fs.existsSync(pidFile)) fs.unlinkSync(pidFile); } catch {}
|
|
1701
|
+
try { if (fs.existsSync(portFile)) fs.unlinkSync(portFile); } catch {}
|
|
1702
|
+
server.close(() => process.exit(0));
|
|
1703
|
+
setTimeout(() => process.exit(0), 1000);
|
|
1704
|
+
};
|
|
1705
|
+
|
|
1706
|
+
process.on('SIGTERM', handleShutdown);
|
|
1707
|
+
process.on('SIGINT', handleShutdown);
|
|
1708
|
+
|
|
1709
|
+
server.listen(0, '127.0.0.1', () => {
|
|
1710
|
+
const port = server.address().port;
|
|
1711
|
+
fs.writeFileSync(pidFile, process.pid.toString(), 'utf-8');
|
|
1712
|
+
fs.writeFileSync(portFile, port.toString(), 'utf-8');
|
|
1713
|
+
|
|
1714
|
+
try {
|
|
1715
|
+
watcher = fs.watch(filePath, (eventType) => {
|
|
1716
|
+
if (eventType === 'change') handleFileChange();
|
|
1717
|
+
});
|
|
1718
|
+
} catch {}
|
|
1719
|
+
});
|
|
1720
|
+
`;
|
|
1721
|
+
const child = child_process.spawn(process.execPath, ["-e", serverCode], {
|
|
1722
|
+
detached: true,
|
|
1723
|
+
stdio: "ignore"
|
|
1724
|
+
});
|
|
1725
|
+
child.unref();
|
|
1726
|
+
let port = null;
|
|
1727
|
+
for (let i = 0; i < 50; i++) {
|
|
1728
|
+
await new Promise((resolve4) => setTimeout(resolve4, 100));
|
|
1729
|
+
if (fs3__namespace.existsSync(PORT_FILE)) {
|
|
1730
|
+
try {
|
|
1731
|
+
port = parseInt(fs3__namespace.readFileSync(PORT_FILE, "utf-8").trim(), 10);
|
|
1732
|
+
if (!isNaN(port)) break;
|
|
1733
|
+
} catch {
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
if (!port) {
|
|
1738
|
+
error("Failed to start preview server");
|
|
1739
|
+
process.exit(1);
|
|
1740
|
+
}
|
|
1741
|
+
const previewUrl = `http://127.0.0.1:${port}/`;
|
|
1742
|
+
const browserOpened = await openBrowser(previewUrl);
|
|
1743
|
+
if (!browserOpened) {
|
|
1744
|
+
info(`Could not open browser. Visit: ${previewUrl}`);
|
|
1745
|
+
}
|
|
1746
|
+
success("Preview server started in background");
|
|
1747
|
+
console.log(` URL: ${previewUrl}`);
|
|
1748
|
+
console.log(` PID: ${child.pid}`);
|
|
1749
|
+
console.log("");
|
|
1750
|
+
info("To update the page: lindoai pages update <website_id> <page_id> --html-file <path>");
|
|
1751
|
+
info("To stop the server: lindoai pages stop-preview");
|
|
1752
|
+
}
|
|
1753
|
+
async function startForegroundPreviewServer(filePath) {
|
|
1754
|
+
const { startLivePreviewServer: startLivePreviewServer2 } = await Promise.resolve().then(() => (init_live_preview_server(), live_preview_server_exports));
|
|
1755
|
+
info("Starting preview server...");
|
|
1756
|
+
const port = await startLivePreviewServer2(filePath);
|
|
1757
|
+
const previewUrl = `http://127.0.0.1:${port}/`;
|
|
1758
|
+
fs3__namespace.writeFileSync(PID_FILE, process.pid.toString(), "utf-8");
|
|
1759
|
+
fs3__namespace.writeFileSync(PORT_FILE, port.toString(), "utf-8");
|
|
1760
|
+
const browserOpened = await openBrowser(previewUrl);
|
|
1761
|
+
if (!browserOpened) {
|
|
1762
|
+
info(`Could not open browser. Visit: ${previewUrl}`);
|
|
1763
|
+
}
|
|
1764
|
+
success("Preview server started");
|
|
1765
|
+
console.log(` URL: ${previewUrl}`);
|
|
1766
|
+
console.log("");
|
|
1767
|
+
info("Press Ctrl+C to stop the server");
|
|
1768
|
+
info("Edit the HTML file and save to see changes in the browser");
|
|
1769
|
+
}
|
|
1770
|
+
var PID_FILE2 = path3__namespace.join(os2__namespace.tmpdir(), "lindoai-blogs-preview.pid");
|
|
1771
|
+
var PORT_FILE2 = path3__namespace.join(os2__namespace.tmpdir(), "lindoai-blogs-preview.port");
|
|
1772
|
+
function createBlogsCommand() {
|
|
1773
|
+
const blogs = new commander.Command("blogs").description("Blog management operations");
|
|
1774
|
+
blogs.command("list").description("List all blogs for a website").requiredOption("-w, --website <id>", "Website ID").option("-p, --page <page>", "Page number", "1").option("-s, --search <search>", "Search term").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1775
|
+
const client = getClient7();
|
|
1776
|
+
try {
|
|
1777
|
+
const response = await client.blogs.list(options.website, {
|
|
1778
|
+
page: parseInt(options.page, 10),
|
|
1779
|
+
search: options.search
|
|
1780
|
+
});
|
|
1781
|
+
if (options.format === "json") {
|
|
1782
|
+
output(response, "json");
|
|
1783
|
+
} else {
|
|
1784
|
+
const result = response.result;
|
|
1785
|
+
if (result?.list && result.list.length > 0) {
|
|
1786
|
+
console.log("\nBlogs:");
|
|
1787
|
+
console.log("------");
|
|
1788
|
+
for (const b of result.list) {
|
|
1789
|
+
console.log(` ID: ${b.blog_id}`);
|
|
1790
|
+
console.log(` Name: ${b.name ?? "N/A"}`);
|
|
1791
|
+
console.log(` Path: ${b.path ?? "N/A"}`);
|
|
1792
|
+
console.log(` Status: ${b.status ?? "N/A"}`);
|
|
1793
|
+
console.log(` Published: ${b.publish_date ? new Date(b.publish_date * 1e3).toISOString() : "No"}`);
|
|
1794
|
+
console.log("");
|
|
1795
|
+
}
|
|
1796
|
+
console.log(`Total: ${result.total ?? result.list.length}`);
|
|
1797
|
+
} else {
|
|
1798
|
+
info("No blogs found");
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
} catch (err) {
|
|
1802
|
+
handleError8(err);
|
|
1803
|
+
}
|
|
1804
|
+
});
|
|
1805
|
+
blogs.command("get").description("Get details of a specific blog").requiredOption("-w, --website <id>", "Website ID").requiredOption("-i, --id <id>", "Blog ID").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1806
|
+
const client = getClient7();
|
|
1807
|
+
try {
|
|
1808
|
+
const response = await client.blogs.get(options.website, options.id);
|
|
1809
|
+
if (options.format === "json") {
|
|
1810
|
+
output(response, "json");
|
|
1811
|
+
} else {
|
|
1812
|
+
const result = response.result;
|
|
1813
|
+
if (result) {
|
|
1814
|
+
console.log("\nBlog Details:");
|
|
1815
|
+
console.log("-------------");
|
|
1816
|
+
console.log(` ID: ${result.blog_id}`);
|
|
1817
|
+
console.log(` Name: ${result.name ?? "N/A"}`);
|
|
1818
|
+
console.log(` Path: ${result.path ?? "N/A"}`);
|
|
1819
|
+
console.log(` Status: ${result.status ?? "N/A"}`);
|
|
1820
|
+
console.log(` Published: ${result.publish_date ? new Date(result.publish_date * 1e3).toISOString() : "No"}`);
|
|
1821
|
+
console.log(` Created: ${result.created_date ?? "N/A"}`);
|
|
1822
|
+
} else {
|
|
1823
|
+
error("Blog not found");
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
} catch (err) {
|
|
1827
|
+
handleError8(err);
|
|
1828
|
+
}
|
|
1829
|
+
});
|
|
1830
|
+
blogs.command("publish").description("Publish a blog").requiredOption("-w, --website <id>", "Website ID").requiredOption("-i, --id <id>", "Blog ID").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1831
|
+
const client = getClient7();
|
|
1832
|
+
try {
|
|
1833
|
+
const response = await client.blogs.publish(options.website, options.id);
|
|
1834
|
+
if (options.format === "json") {
|
|
1835
|
+
output(response, "json");
|
|
1836
|
+
} else {
|
|
1837
|
+
if (response.success) {
|
|
1838
|
+
success("Blog published successfully");
|
|
1839
|
+
console.log(` Blog ID: ${response.result?.blog_id}`);
|
|
1840
|
+
console.log(` Published at: ${response.result?.publish_date ? new Date(response.result.publish_date * 1e3).toISOString() : "N/A"}`);
|
|
1841
|
+
} else {
|
|
1842
|
+
error("Failed to publish blog");
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
} catch (err) {
|
|
1846
|
+
handleError8(err);
|
|
1847
|
+
}
|
|
1848
|
+
});
|
|
1849
|
+
blogs.command("unpublish").description("Unpublish a blog").requiredOption("-w, --website <id>", "Website ID").requiredOption("-i, --id <id>", "Blog ID").option("-f, --format <format>", "Output format (json, table)", "table").action(async (options) => {
|
|
1850
|
+
const client = getClient7();
|
|
1851
|
+
try {
|
|
1852
|
+
const response = await client.blogs.unpublish(options.website, options.id);
|
|
1853
|
+
if (options.format === "json") {
|
|
1854
|
+
output(response, "json");
|
|
1855
|
+
} else {
|
|
1856
|
+
if (response.success) {
|
|
1857
|
+
success("Blog unpublished successfully");
|
|
1858
|
+
console.log(` Blog ID: ${response.result?.blog_id}`);
|
|
1859
|
+
} else {
|
|
1860
|
+
error("Failed to unpublish blog");
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
} catch (err) {
|
|
1864
|
+
handleError8(err);
|
|
1865
|
+
}
|
|
1866
|
+
});
|
|
1867
|
+
blogs.command("edit").description("Edit a blog with live preview").argument("<website_id>", "Website ID").argument("<blog_id>", "Blog ID").option("--file <path>", "Output file path", "./blog.html").option("--background", "Run preview server in background").action(async (websiteId, blogId, options) => {
|
|
1868
|
+
const client = getClient7();
|
|
1869
|
+
try {
|
|
1870
|
+
info("Fetching blog details...");
|
|
1871
|
+
const blogResponse = await client.blogs.get(websiteId, blogId);
|
|
1872
|
+
if (!blogResponse.result) {
|
|
1873
|
+
error("Blog not found");
|
|
1874
|
+
process.exit(1);
|
|
1875
|
+
}
|
|
1876
|
+
const blogPath = blogResponse.result.path;
|
|
1877
|
+
const websiteResponse = await client.websites.getDetails(websiteId);
|
|
1878
|
+
if (!websiteResponse.result) {
|
|
1879
|
+
error("Website not found");
|
|
1880
|
+
process.exit(1);
|
|
1881
|
+
}
|
|
1882
|
+
const baseUrl = websiteResponse.result.custom_domain || websiteResponse.result.verified_domain || websiteResponse.result.preview_url;
|
|
1883
|
+
if (!baseUrl) {
|
|
1884
|
+
error("Could not determine website URL");
|
|
1885
|
+
process.exit(1);
|
|
1886
|
+
}
|
|
1887
|
+
const blogUrl = `https://${baseUrl}${blogPath}`;
|
|
1888
|
+
info(`Fetching HTML from ${blogUrl}...`);
|
|
1889
|
+
const htmlResponse = await fetch(blogUrl);
|
|
1890
|
+
if (!htmlResponse.ok) {
|
|
1891
|
+
error(`Failed to fetch blog HTML: ${htmlResponse.status} ${htmlResponse.statusText}`);
|
|
1892
|
+
info("Make sure the blog is published before editing");
|
|
1893
|
+
process.exit(1);
|
|
1894
|
+
}
|
|
1895
|
+
const html = await htmlResponse.text();
|
|
1896
|
+
const outputPath = path3__namespace.resolve(options.file);
|
|
1897
|
+
fs3__namespace.writeFileSync(outputPath, html, "utf-8");
|
|
1898
|
+
success(`HTML saved to ${outputPath}`);
|
|
1899
|
+
await terminateExistingPreviewServer2();
|
|
1900
|
+
if (options.background) {
|
|
1901
|
+
await startBackgroundPreviewServer2(outputPath);
|
|
1902
|
+
} else {
|
|
1903
|
+
await startForegroundPreviewServer2(outputPath);
|
|
1904
|
+
}
|
|
1905
|
+
} catch (err) {
|
|
1906
|
+
handleError8(err);
|
|
1907
|
+
}
|
|
1908
|
+
});
|
|
1909
|
+
blogs.command("update").description("Update a blog").argument("<website_id>", "Website ID").argument("<blog_id>", "Blog ID").option("--html-file <path>", "Path to local HTML file to upload").option("-f, --format <format>", "Output format (json, table)", "table").action(async (websiteId, blogId, options) => {
|
|
1910
|
+
const client = getClient7();
|
|
1911
|
+
const config = loadConfig();
|
|
1912
|
+
try {
|
|
1913
|
+
if (!options.htmlFile) {
|
|
1914
|
+
error("--html-file option is required");
|
|
1915
|
+
info("Usage: lindoai blogs update <website_id> <blog_id> --html-file <path>");
|
|
1916
|
+
process.exit(1);
|
|
1917
|
+
}
|
|
1918
|
+
const htmlPath = path3__namespace.resolve(options.htmlFile);
|
|
1919
|
+
if (!fs3__namespace.existsSync(htmlPath)) {
|
|
1920
|
+
error(`File not found: ${htmlPath}`);
|
|
1921
|
+
process.exit(1);
|
|
1922
|
+
}
|
|
1923
|
+
const html = fs3__namespace.readFileSync(htmlPath, "utf-8");
|
|
1924
|
+
info(`Read ${html.length} bytes from ${htmlPath}`);
|
|
1925
|
+
const blogResponse = await client.blogs.get(websiteId, blogId);
|
|
1926
|
+
if (!blogResponse.result) {
|
|
1927
|
+
error("Blog not found");
|
|
1928
|
+
process.exit(1);
|
|
1929
|
+
}
|
|
1930
|
+
const blogPath = blogResponse.result.path;
|
|
1931
|
+
info("Updating blog...");
|
|
1932
|
+
const apiBaseUrl = config.baseUrl || "https://api.lindo.ai";
|
|
1933
|
+
const updateUrl = `${apiBaseUrl}/v1/workspace/website/${websiteId}/blogs/${blogId}/update`;
|
|
1934
|
+
const response = await fetch(updateUrl, {
|
|
1935
|
+
method: "POST",
|
|
1936
|
+
headers: {
|
|
1937
|
+
"Content-Type": "application/json",
|
|
1938
|
+
"Authorization": `Bearer ${config.apiKey}`
|
|
1939
|
+
},
|
|
1940
|
+
body: JSON.stringify({
|
|
1941
|
+
html,
|
|
1942
|
+
path: blogPath
|
|
1943
|
+
})
|
|
1944
|
+
});
|
|
1945
|
+
const result = await response.json();
|
|
1946
|
+
if (options.format === "json") {
|
|
1947
|
+
output(result, "json");
|
|
1948
|
+
} else {
|
|
1949
|
+
if (result.success) {
|
|
1950
|
+
success("Blog updated successfully");
|
|
1951
|
+
console.log(` Blog ID: ${result.result?.blog_id}`);
|
|
1952
|
+
if (result.result?.publish_date) {
|
|
1953
|
+
console.log(` Published at: ${new Date(result.result.publish_date * 1e3).toISOString()}`);
|
|
1954
|
+
}
|
|
1955
|
+
} else {
|
|
1956
|
+
error("Failed to update blog");
|
|
1957
|
+
if (result.errors) {
|
|
1958
|
+
for (const err of result.errors) {
|
|
1959
|
+
console.log(` ${err}`);
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
} catch (err) {
|
|
1965
|
+
handleError8(err);
|
|
1966
|
+
}
|
|
1967
|
+
});
|
|
1968
|
+
blogs.command("stop-preview").description("Stop the background preview server").action(async () => {
|
|
1969
|
+
try {
|
|
1970
|
+
if (!fs3__namespace.existsSync(PID_FILE2)) {
|
|
1971
|
+
info("No preview server is running");
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
const pid = parseInt(fs3__namespace.readFileSync(PID_FILE2, "utf-8").trim(), 10);
|
|
1975
|
+
if (isNaN(pid)) {
|
|
1976
|
+
error("Invalid PID file");
|
|
1977
|
+
cleanupPidFiles2();
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
try {
|
|
1981
|
+
process.kill(pid, "SIGTERM");
|
|
1982
|
+
success(`Preview server (PID ${pid}) stopped`);
|
|
1983
|
+
} catch (killErr) {
|
|
1984
|
+
if (killErr.code === "ESRCH") {
|
|
1985
|
+
info("Preview server process not found (may have already stopped)");
|
|
1986
|
+
} else {
|
|
1987
|
+
error(`Failed to stop preview server: ${killErr.message}`);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
cleanupPidFiles2();
|
|
1991
|
+
} catch (err) {
|
|
1992
|
+
if (err instanceof Error) {
|
|
1993
|
+
error(err.message);
|
|
1994
|
+
} else {
|
|
1995
|
+
error("An unexpected error occurred");
|
|
1996
|
+
}
|
|
1997
|
+
process.exit(1);
|
|
1998
|
+
}
|
|
1999
|
+
});
|
|
2000
|
+
return blogs;
|
|
2001
|
+
}
|
|
2002
|
+
function getClient7() {
|
|
2003
|
+
if (!hasApiKey()) {
|
|
2004
|
+
error("API key not configured");
|
|
2005
|
+
info("Run: lindo config set apiKey <your-api-key>");
|
|
2006
|
+
info("Or set the LINDO_API_KEY environment variable");
|
|
2007
|
+
process.exit(1);
|
|
2008
|
+
}
|
|
2009
|
+
const config = loadConfig();
|
|
2010
|
+
return new lindoai.LindoClient({
|
|
2011
|
+
apiKey: config.apiKey,
|
|
2012
|
+
baseUrl: config.baseUrl
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
function handleError8(err) {
|
|
2016
|
+
if (err instanceof lindoai.AuthenticationError) {
|
|
2017
|
+
error("Authentication failed");
|
|
2018
|
+
info("Your API key may be invalid or expired");
|
|
2019
|
+
info("Run: lindo config set apiKey <your-api-key>");
|
|
2020
|
+
process.exit(1);
|
|
2021
|
+
}
|
|
2022
|
+
if (err instanceof Error) {
|
|
2023
|
+
error(err.message);
|
|
2024
|
+
} else {
|
|
2025
|
+
error("An unexpected error occurred");
|
|
2026
|
+
}
|
|
2027
|
+
process.exit(1);
|
|
2028
|
+
}
|
|
2029
|
+
function cleanupPidFiles2() {
|
|
2030
|
+
try {
|
|
2031
|
+
if (fs3__namespace.existsSync(PID_FILE2)) {
|
|
2032
|
+
fs3__namespace.unlinkSync(PID_FILE2);
|
|
2033
|
+
}
|
|
2034
|
+
} catch {
|
|
2035
|
+
}
|
|
2036
|
+
try {
|
|
2037
|
+
if (fs3__namespace.existsSync(PORT_FILE2)) {
|
|
2038
|
+
fs3__namespace.unlinkSync(PORT_FILE2);
|
|
2039
|
+
}
|
|
2040
|
+
} catch {
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
async function terminateExistingPreviewServer2() {
|
|
2044
|
+
if (!fs3__namespace.existsSync(PID_FILE2)) {
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
try {
|
|
2048
|
+
const pid = parseInt(fs3__namespace.readFileSync(PID_FILE2, "utf-8").trim(), 10);
|
|
2049
|
+
if (!isNaN(pid)) {
|
|
2050
|
+
try {
|
|
2051
|
+
process.kill(pid, "SIGTERM");
|
|
2052
|
+
info(`Terminated existing preview server (PID ${pid})`);
|
|
2053
|
+
await new Promise((resolve4) => setTimeout(resolve4, 500));
|
|
2054
|
+
} catch {
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
} catch {
|
|
2058
|
+
}
|
|
2059
|
+
cleanupPidFiles2();
|
|
2060
|
+
}
|
|
2061
|
+
async function startBackgroundPreviewServer2(filePath) {
|
|
2062
|
+
const absolutePath = path3__namespace.resolve(filePath);
|
|
2063
|
+
const serverCode = `
|
|
2064
|
+
const http = require('node:http');
|
|
2065
|
+
const fs = require('node:fs');
|
|
2066
|
+
const path = require('node:path');
|
|
2067
|
+
const os = require('node:os');
|
|
2068
|
+
|
|
2069
|
+
const LIVE_RELOAD_SCRIPT = \`<script>
|
|
2070
|
+
(function() {
|
|
2071
|
+
var eventSource = new EventSource('/__live-reload');
|
|
2072
|
+
eventSource.onmessage = function(event) {
|
|
2073
|
+
if (event.data === 'reload') {
|
|
2074
|
+
window.location.reload();
|
|
2075
|
+
}
|
|
2076
|
+
};
|
|
2077
|
+
eventSource.onerror = function() {
|
|
2078
|
+
console.log('[Live Reload] Connection lost, attempting to reconnect...');
|
|
2079
|
+
};
|
|
2080
|
+
})();
|
|
2081
|
+
</script>\`;
|
|
2082
|
+
|
|
2083
|
+
const filePath = ${JSON.stringify(absolutePath)};
|
|
2084
|
+
const pidFile = path.join(os.tmpdir(), 'lindoai-blogs-preview.pid');
|
|
2085
|
+
const portFile = path.join(os.tmpdir(), 'lindoai-blogs-preview.port');
|
|
2086
|
+
const sseClients = new Set();
|
|
2087
|
+
let debounceTimer = null;
|
|
2088
|
+
|
|
2089
|
+
function injectLiveReload(html) {
|
|
2090
|
+
const bodyCloseTagRegex = /<\\/body>/i;
|
|
2091
|
+
const match = html.match(bodyCloseTagRegex);
|
|
2092
|
+
if (match && match.index !== undefined) {
|
|
2093
|
+
return html.slice(0, match.index) + LIVE_RELOAD_SCRIPT + html.slice(match.index);
|
|
2094
|
+
}
|
|
2095
|
+
return html + LIVE_RELOAD_SCRIPT;
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
function notifyClients() {
|
|
2099
|
+
for (const client of sseClients) {
|
|
2100
|
+
try {
|
|
2101
|
+
client.write('data: reload\\n\\n');
|
|
2102
|
+
} catch {
|
|
2103
|
+
sseClients.delete(client);
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
function handleFileChange() {
|
|
2109
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
2110
|
+
debounceTimer = setTimeout(() => {
|
|
2111
|
+
notifyClients();
|
|
2112
|
+
debounceTimer = null;
|
|
2113
|
+
}, 100);
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
const server = http.createServer((req, res) => {
|
|
2117
|
+
const url = req.url || '/';
|
|
2118
|
+
if (req.method !== 'GET') {
|
|
2119
|
+
res.writeHead(405, { 'Content-Type': 'text/plain' });
|
|
2120
|
+
res.end('Method Not Allowed');
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
if (url === '/__live-reload') {
|
|
2125
|
+
res.writeHead(200, {
|
|
2126
|
+
'Content-Type': 'text/event-stream',
|
|
2127
|
+
'Cache-Control': 'no-cache',
|
|
2128
|
+
'Connection': 'keep-alive',
|
|
2129
|
+
'Access-Control-Allow-Origin': '*',
|
|
2130
|
+
});
|
|
2131
|
+
res.write('data: connected\\n\\n');
|
|
2132
|
+
sseClients.add(res);
|
|
2133
|
+
req.on('close', () => sseClients.delete(res));
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
if (url === '/' || url === '/index.html') {
|
|
2138
|
+
try {
|
|
2139
|
+
const html = fs.readFileSync(filePath, 'utf-8');
|
|
2140
|
+
const injectedHtml = injectLiveReload(html);
|
|
2141
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' });
|
|
2142
|
+
res.end(injectedHtml);
|
|
2143
|
+
} catch (err) {
|
|
2144
|
+
res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
2145
|
+
res.end('<h1>Error loading file</h1><p>' + err.message + '</p>');
|
|
2146
|
+
}
|
|
2147
|
+
return;
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
2151
|
+
res.end('<h1>404 Not Found</h1>');
|
|
2152
|
+
});
|
|
2153
|
+
|
|
2154
|
+
let watcher = null;
|
|
2155
|
+
|
|
2156
|
+
const handleShutdown = () => {
|
|
2157
|
+
if (watcher) watcher.close();
|
|
2158
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
2159
|
+
for (const client of sseClients) {
|
|
2160
|
+
try { client.end(); } catch {}
|
|
2161
|
+
}
|
|
2162
|
+
sseClients.clear();
|
|
2163
|
+
try { if (fs.existsSync(pidFile)) fs.unlinkSync(pidFile); } catch {}
|
|
2164
|
+
try { if (fs.existsSync(portFile)) fs.unlinkSync(portFile); } catch {}
|
|
2165
|
+
server.close(() => process.exit(0));
|
|
2166
|
+
setTimeout(() => process.exit(0), 1000);
|
|
2167
|
+
};
|
|
2168
|
+
|
|
2169
|
+
process.on('SIGTERM', handleShutdown);
|
|
2170
|
+
process.on('SIGINT', handleShutdown);
|
|
2171
|
+
|
|
2172
|
+
server.listen(0, '127.0.0.1', () => {
|
|
2173
|
+
const port = server.address().port;
|
|
2174
|
+
fs.writeFileSync(pidFile, process.pid.toString(), 'utf-8');
|
|
2175
|
+
fs.writeFileSync(portFile, port.toString(), 'utf-8');
|
|
2176
|
+
|
|
2177
|
+
try {
|
|
2178
|
+
watcher = fs.watch(filePath, (eventType) => {
|
|
2179
|
+
if (eventType === 'change') handleFileChange();
|
|
2180
|
+
});
|
|
2181
|
+
} catch {}
|
|
2182
|
+
});
|
|
2183
|
+
`;
|
|
2184
|
+
const child = child_process.spawn(process.execPath, ["-e", serverCode], {
|
|
2185
|
+
detached: true,
|
|
2186
|
+
stdio: "ignore"
|
|
2187
|
+
});
|
|
2188
|
+
child.unref();
|
|
2189
|
+
let port = null;
|
|
2190
|
+
for (let i = 0; i < 50; i++) {
|
|
2191
|
+
await new Promise((resolve4) => setTimeout(resolve4, 100));
|
|
2192
|
+
if (fs3__namespace.existsSync(PORT_FILE2)) {
|
|
2193
|
+
try {
|
|
2194
|
+
port = parseInt(fs3__namespace.readFileSync(PORT_FILE2, "utf-8").trim(), 10);
|
|
2195
|
+
if (!isNaN(port)) break;
|
|
2196
|
+
} catch {
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
if (!port) {
|
|
2201
|
+
error("Failed to start preview server");
|
|
2202
|
+
process.exit(1);
|
|
2203
|
+
}
|
|
2204
|
+
const previewUrl = `http://127.0.0.1:${port}/`;
|
|
2205
|
+
const browserOpened = await openBrowser(previewUrl);
|
|
2206
|
+
if (!browserOpened) {
|
|
2207
|
+
info(`Could not open browser. Visit: ${previewUrl}`);
|
|
2208
|
+
}
|
|
2209
|
+
success("Preview server started in background");
|
|
2210
|
+
console.log(` URL: ${previewUrl}`);
|
|
2211
|
+
console.log(` PID: ${child.pid}`);
|
|
2212
|
+
console.log("");
|
|
2213
|
+
info("To update the blog: lindoai blogs update <website_id> <blog_id> --html-file <path>");
|
|
2214
|
+
info("To stop the server: lindoai blogs stop-preview");
|
|
2215
|
+
}
|
|
2216
|
+
async function startForegroundPreviewServer2(filePath) {
|
|
2217
|
+
const { startLivePreviewServer: startLivePreviewServer2 } = await Promise.resolve().then(() => (init_live_preview_server(), live_preview_server_exports));
|
|
2218
|
+
info("Starting preview server...");
|
|
2219
|
+
const port = await startLivePreviewServer2(filePath);
|
|
2220
|
+
const previewUrl = `http://127.0.0.1:${port}/`;
|
|
2221
|
+
fs3__namespace.writeFileSync(PID_FILE2, process.pid.toString(), "utf-8");
|
|
2222
|
+
fs3__namespace.writeFileSync(PORT_FILE2, port.toString(), "utf-8");
|
|
2223
|
+
const browserOpened = await openBrowser(previewUrl);
|
|
2224
|
+
if (!browserOpened) {
|
|
2225
|
+
info(`Could not open browser. Visit: ${previewUrl}`);
|
|
2226
|
+
}
|
|
2227
|
+
success("Preview server started");
|
|
2228
|
+
console.log(` URL: ${previewUrl}`);
|
|
2229
|
+
console.log("");
|
|
2230
|
+
info("Press Ctrl+C to stop the server");
|
|
2231
|
+
info("Edit the HTML file and save to see changes in the browser");
|
|
2232
|
+
}
|
|
2233
|
+
function generateState() {
|
|
2234
|
+
return crypto__namespace.randomBytes(32).toString("hex");
|
|
2235
|
+
}
|
|
2236
|
+
function verifyState(received, expected) {
|
|
2237
|
+
return received === expected;
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
// src/server/callback.ts
|
|
2241
|
+
function parseCallbackParams(url$1) {
|
|
2242
|
+
try {
|
|
2243
|
+
const fullUrl = url$1.startsWith("http") ? url$1 : `http://localhost${url$1}`;
|
|
2244
|
+
const parsed = new url.URL(fullUrl);
|
|
2245
|
+
const params = parsed.searchParams;
|
|
2246
|
+
return {
|
|
2247
|
+
key: params.get("key") ?? void 0,
|
|
2248
|
+
state: params.get("state") ?? void 0,
|
|
2249
|
+
error: params.get("error") ?? void 0,
|
|
2250
|
+
message: params.get("message") ?? void 0
|
|
2251
|
+
};
|
|
2252
|
+
} catch {
|
|
2253
|
+
return {};
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
function generateSuccessHtml() {
|
|
2257
|
+
return `<!DOCTYPE html>
|
|
2258
|
+
<html lang="en">
|
|
2259
|
+
<head>
|
|
2260
|
+
<meta charset="UTF-8">
|
|
2261
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2262
|
+
<title>Authorization Successful - Lindo CLI</title>
|
|
2263
|
+
<style>
|
|
2264
|
+
body {
|
|
2265
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
2266
|
+
display: flex;
|
|
2267
|
+
justify-content: center;
|
|
2268
|
+
align-items: center;
|
|
2269
|
+
min-height: 100vh;
|
|
2270
|
+
margin: 0;
|
|
2271
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
2272
|
+
color: #fff;
|
|
2273
|
+
}
|
|
2274
|
+
.container {
|
|
2275
|
+
text-align: center;
|
|
2276
|
+
padding: 2rem;
|
|
2277
|
+
background: rgba(255, 255, 255, 0.1);
|
|
2278
|
+
border-radius: 16px;
|
|
2279
|
+
backdrop-filter: blur(10px);
|
|
2280
|
+
max-width: 400px;
|
|
2281
|
+
}
|
|
2282
|
+
.icon {
|
|
2283
|
+
font-size: 4rem;
|
|
2284
|
+
margin-bottom: 1rem;
|
|
2285
|
+
}
|
|
2286
|
+
h1 {
|
|
2287
|
+
margin: 0 0 1rem 0;
|
|
2288
|
+
font-size: 1.5rem;
|
|
2289
|
+
}
|
|
2290
|
+
p {
|
|
2291
|
+
margin: 0;
|
|
2292
|
+
opacity: 0.9;
|
|
2293
|
+
}
|
|
2294
|
+
</style>
|
|
2295
|
+
</head>
|
|
2296
|
+
<body>
|
|
2297
|
+
<div class="container">
|
|
2298
|
+
<div class="icon">\u2713</div>
|
|
2299
|
+
<h1>Authorization Successful</h1>
|
|
2300
|
+
<p>You can close this window and return to your terminal.</p>
|
|
2301
|
+
</div>
|
|
2302
|
+
</body>
|
|
2303
|
+
</html>`;
|
|
2304
|
+
}
|
|
2305
|
+
function generateErrorHtml(errorMessage) {
|
|
2306
|
+
const escapedMessage = errorMessage.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2307
|
+
return `<!DOCTYPE html>
|
|
2308
|
+
<html lang="en">
|
|
2309
|
+
<head>
|
|
2310
|
+
<meta charset="UTF-8">
|
|
2311
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2312
|
+
<title>Authorization Failed - Lindo CLI</title>
|
|
2313
|
+
<style>
|
|
2314
|
+
body {
|
|
2315
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
2316
|
+
display: flex;
|
|
2317
|
+
justify-content: center;
|
|
2318
|
+
align-items: center;
|
|
2319
|
+
min-height: 100vh;
|
|
2320
|
+
margin: 0;
|
|
2321
|
+
background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
|
|
2322
|
+
color: #fff;
|
|
2323
|
+
}
|
|
2324
|
+
.container {
|
|
2325
|
+
text-align: center;
|
|
2326
|
+
padding: 2rem;
|
|
2327
|
+
background: rgba(255, 255, 255, 0.1);
|
|
2328
|
+
border-radius: 16px;
|
|
2329
|
+
backdrop-filter: blur(10px);
|
|
2330
|
+
max-width: 400px;
|
|
2331
|
+
}
|
|
2332
|
+
.icon {
|
|
2333
|
+
font-size: 4rem;
|
|
2334
|
+
margin-bottom: 1rem;
|
|
2335
|
+
}
|
|
2336
|
+
h1 {
|
|
2337
|
+
margin: 0 0 1rem 0;
|
|
2338
|
+
font-size: 1.5rem;
|
|
2339
|
+
}
|
|
2340
|
+
p {
|
|
2341
|
+
margin: 0;
|
|
2342
|
+
opacity: 0.9;
|
|
2343
|
+
}
|
|
2344
|
+
.error-message {
|
|
2345
|
+
margin-top: 1rem;
|
|
2346
|
+
padding: 1rem;
|
|
2347
|
+
background: rgba(0, 0, 0, 0.2);
|
|
2348
|
+
border-radius: 8px;
|
|
2349
|
+
font-family: monospace;
|
|
2350
|
+
font-size: 0.9rem;
|
|
2351
|
+
}
|
|
2352
|
+
</style>
|
|
2353
|
+
</head>
|
|
2354
|
+
<body>
|
|
2355
|
+
<div class="container">
|
|
2356
|
+
<div class="icon">\u2717</div>
|
|
2357
|
+
<h1>Authorization Failed</h1>
|
|
2358
|
+
<p>Something went wrong during authorization.</p>
|
|
2359
|
+
<div class="error-message">${escapedMessage}</div>
|
|
2360
|
+
</div>
|
|
2361
|
+
</body>
|
|
2362
|
+
</html>`;
|
|
2363
|
+
}
|
|
2364
|
+
function generate404Html() {
|
|
2365
|
+
return `<!DOCTYPE html>
|
|
2366
|
+
<html lang="en">
|
|
2367
|
+
<head>
|
|
2368
|
+
<meta charset="UTF-8">
|
|
2369
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2370
|
+
<title>Not Found - Lindo CLI</title>
|
|
2371
|
+
<style>
|
|
2372
|
+
body {
|
|
2373
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
2374
|
+
display: flex;
|
|
2375
|
+
justify-content: center;
|
|
2376
|
+
align-items: center;
|
|
2377
|
+
min-height: 100vh;
|
|
2378
|
+
margin: 0;
|
|
2379
|
+
background: #f5f5f5;
|
|
2380
|
+
color: #333;
|
|
2381
|
+
}
|
|
2382
|
+
.container {
|
|
2383
|
+
text-align: center;
|
|
2384
|
+
padding: 2rem;
|
|
2385
|
+
}
|
|
2386
|
+
h1 {
|
|
2387
|
+
font-size: 3rem;
|
|
2388
|
+
margin: 0 0 1rem 0;
|
|
2389
|
+
color: #999;
|
|
2390
|
+
}
|
|
2391
|
+
p {
|
|
2392
|
+
margin: 0;
|
|
2393
|
+
color: #666;
|
|
2394
|
+
}
|
|
2395
|
+
</style>
|
|
2396
|
+
</head>
|
|
2397
|
+
<body>
|
|
2398
|
+
<div class="container">
|
|
2399
|
+
<h1>404</h1>
|
|
2400
|
+
<p>This endpoint is not available.</p>
|
|
2401
|
+
</div>
|
|
2402
|
+
</body>
|
|
2403
|
+
</html>`;
|
|
2404
|
+
}
|
|
2405
|
+
function createCallbackServer() {
|
|
2406
|
+
let server = null;
|
|
2407
|
+
let pendingCallback = null;
|
|
2408
|
+
function handleRequest(req, res) {
|
|
2409
|
+
const url = req.url || "/";
|
|
2410
|
+
const method = req.method || "GET";
|
|
2411
|
+
const isCallbackPath = url === "/callback" || url.startsWith("/callback?");
|
|
2412
|
+
if (method !== "GET" || !isCallbackPath) {
|
|
2413
|
+
res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
|
|
2414
|
+
res.end(generate404Html());
|
|
2415
|
+
return;
|
|
2416
|
+
}
|
|
2417
|
+
const params = parseCallbackParams(url);
|
|
2418
|
+
if (!pendingCallback) {
|
|
2419
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
2420
|
+
res.end(generateErrorHtml("No pending authorization request"));
|
|
2421
|
+
return;
|
|
2422
|
+
}
|
|
2423
|
+
const { expectedState, resolve: resolve4, timeoutId } = pendingCallback;
|
|
2424
|
+
clearTimeout(timeoutId);
|
|
2425
|
+
pendingCallback = null;
|
|
2426
|
+
if (params.error) {
|
|
2427
|
+
const errorMessage = params.message || params.error;
|
|
2428
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
2429
|
+
res.end(generateErrorHtml(errorMessage));
|
|
2430
|
+
resolve4({
|
|
2431
|
+
success: false,
|
|
2432
|
+
error: errorMessage
|
|
2433
|
+
});
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
if (!params.state || !verifyState(params.state, expectedState)) {
|
|
2437
|
+
const errorMessage = "State token mismatch - possible CSRF attack";
|
|
2438
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
2439
|
+
res.end(generateErrorHtml(errorMessage));
|
|
2440
|
+
resolve4({
|
|
2441
|
+
success: false,
|
|
2442
|
+
error: errorMessage
|
|
2443
|
+
});
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
if (!params.key) {
|
|
2447
|
+
const errorMessage = "No API key received";
|
|
2448
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
2449
|
+
res.end(generateErrorHtml(errorMessage));
|
|
2450
|
+
resolve4({
|
|
2451
|
+
success: false,
|
|
2452
|
+
error: errorMessage
|
|
2453
|
+
});
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
2457
|
+
res.end(generateSuccessHtml());
|
|
2458
|
+
resolve4({
|
|
2459
|
+
success: true,
|
|
2460
|
+
apiKey: params.key
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
2463
|
+
return {
|
|
2464
|
+
async start() {
|
|
2465
|
+
return new Promise((resolve4, reject) => {
|
|
2466
|
+
server = http__namespace.createServer(handleRequest);
|
|
2467
|
+
server.on("error", (err) => {
|
|
2468
|
+
reject(new Error(`Failed to start callback server: ${err.message}`));
|
|
2469
|
+
});
|
|
2470
|
+
server.listen(0, "127.0.0.1", () => {
|
|
2471
|
+
const address = server.address();
|
|
2472
|
+
if (!address || typeof address === "string") {
|
|
2473
|
+
reject(new Error("Failed to get server address"));
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
const port = address.port;
|
|
2477
|
+
const url = `http://127.0.0.1:${port}/callback`;
|
|
2478
|
+
resolve4({ port, url });
|
|
2479
|
+
});
|
|
2480
|
+
});
|
|
2481
|
+
},
|
|
2482
|
+
waitForCallback(state, timeoutMs) {
|
|
2483
|
+
return new Promise((resolve4) => {
|
|
2484
|
+
const timeoutId = setTimeout(() => {
|
|
2485
|
+
if (pendingCallback) {
|
|
2486
|
+
pendingCallback = null;
|
|
2487
|
+
resolve4({
|
|
2488
|
+
success: false,
|
|
2489
|
+
error: "Login timed out waiting for authorization"
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
}, timeoutMs);
|
|
2493
|
+
pendingCallback = {
|
|
2494
|
+
expectedState: state,
|
|
2495
|
+
resolve: resolve4,
|
|
2496
|
+
timeoutId
|
|
2497
|
+
};
|
|
2498
|
+
});
|
|
2499
|
+
},
|
|
2500
|
+
async stop() {
|
|
2501
|
+
return new Promise((resolve4) => {
|
|
2502
|
+
if (pendingCallback) {
|
|
2503
|
+
clearTimeout(pendingCallback.timeoutId);
|
|
2504
|
+
pendingCallback = null;
|
|
2505
|
+
}
|
|
2506
|
+
if (server) {
|
|
2507
|
+
server.close(() => {
|
|
2508
|
+
server = null;
|
|
2509
|
+
resolve4();
|
|
2510
|
+
});
|
|
2511
|
+
} else {
|
|
2512
|
+
resolve4();
|
|
2513
|
+
}
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
// src/commands/login.ts
|
|
2520
|
+
var WEBAPP_BASE_URL = "https://app.lindo.ai";
|
|
2521
|
+
var DEFAULT_TIMEOUT_SECONDS = 120;
|
|
2522
|
+
function buildAuthorizationUrl(state, callbackPort) {
|
|
2523
|
+
const callbackUrl = `http://127.0.0.1:${callbackPort}/callback`;
|
|
2524
|
+
const params = new URLSearchParams({
|
|
2525
|
+
state,
|
|
2526
|
+
callback_url: callbackUrl
|
|
2527
|
+
});
|
|
2528
|
+
return `${WEBAPP_BASE_URL}/cli/authorize?${params.toString()}`;
|
|
2529
|
+
}
|
|
2530
|
+
function createLoginCommand() {
|
|
2531
|
+
const login = new commander.Command("login").description("Authenticate via browser to configure your API key").option(
|
|
2532
|
+
"-t, --timeout <seconds>",
|
|
2533
|
+
"Timeout in seconds for the login flow",
|
|
2534
|
+
String(DEFAULT_TIMEOUT_SECONDS)
|
|
2535
|
+
).option(
|
|
2536
|
+
"--no-browser",
|
|
2537
|
+
"Display the authorization URL without opening the browser"
|
|
2538
|
+
).action(async (options) => {
|
|
2539
|
+
const timeoutSeconds = parseInt(options.timeout, 10);
|
|
2540
|
+
const timeoutMs = timeoutSeconds * 1e3;
|
|
2541
|
+
const shouldOpenBrowser = options.browser;
|
|
2542
|
+
const server = createCallbackServer();
|
|
2543
|
+
let serverStarted = false;
|
|
2544
|
+
try {
|
|
2545
|
+
info("Starting authentication flow...");
|
|
2546
|
+
const { port } = await server.start();
|
|
2547
|
+
serverStarted = true;
|
|
2548
|
+
const state = generateState();
|
|
2549
|
+
const authUrl = buildAuthorizationUrl(state, port);
|
|
2550
|
+
if (shouldOpenBrowser) {
|
|
2551
|
+
info("Opening browser for authorization...");
|
|
2552
|
+
const browserOpened = await openBrowser(authUrl);
|
|
2553
|
+
if (!browserOpened) {
|
|
2554
|
+
info("Could not open browser automatically.");
|
|
2555
|
+
console.log("\nPlease open this URL in your browser:");
|
|
2556
|
+
console.log(`
|
|
2557
|
+
${authUrl}
|
|
2558
|
+
`);
|
|
2559
|
+
}
|
|
2560
|
+
} else {
|
|
2561
|
+
console.log("\nPlease open this URL in your browser:");
|
|
2562
|
+
console.log(`
|
|
2563
|
+
${authUrl}
|
|
2564
|
+
`);
|
|
2565
|
+
}
|
|
2566
|
+
info(`Waiting for authorization (timeout: ${timeoutSeconds}s)...`);
|
|
2567
|
+
const result = await server.waitForCallback(state, timeoutMs);
|
|
2568
|
+
if (result.success && result.apiKey) {
|
|
2569
|
+
saveApiKey(result.apiKey);
|
|
2570
|
+
success("Successfully authenticated!");
|
|
2571
|
+
info(`API key saved to: ${getConfigPath()}`);
|
|
2572
|
+
} else {
|
|
2573
|
+
error(result.error || "Authentication failed");
|
|
2574
|
+
process.exit(1);
|
|
2575
|
+
}
|
|
2576
|
+
} catch (err) {
|
|
2577
|
+
error(err instanceof Error ? err.message : "An unexpected error occurred");
|
|
2578
|
+
process.exit(1);
|
|
2579
|
+
} finally {
|
|
2580
|
+
if (serverStarted) {
|
|
2581
|
+
await server.stop();
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
});
|
|
2585
|
+
return login;
|
|
2586
|
+
}
|
|
2587
|
+
var OPENCODE_SKILLS_DIR = path3__namespace.join(os2__namespace.homedir(), ".config", "opencode", "skills", "lindoai");
|
|
2588
|
+
var SKILL_FILE_PATH = path3__namespace.join(OPENCODE_SKILLS_DIR, "SKILL.md");
|
|
2589
|
+
var SKILL_FILE_CONTENT = `---
|
|
2590
|
+
name: lindoai
|
|
2591
|
+
description: Lindo AI CLI - Command-line interface for the Lindo API
|
|
2592
|
+
---
|
|
2593
|
+
|
|
2594
|
+
# Lindo AI CLI
|
|
2595
|
+
|
|
2596
|
+
The \`lindoai\` CLI provides commands for interacting with the Lindo API, including website management, page editing, blog management, and more.
|
|
2597
|
+
|
|
2598
|
+
## Authentication
|
|
2599
|
+
|
|
2600
|
+
### Login via Browser
|
|
2601
|
+
\`\`\`bash
|
|
2602
|
+
lindoai login
|
|
2603
|
+
\`\`\`
|
|
2604
|
+
Opens your browser to authenticate and automatically configures your API key.
|
|
2605
|
+
|
|
2606
|
+
Options:
|
|
2607
|
+
- \`--timeout <seconds>\` - Timeout for the login flow (default: 120)
|
|
2608
|
+
- \`--no-browser\` - Display the authorization URL without opening the browser
|
|
2609
|
+
|
|
2610
|
+
### Manual Configuration
|
|
2611
|
+
\`\`\`bash
|
|
2612
|
+
lindoai config set apiKey <your-api-key>
|
|
2613
|
+
\`\`\`
|
|
2614
|
+
|
|
2615
|
+
## Website Management
|
|
2616
|
+
|
|
2617
|
+
### List Websites
|
|
2618
|
+
\`\`\`bash
|
|
2619
|
+
lindoai websites list
|
|
2620
|
+
\`\`\`
|
|
2621
|
+
|
|
2622
|
+
### Get Website Details
|
|
2623
|
+
\`\`\`bash
|
|
2624
|
+
lindoai websites get <website_id>
|
|
2625
|
+
\`\`\`
|
|
2626
|
+
|
|
2627
|
+
## Page Management
|
|
2628
|
+
|
|
2629
|
+
### List Pages
|
|
2630
|
+
\`\`\`bash
|
|
2631
|
+
lindoai pages list <website_id>
|
|
2632
|
+
\`\`\`
|
|
2633
|
+
|
|
2634
|
+
### Get Page Details
|
|
2635
|
+
\`\`\`bash
|
|
2636
|
+
lindoai pages get <website_id> <page_id>
|
|
2637
|
+
\`\`\`
|
|
2638
|
+
|
|
2639
|
+
### Live Preview Editing Workflow
|
|
2640
|
+
|
|
2641
|
+
1. **Start editing with live preview:**
|
|
2642
|
+
\`\`\`bash
|
|
2643
|
+
lindoai pages edit <website_id> <page_id> --background
|
|
2644
|
+
\`\`\`
|
|
2645
|
+
This fetches the page HTML, saves it locally, starts a preview server, and opens your browser.
|
|
2646
|
+
|
|
2647
|
+
Options:
|
|
2648
|
+
- \`--file <path>\` - Save HTML to a specific path (default: ./page.html)
|
|
2649
|
+
- \`--background\` - Run the preview server in the background
|
|
2650
|
+
|
|
2651
|
+
2. **Edit the HTML file** in your editor. The browser will automatically refresh when you save.
|
|
2652
|
+
|
|
2653
|
+
3. **Update the page** when you're done:
|
|
2654
|
+
\`\`\`bash
|
|
2655
|
+
lindoai pages update <website_id> <page_id> --html-file ./page.html
|
|
2656
|
+
\`\`\`
|
|
2657
|
+
|
|
2658
|
+
4. **Stop the preview server:**
|
|
2659
|
+
\`\`\`bash
|
|
2660
|
+
lindoai pages stop-preview
|
|
2661
|
+
\`\`\`
|
|
2662
|
+
|
|
2663
|
+
## Blog Management
|
|
2664
|
+
|
|
2665
|
+
### List Blogs
|
|
2666
|
+
\`\`\`bash
|
|
2667
|
+
lindoai blogs list <website_id>
|
|
2668
|
+
\`\`\`
|
|
2669
|
+
|
|
2670
|
+
### Get Blog Details
|
|
2671
|
+
\`\`\`bash
|
|
2672
|
+
lindoai blogs get <website_id> <blog_id>
|
|
2673
|
+
\`\`\`
|
|
2674
|
+
|
|
2675
|
+
### Live Preview Editing Workflow
|
|
2676
|
+
|
|
2677
|
+
1. **Start editing with live preview:**
|
|
2678
|
+
\`\`\`bash
|
|
2679
|
+
lindoai blogs edit <website_id> <blog_id> --background
|
|
2680
|
+
\`\`\`
|
|
2681
|
+
|
|
2682
|
+
Options:
|
|
2683
|
+
- \`--file <path>\` - Save HTML to a specific path (default: ./blog.html)
|
|
2684
|
+
- \`--background\` - Run the preview server in the background
|
|
2685
|
+
|
|
2686
|
+
2. **Edit the HTML file** in your editor. The browser will automatically refresh when you save.
|
|
2687
|
+
|
|
2688
|
+
3. **Update the blog** when you're done:
|
|
2689
|
+
\`\`\`bash
|
|
2690
|
+
lindoai blogs update <website_id> <blog_id> --html-file ./blog.html
|
|
2691
|
+
\`\`\`
|
|
2692
|
+
|
|
2693
|
+
4. **Stop the preview server:**
|
|
2694
|
+
\`\`\`bash
|
|
2695
|
+
lindoai blogs stop-preview
|
|
2696
|
+
\`\`\`
|
|
2697
|
+
|
|
2698
|
+
## AI Agents
|
|
2699
|
+
|
|
2700
|
+
### Run an Agent
|
|
2701
|
+
\`\`\`bash
|
|
2702
|
+
lindoai agents run <agent_id> --input '{"prompt": "Hello!"}'
|
|
2703
|
+
\`\`\`
|
|
2704
|
+
|
|
2705
|
+
Options:
|
|
2706
|
+
- \`--input <json>\` - Input data as JSON string
|
|
2707
|
+
- \`--stream\` - Stream the response
|
|
2708
|
+
- \`--format <format>\` - Output format (json, table)
|
|
2709
|
+
|
|
2710
|
+
## Workflows
|
|
2711
|
+
|
|
2712
|
+
### Start a Workflow
|
|
2713
|
+
\`\`\`bash
|
|
2714
|
+
lindoai workflows start <workflow_id> --params '{"key": "value"}'
|
|
2715
|
+
\`\`\`
|
|
2716
|
+
|
|
2717
|
+
### Get Workflow Status
|
|
2718
|
+
\`\`\`bash
|
|
2719
|
+
lindoai workflows status <instance_id>
|
|
2720
|
+
\`\`\`
|
|
2721
|
+
|
|
2722
|
+
## Workspace
|
|
2723
|
+
|
|
2724
|
+
### Get Workspace Credits
|
|
2725
|
+
\`\`\`bash
|
|
2726
|
+
lindoai workspace credits
|
|
2727
|
+
\`\`\`
|
|
2728
|
+
|
|
2729
|
+
## Analytics
|
|
2730
|
+
|
|
2731
|
+
### Get Workspace Analytics
|
|
2732
|
+
\`\`\`bash
|
|
2733
|
+
lindoai analytics workspace --from 2024-01-01 --to 2024-01-31
|
|
2734
|
+
\`\`\`
|
|
2735
|
+
|
|
2736
|
+
## Configuration
|
|
2737
|
+
|
|
2738
|
+
### View Configuration
|
|
2739
|
+
\`\`\`bash
|
|
2740
|
+
lindoai config get apiKey
|
|
2741
|
+
lindoai config get baseUrl
|
|
2742
|
+
\`\`\`
|
|
2743
|
+
|
|
2744
|
+
### Set Configuration
|
|
2745
|
+
\`\`\`bash
|
|
2746
|
+
lindoai config set apiKey <value>
|
|
2747
|
+
lindoai config set baseUrl <value>
|
|
2748
|
+
\`\`\`
|
|
2749
|
+
|
|
2750
|
+
## Tips for AI Agents
|
|
2751
|
+
|
|
2752
|
+
1. **Always authenticate first** using \`lindoai login\` or by setting the API key.
|
|
2753
|
+
2. **Use the live preview workflow** for editing pages and blogs - it provides instant visual feedback.
|
|
2754
|
+
3. **Run preview servers in background mode** (\`--background\`) to keep the terminal available.
|
|
2755
|
+
4. **Remember to stop preview servers** when done editing with \`stop-preview\`.
|
|
2756
|
+
5. **Use JSON format** (\`--format json\`) when you need to parse command output programmatically.
|
|
2757
|
+
`;
|
|
2758
|
+
function isOpenCodeInstalled() {
|
|
2759
|
+
try {
|
|
2760
|
+
const command = process.platform === "win32" ? "where opencode" : "which opencode";
|
|
2761
|
+
child_process.execSync(command, { stdio: "ignore" });
|
|
2762
|
+
return true;
|
|
2763
|
+
} catch {
|
|
2764
|
+
return false;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
function installOpenCode() {
|
|
2768
|
+
try {
|
|
2769
|
+
info("Installing OpenCode...");
|
|
2770
|
+
child_process.execSync("npm install -g opencode-ai@latest", { stdio: "inherit" });
|
|
2771
|
+
return true;
|
|
2772
|
+
} catch {
|
|
2773
|
+
return false;
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
function ensureSkillInstalled() {
|
|
2777
|
+
if (!fs3__namespace.existsSync(OPENCODE_SKILLS_DIR)) {
|
|
2778
|
+
fs3__namespace.mkdirSync(OPENCODE_SKILLS_DIR, { recursive: true });
|
|
2779
|
+
}
|
|
2780
|
+
fs3__namespace.writeFileSync(SKILL_FILE_PATH, SKILL_FILE_CONTENT, "utf-8");
|
|
2781
|
+
}
|
|
2782
|
+
function createAgentCommand() {
|
|
2783
|
+
const agent = new commander.Command("agent").description("Launch an AI agent that understands all CLI commands").option("--install", "Install OpenCode if not already installed").option("--model <model>", "Specify the model to use with OpenCode").action(async (options) => {
|
|
2784
|
+
if (!isOpenCodeInstalled()) {
|
|
2785
|
+
if (options.install) {
|
|
2786
|
+
const installed = installOpenCode();
|
|
2787
|
+
if (!installed) {
|
|
2788
|
+
error("Failed to install OpenCode");
|
|
2789
|
+
info("Please install manually: npm install -g opencode-ai@latest");
|
|
2790
|
+
process.exit(1);
|
|
2791
|
+
}
|
|
2792
|
+
success("OpenCode installed successfully");
|
|
2793
|
+
} else {
|
|
2794
|
+
error("OpenCode is not installed");
|
|
2795
|
+
info("");
|
|
2796
|
+
info("To install OpenCode, run one of the following:");
|
|
2797
|
+
info(" lindoai agent --install");
|
|
2798
|
+
info(" npm install -g opencode-ai@latest");
|
|
2799
|
+
process.exit(1);
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
try {
|
|
2803
|
+
ensureSkillInstalled();
|
|
2804
|
+
info(`Skill file installed to: ${SKILL_FILE_PATH}`);
|
|
2805
|
+
} catch (err) {
|
|
2806
|
+
error(`Failed to install skill file: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
2807
|
+
process.exit(1);
|
|
2808
|
+
}
|
|
2809
|
+
const args = [];
|
|
2810
|
+
if (options.model) {
|
|
2811
|
+
args.push("--model", options.model);
|
|
2812
|
+
}
|
|
2813
|
+
info("Launching OpenCode...");
|
|
2814
|
+
const opencode = child_process.spawn("opencode", args, {
|
|
2815
|
+
stdio: "inherit",
|
|
2816
|
+
shell: true
|
|
2817
|
+
});
|
|
2818
|
+
opencode.on("error", (err) => {
|
|
2819
|
+
error(`Failed to launch OpenCode: ${err.message}`);
|
|
2820
|
+
process.exit(1);
|
|
2821
|
+
});
|
|
2822
|
+
opencode.on("close", (code) => {
|
|
2823
|
+
process.exit(code ?? 0);
|
|
2824
|
+
});
|
|
2825
|
+
});
|
|
2826
|
+
return agent;
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
// src/index.ts
|
|
2830
|
+
var VERSION = "1.0.0";
|
|
2831
|
+
function createProgram() {
|
|
2832
|
+
const program = new commander.Command();
|
|
2833
|
+
program.name("lindo").description("Command-line interface for the Lindo API").version(VERSION, "-v, --version", "Output the current version").helpOption("-h, --help", "Display help for command");
|
|
2834
|
+
program.addCommand(createConfigCommand());
|
|
2835
|
+
program.addCommand(createAgentsCommand());
|
|
2836
|
+
program.addCommand(createWorkflowsCommand());
|
|
2837
|
+
program.addCommand(createWorkspaceCommand());
|
|
2838
|
+
program.addCommand(createAnalyticsCommand());
|
|
2839
|
+
program.addCommand(createClientsCommand());
|
|
2840
|
+
program.addCommand(createWebsitesCommand());
|
|
2841
|
+
program.addCommand(createPagesCommand());
|
|
2842
|
+
program.addCommand(createBlogsCommand());
|
|
2843
|
+
program.addCommand(createLoginCommand());
|
|
2844
|
+
program.addCommand(createAgentCommand());
|
|
2845
|
+
program.exitOverride((err) => {
|
|
2846
|
+
if (err.code === "commander.help") {
|
|
2847
|
+
process.exit(0);
|
|
2848
|
+
}
|
|
2849
|
+
if (err.code === "commander.version") {
|
|
2850
|
+
process.exit(0);
|
|
2851
|
+
}
|
|
2852
|
+
if (err.code === "commander.missingArgument") {
|
|
2853
|
+
error(err.message);
|
|
2854
|
+
process.exit(1);
|
|
2855
|
+
}
|
|
2856
|
+
if (err.code === "commander.unknownCommand") {
|
|
2857
|
+
error(err.message);
|
|
2858
|
+
info('Run "lindo --help" for available commands');
|
|
2859
|
+
process.exit(1);
|
|
2860
|
+
}
|
|
2861
|
+
throw err;
|
|
2862
|
+
});
|
|
2863
|
+
return program;
|
|
2864
|
+
}
|
|
2865
|
+
function handleAuthenticationError(_err) {
|
|
2866
|
+
error("Authentication failed");
|
|
2867
|
+
info("");
|
|
2868
|
+
info("Your API key may be invalid or expired.");
|
|
2869
|
+
info("");
|
|
2870
|
+
info("To configure your API key:");
|
|
2871
|
+
info(" 1. Run: lindo config set apiKey <your-api-key>");
|
|
2872
|
+
info(" 2. Or set the LINDO_API_KEY environment variable");
|
|
2873
|
+
info("");
|
|
2874
|
+
info("To get an API key:");
|
|
2875
|
+
info(" Visit https://app.lindo.ai/settings/api-keys");
|
|
2876
|
+
}
|
|
2877
|
+
async function main() {
|
|
2878
|
+
const program = createProgram();
|
|
2879
|
+
try {
|
|
2880
|
+
await program.parseAsync(process.argv);
|
|
2881
|
+
} catch (err) {
|
|
2882
|
+
if (err instanceof lindoai.AuthenticationError) {
|
|
2883
|
+
handleAuthenticationError();
|
|
2884
|
+
process.exit(1);
|
|
2885
|
+
}
|
|
2886
|
+
throw err;
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
main().catch((err) => {
|
|
2890
|
+
if (err instanceof Error) {
|
|
2891
|
+
error(`Unexpected error: ${err.message}`);
|
|
2892
|
+
} else {
|
|
2893
|
+
error("An unexpected error occurred");
|
|
2894
|
+
}
|
|
2895
|
+
process.exit(1);
|
|
2896
|
+
});
|
|
2897
|
+
|
|
2898
|
+
exports.createProgram = createProgram;
|
|
2899
|
+
//# sourceMappingURL=index.cjs.map
|
|
2900
|
+
//# sourceMappingURL=index.cjs.map
|