spaceshipai 0.1.0 → 0.1.2
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/.github/workflows/publish.yml +23 -0
- package/README.md +78 -0
- package/bin/init.js +3 -8
- package/package.json +1 -1
- package/src/auth.js +151 -0
- package/src/install.js +7 -1
- package/src/prompt.js +0 -12
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
id-token: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
publish:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- uses: actions/setup-node@v4
|
|
17
|
+
with:
|
|
18
|
+
node-version: '22'
|
|
19
|
+
registry-url: 'https://registry.npmjs.org'
|
|
20
|
+
|
|
21
|
+
- run: npm install -g npm@latest
|
|
22
|
+
|
|
23
|
+
- run: npm publish --access public --provenance
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Spaceship AI CLI
|
|
2
|
+
|
|
3
|
+
Install the [Spaceship AI](https://spaceshipai.io) MCP server into every AI coding tool on your machine with one command.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
SPACESHIP_API_KEY=sk_live_... npx spaceshipai@latest init
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Detects and installs to **Claude Code**, **Cursor**, **VS Code**, and **Windsurf** — whichever you have installed.
|
|
12
|
+
|
|
13
|
+
Get your API key from the [Spaceship dashboard](https://spaceshipai.io).
|
|
14
|
+
|
|
15
|
+
## What it does
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Spaceship AI — MCP installer
|
|
19
|
+
|
|
20
|
+
Installing Spaceship MCP server...
|
|
21
|
+
|
|
22
|
+
✓ Claude Code
|
|
23
|
+
✓ Cursor
|
|
24
|
+
✓ VS Code
|
|
25
|
+
— Windsurf not detected (skipped)
|
|
26
|
+
|
|
27
|
+
Spaceship MCP is ready. Restart your IDE if it was open.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Install to Cursor
|
|
31
|
+
|
|
32
|
+
[](https://cursor.com/en-US/install-mcp?name=Spaceship&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyJzcGFjZXNoaXAtbWNwIl19)
|
|
33
|
+
|
|
34
|
+
## Available MCP tools
|
|
35
|
+
|
|
36
|
+
Once installed, these tools are available in your AI IDE:
|
|
37
|
+
|
|
38
|
+
| Tool | Description |
|
|
39
|
+
|------|-------------|
|
|
40
|
+
| `list_projects` | List all projects in your org |
|
|
41
|
+
| `list_agents` | List agents, optionally filtered by project |
|
|
42
|
+
| `get_agent` | Get full details of a single agent |
|
|
43
|
+
| `create_agent` | Create an agent with auto-generated system prompt |
|
|
44
|
+
| `update_agent` | Update name, prompt, or tools |
|
|
45
|
+
| `delete_agent` | Permanently delete an agent |
|
|
46
|
+
| `run_agent` | Start an async run; returns execution_id |
|
|
47
|
+
| `get_run_status` | Poll run status until completed or errored |
|
|
48
|
+
| `get_run_logs` | Fetch full event log for a completed run |
|
|
49
|
+
| `list_executions` | List recent runs with status and duration |
|
|
50
|
+
| `test_agent` | Quick sync test with 15s timeout |
|
|
51
|
+
| `list_tools` | List tools available to attach to agents |
|
|
52
|
+
|
|
53
|
+
## Manual configuration
|
|
54
|
+
|
|
55
|
+
If you prefer to configure manually, add this to your IDE's MCP config file:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"spaceship": {
|
|
60
|
+
"command": "uvx",
|
|
61
|
+
"args": ["spaceship-mcp"],
|
|
62
|
+
"env": {
|
|
63
|
+
"SPACESHIP_API_KEY": "sk_live_..."
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
| IDE | Config file |
|
|
70
|
+
|-----|-------------|
|
|
71
|
+
| Claude Code | `~/.claude/mcp.json` or run `claude mcp add` |
|
|
72
|
+
| Cursor | `~/.cursor/mcp.json` |
|
|
73
|
+
| VS Code | `.vscode/mcp.json` |
|
|
74
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT
|
package/bin/init.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
installVscode,
|
|
8
8
|
installWindsurf,
|
|
9
9
|
} from '../src/install.js'
|
|
10
|
-
import {
|
|
10
|
+
import { authenticateViaBrowser } from '../src/auth.js'
|
|
11
11
|
|
|
12
12
|
const TEAL = '\x1b[36m'
|
|
13
13
|
const GREEN = '\x1b[32m'
|
|
@@ -18,15 +18,10 @@ const RESET = '\x1b[0m'
|
|
|
18
18
|
async function main() {
|
|
19
19
|
console.log(`\n${BOLD}${TEAL}Spaceship AI${RESET} — MCP installer\n`)
|
|
20
20
|
|
|
21
|
-
// Resolve API key: env var →
|
|
21
|
+
// Resolve API key: env var → browser OAuth flow
|
|
22
22
|
let apiKey = process.env.SPACESHIP_API_KEY
|
|
23
23
|
if (!apiKey) {
|
|
24
|
-
apiKey = await
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (!apiKey || !apiKey.startsWith('sk_')) {
|
|
28
|
-
console.error('\nInvalid API key. Keys must start with sk_live_ or sk_test_.')
|
|
29
|
-
process.exit(1)
|
|
24
|
+
apiKey = await authenticateViaBrowser()
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
// Detect installed tools
|
package/package.json
CHANGED
package/src/auth.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { createServer } from 'http'
|
|
2
|
+
import { randomBytes } from 'crypto'
|
|
3
|
+
import { execSync } from 'child_process'
|
|
4
|
+
|
|
5
|
+
const DASHBOARD_URL = process.env.SPACESHIP_BASE_URL || 'https://spaceshipai.io'
|
|
6
|
+
const TIMEOUT_MS = 120_000
|
|
7
|
+
|
|
8
|
+
function openBrowser(url) {
|
|
9
|
+
try {
|
|
10
|
+
const p = process.platform
|
|
11
|
+
if (p === 'darwin') execSync(`open "${url}"`, { stdio: 'ignore' })
|
|
12
|
+
else if (p === 'win32') execSync(`start "" "${url}"`, { stdio: 'ignore' })
|
|
13
|
+
else execSync(`xdg-open "${url}"`, { stdio: 'ignore' })
|
|
14
|
+
return true
|
|
15
|
+
} catch {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function authenticateViaBrowser() {
|
|
21
|
+
const state = randomBytes(16).toString('hex')
|
|
22
|
+
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const server = createServer((req, res) => {
|
|
25
|
+
try {
|
|
26
|
+
const url = new URL(req.url, 'http://localhost')
|
|
27
|
+
|
|
28
|
+
if (url.pathname !== '/cb') {
|
|
29
|
+
res.writeHead(404)
|
|
30
|
+
res.end()
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const error = url.searchParams.get('error')
|
|
35
|
+
if (error) {
|
|
36
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
|
37
|
+
res.end(cancelledHtml())
|
|
38
|
+
server.close()
|
|
39
|
+
clearTimeout(timer)
|
|
40
|
+
reject(new Error('Authentication cancelled.'))
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const code = url.searchParams.get('code')
|
|
45
|
+
const returnedState = url.searchParams.get('state')
|
|
46
|
+
|
|
47
|
+
if (!code || returnedState !== state) {
|
|
48
|
+
res.writeHead(400)
|
|
49
|
+
res.end('Invalid request')
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Exchange the short-lived code for the actual API key
|
|
54
|
+
fetch(`${DASHBOARD_URL}/cli-auth/exchange?code=${encodeURIComponent(code)}`)
|
|
55
|
+
.then((r) => {
|
|
56
|
+
if (!r.ok) return Promise.reject(new Error(`Exchange failed (${r.status}). Run again to retry.`))
|
|
57
|
+
return r.json()
|
|
58
|
+
})
|
|
59
|
+
.then(({ key }) => {
|
|
60
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
|
61
|
+
res.end(successHtml())
|
|
62
|
+
server.close()
|
|
63
|
+
clearTimeout(timer)
|
|
64
|
+
resolve(key)
|
|
65
|
+
})
|
|
66
|
+
.catch((err) => {
|
|
67
|
+
res.writeHead(500)
|
|
68
|
+
res.end('Exchange failed')
|
|
69
|
+
server.close()
|
|
70
|
+
clearTimeout(timer)
|
|
71
|
+
reject(err)
|
|
72
|
+
})
|
|
73
|
+
} catch (err) {
|
|
74
|
+
reject(err)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
server.listen(0, '127.0.0.1', () => {
|
|
79
|
+
const { port } = server.address()
|
|
80
|
+
const callbackUrl = `http://127.0.0.1:${port}/cb`
|
|
81
|
+
const authUrl = `${DASHBOARD_URL}/cli-auth?callback=${encodeURIComponent(callbackUrl)}&state=${state}`
|
|
82
|
+
|
|
83
|
+
const opened = openBrowser(authUrl)
|
|
84
|
+
if (opened) {
|
|
85
|
+
process.stderr.write('Opening browser for authentication...\n')
|
|
86
|
+
} else {
|
|
87
|
+
process.stderr.write(`Could not open browser automatically. Visit this URL:\n\n ${authUrl}\n\n`)
|
|
88
|
+
}
|
|
89
|
+
process.stderr.write('Waiting for browser authorization... (Ctrl+C to cancel)\n\n')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const timer = setTimeout(() => {
|
|
93
|
+
server.close()
|
|
94
|
+
reject(new Error('Authentication timed out. Run again to retry.'))
|
|
95
|
+
}, TIMEOUT_MS)
|
|
96
|
+
|
|
97
|
+
server.on('error', reject)
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function successHtml() {
|
|
102
|
+
return `<!DOCTYPE html>
|
|
103
|
+
<html>
|
|
104
|
+
<head>
|
|
105
|
+
<title>Spaceship CLI Authorized</title>
|
|
106
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
107
|
+
<style>
|
|
108
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
109
|
+
body{background:#0f0f1a;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
|
|
110
|
+
display:flex;align-items:center;justify-content:center;min-height:100vh;text-align:center}
|
|
111
|
+
.card{padding:48px;max-width:400px}
|
|
112
|
+
.icon{font-size:48px;margin-bottom:16px}
|
|
113
|
+
h2{color:#00daf3;margin:0 0 12px;font-size:22px;font-weight:600}
|
|
114
|
+
p{color:#888;margin:0;font-size:14px;line-height:1.6}
|
|
115
|
+
</style>
|
|
116
|
+
</head>
|
|
117
|
+
<body>
|
|
118
|
+
<div class="card">
|
|
119
|
+
<div class="icon">✓</div>
|
|
120
|
+
<h2>Spaceship CLI authorized</h2>
|
|
121
|
+
<p>You can close this tab and return to your terminal.</p>
|
|
122
|
+
</div>
|
|
123
|
+
</body>
|
|
124
|
+
</html>`
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function cancelledHtml() {
|
|
128
|
+
return `<!DOCTYPE html>
|
|
129
|
+
<html>
|
|
130
|
+
<head>
|
|
131
|
+
<title>Cancelled</title>
|
|
132
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
133
|
+
<style>
|
|
134
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
135
|
+
body{background:#0f0f1a;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
|
|
136
|
+
display:flex;align-items:center;justify-content:center;min-height:100vh;text-align:center}
|
|
137
|
+
.card{padding:48px;max-width:400px}
|
|
138
|
+
.icon{font-size:48px;margin-bottom:16px}
|
|
139
|
+
h2{color:#888;margin:0 0 12px;font-size:22px;font-weight:600}
|
|
140
|
+
p{color:#888;margin:0;font-size:14px}
|
|
141
|
+
</style>
|
|
142
|
+
</head>
|
|
143
|
+
<body>
|
|
144
|
+
<div class="card">
|
|
145
|
+
<div class="icon">✕</div>
|
|
146
|
+
<h2>Authorization cancelled</h2>
|
|
147
|
+
<p>You can close this tab.</p>
|
|
148
|
+
</div>
|
|
149
|
+
</body>
|
|
150
|
+
</html>`
|
|
151
|
+
}
|
package/src/install.js
CHANGED
|
@@ -26,8 +26,14 @@ function writeJsonFile(path, data) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function installClaudeCode(key) {
|
|
29
|
+
// Remove any existing project-scoped entry before adding user-scoped one
|
|
30
|
+
try {
|
|
31
|
+
execSync('claude mcp remove spaceship', { stdio: 'ignore' })
|
|
32
|
+
} catch {
|
|
33
|
+
// Not present — that's fine
|
|
34
|
+
}
|
|
29
35
|
execSync(
|
|
30
|
-
`claude mcp add --transport stdio spaceship --env SPACESHIP_API_KEY=${key} -- uvx spaceship-mcp`,
|
|
36
|
+
`claude mcp add --scope user --transport stdio spaceship --env SPACESHIP_API_KEY=${key} -- uvx spaceship-mcp`,
|
|
31
37
|
{ stdio: 'ignore' }
|
|
32
38
|
)
|
|
33
39
|
}
|
package/src/prompt.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { createInterface } from 'readline'
|
|
2
|
-
|
|
3
|
-
export async function promptApiKey() {
|
|
4
|
-
const rl = createInterface({ input: process.stdin, output: process.stderr })
|
|
5
|
-
|
|
6
|
-
return new Promise((resolve) => {
|
|
7
|
-
rl.question('Enter your Spaceship API key (sk_live_...): ', (answer) => {
|
|
8
|
-
rl.close()
|
|
9
|
-
resolve(answer.trim())
|
|
10
|
-
})
|
|
11
|
-
})
|
|
12
|
-
}
|