nemoris 0.1.1 → 0.1.2
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/README.md +1 -1
- package/SECURITY.md +16 -0
- package/package.json +1 -1
- package/src/onboarding/phases/auth.js +53 -66
- package/src/onboarding/wizard.js +23 -6
package/README.md
CHANGED
package/SECURITY.md
CHANGED
|
@@ -38,6 +38,22 @@ We follow responsible disclosure practices. If you report a vulnerability:
|
|
|
38
38
|
- We will credit you in the release notes (unless you prefer anonymity)
|
|
39
39
|
- We will coordinate disclosure timing with you
|
|
40
40
|
|
|
41
|
+
## Deployment Boundaries
|
|
42
|
+
|
|
43
|
+
Nemoris is a single operator, single-user runtime designed to run on your own machine.
|
|
44
|
+
|
|
45
|
+
It is not designed to be:
|
|
46
|
+
|
|
47
|
+
- a hardened multi-tenant sandbox
|
|
48
|
+
- a public-facing web service
|
|
49
|
+
- a shared server runtime
|
|
50
|
+
|
|
51
|
+
For recovery procedures, see [docs/RECOVERY-FLOWS.md](docs/RECOVERY-FLOWS.md).
|
|
52
|
+
|
|
53
|
+
## Vulnerability Tracking
|
|
54
|
+
|
|
55
|
+
Known vulnerabilities are tracked via GitHub Security Advisories on this repository.
|
|
56
|
+
|
|
41
57
|
## Dependencies
|
|
42
58
|
|
|
43
59
|
Nemoris keeps dependencies minimal by design. We monitor for known vulnerabilities via `npm audit` and update promptly.
|
package/package.json
CHANGED
|
@@ -24,7 +24,6 @@ import {
|
|
|
24
24
|
resolveOpenAICodexAccess,
|
|
25
25
|
} from "../../auth/openai-codex-oauth.js";
|
|
26
26
|
import { getAuthProfile, resolveAuthProfilesPath } from "../../auth/auth-profiles.js";
|
|
27
|
-
import { buildModelSelectionOptions, fetchOpenAIModels } from "../model-catalog.js";
|
|
28
27
|
|
|
29
28
|
const PROVIDER_CONFIGS = {
|
|
30
29
|
anthropic: {
|
|
@@ -179,25 +178,23 @@ function providerDisplayName(provider) {
|
|
|
179
178
|
}
|
|
180
179
|
|
|
181
180
|
async function fetchProviderModelIds(provider, key, { fetchImpl = globalThis.fetch } = {}) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
: null;
|
|
181
|
+
const targets = {
|
|
182
|
+
anthropic: {
|
|
183
|
+
url: "https://api.anthropic.com/v1/models",
|
|
184
|
+
headers: { "x-api-key": key, "anthropic-version": "2023-06-01" },
|
|
185
|
+
},
|
|
186
|
+
openrouter: {
|
|
187
|
+
url: "https://openrouter.ai/api/v1/models",
|
|
188
|
+
headers: { authorization: `Bearer ${key}` },
|
|
189
|
+
},
|
|
190
|
+
openai: {
|
|
191
|
+
url: "https://api.openai.com/v1/models",
|
|
192
|
+
headers: { authorization: `Bearer ${key}` },
|
|
193
|
+
},
|
|
194
|
+
};
|
|
197
195
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
196
|
+
const target = targets[provider];
|
|
197
|
+
if (!target) return [];
|
|
201
198
|
|
|
202
199
|
try {
|
|
203
200
|
const response = await fetchImpl(target.url, {
|
|
@@ -206,48 +203,43 @@ async function fetchProviderModelIds(provider, key, { fetchImpl = globalThis.fet
|
|
|
206
203
|
signal: AbortSignal.timeout(10000),
|
|
207
204
|
});
|
|
208
205
|
const data = await response.json();
|
|
209
|
-
if (!response.ok)
|
|
210
|
-
return [];
|
|
211
|
-
}
|
|
206
|
+
if (!response.ok) return PROVIDER_MODEL_PRESETS[provider]?.map((item) => item.id) ?? [];
|
|
212
207
|
const ids = Array.isArray(data?.data)
|
|
213
208
|
? data.data.map((item) => item?.id).filter(Boolean)
|
|
214
209
|
: [];
|
|
215
|
-
|
|
216
|
-
return ids.map((id) => ensureProviderModelPrefix(provider, id));
|
|
217
|
-
}
|
|
218
|
-
if (provider === "openai") {
|
|
219
|
-
return ids.map((id) => ensureProviderModelPrefix(provider, id));
|
|
220
|
-
}
|
|
221
|
-
return ids;
|
|
210
|
+
return ids.map((id) => ensureProviderModelPrefix(provider, id));
|
|
222
211
|
} catch {
|
|
223
|
-
return [];
|
|
212
|
+
return PROVIDER_MODEL_PRESETS[provider]?.map((item) => item.id) ?? [];
|
|
224
213
|
}
|
|
225
214
|
}
|
|
226
215
|
|
|
227
216
|
async function buildProviderSelectionOptions(provider, key, { fetchImpl = globalThis.fetch } = {}) {
|
|
228
|
-
if (provider === "openai") {
|
|
229
|
-
const discoveredModels = await fetchOpenAIModels(key, { fetchImpl });
|
|
230
|
-
return buildModelSelectionOptions({
|
|
231
|
-
provider,
|
|
232
|
-
discoveredModels,
|
|
233
|
-
includeKeep: false,
|
|
234
|
-
includeManual: true,
|
|
235
|
-
}).map((entry) => ({
|
|
236
|
-
value: entry.value,
|
|
237
|
-
label: entry.label,
|
|
238
|
-
description: entry.hint,
|
|
239
|
-
}));
|
|
240
|
-
}
|
|
241
|
-
|
|
242
217
|
const curated = PROVIDER_MODEL_PRESETS[provider] || [];
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
218
|
+
const fetched = await fetchProviderModelIds(provider, key, { fetchImpl });
|
|
219
|
+
const fetchedSet = new Set(fetched);
|
|
220
|
+
|
|
221
|
+
// Curated models that exist in the fetched list (fall back to all curated if fetch failed)
|
|
222
|
+
const curatedAvailable = fetched.length > 0
|
|
223
|
+
? curated.filter((item) => fetchedSet.has(item.id))
|
|
224
|
+
: curated;
|
|
225
|
+
const curatedIds = new Set(curatedAvailable.map((item) => item.id));
|
|
226
|
+
|
|
227
|
+
// Remaining fetched models not already shown as curated
|
|
228
|
+
const extra = fetched
|
|
229
|
+
.filter((id) => !curatedIds.has(id))
|
|
230
|
+
.map((id) => {
|
|
231
|
+
const displayId = id
|
|
232
|
+
.replace(/^openrouter\//, "")
|
|
233
|
+
.replace(/^openai-codex\//, "")
|
|
234
|
+
.replace(/^anthropic\//, "");
|
|
235
|
+
return { value: id, label: displayId, description: "available from provider" };
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return [
|
|
239
|
+
...curatedAvailable.map((item) => ({ value: item.id, label: item.label, description: item.description })),
|
|
240
|
+
...extra,
|
|
241
|
+
{ value: "__custom__", label: "Enter a different model name...", description: "Use a specific model id not shown in the list." },
|
|
242
|
+
];
|
|
251
243
|
}
|
|
252
244
|
|
|
253
245
|
async function promptForProviderModels(provider, key, tui, { fetchImpl = globalThis.fetch } = {}) {
|
|
@@ -256,7 +248,7 @@ async function promptForProviderModels(provider, key, tui, { fetchImpl = globalT
|
|
|
256
248
|
|
|
257
249
|
const options = await buildProviderSelectionOptions(provider, key, { fetchImpl });
|
|
258
250
|
const chosen = [];
|
|
259
|
-
const manualOptionValue =
|
|
251
|
+
const manualOptionValue = "__custom__";
|
|
260
252
|
const defaultModelValue = options.find((item) => !String(item.value).startsWith("__"))?.value || "";
|
|
261
253
|
|
|
262
254
|
console.log(`\n ${cyan(`Choose ${provider === "openrouter" ? "OpenRouter" : provider === "openai" ? "OpenAI" : "Anthropic"} models`)}`);
|
|
@@ -271,19 +263,14 @@ async function promptForProviderModels(provider, key, tui, { fetchImpl = globalT
|
|
|
271
263
|
}));
|
|
272
264
|
|
|
273
265
|
if (chosen.length > 0) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
pickerOptions.push({
|
|
283
|
-
label: "Enter a different model name...",
|
|
284
|
-
value: manualOptionValue,
|
|
285
|
-
description: "Use a specific model id not shown in the curated list.",
|
|
286
|
-
});
|
|
266
|
+
// Insert "Done" before the custom-entry option at the end
|
|
267
|
+
const customIndex = pickerOptions.findIndex((item) => item.value === "__custom__");
|
|
268
|
+
const doneOption = { label: "Done", value: "__done__", description: "Continue setup with the models already selected." };
|
|
269
|
+
if (customIndex >= 0) {
|
|
270
|
+
pickerOptions.splice(customIndex, 0, doneOption);
|
|
271
|
+
} else {
|
|
272
|
+
pickerOptions.push(doneOption);
|
|
273
|
+
}
|
|
287
274
|
}
|
|
288
275
|
|
|
289
276
|
const picked = await select(
|
package/src/onboarding/wizard.js
CHANGED
|
@@ -164,6 +164,23 @@ async function runInteractiveWizard({
|
|
|
164
164
|
flowOverride = null,
|
|
165
165
|
}) {
|
|
166
166
|
const prompter = createClackPrompter();
|
|
167
|
+
|
|
168
|
+
// ASCII banner — ANSI Shadow font, brand accent colour
|
|
169
|
+
const BRAND = "\x1b[38;2;45;212;191m";
|
|
170
|
+
const RESET = "\x1b[0m";
|
|
171
|
+
const DIM = "\x1b[2m";
|
|
172
|
+
const ascii = [
|
|
173
|
+
"",
|
|
174
|
+
`${BRAND} ███╗ ██╗███████╗███╗ ███╗ ██████╗ ██████╗ ██╗███████╗${RESET}`,
|
|
175
|
+
`${BRAND} ████╗ ██║██╔════╝████╗ ████║██╔═══██╗██╔══██╗██║██╔════╝${RESET}`,
|
|
176
|
+
`${BRAND} ██╔██╗ ██║█████╗ ██╔████╔██║██║ ██║██████╔╝██║███████╗${RESET}`,
|
|
177
|
+
`${BRAND} ██║╚██╗██║██╔══╝ ██║╚██╔╝██║██║ ██║██╔══██╗██║╚════██║${RESET}`,
|
|
178
|
+
`${BRAND} ██║ ╚████║███████╗██║ ╚═╝ ██║╚██████╔╝██║ ██║██║███████║${RESET}`,
|
|
179
|
+
`${BRAND} ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚══════╝${RESET}`,
|
|
180
|
+
"",
|
|
181
|
+
].join("\n");
|
|
182
|
+
console.log(ascii);
|
|
183
|
+
|
|
167
184
|
await prompter.intro("Nemoris setup");
|
|
168
185
|
|
|
169
186
|
await prompter.note([
|
|
@@ -193,9 +210,9 @@ async function runInteractiveWizard({
|
|
|
193
210
|
const action = await prompter.select({
|
|
194
211
|
message: "Config handling",
|
|
195
212
|
options: [
|
|
196
|
-
{ value: "keep", label: "Use existing values" },
|
|
197
|
-
{ value: "update", label: "Update values" },
|
|
198
|
-
{ value: "reset", label: "Reset everything" },
|
|
213
|
+
{ value: "keep", label: "Use existing values", hint: "Skip setup — keep current config and start running" },
|
|
214
|
+
{ value: "update", label: "Update values", hint: "Walk through setup again, pre-filled with current config" },
|
|
215
|
+
{ value: "reset", label: "Reset everything", hint: "Wipe and start fresh" },
|
|
199
216
|
],
|
|
200
217
|
});
|
|
201
218
|
|
|
@@ -208,9 +225,9 @@ async function runInteractiveWizard({
|
|
|
208
225
|
const scope = await prompter.select({
|
|
209
226
|
message: "Reset scope",
|
|
210
227
|
options: [
|
|
211
|
-
{ value: "config", label: "Config only" },
|
|
212
|
-
{ value: "config+state", label: "Config + state
|
|
213
|
-
{ value: "full", label: "Full reset
|
|
228
|
+
{ value: "config", label: "Config only", hint: "Rewrites agents, router, and provider config. Keeps memory and history." },
|
|
229
|
+
{ value: "config+state", label: "Config + state", hint: "Config + wipes memory, run history, and scheduler data. Agent identities kept." },
|
|
230
|
+
{ value: "full", label: "Full reset", hint: "Deletes everything and starts completely fresh. Like a first install." },
|
|
214
231
|
],
|
|
215
232
|
});
|
|
216
233
|
resetInstallArtifacts(installDir, scope);
|