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.
@@ -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
+ [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](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 { promptApiKey } from '../src/prompt.js'
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 → prompt
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 promptApiKey()
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spaceshipai",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Spaceship AI CLI — connect your AI coding tools, manage agents, and more",
5
5
  "type": "module",
6
6
  "bin": {
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">&#10003;</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">&#10005;</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
- }