clay-server 5.5.0-beta.3 → 5.5.0-beta.5
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/lib/github-connection-store.js +39 -0
- package/lib/github-runtime.js +43 -0
- package/lib/project-github-connection.js +164 -0
- package/lib/project.js +8 -0
- package/lib/public/appearance-bootstrap.js +6 -0
- package/lib/public/css/chrome-material.css +133 -0
- package/lib/public/css/menus.css +1 -17
- package/lib/public/css/navigation-island.css +261 -0
- package/lib/public/css/notifications-center.css +1 -17
- package/lib/public/css/settings-shell.css +0 -1
- package/lib/public/css/settings-ui.css +11 -2
- package/lib/public/css/shared-browser.css +1 -18
- package/lib/public/css/split-view-layout.css +9 -5
- package/lib/public/css/sticky-notes.css +1 -18
- package/lib/public/css/title-bar.css +1 -1
- package/lib/public/css/workspace-background-gallery.css +43 -0
- package/lib/public/css/you.css +13 -10
- package/lib/public/index.html +53 -46
- package/lib/public/modules/agent-behavior-nav.js +1 -0
- package/lib/public/modules/app-messages.js +5 -0
- package/lib/public/modules/appearance-preferences.js +81 -0
- package/lib/public/modules/context-sources.js +2 -282
- package/lib/public/modules/github-mark.js +2 -0
- package/lib/public/modules/pane-appearance-bridge.js +9 -1
- package/lib/public/modules/pane-header-tooltips.js +36 -9
- package/lib/public/modules/personal-email-editor.js +305 -0
- package/lib/public/modules/personal-integration-editor.js +70 -0
- package/lib/public/modules/personal-settings-dialog.js +15 -1
- package/lib/public/modules/session-list-material.js +25 -0
- package/lib/public/modules/settings-section-nav.js +2 -2
- package/lib/public/modules/settings-shell-registry.js +6 -5
- package/lib/public/modules/sidebar-collapse-focus.js +22 -0
- package/lib/public/modules/sidebar-sessions.js +2 -0
- package/lib/public/modules/sidebar.js +9 -10
- package/lib/public/modules/split-pane-renderer.js +5 -5
- package/lib/public/modules/split-view-shell-controls.js +9 -5
- package/lib/public/modules/split-view.js +2 -2
- package/lib/public/modules/theme.js +10 -28
- package/lib/public/modules/tool-pane-chrome.js +4 -1
- package/lib/public/modules/tool-pane-material.js +23 -0
- package/lib/public/modules/tooltip.js +7 -3
- package/lib/public/modules/tui-attention.js +9 -4
- package/lib/public/modules/user-settings-account.js +10 -1
- package/lib/public/modules/user-settings-github.js +176 -0
- package/lib/public/modules/user-settings-integrations.js +23 -12
- package/lib/public/modules/user-settings-layout.js +21 -18
- package/lib/public/modules/user-settings-themes.js +6 -2
- package/lib/public/modules/user-settings.js +2 -1
- package/lib/public/modules/workspace-background-gallery.js +92 -0
- package/lib/public/modules/workspace-custom-colors.js +132 -0
- package/lib/public/modules/you-activity.js +4 -2
- package/lib/public/pane-assets/app-C27ONMHZ.js +1 -0
- package/lib/public/pane-assets/chunk-4REWYEXI.js +1 -0
- package/lib/public/pane-assets/chunk-6EDR4PMU.js +1 -0
- package/lib/public/pane-assets/{chunk-4QFNLHW5.js → chunk-76JE6SO6.js} +1 -1
- package/lib/public/pane-assets/chunk-AFATDM32.js +1 -0
- package/lib/public/pane-assets/{chunk-P3AK4ORJ.js → chunk-DTNAWMGQ.js} +1 -1
- package/lib/public/pane-assets/chunk-EGCR2MH4.js +1 -0
- package/lib/public/pane-assets/chunk-ET4NUBYW.js +1 -0
- package/lib/public/pane-assets/chunk-EYJZ3R7Y.js +1 -0
- package/lib/public/pane-assets/{chunk-CXWAJEN5.js → chunk-I5Q4CZDJ.js} +1 -1
- package/lib/public/pane-assets/{chunk-JM46WGDH.js → chunk-KJN6KGEN.js} +1 -1
- package/lib/public/pane-assets/chunk-KZ3CYS5U.js +1 -0
- package/lib/public/pane-assets/chunk-MR4FHQFA.js +1 -0
- package/lib/public/pane-assets/chunk-NRC5RDIL.js +1 -0
- package/lib/public/pane-assets/chunk-NRP4NXVZ.js +1 -0
- package/lib/public/pane-assets/chunk-OERDYIHU.js +1 -0
- package/lib/public/pane-assets/{chunk-V5DLPSLT.js → chunk-OGIHAXLD.js} +1 -1
- package/lib/public/pane-assets/chunk-OQBKX36J.js +2 -0
- package/lib/public/pane-assets/chunk-Q2HJ5CWT.js +1 -0
- package/lib/public/pane-assets/{chunk-3EJ5IAHZ.js → chunk-QOL6PHMZ.js} +1 -1
- package/lib/public/pane-assets/{chunk-32YIYF7R.js → chunk-TVKTFEST.js} +1 -1
- package/lib/public/pane-assets/{chunk-XSSTR6GC.js → chunk-UMXZTYOY.js} +1 -1
- package/lib/public/pane-assets/chunk-W5MFUKXJ.js +1 -0
- package/lib/public/pane-assets/chunk-W7XXER5Z.js +174 -0
- package/lib/public/pane-assets/chunk-Y77DR2RU.js +1 -0
- package/lib/public/pane-assets/{chunk-XVESKMU3.js → chunk-YBYNU4VI.js} +1 -1
- package/lib/public/pane-assets/{chunk-JIV7O5GN.js → chunk-YHBTK56T.js} +1 -1
- package/lib/public/pane-assets/manifest.json +404 -390
- package/lib/public/pane-assets/style-F4MQRRX2.css +1 -0
- package/lib/public/style.css +4 -0
- package/lib/public/workspace-art/workshop-dark.webp +0 -0
- package/lib/public/workspace-art/workshop-light.webp +0 -0
- package/lib/public/workspace-background-catalog.js +40 -0
- package/lib/server-appearance-settings.js +16 -5
- package/lib/server.js +6 -0
- package/lib/session-github.js +1 -7
- package/lib/terminal-manager.js +5 -1
- package/lib/terminal.js +10 -1
- package/package.json +1 -1
- package/lib/public/pane-assets/app-ANJS2LSK.js +0 -1
- package/lib/public/pane-assets/chunk-3TIVBAKM.js +0 -1
- package/lib/public/pane-assets/chunk-5XMZMIFG.js +0 -1
- package/lib/public/pane-assets/chunk-6QFLYOVY.js +0 -1
- package/lib/public/pane-assets/chunk-6S2C7Q2N.js +0 -1
- package/lib/public/pane-assets/chunk-7B2GJI57.js +0 -1
- package/lib/public/pane-assets/chunk-BOFNCPTN.js +0 -1
- package/lib/public/pane-assets/chunk-EWTXBZJO.js +0 -2
- package/lib/public/pane-assets/chunk-G35ZOJGP.js +0 -1
- package/lib/public/pane-assets/chunk-HYT24UBT.js +0 -1
- package/lib/public/pane-assets/chunk-ISQEPZMY.js +0 -1
- package/lib/public/pane-assets/chunk-LBJG4MK2.js +0 -174
- package/lib/public/pane-assets/chunk-QKPGE7DN.js +0 -1
- package/lib/public/pane-assets/chunk-TS5JUEVH.js +0 -1
- package/lib/public/pane-assets/chunk-UGH5MCNK.js +0 -1
- package/lib/public/pane-assets/chunk-VFKDU64W.js +0 -1
- package/lib/public/pane-assets/chunk-VZ2OKDAO.js +0 -1
- package/lib/public/pane-assets/style-VBVCWV44.css +0 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Metadata only. GitHub CLI owns credentials; Clay Studio never reads its stores.
|
|
2
|
+
var fs = require('fs'), path = require('path'), crypto = require('crypto');
|
|
3
|
+
function attach(root) {
|
|
4
|
+
var directory = path.join(root, 'github-connections');
|
|
5
|
+
function file(owner) {
|
|
6
|
+
if (typeof owner !== 'string' || !/^[a-zA-Z0-9_-]{1,128}$/.test(owner)) throw new Error('Invalid connection owner.');
|
|
7
|
+
return path.join(directory, owner + '.json');
|
|
8
|
+
}
|
|
9
|
+
function read(owner) {
|
|
10
|
+
try { return JSON.parse(fs.readFileSync(file(owner), 'utf8')); }
|
|
11
|
+
catch (error) { if (error.code === 'ENOENT') return { revision: null, connection: null }; throw new Error('Could not read GitHub connection.'); }
|
|
12
|
+
}
|
|
13
|
+
function reserve(key) {
|
|
14
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
15
|
+
var target = path.join(directory, 'runtime-' + crypto.createHash('sha256').update(key).digest('hex') + '.lock');
|
|
16
|
+
var fd;
|
|
17
|
+
try { fd = fs.openSync(target, 'wx', 0o600); fs.writeFileSync(fd, JSON.stringify({ startedAt: Date.now(), pid: process.pid })); }
|
|
18
|
+
catch (error) { throw new Error('A GitHub login is already running or its outcome needs operator inspection. No login was retried.'); }
|
|
19
|
+
finally { if (fd !== undefined) fs.closeSync(fd); }
|
|
20
|
+
return function () { fs.unlinkSync(target); };
|
|
21
|
+
}
|
|
22
|
+
function save(owner, revision, connection) {
|
|
23
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
24
|
+
var target = file(owner), fd, temp;
|
|
25
|
+
try {
|
|
26
|
+
fd = fs.openSync(target + '.lock', 'wx', 0o600);
|
|
27
|
+
if (read(owner).revision !== revision) throw new Error('GitHub connection changed. Refresh before confirming again.');
|
|
28
|
+
var next = { revision: crypto.randomUUID(), connection: connection };
|
|
29
|
+
temp = target + '.' + crypto.randomUUID() + '.tmp';
|
|
30
|
+
fs.writeFileSync(temp, JSON.stringify(next), { mode: 0o600 }); fs.renameSync(temp, target);
|
|
31
|
+
return next;
|
|
32
|
+
} finally {
|
|
33
|
+
if (temp && fs.existsSync(temp)) fs.unlinkSync(temp);
|
|
34
|
+
if (fd !== undefined) { fs.closeSync(fd); fs.unlinkSync(target + '.lock'); }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { read: read, save: save, reserve: reserve };
|
|
38
|
+
}
|
|
39
|
+
module.exports = { attach: attach };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Shared GitHub credential context for Personal settings and session work.
|
|
2
|
+
var crypto = require('crypto');
|
|
3
|
+
function environment(identity) {
|
|
4
|
+
var env = Object.assign({}, identity ? require('./build-user-env').buildUserEnv(identity) : process.env, { GH_PROMPT_DISABLED: '1', GH_HOST: 'github.com' });
|
|
5
|
+
if (identity) {
|
|
6
|
+
['GH_TOKEN', 'GITHUB_TOKEN', 'GH_ENTERPRISE_TOKEN', 'GITHUB_ENTERPRISE_TOKEN', 'GH_CONFIG_DIR', 'XDG_CONFIG_HOME'].forEach(function (key) { delete env[key]; });
|
|
7
|
+
env.HOME = identity.home; env.USER = identity.user; env.LOGNAME = identity.user;
|
|
8
|
+
}
|
|
9
|
+
return env;
|
|
10
|
+
}
|
|
11
|
+
function binding(identity) {
|
|
12
|
+
var env = environment(identity);
|
|
13
|
+
return crypto.createHash('sha256').update(JSON.stringify([identity || null, env.HOME, env.PATH, env.GH_CONFIG_DIR, env.XDG_CONFIG_HOME, env.GH_TOKEN, env.GITHUB_TOKEN])).digest('hex');
|
|
14
|
+
}
|
|
15
|
+
function overridden(identity) { var env = environment(identity); return !!(env.GH_TOKEN || env.GITHUB_TOKEN); }
|
|
16
|
+
function run(identity, args) {
|
|
17
|
+
return new Promise(function (resolve, reject) {
|
|
18
|
+
var opts = { cwd: environment(identity).HOME, env: environment(identity), timeout: 15000, maxBuffer: 16384, encoding: 'utf8' };
|
|
19
|
+
if (identity) { opts.uid = identity.uid; opts.gid = identity.gid; }
|
|
20
|
+
var wrapped = require('./os-users').wrapSpawnAsUser('gh', args, opts);
|
|
21
|
+
var child = require('child_process').execFile(wrapped.command, wrapped.args, wrapped.options, function (error, stdout) {
|
|
22
|
+
if (!error) return resolve(stdout);
|
|
23
|
+
var failure = new Error('GitHub verification is unavailable.');
|
|
24
|
+
failure.kind = error.code === 'ENOENT' || error.code === 127 && args[0] === '--version' ? 'missing' : error.code === 4 ? 'unauthenticated' : 'unknown';
|
|
25
|
+
try { if (Number(JSON.parse(stdout).status) === 401) failure.kind = 'unauthenticated'; } catch (ignored) {}
|
|
26
|
+
reject(failure);
|
|
27
|
+
});
|
|
28
|
+
child.stdin.on('error', function () {}); child.stdin.end();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async function inspect(identity, execute) {
|
|
32
|
+
execute = execute || run;
|
|
33
|
+
var result = { status: 'unknown', overridden: overridden(identity), checkedAt: Date.now(), account: null };
|
|
34
|
+
try { await execute(identity, ['--version']); }
|
|
35
|
+
catch (error) { result.status = error.kind === 'missing' ? 'missing' : 'unknown'; return result; }
|
|
36
|
+
try {
|
|
37
|
+
var data = JSON.parse(await execute(identity, ['api', '--hostname', 'github.com', 'user', '--jq', '{id: .id, login: .login}']));
|
|
38
|
+
if (!data || !Number.isSafeInteger(data.id) || data.id <= 0 || typeof data.login !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9-]{0,38}$/.test(data.login)) throw new Error('Invalid identity.');
|
|
39
|
+
result.account = { id: data.id, login: data.login, host: 'github.com' }; result.status = 'authenticated';
|
|
40
|
+
} catch (error) { result.status = error.kind === 'unauthenticated' ? 'unauthenticated' : 'unknown'; }
|
|
41
|
+
result.checkedAt = Date.now(); return result;
|
|
42
|
+
}
|
|
43
|
+
module.exports = { environment: environment, binding: binding, overridden: overridden, inspect: inspect };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// Human-only Personal Integrations flow, bound to live account and runtime identity.
|
|
2
|
+
var crypto = require('crypto'), os = require('os');
|
|
3
|
+
var runtime = require('./github-runtime');
|
|
4
|
+
var LOGIN_ARGS = ['auth', 'login', '--hostname', 'github.com', '--web', '--git-protocol', 'https', '--skip-ssh-key'];
|
|
5
|
+
function attach(ctx) {
|
|
6
|
+
var drafts = new Map(), flows = new Map(), watched = new WeakSet();
|
|
7
|
+
function actor(ws) {
|
|
8
|
+
if (ws && ws._clayAuthCheck && !ws._clayAuthCheck()) throw new Error('Sign in again to manage GitHub.');
|
|
9
|
+
if (!ws || ws.readyState !== 1 || !ctx.clients.has(ws) || ws._clayPane || ws._clayPaneActivation || !ctx.requestAccess.canAccessProject(ws, ctx.slug)) throw new Error('Reopen Personal settings in your connected workspace.');
|
|
10
|
+
var user = ws._clayUser && ctx.usersModule.findUserById(ws._clayUser.id);
|
|
11
|
+
if (ctx.usersModule.isMultiUser() && !user || ws._clayUser && !user) throw new Error('This account is no longer available.');
|
|
12
|
+
if (!ctx.requestAccess.hasPermission(ws, 'terminal')) throw new Error('Terminal permission is required to inspect or connect GitHub.');
|
|
13
|
+
var identity = ctx.osUsers ? user && user.linuxUser && ctx.resolveOsUserInfo(user.linuxUser) : null;
|
|
14
|
+
if (ctx.osUsers && (!identity || !identity.user || !identity.home || !Number.isInteger(identity.uid) || !Number.isInteger(identity.gid))) throw new Error('Your mapped OS account is unavailable.');
|
|
15
|
+
var info = os.userInfo(), env = runtime.environment(identity);
|
|
16
|
+
return { owner: user ? String(user.id) : 'default', user: user ? JSON.stringify(user) : null, identity: identity,
|
|
17
|
+
shared: !identity, canLogin: !!identity || !user || user.role === 'admin',
|
|
18
|
+
runtime: identity || { user: info.username, uid: info.uid, gid: info.gid, home: env.HOME }, binding: runtime.binding(identity) };
|
|
19
|
+
}
|
|
20
|
+
function valid(ws, item) {
|
|
21
|
+
if (!item) return false;
|
|
22
|
+
try { return drafts.get(ws) === item && JSON.stringify(actor(ws)) === item.signature && (!item.expires || Date.now() < item.expires); } catch (error) { return false; }
|
|
23
|
+
}
|
|
24
|
+
function send(ws, request, value) {
|
|
25
|
+
if (ws.readyState === 1) ctx.sendTo(ws, Object.assign({type:'personal_github', requestId:request.requestId, contextId:request.contextId}, value));
|
|
26
|
+
}
|
|
27
|
+
function requireCurrent(ws, item) { if (!valid(ws, item)) throw new Error('GitHub account context changed. Refresh before continuing.'); }
|
|
28
|
+
function snapshot(item, state, extra) {
|
|
29
|
+
return Object.assign({state:state, saved:ctx.storage.read(item.actor.owner), observed:item.observed || null,
|
|
30
|
+
runtime:Object.assign({shared:item.actor.shared},item.actor.runtime), canLogin:item.actor.canLogin && !(item.observed && item.observed.overridden), reviewId:item.reviewId},extra || {});
|
|
31
|
+
}
|
|
32
|
+
function releaseLock(item) { if (item.unlock) { item.unlock(); item.unlock = null; } }
|
|
33
|
+
async function stop(ws, item) {
|
|
34
|
+
if (item.stopping) return;
|
|
35
|
+
item.stopping = true; item.reviewId = null;
|
|
36
|
+
send(ws,item.request,{state:'stopping',message:'Waiting for the GitHub login terminal to exit…'});
|
|
37
|
+
clearInterval(item.timer);
|
|
38
|
+
try {
|
|
39
|
+
if (!item.terminal) throw new Error('Launch outcome is unknown.');
|
|
40
|
+
await ctx.tm.closeAndWait(item.terminal.id,{identity:item.terminal.identity,revalidate:function () {
|
|
41
|
+
if (flows.get(ws) !== item) throw new Error('The login terminal changed.');
|
|
42
|
+
}});
|
|
43
|
+
flows.delete(ws); releaseLock(item); drafts.delete(ws);
|
|
44
|
+
send(ws,item.request,{state:'stopped',message:'Login terminal exited. GitHub CLI credentials may have changed; refresh before another explicit attempt. Your saved Clay Studio connection was retained.'});
|
|
45
|
+
} catch (error) {
|
|
46
|
+
send(ws,item.request,{state:'unknown',message:'Terminal shutdown could not be confirmed. Ask the server operator to inspect this login before retrying. No attempt will replay automatically.'});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function release(ws) {
|
|
50
|
+
var flow = flows.get(ws);
|
|
51
|
+
if (flow) { drafts.delete(ws); stop(ws,flow); }
|
|
52
|
+
else drafts.delete(ws);
|
|
53
|
+
}
|
|
54
|
+
function sameAccount(left,right) { return !!left && !!right && left.id === right.id && left.login === right.login && left.host === right.host; }
|
|
55
|
+
async function inspect(ws,item) {
|
|
56
|
+
var observed = await runtime.inspect(item.actor.identity,ctx.execute);
|
|
57
|
+
requireCurrent(ws,item); item.observed = observed;
|
|
58
|
+
item.reviewId = crypto.randomUUID(); item.expires = Date.now()+120000;
|
|
59
|
+
item.saved = ctx.storage.read(item.actor.owner);
|
|
60
|
+
send(ws,item.request,snapshot(item,'ready'));
|
|
61
|
+
}
|
|
62
|
+
async function run(ws,msg) {
|
|
63
|
+
if (msg.type === 'personal_github_release' || msg.type === 'personal_github_stop') {
|
|
64
|
+
var active = drafts.get(ws) || flows.get(ws);
|
|
65
|
+
if (active && active.request.contextId === msg.contextId) release(ws);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (msg.type === 'personal_github_get') {
|
|
69
|
+
if (flows.has(ws)) throw new Error('Finish or stop the current GitHub login before refreshing.');
|
|
70
|
+
var target = actor(ws);
|
|
71
|
+
var item = {actor:target,signature:JSON.stringify(target),request:msg,saved:ctx.storage.read(target.owner)};
|
|
72
|
+
drafts.set(ws,item);
|
|
73
|
+
if (!watched.has(ws)) { watched.add(ws); ws.once('close',function () { watched.delete(ws); release(ws); }); }
|
|
74
|
+
// Publish the saved record independently of a possibly unavailable network check.
|
|
75
|
+
send(ws,msg,snapshot(item,'checking'));
|
|
76
|
+
await inspect(ws,item); return;
|
|
77
|
+
}
|
|
78
|
+
var item = drafts.get(ws);
|
|
79
|
+
requireCurrent(ws,item);
|
|
80
|
+
if (!item.reviewId || item.reviewId !== msg.reviewId || item.busy) throw new Error('This GitHub review expired or was already used. Refresh status.');
|
|
81
|
+
if (item.request.contextId !== msg.contextId) throw new Error('GitHub context changed.');
|
|
82
|
+
item.request = msg;
|
|
83
|
+
item.busy = true;
|
|
84
|
+
try {
|
|
85
|
+
if (msg.type === 'personal_github_disconnect') {
|
|
86
|
+
if (!item.saved.connection) throw new Error('There is no saved GitHub connection.');
|
|
87
|
+
requireCurrent(ws,item);
|
|
88
|
+
var removed = ctx.storage.save(item.actor.owner,item.saved.revision,null);
|
|
89
|
+
item.saved = removed; item.reviewId = null;
|
|
90
|
+
send(ws,msg,snapshot(item,'saved',{message:'Clay Studio association removed. GitHub CLI credentials and existing CLI-backed tools are retained.'})); return;
|
|
91
|
+
}
|
|
92
|
+
if (msg.type !== 'personal_github_connect' && msg.type !== 'personal_github_login') throw new Error('Unsupported GitHub connection operation.');
|
|
93
|
+
var fresh = await runtime.inspect(item.actor.identity,ctx.execute);
|
|
94
|
+
requireCurrent(ws,item);
|
|
95
|
+
if (ctx.storage.read(item.actor.owner).revision !== item.saved.revision) throw new Error('Saved connection changed. Refresh before confirming again.');
|
|
96
|
+
if (fresh.status !== item.observed.status || fresh.overridden !== item.observed.overridden || fresh.status === 'authenticated' && !sameAccount(fresh.account,item.observed.account)) throw new Error('The GitHub CLI account changed. Refresh and confirm the exact account again.');
|
|
97
|
+
if (msg.type === 'personal_github_connect') {
|
|
98
|
+
if (fresh.status !== 'authenticated') throw new Error('Verify an authenticated GitHub account before connecting.');
|
|
99
|
+
requireCurrent(ws,item);
|
|
100
|
+
var saved = ctx.storage.save(item.actor.owner,item.saved.revision,{account:fresh.account,runtime:item.actor.runtime,shared:item.actor.shared,checkedAt:fresh.checkedAt,connectedAt:Date.now()});
|
|
101
|
+
item.saved = saved; item.observed = fresh; item.reviewId = null;
|
|
102
|
+
send(ws,msg,snapshot(item,'saved',{message:'GitHub association saved for the verified CLI account.'})); return;
|
|
103
|
+
}
|
|
104
|
+
if (!item.actor.canLogin || fresh.overridden) throw new Error('Browser login cannot replace environment credentials. Only an administrator may replace shared runtime credentials.');
|
|
105
|
+
if (fresh.status !== 'authenticated' && fresh.status !== 'unauthenticated') throw new Error('Authentication is unknown. Resolve the CLI installation or network problem before starting login.');
|
|
106
|
+
item.reviewId = null;
|
|
107
|
+
item.unlock = ctx.storage.reserve(JSON.stringify([item.actor.runtime.uid,item.actor.runtime.gid,item.actor.runtime.home]));
|
|
108
|
+
item.attachKey = crypto.randomUUID(); item.expires = null; flows.set(ws,item);
|
|
109
|
+
try {
|
|
110
|
+
item.terminal = ctx.tm.create(100,24,item.actor.identity,ws,{kind:'github-login',title:'GitHub sign in',githubLogin:true,
|
|
111
|
+
revalidateGithub:function () { requireCurrent(ws,item); },
|
|
112
|
+
onExit:function (terminal) {
|
|
113
|
+
if (item.stopping) return;
|
|
114
|
+
clearInterval(item.timer);
|
|
115
|
+
// Defer until create() has assigned the exact terminal identity.
|
|
116
|
+
Promise.resolve().then(async function () {
|
|
117
|
+
if (flows.get(ws) !== item) return;
|
|
118
|
+
try {
|
|
119
|
+
await ctx.tm.closeAndWait(terminal.id,{identity:terminal.identity,revalidate:function () { if (flows.get(ws) !== item) throw new Error('Login changed.'); }});
|
|
120
|
+
flows.delete(ws); releaseLock(item);
|
|
121
|
+
requireCurrent(ws,item);
|
|
122
|
+
send(ws,item.request,{state:'verifying',message:'Login terminal exited. Checking the actual GitHub API identity…'});
|
|
123
|
+
await inspect(ws,item);
|
|
124
|
+
} catch (error) { send(ws,item.request,{state:'unknown',message:'Login ended, but verification or cleanup could not be confirmed. Refresh status before explicitly continuing.'}); }
|
|
125
|
+
});
|
|
126
|
+
}});
|
|
127
|
+
if (!item.terminal) { flows.delete(ws); releaseLock(item); throw new Error('The GitHub login terminal is unavailable.'); }
|
|
128
|
+
item.timer = setInterval(function () { if (!valid(ws,item)) stop(ws,item); },250); item.timer.unref();
|
|
129
|
+
send(ws,item.request,snapshot(item,'started',{terminalId:item.terminal.id,attachKey:item.attachKey,slug:ctx.slug,message:'Use the code and github.com device page shown in the terminal. You may approve on another device. Stop waits for terminal exit; it cannot undo credentials already saved by gh.'}));
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (flows.has(ws)) send(ws,item.request,{state:'unknown',message:'Login launch outcome is uncertain. Ask the server operator to inspect it before retrying.'});
|
|
132
|
+
else throw error;
|
|
133
|
+
}
|
|
134
|
+
} finally { item.busy = false; }
|
|
135
|
+
}
|
|
136
|
+
function terminalMessage(ws,msg) {
|
|
137
|
+
var terminal = msg.id && ctx.tm.inspect(msg.id);
|
|
138
|
+
if (!/^term_/.test(msg.type || '')) return false;
|
|
139
|
+
if (!terminal || terminal.kind !== 'github-login') return !!msg.githubAttachKey;
|
|
140
|
+
var item, owner;
|
|
141
|
+
flows.forEach(function (flow,peer) { if (flow.terminal && flow.terminal.id === msg.id && flow.terminal.identity === terminal.identity) { item = flow; owner = peer; } });
|
|
142
|
+
try {
|
|
143
|
+
if (!item || item.stopping) return true;
|
|
144
|
+
requireCurrent(owner,item);
|
|
145
|
+
if (JSON.stringify(actor(ws)) !== item.signature || msg.githubAttachKey !== item.attachKey) return true;
|
|
146
|
+
if (msg.type === 'term_attach') {
|
|
147
|
+
if (item.peer && item.peer !== ws) return true;
|
|
148
|
+
if (!item.peer) ws.once('close',function () { if (flows.get(owner) === item) stop(owner,item); });
|
|
149
|
+
item.peer = ws; ctx.tm.attach(msg.id,ws);
|
|
150
|
+
} else if (item.peer === ws && msg.type === 'term_input') {
|
|
151
|
+
if (typeof msg.data === 'string' && /^[ynYN\r\n\x03\x1b\[AB]*$/.test(msg.data) && msg.data.length <= 16) ctx.tm.writeGithub(msg.id,msg.data,terminal.identity);
|
|
152
|
+
} else if (item.peer === ws && msg.type === 'term_resize' && Number.isInteger(msg.cols) && Number.isInteger(msg.rows) && msg.cols >= 20 && msg.cols <= 240 && msg.rows >= 5 && msg.rows <= 100) {
|
|
153
|
+
ctx.tm.resizeExact(msg.id,msg.cols,msg.rows,terminal.identity,function () { requireCurrent(owner,item); });
|
|
154
|
+
} else if (item.peer === ws && (msg.type === 'term_close' || msg.type === 'term_detach')) stop(owner,item);
|
|
155
|
+
} catch (error) { if (item) stop(owner,item); }
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
return {handle:function (ws,msg) {
|
|
159
|
+
if (terminalMessage(ws,msg)) return true;
|
|
160
|
+
if (!/^personal_github_/.test(msg.type || '')) return false;
|
|
161
|
+
run(ws,msg).catch(function (error) { send(ws,msg,{state:'error',message:error.message}); }); return true;
|
|
162
|
+
}, destroy:function () { flows.forEach(function (item,ws) { release(ws); }); drafts.clear(); }};
|
|
163
|
+
}
|
|
164
|
+
module.exports = { attach: attach, LOGIN_ARGS: LOGIN_ARGS };
|
package/lib/project.js
CHANGED
|
@@ -2040,6 +2040,7 @@ function createProjectContext(opts) {
|
|
|
2040
2040
|
|
|
2041
2041
|
// --- Vendor login flow (delegated to project-vendor-login.js) ---
|
|
2042
2042
|
if (_sharedBrowser.handleMessage(ws, msg)) return;
|
|
2043
|
+
if (_githubConnection.handle(ws, msg)) return;
|
|
2043
2044
|
if (_vendorLogin.handleVendorLoginMessage(ws, msg)) return;
|
|
2044
2045
|
|
|
2045
2046
|
// --- Notes, terminals, context, user message (delegated to project-user-message.js) ---
|
|
@@ -2458,6 +2459,12 @@ function createProjectContext(opts) {
|
|
|
2458
2459
|
getOsUserInfoForWs: getOsUserInfoForWs,
|
|
2459
2460
|
});
|
|
2460
2461
|
|
|
2462
|
+
var _githubConnection = require('./project-github-connection').attach({
|
|
2463
|
+
storage: opts.githubConnectionStorage, clients: clients, requestAccess: _requestAccess,
|
|
2464
|
+
slug: slug, osUsers: osUsers, usersModule: usersModule, resolveOsUserInfo: resolveOsUserInfo,
|
|
2465
|
+
tm: tm, sendTo: sendTo,
|
|
2466
|
+
});
|
|
2467
|
+
|
|
2461
2468
|
// --- Vendor login flow (delegated to project-vendor-login.js) ---
|
|
2462
2469
|
var _vendorLogin = attachVendorLogin({
|
|
2463
2470
|
workerAuth: _workerAuth,
|
|
@@ -2880,6 +2887,7 @@ function createProjectContext(opts) {
|
|
|
2880
2887
|
function destroy() {
|
|
2881
2888
|
_mcp.destroy();
|
|
2882
2889
|
_sharedBrowser.destroy();
|
|
2890
|
+
_githubConnection.destroy();
|
|
2883
2891
|
_vendorLogin.destroy();
|
|
2884
2892
|
if (_scheduledMessages) _scheduledMessages.shutdown();
|
|
2885
2893
|
if (_autonomousRun) _autonomousRun.shutdown();
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
var light = window.matchMedia && window.matchMedia("(prefers-color-scheme: light)").matches;
|
|
6
6
|
var seed = window.__clayAppearanceSeed;
|
|
7
7
|
|
|
8
|
+
var color = globalThis.ClayWorkspaceBackgrounds.getColor(seed && seed.backgroundColor, seed && seed.customBackgroundColors);
|
|
9
|
+
root.dataset.workspaceColor = color.id;
|
|
10
|
+
root.style.setProperty('--workspace-solid-light', color.light);
|
|
11
|
+
root.style.setProperty('--workspace-solid-dark', color.dark);
|
|
12
|
+
root.dataset.workspaceBackground = seed && seed.background === "workshop" ? "workshop" : "plain";
|
|
13
|
+
|
|
8
14
|
function applyMode(mode) {
|
|
9
15
|
if (mode !== "light" && mode !== "dark") return;
|
|
10
16
|
root.classList.toggle("light-theme", mode === "light");
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* One translucent material for navigation and pane chrome. Keep the filter on
|
|
2
|
+
a backing layer so fixed-position controls retain their original viewport. */
|
|
3
|
+
:root {
|
|
4
|
+
--chrome-material-blur: 14px;
|
|
5
|
+
--chrome-material-saturation: 1.08;
|
|
6
|
+
--chrome-material-tint: 74%;
|
|
7
|
+
--topbar-material-tint: 88%;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
:is(#top-bar, :where(#navigation-island) > #icon-strip, #sidebar-column, #mate-sidebar-column, .title-bar-content,
|
|
11
|
+
.split-pane-header, .tool-pane-header, .terminal-header,
|
|
12
|
+
.file-viewer-tabbar, .shared-browser-header, .dm-header-bar, #contextual-clay > header) {
|
|
13
|
+
--chrome-material-solid: var(--bg-alt);
|
|
14
|
+
--chrome-material-fill: var(--chrome-material-solid);
|
|
15
|
+
--chrome-material-filter: none;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
:is(#top-bar, #sidebar-column, #mate-sidebar-column, .title-bar-content,
|
|
19
|
+
.split-pane-header, .tool-pane-header, .terminal-header,
|
|
20
|
+
.file-viewer-tabbar, .shared-browser-header, .dm-header-bar, #contextual-clay > header) {
|
|
21
|
+
position: relative;
|
|
22
|
+
isolation: isolate;
|
|
23
|
+
background: transparent;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
:is(#sidebar-column, #mate-sidebar-column) {
|
|
27
|
+
--chrome-material-solid: var(--sidebar-bg);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Keep the global menu quieter and denser than the navigation/pane glass. */
|
|
31
|
+
#top-bar {
|
|
32
|
+
--chrome-material-solid: var(--sidebar-bg);
|
|
33
|
+
--chrome-material-tint: var(--topbar-material-tint);
|
|
34
|
+
/* Preserve the toolbar popover layer above chat and below contextual Clay. */
|
|
35
|
+
z-index: 150;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
body.mate-workspace-active :is(#sidebar-column, .title-bar-content) {
|
|
39
|
+
--chrome-material-solid: var(--clay-surround);
|
|
40
|
+
background: transparent;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
:is(#top-bar, #sidebar-column, #mate-sidebar-column, .title-bar-content,
|
|
44
|
+
.split-pane-header, .tool-pane-header, .terminal-header,
|
|
45
|
+
.file-viewer-tabbar, .shared-browser-header, .dm-header-bar, #contextual-clay > header)::before {
|
|
46
|
+
content: "";
|
|
47
|
+
position: absolute;
|
|
48
|
+
inset: 0;
|
|
49
|
+
z-index: -1;
|
|
50
|
+
border-radius: inherit;
|
|
51
|
+
background: var(--chrome-material-fill);
|
|
52
|
+
-webkit-backdrop-filter: var(--chrome-material-filter);
|
|
53
|
+
backdrop-filter: var(--chrome-material-filter);
|
|
54
|
+
pointer-events: none;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* The sidebar's own material continues behind its title and navigation. */
|
|
58
|
+
:is(#sidebar-column, #mate-sidebar-column) > .title-bar-sidebar {
|
|
59
|
+
background: transparent;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Let the one sidebar backing continue through the sticky toolbar. Scrolling
|
|
63
|
+
session paint is clipped below it instead of adding a second tinted face. */
|
|
64
|
+
@supports (clip-path: inset(0px)) {
|
|
65
|
+
#sidebar-column .session-list-sticky-top { background: transparent; }
|
|
66
|
+
#sidebar-column #session-list > :not(.session-list-sticky-top) {
|
|
67
|
+
clip-path: inset(var(--session-list-clip-top, 0px) 0 0);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) and (background: color-mix(in srgb, black 74%, transparent)) {
|
|
72
|
+
:is(#top-bar, :where(#navigation-island) > #icon-strip, #sidebar-column, #mate-sidebar-column, .title-bar-content,
|
|
73
|
+
.split-pane-header, .tool-pane-header, .terminal-header,
|
|
74
|
+
.file-viewer-tabbar, .shared-browser-header, .dm-header-bar, #contextual-clay > header) {
|
|
75
|
+
--chrome-material-fill: color-mix(in srgb, var(--chrome-material-solid) var(--chrome-material-tint), transparent);
|
|
76
|
+
--chrome-material-filter: blur(var(--chrome-material-blur)) saturate(var(--chrome-material-saturation));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* The mobile drawer owns its existing fixed positioning and touch behavior. */
|
|
81
|
+
@media (max-width: 768px) {
|
|
82
|
+
#sidebar {
|
|
83
|
+
--chrome-material-solid: var(--sidebar-bg);
|
|
84
|
+
--chrome-material-fill: var(--chrome-material-solid);
|
|
85
|
+
--chrome-material-filter: none;
|
|
86
|
+
isolation: isolate;
|
|
87
|
+
background: transparent;
|
|
88
|
+
}
|
|
89
|
+
#sidebar::before {
|
|
90
|
+
content: "";
|
|
91
|
+
position: absolute;
|
|
92
|
+
inset: 0;
|
|
93
|
+
z-index: -1;
|
|
94
|
+
background: var(--chrome-material-fill);
|
|
95
|
+
-webkit-backdrop-filter: var(--chrome-material-filter);
|
|
96
|
+
backdrop-filter: var(--chrome-material-filter);
|
|
97
|
+
pointer-events: none;
|
|
98
|
+
}
|
|
99
|
+
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) and (background: color-mix(in srgb, black 74%, transparent)) {
|
|
100
|
+
#sidebar {
|
|
101
|
+
--chrome-material-fill: color-mix(in srgb, var(--chrome-material-solid) var(--chrome-material-tint), transparent);
|
|
102
|
+
--chrome-material-filter: blur(var(--chrome-material-blur)) saturate(var(--chrome-material-saturation));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@media (prefers-reduced-transparency: reduce), (forced-colors: active) {
|
|
108
|
+
:is(#top-bar, :where(#navigation-island) > #icon-strip, #sidebar-column, #mate-sidebar-column, #sidebar, .title-bar-content,
|
|
109
|
+
.split-pane-header, .tool-pane-header, .terminal-header,
|
|
110
|
+
.file-viewer-tabbar, .shared-browser-header, .dm-header-bar, #contextual-clay > header) {
|
|
111
|
+
--chrome-material-fill: var(--chrome-material-solid);
|
|
112
|
+
--chrome-material-filter: none;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Only shared pane shells opt in. Their existing content keeps its own fills;
|
|
117
|
+
this backing also makes otherwise transparent content and gaps opaque. The
|
|
118
|
+
measured header boundary follows wrapping and safe-area changes. */
|
|
119
|
+
#main-panels > .tool-pane-material {
|
|
120
|
+
/* Override the legacy per-tool and docked shell fills, never body surfaces. */
|
|
121
|
+
background: transparent !important;
|
|
122
|
+
isolation: isolate;
|
|
123
|
+
}
|
|
124
|
+
#main-panels > .tool-pane-material::before {
|
|
125
|
+
content: "";
|
|
126
|
+
position: absolute;
|
|
127
|
+
inset: var(--tool-pane-body-top, 0px) 0 0;
|
|
128
|
+
z-index: -1;
|
|
129
|
+
background: var(--bg);
|
|
130
|
+
pointer-events: none;
|
|
131
|
+
}
|
|
132
|
+
/* Browser tabs live inside the shared header, rather than its content body. */
|
|
133
|
+
.tool-pane-material > .tool-pane-header > .shared-browser-tabs { background: transparent; }
|
package/lib/public/css/menus.css
CHANGED
|
@@ -246,23 +246,7 @@
|
|
|
246
246
|
#shared-browser-toggle-btn:hover,
|
|
247
247
|
#terminal-toggle-btn:hover { color: var(--text-secondary); background: rgba(var(--overlay-rgb),0.04); border-color: var(--border); }
|
|
248
248
|
#terminal-toggle-btn { position: relative; }
|
|
249
|
-
|
|
250
|
-
position: absolute;
|
|
251
|
-
top: -4px;
|
|
252
|
-
right: -4px;
|
|
253
|
-
background: var(--brand-green);
|
|
254
|
-
color: var(--brand-ink);
|
|
255
|
-
font-size: 9px;
|
|
256
|
-
font-weight: 700;
|
|
257
|
-
min-width: 14px;
|
|
258
|
-
height: 14px;
|
|
259
|
-
border-radius: 7px;
|
|
260
|
-
display: inline-flex;
|
|
261
|
-
align-items: center;
|
|
262
|
-
justify-content: center;
|
|
263
|
-
padding: 0 3px;
|
|
264
|
-
font-family: "Roboto Mono", monospace;
|
|
265
|
-
}
|
|
249
|
+
|
|
266
250
|
#terminal-count.hidden { display: none; }
|
|
267
251
|
|
|
268
252
|
/* --- QR code button & overlay --- */
|