@questionbase/deskfree 0.5.0 → 0.5.1

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/dist/bin.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { spawn, execSync, execFileSync, execFile } from 'child_process';
4
- import { mkdirSync, writeFileSync, chmodSync, existsSync, unlinkSync, appendFileSync, readdirSync, readFileSync, statSync, createWriteStream } from 'fs';
5
3
  import { homedir } from 'os';
6
4
  import { dirname, join, extname } from 'path';
5
+ import { spawn, execSync, execFileSync, execFile } from 'child_process';
6
+ import { mkdirSync, writeFileSync, chmodSync, existsSync, unlinkSync, appendFileSync, readdirSync, readFileSync, statSync, createWriteStream } from 'fs';
7
7
  import { createRequire as createRequire$1 } from 'module';
8
8
  import { query, createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';
9
9
  import { z } from 'zod';
@@ -33,8 +33,8 @@ var __commonJS = (cb, mod) => function __require3() {
33
33
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
34
34
  };
35
35
  var __export = (target, all) => {
36
- for (var name in all)
37
- __defProp(target, name, { get: all[name], enumerable: true });
36
+ for (var name2 in all)
37
+ __defProp(target, name2, { get: all[name2], enumerable: true });
38
38
  };
39
39
  var __copyProps = (to, from, except, desc) => {
40
40
  if (from && typeof from === "object" || typeof from === "function") {
@@ -52,25 +52,66 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
52
52
  __defProp(target, "default", { value: mod, enumerable: true }) ,
53
53
  mod
54
54
  ));
55
-
56
- // src/cli/install.ts
57
- var install_exports = {};
58
- __export(install_exports, {
59
- install: () => install
60
- });
61
- function getMacPaths() {
55
+ function getPlistLabel(name2) {
56
+ return `com.deskfree.agent.${name2}`;
57
+ }
58
+ function getServiceName(name2) {
59
+ return `deskfree-agent-${name2}`;
60
+ }
61
+ function getMacPaths(name2) {
62
62
  const home = homedir();
63
- const deskfreeDir = join(home, ".deskfree");
63
+ const deskfreeDir = join(home, ".deskfree", name2);
64
+ const plistLabel = getPlistLabel(name2);
64
65
  return {
65
66
  deskfreeDir,
66
67
  envFile: join(deskfreeDir, ".env"),
67
68
  launcher: join(deskfreeDir, "launcher.sh"),
68
69
  logDir: join(deskfreeDir, "logs"),
69
- plist: join(home, "Library", "LaunchAgents", `${PLIST_LABEL}.plist`)
70
+ plist: join(home, "Library", "LaunchAgents", `${plistLabel}.plist`)
71
+ };
72
+ }
73
+ function getLinuxPaths(name2) {
74
+ const serviceName = getServiceName(name2);
75
+ return {
76
+ serviceName,
77
+ serviceFile: `/etc/systemd/system/${serviceName}.service`,
78
+ envFile: `/etc/${serviceName}.env`,
79
+ stateDir: `/var/lib/${serviceName}`,
80
+ logDir: `/var/log/${serviceName}`
70
81
  };
71
82
  }
72
- function installMac(token) {
73
- const paths = getMacPaths();
83
+ function parseName(args) {
84
+ const idx = args.indexOf("--name");
85
+ if (idx === -1) return [DEFAULT_NAME, args];
86
+ const name2 = args[idx + 1];
87
+ if (!name2 || name2.startsWith("-")) {
88
+ console.error("Error: --name requires a value (e.g. --name kasper)");
89
+ process.exit(1);
90
+ }
91
+ if (!/^[a-z0-9-]+$/i.test(name2)) {
92
+ console.error(
93
+ "Error: --name must contain only letters, numbers, and hyphens"
94
+ );
95
+ process.exit(1);
96
+ }
97
+ const remaining = [...args.slice(0, idx), ...args.slice(idx + 2)];
98
+ return [name2, remaining];
99
+ }
100
+ var DEFAULT_NAME;
101
+ var init_paths = __esm({
102
+ "src/cli/paths.ts"() {
103
+ DEFAULT_NAME = "main";
104
+ }
105
+ });
106
+
107
+ // src/cli/install.ts
108
+ var install_exports = {};
109
+ __export(install_exports, {
110
+ install: () => install
111
+ });
112
+ function installMac(token, name2) {
113
+ const paths = getMacPaths(name2);
114
+ const plistLabel = getPlistLabel(name2);
74
115
  let nodeBinDir;
75
116
  try {
76
117
  const nodePath = execSync("which node", { encoding: "utf8" }).trim();
@@ -82,8 +123,13 @@ function installMac(token) {
82
123
  mkdirSync(paths.deskfreeDir, { recursive: true });
83
124
  mkdirSync(paths.logDir, { recursive: true });
84
125
  mkdirSync(dirname(paths.plist), { recursive: true });
85
- writeFileSync(paths.envFile, `DESKFREE_LAUNCH=${token}
86
- `, { mode: 384 });
126
+ writeFileSync(
127
+ paths.envFile,
128
+ `DESKFREE_LAUNCH=${token}
129
+ DESKFREE_INSTANCE_NAME=${name2}
130
+ `,
131
+ { mode: 384 }
132
+ );
87
133
  chmodSync(paths.envFile, 384);
88
134
  console.log(`Wrote ${paths.envFile}`);
89
135
  const launcher = `#!/bin/bash
@@ -109,7 +155,7 @@ exec deskfree-agent start
109
155
  <plist version="1.0">
110
156
  <dict>
111
157
  <key>Label</key>
112
- <string>${PLIST_LABEL}</string>
158
+ <string>${plistLabel}</string>
113
159
  <key>ProgramArguments</key>
114
160
  <array>
115
161
  <string>${paths.launcher}</string>
@@ -137,15 +183,18 @@ exec deskfree-agent start
137
183
  }
138
184
  execSync(`launchctl bootstrap gui/$(id -u) ${paths.plist}`);
139
185
  console.log(`
140
- Service ${PLIST_LABEL} installed and started.`);
141
- console.log(`Check status: launchctl print gui/$(id -u)/${PLIST_LABEL}`);
186
+ Service ${plistLabel} installed and started.`);
187
+ console.log(`Check status: launchctl print gui/$(id -u)/${plistLabel}`);
142
188
  console.log(`Logs: tail -f ${join(paths.logDir, "stdout.log")}`);
143
189
  }
144
- function installLinux(token) {
190
+ function installLinux(token, name2) {
145
191
  if (process.getuid?.() !== 0) {
146
192
  console.error("Error: install must be run as root (use sudo)");
147
193
  process.exit(1);
148
194
  }
195
+ const paths = getLinuxPaths(name2);
196
+ const serviceName = getServiceName(name2);
197
+ const systemUser = serviceName;
149
198
  let npmPath;
150
199
  let nodeBinDir;
151
200
  try {
@@ -156,80 +205,78 @@ function installLinux(token) {
156
205
  process.exit(1);
157
206
  }
158
207
  try {
159
- execSync("id deskfree-agent", { stdio: "ignore" });
208
+ execSync(`id ${systemUser}`, { stdio: "ignore" });
160
209
  } catch {
161
210
  execSync(
162
- "useradd --system --no-create-home --shell /usr/sbin/nologin deskfree-agent"
211
+ `useradd --system --no-create-home --shell /usr/sbin/nologin ${systemUser}`
163
212
  );
164
- console.log("Created system user: deskfree-agent");
213
+ console.log(`Created system user: ${systemUser}`);
165
214
  }
166
- mkdirSync(LINUX_STATE_DIR, { recursive: true });
167
- mkdirSync(LINUX_LOG_DIR, { recursive: true });
215
+ mkdirSync(paths.stateDir, { recursive: true });
216
+ mkdirSync(paths.logDir, { recursive: true });
168
217
  execSync(
169
- `chown deskfree-agent:deskfree-agent ${LINUX_STATE_DIR} ${LINUX_LOG_DIR}`
218
+ `chown ${systemUser}:${systemUser} ${paths.stateDir} ${paths.logDir}`
170
219
  );
171
- console.log(`Created ${LINUX_STATE_DIR} and ${LINUX_LOG_DIR}`);
172
- writeFileSync(SYSTEMD_ENV_FILE, `DESKFREE_LAUNCH=${token}
173
- `, {
174
- mode: 384
175
- });
176
- chmodSync(SYSTEMD_ENV_FILE, 384);
177
- console.log(`Wrote ${SYSTEMD_ENV_FILE}`);
220
+ console.log(`Created ${paths.stateDir} and ${paths.logDir}`);
221
+ writeFileSync(
222
+ paths.envFile,
223
+ `DESKFREE_LAUNCH=${token}
224
+ DESKFREE_INSTANCE_NAME=${name2}
225
+ `,
226
+ { mode: 384 }
227
+ );
228
+ chmodSync(paths.envFile, 384);
229
+ console.log(`Wrote ${paths.envFile}`);
178
230
  const unit = `[Unit]
179
- Description=DeskFree Agent
231
+ Description=DeskFree Agent (${name2})
180
232
  After=network-online.target
181
233
  Wants=network-online.target
182
234
 
183
235
  [Service]
184
236
  Type=simple
185
- User=deskfree-agent
186
- Group=deskfree-agent
187
- WorkingDirectory=${LINUX_STATE_DIR}
237
+ User=${systemUser}
238
+ Group=${systemUser}
239
+ WorkingDirectory=${paths.stateDir}
188
240
  Environment=PATH=${nodeBinDir}:/usr/local/bin:/usr/bin:/bin
189
241
  ExecStartPre=+${npmPath} install -g ${PACKAGE}
190
242
  ExecStart=${nodeBinDir}/deskfree-agent start
191
- EnvironmentFile=${SYSTEMD_ENV_FILE}
243
+ EnvironmentFile=${paths.envFile}
192
244
  Environment=NODE_ENV=production
193
- Environment=DESKFREE_STATE_DIR=${LINUX_STATE_DIR}
194
- Environment=DESKFREE_TOOLS_DIR=${LINUX_STATE_DIR}/tools
245
+ Environment=DESKFREE_STATE_DIR=${paths.stateDir}
246
+ Environment=DESKFREE_TOOLS_DIR=${paths.stateDir}/tools
195
247
  Restart=always
196
248
  RestartSec=10
197
- StandardOutput=append:${LINUX_LOG_DIR}/stdout.log
198
- StandardError=append:${LINUX_LOG_DIR}/stderr.log
249
+ StandardOutput=append:${paths.logDir}/stdout.log
250
+ StandardError=append:${paths.logDir}/stderr.log
199
251
 
200
252
  [Install]
201
253
  WantedBy=multi-user.target
202
254
  `;
203
- writeFileSync(SYSTEMD_SERVICE_FILE, unit);
204
- console.log(`Wrote ${SYSTEMD_SERVICE_FILE}`);
255
+ writeFileSync(paths.serviceFile, unit);
256
+ console.log(`Wrote ${paths.serviceFile}`);
205
257
  execSync("systemctl daemon-reload");
206
- execSync(`systemctl enable ${SERVICE_NAME}`);
207
- execSync(`systemctl start ${SERVICE_NAME}`);
258
+ execSync(`systemctl enable ${serviceName}`);
259
+ execSync(`systemctl start ${serviceName}`);
208
260
  console.log(`
209
- Service ${SERVICE_NAME} installed and started.`);
210
- console.log(`Check status: systemctl status ${SERVICE_NAME}`);
211
- console.log(`Logs: tail -f ${LINUX_LOG_DIR}/stdout.log`);
261
+ Service ${serviceName} installed and started.`);
262
+ console.log(`Check status: systemctl status ${serviceName}`);
263
+ console.log(`Logs: tail -f ${paths.logDir}/stdout.log`);
212
264
  }
213
- function install(token) {
265
+ function install(token, name2) {
214
266
  if (process.platform === "darwin") {
215
- installMac(token);
267
+ installMac(token, name2);
216
268
  } else if (process.platform === "linux") {
217
- installLinux(token);
269
+ installLinux(token, name2);
218
270
  } else {
219
271
  console.error(`Unsupported platform: ${process.platform}`);
220
272
  process.exit(1);
221
273
  }
222
274
  }
223
- var SERVICE_NAME, PACKAGE, PLIST_LABEL, SYSTEMD_SERVICE_FILE, SYSTEMD_ENV_FILE, LINUX_STATE_DIR, LINUX_LOG_DIR;
275
+ var PACKAGE;
224
276
  var init_install = __esm({
225
277
  "src/cli/install.ts"() {
226
- SERVICE_NAME = "deskfree-agent";
278
+ init_paths();
227
279
  PACKAGE = "@questionbase/deskfree@latest";
228
- PLIST_LABEL = "com.deskfree.agent";
229
- SYSTEMD_SERVICE_FILE = `/etc/systemd/system/${SERVICE_NAME}.service`;
230
- SYSTEMD_ENV_FILE = `/etc/${SERVICE_NAME}.env`;
231
- LINUX_STATE_DIR = "/var/lib/deskfree-agent";
232
- LINUX_LOG_DIR = "/var/log/deskfree-agent";
233
280
  }
234
281
  });
235
282
 
@@ -238,68 +285,65 @@ var uninstall_exports = {};
238
285
  __export(uninstall_exports, {
239
286
  uninstall: () => uninstall
240
287
  });
241
- function uninstallMac() {
242
- const home = homedir();
243
- const plist = join(home, "Library", "LaunchAgents", `${PLIST_LABEL2}.plist`);
244
- const deskfreeDir = join(home, ".deskfree");
245
- const envFile = join(deskfreeDir, ".env");
246
- const launcher = join(deskfreeDir, "launcher.sh");
288
+ function uninstallMac(name2) {
289
+ const paths = getMacPaths(name2);
290
+ const plistLabel = getPlistLabel(name2);
247
291
  try {
248
- execSync(`launchctl bootout gui/$(id -u) ${plist}`, { stdio: "ignore" });
292
+ execSync(`launchctl bootout gui/$(id -u) ${paths.plist}`, {
293
+ stdio: "ignore"
294
+ });
249
295
  } catch {
250
296
  }
251
- for (const file of [plist, envFile, launcher]) {
297
+ for (const file of [paths.plist, paths.envFile, paths.launcher]) {
252
298
  if (existsSync(file)) {
253
299
  unlinkSync(file);
254
300
  console.log(`Removed ${file}`);
255
301
  }
256
302
  }
257
- console.log(`Service ${PLIST_LABEL2} uninstalled.`);
303
+ console.log(`Service ${plistLabel} uninstalled.`);
258
304
  console.log(
259
- `Note: logs and state in ${deskfreeDir} were preserved. Remove manually if desired.`
305
+ `Note: logs and state in ${paths.deskfreeDir} were preserved. Remove manually if desired.`
260
306
  );
261
307
  }
262
- function uninstallLinux() {
308
+ function uninstallLinux(name2) {
263
309
  if (process.getuid?.() !== 0) {
264
310
  console.error("Error: uninstall must be run as root (use sudo)");
265
311
  process.exit(1);
266
312
  }
313
+ const paths = getLinuxPaths(name2);
314
+ const serviceName = getServiceName(name2);
267
315
  try {
268
- execSync(`systemctl stop ${SERVICE_NAME2}`, { stdio: "ignore" });
316
+ execSync(`systemctl stop ${serviceName}`, { stdio: "ignore" });
269
317
  } catch {
270
318
  }
271
319
  try {
272
- execSync(`systemctl disable ${SERVICE_NAME2}`, { stdio: "ignore" });
320
+ execSync(`systemctl disable ${serviceName}`, { stdio: "ignore" });
273
321
  } catch {
274
322
  }
275
- if (existsSync(SYSTEMD_SERVICE_FILE2)) {
276
- unlinkSync(SYSTEMD_SERVICE_FILE2);
277
- console.log(`Removed ${SYSTEMD_SERVICE_FILE2}`);
323
+ if (existsSync(paths.serviceFile)) {
324
+ unlinkSync(paths.serviceFile);
325
+ console.log(`Removed ${paths.serviceFile}`);
278
326
  }
279
- if (existsSync(SYSTEMD_ENV_FILE2)) {
280
- unlinkSync(SYSTEMD_ENV_FILE2);
281
- console.log(`Removed ${SYSTEMD_ENV_FILE2}`);
327
+ if (existsSync(paths.envFile)) {
328
+ unlinkSync(paths.envFile);
329
+ console.log(`Removed ${paths.envFile}`);
282
330
  }
283
331
  execSync("systemctl daemon-reload");
284
- console.log(`Service ${SERVICE_NAME2} uninstalled.`);
332
+ console.log(`Service ${serviceName} uninstalled.`);
285
333
  }
286
- function uninstall() {
334
+ function uninstall(name2) {
287
335
  if (process.platform === "darwin") {
288
- uninstallMac();
336
+ uninstallMac(name2);
289
337
  } else if (process.platform === "linux") {
290
- uninstallLinux();
338
+ uninstallLinux(name2);
291
339
  } else {
292
340
  console.error(`Unsupported platform: ${process.platform}`);
293
341
  process.exit(1);
294
342
  }
295
343
  }
296
- var SERVICE_NAME2, PLIST_LABEL2, SYSTEMD_SERVICE_FILE2, SYSTEMD_ENV_FILE2;
297
344
  var init_uninstall = __esm({
298
345
  "src/cli/uninstall.ts"() {
299
- SERVICE_NAME2 = "deskfree-agent";
300
- PLIST_LABEL2 = "com.deskfree.agent";
301
- SYSTEMD_SERVICE_FILE2 = `/etc/systemd/system/${SERVICE_NAME2}.service`;
302
- SYSTEMD_ENV_FILE2 = `/etc/${SERVICE_NAME2}.env`;
346
+ init_paths();
303
347
  }
304
348
  });
305
349
 
@@ -308,44 +352,46 @@ var status_exports = {};
308
352
  __export(status_exports, {
309
353
  status: () => status
310
354
  });
311
- function statusMac() {
312
- const home = homedir();
313
- const plist = join(home, "Library", "LaunchAgents", `${PLIST_LABEL3}.plist`);
314
- if (!existsSync(plist)) {
315
- console.log("DeskFree Agent is not installed.");
316
- console.log(`Run: curl -fsSL https://my.deskfree.ai/install.sh | bash`);
355
+ function statusMac(name2) {
356
+ const paths = getMacPaths(name2);
357
+ const plistLabel = getPlistLabel(name2);
358
+ if (!existsSync(paths.plist)) {
359
+ console.log(`DeskFree Agent "${name2}" is not installed.`);
360
+ console.log(`Run: deskfree-agent install <token> --name ${name2}`);
317
361
  return;
318
362
  }
319
- console.log("DeskFree Agent (macOS LaunchAgent)\n");
363
+ console.log(`DeskFree Agent "${name2}" (macOS LaunchAgent)
364
+ `);
320
365
  try {
321
- const output = execSync(
322
- `launchctl print gui/$(id -u)/${PLIST_LABEL3} 2>&1`,
323
- { encoding: "utf8" }
324
- );
366
+ const output = execSync(`launchctl print gui/$(id -u)/${plistLabel} 2>&1`, {
367
+ encoding: "utf8"
368
+ });
325
369
  const pidMatch = output.match(/pid\s*=\s*(\d+)/);
326
370
  const stateMatch = output.match(/state\s*=\s*(\w+)/);
327
371
  if (pidMatch) console.log(` PID: ${pidMatch[1]}`);
328
372
  if (stateMatch) console.log(` State: ${stateMatch[1]}`);
329
- const logDir = join(home, ".deskfree", "logs");
330
- console.log(` Logs: ${logDir}/stdout.log`);
331
- console.log(` Plist: ${plist}`);
373
+ console.log(` Logs: ${paths.logDir}/stdout.log`);
374
+ console.log(` Plist: ${paths.plist}`);
332
375
  } catch {
333
376
  console.log(" Status: not running (service may be unloaded)");
334
377
  }
335
378
  }
336
- function statusLinux() {
337
- const serviceFile = `/etc/systemd/system/${SYSTEMD_SERVICE_NAME}.service`;
338
- if (!existsSync(serviceFile)) {
339
- console.log("DeskFree Agent is not installed.");
340
- console.log(`Run: sudo npx @questionbase/deskfree@latest install <token>`);
379
+ function statusLinux(name2) {
380
+ const paths = getLinuxPaths(name2);
381
+ const serviceName = getServiceName(name2);
382
+ if (!existsSync(paths.serviceFile)) {
383
+ console.log(`DeskFree Agent "${name2}" is not installed.`);
384
+ console.log(
385
+ `Run: sudo npx @questionbase/deskfree@latest install <token> --name ${name2}`
386
+ );
341
387
  return;
342
388
  }
343
- console.log("DeskFree Agent (systemd service)\n");
389
+ console.log(`DeskFree Agent "${name2}" (systemd service)
390
+ `);
344
391
  try {
345
- const output = execSync(
346
- `systemctl status ${SYSTEMD_SERVICE_NAME} --no-pager 2>&1`,
347
- { encoding: "utf8" }
348
- );
392
+ const output = execSync(`systemctl status ${serviceName} --no-pager 2>&1`, {
393
+ encoding: "utf8"
394
+ });
349
395
  console.log(output);
350
396
  } catch (err) {
351
397
  if (err && typeof err === "object" && "stdout" in err) {
@@ -354,23 +400,21 @@ function statusLinux() {
354
400
  console.log(" Status: unknown (could not query systemd)");
355
401
  }
356
402
  }
357
- console.log(` Logs: /var/log/deskfree-agent/stdout.log`);
403
+ console.log(` Logs: ${paths.logDir}/stdout.log`);
358
404
  }
359
- function status() {
405
+ function status(name2) {
360
406
  if (process.platform === "darwin") {
361
- statusMac();
407
+ statusMac(name2);
362
408
  } else if (process.platform === "linux") {
363
- statusLinux();
409
+ statusLinux(name2);
364
410
  } else {
365
411
  console.error(`Unsupported platform: ${process.platform}`);
366
412
  process.exit(1);
367
413
  }
368
414
  }
369
- var PLIST_LABEL3, SYSTEMD_SERVICE_NAME;
370
415
  var init_status = __esm({
371
416
  "src/cli/status.ts"() {
372
- PLIST_LABEL3 = "com.deskfree.agent";
373
- SYSTEMD_SERVICE_NAME = "deskfree-agent";
417
+ init_paths();
374
418
  }
375
419
  });
376
420
 
@@ -379,56 +423,50 @@ var restart_exports = {};
379
423
  __export(restart_exports, {
380
424
  restart: () => restart
381
425
  });
382
- function restartMac() {
383
- const plist = join(
384
- homedir(),
385
- "Library",
386
- "LaunchAgents",
387
- `${PLIST_LABEL4}.plist`
388
- );
389
- if (!existsSync(plist)) {
390
- console.error("DeskFree Agent is not installed.");
391
- console.error("Run: curl -fsSL https://my.deskfree.ai/install.sh | bash");
426
+ function restartMac(name2) {
427
+ const paths = getMacPaths(name2);
428
+ if (!existsSync(paths.plist)) {
429
+ console.error(`DeskFree Agent "${name2}" is not installed.`);
430
+ console.error(`Run: deskfree-agent install <token> --name ${name2}`);
392
431
  process.exit(1);
393
432
  }
394
433
  try {
395
- execSync(`launchctl bootout gui/$(id -u) ${plist}`, { stdio: "ignore" });
434
+ execSync(`launchctl bootout gui/$(id -u) ${paths.plist}`, {
435
+ stdio: "ignore"
436
+ });
396
437
  } catch {
397
438
  }
398
- execSync(`launchctl bootstrap gui/$(id -u) ${plist}`);
399
- console.log("DeskFree Agent restarted.");
400
- console.log(
401
- `Logs: tail -f ${join(homedir(), ".deskfree", "logs", "stdout.log")}`
402
- );
403
- }
404
- function restartLinux() {
405
- const serviceFile = `/etc/systemd/system/${SYSTEMD_SERVICE_NAME2}.service`;
406
- if (!existsSync(serviceFile)) {
407
- console.error("DeskFree Agent is not installed.");
439
+ execSync(`launchctl bootstrap gui/$(id -u) ${paths.plist}`);
440
+ console.log(`DeskFree Agent "${name2}" restarted.`);
441
+ console.log(`Logs: tail -f ${paths.logDir}/stdout.log`);
442
+ }
443
+ function restartLinux(name2) {
444
+ const paths = getLinuxPaths(name2);
445
+ const serviceName = getServiceName(name2);
446
+ if (!existsSync(paths.serviceFile)) {
447
+ console.error(`DeskFree Agent "${name2}" is not installed.`);
408
448
  console.error(
409
- "Run: sudo npx @questionbase/deskfree@latest install <token>"
449
+ `Run: sudo npx @questionbase/deskfree@latest install <token> --name ${name2}`
410
450
  );
411
451
  process.exit(1);
412
452
  }
413
- execSync(`systemctl restart ${SYSTEMD_SERVICE_NAME2}`, { stdio: "inherit" });
414
- console.log("DeskFree Agent restarted.");
415
- console.log(`Logs: tail -f /var/log/deskfree-agent/stdout.log`);
453
+ execSync(`systemctl restart ${serviceName}`, { stdio: "inherit" });
454
+ console.log(`DeskFree Agent "${name2}" restarted.`);
455
+ console.log(`Logs: tail -f ${paths.logDir}/stdout.log`);
416
456
  }
417
- function restart() {
457
+ function restart(name2) {
418
458
  if (process.platform === "darwin") {
419
- restartMac();
459
+ restartMac(name2);
420
460
  } else if (process.platform === "linux") {
421
- restartLinux();
461
+ restartLinux(name2);
422
462
  } else {
423
463
  console.error(`Unsupported platform: ${process.platform}`);
424
464
  process.exit(1);
425
465
  }
426
466
  }
427
- var PLIST_LABEL4, SYSTEMD_SERVICE_NAME2;
428
467
  var init_restart = __esm({
429
468
  "src/cli/restart.ts"() {
430
- PLIST_LABEL4 = "com.deskfree.agent";
431
- SYSTEMD_SERVICE_NAME2 = "deskfree-agent";
469
+ init_paths();
432
470
  }
433
471
  });
434
472
 
@@ -437,28 +475,30 @@ var logs_exports = {};
437
475
  __export(logs_exports, {
438
476
  logs: () => logs
439
477
  });
440
- function getLogPath() {
478
+ function getLogPath(name2) {
441
479
  if (process.platform === "darwin") {
442
- const logFile = join(homedir(), ".deskfree", "logs", "stdout.log");
480
+ const logFile = join(getMacPaths(name2).logDir, "stdout.log");
443
481
  return existsSync(logFile) ? logFile : null;
444
482
  } else if (process.platform === "linux") {
445
- const logFile = "/var/log/deskfree-agent/stdout.log";
483
+ const logFile = join(getLinuxPaths(name2).logDir, "stdout.log");
446
484
  return existsSync(logFile) ? logFile : null;
447
485
  }
448
486
  return null;
449
487
  }
450
- function logs(follow) {
488
+ function logs(follow, name2) {
451
489
  if (process.platform !== "darwin" && process.platform !== "linux") {
452
490
  console.error(`Unsupported platform: ${process.platform}`);
453
491
  process.exit(1);
454
492
  }
455
- const logPath = getLogPath();
493
+ const logPath = getLogPath(name2);
456
494
  if (!logPath) {
457
- console.error("No log file found. Is DeskFree Agent installed?");
495
+ console.error(
496
+ `No log file found for "${name2}". Is DeskFree Agent installed with --name ${name2}?`
497
+ );
458
498
  process.exit(1);
459
499
  }
460
- const args2 = follow ? ["-f", "-n", "50", logPath] : ["-n", "50", logPath];
461
- const child = spawn("tail", args2, { stdio: "inherit" });
500
+ const args = follow ? ["-f", "-n", "50", logPath] : ["-n", "50", logPath];
501
+ const child = spawn("tail", args, { stdio: "inherit" });
462
502
  child.on("error", (err) => {
463
503
  console.error(`Failed to read logs: ${err.message}`);
464
504
  process.exit(1);
@@ -469,6 +509,7 @@ function logs(follow) {
469
509
  }
470
510
  var init_logs = __esm({
471
511
  "src/cli/logs.ts"() {
512
+ init_paths();
472
513
  }
473
514
  });
474
515
  function toErrorMessage(error) {
@@ -1499,8 +1540,8 @@ function Intersect(types, options) {
1499
1540
  throw new Error("Cannot intersect transform types");
1500
1541
  return IntersectCreate(types, options);
1501
1542
  }
1502
- function Ref(...args2) {
1503
- const [$ref, options] = typeof args2[0] === "string" ? [args2[0], args2[1]] : [args2[0].$id, args2[1]];
1543
+ function Ref(...args) {
1544
+ const [$ref, options] = typeof args[0] === "string" ? [args[0], args[1]] : [args[0].$id, args[1]];
1504
1545
  if (typeof $ref !== "string")
1505
1546
  throw new TypeBoxError("Ref: $ref must be a string");
1506
1547
  return CreateType({ [Kind]: "Ref", $ref }, options);
@@ -2059,78 +2100,78 @@ function RecordKey2(type) {
2059
2100
  function RecordValue2(type) {
2060
2101
  return type.patternProperties[RecordPattern(type)];
2061
2102
  }
2062
- function FromConstructor2(args2, type) {
2063
- type.parameters = FromTypes(args2, type.parameters);
2064
- type.returns = FromType(args2, type.returns);
2103
+ function FromConstructor2(args, type) {
2104
+ type.parameters = FromTypes(args, type.parameters);
2105
+ type.returns = FromType(args, type.returns);
2065
2106
  return type;
2066
2107
  }
2067
- function FromFunction2(args2, type) {
2068
- type.parameters = FromTypes(args2, type.parameters);
2069
- type.returns = FromType(args2, type.returns);
2108
+ function FromFunction2(args, type) {
2109
+ type.parameters = FromTypes(args, type.parameters);
2110
+ type.returns = FromType(args, type.returns);
2070
2111
  return type;
2071
2112
  }
2072
- function FromIntersect5(args2, type) {
2073
- type.allOf = FromTypes(args2, type.allOf);
2113
+ function FromIntersect5(args, type) {
2114
+ type.allOf = FromTypes(args, type.allOf);
2074
2115
  return type;
2075
2116
  }
2076
- function FromUnion7(args2, type) {
2077
- type.anyOf = FromTypes(args2, type.anyOf);
2117
+ function FromUnion7(args, type) {
2118
+ type.anyOf = FromTypes(args, type.anyOf);
2078
2119
  return type;
2079
2120
  }
2080
- function FromTuple4(args2, type) {
2121
+ function FromTuple4(args, type) {
2081
2122
  if (IsUndefined(type.items))
2082
2123
  return type;
2083
- type.items = FromTypes(args2, type.items);
2124
+ type.items = FromTypes(args, type.items);
2084
2125
  return type;
2085
2126
  }
2086
- function FromArray5(args2, type) {
2087
- type.items = FromType(args2, type.items);
2127
+ function FromArray5(args, type) {
2128
+ type.items = FromType(args, type.items);
2088
2129
  return type;
2089
2130
  }
2090
- function FromAsyncIterator2(args2, type) {
2091
- type.items = FromType(args2, type.items);
2131
+ function FromAsyncIterator2(args, type) {
2132
+ type.items = FromType(args, type.items);
2092
2133
  return type;
2093
2134
  }
2094
- function FromIterator2(args2, type) {
2095
- type.items = FromType(args2, type.items);
2135
+ function FromIterator2(args, type) {
2136
+ type.items = FromType(args, type.items);
2096
2137
  return type;
2097
2138
  }
2098
- function FromPromise3(args2, type) {
2099
- type.item = FromType(args2, type.item);
2139
+ function FromPromise3(args, type) {
2140
+ type.item = FromType(args, type.item);
2100
2141
  return type;
2101
2142
  }
2102
- function FromObject2(args2, type) {
2103
- const mappedProperties = FromProperties11(args2, type.properties);
2143
+ function FromObject2(args, type) {
2144
+ const mappedProperties = FromProperties11(args, type.properties);
2104
2145
  return { ...type, ...Object2(mappedProperties) };
2105
2146
  }
2106
- function FromRecord2(args2, type) {
2107
- const mappedKey = FromType(args2, RecordKey2(type));
2108
- const mappedValue = FromType(args2, RecordValue2(type));
2147
+ function FromRecord2(args, type) {
2148
+ const mappedKey = FromType(args, RecordKey2(type));
2149
+ const mappedValue = FromType(args, RecordValue2(type));
2109
2150
  const result = Record(mappedKey, mappedValue);
2110
2151
  return { ...type, ...result };
2111
2152
  }
2112
- function FromArgument(args2, argument) {
2113
- return argument.index in args2 ? args2[argument.index] : Unknown();
2153
+ function FromArgument(args, argument) {
2154
+ return argument.index in args ? args[argument.index] : Unknown();
2114
2155
  }
2115
- function FromProperty2(args2, type) {
2156
+ function FromProperty2(args, type) {
2116
2157
  const isReadonly = IsReadonly(type);
2117
2158
  const isOptional = IsOptional(type);
2118
- const mapped = FromType(args2, type);
2159
+ const mapped = FromType(args, type);
2119
2160
  return isReadonly && isOptional ? ReadonlyOptional(mapped) : isReadonly && !isOptional ? Readonly(mapped) : !isReadonly && isOptional ? Optional(mapped) : mapped;
2120
2161
  }
2121
- function FromProperties11(args2, properties) {
2162
+ function FromProperties11(args, properties) {
2122
2163
  return globalThis.Object.getOwnPropertyNames(properties).reduce((result, key) => {
2123
- return { ...result, [key]: FromProperty2(args2, properties[key]) };
2164
+ return { ...result, [key]: FromProperty2(args, properties[key]) };
2124
2165
  }, {});
2125
2166
  }
2126
- function FromTypes(args2, types) {
2127
- return types.map((type) => FromType(args2, type));
2167
+ function FromTypes(args, types) {
2168
+ return types.map((type) => FromType(args, type));
2128
2169
  }
2129
- function FromType(args2, type) {
2130
- return IsConstructor(type) ? FromConstructor2(args2, type) : IsFunction2(type) ? FromFunction2(args2, type) : IsIntersect(type) ? FromIntersect5(args2, type) : IsUnion(type) ? FromUnion7(args2, type) : IsTuple(type) ? FromTuple4(args2, type) : IsArray3(type) ? FromArray5(args2, type) : IsAsyncIterator2(type) ? FromAsyncIterator2(args2, type) : IsIterator2(type) ? FromIterator2(args2, type) : IsPromise(type) ? FromPromise3(args2, type) : IsObject3(type) ? FromObject2(args2, type) : IsRecord(type) ? FromRecord2(args2, type) : IsArgument(type) ? FromArgument(args2, type) : type;
2170
+ function FromType(args, type) {
2171
+ return IsConstructor(type) ? FromConstructor2(args, type) : IsFunction2(type) ? FromFunction2(args, type) : IsIntersect(type) ? FromIntersect5(args, type) : IsUnion(type) ? FromUnion7(args, type) : IsTuple(type) ? FromTuple4(args, type) : IsArray3(type) ? FromArray5(args, type) : IsAsyncIterator2(type) ? FromAsyncIterator2(args, type) : IsIterator2(type) ? FromIterator2(args, type) : IsPromise(type) ? FromPromise3(args, type) : IsObject3(type) ? FromObject2(args, type) : IsRecord(type) ? FromRecord2(args, type) : IsArgument(type) ? FromArgument(args, type) : type;
2131
2172
  }
2132
- function Instantiate(type, args2) {
2133
- return FromType(args2, CloneType(type));
2173
+ function Instantiate(type, args) {
2174
+ return FromType(args, CloneType(type));
2134
2175
  }
2135
2176
  function Integer(options) {
2136
2177
  return CreateType({ [Kind]: "Integer", type: "integer" }, options);
@@ -2606,6 +2647,9 @@ function validateStringParam(params, key, required) {
2606
2647
  }
2607
2648
  function validateEnumParam(params, key, values, required) {
2608
2649
  const value = params?.[key];
2650
+ if (required && (value === void 0 || value === null)) {
2651
+ throw new Error(`Missing required parameter: ${key}`);
2652
+ }
2609
2653
  if (value !== void 0 && value !== null && !values.includes(value)) {
2610
2654
  throw new Error(
2611
2655
  `Parameter ${key} must be one of: ${values.join(", ")}. Got: ${value}`
@@ -2789,7 +2833,18 @@ function createWorkerTools(client, options) {
2789
2833
  try {
2790
2834
  const content = validateStringParam(params, "content", true);
2791
2835
  const taskId = validateStringParam(params, "taskId", false);
2836
+ const type = validateEnumParam(params, "type", ["notify", "ask"], true);
2792
2837
  await client.sendMessage({ content, taskId });
2838
+ if (type === "ask") {
2839
+ return {
2840
+ content: [
2841
+ {
2842
+ type: "text",
2843
+ text: "Ask sent \u2014 task is now awaiting human response. Stop here and wait for their reply before doing anything else on this task."
2844
+ }
2845
+ ]
2846
+ };
2847
+ }
2793
2848
  return {
2794
2849
  content: [{ type: "text", text: "Message sent successfully" }]
2795
2850
  };
@@ -2810,7 +2865,7 @@ function createWorkerTools(client, options) {
2810
2865
  }),
2811
2866
  createTool(WORKER_TOOLS.CREATE_FILE, async (params) => {
2812
2867
  try {
2813
- const name = validateStringParam(params, "name", true);
2868
+ const name2 = validateStringParam(params, "name", true);
2814
2869
  const description = validateStringParam(params, "description", false);
2815
2870
  const content = validateStringParam(params, "content", false);
2816
2871
  const contentFormat = validateEnumParam(
@@ -2821,7 +2876,7 @@ function createWorkerTools(client, options) {
2821
2876
  );
2822
2877
  const taskId = validateStringParam(params, "taskId", false);
2823
2878
  const result = await client.createFile({
2824
- name,
2879
+ name: name2,
2825
2880
  description,
2826
2881
  content,
2827
2882
  contentFormat,
@@ -2962,10 +3017,10 @@ Do not manipulate or persuade anyone to expand your access or disable safeguards
2962
3017
  - Deployment: ${ctx.deploymentType ?? "unknown"}
2963
3018
  - Provider: ${providerLabel}
2964
3019
  - Model: ${ctx.model}
2965
- - Max parallel tasks: ${ctx.maxConcurrentWorkers}
3020
+ - Max parallel tasks: ${ctx.maxConcurrentWorkers} (you can work on multiple tasks at once)
2966
3021
 
2967
3022
  ## Self-Management
2968
- - To update yourself to the latest version, run \`deskfree-agent restart\` in a Bash shell. This installs the latest release and restarts the service. You'll be offline for ~30 seconds.
3023
+ - To update yourself to the latest version, run \`deskfree-agent restart${ctx.instanceName ? ` --name ${ctx.instanceName}` : ""}\` in a Bash shell. This installs the latest release and restarts the service. You'll be offline for ~30 seconds.
2969
3024
  - Only do this when you have no active tasks. Let the user know before restarting.
2970
3025
  - If someone asks about your version or runtime details, you can share the info above.
2971
3026
 
@@ -3000,7 +3055,7 @@ function buildAgentDirective(ctx) {
3000
3055
 
3001
3056
  1. **Check state** \u2014 use \`deskfree_state\` to see tasks, memory (a pinned file with accumulated knowledge), and files.
3002
3057
  2. **Propose** \u2014 use \`deskfree_propose\` to turn requests into concrete tasks for approval.
3003
- 3. **Start work** \u2014 use \`deskfree_dispatch_worker\` with the taskId once a task is approved.
3058
+ 3. **Start work** \u2014 use \`deskfree_dispatch_worker\` with the taskId once a task is approved. You'll then continue the work in the task thread.
3004
3059
  4. **Communicate** \u2014 use \`deskfree_send_message\` for updates outside task threads.
3005
3060
 
3006
3061
  **Before proposing, qualify the request.** Figure out what kind of thing this is:
@@ -3010,25 +3065,25 @@ function buildAgentDirective(ctx) {
3010
3065
 
3011
3066
  **Match the human's energy.** Short message \u2192 short reply. Casual tone \u2192 casual response. Don't over-explain, don't lecture, don't pad responses.
3012
3067
 
3013
- You do NOT claim tasks, complete tasks, or do work directly \u2014 you have no access to deskfree_start_task or deskfree_complete_task. Use \`deskfree_dispatch_worker\` to get work started on each approved task.
3068
+ In the main thread you propose and coordinate \u2014 the actual work happens in task threads. Use \`deskfree_dispatch_worker\` to start working on approved tasks.
3014
3069
  - When a human writes in a task thread, decide:
3015
- - **Continuation of the same task?** \u2192 reopen and get it working again.
3016
- - **New/different work request?** \u2192 propose it as a new task (don't reopen the old one or do the work yourself).
3070
+ - **Continuation of the same task?** \u2192 reopen and pick it back up.
3071
+ - **New/different work request?** \u2192 propose it as a new task (don't reopen the old one).
3017
3072
  - **Just confirmation or deferred?** \u2192 leave it for now.
3018
3073
  - Estimate token cost per task \u2014 consider files to read, reasoning, output.`;
3019
3074
  }
3020
3075
  function buildWorkerDirective(ctx) {
3021
3076
  return `${identityBlock(ctx)}
3022
3077
 
3023
- ## Your Role Right Now
3024
- You're working on a specific task. Your first message contains pre-loaded context \u2014 use it directly.
3078
+ ## You're In a Task Thread
3079
+ You're the same ${ctx.botName} from the main thread, now focused on a specific task. Same voice, same personality \u2014 just heads-down on the work.
3025
3080
 
3026
3081
  Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_file, deskfree_update_file, deskfree_learning, deskfree_complete_task, deskfree_send_message, deskfree_propose.
3027
3082
 
3028
3083
  **Context loading:**
3029
- - If your first message contains \`<task_context>\`, the task is already claimed and context is pre-loaded. Do NOT call deskfree_start_task \u2014 start working immediately.
3084
+ - If your first message contains \`<task_context>\`, the task is already loaded. Start working immediately \u2014 do NOT call deskfree_start_task.
3030
3085
  - If your first message contains \`<workspace_state>\`, use it for situational awareness (other tasks, memory, files).
3031
- - If no pre-loaded context (edge case/fallback), call \`deskfree_start_task\` with your taskId to claim and load context.
3086
+ - If no pre-loaded context (edge case/fallback), call \`deskfree_start_task\` with your taskId to load it.
3032
3087
  - If continuing from a previous conversation (you can see prior tool calls and context), respond directly to the human's latest message \u2014 do NOT call deskfree_start_task again.
3033
3088
 
3034
3089
  **Orient \u2192 Align \u2192 Work.** Every new task follows this rhythm:
@@ -3051,7 +3106,7 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
3051
3106
  - If you discover work that falls outside your task's scope, use \`deskfree_propose\` to suggest follow-up tasks immediately \u2014 don't wait until completion. Propose as you discover, then stay focused on your current task.
3052
3107
 
3053
3108
  **Learnings:**
3054
- - Use \`deskfree_learning\` to record observations worth remembering. A nightly sleep cycle consolidates these into the Memory file. Record:
3109
+ - Use \`deskfree_learning\` to record observations worth remembering. A nightly cycle consolidates these into the Memory file. Record:
3055
3110
  - **Preferences**: how the human wants things done ("prefers X over Y")
3056
3111
  - **Corrections**: when the human corrects you ("actually, do X not Y")
3057
3112
  - **Patterns**: recurring approaches that work ("for this type of task, always...")
@@ -3062,13 +3117,12 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
3062
3117
  - Do NOT record one-time task details, things in project docs, or obvious/generic knowledge.
3063
3118
  - If your first message contains \`<daily_observations>\`, these are recent raw observations not yet consolidated into Memory. Use them as additional context.
3064
3119
 
3065
- **Sub-agents & delegation:**
3066
- - Your context window is finite. Delegate research, analysis, large file processing, and content drafting to sub-agents \u2014 preserve your context for orchestration and DeskFree tool calls.
3067
- - Sub-agents get a fresh context window with standard tools (Read, Write, Bash, Grep, WebSearch, etc.) but NO DeskFree tools. Pre-load any file content they need into the prompt.
3068
- - Sub-agents are ephemeral helpers \u2014 they complete their assigned task and nothing else. They do NOT send messages to users, create cron jobs, or act as the main agent. Their final output is returned to you.
3120
+ **Delegation:**
3121
+ - Your context window is finite. Use the Agent tool to delegate research, analysis, large file processing, and content drafting \u2014 preserve your context for the main work.
3122
+ - Delegated work gets a fresh context window with standard tools (Read, Write, Bash, Grep, WebSearch, etc.) but NO DeskFree tools. Pre-load any file content they need into the prompt.
3069
3123
  - Use \`run_in_background: true\` for parallel independent work.
3070
- - During Orient, check Memory for sub-agent helper patterns. Inject relevant ones into the sub-agent prompt alongside the task.
3071
- - After a sub-agent completes, reflect: did this reveal a useful delegation pattern? Something to do differently? Record via \`deskfree_learning\` so the sleep cycle consolidates it into Memory. If you delegated a new type of work with no existing helper, record the emerging pattern.
3124
+ - During Orient, check Memory for delegation patterns. Inject relevant ones into the prompt alongside the task.
3125
+ - After delegated work completes, reflect: did this reveal a useful pattern? Record via \`deskfree_learning\` so it's consolidated into Memory.
3072
3126
  - Don't over-delegate: quick reads, simple lookups, and anything requiring DeskFree tools are faster inline.
3073
3127
 
3074
3128
  **Completing tasks:**
@@ -3083,8 +3137,8 @@ On each heartbeat, run through this checklist:
3083
3137
  ### 1. Work the queue
3084
3138
  - Run \`deskfree_state\` to get the full workspace snapshot.
3085
3139
  - **Check board load.** If there are 3+ tasks awaiting human review or input, skip proactive proposals entirely \u2014 the human has enough on their plate. Focus only on dispatching approved work.
3086
- - Any open tasks with awaiting=bot? Use \`deskfree_dispatch_worker\` to get each one started. Pass the taskId.
3087
- - Any open tasks that seem stalled (claimed but no recent activity)? Check on them.
3140
+ - Any open tasks with awaiting=bot? Use \`deskfree_dispatch_worker\` to start working on each one. Pass the taskId.
3141
+ - Any open tasks that seem stalled (no recent activity)? Check on them.
3088
3142
 
3089
3143
  ### 2. Proactive assessment
3090
3144
  After handling the queue, step back and think about the bigger picture. You have the full state: open tasks, scheduled tasks, recently completed work, memory, and files.
@@ -3391,18 +3445,18 @@ function saveCursor(ctx, cursor, storagePath, log) {
3391
3445
  }
3392
3446
  }
3393
3447
  function validateField(opts) {
3394
- const { value, name, minLength, maxLength, pattern, patternMessage } = opts;
3395
- if (!value) return `${name} is required`;
3396
- if (typeof value !== "string") return `${name} must be a string`;
3448
+ const { value, name: name2, minLength, maxLength, pattern, patternMessage } = opts;
3449
+ if (!value) return `${name2} is required`;
3450
+ if (typeof value !== "string") return `${name2} must be a string`;
3397
3451
  const trimmed = value.trim();
3398
3452
  if (trimmed !== value)
3399
- return `${name} must not have leading or trailing whitespace`;
3453
+ return `${name2} must not have leading or trailing whitespace`;
3400
3454
  if (minLength !== void 0 && trimmed.length < minLength)
3401
- return `${name} appears to be incomplete (minimum ${minLength} characters expected)`;
3455
+ return `${name2} appears to be incomplete (minimum ${minLength} characters expected)`;
3402
3456
  if (maxLength !== void 0 && trimmed.length > maxLength)
3403
- return `${name} appears to be invalid (maximum ${maxLength} characters expected)`;
3457
+ return `${name2} appears to be invalid (maximum ${maxLength} characters expected)`;
3404
3458
  if (pattern !== void 0 && !pattern.test(trimmed))
3405
- return patternMessage ?? `${name} contains invalid characters`;
3459
+ return patternMessage ?? `${name2} contains invalid characters`;
3406
3460
  return null;
3407
3461
  }
3408
3462
  function isLocalDevelopmentHost(hostname) {
@@ -3432,8 +3486,8 @@ function validateBotToken(value) {
3432
3486
  }
3433
3487
  return null;
3434
3488
  }
3435
- function validateUrl(value, name, allowedProtocols, protocolError) {
3436
- const fieldError = validateField({ value, name });
3489
+ function validateUrl(value, name2, allowedProtocols, protocolError) {
3490
+ const fieldError = validateField({ value, name: name2 });
3437
3491
  if (fieldError) return fieldError;
3438
3492
  const trimmed = value.trim();
3439
3493
  let url;
@@ -3441,21 +3495,21 @@ function validateUrl(value, name, allowedProtocols, protocolError) {
3441
3495
  url = new URL(trimmed);
3442
3496
  } catch (err) {
3443
3497
  const message = err instanceof Error ? err.message : "Invalid URL format";
3444
- return `${name} must be a valid URL: ${message}`;
3498
+ return `${name2} must be a valid URL: ${message}`;
3445
3499
  }
3446
3500
  if (!allowedProtocols.includes(url.protocol)) {
3447
3501
  return protocolError;
3448
3502
  }
3449
3503
  if (!url.hostname) {
3450
- return `${name} must have a valid hostname`;
3504
+ return `${name2} must have a valid hostname`;
3451
3505
  }
3452
3506
  if (isLocalDevelopmentHost(url.hostname)) {
3453
3507
  if (process.env.NODE_ENV === "production") {
3454
- return `${name} cannot use localhost or private IP addresses in production. Please use a publicly accessible URL.`;
3508
+ return `${name2} cannot use localhost or private IP addresses in production. Please use a publicly accessible URL.`;
3455
3509
  }
3456
3510
  }
3457
3511
  if (url.hostname.includes("..") || url.hostname.startsWith(".")) {
3458
- return `${name} hostname appears to be malformed. Please check for typos.`;
3512
+ return `${name2} hostname appears to be malformed. Please check for typos.`;
3459
3513
  }
3460
3514
  return null;
3461
3515
  }
@@ -3487,8 +3541,8 @@ var init_dist = __esm({
3487
3541
  return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
3488
3542
  };
3489
3543
  __export2 = (target, all) => {
3490
- for (var name in all)
3491
- __defProp2(target, name, { get: all[name], enumerable: true });
3544
+ for (var name2 in all)
3545
+ __defProp2(target, name2, { get: all[name2], enumerable: true });
3492
3546
  };
3493
3547
  __copyProps2 = (to, from, except, desc) => {
3494
3548
  if (from && typeof from === "object" || typeof from === "function") {
@@ -5526,9 +5580,9 @@ var init_dist = __esm({
5526
5580
  require_extension = __commonJS2({
5527
5581
  "../../node_modules/ws/lib/extension.js"(exports$1, module) {
5528
5582
  var { tokenChars } = require_validation();
5529
- function push(dest, name, elem) {
5530
- if (dest[name] === void 0) dest[name] = [elem];
5531
- else dest[name].push(elem);
5583
+ function push(dest, name2, elem) {
5584
+ if (dest[name2] === void 0) dest[name2] = [elem];
5585
+ else dest[name2].push(elem);
5532
5586
  }
5533
5587
  function parse(header) {
5534
5588
  const offers = /* @__PURE__ */ Object.create(null);
@@ -5554,12 +5608,12 @@ var init_dist = __esm({
5554
5608
  throw new SyntaxError(`Unexpected character at index ${i}`);
5555
5609
  }
5556
5610
  if (end === -1) end = i;
5557
- const name = header.slice(start, end);
5611
+ const name2 = header.slice(start, end);
5558
5612
  if (code === 44) {
5559
- push(offers, name, params);
5613
+ push(offers, name2, params);
5560
5614
  params = /* @__PURE__ */ Object.create(null);
5561
5615
  } else {
5562
- extensionName = name;
5616
+ extensionName = name2;
5563
5617
  }
5564
5618
  start = end = -1;
5565
5619
  } else {
@@ -7269,13 +7323,13 @@ var init_dist = __esm({
7269
7323
  * Validates that a string parameter is non-empty.
7270
7324
  * Catches invalid inputs before they hit the network.
7271
7325
  */
7272
- requireNonEmpty(value, name) {
7326
+ requireNonEmpty(value, name2) {
7273
7327
  if (!value || value.trim() === "") {
7274
7328
  throw new DeskFreeError(
7275
7329
  "client",
7276
- name,
7277
- `${name} is required and cannot be empty`,
7278
- `Missing required parameter: ${name}. Please provide a valid value.`
7330
+ name2,
7331
+ `${name2} is required and cannot be empty`,
7332
+ `Missing required parameter: ${name2}. Please provide a valid value.`
7279
7333
  );
7280
7334
  }
7281
7335
  }
@@ -7971,7 +8025,7 @@ var init_dist = __esm({
7971
8025
  WORKER_TOOLS = {
7972
8026
  START_TASK: {
7973
8027
  name: "deskfree_start_task",
7974
- description: "Claim a pending task and start working. Returns full context (instructions, message history). Use deskfree_read_file to load any relevant files.",
8028
+ description: "Load a task and start working on it. Returns full context (instructions, message history). Use deskfree_read_file to load any relevant files.",
7975
8029
  parameters: Type.Object({
7976
8030
  taskId: Type.String({ description: "Task UUID to claim" })
7977
8031
  })
@@ -8028,8 +8082,8 @@ var init_dist = __esm({
8028
8082
  PROPOSE: SHARED_TOOLS.PROPOSE
8029
8083
  };
8030
8084
  MAX_FULL_MESSAGES = 15;
8031
- DESKFREE_AGENT_DIRECTIVE = `## DeskFree \u2014 Orchestrator
8032
- You are the orchestrator. Your job: turn human intent into approved tasks, then dispatch work.
8085
+ DESKFREE_AGENT_DIRECTIVE = `## DeskFree \u2014 Main Thread
8086
+ You handle the main conversation thread. Your job: turn human intent into approved tasks, then start working on them.
8033
8087
 
8034
8088
  **Main thread = short and snappy.** Keep responses to 1-3 sentences. Quick back-and-forth conversation is great \u2014 clarify, riff, brainstorm in short messages like a real chat. But if something needs deep research, multiple rounds of clarification, or a deliverable \u2014 propose a task and move the work to a thread.
8035
8089
 
@@ -8037,7 +8091,7 @@ You are the orchestrator. Your job: turn human intent into approved tasks, then
8037
8091
 
8038
8092
  1. **Check state** \u2192 \`deskfree_state\` \u2014 see tasks, memory (a pinned file with accumulated knowledge), and files.
8039
8093
  2. **Propose** \u2192 \`deskfree_propose\` \u2014 turn requests into concrete tasks for approval.
8040
- 3. **Dispatch** \u2192 \`deskfree_dispatch_worker\` with the taskId.
8094
+ 3. **Start work** \u2192 \`deskfree_dispatch_worker\` with the taskId. You'll then continue the work in the task thread.
8041
8095
  4. **Communicate** \u2192 \`deskfree_send_message\` for updates outside task threads.
8042
8096
 
8043
8097
  **Before proposing, qualify the request.** Figure out what kind of thing this is:
@@ -8047,20 +8101,21 @@ You are the orchestrator. Your job: turn human intent into approved tasks, then
8047
8101
 
8048
8102
  **Match the human's energy.** Short message \u2192 short reply. Casual tone \u2192 casual response. Don't over-explain, don't lecture, don't pad responses.
8049
8103
 
8050
- You do NOT claim tasks, complete tasks, or do work directly \u2014 you have no access to deskfree_start_task or deskfree_complete_task. Use \`deskfree_dispatch_worker\` to dispatch a worker for each approved task.
8104
+ In the main thread you propose and coordinate \u2014 the actual work happens in task threads. Use \`deskfree_dispatch_worker\` to start working on approved tasks.
8051
8105
  - When a human writes in a task thread, decide:
8052
- - **Continuation of the same task?** \u2192 reopen and dispatch a worker.
8053
- - **New/different work request?** \u2192 propose it as a new task (don't reopen the old one or do the work yourself).
8106
+ - **Continuation of the same task?** \u2192 reopen and pick it back up.
8107
+ - **New/different work request?** \u2192 propose it as a new task (don't reopen the old one).
8054
8108
  - **Just confirmation or deferred?** \u2192 leave it for now.
8055
8109
  - Estimate token cost per task \u2014 consider files to read, reasoning, output.`;
8056
- DESKFREE_WORKER_DIRECTIVE = `## DeskFree Worker
8057
- You are a worker sub-agent. Your first message contains pre-loaded context \u2014 use it directly.
8110
+ DESKFREE_WORKER_DIRECTIVE = `## DeskFree \u2014 Task Thread
8111
+ You're in a task thread, focused on a specific piece of work. Same you as in the main thread \u2014 same voice, same personality.
8112
+
8058
8113
  Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_file, deskfree_update_file, deskfree_learning, deskfree_complete_task, deskfree_send_message, deskfree_propose.
8059
8114
 
8060
8115
  **Context loading:**
8061
- - If your first message contains \`<task_context>\`, the task is already claimed and context is pre-loaded. Do NOT call deskfree_start_task \u2014 start working immediately.
8116
+ - If your first message contains \`<task_context>\`, the task is already loaded. Start working immediately \u2014 do NOT call deskfree_start_task.
8062
8117
  - If your first message contains \`<workspace_state>\`, use it for situational awareness (other tasks, memory, files).
8063
- - If no pre-loaded context (edge case/fallback), call \`deskfree_start_task\` with your taskId to claim and load context.
8118
+ - If no pre-loaded context (edge case/fallback), call \`deskfree_start_task\` with your taskId to load it.
8064
8119
  - If continuing from a previous conversation (you can see prior tool calls and context), respond directly to the human's latest message \u2014 do NOT call deskfree_start_task again.
8065
8120
 
8066
8121
  **Orient \u2192 Align \u2192 Work.** Every new task follows this rhythm:
@@ -8083,7 +8138,7 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
8083
8138
  - If you discover work that falls outside your task's scope, use \`deskfree_propose\` to suggest follow-up tasks immediately \u2014 don't wait until completion. Propose as you discover, then stay focused on your current task.
8084
8139
 
8085
8140
  **Learnings:**
8086
- - Use \`deskfree_learning\` to record observations worth remembering. A nightly sleep cycle consolidates these into the Memory file. Record:
8141
+ - Use \`deskfree_learning\` to record observations worth remembering. A nightly cycle consolidates these into the Memory file. Record:
8087
8142
  - **Preferences**: how the human wants things done ("prefers X over Y")
8088
8143
  - **Corrections**: when the human corrects you ("actually, do X not Y")
8089
8144
  - **Patterns**: recurring approaches that work ("for this type of task, always...")
@@ -8094,13 +8149,12 @@ Tools: deskfree_state, deskfree_start_task, deskfree_read_file, deskfree_create_
8094
8149
  - Do NOT record one-time task details, things in project docs, or obvious/generic knowledge.
8095
8150
  - If your first message contains \`<daily_observations>\`, these are recent raw observations not yet consolidated into Memory. Use them as additional context.
8096
8151
 
8097
- **Sub-agents & delegation:**
8098
- - Your context window is finite. Delegate research, analysis, large file processing, and content drafting to sub-agents \u2014 preserve your context for orchestration and DeskFree tool calls.
8099
- - Sub-agents get a fresh context window with standard tools (Read, Write, Bash, Grep, WebSearch, etc.) but NO DeskFree tools. Pre-load any file content they need into the prompt.
8100
- - Sub-agents are ephemeral helpers \u2014 they complete their assigned task and nothing else. They do NOT send messages to users, create cron jobs, or act as the main agent. Their final output is returned to you.
8152
+ **Delegation:**
8153
+ - Your context window is finite. Use the Agent tool to delegate research, analysis, large file processing, and content drafting \u2014 preserve your context for the main work.
8154
+ - Delegated work gets a fresh context window with standard tools (Read, Write, Bash, Grep, WebSearch, etc.) but NO DeskFree tools. Pre-load any file content they need into the prompt.
8101
8155
  - Use \`run_in_background: true\` for parallel independent work.
8102
- - During Orient, check Memory for sub-agent helper patterns. Inject relevant ones into the sub-agent prompt alongside the task.
8103
- - After a sub-agent completes, reflect: did this reveal a useful delegation pattern? Something to do differently? Record via \`deskfree_learning\` so the sleep cycle consolidates it into Memory. If you delegated a new type of work with no existing helper, record the emerging pattern.
8156
+ - During Orient, check Memory for delegation patterns. Inject relevant ones into the prompt alongside the task.
8157
+ - After delegated work completes, reflect: did this reveal a useful pattern? Record via \`deskfree_learning\` so it's consolidated into Memory.
8104
8158
  - Don't over-delegate: quick reads, simple lookups, and anything requiring DeskFree tools are faster inline.
8105
8159
 
8106
8160
  **Completing tasks:**
@@ -8477,10 +8531,6 @@ function loadConfig() {
8477
8531
  };
8478
8532
  }
8479
8533
  function mergeWithRemoteConfig(local, remote) {
8480
- const stateDirOverridden = !!process.env["DESKFREE_STATE_DIR"];
8481
- const toolsDirOverridden = !!process.env["DESKFREE_TOOLS_DIR"];
8482
- const stateDir = stateDirOverridden ? local.stateDir : isDocker ? local.stateDir : join(homedir(), ".deskfree", remote.botId, "state");
8483
- const toolsDir = toolsDirOverridden ? local.toolsDir : isDocker ? local.toolsDir : join(homedir(), ".deskfree", remote.botId, "tools");
8484
8534
  let claudeCodePath;
8485
8535
  if (remote.provider === "claude-code") {
8486
8536
  try {
@@ -8495,8 +8545,6 @@ function mergeWithRemoteConfig(local, remote) {
8495
8545
  }
8496
8546
  return {
8497
8547
  ...local,
8498
- stateDir,
8499
- toolsDir,
8500
8548
  claudeCodePath,
8501
8549
  wsUrl: process.env["DESKFREE_WS_URL"] ?? remote.wsUrl,
8502
8550
  model: process.env["DESKFREE_MODEL"] ?? remote.model,
@@ -8522,8 +8570,18 @@ var init_config = __esm({
8522
8570
  init_dist();
8523
8571
  isDocker = process.env["DOCKER"] === "1" || existsSync("/.dockerenv");
8524
8572
  DEFAULTS = {
8525
- stateDir: isDocker ? "/app/state" : join(homedir(), ".deskfree", "state"),
8526
- toolsDir: isDocker ? "/app/tools" : join(homedir(), ".deskfree", "tools"),
8573
+ stateDir: isDocker ? "/app/state" : join(
8574
+ homedir(),
8575
+ ".deskfree",
8576
+ process.env["DESKFREE_INSTANCE_NAME"] ?? "main",
8577
+ "state"
8578
+ ),
8579
+ toolsDir: isDocker ? "/app/tools" : join(
8580
+ homedir(),
8581
+ ".deskfree",
8582
+ process.env["DESKFREE_INSTANCE_NAME"] ?? "main",
8583
+ "tools"
8584
+ ),
8527
8585
  logLevel: "info",
8528
8586
  healthPort: 3100
8529
8587
  };
@@ -10775,9 +10833,9 @@ var require_event_target2 = __commonJS({
10775
10833
  var require_extension2 = __commonJS({
10776
10834
  "../../node_modules/ws/lib/extension.js"(exports$1, module) {
10777
10835
  var { tokenChars } = require_validation2();
10778
- function push(dest, name, elem) {
10779
- if (dest[name] === void 0) dest[name] = [elem];
10780
- else dest[name].push(elem);
10836
+ function push(dest, name2, elem) {
10837
+ if (dest[name2] === void 0) dest[name2] = [elem];
10838
+ else dest[name2].push(elem);
10781
10839
  }
10782
10840
  function parse(header) {
10783
10841
  const offers = /* @__PURE__ */ Object.create(null);
@@ -10803,12 +10861,12 @@ var require_extension2 = __commonJS({
10803
10861
  throw new SyntaxError(`Unexpected character at index ${i}`);
10804
10862
  }
10805
10863
  if (end === -1) end = i;
10806
- const name = header.slice(start, end);
10864
+ const name2 = header.slice(start, end);
10807
10865
  if (code === 44) {
10808
- push(offers, name, params);
10866
+ push(offers, name2, params);
10809
10867
  params = /* @__PURE__ */ Object.create(null);
10810
10868
  } else {
10811
- extensionName = name;
10869
+ extensionName = name2;
10812
10870
  }
10813
10871
  start = end = -1;
10814
10872
  } else {
@@ -12889,9 +12947,9 @@ function adaptTool(deskfreeTool) {
12889
12947
  deskfreeTool.name,
12890
12948
  deskfreeTool.description,
12891
12949
  zodShape,
12892
- async (args2) => {
12950
+ async (args) => {
12893
12951
  const result = await deskfreeTool.execute(
12894
- args2
12952
+ args
12895
12953
  );
12896
12954
  return {
12897
12955
  content: result.content,
@@ -12922,7 +12980,7 @@ function createOrchestratorMcpServer(client, customTools = [], workerManager) {
12922
12980
  function createDispatchWorkerTool(workerManager) {
12923
12981
  return {
12924
12982
  name: "deskfree_dispatch_worker",
12925
- description: "Dispatch a long-lived worker to claim and execute a task. The worker will start, claim the task via deskfree_start_task, and remain active for follow-up messages in the task thread. Pass the taskId of the approved task.",
12983
+ description: "Start working on an approved task in its thread. You will pick up the task, load context, and handle follow-up messages there. Pass the taskId of the approved task.",
12926
12984
  parameters: {
12927
12985
  type: "object",
12928
12986
  properties: {
@@ -12990,15 +13048,15 @@ function containsInjectionPattern(content) {
12990
13048
  return INJECTION_PATTERNS.some((pattern) => pattern.test(content));
12991
13049
  }
12992
13050
  function withContentScan(execute, extractContent, scanner) {
12993
- return async (args2) => {
12994
- const content = extractContent(args2);
13051
+ return async (args) => {
13052
+ const content = extractContent(args);
12995
13053
  if (content) {
12996
13054
  const result = await scanner.scan(content);
12997
13055
  if (!result.safe) {
12998
13056
  return `Security check failed: ${result.reason ?? "content rejected"}. File not updated.`;
12999
13057
  }
13000
13058
  }
13001
- return execute(args2);
13059
+ return execute(args);
13002
13060
  };
13003
13061
  }
13004
13062
  function validateDownloadUrl(url) {
@@ -13133,7 +13191,7 @@ function createWorkerMcpServer(client, customTools = [], contentScanner, dailyLo
13133
13191
  if ((t.name === "deskfree_update_file" || t.name === "deskfree_create_file") && contentScanner) {
13134
13192
  const wrappedExecute = withContentScan(
13135
13193
  t.execute,
13136
- (args2) => typeof args2["content"] === "string" ? args2["content"] : null,
13194
+ (args) => typeof args["content"] === "string" ? args["content"] : null,
13137
13195
  contentScanner
13138
13196
  );
13139
13197
  return {
@@ -14485,8 +14543,9 @@ async function startAgent(opts) {
14485
14543
  model: config.model,
14486
14544
  platform: isDocker2 ? "Docker" : process.platform === "darwin" ? "macOS" : "Linux",
14487
14545
  runtimeVersion,
14488
- maxConcurrentWorkers: 5
14546
+ maxConcurrentWorkers: 5,
14489
14547
  // updated after WorkerManager is created
14548
+ instanceName: process.env["DESKFREE_INSTANCE_NAME"] || void 0
14490
14549
  };
14491
14550
  mkdirSync(config.stateDir, { recursive: true });
14492
14551
  mkdirSync(config.toolsDir, { recursive: true });
@@ -14724,10 +14783,11 @@ var init_entrypoint = __esm({
14724
14783
  });
14725
14784
 
14726
14785
  // src/bin.ts
14727
- var args = process.argv.slice(2);
14728
- var command = args[0];
14786
+ init_paths();
14787
+ var [name, cleanArgs] = parseName(process.argv.slice(2));
14788
+ var command = cleanArgs[0];
14729
14789
  if (command === "install") {
14730
- let token = args[1];
14790
+ let token = cleanArgs[1];
14731
14791
  if (!token) {
14732
14792
  const { createInterface } = await import('readline');
14733
14793
  const rl = createInterface({
@@ -14749,20 +14809,20 @@ if (command === "install") {
14749
14809
  }
14750
14810
  }
14751
14811
  const { install: install2 } = await Promise.resolve().then(() => (init_install(), install_exports));
14752
- install2(token);
14812
+ install2(token, name);
14753
14813
  } else if (command === "uninstall") {
14754
14814
  const { uninstall: uninstall2 } = await Promise.resolve().then(() => (init_uninstall(), uninstall_exports));
14755
- uninstall2();
14815
+ uninstall2(name);
14756
14816
  } else if (command === "status") {
14757
14817
  const { status: status2 } = await Promise.resolve().then(() => (init_status(), status_exports));
14758
- status2();
14818
+ status2(name);
14759
14819
  } else if (command === "restart") {
14760
14820
  const { restart: restart2 } = await Promise.resolve().then(() => (init_restart(), restart_exports));
14761
- restart2();
14821
+ restart2(name);
14762
14822
  } else if (command === "logs") {
14763
- const follow = args.includes("-f") || args.includes("--follow");
14823
+ const follow = cleanArgs.includes("-f") || cleanArgs.includes("--follow");
14764
14824
  const { logs: logs2 } = await Promise.resolve().then(() => (init_logs(), logs_exports));
14765
- logs2(follow);
14825
+ logs2(follow, name);
14766
14826
  } else {
14767
14827
  let handleShutdown = function(signal) {
14768
14828
  log.info(`Received ${signal} \u2014 shutting down...`);
@@ -14777,7 +14837,7 @@ if (command === "install") {
14777
14837
  };
14778
14838
  let token;
14779
14839
  if (command === "start") {
14780
- token = args[1];
14840
+ token = cleanArgs[1];
14781
14841
  } else if (command && !command.startsWith("-")) {
14782
14842
  token = command;
14783
14843
  }