heyhank 0.1.0 → 0.2.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.
Files changed (156) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -10
  3. package/bin/cli.ts +7 -7
  4. package/bin/ctl.ts +42 -42
  5. package/dist/assets/{AgentsPage-BPhirnCe.js → AgentsPage-B-AAmsMK.js} +3 -3
  6. package/dist/assets/AssistantPage-BV1Mfwdt.js +2 -0
  7. package/dist/assets/BusinessPage-tLpNEz19.js +1 -0
  8. package/dist/assets/{CronManager-DDbz-yiT.js → CronManager-B-K_n3Jg.js} +1 -1
  9. package/dist/assets/HelpPage-Bhf_j6Xr.js +1 -0
  10. package/dist/assets/{IntegrationsPage-CrOitCmJ.js → IntegrationsPage-DAMjs9tM.js} +1 -1
  11. package/dist/assets/JarvisHUD-C_TGXCCn.js +120 -0
  12. package/dist/assets/MediaPage-C48HTTrt.js +1 -0
  13. package/dist/assets/MemoryPage-JkC-qtgp.js +1 -0
  14. package/dist/assets/{PlatformDashboard-Do6F0O2p.js → PlatformDashboard-AUo7tNnE.js} +1 -1
  15. package/dist/assets/{Playground-Fc5cdc5p.js → Playground-AzNMsRBL.js} +1 -1
  16. package/dist/assets/{ProcessPanel-CslEiZkI.js → ProcessPanel-DpE_2sX3.js} +1 -1
  17. package/dist/assets/{PromptsPage-D2EhsdNO.js → PromptsPage-C2RQOs6p.js} +2 -2
  18. package/dist/assets/RunsPage-B9UOyO79.js +1 -0
  19. package/dist/assets/{SandboxManager-a1AVI5q2.js → SandboxManager-jHvYjwfh.js} +1 -1
  20. package/dist/assets/SettingsPage-BBJax6gt.js +51 -0
  21. package/dist/assets/SkillsMarketplace-IjmjfdjD.js +1 -0
  22. package/dist/assets/SocialMediaPage-DoPZHhr2.js +10 -0
  23. package/dist/assets/{TailscalePage-CHiFhZXF.js → TailscalePage-DDEY7ckO.js} +1 -1
  24. package/dist/assets/TelephonyPage-OPNBZYKt.js +9 -0
  25. package/dist/assets/{TerminalPage-Drwyrnfd.js → TerminalPage-BjMbHHW3.js} +1 -1
  26. package/dist/assets/{gemini-live-client-C7rqAW7G.js → gemini-live-client-C70FEtX2.js} +11 -8
  27. package/dist/assets/{index-CEqZnThB.js → index-BgYM4wXw.js} +94 -93
  28. package/dist/assets/index-BkjSoVgn.css +32 -0
  29. package/dist/assets/sw-register-C7NOHtIu.js +1 -0
  30. package/dist/assets/text-chat-client-BSbLJerZ.js +2 -0
  31. package/dist/index.html +2 -2
  32. package/dist/sw.js +1 -1
  33. package/package.json +6 -1
  34. package/server/agent-executor.ts +37 -2
  35. package/server/agent-store.ts +3 -3
  36. package/server/agent-types.ts +11 -0
  37. package/server/assistant-store.ts +232 -6
  38. package/server/auth-manager.ts +9 -0
  39. package/server/cache-headers.ts +1 -1
  40. package/server/calendar-service.ts +10 -0
  41. package/server/ceo/document-store.ts +129 -0
  42. package/server/ceo/finance-store.ts +343 -0
  43. package/server/ceo/kpi-store.ts +208 -0
  44. package/server/ceo/memory-import.ts +277 -0
  45. package/server/ceo/news-store.ts +208 -0
  46. package/server/ceo/template-store.ts +134 -0
  47. package/server/ceo/time-tracking-store.ts +227 -0
  48. package/server/claude-auth-monitor.ts +128 -0
  49. package/server/claude-code-worker.ts +86 -0
  50. package/server/claude-session-discovery.ts +74 -1
  51. package/server/cli-launcher.ts +32 -10
  52. package/server/codex-adapter.ts +2 -2
  53. package/server/codex-ws-proxy.cjs +1 -1
  54. package/server/container-manager.ts +4 -4
  55. package/server/content-intelligence/content-engine.ts +1112 -0
  56. package/server/content-intelligence/platform-knowledge.ts +870 -0
  57. package/server/cron-store.ts +3 -3
  58. package/server/embedding-service.ts +49 -0
  59. package/server/event-bus-types.ts +13 -0
  60. package/server/federation/node-store.ts +5 -4
  61. package/server/fs-utils.ts +28 -1
  62. package/server/hank-notifications-store.ts +91 -0
  63. package/server/hank-tool-executor.ts +1835 -0
  64. package/server/hank-tools.ts +2107 -0
  65. package/server/image-pull-manager.ts +2 -2
  66. package/server/index.ts +25 -2
  67. package/server/llm-providers-streaming.ts +541 -0
  68. package/server/llm-providers.ts +12 -0
  69. package/server/marketplace.ts +249 -0
  70. package/server/mcp-registry.ts +158 -0
  71. package/server/memory-service.ts +296 -0
  72. package/server/obsidian-sync.ts +184 -0
  73. package/server/provider-manager.ts +5 -2
  74. package/server/provider-registry.ts +12 -0
  75. package/server/reminder-scheduler.ts +37 -1
  76. package/server/routes/agent-routes.ts +2 -1
  77. package/server/routes/assistant-routes.ts +198 -5
  78. package/server/routes/ceo-finance-kpi-routes.ts +167 -0
  79. package/server/routes/ceo-news-time-routes.ts +137 -0
  80. package/server/routes/ceo-routes.ts +99 -0
  81. package/server/routes/content-routes.ts +116 -0
  82. package/server/routes/email-routes.ts +147 -0
  83. package/server/routes/env-routes.ts +3 -3
  84. package/server/routes/fs-routes.ts +12 -9
  85. package/server/routes/hank-chat-routes.ts +592 -0
  86. package/server/routes/llm-routes.ts +12 -0
  87. package/server/routes/marketplace-routes.ts +63 -0
  88. package/server/routes/media-routes.ts +1 -1
  89. package/server/routes/memory-routes.ts +127 -0
  90. package/server/routes/platform-routes.ts +14 -675
  91. package/server/routes/sandbox-routes.ts +1 -1
  92. package/server/routes/settings-routes.ts +51 -1
  93. package/server/routes/socialmedia-routes.ts +152 -2
  94. package/server/routes/system-routes.ts +2 -2
  95. package/server/routes/team-routes.ts +71 -0
  96. package/server/routes/telephony-routes.ts +98 -18
  97. package/server/routes.ts +36 -9
  98. package/server/session-creation-service.ts +2 -2
  99. package/server/session-orchestrator.ts +54 -2
  100. package/server/session-types.ts +2 -0
  101. package/server/settings-manager.ts +50 -2
  102. package/server/skill-discovery.ts +68 -0
  103. package/server/socialmedia/adapters/browser-adapter.ts +179 -0
  104. package/server/socialmedia/adapters/postiz-adapter.ts +291 -14
  105. package/server/socialmedia/manager.ts +234 -15
  106. package/server/socialmedia/store.ts +51 -1
  107. package/server/socialmedia/types.ts +35 -2
  108. package/server/socialview/browser-manager.ts +150 -0
  109. package/server/socialview/extractors.ts +1298 -0
  110. package/server/socialview/image-describe.ts +188 -0
  111. package/server/socialview/library.ts +119 -0
  112. package/server/socialview/poster.ts +276 -0
  113. package/server/socialview/routes.ts +371 -0
  114. package/server/socialview/style-analyzer.ts +187 -0
  115. package/server/socialview/style-profiles.ts +67 -0
  116. package/server/socialview/types.ts +166 -0
  117. package/server/socialview/vision.ts +127 -0
  118. package/server/socialview/vnc-manager.ts +110 -0
  119. package/server/style-injector.ts +135 -0
  120. package/server/team-service.ts +239 -0
  121. package/server/team-store.ts +75 -0
  122. package/server/team-types.ts +52 -0
  123. package/server/telephony/audio-bridge.ts +281 -35
  124. package/server/telephony/audio-recorder.ts +132 -0
  125. package/server/telephony/call-manager.ts +803 -104
  126. package/server/telephony/call-types.ts +67 -1
  127. package/server/telephony/esl-client.ts +319 -0
  128. package/server/telephony/freeswitch-sync.ts +155 -0
  129. package/server/telephony/phone-utils.ts +63 -0
  130. package/server/telephony/telephony-store.ts +9 -8
  131. package/server/url-validator.ts +82 -0
  132. package/server/vault-markdown.ts +317 -0
  133. package/server/vault-migration.ts +121 -0
  134. package/server/vault-store.ts +466 -0
  135. package/server/vault-watcher.ts +59 -0
  136. package/server/vector-store.ts +210 -0
  137. package/server/voice-pipeline/gemini-live-adapter.ts +97 -0
  138. package/server/voice-pipeline/greeting-cache.ts +200 -0
  139. package/server/voice-pipeline/manager.ts +249 -0
  140. package/server/voice-pipeline/pipeline.ts +335 -0
  141. package/server/voice-pipeline/providers/index.ts +47 -0
  142. package/server/voice-pipeline/providers/llm-internal.ts +527 -0
  143. package/server/voice-pipeline/providers/stt-google.ts +157 -0
  144. package/server/voice-pipeline/providers/tts-google.ts +126 -0
  145. package/server/voice-pipeline/types.ts +247 -0
  146. package/server/ws-bridge-types.ts +6 -1
  147. package/dist/assets/AssistantPage-DJ-cMQfb.js +0 -1
  148. package/dist/assets/HelpPage-DMfkzERp.js +0 -1
  149. package/dist/assets/MediaPage-CE5rdvkC.js +0 -1
  150. package/dist/assets/RunsPage-C5BZF5Rx.js +0 -1
  151. package/dist/assets/SettingsPage-DirhjQrJ.js +0 -51
  152. package/dist/assets/SocialMediaPage-DBuM28vD.js +0 -1
  153. package/dist/assets/TelephonyPage-x0VV0fOo.js +0 -1
  154. package/dist/assets/index-C8M_PUmX.css +0 -32
  155. package/dist/assets/sw-register-LSSpj6RU.js +0 -1
  156. package/server/socialmedia/adapters/ayrshare-adapter.ts +0 -169
@@ -5,16 +5,18 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlink
5
5
  import { homedir } from "node:os";
6
6
  import { join } from "node:path";
7
7
  import { randomUUID } from "node:crypto";
8
- import type { SocialMediaSettings, SocialPost, ListPostsOpts } from "./types.js";
8
+ import type { SocialMediaSettings, SocialPost, ListPostsOpts, HashtagPool } from "./types.js";
9
9
  import { DEFAULT_SOCIAL_SETTINGS } from "./types.js";
10
10
 
11
11
  const BASE_DIR = join(homedir(), ".heyhank", "socialmedia");
12
12
  const SETTINGS_FILE = join(BASE_DIR, "settings.json");
13
13
  const POSTS_DIR = join(BASE_DIR, "posts");
14
+ const HASHTAG_POOLS_DIR = join(BASE_DIR, "hashtag-pools");
14
15
 
15
16
  function ensureDirs(): void {
16
17
  if (!existsSync(BASE_DIR)) mkdirSync(BASE_DIR, { recursive: true });
17
18
  if (!existsSync(POSTS_DIR)) mkdirSync(POSTS_DIR, { recursive: true });
19
+ if (!existsSync(HASHTAG_POOLS_DIR)) mkdirSync(HASHTAG_POOLS_DIR, { recursive: true });
18
20
  }
19
21
 
20
22
  // ─── Settings ────────────────────────────────────────────────────────────────
@@ -96,3 +98,51 @@ export function deleteLocalPost(postId: string): boolean {
96
98
  return false;
97
99
  }
98
100
  }
101
+
102
+ // ─── Hashtag Pools ──────────────────────────────────────────────────────────
103
+
104
+ export function saveHashtagPool(pool: HashtagPool): void {
105
+ ensureDirs();
106
+ if (!pool.id) pool.id = randomUUID();
107
+ const file = join(HASHTAG_POOLS_DIR, `${pool.id}.json`);
108
+ writeFileSync(file, JSON.stringify(pool, null, 2), "utf-8");
109
+ }
110
+
111
+ export function getHashtagPool(poolId: string): HashtagPool | null {
112
+ const file = join(HASHTAG_POOLS_DIR, `${poolId}.json`);
113
+ if (!existsSync(file)) return null;
114
+ try {
115
+ return JSON.parse(readFileSync(file, "utf-8"));
116
+ } catch {
117
+ return null;
118
+ }
119
+ }
120
+
121
+ export function listHashtagPools(): HashtagPool[] {
122
+ ensureDirs();
123
+ try {
124
+ return readdirSync(HASHTAG_POOLS_DIR)
125
+ .filter((f) => f.endsWith(".json"))
126
+ .map((f) => {
127
+ try {
128
+ return JSON.parse(readFileSync(join(HASHTAG_POOLS_DIR, f), "utf-8")) as HashtagPool;
129
+ } catch {
130
+ return null;
131
+ }
132
+ })
133
+ .filter(Boolean) as HashtagPool[];
134
+ } catch {
135
+ return [];
136
+ }
137
+ }
138
+
139
+ export function deleteHashtagPool(poolId: string): boolean {
140
+ const file = join(HASHTAG_POOLS_DIR, `${poolId}.json`);
141
+ if (!existsSync(file)) return false;
142
+ try {
143
+ unlinkSync(file);
144
+ return true;
145
+ } catch {
146
+ return false;
147
+ }
148
+ }
@@ -1,6 +1,6 @@
1
1
  // Social Media Types
2
2
 
3
- export type SocialBackendId = "postiz" | "ayrshare" | "buffer";
3
+ export type SocialBackendId = "postiz" | "buffer";
4
4
 
5
5
  export type SocialPlatform = "twitter" | "instagram" | "linkedin" | "facebook" | "tiktok" | "threads";
6
6
 
@@ -35,7 +35,7 @@ export interface SocialPost {
35
35
  platforms: SocialPlatform[];
36
36
  scheduledAt?: string | null;
37
37
  mediaUrls: string[];
38
- status: "published" | "scheduled" | "failed" | "draft";
38
+ status: "published" | "scheduled" | "failed" | "draft" | "partial" | "archived";
39
39
  backendId: SocialBackendId | null;
40
40
  backendPostId?: string | null;
41
41
  backendData?: unknown;
@@ -74,6 +74,13 @@ export interface SocialMediaSettings {
74
74
  backend: SocialBackendId | null;
75
75
  backends: Partial<Record<SocialBackendId, SocialBackendConfig>>;
76
76
  defaultPlatforms: SocialPlatform[];
77
+ requireApproval?: boolean;
78
+ /**
79
+ * Platforms for which posting should use the BrowserAdapter (persistent
80
+ * Playwright session via SocialView) instead of the primary backend (Postiz).
81
+ * v1 supports only "twitter" and "tiktok".
82
+ */
83
+ browserPlatforms?: SocialPlatform[];
77
84
  }
78
85
 
79
86
  export interface ListPostsOpts {
@@ -86,4 +93,30 @@ export const DEFAULT_SOCIAL_SETTINGS: SocialMediaSettings = {
86
93
  backend: null,
87
94
  backends: {},
88
95
  defaultPlatforms: [],
96
+ requireApproval: false,
97
+ browserPlatforms: [],
89
98
  };
99
+
100
+ // ─── Hashtag Pools ─────────────────────────────────────────────────────────
101
+
102
+ export interface HashtagPool {
103
+ id: string;
104
+ /** Business or brand name (e.g. "Ferienhaus Steiermark") */
105
+ name: string;
106
+ /** Industry/niche (e.g. "tourism", "saas", "fashion") */
107
+ industry: string;
108
+ /** Language for hashtags (e.g. "de", "en") */
109
+ language: string;
110
+ /** High-reach hashtags (>1M posts) */
111
+ popular: string[];
112
+ /** Medium-reach hashtags (100K-1M posts) */
113
+ medium: string[];
114
+ /** Niche/specific hashtags (<100K posts) */
115
+ niche: string[];
116
+ /** Branded hashtags (company-specific) */
117
+ branded: string[];
118
+ /** Hashtags to NEVER use (banned, irrelevant, competitor) */
119
+ blocked: string[];
120
+ createdAt: string;
121
+ updatedAt: string;
122
+ }
@@ -0,0 +1,150 @@
1
+ // ─── Browser Manager ─────────────────────────────────────────────────────────
2
+ // Manages a headed Chromium per platform, rendered into a shared Xvfb display
3
+ // (:99). Each platform has a persistent user-data-dir so cookies/session
4
+ // survive restarts. User logs in once manually via the noVNC viewer.
5
+
6
+ import { spawn, type ChildProcess } from "node:child_process";
7
+ import { mkdirSync, existsSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { chromium, type BrowserContext, type Page } from "playwright";
10
+ import { HEYHANK_HOME } from "../paths.js";
11
+ import { PLATFORM_URLS, type SocialPlatform, type SocialViewStatus } from "./types.js";
12
+
13
+ const DISPLAY = ":99";
14
+ const DISPLAY_WIDTH = 1440;
15
+ const DISPLAY_HEIGHT = 900;
16
+ const PROFILES_DIR = join(HEYHANK_HOME, "browser-profiles");
17
+
18
+ let xvfbProc: ChildProcess | null = null;
19
+
20
+ interface Session {
21
+ platform: SocialPlatform;
22
+ context: BrowserContext;
23
+ page: Page;
24
+ startedAt: number;
25
+ }
26
+
27
+ const sessions = new Map<SocialPlatform, Session>();
28
+
29
+ /** Start Xvfb on DISPLAY if not running. Idempotent. */
30
+ async function ensureXvfb(): Promise<void> {
31
+ if (xvfbProc && !xvfbProc.killed && xvfbProc.exitCode === null) return;
32
+ // eslint-disable-next-line no-console
33
+ console.log(`[socialview] starting Xvfb on ${DISPLAY}`);
34
+ xvfbProc = spawn(
35
+ "Xvfb",
36
+ [DISPLAY, "-screen", "0", `${DISPLAY_WIDTH}x${DISPLAY_HEIGHT}x24`, "-ac", "+extension", "RANDR"],
37
+ { stdio: "ignore", detached: false },
38
+ );
39
+ xvfbProc.on("exit", (code) => {
40
+ // eslint-disable-next-line no-console
41
+ console.log(`[socialview] Xvfb exited with code ${code}`);
42
+ xvfbProc = null;
43
+ });
44
+ // Give Xvfb a moment to start listening on the display socket.
45
+ await new Promise((r) => setTimeout(r, 500));
46
+ }
47
+
48
+ /** Launch a persistent-context Chromium for a platform, navigate to its URL. */
49
+ export async function startPlatform(platform: SocialPlatform): Promise<SocialViewStatus> {
50
+ const existing = sessions.get(platform);
51
+ if (existing && !existing.page.isClosed()) {
52
+ return getStatus(platform);
53
+ }
54
+
55
+ await ensureXvfb();
56
+
57
+ mkdirSync(PROFILES_DIR, { recursive: true });
58
+ const userDataDir = join(PROFILES_DIR, platform);
59
+ mkdirSync(userDataDir, { recursive: true });
60
+
61
+ const context = await chromium.launchPersistentContext(userDataDir, {
62
+ headless: false,
63
+ viewport: { width: DISPLAY_WIDTH, height: DISPLAY_HEIGHT },
64
+ env: { ...process.env, DISPLAY },
65
+ args: [
66
+ "--no-sandbox",
67
+ "--disable-dev-shm-usage",
68
+ `--window-size=${DISPLAY_WIDTH},${DISPLAY_HEIGHT}`,
69
+ "--window-position=0,0",
70
+ ],
71
+ });
72
+
73
+ const page = context.pages()[0] ?? (await context.newPage());
74
+ await page.goto(PLATFORM_URLS[platform], { waitUntil: "domcontentloaded" }).catch(() => {
75
+ // Navigation errors are non-fatal; user can manually retry.
76
+ });
77
+
78
+ const session: Session = { platform, context, page, startedAt: Date.now() };
79
+ sessions.set(platform, session);
80
+
81
+ context.on("close", () => {
82
+ sessions.delete(platform);
83
+ });
84
+
85
+ return getStatus(platform);
86
+ }
87
+
88
+ export async function stopPlatform(platform: SocialPlatform): Promise<void> {
89
+ const s = sessions.get(platform);
90
+ if (!s) return;
91
+ try {
92
+ await s.context.close();
93
+ } finally {
94
+ sessions.delete(platform);
95
+ }
96
+ }
97
+
98
+ export async function gotoUrl(platform: SocialPlatform, url: string): Promise<SocialViewStatus> {
99
+ const s = sessions.get(platform);
100
+ if (!s) throw new Error(`Platform ${platform} not running`);
101
+ await s.page.goto(url, { waitUntil: "domcontentloaded" });
102
+ return getStatus(platform);
103
+ }
104
+
105
+ export function getStatus(platform: SocialPlatform): SocialViewStatus {
106
+ const s = sessions.get(platform);
107
+ if (!s) {
108
+ return { platform, running: false, loggedIn: null, currentUrl: null, startedAt: null };
109
+ }
110
+ const currentUrl = s.page.isClosed() ? null : s.page.url();
111
+ // Heuristic: URL contains "login" or "signin" or is root → not logged in
112
+ const loggedIn =
113
+ currentUrl === null
114
+ ? null
115
+ : !/\/(login|signin|accounts\/login|uas\/login)/i.test(currentUrl);
116
+ return {
117
+ platform,
118
+ running: !s.page.isClosed(),
119
+ loggedIn,
120
+ currentUrl,
121
+ startedAt: s.startedAt,
122
+ };
123
+ }
124
+
125
+ /** Returns the live Playwright Page for a running platform, or null. */
126
+ export function getPage(platform: SocialPlatform): Page | null {
127
+ const s = sessions.get(platform);
128
+ if (!s || s.page.isClosed()) return null;
129
+ return s.page;
130
+ }
131
+
132
+ export function getAllStatus(): SocialViewStatus[] {
133
+ const platforms: SocialPlatform[] = ["instagram", "twitter", "linkedin", "facebook", "tiktok"];
134
+ return platforms.map(getStatus);
135
+ }
136
+
137
+ export function hasProfile(platform: SocialPlatform): boolean {
138
+ return existsSync(join(PROFILES_DIR, platform));
139
+ }
140
+
141
+ /** Shut down all browsers + Xvfb. Called on process exit. */
142
+ export async function shutdown(): Promise<void> {
143
+ for (const p of Array.from(sessions.keys())) {
144
+ await stopPlatform(p).catch(() => {});
145
+ }
146
+ if (xvfbProc && !xvfbProc.killed) {
147
+ xvfbProc.kill();
148
+ xvfbProc = null;
149
+ }
150
+ }