@phenx-inc/ctlsurf 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/ctlsurf-worker.js +173 -0
- package/electron-vite.config.ts +34 -0
- package/out/headless/index.mjs +1364 -0
- package/out/headless/index.mjs.map +7 -0
- package/out/main/index.js +1131 -0
- package/out/preload/index.js +67 -0
- package/out/renderer/assets/abap-D5KwWAsZ.js +1399 -0
- package/out/renderer/assets/apex-DVGUZ64i.js +331 -0
- package/out/renderer/assets/azcli-BEAhqcuE.js +69 -0
- package/out/renderer/assets/bat-Bqkp9Cfu.js +101 -0
- package/out/renderer/assets/bicep-DIlfshcM.js +110 -0
- package/out/renderer/assets/cameligo-CLaaYNMV.js +175 -0
- package/out/renderer/assets/clojure-fcgFaMHx.js +762 -0
- package/out/renderer/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/out/renderer/assets/coffee-CzJ5oEdj.js +233 -0
- package/out/renderer/assets/cpp-CcN6f0ik.js +390 -0
- package/out/renderer/assets/csharp-BJeIuvde.js +327 -0
- package/out/renderer/assets/csp-D_3BK2Wp.js +54 -0
- package/out/renderer/assets/css-i3rI3_64.js +186 -0
- package/out/renderer/assets/css.worker-umuuUiIb.js +53567 -0
- package/out/renderer/assets/cssMode-DL0XItGB.js +208 -0
- package/out/renderer/assets/cypher-D0--_GAN.js +264 -0
- package/out/renderer/assets/dart-vLMHv35g.js +282 -0
- package/out/renderer/assets/dockerfile--oxj0cAH.js +131 -0
- package/out/renderer/assets/ecl-CeuUgzaZ.js +457 -0
- package/out/renderer/assets/editor.worker-CNgWLVu7.js +13695 -0
- package/out/renderer/assets/elixir-eLfY1jWH.js +570 -0
- package/out/renderer/assets/flow9-ZSTChSMd.js +143 -0
- package/out/renderer/assets/freemarker2-CrOEuDcF.js +995 -0
- package/out/renderer/assets/fsharp-D2uoxuLH.js +218 -0
- package/out/renderer/assets/go-brnMpFrj.js +219 -0
- package/out/renderer/assets/graphql-BeiGgjIU.js +152 -0
- package/out/renderer/assets/handlebars-D4QYaBof.js +414 -0
- package/out/renderer/assets/hcl-CrX1Es2W.js +184 -0
- package/out/renderer/assets/html-B2Dqk2ai.js +303 -0
- package/out/renderer/assets/html.worker-BT47iy49.js +29777 -0
- package/out/renderer/assets/htmlMode-CdZ0Prhd.js +224 -0
- package/out/renderer/assets/index-CJ6RsQWP.css +8108 -0
- package/out/renderer/assets/index-pZmE1QXB.js +211777 -0
- package/out/renderer/assets/ini-BcQysCTb.js +72 -0
- package/out/renderer/assets/java-Dt3iMn2o.js +233 -0
- package/out/renderer/assets/javascript-CK8zNQXj.js +72 -0
- package/out/renderer/assets/json.worker-D4JVmXIe.js +21424 -0
- package/out/renderer/assets/jsonMode-Cewaellc.js +931 -0
- package/out/renderer/assets/julia-Cm3ItYL_.js +512 -0
- package/out/renderer/assets/kotlin-Ddo1SjA5.js +253 -0
- package/out/renderer/assets/less-B7Qaxw-O.js +162 -0
- package/out/renderer/assets/lexon-C1U0m2n9.js +158 -0
- package/out/renderer/assets/liquid-Bd3GPNs2.js +235 -0
- package/out/renderer/assets/lspLanguageFeatures-DSDH7BnA.js +1841 -0
- package/out/renderer/assets/lua-hNsuGJkO.js +163 -0
- package/out/renderer/assets/m3-6ko6q9-_.js +211 -0
- package/out/renderer/assets/markdown-B0YTnTxW.js +230 -0
- package/out/renderer/assets/mdx-CCPVCrXC.js +159 -0
- package/out/renderer/assets/mips-CJm71dS3.js +199 -0
- package/out/renderer/assets/msdax-BBeIktCY.js +376 -0
- package/out/renderer/assets/mysql-BWiizXSn.js +879 -0
- package/out/renderer/assets/objective-c-B1L1C5EC.js +184 -0
- package/out/renderer/assets/pascal-DMQyD4Xk.js +252 -0
- package/out/renderer/assets/pascaligo-VA_LQ1oU.js +165 -0
- package/out/renderer/assets/perl-DC0Z0tlO.js +627 -0
- package/out/renderer/assets/pgsql-DaSGFTLp.js +852 -0
- package/out/renderer/assets/php-Bkx1qpkQ.js +501 -0
- package/out/renderer/assets/pla-DEV89yYj.js +138 -0
- package/out/renderer/assets/postiats-CVVurEnu.js +908 -0
- package/out/renderer/assets/powerquery-BQ_t1ZiQ.js +891 -0
- package/out/renderer/assets/powershell-BXiKvz7Z.js +240 -0
- package/out/renderer/assets/protobuf-CndvAUGu.js +421 -0
- package/out/renderer/assets/pug-BxCXwerb.js +403 -0
- package/out/renderer/assets/python-34jOtlcC.js +295 -0
- package/out/renderer/assets/qsharp-BWK6YLKm.js +302 -0
- package/out/renderer/assets/r-CtqYUQ6l.js +244 -0
- package/out/renderer/assets/razor-DXRw694z.js +545 -0
- package/out/renderer/assets/redis-O7gSt3oh.js +303 -0
- package/out/renderer/assets/redshift-CvYMMYZY.js +810 -0
- package/out/renderer/assets/restructuredtext-B-KQCVu_.js +175 -0
- package/out/renderer/assets/ruby-DCd4DmAr.js +512 -0
- package/out/renderer/assets/rust-B1c0VCeq.js +344 -0
- package/out/renderer/assets/sb-Chfc_wZF.js +116 -0
- package/out/renderer/assets/scala-DbVzH-3O.js +371 -0
- package/out/renderer/assets/scheme-D7PxodDG.js +109 -0
- package/out/renderer/assets/scss-B42qMyAu.js +261 -0
- package/out/renderer/assets/shell-vZEubQ82.js +222 -0
- package/out/renderer/assets/solidity-yHOxYChb.js +1368 -0
- package/out/renderer/assets/sophia-D7pU0Y1d.js +200 -0
- package/out/renderer/assets/sparql-DxuVdnRl.js +202 -0
- package/out/renderer/assets/sql-BAGepFCR.js +854 -0
- package/out/renderer/assets/st-C-b0Dh53.js +417 -0
- package/out/renderer/assets/swift-BmOZGynf.js +313 -0
- package/out/renderer/assets/systemverilog-BOC0OOdC.js +577 -0
- package/out/renderer/assets/tcl-Bb4GCwBr.js +233 -0
- package/out/renderer/assets/ts.worker-C7hW3aY-.js +225330 -0
- package/out/renderer/assets/tsMode-CmND5_wB.js +1265 -0
- package/out/renderer/assets/twig-DvgEGWAV.js +393 -0
- package/out/renderer/assets/typescript-BNNI0Euv.js +337 -0
- package/out/renderer/assets/typespec-R77Ln7Jb.js +128 -0
- package/out/renderer/assets/vb-Bm6ESA0Q.js +373 -0
- package/out/renderer/assets/wgsl-_KPae5vw.js +454 -0
- package/out/renderer/assets/xml-CgdndrNB.js +89 -0
- package/out/renderer/assets/yaml-DNWPIf1s.js +200 -0
- package/out/renderer/index.html +13 -0
- package/package.json +67 -0
- package/resources/icon.icns +0 -0
- package/resources/icon.ico +0 -0
- package/resources/icon.png +0 -0
- package/src/main/agents.ts +46 -0
- package/src/main/bridge.ts +180 -0
- package/src/main/ctlsurfApi.ts +142 -0
- package/src/main/detectMode.ts +17 -0
- package/src/main/headless.ts +182 -0
- package/src/main/index.ts +300 -0
- package/src/main/orchestrator.ts +404 -0
- package/src/main/pty.ts +65 -0
- package/src/main/settingsDir.ts +17 -0
- package/src/main/tui.ts +366 -0
- package/src/main/workerWs.ts +312 -0
- package/src/preload/index.ts +114 -0
- package/src/renderer/App.tsx +275 -0
- package/src/renderer/components/CtlsurfPanel.tsx +49 -0
- package/src/renderer/components/EditorPanel.tsx +232 -0
- package/src/renderer/components/MultiSplitPane.tsx +251 -0
- package/src/renderer/components/PaneLayout.tsx +419 -0
- package/src/renderer/components/SettingsDialog.tsx +204 -0
- package/src/renderer/components/SplitPane.tsx +82 -0
- package/src/renderer/components/StatusBar.tsx +73 -0
- package/src/renderer/components/TerminalPanel.tsx +140 -0
- package/src/renderer/index.html +12 -0
- package/src/renderer/main.tsx +10 -0
- package/src/renderer/styles.css +722 -0
- package/tsconfig.json +8 -0
- package/tsconfig.main.json +15 -0
- package/tsconfig.preload.json +14 -0
- package/tsconfig.renderer.json +15 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { l as languages } from "./index-pZmE1QXB.js";
|
|
2
|
+
const conf = {
|
|
3
|
+
comments: {
|
|
4
|
+
lineComment: "#"
|
|
5
|
+
},
|
|
6
|
+
brackets: [
|
|
7
|
+
["{", "}"],
|
|
8
|
+
["[", "]"],
|
|
9
|
+
["(", ")"]
|
|
10
|
+
],
|
|
11
|
+
autoClosingPairs: [
|
|
12
|
+
{ open: "{", close: "}" },
|
|
13
|
+
{ open: "[", close: "]" },
|
|
14
|
+
{ open: "(", close: ")" },
|
|
15
|
+
{ open: '"', close: '"' },
|
|
16
|
+
{ open: "'", close: "'" }
|
|
17
|
+
],
|
|
18
|
+
surroundingPairs: [
|
|
19
|
+
{ open: "{", close: "}" },
|
|
20
|
+
{ open: "[", close: "]" },
|
|
21
|
+
{ open: "(", close: ")" },
|
|
22
|
+
{ open: '"', close: '"' },
|
|
23
|
+
{ open: "'", close: "'" }
|
|
24
|
+
],
|
|
25
|
+
folding: {
|
|
26
|
+
offSide: true
|
|
27
|
+
},
|
|
28
|
+
onEnterRules: [
|
|
29
|
+
{
|
|
30
|
+
beforeText: /:\s*$/,
|
|
31
|
+
action: {
|
|
32
|
+
indentAction: languages.IndentAction.Indent
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
const language = {
|
|
38
|
+
tokenPostfix: ".yaml",
|
|
39
|
+
brackets: [
|
|
40
|
+
{ token: "delimiter.bracket", open: "{", close: "}" },
|
|
41
|
+
{ token: "delimiter.square", open: "[", close: "]" }
|
|
42
|
+
],
|
|
43
|
+
keywords: ["true", "True", "TRUE", "false", "False", "FALSE", "null", "Null", "Null", "~"],
|
|
44
|
+
numberInteger: /(?:0|[+-]?[0-9]+)/,
|
|
45
|
+
numberFloat: /(?:0|[+-]?[0-9]+)(?:\.[0-9]+)?(?:e[-+][1-9][0-9]*)?/,
|
|
46
|
+
numberOctal: /0o[0-7]+/,
|
|
47
|
+
numberHex: /0x[0-9a-fA-F]+/,
|
|
48
|
+
numberInfinity: /[+-]?\.(?:inf|Inf|INF)/,
|
|
49
|
+
numberNaN: /\.(?:nan|Nan|NAN)/,
|
|
50
|
+
numberDate: /\d{4}-\d\d-\d\d([Tt ]\d\d:\d\d:\d\d(\.\d+)?(( ?[+-]\d\d?(:\d\d)?)|Z)?)?/,
|
|
51
|
+
escapes: /\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/,
|
|
52
|
+
tokenizer: {
|
|
53
|
+
root: [
|
|
54
|
+
{ include: "@whitespace" },
|
|
55
|
+
{ include: "@comment" },
|
|
56
|
+
// Directive
|
|
57
|
+
[/%[^ ]+.*$/, "meta.directive"],
|
|
58
|
+
// Document Markers
|
|
59
|
+
[/---/, "operators.directivesEnd"],
|
|
60
|
+
[/\.{3}/, "operators.documentEnd"],
|
|
61
|
+
// Block Structure Indicators
|
|
62
|
+
[/[-?:](?= )/, "operators"],
|
|
63
|
+
{ include: "@anchor" },
|
|
64
|
+
{ include: "@tagHandle" },
|
|
65
|
+
{ include: "@flowCollections" },
|
|
66
|
+
{ include: "@blockStyle" },
|
|
67
|
+
// Numbers
|
|
68
|
+
[/@numberInteger(?![ \t]*\S+)/, "number"],
|
|
69
|
+
[/@numberFloat(?![ \t]*\S+)/, "number.float"],
|
|
70
|
+
[/@numberOctal(?![ \t]*\S+)/, "number.octal"],
|
|
71
|
+
[/@numberHex(?![ \t]*\S+)/, "number.hex"],
|
|
72
|
+
[/@numberInfinity(?![ \t]*\S+)/, "number.infinity"],
|
|
73
|
+
[/@numberNaN(?![ \t]*\S+)/, "number.nan"],
|
|
74
|
+
[/@numberDate(?![ \t]*\S+)/, "number.date"],
|
|
75
|
+
// Key:Value pair
|
|
76
|
+
[/(".*?"|'.*?'|[^#'"]*?)([ \t]*)(:)( |$)/, ["type", "white", "operators", "white"]],
|
|
77
|
+
{ include: "@flowScalars" },
|
|
78
|
+
// String nodes
|
|
79
|
+
[
|
|
80
|
+
/.+?(?=(\s+#|$))/,
|
|
81
|
+
{
|
|
82
|
+
cases: {
|
|
83
|
+
"@keywords": "keyword",
|
|
84
|
+
"@default": "string"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
],
|
|
89
|
+
// Flow Collection: Flow Mapping
|
|
90
|
+
object: [
|
|
91
|
+
{ include: "@whitespace" },
|
|
92
|
+
{ include: "@comment" },
|
|
93
|
+
// Flow Mapping termination
|
|
94
|
+
[/\}/, "@brackets", "@pop"],
|
|
95
|
+
// Flow Mapping delimiter
|
|
96
|
+
[/,/, "delimiter.comma"],
|
|
97
|
+
// Flow Mapping Key:Value delimiter
|
|
98
|
+
[/:(?= )/, "operators"],
|
|
99
|
+
// Flow Mapping Key:Value key
|
|
100
|
+
[/(?:".*?"|'.*?'|[^,\{\[]+?)(?=: )/, "type"],
|
|
101
|
+
// Start Flow Style
|
|
102
|
+
{ include: "@flowCollections" },
|
|
103
|
+
{ include: "@flowScalars" },
|
|
104
|
+
// Scalar Data types
|
|
105
|
+
{ include: "@tagHandle" },
|
|
106
|
+
{ include: "@anchor" },
|
|
107
|
+
{ include: "@flowNumber" },
|
|
108
|
+
// Other value (keyword or string)
|
|
109
|
+
[
|
|
110
|
+
/[^\},]+/,
|
|
111
|
+
{
|
|
112
|
+
cases: {
|
|
113
|
+
"@keywords": "keyword",
|
|
114
|
+
"@default": "string"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
],
|
|
119
|
+
// Flow Collection: Flow Sequence
|
|
120
|
+
array: [
|
|
121
|
+
{ include: "@whitespace" },
|
|
122
|
+
{ include: "@comment" },
|
|
123
|
+
// Flow Sequence termination
|
|
124
|
+
[/\]/, "@brackets", "@pop"],
|
|
125
|
+
// Flow Sequence delimiter
|
|
126
|
+
[/,/, "delimiter.comma"],
|
|
127
|
+
// Start Flow Style
|
|
128
|
+
{ include: "@flowCollections" },
|
|
129
|
+
{ include: "@flowScalars" },
|
|
130
|
+
// Scalar Data types
|
|
131
|
+
{ include: "@tagHandle" },
|
|
132
|
+
{ include: "@anchor" },
|
|
133
|
+
{ include: "@flowNumber" },
|
|
134
|
+
// Other value (keyword or string)
|
|
135
|
+
[
|
|
136
|
+
/[^\],]+/,
|
|
137
|
+
{
|
|
138
|
+
cases: {
|
|
139
|
+
"@keywords": "keyword",
|
|
140
|
+
"@default": "string"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
],
|
|
145
|
+
// First line of a Block Style
|
|
146
|
+
multiString: [[/^( +).+$/, "string", "@multiStringContinued.$1"]],
|
|
147
|
+
// Further lines of a Block Style
|
|
148
|
+
// Workaround for indentation detection
|
|
149
|
+
multiStringContinued: [
|
|
150
|
+
[
|
|
151
|
+
/^( *).+$/,
|
|
152
|
+
{
|
|
153
|
+
cases: {
|
|
154
|
+
"$1==$S2": "string",
|
|
155
|
+
"@default": { token: "@rematch", next: "@popall" }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
],
|
|
160
|
+
whitespace: [[/[ \t\r\n]+/, "white"]],
|
|
161
|
+
// Only line comments
|
|
162
|
+
comment: [[/#.*$/, "comment"]],
|
|
163
|
+
// Start Flow Collections
|
|
164
|
+
flowCollections: [
|
|
165
|
+
[/\[/, "@brackets", "@array"],
|
|
166
|
+
[/\{/, "@brackets", "@object"]
|
|
167
|
+
],
|
|
168
|
+
// Start Flow Scalars (quoted strings)
|
|
169
|
+
flowScalars: [
|
|
170
|
+
[/"([^"\\]|\\.)*$/, "string.invalid"],
|
|
171
|
+
[/'([^'\\]|\\.)*$/, "string.invalid"],
|
|
172
|
+
[/'[^']*'/, "string"],
|
|
173
|
+
[/"/, "string", "@doubleQuotedString"]
|
|
174
|
+
],
|
|
175
|
+
doubleQuotedString: [
|
|
176
|
+
[/[^\\"]+/, "string"],
|
|
177
|
+
[/@escapes/, "string.escape"],
|
|
178
|
+
[/\\./, "string.escape.invalid"],
|
|
179
|
+
[/"/, "string", "@pop"]
|
|
180
|
+
],
|
|
181
|
+
// Start Block Scalar
|
|
182
|
+
blockStyle: [[/[>|][0-9]*[+-]?$/, "operators", "@multiString"]],
|
|
183
|
+
// Numbers in Flow Collections (terminate with ,]})
|
|
184
|
+
flowNumber: [
|
|
185
|
+
[/@numberInteger(?=[ \t]*[,\]\}])/, "number"],
|
|
186
|
+
[/@numberFloat(?=[ \t]*[,\]\}])/, "number.float"],
|
|
187
|
+
[/@numberOctal(?=[ \t]*[,\]\}])/, "number.octal"],
|
|
188
|
+
[/@numberHex(?=[ \t]*[,\]\}])/, "number.hex"],
|
|
189
|
+
[/@numberInfinity(?=[ \t]*[,\]\}])/, "number.infinity"],
|
|
190
|
+
[/@numberNaN(?=[ \t]*[,\]\}])/, "number.nan"],
|
|
191
|
+
[/@numberDate(?=[ \t]*[,\]\}])/, "number.date"]
|
|
192
|
+
],
|
|
193
|
+
tagHandle: [[/\![^ ]*/, "tag"]],
|
|
194
|
+
anchor: [[/[&*][^ ]+/, "namespace"]]
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
export {
|
|
198
|
+
conf,
|
|
199
|
+
language
|
|
200
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>ctlsurf-worker</title>
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-pZmE1QXB.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/index-CJ6RsQWP.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div id="root"></div>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@phenx-inc/ctlsurf",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Agent-agnostic terminal and desktop app for ctlsurf — run Claude Code, Codex, or any coding agent with live session logging and remote control",
|
|
5
|
+
"main": "out/main/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ctlsurf": "./bin/ctlsurf-worker.js",
|
|
8
|
+
"ctlsurf-worker": "./bin/ctlsurf-worker.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"out/",
|
|
13
|
+
"resources/",
|
|
14
|
+
"src/",
|
|
15
|
+
"package.json",
|
|
16
|
+
"electron-vite.config.ts",
|
|
17
|
+
"tsconfig*.json"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"dev": "electron-vite dev",
|
|
21
|
+
"dev:terminal": "esbuild src/main/headless.ts --bundle --platform=node --target=node18 --format=esm --outfile=out/headless/index.mjs --external:node-pty --sourcemap && node out/headless/index.mjs",
|
|
22
|
+
"build": "electron-vite build && npm run build:headless",
|
|
23
|
+
"build:headless": "esbuild src/main/headless.ts --bundle --platform=node --target=node18 --format=esm --outfile=out/headless/index.mjs --external:node-pty --sourcemap",
|
|
24
|
+
"prepublishOnly": "npm run build",
|
|
25
|
+
"preview": "electron-vite preview",
|
|
26
|
+
"package": "electron-builder",
|
|
27
|
+
"postinstall": "node-gyp rebuild --directory=node_modules/node-pty 2>/dev/null || true"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"ctlsurf",
|
|
31
|
+
"terminal",
|
|
32
|
+
"tui",
|
|
33
|
+
"coding-agent",
|
|
34
|
+
"claude-code",
|
|
35
|
+
"codex",
|
|
36
|
+
"electron",
|
|
37
|
+
"node-pty",
|
|
38
|
+
"xterm"
|
|
39
|
+
],
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@monaco-editor/react": "^4.7.0",
|
|
46
|
+
"@xterm/addon-fit": "^0.10.0",
|
|
47
|
+
"@xterm/addon-web-links": "^0.11.0",
|
|
48
|
+
"@xterm/xterm": "^5.5.0",
|
|
49
|
+
"electron-store": "^10.0.0",
|
|
50
|
+
"esbuild": "^0.27.4",
|
|
51
|
+
"monaco-editor": "^0.55.1",
|
|
52
|
+
"node-pty": "^1.0.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@electron/rebuild": "^4.0.3",
|
|
56
|
+
"@types/node": "^22.15.0",
|
|
57
|
+
"@types/react": "^19.1.0",
|
|
58
|
+
"@types/react-dom": "^19.1.0",
|
|
59
|
+
"@vitejs/plugin-react": "^4.5.2",
|
|
60
|
+
"electron": "^35.0.0",
|
|
61
|
+
"electron-builder": "^25.1.8",
|
|
62
|
+
"electron-vite": "^3.1.0",
|
|
63
|
+
"react": "^19.1.0",
|
|
64
|
+
"react-dom": "^19.1.0",
|
|
65
|
+
"typescript": "^5.8.3"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface AgentConfig {
|
|
2
|
+
id: string
|
|
3
|
+
name: string
|
|
4
|
+
command: string
|
|
5
|
+
args: string[]
|
|
6
|
+
description: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getShellCommand(): string {
|
|
10
|
+
if (process.platform === 'win32') return 'powershell.exe'
|
|
11
|
+
return process.env.SHELL || '/bin/zsh'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getBuiltinAgents(): AgentConfig[] {
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
id: 'shell',
|
|
18
|
+
name: 'Shell',
|
|
19
|
+
command: getShellCommand(),
|
|
20
|
+
args: ['-l'], // login shell to load PATH
|
|
21
|
+
description: 'Default system shell'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'claude',
|
|
25
|
+
name: 'Claude Code',
|
|
26
|
+
command: 'claude',
|
|
27
|
+
args: [],
|
|
28
|
+
description: 'Anthropic Claude Code CLI'
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'codex',
|
|
32
|
+
name: 'Codex CLI',
|
|
33
|
+
command: 'codex',
|
|
34
|
+
args: [],
|
|
35
|
+
description: 'OpenAI Codex CLI'
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getDefaultAgent(): AgentConfig {
|
|
41
|
+
return getBuiltinAgents()[0]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isCodingAgent(agent: AgentConfig): boolean {
|
|
45
|
+
return agent.id !== 'shell'
|
|
46
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { CtlsurfApi } from './ctlsurfApi'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Conversation Bridge
|
|
5
|
+
*
|
|
6
|
+
* Taps the pty output stream and logs chunks to a ctlsurf log block.
|
|
7
|
+
* Generic approach: buffers terminal output and flushes periodically.
|
|
8
|
+
*/
|
|
9
|
+
export class ConversationBridge {
|
|
10
|
+
private api: CtlsurfApi
|
|
11
|
+
private logBlockId: string | null = null
|
|
12
|
+
private pageId: string | null = null
|
|
13
|
+
private buffer: string = ''
|
|
14
|
+
private flushTimer: ReturnType<typeof setTimeout> | null = null
|
|
15
|
+
private flushIntervalMs: number = 3000 // flush every 3 seconds
|
|
16
|
+
private agentName: string = 'shell'
|
|
17
|
+
private sessionActive: boolean = false
|
|
18
|
+
private inputBuffer: string = ''
|
|
19
|
+
|
|
20
|
+
constructor(api: CtlsurfApi) {
|
|
21
|
+
this.api = api
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Start a new logging session.
|
|
26
|
+
* Creates a log block on the given dataspace page.
|
|
27
|
+
*/
|
|
28
|
+
async startSession(dataspacePageId: string, agentName: string, cwd: string): Promise<void> {
|
|
29
|
+
if (!this.api.getApiKey()) {
|
|
30
|
+
console.log('[bridge] No API key set, skipping session logging')
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.pageId = dataspacePageId
|
|
35
|
+
this.agentName = agentName
|
|
36
|
+
this.buffer = ''
|
|
37
|
+
this.inputBuffer = ''
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19)
|
|
41
|
+
const block = await this.api.createBlock(dataspacePageId, {
|
|
42
|
+
type: 'log',
|
|
43
|
+
title: `${agentName} — ${timestamp} — ${cwd}`,
|
|
44
|
+
props: {
|
|
45
|
+
entries: [],
|
|
46
|
+
max_entries: 1000
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
this.logBlockId = block.id
|
|
50
|
+
this.sessionActive = true
|
|
51
|
+
|
|
52
|
+
// Log session start
|
|
53
|
+
await this.api.appendLog(this.logBlockId, 'session_start', `Started ${agentName} session`, {
|
|
54
|
+
agent: agentName,
|
|
55
|
+
cwd,
|
|
56
|
+
timestamp
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
console.log(`[bridge] Session started, log block: ${this.logBlockId}`)
|
|
60
|
+
} catch (err: any) {
|
|
61
|
+
console.error(`[bridge] Failed to start session:`, err.message)
|
|
62
|
+
this.sessionActive = false
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Feed terminal output data into the bridge.
|
|
68
|
+
* Buffers and flushes periodically.
|
|
69
|
+
*/
|
|
70
|
+
feedOutput(data: string): void {
|
|
71
|
+
if (!this.sessionActive) return
|
|
72
|
+
|
|
73
|
+
this.buffer += data
|
|
74
|
+
|
|
75
|
+
// Reset flush timer
|
|
76
|
+
if (this.flushTimer) {
|
|
77
|
+
clearTimeout(this.flushTimer)
|
|
78
|
+
}
|
|
79
|
+
this.flushTimer = setTimeout(() => this.flush(), this.flushIntervalMs)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Feed user input data into the bridge.
|
|
84
|
+
*/
|
|
85
|
+
feedInput(data: string): void {
|
|
86
|
+
if (!this.sessionActive) return
|
|
87
|
+
this.inputBuffer += data
|
|
88
|
+
|
|
89
|
+
// Detect Enter key (newline) — flush the input as a user prompt
|
|
90
|
+
if (data.includes('\r') || data.includes('\n')) {
|
|
91
|
+
const input = this.inputBuffer.trim()
|
|
92
|
+
if (input.length > 0) {
|
|
93
|
+
this.logEntry('user_input', input)
|
|
94
|
+
}
|
|
95
|
+
this.inputBuffer = ''
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Flush buffered output to ctlsurf.
|
|
101
|
+
*/
|
|
102
|
+
private async flush(): Promise<void> {
|
|
103
|
+
if (!this.logBlockId || this.buffer.length === 0) return
|
|
104
|
+
|
|
105
|
+
const chunk = this.buffer
|
|
106
|
+
this.buffer = ''
|
|
107
|
+
|
|
108
|
+
// Strip ANSI escape codes for cleaner log entries
|
|
109
|
+
const cleaned = stripAnsi(chunk)
|
|
110
|
+
if (cleaned.trim().length === 0) return
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
await this.api.appendLog(this.logBlockId, 'terminal_output', cleaned)
|
|
114
|
+
} catch (err: any) {
|
|
115
|
+
console.error(`[bridge] Failed to append log:`, err.message)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Log a specific entry immediately.
|
|
121
|
+
*/
|
|
122
|
+
private async logEntry(action: string, message: string, data?: Record<string, unknown>): Promise<void> {
|
|
123
|
+
if (!this.logBlockId) return
|
|
124
|
+
try {
|
|
125
|
+
await this.api.appendLog(this.logBlockId, action, message, data)
|
|
126
|
+
} catch (err: any) {
|
|
127
|
+
console.error(`[bridge] Failed to log entry:`, err.message)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* End the current session.
|
|
133
|
+
*/
|
|
134
|
+
async endSession(exitCode?: number): Promise<void> {
|
|
135
|
+
if (!this.sessionActive || !this.logBlockId) return
|
|
136
|
+
|
|
137
|
+
// Flush remaining buffer
|
|
138
|
+
await this.flush()
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
await this.api.appendLog(this.logBlockId, 'session_end', `Session ended (exit code: ${exitCode ?? 'unknown'})`, {
|
|
142
|
+
agent: this.agentName,
|
|
143
|
+
exitCode
|
|
144
|
+
})
|
|
145
|
+
} catch (err: any) {
|
|
146
|
+
console.error(`[bridge] Failed to log session end:`, err.message)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (this.flushTimer) {
|
|
150
|
+
clearTimeout(this.flushTimer)
|
|
151
|
+
this.flushTimer = null
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.sessionActive = false
|
|
155
|
+
this.logBlockId = null
|
|
156
|
+
this.buffer = ''
|
|
157
|
+
this.inputBuffer = ''
|
|
158
|
+
console.log('[bridge] Session ended')
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Strip ANSI escape codes from terminal output.
|
|
164
|
+
*/
|
|
165
|
+
function stripAnsi(str: string): string {
|
|
166
|
+
return str
|
|
167
|
+
// CSI sequences (e.g. \x1b[0m, \x1b[?2004h, \x1b[1;32m)
|
|
168
|
+
.replace(/\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g, '')
|
|
169
|
+
// OSC sequences (e.g. \x1b]0;title\x07)
|
|
170
|
+
.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, '')
|
|
171
|
+
// Other escape sequences (charset, keypad mode, etc.)
|
|
172
|
+
.replace(/\x1b[^[\]](.|$)/g, '')
|
|
173
|
+
// Remaining single ESC
|
|
174
|
+
.replace(/\x1b/g, '')
|
|
175
|
+
// Carriage returns
|
|
176
|
+
.replace(/\r/g, '')
|
|
177
|
+
// Control characters except newline/tab
|
|
178
|
+
// eslint-disable-next-line no-control-regex
|
|
179
|
+
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '')
|
|
180
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
const CTLSURF_BASE_URL = 'https://app.ctlsurf.com/api'
|
|
2
|
+
|
|
3
|
+
export class CtlsurfApi {
|
|
4
|
+
private baseUrl: string
|
|
5
|
+
private apiKey: string | null = null
|
|
6
|
+
|
|
7
|
+
constructor(baseUrl?: string) {
|
|
8
|
+
this.baseUrl = baseUrl || CTLSURF_BASE_URL
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
setApiKey(key: string): void {
|
|
12
|
+
this.apiKey = key
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
setBaseUrl(url: string): void {
|
|
16
|
+
this.baseUrl = url.endsWith('/api') ? url : `${url}/api`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getApiKey(): string | null {
|
|
20
|
+
return this.apiKey
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private headers(): Record<string, string> {
|
|
24
|
+
const h: Record<string, string> = { 'Content-Type': 'application/json' }
|
|
25
|
+
if (this.apiKey) {
|
|
26
|
+
h['Authorization'] = `Bearer ${this.apiKey}`
|
|
27
|
+
}
|
|
28
|
+
return h
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private async request(method: string, path: string, body?: unknown): Promise<any> {
|
|
32
|
+
const url = `${this.baseUrl}${path}`
|
|
33
|
+
const opts: RequestInit = {
|
|
34
|
+
method,
|
|
35
|
+
headers: this.headers()
|
|
36
|
+
}
|
|
37
|
+
if (body) {
|
|
38
|
+
opts.body = JSON.stringify(body)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const res = await fetch(url, opts)
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
const text = await res.text()
|
|
44
|
+
throw new Error(`ctlsurf API ${method} ${path}: ${res.status} ${text}`)
|
|
45
|
+
}
|
|
46
|
+
return res.json()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ─── Pages ───────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
async createPage(params: {
|
|
52
|
+
title: string
|
|
53
|
+
type?: string
|
|
54
|
+
parent_id?: string
|
|
55
|
+
folder_id?: string
|
|
56
|
+
cwd?: string
|
|
57
|
+
tags?: string[]
|
|
58
|
+
}): Promise<any> {
|
|
59
|
+
return this.request('POST', '/pages', params)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async findPageByRootPath(rootPath: string): Promise<any> {
|
|
63
|
+
return this.request('POST', '/pages/find-by-root-path', { root_path: rootPath })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ─── Blocks ──────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
async createBlock(pageId: string, params: {
|
|
69
|
+
type: string
|
|
70
|
+
title?: string
|
|
71
|
+
props?: Record<string, unknown>
|
|
72
|
+
}): Promise<any> {
|
|
73
|
+
return this.request('POST', `/blocks/page/${pageId}`, params)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async getBlock(blockId: string): Promise<any> {
|
|
77
|
+
return this.request('GET', `/blocks/${blockId}`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async updateBlock(blockId: string, params: {
|
|
81
|
+
props?: Record<string, unknown>
|
|
82
|
+
title?: string
|
|
83
|
+
}): Promise<any> {
|
|
84
|
+
return this.request('PUT', `/blocks/${blockId}`, params)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ─── Folders ────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
async createFolder(params: { name: string; root_path: string }): Promise<any> {
|
|
90
|
+
return this.request('POST', '/folders', params)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ─── Workers ────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
async getAuthCode(): Promise<{ code: string }> {
|
|
96
|
+
return this.request('POST', '/workers/token-exchange')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async findFolderByPath(rootPath: string): Promise<any> {
|
|
100
|
+
return this.request('POST', '/folders/find-by-path', { root_path: rootPath })
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async getFolderPages(folderId: string): Promise<any[]> {
|
|
104
|
+
const folder = await this.request('GET', `/folders/${folderId}`)
|
|
105
|
+
return folder?.pages || []
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async findFolderByGitRemote(gitRemote: string): Promise<any> {
|
|
109
|
+
// Search folders by listing all and matching git_remote
|
|
110
|
+
const folders = await this.request('GET', '/folders')
|
|
111
|
+
return folders?.find((f: any) => f.git_remote === gitRemote || f.root_path === gitRemote) || null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── Log convenience ─────────────────────────────────
|
|
115
|
+
|
|
116
|
+
async appendLog(blockId: string, action: string, message: string, data?: Record<string, unknown>): Promise<any> {
|
|
117
|
+
// Read-modify-write: get current entries, append, put back
|
|
118
|
+
const block = await this.getBlock(blockId)
|
|
119
|
+
const props = block.props || {}
|
|
120
|
+
const entries = Array.isArray(props.entries) ? [...props.entries] : []
|
|
121
|
+
const maxEntries = props.max_entries || 1000
|
|
122
|
+
|
|
123
|
+
const entry: Record<string, unknown> = {
|
|
124
|
+
_id: `log_${entries.length}`,
|
|
125
|
+
_timestamp: new Date().toISOString(),
|
|
126
|
+
action,
|
|
127
|
+
message
|
|
128
|
+
}
|
|
129
|
+
if (data) {
|
|
130
|
+
entry.data = data
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
entries.push(entry)
|
|
134
|
+
|
|
135
|
+
// Trim oldest if over max
|
|
136
|
+
const trimmed = entries.length > maxEntries ? entries.slice(-maxEntries) : entries
|
|
137
|
+
|
|
138
|
+
return this.updateBlock(blockId, {
|
|
139
|
+
props: { ...props, entries: trimmed }
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function detectMode(argv: string[]): 'gui' | 'terminal' {
|
|
2
|
+
if (argv.includes('--terminal')) return 'terminal'
|
|
3
|
+
if (argv.includes('--desktop')) return 'gui'
|
|
4
|
+
|
|
5
|
+
// SSH session → terminal
|
|
6
|
+
if (process.env.SSH_CONNECTION || process.env.SSH_TTY) return 'terminal'
|
|
7
|
+
|
|
8
|
+
// Docker / container → terminal
|
|
9
|
+
if (process.env.container || process.env.DOCKER_CONTAINER) return 'terminal'
|
|
10
|
+
|
|
11
|
+
// Linux: no display → terminal
|
|
12
|
+
if (process.platform === 'linux') {
|
|
13
|
+
if (!process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) return 'terminal'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return 'gui'
|
|
17
|
+
}
|