@tasksai/install 0.1.12 → 0.1.14
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 +4 -2
- package/package.json +7 -4
- package/runtime/server.py +52 -0
- package/src/index.js +64 -7
- package/runtime/__pycache__/server.cpython-314.pyc +0 -0
package/README.md
CHANGED
|
@@ -5,7 +5,8 @@ Official installer CLI for TasksAI MCP verticals.
|
|
|
5
5
|
Users normally run this through a product-specific GitHub manifest, for example:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm exec --package=@tasksai/install --call 'tasksai-install lawtasksai --source https://github.com/
|
|
8
|
+
npm exec --package=@tasksai/install --call 'tasksai-install lawtasksai --source https://github.com/TasksAI-Official/tasksai-mcp-wrappers/tree/main/verticals/lawtasksai'
|
|
9
|
+
npm exec --package=@tasksai/install --call 'tasksai-install realtor --source https://github.com/TasksAI-Official/tasksai-mcp-wrappers/tree/main/verticals/realtor'
|
|
9
10
|
```
|
|
10
11
|
|
|
11
12
|
In restricted agent environments, grant write access to the default TasksAI
|
|
@@ -13,7 +14,8 @@ application data folder and the selected MCP client config. If the runtime must
|
|
|
13
14
|
be installed elsewhere, pass an exact product install directory:
|
|
14
15
|
|
|
15
16
|
```bash
|
|
16
|
-
npm exec --package=@tasksai/install --call 'tasksai-install farmer --source https://github.com/
|
|
17
|
+
npm exec --package=@tasksai/install --call 'tasksai-install farmer --source https://github.com/TasksAI-Official/tasksai-mcp-wrappers/tree/main/verticals/farmer --install-dir /tmp/tasksai/farmer'
|
|
18
|
+
npm exec --package=@tasksai/install --call 'tasksai-install realtor --source https://github.com/TasksAI-Official/tasksai-mcp-wrappers/tree/main/verticals/realtor --install-dir /tmp/tasksai/realtor'
|
|
17
19
|
```
|
|
18
20
|
|
|
19
21
|
`TASKSAI_INSTALL_DIR=/tmp/tasksai/farmer` is equivalent to `--install-dir`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tasksai/install",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "Shared TasksAI MCP installer CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"src/",
|
|
11
|
-
"runtime/",
|
|
11
|
+
"runtime/server.py",
|
|
12
|
+
"runtime/requirements.txt",
|
|
12
13
|
"README.md"
|
|
13
14
|
],
|
|
14
15
|
"keywords": [
|
|
@@ -17,12 +18,14 @@
|
|
|
17
18
|
"tasksai",
|
|
18
19
|
"lawtasksai",
|
|
19
20
|
"farmertasksai",
|
|
20
|
-
"farmer"
|
|
21
|
+
"farmer",
|
|
22
|
+
"realtortasksai",
|
|
23
|
+
"realtor"
|
|
21
24
|
],
|
|
22
25
|
"license": "UNLICENSED",
|
|
23
26
|
"repository": {
|
|
24
27
|
"type": "git",
|
|
25
|
-
"url": "git+https://github.com/
|
|
28
|
+
"url": "git+https://github.com/TasksAI-Official/tasksai-mcp.git",
|
|
26
29
|
"directory": "packages/installer"
|
|
27
30
|
},
|
|
28
31
|
"publishConfig": {
|
package/runtime/server.py
CHANGED
|
@@ -25,6 +25,7 @@ import time
|
|
|
25
25
|
import asyncio
|
|
26
26
|
import platform
|
|
27
27
|
import httpx
|
|
28
|
+
import uuid
|
|
28
29
|
from datetime import datetime
|
|
29
30
|
from pathlib import Path
|
|
30
31
|
|
|
@@ -86,6 +87,7 @@ else:
|
|
|
86
87
|
API_BASE = os.getenv("TASKSAI_API_BASE", os.getenv("LAWTASKSAI_API_BASE", "https://api.lawtasksai.com"))
|
|
87
88
|
LICENSE_KEY = os.getenv("TASKSAI_LICENSE_KEY", os.getenv("LAWTASKSAI_LICENSE_KEY", ""))
|
|
88
89
|
PRODUCT_ID = os.getenv("TASKSAI_PRODUCT_ID", "") # set by installer; used to resolve correct vertical
|
|
90
|
+
INSTALL_ID = os.getenv("TASKSAI_INSTALL_ID", "")
|
|
89
91
|
|
|
90
92
|
if not LICENSE_KEY:
|
|
91
93
|
print("ERROR: License key is required. Set TASKSAI_LICENSE_KEY in your .env file.", file=sys.stderr, flush=True)
|
|
@@ -473,6 +475,51 @@ async def api_get(path):
|
|
|
473
475
|
return resp.json()
|
|
474
476
|
|
|
475
477
|
|
|
478
|
+
async def api_post(path, payload):
|
|
479
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
480
|
+
resp = await client.post(
|
|
481
|
+
f"{API_BASE}{path}",
|
|
482
|
+
json=payload,
|
|
483
|
+
headers={**AUTH_HEADERS, "X-Product-ID": (_vertical or {}).get("product_id", "law")}
|
|
484
|
+
)
|
|
485
|
+
resp.raise_for_status()
|
|
486
|
+
return resp.json()
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def get_install_id() -> str:
|
|
490
|
+
"""Stable anonymous install id for attribution; stored locally only."""
|
|
491
|
+
global INSTALL_ID
|
|
492
|
+
if INSTALL_ID:
|
|
493
|
+
return INSTALL_ID
|
|
494
|
+
env_path = Path(_dotenv_path) if _dotenv_path else None
|
|
495
|
+
INSTALL_ID = str(uuid.uuid4())
|
|
496
|
+
if env_path:
|
|
497
|
+
try:
|
|
498
|
+
with env_path.open("a", encoding="utf-8") as f:
|
|
499
|
+
f.write(f"\nTASKSAI_INSTALL_ID={INSTALL_ID}\n")
|
|
500
|
+
except Exception:
|
|
501
|
+
pass
|
|
502
|
+
return INSTALL_ID
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
async def report_activation_event(event_name, *, skill_id=None, file_format=None):
|
|
506
|
+
"""Best-effort activation telemetry. Never sends document content or paths."""
|
|
507
|
+
try:
|
|
508
|
+
await api_post("/v1/events/activation", {
|
|
509
|
+
"event_name": event_name,
|
|
510
|
+
"skill_id": skill_id,
|
|
511
|
+
"file_format": file_format,
|
|
512
|
+
"install_id": get_install_id(),
|
|
513
|
+
"metadata": {
|
|
514
|
+
"source": "mcp_runtime",
|
|
515
|
+
"client": "mcp-server",
|
|
516
|
+
"tool_version": SERVER_VERSION,
|
|
517
|
+
},
|
|
518
|
+
})
|
|
519
|
+
except Exception:
|
|
520
|
+
pass
|
|
521
|
+
|
|
522
|
+
|
|
476
523
|
async def load_vertical():
|
|
477
524
|
"""Fetch vertical metadata from /v1/me on startup. Falls back to farmer."""
|
|
478
525
|
global _vertical
|
|
@@ -1270,6 +1317,11 @@ async def call_tool(name, arguments):
|
|
|
1270
1317
|
# ── Save Document ───────────────────────────────────────────────────
|
|
1271
1318
|
elif name == f"{prefix}_save_document":
|
|
1272
1319
|
output_path, fmt = save_document(arguments or {}, product_name)
|
|
1320
|
+
await report_activation_event(
|
|
1321
|
+
"first_file_generated",
|
|
1322
|
+
skill_id=(arguments or {}).get("skill_id"),
|
|
1323
|
+
file_format=fmt,
|
|
1324
|
+
)
|
|
1273
1325
|
return [TextContent(type="text", text=(
|
|
1274
1326
|
"**Document Saved**\n\n"
|
|
1275
1327
|
f"- File: `{output_path}`\n"
|
package/src/index.js
CHANGED
|
@@ -10,13 +10,14 @@ import { spawnSync } from "node:child_process";
|
|
|
10
10
|
import { stdin as input, stdout as output } from "node:process";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
|
|
13
|
-
const INSTALLER_VERSION = "0.1.
|
|
13
|
+
const INSTALLER_VERSION = "0.1.14";
|
|
14
14
|
const INSTALLER_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
15
15
|
const BUNDLED_RUNTIME_DIR = path.resolve(INSTALLER_DIR, "..", "runtime");
|
|
16
16
|
const DEFAULT_SOURCES = {
|
|
17
|
-
lawtasksai: "https://github.com/
|
|
18
|
-
farmer: "https://github.com/
|
|
19
|
-
|
|
17
|
+
lawtasksai: "https://github.com/TasksAI-Official/tasksai-mcp-wrappers/tree/main/verticals/lawtasksai",
|
|
18
|
+
farmer: "https://github.com/TasksAI-Official/tasksai-mcp-wrappers/tree/main/verticals/farmer",
|
|
19
|
+
realtor: "https://github.com/TasksAI-Official/tasksai-mcp-wrappers/tree/main/verticals/realtor",
|
|
20
|
+
teacher: "https://github.com/TasksAI-Official/tasksai-mcp-wrappers/tree/main/verticals/teacher",
|
|
20
21
|
priorauthai: "https://github.com/laudoluxDev/priorauthai-mcp"
|
|
21
22
|
};
|
|
22
23
|
|
|
@@ -147,8 +148,9 @@ function printUsage() {
|
|
|
147
148
|
tasksai-install <product-id> uninstall [--client claude-desktop|cursor|windsurf|codex|all] [--install-dir <path>]
|
|
148
149
|
|
|
149
150
|
Examples:
|
|
150
|
-
tasksai-install lawtasksai --source https://github.com/
|
|
151
|
-
tasksai-install farmer --source https://github.com/
|
|
151
|
+
tasksai-install lawtasksai --source https://github.com/TasksAI-Official/tasksai-mcp-wrappers/tree/main/verticals/lawtasksai
|
|
152
|
+
tasksai-install farmer --source https://github.com/TasksAI-Official/tasksai-mcp-wrappers/tree/main/verticals/farmer
|
|
153
|
+
tasksai-install realtor --source https://github.com/TasksAI-Official/tasksai-mcp-wrappers/tree/main/verticals/realtor
|
|
152
154
|
tasksai-install farmer --install-dir /tmp/tasksai/farmer
|
|
153
155
|
tasksai-install lawtasksai doctor
|
|
154
156
|
|
|
@@ -714,6 +716,12 @@ function verifySource({ options, source, manifest, vertical }) {
|
|
|
714
716
|
if (source.kind === "github" && manifest.official_github_repo !== source.repoUrl) {
|
|
715
717
|
throw new Error(`Source repo ${source.repoUrl} does not match manifest official repo ${manifest.official_github_repo}.`);
|
|
716
718
|
}
|
|
719
|
+
if (source.kind === "github" && manifest.official_manifest_path) {
|
|
720
|
+
const expectedManifestPath = joinGitPath(source.basePath, "agent-install.json");
|
|
721
|
+
if (normalizeGitPath(manifest.official_manifest_path) !== expectedManifestPath) {
|
|
722
|
+
throw new Error(`Source manifest ${expectedManifestPath} does not match manifest official path ${manifest.official_manifest_path}.`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
717
725
|
if (!manifest.official_domain || !vertical.website_url?.includes(manifest.official_domain)) {
|
|
718
726
|
throw new Error("Manifest official domain does not match vertical website URL.");
|
|
719
727
|
}
|
|
@@ -740,6 +748,37 @@ function parseSource(source, ref) {
|
|
|
740
748
|
if (source.startsWith("/") || source.startsWith(".")) {
|
|
741
749
|
return { kind: "file", root: path.resolve(source), ref, repoUrl: source };
|
|
742
750
|
}
|
|
751
|
+
const rawMatch = source.match(/^https:\/\/raw\.githubusercontent\.com\/([^/]+)\/([^/]+)\/([^/]+)\/(.+)$/);
|
|
752
|
+
if (rawMatch) {
|
|
753
|
+
const [, owner, repo, rawRef, rawPath] = rawMatch;
|
|
754
|
+
const manifestPath = normalizeGitPath(rawPath);
|
|
755
|
+
const basePath = manifestPath.endsWith("/agent-install.json")
|
|
756
|
+
? manifestPath.slice(0, -"/agent-install.json".length)
|
|
757
|
+
: path.posix.dirname(manifestPath);
|
|
758
|
+
return {
|
|
759
|
+
kind: "github",
|
|
760
|
+
owner,
|
|
761
|
+
repo,
|
|
762
|
+
ref: rawRef,
|
|
763
|
+
basePath: basePath === "." ? "" : basePath,
|
|
764
|
+
repoUrl: `https://github.com/${owner}/${repo}`
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
const treeMatch = source.match(/^https:\/\/github\.com\/([^/]+)\/([^/#?]+)\/(?:tree|blob)\/([^/]+)(?:\/([^#?]+))?(?:[?#].*)?$/);
|
|
768
|
+
if (treeMatch) {
|
|
769
|
+
const [, owner, repo, treeRef, treePath = ""] = treeMatch;
|
|
770
|
+
const normalizedPath = normalizeGitPath(treePath);
|
|
771
|
+
return {
|
|
772
|
+
kind: "github",
|
|
773
|
+
owner,
|
|
774
|
+
repo,
|
|
775
|
+
ref: treeRef,
|
|
776
|
+
basePath: normalizedPath.endsWith("/agent-install.json")
|
|
777
|
+
? normalizedPath.slice(0, -"/agent-install.json".length)
|
|
778
|
+
: normalizedPath,
|
|
779
|
+
repoUrl: `https://github.com/${owner}/${repo}`
|
|
780
|
+
};
|
|
781
|
+
}
|
|
743
782
|
const match = source.match(/^https:\/\/github\.com\/([^/]+)\/([^/#?]+)(?:[/?#].*)?$/);
|
|
744
783
|
if (!match) throw new Error(`Unsupported source URL: ${source}`);
|
|
745
784
|
const [, owner, repo] = match;
|
|
@@ -748,6 +787,7 @@ function parseSource(source, ref) {
|
|
|
748
787
|
owner,
|
|
749
788
|
repo,
|
|
750
789
|
ref,
|
|
790
|
+
basePath: "",
|
|
751
791
|
repoUrl: `https://github.com/${owner}/${repo}`
|
|
752
792
|
};
|
|
753
793
|
}
|
|
@@ -760,10 +800,27 @@ async function loadText(source, filePath) {
|
|
|
760
800
|
if (source.kind === "file") {
|
|
761
801
|
return fsp.readFile(path.join(source.root, filePath), "utf8");
|
|
762
802
|
}
|
|
763
|
-
const
|
|
803
|
+
const sourcePath = joinGitPath(source.basePath, filePath);
|
|
804
|
+
const rawUrl = `https://raw.githubusercontent.com/${source.owner}/${source.repo}/${encodeURIComponent(source.ref)}/${encodeGitPath(sourcePath)}`;
|
|
764
805
|
return fetchText(rawUrl);
|
|
765
806
|
}
|
|
766
807
|
|
|
808
|
+
function normalizeGitPath(value = "") {
|
|
809
|
+
return value
|
|
810
|
+
.split("/")
|
|
811
|
+
.filter(Boolean)
|
|
812
|
+
.map((part) => decodeURIComponent(part))
|
|
813
|
+
.join("/");
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function joinGitPath(...parts) {
|
|
817
|
+
return normalizeGitPath(parts.filter(Boolean).join("/"));
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function encodeGitPath(value) {
|
|
821
|
+
return normalizeGitPath(value).split("/").map(encodeURIComponent).join("/");
|
|
822
|
+
}
|
|
823
|
+
|
|
767
824
|
function fetchText(url) {
|
|
768
825
|
return new Promise((resolve, reject) => {
|
|
769
826
|
https.get(url, { headers: { "User-Agent": `tasksai-install/${INSTALLER_VERSION}` } }, (response) => {
|
|
Binary file
|