sh3-core 0.15.0 → 0.15.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/dist/actions/ctx-actions.svelte.test.js +111 -0
- package/dist/actions/dispatcher.svelte.js +23 -2
- package/dist/actions/dispatcher.test.js +33 -0
- package/dist/actions/listActionsFromEntries.test.js +78 -0
- package/dist/actions/listActive.d.ts +2 -1
- package/dist/actions/listActive.js +43 -17
- package/dist/actions/listeners.d.ts +16 -0
- package/dist/actions/listeners.js +68 -14
- package/dist/actions/programmatic-dispatch.svelte.test.d.ts +1 -0
- package/dist/actions/programmatic-dispatch.svelte.test.js +98 -0
- package/dist/actions/types.d.ts +37 -0
- package/dist/api.d.ts +1 -1
- package/dist/app/store/verbs.js +4 -0
- package/dist/app-appearance/appearanceShard.svelte.js +19 -6
- package/dist/app-appearance/appearanceState.svelte.js +3 -3
- package/dist/host.js +2 -1
- package/dist/layouts-shard/LayoutSaveModal.svelte +145 -0
- package/dist/layouts-shard/LayoutSaveModal.svelte.d.ts +12 -0
- package/dist/layouts-shard/LayoutsSection.svelte +142 -0
- package/dist/layouts-shard/LayoutsSection.svelte.d.ts +3 -0
- package/dist/layouts-shard/filter.d.ts +3 -0
- package/dist/layouts-shard/filter.js +66 -0
- package/dist/layouts-shard/filter.test.d.ts +1 -0
- package/dist/layouts-shard/filter.test.js +123 -0
- package/dist/layouts-shard/index.d.ts +1 -0
- package/dist/layouts-shard/index.js +1 -0
- package/dist/layouts-shard/layoutsApi.d.ts +12 -0
- package/dist/layouts-shard/layoutsApi.js +41 -0
- package/dist/layouts-shard/layoutsApi.test.d.ts +1 -0
- package/dist/layouts-shard/layoutsApi.test.js +74 -0
- package/dist/layouts-shard/layoutsShard.svelte.d.ts +11 -0
- package/dist/layouts-shard/layoutsShard.svelte.js +231 -0
- package/dist/layouts-shard/layoutsShard.svelte.test.d.ts +1 -0
- package/dist/layouts-shard/layoutsShard.svelte.test.js +215 -0
- package/dist/layouts-shard/layoutsState.svelte.d.ts +9 -0
- package/dist/layouts-shard/layoutsState.svelte.js +50 -0
- package/dist/layouts-shard/layoutsState.test.d.ts +1 -0
- package/dist/layouts-shard/layoutsState.test.js +43 -0
- package/dist/layouts-shard/types.d.ts +21 -0
- package/dist/layouts-shard/types.js +6 -0
- package/dist/{app-appearance/AppAppearanceModal.svelte → overlays/EntityAppearanceModal.svelte} +36 -31
- package/dist/overlays/EntityAppearanceModal.svelte.d.ts +19 -0
- package/dist/overlays/EntityAppearanceModal.test.d.ts +1 -0
- package/dist/overlays/EntityAppearanceModal.test.js +57 -0
- package/dist/overlays/FloatFrame.svelte +149 -8
- package/dist/overlays/FloatFrame.svelte.d.ts +1 -1
- package/dist/overlays/FloatLayer.svelte +2 -2
- package/dist/overlays/float.d.ts +38 -1
- package/dist/overlays/float.js +82 -0
- package/dist/overlays/float.test.js +394 -0
- package/dist/overlays/floatMaximized.svelte.d.ts +4 -0
- package/dist/overlays/floatMaximized.svelte.js +30 -0
- package/dist/runtime/runVerb-shell.test.d.ts +1 -0
- package/dist/runtime/runVerb-shell.test.js +231 -0
- package/dist/sh3core-shard/ShellHome.svelte +3 -0
- package/dist/sh3core-shard/sh3coreShard.svelte.d.ts +7 -0
- package/dist/sh3core-shard/sh3coreShard.svelte.js +23 -0
- package/dist/shards/activate-runtime.test.js +24 -2
- package/dist/shards/activate.svelte.js +18 -4
- package/dist/shards/types.d.ts +44 -4
- package/dist/shell-shard/CommandLine.svelte +143 -0
- package/dist/shell-shard/CommandLine.svelte.d.ts +26 -0
- package/dist/shell-shard/CommandLine.svelte.test.d.ts +1 -0
- package/dist/shell-shard/CommandLine.svelte.test.js +43 -0
- package/dist/shell-shard/InputLine.svelte +17 -40
- package/dist/shell-shard/InputLine.svelte.d.ts +2 -0
- package/dist/shell-shard/ScrollbackView.svelte +10 -3
- package/dist/shell-shard/ScrollbackView.svelte.d.ts +1 -0
- package/dist/shell-shard/Terminal.svelte +94 -22
- package/dist/shell-shard/buffer-store.d.ts +15 -0
- package/dist/shell-shard/buffer-store.js +124 -0
- package/dist/shell-shard/buffer-store.svelte.test.d.ts +1 -0
- package/dist/shell-shard/buffer-store.svelte.test.js +107 -0
- package/dist/shell-shard/buffer-zone-state.svelte.d.ts +38 -0
- package/dist/shell-shard/buffer-zone-state.svelte.js +31 -0
- package/dist/shell-shard/contract.d.ts +7 -0
- package/dist/shell-shard/dispatch-custom.test.js +3 -1
- package/dist/shell-shard/dispatch-gating.test.js +6 -2
- package/dist/shell-shard/dispatch-invoke.test.js +10 -8
- package/dist/shell-shard/dispatch.d.ts +7 -2
- package/dist/shell-shard/dispatch.js +23 -27
- package/dist/shell-shard/display-cwd.d.ts +1 -0
- package/dist/shell-shard/display-cwd.js +27 -0
- package/dist/shell-shard/display-cwd.test.d.ts +1 -0
- package/dist/shell-shard/display-cwd.test.js +29 -0
- package/dist/shell-shard/entries/StatusEntry.svelte +2 -0
- package/dist/shell-shard/manifest.js +2 -1
- package/dist/shell-shard/manifest.test.d.ts +1 -0
- package/dist/shell-shard/manifest.test.js +8 -0
- package/dist/shell-shard/mode-buffer.svelte.d.ts +8 -0
- package/dist/shell-shard/mode-buffer.svelte.js +19 -0
- package/dist/shell-shard/mode-buffer.svelte.test.d.ts +1 -0
- package/dist/shell-shard/mode-buffer.svelte.test.js +25 -0
- package/dist/shell-shard/modes/builtin.js +2 -0
- package/dist/shell-shard/modes/types.d.ts +8 -0
- package/dist/shell-shard/protocol.d.ts +12 -6
- package/dist/shell-shard/replay.d.ts +3 -0
- package/dist/shell-shard/replay.js +44 -0
- package/dist/shell-shard/replay.svelte.test.d.ts +1 -0
- package/dist/shell-shard/replay.svelte.test.js +47 -0
- package/dist/shell-shard/rich-registry.d.ts +5 -0
- package/dist/shell-shard/rich-registry.js +25 -0
- package/dist/shell-shard/rich-registry.test.d.ts +1 -0
- package/dist/shell-shard/rich-registry.test.js +31 -0
- package/dist/shell-shard/scrollback.svelte.d.ts +2 -0
- package/dist/shell-shard/scrollback.svelte.js +23 -0
- package/dist/shell-shard/scrollback.svelte.test.d.ts +1 -0
- package/dist/shell-shard/scrollback.svelte.test.js +51 -0
- package/dist/shell-shard/session-client.svelte.d.ts +18 -2
- package/dist/shell-shard/session-client.svelte.js +21 -4
- package/dist/shell-shard/shellApi.d.ts +2 -1
- package/dist/shell-shard/shellApi.js +32 -3
- package/dist/shell-shard/shellApi.svelte.test.d.ts +1 -0
- package/dist/shell-shard/shellApi.svelte.test.js +59 -0
- package/dist/shell-shard/shellShard.svelte.js +11 -1
- package/dist/shell-shard/terminal-dispatch.test.js +3 -1
- package/dist/shell-shard/verbs/apps.js +9 -0
- package/dist/shell-shard/verbs/env.js +4 -0
- package/dist/shell-shard/verbs/help.js +9 -1
- package/dist/shell-shard/verbs/help.svelte.test.d.ts +1 -0
- package/dist/shell-shard/verbs/help.svelte.test.js +53 -0
- package/dist/shell-shard/verbs/history.js +8 -1
- package/dist/shell-shard/verbs/index.js +0 -8
- package/dist/shell-shard/verbs/shards.js +5 -0
- package/dist/shell-shard/verbs/views.js +9 -0
- package/dist/shell-shard/verbs/zones.js +9 -0
- package/dist/verbs/types.d.ts +9 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/app-appearance/AppAppearanceModal.svelte.d.ts +0 -8
- package/dist/shell-shard/verbs/cat.d.ts +0 -2
- package/dist/shell-shard/verbs/cat.js +0 -34
- package/dist/shell-shard/verbs/cd.test.js +0 -56
- package/dist/shell-shard/verbs/ls.d.ts +0 -2
- package/dist/shell-shard/verbs/ls.js +0 -29
- package/dist/shell-shard/verbs/ls.test.js +0 -49
- package/dist/shell-shard/verbs/session.d.ts +0 -4
- package/dist/shell-shard/verbs/session.js +0 -97
- /package/dist/{shell-shard/verbs/cd.test.d.ts → actions/ctx-actions.svelte.test.d.ts} +0 -0
- /package/dist/{shell-shard/verbs/ls.test.d.ts → actions/listActionsFromEntries.test.d.ts} +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Scrollback } from './scrollback.svelte';
|
|
3
|
+
const FakeComp = {};
|
|
4
|
+
describe('Scrollback rich entry', () => {
|
|
5
|
+
it('stores componentKey alongside component for persistence', () => {
|
|
6
|
+
const sb = new Scrollback();
|
|
7
|
+
sb.push({
|
|
8
|
+
kind: 'rich',
|
|
9
|
+
componentKey: 'test-table',
|
|
10
|
+
component: FakeComp,
|
|
11
|
+
props: { foo: 1 },
|
|
12
|
+
ts: 0,
|
|
13
|
+
});
|
|
14
|
+
const entry = sb.entries[0];
|
|
15
|
+
expect(entry.kind).toBe('rich');
|
|
16
|
+
if (entry.kind === 'rich') {
|
|
17
|
+
expect(entry.componentKey).toBe('test-table');
|
|
18
|
+
expect(entry.component).toStrictEqual(FakeComp);
|
|
19
|
+
expect(entry.props).toEqual({ foo: 1 });
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe('Scrollback.restore', () => {
|
|
24
|
+
it('replaces entries and bumps id counter past max persisted id', () => {
|
|
25
|
+
const sb = new Scrollback();
|
|
26
|
+
sb.restore([
|
|
27
|
+
{ id: 'e500', kind: 'prompt', cwd: '/', line: 'a', ts: 0 },
|
|
28
|
+
{ id: 'e501', kind: 'prompt', cwd: '/', line: 'b', ts: 1 },
|
|
29
|
+
]);
|
|
30
|
+
expect(sb.entries).toHaveLength(2);
|
|
31
|
+
sb.push({ kind: 'prompt', cwd: '/', line: 'c', ts: 2 });
|
|
32
|
+
expect(sb.entries).toHaveLength(3);
|
|
33
|
+
const ids = sb.entries.map((e) => e.id);
|
|
34
|
+
expect(new Set(ids).size).toBe(3);
|
|
35
|
+
const minted = ids[2];
|
|
36
|
+
expect(Number(minted.slice(1))).toBeGreaterThan(501);
|
|
37
|
+
});
|
|
38
|
+
it('preserves $state identity by mutating in place', () => {
|
|
39
|
+
const sb = new Scrollback();
|
|
40
|
+
const ref = sb.entries;
|
|
41
|
+
sb.restore([{ id: 'eX', kind: 'prompt', cwd: '/', line: 'a', ts: 0 }]);
|
|
42
|
+
expect(sb.entries).toBe(ref);
|
|
43
|
+
});
|
|
44
|
+
it('tolerates non-numeric ids without throwing', () => {
|
|
45
|
+
const sb = new Scrollback();
|
|
46
|
+
sb.restore([{ id: 'foo', kind: 'prompt', cwd: '/', line: 'a', ts: 0 }]);
|
|
47
|
+
sb.push({ kind: 'prompt', cwd: '/', line: 'b', ts: 1 });
|
|
48
|
+
expect(sb.entries).toHaveLength(2);
|
|
49
|
+
expect(new Set(sb.entries.map((e) => e.id)).size).toBe(2);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -5,13 +5,29 @@ export declare class SessionClient {
|
|
|
5
5
|
private ws;
|
|
6
6
|
private handlers;
|
|
7
7
|
private pendingHistoryLogs;
|
|
8
|
-
private lastSeq;
|
|
9
8
|
private backoffIndex;
|
|
10
9
|
private closed;
|
|
11
10
|
connected: boolean;
|
|
12
11
|
cwd: string;
|
|
12
|
+
/**
|
|
13
|
+
* Per-user shell tenant root, fixed for the session's lifetime. Used by
|
|
14
|
+
* the input line to render the cwd relative to this prefix (e.g., `~`).
|
|
15
|
+
* Populated from the server's welcome message.
|
|
16
|
+
*/
|
|
17
|
+
tenantRoot: string;
|
|
13
18
|
env: Record<string, string>;
|
|
14
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Per-mode history bundle hydrated from the server's history-bundle reply.
|
|
21
|
+
* Each entry is the persisted history for that mode id. Terminal.svelte
|
|
22
|
+
* mirrors these slots into the corresponding ModeBuffer.history.
|
|
23
|
+
*/
|
|
24
|
+
historyByMode: Record<string, string[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Latest server-event seq the client has observed. Public so the boot
|
|
27
|
+
* path can pre-seed it from a persisted bash buffer snapshot before
|
|
28
|
+
* calling connect(); the WS hello then uses it as a replay cursor.
|
|
29
|
+
*/
|
|
30
|
+
lastSeq: number;
|
|
15
31
|
constructor(url: string);
|
|
16
32
|
connect(): void;
|
|
17
33
|
private scheduleReconnect;
|
|
@@ -21,13 +21,29 @@ export class SessionClient {
|
|
|
21
21
|
this.ws = null;
|
|
22
22
|
this.handlers = new Set();
|
|
23
23
|
this.pendingHistoryLogs = [];
|
|
24
|
-
this.lastSeq = 0;
|
|
25
24
|
this.backoffIndex = 0;
|
|
26
25
|
this.closed = false;
|
|
27
26
|
this.connected = $state(false);
|
|
28
27
|
this.cwd = $state('');
|
|
28
|
+
/**
|
|
29
|
+
* Per-user shell tenant root, fixed for the session's lifetime. Used by
|
|
30
|
+
* the input line to render the cwd relative to this prefix (e.g., `~`).
|
|
31
|
+
* Populated from the server's welcome message.
|
|
32
|
+
*/
|
|
33
|
+
this.tenantRoot = $state('');
|
|
29
34
|
this.env = $state({});
|
|
30
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Per-mode history bundle hydrated from the server's history-bundle reply.
|
|
37
|
+
* Each entry is the persisted history for that mode id. Terminal.svelte
|
|
38
|
+
* mirrors these slots into the corresponding ModeBuffer.history.
|
|
39
|
+
*/
|
|
40
|
+
this.historyByMode = $state({});
|
|
41
|
+
/**
|
|
42
|
+
* Latest server-event seq the client has observed. Public so the boot
|
|
43
|
+
* path can pre-seed it from a persisted bash buffer snapshot before
|
|
44
|
+
* calling connect(); the WS hello then uses it as a replay cursor.
|
|
45
|
+
*/
|
|
46
|
+
this.lastSeq = $state(0);
|
|
31
47
|
}
|
|
32
48
|
connect() {
|
|
33
49
|
if (this.closed)
|
|
@@ -66,14 +82,15 @@ export class SessionClient {
|
|
|
66
82
|
}
|
|
67
83
|
if (msg.t === 'welcome') {
|
|
68
84
|
this.cwd = msg.cwd;
|
|
85
|
+
this.tenantRoot = msg.tenantRoot;
|
|
69
86
|
this.env = msg.env;
|
|
70
87
|
this.lastSeq = msg.seq;
|
|
71
88
|
}
|
|
72
89
|
if (msg.t === 'cwd') {
|
|
73
90
|
this.cwd = msg.cwd;
|
|
74
91
|
}
|
|
75
|
-
if (msg.t === 'history') {
|
|
76
|
-
this.
|
|
92
|
+
if (msg.t === 'history-bundle') {
|
|
93
|
+
this.historyByMode = msg.byMode;
|
|
77
94
|
}
|
|
78
95
|
for (const h of this.handlers)
|
|
79
96
|
h(msg);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { ShellApi } from './registry';
|
|
2
|
-
|
|
2
|
+
import type { ZoneManager } from '../state/types';
|
|
3
|
+
export declare function makeShellApiHeadless(zones?: ZoneManager): ShellApi;
|
|
3
4
|
export declare function makeShellApiForTest(): ShellApi;
|
|
@@ -15,6 +15,7 @@ import { registeredShards, listStandaloneViews } from '../shards/activate.svelte
|
|
|
15
15
|
import { inspectActiveLayout, focusView, closeTab, popoutView, dockFloat, dockIntoActiveLayout, locateSlot as locateSlotInActiveLayout, } from '../layout/inspection';
|
|
16
16
|
import { floatManager } from '../overlays/float';
|
|
17
17
|
import { getUser, isAdmin } from '../auth/index';
|
|
18
|
+
const KNOWN_ZONES = ['ephemeral', 'session', 'workspace', 'user'];
|
|
18
19
|
function collectTabEntries(node) {
|
|
19
20
|
if (node.type === 'tabs') {
|
|
20
21
|
return node.tabs.filter((t) => t.viewId !== null);
|
|
@@ -27,7 +28,7 @@ function collectTabEntries(node) {
|
|
|
27
28
|
}
|
|
28
29
|
return [];
|
|
29
30
|
}
|
|
30
|
-
export function makeShellApiHeadless() {
|
|
31
|
+
export function makeShellApiHeadless(zones) {
|
|
31
32
|
return {
|
|
32
33
|
listApps() {
|
|
33
34
|
return listRegisteredApps().map((m) => ({ id: m.id, label: m.label }));
|
|
@@ -123,8 +124,35 @@ export function makeShellApiHeadless() {
|
|
|
123
124
|
void closeTab(slotId);
|
|
124
125
|
return { ok: true };
|
|
125
126
|
},
|
|
126
|
-
listZones(
|
|
127
|
-
|
|
127
|
+
listZones(shardId) {
|
|
128
|
+
if (!zones)
|
|
129
|
+
return [];
|
|
130
|
+
const byShard = new Map();
|
|
131
|
+
for (const zone of KNOWN_ZONES) {
|
|
132
|
+
for (const id of zones.list(zone)) {
|
|
133
|
+
if (shardId && id !== shardId)
|
|
134
|
+
continue;
|
|
135
|
+
let set = byShard.get(id);
|
|
136
|
+
if (!set) {
|
|
137
|
+
set = new Set();
|
|
138
|
+
byShard.set(id, set);
|
|
139
|
+
}
|
|
140
|
+
set.add(zone);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return Array.from(byShard.entries()).map(([id, set]) => ({
|
|
144
|
+
shardId: id,
|
|
145
|
+
zones: Array.from(set),
|
|
146
|
+
}));
|
|
147
|
+
},
|
|
148
|
+
readZone(shardId, zoneName) {
|
|
149
|
+
var _a;
|
|
150
|
+
if (!zones)
|
|
151
|
+
return null;
|
|
152
|
+
if (!KNOWN_ZONES.includes(zoneName))
|
|
153
|
+
return null;
|
|
154
|
+
return (_a = zones.peek(zoneName, shardId)) !== null && _a !== void 0 ? _a : null;
|
|
155
|
+
},
|
|
128
156
|
whoAmI() {
|
|
129
157
|
var _a;
|
|
130
158
|
const user = getUser();
|
|
@@ -135,6 +163,7 @@ export function makeShellApiHeadless() {
|
|
|
135
163
|
},
|
|
136
164
|
setMode(_id) { return false; },
|
|
137
165
|
listModes() { return []; },
|
|
166
|
+
getMode() { return { id: 'sh3', label: 'sh3' }; },
|
|
138
167
|
};
|
|
139
168
|
}
|
|
140
169
|
export function makeShellApiForTest() {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { makeShellApiHeadless } from './shellApi';
|
|
3
|
+
function makeMockZoneManager() {
|
|
4
|
+
const data = {
|
|
5
|
+
ephemeral: {},
|
|
6
|
+
session: { 'shard-a': { foo: 1 } },
|
|
7
|
+
workspace: { 'shard-a': { bar: 2 }, 'shard-b': { baz: 3 } },
|
|
8
|
+
user: { 'shard-b': { theme: 'dark' } },
|
|
9
|
+
};
|
|
10
|
+
return {
|
|
11
|
+
list: (zone) => Object.keys(data[zone]),
|
|
12
|
+
peek: (zone, shardId) => data[zone][shardId],
|
|
13
|
+
clear: () => { },
|
|
14
|
+
clearAll: () => { },
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
describe('shellApi listZones', () => {
|
|
18
|
+
it('returns empty when no ZoneManager provided (stub mode)', () => {
|
|
19
|
+
const api = makeShellApiHeadless();
|
|
20
|
+
expect(api.listZones()).toEqual([]);
|
|
21
|
+
});
|
|
22
|
+
it('returns one row per shard with the zones it has data in', () => {
|
|
23
|
+
var _a, _b;
|
|
24
|
+
const api = makeShellApiHeadless(makeMockZoneManager());
|
|
25
|
+
const rows = api.listZones();
|
|
26
|
+
const byShard = new Map(rows.map((r) => [r.shardId, r.zones]));
|
|
27
|
+
expect((_a = byShard.get('shard-a')) === null || _a === void 0 ? void 0 : _a.sort()).toEqual(['session', 'workspace']);
|
|
28
|
+
expect((_b = byShard.get('shard-b')) === null || _b === void 0 ? void 0 : _b.sort()).toEqual(['user', 'workspace']);
|
|
29
|
+
});
|
|
30
|
+
it('filters by shardId when provided', () => {
|
|
31
|
+
const api = makeShellApiHeadless(makeMockZoneManager());
|
|
32
|
+
const rows = api.listZones('shard-a');
|
|
33
|
+
expect(rows).toHaveLength(1);
|
|
34
|
+
expect(rows[0].shardId).toBe('shard-a');
|
|
35
|
+
expect(rows[0].zones.sort()).toEqual(['session', 'workspace']);
|
|
36
|
+
});
|
|
37
|
+
it('returns empty array for unknown shardId', () => {
|
|
38
|
+
const api = makeShellApiHeadless(makeMockZoneManager());
|
|
39
|
+
expect(api.listZones('not-a-shard')).toEqual([]);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('shellApi readZone', () => {
|
|
43
|
+
it('returns null when no ZoneManager provided', () => {
|
|
44
|
+
const api = makeShellApiHeadless();
|
|
45
|
+
expect(api.readZone('shard-a', 'workspace')).toBe(null);
|
|
46
|
+
});
|
|
47
|
+
it('returns peeked value via ZoneManager', () => {
|
|
48
|
+
const api = makeShellApiHeadless(makeMockZoneManager());
|
|
49
|
+
expect(api.readZone('shard-a', 'workspace')).toEqual({ bar: 2 });
|
|
50
|
+
});
|
|
51
|
+
it('returns null for unknown zone name', () => {
|
|
52
|
+
const api = makeShellApiHeadless(makeMockZoneManager());
|
|
53
|
+
expect(api.readZone('shard-a', 'bogus')).toBe(null);
|
|
54
|
+
});
|
|
55
|
+
it('returns null for missing shard entry', () => {
|
|
56
|
+
const api = makeShellApiHeadless(makeMockZoneManager());
|
|
57
|
+
expect(api.readZone('not-a-shard', 'workspace')).toBe(null);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -24,12 +24,19 @@ import { makeShellApiHeadless } from './shellApi';
|
|
|
24
24
|
import { focusView } from '../layout/inspection';
|
|
25
25
|
import { floatManager } from '../overlays/float';
|
|
26
26
|
import { getUser, isAdmin } from '../auth/index';
|
|
27
|
+
import { __bindZone, __unbindZone } from './buffer-zone-state.svelte';
|
|
27
28
|
export { makeShellApiHeadless, makeShellApiForTest } from './shellApi';
|
|
28
29
|
export const shellShard = {
|
|
29
30
|
manifest,
|
|
30
31
|
activate(ctx) {
|
|
31
32
|
registerV1Verbs(ctx);
|
|
32
|
-
const shell = makeShellApiHeadless();
|
|
33
|
+
const shell = makeShellApiHeadless(ctx.zones);
|
|
34
|
+
// Bind the shell-shard's workspace zone — backs scrollback persistence
|
|
35
|
+
// (SH8). BufferStore reads/writes through this proxy.
|
|
36
|
+
const zone = ctx.state({
|
|
37
|
+
workspace: { buffers: {} },
|
|
38
|
+
});
|
|
39
|
+
__bindZone(zone);
|
|
33
40
|
// The AZERTY `²` key (top-left on FR keyboards, below Escape) opens the
|
|
34
41
|
// terminal view — focusing it if already mounted, floating it otherwise.
|
|
35
42
|
// Migrated from Shell.svelte's inline keydown handler as proof-of-concept
|
|
@@ -72,4 +79,7 @@ export const shellShard = {
|
|
|
72
79
|
autostart() {
|
|
73
80
|
// Intentionally empty — same pattern as __sh3core__.
|
|
74
81
|
},
|
|
82
|
+
deactivate() {
|
|
83
|
+
__unbindZone();
|
|
84
|
+
},
|
|
75
85
|
};
|
|
@@ -7,6 +7,8 @@ function scaffold(modeId) {
|
|
|
7
7
|
? { id: 'bash', label: 'Bash', transport: 'ws', autoRelocate: false, requiresRole: 'admin' }
|
|
8
8
|
: { id: 'sh3', label: 'SH3', transport: 'none', autoRelocate: true };
|
|
9
9
|
const scrollback = { push: (e) => pushed.push(e) };
|
|
10
|
+
const history = [];
|
|
11
|
+
const buffer = { scrollback, history };
|
|
10
12
|
const session = {
|
|
11
13
|
history: { push: vi.fn() },
|
|
12
14
|
send: (m) => sent.push(m),
|
|
@@ -28,7 +30,7 @@ function scaffold(modeId) {
|
|
|
28
30
|
mode: () => mode,
|
|
29
31
|
role: () => (modeId === 'bash' ? 'admin' : 'user'),
|
|
30
32
|
resolver,
|
|
31
|
-
|
|
33
|
+
buffer: () => buffer,
|
|
32
34
|
session,
|
|
33
35
|
shell,
|
|
34
36
|
fs,
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import AppsTable from '../rich/AppsTable.svelte';
|
|
2
2
|
import AppCard from '../rich/AppCard.svelte';
|
|
3
|
+
import { registerRichComponent } from '../rich-registry';
|
|
4
|
+
const APPS_TABLE_KEY = 'apps-table';
|
|
5
|
+
const APP_CARD_KEY = 'app-card';
|
|
6
|
+
registerRichComponent(APPS_TABLE_KEY, AppsTable);
|
|
7
|
+
registerRichComponent(APP_CARD_KEY, AppCard);
|
|
3
8
|
export const appsVerb = {
|
|
4
9
|
name: 'apps',
|
|
5
10
|
summary: 'List installed apps. Click a row to launch.',
|
|
11
|
+
programmatic: true,
|
|
6
12
|
async run(ctx) {
|
|
7
13
|
const apps = ctx.shell.listApps();
|
|
8
14
|
ctx.scrollback.push({
|
|
9
15
|
kind: 'rich',
|
|
16
|
+
componentKey: APPS_TABLE_KEY,
|
|
10
17
|
component: AppsTable,
|
|
11
18
|
props: {
|
|
12
19
|
data: {
|
|
@@ -29,6 +36,7 @@ export const appsVerb = {
|
|
|
29
36
|
export const appVerb = {
|
|
30
37
|
name: 'app',
|
|
31
38
|
summary: 'Show the currently active app.',
|
|
39
|
+
programmatic: true,
|
|
32
40
|
async run(ctx) {
|
|
33
41
|
const active = ctx.shell.getActiveApp();
|
|
34
42
|
if (!active) {
|
|
@@ -42,6 +50,7 @@ export const appVerb = {
|
|
|
42
50
|
}
|
|
43
51
|
ctx.scrollback.push({
|
|
44
52
|
kind: 'rich',
|
|
53
|
+
componentKey: APP_CARD_KEY,
|
|
45
54
|
component: AppCard,
|
|
46
55
|
props: { data: { id: active.id, label: active.label, shards: [] } },
|
|
47
56
|
ts: Date.now(),
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import EnvTable from '../rich/EnvTable.svelte';
|
|
2
|
+
import { registerRichComponent } from '../rich-registry';
|
|
3
|
+
const ENV_TABLE_KEY = 'env-table';
|
|
4
|
+
registerRichComponent(ENV_TABLE_KEY, EnvTable);
|
|
2
5
|
export const envVerb = {
|
|
3
6
|
name: 'env',
|
|
4
7
|
summary: 'Show the session environment.',
|
|
@@ -6,6 +9,7 @@ export const envVerb = {
|
|
|
6
9
|
const env = ctx.session.env;
|
|
7
10
|
ctx.scrollback.push({
|
|
8
11
|
kind: 'rich',
|
|
12
|
+
componentKey: ENV_TABLE_KEY,
|
|
9
13
|
component: EnvTable,
|
|
10
14
|
props: { data: { env } },
|
|
11
15
|
ts: Date.now(),
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { listVerbs } from '../../shards/registry';
|
|
2
2
|
import HelpTable from '../rich/HelpTable.svelte';
|
|
3
|
+
import { registerRichComponent } from '../rich-registry';
|
|
4
|
+
const HELP_TABLE_KEY = 'help-table';
|
|
5
|
+
registerRichComponent(HELP_TABLE_KEY, HelpTable);
|
|
3
6
|
export function makeHelpVerb() {
|
|
4
7
|
return {
|
|
5
8
|
name: 'help',
|
|
6
9
|
summary: 'List verbs or show detail for one.',
|
|
10
|
+
globalVerb: true,
|
|
7
11
|
async run(ctx) {
|
|
8
|
-
const
|
|
12
|
+
const inSh3 = ctx.shell.getMode().id === 'sh3';
|
|
13
|
+
const rows = listVerbs()
|
|
14
|
+
.filter((v) => inSh3 || v.globalVerb === true)
|
|
15
|
+
.map((v) => ({ name: v.name, summary: v.summary }));
|
|
9
16
|
ctx.scrollback.push({
|
|
10
17
|
kind: 'rich',
|
|
18
|
+
componentKey: HELP_TABLE_KEY,
|
|
11
19
|
component: HelpTable,
|
|
12
20
|
props: {
|
|
13
21
|
data: {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { makeHelpVerb } from './help';
|
|
3
|
+
import { registerVerb, __resetViewRegistryForTest } from '../../shards/registry';
|
|
4
|
+
function makeCtx(modeId) {
|
|
5
|
+
const pushed = [];
|
|
6
|
+
const ctx = {
|
|
7
|
+
shell: { getMode: () => ({ id: modeId, label: modeId }) },
|
|
8
|
+
scrollback: { push: (e) => pushed.push(e) },
|
|
9
|
+
session: {},
|
|
10
|
+
cwd: '/',
|
|
11
|
+
dispatch: async () => { },
|
|
12
|
+
fs: {},
|
|
13
|
+
};
|
|
14
|
+
return { ctx, pushed };
|
|
15
|
+
}
|
|
16
|
+
const sh3Verb = { name: 'apps', summary: 'list apps', async run() { } };
|
|
17
|
+
const globalA = { name: 'clear', summary: 'clear scrollback', globalVerb: true, async run() { } };
|
|
18
|
+
const globalB = { name: 'mode', summary: 'switch mode', globalVerb: true, async run() { } };
|
|
19
|
+
describe('help verb', () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
__resetViewRegistryForTest();
|
|
22
|
+
registerVerb('apps', sh3Verb, 'shell');
|
|
23
|
+
registerVerb('clear', globalA, 'shell');
|
|
24
|
+
registerVerb('mode', globalB, 'shell');
|
|
25
|
+
registerVerb('help', makeHelpVerb(), 'shell');
|
|
26
|
+
});
|
|
27
|
+
it('lists every registered verb in sh3 mode', async () => {
|
|
28
|
+
const help = makeHelpVerb();
|
|
29
|
+
const { ctx, pushed } = makeCtx('sh3');
|
|
30
|
+
await help.run(ctx, []);
|
|
31
|
+
const rich = pushed.find((e) => e.kind === 'rich');
|
|
32
|
+
const names = rich.props.data.rows.map((r) => r.name);
|
|
33
|
+
expect(names).toContain('apps');
|
|
34
|
+
expect(names).toContain('clear');
|
|
35
|
+
expect(names).toContain('mode');
|
|
36
|
+
expect(names).toContain('help');
|
|
37
|
+
});
|
|
38
|
+
it('lists only globalVerb-flagged verbs in a custom mode', async () => {
|
|
39
|
+
const help = makeHelpVerb();
|
|
40
|
+
const { ctx, pushed } = makeCtx('gemini');
|
|
41
|
+
await help.run(ctx, []);
|
|
42
|
+
const rich = pushed.find((e) => e.kind === 'rich');
|
|
43
|
+
const names = rich.props.data.rows.map((r) => r.name);
|
|
44
|
+
expect(names).toContain('clear');
|
|
45
|
+
expect(names).toContain('mode');
|
|
46
|
+
expect(names).toContain('help');
|
|
47
|
+
expect(names).not.toContain('apps');
|
|
48
|
+
});
|
|
49
|
+
it('is flagged globalVerb so it resolves in custom modes', () => {
|
|
50
|
+
const help = makeHelpVerb();
|
|
51
|
+
expect(help.globalVerb).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import HistoryList from '../rich/HistoryList.svelte';
|
|
2
|
+
import { registerRichComponent } from '../rich-registry';
|
|
3
|
+
const HISTORY_LIST_KEY = 'history-list';
|
|
4
|
+
registerRichComponent(HISTORY_LIST_KEY, HistoryList);
|
|
2
5
|
export const historyVerb = {
|
|
3
6
|
name: 'history',
|
|
4
7
|
summary: 'Show the last N history lines. Default 50.',
|
|
5
8
|
async run(ctx, args) {
|
|
9
|
+
var _a;
|
|
6
10
|
const n = args[0] ? Math.max(1, parseInt(args[0], 10) || 50) : 50;
|
|
7
|
-
const
|
|
11
|
+
const modeId = ctx.shell.getMode().id;
|
|
12
|
+
const allLines = (_a = ctx.session.historyByMode[modeId]) !== null && _a !== void 0 ? _a : [];
|
|
13
|
+
const lines = allLines.slice(-n);
|
|
8
14
|
ctx.scrollback.push({
|
|
9
15
|
kind: 'rich',
|
|
16
|
+
componentKey: HISTORY_LIST_KEY,
|
|
10
17
|
component: HistoryList,
|
|
11
18
|
props: {
|
|
12
19
|
data: {
|
|
@@ -10,10 +10,7 @@ import { appsVerb, appVerb } from './apps';
|
|
|
10
10
|
import { shardsVerb } from './shards';
|
|
11
11
|
import { viewsVerb, openVerb, closeVerb, popoutVerb, dockVerb } from './views';
|
|
12
12
|
import { zonesVerb, zoneVerb } from './zones';
|
|
13
|
-
import { pwdVerb, cdVerb, whoamiVerb } from './session';
|
|
14
13
|
import { envVerb } from './env';
|
|
15
|
-
import { lsVerb } from './ls';
|
|
16
|
-
import { catVerb } from './cat';
|
|
17
14
|
import { resetVerb } from './reset';
|
|
18
15
|
export function registerV1Verbs(ctx) {
|
|
19
16
|
ctx.registerVerb(makeHelpVerb());
|
|
@@ -30,11 +27,6 @@ export function registerV1Verbs(ctx) {
|
|
|
30
27
|
ctx.registerVerb(dockVerb);
|
|
31
28
|
ctx.registerVerb(zonesVerb);
|
|
32
29
|
ctx.registerVerb(zoneVerb);
|
|
33
|
-
ctx.registerVerb(pwdVerb);
|
|
34
|
-
ctx.registerVerb(cdVerb);
|
|
35
30
|
ctx.registerVerb(envVerb);
|
|
36
|
-
ctx.registerVerb(whoamiVerb);
|
|
37
|
-
ctx.registerVerb(lsVerb);
|
|
38
|
-
ctx.registerVerb(catVerb);
|
|
39
31
|
ctx.registerVerb(resetVerb);
|
|
40
32
|
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import ShardsTable from '../rich/ShardsTable.svelte';
|
|
2
|
+
import { registerRichComponent } from '../rich-registry';
|
|
3
|
+
const SHARDS_TABLE_KEY = 'shards-table';
|
|
4
|
+
registerRichComponent(SHARDS_TABLE_KEY, ShardsTable);
|
|
2
5
|
export const shardsVerb = {
|
|
3
6
|
name: 'shards',
|
|
4
7
|
summary: 'List active shards.',
|
|
8
|
+
programmatic: true,
|
|
5
9
|
async run(ctx) {
|
|
6
10
|
const shards = ctx.shell.listShards();
|
|
7
11
|
ctx.scrollback.push({
|
|
8
12
|
kind: 'rich',
|
|
13
|
+
componentKey: SHARDS_TABLE_KEY,
|
|
9
14
|
component: ShardsTable,
|
|
10
15
|
props: { data: { shards } },
|
|
11
16
|
ts: Date.now(),
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import ViewsTable from '../rich/ViewsTable.svelte';
|
|
2
|
+
import { registerRichComponent } from '../rich-registry';
|
|
3
|
+
const VIEWS_TABLE_KEY = 'views-table';
|
|
4
|
+
registerRichComponent(VIEWS_TABLE_KEY, ViewsTable);
|
|
2
5
|
export const viewsVerb = {
|
|
3
6
|
name: 'views',
|
|
4
7
|
summary: 'List views currently mounted. Pass --standalone to list summonable views instead.',
|
|
8
|
+
programmatic: true,
|
|
5
9
|
async run(ctx, args) {
|
|
6
10
|
if (args.includes('--standalone')) {
|
|
7
11
|
const standalones = ctx.shell.listStandaloneViews();
|
|
@@ -27,6 +31,7 @@ export const viewsVerb = {
|
|
|
27
31
|
const views = ctx.shell.listViewsInCurrentLayout();
|
|
28
32
|
ctx.scrollback.push({
|
|
29
33
|
kind: 'rich',
|
|
34
|
+
componentKey: VIEWS_TABLE_KEY,
|
|
30
35
|
component: ViewsTable,
|
|
31
36
|
props: {
|
|
32
37
|
data: {
|
|
@@ -52,6 +57,7 @@ export const viewsVerb = {
|
|
|
52
57
|
export const openVerb = {
|
|
53
58
|
name: 'open',
|
|
54
59
|
summary: 'Open a view from any active shard into the current layout.',
|
|
60
|
+
programmatic: true,
|
|
55
61
|
async run(ctx, args) {
|
|
56
62
|
var _a;
|
|
57
63
|
const viewId = args[0];
|
|
@@ -86,6 +92,7 @@ export const openVerb = {
|
|
|
86
92
|
export const popoutVerb = {
|
|
87
93
|
name: 'popout',
|
|
88
94
|
summary: 'Pop a docked view out into a float by slot id.',
|
|
95
|
+
programmatic: true,
|
|
89
96
|
async run(ctx, args) {
|
|
90
97
|
var _a;
|
|
91
98
|
const slotId = args[0];
|
|
@@ -120,6 +127,7 @@ export const popoutVerb = {
|
|
|
120
127
|
export const dockVerb = {
|
|
121
128
|
name: 'dock',
|
|
122
129
|
summary: 'Dock a float back into the current layout by float id. Run with no args to list floats.',
|
|
130
|
+
programmatic: true,
|
|
123
131
|
async run(ctx, args) {
|
|
124
132
|
var _a;
|
|
125
133
|
const floatId = args[0];
|
|
@@ -166,6 +174,7 @@ export const dockVerb = {
|
|
|
166
174
|
export const closeVerb = {
|
|
167
175
|
name: 'close',
|
|
168
176
|
summary: 'Close a view by slot id.',
|
|
177
|
+
programmatic: true,
|
|
169
178
|
async run(ctx, args) {
|
|
170
179
|
var _a;
|
|
171
180
|
const slotId = args[0];
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import ZonesTable from '../rich/ZonesTable.svelte';
|
|
2
2
|
import ZoneTree from '../rich/ZoneTree.svelte';
|
|
3
|
+
import { registerRichComponent } from '../rich-registry';
|
|
4
|
+
const ZONES_TABLE_KEY = 'zones-table';
|
|
5
|
+
const ZONE_TREE_KEY = 'zone-tree';
|
|
6
|
+
registerRichComponent(ZONES_TABLE_KEY, ZonesTable);
|
|
7
|
+
registerRichComponent(ZONE_TREE_KEY, ZoneTree);
|
|
3
8
|
export const zonesVerb = {
|
|
4
9
|
name: 'zones',
|
|
5
10
|
summary: 'List zones for the current user (optionally scoped to a shard).',
|
|
11
|
+
programmatic: true,
|
|
6
12
|
async run(ctx, args) {
|
|
7
13
|
const rows = ctx.shell.listZones(args[0]);
|
|
8
14
|
ctx.scrollback.push({
|
|
9
15
|
kind: 'rich',
|
|
16
|
+
componentKey: ZONES_TABLE_KEY,
|
|
10
17
|
component: ZonesTable,
|
|
11
18
|
props: { data: { rows } },
|
|
12
19
|
ts: Date.now(),
|
|
@@ -16,6 +23,7 @@ export const zonesVerb = {
|
|
|
16
23
|
export const zoneVerb = {
|
|
17
24
|
name: 'zone',
|
|
18
25
|
summary: 'Dump the contents of a zone as a collapsible JSON tree.',
|
|
26
|
+
programmatic: true,
|
|
19
27
|
async run(ctx, args) {
|
|
20
28
|
const [shardId, zoneName] = args;
|
|
21
29
|
if (!shardId || !zoneName) {
|
|
@@ -30,6 +38,7 @@ export const zoneVerb = {
|
|
|
30
38
|
const value = ctx.shell.readZone(shardId, zoneName);
|
|
31
39
|
ctx.scrollback.push({
|
|
32
40
|
kind: 'rich',
|
|
41
|
+
componentKey: ZONE_TREE_KEY,
|
|
33
42
|
component: ZoneTree,
|
|
34
43
|
props: { data: { value } },
|
|
35
44
|
ts: Date.now(),
|
package/dist/verbs/types.d.ts
CHANGED
|
@@ -77,6 +77,15 @@ export interface ShellApi {
|
|
|
77
77
|
id: string;
|
|
78
78
|
label: string;
|
|
79
79
|
}[];
|
|
80
|
+
/**
|
|
81
|
+
* Active shell mode. Returns `{ id: 'sh3', label: 'sh3' }` from headless
|
|
82
|
+
* contexts (no terminal view mounted) so callers can rely on a stable
|
|
83
|
+
* shape — Terminal.svelte overrides this with the live mode.
|
|
84
|
+
*/
|
|
85
|
+
getMode(): {
|
|
86
|
+
id: string;
|
|
87
|
+
label: string;
|
|
88
|
+
};
|
|
80
89
|
}
|
|
81
90
|
export interface VerbContext {
|
|
82
91
|
shell: ShellApi;
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Auto-generated from package.json — do not edit manually. */
|
|
2
|
-
export declare const VERSION = "0.15.
|
|
2
|
+
export declare const VERSION = "0.15.2";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Auto-generated from package.json — do not edit manually. */
|
|
2
|
-
export const VERSION = '0.15.
|
|
2
|
+
export const VERSION = '0.15.2';
|
package/package.json
CHANGED