@rubytech/taskmaster 1.9.5 → 1.9.6

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.9.5",
3
- "commit": "521c1bca1da1357a98d5afc5f71aafcf2a5831b8",
4
- "builtAt": "2026-02-28T15:36:00.493Z"
2
+ "version": "1.9.6",
3
+ "commit": "ed36605579b1ba21fa2309986f83cae397e7206a",
4
+ "builtAt": "2026-02-28T15:54:48.228Z"
5
5
  }
@@ -122,6 +122,29 @@ export const tailscaleHandlers = {
122
122
  message: typeof errObj.message === "string" ? errObj.message : "",
123
123
  };
124
124
  };
125
+ const needsSudo = (output) => {
126
+ const lower = output.toLowerCase();
127
+ return (lower.includes("access denied") ||
128
+ lower.includes("permission denied") ||
129
+ lower.includes("use 'sudo tailscale") ||
130
+ lower.includes("requires root"));
131
+ };
132
+ // Run `tailscale up` (with optional extra args) and capture output.
133
+ // If the command fails with a permission error, retries with `sudo -n`.
134
+ // `tailscale up` blocks until auth completes, so timeouts are expected —
135
+ // the auth URL appears in stdout/stderr before the timeout kills it.
136
+ const runTailscaleUp = async (binary, extraArgs = []) => {
137
+ const args = ["up", ...extraArgs];
138
+ const execOpts = { timeoutMs: 60_000, maxBuffer: 100_000 };
139
+ const result = await runExec(binary, args, execOpts).catch((err) => captureExecOutput(err));
140
+ const output = `${result.stdout}\n${result.stderr}`;
141
+ if (!needsSudo(output))
142
+ return output;
143
+ // Permission denied — retry with sudo -n (non-interactive)
144
+ context.logGateway.info(`tailscale.enable: permission denied, retrying with sudo: ${args.join(" ")}`);
145
+ const sudoResult = await runExec("sudo", ["-n", binary, ...args], execOpts).catch((err) => captureExecOutput(err));
146
+ return `${sudoResult.stdout}\n${sudoResult.stderr}`;
147
+ };
125
148
  try {
126
149
  const binary = await findTailscaleBinary();
127
150
  if (!binary) {
@@ -149,20 +172,7 @@ export const tailscaleHandlers = {
149
172
  // Other status errors (e.g. NeedsLogin) are fine — continue to `tailscale up`
150
173
  }
151
174
  // ── Attempt 1: `tailscale up` ──
152
- // On a headless device this prints:
153
- // "To authenticate, visit:\n\n\thttps://login.tailscale.com/a/..."
154
- // The command blocks until auth completes, so we run it with a
155
- // timeout and parse the auth URL from the output. The URL appears
156
- // within seconds; the timeout just kills the blocking wait.
157
- const child = await runExec(binary, ["up"], {
158
- timeoutMs: 60_000,
159
- maxBuffer: 100_000,
160
- }).catch((err) => {
161
- // `tailscale up` exits non-zero while waiting for auth but still
162
- // prints the URL to stdout/stderr before the timeout kills it.
163
- return captureExecOutput(err);
164
- });
165
- const combined = `${child.stdout}\n${child.stderr}`;
175
+ const combined = await runTailscaleUp(binary);
166
176
  const authUrl = extractAuthUrl(combined);
167
177
  if (authUrl) {
168
178
  respond(true, { authUrl });
@@ -184,11 +194,7 @@ export const tailscaleHandlers = {
184
194
  // state where plain `tailscale up` doesn't produce a new auth URL.
185
195
  // --force-reauth forces a fresh login flow that always emits a URL.
186
196
  context.logGateway.info("tailscale.enable: no auth URL from initial attempt, retrying with --force-reauth");
187
- const retry = await runExec(binary, ["up", "--force-reauth"], {
188
- timeoutMs: 60_000,
189
- maxBuffer: 100_000,
190
- }).catch((err) => captureExecOutput(err));
191
- const retryCombined = `${retry.stdout}\n${retry.stderr}`;
197
+ const retryCombined = await runTailscaleUp(binary, ["--force-reauth"]);
192
198
  const retryUrl = extractAuthUrl(retryCombined);
193
199
  if (retryUrl) {
194
200
  respond(true, { authUrl: retryUrl });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.9.5",
3
+ "version": "1.9.6",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"