@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 makefinks
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # DAEMON
2
+ **DAEMON** (pronounced "day-mon") is an opinionated **terminal-based AI agent** with distinct sci-fi theming,
3
+ delivered through a well-thought-out TUI powered by [OpenTUI](https://github.com/anomalyco/opentui).
4
+ It supports **text and voice interaction** and can be fully controlled through **hotkeys**.
5
+
6
+ DAEMON is focused on **information-gathering workflows** that benefit from **grounded responses**
7
+ but can also interact with and **control** your system through the terminal with scoped permissions.
8
+
9
+ ![DAEMON terminal avatar](img/daemon.gif)
10
+
11
+ ## Highlights
12
+
13
+ ### 👤 Interactive Avatar
14
+ At the core of the TUI is DAEMON's **animated avatar**, reacting to what it's doing in real time:
15
+ listening to audio input, reasoning about questions, calling tools, and generating an answer.
16
+
17
+ The avatar was deliberately designed to feel slightly ominous and alien-like playing into
18
+
19
+ ### 🧠 LLMs
20
+ DAEMON can be powered by **any** model available on [OpenRouter](https://openrouter.ai/models).
21
+ The TUI includes a model picker that can fetch and select models as they become available on OpenRouter.
22
+ Models are sorted by average provider pricing and the picker includes information about their context window and caching support.
23
+ Once a model is selected, the **selection of the inference provider** is possible. Open-source models can vary strongly between providers.
24
+ > While all models on OpenRouter are supported, a curated list is included for the best experience.
25
+ It is generally recommended to use Models that support caching due to being significantly cheaper in DAEMON
26
+
27
+ ![Model Picker](img/model-picker.png)
28
+
29
+
30
+ ### 🎙️ Voice capabilities
31
+ SOTA transcription accuracy is achieved by using OpenAI's latest transcription model `gpt-4o-mini-transcribe-2025-03-20`.
32
+ It features a large vocabulary and can transcribe multilingual inputs with complex terminology.
33
+
34
+ OpenAI's TTS model `gpt-4o-mini-tts-2025-03-20` is used to generate voice output with as little latency as possible.
35
+
36
+ ### 🔎 Web Search
37
+ DAEMON uses the [Exa](https://exa.ai/) search and fetch API for retrieving accurate and up-to-date information.
38
+
39
+ ### 💾 Session Persistence
40
+ DAEMON stores chat sessions locally (SQLite) and lets you resume past conversations.
41
+
42
+ ## ✨ Feature List
43
+
44
+ | Feature | Description |
45
+ | --- | --- |
46
+ | Terminal TUI | OpenTUI-powered interface with sci-fi styling and hotkey controls. |
47
+ | Text + Voice | Supports text input and voice interaction with transcription and TTS. |
48
+ | Animated Avatar | Sci-fi avatar reacts to listening, tool use, and response generation. |
49
+ | Multi-Model Support | Works with all OpenRouter models and includes a curated default list. |
50
+ | Session Persistence | Preferences and chat sessions stored locally on disk. |
51
+ | Workspaces | On-disk workspaces for the agent to work in. |
52
+ | Web Search | Exa-based search and fetch for grounded, up-to-date info. |
53
+ | Grounding | Text-fragment grounding with a dedicated UI. |
54
+ | Bash Execution | Bash integration with approval scoping for potentially dangerous commands. |
55
+ | JS Page Rendering | Optional Playwright renderer for SPA content. |
56
+
57
+ ## 📦 Install (npm/bun)
58
+
59
+ DAEMON is published as a CLI package. It **requires Bun** at runtime, even if you install via npm.
60
+
61
+ To install bun on macOs/Linux
62
+ ```bash
63
+ curl -fsSL https://bun.com/install | bash
64
+ ```
65
+ Then install DAEMON:
66
+ ```bash
67
+ # Global npm install
68
+ npm i -g @makefinks/daemon
69
+ # or via bun
70
+ bun add -g @makefinks/daemon
71
+
72
+ # Then run
73
+ daemon
74
+ ```
75
+
76
+ Configuration is done via environment variables (or the onboarding UI):
77
+
78
+ - `OPENROUTER_API_KEY` (required) - response generation via OpenRouter models
79
+ - `EXA_API_KEY` (optional) - enables web search + fetch grounding via Exa
80
+ - `OPENAI_API_KEY` (optional) - enables voice transcription + TTS
81
+
82
+ > Keys entered via the onboarding UI are stored locally in `~/.config/daemon/credentials.json` with restricted permissions (`0600`). For maximum security, use environment variables instead.
83
+
84
+ ## 🛠️ System dependencies
85
+
86
+ Voice input requires `sox` and platform-specific audio libraries:
87
+
88
+ ### macOS
89
+ ```bash
90
+ brew install sox
91
+ ```
92
+
93
+ ### Linux (Debian/Ubuntu)
94
+ ```bash
95
+ sudo apt install sox libsox-fmt-pulse
96
+ ```
97
+
98
+ ### Linux (Fedora)
99
+ ```bash
100
+ sudo dnf install sox sox-plugins-freeworld
101
+ ```
102
+
103
+ ### Linux (Arch)
104
+ ```bash
105
+ sudo pacman -S sox
106
+ ```
107
+
108
+ ## 🧩 Optional: JS-rendered page support (`renderUrl`)
109
+
110
+ DAEMON defaults to Exa-based `fetchUrls` for retrieving web page text. For JavaScript-heavy sites (SPAs) where `fetchUrls` returns "shell-only" content, DAEMON can optionally use a local Playwright Chromium renderer via the `renderUrl` tool.
111
+
112
+ This feature is **optional** and intentionally not installed by default (browser downloads are large). The render tool is not available to DAEMON without the installation below.
113
+
114
+ ### For global installs (`npm i -g` / `bun add -g`)
115
+
116
+ Playwright must also be installed globally:
117
+
118
+ ```bash
119
+ # 1) Install Playwright globally
120
+ npm i -g playwright
121
+ # or
122
+ bun add -g playwright
123
+
124
+ # 2) Install Chromium browser binaries
125
+ npx playwright install chromium
126
+ ```
package/dist/cli.js ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { spawnSync } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
6
+ import path from "node:path";
7
+ var args = process.argv.slice(2);
8
+ var bunCmd = process.platform === "win32" ? "bun.exe" : "bun";
9
+ var bunCheck = spawnSync(bunCmd, ["--version"], { stdio: "ignore" });
10
+ if (bunCheck.error || bunCheck.status !== 0) {
11
+ console.error("DAEMON requires Bun. Install it from https://bun.sh and try again.");
12
+ process.exit(1);
13
+ }
14
+ var packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
15
+ var entry = path.join(packageRoot, "src", "index.tsx");
16
+ var result = spawnSync(bunCmd, [entry, ...args], { stdio: "inherit" });
17
+ if (result.error) {
18
+ const error = result.error instanceof Error ? result.error : new Error(String(result.error));
19
+ console.error(error.message);
20
+ process.exit(1);
21
+ }
22
+ process.exit(result.status ?? 0);
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@makefinks/daemon",
3
+ "description": "Terminal-first AI agent with an interactive avatar, vim-style controls, and advanced voice capabilities.",
4
+ "author": "makefinks",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "agent",
8
+ "llm-agent",
9
+ "terminal-ui",
10
+ "tui",
11
+ "cli",
12
+ "ai",
13
+ "terminal",
14
+ "llm",
15
+ "daemon",
16
+ "makefinks",
17
+ "openrouter",
18
+ "bun",
19
+ "voice",
20
+ "ai-assistant"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/makefinks/daemon.git"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/makefinks/daemon/issues"
28
+ },
29
+ "module": "src/index.tsx",
30
+ "type": "module",
31
+ "version": "0.1.0",
32
+ "bin": {
33
+ "daemon": "dist/cli.js"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "files": ["dist", "src", "LICENSE", "README.md", "package.json"],
39
+ "scripts": {
40
+ "build:cli": "bun build src/cli.ts --outdir dist --target node",
41
+ "dev": "bun run --watch src/index.tsx",
42
+ "check": "bun run typecheck && bun run lint && bun run format:check",
43
+ "typecheck": "bunx tsc -p tsconfig.json --noEmit",
44
+ "lint": "biome lint src __tests__",
45
+ "lint:fix": "biome lint --write src __tests__",
46
+ "format": "biome format --write src __tests__",
47
+ "format:check": "biome format src __tests__",
48
+ "preview:avatar": "bun run src/avatar-preview.ts",
49
+ "preview:avatar:mp4": "bun run src/avatar-preview.ts --mp4 tmp/avatar-preview.mp4",
50
+ "setup:browsers": "bun run src/scripts/setup-browsers.ts",
51
+ "test": "bun test",
52
+ "test:watch": "bun test --watch",
53
+ "prepublishOnly": "bun run build:cli",
54
+ "release:patch": "npm version patch && git push && git push --tags && npm publish",
55
+ "release:minor": "npm version minor && git push && git push --tags && npm publish",
56
+ "release:major": "npm version major && git push && git push --tags && npm publish"
57
+ },
58
+ "devDependencies": {
59
+ "@biomejs/biome": "^1.9.4",
60
+ "@types/bun": "latest",
61
+ "@types/react": "^19.2.7",
62
+ "playwright": "^1.57.0"
63
+ },
64
+ "peerDependencies": {
65
+ "typescript": "^5.9.2"
66
+ },
67
+ "dependencies": {
68
+ "@ai-sdk/openai": "^3.0.0",
69
+ "@openrouter/ai-sdk-provider": "1.5.4",
70
+ "@opentui-ui/toast": "^0.0.3",
71
+ "@opentui/core": "^0.1.63",
72
+ "@opentui/react": "^0.1.63",
73
+ "ai": "^6.0.0",
74
+ "exa-js": "^2.0.12",
75
+ "openai": "^6.16.0",
76
+ "opentui-spinner": "^0.0.6",
77
+ "react": "^19.2.3"
78
+ }
79
+ }
@@ -0,0 +1,130 @@
1
+ import { generateResponse } from "./daemon-ai";
2
+ import type {
3
+ InteractionMode,
4
+ ModelMessage,
5
+ ReasoningEffort,
6
+ StreamCallbacks,
7
+ TokenUsage,
8
+ ToolApprovalRequest,
9
+ ToolApprovalResponse,
10
+ } from "../types";
11
+
12
+ export interface AgentTurnParams {
13
+ userText: string;
14
+ conversationHistory: ModelMessage[];
15
+ interactionMode: InteractionMode;
16
+ reasoningEffort: ReasoningEffort;
17
+ }
18
+
19
+ export interface AgentTurnResult {
20
+ fullText: string;
21
+ responseMessages: ModelMessage[];
22
+ usage?: TokenUsage;
23
+ finalText?: string;
24
+ }
25
+
26
+ export class AgentTurnRunner {
27
+ private abortController: AbortController | null = null;
28
+ private activeRunId = 0;
29
+
30
+ cancel(): void {
31
+ if (this.abortController) {
32
+ this.abortController.abort();
33
+ this.abortController = null;
34
+ }
35
+ this.activeRunId++;
36
+ }
37
+
38
+ async run(params: AgentTurnParams, callbacks: StreamCallbacks): Promise<AgentTurnResult | null> {
39
+ const runId = ++this.activeRunId;
40
+ this.abortController = new AbortController();
41
+
42
+ const isActive = () => runId === this.activeRunId && this.abortController !== null;
43
+
44
+ let result: AgentTurnResult | null = null;
45
+ let error: Error | null = null;
46
+
47
+ const wrapped: StreamCallbacks = {
48
+ onReasoningToken: (token) => {
49
+ if (!isActive()) return;
50
+ callbacks.onReasoningToken?.(token);
51
+ },
52
+ onToolCallStart: (toolName, toolCallId) => {
53
+ if (!isActive()) return;
54
+ callbacks.onToolCallStart?.(toolName, toolCallId);
55
+ },
56
+ onToolCall: (toolName, args, toolCallId) => {
57
+ if (!isActive()) return;
58
+ callbacks.onToolCall?.(toolName, args, toolCallId);
59
+ },
60
+ onToolResult: (toolName, resultValue, toolCallId) => {
61
+ if (!isActive()) return;
62
+ callbacks.onToolResult?.(toolName, resultValue, toolCallId);
63
+ },
64
+ onToolApprovalRequest: (request) => {
65
+ if (!isActive()) return;
66
+ callbacks.onToolApprovalRequest?.(request);
67
+ },
68
+ onAwaitingApprovals: (pendingApprovals, respondToApprovals) => {
69
+ if (!isActive()) return;
70
+ callbacks.onAwaitingApprovals?.(pendingApprovals, respondToApprovals);
71
+ },
72
+ onSubagentToolCall: (toolCallId, toolName, input) => {
73
+ if (!isActive()) return;
74
+ callbacks.onSubagentToolCall?.(toolCallId, toolName, input);
75
+ },
76
+ onSubagentUsage: (usage) => {
77
+ if (!isActive()) return;
78
+ callbacks.onSubagentUsage?.(usage);
79
+ },
80
+ onSubagentToolResult: (toolCallId, toolName, success) => {
81
+ if (!isActive()) return;
82
+ callbacks.onSubagentToolResult?.(toolCallId, toolName, success);
83
+ },
84
+ onSubagentComplete: (toolCallId, success) => {
85
+ if (!isActive()) return;
86
+ callbacks.onSubagentComplete?.(toolCallId, success);
87
+ },
88
+ onToken: (token) => {
89
+ if (!isActive()) return;
90
+ callbacks.onToken?.(token);
91
+ },
92
+ onStepUsage: (usage) => {
93
+ if (!isActive()) return;
94
+ callbacks.onStepUsage?.(usage);
95
+ },
96
+ onComplete: (fullText, responseMessages, usage, finalText) => {
97
+ if (!isActive()) return;
98
+ result = { fullText, responseMessages, usage, finalText };
99
+ callbacks.onComplete?.(fullText, responseMessages, usage, finalText);
100
+ },
101
+ onError: (err) => {
102
+ if (!isActive()) return;
103
+ error = err;
104
+ callbacks.onError?.(err);
105
+ },
106
+ };
107
+
108
+ try {
109
+ await generateResponse(
110
+ params.userText,
111
+ wrapped,
112
+ params.conversationHistory,
113
+ params.interactionMode,
114
+ this.abortController.signal,
115
+ params.reasoningEffort
116
+ );
117
+ } catch (err) {
118
+ const e = err instanceof Error ? err : new Error(String(err));
119
+ error = e;
120
+ wrapped.onError?.(e);
121
+ } finally {
122
+ if (isActive()) {
123
+ this.abortController = null;
124
+ }
125
+ }
126
+
127
+ if (error) throw error;
128
+ return result;
129
+ }
130
+ }