@rogeriq/cli 0.1.0 → 0.3.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/AGENTS.md CHANGED
@@ -13,7 +13,13 @@ export RIQ_PROJECT_ID=prj_xxx # default project (per-command --project overri
13
13
  export RIQ_ORG_ID=org_xxx # for org-level commands (orgs, projects, keys)
14
14
  ```
15
15
 
16
- The CLI never prompts when these are set. No interactive `--browser` flow.
16
+ The CLI never prompts when these are set. `rogeriq auth login --api-key`
17
+ also works for one-time setup without a browser.
18
+
19
+ **Do not** invoke `rogeriq auth login` without `--api-key` from an agent —
20
+ the no-args form starts the browser-based device flow and will block for
21
+ up to 10 minutes waiting for a human to confirm. Use `--api-key` or env
22
+ vars instead.
17
23
 
18
24
  ## Output contract
19
25
 
package/CHANGELOG.md ADDED
@@ -0,0 +1,44 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 — 2026-05-17
4
+
5
+ ### Added
6
+ - KB articles + agent + integrations + forms + beacons now use the stable
7
+ `/api/v1/...` surface (joins projects + widget + conversations + contacts
8
+ + webhooks already on v1). Internal routes remain as the unstable mirror.
9
+ - MCP server tools: `browse_integration_catalog`, `get_integration_install_url`,
10
+ `disconnect_integration`. Existing tools repointed to v1.
11
+ - **Granular scopes** on API keys. In addition to coarse `read`/`write`/`admin`,
12
+ you can now scope keys per-resource: `kb:write`, `agent:read`,
13
+ `integrations:write`, `projects:admin`, etc. See README for the full table.
14
+ - Audit log now captures `api_key_id` on mutations. `GET /api/projects/:pid/audit-log`
15
+ joins on `api_keys` and returns `api_key_name` + `api_key_prefix` so the
16
+ dashboard can show which key changed what.
17
+
18
+ ## 0.2.0 — 2026-05-17
19
+
20
+ ### Added
21
+ - **Browser-based device authorization flow.** `rogeriq auth login` (no args)
22
+ now opens `rogeriq.com/device?code=XXXX-XXXX`, polls for confirmation, and
23
+ saves the API key automatically. Code is also printed to the terminal as a
24
+ fallback for SSH / headless environments. Pass `--no-browser` to suppress
25
+ the auto-open. `--api-key riq_xxx` still works for CI / scripting.
26
+ - KB articles + search now use the stable `/api/v1/...` surface.
27
+ - Agent status, config, respond, classify, suggest now use `/api/v1/...`.
28
+ - MCP server (`rogeriq mcp`) tools updated to point at v1 endpoints.
29
+ - Unit tests for argv parser, API client (incl. 429 auto-retry), and device
30
+ auth flow (happy path, denied, expired, slow-down backoff).
31
+
32
+ ### Fixed
33
+ - `--help` after a subcommand now correctly prints per-command help instead
34
+ of root help.
35
+ - `--json` / `--quiet` set at the top level now propagate into subcommand
36
+ flag parsing.
37
+ - API client now treats `retry_after: 0` as a valid retry hint (previously
38
+ the 0 was discarded as falsy and the request did not retry).
39
+
40
+ ## 0.1.0 — 2026-05-17
41
+
42
+ Initial release. 18 command groups covering conversations, contacts,
43
+ projects, knowledge base, widget, integrations, webhooks, agent, plus an
44
+ MCP server subcommand for Claude Desktop / Claude Code.
package/README.md CHANGED
@@ -14,18 +14,35 @@ npx @rogeriq/cli --help
14
14
 
15
15
  ## Auth
16
16
 
17
- Create an API key in the dashboard or via the CLI (after one-time browser
18
- login from `app.rogeriq.com`):
17
+ ### Interactive (humans)
18
+
19
+ ```bash
20
+ rogeriq auth login
21
+ ```
22
+
23
+ Opens a browser to `rogeriq.com/device?code=XXXX-XXXX`. Confirm in the
24
+ browser. The CLI picks up the key automatically. Code is also printed to
25
+ the terminal as a fallback (SSH / no browser).
26
+
27
+ Flags:
28
+ - `--no-browser` — skip auto-open; print URL + code only.
29
+ - `--api-base https://...` — point at a different deployment.
30
+
31
+ ### CI / agents / scripts
32
+
33
+ Use `--api-key` (or env var) to skip the browser flow entirely:
19
34
 
20
35
  ```bash
21
36
  rogeriq auth login --api-key riq_xxx
37
+ # Or pass per-invocation:
38
+ RIQ_API_KEY=riq_xxx rogeriq conversations list
22
39
  ```
23
40
 
24
- For CI / agents, prefer env vars (no config file needed):
41
+ Recommended for CI / agents (no config file needed):
25
42
 
26
43
  ```bash
27
44
  export RIQ_API_KEY=riq_xxx
28
- export RIQ_PROJECT_ID=prj_xxx # default project for commands
45
+ export RIQ_PROJECT_ID=prj_xxx
29
46
  ```
30
47
 
31
48
  ## Quick start
@@ -110,6 +127,30 @@ for command-specific flags.
110
127
  | orgId | `RIQ_ORG_ID` |
111
128
  | projectId | `RIQ_PROJECT_ID` |
112
129
 
130
+ ## Scopes
131
+
132
+ API keys can be coarse or granular:
133
+
134
+ | Scope | Grants |
135
+ |-----------------------|----------------------------------------------------------------|
136
+ | `admin` | Everything. |
137
+ | `write` | All `*:read` and `*:write`. Not `*:admin`. |
138
+ | `read` | All `*:read`. |
139
+ | `<resource>:admin` | `<resource>:read`, `:write`, `:admin`. |
140
+ | `<resource>:write` | `<resource>:read`, `:write`. |
141
+ | `<resource>:read` | `<resource>:read` only. |
142
+
143
+ Resources: `conversations`, `messages`, `contacts`, `kb`, `agent`, `widget`,
144
+ `integrations`, `forms`, `beacons`, `webhooks`, `projects`, `analytics`,
145
+ `audit`.
146
+
147
+ Examples — least privilege for an agent that only drafts KB articles + reads
148
+ conversations:
149
+
150
+ ```bash
151
+ rogeriq keys create kb-writer --scopes kb:write,conversations:read
152
+ ```
153
+
113
154
  ## Exit codes
114
155
 
115
156
  | Code | Meaning |
package/dist/index.mjs CHANGED
@@ -275,7 +275,7 @@ var ApiClient = class {
275
275
  const url = path.startsWith("http") ? path : `${this.apiBase}${path}`;
276
276
  const headers = {
277
277
  "X-API-Key": this.apiKey,
278
- "User-Agent": `rogeriq-cli/0.1.0 node/${process.versions.node}`,
278
+ "User-Agent": `rogeriq-cli/0.3.0 node/${process.versions.node}`,
279
279
  "Accept": "application/json",
280
280
  ...init.headers
281
281
  };
@@ -318,7 +318,7 @@ var ApiClient = class {
318
318
  const code = err?.code ?? `HTTP_${res.status}`;
319
319
  const message = err?.error ?? `Request failed: ${res.status} ${res.statusText}`;
320
320
  const exitCode = res.status === 401 || res.status === 403 ? 3 : res.status === 429 ? 4 : 2;
321
- if (res.status === 429 && err?.retry_after && err.retry_after <= 30) {
321
+ if (res.status === 429 && typeof err?.retry_after === "number" && err.retry_after >= 0 && err.retry_after <= 30) {
322
322
  await sleep(err.retry_after * 1e3);
323
323
  return this.request(method, path, body, init);
324
324
  }
@@ -371,39 +371,148 @@ function sleep(ms) {
371
371
  return new Promise((r) => setTimeout(r, ms));
372
372
  }
373
373
 
374
+ // src/lib/device-auth.ts
375
+ import { spawn } from "node:child_process";
376
+ import { hostname, userInfo } from "node:os";
377
+ async function runDeviceFlow(opts) {
378
+ const base = opts.apiBase.replace(/\/$/, "");
379
+ const clientName = opts.clientName ?? defaultClientName();
380
+ const codeRes = await fetch(`${base}/api/auth/device/code`, {
381
+ method: "POST",
382
+ headers: { "Content-Type": "application/json" },
383
+ body: JSON.stringify({ client_name: clientName })
384
+ });
385
+ if (!codeRes.ok) {
386
+ throw new CliError(
387
+ `Failed to start device flow: ${codeRes.status} ${codeRes.statusText}`,
388
+ "DEVICE_CODE_FAILED",
389
+ 2
390
+ );
391
+ }
392
+ const codeBody = await codeRes.json();
393
+ opts.onPrompt?.({
394
+ userCode: codeBody.user_code,
395
+ verificationUri: codeBody.verification_uri,
396
+ verificationUriComplete: codeBody.verification_uri_complete
397
+ });
398
+ if (!opts.noBrowser) {
399
+ openBrowser(codeBody.verification_uri_complete);
400
+ }
401
+ const intervalMs = Math.max(1, opts.pollInterval ?? codeBody.interval) * 1e3;
402
+ const deadline = Date.now() + (opts.timeoutSeconds ?? 600) * 1e3;
403
+ let interval = intervalMs;
404
+ let attempt = 0;
405
+ while (Date.now() < deadline) {
406
+ attempt += 1;
407
+ opts.onPoll?.(attempt);
408
+ await sleep2(interval);
409
+ const res = await fetch(`${base}/api/auth/device/token`, {
410
+ method: "POST",
411
+ headers: { "Content-Type": "application/json" },
412
+ body: JSON.stringify({ device_code: codeBody.device_code })
413
+ });
414
+ if (res.status === 202) {
415
+ const pending = await safeJson(res);
416
+ if (pending?.interval) interval = Math.max(1, pending.interval) * 1e3;
417
+ continue;
418
+ }
419
+ if (res.status === 429) {
420
+ const body = await safeJson(res);
421
+ if (body?.interval) interval = Math.max(1, body.interval) * 1e3;
422
+ else interval = Math.min(6e4, interval * 2);
423
+ continue;
424
+ }
425
+ if (res.status === 410) {
426
+ throw new CliError(
427
+ "Device code expired. Run `rogeriq auth login` again.",
428
+ "DEVICE_CODE_EXPIRED",
429
+ 3
430
+ );
431
+ }
432
+ if (res.status === 403) {
433
+ throw new CliError(
434
+ "Authorization denied.",
435
+ "ACCESS_DENIED",
436
+ 3
437
+ );
438
+ }
439
+ if (!res.ok) {
440
+ const body = await safeJson(res);
441
+ throw new CliError(
442
+ body?.error ?? `Device flow failed: ${res.status} ${res.statusText}`,
443
+ body?.code ?? `HTTP_${res.status}`,
444
+ 2
445
+ );
446
+ }
447
+ const token = await res.json();
448
+ const apiKey = token.access_token ?? token.api_key;
449
+ if (!apiKey) {
450
+ throw new CliError("Server returned no API key", "MISSING_API_KEY", 2);
451
+ }
452
+ return { apiKey, orgId: token.org_id, scopes: token.scopes };
453
+ }
454
+ throw new CliError(
455
+ "Timed out waiting for browser authorization (10 minutes).",
456
+ "DEVICE_FLOW_TIMEOUT",
457
+ 3
458
+ );
459
+ }
460
+ function defaultClientName() {
461
+ try {
462
+ return `rogeriq-cli (${userInfo().username}@${hostname()})`.slice(0, 100);
463
+ } catch {
464
+ return "rogeriq-cli";
465
+ }
466
+ }
467
+ function openBrowser(url) {
468
+ const platform = process.platform;
469
+ const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
470
+ const args = platform === "win32" ? ["/c", "start", "", url] : [url];
471
+ try {
472
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
473
+ child.on("error", () => {
474
+ });
475
+ child.unref();
476
+ } catch {
477
+ }
478
+ }
479
+ function sleep2(ms) {
480
+ return new Promise((r) => setTimeout(r, ms));
481
+ }
482
+ async function safeJson(res) {
483
+ try {
484
+ return await res.json();
485
+ } catch {
486
+ return null;
487
+ }
488
+ }
489
+
374
490
  // src/commands/auth.ts
491
+ var DEFAULT_API_BASE2 = "https://api.rogeriq.com";
375
492
  var auth = {
376
493
  summary: "Manage CLI authentication",
377
494
  subcommands: {
378
495
  login: {
379
- summary: "Save an API key for future commands",
380
- usage: "rogeriq auth login --api-key riq_xxx [--api-base https://api.rogeriq.com]",
496
+ summary: "Browser-based login (or pass --api-key for CI / scripting)",
497
+ usage: "rogeriq auth login [--api-key riq_xxx] [--no-browser] [--api-base https://api.rogeriq.com]",
381
498
  flags: [
382
499
  { name: "api-key", type: "string" },
383
- { name: "api-base", type: "string" }
500
+ { name: "api-base", type: "string" },
501
+ { name: "no-browser", type: "boolean" },
502
+ { name: "client-name", type: "string" }
384
503
  ],
385
504
  async run({ flags }) {
505
+ const apiBase = flags["api-base"] ?? loadConfig().apiBase ?? DEFAULT_API_BASE2;
386
506
  const apiKey = flags["api-key"];
387
- const apiBase = flags["api-base"];
388
- if (!apiKey) {
389
- throw new CliError(
390
- "Pass --api-key riq_xxx. Generate one with `rogeriq keys create <name>` (after a one-time browser login) or in the dashboard.",
391
- "MISSING_API_KEY",
392
- 1
393
- );
394
- }
395
- if (!apiKey.startsWith("riq_")) {
396
- throw new CliError("API key must start with riq_", "INVALID_API_KEY", 1);
507
+ if (apiKey) {
508
+ await loginWithApiKey({ apiKey, apiBase });
509
+ return;
397
510
  }
398
- const updates = { apiKey };
399
- if (apiBase) updates["apiBase"] = apiBase;
400
- saveConfig(updates);
401
- const client = new ApiClient();
402
- const me = await client.get("/api/orgs");
403
- const list = me.raw?.data ?? [];
404
- if (list[0]?.id) saveConfig({ orgId: list[0].id });
405
- logInfo(`Saved API key to ${configPath()}.`);
406
- emit({ ok: true, org_id: list[0]?.id ?? null });
511
+ await loginWithBrowser({
512
+ apiBase,
513
+ noBrowser: flags["no-browser"] === true,
514
+ clientName: flags["client-name"]
515
+ });
407
516
  }
408
517
  },
409
518
  logout: {
@@ -419,7 +528,11 @@ var auth = {
419
528
  async run() {
420
529
  const cfg = loadConfig();
421
530
  if (!cfg.apiKey) {
422
- throw new CliError("Not logged in. Run `rogeriq auth login --api-key riq_xxx`.", "NOT_LOGGED_IN", 3);
531
+ throw new CliError(
532
+ "Not logged in. Run `rogeriq auth login`.",
533
+ "NOT_LOGGED_IN",
534
+ 3
535
+ );
423
536
  }
424
537
  const client = new ApiClient();
425
538
  const orgsRes = await client.get("/api/orgs");
@@ -435,6 +548,64 @@ var auth = {
435
548
  }
436
549
  }
437
550
  };
551
+ async function loginWithApiKey({ apiKey, apiBase }) {
552
+ if (!apiKey.startsWith("riq_")) {
553
+ throw new CliError("API key must start with riq_", "INVALID_API_KEY", 1);
554
+ }
555
+ saveConfig({ apiKey, apiBase });
556
+ const client = new ApiClient();
557
+ const me = await client.get("/api/orgs");
558
+ const list = me.raw?.data ?? [];
559
+ if (list[0]?.id) saveConfig({ orgId: list[0].id });
560
+ logInfo(`Saved API key to ${configPath()}.`);
561
+ emit({ ok: true, org_id: list[0]?.id ?? null });
562
+ }
563
+ async function loginWithBrowser(opts) {
564
+ const result = await runDeviceFlow({
565
+ apiBase: opts.apiBase,
566
+ noBrowser: opts.noBrowser,
567
+ clientName: opts.clientName,
568
+ onPrompt: ({ userCode, verificationUri, verificationUriComplete }) => {
569
+ const sep = "\u2500".repeat(40);
570
+ process.stderr.write(`
571
+ ${sep}
572
+ `);
573
+ process.stderr.write(` Open this URL in your browser:
574
+ `);
575
+ process.stderr.write(` ${verificationUriComplete}
576
+
577
+ `);
578
+ process.stderr.write(` Or visit ${verificationUri}
579
+ `);
580
+ process.stderr.write(` and enter code: ${userCode}
581
+ `);
582
+ process.stderr.write(`${sep}
583
+
584
+ `);
585
+ if (opts.noBrowser) {
586
+ process.stderr.write(" (Skipping browser open: --no-browser)\n\n");
587
+ } else {
588
+ process.stderr.write(" Opening browser\u2026\n\n");
589
+ }
590
+ },
591
+ onPoll: (attempt) => {
592
+ if (attempt === 1) process.stderr.write("Waiting for authorization\u2026\n");
593
+ }
594
+ });
595
+ saveConfig({
596
+ apiKey: result.apiKey,
597
+ apiBase: opts.apiBase,
598
+ orgId: result.orgId
599
+ });
600
+ logInfo(`
601
+ Authorized. Saved API key to ${configPath()}.`);
602
+ emit({
603
+ ok: true,
604
+ org_id: result.orgId ?? null,
605
+ scopes: result.scopes ?? null,
606
+ api_key_prefix: result.apiKey.slice(0, 12) + "..."
607
+ });
608
+ }
438
609
 
439
610
  // src/commands/config.ts
440
611
  var KEYS = ["apiKey", "apiBase", "orgId", "projectId"];
@@ -651,7 +822,7 @@ var keys = {
651
822
  },
652
823
  create: {
653
824
  summary: "Create an API key. Raw key returned ONCE \u2014 copy it now.",
654
- usage: "rogeriq keys create <name> [--scopes read,write,admin] [--expires-days 90]",
825
+ usage: "rogeriq keys create <name> [--scopes read,write,admin | --scopes kb:write,agent:read,...] [--expires-days 90]",
655
826
  flags: [
656
827
  { name: "org", type: "string" },
657
828
  { name: "scopes", type: "string" },
@@ -1022,7 +1193,7 @@ var agent = {
1022
1193
  async run({ flags }) {
1023
1194
  const client = new ApiClient({ projectId: flags["project"] });
1024
1195
  const pid = client.requireProject();
1025
- const res = await client.get(`/api/projects/${pid}/agent/status`);
1196
+ const res = await client.get(`/api/v1/projects/${pid}/agent/status`);
1026
1197
  emit(res.raw?.data ?? res.raw);
1027
1198
  }
1028
1199
  },
@@ -1044,11 +1215,11 @@ var agent = {
1044
1215
  if (flags["persona"]) body["persona"] = flags["persona"];
1045
1216
  if (flags["tone"]) body["tone"] = flags["tone"];
1046
1217
  if (Object.keys(body).length === 0) {
1047
- const res2 = await client.get(`/api/projects/${pid}/agent/config`);
1218
+ const res2 = await client.get(`/api/v1/projects/${pid}/agent/config`);
1048
1219
  emit(res2.raw?.data ?? res2.raw);
1049
1220
  return;
1050
1221
  }
1051
- const res = await client.patch(`/api/projects/${pid}/agent/config`, body);
1222
+ const res = await client.patch(`/api/v1/projects/${pid}/agent/config`, body);
1052
1223
  emit(res.raw?.data ?? res.raw);
1053
1224
  }
1054
1225
  },
@@ -1068,7 +1239,7 @@ var agent = {
1068
1239
  if (!conv) throw new CliError("Usage: rogeriq agent respond <conversation-id>", "BAD_USAGE", 1);
1069
1240
  const client = new ApiClient({ projectId: flags["project"] });
1070
1241
  const pid = client.requireProject();
1071
- const res = await client.post(`/api/projects/${pid}/agent/respond`, {
1242
+ const res = await client.post(`/api/v1/projects/${pid}/agent/respond`, {
1072
1243
  conversation_id: conv
1073
1244
  });
1074
1245
  emit(res.raw?.data ?? res.raw);
@@ -1082,7 +1253,7 @@ var agent = {
1082
1253
  if (!conv) throw new CliError("Usage: rogeriq agent classify <conversation-id>", "BAD_USAGE", 1);
1083
1254
  const client = new ApiClient({ projectId: flags["project"] });
1084
1255
  const pid = client.requireProject();
1085
- const res = await client.post(`/api/projects/${pid}/agent/classify`, {
1256
+ const res = await client.post(`/api/v1/projects/${pid}/agent/classify`, {
1086
1257
  conversation_id: conv
1087
1258
  });
1088
1259
  emit(res.raw?.data ?? res.raw);
@@ -1096,7 +1267,7 @@ var agent = {
1096
1267
  if (!conv) throw new CliError("Usage: rogeriq agent suggest <conversation-id>", "BAD_USAGE", 1);
1097
1268
  const client = new ApiClient({ projectId: flags["project"] });
1098
1269
  const pid = client.requireProject();
1099
- const res = await client.post(`/api/projects/${pid}/agent/suggest`, {
1270
+ const res = await client.post(`/api/v1/projects/${pid}/agent/suggest`, {
1100
1271
  conversation_id: conv
1101
1272
  });
1102
1273
  emit(res.raw?.data ?? res.raw);
@@ -1129,7 +1300,7 @@ var kb = {
1129
1300
  if (flags["limit"]) qs.set("limit", flags["limit"]);
1130
1301
  if (flags["cursor"]) qs.set("cursor", flags["cursor"]);
1131
1302
  const suffix = qs.toString() ? `?${qs}` : "";
1132
- const res = await client.get(`/api/projects/${pid}/kb/articles${suffix}`);
1303
+ const res = await client.get(`/api/v1/projects/${pid}/kb/articles${suffix}`);
1133
1304
  emit(res.raw?.data ?? res.raw);
1134
1305
  }
1135
1306
  },
@@ -1141,7 +1312,7 @@ var kb = {
1141
1312
  if (!id) throw new CliError("Usage: rogeriq kb get <article-id>", "BAD_USAGE", 1);
1142
1313
  const client = new ApiClient({ projectId: flags["project"] });
1143
1314
  const pid = client.requireProject();
1144
- const res = await client.get(`/api/projects/${pid}/kb/articles/${id}`);
1315
+ const res = await client.get(`/api/v1/projects/${pid}/kb/articles/${id}`);
1145
1316
  emit(res.raw?.data ?? res.raw);
1146
1317
  }
1147
1318
  },
@@ -1166,7 +1337,7 @@ var kb = {
1166
1337
  const body = { title, content };
1167
1338
  if (flags["category-id"]) body["category_id"] = flags["category-id"];
1168
1339
  if (flags["status"]) body["status"] = flags["status"];
1169
- const res = await client.post(`/api/projects/${pid}/kb/articles`, body);
1340
+ const res = await client.post(`/api/v1/projects/${pid}/kb/articles`, body);
1170
1341
  emit(res.raw?.data ?? res.raw);
1171
1342
  }
1172
1343
  },
@@ -1190,7 +1361,7 @@ var kb = {
1190
1361
  else if (flags["content"]) body["content"] = flags["content"];
1191
1362
  if (flags["status"]) body["status"] = flags["status"];
1192
1363
  if (Object.keys(body).length === 0) throw new CliError("No updates provided", "NO_UPDATES", 1);
1193
- const res = await client.patch(`/api/projects/${pid}/kb/articles/${id}`, body);
1364
+ const res = await client.patch(`/api/v1/projects/${pid}/kb/articles/${id}`, body);
1194
1365
  emit(res.raw?.data ?? res.raw);
1195
1366
  }
1196
1367
  },
@@ -1202,7 +1373,7 @@ var kb = {
1202
1373
  if (!id) throw new CliError("Usage: rogeriq kb delete <id>", "BAD_USAGE", 1);
1203
1374
  const client = new ApiClient({ projectId: flags["project"] });
1204
1375
  const pid = client.requireProject();
1205
- await client.del(`/api/projects/${pid}/kb/articles/${id}`);
1376
+ await client.del(`/api/v1/projects/${pid}/kb/articles/${id}`);
1206
1377
  emit({ ok: true, deleted: id });
1207
1378
  }
1208
1379
  },
@@ -1214,7 +1385,7 @@ var kb = {
1214
1385
  if (!id) throw new CliError("Usage: rogeriq kb publish <id>", "BAD_USAGE", 1);
1215
1386
  const client = new ApiClient({ projectId: flags["project"] });
1216
1387
  const pid = client.requireProject();
1217
- const res = await client.post(`/api/projects/${pid}/kb/articles/${id}/publish`);
1388
+ const res = await client.post(`/api/v1/projects/${pid}/kb/articles/${id}/publish`);
1218
1389
  emit(res.raw?.data ?? res.raw);
1219
1390
  }
1220
1391
  },
@@ -1226,7 +1397,7 @@ var kb = {
1226
1397
  if (!q) throw new CliError("Usage: rogeriq kb search <query>", "BAD_USAGE", 1);
1227
1398
  const client = new ApiClient({ projectId: flags["project"] });
1228
1399
  const pid = client.requireProject();
1229
- const res = await client.get(`/api/projects/${pid}/kb/search?q=${encodeURIComponent(q)}`);
1400
+ const res = await client.get(`/api/v1/projects/${pid}/kb/search?q=${encodeURIComponent(q)}`);
1230
1401
  emit(res.raw?.data ?? res.raw);
1231
1402
  }
1232
1403
  },
@@ -1246,7 +1417,7 @@ var kb = {
1246
1417
  statSync(target);
1247
1418
  const content = readFileSync2(target, "utf8");
1248
1419
  const title = target.split("/").pop().replace(/\.[^.]+$/, "");
1249
- const res = await client.post(`/api/projects/${pid}/kb/articles`, { title, content });
1420
+ const res = await client.post(`/api/v1/projects/${pid}/kb/articles`, { title, content });
1250
1421
  emit(res.raw?.data ?? res.raw);
1251
1422
  }
1252
1423
  }
@@ -1357,68 +1528,97 @@ var integrations = {
1357
1528
  summary: "Manage third-party integrations (Slack, Shopify, Stripe, Linear, GitHub, Discord)",
1358
1529
  subcommands: {
1359
1530
  list: {
1360
- summary: "List integrations connected to current project",
1531
+ summary: "List integrations installed on the current project",
1361
1532
  async run({ flags }) {
1362
1533
  const client = new ApiClient({ projectId: flags["project"] });
1363
1534
  const pid = client.requireProject();
1364
- const res = await client.get(`/api/projects/${pid}/integrations`);
1535
+ const res = await client.get(`/api/v1/projects/${pid}/integrations`);
1536
+ emit(res.raw?.data ?? res.raw);
1537
+ }
1538
+ },
1539
+ catalog: {
1540
+ summary: "Browse the full integration catalog (installed + available)",
1541
+ flags: [
1542
+ { name: "q", type: "string" },
1543
+ { name: "category", type: "string" },
1544
+ { name: "status", type: "string" }
1545
+ ],
1546
+ async run({ flags }) {
1547
+ const client = new ApiClient({ projectId: flags["project"] });
1548
+ const pid = client.requireProject();
1549
+ const qs = new URLSearchParams();
1550
+ if (flags["q"]) qs.set("q", flags["q"]);
1551
+ if (flags["category"]) qs.set("category", flags["category"]);
1552
+ if (flags["status"]) qs.set("status", flags["status"]);
1553
+ const suffix = qs.toString() ? `?${qs}` : "";
1554
+ const res = await client.get(`/api/v1/projects/${pid}/integrations/catalog${suffix}`);
1365
1555
  emit(res.raw?.data ?? res.raw);
1366
1556
  }
1367
1557
  },
1368
1558
  get: {
1369
- summary: "Get one integration",
1370
- usage: "rogeriq integrations get <integration-id>",
1559
+ summary: "Get catalog entry + install state for one provider",
1560
+ usage: "rogeriq integrations get <provider>",
1371
1561
  async run({ positional, flags }) {
1372
- const [id] = positional;
1373
- if (!id) throw new CliError("Usage: rogeriq integrations get <id>", "BAD_USAGE", 1);
1562
+ const [provider] = positional;
1563
+ if (!provider) throw new CliError("Usage: rogeriq integrations get <provider>", "BAD_USAGE", 1);
1374
1564
  const client = new ApiClient({ projectId: flags["project"] });
1375
1565
  const pid = client.requireProject();
1376
- const res = await client.get(`/api/projects/${pid}/integrations/${id}`);
1566
+ const res = await client.get(`/api/v1/projects/${pid}/integrations/catalog/${provider}`);
1377
1567
  emit(res.raw?.data ?? res.raw);
1378
1568
  }
1379
1569
  },
1380
1570
  connect: {
1381
- summary: "Get OAuth install URL for a provider (open in browser to complete)",
1382
- usage: "rogeriq integrations connect <slack|github|shopify|stripe|linear|discord>",
1571
+ summary: "Get the install URL for a provider (open in browser to complete OAuth)",
1572
+ usage: "rogeriq integrations connect <provider>",
1383
1573
  async run({ positional, flags }) {
1384
1574
  const [provider] = positional;
1385
1575
  if (!provider) throw new CliError("Usage: rogeriq integrations connect <provider>", "BAD_USAGE", 1);
1386
1576
  const client = new ApiClient({ projectId: flags["project"] });
1387
1577
  const pid = client.requireProject();
1388
- const res = await client.get(`/api/projects/${pid}/integrations/${provider}/install-url`);
1578
+ const res = await client.post(`/api/v1/projects/${pid}/integrations/${provider}/install-url`);
1389
1579
  emit(res.raw?.data ?? res.raw);
1390
1580
  }
1391
1581
  },
1392
- configure: {
1393
- summary: "Update integration config (provider-specific JSON payload)",
1394
- usage: "rogeriq integrations configure <id> --config '{...}'",
1395
- flags: [{ name: "config", type: "string" }],
1582
+ lifecycle: {
1583
+ summary: "Update integration lifecycle (status, display name, sidebar, settings)",
1584
+ usage: "rogeriq integrations lifecycle <provider> [--status active|paused] [--display-name ...] [--sidebar true|false] [--settings '{...}']",
1585
+ flags: [
1586
+ { name: "status", type: "string" },
1587
+ { name: "display-name", type: "string" },
1588
+ { name: "sidebar", type: "string" },
1589
+ { name: "settings", type: "string" }
1590
+ ],
1396
1591
  async run({ positional, flags }) {
1397
- const [id] = positional;
1398
- const raw = flags["config"];
1399
- if (!id || !raw) throw new CliError("Usage: rogeriq integrations configure <id> --config '{...}'", "BAD_USAGE", 1);
1400
- let body;
1401
- try {
1402
- body = JSON.parse(raw);
1403
- } catch {
1404
- throw new CliError("--config must be valid JSON", "BAD_JSON", 1);
1592
+ const [provider] = positional;
1593
+ if (!provider) throw new CliError("Usage: rogeriq integrations lifecycle <provider>", "BAD_USAGE", 1);
1594
+ const body = {};
1595
+ if (flags["status"]) body["status"] = flags["status"];
1596
+ if (flags["display-name"]) body["display_name"] = flags["display-name"];
1597
+ if (flags["sidebar"]) body["sidebar_enabled"] = flags["sidebar"] === "true";
1598
+ if (flags["settings"]) {
1599
+ try {
1600
+ body["settings"] = JSON.parse(flags["settings"]);
1601
+ } catch {
1602
+ throw new CliError("--settings must be valid JSON", "BAD_JSON", 1);
1603
+ }
1405
1604
  }
1605
+ if (Object.keys(body).length === 0) throw new CliError("No lifecycle changes provided", "NO_UPDATES", 1);
1406
1606
  const client = new ApiClient({ projectId: flags["project"] });
1407
1607
  const pid = client.requireProject();
1408
- const res = await client.patch(`/api/projects/${pid}/integrations/${id}`, body);
1608
+ const res = await client.patch(`/api/v1/projects/${pid}/integrations/${provider}/lifecycle`, body);
1409
1609
  emit(res.raw?.data ?? res.raw);
1410
1610
  }
1411
1611
  },
1412
1612
  disconnect: {
1413
1613
  summary: "Disconnect an integration",
1414
- usage: "rogeriq integrations disconnect <id>",
1614
+ usage: "rogeriq integrations disconnect <provider>",
1415
1615
  async run({ positional, flags }) {
1416
- const [id] = positional;
1417
- if (!id) throw new CliError("Usage: rogeriq integrations disconnect <id>", "BAD_USAGE", 1);
1616
+ const [provider] = positional;
1617
+ if (!provider) throw new CliError("Usage: rogeriq integrations disconnect <provider>", "BAD_USAGE", 1);
1418
1618
  const client = new ApiClient({ projectId: flags["project"] });
1419
1619
  const pid = client.requireProject();
1420
- await client.del(`/api/projects/${pid}/integrations/${id}`);
1421
- emit({ ok: true, disconnected: id });
1620
+ await client.del(`/api/v1/projects/${pid}/integrations/${provider}`);
1621
+ emit({ ok: true, disconnected: provider });
1422
1622
  }
1423
1623
  }
1424
1624
  }
@@ -1434,7 +1634,7 @@ var forms = {
1434
1634
  async run({ flags }) {
1435
1635
  const client = new ApiClient({ projectId: flags["project"] });
1436
1636
  const pid = client.requireProject();
1437
- const res = await client.get(`/api/projects/${pid}/forms`);
1637
+ const res = await client.get(`/api/v1/projects/${pid}/forms`);
1438
1638
  emit(res.raw?.data ?? res.raw);
1439
1639
  }
1440
1640
  },
@@ -1446,7 +1646,7 @@ var forms = {
1446
1646
  if (!id) throw new CliError("Usage: rogeriq forms get <id>", "BAD_USAGE", 1);
1447
1647
  const client = new ApiClient({ projectId: flags["project"] });
1448
1648
  const pid = client.requireProject();
1449
- const res = await client.get(`/api/projects/${pid}/forms/${id}`);
1649
+ const res = await client.get(`/api/v1/projects/${pid}/forms/${id}`);
1450
1650
  emit(res.raw?.data ?? res.raw);
1451
1651
  }
1452
1652
  },
@@ -1467,7 +1667,7 @@ var forms = {
1467
1667
  else throw new CliError("--schema or --schema-file required", "MISSING_SCHEMA", 1);
1468
1668
  const client = new ApiClient({ projectId: flags["project"] });
1469
1669
  const pid = client.requireProject();
1470
- const res = await client.post(`/api/projects/${pid}/forms`, { name, schema });
1670
+ const res = await client.post(`/api/v1/projects/${pid}/forms`, { name, schema });
1471
1671
  emit(res.raw?.data ?? res.raw);
1472
1672
  }
1473
1673
  },
@@ -1489,7 +1689,7 @@ var forms = {
1489
1689
  if (Object.keys(body).length === 0) throw new CliError("No updates provided", "NO_UPDATES", 1);
1490
1690
  const client = new ApiClient({ projectId: flags["project"] });
1491
1691
  const pid = client.requireProject();
1492
- const res = await client.patch(`/api/projects/${pid}/forms/${id}`, body);
1692
+ const res = await client.patch(`/api/v1/projects/${pid}/forms/${id}`, body);
1493
1693
  emit(res.raw?.data ?? res.raw);
1494
1694
  }
1495
1695
  },
@@ -1501,7 +1701,7 @@ var forms = {
1501
1701
  if (!id) throw new CliError("Usage: rogeriq forms archive <id>", "BAD_USAGE", 1);
1502
1702
  const client = new ApiClient({ projectId: flags["project"] });
1503
1703
  const pid = client.requireProject();
1504
- const res = await client.post(`/api/projects/${pid}/forms/${id}/archive`);
1704
+ const res = await client.post(`/api/v1/projects/${pid}/forms/${id}/archive`);
1505
1705
  emit(res.raw?.data ?? res.raw);
1506
1706
  }
1507
1707
  },
@@ -1513,7 +1713,7 @@ var forms = {
1513
1713
  if (!id) throw new CliError("Usage: rogeriq forms submissions <id>", "BAD_USAGE", 1);
1514
1714
  const client = new ApiClient({ projectId: flags["project"] });
1515
1715
  const pid = client.requireProject();
1516
- const res = await client.get(`/api/projects/${pid}/forms/${id}/submissions`);
1716
+ const res = await client.get(`/api/v1/projects/${pid}/forms/${id}/submissions`);
1517
1717
  emit(res.raw?.data ?? res.raw);
1518
1718
  }
1519
1719
  }
@@ -1529,7 +1729,7 @@ var beacons = {
1529
1729
  async run({ flags }) {
1530
1730
  const client = new ApiClient({ projectId: flags["project"] });
1531
1731
  const pid = client.requireProject();
1532
- const res = await client.get(`/api/projects/${pid}/beacons`);
1732
+ const res = await client.get(`/api/v1/projects/${pid}/beacons`);
1533
1733
  emit(res.raw?.data ?? res.raw);
1534
1734
  }
1535
1735
  },
@@ -1541,7 +1741,7 @@ var beacons = {
1541
1741
  if (!id) throw new CliError("Usage: rogeriq beacons get <id>", "BAD_USAGE", 1);
1542
1742
  const client = new ApiClient({ projectId: flags["project"] });
1543
1743
  const pid = client.requireProject();
1544
- const res = await client.get(`/api/projects/${pid}/beacons/${id}`);
1744
+ const res = await client.get(`/api/v1/projects/${pid}/beacons/${id}`);
1545
1745
  emit(res.raw?.data ?? res.raw);
1546
1746
  }
1547
1747
  },
@@ -1566,7 +1766,7 @@ var beacons = {
1566
1766
  }
1567
1767
  const client = new ApiClient({ projectId: flags["project"] });
1568
1768
  const pid = client.requireProject();
1569
- const res = await client.post(`/api/projects/${pid}/beacons`, body);
1769
+ const res = await client.post(`/api/v1/projects/${pid}/beacons`, body);
1570
1770
  emit(res.raw?.data ?? res.raw);
1571
1771
  }
1572
1772
  },
@@ -1586,7 +1786,7 @@ var beacons = {
1586
1786
  }
1587
1787
  const client = new ApiClient({ projectId: flags["project"] });
1588
1788
  const pid = client.requireProject();
1589
- const res = await client.patch(`/api/projects/${pid}/beacons/${id}`, body);
1789
+ const res = await client.patch(`/api/v1/projects/${pid}/beacons/${id}`, body);
1590
1790
  emit(res.raw?.data ?? res.raw);
1591
1791
  }
1592
1792
  },
@@ -1598,7 +1798,7 @@ var beacons = {
1598
1798
  if (!id) throw new CliError("Usage: rogeriq beacons archive <id>", "BAD_USAGE", 1);
1599
1799
  const client = new ApiClient({ projectId: flags["project"] });
1600
1800
  const pid = client.requireProject();
1601
- const res = await client.post(`/api/projects/${pid}/beacons/${id}/archive`);
1801
+ const res = await client.post(`/api/v1/projects/${pid}/beacons/${id}/archive`);
1602
1802
  emit(res.raw?.data ?? res.raw);
1603
1803
  }
1604
1804
  }
@@ -1912,7 +2112,7 @@ var tools = [
1912
2112
  },
1913
2113
  async call(client, args) {
1914
2114
  const pid = client.requireProject();
1915
- const res = await client.get(`/api/projects/${pid}/kb/search?q=${encodeURIComponent(String(args["q"]))}`);
2115
+ const res = await client.get(`/api/v1/projects/${pid}/kb/search?q=${encodeURIComponent(String(args["q"]))}`);
1916
2116
  return res.raw;
1917
2117
  }
1918
2118
  },
@@ -1922,7 +2122,7 @@ var tools = [
1922
2122
  inputSchema: { type: "object", properties: {} },
1923
2123
  async call(client) {
1924
2124
  const pid = client.requireProject();
1925
- const res = await client.get(`/api/projects/${pid}/kb/articles`);
2125
+ const res = await client.get(`/api/v1/projects/${pid}/kb/articles`);
1926
2126
  return res.raw;
1927
2127
  }
1928
2128
  },
@@ -1941,7 +2141,7 @@ var tools = [
1941
2141
  },
1942
2142
  async call(client, args) {
1943
2143
  const pid = client.requireProject();
1944
- const res = await client.post(`/api/projects/${pid}/kb/articles`, args);
2144
+ const res = await client.post(`/api/v1/projects/${pid}/kb/articles`, args);
1945
2145
  return res.raw;
1946
2146
  }
1947
2147
  },
@@ -1955,7 +2155,7 @@ var tools = [
1955
2155
  },
1956
2156
  async call(client, args) {
1957
2157
  const pid = client.requireProject();
1958
- const res = await client.post(`/api/projects/${pid}/agent/respond`, args);
2158
+ const res = await client.post(`/api/v1/projects/${pid}/agent/respond`, args);
1959
2159
  return res.raw;
1960
2160
  }
1961
2161
  },
@@ -2021,7 +2221,57 @@ var tools = [
2021
2221
  inputSchema: { type: "object", properties: {} },
2022
2222
  async call(client) {
2023
2223
  const pid = client.requireProject();
2024
- const res = await client.get(`/api/projects/${pid}/integrations`);
2224
+ const res = await client.get(`/api/v1/projects/${pid}/integrations`);
2225
+ return res.raw;
2226
+ }
2227
+ },
2228
+ {
2229
+ name: "browse_integration_catalog",
2230
+ description: "Browse all available integrations with install state. Optional category/status/query filters.",
2231
+ inputSchema: {
2232
+ type: "object",
2233
+ properties: {
2234
+ q: { type: "string", description: "Free-text search" },
2235
+ category: { type: "string" },
2236
+ status: { type: "string" }
2237
+ }
2238
+ },
2239
+ async call(client, args) {
2240
+ const pid = client.requireProject();
2241
+ const qs = new URLSearchParams();
2242
+ for (const k of ["q", "category", "status"]) {
2243
+ const v = args[k];
2244
+ if (v !== void 0 && v !== null) qs.set(k, String(v));
2245
+ }
2246
+ const res = await client.get(`/api/v1/projects/${pid}/integrations/catalog?${qs}`);
2247
+ return res.raw;
2248
+ }
2249
+ },
2250
+ {
2251
+ name: "get_integration_install_url",
2252
+ description: "Return a browser URL the user should open to install or reconnect an integration.",
2253
+ inputSchema: {
2254
+ type: "object",
2255
+ properties: { provider: { type: "string", description: "e.g. slack, github, shopify" } },
2256
+ required: ["provider"]
2257
+ },
2258
+ async call(client, args) {
2259
+ const pid = client.requireProject();
2260
+ const res = await client.post(`/api/v1/projects/${pid}/integrations/${args["provider"]}/install-url`);
2261
+ return res.raw;
2262
+ }
2263
+ },
2264
+ {
2265
+ name: "disconnect_integration",
2266
+ description: "Disconnect an integration (status=disconnected, sidebar hidden).",
2267
+ inputSchema: {
2268
+ type: "object",
2269
+ properties: { provider: { type: "string" } },
2270
+ required: ["provider"]
2271
+ },
2272
+ async call(client, args) {
2273
+ const pid = client.requireProject();
2274
+ const res = await client.del(`/api/v1/projects/${pid}/integrations/${args["provider"]}`);
2025
2275
  return res.raw;
2026
2276
  }
2027
2277
  },
@@ -2090,7 +2340,7 @@ async function runMcpServer() {
2090
2340
  return {
2091
2341
  protocolVersion: "2024-11-05",
2092
2342
  capabilities: { tools: {} },
2093
- serverInfo: { name: "rogeriq", version: "0.1.0" }
2343
+ serverInfo: { name: "rogeriq", version: "0.3.0" }
2094
2344
  };
2095
2345
  case "notifications/initialized":
2096
2346
  return void 0;
@@ -2213,7 +2463,7 @@ var GLOBAL_FLAGS = [
2213
2463
  { name: "version", alias: "v", type: "boolean" },
2214
2464
  { name: "project", type: "string" }
2215
2465
  ];
2216
- var VERSION = "0.1.0";
2466
+ var VERSION = "0.3.0";
2217
2467
  async function main() {
2218
2468
  const argv = process.argv.slice(2);
2219
2469
  const { positional, flags } = parseArgs(argv, GLOBAL_FLAGS);
@@ -2226,7 +2476,7 @@ async function main() {
2226
2476
  `);
2227
2477
  return;
2228
2478
  }
2229
- if (positional.length === 0 || flagBool(flags, "help")) {
2479
+ if (positional.length === 0) {
2230
2480
  printRootHelp();
2231
2481
  return;
2232
2482
  }
@@ -2252,6 +2502,9 @@ async function main() {
2252
2502
  const cmdParsed = parseArgs(cmdArgsRaw, [...GLOBAL_FLAGS, ...cmd.flags ?? []]);
2253
2503
  const project = flagString(cmdParsed.flags, "project") ?? flagString(flags, "project");
2254
2504
  if (project) cmdParsed.flags["project"] = project;
2505
+ if (flagBool(flags, "help")) cmdParsed.flags["help"] = true;
2506
+ if (flagBool(flags, "json")) cmdParsed.flags["json"] = true;
2507
+ if (flagBool(flags, "quiet")) cmdParsed.flags["quiet"] = true;
2255
2508
  if (flagBool(cmdParsed.flags, "help")) {
2256
2509
  printCommandHelp(groupName, subName ?? "", cmd);
2257
2510
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogeriq/cli",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Command-line interface for RogerIQ. Manage conversations, projects, knowledge base, widget, integrations, and webhooks from your terminal or AI agents.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,18 +10,21 @@
10
10
  "files": [
11
11
  "dist",
12
12
  "README.md",
13
- "AGENTS.md"
13
+ "AGENTS.md",
14
+ "CHANGELOG.md"
14
15
  ],
15
16
  "scripts": {
16
17
  "build": "esbuild src/index.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/index.mjs --banner:js='#!/usr/bin/env node' --packages=external",
17
18
  "dev": "esbuild src/index.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/index.mjs --banner:js='#!/usr/bin/env node' --watch",
18
- "typecheck": "tsc --noEmit"
19
+ "typecheck": "tsc --noEmit",
20
+ "test": "vitest run"
19
21
  },
20
22
  "dependencies": {},
21
23
  "devDependencies": {
22
24
  "@types/node": "^22.10.0",
23
25
  "esbuild": "^0.24.0",
24
- "typescript": "^5.9.3"
26
+ "typescript": "^5.9.3",
27
+ "vitest": "^4.1.0"
25
28
  },
26
29
  "engines": {
27
30
  "node": ">=18"