@tasksai/install 0.1.4 → 0.1.5
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/README.md +2 -0
- package/package.json +1 -1
- package/src/index.js +59 -10
package/README.md
CHANGED
|
@@ -28,6 +28,8 @@ The installer:
|
|
|
28
28
|
- stores credentials outside MCP client config
|
|
29
29
|
- configures supported MCP clients
|
|
30
30
|
- checks required write access before changing local files
|
|
31
|
+
- prints customer-facing recovery instructions when an AI assistant is blocked
|
|
32
|
+
from writing local config files
|
|
31
33
|
- supports Claude Desktop, Cursor, Windsurf, and Codex
|
|
32
34
|
- runs a local health check
|
|
33
35
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import readline from "node:readline/promises";
|
|
|
9
9
|
import { spawnSync } from "node:child_process";
|
|
10
10
|
import { stdin as input, stdout as output } from "node:process";
|
|
11
11
|
|
|
12
|
-
const INSTALLER_VERSION = "0.1.
|
|
12
|
+
const INSTALLER_VERSION = "0.1.5";
|
|
13
13
|
const DEFAULT_SOURCES = {
|
|
14
14
|
lawtasksai: "https://github.com/laudoluxDev/lawtasksai-mcp",
|
|
15
15
|
farmer: "https://github.com/laudoluxDev/farmertasksai-mcp",
|
|
@@ -163,7 +163,7 @@ async function install(options, { updateOnly = false } = {}) {
|
|
|
163
163
|
const runtimeDir = path.join(installDir, "runtime");
|
|
164
164
|
const vendorDir = path.join(installDir, "python");
|
|
165
165
|
const clients = updateOnly ? [] : resolveClients(options.client);
|
|
166
|
-
await preflightWriteAccess({ operation: updateOnly ? "update" : "install", installDir, clients });
|
|
166
|
+
await preflightWriteAccess({ operation: updateOnly ? "update" : "install", installDir, clients, options, vertical, source });
|
|
167
167
|
|
|
168
168
|
await fsp.mkdir(runtimeDir, { recursive: true });
|
|
169
169
|
await fsp.mkdir(path.join(installDir, "logs"), { recursive: true });
|
|
@@ -251,7 +251,7 @@ async function doctor(options, { quietSuccess = false } = {}) {
|
|
|
251
251
|
async function uninstall(options) {
|
|
252
252
|
const installDir = getInstallDir(options.productId, options);
|
|
253
253
|
const clients = resolveClients(options.client);
|
|
254
|
-
await preflightWriteAccess({ operation: "uninstall", installDir, clients });
|
|
254
|
+
await preflightWriteAccess({ operation: "uninstall", installDir, clients, options });
|
|
255
255
|
|
|
256
256
|
for (const client of clients) {
|
|
257
257
|
const configPath = client.configPath();
|
|
@@ -407,7 +407,7 @@ function resolveClients(clientOption) {
|
|
|
407
407
|
return [client];
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
-
async function preflightWriteAccess({ operation, installDir, clients }) {
|
|
410
|
+
async function preflightWriteAccess({ operation, installDir, clients, options, vertical, source }) {
|
|
411
411
|
const checks = [
|
|
412
412
|
{
|
|
413
413
|
label: "TasksAI install directory",
|
|
@@ -440,13 +440,62 @@ async function preflightWriteAccess({ operation, installDir, clients }) {
|
|
|
440
440
|
|
|
441
441
|
if (!failures.length) return;
|
|
442
442
|
|
|
443
|
-
|
|
443
|
+
throw new Error(formatPermissionRecovery({ operation, failures, options, vertical, source }));
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function formatPermissionRecovery({ operation, failures, options, vertical, source }) {
|
|
447
|
+
const productName = vertical?.display_name || options.productId;
|
|
448
|
+
const clientNames = resolveClients(options.client).map((client) => client.displayName).join(", ");
|
|
449
|
+
const blockedPaths = failures
|
|
444
450
|
.map((failure) => `- ${failure.label}: ${failure.targetPath} (${failure.reason})`)
|
|
445
451
|
.join("\n");
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
);
|
|
452
|
+
const installCommand = buildInstallCommand({ options, source });
|
|
453
|
+
const doctorCommand = buildDoctorCommand(options);
|
|
454
|
+
const installPage = vertical?.website_url ? `${vertical.website_url.replace(/\/$/, "")}/install.html` : null;
|
|
455
|
+
const supportPage = vertical?.website_url ? `${vertical.website_url.replace(/\/$/, "")}/support.html` : null;
|
|
456
|
+
|
|
457
|
+
return [
|
|
458
|
+
`Your AI assistant could not finish the ${productName} ${operation} because it cannot write to required local setup files.`,
|
|
459
|
+
"",
|
|
460
|
+
"This is a normal security restriction in some AI apps and sandboxed environments. No account data was changed.",
|
|
461
|
+
"",
|
|
462
|
+
"Blocked paths:",
|
|
463
|
+
blockedPaths,
|
|
464
|
+
"",
|
|
465
|
+
"To finish setup, run this official installer command directly in Terminal:",
|
|
466
|
+
installCommand,
|
|
467
|
+
"",
|
|
468
|
+
"Then restart your AI app and run this health check:",
|
|
469
|
+
doctorCommand,
|
|
470
|
+
"",
|
|
471
|
+
`Selected client: ${clientNames}`,
|
|
472
|
+
installPage ? `Install help: ${installPage}` : null,
|
|
473
|
+
supportPage ? `Support: ${supportPage}` : null,
|
|
474
|
+
"",
|
|
475
|
+
"If you still want the AI assistant to finish automatically, grant it write access to the blocked paths and rerun the same install prompt."
|
|
476
|
+
].filter(Boolean).join("\n");
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function buildInstallCommand({ options, source }) {
|
|
480
|
+
const parts = ["npx", "@tasksai/install", options.productId];
|
|
481
|
+
const sourceUrl = source?.repoUrl || options.source;
|
|
482
|
+
if (sourceUrl) parts.push("--source", sourceUrl);
|
|
483
|
+
if (options.client) parts.push("--client", options.client);
|
|
484
|
+
if (options.installDir) parts.push("--install-dir", resolveUserPath(options.installDir));
|
|
485
|
+
return parts.map(shellToken).join(" ");
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function buildDoctorCommand(options) {
|
|
489
|
+
const parts = ["npx", "@tasksai/install", options.productId, "doctor"];
|
|
490
|
+
if (options.client) parts.push("--client", options.client);
|
|
491
|
+
if (options.installDir) parts.push("--install-dir", resolveUserPath(options.installDir));
|
|
492
|
+
return parts.map(shellToken).join(" ");
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function shellToken(value) {
|
|
496
|
+
const text = String(value);
|
|
497
|
+
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(text)) return text;
|
|
498
|
+
return `'${text.replace(/'/g, "'\\''")}'`;
|
|
450
499
|
}
|
|
451
500
|
|
|
452
501
|
async function checkWritable({ label, targetPath, kind }) {
|
|
@@ -848,7 +897,7 @@ function formatError(error) {
|
|
|
848
897
|
|
|
849
898
|
function redact(text) {
|
|
850
899
|
return String(text)
|
|
851
|
-
.replace(/\b
|
|
900
|
+
.replace(/\b(?:lt|ft|pa|rt|tt|th|mt|ct|at|ch|cu|dt|ds|el|ep|fu|hr|in|ll|ms|mo|mu|nt|pt|pp|pl|pr|re|sl|ta|vt)_[A-Za-z0-9_-]{8,}\b/g, (match) => `${match.split("_")[0]}_[REDACTED]`)
|
|
852
901
|
.replace(/Bearer\s+[A-Za-z0-9._-]+/gi, "Bearer [REDACTED]")
|
|
853
902
|
.replace(/gh[opsu]_[A-Za-z0-9_]+/g, "gh_[REDACTED]");
|
|
854
903
|
}
|