@senso-ai/cli 0.2.2
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/LICENSE +661 -0
- package/README.md +422 -0
- package/dist/cli.js +1503 -0
- package/package.json +53 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1503 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/lib/version.ts
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { dirname, join } from "path";
|
|
9
|
+
import { readFileSync } from "fs";
|
|
10
|
+
var _version = "0.0.0";
|
|
11
|
+
try {
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
for (const dir of [__dirname, join(__dirname, ".."), join(__dirname, "../..")]) {
|
|
14
|
+
try {
|
|
15
|
+
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf-8"));
|
|
16
|
+
if (pkg.version) {
|
|
17
|
+
_version = pkg.version;
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
var version = _version;
|
|
26
|
+
|
|
27
|
+
// src/utils/branding.ts
|
|
28
|
+
import figlet from "figlet";
|
|
29
|
+
import gradient from "gradient-string";
|
|
30
|
+
import boxen from "boxen";
|
|
31
|
+
import pc from "picocolors";
|
|
32
|
+
var sensoGradient = gradient(["#0D9373", "#07C983"]);
|
|
33
|
+
function banner() {
|
|
34
|
+
const ascii = figlet.textSync("SENSO", {
|
|
35
|
+
font: "ANSI Shadow",
|
|
36
|
+
horizontalLayout: "fitted"
|
|
37
|
+
});
|
|
38
|
+
const branded = sensoGradient.multiline(ascii);
|
|
39
|
+
const tagline = pc.dim(" Infrastructure for the Agentic Web");
|
|
40
|
+
console.log(
|
|
41
|
+
boxen(`${branded}
|
|
42
|
+
${tagline}`, {
|
|
43
|
+
padding: 1,
|
|
44
|
+
margin: { top: 1, bottom: 1, left: 2, right: 2 },
|
|
45
|
+
borderStyle: "round",
|
|
46
|
+
borderColor: "green"
|
|
47
|
+
})
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
function miniBanner() {
|
|
51
|
+
const title = sensoGradient("Senso CLI");
|
|
52
|
+
console.log(`
|
|
53
|
+
${title} ${pc.dim(`v${version}`)}
|
|
54
|
+
`);
|
|
55
|
+
}
|
|
56
|
+
function updateBox(current, latest) {
|
|
57
|
+
const msg = [
|
|
58
|
+
`${pc.yellow("Update available!")} ${pc.dim(current)} \u2192 ${pc.green(latest)}`,
|
|
59
|
+
"",
|
|
60
|
+
`Run ${pc.cyan("senso update")} to update`
|
|
61
|
+
].join("\n");
|
|
62
|
+
console.error(
|
|
63
|
+
boxen(msg, {
|
|
64
|
+
padding: 1,
|
|
65
|
+
margin: { top: 1, bottom: 0, left: 2, right: 2 },
|
|
66
|
+
borderStyle: "round",
|
|
67
|
+
borderColor: "yellow"
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/utils/updater.ts
|
|
73
|
+
import semver from "semver";
|
|
74
|
+
|
|
75
|
+
// src/lib/config.ts
|
|
76
|
+
import { readFileSync as readFileSync2, writeFileSync, mkdirSync, unlinkSync } from "fs";
|
|
77
|
+
import { join as join2 } from "path";
|
|
78
|
+
import envPaths from "env-paths";
|
|
79
|
+
var paths = envPaths("senso", { suffix: "" });
|
|
80
|
+
var CONFIG_FILE = join2(paths.config, "config.json");
|
|
81
|
+
var DEFAULT_BASE_URL = "https://apiv2.senso.ai/api/v1";
|
|
82
|
+
function readConfig() {
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(readFileSync2(CONFIG_FILE, "utf-8"));
|
|
85
|
+
} catch {
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function writeConfig(config) {
|
|
90
|
+
mkdirSync(paths.config, { recursive: true });
|
|
91
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", {
|
|
92
|
+
mode: 384
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function updateConfig(partial) {
|
|
96
|
+
const config = readConfig();
|
|
97
|
+
writeConfig({ ...config, ...partial });
|
|
98
|
+
}
|
|
99
|
+
function clearConfig() {
|
|
100
|
+
try {
|
|
101
|
+
unlinkSync(CONFIG_FILE);
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function getApiKey(opts) {
|
|
106
|
+
return opts?.apiKey || process.env.SENSO_API_KEY || readConfig().apiKey;
|
|
107
|
+
}
|
|
108
|
+
function getBaseUrl(opts) {
|
|
109
|
+
return opts?.baseUrl || process.env.SENSO_BASE_URL || readConfig().baseUrl || DEFAULT_BASE_URL;
|
|
110
|
+
}
|
|
111
|
+
function getConfigPath() {
|
|
112
|
+
return CONFIG_FILE;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/utils/updater.ts
|
|
116
|
+
var GITHUB_REPO = "AI-Template-SDK/senso-user-cli";
|
|
117
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
118
|
+
async function checkForUpdate(quiet) {
|
|
119
|
+
if (process.env.SENSO_NO_UPDATE_CHECK === "1" || quiet) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const config = readConfig();
|
|
123
|
+
const lastCheck = config.lastUpdateCheck ? new Date(config.lastUpdateCheck).getTime() : 0;
|
|
124
|
+
if (Date.now() - lastCheck < CHECK_INTERVAL_MS) {
|
|
125
|
+
if (config.latestVersion && semver.gt(config.latestVersion, version)) {
|
|
126
|
+
updateBox(version, config.latestVersion);
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
const res = await fetch(
|
|
132
|
+
`https://api.github.com/repos/${GITHUB_REPO}/releases/latest`,
|
|
133
|
+
{
|
|
134
|
+
headers: { Accept: "application/vnd.github.v3+json" },
|
|
135
|
+
signal: AbortSignal.timeout(5e3)
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
if (!res.ok) return;
|
|
139
|
+
const release = await res.json();
|
|
140
|
+
const latest = release.tag_name.replace(/^v/, "");
|
|
141
|
+
updateConfig({
|
|
142
|
+
lastUpdateCheck: (/* @__PURE__ */ new Date()).toISOString(),
|
|
143
|
+
latestVersion: latest
|
|
144
|
+
});
|
|
145
|
+
if (semver.gt(latest, version)) {
|
|
146
|
+
updateBox(version, latest);
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function getLatestRelease() {
|
|
152
|
+
try {
|
|
153
|
+
const res = await fetch(
|
|
154
|
+
`https://api.github.com/repos/${GITHUB_REPO}/releases/latest`,
|
|
155
|
+
{
|
|
156
|
+
headers: { Accept: "application/vnd.github.v3+json" },
|
|
157
|
+
signal: AbortSignal.timeout(1e4)
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
if (!res.ok) return null;
|
|
161
|
+
return await res.json();
|
|
162
|
+
} catch {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/commands/auth.ts
|
|
168
|
+
import * as p from "@clack/prompts";
|
|
169
|
+
import pc3 from "picocolors";
|
|
170
|
+
|
|
171
|
+
// src/lib/api-client.ts
|
|
172
|
+
var ApiError = class extends Error {
|
|
173
|
+
constructor(status, statusText, body) {
|
|
174
|
+
const msg = typeof body === "object" && body && "error" in body ? body.error : statusText;
|
|
175
|
+
super(msg);
|
|
176
|
+
this.status = status;
|
|
177
|
+
this.statusText = statusText;
|
|
178
|
+
this.body = body;
|
|
179
|
+
this.name = "ApiError";
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
async function apiRequest(opts) {
|
|
183
|
+
const apiKey = getApiKey({ apiKey: opts.apiKey });
|
|
184
|
+
if (!apiKey) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
"No API key found. Run `senso login` or set SENSO_API_KEY."
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
const baseUrl = getBaseUrl({ baseUrl: opts.baseUrl });
|
|
190
|
+
const url = new URL(`${baseUrl}${opts.path}`);
|
|
191
|
+
if (opts.params) {
|
|
192
|
+
for (const [key, val] of Object.entries(opts.params)) {
|
|
193
|
+
if (val !== void 0) {
|
|
194
|
+
url.searchParams.set(key, String(val));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const controller = new AbortController();
|
|
199
|
+
const timeout = setTimeout(() => controller.abort(), 3e4);
|
|
200
|
+
try {
|
|
201
|
+
const res = await fetch(url.toString(), {
|
|
202
|
+
method: opts.method || "GET",
|
|
203
|
+
headers: {
|
|
204
|
+
"X-API-Key": apiKey,
|
|
205
|
+
Accept: "application/json",
|
|
206
|
+
...opts.body ? { "Content-Type": "application/json" } : {},
|
|
207
|
+
"User-Agent": `senso-cli/${version}`
|
|
208
|
+
},
|
|
209
|
+
body: opts.body ? JSON.stringify(opts.body) : void 0,
|
|
210
|
+
signal: controller.signal
|
|
211
|
+
});
|
|
212
|
+
if (!res.ok) {
|
|
213
|
+
let body;
|
|
214
|
+
try {
|
|
215
|
+
body = await res.json();
|
|
216
|
+
} catch {
|
|
217
|
+
body = await res.text();
|
|
218
|
+
}
|
|
219
|
+
throw new ApiError(res.status, res.statusText, body);
|
|
220
|
+
}
|
|
221
|
+
if (res.status === 204) {
|
|
222
|
+
return void 0;
|
|
223
|
+
}
|
|
224
|
+
return await res.json();
|
|
225
|
+
} finally {
|
|
226
|
+
clearTimeout(timeout);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function formatApiError(err) {
|
|
230
|
+
if (err instanceof ApiError) {
|
|
231
|
+
switch (err.status) {
|
|
232
|
+
case 401:
|
|
233
|
+
return "Authentication failed. Run `senso login` to update your API key.";
|
|
234
|
+
case 403:
|
|
235
|
+
return "Permission denied. Check your API key permissions.";
|
|
236
|
+
case 404:
|
|
237
|
+
return "Resource not found.";
|
|
238
|
+
case 409:
|
|
239
|
+
return `Conflict: ${err.message}`;
|
|
240
|
+
default:
|
|
241
|
+
if (err.status >= 500) {
|
|
242
|
+
return "Server error. Try again later.";
|
|
243
|
+
}
|
|
244
|
+
return `API error (${err.status}): ${err.message}`;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (err instanceof Error) {
|
|
248
|
+
if (err.name === "AbortError") {
|
|
249
|
+
return "Request timed out. Try again later.";
|
|
250
|
+
}
|
|
251
|
+
if (err.message.includes("fetch failed") || err.message.includes("ECONNREFUSED")) {
|
|
252
|
+
return "Could not connect to Senso API. Check your internet connection.";
|
|
253
|
+
}
|
|
254
|
+
return err.message;
|
|
255
|
+
}
|
|
256
|
+
return String(err);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/utils/logger.ts
|
|
260
|
+
import pc2 from "picocolors";
|
|
261
|
+
function success(msg) {
|
|
262
|
+
console.log(` ${pc2.green("\u2713")} ${msg}`);
|
|
263
|
+
}
|
|
264
|
+
function error(msg) {
|
|
265
|
+
console.error(` ${pc2.red("\u2717")} ${msg}`);
|
|
266
|
+
}
|
|
267
|
+
function warn(msg) {
|
|
268
|
+
console.error(` ${pc2.yellow("!")} ${msg}`);
|
|
269
|
+
}
|
|
270
|
+
function info(msg) {
|
|
271
|
+
console.log(` ${pc2.cyan("\u2139")} ${msg}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/commands/auth.ts
|
|
275
|
+
async function verifyApiKey(apiKey, baseUrl) {
|
|
276
|
+
return apiRequest({
|
|
277
|
+
path: "/org/me",
|
|
278
|
+
apiKey,
|
|
279
|
+
baseUrl
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
function registerAuthCommands(program2) {
|
|
283
|
+
program2.command("login").description("Save API key to config (validates via GET /org/me)").action(async () => {
|
|
284
|
+
const opts = program2.opts();
|
|
285
|
+
banner();
|
|
286
|
+
console.log(` ${pc3.bold("Welcome to Senso CLI!")}
|
|
287
|
+
`);
|
|
288
|
+
console.log(` ${pc3.dim("1.")} Go to ${pc3.cyan("https://docs.senso.ai")} to create an account`);
|
|
289
|
+
console.log(` ${pc3.dim("2.")} Generate an API key from your dashboard
|
|
290
|
+
`);
|
|
291
|
+
const result = await p.text({
|
|
292
|
+
message: "Paste your API key:",
|
|
293
|
+
placeholder: "tgr_...",
|
|
294
|
+
validate: (val) => {
|
|
295
|
+
if (!val || val.trim().length < 4) return "API key is required";
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
if (p.isCancel(result)) {
|
|
299
|
+
p.cancel("Login cancelled.");
|
|
300
|
+
process.exit(0);
|
|
301
|
+
}
|
|
302
|
+
const apiKey = result.trim();
|
|
303
|
+
const spin = p.spinner();
|
|
304
|
+
spin.start("Verifying API key...");
|
|
305
|
+
try {
|
|
306
|
+
const org = await verifyApiKey(apiKey, opts.baseUrl);
|
|
307
|
+
spin.stop("API key verified");
|
|
308
|
+
writeConfig({
|
|
309
|
+
apiKey,
|
|
310
|
+
...opts.baseUrl ? { baseUrl: opts.baseUrl } : {},
|
|
311
|
+
orgName: org.name,
|
|
312
|
+
orgId: org.org_id,
|
|
313
|
+
orgSlug: org.slug,
|
|
314
|
+
isFreeTier: org.is_free_tier
|
|
315
|
+
});
|
|
316
|
+
success(`Authenticated as ${pc3.bold(`"${org.name}"`)} (${pc3.dim(org.org_id)})`);
|
|
317
|
+
success(`Config saved to ${pc3.dim(getConfigPath())}`);
|
|
318
|
+
console.log();
|
|
319
|
+
} catch (err) {
|
|
320
|
+
spin.stop("Verification failed");
|
|
321
|
+
error(formatApiError(err));
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
program2.command("logout").description("Remove stored credentials").action(() => {
|
|
326
|
+
clearConfig();
|
|
327
|
+
success("Credentials removed.");
|
|
328
|
+
});
|
|
329
|
+
program2.command("whoami").description("Show current auth status and org info").action(async () => {
|
|
330
|
+
const opts = program2.opts();
|
|
331
|
+
const apiKey = getApiKey({ apiKey: opts.apiKey });
|
|
332
|
+
if (!apiKey) {
|
|
333
|
+
error("Not logged in. Run `senso login` to authenticate.");
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
const config = readConfig();
|
|
337
|
+
try {
|
|
338
|
+
const org = await verifyApiKey(apiKey, opts.baseUrl);
|
|
339
|
+
const format = opts.output || "plain";
|
|
340
|
+
if (format === "json") {
|
|
341
|
+
console.log(
|
|
342
|
+
JSON.stringify({
|
|
343
|
+
orgId: org.org_id,
|
|
344
|
+
orgName: org.name,
|
|
345
|
+
orgSlug: org.slug,
|
|
346
|
+
isFreeTier: org.is_free_tier,
|
|
347
|
+
apiKeyPrefix: apiKey.slice(0, 8) + "..."
|
|
348
|
+
}, null, 2)
|
|
349
|
+
);
|
|
350
|
+
} else {
|
|
351
|
+
console.log();
|
|
352
|
+
console.log(` ${pc3.bold("Organization:")} ${org.name}`);
|
|
353
|
+
console.log(` ${pc3.bold("Org ID:")} ${org.org_id}`);
|
|
354
|
+
console.log(` ${pc3.bold("Slug:")} ${org.slug}`);
|
|
355
|
+
console.log(` ${pc3.bold("Tier:")} ${org.is_free_tier ? "Free" : "Paid"}`);
|
|
356
|
+
console.log(` ${pc3.bold("API Key:")} ${apiKey.slice(0, 8)}...`);
|
|
357
|
+
console.log(` ${pc3.bold("Config:")} ${getConfigPath()}`);
|
|
358
|
+
console.log();
|
|
359
|
+
}
|
|
360
|
+
} catch (err) {
|
|
361
|
+
if (config.orgName) {
|
|
362
|
+
warn(`Could not reach API: ${formatApiError(err)}`);
|
|
363
|
+
console.log(` ${pc3.bold("Organization:")} ${config.orgName} ${pc3.dim("(cached)")}`);
|
|
364
|
+
console.log(` ${pc3.bold("Org ID:")} ${config.orgId}`);
|
|
365
|
+
} else {
|
|
366
|
+
error(formatApiError(err));
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/commands/org.ts
|
|
374
|
+
function registerOrgCommands(program2) {
|
|
375
|
+
const org = program2.command("org").description("Organization management");
|
|
376
|
+
org.command("get").description("Get organization details").action(async () => {
|
|
377
|
+
const opts = program2.opts();
|
|
378
|
+
try {
|
|
379
|
+
const data = await apiRequest({ path: "/org/me", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
380
|
+
console.log(JSON.stringify(data, null, 2));
|
|
381
|
+
} catch (err) {
|
|
382
|
+
error(formatApiError(err));
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
org.command("update").description("Update organization details").requiredOption("--data <json>", "JSON org fields to update").action(async (cmdOpts) => {
|
|
387
|
+
const opts = program2.opts();
|
|
388
|
+
try {
|
|
389
|
+
const body = JSON.parse(cmdOpts.data);
|
|
390
|
+
const data = await apiRequest({
|
|
391
|
+
method: "PATCH",
|
|
392
|
+
path: "/org/me",
|
|
393
|
+
body,
|
|
394
|
+
apiKey: opts.apiKey,
|
|
395
|
+
baseUrl: opts.baseUrl
|
|
396
|
+
});
|
|
397
|
+
success("Organization updated.");
|
|
398
|
+
console.log(JSON.stringify(data, null, 2));
|
|
399
|
+
} catch (err) {
|
|
400
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/commands/users.ts
|
|
407
|
+
function registerUserCommands(program2) {
|
|
408
|
+
const users = program2.command("users").description("Manage users in organization");
|
|
409
|
+
users.command("list").description("List users in organization").action(async () => {
|
|
410
|
+
const opts = program2.opts();
|
|
411
|
+
try {
|
|
412
|
+
const data = await apiRequest({ path: "/org/users", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
413
|
+
console.log(JSON.stringify(data, null, 2));
|
|
414
|
+
} catch (err) {
|
|
415
|
+
error(formatApiError(err));
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
users.command("add").description("Add user to organization").requiredOption("--data <json>", "JSON user data").action(async (cmdOpts) => {
|
|
420
|
+
const opts = program2.opts();
|
|
421
|
+
try {
|
|
422
|
+
const body = JSON.parse(cmdOpts.data);
|
|
423
|
+
const data = await apiRequest({
|
|
424
|
+
method: "POST",
|
|
425
|
+
path: "/org/users",
|
|
426
|
+
body,
|
|
427
|
+
apiKey: opts.apiKey,
|
|
428
|
+
baseUrl: opts.baseUrl
|
|
429
|
+
});
|
|
430
|
+
success("User added.");
|
|
431
|
+
console.log(JSON.stringify(data, null, 2));
|
|
432
|
+
} catch (err) {
|
|
433
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
users.command("get <userId>").description("Get a user in the organization").action(async (userId) => {
|
|
438
|
+
const opts = program2.opts();
|
|
439
|
+
try {
|
|
440
|
+
const data = await apiRequest({ path: `/org/users/${userId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
441
|
+
console.log(JSON.stringify(data, null, 2));
|
|
442
|
+
} catch (err) {
|
|
443
|
+
error(formatApiError(err));
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
users.command("update <userId>").description("Update a user's role").requiredOption("--data <json>", "JSON user update data").action(async (userId, cmdOpts) => {
|
|
448
|
+
const opts = program2.opts();
|
|
449
|
+
try {
|
|
450
|
+
const body = JSON.parse(cmdOpts.data);
|
|
451
|
+
const data = await apiRequest({
|
|
452
|
+
method: "PATCH",
|
|
453
|
+
path: `/org/users/${userId}`,
|
|
454
|
+
body,
|
|
455
|
+
apiKey: opts.apiKey,
|
|
456
|
+
baseUrl: opts.baseUrl
|
|
457
|
+
});
|
|
458
|
+
success(`User ${userId} updated.`);
|
|
459
|
+
console.log(JSON.stringify(data, null, 2));
|
|
460
|
+
} catch (err) {
|
|
461
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
users.command("remove <userId>").description("Remove a user from the organization").action(async (userId) => {
|
|
466
|
+
const opts = program2.opts();
|
|
467
|
+
try {
|
|
468
|
+
await apiRequest({ method: "DELETE", path: `/org/users/${userId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
469
|
+
success(`User ${userId} removed.`);
|
|
470
|
+
} catch (err) {
|
|
471
|
+
error(formatApiError(err));
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/commands/api-keys.ts
|
|
478
|
+
function registerApiKeyCommands(program2) {
|
|
479
|
+
const keys = program2.command("api-keys").description("Manage API keys");
|
|
480
|
+
keys.command("list").description("List API keys").action(async () => {
|
|
481
|
+
const opts = program2.opts();
|
|
482
|
+
try {
|
|
483
|
+
const data = await apiRequest({ path: "/org/api-keys", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
484
|
+
console.log(JSON.stringify(data, null, 2));
|
|
485
|
+
} catch (err) {
|
|
486
|
+
error(formatApiError(err));
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
keys.command("create").description("Create API key").requiredOption("--data <json>", "JSON key configuration").action(async (cmdOpts) => {
|
|
491
|
+
const opts = program2.opts();
|
|
492
|
+
try {
|
|
493
|
+
const body = JSON.parse(cmdOpts.data);
|
|
494
|
+
const data = await apiRequest({
|
|
495
|
+
method: "POST",
|
|
496
|
+
path: "/org/api-keys",
|
|
497
|
+
body,
|
|
498
|
+
apiKey: opts.apiKey,
|
|
499
|
+
baseUrl: opts.baseUrl
|
|
500
|
+
});
|
|
501
|
+
success("API key created.");
|
|
502
|
+
console.log(JSON.stringify(data, null, 2));
|
|
503
|
+
} catch (err) {
|
|
504
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
keys.command("get <keyId>").description("Get API key details").action(async (keyId) => {
|
|
509
|
+
const opts = program2.opts();
|
|
510
|
+
try {
|
|
511
|
+
const data = await apiRequest({ path: `/org/api-keys/${keyId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
512
|
+
console.log(JSON.stringify(data, null, 2));
|
|
513
|
+
} catch (err) {
|
|
514
|
+
error(formatApiError(err));
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
keys.command("update <keyId>").description("Update API key").requiredOption("--data <json>", "JSON key updates").action(async (keyId, cmdOpts) => {
|
|
519
|
+
const opts = program2.opts();
|
|
520
|
+
try {
|
|
521
|
+
const body = JSON.parse(cmdOpts.data);
|
|
522
|
+
const data = await apiRequest({
|
|
523
|
+
method: "PATCH",
|
|
524
|
+
path: `/org/api-keys/${keyId}`,
|
|
525
|
+
body,
|
|
526
|
+
apiKey: opts.apiKey,
|
|
527
|
+
baseUrl: opts.baseUrl
|
|
528
|
+
});
|
|
529
|
+
success(`API key ${keyId} updated.`);
|
|
530
|
+
console.log(JSON.stringify(data, null, 2));
|
|
531
|
+
} catch (err) {
|
|
532
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
keys.command("delete <keyId>").description("Delete API key").action(async (keyId) => {
|
|
537
|
+
const opts = program2.opts();
|
|
538
|
+
try {
|
|
539
|
+
await apiRequest({ method: "DELETE", path: `/org/api-keys/${keyId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
540
|
+
success(`API key ${keyId} deleted.`);
|
|
541
|
+
} catch (err) {
|
|
542
|
+
error(formatApiError(err));
|
|
543
|
+
process.exit(1);
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
keys.command("revoke <keyId>").description("Revoke API key").action(async (keyId) => {
|
|
547
|
+
const opts = program2.opts();
|
|
548
|
+
try {
|
|
549
|
+
await apiRequest({ method: "POST", path: `/org/api-keys/${keyId}/revoke`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
550
|
+
success(`API key ${keyId} revoked.`);
|
|
551
|
+
} catch (err) {
|
|
552
|
+
error(formatApiError(err));
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// src/commands/categories.ts
|
|
559
|
+
function registerCategoryCommands(program2) {
|
|
560
|
+
const cat = program2.command("categories").description("Manage categories");
|
|
561
|
+
cat.command("list").description("List categories").action(async () => {
|
|
562
|
+
const opts = program2.opts();
|
|
563
|
+
try {
|
|
564
|
+
const data = await apiRequest({ path: "/org/categories", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
565
|
+
console.log(JSON.stringify(data, null, 2));
|
|
566
|
+
} catch (err) {
|
|
567
|
+
error(formatApiError(err));
|
|
568
|
+
process.exit(1);
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
cat.command("list-all").description("List all categories with their topics").action(async () => {
|
|
572
|
+
const opts = program2.opts();
|
|
573
|
+
try {
|
|
574
|
+
const data = await apiRequest({ path: "/org/categories/all", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
575
|
+
console.log(JSON.stringify(data, null, 2));
|
|
576
|
+
} catch (err) {
|
|
577
|
+
error(formatApiError(err));
|
|
578
|
+
process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
cat.command("create <name>").description("Create a category").action(async (name) => {
|
|
582
|
+
const opts = program2.opts();
|
|
583
|
+
try {
|
|
584
|
+
const data = await apiRequest({
|
|
585
|
+
method: "POST",
|
|
586
|
+
path: "/org/categories",
|
|
587
|
+
body: { name },
|
|
588
|
+
apiKey: opts.apiKey,
|
|
589
|
+
baseUrl: opts.baseUrl
|
|
590
|
+
});
|
|
591
|
+
success(`Category "${name}" created.`);
|
|
592
|
+
console.log(JSON.stringify(data, null, 2));
|
|
593
|
+
} catch (err) {
|
|
594
|
+
error(formatApiError(err));
|
|
595
|
+
process.exit(1);
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
cat.command("get <id>").description("Get category by ID").action(async (id) => {
|
|
599
|
+
const opts = program2.opts();
|
|
600
|
+
try {
|
|
601
|
+
const data = await apiRequest({ path: `/org/categories/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
602
|
+
console.log(JSON.stringify(data, null, 2));
|
|
603
|
+
} catch (err) {
|
|
604
|
+
error(formatApiError(err));
|
|
605
|
+
process.exit(1);
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
cat.command("update <id>").description("Update a category").requiredOption("--name <name>", "New category name").action(async (id, cmdOpts) => {
|
|
609
|
+
const opts = program2.opts();
|
|
610
|
+
try {
|
|
611
|
+
const data = await apiRequest({
|
|
612
|
+
method: "PATCH",
|
|
613
|
+
path: `/org/categories/${id}`,
|
|
614
|
+
body: { name: cmdOpts.name },
|
|
615
|
+
apiKey: opts.apiKey,
|
|
616
|
+
baseUrl: opts.baseUrl
|
|
617
|
+
});
|
|
618
|
+
success(`Category ${id} updated.`);
|
|
619
|
+
console.log(JSON.stringify(data, null, 2));
|
|
620
|
+
} catch (err) {
|
|
621
|
+
error(formatApiError(err));
|
|
622
|
+
process.exit(1);
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
cat.command("delete <id>").description("Delete a category").action(async (id) => {
|
|
626
|
+
const opts = program2.opts();
|
|
627
|
+
try {
|
|
628
|
+
await apiRequest({ method: "DELETE", path: `/org/categories/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
629
|
+
success(`Category ${id} deleted.`);
|
|
630
|
+
} catch (err) {
|
|
631
|
+
error(formatApiError(err));
|
|
632
|
+
process.exit(1);
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
cat.command("batch-create").description("Batch create categories with topics").requiredOption("--data <json>", "JSON array of categories with topics").action(async (cmdOpts) => {
|
|
636
|
+
const opts = program2.opts();
|
|
637
|
+
try {
|
|
638
|
+
const body = JSON.parse(cmdOpts.data);
|
|
639
|
+
const data = await apiRequest({
|
|
640
|
+
method: "POST",
|
|
641
|
+
path: "/org/categories/batch",
|
|
642
|
+
body,
|
|
643
|
+
apiKey: opts.apiKey,
|
|
644
|
+
baseUrl: opts.baseUrl
|
|
645
|
+
});
|
|
646
|
+
success("Batch create completed.");
|
|
647
|
+
console.log(JSON.stringify(data, null, 2));
|
|
648
|
+
} catch (err) {
|
|
649
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
650
|
+
process.exit(1);
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/commands/topics.ts
|
|
656
|
+
function registerTopicCommands(program2) {
|
|
657
|
+
const topics = program2.command("topics").description("Manage topics within categories");
|
|
658
|
+
topics.command("list <categoryId>").description("List topics for a category").action(async (categoryId) => {
|
|
659
|
+
const opts = program2.opts();
|
|
660
|
+
try {
|
|
661
|
+
const data = await apiRequest({
|
|
662
|
+
path: `/org/categories/${categoryId}/topics`,
|
|
663
|
+
apiKey: opts.apiKey,
|
|
664
|
+
baseUrl: opts.baseUrl
|
|
665
|
+
});
|
|
666
|
+
console.log(JSON.stringify(data, null, 2));
|
|
667
|
+
} catch (err) {
|
|
668
|
+
error(formatApiError(err));
|
|
669
|
+
process.exit(1);
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
topics.command("create <categoryId>").description("Create topic in category").requiredOption("--name <name>", "Topic name").action(async (categoryId, cmdOpts) => {
|
|
673
|
+
const opts = program2.opts();
|
|
674
|
+
try {
|
|
675
|
+
const data = await apiRequest({
|
|
676
|
+
method: "POST",
|
|
677
|
+
path: `/org/categories/${categoryId}/topics`,
|
|
678
|
+
body: { name: cmdOpts.name },
|
|
679
|
+
apiKey: opts.apiKey,
|
|
680
|
+
baseUrl: opts.baseUrl
|
|
681
|
+
});
|
|
682
|
+
success(`Topic "${cmdOpts.name}" created.`);
|
|
683
|
+
console.log(JSON.stringify(data, null, 2));
|
|
684
|
+
} catch (err) {
|
|
685
|
+
error(formatApiError(err));
|
|
686
|
+
process.exit(1);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
topics.command("get <categoryId> <topicId>").description("Get topic by ID").action(async (categoryId, topicId) => {
|
|
690
|
+
const opts = program2.opts();
|
|
691
|
+
try {
|
|
692
|
+
const data = await apiRequest({
|
|
693
|
+
path: `/org/categories/${categoryId}/topics/${topicId}`,
|
|
694
|
+
apiKey: opts.apiKey,
|
|
695
|
+
baseUrl: opts.baseUrl
|
|
696
|
+
});
|
|
697
|
+
console.log(JSON.stringify(data, null, 2));
|
|
698
|
+
} catch (err) {
|
|
699
|
+
error(formatApiError(err));
|
|
700
|
+
process.exit(1);
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
topics.command("update <categoryId> <topicId>").description("Update a topic").requiredOption("--name <name>", "New topic name").action(async (categoryId, topicId, cmdOpts) => {
|
|
704
|
+
const opts = program2.opts();
|
|
705
|
+
try {
|
|
706
|
+
const data = await apiRequest({
|
|
707
|
+
method: "PATCH",
|
|
708
|
+
path: `/org/categories/${categoryId}/topics/${topicId}`,
|
|
709
|
+
body: { name: cmdOpts.name },
|
|
710
|
+
apiKey: opts.apiKey,
|
|
711
|
+
baseUrl: opts.baseUrl
|
|
712
|
+
});
|
|
713
|
+
success(`Topic ${topicId} updated.`);
|
|
714
|
+
console.log(JSON.stringify(data, null, 2));
|
|
715
|
+
} catch (err) {
|
|
716
|
+
error(formatApiError(err));
|
|
717
|
+
process.exit(1);
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
topics.command("delete <categoryId> <topicId>").description("Delete a topic").action(async (categoryId, topicId) => {
|
|
721
|
+
const opts = program2.opts();
|
|
722
|
+
try {
|
|
723
|
+
await apiRequest({
|
|
724
|
+
method: "DELETE",
|
|
725
|
+
path: `/org/categories/${categoryId}/topics/${topicId}`,
|
|
726
|
+
apiKey: opts.apiKey,
|
|
727
|
+
baseUrl: opts.baseUrl
|
|
728
|
+
});
|
|
729
|
+
success(`Topic ${topicId} deleted.`);
|
|
730
|
+
} catch (err) {
|
|
731
|
+
error(formatApiError(err));
|
|
732
|
+
process.exit(1);
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
topics.command("batch-create <categoryId>").description("Batch create topics in a category").requiredOption("--data <json>", "JSON array of topics").action(async (categoryId, cmdOpts) => {
|
|
736
|
+
const opts = program2.opts();
|
|
737
|
+
try {
|
|
738
|
+
const body = JSON.parse(cmdOpts.data);
|
|
739
|
+
const data = await apiRequest({
|
|
740
|
+
method: "POST",
|
|
741
|
+
path: `/org/categories/${categoryId}/topics/batch`,
|
|
742
|
+
body,
|
|
743
|
+
apiKey: opts.apiKey,
|
|
744
|
+
baseUrl: opts.baseUrl
|
|
745
|
+
});
|
|
746
|
+
success("Batch create completed.");
|
|
747
|
+
console.log(JSON.stringify(data, null, 2));
|
|
748
|
+
} catch (err) {
|
|
749
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
750
|
+
process.exit(1);
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// src/commands/search.ts
|
|
756
|
+
import pc5 from "picocolors";
|
|
757
|
+
|
|
758
|
+
// src/lib/output.ts
|
|
759
|
+
import pc4 from "picocolors";
|
|
760
|
+
function outputJson(data) {
|
|
761
|
+
console.log(JSON.stringify(data, null, 2));
|
|
762
|
+
}
|
|
763
|
+
function outputTable(rows, columns) {
|
|
764
|
+
if (rows.length === 0) {
|
|
765
|
+
console.log(pc4.dim(" No results."));
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
const cols = columns || Object.keys(rows[0]);
|
|
769
|
+
const widths = cols.map((col) => {
|
|
770
|
+
const maxVal = rows.reduce(
|
|
771
|
+
(max, row) => Math.max(max, String(row[col] ?? "").length),
|
|
772
|
+
0
|
|
773
|
+
);
|
|
774
|
+
return Math.max(col.length, maxVal);
|
|
775
|
+
});
|
|
776
|
+
const header = cols.map((col, i) => pc4.bold(col.padEnd(widths[i]))).join(" ");
|
|
777
|
+
console.log(` ${header}`);
|
|
778
|
+
console.log(` ${widths.map((w) => "\u2500".repeat(w)).join(" ")}`);
|
|
779
|
+
for (const row of rows) {
|
|
780
|
+
const line = cols.map((col, i) => String(row[col] ?? "").padEnd(widths[i])).join(" ");
|
|
781
|
+
console.log(` ${line}`);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
function outputPlain(lines) {
|
|
785
|
+
const arr = Array.isArray(lines) ? lines : [lines];
|
|
786
|
+
for (const line of arr) {
|
|
787
|
+
console.log(line);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
function output(format, data) {
|
|
791
|
+
switch (format) {
|
|
792
|
+
case "json":
|
|
793
|
+
outputJson(data.json);
|
|
794
|
+
break;
|
|
795
|
+
case "table":
|
|
796
|
+
if (data.table) {
|
|
797
|
+
outputTable(data.table.rows, data.table.columns);
|
|
798
|
+
} else {
|
|
799
|
+
outputJson(data.json);
|
|
800
|
+
}
|
|
801
|
+
break;
|
|
802
|
+
case "plain":
|
|
803
|
+
outputPlain(data.plain);
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// src/commands/search.ts
|
|
809
|
+
function registerSearchCommands(program2) {
|
|
810
|
+
const search = program2.command("search").description("Semantic search over your knowledge base");
|
|
811
|
+
search.argument("<query>", "Search query").option("--max-results <n>", "Maximum number of results", "5").action(async (query, cmdOpts) => {
|
|
812
|
+
const opts = program2.opts();
|
|
813
|
+
try {
|
|
814
|
+
const data = await apiRequest({
|
|
815
|
+
method: "POST",
|
|
816
|
+
path: "/org/search",
|
|
817
|
+
body: { query, max_results: parseInt(cmdOpts.maxResults) },
|
|
818
|
+
apiKey: opts.apiKey,
|
|
819
|
+
baseUrl: opts.baseUrl
|
|
820
|
+
});
|
|
821
|
+
const format = opts.output || "plain";
|
|
822
|
+
const res = data;
|
|
823
|
+
output(format, {
|
|
824
|
+
json: data,
|
|
825
|
+
table: res.results ? {
|
|
826
|
+
rows: res.results.map((r) => ({
|
|
827
|
+
id: r.content_id,
|
|
828
|
+
title: r.title,
|
|
829
|
+
text: String(r.chunk_text || "").slice(0, 80)
|
|
830
|
+
})),
|
|
831
|
+
columns: ["id", "title", "text"]
|
|
832
|
+
} : void 0,
|
|
833
|
+
plain: [
|
|
834
|
+
"",
|
|
835
|
+
res.answer ? ` ${pc5.bold("Answer:")} ${res.answer}` : "",
|
|
836
|
+
"",
|
|
837
|
+
...(res.results || []).map(
|
|
838
|
+
(r, i) => ` ${pc5.dim(`${i + 1}.`)} ${pc5.bold(String(r.title || "Untitled"))}
|
|
839
|
+
${String(r.chunk_text || "").slice(0, 120)}
|
|
840
|
+
${pc5.dim(`ID: ${r.content_id}`)}`
|
|
841
|
+
),
|
|
842
|
+
""
|
|
843
|
+
].filter(Boolean)
|
|
844
|
+
});
|
|
845
|
+
} catch (err) {
|
|
846
|
+
error(formatApiError(err));
|
|
847
|
+
process.exit(1);
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
search.command("context <query>").description("Semantic search \u2014 chunks only").option("--max-results <n>", "Maximum results", "5").action(async (query, cmdOpts) => {
|
|
851
|
+
const opts = program2.opts();
|
|
852
|
+
try {
|
|
853
|
+
const data = await apiRequest({
|
|
854
|
+
method: "POST",
|
|
855
|
+
path: "/org/search/context",
|
|
856
|
+
body: { query, max_results: parseInt(cmdOpts.maxResults) },
|
|
857
|
+
apiKey: opts.apiKey,
|
|
858
|
+
baseUrl: opts.baseUrl
|
|
859
|
+
});
|
|
860
|
+
outputByFormat(opts.output, data);
|
|
861
|
+
} catch (err) {
|
|
862
|
+
error(formatApiError(err));
|
|
863
|
+
process.exit(1);
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
search.command("content <query>").description("Semantic search \u2014 content IDs only").option("--max-results <n>", "Maximum results", "5").action(async (query, cmdOpts) => {
|
|
867
|
+
const opts = program2.opts();
|
|
868
|
+
try {
|
|
869
|
+
const data = await apiRequest({
|
|
870
|
+
method: "POST",
|
|
871
|
+
path: "/org/search/content",
|
|
872
|
+
body: { query, max_results: parseInt(cmdOpts.maxResults) },
|
|
873
|
+
apiKey: opts.apiKey,
|
|
874
|
+
baseUrl: opts.baseUrl
|
|
875
|
+
});
|
|
876
|
+
outputByFormat(opts.output, data);
|
|
877
|
+
} catch (err) {
|
|
878
|
+
error(formatApiError(err));
|
|
879
|
+
process.exit(1);
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
function outputByFormat(format, data) {
|
|
884
|
+
if (format === "json") {
|
|
885
|
+
console.log(JSON.stringify(data, null, 2));
|
|
886
|
+
} else {
|
|
887
|
+
console.log(JSON.stringify(data, null, 2));
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// src/commands/ingest.ts
|
|
892
|
+
function registerIngestCommands(program2) {
|
|
893
|
+
const ingest = program2.command("ingest").description("Content ingestion (upload, reprocess)");
|
|
894
|
+
ingest.command("upload").description("Request presigned S3 upload URLs").requiredOption("--files <filenames...>", "File names to get upload URLs for").action(async (cmdOpts) => {
|
|
895
|
+
const opts = program2.opts();
|
|
896
|
+
try {
|
|
897
|
+
const data = await apiRequest({
|
|
898
|
+
method: "POST",
|
|
899
|
+
path: "/org/ingestion/upload",
|
|
900
|
+
body: { files: cmdOpts.files },
|
|
901
|
+
apiKey: opts.apiKey,
|
|
902
|
+
baseUrl: opts.baseUrl
|
|
903
|
+
});
|
|
904
|
+
console.log(JSON.stringify(data, null, 2));
|
|
905
|
+
} catch (err) {
|
|
906
|
+
error(formatApiError(err));
|
|
907
|
+
process.exit(1);
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
ingest.command("reprocess <contentId>").description("Request re-ingestion of existing content").action(async (contentId) => {
|
|
911
|
+
const opts = program2.opts();
|
|
912
|
+
try {
|
|
913
|
+
await apiRequest({
|
|
914
|
+
method: "POST",
|
|
915
|
+
path: `/org/ingestion/${contentId}/reprocess`,
|
|
916
|
+
apiKey: opts.apiKey,
|
|
917
|
+
baseUrl: opts.baseUrl
|
|
918
|
+
});
|
|
919
|
+
success(`Reprocess triggered for content ${contentId}.`);
|
|
920
|
+
} catch (err) {
|
|
921
|
+
error(formatApiError(err));
|
|
922
|
+
process.exit(1);
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// src/commands/content.ts
|
|
928
|
+
import pc6 from "picocolors";
|
|
929
|
+
function registerContentCommands(program2) {
|
|
930
|
+
const content = program2.command("content").description("Manage content items");
|
|
931
|
+
content.command("list").description("List content items").option("--limit <n>", "Items per page", "10").option("--offset <n>", "Pagination offset", "0").action(async (cmdOpts) => {
|
|
932
|
+
const opts = program2.opts();
|
|
933
|
+
try {
|
|
934
|
+
const data = await apiRequest({
|
|
935
|
+
path: "/org/content",
|
|
936
|
+
params: { limit: cmdOpts.limit, offset: cmdOpts.offset },
|
|
937
|
+
apiKey: opts.apiKey,
|
|
938
|
+
baseUrl: opts.baseUrl
|
|
939
|
+
});
|
|
940
|
+
const format = opts.output || "plain";
|
|
941
|
+
const rows = Array.isArray(data) ? data : [];
|
|
942
|
+
output(format, {
|
|
943
|
+
json: data,
|
|
944
|
+
table: {
|
|
945
|
+
rows: rows.map((r) => ({
|
|
946
|
+
id: r.id || r.content_id,
|
|
947
|
+
title: r.title,
|
|
948
|
+
status: r.status
|
|
949
|
+
})),
|
|
950
|
+
columns: ["id", "title", "status"]
|
|
951
|
+
},
|
|
952
|
+
plain: rows.length ? rows.map(
|
|
953
|
+
(r) => ` ${pc6.bold(String(r.title || "Untitled"))} ${pc6.dim(`(${r.id || r.content_id})`)} ${r.status ? pc6.dim(`[${r.status}]`) : ""}`
|
|
954
|
+
) : [" No content found."]
|
|
955
|
+
});
|
|
956
|
+
} catch (err) {
|
|
957
|
+
error(formatApiError(err));
|
|
958
|
+
process.exit(1);
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
content.command("get <id>").description("Get content item by ID").action(async (id) => {
|
|
962
|
+
const opts = program2.opts();
|
|
963
|
+
try {
|
|
964
|
+
const data = await apiRequest({ path: `/org/content/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
965
|
+
console.log(JSON.stringify(data, null, 2));
|
|
966
|
+
} catch (err) {
|
|
967
|
+
error(formatApiError(err));
|
|
968
|
+
process.exit(1);
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
content.command("delete <id>").description("Delete content (local + external)").action(async (id) => {
|
|
972
|
+
const opts = program2.opts();
|
|
973
|
+
try {
|
|
974
|
+
await apiRequest({ method: "DELETE", path: `/org/content/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
975
|
+
success(`Content ${id} deleted.`);
|
|
976
|
+
} catch (err) {
|
|
977
|
+
error(formatApiError(err));
|
|
978
|
+
process.exit(1);
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
content.command("unpublish <id>").description("Unpublish content (external delete + set draft)").action(async (id) => {
|
|
982
|
+
const opts = program2.opts();
|
|
983
|
+
try {
|
|
984
|
+
await apiRequest({ method: "POST", path: `/org/content/${id}/unpublish`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
985
|
+
success(`Content ${id} unpublished.`);
|
|
986
|
+
} catch (err) {
|
|
987
|
+
error(formatApiError(err));
|
|
988
|
+
process.exit(1);
|
|
989
|
+
}
|
|
990
|
+
});
|
|
991
|
+
content.command("verification").description("List content awaiting verification").action(async () => {
|
|
992
|
+
const opts = program2.opts();
|
|
993
|
+
try {
|
|
994
|
+
const data = await apiRequest({ path: "/org/content/verification", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
995
|
+
console.log(JSON.stringify(data, null, 2));
|
|
996
|
+
} catch (err) {
|
|
997
|
+
error(formatApiError(err));
|
|
998
|
+
process.exit(1);
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
content.command("reject <versionId>").description("Reject a content version").action(async (versionId) => {
|
|
1002
|
+
const opts = program2.opts();
|
|
1003
|
+
try {
|
|
1004
|
+
await apiRequest({ method: "POST", path: `/org/content/versions/${versionId}/reject`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1005
|
+
success(`Version ${versionId} rejected.`);
|
|
1006
|
+
} catch (err) {
|
|
1007
|
+
error(formatApiError(err));
|
|
1008
|
+
process.exit(1);
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
content.command("restore <versionId>").description("Restore a content version to draft").action(async (versionId) => {
|
|
1012
|
+
const opts = program2.opts();
|
|
1013
|
+
try {
|
|
1014
|
+
await apiRequest({ method: "POST", path: `/org/content/versions/${versionId}/restore`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1015
|
+
success(`Version ${versionId} restored to draft.`);
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
error(formatApiError(err));
|
|
1018
|
+
process.exit(1);
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
content.command("owners <id>").description("List content owners").action(async (id) => {
|
|
1022
|
+
const opts = program2.opts();
|
|
1023
|
+
try {
|
|
1024
|
+
const data = await apiRequest({ path: `/org/content/${id}/owners`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1025
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1026
|
+
} catch (err) {
|
|
1027
|
+
error(formatApiError(err));
|
|
1028
|
+
process.exit(1);
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
content.command("set-owners <id>").description("Replace content owners").requiredOption("--user-ids <ids...>", "User IDs to set as owners").action(async (id, cmdOpts) => {
|
|
1032
|
+
const opts = program2.opts();
|
|
1033
|
+
try {
|
|
1034
|
+
await apiRequest({
|
|
1035
|
+
method: "PUT",
|
|
1036
|
+
path: `/org/content/${id}/owners`,
|
|
1037
|
+
body: { user_ids: cmdOpts.userIds },
|
|
1038
|
+
apiKey: opts.apiKey,
|
|
1039
|
+
baseUrl: opts.baseUrl
|
|
1040
|
+
});
|
|
1041
|
+
success(`Owners updated for content ${id}.`);
|
|
1042
|
+
} catch (err) {
|
|
1043
|
+
error(formatApiError(err));
|
|
1044
|
+
process.exit(1);
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
content.command("remove-owner <id> <userId>").description("Remove content owner").action(async (id, userId) => {
|
|
1048
|
+
const opts = program2.opts();
|
|
1049
|
+
try {
|
|
1050
|
+
await apiRequest({ method: "DELETE", path: `/org/content/${id}/owners/${userId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1051
|
+
success(`Owner ${userId} removed from content ${id}.`);
|
|
1052
|
+
} catch (err) {
|
|
1053
|
+
error(formatApiError(err));
|
|
1054
|
+
process.exit(1);
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// src/commands/generate.ts
|
|
1060
|
+
function registerGenerateCommands(program2) {
|
|
1061
|
+
const gen = program2.command("generate").description("Content generation settings and triggers");
|
|
1062
|
+
gen.command("settings").description("Get content generation settings").action(async () => {
|
|
1063
|
+
const opts = program2.opts();
|
|
1064
|
+
try {
|
|
1065
|
+
const data = await apiRequest({ path: "/org/content-generation", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1066
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1067
|
+
} catch (err) {
|
|
1068
|
+
error(formatApiError(err));
|
|
1069
|
+
process.exit(1);
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
gen.command("update-settings").description("Update content generation settings").requiredOption("--data <json>", "JSON settings to update").action(async (cmdOpts) => {
|
|
1073
|
+
const opts = program2.opts();
|
|
1074
|
+
try {
|
|
1075
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1076
|
+
const data = await apiRequest({ method: "PATCH", path: "/org/content-generation", body, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1077
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1078
|
+
} catch (err) {
|
|
1079
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1080
|
+
process.exit(1);
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
gen.command("sample").description("Generate ad hoc content sample").requiredOption("--instructions <text>", "Instructions for generation").action(async (cmdOpts) => {
|
|
1084
|
+
const opts = program2.opts();
|
|
1085
|
+
try {
|
|
1086
|
+
const data = await apiRequest({
|
|
1087
|
+
method: "POST",
|
|
1088
|
+
path: "/org/content-generation/sample",
|
|
1089
|
+
body: { instructions: cmdOpts.instructions },
|
|
1090
|
+
apiKey: opts.apiKey,
|
|
1091
|
+
baseUrl: opts.baseUrl
|
|
1092
|
+
});
|
|
1093
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1094
|
+
} catch (err) {
|
|
1095
|
+
error(formatApiError(err));
|
|
1096
|
+
process.exit(1);
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
gen.command("run").description("Trigger content engine run").action(async () => {
|
|
1100
|
+
const opts = program2.opts();
|
|
1101
|
+
try {
|
|
1102
|
+
const data = await apiRequest({ method: "POST", path: "/org/content-generation/run", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1103
|
+
success("Content generation run triggered.");
|
|
1104
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1105
|
+
} catch (err) {
|
|
1106
|
+
error(formatApiError(err));
|
|
1107
|
+
process.exit(1);
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// src/commands/engine.ts
|
|
1113
|
+
function registerEngineCommands(program2) {
|
|
1114
|
+
const engine = program2.command("engine").description("Content engine operations (publish/draft)");
|
|
1115
|
+
engine.command("publish").description("Publish content via content engine").requiredOption("--data <json>", "JSON payload for publish").action(async (cmdOpts) => {
|
|
1116
|
+
const opts = program2.opts();
|
|
1117
|
+
try {
|
|
1118
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1119
|
+
const data = await apiRequest({
|
|
1120
|
+
method: "POST",
|
|
1121
|
+
path: "/org/content-engine/publish",
|
|
1122
|
+
body,
|
|
1123
|
+
apiKey: opts.apiKey,
|
|
1124
|
+
baseUrl: opts.baseUrl
|
|
1125
|
+
});
|
|
1126
|
+
success("Content published.");
|
|
1127
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1128
|
+
} catch (err) {
|
|
1129
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1130
|
+
process.exit(1);
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
engine.command("draft").description("Save content as draft via content engine").requiredOption("--data <json>", "JSON payload for draft").action(async (cmdOpts) => {
|
|
1134
|
+
const opts = program2.opts();
|
|
1135
|
+
try {
|
|
1136
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1137
|
+
const data = await apiRequest({
|
|
1138
|
+
method: "POST",
|
|
1139
|
+
path: "/org/content-engine/draft",
|
|
1140
|
+
body,
|
|
1141
|
+
apiKey: opts.apiKey,
|
|
1142
|
+
baseUrl: opts.baseUrl
|
|
1143
|
+
});
|
|
1144
|
+
success("Content saved as draft.");
|
|
1145
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1146
|
+
} catch (err) {
|
|
1147
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1148
|
+
process.exit(1);
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// src/commands/brand-kit.ts
|
|
1154
|
+
function registerBrandKitCommands(program2) {
|
|
1155
|
+
const bk = program2.command("brand-kit").description("Manage brand kit");
|
|
1156
|
+
bk.command("get").description("Get brand kit").action(async () => {
|
|
1157
|
+
const opts = program2.opts();
|
|
1158
|
+
try {
|
|
1159
|
+
const data = await apiRequest({ path: "/org/brand-kit", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1160
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1161
|
+
} catch (err) {
|
|
1162
|
+
error(formatApiError(err));
|
|
1163
|
+
process.exit(1);
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
bk.command("set").description("Upsert brand kit").requiredOption("--data <json>", "JSON brand kit data").action(async (cmdOpts) => {
|
|
1167
|
+
const opts = program2.opts();
|
|
1168
|
+
try {
|
|
1169
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1170
|
+
const data = await apiRequest({
|
|
1171
|
+
method: "PUT",
|
|
1172
|
+
path: "/org/brand-kit",
|
|
1173
|
+
body,
|
|
1174
|
+
apiKey: opts.apiKey,
|
|
1175
|
+
baseUrl: opts.baseUrl
|
|
1176
|
+
});
|
|
1177
|
+
success("Brand kit updated.");
|
|
1178
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1179
|
+
} catch (err) {
|
|
1180
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1181
|
+
process.exit(1);
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// src/commands/content-types.ts
|
|
1187
|
+
function registerContentTypeCommands(program2) {
|
|
1188
|
+
const ct = program2.command("content-types").description("Manage content types");
|
|
1189
|
+
ct.command("list").description("List content types").action(async () => {
|
|
1190
|
+
const opts = program2.opts();
|
|
1191
|
+
try {
|
|
1192
|
+
const data = await apiRequest({ path: "/org/content-types", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1193
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1194
|
+
} catch (err) {
|
|
1195
|
+
error(formatApiError(err));
|
|
1196
|
+
process.exit(1);
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
ct.command("create").description("Create a content type").requiredOption("--data <json>", "JSON content type definition").action(async (cmdOpts) => {
|
|
1200
|
+
const opts = program2.opts();
|
|
1201
|
+
try {
|
|
1202
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1203
|
+
const data = await apiRequest({
|
|
1204
|
+
method: "POST",
|
|
1205
|
+
path: "/org/content-types",
|
|
1206
|
+
body,
|
|
1207
|
+
apiKey: opts.apiKey,
|
|
1208
|
+
baseUrl: opts.baseUrl
|
|
1209
|
+
});
|
|
1210
|
+
success("Content type created.");
|
|
1211
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1212
|
+
} catch (err) {
|
|
1213
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1214
|
+
process.exit(1);
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
ct.command("get <id>").description("Get content type by ID").action(async (id) => {
|
|
1218
|
+
const opts = program2.opts();
|
|
1219
|
+
try {
|
|
1220
|
+
const data = await apiRequest({ path: `/org/content-types/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1221
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1222
|
+
} catch (err) {
|
|
1223
|
+
error(formatApiError(err));
|
|
1224
|
+
process.exit(1);
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
ct.command("update <id>").description("Update a content type").requiredOption("--data <json>", "JSON content type updates").action(async (id, cmdOpts) => {
|
|
1228
|
+
const opts = program2.opts();
|
|
1229
|
+
try {
|
|
1230
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1231
|
+
const data = await apiRequest({
|
|
1232
|
+
method: "PATCH",
|
|
1233
|
+
path: `/org/content-types/${id}`,
|
|
1234
|
+
body,
|
|
1235
|
+
apiKey: opts.apiKey,
|
|
1236
|
+
baseUrl: opts.baseUrl
|
|
1237
|
+
});
|
|
1238
|
+
success(`Content type ${id} updated.`);
|
|
1239
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1240
|
+
} catch (err) {
|
|
1241
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1242
|
+
process.exit(1);
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
ct.command("delete <id>").description("Delete a content type").action(async (id) => {
|
|
1246
|
+
const opts = program2.opts();
|
|
1247
|
+
try {
|
|
1248
|
+
await apiRequest({ method: "DELETE", path: `/org/content-types/${id}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1249
|
+
success(`Content type ${id} deleted.`);
|
|
1250
|
+
} catch (err) {
|
|
1251
|
+
error(formatApiError(err));
|
|
1252
|
+
process.exit(1);
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// src/commands/prompts.ts
|
|
1258
|
+
function registerPromptCommands(program2) {
|
|
1259
|
+
const prompts = program2.command("prompts").description("Manage prompts (geo questions)");
|
|
1260
|
+
prompts.command("list").description("List prompts").action(async () => {
|
|
1261
|
+
const opts = program2.opts();
|
|
1262
|
+
try {
|
|
1263
|
+
const data = await apiRequest({ path: "/org/prompts", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1264
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1265
|
+
} catch (err) {
|
|
1266
|
+
error(formatApiError(err));
|
|
1267
|
+
process.exit(1);
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
1270
|
+
prompts.command("create").description("Create a prompt").requiredOption("--data <json>", "JSON prompt definition").action(async (cmdOpts) => {
|
|
1271
|
+
const opts = program2.opts();
|
|
1272
|
+
try {
|
|
1273
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1274
|
+
const data = await apiRequest({
|
|
1275
|
+
method: "POST",
|
|
1276
|
+
path: "/org/prompts",
|
|
1277
|
+
body,
|
|
1278
|
+
apiKey: opts.apiKey,
|
|
1279
|
+
baseUrl: opts.baseUrl
|
|
1280
|
+
});
|
|
1281
|
+
success("Prompt created.");
|
|
1282
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1283
|
+
} catch (err) {
|
|
1284
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1285
|
+
process.exit(1);
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1288
|
+
prompts.command("get <promptId>").description("Get prompt with full run history").action(async (promptId) => {
|
|
1289
|
+
const opts = program2.opts();
|
|
1290
|
+
try {
|
|
1291
|
+
const data = await apiRequest({ path: `/org/prompts/${promptId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1292
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1293
|
+
} catch (err) {
|
|
1294
|
+
error(formatApiError(err));
|
|
1295
|
+
process.exit(1);
|
|
1296
|
+
}
|
|
1297
|
+
});
|
|
1298
|
+
prompts.command("delete <promptId>").description("Delete a prompt").action(async (promptId) => {
|
|
1299
|
+
const opts = program2.opts();
|
|
1300
|
+
try {
|
|
1301
|
+
await apiRequest({ method: "DELETE", path: `/org/prompts/${promptId}`, apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1302
|
+
success(`Prompt ${promptId} deleted.`);
|
|
1303
|
+
} catch (err) {
|
|
1304
|
+
error(formatApiError(err));
|
|
1305
|
+
process.exit(1);
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// src/commands/run-config.ts
|
|
1311
|
+
function registerRunConfigCommands(program2) {
|
|
1312
|
+
const rc = program2.command("run-config").description("Run configuration (models, schedule)");
|
|
1313
|
+
rc.command("models").description("Get configured AI models").action(async () => {
|
|
1314
|
+
const opts = program2.opts();
|
|
1315
|
+
try {
|
|
1316
|
+
const data = await apiRequest({ path: "/org/run-models", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1317
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1318
|
+
} catch (err) {
|
|
1319
|
+
error(formatApiError(err));
|
|
1320
|
+
process.exit(1);
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
rc.command("set-models").description("Set AI models").requiredOption("--data <json>", "JSON model configuration").action(async (cmdOpts) => {
|
|
1324
|
+
const opts = program2.opts();
|
|
1325
|
+
try {
|
|
1326
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1327
|
+
const data = await apiRequest({
|
|
1328
|
+
method: "PUT",
|
|
1329
|
+
path: "/org/run-models",
|
|
1330
|
+
body,
|
|
1331
|
+
apiKey: opts.apiKey,
|
|
1332
|
+
baseUrl: opts.baseUrl
|
|
1333
|
+
});
|
|
1334
|
+
success("Models updated.");
|
|
1335
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1336
|
+
} catch (err) {
|
|
1337
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1338
|
+
process.exit(1);
|
|
1339
|
+
}
|
|
1340
|
+
});
|
|
1341
|
+
rc.command("schedule").description("Get run schedule").action(async () => {
|
|
1342
|
+
const opts = program2.opts();
|
|
1343
|
+
try {
|
|
1344
|
+
const data = await apiRequest({ path: "/org/run-schedule", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1345
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1346
|
+
} catch (err) {
|
|
1347
|
+
error(formatApiError(err));
|
|
1348
|
+
process.exit(1);
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
rc.command("set-schedule").description("Set run schedule").requiredOption("--data <json>", "JSON schedule configuration").action(async (cmdOpts) => {
|
|
1352
|
+
const opts = program2.opts();
|
|
1353
|
+
try {
|
|
1354
|
+
const body = JSON.parse(cmdOpts.data);
|
|
1355
|
+
const data = await apiRequest({
|
|
1356
|
+
method: "PUT",
|
|
1357
|
+
path: "/org/run-schedule",
|
|
1358
|
+
body,
|
|
1359
|
+
apiKey: opts.apiKey,
|
|
1360
|
+
baseUrl: opts.baseUrl
|
|
1361
|
+
});
|
|
1362
|
+
success("Schedule updated.");
|
|
1363
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1364
|
+
} catch (err) {
|
|
1365
|
+
error(err instanceof SyntaxError ? "Invalid JSON in --data" : formatApiError(err));
|
|
1366
|
+
process.exit(1);
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// src/commands/members.ts
|
|
1372
|
+
function registerMemberCommands(program2) {
|
|
1373
|
+
const members = program2.command("members").description("Organization members");
|
|
1374
|
+
members.command("list").description("List organization members").action(async () => {
|
|
1375
|
+
const opts = program2.opts();
|
|
1376
|
+
try {
|
|
1377
|
+
const data = await apiRequest({ path: "/org/members", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1378
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1379
|
+
} catch (err) {
|
|
1380
|
+
error(formatApiError(err));
|
|
1381
|
+
process.exit(1);
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// src/commands/notifications.ts
|
|
1387
|
+
function registerNotificationCommands(program2) {
|
|
1388
|
+
const notif = program2.command("notifications").description("Manage notifications");
|
|
1389
|
+
notif.command("list").description("List notifications").action(async () => {
|
|
1390
|
+
const opts = program2.opts();
|
|
1391
|
+
try {
|
|
1392
|
+
const data = await apiRequest({ path: "/app/v1/notifications", apiKey: opts.apiKey, baseUrl: opts.baseUrl });
|
|
1393
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1394
|
+
} catch (err) {
|
|
1395
|
+
error(formatApiError(err));
|
|
1396
|
+
process.exit(1);
|
|
1397
|
+
}
|
|
1398
|
+
});
|
|
1399
|
+
notif.command("read <id>").description("Mark notification as read").action(async (id) => {
|
|
1400
|
+
const opts = program2.opts();
|
|
1401
|
+
try {
|
|
1402
|
+
await apiRequest({
|
|
1403
|
+
method: "POST",
|
|
1404
|
+
path: `/app/v1/notifications/${id}/read`,
|
|
1405
|
+
apiKey: opts.apiKey,
|
|
1406
|
+
baseUrl: opts.baseUrl
|
|
1407
|
+
});
|
|
1408
|
+
success(`Notification ${id} marked as read.`);
|
|
1409
|
+
} catch (err) {
|
|
1410
|
+
error(formatApiError(err));
|
|
1411
|
+
process.exit(1);
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// src/commands/update.ts
|
|
1417
|
+
import semver2 from "semver";
|
|
1418
|
+
import pc7 from "picocolors";
|
|
1419
|
+
import { execSync } from "child_process";
|
|
1420
|
+
function registerUpdateCommand(program2) {
|
|
1421
|
+
program2.command("update").description("Update CLI to the latest version").action(async () => {
|
|
1422
|
+
info(`Current version: ${pc7.bold(version)}`);
|
|
1423
|
+
info("Checking for updates...");
|
|
1424
|
+
const release = await getLatestRelease();
|
|
1425
|
+
if (!release) {
|
|
1426
|
+
error("Could not check for updates. Try again later.");
|
|
1427
|
+
process.exit(1);
|
|
1428
|
+
}
|
|
1429
|
+
const latest = release.tag_name.replace(/^v/, "");
|
|
1430
|
+
if (!semver2.gt(latest, version)) {
|
|
1431
|
+
success(`Already on the latest version (${version}).`);
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
info(`New version available: ${pc7.bold(latest)}`);
|
|
1435
|
+
info("Updating...");
|
|
1436
|
+
try {
|
|
1437
|
+
execSync("npm install -g senso-user-cli@latest", {
|
|
1438
|
+
stdio: "inherit"
|
|
1439
|
+
});
|
|
1440
|
+
success(`Updated to v${latest}.`);
|
|
1441
|
+
if (release.body) {
|
|
1442
|
+
console.log();
|
|
1443
|
+
console.log(pc7.dim("Release notes:"));
|
|
1444
|
+
console.log(pc7.dim(release.body.slice(0, 500)));
|
|
1445
|
+
}
|
|
1446
|
+
} catch {
|
|
1447
|
+
warn("Global npm install failed. Trying npx reinstall...");
|
|
1448
|
+
try {
|
|
1449
|
+
execSync(
|
|
1450
|
+
"npx --yes github:AI-Template-SDK/senso-user-cli --version",
|
|
1451
|
+
{ stdio: "inherit" }
|
|
1452
|
+
);
|
|
1453
|
+
success("Updated via npx cache refresh.");
|
|
1454
|
+
} catch {
|
|
1455
|
+
error("Update failed. Please reinstall manually:");
|
|
1456
|
+
console.log(
|
|
1457
|
+
` ${pc7.cyan("npm install -g senso-user-cli")}`
|
|
1458
|
+
);
|
|
1459
|
+
console.log(
|
|
1460
|
+
` ${pc7.dim("or")} ${pc7.cyan("npx github:AI-Template-SDK/senso-user-cli")}`
|
|
1461
|
+
);
|
|
1462
|
+
process.exit(1);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
// src/cli.ts
|
|
1469
|
+
var program = new Command();
|
|
1470
|
+
program.name("senso").description("Senso CLI \u2014 Infrastructure for the Agentic Web").version(version, "-v, --version").option("--api-key <key>", "Override API key (or set SENSO_API_KEY)").option("--base-url <url>", "Override API base URL").option("--output <format>", "Output format: json | table | plain", "plain").option("--quiet", "Suppress non-essential output").option("--no-update-check", "Skip version check").hook("preAction", async () => {
|
|
1471
|
+
const opts = program.opts();
|
|
1472
|
+
if (!opts.quiet) {
|
|
1473
|
+
miniBanner();
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
registerAuthCommands(program);
|
|
1477
|
+
registerOrgCommands(program);
|
|
1478
|
+
registerUserCommands(program);
|
|
1479
|
+
registerApiKeyCommands(program);
|
|
1480
|
+
registerCategoryCommands(program);
|
|
1481
|
+
registerTopicCommands(program);
|
|
1482
|
+
registerSearchCommands(program);
|
|
1483
|
+
registerIngestCommands(program);
|
|
1484
|
+
registerContentCommands(program);
|
|
1485
|
+
registerGenerateCommands(program);
|
|
1486
|
+
registerEngineCommands(program);
|
|
1487
|
+
registerBrandKitCommands(program);
|
|
1488
|
+
registerContentTypeCommands(program);
|
|
1489
|
+
registerPromptCommands(program);
|
|
1490
|
+
registerRunConfigCommands(program);
|
|
1491
|
+
registerMemberCommands(program);
|
|
1492
|
+
registerNotificationCommands(program);
|
|
1493
|
+
registerUpdateCommand(program);
|
|
1494
|
+
async function main() {
|
|
1495
|
+
const quiet = process.argv.includes("--quiet") || process.argv.includes("--output") && process.argv[process.argv.indexOf("--output") + 1] === "json";
|
|
1496
|
+
checkForUpdate(quiet).catch(() => {
|
|
1497
|
+
});
|
|
1498
|
+
await program.parseAsync(process.argv);
|
|
1499
|
+
}
|
|
1500
|
+
main().catch((err) => {
|
|
1501
|
+
console.error(err);
|
|
1502
|
+
process.exit(1);
|
|
1503
|
+
});
|