sh3-core 0.6.0
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 +9 -0
- package/dist/Shell.svelte +283 -0
- package/dist/Shell.svelte.d.ts +5 -0
- package/dist/api.d.ts +28 -0
- package/dist/api.js +50 -0
- package/dist/app/admin/ApiKeysView.svelte +169 -0
- package/dist/app/admin/ApiKeysView.svelte.d.ts +3 -0
- package/dist/app/admin/AuthSettingsView.svelte +105 -0
- package/dist/app/admin/AuthSettingsView.svelte.d.ts +3 -0
- package/dist/app/admin/SystemView.svelte +73 -0
- package/dist/app/admin/SystemView.svelte.d.ts +3 -0
- package/dist/app/admin/UsersView.svelte +188 -0
- package/dist/app/admin/UsersView.svelte.d.ts +3 -0
- package/dist/app/admin/adminApp.d.ts +7 -0
- package/dist/app/admin/adminApp.js +25 -0
- package/dist/app/admin/adminShard.svelte.d.ts +4 -0
- package/dist/app/admin/adminShard.svelte.js +62 -0
- package/dist/app/store/InstalledView.svelte +246 -0
- package/dist/app/store/InstalledView.svelte.d.ts +3 -0
- package/dist/app/store/StoreView.svelte +522 -0
- package/dist/app/store/StoreView.svelte.d.ts +3 -0
- package/dist/app/store/storeApp.d.ts +10 -0
- package/dist/app/store/storeApp.js +26 -0
- package/dist/app/store/storeShard.svelte.d.ts +38 -0
- package/dist/app/store/storeShard.svelte.js +218 -0
- package/dist/apps/lifecycle.d.ts +42 -0
- package/dist/apps/lifecycle.js +184 -0
- package/dist/apps/registry.svelte.d.ts +40 -0
- package/dist/apps/registry.svelte.js +59 -0
- package/dist/apps/terminal/manifest.d.ts +8 -0
- package/dist/apps/terminal/manifest.js +13 -0
- package/dist/apps/terminal/terminal-app.d.ts +7 -0
- package/dist/apps/terminal/terminal-app.js +14 -0
- package/dist/apps/types.d.ts +93 -0
- package/dist/apps/types.js +10 -0
- package/dist/artifact.d.ts +32 -0
- package/dist/artifact.js +1 -0
- package/dist/assets/SH3.png +0 -0
- package/dist/assets/icons.svg +1126 -0
- package/dist/assets.d.ts +13 -0
- package/dist/auth/GuestBanner.svelte +134 -0
- package/dist/auth/GuestBanner.svelte.d.ts +3 -0
- package/dist/auth/SignInWall.svelte +203 -0
- package/dist/auth/SignInWall.svelte.d.ts +7 -0
- package/dist/auth/auth.svelte.d.ts +69 -0
- package/dist/auth/auth.svelte.js +165 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/types.d.ts +41 -0
- package/dist/auth/types.js +6 -0
- package/dist/build.d.ts +49 -0
- package/dist/build.js +236 -0
- package/dist/contract.d.ts +20 -0
- package/dist/contract.js +28 -0
- package/dist/createShell.d.ts +24 -0
- package/dist/createShell.js +131 -0
- package/dist/documents/backends.d.ts +17 -0
- package/dist/documents/backends.js +156 -0
- package/dist/documents/config.d.ts +7 -0
- package/dist/documents/config.js +27 -0
- package/dist/documents/handle.d.ts +6 -0
- package/dist/documents/handle.js +154 -0
- package/dist/documents/http-backend.d.ts +22 -0
- package/dist/documents/http-backend.js +78 -0
- package/dist/documents/index.d.ts +6 -0
- package/dist/documents/index.js +8 -0
- package/dist/documents/notifications.d.ts +9 -0
- package/dist/documents/notifications.js +39 -0
- package/dist/documents/types.d.ts +97 -0
- package/dist/documents/types.js +12 -0
- package/dist/env/client.d.ts +44 -0
- package/dist/env/client.js +106 -0
- package/dist/env/index.d.ts +2 -0
- package/dist/env/index.js +1 -0
- package/dist/env/types.d.ts +12 -0
- package/dist/env/types.js +8 -0
- package/dist/host-entry.d.ts +13 -0
- package/dist/host-entry.js +17 -0
- package/dist/host.d.ts +15 -0
- package/dist/host.js +86 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +14 -0
- package/dist/layout/DragPreview.svelte +63 -0
- package/dist/layout/DragPreview.svelte.d.ts +3 -0
- package/dist/layout/LayoutRenderer.svelte +262 -0
- package/dist/layout/LayoutRenderer.svelte.d.ts +6 -0
- package/dist/layout/SlotContainer.svelte +140 -0
- package/dist/layout/SlotContainer.svelte.d.ts +8 -0
- package/dist/layout/SlotDropZone.svelte +122 -0
- package/dist/layout/SlotDropZone.svelte.d.ts +8 -0
- package/dist/layout/drag.svelte.d.ts +45 -0
- package/dist/layout/drag.svelte.js +200 -0
- package/dist/layout/inspection.d.ts +72 -0
- package/dist/layout/inspection.js +209 -0
- package/dist/layout/ops.d.ts +100 -0
- package/dist/layout/ops.js +310 -0
- package/dist/layout/slotHostPool.svelte.d.ts +36 -0
- package/dist/layout/slotHostPool.svelte.js +229 -0
- package/dist/layout/store.svelte.d.ts +39 -0
- package/dist/layout/store.svelte.js +153 -0
- package/dist/layout/tree-walk.d.ts +15 -0
- package/dist/layout/tree-walk.js +33 -0
- package/dist/layout/types.d.ts +108 -0
- package/dist/layout/types.js +25 -0
- package/dist/migrations/shell-rename.d.ts +16 -0
- package/dist/migrations/shell-rename.js +48 -0
- package/dist/overlays/ModalFrame.svelte +87 -0
- package/dist/overlays/ModalFrame.svelte.d.ts +10 -0
- package/dist/overlays/PopupFrame.svelte +85 -0
- package/dist/overlays/PopupFrame.svelte.d.ts +10 -0
- package/dist/overlays/ToastItem.svelte +77 -0
- package/dist/overlays/ToastItem.svelte.d.ts +9 -0
- package/dist/overlays/focusTrap.d.ts +1 -0
- package/dist/overlays/focusTrap.js +64 -0
- package/dist/overlays/modal.d.ts +9 -0
- package/dist/overlays/modal.js +141 -0
- package/dist/overlays/popup.d.ts +9 -0
- package/dist/overlays/popup.js +108 -0
- package/dist/overlays/roots.d.ts +4 -0
- package/dist/overlays/roots.js +31 -0
- package/dist/overlays/toast.d.ts +6 -0
- package/dist/overlays/toast.js +93 -0
- package/dist/overlays/types.d.ts +31 -0
- package/dist/overlays/types.js +15 -0
- package/dist/platform/index.d.ts +10 -0
- package/dist/platform/index.js +33 -0
- package/dist/platform/tauri-backend.d.ts +15 -0
- package/dist/platform/tauri-backend.js +58 -0
- package/dist/primitives/.gitkeep +0 -0
- package/dist/primitives/ResizableSplitter.svelte +333 -0
- package/dist/primitives/ResizableSplitter.svelte.d.ts +35 -0
- package/dist/primitives/TabbedPanel.svelte +305 -0
- package/dist/primitives/TabbedPanel.svelte.d.ts +50 -0
- package/dist/primitives/base.css +42 -0
- package/dist/registry/client.d.ts +74 -0
- package/dist/registry/client.js +117 -0
- package/dist/registry/index.d.ts +13 -0
- package/dist/registry/index.js +14 -0
- package/dist/registry/installer.d.ts +53 -0
- package/dist/registry/installer.js +168 -0
- package/dist/registry/integrity.d.ts +32 -0
- package/dist/registry/integrity.js +92 -0
- package/dist/registry/loader.d.ts +50 -0
- package/dist/registry/loader.js +145 -0
- package/dist/registry/schema.d.ts +47 -0
- package/dist/registry/schema.js +185 -0
- package/dist/registry/storage.d.ts +37 -0
- package/dist/registry/storage.js +101 -0
- package/dist/registry/types.d.ts +262 -0
- package/dist/registry/types.js +14 -0
- package/dist/server-shard/types.d.ts +67 -0
- package/dist/server-shard/types.js +13 -0
- package/dist/sh3core-shard/ShellHome.svelte +192 -0
- package/dist/sh3core-shard/ShellHome.svelte.d.ts +3 -0
- package/dist/sh3core-shard/ShellTitle.svelte +171 -0
- package/dist/sh3core-shard/ShellTitle.svelte.d.ts +3 -0
- package/dist/sh3core-shard/sh3coreShard.svelte.d.ts +2 -0
- package/dist/sh3core-shard/sh3coreShard.svelte.js +53 -0
- package/dist/shards/activate.svelte.d.ts +52 -0
- package/dist/shards/activate.svelte.js +186 -0
- package/dist/shards/registry.d.ts +4 -0
- package/dist/shards/registry.js +28 -0
- package/dist/shards/types.d.ts +207 -0
- package/dist/shards/types.js +20 -0
- package/dist/shell-shard/InputLine.svelte +133 -0
- package/dist/shell-shard/InputLine.svelte.d.ts +11 -0
- package/dist/shell-shard/ScrollbackView.svelte +47 -0
- package/dist/shell-shard/ScrollbackView.svelte.d.ts +7 -0
- package/dist/shell-shard/Terminal.svelte +122 -0
- package/dist/shell-shard/Terminal.svelte.d.ts +8 -0
- package/dist/shell-shard/entries/PromptEntry.svelte +25 -0
- package/dist/shell-shard/entries/PromptEntry.svelte.d.ts +7 -0
- package/dist/shell-shard/entries/RichEntry.svelte +19 -0
- package/dist/shell-shard/entries/RichEntry.svelte.d.ts +8 -0
- package/dist/shell-shard/entries/StatusEntry.svelte +22 -0
- package/dist/shell-shard/entries/StatusEntry.svelte.d.ts +7 -0
- package/dist/shell-shard/entries/TextEntry.svelte +25 -0
- package/dist/shell-shard/entries/TextEntry.svelte.d.ts +7 -0
- package/dist/shell-shard/manifest.d.ts +2 -0
- package/dist/shell-shard/manifest.js +11 -0
- package/dist/shell-shard/protocol.d.ts +90 -0
- package/dist/shell-shard/protocol.js +11 -0
- package/dist/shell-shard/registry.d.ts +69 -0
- package/dist/shell-shard/registry.js +47 -0
- package/dist/shell-shard/rich/AppCard.svelte +25 -0
- package/dist/shell-shard/rich/AppCard.svelte.d.ts +10 -0
- package/dist/shell-shard/rich/AppsTable.svelte +29 -0
- package/dist/shell-shard/rich/AppsTable.svelte.d.ts +12 -0
- package/dist/shell-shard/rich/EnvTable.svelte +27 -0
- package/dist/shell-shard/rich/EnvTable.svelte.d.ts +8 -0
- package/dist/shell-shard/rich/HelpTable.svelte +29 -0
- package/dist/shell-shard/rich/HelpTable.svelte.d.ts +12 -0
- package/dist/shell-shard/rich/HistoryList.svelte +37 -0
- package/dist/shell-shard/rich/HistoryList.svelte.d.ts +9 -0
- package/dist/shell-shard/rich/ShardsTable.svelte +28 -0
- package/dist/shell-shard/rich/ShardsTable.svelte.d.ts +12 -0
- package/dist/shell-shard/rich/ViewsTable.svelte +31 -0
- package/dist/shell-shard/rich/ViewsTable.svelte.d.ts +13 -0
- package/dist/shell-shard/rich/ZoneTree.svelte +19 -0
- package/dist/shell-shard/rich/ZoneTree.svelte.d.ts +8 -0
- package/dist/shell-shard/rich/ZonesTable.svelte +27 -0
- package/dist/shell-shard/rich/ZonesTable.svelte.d.ts +11 -0
- package/dist/shell-shard/scrollback.svelte.d.ts +36 -0
- package/dist/shell-shard/scrollback.svelte.js +43 -0
- package/dist/shell-shard/session-client.svelte.d.ts +23 -0
- package/dist/shell-shard/session-client.svelte.js +120 -0
- package/dist/shell-shard/shellShard.svelte.d.ts +2 -0
- package/dist/shell-shard/shellShard.svelte.js +139 -0
- package/dist/shell-shard/verbs/apps.d.ts +3 -0
- package/dist/shell-shard/verbs/apps.js +50 -0
- package/dist/shell-shard/verbs/clear.d.ts +2 -0
- package/dist/shell-shard/verbs/clear.js +7 -0
- package/dist/shell-shard/verbs/help.d.ts +2 -0
- package/dist/shell-shard/verbs/help.js +21 -0
- package/dist/shell-shard/verbs/history.d.ts +2 -0
- package/dist/shell-shard/verbs/history.js +20 -0
- package/dist/shell-shard/verbs/index.d.ts +2 -0
- package/dist/shell-shard/verbs/index.js +29 -0
- package/dist/shell-shard/verbs/session.d.ts +5 -0
- package/dist/shell-shard/verbs/session.js +65 -0
- package/dist/shell-shard/verbs/shards.d.ts +2 -0
- package/dist/shell-shard/verbs/shards.js +14 -0
- package/dist/shell-shard/verbs/views.d.ts +4 -0
- package/dist/shell-shard/verbs/views.js +90 -0
- package/dist/shell-shard/verbs/zones.d.ts +3 -0
- package/dist/shell-shard/verbs/zones.js +38 -0
- package/dist/shellRuntime.svelte.d.ts +27 -0
- package/dist/shellRuntime.svelte.js +27 -0
- package/dist/state/backends.d.ts +26 -0
- package/dist/state/backends.js +99 -0
- package/dist/state/manage.d.ts +14 -0
- package/dist/state/manage.js +40 -0
- package/dist/state/types.d.ts +55 -0
- package/dist/state/types.js +17 -0
- package/dist/state/zones.svelte.d.ts +53 -0
- package/dist/state/zones.svelte.js +141 -0
- package/dist/theme.d.ts +28 -0
- package/dist/theme.js +92 -0
- package/dist/tokens.css +102 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +60 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Scrollback store for a single shell.terminal view instance.
|
|
3
|
+
*
|
|
4
|
+
* One instance per view mount. Holds ScrollbackEntry[], supports:
|
|
5
|
+
* - push(entry): append or coalesce with the previous entry if both
|
|
6
|
+
* are text entries with the same stream.
|
|
7
|
+
* - clear(): wipe all entries (local only — does not affect other
|
|
8
|
+
* attached views).
|
|
9
|
+
* - cap at a per-view limit (default 2000) — oldest trimmed.
|
|
10
|
+
*
|
|
11
|
+
* Uses Svelte 5 runes. The backing array is $state() so consumers just
|
|
12
|
+
* read .entries and reactivity Just Works™.
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_CAP = 2000;
|
|
15
|
+
let nextId = 0;
|
|
16
|
+
function mkId() {
|
|
17
|
+
return `e${++nextId}`;
|
|
18
|
+
}
|
|
19
|
+
export class Scrollback {
|
|
20
|
+
constructor(cap = DEFAULT_CAP) {
|
|
21
|
+
this.entries = $state([]);
|
|
22
|
+
this.cap = cap;
|
|
23
|
+
}
|
|
24
|
+
push(entry) {
|
|
25
|
+
// Coalesce consecutive text entries of the same stream
|
|
26
|
+
const last = this.entries[this.entries.length - 1];
|
|
27
|
+
if (last &&
|
|
28
|
+
last.kind === 'text' &&
|
|
29
|
+
entry.kind === 'text' &&
|
|
30
|
+
last.stream === entry.stream) {
|
|
31
|
+
last.chunks.push(...entry.chunks);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const full = Object.assign(Object.assign({}, entry), { id: mkId() });
|
|
35
|
+
this.entries.push(full);
|
|
36
|
+
if (this.entries.length > this.cap) {
|
|
37
|
+
this.entries.splice(0, this.entries.length - this.cap);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
clear() {
|
|
41
|
+
this.entries.length = 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ClientMessage, ServerMessage } from './protocol';
|
|
2
|
+
type MessageHandler = (msg: ServerMessage) => void;
|
|
3
|
+
export declare class SessionClient {
|
|
4
|
+
private readonly url;
|
|
5
|
+
private ws;
|
|
6
|
+
private handlers;
|
|
7
|
+
private pendingHistoryLogs;
|
|
8
|
+
private lastSeq;
|
|
9
|
+
private backoffIndex;
|
|
10
|
+
private closed;
|
|
11
|
+
connected: boolean;
|
|
12
|
+
cwd: string;
|
|
13
|
+
env: Record<string, string>;
|
|
14
|
+
history: string[];
|
|
15
|
+
constructor(url: string);
|
|
16
|
+
connect(): void;
|
|
17
|
+
private scheduleReconnect;
|
|
18
|
+
send(msg: ClientMessage): void;
|
|
19
|
+
private sendRaw;
|
|
20
|
+
onMessage(handler: MessageHandler): () => void;
|
|
21
|
+
close(): void;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Session client for shell-shard.
|
|
3
|
+
*
|
|
4
|
+
* One instance per view mount. Wraps a WebSocket to /api/shell/session.
|
|
5
|
+
*
|
|
6
|
+
* Responsibilities:
|
|
7
|
+
* - Connect with exponential backoff on initial failure
|
|
8
|
+
* - Type-safe send() / onMessage()
|
|
9
|
+
* - Reconnect with replayFrom cursor on drop
|
|
10
|
+
* - Queue history-log messages while disconnected; flush on reconnect
|
|
11
|
+
* - Expose a reactive `connected` signal + latest known cwd/env
|
|
12
|
+
*
|
|
13
|
+
* The client does NOT parse message meaning beyond the `t` discriminator —
|
|
14
|
+
* it's a transport, not a dispatcher. Higher layers (Terminal.svelte) read
|
|
15
|
+
* messages via onMessage.
|
|
16
|
+
*/
|
|
17
|
+
const BACKOFFS = [1000, 2000, 5000, 10000];
|
|
18
|
+
export class SessionClient {
|
|
19
|
+
constructor(url) {
|
|
20
|
+
this.url = url;
|
|
21
|
+
this.ws = null;
|
|
22
|
+
this.handlers = new Set();
|
|
23
|
+
this.pendingHistoryLogs = [];
|
|
24
|
+
this.lastSeq = 0;
|
|
25
|
+
this.backoffIndex = 0;
|
|
26
|
+
this.closed = false;
|
|
27
|
+
this.connected = $state(false);
|
|
28
|
+
this.cwd = $state('');
|
|
29
|
+
this.env = $state({});
|
|
30
|
+
this.history = $state([]);
|
|
31
|
+
}
|
|
32
|
+
connect() {
|
|
33
|
+
if (this.closed)
|
|
34
|
+
return;
|
|
35
|
+
try {
|
|
36
|
+
this.ws = new WebSocket(this.url);
|
|
37
|
+
}
|
|
38
|
+
catch (_a) {
|
|
39
|
+
this.scheduleReconnect();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
this.ws.addEventListener('open', () => {
|
|
43
|
+
this.backoffIndex = 0;
|
|
44
|
+
this.connected = true;
|
|
45
|
+
const hello = this.lastSeq > 0
|
|
46
|
+
? { t: 'hello', replayFrom: this.lastSeq }
|
|
47
|
+
: { t: 'hello' };
|
|
48
|
+
this.sendRaw(hello);
|
|
49
|
+
// Flush any queued history-log messages accumulated while offline
|
|
50
|
+
for (const queued of this.pendingHistoryLogs) {
|
|
51
|
+
this.sendRaw(queued);
|
|
52
|
+
}
|
|
53
|
+
this.pendingHistoryLogs = [];
|
|
54
|
+
});
|
|
55
|
+
this.ws.addEventListener('message', (evt) => {
|
|
56
|
+
let msg;
|
|
57
|
+
try {
|
|
58
|
+
msg = JSON.parse(evt.data);
|
|
59
|
+
}
|
|
60
|
+
catch (_a) {
|
|
61
|
+
return; // ignore malformed
|
|
62
|
+
}
|
|
63
|
+
// Track latest seq for reconnect replay
|
|
64
|
+
if (msg.t === 'event' && typeof msg.event.seq === 'number') {
|
|
65
|
+
this.lastSeq = msg.event.seq;
|
|
66
|
+
}
|
|
67
|
+
if (msg.t === 'welcome') {
|
|
68
|
+
this.cwd = msg.cwd;
|
|
69
|
+
this.env = msg.env;
|
|
70
|
+
this.lastSeq = msg.seq;
|
|
71
|
+
}
|
|
72
|
+
if (msg.t === 'cwd') {
|
|
73
|
+
this.cwd = msg.cwd;
|
|
74
|
+
}
|
|
75
|
+
if (msg.t === 'history') {
|
|
76
|
+
this.history = msg.lines;
|
|
77
|
+
}
|
|
78
|
+
for (const h of this.handlers)
|
|
79
|
+
h(msg);
|
|
80
|
+
});
|
|
81
|
+
this.ws.addEventListener('close', () => {
|
|
82
|
+
this.connected = false;
|
|
83
|
+
this.ws = null;
|
|
84
|
+
this.scheduleReconnect();
|
|
85
|
+
});
|
|
86
|
+
this.ws.addEventListener('error', () => {
|
|
87
|
+
// close event will fire next
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
scheduleReconnect() {
|
|
91
|
+
if (this.closed)
|
|
92
|
+
return;
|
|
93
|
+
const delay = BACKOFFS[Math.min(this.backoffIndex, BACKOFFS.length - 1)];
|
|
94
|
+
this.backoffIndex++;
|
|
95
|
+
setTimeout(() => this.connect(), delay);
|
|
96
|
+
}
|
|
97
|
+
send(msg) {
|
|
98
|
+
if (msg.t === 'history-log' && !this.connected) {
|
|
99
|
+
// Queue for flush on reconnect
|
|
100
|
+
this.pendingHistoryLogs.push(msg);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this.sendRaw(msg);
|
|
104
|
+
}
|
|
105
|
+
sendRaw(msg) {
|
|
106
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
107
|
+
return;
|
|
108
|
+
this.ws.send(JSON.stringify(msg));
|
|
109
|
+
}
|
|
110
|
+
onMessage(handler) {
|
|
111
|
+
this.handlers.add(handler);
|
|
112
|
+
return () => this.handlers.delete(handler);
|
|
113
|
+
}
|
|
114
|
+
close() {
|
|
115
|
+
var _a;
|
|
116
|
+
this.closed = true;
|
|
117
|
+
(_a = this.ws) === null || _a === void 0 ? void 0 : _a.close();
|
|
118
|
+
this.ws = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* shell-shard — framework built-in providing the `shell.terminal` view.
|
|
3
|
+
*
|
|
4
|
+
* Client half of the terminal/REPL shard. See spec at
|
|
5
|
+
* docs/superpowers/specs/2026-04-10-shell-shard-design.md.
|
|
6
|
+
*
|
|
7
|
+
* activate() is admin-gated: when ctx.isAdmin is false the shard activates
|
|
8
|
+
* but registers no views, so non-admin users literally cannot mount the
|
|
9
|
+
* terminal. The server WebSocket endpoint (/api/shell/session) also refuses
|
|
10
|
+
* non-admin upgrades as a second line of defense.
|
|
11
|
+
*
|
|
12
|
+
* autostart() is defined so the shard activates at boot without requiring
|
|
13
|
+
* a dedicated app to launch it first, matching the __sh3core__ pattern.
|
|
14
|
+
*/
|
|
15
|
+
import { mount, unmount } from 'svelte';
|
|
16
|
+
import { manifest } from './manifest';
|
|
17
|
+
import Terminal from './Terminal.svelte';
|
|
18
|
+
import { listRegisteredApps, getActiveApp } from '../apps/registry.svelte';
|
|
19
|
+
import { launchApp } from '../apps/lifecycle';
|
|
20
|
+
import { registeredShards } from '../shards/activate.svelte';
|
|
21
|
+
import { inspectActiveLayout, focusView, closeTab } from '../layout/inspection';
|
|
22
|
+
import { getUser, isAdmin } from '../auth/index';
|
|
23
|
+
/** Walk a layout tree and collect all tab entries (slotId + viewId + label). */
|
|
24
|
+
function collectTabEntries(node) {
|
|
25
|
+
if (node.type === 'tabs') {
|
|
26
|
+
return node.tabs.filter((t) => t.viewId !== null);
|
|
27
|
+
}
|
|
28
|
+
if (node.type === 'split') {
|
|
29
|
+
return node.children.flatMap(collectTabEntries);
|
|
30
|
+
}
|
|
31
|
+
// slot node: wrap as a synthetic tab entry if it has a viewId
|
|
32
|
+
if (node.viewId !== null) {
|
|
33
|
+
return [{ slotId: node.slotId, viewId: node.viewId, label: node.viewId }];
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
function makeShellApi(_ctx) {
|
|
38
|
+
return {
|
|
39
|
+
// → apps/registry.svelte: listRegisteredApps() returns AppManifest[]
|
|
40
|
+
listApps() {
|
|
41
|
+
return listRegisteredApps().map((m) => ({ id: m.id, label: m.label }));
|
|
42
|
+
},
|
|
43
|
+
// → apps/registry.svelte: getActiveApp() returns AppManifest | null
|
|
44
|
+
getActiveApp() {
|
|
45
|
+
const m = getActiveApp();
|
|
46
|
+
return m ? { id: m.id, label: m.label } : null;
|
|
47
|
+
},
|
|
48
|
+
// → apps/lifecycle: launchApp() is async; fire-and-forget to keep ShellApi sync.
|
|
49
|
+
// Verb handlers display feedback independently via scrollback.
|
|
50
|
+
launchApp(id) {
|
|
51
|
+
void launchApp(id);
|
|
52
|
+
},
|
|
53
|
+
// → shards/activate.svelte: registeredShards reactive map
|
|
54
|
+
listShards() {
|
|
55
|
+
return Array.from(registeredShards.values()).map((s) => ({
|
|
56
|
+
id: s.manifest.id,
|
|
57
|
+
label: s.manifest.label,
|
|
58
|
+
version: s.manifest.version,
|
|
59
|
+
}));
|
|
60
|
+
},
|
|
61
|
+
// → layout/inspection: inspectActiveLayout() + tree walk
|
|
62
|
+
listViewsInCurrentLayout() {
|
|
63
|
+
try {
|
|
64
|
+
const { root } = inspectActiveLayout();
|
|
65
|
+
return collectTabEntries(root).map((t) => {
|
|
66
|
+
var _a;
|
|
67
|
+
return ({
|
|
68
|
+
slotId: t.slotId,
|
|
69
|
+
viewId: (_a = t.viewId) !== null && _a !== void 0 ? _a : '',
|
|
70
|
+
label: t.label,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (_a) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
// → layout/inspection: focusView(viewId) returns boolean
|
|
79
|
+
openViewInCurrentLayout(viewId) {
|
|
80
|
+
try {
|
|
81
|
+
const found = focusView(viewId);
|
|
82
|
+
return found ? { ok: true } : { ok: false, error: `view "${viewId}" not found in current layout` };
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
// → layout/inspection: closeTab(slotId) is async (guarded close).
|
|
89
|
+
// Fire-and-forget; the tab disappears asynchronously. ShellApi stays sync.
|
|
90
|
+
closeSlot(slotId) {
|
|
91
|
+
void closeTab(slotId);
|
|
92
|
+
return { ok: true };
|
|
93
|
+
},
|
|
94
|
+
// TODO Phase 10: wire to zone manager when state:manage permission is available.
|
|
95
|
+
// The shell manifest declares permissions: [] so ctx.zones is undefined.
|
|
96
|
+
// A future permission grant + ctx.zones.list() would power these.
|
|
97
|
+
listZones(_shardId) { return []; },
|
|
98
|
+
readZone(_shardId, _zoneName) { return null; },
|
|
99
|
+
// → auth/index: getUser() + isAdmin()
|
|
100
|
+
whoAmI() {
|
|
101
|
+
var _a;
|
|
102
|
+
const user = getUser();
|
|
103
|
+
return {
|
|
104
|
+
userId: (_a = user === null || user === void 0 ? void 0 : user.id) !== null && _a !== void 0 ? _a : 'guest',
|
|
105
|
+
admin: isAdmin(),
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export const shellShard = {
|
|
111
|
+
manifest,
|
|
112
|
+
activate(ctx) {
|
|
113
|
+
if (!ctx.isAdmin) {
|
|
114
|
+
// Non-admin: don't expose the view. Nothing to register.
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const shell = makeShellApi(ctx);
|
|
118
|
+
const factory = {
|
|
119
|
+
mount(container, _context) {
|
|
120
|
+
const proto = typeof location !== 'undefined' && location.protocol === 'https:' ? 'wss' : 'ws';
|
|
121
|
+
const host = typeof location !== 'undefined' ? location.host : 'localhost';
|
|
122
|
+
const wsUrl = `${proto}://${host}/api/shell/session`;
|
|
123
|
+
const instance = mount(Terminal, {
|
|
124
|
+
target: container,
|
|
125
|
+
props: { shell, wsUrl },
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
unmount() {
|
|
129
|
+
unmount(instance);
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
ctx.registerView('shell:terminal', factory);
|
|
135
|
+
},
|
|
136
|
+
autostart() {
|
|
137
|
+
// Intentionally empty — same pattern as __sh3core__.
|
|
138
|
+
},
|
|
139
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import AppsTable from '../rich/AppsTable.svelte';
|
|
2
|
+
import AppCard from '../rich/AppCard.svelte';
|
|
3
|
+
export const appsVerb = {
|
|
4
|
+
name: 'apps',
|
|
5
|
+
summary: 'List installed apps. Click a row to launch.',
|
|
6
|
+
async run(ctx) {
|
|
7
|
+
const apps = ctx.shell.listApps();
|
|
8
|
+
ctx.scrollback.push({
|
|
9
|
+
kind: 'rich',
|
|
10
|
+
component: AppsTable,
|
|
11
|
+
props: {
|
|
12
|
+
data: {
|
|
13
|
+
apps,
|
|
14
|
+
onClickApp: (id) => {
|
|
15
|
+
ctx.shell.launchApp(id);
|
|
16
|
+
ctx.scrollback.push({
|
|
17
|
+
kind: 'status',
|
|
18
|
+
text: `shell: launched app ${id}`,
|
|
19
|
+
level: 'info',
|
|
20
|
+
ts: Date.now(),
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
ts: Date.now(),
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
export const appVerb = {
|
|
30
|
+
name: 'app',
|
|
31
|
+
summary: 'Show the currently active app.',
|
|
32
|
+
async run(ctx) {
|
|
33
|
+
const active = ctx.shell.getActiveApp();
|
|
34
|
+
if (!active) {
|
|
35
|
+
ctx.scrollback.push({
|
|
36
|
+
kind: 'status',
|
|
37
|
+
text: 'shell: no active app',
|
|
38
|
+
level: 'info',
|
|
39
|
+
ts: Date.now(),
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
ctx.scrollback.push({
|
|
44
|
+
kind: 'rich',
|
|
45
|
+
component: AppCard,
|
|
46
|
+
props: { data: { id: active.id, label: active.label, shards: [] } },
|
|
47
|
+
ts: Date.now(),
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import HelpTable from '../rich/HelpTable.svelte';
|
|
2
|
+
export function makeHelpVerb(registry) {
|
|
3
|
+
return {
|
|
4
|
+
name: 'help',
|
|
5
|
+
summary: 'List verbs or show detail for one.',
|
|
6
|
+
async run(ctx) {
|
|
7
|
+
const rows = registry.list().map((v) => ({ name: v.name, summary: v.summary }));
|
|
8
|
+
ctx.scrollback.push({
|
|
9
|
+
kind: 'rich',
|
|
10
|
+
component: HelpTable,
|
|
11
|
+
props: {
|
|
12
|
+
data: {
|
|
13
|
+
rows,
|
|
14
|
+
onClickName: (name) => ctx.dispatch(name),
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
ts: Date.now(),
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import HistoryList from '../rich/HistoryList.svelte';
|
|
2
|
+
export const historyVerb = {
|
|
3
|
+
name: 'history',
|
|
4
|
+
summary: 'Show the last N history lines. Default 50.',
|
|
5
|
+
async run(ctx, args) {
|
|
6
|
+
const n = args[0] ? Math.max(1, parseInt(args[0], 10) || 50) : 50;
|
|
7
|
+
const lines = ctx.session.history.slice(-n);
|
|
8
|
+
ctx.scrollback.push({
|
|
9
|
+
kind: 'rich',
|
|
10
|
+
component: HistoryList,
|
|
11
|
+
props: {
|
|
12
|
+
data: {
|
|
13
|
+
lines,
|
|
14
|
+
onClickLine: (line) => ctx.dispatch(line),
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
ts: Date.now(),
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Bundle of v1 verbs for shell-shard. Call registerV1Verbs(registry) once
|
|
3
|
+
* at activate time to populate the registry.
|
|
4
|
+
*/
|
|
5
|
+
import { makeHelpVerb } from './help';
|
|
6
|
+
import { clearVerb } from './clear';
|
|
7
|
+
import { historyVerb } from './history';
|
|
8
|
+
import { appsVerb, appVerb } from './apps';
|
|
9
|
+
import { shardsVerb } from './shards';
|
|
10
|
+
import { viewsVerb, openVerb, closeVerb } from './views';
|
|
11
|
+
import { zonesVerb, zoneVerb } from './zones';
|
|
12
|
+
import { pwdVerb, cdVerb, envVerb, whoamiVerb } from './session';
|
|
13
|
+
export function registerV1Verbs(registry) {
|
|
14
|
+
registry.register(makeHelpVerb(registry));
|
|
15
|
+
registry.register(clearVerb);
|
|
16
|
+
registry.register(historyVerb);
|
|
17
|
+
registry.register(appsVerb);
|
|
18
|
+
registry.register(appVerb);
|
|
19
|
+
registry.register(shardsVerb);
|
|
20
|
+
registry.register(viewsVerb);
|
|
21
|
+
registry.register(openVerb);
|
|
22
|
+
registry.register(closeVerb);
|
|
23
|
+
registry.register(zonesVerb);
|
|
24
|
+
registry.register(zoneVerb);
|
|
25
|
+
registry.register(pwdVerb);
|
|
26
|
+
registry.register(cdVerb);
|
|
27
|
+
registry.register(envVerb);
|
|
28
|
+
registry.register(whoamiVerb);
|
|
29
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Session verbs: pwd, cd, env, whoami.
|
|
3
|
+
*
|
|
4
|
+
* cd is server-authoritative — the client forwards it as a 'submit' message
|
|
5
|
+
* so ws.ts can intercept and update session.cwd without spawning a process.
|
|
6
|
+
*/
|
|
7
|
+
import EnvTable from '../rich/EnvTable.svelte';
|
|
8
|
+
export const pwdVerb = {
|
|
9
|
+
name: 'pwd',
|
|
10
|
+
summary: 'Show the server session cwd.',
|
|
11
|
+
async run(ctx) {
|
|
12
|
+
ctx.scrollback.push({
|
|
13
|
+
kind: 'text',
|
|
14
|
+
stream: 'stdout',
|
|
15
|
+
chunks: [ctx.cwd + '\n'],
|
|
16
|
+
ts: Date.now(),
|
|
17
|
+
});
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
export const cdVerb = {
|
|
21
|
+
name: 'cd',
|
|
22
|
+
summary: 'Change the session working directory.',
|
|
23
|
+
async run(ctx, args) {
|
|
24
|
+
var _a;
|
|
25
|
+
const path = (_a = args[0]) !== null && _a !== void 0 ? _a : '';
|
|
26
|
+
if (!path) {
|
|
27
|
+
ctx.scrollback.push({
|
|
28
|
+
kind: 'status',
|
|
29
|
+
text: 'usage: cd <path>',
|
|
30
|
+
level: 'warn',
|
|
31
|
+
ts: Date.now(),
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// cd is server-authoritative. Forward the submit so the server's
|
|
36
|
+
// dispatcher can intercept it and update session.cwd (see ws.ts).
|
|
37
|
+
ctx.session.send({ t: 'submit', line: `cd ${path}` });
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
export const envVerb = {
|
|
41
|
+
name: 'env',
|
|
42
|
+
summary: 'Show the session environment.',
|
|
43
|
+
async run(ctx) {
|
|
44
|
+
const env = ctx.session.env;
|
|
45
|
+
ctx.scrollback.push({
|
|
46
|
+
kind: 'rich',
|
|
47
|
+
component: EnvTable,
|
|
48
|
+
props: { data: { env } },
|
|
49
|
+
ts: Date.now(),
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
export const whoamiVerb = {
|
|
54
|
+
name: 'whoami',
|
|
55
|
+
summary: 'Show the current admin user and session info.',
|
|
56
|
+
async run(ctx) {
|
|
57
|
+
const me = ctx.shell.whoAmI();
|
|
58
|
+
ctx.scrollback.push({
|
|
59
|
+
kind: 'text',
|
|
60
|
+
stream: 'stdout',
|
|
61
|
+
chunks: [`${me.userId}${me.admin ? ' (admin)' : ''}\n`],
|
|
62
|
+
ts: Date.now(),
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import ShardsTable from '../rich/ShardsTable.svelte';
|
|
2
|
+
export const shardsVerb = {
|
|
3
|
+
name: 'shards',
|
|
4
|
+
summary: 'List active shards.',
|
|
5
|
+
async run(ctx) {
|
|
6
|
+
const shards = ctx.shell.listShards();
|
|
7
|
+
ctx.scrollback.push({
|
|
8
|
+
kind: 'rich',
|
|
9
|
+
component: ShardsTable,
|
|
10
|
+
props: { data: { shards } },
|
|
11
|
+
ts: Date.now(),
|
|
12
|
+
});
|
|
13
|
+
},
|
|
14
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import ViewsTable from '../rich/ViewsTable.svelte';
|
|
2
|
+
export const viewsVerb = {
|
|
3
|
+
name: 'views',
|
|
4
|
+
summary: 'List views currently mounted in the active layout.',
|
|
5
|
+
async run(ctx) {
|
|
6
|
+
const views = ctx.shell.listViewsInCurrentLayout();
|
|
7
|
+
ctx.scrollback.push({
|
|
8
|
+
kind: 'rich',
|
|
9
|
+
component: ViewsTable,
|
|
10
|
+
props: {
|
|
11
|
+
data: {
|
|
12
|
+
views,
|
|
13
|
+
onClose: (slotId) => {
|
|
14
|
+
var _a;
|
|
15
|
+
const result = ctx.shell.closeSlot(slotId);
|
|
16
|
+
if (!result.ok) {
|
|
17
|
+
ctx.scrollback.push({
|
|
18
|
+
kind: 'status',
|
|
19
|
+
text: `shell: close failed — ${(_a = result.error) !== null && _a !== void 0 ? _a : 'unknown'}`,
|
|
20
|
+
level: 'error',
|
|
21
|
+
ts: Date.now(),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
ts: Date.now(),
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export const openVerb = {
|
|
32
|
+
name: 'open',
|
|
33
|
+
summary: 'Open a view from any active shard into the current layout.',
|
|
34
|
+
async run(ctx, args) {
|
|
35
|
+
var _a;
|
|
36
|
+
const viewId = args[0];
|
|
37
|
+
if (!viewId) {
|
|
38
|
+
ctx.scrollback.push({
|
|
39
|
+
kind: 'status',
|
|
40
|
+
text: 'usage: open <viewId>',
|
|
41
|
+
level: 'warn',
|
|
42
|
+
ts: Date.now(),
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const result = ctx.shell.openViewInCurrentLayout(viewId);
|
|
47
|
+
if (!result.ok) {
|
|
48
|
+
ctx.scrollback.push({
|
|
49
|
+
kind: 'status',
|
|
50
|
+
text: `shell: open failed — ${(_a = result.error) !== null && _a !== void 0 ? _a : 'unknown'}`,
|
|
51
|
+
level: 'error',
|
|
52
|
+
ts: Date.now(),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
ctx.scrollback.push({
|
|
57
|
+
kind: 'status',
|
|
58
|
+
text: `shell: opened ${viewId}`,
|
|
59
|
+
level: 'info',
|
|
60
|
+
ts: Date.now(),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
export const closeVerb = {
|
|
66
|
+
name: 'close',
|
|
67
|
+
summary: 'Close a view by slot id.',
|
|
68
|
+
async run(ctx, args) {
|
|
69
|
+
var _a;
|
|
70
|
+
const slotId = args[0];
|
|
71
|
+
if (!slotId) {
|
|
72
|
+
ctx.scrollback.push({
|
|
73
|
+
kind: 'status',
|
|
74
|
+
text: 'usage: close <slotId>',
|
|
75
|
+
level: 'warn',
|
|
76
|
+
ts: Date.now(),
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const result = ctx.shell.closeSlot(slotId);
|
|
81
|
+
if (!result.ok) {
|
|
82
|
+
ctx.scrollback.push({
|
|
83
|
+
kind: 'status',
|
|
84
|
+
text: `shell: close failed — ${(_a = result.error) !== null && _a !== void 0 ? _a : 'unknown'}`,
|
|
85
|
+
level: 'error',
|
|
86
|
+
ts: Date.now(),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
};
|