hissuno 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -302,64 +302,73 @@ hissuno --json add issues
302
302
 
303
303
  **feedback** - one or more conversation messages (role + content), name (optional), tags (optional)
304
304
 
305
- ### `hissuno integrate [platform] [action]`
305
+ ### `hissuno integrations`
306
306
 
307
- Manage third-party integrations. Supported platforms: `intercom`, `gong`, `zendesk`, `slack`, `github`, `jira`, `linear`.
307
+ Manage third-party integrations. Supported platforms: `intercom`, `gong`, `zendesk`, `slack`, `github`, `jira`, `linear`, `fathom`, `hubspot`, `notion`.
308
308
 
309
309
  ```bash
310
310
  # List all integrations with their connection status
311
- hissuno integrate
311
+ hissuno integrations list
312
312
 
313
313
  # Interactive wizard for a specific platform
314
- hissuno integrate intercom
314
+ hissuno integrations intercom
315
315
 
316
316
  # Check detailed status
317
- hissuno integrate gong status
317
+ hissuno integrations status gong
318
318
 
319
319
  # Connect a platform
320
- hissuno integrate slack connect
320
+ hissuno integrations add slack
321
321
 
322
322
  # Connect Gong with inline credentials (non-interactive)
323
- hissuno integrate gong connect \
323
+ hissuno integrations add gong \
324
324
  --access-key YOUR_KEY \
325
325
  --access-key-secret YOUR_SECRET \
326
326
  --sync-frequency 24h
327
327
 
328
328
  # Connect Zendesk
329
- hissuno integrate zendesk connect \
329
+ hissuno integrations add zendesk \
330
330
  --subdomain mycompany \
331
331
  --email admin@mycompany.com \
332
332
  --api-token YOUR_TOKEN
333
333
 
334
334
  # Connect Intercom with an API token
335
- hissuno integrate intercom connect --access-token YOUR_TOKEN
335
+ hissuno integrations add intercom --access-token YOUR_TOKEN
336
+
337
+ # Connect Fathom with an API key
338
+ hissuno integrations add fathom --api-key YOUR_KEY --sync-frequency 24h
339
+
340
+ # Connect HubSpot with a private app token
341
+ hissuno integrations add hubspot --access-token YOUR_TOKEN --overwrite-policy fill_nulls
342
+
343
+ # Connect Notion with an internal integration token
344
+ hissuno integrations add notion --access-token YOUR_TOKEN
336
345
 
337
346
  # Update sync settings
338
- hissuno integrate intercom configure
347
+ hissuno integrations configure intercom
339
348
 
340
349
  # Trigger a manual sync
341
- hissuno integrate gong sync
342
- hissuno integrate zendesk sync --mode full
350
+ hissuno integrations sync gong
351
+ hissuno integrations sync zendesk --mode full
343
352
 
344
353
  # Disconnect an integration
345
- hissuno integrate github disconnect
354
+ hissuno integrations disconnect github
346
355
 
347
356
  # JSON output
348
- hissuno --json integrate
357
+ hissuno --json integrations list
349
358
  ```
350
359
 
351
- #### Actions
360
+ #### Subcommands
352
361
 
353
- | Action | Description |
354
- |--------|-------------|
355
- | _(none)_ | Interactive wizard - shows status and prompts for next steps |
356
- | `status` | Show detailed connection status |
357
- | `connect` | Connect the platform (OAuth or token-based) |
358
- | `configure` | Update settings (sync frequency, auto-sync toggle) |
359
- | `sync` | Trigger a manual sync (Intercom, Gong, Zendesk only) |
360
- | `disconnect` | Disconnect the integration |
362
+ | Subcommand | Args | Description |
363
+ |------------|------|-------------|
364
+ | `list` | | List all integrations with connection status |
365
+ | `add` | `<platform>` | Connect a platform (OAuth or token-based) |
366
+ | `status` | `<platform>` | Show detailed connection status |
367
+ | `configure` | `<platform>` | Update settings (sync frequency, auto-sync toggle) |
368
+ | `sync` | `<platform>` | Trigger a manual sync (Intercom, Gong, Zendesk, Fathom, HubSpot) |
369
+ | `disconnect` | `<platform>` | Disconnect the integration |
361
370
 
362
- #### Connection options
371
+ #### Connection options (for `add`)
363
372
 
364
373
  | Option | Platform | Description |
365
374
  |--------|----------|-------------|
@@ -369,11 +378,13 @@ hissuno --json integrate
369
378
  | `--subdomain <subdomain>` | Zendesk | Zendesk subdomain |
370
379
  | `--email <email>` | Zendesk | Admin email |
371
380
  | `--api-token <token>` | Zendesk | API token |
372
- | `--access-token <token>` | Intercom | Access token |
373
- | `--sync-frequency <freq>` | Gong, Zendesk, Intercom | Sync frequency: `manual`, `1h`, `6h`, `24h` |
374
- | `--mode <mode>` | sync action | Sync mode: `incremental` (default), `full` |
381
+ | `--access-token <token>` | Intercom, HubSpot, Notion | Access token |
382
+ | `--api-key <key>` | Fathom | API key |
383
+ | `--overwrite-policy <policy>` | HubSpot | `fill_nulls`, `hubspot_wins`, `never_overwrite` |
384
+ | `--sync-frequency <freq>` | Gong, Zendesk, Intercom, Fathom, HubSpot | Sync frequency: `manual`, `1h`, `6h`, `24h` |
385
+ | `--mode <mode>` | `sync` subcommand | Sync mode: `incremental` (default), `full` |
375
386
 
376
- OAuth-based platforms (Slack, GitHub, Jira, Linear) open your browser to complete authorization.
387
+ OAuth-based platforms (Slack, GitHub, Jira, Linear) open your browser to complete authorization. HubSpot, Intercom, and Notion support both OAuth and token-based authentication.
377
388
 
378
389
  ### `hissuno types`
379
390
 
package/dist/index.js CHANGED
@@ -95,9 +95,6 @@ function formatResourceList(type, items, total) {
95
95
  case "knowledge":
96
96
  lines.push(formatKnowledgeRow(item, name));
97
97
  break;
98
- case "sources":
99
- lines.push(formatSourceRow(item, name));
100
- break;
101
98
  case "scopes":
102
99
  lines.push(formatScopeRow(item, name));
103
100
  break;
@@ -175,14 +172,6 @@ function formatScopeRow(item, name) {
175
172
  ${parts.join(" ")}`;
176
173
  }
177
174
  function formatKnowledgeRow(item, name) {
178
- const desc = item.description ? dimText(truncate(String(item.description), 60)) : "";
179
- const sources = item.sourceCount != null ? `${item.sourceCount} sources` : "";
180
- const lastAnalyzed = item.lastAnalyzedAt ? `analyzed ${formatDate(item.lastAnalyzedAt)}` : dimText("not analyzed");
181
- const parts = [desc, sources, lastAnalyzed].filter(Boolean);
182
- return ` ${itemName(name)}
183
- ${parts.join(" ")}`;
184
- }
185
- function formatSourceRow(item, name) {
186
175
  const id = dimText(truncate(String(item.id ?? ""), 12));
187
176
  const type = item.type ? badge(String(item.type), MAGENTA) : "";
188
177
  const status = item.status ? label(String(item.status)) : "";
@@ -213,9 +202,6 @@ function formatResourceDetail(type, item, extra) {
213
202
  case "knowledge":
214
203
  formatKnowledgeDetail(lines, item);
215
204
  break;
216
- case "sources":
217
- formatSourceDetail(lines, item);
218
- break;
219
205
  case "scopes":
220
206
  formatScopeDetail(lines, item);
221
207
  break;
@@ -295,20 +281,6 @@ function formatScopeDetail(lines, item) {
295
281
  }
296
282
  }
297
283
  function formatKnowledgeDetail(lines, item) {
298
- if (item.description) lines.push(`${label("Description:")} ${item.description}`);
299
- if (item.sourceCount != null) lines.push(`${label("Sources:")} ${item.sourceCount}`);
300
- if (item.lastAnalyzedAt) lines.push(`${label("Last analyzed:")} ${formatDate(item.lastAnalyzedAt)}`);
301
- if (item.compiled_at) lines.push(`${label("Compiled:")} ${formatDate(item.compiled_at)}`);
302
- const sources = item.sources;
303
- if (sources && sources.length > 0) {
304
- lines.push("", `${BOLD}Sources:${RESET}`);
305
- for (const src of sources) {
306
- const parts = [src.type, src.status].filter(Boolean).join(" - ");
307
- lines.push(` - ${src.name ?? "Unnamed"}${parts ? ` (${parts})` : ""}`);
308
- }
309
- }
310
- }
311
- function formatSourceDetail(lines, item) {
312
284
  if (item.type) lines.push(`${label("Type:")} ${badge(String(item.type), MAGENTA)}`);
313
285
  if (item.status) lines.push(`${label("Status:")} ${item.status}`);
314
286
  if (item.enabled !== void 0) lines.push(`${label("Enabled:")} ${item.enabled ? badge("Yes", GREEN) : badge("No", RED)}`);
@@ -361,14 +333,10 @@ function formatResourceTypes() {
361
333
  heading("Hissuno Resource Types"),
362
334
  "",
363
335
  `${BOLD}knowledge${RESET}`,
364
- " Knowledge packages (grouped, compiled knowledge bundles).",
336
+ " Knowledge sources (codebases, documents, URLs, Notion pages).",
365
337
  ` ${label("Filters:")} (none)`,
366
338
  ` ${label("Search:")} Semantic vector search across all knowledge chunks`,
367
339
  "",
368
- `${BOLD}sources${RESET}`,
369
- " Individual knowledge sources (codebases, documents, URLs, Notion pages).",
370
- ` ${label("Filters:")} (none)`,
371
- "",
372
340
  `${BOLD}feedback${RESET}`,
373
341
  " Customer feedback sessions (conversations from widget, Slack, Intercom, etc.).",
374
342
  ` ${label("Filters:")} --source, --status, --tags, --contact-id, --search`,
@@ -400,17 +368,11 @@ function formatResourceTypes() {
400
368
  // src/commands/types.ts
401
369
  var RESOURCE_TYPE_DEFINITIONS = {
402
370
  knowledge: {
403
- description: "Knowledge packages (grouped, compiled knowledge bundles).",
371
+ description: "Knowledge sources (codebases, documents, URLs, Notion pages).",
404
372
  filters: [],
405
373
  search: "Semantic vector search across all knowledge chunks",
406
374
  add: null
407
375
  },
408
- sources: {
409
- description: "Individual knowledge sources (codebases, documents, URLs, Notion pages).",
410
- filters: [],
411
- search: null,
412
- add: null
413
- },
414
376
  feedback: {
415
377
  description: "Customer feedback sessions (conversations from widget, Slack, Intercom, etc.).",
416
378
  filters: ["source", "status", "tags", "contact_id", "search"],
@@ -640,14 +602,13 @@ function resolveCustomerType(opt) {
640
602
 
641
603
  // src/commands/list.ts
642
604
  var TYPE_ENDPOINTS = {
643
- knowledge: { path: "/api/knowledge/packages", key: "packages" },
644
- sources: { path: "/api/knowledge/sources", key: "sources" },
605
+ knowledge: { path: "/api/knowledge/sources", key: "sources" },
645
606
  feedback: { path: "/api/sessions", key: "sessions" },
646
607
  issues: { path: "/api/issues", key: "issues" },
647
608
  customers: { path: "/api/contacts", key: "contacts" },
648
609
  scopes: { path: "/api/product-scopes", key: "scopes" }
649
610
  };
650
- var listCommand = new Command2("list").description("List resources of a given type").argument("<type>", "Resource type: knowledge, sources, feedback, issues, customers, scopes").option("--source <source>", "Filter feedback by source (widget|slack|intercom|gong|api|manual)").option("--status <status>", "Filter by status").option("--tags <tags>", "Filter feedback by tags (comma-separated)").option("--contact-id <id>", "Filter feedback by contact ID").option("--search <query>", "Text search filter").option("--issue-type <type>", "Filter issues by type (bug|feature_request|change_request)").option("--priority <priority>", "Filter issues by priority (low|medium|high)").option("--company-id <id>", "Filter contacts by company ID").option("--role <role>", "Filter contacts by role").option("--customer-type <type>", "Customer sub-type: contacts (default) or companies").option("--stage <stage>", "Filter companies by stage (prospect|onboarding|active|churned|expansion)").option("--industry <industry>", "Filter companies by industry").option("--limit <n>", "Max results (default: 20)", "20").action(async (type, opts, cmd) => {
611
+ var listCommand = new Command2("list").description("List resources of a given type").argument("<type>", "Resource type: knowledge, feedback, issues, customers, scopes").option("--source <source>", "Filter feedback by source (widget|slack|intercom|gong|api|manual)").option("--status <status>", "Filter by status").option("--tags <tags>", "Filter feedback by tags (comma-separated)").option("--contact-id <id>", "Filter feedback by contact ID").option("--search <query>", "Text search filter").option("--issue-type <type>", "Filter issues by type (bug|feature_request|change_request)").option("--priority <priority>", "Filter issues by priority (low|medium|high)").option("--company-id <id>", "Filter contacts by company ID").option("--role <role>", "Filter contacts by role").option("--customer-type <type>", "Customer sub-type: contacts (default) or companies").option("--stage <stage>", "Filter companies by stage (prospect|onboarding|active|churned|expansion)").option("--industry <industry>", "Filter companies by industry").option("--limit <n>", "Max results (default: 20)", "20").action(async (type, opts, cmd) => {
651
612
  const config = requireConfig();
652
613
  const jsonMode = cmd.parent?.opts().json;
653
614
  const validTypes = Object.keys(TYPE_ENDPOINTS);
@@ -717,21 +678,19 @@ function cliTypeToEntityType(type, customerType) {
717
678
  contact: "contact",
718
679
  contacts: "contact",
719
680
  knowledge: "knowledge_source",
720
- sources: "knowledge_source",
721
681
  scopes: "product_scope",
722
682
  scope: "product_scope"
723
683
  };
724
684
  return map[type] ?? type;
725
685
  }
726
686
  var TYPE_ENDPOINTS2 = {
727
- knowledge: { path: (id) => `/api/knowledge/packages/${id}`, key: "package" },
728
- sources: { path: (id) => `/api/knowledge/sources/${id}`, key: "source" },
687
+ knowledge: { path: (id) => `/api/knowledge/sources/${id}`, key: "source" },
729
688
  feedback: { path: (id) => `/api/sessions/${id}`, key: "session" },
730
689
  issues: { path: (id) => `/api/issues/${id}`, key: "issue" },
731
690
  customers: { path: (id) => `/api/contacts/${id}`, key: "contact" },
732
691
  scopes: { path: (id) => `/api/product-scopes/${id}`, key: "scope" }
733
692
  };
734
- var getCommand = new Command3("get").description("Get full details of a specific resource").argument("<type>", "Resource type: knowledge, sources, feedback, issues, customers, scopes").argument("<id>", "Resource ID").option("--customer-type <type>", "Customer sub-type: contacts (default) or companies").action(async (type, id, opts, cmd) => {
693
+ var getCommand = new Command3("get").description("Get full details of a specific resource").argument("<type>", "Resource type: knowledge, feedback, issues, customers, scopes").argument("<id>", "Resource ID").option("--customer-type <type>", "Customer sub-type: contacts (default) or companies").action(async (type, id, opts, cmd) => {
735
694
  const config = requireConfig();
736
695
  const jsonMode = cmd.parent?.opts().json;
737
696
  const validTypes = Object.keys(TYPE_ENDPOINTS2);
@@ -1054,7 +1013,7 @@ var addCommand = new Command5("add").description("Create a new resource interact
1054
1013
  }
1055
1014
  });
1056
1015
 
1057
- // src/commands/integrate.ts
1016
+ // src/commands/integrations.ts
1058
1017
  import { Command as Command6 } from "commander";
1059
1018
  import { input as input2, select as select2, confirm as confirm2, password } from "@inquirer/prompts";
1060
1019
 
@@ -1066,10 +1025,10 @@ function openBrowser(url) {
1066
1025
  exec(`${cmd} "${url}"`);
1067
1026
  }
1068
1027
 
1069
- // src/commands/integrate.ts
1070
- var PLATFORMS = ["intercom", "gong", "zendesk", "slack", "github", "jira", "linear"];
1028
+ // src/commands/integrations.ts
1029
+ var PLATFORMS = ["intercom", "gong", "zendesk", "slack", "github", "jira", "linear", "fathom", "hubspot", "notion"];
1071
1030
  var OAUTH_PLATFORMS = ["slack", "github", "jira", "linear"];
1072
- var SYNCABLE_PLATFORMS = ["intercom", "gong", "zendesk"];
1031
+ var SYNCABLE_PLATFORMS = ["intercom", "gong", "zendesk", "fathom", "hubspot"];
1073
1032
  var PLATFORM_LABELS = {
1074
1033
  intercom: "Intercom",
1075
1034
  gong: "Gong",
@@ -1077,10 +1036,19 @@ var PLATFORM_LABELS = {
1077
1036
  slack: "Slack",
1078
1037
  github: "GitHub",
1079
1038
  jira: "Jira",
1080
- linear: "Linear"
1039
+ linear: "Linear",
1040
+ fathom: "Fathom",
1041
+ hubspot: "HubSpot",
1042
+ notion: "Notion"
1081
1043
  };
1082
- async function getProjectId(config) {
1083
- return resolveProjectId(config);
1044
+ var SYNC_FREQUENCY_CHOICES = [
1045
+ { value: "manual", name: "Manual" },
1046
+ { value: "1h", name: "Every hour" },
1047
+ { value: "6h", name: "Every 6 hours" },
1048
+ { value: "24h", name: "Every 24 hours" }
1049
+ ];
1050
+ async function promptSyncFrequency(existing) {
1051
+ return existing || await select2({ message: "Sync frequency:", choices: [...SYNC_FREQUENCY_CHOICES], default: "24h" });
1084
1052
  }
1085
1053
  async function getStatus(config, platform2, projectId) {
1086
1054
  return apiCall(config, "GET", `/api/integrations/${platform2}?projectId=${projectId}`);
@@ -1137,6 +1105,28 @@ function formatStatus(platform2, data) {
1137
1105
  if (data.lastSyncStatus) lines.push(`**Last Sync Status:** ${data.lastSyncStatus}`);
1138
1106
  lines.push(`**Tickets Synced:** ${data.lastSyncTicketsCount ?? 0}`);
1139
1107
  }
1108
+ if (platform2 === "fathom") {
1109
+ if (data.syncFrequency) lines.push(`**Sync Frequency:** ${data.syncFrequency}`);
1110
+ lines.push(`**Sync Enabled:** ${data.syncEnabled ? "Yes" : "No"}`);
1111
+ if (data.lastSyncAt) lines.push(`**Last Sync:** ${data.lastSyncAt}`);
1112
+ if (data.lastSyncStatus) lines.push(`**Last Sync Status:** ${data.lastSyncStatus}`);
1113
+ lines.push(`**Meetings Synced:** ${data.lastSyncMeetingsCount ?? 0}`);
1114
+ }
1115
+ if (platform2 === "hubspot") {
1116
+ if (data.hubName) lines.push(`**Hub:** ${data.hubName}`);
1117
+ if (data.authMethod) lines.push(`**Auth Method:** ${data.authMethod}`);
1118
+ if (data.syncFrequency) lines.push(`**Sync Frequency:** ${data.syncFrequency}`);
1119
+ lines.push(`**Sync Enabled:** ${data.syncEnabled ? "Yes" : "No"}`);
1120
+ if (data.overwritePolicy) lines.push(`**Overwrite Policy:** ${data.overwritePolicy}`);
1121
+ if (data.lastSyncAt) lines.push(`**Last Sync:** ${data.lastSyncAt}`);
1122
+ if (data.lastSyncStatus) lines.push(`**Last Sync Status:** ${data.lastSyncStatus}`);
1123
+ lines.push(`**Companies Synced:** ${data.lastSyncCompaniesCount ?? 0}`);
1124
+ lines.push(`**Contacts Synced:** ${data.lastSyncContactsCount ?? 0}`);
1125
+ }
1126
+ if (platform2 === "notion") {
1127
+ if (data.workspaceName) lines.push(`**Workspace:** ${data.workspaceName}`);
1128
+ if (data.authMethod) lines.push(`**Auth Method:** ${data.authMethod}`);
1129
+ }
1140
1130
  const stats = data.stats;
1141
1131
  if (stats?.totalSynced != null) {
1142
1132
  lines.push(`**Total Synced:** ${stats.totalSynced}`);
@@ -1165,16 +1155,7 @@ async function connectGong(config, projectId, opts) {
1165
1155
  const accessKey = opts.accessKey || await input2({ message: "Access Key:", validate: (v) => v.length > 0 || "Required" });
1166
1156
  const accessKeySecret = opts.accessKeySecret || await password({ message: "Access Key Secret:", mask: "*", validate: (v) => v.length > 0 || "Required" });
1167
1157
  const baseUrl = opts.baseUrl || await input2({ message: "Base URL:", default: "https://api.gong.io" });
1168
- const syncFrequency = opts.syncFrequency || await select2({
1169
- message: "Sync frequency:",
1170
- choices: [
1171
- { value: "manual", name: "Manual" },
1172
- { value: "1h", name: "Every hour" },
1173
- { value: "6h", name: "Every 6 hours" },
1174
- { value: "24h", name: "Every 24 hours" }
1175
- ],
1176
- default: "24h"
1177
- });
1158
+ const syncFrequency = await promptSyncFrequency(opts.syncFrequency);
1178
1159
  console.log("\nValidating credentials...");
1179
1160
  const result = await apiCall(config, "POST", "/api/integrations/gong/connect", {
1180
1161
  projectId,
@@ -1195,16 +1176,7 @@ async function connectZendesk(config, projectId, opts) {
1195
1176
  const subdomain = opts.subdomain || await input2({ message: "Zendesk subdomain (e.g., mycompany):", validate: (v) => v.length > 0 || "Required" });
1196
1177
  const email = opts.email || await input2({ message: "Admin email:", validate: (v) => v.includes("@") || "Must be a valid email" });
1197
1178
  const apiToken = opts.apiToken || await password({ message: "API token:", mask: "*", validate: (v) => v.length > 0 || "Required" });
1198
- const syncFrequency = opts.syncFrequency || await select2({
1199
- message: "Sync frequency:",
1200
- choices: [
1201
- { value: "manual", name: "Manual" },
1202
- { value: "1h", name: "Every hour" },
1203
- { value: "6h", name: "Every 6 hours" },
1204
- { value: "24h", name: "Every 24 hours" }
1205
- ],
1206
- default: "24h"
1207
- });
1179
+ const syncFrequency = await promptSyncFrequency(opts.syncFrequency);
1208
1180
  console.log("\nValidating credentials...");
1209
1181
  const result = await apiCall(config, "POST", "/api/integrations/zendesk/connect", {
1210
1182
  projectId,
@@ -1233,16 +1205,7 @@ async function connectIntercom(config, projectId, opts) {
1233
1205
  return connectOAuth(config, "intercom", projectId);
1234
1206
  }
1235
1207
  const accessToken = opts.accessToken || await password({ message: "Access Token:", mask: "*", validate: (v) => v.length > 0 || "Required" });
1236
- const syncFrequency = opts.syncFrequency || await select2({
1237
- message: "Sync frequency:",
1238
- choices: [
1239
- { value: "manual", name: "Manual" },
1240
- { value: "1h", name: "Every hour" },
1241
- { value: "6h", name: "Every 6 hours" },
1242
- { value: "24h", name: "Every 24 hours" }
1243
- ],
1244
- default: "24h"
1245
- });
1208
+ const syncFrequency = await promptSyncFrequency(opts.syncFrequency);
1246
1209
  console.log("\nValidating credentials...");
1247
1210
  const result = await apiCall(config, "POST", "/api/integrations/intercom/connect", {
1248
1211
  projectId,
@@ -1257,17 +1220,88 @@ async function connectIntercom(config, projectId, opts) {
1257
1220
  error(`Connection failed: ${data.error || "Unknown error"}`);
1258
1221
  return false;
1259
1222
  }
1223
+ async function connectFathom(config, projectId, opts) {
1224
+ const apiKey = opts.apiKey || await password({ message: "Fathom API Key:", mask: "*", validate: (v) => v.length > 0 || "Required" });
1225
+ const syncFrequency = await promptSyncFrequency(opts.syncFrequency);
1226
+ console.log("\nValidating credentials...");
1227
+ const result = await apiCall(config, "POST", "/api/integrations/fathom/connect", {
1228
+ projectId,
1229
+ apiKey,
1230
+ syncFrequency
1231
+ });
1232
+ if (result.ok) {
1233
+ success("Fathom connected successfully!");
1234
+ return true;
1235
+ }
1236
+ const data = result.data;
1237
+ error(`Connection failed: ${data.error || "Unknown error"}`);
1238
+ return false;
1239
+ }
1240
+ async function connectHubspot(config, projectId, opts) {
1241
+ const method = opts.accessToken ? "token" : await select2({
1242
+ message: "Connection method:",
1243
+ choices: [
1244
+ { value: "token", name: "Private App Token" },
1245
+ { value: "oauth", name: "OAuth (opens browser)" }
1246
+ ]
1247
+ });
1248
+ if (method === "oauth") {
1249
+ return connectOAuth(config, "hubspot", projectId);
1250
+ }
1251
+ const accessToken = opts.accessToken || await password({ message: "Private App Token:", mask: "*", validate: (v) => v.length > 0 || "Required" });
1252
+ const syncFrequency = await promptSyncFrequency(opts.syncFrequency);
1253
+ const overwritePolicy = opts.overwritePolicy || await select2({
1254
+ message: "Overwrite policy for existing records:",
1255
+ choices: [
1256
+ { value: "fill_nulls", name: "Fill nulls only (keep existing data, fill gaps)" },
1257
+ { value: "hubspot_wins", name: "HubSpot wins (overwrite with HubSpot data)" },
1258
+ { value: "never_overwrite", name: "Never overwrite (only create new records)" }
1259
+ ],
1260
+ default: "fill_nulls"
1261
+ });
1262
+ console.log("\nValidating credentials...");
1263
+ const result = await apiCall(config, "POST", "/api/integrations/hubspot/connect", {
1264
+ projectId,
1265
+ accessToken,
1266
+ syncFrequency,
1267
+ overwritePolicy
1268
+ });
1269
+ if (result.ok) {
1270
+ success("HubSpot connected successfully!");
1271
+ return true;
1272
+ }
1273
+ const data = result.data;
1274
+ error(`Connection failed: ${data.error || "Unknown error"}`);
1275
+ return false;
1276
+ }
1277
+ async function connectNotion(config, projectId, opts) {
1278
+ const method = opts.accessToken ? "token" : await select2({
1279
+ message: "Connection method:",
1280
+ choices: [
1281
+ { value: "token", name: "Internal Integration Token" },
1282
+ { value: "oauth", name: "OAuth (opens browser)" }
1283
+ ]
1284
+ });
1285
+ if (method === "oauth") {
1286
+ return connectOAuth(config, "notion", projectId);
1287
+ }
1288
+ const accessToken = opts.accessToken || await password({ message: "Integration Token:", mask: "*", validate: (v) => v.length > 0 || "Required" });
1289
+ console.log("\nValidating credentials...");
1290
+ const result = await apiCall(config, "POST", "/api/integrations/notion/connect", {
1291
+ projectId,
1292
+ accessToken
1293
+ });
1294
+ if (result.ok) {
1295
+ success("Notion connected successfully!");
1296
+ return true;
1297
+ }
1298
+ const data = result.data;
1299
+ error(`Connection failed: ${data.error || "Unknown error"}`);
1300
+ return false;
1301
+ }
1260
1302
  async function configureIntegration(config, platform2, projectId) {
1261
1303
  if (SYNCABLE_PLATFORMS.includes(platform2)) {
1262
- const syncFrequency = await select2({
1263
- message: "Sync frequency:",
1264
- choices: [
1265
- { value: "manual", name: "Manual" },
1266
- { value: "1h", name: "Every hour" },
1267
- { value: "6h", name: "Every 6 hours" },
1268
- { value: "24h", name: "Every 24 hours" }
1269
- ]
1270
- });
1304
+ const syncFrequency = await promptSyncFrequency();
1271
1305
  const result = await apiCall(config, "PATCH", `/api/integrations/${platform2}?projectId=${projectId}`, {
1272
1306
  syncFrequency
1273
1307
  });
@@ -1401,6 +1435,12 @@ async function interactiveWizard(config, platform2, projectId, jsonMode) {
1401
1435
  await connectZendesk(config, projectId, {});
1402
1436
  } else if (platform2 === "intercom") {
1403
1437
  await connectIntercom(config, projectId, {});
1438
+ } else if (platform2 === "fathom") {
1439
+ await connectFathom(config, projectId, {});
1440
+ } else if (platform2 === "hubspot") {
1441
+ await connectHubspot(config, projectId, {});
1442
+ } else if (platform2 === "notion") {
1443
+ await connectNotion(config, projectId, {});
1404
1444
  }
1405
1445
  }
1406
1446
  return;
@@ -1427,122 +1467,159 @@ async function interactiveWizard(config, platform2, projectId, jsonMode) {
1427
1467
  break;
1428
1468
  }
1429
1469
  }
1430
- var integrateCommand = new Command6("integrate").description("Manage integrations (intercom, gong, zendesk, slack, github, jira, linear)").argument("[platform]", "Integration platform").argument("[action]", "Action: status, connect, configure, sync, disconnect").option("--access-key <key>", "Gong access key").option("--access-key-secret <secret>", "Gong access key secret").option("--base-url <url>", "Gong base URL").option("--subdomain <subdomain>", "Zendesk subdomain").option("--email <email>", "Zendesk admin email").option("--api-token <token>", "Zendesk API token").option("--access-token <token>", "Intercom access token").option("--sync-frequency <freq>", "Sync frequency: manual, 1h, 6h, 24h").option("--mode <mode>", "Sync mode: incremental, full").action(async (platformArg, actionArg, opts, cmd) => {
1431
- const config = requireConfig();
1432
- const jsonMode = cmd.parent?.opts().json;
1433
- const projectId = await getProjectId(config);
1434
- if (!platformArg) {
1435
- const results = await Promise.all(
1436
- PLATFORMS.map(async (p) => {
1437
- const result = await getStatus(config, p, projectId);
1438
- return {
1439
- platform: p,
1440
- name: PLATFORM_LABELS[p],
1441
- connected: result.ok ? result.data.connected === true : false,
1442
- data: result.ok ? result.data : null
1443
- };
1444
- })
1445
- );
1446
- if (jsonMode) {
1447
- console.log(renderJson(results));
1448
- return;
1449
- }
1450
- console.log(renderMarkdown("# Integrations\n"));
1451
- for (const r of results) {
1452
- const status = r.connected ? "\x1B[32mConnected\x1B[0m" : "\x1B[2mNot connected\x1B[0m";
1453
- console.log(` ${r.name.padEnd(12)} ${status}`);
1454
- }
1455
- console.log("\nRun `hissuno integrate <platform>` to manage a specific integration.");
1456
- return;
1457
- }
1470
+ function validatePlatform(platformArg) {
1458
1471
  const platform2 = platformArg.toLowerCase();
1459
1472
  if (!PLATFORMS.includes(platform2)) {
1460
1473
  error(`Unknown platform "${platformArg}". Supported: ${PLATFORMS.join(", ")}`);
1461
1474
  process.exit(1);
1462
1475
  }
1463
- if (!actionArg) {
1464
- await interactiveWizard(config, platform2, projectId, jsonMode);
1476
+ return platform2;
1477
+ }
1478
+ async function listIntegrations(config, projectId, jsonMode) {
1479
+ const results = await Promise.all(
1480
+ PLATFORMS.map(async (p) => {
1481
+ const result = await getStatus(config, p, projectId);
1482
+ return {
1483
+ platform: p,
1484
+ name: PLATFORM_LABELS[p],
1485
+ connected: result.ok ? result.data.connected === true : false,
1486
+ data: result.ok ? result.data : null
1487
+ };
1488
+ })
1489
+ );
1490
+ if (jsonMode) {
1491
+ console.log(renderJson(results));
1465
1492
  return;
1466
1493
  }
1467
- const action = actionArg.toLowerCase();
1468
- switch (action) {
1469
- case "status": {
1470
- const result = await getStatus(config, platform2, projectId);
1471
- if (!result.ok) {
1472
- const data2 = result.data;
1473
- error(`Failed: ${data2.error || `HTTP ${result.status}`}`);
1474
- process.exit(1);
1475
- }
1476
- const data = result.data;
1477
- if (jsonMode) {
1478
- console.log(renderJson(data));
1479
- } else {
1480
- console.log(renderMarkdown(formatStatus(platform2, data)));
1481
- }
1482
- break;
1483
- }
1484
- case "connect": {
1485
- const statusResult = await getStatus(config, platform2, projectId);
1486
- if (statusResult.ok && statusResult.data.connected) {
1487
- warn(`${PLATFORM_LABELS[platform2]} is already connected.`);
1488
- return;
1489
- }
1490
- if (OAUTH_PLATFORMS.includes(platform2)) {
1491
- await connectOAuth(config, platform2, projectId);
1492
- } else if (platform2 === "gong") {
1493
- await connectGong(config, projectId, {
1494
- accessKey: opts.accessKey || "",
1495
- accessKeySecret: opts.accessKeySecret || "",
1496
- baseUrl: opts.baseUrl || "",
1497
- syncFrequency: opts.syncFrequency || ""
1498
- });
1499
- } else if (platform2 === "zendesk") {
1500
- await connectZendesk(config, projectId, {
1501
- subdomain: opts.subdomain || "",
1502
- email: opts.email || "",
1503
- apiToken: opts.apiToken || "",
1504
- syncFrequency: opts.syncFrequency || ""
1505
- });
1506
- } else if (platform2 === "intercom") {
1507
- await connectIntercom(config, projectId, {
1508
- accessToken: opts.accessToken || "",
1509
- syncFrequency: opts.syncFrequency || ""
1510
- });
1511
- }
1512
- break;
1513
- }
1514
- case "configure": {
1515
- const statusResult = await getStatus(config, platform2, projectId);
1516
- if (!statusResult.ok || !statusResult.data.connected) {
1517
- error(`${PLATFORM_LABELS[platform2]} is not connected. Run 'hissuno integrate ${platform2} connect' first.`);
1518
- process.exit(1);
1519
- }
1520
- await configureIntegration(config, platform2, projectId);
1521
- break;
1522
- }
1523
- case "sync": {
1524
- const statusResult = await getStatus(config, platform2, projectId);
1525
- if (!statusResult.ok || !statusResult.data.connected) {
1526
- error(`${PLATFORM_LABELS[platform2]} is not connected.`);
1527
- process.exit(1);
1528
- }
1529
- await syncIntegration(config, platform2, projectId, opts.mode || "");
1530
- break;
1531
- }
1532
- case "disconnect": {
1533
- const statusResult = await getStatus(config, platform2, projectId);
1534
- if (!statusResult.ok || !statusResult.data.connected) {
1535
- error(`${PLATFORM_LABELS[platform2]} is not connected.`);
1536
- return;
1537
- }
1538
- await disconnectIntegration(config, platform2, projectId);
1539
- break;
1540
- }
1541
- default:
1542
- error(`Unknown action "${action}". Valid actions: status, connect, configure, sync, disconnect`);
1543
- process.exit(1);
1494
+ console.log(renderMarkdown("# Integrations\n"));
1495
+ for (const r of results) {
1496
+ const status = r.connected ? "\x1B[32mConnected\x1B[0m" : "\x1B[2mNot connected\x1B[0m";
1497
+ console.log(` ${r.name.padEnd(12)} ${status}`);
1544
1498
  }
1499
+ console.log("\nRun `hissuno integrations <platform>` to manage a specific integration.");
1500
+ }
1501
+ var listSubcommand = new Command6("list").description("List all integrations with connection status").action(async (_opts, cmd) => {
1502
+ const config = requireConfig();
1503
+ const jsonMode = cmd.parent?.parent?.opts().json;
1504
+ const projectId = await resolveProjectId(config);
1505
+ await listIntegrations(config, projectId, jsonMode);
1506
+ });
1507
+ var addSubcommand = new Command6("add").description("Connect an integration platform").argument("<platform>", `Platform to connect (${PLATFORMS.join(", ")})`).option("--access-key <key>", "Gong access key").option("--access-key-secret <secret>", "Gong access key secret").option("--base-url <url>", "Gong base URL").option("--subdomain <subdomain>", "Zendesk subdomain").option("--email <email>", "Zendesk admin email").option("--api-token <token>", "Zendesk API token").option("--access-token <token>", "Intercom/HubSpot/Notion access token").option("--api-key <key>", "Fathom API key").option("--overwrite-policy <policy>", "HubSpot overwrite policy: fill_nulls, hubspot_wins, never_overwrite").option("--sync-frequency <freq>", "Sync frequency: manual, 1h, 6h, 24h").action(async (platformArg, opts, cmd) => {
1508
+ const config = requireConfig();
1509
+ const platform2 = validatePlatform(platformArg);
1510
+ const projectId = await resolveProjectId(config);
1511
+ const statusResult = await getStatus(config, platform2, projectId);
1512
+ if (statusResult.ok && statusResult.data.connected) {
1513
+ warn(`${PLATFORM_LABELS[platform2]} is already connected.`);
1514
+ return;
1515
+ }
1516
+ if (OAUTH_PLATFORMS.includes(platform2)) {
1517
+ await connectOAuth(config, platform2, projectId);
1518
+ } else if (platform2 === "gong") {
1519
+ await connectGong(config, projectId, {
1520
+ accessKey: opts.accessKey || "",
1521
+ accessKeySecret: opts.accessKeySecret || "",
1522
+ baseUrl: opts.baseUrl || "",
1523
+ syncFrequency: opts.syncFrequency || ""
1524
+ });
1525
+ } else if (platform2 === "zendesk") {
1526
+ await connectZendesk(config, projectId, {
1527
+ subdomain: opts.subdomain || "",
1528
+ email: opts.email || "",
1529
+ apiToken: opts.apiToken || "",
1530
+ syncFrequency: opts.syncFrequency || ""
1531
+ });
1532
+ } else if (platform2 === "intercom") {
1533
+ await connectIntercom(config, projectId, {
1534
+ accessToken: opts.accessToken || "",
1535
+ syncFrequency: opts.syncFrequency || ""
1536
+ });
1537
+ } else if (platform2 === "fathom") {
1538
+ await connectFathom(config, projectId, {
1539
+ apiKey: opts.apiKey || "",
1540
+ syncFrequency: opts.syncFrequency || ""
1541
+ });
1542
+ } else if (platform2 === "hubspot") {
1543
+ await connectHubspot(config, projectId, {
1544
+ accessToken: opts.accessToken || "",
1545
+ syncFrequency: opts.syncFrequency || "",
1546
+ overwritePolicy: opts.overwritePolicy || ""
1547
+ });
1548
+ } else if (platform2 === "notion") {
1549
+ await connectNotion(config, projectId, {
1550
+ accessToken: opts.accessToken || ""
1551
+ });
1552
+ }
1553
+ });
1554
+ var statusSubcommand = new Command6("status").description("Show detailed status of an integration").argument("<platform>", `Platform to check (${PLATFORMS.join(", ")})`).action(async (platformArg, _opts, cmd) => {
1555
+ const config = requireConfig();
1556
+ const jsonMode = cmd.parent?.parent?.opts().json;
1557
+ const platform2 = validatePlatform(platformArg);
1558
+ const projectId = await resolveProjectId(config);
1559
+ const result = await getStatus(config, platform2, projectId);
1560
+ if (!result.ok) {
1561
+ const data2 = result.data;
1562
+ error(`Failed: ${data2.error || `HTTP ${result.status}`}`);
1563
+ process.exit(1);
1564
+ }
1565
+ const data = result.data;
1566
+ if (jsonMode) {
1567
+ console.log(renderJson(data));
1568
+ } else {
1569
+ console.log(renderMarkdown(formatStatus(platform2, data)));
1570
+ }
1571
+ });
1572
+ var configureSubcommand = new Command6("configure").description("Update integration settings").argument("<platform>", `Platform to configure (${PLATFORMS.join(", ")})`).action(async (platformArg) => {
1573
+ const config = requireConfig();
1574
+ const platform2 = validatePlatform(platformArg);
1575
+ const projectId = await resolveProjectId(config);
1576
+ const statusResult = await getStatus(config, platform2, projectId);
1577
+ if (!statusResult.ok || !statusResult.data.connected) {
1578
+ error(`${PLATFORM_LABELS[platform2]} is not connected. Run 'hissuno integrations add ${platform2}' first.`);
1579
+ process.exit(1);
1580
+ }
1581
+ await configureIntegration(config, platform2, projectId);
1582
+ });
1583
+ var syncSubcommand = new Command6("sync").description("Trigger a manual sync").argument("<platform>", `Platform to sync (${PLATFORMS.join(", ")})`).option("--mode <mode>", "Sync mode: incremental, full").action(async (platformArg, opts) => {
1584
+ const config = requireConfig();
1585
+ const platform2 = validatePlatform(platformArg);
1586
+ const projectId = await resolveProjectId(config);
1587
+ const statusResult = await getStatus(config, platform2, projectId);
1588
+ if (!statusResult.ok || !statusResult.data.connected) {
1589
+ error(`${PLATFORM_LABELS[platform2]} is not connected.`);
1590
+ process.exit(1);
1591
+ }
1592
+ await syncIntegration(config, platform2, projectId, opts.mode || "");
1593
+ });
1594
+ var disconnectSubcommand = new Command6("disconnect").description("Disconnect an integration").argument("<platform>", `Platform to disconnect (${PLATFORMS.join(", ")})`).action(async (platformArg) => {
1595
+ const config = requireConfig();
1596
+ const platform2 = validatePlatform(platformArg);
1597
+ const projectId = await resolveProjectId(config);
1598
+ const statusResult = await getStatus(config, platform2, projectId);
1599
+ if (!statusResult.ok || !statusResult.data.connected) {
1600
+ error(`${PLATFORM_LABELS[platform2]} is not connected.`);
1601
+ return;
1602
+ }
1603
+ await disconnectIntegration(config, platform2, projectId);
1604
+ });
1605
+ var integrationsCommand = new Command6("integrations").description("Manage integrations (intercom, gong, zendesk, slack, github, jira, linear, fathom, hubspot, notion)").argument("[platform]", "Platform for interactive wizard").action(async (platformArg, _opts, cmd) => {
1606
+ const config = requireConfig();
1607
+ const jsonMode = cmd.parent?.opts().json;
1608
+ if (!platformArg) {
1609
+ const projectId2 = await resolveProjectId(config);
1610
+ await listIntegrations(config, projectId2, jsonMode);
1611
+ return;
1612
+ }
1613
+ const platform2 = validatePlatform(platformArg);
1614
+ const projectId = await resolveProjectId(config);
1615
+ await interactiveWizard(config, platform2, projectId, jsonMode);
1545
1616
  });
1617
+ integrationsCommand.addCommand(listSubcommand);
1618
+ integrationsCommand.addCommand(addSubcommand);
1619
+ integrationsCommand.addCommand(statusSubcommand);
1620
+ integrationsCommand.addCommand(configureSubcommand);
1621
+ integrationsCommand.addCommand(syncSubcommand);
1622
+ integrationsCommand.addCommand(disconnectSubcommand);
1546
1623
 
1547
1624
  // src/commands/update.ts
1548
1625
  import { Command as Command7 } from "commander";
@@ -1751,9 +1828,9 @@ var log = {
1751
1828
  },
1752
1829
  _integrationHint() {
1753
1830
  console.log(` ${BOLD2}Connect your data sources:${RESET2}`);
1754
- console.log(` ${CYAN2}hissuno integrate${RESET2} ${DIM2}# see all integrations${RESET2}`);
1755
- console.log(` ${CYAN2}hissuno integrate slack${RESET2} ${DIM2}# connect Slack${RESET2}`);
1756
- console.log(` ${CYAN2}hissuno integrate intercom${RESET2} ${DIM2}# connect Intercom${RESET2}`);
1831
+ console.log(` ${CYAN2}hissuno integrations list${RESET2} ${DIM2}# see all integrations${RESET2}`);
1832
+ console.log(` ${CYAN2}hissuno integrations add slack${RESET2} ${DIM2}# connect Slack${RESET2}`);
1833
+ console.log(` ${CYAN2}hissuno integrations add intercom${RESET2} ${DIM2}# connect Intercom${RESET2}`);
1757
1834
  console.log();
1758
1835
  }
1759
1836
  };
@@ -2239,7 +2316,7 @@ var OAUTH_PLATFORMS2 = {
2239
2316
  };
2240
2317
  var OAUTH_PLATFORM_NAMES = Object.keys(OAUTH_PLATFORMS2);
2241
2318
  async function setupOAuth(appDir, platformArg, opts = {}) {
2242
- const platform2 = platformArg ? validatePlatform(platformArg) : await select5({
2319
+ const platform2 = platformArg ? validatePlatform2(platformArg) : await select5({
2243
2320
  message: "Which integration?",
2244
2321
  choices: OAUTH_PLATFORM_NAMES.map((p) => ({
2245
2322
  value: p,
@@ -2289,7 +2366,7 @@ async function setupOAuth(appDir, platformArg, opts = {}) {
2289
2366
  console.log(` npm run dev`);
2290
2367
  console.log();
2291
2368
  }
2292
- function validatePlatform(name) {
2369
+ function validatePlatform2(name) {
2293
2370
  const lower = name.toLowerCase();
2294
2371
  if (!OAUTH_PLATFORM_NAMES.includes(lower)) {
2295
2372
  throw new Error(
@@ -2553,6 +2630,12 @@ ${BOLD3}Step 3: Connect Data Sources (optional)${RESET3}`);
2553
2630
  await connectZendesk(fullConfig, fullConfig.project_id, {});
2554
2631
  } else if (platform2 === "intercom") {
2555
2632
  await connectIntercom(fullConfig, fullConfig.project_id, {});
2633
+ } else if (platform2 === "fathom") {
2634
+ await connectFathom(fullConfig, fullConfig.project_id, {});
2635
+ } else if (platform2 === "hubspot") {
2636
+ await connectHubspot(fullConfig, fullConfig.project_id, {});
2637
+ } else if (platform2 === "notion") {
2638
+ await connectNotion(fullConfig, fullConfig.project_id, {});
2556
2639
  }
2557
2640
  connectMore = await confirm8({
2558
2641
  message: "Connect another?",
@@ -2566,7 +2649,7 @@ ${DIM3}Next steps:${RESET3}
2566
2649
  hissuno status Check connection health
2567
2650
  hissuno search "checkout bugs" Search your intelligence
2568
2651
  hissuno list feedback List feedback sessions
2569
- hissuno integrate Manage integrations
2652
+ hissuno integrations Manage integrations
2570
2653
  `);
2571
2654
  });
2572
2655
  function maskApiKey(key) {
@@ -2980,6 +3063,6 @@ program.addCommand(getCommand);
2980
3063
  program.addCommand(searchCommand);
2981
3064
  program.addCommand(addCommand);
2982
3065
  program.addCommand(updateCommand);
2983
- program.addCommand(integrateCommand);
3066
+ program.addCommand(integrationsCommand);
2984
3067
  program.addCommand(membersCommand);
2985
3068
  program.parseAsync(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hissuno",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Hissuno CLI — set up, connect, and query your product intelligence platform",
5
5
  "type": "module",
6
6
  "bin": {
package/skills/SKILL.md CHANGED
@@ -71,7 +71,7 @@ hissuno skills uninstall # Remove installed skills
71
71
  | Find items by meaning | `hissuno search <query>` | "Find feedback about checkout flow" |
72
72
  | Create new data | `hissuno add <type>` | "Log a bug about login failures" |
73
73
  | Traverse relationships | `hissuno get <type> <id> --json` | "What companies are affected by this issue?" |
74
- | Connect a data source | `hissuno integrate` | "Connect Intercom" |
74
+ | Connect a data source | `hissuno integrations add <platform>` | "Connect Intercom" |
75
75
  | View current connection | `hissuno config show` | "Which instance am I connected to?" |
76
76
  | Switch environment | `hissuno profile use <name>` | "Switch to staging" |
77
77
 
@@ -111,9 +111,9 @@ Use `search` for semantic (meaning-based) matching. Use `list` when you want to
111
111
  3. `hissuno config show` to verify the active connection
112
112
 
113
113
  ### Connect a Data Source
114
- 1. `hissuno integrate` to see all integration statuses
115
- 2. `hissuno integrate <platform>` to start the interactive setup wizard
116
- 3. For syncable platforms: `hissuno integrate <platform> sync` to trigger initial data pull
114
+ 1. `hissuno integrations list` to see all integration statuses
115
+ 2. `hissuno integrations <platform>` to start the interactive setup wizard
116
+ 3. For syncable platforms: `hissuno integrations sync <platform>` to trigger initial data pull
117
117
 
118
118
  ## CLI Reference
119
119
 
@@ -132,8 +132,8 @@ hissuno get customers <id> # Contact details + company rel
132
132
  hissuno add issues # Interactive issue creation
133
133
  hissuno add scopes # Create a product scope with goals
134
134
  hissuno update scopes <id> # Modify scope name, type, or goals
135
- hissuno integrate # List all integration statuses
136
- hissuno integrate intercom sync # Sync Intercom conversations
135
+ hissuno integrations list # List all integration statuses
136
+ hissuno integrations sync intercom # Sync Intercom conversations
137
137
  hissuno config show # View active connection
138
138
  hissuno profile list # List profiles
139
139
  hissuno profile use staging # Switch to staging profile
@@ -320,23 +320,34 @@ hissuno update scopes <id>
320
320
 
321
321
  ---
322
322
 
323
- ### `hissuno integrate [platform] [action]`
323
+ ### `hissuno integrations`
324
324
 
325
325
  Manage integrations with external platforms.
326
326
 
327
327
  **Supported platforms:** `intercom`, `gong`, `zendesk`, `slack`, `github`, `jira`, `linear`
328
328
 
329
329
  ```bash
330
- hissuno integrate # List all integrations with status
331
- hissuno integrate <platform> # Interactive setup wizard
332
- hissuno integrate <platform> status # Detailed status
333
- hissuno integrate <platform> connect # Connect (OAuth or token)
334
- hissuno integrate <platform> configure # Update settings
335
- hissuno integrate <platform> sync # Trigger manual sync
336
- hissuno integrate <platform> disconnect # Disconnect
330
+ hissuno integrations list # List all integrations with status
331
+ hissuno integrations <platform> # Interactive setup wizard
332
+ hissuno integrations status <platform> # Detailed status
333
+ hissuno integrations add <platform> # Connect (OAuth or token)
334
+ hissuno integrations configure <platform> # Update settings
335
+ hissuno integrations sync <platform> # Trigger manual sync
336
+ hissuno integrations disconnect <platform> # Disconnect
337
337
  ```
338
338
 
339
- **Platform-specific connect options:**
339
+ **Subcommands:**
340
+
341
+ | Subcommand | Args | Description |
342
+ |------------|------|-------------|
343
+ | `list` | | List all integrations with connection status |
344
+ | `add` | `<platform>` | Connect a platform (OAuth or token-based) |
345
+ | `status` | `<platform>` | Show detailed connection status |
346
+ | `configure` | `<platform>` | Update settings (sync frequency, auto-sync toggle) |
347
+ | `sync` | `<platform>` | Trigger a manual sync (Intercom, Gong, Zendesk only) |
348
+ | `disconnect` | `<platform>` | Disconnect the integration |
349
+
350
+ **Platform-specific add options:**
340
351
 
341
352
  | Option | Platforms | Description |
342
353
  |--------|-----------|-------------|
@@ -346,8 +357,10 @@ hissuno integrate <platform> disconnect # Disconnect
346
357
  | `--subdomain <sub>` | zendesk | Zendesk subdomain |
347
358
  | `--email <email>` | zendesk | Admin email |
348
359
  | `--api-token <token>` | zendesk | API token |
349
- | `--access-token <token>` | intercom | Access token |
350
- | `--sync-frequency <freq>` | gong, zendesk, intercom | `manual`, `1h`, `6h`, `24h` |
351
- | `--mode <mode>` | sync action | `incremental` (default) or `full` |
360
+ | `--access-token <token>` | intercom, hubspot, notion | Access token |
361
+ | `--api-key <key>` | fathom | API key |
362
+ | `--overwrite-policy <policy>` | hubspot | `fill_nulls`, `hubspot_wins`, `never_overwrite` |
363
+ | `--sync-frequency <freq>` | gong, zendesk, intercom, fathom, hubspot | `manual`, `1h`, `6h`, `24h` |
364
+ | `--mode <mode>` | sync subcommand | `incremental` (default) or `full` |
352
365
 
353
366
  See `references/INTEGRATIONS.md` for full platform details.
@@ -9,10 +9,13 @@ Connect external data sources to Hissuno via the CLI or dashboard.
9
9
  | Intercom | Token or OAuth | Yes | Customer conversations |
10
10
  | Gong | Token | Yes | Sales call recordings |
11
11
  | Zendesk | Token | Yes | Support tickets |
12
+ | Fathom | Token | Yes | Meeting recordings |
13
+ | HubSpot | Token or OAuth | Yes | CRM (companies and contacts) |
12
14
  | Slack | OAuth | No | Workspace messages |
13
15
  | GitHub | OAuth | No | Issues and PRs |
14
16
  | Jira | OAuth | No | Issue tracking (auto-sync available) |
15
17
  | Linear | OAuth | No | Issue tracking (auto-sync available) |
18
+ | Notion | Token or OAuth | No | Documentation |
16
19
 
17
20
  **Syncable** platforms pull data on a schedule (manual, 1h, 6h, 24h). Non-syncable platforms use real-time hooks or manual import.
18
21
 
@@ -21,34 +24,31 @@ Connect external data sources to Hissuno via the CLI or dashboard.
21
24
  These integrations are configured through the Hissuno dashboard, not the CLI:
22
25
  - Chat widget (embedded on your site)
23
26
  - PostHog (product analytics)
24
- - HubSpot (CRM)
25
- - Fathom (meeting recordings)
26
- - Notion (documentation)
27
27
 
28
28
  ## CLI Commands
29
29
 
30
30
  ### List all integrations
31
31
 
32
32
  ```bash
33
- hissuno integrate # Shows status of all 7 platforms
34
- hissuno integrate --json # JSON output
33
+ hissuno integrations list # Shows status of all 7 platforms
34
+ hissuno --json integrations list # JSON output
35
35
  ```
36
36
 
37
37
  ### Interactive setup
38
38
 
39
39
  ```bash
40
- hissuno integrate <platform> # Interactive wizard: shows status, offers connect/configure/sync/disconnect
40
+ hissuno integrations <platform> # Interactive wizard: shows status, offers add/configure/sync/disconnect
41
41
  ```
42
42
 
43
43
  ### Direct actions
44
44
 
45
45
  ```bash
46
- hissuno integrate <platform> status # Detailed connection status
47
- hissuno integrate <platform> connect # Connect (OAuth opens browser, token prompts for credentials)
48
- hissuno integrate <platform> configure # Update sync frequency or settings
49
- hissuno integrate <platform> sync # Trigger manual sync (syncable platforms only)
50
- hissuno integrate <platform> sync --mode full # Full re-sync (vs incremental)
51
- hissuno integrate <platform> disconnect # Disconnect (with confirmation)
46
+ hissuno integrations status <platform> # Detailed connection status
47
+ hissuno integrations add <platform> # Connect (OAuth opens browser, token prompts for credentials)
48
+ hissuno integrations configure <platform> # Update sync frequency or settings
49
+ hissuno integrations sync <platform> # Trigger manual sync (syncable platforms only)
50
+ hissuno integrations sync <platform> --mode full # Full re-sync (vs incremental)
51
+ hissuno integrations disconnect <platform> # Disconnect (with confirmation)
52
52
  ```
53
53
 
54
54
  ### Connection Options
@@ -57,17 +57,32 @@ Some platforms accept credentials directly (non-interactive):
57
57
 
58
58
  **Gong:**
59
59
  ```bash
60
- hissuno integrate gong connect --access-key <key> --access-key-secret <secret> --base-url <url> --sync-frequency 24h
60
+ hissuno integrations add gong --access-key <key> --access-key-secret <secret> --base-url <url> --sync-frequency 24h
61
61
  ```
62
62
 
63
63
  **Zendesk:**
64
64
  ```bash
65
- hissuno integrate zendesk connect --subdomain <sub> --email <email> --api-token <token> --sync-frequency 24h
65
+ hissuno integrations add zendesk --subdomain <sub> --email <email> --api-token <token> --sync-frequency 24h
66
66
  ```
67
67
 
68
68
  **Intercom:**
69
69
  ```bash
70
- hissuno integrate intercom connect --access-token <token> --sync-frequency 24h
70
+ hissuno integrations add intercom --access-token <token> --sync-frequency 24h
71
+ ```
72
+
73
+ **Fathom:**
74
+ ```bash
75
+ hissuno integrations add fathom --api-key <key> --sync-frequency 24h
76
+ ```
77
+
78
+ **HubSpot:**
79
+ ```bash
80
+ hissuno integrations add hubspot --access-token <token> --sync-frequency 24h --overwrite-policy fill_nulls
81
+ ```
82
+
83
+ **Notion:**
84
+ ```bash
85
+ hissuno integrations add notion --access-token <token>
71
86
  ```
72
87
 
73
88
  ## Status Details
@@ -77,7 +92,10 @@ Each platform returns different status fields when connected:
77
92
  - **Intercom**: workspace name, auth method, sync frequency, last sync status, conversations synced
78
93
  - **Gong**: sync frequency, last sync status, calls synced
79
94
  - **Zendesk**: subdomain, account name, sync frequency, last sync status, tickets synced
95
+ - **Fathom**: sync frequency, last sync status, meetings synced
96
+ - **HubSpot**: hub name, auth method, sync frequency, overwrite policy, last sync status, companies/contacts synced
80
97
  - **Slack**: workspace name, domain, installed by
81
98
  - **GitHub**: account login, installed by
82
99
  - **Jira**: site URL, project key, issue type, auto-sync status
83
100
  - **Linear**: organization, team, auto-sync status
101
+ - **Notion**: workspace name, auth method