@somewhere-tech/cli 0.1.6 → 0.2.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/README.md +1 -0
- package/dist/commands/auth.js +129 -20
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/mcp.js +96 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/auth.js +3 -1
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/device-login.js +80 -0
- package/dist/lib/device-login.js.map +1 -0
- package/dist/lib/device.js +48 -0
- package/dist/lib/device.js.map +1 -0
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -52,6 +52,7 @@ After `somewhere init`, Claude Code and Codex auto-connect via the `.mcp.json` i
|
|
|
52
52
|
| `somewhere open --dashboard` | Open the dashboard |
|
|
53
53
|
| `somewhere dev` | Run local dev with platform env vars |
|
|
54
54
|
| `somewhere api GET /v1/projects` | Raw API call with auto-auth |
|
|
55
|
+
| `somewhere mcp` | Run MCP server over stdio (proxies to mcp.somewhere.tech) |
|
|
55
56
|
|
|
56
57
|
Short alias: `sw` works everywhere `somewhere` does.
|
|
57
58
|
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,30 +1,21 @@
|
|
|
1
1
|
import ora from 'ora';
|
|
2
2
|
import { browserLogin } from '../lib/auth.js';
|
|
3
|
+
import { deviceLogin, DeviceLoginTimeout, DeviceCodeUnsupported, } from '../lib/device-login.js';
|
|
3
4
|
import { ApiClient } from '../lib/client.js';
|
|
4
|
-
import { clearConfig, loadConfig, saveConfig, saveGlobalMcpConfig } from '../lib/config.js';
|
|
5
|
+
import { clearConfig, loadConfig, saveConfig, saveGlobalMcpConfig, } from '../lib/config.js';
|
|
6
|
+
import { getDeviceId, getDeviceKeyName } from '../lib/device.js';
|
|
5
7
|
import { dim, error, info, success, teal } from '../lib/output.js';
|
|
6
8
|
export function registerAuth(program) {
|
|
7
9
|
program
|
|
8
10
|
.command('login')
|
|
9
|
-
.description('Authenticate with somewhere.tech
|
|
10
|
-
.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
saveGlobalMcpConfig(config.token);
|
|
16
|
-
spinner.stop();
|
|
17
|
-
success(`Logged in as ${teal(config.user.email)}`);
|
|
18
|
-
console.log('');
|
|
19
|
-
info('Done. Claude Code is connected.');
|
|
20
|
-
info('Start a new session to use Somewhere tools.');
|
|
21
|
-
process.exit(0);
|
|
22
|
-
}
|
|
23
|
-
catch (err) {
|
|
24
|
-
spinner.fail('Login failed');
|
|
25
|
-
error(err instanceof Error ? err.message : String(err));
|
|
26
|
-
process.exit(1);
|
|
11
|
+
.description('Authenticate with somewhere.tech')
|
|
12
|
+
.option('--legacy', 'Use the localhost-callback flow instead of device code')
|
|
13
|
+
.action(async (opts) => {
|
|
14
|
+
if (opts.legacy) {
|
|
15
|
+
await runLegacyLogin();
|
|
16
|
+
return;
|
|
27
17
|
}
|
|
18
|
+
await runDeviceLogin();
|
|
28
19
|
});
|
|
29
20
|
program
|
|
30
21
|
.command('logout')
|
|
@@ -53,10 +44,128 @@ export function registerAuth(program) {
|
|
|
53
44
|
info(`Projects: ${projects.projects.length} (${deployed} deployed, ${draft} draft)`);
|
|
54
45
|
info(`Profile: ${config.user.username}.somewhere.tech`);
|
|
55
46
|
}
|
|
56
|
-
catch
|
|
47
|
+
catch {
|
|
57
48
|
console.log(teal(config.user.email));
|
|
58
49
|
info(dim('Could not fetch account details'));
|
|
59
50
|
}
|
|
60
51
|
});
|
|
52
|
+
const auth = program.command('auth').description('Manage stored credentials');
|
|
53
|
+
auth
|
|
54
|
+
.command('set <token>')
|
|
55
|
+
.description('Save an smt_ token directly (no browser flow)')
|
|
56
|
+
.action(async (token) => {
|
|
57
|
+
if (!token.startsWith('smt_')) {
|
|
58
|
+
error('Token must start with smt_');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
// Verify the token works before persisting it.
|
|
62
|
+
const client = new ApiClient(token);
|
|
63
|
+
let email = '';
|
|
64
|
+
let username = '';
|
|
65
|
+
try {
|
|
66
|
+
const me = await client.call('GET', '/auth/platform-me');
|
|
67
|
+
email = me.email ?? '';
|
|
68
|
+
username = me.username ?? '';
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
error(`Token is not valid: ${err instanceof Error ? err.message : String(err)}`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
saveConfig({ token, user: { email, username } });
|
|
75
|
+
saveGlobalMcpConfig(token);
|
|
76
|
+
success(`Token saved.${email ? ` Logged in as ${teal(email)}.` : ''}`);
|
|
77
|
+
info('Run `somewhere projects` to verify.');
|
|
78
|
+
});
|
|
79
|
+
auth
|
|
80
|
+
.command('status')
|
|
81
|
+
.description('Show current login state, device ID, and key name')
|
|
82
|
+
.action(() => {
|
|
83
|
+
const config = loadConfig();
|
|
84
|
+
const deviceId = getDeviceId();
|
|
85
|
+
const keyName = getDeviceKeyName();
|
|
86
|
+
if (!config) {
|
|
87
|
+
info('Not logged in.');
|
|
88
|
+
info(`Device ID: ${deviceId}`);
|
|
89
|
+
info(`Key name: ${keyName}`);
|
|
90
|
+
info(dim('Run `somewhere login` to authenticate.'));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
success(`Logged in as ${teal(config.user.email || '(unknown)')}`);
|
|
94
|
+
info(`Device ID: ${deviceId}`);
|
|
95
|
+
info(`Key name: ${keyName}`);
|
|
96
|
+
info(`Token: ${config.token.slice(0, 8)}…${config.token.slice(-4)}`);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function installCancelHandler(getSpinner) {
|
|
100
|
+
process.on('SIGINT', () => {
|
|
101
|
+
getSpinner()?.stop();
|
|
102
|
+
process.stdout.write('\u001B[?25h'); // restore cursor in case ora hid it
|
|
103
|
+
console.log('');
|
|
104
|
+
info('Login cancelled.');
|
|
105
|
+
process.exit(130);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
async function runDeviceLogin() {
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log(' Login to somewhere.tech');
|
|
111
|
+
console.log('');
|
|
112
|
+
let spinner = null;
|
|
113
|
+
installCancelHandler(() => spinner);
|
|
114
|
+
try {
|
|
115
|
+
const config = await deviceLogin({
|
|
116
|
+
onPrompt: ({ code, approvalUrl }) => {
|
|
117
|
+
console.log(' Open this URL in any browser:');
|
|
118
|
+
console.log(` ${teal(approvalUrl)}`);
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log(` Enter code: ${teal(code)}`);
|
|
121
|
+
console.log('');
|
|
122
|
+
},
|
|
123
|
+
onWaiting: () => {
|
|
124
|
+
spinner = ora({
|
|
125
|
+
text: 'Waiting for approval… (press Ctrl+C to cancel)',
|
|
126
|
+
color: 'cyan',
|
|
127
|
+
}).start();
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
spinner?.stop();
|
|
131
|
+
saveConfig(config);
|
|
132
|
+
saveGlobalMcpConfig(config.token);
|
|
133
|
+
success(`Logged in as ${teal(config.user.email || '(unknown)')}`);
|
|
134
|
+
success(`Device: ${getDeviceKeyName()}`);
|
|
135
|
+
success('Claude Code MCP configured');
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
spinner?.stop();
|
|
140
|
+
if (err instanceof DeviceLoginTimeout) {
|
|
141
|
+
error(err.message);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
if (err instanceof DeviceCodeUnsupported) {
|
|
145
|
+
info(dim('Falling back to localhost-callback login…'));
|
|
146
|
+
await runLegacyLogin();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
error(err instanceof Error ? err.message : String(err));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function runLegacyLogin() {
|
|
154
|
+
const spinner = ora('Waiting for browser login… (press Ctrl+C to cancel)').start();
|
|
155
|
+
installCancelHandler(() => spinner);
|
|
156
|
+
try {
|
|
157
|
+
const config = await browserLogin();
|
|
158
|
+
saveConfig(config);
|
|
159
|
+
saveGlobalMcpConfig(config.token);
|
|
160
|
+
spinner.stop();
|
|
161
|
+
success(`Logged in as ${teal(config.user.email)}`);
|
|
162
|
+
success('Claude Code MCP configured');
|
|
163
|
+
process.exit(0);
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
spinner.fail('Login failed');
|
|
167
|
+
error(err instanceof Error ? err.message : String(err));
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
61
170
|
}
|
|
62
171
|
//# sourceMappingURL=auth.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
2
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
3
|
+
import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js';
|
|
4
|
+
import { loadConfig } from '../lib/config.js';
|
|
5
|
+
const UPSTREAM_URL = 'https://mcp.somewhere.tech/mcp';
|
|
6
|
+
// Emit a JSON-RPC error notification on stdout so MCP hosts surface a clear
|
|
7
|
+
// message instead of a generic crash. id=null because it's not a response to
|
|
8
|
+
// any specific request.
|
|
9
|
+
function emitFatalError(message) {
|
|
10
|
+
const payload = {
|
|
11
|
+
jsonrpc: '2.0',
|
|
12
|
+
id: null,
|
|
13
|
+
error: { code: -32001, message },
|
|
14
|
+
};
|
|
15
|
+
process.stdout.write(JSON.stringify(payload) + '\n');
|
|
16
|
+
}
|
|
17
|
+
export function registerMcp(program) {
|
|
18
|
+
program
|
|
19
|
+
.command('mcp')
|
|
20
|
+
.description('Run somewhere.tech MCP server over stdio (proxies to mcp.somewhere.tech)')
|
|
21
|
+
.action(async () => {
|
|
22
|
+
const config = loadConfig();
|
|
23
|
+
if (!config?.token) {
|
|
24
|
+
emitFatalError('Not logged in. Run: somewhere login');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
const httpTransport = new StreamableHTTPClientTransport(new URL(UPSTREAM_URL), {
|
|
28
|
+
requestInit: {
|
|
29
|
+
headers: { Authorization: `Bearer ${config.token}` },
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
const stdioTransport = new StdioServerTransport();
|
|
33
|
+
let shuttingDown = false;
|
|
34
|
+
const shutdown = async (reason) => {
|
|
35
|
+
if (shuttingDown)
|
|
36
|
+
return;
|
|
37
|
+
shuttingDown = true;
|
|
38
|
+
if (reason instanceof UnauthorizedError) {
|
|
39
|
+
emitFatalError('somewhere.tech rejected the API key. Run: somewhere login');
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
await stdioTransport.close();
|
|
43
|
+
}
|
|
44
|
+
catch { }
|
|
45
|
+
try {
|
|
46
|
+
await httpTransport.close();
|
|
47
|
+
}
|
|
48
|
+
catch { }
|
|
49
|
+
};
|
|
50
|
+
stdioTransport.onmessage = (msg) => {
|
|
51
|
+
httpTransport.send(msg).catch((err) => {
|
|
52
|
+
if (err instanceof UnauthorizedError) {
|
|
53
|
+
void shutdown(err);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
process.stderr.write(`[somewhere mcp] upstream send failed: ${String(err)}\n`);
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
httpTransport.onmessage = (msg) => {
|
|
60
|
+
stdioTransport.send(msg).catch((err) => {
|
|
61
|
+
process.stderr.write(`[somewhere mcp] stdio send failed: ${String(err)}\n`);
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
stdioTransport.onclose = () => { void shutdown(); };
|
|
65
|
+
httpTransport.onclose = () => { void shutdown(); };
|
|
66
|
+
stdioTransport.onerror = (err) => {
|
|
67
|
+
process.stderr.write(`[somewhere mcp] stdio error: ${String(err)}\n`);
|
|
68
|
+
};
|
|
69
|
+
httpTransport.onerror = (err) => {
|
|
70
|
+
if (err instanceof UnauthorizedError) {
|
|
71
|
+
void shutdown(err);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
process.stderr.write(`[somewhere mcp] upstream error: ${String(err)}\n`);
|
|
75
|
+
};
|
|
76
|
+
try {
|
|
77
|
+
await httpTransport.start();
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
if (err instanceof UnauthorizedError) {
|
|
81
|
+
emitFatalError('somewhere.tech rejected the API key. Run: somewhere login');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
emitFatalError(`Failed to connect to mcp.somewhere.tech: ${String(err)}`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
await stdioTransport.start();
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
emitFatalError(`Failed to start stdio transport: ${String(err)}`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -9,10 +9,11 @@ import { registerStatus } from './commands/status.js';
|
|
|
9
9
|
import { registerOpen } from './commands/open.js';
|
|
10
10
|
import { registerDev } from './commands/dev.js';
|
|
11
11
|
import { registerApi } from './commands/api.js';
|
|
12
|
+
import { registerMcp } from './commands/mcp.js';
|
|
12
13
|
const program = new Command()
|
|
13
14
|
.name('somewhere')
|
|
14
15
|
.description('CLI for somewhere.tech')
|
|
15
|
-
.version('0.1
|
|
16
|
+
.version('0.2.1');
|
|
16
17
|
registerAuth(program);
|
|
17
18
|
registerInit(program);
|
|
18
19
|
registerProject(program);
|
|
@@ -23,5 +24,6 @@ registerStatus(program);
|
|
|
23
24
|
registerOpen(program);
|
|
24
25
|
registerDev(program);
|
|
25
26
|
registerApi(program);
|
|
27
|
+
registerMcp(program);
|
|
26
28
|
program.parse();
|
|
27
29
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE;KAC1B,IAAI,CAAC,WAAW,CAAC;KACjB,WAAW,CAAC,wBAAwB,CAAC;KACrC,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,YAAY,CAAC,OAAO,CAAC,CAAC;AACtB,YAAY,CAAC,OAAO,CAAC,CAAC;AACtB,eAAe,CAAC,OAAO,CAAC,CAAC;AACzB,cAAc,CAAC,OAAO,CAAC,CAAC;AACxB,YAAY,CAAC,OAAO,CAAC,CAAC;AACtB,WAAW,CAAC,OAAO,CAAC,CAAC;AACrB,cAAc,CAAC,OAAO,CAAC,CAAC;AACxB,YAAY,CAAC,OAAO,CAAC,CAAC;AACtB,WAAW,CAAC,OAAO,CAAC,CAAC;AACrB,WAAW,CAAC,OAAO,CAAC,CAAC;AAErB,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE;KAC1B,IAAI,CAAC,WAAW,CAAC;KACjB,WAAW,CAAC,wBAAwB,CAAC;KACrC,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,YAAY,CAAC,OAAO,CAAC,CAAC;AACtB,YAAY,CAAC,OAAO,CAAC,CAAC;AACtB,eAAe,CAAC,OAAO,CAAC,CAAC;AACzB,cAAc,CAAC,OAAO,CAAC,CAAC;AACxB,YAAY,CAAC,OAAO,CAAC,CAAC;AACtB,WAAW,CAAC,OAAO,CAAC,CAAC;AACrB,cAAc,CAAC,OAAO,CAAC,CAAC;AACxB,YAAY,CAAC,OAAO,CAAC,CAAC;AACtB,WAAW,CAAC,OAAO,CAAC,CAAC;AACrB,WAAW,CAAC,OAAO,CAAC,CAAC;AACrB,WAAW,CAAC,OAAO,CAAC,CAAC;AAErB,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
package/dist/lib/auth.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
2
|
import open from 'open';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
+
import { getDeviceKeyName } from './device.js';
|
|
4
5
|
const AUTH_URL = 'https://api.somewhere.tech/v1/auth/cli';
|
|
5
6
|
/**
|
|
6
7
|
* Browser-based OAuth login flow:
|
|
@@ -136,7 +137,8 @@ export async function browserLogin() {
|
|
|
136
137
|
}
|
|
137
138
|
const port = addr.port;
|
|
138
139
|
const redirect = `http://localhost:${port}/callback`;
|
|
139
|
-
const
|
|
140
|
+
const deviceName = getDeviceKeyName();
|
|
141
|
+
const loginUrl = `${AUTH_URL}?redirect=${encodeURIComponent(redirect)}&device_name=${encodeURIComponent(deviceName)}`;
|
|
140
142
|
console.log('');
|
|
141
143
|
console.log(` If the browser doesn't open, visit this URL:`);
|
|
142
144
|
console.log(` ${chalk.cyan(loginUrl)}`);
|
package/dist/lib/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device-code login. Same pattern as `npm login` and `gh auth login`.
|
|
3
|
+
*
|
|
4
|
+
* 1. Generate a human-readable XXXX-XXXX code
|
|
5
|
+
* 2. POST it to the platform — server stores it pending
|
|
6
|
+
* 3. Print the URL + code, poll status
|
|
7
|
+
* 4. When the user approves in a browser, the next poll returns the token
|
|
8
|
+
*
|
|
9
|
+
* Why over the localhost-callback flow:
|
|
10
|
+
* - works over SSH / cloud dev environments
|
|
11
|
+
* - no port binding, no firewall holes
|
|
12
|
+
* - browser and terminal can be on different machines
|
|
13
|
+
*/
|
|
14
|
+
import { randomInt } from 'node:crypto';
|
|
15
|
+
import { getDeviceKeyName } from './device.js';
|
|
16
|
+
const API_BASE = 'https://api.somewhere.tech';
|
|
17
|
+
const POLL_INTERVAL_MS = 2000;
|
|
18
|
+
const TIMEOUT_MS = 5 * 60 * 1000;
|
|
19
|
+
// Skip ambiguous chars (no I, O, 0, 1) — same alphabet the worker validates.
|
|
20
|
+
const ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
|
21
|
+
function generateCode() {
|
|
22
|
+
const chars = [];
|
|
23
|
+
for (let i = 0; i < 8; i++) {
|
|
24
|
+
chars.push(ALPHABET[randomInt(0, ALPHABET.length)]);
|
|
25
|
+
}
|
|
26
|
+
return `${chars.slice(0, 4).join('')}-${chars.slice(4).join('')}`;
|
|
27
|
+
}
|
|
28
|
+
export class DeviceLoginTimeout extends Error {
|
|
29
|
+
constructor() {
|
|
30
|
+
super('Login timed out. Run `somewhere login` to try again.');
|
|
31
|
+
this.name = 'DeviceLoginTimeout';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export class DeviceCodeUnsupported extends Error {
|
|
35
|
+
constructor() {
|
|
36
|
+
super('Device-code endpoint not available on this platform.');
|
|
37
|
+
this.name = 'DeviceCodeUnsupported';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export async function deviceLogin(callbacks) {
|
|
41
|
+
const code = generateCode();
|
|
42
|
+
const deviceName = getDeviceKeyName();
|
|
43
|
+
const startRes = await fetch(`${API_BASE}/v1/auth/device-code`, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { 'Content-Type': 'application/json' },
|
|
46
|
+
body: JSON.stringify({ code, device_name: deviceName }),
|
|
47
|
+
});
|
|
48
|
+
if (startRes.status === 404) {
|
|
49
|
+
// Endpoint not deployed yet → caller can fall back to legacy flow
|
|
50
|
+
throw new DeviceCodeUnsupported();
|
|
51
|
+
}
|
|
52
|
+
if (!startRes.ok) {
|
|
53
|
+
const text = await startRes.text();
|
|
54
|
+
throw new Error(`Failed to start login: HTTP ${startRes.status} ${text.slice(0, 200)}`);
|
|
55
|
+
}
|
|
56
|
+
const start = (await startRes.json());
|
|
57
|
+
callbacks.onPrompt({ code, approvalUrl: start.data.approval_url });
|
|
58
|
+
callbacks.onWaiting?.();
|
|
59
|
+
const startedAt = Date.now();
|
|
60
|
+
while (Date.now() - startedAt < TIMEOUT_MS) {
|
|
61
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
62
|
+
const pollRes = await fetch(`${API_BASE}/v1/auth/device-code/status?code=${encodeURIComponent(code)}`);
|
|
63
|
+
if (!pollRes.ok) {
|
|
64
|
+
// Transient errors are non-fatal — keep polling until timeout.
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const status = (await pollRes.json());
|
|
68
|
+
if (status.data.status === 'expired') {
|
|
69
|
+
throw new DeviceLoginTimeout();
|
|
70
|
+
}
|
|
71
|
+
if (status.data.status === 'approved') {
|
|
72
|
+
return {
|
|
73
|
+
token: status.data.token,
|
|
74
|
+
user: { email: status.data.email, username: '' },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
throw new DeviceLoginTimeout();
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=device-login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-login.js","sourceRoot":"","sources":["../../src/lib/device-login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir, hostname } from 'node:os';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
const CONFIG_DIR = join(homedir(), '.somewhere');
|
|
6
|
+
const DEVICE_PATH = join(CONFIG_DIR, 'device.json');
|
|
7
|
+
function ensureDir() {
|
|
8
|
+
if (!existsSync(CONFIG_DIR))
|
|
9
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
function loadDevice() {
|
|
12
|
+
if (!existsSync(DEVICE_PATH))
|
|
13
|
+
return null;
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(readFileSync(DEVICE_PATH, 'utf-8'));
|
|
16
|
+
if (typeof parsed.id !== 'string' || parsed.id.length < 8)
|
|
17
|
+
return null;
|
|
18
|
+
return parsed;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function saveDevice(device) {
|
|
25
|
+
ensureDir();
|
|
26
|
+
writeFileSync(DEVICE_PATH, JSON.stringify(device, null, 2) + '\n', { mode: 0o600 });
|
|
27
|
+
}
|
|
28
|
+
export function getDeviceId() {
|
|
29
|
+
const existing = loadDevice();
|
|
30
|
+
if (existing)
|
|
31
|
+
return existing.id;
|
|
32
|
+
const fresh = { id: randomUUID(), created_at: new Date().toISOString() };
|
|
33
|
+
saveDevice(fresh);
|
|
34
|
+
return fresh.id;
|
|
35
|
+
}
|
|
36
|
+
function safeHostname() {
|
|
37
|
+
const raw = hostname() || 'unknown';
|
|
38
|
+
return raw
|
|
39
|
+
.replace(/\.local$/i, '')
|
|
40
|
+
.replace(/[^A-Za-z0-9_\-]/g, '-')
|
|
41
|
+
.slice(0, 32) || 'unknown';
|
|
42
|
+
}
|
|
43
|
+
export function getDeviceKeyName() {
|
|
44
|
+
const id = getDeviceId();
|
|
45
|
+
const short = id.replace(/-/g, '').slice(0, 8);
|
|
46
|
+
return `CLI \u00b7 ${safeHostname()} \u00b7 ${short}`;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=device.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.js","sourceRoot":"","sources":["../../src/lib/device.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC;AACjD,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAOpD,SAAS,SAAS;IAChB,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC1E,CAAC;AAED,SAAS,UAAU;IACjB,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAe,CAAC;QAC5E,IAAI,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ,IAAI,MAAM,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QACvE,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,MAAkB;IACpC,SAAS,EAAE,CAAC;IACZ,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACtF,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,MAAM,QAAQ,GAAG,UAAU,EAAE,CAAC;IAC9B,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC,EAAE,CAAC;IACjC,MAAM,KAAK,GAAe,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;IACrF,UAAU,CAAC,KAAK,CAAC,CAAC;IAClB,OAAO,KAAK,CAAC,EAAE,CAAC;AAClB,CAAC;AAED,SAAS,YAAY;IACnB,MAAM,GAAG,GAAG,QAAQ,EAAE,IAAI,SAAS,CAAC;IACpC,OAAO,GAAG;SACP,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;SACxB,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC;SAChC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,SAAS,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,MAAM,EAAE,GAAG,WAAW,EAAE,CAAC;IACzB,MAAM,KAAK,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/C,OAAO,cAAc,YAAY,EAAE,WAAW,KAAK,EAAE,CAAC;AACxD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@somewhere-tech/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI for somewhere.tech — auth, projects, deploy, logs, env.",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "CLI for somewhere.tech — auth, projects, deploy, logs, env, MCP bridge.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"somewhere",
|
|
7
7
|
"somewhere.tech",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"homepage": "https://somewhere.tech",
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
|
-
"url": "git+https://github.com/
|
|
15
|
+
"url": "git+https://github.com/Somewhere-Tech/somewhere-cli.git"
|
|
16
16
|
},
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"author": "somewhere.tech",
|
|
@@ -36,15 +36,16 @@
|
|
|
36
36
|
"node": ">=18"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
40
40
|
"chalk": "^5.3.0",
|
|
41
|
-
"
|
|
41
|
+
"commander": "^12.0.0",
|
|
42
42
|
"open": "^10.0.0",
|
|
43
|
+
"ora": "^8.0.0",
|
|
43
44
|
"prompts": "^2.4.2"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
|
-
"typescript": "^5.4.0",
|
|
47
47
|
"@types/node": "^20.0.0",
|
|
48
|
-
"@types/prompts": "^2.4.0"
|
|
48
|
+
"@types/prompts": "^2.4.0",
|
|
49
|
+
"typescript": "^5.4.0"
|
|
49
50
|
}
|
|
50
51
|
}
|