clay-server 5.1.0-beta.2 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +9 -91
- package/lib/builtin-mates.js +7 -7
- package/lib/daemon-settings.js +11 -0
- package/lib/daemon.js +8 -37
- package/lib/linear-store.js +35 -8
- package/lib/mate-identity-file.js +39 -0
- package/lib/mate-knowledge-sync.js +1 -1
- package/lib/mate-ready-creation.js +2 -1
- package/lib/mates-identity.js +4 -4
- package/lib/mates-prompts.js +4 -3
- package/lib/mates.js +12 -11
- package/lib/os-users.js +5 -16
- package/lib/project-connection.js +0 -7
- package/lib/project-debate.js +3 -2
- package/lib/project-filesystem.js +21 -39
- package/lib/project-mate-interaction.js +7 -6
- package/lib/project-server-settings.js +31 -0
- package/lib/project-sessions.js +57 -184
- package/lib/project.js +3 -3
- package/lib/public/app.js +6 -37
- package/lib/public/css/input.css +3 -1
- package/lib/public/css/menus.css +3 -0
- package/lib/public/css/overlays.css +7 -0
- package/lib/public/css/project-settings-modal.css +56 -0
- package/lib/public/css/server-settings-system.css +125 -0
- 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/title-bar.css +5 -1
- package/lib/public/index.html +173 -447
- package/lib/public/modules/account-island.js +9 -7
- 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-dm.js +0 -2
- package/lib/public/modules/app-messages.js +13 -52
- package/lib/public/modules/app-rate-limit.js +70 -12
- package/lib/public/modules/avatar.js +6 -1
- package/lib/public/modules/context-sources.js +27 -18
- package/lib/public/modules/default-ai.js +14 -7
- package/lib/public/modules/header-tui-font.js +10 -20
- package/lib/public/modules/home-mate-avatar-editor.js +2 -2
- package/lib/public/modules/linear-settings.js +3 -2
- package/lib/public/modules/markdown.js +5 -6
- 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 +15 -6
- package/lib/public/modules/project-settings-environment.js +106 -0
- package/lib/public/modules/project-settings.js +72 -628
- 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-github.js +2 -1
- package/lib/public/modules/session-presence-ui.js +1 -1
- 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/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/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/style.css +4 -0
- package/lib/sdk-message-processor.js +16 -4
- package/lib/sdk-quota-snapshot.js +37 -0
- package/lib/server-appearance-settings.js +71 -0
- package/lib/server-linear.js +14 -8
- package/lib/server-mate-instructions.js +2 -2
- package/lib/server-mates.js +2 -1
- package/lib/server-notification-settings.js +67 -0
- package/lib/server-settings.js +4 -0
- package/lib/server-storage.js +57 -0
- package/lib/server.js +10 -2
- package/lib/sessions.js +3 -4
- package/lib/startup-projects.js +40 -0
- package/lib/storage-native-walk.js +85 -0
- package/lib/storage-usage.js +144 -0
- package/lib/users-preferences.js +0 -74
- package/lib/users.js +0 -4
- package/lib/ws-schema.js +2 -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 +3 -3
package/bin/cli.js
CHANGED
|
@@ -37,6 +37,7 @@ var { generateAuthToken } = require("../lib/server");
|
|
|
37
37
|
var { enableMultiUser, disableMultiUser, hasAdmin, isMultiUser, getSetupCode } = require("../lib/users");
|
|
38
38
|
var clayStudioCert = require("../lib/clay-studio-cert");
|
|
39
39
|
var cliWordmark = require("../lib/cli-wordmark");
|
|
40
|
+
var buildStartupProjects = require("../lib/startup-projects").buildStartupProjects;
|
|
40
41
|
|
|
41
42
|
function openUrl(url) {
|
|
42
43
|
try {
|
|
@@ -446,9 +447,6 @@ async function restartDaemonFromConfig() {
|
|
|
446
447
|
var newConfig = Object.assign({}, lastConfig, {
|
|
447
448
|
pid: null,
|
|
448
449
|
port: targetPort,
|
|
449
|
-
projects: (lastConfig.projects || []).filter(function (p) {
|
|
450
|
-
return fs.existsSync(p.path);
|
|
451
|
-
})
|
|
452
450
|
});
|
|
453
451
|
|
|
454
452
|
ensureConfigDir();
|
|
@@ -1445,56 +1443,15 @@ async function forkDaemon(mode, keepAwake, extraProjects, addCwd, wantOsUsers) {
|
|
|
1445
1443
|
return;
|
|
1446
1444
|
}
|
|
1447
1445
|
|
|
1448
|
-
var allProjects = [];
|
|
1449
|
-
var usedSlugs = [];
|
|
1450
|
-
|
|
1451
|
-
// Load previous config to preserve per-project settings (visibility, allowedUsers)
|
|
1452
1446
|
var prevConfig = loadConfig();
|
|
1453
|
-
var
|
|
1454
|
-
if (prevConfig && prevConfig.projects) {
|
|
1455
|
-
for (var pi = 0; pi < prevConfig.projects.length; pi++) {
|
|
1456
|
-
prevProjectMap[prevConfig.projects[pi].path] = prevConfig.projects[pi];
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
// Only include cwd if explicitly requested
|
|
1447
|
+
var recentProjects = extraProjects || [];
|
|
1461
1448
|
if (addCwd) {
|
|
1462
|
-
var
|
|
1463
|
-
|
|
1464
|
-
// Restore title/icon from .clayrc if available
|
|
1465
|
-
var cwdRc = loadClayrc();
|
|
1466
|
-
var cwdRecent = cwdRc.recentProjects || [];
|
|
1467
|
-
for (var cr = 0; cr < cwdRecent.length; cr++) {
|
|
1468
|
-
if (cwdRecent[cr].path === cwd) {
|
|
1469
|
-
if (cwdRecent[cr].title) cwdEntry.title = cwdRecent[cr].title;
|
|
1470
|
-
if (cwdRecent[cr].icon) cwdEntry.icon = cwdRecent[cr].icon;
|
|
1471
|
-
break;
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
// Restore project-level settings from previous config
|
|
1475
|
-
if (prevProjectMap[cwd]) {
|
|
1476
|
-
cwdEntry = Object.assign({}, prevProjectMap[cwd], cwdEntry);
|
|
1477
|
-
}
|
|
1478
|
-
allProjects.push(cwdEntry);
|
|
1479
|
-
usedSlugs.push(slug);
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
// Add restored projects (from ~/.clayrc)
|
|
1483
|
-
if (extraProjects && extraProjects.length > 0) {
|
|
1484
|
-
for (var ep = 0; ep < extraProjects.length; ep++) {
|
|
1485
|
-
var rp = extraProjects[ep];
|
|
1486
|
-
if (rp.path === cwd) continue; // skip if same as cwd
|
|
1487
|
-
if (!fs.existsSync(rp.path)) continue; // skip missing directories
|
|
1488
|
-
var rpSlug = generateSlug(rp.path, usedSlugs);
|
|
1489
|
-
usedSlugs.push(rpSlug);
|
|
1490
|
-
var rpEntry = { path: rp.path, slug: rpSlug, title: rp.title || undefined, icon: rp.icon || undefined, addedAt: rp.addedAt || Date.now() };
|
|
1491
|
-
// Restore project-level settings from previous config
|
|
1492
|
-
if (prevProjectMap[rp.path]) {
|
|
1493
|
-
rpEntry = Object.assign({}, prevProjectMap[rp.path], rpEntry);
|
|
1494
|
-
}
|
|
1495
|
-
allProjects.push(rpEntry);
|
|
1496
|
-
}
|
|
1449
|
+
var cwdRecent = (loadClayrc().recentProjects || []).filter(function (p) { return p.path === cwd; });
|
|
1450
|
+
recentProjects = cwdRecent.concat(recentProjects);
|
|
1497
1451
|
}
|
|
1452
|
+
var allProjects = buildStartupProjects(prevConfig, recentProjects, {
|
|
1453
|
+
cwd: cwd, addCwd: addCwd, existsSync: fs.existsSync, generateSlug: generateSlug,
|
|
1454
|
+
});
|
|
1498
1455
|
|
|
1499
1456
|
var config = Object.assign({}, prevConfig || {}, {
|
|
1500
1457
|
pid: null,
|
|
@@ -1629,49 +1586,10 @@ async function devMode(mode, keepAwake, existingPinHash, wantOsUsers) {
|
|
|
1629
1586
|
return;
|
|
1630
1587
|
}
|
|
1631
1588
|
|
|
1632
|
-
var slug = generateSlug(cwd, []);
|
|
1633
|
-
var cwdDevEntry = { path: cwd, slug: slug, addedAt: Date.now() };
|
|
1634
|
-
|
|
1635
|
-
// Load previous config to preserve per-project settings (visibility, allowedUsers)
|
|
1636
1589
|
var prevDevConfig = loadConfig();
|
|
1637
|
-
var
|
|
1638
|
-
|
|
1639
|
-
for (var pdi = 0; pdi < prevDevConfig.projects.length; pdi++) {
|
|
1640
|
-
prevDevProjectMap[prevDevConfig.projects[pdi].path] = prevDevConfig.projects[pdi];
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
// Restore previous projects
|
|
1645
|
-
var rc = loadClayrc();
|
|
1646
|
-
var restorable = (rc.recentProjects || []).filter(function (p) {
|
|
1647
|
-
return p.path !== cwd && fs.existsSync(p.path);
|
|
1590
|
+
var allProjects = buildStartupProjects(prevDevConfig, loadClayrc().recentProjects || [], {
|
|
1591
|
+
cwd: cwd, addCwd: true, existsSync: fs.existsSync, generateSlug: generateSlug,
|
|
1648
1592
|
});
|
|
1649
|
-
// Restore title/icon for cwd from .clayrc
|
|
1650
|
-
var rcAll = rc.recentProjects || [];
|
|
1651
|
-
for (var ci = 0; ci < rcAll.length; ci++) {
|
|
1652
|
-
if (rcAll[ci].path === cwd) {
|
|
1653
|
-
if (rcAll[ci].title) cwdDevEntry.title = rcAll[ci].title;
|
|
1654
|
-
if (rcAll[ci].icon) cwdDevEntry.icon = rcAll[ci].icon;
|
|
1655
|
-
break;
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
// Restore access settings for cwd from previous config
|
|
1659
|
-
if (prevDevProjectMap[cwd]) {
|
|
1660
|
-
cwdDevEntry = Object.assign({}, prevDevProjectMap[cwd], cwdDevEntry);
|
|
1661
|
-
}
|
|
1662
|
-
var allProjects = [cwdDevEntry];
|
|
1663
|
-
var usedSlugs = [slug];
|
|
1664
|
-
for (var ri = 0; ri < restorable.length; ri++) {
|
|
1665
|
-
var rp = restorable[ri];
|
|
1666
|
-
var rpSlug = generateSlug(rp.path, usedSlugs);
|
|
1667
|
-
usedSlugs.push(rpSlug);
|
|
1668
|
-
var rpDevEntry = { path: rp.path, slug: rpSlug, title: rp.title || undefined, icon: rp.icon || undefined, addedAt: rp.addedAt || Date.now() };
|
|
1669
|
-
// Restore project-level settings from previous config
|
|
1670
|
-
if (prevDevProjectMap[rp.path]) {
|
|
1671
|
-
rpDevEntry = Object.assign({}, prevDevProjectMap[rp.path], rpDevEntry);
|
|
1672
|
-
}
|
|
1673
|
-
allProjects.push(rpDevEntry);
|
|
1674
|
-
}
|
|
1675
1593
|
|
|
1676
1594
|
var config = Object.assign({}, prevDevConfig || {}, {
|
|
1677
1595
|
pid: null,
|
package/lib/builtin-mates.js
CHANGED
|
@@ -156,7 +156,7 @@ var BUILTIN_MATES = [
|
|
|
156
156
|
];
|
|
157
157
|
|
|
158
158
|
// ---------------------------------------------------------------------------
|
|
159
|
-
// ALLY
|
|
159
|
+
// ALLY AGENTS.md template
|
|
160
160
|
// ---------------------------------------------------------------------------
|
|
161
161
|
|
|
162
162
|
var ALLY_TEMPLATE =
|
|
@@ -245,7 +245,7 @@ var ALLY_TEMPLATE =
|
|
|
245
245
|
"- Do not promote transient information or emotional states.\n";
|
|
246
246
|
|
|
247
247
|
// ---------------------------------------------------------------------------
|
|
248
|
-
// CLAY
|
|
248
|
+
// CLAY AGENTS.md template (host agent — the app itself answers)
|
|
249
249
|
// ---------------------------------------------------------------------------
|
|
250
250
|
|
|
251
251
|
var CLAY_TEMPLATE =
|
|
@@ -321,7 +321,7 @@ var CLAY_TEMPLATE =
|
|
|
321
321
|
"After that, jump straight into search. No more meta-conversation.\n";
|
|
322
322
|
|
|
323
323
|
// ---------------------------------------------------------------------------
|
|
324
|
-
// ARCH
|
|
324
|
+
// ARCH AGENTS.md template
|
|
325
325
|
// ---------------------------------------------------------------------------
|
|
326
326
|
|
|
327
327
|
var ARCH_TEMPLATE =
|
|
@@ -392,7 +392,7 @@ var ARCH_TEMPLATE =
|
|
|
392
392
|
"If nothing is there, work without it and learn through the conversation.\n";
|
|
393
393
|
|
|
394
394
|
// ---------------------------------------------------------------------------
|
|
395
|
-
// RUSH
|
|
395
|
+
// RUSH AGENTS.md template
|
|
396
396
|
// ---------------------------------------------------------------------------
|
|
397
397
|
|
|
398
398
|
var RUSH_TEMPLATE =
|
|
@@ -459,7 +459,7 @@ var RUSH_TEMPLATE =
|
|
|
459
459
|
"and deadlines. Use what is available. If nothing is there, work without it.\n";
|
|
460
460
|
|
|
461
461
|
// ---------------------------------------------------------------------------
|
|
462
|
-
// WARD
|
|
462
|
+
// WARD AGENTS.md template
|
|
463
463
|
// ---------------------------------------------------------------------------
|
|
464
464
|
|
|
465
465
|
var WARD_TEMPLATE =
|
|
@@ -527,7 +527,7 @@ var WARD_TEMPLATE =
|
|
|
527
527
|
"tech stack, and known risk areas. Use what is available. If nothing is there, work without it.\n";
|
|
528
528
|
|
|
529
529
|
// ---------------------------------------------------------------------------
|
|
530
|
-
// PIXEL
|
|
530
|
+
// PIXEL AGENTS.md template
|
|
531
531
|
// ---------------------------------------------------------------------------
|
|
532
532
|
|
|
533
533
|
var PIXEL_TEMPLATE =
|
|
@@ -599,7 +599,7 @@ var PIXEL_TEMPLATE =
|
|
|
599
599
|
"target audience, and UX decisions. Use what is available. If nothing is there, work without it.\n";
|
|
600
600
|
|
|
601
601
|
// ---------------------------------------------------------------------------
|
|
602
|
-
// BUZZ
|
|
602
|
+
// BUZZ AGENTS.md template
|
|
603
603
|
// ---------------------------------------------------------------------------
|
|
604
604
|
|
|
605
605
|
var BUZZ_TEMPLATE =
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Persist before publishing a setting to live daemon state.
|
|
2
|
+
function saveDaemonSetting(config, key, value, saveConfig) {
|
|
3
|
+
var overrides = {}; overrides[key] = value;
|
|
4
|
+
var next = Object.assign({}, config, overrides);
|
|
5
|
+
if (value === undefined) delete next[key];
|
|
6
|
+
saveConfig(next);
|
|
7
|
+
if (value === undefined) delete config[key];
|
|
8
|
+
Object.assign(config, next);
|
|
9
|
+
return next;
|
|
10
|
+
}
|
|
11
|
+
module.exports = { saveDaemonSetting: saveDaemonSetting };
|
package/lib/daemon.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var saveDaemonSetting = require("./daemon-settings").saveDaemonSetting;
|
|
2
3
|
|
|
3
4
|
// --- Node version check ---
|
|
4
5
|
var nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
|
|
@@ -72,9 +73,7 @@ if (projectLogContext.initializeProjectKnowledge(config)) {
|
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
// --- OS users mode: check required system dependencies ---
|
|
75
|
-
//
|
|
76
|
-
// like a normal login; operators can disable via config / the settings toggle.
|
|
77
|
-
osUsersMod.setInheritGroups(config.inheritGroups !== false);
|
|
76
|
+
// User-switched processes inherit the account groups, like a normal login.
|
|
78
77
|
if (config.osUsers) {
|
|
79
78
|
var checkExec = require("child_process").execFileSync;
|
|
80
79
|
var missing = [];
|
|
@@ -86,11 +85,7 @@ if (config.osUsers) {
|
|
|
86
85
|
console.error("[daemon] Install with: sudo apt install " + missing.map(function (m) { return m.split(" ")[0]; }).join(" "));
|
|
87
86
|
process.exit(78); // EX_CONFIG
|
|
88
87
|
}
|
|
89
|
-
|
|
90
|
-
console.log("[daemon] Sessions inherit each user's supplementary groups (docker, etc.); disable in Settings > User Groups.");
|
|
91
|
-
} else {
|
|
92
|
-
console.log("[daemon] Sessions run with primary group only (supplementary groups dropped); enable in Settings > User Groups.");
|
|
93
|
-
}
|
|
88
|
+
console.log("[daemon] Sessions inherit each user's supplementary groups when setpriv is available.");
|
|
94
89
|
}
|
|
95
90
|
|
|
96
91
|
// --- TLS ---
|
|
@@ -637,12 +632,7 @@ var relay = createServer({
|
|
|
637
632
|
return { envrc: config.sharedEnv || "" };
|
|
638
633
|
},
|
|
639
634
|
onSetSharedEnv: function (envrc) {
|
|
640
|
-
|
|
641
|
-
config.sharedEnv = envrc;
|
|
642
|
-
} else {
|
|
643
|
-
delete config.sharedEnv;
|
|
644
|
-
}
|
|
645
|
-
saveConfig(config);
|
|
635
|
+
saveDaemonSetting(config, "sharedEnv", envrc || undefined, saveConfig);
|
|
646
636
|
return { ok: true };
|
|
647
637
|
},
|
|
648
638
|
onGetServerDefaultEffort: function () {
|
|
@@ -812,7 +802,6 @@ var relay = createServer({
|
|
|
812
802
|
keepAwake: !!config.keepAwake,
|
|
813
803
|
autoContinueOnRateLimit: !!config.autoContinueOnRateLimit,
|
|
814
804
|
osUsers: !!config.osUsers,
|
|
815
|
-
inheritGroups: config.inheritGroups !== false,
|
|
816
805
|
chatLayout: config.chatLayout || "channel",
|
|
817
806
|
matesEnabled: config.matesEnabled === true,
|
|
818
807
|
capsulesEnabled: config.capsulesEnabled === true,
|
|
@@ -827,25 +816,18 @@ var relay = createServer({
|
|
|
827
816
|
onSetImageRetention: function (days) {
|
|
828
817
|
var d = parseInt(days, 10);
|
|
829
818
|
if (isNaN(d) || d < 0) d = 7;
|
|
830
|
-
config
|
|
831
|
-
saveConfig(config);
|
|
819
|
+
saveDaemonSetting(config, "imageRetentionDays", d, saveConfig);
|
|
832
820
|
console.log("[daemon] Image retention:", d === 0 ? "forever" : d + " days");
|
|
833
821
|
return { ok: true, days: d };
|
|
834
822
|
},
|
|
835
823
|
onSetUpdateChannel: function (channel) {
|
|
836
|
-
config
|
|
837
|
-
saveConfig(config);
|
|
824
|
+
saveDaemonSetting(config, "updateChannel", channel === "beta" ? "beta" : "stable", saveConfig);
|
|
838
825
|
console.log("[daemon] Update channel:", config.updateChannel, "(web)");
|
|
839
826
|
return { ok: true, updateChannel: config.updateChannel };
|
|
840
827
|
},
|
|
841
828
|
onSetPin: function (pin) {
|
|
842
|
-
|
|
843
|
-
config.pinHash = generateAuthToken(pin);
|
|
844
|
-
} else {
|
|
845
|
-
config.pinHash = null;
|
|
846
|
-
}
|
|
829
|
+
saveDaemonSetting(config, "pinHash", pin ? generateAuthToken(pin) : null, saveConfig);
|
|
847
830
|
relay.setAuthToken(config.pinHash);
|
|
848
|
-
saveConfig(config);
|
|
849
831
|
console.log("[daemon] PIN", pin ? "set" : "removed", "(web)");
|
|
850
832
|
return { ok: true, pinEnabled: !!config.pinHash };
|
|
851
833
|
},
|
|
@@ -921,8 +903,7 @@ var relay = createServer({
|
|
|
921
903
|
},
|
|
922
904
|
onSetKeepAwake: function (value) {
|
|
923
905
|
var want = !!value;
|
|
924
|
-
config
|
|
925
|
-
saveConfig(config);
|
|
906
|
+
saveDaemonSetting(config, "keepAwake", want, saveConfig);
|
|
926
907
|
if (want && !caffeinateProc && process.platform === "darwin") {
|
|
927
908
|
try {
|
|
928
909
|
var { spawn: spawnCaff } = require("child_process");
|
|
@@ -936,16 +917,6 @@ var relay = createServer({
|
|
|
936
917
|
console.log("[daemon] Keep awake:", want, "(web)");
|
|
937
918
|
return { ok: true, keepAwake: want };
|
|
938
919
|
},
|
|
939
|
-
onSetInheritGroups: function (value) {
|
|
940
|
-
var want = !!value;
|
|
941
|
-
config.inheritGroups = want;
|
|
942
|
-
saveConfig(config);
|
|
943
|
-
// Apply live so subsequent session/terminal spawns pick it up without a
|
|
944
|
-
// daemon restart; already-running sessions keep their current groups.
|
|
945
|
-
osUsersMod.setInheritGroups(want);
|
|
946
|
-
console.log("[daemon] Inherit supplementary groups:", want, "(web)");
|
|
947
|
-
return { ok: true, inheritGroups: want };
|
|
948
|
-
},
|
|
949
920
|
onShutdown: function () {
|
|
950
921
|
console.log("[daemon] Shutdown requested via web UI");
|
|
951
922
|
gracefulShutdown();
|
package/lib/linear-store.js
CHANGED
|
@@ -19,34 +19,61 @@ function attachLinearStore(root) {
|
|
|
19
19
|
catch (error) { if (error.code !== 'EEXIST') throw error; }
|
|
20
20
|
return fs.readFileSync(file);
|
|
21
21
|
}
|
|
22
|
-
function
|
|
22
|
+
function record(owner, id) {
|
|
23
23
|
var data = read(owner);
|
|
24
|
+
return !id || id === "default" ? data : Object.hasOwn(data.connections || {}, id) ? data.connections[id] : {};
|
|
25
|
+
}
|
|
26
|
+
function key(owner, id) {
|
|
27
|
+
var data = record(owner, id);
|
|
24
28
|
if (!data.secret) return null;
|
|
25
29
|
var parts = data.secret.split('.').map(function (part) { return Buffer.from(part, 'base64'); });
|
|
26
30
|
var cipher = crypto.createDecipheriv('aes-256-gcm', masterKey(), parts[0]);
|
|
27
|
-
cipher.setAAD(Buffer.from(owner)); cipher.setAuthTag(parts[1]);
|
|
31
|
+
cipher.setAAD(Buffer.from(id && id !== 'default' ? owner + ':' + id : owner)); cipher.setAuthTag(parts[1]);
|
|
28
32
|
return Buffer.concat([cipher.update(parts[2]), cipher.final()]).toString('utf8');
|
|
29
33
|
}
|
|
30
|
-
function view(owner) {
|
|
31
|
-
var data =
|
|
34
|
+
function view(owner, id) {
|
|
35
|
+
var data = record(owner, id);
|
|
32
36
|
return { connected: !!data.secret, workspace: data.workspace || null, revision: data.revision || null };
|
|
33
37
|
}
|
|
34
|
-
function save(owner, secret, workspace) {
|
|
38
|
+
function save(owner, secret, workspace, id, name) {
|
|
39
|
+
if (id && (!/^[a-zA-Z0-9_-]{1,64}$/.test(id) || ['__proto__', 'constructor', 'prototype'].indexOf(id) >= 0)) throw new Error("Invalid connection.");
|
|
40
|
+
var previous = read(owner);
|
|
35
41
|
var file = filename(owner), data = { revision: crypto.randomBytes(16).toString('hex') };
|
|
36
42
|
if (secret !== null) {
|
|
37
43
|
if (typeof secret !== 'string' || !secret.trim() || secret.length > 4096 || /[\r\n]/.test(secret)) throw new Error('Enter a valid Linear API key.');
|
|
38
44
|
var iv = crypto.randomBytes(12), cipher = crypto.createCipheriv('aes-256-gcm', masterKey(), iv);
|
|
39
|
-
cipher.setAAD(Buffer.from(owner));
|
|
45
|
+
cipher.setAAD(Buffer.from(id && id !== 'default' ? owner + ':' + id : owner));
|
|
40
46
|
var bytes = Buffer.concat([cipher.update(secret.trim(), 'utf8'), cipher.final()]);
|
|
41
47
|
data.secret = [iv, cipher.getAuthTag(), bytes].map(function (part) { return part.toString('base64'); }).join('.');
|
|
42
48
|
data.workspace = workspace;
|
|
43
49
|
}
|
|
50
|
+
if (name !== undefined) {
|
|
51
|
+
if (typeof name !== 'string' || !name.trim() || name.trim().length > 80) throw new Error('Enter a connection name of up to 80 characters.');
|
|
52
|
+
data.name = name.trim();
|
|
53
|
+
} else data.name = record(owner, id).name;
|
|
54
|
+
if (id && id !== 'default') {
|
|
55
|
+
previous.connections = previous.connections || {};
|
|
56
|
+
if (secret === null) delete previous.connections[id];
|
|
57
|
+
else {
|
|
58
|
+
if (!previous.connections[id] && Object.keys(previous.connections).length >= 30) throw new Error('Connection limit reached.');
|
|
59
|
+
previous.connections[id] = data;
|
|
60
|
+
}
|
|
61
|
+
data = previous;
|
|
62
|
+
} else data.connections = previous.connections;
|
|
44
63
|
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
45
64
|
var temp = file + '.' + crypto.randomBytes(8).toString('hex') + '.tmp';
|
|
46
65
|
try { fs.writeFileSync(temp, JSON.stringify(data), { mode: 0o600 }); fs.renameSync(temp, file); }
|
|
47
66
|
finally { try { fs.unlinkSync(temp); } catch (error) { if (error.code !== 'ENOENT') throw error; } }
|
|
48
|
-
return view(owner);
|
|
67
|
+
return view(owner, id);
|
|
68
|
+
}
|
|
69
|
+
function list(owner) {
|
|
70
|
+
var data = read(owner), entries = [];
|
|
71
|
+
if (data.secret) entries.push(Object.assign({ id: 'default', name: data.name || 'Existing connection', legacy: true }, view(owner)));
|
|
72
|
+
Object.keys(data.connections || {}).forEach(function (id) {
|
|
73
|
+
entries.push(Object.assign({ id: id, name: data.connections[id].name || 'Linear', legacy: false }, view(owner, id)));
|
|
74
|
+
});
|
|
75
|
+
return entries;
|
|
49
76
|
}
|
|
50
|
-
return { key: key, view: view, save: save };
|
|
77
|
+
return { key: key, view: view, save: save, list: list };
|
|
51
78
|
}
|
|
52
79
|
module.exports = { attachLinearStore: attachLinearStore };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Resolve the canonical Mate identity without overwriting either document.
|
|
2
|
+
var fs = require('fs');
|
|
3
|
+
var path = require('path');
|
|
4
|
+
var crypto = require('crypto');
|
|
5
|
+
|
|
6
|
+
function regularFile(file) {
|
|
7
|
+
try {
|
|
8
|
+
var stat = fs.lstatSync(file);
|
|
9
|
+
if (!stat.isFile()) throw new Error('Mate identity must be a regular file: ' + file);
|
|
10
|
+
return stat;
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if (error.code === 'ENOENT') return null;
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function resolveMateIdentityFile(directory) {
|
|
18
|
+
var canonical = path.join(directory, 'AGENTS.md');
|
|
19
|
+
var legacy = path.join(directory, 'CLAUDE.md');
|
|
20
|
+
var current = regularFile(canonical);
|
|
21
|
+
if (!regularFile(legacy)) return canonical;
|
|
22
|
+
if (!current) {
|
|
23
|
+
// Exclusive linking preserves owner, mode and bytes and never replaces a
|
|
24
|
+
// concurrently created canonical file. Unlink only after publication.
|
|
25
|
+
try { fs.linkSync(legacy, canonical); }
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error.code !== 'EEXIST') throw error;
|
|
28
|
+
return resolveMateIdentityFile(directory);
|
|
29
|
+
}
|
|
30
|
+
} else if (fs.statSync(canonical).ino !== fs.statSync(legacy).ino) {
|
|
31
|
+
// Preserve a conflicting legacy document outside instruction discovery.
|
|
32
|
+
var backup = path.join(directory, '.identity-legacy-' + crypto.randomBytes(12).toString('hex') + '.md');
|
|
33
|
+
fs.linkSync(legacy, backup);
|
|
34
|
+
}
|
|
35
|
+
fs.unlinkSync(legacy);
|
|
36
|
+
return canonical;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {resolveMateIdentityFile: resolveMateIdentityFile};
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// call idempotent and makes deletions and rewrites work without the call site
|
|
13
13
|
// having to describe what changed.
|
|
14
14
|
//
|
|
15
|
-
// Identity is never mirrored. CLAUDE.md, base-template.md, identity-backup.md,
|
|
15
|
+
// Identity is never mirrored. AGENTS.md, legacy CLAUDE.md, base-template.md, identity-backup.md,
|
|
16
16
|
// and identity-history.jsonl define who a Mate is and remain canonical files.
|
|
17
17
|
// Sticky notes are a separate attention layer and are excluded too.
|
|
18
18
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
var resolveMateIdentityFile = require('./mate-identity-file').resolveMateIdentityFile;
|
|
1
2
|
// Atomic finalization of an interviewed Mate. No placeholder is exposed unless
|
|
2
3
|
// the complete identity has passed validation and is ready to register.
|
|
3
4
|
|
|
@@ -55,7 +56,7 @@ function createReadyMate(mates, mateCtx, definition) {
|
|
|
55
56
|
autonomy: ready.autonomy,
|
|
56
57
|
});
|
|
57
58
|
var mateDir = mates.getMateDir(mateCtx, mate.id);
|
|
58
|
-
var identityPath =
|
|
59
|
+
var identityPath = resolveMateIdentityFile(mateDir);
|
|
59
60
|
fs.writeFileSync(identityPath, ready.identityMarkdown + "\n");
|
|
60
61
|
mates.enforceAllSections(identityPath, { ctx: mateCtx, mateId: mate.id });
|
|
61
62
|
fs.mkdirSync(path.join(mateDir, "knowledge"), { recursive: true });
|
package/lib/mates-identity.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Mates identity module -- Identity extraction, backup, and change tracking.
|
|
3
3
|
*
|
|
4
4
|
* Manages the boundary between a mate's user-authored identity and
|
|
5
|
-
* system-managed sections in
|
|
5
|
+
* system-managed sections in AGENTS.md.
|
|
6
6
|
* Extracted from mates.js to keep module sizes manageable.
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -18,7 +18,7 @@ var IDENTITY_MIN_LENGTH = 50;
|
|
|
18
18
|
/**
|
|
19
19
|
* Build the capabilities section for a primary mate.
|
|
20
20
|
* Injected as a system section so it auto-updates with code changes
|
|
21
|
-
* without touching the mate's identity in
|
|
21
|
+
* without touching the mate's identity in AGENTS.md.
|
|
22
22
|
*/
|
|
23
23
|
function buildPrimaryCapabilitiesSection(mate) {
|
|
24
24
|
if (!mate || !mate.primary) return "";
|
|
@@ -52,9 +52,9 @@ function buildPrimaryCapabilitiesSection(mate) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* Extract identity content from a
|
|
55
|
+
* Extract identity content from a AGENTS.md string.
|
|
56
56
|
* Identity is everything before the first system marker.
|
|
57
|
-
* @param {string} content - full
|
|
57
|
+
* @param {string} content - full AGENTS.md content
|
|
58
58
|
* @param {string[]} allMarkers - array of all system marker strings
|
|
59
59
|
*/
|
|
60
60
|
function extractIdentity(content, allMarkers) {
|
package/lib/mates-prompts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Mates prompts module -- System section enforcers for mate
|
|
2
|
+
* Mates prompts module -- System section enforcers for mate AGENTS.md files.
|
|
3
3
|
*
|
|
4
4
|
* Each enforcer manages a specific system-managed section (team awareness,
|
|
5
5
|
* session memory, sticky notes, project registry, debate awareness).
|
|
@@ -20,7 +20,7 @@ var TEAM_SECTION =
|
|
|
20
20
|
"**This section is managed by the system and cannot be removed.**\n\n" +
|
|
21
21
|
"You are one of several AI Mates in this workspace. Your teammates and their profiles are listed in `../mates.json`. " +
|
|
22
22
|
"Each teammate's identity and working style is described in their own directory:\n\n" +
|
|
23
|
-
"- `../{mate_id}/
|
|
23
|
+
"- `../{mate_id}/AGENTS.md` -- their identity, personality, and working style\n" +
|
|
24
24
|
"- `../{mate_id}/mate.yaml` -- their metadata (name, role, status, activities)\n" +
|
|
25
25
|
"- `../common-knowledge.json` -- shared knowledge registry; files listed here are readable by all mates\n\n" +
|
|
26
26
|
"Check the team registry when it would be relevant to know who else is available or what they do. " +
|
|
@@ -31,6 +31,7 @@ var SESSION_MEMORY_MARKER = "<!-- SESSION_MEMORY_MANAGED_BY_SYSTEM -->";
|
|
|
31
31
|
var SESSION_MEMORY_SECTION =
|
|
32
32
|
"\n\n" + SESSION_MEMORY_MARKER + "\n" +
|
|
33
33
|
"## Session Memory\n\n" +
|
|
34
|
+
"Your identity is stored in `AGENTS.md` in your Mate directory. Read and edit that file when reshaping your identity; do not create a separate `CLAUDE.md`.\n\n" +
|
|
34
35
|
"**This section is managed by the system and cannot be removed.**\n\n" +
|
|
35
36
|
"Your `knowledge/memory-summary.md` file contains your compressed long-term memory, " +
|
|
36
37
|
"automatically maintained across sessions. Refer to it for context about past " +
|
|
@@ -115,7 +116,7 @@ function buildTeamSection(ctx, currentMateId, loadMates) {
|
|
|
115
116
|
|
|
116
117
|
section += "\n" +
|
|
117
118
|
"Each teammate's full identity is in their own directory:\n\n" +
|
|
118
|
-
"- `../{mate_id}/
|
|
119
|
+
"- `../{mate_id}/AGENTS.md` -- identity, personality, working style\n" +
|
|
119
120
|
"- `../{mate_id}/mate.yaml` -- metadata (name, role, status, activities)\n" +
|
|
120
121
|
"- `../common-knowledge.json` -- shared knowledge readable by all mates\n\n" +
|
|
121
122
|
"Use the **ID** (not the name) when referencing teammates in structured data. " +
|
package/lib/mates.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
var resolveMateIdentityFile = require('./mate-identity-file').resolveMateIdentityFile;
|
|
1
2
|
var fs = require("fs");
|
|
2
3
|
var path = require("path");
|
|
3
4
|
var crypto = require("crypto");
|
|
@@ -161,7 +162,7 @@ function createMate(ctx, seedData) {
|
|
|
161
162
|
yaml += "model: " + JSON.stringify(seedData.model || null) + "\n";
|
|
162
163
|
fs.writeFileSync(path.join(mateDir, "mate.yaml"), yaml);
|
|
163
164
|
|
|
164
|
-
// Write initial
|
|
165
|
+
// Write initial AGENTS.md (will be replaced by interview)
|
|
165
166
|
var claudeMd = "# Mate Identity\n\n";
|
|
166
167
|
claudeMd += "This mate is currently being interviewed. Identity will be generated after the interview.\n\n";
|
|
167
168
|
claudeMd += "## Seed Data\n\n";
|
|
@@ -176,7 +177,7 @@ function createMate(ctx, seedData) {
|
|
|
176
177
|
claudeMd += TEAM_SECTION;
|
|
177
178
|
claudeMd += SESSION_MEMORY_SECTION;
|
|
178
179
|
claudeMd += crisisSafety.getSection();
|
|
179
|
-
fs.writeFileSync(
|
|
180
|
+
fs.writeFileSync(resolveMateIdentityFile(mateDir), claudeMd);
|
|
180
181
|
|
|
181
182
|
// Log creation (identity is placeholder, will be replaced by interview)
|
|
182
183
|
logIdentityChange(mateDir, "create_custom", initialIdentity, "");
|
|
@@ -344,7 +345,7 @@ function migrateLegacyMates() {
|
|
|
344
345
|
}
|
|
345
346
|
|
|
346
347
|
/**
|
|
347
|
-
* Strip all system sections from
|
|
348
|
+
* Strip all system sections from AGENTS.md content, returning only identity.
|
|
348
349
|
*/
|
|
349
350
|
function stripAllSystemSections(content) {
|
|
350
351
|
return extractIdentity(content);
|
|
@@ -354,10 +355,10 @@ function stripAllSystemSections(content) {
|
|
|
354
355
|
var _warnedPaths = {};
|
|
355
356
|
|
|
356
357
|
/**
|
|
357
|
-
* Atomic enforce: read
|
|
358
|
+
* Atomic enforce: read AGENTS.md once, enforce all system sections, write once.
|
|
358
359
|
* Includes identity backup, validation, and change tracking.
|
|
359
360
|
* Returns true if the file was modified, false if already correct.
|
|
360
|
-
* @param {string} filePath - path to
|
|
361
|
+
* @param {string} filePath - path to AGENTS.md
|
|
361
362
|
* @param {object} opts - optional { ctx, mateId } for dynamic team section
|
|
362
363
|
*/
|
|
363
364
|
function enforceAllSections(filePath, opts) {
|
|
@@ -529,7 +530,7 @@ function createBuiltinMate(ctx, builtinKey) {
|
|
|
529
530
|
yaml += "autonomy: " + (seedData.autonomy || "always_ask") + "\n";
|
|
530
531
|
fs.writeFileSync(path.join(mateDir, "mate.yaml"), yaml);
|
|
531
532
|
|
|
532
|
-
// Write
|
|
533
|
+
// Write AGENTS.md with full template + system sections
|
|
533
534
|
var claudeMd = def.getClaudeMd();
|
|
534
535
|
var builtinIdentity = claudeMd.trimEnd();
|
|
535
536
|
claudeMd += TEAM_SECTION;
|
|
@@ -537,7 +538,7 @@ function createBuiltinMate(ctx, builtinKey) {
|
|
|
537
538
|
claudeMd += STICKY_NOTES_SECTION;
|
|
538
539
|
claudeMd += DEBATE_AWARENESS_SECTION;
|
|
539
540
|
claudeMd += crisisSafety.getSection();
|
|
540
|
-
fs.writeFileSync(
|
|
541
|
+
fs.writeFileSync(resolveMateIdentityFile(mateDir), claudeMd);
|
|
541
542
|
|
|
542
543
|
// Backup identity and log creation
|
|
543
544
|
backupIdentity(mateDir, builtinIdentity);
|
|
@@ -621,7 +622,7 @@ function syncArchivedBuiltinMates(ctx) {
|
|
|
621
622
|
*
|
|
622
623
|
* What gets synced:
|
|
623
624
|
* - Metadata: bio, name, profile, globalSearch, primary flag
|
|
624
|
-
* -
|
|
625
|
+
* - AGENTS.md identity: re-applied when templateVersion changes
|
|
625
626
|
* - templateVersion is stored on the mate object to track updates
|
|
626
627
|
*
|
|
627
628
|
* What is NOT synced (regular builtin mates):
|
|
@@ -671,10 +672,10 @@ function syncPrimaryMates(ctx) {
|
|
|
671
672
|
}
|
|
672
673
|
}
|
|
673
674
|
|
|
674
|
-
// --- Sync
|
|
675
|
+
// --- Sync AGENTS.md identity (only when templateVersion changes) ---
|
|
675
676
|
// Uses 3-way comparison to preserve user/Ally modifications:
|
|
676
677
|
// - base-template.md = the template that was last synced (what code wrote)
|
|
677
|
-
// - current identity = what's in
|
|
678
|
+
// - current identity = what's in AGENTS.md now (may have been modified)
|
|
678
679
|
// - new template = latest code definition
|
|
679
680
|
//
|
|
680
681
|
// If current identity === old base → user didn't touch it → safe to replace
|
|
@@ -684,7 +685,7 @@ function syncPrimaryMates(ctx) {
|
|
|
684
685
|
|
|
685
686
|
if (latestVersion > currentVersion) {
|
|
686
687
|
var mateDir = getMateDir(ctx, mate.id);
|
|
687
|
-
var claudePath =
|
|
688
|
+
var claudePath = resolveMateIdentityFile(mateDir);
|
|
688
689
|
var basePath = path.join(mateDir, "knowledge", "base-template.md");
|
|
689
690
|
var latestIdentity = def.getClaudeMd().trimEnd();
|
|
690
691
|
|
package/lib/os-users.js
CHANGED
|
@@ -17,20 +17,11 @@ function isSafeLinuxUsername(username) {
|
|
|
17
17
|
// --- Supplementary group inheritance (issue #367) ---
|
|
18
18
|
// Node's child_process { uid, gid } options call setuid/setgid but never
|
|
19
19
|
// initgroups(), so spawned sessions drop all supplementary groups (docker,
|
|
20
|
-
// video, wheel, ...).
|
|
20
|
+
// video, wheel, ...). Route user-switched spawns through
|
|
21
21
|
// `setpriv --init-groups` so the child gets the same groups as a normal login.
|
|
22
|
-
var _inheritGroups = true; // default: behave like a normal login shell
|
|
23
22
|
var _setprivAvail = null;
|
|
24
23
|
var _warnedNoSetpriv = false;
|
|
25
24
|
|
|
26
|
-
function setInheritGroups(value) {
|
|
27
|
-
_inheritGroups = !!value;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function getInheritGroups() {
|
|
31
|
-
return _inheritGroups;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
25
|
function setprivAvailable() {
|
|
35
26
|
if (_setprivAvail !== null) return _setprivAvail;
|
|
36
27
|
try {
|
|
@@ -50,7 +41,7 @@ function setprivAvailable() {
|
|
|
50
41
|
* spawn()/execFileSync()/pty.spawn().
|
|
51
42
|
*
|
|
52
43
|
* - Same-user spawns (no uid/gid in options) are returned unchanged.
|
|
53
|
-
* - When
|
|
44
|
+
* - When setpriv is unavailable, falls back to
|
|
54
45
|
* Node's uid/gid options (primary group only) so behavior degrades safely.
|
|
55
46
|
* Requires the parent process to be root, which is the case in os-users mode.
|
|
56
47
|
*/
|
|
@@ -62,7 +53,7 @@ function wrapSpawnAsUser(command, args, options, setprivCheck) {
|
|
|
62
53
|
if (uid == null || gid == null) return { command: command, args: args, options: options };
|
|
63
54
|
|
|
64
55
|
var hasSetpriv = typeof setprivCheck === "function" ? setprivCheck() : setprivAvailable();
|
|
65
|
-
if (
|
|
56
|
+
if (hasSetpriv) {
|
|
66
57
|
var wrappedArgs = ["--reuid", String(uid), "--regid", String(gid), "--init-groups", "--", command].concat(args);
|
|
67
58
|
var newOpts = Object.assign({}, options);
|
|
68
59
|
delete newOpts.uid;
|
|
@@ -70,9 +61,9 @@ function wrapSpawnAsUser(command, args, options, setprivCheck) {
|
|
|
70
61
|
return { command: "setpriv", args: wrappedArgs, options: newOpts };
|
|
71
62
|
}
|
|
72
63
|
|
|
73
|
-
if (
|
|
64
|
+
if (!_warnedNoSetpriv) {
|
|
74
65
|
_warnedNoSetpriv = true;
|
|
75
|
-
console.warn("[os-users]
|
|
66
|
+
console.warn("[os-users] `setpriv` not found; supplementary groups will be dropped. Install util-linux to restore them.");
|
|
76
67
|
}
|
|
77
68
|
return { command: command, args: args, options: options };
|
|
78
69
|
}
|
|
@@ -673,8 +664,6 @@ module.exports = {
|
|
|
673
664
|
grantAllUsersAccess: grantAllUsersAccess,
|
|
674
665
|
installClaudeCli: installClaudeCli,
|
|
675
666
|
claudeAvailableForUser: claudeAvailableForUser,
|
|
676
|
-
setInheritGroups: setInheritGroups,
|
|
677
|
-
getInheritGroups: getInheritGroups,
|
|
678
667
|
wrapSpawnAsUser: wrapSpawnAsUser,
|
|
679
668
|
deactivateLinuxUser: deactivateLinuxUser,
|
|
680
669
|
ensureProjectsDir: ensureProjectsDir,
|