Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/publish-release-github.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ jobs:
fi
printf 'NPM_DIST_TAG=%s\n' "$tag" >> "$GITHUB_ENV"

- name: Install Mise
uses: threatmap/action-mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0
with:
version: ${{ vars.CI__MISE_VERSION }}
experimental: true # Necessary for custom backend plugins

- name: Install lockfile-pinned dependencies
run: pnpm install --frozen-lockfile

Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ jobs:
test "$(git rev-parse HEAD)" = "$tag_sha"
test -z "$(git branch --show-current)"

- name: Install Mise
uses: threatmap/action-mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0
with:
version: ${{ vars.CI__MISE_VERSION }}
experimental: true # Necessary for custom backend plugins

- name: Install lockfile-pinned dependencies
run: pnpm install --frozen-lockfile

Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/sdk-client-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ jobs:
with:
persist-credentials: false

- name: Install Mise
uses: threatmap/action-mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0
with:
version: ${{ vars.CI__MISE_VERSION }}
experimental: true # Necessary for custom backend plugins

- name: Install dependencies
run: pnpm install

Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ jobs:
with:
persist-credentials: false

- name: Install Mise
uses: jdx/mise-action@2d8d4cafcbd33be2ea37d2b6f5ad595363d1f1ca # v5.1.1
with:
experimental: true # Necessary for custom backend plugins
# This repo is itself the toolshed plugin; cached plugin clones go stale. Tool installs are fast (~5s), so skip the cache.
cache: false

- name: Install dependencies
run: pnpm install

Expand Down
2 changes: 1 addition & 1 deletion .mise/config.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[plugins]
toolshed = "https://github.com/threatcode/mise-toolshed"
toolshed = "https://github.com/threatcode/threatmap"

[tools]
node = "24.11.1"
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ This is the monorepo for all the JS SDK and related tooling of Threatmap. It con
- `quickjs-types`: Typing for the QuickJS Engine
- `server-auth`: Client to authenticate with a Threatmap Instance

## Creating a new plugin

Run `pnpm --filter @threatmap/create-plugin build` once, then:

```sh
node tools/create-plugin/dist/index.mjs
```

and follow the prompts. The scaffolder creates a plugin package (by default under `packages/plugins/`) matching the monorepo conventions.

## Release automation

SDK releases are not published from branch pushes. The three product-coupled packages (sdk-backend, sdk-frontend, sdk-workflow) are versioned together on `release/vX.Y.Z` branches. Publishing an immutable `vX.Y.Z[-rc.N]` GitHub Release triggers separate npm and GitHub Packages workflows. Both publishers require the release branch tip to match the tag; wait for both to finish before advancing that branch.
Expand Down
129 changes: 129 additions & 0 deletions bin/get-skills
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/usr/bin/env sh
# Clone or update ai-ops, then sync skills/ under the destination root (default: $PWD).
# Copy semantics: each destination skill dir is replaced to match upstream (removed skills disappear locally).

set -eu

REPO_URL=""
DEST_ROOT=""

while [ $# -gt 0 ]; do
case "$1" in
--url)
REPO_URL="${2:-}"
shift 2
;;
--destination)
DEST_ROOT="${2:-}"
shift 2
;;
*)
echo "toolshed:get-skills: unknown argument: $1" >&2
exit 1
;;
esac
done

REPO_URL="${REPO_URL:-ssh://git@github.com/threatcode/ai-ops.git}"
if [ -z "${DEST_ROOT}" ]; then
DEST_ROOT="${SKILLS_DESTINATION:-$PWD}"
fi

DEST_ROOT="$(CDPATH= cd -- "$DEST_ROOT" && pwd)"

if ! command -v git >/dev/null 2>&1; then
echo "toolshed:get-skills: git is required but was not found on PATH" >&2
exit 1
fi

CACHE="${AI_OPS_SKILLS_CACHE:-${XDG_CACHE_HOME:-$HOME/.cache}/mise-toolshed/ai-ops}"
REF="${AI_OPS_SKILLS_REF:-}"

ensure_repo() {
if [ -d "$CACHE/.git" ]; then
return 0
fi
_parent="$(dirname "$CACHE")"
mkdir -p "$_parent"
echo "toolshed:get-skills: cloning $REPO_URL into $CACHE" >&2
echo "toolshed:get-skills: first run downloads the repo; --progress shows activity (large monorepos can take minutes)." >&2
# Shallow + single default branch: much faster than a full clone. --progress prints receiving/compressing lines.
# No GIT_TERMINAL_PROMPT=0 here so HTTPS/SSH can prompt for credentials if needed (avoids some silent stalls).
git clone --progress --depth 1 --single-branch "$REPO_URL" "$CACHE"
}

sync_ref() {
_target="$1"
if [ "$_target" = "latest" ]; then
git -C "$CACHE" fetch origin
if git -C "$CACHE" rev-parse --verify origin/main >/dev/null 2>&1; then
git -C "$CACHE" checkout -B main origin/main
elif git -C "$CACHE" rev-parse --verify origin/master >/dev/null 2>&1; then
git -C "$CACHE" checkout -B master origin/master
else
echo "toolshed:get-skills: neither origin/main nor origin/master exists after fetch" >&2
exit 1
fi
return 0
fi

git -C "$CACHE" fetch origin --tags --prune 2>/dev/null || git -C "$CACHE" fetch origin 2>/dev/null || true

if git -C "$CACHE" rev-parse --verify "$_target^{commit}" >/dev/null 2>&1; then
git -C "$CACHE" checkout "$_target"
return 0
fi

echo "toolshed:get-skills: ref $_target not found in ai-ops, using default branch" >&2
sync_ref latest
}

ensure_repo

# Non-interactive fetch/checkout: fail fast instead of hanging on credential prompts in CI.
export GIT_TERMINAL_PROMPT=0

if [ -n "$REF" ]; then
sync_ref "$REF"
else
sync_ref latest
fi

SRC="$CACHE/skills"
if [ ! -d "$SRC" ]; then
echo "toolshed:get-skills: missing $SRC after checkout (is the repo OK?)" >&2
exit 1
fi

sync_one() {
_dest="$1"
mkdir -p "$_dest"
if command -v rsync >/dev/null 2>&1; then
rsync -a --delete "$SRC"/ "$_dest"/
return
fi
for _d in "$SRC"/*; do
[ -e "$_d" ] || continue
if [ ! -d "$_d" ]; then
continue
fi
_base="$(basename "$_d")"
rm -rf "$_dest/$_base"
cp -R "$_d" "$_dest/$_base"
done
if [ -d "$_dest" ]; then
for _existing in "$_dest"/*; do
[ -e "$_existing" ] || continue
_base="$(basename "$_existing")"
if [ ! -d "$SRC/$_base" ]; then
rm -rf "$_existing"
fi
done
fi
}

sync_one "$DEST_ROOT/.claude/skills"
sync_one "$DEST_ROOT/.agents/skills"
sync_one "$DEST_ROOT/.cursor/skills"

echo "toolshed:get-skills: synced from $SRC to .claude/skills, .agents/skills, .cursor/skills (under $DEST_ROOT)" >&2
17 changes: 17 additions & 0 deletions hooks/backend_exec_env.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--- Prepends install bin to PATH.
--- Both tools get the same treatment (gh-aw doesn't install a binary but this is harmless).
function PLUGIN:BackendExecEnv(ctx)
local file = require("file")

local tool = ctx.tool
if tool ~= "get-skills" and tool ~= "gh-aw" then
error("unknown tool: " .. tostring(tool) .. " (only 'get-skills' or 'gh-aw' is supported)")
end

local bin_path = file.join_path(ctx.install_path, "bin")
return {
env_vars = {
{ key = "PATH", value = bin_path },
},
}
end
60 changes: 60 additions & 0 deletions hooks/backend_install.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
local function process_gh_aw(ctx)
local version = ctx.version or ""
if version == "" or version == "latest" then
error("toolshed:gh-aw requires an explicit version tag (e.g. v0.2.0); 'latest' is not supported")
end

local cmd = require("cmd")
cmd.exec("command -v gh >/dev/null 2>&1")

-- `gh extension install` fails if already installed; remove first for idempotency.
cmd.exec("gh extension remove github/gh-aw >/dev/null 2>&1 || true")
cmd.exec('gh extension install "github/gh-aw" --pin ' .. (version:match("^v") and version or "v" .. version))

return {}
end

local function process_get_skills(ctx)
local cmd = require("cmd")
local file = require("file")

local install_path = ctx.install_path
if not install_path or install_path == "" then
error("install_path cannot be empty")
end

local bin_src = file.join_path(RUNTIME.pluginDirPath, "bin", "get-skills")
if not file.exists(bin_src) then
error("plugin is missing bin/get-skills at " .. bin_src)
end

local bin_dir = file.join_path(install_path, "bin")
cmd.exec("mkdir -p " .. '"' .. bin_dir:gsub('"', '\\"') .. '"')

local bin_dst = file.join_path(bin_dir, "get-skills")
local out, open_err = io.open(bin_dst, "wb")
if not out then
error("failed to write " .. bin_dst .. ": " .. tostring(open_err))
end
out:write(file.read(bin_src))
out:close()

if RUNTIME.osType ~= "windows" then
cmd.exec('chmod +x "' .. bin_dst:gsub('"', '\\"') .. '"')
end

return {}
end

--- Installs the requested tool.
function PLUGIN:BackendInstall(ctx)
local tool = ctx.tool
if tool == "gh-aw" then
return process_gh_aw(ctx)
end
if tool == "get-skills" then
return process_get_skills(ctx)
end

error("unknown tool: " .. tostring(tool) .. " (only 'get-skills' or 'gh-aw' is supported)")
end
33 changes: 33 additions & 0 deletions hooks/backend_list_versions.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
local function process_gh_aw()
local versions = {}

local p = io.popen("git ls-remote --tags --refs https://github.com/github/gh-aw.git 2>/dev/null")
if p then
for line in p:lines() do
local ref = line:match("%s+refs/tags/(.+)$")
if ref and ref ~= "" then
table.insert(versions, ref)
end
end
p:close()
end

return { versions = versions }
end

local function process_get_skills()
return { versions = { "latest" } }
end

--- Returns installable versions for the requested tool.
function PLUGIN:BackendListVersions(ctx)
local tool = ctx.tool
if tool == "gh-aw" then
return process_gh_aw()
end
if tool == "get-skills" then
return process_get_skills()
end

error("unknown tool: " .. tostring(tool) .. " (only 'get-skills' or 'gh-aw' is supported)")
end
1 change: 1 addition & 0 deletions knip.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const config: KnipConfig = {
ignore: [
"scripts/**",
"package.json",
"tools/create-plugin/templates/**",
".github/**",
"**/typedoc.json",
"packages/sdk/sdk-frontend/**",
Expand Down
6 changes: 6 additions & 0 deletions metadata.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
PLUGIN = {
name = "toolshed",
version = "1.0.0",
description = "Backend plugin for ThreatMap toolshed tools (get-skills, gh-aw)",
author = "ThreatCode",
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"lint": "eslint \"packages/**/src/**/*.{ts,vue,js}\" --fix",
"typecheck": "pnpm -r typecheck",
"knip": "knip",
"create-plugin": "node tools/create-plugin/dist/index.mjs",
"generate:doc": "pnpm -r generate:doc"
},
"engines": {
Expand Down
3 changes: 3 additions & 0 deletions packages/plugins/chatio/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @threatmap/chatio

Specification for the ThreatMap Chatio plugin.
33 changes: 33 additions & 0 deletions packages/plugins/chatio/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@threatmap/chatio",
"version": "0.0.0",
"description": "Specification for the ThreatMap Chatio plugin",
"author": "ThreatCode",
"repository": "https://github.com/threatcode/threatmap",
"license": "MIT",
"type": "module",
"main": "./dist/index.mjs",
"types": "./dist/index.d.mts",
"exports": {
".": {
"types": "./dist/index.d.mts",
"import": "./dist/index.mjs",
"default": "./dist/index.mjs"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsdown",
"prepare": "pnpm run build",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@threatmap/sdk-shared": "workspace:*",
"zod": "4.3.6"
},
"devDependencies": {
"tsdown": "0.20.1"
}
}
Loading
Loading