closeclaw 3.0.3 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +910 -742
- 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;
|
|
@@ -539,10 +417,20 @@ function verifyLicense(key) {
|
|
|
539
417
|
throw new Error("License key is required (set NODE_ENV=development to bypass)");
|
|
540
418
|
}
|
|
541
419
|
try {
|
|
420
|
+
const parts = key.split(".");
|
|
421
|
+
if (parts.length !== 3) throw new Error("Invalid JWT format");
|
|
422
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
423
|
+
const header = JSON.parse(Buffer.from(headerB64, "base64url").toString());
|
|
424
|
+
if (header.alg !== "EdDSA") throw new Error(`Unsupported algorithm: ${header.alg}`);
|
|
542
425
|
const publicKey = (0, import_node_crypto2.createPublicKey)(ED25519_PUBLIC_KEY_PEM);
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
426
|
+
const signingInput = `${headerB64}.${payloadB64}`;
|
|
427
|
+
const signature = Buffer.from(signatureB64, "base64url");
|
|
428
|
+
const valid = (0, import_node_crypto2.verify)(null, Buffer.from(signingInput), publicKey, signature);
|
|
429
|
+
if (!valid) throw new Error("Invalid signature");
|
|
430
|
+
const decoded = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
431
|
+
if (typeof decoded.exp === "number" && decoded.exp < Math.floor(Date.now() / 1e3)) {
|
|
432
|
+
throw new Error("License expired");
|
|
433
|
+
}
|
|
546
434
|
if (!decoded.sub || typeof decoded.sub !== "string") {
|
|
547
435
|
throw new Error("License missing 'sub' claim");
|
|
548
436
|
}
|
|
@@ -570,10 +458,10 @@ function verifyLicense(key) {
|
|
|
570
458
|
_devMode = false;
|
|
571
459
|
return _license;
|
|
572
460
|
} catch (err) {
|
|
573
|
-
if (err instanceof
|
|
461
|
+
if (err instanceof jwt.TokenExpiredError) {
|
|
574
462
|
throw new Error("License key has expired");
|
|
575
463
|
}
|
|
576
|
-
if (err instanceof
|
|
464
|
+
if (err instanceof jwt.JsonWebTokenError) {
|
|
577
465
|
throw new Error(`Invalid license key: ${err.message}`);
|
|
578
466
|
}
|
|
579
467
|
throw err;
|
|
@@ -589,8 +477,8 @@ function checkSeatLimit() {
|
|
|
589
477
|
if (!_license) return false;
|
|
590
478
|
if (_devMode) return true;
|
|
591
479
|
const db = getSqlite();
|
|
592
|
-
const
|
|
593
|
-
return
|
|
480
|
+
const result2 = db.prepare("SELECT COUNT(*) as count FROM profiles").get();
|
|
481
|
+
return result2.count <= _license.seats;
|
|
594
482
|
}
|
|
595
483
|
function getLicense() {
|
|
596
484
|
return _license;
|
|
@@ -602,11 +490,10 @@ function _resetLicense() {
|
|
|
602
490
|
_license = null;
|
|
603
491
|
_devMode = false;
|
|
604
492
|
}
|
|
605
|
-
var
|
|
493
|
+
var import_node_crypto2, ED25519_PUBLIC_KEY_PEM, _license, _devMode, DEV_LICENSE;
|
|
606
494
|
var init_license = __esm({
|
|
607
495
|
"src/license.ts"() {
|
|
608
496
|
"use strict";
|
|
609
|
-
import_jsonwebtoken = __toESM(require("jsonwebtoken"), 1);
|
|
610
497
|
import_node_crypto2 = require("crypto");
|
|
611
498
|
init_connection();
|
|
612
499
|
ED25519_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
@@ -965,6 +852,273 @@ var init_migrate = __esm({
|
|
|
965
852
|
}
|
|
966
853
|
});
|
|
967
854
|
|
|
855
|
+
// src/db/queries/profiles.ts
|
|
856
|
+
var profiles_exports = {};
|
|
857
|
+
__export(profiles_exports, {
|
|
858
|
+
countProfiles: () => countProfiles,
|
|
859
|
+
createProfile: () => createProfile,
|
|
860
|
+
getProfile: () => getProfile,
|
|
861
|
+
getProfileByEmail: () => getProfileByEmail,
|
|
862
|
+
getProfileById: () => getProfileById,
|
|
863
|
+
isSystemAdmin: () => isSystemAdmin,
|
|
864
|
+
updateProfile: () => updateProfile
|
|
865
|
+
});
|
|
866
|
+
function getProfile(userId) {
|
|
867
|
+
const db = getSqlite();
|
|
868
|
+
const profile = db.prepare("SELECT * FROM profiles WHERE id = ?").get(userId);
|
|
869
|
+
if (!profile) return null;
|
|
870
|
+
const orgMemberships = db.prepare("SELECT org_id, role FROM org_members WHERE user_id = ?").all(userId);
|
|
871
|
+
const projectMemberships = db.prepare("SELECT project_id, role FROM project_members WHERE user_id = ?").all(userId);
|
|
872
|
+
const orgRoles = {};
|
|
873
|
+
for (const m of orgMemberships) {
|
|
874
|
+
orgRoles[m.org_id] = m.role;
|
|
875
|
+
}
|
|
876
|
+
const projectRoles = {};
|
|
877
|
+
for (const m of projectMemberships) {
|
|
878
|
+
projectRoles[m.project_id] = m.role;
|
|
879
|
+
}
|
|
880
|
+
return {
|
|
881
|
+
...profile,
|
|
882
|
+
isSystemAdmin: profile.is_system_admin ?? false,
|
|
883
|
+
orgRoles,
|
|
884
|
+
projectRoles
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
function getProfileById(userId) {
|
|
888
|
+
const db = getSqlite();
|
|
889
|
+
return db.prepare("SELECT * FROM profiles WHERE id = ?").get(userId);
|
|
890
|
+
}
|
|
891
|
+
function getProfileByEmail(email) {
|
|
892
|
+
const db = getSqlite();
|
|
893
|
+
return db.prepare("SELECT * FROM profiles WHERE email = ?").get(email);
|
|
894
|
+
}
|
|
895
|
+
function updateProfile(userId, data) {
|
|
896
|
+
const db = getSqlite();
|
|
897
|
+
const fields = { ...data, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
898
|
+
const keys = Object.keys(fields);
|
|
899
|
+
const setClause = keys.map((k) => `${k} = ?`).join(", ");
|
|
900
|
+
const values = keys.map((k) => fields[k]);
|
|
901
|
+
db.prepare(`UPDATE profiles SET ${setClause} WHERE id = ?`).run(...values, userId);
|
|
902
|
+
return db.prepare("SELECT * FROM profiles WHERE id = ?").get(userId);
|
|
903
|
+
}
|
|
904
|
+
function createProfile(data) {
|
|
905
|
+
const db = getSqlite();
|
|
906
|
+
const id = (0, import_node_crypto3.randomUUID)();
|
|
907
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
908
|
+
db.prepare(
|
|
909
|
+
`INSERT INTO profiles (id, email, password_hash, full_name, display_name, created_at, updated_at)
|
|
910
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
911
|
+
).run(
|
|
912
|
+
id,
|
|
913
|
+
data.email,
|
|
914
|
+
data.password_hash,
|
|
915
|
+
data.full_name || null,
|
|
916
|
+
data.display_name || null,
|
|
917
|
+
now,
|
|
918
|
+
now
|
|
919
|
+
);
|
|
920
|
+
return db.prepare("SELECT * FROM profiles WHERE id = ?").get(id);
|
|
921
|
+
}
|
|
922
|
+
function countProfiles() {
|
|
923
|
+
const db = getSqlite();
|
|
924
|
+
const row = db.prepare("SELECT COUNT(*) as count FROM profiles").get();
|
|
925
|
+
return row.count;
|
|
926
|
+
}
|
|
927
|
+
function isSystemAdmin(userId) {
|
|
928
|
+
const db = getSqlite();
|
|
929
|
+
const row = db.prepare("SELECT is_system_admin FROM profiles WHERE id = ?").get(userId);
|
|
930
|
+
return row?.is_system_admin === 1;
|
|
931
|
+
}
|
|
932
|
+
var import_node_crypto3;
|
|
933
|
+
var init_profiles = __esm({
|
|
934
|
+
"src/db/queries/profiles.ts"() {
|
|
935
|
+
"use strict";
|
|
936
|
+
init_connection();
|
|
937
|
+
import_node_crypto3 = require("crypto");
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
// src/auth/jwt.ts
|
|
942
|
+
var jwt_exports = {};
|
|
943
|
+
__export(jwt_exports, {
|
|
944
|
+
_resetSecret: () => _resetSecret,
|
|
945
|
+
generateJwtSecret: () => generateJwtSecret,
|
|
946
|
+
generateRefreshToken: () => generateRefreshToken,
|
|
947
|
+
signAccessToken: () => signAccessToken,
|
|
948
|
+
verifyAccessToken: () => verifyAccessToken
|
|
949
|
+
});
|
|
950
|
+
function generateJwtSecret(dataDir) {
|
|
951
|
+
if (_secret) return _secret;
|
|
952
|
+
const secretPath = (0, import_node_path2.join)(dataDir, "jwt.secret");
|
|
953
|
+
if ((0, import_node_fs2.existsSync)(secretPath)) {
|
|
954
|
+
_secret = (0, import_node_fs2.readFileSync)(secretPath, "utf-8").trim();
|
|
955
|
+
return _secret;
|
|
956
|
+
}
|
|
957
|
+
const dir = (0, import_node_path2.dirname)(secretPath);
|
|
958
|
+
if (!(0, import_node_fs2.existsSync)(dir)) {
|
|
959
|
+
(0, import_node_fs2.mkdirSync)(dir, { recursive: true });
|
|
960
|
+
}
|
|
961
|
+
_secret = (0, import_node_crypto4.randomBytes)(32).toString("hex");
|
|
962
|
+
(0, import_node_fs2.writeFileSync)(secretPath, _secret, { mode: 384 });
|
|
963
|
+
return _secret;
|
|
964
|
+
}
|
|
965
|
+
function getSecret() {
|
|
966
|
+
if (!_secret) {
|
|
967
|
+
throw new Error("JWT secret not initialized. Call generateJwtSecret(dataDir) first.");
|
|
968
|
+
}
|
|
969
|
+
return _secret;
|
|
970
|
+
}
|
|
971
|
+
function signAccessToken(payload) {
|
|
972
|
+
return import_jsonwebtoken.default.sign(payload, getSecret(), {
|
|
973
|
+
algorithm: "HS256",
|
|
974
|
+
expiresIn: "15m"
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
function verifyAccessToken(token) {
|
|
978
|
+
return import_jsonwebtoken.default.verify(token, getSecret(), {
|
|
979
|
+
algorithms: ["HS256"]
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
function generateRefreshToken() {
|
|
983
|
+
return (0, import_node_crypto4.randomBytes)(64).toString("hex");
|
|
984
|
+
}
|
|
985
|
+
function _resetSecret() {
|
|
986
|
+
_secret = null;
|
|
987
|
+
}
|
|
988
|
+
var import_jsonwebtoken, import_node_crypto4, import_node_fs2, import_node_path2, _secret;
|
|
989
|
+
var init_jwt = __esm({
|
|
990
|
+
"src/auth/jwt.ts"() {
|
|
991
|
+
"use strict";
|
|
992
|
+
import_jsonwebtoken = __toESM(require("jsonwebtoken"), 1);
|
|
993
|
+
import_node_crypto4 = require("crypto");
|
|
994
|
+
import_node_fs2 = require("fs");
|
|
995
|
+
import_node_path2 = require("path");
|
|
996
|
+
_secret = null;
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// src/config.ts
|
|
1001
|
+
var config_exports = {};
|
|
1002
|
+
__export(config_exports, {
|
|
1003
|
+
getConfig: () => getConfig,
|
|
1004
|
+
loadConfig: () => loadConfig
|
|
1005
|
+
});
|
|
1006
|
+
function getDefaults() {
|
|
1007
|
+
return {
|
|
1008
|
+
port: 4800,
|
|
1009
|
+
host: "0.0.0.0",
|
|
1010
|
+
dataDir: DEFAULT_DATA_DIR,
|
|
1011
|
+
logLevel: "info",
|
|
1012
|
+
github: {
|
|
1013
|
+
appId: "",
|
|
1014
|
+
privateKeyPath: "",
|
|
1015
|
+
webhookSecret: "",
|
|
1016
|
+
installationId: ""
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
function readConfigFile() {
|
|
1021
|
+
if (!(0, import_node_fs3.existsSync)(CONFIG_PATH)) {
|
|
1022
|
+
const dir = (0, import_node_path3.dirname)(CONFIG_PATH);
|
|
1023
|
+
if (!(0, import_node_fs3.existsSync)(dir)) {
|
|
1024
|
+
(0, import_node_fs3.mkdirSync)(dir, { recursive: true });
|
|
1025
|
+
}
|
|
1026
|
+
(0, import_node_fs3.writeFileSync)(CONFIG_PATH, JSON.stringify(getDefaults(), null, 2), "utf-8");
|
|
1027
|
+
return {};
|
|
1028
|
+
}
|
|
1029
|
+
try {
|
|
1030
|
+
const raw = (0, import_node_fs3.readFileSync)(CONFIG_PATH, "utf-8");
|
|
1031
|
+
return JSON.parse(raw);
|
|
1032
|
+
} catch {
|
|
1033
|
+
return {};
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
function readEnvVars() {
|
|
1037
|
+
const env = {};
|
|
1038
|
+
if (process.env.PLATFORM_PORT) {
|
|
1039
|
+
const port2 = parseInt(process.env.PLATFORM_PORT, 10);
|
|
1040
|
+
if (!isNaN(port2)) env.port = port2;
|
|
1041
|
+
}
|
|
1042
|
+
if (process.env.PLATFORM_HOST) {
|
|
1043
|
+
env.host = process.env.PLATFORM_HOST;
|
|
1044
|
+
}
|
|
1045
|
+
if (process.env.PLATFORM_DATA_DIR) {
|
|
1046
|
+
env.dataDir = process.env.PLATFORM_DATA_DIR;
|
|
1047
|
+
}
|
|
1048
|
+
if (process.env.PLATFORM_LOG_LEVEL) {
|
|
1049
|
+
const level = process.env.PLATFORM_LOG_LEVEL;
|
|
1050
|
+
if (["debug", "info", "warn", "error"].includes(level)) {
|
|
1051
|
+
env.logLevel = level;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
const gh = {};
|
|
1055
|
+
let hasGithub = false;
|
|
1056
|
+
if (process.env.GITHUB_APP_ID) {
|
|
1057
|
+
gh.appId = process.env.GITHUB_APP_ID;
|
|
1058
|
+
hasGithub = true;
|
|
1059
|
+
}
|
|
1060
|
+
if (process.env.GITHUB_PRIVATE_KEY_PATH) {
|
|
1061
|
+
gh.privateKeyPath = process.env.GITHUB_PRIVATE_KEY_PATH;
|
|
1062
|
+
hasGithub = true;
|
|
1063
|
+
}
|
|
1064
|
+
if (process.env.GITHUB_WEBHOOK_SECRET) {
|
|
1065
|
+
gh.webhookSecret = process.env.GITHUB_WEBHOOK_SECRET;
|
|
1066
|
+
hasGithub = true;
|
|
1067
|
+
}
|
|
1068
|
+
if (process.env.GITHUB_INSTALLATION_ID) {
|
|
1069
|
+
gh.installationId = process.env.GITHUB_INSTALLATION_ID;
|
|
1070
|
+
hasGithub = true;
|
|
1071
|
+
}
|
|
1072
|
+
if (hasGithub) {
|
|
1073
|
+
env.github = gh;
|
|
1074
|
+
}
|
|
1075
|
+
return env;
|
|
1076
|
+
}
|
|
1077
|
+
function deepMerge(target, ...sources) {
|
|
1078
|
+
const result2 = { ...target };
|
|
1079
|
+
for (const source of sources) {
|
|
1080
|
+
if (source.port !== void 0) result2.port = source.port;
|
|
1081
|
+
if (source.host !== void 0) result2.host = source.host;
|
|
1082
|
+
if (source.dataDir !== void 0) result2.dataDir = source.dataDir;
|
|
1083
|
+
if (source.logLevel !== void 0) result2.logLevel = source.logLevel;
|
|
1084
|
+
if (source.github) {
|
|
1085
|
+
result2.github = {
|
|
1086
|
+
...result2.github,
|
|
1087
|
+
...source.github
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
return result2;
|
|
1092
|
+
}
|
|
1093
|
+
function loadConfig(cliFlags) {
|
|
1094
|
+
const defaults = getDefaults();
|
|
1095
|
+
const envVars = readEnvVars();
|
|
1096
|
+
const fileConfig = readConfigFile();
|
|
1097
|
+
_config = deepMerge(defaults, envVars, fileConfig, cliFlags || {});
|
|
1098
|
+
if (!(0, import_node_fs3.existsSync)(_config.dataDir)) {
|
|
1099
|
+
(0, import_node_fs3.mkdirSync)(_config.dataDir, { recursive: true });
|
|
1100
|
+
}
|
|
1101
|
+
return _config;
|
|
1102
|
+
}
|
|
1103
|
+
function getConfig() {
|
|
1104
|
+
if (!_config) {
|
|
1105
|
+
throw new Error("Config not loaded. Call loadConfig() first.");
|
|
1106
|
+
}
|
|
1107
|
+
return _config;
|
|
1108
|
+
}
|
|
1109
|
+
var import_node_fs3, import_node_path3, import_node_os, DEFAULT_DATA_DIR, CONFIG_PATH, _config;
|
|
1110
|
+
var init_config = __esm({
|
|
1111
|
+
"src/config.ts"() {
|
|
1112
|
+
"use strict";
|
|
1113
|
+
import_node_fs3 = require("fs");
|
|
1114
|
+
import_node_path3 = require("path");
|
|
1115
|
+
import_node_os = require("os");
|
|
1116
|
+
DEFAULT_DATA_DIR = (0, import_node_path3.join)((0, import_node_os.homedir)(), ".closeclaw", "data");
|
|
1117
|
+
CONFIG_PATH = (0, import_node_path3.join)((0, import_node_os.homedir)(), ".closeclaw", "config.json");
|
|
1118
|
+
_config = null;
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
|
|
968
1122
|
// src/services/openclaw-manager.ts
|
|
969
1123
|
var openclaw_manager_exports = {};
|
|
970
1124
|
__export(openclaw_manager_exports, {
|
|
@@ -1148,54 +1302,6 @@ var init_openclaw_manager = __esm({
|
|
|
1148
1302
|
}
|
|
1149
1303
|
});
|
|
1150
1304
|
|
|
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
1305
|
// src/auth/ws-auth.ts
|
|
1200
1306
|
function authenticateConnection(req) {
|
|
1201
1307
|
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
@@ -1256,8 +1362,8 @@ function getCached(key) {
|
|
|
1256
1362
|
}
|
|
1257
1363
|
return entry.result;
|
|
1258
1364
|
}
|
|
1259
|
-
function setCache(key,
|
|
1260
|
-
accessCache.set(key, { result, expiresAt: Date.now() + CACHE_TTL });
|
|
1365
|
+
function setCache(key, result2) {
|
|
1366
|
+
accessCache.set(key, { result: result2, expiresAt: Date.now() + CACHE_TTL });
|
|
1261
1367
|
}
|
|
1262
1368
|
function invalidateUserCache(userId) {
|
|
1263
1369
|
for (const key of accessCache.keys()) {
|
|
@@ -1275,14 +1381,14 @@ function getOrgAccess(userId, orgId) {
|
|
|
1275
1381
|
"SELECT role FROM org_members WHERE org_id = ? AND user_id = ?"
|
|
1276
1382
|
).get(orgId, userId);
|
|
1277
1383
|
if (!member) {
|
|
1278
|
-
const
|
|
1279
|
-
setCache(cacheKey,
|
|
1280
|
-
return
|
|
1384
|
+
const result3 = { allowed: false };
|
|
1385
|
+
setCache(cacheKey, result3);
|
|
1386
|
+
return result3;
|
|
1281
1387
|
}
|
|
1282
1388
|
const orgRole = member.role === "owner" || member.role === "admin" ? "admin" : "member";
|
|
1283
|
-
const
|
|
1284
|
-
setCache(cacheKey,
|
|
1285
|
-
return
|
|
1389
|
+
const result2 = { allowed: true, orgRole };
|
|
1390
|
+
setCache(cacheKey, result2);
|
|
1391
|
+
return result2;
|
|
1286
1392
|
}
|
|
1287
1393
|
function getProjectAccess(userId, projectId) {
|
|
1288
1394
|
const cacheKey = `${userId}:proj:${projectId}`;
|
|
@@ -1293,28 +1399,28 @@ function getProjectAccess(userId, projectId) {
|
|
|
1293
1399
|
"SELECT org_id FROM projects WHERE id = ?"
|
|
1294
1400
|
).get(projectId);
|
|
1295
1401
|
if (!project) {
|
|
1296
|
-
const
|
|
1297
|
-
setCache(cacheKey,
|
|
1298
|
-
return
|
|
1402
|
+
const result3 = { allowed: false };
|
|
1403
|
+
setCache(cacheKey, result3);
|
|
1404
|
+
return result3;
|
|
1299
1405
|
}
|
|
1300
1406
|
const orgAccess = getOrgAccess(userId, project.org_id);
|
|
1301
1407
|
if (orgAccess.orgRole === "admin") {
|
|
1302
|
-
const
|
|
1303
|
-
setCache(cacheKey,
|
|
1304
|
-
return
|
|
1408
|
+
const result3 = { allowed: true, orgRole: "admin", projectRole: "project_lead" };
|
|
1409
|
+
setCache(cacheKey, result3);
|
|
1410
|
+
return result3;
|
|
1305
1411
|
}
|
|
1306
1412
|
const projectMember = db.prepare(
|
|
1307
1413
|
"SELECT role FROM project_members WHERE project_id = ? AND user_id = ?"
|
|
1308
1414
|
).get(projectId, userId);
|
|
1309
1415
|
if (!projectMember) {
|
|
1310
|
-
const
|
|
1311
|
-
setCache(cacheKey,
|
|
1312
|
-
return
|
|
1416
|
+
const result3 = { allowed: false, orgRole: orgAccess.orgRole };
|
|
1417
|
+
setCache(cacheKey, result3);
|
|
1418
|
+
return result3;
|
|
1313
1419
|
}
|
|
1314
1420
|
const projectRole = projectMember.role === "lead" || projectMember.role === "project_lead" ? "project_lead" : "member";
|
|
1315
|
-
const
|
|
1316
|
-
setCache(cacheKey,
|
|
1317
|
-
return
|
|
1421
|
+
const result2 = { allowed: true, orgRole: orgAccess.orgRole, projectRole };
|
|
1422
|
+
setCache(cacheKey, result2);
|
|
1423
|
+
return result2;
|
|
1318
1424
|
}
|
|
1319
1425
|
function requireOrgAccess(userId, orgId) {
|
|
1320
1426
|
const access = getOrgAccess(userId, orgId);
|
|
@@ -1539,11 +1645,11 @@ async function routeMessage(conn, raw) {
|
|
|
1539
1645
|
}
|
|
1540
1646
|
log.ws("in", conn.clientId, msg);
|
|
1541
1647
|
if (type === "subscribe") {
|
|
1542
|
-
const
|
|
1543
|
-
if (
|
|
1648
|
+
const result2 = await subscribe(conn, msg.scope, msg.projectId, msg.taskId);
|
|
1649
|
+
if (result2.ok) {
|
|
1544
1650
|
sendResponse(conn, id, { ok: true });
|
|
1545
1651
|
} else {
|
|
1546
|
-
sendError(conn, id, "SUBSCRIBE_FAILED",
|
|
1652
|
+
sendError(conn, id, "SUBSCRIBE_FAILED", result2.error || "Subscription failed");
|
|
1547
1653
|
}
|
|
1548
1654
|
return;
|
|
1549
1655
|
}
|
|
@@ -1566,15 +1672,15 @@ async function routeMessage(conn, raw) {
|
|
|
1566
1672
|
}
|
|
1567
1673
|
}
|
|
1568
1674
|
try {
|
|
1569
|
-
const
|
|
1570
|
-
if (idempotencyKey &&
|
|
1675
|
+
const result2 = await handler({ conn, requestId: id, data: msg.data || {} });
|
|
1676
|
+
if (idempotencyKey && result2.ok) {
|
|
1571
1677
|
idempotencyCache.set(idempotencyKey, {
|
|
1572
|
-
response:
|
|
1678
|
+
response: result2,
|
|
1573
1679
|
expiresAt: Date.now() + 3e5
|
|
1574
1680
|
// 5 min
|
|
1575
1681
|
});
|
|
1576
1682
|
}
|
|
1577
|
-
sendResponse(conn, id,
|
|
1683
|
+
sendResponse(conn, id, result2);
|
|
1578
1684
|
} catch (err) {
|
|
1579
1685
|
if (err instanceof AccessDenied) {
|
|
1580
1686
|
sendError(conn, id, err.code, err.message);
|
|
@@ -1584,13 +1690,13 @@ async function routeMessage(conn, raw) {
|
|
|
1584
1690
|
}
|
|
1585
1691
|
}
|
|
1586
1692
|
}
|
|
1587
|
-
function sendResponse(conn, id,
|
|
1693
|
+
function sendResponse(conn, id, result2) {
|
|
1588
1694
|
if (conn.ws.readyState !== 1) return;
|
|
1589
1695
|
const msg = {
|
|
1590
1696
|
id,
|
|
1591
1697
|
type: "response",
|
|
1592
|
-
ok:
|
|
1593
|
-
...
|
|
1698
|
+
ok: result2.ok,
|
|
1699
|
+
...result2.ok ? { data: result2.data } : { error: result2.error }
|
|
1594
1700
|
};
|
|
1595
1701
|
log.ws("out", conn.clientId, msg);
|
|
1596
1702
|
conn.ws.send(JSON.stringify(msg));
|
|
@@ -1781,9 +1887,9 @@ function buildTaskPrompt(task, project) {
|
|
|
1781
1887
|
const comments2 = task.comments;
|
|
1782
1888
|
if (comments2?.length) {
|
|
1783
1889
|
lines.push("## Existing Comments");
|
|
1784
|
-
for (const
|
|
1785
|
-
const who =
|
|
1786
|
-
lines.push(`**${who}:** ${
|
|
1890
|
+
for (const c2 of comments2) {
|
|
1891
|
+
const who = c2.is_ai_message ? "AI" : "Human";
|
|
1892
|
+
lines.push(`**${who}:** ${c2.content}`, "");
|
|
1787
1893
|
}
|
|
1788
1894
|
}
|
|
1789
1895
|
const checklists2 = task.checklists;
|
|
@@ -2379,7 +2485,7 @@ function upsertPullRequest(data) {
|
|
|
2379
2485
|
);
|
|
2380
2486
|
return db.prepare("SELECT * FROM pull_requests WHERE id = ?").get(existing.id);
|
|
2381
2487
|
}
|
|
2382
|
-
const id = (0,
|
|
2488
|
+
const id = (0, import_node_crypto5.randomUUID)();
|
|
2383
2489
|
db.prepare(
|
|
2384
2490
|
`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
2491
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
@@ -2445,7 +2551,7 @@ function saveTaskBranches(taskId, branches) {
|
|
|
2445
2551
|
);
|
|
2446
2552
|
const insertAll = db.transaction((rows) => {
|
|
2447
2553
|
for (const b of rows) {
|
|
2448
|
-
insert.run((0,
|
|
2554
|
+
insert.run((0, import_node_crypto5.randomUUID)(), taskId, b.projectRepoId, b.branchName, b.sourceBranch, now, now);
|
|
2449
2555
|
}
|
|
2450
2556
|
});
|
|
2451
2557
|
insertAll(branches);
|
|
@@ -2494,7 +2600,7 @@ function upsertProjectRepo(data) {
|
|
|
2494
2600
|
);
|
|
2495
2601
|
return db.prepare("SELECT * FROM project_repos WHERE id = ?").get(existing.id);
|
|
2496
2602
|
}
|
|
2497
|
-
const id = (0,
|
|
2603
|
+
const id = (0, import_node_crypto5.randomUUID)();
|
|
2498
2604
|
db.prepare(
|
|
2499
2605
|
`INSERT INTO project_repos (id, project_id, repo_owner, repo_name, repo_url, label, installation_id, default_branch, created_at, updated_at)
|
|
2500
2606
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
@@ -2527,7 +2633,7 @@ function getTaskActivity(taskId, limit = 50) {
|
|
|
2527
2633
|
}
|
|
2528
2634
|
function createTaskActivity(data) {
|
|
2529
2635
|
const db = getSqlite();
|
|
2530
|
-
const id = (0,
|
|
2636
|
+
const id = (0, import_node_crypto5.randomUUID)();
|
|
2531
2637
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2532
2638
|
db.prepare(
|
|
2533
2639
|
`INSERT INTO task_activity (id, task_id, project_id, event_type, description, metadata, created_at)
|
|
@@ -2543,12 +2649,12 @@ function createTaskActivity(data) {
|
|
|
2543
2649
|
);
|
|
2544
2650
|
return db.prepare("SELECT * FROM task_activity WHERE id = ?").get(id);
|
|
2545
2651
|
}
|
|
2546
|
-
var
|
|
2652
|
+
var import_node_crypto5;
|
|
2547
2653
|
var init_github2 = __esm({
|
|
2548
2654
|
"src/db/queries/github.ts"() {
|
|
2549
2655
|
"use strict";
|
|
2550
2656
|
init_connection();
|
|
2551
|
-
|
|
2657
|
+
import_node_crypto5 = require("crypto");
|
|
2552
2658
|
}
|
|
2553
2659
|
});
|
|
2554
2660
|
|
|
@@ -2824,7 +2930,7 @@ function getTaskForAgent(taskId) {
|
|
|
2824
2930
|
}
|
|
2825
2931
|
function createTask(data) {
|
|
2826
2932
|
const db = getSqlite();
|
|
2827
|
-
const id = (0,
|
|
2933
|
+
const id = (0, import_node_crypto6.randomUUID)();
|
|
2828
2934
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2829
2935
|
db.prepare(
|
|
2830
2936
|
`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 +3036,8 @@ function updatePositionOptimistic(taskId, status, position, expectedUpdatedAt, e
|
|
|
2930
3036
|
if (typeof v === "boolean") return v ? 1 : 0;
|
|
2931
3037
|
return v;
|
|
2932
3038
|
});
|
|
2933
|
-
const
|
|
2934
|
-
return
|
|
3039
|
+
const result2 = db.prepare(`UPDATE tasks SET ${setClause} WHERE id = ? AND updated_at = ?`).run(...values, taskId, expectedUpdatedAt);
|
|
3040
|
+
return result2.changes > 0;
|
|
2935
3041
|
}
|
|
2936
3042
|
function getNextTaskNumber(projectId) {
|
|
2937
3043
|
const db = getSqlite();
|
|
@@ -3027,12 +3133,12 @@ function recordColumnTransition(taskId, oldStatus, newStatus) {
|
|
|
3027
3133
|
"UPDATE tasks SET column_entered_at = ?, column_times = ? WHERE id = ?"
|
|
3028
3134
|
).run(now, JSON.stringify(columnTimes), taskId);
|
|
3029
3135
|
}
|
|
3030
|
-
var
|
|
3136
|
+
var import_node_crypto6;
|
|
3031
3137
|
var init_tasks = __esm({
|
|
3032
3138
|
"src/db/queries/tasks.ts"() {
|
|
3033
3139
|
"use strict";
|
|
3034
3140
|
init_connection();
|
|
3035
|
-
|
|
3141
|
+
import_node_crypto6 = require("crypto");
|
|
3036
3142
|
}
|
|
3037
3143
|
});
|
|
3038
3144
|
|
|
@@ -3096,7 +3202,7 @@ function getActivity(taskId, limit = 50) {
|
|
|
3096
3202
|
}
|
|
3097
3203
|
function createActivity(data) {
|
|
3098
3204
|
const db = getSqlite();
|
|
3099
|
-
const id = (0,
|
|
3205
|
+
const id = (0, import_node_crypto7.randomUUID)();
|
|
3100
3206
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3101
3207
|
db.prepare(
|
|
3102
3208
|
`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 +3233,7 @@ function createActivities(entries) {
|
|
|
3127
3233
|
const insertMany = db.transaction((rows) => {
|
|
3128
3234
|
for (const e of rows) {
|
|
3129
3235
|
insert.run(
|
|
3130
|
-
(0,
|
|
3236
|
+
(0, import_node_crypto7.randomUUID)(),
|
|
3131
3237
|
e.task_id || null,
|
|
3132
3238
|
e.project_id || null,
|
|
3133
3239
|
e.user_id || null,
|
|
@@ -3142,19 +3248,19 @@ function createActivities(entries) {
|
|
|
3142
3248
|
});
|
|
3143
3249
|
insertMany(entries);
|
|
3144
3250
|
}
|
|
3145
|
-
var
|
|
3251
|
+
var import_node_crypto7;
|
|
3146
3252
|
var init_activity = __esm({
|
|
3147
3253
|
"src/db/queries/activity.ts"() {
|
|
3148
3254
|
"use strict";
|
|
3149
3255
|
init_connection();
|
|
3150
|
-
|
|
3256
|
+
import_node_crypto7 = require("crypto");
|
|
3151
3257
|
}
|
|
3152
3258
|
});
|
|
3153
3259
|
|
|
3154
3260
|
// src/db/queries/projects.ts
|
|
3155
3261
|
function createProject(data) {
|
|
3156
3262
|
const db = getSqlite();
|
|
3157
|
-
const id = (0,
|
|
3263
|
+
const id = (0, import_node_crypto8.randomUUID)();
|
|
3158
3264
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3159
3265
|
db.prepare(
|
|
3160
3266
|
`INSERT INTO projects (id, org_id, name, slug, prefix, description, color, icon, created_by, agent_id, agent_instructions, created_at, updated_at)
|
|
@@ -3235,7 +3341,7 @@ function getProjectMembers(projectId) {
|
|
|
3235
3341
|
}
|
|
3236
3342
|
function addProjectMember(data) {
|
|
3237
3343
|
const db = getSqlite();
|
|
3238
|
-
const id = (0,
|
|
3344
|
+
const id = (0, import_node_crypto8.randomUUID)();
|
|
3239
3345
|
db.prepare(
|
|
3240
3346
|
`INSERT INTO project_members (id, project_id, user_id, role) VALUES (?, ?, ?, ?)`
|
|
3241
3347
|
).run(id, data.project_id, data.user_id, data.role || "member");
|
|
@@ -3299,12 +3405,12 @@ function getProjectOrgId(projectId) {
|
|
|
3299
3405
|
const row = db.prepare("SELECT org_id FROM projects WHERE id = ?").get(projectId);
|
|
3300
3406
|
return row?.org_id || null;
|
|
3301
3407
|
}
|
|
3302
|
-
var
|
|
3408
|
+
var import_node_crypto8;
|
|
3303
3409
|
var init_projects = __esm({
|
|
3304
3410
|
"src/db/queries/projects.ts"() {
|
|
3305
3411
|
"use strict";
|
|
3306
3412
|
init_connection();
|
|
3307
|
-
|
|
3413
|
+
import_node_crypto8 = require("crypto");
|
|
3308
3414
|
}
|
|
3309
3415
|
});
|
|
3310
3416
|
|
|
@@ -3549,10 +3655,10 @@ var init_tasks2 = __esm({
|
|
|
3549
3655
|
const prompt = buildTaskPrompt(task, project);
|
|
3550
3656
|
try {
|
|
3551
3657
|
if (openclaw.isHealthy()) {
|
|
3552
|
-
const
|
|
3658
|
+
const result2 = await openclaw.dispatchTask(project.agent_id, taskId, prompt);
|
|
3553
3659
|
updateTask(taskId, {
|
|
3554
3660
|
ai_typing: true,
|
|
3555
|
-
ai_run_id:
|
|
3661
|
+
ai_run_id: result2?.runId || null,
|
|
3556
3662
|
ai_dispatched_by: conn.userId
|
|
3557
3663
|
});
|
|
3558
3664
|
log.info("ai", `Auto-dispatched ${taskId} -> ${project.agent_id} (by ${conn.userId})`);
|
|
@@ -3638,92 +3744,6 @@ var init_tasks2 = __esm({
|
|
|
3638
3744
|
}
|
|
3639
3745
|
});
|
|
3640
3746
|
|
|
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
3747
|
// src/db/queries/orgs.ts
|
|
3728
3748
|
function createOrg(data) {
|
|
3729
3749
|
const db = getSqlite();
|
|
@@ -3883,8 +3903,8 @@ var init_projects2 = __esm({
|
|
|
3883
3903
|
return { ok: true, data: [] };
|
|
3884
3904
|
}
|
|
3885
3905
|
try {
|
|
3886
|
-
const
|
|
3887
|
-
return { ok: true, data:
|
|
3906
|
+
const result2 = await openclaw.rpc("agents.list", {});
|
|
3907
|
+
return { ok: true, data: result2 || [] };
|
|
3888
3908
|
} catch (err) {
|
|
3889
3909
|
log.error("projects", "Failed to list agents from OpenClaw", err);
|
|
3890
3910
|
return { ok: true, data: [] };
|
|
@@ -5131,8 +5151,8 @@ async function dispatchToAgent(agentId, taskId, message) {
|
|
|
5131
5151
|
if (!openclaw.isHealthy()) {
|
|
5132
5152
|
throw new Error("AI agent system is unavailable");
|
|
5133
5153
|
}
|
|
5134
|
-
const
|
|
5135
|
-
return { runId:
|
|
5154
|
+
const result2 = await openclaw.dispatchTask(agentId, taskId, message);
|
|
5155
|
+
return { runId: result2?.runId };
|
|
5136
5156
|
}
|
|
5137
5157
|
function setupAgentEventForwarding() {
|
|
5138
5158
|
openclaw.onEvent("*", (event) => {
|
|
@@ -5349,9 +5369,9 @@ async function adminRpc(userId, orgId, method, params = {}) {
|
|
|
5349
5369
|
return { ok: false, error: { code: "OPENCLAW_UNAVAILABLE", message: "OpenClaw gateway is not connected" } };
|
|
5350
5370
|
}
|
|
5351
5371
|
try {
|
|
5352
|
-
const
|
|
5372
|
+
const result2 = await openclaw.rpc(method, params);
|
|
5353
5373
|
log.oc("out", { method });
|
|
5354
|
-
return { ok: true, data:
|
|
5374
|
+
return { ok: true, data: result2 };
|
|
5355
5375
|
} catch (err) {
|
|
5356
5376
|
log.error("admin", `RPC ${method} failed`, err);
|
|
5357
5377
|
return { ok: false, error: { code: "RPC_FAILED", message: err.message || `RPC ${method} failed` } };
|
|
@@ -5571,13 +5591,13 @@ var init_github3 = __esm({
|
|
|
5571
5591
|
repo.installation_id,
|
|
5572
5592
|
`/repos/${repo.repo_owner}/${repo.repo_name}/branches?per_page=100`
|
|
5573
5593
|
);
|
|
5574
|
-
const
|
|
5594
|
+
const result2 = branches.map((b) => ({
|
|
5575
5595
|
name: b.name,
|
|
5576
5596
|
sha: b.commit?.sha,
|
|
5577
5597
|
protected: b.protected
|
|
5578
5598
|
}));
|
|
5579
5599
|
const enriched = await Promise.all(
|
|
5580
|
-
|
|
5600
|
+
result2.slice(0, 30).map(async (branch) => {
|
|
5581
5601
|
try {
|
|
5582
5602
|
const commit = await githubApi(
|
|
5583
5603
|
repo.installation_id,
|
|
@@ -5593,7 +5613,7 @@ var init_github3 = __esm({
|
|
|
5593
5613
|
}
|
|
5594
5614
|
})
|
|
5595
5615
|
);
|
|
5596
|
-
const remaining =
|
|
5616
|
+
const remaining = result2.slice(30).map((b) => ({
|
|
5597
5617
|
...b,
|
|
5598
5618
|
lastCommitDate: null,
|
|
5599
5619
|
lastCommitMessage: null
|
|
@@ -6025,11 +6045,11 @@ var init_agent_bridge = __esm({
|
|
|
6025
6045
|
init_projects();
|
|
6026
6046
|
init_profiles();
|
|
6027
6047
|
agentBridge = new import_hono.Hono();
|
|
6028
|
-
agentBridge.post("/internal/task-update", async (
|
|
6048
|
+
agentBridge.post("/internal/task-update", async (c2) => {
|
|
6029
6049
|
try {
|
|
6030
|
-
const { task_id, status, ai_plan, ai_plan_status, comment, side } = await
|
|
6050
|
+
const { task_id, status, ai_plan, ai_plan_status, comment, side } = await c2.req.json();
|
|
6031
6051
|
if (!task_id) {
|
|
6032
|
-
return
|
|
6052
|
+
return c2.json({ ok: false, error: "task_id required" }, 400);
|
|
6033
6053
|
}
|
|
6034
6054
|
log.internal("POST", `/internal/task-update task=${task_id}`);
|
|
6035
6055
|
const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
@@ -6051,7 +6071,7 @@ var init_agent_bridge = __esm({
|
|
|
6051
6071
|
log.db("tasks", "UPDATE", task_id);
|
|
6052
6072
|
} catch (err) {
|
|
6053
6073
|
log.error("internal", "Task update failed", err);
|
|
6054
|
-
return
|
|
6074
|
+
return c2.json({ ok: false, error: err.message }, 500);
|
|
6055
6075
|
}
|
|
6056
6076
|
}
|
|
6057
6077
|
if (comment) {
|
|
@@ -6079,39 +6099,39 @@ var init_agent_bridge = __esm({
|
|
|
6079
6099
|
changes: updates
|
|
6080
6100
|
});
|
|
6081
6101
|
}
|
|
6082
|
-
return
|
|
6102
|
+
return c2.json({ ok: true });
|
|
6083
6103
|
} catch (err) {
|
|
6084
6104
|
log.error("internal", "task-update error", err);
|
|
6085
|
-
return
|
|
6105
|
+
return c2.json({ ok: false, error: err.message }, 500);
|
|
6086
6106
|
}
|
|
6087
6107
|
});
|
|
6088
|
-
agentBridge.post("/internal/task-get", async (
|
|
6108
|
+
agentBridge.post("/internal/task-get", async (c2) => {
|
|
6089
6109
|
try {
|
|
6090
|
-
const { task_id } = await
|
|
6110
|
+
const { task_id } = await c2.req.json();
|
|
6091
6111
|
if (!task_id) {
|
|
6092
|
-
return
|
|
6112
|
+
return c2.json({ ok: false, error: "task_id required" }, 400);
|
|
6093
6113
|
}
|
|
6094
6114
|
log.internal("POST", `/internal/task-get task=${task_id}`);
|
|
6095
6115
|
const task = getTaskForAgent(task_id);
|
|
6096
6116
|
if (!task) {
|
|
6097
|
-
return
|
|
6117
|
+
return c2.json({ ok: false, error: "Task not found" }, 500);
|
|
6098
6118
|
}
|
|
6099
|
-
return
|
|
6119
|
+
return c2.json({ ok: true, task });
|
|
6100
6120
|
} catch (err) {
|
|
6101
6121
|
log.error("internal", "task-get error", err);
|
|
6102
|
-
return
|
|
6122
|
+
return c2.json({ ok: false, error: err.message }, 500);
|
|
6103
6123
|
}
|
|
6104
6124
|
});
|
|
6105
|
-
agentBridge.post("/internal/git-list-branches", async (
|
|
6125
|
+
agentBridge.post("/internal/git-list-branches", async (c2) => {
|
|
6106
6126
|
try {
|
|
6107
|
-
const { task_id } = await
|
|
6108
|
-
if (!task_id) return
|
|
6127
|
+
const { task_id } = await c2.req.json();
|
|
6128
|
+
if (!task_id) return c2.json({ ok: false, error: "task_id required" }, 400);
|
|
6109
6129
|
log.internal("POST", `/internal/git-list-branches task=${task_id}`);
|
|
6110
6130
|
const taskBranches2 = getTaskBranchesWithRepo(task_id);
|
|
6111
6131
|
if (!taskBranches2 || taskBranches2.length === 0) {
|
|
6112
|
-
return
|
|
6132
|
+
return c2.json({ ok: true, branches: [] });
|
|
6113
6133
|
}
|
|
6114
|
-
const
|
|
6134
|
+
const result2 = [];
|
|
6115
6135
|
for (const tb of taskBranches2) {
|
|
6116
6136
|
const repo = tb.project_repos;
|
|
6117
6137
|
if (!repo?.installation_id) continue;
|
|
@@ -6120,7 +6140,7 @@ var init_agent_bridge = __esm({
|
|
|
6120
6140
|
repo.installation_id,
|
|
6121
6141
|
`/repos/${repo.repo_owner}/${repo.repo_name}/branches?per_page=100`
|
|
6122
6142
|
);
|
|
6123
|
-
|
|
6143
|
+
result2.push({
|
|
6124
6144
|
repo_label: repo.label,
|
|
6125
6145
|
repo_owner: repo.repo_owner,
|
|
6126
6146
|
repo_name: repo.repo_name,
|
|
@@ -6130,35 +6150,35 @@ var init_agent_bridge = __esm({
|
|
|
6130
6150
|
log.error("internal", `Failed to list branches for ${repo.repo_owner}/${repo.repo_name}`, err);
|
|
6131
6151
|
}
|
|
6132
6152
|
}
|
|
6133
|
-
return
|
|
6153
|
+
return c2.json({ ok: true, repos: result2 });
|
|
6134
6154
|
} catch (err) {
|
|
6135
6155
|
log.error("internal", "git-list-branches error", err);
|
|
6136
|
-
return
|
|
6156
|
+
return c2.json({ ok: false, error: err.message }, 500);
|
|
6137
6157
|
}
|
|
6138
6158
|
});
|
|
6139
|
-
agentBridge.post("/internal/git-create-branch", async (
|
|
6159
|
+
agentBridge.post("/internal/git-create-branch", async (c2) => {
|
|
6140
6160
|
try {
|
|
6141
|
-
const { task_id } = await
|
|
6142
|
-
if (!task_id) return
|
|
6161
|
+
const { task_id } = await c2.req.json();
|
|
6162
|
+
if (!task_id) return c2.json({ ok: false, error: "task_id required" }, 400);
|
|
6143
6163
|
log.internal("POST", `/internal/git-create-branch task=${task_id}`);
|
|
6144
6164
|
const task = getTask(task_id);
|
|
6145
|
-
if (!task) return
|
|
6165
|
+
if (!task) return c2.json({ ok: false, error: "Task not found" }, 404);
|
|
6146
6166
|
await createBranchesForTask(task_id, task.project_id);
|
|
6147
|
-
return
|
|
6167
|
+
return c2.json({ ok: true });
|
|
6148
6168
|
} catch (err) {
|
|
6149
6169
|
log.error("internal", "git-create-branch error", err);
|
|
6150
|
-
return
|
|
6170
|
+
return c2.json({ ok: false, error: err.message }, 500);
|
|
6151
6171
|
}
|
|
6152
6172
|
});
|
|
6153
|
-
agentBridge.post("/internal/git-create-pr", async (
|
|
6173
|
+
agentBridge.post("/internal/git-create-pr", async (c2) => {
|
|
6154
6174
|
try {
|
|
6155
|
-
const { task_id } = await
|
|
6156
|
-
if (!task_id) return
|
|
6175
|
+
const { task_id } = await c2.req.json();
|
|
6176
|
+
if (!task_id) return c2.json({ ok: false, error: "task_id required" }, 400);
|
|
6157
6177
|
log.internal("POST", `/internal/git-create-pr task=${task_id}`);
|
|
6158
6178
|
const task = getTaskForAgent(task_id);
|
|
6159
|
-
if (!task) return
|
|
6179
|
+
if (!task) return c2.json({ ok: false, error: "Task not found" }, 404);
|
|
6160
6180
|
const branches = getTaskBranchesWithRepo(task_id);
|
|
6161
|
-
if (!branches || branches.length === 0) return
|
|
6181
|
+
if (!branches || branches.length === 0) return c2.json({ ok: false, error: "No branches configured for this task" }, 400);
|
|
6162
6182
|
const project = getProject(task.project_id);
|
|
6163
6183
|
const results = [];
|
|
6164
6184
|
for (const tb of branches) {
|
|
@@ -6245,22 +6265,22 @@ ${task.ai_plan}`);
|
|
|
6245
6265
|
log.error("internal", `Failed to create PR for ${repo.repo_owner}/${repo.repo_name}`, err);
|
|
6246
6266
|
}
|
|
6247
6267
|
}
|
|
6248
|
-
return
|
|
6268
|
+
return c2.json({ ok: true, results });
|
|
6249
6269
|
} catch (err) {
|
|
6250
6270
|
log.error("internal", "git-create-pr error", err);
|
|
6251
|
-
return
|
|
6271
|
+
return c2.json({ ok: false, error: err.message }, 500);
|
|
6252
6272
|
}
|
|
6253
6273
|
});
|
|
6254
|
-
agentBridge.post("/internal/git-push", async (
|
|
6274
|
+
agentBridge.post("/internal/git-push", async (c2) => {
|
|
6255
6275
|
try {
|
|
6256
|
-
const { task_id, message } = await
|
|
6257
|
-
if (!task_id) return
|
|
6258
|
-
if (!message) return
|
|
6276
|
+
const { task_id, message } = await c2.req.json();
|
|
6277
|
+
if (!task_id) return c2.json({ ok: false, error: "task_id required" }, 400);
|
|
6278
|
+
if (!message) return c2.json({ ok: false, error: "message (commit message) required" }, 400);
|
|
6259
6279
|
log.internal("POST", `/internal/git-push task=${task_id}`);
|
|
6260
6280
|
const task = getTask(task_id);
|
|
6261
|
-
if (!task) return
|
|
6281
|
+
if (!task) return c2.json({ ok: false, error: "Task not found" }, 404);
|
|
6262
6282
|
const branches = getTaskBranchesWithRepo(task_id);
|
|
6263
|
-
if (branches.length === 0) return
|
|
6283
|
+
if (branches.length === 0) return c2.json({ ok: false, error: "No branches configured for this task" }, 400);
|
|
6264
6284
|
let dispatcher = null;
|
|
6265
6285
|
if (task.ai_dispatched_by) {
|
|
6266
6286
|
dispatcher = getProfileById(task.ai_dispatched_by);
|
|
@@ -6344,20 +6364,20 @@ ${task.ai_plan}`);
|
|
|
6344
6364
|
log.error("internal", `git-push failed for ${repoLabel}`, err);
|
|
6345
6365
|
}
|
|
6346
6366
|
}
|
|
6347
|
-
return
|
|
6367
|
+
return c2.json({ ok: true, results });
|
|
6348
6368
|
} catch (err) {
|
|
6349
6369
|
log.error("internal", "git-push error", err);
|
|
6350
|
-
return
|
|
6370
|
+
return c2.json({ ok: false, error: err.message }, 500);
|
|
6351
6371
|
}
|
|
6352
6372
|
});
|
|
6353
|
-
agentBridge.post("/internal/git-pr-status", async (
|
|
6373
|
+
agentBridge.post("/internal/git-pr-status", async (c2) => {
|
|
6354
6374
|
try {
|
|
6355
|
-
const { task_id } = await
|
|
6356
|
-
if (!task_id) return
|
|
6375
|
+
const { task_id } = await c2.req.json();
|
|
6376
|
+
if (!task_id) return c2.json({ ok: false, error: "task_id required" }, 400);
|
|
6357
6377
|
log.internal("POST", `/internal/git-pr-status task=${task_id}`);
|
|
6358
6378
|
const branches = getTaskBranchesWithRepo(task_id);
|
|
6359
6379
|
if (!branches || branches.length === 0) {
|
|
6360
|
-
return
|
|
6380
|
+
return c2.json({ ok: true, branches: [], summary: "No branches configured" });
|
|
6361
6381
|
}
|
|
6362
6382
|
const results = branches.map((tb) => {
|
|
6363
6383
|
const repo = tb.project_repos;
|
|
@@ -6400,14 +6420,14 @@ ${task.ai_plan}`);
|
|
|
6400
6420
|
const readyToMerge = results.some(
|
|
6401
6421
|
(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
6422
|
);
|
|
6403
|
-
return
|
|
6423
|
+
return c2.json({
|
|
6404
6424
|
ok: true,
|
|
6405
6425
|
branches: results,
|
|
6406
6426
|
summary: readyToMerge ? "Ready to merge" : results.some((r) => r.pr_number) ? "PR open -- not ready" : "No PR yet"
|
|
6407
6427
|
});
|
|
6408
6428
|
} catch (err) {
|
|
6409
6429
|
log.error("internal", "git-pr-status error", err);
|
|
6410
|
-
return
|
|
6430
|
+
return c2.json({ ok: false, error: err.message }, 500);
|
|
6411
6431
|
}
|
|
6412
6432
|
});
|
|
6413
6433
|
}
|
|
@@ -6450,10 +6470,10 @@ var init_middleware = __esm({
|
|
|
6450
6470
|
"use strict";
|
|
6451
6471
|
import_factory = require("hono/factory");
|
|
6452
6472
|
init_jwt();
|
|
6453
|
-
authMiddleware = (0, import_factory.createMiddleware)(async (
|
|
6454
|
-
const authHeader =
|
|
6473
|
+
authMiddleware = (0, import_factory.createMiddleware)(async (c2, next) => {
|
|
6474
|
+
const authHeader = c2.req.header("Authorization");
|
|
6455
6475
|
if (!authHeader?.startsWith("Bearer ")) {
|
|
6456
|
-
return
|
|
6476
|
+
return c2.json(
|
|
6457
6477
|
{ error: "Missing or invalid authorization header", code: "UNAUTHORIZED" },
|
|
6458
6478
|
401
|
|
6459
6479
|
);
|
|
@@ -6461,10 +6481,10 @@ var init_middleware = __esm({
|
|
|
6461
6481
|
const token = authHeader.slice(7);
|
|
6462
6482
|
try {
|
|
6463
6483
|
const payload = verifyAccessToken(token);
|
|
6464
|
-
|
|
6465
|
-
|
|
6484
|
+
c2.set("userId", payload.sub);
|
|
6485
|
+
c2.set("email", payload.email);
|
|
6466
6486
|
} catch {
|
|
6467
|
-
return
|
|
6487
|
+
return c2.json(
|
|
6468
6488
|
{ error: "Invalid or expired token", code: "UNAUTHORIZED" },
|
|
6469
6489
|
401
|
|
6470
6490
|
);
|
|
@@ -6524,14 +6544,14 @@ var init_routes = __esm({
|
|
|
6524
6544
|
preferences: import_zod5.z.record(import_zod5.z.unknown()).optional()
|
|
6525
6545
|
});
|
|
6526
6546
|
auth = new import_hono2.Hono();
|
|
6527
|
-
auth.post("/signup", async (
|
|
6528
|
-
const body = await
|
|
6547
|
+
auth.post("/signup", async (c2) => {
|
|
6548
|
+
const body = await c2.req.json().catch(() => null);
|
|
6529
6549
|
if (!body) {
|
|
6530
|
-
return
|
|
6550
|
+
return c2.json({ error: "Invalid JSON body", code: "VALIDATION_ERROR" }, 400);
|
|
6531
6551
|
}
|
|
6532
6552
|
const parsed = signupSchema.safeParse(body);
|
|
6533
6553
|
if (!parsed.success) {
|
|
6534
|
-
return
|
|
6554
|
+
return c2.json(
|
|
6535
6555
|
{ error: parsed.error.issues[0].message, code: "VALIDATION_ERROR" },
|
|
6536
6556
|
400
|
|
6537
6557
|
);
|
|
@@ -6540,7 +6560,7 @@ var init_routes = __esm({
|
|
|
6540
6560
|
const db = getSqlite();
|
|
6541
6561
|
const existing = db.prepare("SELECT id FROM profiles WHERE email = ?").get(email);
|
|
6542
6562
|
if (existing) {
|
|
6543
|
-
return
|
|
6563
|
+
return c2.json({ error: "Email already registered", code: "CONFLICT" }, 409);
|
|
6544
6564
|
}
|
|
6545
6565
|
const passwordHash = await hashPassword(password);
|
|
6546
6566
|
const userCount = db.prepare("SELECT COUNT(*) as count FROM profiles").get();
|
|
@@ -6554,20 +6574,20 @@ var init_routes = __esm({
|
|
|
6554
6574
|
const accessToken = signAccessToken({ sub: id, email });
|
|
6555
6575
|
const refreshToken = createRefreshTokenRow(id);
|
|
6556
6576
|
const profile = db.prepare("SELECT * FROM profiles WHERE id = ?").get(id);
|
|
6557
|
-
return
|
|
6577
|
+
return c2.json({
|
|
6558
6578
|
access_token: accessToken,
|
|
6559
6579
|
refresh_token: refreshToken,
|
|
6560
6580
|
profile: sanitizeProfile(profile)
|
|
6561
6581
|
}, 201);
|
|
6562
6582
|
});
|
|
6563
|
-
auth.post("/login", async (
|
|
6564
|
-
const body = await
|
|
6583
|
+
auth.post("/login", async (c2) => {
|
|
6584
|
+
const body = await c2.req.json().catch(() => null);
|
|
6565
6585
|
if (!body) {
|
|
6566
|
-
return
|
|
6586
|
+
return c2.json({ error: "Invalid JSON body", code: "VALIDATION_ERROR" }, 400);
|
|
6567
6587
|
}
|
|
6568
6588
|
const parsed = loginSchema.safeParse(body);
|
|
6569
6589
|
if (!parsed.success) {
|
|
6570
|
-
return
|
|
6590
|
+
return c2.json(
|
|
6571
6591
|
{ error: parsed.error.issues[0].message, code: "VALIDATION_ERROR" },
|
|
6572
6592
|
400
|
|
6573
6593
|
);
|
|
@@ -6576,28 +6596,28 @@ var init_routes = __esm({
|
|
|
6576
6596
|
const db = getSqlite();
|
|
6577
6597
|
const profile = db.prepare("SELECT * FROM profiles WHERE email = ?").get(email);
|
|
6578
6598
|
if (!profile) {
|
|
6579
|
-
return
|
|
6599
|
+
return c2.json({ error: "Invalid email or password", code: "UNAUTHORIZED" }, 401);
|
|
6580
6600
|
}
|
|
6581
6601
|
const valid = await verifyPassword(profile.password_hash, password);
|
|
6582
6602
|
if (!valid) {
|
|
6583
|
-
return
|
|
6603
|
+
return c2.json({ error: "Invalid email or password", code: "UNAUTHORIZED" }, 401);
|
|
6584
6604
|
}
|
|
6585
6605
|
const accessToken = signAccessToken({ sub: profile.id, email });
|
|
6586
6606
|
const refreshToken = createRefreshTokenRow(profile.id);
|
|
6587
|
-
return
|
|
6607
|
+
return c2.json({
|
|
6588
6608
|
access_token: accessToken,
|
|
6589
6609
|
refresh_token: refreshToken,
|
|
6590
6610
|
profile: sanitizeProfile(profile)
|
|
6591
6611
|
});
|
|
6592
6612
|
});
|
|
6593
|
-
auth.post("/refresh", async (
|
|
6594
|
-
const body = await
|
|
6613
|
+
auth.post("/refresh", async (c2) => {
|
|
6614
|
+
const body = await c2.req.json().catch(() => null);
|
|
6595
6615
|
if (!body) {
|
|
6596
|
-
return
|
|
6616
|
+
return c2.json({ error: "Invalid JSON body", code: "VALIDATION_ERROR" }, 400);
|
|
6597
6617
|
}
|
|
6598
6618
|
const parsed = refreshSchema.safeParse(body);
|
|
6599
6619
|
if (!parsed.success) {
|
|
6600
|
-
return
|
|
6620
|
+
return c2.json(
|
|
6601
6621
|
{ error: parsed.error.issues[0].message, code: "VALIDATION_ERROR" },
|
|
6602
6622
|
400
|
|
6603
6623
|
);
|
|
@@ -6608,45 +6628,45 @@ var init_routes = __esm({
|
|
|
6608
6628
|
"SELECT * FROM refresh_tokens WHERE token = ?"
|
|
6609
6629
|
).get(refresh_token);
|
|
6610
6630
|
if (!tokenRow) {
|
|
6611
|
-
return
|
|
6631
|
+
return c2.json({ error: "Invalid refresh token", code: "UNAUTHORIZED" }, 401);
|
|
6612
6632
|
}
|
|
6613
6633
|
if (new Date(tokenRow.expires_at) < /* @__PURE__ */ new Date()) {
|
|
6614
6634
|
db.prepare("DELETE FROM refresh_tokens WHERE id = ?").run(tokenRow.id);
|
|
6615
|
-
return
|
|
6635
|
+
return c2.json({ error: "Refresh token expired", code: "UNAUTHORIZED" }, 401);
|
|
6616
6636
|
}
|
|
6617
6637
|
const userId = tokenRow.user_id;
|
|
6618
6638
|
const profile = db.prepare("SELECT * FROM profiles WHERE id = ?").get(userId);
|
|
6619
6639
|
if (!profile) {
|
|
6620
6640
|
db.prepare("DELETE FROM refresh_tokens WHERE id = ?").run(tokenRow.id);
|
|
6621
|
-
return
|
|
6641
|
+
return c2.json({ error: "User not found", code: "UNAUTHORIZED" }, 401);
|
|
6622
6642
|
}
|
|
6623
6643
|
db.prepare("DELETE FROM refresh_tokens WHERE id = ?").run(tokenRow.id);
|
|
6624
6644
|
const newAccessToken = signAccessToken({ sub: userId, email: profile.email });
|
|
6625
6645
|
const newRefreshToken = createRefreshTokenRow(userId);
|
|
6626
|
-
return
|
|
6646
|
+
return c2.json({
|
|
6627
6647
|
access_token: newAccessToken,
|
|
6628
6648
|
refresh_token: newRefreshToken,
|
|
6629
6649
|
profile: sanitizeProfile(profile)
|
|
6630
6650
|
});
|
|
6631
6651
|
});
|
|
6632
|
-
auth.get("/me", authMiddleware, async (
|
|
6633
|
-
const userId =
|
|
6652
|
+
auth.get("/me", authMiddleware, async (c2) => {
|
|
6653
|
+
const userId = c2.get("userId");
|
|
6634
6654
|
const db = getSqlite();
|
|
6635
6655
|
const profile = db.prepare("SELECT * FROM profiles WHERE id = ?").get(userId);
|
|
6636
6656
|
if (!profile) {
|
|
6637
|
-
return
|
|
6657
|
+
return c2.json({ error: "Profile not found", code: "NOT_FOUND" }, 404);
|
|
6638
6658
|
}
|
|
6639
|
-
return
|
|
6659
|
+
return c2.json({ data: sanitizeProfile(profile) });
|
|
6640
6660
|
});
|
|
6641
|
-
auth.patch("/me", authMiddleware, async (
|
|
6642
|
-
const userId =
|
|
6643
|
-
const body = await
|
|
6661
|
+
auth.patch("/me", authMiddleware, async (c2) => {
|
|
6662
|
+
const userId = c2.get("userId");
|
|
6663
|
+
const body = await c2.req.json().catch(() => null);
|
|
6644
6664
|
if (!body) {
|
|
6645
|
-
return
|
|
6665
|
+
return c2.json({ error: "Invalid JSON body", code: "VALIDATION_ERROR" }, 400);
|
|
6646
6666
|
}
|
|
6647
6667
|
const parsed = updateProfileSchema2.safeParse(body);
|
|
6648
6668
|
if (!parsed.success) {
|
|
6649
|
-
return
|
|
6669
|
+
return c2.json(
|
|
6650
6670
|
{ error: parsed.error.issues[0].message, code: "VALIDATION_ERROR" },
|
|
6651
6671
|
400
|
|
6652
6672
|
);
|
|
@@ -6671,29 +6691,29 @@ var init_routes = __esm({
|
|
|
6671
6691
|
values.push(key === "preferences" ? JSON.stringify(value) : value);
|
|
6672
6692
|
}
|
|
6673
6693
|
if (setClauses.length === 0) {
|
|
6674
|
-
return
|
|
6694
|
+
return c2.json({ error: "No valid fields to update", code: "VALIDATION_ERROR" }, 400);
|
|
6675
6695
|
}
|
|
6676
6696
|
setClauses.push("updated_at = ?");
|
|
6677
6697
|
values.push((/* @__PURE__ */ new Date()).toISOString());
|
|
6678
6698
|
values.push(userId);
|
|
6679
6699
|
db.prepare(`UPDATE profiles SET ${setClauses.join(", ")} WHERE id = ?`).run(...values);
|
|
6680
6700
|
const profile = db.prepare("SELECT * FROM profiles WHERE id = ?").get(userId);
|
|
6681
|
-
return
|
|
6701
|
+
return c2.json({ data: sanitizeProfile(profile) });
|
|
6682
6702
|
});
|
|
6683
6703
|
routes_default = auth;
|
|
6684
6704
|
}
|
|
6685
6705
|
});
|
|
6686
6706
|
|
|
6687
6707
|
// src/lib/errors.ts
|
|
6688
|
-
function errorResponse(
|
|
6689
|
-
return
|
|
6708
|
+
function errorResponse(c2, statusCode, message, code) {
|
|
6709
|
+
return c2.json({ error: { message, code: code ?? "ERROR" } }, statusCode);
|
|
6690
6710
|
}
|
|
6691
|
-
function handleError(
|
|
6711
|
+
function handleError(c2, err) {
|
|
6692
6712
|
if (err instanceof AppError) {
|
|
6693
|
-
return errorResponse(
|
|
6713
|
+
return errorResponse(c2, err.statusCode, err.message, err.code);
|
|
6694
6714
|
}
|
|
6695
6715
|
console.error("Unhandled error:", err);
|
|
6696
|
-
return errorResponse(
|
|
6716
|
+
return errorResponse(c2, 500, "Internal server error", "INTERNAL_ERROR");
|
|
6697
6717
|
}
|
|
6698
6718
|
var AppError;
|
|
6699
6719
|
var init_errors = __esm({
|
|
@@ -6812,12 +6832,12 @@ function extractTaskReference(text2) {
|
|
|
6812
6832
|
taskNumber: parseInt(parts[1], 10)
|
|
6813
6833
|
};
|
|
6814
6834
|
}
|
|
6815
|
-
async function handlePullRequestEvent(
|
|
6835
|
+
async function handlePullRequestEvent(c2, payload) {
|
|
6816
6836
|
const action = payload.action;
|
|
6817
6837
|
const pr = payload.pull_request;
|
|
6818
6838
|
const repo = payload.repository;
|
|
6819
6839
|
if (!pr || !repo) {
|
|
6820
|
-
return
|
|
6840
|
+
return c2.json({ message: "Invalid payload" }, 200);
|
|
6821
6841
|
}
|
|
6822
6842
|
const repoOwner = repo.owner?.login;
|
|
6823
6843
|
const repoName = repo.name;
|
|
@@ -6841,7 +6861,7 @@ async function handlePullRequestEvent(c, payload) {
|
|
|
6841
6861
|
}
|
|
6842
6862
|
if (!projectId) {
|
|
6843
6863
|
log.debug("github", `No matching project for ${repoOwner}/${repoName}`);
|
|
6844
|
-
return
|
|
6864
|
+
return c2.json({ message: "No matching project found" }, 200);
|
|
6845
6865
|
}
|
|
6846
6866
|
const project = { id: projectId, prefix: projectPrefix };
|
|
6847
6867
|
const searchText = [pr.title || "", pr.body || "", pr.head?.ref || ""].join(" ");
|
|
@@ -6911,7 +6931,7 @@ async function handlePullRequestEvent(c, payload) {
|
|
|
6911
6931
|
);
|
|
6912
6932
|
} catch (err) {
|
|
6913
6933
|
log.error("github", "Failed to upsert pull request", err);
|
|
6914
|
-
return
|
|
6934
|
+
return c2.json({ message: "Failed to process webhook" }, 200);
|
|
6915
6935
|
}
|
|
6916
6936
|
log.db("pull_requests", "UPSERT", `PR #${pr.number} (${action}) for project ${project.id}`);
|
|
6917
6937
|
const branchRef = pr.head?.ref;
|
|
@@ -6979,25 +6999,25 @@ async function handlePullRequestEvent(c, payload) {
|
|
|
6979
6999
|
if (projectId) {
|
|
6980
7000
|
broadcastToProject(projectId, "task.updated", { taskId, changes: { pr_event: action } });
|
|
6981
7001
|
}
|
|
6982
|
-
return
|
|
7002
|
+
return c2.json({ message: "Webhook processed" }, 200);
|
|
6983
7003
|
}
|
|
6984
|
-
async function handleCheckRunEvent(
|
|
7004
|
+
async function handleCheckRunEvent(c2, payload) {
|
|
6985
7005
|
const action = payload.action;
|
|
6986
7006
|
const checkRun = payload.check_run;
|
|
6987
7007
|
const repo = payload.repository;
|
|
6988
7008
|
if (!checkRun || !repo || action !== "completed") {
|
|
6989
|
-
return
|
|
7009
|
+
return c2.json({ message: "Check run ignored" }, 200);
|
|
6990
7010
|
}
|
|
6991
7011
|
log.info("github", `Check run ${checkRun.name}: ${checkRun.conclusion} on ${repo.full_name}`);
|
|
6992
7012
|
const headSha = checkRun.head_sha;
|
|
6993
|
-
if (!headSha) return
|
|
7013
|
+
if (!headSha) return c2.json({ message: "No head_sha" }, 200);
|
|
6994
7014
|
const db = getSqlite();
|
|
6995
7015
|
const prRecord = db.prepare(
|
|
6996
7016
|
"SELECT task_id, project_id FROM pull_requests WHERE head_sha = ? LIMIT 1"
|
|
6997
7017
|
).get(headSha);
|
|
6998
7018
|
if (!prRecord?.task_id) {
|
|
6999
7019
|
log.debug("github", `No PR/task found for check run SHA ${headSha.substring(0, 7)}`);
|
|
7000
|
-
return
|
|
7020
|
+
return c2.json({ message: "No matching task" }, 200);
|
|
7001
7021
|
}
|
|
7002
7022
|
const taskId = prRecord.task_id;
|
|
7003
7023
|
const projectId = prRecord.project_id;
|
|
@@ -7005,11 +7025,11 @@ async function handleCheckRunEvent(c, payload) {
|
|
|
7005
7025
|
"SELECT id, ci_checks, merge_readiness FROM task_branches WHERE task_id = ?"
|
|
7006
7026
|
).all(taskId);
|
|
7007
7027
|
if (!taskBranches2 || taskBranches2.length === 0) {
|
|
7008
|
-
return
|
|
7028
|
+
return c2.json({ message: "No task branches" }, 200);
|
|
7009
7029
|
}
|
|
7010
7030
|
const tb = taskBranches2[0];
|
|
7011
7031
|
const ciChecks = tb.ci_checks ? typeof tb.ci_checks === "string" ? JSON.parse(tb.ci_checks) : tb.ci_checks : [];
|
|
7012
|
-
const existingIdx = ciChecks.findIndex((
|
|
7032
|
+
const existingIdx = ciChecks.findIndex((c3) => c3.name === checkRun.name);
|
|
7013
7033
|
const checkData = {
|
|
7014
7034
|
name: checkRun.name,
|
|
7015
7035
|
status: checkRun.status,
|
|
@@ -7025,7 +7045,7 @@ async function handleCheckRunEvent(c, payload) {
|
|
|
7025
7045
|
} else {
|
|
7026
7046
|
ciChecks.push(checkData);
|
|
7027
7047
|
}
|
|
7028
|
-
const allPassed = ciChecks.length > 0 && ciChecks.every((
|
|
7048
|
+
const allPassed = ciChecks.length > 0 && ciChecks.every((c3) => c3.conclusion === "success" || c3.conclusion === "neutral" || c3.conclusion === "skipped");
|
|
7029
7049
|
const existingMergeReadiness = tb.merge_readiness ? typeof tb.merge_readiness === "string" ? JSON.parse(tb.merge_readiness) : tb.merge_readiness : {};
|
|
7030
7050
|
db.prepare(
|
|
7031
7051
|
"UPDATE task_branches SET ci_checks = ?, merge_readiness = ? WHERE id = ?"
|
|
@@ -7055,17 +7075,17 @@ async function handleCheckRunEvent(c, payload) {
|
|
|
7055
7075
|
}).catch(() => {
|
|
7056
7076
|
});
|
|
7057
7077
|
}
|
|
7058
|
-
return
|
|
7078
|
+
return c2.json({ message: "Check run processed" }, 200);
|
|
7059
7079
|
}
|
|
7060
|
-
async function handleCheckSuiteEvent(
|
|
7080
|
+
async function handleCheckSuiteEvent(c2, payload) {
|
|
7061
7081
|
const action = payload.action;
|
|
7062
7082
|
const suite = payload.check_suite;
|
|
7063
7083
|
const repo = payload.repository;
|
|
7064
7084
|
if (!suite || !repo || action !== "completed") {
|
|
7065
|
-
return
|
|
7085
|
+
return c2.json({ message: "Check suite ignored" }, 200);
|
|
7066
7086
|
}
|
|
7067
7087
|
if (suite.conclusion !== "success") {
|
|
7068
|
-
return
|
|
7088
|
+
return c2.json({ message: "Suite not successful" }, 200);
|
|
7069
7089
|
}
|
|
7070
7090
|
log.info("github", `Check suite passed on ${repo.full_name}, branch: ${suite.head_branch}`);
|
|
7071
7091
|
const db = getSqlite();
|
|
@@ -7073,7 +7093,7 @@ async function handleCheckSuiteEvent(c, payload) {
|
|
|
7073
7093
|
"SELECT id, github_pr_number, task_id, project_id, is_draft FROM pull_requests WHERE head_sha = ? AND is_draft = 1 LIMIT 1"
|
|
7074
7094
|
).get(suite.head_sha);
|
|
7075
7095
|
if (!prRecord) {
|
|
7076
|
-
return
|
|
7096
|
+
return c2.json({ message: "No draft PR to convert" }, 200);
|
|
7077
7097
|
}
|
|
7078
7098
|
const repoOwner = repo.owner?.login;
|
|
7079
7099
|
const repoName = repo.name;
|
|
@@ -7107,22 +7127,22 @@ async function handleCheckSuiteEvent(c, payload) {
|
|
|
7107
7127
|
log.error("github", `Failed to mark PR #${prRecord.github_pr_number} as ready`, err);
|
|
7108
7128
|
}
|
|
7109
7129
|
}
|
|
7110
|
-
return
|
|
7130
|
+
return c2.json({ message: "Check suite processed" }, 200);
|
|
7111
7131
|
}
|
|
7112
|
-
async function handlePullRequestReviewEvent(
|
|
7132
|
+
async function handlePullRequestReviewEvent(c2, payload) {
|
|
7113
7133
|
const action = payload.action;
|
|
7114
7134
|
const review = payload.review;
|
|
7115
7135
|
const pr = payload.pull_request;
|
|
7116
7136
|
const repo = payload.repository;
|
|
7117
7137
|
if (!review || !pr || !repo || action !== "submitted") {
|
|
7118
|
-
return
|
|
7138
|
+
return c2.json({ message: "Review ignored" }, 200);
|
|
7119
7139
|
}
|
|
7120
7140
|
const reviewState = review.state;
|
|
7121
7141
|
const reviewer = review.user?.login;
|
|
7122
7142
|
const commitId = review.commit_id;
|
|
7123
7143
|
log.info("github", `Review by ${reviewer}: ${reviewState} on PR #${pr.number}`);
|
|
7124
7144
|
const branchRef = pr.head?.ref;
|
|
7125
|
-
if (!branchRef) return
|
|
7145
|
+
if (!branchRef) return c2.json({ message: "No branch ref" }, 200);
|
|
7126
7146
|
const db = getSqlite();
|
|
7127
7147
|
const taskBranch = db.prepare(
|
|
7128
7148
|
`SELECT tb.id, tb.task_id, tb.review_statuses, tb.merge_readiness, pr2.project_id
|
|
@@ -7132,7 +7152,7 @@ async function handlePullRequestReviewEvent(c, payload) {
|
|
|
7132
7152
|
).get(branchRef);
|
|
7133
7153
|
if (!taskBranch) {
|
|
7134
7154
|
log.debug("github", `No task_branch for branch ${branchRef}`);
|
|
7135
|
-
return
|
|
7155
|
+
return c2.json({ message: "No matching task branch" }, 200);
|
|
7136
7156
|
}
|
|
7137
7157
|
const taskId = taskBranch.task_id;
|
|
7138
7158
|
const projectId = taskBranch.project_id;
|
|
@@ -7172,15 +7192,15 @@ async function handlePullRequestReviewEvent(c, payload) {
|
|
|
7172
7192
|
}).catch(() => {
|
|
7173
7193
|
});
|
|
7174
7194
|
}
|
|
7175
|
-
return
|
|
7195
|
+
return c2.json({ message: "Review processed" }, 200);
|
|
7176
7196
|
}
|
|
7177
|
-
async function handlePushEvent(
|
|
7197
|
+
async function handlePushEvent(c2, payload) {
|
|
7178
7198
|
const ref = payload.ref;
|
|
7179
7199
|
const repo = payload.repository;
|
|
7180
7200
|
const commits = payload.commits || [];
|
|
7181
7201
|
const forced = payload.forced || false;
|
|
7182
7202
|
if (!ref || !repo) {
|
|
7183
|
-
return
|
|
7203
|
+
return c2.json({ message: "Invalid push payload" }, 200);
|
|
7184
7204
|
}
|
|
7185
7205
|
const branchName = ref.replace("refs/heads/", "");
|
|
7186
7206
|
const repoOwner = repo.owner?.login || repo.owner?.name;
|
|
@@ -7190,7 +7210,7 @@ async function handlePushEvent(c, payload) {
|
|
|
7190
7210
|
`Push to ${repo.full_name}/${branchName}: ${commits.length} commit(s)${forced ? " [FORCE PUSH]" : ""}`
|
|
7191
7211
|
);
|
|
7192
7212
|
if (commits.length === 0) {
|
|
7193
|
-
return
|
|
7213
|
+
return c2.json({ message: "Push with no commits" }, 200);
|
|
7194
7214
|
}
|
|
7195
7215
|
const db = getSqlite();
|
|
7196
7216
|
const taskBranch = db.prepare(
|
|
@@ -7201,7 +7221,7 @@ async function handlePushEvent(c, payload) {
|
|
|
7201
7221
|
).get(branchName);
|
|
7202
7222
|
if (!taskBranch) {
|
|
7203
7223
|
log.debug("github", `No task_branch found for branch ${branchName}`);
|
|
7204
|
-
return
|
|
7224
|
+
return c2.json({ message: "No matching task branch" }, 200);
|
|
7205
7225
|
}
|
|
7206
7226
|
const taskId = taskBranch.task_id;
|
|
7207
7227
|
const projectId = taskBranch.project_id;
|
|
@@ -7228,7 +7248,7 @@ async function handlePushEvent(c, payload) {
|
|
|
7228
7248
|
params.push(taskBranch.id);
|
|
7229
7249
|
db.prepare(updateSql).run(...params);
|
|
7230
7250
|
log.db("task_branches", "UPDATE", taskBranch.id);
|
|
7231
|
-
const commitMessages = commits.map((
|
|
7251
|
+
const commitMessages = commits.map((c3) => c3.message?.split("\n")[0] || "").join(", ");
|
|
7232
7252
|
createTaskActivity({
|
|
7233
7253
|
task_id: taskId,
|
|
7234
7254
|
project_id: projectId,
|
|
@@ -7238,11 +7258,11 @@ async function handlePushEvent(c, payload) {
|
|
|
7238
7258
|
branch_name: branchName,
|
|
7239
7259
|
commit_count: commits.length,
|
|
7240
7260
|
forced,
|
|
7241
|
-
commits: commits.slice(0, 10).map((
|
|
7242
|
-
sha:
|
|
7243
|
-
message:
|
|
7244
|
-
author:
|
|
7245
|
-
timestamp:
|
|
7261
|
+
commits: commits.slice(0, 10).map((c3) => ({
|
|
7262
|
+
sha: c3.id?.substring(0, 7),
|
|
7263
|
+
message: c3.message?.split("\n")[0],
|
|
7264
|
+
author: c3.author?.username || c3.author?.name,
|
|
7265
|
+
timestamp: c3.timestamp
|
|
7246
7266
|
})),
|
|
7247
7267
|
files_touched: Array.from(allFilesTouched).slice(0, 20)
|
|
7248
7268
|
}
|
|
@@ -7283,13 +7303,13 @@ async function handlePushEvent(c, payload) {
|
|
|
7283
7303
|
}, true).catch(() => {
|
|
7284
7304
|
});
|
|
7285
7305
|
}
|
|
7286
|
-
return
|
|
7306
|
+
return c2.json({ message: "Push processed" }, 200);
|
|
7287
7307
|
}
|
|
7288
|
-
async function handleWorkflowJobEvent(
|
|
7308
|
+
async function handleWorkflowJobEvent(c2, payload) {
|
|
7289
7309
|
const action = payload.action;
|
|
7290
7310
|
const job = payload.workflow_job;
|
|
7291
7311
|
const repo = payload.repository;
|
|
7292
|
-
if (!job || !repo) return
|
|
7312
|
+
if (!job || !repo) return c2.json({ message: "Invalid workflow_job" }, 200);
|
|
7293
7313
|
const steps = (job.steps || []).map((s) => ({
|
|
7294
7314
|
name: s.name,
|
|
7295
7315
|
status: s.status,
|
|
@@ -7298,7 +7318,7 @@ async function handleWorkflowJobEvent(c, payload) {
|
|
|
7298
7318
|
completed_at: s.completed_at
|
|
7299
7319
|
}));
|
|
7300
7320
|
const headBranch = job.head_branch;
|
|
7301
|
-
if (!headBranch) return
|
|
7321
|
+
if (!headBranch) return c2.json({ message: "No head branch" }, 200);
|
|
7302
7322
|
const db = getSqlite();
|
|
7303
7323
|
const taskBranch = db.prepare(
|
|
7304
7324
|
`SELECT tb.id, tb.task_id, pr2.project_id
|
|
@@ -7306,7 +7326,7 @@ async function handleWorkflowJobEvent(c, payload) {
|
|
|
7306
7326
|
LEFT JOIN project_repos pr2 ON pr2.id = tb.repo_id
|
|
7307
7327
|
WHERE tb.branch_name = ? LIMIT 1`
|
|
7308
7328
|
).get(headBranch);
|
|
7309
|
-
if (!taskBranch) return
|
|
7329
|
+
if (!taskBranch) return c2.json({ message: "No matching task branch" }, 200);
|
|
7310
7330
|
const taskId = taskBranch.task_id;
|
|
7311
7331
|
const projectId = taskBranch.project_id;
|
|
7312
7332
|
const deployStatus = action === "completed" ? job.conclusion === "success" ? "success" : "failure" : "in_progress";
|
|
@@ -7326,14 +7346,14 @@ async function handleWorkflowJobEvent(c, payload) {
|
|
|
7326
7346
|
if (projectId) {
|
|
7327
7347
|
broadcastToProject(projectId, "task.updated", { taskId, changes: { deploy_steps: steps } });
|
|
7328
7348
|
}
|
|
7329
|
-
return
|
|
7349
|
+
return c2.json({ message: "Workflow job processed" }, 200);
|
|
7330
7350
|
}
|
|
7331
|
-
async function handleDeploymentStatusEvent(
|
|
7351
|
+
async function handleDeploymentStatusEvent(c2, payload) {
|
|
7332
7352
|
const deploymentStatus = payload.deployment_status;
|
|
7333
7353
|
const deployment = payload.deployment;
|
|
7334
7354
|
const repo = payload.repository;
|
|
7335
7355
|
if (!deploymentStatus || !deployment || !repo) {
|
|
7336
|
-
return
|
|
7356
|
+
return c2.json({ message: "Invalid deployment_status" }, 200);
|
|
7337
7357
|
}
|
|
7338
7358
|
const environment = deployment.environment;
|
|
7339
7359
|
const state = deploymentStatus.state;
|
|
@@ -7350,7 +7370,7 @@ async function handleDeploymentStatusEvent(c, payload) {
|
|
|
7350
7370
|
).get(ref);
|
|
7351
7371
|
if (!taskBranch) {
|
|
7352
7372
|
log.debug("github", `No task_branch for deployment ref ${ref}`);
|
|
7353
|
-
return
|
|
7373
|
+
return c2.json({ message: "No matching task branch" }, 200);
|
|
7354
7374
|
}
|
|
7355
7375
|
const taskId = taskBranch.task_id;
|
|
7356
7376
|
const projectId = taskBranch.project_id;
|
|
@@ -7426,17 +7446,17 @@ async function handleDeploymentStatusEvent(c, payload) {
|
|
|
7426
7446
|
}
|
|
7427
7447
|
broadcastToProject(projectId, "task.updated", { taskId, changes: { deploy: { environment, state } } });
|
|
7428
7448
|
}
|
|
7429
|
-
return
|
|
7449
|
+
return c2.json({ message: "Deployment status processed" }, 200);
|
|
7430
7450
|
}
|
|
7431
|
-
async function handleDeploymentProtectionRuleEvent(
|
|
7451
|
+
async function handleDeploymentProtectionRuleEvent(c2, payload) {
|
|
7432
7452
|
const action = payload.action;
|
|
7433
7453
|
const environment = payload.environment;
|
|
7434
7454
|
const deployment = payload.deployment;
|
|
7435
7455
|
const repo = payload.repository;
|
|
7436
|
-
if (!environment || !repo) return
|
|
7456
|
+
if (!environment || !repo) return c2.json({ message: "Invalid protection rule" }, 200);
|
|
7437
7457
|
log.info("github", `Deploy protection: ${action} for ${environment} on ${repo.full_name}`);
|
|
7438
7458
|
const ref = deployment?.ref;
|
|
7439
|
-
if (!ref) return
|
|
7459
|
+
if (!ref) return c2.json({ message: "No ref" }, 200);
|
|
7440
7460
|
const db = getSqlite();
|
|
7441
7461
|
const taskBranch = db.prepare(
|
|
7442
7462
|
`SELECT tb.id, tb.task_id, pr2.project_id
|
|
@@ -7444,7 +7464,7 @@ async function handleDeploymentProtectionRuleEvent(c, payload) {
|
|
|
7444
7464
|
LEFT JOIN project_repos pr2 ON pr2.id = tb.repo_id
|
|
7445
7465
|
WHERE tb.branch_name = ? LIMIT 1`
|
|
7446
7466
|
).get(ref);
|
|
7447
|
-
if (!taskBranch) return
|
|
7467
|
+
if (!taskBranch) return c2.json({ message: "No matching task branch" }, 200);
|
|
7448
7468
|
const taskId = taskBranch.task_id;
|
|
7449
7469
|
const projectId = taskBranch.project_id;
|
|
7450
7470
|
const reviewers = (payload.reviewers || []).map((r) => r.reviewer?.login).filter(Boolean);
|
|
@@ -7467,29 +7487,29 @@ async function handleDeploymentProtectionRuleEvent(c, payload) {
|
|
|
7467
7487
|
if (projectId) {
|
|
7468
7488
|
broadcastToProject(projectId, "task.updated", { taskId, changes: { deploy_approval: approvals } });
|
|
7469
7489
|
}
|
|
7470
|
-
return
|
|
7490
|
+
return c2.json({ message: "Protection rule processed" }, 200);
|
|
7471
7491
|
}
|
|
7472
|
-
async function handleIssueCommentEvent(
|
|
7492
|
+
async function handleIssueCommentEvent(c2, payload) {
|
|
7473
7493
|
const action = payload.action;
|
|
7474
7494
|
const comment = payload.comment;
|
|
7475
7495
|
const issue = payload.issue;
|
|
7476
7496
|
const repo = payload.repository;
|
|
7477
7497
|
if (action !== "created" || !comment || !issue || !repo) {
|
|
7478
|
-
return
|
|
7498
|
+
return c2.json({ message: "Issue comment ignored" }, 200);
|
|
7479
7499
|
}
|
|
7480
|
-
if (!issue.pull_request) return
|
|
7500
|
+
if (!issue.pull_request) return c2.json({ message: "Not a PR comment" }, 200);
|
|
7481
7501
|
const prNumber = issue.number;
|
|
7482
7502
|
const repoOwner = repo.owner?.login;
|
|
7483
7503
|
const repoName = repo.name;
|
|
7484
7504
|
const author = comment.user?.login;
|
|
7485
7505
|
const body = comment.body;
|
|
7486
|
-
if (comment.performed_via_github_app) return
|
|
7506
|
+
if (comment.performed_via_github_app) return c2.json({ message: "Bot comment skipped" }, 200);
|
|
7487
7507
|
log.info("github", `PR comment by ${author} on #${prNumber} in ${repoOwner}/${repoName}`);
|
|
7488
7508
|
const db = getSqlite();
|
|
7489
7509
|
const prRecord = db.prepare(
|
|
7490
7510
|
"SELECT task_id, project_id FROM pull_requests WHERE github_pr_number = ? LIMIT 1"
|
|
7491
7511
|
).get(prNumber);
|
|
7492
|
-
if (!prRecord?.task_id) return
|
|
7512
|
+
if (!prRecord?.task_id) return c2.json({ message: "No linked task" }, 200);
|
|
7493
7513
|
const commentId = `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
7494
7514
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7495
7515
|
db.prepare(
|
|
@@ -7511,7 +7531,7 @@ ${body}`,
|
|
|
7511
7531
|
task_id: prRecord.task_id
|
|
7512
7532
|
});
|
|
7513
7533
|
}
|
|
7514
|
-
return
|
|
7534
|
+
return c2.json({ message: "Issue comment processed" }, 200);
|
|
7515
7535
|
}
|
|
7516
7536
|
var import_hono3, import_zod6, github, deliveryCache, DELIVERY_TTL_MS, connectRepoSchema2, github_default;
|
|
7517
7537
|
var init_github4 = __esm({
|
|
@@ -7542,13 +7562,13 @@ var init_github4 = __esm({
|
|
|
7542
7562
|
github_repo_owner: import_zod6.z.string().min(1),
|
|
7543
7563
|
github_repo_name: import_zod6.z.string().min(1)
|
|
7544
7564
|
});
|
|
7545
|
-
github.patch("/projects/:projectId/github", authMiddleware, async (
|
|
7565
|
+
github.patch("/projects/:projectId/github", authMiddleware, async (c2) => {
|
|
7546
7566
|
try {
|
|
7547
|
-
const projectId =
|
|
7548
|
-
const body = await
|
|
7567
|
+
const projectId = c2.req.param("projectId");
|
|
7568
|
+
const body = await c2.req.json();
|
|
7549
7569
|
const parsed = connectRepoSchema2.safeParse(body);
|
|
7550
7570
|
if (!parsed.success) {
|
|
7551
|
-
return errorResponse(
|
|
7571
|
+
return errorResponse(c2, 400, parsed.error.issues[0].message, "VALIDATION_ERROR");
|
|
7552
7572
|
}
|
|
7553
7573
|
const db = getSqlite();
|
|
7554
7574
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -7565,83 +7585,83 @@ var init_github4 = __esm({
|
|
|
7565
7585
|
);
|
|
7566
7586
|
const project = db.prepare("SELECT * FROM projects WHERE id = ?").get(projectId);
|
|
7567
7587
|
if (!project) {
|
|
7568
|
-
return errorResponse(
|
|
7588
|
+
return errorResponse(c2, 404, "Project not found", "NOT_FOUND");
|
|
7569
7589
|
}
|
|
7570
|
-
return
|
|
7590
|
+
return c2.json({ data: project });
|
|
7571
7591
|
} catch (err) {
|
|
7572
|
-
return handleError(
|
|
7592
|
+
return handleError(c2, err);
|
|
7573
7593
|
}
|
|
7574
7594
|
});
|
|
7575
|
-
github.get("/projects/:projectId/pull-requests", authMiddleware, async (
|
|
7595
|
+
github.get("/projects/:projectId/pull-requests", authMiddleware, async (c2) => {
|
|
7576
7596
|
try {
|
|
7577
|
-
const projectId =
|
|
7578
|
-
const taskId =
|
|
7597
|
+
const projectId = c2.req.param("projectId");
|
|
7598
|
+
const taskId = c2.req.query("task_id");
|
|
7579
7599
|
const data = getPullRequests(projectId, taskId);
|
|
7580
|
-
return
|
|
7600
|
+
return c2.json({ data: data || [] });
|
|
7581
7601
|
} catch (err) {
|
|
7582
|
-
return handleError(
|
|
7602
|
+
return handleError(c2, err);
|
|
7583
7603
|
}
|
|
7584
7604
|
});
|
|
7585
|
-
github.get("/tasks/:taskId/pull-requests", authMiddleware, async (
|
|
7605
|
+
github.get("/tasks/:taskId/pull-requests", authMiddleware, async (c2) => {
|
|
7586
7606
|
try {
|
|
7587
|
-
const taskId =
|
|
7607
|
+
const taskId = c2.req.param("taskId");
|
|
7588
7608
|
const data = getTaskPullRequests(taskId);
|
|
7589
|
-
return
|
|
7609
|
+
return c2.json({ data: data || [] });
|
|
7590
7610
|
} catch (err) {
|
|
7591
|
-
return handleError(
|
|
7611
|
+
return handleError(c2, err);
|
|
7592
7612
|
}
|
|
7593
7613
|
});
|
|
7594
|
-
github.post("/github/webhook", async (
|
|
7614
|
+
github.post("/github/webhook", async (c2) => {
|
|
7595
7615
|
try {
|
|
7596
|
-
const rawBody = await
|
|
7597
|
-
const signatureHeader =
|
|
7616
|
+
const rawBody = await c2.req.text();
|
|
7617
|
+
const signatureHeader = c2.req.header("x-hub-signature-256");
|
|
7598
7618
|
if (!verifyWebhookSignature(rawBody, signatureHeader)) {
|
|
7599
7619
|
log.warn("github", "Webhook signature verification failed");
|
|
7600
|
-
return
|
|
7620
|
+
return c2.json({ error: "Invalid signature" }, 401);
|
|
7601
7621
|
}
|
|
7602
7622
|
const payload = JSON.parse(rawBody);
|
|
7603
|
-
const event =
|
|
7604
|
-
const deliveryId =
|
|
7623
|
+
const event = c2.req.header("x-github-event");
|
|
7624
|
+
const deliveryId = c2.req.header("x-github-delivery");
|
|
7605
7625
|
log.info("github", `Webhook received: ${event} (delivery: ${deliveryId})`);
|
|
7606
7626
|
if (event === "ping") {
|
|
7607
|
-
return
|
|
7627
|
+
return c2.json({ message: "pong" }, 200);
|
|
7608
7628
|
}
|
|
7609
7629
|
if (isDeliveryDuplicate(deliveryId)) {
|
|
7610
7630
|
log.debug("github", `Skipping duplicate delivery: ${deliveryId}`);
|
|
7611
|
-
return
|
|
7631
|
+
return c2.json({ message: "Duplicate delivery ignored" }, 200);
|
|
7612
7632
|
}
|
|
7613
7633
|
if (event === "pull_request") {
|
|
7614
|
-
return await handlePullRequestEvent(
|
|
7634
|
+
return await handlePullRequestEvent(c2, payload);
|
|
7615
7635
|
}
|
|
7616
7636
|
if (event === "push") {
|
|
7617
|
-
return await handlePushEvent(
|
|
7637
|
+
return await handlePushEvent(c2, payload);
|
|
7618
7638
|
}
|
|
7619
7639
|
if (event === "check_run") {
|
|
7620
|
-
return await handleCheckRunEvent(
|
|
7640
|
+
return await handleCheckRunEvent(c2, payload);
|
|
7621
7641
|
}
|
|
7622
7642
|
if (event === "check_suite") {
|
|
7623
|
-
return await handleCheckSuiteEvent(
|
|
7643
|
+
return await handleCheckSuiteEvent(c2, payload);
|
|
7624
7644
|
}
|
|
7625
7645
|
if (event === "pull_request_review") {
|
|
7626
|
-
return await handlePullRequestReviewEvent(
|
|
7646
|
+
return await handlePullRequestReviewEvent(c2, payload);
|
|
7627
7647
|
}
|
|
7628
7648
|
if (event === "workflow_job") {
|
|
7629
|
-
return await handleWorkflowJobEvent(
|
|
7649
|
+
return await handleWorkflowJobEvent(c2, payload);
|
|
7630
7650
|
}
|
|
7631
7651
|
if (event === "deployment_status") {
|
|
7632
|
-
return await handleDeploymentStatusEvent(
|
|
7652
|
+
return await handleDeploymentStatusEvent(c2, payload);
|
|
7633
7653
|
}
|
|
7634
7654
|
if (event === "deployment_protection_rule") {
|
|
7635
|
-
return await handleDeploymentProtectionRuleEvent(
|
|
7655
|
+
return await handleDeploymentProtectionRuleEvent(c2, payload);
|
|
7636
7656
|
}
|
|
7637
7657
|
if (event === "issue_comment") {
|
|
7638
|
-
return await handleIssueCommentEvent(
|
|
7658
|
+
return await handleIssueCommentEvent(c2, payload);
|
|
7639
7659
|
}
|
|
7640
7660
|
log.debug("github", `Ignoring event: ${event}`);
|
|
7641
|
-
return
|
|
7661
|
+
return c2.json({ message: "Event ignored" }, 200);
|
|
7642
7662
|
} catch (err) {
|
|
7643
7663
|
log.error("github", "Webhook processing error", err);
|
|
7644
|
-
return
|
|
7664
|
+
return c2.json({ message: "Webhook error" }, 200);
|
|
7645
7665
|
}
|
|
7646
7666
|
});
|
|
7647
7667
|
github_default = github;
|
|
@@ -7719,7 +7739,7 @@ var init_index = __esm({
|
|
|
7719
7739
|
}
|
|
7720
7740
|
app.get(
|
|
7721
7741
|
"/health",
|
|
7722
|
-
(
|
|
7742
|
+
(c2) => c2.json({
|
|
7723
7743
|
status: "ok",
|
|
7724
7744
|
service: "closeclaw",
|
|
7725
7745
|
connections: getConnectionCount(),
|
|
@@ -7730,31 +7750,31 @@ var init_index = __esm({
|
|
|
7730
7750
|
app.route("/api", github_default);
|
|
7731
7751
|
app.route("", agentBridge);
|
|
7732
7752
|
attachmentRoutes = new import_hono4.Hono();
|
|
7733
|
-
attachmentRoutes.post("/api/attachments/upload", authMiddleware, async (
|
|
7734
|
-
const body = await
|
|
7753
|
+
attachmentRoutes.post("/api/attachments/upload", authMiddleware, async (c2) => {
|
|
7754
|
+
const body = await c2.req.parseBody();
|
|
7735
7755
|
const file = body["file"];
|
|
7736
7756
|
const taskId = body["task_id"];
|
|
7737
7757
|
if (!taskId || !file || typeof file === "string") {
|
|
7738
|
-
return
|
|
7758
|
+
return c2.json({ error: "task_id and file required" }, 400);
|
|
7739
7759
|
}
|
|
7740
7760
|
const arrayBuffer = await file.arrayBuffer();
|
|
7741
7761
|
const buffer = Buffer.from(arrayBuffer);
|
|
7742
7762
|
const { filePath, fileSize } = await saveFile(taskId, file.name, buffer);
|
|
7743
7763
|
const attachment = createAttachment({
|
|
7744
7764
|
task_id: taskId,
|
|
7745
|
-
uploaded_by:
|
|
7765
|
+
uploaded_by: c2.get("userId"),
|
|
7746
7766
|
file_name: file.name,
|
|
7747
7767
|
file_path: filePath,
|
|
7748
7768
|
file_type: file.type || void 0,
|
|
7749
7769
|
file_size: fileSize
|
|
7750
7770
|
});
|
|
7751
|
-
return
|
|
7771
|
+
return c2.json({ ok: true, data: attachment }, 201);
|
|
7752
7772
|
});
|
|
7753
|
-
attachmentRoutes.get("/api/attachments/:taskId/:filename", authMiddleware, (
|
|
7754
|
-
const taskId =
|
|
7755
|
-
const filename =
|
|
7773
|
+
attachmentRoutes.get("/api/attachments/:taskId/:filename", authMiddleware, (c2) => {
|
|
7774
|
+
const taskId = c2.req.param("taskId");
|
|
7775
|
+
const filename = c2.req.param("filename");
|
|
7756
7776
|
const fullPath = getFilePath(`${taskId}/${filename}`);
|
|
7757
|
-
if (!fullPath) return
|
|
7777
|
+
if (!fullPath) return c2.json({ error: "File not found" }, 404);
|
|
7758
7778
|
const stats = (0, import_node_fs6.statSync)(fullPath);
|
|
7759
7779
|
const stream = (0, import_node_fs6.createReadStream)(fullPath);
|
|
7760
7780
|
return new Response(import_node_stream.Readable.toWeb(stream), {
|
|
@@ -7801,19 +7821,43 @@ var import_node_fs7 = require("fs");
|
|
|
7801
7821
|
var import_node_path6 = require("path");
|
|
7802
7822
|
var import_node_child_process3 = require("child_process");
|
|
7803
7823
|
var import_node_readline = require("readline");
|
|
7824
|
+
var c = {
|
|
7825
|
+
reset: "\x1B[0m",
|
|
7826
|
+
bold: "\x1B[1m",
|
|
7827
|
+
dim: "\x1B[2m",
|
|
7828
|
+
green: "\x1B[32m",
|
|
7829
|
+
red: "\x1B[31m",
|
|
7830
|
+
yellow: "\x1B[33m",
|
|
7831
|
+
cyan: "\x1B[36m",
|
|
7832
|
+
magenta: "\x1B[35m",
|
|
7833
|
+
white: "\x1B[37m"
|
|
7834
|
+
};
|
|
7835
|
+
var PASS = `${c.green}PASS${c.reset}`;
|
|
7836
|
+
var FAIL = `${c.red}FAIL${c.reset}`;
|
|
7837
|
+
var WARN = `${c.yellow}WARN${c.reset}`;
|
|
7838
|
+
var SKIP = `${c.dim}SKIP${c.reset}`;
|
|
7839
|
+
var INFO = `${c.cyan}INFO${c.reset}`;
|
|
7840
|
+
function step(num, total, label) {
|
|
7841
|
+
console.log(`
|
|
7842
|
+
${c.cyan}[${num}/${total}]${c.reset} ${c.bold}${label}${c.reset}`);
|
|
7843
|
+
}
|
|
7844
|
+
function result(status, detail) {
|
|
7845
|
+
console.log(` ${status} ${detail}`);
|
|
7846
|
+
}
|
|
7804
7847
|
var HOME = process.env.HOME || process.env.USERPROFILE || "~";
|
|
7805
7848
|
var PID_FILE = (0, import_node_path6.join)(HOME, ".closeclaw", "platform.pid");
|
|
7849
|
+
var LICENSE_FILE = (0, import_node_path6.join)(HOME, ".closeclaw", "license.key");
|
|
7850
|
+
var CONFIG_DIR = (0, import_node_path6.join)(HOME, ".closeclaw");
|
|
7851
|
+
var DATA_DIR = (0, import_node_path6.join)(HOME, ".closeclaw", "data");
|
|
7806
7852
|
function readPkgVersion() {
|
|
7807
7853
|
try {
|
|
7808
7854
|
const candidates = [
|
|
7809
7855
|
(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")
|
|
7856
|
+
(0, import_node_path6.join)(__dirname, "package.json")
|
|
7812
7857
|
];
|
|
7813
7858
|
for (const p of candidates) {
|
|
7814
7859
|
if ((0, import_node_fs7.existsSync)(p)) {
|
|
7815
|
-
|
|
7816
|
-
return pkg.version ?? "unknown";
|
|
7860
|
+
return JSON.parse((0, import_node_fs7.readFileSync)(p, "utf-8")).version ?? "unknown";
|
|
7817
7861
|
}
|
|
7818
7862
|
}
|
|
7819
7863
|
return "unknown";
|
|
@@ -7822,16 +7866,12 @@ function readPkgVersion() {
|
|
|
7822
7866
|
}
|
|
7823
7867
|
}
|
|
7824
7868
|
function writePid() {
|
|
7825
|
-
|
|
7826
|
-
if (!(0, import_node_fs7.existsSync)(dir)) {
|
|
7827
|
-
(0, import_node_fs7.mkdirSync)(dir, { recursive: true });
|
|
7828
|
-
}
|
|
7869
|
+
(0, import_node_fs7.mkdirSync)(CONFIG_DIR, { recursive: true });
|
|
7829
7870
|
(0, import_node_fs7.writeFileSync)(PID_FILE, String(process.pid), "utf-8");
|
|
7830
7871
|
}
|
|
7831
7872
|
function readPid() {
|
|
7832
7873
|
try {
|
|
7833
|
-
const
|
|
7834
|
-
const pid = Number(raw);
|
|
7874
|
+
const pid = Number((0, import_node_fs7.readFileSync)(PID_FILE, "utf-8").trim());
|
|
7835
7875
|
return Number.isFinite(pid) ? pid : null;
|
|
7836
7876
|
} catch {
|
|
7837
7877
|
return null;
|
|
@@ -7845,14 +7885,6 @@ function isProcessAlive(pid) {
|
|
|
7845
7885
|
return false;
|
|
7846
7886
|
}
|
|
7847
7887
|
}
|
|
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
7888
|
function whichSync(bin) {
|
|
7857
7889
|
try {
|
|
7858
7890
|
return (0, import_node_child_process3.execSync)(`which ${bin}`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim() || null;
|
|
@@ -7860,187 +7892,346 @@ function whichSync(bin) {
|
|
|
7860
7892
|
return null;
|
|
7861
7893
|
}
|
|
7862
7894
|
}
|
|
7895
|
+
function ask(question) {
|
|
7896
|
+
const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
7897
|
+
return new Promise((resolve) => {
|
|
7898
|
+
rl.question(question, (answer) => {
|
|
7899
|
+
rl.close();
|
|
7900
|
+
resolve(answer.trim());
|
|
7901
|
+
});
|
|
7902
|
+
});
|
|
7903
|
+
}
|
|
7863
7904
|
function promptPassword(prompt) {
|
|
7864
7905
|
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 {
|
|
7906
|
+
if (!process.stdin.isTTY) {
|
|
7907
|
+
const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
7897
7908
|
rl.question(prompt, (answer) => {
|
|
7898
7909
|
rl.close();
|
|
7899
7910
|
resolve(answer);
|
|
7900
7911
|
});
|
|
7912
|
+
return;
|
|
7901
7913
|
}
|
|
7914
|
+
process.stdout.write(prompt);
|
|
7915
|
+
const raw = process.stdin;
|
|
7916
|
+
raw.setRawMode?.(true);
|
|
7917
|
+
raw.resume();
|
|
7918
|
+
raw.setEncoding("utf-8");
|
|
7919
|
+
let password = "";
|
|
7920
|
+
const onData = (ch) => {
|
|
7921
|
+
if (ch === "\n" || ch === "\r" || ch === "") {
|
|
7922
|
+
raw.setRawMode?.(false);
|
|
7923
|
+
raw.pause();
|
|
7924
|
+
raw.removeListener("data", onData);
|
|
7925
|
+
process.stdout.write("\n");
|
|
7926
|
+
resolve(password);
|
|
7927
|
+
} else if (ch === "") {
|
|
7928
|
+
raw.setRawMode?.(false);
|
|
7929
|
+
reject(new Error("Aborted"));
|
|
7930
|
+
} else if (ch === "\x7F" || ch === "\b") {
|
|
7931
|
+
if (password.length > 0) {
|
|
7932
|
+
password = password.slice(0, -1);
|
|
7933
|
+
process.stdout.write("\b \b");
|
|
7934
|
+
}
|
|
7935
|
+
} else {
|
|
7936
|
+
password += ch;
|
|
7937
|
+
process.stdout.write("*");
|
|
7938
|
+
}
|
|
7939
|
+
};
|
|
7940
|
+
raw.on("data", onData);
|
|
7902
7941
|
});
|
|
7903
7942
|
}
|
|
7943
|
+
function getSavedLicenseKey() {
|
|
7944
|
+
try {
|
|
7945
|
+
return (0, import_node_fs7.readFileSync)(LICENSE_FILE, "utf-8").trim() || null;
|
|
7946
|
+
} catch {
|
|
7947
|
+
return null;
|
|
7948
|
+
}
|
|
7949
|
+
}
|
|
7950
|
+
function saveLicenseKey(key) {
|
|
7951
|
+
(0, import_node_fs7.mkdirSync)(CONFIG_DIR, { recursive: true });
|
|
7952
|
+
(0, import_node_fs7.writeFileSync)(LICENSE_FILE, key, { mode: 384 });
|
|
7953
|
+
}
|
|
7954
|
+
async function commandOnboard() {
|
|
7955
|
+
const version = readPkgVersion();
|
|
7956
|
+
const TOTAL_STEPS = 7;
|
|
7957
|
+
console.log("");
|
|
7958
|
+
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}`);
|
|
7959
|
+
console.log(` ${c.cyan}${c.bold}\u2551 CloseClaw \u2014 Setup Wizard \u2551${c.reset}`);
|
|
7960
|
+
console.log(` ${c.cyan}${c.bold}\u2551 v${version.padEnd(28)}\u2551${c.reset}`);
|
|
7961
|
+
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}`);
|
|
7962
|
+
step(1, TOTAL_STEPS, "License Key");
|
|
7963
|
+
let licenseKey = getSavedLicenseKey();
|
|
7964
|
+
if (licenseKey) {
|
|
7965
|
+
result(PASS, `Found saved key at ${c.dim}~/.closeclaw/license.key${c.reset}`);
|
|
7966
|
+
result(INFO, `Key: ${c.dim}${licenseKey.substring(0, 30)}...${c.reset}`);
|
|
7967
|
+
} else {
|
|
7968
|
+
result(INFO, "No license key found.");
|
|
7969
|
+
const key = await ask(` Enter your license key (or 'dev' for development mode): `);
|
|
7970
|
+
if (key === "dev" || key === "development") {
|
|
7971
|
+
process.env.NODE_ENV = "development";
|
|
7972
|
+
result(WARN, "Development mode \u2014 no license required");
|
|
7973
|
+
} else if (key) {
|
|
7974
|
+
licenseKey = key;
|
|
7975
|
+
saveLicenseKey(key);
|
|
7976
|
+
result(PASS, `License key saved to ${c.dim}~/.closeclaw/license.key${c.reset}`);
|
|
7977
|
+
} else {
|
|
7978
|
+
result(FAIL, "No key provided. Get one from your administrator.");
|
|
7979
|
+
process.exit(1);
|
|
7980
|
+
}
|
|
7981
|
+
}
|
|
7982
|
+
if (licenseKey && process.env.NODE_ENV !== "development") {
|
|
7983
|
+
try {
|
|
7984
|
+
const licenseMod = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
7985
|
+
const verify = licenseMod.verifyLicense || licenseMod.default;
|
|
7986
|
+
if (typeof verify === "function") {
|
|
7987
|
+
const payload = verify(licenseKey);
|
|
7988
|
+
if (payload) {
|
|
7989
|
+
result(PASS, `Licensed to: ${c.bold}${payload.sub}${c.reset}`);
|
|
7990
|
+
result(INFO, `Tier: ${payload.tier} | Seats: ${payload.seats} | Expires: ${new Date(payload.exp * 1e3).toLocaleDateString()}`);
|
|
7991
|
+
}
|
|
7992
|
+
}
|
|
7993
|
+
} catch (err) {
|
|
7994
|
+
result(FAIL, `Invalid license key: ${err.message}`);
|
|
7995
|
+
result(INFO, "Enter 'dev' to use development mode, or get a valid key.");
|
|
7996
|
+
process.exit(1);
|
|
7997
|
+
}
|
|
7998
|
+
}
|
|
7999
|
+
step(2, TOTAL_STEPS, "Node.js");
|
|
8000
|
+
const nodeVersion = process.versions.node;
|
|
8001
|
+
const [major] = nodeVersion.split(".").map(Number);
|
|
8002
|
+
if (major >= 22) {
|
|
8003
|
+
result(PASS, `Node.js ${c.bold}v${nodeVersion}${c.reset}`);
|
|
8004
|
+
} else {
|
|
8005
|
+
result(WARN, `Node.js v${nodeVersion} \u2014 ${c.yellow}v22+ recommended${c.reset}`);
|
|
8006
|
+
}
|
|
8007
|
+
step(3, TOTAL_STEPS, "Data Directory");
|
|
8008
|
+
const dataDir = process.env.PLATFORM_DATA_DIR || DATA_DIR;
|
|
8009
|
+
if ((0, import_node_fs7.existsSync)(dataDir)) {
|
|
8010
|
+
const dbExists = (0, import_node_fs7.existsSync)((0, import_node_path6.join)(dataDir, "platform.db"));
|
|
8011
|
+
result(PASS, `${c.dim}${dataDir}${c.reset}`);
|
|
8012
|
+
if (dbExists) {
|
|
8013
|
+
const size = (0, import_node_fs7.statSync)((0, import_node_path6.join)(dataDir, "platform.db")).size;
|
|
8014
|
+
result(INFO, `Database exists (${(size / 1024).toFixed(0)} KB)`);
|
|
8015
|
+
} else {
|
|
8016
|
+
result(INFO, "Fresh install \u2014 database will be created on first start");
|
|
8017
|
+
}
|
|
8018
|
+
} else {
|
|
8019
|
+
(0, import_node_fs7.mkdirSync)(dataDir, { recursive: true });
|
|
8020
|
+
result(PASS, `Created ${c.dim}${dataDir}${c.reset}`);
|
|
8021
|
+
}
|
|
8022
|
+
step(4, TOTAL_STEPS, "Database");
|
|
8023
|
+
try {
|
|
8024
|
+
const { initDatabase: initDatabase2 } = await Promise.resolve().then(() => (init_connection(), connection_exports));
|
|
8025
|
+
initDatabase2(dataDir);
|
|
8026
|
+
result(PASS, "SQLite initialized (WAL mode)");
|
|
8027
|
+
const { runMigrations: runMigrations2 } = await Promise.resolve().then(() => (init_migrate(), migrate_exports));
|
|
8028
|
+
runMigrations2();
|
|
8029
|
+
result(PASS, "Schema up to date");
|
|
8030
|
+
const { countProfiles: countProfiles2 } = await Promise.resolve().then(() => (init_profiles(), profiles_exports));
|
|
8031
|
+
const userCount = countProfiles2();
|
|
8032
|
+
if (userCount === 0) {
|
|
8033
|
+
result(INFO, "No users yet \u2014 first signup will be admin");
|
|
8034
|
+
} else {
|
|
8035
|
+
result(INFO, `${userCount} user${userCount > 1 ? "s" : ""} registered`);
|
|
8036
|
+
}
|
|
8037
|
+
const { closeDatabase: closeDatabase2 } = await Promise.resolve().then(() => (init_connection(), connection_exports));
|
|
8038
|
+
closeDatabase2();
|
|
8039
|
+
} catch (err) {
|
|
8040
|
+
result(FAIL, `Database error: ${err.message}`);
|
|
8041
|
+
process.exit(1);
|
|
8042
|
+
}
|
|
8043
|
+
step(5, TOTAL_STEPS, "Auth");
|
|
8044
|
+
const secretPath = (0, import_node_path6.join)(dataDir, "jwt.secret");
|
|
8045
|
+
if ((0, import_node_fs7.existsSync)(secretPath)) {
|
|
8046
|
+
result(PASS, `JWT secret exists at ${c.dim}${secretPath}${c.reset}`);
|
|
8047
|
+
} else {
|
|
8048
|
+
try {
|
|
8049
|
+
const { generateJwtSecret: generateJwtSecret2 } = await Promise.resolve().then(() => (init_jwt(), jwt_exports));
|
|
8050
|
+
generateJwtSecret2(dataDir);
|
|
8051
|
+
result(PASS, "JWT secret generated");
|
|
8052
|
+
} catch (err) {
|
|
8053
|
+
result(FAIL, `Could not generate JWT secret: ${err.message}`);
|
|
8054
|
+
process.exit(1);
|
|
8055
|
+
}
|
|
8056
|
+
}
|
|
8057
|
+
step(6, TOTAL_STEPS, "OpenClaw");
|
|
8058
|
+
const openclawPath = whichSync("openclaw");
|
|
8059
|
+
if (openclawPath) {
|
|
8060
|
+
result(PASS, `Found at ${c.dim}${openclawPath}${c.reset}`);
|
|
8061
|
+
try {
|
|
8062
|
+
const ver = (0, import_node_child_process3.execSync)(`${openclawPath} --version 2>/dev/null || echo unknown`, { encoding: "utf-8" }).trim();
|
|
8063
|
+
result(INFO, `Version: ${ver}`);
|
|
8064
|
+
} catch {
|
|
8065
|
+
}
|
|
8066
|
+
try {
|
|
8067
|
+
const plugins = (0, import_node_child_process3.execSync)(`${openclawPath} plugins list 2>/dev/null || true`, { encoding: "utf-8" });
|
|
8068
|
+
if (plugins.includes("platform-tools")) {
|
|
8069
|
+
result(PASS, "platform-tools plugin installed");
|
|
8070
|
+
} else {
|
|
8071
|
+
result(WARN, "platform-tools plugin not installed");
|
|
8072
|
+
const pluginDir = (0, import_node_path6.join)(__dirname, "..", "packages", "platform-tools");
|
|
8073
|
+
const altDir = (0, import_node_path6.join)(__dirname, "..", "..", "platform-tools");
|
|
8074
|
+
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;
|
|
8075
|
+
if (resolved) {
|
|
8076
|
+
try {
|
|
8077
|
+
(0, import_node_child_process3.execSync)(`${openclawPath} plugins install -l "${resolved}"`, { stdio: "pipe" });
|
|
8078
|
+
result(PASS, "platform-tools auto-installed");
|
|
8079
|
+
} catch {
|
|
8080
|
+
result(WARN, `Auto-install failed. Run manually: ${c.dim}openclaw plugins install @prajwalshete/platform-tools${c.reset}`);
|
|
8081
|
+
}
|
|
8082
|
+
} else {
|
|
8083
|
+
result(INFO, `Install manually: ${c.dim}openclaw plugins install @prajwalshete/platform-tools${c.reset}`);
|
|
8084
|
+
}
|
|
8085
|
+
}
|
|
8086
|
+
} catch {
|
|
8087
|
+
}
|
|
8088
|
+
try {
|
|
8089
|
+
const net = await import("net");
|
|
8090
|
+
const running = await new Promise((resolve) => {
|
|
8091
|
+
const sock = net.createConnection({ port: 18789, host: "127.0.0.1", timeout: 2e3 });
|
|
8092
|
+
sock.on("connect", () => {
|
|
8093
|
+
sock.destroy();
|
|
8094
|
+
resolve(true);
|
|
8095
|
+
});
|
|
8096
|
+
sock.on("error", () => resolve(false));
|
|
8097
|
+
sock.on("timeout", () => {
|
|
8098
|
+
sock.destroy();
|
|
8099
|
+
resolve(false);
|
|
8100
|
+
});
|
|
8101
|
+
});
|
|
8102
|
+
if (running) {
|
|
8103
|
+
result(PASS, "Gateway running on port 18789");
|
|
8104
|
+
} else {
|
|
8105
|
+
result(WARN, `Gateway not running. Start with: ${c.dim}openclaw gateway${c.reset}`);
|
|
8106
|
+
}
|
|
8107
|
+
} catch {
|
|
8108
|
+
}
|
|
8109
|
+
} else {
|
|
8110
|
+
result(SKIP, "OpenClaw not installed (AI features will be disabled)");
|
|
8111
|
+
result(INFO, `Install: ${c.dim}npm install -g openclaw && openclaw onboard${c.reset}`);
|
|
8112
|
+
}
|
|
8113
|
+
step(7, TOTAL_STEPS, "Ready!");
|
|
8114
|
+
console.log("");
|
|
8115
|
+
console.log(` ${c.green}${c.bold}Setup complete.${c.reset} Start the server with:`);
|
|
8116
|
+
console.log("");
|
|
8117
|
+
if (licenseKey) {
|
|
8118
|
+
console.log(` ${c.cyan}closeclaw start${c.reset}`);
|
|
8119
|
+
} else {
|
|
8120
|
+
console.log(` ${c.cyan}closeclaw start${c.reset} ${c.dim}(dev mode)${c.reset}`);
|
|
8121
|
+
}
|
|
8122
|
+
console.log("");
|
|
8123
|
+
console.log(` Then open ${c.bold}http://localhost:3001${c.reset} in your browser.`);
|
|
8124
|
+
console.log(` First signup becomes admin.`);
|
|
8125
|
+
console.log("");
|
|
8126
|
+
const startNow = await ask(` Start the server now? ${c.dim}(Y/n)${c.reset} `);
|
|
8127
|
+
if (!startNow || startNow.toLowerCase() === "y" || startNow.toLowerCase() === "yes") {
|
|
8128
|
+
console.log("");
|
|
8129
|
+
const startArgs = [];
|
|
8130
|
+
if (licenseKey) startArgs.push("--key", licenseKey);
|
|
8131
|
+
await commandStart(startArgs);
|
|
8132
|
+
}
|
|
8133
|
+
}
|
|
7904
8134
|
async function commandStart(args) {
|
|
7905
8135
|
const { values } = (0, import_node_util2.parseArgs)({
|
|
7906
8136
|
args,
|
|
7907
8137
|
options: {
|
|
7908
8138
|
port: { type: "string", short: "p", default: process.env.PORT || "3001" },
|
|
7909
8139
|
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 ||
|
|
8140
|
+
key: { type: "string", short: "k", default: "" },
|
|
8141
|
+
"data-dir": { type: "string", short: "d", default: process.env.PLATFORM_DATA_DIR || DATA_DIR },
|
|
7912
8142
|
domain: { type: "string", default: process.env.PLATFORM_DOMAIN || "" }
|
|
7913
8143
|
},
|
|
7914
8144
|
strict: true
|
|
7915
8145
|
});
|
|
7916
8146
|
const port2 = Number(values.port);
|
|
7917
8147
|
const host2 = values.host;
|
|
7918
|
-
|
|
8148
|
+
let licenseKey = values.key || getSavedLicenseKey() || "";
|
|
7919
8149
|
const dataDir = values["data-dir"];
|
|
7920
8150
|
const domain = values.domain;
|
|
7921
|
-
checkNodeVersion();
|
|
7922
|
-
console.log("[closeclaw] Starting Platform V3...");
|
|
7923
|
-
console.log(`[closeclaw] Data directory: ${dataDir}`);
|
|
7924
8151
|
process.env.PORT = String(port2);
|
|
7925
8152
|
process.env.HOST = host2;
|
|
7926
8153
|
if (licenseKey) process.env.PLATFORM_LICENSE_KEY = licenseKey;
|
|
7927
8154
|
process.env.PLATFORM_DATA_DIR = dataDir;
|
|
7928
8155
|
if (domain) process.env.PLATFORM_DOMAIN = domain;
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
loadConfig2();
|
|
7935
|
-
}
|
|
7936
|
-
} catch {
|
|
8156
|
+
if (!(0, import_node_fs7.existsSync)(dataDir) || !(0, import_node_fs7.existsSync)((0, import_node_path6.join)(dataDir, "jwt.secret"))) {
|
|
8157
|
+
console.log(`
|
|
8158
|
+
${c.yellow}Not onboarded yet.${c.reset} Run ${c.cyan}closeclaw onboard${c.reset} first.
|
|
8159
|
+
`);
|
|
8160
|
+
process.exit(1);
|
|
7937
8161
|
}
|
|
7938
8162
|
const isDev = process.env.NODE_ENV === "development" || process.env.NODE_ENV === "dev";
|
|
7939
|
-
if (!isDev) {
|
|
8163
|
+
if (!isDev && licenseKey) {
|
|
7940
8164
|
try {
|
|
7941
8165
|
const licenseMod = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
7942
8166
|
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 {
|
|
8167
|
+
if (typeof verify === "function") verify(licenseKey);
|
|
8168
|
+
} catch (err) {
|
|
8169
|
+
console.error(` ${c.red}License error:${c.reset} ${err.message}`);
|
|
8170
|
+
console.error(` Run ${c.cyan}closeclaw onboard${c.reset} to update your key.
|
|
8171
|
+
`);
|
|
8172
|
+
process.exit(1);
|
|
7952
8173
|
}
|
|
7953
|
-
}
|
|
7954
|
-
|
|
8174
|
+
}
|
|
8175
|
+
try {
|
|
8176
|
+
const configMod = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
8177
|
+
(configMod.loadConfig || configMod.default)?.();
|
|
8178
|
+
} catch {
|
|
7955
8179
|
}
|
|
7956
8180
|
const { initDatabase: initDatabase2 } = await Promise.resolve().then(() => (init_connection(), connection_exports));
|
|
7957
8181
|
initDatabase2(dataDir);
|
|
7958
|
-
console.log("[closeclaw] Database initialized");
|
|
7959
8182
|
const { runMigrations: runMigrations2 } = await Promise.resolve().then(() => (init_migrate(), migrate_exports));
|
|
7960
8183
|
runMigrations2();
|
|
7961
|
-
|
|
8184
|
+
const { generateJwtSecret: generateJwtSecret2 } = await Promise.resolve().then(() => (init_jwt(), jwt_exports));
|
|
8185
|
+
generateJwtSecret2(dataDir);
|
|
7962
8186
|
const openclawPath = whichSync("openclaw");
|
|
7963
8187
|
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
8188
|
const { isGatewayRunning: isGatewayRunning2, startGateway: startGateway2, registerShutdownHook: registerShutdownHook2 } = await Promise.resolve().then(() => (init_openclaw_manager(), openclaw_manager_exports));
|
|
7982
8189
|
registerShutdownHook2();
|
|
7983
|
-
const
|
|
7984
|
-
if (!
|
|
7985
|
-
console.log(
|
|
8190
|
+
const running = await isGatewayRunning2();
|
|
8191
|
+
if (!running) {
|
|
8192
|
+
console.log(` ${c.dim}Starting OpenClaw gateway...${c.reset}`);
|
|
7986
8193
|
await startGateway2();
|
|
7987
|
-
} else {
|
|
7988
|
-
console.log("[closeclaw] OpenClaw gateway already running");
|
|
7989
8194
|
}
|
|
7990
|
-
} else {
|
|
7991
|
-
console.log("[closeclaw] OpenClaw not found in PATH -- skipping gateway");
|
|
7992
8195
|
}
|
|
7993
8196
|
await Promise.resolve().then(() => (init_index(), index_exports));
|
|
7994
8197
|
writePid();
|
|
7995
8198
|
const version = readPkgVersion();
|
|
7996
8199
|
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
8200
|
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`);
|
|
8201
|
+
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}`);
|
|
8202
|
+
console.log(` ${c.green}${c.bold}\u2551 CloseClaw v${version.padEnd(31)}\u2551${c.reset}`);
|
|
8203
|
+
console.log(` ${c.green}${c.bold}\u2551 \u2551${c.reset}`);
|
|
8204
|
+
console.log(` ${c.green}${c.bold}\u2551${c.reset} ${c.cyan}${localUrl.padEnd(40)}${c.reset}${c.green}${c.bold}\u2551${c.reset}`);
|
|
8205
|
+
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
8206
|
if (openclawPath) {
|
|
8010
|
-
console.log(` \
|
|
8207
|
+
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
8208
|
}
|
|
8012
|
-
console.log(
|
|
8013
|
-
console.log(
|
|
8209
|
+
console.log(` ${c.green}${c.bold}\u2551 \u2551${c.reset}`);
|
|
8210
|
+
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
8211
|
console.log("");
|
|
8015
8212
|
}
|
|
8016
8213
|
async function commandStop() {
|
|
8017
8214
|
const pid = readPid();
|
|
8018
8215
|
if (!pid) {
|
|
8019
|
-
console.log("
|
|
8216
|
+
console.log(" Server is not running.");
|
|
8020
8217
|
process.exit(1);
|
|
8021
8218
|
}
|
|
8022
8219
|
if (!isProcessAlive(pid)) {
|
|
8023
|
-
console.log(`
|
|
8220
|
+
console.log(` Process ${pid} is not running. Cleaning up.`);
|
|
8024
8221
|
try {
|
|
8025
8222
|
(0, import_node_fs7.unlinkSync)(PID_FILE);
|
|
8026
8223
|
} catch {
|
|
8027
8224
|
}
|
|
8028
8225
|
process.exit(0);
|
|
8029
8226
|
}
|
|
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
|
-
}
|
|
8227
|
+
console.log(` Stopping server (PID ${pid})...`);
|
|
8228
|
+
process.kill(pid, "SIGTERM");
|
|
8037
8229
|
const deadline = Date.now() + 1e4;
|
|
8038
8230
|
while (Date.now() < deadline) {
|
|
8039
8231
|
if (!isProcessAlive(pid)) break;
|
|
8040
8232
|
await new Promise((r) => setTimeout(r, 250));
|
|
8041
8233
|
}
|
|
8042
8234
|
if (isProcessAlive(pid)) {
|
|
8043
|
-
console.warn("[closeclaw] Process did not exit in time, sending SIGKILL...");
|
|
8044
8235
|
try {
|
|
8045
8236
|
process.kill(pid, "SIGKILL");
|
|
8046
8237
|
} catch {
|
|
@@ -8050,14 +8241,13 @@ async function commandStop() {
|
|
|
8050
8241
|
(0, import_node_fs7.unlinkSync)(PID_FILE);
|
|
8051
8242
|
} catch {
|
|
8052
8243
|
}
|
|
8053
|
-
console.log("
|
|
8244
|
+
console.log(" Server stopped.");
|
|
8054
8245
|
}
|
|
8055
8246
|
async function commandStatus() {
|
|
8056
8247
|
const pid = readPid();
|
|
8057
8248
|
if (!pid || !isProcessAlive(pid)) {
|
|
8058
|
-
console.log(
|
|
8249
|
+
console.log(` ${c.red}Status: stopped${c.reset}`);
|
|
8059
8250
|
if (pid) {
|
|
8060
|
-
console.log(`[closeclaw] Stale PID file references process ${pid}`);
|
|
8061
8251
|
try {
|
|
8062
8252
|
(0, import_node_fs7.unlinkSync)(PID_FILE);
|
|
8063
8253
|
} catch {
|
|
@@ -8072,160 +8262,138 @@ async function commandStatus() {
|
|
|
8072
8262
|
healthy = res.ok;
|
|
8073
8263
|
} catch {
|
|
8074
8264
|
}
|
|
8075
|
-
console.log(`
|
|
8076
|
-
console.log(`
|
|
8077
|
-
console.log(`
|
|
8078
|
-
console.log(`
|
|
8265
|
+
console.log(` ${c.green}Status: running${c.reset}`);
|
|
8266
|
+
console.log(` PID: ${pid}`);
|
|
8267
|
+
console.log(` Port: ${port2}`);
|
|
8268
|
+
console.log(` Health: ${healthy ? c.green + "ok" + c.reset : c.red + "unreachable" + c.reset}`);
|
|
8079
8269
|
try {
|
|
8080
8270
|
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`);
|
|
8271
|
+
const sec = Math.floor((Date.now() - stat.mtimeMs) / 1e3);
|
|
8272
|
+
console.log(` Uptime: ${Math.floor(sec / 3600)}h ${Math.floor(sec % 3600 / 60)}m ${sec % 60}s`);
|
|
8087
8273
|
} catch {
|
|
8088
8274
|
}
|
|
8089
8275
|
}
|
|
8090
8276
|
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");
|
|
8277
|
+
const { values } = (0, import_node_util2.parseArgs)({ args, options: { email: { type: "string", short: "e" } }, strict: true });
|
|
8278
|
+
if (!values.email) {
|
|
8279
|
+
console.error(" --email required");
|
|
8102
8280
|
process.exit(1);
|
|
8103
8281
|
}
|
|
8104
|
-
const dataDir = process.env.PLATFORM_DATA_DIR || (0, import_node_path6.join)(HOME, ".closeclaw", "data");
|
|
8105
8282
|
const { initDatabase: initDatabase2 } = await Promise.resolve().then(() => (init_connection(), connection_exports));
|
|
8106
|
-
initDatabase2(
|
|
8283
|
+
initDatabase2(DATA_DIR);
|
|
8107
8284
|
const { runMigrations: runMigrations2 } = await Promise.resolve().then(() => (init_migrate(), migrate_exports));
|
|
8108
8285
|
runMigrations2();
|
|
8109
8286
|
const { getProfileByEmail: getProfileByEmail2, updateProfile: updateProfile2 } = await Promise.resolve().then(() => (init_profiles(), profiles_exports));
|
|
8110
|
-
const profile = getProfileByEmail2(email);
|
|
8287
|
+
const profile = getProfileByEmail2(values.email);
|
|
8111
8288
|
if (!profile) {
|
|
8112
|
-
console.error(`
|
|
8289
|
+
console.error(` No user: ${values.email}`);
|
|
8113
8290
|
process.exit(1);
|
|
8114
8291
|
}
|
|
8115
|
-
const password = await promptPassword("New password: ");
|
|
8292
|
+
const password = await promptPassword(" New password: ");
|
|
8116
8293
|
if (!password || password.length < 8) {
|
|
8117
|
-
console.error("
|
|
8294
|
+
console.error(" Min 8 characters.");
|
|
8118
8295
|
process.exit(1);
|
|
8119
8296
|
}
|
|
8120
|
-
const confirm = await promptPassword("Confirm
|
|
8297
|
+
const confirm = await promptPassword(" Confirm: ");
|
|
8121
8298
|
if (password !== confirm) {
|
|
8122
|
-
console.error("
|
|
8299
|
+
console.error(" Passwords don't match.");
|
|
8123
8300
|
process.exit(1);
|
|
8124
8301
|
}
|
|
8125
8302
|
const { hashPassword: hashPassword2 } = await Promise.resolve().then(() => (init_passwords(), passwords_exports));
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
console.log(`[closeclaw] Password updated for ${email}`);
|
|
8303
|
+
updateProfile2(profile.id, { password_hash: await hashPassword2(password) });
|
|
8304
|
+
console.log(` Password updated for ${values.email}`);
|
|
8129
8305
|
const { closeDatabase: closeDatabase2 } = await Promise.resolve().then(() => (init_connection(), connection_exports));
|
|
8130
8306
|
closeDatabase2();
|
|
8131
8307
|
}
|
|
8132
8308
|
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");
|
|
8309
|
+
const { values } = (0, import_node_util2.parseArgs)({ args, options: { output: { type: "string", short: "o", default: "closeclaw-backup.db" } }, strict: true });
|
|
8310
|
+
const dbPath = (0, import_node_path6.join)(DATA_DIR, "platform.db");
|
|
8143
8311
|
if (!(0, import_node_fs7.existsSync)(dbPath)) {
|
|
8144
|
-
console.error(`
|
|
8312
|
+
console.error(` Database not found at ${dbPath}`);
|
|
8145
8313
|
process.exit(1);
|
|
8146
8314
|
}
|
|
8147
8315
|
const Database2 = (await import("better-sqlite3")).default;
|
|
8148
8316
|
const db = new Database2(dbPath, { readonly: true });
|
|
8149
8317
|
try {
|
|
8150
|
-
await db.backup(output);
|
|
8151
|
-
console.log(`
|
|
8152
|
-
} catch (err) {
|
|
8153
|
-
console.error(`[closeclaw] Export failed: ${err.message}`);
|
|
8154
|
-
process.exit(1);
|
|
8318
|
+
await db.backup(values.output);
|
|
8319
|
+
console.log(` Exported to ${values.output}`);
|
|
8155
8320
|
} finally {
|
|
8156
8321
|
db.close();
|
|
8157
8322
|
}
|
|
8158
8323
|
}
|
|
8159
|
-
function commandVersion() {
|
|
8160
|
-
const version = readPkgVersion();
|
|
8161
|
-
console.log(`platform v${version}`);
|
|
8162
|
-
}
|
|
8163
8324
|
function printUsage() {
|
|
8164
8325
|
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
|
|
8326
|
+
${c.bold}closeclaw${c.reset} \u2014 AI-powered project management platform
|
|
8177
8327
|
|
|
8178
|
-
|
|
8328
|
+
${c.bold}Usage:${c.reset}
|
|
8329
|
+
closeclaw ${c.cyan}<command>${c.reset} [options]
|
|
8179
8330
|
|
|
8180
|
-
|
|
8331
|
+
${c.bold}Commands:${c.reset}
|
|
8332
|
+
${c.cyan}onboard${c.reset} Interactive setup wizard (run this first)
|
|
8333
|
+
${c.cyan}start${c.reset} Start the server
|
|
8334
|
+
--port, -p Port (default: 3001)
|
|
8335
|
+
--key, -k License key (or saved from onboard)
|
|
8336
|
+
--domain Public domain for auto-SSL
|
|
8337
|
+
${c.cyan}stop${c.reset} Stop the server
|
|
8338
|
+
${c.cyan}status${c.reset} Show server status
|
|
8339
|
+
${c.cyan}reset-password${c.reset} Reset a user's password
|
|
8340
|
+
--email, -e User email
|
|
8341
|
+
${c.cyan}export${c.reset} Backup the database
|
|
8342
|
+
--output, -o Output file
|
|
8343
|
+
${c.cyan}version${c.reset} Print version
|
|
8181
8344
|
|
|
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
|
|
8345
|
+
${c.bold}Getting started:${c.reset}
|
|
8346
|
+
${c.dim}$ closeclaw onboard${c.reset}
|
|
8190
8347
|
`);
|
|
8191
8348
|
}
|
|
8192
8349
|
async function main() {
|
|
8193
8350
|
const args = process.argv.slice(2);
|
|
8194
8351
|
const command = args[0];
|
|
8195
|
-
const
|
|
8352
|
+
const rest = args.slice(1);
|
|
8196
8353
|
switch (command) {
|
|
8354
|
+
case "onboard":
|
|
8355
|
+
return commandOnboard();
|
|
8197
8356
|
case "start":
|
|
8198
|
-
|
|
8199
|
-
break;
|
|
8357
|
+
return commandStart(rest);
|
|
8200
8358
|
case "stop":
|
|
8201
|
-
|
|
8202
|
-
break;
|
|
8359
|
+
return commandStop();
|
|
8203
8360
|
case "status":
|
|
8204
|
-
|
|
8205
|
-
break;
|
|
8361
|
+
return commandStatus();
|
|
8206
8362
|
case "reset-password":
|
|
8207
|
-
|
|
8208
|
-
break;
|
|
8363
|
+
return commandResetPassword(rest);
|
|
8209
8364
|
case "export":
|
|
8210
|
-
|
|
8211
|
-
break;
|
|
8365
|
+
return commandExport(rest);
|
|
8212
8366
|
case "version":
|
|
8213
8367
|
case "--version":
|
|
8214
8368
|
case "-v":
|
|
8215
|
-
|
|
8369
|
+
console.log(` closeclaw v${readPkgVersion()}`);
|
|
8216
8370
|
break;
|
|
8217
8371
|
case "help":
|
|
8218
8372
|
case "--help":
|
|
8219
|
-
case void 0:
|
|
8220
8373
|
printUsage();
|
|
8221
8374
|
break;
|
|
8375
|
+
case void 0:
|
|
8376
|
+
console.log(`
|
|
8377
|
+
${c.bold}CloseClaw${c.reset} v${readPkgVersion()}
|
|
8378
|
+
`);
|
|
8379
|
+
if (!(0, import_node_fs7.existsSync)((0, import_node_path6.join)(DATA_DIR, "jwt.secret"))) {
|
|
8380
|
+
console.log(` ${c.yellow}First time?${c.reset} Run ${c.cyan}closeclaw onboard${c.reset} to get started.
|
|
8381
|
+
`);
|
|
8382
|
+
} else {
|
|
8383
|
+
console.log(` Run ${c.cyan}closeclaw start${c.reset} to launch the server.
|
|
8384
|
+
`);
|
|
8385
|
+
printUsage();
|
|
8386
|
+
}
|
|
8387
|
+
break;
|
|
8222
8388
|
default:
|
|
8223
|
-
console.error(`Unknown command: ${command}`);
|
|
8389
|
+
console.error(` Unknown command: ${command}`);
|
|
8224
8390
|
printUsage();
|
|
8225
8391
|
process.exit(1);
|
|
8226
8392
|
}
|
|
8227
8393
|
}
|
|
8228
8394
|
main().catch((err) => {
|
|
8229
|
-
console.error(
|
|
8395
|
+
console.error(`
|
|
8396
|
+
${c.red}Fatal:${c.reset} ${err.message}
|
|
8397
|
+
`);
|
|
8230
8398
|
process.exit(1);
|
|
8231
8399
|
});
|