@sarkar-ai/deskmate 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +8 -10
- package/README.md +95 -32
- package/dist/cli/init.js +180 -57
- package/dist/cli.js +47 -28
- package/dist/index.js +49 -27
- package/install.sh +103 -24
- package/package.json +5 -5
- package/dist/telegram/bot.js +0 -333
package/.env.example
CHANGED
|
@@ -4,24 +4,22 @@
|
|
|
4
4
|
# Run `deskmate init` for interactive setup, or copy this file to .env and edit.
|
|
5
5
|
# Alternative: ./install.sh
|
|
6
6
|
|
|
7
|
-
#
|
|
8
|
-
# https://t.me/BotFather → /newbot → copy token
|
|
9
|
-
TELEGRAM_BOT_TOKEN=your_bot_token_here
|
|
10
|
-
|
|
11
|
-
# Your Telegram User ID (get from @userinfobot)
|
|
12
|
-
# https://t.me/userinfobot → send any message → copy Id
|
|
13
|
-
# Only this user can interact with the bot
|
|
14
|
-
ALLOWED_USER_ID=your_user_id_here
|
|
15
|
-
|
|
16
|
-
# Multi-client allowed users (for gateway mode)
|
|
7
|
+
# Allowed users (required)
|
|
17
8
|
# Format: clientType:userId, comma-separated
|
|
18
9
|
# Example: telegram:123456,discord:987654321,slack:U12345
|
|
19
10
|
ALLOWED_USERS=telegram:your_user_id_here
|
|
20
11
|
|
|
12
|
+
# Telegram Bot Token (get from @BotFather)
|
|
13
|
+
# https://t.me/BotFather -> /newbot -> copy token
|
|
14
|
+
TELEGRAM_BOT_TOKEN=your_bot_token_here
|
|
15
|
+
|
|
21
16
|
# Anthropic API Key
|
|
22
17
|
# https://console.anthropic.com/
|
|
23
18
|
ANTHROPIC_API_KEY=your_anthropic_key_here
|
|
24
19
|
|
|
20
|
+
# Legacy single-user Telegram ID (still supported, converted to telegram:<id> internally)
|
|
21
|
+
# ALLOWED_USER_ID=your_user_id_here
|
|
22
|
+
|
|
25
23
|
# ===========================================
|
|
26
24
|
# Optional Configuration
|
|
27
25
|
# ===========================================
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Control your Local Machine from anywhere using natural language.
|
|
|
8
8
|
<a href="#requirements"><img src="https://img.shields.io/badge/node-%3E%3D18-green.svg?style=for-the-badge" alt="Node"></a>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
-
Deskmate is a
|
|
11
|
+
Deskmate is a local execution agent that lets you control your personal machine using natural language and talks to you on the channels you already use. Deskmate focuses on execution, not autonomy or orchestration. Send a Telegram message from your phone, and it executes on your machine. Powered by the [Claude Agent SDK](https://docs.anthropic.com/en/docs/claude-code/agent-sdk) with full local tool access — no sandboxed command set, no artificial limits.
|
|
12
12
|
|
|
13
13
|
A passion project developed, born from a simple goal: staying in creative and developer flow even when I'm not sitting at my desk. Inspired by [gen-shell](https://github.com/sarkarsaurabh27/gen-shell).
|
|
14
14
|
|
|
@@ -18,6 +18,10 @@ A passion project developed, born from a simple goal: staying in creative and de
|
|
|
18
18
|
|
|
19
19
|
## Demo
|
|
20
20
|
|
|
21
|
+
<p align="center">
|
|
22
|
+
<img src="assets/deskmate-screenshot.jpeg" alt="Deskmate Screenshot" width="500">
|
|
23
|
+
</p>
|
|
24
|
+
|
|
21
25
|
| Telegram Conversation | Installation |
|
|
22
26
|
|:---:|:---:|
|
|
23
27
|
|  |  |
|
|
@@ -47,6 +51,17 @@ Telegram / Discord* / Slack* / ...
|
|
|
47
51
|
|
|
48
52
|
The Gateway is the control plane. Each messaging platform is a thin I/O adapter. The agent has unrestricted access to your machine (approve-by-default), with optional approval gating for protected folders.
|
|
49
53
|
|
|
54
|
+
## Responsibility Boundary
|
|
55
|
+
|
|
56
|
+
Deskmate’s responsibility is **execution**.
|
|
57
|
+
|
|
58
|
+
- It turns intent into concrete system actions
|
|
59
|
+
- It does not coordinate other agents
|
|
60
|
+
- It does not monitor agent health or resource usage
|
|
61
|
+
|
|
62
|
+
If you want visibility into what agents are doing on your machine,
|
|
63
|
+
see **Riva**, the local observability layer.
|
|
64
|
+
|
|
50
65
|
## Highlights
|
|
51
66
|
|
|
52
67
|
- **Full local access** — the agent can run any command, read/write any file, take screenshots. No artificial 6-tool sandbox.
|
|
@@ -87,25 +102,34 @@ The installer guides you through these (macOS only). You can also configure them
|
|
|
87
102
|
|
|
88
103
|
## Quick Start
|
|
89
104
|
|
|
90
|
-
### Install from npm (recommended
|
|
105
|
+
### Option A: Install from npm (recommended)
|
|
91
106
|
|
|
92
107
|
```bash
|
|
93
|
-
npm install -g deskmate
|
|
108
|
+
npm install -g @sarkar-ai/deskmate
|
|
94
109
|
deskmate init
|
|
95
110
|
```
|
|
96
111
|
|
|
97
112
|
The wizard walks you through everything: API keys, Telegram credentials,
|
|
98
|
-
platform permissions, and background service setup.
|
|
113
|
+
platform permissions, and background service setup. Config is stored in
|
|
114
|
+
`~/.config/deskmate/.env`.
|
|
99
115
|
|
|
100
|
-
|
|
116
|
+
After setup, run manually with `deskmate` or let the background service handle it.
|
|
117
|
+
|
|
118
|
+
### Option B: Install from source (for contributors)
|
|
101
119
|
|
|
102
120
|
```bash
|
|
103
121
|
git clone https://github.com/sarkar-ai-taken/deskmate.git
|
|
104
122
|
cd deskmate
|
|
105
123
|
npm install --legacy-peer-deps
|
|
106
|
-
cp .env.example .env # edit with your credentials
|
|
107
124
|
npm run build
|
|
108
|
-
./install.sh #
|
|
125
|
+
./install.sh # interactive: configures .env, service, permissions
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Or use the TypeScript wizard instead of the shell installer:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
cp .env.example .env # edit with your credentials
|
|
132
|
+
npx deskmate init # or: npm link && deskmate init
|
|
109
133
|
```
|
|
110
134
|
|
|
111
135
|
To reconfigure later: `deskmate init`
|
|
@@ -114,30 +138,26 @@ To reconfigure later: `deskmate init`
|
|
|
114
138
|
|
|
115
139
|
| Mode | Command | Description |
|
|
116
140
|
|------|---------|-------------|
|
|
117
|
-
|
|
|
118
|
-
| Gateway | `deskmate gateway` | Multi-client gateway (recommended for new setups) |
|
|
141
|
+
| Gateway | `deskmate` | Multi-client gateway (default) |
|
|
119
142
|
| MCP | `deskmate mcp` | MCP server for Claude Desktop |
|
|
120
|
-
| Both | `deskmate both` |
|
|
143
|
+
| Both | `deskmate both` | Gateway + MCP simultaneously |
|
|
144
|
+
|
|
145
|
+
> **Note:** `deskmate telegram` still works but is a deprecated alias that starts the gateway.
|
|
121
146
|
|
|
122
147
|
## Gateway Mode
|
|
123
148
|
|
|
124
|
-
The gateway is the
|
|
149
|
+
The gateway is the default way to run Deskmate. It separates platform I/O from agent logic, so adding a new messaging client doesn't require touching auth, sessions, or the agent layer.
|
|
125
150
|
|
|
126
151
|
```bash
|
|
127
152
|
# Configure multi-client auth
|
|
128
153
|
ALLOWED_USERS=telegram:123456,discord:987654321
|
|
129
154
|
|
|
130
155
|
# Start
|
|
131
|
-
deskmate
|
|
156
|
+
deskmate
|
|
132
157
|
```
|
|
133
158
|
|
|
134
159
|
The gateway auto-registers clients based on available env vars. If `TELEGRAM_BOT_TOKEN` is set, Telegram is active. Future clients (Discord, Slack) follow the same pattern.
|
|
135
160
|
|
|
136
|
-
### Gateway vs Telegram mode
|
|
137
|
-
|
|
138
|
-
- **`deskmate telegram`** — original standalone bot. Simple, self-contained, no gateway overhead. Good for single-user Telegram-only setups.
|
|
139
|
-
- **`deskmate gateway`** — centralized architecture. Auth, sessions, and agent orchestration are shared. Required for multi-client setups and recommended for new installations.
|
|
140
|
-
|
|
141
161
|
## Bot Commands
|
|
142
162
|
|
|
143
163
|
| Command | Description |
|
|
@@ -187,32 +207,77 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
|
187
207
|
|
|
188
208
|
Restart Claude Desktop. You can now ask Claude to interact with your local machine.
|
|
189
209
|
|
|
190
|
-
### Combined Mode (
|
|
210
|
+
### Combined Mode (Gateway + MCP)
|
|
211
|
+
|
|
212
|
+
Run both with `deskmate both`. MCP handles Claude Desktop requests; the gateway handles Telegram (and future clients), sending approval notifications to your phone so you can approve sensitive operations from anywhere.
|
|
191
213
|
|
|
192
|
-
|
|
214
|
+
### Observability
|
|
215
|
+
|
|
216
|
+
Deskmate focuses on executing actions safely.
|
|
217
|
+
|
|
218
|
+
For monitoring agent behavior, resource usage, and failures across
|
|
219
|
+
multiple local agents, see **Riva** (local-first agent observability).
|
|
193
220
|
|
|
194
221
|
## Security
|
|
195
222
|
|
|
196
223
|
> **Important**: The agent can execute arbitrary commands on your machine. This is by design — the strategy is approve-by-default for read-only operations, with approval gating for protected folders and write operations.
|
|
197
224
|
|
|
198
|
-
|
|
225
|
+
### Built-in protections
|
|
199
226
|
|
|
200
227
|
| Layer | What it does |
|
|
201
228
|
|-------|-------------|
|
|
202
|
-
| **User authentication** | Only
|
|
203
|
-
| **
|
|
204
|
-
| **
|
|
205
|
-
| **
|
|
206
|
-
| **
|
|
207
|
-
| **Session isolation** |
|
|
208
|
-
|
|
209
|
-
**
|
|
229
|
+
| **User authentication** | Allowlist-based access control via `SecurityManager`. Only users in `ALLOWED_USERS` can interact. Supports per-client auth (`telegram:123`, `discord:456`) and wildcards (`*:*`). |
|
|
230
|
+
| **Action approval** | `ApprovalManager` gates sensitive operations. Write commands, file writes, and folder access require explicit human approval with configurable timeouts (default 5 min). |
|
|
231
|
+
| **Protected folders** | OS-aware folder protection. Desktop, Documents, Downloads, Pictures, Movies/Videos, Music, and iCloud (macOS) require approval. Session-based caching avoids repeated prompts. |
|
|
232
|
+
| **Safe command auto-approval** | Read-only commands (`ls`, `cat`, `git status`, `docker ps`, `node -v`, etc.) auto-approve. Full list in `src/core/approval.ts`. |
|
|
233
|
+
| **Command execution limits** | 2-minute timeout and 10 MB output buffer per command. Prevents runaway processes and memory exhaustion. |
|
|
234
|
+
| **Session isolation** | Sessions keyed by `clientType:channelId`. 30-minute idle timeout with automatic pruning. Optional disk persistence survives restarts. |
|
|
235
|
+
| **Input validation** | MCP tools use Zod schema validation. Telegram callbacks validated via regex patterns. |
|
|
236
|
+
| **No open ports** | The bot polls Telegram's servers — no inbound ports exposed. |
|
|
237
|
+
| **No sudo by default** | The agent won't use sudo unless you explicitly ask. |
|
|
238
|
+
| **Structured logging** | All actions logged with timestamps, context hierarchy, and configurable log levels for audit trails. |
|
|
239
|
+
| **Stale message protection** | Telegram client drops pending updates on startup (`drop_pending_updates: true`), preventing replay of messages received while offline. |
|
|
240
|
+
|
|
241
|
+
### Approval workflow
|
|
242
|
+
|
|
243
|
+
1. User sends a message that triggers a sensitive operation (e.g., writing to `~/Documents`)
|
|
244
|
+
2. `ApprovalManager` checks if the action matches a safe auto-approve pattern
|
|
245
|
+
3. If not safe, a pending approval is created with a timeout countdown
|
|
246
|
+
4. Approval request is broadcast to all clients with recent activity (last 30 min)
|
|
247
|
+
5. User taps Approve/Reject via inline buttons (Telegram) or equivalent
|
|
248
|
+
6. Action executes on approval, or is cancelled on rejection/timeout
|
|
249
|
+
|
|
250
|
+
Set `REQUIRE_APPROVAL_FOR_ALL=true` to gate every operation, including reads.
|
|
251
|
+
|
|
252
|
+
### Recommendations
|
|
253
|
+
|
|
210
254
|
- Set `WORKING_DIR` to limit default command scope
|
|
211
|
-
- Use `ALLOWED_USERS`
|
|
255
|
+
- Use `ALLOWED_USERS` for multi-client allowlisting
|
|
256
|
+
- Use `ALLOWED_FOLDERS` to pre-approve specific directories
|
|
212
257
|
- Review logs regularly (`logs/stdout.log`)
|
|
213
258
|
- Keep `.env` secure and never commit it
|
|
214
259
|
- Use `REQUIRE_APPROVAL_FOR_ALL=true` if you want to approve every operation
|
|
215
260
|
|
|
261
|
+
### Execution Philosophy
|
|
262
|
+
|
|
263
|
+
Deskmate follows an **approve-by-default, visible-by-design** model.
|
|
264
|
+
|
|
265
|
+
- Read-only operations are auto-approved
|
|
266
|
+
- Sensitive operations require explicit confirmation
|
|
267
|
+
- All actions are logged locally
|
|
268
|
+
|
|
269
|
+
The goal is speed without hidden behavior.
|
|
270
|
+
|
|
271
|
+
## Non-goals
|
|
272
|
+
|
|
273
|
+
Deskmate is intentionally not:
|
|
274
|
+
- A multi-agent orchestration framework
|
|
275
|
+
- A cloud-hosted control plane
|
|
276
|
+
- A long-running autonomous system
|
|
277
|
+
- A monitoring or observability tool
|
|
278
|
+
|
|
279
|
+
These constraints are deliberate.
|
|
280
|
+
|
|
216
281
|
## Architecture
|
|
217
282
|
|
|
218
283
|
```
|
|
@@ -233,8 +298,6 @@ src/
|
|
|
233
298
|
│ └── session.ts # Session manager (composite keys, idle pruning)
|
|
234
299
|
├── clients/
|
|
235
300
|
│ └── telegram.ts # Telegram adapter (grammY)
|
|
236
|
-
├── telegram/
|
|
237
|
-
│ └── bot.ts # Legacy standalone Telegram bot
|
|
238
301
|
└── mcp/
|
|
239
302
|
└── server.ts # MCP server
|
|
240
303
|
```
|
|
@@ -312,7 +375,7 @@ systemctl --user status deskmate.service
|
|
|
312
375
|
|
|
313
376
|
**Bot not responding?**
|
|
314
377
|
1. Check logs: `tail -f logs/stderr.log`
|
|
315
|
-
2. Verify your `
|
|
378
|
+
2. Verify your `ALLOWED_USERS` includes your Telegram ID (e.g. `telegram:123456`)
|
|
316
379
|
3. Ensure Claude Code CLI is installed: `which claude`
|
|
317
380
|
|
|
318
381
|
**Commands timing out?**
|
package/dist/cli/init.js
CHANGED
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Interactive Setup Wizard
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Supports two install paths:
|
|
6
|
+
* 1. npm global: `npm install -g @sarkar-ai/deskmate && deskmate init`
|
|
7
|
+
* — config stored in ~/.config/deskmate/.env
|
|
8
|
+
* — service uses the global `deskmate` binary
|
|
7
9
|
*
|
|
8
|
-
*
|
|
10
|
+
* 2. Source clone: `git clone ... && cd deskmate && deskmate init` (or ./install.sh)
|
|
11
|
+
* — config stored in project root .env
|
|
12
|
+
* — service uses `node <projectDir>/dist/index.js`
|
|
13
|
+
*
|
|
14
|
+
* For an alternative shell-based installer (source path only), see ./install.sh.
|
|
9
15
|
*/
|
|
10
16
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
17
|
if (k2 === undefined) k2 = k;
|
|
@@ -89,6 +95,67 @@ function detectPlatform() {
|
|
|
89
95
|
return "unsupported";
|
|
90
96
|
}
|
|
91
97
|
}
|
|
98
|
+
function resolveInstallPaths() {
|
|
99
|
+
// dist/cli/init.js -> go up two levels to get package root
|
|
100
|
+
const packageDir = path.resolve(__dirname, "..", "..");
|
|
101
|
+
const isGlobal = __dirname.includes("node_modules");
|
|
102
|
+
if (isGlobal) {
|
|
103
|
+
const configDir = path.join(os.homedir(), ".config", "deskmate");
|
|
104
|
+
// Use the global deskmate binary (which is in PATH)
|
|
105
|
+
const deskmateCmd = process.argv[1] || "deskmate";
|
|
106
|
+
// Resolve to an absolute path so the service always finds it
|
|
107
|
+
let deskmateBin;
|
|
108
|
+
try {
|
|
109
|
+
deskmateBin = (0, child_process_1.execSync)("which deskmate", { encoding: "utf-8" }).trim();
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
deskmateBin = deskmateCmd;
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
isGlobalInstall: true,
|
|
116
|
+
packageDir,
|
|
117
|
+
configDir,
|
|
118
|
+
execStart: (runMode) => `${deskmateBin} ${runMode}`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// Source install — configDir is the project root
|
|
122
|
+
return {
|
|
123
|
+
isGlobalInstall: false,
|
|
124
|
+
packageDir,
|
|
125
|
+
configDir: packageDir,
|
|
126
|
+
execStart: (runMode) => `${process.execPath} ${packageDir}/dist/index.js ${runMode}`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// .env reader
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
async function loadExistingEnv(envPath) {
|
|
133
|
+
const existing = {};
|
|
134
|
+
try {
|
|
135
|
+
const content = await fs.readFile(envPath, "utf-8");
|
|
136
|
+
for (const line of content.split("\n")) {
|
|
137
|
+
const trimmed = line.trim();
|
|
138
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
139
|
+
continue;
|
|
140
|
+
const eqIdx = trimmed.indexOf("=");
|
|
141
|
+
if (eqIdx > 0) {
|
|
142
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
143
|
+
const value = trimmed.slice(eqIdx + 1).trim();
|
|
144
|
+
if (value)
|
|
145
|
+
existing[key] = value;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// file doesn't exist — fine
|
|
151
|
+
}
|
|
152
|
+
return existing;
|
|
153
|
+
}
|
|
154
|
+
function mask(value) {
|
|
155
|
+
if (value.length <= 8)
|
|
156
|
+
return "****";
|
|
157
|
+
return value.slice(0, 4) + "..." + value.slice(-4);
|
|
158
|
+
}
|
|
92
159
|
// ---------------------------------------------------------------------------
|
|
93
160
|
// Service installation helpers
|
|
94
161
|
// ---------------------------------------------------------------------------
|
|
@@ -102,7 +169,10 @@ function systemdDir() {
|
|
|
102
169
|
function systemdPath() {
|
|
103
170
|
return path.join(systemdDir(), "deskmate.service");
|
|
104
171
|
}
|
|
105
|
-
function buildPlist(
|
|
172
|
+
function buildPlist(execStart, workingDir, logsDir) {
|
|
173
|
+
// Split the execStart into program + arguments for ProgramArguments array
|
|
174
|
+
const parts = execStart.split(" ");
|
|
175
|
+
const argsXml = parts.map((p) => ` <string>${p}</string>`).join("\n");
|
|
106
176
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
107
177
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
108
178
|
<plist version="1.0">
|
|
@@ -112,13 +182,11 @@ function buildPlist(nodePath, projectDir, logsDir, runMode) {
|
|
|
112
182
|
|
|
113
183
|
<key>ProgramArguments</key>
|
|
114
184
|
<array>
|
|
115
|
-
|
|
116
|
-
<string>${projectDir}/dist/index.js</string>
|
|
117
|
-
<string>${runMode}</string>
|
|
185
|
+
${argsXml}
|
|
118
186
|
</array>
|
|
119
187
|
|
|
120
188
|
<key>WorkingDirectory</key>
|
|
121
|
-
<string>${
|
|
189
|
+
<string>${workingDir}</string>
|
|
122
190
|
|
|
123
191
|
<key>EnvironmentVariables</key>
|
|
124
192
|
<dict>
|
|
@@ -140,7 +208,7 @@ function buildPlist(nodePath, projectDir, logsDir, runMode) {
|
|
|
140
208
|
</dict>
|
|
141
209
|
</plist>`;
|
|
142
210
|
}
|
|
143
|
-
function buildSystemdUnit(
|
|
211
|
+
function buildSystemdUnit(execStart, workingDir, logsDir) {
|
|
144
212
|
return `[Unit]
|
|
145
213
|
Description=Deskmate - Local Machine Assistant
|
|
146
214
|
After=network-online.target
|
|
@@ -148,8 +216,8 @@ Wants=network-online.target
|
|
|
148
216
|
|
|
149
217
|
[Service]
|
|
150
218
|
Type=simple
|
|
151
|
-
ExecStart=${
|
|
152
|
-
WorkingDirectory=${
|
|
219
|
+
ExecStart=${execStart}
|
|
220
|
+
WorkingDirectory=${workingDir}
|
|
153
221
|
Environment=PATH=/usr/local/bin:/usr/bin:/bin:${os.homedir()}/.local/bin
|
|
154
222
|
Restart=always
|
|
155
223
|
RestartSec=5
|
|
@@ -162,8 +230,7 @@ StandardError=append:${logsDir}/stderr.log
|
|
|
162
230
|
[Install]
|
|
163
231
|
WantedBy=default.target`;
|
|
164
232
|
}
|
|
165
|
-
async function installMacosService(
|
|
166
|
-
const nodePath = process.execPath;
|
|
233
|
+
async function installMacosService(execStart, workingDir, logsDir) {
|
|
167
234
|
const dest = plistPath();
|
|
168
235
|
// Unload existing
|
|
169
236
|
try {
|
|
@@ -177,13 +244,12 @@ async function installMacosService(projectDir, logsDir, runMode) {
|
|
|
177
244
|
}
|
|
178
245
|
await fs.mkdir(path.dirname(dest), { recursive: true });
|
|
179
246
|
await fs.mkdir(logsDir, { recursive: true });
|
|
180
|
-
await fs.writeFile(dest, buildPlist(
|
|
247
|
+
await fs.writeFile(dest, buildPlist(execStart, workingDir, logsDir), "utf-8");
|
|
181
248
|
(0, child_process_1.execSync)(`launchctl load "${dest}"`);
|
|
182
249
|
console.log("\n Service installed and started via launchd.");
|
|
183
250
|
console.log(` Plist: ${dest}`);
|
|
184
251
|
}
|
|
185
|
-
async function installLinuxService(
|
|
186
|
-
const nodePath = process.execPath;
|
|
252
|
+
async function installLinuxService(execStart, workingDir, logsDir) {
|
|
187
253
|
const dest = systemdPath();
|
|
188
254
|
// Stop existing
|
|
189
255
|
try {
|
|
@@ -194,7 +260,7 @@ async function installLinuxService(projectDir, logsDir, runMode) {
|
|
|
194
260
|
}
|
|
195
261
|
await fs.mkdir(systemdDir(), { recursive: true });
|
|
196
262
|
await fs.mkdir(logsDir, { recursive: true });
|
|
197
|
-
await fs.writeFile(dest, buildSystemdUnit(
|
|
263
|
+
await fs.writeFile(dest, buildSystemdUnit(execStart, workingDir, logsDir), "utf-8");
|
|
198
264
|
(0, child_process_1.execSync)("systemctl --user daemon-reload");
|
|
199
265
|
(0, child_process_1.execSync)("systemctl --user enable deskmate.service");
|
|
200
266
|
(0, child_process_1.execSync)("systemctl --user start deskmate.service");
|
|
@@ -286,33 +352,89 @@ async function runInitWizard() {
|
|
|
286
352
|
console.log("Install it from: https://docs.anthropic.com/en/docs/claude-code");
|
|
287
353
|
console.log("");
|
|
288
354
|
}
|
|
355
|
+
// Detect install type and resolve paths
|
|
356
|
+
const paths = resolveInstallPaths();
|
|
357
|
+
if (paths.isGlobalInstall) {
|
|
358
|
+
console.log(`Install type: npm global`);
|
|
359
|
+
console.log(`Config directory: ${paths.configDir}\n`);
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
console.log(`Install type: source`);
|
|
363
|
+
console.log(`Project directory: ${paths.packageDir}\n`);
|
|
364
|
+
}
|
|
365
|
+
// Ensure config directory exists
|
|
366
|
+
await fs.mkdir(paths.configDir, { recursive: true });
|
|
289
367
|
// ----- Step 1: .env wizard -----
|
|
368
|
+
const envPath = path.join(paths.configDir, ".env");
|
|
369
|
+
const existing = await loadExistingEnv(envPath);
|
|
370
|
+
const hasExisting = Object.keys(existing).length > 0;
|
|
371
|
+
if (hasExisting) {
|
|
372
|
+
console.log("Found existing .env — values shown below. Press Enter to keep current value.\n");
|
|
373
|
+
}
|
|
290
374
|
const env = {};
|
|
291
|
-
env.AGENT_PROVIDER = "claude-code";
|
|
292
|
-
|
|
293
|
-
if (
|
|
294
|
-
|
|
375
|
+
env.AGENT_PROVIDER = existing.AGENT_PROVIDER || "claude-code";
|
|
376
|
+
// Anthropic API Key
|
|
377
|
+
if (existing.ANTHROPIC_API_KEY) {
|
|
378
|
+
const apiKey = await ask(rl, `Anthropic API Key [${mask(existing.ANTHROPIC_API_KEY)}]: `);
|
|
379
|
+
env.ANTHROPIC_API_KEY = apiKey || existing.ANTHROPIC_API_KEY;
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
const apiKey = await ask(rl, "Anthropic API Key (for Claude Code): ");
|
|
383
|
+
if (apiKey)
|
|
384
|
+
env.ANTHROPIC_API_KEY = apiKey;
|
|
385
|
+
}
|
|
386
|
+
// Telegram
|
|
295
387
|
console.log("\n--- Telegram Configuration ---\n");
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
388
|
+
if (!existing.TELEGRAM_BOT_TOKEN) {
|
|
389
|
+
console.log(" Bot token → message @BotFather on Telegram, send /newbot");
|
|
390
|
+
console.log(" User ID → message @userinfobot on Telegram, copy the number");
|
|
391
|
+
console.log("");
|
|
392
|
+
}
|
|
393
|
+
if (existing.TELEGRAM_BOT_TOKEN) {
|
|
394
|
+
const token = await ask(rl, `Telegram Bot Token [${mask(existing.TELEGRAM_BOT_TOKEN)}]: `);
|
|
395
|
+
env.TELEGRAM_BOT_TOKEN = token || existing.TELEGRAM_BOT_TOKEN;
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
const token = await ask(rl, "Telegram Bot Token (from @BotFather): ");
|
|
399
|
+
if (token)
|
|
400
|
+
env.TELEGRAM_BOT_TOKEN = token;
|
|
401
|
+
}
|
|
402
|
+
// User ID / Allowed Users
|
|
403
|
+
const existingUsers = existing.ALLOWED_USERS;
|
|
404
|
+
const existingUserId = existing.ALLOWED_USER_ID;
|
|
405
|
+
if (existingUsers) {
|
|
406
|
+
const users = await ask(rl, `Allowed users [${existingUsers}]: `);
|
|
407
|
+
env.ALLOWED_USERS = users || existingUsers;
|
|
408
|
+
}
|
|
409
|
+
else if (existingUserId) {
|
|
410
|
+
const userId = await ask(rl, `Telegram User ID [${existingUserId}]: `);
|
|
411
|
+
const id = userId || existingUserId;
|
|
412
|
+
env.ALLOWED_USER_ID = id;
|
|
413
|
+
env.ALLOWED_USERS = `telegram:${id}`;
|
|
306
414
|
}
|
|
415
|
+
else {
|
|
416
|
+
const userId = await ask(rl, "Your Telegram User ID (from @userinfobot): ");
|
|
417
|
+
if (userId) {
|
|
418
|
+
env.ALLOWED_USER_ID = userId;
|
|
419
|
+
env.ALLOWED_USERS = `telegram:${userId}`;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
// General config
|
|
307
423
|
console.log("\n--- General Configuration ---\n");
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
424
|
+
const defaultWorkingDir = existing.WORKING_DIR || os.homedir();
|
|
425
|
+
const workingDir = await ask(rl, `Working directory [${defaultWorkingDir}]: `);
|
|
426
|
+
env.WORKING_DIR = workingDir || defaultWorkingDir;
|
|
427
|
+
const defaultBotName = existing.BOT_NAME || "Deskmate";
|
|
428
|
+
const botName = await ask(rl, `Bot name [${defaultBotName}]: `);
|
|
429
|
+
env.BOT_NAME = botName || defaultBotName;
|
|
430
|
+
// Carry over other existing values that we don't prompt for
|
|
431
|
+
if (existing.LOG_LEVEL)
|
|
432
|
+
env.LOG_LEVEL = existing.LOG_LEVEL;
|
|
433
|
+
if (existing.REQUIRE_APPROVAL_FOR_ALL)
|
|
434
|
+
env.REQUIRE_APPROVAL_FOR_ALL = existing.REQUIRE_APPROVAL_FOR_ALL;
|
|
435
|
+
if (existing.ALLOWED_FOLDERS)
|
|
436
|
+
env.ALLOWED_FOLDERS = existing.ALLOWED_FOLDERS;
|
|
314
437
|
// ----- Step 2: Write .env -----
|
|
315
|
-
const envPath = path.join(process.cwd(), ".env");
|
|
316
438
|
let envContent = "# Deskmate Configuration (generated by deskmate init)\n\n";
|
|
317
439
|
for (const [key, value] of Object.entries(env)) {
|
|
318
440
|
envContent += `${key}=${value}\n`;
|
|
@@ -322,20 +444,18 @@ async function runInitWizard() {
|
|
|
322
444
|
if (!env.REQUIRE_APPROVAL_FOR_ALL)
|
|
323
445
|
envContent += "REQUIRE_APPROVAL_FOR_ALL=false\n";
|
|
324
446
|
try {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
console.log(`Configuration saved to ${newPath}`);
|
|
338
|
-
console.log("Review and rename it to .env when ready.");
|
|
447
|
+
if (hasExisting) {
|
|
448
|
+
const overwrite = await askYesNo(rl, "\nOverwrite existing .env with new values?");
|
|
449
|
+
if (overwrite) {
|
|
450
|
+
await fs.writeFile(envPath, envContent, "utf-8");
|
|
451
|
+
console.log(`Configuration saved to ${envPath}`);
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
const newPath = envPath + ".new";
|
|
455
|
+
await fs.writeFile(newPath, envContent, "utf-8");
|
|
456
|
+
console.log(`Configuration saved to ${newPath}`);
|
|
457
|
+
console.log("Review and rename it to .env when ready.");
|
|
458
|
+
}
|
|
339
459
|
}
|
|
340
460
|
else {
|
|
341
461
|
await fs.writeFile(envPath, envContent, "utf-8");
|
|
@@ -352,16 +472,15 @@ async function runInitWizard() {
|
|
|
352
472
|
console.log("");
|
|
353
473
|
const installService = await askYesNo(rl, "Install as background service?");
|
|
354
474
|
if (installService) {
|
|
355
|
-
|
|
356
|
-
const projectDir = path.resolve(__dirname, "..");
|
|
357
|
-
const logsDir = path.join(projectDir, "logs");
|
|
475
|
+
const logsDir = path.join(paths.configDir, "logs");
|
|
358
476
|
const runMode = "gateway";
|
|
477
|
+
const execStart = paths.execStart(runMode);
|
|
359
478
|
try {
|
|
360
479
|
if (platform === "macos") {
|
|
361
|
-
await installMacosService(
|
|
480
|
+
await installMacosService(execStart, paths.configDir, logsDir);
|
|
362
481
|
}
|
|
363
482
|
else {
|
|
364
|
-
await installLinuxService(
|
|
483
|
+
await installLinuxService(execStart, paths.configDir, logsDir);
|
|
365
484
|
}
|
|
366
485
|
}
|
|
367
486
|
catch (err) {
|
|
@@ -383,5 +502,9 @@ async function runInitWizard() {
|
|
|
383
502
|
}
|
|
384
503
|
rl.close();
|
|
385
504
|
console.log("\nSetup complete! Your bot is ready.");
|
|
505
|
+
if (paths.isGlobalInstall) {
|
|
506
|
+
console.log(`\nRun "deskmate" to start, or the background service is already running.`);
|
|
507
|
+
console.log(`Config: ${paths.configDir}/.env`);
|
|
508
|
+
}
|
|
386
509
|
console.log("");
|
|
387
510
|
}
|