closeclaw 3.0.3 → 3.0.4
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/cli.cjs +894 -735
- package/dist/cli.jsc +0 -0
- package/dist/index.jsc +0 -0
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -30,128 +30,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
mod
|
|
31
31
|
));
|
|
32
32
|
|
|
33
|
-
// src/config.ts
|
|
34
|
-
var config_exports = {};
|
|
35
|
-
__export(config_exports, {
|
|
36
|
-
getConfig: () => getConfig,
|
|
37
|
-
loadConfig: () => loadConfig
|
|
38
|
-
});
|
|
39
|
-
function getDefaults() {
|
|
40
|
-
return {
|
|
41
|
-
port: 4800,
|
|
42
|
-
host: "0.0.0.0",
|
|
43
|
-
dataDir: DEFAULT_DATA_DIR,
|
|
44
|
-
logLevel: "info",
|
|
45
|
-
github: {
|
|
46
|
-
appId: "",
|
|
47
|
-
privateKeyPath: "",
|
|
48
|
-
webhookSecret: "",
|
|
49
|
-
installationId: ""
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
function readConfigFile() {
|
|
54
|
-
if (!(0, import_node_fs.existsSync)(CONFIG_PATH)) {
|
|
55
|
-
const dir = (0, import_node_path.dirname)(CONFIG_PATH);
|
|
56
|
-
if (!(0, import_node_fs.existsSync)(dir)) {
|
|
57
|
-
(0, import_node_fs.mkdirSync)(dir, { recursive: true });
|
|
58
|
-
}
|
|
59
|
-
(0, import_node_fs.writeFileSync)(CONFIG_PATH, JSON.stringify(getDefaults(), null, 2), "utf-8");
|
|
60
|
-
return {};
|
|
61
|
-
}
|
|
62
|
-
try {
|
|
63
|
-
const raw = (0, import_node_fs.readFileSync)(CONFIG_PATH, "utf-8");
|
|
64
|
-
return JSON.parse(raw);
|
|
65
|
-
} catch {
|
|
66
|
-
return {};
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
function readEnvVars() {
|
|
70
|
-
const env = {};
|
|
71
|
-
if (process.env.PLATFORM_PORT) {
|
|
72
|
-
const port2 = parseInt(process.env.PLATFORM_PORT, 10);
|
|
73
|
-
if (!isNaN(port2)) env.port = port2;
|
|
74
|
-
}
|
|
75
|
-
if (process.env.PLATFORM_HOST) {
|
|
76
|
-
env.host = process.env.PLATFORM_HOST;
|
|
77
|
-
}
|
|
78
|
-
if (process.env.PLATFORM_DATA_DIR) {
|
|
79
|
-
env.dataDir = process.env.PLATFORM_DATA_DIR;
|
|
80
|
-
}
|
|
81
|
-
if (process.env.PLATFORM_LOG_LEVEL) {
|
|
82
|
-
const level = process.env.PLATFORM_LOG_LEVEL;
|
|
83
|
-
if (["debug", "info", "warn", "error"].includes(level)) {
|
|
84
|
-
env.logLevel = level;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
const gh = {};
|
|
88
|
-
let hasGithub = false;
|
|
89
|
-
if (process.env.GITHUB_APP_ID) {
|
|
90
|
-
gh.appId = process.env.GITHUB_APP_ID;
|
|
91
|
-
hasGithub = true;
|
|
92
|
-
}
|
|
93
|
-
if (process.env.GITHUB_PRIVATE_KEY_PATH) {
|
|
94
|
-
gh.privateKeyPath = process.env.GITHUB_PRIVATE_KEY_PATH;
|
|
95
|
-
hasGithub = true;
|
|
96
|
-
}
|
|
97
|
-
if (process.env.GITHUB_WEBHOOK_SECRET) {
|
|
98
|
-
gh.webhookSecret = process.env.GITHUB_WEBHOOK_SECRET;
|
|
99
|
-
hasGithub = true;
|
|
100
|
-
}
|
|
101
|
-
if (process.env.GITHUB_INSTALLATION_ID) {
|
|
102
|
-
gh.installationId = process.env.GITHUB_INSTALLATION_ID;
|
|
103
|
-
hasGithub = true;
|
|
104
|
-
}
|
|
105
|
-
if (hasGithub) {
|
|
106
|
-
env.github = gh;
|
|
107
|
-
}
|
|
108
|
-
return env;
|
|
109
|
-
}
|
|
110
|
-
function deepMerge(target, ...sources) {
|
|
111
|
-
const result = { ...target };
|
|
112
|
-
for (const source of sources) {
|
|
113
|
-
if (source.port !== void 0) result.port = source.port;
|
|
114
|
-
if (source.host !== void 0) result.host = source.host;
|
|
115
|
-
if (source.dataDir !== void 0) result.dataDir = source.dataDir;
|
|
116
|
-
if (source.logLevel !== void 0) result.logLevel = source.logLevel;
|
|
117
|
-
if (source.github) {
|
|
118
|
-
result.github = {
|
|
119
|
-
...result.github,
|
|
120
|
-
...source.github
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return result;
|
|
125
|
-
}
|
|
126
|
-
function loadConfig(cliFlags) {
|
|
127
|
-
const defaults = getDefaults();
|
|
128
|
-
const envVars = readEnvVars();
|
|
129
|
-
const fileConfig = readConfigFile();
|
|
130
|
-
_config = deepMerge(defaults, envVars, fileConfig, cliFlags || {});
|
|
131
|
-
if (!(0, import_node_fs.existsSync)(_config.dataDir)) {
|
|
132
|
-
(0, import_node_fs.mkdirSync)(_config.dataDir, { recursive: true });
|
|
133
|
-
}
|
|
134
|
-
return _config;
|
|
135
|
-
}
|
|
136
|
-
function getConfig() {
|
|
137
|
-
if (!_config) {
|
|
138
|
-
throw new Error("Config not loaded. Call loadConfig() first.");
|
|
139
|
-
}
|
|
140
|
-
return _config;
|
|
141
|
-
}
|
|
142
|
-
var import_node_fs, import_node_path, import_node_os, DEFAULT_DATA_DIR, CONFIG_PATH, _config;
|
|
143
|
-
var init_config = __esm({
|
|
144
|
-
"src/config.ts"() {
|
|
145
|
-
"use strict";
|
|
146
|
-
import_node_fs = require("fs");
|
|
147
|
-
import_node_path = require("path");
|
|
148
|
-
import_node_os = require("os");
|
|
149
|
-
DEFAULT_DATA_DIR = (0, import_node_path.join)((0, import_node_os.homedir)(), ".closeclaw", "data");
|
|
150
|
-
CONFIG_PATH = (0, import_node_path.join)((0, import_node_os.homedir)(), ".closeclaw", "config.json");
|
|
151
|
-
_config = null;
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
|
|
155
33
|
// src/db/schema.ts
|
|
156
34
|
var schema_exports = {};
|
|
157
35
|
__export(schema_exports, {
|
|
@@ -471,15 +349,15 @@ __export(connection_exports, {
|
|
|
471
349
|
initDatabase: () => initDatabase
|
|
472
350
|
});
|
|
473
351
|
function getDataDir() {
|
|
474
|
-
return process.env.PLATFORM_DATA_DIR || (0,
|
|
352
|
+
return process.env.PLATFORM_DATA_DIR || (0, import_node_path.join)(process.env.HOME || "~", ".closeclaw", "data");
|
|
475
353
|
}
|
|
476
354
|
function initDatabase(dataDir) {
|
|
477
355
|
if (_db) return _db;
|
|
478
356
|
const dir = dataDir || getDataDir();
|
|
479
|
-
if (!(0,
|
|
480
|
-
(0,
|
|
357
|
+
if (!(0, import_node_fs.existsSync)(dir)) {
|
|
358
|
+
(0, import_node_fs.mkdirSync)(dir, { recursive: true });
|
|
481
359
|
}
|
|
482
|
-
const dbPath = (0,
|
|
360
|
+
const dbPath = (0, import_node_path.join)(dir, "platform.db");
|
|
483
361
|
_sqlite = new import_better_sqlite3.default(dbPath);
|
|
484
362
|
_sqlite.pragma("journal_mode = WAL");
|
|
485
363
|
_sqlite.pragma("synchronous = NORMAL");
|
|
@@ -505,14 +383,14 @@ function closeDatabase() {
|
|
|
505
383
|
_db = null;
|
|
506
384
|
}
|
|
507
385
|
}
|
|
508
|
-
var import_better_sqlite3, import_better_sqlite32,
|
|
386
|
+
var import_better_sqlite3, import_better_sqlite32, import_node_path, import_node_fs, _db, _sqlite;
|
|
509
387
|
var init_connection = __esm({
|
|
510
388
|
"src/db/connection.ts"() {
|
|
511
389
|
"use strict";
|
|
512
390
|
import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
|
|
513
391
|
import_better_sqlite32 = require("drizzle-orm/better-sqlite3");
|
|
514
|
-
|
|
515
|
-
|
|
392
|
+
import_node_path = require("path");
|
|
393
|
+
import_node_fs = require("fs");
|
|
516
394
|
init_schema();
|
|
517
395
|
_db = null;
|
|
518
396
|
_sqlite = null;
|
|
@@ -589,8 +467,8 @@ function checkSeatLimit() {
|
|
|
589
467
|
if (!_license) return false;
|
|
590
468
|
if (_devMode) return true;
|
|
591
469
|
const db = getSqlite();
|
|
592
|
-
const
|
|
593
|
-
return
|
|
470
|
+
const result2 = db.prepare("SELECT COUNT(*) as count FROM profiles").get();
|
|
471
|
+
return result2.count <= _license.seats;
|
|
594
472
|
}
|
|
595
473
|
function getLicense() {
|
|
596
474
|
return _license;
|
|
@@ -965,6 +843,273 @@ var init_migrate = __esm({
|
|
|
965
843
|
}
|
|
966
844
|
});
|
|
967
845
|
|
|
846
|
+
// src/db/queries/profiles.ts
|
|
847
|
+
var profiles_exports = {};
|
|
848
|
+
__export(profiles_exports, {
|
|
849
|
+
countProfiles: () => countProfiles,
|
|
850
|
+
createProfile: () => createProfile,
|
|
851
|
+
getProfile: () => getProfile,
|
|
852
|
+
getProfileByEmail: () => getProfileByEmail,
|
|
853
|
+
getProfileById: () => getProfileById,
|
|
854
|
+
isSystemAdmin: () => isSystemAdmin,
|
|
855
|
+
updateProfile: () => updateProfile
|
|
856
|
+
});
|
|
857
|
+
function getProfile(userId) {
|
|
858
|
+
const db = getSqlite();
|
|
859
|
+
const profile = db.prepare("SELECT * FROM profiles WHERE id = ?").get(userId);
|
|
860
|
+
if (!profile) return null;
|
|
861
|
+
const orgMemberships = db.prepare("SELECT org_id, role FROM org_members WHERE user_id = ?").all(userId);
|
|
862
|
+
const projectMemberships = db.prepare("SELECT project_id, role FROM project_members WHERE user_id = ?").all(userId);
|
|
863
|
+
const orgRoles = {};
|
|
864
|
+
for (const m of orgMemberships) {
|
|
865
|
+
orgRoles[m.org_id] = m.role;
|
|
866
|
+
}
|
|
867
|
+
const projectRoles = {};
|
|
868
|
+
for (const m of projectMemberships) {
|
|
869
|
+
projectRoles[m.project_id] = m.role;
|
|
870
|
+
}
|
|
871
|
+
return {
|
|
872
|
+
...profile,
|
|
873
|
+
isSystemAdmin: profile.is_system_admin ?? false,
|
|
874
|
+
orgRoles,
|
|
875
|
+
projectRoles
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
function getProfileById(userId) {
|
|
879
|
+
const db = getSqlite();
|
|
880
|
+
return db.prepare("SELECT * FROM profiles WHERE id = ?").get(userId);
|
|
881
|
+
}
|
|
882
|
+
function getProfileByEmail(email) {
|
|
883
|
+
const db = getSqlite();
|
|
884
|
+
return db.prepare("SELECT * FROM profiles WHERE email = ?").get(email);
|
|
885
|
+
}
|
|
886
|
+
function updateProfile(userId, data) {
|
|
887
|
+
const db = getSqlite();
|
|
888
|
+
const fields = { ...data, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
889
|
+
const keys = Object.keys(fields);
|
|
890
|
+
const setClause = keys.map((k) => `${k} = ?`).join(", ");
|
|
891
|
+
const values = keys.map((k) => fields[k]);
|
|
892
|
+
db.prepare(`UPDATE profiles SET ${setClause} WHERE id = ?`).run(...values, userId);
|
|
893
|
+
return db.prepare("SELECT * FROM profiles WHERE id = ?").get(userId);
|
|
894
|
+
}
|
|
895
|
+
function createProfile(data) {
|
|
896
|
+
const db = getSqlite();
|
|
897
|
+
const id = (0, import_node_crypto3.randomUUID)();
|
|
898
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
899
|
+
db.prepare(
|
|
900
|
+
`INSERT INTO profiles (id, email, password_hash, full_name, display_name, created_at, updated_at)
|
|
901
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
902
|
+
).run(
|
|
903
|
+
id,
|
|
904
|
+
data.email,
|
|
905
|
+
data.password_hash,
|
|
906
|
+
data.full_name || null,
|
|
907
|
+
data.display_name || null,
|
|
908
|
+
now,
|
|
909
|
+
now
|
|
910
|
+
);
|
|
911
|
+
return db.prepare("SELECT * FROM profiles WHERE id = ?").get(id);
|
|
912
|
+
}
|
|
913
|
+
function countProfiles() {
|
|
914
|
+
const db = getSqlite();
|
|
915
|
+
const row = db.prepare("SELECT COUNT(*) as count FROM profiles").get();
|
|
916
|
+
return row.count;
|
|
917
|
+
}
|
|
918
|
+
function isSystemAdmin(userId) {
|
|
919
|
+
const db = getSqlite();
|
|
920
|
+
const row = db.prepare("SELECT is_system_admin FROM profiles WHERE id = ?").get(userId);
|
|
921
|
+
return row?.is_system_admin === 1;
|
|
922
|
+
}
|
|
923
|
+
var import_node_crypto3;
|
|
924
|
+
var init_profiles = __esm({
|
|
925
|
+
"src/db/queries/profiles.ts"() {
|
|
926
|
+
"use strict";
|
|
927
|
+
init_connection();
|
|
928
|
+
import_node_crypto3 = require("crypto");
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
// src/auth/jwt.ts
|
|
933
|
+
var jwt_exports = {};
|
|
934
|
+
__export(jwt_exports, {
|
|
935
|
+
_resetSecret: () => _resetSecret,
|
|
936
|
+
generateJwtSecret: () => generateJwtSecret,
|
|
937
|
+
generateRefreshToken: () => generateRefreshToken,
|
|
938
|
+
signAccessToken: () => signAccessToken,
|
|
939
|
+
verifyAccessToken: () => verifyAccessToken
|
|
940
|
+
});
|
|
941
|
+
function generateJwtSecret(dataDir) {
|
|
942
|
+
if (_secret) return _secret;
|
|
943
|
+
const secretPath = (0, import_node_path2.join)(dataDir, "jwt.secret");
|
|
944
|
+
if ((0, import_node_fs2.existsSync)(secretPath)) {
|
|
945
|
+
_secret = (0, import_node_fs2.readFileSync)(secretPath, "utf-8").trim();
|
|
946
|
+
return _secret;
|
|
947
|
+
}
|
|
948
|
+
const dir = (0, import_node_path2.dirname)(secretPath);
|
|
949
|
+
if (!(0, import_node_fs2.existsSync)(dir)) {
|
|
950
|
+
(0, import_node_fs2.mkdirSync)(dir, { recursive: true });
|
|
951
|
+
}
|
|
952
|
+
_secret = (0, import_node_crypto4.randomBytes)(32).toString("hex");
|
|
953
|
+
(0, import_node_fs2.writeFileSync)(secretPath, _secret, { mode: 384 });
|
|
954
|
+
return _secret;
|
|
955
|
+
}
|
|
956
|
+
function getSecret() {
|
|
957
|
+
if (!_secret) {
|
|
958
|
+
throw new Error("JWT secret not initialized. Call generateJwtSecret(dataDir) first.");
|
|
959
|
+
}
|
|
960
|
+
return _secret;
|
|
961
|
+
}
|
|
962
|
+
function signAccessToken(payload) {
|
|
963
|
+
return import_jsonwebtoken2.default.sign(payload, getSecret(), {
|
|
964
|
+
algorithm: "HS256",
|
|
965
|
+
expiresIn: "15m"
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
function verifyAccessToken(token) {
|
|
969
|
+
return import_jsonwebtoken2.default.verify(token, getSecret(), {
|
|
970
|
+
algorithms: ["HS256"]
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
function generateRefreshToken() {
|
|
974
|
+
return (0, import_node_crypto4.randomBytes)(64).toString("hex");
|
|
975
|
+
}
|
|
976
|
+
function _resetSecret() {
|
|
977
|
+
_secret = null;
|
|
978
|
+
}
|
|
979
|
+
var import_jsonwebtoken2, import_node_crypto4, import_node_fs2, import_node_path2, _secret;
|
|
980
|
+
var init_jwt = __esm({
|
|
981
|
+
"src/auth/jwt.ts"() {
|
|
982
|
+
"use strict";
|
|
983
|
+
import_jsonwebtoken2 = __toESM(require("jsonwebtoken"), 1);
|
|
984
|
+
import_node_crypto4 = require("crypto");
|
|
985
|
+
import_node_fs2 = require("fs");
|
|
986
|
+
import_node_path2 = require("path");
|
|
987
|
+
_secret = null;
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
// src/config.ts
|
|
992
|
+
var config_exports = {};
|
|
993
|
+
__export(config_exports, {
|
|
994
|
+
getConfig: () => getConfig,
|
|
995
|
+
loadConfig: () => loadConfig
|
|
996
|
+
});
|
|
997
|
+
function getDefaults() {
|
|
998
|
+
return {
|
|
999
|
+
port: 4800,
|
|
1000
|
+
host: "0.0.0.0",
|
|
1001
|
+
dataDir: DEFAULT_DATA_DIR,
|
|
1002
|
+
logLevel: "info",
|
|
1003
|
+
github: {
|
|
1004
|
+
appId: "",
|
|
1005
|
+
privateKeyPath: "",
|
|
1006
|
+
webhookSecret: "",
|
|
1007
|
+
installationId: ""
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
function readConfigFile() {
|
|
1012
|
+
if (!(0, import_node_fs3.existsSync)(CONFIG_PATH)) {
|
|
1013
|
+
const dir = (0, import_node_path3.dirname)(CONFIG_PATH);
|
|
1014
|
+
if (!(0, import_node_fs3.existsSync)(dir)) {
|
|
1015
|
+
(0, import_node_fs3.mkdirSync)(dir, { recursive: true });
|
|
1016
|
+
}
|
|
1017
|
+
(0, import_node_fs3.writeFileSync)(CONFIG_PATH, JSON.stringify(getDefaults(), null, 2), "utf-8");
|
|
1018
|
+
return {};
|
|
1019
|
+
}
|
|
1020
|
+
try {
|
|
1021
|
+
const raw = (0, import_node_fs3.readFileSync)(CONFIG_PATH, "utf-8");
|
|
1022
|
+
return JSON.parse(raw);
|
|
1023
|
+
} catch {
|
|
1024
|
+
return {};
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
function readEnvVars() {
|
|
1028
|
+
const env = {};
|
|
1029
|
+
if (process.env.PLATFORM_PORT) {
|
|
1030
|
+
const port2 = parseInt(process.env.PLATFORM_PORT, 10);
|
|
1031
|
+
if (!isNaN(port2)) env.port = port2;
|
|
1032
|
+
}
|
|
1033
|
+
if (process.env.PLATFORM_HOST) {
|
|
1034
|
+
env.host = process.env.PLATFORM_HOST;
|
|
1035
|
+
}
|
|
1036
|
+
if (process.env.PLATFORM_DATA_DIR) {
|
|
1037
|
+
env.dataDir = process.env.PLATFORM_DATA_DIR;
|
|
1038
|
+
}
|
|
1039
|
+
if (process.env.PLATFORM_LOG_LEVEL) {
|
|
1040
|
+
const level = process.env.PLATFORM_LOG_LEVEL;
|
|
1041
|
+
if (["debug", "info", "warn", "error"].includes(level)) {
|
|
1042
|
+
env.logLevel = level;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
const gh = {};
|
|
1046
|
+
let hasGithub = false;
|
|
1047
|
+
if (process.env.GITHUB_APP_ID) {
|
|
1048
|
+
gh.appId = process.env.GITHUB_APP_ID;
|
|
1049
|
+
hasGithub = true;
|
|
1050
|
+
}
|
|
1051
|
+
if (process.env.GITHUB_PRIVATE_KEY_PATH) {
|
|
1052
|
+
gh.privateKeyPath = process.env.GITHUB_PRIVATE_KEY_PATH;
|
|
1053
|
+
hasGithub = true;
|
|
1054
|
+
}
|
|
1055
|
+
if (process.env.GITHUB_WEBHOOK_SECRET) {
|
|
1056
|
+
gh.webhookSecret = process.env.GITHUB_WEBHOOK_SECRET;
|
|
1057
|
+
hasGithub = true;
|
|
1058
|
+
}
|
|
1059
|
+
if (process.env.GITHUB_INSTALLATION_ID) {
|
|
1060
|
+
gh.installationId = process.env.GITHUB_INSTALLATION_ID;
|
|
1061
|
+
hasGithub = true;
|
|
1062
|
+
}
|
|
1063
|
+
if (hasGithub) {
|
|
1064
|
+
env.github = gh;
|
|
1065
|
+
}
|
|
1066
|
+
return env;
|
|
1067
|
+
}
|
|
1068
|
+
function deepMerge(target, ...sources) {
|
|
1069
|
+
const result2 = { ...target };
|
|
1070
|
+
for (const source of sources) {
|
|
1071
|
+
if (source.port !== void 0) result2.port = source.port;
|
|
1072
|
+
if (source.host !== void 0) result2.host = source.host;
|
|
1073
|
+
if (source.dataDir !== void 0) result2.dataDir = source.dataDir;
|
|
1074
|
+
if (source.logLevel !== void 0) result2.logLevel = source.logLevel;
|
|
1075
|
+
if (source.github) {
|
|
1076
|
+
result2.github = {
|
|
1077
|
+
...result2.github,
|
|
1078
|
+
...source.github
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
return result2;
|
|
1083
|
+
}
|
|
1084
|
+
function loadConfig(cliFlags) {
|
|
1085
|
+
const defaults = getDefaults();
|
|
1086
|
+
const envVars = readEnvVars();
|
|
1087
|
+
const fileConfig = readConfigFile();
|
|
1088
|
+
_config = deepMerge(defaults, envVars, fileConfig, cliFlags || {});
|
|
1089
|
+
if (!(0, import_node_fs3.existsSync)(_config.dataDir)) {
|
|
1090
|
+
(0, import_node_fs3.mkdirSync)(_config.dataDir, { recursive: true });
|
|
1091
|
+
}
|
|
1092
|
+
return _config;
|
|
1093
|
+
}
|
|
1094
|
+
function getConfig() {
|
|
1095
|
+
if (!_config) {
|
|
1096
|
+
throw new Error("Config not loaded. Call loadConfig() first.");
|
|
1097
|
+
}
|
|
1098
|
+
return _config;
|
|
1099
|
+
}
|
|
1100
|
+
var import_node_fs3, import_node_path3, import_node_os, DEFAULT_DATA_DIR, CONFIG_PATH, _config;
|
|
1101
|
+
var init_config = __esm({
|
|
1102
|
+
"src/config.ts"() {
|
|
1103
|
+
"use strict";
|
|
1104
|
+
import_node_fs3 = require("fs");
|
|
1105
|
+
import_node_path3 = require("path");
|
|
1106
|
+
import_node_os = require("os");
|
|
1107
|
+
DEFAULT_DATA_DIR = (0, import_node_path3.join)((0, import_node_os.homedir)(), ".closeclaw", "data");
|
|
1108
|
+
CONFIG_PATH = (0, import_node_path3.join)((0, import_node_os.homedir)(), ".closeclaw", "config.json");
|
|
1109
|
+
_config = null;
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
|
|
968
1113
|
// src/services/openclaw-manager.ts
|
|
969
1114
|
var openclaw_manager_exports = {};
|
|
970
1115
|
__export(openclaw_manager_exports, {
|
|
@@ -1148,54 +1293,6 @@ var init_openclaw_manager = __esm({
|
|
|
1148
1293
|
}
|
|
1149
1294
|
});
|
|
1150
1295
|
|
|
1151
|
-
// src/auth/jwt.ts
|
|
1152
|
-
function generateJwtSecret(dataDir) {
|
|
1153
|
-
if (_secret) return _secret;
|
|
1154
|
-
const secretPath = (0, import_node_path3.join)(dataDir, "jwt.secret");
|
|
1155
|
-
if ((0, import_node_fs3.existsSync)(secretPath)) {
|
|
1156
|
-
_secret = (0, import_node_fs3.readFileSync)(secretPath, "utf-8").trim();
|
|
1157
|
-
return _secret;
|
|
1158
|
-
}
|
|
1159
|
-
const dir = (0, import_node_path3.dirname)(secretPath);
|
|
1160
|
-
if (!(0, import_node_fs3.existsSync)(dir)) {
|
|
1161
|
-
(0, import_node_fs3.mkdirSync)(dir, { recursive: true });
|
|
1162
|
-
}
|
|
1163
|
-
_secret = (0, import_node_crypto3.randomBytes)(32).toString("hex");
|
|
1164
|
-
(0, import_node_fs3.writeFileSync)(secretPath, _secret, { mode: 384 });
|
|
1165
|
-
return _secret;
|
|
1166
|
-
}
|
|
1167
|
-
function getSecret() {
|
|
1168
|
-
if (!_secret) {
|
|
1169
|
-
throw new Error("JWT secret not initialized. Call generateJwtSecret(dataDir) first.");
|
|
1170
|
-
}
|
|
1171
|
-
return _secret;
|
|
1172
|
-
}
|
|
1173
|
-
function signAccessToken(payload) {
|
|
1174
|
-
return import_jsonwebtoken2.default.sign(payload, getSecret(), {
|
|
1175
|
-
algorithm: "HS256",
|
|
1176
|
-
expiresIn: "15m"
|
|
1177
|
-
});
|
|
1178
|
-
}
|
|
1179
|
-
function verifyAccessToken(token) {
|
|
1180
|
-
return import_jsonwebtoken2.default.verify(token, getSecret(), {
|
|
1181
|
-
algorithms: ["HS256"]
|
|
1182
|
-
});
|
|
1183
|
-
}
|
|
1184
|
-
function generateRefreshToken() {
|
|
1185
|
-
return (0, import_node_crypto3.randomBytes)(64).toString("hex");
|
|
1186
|
-
}
|
|
1187
|
-
var import_jsonwebtoken2, import_node_crypto3, import_node_fs3, import_node_path3, _secret;
|
|
1188
|
-
var init_jwt = __esm({
|
|
1189
|
-
"src/auth/jwt.ts"() {
|
|
1190
|
-
"use strict";
|
|
1191
|
-
import_jsonwebtoken2 = __toESM(require("jsonwebtoken"), 1);
|
|
1192
|
-
import_node_crypto3 = require("crypto");
|
|
1193
|
-
import_node_fs3 = require("fs");
|
|
1194
|
-
import_node_path3 = require("path");
|
|
1195
|
-
_secret = null;
|
|
1196
|
-
}
|
|
1197
|
-
});
|
|
1198
|
-
|
|
1199
1296
|
// src/auth/ws-auth.ts
|
|
1200
1297
|
function authenticateConnection(req) {
|
|
1201
1298
|
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
@@ -1256,8 +1353,8 @@ function getCached(key) {
|
|
|
1256
1353
|
}
|
|
1257
1354
|
return entry.result;
|
|
1258
1355
|
}
|
|
1259
|
-
function setCache(key,
|
|
1260
|
-
accessCache.set(key, { result, expiresAt: Date.now() + CACHE_TTL });
|
|
1356
|
+
function setCache(key, result2) {
|
|
1357
|
+
accessCache.set(key, { result: result2, expiresAt: Date.now() + CACHE_TTL });
|
|
1261
1358
|
}
|
|
1262
1359
|
function invalidateUserCache(userId) {
|
|
1263
1360
|
for (const key of accessCache.keys()) {
|
|
@@ -1275,14 +1372,14 @@ function getOrgAccess(userId, orgId) {
|
|
|
1275
1372
|
"SELECT role FROM org_members WHERE org_id = ? AND user_id = ?"
|
|
1276
1373
|
).get(orgId, userId);
|
|
1277
1374
|
if (!member) {
|
|
1278
|
-
const
|
|
1279
|
-
setCache(cacheKey,
|
|
1280
|
-
return
|
|
1375
|
+
const result3 = { allowed: false };
|
|
1376
|
+
setCache(cacheKey, result3);
|
|
1377
|
+
return result3;
|
|
1281
1378
|
}
|
|
1282
1379
|
const orgRole = member.role === "owner" || member.role === "admin" ? "admin" : "member";
|
|
1283
|
-
const
|
|
1284
|
-
setCache(cacheKey,
|
|
1285
|
-
return
|
|
1380
|
+
const result2 = { allowed: true, orgRole };
|
|
1381
|
+
setCache(cacheKey, result2);
|
|
1382
|
+
return result2;
|
|
1286
1383
|
}
|
|
1287
1384
|
function getProjectAccess(userId, projectId) {
|
|
1288
1385
|
const cacheKey = `${userId}:proj:${projectId}`;
|
|
@@ -1293,28 +1390,28 @@ function getProjectAccess(userId, projectId) {
|
|
|
1293
1390
|
"SELECT org_id FROM projects WHERE id = ?"
|
|
1294
1391
|
).get(projectId);
|
|
1295
1392
|
if (!project) {
|
|
1296
|
-
const
|
|
1297
|
-
setCache(cacheKey,
|
|
1298
|
-
return
|
|
1393
|
+
const result3 = { allowed: false };
|
|
1394
|
+
setCache(cacheKey, result3);
|
|
1395
|
+
return result3;
|
|
1299
1396
|
}
|
|
1300
1397
|
const orgAccess = getOrgAccess(userId, project.org_id);
|
|
1301
1398
|
if (orgAccess.orgRole === "admin") {
|
|
1302
|
-
const
|
|
1303
|
-
setCache(cacheKey,
|
|
1304
|
-
return
|
|
1399
|
+
const result3 = { allowed: true, orgRole: "admin", projectRole: "project_lead" };
|
|
1400
|
+
setCache(cacheKey, result3);
|
|
1401
|
+
return result3;
|
|
1305
1402
|
}
|
|
1306
1403
|
const projectMember = db.prepare(
|
|
1307
1404
|
"SELECT role FROM project_members WHERE project_id = ? AND user_id = ?"
|
|
1308
1405
|
).get(projectId, userId);
|
|
1309
1406
|
if (!projectMember) {
|
|
1310
|
-
const
|
|
1311
|
-
setCache(cacheKey,
|
|
1312
|
-
return
|
|
1407
|
+
const result3 = { allowed: false, orgRole: orgAccess.orgRole };
|
|
1408
|
+
setCache(cacheKey, result3);
|
|
1409
|
+
return result3;
|
|
1313
1410
|
}
|
|
1314
1411
|
const projectRole = projectMember.role === "lead" || projectMember.role === "project_lead" ? "project_lead" : "member";
|
|
1315
|
-
const
|
|
1316
|
-
setCache(cacheKey,
|
|
1317
|
-
return
|
|
1412
|
+
const result2 = { allowed: true, orgRole: orgAccess.orgRole, projectRole };
|
|
1413
|
+
setCache(cacheKey, result2);
|
|
1414
|
+
return result2;
|
|
1318
1415
|
}
|
|
1319
1416
|
function requireOrgAccess(userId, orgId) {
|
|
1320
1417
|
const access = getOrgAccess(userId, orgId);
|
|
@@ -1539,11 +1636,11 @@ async function routeMessage(conn, raw) {
|
|
|
1539
1636
|
}
|
|
1540
1637
|
log.ws("in", conn.clientId, msg);
|
|
1541
1638
|
if (type === "subscribe") {
|
|
1542
|
-
const
|
|
1543
|
-
if (
|
|
1639
|
+
const result2 = await subscribe(conn, msg.scope, msg.projectId, msg.taskId);
|
|
1640
|
+
if (result2.ok) {
|
|
1544
1641
|
sendResponse(conn, id, { ok: true });
|
|
1545
1642
|
} else {
|
|
1546
|
-
sendError(conn, id, "SUBSCRIBE_FAILED",
|
|
1643
|
+
sendError(conn, id, "SUBSCRIBE_FAILED", result2.error || "Subscription failed");
|
|
1547
1644
|
}
|
|
1548
1645
|
return;
|
|
1549
1646
|
}
|
|
@@ -1566,15 +1663,15 @@ async function routeMessage(conn, raw) {
|
|
|
1566
1663
|
}
|
|
1567
1664
|
}
|
|
1568
1665
|
try {
|
|
1569
|
-
const
|
|
1570
|
-
if (idempotencyKey &&
|
|
1666
|
+
const result2 = await handler({ conn, requestId: id, data: msg.data || {} });
|
|
1667
|
+
if (idempotencyKey && result2.ok) {
|
|
1571
1668
|
idempotencyCache.set(idempotencyKey, {
|
|
1572
|
-
response:
|
|
1669
|
+
response: result2,
|
|
1573
1670
|
expiresAt: Date.now() + 3e5
|
|
1574
1671
|
// 5 min
|
|
1575
1672
|
});
|
|
1576
1673
|
}
|
|
1577
|
-
sendResponse(conn, id,
|
|
1674
|
+
sendResponse(conn, id, result2);
|
|
1578
1675
|
} catch (err) {
|
|
1579
1676
|
if (err instanceof AccessDenied) {
|
|
1580
1677
|
sendError(conn, id, err.code, err.message);
|
|
@@ -1584,13 +1681,13 @@ async function routeMessage(conn, raw) {
|
|
|
1584
1681
|
}
|
|
1585
1682
|
}
|
|
1586
1683
|
}
|
|
1587
|
-
function sendResponse(conn, id,
|
|
1684
|
+
function sendResponse(conn, id, result2) {
|
|
1588
1685
|
if (conn.ws.readyState !== 1) return;
|
|
1589
1686
|
const msg = {
|
|
1590
1687
|
id,
|
|
1591
1688
|
type: "response",
|
|
1592
|
-
ok:
|
|
1593
|
-
...
|
|
1689
|
+
ok: result2.ok,
|
|
1690
|
+
...result2.ok ? { data: result2.data } : { error: result2.error }
|
|
1594
1691
|
};
|
|
1595
1692
|
log.ws("out", conn.clientId, msg);
|
|
1596
1693
|
conn.ws.send(JSON.stringify(msg));
|
|
@@ -1781,9 +1878,9 @@ function buildTaskPrompt(task, project) {
|
|
|
1781
1878
|
const comments2 = task.comments;
|
|
1782
1879
|
if (comments2?.length) {
|
|
1783
1880
|
lines.push("## Existing Comments");
|
|
1784
|
-
for (const
|
|
1785
|
-
const who =
|
|
1786
|
-
lines.push(`**${who}:** ${
|
|
1881
|
+
for (const c2 of comments2) {
|
|
1882
|
+
const who = c2.is_ai_message ? "AI" : "Human";
|
|
1883
|
+
lines.push(`**${who}:** ${c2.content}`, "");
|
|
1787
1884
|
}
|
|
1788
1885
|
}
|
|
1789
1886
|
const checklists2 = task.checklists;
|
|
@@ -2379,7 +2476,7 @@ function upsertPullRequest(data) {
|
|
|
2379
2476
|
);
|
|
2380
2477
|
return db.prepare("SELECT * FROM pull_requests WHERE id = ?").get(existing.id);
|
|
2381
2478
|
}
|
|
2382
|
-
const id = (0,
|
|
2479
|
+
const id = (0, import_node_crypto5.randomUUID)();
|
|
2383
2480
|
db.prepare(
|
|
2384
2481
|
`INSERT INTO pull_requests (id, project_id, task_id, github_pr_number, github_pr_url, title, state, author, branch, merged_at, closed_at, created_at, updated_at)
|
|
2385
2482
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
@@ -2445,7 +2542,7 @@ function saveTaskBranches(taskId, branches) {
|
|
|
2445
2542
|
);
|
|
2446
2543
|
const insertAll = db.transaction((rows) => {
|
|
2447
2544
|
for (const b of rows) {
|
|
2448
|
-
insert.run((0,
|
|
2545
|
+
insert.run((0, import_node_crypto5.randomUUID)(), taskId, b.projectRepoId, b.branchName, b.sourceBranch, now, now);
|
|
2449
2546
|
}
|
|
2450
2547
|
});
|
|
2451
2548
|
insertAll(branches);
|
|
@@ -2494,7 +2591,7 @@ function upsertProjectRepo(data) {
|
|
|
2494
2591
|
);
|
|
2495
2592
|
return db.prepare("SELECT * FROM project_repos WHERE id = ?").get(existing.id);
|
|
2496
2593
|
}
|
|
2497
|
-
const id = (0,
|
|
2594
|
+
const id = (0, import_node_crypto5.randomUUID)();
|
|
2498
2595
|
db.prepare(
|
|
2499
2596
|
`INSERT INTO project_repos (id, project_id, repo_owner, repo_name, repo_url, label, installation_id, default_branch, created_at, updated_at)
|
|
2500
2597
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
@@ -2527,7 +2624,7 @@ function getTaskActivity(taskId, limit = 50) {
|
|
|
2527
2624
|
}
|
|
2528
2625
|
function createTaskActivity(data) {
|
|
2529
2626
|
const db = getSqlite();
|
|
2530
|
-
const id = (0,
|
|
2627
|
+
const id = (0, import_node_crypto5.randomUUID)();
|
|
2531
2628
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2532
2629
|
db.prepare(
|
|
2533
2630
|
`INSERT INTO task_activity (id, task_id, project_id, event_type, description, metadata, created_at)
|
|
@@ -2543,12 +2640,12 @@ function createTaskActivity(data) {
|
|
|
2543
2640
|
);
|
|
2544
2641
|
return db.prepare("SELECT * FROM task_activity WHERE id = ?").get(id);
|
|
2545
2642
|
}
|
|
2546
|
-
var
|
|
2643
|
+
var import_node_crypto5;
|
|
2547
2644
|
var init_github2 = __esm({
|
|
2548
2645
|
"src/db/queries/github.ts"() {
|
|
2549
2646
|
"use strict";
|
|
2550
2647
|
init_connection();
|
|
2551
|
-
|
|
2648
|
+
import_node_crypto5 = require("crypto");
|
|
2552
2649
|
}
|
|
2553
2650
|
});
|
|
2554
2651
|
|
|
@@ -2824,7 +2921,7 @@ function getTaskForAgent(taskId) {
|
|
|
2824
2921
|
}
|
|
2825
2922
|
function createTask(data) {
|
|
2826
2923
|
const db = getSqlite();
|
|
2827
|
-
const id = (0,
|
|
2924
|
+
const id = (0, import_node_crypto6.randomUUID)();
|
|
2828
2925
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2829
2926
|
db.prepare(
|
|
2830
2927
|
`INSERT INTO tasks (id, project_id, parent_task_id, task_number, title, description, status, side, priority, story_points, due_date, start_date, position, created_at, updated_at)
|
|
@@ -2930,8 +3027,8 @@ function updatePositionOptimistic(taskId, status, position, expectedUpdatedAt, e
|
|
|
2930
3027
|
if (typeof v === "boolean") return v ? 1 : 0;
|
|
2931
3028
|
return v;
|
|
2932
3029
|
});
|
|
2933
|
-
const
|
|
2934
|
-
return
|
|
3030
|
+
const result2 = db.prepare(`UPDATE tasks SET ${setClause} WHERE id = ? AND updated_at = ?`).run(...values, taskId, expectedUpdatedAt);
|
|
3031
|
+
return result2.changes > 0;
|
|
2935
3032
|
}
|
|
2936
3033
|
function getNextTaskNumber(projectId) {
|
|
2937
3034
|
const db = getSqlite();
|
|
@@ -3027,12 +3124,12 @@ function recordColumnTransition(taskId, oldStatus, newStatus) {
|
|
|
3027
3124
|
"UPDATE tasks SET column_entered_at = ?, column_times = ? WHERE id = ?"
|
|
3028
3125
|
).run(now, JSON.stringify(columnTimes), taskId);
|
|
3029
3126
|
}
|
|
3030
|
-
var
|
|
3127
|
+
var import_node_crypto6;
|
|
3031
3128
|
var init_tasks = __esm({
|
|
3032
3129
|
"src/db/queries/tasks.ts"() {
|
|
3033
3130
|
"use strict";
|
|
3034
3131
|
init_connection();
|
|
3035
|
-
|
|
3132
|
+
import_node_crypto6 = require("crypto");
|
|
3036
3133
|
}
|
|
3037
3134
|
});
|
|
3038
3135
|
|
|
@@ -3096,7 +3193,7 @@ function getActivity(taskId, limit = 50) {
|
|
|
3096
3193
|
}
|
|
3097
3194
|
function createActivity(data) {
|
|
3098
3195
|
const db = getSqlite();
|
|
3099
|
-
const id = (0,
|
|
3196
|
+
const id = (0, import_node_crypto7.randomUUID)();
|
|
3100
3197
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3101
3198
|
db.prepare(
|
|
3102
3199
|
`INSERT INTO activity_log (id, task_id, project_id, user_id, action, field, old_value, new_value, metadata, is_ai_action, ai_agent_id, created_at)
|
|
@@ -3127,7 +3224,7 @@ function createActivities(entries) {
|
|
|
3127
3224
|
const insertMany = db.transaction((rows) => {
|
|
3128
3225
|
for (const e of rows) {
|
|
3129
3226
|
insert.run(
|
|
3130
|
-
(0,
|
|
3227
|
+
(0, import_node_crypto7.randomUUID)(),
|
|
3131
3228
|
e.task_id || null,
|
|
3132
3229
|
e.project_id || null,
|
|
3133
3230
|
e.user_id || null,
|
|
@@ -3142,19 +3239,19 @@ function createActivities(entries) {
|
|
|
3142
3239
|
});
|
|
3143
3240
|
insertMany(entries);
|
|
3144
3241
|
}
|
|
3145
|
-
var
|
|
3242
|
+
var import_node_crypto7;
|
|
3146
3243
|
var init_activity = __esm({
|
|
3147
3244
|
"src/db/queries/activity.ts"() {
|
|
3148
3245
|
"use strict";
|
|
3149
3246
|
init_connection();
|
|
3150
|
-
|
|
3247
|
+
import_node_crypto7 = require("crypto");
|
|
3151
3248
|
}
|
|
3152
3249
|
});
|
|
3153
3250
|
|
|
3154
3251
|
// src/db/queries/projects.ts
|
|
3155
3252
|
function createProject(data) {
|
|
3156
3253
|
const db = getSqlite();
|
|
3157
|
-
const id = (0,
|
|
3254
|
+
const id = (0, import_node_crypto8.randomUUID)();
|
|
3158
3255
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3159
3256
|
db.prepare(
|
|
3160
3257
|
`INSERT INTO projects (id, org_id, name, slug, prefix, description, color, icon, created_by, agent_id, agent_instructions, created_at, updated_at)
|
|
@@ -3235,7 +3332,7 @@ function getProjectMembers(projectId) {
|
|
|
3235
3332
|
}
|
|
3236
3333
|
function addProjectMember(data) {
|
|
3237
3334
|
const db = getSqlite();
|
|
3238
|
-
const id = (0,
|
|
3335
|
+
const id = (0, import_node_crypto8.randomUUID)();
|
|
3239
3336
|
db.prepare(
|
|
3240
3337
|
`INSERT INTO project_members (id, project_id, user_id, role) VALUES (?, ?, ?, ?)`
|
|
3241
3338
|
).run(id, data.project_id, data.user_id, data.role || "member");
|
|
@@ -3299,12 +3396,12 @@ function getProjectOrgId(projectId) {
|
|
|
3299
3396
|
const row = db.prepare("SELECT org_id FROM projects WHERE id = ?").get(projectId);
|
|
3300
3397
|
return row?.org_id || null;
|
|
3301
3398
|
}
|
|
3302
|
-
var
|
|
3399
|
+
var import_node_crypto8;
|
|
3303
3400
|
var init_projects = __esm({
|
|
3304
3401
|
"src/db/queries/projects.ts"() {
|
|
3305
3402
|
"use strict";
|
|
3306
3403
|
init_connection();
|
|
3307
|
-
|
|
3404
|
+
import_node_crypto8 = require("crypto");
|
|
3308
3405
|
}
|
|
3309
3406
|
});
|
|
3310
3407
|
|
|
@@ -3549,10 +3646,10 @@ var init_tasks2 = __esm({
|
|
|
3549
3646
|
const prompt = buildTaskPrompt(task, project);
|
|
3550
3647
|
try {
|
|
3551
3648
|
if (openclaw.isHealthy()) {
|
|
3552
|
-
const
|
|
3649
|
+
const result2 = await openclaw.dispatchTask(project.agent_id, taskId, prompt);
|
|
3553
3650
|
updateTask(taskId, {
|
|
3554
3651
|
ai_typing: true,
|
|
3555
|
-
ai_run_id:
|
|
3652
|
+
ai_run_id: result2?.runId || null,
|
|
3556
3653
|
ai_dispatched_by: conn.userId
|
|
3557
3654
|
});
|
|
3558
3655
|
log.info("ai", `Auto-dispatched ${taskId} -> ${project.agent_id} (by ${conn.userId})`);
|
|
@@ -3638,92 +3735,6 @@ var init_tasks2 = __esm({
|
|
|
3638
3735
|
}
|
|
3639
3736
|
});
|
|
3640
3737
|
|
|
3641
|
-
// src/db/queries/profiles.ts
|
|
3642
|
-
var profiles_exports = {};
|
|
3643
|
-
__export(profiles_exports, {
|
|
3644
|
-
countProfiles: () => countProfiles,
|
|
3645
|
-
createProfile: () => createProfile,
|
|
3646
|
-
getProfile: () => getProfile,
|
|
3647
|
-
getProfileByEmail: () => getProfileByEmail,
|
|
3648
|
-
getProfileById: () => getProfileById,
|
|
3649
|
-
isSystemAdmin: () => isSystemAdmin,
|
|
3650
|
-
updateProfile: () => updateProfile
|
|
3651
|
-
});
|
|
3652
|
-
function getProfile(userId) {
|
|
3653
|
-
const db = getSqlite();
|
|
3654
|
-
const profile = db.prepare("SELECT * FROM profiles WHERE id = ?").get(userId);
|
|
3655
|
-
if (!profile) return null;
|
|
3656
|
-
const orgMemberships = db.prepare("SELECT org_id, role FROM org_members WHERE user_id = ?").all(userId);
|
|
3657
|
-
const projectMemberships = db.prepare("SELECT project_id, role FROM project_members WHERE user_id = ?").all(userId);
|
|
3658
|
-
const orgRoles = {};
|
|
3659
|
-
for (const m of orgMemberships) {
|
|
3660
|
-
orgRoles[m.org_id] = m.role;
|
|
3661
|
-
}
|
|
3662
|
-
const projectRoles = {};
|
|
3663
|
-
for (const m of projectMemberships) {
|
|
3664
|
-
projectRoles[m.project_id] = m.role;
|
|
3665
|
-
}
|
|
3666
|
-
return {
|
|
3667
|
-
...profile,
|
|
3668
|
-
isSystemAdmin: profile.is_system_admin ?? false,
|
|
3669
|
-
orgRoles,
|
|
3670
|
-
projectRoles
|
|
3671
|
-
};
|
|
3672
|
-
}
|
|
3673
|
-
function getProfileById(userId) {
|
|
3674
|
-
const db = getSqlite();
|
|
3675
|
-
return db.prepare("SELECT * FROM profiles WHERE id = ?").get(userId);
|
|
3676
|
-
}
|
|
3677
|
-
function getProfileByEmail(email) {
|
|
3678
|
-
const db = getSqlite();
|
|
3679
|
-
return db.prepare("SELECT * FROM profiles WHERE email = ?").get(email);
|
|
3680
|
-
}
|
|
3681
|
-
function updateProfile(userId, data) {
|
|
3682
|
-
const db = getSqlite();
|
|
3683
|
-
const fields = { ...data, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3684
|
-
const keys = Object.keys(fields);
|
|
3685
|
-
const setClause = keys.map((k) => `${k} = ?`).join(", ");
|
|
3686
|
-
const values = keys.map((k) => fields[k]);
|
|
3687
|
-
db.prepare(`UPDATE profiles SET ${setClause} WHERE id = ?`).run(...values, userId);
|
|
3688
|
-
return db.prepare("SELECT * FROM profiles WHERE id = ?").get(userId);
|
|
3689
|
-
}
|
|
3690
|
-
function createProfile(data) {
|
|
3691
|
-
const db = getSqlite();
|
|
3692
|
-
const id = (0, import_node_crypto8.randomUUID)();
|
|
3693
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3694
|
-
db.prepare(
|
|
3695
|
-
`INSERT INTO profiles (id, email, password_hash, full_name, display_name, created_at, updated_at)
|
|
3696
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
3697
|
-
).run(
|
|
3698
|
-
id,
|
|
3699
|
-
data.email,
|
|
3700
|
-
data.password_hash,
|
|
3701
|
-
data.full_name || null,
|
|
3702
|
-
data.display_name || null,
|
|
3703
|
-
now,
|
|
3704
|
-
now
|
|
3705
|
-
);
|
|
3706
|
-
return db.prepare("SELECT * FROM profiles WHERE id = ?").get(id);
|
|
3707
|
-
}
|
|
3708
|
-
function countProfiles() {
|
|
3709
|
-
const db = getSqlite();
|
|
3710
|
-
const row = db.prepare("SELECT COUNT(*) as count FROM profiles").get();
|
|
3711
|
-
return row.count;
|
|
3712
|
-
}
|
|
3713
|
-
function isSystemAdmin(userId) {
|
|
3714
|
-
const db = getSqlite();
|
|
3715
|
-
const row = db.prepare("SELECT is_system_admin FROM profiles WHERE id = ?").get(userId);
|
|
3716
|
-
return row?.is_system_admin === 1;
|
|
3717
|
-
}
|
|
3718
|
-
var import_node_crypto8;
|
|
3719
|
-
var init_profiles = __esm({
|
|
3720
|
-
"src/db/queries/profiles.ts"() {
|
|
3721
|
-
"use strict";
|
|
3722
|
-
init_connection();
|
|
3723
|
-
import_node_crypto8 = require("crypto");
|
|
3724
|
-
}
|
|
3725
|
-
});
|
|
3726
|
-
|
|
3727
3738
|
// src/db/queries/orgs.ts
|
|
3728
3739
|
function createOrg(data) {
|
|
3729
3740
|
const db = getSqlite();
|
|
@@ -3883,8 +3894,8 @@ var init_projects2 = __esm({
|
|
|
3883
3894
|
return { ok: true, data: [] };
|
|
3884
3895
|
}
|
|
3885
3896
|
try {
|
|
3886
|
-
const
|
|
3887
|
-
return { ok: true, data:
|
|
3897
|
+
const result2 = await openclaw.rpc("agents.list", {});
|
|
3898
|
+
return { ok: true, data: result2 || [] };
|
|
3888
3899
|
} catch (err) {
|
|
3889
3900
|
log.error("projects", "Failed to list agents from OpenClaw", err);
|
|
3890
3901
|
return { ok: true, data: [] };
|
|
@@ -5131,8 +5142,8 @@ async function dispatchToAgent(agentId, taskId, message) {
|
|
|
5131
5142
|
if (!openclaw.isHealthy()) {
|
|
5132
5143
|
throw new Error("AI agent system is unavailable");
|
|
5133
5144
|
}
|
|
5134
|
-
const
|
|
5135
|
-
return { runId:
|
|
5145
|
+
const result2 = await openclaw.dispatchTask(agentId, taskId, message);
|
|
5146
|
+
return { runId: result2?.runId };
|
|
5136
5147
|
}
|
|
5137
5148
|
function setupAgentEventForwarding() {
|
|
5138
5149
|
openclaw.onEvent("*", (event) => {
|
|
@@ -5349,9 +5360,9 @@ async function adminRpc(userId, orgId, method, params = {}) {
|
|
|
5349
5360
|
return { ok: false, error: { code: "OPENCLAW_UNAVAILABLE", message: "OpenClaw gateway is not connected" } };
|
|
5350
5361
|
}
|
|
5351
5362
|
try {
|
|
5352
|
-
const
|
|
5363
|
+
const result2 = await openclaw.rpc(method, params);
|
|
5353
5364
|
log.oc("out", { method });
|
|
5354
|
-
return { ok: true, data:
|
|
5365
|
+
return { ok: true, data: result2 };
|
|
5355
5366
|
} catch (err) {
|
|
5356
5367
|
log.error("admin", `RPC ${method} failed`, err);
|
|
5357
5368
|
return { ok: false, error: { code: "RPC_FAILED", message: err.message || `RPC ${method} failed` } };
|
|
@@ -5571,13 +5582,13 @@ var init_github3 = __esm({
|
|
|
5571
5582
|
repo.installation_id,
|
|
5572
5583
|
`/repos/${repo.repo_owner}/${repo.repo_name}/branches?per_page=100`
|
|
5573
5584
|
);
|
|
5574
|
-
const
|
|
5585
|
+
const result2 = branches.map((b) => ({
|
|
5575
5586
|
name: b.name,
|
|
5576
5587
|
sha: b.commit?.sha,
|
|
5577
5588
|
protected: b.protected
|
|
5578
5589
|
}));
|
|
5579
5590
|
const enriched = await Promise.all(
|
|
5580
|
-
|
|
5591
|
+
result2.slice(0, 30).map(async (branch) => {
|
|
5581
5592
|
try {
|
|
5582
5593
|
const commit = await githubApi(
|
|
5583
5594
|
repo.installation_id,
|
|
@@ -5593,7 +5604,7 @@ var init_github3 = __esm({
|
|
|
5593
5604
|
}
|
|
5594
5605
|
})
|
|
5595
5606
|
);
|
|
5596
|
-
const remaining =
|
|
5607
|
+
const remaining = result2.slice(30).map((b) => ({
|
|
5597
5608
|
...b,
|
|
5598
5609
|
lastCommitDate: null,
|
|
5599
5610
|
lastCommitMessage: null
|
|
@@ -6025,11 +6036,11 @@ var init_agent_bridge = __esm({
|
|
|
6025
6036
|
init_projects();
|
|
6026
6037
|
init_profiles();
|
|
6027
6038
|
agentBridge = new import_hono.Hono();
|
|
6028
|
-
agentBridge.post("/internal/task-update", async (
|
|
6039
|
+
agentBridge.post("/internal/task-update", async (c2) => {
|
|
6029
6040
|
try {
|
|
6030
|
-
const { task_id, status, ai_plan, ai_plan_status, comment, side } = await
|
|
6041
|
+
const { task_id, status, ai_plan, ai_plan_status, comment, side } = await c2.req.json();
|
|
6031
6042
|
if (!task_id) {
|
|
6032
|
-
return
|
|
6043
|
+
return c2.json({ ok: false, error: "task_id required" }, 400);
|
|
6033
6044
|
}
|
|
6034
6045
|
log.internal("POST", `/internal/task-update task=${task_id}`);
|
|
6035
6046
|
const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
@@ -6051,7 +6062,7 @@ var init_agent_bridge = __esm({
|
|
|
6051
6062
|
log.db("tasks", "UPDATE", task_id);
|
|
6052
6063
|
} catch (err) {
|
|
6053
6064
|
log.error("internal", "Task update failed", err);
|
|
6054
|
-
return
|
|
6065
|
+
return c2.json({ ok: false, error: err.message }, 500);
|
|
6055
6066
|
}
|
|
6056
6067
|
}
|
|
6057
6068
|
if (comment) {
|
|
@@ -6079,39 +6090,39 @@ var init_agent_bridge = __esm({
|
|
|
6079
6090
|
changes: updates
|
|
6080
6091
|
});
|
|
6081
6092
|
}
|
|
6082
|
-
return
|
|
6093
|
+
return c2.json({ ok: true });
|
|
6083
6094
|
} catch (err) {
|
|
6084
6095
|
log.error("internal", "task-update error", err);
|
|
6085
|
-
return
|
|
6096
|
+
return c2.json({ ok: false, error: err.message }, 500);
|
|
6086
6097
|
}
|
|
6087
6098
|
});
|
|
6088
|
-
agentBridge.post("/internal/task-get", async (
|
|
6099
|
+
agentBridge.post("/internal/task-get", async (c2) => {
|
|
6089
6100
|
try {
|
|
6090
|
-
const { task_id } = await
|
|
6101
|
+
const { task_id } = await c2.req.json();
|
|
6091
6102
|
if (!task_id) {
|
|
6092
|
-
return
|
|
6103
|
+
return c2.json({ ok: false, error: "task_id required" }, 400);
|
|
6093
6104
|
}
|
|
6094
6105
|
log.internal("POST", `/internal/task-get task=${task_id}`);
|
|
6095
6106
|
const task = getTaskForAgent(task_id);
|
|
6096
6107
|
if (!task) {
|
|
6097
|
-
return
|
|
6108
|
+
return c2.json({ ok: false, error: "Task not found" }, 500);
|
|
6098
6109
|
}
|
|
6099
|
-
return
|
|
6110
|
+
return c2.json({ ok: true, task });
|
|
6100
6111
|
} catch (err) {
|
|
6101
6112
|
log.error("internal", "task-get error", err);
|
|
6102
|
-
return
|
|
6113
|
+
return c2.json({ ok: false, error: err.message }, 500);
|
|
6103
6114
|
}
|
|
6104
6115
|
});
|
|
6105
|
-
agentBridge.post("/internal/git-list-branches", async (
|
|
6116
|
+
agentBridge.post("/internal/git-list-branches", async (c2) => {
|
|
6106
6117
|
try {
|
|
6107
|
-
const { task_id } = await
|
|
6108
|
-
if (!task_id) return
|
|
6118
|
+
const { task_id } = await c2.req.json();
|
|
6119
|
+
if (!task_id) return c2.json({ ok: false, error: "task_id required" }, 400);
|
|
6109
6120
|
log.internal("POST", `/internal/git-list-branches task=${task_id}`);
|
|
6110
6121
|
const taskBranches2 = getTaskBranchesWithRepo(task_id);
|
|
6111
6122
|
if (!taskBranches2 || taskBranches2.length === 0) {
|
|
6112
|
-
return
|
|
6123
|
+
return c2.json({ ok: true, branches: [] });
|
|
6113
6124
|
}
|
|
6114
|
-
const
|
|
6125
|
+
const result2 = [];
|
|
6115
6126
|
for (const tb of taskBranches2) {
|
|
6116
6127
|
const repo = tb.project_repos;
|
|
6117
6128
|
if (!repo?.installation_id) continue;
|
|
@@ -6120,7 +6131,7 @@ var init_agent_bridge = __esm({
|
|
|
6120
6131
|
repo.installation_id,
|
|
6121
6132
|
`/repos/${repo.repo_owner}/${repo.repo_name}/branches?per_page=100`
|
|
6122
6133
|
);
|
|
6123
|
-
|
|
6134
|
+
result2.push({
|
|
6124
6135
|
repo_label: repo.label,
|
|
6125
6136
|
repo_owner: repo.repo_owner,
|
|
6126
6137
|
repo_name: repo.repo_name,
|
|
@@ -6130,35 +6141,35 @@ var init_agent_bridge = __esm({
|
|
|
6130
6141
|
log.error("internal", `Failed to list branches for ${repo.repo_owner}/${repo.repo_name}`, err);
|
|
6131
6142
|
}
|
|
6132
6143
|
}
|
|
6133
|
-
return
|
|
6144
|
+
return c2.json({ ok: true, repos: result2 });
|
|
6134
6145
|
} catch (err) {
|
|
6135
6146
|
log.error("internal", "git-list-branches error", err);
|
|
6136
|
-
return
|
|
6147
|
+
return c2.json({ ok: false, error: err.message }, 500);
|
|
6137
6148
|
}
|
|
6138
6149
|
});
|
|
6139
|
-
agentBridge.post("/internal/git-create-branch", async (
|
|
6150
|
+
agentBridge.post("/internal/git-create-branch", async (c2) => {
|
|
6140
6151
|
try {
|
|
6141
|
-
const { task_id } = await
|
|
6142
|
-
if (!task_id) return
|
|
6152
|
+
const { task_id } = await c2.req.json();
|
|
6153
|
+
if (!task_id) return c2.json({ ok: false, error: "task_id required" }, 400);
|
|
6143
6154
|
log.internal("POST", `/internal/git-create-branch task=${task_id}`);
|
|
6144
6155
|
const task = getTask(task_id);
|
|
6145
|
-
if (!task) return
|
|
6156
|
+
if (!task) return c2.json({ ok: false, error: "Task not found" }, 404);
|
|
6146
6157
|
await createBranchesForTask(task_id, task.project_id);
|
|
6147
|
-
return
|
|
6158
|
+
return c2.json({ ok: true });
|
|
6148
6159
|
} catch (err) {
|
|
6149
6160
|
log.error("internal", "git-create-branch error", err);
|
|
6150
|
-
return
|
|
6161
|
+
return c2.json({ ok: false, error: err.message }, 500);
|
|
6151
6162
|
}
|
|
6152
6163
|
});
|
|
6153
|
-
agentBridge.post("/internal/git-create-pr", async (
|
|
6164
|
+
agentBridge.post("/internal/git-create-pr", async (c2) => {
|
|
6154
6165
|
try {
|
|
6155
|
-
const { task_id } = await
|
|
6156
|
-
if (!task_id) return
|
|
6166
|
+
const { task_id } = await c2.req.json();
|
|
6167
|
+
if (!task_id) return c2.json({ ok: false, error: "task_id required" }, 400);
|
|
6157
6168
|
log.internal("POST", `/internal/git-create-pr task=${task_id}`);
|
|
6158
6169
|
const task = getTaskForAgent(task_id);
|
|
6159
|
-
if (!task) return
|
|
6170
|
+
if (!task) return c2.json({ ok: false, error: "Task not found" }, 404);
|
|
6160
6171
|
const branches = getTaskBranchesWithRepo(task_id);
|
|
6161
|
-
if (!branches || branches.length === 0) return
|
|
6172
|
+
if (!branches || branches.length === 0) return c2.json({ ok: false, error: "No branches configured for this task" }, 400);
|
|
6162
6173
|
const project = getProject(task.project_id);
|
|
6163
6174
|
const results = [];
|
|
6164
6175
|
for (const tb of branches) {
|
|
@@ -6245,22 +6256,22 @@ ${task.ai_plan}`);
|
|
|
6245
6256
|
log.error("internal", `Failed to create PR for ${repo.repo_owner}/${repo.repo_name}`, err);
|
|
6246
6257
|
}
|
|
6247
6258
|
}
|
|
6248
|
-
return
|
|
6259
|
+
return c2.json({ ok: true, results });
|
|
6249
6260
|
} catch (err) {
|
|
6250
6261
|
log.error("internal", "git-create-pr error", err);
|
|
6251
|
-
return
|
|
6262
|
+
return c2.json({ ok: false, error: err.message }, 500);
|
|
6252
6263
|
}
|
|
6253
6264
|
});
|
|
6254
|
-
agentBridge.post("/internal/git-push", async (
|
|
6265
|
+
agentBridge.post("/internal/git-push", async (c2) => {
|
|
6255
6266
|
try {
|
|
6256
|
-
const { task_id, message } = await
|
|
6257
|
-
if (!task_id) return
|
|
6258
|
-
if (!message) return
|
|
6267
|
+
const { task_id, message } = await c2.req.json();
|
|
6268
|
+
if (!task_id) return c2.json({ ok: false, error: "task_id required" }, 400);
|
|
6269
|
+
if (!message) return c2.json({ ok: false, error: "message (commit message) required" }, 400);
|
|
6259
6270
|
log.internal("POST", `/internal/git-push task=${task_id}`);
|
|
6260
6271
|
const task = getTask(task_id);
|
|
6261
|
-
if (!task) return
|
|
6272
|
+
if (!task) return c2.json({ ok: false, error: "Task not found" }, 404);
|
|
6262
6273
|
const branches = getTaskBranchesWithRepo(task_id);
|
|
6263
|
-
if (branches.length === 0) return
|
|
6274
|
+
if (branches.length === 0) return c2.json({ ok: false, error: "No branches configured for this task" }, 400);
|
|
6264
6275
|
let dispatcher = null;
|
|
6265
6276
|
if (task.ai_dispatched_by) {
|
|
6266
6277
|
dispatcher = getProfileById(task.ai_dispatched_by);
|
|
@@ -6344,20 +6355,20 @@ ${task.ai_plan}`);
|
|
|
6344
6355
|
log.error("internal", `git-push failed for ${repoLabel}`, err);
|
|
6345
6356
|
}
|
|
6346
6357
|
}
|
|
6347
|
-
return
|
|
6358
|
+
return c2.json({ ok: true, results });
|
|
6348
6359
|
} catch (err) {
|
|
6349
6360
|
log.error("internal", "git-push error", err);
|
|
6350
|
-
return
|
|
6361
|
+
return c2.json({ ok: false, error: err.message }, 500);
|
|
6351
6362
|
}
|
|
6352
6363
|
});
|
|
6353
|
-
agentBridge.post("/internal/git-pr-status", async (
|
|
6364
|
+
agentBridge.post("/internal/git-pr-status", async (c2) => {
|
|
6354
6365
|
try {
|
|
6355
|
-
const { task_id } = await
|
|
6356
|
-
if (!task_id) return
|
|
6366
|
+
const { task_id } = await c2.req.json();
|
|
6367
|
+
if (!task_id) return c2.json({ ok: false, error: "task_id required" }, 400);
|
|
6357
6368
|
log.internal("POST", `/internal/git-pr-status task=${task_id}`);
|
|
6358
6369
|
const branches = getTaskBranchesWithRepo(task_id);
|
|
6359
6370
|
if (!branches || branches.length === 0) {
|
|
6360
|
-
return
|
|
6371
|
+
return c2.json({ ok: true, branches: [], summary: "No branches configured" });
|
|
6361
6372
|
}
|
|
6362
6373
|
const results = branches.map((tb) => {
|
|
6363
6374
|
const repo = tb.project_repos;
|
|
@@ -6400,14 +6411,14 @@ ${task.ai_plan}`);
|
|
|
6400
6411
|
const readyToMerge = results.some(
|
|
6401
6412
|
(r) => r.pr_number && r.merge_readiness.ci_passed && r.merge_readiness.approved && r.merge_readiness.no_conflicts && r.merge_readiness.up_to_date
|
|
6402
6413
|
);
|
|
6403
|
-
return
|
|
6414
|
+
return c2.json({
|
|
6404
6415
|
ok: true,
|
|
6405
6416
|
branches: results,
|
|
6406
6417
|
summary: readyToMerge ? "Ready to merge" : results.some((r) => r.pr_number) ? "PR open -- not ready" : "No PR yet"
|
|
6407
6418
|
});
|
|
6408
6419
|
} catch (err) {
|
|
6409
6420
|
log.error("internal", "git-pr-status error", err);
|
|
6410
|
-
return
|
|
6421
|
+
return c2.json({ ok: false, error: err.message }, 500);
|
|
6411
6422
|
}
|
|
6412
6423
|
});
|
|
6413
6424
|
}
|
|
@@ -6450,10 +6461,10 @@ var init_middleware = __esm({
|
|
|
6450
6461
|
"use strict";
|
|
6451
6462
|
import_factory = require("hono/factory");
|
|
6452
6463
|
init_jwt();
|
|
6453
|
-
authMiddleware = (0, import_factory.createMiddleware)(async (
|
|
6454
|
-
const authHeader =
|
|
6464
|
+
authMiddleware = (0, import_factory.createMiddleware)(async (c2, next) => {
|
|
6465
|
+
const authHeader = c2.req.header("Authorization");
|
|
6455
6466
|
if (!authHeader?.startsWith("Bearer ")) {
|
|
6456
|
-
return
|
|
6467
|
+
return c2.json(
|
|
6457
6468
|
{ error: "Missing or invalid authorization header", code: "UNAUTHORIZED" },
|
|
6458
6469
|
401
|
|
6459
6470
|
);
|
|
@@ -6461,10 +6472,10 @@ var init_middleware = __esm({
|
|
|
6461
6472
|
const token = authHeader.slice(7);
|
|
6462
6473
|
try {
|
|
6463
6474
|
const payload = verifyAccessToken(token);
|
|
6464
|
-
|
|
6465
|
-
|
|
6475
|
+
c2.set("userId", payload.sub);
|
|
6476
|
+
c2.set("email", payload.email);
|
|
6466
6477
|
} catch {
|
|
6467
|
-
return
|
|
6478
|
+
return c2.json(
|
|
6468
6479
|
{ error: "Invalid or expired token", code: "UNAUTHORIZED" },
|
|
6469
6480
|
401
|
|
6470
6481
|
);
|
|
@@ -6524,14 +6535,14 @@ var init_routes = __esm({
|
|
|
6524
6535
|
preferences: import_zod5.z.record(import_zod5.z.unknown()).optional()
|
|
6525
6536
|
});
|
|
6526
6537
|
auth = new import_hono2.Hono();
|
|
6527
|
-
auth.post("/signup", async (
|
|
6528
|
-
const body = await
|
|
6538
|
+
auth.post("/signup", async (c2) => {
|
|
6539
|
+
const body = await c2.req.json().catch(() => null);
|
|
6529
6540
|
if (!body) {
|
|
6530
|
-
return
|
|
6541
|
+
return c2.json({ error: "Invalid JSON body", code: "VALIDATION_ERROR" }, 400);
|
|
6531
6542
|
}
|
|
6532
6543
|
const parsed = signupSchema.safeParse(body);
|
|
6533
6544
|
if (!parsed.success) {
|
|
6534
|
-
return
|
|
6545
|
+
return c2.json(
|
|
6535
6546
|
{ error: parsed.error.issues[0].message, code: "VALIDATION_ERROR" },
|
|
6536
6547
|
400
|
|
6537
6548
|
);
|
|
@@ -6540,7 +6551,7 @@ var init_routes = __esm({
|
|
|
6540
6551
|
const db = getSqlite();
|
|
6541
6552
|
const existing = db.prepare("SELECT id FROM profiles WHERE email = ?").get(email);
|
|
6542
6553
|
if (existing) {
|
|
6543
|
-
return
|
|
6554
|
+
return c2.json({ error: "Email already registered", code: "CONFLICT" }, 409);
|
|
6544
6555
|
}
|
|
6545
6556
|
const passwordHash = await hashPassword(password);
|
|
6546
6557
|
const userCount = db.prepare("SELECT COUNT(*) as count FROM profiles").get();
|
|
@@ -6554,20 +6565,20 @@ var init_routes = __esm({
|
|
|
6554
6565
|
const accessToken = signAccessToken({ sub: id, email });
|
|
6555
6566
|
const refreshToken = createRefreshTokenRow(id);
|
|
6556
6567
|
const profile = db.prepare("SELECT * FROM profiles WHERE id = ?").get(id);
|
|
6557
|
-
return
|
|
6568
|
+
return c2.json({
|
|
6558
6569
|
access_token: accessToken,
|
|
6559
6570
|
refresh_token: refreshToken,
|
|
6560
6571
|
profile: sanitizeProfile(profile)
|
|
6561
6572
|
}, 201);
|
|
6562
6573
|
});
|
|
6563
|
-
auth.post("/login", async (
|
|
6564
|
-
const body = await
|
|
6574
|
+
auth.post("/login", async (c2) => {
|
|
6575
|
+
const body = await c2.req.json().catch(() => null);
|
|
6565
6576
|
if (!body) {
|
|
6566
|
-
return
|
|
6577
|
+
return c2.json({ error: "Invalid JSON body", code: "VALIDATION_ERROR" }, 400);
|
|
6567
6578
|
}
|
|
6568
6579
|
const parsed = loginSchema.safeParse(body);
|
|
6569
6580
|
if (!parsed.success) {
|
|
6570
|
-
return
|
|
6581
|
+
return c2.json(
|
|
6571
6582
|
{ error: parsed.error.issues[0].message, code: "VALIDATION_ERROR" },
|
|
6572
6583
|
400
|
|
6573
6584
|
);
|
|
@@ -6576,28 +6587,28 @@ var init_routes = __esm({
|
|
|
6576
6587
|
const db = getSqlite();
|
|
6577
6588
|
const profile = db.prepare("SELECT * FROM profiles WHERE email = ?").get(email);
|
|
6578
6589
|
if (!profile) {
|
|
6579
|
-
return
|
|
6590
|
+
return c2.json({ error: "Invalid email or password", code: "UNAUTHORIZED" }, 401);
|
|
6580
6591
|
}
|
|
6581
6592
|
const valid = await verifyPassword(profile.password_hash, password);
|
|
6582
6593
|
if (!valid) {
|
|
6583
|
-
return
|
|
6594
|
+
return c2.json({ error: "Invalid email or password", code: "UNAUTHORIZED" }, 401);
|
|
6584
6595
|
}
|
|
6585
6596
|
const accessToken = signAccessToken({ sub: profile.id, email });
|
|
6586
6597
|
const refreshToken = createRefreshTokenRow(profile.id);
|
|
6587
|
-
return
|
|
6598
|
+
return c2.json({
|
|
6588
6599
|
access_token: accessToken,
|
|
6589
6600
|
refresh_token: refreshToken,
|
|
6590
6601
|
profile: sanitizeProfile(profile)
|
|
6591
6602
|
});
|
|
6592
6603
|
});
|
|
6593
|
-
auth.post("/refresh", async (
|
|
6594
|
-
const body = await
|
|
6604
|
+
auth.post("/refresh", async (c2) => {
|
|
6605
|
+
const body = await c2.req.json().catch(() => null);
|
|
6595
6606
|
if (!body) {
|
|
6596
|
-
return
|
|
6607
|
+
return c2.json({ error: "Invalid JSON body", code: "VALIDATION_ERROR" }, 400);
|
|
6597
6608
|
}
|
|
6598
6609
|
const parsed = refreshSchema.safeParse(body);
|
|
6599
6610
|
if (!parsed.success) {
|
|
6600
|
-
return
|
|
6611
|
+
return c2.json(
|
|
6601
6612
|
{ error: parsed.error.issues[0].message, code: "VALIDATION_ERROR" },
|
|
6602
6613
|
400
|
|
6603
6614
|
);
|
|
@@ -6608,45 +6619,45 @@ var init_routes = __esm({
|
|
|
6608
6619
|
"SELECT * FROM refresh_tokens WHERE token = ?"
|
|
6609
6620
|
).get(refresh_token);
|
|
6610
6621
|
if (!tokenRow) {
|
|
6611
|
-
return
|
|
6622
|
+
return c2.json({ error: "Invalid refresh token", code: "UNAUTHORIZED" }, 401);
|
|
6612
6623
|
}
|
|
6613
6624
|
if (new Date(tokenRow.expires_at) < /* @__PURE__ */ new Date()) {
|
|
6614
6625
|
db.prepare("DELETE FROM refresh_tokens WHERE id = ?").run(tokenRow.id);
|
|
6615
|
-
return
|
|
6626
|
+
return c2.json({ error: "Refresh token expired", code: "UNAUTHORIZED" }, 401);
|
|
6616
6627
|
}
|
|
6617
6628
|
const userId = tokenRow.user_id;
|
|
6618
6629
|
const profile = db.prepare("SELECT * FROM profiles WHERE id = ?").get(userId);
|
|
6619
6630
|
if (!profile) {
|
|
6620
6631
|
db.prepare("DELETE FROM refresh_tokens WHERE id = ?").run(tokenRow.id);
|
|
6621
|
-
return
|
|
6632
|
+
return c2.json({ error: "User not found", code: "UNAUTHORIZED" }, 401);
|
|
6622
6633
|
}
|
|
6623
6634
|
db.prepare("DELETE FROM refresh_tokens WHERE id = ?").run(tokenRow.id);
|
|
6624
6635
|
const newAccessToken = signAccessToken({ sub: userId, email: profile.email });
|
|
6625
6636
|
const newRefreshToken = createRefreshTokenRow(userId);
|
|
6626
|
-
return
|
|
6637
|
+
return c2.json({
|
|
6627
6638
|
access_token: newAccessToken,
|
|
6628
6639
|
refresh_token: newRefreshToken,
|
|
6629
6640
|
profile: sanitizeProfile(profile)
|
|
6630
6641
|
});
|
|
6631
6642
|
});
|
|
6632
|
-
auth.get("/me", authMiddleware, async (
|
|
6633
|
-
const userId =
|
|
6643
|
+
auth.get("/me", authMiddleware, async (c2) => {
|
|
6644
|
+
const userId = c2.get("userId");
|
|
6634
6645
|
const db = getSqlite();
|
|
6635
6646
|
const profile = db.prepare("SELECT * FROM profiles WHERE id = ?").get(userId);
|
|
6636
6647
|
if (!profile) {
|
|
6637
|
-
return
|
|
6648
|
+
return c2.json({ error: "Profile not found", code: "NOT_FOUND" }, 404);
|
|
6638
6649
|
}
|
|
6639
|
-
return
|
|
6650
|
+
return c2.json({ data: sanitizeProfile(profile) });
|
|
6640
6651
|
});
|
|
6641
|
-
auth.patch("/me", authMiddleware, async (
|
|
6642
|
-
const userId =
|
|
6643
|
-
const body = await
|
|
6652
|
+
auth.patch("/me", authMiddleware, async (c2) => {
|
|
6653
|
+
const userId = c2.get("userId");
|
|
6654
|
+
const body = await c2.req.json().catch(() => null);
|
|
6644
6655
|
if (!body) {
|
|
6645
|
-
return
|
|
6656
|
+
return c2.json({ error: "Invalid JSON body", code: "VALIDATION_ERROR" }, 400);
|
|
6646
6657
|
}
|
|
6647
6658
|
const parsed = updateProfileSchema2.safeParse(body);
|
|
6648
6659
|
if (!parsed.success) {
|
|
6649
|
-
return
|
|
6660
|
+
return c2.json(
|
|
6650
6661
|
{ error: parsed.error.issues[0].message, code: "VALIDATION_ERROR" },
|
|
6651
6662
|
400
|
|
6652
6663
|
);
|
|
@@ -6671,29 +6682,29 @@ var init_routes = __esm({
|
|
|
6671
6682
|
values.push(key === "preferences" ? JSON.stringify(value) : value);
|
|
6672
6683
|
}
|
|
6673
6684
|
if (setClauses.length === 0) {
|
|
6674
|
-
return
|
|
6685
|
+
return c2.json({ error: "No valid fields to update", code: "VALIDATION_ERROR" }, 400);
|
|
6675
6686
|
}
|
|
6676
6687
|
setClauses.push("updated_at = ?");
|
|
6677
6688
|
values.push((/* @__PURE__ */ new Date()).toISOString());
|
|
6678
6689
|
values.push(userId);
|
|
6679
6690
|
db.prepare(`UPDATE profiles SET ${setClauses.join(", ")} WHERE id = ?`).run(...values);
|
|
6680
6691
|
const profile = db.prepare("SELECT * FROM profiles WHERE id = ?").get(userId);
|
|
6681
|
-
return
|
|
6692
|
+
return c2.json({ data: sanitizeProfile(profile) });
|
|
6682
6693
|
});
|
|
6683
6694
|
routes_default = auth;
|
|
6684
6695
|
}
|
|
6685
6696
|
});
|
|
6686
6697
|
|
|
6687
6698
|
// src/lib/errors.ts
|
|
6688
|
-
function errorResponse(
|
|
6689
|
-
return
|
|
6699
|
+
function errorResponse(c2, statusCode, message, code) {
|
|
6700
|
+
return c2.json({ error: { message, code: code ?? "ERROR" } }, statusCode);
|
|
6690
6701
|
}
|
|
6691
|
-
function handleError(
|
|
6702
|
+
function handleError(c2, err) {
|
|
6692
6703
|
if (err instanceof AppError) {
|
|
6693
|
-
return errorResponse(
|
|
6704
|
+
return errorResponse(c2, err.statusCode, err.message, err.code);
|
|
6694
6705
|
}
|
|
6695
6706
|
console.error("Unhandled error:", err);
|
|
6696
|
-
return errorResponse(
|
|
6707
|
+
return errorResponse(c2, 500, "Internal server error", "INTERNAL_ERROR");
|
|
6697
6708
|
}
|
|
6698
6709
|
var AppError;
|
|
6699
6710
|
var init_errors = __esm({
|
|
@@ -6812,12 +6823,12 @@ function extractTaskReference(text2) {
|
|
|
6812
6823
|
taskNumber: parseInt(parts[1], 10)
|
|
6813
6824
|
};
|
|
6814
6825
|
}
|
|
6815
|
-
async function handlePullRequestEvent(
|
|
6826
|
+
async function handlePullRequestEvent(c2, payload) {
|
|
6816
6827
|
const action = payload.action;
|
|
6817
6828
|
const pr = payload.pull_request;
|
|
6818
6829
|
const repo = payload.repository;
|
|
6819
6830
|
if (!pr || !repo) {
|
|
6820
|
-
return
|
|
6831
|
+
return c2.json({ message: "Invalid payload" }, 200);
|
|
6821
6832
|
}
|
|
6822
6833
|
const repoOwner = repo.owner?.login;
|
|
6823
6834
|
const repoName = repo.name;
|
|
@@ -6841,7 +6852,7 @@ async function handlePullRequestEvent(c, payload) {
|
|
|
6841
6852
|
}
|
|
6842
6853
|
if (!projectId) {
|
|
6843
6854
|
log.debug("github", `No matching project for ${repoOwner}/${repoName}`);
|
|
6844
|
-
return
|
|
6855
|
+
return c2.json({ message: "No matching project found" }, 200);
|
|
6845
6856
|
}
|
|
6846
6857
|
const project = { id: projectId, prefix: projectPrefix };
|
|
6847
6858
|
const searchText = [pr.title || "", pr.body || "", pr.head?.ref || ""].join(" ");
|
|
@@ -6911,7 +6922,7 @@ async function handlePullRequestEvent(c, payload) {
|
|
|
6911
6922
|
);
|
|
6912
6923
|
} catch (err) {
|
|
6913
6924
|
log.error("github", "Failed to upsert pull request", err);
|
|
6914
|
-
return
|
|
6925
|
+
return c2.json({ message: "Failed to process webhook" }, 200);
|
|
6915
6926
|
}
|
|
6916
6927
|
log.db("pull_requests", "UPSERT", `PR #${pr.number} (${action}) for project ${project.id}`);
|
|
6917
6928
|
const branchRef = pr.head?.ref;
|
|
@@ -6979,25 +6990,25 @@ async function handlePullRequestEvent(c, payload) {
|
|
|
6979
6990
|
if (projectId) {
|
|
6980
6991
|
broadcastToProject(projectId, "task.updated", { taskId, changes: { pr_event: action } });
|
|
6981
6992
|
}
|
|
6982
|
-
return
|
|
6993
|
+
return c2.json({ message: "Webhook processed" }, 200);
|
|
6983
6994
|
}
|
|
6984
|
-
async function handleCheckRunEvent(
|
|
6995
|
+
async function handleCheckRunEvent(c2, payload) {
|
|
6985
6996
|
const action = payload.action;
|
|
6986
6997
|
const checkRun = payload.check_run;
|
|
6987
6998
|
const repo = payload.repository;
|
|
6988
6999
|
if (!checkRun || !repo || action !== "completed") {
|
|
6989
|
-
return
|
|
7000
|
+
return c2.json({ message: "Check run ignored" }, 200);
|
|
6990
7001
|
}
|
|
6991
7002
|
log.info("github", `Check run ${checkRun.name}: ${checkRun.conclusion} on ${repo.full_name}`);
|
|
6992
7003
|
const headSha = checkRun.head_sha;
|
|
6993
|
-
if (!headSha) return
|
|
7004
|
+
if (!headSha) return c2.json({ message: "No head_sha" }, 200);
|
|
6994
7005
|
const db = getSqlite();
|
|
6995
7006
|
const prRecord = db.prepare(
|
|
6996
7007
|
"SELECT task_id, project_id FROM pull_requests WHERE head_sha = ? LIMIT 1"
|
|
6997
7008
|
).get(headSha);
|
|
6998
7009
|
if (!prRecord?.task_id) {
|
|
6999
7010
|
log.debug("github", `No PR/task found for check run SHA ${headSha.substring(0, 7)}`);
|
|
7000
|
-
return
|
|
7011
|
+
return c2.json({ message: "No matching task" }, 200);
|
|
7001
7012
|
}
|
|
7002
7013
|
const taskId = prRecord.task_id;
|
|
7003
7014
|
const projectId = prRecord.project_id;
|
|
@@ -7005,11 +7016,11 @@ async function handleCheckRunEvent(c, payload) {
|
|
|
7005
7016
|
"SELECT id, ci_checks, merge_readiness FROM task_branches WHERE task_id = ?"
|
|
7006
7017
|
).all(taskId);
|
|
7007
7018
|
if (!taskBranches2 || taskBranches2.length === 0) {
|
|
7008
|
-
return
|
|
7019
|
+
return c2.json({ message: "No task branches" }, 200);
|
|
7009
7020
|
}
|
|
7010
7021
|
const tb = taskBranches2[0];
|
|
7011
7022
|
const ciChecks = tb.ci_checks ? typeof tb.ci_checks === "string" ? JSON.parse(tb.ci_checks) : tb.ci_checks : [];
|
|
7012
|
-
const existingIdx = ciChecks.findIndex((
|
|
7023
|
+
const existingIdx = ciChecks.findIndex((c3) => c3.name === checkRun.name);
|
|
7013
7024
|
const checkData = {
|
|
7014
7025
|
name: checkRun.name,
|
|
7015
7026
|
status: checkRun.status,
|
|
@@ -7025,7 +7036,7 @@ async function handleCheckRunEvent(c, payload) {
|
|
|
7025
7036
|
} else {
|
|
7026
7037
|
ciChecks.push(checkData);
|
|
7027
7038
|
}
|
|
7028
|
-
const allPassed = ciChecks.length > 0 && ciChecks.every((
|
|
7039
|
+
const allPassed = ciChecks.length > 0 && ciChecks.every((c3) => c3.conclusion === "success" || c3.conclusion === "neutral" || c3.conclusion === "skipped");
|
|
7029
7040
|
const existingMergeReadiness = tb.merge_readiness ? typeof tb.merge_readiness === "string" ? JSON.parse(tb.merge_readiness) : tb.merge_readiness : {};
|
|
7030
7041
|
db.prepare(
|
|
7031
7042
|
"UPDATE task_branches SET ci_checks = ?, merge_readiness = ? WHERE id = ?"
|
|
@@ -7055,17 +7066,17 @@ async function handleCheckRunEvent(c, payload) {
|
|
|
7055
7066
|
}).catch(() => {
|
|
7056
7067
|
});
|
|
7057
7068
|
}
|
|
7058
|
-
return
|
|
7069
|
+
return c2.json({ message: "Check run processed" }, 200);
|
|
7059
7070
|
}
|
|
7060
|
-
async function handleCheckSuiteEvent(
|
|
7071
|
+
async function handleCheckSuiteEvent(c2, payload) {
|
|
7061
7072
|
const action = payload.action;
|
|
7062
7073
|
const suite = payload.check_suite;
|
|
7063
7074
|
const repo = payload.repository;
|
|
7064
7075
|
if (!suite || !repo || action !== "completed") {
|
|
7065
|
-
return
|
|
7076
|
+
return c2.json({ message: "Check suite ignored" }, 200);
|
|
7066
7077
|
}
|
|
7067
7078
|
if (suite.conclusion !== "success") {
|
|
7068
|
-
return
|
|
7079
|
+
return c2.json({ message: "Suite not successful" }, 200);
|
|
7069
7080
|
}
|
|
7070
7081
|
log.info("github", `Check suite passed on ${repo.full_name}, branch: ${suite.head_branch}`);
|
|
7071
7082
|
const db = getSqlite();
|
|
@@ -7073,7 +7084,7 @@ async function handleCheckSuiteEvent(c, payload) {
|
|
|
7073
7084
|
"SELECT id, github_pr_number, task_id, project_id, is_draft FROM pull_requests WHERE head_sha = ? AND is_draft = 1 LIMIT 1"
|
|
7074
7085
|
).get(suite.head_sha);
|
|
7075
7086
|
if (!prRecord) {
|
|
7076
|
-
return
|
|
7087
|
+
return c2.json({ message: "No draft PR to convert" }, 200);
|
|
7077
7088
|
}
|
|
7078
7089
|
const repoOwner = repo.owner?.login;
|
|
7079
7090
|
const repoName = repo.name;
|
|
@@ -7107,22 +7118,22 @@ async function handleCheckSuiteEvent(c, payload) {
|
|
|
7107
7118
|
log.error("github", `Failed to mark PR #${prRecord.github_pr_number} as ready`, err);
|
|
7108
7119
|
}
|
|
7109
7120
|
}
|
|
7110
|
-
return
|
|
7121
|
+
return c2.json({ message: "Check suite processed" }, 200);
|
|
7111
7122
|
}
|
|
7112
|
-
async function handlePullRequestReviewEvent(
|
|
7123
|
+
async function handlePullRequestReviewEvent(c2, payload) {
|
|
7113
7124
|
const action = payload.action;
|
|
7114
7125
|
const review = payload.review;
|
|
7115
7126
|
const pr = payload.pull_request;
|
|
7116
7127
|
const repo = payload.repository;
|
|
7117
7128
|
if (!review || !pr || !repo || action !== "submitted") {
|
|
7118
|
-
return
|
|
7129
|
+
return c2.json({ message: "Review ignored" }, 200);
|
|
7119
7130
|
}
|
|
7120
7131
|
const reviewState = review.state;
|
|
7121
7132
|
const reviewer = review.user?.login;
|
|
7122
7133
|
const commitId = review.commit_id;
|
|
7123
7134
|
log.info("github", `Review by ${reviewer}: ${reviewState} on PR #${pr.number}`);
|
|
7124
7135
|
const branchRef = pr.head?.ref;
|
|
7125
|
-
if (!branchRef) return
|
|
7136
|
+
if (!branchRef) return c2.json({ message: "No branch ref" }, 200);
|
|
7126
7137
|
const db = getSqlite();
|
|
7127
7138
|
const taskBranch = db.prepare(
|
|
7128
7139
|
`SELECT tb.id, tb.task_id, tb.review_statuses, tb.merge_readiness, pr2.project_id
|
|
@@ -7132,7 +7143,7 @@ async function handlePullRequestReviewEvent(c, payload) {
|
|
|
7132
7143
|
).get(branchRef);
|
|
7133
7144
|
if (!taskBranch) {
|
|
7134
7145
|
log.debug("github", `No task_branch for branch ${branchRef}`);
|
|
7135
|
-
return
|
|
7146
|
+
return c2.json({ message: "No matching task branch" }, 200);
|
|
7136
7147
|
}
|
|
7137
7148
|
const taskId = taskBranch.task_id;
|
|
7138
7149
|
const projectId = taskBranch.project_id;
|
|
@@ -7172,15 +7183,15 @@ async function handlePullRequestReviewEvent(c, payload) {
|
|
|
7172
7183
|
}).catch(() => {
|
|
7173
7184
|
});
|
|
7174
7185
|
}
|
|
7175
|
-
return
|
|
7186
|
+
return c2.json({ message: "Review processed" }, 200);
|
|
7176
7187
|
}
|
|
7177
|
-
async function handlePushEvent(
|
|
7188
|
+
async function handlePushEvent(c2, payload) {
|
|
7178
7189
|
const ref = payload.ref;
|
|
7179
7190
|
const repo = payload.repository;
|
|
7180
7191
|
const commits = payload.commits || [];
|
|
7181
7192
|
const forced = payload.forced || false;
|
|
7182
7193
|
if (!ref || !repo) {
|
|
7183
|
-
return
|
|
7194
|
+
return c2.json({ message: "Invalid push payload" }, 200);
|
|
7184
7195
|
}
|
|
7185
7196
|
const branchName = ref.replace("refs/heads/", "");
|
|
7186
7197
|
const repoOwner = repo.owner?.login || repo.owner?.name;
|
|
@@ -7190,7 +7201,7 @@ async function handlePushEvent(c, payload) {
|
|
|
7190
7201
|
`Push to ${repo.full_name}/${branchName}: ${commits.length} commit(s)${forced ? " [FORCE PUSH]" : ""}`
|
|
7191
7202
|
);
|
|
7192
7203
|
if (commits.length === 0) {
|
|
7193
|
-
return
|
|
7204
|
+
return c2.json({ message: "Push with no commits" }, 200);
|
|
7194
7205
|
}
|
|
7195
7206
|
const db = getSqlite();
|
|
7196
7207
|
const taskBranch = db.prepare(
|
|
@@ -7201,7 +7212,7 @@ async function handlePushEvent(c, payload) {
|
|
|
7201
7212
|
).get(branchName);
|
|
7202
7213
|
if (!taskBranch) {
|
|
7203
7214
|
log.debug("github", `No task_branch found for branch ${branchName}`);
|
|
7204
|
-
return
|
|
7215
|
+
return c2.json({ message: "No matching task branch" }, 200);
|
|
7205
7216
|
}
|
|
7206
7217
|
const taskId = taskBranch.task_id;
|
|
7207
7218
|
const projectId = taskBranch.project_id;
|
|
@@ -7228,7 +7239,7 @@ async function handlePushEvent(c, payload) {
|
|
|
7228
7239
|
params.push(taskBranch.id);
|
|
7229
7240
|
db.prepare(updateSql).run(...params);
|
|
7230
7241
|
log.db("task_branches", "UPDATE", taskBranch.id);
|
|
7231
|
-
const commitMessages = commits.map((
|
|
7242
|
+
const commitMessages = commits.map((c3) => c3.message?.split("\n")[0] || "").join(", ");
|
|
7232
7243
|
createTaskActivity({
|
|
7233
7244
|
task_id: taskId,
|
|
7234
7245
|
project_id: projectId,
|
|
@@ -7238,11 +7249,11 @@ async function handlePushEvent(c, payload) {
|
|
|
7238
7249
|
branch_name: branchName,
|
|
7239
7250
|
commit_count: commits.length,
|
|
7240
7251
|
forced,
|
|
7241
|
-
commits: commits.slice(0, 10).map((
|
|
7242
|
-
sha:
|
|
7243
|
-
message:
|
|
7244
|
-
author:
|
|
7245
|
-
timestamp:
|
|
7252
|
+
commits: commits.slice(0, 10).map((c3) => ({
|
|
7253
|
+
sha: c3.id?.substring(0, 7),
|
|
7254
|
+
message: c3.message?.split("\n")[0],
|
|
7255
|
+
author: c3.author?.username || c3.author?.name,
|
|
7256
|
+
timestamp: c3.timestamp
|
|
7246
7257
|
})),
|
|
7247
7258
|
files_touched: Array.from(allFilesTouched).slice(0, 20)
|
|
7248
7259
|
}
|
|
@@ -7283,13 +7294,13 @@ async function handlePushEvent(c, payload) {
|
|
|
7283
7294
|
}, true).catch(() => {
|
|
7284
7295
|
});
|
|
7285
7296
|
}
|
|
7286
|
-
return
|
|
7297
|
+
return c2.json({ message: "Push processed" }, 200);
|
|
7287
7298
|
}
|
|
7288
|
-
async function handleWorkflowJobEvent(
|
|
7299
|
+
async function handleWorkflowJobEvent(c2, payload) {
|
|
7289
7300
|
const action = payload.action;
|
|
7290
7301
|
const job = payload.workflow_job;
|
|
7291
7302
|
const repo = payload.repository;
|
|
7292
|
-
if (!job || !repo) return
|
|
7303
|
+
if (!job || !repo) return c2.json({ message: "Invalid workflow_job" }, 200);
|
|
7293
7304
|
const steps = (job.steps || []).map((s) => ({
|
|
7294
7305
|
name: s.name,
|
|
7295
7306
|
status: s.status,
|
|
@@ -7298,7 +7309,7 @@ async function handleWorkflowJobEvent(c, payload) {
|
|
|
7298
7309
|
completed_at: s.completed_at
|
|
7299
7310
|
}));
|
|
7300
7311
|
const headBranch = job.head_branch;
|
|
7301
|
-
if (!headBranch) return
|
|
7312
|
+
if (!headBranch) return c2.json({ message: "No head branch" }, 200);
|
|
7302
7313
|
const db = getSqlite();
|
|
7303
7314
|
const taskBranch = db.prepare(
|
|
7304
7315
|
`SELECT tb.id, tb.task_id, pr2.project_id
|
|
@@ -7306,7 +7317,7 @@ async function handleWorkflowJobEvent(c, payload) {
|
|
|
7306
7317
|
LEFT JOIN project_repos pr2 ON pr2.id = tb.repo_id
|
|
7307
7318
|
WHERE tb.branch_name = ? LIMIT 1`
|
|
7308
7319
|
).get(headBranch);
|
|
7309
|
-
if (!taskBranch) return
|
|
7320
|
+
if (!taskBranch) return c2.json({ message: "No matching task branch" }, 200);
|
|
7310
7321
|
const taskId = taskBranch.task_id;
|
|
7311
7322
|
const projectId = taskBranch.project_id;
|
|
7312
7323
|
const deployStatus = action === "completed" ? job.conclusion === "success" ? "success" : "failure" : "in_progress";
|
|
@@ -7326,14 +7337,14 @@ async function handleWorkflowJobEvent(c, payload) {
|
|
|
7326
7337
|
if (projectId) {
|
|
7327
7338
|
broadcastToProject(projectId, "task.updated", { taskId, changes: { deploy_steps: steps } });
|
|
7328
7339
|
}
|
|
7329
|
-
return
|
|
7340
|
+
return c2.json({ message: "Workflow job processed" }, 200);
|
|
7330
7341
|
}
|
|
7331
|
-
async function handleDeploymentStatusEvent(
|
|
7342
|
+
async function handleDeploymentStatusEvent(c2, payload) {
|
|
7332
7343
|
const deploymentStatus = payload.deployment_status;
|
|
7333
7344
|
const deployment = payload.deployment;
|
|
7334
7345
|
const repo = payload.repository;
|
|
7335
7346
|
if (!deploymentStatus || !deployment || !repo) {
|
|
7336
|
-
return
|
|
7347
|
+
return c2.json({ message: "Invalid deployment_status" }, 200);
|
|
7337
7348
|
}
|
|
7338
7349
|
const environment = deployment.environment;
|
|
7339
7350
|
const state = deploymentStatus.state;
|
|
@@ -7350,7 +7361,7 @@ async function handleDeploymentStatusEvent(c, payload) {
|
|
|
7350
7361
|
).get(ref);
|
|
7351
7362
|
if (!taskBranch) {
|
|
7352
7363
|
log.debug("github", `No task_branch for deployment ref ${ref}`);
|
|
7353
|
-
return
|
|
7364
|
+
return c2.json({ message: "No matching task branch" }, 200);
|
|
7354
7365
|
}
|
|
7355
7366
|
const taskId = taskBranch.task_id;
|
|
7356
7367
|
const projectId = taskBranch.project_id;
|
|
@@ -7426,17 +7437,17 @@ async function handleDeploymentStatusEvent(c, payload) {
|
|
|
7426
7437
|
}
|
|
7427
7438
|
broadcastToProject(projectId, "task.updated", { taskId, changes: { deploy: { environment, state } } });
|
|
7428
7439
|
}
|
|
7429
|
-
return
|
|
7440
|
+
return c2.json({ message: "Deployment status processed" }, 200);
|
|
7430
7441
|
}
|
|
7431
|
-
async function handleDeploymentProtectionRuleEvent(
|
|
7442
|
+
async function handleDeploymentProtectionRuleEvent(c2, payload) {
|
|
7432
7443
|
const action = payload.action;
|
|
7433
7444
|
const environment = payload.environment;
|
|
7434
7445
|
const deployment = payload.deployment;
|
|
7435
7446
|
const repo = payload.repository;
|
|
7436
|
-
if (!environment || !repo) return
|
|
7447
|
+
if (!environment || !repo) return c2.json({ message: "Invalid protection rule" }, 200);
|
|
7437
7448
|
log.info("github", `Deploy protection: ${action} for ${environment} on ${repo.full_name}`);
|
|
7438
7449
|
const ref = deployment?.ref;
|
|
7439
|
-
if (!ref) return
|
|
7450
|
+
if (!ref) return c2.json({ message: "No ref" }, 200);
|
|
7440
7451
|
const db = getSqlite();
|
|
7441
7452
|
const taskBranch = db.prepare(
|
|
7442
7453
|
`SELECT tb.id, tb.task_id, pr2.project_id
|
|
@@ -7444,7 +7455,7 @@ async function handleDeploymentProtectionRuleEvent(c, payload) {
|
|
|
7444
7455
|
LEFT JOIN project_repos pr2 ON pr2.id = tb.repo_id
|
|
7445
7456
|
WHERE tb.branch_name = ? LIMIT 1`
|
|
7446
7457
|
).get(ref);
|
|
7447
|
-
if (!taskBranch) return
|
|
7458
|
+
if (!taskBranch) return c2.json({ message: "No matching task branch" }, 200);
|
|
7448
7459
|
const taskId = taskBranch.task_id;
|
|
7449
7460
|
const projectId = taskBranch.project_id;
|
|
7450
7461
|
const reviewers = (payload.reviewers || []).map((r) => r.reviewer?.login).filter(Boolean);
|
|
@@ -7467,29 +7478,29 @@ async function handleDeploymentProtectionRuleEvent(c, payload) {
|
|
|
7467
7478
|
if (projectId) {
|
|
7468
7479
|
broadcastToProject(projectId, "task.updated", { taskId, changes: { deploy_approval: approvals } });
|
|
7469
7480
|
}
|
|
7470
|
-
return
|
|
7481
|
+
return c2.json({ message: "Protection rule processed" }, 200);
|
|
7471
7482
|
}
|
|
7472
|
-
async function handleIssueCommentEvent(
|
|
7483
|
+
async function handleIssueCommentEvent(c2, payload) {
|
|
7473
7484
|
const action = payload.action;
|
|
7474
7485
|
const comment = payload.comment;
|
|
7475
7486
|
const issue = payload.issue;
|
|
7476
7487
|
const repo = payload.repository;
|
|
7477
7488
|
if (action !== "created" || !comment || !issue || !repo) {
|
|
7478
|
-
return
|
|
7489
|
+
return c2.json({ message: "Issue comment ignored" }, 200);
|
|
7479
7490
|
}
|
|
7480
|
-
if (!issue.pull_request) return
|
|
7491
|
+
if (!issue.pull_request) return c2.json({ message: "Not a PR comment" }, 200);
|
|
7481
7492
|
const prNumber = issue.number;
|
|
7482
7493
|
const repoOwner = repo.owner?.login;
|
|
7483
7494
|
const repoName = repo.name;
|
|
7484
7495
|
const author = comment.user?.login;
|
|
7485
7496
|
const body = comment.body;
|
|
7486
|
-
if (comment.performed_via_github_app) return
|
|
7497
|
+
if (comment.performed_via_github_app) return c2.json({ message: "Bot comment skipped" }, 200);
|
|
7487
7498
|
log.info("github", `PR comment by ${author} on #${prNumber} in ${repoOwner}/${repoName}`);
|
|
7488
7499
|
const db = getSqlite();
|
|
7489
7500
|
const prRecord = db.prepare(
|
|
7490
7501
|
"SELECT task_id, project_id FROM pull_requests WHERE github_pr_number = ? LIMIT 1"
|
|
7491
7502
|
).get(prNumber);
|
|
7492
|
-
if (!prRecord?.task_id) return
|
|
7503
|
+
if (!prRecord?.task_id) return c2.json({ message: "No linked task" }, 200);
|
|
7493
7504
|
const commentId = `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
7494
7505
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7495
7506
|
db.prepare(
|
|
@@ -7511,7 +7522,7 @@ ${body}`,
|
|
|
7511
7522
|
task_id: prRecord.task_id
|
|
7512
7523
|
});
|
|
7513
7524
|
}
|
|
7514
|
-
return
|
|
7525
|
+
return c2.json({ message: "Issue comment processed" }, 200);
|
|
7515
7526
|
}
|
|
7516
7527
|
var import_hono3, import_zod6, github, deliveryCache, DELIVERY_TTL_MS, connectRepoSchema2, github_default;
|
|
7517
7528
|
var init_github4 = __esm({
|
|
@@ -7542,13 +7553,13 @@ var init_github4 = __esm({
|
|
|
7542
7553
|
github_repo_owner: import_zod6.z.string().min(1),
|
|
7543
7554
|
github_repo_name: import_zod6.z.string().min(1)
|
|
7544
7555
|
});
|
|
7545
|
-
github.patch("/projects/:projectId/github", authMiddleware, async (
|
|
7556
|
+
github.patch("/projects/:projectId/github", authMiddleware, async (c2) => {
|
|
7546
7557
|
try {
|
|
7547
|
-
const projectId =
|
|
7548
|
-
const body = await
|
|
7558
|
+
const projectId = c2.req.param("projectId");
|
|
7559
|
+
const body = await c2.req.json();
|
|
7549
7560
|
const parsed = connectRepoSchema2.safeParse(body);
|
|
7550
7561
|
if (!parsed.success) {
|
|
7551
|
-
return errorResponse(
|
|
7562
|
+
return errorResponse(c2, 400, parsed.error.issues[0].message, "VALIDATION_ERROR");
|
|
7552
7563
|
}
|
|
7553
7564
|
const db = getSqlite();
|
|
7554
7565
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -7565,83 +7576,83 @@ var init_github4 = __esm({
|
|
|
7565
7576
|
);
|
|
7566
7577
|
const project = db.prepare("SELECT * FROM projects WHERE id = ?").get(projectId);
|
|
7567
7578
|
if (!project) {
|
|
7568
|
-
return errorResponse(
|
|
7579
|
+
return errorResponse(c2, 404, "Project not found", "NOT_FOUND");
|
|
7569
7580
|
}
|
|
7570
|
-
return
|
|
7581
|
+
return c2.json({ data: project });
|
|
7571
7582
|
} catch (err) {
|
|
7572
|
-
return handleError(
|
|
7583
|
+
return handleError(c2, err);
|
|
7573
7584
|
}
|
|
7574
7585
|
});
|
|
7575
|
-
github.get("/projects/:projectId/pull-requests", authMiddleware, async (
|
|
7586
|
+
github.get("/projects/:projectId/pull-requests", authMiddleware, async (c2) => {
|
|
7576
7587
|
try {
|
|
7577
|
-
const projectId =
|
|
7578
|
-
const taskId =
|
|
7588
|
+
const projectId = c2.req.param("projectId");
|
|
7589
|
+
const taskId = c2.req.query("task_id");
|
|
7579
7590
|
const data = getPullRequests(projectId, taskId);
|
|
7580
|
-
return
|
|
7591
|
+
return c2.json({ data: data || [] });
|
|
7581
7592
|
} catch (err) {
|
|
7582
|
-
return handleError(
|
|
7593
|
+
return handleError(c2, err);
|
|
7583
7594
|
}
|
|
7584
7595
|
});
|
|
7585
|
-
github.get("/tasks/:taskId/pull-requests", authMiddleware, async (
|
|
7596
|
+
github.get("/tasks/:taskId/pull-requests", authMiddleware, async (c2) => {
|
|
7586
7597
|
try {
|
|
7587
|
-
const taskId =
|
|
7598
|
+
const taskId = c2.req.param("taskId");
|
|
7588
7599
|
const data = getTaskPullRequests(taskId);
|
|
7589
|
-
return
|
|
7600
|
+
return c2.json({ data: data || [] });
|
|
7590
7601
|
} catch (err) {
|
|
7591
|
-
return handleError(
|
|
7602
|
+
return handleError(c2, err);
|
|
7592
7603
|
}
|
|
7593
7604
|
});
|
|
7594
|
-
github.post("/github/webhook", async (
|
|
7605
|
+
github.post("/github/webhook", async (c2) => {
|
|
7595
7606
|
try {
|
|
7596
|
-
const rawBody = await
|
|
7597
|
-
const signatureHeader =
|
|
7607
|
+
const rawBody = await c2.req.text();
|
|
7608
|
+
const signatureHeader = c2.req.header("x-hub-signature-256");
|
|
7598
7609
|
if (!verifyWebhookSignature(rawBody, signatureHeader)) {
|
|
7599
7610
|
log.warn("github", "Webhook signature verification failed");
|
|
7600
|
-
return
|
|
7611
|
+
return c2.json({ error: "Invalid signature" }, 401);
|
|
7601
7612
|
}
|
|
7602
7613
|
const payload = JSON.parse(rawBody);
|
|
7603
|
-
const event =
|
|
7604
|
-
const deliveryId =
|
|
7614
|
+
const event = c2.req.header("x-github-event");
|
|
7615
|
+
const deliveryId = c2.req.header("x-github-delivery");
|
|
7605
7616
|
log.info("github", `Webhook received: ${event} (delivery: ${deliveryId})`);
|
|
7606
7617
|
if (event === "ping") {
|
|
7607
|
-
return
|
|
7618
|
+
return c2.json({ message: "pong" }, 200);
|
|
7608
7619
|
}
|
|
7609
7620
|
if (isDeliveryDuplicate(deliveryId)) {
|
|
7610
7621
|
log.debug("github", `Skipping duplicate delivery: ${deliveryId}`);
|
|
7611
|
-
return
|
|
7622
|
+
return c2.json({ message: "Duplicate delivery ignored" }, 200);
|
|
7612
7623
|
}
|
|
7613
7624
|
if (event === "pull_request") {
|
|
7614
|
-
return await handlePullRequestEvent(
|
|
7625
|
+
return await handlePullRequestEvent(c2, payload);
|
|
7615
7626
|
}
|
|
7616
7627
|
if (event === "push") {
|
|
7617
|
-
return await handlePushEvent(
|
|
7628
|
+
return await handlePushEvent(c2, payload);
|
|
7618
7629
|
}
|
|
7619
7630
|
if (event === "check_run") {
|
|
7620
|
-
return await handleCheckRunEvent(
|
|
7631
|
+
return await handleCheckRunEvent(c2, payload);
|
|
7621
7632
|
}
|
|
7622
7633
|
if (event === "check_suite") {
|
|
7623
|
-
return await handleCheckSuiteEvent(
|
|
7634
|
+
return await handleCheckSuiteEvent(c2, payload);
|
|
7624
7635
|
}
|
|
7625
7636
|
if (event === "pull_request_review") {
|
|
7626
|
-
return await handlePullRequestReviewEvent(
|
|
7637
|
+
return await handlePullRequestReviewEvent(c2, payload);
|
|
7627
7638
|
}
|
|
7628
7639
|
if (event === "workflow_job") {
|
|
7629
|
-
return await handleWorkflowJobEvent(
|
|
7640
|
+
return await handleWorkflowJobEvent(c2, payload);
|
|
7630
7641
|
}
|
|
7631
7642
|
if (event === "deployment_status") {
|
|
7632
|
-
return await handleDeploymentStatusEvent(
|
|
7643
|
+
return await handleDeploymentStatusEvent(c2, payload);
|
|
7633
7644
|
}
|
|
7634
7645
|
if (event === "deployment_protection_rule") {
|
|
7635
|
-
return await handleDeploymentProtectionRuleEvent(
|
|
7646
|
+
return await handleDeploymentProtectionRuleEvent(c2, payload);
|
|
7636
7647
|
}
|
|
7637
7648
|
if (event === "issue_comment") {
|
|
7638
|
-
return await handleIssueCommentEvent(
|
|
7649
|
+
return await handleIssueCommentEvent(c2, payload);
|
|
7639
7650
|
}
|
|
7640
7651
|
log.debug("github", `Ignoring event: ${event}`);
|
|
7641
|
-
return
|
|
7652
|
+
return c2.json({ message: "Event ignored" }, 200);
|
|
7642
7653
|
} catch (err) {
|
|
7643
7654
|
log.error("github", "Webhook processing error", err);
|
|
7644
|
-
return
|
|
7655
|
+
return c2.json({ message: "Webhook error" }, 200);
|
|
7645
7656
|
}
|
|
7646
7657
|
});
|
|
7647
7658
|
github_default = github;
|
|
@@ -7719,7 +7730,7 @@ var init_index = __esm({
|
|
|
7719
7730
|
}
|
|
7720
7731
|
app.get(
|
|
7721
7732
|
"/health",
|
|
7722
|
-
(
|
|
7733
|
+
(c2) => c2.json({
|
|
7723
7734
|
status: "ok",
|
|
7724
7735
|
service: "closeclaw",
|
|
7725
7736
|
connections: getConnectionCount(),
|
|
@@ -7730,31 +7741,31 @@ var init_index = __esm({
|
|
|
7730
7741
|
app.route("/api", github_default);
|
|
7731
7742
|
app.route("", agentBridge);
|
|
7732
7743
|
attachmentRoutes = new import_hono4.Hono();
|
|
7733
|
-
attachmentRoutes.post("/api/attachments/upload", authMiddleware, async (
|
|
7734
|
-
const body = await
|
|
7744
|
+
attachmentRoutes.post("/api/attachments/upload", authMiddleware, async (c2) => {
|
|
7745
|
+
const body = await c2.req.parseBody();
|
|
7735
7746
|
const file = body["file"];
|
|
7736
7747
|
const taskId = body["task_id"];
|
|
7737
7748
|
if (!taskId || !file || typeof file === "string") {
|
|
7738
|
-
return
|
|
7749
|
+
return c2.json({ error: "task_id and file required" }, 400);
|
|
7739
7750
|
}
|
|
7740
7751
|
const arrayBuffer = await file.arrayBuffer();
|
|
7741
7752
|
const buffer = Buffer.from(arrayBuffer);
|
|
7742
7753
|
const { filePath, fileSize } = await saveFile(taskId, file.name, buffer);
|
|
7743
7754
|
const attachment = createAttachment({
|
|
7744
7755
|
task_id: taskId,
|
|
7745
|
-
uploaded_by:
|
|
7756
|
+
uploaded_by: c2.get("userId"),
|
|
7746
7757
|
file_name: file.name,
|
|
7747
7758
|
file_path: filePath,
|
|
7748
7759
|
file_type: file.type || void 0,
|
|
7749
7760
|
file_size: fileSize
|
|
7750
7761
|
});
|
|
7751
|
-
return
|
|
7762
|
+
return c2.json({ ok: true, data: attachment }, 201);
|
|
7752
7763
|
});
|
|
7753
|
-
attachmentRoutes.get("/api/attachments/:taskId/:filename", authMiddleware, (
|
|
7754
|
-
const taskId =
|
|
7755
|
-
const filename =
|
|
7764
|
+
attachmentRoutes.get("/api/attachments/:taskId/:filename", authMiddleware, (c2) => {
|
|
7765
|
+
const taskId = c2.req.param("taskId");
|
|
7766
|
+
const filename = c2.req.param("filename");
|
|
7756
7767
|
const fullPath = getFilePath(`${taskId}/${filename}`);
|
|
7757
|
-
if (!fullPath) return
|
|
7768
|
+
if (!fullPath) return c2.json({ error: "File not found" }, 404);
|
|
7758
7769
|
const stats = (0, import_node_fs6.statSync)(fullPath);
|
|
7759
7770
|
const stream = (0, import_node_fs6.createReadStream)(fullPath);
|
|
7760
7771
|
return new Response(import_node_stream.Readable.toWeb(stream), {
|
|
@@ -7801,19 +7812,43 @@ var import_node_fs7 = require("fs");
|
|
|
7801
7812
|
var import_node_path6 = require("path");
|
|
7802
7813
|
var import_node_child_process3 = require("child_process");
|
|
7803
7814
|
var import_node_readline = require("readline");
|
|
7815
|
+
var c = {
|
|
7816
|
+
reset: "\x1B[0m",
|
|
7817
|
+
bold: "\x1B[1m",
|
|
7818
|
+
dim: "\x1B[2m",
|
|
7819
|
+
green: "\x1B[32m",
|
|
7820
|
+
red: "\x1B[31m",
|
|
7821
|
+
yellow: "\x1B[33m",
|
|
7822
|
+
cyan: "\x1B[36m",
|
|
7823
|
+
magenta: "\x1B[35m",
|
|
7824
|
+
white: "\x1B[37m"
|
|
7825
|
+
};
|
|
7826
|
+
var PASS = `${c.green}PASS${c.reset}`;
|
|
7827
|
+
var FAIL = `${c.red}FAIL${c.reset}`;
|
|
7828
|
+
var WARN = `${c.yellow}WARN${c.reset}`;
|
|
7829
|
+
var SKIP = `${c.dim}SKIP${c.reset}`;
|
|
7830
|
+
var INFO = `${c.cyan}INFO${c.reset}`;
|
|
7831
|
+
function step(num, total, label) {
|
|
7832
|
+
console.log(`
|
|
7833
|
+
${c.cyan}[${num}/${total}]${c.reset} ${c.bold}${label}${c.reset}`);
|
|
7834
|
+
}
|
|
7835
|
+
function result(status, detail) {
|
|
7836
|
+
console.log(` ${status} ${detail}`);
|
|
7837
|
+
}
|
|
7804
7838
|
var HOME = process.env.HOME || process.env.USERPROFILE || "~";
|
|
7805
7839
|
var PID_FILE = (0, import_node_path6.join)(HOME, ".closeclaw", "platform.pid");
|
|
7840
|
+
var LICENSE_FILE = (0, import_node_path6.join)(HOME, ".closeclaw", "license.key");
|
|
7841
|
+
var CONFIG_DIR = (0, import_node_path6.join)(HOME, ".closeclaw");
|
|
7842
|
+
var DATA_DIR = (0, import_node_path6.join)(HOME, ".closeclaw", "data");
|
|
7806
7843
|
function readPkgVersion() {
|
|
7807
7844
|
try {
|
|
7808
7845
|
const candidates = [
|
|
7809
7846
|
(0, import_node_path6.join)(__dirname, "..", "package.json"),
|
|
7810
|
-
(0, import_node_path6.join)(__dirname, "package.json")
|
|
7811
|
-
(0, import_node_path6.join)(process.cwd(), "package.json")
|
|
7847
|
+
(0, import_node_path6.join)(__dirname, "package.json")
|
|
7812
7848
|
];
|
|
7813
7849
|
for (const p of candidates) {
|
|
7814
7850
|
if ((0, import_node_fs7.existsSync)(p)) {
|
|
7815
|
-
|
|
7816
|
-
return pkg.version ?? "unknown";
|
|
7851
|
+
return JSON.parse((0, import_node_fs7.readFileSync)(p, "utf-8")).version ?? "unknown";
|
|
7817
7852
|
}
|
|
7818
7853
|
}
|
|
7819
7854
|
return "unknown";
|
|
@@ -7822,16 +7857,12 @@ function readPkgVersion() {
|
|
|
7822
7857
|
}
|
|
7823
7858
|
}
|
|
7824
7859
|
function writePid() {
|
|
7825
|
-
|
|
7826
|
-
if (!(0, import_node_fs7.existsSync)(dir)) {
|
|
7827
|
-
(0, import_node_fs7.mkdirSync)(dir, { recursive: true });
|
|
7828
|
-
}
|
|
7860
|
+
(0, import_node_fs7.mkdirSync)(CONFIG_DIR, { recursive: true });
|
|
7829
7861
|
(0, import_node_fs7.writeFileSync)(PID_FILE, String(process.pid), "utf-8");
|
|
7830
7862
|
}
|
|
7831
7863
|
function readPid() {
|
|
7832
7864
|
try {
|
|
7833
|
-
const
|
|
7834
|
-
const pid = Number(raw);
|
|
7865
|
+
const pid = Number((0, import_node_fs7.readFileSync)(PID_FILE, "utf-8").trim());
|
|
7835
7866
|
return Number.isFinite(pid) ? pid : null;
|
|
7836
7867
|
} catch {
|
|
7837
7868
|
return null;
|
|
@@ -7845,14 +7876,6 @@ function isProcessAlive(pid) {
|
|
|
7845
7876
|
return false;
|
|
7846
7877
|
}
|
|
7847
7878
|
}
|
|
7848
|
-
function checkNodeVersion() {
|
|
7849
|
-
const [major] = process.versions.node.split(".").map(Number);
|
|
7850
|
-
if (major < 22) {
|
|
7851
|
-
console.warn(
|
|
7852
|
-
`\x1B[33m[warn] Node.js >= 22 is required (you have ${process.versions.node}). Things may break.\x1B[0m`
|
|
7853
|
-
);
|
|
7854
|
-
}
|
|
7855
|
-
}
|
|
7856
7879
|
function whichSync(bin) {
|
|
7857
7880
|
try {
|
|
7858
7881
|
return (0, import_node_child_process3.execSync)(`which ${bin}`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim() || null;
|
|
@@ -7860,187 +7883,346 @@ function whichSync(bin) {
|
|
|
7860
7883
|
return null;
|
|
7861
7884
|
}
|
|
7862
7885
|
}
|
|
7886
|
+
function ask(question) {
|
|
7887
|
+
const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
7888
|
+
return new Promise((resolve) => {
|
|
7889
|
+
rl.question(question, (answer) => {
|
|
7890
|
+
rl.close();
|
|
7891
|
+
resolve(answer.trim());
|
|
7892
|
+
});
|
|
7893
|
+
});
|
|
7894
|
+
}
|
|
7863
7895
|
function promptPassword(prompt) {
|
|
7864
7896
|
return new Promise((resolve, reject) => {
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
process.stdout.write(prompt);
|
|
7868
|
-
const raw = process.stdin;
|
|
7869
|
-
raw.setRawMode?.(true);
|
|
7870
|
-
raw.resume();
|
|
7871
|
-
raw.setEncoding("utf-8");
|
|
7872
|
-
let password = "";
|
|
7873
|
-
const onData = (char) => {
|
|
7874
|
-
if (char === "\n" || char === "\r" || char === "") {
|
|
7875
|
-
raw.setRawMode?.(false);
|
|
7876
|
-
raw.pause();
|
|
7877
|
-
raw.removeListener("data", onData);
|
|
7878
|
-
rl.close();
|
|
7879
|
-
process.stdout.write("\n");
|
|
7880
|
-
resolve(password);
|
|
7881
|
-
} else if (char === "") {
|
|
7882
|
-
raw.setRawMode?.(false);
|
|
7883
|
-
rl.close();
|
|
7884
|
-
reject(new Error("Aborted"));
|
|
7885
|
-
} else if (char === "\x7F" || char === "\b") {
|
|
7886
|
-
if (password.length > 0) {
|
|
7887
|
-
password = password.slice(0, -1);
|
|
7888
|
-
process.stdout.write("\b \b");
|
|
7889
|
-
}
|
|
7890
|
-
} else {
|
|
7891
|
-
password += char;
|
|
7892
|
-
process.stdout.write("*");
|
|
7893
|
-
}
|
|
7894
|
-
};
|
|
7895
|
-
raw.on("data", onData);
|
|
7896
|
-
} else {
|
|
7897
|
+
if (!process.stdin.isTTY) {
|
|
7898
|
+
const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
7897
7899
|
rl.question(prompt, (answer) => {
|
|
7898
7900
|
rl.close();
|
|
7899
7901
|
resolve(answer);
|
|
7900
7902
|
});
|
|
7903
|
+
return;
|
|
7901
7904
|
}
|
|
7905
|
+
process.stdout.write(prompt);
|
|
7906
|
+
const raw = process.stdin;
|
|
7907
|
+
raw.setRawMode?.(true);
|
|
7908
|
+
raw.resume();
|
|
7909
|
+
raw.setEncoding("utf-8");
|
|
7910
|
+
let password = "";
|
|
7911
|
+
const onData = (ch) => {
|
|
7912
|
+
if (ch === "\n" || ch === "\r" || ch === "") {
|
|
7913
|
+
raw.setRawMode?.(false);
|
|
7914
|
+
raw.pause();
|
|
7915
|
+
raw.removeListener("data", onData);
|
|
7916
|
+
process.stdout.write("\n");
|
|
7917
|
+
resolve(password);
|
|
7918
|
+
} else if (ch === "") {
|
|
7919
|
+
raw.setRawMode?.(false);
|
|
7920
|
+
reject(new Error("Aborted"));
|
|
7921
|
+
} else if (ch === "\x7F" || ch === "\b") {
|
|
7922
|
+
if (password.length > 0) {
|
|
7923
|
+
password = password.slice(0, -1);
|
|
7924
|
+
process.stdout.write("\b \b");
|
|
7925
|
+
}
|
|
7926
|
+
} else {
|
|
7927
|
+
password += ch;
|
|
7928
|
+
process.stdout.write("*");
|
|
7929
|
+
}
|
|
7930
|
+
};
|
|
7931
|
+
raw.on("data", onData);
|
|
7902
7932
|
});
|
|
7903
7933
|
}
|
|
7934
|
+
function getSavedLicenseKey() {
|
|
7935
|
+
try {
|
|
7936
|
+
return (0, import_node_fs7.readFileSync)(LICENSE_FILE, "utf-8").trim() || null;
|
|
7937
|
+
} catch {
|
|
7938
|
+
return null;
|
|
7939
|
+
}
|
|
7940
|
+
}
|
|
7941
|
+
function saveLicenseKey(key) {
|
|
7942
|
+
(0, import_node_fs7.mkdirSync)(CONFIG_DIR, { recursive: true });
|
|
7943
|
+
(0, import_node_fs7.writeFileSync)(LICENSE_FILE, key, { mode: 384 });
|
|
7944
|
+
}
|
|
7945
|
+
async function commandOnboard() {
|
|
7946
|
+
const version = readPkgVersion();
|
|
7947
|
+
const TOTAL_STEPS = 7;
|
|
7948
|
+
console.log("");
|
|
7949
|
+
console.log(` ${c.cyan}${c.bold}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557${c.reset}`);
|
|
7950
|
+
console.log(` ${c.cyan}${c.bold}\u2551 CloseClaw \u2014 Setup Wizard \u2551${c.reset}`);
|
|
7951
|
+
console.log(` ${c.cyan}${c.bold}\u2551 v${version.padEnd(28)}\u2551${c.reset}`);
|
|
7952
|
+
console.log(` ${c.cyan}${c.bold}\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${c.reset}`);
|
|
7953
|
+
step(1, TOTAL_STEPS, "License Key");
|
|
7954
|
+
let licenseKey = getSavedLicenseKey();
|
|
7955
|
+
if (licenseKey) {
|
|
7956
|
+
result(PASS, `Found saved key at ${c.dim}~/.closeclaw/license.key${c.reset}`);
|
|
7957
|
+
result(INFO, `Key: ${c.dim}${licenseKey.substring(0, 30)}...${c.reset}`);
|
|
7958
|
+
} else {
|
|
7959
|
+
result(INFO, "No license key found.");
|
|
7960
|
+
const key = await ask(` Enter your license key (or 'dev' for development mode): `);
|
|
7961
|
+
if (key === "dev" || key === "development") {
|
|
7962
|
+
process.env.NODE_ENV = "development";
|
|
7963
|
+
result(WARN, "Development mode \u2014 no license required");
|
|
7964
|
+
} else if (key) {
|
|
7965
|
+
licenseKey = key;
|
|
7966
|
+
saveLicenseKey(key);
|
|
7967
|
+
result(PASS, `License key saved to ${c.dim}~/.closeclaw/license.key${c.reset}`);
|
|
7968
|
+
} else {
|
|
7969
|
+
result(FAIL, "No key provided. Get one from your administrator.");
|
|
7970
|
+
process.exit(1);
|
|
7971
|
+
}
|
|
7972
|
+
}
|
|
7973
|
+
if (licenseKey && process.env.NODE_ENV !== "development") {
|
|
7974
|
+
try {
|
|
7975
|
+
const licenseMod = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
7976
|
+
const verify = licenseMod.verifyLicense || licenseMod.default;
|
|
7977
|
+
if (typeof verify === "function") {
|
|
7978
|
+
const payload = verify(licenseKey);
|
|
7979
|
+
if (payload) {
|
|
7980
|
+
result(PASS, `Licensed to: ${c.bold}${payload.sub}${c.reset}`);
|
|
7981
|
+
result(INFO, `Tier: ${payload.tier} | Seats: ${payload.seats} | Expires: ${new Date(payload.exp * 1e3).toLocaleDateString()}`);
|
|
7982
|
+
}
|
|
7983
|
+
}
|
|
7984
|
+
} catch (err) {
|
|
7985
|
+
result(FAIL, `Invalid license key: ${err.message}`);
|
|
7986
|
+
result(INFO, "Enter 'dev' to use development mode, or get a valid key.");
|
|
7987
|
+
process.exit(1);
|
|
7988
|
+
}
|
|
7989
|
+
}
|
|
7990
|
+
step(2, TOTAL_STEPS, "Node.js");
|
|
7991
|
+
const nodeVersion = process.versions.node;
|
|
7992
|
+
const [major] = nodeVersion.split(".").map(Number);
|
|
7993
|
+
if (major >= 22) {
|
|
7994
|
+
result(PASS, `Node.js ${c.bold}v${nodeVersion}${c.reset}`);
|
|
7995
|
+
} else {
|
|
7996
|
+
result(WARN, `Node.js v${nodeVersion} \u2014 ${c.yellow}v22+ recommended${c.reset}`);
|
|
7997
|
+
}
|
|
7998
|
+
step(3, TOTAL_STEPS, "Data Directory");
|
|
7999
|
+
const dataDir = process.env.PLATFORM_DATA_DIR || DATA_DIR;
|
|
8000
|
+
if ((0, import_node_fs7.existsSync)(dataDir)) {
|
|
8001
|
+
const dbExists = (0, import_node_fs7.existsSync)((0, import_node_path6.join)(dataDir, "platform.db"));
|
|
8002
|
+
result(PASS, `${c.dim}${dataDir}${c.reset}`);
|
|
8003
|
+
if (dbExists) {
|
|
8004
|
+
const size = (0, import_node_fs7.statSync)((0, import_node_path6.join)(dataDir, "platform.db")).size;
|
|
8005
|
+
result(INFO, `Database exists (${(size / 1024).toFixed(0)} KB)`);
|
|
8006
|
+
} else {
|
|
8007
|
+
result(INFO, "Fresh install \u2014 database will be created on first start");
|
|
8008
|
+
}
|
|
8009
|
+
} else {
|
|
8010
|
+
(0, import_node_fs7.mkdirSync)(dataDir, { recursive: true });
|
|
8011
|
+
result(PASS, `Created ${c.dim}${dataDir}${c.reset}`);
|
|
8012
|
+
}
|
|
8013
|
+
step(4, TOTAL_STEPS, "Database");
|
|
8014
|
+
try {
|
|
8015
|
+
const { initDatabase: initDatabase2 } = await Promise.resolve().then(() => (init_connection(), connection_exports));
|
|
8016
|
+
initDatabase2(dataDir);
|
|
8017
|
+
result(PASS, "SQLite initialized (WAL mode)");
|
|
8018
|
+
const { runMigrations: runMigrations2 } = await Promise.resolve().then(() => (init_migrate(), migrate_exports));
|
|
8019
|
+
runMigrations2();
|
|
8020
|
+
result(PASS, "Schema up to date");
|
|
8021
|
+
const { countProfiles: countProfiles2 } = await Promise.resolve().then(() => (init_profiles(), profiles_exports));
|
|
8022
|
+
const userCount = countProfiles2();
|
|
8023
|
+
if (userCount === 0) {
|
|
8024
|
+
result(INFO, "No users yet \u2014 first signup will be admin");
|
|
8025
|
+
} else {
|
|
8026
|
+
result(INFO, `${userCount} user${userCount > 1 ? "s" : ""} registered`);
|
|
8027
|
+
}
|
|
8028
|
+
const { closeDatabase: closeDatabase2 } = await Promise.resolve().then(() => (init_connection(), connection_exports));
|
|
8029
|
+
closeDatabase2();
|
|
8030
|
+
} catch (err) {
|
|
8031
|
+
result(FAIL, `Database error: ${err.message}`);
|
|
8032
|
+
process.exit(1);
|
|
8033
|
+
}
|
|
8034
|
+
step(5, TOTAL_STEPS, "Auth");
|
|
8035
|
+
const secretPath = (0, import_node_path6.join)(dataDir, "jwt.secret");
|
|
8036
|
+
if ((0, import_node_fs7.existsSync)(secretPath)) {
|
|
8037
|
+
result(PASS, `JWT secret exists at ${c.dim}${secretPath}${c.reset}`);
|
|
8038
|
+
} else {
|
|
8039
|
+
try {
|
|
8040
|
+
const { generateJwtSecret: generateJwtSecret2 } = await Promise.resolve().then(() => (init_jwt(), jwt_exports));
|
|
8041
|
+
generateJwtSecret2(dataDir);
|
|
8042
|
+
result(PASS, "JWT secret generated");
|
|
8043
|
+
} catch (err) {
|
|
8044
|
+
result(FAIL, `Could not generate JWT secret: ${err.message}`);
|
|
8045
|
+
process.exit(1);
|
|
8046
|
+
}
|
|
8047
|
+
}
|
|
8048
|
+
step(6, TOTAL_STEPS, "OpenClaw");
|
|
8049
|
+
const openclawPath = whichSync("openclaw");
|
|
8050
|
+
if (openclawPath) {
|
|
8051
|
+
result(PASS, `Found at ${c.dim}${openclawPath}${c.reset}`);
|
|
8052
|
+
try {
|
|
8053
|
+
const ver = (0, import_node_child_process3.execSync)(`${openclawPath} --version 2>/dev/null || echo unknown`, { encoding: "utf-8" }).trim();
|
|
8054
|
+
result(INFO, `Version: ${ver}`);
|
|
8055
|
+
} catch {
|
|
8056
|
+
}
|
|
8057
|
+
try {
|
|
8058
|
+
const plugins = (0, import_node_child_process3.execSync)(`${openclawPath} plugins list 2>/dev/null || true`, { encoding: "utf-8" });
|
|
8059
|
+
if (plugins.includes("platform-tools")) {
|
|
8060
|
+
result(PASS, "platform-tools plugin installed");
|
|
8061
|
+
} else {
|
|
8062
|
+
result(WARN, "platform-tools plugin not installed");
|
|
8063
|
+
const pluginDir = (0, import_node_path6.join)(__dirname, "..", "packages", "platform-tools");
|
|
8064
|
+
const altDir = (0, import_node_path6.join)(__dirname, "..", "..", "platform-tools");
|
|
8065
|
+
const resolved = (0, import_node_fs7.existsSync)((0, import_node_path6.join)(pluginDir, "package.json")) ? pluginDir : (0, import_node_fs7.existsSync)((0, import_node_path6.join)(altDir, "package.json")) ? altDir : null;
|
|
8066
|
+
if (resolved) {
|
|
8067
|
+
try {
|
|
8068
|
+
(0, import_node_child_process3.execSync)(`${openclawPath} plugins install -l "${resolved}"`, { stdio: "pipe" });
|
|
8069
|
+
result(PASS, "platform-tools auto-installed");
|
|
8070
|
+
} catch {
|
|
8071
|
+
result(WARN, `Auto-install failed. Run manually: ${c.dim}openclaw plugins install @prajwalshete/platform-tools${c.reset}`);
|
|
8072
|
+
}
|
|
8073
|
+
} else {
|
|
8074
|
+
result(INFO, `Install manually: ${c.dim}openclaw plugins install @prajwalshete/platform-tools${c.reset}`);
|
|
8075
|
+
}
|
|
8076
|
+
}
|
|
8077
|
+
} catch {
|
|
8078
|
+
}
|
|
8079
|
+
try {
|
|
8080
|
+
const net = await import("net");
|
|
8081
|
+
const running = await new Promise((resolve) => {
|
|
8082
|
+
const sock = net.createConnection({ port: 18789, host: "127.0.0.1", timeout: 2e3 });
|
|
8083
|
+
sock.on("connect", () => {
|
|
8084
|
+
sock.destroy();
|
|
8085
|
+
resolve(true);
|
|
8086
|
+
});
|
|
8087
|
+
sock.on("error", () => resolve(false));
|
|
8088
|
+
sock.on("timeout", () => {
|
|
8089
|
+
sock.destroy();
|
|
8090
|
+
resolve(false);
|
|
8091
|
+
});
|
|
8092
|
+
});
|
|
8093
|
+
if (running) {
|
|
8094
|
+
result(PASS, "Gateway running on port 18789");
|
|
8095
|
+
} else {
|
|
8096
|
+
result(WARN, `Gateway not running. Start with: ${c.dim}openclaw gateway${c.reset}`);
|
|
8097
|
+
}
|
|
8098
|
+
} catch {
|
|
8099
|
+
}
|
|
8100
|
+
} else {
|
|
8101
|
+
result(SKIP, "OpenClaw not installed (AI features will be disabled)");
|
|
8102
|
+
result(INFO, `Install: ${c.dim}npm install -g openclaw && openclaw onboard${c.reset}`);
|
|
8103
|
+
}
|
|
8104
|
+
step(7, TOTAL_STEPS, "Ready!");
|
|
8105
|
+
console.log("");
|
|
8106
|
+
console.log(` ${c.green}${c.bold}Setup complete.${c.reset} Start the server with:`);
|
|
8107
|
+
console.log("");
|
|
8108
|
+
if (licenseKey) {
|
|
8109
|
+
console.log(` ${c.cyan}closeclaw start${c.reset}`);
|
|
8110
|
+
} else {
|
|
8111
|
+
console.log(` ${c.cyan}closeclaw start${c.reset} ${c.dim}(dev mode)${c.reset}`);
|
|
8112
|
+
}
|
|
8113
|
+
console.log("");
|
|
8114
|
+
console.log(` Then open ${c.bold}http://localhost:3001${c.reset} in your browser.`);
|
|
8115
|
+
console.log(` First signup becomes admin.`);
|
|
8116
|
+
console.log("");
|
|
8117
|
+
const startNow = await ask(` Start the server now? ${c.dim}(Y/n)${c.reset} `);
|
|
8118
|
+
if (!startNow || startNow.toLowerCase() === "y" || startNow.toLowerCase() === "yes") {
|
|
8119
|
+
console.log("");
|
|
8120
|
+
const startArgs = [];
|
|
8121
|
+
if (licenseKey) startArgs.push("--key", licenseKey);
|
|
8122
|
+
await commandStart(startArgs);
|
|
8123
|
+
}
|
|
8124
|
+
}
|
|
7904
8125
|
async function commandStart(args) {
|
|
7905
8126
|
const { values } = (0, import_node_util2.parseArgs)({
|
|
7906
8127
|
args,
|
|
7907
8128
|
options: {
|
|
7908
8129
|
port: { type: "string", short: "p", default: process.env.PORT || "3001" },
|
|
7909
8130
|
host: { type: "string", short: "h", default: process.env.HOST || "0.0.0.0" },
|
|
7910
|
-
key: { type: "string", short: "k", default:
|
|
7911
|
-
"data-dir": { type: "string", short: "d", default: process.env.PLATFORM_DATA_DIR ||
|
|
8131
|
+
key: { type: "string", short: "k", default: "" },
|
|
8132
|
+
"data-dir": { type: "string", short: "d", default: process.env.PLATFORM_DATA_DIR || DATA_DIR },
|
|
7912
8133
|
domain: { type: "string", default: process.env.PLATFORM_DOMAIN || "" }
|
|
7913
8134
|
},
|
|
7914
8135
|
strict: true
|
|
7915
8136
|
});
|
|
7916
8137
|
const port2 = Number(values.port);
|
|
7917
8138
|
const host2 = values.host;
|
|
7918
|
-
|
|
8139
|
+
let licenseKey = values.key || getSavedLicenseKey() || "";
|
|
7919
8140
|
const dataDir = values["data-dir"];
|
|
7920
8141
|
const domain = values.domain;
|
|
7921
|
-
checkNodeVersion();
|
|
7922
|
-
console.log("[closeclaw] Starting Platform V3...");
|
|
7923
|
-
console.log(`[closeclaw] Data directory: ${dataDir}`);
|
|
7924
8142
|
process.env.PORT = String(port2);
|
|
7925
8143
|
process.env.HOST = host2;
|
|
7926
8144
|
if (licenseKey) process.env.PLATFORM_LICENSE_KEY = licenseKey;
|
|
7927
8145
|
process.env.PLATFORM_DATA_DIR = dataDir;
|
|
7928
8146
|
if (domain) process.env.PLATFORM_DOMAIN = domain;
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
loadConfig2();
|
|
7935
|
-
}
|
|
7936
|
-
} catch {
|
|
8147
|
+
if (!(0, import_node_fs7.existsSync)(dataDir) || !(0, import_node_fs7.existsSync)((0, import_node_path6.join)(dataDir, "jwt.secret"))) {
|
|
8148
|
+
console.log(`
|
|
8149
|
+
${c.yellow}Not onboarded yet.${c.reset} Run ${c.cyan}closeclaw onboard${c.reset} first.
|
|
8150
|
+
`);
|
|
8151
|
+
process.exit(1);
|
|
7937
8152
|
}
|
|
7938
8153
|
const isDev = process.env.NODE_ENV === "development" || process.env.NODE_ENV === "dev";
|
|
7939
|
-
if (!isDev) {
|
|
8154
|
+
if (!isDev && licenseKey) {
|
|
7940
8155
|
try {
|
|
7941
8156
|
const licenseMod = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
7942
8157
|
const verify = licenseMod.verifyLicense || licenseMod.default;
|
|
7943
|
-
if (typeof verify === "function")
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
console.log("[closeclaw] License verified");
|
|
7950
|
-
}
|
|
7951
|
-
} catch {
|
|
8158
|
+
if (typeof verify === "function") verify(licenseKey);
|
|
8159
|
+
} catch (err) {
|
|
8160
|
+
console.error(` ${c.red}License error:${c.reset} ${err.message}`);
|
|
8161
|
+
console.error(` Run ${c.cyan}closeclaw onboard${c.reset} to update your key.
|
|
8162
|
+
`);
|
|
8163
|
+
process.exit(1);
|
|
7952
8164
|
}
|
|
7953
|
-
}
|
|
7954
|
-
|
|
8165
|
+
}
|
|
8166
|
+
try {
|
|
8167
|
+
const configMod = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
8168
|
+
(configMod.loadConfig || configMod.default)?.();
|
|
8169
|
+
} catch {
|
|
7955
8170
|
}
|
|
7956
8171
|
const { initDatabase: initDatabase2 } = await Promise.resolve().then(() => (init_connection(), connection_exports));
|
|
7957
8172
|
initDatabase2(dataDir);
|
|
7958
|
-
console.log("[closeclaw] Database initialized");
|
|
7959
8173
|
const { runMigrations: runMigrations2 } = await Promise.resolve().then(() => (init_migrate(), migrate_exports));
|
|
7960
8174
|
runMigrations2();
|
|
7961
|
-
|
|
8175
|
+
const { generateJwtSecret: generateJwtSecret2 } = await Promise.resolve().then(() => (init_jwt(), jwt_exports));
|
|
8176
|
+
generateJwtSecret2(dataDir);
|
|
7962
8177
|
const openclawPath = whichSync("openclaw");
|
|
7963
8178
|
if (openclawPath) {
|
|
7964
|
-
try {
|
|
7965
|
-
const pluginDir = (0, import_node_path6.join)(__dirname, "..", "packages", "platform-tools");
|
|
7966
|
-
const altPluginDir = (0, import_node_path6.join)(__dirname, "..", "..", "platform-tools");
|
|
7967
|
-
const resolvedPluginDir = (0, import_node_fs7.existsSync)((0, import_node_path6.join)(pluginDir, "package.json")) ? pluginDir : (0, import_node_fs7.existsSync)((0, import_node_path6.join)(altPluginDir, "package.json")) ? altPluginDir : null;
|
|
7968
|
-
if (resolvedPluginDir) {
|
|
7969
|
-
const pluginCheck = (0, import_node_child_process3.execSync)(`${openclawPath} plugins list 2>/dev/null || true`, { encoding: "utf-8" });
|
|
7970
|
-
if (!pluginCheck.includes("platform-tools")) {
|
|
7971
|
-
console.log("[closeclaw] Installing platform-tools plugin into OpenClaw...");
|
|
7972
|
-
(0, import_node_child_process3.execSync)(`${openclawPath} plugins install -l "${resolvedPluginDir}"`, { stdio: "inherit" });
|
|
7973
|
-
console.log("[closeclaw] platform-tools plugin installed");
|
|
7974
|
-
} else {
|
|
7975
|
-
console.log("[closeclaw] platform-tools plugin already installed");
|
|
7976
|
-
}
|
|
7977
|
-
}
|
|
7978
|
-
} catch (err) {
|
|
7979
|
-
console.warn("[closeclaw] Could not auto-install platform-tools plugin:", err.message);
|
|
7980
|
-
}
|
|
7981
8179
|
const { isGatewayRunning: isGatewayRunning2, startGateway: startGateway2, registerShutdownHook: registerShutdownHook2 } = await Promise.resolve().then(() => (init_openclaw_manager(), openclaw_manager_exports));
|
|
7982
8180
|
registerShutdownHook2();
|
|
7983
|
-
const
|
|
7984
|
-
if (!
|
|
7985
|
-
console.log(
|
|
8181
|
+
const running = await isGatewayRunning2();
|
|
8182
|
+
if (!running) {
|
|
8183
|
+
console.log(` ${c.dim}Starting OpenClaw gateway...${c.reset}`);
|
|
7986
8184
|
await startGateway2();
|
|
7987
|
-
} else {
|
|
7988
|
-
console.log("[closeclaw] OpenClaw gateway already running");
|
|
7989
8185
|
}
|
|
7990
|
-
} else {
|
|
7991
|
-
console.log("[closeclaw] OpenClaw not found in PATH -- skipping gateway");
|
|
7992
8186
|
}
|
|
7993
8187
|
await Promise.resolve().then(() => (init_index(), index_exports));
|
|
7994
8188
|
writePid();
|
|
7995
8189
|
const version = readPkgVersion();
|
|
7996
8190
|
const localUrl = `http://localhost:${port2}`;
|
|
7997
|
-
const networkUrl = host2 === "0.0.0.0" ? `http://<host-ip>:${port2}` : `http://${host2}:${port2}`;
|
|
7998
|
-
const publicUrl = domain ? `https://${domain}` : null;
|
|
7999
8191
|
console.log("");
|
|
8000
|
-
console.log(
|
|
8001
|
-
console.log(` \
|
|
8002
|
-
console.log(
|
|
8003
|
-
console.log(` \
|
|
8004
|
-
console.log(` \
|
|
8005
|
-
if (publicUrl) {
|
|
8006
|
-
console.log(` \u2502 Public: ${publicUrl.padEnd(34)}\u2502`);
|
|
8007
|
-
}
|
|
8008
|
-
console.log(` \u2502 PID: ${String(process.pid).padEnd(34)}\u2502`);
|
|
8192
|
+
console.log(` ${c.green}${c.bold}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557${c.reset}`);
|
|
8193
|
+
console.log(` ${c.green}${c.bold}\u2551 CloseClaw v${version.padEnd(31)}\u2551${c.reset}`);
|
|
8194
|
+
console.log(` ${c.green}${c.bold}\u2551 \u2551${c.reset}`);
|
|
8195
|
+
console.log(` ${c.green}${c.bold}\u2551${c.reset} ${c.cyan}${localUrl.padEnd(40)}${c.reset}${c.green}${c.bold}\u2551${c.reset}`);
|
|
8196
|
+
console.log(` ${c.green}${c.bold}\u2551${c.reset} ${c.dim}PID: ${String(process.pid).padEnd(36)}${c.reset}${c.green}${c.bold}\u2551${c.reset}`);
|
|
8009
8197
|
if (openclawPath) {
|
|
8010
|
-
console.log(` \
|
|
8198
|
+
console.log(` ${c.green}${c.bold}\u2551${c.reset} ${c.dim}OpenClaw: port 18789${" ".repeat(21)}${c.reset}${c.green}${c.bold}\u2551${c.reset}`);
|
|
8011
8199
|
}
|
|
8012
|
-
console.log(
|
|
8013
|
-
console.log(
|
|
8200
|
+
console.log(` ${c.green}${c.bold}\u2551 \u2551${c.reset}`);
|
|
8201
|
+
console.log(` ${c.green}${c.bold}\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${c.reset}`);
|
|
8014
8202
|
console.log("");
|
|
8015
8203
|
}
|
|
8016
8204
|
async function commandStop() {
|
|
8017
8205
|
const pid = readPid();
|
|
8018
8206
|
if (!pid) {
|
|
8019
|
-
console.log("
|
|
8207
|
+
console.log(" Server is not running.");
|
|
8020
8208
|
process.exit(1);
|
|
8021
8209
|
}
|
|
8022
8210
|
if (!isProcessAlive(pid)) {
|
|
8023
|
-
console.log(`
|
|
8211
|
+
console.log(` Process ${pid} is not running. Cleaning up.`);
|
|
8024
8212
|
try {
|
|
8025
8213
|
(0, import_node_fs7.unlinkSync)(PID_FILE);
|
|
8026
8214
|
} catch {
|
|
8027
8215
|
}
|
|
8028
8216
|
process.exit(0);
|
|
8029
8217
|
}
|
|
8030
|
-
console.log(`
|
|
8031
|
-
|
|
8032
|
-
process.kill(pid, "SIGTERM");
|
|
8033
|
-
} catch (err) {
|
|
8034
|
-
console.error(`[closeclaw] Failed to stop process: ${err.message}`);
|
|
8035
|
-
process.exit(1);
|
|
8036
|
-
}
|
|
8218
|
+
console.log(` Stopping server (PID ${pid})...`);
|
|
8219
|
+
process.kill(pid, "SIGTERM");
|
|
8037
8220
|
const deadline = Date.now() + 1e4;
|
|
8038
8221
|
while (Date.now() < deadline) {
|
|
8039
8222
|
if (!isProcessAlive(pid)) break;
|
|
8040
8223
|
await new Promise((r) => setTimeout(r, 250));
|
|
8041
8224
|
}
|
|
8042
8225
|
if (isProcessAlive(pid)) {
|
|
8043
|
-
console.warn("[closeclaw] Process did not exit in time, sending SIGKILL...");
|
|
8044
8226
|
try {
|
|
8045
8227
|
process.kill(pid, "SIGKILL");
|
|
8046
8228
|
} catch {
|
|
@@ -8050,14 +8232,13 @@ async function commandStop() {
|
|
|
8050
8232
|
(0, import_node_fs7.unlinkSync)(PID_FILE);
|
|
8051
8233
|
} catch {
|
|
8052
8234
|
}
|
|
8053
|
-
console.log("
|
|
8235
|
+
console.log(" Server stopped.");
|
|
8054
8236
|
}
|
|
8055
8237
|
async function commandStatus() {
|
|
8056
8238
|
const pid = readPid();
|
|
8057
8239
|
if (!pid || !isProcessAlive(pid)) {
|
|
8058
|
-
console.log(
|
|
8240
|
+
console.log(` ${c.red}Status: stopped${c.reset}`);
|
|
8059
8241
|
if (pid) {
|
|
8060
|
-
console.log(`[closeclaw] Stale PID file references process ${pid}`);
|
|
8061
8242
|
try {
|
|
8062
8243
|
(0, import_node_fs7.unlinkSync)(PID_FILE);
|
|
8063
8244
|
} catch {
|
|
@@ -8072,160 +8253,138 @@ async function commandStatus() {
|
|
|
8072
8253
|
healthy = res.ok;
|
|
8073
8254
|
} catch {
|
|
8074
8255
|
}
|
|
8075
|
-
console.log(`
|
|
8076
|
-
console.log(`
|
|
8077
|
-
console.log(`
|
|
8078
|
-
console.log(`
|
|
8256
|
+
console.log(` ${c.green}Status: running${c.reset}`);
|
|
8257
|
+
console.log(` PID: ${pid}`);
|
|
8258
|
+
console.log(` Port: ${port2}`);
|
|
8259
|
+
console.log(` Health: ${healthy ? c.green + "ok" + c.reset : c.red + "unreachable" + c.reset}`);
|
|
8079
8260
|
try {
|
|
8080
8261
|
const stat = (0, import_node_fs7.statSync)(PID_FILE);
|
|
8081
|
-
const
|
|
8082
|
-
|
|
8083
|
-
const hours = Math.floor(uptimeSec / 3600);
|
|
8084
|
-
const minutes = Math.floor(uptimeSec % 3600 / 60);
|
|
8085
|
-
const seconds = uptimeSec % 60;
|
|
8086
|
-
console.log(`[closeclaw] Uptime: ${hours}h ${minutes}m ${seconds}s`);
|
|
8262
|
+
const sec = Math.floor((Date.now() - stat.mtimeMs) / 1e3);
|
|
8263
|
+
console.log(` Uptime: ${Math.floor(sec / 3600)}h ${Math.floor(sec % 3600 / 60)}m ${sec % 60}s`);
|
|
8087
8264
|
} catch {
|
|
8088
8265
|
}
|
|
8089
8266
|
}
|
|
8090
8267
|
async function commandResetPassword(args) {
|
|
8091
|
-
const { values } = (0, import_node_util2.parseArgs)({
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
email: { type: "string", short: "e" }
|
|
8095
|
-
},
|
|
8096
|
-
strict: true
|
|
8097
|
-
});
|
|
8098
|
-
const email = values.email;
|
|
8099
|
-
if (!email) {
|
|
8100
|
-
console.error("[closeclaw] --email is required");
|
|
8101
|
-
console.error("Usage: platform reset-password --email user@example.com");
|
|
8268
|
+
const { values } = (0, import_node_util2.parseArgs)({ args, options: { email: { type: "string", short: "e" } }, strict: true });
|
|
8269
|
+
if (!values.email) {
|
|
8270
|
+
console.error(" --email required");
|
|
8102
8271
|
process.exit(1);
|
|
8103
8272
|
}
|
|
8104
|
-
const dataDir = process.env.PLATFORM_DATA_DIR || (0, import_node_path6.join)(HOME, ".closeclaw", "data");
|
|
8105
8273
|
const { initDatabase: initDatabase2 } = await Promise.resolve().then(() => (init_connection(), connection_exports));
|
|
8106
|
-
initDatabase2(
|
|
8274
|
+
initDatabase2(DATA_DIR);
|
|
8107
8275
|
const { runMigrations: runMigrations2 } = await Promise.resolve().then(() => (init_migrate(), migrate_exports));
|
|
8108
8276
|
runMigrations2();
|
|
8109
8277
|
const { getProfileByEmail: getProfileByEmail2, updateProfile: updateProfile2 } = await Promise.resolve().then(() => (init_profiles(), profiles_exports));
|
|
8110
|
-
const profile = getProfileByEmail2(email);
|
|
8278
|
+
const profile = getProfileByEmail2(values.email);
|
|
8111
8279
|
if (!profile) {
|
|
8112
|
-
console.error(`
|
|
8280
|
+
console.error(` No user: ${values.email}`);
|
|
8113
8281
|
process.exit(1);
|
|
8114
8282
|
}
|
|
8115
|
-
const password = await promptPassword("New password: ");
|
|
8283
|
+
const password = await promptPassword(" New password: ");
|
|
8116
8284
|
if (!password || password.length < 8) {
|
|
8117
|
-
console.error("
|
|
8285
|
+
console.error(" Min 8 characters.");
|
|
8118
8286
|
process.exit(1);
|
|
8119
8287
|
}
|
|
8120
|
-
const confirm = await promptPassword("Confirm
|
|
8288
|
+
const confirm = await promptPassword(" Confirm: ");
|
|
8121
8289
|
if (password !== confirm) {
|
|
8122
|
-
console.error("
|
|
8290
|
+
console.error(" Passwords don't match.");
|
|
8123
8291
|
process.exit(1);
|
|
8124
8292
|
}
|
|
8125
8293
|
const { hashPassword: hashPassword2 } = await Promise.resolve().then(() => (init_passwords(), passwords_exports));
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
console.log(`[closeclaw] Password updated for ${email}`);
|
|
8294
|
+
updateProfile2(profile.id, { password_hash: await hashPassword2(password) });
|
|
8295
|
+
console.log(` Password updated for ${values.email}`);
|
|
8129
8296
|
const { closeDatabase: closeDatabase2 } = await Promise.resolve().then(() => (init_connection(), connection_exports));
|
|
8130
8297
|
closeDatabase2();
|
|
8131
8298
|
}
|
|
8132
8299
|
async function commandExport(args) {
|
|
8133
|
-
const { values } = (0, import_node_util2.parseArgs)({
|
|
8134
|
-
|
|
8135
|
-
options: {
|
|
8136
|
-
output: { type: "string", short: "o", default: "platform-backup.db" }
|
|
8137
|
-
},
|
|
8138
|
-
strict: true
|
|
8139
|
-
});
|
|
8140
|
-
const output = values.output;
|
|
8141
|
-
const dataDir = process.env.PLATFORM_DATA_DIR || (0, import_node_path6.join)(HOME, ".closeclaw", "data");
|
|
8142
|
-
const dbPath = (0, import_node_path6.join)(dataDir, "platform.db");
|
|
8300
|
+
const { values } = (0, import_node_util2.parseArgs)({ args, options: { output: { type: "string", short: "o", default: "closeclaw-backup.db" } }, strict: true });
|
|
8301
|
+
const dbPath = (0, import_node_path6.join)(DATA_DIR, "platform.db");
|
|
8143
8302
|
if (!(0, import_node_fs7.existsSync)(dbPath)) {
|
|
8144
|
-
console.error(`
|
|
8303
|
+
console.error(` Database not found at ${dbPath}`);
|
|
8145
8304
|
process.exit(1);
|
|
8146
8305
|
}
|
|
8147
8306
|
const Database2 = (await import("better-sqlite3")).default;
|
|
8148
8307
|
const db = new Database2(dbPath, { readonly: true });
|
|
8149
8308
|
try {
|
|
8150
|
-
await db.backup(output);
|
|
8151
|
-
console.log(`
|
|
8152
|
-
} catch (err) {
|
|
8153
|
-
console.error(`[closeclaw] Export failed: ${err.message}`);
|
|
8154
|
-
process.exit(1);
|
|
8309
|
+
await db.backup(values.output);
|
|
8310
|
+
console.log(` Exported to ${values.output}`);
|
|
8155
8311
|
} finally {
|
|
8156
8312
|
db.close();
|
|
8157
8313
|
}
|
|
8158
8314
|
}
|
|
8159
|
-
function commandVersion() {
|
|
8160
|
-
const version = readPkgVersion();
|
|
8161
|
-
console.log(`platform v${version}`);
|
|
8162
|
-
}
|
|
8163
8315
|
function printUsage() {
|
|
8164
8316
|
console.log(`
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
Usage:
|
|
8168
|
-
platform <command> [options]
|
|
8169
|
-
|
|
8170
|
-
Commands:
|
|
8171
|
-
start Start the platform server
|
|
8172
|
-
--port, -p HTTP port (default: 3001)
|
|
8173
|
-
--host, -h Bind host (default: 0.0.0.0)
|
|
8174
|
-
--key, -k License key
|
|
8175
|
-
--data-dir, -d Data directory (default: ~/.closeclaw/data)
|
|
8176
|
-
--domain Public domain name
|
|
8317
|
+
${c.bold}closeclaw${c.reset} \u2014 AI-powered project management platform
|
|
8177
8318
|
|
|
8178
|
-
|
|
8319
|
+
${c.bold}Usage:${c.reset}
|
|
8320
|
+
closeclaw ${c.cyan}<command>${c.reset} [options]
|
|
8179
8321
|
|
|
8180
|
-
|
|
8322
|
+
${c.bold}Commands:${c.reset}
|
|
8323
|
+
${c.cyan}onboard${c.reset} Interactive setup wizard (run this first)
|
|
8324
|
+
${c.cyan}start${c.reset} Start the server
|
|
8325
|
+
--port, -p Port (default: 3001)
|
|
8326
|
+
--key, -k License key (or saved from onboard)
|
|
8327
|
+
--domain Public domain for auto-SSL
|
|
8328
|
+
${c.cyan}stop${c.reset} Stop the server
|
|
8329
|
+
${c.cyan}status${c.reset} Show server status
|
|
8330
|
+
${c.cyan}reset-password${c.reset} Reset a user's password
|
|
8331
|
+
--email, -e User email
|
|
8332
|
+
${c.cyan}export${c.reset} Backup the database
|
|
8333
|
+
--output, -o Output file
|
|
8334
|
+
${c.cyan}version${c.reset} Print version
|
|
8181
8335
|
|
|
8182
|
-
reset
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
export Export database backup
|
|
8186
|
-
--output, -o Output file path (default: platform-backup.db)
|
|
8187
|
-
|
|
8188
|
-
version Print version
|
|
8189
|
-
help Show this help
|
|
8336
|
+
${c.bold}Getting started:${c.reset}
|
|
8337
|
+
${c.dim}$ closeclaw onboard${c.reset}
|
|
8190
8338
|
`);
|
|
8191
8339
|
}
|
|
8192
8340
|
async function main() {
|
|
8193
8341
|
const args = process.argv.slice(2);
|
|
8194
8342
|
const command = args[0];
|
|
8195
|
-
const
|
|
8343
|
+
const rest = args.slice(1);
|
|
8196
8344
|
switch (command) {
|
|
8345
|
+
case "onboard":
|
|
8346
|
+
return commandOnboard();
|
|
8197
8347
|
case "start":
|
|
8198
|
-
|
|
8199
|
-
break;
|
|
8348
|
+
return commandStart(rest);
|
|
8200
8349
|
case "stop":
|
|
8201
|
-
|
|
8202
|
-
break;
|
|
8350
|
+
return commandStop();
|
|
8203
8351
|
case "status":
|
|
8204
|
-
|
|
8205
|
-
break;
|
|
8352
|
+
return commandStatus();
|
|
8206
8353
|
case "reset-password":
|
|
8207
|
-
|
|
8208
|
-
break;
|
|
8354
|
+
return commandResetPassword(rest);
|
|
8209
8355
|
case "export":
|
|
8210
|
-
|
|
8211
|
-
break;
|
|
8356
|
+
return commandExport(rest);
|
|
8212
8357
|
case "version":
|
|
8213
8358
|
case "--version":
|
|
8214
8359
|
case "-v":
|
|
8215
|
-
|
|
8360
|
+
console.log(` closeclaw v${readPkgVersion()}`);
|
|
8216
8361
|
break;
|
|
8217
8362
|
case "help":
|
|
8218
8363
|
case "--help":
|
|
8219
|
-
case void 0:
|
|
8220
8364
|
printUsage();
|
|
8221
8365
|
break;
|
|
8366
|
+
case void 0:
|
|
8367
|
+
console.log(`
|
|
8368
|
+
${c.bold}CloseClaw${c.reset} v${readPkgVersion()}
|
|
8369
|
+
`);
|
|
8370
|
+
if (!(0, import_node_fs7.existsSync)((0, import_node_path6.join)(DATA_DIR, "jwt.secret"))) {
|
|
8371
|
+
console.log(` ${c.yellow}First time?${c.reset} Run ${c.cyan}closeclaw onboard${c.reset} to get started.
|
|
8372
|
+
`);
|
|
8373
|
+
} else {
|
|
8374
|
+
console.log(` Run ${c.cyan}closeclaw start${c.reset} to launch the server.
|
|
8375
|
+
`);
|
|
8376
|
+
printUsage();
|
|
8377
|
+
}
|
|
8378
|
+
break;
|
|
8222
8379
|
default:
|
|
8223
|
-
console.error(`Unknown command: ${command}`);
|
|
8380
|
+
console.error(` Unknown command: ${command}`);
|
|
8224
8381
|
printUsage();
|
|
8225
8382
|
process.exit(1);
|
|
8226
8383
|
}
|
|
8227
8384
|
}
|
|
8228
8385
|
main().catch((err) => {
|
|
8229
|
-
console.error(
|
|
8386
|
+
console.error(`
|
|
8387
|
+
${c.red}Fatal:${c.reset} ${err.message}
|
|
8388
|
+
`);
|
|
8230
8389
|
process.exit(1);
|
|
8231
8390
|
});
|