agim-cli 1.0.8 → 1.0.10
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/CHANGELOG.md +76 -0
- package/dist/cli.js +64 -0
- package/dist/cli.js.map +1 -1
- package/dist/core/admin-allowlist.d.ts +25 -0
- package/dist/core/admin-allowlist.d.ts.map +1 -0
- package/dist/core/admin-allowlist.js +130 -0
- package/dist/core/admin-allowlist.js.map +1 -0
- package/dist/core/admin-bootstrap.d.ts +28 -0
- package/dist/core/admin-bootstrap.d.ts.map +1 -0
- package/dist/core/admin-bootstrap.js +132 -0
- package/dist/core/admin-bootstrap.js.map +1 -0
- package/dist/core/approval-router.d.ts.map +1 -1
- package/dist/core/approval-router.js +15 -0
- package/dist/core/approval-router.js.map +1 -1
- package/dist/core/commands/builtin.d.ts +1 -1
- package/dist/core/commands/builtin.d.ts.map +1 -1
- package/dist/core/commands/builtin.js +8 -4
- package/dist/core/commands/builtin.js.map +1 -1
- package/dist/core/commands/service.d.ts +14 -0
- package/dist/core/commands/service.d.ts.map +1 -0
- package/dist/core/commands/service.js +85 -0
- package/dist/core/commands/service.js.map +1 -0
- package/dist/core/commands/setup.d.ts +3 -0
- package/dist/core/commands/setup.d.ts.map +1 -0
- package/dist/core/commands/setup.js +64 -0
- package/dist/core/commands/setup.js.map +1 -0
- package/dist/core/restart-completion.d.ts +5 -0
- package/dist/core/restart-completion.d.ts.map +1 -0
- package/dist/core/restart-completion.js +156 -0
- package/dist/core/restart-completion.js.map +1 -0
- package/dist/core/restart-flow.d.ts +40 -0
- package/dist/core/restart-flow.d.ts.map +1 -0
- package/dist/core/restart-flow.js +177 -0
- package/dist/core/restart-flow.js.map +1 -0
- package/dist/core/restart-preflight.d.ts +7 -0
- package/dist/core/restart-preflight.d.ts.map +1 -0
- package/dist/core/restart-preflight.js +95 -0
- package/dist/core/restart-preflight.js.map +1 -0
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +23 -2
- package/dist/core/router.js.map +1 -1
- package/dist/core/self-protect.d.ts +8 -0
- package/dist/core/self-protect.d.ts.map +1 -0
- package/dist/core/self-protect.js +119 -0
- package/dist/core/self-protect.js.map +1 -0
- package/dist/core/service-intent.d.ts +17 -0
- package/dist/core/service-intent.d.ts.map +1 -0
- package/dist/core/service-intent.js +87 -0
- package/dist/core/service-intent.js.map +1 -0
- package/dist/core/types.d.ts +8 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/web/public/settings.html +133 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +110 -37
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
package/dist/web/server.js
CHANGED
|
@@ -431,6 +431,19 @@ export async function startWebServer(options) {
|
|
|
431
431
|
if (url.pathname === '/api/service/restart' && req.method === 'POST') {
|
|
432
432
|
return handleServiceRestart(res);
|
|
433
433
|
}
|
|
434
|
+
// Admin allowlist — list + add/remove via the Safety card. Locally
|
|
435
|
+
// gated: only allowed when bound to 127.0.0.1, since the page has
|
|
436
|
+
// no auth and we don't want random LAN visitors granting themselves
|
|
437
|
+
// admin. See `bindHost` upthread.
|
|
438
|
+
if (url.pathname === '/api/admin-allowlist' && req.method === 'GET') {
|
|
439
|
+
return handleAdminAllowlistGet(res);
|
|
440
|
+
}
|
|
441
|
+
if (url.pathname === '/api/admin-allowlist' && req.method === 'POST') {
|
|
442
|
+
return handleAdminAllowlistAdd(req, res, bindHost);
|
|
443
|
+
}
|
|
444
|
+
if (url.pathname === '/api/admin-allowlist' && req.method === 'DELETE') {
|
|
445
|
+
return handleAdminAllowlistRemove(req, res, bindHost);
|
|
446
|
+
}
|
|
434
447
|
res.writeHead(404);
|
|
435
448
|
res.end('Not found');
|
|
436
449
|
});
|
|
@@ -1039,7 +1052,7 @@ async function handleServiceStop(res) {
|
|
|
1039
1052
|
}, 200);
|
|
1040
1053
|
}
|
|
1041
1054
|
async function handleServiceRestart(res) {
|
|
1042
|
-
const { detectService
|
|
1055
|
+
const { detectService } = await import('../cli-ui/service.js');
|
|
1043
1056
|
const st = detectService();
|
|
1044
1057
|
if (st.mode === 'systemd') {
|
|
1045
1058
|
try {
|
|
@@ -1060,44 +1073,104 @@ async function handleServiceRestart(res) {
|
|
|
1060
1073
|
sendJson(res, 409, { error: 'foreground service is running in another terminal; restart it manually (Ctrl-C + re-run)' });
|
|
1061
1074
|
return;
|
|
1062
1075
|
}
|
|
1063
|
-
// background or 'none':
|
|
1064
|
-
//
|
|
1065
|
-
// so the
|
|
1066
|
-
|
|
1067
|
-
const
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1076
|
+
// background or 'none': delegate to the shared restart-flow. The flow
|
|
1077
|
+
// runs pre-flight checks, writes restart-pending.json with source='web'
|
|
1078
|
+
// (so the new daemon doesn't try to push an IM completion message —
|
|
1079
|
+
// browser polling handles that), and spawns the detached helper.
|
|
1080
|
+
const { initiateRestart } = await import('../core/restart-flow.js');
|
|
1081
|
+
const result = await initiateRestart({ source: 'web' });
|
|
1082
|
+
if (!result.ok) {
|
|
1083
|
+
sendJson(res, 409, { error: result.message, details: result.errors });
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
sendJson(res, 200, { ok: true, mode: st.mode, restarting: true, message: result.message });
|
|
1087
|
+
}
|
|
1088
|
+
// ============================================================
|
|
1089
|
+
// Admin allowlist endpoints (Safety card → Admin Users)
|
|
1090
|
+
// ============================================================
|
|
1091
|
+
//
|
|
1092
|
+
// All three are gated on the server's bindHost being a loopback address.
|
|
1093
|
+
// The web console has no auth, so we MUST not let a LAN visitor add
|
|
1094
|
+
// themselves as admin. When bindHost is 0.0.0.0 we hide the editor on
|
|
1095
|
+
// the frontend AND refuse here as a defense-in-depth.
|
|
1096
|
+
function isLocalBind(bindHost) {
|
|
1097
|
+
return bindHost === '127.0.0.1' || bindHost === '::1' || bindHost === 'localhost';
|
|
1098
|
+
}
|
|
1099
|
+
async function handleAdminAllowlistGet(res) {
|
|
1100
|
+
try {
|
|
1101
|
+
const { listAdmins, isAllowlistConfigured } = await import('../core/admin-allowlist.js');
|
|
1102
|
+
const { BOOTSTRAP_TOKEN_FILE } = await import('../core/admin-bootstrap.js');
|
|
1103
|
+
const hasBootstrap = existsSync(BOOTSTRAP_TOKEN_FILE);
|
|
1104
|
+
sendJson(res, 200, {
|
|
1105
|
+
admins: listAdmins(),
|
|
1106
|
+
configured: isAllowlistConfigured(),
|
|
1107
|
+
bootstrapAvailable: hasBootstrap,
|
|
1108
|
+
bootstrapTokenPath: BOOTSTRAP_TOKEN_FILE,
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
catch (err) {
|
|
1112
|
+
sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
async function handleAdminAllowlistAdd(req, res, bindHost) {
|
|
1116
|
+
if (!isLocalBind(bindHost)) {
|
|
1117
|
+
sendJson(res, 403, {
|
|
1118
|
+
error: 'admin editor disabled when web is bound publicly (no auth on this endpoint).',
|
|
1119
|
+
});
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
try {
|
|
1123
|
+
const body = await readBody(req, res);
|
|
1124
|
+
const parsed = JSON.parse(body || '{}');
|
|
1125
|
+
const platform = (parsed.platform || '').trim();
|
|
1126
|
+
const userId = (parsed.userId || '').trim();
|
|
1127
|
+
if (!platform || !userId) {
|
|
1128
|
+
sendJson(res, 400, { error: 'platform and userId required' });
|
|
1129
|
+
return;
|
|
1096
1130
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1131
|
+
const { promoteAdmin, listAdmins } = await import('../core/admin-allowlist.js');
|
|
1132
|
+
const added = await promoteAdmin(platform, userId);
|
|
1133
|
+
sendJson(res, 200, { ok: true, added, admins: listAdmins() });
|
|
1134
|
+
}
|
|
1135
|
+
catch (err) {
|
|
1136
|
+
sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
async function handleAdminAllowlistRemove(req, res, bindHost) {
|
|
1140
|
+
if (!isLocalBind(bindHost)) {
|
|
1141
|
+
sendJson(res, 403, { error: 'admin editor disabled on public binds' });
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
try {
|
|
1145
|
+
const body = await readBody(req, res);
|
|
1146
|
+
const parsed = JSON.parse(body || '{}');
|
|
1147
|
+
const platform = (parsed.platform || '').trim().toLowerCase();
|
|
1148
|
+
const userId = (parsed.userId || '').trim();
|
|
1149
|
+
if (!platform || !userId) {
|
|
1150
|
+
sendJson(res, 400, { error: 'platform and userId required' });
|
|
1151
|
+
return;
|
|
1099
1152
|
}
|
|
1100
|
-
|
|
1153
|
+
const { listAdmins } = await import('../core/admin-allowlist.js');
|
|
1154
|
+
const remaining = listAdmins().filter((a) => !(a.platform === platform && a.userId === userId));
|
|
1155
|
+
const newRaw = remaining.map((a) => `${a.platform}:${a.userId}`).join(',');
|
|
1156
|
+
// Persist to env file. Note this nukes both runtime + env-derived
|
|
1157
|
+
// entries — fine for the editor's use case (it round-trips through
|
|
1158
|
+
// the full list).
|
|
1159
|
+
const { updateEnvFile } = await import('../cli-ui/env-file.js');
|
|
1160
|
+
updateEnvFile({ IMHUB_ADMIN_USERS: newRaw || null });
|
|
1161
|
+
// Update process.env so getEntries() picks it up next call.
|
|
1162
|
+
if (newRaw)
|
|
1163
|
+
process.env.IMHUB_ADMIN_USERS = newRaw;
|
|
1164
|
+
else
|
|
1165
|
+
delete process.env.IMHUB_ADMIN_USERS;
|
|
1166
|
+
// Reset the parse cache so the change is visible immediately.
|
|
1167
|
+
const { _resetCache } = await import('../core/admin-allowlist.js');
|
|
1168
|
+
_resetCache();
|
|
1169
|
+
sendJson(res, 200, { ok: true, admins: remaining });
|
|
1170
|
+
}
|
|
1171
|
+
catch (err) {
|
|
1172
|
+
sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
1173
|
+
}
|
|
1101
1174
|
}
|
|
1102
1175
|
/**
|
|
1103
1176
|
* POST /api/notify → push a message to an IM thread.
|