@rubytech/taskmaster 1.0.30 → 1.0.31

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,5 +1,5 @@
1
1
  {
2
- "version": "1.0.30",
3
- "commit": "4f7f38c94f22176628825f47bac0d89dee30ac36",
4
- "builtAt": "2026-02-16T08:55:07.085Z"
2
+ "version": "1.0.31",
3
+ "commit": "ad839a16bacf9dad316897cb42b145ce210aba2e",
4
+ "builtAt": "2026-02-16T09:11:05.952Z"
5
5
  }
@@ -64,13 +64,14 @@ export function registerMaintenanceCommands(program) {
64
64
  });
65
65
  program
66
66
  .command("uninstall")
67
- .description("Uninstall the gateway service + local data (CLI remains)")
67
+ .description("Uninstall the gateway service + local data (CLI remains unless --purge)")
68
68
  .addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/uninstall", "docs.taskmaster.bot/cli/uninstall")}\n`)
69
69
  .option("--service", "Remove the gateway service", false)
70
70
  .option("--state", "Remove state + config", false)
71
71
  .option("--workspace", "Remove workspace dirs", false)
72
72
  .option("--app", "Remove the macOS app", false)
73
73
  .option("--all", "Remove service + state + workspace + app", false)
74
+ .option("--purge", "Remove everything including the npm package itself", false)
74
75
  .option("--yes", "Skip confirmation prompts", false)
75
76
  .option("--non-interactive", "Disable prompts (requires --yes)", false)
76
77
  .option("--dry-run", "Print actions without removing files", false)
@@ -82,6 +83,7 @@ export function registerMaintenanceCommands(program) {
82
83
  workspace: Boolean(opts.workspace),
83
84
  app: Boolean(opts.app),
84
85
  all: Boolean(opts.all),
86
+ purge: Boolean(opts.purge),
85
87
  yes: Boolean(opts.yes),
86
88
  nonInteractive: Boolean(opts.nonInteractive),
87
89
  dryRun: Boolean(opts.dryRun),
@@ -1,27 +1,31 @@
1
+ import { execFile } from "node:child_process";
2
+ import fs from "node:fs/promises";
1
3
  import path from "node:path";
4
+ import { promisify } from "node:util";
2
5
  import { cancel, confirm, isCancel, multiselect } from "@clack/prompts";
3
6
  import { isNixMode, loadConfig, resolveConfigPath, resolveOAuthDir, resolveStateDir, } from "../config/config.js";
4
7
  import { resolveGatewayService } from "../daemon/service.js";
5
8
  import { stylePromptHint, stylePromptMessage, stylePromptTitle } from "../terminal/prompt-style.js";
6
9
  import { resolveHomeDir } from "../utils.js";
7
10
  import { collectWorkspaceDirs, isPathWithin, removePath } from "./cleanup-utils.js";
11
+ const execFileAsync = promisify(execFile);
8
12
  const multiselectStyled = (params) => multiselect({
9
13
  ...params,
10
14
  message: stylePromptMessage(params.message),
11
15
  options: params.options.map((opt) => opt.hint === undefined ? opt : { ...opt, hint: stylePromptHint(opt.hint) }),
12
16
  });
13
17
  function buildScopeSelection(opts) {
14
- const hadExplicit = Boolean(opts.all || opts.service || opts.state || opts.workspace || opts.app);
18
+ const hadExplicit = Boolean(opts.all || opts.purge || opts.service || opts.state || opts.workspace || opts.app);
15
19
  const scopes = new Set();
16
- if (opts.all || opts.service)
20
+ if (opts.all || opts.purge || opts.service)
17
21
  scopes.add("service");
18
- if (opts.all || opts.state)
22
+ if (opts.all || opts.purge || opts.state)
19
23
  scopes.add("state");
20
- if (opts.all || opts.workspace)
24
+ if (opts.all || opts.purge || opts.workspace)
21
25
  scopes.add("workspace");
22
26
  if (opts.all || opts.app)
23
27
  scopes.add("app");
24
- return { scopes, hadExplicit };
28
+ return { scopes, hadExplicit, purge: Boolean(opts.purge) };
25
29
  }
26
30
  async function stopAndUninstallService(runtime) {
27
31
  if (isNixMode) {
@@ -64,8 +68,66 @@ async function removeMacApp(runtime, dryRun) {
64
68
  label: "/Applications/Taskmaster.app",
65
69
  });
66
70
  }
71
+ async function removeLegacyAvahiService(runtime, dryRun) {
72
+ if (process.platform !== "linux")
73
+ return;
74
+ const avahiPath = "/etc/avahi/services/taskmaster.service";
75
+ try {
76
+ await fs.access(avahiPath);
77
+ }
78
+ catch {
79
+ return; // doesn't exist
80
+ }
81
+ if (dryRun) {
82
+ runtime.log(`[dry-run] remove ${avahiPath}`);
83
+ return;
84
+ }
85
+ try {
86
+ await fs.rm(avahiPath, { force: true });
87
+ runtime.log(`Removed ${avahiPath}`);
88
+ try {
89
+ await execFileAsync("systemctl", ["restart", "avahi-daemon"]);
90
+ }
91
+ catch {
92
+ // avahi restart is best-effort
93
+ }
94
+ }
95
+ catch {
96
+ runtime.log(`Could not remove ${avahiPath} (may need sudo).`);
97
+ }
98
+ }
99
+ async function removeRootStateDirs(runtime, dryRun) {
100
+ if (process.platform !== "linux")
101
+ return;
102
+ const dirs = ["/root/.taskmaster", "/root/taskmaster"];
103
+ for (const dir of dirs) {
104
+ try {
105
+ await fs.access(dir);
106
+ }
107
+ catch {
108
+ continue; // doesn't exist
109
+ }
110
+ await removePath(dir, runtime, { dryRun, label: dir });
111
+ }
112
+ }
113
+ async function removeNpmPackage(runtime, dryRun) {
114
+ if (dryRun) {
115
+ runtime.log("[dry-run] npm uninstall -g @rubytech/taskmaster");
116
+ return;
117
+ }
118
+ try {
119
+ await execFileAsync("npm", ["uninstall", "-g", "@rubytech/taskmaster"], {
120
+ timeout: 30_000,
121
+ });
122
+ runtime.log("Removed @rubytech/taskmaster global package.");
123
+ }
124
+ catch {
125
+ runtime.log("Could not remove the npm package (likely needs elevated permissions).\n" +
126
+ "Run manually: sudo npm uninstall -g @rubytech/taskmaster");
127
+ }
128
+ }
67
129
  export async function uninstallCommand(runtime, opts) {
68
- const { scopes, hadExplicit } = buildScopeSelection(opts);
130
+ const { scopes, hadExplicit, purge } = buildScopeSelection(opts);
69
131
  const interactive = !opts.nonInteractive;
70
132
  if (!interactive && !opts.yes) {
71
133
  runtime.error("Non-interactive mode requires --yes.");
@@ -151,8 +213,15 @@ export async function uninstallCommand(runtime, opts) {
151
213
  if (scopes.has("app")) {
152
214
  await removeMacApp(runtime, dryRun);
153
215
  }
154
- runtime.log("CLI still installed. Remove via npm/pnpm if desired.");
155
- if (scopes.has("state") && !scopes.has("workspace")) {
216
+ if (purge) {
217
+ await removeLegacyAvahiService(runtime, dryRun);
218
+ await removeRootStateDirs(runtime, dryRun);
219
+ await removeNpmPackage(runtime, dryRun);
220
+ }
221
+ else {
222
+ runtime.log("CLI still installed. Remove via npm/pnpm if desired.");
223
+ }
224
+ if (!purge && scopes.has("state") && !scopes.has("workspace")) {
156
225
  const home = resolveHomeDir();
157
226
  if (home && workspaceDirs.some((dir) => dir.startsWith(path.resolve(home)))) {
158
227
  runtime.log("Tip: workspaces were preserved. Re-run with --workspace to remove them.");