offsite 0.0.3 → 0.0.4
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 +18 -7
- package/apps/server/dist/main.mjs +5767 -0
- package/apps/server/dist/main.mjs.map +1 -0
- package/apps/web/dist/assets/{angular-html-ji4UO7hp.js → angular-html-prQpjz7s.js} +1 -1
- package/apps/web/dist/assets/{angular-ts-CkobLhDa.js → angular-ts-BE4HJ5Uh.js} +1 -1
- package/apps/web/dist/assets/{apl-DAjPJTs4.js → apl-CJ-NIQ-7.js} +1 -1
- package/apps/web/dist/assets/{astro-DlwA-zmc.js → astro-CVkyXRVY.js} +1 -1
- package/apps/web/dist/assets/{blade-wTlHdbgB.js → blade-CQFMHse_.js} +1 -1
- package/apps/web/dist/assets/{c-HyCIpwKL.js → c-gqNWYRL8.js} +1 -1
- package/apps/web/dist/assets/{chapel-SOYLpFSa.js → chapel-CQ9_BV4j.js} +1 -1
- package/apps/web/dist/assets/{cobol-DlvLD04V.js → cobol-D-9v184m.js} +1 -1
- package/apps/web/dist/assets/{coffee-CGp5oJTY.js → coffee-BE0tIOLL.js} +1 -1
- package/apps/web/dist/assets/{cpp-CzmH13Nm.js → cpp-CzOLC-NF.js} +1 -1
- package/apps/web/dist/assets/{crystal-zS14MbR_.js → crystal-f1GcBNi_.js} +1 -1
- package/apps/web/dist/assets/{css-DQEQn59R.js → css-DlqRAumc.js} +1 -1
- package/apps/web/dist/assets/{edge-Bkv0KzZU.js → edge-DQkkPJaW.js} +1 -1
- package/apps/web/dist/assets/{elixir-B8tse0js.js → elixir-ClG78o74.js} +1 -1
- package/apps/web/dist/assets/{elm-F2InAxd7.js → elm-DZDmCmuw.js} +1 -1
- package/apps/web/dist/assets/{erb-CAIamwOm.js → erb-B1mu0-DN.js} +1 -1
- package/apps/web/dist/assets/{git-rebase-U0z47rUi.js → git-rebase-x1gs-rK2.js} +1 -1
- package/apps/web/dist/assets/{glimmer-js-Bg_zPoN-.js → glimmer-js-Cjos1igW.js} +1 -1
- package/apps/web/dist/assets/{glimmer-ts-v3suqAPH.js → glimmer-ts-CrR-PFvn.js} +1 -1
- package/apps/web/dist/assets/{glsl-8io-Pcmd.js → glsl-DR0G8e99.js} +1 -1
- package/apps/web/dist/assets/{graphql-B_q9cV-C.js → graphql-DKmrBZt7.js} +1 -1
- package/apps/web/dist/assets/{hack-DosfNWDX.js → hack-Brs_j9Xv.js} +1 -1
- package/apps/web/dist/assets/{haml-DcBtkGn5.js → haml-8Lc-lF96.js} +1 -1
- package/apps/web/dist/assets/{handlebars-2F0KxPFu.js → handlebars-Ds8WdPdv.js} +1 -1
- package/apps/web/dist/assets/{html-DrWZZyp3.js → html-C0gvYDho.js} +1 -1
- package/apps/web/dist/assets/{html-derivative-BaV8vDp-.js → html-derivative-CU9dkErg.js} +1 -1
- package/apps/web/dist/assets/{http-Dwo7FxJA.js → http-BE0w0Fty.js} +1 -1
- package/apps/web/dist/assets/{hurl-9r6SjmMP.js → hurl-DqZk6YcW.js} +1 -1
- package/apps/web/dist/assets/index-0B-Gdd7b.css +2 -0
- package/apps/web/dist/assets/index-rgKS_aMw.js +1148 -0
- package/apps/web/dist/assets/{java-CAkt7S2h.js → java-PSSQ0YmM.js} +1 -1
- package/apps/web/dist/assets/{javascript-CMo8GEjU.js → javascript-Ci3r4rzs.js} +1 -1
- package/apps/web/dist/assets/{jinja-XhRt7Tzv.js → jinja-lLTko9Wl.js} +1 -1
- package/apps/web/dist/assets/{jison-BXZQEb4C.js → jison-1BpHD5UK.js} +1 -1
- package/apps/web/dist/assets/{json-SyJDQmRp.js → json-BAUn76M4.js} +1 -1
- package/apps/web/dist/assets/{jsx-B76-zKzH.js → jsx-B8sFp1vz.js} +1 -1
- package/apps/web/dist/assets/{julia-Ca5lbuMA.js → julia-C4TZ4EL9.js} +1 -1
- package/apps/web/dist/assets/{just-CDt2RojJ.js → just-D5qIrqkp.js} +1 -1
- package/apps/web/dist/assets/{latex-D9bybvWo.js → latex-BL7XYxFg.js} +1 -1
- package/apps/web/dist/assets/{liquid-DMp-tByG.js → liquid-DBx9Hq7B.js} +1 -1
- package/apps/web/dist/assets/{lua-zNnfh_4o.js → lua-BqjicQyd.js} +1 -1
- package/apps/web/dist/assets/{marko-Ce5B11L0.js → marko-CHgy7yaY.js} +1 -1
- package/apps/web/dist/assets/{mdc-Btapt9E8.js → mdc-DOloBwb8.js} +1 -1
- package/apps/web/dist/assets/{nginx-Bzjverzh.js → nginx-g7I1HPwV.js} +1 -1
- package/apps/web/dist/assets/{nim-D6zpNhUX.js → nim-O6zJV1LD.js} +1 -1
- package/apps/web/dist/assets/{org-DTy-FvFD.js → org-HLg4-757.js} +1 -1
- package/apps/web/dist/assets/{perl-BV8Dxusy.js → perl-B3cDKTai.js} +1 -1
- package/apps/web/dist/assets/{php-BjkIa26S.js → php-2eDApoU3.js} +1 -1
- package/apps/web/dist/assets/{pug-DdfmTHk6.js → pug-itzGn-nK.js} +1 -1
- package/apps/web/dist/assets/{qml-BWLYImhB.js → qml-sU0VtUmf.js} +1 -1
- package/apps/web/dist/assets/{r-CKc9klse.js → r-C6mhPqgr.js} +1 -1
- package/apps/web/dist/assets/{razor-YcF2a59Y.js → razor-CSzRtbW7.js} +1 -1
- package/apps/web/dist/assets/{regexp-D3esVJ1X.js → regexp-BmdBJRV9.js} +1 -1
- package/apps/web/dist/assets/{rst-DVaj4AQD.js → rst-BRxUd6sp.js} +1 -1
- package/apps/web/dist/assets/{ruby-DBJQEVev.js → ruby-CQHHOfmX.js} +1 -1
- package/apps/web/dist/assets/{sas-NSuLkGD1.js → sas-DlaL8b25.js} +1 -1
- package/apps/web/dist/assets/{scss-TMy8m-so.js → scss-Cc71ABWp.js} +1 -1
- package/apps/web/dist/assets/{shellscript-BVuIsFkY.js → shellscript-CzA_LIIm.js} +1 -1
- package/apps/web/dist/assets/{shellsession-CV7MHIu6.js → shellsession-DESJlgp3.js} +1 -1
- package/apps/web/dist/assets/{soy-DDY0EoKE.js → soy-Nkvq88YN.js} +1 -1
- package/apps/web/dist/assets/{sql-CoZqqtiv.js → sql-CtPxrO7T.js} +1 -1
- package/apps/web/dist/assets/{stata-B8C1noIb.js → stata-BacEfVTr.js} +1 -1
- package/apps/web/dist/assets/{surrealql-BYiUJt-V.js → surrealql-CYW10JNe.js} +1 -1
- package/apps/web/dist/assets/{svelte-DGeZ8dPs.js → svelte-D3_tCKGs.js} +1 -1
- package/apps/web/dist/assets/{templ-Dn21SE_C.js → templ-BGKIfY_b.js} +1 -1
- package/apps/web/dist/assets/{tex-Da9GBCsM.js → tex-BkkYOaXX.js} +1 -1
- package/apps/web/dist/assets/{ts-tags-BuL1T6K3.js → ts-tags-B02tnbpZ.js} +1 -1
- package/apps/web/dist/assets/{tsx-DiLCnfgl.js → tsx-D3MB1IUf.js} +1 -1
- package/apps/web/dist/assets/{twig-Cx1peiH-.js → twig-CPhw2wGK.js} +1 -1
- package/apps/web/dist/assets/{typescript-pVxvHJ4R.js → typescript-B7U4faGm.js} +1 -1
- package/apps/web/dist/assets/{typst-CJQbitzj.js → typst-CN9EZswy.js} +1 -1
- package/apps/web/dist/assets/{vue-CXKyVnbB.js → vue-YH8dfjo_.js} +1 -1
- package/apps/web/dist/assets/{vue-html-AQjnBMTh.js → vue-html-NwX2xzgp.js} +1 -1
- package/apps/web/dist/assets/{vue-vine-DYxt9AbK.js → vue-vine-DoRmnjLj.js} +1 -1
- package/apps/web/dist/assets/{xml-CEJ01uJ1.js → xml-DgfgLpPc.js} +1 -1
- package/apps/web/dist/assets/{xsl-CULFBy6v.js → xsl-CEwQRUK_.js} +1 -1
- package/apps/web/dist/assets/{yaml-BNf68xM_.js → yaml-CEFOzF95.js} +1 -1
- package/apps/web/dist/icon.svg +5 -0
- package/apps/web/dist/index.html +2 -2
- package/apps/web/dist/sw.js +19 -8
- package/bin/offsite.js +1 -1
- package/package.json +5 -1
- package/apps/server/dist/args.js +0 -11
- package/apps/server/dist/claude.js +0 -245
- package/apps/server/dist/config.js +0 -191
- package/apps/server/dist/content.js +0 -172
- package/apps/server/dist/db.js +0 -13
- package/apps/server/dist/files.js +0 -50
- package/apps/server/dist/git.js +0 -133
- package/apps/server/dist/history.js +0 -137
- package/apps/server/dist/hub.js +0 -10
- package/apps/server/dist/main.js +0 -70
- package/apps/server/dist/projects.js +0 -92
- package/apps/server/dist/protocol.js +0 -20
- package/apps/server/dist/push.js +0 -152
- package/apps/server/dist/run.js +0 -375
- package/apps/server/dist/runs.js +0 -102
- package/apps/server/dist/schemes.js +0 -262
- package/apps/server/dist/sessions.js +0 -99
- package/apps/server/dist/socket.js +0 -193
- package/apps/server/dist/storage.js +0 -71
- package/apps/server/dist/terminal/alerts.js +0 -145
- package/apps/server/dist/terminal/process.js +0 -207
- package/apps/server/dist/terminal/shell-check.js +0 -91
- package/apps/server/dist/terminal/shell-integration.js +0 -112
- package/apps/server/dist/terminal/terminal.js +0 -402
- package/apps/web/dist/assets/index-CIBt6t2M.css +0 -2
- package/apps/web/dist/assets/index-CPpMMYyZ.js +0 -902
- /package/apps/server/src/{terminal → services/terminal}/shell-integration/bash/offsite.bash +0 -0
- /package/apps/server/src/{terminal → services/terminal}/shell-integration/fish/offsite.fish +0 -0
- /package/apps/server/src/{terminal → services/terminal}/shell-integration/zsh/.zshenv +0 -0
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
export const ALERT_DELAY = 500;
|
|
2
|
-
const BELL_GAP = 5000;
|
|
3
|
-
const NOTIFICATION_GAP = 1000;
|
|
4
|
-
const PAYLOAD_LIMIT = 1024;
|
|
5
|
-
const ESC = 0x1b;
|
|
6
|
-
const BEL = 0x07;
|
|
7
|
-
const CAN = 0x18;
|
|
8
|
-
const SUB = 0x1a;
|
|
9
|
-
export class Scanner {
|
|
10
|
-
title = '';
|
|
11
|
-
state = 'ground';
|
|
12
|
-
payload = [];
|
|
13
|
-
scan(bytes) {
|
|
14
|
-
let alert = null;
|
|
15
|
-
let index = 0;
|
|
16
|
-
while (index < bytes.length) {
|
|
17
|
-
if (this.state === 'ground') {
|
|
18
|
-
let offset = index;
|
|
19
|
-
while (offset < bytes.length &&
|
|
20
|
-
bytes[offset] !== ESC &&
|
|
21
|
-
bytes[offset] !== BEL)
|
|
22
|
-
offset++;
|
|
23
|
-
if (offset === bytes.length)
|
|
24
|
-
break;
|
|
25
|
-
index = offset;
|
|
26
|
-
}
|
|
27
|
-
const found = this.step(bytes[index]);
|
|
28
|
-
index++;
|
|
29
|
-
if (found === null)
|
|
30
|
-
continue;
|
|
31
|
-
if (found.type === 'bell')
|
|
32
|
-
alert ??= found;
|
|
33
|
-
else
|
|
34
|
-
alert = found;
|
|
35
|
-
}
|
|
36
|
-
return alert;
|
|
37
|
-
}
|
|
38
|
-
step(byte) {
|
|
39
|
-
if (byte === CAN || byte === SUB) {
|
|
40
|
-
this.state = 'ground';
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
switch (this.state) {
|
|
44
|
-
case 'ground':
|
|
45
|
-
if (byte === ESC)
|
|
46
|
-
this.state = 'escape';
|
|
47
|
-
if (byte === BEL)
|
|
48
|
-
return { type: 'bell' };
|
|
49
|
-
return null;
|
|
50
|
-
case 'escape':
|
|
51
|
-
if (byte === 0x5d) {
|
|
52
|
-
this.payload = [];
|
|
53
|
-
this.state = 'osc';
|
|
54
|
-
}
|
|
55
|
-
else if ([0x50, 0x58, 0x5e, 0x5f].includes(byte))
|
|
56
|
-
this.state = 'text';
|
|
57
|
-
else if (byte !== ESC)
|
|
58
|
-
this.state = 'ground';
|
|
59
|
-
if (byte === BEL)
|
|
60
|
-
return { type: 'bell' };
|
|
61
|
-
return null;
|
|
62
|
-
case 'osc':
|
|
63
|
-
if (byte === BEL) {
|
|
64
|
-
this.state = 'ground';
|
|
65
|
-
return this.finish();
|
|
66
|
-
}
|
|
67
|
-
if (byte === ESC)
|
|
68
|
-
this.state = 'oscEscape';
|
|
69
|
-
else if (this.payload.length < PAYLOAD_LIMIT)
|
|
70
|
-
this.payload.push(byte);
|
|
71
|
-
return null;
|
|
72
|
-
case 'oscEscape':
|
|
73
|
-
if (byte === 0x5c) {
|
|
74
|
-
this.state = 'ground';
|
|
75
|
-
return this.finish();
|
|
76
|
-
}
|
|
77
|
-
this.state = 'escape';
|
|
78
|
-
return this.step(byte);
|
|
79
|
-
case 'text':
|
|
80
|
-
if (byte === ESC)
|
|
81
|
-
this.state = 'textEscape';
|
|
82
|
-
return null;
|
|
83
|
-
case 'textEscape':
|
|
84
|
-
if (byte === 0x5c) {
|
|
85
|
-
this.state = 'ground';
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
this.state = 'escape';
|
|
89
|
-
return this.step(byte);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
finish() {
|
|
93
|
-
const text = Buffer.from(this.payload).toString('utf8');
|
|
94
|
-
if (text.startsWith('0;') || text.startsWith('2;')) {
|
|
95
|
-
this.title = text.slice(2);
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
if (text.startsWith('9;')) {
|
|
99
|
-
const body = text.slice(2);
|
|
100
|
-
const digits = /^\d*/.exec(body)?.[0].length ?? 0;
|
|
101
|
-
if (digits > 0 && (body.length === digits || body[digits] === ';'))
|
|
102
|
-
return null;
|
|
103
|
-
return { type: 'notification', title: '', body };
|
|
104
|
-
}
|
|
105
|
-
if (text.startsWith('777;notify;')) {
|
|
106
|
-
const rest = text.slice('777;notify;'.length);
|
|
107
|
-
const split = rest.indexOf(';');
|
|
108
|
-
return split === -1
|
|
109
|
-
? { type: 'notification', title: rest, body: '' }
|
|
110
|
-
: {
|
|
111
|
-
type: 'notification',
|
|
112
|
-
title: rest.slice(0, split),
|
|
113
|
-
body: rest.slice(split + 1),
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
export class Alerts {
|
|
120
|
-
pushedAt = null;
|
|
121
|
-
bellPending = false;
|
|
122
|
-
startBell() {
|
|
123
|
-
if (this.bellPending || this.pushedRecently(BELL_GAP))
|
|
124
|
-
return false;
|
|
125
|
-
this.bellPending = true;
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
128
|
-
finishBell() {
|
|
129
|
-
if (!this.bellPending)
|
|
130
|
-
return false;
|
|
131
|
-
this.bellPending = false;
|
|
132
|
-
this.pushedAt = Date.now();
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
startNotification() {
|
|
136
|
-
this.bellPending = false;
|
|
137
|
-
if (this.pushedRecently(NOTIFICATION_GAP))
|
|
138
|
-
return false;
|
|
139
|
-
this.pushedAt = Date.now();
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
pushedRecently(gap) {
|
|
143
|
-
return this.pushedAt !== null && Date.now() - this.pushedAt < gap;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
3
|
-
import { readFile, readlink, stat } from 'node:fs/promises';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { promisify } from 'node:util';
|
|
6
|
-
import { spawn } from '@lydell/node-pty';
|
|
7
|
-
import { defaultShell, platform, readConfig, version } from '../config.js';
|
|
8
|
-
import { prepare } from './shell-integration.js';
|
|
9
|
-
const run = promisify(execFile);
|
|
10
|
-
const iterm2Version = '3.6.8';
|
|
11
|
-
const ghosttyVersion = '1.3.1';
|
|
12
|
-
const shellNames = process.platform === 'win32'
|
|
13
|
-
? ['pwsh', 'powershell', 'cmd']
|
|
14
|
-
: ['zsh', 'bash', 'fish', 'pwsh'];
|
|
15
|
-
export function startShell(cwd) {
|
|
16
|
-
const options = launch();
|
|
17
|
-
const env = {};
|
|
18
|
-
let file;
|
|
19
|
-
let args;
|
|
20
|
-
if (process.platform === 'win32') {
|
|
21
|
-
Object.assign(env, process.env);
|
|
22
|
-
file = options.command ?? defaultShell();
|
|
23
|
-
args = [];
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
for (const name of ['HOME', 'USER', 'LOGNAME', 'TMPDIR', 'SSH_AUTH_SOCK']) {
|
|
27
|
-
const value = process.env[name];
|
|
28
|
-
if (value !== undefined)
|
|
29
|
-
env[name] = value;
|
|
30
|
-
}
|
|
31
|
-
const shell = defaultShell();
|
|
32
|
-
env.SHELL = shell;
|
|
33
|
-
env.LANG = 'en_US.UTF-8';
|
|
34
|
-
if (process.env.PATH !== undefined)
|
|
35
|
-
env.PATH = process.env.PATH;
|
|
36
|
-
const program = (options.shellIntegration
|
|
37
|
-
? prepare(env, options.command, shell)
|
|
38
|
-
: null) ??
|
|
39
|
-
options.command ??
|
|
40
|
-
`'${shell}'`;
|
|
41
|
-
file = '/bin/bash';
|
|
42
|
-
args = ['--noprofile', '--norc', '-c', `export -n PATH; exec -l ${program}`];
|
|
43
|
-
}
|
|
44
|
-
env.TERM = 'xterm-256color';
|
|
45
|
-
env.COLORTERM = 'truecolor';
|
|
46
|
-
Object.assign(env, options.environment);
|
|
47
|
-
return spawn(file, args, {
|
|
48
|
-
name: 'xterm-256color',
|
|
49
|
-
cols: 80,
|
|
50
|
-
rows: 24,
|
|
51
|
-
cwd,
|
|
52
|
-
env,
|
|
53
|
-
encoding: null,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
export async function currentDirectory(pid) {
|
|
57
|
-
if (process.platform === 'linux')
|
|
58
|
-
return readlink(`/proc/${pid}/cwd`);
|
|
59
|
-
if (process.platform === 'darwin') {
|
|
60
|
-
const { stdout } = await run('lsof', [
|
|
61
|
-
'-a',
|
|
62
|
-
'-d',
|
|
63
|
-
'cwd',
|
|
64
|
-
'-p',
|
|
65
|
-
String(pid),
|
|
66
|
-
'-Fn',
|
|
67
|
-
]);
|
|
68
|
-
const line = stdout.split('\n').find((item) => item.startsWith('n'));
|
|
69
|
-
if (line === undefined)
|
|
70
|
-
throw new Error(`lsof found no folder for ${pid}`);
|
|
71
|
-
return line.slice(1);
|
|
72
|
-
}
|
|
73
|
-
throw new Error('reading the directory of another process is not supported on this platform');
|
|
74
|
-
}
|
|
75
|
-
export async function busyProgram(pid) {
|
|
76
|
-
if (process.platform === 'win32') {
|
|
77
|
-
const { stdout } = await run('powershell.exe', [
|
|
78
|
-
'-NoProfile',
|
|
79
|
-
'-Command',
|
|
80
|
-
`Get-CimInstance Win32_Process -Filter "ParentProcessId=${pid}" | Select-Object -ExpandProperty Name`,
|
|
81
|
-
]);
|
|
82
|
-
const names = [
|
|
83
|
-
...new Set(stdout
|
|
84
|
-
.split(/\r?\n/)
|
|
85
|
-
.map((line) => line.trim())
|
|
86
|
-
.filter((line) => line !== '')),
|
|
87
|
-
];
|
|
88
|
-
return names.length === 0 ? null : names.join(', ');
|
|
89
|
-
}
|
|
90
|
-
const group = await foregroundGroup(pid);
|
|
91
|
-
if (group <= 0)
|
|
92
|
-
throw new Error('its foreground process group is unknown');
|
|
93
|
-
if (group === pid)
|
|
94
|
-
return null;
|
|
95
|
-
try {
|
|
96
|
-
return await programName(group);
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
console.warn(`could not read the name of process ${group}: ${error}`);
|
|
100
|
-
return `process ${group}`;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
async function foregroundGroup(pid) {
|
|
104
|
-
if (process.platform === 'linux') {
|
|
105
|
-
const text = await readFile(`/proc/${pid}/stat`, 'utf8');
|
|
106
|
-
return Number(text.slice(text.lastIndexOf(')') + 2).split(' ')[5]);
|
|
107
|
-
}
|
|
108
|
-
const { stdout } = await run('ps', ['-o', 'tpgid=', '-p', String(pid)]);
|
|
109
|
-
return Number(stdout.trim());
|
|
110
|
-
}
|
|
111
|
-
async function programName(pid) {
|
|
112
|
-
if (process.platform === 'linux')
|
|
113
|
-
return (await readFile(`/proc/${pid}/comm`, 'utf8')).trimEnd();
|
|
114
|
-
const { stdout } = await run('ps', ['-o', 'comm=', '-p', String(pid)]);
|
|
115
|
-
return path.basename(stdout.trim());
|
|
116
|
-
}
|
|
117
|
-
export async function gitBranch(directory) {
|
|
118
|
-
let git = null;
|
|
119
|
-
for (let folder = directory;; folder = path.dirname(folder)) {
|
|
120
|
-
const candidate = path.join(folder, '.git');
|
|
121
|
-
if ((await stat(candidate).catch(() => null)) !== null) {
|
|
122
|
-
git = candidate;
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
if (path.dirname(folder) === folder)
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
if (git === null)
|
|
129
|
-
return null;
|
|
130
|
-
const info = await stat(git);
|
|
131
|
-
if (info.isFile()) {
|
|
132
|
-
const text = await readSmallFile(git);
|
|
133
|
-
if (text === null || !text.startsWith('gitdir:'))
|
|
134
|
-
return null;
|
|
135
|
-
git = path.resolve(path.dirname(git), text.slice('gitdir:'.length).trim());
|
|
136
|
-
}
|
|
137
|
-
const head = (await readSmallFile(path.join(git, 'HEAD')))?.trim();
|
|
138
|
-
if (head === undefined)
|
|
139
|
-
return null;
|
|
140
|
-
if (head.startsWith('ref: refs/heads/'))
|
|
141
|
-
return head.slice('ref: refs/heads/'.length);
|
|
142
|
-
return head.length >= 7 ? head.slice(0, 7) : null;
|
|
143
|
-
}
|
|
144
|
-
async function readSmallFile(file) {
|
|
145
|
-
const info = await stat(file).catch(() => null);
|
|
146
|
-
if (info === null || !info.isFile())
|
|
147
|
-
return null;
|
|
148
|
-
return (await readFile(file, 'utf8')).slice(0, 4096);
|
|
149
|
-
}
|
|
150
|
-
function launch() {
|
|
151
|
-
const config = readConfig().terminal;
|
|
152
|
-
const [program, terminal, programVersion] = {
|
|
153
|
-
offsite: ['offsite', 'offsite', version],
|
|
154
|
-
ghostty: ['ghostty', 'ghostty', ghosttyVersion],
|
|
155
|
-
iterm2: ['iTerm.app', 'iTerm2', iterm2Version],
|
|
156
|
-
}[config.behavior.terminalIdentity];
|
|
157
|
-
return {
|
|
158
|
-
command: config.shell[platform()],
|
|
159
|
-
environment: {
|
|
160
|
-
TERM_PROGRAM: program,
|
|
161
|
-
TERM_PROGRAM_VERSION: programVersion,
|
|
162
|
-
LC_TERMINAL: terminal,
|
|
163
|
-
LC_TERMINAL_VERSION: programVersion,
|
|
164
|
-
},
|
|
165
|
-
shellIntegration: config.behavior.shellIntegration,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
export function listShells() {
|
|
169
|
-
const candidates = (process.env.PATH ?? '')
|
|
170
|
-
.split(path.delimiter)
|
|
171
|
-
.filter((directory) => directory !== '')
|
|
172
|
-
.flatMap((directory) => shellNames.map((name) => path.join(directory, executable(name))));
|
|
173
|
-
if (process.platform !== 'win32') {
|
|
174
|
-
try {
|
|
175
|
-
candidates.push(...readFileSync('/etc/shells', 'utf8')
|
|
176
|
-
.split('\n')
|
|
177
|
-
.map((line) => line.trim())
|
|
178
|
-
.filter((line) => line.startsWith('/')));
|
|
179
|
-
}
|
|
180
|
-
catch { }
|
|
181
|
-
}
|
|
182
|
-
const seen = new Set();
|
|
183
|
-
const shells = [];
|
|
184
|
-
for (const name of shellNames) {
|
|
185
|
-
for (const candidate of candidates) {
|
|
186
|
-
if (path.basename(candidate) !== executable(name))
|
|
187
|
-
continue;
|
|
188
|
-
if (!statSync(candidate, { throwIfNoEntry: false })?.isFile())
|
|
189
|
-
continue;
|
|
190
|
-
let target;
|
|
191
|
-
try {
|
|
192
|
-
target = realpathSync(candidate);
|
|
193
|
-
}
|
|
194
|
-
catch {
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
if (seen.has(target))
|
|
198
|
-
continue;
|
|
199
|
-
seen.add(target);
|
|
200
|
-
shells.push({ name, path: candidate });
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return shells;
|
|
204
|
-
}
|
|
205
|
-
function executable(name) {
|
|
206
|
-
return process.platform === 'win32' ? `${name}.exe` : name;
|
|
207
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
function zshOptionsSupported(options) {
|
|
2
|
-
for (let position = 0; position < options.length; position++) {
|
|
3
|
-
const word = options[position];
|
|
4
|
-
if (!word.startsWith('-') && !word.startsWith('+'))
|
|
5
|
-
return false;
|
|
6
|
-
const enable = word.startsWith('-');
|
|
7
|
-
const flags = word.slice(1);
|
|
8
|
-
if (flags === '' || flags === '-')
|
|
9
|
-
return false;
|
|
10
|
-
if (flags.startsWith('-')) {
|
|
11
|
-
if (!zshOptionAllowed(flags.slice(1), enable))
|
|
12
|
-
return false;
|
|
13
|
-
continue;
|
|
14
|
-
}
|
|
15
|
-
for (let index = 0; index < flags.length; index++) {
|
|
16
|
-
const letter = flags[index];
|
|
17
|
-
if (letter === 'o') {
|
|
18
|
-
let name = flags.slice(index + 1);
|
|
19
|
-
if (name === '') {
|
|
20
|
-
position++;
|
|
21
|
-
if (position === options.length)
|
|
22
|
-
return false;
|
|
23
|
-
name = options[position];
|
|
24
|
-
}
|
|
25
|
-
if (!zshOptionAllowed(name, enable))
|
|
26
|
-
return false;
|
|
27
|
-
break;
|
|
28
|
-
}
|
|
29
|
-
if (letter === 'b' || letter === 'c')
|
|
30
|
-
return false;
|
|
31
|
-
if (enable && 'dfnprvx'.includes(letter))
|
|
32
|
-
return false;
|
|
33
|
-
if (!enable && letter === 'i')
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
function zshOptionAllowed(option, enable) {
|
|
40
|
-
let name = option.toLowerCase().replace(/[-_]/g, '');
|
|
41
|
-
let value = enable;
|
|
42
|
-
if (name.startsWith('no')) {
|
|
43
|
-
name = name.slice(2);
|
|
44
|
-
value = !enable;
|
|
45
|
-
}
|
|
46
|
-
if (['rcs', 'globalrcs', 'interactive', 'exec'].includes(name))
|
|
47
|
-
return value;
|
|
48
|
-
if (['privileged', 'restricted', 'sourcetrace', 'verbose', 'xtrace'].includes(name))
|
|
49
|
-
return !value;
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
export function shellIntegrationIssue(command, platform, defaultShell) {
|
|
53
|
-
if (platform === 'windows')
|
|
54
|
-
return 'Not available on Windows';
|
|
55
|
-
if (command !== null && /[\n"'\\$`;&|<>()*?[\]{}#!]/.test(command))
|
|
56
|
-
return 'Not available for commands with shell syntax';
|
|
57
|
-
const words = command === null
|
|
58
|
-
? [defaultShell]
|
|
59
|
-
: command.split(/[ \t]/).filter((word) => word !== '');
|
|
60
|
-
if (words.length === 0)
|
|
61
|
-
return 'Available for bash, zsh and fish';
|
|
62
|
-
const path = words[0];
|
|
63
|
-
const name = path.split('/').pop();
|
|
64
|
-
const options = words.slice(1);
|
|
65
|
-
if (name === 'bash') {
|
|
66
|
-
if (platform === 'macos' && (path === '/bin/bash' || !path.includes('/')))
|
|
67
|
-
return 'Needs a newer bash than the one macOS includes';
|
|
68
|
-
if (options.every((option) => ['-l', '--login', '-i'].includes(option)))
|
|
69
|
-
return null;
|
|
70
|
-
return 'Not available with these bash options';
|
|
71
|
-
}
|
|
72
|
-
if (name === 'zsh') {
|
|
73
|
-
if (zshOptionsSupported(options))
|
|
74
|
-
return null;
|
|
75
|
-
return 'Not available with these zsh options';
|
|
76
|
-
}
|
|
77
|
-
if (name === 'fish') {
|
|
78
|
-
const supported = options.every((option) => {
|
|
79
|
-
if (option.startsWith('--')) {
|
|
80
|
-
const name = option.slice(2).split('=')[0];
|
|
81
|
-
return (name === '' ||
|
|
82
|
-
!['command', 'no-config', 'no-execute'].some((long) => long.startsWith(name)));
|
|
83
|
-
}
|
|
84
|
-
return !(option.startsWith('-') && /[cNn]/.test(option));
|
|
85
|
-
});
|
|
86
|
-
if (supported)
|
|
87
|
-
return null;
|
|
88
|
-
return 'Not available with these fish options';
|
|
89
|
-
}
|
|
90
|
-
return 'Available for bash, zsh and fish';
|
|
91
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { platform } from '../config.js';
|
|
5
|
-
import { home } from '../storage.js';
|
|
6
|
-
import { shellIntegrationIssue } from './shell-check.js';
|
|
7
|
-
const scripts = path.join(import.meta.dirname, '..', '..', 'src', 'terminal', 'shell-integration');
|
|
8
|
-
const folder = path.join(home, 'shell-integration');
|
|
9
|
-
export function prepare(env, command, shell) {
|
|
10
|
-
if (shellIntegrationIssue(command, platform(), shell) !== null)
|
|
11
|
-
return null;
|
|
12
|
-
const words = command === null
|
|
13
|
-
? [shell]
|
|
14
|
-
: command.split(/[ \t]/).filter((word) => word !== '');
|
|
15
|
-
const program = command === null ? `'${shell}'` : words[0];
|
|
16
|
-
const args = words.slice(1);
|
|
17
|
-
const name = words[0].split('/').pop();
|
|
18
|
-
let integration;
|
|
19
|
-
if (name === 'bash') {
|
|
20
|
-
const userHome = process.env.HOME;
|
|
21
|
-
if (userHome === undefined || userHome === '')
|
|
22
|
-
return null;
|
|
23
|
-
const script = path.join(folder, 'bash', 'offsite.bash');
|
|
24
|
-
if (!writeScript(script, 'bash/offsite.bash'))
|
|
25
|
-
return null;
|
|
26
|
-
env.ENV = script.replace(/[\\$`"]/g, (character) => `\\${character}`);
|
|
27
|
-
env.HISTFILE = path.join(userHome, '.bash_history');
|
|
28
|
-
integration = ' --posix';
|
|
29
|
-
}
|
|
30
|
-
else if (name === 'zsh') {
|
|
31
|
-
if (!zshGlobalsSupported())
|
|
32
|
-
return null;
|
|
33
|
-
if (['.zshenv', '.zprofile', '.zshrc', '.zlogin'].every((file) => !existsSync(path.join(os.homedir(), file))))
|
|
34
|
-
return null;
|
|
35
|
-
if (!writeScript(path.join(folder, 'zsh', '.zshenv'), 'zsh/.zshenv'))
|
|
36
|
-
return null;
|
|
37
|
-
env.ZDOTDIR = path.join(folder, 'zsh');
|
|
38
|
-
integration = '';
|
|
39
|
-
}
|
|
40
|
-
else if (name === 'fish') {
|
|
41
|
-
const script = path.join(folder, 'fish', 'offsite.fish');
|
|
42
|
-
if (!writeScript(script, 'fish/offsite.fish'))
|
|
43
|
-
return null;
|
|
44
|
-
const source = `source '${script.replaceAll('\\', '\\\\').replaceAll("'", "\\'")}'`;
|
|
45
|
-
integration = ` --init-command '${source.replaceAll("'", "'\\''")}'`;
|
|
46
|
-
}
|
|
47
|
-
else
|
|
48
|
-
throw new Error(`shell integration does not support ${name}`);
|
|
49
|
-
return [`${program}${integration}`, ...args].join(' ');
|
|
50
|
-
}
|
|
51
|
-
function zshGlobalsSupported() {
|
|
52
|
-
for (const global of ['/etc/zshenv', '/etc/zsh/zshenv']) {
|
|
53
|
-
let contents;
|
|
54
|
-
try {
|
|
55
|
-
contents = readFileSync(global, 'utf8');
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
if (error.code === 'ENOENT')
|
|
59
|
-
continue;
|
|
60
|
-
console.warn(`could not read ${global}, zsh starts without shell integration: ${error}`);
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
for (const rawLine of contents.split('\n')) {
|
|
64
|
-
const line = rawLine.trimStart();
|
|
65
|
-
if (line.startsWith('#'))
|
|
66
|
-
continue;
|
|
67
|
-
const normalized = line.toLowerCase().replaceAll('_', '');
|
|
68
|
-
if ([
|
|
69
|
-
'zdotdir',
|
|
70
|
-
'rcs',
|
|
71
|
-
'emulate',
|
|
72
|
-
'debug',
|
|
73
|
-
'xtrace',
|
|
74
|
-
'verbose',
|
|
75
|
-
'sourcetrace',
|
|
76
|
-
].some((word) => normalized.includes(word)))
|
|
77
|
-
return false;
|
|
78
|
-
let setting = false;
|
|
79
|
-
for (const word of line.split(/[ \t;&|]/)) {
|
|
80
|
-
if (word === '')
|
|
81
|
-
continue;
|
|
82
|
-
if (['set', 'setopt', 'unsetopt'].includes(word)) {
|
|
83
|
-
setting = true;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
const flag = word.startsWith('-') || word.startsWith('+');
|
|
87
|
-
if (setting && flag && /[fxv]/.test(word))
|
|
88
|
-
return false;
|
|
89
|
-
if (!flag)
|
|
90
|
-
setting = false;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return true;
|
|
95
|
-
}
|
|
96
|
-
function writeScript(target, source) {
|
|
97
|
-
try {
|
|
98
|
-
const contents = readFileSync(path.join(scripts, source), 'utf8');
|
|
99
|
-
const existing = lstatSync(target, { throwIfNoEntry: false });
|
|
100
|
-
if (existing?.isFile() && readFileSync(target, 'utf8') === contents)
|
|
101
|
-
return true;
|
|
102
|
-
mkdirSync(path.dirname(target), { recursive: true });
|
|
103
|
-
rmSync(`${target}.tmp`, { force: true });
|
|
104
|
-
writeFileSync(`${target}.tmp`, contents, { flag: 'wx' });
|
|
105
|
-
renameSync(`${target}.tmp`, target);
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
catch (error) {
|
|
109
|
-
console.warn(`could not write ${target}, the shell starts without shell integration: ${error}`);
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
|
-
}
|