@somewhere-tech/cli 0.2.0 → 0.2.3

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.
@@ -1,30 +1,22 @@
1
+ import open from 'open';
1
2
  import ora from 'ora';
2
3
  import { browserLogin } from '../lib/auth.js';
4
+ import { deviceLogin, DeviceLoginTimeout, DeviceCodeUnsupported, } from '../lib/device-login.js';
3
5
  import { ApiClient } from '../lib/client.js';
4
- import { clearConfig, loadConfig, saveConfig, saveGlobalMcpConfig } from '../lib/config.js';
6
+ import { clearConfig, loadConfig, saveConfig, saveGlobalMcpConfig, } from '../lib/config.js';
7
+ import { getDeviceId, getDeviceKeyName } from '../lib/device.js';
5
8
  import { dim, error, info, success, teal } from '../lib/output.js';
6
9
  export function registerAuth(program) {
7
10
  program
8
11
  .command('login')
9
- .description('Authenticate with somewhere.tech via browser')
10
- .action(async () => {
11
- const spinner = ora('Opening browser for authentication...').start();
12
- try {
13
- const config = await browserLogin();
14
- saveConfig(config);
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);
12
+ .description('Authenticate with somewhere.tech')
13
+ .option('--legacy', 'Use the localhost-callback flow instead of device code')
14
+ .action(async (opts) => {
15
+ if (opts.legacy) {
16
+ await runLegacyLogin();
17
+ return;
27
18
  }
19
+ await runDeviceLogin();
28
20
  });
29
21
  program
30
22
  .command('logout')
@@ -53,10 +45,131 @@ export function registerAuth(program) {
53
45
  info(`Projects: ${projects.projects.length} (${deployed} deployed, ${draft} draft)`);
54
46
  info(`Profile: ${config.user.username}.somewhere.tech`);
55
47
  }
56
- catch (err) {
48
+ catch {
57
49
  console.log(teal(config.user.email));
58
50
  info(dim('Could not fetch account details'));
59
51
  }
60
52
  });
53
+ const auth = program.command('auth').description('Manage stored credentials');
54
+ auth
55
+ .command('set <token>')
56
+ .description('Save an smt_ token directly (no browser flow)')
57
+ .action(async (token) => {
58
+ if (!token.startsWith('smt_')) {
59
+ error('Token must start with smt_');
60
+ process.exit(1);
61
+ }
62
+ // Verify the token works before persisting it.
63
+ const client = new ApiClient(token);
64
+ let email = '';
65
+ let username = '';
66
+ try {
67
+ const me = await client.call('GET', '/auth/platform-me');
68
+ email = me.email ?? '';
69
+ username = me.username ?? '';
70
+ }
71
+ catch (err) {
72
+ error(`Token is not valid: ${err instanceof Error ? err.message : String(err)}`);
73
+ process.exit(1);
74
+ }
75
+ saveConfig({ token, user: { email, username } });
76
+ saveGlobalMcpConfig(token);
77
+ success(`Token saved.${email ? ` Logged in as ${teal(email)}.` : ''}`);
78
+ info('Run `somewhere projects` to verify.');
79
+ });
80
+ auth
81
+ .command('status')
82
+ .description('Show current login state, device ID, and key name')
83
+ .action(() => {
84
+ const config = loadConfig();
85
+ const deviceId = getDeviceId();
86
+ const keyName = getDeviceKeyName();
87
+ if (!config) {
88
+ info('Not logged in.');
89
+ info(`Device ID: ${deviceId}`);
90
+ info(`Key name: ${keyName}`);
91
+ info(dim('Run `somewhere login` to authenticate.'));
92
+ return;
93
+ }
94
+ success(`Logged in as ${teal(config.user.email || '(unknown)')}`);
95
+ info(`Device ID: ${deviceId}`);
96
+ info(`Key name: ${keyName}`);
97
+ info(`Token: ${config.token.slice(0, 8)}…${config.token.slice(-4)}`);
98
+ });
99
+ }
100
+ function installCancelHandler(getSpinner) {
101
+ process.on('SIGINT', () => {
102
+ getSpinner()?.stop();
103
+ process.stdout.write('\u001B[?25h'); // restore cursor in case ora hid it
104
+ console.log('');
105
+ info('Login cancelled.');
106
+ process.exit(130);
107
+ });
108
+ }
109
+ async function runDeviceLogin() {
110
+ console.log('');
111
+ console.log(' Login to somewhere.tech');
112
+ console.log('');
113
+ let spinner = null;
114
+ installCancelHandler(() => spinner);
115
+ try {
116
+ const config = await deviceLogin({
117
+ onPrompt: ({ code, approvalUrl }) => {
118
+ console.log(` Code: ${teal(code)}`);
119
+ console.log('');
120
+ console.log(' Opening your browser…');
121
+ console.log(` If it doesn't open, visit: ${teal(approvalUrl)}`);
122
+ console.log('');
123
+ open(approvalUrl).catch(() => {
124
+ // Browser didn't open — URL is already printed above
125
+ });
126
+ },
127
+ onWaiting: () => {
128
+ spinner = ora({
129
+ text: 'Waiting for approval… (press Ctrl+C to cancel)',
130
+ color: 'cyan',
131
+ }).start();
132
+ },
133
+ });
134
+ spinner?.stop();
135
+ saveConfig(config);
136
+ saveGlobalMcpConfig(config.token);
137
+ success(`Logged in as ${teal(config.user.email || '(unknown)')}`);
138
+ success(`Device: ${getDeviceKeyName()}`);
139
+ success('Claude Code MCP configured');
140
+ process.exit(0);
141
+ }
142
+ catch (err) {
143
+ spinner?.stop();
144
+ if (err instanceof DeviceLoginTimeout) {
145
+ error(err.message);
146
+ process.exit(1);
147
+ }
148
+ if (err instanceof DeviceCodeUnsupported) {
149
+ info(dim('Falling back to localhost-callback login…'));
150
+ await runLegacyLogin();
151
+ return;
152
+ }
153
+ error(err instanceof Error ? err.message : String(err));
154
+ process.exit(1);
155
+ }
156
+ }
157
+ async function runLegacyLogin() {
158
+ const spinner = ora('Waiting for browser login… (press Ctrl+C to cancel)').start();
159
+ installCancelHandler(() => spinner);
160
+ try {
161
+ const config = await browserLogin();
162
+ saveConfig(config);
163
+ saveGlobalMcpConfig(config.token);
164
+ spinner.stop();
165
+ success(`Logged in as ${teal(config.user.email)}`);
166
+ success('Claude Code MCP configured');
167
+ process.exit(0);
168
+ }
169
+ catch (err) {
170
+ spinner.fail('Login failed');
171
+ error(err instanceof Error ? err.message : String(err));
172
+ process.exit(1);
173
+ }
61
174
  }
62
175
  //# sourceMappingURL=auth.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AACA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAY,UAAU,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACtG,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAEnE,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,8CAA8C,CAAC;SAC3D,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,MAAM,OAAO,GAAG,GAAG,CAAC,uCAAuC,CAAC,CAAC,KAAK,EAAE,CAAC;QACrE,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,YAAY,EAAE,CAAC;YACpC,UAAU,CAAC,MAAM,CAAC,CAAC;YACnB,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAClC,OAAO,CAAC,IAAI,EAAE,CAAC;YACf,OAAO,CAAC,gBAAgB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACnD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,IAAI,CAAC,iCAAiC,CAAC,CAAC;YACxC,IAAI,CAAC,6CAA6C,CAAC,CAAC;YACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAC7B,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,2BAA2B,CAAC;SACxC,MAAM,CAAC,GAAG,EAAE;QACX,WAAW,EAAE,CAAC;QACd,OAAO,CAAC,yDAAyD,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEL,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,wBAAwB,CAAC;SACrC,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,KAAK,CAAC,qCAAqC,CAAC,CAAC;YAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,CAI9B,KAAK,EAAE,iBAAiB,CAAC,CAAC;YAE7B,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAG/B,KAAK,EAAE,WAAW,CAAC,CAAC;YAEvB,MAAM,QAAQ,GAAG,QAAQ,CAAC,cAAc,IAAI,CAAC,CAAC;YAC9C,MAAM,KAAK,GAAG,CAAC,QAAQ,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAC5C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,OAAO,CAC5B,CAAC,MAAM,CAAC;YAET,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;YACpE,IAAI,CAAC,SAAS,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;YACjE,IAAI,CAAC,aAAa,QAAQ,CAAC,QAAQ,CAAC,MAAM,KAAK,QAAQ,cAAc,KAAK,SAAS,CAAC,CAAC;YACrF,IAAI,CAAC,YAAY,MAAM,CAAC,IAAI,CAAC,QAAQ,iBAAiB,CAAC,CAAC;QAC1D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YACrC,IAAI,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
1
4
  import { Command } from 'commander';
2
5
  import { registerAuth } from './commands/auth.js';
3
6
  import { registerInit } from './commands/init.js';
@@ -10,10 +13,11 @@ import { registerOpen } from './commands/open.js';
10
13
  import { registerDev } from './commands/dev.js';
11
14
  import { registerApi } from './commands/api.js';
12
15
  import { registerMcp } from './commands/mcp.js';
16
+ const pkg = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8'));
13
17
  const program = new Command()
14
18
  .name('somewhere')
15
19
  .description('CLI for somewhere.tech')
16
- .version('0.2.0');
20
+ .version(pkg.version);
17
21
  registerAuth(program);
18
22
  registerInit(program);
19
23
  registerProject(program);
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;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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,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,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CACnE,CAAC;AAEzB,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE;KAC1B,IAAI,CAAC,WAAW,CAAC;KACjB,WAAW,CAAC,wBAAwB,CAAC;KACrC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAExB,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"}
@@ -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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@somewhere-tech/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "description": "CLI for somewhere.tech — auth, projects, deploy, logs, env, MCP bridge.",
5
5
  "keywords": [
6
6
  "somewhere",