@shogo-ai/worker 1.7.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shogo Technologies, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # @shogo-ai/worker
2
+
3
+ Run Shogo Cloud Agents on a machine you already own — a laptop, devbox, or CI runner.
4
+ Outbound-only: the worker dials Shogo Cloud over HTTPS, never the other way around.
5
+
6
+ > Licensed **MIT**. The agent runtime it spawns is licensed AGPL-3.0-or-later
7
+ > and ships as a separate binary — see [Architecture & licensing](#architecture--licensing) below.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ # Requires node >= 20 (or bun >= 1.3)
13
+ npm i -g @shogo-ai/worker
14
+ ```
15
+
16
+ > Prefer a single self-contained binary? Grab the prebuilt tarball for your
17
+ > OS / arch from the [latest worker release](https://github.com/shogo-ai/shogo-ai/releases?q=worker-v)
18
+ > — it has no Node / Bun dependency.
19
+
20
+ ## Quick start
21
+
22
+ ```bash
23
+ # 1. Sign in (opens your browser; falls back to --api-key for CI)
24
+ shogo login
25
+
26
+ # 2. Download the AGPL agent-runtime binary into ~/.shogo/runtime/
27
+ shogo runtime install
28
+
29
+ # 3. Start the worker (detached by default)
30
+ shogo worker start --name my-devbox
31
+
32
+ # 4. Confirm it's online
33
+ shogo worker status
34
+
35
+ # 5. Open https://studio.shogo.ai — your machine appears in the
36
+ # environment dropdown next to the desktop entries.
37
+ ```
38
+
39
+ ### Headless / CI
40
+
41
+ ```bash
42
+ # Skip the browser flow with a personal API key from
43
+ # https://studio.shogo.ai/api-keys
44
+ shogo login --api-key "shogo_sk_..."
45
+
46
+ # Or skip `login` entirely
47
+ SHOGO_API_KEY=shogo_sk_... shogo runtime install
48
+ SHOGO_API_KEY=shogo_sk_... shogo worker start --foreground
49
+ ```
50
+
51
+ ## Commands
52
+
53
+ | Command | What it does |
54
+ |---------|--------------|
55
+ | `shogo login` | Browser device-code flow against `studio.shogo.ai`. `--api-key` / `SHOGO_API_KEY` for headless. `--workspace <id>` to pre-select. `--no-browser` to print the URL only. |
56
+ | `shogo runtime install` | Download + verify + extract the AGPL agent-runtime tarball into `~/.shogo/runtime/`. Flags: `--channel stable\|beta\|nightly`, `--version`, `--force`, `--base-url`. |
57
+ | `shogo runtime version` | Print the installed agent-runtime version. |
58
+ | `shogo runtime where` | Print the resolved binary path (priority order: `--runtime-bin` → `$SHOGO_AGENT_RUNTIME_BIN` → `~/.shogo/runtime/agent-runtime` → `$PATH`). |
59
+ | `shogo runtime update` | Reinstall the latest in-channel build. |
60
+ | `shogo worker start` | Pair this machine with Shogo Cloud. `--foreground` to attach to stdout (e.g. `systemd --user`); default detaches and writes `~/.shogo/worker.pid`. |
61
+ | `shogo worker stop` | Kill the running worker via PID file. |
62
+ | `shogo worker status` | Online / offline + uptime. |
63
+ | `shogo worker logs [-f]` | Tail `~/.shogo/logs/worker.log`. |
64
+ | `shogo config show` | Print config (API key masked). |
65
+ | `shogo config set <key> <value>` | Edit a single key. |
66
+
67
+ ## Files & layout
68
+
69
+ ```
70
+ ~/.shogo/
71
+ ├── config.json # API key + cloud URL (mode 0600)
72
+ ├── device-id # stable per-machine UUID; dedupes re-logins
73
+ ├── worker.pid # lifecycle file for `worker start/stop/status`
74
+ ├── logs/
75
+ │ ├── worker.log
76
+ │ └── worker.err.log
77
+ └── runtime/
78
+ ├── agent-runtime # AGPL binary, downloaded by `shogo runtime install`
79
+ └── version.json
80
+ ```
81
+
82
+ ## Networking
83
+
84
+ The worker needs outbound HTTPS (TCP 443) to 3 hosts. **No inbound ports required.**
85
+
86
+ | Host | Purpose | If blocked |
87
+ |------|---------|-----------|
88
+ | `studio.shogo.ai` | Sign-in, session, heartbeat, on-demand tunnel WS | **FATAL** — worker can't run |
89
+ | `api-direct.shogo.ai` | Direct tunnel fallback (when used) | Graceful — edge routing takes over |
90
+ | `github.com` / `objects.githubusercontent.com` | Runtime binary downloads (`shogo runtime install`) | Only `runtime install/update` affected; can be self-mirrored via `--base-url` |
91
+
92
+ ### Corporate proxy
93
+
94
+ ```bash
95
+ # HTTPS_PROXY / https_proxy / HTTP_PROXY / http_proxy all honoured
96
+ HTTPS_PROXY=http://proxy.corp:3128 shogo worker start
97
+
98
+ # Or pass explicitly (overrides env)
99
+ shogo worker start --proxy http://proxy.corp:3128
100
+
101
+ # TLS-inspecting proxy needs your corp root CA
102
+ NODE_EXTRA_CA_CERTS=/etc/ssl/corp-root.pem \
103
+ HTTPS_PROXY=http://proxy.corp:3128 \
104
+ shogo worker start --debug
105
+ ```
106
+
107
+ `shogo worker start --debug` runs a preflight that includes a proxy-reachability
108
+ check when `HTTPS_PROXY` is set.
109
+
110
+ ## Architecture & licensing
111
+
112
+ The worker is split into two processes that talk over localhost only:
113
+
114
+ ```
115
+ ┌────────────────────────────────────┐ ┌────────────────────────────────────┐
116
+ │ shogo (this package) │ │ agent-runtime │
117
+ │ ── MIT ────────────────────────── │ │ ── AGPL-3.0-or-later ──────────── │
118
+ │ │ │ │
119
+ │ • CLI (login / start / stop) │ │ • Runs your agents │
120
+ │ • WorkerTunnel: HTTP heartbeat + ├────► • Tools, LLM proxy, plans │
121
+ │ on-demand WS to Shogo Cloud │ │ │
122
+ │ • WorkerRuntimeManager: spawns │ │ │
123
+ │ one runtime per project │ │ │
124
+ │ │ │ │
125
+ │ Spawned by you / systemd / etc. │ │ Spawned by the worker (process │
126
+ │ │ │ boundary; not linked as library) │
127
+ └────────────────────────────────────┘ └────────────────────────────────────┘
128
+ ```
129
+
130
+ The MIT worker discovers and spawns the AGPL runtime as a separate OS process —
131
+ no library link, no dynamic import, no embed. This keeps the licenses cleanly
132
+ separated: you may consume `@shogo-ai/worker` from MIT / Apache-2.0 / proprietary
133
+ code without AGPL infecting it.
134
+
135
+ The runtime spawns themselves are managed by `WorkerRuntimeManager`: per-project
136
+ port allocation, env injection, restart-with-backoff, idle eviction, and health
137
+ checks. The same code path is consumed by Shogo Desktop (`apps/api`) for its own
138
+ agent runtimes — so any improvement made here ships to both.
139
+
140
+ ## Programmatic use
141
+
142
+ Both core classes are exposed for direct embedding (e.g. building your own
143
+ desktop wrapper):
144
+
145
+ ```ts
146
+ import { WorkerTunnel, WorkerRuntimeManager } from '@shogo-ai/worker'
147
+
148
+ const runtimeManager = new WorkerRuntimeManager({
149
+ defaultSpawnConfig: { cloudUrl: 'https://studio.shogo.ai', apiKey: process.env.SHOGO_API_KEY! },
150
+ })
151
+
152
+ const tunnel = new WorkerTunnel({
153
+ apiKey: process.env.SHOGO_API_KEY!,
154
+ cloudUrl: 'https://studio.shogo.ai',
155
+ resolver: runtimeManager, // implements RuntimeResolver
156
+ kind: 'cli-worker',
157
+ onAuthRevoked: (reason) => { /* re-login UX */ },
158
+ })
159
+ tunnel.start()
160
+ ```
161
+
162
+ ## Troubleshooting
163
+
164
+ ```bash
165
+ shogo worker start --debug # run preflight checks
166
+ shogo worker logs --follow # tail live logs
167
+ SHOGO_DEBUG=1 shogo worker status # verbose errors
168
+
169
+ # Runtime missing?
170
+ shogo runtime where # see what's resolved
171
+ shogo runtime install # (re)download the latest stable binary
172
+ ```
173
+
174
+ ## Links
175
+
176
+ - [Cloud Agent: My Machines guide](../../docs/cloud-agent-my-machines.md) — full
177
+ walk-through, security model, deploy patterns
178
+ - [Networking & firewall guide](../../docs/my-machines-networking.md)
179
+ - Source: [github.com/shogo-ai/shogo-ai](https://github.com/shogo-ai/shogo-ai)
package/bin/shogo.mjs ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (C) 2026 Shogo Technologies, Inc.
4
+ //
5
+ // `shogo` CLI entry shim. Picks an execution mode in this order:
6
+ //
7
+ // 1. Compiled per-platform binary at $PREFIX/dist/shogo (in the
8
+ // tarball release — Bun bundled, no runtime deps).
9
+ // 2. Bundled JS at ../dist/cli.mjs (npm install
10
+ // with no Bun on PATH — Node ESM-loadable).
11
+ // 3. Source TS at ../src/cli.ts (monorepo
12
+ // / `bun link`ed dev, requires Bun or tsx).
13
+ import { fileURLToPath } from 'node:url';
14
+ import { existsSync } from 'node:fs';
15
+ import { dirname, join } from 'node:path';
16
+ import { spawn } from 'node:child_process';
17
+
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ const args = process.argv.slice(2);
20
+
21
+ const compiledBin = join(__dirname, '..', 'dist', process.platform === 'win32' ? 'shogo.exe' : 'shogo');
22
+ const distEntry = join(__dirname, '..', 'dist', 'cli.mjs');
23
+ const srcEntry = join(__dirname, '..', 'src', 'cli.ts');
24
+
25
+ if (existsSync(compiledBin)) {
26
+ const child = spawn(compiledBin, args, { stdio: 'inherit' });
27
+ child.on('exit', (code) => process.exit(code ?? 0));
28
+ } else if (existsSync(distEntry)) {
29
+ await import(distEntry);
30
+ } else if (existsSync(srcEntry)) {
31
+ const runner = process.versions.bun ? 'bun' : 'tsx';
32
+ const child = spawn(runner, [srcEntry, ...args], { stdio: 'inherit' });
33
+ child.on('exit', (code) => process.exit(code ?? 0));
34
+ } else {
35
+ console.error('shogo: no executable entry found (looked for dist/shogo, dist/cli.mjs, src/cli.ts)');
36
+ console.error(' Reinstall with `npm i -g @shogo-ai/worker` or rebuild via `bun run build`.');
37
+ process.exit(1);
38
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@shogo-ai/worker",
3
+ "version": "1.7.4",
4
+ "description": "Shogo Cloud Agent Worker — run Shogo agents on your own machine (laptop, devbox, CI).",
5
+ "license": "MIT",
6
+ "author": "Shogo Technologies, Inc.",
7
+ "homepage": "https://shogo.ai/docs/cloud-agent/my-machines",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/shogo-labs/shogo-ai.git",
11
+ "directory": "packages/shogo-worker"
12
+ },
13
+ "bin": {
14
+ "shogo": "./bin/shogo.mjs"
15
+ },
16
+ "main": "./src/cli.ts",
17
+ "type": "module",
18
+ "exports": {
19
+ ".": "./src/cli.ts",
20
+ "./tunnel": "./src/lib/tunnel.ts",
21
+ "./runtime-manager": "./src/lib/runtime-manager.ts",
22
+ "./runtime-resolver": "./src/lib/runtime-resolver.ts",
23
+ "./runtime-install": "./src/lib/runtime-install.ts",
24
+ "./cloud-login": "./src/lib/cloud-login.ts",
25
+ "./paths": "./src/lib/paths.ts"
26
+ },
27
+ "files": [
28
+ "bin",
29
+ "src",
30
+ "README.md"
31
+ ],
32
+ "scripts": {
33
+ "start": "bun src/cli.ts",
34
+ "typecheck": "tsc --noEmit",
35
+ "build": "bun run build:js",
36
+ "build:js": "bun build --target=node --format=esm --minify --external bun --external @shogo-ai/sdk --external commander --external picocolors --outdir dist src/cli.ts && mv dist/cli.js dist/cli.mjs",
37
+ "build:bin:darwin-arm64": "bun build --compile --minify --target=bun-darwin-arm64 src/cli.ts --outfile dist/shogo-darwin-arm64",
38
+ "build:bin:darwin-x64": "bun build --compile --minify --target=bun-darwin-x64 src/cli.ts --outfile dist/shogo-darwin-x64",
39
+ "build:bin:linux-x64": "bun build --compile --minify --target=bun-linux-x64 src/cli.ts --outfile dist/shogo-linux-x64",
40
+ "build:bin:linux-arm64": "bun build --compile --minify --target=bun-linux-arm64 src/cli.ts --outfile dist/shogo-linux-arm64",
41
+ "build:bin:all": "bun run build:bin:darwin-arm64 && bun run build:bin:darwin-x64 && bun run build:bin:linux-x64 && bun run build:bin:linux-arm64",
42
+ "prepublishOnly": "bun run typecheck"
43
+ },
44
+ "dependencies": {
45
+ "@shogo-ai/sdk": "workspace:*",
46
+ "commander": "^12.1.0",
47
+ "picocolors": "^1.1.1"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^20.0.0",
51
+ "typescript": "^5.3.0"
52
+ },
53
+ "engines": {
54
+ "node": ">=20"
55
+ }
56
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,127 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (C) 2026 Shogo Technologies, Inc.
3
+ /**
4
+ * Shogo Worker CLI — entry.
5
+ *
6
+ * `shogo login` — save an API key to ~/.shogo/config.json
7
+ * `shogo worker start [flags]` — pair this machine with Shogo Cloud
8
+ * `shogo worker stop` — stop the local worker
9
+ * `shogo worker status` — show running/stopped
10
+ * `shogo worker logs [--follow]` — tail worker logs
11
+ * `shogo config show | set <k> <v>` — inspect/modify config
12
+ */
13
+ import { Command } from 'commander';
14
+ import pc from 'picocolors';
15
+ import { runStart } from './commands/start.ts';
16
+ import { runStop } from './commands/stop.ts';
17
+ import { runStatus } from './commands/status.ts';
18
+ import { runLogs } from './commands/logs.ts';
19
+ import { runLogin } from './commands/login.ts';
20
+ import { runConfigShow, runConfigSet } from './commands/config.ts';
21
+ import {
22
+ runRuntimeInstall,
23
+ runRuntimeUpdate,
24
+ runRuntimeVersion,
25
+ runRuntimeWhere,
26
+ } from './commands/runtime.ts';
27
+
28
+ const VERSION = '0.1.0';
29
+
30
+ const program = new Command();
31
+ program
32
+ .name('shogo')
33
+ .description('Shogo Cloud Agent Worker — run Shogo agents on your own machine.')
34
+ .version(VERSION);
35
+
36
+ program
37
+ .command('login')
38
+ .description('Pair this machine with Shogo Cloud (browser device flow, or --api-key for CI)')
39
+ .option('--api-key <key>', 'CI escape hatch — skip the browser flow and use a key directly')
40
+ .option('--cloud-url <url>', 'Shogo Cloud URL (default: https://studio.shogo.ai)')
41
+ .option('--name <name>', 'Device label shown in the dashboard (default: hostname)')
42
+ .option('--workspace <id>', 'Pre-select a workspace on the bridge picker')
43
+ .option('--no-browser', 'Do not auto-open the browser; print the URL instead')
44
+ .action((flags) => handle(() => runLogin(flags)));
45
+
46
+ const worker = program.command('worker').description('Manage the local worker process');
47
+
48
+ worker
49
+ .command('start')
50
+ .description('Start the worker and pair with Shogo Cloud')
51
+ .option('--name <name>', 'Instance name shown in the dashboard (default: hostname)')
52
+ .option('--worker-dir <path>', 'Working directory for the worker (default: $PWD)')
53
+ .option('--api-key <key>', 'API key (overrides config/env)')
54
+ .option('--cloud-url <url>', 'Shogo Cloud URL')
55
+ .option('--port <port>', 'Local HTTP port for the embedded API')
56
+ .option('--proxy <url>', 'HTTPS proxy (overrides HTTPS_PROXY env)')
57
+ .option('--project <id>', 'Pin to a single project (default: multi-project on demand)')
58
+ .option('--runtime-bin <path>', 'Override the agent-runtime binary path')
59
+ .option('--debug', 'Run preflight checks before starting')
60
+ .option('--foreground', 'Run in foreground (don\'t detach)')
61
+ .action((flags) => handle(() => runStart(flags)));
62
+
63
+ worker
64
+ .command('stop')
65
+ .description('Stop the running worker')
66
+ .action(() => handle(runStop));
67
+
68
+ worker
69
+ .command('status')
70
+ .description('Show worker status')
71
+ .action(() => handle(runStatus));
72
+
73
+ worker
74
+ .command('logs')
75
+ .description('Tail worker logs from ~/.shogo/logs/worker.log')
76
+ .option('-f, --follow', 'Follow the log (tail -F)')
77
+ .option('--err', 'Show stderr log instead')
78
+ .action((flags) => handle(() => runLogs(flags)));
79
+
80
+ const runtime = program
81
+ .command('runtime')
82
+ .description('Install / inspect the local agent-runtime binary');
83
+ runtime
84
+ .command('install')
85
+ .description('Download + verify the agent-runtime tarball into ~/.shogo/runtime/')
86
+ .option('--channel <channel>', 'stable | beta | nightly (default: stable)')
87
+ .option('--version <version>', 'install a specific version (e.g. 0.1.0)')
88
+ .option('--base-url <url>', 'override release base URL (default: GitHub Releases)')
89
+ .option('--force', 'reinstall even if the same version is already on disk')
90
+ .action((flags) => handle(() => runRuntimeInstall(flags)));
91
+ runtime
92
+ .command('version')
93
+ .description('Print the installed agent-runtime version')
94
+ .action(() => handle(() => runRuntimeVersion()));
95
+ runtime
96
+ .command('where')
97
+ .description('Print the resolved agent-runtime binary path')
98
+ .action(() => handle(() => runRuntimeWhere()));
99
+ runtime
100
+ .command('update')
101
+ .description('Update agent-runtime to the latest in its channel')
102
+ .option('--channel <channel>', 'override channel (defaults to whatever is installed)')
103
+ .option('--base-url <url>', 'override release base URL')
104
+ .action((flags) => handle(() => runRuntimeUpdate(flags)));
105
+
106
+ const config = program.command('config').description('Inspect or modify ~/.shogo/config.json');
107
+ config
108
+ .command('show')
109
+ .description('Print current config (API key masked)')
110
+ .action(() => handle(runConfigShow));
111
+ config
112
+ .command('set <key> <value>')
113
+ .description('Set apiKey / cloudUrl / name / workerDir / port')
114
+ .action((k: string, v: string) => handle(() => runConfigSet(k, v)));
115
+
116
+ program.showHelpAfterError(pc.dim('\n(use --help for usage)'));
117
+ program.parseAsync(process.argv);
118
+
119
+ async function handle(fn: () => Promise<void> | void): Promise<void> {
120
+ try {
121
+ await fn();
122
+ } catch (err: any) {
123
+ console.error(pc.red(`✗ ${err?.message ?? err}`));
124
+ if (process.env.SHOGO_DEBUG) console.error(err?.stack);
125
+ process.exit(1);
126
+ }
127
+ }
@@ -0,0 +1,29 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (C) 2026 Shogo Technologies, Inc.
3
+ import pc from 'picocolors';
4
+ import { loadConfig, saveConfig } from '../lib/config.ts';
5
+ import { CONFIG_FILE } from '../lib/paths.ts';
6
+
7
+ export async function runConfigShow(): Promise<void> {
8
+ const cfg = loadConfig();
9
+ if (Object.keys(cfg).length === 0) {
10
+ console.log(pc.dim('(no config — run `shogo login` or `shogo config set`)'));
11
+ return;
12
+ }
13
+ const masked = { ...cfg, apiKey: cfg.apiKey ? `***${cfg.apiKey.slice(-4)}` : undefined };
14
+ console.log(pc.dim(`file: ${CONFIG_FILE}`));
15
+ console.log(JSON.stringify(masked, null, 2));
16
+ }
17
+
18
+ export async function runConfigSet(key: string, value: string): Promise<void> {
19
+ const allowed = ['apiKey', 'cloudUrl', 'name', 'workerDir', 'port'] as const;
20
+ if (!allowed.includes(key as any)) {
21
+ console.error(pc.red(`Unknown key: ${key}`));
22
+ console.error(pc.dim(`Allowed: ${allowed.join(', ')}`));
23
+ process.exit(1);
24
+ }
25
+ const cfg = loadConfig();
26
+ (cfg as any)[key] = key === 'port' ? parseInt(value, 10) : value;
27
+ saveConfig(cfg);
28
+ console.log(pc.green(`✓ ${key} set`));
29
+ }
@@ -0,0 +1,126 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (C) 2026 Shogo Technologies, Inc.
3
+ /**
4
+ * `shogo login` — pair this machine with Shogo Cloud.
5
+ *
6
+ * Two modes, in priority order:
7
+ *
8
+ * 1. `--api-key <key>` flag, or `SHOGO_API_KEY` env var
9
+ * → CI / headless mode. Validate against cloud, save, done.
10
+ *
11
+ * 2. Interactive (default)
12
+ * → Poll-based device flow (see lib/cloud-login.ts):
13
+ * POST /api/cli/login/start → state + authUrl
14
+ * open authUrl in browser → user approves on cloud
15
+ * GET /api/cli/login/poll → key once approved
16
+ *
17
+ * The minted key is written to ~/.shogo/config.json with mode 0600 so
18
+ * other local users can't read it. The same file is what
19
+ * `shogo worker start` reads on boot.
20
+ *
21
+ * The legacy "paste a shogo_sk_ key at the prompt" flow is still
22
+ * supported via `--api-key`; it just isn't the default any more.
23
+ */
24
+ import pc from 'picocolors';
25
+ import { loadConfig, saveConfig } from '../lib/config.ts';
26
+ import { runCloudLogin, CloudLoginError } from '../lib/cloud-login.ts';
27
+ import { getOrCreateDeviceId } from '../lib/device-id.ts';
28
+
29
+ export interface LoginFlags {
30
+ apiKey?: string;
31
+ cloudUrl?: string;
32
+ name?: string;
33
+ workspace?: string;
34
+ noBrowser?: boolean;
35
+ }
36
+
37
+ const DEFAULT_CLOUD_URL = 'https://studio.shogo.ai';
38
+
39
+ export async function runLogin(flags: LoginFlags): Promise<void> {
40
+ const cfg = loadConfig();
41
+ const cloudUrl = (flags.cloudUrl || cfg.cloudUrl || DEFAULT_CLOUD_URL).replace(/\/$/, '');
42
+
43
+ const escapeKey = flags.apiKey || process.env.SHOGO_API_KEY;
44
+ if (escapeKey) {
45
+ await loginWithApiKey({ key: escapeKey, cloudUrl, cfg, name: flags.name });
46
+ return;
47
+ }
48
+
49
+ const deviceId = getOrCreateDeviceId();
50
+ let result;
51
+ try {
52
+ result = await runCloudLogin({
53
+ cloudUrl,
54
+ deviceId,
55
+ deviceName: flags.name,
56
+ workspaceId: flags.workspace,
57
+ openBrowser: !flags.noBrowser,
58
+ });
59
+ } catch (err: any) {
60
+ if (err instanceof CloudLoginError) {
61
+ console.error(pc.red(`✗ ${err.message}`));
62
+ if (err.kind === 'transport') {
63
+ console.error(pc.dim(` If your network blocks browsers, run with --api-key <key> instead.`));
64
+ }
65
+ process.exit(1);
66
+ }
67
+ throw err;
68
+ }
69
+
70
+ cfg.apiKey = result.key;
71
+ cfg.cloudUrl = cloudUrl;
72
+ if (flags.name) cfg.name = flags.name;
73
+ saveConfig(cfg);
74
+
75
+ console.log('');
76
+ console.log(pc.green('✓ Signed in to Shogo Cloud'));
77
+ if (result.workspace) console.log(pc.dim(' workspace: ') + result.workspace);
78
+ if (result.email) console.log(pc.dim(' email: ') + result.email);
79
+ console.log(pc.dim(' saved to: ') + '~/.shogo/config.json');
80
+ console.log('');
81
+ console.log(pc.dim(' next: ') + 'shogo worker start');
82
+ }
83
+
84
+ interface ApiKeyOpts {
85
+ key: string;
86
+ cloudUrl: string;
87
+ cfg: ReturnType<typeof loadConfig>;
88
+ name?: string;
89
+ }
90
+
91
+ async function loginWithApiKey({ key, cloudUrl, cfg, name }: ApiKeyOpts): Promise<void> {
92
+ if (!/^shogo_sk_/.test(key)) {
93
+ console.error(pc.red('✗ API key should start with "shogo_sk_". Copy it verbatim from the API Keys page.'));
94
+ process.exit(1);
95
+ }
96
+
97
+ // Mirror apps/api/src/routes/local-auth.ts: re-validate against cloud
98
+ // before persisting so the user gets immediate feedback on a bad key.
99
+ let validation: { valid?: boolean; error?: string; workspace?: { name?: string } | null; user?: { email?: string } | null };
100
+ try {
101
+ const res = await fetch(`${cloudUrl}/api/api-keys/validate`, {
102
+ method: 'POST',
103
+ headers: { 'content-type': 'application/json' },
104
+ body: JSON.stringify({ key }),
105
+ signal: AbortSignal.timeout(10_000),
106
+ });
107
+ validation = await res.json().catch(() => ({} as any)) as typeof validation;
108
+ if (!res.ok || !validation?.valid) {
109
+ console.error(pc.red(`✗ ${validation?.error || `Cloud rejected the key (HTTP ${res.status}).`}`));
110
+ process.exit(1);
111
+ }
112
+ } catch (err: any) {
113
+ console.error(pc.red(`✗ Cannot reach Shogo Cloud at ${cloudUrl}: ${err?.message ?? err}`));
114
+ process.exit(1);
115
+ }
116
+
117
+ cfg.apiKey = key;
118
+ cfg.cloudUrl = cloudUrl;
119
+ if (name) cfg.name = name;
120
+ saveConfig(cfg);
121
+
122
+ console.log(pc.green('✓ API key saved to ~/.shogo/config.json'));
123
+ if (validation.workspace?.name) console.log(pc.dim(' workspace: ') + validation.workspace.name);
124
+ if (validation.user?.email) console.log(pc.dim(' email: ') + validation.user.email);
125
+ console.log(pc.dim(' next: ') + 'shogo worker start');
126
+ }
@@ -0,0 +1,24 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (C) 2026 Shogo Technologies, Inc.
3
+ import { spawn } from 'node:child_process';
4
+ import { existsSync, statSync } from 'node:fs';
5
+ import pc from 'picocolors';
6
+ import { WORKER_LOG, WORKER_ERR } from '../lib/paths.ts';
7
+
8
+ export interface LogsFlags { follow?: boolean; err?: boolean }
9
+
10
+ export async function runLogs(flags: LogsFlags): Promise<void> {
11
+ const file = flags.err ? WORKER_ERR : WORKER_LOG;
12
+ if (!existsSync(file)) {
13
+ console.log(pc.dim('No logs yet.'));
14
+ return;
15
+ }
16
+ const args = flags.follow ? ['-F', '-n', '200', file] : ['-n', '200', file];
17
+ const child = spawn('tail', args, { stdio: 'inherit' });
18
+ child.on('exit', (code) => process.exit(code ?? 0));
19
+
20
+ if (!flags.follow) {
21
+ const size = statSync(file).size;
22
+ if (size === 0) console.log(pc.dim('(log file is empty)'));
23
+ }
24
+ }