@jlongo78/agent-spaces 1.0.0 → 1.0.1
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/admin/users/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/admin/users/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/analytics/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/analytics/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/network/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/network/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/workspaces/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/workspaces/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.html +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.html +1 -1
- package/.next/standalone/.next/server/app/admin/users.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.html +1 -1
- package/.next/standalone/.next/server/app/analytics.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/admin/analytics/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/admin/os-users/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/admin/users/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/admin/users/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/analytics/overview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/me/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/totp/setup/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/totp/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/totp/verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bulk/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/chat/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/claude/usage/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/events/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/folders/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/discovered/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/handshake/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/health/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/identity/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/keys/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/keys/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/nodes/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/nodes/check/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/nodes/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/panes/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/panes/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/projects/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/proxy/[nodeId]/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/sessions/[id]/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/sessions/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/workspaces/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/workspaces/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/panes/[id]/diff/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/panes/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/panes/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/proxy/models/[modelId]/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/proxy/models/[modelId]/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/server-config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/chat/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sync/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/tags/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/tier/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/updates/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/whisper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wizard/chat/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/context/[key]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/context/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/messages/[msgId]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/[todoId]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/login/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/login.html +1 -1
- package/.next/standalone/.next/server/app/login.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/m/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/m/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/projects.html +1 -1
- package/.next/standalone/.next/server/app/m/projects.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/m/projects/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions/[id]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/m/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/sessions/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/m/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/sessions.html +1 -1
- package/.next/standalone/.next/server/app/m/sessions.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/m/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/settings.html +1 -1
- package/.next/standalone/.next/server/app/m/settings.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/m/settings/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/m/terminal/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/terminal.html +1 -1
- package/.next/standalone/.next/server/app/m/terminal.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.html +1 -1
- package/.next/standalone/.next/server/app/m.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/m/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/network.html +1 -1
- package/.next/standalone/.next/server/app/network.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.html +1 -1
- package/.next/standalone/.next/server/app/projects.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.html +1 -1
- package/.next/standalone/.next/server/app/sessions.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.html +1 -1
- package/.next/standalone/.next/server/app/terminal.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/vr/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/vr/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/vr.html +1 -1
- package/.next/standalone/.next/server/app/vr.rsc +2 -2
- package/.next/standalone/.next/server/app/vr.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/vr.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/vr.segments/vr/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.html +1 -1
- package/.next/standalone/.next/server/app/workspaces.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/chunks/_1-inpovedul0t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1yl1yt70hdi7k._.js +1 -1
- package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__00lhc9vpmf8oy._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/preview-props.json +3 -3
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/{3bk2w5qzafz7n.css → 1a5d9qolqzld9.css} +1 -1
- package/.next/standalone/package.json +6 -6
- package/README.md +19 -0
- package/bin/lib/cli-flags.js +2 -0
- package/bin/lib/remote.js +124 -15
- package/bin/spaces.js +330 -57
- package/package.json +6 -6
- /package/.next/standalone/.next/static/{_RTyuAfH-40lIlwVs2azh → FHvIomYPw2stnbhS8PRRv}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{_RTyuAfH-40lIlwVs2azh → FHvIomYPw2stnbhS8PRRv}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{_RTyuAfH-40lIlwVs2azh → FHvIomYPw2stnbhS8PRRv}/_ssgManifest.js +0 -0
package/bin/lib/remote.js
CHANGED
|
@@ -52,18 +52,20 @@ function validateDestination(dest) {
|
|
|
52
52
|
*/
|
|
53
53
|
/**
|
|
54
54
|
* `command` run through the remote user's login shell with nvm loaded — the
|
|
55
|
-
* PATH an interactive session would have
|
|
56
|
-
*
|
|
57
|
-
*
|
|
55
|
+
* PATH an interactive session would have — and ~/.local/bin first on it,
|
|
56
|
+
* where `--remote` installs Spaces when npm's own global folder belongs to
|
|
57
|
+
* root. `command` is spliced inside single quotes, so it must never contain
|
|
58
|
+
* one; every caller passes a constant or something built only from
|
|
59
|
+
* validated parts.
|
|
58
60
|
*/
|
|
59
61
|
function inLoginShell(command) {
|
|
60
62
|
if (command.includes("'")) throw new Error('a remote command cannot contain a single quote');
|
|
61
|
-
return `"\${SHELL:-sh}" -lc '[ -s "$HOME/.nvm/nvm.sh" ] && . "$HOME/.nvm/nvm.sh" >/dev/null 2>&1; ${command}'`;
|
|
63
|
+
return `"\${SHELL:-sh}" -lc '[ -s "$HOME/.nvm/nvm.sh" ] && . "$HOME/.nvm/nvm.sh" >/dev/null 2>&1; PATH="$HOME/.local/bin:$PATH"; ${command}'`;
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
function sshStartArgs(dest, { loginShell = false } = {}) {
|
|
65
|
-
const
|
|
66
|
-
return ['-o', 'ConnectTimeout=15', '--', dest,
|
|
66
|
+
function sshStartArgs(dest, { loginShell = false, login = false } = {}) {
|
|
67
|
+
const start = `spaces start --print-token${login ? ' --print-login' : ''}`;
|
|
68
|
+
return ['-o', 'ConnectTimeout=15', '--', dest, loginShell ? inLoginShell(start) : start];
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
// ─── Installing and updating the remote ────────────────────
|
|
@@ -88,8 +90,48 @@ const PROBE = inLoginShell([
|
|
|
88
90
|
'else echo SPACES_MISSING=1; fi',
|
|
89
91
|
].join(' '));
|
|
90
92
|
|
|
91
|
-
function sshProbeArgs(dest) {
|
|
92
|
-
return ['-o', 'ConnectTimeout=15', '--', dest, PROBE];
|
|
93
|
+
function sshProbeArgs(dest, { batch = false } = {}) {
|
|
94
|
+
return [...(batch ? ['-o', 'BatchMode=yes'] : []), '-o', 'ConnectTimeout=15', '--', dest, PROBE];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ─── Signing in with a key ──────────────────────────────────
|
|
98
|
+
|
|
99
|
+
/** The private keys plain `ssh` tries by default, in the order it tries them. */
|
|
100
|
+
const DEFAULT_KEYS = ['id_ed25519', 'id_ecdsa', 'id_rsa'];
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Why a no-prompt (`BatchMode`) ssh failed: `'password'` (the server wants a
|
|
104
|
+
* password, or another key), `'hostkey'` (ssh would have asked about an
|
|
105
|
+
* unknown host key), or `'other'`.
|
|
106
|
+
*/
|
|
107
|
+
function classifySshFailure(stderr) {
|
|
108
|
+
const text = String(stderr);
|
|
109
|
+
if (/Host key verification failed|REMOTE HOST IDENTIFICATION HAS CHANGED/i.test(text)) return 'hostkey';
|
|
110
|
+
if (/Permission denied/i.test(text)) return 'password';
|
|
111
|
+
return 'other';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* One public key line, as `ssh-keygen` writes it, or null. It travels to the
|
|
116
|
+
* remote on stdin rather than in the command, but is still checked: it is
|
|
117
|
+
* about to be trusted for every future sign-in there.
|
|
118
|
+
*/
|
|
119
|
+
function publicKeyLine(text) {
|
|
120
|
+
const line = String(text).split(/\r?\n/)[0].trim();
|
|
121
|
+
return /^(ssh-ed25519|ecdsa-sha2-nistp(256|384|521)|ssh-rsa|sk-ssh-ed25519@openssh\.com) [A-Za-z0-9+/]+=*( [^\r\n]*)?$/.test(line)
|
|
122
|
+
? line
|
|
123
|
+
: null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Adds the public key on stdin to ~/.ssh/authorized_keys on the remote,
|
|
128
|
+
* once, creating the file with the permissions sshd insists on. `sh -c`
|
|
129
|
+
* so it runs the same whatever the remote user's own shell is.
|
|
130
|
+
*/
|
|
131
|
+
const AUTHORIZE_KEY = `sh -c 'umask 077; mkdir -p "$HOME/.ssh" && k=$(cat) && touch "$HOME/.ssh/authorized_keys" && { grep -qxF "$k" "$HOME/.ssh/authorized_keys" || printf "%s\\n" "$k" >> "$HOME/.ssh/authorized_keys"; }'`;
|
|
132
|
+
|
|
133
|
+
function sshAuthorizeKeyArgs(dest) {
|
|
134
|
+
return ['-o', 'ConnectTimeout=15', '--', dest, AUTHORIZE_KEY];
|
|
93
135
|
}
|
|
94
136
|
|
|
95
137
|
const SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
@@ -165,12 +207,21 @@ function installSpec(localVersion, override) {
|
|
|
165
207
|
}
|
|
166
208
|
|
|
167
209
|
/**
|
|
168
|
-
* Installs `spec` on the remote
|
|
169
|
-
*
|
|
170
|
-
*
|
|
210
|
+
* Installs `spec` on the remote and starts it, in one SSH session — each
|
|
211
|
+
* session can mean another password prompt. Into npm's global folder when
|
|
212
|
+
* this account can write there (nvm's always can), else into ~/.local,
|
|
213
|
+
* which needs no root. For an update, `spaces restart` first puts the new
|
|
214
|
+
* version in charge: the freshly installed one, which knows to go through
|
|
215
|
+
* the service when there is one. Ends with what `spaces start
|
|
216
|
+
* --print-token` prints, so the caller need not ask again.
|
|
171
217
|
*/
|
|
172
|
-
function sshInstallArgs(dest, spec, { restart = false } = {}) {
|
|
173
|
-
const command =
|
|
218
|
+
function sshInstallArgs(dest, spec, { restart = false, login = false } = {}) {
|
|
219
|
+
const command = [
|
|
220
|
+
`if [ -w "$(npm prefix -g)/lib" ]; then npm install -g ${spec};`,
|
|
221
|
+
`else npm install -g --prefix "$HOME/.local" ${spec}; fi`,
|
|
222
|
+
restart ? '&& spaces restart' : '',
|
|
223
|
+
`&& spaces start --print-token${login ? ' --print-login' : ''}`,
|
|
224
|
+
].filter(Boolean).join(' ');
|
|
174
225
|
return ['-o', 'ConnectTimeout=15', '--', dest, inLoginShell(command)];
|
|
175
226
|
}
|
|
176
227
|
|
|
@@ -209,9 +260,13 @@ function installFailureHint(kind, dest, spec) {
|
|
|
209
260
|
}
|
|
210
261
|
}
|
|
211
262
|
|
|
212
|
-
/**
|
|
263
|
+
/**
|
|
264
|
+
* `ssh` arguments for the tunnel: local port → the remote server's loopback.
|
|
265
|
+
* `-v` so its log says when the forward is up (see tunnelReady).
|
|
266
|
+
*/
|
|
213
267
|
function sshTunnelArgs(dest, localPort, remotePort) {
|
|
214
268
|
return [
|
|
269
|
+
'-v',
|
|
215
270
|
'-N',
|
|
216
271
|
'-o', 'ExitOnForwardFailure=yes',
|
|
217
272
|
'-o', 'ServerAliveInterval=30',
|
|
@@ -221,6 +276,27 @@ function sshTunnelArgs(dest, localPort, remotePort) {
|
|
|
221
276
|
];
|
|
222
277
|
}
|
|
223
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Whether the tunnel's `ssh -v` log says the forward from `localPort` is
|
|
281
|
+
* listening. Waiting on the log rather than probing the port: a probe is a
|
|
282
|
+
* child process, and on Windows each one that exits resets the console,
|
|
283
|
+
* which garbles a password being typed into ssh's prompt meanwhile.
|
|
284
|
+
*/
|
|
285
|
+
function tunnelReady(log, localPort) {
|
|
286
|
+
const port = String(localPort).replace(/\D/g, '');
|
|
287
|
+
return new RegExp(`Local forwarding listening on 127\\.0\\.0\\.1 port ${port}\\b|Local connections to 127\\.0\\.0\\.1:${port} forwarded`).test(String(log));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Whether the tunnel's log shows ssh got past authentication. */
|
|
291
|
+
function tunnelAuthenticated(log) {
|
|
292
|
+
return /^Authenticated to /m.test(String(log));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** An `ssh -v` log without its debug chatter, for showing a failure. */
|
|
296
|
+
function withoutDebug(log) {
|
|
297
|
+
return String(log).split(/\r?\n/).filter((l) => !/^(debug\d:|OpenSSH_)/.test(l)).join('\n');
|
|
298
|
+
}
|
|
299
|
+
|
|
224
300
|
/** The port `spaces start` reported, or null. */
|
|
225
301
|
function parseStartOutput(stdout) {
|
|
226
302
|
const m = PORT_LINE.exec(String(stdout));
|
|
@@ -229,6 +305,29 @@ function parseStartOutput(stdout) {
|
|
|
229
305
|
return port >= 1 && port <= 65535 ? port : null;
|
|
230
306
|
}
|
|
231
307
|
|
|
308
|
+
/** The browser sign-in code `spaces start --print-login` reported, or null. */
|
|
309
|
+
function parseStartLogin(stdout) {
|
|
310
|
+
const m = /^SPACES_LOGIN=([A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)\s*$/m.exec(String(stdout));
|
|
311
|
+
return m ? m[1] : null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** The path the remote serves the web app under, or '' (also when malformed). */
|
|
315
|
+
function parseStartBasePath(stdout) {
|
|
316
|
+
const m = /^SPACES_BASE_PATH=(\/[A-Za-z0-9._~\/-]*?)\/?\s*$/m.exec(String(stdout));
|
|
317
|
+
return m ? m[1] : '';
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* The local port `spaces open --remote` tunnels from, the same each time for
|
|
322
|
+
* the same destination, so the address stays put in the browser's history.
|
|
323
|
+
* Within 41000–41999; another is used when this one is taken.
|
|
324
|
+
*/
|
|
325
|
+
function preferredLocalPort(dest) {
|
|
326
|
+
let h = 0;
|
|
327
|
+
for (const c of String(dest)) h = (h * 31 + c.charCodeAt(0)) >>> 0;
|
|
328
|
+
return 41000 + (h % 1000);
|
|
329
|
+
}
|
|
330
|
+
|
|
232
331
|
/** The session token `spaces start --print-token` reported, or null. */
|
|
233
332
|
function parseStartToken(stdout) {
|
|
234
333
|
const m = TOKEN_LINE.exec(String(stdout));
|
|
@@ -283,11 +382,21 @@ module.exports = {
|
|
|
283
382
|
sshTunnelArgs,
|
|
284
383
|
parseStartOutput,
|
|
285
384
|
parseStartToken,
|
|
385
|
+
parseStartLogin,
|
|
386
|
+
parseStartBasePath,
|
|
387
|
+
preferredLocalPort,
|
|
286
388
|
classifyStartFailure,
|
|
287
389
|
installHint,
|
|
288
390
|
splitRemote,
|
|
289
391
|
PACKAGE,
|
|
392
|
+
DEFAULT_KEYS,
|
|
393
|
+
classifySshFailure,
|
|
394
|
+
publicKeyLine,
|
|
395
|
+
sshAuthorizeKeyArgs,
|
|
290
396
|
inLoginShell,
|
|
397
|
+
tunnelReady,
|
|
398
|
+
tunnelAuthenticated,
|
|
399
|
+
withoutDebug,
|
|
291
400
|
sshProbeArgs,
|
|
292
401
|
parseProbe,
|
|
293
402
|
nodeIsSupported,
|