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,191 +0,0 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { broadcast } from './hub.js';
|
|
4
|
-
import { schemes } from './schemes.js';
|
|
5
|
-
import { home, readJson, writeJson } from './storage.js';
|
|
6
|
-
export const version = '0.0.0';
|
|
7
|
-
const configFile = path.join(home, 'config.json');
|
|
8
|
-
export const defaults = {
|
|
9
|
-
theme: schemes[0].id,
|
|
10
|
-
claudeBaseDir: path.join(os.homedir(), '.claude'),
|
|
11
|
-
composer: {
|
|
12
|
-
accountId: null,
|
|
13
|
-
model: 'haiku',
|
|
14
|
-
effort: 'low',
|
|
15
|
-
permissionMode: 'bypassPermissions',
|
|
16
|
-
},
|
|
17
|
-
terminal: {
|
|
18
|
-
shell: { macos: null, linux: null, windows: null },
|
|
19
|
-
font: {
|
|
20
|
-
family: 'FiraCode Nerd Font',
|
|
21
|
-
size: 14,
|
|
22
|
-
lineHeight: 1.35,
|
|
23
|
-
ligatures: true,
|
|
24
|
-
},
|
|
25
|
-
cursor: { style: 'bar', blink: true },
|
|
26
|
-
padding: { top: 4, right: 4, bottom: 0, left: 4 },
|
|
27
|
-
scrollback: 1000,
|
|
28
|
-
boldIsBright: true,
|
|
29
|
-
behavior: {
|
|
30
|
-
modernizeShiftEnter: true,
|
|
31
|
-
pasteBrowserImages: true,
|
|
32
|
-
showGitBranch: true,
|
|
33
|
-
captureShortcuts: true,
|
|
34
|
-
bellFlash: true,
|
|
35
|
-
bellSound: true,
|
|
36
|
-
terminalIdentity: 'offsite',
|
|
37
|
-
shellIntegration: true,
|
|
38
|
-
clickToMoveCursor: true,
|
|
39
|
-
optionAsMeta: 'off',
|
|
40
|
-
confirmCloseBusyTab: true,
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
const choices = {
|
|
45
|
-
theme: schemes.map((scheme) => scheme.id),
|
|
46
|
-
'composer.effort': ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
47
|
-
'composer.permissionMode': [
|
|
48
|
-
'default',
|
|
49
|
-
'acceptEdits',
|
|
50
|
-
'bypassPermissions',
|
|
51
|
-
'plan',
|
|
52
|
-
'dontAsk',
|
|
53
|
-
'auto',
|
|
54
|
-
],
|
|
55
|
-
'terminal.cursor.style': ['block', 'bar', 'underline'],
|
|
56
|
-
'terminal.behavior.terminalIdentity': ['offsite', 'ghostty', 'iterm2'],
|
|
57
|
-
'terminal.behavior.optionAsMeta': ['off', 'left', 'right', 'both'],
|
|
58
|
-
};
|
|
59
|
-
const nullable = [
|
|
60
|
-
'composer.accountId',
|
|
61
|
-
'composer.model',
|
|
62
|
-
'composer.effort',
|
|
63
|
-
'composer.permissionMode',
|
|
64
|
-
'terminal.shell.macos',
|
|
65
|
-
'terminal.shell.linux',
|
|
66
|
-
'terminal.shell.windows',
|
|
67
|
-
];
|
|
68
|
-
const integers = [
|
|
69
|
-
'terminal.scrollback',
|
|
70
|
-
'terminal.padding.top',
|
|
71
|
-
'terminal.padding.right',
|
|
72
|
-
'terminal.padding.bottom',
|
|
73
|
-
'terminal.padding.left',
|
|
74
|
-
];
|
|
75
|
-
export function platform() {
|
|
76
|
-
if (process.platform === 'darwin')
|
|
77
|
-
return 'macos';
|
|
78
|
-
if (process.platform === 'linux')
|
|
79
|
-
return 'linux';
|
|
80
|
-
if (process.platform === 'win32')
|
|
81
|
-
return 'windows';
|
|
82
|
-
throw new Error(`unsupported platform ${process.platform}`);
|
|
83
|
-
}
|
|
84
|
-
export function defaultShell() {
|
|
85
|
-
if (process.platform === 'win32')
|
|
86
|
-
return process.env.COMSPEC ?? 'cmd.exe';
|
|
87
|
-
return os.userInfo().shell ?? '/bin/sh';
|
|
88
|
-
}
|
|
89
|
-
export function configInfo() {
|
|
90
|
-
return {
|
|
91
|
-
config: readConfig(),
|
|
92
|
-
defaults,
|
|
93
|
-
platform: platform(),
|
|
94
|
-
version,
|
|
95
|
-
defaultShell: defaultShell(),
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
export function readConfig() {
|
|
99
|
-
let user;
|
|
100
|
-
try {
|
|
101
|
-
user = readJson('config.json');
|
|
102
|
-
}
|
|
103
|
-
catch (error) {
|
|
104
|
-
console.warn(`invalid ${configFile}, using the default config: ${error}`);
|
|
105
|
-
return structuredClone(defaults);
|
|
106
|
-
}
|
|
107
|
-
if (user === null)
|
|
108
|
-
return structuredClone(defaults);
|
|
109
|
-
if (!isObject(user)) {
|
|
110
|
-
console.warn(`${configFile} is not an object, using the default config`);
|
|
111
|
-
return structuredClone(defaults);
|
|
112
|
-
}
|
|
113
|
-
let config = structuredClone(defaults);
|
|
114
|
-
for (const [key, value] of Object.entries(user))
|
|
115
|
-
config = merge(config, [key], value);
|
|
116
|
-
return config;
|
|
117
|
-
}
|
|
118
|
-
export function saveConfig(value) {
|
|
119
|
-
const error = invalid(value, defaults, []);
|
|
120
|
-
if (error !== null)
|
|
121
|
-
throw new Error(error);
|
|
122
|
-
writeJson('config.json', value);
|
|
123
|
-
process.env.CLAUDE_CONFIG_DIR = value.claudeBaseDir;
|
|
124
|
-
broadcast({ type: 'config', config: value });
|
|
125
|
-
return value;
|
|
126
|
-
}
|
|
127
|
-
function merge(config, keys, user) {
|
|
128
|
-
const template = keys.reduce((value, key) => (isObject(value) ? value[key] : undefined), config);
|
|
129
|
-
if (isObject(template) && isObject(user)) {
|
|
130
|
-
let next = config;
|
|
131
|
-
for (const [key, value] of Object.entries(user))
|
|
132
|
-
next = merge(next, [...keys, key], value);
|
|
133
|
-
return next;
|
|
134
|
-
}
|
|
135
|
-
const candidate = structuredClone(config);
|
|
136
|
-
const parent = keys
|
|
137
|
-
.slice(0, -1)
|
|
138
|
-
.reduce((value, key) => value[key], candidate);
|
|
139
|
-
parent[keys[keys.length - 1]] = user;
|
|
140
|
-
const error = invalid(candidate, defaults, []);
|
|
141
|
-
if (error === null)
|
|
142
|
-
return candidate;
|
|
143
|
-
console.warn(`invalid ${keys.join('.')} in ${configFile}, using its default: ${error}`);
|
|
144
|
-
return config;
|
|
145
|
-
}
|
|
146
|
-
function invalid(value, template, keys) {
|
|
147
|
-
const name = keys.join('.');
|
|
148
|
-
if (isObject(template)) {
|
|
149
|
-
if (!isObject(value))
|
|
150
|
-
return `${name || 'config'} must be an object`;
|
|
151
|
-
for (const key of Object.keys(value))
|
|
152
|
-
if (!(key in template))
|
|
153
|
-
return `unknown setting ${[...keys, key].join('.')}`;
|
|
154
|
-
for (const key of Object.keys(template)) {
|
|
155
|
-
if (!(key in value))
|
|
156
|
-
return `missing setting ${[...keys, key].join('.')}`;
|
|
157
|
-
const error = invalid(value[key], template[key], [...keys, key]);
|
|
158
|
-
if (error !== null)
|
|
159
|
-
return error;
|
|
160
|
-
}
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
if (nullable.includes(name) && value === null)
|
|
164
|
-
return null;
|
|
165
|
-
if (name === 'claudeBaseDir')
|
|
166
|
-
return typeof value === 'string' && path.isAbsolute(value)
|
|
167
|
-
? null
|
|
168
|
-
: 'claudeBaseDir must be an absolute path';
|
|
169
|
-
if (name in choices) {
|
|
170
|
-
if (choices[name].includes(value))
|
|
171
|
-
return null;
|
|
172
|
-
return `${name} must be one of ${choices[name].join(', ')}`;
|
|
173
|
-
}
|
|
174
|
-
if (template === null) {
|
|
175
|
-
if (typeof value === 'string')
|
|
176
|
-
return null;
|
|
177
|
-
return `${name} must be text or null`;
|
|
178
|
-
}
|
|
179
|
-
if (typeof value !== typeof template)
|
|
180
|
-
return `${name} must be a ${typeof template}`;
|
|
181
|
-
if (typeof value !== 'number')
|
|
182
|
-
return null;
|
|
183
|
-
if (!Number.isFinite(value) || value < 0)
|
|
184
|
-
return `${name} must be a number of at least 0`;
|
|
185
|
-
if (integers.includes(name) && !Number.isInteger(value))
|
|
186
|
-
return `${name} must be a whole number`;
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
function isObject(value) {
|
|
190
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
191
|
-
}
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
export function toMessage(type, item, time) {
|
|
2
|
-
return {
|
|
3
|
-
type,
|
|
4
|
-
uuid: item.uuid,
|
|
5
|
-
time,
|
|
6
|
-
content: isRecord(item.message) ? toBlocks(item.message.content) : [],
|
|
7
|
-
parent: item.parent_tool_use_id,
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
export function contextTokens(message) {
|
|
11
|
-
if (!isRecord(message) || !isRecord(message.usage))
|
|
12
|
-
return null;
|
|
13
|
-
const usage = message.usage;
|
|
14
|
-
return [
|
|
15
|
-
usage.input_tokens,
|
|
16
|
-
usage.cache_creation_input_tokens,
|
|
17
|
-
usage.cache_read_input_tokens,
|
|
18
|
-
usage.output_tokens,
|
|
19
|
-
].reduce((total, value) => total + (typeof value === 'number' ? value : 0), 0);
|
|
20
|
-
}
|
|
21
|
-
export function toContext(usage) {
|
|
22
|
-
const messages = usage.messageBreakdown;
|
|
23
|
-
const sections = [
|
|
24
|
-
{
|
|
25
|
-
title: 'Messages',
|
|
26
|
-
items: messages === undefined
|
|
27
|
-
? []
|
|
28
|
-
: [
|
|
29
|
-
{ name: 'User messages', tokens: messages.userMessageTokens },
|
|
30
|
-
{
|
|
31
|
-
name: 'Assistant messages',
|
|
32
|
-
tokens: messages.assistantMessageTokens,
|
|
33
|
-
},
|
|
34
|
-
{ name: 'Tool calls', tokens: messages.toolCallTokens },
|
|
35
|
-
{ name: 'Tool results', tokens: messages.toolResultTokens },
|
|
36
|
-
{ name: 'Attachments', tokens: messages.attachmentTokens },
|
|
37
|
-
],
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
title: 'Tools used',
|
|
41
|
-
items: (messages?.toolCallsByType ?? []).map((tool) => ({
|
|
42
|
-
name: tool.name,
|
|
43
|
-
tokens: tool.callTokens + tool.resultTokens,
|
|
44
|
-
})),
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
title: 'Memory files',
|
|
48
|
-
items: usage.memoryFiles.map((file) => ({
|
|
49
|
-
name: file.path,
|
|
50
|
-
tokens: file.tokens,
|
|
51
|
-
})),
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
title: 'MCP tools',
|
|
55
|
-
items: usage.mcpTools.map((tool) => ({
|
|
56
|
-
name: `${tool.serverName} · ${tool.name}`,
|
|
57
|
-
tokens: tool.tokens,
|
|
58
|
-
})),
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
title: 'Agents',
|
|
62
|
-
items: usage.agents.map((agent) => ({
|
|
63
|
-
name: agent.agentType,
|
|
64
|
-
tokens: agent.tokens,
|
|
65
|
-
})),
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
title: 'Skills',
|
|
69
|
-
items: (usage.skills?.skillFrontmatter ?? []).map((skill) => ({
|
|
70
|
-
name: skill.name,
|
|
71
|
-
tokens: skill.tokens,
|
|
72
|
-
})),
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
title: 'Last response',
|
|
76
|
-
items: usage.apiUsage === null
|
|
77
|
-
? []
|
|
78
|
-
: [
|
|
79
|
-
{ name: 'Input', tokens: usage.apiUsage.input_tokens },
|
|
80
|
-
{
|
|
81
|
-
name: 'Cache read',
|
|
82
|
-
tokens: usage.apiUsage.cache_read_input_tokens,
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
name: 'Cache write',
|
|
86
|
-
tokens: usage.apiUsage.cache_creation_input_tokens,
|
|
87
|
-
},
|
|
88
|
-
{ name: 'Output', tokens: usage.apiUsage.output_tokens },
|
|
89
|
-
],
|
|
90
|
-
},
|
|
91
|
-
];
|
|
92
|
-
return {
|
|
93
|
-
tokens: usage.totalTokens,
|
|
94
|
-
window: usage.maxTokens,
|
|
95
|
-
model: usage.model,
|
|
96
|
-
categories: usage.categories.flatMap((category) => category.kind === 'deferred'
|
|
97
|
-
? []
|
|
98
|
-
: [
|
|
99
|
-
{
|
|
100
|
-
name: category.name,
|
|
101
|
-
tokens: category.tokens,
|
|
102
|
-
kind: category.kind,
|
|
103
|
-
},
|
|
104
|
-
]),
|
|
105
|
-
sections: sections
|
|
106
|
-
.map((section) => ({
|
|
107
|
-
title: section.title,
|
|
108
|
-
items: section.items.filter((item) => item.tokens > 0),
|
|
109
|
-
}))
|
|
110
|
-
.filter((section) => section.items.length > 0),
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
function toBlocks(content) {
|
|
114
|
-
if (typeof content === 'string')
|
|
115
|
-
return [{ type: 'text', text: content }];
|
|
116
|
-
if (!Array.isArray(content))
|
|
117
|
-
return [];
|
|
118
|
-
return content.flatMap((block) => {
|
|
119
|
-
if (!isRecord(block))
|
|
120
|
-
return [];
|
|
121
|
-
switch (block.type) {
|
|
122
|
-
case 'text':
|
|
123
|
-
return typeof block.text === 'string' &&
|
|
124
|
-
!block.text.startsWith('<system-reminder>') &&
|
|
125
|
-
!block.text.startsWith('<local-command-caveat>')
|
|
126
|
-
? [{ type: 'text', text: block.text }]
|
|
127
|
-
: [];
|
|
128
|
-
case 'thinking':
|
|
129
|
-
return typeof block.thinking === 'string' && block.thinking !== ''
|
|
130
|
-
? [{ type: 'thinking', thinking: block.thinking }]
|
|
131
|
-
: [];
|
|
132
|
-
case 'image': {
|
|
133
|
-
const source = block.source;
|
|
134
|
-
return isRecord(source) &&
|
|
135
|
-
source.type === 'base64' &&
|
|
136
|
-
typeof source.media_type === 'string' &&
|
|
137
|
-
typeof source.data === 'string'
|
|
138
|
-
? [{ type: 'image', mediaType: source.media_type, data: source.data }]
|
|
139
|
-
: [];
|
|
140
|
-
}
|
|
141
|
-
case 'tool_use':
|
|
142
|
-
return typeof block.id === 'string' &&
|
|
143
|
-
typeof block.name === 'string' &&
|
|
144
|
-
isRecord(block.input)
|
|
145
|
-
? [
|
|
146
|
-
{
|
|
147
|
-
type: 'tool_use',
|
|
148
|
-
id: block.id,
|
|
149
|
-
name: block.name,
|
|
150
|
-
input: block.input,
|
|
151
|
-
},
|
|
152
|
-
]
|
|
153
|
-
: [];
|
|
154
|
-
case 'tool_result':
|
|
155
|
-
return typeof block.tool_use_id === 'string'
|
|
156
|
-
? [
|
|
157
|
-
{
|
|
158
|
-
type: 'tool_result',
|
|
159
|
-
toolUseId: block.tool_use_id,
|
|
160
|
-
content: toBlocks(block.content),
|
|
161
|
-
isError: block.is_error === true,
|
|
162
|
-
},
|
|
163
|
-
]
|
|
164
|
-
: [];
|
|
165
|
-
default:
|
|
166
|
-
return [];
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
function isRecord(value) {
|
|
171
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
172
|
-
}
|
package/apps/server/dist/db.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { mkdirSync } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { DatabaseSync } from 'node:sqlite';
|
|
4
|
-
import { home } from './storage.js';
|
|
5
|
-
mkdirSync(home, { recursive: true });
|
|
6
|
-
export const db = new DatabaseSync(path.join(home, 'offsite.db'));
|
|
7
|
-
db.exec(`
|
|
8
|
-
CREATE TABLE IF NOT EXISTS projects (
|
|
9
|
-
path TEXT PRIMARY KEY,
|
|
10
|
-
name TEXT NOT NULL,
|
|
11
|
-
logo TEXT
|
|
12
|
-
)
|
|
13
|
-
`);
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { mkdirSync, readdirSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { git } from './git.js';
|
|
5
|
-
import { home } from './storage.js';
|
|
6
|
-
const FILE_LIMIT = 20_000;
|
|
7
|
-
const skipped = new Set(['node_modules', '.git', 'dist', 'target', '.next']);
|
|
8
|
-
export async function listFiles(cwd) {
|
|
9
|
-
try {
|
|
10
|
-
const { stdout } = await git(cwd, [
|
|
11
|
-
'ls-files',
|
|
12
|
-
'--cached',
|
|
13
|
-
'--others',
|
|
14
|
-
'--exclude-standard',
|
|
15
|
-
]);
|
|
16
|
-
return stdout.split('\n').filter(Boolean).slice(0, FILE_LIMIT);
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
return walk(cwd);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
export function saveUpload(name, data) {
|
|
23
|
-
const dir = path.join(home, 'uploads', randomUUID());
|
|
24
|
-
mkdirSync(dir, { recursive: true });
|
|
25
|
-
const file = path.join(dir, path.basename(name));
|
|
26
|
-
writeFileSync(file, data);
|
|
27
|
-
return file;
|
|
28
|
-
}
|
|
29
|
-
function walk(root) {
|
|
30
|
-
const files = [];
|
|
31
|
-
const pending = [''];
|
|
32
|
-
while (pending.length > 0 && files.length < FILE_LIMIT) {
|
|
33
|
-
const dir = pending.pop() ?? '';
|
|
34
|
-
let entries;
|
|
35
|
-
try {
|
|
36
|
-
entries = readdirSync(path.join(root, dir), { withFileTypes: true });
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
for (const entry of entries) {
|
|
42
|
-
const relative = dir === '' ? entry.name : `${dir}/${entry.name}`;
|
|
43
|
-
if (entry.isDirectory() && !skipped.has(entry.name))
|
|
44
|
-
pending.push(relative);
|
|
45
|
-
if (entry.isFile())
|
|
46
|
-
files.push(relative);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return files;
|
|
50
|
-
}
|
package/apps/server/dist/git.js
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import { readFileSync, statSync } from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { promisify } from 'node:util';
|
|
5
|
-
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
6
|
-
import { claudeOptions } from './claude.js';
|
|
7
|
-
const DIFF_LIMIT = 80_000;
|
|
8
|
-
const UNTRACKED_LIMIT = 100;
|
|
9
|
-
const NEW_FILE_LIMIT = 256 * 1024;
|
|
10
|
-
export async function gitStatus(cwd) {
|
|
11
|
-
let output;
|
|
12
|
-
try {
|
|
13
|
-
output = await git(cwd, ['status', '--porcelain=v2', '--branch']);
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
const status = {
|
|
19
|
-
branch: null,
|
|
20
|
-
upstream: null,
|
|
21
|
-
ahead: 0,
|
|
22
|
-
behind: 0,
|
|
23
|
-
changed: 0,
|
|
24
|
-
};
|
|
25
|
-
for (const line of output.stdout.split('\n').filter(Boolean)) {
|
|
26
|
-
const [marker, key, ...rest] = line.split(' ');
|
|
27
|
-
if (marker !== '#')
|
|
28
|
-
status.changed++;
|
|
29
|
-
else if (key === 'branch.head')
|
|
30
|
-
status.branch = rest[0] === '(detached)' ? null : rest[0];
|
|
31
|
-
else if (key === 'branch.upstream')
|
|
32
|
-
status.upstream = rest[0];
|
|
33
|
-
else if (key === 'branch.ab') {
|
|
34
|
-
status.ahead = Number(rest[0]);
|
|
35
|
-
status.behind = -Number(rest[1]);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return status;
|
|
39
|
-
}
|
|
40
|
-
export async function commit(cwd, message) {
|
|
41
|
-
if (message.trim() === '')
|
|
42
|
-
throw new Error('Write a commit message first');
|
|
43
|
-
await git(cwd, ['add', '--all']);
|
|
44
|
-
await git(cwd, ['commit', '--message', message]);
|
|
45
|
-
return gitStatus(cwd);
|
|
46
|
-
}
|
|
47
|
-
export async function push(cwd) {
|
|
48
|
-
const status = await gitStatus(cwd);
|
|
49
|
-
if (status === null)
|
|
50
|
-
throw new Error(`${cwd} is not a git repository`);
|
|
51
|
-
if (status.upstream === null)
|
|
52
|
-
await git(cwd, ['push', '--set-upstream', 'origin', 'HEAD']);
|
|
53
|
-
else
|
|
54
|
-
await git(cwd, ['push']);
|
|
55
|
-
return gitStatus(cwd);
|
|
56
|
-
}
|
|
57
|
-
export async function createPullRequest(cwd) {
|
|
58
|
-
const { stdout } = await promisify(execFile)('gh', ['pr', 'create', '--fill'], { cwd });
|
|
59
|
-
const url = stdout.trim().split('\n').at(-1);
|
|
60
|
-
if (url === undefined)
|
|
61
|
-
throw new Error('gh did not return a pull request URL');
|
|
62
|
-
return { url };
|
|
63
|
-
}
|
|
64
|
-
export async function suggestCommitMessage(accountId, cwd) {
|
|
65
|
-
const diff = (await workingDiff(cwd)).slice(0, DIFF_LIMIT);
|
|
66
|
-
if (diff === '')
|
|
67
|
-
throw new Error('There is nothing to commit');
|
|
68
|
-
const conversation = query({
|
|
69
|
-
prompt: `Write a git commit message for this diff. Reply with only the message: a short summary line, optionally followed by a blank line and a brief body.\n\n${diff}`,
|
|
70
|
-
options: {
|
|
71
|
-
...claudeOptions(accountId, cwd),
|
|
72
|
-
systemPrompt: 'You write concise git commit messages.',
|
|
73
|
-
tools: [],
|
|
74
|
-
model: 'haiku',
|
|
75
|
-
maxTurns: 1,
|
|
76
|
-
persistSession: false,
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
for await (const message of conversation) {
|
|
80
|
-
if (message.type !== 'result')
|
|
81
|
-
continue;
|
|
82
|
-
if (message.subtype !== 'success')
|
|
83
|
-
throw new Error(message.errors.join('\n'));
|
|
84
|
-
if (message.is_error)
|
|
85
|
-
throw new Error(message.result);
|
|
86
|
-
return { message: message.result.trim() };
|
|
87
|
-
}
|
|
88
|
-
throw new Error('Claude did not reply');
|
|
89
|
-
}
|
|
90
|
-
export async function workingDiff(cwd) {
|
|
91
|
-
try {
|
|
92
|
-
await git(cwd, ['rev-parse', '--is-inside-work-tree']);
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
throw new Error(`${cwd} is not a git repository`);
|
|
96
|
-
}
|
|
97
|
-
const tracked = await git(cwd, [
|
|
98
|
-
'diff',
|
|
99
|
-
'HEAD',
|
|
100
|
-
'--no-color',
|
|
101
|
-
'--no-ext-diff',
|
|
102
|
-
]);
|
|
103
|
-
const untracked = await git(cwd, [
|
|
104
|
-
'ls-files',
|
|
105
|
-
'--others',
|
|
106
|
-
'--exclude-standard',
|
|
107
|
-
]);
|
|
108
|
-
const added = untracked.stdout
|
|
109
|
-
.split('\n')
|
|
110
|
-
.filter(Boolean)
|
|
111
|
-
.slice(0, UNTRACKED_LIMIT)
|
|
112
|
-
.map((file) => newFilePatch(cwd, file));
|
|
113
|
-
return [tracked.stdout, ...added].join('');
|
|
114
|
-
}
|
|
115
|
-
export function git(cwd, args) {
|
|
116
|
-
return promisify(execFile)('git', args, {
|
|
117
|
-
cwd,
|
|
118
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
function newFilePatch(cwd, file) {
|
|
122
|
-
const header = `diff --git a/${file} b/${file}\nnew file\n--- /dev/null\n+++ b/${file}\n`;
|
|
123
|
-
const full = path.join(cwd, file);
|
|
124
|
-
if (statSync(full).size > NEW_FILE_LIMIT)
|
|
125
|
-
return `${header}Binary or large file\n`;
|
|
126
|
-
const content = readFileSync(full, 'utf8');
|
|
127
|
-
if (content.includes('\0'))
|
|
128
|
-
return `${header}Binary or large file\n`;
|
|
129
|
-
const lines = content.split('\n');
|
|
130
|
-
if (lines.at(-1) === '')
|
|
131
|
-
lines.pop();
|
|
132
|
-
return `${header}@@ -0,0 +1,${lines.length} @@\n${lines.map((line) => `+${line}\n`).join('')}`;
|
|
133
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { getSessionMessages, getSubagentMessages, } from '@anthropic-ai/claude-agent-sdk';
|
|
4
|
-
import { readConfig } from './config.js';
|
|
5
|
-
import { contextTokens, toMessage } from './content.js';
|
|
6
|
-
import { runs } from './run.js';
|
|
7
|
-
async function sessionEntries(sessionId) {
|
|
8
|
-
const projectsDir = path.join(readConfig().claudeBaseDir, 'projects');
|
|
9
|
-
for (const dir of await readdir(projectsDir)) {
|
|
10
|
-
let text;
|
|
11
|
-
try {
|
|
12
|
-
text = await readFile(path.join(projectsDir, dir, `${sessionId}.jsonl`), 'utf8');
|
|
13
|
-
}
|
|
14
|
-
catch (error) {
|
|
15
|
-
if (error.code === 'ENOENT')
|
|
16
|
-
continue;
|
|
17
|
-
throw error;
|
|
18
|
-
}
|
|
19
|
-
return text.split('\n').flatMap((line) => {
|
|
20
|
-
const entry = parseLine(line);
|
|
21
|
-
return entry !== null && typeof entry.type === 'string'
|
|
22
|
-
? [{ ...entry, type: entry.type }]
|
|
23
|
-
: [];
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
return [];
|
|
27
|
-
}
|
|
28
|
-
function messageTimes(entries) {
|
|
29
|
-
const times = new Map();
|
|
30
|
-
for (const entry of entries)
|
|
31
|
-
if (typeof entry.uuid === 'string' && typeof entry.timestamp === 'string')
|
|
32
|
-
times.set(entry.uuid, Date.parse(entry.timestamp));
|
|
33
|
-
return times;
|
|
34
|
-
}
|
|
35
|
-
function compactedBefore(entries, uuid) {
|
|
36
|
-
if (uuid === undefined)
|
|
37
|
-
return null;
|
|
38
|
-
const start = entries.findIndex((entry) => entry.uuid === uuid);
|
|
39
|
-
const index = entries.findLastIndex((entry, position) => position < start &&
|
|
40
|
-
entry.type === 'system' &&
|
|
41
|
-
entry.subtype === 'compact_boundary');
|
|
42
|
-
return index < 0 ? null : index;
|
|
43
|
-
}
|
|
44
|
-
function parseLine(line) {
|
|
45
|
-
try {
|
|
46
|
-
return JSON.parse(line);
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
function mainMessages(disk, times) {
|
|
53
|
-
return disk.flatMap((item) => item.type === 'system' || item.parent_tool_use_id !== null
|
|
54
|
-
? []
|
|
55
|
-
: [toMessage(item.type, item, times.get(item.uuid) ?? null)]);
|
|
56
|
-
}
|
|
57
|
-
export async function history(sessionId, cwd) {
|
|
58
|
-
const run = runs.get(sessionId);
|
|
59
|
-
const [disk, entries] = await Promise.all([
|
|
60
|
-
getSessionMessages(sessionId, { dir: cwd }),
|
|
61
|
-
sessionEntries(sessionId),
|
|
62
|
-
]);
|
|
63
|
-
const resumeAt = run?.resumeAt ?? null;
|
|
64
|
-
const kept = resumeAt === null
|
|
65
|
-
? disk
|
|
66
|
-
: disk.slice(0, disk.findIndex((item) => item.uuid === resumeAt) + 1);
|
|
67
|
-
const messages = mainMessages(kept, messageTimes(entries));
|
|
68
|
-
messages.push(...(await subagentMessages(sessionId, cwd, messages)));
|
|
69
|
-
const seen = new Set(messages.map((item) => item.uuid));
|
|
70
|
-
const last = kept.findLast((item) => item.type === 'assistant' && item.parent_tool_use_id === null);
|
|
71
|
-
const tokens = last === undefined ? null : contextTokens(last.message);
|
|
72
|
-
return {
|
|
73
|
-
type: 'history',
|
|
74
|
-
sessionId,
|
|
75
|
-
messages: [
|
|
76
|
-
...messages,
|
|
77
|
-
...(run?.messages.filter((item) => !seen.has(item.uuid)) ?? []),
|
|
78
|
-
],
|
|
79
|
-
requests: [...(run?.pending.values() ?? [])].map((item) => item.request),
|
|
80
|
-
queue: run?.queue ?? [],
|
|
81
|
-
settings: run?.settings ?? null,
|
|
82
|
-
context: run?.context ??
|
|
83
|
-
(tokens === null
|
|
84
|
-
? null
|
|
85
|
-
: { tokens, window: null, model: null, categories: [], sections: [] }),
|
|
86
|
-
before: compactedBefore(entries, disk[0]?.uuid),
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
export async function earlier(sessionId, cwd, before) {
|
|
90
|
-
const entries = await sessionEntries(sessionId);
|
|
91
|
-
const kept = entries.slice(0, before);
|
|
92
|
-
const disk = await getSessionMessages(sessionId, {
|
|
93
|
-
sessionStore: { append: () => Promise.resolve(), load: async () => kept },
|
|
94
|
-
});
|
|
95
|
-
const messages = mainMessages(disk, messageTimes(entries));
|
|
96
|
-
messages.push(...(await subagentMessages(sessionId, cwd, messages)));
|
|
97
|
-
messages.push({
|
|
98
|
-
type: 'notice',
|
|
99
|
-
uuid: `compacted:${before}`,
|
|
100
|
-
time: null,
|
|
101
|
-
text: 'Conversation compacted',
|
|
102
|
-
isError: false,
|
|
103
|
-
});
|
|
104
|
-
return { messages, before: compactedBefore(entries, disk[0]?.uuid) };
|
|
105
|
-
}
|
|
106
|
-
async function subagentMessages(sessionId, cwd, messages) {
|
|
107
|
-
const agents = new Set(messages.flatMap((message) => message.type === 'assistant'
|
|
108
|
-
? message.content.flatMap((block) => block.type === 'tool_use' &&
|
|
109
|
-
(block.name === 'Agent' || block.name === 'Task')
|
|
110
|
-
? [block.id]
|
|
111
|
-
: [])
|
|
112
|
-
: []));
|
|
113
|
-
const launches = messages.flatMap((message) => message.type === 'user'
|
|
114
|
-
? message.content.flatMap((block) => {
|
|
115
|
-
if (block.type !== 'tool_result' || !agents.has(block.toolUseId))
|
|
116
|
-
return [];
|
|
117
|
-
const agentId = /agentId: (\w+)/.exec(block.content
|
|
118
|
-
.map((item) => (item.type === 'text' ? item.text : ''))
|
|
119
|
-
.join('\n'))?.[1];
|
|
120
|
-
return agentId === undefined
|
|
121
|
-
? []
|
|
122
|
-
: [{ agentId, parent: block.toolUseId }];
|
|
123
|
-
})
|
|
124
|
-
: []);
|
|
125
|
-
const loaded = await Promise.all(launches.map(async (launch) => {
|
|
126
|
-
const disk = await getSubagentMessages(sessionId, launch.agentId, {
|
|
127
|
-
dir: cwd,
|
|
128
|
-
});
|
|
129
|
-
const items = disk.flatMap((item) => item.type === 'system'
|
|
130
|
-
? []
|
|
131
|
-
: [
|
|
132
|
-
toMessage(item.type, { ...item, parent_tool_use_id: launch.parent }, null),
|
|
133
|
-
]);
|
|
134
|
-
return [...items, ...(await subagentMessages(sessionId, cwd, items))];
|
|
135
|
-
}));
|
|
136
|
-
return loaded.flat();
|
|
137
|
-
}
|