clay-server 5.1.0-beta.1 → 5.1.0-beta.3
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/LICENSES/README.md +4 -0
- package/LICENSES/dicebear-avatars.md +16 -0
- package/bin/cli.js +9 -91
- package/lib/builtin-mates.js +7 -7
- package/lib/daemon-project-access.js +7 -1
- package/lib/daemon-settings.js +11 -0
- package/lib/daemon.js +10 -37
- package/lib/dicebear-avatar.js +62 -0
- package/lib/knowledge-db-service.js +175 -0
- package/lib/knowledge-db-validation.js +0 -0
- package/lib/knowledge-document-index.js +49 -0
- package/lib/knowledge-draft-store.js +56 -0
- package/lib/knowledge-file-store.js +199 -0
- package/lib/knowledge-markdown.js +128 -0
- package/lib/linear-store.js +35 -8
- package/lib/mate-identity-file.js +39 -0
- package/lib/mate-knowledge-mcp-server.js +29 -6
- package/lib/mate-knowledge-service.js +23 -2
- package/lib/mate-knowledge-sync.js +37 -22
- package/lib/mate-ready-creation.js +2 -1
- package/lib/mate-workspace-color.js +4 -0
- package/lib/mates-identity.js +4 -4
- package/lib/mates-prompts.js +4 -3
- package/lib/mates.js +30 -11
- package/lib/os-users.js +5 -16
- package/lib/project-connection.js +36 -9
- package/lib/project-debate-proposal.js +6 -0
- package/lib/project-debate-workspace.js +43 -0
- package/lib/project-debate.js +4 -2
- package/lib/project-filesystem.js +21 -39
- package/lib/project-knowledge.js +315 -136
- package/lib/project-mate-creation-proposal.js +1 -0
- package/lib/project-mate-interaction.js +7 -6
- package/lib/project-server-settings.js +31 -0
- package/lib/project-sessions.js +92 -182
- package/lib/project-user-knowledge.js +60 -0
- package/lib/project.js +62 -21
- package/lib/public/app.js +79 -71
- package/lib/public/clay-studio-wordmark-bold.svg +20 -0
- package/lib/public/css/clay-workspace.css +105 -0
- package/lib/public/css/debates-workbench.css +47 -0
- package/lib/public/css/home-debate-live.css +26 -0
- package/lib/public/css/home-hub.css +3 -1
- package/lib/public/css/home-mate-model-picker.css +15 -131
- package/lib/public/css/home-mate-settings.css +168 -16
- package/lib/public/css/icon-strip.css +249 -32
- package/lib/public/css/input.css +3 -1
- package/lib/public/css/mate-conversation.css +176 -0
- package/lib/public/css/mate-knowledge-workbench.css +118 -0
- package/lib/public/css/mate-workspace-color.css +16 -0
- package/lib/public/css/mates.css +28 -28
- package/lib/public/css/menus.css +3 -0
- package/lib/public/css/messages.css +1 -114
- package/lib/public/css/mobile-nav.css +16 -16
- package/lib/public/css/overlays.css +15 -0
- package/lib/public/css/project-logs.css +7 -0
- package/lib/public/css/project-mate-creation.css +105 -0
- package/lib/public/css/project-settings-modal.css +56 -0
- package/lib/public/css/right-workbench.css +3 -3
- package/lib/public/css/server-settings-system.css +125 -0
- package/lib/public/css/session-folders.css +80 -21
- package/lib/public/css/session-github.css +25 -16
- package/lib/public/css/settings-ui.css +247 -22
- package/lib/public/css/shared-browser.css +7 -3
- package/lib/public/css/sidebar.css +183 -107
- package/lib/public/css/title-bar.css +156 -1
- package/lib/public/css/worker-delegation.css +47 -0
- package/lib/public/css/worker-proposal.css +72 -150
- package/lib/public/css/you.css +74 -0
- package/lib/public/index.html +206 -548
- package/lib/public/modules/account-island.js +232 -0
- package/lib/public/modules/admin-settings-email.js +197 -0
- package/lib/public/modules/admin-settings-invitations.js +183 -0
- package/lib/public/modules/admin-settings-projects.js +198 -0
- package/lib/public/modules/admin-settings-ui.js +100 -0
- package/lib/public/modules/admin-settings-users.js +331 -0
- package/lib/public/modules/admin.js +19 -949
- package/lib/public/modules/app-cursors.js +19 -43
- package/lib/public/modules/app-debate-ui.js +4 -3
- package/lib/public/modules/app-dm.js +0 -2
- package/lib/public/modules/app-header.js +106 -0
- package/lib/public/modules/app-message-router.js +12 -3
- package/lib/public/modules/app-messages.js +47 -61
- package/lib/public/modules/app-notifications.js +4 -7
- package/lib/public/modules/app-projects.js +5 -38
- package/lib/public/modules/app-rate-limit.js +70 -12
- package/lib/public/modules/app-rendering.js +12 -13
- package/lib/public/modules/avatar.js +30 -9
- package/lib/public/modules/chat-render-runtime.js +16 -4
- package/lib/public/modules/command-palette.js +2 -15
- package/lib/public/modules/context-sources.js +27 -18
- package/lib/public/modules/cursor-sharing-preference.js +3 -3
- package/lib/public/modules/debate-controls.js +299 -0
- package/lib/public/modules/debate.js +2 -0
- package/lib/public/modules/debates-workbench.js +339 -0
- package/lib/public/modules/default-ai.js +88 -65
- package/lib/public/modules/header-tui-font.js +10 -20
- package/lib/public/modules/home-debate-controls.js +18 -289
- package/lib/public/modules/home-mate-avatar-editor.js +228 -0
- package/lib/public/modules/home-mate-color-picker.js +66 -0
- package/lib/public/modules/home-mate-creation.js +1 -1
- package/lib/public/modules/home-mate-instructions.js +90 -0
- package/lib/public/modules/home-mate-model-picker.js +80 -271
- package/lib/public/modules/home-mate-profile-editor.js +72 -0
- package/lib/public/modules/home-mate-settings.js +118 -125
- package/lib/public/modules/home-shell.js +3 -0
- package/lib/public/modules/home-surface-boot.js +1 -9
- package/lib/public/modules/icon-strip-tooltip.js +95 -0
- package/lib/public/modules/knowledge-db-edit-state.js +35 -0
- package/lib/public/modules/knowledge-db-workbench.js +234 -0
- package/lib/public/modules/knowledge-history-state.js +55 -0
- package/lib/public/modules/knowledge-markdown-editor.js +57 -0
- package/lib/public/modules/knowledge-preview-links.js +70 -0
- package/lib/public/modules/knowledge-workbench-shell.js +21 -0
- package/lib/public/modules/linear-settings.js +3 -2
- package/lib/public/modules/markdown.js +5 -6
- package/lib/public/modules/mate-knowledge-workbench-state.js +174 -0
- package/lib/public/modules/mate-knowledge-workbench.js +500 -0
- package/lib/public/modules/mate-knowledge.js +11 -3
- package/lib/public/modules/mate-management.js +54 -10
- package/lib/public/modules/mate-sidebar.js +11 -1
- package/lib/public/modules/mate-workspace-color.js +33 -0
- package/lib/public/modules/mention.js +5 -13
- package/lib/public/modules/notification-settings.js +81 -0
- package/lib/public/modules/notifications.js +44 -28
- package/lib/public/modules/personal-settings-dialog.js +51 -0
- package/lib/public/modules/profile.js +66 -24
- package/lib/public/modules/project-chat-identity.js +79 -0
- package/lib/public/modules/project-debate-planning-workspace.js +250 -0
- package/lib/public/modules/project-debate-workspace.js +185 -0
- package/lib/public/modules/project-header-identity.js +127 -0
- package/lib/public/modules/project-logs-render.js +9 -4
- package/lib/public/modules/project-mate-creation-workspace.js +414 -0
- package/lib/public/modules/project-mate-navigation.js +238 -0
- package/lib/public/modules/project-session-navigation.js +29 -0
- package/lib/public/modules/project-settings-environment.js +106 -0
- package/lib/public/modules/project-settings.js +72 -628
- package/lib/public/modules/project-switcher.js +6 -8
- package/lib/public/modules/projects-hub-view.js +5 -6
- package/lib/public/modules/projects-hub.js +2 -6
- package/lib/public/modules/qrcode.js +27 -8
- package/lib/public/modules/search-clay-chat.js +8 -11
- package/lib/public/modules/server-settings-controls.js +316 -0
- package/lib/public/modules/server-settings-environment.js +194 -0
- package/lib/public/modules/server-settings-navigation.js +39 -0
- package/lib/public/modules/server-settings-state.js +54 -0
- package/lib/public/modules/server-settings.js +116 -788
- package/lib/public/modules/server-storage-settings.js +84 -0
- package/lib/public/modules/session-activity-tooltip.js +12 -0
- package/lib/public/modules/session-create-form.js +86 -14
- package/lib/public/modules/session-create.js +50 -7
- package/lib/public/modules/session-github.js +2 -1
- package/lib/public/modules/session-list-visibility.js +41 -0
- package/lib/public/modules/session-presence-ui.js +3 -0
- package/lib/public/modules/session-title-marquee.js +51 -0
- package/lib/public/modules/settings-env-values.js +34 -0
- package/lib/public/modules/settings-select.js +121 -0
- package/lib/public/modules/shared-browser.js +22 -12
- package/lib/public/modules/sidebar-mates.js +3 -50
- package/lib/public/modules/sidebar-mobile.js +63 -60
- package/lib/public/modules/sidebar-projects.js +84 -97
- package/lib/public/modules/sidebar-session-folders-ui.js +2 -2
- package/lib/public/modules/sidebar-session-hierarchy.js +149 -70
- package/lib/public/modules/sidebar-sessions.js +49 -50
- package/lib/public/modules/sidebar.js +11 -13
- package/lib/public/modules/stt-picker.js +14 -5
- package/lib/public/modules/terminal-prefs.js +22 -0
- package/lib/public/modules/theme.js +88 -52
- package/lib/public/modules/thinking-lifecycle.js +7 -6
- package/lib/public/modules/tool-palette-order.js +4 -3
- package/lib/public/modules/tool-palette.js +13 -3
- package/lib/public/modules/tools.js +22 -22
- package/lib/public/modules/user-settings-account.js +318 -0
- package/lib/public/modules/user-settings-appearance.js +55 -0
- package/lib/public/modules/user-settings-editors.js +24 -0
- package/lib/public/modules/user-settings-integrations.js +104 -0
- package/lib/public/modules/user-settings-layout.js +80 -0
- package/lib/public/modules/user-settings-preferences.js +48 -0
- package/lib/public/modules/user-settings-profile.js +53 -0
- package/lib/public/modules/user-settings-themes.js +38 -0
- package/lib/public/modules/user-settings-voice.js +75 -0
- package/lib/public/modules/user-settings.js +53 -480
- package/lib/public/modules/worker-proposal.js +42 -8
- package/lib/public/modules/worker-runtime-picker.js +115 -0
- package/lib/public/modules/you-activity.js +121 -0
- package/lib/public/modules/you.js +251 -0
- package/lib/public/style.css +20 -8
- package/lib/public/vendor/codemirror/LICENSE +21 -0
- package/lib/public/vendor/codemirror/codemirror.css +344 -0
- package/lib/public/vendor/codemirror/codemirror.js +9884 -0
- package/lib/public/vendor/codemirror/continuelist.js +101 -0
- package/lib/public/vendor/codemirror/markdown.js +886 -0
- package/lib/public/vendor/codemirror/show-hint.css +37 -0
- package/lib/public/vendor/codemirror/show-hint.js +523 -0
- package/lib/sdk-bridge.js +15 -3
- package/lib/sdk-message-processor.js +16 -4
- package/lib/sdk-quota-snapshot.js +37 -0
- package/lib/server-admin.js +11 -0
- package/lib/server-appearance-settings.js +71 -0
- package/lib/server-home-chat-events.js +1 -1
- package/lib/server-home-chat.js +25 -3
- package/lib/server-home-debate-planning.js +4 -1
- package/lib/server-home-debates.js +2 -0
- package/lib/server-home-mate-creation.js +4 -1
- package/lib/server-linear.js +14 -8
- package/lib/server-mate-instructions.js +44 -0
- package/lib/server-mates.js +14 -4
- package/lib/server-notification-settings.js +67 -0
- package/lib/server-palette.js +1 -1
- package/lib/server-settings.js +69 -1
- package/lib/server-storage.js +57 -0
- package/lib/server-user-knowledge.js +119 -0
- package/lib/server.js +31 -3
- package/lib/session-visibility.js +7 -2
- package/lib/sessions.js +35 -10
- package/lib/startup-projects.js +40 -0
- package/lib/storage-native-walk.js +85 -0
- package/lib/storage-usage.js +144 -0
- package/lib/user-knowledge-service.js +167 -0
- package/lib/users-permissions.js +7 -0
- package/lib/users-preferences.js +0 -74
- package/lib/users-project-mate-filter-preferences.js +39 -0
- package/lib/users.js +6 -4
- package/lib/workspace-query-access.js +4 -1
- package/lib/ws-schema.js +52 -4
- package/lib/yoke/acp-process-manager.js +2 -2
- package/lib/yoke/adapters/codex.js +12 -52
- package/lib/yoke/codex-app-server.js +2 -2
- package/lib/yoke/codex-model-catalog.js +52 -0
- package/lib/yoke/codex-rate-limits.js +26 -0
- package/package.json +12 -3
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
|
|
3
|
+
function size(bytes) {
|
|
4
|
+
if (bytes < 1024) return bytes + ' B';
|
|
5
|
+
var units = ['KiB', 'MiB', 'GiB', 'TiB'];
|
|
6
|
+
var index = -1;
|
|
7
|
+
do { bytes /= 1024; index++; } while (bytes >= 1024 && index < units.length - 1);
|
|
8
|
+
return bytes.toFixed(1) + ' ' + units[index];
|
|
9
|
+
}
|
|
10
|
+
function cell(row, text, header) {
|
|
11
|
+
var el = document.createElement(header ? 'th' : 'td');
|
|
12
|
+
el.textContent = text;
|
|
13
|
+
if (header) el.scope = 'row';
|
|
14
|
+
row.appendChild(el);
|
|
15
|
+
}
|
|
16
|
+
function render(data) {
|
|
17
|
+
var projectMode = data.attribution === 'project';
|
|
18
|
+
document.getElementById('settings-storage-description').textContent = projectMode ? 'Project disk usage grouped by project owner, alongside shared Clay data.' : 'Disk usage across Clay data, registered projects and mapped Linux home directories.';
|
|
19
|
+
document.getElementById('settings-storage-owner-heading').textContent = projectMode ? 'Projects' : 'Linux account';
|
|
20
|
+
document.querySelector('.settings-storage-table').setAttribute('aria-label', projectMode ? 'Allocated disk usage by project owner' : 'Allocated disk usage by filesystem owner');
|
|
21
|
+
document.getElementById('settings-storage-explanation').textContent = (projectMode ? 'Project files are attributed to their registered project owner. Nested projects use their own owner; conflicting shared paths or hard links are unattributed. ' : 'Allocated space is attributed by Linux file owner, including directory metadata. ') + 'Shared paths count once. Symlinks and nested mounts are skipped. Results may be cached for 5 minutes. Refresh usage requests a new measurement.';
|
|
22
|
+
document.getElementById('settings-storage-attribution-note').textContent = projectMode ? 'System / unattributed includes Clay data outside projects, unowned projects and conflicting shared files. Home directories are included only if registered as projects.' : 'System / unattributed includes unmapped or shared Linux identities. Unavailable means the account cannot be uniquely mapped; it does not mean zero usage.';
|
|
23
|
+
var body = document.getElementById('settings-storage-users');
|
|
24
|
+
body.replaceChildren();
|
|
25
|
+
data.users.slice().sort(function (a, b) { return b.bytes - a.bytes; }).forEach(function (user) {
|
|
26
|
+
var row = document.createElement('tr');
|
|
27
|
+
cell(row, user.name, true);
|
|
28
|
+
cell(row, projectMode ? String(user.projects || 0) : user.linuxUser || 'No Linux account');
|
|
29
|
+
cell(row, user.mapped ? (data.scanning || data.partial ? 'At least ' : '') + size(user.bytes) : 'Unavailable');
|
|
30
|
+
cell(row, user.mapped ? user.files.toLocaleString() : '—');
|
|
31
|
+
body.appendChild(row);
|
|
32
|
+
});
|
|
33
|
+
var shared = document.createElement('tr');
|
|
34
|
+
cell(shared, 'System / unattributed', true); cell(shared, projectMode ? 'Shared / unowned' : 'Other filesystem owners');
|
|
35
|
+
cell(shared, size(data.unattributedBytes)); cell(shared, data.unattributedFiles.toLocaleString());
|
|
36
|
+
body.appendChild(shared);
|
|
37
|
+
document.getElementById('settings-storage-total').textContent = (data.partial || data.scanning ? 'At least ' : '') + size(data.totalBytes);
|
|
38
|
+
var status = document.getElementById('settings-storage-status');
|
|
39
|
+
status.textContent = (data.scanning ? 'Scan in progress; these totals are not final. ' : '') + (data.partial ? 'Partial scan: some paths were unreadable, on another filesystem, or exceeded the scan limit. ' : '') +
|
|
40
|
+
'Checked ' + new Date(data.checkedAt).toLocaleTimeString() + '. ' + data.roots + ' storage roots scanned.';
|
|
41
|
+
status.dataset.state = data.scanning ? 'pending' : data.partial ? 'error' : 'saved';
|
|
42
|
+
document.getElementById('settings-storage-results').hidden = false;
|
|
43
|
+
}
|
|
44
|
+
export async function loadStorageUsage(refresh) {
|
|
45
|
+
if (store.get('serverStorageLoading')) return;
|
|
46
|
+
clearTimeout(store.get('serverStorageTimer'));
|
|
47
|
+
var button = document.getElementById('settings-storage-refresh');
|
|
48
|
+
var status = document.getElementById('settings-storage-status');
|
|
49
|
+
store.set({ serverStorageLoading: true });
|
|
50
|
+
button.disabled = true;
|
|
51
|
+
button.textContent = 'Measuring…';
|
|
52
|
+
if (!store.get('serverStorageScanning')) status.textContent = 'Measuring disk usage. Large directories can take several seconds.';
|
|
53
|
+
status.dataset.state = 'pending';
|
|
54
|
+
try {
|
|
55
|
+
var response = await fetch('/api/server/storage' + (refresh === true ? '?refresh=1' : ''), { credentials: 'same-origin' });
|
|
56
|
+
var data = await response.json();
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
if (response.status === 401 || response.status === 403) {
|
|
59
|
+
store.set({ serverStorageUsage: null });
|
|
60
|
+
document.getElementById('settings-storage-results').hidden = true;
|
|
61
|
+
}
|
|
62
|
+
throw new Error(data.error || 'Disk usage is unavailable. Try refreshing.');
|
|
63
|
+
}
|
|
64
|
+
store.set({ serverStorageUsage: data, serverStorageScanning: !!data.scanning });
|
|
65
|
+
if (data.users) render(data);
|
|
66
|
+
if (data.scanning) {
|
|
67
|
+
status.textContent = 'Scan in progress. Project files, including node_modules, are being measured; totals are not final.';
|
|
68
|
+
store.set({ serverStorageTimer: setTimeout(function () {
|
|
69
|
+
if (store.get('serverSettingsPage') === 'storage' && !document.getElementById('server-settings').classList.contains('hidden')) loadStorageUsage();
|
|
70
|
+
}, 1500) });
|
|
71
|
+
}
|
|
72
|
+
} catch (error) {
|
|
73
|
+
store.set({ serverStorageScanning: false });
|
|
74
|
+
status.textContent = error.message + (store.get('serverStorageUsage') ? ' Previous measurements remain below.' : '');
|
|
75
|
+
status.dataset.state = 'error';
|
|
76
|
+
} finally {
|
|
77
|
+
store.set({ serverStorageLoading: false });
|
|
78
|
+
button.disabled = !!store.get('serverStorageScanning');
|
|
79
|
+
button.textContent = button.disabled ? 'Measuring…' : 'Refresh usage';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export function initStorageUsage() {
|
|
83
|
+
document.getElementById('settings-storage-refresh').addEventListener('click', function () { loadStorageUsage(true); });
|
|
84
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
function formatSessionActivity(value) {
|
|
2
|
+
if (!value) return "";
|
|
3
|
+
var date = new Date(value);
|
|
4
|
+
if (isNaN(date.getTime())) return "";
|
|
5
|
+
try {
|
|
6
|
+
return "Last activity: " + new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(date);
|
|
7
|
+
} catch (error) {
|
|
8
|
+
return "Last activity: " + date.toLocaleString();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { formatSessionActivity };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The inline "new session" draft row that sits under a folder header (also when
|
|
2
|
-
// the folder is collapsed): provider icon, a compact provider picker
|
|
3
|
-
// Cancel. The
|
|
2
|
+
// the folder is collapsed): provider icon, a compact provider picker, Create
|
|
3
|
+
// and icon Cancel. The header action remains New session. State and server round
|
|
4
4
|
// trips are in session-create.js; this module only builds and repaints the DOM
|
|
5
5
|
// from the store, so the row keeps its provider, focus and error across list
|
|
6
6
|
// rerenders and server updates.
|
|
@@ -12,7 +12,7 @@ import { startNewSession } from './sidebar-sessions.js';
|
|
|
12
12
|
import { closeMobileSheet } from './sidebar-mobile.js';
|
|
13
13
|
import {
|
|
14
14
|
createState, createOptions, installedVendorIds,
|
|
15
|
-
|
|
15
|
+
beginSessionCreate, closeSessionCreate, chooseVendor, setCreateMenu, submitSessionCreate, submitMateSessionCreate, saveProjectDefault, retryOptions
|
|
16
16
|
} from './session-create.js';
|
|
17
17
|
|
|
18
18
|
// Every registered provider, in the established order, followed by any the
|
|
@@ -61,6 +61,15 @@ function focusHeaderButton(folderId) {
|
|
|
61
61
|
requestAnimationFrame(applyFocus);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
function focusCreateRow(folderId) {
|
|
65
|
+
var rows = document.querySelectorAll('.session-create-row[data-folder-id="' + folderId + '"]');
|
|
66
|
+
for (var i = 0; i < rows.length; i++) {
|
|
67
|
+
if (!rows[i].getClientRects().length) continue;
|
|
68
|
+
var target = rows[i].querySelector('[data-focus-key="vendor"]') || rows[i].querySelector('[data-focus-key="create"]') || rows[i].querySelector('[data-focus-key="cancel"]');
|
|
69
|
+
if (target && !target.disabled) { target.focus({ preventScroll: true }); return; }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
64
73
|
function prefersReducedMotion() {
|
|
65
74
|
return typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
66
75
|
}
|
|
@@ -106,20 +115,25 @@ function createStatusMessage(form, options, valid) {
|
|
|
106
115
|
function paintNewSessionButton(btn, folderId, label) {
|
|
107
116
|
var form = createState();
|
|
108
117
|
var active = !!(form && form.folderId === folderId);
|
|
118
|
+
var direct = active && form.mode === "mate-direct";
|
|
109
119
|
var options = createOptions();
|
|
110
|
-
var valid = active && options.loaded && installedVendorIds().indexOf(form.vendor) !== -1;
|
|
120
|
+
var valid = direct || (active && options.loaded && installedVendorIds().indexOf(form.vendor) !== -1);
|
|
111
121
|
var pending = active && form.phase === "pending";
|
|
112
|
-
var
|
|
113
|
-
var
|
|
122
|
+
var uncertain = direct && form.phase === "uncertain";
|
|
123
|
+
var buttonText = direct ? (pending ? "Creating…" : (uncertain ? "Check sessions" : "Retry")) : "New session";
|
|
124
|
+
var status = active ? (direct ? (form.error || (pending ? "Creating session…" : "")) : createStatusMessage(form, options, valid)) : "";
|
|
114
125
|
ensureNewSessionButtonContent(btn);
|
|
115
|
-
btn.classList.toggle("is-create",
|
|
126
|
+
btn.classList.toggle("is-create", direct);
|
|
116
127
|
btn.setAttribute("aria-expanded", String(active));
|
|
117
128
|
btn.setAttribute("aria-busy", String(pending));
|
|
118
|
-
|
|
129
|
+
var activeLabel = direct
|
|
130
|
+
? (pending ? "Creating session in " : (uncertain ? "Check sessions in " : "Retry new session in "))
|
|
131
|
+
: "New session in ";
|
|
132
|
+
btn.setAttribute("aria-label", active ? activeLabel + label : "New session in " + label);
|
|
119
133
|
var controls = btn.getAttribute("aria-controls");
|
|
120
134
|
if (active && status && controls) btn.setAttribute("aria-describedby", controls + "-status");
|
|
121
135
|
else btn.removeAttribute("aria-describedby");
|
|
122
|
-
btn.disabled =
|
|
136
|
+
btn.disabled = direct ? (pending || uncertain) : !!(form && form.phase === "pending");
|
|
123
137
|
setNewSessionButtonText(btn, buttonText, active && (!form.openedAt || Date.now() - form.openedAt < 400 || btn.isConnected));
|
|
124
138
|
refreshIcons();
|
|
125
139
|
}
|
|
@@ -136,8 +150,12 @@ export function renderNewSessionButton(folderId, label, surface) {
|
|
|
136
150
|
btn.addEventListener("click", function (event) {
|
|
137
151
|
event.stopPropagation();
|
|
138
152
|
var live = createState();
|
|
139
|
-
if (live && live.folderId === folderId) {
|
|
140
|
-
|
|
153
|
+
if (live && live.folderId === folderId) {
|
|
154
|
+
if (live.mode === "mate-direct") submitMateSessionCreate();
|
|
155
|
+
else focusCreateRow(folderId);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
beginSessionCreate(folderId);
|
|
141
159
|
focusHeaderButton(folderId);
|
|
142
160
|
});
|
|
143
161
|
return btn;
|
|
@@ -160,6 +178,10 @@ export function renderCreateRow(folderId, label, surface) {
|
|
|
160
178
|
root.setAttribute("role", "group");
|
|
161
179
|
root.setAttribute("aria-label", "New session in " + label);
|
|
162
180
|
root.dataset.folderId = folderId;
|
|
181
|
+
if (form.mode === "mate-direct") {
|
|
182
|
+
paintMateDirectRow(root, form);
|
|
183
|
+
return root;
|
|
184
|
+
}
|
|
163
185
|
root.addEventListener("keydown", function (event) {
|
|
164
186
|
// Enter and Escape belong to the input method while composing.
|
|
165
187
|
if (event.isComposing || event.keyCode === 229) return;
|
|
@@ -195,6 +217,43 @@ export function renderCreateRow(folderId, label, surface) {
|
|
|
195
217
|
return root;
|
|
196
218
|
}
|
|
197
219
|
|
|
220
|
+
function paintMateDirectRow(root, form) {
|
|
221
|
+
var pending = form.phase === "pending";
|
|
222
|
+
var uncertain = form.phase === "uncertain";
|
|
223
|
+
root.setAttribute("aria-busy", String(pending));
|
|
224
|
+
root.innerHTML = "";
|
|
225
|
+
var line = document.createElement("div");
|
|
226
|
+
line.className = "session-create-line session-create-direct-line";
|
|
227
|
+
var status = document.createElement("div");
|
|
228
|
+
status.id = root.id + "-status";
|
|
229
|
+
status.className = "session-create-status" + (form.error ? " is-error" : "");
|
|
230
|
+
status.setAttribute("role", form.error ? "alert" : "status");
|
|
231
|
+
status.textContent = form.error || "Creating session…";
|
|
232
|
+
line.appendChild(status);
|
|
233
|
+
if (!pending && !uncertain) {
|
|
234
|
+
var retry = document.createElement("button");
|
|
235
|
+
retry.type = "button";
|
|
236
|
+
retry.className = "session-create-retry";
|
|
237
|
+
retry.dataset.focusKey = "retry";
|
|
238
|
+
retry.textContent = "Retry";
|
|
239
|
+
retry.addEventListener("click", function () { submitMateSessionCreate(); });
|
|
240
|
+
line.appendChild(retry);
|
|
241
|
+
}
|
|
242
|
+
var cancel = iconButton("x", "Close new session status", "cancel", function () { closeAndFocus(form.folderId); });
|
|
243
|
+
cancel.disabled = pending;
|
|
244
|
+
line.appendChild(cancel);
|
|
245
|
+
root.appendChild(line);
|
|
246
|
+
if (form.wantFocus && !pending && !uncertain) {
|
|
247
|
+
queueMicrotask(function () {
|
|
248
|
+
if (!retry.isConnected) return;
|
|
249
|
+
retry.focus({ preventScroll: true });
|
|
250
|
+
var live = createState();
|
|
251
|
+
if (live) store.set({ sessionCreate: Object.assign({}, live, { wantFocus: false }) });
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
refreshIcons();
|
|
255
|
+
}
|
|
256
|
+
|
|
198
257
|
function closeMenu(refocus) {
|
|
199
258
|
var live = createState();
|
|
200
259
|
if (!live || !live.menuOpen) return;
|
|
@@ -223,10 +282,16 @@ function submitAndClose() {
|
|
|
223
282
|
function paintRow(root) {
|
|
224
283
|
var form = createState();
|
|
225
284
|
if (!form) return;
|
|
285
|
+
if (form.mode === "mate-direct") {
|
|
286
|
+
paintMateDirectRow(root, form);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
226
289
|
var options = createOptions();
|
|
227
290
|
var pending = form.phase === "pending";
|
|
228
291
|
var active = document.activeElement;
|
|
229
292
|
var focusKey = active && root.contains(active) ? active.dataset.focusKey : null;
|
|
293
|
+
var installed = installedVendorIds();
|
|
294
|
+
var valid = options.loaded && installed.indexOf(form.vendor) !== -1;
|
|
230
295
|
// An explicit focus request wins over wherever focus happens to be.
|
|
231
296
|
if (form.wantFocus && root.getClientRects().length) focusKey = form.focusKey || "vendor";
|
|
232
297
|
root.setAttribute("aria-busy", String(pending));
|
|
@@ -234,8 +299,6 @@ function paintRow(root) {
|
|
|
234
299
|
|
|
235
300
|
var line = document.createElement("div");
|
|
236
301
|
line.className = "session-create-line";
|
|
237
|
-
var installed = installedVendorIds();
|
|
238
|
-
|
|
239
302
|
var icon = document.createElement("img");
|
|
240
303
|
icon.className = "session-create-icon";
|
|
241
304
|
icon.alt = "";
|
|
@@ -268,12 +331,21 @@ function paintRow(root) {
|
|
|
268
331
|
});
|
|
269
332
|
line.appendChild(picker);
|
|
270
333
|
|
|
334
|
+
var submit = document.createElement("button");
|
|
335
|
+
submit.type = "button";
|
|
336
|
+
submit.className = "session-create-submit";
|
|
337
|
+
submit.dataset.focusKey = "create";
|
|
338
|
+
submit.textContent = "Create";
|
|
339
|
+
submit.setAttribute("aria-label", "Create " + (root.getAttribute("aria-label") || "new session"));
|
|
340
|
+
submit.disabled = pending || !valid;
|
|
341
|
+
submit.addEventListener("click", function (event) { event.stopPropagation(); submitAndClose(); });
|
|
342
|
+
line.appendChild(submit);
|
|
343
|
+
|
|
271
344
|
var cancel = iconButton("x", "Cancel new session", "cancel", function () { closeAndFocus(form.folderId); });
|
|
272
345
|
cancel.disabled = pending;
|
|
273
346
|
line.appendChild(cancel);
|
|
274
347
|
root.appendChild(line);
|
|
275
348
|
|
|
276
|
-
var valid = options.loaded && installed.indexOf(form.vendor) !== -1;
|
|
277
349
|
var optionsError = options.error || "";
|
|
278
350
|
var message = createStatusMessage(form, options, valid);
|
|
279
351
|
var status = document.createElement("div");
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Every real folder header (custom folders and Unfiled, not the Favorites tag) has a quiet
|
|
4
4
|
// "+ New session" button that opens one draft row directly under that header:
|
|
5
|
-
// provider icon, a compact provider picker and icon Cancel
|
|
6
|
-
// action that opened the draft becomes Create. Only the
|
|
5
|
+
// provider icon, a compact provider picker, Create and icon Cancel. Only the
|
|
7
6
|
// provider is chosen; the model and effort stay whatever that provider's
|
|
8
7
|
// existing defaults produce. The installed providers and the project default
|
|
9
8
|
// come from the server; the saved project default is preselected, and a
|
|
10
9
|
// default changes only through the explicit action beside an available
|
|
11
10
|
// provider. All mutable state lives in the store (sessionCreate,
|
|
12
11
|
// sessionCreateOptions, sessionCreateLocks, sessionCreateReveal); the DOM is
|
|
13
|
-
// built in session-create-form.js.
|
|
12
|
+
// built in session-create-form.js. Provider-mode submission is an explicit
|
|
13
|
+
// Create action inside the draft row; the header only opens or focuses it.
|
|
14
14
|
|
|
15
15
|
import { store } from './store.js';
|
|
16
16
|
import { getWs } from './ws-ref.js';
|
|
@@ -18,6 +18,7 @@ import { showToast } from './utils.js';
|
|
|
18
18
|
import { resolvePreferredVendor } from './default-vendor.js';
|
|
19
19
|
import { sendFolderOp, currentFolderState } from './session-folders.js';
|
|
20
20
|
import { queueSidebarCreationEffect } from './sidebar-creation-effect.js';
|
|
21
|
+
import { isMateWorkspace } from './project-mate-navigation.js';
|
|
21
22
|
|
|
22
23
|
// Ids are unique without any module-level counter.
|
|
23
24
|
function nextId(prefix) {
|
|
@@ -66,13 +67,29 @@ export function openSessionCreate(folderId) {
|
|
|
66
67
|
}
|
|
67
68
|
var options = createOptions();
|
|
68
69
|
store.set({ sessionCreate: {
|
|
69
|
-
folderId: folderId, vendor: options.loaded ? preferredVendor(options) : "", phase: "ready", requestId: null, timer: null, error: "", notice: "",
|
|
70
|
+
mode: "provider", folderId: folderId, vendor: options.loaded ? preferredVendor(options) : "", phase: "ready", requestId: null, timer: null, error: "", notice: "",
|
|
70
71
|
defaultRequestId: null, defaultSaving: false, defaultVendor: null, focusKey: "vendor", wantFocus: false, scroll: true, restoreFocusKey: null,
|
|
71
72
|
openedAt: Date.now(),
|
|
72
73
|
} });
|
|
73
74
|
requestOptions();
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
export function beginSessionCreate(folderId) {
|
|
78
|
+
if (!isMateWorkspace(store.snap())) {
|
|
79
|
+
openSessionCreate(folderId);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
var current = createState();
|
|
83
|
+
if (current && current.phase === "pending") return false;
|
|
84
|
+
if (!current || current.mode !== "mate-direct" || current.folderId !== folderId) {
|
|
85
|
+
store.set({ sessionCreate: {
|
|
86
|
+
mode: "mate-direct", folderId: folderId, phase: "ready", requestId: null, timer: null,
|
|
87
|
+
error: "", notice: "", focusKey: "retry", wantFocus: false, scroll: true, openedAt: Date.now(),
|
|
88
|
+
} });
|
|
89
|
+
}
|
|
90
|
+
return submitMateSessionCreate();
|
|
91
|
+
}
|
|
92
|
+
|
|
76
93
|
export function closeSessionCreate() {
|
|
77
94
|
var current = createState();
|
|
78
95
|
if (!current || current.phase === "pending") return;
|
|
@@ -178,10 +195,35 @@ export function submitSessionCreate(startNewSession) {
|
|
|
178
195
|
return true;
|
|
179
196
|
}
|
|
180
197
|
|
|
198
|
+
export function submitMateSessionCreate() {
|
|
199
|
+
var form = createState();
|
|
200
|
+
if (!form || form.mode !== "mate-direct" || form.phase === "pending" || form.phase === "uncertain") return false;
|
|
201
|
+
var requestId = nextId("scm-");
|
|
202
|
+
var timer = setTimeout(function () { onPendingTimeout(requestId); }, PENDING_TIMEOUT_MS);
|
|
203
|
+
patch({ phase: "pending", requestId: requestId, error: "", notice: "", timer: timer, wantFocus: false });
|
|
204
|
+
if (!send({
|
|
205
|
+
type: "new_session",
|
|
206
|
+
mateDefaults: true,
|
|
207
|
+
forceNew: true,
|
|
208
|
+
requestId: requestId,
|
|
209
|
+
folderSlug: store.get('currentSlug'),
|
|
210
|
+
folderId: form.folderId === "unfiled" ? null : form.folderId,
|
|
211
|
+
})) {
|
|
212
|
+
clearTimeout(timer);
|
|
213
|
+
patch({ phase: "ready", requestId: null, timer: null, error: "Not connected. Reconnect and try again.", focusKey: "retry", wantFocus: true });
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
181
219
|
function onPendingTimeout(requestId) {
|
|
182
220
|
var form = createState();
|
|
183
221
|
if (!form || form.requestId !== requestId || form.phase !== "pending") return;
|
|
184
222
|
forgetLock(requestId);
|
|
223
|
+
if (form.mode === "mate-direct") {
|
|
224
|
+
patch({ phase: "uncertain", timer: null, error: "No response yet. The session may already have been created; check the list before trying again." });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
185
227
|
patch({ phase: "ready", requestId: null, timer: null, error: "No response yet. The session may already have been created; check the list first." });
|
|
186
228
|
}
|
|
187
229
|
|
|
@@ -200,7 +242,7 @@ export function handleNewSessionResult(msg) {
|
|
|
200
242
|
clearTimeout(form.timer);
|
|
201
243
|
if (!msg.ok) {
|
|
202
244
|
restoreLock(msg.requestId);
|
|
203
|
-
patch({ phase: "ready", requestId: null, timer: null, error: msg.error || "The session could not be created.", focusKey: "create", wantFocus: true });
|
|
245
|
+
patch({ phase: "ready", requestId: null, timer: null, error: msg.error || "The session could not be created.", focusKey: form.mode === "mate-direct" ? "retry" : "create", wantFocus: true });
|
|
204
246
|
return true;
|
|
205
247
|
}
|
|
206
248
|
forgetLock(msg.requestId);
|
|
@@ -300,7 +342,8 @@ function onDisconnect(form) {
|
|
|
300
342
|
if (!form) return;
|
|
301
343
|
if (form.timer) clearTimeout(form.timer);
|
|
302
344
|
if (form.phase === "pending") {
|
|
303
|
-
patch({ phase: "
|
|
345
|
+
if (form.mode === "mate-direct") patch({ phase: "uncertain", timer: null, error: "Connection lost. The session may already have been created; check the list before trying again." });
|
|
346
|
+
else patch({ phase: "ready", requestId: null, timer: null, error: "Connection lost. The session may already have been created; check the list first." });
|
|
304
347
|
}
|
|
305
348
|
if (form.defaultSaving) patch({ defaultSaving: false, defaultRequestId: null, defaultVendor: null, error: "Connection lost before the project default was confirmed." });
|
|
306
349
|
}
|
|
@@ -319,7 +362,7 @@ store.subscribe(function (state, previous) {
|
|
|
319
362
|
var form = state.sessionCreate;
|
|
320
363
|
if (previous.connected && !state.connected) onDisconnect(form);
|
|
321
364
|
// The providers are requested again after a reconnect.
|
|
322
|
-
if (!previous.connected && state.connected && form) requestOptions();
|
|
365
|
+
if (!previous.connected && state.connected && form && form.mode !== "mate-direct") requestOptions();
|
|
323
366
|
// Folder removed while its row was open: say so instead of letting a later
|
|
324
367
|
// create fail, and never silently file the session elsewhere.
|
|
325
368
|
if (form && state.sessionFolders !== previous.sessionFolders && form.folderId !== "unfiled") {
|
|
@@ -76,10 +76,11 @@ export function appendGithubWork(row, session, title) {
|
|
|
76
76
|
row.insertBefore(column, title);
|
|
77
77
|
column.appendChild(title);
|
|
78
78
|
var links = document.createElement('span');
|
|
79
|
+
links.className = 'session-work-links-slot';
|
|
79
80
|
links.innerHTML = html;
|
|
80
81
|
links.addEventListener('click', function (event) { if (event.target.closest('a')) event.stopPropagation(); });
|
|
81
82
|
links.addEventListener('contextmenu', function (event) { event.stopPropagation(); });
|
|
82
|
-
|
|
83
|
+
row.appendChild(links);
|
|
83
84
|
}
|
|
84
85
|
export function syncGithubSessions(sessions) {
|
|
85
86
|
bindGithubWorkPopover();
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Visibility policy for the ordinary desktop and mobile session lists.
|
|
2
|
+
// Debate sessions remain in the session cache for the Debate workbench and
|
|
3
|
+
// exact-session navigation; only their ordinary-list presentation is removed.
|
|
4
|
+
|
|
5
|
+
export function isDebateOwnedSession(session) {
|
|
6
|
+
var phase = session && session.homeDebatePhase;
|
|
7
|
+
return !!(session && (
|
|
8
|
+
session.homeDebatePlanning === true
|
|
9
|
+
|| phase === "planning"
|
|
10
|
+
|| phase === "live"
|
|
11
|
+
|| phase === "ended"
|
|
12
|
+
|| phase === "interrupted"
|
|
13
|
+
|| (session.loop && session.loop.source === "debate")
|
|
14
|
+
));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ordinarySessionListSessions(sessions) {
|
|
18
|
+
var source = Array.isArray(sessions) ? sessions : [];
|
|
19
|
+
var hiddenIds = new Set();
|
|
20
|
+
for (var i = 0; i < source.length; i++) {
|
|
21
|
+
if (isDebateOwnedSession(source[i])) hiddenIds.add(source[i].id);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// A debate Driver owns its Worker rows even if an older Worker payload did
|
|
25
|
+
// not copy the debate marker. Follow parent links so those children cannot
|
|
26
|
+
// leak into the ordinary list as orphan Workers.
|
|
27
|
+
var changed = true;
|
|
28
|
+
while (changed) {
|
|
29
|
+
changed = false;
|
|
30
|
+
for (var j = 0; j < source.length; j++) {
|
|
31
|
+
var session = source[j];
|
|
32
|
+
if (hiddenIds.has(session.id) || !hiddenIds.has(session.parentSessionId)) continue;
|
|
33
|
+
hiddenIds.add(session.id);
|
|
34
|
+
changed = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return source.filter(function (session) {
|
|
39
|
+
return !hiddenIds.has(session.id);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -39,6 +39,9 @@ export function renderSessionPresence(row, sessionId) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
var anchor = row.querySelector(".session-row-trailing, .session-item-age, .mobile-worker-generation, .mobile-session-unread, .session-row-actions, .mobile-session-star");
|
|
42
|
+
// Desktop age/actions now live inside one trailing slot. insertBefore only
|
|
43
|
+
// accepts a direct child, so promote a nested match to its row-level owner.
|
|
44
|
+
while (anchor && anchor.parentNode !== row) anchor = anchor.parentNode;
|
|
42
45
|
row.insertBefore(container, anchor || null);
|
|
43
46
|
return container;
|
|
44
47
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
function resetMarquee(state) {
|
|
2
|
+
if (state.frame) cancelAnimationFrame(state.frame);
|
|
3
|
+
state.frame = null;
|
|
4
|
+
state.row.classList.remove("session-title-marquee");
|
|
5
|
+
state.title.style.removeProperty("--session-marquee-distance");
|
|
6
|
+
state.title.style.removeProperty("--session-marquee-duration");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function measureMarquee(state) {
|
|
10
|
+
state.frame = null;
|
|
11
|
+
if (!state.active || !state.row.isConnected) return;
|
|
12
|
+
var titleRect = state.title.getBoundingClientRect();
|
|
13
|
+
var actionRect = state.actions ? state.actions.getBoundingClientRect() : null;
|
|
14
|
+
var rowRect = state.row.getBoundingClientRect();
|
|
15
|
+
var right = actionRect && actionRect.width ? actionRect.left - 5 : rowRect.right - 8;
|
|
16
|
+
var available = Math.max(0, right - titleRect.left);
|
|
17
|
+
var overflow = state.title.scrollWidth - available;
|
|
18
|
+
if (overflow <= 4) return;
|
|
19
|
+
state.title.style.setProperty("--session-marquee-distance", (-overflow) + "px");
|
|
20
|
+
state.title.style.setProperty("--session-marquee-duration", Math.max(5, overflow / 22) + "s");
|
|
21
|
+
state.row.classList.add("session-title-marquee");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function activate(state) {
|
|
25
|
+
state.active = true;
|
|
26
|
+
resetMarquee(state);
|
|
27
|
+
state.active = true;
|
|
28
|
+
state.frame = requestAnimationFrame(function () { measureMarquee(state); });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function bindSessionTitleMarquee(row, title, actions) {
|
|
32
|
+
if (!row || !title) return;
|
|
33
|
+
var state = { row: row, title: title, actions: actions, active: false, frame: null };
|
|
34
|
+
function leave() { state.active = false; resetMarquee(state); }
|
|
35
|
+
function enter() { activate(state); }
|
|
36
|
+
row.addEventListener("mouseenter", enter);
|
|
37
|
+
row.addEventListener("focusin", enter);
|
|
38
|
+
row.addEventListener("mouseleave", leave);
|
|
39
|
+
row.addEventListener("focusout", function (event) {
|
|
40
|
+
if (!row.contains(event.relatedTarget)) leave();
|
|
41
|
+
});
|
|
42
|
+
window.addEventListener("resize", leave);
|
|
43
|
+
var observer = typeof MutationObserver === "undefined" ? null : new MutationObserver(function () {
|
|
44
|
+
if (!row.isConnected) {
|
|
45
|
+
leave();
|
|
46
|
+
observer.disconnect();
|
|
47
|
+
window.removeEventListener("resize", leave);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
if (observer && document.body) observer.observe(document.body, { childList: true, subtree: true });
|
|
51
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Check if text looks like env format: first line starts with a valid VAR_NAME=
|
|
2
|
+
export function looksLikeEnv(text) {
|
|
3
|
+
var first = text.split("\n")[0].trim();
|
|
4
|
+
if (first.indexOf("export ") === 0) first = first.substring(7);
|
|
5
|
+
return /^[A-Za-z_][A-Za-z0-9_]*=/.test(first);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function parseEnvString(str) {
|
|
9
|
+
var pairs = [];
|
|
10
|
+
if (!str) return pairs;
|
|
11
|
+
var lines = str.split("\n");
|
|
12
|
+
for (var i = 0; i < lines.length; i++) {
|
|
13
|
+
var line = lines[i].trim();
|
|
14
|
+
if (!line || line.charAt(0) === "#") continue;
|
|
15
|
+
// Strip leading "export "
|
|
16
|
+
if (line.indexOf("export ") === 0) line = line.substring(7);
|
|
17
|
+
var eq = line.indexOf("=");
|
|
18
|
+
if (eq === -1) continue;
|
|
19
|
+
var key = line.substring(0, eq).trim();
|
|
20
|
+
var val = line.substring(eq + 1).trim();
|
|
21
|
+
// Saved values are JSON-quoted so shell metacharacters remain data.
|
|
22
|
+
if (val.charAt(0) === '"' && val.charAt(val.length - 1) === '"') {
|
|
23
|
+
try { val = JSON.parse(val); } catch (e) { val = val.substring(1, val.length - 1); }
|
|
24
|
+
} else if (val.charAt(0) === "'" && val.charAt(val.length - 1) === "'") {
|
|
25
|
+
val = val.substring(1, val.length - 1);
|
|
26
|
+
}
|
|
27
|
+
if (key) pairs.push({ key: key, value: val });
|
|
28
|
+
}
|
|
29
|
+
return pairs;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function serializeEnvValue(value) {
|
|
33
|
+
return JSON.stringify(value == null ? "" : String(value));
|
|
34
|
+
}
|