@questionbase/deskfree 0.5.0 → 0.5.2

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