@makefinks/daemon 0.1.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 (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +126 -0
  3. package/dist/cli.js +22 -0
  4. package/package.json +79 -0
  5. package/src/ai/agent-turn-runner.ts +130 -0
  6. package/src/ai/daemon-ai.ts +403 -0
  7. package/src/ai/exa-client.ts +21 -0
  8. package/src/ai/exa-fetch-cache.ts +104 -0
  9. package/src/ai/model-config.ts +99 -0
  10. package/src/ai/sanitize-messages.ts +83 -0
  11. package/src/ai/system-prompt.ts +363 -0
  12. package/src/ai/tools/fetch-urls.ts +187 -0
  13. package/src/ai/tools/grounding-manager.ts +94 -0
  14. package/src/ai/tools/index.ts +52 -0
  15. package/src/ai/tools/read-file.ts +100 -0
  16. package/src/ai/tools/render-url.ts +275 -0
  17. package/src/ai/tools/run-bash.ts +224 -0
  18. package/src/ai/tools/subagents.ts +195 -0
  19. package/src/ai/tools/todo-manager.ts +150 -0
  20. package/src/ai/tools/web-search.ts +91 -0
  21. package/src/app/App.tsx +711 -0
  22. package/src/app/components/AppOverlays.tsx +131 -0
  23. package/src/app/components/AvatarLayer.tsx +51 -0
  24. package/src/app/components/ConversationPane.tsx +476 -0
  25. package/src/avatar/DaemonAvatarRenderable.ts +343 -0
  26. package/src/avatar/daemon-avatar-rig.ts +1165 -0
  27. package/src/avatar-preview.ts +186 -0
  28. package/src/cli.ts +26 -0
  29. package/src/components/ApiKeyInput.tsx +99 -0
  30. package/src/components/ApiKeyStep.tsx +95 -0
  31. package/src/components/ApprovalPicker.tsx +109 -0
  32. package/src/components/ContentBlockView.tsx +141 -0
  33. package/src/components/DaemonText.tsx +34 -0
  34. package/src/components/DeviceMenu.tsx +166 -0
  35. package/src/components/GroundingBadge.tsx +21 -0
  36. package/src/components/GroundingMenu.tsx +310 -0
  37. package/src/components/HotkeysPane.tsx +115 -0
  38. package/src/components/InlineStatusIndicator.tsx +106 -0
  39. package/src/components/ModelMenu.tsx +411 -0
  40. package/src/components/OnboardingOverlay.tsx +446 -0
  41. package/src/components/ProviderMenu.tsx +177 -0
  42. package/src/components/SessionMenu.tsx +297 -0
  43. package/src/components/SettingsMenu.tsx +291 -0
  44. package/src/components/StatusBar.tsx +126 -0
  45. package/src/components/TokenUsageDisplay.tsx +92 -0
  46. package/src/components/ToolCallView.tsx +113 -0
  47. package/src/components/TypingInputBar.tsx +131 -0
  48. package/src/components/tool-layouts/components.tsx +120 -0
  49. package/src/components/tool-layouts/defaults.ts +9 -0
  50. package/src/components/tool-layouts/index.ts +22 -0
  51. package/src/components/tool-layouts/layouts/bash.ts +110 -0
  52. package/src/components/tool-layouts/layouts/grounding.tsx +98 -0
  53. package/src/components/tool-layouts/layouts/index.ts +8 -0
  54. package/src/components/tool-layouts/layouts/read-file.ts +59 -0
  55. package/src/components/tool-layouts/layouts/subagent.tsx +118 -0
  56. package/src/components/tool-layouts/layouts/system-info.ts +8 -0
  57. package/src/components/tool-layouts/layouts/todo.tsx +139 -0
  58. package/src/components/tool-layouts/layouts/url-tools.ts +220 -0
  59. package/src/components/tool-layouts/layouts/web-search.ts +110 -0
  60. package/src/components/tool-layouts/registry.ts +17 -0
  61. package/src/components/tool-layouts/types.ts +94 -0
  62. package/src/hooks/daemon-event-handlers.ts +944 -0
  63. package/src/hooks/keyboard-handlers.ts +399 -0
  64. package/src/hooks/menu-navigation.ts +147 -0
  65. package/src/hooks/use-app-audio-devices-loader.ts +71 -0
  66. package/src/hooks/use-app-callbacks.ts +202 -0
  67. package/src/hooks/use-app-context-builder.ts +159 -0
  68. package/src/hooks/use-app-display-state.ts +162 -0
  69. package/src/hooks/use-app-menus.ts +51 -0
  70. package/src/hooks/use-app-model-pricing-loader.ts +45 -0
  71. package/src/hooks/use-app-model.ts +123 -0
  72. package/src/hooks/use-app-openrouter-models-loader.ts +44 -0
  73. package/src/hooks/use-app-openrouter-provider-loader.ts +35 -0
  74. package/src/hooks/use-app-preferences-bootstrap.ts +212 -0
  75. package/src/hooks/use-app-sessions.ts +105 -0
  76. package/src/hooks/use-app-settings.ts +62 -0
  77. package/src/hooks/use-conversation-manager.ts +163 -0
  78. package/src/hooks/use-copy-on-select.ts +50 -0
  79. package/src/hooks/use-daemon-events.ts +396 -0
  80. package/src/hooks/use-daemon-keyboard.ts +397 -0
  81. package/src/hooks/use-grounding.ts +46 -0
  82. package/src/hooks/use-input-history.ts +92 -0
  83. package/src/hooks/use-menu-keyboard.ts +93 -0
  84. package/src/hooks/use-playwright-notification.ts +23 -0
  85. package/src/hooks/use-reasoning-animation.ts +97 -0
  86. package/src/hooks/use-response-timer.ts +55 -0
  87. package/src/hooks/use-tool-approval.tsx +202 -0
  88. package/src/hooks/use-typing-mode.ts +137 -0
  89. package/src/hooks/use-voice-dependencies-notification.ts +37 -0
  90. package/src/index.tsx +48 -0
  91. package/src/scripts/setup-browsers.ts +42 -0
  92. package/src/state/app-context.tsx +160 -0
  93. package/src/state/daemon-events.ts +67 -0
  94. package/src/state/daemon-state.ts +493 -0
  95. package/src/state/migrations/001-init.ts +33 -0
  96. package/src/state/migrations/index.ts +8 -0
  97. package/src/state/model-history-store.ts +45 -0
  98. package/src/state/runtime-context.ts +21 -0
  99. package/src/state/session-store.ts +359 -0
  100. package/src/types/index.ts +405 -0
  101. package/src/types/theme.ts +52 -0
  102. package/src/ui/constants.ts +157 -0
  103. package/src/utils/clipboard.ts +89 -0
  104. package/src/utils/debug-logger.ts +69 -0
  105. package/src/utils/formatters.ts +242 -0
  106. package/src/utils/js-rendering.ts +77 -0
  107. package/src/utils/markdown-tables.ts +234 -0
  108. package/src/utils/model-metadata.ts +191 -0
  109. package/src/utils/openrouter-endpoints.ts +212 -0
  110. package/src/utils/openrouter-models.ts +205 -0
  111. package/src/utils/openrouter-pricing.ts +59 -0
  112. package/src/utils/openrouter-reported-cost.ts +16 -0
  113. package/src/utils/paste.ts +33 -0
  114. package/src/utils/preferences.ts +289 -0
  115. package/src/utils/text-fragment.ts +39 -0
  116. package/src/utils/tool-output-preview.ts +250 -0
  117. package/src/utils/voice-dependencies.ts +107 -0
  118. package/src/utils/workspace-manager.ts +85 -0
  119. package/src/voice/audio-recorder.ts +579 -0
  120. package/src/voice/mic-level.ts +35 -0
  121. package/src/voice/tts/openai-tts-stream.ts +222 -0
  122. package/src/voice/tts/speech-controller.ts +64 -0
  123. package/src/voice/tts/tts-player.ts +257 -0
  124. package/src/voice/voice-input-controller.ts +96 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Voice dependencies detection.
3
+ * Detects whether sox is available for voice input/output.
4
+ */
5
+
6
+ import { spawn } from "node:child_process";
7
+
8
+ export interface VoiceCapability {
9
+ available: boolean;
10
+ reason: string;
11
+ hint?: string;
12
+ }
13
+
14
+ export interface VoiceDependencies {
15
+ sox: VoiceCapability;
16
+ /** Whether voice input (recording) is available */
17
+ canRecordAudio: boolean;
18
+ /** Whether voice output (TTS playback) is available */
19
+ canPlayAudio: boolean;
20
+ }
21
+
22
+ // Cache the detection result
23
+ let cachedDependencies: VoiceDependencies | null = null;
24
+
25
+ /**
26
+ * Check if a binary exists on PATH by attempting to run it.
27
+ */
28
+ async function checkBinaryOnPath(binary: string): Promise<boolean> {
29
+ return new Promise((resolve) => {
30
+ const proc = spawn(binary, ["--version"], {
31
+ stdio: ["ignore", "ignore", "ignore"],
32
+ });
33
+
34
+ proc.on("error", () => resolve(false));
35
+ proc.on("close", (code) => resolve(code === 0));
36
+
37
+ // Timeout after 2 seconds
38
+ setTimeout(() => {
39
+ proc.kill();
40
+ resolve(false);
41
+ }, 2000);
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Detect sox availability.
47
+ */
48
+ async function detectSox(): Promise<VoiceCapability> {
49
+ const available = await checkBinaryOnPath("sox");
50
+
51
+ if (available) {
52
+ return {
53
+ available: true,
54
+ reason: "sox is installed.",
55
+ };
56
+ }
57
+
58
+ return {
59
+ available: false,
60
+ reason: "sox is not installed.",
61
+ hint: process.platform === "darwin" ? "Run: brew install sox" : "Install sox using your package manager.",
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Detect all voice dependencies.
67
+ * Results are cached after first detection.
68
+ */
69
+ export async function detectVoiceDependencies(): Promise<VoiceDependencies> {
70
+ if (cachedDependencies) {
71
+ return cachedDependencies;
72
+ }
73
+
74
+ const sox = await detectSox();
75
+
76
+ cachedDependencies = {
77
+ sox,
78
+ // Voice input requires sox for recording
79
+ canRecordAudio: sox.available,
80
+ // Voice output requires sox for playback
81
+ canPlayAudio: sox.available,
82
+ };
83
+
84
+ return cachedDependencies;
85
+ }
86
+
87
+ /**
88
+ * Get the cached voice dependencies (returns null if not yet detected).
89
+ */
90
+ export function getCachedVoiceDependencies(): VoiceDependencies | null {
91
+ return cachedDependencies;
92
+ }
93
+
94
+ /**
95
+ * Clear the cached voice dependencies (useful for testing).
96
+ */
97
+ export function clearVoiceDependenciesCache(): void {
98
+ cachedDependencies = null;
99
+ }
100
+
101
+ /**
102
+ * Synchronously check if sox is available.
103
+ * Uses cached result if available, otherwise returns false.
104
+ */
105
+ export function isSoxAvailable(): boolean {
106
+ return cachedDependencies?.sox.available ?? false;
107
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Workspace manager for DAEMON sessions.
3
+ * Each session gets a persistent workspace directory for file operations,
4
+ * cloning repositories, and other agent tasks that shouldn't pollute
5
+ * the user's current working directory.
6
+ */
7
+
8
+ import { promises as fs } from "node:fs";
9
+ import path from "node:path";
10
+ import { getAppConfigDir } from "./preferences";
11
+ import { debug } from "./debug-logger";
12
+
13
+ const WORKSPACES_DIR = "workspaces";
14
+
15
+ /**
16
+ * Get the root directory for all session workspaces.
17
+ * @returns Path to ~/.config/daemon/workspaces/
18
+ */
19
+ export function getWorkspacesRoot(): string {
20
+ return path.join(getAppConfigDir(), WORKSPACES_DIR);
21
+ }
22
+
23
+ /**
24
+ * Get the workspace path for a specific session.
25
+ * Does not create the directory - use ensureWorkspaceExists for that.
26
+ * @param sessionId - The session UUID
27
+ * @returns Path to ~/.config/daemon/workspaces/{sessionId}/
28
+ */
29
+ export function getWorkspacePath(sessionId: string): string {
30
+ return path.join(getWorkspacesRoot(), sessionId);
31
+ }
32
+
33
+ /**
34
+ * Ensure the workspace directory exists for a session.
35
+ * Creates the directory if it doesn't exist.
36
+ * @param sessionId - The session UUID
37
+ * @returns The absolute path to the workspace directory
38
+ */
39
+ export async function ensureWorkspaceExists(sessionId: string): Promise<string> {
40
+ const workspacePath = getWorkspacePath(sessionId);
41
+ try {
42
+ await fs.mkdir(workspacePath, { recursive: true });
43
+ debug.info("workspace-created", { sessionId, path: workspacePath });
44
+ } catch (error) {
45
+ // If directory already exists, that's fine
46
+ const err = error instanceof Error ? error : new Error(String(error));
47
+ if (!err.message.includes("EEXIST")) {
48
+ debug.error("workspace-create-failed", { sessionId, message: err.message });
49
+ throw err;
50
+ }
51
+ }
52
+ return workspacePath;
53
+ }
54
+
55
+ /**
56
+ * Delete the workspace directory for a session.
57
+ * Called when a session is deleted.
58
+ * @param sessionId - The session UUID
59
+ */
60
+ export async function deleteWorkspace(sessionId: string): Promise<void> {
61
+ const workspacePath = getWorkspacePath(sessionId);
62
+ try {
63
+ await fs.rm(workspacePath, { recursive: true, force: true });
64
+ debug.info("workspace-deleted", { sessionId, path: workspacePath });
65
+ } catch (error) {
66
+ // Log but don't throw - cleanup failures shouldn't break session deletion
67
+ const err = error instanceof Error ? error : new Error(String(error));
68
+ debug.error("workspace-delete-failed", { sessionId, message: err.message });
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Check if a workspace exists for a session.
74
+ * @param sessionId - The session UUID
75
+ * @returns true if the workspace directory exists
76
+ */
77
+ export async function workspaceExists(sessionId: string): Promise<boolean> {
78
+ const workspacePath = getWorkspacePath(sessionId);
79
+ try {
80
+ const stat = await fs.stat(workspacePath);
81
+ return stat.isDirectory();
82
+ } catch {
83
+ return false;
84
+ }
85
+ }