@questionbase/deskfree 0.4.4 → 0.4.7

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.
@@ -1,13 +1,99 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { execSync } from 'child_process';
3
- import { writeFileSync, chmodSync } from 'fs';
3
+ import { mkdirSync, writeFileSync, chmodSync } from 'fs';
4
+ import { homedir } from 'os';
5
+ import { dirname, join } from 'path';
4
6
 
5
7
  createRequire(import.meta.url);
6
8
  var SERVICE_NAME = "deskfree-agent";
7
- var SERVICE_FILE = `/etc/systemd/system/${SERVICE_NAME}.service`;
8
- var ENV_FILE = `/etc/${SERVICE_NAME}.env`;
9
9
  var PACKAGE = "@questionbase/deskfree@latest";
10
- function install(token) {
10
+ var PLIST_LABEL = "com.deskfree.agent";
11
+ function getMacPaths() {
12
+ const home = homedir();
13
+ const deskfreeDir = join(home, ".deskfree");
14
+ return {
15
+ deskfreeDir,
16
+ envFile: join(deskfreeDir, ".env"),
17
+ launcher: join(deskfreeDir, "launcher.sh"),
18
+ logDir: join(deskfreeDir, "logs"),
19
+ plist: join(home, "Library", "LaunchAgents", `${PLIST_LABEL}.plist`)
20
+ };
21
+ }
22
+ function installMac(token) {
23
+ const paths = getMacPaths();
24
+ let nodeBinDir;
25
+ try {
26
+ const nodePath = execSync("which node", { encoding: "utf8" }).trim();
27
+ nodeBinDir = dirname(nodePath);
28
+ } catch {
29
+ console.error("Error: node not found in PATH");
30
+ process.exit(1);
31
+ }
32
+ mkdirSync(paths.deskfreeDir, { recursive: true });
33
+ mkdirSync(paths.logDir, { recursive: true });
34
+ mkdirSync(dirname(paths.plist), { recursive: true });
35
+ writeFileSync(paths.envFile, `DESKFREE_LAUNCH=${token}
36
+ `, { mode: 384 });
37
+ chmodSync(paths.envFile, 384);
38
+ console.log(`Wrote ${paths.envFile}`);
39
+ const launcher = `#!/bin/bash
40
+ set -euo pipefail
41
+
42
+ export PATH="${nodeBinDir}:$PATH"
43
+
44
+ # Update to latest version before starting
45
+ npm install -g ${PACKAGE} 2>/dev/null || true
46
+
47
+ # Source env
48
+ set -a
49
+ source "${paths.envFile}"
50
+ set +a
51
+
52
+ exec deskfree-agent start
53
+ `;
54
+ writeFileSync(paths.launcher, launcher, { mode: 493 });
55
+ chmodSync(paths.launcher, 493);
56
+ console.log(`Wrote ${paths.launcher}`);
57
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
58
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
59
+ <plist version="1.0">
60
+ <dict>
61
+ <key>Label</key>
62
+ <string>${PLIST_LABEL}</string>
63
+ <key>ProgramArguments</key>
64
+ <array>
65
+ <string>${paths.launcher}</string>
66
+ </array>
67
+ <key>KeepAlive</key>
68
+ <true/>
69
+ <key>RunAtLoad</key>
70
+ <true/>
71
+ <key>StandardOutPath</key>
72
+ <string>${join(paths.logDir, "stdout.log")}</string>
73
+ <key>StandardErrorPath</key>
74
+ <string>${join(paths.logDir, "stderr.log")}</string>
75
+ <key>ThrottleInterval</key>
76
+ <integer>10</integer>
77
+ </dict>
78
+ </plist>
79
+ `;
80
+ writeFileSync(paths.plist, plist);
81
+ console.log(`Wrote ${paths.plist}`);
82
+ try {
83
+ execSync(`launchctl bootout gui/$(id -u) ${paths.plist}`, {
84
+ stdio: "ignore"
85
+ });
86
+ } catch {
87
+ }
88
+ execSync(`launchctl bootstrap gui/$(id -u) ${paths.plist}`);
89
+ console.log(`
90
+ Service ${PLIST_LABEL} installed and started.`);
91
+ console.log(`Check status: launchctl print gui/$(id -u)/${PLIST_LABEL}`);
92
+ console.log(`Logs: tail -f ${join(paths.logDir, "stdout.log")}`);
93
+ }
94
+ var SYSTEMD_SERVICE_FILE = `/etc/systemd/system/${SERVICE_NAME}.service`;
95
+ var SYSTEMD_ENV_FILE = `/etc/${SERVICE_NAME}.env`;
96
+ function installLinux(token) {
11
97
  if (process.getuid?.() !== 0) {
12
98
  console.error("Error: install must be run as root (use sudo)");
13
99
  process.exit(1);
@@ -19,10 +105,12 @@ function install(token) {
19
105
  console.error("Error: npx not found in PATH");
20
106
  process.exit(1);
21
107
  }
22
- writeFileSync(ENV_FILE, `DESKFREE_LAUNCH=${token}
23
- `, { mode: 384 });
24
- chmodSync(ENV_FILE, 384);
25
- console.log(`Wrote ${ENV_FILE}`);
108
+ writeFileSync(SYSTEMD_ENV_FILE, `DESKFREE_LAUNCH=${token}
109
+ `, {
110
+ mode: 384
111
+ });
112
+ chmodSync(SYSTEMD_ENV_FILE, 384);
113
+ console.log(`Wrote ${SYSTEMD_ENV_FILE}`);
26
114
  const unit = `[Unit]
27
115
  Description=DeskFree Agent
28
116
  After=network-online.target
@@ -30,8 +118,9 @@ Wants=network-online.target
30
118
 
31
119
  [Service]
32
120
  Type=simple
121
+ ExecStartPre=${npxPath} ${PACKAGE} --version
33
122
  ExecStart=${npxPath} ${PACKAGE} start
34
- EnvironmentFile=${ENV_FILE}
123
+ EnvironmentFile=${SYSTEMD_ENV_FILE}
35
124
  Environment=NODE_ENV=production
36
125
  Restart=always
37
126
  RestartSec=10
@@ -39,14 +128,25 @@ RestartSec=10
39
128
  [Install]
40
129
  WantedBy=multi-user.target
41
130
  `;
42
- writeFileSync(SERVICE_FILE, unit);
43
- console.log(`Wrote ${SERVICE_FILE}`);
131
+ writeFileSync(SYSTEMD_SERVICE_FILE, unit);
132
+ console.log(`Wrote ${SYSTEMD_SERVICE_FILE}`);
44
133
  execSync("systemctl daemon-reload");
45
134
  execSync(`systemctl enable ${SERVICE_NAME}`);
46
135
  execSync(`systemctl start ${SERVICE_NAME}`);
47
- console.log(`Service ${SERVICE_NAME} installed and started.`);
136
+ console.log(`
137
+ Service ${SERVICE_NAME} installed and started.`);
48
138
  console.log(`Check status: systemctl status ${SERVICE_NAME}`);
49
139
  }
140
+ function install(token) {
141
+ if (process.platform === "darwin") {
142
+ installMac(token);
143
+ } else if (process.platform === "linux") {
144
+ installLinux(token);
145
+ } else {
146
+ console.error(`Unsupported platform: ${process.platform}`);
147
+ process.exit(1);
148
+ }
149
+ }
50
150
 
51
151
  export { install };
52
152
  //# sourceMappingURL=install.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/install.ts"],"names":[],"mappings":";;;;;AAGA,IAAM,YAAA,GAAe,gBAAA;AACrB,IAAM,YAAA,GAAe,uBAAuB,YAAY,CAAA,QAAA,CAAA;AACxD,IAAM,QAAA,GAAW,QAAQ,YAAY,CAAA,IAAA,CAAA;AACrC,IAAM,OAAA,GAAU,+BAAA;AAET,SAAS,QAAQ,KAAA,EAAqB;AAC3C,EAAA,IAAI,OAAA,CAAQ,MAAA,IAAS,KAAM,CAAA,EAAG;AAC5B,IAAA,OAAA,CAAQ,MAAM,+CAA+C,CAAA;AAC7D,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AAGA,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI;AACF,IAAA,OAAA,GAAU,SAAS,WAAA,EAAa,EAAE,UAAU,MAAA,EAAQ,EAAE,IAAA,EAAK;AAAA,EAC7D,CAAA,CAAA,MAAQ;AACN,IAAA,OAAA,CAAQ,MAAM,8BAA8B,CAAA;AAC5C,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AAGA,EAAA,aAAA,CAAc,QAAA,EAAU,mBAAmB,KAAK;AAAA,CAAA,EAAM,EAAE,IAAA,EAAM,GAAA,EAAO,CAAA;AACrE,EAAA,SAAA,CAAU,UAAU,GAAK,CAAA;AACzB,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,MAAA,EAAS,QAAQ,CAAA,CAAE,CAAA;AAG/B,EAAA,MAAM,IAAA,GAAO,CAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA,UAAA,EAOH,OAAO,IAAI,OAAO,CAAA;AAAA,gBAAA,EACZ,QAAQ;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA,CAAA;AASxB,EAAA,aAAA,CAAc,cAAc,IAAI,CAAA;AAChC,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,MAAA,EAAS,YAAY,CAAA,CAAE,CAAA;AAGnC,EAAA,QAAA,CAAS,yBAAyB,CAAA;AAClC,EAAA,QAAA,CAAS,CAAA,iBAAA,EAAoB,YAAY,CAAA,CAAE,CAAA;AAC3C,EAAA,QAAA,CAAS,CAAA,gBAAA,EAAmB,YAAY,CAAA,CAAE,CAAA;AAE1C,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,QAAA,EAAW,YAAY,CAAA,uBAAA,CAAyB,CAAA;AAC5D,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,+BAAA,EAAkC,YAAY,CAAA,CAAE,CAAA;AAC9D","file":"install.js","sourcesContent":["import { execSync } from 'node:child_process';\nimport { chmodSync, writeFileSync } from 'node:fs';\n\nconst SERVICE_NAME = 'deskfree-agent';\nconst SERVICE_FILE = `/etc/systemd/system/${SERVICE_NAME}.service`;\nconst ENV_FILE = `/etc/${SERVICE_NAME}.env`;\nconst PACKAGE = '@questionbase/deskfree@latest';\n\nexport function install(token: string): void {\n if (process.getuid?.() !== 0) {\n console.error('Error: install must be run as root (use sudo)');\n process.exit(1);\n }\n\n // Resolve full npx path so systemd doesn't depend on PATH\n let npxPath: string;\n try {\n npxPath = execSync('which npx', { encoding: 'utf8' }).trim();\n } catch {\n console.error('Error: npx not found in PATH');\n process.exit(1);\n }\n\n // Write env file with token (chmod 600 — only root can read)\n writeFileSync(ENV_FILE, `DESKFREE_LAUNCH=${token}\\n`, { mode: 0o600 });\n chmodSync(ENV_FILE, 0o600);\n console.log(`Wrote ${ENV_FILE}`);\n\n // Write systemd unit file\n const unit = `[Unit]\nDescription=DeskFree Agent\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nExecStart=${npxPath} ${PACKAGE} start\nEnvironmentFile=${ENV_FILE}\nEnvironment=NODE_ENV=production\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\n`;\n\n writeFileSync(SERVICE_FILE, unit);\n console.log(`Wrote ${SERVICE_FILE}`);\n\n // Enable and start\n execSync('systemctl daemon-reload');\n execSync(`systemctl enable ${SERVICE_NAME}`);\n execSync(`systemctl start ${SERVICE_NAME}`);\n\n console.log(`Service ${SERVICE_NAME} installed and started.`);\n console.log(`Check status: systemctl status ${SERVICE_NAME}`);\n}\n"]}
1
+ {"version":3,"sources":["../../src/cli/install.ts"],"names":[],"mappings":"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","file":"install.js","sourcesContent":["import { execSync } from 'node:child_process';\nimport { chmodSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nconst SERVICE_NAME = 'deskfree-agent';\nconst PACKAGE = '@questionbase/deskfree@latest';\n\n// ── macOS (launchd) ─────────────────────────────────────────────────────────\n\nconst PLIST_LABEL = 'com.deskfree.agent';\n\nfunction getMacPaths() {\n const home = homedir();\n const deskfreeDir = join(home, '.deskfree');\n return {\n deskfreeDir,\n envFile: join(deskfreeDir, '.env'),\n launcher: join(deskfreeDir, 'launcher.sh'),\n logDir: join(deskfreeDir, 'logs'),\n plist: join(home, 'Library', 'LaunchAgents', `${PLIST_LABEL}.plist`),\n };\n}\n\nfunction installMac(token: string): void {\n const paths = getMacPaths();\n\n // Resolve full path to node's bin directory (survives nvm/fnm across sessions)\n let nodeBinDir: string;\n try {\n const nodePath = execSync('which node', { encoding: 'utf8' }).trim();\n nodeBinDir = dirname(nodePath);\n } catch {\n console.error('Error: node not found in PATH');\n process.exit(1);\n }\n\n // Create directories\n mkdirSync(paths.deskfreeDir, { recursive: true });\n mkdirSync(paths.logDir, { recursive: true });\n mkdirSync(dirname(paths.plist), { recursive: true });\n\n // Write env file (chmod 600)\n writeFileSync(paths.envFile, `DESKFREE_LAUNCH=${token}\\n`, { mode: 0o600 });\n chmodSync(paths.envFile, 0o600);\n console.log(`Wrote ${paths.envFile}`);\n\n // Write launcher script\n const launcher = `#!/bin/bash\nset -euo pipefail\n\nexport PATH=\"${nodeBinDir}:$PATH\"\n\n# Update to latest version before starting\nnpm install -g ${PACKAGE} 2>/dev/null || true\n\n# Source env\nset -a\nsource \"${paths.envFile}\"\nset +a\n\nexec deskfree-agent start\n`;\n\n writeFileSync(paths.launcher, launcher, { mode: 0o755 });\n chmodSync(paths.launcher, 0o755);\n console.log(`Wrote ${paths.launcher}`);\n\n // Write LaunchAgent plist\n const plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${PLIST_LABEL}</string>\n <key>ProgramArguments</key>\n <array>\n <string>${paths.launcher}</string>\n </array>\n <key>KeepAlive</key>\n <true/>\n <key>RunAtLoad</key>\n <true/>\n <key>StandardOutPath</key>\n <string>${join(paths.logDir, 'stdout.log')}</string>\n <key>StandardErrorPath</key>\n <string>${join(paths.logDir, 'stderr.log')}</string>\n <key>ThrottleInterval</key>\n <integer>10</integer>\n</dict>\n</plist>\n`;\n\n writeFileSync(paths.plist, plist);\n console.log(`Wrote ${paths.plist}`);\n\n // Unload if already loaded (ignore errors)\n try {\n execSync(`launchctl bootout gui/$(id -u) ${paths.plist}`, {\n stdio: 'ignore',\n });\n } catch {\n // not loaded — fine\n }\n\n // Load the service\n execSync(`launchctl bootstrap gui/$(id -u) ${paths.plist}`);\n\n console.log(`\\nService ${PLIST_LABEL} installed and started.`);\n console.log(`Check status: launchctl print gui/$(id -u)/${PLIST_LABEL}`);\n console.log(`Logs: tail -f ${join(paths.logDir, 'stdout.log')}`);\n}\n\n// ── Linux (systemd) ─────────────────────────────────────────────────────────\n\nconst SYSTEMD_SERVICE_FILE = `/etc/systemd/system/${SERVICE_NAME}.service`;\nconst SYSTEMD_ENV_FILE = `/etc/${SERVICE_NAME}.env`;\n\nfunction installLinux(token: string): void {\n if (process.getuid?.() !== 0) {\n console.error('Error: install must be run as root (use sudo)');\n process.exit(1);\n }\n\n // Resolve full npx path so systemd doesn't depend on PATH\n let npxPath: string;\n try {\n npxPath = execSync('which npx', { encoding: 'utf8' }).trim();\n } catch {\n console.error('Error: npx not found in PATH');\n process.exit(1);\n }\n\n // Write env file with token (chmod 600 — only root can read)\n writeFileSync(SYSTEMD_ENV_FILE, `DESKFREE_LAUNCH=${token}\\n`, {\n mode: 0o600,\n });\n chmodSync(SYSTEMD_ENV_FILE, 0o600);\n console.log(`Wrote ${SYSTEMD_ENV_FILE}`);\n\n // Write systemd unit file\n const unit = `[Unit]\nDescription=DeskFree Agent\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nExecStartPre=${npxPath} ${PACKAGE} --version\nExecStart=${npxPath} ${PACKAGE} start\nEnvironmentFile=${SYSTEMD_ENV_FILE}\nEnvironment=NODE_ENV=production\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\n`;\n\n writeFileSync(SYSTEMD_SERVICE_FILE, unit);\n console.log(`Wrote ${SYSTEMD_SERVICE_FILE}`);\n\n // Enable and start\n execSync('systemctl daemon-reload');\n execSync(`systemctl enable ${SERVICE_NAME}`);\n execSync(`systemctl start ${SERVICE_NAME}`);\n\n console.log(`\\nService ${SERVICE_NAME} installed and started.`);\n console.log(`Check status: systemctl status ${SERVICE_NAME}`);\n}\n\n// ── Public API ──────────────────────────────────────────────────────────────\n\nexport function install(token: string): void {\n if (process.platform === 'darwin') {\n installMac(token);\n } else if (process.platform === 'linux') {\n installLinux(token);\n } else {\n console.error(`Unsupported platform: ${process.platform}`);\n process.exit(1);\n }\n}\n"]}
@@ -1,12 +1,36 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { execSync } from 'child_process';
3
3
  import { existsSync, unlinkSync } from 'fs';
4
+ import { homedir } from 'os';
5
+ import { join } from 'path';
4
6
 
5
7
  createRequire(import.meta.url);
6
8
  var SERVICE_NAME = "deskfree-agent";
7
- var SERVICE_FILE = `/etc/systemd/system/${SERVICE_NAME}.service`;
8
- var ENV_FILE = `/etc/${SERVICE_NAME}.env`;
9
- function uninstall() {
9
+ var PLIST_LABEL = "com.deskfree.agent";
10
+ function uninstallMac() {
11
+ const home = homedir();
12
+ const plist = join(home, "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
13
+ const deskfreeDir = join(home, ".deskfree");
14
+ const envFile = join(deskfreeDir, ".env");
15
+ const launcher = join(deskfreeDir, "launcher.sh");
16
+ try {
17
+ execSync(`launchctl bootout gui/$(id -u) ${plist}`, { stdio: "ignore" });
18
+ } catch {
19
+ }
20
+ for (const file of [plist, envFile, launcher]) {
21
+ if (existsSync(file)) {
22
+ unlinkSync(file);
23
+ console.log(`Removed ${file}`);
24
+ }
25
+ }
26
+ console.log(`Service ${PLIST_LABEL} uninstalled.`);
27
+ console.log(
28
+ `Note: logs and state in ${deskfreeDir} were preserved. Remove manually if desired.`
29
+ );
30
+ }
31
+ var SYSTEMD_SERVICE_FILE = `/etc/systemd/system/${SERVICE_NAME}.service`;
32
+ var SYSTEMD_ENV_FILE = `/etc/${SERVICE_NAME}.env`;
33
+ function uninstallLinux() {
10
34
  if (process.getuid?.() !== 0) {
11
35
  console.error("Error: uninstall must be run as root (use sudo)");
12
36
  process.exit(1);
@@ -19,17 +43,27 @@ function uninstall() {
19
43
  execSync(`systemctl disable ${SERVICE_NAME}`, { stdio: "ignore" });
20
44
  } catch {
21
45
  }
22
- if (existsSync(SERVICE_FILE)) {
23
- unlinkSync(SERVICE_FILE);
24
- console.log(`Removed ${SERVICE_FILE}`);
46
+ if (existsSync(SYSTEMD_SERVICE_FILE)) {
47
+ unlinkSync(SYSTEMD_SERVICE_FILE);
48
+ console.log(`Removed ${SYSTEMD_SERVICE_FILE}`);
25
49
  }
26
- if (existsSync(ENV_FILE)) {
27
- unlinkSync(ENV_FILE);
28
- console.log(`Removed ${ENV_FILE}`);
50
+ if (existsSync(SYSTEMD_ENV_FILE)) {
51
+ unlinkSync(SYSTEMD_ENV_FILE);
52
+ console.log(`Removed ${SYSTEMD_ENV_FILE}`);
29
53
  }
30
54
  execSync("systemctl daemon-reload");
31
55
  console.log(`Service ${SERVICE_NAME} uninstalled.`);
32
56
  }
57
+ function uninstall() {
58
+ if (process.platform === "darwin") {
59
+ uninstallMac();
60
+ } else if (process.platform === "linux") {
61
+ uninstallLinux();
62
+ } else {
63
+ console.error(`Unsupported platform: ${process.platform}`);
64
+ process.exit(1);
65
+ }
66
+ }
33
67
 
34
68
  export { uninstall };
35
69
  //# sourceMappingURL=uninstall.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/uninstall.ts"],"names":[],"mappings":";;;;;AAGA,IAAM,YAAA,GAAe,gBAAA;AACrB,IAAM,YAAA,GAAe,uBAAuB,YAAY,CAAA,QAAA,CAAA;AACxD,IAAM,QAAA,GAAW,QAAQ,YAAY,CAAA,IAAA,CAAA;AAE9B,SAAS,SAAA,GAAkB;AAChC,EAAA,IAAI,OAAA,CAAQ,MAAA,IAAS,KAAM,CAAA,EAAG;AAC5B,IAAA,OAAA,CAAQ,MAAM,iDAAiD,CAAA;AAC/D,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AAGA,EAAA,IAAI;AACF,IAAA,QAAA,CAAS,kBAAkB,YAAY,CAAA,CAAA,EAAI,EAAE,KAAA,EAAO,UAAU,CAAA;AAAA,EAChE,CAAA,CAAA,MAAQ;AAAA,EAER;AACA,EAAA,IAAI;AACF,IAAA,QAAA,CAAS,qBAAqB,YAAY,CAAA,CAAA,EAAI,EAAE,KAAA,EAAO,UAAU,CAAA;AAAA,EACnE,CAAA,CAAA,MAAQ;AAAA,EAER;AAGA,EAAA,IAAI,UAAA,CAAW,YAAY,CAAA,EAAG;AAC5B,IAAA,UAAA,CAAW,YAAY,CAAA;AACvB,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,QAAA,EAAW,YAAY,CAAA,CAAE,CAAA;AAAA,EACvC;AACA,EAAA,IAAI,UAAA,CAAW,QAAQ,CAAA,EAAG;AACxB,IAAA,UAAA,CAAW,QAAQ,CAAA;AACnB,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,QAAA,EAAW,QAAQ,CAAA,CAAE,CAAA;AAAA,EACnC;AAEA,EAAA,QAAA,CAAS,yBAAyB,CAAA;AAClC,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,QAAA,EAAW,YAAY,CAAA,aAAA,CAAe,CAAA;AACpD","file":"uninstall.js","sourcesContent":["import { execSync } from 'node:child_process';\nimport { existsSync, unlinkSync } from 'node:fs';\n\nconst SERVICE_NAME = 'deskfree-agent';\nconst SERVICE_FILE = `/etc/systemd/system/${SERVICE_NAME}.service`;\nconst ENV_FILE = `/etc/${SERVICE_NAME}.env`;\n\nexport function uninstall(): void {\n if (process.getuid?.() !== 0) {\n console.error('Error: uninstall must be run as root (use sudo)');\n process.exit(1);\n }\n\n // Stop and disable (ignore errors if not running/enabled)\n try {\n execSync(`systemctl stop ${SERVICE_NAME}`, { stdio: 'ignore' });\n } catch {\n // not running — fine\n }\n try {\n execSync(`systemctl disable ${SERVICE_NAME}`, { stdio: 'ignore' });\n } catch {\n // not enabled — fine\n }\n\n // Remove files\n if (existsSync(SERVICE_FILE)) {\n unlinkSync(SERVICE_FILE);\n console.log(`Removed ${SERVICE_FILE}`);\n }\n if (existsSync(ENV_FILE)) {\n unlinkSync(ENV_FILE);\n console.log(`Removed ${ENV_FILE}`);\n }\n\n execSync('systemctl daemon-reload');\n console.log(`Service ${SERVICE_NAME} uninstalled.`);\n}\n"]}
1
+ {"version":3,"sources":["../../src/cli/uninstall.ts"],"names":[],"mappings":"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","file":"uninstall.js","sourcesContent":["import { execSync } from 'node:child_process';\nimport { existsSync, unlinkSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nconst SERVICE_NAME = 'deskfree-agent';\n\n// ── macOS (launchd) ─────────────────────────────────────────────────────────\n\nconst PLIST_LABEL = 'com.deskfree.agent';\n\nfunction uninstallMac(): void {\n const home = homedir();\n const plist = join(home, 'Library', 'LaunchAgents', `${PLIST_LABEL}.plist`);\n const deskfreeDir = join(home, '.deskfree');\n const envFile = join(deskfreeDir, '.env');\n const launcher = join(deskfreeDir, 'launcher.sh');\n\n // Unload (ignore errors if not loaded)\n try {\n execSync(`launchctl bootout gui/$(id -u) ${plist}`, { stdio: 'ignore' });\n } catch {\n // not loaded — fine\n }\n\n // Remove files\n for (const file of [plist, envFile, launcher]) {\n if (existsSync(file)) {\n unlinkSync(file);\n console.log(`Removed ${file}`);\n }\n }\n\n console.log(`Service ${PLIST_LABEL} uninstalled.`);\n console.log(\n `Note: logs and state in ${deskfreeDir} were preserved. Remove manually if desired.`,\n );\n}\n\n// ── Linux (systemd) ─────────────────────────────────────────────────────────\n\nconst SYSTEMD_SERVICE_FILE = `/etc/systemd/system/${SERVICE_NAME}.service`;\nconst SYSTEMD_ENV_FILE = `/etc/${SERVICE_NAME}.env`;\n\nfunction uninstallLinux(): void {\n if (process.getuid?.() !== 0) {\n console.error('Error: uninstall must be run as root (use sudo)');\n process.exit(1);\n }\n\n // Stop and disable (ignore errors if not running/enabled)\n try {\n execSync(`systemctl stop ${SERVICE_NAME}`, { stdio: 'ignore' });\n } catch {\n // not running — fine\n }\n try {\n execSync(`systemctl disable ${SERVICE_NAME}`, { stdio: 'ignore' });\n } catch {\n // not enabled — fine\n }\n\n // Remove files\n if (existsSync(SYSTEMD_SERVICE_FILE)) {\n unlinkSync(SYSTEMD_SERVICE_FILE);\n console.log(`Removed ${SYSTEMD_SERVICE_FILE}`);\n }\n if (existsSync(SYSTEMD_ENV_FILE)) {\n unlinkSync(SYSTEMD_ENV_FILE);\n console.log(`Removed ${SYSTEMD_ENV_FILE}`);\n }\n\n execSync('systemctl daemon-reload');\n console.log(`Service ${SERVICE_NAME} uninstalled.`);\n}\n\n// ── Public API ──────────────────────────────────────────────────────────────\n\nexport function uninstall(): void {\n if (process.platform === 'darwin') {\n uninstallMac();\n } else if (process.platform === 'linux') {\n uninstallLinux();\n } else {\n console.error(`Unsupported platform: ${process.platform}`);\n process.exit(1);\n }\n}\n"]}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { query, tool, createSdkMcpServer } from '@anthropic-ai/claude-agent-sdk';
3
3
  import { createRequire as createRequire$1 } from 'module';
4
- import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, unlinkSync, createWriteStream } from 'fs';
4
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync, unlinkSync, createWriteStream, appendFileSync, statSync } from 'fs';
5
5
  import { join, dirname, extname } from 'path';
6
6
  import { execFileSync, execFile } from 'child_process';
7
7
  import { z } from 'zod';
@@ -10174,6 +10174,8 @@ var SHARED_TOOLS = {
10174
10174
  })
10175
10175
  },
10176
10176
  PROPOSE: {
10177
+ name: "deskfree_propose",
10178
+ description: "Propose a plan for human approval. Nothing is created until the human reviews and approves in a modal. Put all details in the instructions field using simple markdown (bold, lists, inline code). Do NOT use markdown headers (#) \u2014 use **bold text** instead for section labels.",
10177
10179
  parameters: Type.Object({
10178
10180
  context: Type.Optional(
10179
10181
  Type.String({
@@ -10289,7 +10291,9 @@ var WORKER_TOOLS = {
10289
10291
  })
10290
10292
  },
10291
10293
  COMPLETE_TASK: SHARED_TOOLS.COMPLETE_TASK,
10292
- SEND_MESSAGE: SHARED_TOOLS.SEND_MESSAGE};
10294
+ SEND_MESSAGE: SHARED_TOOLS.SEND_MESSAGE,
10295
+ PROPOSE: SHARED_TOOLS.PROPOSE
10296
+ };
10293
10297
  var MAX_FULL_MESSAGES = 15;
10294
10298
  function trimTaskContext(result) {
10295
10299
  const trimmed = {
@@ -10606,6 +10610,41 @@ function createWorkerTools(client, options) {
10606
10610
  return errorResult(err);
10607
10611
  }
10608
10612
  }),
10613
+ createTool(WORKER_TOOLS.PROPOSE, async (params) => {
10614
+ try {
10615
+ const context = validateStringParam(params, "context", false);
10616
+ const taskId = validateStringParam(params, "taskId", false);
10617
+ const rawTasks = params.tasks;
10618
+ if (!Array.isArray(rawTasks) || rawTasks.length === 0) {
10619
+ throw new Error("tasks must be a non-empty array of task objects");
10620
+ }
10621
+ const tasks = rawTasks;
10622
+ for (let i = 0; i < tasks.length; i++) {
10623
+ const task = tasks[i];
10624
+ if (!task || typeof task !== "object") {
10625
+ throw new Error(`tasks[${i}] must be an object`);
10626
+ }
10627
+ if (!task.title || typeof task.title !== "string" || task.title.trim() === "") {
10628
+ throw new Error(`tasks[${i}].title must be a non-empty string`);
10629
+ }
10630
+ }
10631
+ await client.proposePlan({
10632
+ context,
10633
+ tasks,
10634
+ taskId
10635
+ });
10636
+ return {
10637
+ content: [
10638
+ {
10639
+ type: "text",
10640
+ text: `Proposal created with ${tasks.length} task(s)`
10641
+ }
10642
+ ]
10643
+ };
10644
+ } catch (err) {
10645
+ return errorResult(err);
10646
+ }
10647
+ }),
10609
10648
  createTool(WORKER_TOOLS.COMPLETE_TASK, async (params) => {
10610
10649
  try {
10611
10650
  const taskId = validateStringParam(params, "taskId", true);
@@ -10649,7 +10688,10 @@ You are the orchestrator. Your job: turn human intent into approved tasks, then
10649
10688
  **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.
10650
10689
 
10651
10690
  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.
10652
- - When a human writes in a task thread, decide: does it need bot action? If yes \u2192 reopen and dispatch a worker. If it's just confirmation or deferred \u2014 leave it for now.
10691
+ - When a human writes in a task thread, decide:
10692
+ - **Continuation of the same task?** \u2192 reopen and dispatch a worker.
10693
+ - **New/different work request?** \u2192 propose it as a new task (don't reopen the old one or do the work yourself).
10694
+ - **Just confirmation or deferred?** \u2192 leave it for now.
10653
10695
  - Estimate token cost per task \u2014 consider files to read, reasoning, output.`;
10654
10696
  var DESKFREE_WORKER_DIRECTIVE = `## DeskFree Worker
10655
10697
  You are a worker sub-agent. Your first message contains pre-loaded context \u2014 use it directly.
@@ -11283,6 +11325,14 @@ function validateApiUrl(value) {
11283
11325
 
11284
11326
  // src/agents/orchestrator.ts
11285
11327
  var MAX_ORCHESTRATOR_TURNS = 20;
11328
+ var ORCHESTRATOR_ALLOWED_TOOLS = [
11329
+ "mcp__deskfree-orchestrator__*",
11330
+ "Read",
11331
+ "Glob",
11332
+ "Grep",
11333
+ "WebSearch",
11334
+ "WebFetch"
11335
+ ];
11286
11336
  var DISALLOWED_BUILTIN_TOOLS = [
11287
11337
  "TodoWrite",
11288
11338
  "AskUserQuestion",
@@ -11299,8 +11349,6 @@ function runOrchestrator(opts) {
11299
11349
  return query({
11300
11350
  prompt,
11301
11351
  options: {
11302
- debug: true,
11303
- debugFile: "/dev/stderr",
11304
11352
  stderr: (data) => {
11305
11353
  process.stderr.write(`[orchestrator-sdk] ${data}
11306
11354
  `);
@@ -11318,18 +11366,7 @@ function runOrchestrator(opts) {
11318
11366
  "deskfree-orchestrator": orchestratorServer
11319
11367
  },
11320
11368
  tools: [],
11321
- allowedTools: [
11322
- "mcp__deskfree-orchestrator__*",
11323
- "Read",
11324
- "Write",
11325
- "Edit",
11326
- "Bash",
11327
- "Glob",
11328
- "Grep",
11329
- "WebSearch",
11330
- "WebFetch",
11331
- "NotebookEdit"
11332
- ],
11369
+ allowedTools: ORCHESTRATOR_ALLOWED_TOOLS,
11333
11370
  disallowedTools: DISALLOWED_BUILTIN_TOOLS
11334
11371
  }
11335
11372
  });
@@ -11350,18 +11387,7 @@ function runHeartbeat(opts) {
11350
11387
  "deskfree-orchestrator": orchestratorServer
11351
11388
  },
11352
11389
  tools: [],
11353
- allowedTools: [
11354
- "mcp__deskfree-orchestrator__*",
11355
- "Read",
11356
- "Write",
11357
- "Edit",
11358
- "Bash",
11359
- "Glob",
11360
- "Grep",
11361
- "WebSearch",
11362
- "WebFetch",
11363
- "NotebookEdit"
11364
- ],
11390
+ allowedTools: ORCHESTRATOR_ALLOWED_TOOLS,
11365
11391
  disallowedTools: DISALLOWED_BUILTIN_TOOLS
11366
11392
  }
11367
11393
  });
@@ -11523,6 +11549,72 @@ function getHealthState() {
11523
11549
  uptimeSeconds: Math.floor((Date.now() - startTime) / 1e3)
11524
11550
  };
11525
11551
  }
11552
+ var LEVELS = {
11553
+ debug: 0,
11554
+ info: 1,
11555
+ warn: 2,
11556
+ error: 3
11557
+ };
11558
+ var MAX_LOG_FILE_BYTES = 5 * 1024 * 1024;
11559
+ var ROTATION_CHECK_INTERVAL = 500;
11560
+ var logFilePath = null;
11561
+ var writesSinceCheck = 0;
11562
+ function enableFileLogging(filePath) {
11563
+ mkdirSync(dirname(filePath), { recursive: true });
11564
+ logFilePath = filePath;
11565
+ }
11566
+ function getLogFilePath() {
11567
+ return logFilePath;
11568
+ }
11569
+ function rotateIfNeeded() {
11570
+ if (!logFilePath) return;
11571
+ try {
11572
+ const stat = statSync(logFilePath);
11573
+ if (stat.size <= MAX_LOG_FILE_BYTES) return;
11574
+ const content = readFileSync(logFilePath, "utf8");
11575
+ const half = Math.floor(content.length / 2);
11576
+ const newlineIdx = content.indexOf("\n", half);
11577
+ const trimmed = newlineIdx >= 0 ? content.slice(newlineIdx + 1) : content.slice(half);
11578
+ writeFileSync(logFilePath, trimmed);
11579
+ } catch {
11580
+ }
11581
+ }
11582
+ function createLogger(component, minLevel = "info") {
11583
+ const minLevelNum = LEVELS[minLevel];
11584
+ function log(level, message, fields) {
11585
+ if (LEVELS[level] < minLevelNum) return;
11586
+ const entry = {
11587
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
11588
+ level,
11589
+ component,
11590
+ msg: message,
11591
+ ...fields
11592
+ };
11593
+ const line = JSON.stringify(entry);
11594
+ if (level === "error" || level === "warn") {
11595
+ process.stderr.write(line + "\n");
11596
+ } else {
11597
+ process.stdout.write(line + "\n");
11598
+ }
11599
+ if (logFilePath) {
11600
+ try {
11601
+ appendFileSync(logFilePath, line + "\n");
11602
+ if (++writesSinceCheck >= ROTATION_CHECK_INTERVAL) {
11603
+ writesSinceCheck = 0;
11604
+ rotateIfNeeded();
11605
+ }
11606
+ } catch {
11607
+ }
11608
+ }
11609
+ }
11610
+ return {
11611
+ debug: (msg, fields) => log("debug", msg, fields),
11612
+ info: (msg, fields) => log("info", msg, fields),
11613
+ warn: (msg, fields) => log("warn", msg, fields),
11614
+ error: (msg, fields) => log("error", msg, fields)
11615
+ };
11616
+ }
11617
+ var logger = createLogger("runtime");
11526
11618
 
11527
11619
  // src/gateway/polling.ts
11528
11620
  var deliveredMessageIds = /* @__PURE__ */ new Set();
@@ -11899,6 +11991,49 @@ async function runWebSocketConnection(opts) {
11899
11991
  cleanup();
11900
11992
  ws.close(1e3, "reload");
11901
11993
  process.kill(process.pid, "SIGTERM");
11994
+ } else if (msg.action === "logs") {
11995
+ const MAX_RESPONSE_BYTES = 12e4;
11996
+ const lineCount = msg.lines ?? 200;
11997
+ try {
11998
+ const logFile = getLogFilePath();
11999
+ if (!logFile) {
12000
+ ws.send(
12001
+ JSON.stringify({
12002
+ action: "logsResponse",
12003
+ lines: [],
12004
+ error: "File logging not enabled"
12005
+ })
12006
+ );
12007
+ } else {
12008
+ const content = readFileSync(logFile, "utf8");
12009
+ const allLines = content.split("\n").filter(Boolean);
12010
+ let tail = allLines.slice(-lineCount);
12011
+ while (tail.length > 1) {
12012
+ const payload = JSON.stringify({
12013
+ action: "logsResponse",
12014
+ lines: tail
12015
+ });
12016
+ if (Buffer.byteLength(payload) <= MAX_RESPONSE_BYTES) break;
12017
+ tail = tail.slice(Math.ceil(tail.length * 0.25));
12018
+ }
12019
+ ws.send(
12020
+ JSON.stringify({
12021
+ action: "logsResponse",
12022
+ lines: tail
12023
+ })
12024
+ );
12025
+ }
12026
+ } catch (err) {
12027
+ const errMsg = err instanceof Error ? err.message : String(err);
12028
+ log.warn(`Failed to read logs: ${errMsg}`);
12029
+ ws.send(
12030
+ JSON.stringify({
12031
+ action: "logsResponse",
12032
+ lines: [],
12033
+ error: errMsg
12034
+ })
12035
+ );
12036
+ }
11902
12037
  }
11903
12038
  } catch (err) {
11904
12039
  const message = err instanceof Error ? err.message : String(err);
@@ -12464,21 +12599,35 @@ function scheduleDailyCycle(label, run, hour, timezone, signal, log) {
12464
12599
  }
12465
12600
  function msUntilNextLocalHour(hour, timezone) {
12466
12601
  const now = Date.now();
12467
- const fmt = new Intl.DateTimeFormat("en-US", {
12602
+ const hourFmt = new Intl.DateTimeFormat("en-US", {
12468
12603
  timeZone: timezone,
12469
12604
  hour: "numeric",
12470
12605
  hour12: false
12471
12606
  });
12607
+ const detailFmt = new Intl.DateTimeFormat("en-US", {
12608
+ timeZone: timezone,
12609
+ minute: "numeric",
12610
+ second: "numeric"
12611
+ });
12472
12612
  for (let offsetMs = 6e4; offsetMs <= 25 * 36e5; offsetMs += 36e5) {
12473
12613
  const candidateMs = now + offsetMs;
12474
- const parts = fmt.formatToParts(new Date(candidateMs));
12614
+ const parts = hourFmt.formatToParts(new Date(candidateMs));
12475
12615
  const hourPart = parts.find((p) => p.type === "hour");
12476
12616
  const candidateHour = parseInt(hourPart?.value ?? "-1", 10);
12477
12617
  if (candidateHour === hour) {
12478
- const candidate = new Date(candidateMs);
12479
- candidate.setUTCMinutes(0, 0, 0);
12480
- const snappedMs = candidate.getTime();
12481
- return Math.max(snappedMs - now, 6e4);
12618
+ const detailParts = detailFmt.formatToParts(new Date(candidateMs));
12619
+ const localMinute = parseInt(
12620
+ detailParts.find((p) => p.type === "minute")?.value ?? "0",
12621
+ 10
12622
+ );
12623
+ const localSecond = parseInt(
12624
+ detailParts.find((p) => p.type === "second")?.value ?? "0",
12625
+ 10
12626
+ );
12627
+ const snappedMs = candidateMs - localMinute * 6e4 - localSecond * 1e3;
12628
+ if (snappedMs > now + 6e4) {
12629
+ return snappedMs - now;
12630
+ }
12482
12631
  }
12483
12632
  }
12484
12633
  return 24 * 36e5;
@@ -12566,40 +12715,6 @@ async function loadToolModules(tools, toolsDir, log) {
12566
12715
  }
12567
12716
  return allTools;
12568
12717
  }
12569
-
12570
- // src/util/logger.ts
12571
- var LEVELS = {
12572
- debug: 0,
12573
- info: 1,
12574
- warn: 2,
12575
- error: 3
12576
- };
12577
- function createLogger(component, minLevel = "info") {
12578
- const minLevelNum = LEVELS[minLevel];
12579
- function log(level, message, fields) {
12580
- if (LEVELS[level] < minLevelNum) return;
12581
- const entry = {
12582
- ts: (/* @__PURE__ */ new Date()).toISOString(),
12583
- level,
12584
- component,
12585
- msg: message,
12586
- ...fields
12587
- };
12588
- const line = JSON.stringify(entry);
12589
- if (level === "error" || level === "warn") {
12590
- process.stderr.write(line + "\n");
12591
- } else {
12592
- process.stdout.write(line + "\n");
12593
- }
12594
- }
12595
- return {
12596
- debug: (msg, fields) => log("debug", msg, fields),
12597
- info: (msg, fields) => log("info", msg, fields),
12598
- warn: (msg, fields) => log("warn", msg, fields),
12599
- error: (msg, fields) => log("error", msg, fields)
12600
- };
12601
- }
12602
- var logger = createLogger("runtime");
12603
12718
  var MAX_MESSAGE_CONTENT_LENGTH = 32 * 1024;
12604
12719
  var MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024;
12605
12720
  var MAX_TOTAL_DOWNLOAD_SIZE = 50 * 1024 * 1024;
@@ -13047,8 +13162,6 @@ function runWorker(opts) {
13047
13162
  return query({
13048
13163
  prompt,
13049
13164
  options: {
13050
- debug: true,
13051
- debugFile: "/dev/stderr",
13052
13165
  stderr: (data) => {
13053
13166
  process.stderr.write(`[worker-sdk] ${data}
13054
13167
  `);
@@ -13426,7 +13539,7 @@ ${userMessage}
13426
13539
  try {
13427
13540
  await client.reportUsage({
13428
13541
  taskId,
13429
- estimatedCost: result.total_cost_usd
13542
+ estimatedCost: Math.round(result.total_cost_usd * 1e6) / 1e6
13430
13543
  });
13431
13544
  } catch {
13432
13545
  log.warn(`Failed to report usage for task ${taskId}`);
@@ -13632,6 +13745,9 @@ async function startAgent(opts) {
13632
13745
  }
13633
13746
  mkdirSync(config.stateDir, { recursive: true });
13634
13747
  mkdirSync(config.toolsDir, { recursive: true });
13748
+ const logFile = join(config.stateDir, "runtime.log");
13749
+ enableFileLogging(logFile);
13750
+ log.info(`File logging enabled: ${logFile}`);
13635
13751
  if (config.tools.length > 0) {
13636
13752
  log.info(`Installing ${config.tools.length} tool package(s)...`);
13637
13753
  await installTools(config.tools, config.toolsDir, log);