@askexenow/exe-os 0.8.60 → 0.8.61
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/dist/assets/tmux.conf +37 -0
- package/dist/bin/cli.js +151 -111
- package/dist/bin/exe-new-employee.js +862 -114
- package/dist/bin/exe-start.sh +133 -0
- package/dist/bin/install.js +182 -20
- package/dist/hooks/response-ingest-worker.js +3 -1
- package/dist/hooks/summary-worker.js +3 -1
- package/dist/lib/session-wrappers.js +107 -0
- package/package.json +3 -3
|
@@ -17,15 +17,15 @@ var __export = (target, all) => {
|
|
|
17
17
|
|
|
18
18
|
// src/lib/config.ts
|
|
19
19
|
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
20
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
21
|
-
import
|
|
20
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
|
|
21
|
+
import path2 from "path";
|
|
22
22
|
import os from "os";
|
|
23
23
|
function resolveDataDir() {
|
|
24
24
|
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
25
25
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
26
|
-
const newDir =
|
|
27
|
-
const legacyDir =
|
|
28
|
-
if (!
|
|
26
|
+
const newDir = path2.join(os.homedir(), ".exe-os");
|
|
27
|
+
const legacyDir = path2.join(os.homedir(), ".exe-mem");
|
|
28
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
29
29
|
try {
|
|
30
30
|
renameSync(legacyDir, newDir);
|
|
31
31
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -41,10 +41,10 @@ var init_config = __esm({
|
|
|
41
41
|
"src/lib/config.ts"() {
|
|
42
42
|
"use strict";
|
|
43
43
|
EXE_AI_DIR = resolveDataDir();
|
|
44
|
-
DB_PATH =
|
|
45
|
-
MODELS_DIR =
|
|
46
|
-
CONFIG_PATH =
|
|
47
|
-
LEGACY_LANCE_PATH =
|
|
44
|
+
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
45
|
+
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
46
|
+
CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
|
|
47
|
+
LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
|
|
48
48
|
CURRENT_CONFIG_VERSION = 1;
|
|
49
49
|
DEFAULT_CONFIG = {
|
|
50
50
|
config_version: CURRENT_CONFIG_VERSION,
|
|
@@ -112,6 +112,97 @@ var init_config = __esm({
|
|
|
112
112
|
}
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
+
// src/lib/employees.ts
|
|
116
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
117
|
+
import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3 } from "fs";
|
|
118
|
+
import { execSync } from "child_process";
|
|
119
|
+
import path3 from "path";
|
|
120
|
+
function validateEmployeeName(name) {
|
|
121
|
+
if (!name) {
|
|
122
|
+
return { valid: false, error: "Name is required" };
|
|
123
|
+
}
|
|
124
|
+
if (name.length > 32) {
|
|
125
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
126
|
+
}
|
|
127
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
128
|
+
return {
|
|
129
|
+
valid: false,
|
|
130
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return { valid: true };
|
|
134
|
+
}
|
|
135
|
+
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
136
|
+
if (!existsSync3(employeesPath)) {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
const raw = await readFile2(employeesPath, "utf-8");
|
|
140
|
+
try {
|
|
141
|
+
return JSON.parse(raw);
|
|
142
|
+
} catch {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
147
|
+
await mkdir2(path3.dirname(employeesPath), { recursive: true });
|
|
148
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
149
|
+
}
|
|
150
|
+
function addEmployee(employees, employee) {
|
|
151
|
+
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
152
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
153
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
154
|
+
}
|
|
155
|
+
return [...employees, normalized];
|
|
156
|
+
}
|
|
157
|
+
function findExeBin() {
|
|
158
|
+
try {
|
|
159
|
+
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
160
|
+
} catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function registerBinSymlinks(name) {
|
|
165
|
+
const created = [];
|
|
166
|
+
const skipped = [];
|
|
167
|
+
const errors = [];
|
|
168
|
+
const exeBinPath = findExeBin();
|
|
169
|
+
if (!exeBinPath) {
|
|
170
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
171
|
+
return { created, skipped, errors };
|
|
172
|
+
}
|
|
173
|
+
const binDir = path3.dirname(exeBinPath);
|
|
174
|
+
let target;
|
|
175
|
+
try {
|
|
176
|
+
target = readlinkSync(exeBinPath);
|
|
177
|
+
} catch {
|
|
178
|
+
errors.push("Could not read 'exe' symlink");
|
|
179
|
+
return { created, skipped, errors };
|
|
180
|
+
}
|
|
181
|
+
for (const suffix of ["", "-opencode"]) {
|
|
182
|
+
const linkName = `${name}${suffix}`;
|
|
183
|
+
const linkPath = path3.join(binDir, linkName);
|
|
184
|
+
if (existsSync3(linkPath)) {
|
|
185
|
+
skipped.push(linkName);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
symlinkSync(target, linkPath);
|
|
190
|
+
created.push(linkName);
|
|
191
|
+
} catch (err) {
|
|
192
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return { created, skipped, errors };
|
|
196
|
+
}
|
|
197
|
+
var EMPLOYEES_PATH;
|
|
198
|
+
var init_employees = __esm({
|
|
199
|
+
"src/lib/employees.ts"() {
|
|
200
|
+
"use strict";
|
|
201
|
+
init_config();
|
|
202
|
+
EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
115
206
|
// src/lib/db-retry.ts
|
|
116
207
|
var init_db_retry = __esm({
|
|
117
208
|
"src/lib/db-retry.ts"() {
|
|
@@ -675,17 +766,17 @@ __export(identity_exports, {
|
|
|
675
766
|
listIdentities: () => listIdentities,
|
|
676
767
|
updateIdentity: () => updateIdentity
|
|
677
768
|
});
|
|
678
|
-
import { existsSync as
|
|
679
|
-
import { readdirSync } from "fs";
|
|
680
|
-
import
|
|
769
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
770
|
+
import { readdirSync as readdirSync2 } from "fs";
|
|
771
|
+
import path6 from "path";
|
|
681
772
|
import { createHash } from "crypto";
|
|
682
773
|
function ensureDir() {
|
|
683
|
-
if (!
|
|
684
|
-
|
|
774
|
+
if (!existsSync6(IDENTITY_DIR)) {
|
|
775
|
+
mkdirSync3(IDENTITY_DIR, { recursive: true });
|
|
685
776
|
}
|
|
686
777
|
}
|
|
687
778
|
function identityPath(agentId) {
|
|
688
|
-
return
|
|
779
|
+
return path6.join(IDENTITY_DIR, `${agentId}.md`);
|
|
689
780
|
}
|
|
690
781
|
function parseFrontmatter(raw) {
|
|
691
782
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
@@ -726,8 +817,8 @@ function contentHash(content) {
|
|
|
726
817
|
}
|
|
727
818
|
function getIdentity(agentId) {
|
|
728
819
|
const filePath = identityPath(agentId);
|
|
729
|
-
if (!
|
|
730
|
-
const raw =
|
|
820
|
+
if (!existsSync6(filePath)) return null;
|
|
821
|
+
const raw = readFileSync6(filePath, "utf-8");
|
|
731
822
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
732
823
|
return {
|
|
733
824
|
agentId,
|
|
@@ -741,7 +832,7 @@ async function updateIdentity(agentId, content, updatedBy) {
|
|
|
741
832
|
ensureDir();
|
|
742
833
|
const filePath = identityPath(agentId);
|
|
743
834
|
const hash = contentHash(content);
|
|
744
|
-
|
|
835
|
+
writeFileSync3(filePath, content, "utf-8");
|
|
745
836
|
try {
|
|
746
837
|
const client = getClient();
|
|
747
838
|
await client.execute({
|
|
@@ -758,7 +849,7 @@ async function updateIdentity(agentId, content, updatedBy) {
|
|
|
758
849
|
}
|
|
759
850
|
function listIdentities() {
|
|
760
851
|
ensureDir();
|
|
761
|
-
const files =
|
|
852
|
+
const files = readdirSync2(IDENTITY_DIR).filter((f) => f.endsWith(".md"));
|
|
762
853
|
const results = [];
|
|
763
854
|
for (const file of files) {
|
|
764
855
|
const agentId = file.replace(".md", "");
|
|
@@ -797,99 +888,746 @@ var init_identity = __esm({
|
|
|
797
888
|
"use strict";
|
|
798
889
|
init_config();
|
|
799
890
|
init_database();
|
|
800
|
-
IDENTITY_DIR =
|
|
891
|
+
IDENTITY_DIR = path6.join(EXE_AI_DIR, "identity");
|
|
801
892
|
}
|
|
802
893
|
});
|
|
803
894
|
|
|
804
|
-
// src/
|
|
805
|
-
import
|
|
806
|
-
import
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
|
|
895
|
+
// src/lib/agent-symlinks.ts
|
|
896
|
+
import os2 from "os";
|
|
897
|
+
import path7 from "path";
|
|
898
|
+
import {
|
|
899
|
+
existsSync as existsSync7,
|
|
900
|
+
lstatSync,
|
|
901
|
+
mkdirSync as mkdirSync4,
|
|
902
|
+
readlinkSync as readlinkSync2,
|
|
903
|
+
symlinkSync as symlinkSync2
|
|
904
|
+
} from "fs";
|
|
905
|
+
function claudeAgentsDir(homeDir) {
|
|
906
|
+
return path7.join(homeDir, ".claude", "agents");
|
|
907
|
+
}
|
|
908
|
+
function identitySourcePath(homeDir, agentId) {
|
|
909
|
+
return path7.join(homeDir, ".exe-os", "identity", `${agentId}.md`);
|
|
910
|
+
}
|
|
911
|
+
function claudeAgentLinkPath(homeDir, agentId) {
|
|
912
|
+
return path7.join(claudeAgentsDir(homeDir), `${agentId}.md`);
|
|
913
|
+
}
|
|
914
|
+
function ensureAgentSymlink(agentId, homeDir = os2.homedir()) {
|
|
915
|
+
const target = identitySourcePath(homeDir, agentId);
|
|
916
|
+
const link = claudeAgentLinkPath(homeDir, agentId);
|
|
917
|
+
mkdirSync4(claudeAgentsDir(homeDir), { recursive: true });
|
|
918
|
+
if (existsSync7(link)) {
|
|
919
|
+
let stat;
|
|
920
|
+
try {
|
|
921
|
+
stat = lstatSync(link);
|
|
922
|
+
} catch {
|
|
923
|
+
return { agentId, action: "conflict", target, link, conflict: "stat_failed" };
|
|
924
|
+
}
|
|
925
|
+
if (!stat.isSymbolicLink()) {
|
|
926
|
+
return { agentId, action: "conflict", target, link, conflict: "regular_file" };
|
|
927
|
+
}
|
|
928
|
+
let currentTarget;
|
|
929
|
+
try {
|
|
930
|
+
currentTarget = readlinkSync2(link);
|
|
931
|
+
} catch {
|
|
932
|
+
return { agentId, action: "conflict", target, link, conflict: "readlink_failed" };
|
|
933
|
+
}
|
|
934
|
+
if (currentTarget === target) {
|
|
935
|
+
return { agentId, action: "already_correct", target, link };
|
|
936
|
+
}
|
|
937
|
+
return { agentId, action: "conflict", target, link, conflict: `points_to:${currentTarget}` };
|
|
821
938
|
}
|
|
822
|
-
|
|
939
|
+
try {
|
|
940
|
+
symlinkSync2(target, link);
|
|
941
|
+
} catch (err) {
|
|
823
942
|
return {
|
|
824
|
-
|
|
825
|
-
|
|
943
|
+
agentId,
|
|
944
|
+
action: "conflict",
|
|
945
|
+
target,
|
|
946
|
+
link,
|
|
947
|
+
conflict: err instanceof Error ? err.message : String(err)
|
|
826
948
|
};
|
|
827
949
|
}
|
|
828
|
-
return {
|
|
950
|
+
return { agentId, action: "created", target, link };
|
|
829
951
|
}
|
|
830
|
-
async function
|
|
831
|
-
|
|
832
|
-
|
|
952
|
+
async function ensureAllAgentSymlinks(homeDir = os2.homedir()) {
|
|
953
|
+
const employees = await loadEmployees();
|
|
954
|
+
return employees.map((emp) => ensureAgentSymlink(emp.name, homeDir));
|
|
955
|
+
}
|
|
956
|
+
var init_agent_symlinks = __esm({
|
|
957
|
+
"src/lib/agent-symlinks.ts"() {
|
|
958
|
+
"use strict";
|
|
959
|
+
init_employees();
|
|
833
960
|
}
|
|
834
|
-
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
// src/lib/mcp-prefix.ts
|
|
964
|
+
function expandDualPrefixTools(shortNames) {
|
|
965
|
+
const out = [];
|
|
966
|
+
for (const name of shortNames) {
|
|
967
|
+
for (const prefix of MCP_TOOL_PREFIXES) {
|
|
968
|
+
out.push(prefix + name);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return out;
|
|
972
|
+
}
|
|
973
|
+
var MCP_PRIMARY_KEY, MCP_LEGACY_KEY, MCP_TOOL_PREFIXES;
|
|
974
|
+
var init_mcp_prefix = __esm({
|
|
975
|
+
"src/lib/mcp-prefix.ts"() {
|
|
976
|
+
"use strict";
|
|
977
|
+
MCP_PRIMARY_KEY = "exe-os";
|
|
978
|
+
MCP_LEGACY_KEY = "exe-mem";
|
|
979
|
+
MCP_TOOL_PREFIXES = [
|
|
980
|
+
`mcp__${MCP_PRIMARY_KEY}__`,
|
|
981
|
+
`mcp__${MCP_LEGACY_KEY}__`
|
|
982
|
+
];
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
// src/adapters/claude/installer.ts
|
|
987
|
+
var installer_exports = {};
|
|
988
|
+
__export(installer_exports, {
|
|
989
|
+
copySlashCommands: () => copySlashCommands,
|
|
990
|
+
mergeHooks: () => mergeHooks,
|
|
991
|
+
registerMcpServer: () => registerMcpServer,
|
|
992
|
+
resolvePackageRoot: () => resolvePackageRoot,
|
|
993
|
+
runInstaller: () => runInstaller,
|
|
994
|
+
setupTmux: () => setupTmux
|
|
995
|
+
});
|
|
996
|
+
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, readdir } from "fs/promises";
|
|
997
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync4, copyFileSync, mkdirSync as mkdirSync5 } from "fs";
|
|
998
|
+
import path8 from "path";
|
|
999
|
+
import os3 from "os";
|
|
1000
|
+
import { execSync as execSync2 } from "child_process";
|
|
1001
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1002
|
+
function resolvePackageRoot() {
|
|
1003
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
1004
|
+
let dir = path8.dirname(thisFile);
|
|
1005
|
+
const root = path8.parse(dir).root;
|
|
1006
|
+
while (dir !== root) {
|
|
1007
|
+
const pkgPath = path8.join(dir, "package.json");
|
|
1008
|
+
if (existsSync8(pkgPath)) {
|
|
1009
|
+
try {
|
|
1010
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
1011
|
+
if (pkg.name === "@askexenow/exe-os" || pkg.name === "exe-os") return dir;
|
|
1012
|
+
} catch {
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
dir = path8.dirname(dir);
|
|
1016
|
+
}
|
|
1017
|
+
return path8.resolve(path8.dirname(thisFile), "..", "..", "..");
|
|
1018
|
+
}
|
|
1019
|
+
async function copySlashCommands(packageRoot, homeDir = os3.homedir()) {
|
|
1020
|
+
let copied = 0;
|
|
1021
|
+
let skipped = 0;
|
|
1022
|
+
const skillsBase = path8.join(homeDir, ".claude", "skills");
|
|
1023
|
+
const exeDir = path8.join(packageRoot, "src", "commands", "exe");
|
|
1024
|
+
if (existsSync8(exeDir)) {
|
|
1025
|
+
const entries = await readdir(exeDir);
|
|
1026
|
+
const mdFiles = entries.filter((f) => f.endsWith(".md"));
|
|
1027
|
+
for (const file of mdFiles) {
|
|
1028
|
+
const name = file.replace(".md", "");
|
|
1029
|
+
const destDir = path8.join(skillsBase, `exe-${name}`);
|
|
1030
|
+
await mkdir3(destDir, { recursive: true });
|
|
1031
|
+
const srcPath = path8.join(exeDir, file);
|
|
1032
|
+
const destPath = path8.join(destDir, "SKILL.md");
|
|
1033
|
+
const result = await copyAsSkill(srcPath, destPath, `exe-${name}`);
|
|
1034
|
+
if (result) copied++;
|
|
1035
|
+
else skipped++;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
const topLevelSrc = path8.join(packageRoot, "src", "commands", "exe.md");
|
|
1039
|
+
if (existsSync8(topLevelSrc)) {
|
|
1040
|
+
const destDir = path8.join(skillsBase, "exe");
|
|
1041
|
+
await mkdir3(destDir, { recursive: true });
|
|
1042
|
+
const destPath = path8.join(destDir, "SKILL.md");
|
|
1043
|
+
const result = await copyAsSkill(topLevelSrc, destPath, "exe");
|
|
1044
|
+
if (result) copied++;
|
|
1045
|
+
else skipped++;
|
|
1046
|
+
}
|
|
1047
|
+
return { copied, skipped };
|
|
1048
|
+
}
|
|
1049
|
+
async function copyAsSkill(srcPath, destPath, skillName) {
|
|
1050
|
+
let content = await readFile3(srcPath, "utf-8");
|
|
1051
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1052
|
+
if (fmMatch?.[1]) {
|
|
1053
|
+
const fm = fmMatch[1];
|
|
1054
|
+
if (fm.includes("name:")) {
|
|
1055
|
+
content = content.replace(
|
|
1056
|
+
/^(---\n[\s\S]*?)name:\s*[^\n]+/,
|
|
1057
|
+
`$1name: ${skillName}`
|
|
1058
|
+
);
|
|
1059
|
+
} else {
|
|
1060
|
+
content = content.replace(/^---\n/, `---
|
|
1061
|
+
name: ${skillName}
|
|
1062
|
+
`);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
if (existsSync8(destPath)) {
|
|
1066
|
+
const existing = await readFile3(destPath, "utf-8");
|
|
1067
|
+
if (existing === content) return false;
|
|
1068
|
+
}
|
|
1069
|
+
await writeFile3(destPath, content);
|
|
1070
|
+
return true;
|
|
1071
|
+
}
|
|
1072
|
+
async function registerMcpServer(packageRoot, homeDir = os3.homedir()) {
|
|
1073
|
+
const claudeJsonPath = path8.join(homeDir, ".claude.json");
|
|
1074
|
+
let claudeJson = {};
|
|
1075
|
+
if (existsSync8(claudeJsonPath)) {
|
|
1076
|
+
try {
|
|
1077
|
+
claudeJson = JSON.parse(await readFile3(claudeJsonPath, "utf-8"));
|
|
1078
|
+
} catch {
|
|
1079
|
+
claudeJson = {};
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
if (!claudeJson.mcpServers) {
|
|
1083
|
+
claudeJson.mcpServers = {};
|
|
1084
|
+
}
|
|
1085
|
+
const newEntry = {
|
|
1086
|
+
type: "stdio",
|
|
1087
|
+
command: "node",
|
|
1088
|
+
args: [path8.join(packageRoot, "dist", "mcp", "server.js")],
|
|
1089
|
+
env: {}
|
|
1090
|
+
};
|
|
1091
|
+
const legacy = claudeJson.mcpServers[MCP_LEGACY_KEY];
|
|
1092
|
+
const current = claudeJson.mcpServers[MCP_PRIMARY_KEY];
|
|
1093
|
+
const currentMatches = current && JSON.stringify(current) === JSON.stringify(newEntry);
|
|
1094
|
+
if (currentMatches && !legacy) {
|
|
1095
|
+
return false;
|
|
1096
|
+
}
|
|
1097
|
+
claudeJson.mcpServers[MCP_PRIMARY_KEY] = newEntry;
|
|
1098
|
+
if (legacy) {
|
|
1099
|
+
delete claudeJson.mcpServers[MCP_LEGACY_KEY];
|
|
1100
|
+
}
|
|
1101
|
+
await writeFile3(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
1102
|
+
return true;
|
|
1103
|
+
}
|
|
1104
|
+
async function mergeHooks(packageRoot, homeDir = os3.homedir()) {
|
|
1105
|
+
const settingsPath = path8.join(homeDir, ".claude", "settings.json");
|
|
1106
|
+
let settings = {};
|
|
1107
|
+
if (existsSync8(settingsPath)) {
|
|
1108
|
+
try {
|
|
1109
|
+
settings = JSON.parse(await readFile3(settingsPath, "utf-8"));
|
|
1110
|
+
} catch {
|
|
1111
|
+
settings = {};
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
if (!settings.hooks) {
|
|
1115
|
+
settings.hooks = {};
|
|
1116
|
+
}
|
|
1117
|
+
if (settings.hooks && typeof settings.hooks !== "object") {
|
|
1118
|
+
console.warn("[exe-os] Unexpected hooks schema in settings.json \u2014 skipping hook installation");
|
|
1119
|
+
return { added: 0, skipped: 0 };
|
|
1120
|
+
}
|
|
1121
|
+
const hooksToRegister = [
|
|
1122
|
+
{
|
|
1123
|
+
event: "PostToolUse",
|
|
1124
|
+
group: {
|
|
1125
|
+
matcher: "Bash|Edit|Write|Read|Glob|Grep|Agent|mcp__.*",
|
|
1126
|
+
hooks: [
|
|
1127
|
+
{
|
|
1128
|
+
type: "command",
|
|
1129
|
+
command: `node "${path8.join(packageRoot, "dist", "hooks", "ingest.js")}"`
|
|
1130
|
+
},
|
|
1131
|
+
{
|
|
1132
|
+
type: "command",
|
|
1133
|
+
command: `node "${path8.join(packageRoot, "dist", "hooks", "error-recall.js")}"`
|
|
1134
|
+
}
|
|
1135
|
+
]
|
|
1136
|
+
},
|
|
1137
|
+
marker: "dist/hooks/ingest.js"
|
|
1138
|
+
},
|
|
1139
|
+
{
|
|
1140
|
+
event: "SessionStart",
|
|
1141
|
+
group: {
|
|
1142
|
+
hooks: [
|
|
1143
|
+
{
|
|
1144
|
+
type: "command",
|
|
1145
|
+
command: `node "${path8.join(packageRoot, "dist", "hooks", "session-start.js")}"`,
|
|
1146
|
+
timeout: 1e4
|
|
1147
|
+
}
|
|
1148
|
+
]
|
|
1149
|
+
},
|
|
1150
|
+
marker: "dist/hooks/session-start.js"
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
event: "UserPromptSubmit",
|
|
1154
|
+
group: {
|
|
1155
|
+
hooks: [
|
|
1156
|
+
{
|
|
1157
|
+
type: "command",
|
|
1158
|
+
command: `node "${path8.join(packageRoot, "dist", "hooks", "prompt-submit.js")}"`
|
|
1159
|
+
}
|
|
1160
|
+
]
|
|
1161
|
+
},
|
|
1162
|
+
marker: "dist/hooks/prompt-submit.js"
|
|
1163
|
+
},
|
|
1164
|
+
{
|
|
1165
|
+
event: "UserPromptSubmit",
|
|
1166
|
+
group: {
|
|
1167
|
+
hooks: [
|
|
1168
|
+
{
|
|
1169
|
+
type: "command",
|
|
1170
|
+
command: `node "${path8.join(packageRoot, "dist", "hooks", "exe-heartbeat-hook.js")}"`,
|
|
1171
|
+
timeout: 5e3
|
|
1172
|
+
}
|
|
1173
|
+
]
|
|
1174
|
+
},
|
|
1175
|
+
marker: "dist/hooks/exe-heartbeat-hook.js"
|
|
1176
|
+
},
|
|
1177
|
+
{
|
|
1178
|
+
event: "Stop",
|
|
1179
|
+
group: {
|
|
1180
|
+
hooks: [
|
|
1181
|
+
{
|
|
1182
|
+
type: "command",
|
|
1183
|
+
command: `node "${path8.join(packageRoot, "dist", "hooks", "stop.js")}"`
|
|
1184
|
+
}
|
|
1185
|
+
]
|
|
1186
|
+
},
|
|
1187
|
+
marker: "dist/hooks/stop.js"
|
|
1188
|
+
},
|
|
1189
|
+
{
|
|
1190
|
+
event: "PreToolUse",
|
|
1191
|
+
group: {
|
|
1192
|
+
matcher: "Write|Edit|Read|Bash",
|
|
1193
|
+
hooks: [
|
|
1194
|
+
{
|
|
1195
|
+
type: "command",
|
|
1196
|
+
command: `node "${path8.join(packageRoot, "dist", "hooks", "pre-tool-use.js")}"`
|
|
1197
|
+
}
|
|
1198
|
+
]
|
|
1199
|
+
},
|
|
1200
|
+
marker: "dist/hooks/pre-tool-use.js"
|
|
1201
|
+
},
|
|
1202
|
+
{
|
|
1203
|
+
event: "SubagentStop",
|
|
1204
|
+
group: {
|
|
1205
|
+
hooks: [
|
|
1206
|
+
{
|
|
1207
|
+
type: "command",
|
|
1208
|
+
command: `node "${path8.join(packageRoot, "dist", "hooks", "subagent-stop.js")}"`
|
|
1209
|
+
}
|
|
1210
|
+
]
|
|
1211
|
+
},
|
|
1212
|
+
marker: "dist/hooks/subagent-stop.js"
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
event: "PreCompact",
|
|
1216
|
+
group: {
|
|
1217
|
+
hooks: [
|
|
1218
|
+
{
|
|
1219
|
+
type: "command",
|
|
1220
|
+
command: `node "${path8.join(packageRoot, "dist", "hooks", "pre-compact.js")}"`,
|
|
1221
|
+
timeout: 1e4
|
|
1222
|
+
}
|
|
1223
|
+
]
|
|
1224
|
+
},
|
|
1225
|
+
marker: "dist/hooks/pre-compact.js"
|
|
1226
|
+
},
|
|
1227
|
+
{
|
|
1228
|
+
event: "SessionEnd",
|
|
1229
|
+
group: {
|
|
1230
|
+
hooks: [
|
|
1231
|
+
{
|
|
1232
|
+
type: "command",
|
|
1233
|
+
command: `node "${path8.join(packageRoot, "dist", "hooks", "session-end.js")}"`
|
|
1234
|
+
}
|
|
1235
|
+
]
|
|
1236
|
+
},
|
|
1237
|
+
marker: "dist/hooks/session-end.js"
|
|
1238
|
+
},
|
|
1239
|
+
{
|
|
1240
|
+
event: "Notification",
|
|
1241
|
+
group: {
|
|
1242
|
+
hooks: [
|
|
1243
|
+
{
|
|
1244
|
+
type: "command",
|
|
1245
|
+
command: `node "${path8.join(packageRoot, "dist", "hooks", "notification.js")}"`
|
|
1246
|
+
}
|
|
1247
|
+
]
|
|
1248
|
+
},
|
|
1249
|
+
marker: "dist/hooks/notification.js"
|
|
1250
|
+
},
|
|
1251
|
+
{
|
|
1252
|
+
event: "PostCompact",
|
|
1253
|
+
group: {
|
|
1254
|
+
hooks: [
|
|
1255
|
+
{
|
|
1256
|
+
type: "command",
|
|
1257
|
+
command: `node "${path8.join(packageRoot, "dist", "hooks", "post-compact.js")}"`,
|
|
1258
|
+
timeout: 1e4
|
|
1259
|
+
}
|
|
1260
|
+
]
|
|
1261
|
+
},
|
|
1262
|
+
marker: "dist/hooks/post-compact.js"
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
event: "InstructionsLoaded",
|
|
1266
|
+
group: {
|
|
1267
|
+
hooks: [
|
|
1268
|
+
{
|
|
1269
|
+
type: "command",
|
|
1270
|
+
command: `node "${path8.join(packageRoot, "dist", "hooks", "instructions-loaded.js")}"`
|
|
1271
|
+
}
|
|
1272
|
+
]
|
|
1273
|
+
},
|
|
1274
|
+
marker: "dist/hooks/instructions-loaded.js"
|
|
1275
|
+
}
|
|
1276
|
+
];
|
|
1277
|
+
let added = 0;
|
|
1278
|
+
let skipped = 0;
|
|
1279
|
+
for (const { event, group, marker } of hooksToRegister) {
|
|
1280
|
+
if (!settings.hooks[event]) {
|
|
1281
|
+
settings.hooks[event] = [];
|
|
1282
|
+
}
|
|
1283
|
+
if (!Array.isArray(settings.hooks[event])) {
|
|
1284
|
+
console.warn(`[exe-os] Hook event "${event}" has unexpected structure \u2014 skipping`);
|
|
1285
|
+
skipped++;
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
const existing = settings.hooks[event];
|
|
1289
|
+
const correctCommand = group.hooks[0]?.command ?? "";
|
|
1290
|
+
const alreadyCorrect = existing.some(
|
|
1291
|
+
(g) => g.hooks.some((h) => h.command === correctCommand)
|
|
1292
|
+
);
|
|
1293
|
+
if (alreadyCorrect) {
|
|
1294
|
+
skipped++;
|
|
1295
|
+
} else {
|
|
1296
|
+
settings.hooks[event] = existing.filter(
|
|
1297
|
+
(g) => !g.hooks.some((h) => h.command.includes(marker))
|
|
1298
|
+
);
|
|
1299
|
+
settings.hooks[event].push(group);
|
|
1300
|
+
added++;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
const perms = settings.permissions;
|
|
1304
|
+
if (!perms) {
|
|
1305
|
+
settings.permissions = {};
|
|
1306
|
+
}
|
|
1307
|
+
const permissions = settings.permissions;
|
|
1308
|
+
if (!Array.isArray(permissions.allow)) {
|
|
1309
|
+
permissions.allow = [];
|
|
1310
|
+
}
|
|
1311
|
+
const toolNames = [
|
|
1312
|
+
// Core memory
|
|
1313
|
+
"store_memory",
|
|
1314
|
+
"recall_my_memory",
|
|
1315
|
+
"commit_to_long_term_memory",
|
|
1316
|
+
"consolidate_memories",
|
|
1317
|
+
"ask_team_memory",
|
|
1318
|
+
"get_session_context",
|
|
1319
|
+
// Tasks
|
|
1320
|
+
"create_task",
|
|
1321
|
+
"list_tasks",
|
|
1322
|
+
"get_task",
|
|
1323
|
+
"update_task",
|
|
1324
|
+
"close_task",
|
|
1325
|
+
"checkpoint_task",
|
|
1326
|
+
// Behaviors
|
|
1327
|
+
"store_behavior",
|
|
1328
|
+
"deactivate_behavior",
|
|
1329
|
+
"list_behaviors",
|
|
1330
|
+
// Identity
|
|
1331
|
+
"get_identity",
|
|
1332
|
+
"update_identity",
|
|
1333
|
+
// Messaging
|
|
1334
|
+
"send_message",
|
|
1335
|
+
"acknowledge_messages",
|
|
1336
|
+
"send_whatsapp",
|
|
1337
|
+
"query_conversations",
|
|
1338
|
+
// Reminders + triggers
|
|
1339
|
+
"create_reminder",
|
|
1340
|
+
"complete_reminder",
|
|
1341
|
+
"list_reminders",
|
|
1342
|
+
"create_trigger",
|
|
1343
|
+
"list_triggers",
|
|
1344
|
+
// GraphRAG
|
|
1345
|
+
"query_relationships",
|
|
1346
|
+
"merge_entities",
|
|
1347
|
+
// Documents + wiki
|
|
1348
|
+
"ingest_document",
|
|
1349
|
+
"list_documents",
|
|
1350
|
+
"purge_document",
|
|
1351
|
+
"rerank_documents",
|
|
1352
|
+
"set_document_importance",
|
|
1353
|
+
"create_wiki_page",
|
|
1354
|
+
"update_wiki_page",
|
|
1355
|
+
"get_wiki_page",
|
|
1356
|
+
"list_wiki_pages",
|
|
1357
|
+
// System
|
|
1358
|
+
"load_skill",
|
|
1359
|
+
"apply_starter_pack",
|
|
1360
|
+
"resume_employee",
|
|
1361
|
+
"deploy_client"
|
|
1362
|
+
];
|
|
1363
|
+
const allowList = permissions.allow;
|
|
1364
|
+
for (const tool of expandDualPrefixTools(toolNames)) {
|
|
1365
|
+
if (!allowList.includes(tool)) {
|
|
1366
|
+
allowList.push(tool);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
await mkdir3(path8.dirname(settingsPath), { recursive: true });
|
|
1370
|
+
await writeFile3(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
1371
|
+
return { added, skipped };
|
|
1372
|
+
}
|
|
1373
|
+
async function injectOrchestrationRules(homeDir) {
|
|
1374
|
+
const claudeDir = path8.join(homeDir, ".claude");
|
|
1375
|
+
const claudeMdPath = path8.join(claudeDir, "CLAUDE.md");
|
|
1376
|
+
await mkdir3(claudeDir, { recursive: true });
|
|
1377
|
+
let existing = "";
|
|
835
1378
|
try {
|
|
836
|
-
|
|
1379
|
+
existing = await readFile3(claudeMdPath, "utf-8");
|
|
837
1380
|
} catch {
|
|
838
|
-
return [];
|
|
839
1381
|
}
|
|
1382
|
+
const startIdx = existing.indexOf(EXE_SECTION_START);
|
|
1383
|
+
const endIdx = existing.indexOf(EXE_SECTION_END);
|
|
1384
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
1385
|
+
const currentSection = existing.slice(startIdx, endIdx + EXE_SECTION_END.length);
|
|
1386
|
+
if (currentSection === ORCHESTRATION_RULES) {
|
|
1387
|
+
return "unchanged";
|
|
1388
|
+
}
|
|
1389
|
+
const updated = existing.slice(0, startIdx) + ORCHESTRATION_RULES + existing.slice(endIdx + EXE_SECTION_END.length);
|
|
1390
|
+
await writeFile3(claudeMdPath, updated, "utf-8");
|
|
1391
|
+
return "updated";
|
|
1392
|
+
}
|
|
1393
|
+
const separator = existing.length > 0 && !existing.endsWith("\n\n") ? "\n\n" : "";
|
|
1394
|
+
await writeFile3(claudeMdPath, existing + separator + ORCHESTRATION_RULES + "\n", "utf-8");
|
|
1395
|
+
return "injected";
|
|
840
1396
|
}
|
|
841
|
-
async function
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1397
|
+
async function runInstaller(homeDir) {
|
|
1398
|
+
const packageRoot = resolvePackageRoot();
|
|
1399
|
+
process.stderr.write(`exe-os installer v1.3.0
|
|
1400
|
+
`);
|
|
1401
|
+
process.stderr.write(`Package root: ${packageRoot}
|
|
1402
|
+
|
|
1403
|
+
`);
|
|
1404
|
+
const cmdResult = await copySlashCommands(packageRoot, homeDir);
|
|
1405
|
+
process.stderr.write(
|
|
1406
|
+
`Slash commands: ${cmdResult.copied} copied, ${cmdResult.skipped} unchanged
|
|
1407
|
+
`
|
|
1408
|
+
);
|
|
1409
|
+
const mcpChanged = await registerMcpServer(packageRoot, homeDir);
|
|
1410
|
+
process.stderr.write(
|
|
1411
|
+
`MCP server: ${mcpChanged ? "registered" : "already registered"}
|
|
1412
|
+
`
|
|
1413
|
+
);
|
|
1414
|
+
const hookResult = await mergeHooks(packageRoot, homeDir);
|
|
1415
|
+
process.stderr.write(
|
|
1416
|
+
`Hooks: ${hookResult.added} added, ${hookResult.skipped} unchanged
|
|
1417
|
+
`
|
|
1418
|
+
);
|
|
1419
|
+
const resolvedHome = homeDir ?? os3.homedir();
|
|
1420
|
+
const exeWorkspace = path8.join(resolvedHome, "exe");
|
|
1421
|
+
if (!existsSync8(exeWorkspace)) {
|
|
1422
|
+
try {
|
|
1423
|
+
await mkdir3(path8.join(exeWorkspace, "content"), { recursive: true });
|
|
1424
|
+
await mkdir3(path8.join(exeWorkspace, "operations"), { recursive: true });
|
|
1425
|
+
await mkdir3(path8.join(exeWorkspace, "output"), { recursive: true });
|
|
1426
|
+
process.stderr.write(
|
|
1427
|
+
`Created ~/exe/ \u2014 your automation workspace for non-code projects
|
|
1428
|
+
`
|
|
1429
|
+
);
|
|
1430
|
+
} catch {
|
|
1431
|
+
}
|
|
849
1432
|
}
|
|
850
|
-
|
|
1433
|
+
const claudeMdResult = await injectOrchestrationRules(resolvedHome);
|
|
1434
|
+
process.stderr.write(
|
|
1435
|
+
`CLAUDE.md orchestration: ${claudeMdResult}
|
|
1436
|
+
`
|
|
1437
|
+
);
|
|
1438
|
+
const symlinkResults = await ensureAllAgentSymlinks(resolvedHome);
|
|
1439
|
+
process.stderr.write(
|
|
1440
|
+
`Agent symlinks: ${summarizeSymlinkResults(symlinkResults)}
|
|
1441
|
+
`
|
|
1442
|
+
);
|
|
1443
|
+
process.stderr.write(`
|
|
1444
|
+
exe-os installed successfully.
|
|
1445
|
+
`);
|
|
851
1446
|
}
|
|
852
|
-
function
|
|
1447
|
+
function setupTmux(home) {
|
|
1448
|
+
const homeDir = home ?? os3.homedir();
|
|
1449
|
+
const exeDir = path8.join(homeDir, ".exe-os");
|
|
1450
|
+
const exeTmuxConf = path8.join(exeDir, "tmux.conf");
|
|
1451
|
+
const userTmuxConf = path8.join(homeDir, ".tmux.conf");
|
|
1452
|
+
const backupPath = path8.join(homeDir, ".tmux.conf.backup");
|
|
1453
|
+
const sourceLine = "source-file ~/.exe-os/tmux.conf";
|
|
1454
|
+
const pkgRoot = resolvePackageRoot();
|
|
1455
|
+
const assetPath = path8.join(pkgRoot, "dist", "assets", "tmux.conf");
|
|
1456
|
+
if (!existsSync8(assetPath)) {
|
|
1457
|
+
process.stderr.write(`exe-os: tmux.conf asset not found at ${assetPath} \u2014 skipping tmux setup
|
|
1458
|
+
`);
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
mkdirSync5(exeDir, { recursive: true });
|
|
1462
|
+
copyFileSync(assetPath, exeTmuxConf);
|
|
1463
|
+
if (existsSync8(userTmuxConf)) {
|
|
1464
|
+
const existing = readFileSync7(userTmuxConf, "utf8");
|
|
1465
|
+
if (!existing.includes(sourceLine)) {
|
|
1466
|
+
if (!existsSync8(backupPath)) {
|
|
1467
|
+
copyFileSync(userTmuxConf, backupPath);
|
|
1468
|
+
process.stderr.write(`exe-os: backed up existing tmux config to ${backupPath}
|
|
1469
|
+
`);
|
|
1470
|
+
}
|
|
1471
|
+
writeFileSync4(userTmuxConf, `${sourceLine}
|
|
1472
|
+
${existing}`);
|
|
1473
|
+
}
|
|
1474
|
+
} else {
|
|
1475
|
+
writeFileSync4(userTmuxConf, `# Exe OS tmux defaults \u2014 remove this line to use your own config
|
|
1476
|
+
${sourceLine}
|
|
1477
|
+
`);
|
|
1478
|
+
}
|
|
853
1479
|
try {
|
|
854
|
-
|
|
1480
|
+
execSync2(`tmux source-file ${exeTmuxConf} 2>/dev/null`);
|
|
855
1481
|
} catch {
|
|
856
|
-
return null;
|
|
857
1482
|
}
|
|
1483
|
+
process.stderr.write("exe-os: tmux config installed\n");
|
|
858
1484
|
}
|
|
859
|
-
function
|
|
860
|
-
|
|
861
|
-
const
|
|
862
|
-
const
|
|
863
|
-
const
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1485
|
+
function summarizeSymlinkResults(results) {
|
|
1486
|
+
if (results.length === 0) return "no employees in roster";
|
|
1487
|
+
const created = results.filter((r) => r.action === "created").length;
|
|
1488
|
+
const already = results.filter((r) => r.action === "already_correct").length;
|
|
1489
|
+
const conflicts = results.filter((r) => r.action === "conflict");
|
|
1490
|
+
let summary = `${created} created, ${already} already linked`;
|
|
1491
|
+
if (conflicts.length > 0) {
|
|
1492
|
+
const details = conflicts.map((r) => `${r.agentId} (${r.conflict ?? "unknown"})`).join(", ");
|
|
1493
|
+
summary += `, ${conflicts.length} conflict(s): ${details}`;
|
|
867
1494
|
}
|
|
868
|
-
|
|
869
|
-
|
|
1495
|
+
return summary;
|
|
1496
|
+
}
|
|
1497
|
+
var EXE_SECTION_START, EXE_SECTION_END, ORCHESTRATION_RULES;
|
|
1498
|
+
var init_installer = __esm({
|
|
1499
|
+
"src/adapters/claude/installer.ts"() {
|
|
1500
|
+
"use strict";
|
|
1501
|
+
init_agent_symlinks();
|
|
1502
|
+
init_mcp_prefix();
|
|
1503
|
+
EXE_SECTION_START = "<!-- exe-os:orchestration-start -->";
|
|
1504
|
+
EXE_SECTION_END = "<!-- exe-os:orchestration-end -->";
|
|
1505
|
+
ORCHESTRATION_RULES = `${EXE_SECTION_START}
|
|
1506
|
+
## exe-os Multi-Agent Orchestration (auto-managed \u2014 do not edit)
|
|
1507
|
+
|
|
1508
|
+
These rules are injected by exe-os and override default behavior for multi-agent coordination.
|
|
1509
|
+
|
|
1510
|
+
- **Session routing is deterministic.** When working in exeN (e.g. exe1), ALL employee sessions use the same number: yoshi-exe1, tom-exe1, mari-exe1. NEVER reference, consider, or dispatch to employee sessions from other project numbers. This is not a choice \u2014 it is a hard constraint.
|
|
1511
|
+
- **Always use create_task to assign work.** Never use messages, tmux send-keys, or ad-hoc instructions as a substitute for tasks.
|
|
1512
|
+
- **Never modify another agent's in-progress task.** Create a new task instead. Modifying active tasks causes race conditions.
|
|
1513
|
+
- **Chain of command:** founder \u2192 exe \u2192 yoshi \u2192 engineers. Exe does not assign directly to tom or other engineers below yoshi.
|
|
1514
|
+
- **Verify dispatch.** After every create_task, confirm the employee received and started the task via tmux capture-pane.
|
|
1515
|
+
${EXE_SECTION_END}`;
|
|
1516
|
+
}
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
// src/bin/exe-new-employee.ts
|
|
1520
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
|
|
1521
|
+
import path9 from "path";
|
|
1522
|
+
|
|
1523
|
+
// src/lib/session-wrappers.ts
|
|
1524
|
+
import {
|
|
1525
|
+
existsSync,
|
|
1526
|
+
readFileSync,
|
|
1527
|
+
writeFileSync,
|
|
1528
|
+
mkdirSync,
|
|
1529
|
+
chmodSync,
|
|
1530
|
+
readdirSync,
|
|
1531
|
+
unlinkSync
|
|
1532
|
+
} from "fs";
|
|
1533
|
+
import path from "path";
|
|
1534
|
+
import { homedir } from "os";
|
|
1535
|
+
var MAX_N = 9;
|
|
1536
|
+
function generateSessionWrappers(packageRoot, homeDir) {
|
|
1537
|
+
const home = homeDir ?? homedir();
|
|
1538
|
+
const binDir = path.join(home, ".exe-os", "bin");
|
|
1539
|
+
const rosterPath = path.join(home, ".exe-os", "exe-employees.json");
|
|
1540
|
+
mkdirSync(binDir, { recursive: true });
|
|
1541
|
+
const exeStartDst = path.join(binDir, "exe-start");
|
|
1542
|
+
const candidates = [
|
|
1543
|
+
path.join(packageRoot, "dist", "bin", "exe-start.sh"),
|
|
1544
|
+
path.join(packageRoot, "src", "bin", "exe-start.sh")
|
|
1545
|
+
];
|
|
1546
|
+
for (const src of candidates) {
|
|
1547
|
+
if (existsSync(src)) {
|
|
1548
|
+
writeFileSync(exeStartDst, readFileSync(src));
|
|
1549
|
+
chmodSync(exeStartDst, 493);
|
|
1550
|
+
break;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
let employees = [];
|
|
870
1554
|
try {
|
|
871
|
-
|
|
1555
|
+
employees = JSON.parse(readFileSync(rosterPath, "utf8"));
|
|
872
1556
|
} catch {
|
|
873
|
-
|
|
874
|
-
return { created, skipped, errors };
|
|
1557
|
+
return { created: 0, pathConfigured: false };
|
|
875
1558
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
continue;
|
|
1559
|
+
if (employees.length === 0) {
|
|
1560
|
+
return { created: 0, pathConfigured: false };
|
|
1561
|
+
}
|
|
1562
|
+
try {
|
|
1563
|
+
for (const f of readdirSync(binDir)) {
|
|
1564
|
+
if (f === "exe-start") continue;
|
|
1565
|
+
const fPath = path.join(binDir, f);
|
|
1566
|
+
try {
|
|
1567
|
+
const content = readFileSync(fPath, "utf8");
|
|
1568
|
+
if (content.includes("exe-start")) {
|
|
1569
|
+
unlinkSync(fPath);
|
|
1570
|
+
}
|
|
1571
|
+
} catch {
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
} catch {
|
|
1575
|
+
}
|
|
1576
|
+
let created = 0;
|
|
1577
|
+
const wrapperContent = `#!/bin/bash
|
|
1578
|
+
exec "${exeStartDst}" "$0" "$@"
|
|
1579
|
+
`;
|
|
1580
|
+
for (const emp of employees) {
|
|
1581
|
+
for (let n = 1; n <= MAX_N; n++) {
|
|
1582
|
+
const wrapperPath = path.join(binDir, `${emp.name}${n}`);
|
|
1583
|
+
writeFileSync(wrapperPath, wrapperContent);
|
|
1584
|
+
chmodSync(wrapperPath, 493);
|
|
1585
|
+
created++;
|
|
882
1586
|
}
|
|
1587
|
+
}
|
|
1588
|
+
const pathConfigured = ensurePath(home, binDir);
|
|
1589
|
+
return { created, pathConfigured };
|
|
1590
|
+
}
|
|
1591
|
+
function ensurePath(home, binDir) {
|
|
1592
|
+
if (process.env.PATH?.split(":").includes(binDir)) {
|
|
1593
|
+
return false;
|
|
1594
|
+
}
|
|
1595
|
+
const exportLine = `
|
|
1596
|
+
# exe-os session commands
|
|
1597
|
+
export PATH="${binDir}:$PATH"
|
|
1598
|
+
`;
|
|
1599
|
+
const shell = process.env.SHELL ?? "/bin/bash";
|
|
1600
|
+
const profilePaths = [];
|
|
1601
|
+
if (shell.includes("zsh")) {
|
|
1602
|
+
profilePaths.push(path.join(home, ".zshrc"));
|
|
1603
|
+
} else if (shell.includes("bash")) {
|
|
1604
|
+
profilePaths.push(path.join(home, ".bashrc"));
|
|
1605
|
+
profilePaths.push(path.join(home, ".bash_profile"));
|
|
1606
|
+
} else {
|
|
1607
|
+
profilePaths.push(path.join(home, ".profile"));
|
|
1608
|
+
}
|
|
1609
|
+
for (const profilePath of profilePaths) {
|
|
883
1610
|
try {
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1611
|
+
let content = "";
|
|
1612
|
+
try {
|
|
1613
|
+
content = readFileSync(profilePath, "utf8");
|
|
1614
|
+
} catch {
|
|
1615
|
+
}
|
|
1616
|
+
if (content.includes(".exe-os/bin")) {
|
|
1617
|
+
return false;
|
|
1618
|
+
}
|
|
1619
|
+
writeFileSync(profilePath, content + exportLine);
|
|
1620
|
+
return true;
|
|
1621
|
+
} catch {
|
|
1622
|
+
continue;
|
|
888
1623
|
}
|
|
889
1624
|
}
|
|
890
|
-
return
|
|
1625
|
+
return false;
|
|
891
1626
|
}
|
|
892
1627
|
|
|
1628
|
+
// src/bin/exe-new-employee.ts
|
|
1629
|
+
init_employees();
|
|
1630
|
+
|
|
893
1631
|
// src/lib/global-procedures.ts
|
|
894
1632
|
init_database();
|
|
895
1633
|
import { randomUUID } from "crypto";
|
|
@@ -1342,18 +2080,19 @@ function isMainModule(importMetaUrl) {
|
|
|
1342
2080
|
|
|
1343
2081
|
// src/lib/plan-limits.ts
|
|
1344
2082
|
init_database();
|
|
1345
|
-
|
|
1346
|
-
import
|
|
2083
|
+
init_employees();
|
|
2084
|
+
import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
|
|
2085
|
+
import path5 from "path";
|
|
1347
2086
|
|
|
1348
2087
|
// src/lib/license.ts
|
|
1349
2088
|
init_config();
|
|
1350
|
-
import { readFileSync as
|
|
2089
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
1351
2090
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1352
|
-
import
|
|
2091
|
+
import path4 from "path";
|
|
1353
2092
|
import { jwtVerify, importSPKI } from "jose";
|
|
1354
|
-
var LICENSE_PATH =
|
|
1355
|
-
var CACHE_PATH =
|
|
1356
|
-
var DEVICE_ID_PATH =
|
|
2093
|
+
var LICENSE_PATH = path4.join(EXE_AI_DIR, "license.key");
|
|
2094
|
+
var CACHE_PATH = path4.join(EXE_AI_DIR, "license-cache.json");
|
|
2095
|
+
var DEVICE_ID_PATH = path4.join(EXE_AI_DIR, "device-id");
|
|
1357
2096
|
var API_BASE = "https://askexe.com/cloud";
|
|
1358
2097
|
var RETRY_DELAY_MS = 500;
|
|
1359
2098
|
async function fetchRetry(url, init) {
|
|
@@ -1386,37 +2125,37 @@ var FREE_LICENSE = {
|
|
|
1386
2125
|
memoryLimit: 5e4
|
|
1387
2126
|
};
|
|
1388
2127
|
function loadDeviceId() {
|
|
1389
|
-
const deviceJsonPath =
|
|
2128
|
+
const deviceJsonPath = path4.join(EXE_AI_DIR, "device.json");
|
|
1390
2129
|
try {
|
|
1391
|
-
if (
|
|
1392
|
-
const data = JSON.parse(
|
|
2130
|
+
if (existsSync4(deviceJsonPath)) {
|
|
2131
|
+
const data = JSON.parse(readFileSync4(deviceJsonPath, "utf8"));
|
|
1393
2132
|
if (data.deviceId) return data.deviceId;
|
|
1394
2133
|
}
|
|
1395
2134
|
} catch {
|
|
1396
2135
|
}
|
|
1397
2136
|
try {
|
|
1398
|
-
if (
|
|
1399
|
-
const id2 =
|
|
2137
|
+
if (existsSync4(DEVICE_ID_PATH)) {
|
|
2138
|
+
const id2 = readFileSync4(DEVICE_ID_PATH, "utf8").trim();
|
|
1400
2139
|
if (id2) return id2;
|
|
1401
2140
|
}
|
|
1402
2141
|
} catch {
|
|
1403
2142
|
}
|
|
1404
2143
|
const id = randomUUID2();
|
|
1405
|
-
|
|
1406
|
-
|
|
2144
|
+
mkdirSync2(EXE_AI_DIR, { recursive: true });
|
|
2145
|
+
writeFileSync2(DEVICE_ID_PATH, id, "utf8");
|
|
1407
2146
|
return id;
|
|
1408
2147
|
}
|
|
1409
2148
|
function loadLicense() {
|
|
1410
2149
|
try {
|
|
1411
|
-
if (!
|
|
1412
|
-
return
|
|
2150
|
+
if (!existsSync4(LICENSE_PATH)) return null;
|
|
2151
|
+
return readFileSync4(LICENSE_PATH, "utf8").trim();
|
|
1413
2152
|
} catch {
|
|
1414
2153
|
return null;
|
|
1415
2154
|
}
|
|
1416
2155
|
}
|
|
1417
2156
|
function saveLicense(apiKey) {
|
|
1418
|
-
|
|
1419
|
-
|
|
2157
|
+
mkdirSync2(EXE_AI_DIR, { recursive: true });
|
|
2158
|
+
writeFileSync2(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
1420
2159
|
}
|
|
1421
2160
|
async function verifyLicenseJwt(token) {
|
|
1422
2161
|
try {
|
|
@@ -1442,8 +2181,8 @@ async function verifyLicenseJwt(token) {
|
|
|
1442
2181
|
}
|
|
1443
2182
|
async function getCachedLicense() {
|
|
1444
2183
|
try {
|
|
1445
|
-
if (!
|
|
1446
|
-
const raw = JSON.parse(
|
|
2184
|
+
if (!existsSync4(CACHE_PATH)) return null;
|
|
2185
|
+
const raw = JSON.parse(readFileSync4(CACHE_PATH, "utf8"));
|
|
1447
2186
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
1448
2187
|
return await verifyLicenseJwt(raw.token);
|
|
1449
2188
|
} catch {
|
|
@@ -1452,8 +2191,8 @@ async function getCachedLicense() {
|
|
|
1452
2191
|
}
|
|
1453
2192
|
function readCachedToken() {
|
|
1454
2193
|
try {
|
|
1455
|
-
if (!
|
|
1456
|
-
const raw = JSON.parse(
|
|
2194
|
+
if (!existsSync4(CACHE_PATH)) return null;
|
|
2195
|
+
const raw = JSON.parse(readFileSync4(CACHE_PATH, "utf8"));
|
|
1457
2196
|
return typeof raw.token === "string" ? raw.token : null;
|
|
1458
2197
|
} catch {
|
|
1459
2198
|
return null;
|
|
@@ -1487,7 +2226,7 @@ function getRawCachedPlan() {
|
|
|
1487
2226
|
}
|
|
1488
2227
|
function cacheResponse(token) {
|
|
1489
2228
|
try {
|
|
1490
|
-
|
|
2229
|
+
writeFileSync2(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
1491
2230
|
} catch {
|
|
1492
2231
|
}
|
|
1493
2232
|
}
|
|
@@ -1552,9 +2291,9 @@ async function checkLicense() {
|
|
|
1552
2291
|
let key = loadLicense();
|
|
1553
2292
|
if (!key) {
|
|
1554
2293
|
try {
|
|
1555
|
-
const configPath =
|
|
1556
|
-
if (
|
|
1557
|
-
const raw = JSON.parse(
|
|
2294
|
+
const configPath = path4.join(EXE_AI_DIR, "config.json");
|
|
2295
|
+
if (existsSync4(configPath)) {
|
|
2296
|
+
const raw = JSON.parse(readFileSync4(configPath, "utf8"));
|
|
1558
2297
|
const cloud = raw.cloud;
|
|
1559
2298
|
if (cloud?.apiKey) {
|
|
1560
2299
|
key = cloud.apiKey;
|
|
@@ -1579,7 +2318,7 @@ var PlanLimitError = class extends Error {
|
|
|
1579
2318
|
this.name = "PlanLimitError";
|
|
1580
2319
|
}
|
|
1581
2320
|
};
|
|
1582
|
-
var CACHE_PATH2 =
|
|
2321
|
+
var CACHE_PATH2 = path5.join(EXE_AI_DIR, "license-cache.json");
|
|
1583
2322
|
async function assertEmployeeLimit(license, rosterPath) {
|
|
1584
2323
|
const lic = license ?? await checkLicense();
|
|
1585
2324
|
if (lic.employeeLimit < 0) return;
|
|
@@ -1675,7 +2414,7 @@ async function main() {
|
|
|
1675
2414
|
const identityTemplate = templateKey ? getIdentityTemplate(templateKey) : null;
|
|
1676
2415
|
if (identityTemplate) {
|
|
1677
2416
|
const idPath = identityPath2(name);
|
|
1678
|
-
const dir =
|
|
2417
|
+
const dir = path9.dirname(idPath);
|
|
1679
2418
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
1680
2419
|
const content = identityTemplate.replace(/^agent_id: \w+/m, `agent_id: ${name}`);
|
|
1681
2420
|
fs.writeFileSync(idPath, content, "utf-8");
|
|
@@ -1683,9 +2422,9 @@ async function main() {
|
|
|
1683
2422
|
}
|
|
1684
2423
|
} catch {
|
|
1685
2424
|
}
|
|
1686
|
-
const taskDir =
|
|
1687
|
-
if (!
|
|
1688
|
-
|
|
2425
|
+
const taskDir = path9.join(process.cwd(), "exe", name);
|
|
2426
|
+
if (!existsSync9(taskDir)) {
|
|
2427
|
+
mkdirSync6(taskDir, { recursive: true });
|
|
1689
2428
|
}
|
|
1690
2429
|
const bins = registerBinSymlinks(newEmployee.name);
|
|
1691
2430
|
if (bins.created.length > 0) {
|
|
@@ -1694,6 +2433,15 @@ async function main() {
|
|
|
1694
2433
|
if (bins.errors.length > 0) {
|
|
1695
2434
|
console.error(`Warning: some launchers failed: ${bins.errors.join("; ")}`);
|
|
1696
2435
|
}
|
|
2436
|
+
try {
|
|
2437
|
+
const { resolvePackageRoot: resolvePackageRoot2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
|
|
2438
|
+
const pkgRoot = resolvePackageRoot2();
|
|
2439
|
+
const wrapResult = generateSessionWrappers(pkgRoot);
|
|
2440
|
+
if (wrapResult.created > 0) {
|
|
2441
|
+
console.log(`Session wrappers: ${wrapResult.created} generated (${newEmployee.name}1-9 added)`);
|
|
2442
|
+
}
|
|
2443
|
+
} catch {
|
|
2444
|
+
}
|
|
1697
2445
|
console.log(`Created employee: ${newEmployee.name} (${newEmployee.role})`);
|
|
1698
2446
|
}
|
|
1699
2447
|
if (isMainModule(import.meta.url)) {
|