@volter/supercode-teams 0.2.68 → 0.2.70
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/machine.mjs +5 -1
- package/package.json +1 -1
- package/popup-terminals.mjs +5 -3
- package/terminal-basic.mjs +49 -0
- package/terminal-look.mjs +128 -0
package/machine.mjs
CHANGED
|
@@ -813,7 +813,11 @@ export class TeamsMachine extends MachineCore {
|
|
|
813
813
|
if (!terminal) return;
|
|
814
814
|
const attach = process.platform === 'win32'
|
|
815
815
|
? [process.execPath, fileURLToPath(new URL('./bin/fleet.mjs', import.meta.url)), 'attach', contextKey, '--mode', 'control']
|
|
816
|
-
|
|
816
|
+
// Presented as `supercode open`'s own client is (crates/cli/src/open.rs `attach`): the terminal sets its own
|
|
817
|
+
// TERM (WezTerm's is xterm-256color), and only a screen-256color client gets the pane server's no-alternate-
|
|
818
|
+
// screen override, so the window's own scrollback, wheel and selection work instead of the wheel arriving
|
|
819
|
+
// in the pane as up/down arrows.
|
|
820
|
+
: ['/usr/bin/env', 'TERM=screen-256color', spawnSync('which', ['tmux'], { encoding: 'utf8' }).stdout.trim() || 'tmux', ...(this.tmuxSocket ? ['-L', this.tmuxSocket] : []), '-T', 'RGB', 'attach-session', '-t', session.label ?? contextKey];
|
|
817
821
|
openPopup(terminal, attach, { ...this.env, TMUX: '' });
|
|
818
822
|
}
|
|
819
823
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.70",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A machine daemon serving this machine's doors, and a local or remote Teams server for shared session discovery, OpenTelemetry ingestion and access to enrolled machines.",
|
|
6
6
|
"license": "MIT",
|
package/popup-terminals.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { chmodSync, existsSync, writeFileSync } from 'node:fs';
|
|
|
8
8
|
import { tmpdir } from 'node:os';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
import { randomBytes } from 'node:crypto';
|
|
11
|
+
import { weztermConfigFile } from './terminal-look.mjs';
|
|
11
12
|
|
|
12
13
|
const onPath = (name) => {
|
|
13
14
|
const found = spawnSync(process.platform === 'win32' ? 'where' : 'which', [name], { encoding: 'utf8', timeout: 3_000 });
|
|
@@ -29,7 +30,8 @@ function selfDeletingScript(argv, extension) {
|
|
|
29
30
|
/** Adapters by platform, in rank order: { name, find() → program or null, launch(program, argv) → [command, args] }. */
|
|
30
31
|
const ADAPTERS = {
|
|
31
32
|
darwin: [
|
|
32
|
-
|
|
33
|
+
// WezTerm opens with supercode's own config: it looks like this machine's Terminal.app (terminal-look.mjs).
|
|
34
|
+
{ name: 'wezterm', find: () => onPath('wezterm') ?? firstPath('/Applications/WezTerm.app/Contents/MacOS/wezterm'), launch: (p, argv) => [p, ['--config-file', weztermConfigFile(), 'start', '--always-new-process', '--', ...argv]] },
|
|
33
35
|
{ name: 'kitty', find: () => onPath('kitty') ?? firstPath('/Applications/kitty.app/Contents/MacOS/kitty'), launch: (p, argv) => [p, argv] },
|
|
34
36
|
{ name: 'ghostty', find: () => firstPath('/Applications/Ghostty.app'), launch: (p, argv) => ['open', ['-na', p, '--args', '-e', ...argv]] },
|
|
35
37
|
{ name: 'alacritty', find: () => onPath('alacritty') ?? firstPath('/Applications/Alacritty.app/Contents/MacOS/alacritty'), launch: (p, argv) => [p, ['-e', ...argv]] },
|
|
@@ -41,13 +43,13 @@ const ADAPTERS = {
|
|
|
41
43
|
// Windows Terminal first: its window opens in front of the others, where one started by a background process
|
|
42
44
|
// (the daemon is one) opens behind them; WezTerm started that way never painted its window (volter-desktop).
|
|
43
45
|
{ name: 'windows-terminal', find: () => onPath('wt') ?? firstPath(join(localApps, 'Microsoft', 'WindowsApps', 'wt.exe')), launch: (p, argv) => [p, ['-w', 'new', 'new-tab', '--', ...argv]] },
|
|
44
|
-
{ name: 'wezterm', find: () => onPath('wezterm') ?? firstPath(join(programFiles, 'WezTerm', 'wezterm.exe')), launch: (p, argv) => [p, ['start', '--always-new-process', '--', ...argv]] },
|
|
46
|
+
{ name: 'wezterm', find: () => onPath('wezterm') ?? firstPath(join(programFiles, 'WezTerm', 'wezterm.exe')), launch: (p, argv) => [p, ['--config-file', weztermConfigFile(), 'start', '--always-new-process', '--', ...argv]] },
|
|
45
47
|
{ name: 'alacritty', find: () => onPath('alacritty') ?? firstPath(join(programFiles, 'Alacritty', 'alacritty.exe')), launch: (p, argv) => [p, ['-e', ...argv]] },
|
|
46
48
|
// A console window of its own, on every Windows: it lives exactly as long as its command.
|
|
47
49
|
{ name: 'conhost', find: () => firstPath(join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'conhost.exe')), launch: (p, argv) => [p, argv] },
|
|
48
50
|
],
|
|
49
51
|
linux: [
|
|
50
|
-
{ name: 'wezterm', find: () => onPath('wezterm'), launch: (p, argv) => [p, ['start', '--always-new-process', '--', ...argv]] },
|
|
52
|
+
{ name: 'wezterm', find: () => onPath('wezterm'), launch: (p, argv) => [p, ['--config-file', weztermConfigFile(), 'start', '--always-new-process', '--', ...argv]] },
|
|
51
53
|
{ name: 'kitty', find: () => onPath('kitty'), launch: (p, argv) => [p, argv] },
|
|
52
54
|
{ name: 'ghostty', find: () => onPath('ghostty'), launch: (p, argv) => [p, ['-e', ...argv]] },
|
|
53
55
|
{ name: 'alacritty', find: () => onPath('alacritty'), launch: (p, argv) => [p, ['-e', ...argv]] },
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Generated by sdk/teams/scripts/terminal-basic.mjs from https://brand.volter.ai/tokens.json
|
|
2
|
+
// (the brand's terminal roles: Terminal.app's Basic profile). Do not edit; re-run the script after a brand change.
|
|
3
|
+
export const TERMINAL_BASIC = {
|
|
4
|
+
"light": {
|
|
5
|
+
"background": "#ffffff",
|
|
6
|
+
"foreground": "#000000",
|
|
7
|
+
"cursor": "#9d9d9d",
|
|
8
|
+
"selection": "#b3d7ff",
|
|
9
|
+
"black": "#000000",
|
|
10
|
+
"red": "#990000",
|
|
11
|
+
"green": "#00a600",
|
|
12
|
+
"yellow": "#999900",
|
|
13
|
+
"blue": "#0000b3",
|
|
14
|
+
"magenta": "#b300b3",
|
|
15
|
+
"cyan": "#00a6b3",
|
|
16
|
+
"white": "#bfbfbf",
|
|
17
|
+
"brightBlack": "#666666",
|
|
18
|
+
"brightRed": "#e60000",
|
|
19
|
+
"brightGreen": "#00d900",
|
|
20
|
+
"brightYellow": "#e6e600",
|
|
21
|
+
"brightBlue": "#0000ff",
|
|
22
|
+
"brightMagenta": "#e600e6",
|
|
23
|
+
"brightCyan": "#00e6e6",
|
|
24
|
+
"brightWhite": "#e6e6e6"
|
|
25
|
+
},
|
|
26
|
+
"dark": {
|
|
27
|
+
"background": "#1e1e1e",
|
|
28
|
+
"foreground": "#ffffff",
|
|
29
|
+
"cursor": "#9d9d9d",
|
|
30
|
+
"selection": "#3f638b",
|
|
31
|
+
"black": "#000000",
|
|
32
|
+
"red": "#990000",
|
|
33
|
+
"green": "#00a600",
|
|
34
|
+
"yellow": "#999900",
|
|
35
|
+
"blue": "#0000b3",
|
|
36
|
+
"magenta": "#b300b3",
|
|
37
|
+
"cyan": "#00a6b3",
|
|
38
|
+
"white": "#bfbfbf",
|
|
39
|
+
"brightBlack": "#666666",
|
|
40
|
+
"brightRed": "#e60000",
|
|
41
|
+
"brightGreen": "#00d900",
|
|
42
|
+
"brightYellow": "#e6e600",
|
|
43
|
+
"brightBlue": "#0000ff",
|
|
44
|
+
"brightMagenta": "#e600e6",
|
|
45
|
+
"brightCyan": "#00e6e6",
|
|
46
|
+
"brightWhite": "#e6e6e6"
|
|
47
|
+
},
|
|
48
|
+
"fontSize": 11
|
|
49
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// The look of a WezTerm window supercode opens: Apple's Terminal.app, so a person from macOS feels at home (the
|
|
2
|
+
// owner's ruling). On a Mac the window takes this machine's own Terminal.app default profile: its colours, face, size,
|
|
3
|
+
// spacing, cursor, Option key, bell, opacity, blur and window size, read from com.apple.Terminal and converted to sRGB
|
|
4
|
+
// by AppKit itself. Whatever the profile leaves unset, and every slot on a machine without Terminal.app, is Terminal.app's
|
|
5
|
+
// Basic profile (terminal-basic.mjs, the brand's terminal roles), light or dark with the system's appearance as Basic is.
|
|
6
|
+
// The config is WezTerm's own file, passed with `--config-file`; the person's ~/.wezterm.lua is not read for it.
|
|
7
|
+
import { execFileSync } from 'node:child_process';
|
|
8
|
+
import { existsSync, renameSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { TERMINAL_BASIC } from './terminal-basic.mjs';
|
|
12
|
+
|
|
13
|
+
const TERMINAL_FONTS = '/System/Applications/Utilities/Terminal.app/Contents/Resources/Fonts';
|
|
14
|
+
const ANSI = ['Black', 'Red', 'Green', 'Yellow', 'Blue', 'Magenta', 'Cyan', 'White'];
|
|
15
|
+
|
|
16
|
+
// Runs in osascript's JavaScript for Automation with the Objective-C bridge: no Apple Event is sent, so no automation
|
|
17
|
+
// permission is asked for. Terminal.app's own faces (SF Mono Terminal) are registered first, for the process only, so
|
|
18
|
+
// the profile's archived font resolves to its family.
|
|
19
|
+
const READ_PROFILE = `
|
|
20
|
+
ObjC.import('AppKit'); ObjC.import('CoreText');
|
|
21
|
+
function run() {
|
|
22
|
+
const defaults = $.NSUserDefaults.alloc.initWithSuiteName('com.apple.Terminal');
|
|
23
|
+
const name = ObjC.unwrap(defaults.stringForKey('Default Window Settings'));
|
|
24
|
+
const profiles = defaults.dictionaryForKey('Window Settings');
|
|
25
|
+
if (!name || profiles.isNil()) return 'null';
|
|
26
|
+
const profile = profiles.objectForKey(name);
|
|
27
|
+
if (profile.isNil()) return JSON.stringify({ name });
|
|
28
|
+
const dir = ${JSON.stringify(TERMINAL_FONTS)};
|
|
29
|
+
const files = $.NSFileManager.defaultManager.contentsOfDirectoryAtPathError(dir, null);
|
|
30
|
+
if (!files.isNil()) for (const file of ObjC.deepUnwrap(files)) $.CTFontManagerRegisterFontsForURL($.NSURL.fileURLWithPath(dir + '/' + file), 1, null);
|
|
31
|
+
const value = (key) => { const item = profile.objectForKey(key); return item.isNil() ? undefined : ObjC.deepUnwrap(item); };
|
|
32
|
+
const archived = (key) => { const data = profile.objectForKey(key); return data.isNil() ? null : $.NSKeyedUnarchiver.unarchiveObjectWithData(data); };
|
|
33
|
+
const colors = {};
|
|
34
|
+
for (const key of ObjC.deepUnwrap(profile.allKeys)) {
|
|
35
|
+
if (!/Color$/.test(key)) continue;
|
|
36
|
+
const color = archived(key);
|
|
37
|
+
const srgb = color && !color.isNil() ? color.colorUsingColorSpace($.NSColorSpace.sRGBColorSpace) : null;
|
|
38
|
+
if (srgb && !srgb.isNil()) colors[key] = [srgb.redComponent, srgb.greenComponent, srgb.blueComponent, srgb.alphaComponent];
|
|
39
|
+
}
|
|
40
|
+
const font = archived('Font');
|
|
41
|
+
return JSON.stringify({
|
|
42
|
+
name, colors,
|
|
43
|
+
font: font && !font.isNil() ? { family: ObjC.unwrap(font.familyName), size: font.pointSize } : null,
|
|
44
|
+
widthSpacing: value('FontWidthSpacing'), heightSpacing: value('FontHeightSpacing'),
|
|
45
|
+
cursorType: value('CursorType'), cursorBlink: value('CursorBlink'), optionIsMeta: value('useOptionAsMetaKey'),
|
|
46
|
+
bell: value('Bell'), visualBell: value('VisualBell'), brightBold: value('UseBrightBold'),
|
|
47
|
+
blur: value('BackgroundBlur'), columns: value('columnCount'), rows: value('rowCount'),
|
|
48
|
+
});
|
|
49
|
+
}`;
|
|
50
|
+
|
|
51
|
+
/** This machine's Terminal.app default profile, as read by AppKit; null off macOS or when it cannot be read. */
|
|
52
|
+
export function terminalAppProfile(platform = process.platform) {
|
|
53
|
+
if (platform !== 'darwin') return null;
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(execFileSync('osascript', ['-l', 'JavaScript', '-e', READ_PROFILE], { encoding: 'utf8', timeout: 5_000, stdio: ['ignore', 'pipe', 'ignore'] }));
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const hex = ([r, g, b]) => `#${[r, g, b].map((part) => Math.round(Math.min(1, Math.max(0, part)) * 255).toString(16).padStart(2, '0')).join('')}`;
|
|
62
|
+
const lua = (value) => (typeof value === 'string' ? JSON.stringify(value) : String(value));
|
|
63
|
+
|
|
64
|
+
/** WezTerm's config (Lua) for a window that looks like Terminal.app with `profile` (from terminalAppProfile, or null). */
|
|
65
|
+
export function weztermConfig(profile, { fontDirs = existsSync(TERMINAL_FONTS) ? [TERMINAL_FONTS] : [] } = {}) {
|
|
66
|
+
const stored = profile?.colors ?? {};
|
|
67
|
+
// One colour table per appearance: the profile's colour where it stores one, else Basic's for that appearance.
|
|
68
|
+
const scheme = (basic) => {
|
|
69
|
+
const pick = (key, slot) => (stored[key] ? hex(stored[key]) : basic[slot]);
|
|
70
|
+
const background = pick('BackgroundColor', 'background');
|
|
71
|
+
const foreground = pick('TextColor', 'foreground');
|
|
72
|
+
const cursor = pick('CursorColor', 'cursor');
|
|
73
|
+
return {
|
|
74
|
+
background, foreground,
|
|
75
|
+
cursor_bg: cursor, cursor_border: cursor, cursor_fg: background,
|
|
76
|
+
selection_bg: pick('SelectionColor', 'selection'), selection_fg: foreground,
|
|
77
|
+
ansi: ANSI.map((name) => pick(`ANSI${name}Color`, name.toLowerCase())),
|
|
78
|
+
brights: ANSI.map((name) => pick(`ANSIBright${name}Color`, `bright${name}`)),
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
const table = (colors) => `{\n${Object.entries(colors).map(([key, value]) => ` ${key} = ${Array.isArray(value) ? `{ ${value.map(lua).join(', ')} }` : lua(value)},`).join('\n')}\n }`;
|
|
82
|
+
const families = [...new Set([profile?.font?.family, 'SF Mono Terminal', 'SF Mono', 'Menlo'].filter(Boolean))];
|
|
83
|
+
const opacity = stored.BackgroundColor?.[3] ?? 1;
|
|
84
|
+
const cursorShape = ['Block', 'Underline', 'Bar'][profile?.cursorType ?? 0] ?? 'Block';
|
|
85
|
+
const composed = !profile?.optionIsMeta;
|
|
86
|
+
const lines = [
|
|
87
|
+
'-- Generated by supercode (sdk/teams/terminal-look.mjs): this window looks like Terminal.app' + (profile?.name ? ` with this machine's "${profile.name}" profile.` : "'s Basic profile."),
|
|
88
|
+
"local wezterm = require 'wezterm'",
|
|
89
|
+
'local config = wezterm.config_builder()',
|
|
90
|
+
`local schemes = {\n light = ${table(scheme(TERMINAL_BASIC.light))},\n dark = ${table(scheme(TERMINAL_BASIC.dark))},\n}`,
|
|
91
|
+
"local appearance = wezterm.gui and wezterm.gui.get_appearance() or 'Dark'",
|
|
92
|
+
"config.colors = appearance:find('Dark') and schemes.dark or schemes.light",
|
|
93
|
+
`config.font_dirs = { ${fontDirs.map(lua).join(', ')} }`,
|
|
94
|
+
`config.font = wezterm.font_with_fallback({ ${families.map(lua).join(', ')} })`,
|
|
95
|
+
`config.font_size = ${profile?.font?.size ?? TERMINAL_BASIC.fontSize}`,
|
|
96
|
+
`config.cell_width = ${profile?.widthSpacing ?? 1}`,
|
|
97
|
+
`config.line_height = ${profile?.heightSpacing ?? 1}`,
|
|
98
|
+
`config.default_cursor_style = ${lua(`${profile?.cursorBlink ? 'Blinking' : 'Steady'}${cursorShape}`)}`,
|
|
99
|
+
`config.send_composed_key_when_left_alt_is_pressed = ${composed}`,
|
|
100
|
+
`config.send_composed_key_when_right_alt_is_pressed = ${composed}`,
|
|
101
|
+
`config.audible_bell = ${lua(profile?.bell === false ? 'Disabled' : 'SystemBeep')}`,
|
|
102
|
+
...(profile?.visualBell ? ["config.visual_bell = { fade_in_duration_ms = 75, fade_out_duration_ms = 75, target = 'BackgroundColor' }"] : []),
|
|
103
|
+
...(typeof profile?.brightBold === 'boolean' ? [`config.bold_brightens_ansi_colors = ${profile.brightBold}`] : []),
|
|
104
|
+
`config.window_background_opacity = ${opacity}`,
|
|
105
|
+
// Terminal.app's blur is 0 to 1; WezTerm's is a radius. 40 at full blur is an estimate, not a measurement.
|
|
106
|
+
`config.macos_window_background_blur = ${opacity < 1 ? Math.round((profile?.blur ?? 0) * 40) : 0}`,
|
|
107
|
+
`config.initial_cols = ${profile?.columns ?? 80}`,
|
|
108
|
+
`config.initial_rows = ${profile?.rows ?? 24}`,
|
|
109
|
+
// Terminal.app's margin around the text is not measured; this padding is an estimate.
|
|
110
|
+
'config.window_padding = { left = 4, right = 4, top = 2, bottom = 2 }',
|
|
111
|
+
'config.hide_tab_bar_if_only_one_tab = true',
|
|
112
|
+
'config.enable_scroll_bar = false',
|
|
113
|
+
// Terminal.app does not ask before closing a window whose process is tmux (its default "ask before closing" list).
|
|
114
|
+
"config.window_close_confirmation = 'NeverPrompt'",
|
|
115
|
+
'return config',
|
|
116
|
+
'',
|
|
117
|
+
];
|
|
118
|
+
return lines.join('\n');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Writes the config for this machine and returns its path. */
|
|
122
|
+
export function weztermConfigFile(platform = process.platform) {
|
|
123
|
+
const path = join(tmpdir(), `supercode-wezterm-${process.getuid?.() ?? 'user'}.lua`);
|
|
124
|
+
const next = `${path}.${process.pid}`;
|
|
125
|
+
writeFileSync(next, weztermConfig(terminalAppProfile(platform)));
|
|
126
|
+
renameSync(next, path);
|
|
127
|
+
return path;
|
|
128
|
+
}
|