aicodeman 1.8.2 → 1.9.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/README.md +80 -103
- package/README.zh-CN.md +80 -103
- package/dist/types/common.d.ts +25 -0
- package/dist/types/common.d.ts.map +1 -1
- package/dist/types/common.js +1 -0
- package/dist/types/common.js.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/app.adc992cd.js.gz +0 -0
- package/dist/web/public/constants.e7175b95.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/i18n.4dcf1b87.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +30 -19
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
- package/dist/web/public/{keyboard-accessory.3f91acef.js → keyboard-accessory.9936227d.js} +361 -2
- package/dist/web/public/keyboard-accessory.9936227d.js.br +0 -0
- package/dist/web/public/keyboard-accessory.9936227d.js.gz +0 -0
- package/dist/web/public/mobile-handlers.bc2b8c1d.js.gz +0 -0
- package/dist/web/public/mobile.40e2d64e.css +1 -0
- package/dist/web/public/mobile.40e2d64e.css.br +0 -0
- package/dist/web/public/mobile.40e2d64e.css.gz +0 -0
- package/dist/web/public/notification-manager.fca811fd.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.58582d4c.js → panels-ui.3910e1e2.js} +16 -16
- package/dist/web/public/panels-ui.3910e1e2.js.br +0 -0
- package/dist/web/public/panels-ui.3910e1e2.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/{session-ui.fd39c53c.js → session-ui.ab6dd9aa.js} +15 -15
- package/dist/web/public/session-ui.ab6dd9aa.js.br +0 -0
- package/dist/web/public/session-ui.ab6dd9aa.js.gz +0 -0
- package/dist/web/public/{settings-ui.478c8242.js → settings-ui.b8def032.js} +1 -1
- package/dist/web/public/settings-ui.b8def032.js.br +0 -0
- package/dist/web/public/settings-ui.b8def032.js.gz +0 -0
- package/dist/web/public/styles.42dd71e1.css +1 -0
- package/dist/web/public/styles.42dd71e1.css.br +0 -0
- package/dist/web/public/styles.42dd71e1.css.gz +0 -0
- package/dist/web/public/subagent-windows.69b60c35.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/terminal-ui.f3a51974.js +3 -0
- package/dist/web/public/terminal-ui.f3a51974.js.br +0 -0
- package/dist/web/public/terminal-ui.f3a51974.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
- package/dist/web/public/webview-tabs.js +43 -13
- package/dist/web/public/webview-tabs.js.br +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +313 -8
- package/dist/web/routes/file-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +173 -99
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +10 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +30 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/webview-proxy.d.ts +18 -1
- package/dist/web/webview-proxy.d.ts.map +1 -1
- package/dist/web/webview-proxy.js +129 -1
- package/dist/web/webview-proxy.js.map +1 -1
- package/package.json +17 -6
- package/dist/web/public/keyboard-accessory.3f91acef.js.br +0 -0
- package/dist/web/public/keyboard-accessory.3f91acef.js.gz +0 -0
- package/dist/web/public/mobile.2ebdbb03.css +0 -1
- package/dist/web/public/mobile.2ebdbb03.css.br +0 -0
- package/dist/web/public/mobile.2ebdbb03.css.gz +0 -0
- package/dist/web/public/panels-ui.58582d4c.js.br +0 -0
- package/dist/web/public/panels-ui.58582d4c.js.gz +0 -0
- package/dist/web/public/session-ui.fd39c53c.js.br +0 -0
- package/dist/web/public/session-ui.fd39c53c.js.gz +0 -0
- package/dist/web/public/settings-ui.478c8242.js.br +0 -0
- package/dist/web/public/settings-ui.478c8242.js.gz +0 -0
- package/dist/web/public/styles.3f797179.css +0 -1
- package/dist/web/public/styles.3f797179.css.br +0 -0
- package/dist/web/public/styles.3f797179.css.gz +0 -0
- package/dist/web/public/terminal-ui.2d582bf6.js +0 -3
- package/dist/web/public/terminal-ui.2d582bf6.js.br +0 -0
- package/dist/web/public/terminal-ui.2d582bf6.js.gz +0 -0
package/dist/types/common.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - CleanupRegistration / CleanupResourceType — entries for the centralized CleanupManager
|
|
10
10
|
* - NiceConfig / DEFAULT_NICE_CONFIG — process priority settings for `nice`/`ionice`
|
|
11
11
|
* - ProcessStats — memory/CPU/child-count snapshot for resource monitoring
|
|
12
|
+
* - FilesystemBrowseData — bounded path-picker directory listing returned to the web UI
|
|
12
13
|
*/
|
|
13
14
|
/**
|
|
14
15
|
* Interface for objects that hold resources requiring explicit cleanup.
|
|
@@ -59,6 +60,30 @@ export interface ProcessStats {
|
|
|
59
60
|
/** Timestamp of stats collection */
|
|
60
61
|
updatedAt: number;
|
|
61
62
|
}
|
|
63
|
+
/** A selectable entry returned by the filesystem path-picker API. */
|
|
64
|
+
export type FilesystemPreviewKind = 'image' | 'text' | 'document';
|
|
65
|
+
export interface FilesystemBrowseEntry {
|
|
66
|
+
name: string;
|
|
67
|
+
path: string;
|
|
68
|
+
type: 'file' | 'directory';
|
|
69
|
+
size?: number;
|
|
70
|
+
symlink?: boolean;
|
|
71
|
+
previewKind?: FilesystemPreviewKind;
|
|
72
|
+
}
|
|
73
|
+
/** A named root the path picker may browse without escaping its allowlist. */
|
|
74
|
+
export interface FilesystemBrowseRoot {
|
|
75
|
+
label: string;
|
|
76
|
+
path: string;
|
|
77
|
+
}
|
|
78
|
+
/** Response payload for `GET /api/filesystem/browse`. */
|
|
79
|
+
export interface FilesystemBrowseData {
|
|
80
|
+
path: string;
|
|
81
|
+
parent: string | null;
|
|
82
|
+
root: string;
|
|
83
|
+
roots: FilesystemBrowseRoot[];
|
|
84
|
+
entries: FilesystemBrowseEntry[];
|
|
85
|
+
truncated: boolean;
|
|
86
|
+
}
|
|
62
87
|
export type CleanupResourceType = 'timer' | 'interval' | 'watcher' | 'listener' | 'stream';
|
|
63
88
|
/**
|
|
64
89
|
* Registration entry for a cleanup resource.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/types/common.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/types/common.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,+DAA+D;IAC/D,OAAO,IAAI,IAAI,CAAC;IAChB,4CAA4C;IAC5C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED;;GAEG;AACH;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,mBAAmB,EAAE,UAGjC,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,2BAA2B;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qEAAqE;AACrE,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,CAAC;AAElE,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACrC;AAED,8EAA8E;AAC9E,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,yDAAyD;AACzD,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAC9B,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE3F;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,8CAA8C;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,uBAAuB;IACvB,IAAI,EAAE,mBAAmB,CAAC;IAC1B,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gCAAgC;IAChC,YAAY,EAAE,MAAM,CAAC;CACtB"}
|
package/dist/types/common.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - CleanupRegistration / CleanupResourceType — entries for the centralized CleanupManager
|
|
10
10
|
* - NiceConfig / DEFAULT_NICE_CONFIG — process priority settings for `nice`/`ionice`
|
|
11
11
|
* - ProcessStats — memory/CPU/child-count snapshot for resource monitoring
|
|
12
|
+
* - FilesystemBrowseData — bounded path-picker directory listing returned to the web UI
|
|
12
13
|
*/
|
|
13
14
|
export const DEFAULT_NICE_CONFIG = {
|
|
14
15
|
enabled: false,
|
package/dist/types/common.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/types/common.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/types/common.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAwCH,MAAM,CAAC,MAAM,mBAAmB,GAAe;IAC7C,OAAO,EAAE,KAAK;IACd,SAAS,EAAE,EAAE;CACd,CAAC"}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
<link rel="manifest" href="manifest.json">
|
|
17
17
|
<title>Codeman</title>
|
|
18
18
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E">
|
|
19
|
-
<link rel="stylesheet" href="styles.
|
|
20
|
-
<link rel="stylesheet" href="mobile.
|
|
19
|
+
<link rel="stylesheet" href="styles.42dd71e1.css">
|
|
20
|
+
<link rel="stylesheet" href="mobile.40e2d64e.css" media="(max-width: 1023px)">
|
|
21
21
|
<!-- xterm.css loaded async — terminal won't display until xterm.js runs anyway -->
|
|
22
22
|
<link rel="preload" href="vendor/xterm.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
|
23
23
|
<noscript><link rel="stylesheet" href="vendor/xterm.css"></noscript>
|
|
@@ -45,20 +45,20 @@
|
|
|
45
45
|
<!-- Synchronous mobile detection — runs before first paint to prevent panel flash -->
|
|
46
46
|
<script>if(window.innerWidth<768||(('ontouchstart' in window||navigator.maxTouchPoints>0)&&window.innerWidth<1024))document.documentElement.classList.add('mobile-init');</script>
|
|
47
47
|
<!-- Synchronous skin selection — runs before first paint to prevent theme flash -->
|
|
48
|
-
<script>try{var s=localStorage.getItem('codeman:skin')
|
|
48
|
+
<script>try{var s=localStorage.getItem('codeman:skin'),a=['og','daylight-green','daylight-blue','paper-gray','solarized-light','catppuccin-latte','rose-pine-dawn'];if(a.indexOf(s)<0)s='daylight-blue';document.documentElement.dataset.skin=s;window.__codemanSkin=s;}catch(e){document.documentElement.dataset.skin='daylight-blue';window.__codemanSkin='daylight-blue';}</script>
|
|
49
49
|
<!-- Apply the saved per-device language before first paint. The full translation
|
|
50
50
|
layer loads below; setting lang/dir here prevents an English accessibility
|
|
51
51
|
tree from flashing while the deferred scripts start. -->
|
|
52
52
|
<script>try{var m=window.innerWidth<768||(('ontouchstart' in window||navigator.maxTouchPoints>0)&&window.innerWidth<1024);var k=m?'codeman-app-settings-mobile':'codeman-app-settings';var l=JSON.parse(localStorage.getItem(k)||'{}').language;l=l==='zh-CN'?'zh-CN':'en';document.documentElement.lang=l;window.__codemanLanguage=l;}catch(e){document.documentElement.lang='en';window.__codemanLanguage='en';}</script>
|
|
53
53
|
<!-- Inline critical CSS for instant skeleton paint (before styles.css loads) -->
|
|
54
54
|
<style>
|
|
55
|
-
.loading-skeleton{display:flex;flex-direction:column;height:100vh;height:100dvh;background
|
|
56
|
-
.skeleton-header{height:40px;background:rgba(31,38,48,0.85);border-bottom:1px solid rgba(255,255,255,0.08);display:flex;align-items:center;padding:0 12px}
|
|
57
|
-
.skeleton-brand{color
|
|
55
|
+
.loading-skeleton{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--bg-dark,#11151c)}
|
|
56
|
+
.skeleton-header{height:40px;background:var(--glass-bg,rgba(31,38,48,0.85));border-bottom:1px solid var(--glass-border,rgba(255,255,255,0.08));display:flex;align-items:center;padding:0 12px}
|
|
57
|
+
.skeleton-brand{color:var(--accent,#38b6f0);font-size:14px;font-weight:700;font-family:'Manrope',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;opacity:.85}
|
|
58
58
|
.skeleton-tabs{display:flex;gap:4px;margin-left:16px}
|
|
59
|
-
.skeleton-tab{width:80px;height:24px;background:rgba(255,255,255,0.04);border-radius:6px}
|
|
60
|
-
.skeleton-terminal{flex:1;background
|
|
61
|
-
.skeleton-toolbar{height:42px;background:rgba(31,38,48,0.85);border-top:1px solid rgba(255,255,255,0.08)}
|
|
59
|
+
.skeleton-tab{width:80px;height:24px;background:var(--control-bg,rgba(255,255,255,0.04));border-radius:6px}
|
|
60
|
+
.skeleton-terminal{flex:1;background:var(--term-bg,#161b23)}
|
|
61
|
+
.skeleton-toolbar{height:42px;background:var(--glass-bg,rgba(31,38,48,0.85));border-top:1px solid var(--glass-border,rgba(255,255,255,0.08))}
|
|
62
62
|
.app-loaded .loading-skeleton{display:none}
|
|
63
63
|
</style>
|
|
64
64
|
</head>
|
|
@@ -1263,9 +1263,17 @@
|
|
|
1263
1263
|
<div class="settings-item settings-item-skin" title="Visual theme for this device (not synced)">
|
|
1264
1264
|
<span class="settings-item-label">Skin</span>
|
|
1265
1265
|
<select id="appSettingsSkin" class="form-select">
|
|
1266
|
-
<
|
|
1267
|
-
|
|
1268
|
-
|
|
1266
|
+
<optgroup label="Light">
|
|
1267
|
+
<option value="paper-gray">Paper Gray</option>
|
|
1268
|
+
<option value="solarized-light">Solarized Light</option>
|
|
1269
|
+
<option value="catppuccin-latte">Catppuccin Latte</option>
|
|
1270
|
+
<option value="rose-pine-dawn">Rosé Pine Dawn</option>
|
|
1271
|
+
</optgroup>
|
|
1272
|
+
<optgroup label="Dark">
|
|
1273
|
+
<option value="daylight-blue">Daylight Blue</option>
|
|
1274
|
+
<option value="daylight-green">Daylight Green</option>
|
|
1275
|
+
<option value="og">OG Codeman</option>
|
|
1276
|
+
</optgroup>
|
|
1269
1277
|
</select>
|
|
1270
1278
|
</div>
|
|
1271
1279
|
<div class="settings-item" id="appSettingsWebglRendererItem" title="Use the GPU-accelerated WebGL terminal renderer (desktop only). Turn off to force the DOM renderer if you hit GPU glitches. Codeman also auto-falls-back to the DOM renderer after repeated GPU stalls.">
|
|
@@ -2035,8 +2043,11 @@
|
|
|
2035
2043
|
</div>
|
|
2036
2044
|
<div class="form-row">
|
|
2037
2045
|
<label>Folder Path</label>
|
|
2038
|
-
<
|
|
2039
|
-
|
|
2046
|
+
<div class="path-input-group">
|
|
2047
|
+
<input type="text" id="linkCasePath" placeholder="/mnt/d/AI/my-project" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false">
|
|
2048
|
+
<button type="button" class="btn path-input-browse" onclick="app.openLinkCasePathPicker()">Browse…</button>
|
|
2049
|
+
</div>
|
|
2050
|
+
<span class="form-hint">Choose an existing folder from this computer or enter its absolute path</span>
|
|
2040
2051
|
</div>
|
|
2041
2052
|
</div>
|
|
2042
2053
|
<!-- Remote Tab -->
|
|
@@ -2599,21 +2610,21 @@
|
|
|
2599
2610
|
<script defer src="mobile-handlers.bc2b8c1d.js"></script>
|
|
2600
2611
|
<script defer src="voice-input.085e9e73.js"></script>
|
|
2601
2612
|
<script defer src="notification-manager.fca811fd.js"></script>
|
|
2602
|
-
<script defer src="keyboard-accessory.
|
|
2613
|
+
<script defer src="keyboard-accessory.9936227d.js"></script>
|
|
2603
2614
|
<script defer src="input-cjk.63794d0b.js"></script>
|
|
2604
2615
|
<!-- Hardened markdown HTML sanitizer (wires DOMPurify). Must precede app.js. -->
|
|
2605
2616
|
<script defer src="sanitize-html.bc7078d6.js"></script>
|
|
2606
2617
|
<script defer src="app.adc992cd.js"></script>
|
|
2607
|
-
<script defer src="terminal-ui.
|
|
2618
|
+
<script defer src="terminal-ui.f3a51974.js"></script>
|
|
2608
2619
|
<script defer src="respawn-ui.2d249da9.js"></script>
|
|
2609
2620
|
<script defer src="ralph-panel.6de2d0f8.js"></script>
|
|
2610
2621
|
<script defer src="orchestrator-panel.js"></script>
|
|
2611
2622
|
<script defer src="cron-ui.js"></script>
|
|
2612
|
-
<script defer src="settings-ui.
|
|
2613
|
-
<script defer src="panels-ui.
|
|
2623
|
+
<script defer src="settings-ui.b8def032.js"></script>
|
|
2624
|
+
<script defer src="panels-ui.3910e1e2.js"></script>
|
|
2614
2625
|
<script defer src="ultracode-panel.js"></script>
|
|
2615
2626
|
<script defer src="admin-ui.js"></script>
|
|
2616
|
-
<script defer src="session-ui.
|
|
2627
|
+
<script defer src="session-ui.ab6dd9aa.js"></script>
|
|
2617
2628
|
<script defer src="webview-tabs.js"></script>
|
|
2618
2629
|
<script defer src="ralph-wizard.13a1831e.js"></script>
|
|
2619
2630
|
<script defer src="api-client.c9b1cddc.js"></script>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Mobile keyboard accessory bar and modal focus trap.
|
|
3
3
|
*
|
|
4
|
-
* Defines
|
|
4
|
+
* Defines three exports:
|
|
5
5
|
*
|
|
6
6
|
* - KeyboardAccessoryBar (singleton object) — Quick action buttons shown above the virtual
|
|
7
7
|
* keyboard on mobile: arrow up/down, /init, /clear, /compact, paste, Esc, and dismiss.
|
|
@@ -10,12 +10,15 @@
|
|
|
10
10
|
* Destructive actions (/clear, /compact) require double-tap confirmation (2s amber state).
|
|
11
11
|
* Commands are sent as text + Enter separately for Ink compatibility.
|
|
12
12
|
* Only initializes on touch devices (MobileDetection.isTouchDevice guard).
|
|
13
|
+
* - PathPicker (singleton object) — Lazy server-side file/folder browser shared
|
|
14
|
+
* by Link Existing and the extended mobile keyboard bar.
|
|
13
15
|
*
|
|
14
16
|
* - FocusTrap (class) — Traps Tab/Shift+Tab keyboard focus within a modal element.
|
|
15
17
|
* Saves and restores previously focused element on deactivate. Used by Ralph wizard
|
|
16
18
|
* and other modal dialogs.
|
|
17
19
|
*
|
|
18
20
|
* @globals {object} KeyboardAccessoryBar
|
|
21
|
+
* @globals {object} PathPicker
|
|
19
22
|
* @globals {class} FocusTrap
|
|
20
23
|
*
|
|
21
24
|
* @dependency mobile-handlers.js (MobileDetection.isTouchDevice)
|
|
@@ -26,6 +29,338 @@
|
|
|
26
29
|
// Codeman — Keyboard accessory bar and focus trap for modals
|
|
27
30
|
// Loaded after mobile-handlers.js, before app.js
|
|
28
31
|
|
|
32
|
+
// ═══════════════════════════════════════════════════════════════
|
|
33
|
+
// Shared Filesystem Path Picker
|
|
34
|
+
// ═══════════════════════════════════════════════════════════════
|
|
35
|
+
|
|
36
|
+
const PathPicker = {
|
|
37
|
+
overlay: null,
|
|
38
|
+
_options: null,
|
|
39
|
+
_selectedPath: '',
|
|
40
|
+
_previousFocus: null,
|
|
41
|
+
_keydownHandler: null,
|
|
42
|
+
_loadSequence: 0,
|
|
43
|
+
_previewOverlay: null,
|
|
44
|
+
_previewRequestSequence: 0,
|
|
45
|
+
_previewPreviousFocus: null,
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Open the lazy filesystem browser.
|
|
49
|
+
* @param {{sessionId?: string, initialPath?: string, directoriesOnly?: boolean,
|
|
50
|
+
* title?: string, onSelect: (path: string) => void}} options
|
|
51
|
+
*/
|
|
52
|
+
open(options) {
|
|
53
|
+
this.close(false);
|
|
54
|
+
this._options = options;
|
|
55
|
+
this._selectedPath = '';
|
|
56
|
+
this._previousFocus = document.activeElement;
|
|
57
|
+
this._previousFocus?.blur?.();
|
|
58
|
+
|
|
59
|
+
const overlay = document.createElement('div');
|
|
60
|
+
overlay.className = 'path-picker-overlay';
|
|
61
|
+
overlay.setAttribute('role', 'dialog');
|
|
62
|
+
overlay.setAttribute('aria-modal', 'true');
|
|
63
|
+
overlay.setAttribute('aria-label', options.title || 'Select a path');
|
|
64
|
+
overlay.innerHTML = `
|
|
65
|
+
<div class="path-picker-dialog">
|
|
66
|
+
<div class="path-picker-header">
|
|
67
|
+
<strong class="path-picker-title"></strong>
|
|
68
|
+
<button type="button" class="path-picker-close" aria-label="Close">×</button>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="path-picker-roots-row">
|
|
71
|
+
<label for="pathPickerRoot">Location</label>
|
|
72
|
+
<select id="pathPickerRoot" class="path-picker-roots"></select>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="path-picker-nav">
|
|
75
|
+
<button type="button" class="path-picker-up" title="Parent folder" aria-label="Parent folder">↑</button>
|
|
76
|
+
<div class="path-picker-current" title="Current folder"></div>
|
|
77
|
+
<button type="button" class="path-picker-refresh" title="Refresh" aria-label="Refresh">↻</button>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="path-picker-status" aria-live="polite">Loading...</div>
|
|
80
|
+
<div class="path-picker-list" role="listbox"></div>
|
|
81
|
+
<div class="path-picker-selection">
|
|
82
|
+
<span class="path-picker-selection-label">Selected</span>
|
|
83
|
+
<span class="path-picker-selection-value">None</span>
|
|
84
|
+
</div>
|
|
85
|
+
<div class="path-picker-actions">
|
|
86
|
+
<button type="button" class="path-picker-current-select">Select Current Folder</button>
|
|
87
|
+
<span class="path-picker-action-spacer"></span>
|
|
88
|
+
<button type="button" class="path-picker-cancel">Cancel</button>
|
|
89
|
+
<button type="button" class="path-picker-confirm" disabled>Select</button>
|
|
90
|
+
</div>
|
|
91
|
+
</div>`;
|
|
92
|
+
|
|
93
|
+
this.overlay = overlay;
|
|
94
|
+
overlay.querySelector('.path-picker-title').textContent = options.title || 'Select a Path';
|
|
95
|
+
overlay.querySelector('.path-picker-close').addEventListener('click', () => this.close(true));
|
|
96
|
+
overlay.querySelector('.path-picker-cancel').addEventListener('click', () => this.close(true));
|
|
97
|
+
overlay.querySelector('.path-picker-confirm').addEventListener('click', () => this.confirm());
|
|
98
|
+
overlay.querySelector('.path-picker-current-select').addEventListener('click', () => {
|
|
99
|
+
const current = overlay.querySelector('.path-picker-current').textContent;
|
|
100
|
+
if (current) this.select(current);
|
|
101
|
+
});
|
|
102
|
+
overlay.querySelector('.path-picker-refresh').addEventListener('click', () => this.load());
|
|
103
|
+
overlay.querySelector('.path-picker-up').addEventListener('click', () => {
|
|
104
|
+
const parent = overlay.querySelector('.path-picker-up').dataset.parent;
|
|
105
|
+
if (parent) this.load(parent);
|
|
106
|
+
});
|
|
107
|
+
overlay.querySelector('.path-picker-roots').addEventListener('change', (event) => this.load(event.target.value));
|
|
108
|
+
overlay.addEventListener('click', (event) => {
|
|
109
|
+
if (event.target === overlay) this.close(true);
|
|
110
|
+
});
|
|
111
|
+
this._keydownHandler = (event) => {
|
|
112
|
+
if (event.key === 'Escape') {
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
if (this._previewOverlay) this.closePreview(true);
|
|
115
|
+
else this.close(true);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
document.addEventListener('keydown', this._keydownHandler);
|
|
119
|
+
document.body.appendChild(overlay);
|
|
120
|
+
this.load(options.initialPath || '');
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
async load(path) {
|
|
124
|
+
if (!this.overlay || !this._options) return;
|
|
125
|
+
const loadSequence = ++this._loadSequence;
|
|
126
|
+
const list = this.overlay.querySelector('.path-picker-list');
|
|
127
|
+
const status = this.overlay.querySelector('.path-picker-status');
|
|
128
|
+
list.replaceChildren();
|
|
129
|
+
status.textContent = 'Loading...';
|
|
130
|
+
|
|
131
|
+
const params = new URLSearchParams();
|
|
132
|
+
if (path) params.set('path', path);
|
|
133
|
+
if (this._options.sessionId) params.set('sessionId', this._options.sessionId);
|
|
134
|
+
try {
|
|
135
|
+
const response = await fetch(`/api/filesystem/browse?${params.toString()}`);
|
|
136
|
+
const result = await response.json();
|
|
137
|
+
if (!response.ok || !result.success) throw new Error(result.error || 'Failed to browse this folder');
|
|
138
|
+
if (!this.overlay || loadSequence !== this._loadSequence) return;
|
|
139
|
+
this.render(result.data);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (!this.overlay || loadSequence !== this._loadSequence) return;
|
|
142
|
+
if (path) {
|
|
143
|
+
this.load('');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
status.textContent = error.message || 'Failed to browse this folder';
|
|
147
|
+
status.classList.add('error');
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
render(data) {
|
|
152
|
+
const rootSelect = this.overlay.querySelector('.path-picker-roots');
|
|
153
|
+
rootSelect.replaceChildren();
|
|
154
|
+
for (const root of data.roots) {
|
|
155
|
+
const option = document.createElement('option');
|
|
156
|
+
option.value = root.path;
|
|
157
|
+
option.textContent = `${root.label} — ${root.path}`;
|
|
158
|
+
option.selected = data.path === root.path || data.root === root.path;
|
|
159
|
+
rootSelect.appendChild(option);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.overlay.querySelector('.path-picker-current').textContent = data.path;
|
|
163
|
+
const up = this.overlay.querySelector('.path-picker-up');
|
|
164
|
+
up.dataset.parent = data.parent || '';
|
|
165
|
+
up.disabled = !data.parent;
|
|
166
|
+
const status = this.overlay.querySelector('.path-picker-status');
|
|
167
|
+
status.classList.remove('error');
|
|
168
|
+
status.textContent = data.entries.length === 0
|
|
169
|
+
? 'This folder is empty'
|
|
170
|
+
: `${data.entries.length} item${data.entries.length === 1 ? '' : 's'}${data.truncated ? ' (first 500)' : ''}`;
|
|
171
|
+
|
|
172
|
+
const list = this.overlay.querySelector('.path-picker-list');
|
|
173
|
+
list.replaceChildren();
|
|
174
|
+
for (const entry of data.entries) {
|
|
175
|
+
const row = document.createElement('div');
|
|
176
|
+
row.className = 'path-picker-item';
|
|
177
|
+
if (entry.type === 'file' && this._options.directoriesOnly && !entry.previewKind) {
|
|
178
|
+
row.classList.add('not-selectable');
|
|
179
|
+
}
|
|
180
|
+
row.dataset.path = entry.path;
|
|
181
|
+
row.dataset.type = entry.type;
|
|
182
|
+
row.setAttribute('role', 'option');
|
|
183
|
+
|
|
184
|
+
const open = document.createElement('button');
|
|
185
|
+
open.type = 'button';
|
|
186
|
+
open.className = 'path-picker-item-main';
|
|
187
|
+
const icon = document.createElement('span');
|
|
188
|
+
icon.className = 'path-picker-item-icon';
|
|
189
|
+
icon.textContent = entry.type === 'directory' ? '\uD83D\uDCC1' : '\uD83D\uDCC4';
|
|
190
|
+
const name = document.createElement('span');
|
|
191
|
+
name.className = 'path-picker-item-name';
|
|
192
|
+
name.textContent = entry.name;
|
|
193
|
+
open.append(icon, name);
|
|
194
|
+
if (entry.symlink) {
|
|
195
|
+
const link = document.createElement('span');
|
|
196
|
+
link.className = 'path-picker-item-link';
|
|
197
|
+
link.textContent = '\u2197';
|
|
198
|
+
open.appendChild(link);
|
|
199
|
+
}
|
|
200
|
+
if (entry.type === 'directory') {
|
|
201
|
+
const chevron = document.createElement('span');
|
|
202
|
+
chevron.className = 'path-picker-item-chevron';
|
|
203
|
+
chevron.textContent = '\u203A';
|
|
204
|
+
open.appendChild(chevron);
|
|
205
|
+
open.addEventListener('click', () => this.load(entry.path));
|
|
206
|
+
} else if (entry.previewKind) {
|
|
207
|
+
const preview = document.createElement('span');
|
|
208
|
+
preview.className = 'path-picker-item-preview';
|
|
209
|
+
preview.textContent = '\uD83D\uDC41';
|
|
210
|
+
open.appendChild(preview);
|
|
211
|
+
open.title = `Preview ${entry.name}`;
|
|
212
|
+
open.setAttribute('aria-label', `Preview ${entry.name}`);
|
|
213
|
+
open.addEventListener('click', () => this.openPreview(entry));
|
|
214
|
+
} else if (!this._options.directoriesOnly) {
|
|
215
|
+
open.addEventListener('click', () => this.select(entry.path));
|
|
216
|
+
} else {
|
|
217
|
+
open.disabled = true;
|
|
218
|
+
}
|
|
219
|
+
row.appendChild(open);
|
|
220
|
+
|
|
221
|
+
if (entry.type === 'directory' || !this._options.directoriesOnly) {
|
|
222
|
+
const choose = document.createElement('button');
|
|
223
|
+
choose.type = 'button';
|
|
224
|
+
choose.className = 'path-picker-item-select';
|
|
225
|
+
choose.textContent = 'Choose';
|
|
226
|
+
choose.addEventListener('click', () => this.select(entry.path));
|
|
227
|
+
row.appendChild(choose);
|
|
228
|
+
}
|
|
229
|
+
list.appendChild(row);
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
select(path) {
|
|
234
|
+
if (!this.overlay) return;
|
|
235
|
+
this._selectedPath = path;
|
|
236
|
+
this.overlay.querySelector('.path-picker-selection-value').textContent = path;
|
|
237
|
+
this.overlay.querySelector('.path-picker-confirm').disabled = false;
|
|
238
|
+
this.overlay.querySelectorAll('.path-picker-item').forEach((row) => {
|
|
239
|
+
const selected = row.dataset.path === path;
|
|
240
|
+
row.classList.toggle('selected', selected);
|
|
241
|
+
row.setAttribute('aria-selected', selected ? 'true' : 'false');
|
|
242
|
+
});
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
openPreview(entry) {
|
|
246
|
+
this.closePreview(false);
|
|
247
|
+
this._previewPreviousFocus = document.activeElement;
|
|
248
|
+
const requestSequence = ++this._previewRequestSequence;
|
|
249
|
+
const params = new URLSearchParams({ path: entry.path });
|
|
250
|
+
if (this._options?.sessionId) params.set('sessionId', this._options.sessionId);
|
|
251
|
+
const previewUrl = `/api/filesystem/preview?${params.toString()}`;
|
|
252
|
+
|
|
253
|
+
const overlay = document.createElement('div');
|
|
254
|
+
overlay.className = 'path-preview-overlay';
|
|
255
|
+
overlay.setAttribute('role', 'dialog');
|
|
256
|
+
overlay.setAttribute('aria-modal', 'true');
|
|
257
|
+
overlay.setAttribute('aria-label', `Preview ${entry.name}`);
|
|
258
|
+
overlay.innerHTML = `
|
|
259
|
+
<div class="path-preview-dialog">
|
|
260
|
+
<div class="path-preview-header">
|
|
261
|
+
<div class="path-preview-heading">
|
|
262
|
+
<strong class="path-preview-title"></strong>
|
|
263
|
+
<span class="path-preview-path"></span>
|
|
264
|
+
</div>
|
|
265
|
+
<a class="path-preview-open" target="_blank" rel="noopener noreferrer">Open</a>
|
|
266
|
+
<button type="button" class="path-preview-close" aria-label="Close preview">×</button>
|
|
267
|
+
</div>
|
|
268
|
+
<div class="path-preview-body"><div class="path-preview-loading">Loading preview...</div></div>
|
|
269
|
+
</div>`;
|
|
270
|
+
overlay.querySelector('.path-preview-title').textContent = entry.name;
|
|
271
|
+
overlay.querySelector('.path-preview-path').textContent = entry.path;
|
|
272
|
+
overlay.querySelector('.path-preview-open').href = previewUrl;
|
|
273
|
+
overlay.querySelector('.path-preview-close').addEventListener('click', () => this.closePreview(true));
|
|
274
|
+
overlay.addEventListener('click', (event) => {
|
|
275
|
+
if (event.target === overlay) this.closePreview(true);
|
|
276
|
+
});
|
|
277
|
+
document.body.appendChild(overlay);
|
|
278
|
+
this._previewOverlay = overlay;
|
|
279
|
+
|
|
280
|
+
const body = overlay.querySelector('.path-preview-body');
|
|
281
|
+
if (entry.previewKind === 'image') {
|
|
282
|
+
const image = document.createElement('img');
|
|
283
|
+
image.className = 'path-preview-image';
|
|
284
|
+
image.alt = entry.name;
|
|
285
|
+
image.addEventListener('load', () => body.querySelector('.path-preview-loading')?.remove());
|
|
286
|
+
image.addEventListener('error', () => this.showPreviewError('Image preview failed to load'));
|
|
287
|
+
image.src = previewUrl;
|
|
288
|
+
body.appendChild(image);
|
|
289
|
+
} else if (entry.previewKind === 'text') {
|
|
290
|
+
fetch(previewUrl)
|
|
291
|
+
.then(async (response) => {
|
|
292
|
+
const content = await response.text();
|
|
293
|
+
if (!response.ok) {
|
|
294
|
+
let message = 'Text preview failed to load';
|
|
295
|
+
try {
|
|
296
|
+
message = JSON.parse(content).error || message;
|
|
297
|
+
} catch {}
|
|
298
|
+
throw new Error(message);
|
|
299
|
+
}
|
|
300
|
+
return content;
|
|
301
|
+
})
|
|
302
|
+
.then((content) => {
|
|
303
|
+
if (!this._previewOverlay || requestSequence !== this._previewRequestSequence) return;
|
|
304
|
+
const pre = document.createElement('pre');
|
|
305
|
+
pre.className = 'path-preview-text';
|
|
306
|
+
pre.textContent = content;
|
|
307
|
+
body.replaceChildren(pre);
|
|
308
|
+
})
|
|
309
|
+
.catch((error) => {
|
|
310
|
+
if (requestSequence === this._previewRequestSequence) this.showPreviewError(error.message);
|
|
311
|
+
});
|
|
312
|
+
} else {
|
|
313
|
+
const frame = document.createElement('iframe');
|
|
314
|
+
frame.className = 'path-preview-frame';
|
|
315
|
+
frame.title = entry.name;
|
|
316
|
+
frame.addEventListener('load', () => body.querySelector('.path-preview-loading')?.remove());
|
|
317
|
+
frame.src = previewUrl;
|
|
318
|
+
body.appendChild(frame);
|
|
319
|
+
}
|
|
320
|
+
overlay.querySelector('.path-preview-close').focus();
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
showPreviewError(message) {
|
|
324
|
+
const body = this._previewOverlay?.querySelector('.path-preview-body');
|
|
325
|
+
if (!body) return;
|
|
326
|
+
const error = document.createElement('div');
|
|
327
|
+
error.className = 'path-preview-error';
|
|
328
|
+
error.textContent = message || 'Preview failed to load';
|
|
329
|
+
body.replaceChildren(error);
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
closePreview(restoreFocus = true) {
|
|
333
|
+
this._previewRequestSequence += 1;
|
|
334
|
+
this._previewOverlay?.remove();
|
|
335
|
+
this._previewOverlay = null;
|
|
336
|
+
const previousFocus = this._previewPreviousFocus;
|
|
337
|
+
this._previewPreviousFocus = null;
|
|
338
|
+
if (restoreFocus) previousFocus?.focus?.();
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
confirm() {
|
|
342
|
+
if (!this._selectedPath || !this._options) return;
|
|
343
|
+
const selectedPath = this._selectedPath;
|
|
344
|
+
const onSelect = this._options.onSelect;
|
|
345
|
+
this.close(false);
|
|
346
|
+
onSelect(selectedPath);
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
close(restoreFocus = true) {
|
|
350
|
+
if (this._keydownHandler) document.removeEventListener('keydown', this._keydownHandler);
|
|
351
|
+
this._keydownHandler = null;
|
|
352
|
+
this._loadSequence += 1;
|
|
353
|
+
this.closePreview(false);
|
|
354
|
+
this.overlay?.remove();
|
|
355
|
+
this.overlay = null;
|
|
356
|
+
const previousFocus = this._previousFocus;
|
|
357
|
+
this._previousFocus = null;
|
|
358
|
+
this._options = null;
|
|
359
|
+
this._selectedPath = '';
|
|
360
|
+
if (restoreFocus) previousFocus?.focus?.();
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
|
|
29
364
|
// ═══════════════════════════════════════════════════════════════
|
|
30
365
|
// Mobile Keyboard Accessory Bar
|
|
31
366
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -92,6 +427,8 @@ const KeyboardAccessoryBar = {
|
|
|
92
427
|
<rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>
|
|
93
428
|
</svg>
|
|
94
429
|
</button>
|
|
430
|
+
<button class="accessory-btn" data-action="pick-path" title="Insert a file or folder path">📁 Path</button>
|
|
431
|
+
<button class="accessory-btn" data-action="clear-input" title="Clear the current unsent input">⌫ All</button>
|
|
95
432
|
<button class="accessory-btn" data-action="tab" title="Tab">Tab</button>
|
|
96
433
|
<button class="accessory-btn" data-action="shift-tab" title="Shift+Tab">⇧Tab</button>
|
|
97
434
|
<button class="accessory-btn" data-action="effort-max" title="/effort max">Max</button>
|
|
@@ -128,7 +465,7 @@ const KeyboardAccessoryBar = {
|
|
|
128
465
|
this.handleAction(action, btn);
|
|
129
466
|
|
|
130
467
|
// Refocus terminal so keyboard stays open (tap blurs terminal → keyboard dismisses → toolbar shifts)
|
|
131
|
-
const refocusActions = new Set(['scroll-up', 'scroll-down', 'arrow-left', 'arrow-right', 'tab', 'shift-tab', 'ctrl-o', 'opt-enter', 'esc', 'effort-max']);
|
|
468
|
+
const refocusActions = new Set(['scroll-up', 'scroll-down', 'arrow-left', 'arrow-right', 'tab', 'shift-tab', 'ctrl-o', 'opt-enter', 'esc', 'effort-max', 'clear-input']);
|
|
132
469
|
if (refocusActions.has(action) ||
|
|
133
470
|
((action === 'clear' || action === 'compact') && this._confirmAction)) {
|
|
134
471
|
if (typeof app !== 'undefined' && app.terminal) {
|
|
@@ -207,6 +544,12 @@ const KeyboardAccessoryBar = {
|
|
|
207
544
|
case 'paste':
|
|
208
545
|
this.pasteFromClipboard();
|
|
209
546
|
break;
|
|
547
|
+
case 'pick-path':
|
|
548
|
+
this.pickPath();
|
|
549
|
+
break;
|
|
550
|
+
case 'clear-input':
|
|
551
|
+
app.clearTerminalInput?.();
|
|
552
|
+
break;
|
|
210
553
|
case 'dismiss':
|
|
211
554
|
// Blur active element to dismiss keyboard
|
|
212
555
|
document.activeElement?.blur();
|
|
@@ -265,6 +608,22 @@ const KeyboardAccessoryBar = {
|
|
|
265
608
|
}).catch(() => {});
|
|
266
609
|
},
|
|
267
610
|
|
|
611
|
+
/** Browse the active session's workspace and insert a selected path without Enter. */
|
|
612
|
+
pickPath() {
|
|
613
|
+
if (!app.activeSessionId) return;
|
|
614
|
+
const session = app.sessions?.get(app.activeSessionId);
|
|
615
|
+
PathPicker.open({
|
|
616
|
+
title: 'Insert File or Folder Path',
|
|
617
|
+
sessionId: app.activeSessionId,
|
|
618
|
+
initialPath: session?.workingDir || '',
|
|
619
|
+
directoriesOnly: false,
|
|
620
|
+
onSelect: (path) => {
|
|
621
|
+
app.insertTerminalText?.(path);
|
|
622
|
+
setTimeout(() => app.terminal?.focus(), 100);
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
},
|
|
626
|
+
|
|
268
627
|
/** Show a paste overlay for iOS compatibility.
|
|
269
628
|
* Handles three input paths from one dialog:
|
|
270
629
|
* - Text: long-press the textarea → Paste → Send (unchanged).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|