@webship.co/workspace-upsun 0.0.0-stage → 1.0.0-beta1

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.
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env bash
2
+
3
+ ################################################################################
4
+ ## Webship Workspace — install Upsun's own AI toolkit for coding agents
5
+ ################################################################################
6
+ ## Upsun publishes a skill and an MCP server for coding agents:
7
+ ##
8
+ ## https://developer.upsun.com/ai
9
+ ## https://github.com/upsun/ai the toolkit: plugin + skill + MCP
10
+ ## https://github.com/upsun/skills the skill on its own
11
+ ##
12
+ ## The skill teaches an agent the Upsun CLI — 130+ commands across 30 namespaces,
13
+ ## plus per-framework config recipes including Drupal and PHP — so it writes a
14
+ ## working `.upsun/config.yaml` the first time instead of learning by failed
15
+ ## deploy. The MCP server answers the same questions over HTTP when no CLI is
16
+ ## available.
17
+ ##
18
+ ## bash core/scripts/upsun-ai.sh install the skill, register the MCP
19
+ ## bash core/scripts/upsun-ai.sh --check report, change nothing
20
+ ## bash core/scripts/upsun-ai.sh --quiet only speak when something is wrong
21
+ ##
22
+ ## Two deliberate choices:
23
+ ##
24
+ ## - **The MCP is registered read-only** (`enable-write: false`, which is Upsun's
25
+ ## own default). This is a CLI that manages live client environments; an agent
26
+ ## that can read them is useful, an agent that can change them is a decision for
27
+ ## a person, per environment, not a default of a setup script.
28
+ ## - **The skill is copied, not symlinked**, into ~/.claude/skills/upsun — the same
29
+ ## place cmd-tools-ai-items.sh installs Webship's own skills, so it is visible to
30
+ ## `--source claude` and cannot vanish when a temporary clone is cleaned up.
31
+ ##
32
+ ## Self-contained plain bash, like trust-https.sh and host-mounts.sh beside it.
33
+ ################################################################################
34
+
35
+ set -u
36
+
37
+ CHECK_ONLY='no'
38
+ QUIET='no'
39
+ while [ $# -gt 0 ]; do
40
+ case "$1" in
41
+ -c|--check) CHECK_ONLY='yes'; shift ;;
42
+ -q|--quiet) QUIET='yes'; shift ;;
43
+ -h|--help) sed -n '3,33p' "${BASH_SOURCE[0]}" | sed 's/^## \{0,1\}//'; exit 0 ;;
44
+ *) echo "Unknown option: $1 (see --help)"; exit 1 ;;
45
+ esac
46
+ done
47
+
48
+ REPO='https://github.com/upsun/ai.git'
49
+ SKILL_SRC='plugins/upsun/skills/upsun'
50
+ SKILL_DEST="${HOME}/.claude/skills/upsun"
51
+ MCP_URL='https://mcp.upsun.com/mcp'
52
+
53
+ function say() { [ "${QUIET}" == 'yes' ] || echo "$@" ; }
54
+ function warn() { echo "$@" ; }
55
+ function have() { command -v "$1" > /dev/null 2>&1 ; }
56
+
57
+ PROBLEMS=0
58
+
59
+ # ------------------------------------------------------------------- the skill ---
60
+
61
+ skill_installed='no'
62
+ [ -f "${SKILL_DEST}/SKILL.md" ] && skill_installed='yes'
63
+
64
+ if [ "${skill_installed}" == 'yes' ] ; then
65
+ say " ✓ upsun skill ~/.claude/skills/upsun ($(find "${SKILL_DEST}" -type f | wc -l) files)"
66
+ elif [ "${CHECK_ONLY}" == 'yes' ] ; then
67
+ warn " ✗ upsun skill not installed — an agent has to learn the Upsun CLI by failing"
68
+ PROBLEMS=$((PROBLEMS + 1))
69
+ else
70
+ if ! have git ; then
71
+ warn " ✗ git is needed to fetch the skill."
72
+ PROBLEMS=$((PROBLEMS + 1))
73
+ else
74
+ tmp="$(mktemp -d)"
75
+ # A shallow clone: this is 164KB of Markdown, and none of its history matters here.
76
+ if git clone --quiet --depth=1 "${REPO}" "${tmp}/ai" 2>/dev/null \
77
+ && [ -f "${tmp}/ai/${SKILL_SRC}/SKILL.md" ] ; then
78
+ mkdir -p "${HOME}/.claude/skills"
79
+ rm -rf "${SKILL_DEST}"
80
+ cp -r "${tmp}/ai/${SKILL_SRC}" "${SKILL_DEST}"
81
+ say " ✓ upsun skill installed to ~/.claude/skills/upsun ($(find "${SKILL_DEST}" -type f | wc -l) files)"
82
+ else
83
+ warn " ✗ could not fetch ${REPO} — check network access to GitHub."
84
+ PROBLEMS=$((PROBLEMS + 1))
85
+ fi
86
+ rm -rf "${tmp}"
87
+ fi
88
+ fi
89
+
90
+ # --------------------------------------------------------------------- the MCP ---
91
+
92
+ if ! have claude ; then
93
+ say " – upsun MCP skipped: the claude CLI is not installed"
94
+ else
95
+ if claude mcp get upsun > /dev/null 2>&1 ; then
96
+ say " ✓ upsun MCP registered (claude mcp get upsun)"
97
+ elif [ "${CHECK_ONLY}" == 'yes' ] ; then
98
+ warn " ✗ upsun MCP not registered"
99
+ PROBLEMS=$((PROBLEMS + 1))
100
+ else
101
+ # enable-write: false is Upsun's own default and it stays that way here: see the
102
+ # header. The token comes from the environment so no credential is written into
103
+ # ~/.claude.json — ${UPSUN_API_TOKEN} is expanded when the server is loaded.
104
+ if claude mcp add --transport http upsun "${MCP_URL}" -s user \
105
+ --header 'upsun-api-token: ${UPSUN_API_TOKEN:-}' \
106
+ --header 'enable-write: false' > /dev/null 2>&1 ; then
107
+ say " ✓ upsun MCP registered read-only at ${MCP_URL}"
108
+ else
109
+ warn " ✗ could not register the upsun MCP server with the claude CLI."
110
+ PROBLEMS=$((PROBLEMS + 1))
111
+ fi
112
+ fi
113
+ fi
114
+
115
+ # ------------------------------------------------------------------ what next ---
116
+
117
+ say ""
118
+ if [ "${CHECK_ONLY}" == 'yes' ] ; then
119
+ [ "${PROBLEMS}" -eq 0 ] && { say "Upsun's AI toolkit is installed."; exit 0; }
120
+ warn "Upsun's AI toolkit is incomplete — run without --check to install it."
121
+ exit 1
122
+ fi
123
+
124
+ if [ "${PROBLEMS}" -eq 0 ] ; then
125
+ say "Upsun's AI toolkit is installed. Two things it needs from you:"
126
+ say " • The skill reaches a session after a restart."
127
+ say " • The MCP authenticates with an UPSUN API token, not the browser session:"
128
+ say " export UPSUN_API_TOKEN=… (Upsun console → My profile → API tokens)"
129
+ say " It must be an Upsun token — an Atlassian token is refused with a message"
130
+ say " that reads like an expiry rather than the wrong kind of token."
131
+ exit 0
132
+ fi
133
+ exit 1