deskssh 0.0.1 → 0.0.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 +8 -63
- package/package.json +24 -38
- package/.eslintrc.cjs +0 -21
- package/.github/workflows/ci.yml +0 -40
- package/.prettierignore +0 -5
- package/.prettierrc.json +0 -6
- package/CONTRIBUTING.md +0 -59
- package/LICENSE +0 -661
- package/packages/cli/README.md +0 -11
- package/packages/cli/package.json +0 -37
- package/packages/core/dist/index.d.ts +0 -2
- package/packages/core/dist/index.d.ts.map +0 -1
- package/packages/core/dist/index.js +0 -6
- package/packages/core/dist/index.js.map +0 -1
- package/packages/core/package.json +0 -22
- package/packages/core/src/index.test.ts +0 -8
- package/packages/core/src/index.ts +0 -6
- package/packages/core/tsconfig.json +0 -9
- package/packages/server/dist/index.d.ts +0 -3
- package/packages/server/dist/index.d.ts.map +0 -1
- package/packages/server/dist/index.js +0 -7
- package/packages/server/dist/index.js.map +0 -1
- package/packages/server/package.json +0 -19
- package/packages/server/src/index.ts +0 -8
- package/packages/server/tsconfig.json +0 -10
- package/packages/web/dist/assets/index-DNUNZ8WK.js +0 -65
- package/packages/web/dist/index.html +0 -12
- package/packages/web/index.html +0 -12
- package/packages/web/node_modules/.bin/browserslist +0 -17
- package/packages/web/node_modules/.bin/vite +0 -17
- package/packages/web/package.json +0 -27
- package/packages/web/src/App.tsx +0 -17
- package/packages/web/src/i18n/en.ts +0 -8
- package/packages/web/src/i18n/es.ts +0 -7
- package/packages/web/src/i18n/index.ts +0 -27
- package/packages/web/src/main.tsx +0 -12
- package/packages/web/tsconfig.json +0 -14
- package/packages/web/vite.config.ts +0 -6
- package/pnpm-workspace.yaml +0 -2
- package/specs/001-core/plan.md +0 -246
- package/specs/001-core/spec.md +0 -206
- package/specs/001-core/tasks.md +0 -110
- package/specs/constitution.md +0 -110
- package/specs/glossary.md +0 -35
- package/specs/vision.md +0 -145
- package/tsconfig.base.json +0 -23
- package/tsconfig.json +0 -4
- package/vitest.config.ts +0 -7
- /package/{packages/cli/bin → bin}/deskssh.js +0 -0
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>DeskSSH</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-DNUNZ8WK.js"></script>
|
|
8
|
-
</head>
|
|
9
|
-
<body>
|
|
10
|
-
<div id="root"></div>
|
|
11
|
-
</body>
|
|
12
|
-
</html>
|
package/packages/web/index.html
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>DeskSSH</title>
|
|
7
|
-
</head>
|
|
8
|
-
<body>
|
|
9
|
-
<div id="root"></div>
|
|
10
|
-
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
-
</body>
|
|
12
|
-
</html>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
-
|
|
4
|
-
case `uname` in
|
|
5
|
-
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
|
6
|
-
esac
|
|
7
|
-
|
|
8
|
-
if [ -z "$NODE_PATH" ]; then
|
|
9
|
-
export NODE_PATH="/home/nestor/Proyectos/DeskSSH/node_modules/.pnpm/browserslist@4.28.4/node_modules/browserslist/node_modules:/home/nestor/Proyectos/DeskSSH/node_modules/.pnpm/browserslist@4.28.4/node_modules:/home/nestor/Proyectos/DeskSSH/node_modules/.pnpm/node_modules"
|
|
10
|
-
else
|
|
11
|
-
export NODE_PATH="/home/nestor/Proyectos/DeskSSH/node_modules/.pnpm/browserslist@4.28.4/node_modules/browserslist/node_modules:/home/nestor/Proyectos/DeskSSH/node_modules/.pnpm/browserslist@4.28.4/node_modules:/home/nestor/Proyectos/DeskSSH/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
12
|
-
fi
|
|
13
|
-
if [ -x "$basedir/node" ]; then
|
|
14
|
-
exec "$basedir/node" "$basedir/../../../../node_modules/.pnpm/browserslist@4.28.4/node_modules/browserslist/cli.js" "$@"
|
|
15
|
-
else
|
|
16
|
-
exec node "$basedir/../../../../node_modules/.pnpm/browserslist@4.28.4/node_modules/browserslist/cli.js" "$@"
|
|
17
|
-
fi
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
-
|
|
4
|
-
case `uname` in
|
|
5
|
-
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
|
6
|
-
esac
|
|
7
|
-
|
|
8
|
-
if [ -z "$NODE_PATH" ]; then
|
|
9
|
-
export NODE_PATH="/home/nestor/Proyectos/DeskSSH/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.43/node_modules/vite/bin/node_modules:/home/nestor/Proyectos/DeskSSH/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.43/node_modules/vite/node_modules:/home/nestor/Proyectos/DeskSSH/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.43/node_modules:/home/nestor/Proyectos/DeskSSH/node_modules/.pnpm/node_modules"
|
|
10
|
-
else
|
|
11
|
-
export NODE_PATH="/home/nestor/Proyectos/DeskSSH/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.43/node_modules/vite/bin/node_modules:/home/nestor/Proyectos/DeskSSH/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.43/node_modules/vite/node_modules:/home/nestor/Proyectos/DeskSSH/node_modules/.pnpm/vite@5.4.21_@types+node@20.19.43/node_modules:/home/nestor/Proyectos/DeskSSH/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
12
|
-
fi
|
|
13
|
-
if [ -x "$basedir/node" ]; then
|
|
14
|
-
exec "$basedir/node" "$basedir/../vite/bin/vite.js" "$@"
|
|
15
|
-
else
|
|
16
|
-
exec node "$basedir/../vite/bin/vite.js" "$@"
|
|
17
|
-
fi
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@deskssh/web",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"description": "DeskSSH browser UI: the desktop shell and app views",
|
|
5
|
-
"license": "AGPL-3.0-or-later",
|
|
6
|
-
"private": true,
|
|
7
|
-
"type": "module",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"dev": "vite",
|
|
10
|
-
"build": "tsc --noEmit && vite build",
|
|
11
|
-
"preview": "vite preview",
|
|
12
|
-
"typecheck": "tsc --noEmit",
|
|
13
|
-
"clean": "rm -rf dist"
|
|
14
|
-
},
|
|
15
|
-
"dependencies": {
|
|
16
|
-
"@deskssh/core": "workspace:*",
|
|
17
|
-
"lucide-react": "^0.395.0",
|
|
18
|
-
"react": "^18.3.1",
|
|
19
|
-
"react-dom": "^18.3.1"
|
|
20
|
-
},
|
|
21
|
-
"devDependencies": {
|
|
22
|
-
"@types/react": "^18.3.3",
|
|
23
|
-
"@types/react-dom": "^18.3.0",
|
|
24
|
-
"@vitejs/plugin-react": "^4.3.1",
|
|
25
|
-
"vite": "^5.3.1"
|
|
26
|
-
}
|
|
27
|
-
}
|
package/packages/web/src/App.tsx
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
import { TerminalSquare } from 'lucide-react';
|
|
3
|
-
import { detectLocale, translate } from './i18n';
|
|
4
|
-
|
|
5
|
-
export function App() {
|
|
6
|
-
const locale = useMemo(() => detectLocale(), []);
|
|
7
|
-
|
|
8
|
-
return (
|
|
9
|
-
<main>
|
|
10
|
-
<h1>
|
|
11
|
-
<TerminalSquare aria-hidden /> DeskSSH
|
|
12
|
-
</h1>
|
|
13
|
-
<p>{translate(locale, 'app.tagline')}</p>
|
|
14
|
-
<small>{translate(locale, 'app.status.scaffolding')}</small>
|
|
15
|
-
</main>
|
|
16
|
-
);
|
|
17
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// English catalog (source of truth). Every user-facing string lives here so the
|
|
2
|
-
// UI is i18n-ready from day 1 (constitution-aligned: EN + ES ship in v1).
|
|
3
|
-
export const en = {
|
|
4
|
-
'app.tagline': 'A graphical desktop over plain SSH',
|
|
5
|
-
'app.status.scaffolding': 'Scaffolding — no functionality yet',
|
|
6
|
-
} as const;
|
|
7
|
-
|
|
8
|
-
export type MessageKey = keyof typeof en;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { MessageKey } from './en';
|
|
2
|
-
|
|
3
|
-
// Spanish catalog. Must cover every MessageKey (enforced by the Record type).
|
|
4
|
-
export const es: Record<MessageKey, string> = {
|
|
5
|
-
'app.tagline': 'Un escritorio gráfico sobre SSH puro',
|
|
6
|
-
'app.status.scaffolding': 'Andamiaje — todavía sin funcionalidad',
|
|
7
|
-
};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { en, type MessageKey } from './en';
|
|
2
|
-
import { es } from './es';
|
|
3
|
-
|
|
4
|
-
export type Locale = 'en' | 'es';
|
|
5
|
-
|
|
6
|
-
const catalogs: Record<Locale, Record<MessageKey, string>> = { en, es };
|
|
7
|
-
|
|
8
|
-
export const SUPPORTED_LOCALES: readonly Locale[] = ['en', 'es'];
|
|
9
|
-
export const DEFAULT_LOCALE: Locale = 'en';
|
|
10
|
-
|
|
11
|
-
/** Pick the best supported locale from the browser, falling back to English. */
|
|
12
|
-
export function detectLocale(
|
|
13
|
-
preferred: readonly string[] = typeof navigator !== 'undefined' ? navigator.languages : [],
|
|
14
|
-
): Locale {
|
|
15
|
-
for (const lang of preferred) {
|
|
16
|
-
const base = lang.toLowerCase().split('-')[0];
|
|
17
|
-
if (base && SUPPORTED_LOCALES.includes(base as Locale)) return base as Locale;
|
|
18
|
-
}
|
|
19
|
-
return DEFAULT_LOCALE;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Translate a key for a locale. Missing translations fall back to English. */
|
|
23
|
-
export function translate(locale: Locale, key: MessageKey): string {
|
|
24
|
-
return catalogs[locale][key] ?? en[key];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export type { MessageKey };
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { StrictMode } from 'react';
|
|
2
|
-
import { createRoot } from 'react-dom/client';
|
|
3
|
-
import { App } from './App';
|
|
4
|
-
|
|
5
|
-
const container = document.getElementById('root');
|
|
6
|
-
if (!container) throw new Error('Root element #root not found');
|
|
7
|
-
|
|
8
|
-
createRoot(container).render(
|
|
9
|
-
<StrictMode>
|
|
10
|
-
<App />
|
|
11
|
-
</StrictMode>,
|
|
12
|
-
);
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"composite": false,
|
|
5
|
-
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
6
|
-
"jsx": "react-jsx",
|
|
7
|
-
"module": "ESNext",
|
|
8
|
-
"moduleResolution": "Bundler",
|
|
9
|
-
"noEmit": true,
|
|
10
|
-
"allowImportingTsExtensions": true,
|
|
11
|
-
"types": ["vite/client"]
|
|
12
|
-
},
|
|
13
|
-
"include": ["src", "vite.config.ts"]
|
|
14
|
-
}
|
package/pnpm-workspace.yaml
DELETED
package/specs/001-core/plan.md
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
# Technical plan — 001 Core
|
|
2
|
-
|
|
3
|
-
**How** to build what [`spec.md`](./spec.md) describes, respecting
|
|
4
|
-
[`constitution.md`](../constitution.md). The stack proposals are the recommended
|
|
5
|
-
starting point, not dogma; open decisions are marked `[NEEDS DECISION]`.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 1. Delivery model (one web app, two distributions)
|
|
10
|
-
|
|
11
|
-
Decision of record: **agnostic core + web-first; a single web app delivered two
|
|
12
|
-
ways — hosted, or self-hosted via npm. No native desktop wrapper.**
|
|
13
|
-
|
|
14
|
-
Reasons:
|
|
15
|
-
|
|
16
|
-
- The user wants "anyone to be able to access it on the network" → favors web.
|
|
17
|
-
- The **browser cannot open raw SSH/TCP connections**, so the SSH session must live
|
|
18
|
-
in a **backend** (gateway). That forces, anyway, a core separable from the UI
|
|
19
|
-
(Art. 5).
|
|
20
|
-
- The offline/LAN need is met by **self-hosting the same web app via npm** (run it
|
|
21
|
-
locally, open it in the browser), not by a native desktop build. Local users
|
|
22
|
-
install from npm; there is **no Tauri/Electron app**.
|
|
23
|
-
|
|
24
|
-
Result: **a single logic base and one web UI**, distributed as **hosted** or
|
|
25
|
-
**self-hosted (npm)**. **Confirmed (2026-06-25; native desktop dropped 2026-06-26).**
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
┌────────────────────────────┐ ┌───────────────────────────────┐
|
|
29
|
-
│ Frontend (browser UI) │ WS/ │ Backend (DeskSSH gateway) │
|
|
30
|
-
│ desktop shell + UI │ <────> │ SSH sessions + API │
|
|
31
|
-
│ React + TS │ HTTP │ uses the CORE │
|
|
32
|
-
└────────────────────────────┘ │ ┌────────────────────────┐ │ SSH
|
|
33
|
-
│ │ core (agnostic) │ │ <─────> Remote
|
|
34
|
-
│ │ adapters, parsers, │ │ host
|
|
35
|
-
│ │ apps, sessions │ │ (POSIX)
|
|
36
|
-
│ └────────────────────────┘ │
|
|
37
|
-
└───────────────────────────────┘
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## 2. Layered architecture
|
|
41
|
-
|
|
42
|
-
1. **`core`** (agnostic, no UI nor presentation network I/O):
|
|
43
|
-
- _Session manager_: abstraction over an SSH connection (run command, open PTY,
|
|
44
|
-
open SFTP).
|
|
45
|
-
- _OS adapters_: detection + family-specific commands (Art. 6).
|
|
46
|
-
- _Parsers_: turn command output into data structures; with fallback to raw
|
|
47
|
-
output (Art. 7).
|
|
48
|
-
- _Apps_: each app defines what data it requests and what commands it runs (file
|
|
49
|
-
manager, processes, services, monitor, editor, logs).
|
|
50
|
-
- _Transparency_: every executed command is logged/exposed (Art. 3).
|
|
51
|
-
2. **`server`** (web gateway): keeps SSH sessions alive, authenticates the user,
|
|
52
|
-
exposes an API (HTTP for one-off actions, WebSocket for PTY and streams),
|
|
53
|
-
isolates sessions between users.
|
|
54
|
-
3. **`web`** (frontend): desktop shell (windows, taskbar, launcher) and each app's
|
|
55
|
-
views. Talks to `server`, never to SSH directly.
|
|
56
|
-
4. **Distribution**: the same `server` + `web` runs **hosted** or **self-hosted via
|
|
57
|
-
npm** (local/LAN). No separate native app.
|
|
58
|
-
|
|
59
|
-
## 3. Proposed stack (v1)
|
|
60
|
-
|
|
61
|
-
Core stack **confirmed (2026-06-26): TypeScript + Node + `ssh2` + React.** UI
|
|
62
|
-
details (styling framework, icon set) still open — see §8. Alternatives noted.
|
|
63
|
-
|
|
64
|
-
- **Language:** TypeScript across the stack → a single language lowers the
|
|
65
|
-
contribution barrier (Art. 9).
|
|
66
|
-
- **Monorepo:** pnpm workspaces. Packages: `core`, `server`, `web` (and later
|
|
67
|
-
`desktop`).
|
|
68
|
-
- **Backend (`server`):** Node.js + the `ssh2` SSH library (mature, supports exec,
|
|
69
|
-
PTY and SFTP) + WebSocket (`ws`). _Alternative:_ Rust (`russh`) for
|
|
70
|
-
security/performance, at the cost of a higher entry barrier → discarded for v1.
|
|
71
|
-
- **Frontend (`web`):** React + TypeScript. Terminal with **`xterm.js`**.
|
|
72
|
-
Movable/resizable windows with a lightweight library (e.g. `react-rnd` style) or
|
|
73
|
-
custom components.
|
|
74
|
-
- _Styling/components:_ **Tailwind CSS + Radix UI via shadcn/ui** (all **MIT** —
|
|
75
|
-
AGPL-compatible). Tailwind for the custom desktop look; Radix primitives bring
|
|
76
|
-
built-in accessibility (focus, keyboard, ARIA), key for the accessibility-first
|
|
77
|
-
goal; shadcn/ui delivers them as in-repo, editable components.
|
|
78
|
-
- _Icon set:_ **Lucide** (`lucide-react`, **ISC** — AGPL-compatible, no
|
|
79
|
-
attribution), chosen for its clean, uniform stroke style fitting the
|
|
80
|
-
accessibility-first UI. (Considered: Tabler/Phosphor/Heroicons MIT, Material
|
|
81
|
-
Symbols Apache-2.0, Font Awesome Free CC BY; FA Pro proprietary — avoided.)
|
|
82
|
-
- **Distribution:** published to **npm** so local/LAN users self-host the web app;
|
|
83
|
-
a hosted deployment serves internet-open servers. No native desktop build.
|
|
84
|
-
|
|
85
|
-
## 4. Core design
|
|
86
|
-
|
|
87
|
-
### Capability contract (adapter IR)
|
|
88
|
-
|
|
89
|
-
The core defines a **capability contract**: a closed catalog of abstract, **typed**
|
|
90
|
-
operations that apps invoke **without knowing which OS they run on**. It is the
|
|
91
|
-
system's "intermediate language" (an _intermediate representation_, IR). Two pieces:
|
|
92
|
-
|
|
93
|
-
1. **Contract (typed interface).** Each capability declares inputs and, above all, a
|
|
94
|
-
**normalized output**. Examples:
|
|
95
|
-
- `listDir(path) → FileEntry[]` — not text, but an array of
|
|
96
|
-
`{ name, type, size, mode, owner, mtime }`.
|
|
97
|
-
- `listProcesses() → Process[]`
|
|
98
|
-
- `readFile(path) → bytes` · `writeFile(path, bytes) → void`
|
|
99
|
-
- `serviceAction(name, action) → ServiceState`
|
|
100
|
-
|
|
101
|
-
The value is in the **output type**: if an app knows `listDir` returns a
|
|
102
|
-
`FileEntry[]`, it no longer cares how it was obtained nor on which platform.
|
|
103
|
-
|
|
104
|
-
2. **Adapter manifests (declarative) + escape hatch.** Each platform implements the
|
|
105
|
-
contract. 80% of cases **declaratively** (command template + output normalization
|
|
106
|
-
spec); the hard 20% (busybox, PowerShell) with a **code hook**.
|
|
107
|
-
|
|
108
|
-
**Rules that hold it together:**
|
|
109
|
-
|
|
110
|
-
- Apps **never** parse raw output; they only consume contract types. Parsing and its
|
|
111
|
-
_fallback_ live in the adapter (reinforces Art. 7).
|
|
112
|
-
- **Normalize at the source**: request structured output (`stat -c`, `ps -eo`,
|
|
113
|
-
`ConvertTo-Json`…) instead of parsing human format.
|
|
114
|
-
- **Capability gaps**: if a platform doesn't support an operation (e.g. `chmod` on
|
|
115
|
-
Windows), the adapter declares it _unsupported_ and the UI degrades gracefully
|
|
116
|
-
instead of pretending.
|
|
117
|
-
|
|
118
|
-
#### Non-interactive primitives > driving TUIs
|
|
119
|
-
|
|
120
|
-
A corollary of the contract (and the answer to "how to emulate nano"): DeskSSH
|
|
121
|
-
**does not drive remote interactive tools** (nano, vim, top…) by sending keystrokes
|
|
122
|
-
over a PTY —it would be fragile and impossible to normalize—. It uses
|
|
123
|
-
**non-interactive, structured primitives** and **emulates the experience on the
|
|
124
|
-
client**:
|
|
125
|
-
|
|
126
|
-
- **Editor** = `readFile` + `writeFile` + a custom GUI editor (no remote nano is
|
|
127
|
-
launched).
|
|
128
|
-
- **Monitor** = `listProcesses`/`systemMetrics` by _polling_, not a live `top`.
|
|
129
|
-
- The **only** deliberate exception is the **terminal** app, which does expose the
|
|
130
|
-
raw shell (there the user sees `bash`/`PowerShell`/`csh`).
|
|
131
|
-
|
|
132
|
-
### OS adapters
|
|
133
|
-
|
|
134
|
-
- The contract above exposes a **uniform interface** (`listDir`, `stat`,
|
|
135
|
-
`listProcesses`, `listServices`, `serviceAction`, `systemMetrics`, …).
|
|
136
|
-
- Each **OS family** is an adapter implementing that contract, declaratively when
|
|
137
|
-
possible. **v1 covers only Debian/Ubuntu/Mint** (see host roadmap below).
|
|
138
|
-
- Detection on connect (`/etc/os-release`, `uname`), with a generic POSIX fallback
|
|
139
|
-
adapter for not-yet-supported Unix-likes.
|
|
140
|
-
- Prefer machine-readable output (`stat -c '%n|%s|%a|...'`, `ps -eo ...`, `--json`
|
|
141
|
-
flags where available) over parsing human format.
|
|
142
|
-
|
|
143
|
-
#### Supported-host roadmap
|
|
144
|
-
|
|
145
|
-
The tier number indicates **roadmap priority, NOT difficulty** (see the _Effort_
|
|
146
|
-
column). Windows is prioritized for popularity despite being the most costly.
|
|
147
|
-
|
|
148
|
-
| Tier | Hosts | Effort | Notes |
|
|
149
|
-
| ---------- | ------------------------------------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
150
|
-
| **1** (v1) | Debian / Ubuntu / Mint | base | POSIX + GNU coreutils + systemd |
|
|
151
|
-
| **2** | Windows | **high** | Non-POSIX: its own PowerShell adapter family. Still agentless (PowerShell ships with the OS). Prioritized for popularity, not ease. |
|
|
152
|
-
| **3** | Rest of mainstream Linux (RHEL/Fedora/Rocky, Arch, openSUSE) | low | Same paradigm as v1 (systemd + GNU); differ mainly in the package manager |
|
|
153
|
-
| **4** | macOS, FreeBSD | medium | BSD userland; init `launchd` (macOS) / `rc.d` (FreeBSD), not systemd |
|
|
154
|
-
| **5** | Alpine | medium | `busybox` (trimmed flags), OpenRC, musl |
|
|
155
|
-
|
|
156
|
-
> Constitution note: when Tier 2 arrives, the **"POSIX utilities" wording of Art. 2
|
|
157
|
-
> will need generalizing** (still agentless, but no longer POSIX).
|
|
158
|
-
|
|
159
|
-
### File opening: handlers and routes (FR-025)
|
|
160
|
-
|
|
161
|
-
- **File-type handler registry:** each DeskSSH app declares which types it can open.
|
|
162
|
-
It is the basis of "Open" (default handler) and "Open with" (choose handler), and
|
|
163
|
-
the seed of extensibility for future apps.
|
|
164
|
-
- **Two opening routes:**
|
|
165
|
-
- **(A) Render in DeskSSH:** the contract's `readFile` → painted by a GUI handler.
|
|
166
|
-
With a **size limit**/warning (Art. 8); no handler for the type → route B is
|
|
167
|
-
offered.
|
|
168
|
-
- **(B) _Handoff_ to the client:** download over **streaming SFTP** (don't load
|
|
169
|
-
into memory) to the user's local machine.
|
|
170
|
-
- **Browser limitation (`[NEEDS DECISION]`):** DeskSSH always runs in a browser
|
|
171
|
-
(hosted or self-hosted), so "Open on the client" can only **download** (and inline
|
|
172
|
-
depending on type); it cannot force the OS's default app. Resolve as plain
|
|
173
|
-
download, inline-by-type, or both.
|
|
174
|
-
|
|
175
|
-
### Parsers and resilience
|
|
176
|
-
|
|
177
|
-
- Each parser receives output + exit code; on unexpected format it returns a
|
|
178
|
-
"degraded" result with the raw output (Art. 7), never throwing and breaking.
|
|
179
|
-
|
|
180
|
-
### Transparency
|
|
181
|
-
|
|
182
|
-
- Every execution goes through a single point that logs `{command, host, timestamp,
|
|
183
|
-
exitCode}` and makes it queryable from the UI (FR-013, Art. 3).
|
|
184
|
-
|
|
185
|
-
### Performance (Art. 8)
|
|
186
|
-
|
|
187
|
-
- VFS listing cache with per-action invalidation.
|
|
188
|
-
- Batching related commands into a single invocation when possible.
|
|
189
|
-
- Optimistic UI on file operations, with reconciliation.
|
|
190
|
-
|
|
191
|
-
## 5. Security (Art. 4)
|
|
192
|
-
|
|
193
|
-
- The backend is the critical surface: gateway user authentication, strict per-user
|
|
194
|
-
session isolation, rate limiting.
|
|
195
|
-
- Secrets: never in plain text nor in logs. **v1 does not persist** (ask each
|
|
196
|
-
session, in-memory only); an encrypted store (OS keychain / local encryption with
|
|
197
|
-
a derived key) is post-v1 (FR-005).
|
|
198
|
-
- Destructive actions: mandatory confirmation at the app layer (FR-090).
|
|
199
|
-
- SSH host key verification (avoid MITM); `known_hosts` policy.
|
|
200
|
-
- Auditing: the transparency log also serves as an audit trail.
|
|
201
|
-
|
|
202
|
-
## 6. Phases / milestones
|
|
203
|
-
|
|
204
|
-
**v1 = focused cut, accessibility first.** v1 app set: connection/hosts, desktop
|
|
205
|
-
shell, file manager, text editor, terminal and **system monitor**. Processes,
|
|
206
|
-
services, log viewer and packages are **post-v1**.
|
|
207
|
-
|
|
208
|
-
- **M0 — Scaffolding:** monorepo, empty packages, basic CI, license, contributing.
|
|
209
|
-
- **M1 — Connection core:** SSH session (exec/PTY/SFTP) + OS detection +
|
|
210
|
-
Debian/Ubuntu adapter + capability-contract base. Demonstrable via tests/minimal
|
|
211
|
-
CLI.
|
|
212
|
-
- **M2 — Shell + Terminal + File manager:** first usable desktop.
|
|
213
|
-
- **M3 — Text editor + System monitor + transparency in the UI:** **completes v1.**
|
|
214
|
-
- **── 🚀 v1 release ──**
|
|
215
|
-
- **Post-v1 (admin apps):** Processes + Services + Log viewer + Packages.
|
|
216
|
-
- **Post-v1 (hosts):** go down the tier roadmap (Windows → rest of Linux → …).
|
|
217
|
-
- **Post-v1 (hosted):** a hosted web deployment for internet-open servers
|
|
218
|
-
(self-hosted npm is available from v1).
|
|
219
|
-
|
|
220
|
-
## 7. Risks and mitigations
|
|
221
|
-
|
|
222
|
-
| Risk | Impact | Mitigation |
|
|
223
|
-
| ------------------------------------------ | --------------------- | --------------------------------------------------- |
|
|
224
|
-
| Command output varies by OS/locale/version | Fragile parsing | Adapters + machine output + raw fallback (Art. 6/7) |
|
|
225
|
-
| Latency from round trips | Slow UX | Cache, batching, optimistic UI (Art. 8) |
|
|
226
|
-
| Backend = exposed SSH gateway | High security risk | Auth, isolation, host keys, auditing (§5) |
|
|
227
|
-
| App over-scope | v1 never ships | Lock a minimal app subset per milestone |
|
|
228
|
-
| Coupling logic to the UI | Breaks future desktop | Strict agnostic core (Art. 5) |
|
|
229
|
-
|
|
230
|
-
## 8. Decisions
|
|
231
|
-
|
|
232
|
-
**Closed (2026-06-25):**
|
|
233
|
-
|
|
234
|
-
- **Web-first** + agnostic core as the v1 architecture.
|
|
235
|
-
- **License AGPL-3.0-or-later** (see `constitution.md` and `LICENSE`).
|
|
236
|
-
- **User #1 = accessibility**; **focused v1** (app set in §6).
|
|
237
|
-
|
|
238
|
-
**Closed (2026-06-26):**
|
|
239
|
-
|
|
240
|
-
- **Core stack: TypeScript + Node + `ssh2` + React.**
|
|
241
|
-
- **UI: Tailwind CSS + Radix UI (via shadcn/ui)** — all MIT.
|
|
242
|
-
- **Icon set: Lucide** (ISC — AGPL-compatible, no attribution required).
|
|
243
|
-
- **No native desktop app:** local/LAN use = self-hosted **npm** web app.
|
|
244
|
-
- **v1 credentials: not persisted** — asked per session (FR-005).
|
|
245
|
-
|
|
246
|
-
**Open:** product-level decisions tracked in spec §9 (`ssh-agent`, i18n scope).
|
package/specs/001-core/spec.md
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
# Functional specification — 001 Core (base experience)
|
|
2
|
-
|
|
3
|
-
Defines **what** DeskSSH does and **why**, without going into how it is implemented
|
|
4
|
-
(that's in `plan.md`). Open decisions are marked `[NEEDS DECISION]` and listed at
|
|
5
|
-
the end.
|
|
6
|
-
|
|
7
|
-
Related: [`../constitution.md`](../constitution.md) · [`../glossary.md`](../glossary.md)
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## 1. Problem
|
|
12
|
-
|
|
13
|
-
Administering a Linux server with no graphical environment forces you to know the
|
|
14
|
-
command line. Existing graphical alternatives:
|
|
15
|
-
|
|
16
|
-
- **Remote desktop (VNC/RDP/X)**: requires an installed graphical environment and
|
|
17
|
-
consumes bandwidth transmitting pixels; unviable on headless servers.
|
|
18
|
-
- **Web panels (Cockpit, Webmin)**: require installing and maintaining an agent on
|
|
19
|
-
the server, and offer a "web form" UX, not a desktop one.
|
|
20
|
-
|
|
21
|
-
There is no tool that gives a **real desktop experience** over **any server with
|
|
22
|
-
just SSH**, installing nothing and transmitting no pixels.
|
|
23
|
-
|
|
24
|
-
## 2. Value proposition
|
|
25
|
-
|
|
26
|
-
DeskSSH presents a **familiar desktop** (windows, file manager, terminal, monitor,
|
|
27
|
-
editor, services) whose backend is **plain SSH**: every interaction is translated
|
|
28
|
-
into commands executed on the host. No agents, no streaming, and with
|
|
29
|
-
**transparency**: you can always see the command behind the click.
|
|
30
|
-
|
|
31
|
-
## 3. Goals (v1)
|
|
32
|
-
|
|
33
|
-
- G1. Connect to remote hosts over SSH (key or password) and manage them.
|
|
34
|
-
- G2. Offer a desktop shell with windows and basic multitasking.
|
|
35
|
-
- G3. Include an initial set of useful apps (see §6).
|
|
36
|
-
- G4. Work **agentless** on common Linux servers.
|
|
37
|
-
- G5. Make the equivalent command of each action visible (transparency).
|
|
38
|
-
|
|
39
|
-
## 4. Non-goals (v1)
|
|
40
|
-
|
|
41
|
-
- Desktop streaming or remote graphical applications (forbidden by the
|
|
42
|
-
constitution).
|
|
43
|
-
- Windows, macOS, \*BSD and other Linux distros as remote host: **out of v1**,
|
|
44
|
-
planned in the host roadmap (Tier 2+, see `plan.md §4`).
|
|
45
|
-
- Multi-user / real-time collaboration over the same session.
|
|
46
|
-
- Fleet orchestration (managing many servers at once).
|
|
47
|
-
- Third-party app/plugin store (the architecture will allow it, but not in v1).
|
|
48
|
-
|
|
49
|
-
## 5. Personas and use cases
|
|
50
|
-
|
|
51
|
-
> **v1 primary persona: "User with low CLI fluency".** v1 prioritizes
|
|
52
|
-
> **accessibility**: safe, guided defaults, plain language, the terminal as a last
|
|
53
|
-
> resort and transparency (Art. 3) presented in an _educational, on-demand_ way,
|
|
54
|
-
> not as the protagonist. Other personas are served, but they do not steer the
|
|
55
|
-
> design.
|
|
56
|
-
|
|
57
|
-
- **⭐ User with a VPS but low CLI fluency (PRIMARY)** — manages their server with a
|
|
58
|
-
GUI without having to master the terminal. _"I want to manage my server without
|
|
59
|
-
the console intimidating me."_
|
|
60
|
-
- **Sysadmin / DevOps** — manages VPSs and servers; wants speed and to see what
|
|
61
|
-
runs. _"I review services and logs without memorizing flags."_
|
|
62
|
-
- **Developer** — deploys to a VPS; wants to manage files and processes
|
|
63
|
-
comfortably. _"I upload a build and restart the service without opening 3
|
|
64
|
-
terminals."_
|
|
65
|
-
- **Person learning Linux** — command transparency teaches them. _"I see what
|
|
66
|
-
command each thing I click does."_
|
|
67
|
-
|
|
68
|
-
### Main journey
|
|
69
|
-
|
|
70
|
-
1. The user adds a host (address, user, authentication method).
|
|
71
|
-
2. They connect; DeskSSH detects the OS and opens the **desktop**.
|
|
72
|
-
3. They open the **file manager**, browse, copy a file (seeing the confirmation
|
|
73
|
-
and, optionally, the command).
|
|
74
|
-
4. They open the **system monitor** and check CPU/memory/disk at a glance.
|
|
75
|
-
(Service/process management arrives _post-v1_.)
|
|
76
|
-
5. They open a real **terminal** for something specific. They close the session.
|
|
77
|
-
|
|
78
|
-
## 6. Functional requirements
|
|
79
|
-
|
|
80
|
-
> **v1 app scope (focused cut):** Connection/hosts, Desktop shell, File manager,
|
|
81
|
-
> Text editor, Terminal and **System monitor**. The apps marked _(post-v1)_ below
|
|
82
|
-
> —Processes, Services, Log viewer, Packages— are specified here but implemented
|
|
83
|
-
> after v1 (see `plan.md §6`).
|
|
84
|
-
|
|
85
|
-
### Connection and hosts
|
|
86
|
-
|
|
87
|
-
- **FR-001** Add, edit and remove hosts (name, address, port, user).
|
|
88
|
-
- **FR-002** Authentication via: (a) **SSH private key** —the user provides the key
|
|
89
|
-
file (**PEM / OpenSSH / PKCS#8** formats), with **optional passphrase**—, and
|
|
90
|
-
(b) **password**. (Key protection/storage is governed by FR-005 and constitution
|
|
91
|
-
Art. 4.) `ssh-agent` support is **post-v1**.
|
|
92
|
-
- **FR-003** Connect/disconnect; show session state (connecting, alive, dropped,
|
|
93
|
-
error).
|
|
94
|
-
- **FR-004** Detect the host's OS family to choose the adapter (Art. 6).
|
|
95
|
-
- **FR-005** Never persist secrets in plain text (Art. 4). **v1: do not persist
|
|
96
|
-
credentials** — entered per session and kept only in memory while connected; an
|
|
97
|
-
encrypted store (OS keychain / local encryption) may come post-v1.
|
|
98
|
-
|
|
99
|
-
### Desktop shell
|
|
100
|
-
|
|
101
|
-
- **FR-010** Show a desktop with movable/resizable windows and a taskbar.
|
|
102
|
-
- **FR-011** App launcher ("start menu") to open the available apps.
|
|
103
|
-
- **FR-012** Support multiple windows/apps open simultaneously over one session.
|
|
104
|
-
- **FR-013** Visible indicator to inspect the command of the last action
|
|
105
|
-
(transparency, Art. 3).
|
|
106
|
-
|
|
107
|
-
### App: File manager
|
|
108
|
-
|
|
109
|
-
- **FR-020** Browse the remote directory tree with icons and details.
|
|
110
|
-
- **FR-021** Create folder, rename, copy, move and delete (delete/overwrite require
|
|
111
|
-
confirmation, Art. 4).
|
|
112
|
-
- **FR-022** View properties (size, permissions, owner, dates).
|
|
113
|
-
- **FR-023** Upload and download files between the user's machine and the host.
|
|
114
|
-
- **FR-024** Drag & drop in the file manager — **post-v1** (not in the focused v1
|
|
115
|
-
cut).
|
|
116
|
-
- **FR-025** When opening a file, the user chooses between two **execution
|
|
117
|
-
locations**:
|
|
118
|
-
- **(A) In DeskSSH** — _Open_ (default DeskSSH app for that type) or _Open with_
|
|
119
|
-
(choose among DeskSSH apps/viewers). The file is read via `readFile` and
|
|
120
|
-
rendered in the GUI; **nothing runs on the remote** (Art. 10). Requires a
|
|
121
|
-
DeskSSH _handler_ for that type; if none, option (B) is offered.
|
|
122
|
-
- **(B) On the client** — _Open on the client_ (downloaded over SFTP, streaming,
|
|
123
|
-
to the **user's local machine** and opened with their OS's default program) or
|
|
124
|
-
_Download_ (just save locally).
|
|
125
|
-
|
|
126
|
-
Vocabulary note: **"the client" = the user's local machine** (their browser/OS in
|
|
127
|
-
the web model), not the DeskSSH server.
|
|
128
|
-
Since DeskSSH always runs in a browser (hosted or self-hosted npm), it cannot
|
|
129
|
-
force opening with the OS's default program. `[NEEDS DECISION]` does "Open on the
|
|
130
|
-
client" resolve as a **plain download**, as **inline opening** depending on type,
|
|
131
|
-
or both?
|
|
132
|
-
|
|
133
|
-
### App: Terminal
|
|
134
|
-
|
|
135
|
-
- **FR-030** Real interactive terminal (PTY over SSH) with resizing.
|
|
136
|
-
- **FR-031** Reuse the already-connected host's SSH session.
|
|
137
|
-
|
|
138
|
-
### App: Processes / Task manager _(post-v1)_
|
|
139
|
-
|
|
140
|
-
- **FR-040** List processes (CPU/mem usage, PID, user, command).
|
|
141
|
-
- **FR-041** Terminate a process (mandatory confirmation).
|
|
142
|
-
|
|
143
|
-
### App: System monitor
|
|
144
|
-
|
|
145
|
-
- **FR-050** Show CPU, memory, disk and uptime, with periodic refresh.
|
|
146
|
-
|
|
147
|
-
### App: Service manager _(post-v1)_
|
|
148
|
-
|
|
149
|
-
- **FR-060** List services (systemd first) and their state.
|
|
150
|
-
- **FR-061** Start, stop and restart services (mandatory confirmation).
|
|
151
|
-
- **FR-062** View a service's recent state/log.
|
|
152
|
-
|
|
153
|
-
### App: Text editor
|
|
154
|
-
|
|
155
|
-
- **FR-070** Open, edit and save remote text files.
|
|
156
|
-
- **FR-071** Warn about unsaved edits on close.
|
|
157
|
-
|
|
158
|
-
### App: Log viewer _(post-v1)_
|
|
159
|
-
|
|
160
|
-
- **FR-080** View and follow (`tail -f`/`journalctl -f`) logs in streaming.
|
|
161
|
-
|
|
162
|
-
### Cross-cutting
|
|
163
|
-
|
|
164
|
-
- **FR-090** Every destructive action asks for explicit confirmation (Art. 4).
|
|
165
|
-
- **FR-091** If parsing output fails, show the raw output without breaking the app
|
|
166
|
-
(Art. 7).
|
|
167
|
-
- **FR-092** Show latency/state of in-flight network operations (Art. 8).
|
|
168
|
-
|
|
169
|
-
## 7. Non-functional requirements
|
|
170
|
-
|
|
171
|
-
- **NFR-Security** — Fully comply with Article 4 of the constitution.
|
|
172
|
-
- **NFR-Portability** — v1 covers **Debian/Ubuntu/Mint** as remote host; support for
|
|
173
|
-
more OSes is added via adapters (Art. 6) following the **host roadmap**
|
|
174
|
-
(`plan.md §4`).
|
|
175
|
-
- **NFR-Performance** — Common operations (list a folder, refresh the monitor)
|
|
176
|
-
perceptibly fluid at typical network latencies; minimize round trips.
|
|
177
|
-
- **NFR-Resilience** — No parsing/network failure crashes the app (Art. 7).
|
|
178
|
-
- **NFR-Accessibility / i18n** — Keyboard-navigable UI; **i18n-ready from day 1**
|
|
179
|
-
(all strings externalized) and **v1 ships EN + ES**.
|
|
180
|
-
- **NFR-Openness** — Stack and dependencies 100% open source (Art. 9).
|
|
181
|
-
|
|
182
|
-
## 8. Acceptance criteria (v1, high level)
|
|
183
|
-
|
|
184
|
-
- A user can add a real Linux host, connect and open the desktop.
|
|
185
|
-
- They can browse files, open a working terminal, view processes/services and edit a
|
|
186
|
-
file, all agentless.
|
|
187
|
-
- Every destructive action asks for confirmation; no secret is stored in plain text.
|
|
188
|
-
- A parsing failure on an "odd" host shows raw output without crashing.
|
|
189
|
-
|
|
190
|
-
## 9. Open decisions `[NEEDS DECISION]`
|
|
191
|
-
|
|
192
|
-
1. ~~Open source license~~ → **Resolved (2026-06-25): AGPL-3.0-or-later** (see
|
|
193
|
-
`constitution.md` and `LICENSE`).
|
|
194
|
-
2. ~~Confirm web-first~~ → **Resolved: web-first** (2026-06-25), one web app
|
|
195
|
-
delivered hosted or self-hosted via npm; **no native desktop** (2026-06-26). See
|
|
196
|
-
`plan.md §1`.
|
|
197
|
-
3. ~~`ssh-agent`~~ → **Resolved (2026-06-26): post-v1** (v1 = key PEM/passphrase +
|
|
198
|
-
password) (FR-002).
|
|
199
|
-
4. ~~Credential store~~ → **Resolved (2026-06-26): v1 does not persist** (ask each
|
|
200
|
-
session); encrypted store post-v1 (FR-005).
|
|
201
|
-
5. ~~Drag & drop~~ → **Resolved (2026-06-26): post-v1** (FR-024).
|
|
202
|
-
6. ~~i18n scope~~ → **Resolved (2026-06-26): i18n-ready from day 1, v1 ships EN +
|
|
203
|
-
ES** (NFR-Accessibility/i18n).
|
|
204
|
-
7. ~~v1 app set~~ → **Resolved (2026-06-25):** focused cut = Connection/hosts,
|
|
205
|
-
Shell, File manager, Editor, Terminal and System monitor; the rest _(post-v1)_
|
|
206
|
-
(see §6 and `plan.md §6`).
|