neoagent 2.3.1-beta.93 → 2.3.1-beta.94
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/com.neoagent.plist +1 -1
- package/flutter_app/lib/features/notifications/notification_interceptor.dart +12 -2
- package/flutter_app/lib/main_chat.dart +23 -16
- package/flutter_app/lib/main_controller.dart +27 -1
- package/flutter_app/lib/main_shared.dart +1 -1
- package/flutter_app/lib/src/backend_client.dart +2 -10
- package/lib/manager.js +260 -134
- package/package.json +1 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +74352 -73147
- package/server/routes/triggers.js +41 -32
- package/server/services/ai/engine.js +15 -5
- package/server/services/ai/taskAnalysis.js +1 -0
- package/server/services/android/controller.js +3 -3
package/lib/manager.js
CHANGED
|
@@ -119,28 +119,45 @@ function readEnvFileRaw() {
|
|
|
119
119
|
return fs.readFileSync(ENV_FILE, 'utf8');
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
function sanitizeEnvKey(key) {
|
|
123
|
+
return String(key).replace(/[\r\n]/g, '');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function sanitizeEnvValue(value) {
|
|
127
|
+
return String(value).replace(/[\r\n]/g, '');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function validateEnvKey(key) {
|
|
131
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(key)) {
|
|
132
|
+
throw new Error(`Invalid env key "${key}". Keys must be uppercase letters, digits, and underscores (e.g. PORT, ANTHROPIC_API_KEY).`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
122
136
|
function upsertEnvValue(key, value) {
|
|
137
|
+
const safeKey = sanitizeEnvKey(key);
|
|
138
|
+
const safeValue = sanitizeEnvValue(value);
|
|
123
139
|
const raw = readEnvFileRaw();
|
|
124
140
|
const lines = raw ? raw.split('\n') : [];
|
|
125
141
|
let replaced = false;
|
|
126
142
|
|
|
127
143
|
for (let i = 0; i < lines.length; i++) {
|
|
128
|
-
if (lines[i].startsWith(`${
|
|
129
|
-
lines[i] = `${
|
|
144
|
+
if (lines[i].startsWith(`${safeKey}=`)) {
|
|
145
|
+
lines[i] = `${safeKey}=${safeValue}`;
|
|
130
146
|
replaced = true;
|
|
131
147
|
break;
|
|
132
148
|
}
|
|
133
149
|
}
|
|
134
150
|
|
|
135
|
-
if (!replaced) lines.push(`${
|
|
151
|
+
if (!replaced) lines.push(`${safeKey}=${safeValue}`);
|
|
136
152
|
const output = lines.filter((_, idx, arr) => idx !== arr.length - 1 || arr[idx] !== '').join('\n') + '\n';
|
|
137
153
|
fs.writeFileSync(ENV_FILE, output, { mode: 0o600 });
|
|
138
154
|
}
|
|
139
155
|
|
|
140
156
|
function removeEnvValue(key) {
|
|
157
|
+
const safeKey = sanitizeEnvKey(key);
|
|
141
158
|
const raw = readEnvFileRaw();
|
|
142
159
|
if (!raw) return false;
|
|
143
|
-
const lines = raw.split('\n').filter((line) => !line.startsWith(`${
|
|
160
|
+
const lines = raw.split('\n').filter((line) => !line.startsWith(`${safeKey}=`));
|
|
144
161
|
const output = lines.filter((_, idx, arr) => idx !== arr.length - 1 || arr[idx] !== '').join('\n') + '\n';
|
|
145
162
|
fs.writeFileSync(ENV_FILE, output, { mode: 0o600 });
|
|
146
163
|
return true;
|
|
@@ -381,15 +398,19 @@ function listNeoAgentServerProcesses() {
|
|
|
381
398
|
};
|
|
382
399
|
})
|
|
383
400
|
.filter(Boolean)
|
|
384
|
-
.filter((entry) =>
|
|
385
|
-
entry.pid
|
|
386
|
-
|
|
387
|
-
(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
401
|
+
.filter((entry) => {
|
|
402
|
+
if (entry.pid === process.pid) return false;
|
|
403
|
+
const cmd = String(entry.command || '');
|
|
404
|
+
const cmdNormalized = cmd.replace(/\\/g, '/');
|
|
405
|
+
const executablePart = cmd.split(/\s+/)[0] || '';
|
|
406
|
+
const executableBase = path.basename(executablePart);
|
|
407
|
+
const isNode = /^node\d*$/.test(executableBase) || /(^|\s)node\d*(\s|$)/.test(cmd);
|
|
408
|
+
return isNode && (
|
|
409
|
+
appIndexPattern.test(cmdNormalized) ||
|
|
410
|
+
genericNeoAgentPattern.test(cmdNormalized) ||
|
|
411
|
+
repoNamePattern.test(cmd)
|
|
412
|
+
);
|
|
413
|
+
});
|
|
393
414
|
}
|
|
394
415
|
|
|
395
416
|
function killNeoAgentServerProcesses() {
|
|
@@ -464,7 +485,12 @@ async function cmdSetup() {
|
|
|
464
485
|
logInfo('Press Enter to keep the current value shown in brackets.');
|
|
465
486
|
|
|
466
487
|
heading('Core');
|
|
467
|
-
const
|
|
488
|
+
const portRaw = await ask('Server port', current.PORT || '3333');
|
|
489
|
+
const portNum = Number(portRaw);
|
|
490
|
+
if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) {
|
|
491
|
+
throw new Error(`Invalid port "${portRaw}". Must be an integer between 1 and 65535.`);
|
|
492
|
+
}
|
|
493
|
+
const port = String(portNum);
|
|
468
494
|
const publicUrl = await ask('Public base URL', current.PUBLIC_URL || '');
|
|
469
495
|
const secureCookiesDefault = current.SECURE_COOKIES ||
|
|
470
496
|
(String(publicUrl || '').trim().startsWith('https://') ? 'true' : 'false');
|
|
@@ -735,6 +761,31 @@ async function cmdMigrate(args = []) {
|
|
|
735
761
|
});
|
|
736
762
|
}
|
|
737
763
|
|
|
764
|
+
async function pollDeviceCode({ pollUrl, pollBody, pollHeaders = {}, intervalMs, timeoutMs, onToken }) {
|
|
765
|
+
const start = Date.now();
|
|
766
|
+
let currentInterval = intervalMs;
|
|
767
|
+
while (Date.now() - start < timeoutMs) {
|
|
768
|
+
await new Promise((r) => setTimeout(r, currentInterval));
|
|
769
|
+
const res = await fetch(pollUrl, {
|
|
770
|
+
method: 'POST',
|
|
771
|
+
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', ...pollHeaders },
|
|
772
|
+
body: JSON.stringify(pollBody()),
|
|
773
|
+
});
|
|
774
|
+
if (res.status === 403 || res.status === 404) continue;
|
|
775
|
+
if (!res.ok) {
|
|
776
|
+
const text = await res.text().catch(() => 'Unknown error');
|
|
777
|
+
throw new Error(`Token poll failed: HTTP ${res.status} — ${text}`);
|
|
778
|
+
}
|
|
779
|
+
const data = await res.json();
|
|
780
|
+
const done = await onToken(data);
|
|
781
|
+
if (done) return;
|
|
782
|
+
if (data.error === 'authorization_pending') continue;
|
|
783
|
+
if (data.error === 'slow_down') { currentInterval += 5000; continue; }
|
|
784
|
+
if (data.error) throw new Error(`Authentication failed: ${data.error_description || data.error}`);
|
|
785
|
+
}
|
|
786
|
+
throw new Error('Authentication timed out after 15 minutes.');
|
|
787
|
+
}
|
|
788
|
+
|
|
738
789
|
async function cmdLogin(args = []) {
|
|
739
790
|
const provider = args[0];
|
|
740
791
|
if (provider !== 'github-copilot' && provider !== 'openai-codex') {
|
|
@@ -751,54 +802,28 @@ async function cmdLogin(args = []) {
|
|
|
751
802
|
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
|
|
752
803
|
body: JSON.stringify({ client_id: clientId, scope: 'user:email' })
|
|
753
804
|
});
|
|
754
|
-
|
|
755
|
-
if (!reqRes.ok) {
|
|
756
|
-
throw new Error(`Failed to request device code: HTTP ${reqRes.status}`);
|
|
757
|
-
}
|
|
805
|
+
if (!reqRes.ok) throw new Error(`Failed to request device code: HTTP ${reqRes.status}`);
|
|
758
806
|
|
|
759
807
|
const { device_code, user_code, verification_uri, interval } = await reqRes.json();
|
|
760
|
-
|
|
761
808
|
console.log(`\n ${COLORS.cyan}Please visit:${COLORS.reset} ${verification_uri}`);
|
|
762
|
-
console.log(` ${COLORS.cyan}
|
|
763
|
-
|
|
809
|
+
console.log(` ${COLORS.cyan}Enter code:${COLORS.reset} ${COLORS.bold}${user_code}${COLORS.reset}\n`);
|
|
764
810
|
logInfo('Waiting for authorization (timeout in 15m)...');
|
|
765
|
-
const startTime = Date.now();
|
|
766
|
-
const timeoutMs = 15 * 60 * 1000;
|
|
767
|
-
let currentPollInterval = (interval || 5) * 1000;
|
|
768
|
-
|
|
769
|
-
while (Date.now() - startTime < timeoutMs) {
|
|
770
|
-
await new Promise((r) => setTimeout(r, currentPollInterval));
|
|
771
|
-
const tokenRes = await fetch('https://github.com/login/oauth/access_token', {
|
|
772
|
-
method: 'POST',
|
|
773
|
-
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
|
|
774
|
-
body: JSON.stringify({
|
|
775
|
-
client_id: clientId,
|
|
776
|
-
device_code,
|
|
777
|
-
grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
|
|
778
|
-
})
|
|
779
|
-
});
|
|
780
|
-
|
|
781
|
-
if (!tokenRes.ok) {
|
|
782
|
-
const errorText = await tokenRes.text().catch(() => 'Unknown error');
|
|
783
|
-
throw new Error(`GitHub token request failed: HTTP ${tokenRes.status} - ${errorText}`);
|
|
784
|
-
}
|
|
785
811
|
|
|
786
|
-
|
|
787
|
-
|
|
812
|
+
await pollDeviceCode({
|
|
813
|
+
pollUrl: 'https://github.com/login/oauth/access_token',
|
|
814
|
+
pollBody: () => ({ client_id: clientId, device_code, grant_type: 'urn:ietf:params:oauth:grant-type:device_code' }),
|
|
815
|
+
intervalMs: (interval || 5) * 1000,
|
|
816
|
+
timeoutMs: 15 * 60 * 1000,
|
|
817
|
+
onToken: async (data) => {
|
|
818
|
+
if (!data.access_token) return false;
|
|
788
819
|
upsertEnvValue('GITHUB_COPILOT_ACCESS_TOKEN', data.access_token);
|
|
789
|
-
logOk('
|
|
790
|
-
logInfo('
|
|
820
|
+
logOk('Saved GitHub Copilot access token to .env');
|
|
821
|
+
logInfo('Restarting NeoAgent to apply credentials...');
|
|
791
822
|
cmdRestart();
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
currentPollInterval += 5000;
|
|
797
|
-
} else if (data.error) {
|
|
798
|
-
throw new Error(`Authentication failed: ${data.error_description || data.error}`);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
throw new Error('GitHub authentication timed out after 15 minutes.');
|
|
823
|
+
return true;
|
|
824
|
+
},
|
|
825
|
+
});
|
|
826
|
+
return;
|
|
802
827
|
} else if (provider === 'openai-codex') {
|
|
803
828
|
heading('OpenAI Codex Login');
|
|
804
829
|
const clientId = 'app_EMoamEEZ73f0CkXaXp7hrann';
|
|
@@ -820,53 +845,24 @@ async function cmdLogin(args = []) {
|
|
|
820
845
|
const verification_uri = 'https://auth.openai.com/codex/device';
|
|
821
846
|
|
|
822
847
|
console.log(`\n ${COLORS.cyan}Please visit:${COLORS.reset} ${verification_uri}`);
|
|
823
|
-
console.log(` ${COLORS.cyan}
|
|
824
|
-
|
|
848
|
+
console.log(` ${COLORS.cyan}Enter code:${COLORS.reset} ${COLORS.bold}${user_code}${COLORS.reset}\n`);
|
|
825
849
|
logInfo('Waiting for authorization (timeout in 15m)...');
|
|
826
|
-
|
|
827
|
-
const timeoutMs = 15 * 60 * 1000;
|
|
828
|
-
let currentPollInterval = (interval || 5) * 1000;
|
|
850
|
+
|
|
829
851
|
let authorizationCode = null;
|
|
830
852
|
let codeVerifier = null;
|
|
831
853
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
// These statuses are returned by OpenAI while authorization is pending
|
|
845
|
-
continue;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
if (!tokenRes.ok) {
|
|
849
|
-
const errorText = await tokenRes.text().catch(() => 'Unknown error');
|
|
850
|
-
throw new Error(`OpenAI token request failed: HTTP ${tokenRes.status} - ${errorText}`);
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
const pollData = await tokenRes.json();
|
|
854
|
-
if (pollData.authorization_code && pollData.code_verifier) {
|
|
855
|
-
authorizationCode = pollData.authorization_code;
|
|
856
|
-
codeVerifier = pollData.code_verifier;
|
|
857
|
-
break;
|
|
858
|
-
} else if (pollData.error === 'authorization_pending') {
|
|
859
|
-
// Continue polling
|
|
860
|
-
} else if (pollData.error === 'slow_down') {
|
|
861
|
-
currentPollInterval += 5000;
|
|
862
|
-
} else if (pollData.error) {
|
|
863
|
-
throw new Error(`Authentication failed: ${pollData.error_description || pollData.error}`);
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
if (!authorizationCode || !codeVerifier) {
|
|
868
|
-
throw new Error('OpenAI authentication timed out after 15 minutes.');
|
|
869
|
-
}
|
|
854
|
+
await pollDeviceCode({
|
|
855
|
+
pollUrl: 'https://auth.openai.com/api/accounts/deviceauth/token',
|
|
856
|
+
pollBody: () => ({ device_auth_id, user_code }),
|
|
857
|
+
intervalMs: (interval || 5) * 1000,
|
|
858
|
+
timeoutMs: 15 * 60 * 1000,
|
|
859
|
+
onToken: async (data) => {
|
|
860
|
+
if (!data.authorization_code || !data.code_verifier) return false;
|
|
861
|
+
authorizationCode = data.authorization_code;
|
|
862
|
+
codeVerifier = data.code_verifier;
|
|
863
|
+
return true;
|
|
864
|
+
},
|
|
865
|
+
});
|
|
870
866
|
|
|
871
867
|
logInfo('Exchanging authorization code for access token...');
|
|
872
868
|
const exchangeRes = await fetch('https://auth.openai.com/oauth/token', {
|
|
@@ -877,27 +873,26 @@ async function cmdLogin(args = []) {
|
|
|
877
873
|
code: authorizationCode,
|
|
878
874
|
redirect_uri: 'https://auth.openai.com/deviceauth/callback',
|
|
879
875
|
client_id: clientId,
|
|
880
|
-
code_verifier: codeVerifier
|
|
881
|
-
})
|
|
876
|
+
code_verifier: codeVerifier,
|
|
877
|
+
}),
|
|
882
878
|
});
|
|
883
879
|
|
|
884
880
|
if (!exchangeRes.ok) {
|
|
885
881
|
const errorText = await exchangeRes.text().catch(() => 'Unknown error');
|
|
886
|
-
throw new Error(`OpenAI token exchange failed: HTTP ${exchangeRes.status}
|
|
882
|
+
throw new Error(`OpenAI token exchange failed: HTTP ${exchangeRes.status} — ${errorText}`);
|
|
887
883
|
}
|
|
888
884
|
|
|
889
885
|
const exchangeData = await exchangeRes.json();
|
|
890
|
-
if (exchangeData.access_token) {
|
|
891
|
-
upsertEnvValue('OPENAI_CODEX_ACCESS_TOKEN', exchangeData.access_token);
|
|
892
|
-
if (exchangeData.refresh_token) {
|
|
893
|
-
upsertEnvValue('OPENAI_CODEX_REFRESH_TOKEN', exchangeData.refresh_token);
|
|
894
|
-
}
|
|
895
|
-
logOk('Successfully authenticated and saved OpenAI Codex tokens to .env');
|
|
896
|
-
logInfo('Applying updated provider credentials by restarting NeoAgent...');
|
|
897
|
-
cmdRestart();
|
|
898
|
-
} else {
|
|
886
|
+
if (!exchangeData.access_token) {
|
|
899
887
|
throw new Error('OpenAI token exchange succeeded but did not return an access token.');
|
|
900
888
|
}
|
|
889
|
+
upsertEnvValue('OPENAI_CODEX_ACCESS_TOKEN', exchangeData.access_token);
|
|
890
|
+
if (exchangeData.refresh_token) {
|
|
891
|
+
upsertEnvValue('OPENAI_CODEX_REFRESH_TOKEN', exchangeData.refresh_token);
|
|
892
|
+
}
|
|
893
|
+
logOk('Saved OpenAI Codex tokens to .env');
|
|
894
|
+
logInfo('Restarting NeoAgent to apply credentials...');
|
|
895
|
+
cmdRestart();
|
|
901
896
|
}
|
|
902
897
|
}
|
|
903
898
|
|
|
@@ -1052,6 +1047,54 @@ async function ensureQemuInstalled() {
|
|
|
1052
1047
|
}
|
|
1053
1048
|
}
|
|
1054
1049
|
|
|
1050
|
+
function ensureYtDlpInstalled() {
|
|
1051
|
+
heading('Ensure yt-dlp Installed');
|
|
1052
|
+
if (commandExists('yt-dlp')) {
|
|
1053
|
+
const ver = runQuiet('yt-dlp', ['--version']);
|
|
1054
|
+
logOk(`yt-dlp ${ver.status === 0 ? ver.stdout.trim() : '(version unknown)'}`);
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
logInfo('yt-dlp not found. Attempting to install...');
|
|
1059
|
+
const platform = detectPlatform();
|
|
1060
|
+
|
|
1061
|
+
if (platform === 'macos') {
|
|
1062
|
+
if (!commandExists('brew')) {
|
|
1063
|
+
logWarn('Homebrew not found — skipping yt-dlp install. Install manually: brew install yt-dlp');
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
try {
|
|
1067
|
+
runOrThrow('brew', ['install', 'yt-dlp']);
|
|
1068
|
+
logOk('yt-dlp installed via Homebrew');
|
|
1069
|
+
} catch {
|
|
1070
|
+
logWarn('yt-dlp install failed. Install manually: brew install yt-dlp');
|
|
1071
|
+
}
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
if (platform === 'linux') {
|
|
1076
|
+
if (commandExists('pipx')) {
|
|
1077
|
+
try {
|
|
1078
|
+
runOrThrow('pipx', ['install', 'yt-dlp']);
|
|
1079
|
+
logOk('yt-dlp installed via pipx');
|
|
1080
|
+
return;
|
|
1081
|
+
} catch {
|
|
1082
|
+
// fall through to pip3
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
if (commandExists('pip3')) {
|
|
1086
|
+
try {
|
|
1087
|
+
runOrThrow('pip3', ['install', '--user', 'yt-dlp']);
|
|
1088
|
+
logOk('yt-dlp installed via pip3');
|
|
1089
|
+
return;
|
|
1090
|
+
} catch {
|
|
1091
|
+
// fall through to warn
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
logWarn('Could not install yt-dlp automatically. Install manually: pipx install yt-dlp');
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1055
1098
|
async function cmdInstall() {
|
|
1056
1099
|
heading(`Install ${APP_NAME}`);
|
|
1057
1100
|
if (!fs.existsSync(ENV_FILE)) {
|
|
@@ -1061,6 +1104,7 @@ async function cmdInstall() {
|
|
|
1061
1104
|
|
|
1062
1105
|
installDependencies();
|
|
1063
1106
|
await ensureQemuInstalled();
|
|
1107
|
+
ensureYtDlpInstalled();
|
|
1064
1108
|
buildBundledWebClientIfPossible({ required: true });
|
|
1065
1109
|
|
|
1066
1110
|
const platform = detectPlatform();
|
|
@@ -1122,7 +1166,7 @@ function cmdStop() {
|
|
|
1122
1166
|
logOk(`Stopped pid ${pid}`);
|
|
1123
1167
|
stopped = true;
|
|
1124
1168
|
} catch {
|
|
1125
|
-
logWarn(`pid ${pid} not running`);
|
|
1169
|
+
logWarn(`pid ${pid} was not running (stale PID file)`);
|
|
1126
1170
|
}
|
|
1127
1171
|
}
|
|
1128
1172
|
fs.rmSync(pidPath, { force: true });
|
|
@@ -1183,22 +1227,52 @@ async function cmdStatus() {
|
|
|
1183
1227
|
const port = loadEnvPort();
|
|
1184
1228
|
const running = await isPortOpen(port);
|
|
1185
1229
|
const releaseChannel = currentReleaseChannel();
|
|
1230
|
+
const platform = detectPlatform();
|
|
1186
1231
|
|
|
1187
1232
|
if (running) {
|
|
1188
|
-
logOk(`
|
|
1233
|
+
logOk(`server http://localhost:${port}`);
|
|
1189
1234
|
} else {
|
|
1190
|
-
logWarn(`not reachable on port ${port}`);
|
|
1235
|
+
logWarn(`server not reachable on port ${port}`);
|
|
1191
1236
|
}
|
|
1192
1237
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1238
|
+
if (platform === 'macos' && fs.existsSync(PLIST_DST)) {
|
|
1239
|
+
const svcRes = runQuiet('launchctl', ['list', SERVICE_LABEL]);
|
|
1240
|
+
if (svcRes.status === 0 && svcRes.stdout.trim()) {
|
|
1241
|
+
logOk(`service launchd (${SERVICE_LABEL})`);
|
|
1242
|
+
} else {
|
|
1243
|
+
logWarn(`service launchd unit not loaded — run: neoagent install`);
|
|
1244
|
+
}
|
|
1245
|
+
} else if (platform === 'linux' && fs.existsSync(SYSTEMD_UNIT)) {
|
|
1246
|
+
const svcRes = runQuiet('systemctl', ['--user', 'is-active', 'neoagent']);
|
|
1247
|
+
if (svcRes.status === 0 && svcRes.stdout.trim() === 'active') {
|
|
1248
|
+
logOk('service systemd (neoagent)');
|
|
1249
|
+
} else {
|
|
1250
|
+
logWarn('service systemd unit not active — run: neoagent install');
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
if (fs.existsSync(ENV_FILE)) {
|
|
1255
|
+
logOk(`config ${ENV_FILE}`);
|
|
1256
|
+
} else {
|
|
1257
|
+
logWarn(`config .env not found — run: neoagent setup`);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
if (hasBundledWebClient(WEB_CLIENT_DIR)) {
|
|
1261
|
+
logOk('web bundled Flutter client present');
|
|
1262
|
+
} else {
|
|
1263
|
+
logWarn('web no bundled client — run: neoagent rebuild-web');
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
console.log('');
|
|
1267
|
+
console.log(` install ${APP_DIR}`);
|
|
1268
|
+
console.log(` version ${currentInstalledVersionLabel()}`);
|
|
1269
|
+
console.log(` channel ${releaseChannelSummary(releaseChannel)}`);
|
|
1196
1270
|
|
|
1197
1271
|
const processes = listNeoAgentServerProcesses();
|
|
1198
1272
|
if (processes.length > 0) {
|
|
1199
|
-
console.log(`
|
|
1273
|
+
console.log(` pids ${processes.map((proc) => proc.pid).join(', ')}`);
|
|
1200
1274
|
if (processes.length > 1) {
|
|
1201
|
-
logWarn(`multiple NeoAgent
|
|
1275
|
+
logWarn(`multiple NeoAgent processes detected (${processes.length})`);
|
|
1202
1276
|
}
|
|
1203
1277
|
}
|
|
1204
1278
|
}
|
|
@@ -1260,6 +1334,7 @@ function cmdUpdate(args = []) {
|
|
|
1260
1334
|
const targetBranch = resolvePreferredGitBranch(releaseChannel);
|
|
1261
1335
|
logInfo(`Using git branch ${targetBranch} for the ${releaseChannel} channel.`);
|
|
1262
1336
|
ensureGitBranchForReleaseChannel(targetBranch);
|
|
1337
|
+
backupRuntimeData();
|
|
1263
1338
|
runOrThrow('git', ['pull', '--rebase', '--autostash', 'origin', targetBranch]);
|
|
1264
1339
|
|
|
1265
1340
|
const next = runQuiet('git', ['rev-parse', '--short', 'HEAD']);
|
|
@@ -1289,6 +1364,7 @@ function cmdUpdate(args = []) {
|
|
|
1289
1364
|
}
|
|
1290
1365
|
|
|
1291
1366
|
versionAfter = currentInstalledVersionLabel();
|
|
1367
|
+
ensureYtDlpInstalled();
|
|
1292
1368
|
|
|
1293
1369
|
if (!hasBundledWebClient(WEB_CLIENT_DIR)) {
|
|
1294
1370
|
throw new Error('No bundled Flutter web client found after update.');
|
|
@@ -1300,11 +1376,16 @@ function cmdUpdate(args = []) {
|
|
|
1300
1376
|
|
|
1301
1377
|
async function cmdEnv(args = []) {
|
|
1302
1378
|
heading('Environment Variables');
|
|
1303
|
-
|
|
1379
|
+
const action = (args[0] || '').trim().toLowerCase();
|
|
1304
1380
|
|
|
1305
1381
|
if (!action) {
|
|
1306
|
-
|
|
1307
|
-
|
|
1382
|
+
console.log('Usage: neoagent env <subcommand>');
|
|
1383
|
+
console.log('');
|
|
1384
|
+
console.log(' neoagent env list List all variables (secrets masked)');
|
|
1385
|
+
console.log(' neoagent env get KEY Print a single variable');
|
|
1386
|
+
console.log(' neoagent env set KEY VALUE Set a variable');
|
|
1387
|
+
console.log(' neoagent env unset KEY Remove a variable');
|
|
1388
|
+
return;
|
|
1308
1389
|
}
|
|
1309
1390
|
|
|
1310
1391
|
if (action === 'list') {
|
|
@@ -1329,16 +1410,17 @@ async function cmdEnv(args = []) {
|
|
|
1329
1410
|
}
|
|
1330
1411
|
|
|
1331
1412
|
if (action === 'set') {
|
|
1332
|
-
const key = args[1]
|
|
1333
|
-
const value = args.slice(2).join(' ')
|
|
1413
|
+
const key = args[1];
|
|
1414
|
+
const value = args.slice(2).join(' ');
|
|
1334
1415
|
if (!key || !value) throw new Error('Usage: neoagent env set <KEY> <VALUE>');
|
|
1416
|
+
validateEnvKey(key);
|
|
1335
1417
|
upsertEnvValue(key, value);
|
|
1336
1418
|
logOk(`Set ${key} in ${ENV_FILE}`);
|
|
1337
1419
|
return;
|
|
1338
1420
|
}
|
|
1339
1421
|
|
|
1340
1422
|
if (action === 'unset') {
|
|
1341
|
-
const key = args[1]
|
|
1423
|
+
const key = args[1];
|
|
1342
1424
|
if (!key) throw new Error('Usage: neoagent env unset <KEY>');
|
|
1343
1425
|
removeEnvValue(key);
|
|
1344
1426
|
logOk(`Removed ${key} from ${ENV_FILE}`);
|
|
@@ -1348,16 +1430,55 @@ async function cmdEnv(args = []) {
|
|
|
1348
1430
|
throw new Error('Usage: neoagent env [list|get|set|unset] ...');
|
|
1349
1431
|
}
|
|
1350
1432
|
|
|
1433
|
+
function cmdVersion() {
|
|
1434
|
+
console.log(currentInstalledVersionLabel());
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1351
1437
|
function printHelp() {
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
console.log(
|
|
1438
|
+
const c = COLORS;
|
|
1439
|
+
const W = 38;
|
|
1440
|
+
|
|
1441
|
+
function row(cmd, desc) {
|
|
1442
|
+
const padded = ` neoagent ${cmd}`.padEnd(W);
|
|
1443
|
+
console.log(`${padded}${c.dim}${desc}${c.reset}`);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
console.log(`\n${c.bold}neoagent${c.reset} — manage your NeoAgent server\n`);
|
|
1447
|
+
console.log(`${c.bold}Usage${c.reset} neoagent <command> [args]\n`);
|
|
1448
|
+
|
|
1449
|
+
console.log(`${c.bold}Lifecycle${c.reset}`);
|
|
1450
|
+
row('install', 'First-time install and service setup');
|
|
1451
|
+
row('start', 'Start the server');
|
|
1452
|
+
row('stop', 'Stop the server');
|
|
1453
|
+
row('restart', 'Stop, then start');
|
|
1454
|
+
row('status', 'Health overview (server, service, config)');
|
|
1455
|
+
row('logs', 'Tail server logs');
|
|
1456
|
+
row('uninstall', 'Remove the system service');
|
|
1457
|
+
console.log('');
|
|
1458
|
+
|
|
1459
|
+
console.log(`${c.bold}Configuration${c.reset}`);
|
|
1460
|
+
row('setup', 'Interactive configuration wizard');
|
|
1461
|
+
row('env list', 'List all variables (secrets masked)');
|
|
1462
|
+
row('env get KEY', 'Print a single variable');
|
|
1463
|
+
row('env set KEY VALUE', 'Set a variable');
|
|
1464
|
+
row('env unset KEY', 'Remove a variable');
|
|
1465
|
+
row('channel', 'Show current release channel');
|
|
1466
|
+
row('channel stable|beta', 'Switch release channel');
|
|
1467
|
+
console.log('');
|
|
1468
|
+
|
|
1469
|
+
console.log(`${c.bold}Updates & Auth${c.reset}`);
|
|
1470
|
+
row('update', 'Update to latest on current channel');
|
|
1471
|
+
row('update stable|beta', 'Update and switch channel');
|
|
1472
|
+
row('login github-copilot','Authenticate GitHub Copilot');
|
|
1473
|
+
row('login openai-codex', 'Authenticate OpenAI Codex');
|
|
1474
|
+
console.log('');
|
|
1475
|
+
|
|
1476
|
+
console.log(`${c.bold}Maintenance${c.reset}`);
|
|
1477
|
+
row('migrate', 'Migrate from another agent installation');
|
|
1478
|
+
row('migrate dry-run', 'Preview what would be migrated');
|
|
1479
|
+
row('rebuild-web', 'Rebuild the bundled Flutter web client');
|
|
1480
|
+
row('version', 'Print installed version');
|
|
1481
|
+
console.log('');
|
|
1361
1482
|
}
|
|
1362
1483
|
|
|
1363
1484
|
async function runCLI(argv) {
|
|
@@ -1408,13 +1529,18 @@ async function runCLI(argv) {
|
|
|
1408
1529
|
case 'login':
|
|
1409
1530
|
await cmdLogin(argv.slice(1));
|
|
1410
1531
|
break;
|
|
1532
|
+
case 'version':
|
|
1533
|
+
case '--version':
|
|
1534
|
+
case '-V':
|
|
1535
|
+
cmdVersion();
|
|
1536
|
+
break;
|
|
1411
1537
|
case 'help':
|
|
1412
1538
|
case '--help':
|
|
1413
1539
|
case '-h':
|
|
1414
1540
|
printHelp();
|
|
1415
1541
|
break;
|
|
1416
1542
|
default:
|
|
1417
|
-
throw new Error(`Unknown command: ${command}
|
|
1543
|
+
throw new Error(`Unknown command: ${command}. Run "neoagent --help" for usage.`);
|
|
1418
1544
|
}
|
|
1419
1545
|
}
|
|
1420
1546
|
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
2700d443d51328af53dfc4e4cb2cec1f
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "3348826037" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|