@sonnechasser/ntrp 0.1.8 → 0.2.1
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/index.js +1933 -623
- package/dist/index.js.map +1 -1
- package/dist/investigation/verbosity-cli.js +903 -205
- package/dist/investigation/verbosity-cli.js.map +1 -1
- package/dist/mcp/server.js +928 -225
- package/dist/mcp/server.js.map +1 -1
- package/dist/whimsy/time-bank-smoke.js +1810 -495
- package/dist/whimsy/time-bank-smoke.js.map +1 -1
- package/package.json +2 -1
|
@@ -359,7 +359,7 @@ function migrateUsageIfNeeded(state) {
|
|
|
359
359
|
if (state.usage?.first_active_at) return { state, changed: false };
|
|
360
360
|
const fromCredits = rebuildFromCredits(state.credits);
|
|
361
361
|
const prior = state.usage;
|
|
362
|
-
const
|
|
362
|
+
const usage3 = {
|
|
363
363
|
sessions_closed: prior?.sessions_closed ?? 0,
|
|
364
364
|
llm_calls: prior?.llm_calls ?? 0,
|
|
365
365
|
input_tokens: prior?.input_tokens ?? 0,
|
|
@@ -367,7 +367,7 @@ function migrateUsageIfNeeded(state) {
|
|
|
367
367
|
...fromCredits,
|
|
368
368
|
weekly: mergeWeekly(prior?.weekly ?? [], fromCredits.weekly)
|
|
369
369
|
};
|
|
370
|
-
return { state: { ...state, usage:
|
|
370
|
+
return { state: { ...state, usage: usage3 }, changed: true };
|
|
371
371
|
}
|
|
372
372
|
var init_usage_backfill = __esm({
|
|
373
373
|
"src/whimsy/usage-backfill.ts"() {
|
|
@@ -1212,48 +1212,48 @@ function bumpWeekly2(weekly, patch) {
|
|
|
1212
1212
|
}
|
|
1213
1213
|
function touchUsage(state, patch) {
|
|
1214
1214
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
1215
|
-
const
|
|
1215
|
+
const usage3 = ensureUsage(state);
|
|
1216
1216
|
return {
|
|
1217
1217
|
...state,
|
|
1218
1218
|
usage: {
|
|
1219
|
-
...
|
|
1219
|
+
...usage3,
|
|
1220
1220
|
...patch,
|
|
1221
|
-
first_active_at:
|
|
1221
|
+
first_active_at: usage3.first_active_at ?? now2,
|
|
1222
1222
|
last_active_at: now2,
|
|
1223
|
-
weekly: patch.weekly ??
|
|
1223
|
+
weekly: patch.weekly ?? usage3.weekly
|
|
1224
1224
|
}
|
|
1225
1225
|
};
|
|
1226
1226
|
}
|
|
1227
1227
|
function recordUsageFromCredit(action, minutes) {
|
|
1228
1228
|
if (minutes <= 0) return;
|
|
1229
1229
|
let state = loadProgress();
|
|
1230
|
-
const
|
|
1231
|
-
const weekly = bumpWeekly2(
|
|
1230
|
+
const usage3 = ensureUsage(state);
|
|
1231
|
+
const weekly = bumpWeekly2(usage3.weekly, { minutes_saved: minutes, actions: 1 });
|
|
1232
1232
|
const counters = { weekly };
|
|
1233
|
-
if (action === "diagnose" || action === "diagnose_findings") counters.diagnoses =
|
|
1234
|
-
if (action === "metrics" || action === "metrics_findings") counters.metrics_runs =
|
|
1235
|
-
if (action === "deliverable" || action === "deliverable_deck") counters.deliverables =
|
|
1236
|
-
if (action === "nl_answer") counters.nl_exchanges =
|
|
1233
|
+
if (action === "diagnose" || action === "diagnose_findings") counters.diagnoses = usage3.diagnoses + 1;
|
|
1234
|
+
if (action === "metrics" || action === "metrics_findings") counters.metrics_runs = usage3.metrics_runs + 1;
|
|
1235
|
+
if (action === "deliverable" || action === "deliverable_deck") counters.deliverables = usage3.deliverables + 1;
|
|
1236
|
+
if (action === "nl_answer") counters.nl_exchanges = usage3.nl_exchanges + 1;
|
|
1237
1237
|
state = touchUsage(state, counters);
|
|
1238
1238
|
saveProgress(state);
|
|
1239
1239
|
}
|
|
1240
1240
|
function recordSessionClosed() {
|
|
1241
1241
|
let state = loadProgress();
|
|
1242
|
-
const
|
|
1242
|
+
const usage3 = ensureUsage(state);
|
|
1243
1243
|
state = touchUsage(state, {
|
|
1244
|
-
sessions_closed:
|
|
1245
|
-
weekly: bumpWeekly2(
|
|
1244
|
+
sessions_closed: usage3.sessions_closed + 1,
|
|
1245
|
+
weekly: bumpWeekly2(usage3.weekly, { actions: 1 })
|
|
1246
1246
|
});
|
|
1247
1247
|
saveProgress(state);
|
|
1248
1248
|
}
|
|
1249
1249
|
function recordLlmUsage(tokenUsage) {
|
|
1250
1250
|
let state = loadProgress();
|
|
1251
|
-
const
|
|
1252
|
-
const weekly = bumpWeekly2(
|
|
1251
|
+
const usage3 = ensureUsage(state);
|
|
1252
|
+
const weekly = bumpWeekly2(usage3.weekly, { llm_calls: 1 });
|
|
1253
1253
|
state = touchUsage(state, {
|
|
1254
|
-
llm_calls:
|
|
1255
|
-
input_tokens:
|
|
1256
|
-
output_tokens:
|
|
1254
|
+
llm_calls: usage3.llm_calls + 1,
|
|
1255
|
+
input_tokens: usage3.input_tokens + (tokenUsage?.input_tokens ?? 0),
|
|
1256
|
+
output_tokens: usage3.output_tokens + (tokenUsage?.output_tokens ?? 0),
|
|
1257
1257
|
weekly
|
|
1258
1258
|
});
|
|
1259
1259
|
saveProgress(state);
|
|
@@ -6639,55 +6639,108 @@ var init_markdown = __esm({
|
|
|
6639
6639
|
}
|
|
6640
6640
|
});
|
|
6641
6641
|
|
|
6642
|
-
// src/ai/llm/
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
if (seen.has(current)) break;
|
|
6655
|
-
seen.add(current);
|
|
6656
|
-
const entry = byId.get(current);
|
|
6657
|
-
if (!entry) return current;
|
|
6658
|
-
if (entry.status === "active") return entry.id;
|
|
6659
|
-
if (!entry.successor_id) {
|
|
6660
|
-
const fallback = cheapestActiveInTier(entry.provider, entry.tier);
|
|
6661
|
-
return fallback?.id ?? current;
|
|
6662
|
-
}
|
|
6663
|
-
current = entry.successor_id;
|
|
6642
|
+
// src/ai/llm/models-cache.ts
|
|
6643
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
6644
|
+
import { join as join9 } from "path";
|
|
6645
|
+
function cachePath() {
|
|
6646
|
+
return join9(ntrpHome(), "models.json");
|
|
6647
|
+
}
|
|
6648
|
+
function loadFile() {
|
|
6649
|
+
if (cached) return cached;
|
|
6650
|
+
const path = cachePath();
|
|
6651
|
+
if (!existsSync9(path)) {
|
|
6652
|
+
cached = { version: 1, providers: {} };
|
|
6653
|
+
return cached;
|
|
6664
6654
|
}
|
|
6665
|
-
|
|
6655
|
+
try {
|
|
6656
|
+
const parsed = JSON.parse(readFileSync7(path, "utf-8"));
|
|
6657
|
+
cached = { version: 1, providers: parsed.providers ?? {} };
|
|
6658
|
+
} catch {
|
|
6659
|
+
cached = { version: 1, providers: {} };
|
|
6660
|
+
}
|
|
6661
|
+
return cached;
|
|
6662
|
+
}
|
|
6663
|
+
function saveFile(file) {
|
|
6664
|
+
writeFileSync7(cachePath(), JSON.stringify(file, null, 2) + "\n");
|
|
6665
|
+
cached = file;
|
|
6666
|
+
}
|
|
6667
|
+
function getProviderModels(provider) {
|
|
6668
|
+
return loadFile().providers[provider];
|
|
6666
6669
|
}
|
|
6667
|
-
function
|
|
6670
|
+
function setProviderModels(provider, entry) {
|
|
6671
|
+
const file = loadFile();
|
|
6672
|
+
file.providers[provider] = entry;
|
|
6673
|
+
saveFile(file);
|
|
6674
|
+
}
|
|
6675
|
+
function getCachedTierModel(provider, tier) {
|
|
6676
|
+
return getProviderModels(provider)?.tier_stack?.[tier];
|
|
6677
|
+
}
|
|
6678
|
+
function findCachedModel(provider, modelId) {
|
|
6679
|
+
return getProviderModels(provider)?.models.find((m) => m.id === modelId);
|
|
6680
|
+
}
|
|
6681
|
+
function cachedModelProvider(modelId) {
|
|
6682
|
+
const file = loadFile();
|
|
6683
|
+
for (const [provider, entry] of Object.entries(file.providers)) {
|
|
6684
|
+
if (entry.models.some((m) => m.id === modelId)) return provider;
|
|
6685
|
+
}
|
|
6686
|
+
return void 0;
|
|
6687
|
+
}
|
|
6688
|
+
function markModelNoTools(provider, modelId) {
|
|
6689
|
+
const file = loadFile();
|
|
6690
|
+
const entry = file.providers[provider];
|
|
6691
|
+
if (!entry) return;
|
|
6692
|
+
const noTools = new Set(entry.quirks?.no_tools ?? []);
|
|
6693
|
+
if (noTools.has(modelId)) return;
|
|
6694
|
+
noTools.add(modelId);
|
|
6695
|
+
entry.quirks = { ...entry.quirks, no_tools: [...noTools] };
|
|
6696
|
+
saveFile(file);
|
|
6697
|
+
}
|
|
6698
|
+
function modelHasNoToolsQuirk(provider, modelId) {
|
|
6699
|
+
return !!getProviderModels(provider)?.quirks?.no_tools?.includes(modelId);
|
|
6700
|
+
}
|
|
6701
|
+
function isProviderCacheStale(provider, ttlMs = CACHE_TTL_MS) {
|
|
6702
|
+
const entry = getProviderModels(provider);
|
|
6703
|
+
if (!entry) return true;
|
|
6704
|
+
const fetched = Date.parse(entry.fetched_at);
|
|
6705
|
+
if (Number.isNaN(fetched)) return true;
|
|
6706
|
+
return Date.now() - fetched > ttlMs;
|
|
6707
|
+
}
|
|
6708
|
+
var CACHE_TTL_MS, cached;
|
|
6709
|
+
var init_models_cache = __esm({
|
|
6710
|
+
"src/ai/llm/models-cache.ts"() {
|
|
6711
|
+
"use strict";
|
|
6712
|
+
init_store();
|
|
6713
|
+
CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
6714
|
+
cached = null;
|
|
6715
|
+
}
|
|
6716
|
+
});
|
|
6717
|
+
|
|
6718
|
+
// src/ai/llm/catalog.ts
|
|
6719
|
+
function catalogTierDefault(provider, tier) {
|
|
6668
6720
|
const candidates = ENTRIES.filter(
|
|
6669
6721
|
(e) => e.provider === provider && e.tier === tier && e.status === "active"
|
|
6670
6722
|
);
|
|
6671
6723
|
if (candidates.length === 0) return void 0;
|
|
6672
6724
|
return candidates.sort((a, b) => a.relative_cost - b.relative_cost)[0];
|
|
6673
6725
|
}
|
|
6674
|
-
function
|
|
6675
|
-
|
|
6676
|
-
if (!entry) {
|
|
6677
|
-
throw new Error(`No active ${tier}-tier model for provider ${provider} in catalog`);
|
|
6678
|
-
}
|
|
6679
|
-
return entry;
|
|
6726
|
+
function modelProviderHint(modelId) {
|
|
6727
|
+
return cachedModelProvider(modelId) ?? byId.get(modelId)?.provider;
|
|
6680
6728
|
}
|
|
6681
|
-
function
|
|
6682
|
-
if (override)
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6729
|
+
function overrideForProvider(override, provider, activeProvider) {
|
|
6730
|
+
if (!override) return void 0;
|
|
6731
|
+
const hint = modelProviderHint(override);
|
|
6732
|
+
if (hint) return hint === provider ? override : void 0;
|
|
6733
|
+
return provider === activeProvider ? override : void 0;
|
|
6734
|
+
}
|
|
6735
|
+
function resolveModelSafe(provider, tier, override) {
|
|
6736
|
+
if (override) return override;
|
|
6737
|
+
const discovered = getCachedTierModel(provider, tier);
|
|
6738
|
+
if (discovered) return discovered;
|
|
6739
|
+
return catalogTierDefault(provider, tier)?.id;
|
|
6689
6740
|
}
|
|
6690
6741
|
function formatModelLabel(provider, modelId) {
|
|
6742
|
+
const cachedName = findCachedModel(provider, modelId)?.display_name;
|
|
6743
|
+
if (cachedName) return `${provider}/${cachedName}`;
|
|
6691
6744
|
const entry = byId.get(modelId);
|
|
6692
6745
|
return entry ? `${provider}/${entry.display_name}` : `${provider}/${modelId}`;
|
|
6693
6746
|
}
|
|
@@ -6695,6 +6748,7 @@ var ENTRIES, byId;
|
|
|
6695
6748
|
var init_catalog = __esm({
|
|
6696
6749
|
"src/ai/llm/catalog.ts"() {
|
|
6697
6750
|
"use strict";
|
|
6751
|
+
init_models_cache();
|
|
6698
6752
|
ENTRIES = [
|
|
6699
6753
|
{
|
|
6700
6754
|
id: "claude-opus-4-6",
|
|
@@ -6779,6 +6833,9 @@ function formatLlmAttribution(meta) {
|
|
|
6779
6833
|
return line;
|
|
6780
6834
|
}
|
|
6781
6835
|
function printLlmAttribution(meta) {
|
|
6836
|
+
for (const notice of meta.notices ?? []) {
|
|
6837
|
+
console.log(chalk6.dim(` ${notice}`));
|
|
6838
|
+
}
|
|
6782
6839
|
const line = formatLlmAttribution(meta);
|
|
6783
6840
|
if (line) console.log(chalk6.dim(` ${line}`));
|
|
6784
6841
|
}
|
|
@@ -7096,6 +7153,7 @@ async function renderDiagnoseStream(options) {
|
|
|
7096
7153
|
let modelUsed = "";
|
|
7097
7154
|
let providerUsed;
|
|
7098
7155
|
let failover;
|
|
7156
|
+
let notices;
|
|
7099
7157
|
let rawPrompt = "";
|
|
7100
7158
|
try {
|
|
7101
7159
|
for await (const event of runFindings(fullResult)) {
|
|
@@ -7111,6 +7169,7 @@ async function renderDiagnoseStream(options) {
|
|
|
7111
7169
|
modelUsed = event.model_used;
|
|
7112
7170
|
providerUsed = event.provider_used;
|
|
7113
7171
|
failover = event.failover;
|
|
7172
|
+
notices = event.usage?.notices;
|
|
7114
7173
|
rawPrompt = event.raw_prompt;
|
|
7115
7174
|
}
|
|
7116
7175
|
}
|
|
@@ -7134,7 +7193,8 @@ async function renderDiagnoseStream(options) {
|
|
|
7134
7193
|
printLlmAttribution({
|
|
7135
7194
|
model_used: modelUsed,
|
|
7136
7195
|
provider_used: providerUsed,
|
|
7137
|
-
failover
|
|
7196
|
+
failover,
|
|
7197
|
+
notices
|
|
7138
7198
|
});
|
|
7139
7199
|
} catch (err) {
|
|
7140
7200
|
findingsSpinner.fail(deep ? "Agentic investigation failed" : "AI findings failed");
|
|
@@ -7189,10 +7249,232 @@ var init_terminal = __esm({
|
|
|
7189
7249
|
}
|
|
7190
7250
|
});
|
|
7191
7251
|
|
|
7252
|
+
// src/ai/llm/providers.ts
|
|
7253
|
+
var providers_exports = {};
|
|
7254
|
+
__export(providers_exports, {
|
|
7255
|
+
findSpecByConfigKey: () => findSpecByConfigKey,
|
|
7256
|
+
getProviderSpec: () => getProviderSpec,
|
|
7257
|
+
isEndpointEnabled: () => isEndpointEnabled,
|
|
7258
|
+
keyConfigNameFor: () => keyConfigNameFor,
|
|
7259
|
+
listProviderSpecs: () => listProviderSpecs,
|
|
7260
|
+
loadCustomProviders: () => loadCustomProviders,
|
|
7261
|
+
modelsUrl: () => modelsUrl,
|
|
7262
|
+
providerLabel: () => providerLabel,
|
|
7263
|
+
removeCustomProvider: () => removeCustomProvider,
|
|
7264
|
+
resetProvidersCache: () => resetProvidersCache,
|
|
7265
|
+
saveCustomProvider: () => saveCustomProvider
|
|
7266
|
+
});
|
|
7267
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
7268
|
+
import { join as join10 } from "path";
|
|
7269
|
+
function providersPath() {
|
|
7270
|
+
return join10(ntrpHome(), "providers.json");
|
|
7271
|
+
}
|
|
7272
|
+
function loadCustomProviders() {
|
|
7273
|
+
if (cachedEntries) return cachedEntries;
|
|
7274
|
+
const path = providersPath();
|
|
7275
|
+
if (!existsSync10(path)) {
|
|
7276
|
+
cachedEntries = [];
|
|
7277
|
+
return cachedEntries;
|
|
7278
|
+
}
|
|
7279
|
+
try {
|
|
7280
|
+
const parsed = JSON.parse(readFileSync8(path, "utf-8"));
|
|
7281
|
+
cachedEntries = Array.isArray(parsed.providers) ? parsed.providers : [];
|
|
7282
|
+
} catch {
|
|
7283
|
+
cachedEntries = [];
|
|
7284
|
+
}
|
|
7285
|
+
return cachedEntries;
|
|
7286
|
+
}
|
|
7287
|
+
function saveCustomProvider(entry) {
|
|
7288
|
+
const entries = loadCustomProviders().filter((e) => e.id !== entry.id);
|
|
7289
|
+
entries.push(entry);
|
|
7290
|
+
writeFileSync8(providersPath(), JSON.stringify({ version: 1, providers: entries }, null, 2) + "\n");
|
|
7291
|
+
cachedEntries = entries;
|
|
7292
|
+
}
|
|
7293
|
+
function removeCustomProvider(id) {
|
|
7294
|
+
const entries = loadCustomProviders().filter((e) => e.id !== id);
|
|
7295
|
+
writeFileSync8(providersPath(), JSON.stringify({ version: 1, providers: entries }, null, 2) + "\n");
|
|
7296
|
+
cachedEntries = entries;
|
|
7297
|
+
}
|
|
7298
|
+
function resetProvidersCache() {
|
|
7299
|
+
cachedEntries = null;
|
|
7300
|
+
}
|
|
7301
|
+
function customEntryToSpec(entry) {
|
|
7302
|
+
return {
|
|
7303
|
+
id: entry.id,
|
|
7304
|
+
label: entry.label ?? entry.id,
|
|
7305
|
+
api: "openai-compat",
|
|
7306
|
+
base_url: entry.base_url.replace(/\/+$/, ""),
|
|
7307
|
+
key_prefixes: [],
|
|
7308
|
+
shared_prefixes: [],
|
|
7309
|
+
key_config_name: keyConfigNameFor(entry.id),
|
|
7310
|
+
requires_key: entry.requires_key ?? false,
|
|
7311
|
+
custom: true
|
|
7312
|
+
};
|
|
7313
|
+
}
|
|
7314
|
+
function keyConfigNameFor(providerId) {
|
|
7315
|
+
return providerId === "anthropic" ? "api-key" : `${providerId}-api-key`;
|
|
7316
|
+
}
|
|
7317
|
+
function listProviderSpecs() {
|
|
7318
|
+
const customs = loadCustomProviders();
|
|
7319
|
+
const customById = new Map(customs.map((e) => [e.id, e]));
|
|
7320
|
+
const specs = BUILTIN_SPECS.map((spec) => {
|
|
7321
|
+
const override = customById.get(spec.id);
|
|
7322
|
+
if (override?.base_url) {
|
|
7323
|
+
return { ...spec, base_url: override.base_url.replace(/\/+$/, "") };
|
|
7324
|
+
}
|
|
7325
|
+
return spec;
|
|
7326
|
+
});
|
|
7327
|
+
for (const entry of customs) {
|
|
7328
|
+
if (!BUILTIN_SPECS.some((s) => s.id === entry.id)) {
|
|
7329
|
+
specs.push(customEntryToSpec(entry));
|
|
7330
|
+
}
|
|
7331
|
+
}
|
|
7332
|
+
return specs;
|
|
7333
|
+
}
|
|
7334
|
+
function getProviderSpec(id) {
|
|
7335
|
+
return listProviderSpecs().find((s) => s.id === id);
|
|
7336
|
+
}
|
|
7337
|
+
function findSpecByConfigKey(configKey) {
|
|
7338
|
+
return listProviderSpecs().find((s) => s.key_config_name === configKey);
|
|
7339
|
+
}
|
|
7340
|
+
function isEndpointEnabled(id) {
|
|
7341
|
+
const entry = loadCustomProviders().find((e) => e.id === id);
|
|
7342
|
+
return !!entry && entry.enabled !== false;
|
|
7343
|
+
}
|
|
7344
|
+
function modelsUrl(spec) {
|
|
7345
|
+
if (spec.api === "anthropic") return `${spec.base_url}/v1/models?limit=100`;
|
|
7346
|
+
return `${spec.base_url}/models`;
|
|
7347
|
+
}
|
|
7348
|
+
function providerLabel(id) {
|
|
7349
|
+
return getProviderSpec(id)?.label ?? id;
|
|
7350
|
+
}
|
|
7351
|
+
var BUILTIN_SPECS, cachedEntries;
|
|
7352
|
+
var init_providers = __esm({
|
|
7353
|
+
"src/ai/llm/providers.ts"() {
|
|
7354
|
+
"use strict";
|
|
7355
|
+
init_store();
|
|
7356
|
+
BUILTIN_SPECS = [
|
|
7357
|
+
{
|
|
7358
|
+
id: "anthropic",
|
|
7359
|
+
label: "Anthropic",
|
|
7360
|
+
api: "anthropic",
|
|
7361
|
+
base_url: "https://api.anthropic.com",
|
|
7362
|
+
key_prefixes: ["sk-ant-"],
|
|
7363
|
+
shared_prefixes: [],
|
|
7364
|
+
key_config_name: "api-key",
|
|
7365
|
+
requires_key: true
|
|
7366
|
+
},
|
|
7367
|
+
{
|
|
7368
|
+
id: "openai",
|
|
7369
|
+
label: "OpenAI",
|
|
7370
|
+
api: "openai-compat",
|
|
7371
|
+
base_url: "https://api.openai.com/v1",
|
|
7372
|
+
key_prefixes: ["sk-proj-", "sk-svcacct-", "sk-admin-"],
|
|
7373
|
+
shared_prefixes: ["sk-"],
|
|
7374
|
+
key_config_name: "openai-api-key",
|
|
7375
|
+
env_var: "OPENAI_API_KEY",
|
|
7376
|
+
requires_key: true
|
|
7377
|
+
},
|
|
7378
|
+
{
|
|
7379
|
+
id: "google",
|
|
7380
|
+
label: "Google Gemini",
|
|
7381
|
+
api: "openai-compat",
|
|
7382
|
+
base_url: "https://generativelanguage.googleapis.com/v1beta/openai",
|
|
7383
|
+
key_prefixes: ["AIza"],
|
|
7384
|
+
shared_prefixes: [],
|
|
7385
|
+
key_config_name: "google-api-key",
|
|
7386
|
+
requires_key: true
|
|
7387
|
+
},
|
|
7388
|
+
{
|
|
7389
|
+
id: "groq",
|
|
7390
|
+
label: "Groq",
|
|
7391
|
+
api: "openai-compat",
|
|
7392
|
+
base_url: "https://api.groq.com/openai/v1",
|
|
7393
|
+
key_prefixes: ["gsk_"],
|
|
7394
|
+
shared_prefixes: [],
|
|
7395
|
+
key_config_name: "groq-api-key",
|
|
7396
|
+
requires_key: true
|
|
7397
|
+
},
|
|
7398
|
+
{
|
|
7399
|
+
id: "mistral",
|
|
7400
|
+
label: "Mistral",
|
|
7401
|
+
api: "openai-compat",
|
|
7402
|
+
base_url: "https://api.mistral.ai/v1",
|
|
7403
|
+
key_prefixes: [],
|
|
7404
|
+
shared_prefixes: [],
|
|
7405
|
+
key_config_name: "mistral-api-key",
|
|
7406
|
+
requires_key: true
|
|
7407
|
+
},
|
|
7408
|
+
{
|
|
7409
|
+
id: "deepseek",
|
|
7410
|
+
label: "DeepSeek",
|
|
7411
|
+
api: "openai-compat",
|
|
7412
|
+
base_url: "https://api.deepseek.com/v1",
|
|
7413
|
+
key_prefixes: [],
|
|
7414
|
+
shared_prefixes: ["sk-"],
|
|
7415
|
+
key_config_name: "deepseek-api-key",
|
|
7416
|
+
requires_key: true
|
|
7417
|
+
},
|
|
7418
|
+
{
|
|
7419
|
+
id: "xai",
|
|
7420
|
+
label: "xAI",
|
|
7421
|
+
api: "openai-compat",
|
|
7422
|
+
base_url: "https://api.x.ai/v1",
|
|
7423
|
+
key_prefixes: ["xai-"],
|
|
7424
|
+
shared_prefixes: [],
|
|
7425
|
+
key_config_name: "xai-api-key",
|
|
7426
|
+
requires_key: true
|
|
7427
|
+
},
|
|
7428
|
+
{
|
|
7429
|
+
id: "openrouter",
|
|
7430
|
+
label: "OpenRouter",
|
|
7431
|
+
api: "openai-compat",
|
|
7432
|
+
base_url: "https://openrouter.ai/api/v1",
|
|
7433
|
+
key_prefixes: ["sk-or-"],
|
|
7434
|
+
shared_prefixes: [],
|
|
7435
|
+
key_config_name: "openrouter-api-key",
|
|
7436
|
+
requires_key: true
|
|
7437
|
+
},
|
|
7438
|
+
{
|
|
7439
|
+
id: "together",
|
|
7440
|
+
label: "Together AI",
|
|
7441
|
+
api: "openai-compat",
|
|
7442
|
+
base_url: "https://api.together.xyz/v1",
|
|
7443
|
+
key_prefixes: [],
|
|
7444
|
+
shared_prefixes: [],
|
|
7445
|
+
key_config_name: "together-api-key",
|
|
7446
|
+
requires_key: true
|
|
7447
|
+
},
|
|
7448
|
+
{
|
|
7449
|
+
id: "fireworks",
|
|
7450
|
+
label: "Fireworks AI",
|
|
7451
|
+
api: "openai-compat",
|
|
7452
|
+
base_url: "https://api.fireworks.ai/inference/v1",
|
|
7453
|
+
key_prefixes: ["fw_"],
|
|
7454
|
+
shared_prefixes: [],
|
|
7455
|
+
key_config_name: "fireworks-api-key",
|
|
7456
|
+
requires_key: true
|
|
7457
|
+
},
|
|
7458
|
+
{
|
|
7459
|
+
id: "ollama",
|
|
7460
|
+
label: "Ollama (local)",
|
|
7461
|
+
api: "openai-compat",
|
|
7462
|
+
base_url: "http://localhost:11434/v1",
|
|
7463
|
+
key_prefixes: [],
|
|
7464
|
+
shared_prefixes: [],
|
|
7465
|
+
key_config_name: "ollama-api-key",
|
|
7466
|
+
requires_key: false
|
|
7467
|
+
}
|
|
7468
|
+
];
|
|
7469
|
+
cachedEntries = null;
|
|
7470
|
+
}
|
|
7471
|
+
});
|
|
7472
|
+
|
|
7192
7473
|
// src/config/llm-config.ts
|
|
7193
7474
|
function parseProvider(raw) {
|
|
7194
|
-
if (raw
|
|
7195
|
-
|
|
7475
|
+
if (!raw?.trim()) return void 0;
|
|
7476
|
+
const id = raw.trim();
|
|
7477
|
+
return getProviderSpec(id) ? id : void 0;
|
|
7196
7478
|
}
|
|
7197
7479
|
function parseTier(raw) {
|
|
7198
7480
|
if (raw === "high" || raw === "medium" || raw === "low") return raw;
|
|
@@ -7200,7 +7482,7 @@ function parseTier(raw) {
|
|
|
7200
7482
|
}
|
|
7201
7483
|
function parseFailoverOrder(raw) {
|
|
7202
7484
|
if (!raw?.trim()) return ["openai"];
|
|
7203
|
-
return raw.split(",").map((s) => s.trim()).filter((s) => s
|
|
7485
|
+
return raw.split(",").map((s) => s.trim()).filter((s) => !!s && !!getProviderSpec(s));
|
|
7204
7486
|
}
|
|
7205
7487
|
function parseAutoFailover(raw) {
|
|
7206
7488
|
if (!raw) return false;
|
|
@@ -7215,30 +7497,42 @@ function getOpenAiApiKey() {
|
|
|
7215
7497
|
if (fromConfig) return fromConfig;
|
|
7216
7498
|
return process.env.OPENAI_API_KEY?.trim() || void 0;
|
|
7217
7499
|
}
|
|
7500
|
+
function getProviderApiKey(provider) {
|
|
7501
|
+
const spec = getProviderSpec(provider);
|
|
7502
|
+
if (!spec) return void 0;
|
|
7503
|
+
const record = loadConfig();
|
|
7504
|
+
const fromConfig = record[spec.key_config_name]?.trim();
|
|
7505
|
+
if (fromConfig) return fromConfig;
|
|
7506
|
+
if (spec.env_var) {
|
|
7507
|
+
const fromEnv = process.env[spec.env_var]?.trim();
|
|
7508
|
+
if (fromEnv) return fromEnv;
|
|
7509
|
+
}
|
|
7510
|
+
return void 0;
|
|
7511
|
+
}
|
|
7218
7512
|
function hasProviderKey(provider) {
|
|
7219
|
-
|
|
7220
|
-
|
|
7513
|
+
const spec = getProviderSpec(provider);
|
|
7514
|
+
if (!spec) return false;
|
|
7515
|
+
if (!spec.requires_key) return isEndpointEnabled(spec.id) || !!getProviderApiKey(provider);
|
|
7516
|
+
return !!getProviderApiKey(provider);
|
|
7221
7517
|
}
|
|
7222
7518
|
function getAvailableProviders() {
|
|
7223
|
-
|
|
7224
|
-
if (hasProviderKey("anthropic")) out.push("anthropic");
|
|
7225
|
-
if (hasProviderKey("openai")) out.push("openai");
|
|
7226
|
-
return out;
|
|
7519
|
+
return listProviderSpecs().filter((s) => hasProviderKey(s.id)).map((s) => s.id);
|
|
7227
7520
|
}
|
|
7228
7521
|
function hasAnyLlmProvider() {
|
|
7229
7522
|
return getAvailableProviders().length > 0;
|
|
7230
7523
|
}
|
|
7524
|
+
function hasKeylessConfiguredProvider() {
|
|
7525
|
+
return listProviderSpecs().some((s) => !s.requires_key && hasProviderKey(s.id));
|
|
7526
|
+
}
|
|
7231
7527
|
function applyLazyMigration(config) {
|
|
7232
7528
|
if (migrated) return;
|
|
7233
7529
|
migrated = true;
|
|
7234
7530
|
let changed = false;
|
|
7235
7531
|
const record = config;
|
|
7236
7532
|
if (!record["llm-primary"]) {
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
} else if (record["openai-api-key"] || process.env.OPENAI_API_KEY) {
|
|
7241
|
-
record["llm-primary"] = "openai";
|
|
7533
|
+
const available = getAvailableProviders();
|
|
7534
|
+
if (available.length > 0) {
|
|
7535
|
+
record["llm-primary"] = available[0];
|
|
7242
7536
|
changed = true;
|
|
7243
7537
|
}
|
|
7244
7538
|
}
|
|
@@ -7278,20 +7572,20 @@ function loadLlmConfig() {
|
|
|
7278
7572
|
openaiKey: getOpenAiApiKey()
|
|
7279
7573
|
};
|
|
7280
7574
|
}
|
|
7281
|
-
function getProviderApiKey(provider) {
|
|
7282
|
-
if (provider === "anthropic") return getAnthropicApiKey();
|
|
7283
|
-
return getOpenAiApiKey();
|
|
7284
|
-
}
|
|
7285
7575
|
function getInvestigationApiKey(provider) {
|
|
7286
7576
|
if (provider === "anthropic") {
|
|
7287
7577
|
return process.env.NTRP_INVESTIGATION_API_KEY?.trim() || getAnthropicApiKey();
|
|
7288
7578
|
}
|
|
7289
|
-
|
|
7579
|
+
if (provider === "openai") {
|
|
7580
|
+
return process.env.NTRP_INVESTIGATION_OPENAI_KEY?.trim() || getOpenAiApiKey();
|
|
7581
|
+
}
|
|
7582
|
+
return getProviderApiKey(provider);
|
|
7290
7583
|
}
|
|
7291
7584
|
var migrated;
|
|
7292
7585
|
var init_llm_config = __esm({
|
|
7293
7586
|
"src/config/llm-config.ts"() {
|
|
7294
7587
|
"use strict";
|
|
7588
|
+
init_providers();
|
|
7295
7589
|
init_store();
|
|
7296
7590
|
migrated = false;
|
|
7297
7591
|
}
|
|
@@ -7309,7 +7603,13 @@ function resolvePrimaryApiKey(ctx) {
|
|
|
7309
7603
|
if (isInvestigationMode(ctx)) {
|
|
7310
7604
|
return getInvestigationApiKey(primary) ?? getInvestigationApiKey("anthropic") ?? getInvestigationApiKey("openai");
|
|
7311
7605
|
}
|
|
7312
|
-
|
|
7606
|
+
const primaryKey = getProviderApiKey(primary);
|
|
7607
|
+
if (primaryKey) return primaryKey;
|
|
7608
|
+
for (const provider of getAvailableProviders()) {
|
|
7609
|
+
const key = getProviderApiKey(provider);
|
|
7610
|
+
if (key) return key;
|
|
7611
|
+
}
|
|
7612
|
+
return void 0;
|
|
7313
7613
|
}
|
|
7314
7614
|
function canUseReplAi(ctx) {
|
|
7315
7615
|
if (!ctx) return false;
|
|
@@ -7320,25 +7620,21 @@ function canUseReplAi(ctx) {
|
|
|
7320
7620
|
}
|
|
7321
7621
|
function assertReplAi(ctx) {
|
|
7322
7622
|
if (!ctx) {
|
|
7323
|
-
throw new Error(
|
|
7324
|
-
"AI features require stored API keys. Run `ntrp`, then /config set api-key or /config set openai-api-key."
|
|
7325
|
-
);
|
|
7623
|
+
throw new Error(`AI features require stored API keys. Run \`ntrp\`, then /connect.`);
|
|
7326
7624
|
}
|
|
7327
7625
|
if (!canUseReplAi(ctx)) {
|
|
7328
7626
|
if (!hasAnyLlmProvider()) {
|
|
7329
|
-
throw new Error(
|
|
7330
|
-
"No LLM API key configured. Run: /config set api-key (Anthropic) and/or /config set openai-api-key"
|
|
7331
|
-
);
|
|
7627
|
+
throw new Error(NO_KEY_MESSAGE);
|
|
7332
7628
|
}
|
|
7333
7629
|
throw new Error(
|
|
7334
7630
|
"AI features run only in the interactive REPL or headless mode with stored keys."
|
|
7335
7631
|
);
|
|
7336
7632
|
}
|
|
7337
7633
|
const key = resolvePrimaryApiKey(ctx);
|
|
7338
|
-
if (!key) {
|
|
7339
|
-
throw new Error(
|
|
7634
|
+
if (!key && !hasKeylessConfiguredProvider()) {
|
|
7635
|
+
throw new Error(NO_KEY_MESSAGE);
|
|
7340
7636
|
}
|
|
7341
|
-
return key;
|
|
7637
|
+
return key ?? "";
|
|
7342
7638
|
}
|
|
7343
7639
|
function hasEnvApiKeyHint() {
|
|
7344
7640
|
return !!(process.env.ANTHROPIC_API_KEY ?? process.env.NTRP_API_KEY ?? process.env.OPENAI_API_KEY);
|
|
@@ -7351,10 +7647,12 @@ function describeLlmReadiness() {
|
|
|
7351
7647
|
openai: providers.includes("openai")
|
|
7352
7648
|
};
|
|
7353
7649
|
}
|
|
7650
|
+
var NO_KEY_MESSAGE;
|
|
7354
7651
|
var init_gate = __esm({
|
|
7355
7652
|
"src/ai/llm/gate.ts"() {
|
|
7356
7653
|
"use strict";
|
|
7357
7654
|
init_llm_config();
|
|
7655
|
+
NO_KEY_MESSAGE = "No LLM API key configured. Run /connect and paste any provider's key (Anthropic, OpenAI, Groq, Gemini, ...).";
|
|
7358
7656
|
}
|
|
7359
7657
|
});
|
|
7360
7658
|
|
|
@@ -7379,18 +7677,18 @@ var init_repl_api = __esm({
|
|
|
7379
7677
|
});
|
|
7380
7678
|
|
|
7381
7679
|
// src/demo/taxonomy-cache.ts
|
|
7382
|
-
import { readFileSync as
|
|
7680
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, existsSync as existsSync11, mkdirSync as mkdirSync7, unlinkSync as unlinkSync4 } from "fs";
|
|
7383
7681
|
import { homedir as homedir3 } from "os";
|
|
7384
|
-
import { join as
|
|
7682
|
+
import { join as join11 } from "path";
|
|
7385
7683
|
function ensureDir6() {
|
|
7386
|
-
if (!
|
|
7684
|
+
if (!existsSync11(NTRP_DIR4)) {
|
|
7387
7685
|
mkdirSync7(NTRP_DIR4, { recursive: true });
|
|
7388
7686
|
}
|
|
7389
7687
|
}
|
|
7390
7688
|
function loadCachedTaxonomy(profile) {
|
|
7391
|
-
if (!
|
|
7689
|
+
if (!existsSync11(TAXONOMY_PATH)) return null;
|
|
7392
7690
|
try {
|
|
7393
|
-
const parsed = JSON.parse(
|
|
7691
|
+
const parsed = JSON.parse(readFileSync9(TAXONOMY_PATH, "utf-8"));
|
|
7394
7692
|
if (!parsed || typeof parsed !== "object") return null;
|
|
7395
7693
|
if (parsed.profile_updated_at !== profile.updated_at) return null;
|
|
7396
7694
|
return parsed;
|
|
@@ -7400,10 +7698,10 @@ function loadCachedTaxonomy(profile) {
|
|
|
7400
7698
|
}
|
|
7401
7699
|
function saveCachedTaxonomy(taxonomy) {
|
|
7402
7700
|
ensureDir6();
|
|
7403
|
-
|
|
7701
|
+
writeFileSync9(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
|
|
7404
7702
|
}
|
|
7405
7703
|
function invalidateTaxonomy() {
|
|
7406
|
-
if (
|
|
7704
|
+
if (existsSync11(TAXONOMY_PATH)) {
|
|
7407
7705
|
try {
|
|
7408
7706
|
unlinkSync4(TAXONOMY_PATH);
|
|
7409
7707
|
} catch {
|
|
@@ -7414,8 +7712,8 @@ var NTRP_DIR4, TAXONOMY_PATH;
|
|
|
7414
7712
|
var init_taxonomy_cache = __esm({
|
|
7415
7713
|
"src/demo/taxonomy-cache.ts"() {
|
|
7416
7714
|
"use strict";
|
|
7417
|
-
NTRP_DIR4 =
|
|
7418
|
-
TAXONOMY_PATH =
|
|
7715
|
+
NTRP_DIR4 = join11(homedir3(), ".ntrp");
|
|
7716
|
+
TAXONOMY_PATH = join11(NTRP_DIR4, "demo-taxonomy.json");
|
|
7419
7717
|
}
|
|
7420
7718
|
});
|
|
7421
7719
|
|
|
@@ -7440,6 +7738,12 @@ var init_types2 = __esm({
|
|
|
7440
7738
|
});
|
|
7441
7739
|
|
|
7442
7740
|
// src/ai/llm/errors.ts
|
|
7741
|
+
function isToolsUnsupportedMessage(message) {
|
|
7742
|
+
const msg = message.toLowerCase();
|
|
7743
|
+
const mentionsTools = msg.includes("tool") || msg.includes("function");
|
|
7744
|
+
const mentionsUnsupported = msg.includes("not support") || msg.includes("unsupported") || msg.includes("no support") || msg.includes("not available") || msg.includes("not enabled");
|
|
7745
|
+
return mentionsTools && mentionsUnsupported;
|
|
7746
|
+
}
|
|
7443
7747
|
function mapAnthropicError(err, provider) {
|
|
7444
7748
|
const e = err;
|
|
7445
7749
|
const status = e.status;
|
|
@@ -7457,6 +7761,9 @@ function mapAnthropicError(err, provider) {
|
|
|
7457
7761
|
if (status === 503) {
|
|
7458
7762
|
return new LlmError("OVERLOADED", message, provider, status);
|
|
7459
7763
|
}
|
|
7764
|
+
if (isToolsUnsupportedMessage(message)) {
|
|
7765
|
+
return new LlmError("TOOLS_UNSUPPORTED", message, provider, status);
|
|
7766
|
+
}
|
|
7460
7767
|
if (status === 404 || message.toLowerCase().includes("model")) {
|
|
7461
7768
|
return new LlmError("MODEL_NOT_FOUND", message, provider, status);
|
|
7462
7769
|
}
|
|
@@ -7465,6 +7772,11 @@ function mapAnthropicError(err, provider) {
|
|
|
7465
7772
|
}
|
|
7466
7773
|
return new LlmError("UNKNOWN", message, provider, status);
|
|
7467
7774
|
}
|
|
7775
|
+
function isModelNotFoundMessage(message) {
|
|
7776
|
+
const msg = message.toLowerCase();
|
|
7777
|
+
if (!msg.includes("model")) return false;
|
|
7778
|
+
return msg.includes("not found") || msg.includes("does not exist") || msg.includes("decommissioned") || msg.includes("deprecated") || msg.includes("retired") || msg.includes("do not have access") || msg.includes("invalid model");
|
|
7779
|
+
}
|
|
7468
7780
|
function mapOpenAiError(err, provider) {
|
|
7469
7781
|
const e = err;
|
|
7470
7782
|
const status = e.status;
|
|
@@ -7479,7 +7791,10 @@ function mapOpenAiError(err, provider) {
|
|
|
7479
7791
|
if (status === 503 || code === "server_error") {
|
|
7480
7792
|
return new LlmError("OVERLOADED", message, provider, status);
|
|
7481
7793
|
}
|
|
7482
|
-
if (
|
|
7794
|
+
if (isToolsUnsupportedMessage(message)) {
|
|
7795
|
+
return new LlmError("TOOLS_UNSUPPORTED", message, provider, status);
|
|
7796
|
+
}
|
|
7797
|
+
if (status === 404 || code === "model_not_found" || code === "model_decommissioned" || isModelNotFoundMessage(message)) {
|
|
7483
7798
|
return new LlmError("MODEL_NOT_FOUND", message, provider, status);
|
|
7484
7799
|
}
|
|
7485
7800
|
if (code === "context_length_exceeded") {
|
|
@@ -7615,8 +7930,15 @@ var init_anthropic = __esm({
|
|
|
7615
7930
|
}
|
|
7616
7931
|
});
|
|
7617
7932
|
|
|
7618
|
-
// src/ai/llm/adapters/openai.ts
|
|
7933
|
+
// src/ai/llm/adapters/openai-compat.ts
|
|
7619
7934
|
import OpenAI from "openai";
|
|
7935
|
+
function makeClient(apiKey, baseUrl) {
|
|
7936
|
+
return new OpenAI({
|
|
7937
|
+
// Keyless endpoints (Ollama) still need a non-empty string for the SDK.
|
|
7938
|
+
apiKey: apiKey || "local",
|
|
7939
|
+
...baseUrl ? { baseURL: baseUrl } : {}
|
|
7940
|
+
});
|
|
7941
|
+
}
|
|
7620
7942
|
function toOpenAiTools(tools) {
|
|
7621
7943
|
return tools.map((t) => ({
|
|
7622
7944
|
type: "function",
|
|
@@ -7685,9 +8007,8 @@ function parseResponse2(message) {
|
|
|
7685
8007
|
assistant_message: { role: "assistant", content: text, tool_calls }
|
|
7686
8008
|
};
|
|
7687
8009
|
}
|
|
7688
|
-
async function
|
|
7689
|
-
const
|
|
7690
|
-
const client = new OpenAI({ apiKey });
|
|
8010
|
+
async function openaiCompatComplete(provider, baseUrl, apiKey, model, req) {
|
|
8011
|
+
const client = makeClient(apiKey, baseUrl);
|
|
7691
8012
|
try {
|
|
7692
8013
|
const response = await client.chat.completions.create({
|
|
7693
8014
|
model,
|
|
@@ -7697,7 +8018,7 @@ async function openaiComplete(apiKey, model, req) {
|
|
|
7697
8018
|
});
|
|
7698
8019
|
const choice = response.choices[0];
|
|
7699
8020
|
if (!choice?.message) {
|
|
7700
|
-
throw new Error(
|
|
8021
|
+
throw new Error(`${provider} returned no message`);
|
|
7701
8022
|
}
|
|
7702
8023
|
const parsed = parseResponse2(choice.message);
|
|
7703
8024
|
if (response.usage) {
|
|
@@ -7708,15 +8029,11 @@ async function openaiComplete(apiKey, model, req) {
|
|
|
7708
8029
|
}
|
|
7709
8030
|
return parsed;
|
|
7710
8031
|
} catch (err) {
|
|
7711
|
-
if (err instanceof OpenAI.APIError) {
|
|
7712
|
-
throw mapOpenAiError(err, provider);
|
|
7713
|
-
}
|
|
7714
8032
|
throw mapOpenAiError(err, provider);
|
|
7715
8033
|
}
|
|
7716
8034
|
}
|
|
7717
|
-
async function*
|
|
7718
|
-
const
|
|
7719
|
-
const client = new OpenAI({ apiKey });
|
|
8035
|
+
async function* openaiCompatStream(provider, baseUrl, apiKey, model, req) {
|
|
8036
|
+
const client = makeClient(apiKey, baseUrl);
|
|
7720
8037
|
try {
|
|
7721
8038
|
const stream = await client.chat.completions.create({
|
|
7722
8039
|
model,
|
|
@@ -7729,19 +8046,316 @@ async function* openaiStream(apiKey, model, req) {
|
|
|
7729
8046
|
if (delta) yield { type: "text_delta", text: delta };
|
|
7730
8047
|
}
|
|
7731
8048
|
} catch (err) {
|
|
7732
|
-
if (err instanceof OpenAI.APIError) {
|
|
7733
|
-
throw mapOpenAiError(err, provider);
|
|
7734
|
-
}
|
|
7735
8049
|
throw mapOpenAiError(err, provider);
|
|
7736
8050
|
}
|
|
7737
8051
|
}
|
|
7738
|
-
var
|
|
7739
|
-
"src/ai/llm/adapters/openai.ts"() {
|
|
8052
|
+
var init_openai_compat = __esm({
|
|
8053
|
+
"src/ai/llm/adapters/openai-compat.ts"() {
|
|
7740
8054
|
"use strict";
|
|
7741
8055
|
init_errors2();
|
|
7742
8056
|
}
|
|
7743
8057
|
});
|
|
7744
8058
|
|
|
8059
|
+
// src/ai/llm/http.ts
|
|
8060
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
8061
|
+
function fixtureResponse(url, headers) {
|
|
8062
|
+
try {
|
|
8063
|
+
const raw = readFileSync10(process.env.NTRP_LLM_HTTP_FIXTURE, "utf-8");
|
|
8064
|
+
const entries = JSON.parse(raw);
|
|
8065
|
+
const headerValues = Object.values(headers).join(" ");
|
|
8066
|
+
for (const entry of entries) {
|
|
8067
|
+
if (!url.includes(entry.url_includes)) continue;
|
|
8068
|
+
if (entry.auth_includes && !headerValues.includes(entry.auth_includes)) continue;
|
|
8069
|
+
return { status: entry.status, ok: entry.status >= 200 && entry.status < 300, body: entry.body };
|
|
8070
|
+
}
|
|
8071
|
+
} catch {
|
|
8072
|
+
}
|
|
8073
|
+
return { status: 0, ok: false, body: void 0 };
|
|
8074
|
+
}
|
|
8075
|
+
async function llmHttpGetJson(url, headers, timeoutMs = 6e3) {
|
|
8076
|
+
if (process.env.NTRP_LLM_HTTP_FIXTURE) {
|
|
8077
|
+
return fixtureResponse(url, headers);
|
|
8078
|
+
}
|
|
8079
|
+
const controller = new AbortController();
|
|
8080
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
8081
|
+
try {
|
|
8082
|
+
const res = await fetch(url, { method: "GET", headers, signal: controller.signal });
|
|
8083
|
+
let body;
|
|
8084
|
+
try {
|
|
8085
|
+
body = await res.json();
|
|
8086
|
+
} catch {
|
|
8087
|
+
body = void 0;
|
|
8088
|
+
}
|
|
8089
|
+
return { status: res.status, ok: res.ok, body };
|
|
8090
|
+
} catch {
|
|
8091
|
+
return { status: 0, ok: false, body: void 0 };
|
|
8092
|
+
} finally {
|
|
8093
|
+
clearTimeout(timer);
|
|
8094
|
+
}
|
|
8095
|
+
}
|
|
8096
|
+
var init_http = __esm({
|
|
8097
|
+
"src/ai/llm/http.ts"() {
|
|
8098
|
+
"use strict";
|
|
8099
|
+
}
|
|
8100
|
+
});
|
|
8101
|
+
|
|
8102
|
+
// src/ai/llm/ranking.ts
|
|
8103
|
+
function compareModels(a, b) {
|
|
8104
|
+
const createdA = a.created ?? 0;
|
|
8105
|
+
const createdB = b.created ?? 0;
|
|
8106
|
+
if (createdA !== createdB) return createdB - createdA;
|
|
8107
|
+
const versionA = extractVersion(a.id);
|
|
8108
|
+
const versionB = extractVersion(b.id);
|
|
8109
|
+
if (versionA !== versionB) return versionB - versionA;
|
|
8110
|
+
if (a.id.length !== b.id.length) return a.id.length - b.id.length;
|
|
8111
|
+
return a.id.localeCompare(b.id);
|
|
8112
|
+
}
|
|
8113
|
+
function extractVersion(id) {
|
|
8114
|
+
const match = id.match(/(\d+(?:\.\d+)?)/);
|
|
8115
|
+
return match ? Number(match[1]) : 0;
|
|
8116
|
+
}
|
|
8117
|
+
function pickByPatterns(models, patterns) {
|
|
8118
|
+
for (const pattern of patterns) {
|
|
8119
|
+
const matches = models.filter((m) => pattern.test(m.id));
|
|
8120
|
+
if (matches.length > 0) return [...matches].sort(compareModels)[0];
|
|
8121
|
+
}
|
|
8122
|
+
return void 0;
|
|
8123
|
+
}
|
|
8124
|
+
function genericBucket(model) {
|
|
8125
|
+
if (GENERIC_HIGH.test(model.id)) return "high";
|
|
8126
|
+
if (GENERIC_LOW.test(model.id)) return "low";
|
|
8127
|
+
return "medium";
|
|
8128
|
+
}
|
|
8129
|
+
function genericPick(models, tier) {
|
|
8130
|
+
const bucket = models.filter((m) => genericBucket(m) === tier);
|
|
8131
|
+
if (bucket.length > 0) return [...bucket].sort(compareModels)[0];
|
|
8132
|
+
return void 0;
|
|
8133
|
+
}
|
|
8134
|
+
function rankModels(providerId, models) {
|
|
8135
|
+
if (models.length === 0) return null;
|
|
8136
|
+
const preferences = PROVIDER_PREFERENCES[providerId];
|
|
8137
|
+
const picks = {};
|
|
8138
|
+
for (const tier of ["high", "medium", "low"]) {
|
|
8139
|
+
const preferred = preferences ? pickByPatterns(models, preferences[tier]) : void 0;
|
|
8140
|
+
const generic = preferred ?? genericPick(models, tier);
|
|
8141
|
+
if (generic) picks[tier] = generic.id;
|
|
8142
|
+
}
|
|
8143
|
+
const anyModel = [...models].sort(compareModels)[0].id;
|
|
8144
|
+
const high = picks.high ?? picks.medium ?? picks.low ?? anyModel;
|
|
8145
|
+
const medium = picks.medium ?? picks.high ?? picks.low ?? anyModel;
|
|
8146
|
+
const low = picks.low ?? picks.medium ?? picks.high ?? anyModel;
|
|
8147
|
+
return { high, medium, low };
|
|
8148
|
+
}
|
|
8149
|
+
var PROVIDER_PREFERENCES, GENERIC_LOW, GENERIC_HIGH;
|
|
8150
|
+
var init_ranking = __esm({
|
|
8151
|
+
"src/ai/llm/ranking.ts"() {
|
|
8152
|
+
"use strict";
|
|
8153
|
+
PROVIDER_PREFERENCES = {
|
|
8154
|
+
anthropic: {
|
|
8155
|
+
high: [/^claude-opus/i, /^claude-sonnet/i],
|
|
8156
|
+
medium: [/^claude-sonnet/i, /^claude-haiku/i],
|
|
8157
|
+
low: [/^claude-haiku/i, /^claude-sonnet/i]
|
|
8158
|
+
},
|
|
8159
|
+
openai: {
|
|
8160
|
+
high: [/^gpt-5(?!.*(mini|nano|chat))/i, /^gpt-4\.1(?!.*(mini|nano))/i, /^gpt-4o(?!.*mini)/i, /^o3(?!.*mini)/i],
|
|
8161
|
+
medium: [/^gpt-5.*mini/i, /^gpt-4\.1-mini/i, /^gpt-4o-mini/i, /^o4-mini/i],
|
|
8162
|
+
low: [/^gpt-5.*nano/i, /^gpt-4\.1-nano/i, /^gpt-4o-mini/i]
|
|
8163
|
+
},
|
|
8164
|
+
google: {
|
|
8165
|
+
high: [/^gemini-[\d.]+-pro/i, /^gemini-[\d.]+-flash(?!-lite)/i],
|
|
8166
|
+
medium: [/^gemini-[\d.]+-flash(?!-lite|-8b)/i, /^gemini-[\d.]+-pro/i],
|
|
8167
|
+
low: [/^gemini-[\d.]+-flash-lite/i, /flash-8b/i, /^gemini-[\d.]+-flash(?!-lite)/i]
|
|
8168
|
+
},
|
|
8169
|
+
groq: {
|
|
8170
|
+
high: [/llama-3\.3-70b/i, /gpt-oss-120b/i, /70b/i, /deepseek-r1/i],
|
|
8171
|
+
medium: [/llama-3\.1-8b-instant/i, /gpt-oss-20b/i, /llama.*8b/i],
|
|
8172
|
+
low: [/8b-instant/i, /llama.*8b/i, /gemma/i]
|
|
8173
|
+
},
|
|
8174
|
+
deepseek: {
|
|
8175
|
+
high: [/reasoner/i, /chat/i],
|
|
8176
|
+
medium: [/chat/i],
|
|
8177
|
+
low: [/chat/i]
|
|
8178
|
+
},
|
|
8179
|
+
mistral: {
|
|
8180
|
+
high: [/large/i, /medium/i],
|
|
8181
|
+
medium: [/medium/i, /^mistral-small/i],
|
|
8182
|
+
low: [/ministral/i, /small/i, /tiny/i]
|
|
8183
|
+
},
|
|
8184
|
+
xai: {
|
|
8185
|
+
high: [/^grok-\d+(?!.*(mini|fast))/i, /^grok(?!.*(mini|fast))/i],
|
|
8186
|
+
medium: [/^grok.*mini(?!.*fast)/i, /^grok.*fast/i],
|
|
8187
|
+
low: [/^grok.*mini.*fast/i, /^grok.*mini/i]
|
|
8188
|
+
},
|
|
8189
|
+
openrouter: {
|
|
8190
|
+
high: [/^openrouter\/auto$/i, /claude.*opus/i, /^openai\/gpt-5(?!.*(mini|nano))/i, /gemini.*pro/i],
|
|
8191
|
+
medium: [/claude.*sonnet/i, /gpt-5.*mini/i, /gpt-4\.1-mini/i, /gemini.*flash(?!-lite)/i],
|
|
8192
|
+
low: [/claude.*haiku/i, /nano/i, /flash-lite/i, /mini/i]
|
|
8193
|
+
}
|
|
8194
|
+
};
|
|
8195
|
+
GENERIC_LOW = /(mini|nano|lite|tiny|micro|small|haiku|instant|flash|turbo|\b0?\.?5b\b|\b[1-8]b\b)/i;
|
|
8196
|
+
GENERIC_HIGH = /(opus|ultra|large|max\b|\bpro\b|405b|253b|235b|120b|72b|70b|reason|-r1\b|think|deep)/i;
|
|
8197
|
+
}
|
|
8198
|
+
});
|
|
8199
|
+
|
|
8200
|
+
// src/ai/llm/discovery.ts
|
|
8201
|
+
var discovery_exports = {};
|
|
8202
|
+
__export(discovery_exports, {
|
|
8203
|
+
fetchProviderModels: () => fetchProviderModels,
|
|
8204
|
+
filterChatModels: () => filterChatModels,
|
|
8205
|
+
refreshProviderModels: () => refreshProviderModels,
|
|
8206
|
+
refreshStaleProviderCaches: () => refreshStaleProviderCaches,
|
|
8207
|
+
rerankExcluding: () => rerankExcluding,
|
|
8208
|
+
storeDiscoveredModels: () => storeDiscoveredModels
|
|
8209
|
+
});
|
|
8210
|
+
function authHeaders(spec, apiKey) {
|
|
8211
|
+
if (spec.api === "anthropic") {
|
|
8212
|
+
return { "x-api-key": apiKey ?? "", "anthropic-version": "2023-06-01" };
|
|
8213
|
+
}
|
|
8214
|
+
return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
|
|
8215
|
+
}
|
|
8216
|
+
function normalizeItem(spec, item) {
|
|
8217
|
+
let id = item.id ?? "";
|
|
8218
|
+
if (!id) return null;
|
|
8219
|
+
if (id.startsWith("models/")) id = id.slice("models/".length);
|
|
8220
|
+
const model = { id };
|
|
8221
|
+
const display2 = item.display_name ?? item.name;
|
|
8222
|
+
if (display2 && display2 !== id) model.display_name = display2;
|
|
8223
|
+
if (typeof item.created === "number") model.created = item.created;
|
|
8224
|
+
else if (item.created_at) {
|
|
8225
|
+
const parsed = Date.parse(item.created_at);
|
|
8226
|
+
if (!Number.isNaN(parsed)) model.created = Math.floor(parsed / 1e3);
|
|
8227
|
+
}
|
|
8228
|
+
if (typeof item.context_length === "number") model.context_length = item.context_length;
|
|
8229
|
+
if (Array.isArray(item.supported_parameters)) {
|
|
8230
|
+
model.supports_tools = item.supported_parameters.includes("tools");
|
|
8231
|
+
}
|
|
8232
|
+
return model;
|
|
8233
|
+
}
|
|
8234
|
+
async function fetchProviderModels(spec, apiKey, timeoutMs = 6e3) {
|
|
8235
|
+
const headers = authHeaders(spec, apiKey);
|
|
8236
|
+
if (spec.api === "anthropic") {
|
|
8237
|
+
const models2 = [];
|
|
8238
|
+
let url = modelsUrl(spec);
|
|
8239
|
+
for (let page = 0; page < 5 && url; page++) {
|
|
8240
|
+
const res2 = await llmHttpGetJson(url, headers, timeoutMs);
|
|
8241
|
+
if (!res2.ok) return models2.length > 0 ? { ok: true, models: models2 } : { ok: false, status: res2.status };
|
|
8242
|
+
const body2 = res2.body;
|
|
8243
|
+
for (const item of body2?.data ?? []) {
|
|
8244
|
+
const model = normalizeItem(spec, item);
|
|
8245
|
+
if (model) models2.push(model);
|
|
8246
|
+
}
|
|
8247
|
+
url = body2?.has_more && body2.last_id ? `${spec.base_url}/v1/models?limit=100&after_id=${encodeURIComponent(body2.last_id)}` : null;
|
|
8248
|
+
}
|
|
8249
|
+
return { ok: true, models: models2 };
|
|
8250
|
+
}
|
|
8251
|
+
const res = await llmHttpGetJson(modelsUrl(spec), headers, timeoutMs);
|
|
8252
|
+
if (!res.ok) return { ok: false, status: res.status };
|
|
8253
|
+
const body = res.body;
|
|
8254
|
+
const list = Array.isArray(body) ? body : body?.data ?? [];
|
|
8255
|
+
const models = [];
|
|
8256
|
+
for (const item of list) {
|
|
8257
|
+
const model = normalizeItem(spec, item);
|
|
8258
|
+
if (model) models.push(model);
|
|
8259
|
+
}
|
|
8260
|
+
return { ok: true, models };
|
|
8261
|
+
}
|
|
8262
|
+
function filterChatModels(spec, models) {
|
|
8263
|
+
const extra = PROVIDER_EXCLUDE[spec.id];
|
|
8264
|
+
return models.filter((m) => !NON_CHAT.test(m.id) && !(extra && extra.test(m.id)));
|
|
8265
|
+
}
|
|
8266
|
+
function storeDiscoveredModels(providerId, rawModels) {
|
|
8267
|
+
const spec = getProviderSpec(providerId);
|
|
8268
|
+
if (!spec || rawModels.length === 0) return null;
|
|
8269
|
+
const chat = filterChatModels(spec, rawModels);
|
|
8270
|
+
const usable = chat.length > 0 ? chat : rawModels;
|
|
8271
|
+
const stack = rankModels(providerId, usable);
|
|
8272
|
+
if (!stack) return null;
|
|
8273
|
+
const prior = getProviderModels(providerId);
|
|
8274
|
+
const entry = {
|
|
8275
|
+
fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8276
|
+
models: usable,
|
|
8277
|
+
tier_stack: stack,
|
|
8278
|
+
...prior?.quirks ? { quirks: prior.quirks } : {}
|
|
8279
|
+
};
|
|
8280
|
+
setProviderModels(providerId, entry);
|
|
8281
|
+
return entry;
|
|
8282
|
+
}
|
|
8283
|
+
async function refreshProviderModels(providerId, opts = {}) {
|
|
8284
|
+
const spec = getProviderSpec(providerId);
|
|
8285
|
+
if (!spec) return null;
|
|
8286
|
+
if (!opts.force && !isProviderCacheStale(providerId)) {
|
|
8287
|
+
return getProviderModels(providerId) ?? null;
|
|
8288
|
+
}
|
|
8289
|
+
const apiKey = opts.apiKey ?? getProviderApiKey(providerId);
|
|
8290
|
+
if (spec.requires_key && !apiKey) return null;
|
|
8291
|
+
const result = await fetchProviderModels(spec, apiKey);
|
|
8292
|
+
if (!result.ok) return null;
|
|
8293
|
+
return storeDiscoveredModels(providerId, result.models);
|
|
8294
|
+
}
|
|
8295
|
+
function rerankExcluding(providerId, deadModelId) {
|
|
8296
|
+
const prior = getProviderModels(providerId);
|
|
8297
|
+
if (!prior) return null;
|
|
8298
|
+
const survivors = prior.models.filter((m) => m.id !== deadModelId);
|
|
8299
|
+
const stack = rankModels(providerId, survivors);
|
|
8300
|
+
if (!stack) return null;
|
|
8301
|
+
const entry = { ...prior, models: survivors, tier_stack: stack };
|
|
8302
|
+
setProviderModels(providerId, entry);
|
|
8303
|
+
return entry;
|
|
8304
|
+
}
|
|
8305
|
+
async function refreshStaleProviderCaches() {
|
|
8306
|
+
await Promise.allSettled(
|
|
8307
|
+
getAvailableProviders().filter((p) => isProviderCacheStale(p)).map((p) => refreshProviderModels(p))
|
|
8308
|
+
);
|
|
8309
|
+
}
|
|
8310
|
+
var NON_CHAT, PROVIDER_EXCLUDE;
|
|
8311
|
+
var init_discovery = __esm({
|
|
8312
|
+
"src/ai/llm/discovery.ts"() {
|
|
8313
|
+
"use strict";
|
|
8314
|
+
init_llm_config();
|
|
8315
|
+
init_http();
|
|
8316
|
+
init_models_cache();
|
|
8317
|
+
init_providers();
|
|
8318
|
+
init_ranking();
|
|
8319
|
+
NON_CHAT = /(embed|embedding|whisper|tts|dall-e|davinci|babbage|curie|\bada\b|moderation|-audio|realtime|transcribe|-image|rerank|guard|voice|sora|distil-whisper)/i;
|
|
8320
|
+
PROVIDER_EXCLUDE = {
|
|
8321
|
+
openai: /(chatgpt|-search|deep-research|-pro\b|computer-use|codex-mini|-instruct\b)/i
|
|
8322
|
+
};
|
|
8323
|
+
}
|
|
8324
|
+
});
|
|
8325
|
+
|
|
8326
|
+
// src/ai/llm/heal.ts
|
|
8327
|
+
async function healModelNotFound(opts) {
|
|
8328
|
+
const { provider, tier, deadModel } = opts;
|
|
8329
|
+
const refreshed = await refreshProviderModels(provider, { apiKey: opts.apiKey, force: true });
|
|
8330
|
+
let candidate = refreshed?.tier_stack?.[tier] ?? getCachedTierModel(provider, tier);
|
|
8331
|
+
if (!candidate || candidate === deadModel) {
|
|
8332
|
+
const reranked = rerankExcluding(provider, deadModel);
|
|
8333
|
+
candidate = reranked?.tier_stack?.[tier];
|
|
8334
|
+
}
|
|
8335
|
+
if (!candidate || candidate === deadModel) return null;
|
|
8336
|
+
clearDeadOverride(deadModel, opts.ctx);
|
|
8337
|
+
return {
|
|
8338
|
+
model: candidate,
|
|
8339
|
+
notice: `model ${deadModel} is no longer available \u2014 switched to ${candidate}`
|
|
8340
|
+
};
|
|
8341
|
+
}
|
|
8342
|
+
function clearDeadOverride(deadModel, ctx) {
|
|
8343
|
+
if (getConfigValue("llm-model-override")?.trim() === deadModel) {
|
|
8344
|
+
deleteConfigValue("llm-model-override");
|
|
8345
|
+
}
|
|
8346
|
+
if (ctx?.llm?.modelOverride === deadModel) {
|
|
8347
|
+
ctx.llm.modelOverride = void 0;
|
|
8348
|
+
}
|
|
8349
|
+
}
|
|
8350
|
+
var init_heal = __esm({
|
|
8351
|
+
"src/ai/llm/heal.ts"() {
|
|
8352
|
+
"use strict";
|
|
8353
|
+
init_store();
|
|
8354
|
+
init_discovery();
|
|
8355
|
+
init_models_cache();
|
|
8356
|
+
}
|
|
8357
|
+
});
|
|
8358
|
+
|
|
7745
8359
|
// src/ai/llm/surfaces.ts
|
|
7746
8360
|
function tierForSurface(surface, userTier) {
|
|
7747
8361
|
const spec = SURFACE_SPECS[surface];
|
|
@@ -7769,6 +8383,26 @@ var init_surfaces = __esm({
|
|
|
7769
8383
|
});
|
|
7770
8384
|
|
|
7771
8385
|
// src/ai/llm/session-state.ts
|
|
8386
|
+
var session_state_exports = {};
|
|
8387
|
+
__export(session_state_exports, {
|
|
8388
|
+
availableEngineLabels: () => availableEngineLabels,
|
|
8389
|
+
clearLlmSession: () => clearLlmSession,
|
|
8390
|
+
countAvailableEngines: () => countAvailableEngines,
|
|
8391
|
+
ensureLlmSession: () => ensureLlmSession,
|
|
8392
|
+
formatActiveStack: () => formatActiveStack,
|
|
8393
|
+
formatActiveStackShort: () => formatActiveStackShort,
|
|
8394
|
+
getSessionModelOverride: () => getSessionModelOverride,
|
|
8395
|
+
getSessionProvider: () => getSessionProvider,
|
|
8396
|
+
getSessionTier: () => getSessionTier,
|
|
8397
|
+
isSessionAutoFailover: () => isSessionAutoFailover,
|
|
8398
|
+
resolveActiveProvider: () => resolveActiveProvider,
|
|
8399
|
+
resolveAutoFailoverEnabled: () => resolveAutoFailoverEnabled,
|
|
8400
|
+
resolveEffectiveModelOverride: () => resolveEffectiveModelOverride,
|
|
8401
|
+
resolveEffectiveTier: () => resolveEffectiveTier,
|
|
8402
|
+
resolveModelForActive: () => resolveModelForActive,
|
|
8403
|
+
resolveProviderOrder: () => resolveProviderOrder,
|
|
8404
|
+
validateModelForProvider: () => validateModelForProvider
|
|
8405
|
+
});
|
|
7772
8406
|
function ensureLlmSession(ctx) {
|
|
7773
8407
|
if (!ctx.llm) ctx.llm = {};
|
|
7774
8408
|
return ctx.llm;
|
|
@@ -7793,7 +8427,7 @@ function resolveActiveProvider(ctx) {
|
|
|
7793
8427
|
if (session && hasProviderKey(session)) return session;
|
|
7794
8428
|
const cfg = loadLlmConfig();
|
|
7795
8429
|
if (hasProviderKey(cfg.primary)) return cfg.primary;
|
|
7796
|
-
const available =
|
|
8430
|
+
const available = getAvailableProviders();
|
|
7797
8431
|
if (available.length > 0) return available[0];
|
|
7798
8432
|
return cfg.primary;
|
|
7799
8433
|
}
|
|
@@ -7815,8 +8449,8 @@ function resolveModelForActive(ctx, surface) {
|
|
|
7815
8449
|
const provider = resolveActiveProvider(ctx);
|
|
7816
8450
|
const tier = resolveEffectiveTier(ctx, surface);
|
|
7817
8451
|
const override = resolveEffectiveModelOverride(ctx);
|
|
7818
|
-
const providerOverride = override
|
|
7819
|
-
const modelId =
|
|
8452
|
+
const providerOverride = overrideForProvider(override, provider, provider);
|
|
8453
|
+
const modelId = resolveModelSafe(provider, tier, providerOverride);
|
|
7820
8454
|
return { provider, tier, modelId };
|
|
7821
8455
|
}
|
|
7822
8456
|
function resolveProviderOrder(ctx) {
|
|
@@ -7827,26 +8461,30 @@ function resolveProviderOrder(ctx) {
|
|
|
7827
8461
|
for (const p of cfg.failoverOrder) {
|
|
7828
8462
|
if (p !== active && hasProviderKey(p) && !order.includes(p)) order.push(p);
|
|
7829
8463
|
}
|
|
7830
|
-
for (const p of
|
|
7831
|
-
if (p !== active &&
|
|
8464
|
+
for (const p of getAvailableProviders()) {
|
|
8465
|
+
if (p !== active && !order.includes(p)) order.push(p);
|
|
7832
8466
|
}
|
|
7833
8467
|
return order;
|
|
7834
8468
|
}
|
|
7835
8469
|
function formatActiveStack(ctx, surface = "agentic_investigation") {
|
|
7836
8470
|
const { provider, tier, modelId } = resolveModelForActive(ctx, surface);
|
|
7837
|
-
return `${provider} \xB7 ${tier} \xB7 ${modelId}`;
|
|
8471
|
+
return `${provider} \xB7 ${tier} \xB7 ${modelId ?? "no models yet (run /connect)"}`;
|
|
8472
|
+
}
|
|
8473
|
+
function formatActiveStackShort(ctx, surface = "agentic_investigation") {
|
|
8474
|
+
const { provider, tier } = resolveModelForActive(ctx, surface);
|
|
8475
|
+
return `${provider} \xB7 ${tier}`;
|
|
7838
8476
|
}
|
|
7839
8477
|
function countAvailableEngines() {
|
|
7840
|
-
return
|
|
8478
|
+
return getAvailableProviders().length;
|
|
7841
8479
|
}
|
|
7842
8480
|
function availableEngineLabels() {
|
|
7843
|
-
return
|
|
8481
|
+
return getAvailableProviders();
|
|
7844
8482
|
}
|
|
7845
8483
|
function validateModelForProvider(modelId, provider) {
|
|
7846
|
-
const
|
|
7847
|
-
if (!
|
|
7848
|
-
if (
|
|
7849
|
-
return `Model ${modelId} belongs to ${
|
|
8484
|
+
const hint = modelProviderHint(modelId);
|
|
8485
|
+
if (!hint) return null;
|
|
8486
|
+
if (hint !== provider) {
|
|
8487
|
+
return `Model ${modelId} belongs to ${hint}. Run /provider ${hint} first.`;
|
|
7850
8488
|
}
|
|
7851
8489
|
return null;
|
|
7852
8490
|
}
|
|
@@ -7860,23 +8498,16 @@ var init_session_state = __esm({
|
|
|
7860
8498
|
});
|
|
7861
8499
|
|
|
7862
8500
|
// src/ai/llm/resolver.ts
|
|
7863
|
-
function getProviderOrder(config, ctx) {
|
|
7864
|
-
void config;
|
|
7865
|
-
return resolveProviderOrder(ctx);
|
|
7866
|
-
}
|
|
7867
8501
|
function resolveCompletionContext(surface, opts = {}) {
|
|
7868
8502
|
const activeProvider = resolveActiveProvider(opts.ctx);
|
|
7869
8503
|
const tier = opts.tier ?? resolveEffectiveTier(opts.ctx, surface);
|
|
7870
8504
|
const override = opts.modelOverride ?? resolveEffectiveModelOverride(opts.ctx);
|
|
7871
8505
|
const providerOrder = resolveProviderOrder(opts.ctx);
|
|
7872
8506
|
const modelByProvider = {};
|
|
7873
|
-
for (const provider of providerOrder) {
|
|
7874
|
-
const providerOverride =
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
if (!modelByProvider[activeProvider]) {
|
|
7878
|
-
const activeOverride = override && getCatalogEntry(override)?.provider === activeProvider ? override : void 0;
|
|
7879
|
-
modelByProvider[activeProvider] = resolveModel(activeProvider, tier, activeOverride);
|
|
8507
|
+
for (const provider of /* @__PURE__ */ new Set([...providerOrder, activeProvider])) {
|
|
8508
|
+
const providerOverride = overrideForProvider(override, provider, activeProvider);
|
|
8509
|
+
const model = resolveModelSafe(provider, tier, providerOverride);
|
|
8510
|
+
if (model) modelByProvider[provider] = model;
|
|
7880
8511
|
}
|
|
7881
8512
|
return {
|
|
7882
8513
|
providerOrder,
|
|
@@ -7902,70 +8533,142 @@ var init_resolver2 = __esm({
|
|
|
7902
8533
|
|
|
7903
8534
|
// src/ai/llm/failover.ts
|
|
7904
8535
|
async function completeOnProvider(provider, model, apiKey, req) {
|
|
7905
|
-
|
|
7906
|
-
|
|
8536
|
+
const spec = getProviderSpec(provider);
|
|
8537
|
+
if (!spec) {
|
|
8538
|
+
throw new LlmError("UNKNOWN", `Unknown provider "${provider}" \u2014 run /connect to register it.`, provider);
|
|
8539
|
+
}
|
|
8540
|
+
if (spec.api === "anthropic") {
|
|
8541
|
+
return anthropicComplete(apiKey ?? "", model, req);
|
|
8542
|
+
}
|
|
8543
|
+
return openaiCompatComplete(provider, spec.base_url, apiKey, model, req);
|
|
8544
|
+
}
|
|
8545
|
+
function usableKey(provider, ctx) {
|
|
8546
|
+
const spec = getProviderSpec(provider);
|
|
8547
|
+
if (!spec) return { ok: false };
|
|
8548
|
+
const apiKey = getApiKeyForProvider(provider, ctx);
|
|
8549
|
+
if (spec.requires_key && !apiKey) return { ok: false };
|
|
8550
|
+
return { ok: true, apiKey };
|
|
8551
|
+
}
|
|
8552
|
+
async function resolveModelWithDiscovery(provider, cfg, opts) {
|
|
8553
|
+
const known = cfg.modelByProvider[provider];
|
|
8554
|
+
if (known) return known;
|
|
8555
|
+
const override = overrideForProvider(opts.modelOverride, provider, cfg.activeProvider);
|
|
8556
|
+
const direct = resolveModelSafe(provider, cfg.tier, override);
|
|
8557
|
+
if (direct) return direct;
|
|
8558
|
+
await refreshProviderModels(provider, { apiKey: opts.apiKey, force: true }).catch(() => null);
|
|
8559
|
+
return resolveModelSafe(provider, cfg.tier, override);
|
|
8560
|
+
}
|
|
8561
|
+
function stripTools(req) {
|
|
8562
|
+
const { tools: _tools, ...rest } = req;
|
|
8563
|
+
return rest;
|
|
7907
8564
|
}
|
|
7908
8565
|
async function completeWithFailover(req, opts = {}) {
|
|
7909
|
-
const
|
|
8566
|
+
const cfg = resolveCompletionContext(req.surface, {
|
|
7910
8567
|
max_tokens: req.max_tokens,
|
|
7911
8568
|
tier: opts.tier,
|
|
7912
8569
|
modelOverride: opts.modelOverride,
|
|
7913
8570
|
ctx: opts.ctx
|
|
7914
8571
|
});
|
|
7915
|
-
const providers =
|
|
8572
|
+
const providers = cfg.providerOrder;
|
|
7916
8573
|
if (providers.length === 0) {
|
|
7917
|
-
throw new Error(
|
|
8574
|
+
throw new Error(NO_PROVIDER_MESSAGE);
|
|
7918
8575
|
}
|
|
8576
|
+
const notices = [];
|
|
7919
8577
|
let lastError;
|
|
7920
8578
|
let failoverFrom;
|
|
8579
|
+
const buildMeta = (provider, model, response) => ({
|
|
8580
|
+
provider_used: provider,
|
|
8581
|
+
model_used: model,
|
|
8582
|
+
...response.token_usage ?? {},
|
|
8583
|
+
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {},
|
|
8584
|
+
...notices.length > 0 ? { notices: [...notices] } : {}
|
|
8585
|
+
});
|
|
7921
8586
|
for (let i = 0; i < providers.length; i++) {
|
|
7922
8587
|
const provider = providers[i];
|
|
7923
|
-
const
|
|
7924
|
-
if (!
|
|
7925
|
-
let model =
|
|
8588
|
+
const key = usableKey(provider, opts.ctx);
|
|
8589
|
+
if (!key.ok) continue;
|
|
8590
|
+
let model = await resolveModelWithDiscovery(provider, cfg, {
|
|
8591
|
+
modelOverride: opts.modelOverride,
|
|
8592
|
+
apiKey: key.apiKey
|
|
8593
|
+
});
|
|
8594
|
+
if (!model) {
|
|
8595
|
+
lastError = new LlmError(
|
|
8596
|
+
"MODEL_NOT_FOUND",
|
|
8597
|
+
`No models known for provider "${provider}". Run /connect or /model refresh.`,
|
|
8598
|
+
provider
|
|
8599
|
+
);
|
|
8600
|
+
continue;
|
|
8601
|
+
}
|
|
8602
|
+
let effectiveReq = req;
|
|
8603
|
+
if (req.tools?.length && modelHasNoToolsQuirk(provider, model)) {
|
|
8604
|
+
effectiveReq = stripTools(req);
|
|
8605
|
+
notices.push(`${model} doesn't support tool calling \u2014 answering without live data tools`);
|
|
8606
|
+
}
|
|
7926
8607
|
try {
|
|
7927
|
-
const response = await completeOnProvider(provider, model, apiKey,
|
|
7928
|
-
const meta =
|
|
7929
|
-
provider_used: provider,
|
|
7930
|
-
model_used: model,
|
|
7931
|
-
...response.token_usage ?? {},
|
|
7932
|
-
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
|
|
7933
|
-
};
|
|
8608
|
+
const response = await completeOnProvider(provider, model, key.apiKey, effectiveReq);
|
|
8609
|
+
const meta = buildMeta(provider, model, response);
|
|
7934
8610
|
recordLlmUsage(response.token_usage);
|
|
7935
8611
|
return { response, meta };
|
|
7936
8612
|
} catch (err) {
|
|
7937
|
-
|
|
8613
|
+
let llmErr = err;
|
|
7938
8614
|
if (llmErr.name !== "LlmError") throw err;
|
|
7939
8615
|
lastError = llmErr;
|
|
7940
|
-
if (llmErr.code === "
|
|
7941
|
-
|
|
8616
|
+
if (llmErr.code === "TOOLS_UNSUPPORTED" && effectiveReq.tools?.length) {
|
|
8617
|
+
markModelNoTools(provider, model);
|
|
8618
|
+
notices.push(`${model} doesn't support tool calling \u2014 retrying without live data tools`);
|
|
7942
8619
|
try {
|
|
7943
|
-
const response = await completeOnProvider(provider, model, apiKey,
|
|
7944
|
-
const meta =
|
|
7945
|
-
provider_used: provider,
|
|
7946
|
-
model_used: model,
|
|
7947
|
-
...response.token_usage ?? {},
|
|
7948
|
-
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
|
|
7949
|
-
};
|
|
8620
|
+
const response = await completeOnProvider(provider, model, key.apiKey, stripTools(effectiveReq));
|
|
8621
|
+
const meta = buildMeta(provider, model, response);
|
|
7950
8622
|
recordLlmUsage(response.token_usage);
|
|
7951
8623
|
return { response, meta };
|
|
7952
8624
|
} catch (retryErr) {
|
|
7953
8625
|
const retryLlm = retryErr;
|
|
7954
|
-
if (retryLlm.name
|
|
7955
|
-
|
|
8626
|
+
if (retryLlm.name !== "LlmError") throw retryErr;
|
|
8627
|
+
lastError = retryLlm;
|
|
8628
|
+
llmErr = retryLlm;
|
|
8629
|
+
}
|
|
8630
|
+
}
|
|
8631
|
+
if (llmErr.code === "MODEL_NOT_FOUND") {
|
|
8632
|
+
const healed = await healModelNotFound({
|
|
8633
|
+
provider,
|
|
8634
|
+
tier: cfg.tier,
|
|
8635
|
+
deadModel: model,
|
|
8636
|
+
apiKey: key.apiKey,
|
|
8637
|
+
ctx: opts.ctx
|
|
8638
|
+
}).catch(() => null);
|
|
8639
|
+
if (healed) {
|
|
8640
|
+
notices.push(healed.notice);
|
|
8641
|
+
model = healed.model;
|
|
8642
|
+
let retryReq = req;
|
|
8643
|
+
if (req.tools?.length && modelHasNoToolsQuirk(provider, model)) {
|
|
8644
|
+
retryReq = stripTools(req);
|
|
8645
|
+
notices.push(`${model} doesn't support tool calling \u2014 answering without live data tools`);
|
|
8646
|
+
}
|
|
8647
|
+
try {
|
|
8648
|
+
const response = await completeOnProvider(provider, model, key.apiKey, retryReq);
|
|
8649
|
+
const meta = buildMeta(provider, model, response);
|
|
8650
|
+
recordLlmUsage(response.token_usage);
|
|
8651
|
+
return { response, meta };
|
|
8652
|
+
} catch (retryErr) {
|
|
8653
|
+
const retryLlm = retryErr;
|
|
8654
|
+
if (retryLlm.name !== "LlmError") throw retryErr;
|
|
8655
|
+
lastError = retryLlm;
|
|
8656
|
+
llmErr = retryLlm;
|
|
8657
|
+
}
|
|
7956
8658
|
}
|
|
7957
8659
|
}
|
|
7958
8660
|
if (!isFailoverEligible(llmErr.code)) throw llmErr;
|
|
7959
8661
|
const next = providers[i + 1];
|
|
7960
8662
|
if (next) {
|
|
7961
8663
|
failoverFrom = failoverFrom ?? provider;
|
|
8664
|
+
notices.push(`${provider} unavailable (${llmErr.code.toLowerCase()}) \u2014 trying ${next}`);
|
|
7962
8665
|
opts.onFailover?.(provider, next, llmErr.code);
|
|
7963
8666
|
continue;
|
|
7964
8667
|
}
|
|
7965
8668
|
throw llmErr;
|
|
7966
8669
|
}
|
|
7967
8670
|
}
|
|
7968
|
-
throw lastError ?? new Error(
|
|
8671
|
+
throw lastError ?? new Error(NO_PROVIDER_MESSAGE);
|
|
7969
8672
|
}
|
|
7970
8673
|
async function* streamWithFailover(req, opts = {}) {
|
|
7971
8674
|
const cfg = resolveCompletionContext(req.surface, {
|
|
@@ -7974,23 +8677,47 @@ async function* streamWithFailover(req, opts = {}) {
|
|
|
7974
8677
|
modelOverride: opts.modelOverride,
|
|
7975
8678
|
ctx: opts.ctx
|
|
7976
8679
|
});
|
|
7977
|
-
const providers =
|
|
8680
|
+
const providers = cfg.providerOrder;
|
|
7978
8681
|
if (providers.length === 0) {
|
|
7979
|
-
throw new Error(
|
|
8682
|
+
throw new Error(NO_PROVIDER_MESSAGE);
|
|
7980
8683
|
}
|
|
8684
|
+
const notices = [];
|
|
7981
8685
|
let lastError;
|
|
7982
8686
|
let failoverFrom;
|
|
8687
|
+
async function* streamOnProvider(provider, model, apiKey) {
|
|
8688
|
+
const spec = getProviderSpec(provider);
|
|
8689
|
+
if (!spec) {
|
|
8690
|
+
throw new LlmError("UNKNOWN", `Unknown provider "${provider}" \u2014 run /connect to register it.`, provider);
|
|
8691
|
+
}
|
|
8692
|
+
if (spec.api === "anthropic") {
|
|
8693
|
+
yield* anthropicStream(apiKey ?? "", model, req);
|
|
8694
|
+
return;
|
|
8695
|
+
}
|
|
8696
|
+
yield* openaiCompatStream(provider, spec.base_url, apiKey, model, req);
|
|
8697
|
+
}
|
|
7983
8698
|
for (let i = 0; i < providers.length; i++) {
|
|
7984
8699
|
const provider = providers[i];
|
|
7985
|
-
const
|
|
7986
|
-
if (!
|
|
7987
|
-
|
|
7988
|
-
|
|
8700
|
+
const key = usableKey(provider, opts.ctx);
|
|
8701
|
+
if (!key.ok) continue;
|
|
8702
|
+
let model = await resolveModelWithDiscovery(provider, cfg, {
|
|
8703
|
+
modelOverride: opts.modelOverride,
|
|
8704
|
+
apiKey: key.apiKey
|
|
8705
|
+
});
|
|
8706
|
+
if (!model) {
|
|
8707
|
+
lastError = new LlmError(
|
|
8708
|
+
"MODEL_NOT_FOUND",
|
|
8709
|
+
`No models known for provider "${provider}". Run /connect or /model refresh.`,
|
|
8710
|
+
provider
|
|
8711
|
+
);
|
|
8712
|
+
continue;
|
|
8713
|
+
}
|
|
8714
|
+
let yieldedAny = false;
|
|
8715
|
+
const attempt = async function* (attemptModel) {
|
|
7989
8716
|
let fullText = "";
|
|
7990
|
-
const
|
|
7991
|
-
for await (const event of streamFn(apiKey, model, req)) {
|
|
8717
|
+
for await (const event of streamOnProvider(provider, attemptModel, key.apiKey)) {
|
|
7992
8718
|
if (event.type === "text_delta") {
|
|
7993
8719
|
fullText += event.text;
|
|
8720
|
+
yieldedAny = true;
|
|
7994
8721
|
yield event;
|
|
7995
8722
|
}
|
|
7996
8723
|
}
|
|
@@ -7998,10 +8725,11 @@ async function* streamWithFailover(req, opts = {}) {
|
|
|
7998
8725
|
recordLlmUsage({ input_tokens: 0, output_tokens: estimatedOut });
|
|
7999
8726
|
const meta = {
|
|
8000
8727
|
provider_used: provider,
|
|
8001
|
-
model_used:
|
|
8728
|
+
model_used: attemptModel,
|
|
8002
8729
|
input_tokens: 0,
|
|
8003
8730
|
output_tokens: estimatedOut,
|
|
8004
|
-
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
|
|
8731
|
+
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {},
|
|
8732
|
+
...notices.length > 0 ? { notices: [...notices] } : {}
|
|
8005
8733
|
};
|
|
8006
8734
|
yield {
|
|
8007
8735
|
type: "done",
|
|
@@ -8013,32 +8741,66 @@ async function* streamWithFailover(req, opts = {}) {
|
|
|
8013
8741
|
},
|
|
8014
8742
|
meta
|
|
8015
8743
|
};
|
|
8744
|
+
};
|
|
8745
|
+
try {
|
|
8746
|
+
yield* attempt(model);
|
|
8016
8747
|
return;
|
|
8017
8748
|
} catch (err) {
|
|
8018
8749
|
const llmErr = err;
|
|
8019
8750
|
if (llmErr.name !== "LlmError") throw err;
|
|
8020
8751
|
lastError = llmErr;
|
|
8021
|
-
if (
|
|
8752
|
+
if (yieldedAny) throw llmErr;
|
|
8753
|
+
if (llmErr.code === "MODEL_NOT_FOUND") {
|
|
8754
|
+
const healed = await healModelNotFound({
|
|
8755
|
+
provider,
|
|
8756
|
+
tier: cfg.tier,
|
|
8757
|
+
deadModel: model,
|
|
8758
|
+
apiKey: key.apiKey,
|
|
8759
|
+
ctx: opts.ctx
|
|
8760
|
+
}).catch(() => null);
|
|
8761
|
+
if (healed) {
|
|
8762
|
+
notices.push(healed.notice);
|
|
8763
|
+
model = healed.model;
|
|
8764
|
+
try {
|
|
8765
|
+
yield* attempt(model);
|
|
8766
|
+
return;
|
|
8767
|
+
} catch (retryErr) {
|
|
8768
|
+
const retryLlm = retryErr;
|
|
8769
|
+
if (retryLlm.name !== "LlmError") throw retryErr;
|
|
8770
|
+
lastError = retryLlm;
|
|
8771
|
+
if (yieldedAny) throw retryLlm;
|
|
8772
|
+
}
|
|
8773
|
+
}
|
|
8774
|
+
}
|
|
8775
|
+
if (!isFailoverEligible(lastError.code)) throw lastError;
|
|
8022
8776
|
const next = providers[i + 1];
|
|
8023
8777
|
if (next) {
|
|
8024
8778
|
failoverFrom = failoverFrom ?? provider;
|
|
8025
|
-
|
|
8779
|
+
notices.push(`${provider} unavailable (${lastError.code.toLowerCase()}) \u2014 trying ${next}`);
|
|
8780
|
+
opts.onFailover?.(provider, next, lastError.code);
|
|
8026
8781
|
continue;
|
|
8027
8782
|
}
|
|
8028
|
-
throw
|
|
8783
|
+
throw lastError;
|
|
8029
8784
|
}
|
|
8030
8785
|
}
|
|
8031
|
-
throw lastError ?? new Error(
|
|
8786
|
+
throw lastError ?? new Error(NO_PROVIDER_MESSAGE);
|
|
8032
8787
|
}
|
|
8788
|
+
var NO_PROVIDER_MESSAGE;
|
|
8033
8789
|
var init_failover = __esm({
|
|
8034
8790
|
"src/ai/llm/failover.ts"() {
|
|
8035
8791
|
"use strict";
|
|
8036
8792
|
init_usage_stats();
|
|
8037
8793
|
init_anthropic();
|
|
8038
|
-
|
|
8794
|
+
init_openai_compat();
|
|
8039
8795
|
init_catalog();
|
|
8796
|
+
init_discovery();
|
|
8040
8797
|
init_errors2();
|
|
8798
|
+
init_heal();
|
|
8799
|
+
init_models_cache();
|
|
8800
|
+
init_providers();
|
|
8801
|
+
init_types2();
|
|
8041
8802
|
init_resolver2();
|
|
8803
|
+
NO_PROVIDER_MESSAGE = "No LLM provider configured. Run /connect and paste any API key (Anthropic, OpenAI, Groq, Gemini, ...).";
|
|
8042
8804
|
}
|
|
8043
8805
|
});
|
|
8044
8806
|
|
|
@@ -8407,8 +9169,8 @@ function markFailure(ctx) {
|
|
|
8407
9169
|
}
|
|
8408
9170
|
async function loadOrBuildTaxonomy(profile, forceRegen, ctx) {
|
|
8409
9171
|
if (!forceRegen) {
|
|
8410
|
-
const
|
|
8411
|
-
if (
|
|
9172
|
+
const cached2 = loadCachedTaxonomy(profile);
|
|
9173
|
+
if (cached2) return cached2;
|
|
8412
9174
|
}
|
|
8413
9175
|
const spinnerText = forceRegen ? "Rebuilding market taxonomy\u2026" : "Researching your market taxonomy\u2026";
|
|
8414
9176
|
const spinner = ora2({ text: spinnerText, discardStdin: false }).start();
|
|
@@ -8566,7 +9328,7 @@ __export(ingest_exports, {
|
|
|
8566
9328
|
});
|
|
8567
9329
|
import chalk9 from "chalk";
|
|
8568
9330
|
import ora3 from "ora";
|
|
8569
|
-
import { readFileSync as
|
|
9331
|
+
import { readFileSync as readFileSync11, existsSync as existsSync12 } from "fs";
|
|
8570
9332
|
import { basename as basename2 } from "path";
|
|
8571
9333
|
async function handler2(args, ctx) {
|
|
8572
9334
|
const { positional, flags } = parseArgs(args, [
|
|
@@ -8590,7 +9352,7 @@ async function handler2(args, ctx) {
|
|
|
8590
9352
|
console.error(chalk9.dim(" /ingest --demo [--scenario <name>]"));
|
|
8591
9353
|
process.exit(1);
|
|
8592
9354
|
}
|
|
8593
|
-
if (!
|
|
9355
|
+
if (!existsSync12(file)) {
|
|
8594
9356
|
console.error(chalk9.red(` File not found: ${file}`));
|
|
8595
9357
|
process.exit(1);
|
|
8596
9358
|
}
|
|
@@ -8608,7 +9370,7 @@ async function handler2(args, ctx) {
|
|
|
8608
9370
|
try {
|
|
8609
9371
|
await initSchema();
|
|
8610
9372
|
spinner.text = "Parsing CSV...";
|
|
8611
|
-
const content =
|
|
9373
|
+
const content = readFileSync11(file, "utf-8");
|
|
8612
9374
|
const { rows, headers } = parseCSV(content);
|
|
8613
9375
|
if (rows.length === 0) {
|
|
8614
9376
|
spinner.fail("CSV is empty");
|
|
@@ -10933,16 +11695,16 @@ var init_compute = __esm({
|
|
|
10933
11695
|
});
|
|
10934
11696
|
|
|
10935
11697
|
// src/data/playbook.ts
|
|
10936
|
-
import { existsSync as
|
|
10937
|
-
import { join as
|
|
11698
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12, appendFileSync } from "fs";
|
|
11699
|
+
import { join as join12 } from "path";
|
|
10938
11700
|
function playsPath() {
|
|
10939
|
-
return
|
|
11701
|
+
return join12(getMemoryDir(), PLAYS_FILE);
|
|
10940
11702
|
}
|
|
10941
11703
|
function getCustomPlays() {
|
|
10942
11704
|
const path = playsPath();
|
|
10943
|
-
if (!
|
|
11705
|
+
if (!existsSync13(path)) return [];
|
|
10944
11706
|
const out = [];
|
|
10945
|
-
for (const line of
|
|
11707
|
+
for (const line of readFileSync12(path, "utf-8").split("\n")) {
|
|
10946
11708
|
const trimmed = line.trim();
|
|
10947
11709
|
if (!trimmed) continue;
|
|
10948
11710
|
try {
|
|
@@ -11534,7 +12296,7 @@ async function runMetricsAnalysis(options = {}) {
|
|
|
11534
12296
|
if (options.findings) {
|
|
11535
12297
|
if (!canUseReplAi(options.ctx)) {
|
|
11536
12298
|
throw new Error(
|
|
11537
|
-
"AI metrics findings require stored API keys. Run `ntrp`,
|
|
12299
|
+
"AI metrics findings require stored API keys. Run `ntrp`, then /connect (any provider key), then /metrics --findings."
|
|
11538
12300
|
);
|
|
11539
12301
|
}
|
|
11540
12302
|
options.onProgress?.("findings");
|
|
@@ -11943,7 +12705,8 @@ async function* streamFindings(input, ctx) {
|
|
|
11943
12705
|
findings,
|
|
11944
12706
|
model_used: meta.model_used,
|
|
11945
12707
|
provider_used: meta.provider_used,
|
|
11946
|
-
raw_prompt: userMessage
|
|
12708
|
+
raw_prompt: userMessage,
|
|
12709
|
+
usage: meta
|
|
11947
12710
|
};
|
|
11948
12711
|
}
|
|
11949
12712
|
}
|
|
@@ -12244,9 +13007,9 @@ var init_tool_schemas = __esm({
|
|
|
12244
13007
|
});
|
|
12245
13008
|
|
|
12246
13009
|
// src/ai/privacy.ts
|
|
12247
|
-
import { existsSync as
|
|
13010
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2 } from "fs";
|
|
12248
13011
|
import { homedir as homedir4 } from "os";
|
|
12249
|
-
import { join as
|
|
13012
|
+
import { join as join13 } from "path";
|
|
12250
13013
|
function stripPII(obj) {
|
|
12251
13014
|
if (obj === null || obj === void 0) return obj;
|
|
12252
13015
|
if (typeof obj !== "object") return obj;
|
|
@@ -12261,14 +13024,14 @@ function stripPII(obj) {
|
|
|
12261
13024
|
return out;
|
|
12262
13025
|
}
|
|
12263
13026
|
function ensureAuditDir() {
|
|
12264
|
-
if (!
|
|
13027
|
+
if (!existsSync14(AUDIT_DIR)) {
|
|
12265
13028
|
mkdirSync8(AUDIT_DIR, { recursive: true });
|
|
12266
13029
|
}
|
|
12267
13030
|
}
|
|
12268
13031
|
function logToolCall(entry) {
|
|
12269
13032
|
ensureAuditDir();
|
|
12270
13033
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
12271
|
-
const path =
|
|
13034
|
+
const path = join13(AUDIT_DIR, `agentic-${date}.jsonl`);
|
|
12272
13035
|
appendFileSync2(path, JSON.stringify(entry) + "\n");
|
|
12273
13036
|
}
|
|
12274
13037
|
var PII_FIELDS, AUDIT_DIR;
|
|
@@ -12292,7 +13055,7 @@ var init_privacy = __esm({
|
|
|
12292
13055
|
"raw_data",
|
|
12293
13056
|
"metadata"
|
|
12294
13057
|
]);
|
|
12295
|
-
AUDIT_DIR =
|
|
13058
|
+
AUDIT_DIR = join13(homedir4(), ".ntrp", "audit");
|
|
12296
13059
|
}
|
|
12297
13060
|
});
|
|
12298
13061
|
|
|
@@ -12768,7 +13531,7 @@ __export(ingest_chat_exports, {
|
|
|
12768
13531
|
loadDemoFromChat: () => loadDemoFromChat,
|
|
12769
13532
|
looksLikeFilePath: () => looksLikeFilePath
|
|
12770
13533
|
});
|
|
12771
|
-
import { existsSync as
|
|
13534
|
+
import { existsSync as existsSync15 } from "fs";
|
|
12772
13535
|
import { basename as basename3, resolve as resolve4 } from "path";
|
|
12773
13536
|
import { homedir as homedir5 } from "os";
|
|
12774
13537
|
import chalk15 from "chalk";
|
|
@@ -12788,11 +13551,11 @@ function extractFilePath(input) {
|
|
|
12788
13551
|
const m = trimmed.match(re);
|
|
12789
13552
|
if (m?.[1]) {
|
|
12790
13553
|
const p = expandPath(m[1]);
|
|
12791
|
-
if (
|
|
13554
|
+
if (existsSync15(p)) return p;
|
|
12792
13555
|
}
|
|
12793
13556
|
if (!m?.[1] && re.test(trimmed) && trimmed.toLowerCase().endsWith(".csv")) {
|
|
12794
13557
|
const p = expandPath(trimmed.replace(/^["']|["']$/g, ""));
|
|
12795
|
-
if (
|
|
13558
|
+
if (existsSync15(p)) return p;
|
|
12796
13559
|
}
|
|
12797
13560
|
}
|
|
12798
13561
|
return null;
|
|
@@ -12822,12 +13585,12 @@ async function ingestFromChat(ctx, filePath) {
|
|
|
12822
13585
|
}
|
|
12823
13586
|
const { handler: ingest } = await Promise.resolve().then(() => (init_ingest(), ingest_exports));
|
|
12824
13587
|
const { detectEntityType: detectEntityType2 } = await Promise.resolve().then(() => (init_csv_detect(), csv_detect_exports));
|
|
12825
|
-
const { readFileSync:
|
|
13588
|
+
const { readFileSync: readFileSync19 } = await import("fs");
|
|
12826
13589
|
const { parseCSV: parseCSV2 } = await Promise.resolve().then(() => (init_csv_parse(), csv_parse_exports));
|
|
12827
13590
|
const { getStoredApiKey } = await Promise.resolve().then(() => (init_repl_api(), repl_api_exports));
|
|
12828
13591
|
let headerCheckFailed = false;
|
|
12829
13592
|
try {
|
|
12830
|
-
const raw =
|
|
13593
|
+
const raw = readFileSync19(filePath, "utf-8");
|
|
12831
13594
|
const { headers } = parseCSV2(raw);
|
|
12832
13595
|
const detected = detectEntityType2(headers, "unknown");
|
|
12833
13596
|
if (!detected) headerCheckFailed = true;
|
|
@@ -13186,15 +13949,18 @@ function inferHandoffTarget(input) {
|
|
|
13186
13949
|
return "plan";
|
|
13187
13950
|
}
|
|
13188
13951
|
function isShipIntent(input) {
|
|
13189
|
-
|
|
13190
|
-
|
|
13191
|
-
);
|
|
13952
|
+
const line = input.trim();
|
|
13953
|
+
if (/\?\s*$/.test(line) || QUESTION_LEAD_RE.test(line)) return false;
|
|
13954
|
+
return SHIP_INTENT_RE.test(line);
|
|
13192
13955
|
}
|
|
13956
|
+
var QUESTION_LEAD_RE, SHIP_INTENT_RE;
|
|
13193
13957
|
var init_handoff_draft = __esm({
|
|
13194
13958
|
"src/conversation/handoff-draft.ts"() {
|
|
13195
13959
|
"use strict";
|
|
13196
13960
|
init_profile();
|
|
13197
13961
|
init_session_analysis();
|
|
13962
|
+
QUESTION_LEAD_RE = /^\s*(what|why|how|when|where|who|which|is|are|was|were|do|does|did|explain|tell me|help me understand)\b/i;
|
|
13963
|
+
SHIP_INTENT_RE = /\b(ship|export|deliver|write[- ]?up|board memo|action plan|turn (this|it|that) into|(draft|create|make|build|prepare|generate|send)\s+(me\s+)?(a\s+|the\s+)?hand[- ]?off|hand[- ]?off\s+(prompt|doc|document|plan))\b/i;
|
|
13198
13964
|
}
|
|
13199
13965
|
});
|
|
13200
13966
|
|
|
@@ -13575,12 +14341,12 @@ async function handleDraftHandoff(input) {
|
|
|
13575
14341
|
};
|
|
13576
14342
|
}
|
|
13577
14343
|
async function executeToolCall(name, input, ctx) {
|
|
13578
|
-
const
|
|
13579
|
-
if (!
|
|
14344
|
+
const handler46 = HANDLERS[name];
|
|
14345
|
+
if (!handler46) {
|
|
13580
14346
|
return JSON.stringify({ error: `Unknown tool '${name}'` });
|
|
13581
14347
|
}
|
|
13582
14348
|
const start = Date.now();
|
|
13583
|
-
const rawResult = await
|
|
14349
|
+
const rawResult = await handler46(input, ctx);
|
|
13584
14350
|
const safeResult = stripPII(rawResult);
|
|
13585
14351
|
const resultJson = JSON.stringify(safeResult);
|
|
13586
14352
|
const duration = Date.now() - start;
|
|
@@ -13973,7 +14739,7 @@ async function runDiagnosis(options = {}) {
|
|
|
13973
14739
|
if (options.findings) {
|
|
13974
14740
|
if (!canUseReplAi(options.ctx)) {
|
|
13975
14741
|
throw new Error(
|
|
13976
|
-
"AI findings require stored API keys. Run `ntrp`, then /
|
|
14742
|
+
"AI findings require stored API keys. Run `ntrp`, then /connect (any provider key), and use /diagnose --findings."
|
|
13977
14743
|
);
|
|
13978
14744
|
}
|
|
13979
14745
|
if (options.deep) {
|
|
@@ -14116,7 +14882,7 @@ async function handler3(args, ctx) {
|
|
|
14116
14882
|
console.log();
|
|
14117
14883
|
console.log(" " + chalk16.red("AI findings run only in the interactive REPL."));
|
|
14118
14884
|
console.log(" " + chalk16.dim("Vital signs compute without a key \u2014 omit --findings for numbers only."));
|
|
14119
|
-
console.log(" " + chalk16.dim("Start with ") + paint("accent", "ntrp") + chalk16.dim(",
|
|
14885
|
+
console.log(" " + chalk16.dim("Start with ") + paint("accent", "ntrp") + chalk16.dim(", run ") + paint("accent", "/connect") + chalk16.dim(" (any provider key), then /diagnose --findings."));
|
|
14120
14886
|
console.log();
|
|
14121
14887
|
return;
|
|
14122
14888
|
}
|
|
@@ -14776,6 +15542,235 @@ var init_profile2 = __esm({
|
|
|
14776
15542
|
}
|
|
14777
15543
|
});
|
|
14778
15544
|
|
|
15545
|
+
// src/ai/llm/detect.ts
|
|
15546
|
+
function detectProviderByKey(key) {
|
|
15547
|
+
const k = key.trim();
|
|
15548
|
+
const specs = listProviderSpecs();
|
|
15549
|
+
let best;
|
|
15550
|
+
for (const spec of specs) {
|
|
15551
|
+
for (const prefix of spec.key_prefixes) {
|
|
15552
|
+
if (k.startsWith(prefix) && (!best || prefix.length > best.length)) {
|
|
15553
|
+
best = { id: spec.id, length: prefix.length };
|
|
15554
|
+
}
|
|
15555
|
+
}
|
|
15556
|
+
}
|
|
15557
|
+
if (best) return { certain: best.id, candidates: [best.id] };
|
|
15558
|
+
const shared = specs.filter((s) => s.shared_prefixes.some((p) => k.startsWith(p)));
|
|
15559
|
+
if (shared.length > 0) return { candidates: shared.map((s) => s.id) };
|
|
15560
|
+
const noPrefix = specs.filter(
|
|
15561
|
+
(s) => s.requires_key && !s.custom && s.key_prefixes.length === 0 && s.shared_prefixes.length === 0
|
|
15562
|
+
);
|
|
15563
|
+
return { candidates: noPrefix.map((s) => s.id) };
|
|
15564
|
+
}
|
|
15565
|
+
async function probeProviders(key, candidateIds, timeoutMs = 5e3) {
|
|
15566
|
+
const results = await Promise.all(
|
|
15567
|
+
candidateIds.map(async (id) => {
|
|
15568
|
+
const spec = getProviderSpec(id);
|
|
15569
|
+
if (!spec) return { id, ok: false, status: 0 };
|
|
15570
|
+
const result = await fetchProviderModels(spec, key, timeoutMs);
|
|
15571
|
+
if (result.ok) return { id, ok: true, models: result.models };
|
|
15572
|
+
return { id, ok: false, status: result.status };
|
|
15573
|
+
})
|
|
15574
|
+
);
|
|
15575
|
+
const accepted = [];
|
|
15576
|
+
let sawNetworkFailure = false;
|
|
15577
|
+
for (const r of results) {
|
|
15578
|
+
if (r.ok && r.models.length > 0) accepted.push({ provider: r.id, models: r.models });
|
|
15579
|
+
else if (!r.ok && r.status === 0) sawNetworkFailure = true;
|
|
15580
|
+
}
|
|
15581
|
+
return { accepted, sawNetworkFailure };
|
|
15582
|
+
}
|
|
15583
|
+
var init_detect = __esm({
|
|
15584
|
+
"src/ai/llm/detect.ts"() {
|
|
15585
|
+
"use strict";
|
|
15586
|
+
init_discovery();
|
|
15587
|
+
init_providers();
|
|
15588
|
+
}
|
|
15589
|
+
});
|
|
15590
|
+
|
|
15591
|
+
// src/services/connect.ts
|
|
15592
|
+
var connect_exports = {};
|
|
15593
|
+
__export(connect_exports, {
|
|
15594
|
+
ConnectCancelled: () => ConnectCancelled,
|
|
15595
|
+
ConnectError: () => ConnectError,
|
|
15596
|
+
connectCustomEndpoint: () => connectCustomEndpoint,
|
|
15597
|
+
connectKeyless: () => connectKeyless,
|
|
15598
|
+
connectWithKey: () => connectWithKey,
|
|
15599
|
+
describeConnectOutcome: () => describeConnectOutcome
|
|
15600
|
+
});
|
|
15601
|
+
function finishConnect(spec, opts) {
|
|
15602
|
+
const before = getAvailableProviders();
|
|
15603
|
+
if (opts.key) {
|
|
15604
|
+
setConfigValue(spec.key_config_name, opts.key);
|
|
15605
|
+
}
|
|
15606
|
+
const entry = opts.models && opts.models.length > 0 ? storeDiscoveredModels(spec.id, opts.models) : null;
|
|
15607
|
+
const cfg = loadLlmConfig();
|
|
15608
|
+
let becamePrimary = false;
|
|
15609
|
+
if (cfg.primary !== spec.id && (before.length === 0 || !hasProviderKey(cfg.primary))) {
|
|
15610
|
+
setConfigValue("llm-primary", spec.id);
|
|
15611
|
+
becamePrimary = true;
|
|
15612
|
+
}
|
|
15613
|
+
return {
|
|
15614
|
+
provider: spec.id,
|
|
15615
|
+
label: spec.label,
|
|
15616
|
+
modelCount: entry?.models.length ?? 0,
|
|
15617
|
+
...entry ? { stack: entry.tier_stack } : {},
|
|
15618
|
+
becamePrimary,
|
|
15619
|
+
offline: !!opts.offline
|
|
15620
|
+
};
|
|
15621
|
+
}
|
|
15622
|
+
async function connectWithKey(rawKey, opts = {}) {
|
|
15623
|
+
const key = rawKey.trim();
|
|
15624
|
+
if (!key) throw new ConnectError("Empty key.");
|
|
15625
|
+
if (opts.providerId) {
|
|
15626
|
+
const spec = getProviderSpec(opts.providerId);
|
|
15627
|
+
if (!spec) {
|
|
15628
|
+
throw new ConnectError(
|
|
15629
|
+
`Unknown provider "${opts.providerId}". Use a built-in id or /connect --base-url <url> --id ${opts.providerId} for a custom endpoint.`
|
|
15630
|
+
);
|
|
15631
|
+
}
|
|
15632
|
+
const result = await fetchProviderModels(spec, key);
|
|
15633
|
+
if (result.ok) return finishConnect(spec, { key, models: result.models });
|
|
15634
|
+
if (result.status === 0) {
|
|
15635
|
+
return finishConnect(spec, { key, offline: true });
|
|
15636
|
+
}
|
|
15637
|
+
throw new ConnectError(`${spec.label} rejected this key (HTTP ${result.status}) \u2014 double-check it and try again.`);
|
|
15638
|
+
}
|
|
15639
|
+
const detection = detectProviderByKey(key);
|
|
15640
|
+
if (detection.certain) {
|
|
15641
|
+
const spec = getProviderSpec(detection.certain);
|
|
15642
|
+
const result = await fetchProviderModels(spec, key);
|
|
15643
|
+
if (result.ok) return finishConnect(spec, { key, models: result.models });
|
|
15644
|
+
if (result.status === 0) return finishConnect(spec, { key, offline: true });
|
|
15645
|
+
throw new ConnectError(`${spec.label} rejected this key (HTTP ${result.status}) \u2014 double-check it and try again.`);
|
|
15646
|
+
}
|
|
15647
|
+
const report = await probeProviders(key, detection.candidates);
|
|
15648
|
+
if (report.accepted.length === 1) {
|
|
15649
|
+
const match = report.accepted[0];
|
|
15650
|
+
const spec = getProviderSpec(match.provider);
|
|
15651
|
+
if (opts.callbacks?.confirmDetection) {
|
|
15652
|
+
const ok = await opts.callbacks.confirmDetection(match.provider);
|
|
15653
|
+
if (!ok) throw new ConnectCancelled();
|
|
15654
|
+
}
|
|
15655
|
+
return finishConnect(spec, { key, models: match.models });
|
|
15656
|
+
}
|
|
15657
|
+
if (report.accepted.length > 1) {
|
|
15658
|
+
if (opts.callbacks?.chooseProvider) {
|
|
15659
|
+
const chosen = await opts.callbacks.chooseProvider(report.accepted);
|
|
15660
|
+
if (!chosen) throw new ConnectCancelled();
|
|
15661
|
+
const match = report.accepted.find((a) => a.provider === chosen);
|
|
15662
|
+
return finishConnect(getProviderSpec(chosen), { key, models: match.models });
|
|
15663
|
+
}
|
|
15664
|
+
throw new ConnectError(
|
|
15665
|
+
`Multiple providers accepted this key (${report.accepted.map((a) => a.provider).join(", ")}). Re-run with --provider <id>.`
|
|
15666
|
+
);
|
|
15667
|
+
}
|
|
15668
|
+
if (report.sawNetworkFailure) {
|
|
15669
|
+
throw new ConnectError(
|
|
15670
|
+
`Couldn't reach ${detection.candidates.map(providerLabel).join(" / ")} to identify this key. Check your connection, or force a provider with --provider <id>.`
|
|
15671
|
+
);
|
|
15672
|
+
}
|
|
15673
|
+
throw new ConnectError(
|
|
15674
|
+
`No provider accepted this key (tried ${detection.candidates.map(providerLabel).join(", ")}). If it belongs to an OpenAI-compatible endpoint, run /connect --base-url <url>.`
|
|
15675
|
+
);
|
|
15676
|
+
}
|
|
15677
|
+
async function connectCustomEndpoint(opts) {
|
|
15678
|
+
const id = opts.id.trim().toLowerCase();
|
|
15679
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(id)) {
|
|
15680
|
+
throw new ConnectError(`Invalid provider id "${opts.id}" \u2014 use letters, digits, dashes.`);
|
|
15681
|
+
}
|
|
15682
|
+
const baseUrl = opts.baseUrl.trim().replace(/\/+$/, "");
|
|
15683
|
+
if (!/^https?:\/\//.test(baseUrl)) {
|
|
15684
|
+
throw new ConnectError(`Base URL must start with http:// or https:// (got "${opts.baseUrl}").`);
|
|
15685
|
+
}
|
|
15686
|
+
const builtin = getProviderSpec(id);
|
|
15687
|
+
const spec = builtin ? { ...builtin, base_url: baseUrl } : {
|
|
15688
|
+
id,
|
|
15689
|
+
label: opts.label ?? id,
|
|
15690
|
+
api: "openai-compat",
|
|
15691
|
+
base_url: baseUrl,
|
|
15692
|
+
key_prefixes: [],
|
|
15693
|
+
shared_prefixes: [],
|
|
15694
|
+
key_config_name: `${id}-api-key`,
|
|
15695
|
+
requires_key: !!opts.key,
|
|
15696
|
+
custom: true
|
|
15697
|
+
};
|
|
15698
|
+
const result = await fetchProviderModels(spec, opts.key);
|
|
15699
|
+
if (!result.ok) {
|
|
15700
|
+
if (result.status === 0) {
|
|
15701
|
+
throw new ConnectError(`Couldn't reach ${baseUrl} \u2014 check the URL (expects an OpenAI-compatible /models endpoint).`);
|
|
15702
|
+
}
|
|
15703
|
+
if (result.status === 401 || result.status === 403) {
|
|
15704
|
+
throw new ConnectError(
|
|
15705
|
+
opts.key ? `${spec.label} rejected the key (HTTP ${result.status}).` : `${spec.label} requires an API key (HTTP ${result.status}) \u2014 re-run with a key.`
|
|
15706
|
+
);
|
|
15707
|
+
}
|
|
15708
|
+
throw new ConnectError(`${baseUrl} answered HTTP ${result.status} \u2014 is this an OpenAI-compatible endpoint?`);
|
|
15709
|
+
}
|
|
15710
|
+
if (result.models.length === 0) {
|
|
15711
|
+
throw new ConnectError(`${baseUrl} lists no models \u2014 nothing to connect.`);
|
|
15712
|
+
}
|
|
15713
|
+
saveCustomProvider({
|
|
15714
|
+
id,
|
|
15715
|
+
...opts.label ? { label: opts.label } : {},
|
|
15716
|
+
base_url: baseUrl,
|
|
15717
|
+
requires_key: !!opts.key,
|
|
15718
|
+
enabled: true
|
|
15719
|
+
});
|
|
15720
|
+
return finishConnect(getProviderSpec(id), { key: opts.key, models: result.models });
|
|
15721
|
+
}
|
|
15722
|
+
async function connectKeyless(providerId, baseUrl) {
|
|
15723
|
+
const builtin = getProviderSpec(providerId);
|
|
15724
|
+
if (!builtin) throw new ConnectError(`Unknown provider "${providerId}".`);
|
|
15725
|
+
const spec = baseUrl ? { ...builtin, base_url: baseUrl.replace(/\/+$/, "") } : builtin;
|
|
15726
|
+
const result = await fetchProviderModels(spec, void 0);
|
|
15727
|
+
if (!result.ok) {
|
|
15728
|
+
throw new ConnectError(
|
|
15729
|
+
`${spec.label} not reachable at ${spec.base_url}. Is it running? (ollama serve, then retry)`
|
|
15730
|
+
);
|
|
15731
|
+
}
|
|
15732
|
+
if (result.models.length === 0) {
|
|
15733
|
+
throw new ConnectError(`${spec.label} is running but has no models \u2014 pull one first (e.g. \`ollama pull llama3.2\`).`);
|
|
15734
|
+
}
|
|
15735
|
+
saveCustomProvider({ id: spec.id, base_url: spec.base_url, enabled: true });
|
|
15736
|
+
return finishConnect(getProviderSpec(spec.id), { models: result.models });
|
|
15737
|
+
}
|
|
15738
|
+
function describeConnectOutcome(outcome) {
|
|
15739
|
+
const lines = [];
|
|
15740
|
+
if (outcome.offline) {
|
|
15741
|
+
lines.push(`${outcome.label} key saved \u2014 provider unreachable right now, models will be discovered on first use.`);
|
|
15742
|
+
} else {
|
|
15743
|
+
lines.push(`Connected ${outcome.label} \u2014 ${outcome.modelCount} chat model${outcome.modelCount === 1 ? "" : "s"} available.`);
|
|
15744
|
+
}
|
|
15745
|
+
if (outcome.stack) {
|
|
15746
|
+
lines.push(`high ${outcome.stack.high}`);
|
|
15747
|
+
lines.push(`medium ${outcome.stack.medium}`);
|
|
15748
|
+
lines.push(`low ${outcome.stack.low}`);
|
|
15749
|
+
}
|
|
15750
|
+
if (outcome.becamePrimary) {
|
|
15751
|
+
lines.push(`Primary engine: ${outcome.provider}`);
|
|
15752
|
+
}
|
|
15753
|
+
return lines;
|
|
15754
|
+
}
|
|
15755
|
+
var ConnectError, ConnectCancelled;
|
|
15756
|
+
var init_connect = __esm({
|
|
15757
|
+
"src/services/connect.ts"() {
|
|
15758
|
+
"use strict";
|
|
15759
|
+
init_detect();
|
|
15760
|
+
init_discovery();
|
|
15761
|
+
init_providers();
|
|
15762
|
+
init_llm_config();
|
|
15763
|
+
init_store();
|
|
15764
|
+
ConnectError = class extends Error {
|
|
15765
|
+
};
|
|
15766
|
+
ConnectCancelled = class extends ConnectError {
|
|
15767
|
+
constructor() {
|
|
15768
|
+
super("Connect cancelled.");
|
|
15769
|
+
}
|
|
15770
|
+
};
|
|
15771
|
+
}
|
|
15772
|
+
});
|
|
15773
|
+
|
|
14779
15774
|
// src/commands/onboard.ts
|
|
14780
15775
|
var onboard_exports = {};
|
|
14781
15776
|
__export(onboard_exports, {
|
|
@@ -15103,42 +16098,58 @@ function printIntro() {
|
|
|
15103
16098
|
}
|
|
15104
16099
|
async function ensureLlmKeys(session) {
|
|
15105
16100
|
if (hasAnyLlmProvider()) return;
|
|
16101
|
+
const { connectWithKey: connectWithKey2, describeConnectOutcome: describeConnectOutcome2, ConnectCancelled: ConnectCancelled2 } = await Promise.resolve().then(() => (init_connect(), connect_exports));
|
|
16102
|
+
const { providerLabel: providerLabel2 } = await Promise.resolve().then(() => (init_providers(), providers_exports));
|
|
16103
|
+
const { countAvailableEngines: countAvailableEngines2 } = await Promise.resolve().then(() => (init_session_state(), session_state_exports));
|
|
15106
16104
|
console.log();
|
|
15107
16105
|
console.log(" " + chalk19.dim("Onboarding uses AI to draft your profile."));
|
|
15108
16106
|
console.log(
|
|
15109
|
-
" " + chalk19.dim("
|
|
16107
|
+
" " + chalk19.dim("Paste any provider's API key \u2014 Anthropic, OpenAI, Groq, Gemini, Mistral, ...")
|
|
15110
16108
|
);
|
|
15111
|
-
|
|
15112
|
-
|
|
15113
|
-
{ value: "openai", label: "OpenAI (GPT)", description: "Full parity on all surfaces" }
|
|
15114
|
-
]);
|
|
15115
|
-
const firstLabel = first === "anthropic" ? "Anthropic API key" : "OpenAI API key";
|
|
15116
|
-
const firstKey = await session.askSecret(firstLabel, { confirm: true });
|
|
15117
|
-
if (first === "anthropic") setConfigValue("api-key", firstKey);
|
|
15118
|
-
else setConfigValue("openai-api-key", firstKey);
|
|
15119
|
-
setConfigValue("llm-primary", first);
|
|
15120
|
-
setConfigValue("llm-tier", "high");
|
|
15121
|
-
const second = first === "anthropic" ? "openai" : "anthropic";
|
|
15122
|
-
setConfigValue("llm-failover-order", second);
|
|
15123
|
-
setConfigValue("llm-auto-failover", "off");
|
|
15124
|
-
const addSecond = await session.confirm(
|
|
15125
|
-
`Add a second engine (${second}) for switching in the REPL?`,
|
|
15126
|
-
false
|
|
16109
|
+
console.log(
|
|
16110
|
+
" " + chalk19.dim("NTRP detects the provider and discovers its models. Or run ") + paint("accent", "/connect") + chalk19.dim(" anytime.")
|
|
15127
16111
|
);
|
|
15128
|
-
|
|
15129
|
-
const
|
|
15130
|
-
const
|
|
15131
|
-
|
|
15132
|
-
|
|
16112
|
+
for (; ; ) {
|
|
16113
|
+
const key = await session.askSecret("LLM API key (any provider)", { confirm: false });
|
|
16114
|
+
const spinner = ora6({ text: "Identifying provider\u2026", discardStdin: false }).start();
|
|
16115
|
+
try {
|
|
16116
|
+
const outcome = await connectWithKey2(key, {
|
|
16117
|
+
callbacks: {
|
|
16118
|
+
confirmDetection: async (providerId) => {
|
|
16119
|
+
spinner.stop();
|
|
16120
|
+
return session.confirm(`Detected ${providerLabel2(providerId)} \u2014 connect it?`, true);
|
|
16121
|
+
},
|
|
16122
|
+
chooseProvider: async (accepted) => {
|
|
16123
|
+
spinner.stop();
|
|
16124
|
+
return session.choose(
|
|
16125
|
+
"Multiple providers accepted this key \u2014 which is it?",
|
|
16126
|
+
accepted.map((a) => ({ value: a.provider, label: providerLabel2(a.provider) }))
|
|
16127
|
+
);
|
|
16128
|
+
}
|
|
16129
|
+
}
|
|
16130
|
+
});
|
|
16131
|
+
spinner.stop();
|
|
16132
|
+
const [headline, ...rest] = describeConnectOutcome2(outcome);
|
|
16133
|
+
console.log(" " + paint("success", "\u2713") + " " + (headline ?? ""));
|
|
16134
|
+
for (const line of rest) console.log(" " + chalk19.dim(line));
|
|
16135
|
+
} catch (err) {
|
|
16136
|
+
spinner.stop();
|
|
16137
|
+
if (!(err instanceof ConnectCancelled2)) {
|
|
16138
|
+
console.log(" " + chalk19.red(String(err.message ?? err)));
|
|
16139
|
+
}
|
|
16140
|
+
const retry = await session.confirm("Try another key?", true);
|
|
16141
|
+
if (retry) continue;
|
|
16142
|
+
if (!hasAnyLlmProvider()) return;
|
|
16143
|
+
}
|
|
16144
|
+
const addAnother = await session.confirm("Add another engine? (switch anytime with /provider)", false);
|
|
16145
|
+
if (!addAnother) break;
|
|
16146
|
+
}
|
|
16147
|
+
if (countAvailableEngines2() >= 2) {
|
|
15133
16148
|
const enableFailover = await session.confirm(
|
|
15134
16149
|
"Enable auto-failover on rate limits? (off = you choose engine with /provider)",
|
|
15135
16150
|
false
|
|
15136
16151
|
);
|
|
15137
|
-
|
|
15138
|
-
} else {
|
|
15139
|
-
console.log(
|
|
15140
|
-
" " + chalk19.dim(`Single engine \u2014 add ${second} later via /config set ${second === "anthropic" ? "api-key" : "openai-api-key"}.`)
|
|
15141
|
-
);
|
|
16152
|
+
setConfigValue("llm-auto-failover", enableFailover ? "on" : "off");
|
|
15142
16153
|
}
|
|
15143
16154
|
console.log(" " + paint("success", "\u2713") + " " + chalk19.dim("LLM engines configured. Use /provider to switch."));
|
|
15144
16155
|
}
|
|
@@ -15208,7 +16219,7 @@ __export(new_exports, {
|
|
|
15208
16219
|
handler: () => handler6
|
|
15209
16220
|
});
|
|
15210
16221
|
import chalk20 from "chalk";
|
|
15211
|
-
import { existsSync as
|
|
16222
|
+
import { existsSync as existsSync16 } from "fs";
|
|
15212
16223
|
import { basename as basename4 } from "path";
|
|
15213
16224
|
async function handler6(args, ctx) {
|
|
15214
16225
|
const { positional, flags } = parseArgs(args, ["demo", "empty", "list-scenarios", "regen-taxonomy"]);
|
|
@@ -15230,7 +16241,7 @@ async function handler6(args, ctx) {
|
|
|
15230
16241
|
console.error(chalk20.red(" Usage: /new <file.csv> | --demo [--scenario <name>] | --empty [--lens health|metrics]"));
|
|
15231
16242
|
return;
|
|
15232
16243
|
}
|
|
15233
|
-
if (source.kind === "file" && !
|
|
16244
|
+
if (source.kind === "file" && !existsSync16(source.path)) {
|
|
15234
16245
|
console.error(chalk20.red(` File not found: ${source.path}`));
|
|
15235
16246
|
return;
|
|
15236
16247
|
}
|
|
@@ -15297,11 +16308,11 @@ async function handler6(args, ctx) {
|
|
|
15297
16308
|
return "New empty session";
|
|
15298
16309
|
}
|
|
15299
16310
|
if (lens === "revenue_metrics") {
|
|
15300
|
-
const
|
|
16311
|
+
const ora19 = (await import("ora")).default;
|
|
15301
16312
|
const { runMetricsAnalysis: runMetricsAnalysis2 } = await Promise.resolve().then(() => (init_metrics_analysis(), metrics_analysis_exports));
|
|
15302
16313
|
const { renderMetricsReport: renderMetricsReport2 } = await Promise.resolve().then(() => (init_metrics_report(), metrics_report_exports));
|
|
15303
16314
|
const structured = isStructuredOutput(ctx.execution);
|
|
15304
|
-
const spinner = structured ? null :
|
|
16315
|
+
const spinner = structured ? null : ora19({ text: "Computing SaaS metrics\u2026", indent: 2, discardStdin: false }).start();
|
|
15305
16316
|
let result;
|
|
15306
16317
|
try {
|
|
15307
16318
|
result = await runMetricsAnalysis2({
|
|
@@ -15507,7 +16518,7 @@ __export(session_exports, {
|
|
|
15507
16518
|
handler: () => handler8
|
|
15508
16519
|
});
|
|
15509
16520
|
import chalk22 from "chalk";
|
|
15510
|
-
import { join as
|
|
16521
|
+
import { join as join14 } from "path";
|
|
15511
16522
|
import ora7 from "ora";
|
|
15512
16523
|
async function handler8(args, ctx) {
|
|
15513
16524
|
const sub = args[0];
|
|
@@ -15596,7 +16607,7 @@ async function pickUp(idArg, ctx) {
|
|
|
15596
16607
|
}
|
|
15597
16608
|
resetContextForSwitch(ctx, {
|
|
15598
16609
|
sessionId: target.id,
|
|
15599
|
-
sessionFile:
|
|
16610
|
+
sessionFile: join14(getSessionsDir(), `${target.id}.json`),
|
|
15600
16611
|
sessionName: session.name,
|
|
15601
16612
|
messages: [...session.messages],
|
|
15602
16613
|
conversation: session.thread ? [...session.thread] : [],
|
|
@@ -15908,7 +16919,7 @@ __export(report_exports, {
|
|
|
15908
16919
|
handler: () => handler9
|
|
15909
16920
|
});
|
|
15910
16921
|
import chalk23 from "chalk";
|
|
15911
|
-
import { writeFileSync as
|
|
16922
|
+
import { writeFileSync as writeFileSync10 } from "fs";
|
|
15912
16923
|
import { dirname as dirname2 } from "path";
|
|
15913
16924
|
async function handler9(args, ctx) {
|
|
15914
16925
|
const { flags } = parseArgs(args);
|
|
@@ -16004,7 +17015,7 @@ async function handler9(args, ctx) {
|
|
|
16004
17015
|
if (!isInsideNtrp(resolvedOutput)) {
|
|
16005
17016
|
console.warn(chalk23.yellow(` Warning: writing report outside ~/.ntrp (${dirname2(resolvedOutput)})`));
|
|
16006
17017
|
}
|
|
16007
|
-
|
|
17018
|
+
writeFileSync10(resolvedOutput, rendered);
|
|
16008
17019
|
console.log(chalk23.green(` Report written to ${resolvedOutput}`));
|
|
16009
17020
|
} else if (rendered) {
|
|
16010
17021
|
console.log(rendered);
|
|
@@ -16035,8 +17046,8 @@ var init_report2 = __esm({
|
|
|
16035
17046
|
});
|
|
16036
17047
|
|
|
16037
17048
|
// src/output/notes-export.ts
|
|
16038
|
-
import { writeFileSync as
|
|
16039
|
-
import { join as
|
|
17049
|
+
import { writeFileSync as writeFileSync11 } from "fs";
|
|
17050
|
+
import { join as join15 } from "path";
|
|
16040
17051
|
function exportToNotes(data) {
|
|
16041
17052
|
const { computeResult, divergences, findings, exchanges } = data;
|
|
16042
17053
|
const { aggregate, segments } = computeResult;
|
|
@@ -16045,7 +17056,7 @@ function exportToNotes(data) {
|
|
|
16045
17056
|
const timeStr = formatTime(now2);
|
|
16046
17057
|
const filename = `${dateStr}-${timeStr}-gtm-health.md`;
|
|
16047
17058
|
const dir = getExportsDir();
|
|
16048
|
-
const filepath =
|
|
17059
|
+
const filepath = join15(dir, filename);
|
|
16049
17060
|
const severityTags = /* @__PURE__ */ new Set();
|
|
16050
17061
|
for (const f of findings) severityTags.add(f.severity);
|
|
16051
17062
|
const tags = ["ntrp", "gtm-health", ...severityTags];
|
|
@@ -16134,7 +17145,7 @@ function exportToNotes(data) {
|
|
|
16134
17145
|
}
|
|
16135
17146
|
}
|
|
16136
17147
|
const content = frontmatter.join("\n") + "\n\n" + body.join("\n") + "\n";
|
|
16137
|
-
|
|
17148
|
+
writeFileSync11(filepath, content);
|
|
16138
17149
|
return filepath;
|
|
16139
17150
|
}
|
|
16140
17151
|
function formatDate(d) {
|
|
@@ -16274,8 +17285,8 @@ __export(backmeup_exports, {
|
|
|
16274
17285
|
});
|
|
16275
17286
|
import chalk25 from "chalk";
|
|
16276
17287
|
import Papa5 from "papaparse";
|
|
16277
|
-
import { mkdirSync as mkdirSync9, writeFileSync as
|
|
16278
|
-
import { join as
|
|
17288
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync12 } from "fs";
|
|
17289
|
+
import { join as join16 } from "path";
|
|
16279
17290
|
function sanitizeCsvValue(value) {
|
|
16280
17291
|
if (typeof value !== "string") return value;
|
|
16281
17292
|
return CSV_FORMULA_RE.test(value) ? `'${value}` : value;
|
|
@@ -16311,7 +17322,7 @@ async function handler11(args, _ctx) {
|
|
|
16311
17322
|
if (!isInsideNtrp(baseDir)) {
|
|
16312
17323
|
console.warn(chalk25.yellow(` Warning: writing backup outside ~/.ntrp (${baseDir})`));
|
|
16313
17324
|
}
|
|
16314
|
-
const folder =
|
|
17325
|
+
const folder = join16(baseDir, folderName);
|
|
16315
17326
|
mkdirSync9(folder, { recursive: true });
|
|
16316
17327
|
const generatedAt = now2.toISOString();
|
|
16317
17328
|
let fileCount = 0;
|
|
@@ -16326,7 +17337,7 @@ async function handler11(args, _ctx) {
|
|
|
16326
17337
|
"Total At Risk": health.total_value_at_risk != null ? formatCurrency(health.total_value_at_risk) : "N/A",
|
|
16327
17338
|
"Generated At": generatedAt
|
|
16328
17339
|
}));
|
|
16329
|
-
|
|
17340
|
+
writeFileSync12(join16(folder, "cover-sheet.csv"), Papa5.unparse(sanitizeCsvRows(coverRows)), "utf-8");
|
|
16330
17341
|
fileCount++;
|
|
16331
17342
|
if (findings.length > 0) {
|
|
16332
17343
|
const findingsRows = findings.map((f) => ({
|
|
@@ -16336,7 +17347,7 @@ async function handler11(args, _ctx) {
|
|
|
16336
17347
|
Finding: f.finding,
|
|
16337
17348
|
"Recommended Plays": f.recommended_plays ? f.recommended_plays.map((p) => p.play_name).join("; ") : ""
|
|
16338
17349
|
}));
|
|
16339
|
-
|
|
17350
|
+
writeFileSync12(join16(folder, "findings.csv"), Papa5.unparse(sanitizeCsvRows(findingsRows)), "utf-8");
|
|
16340
17351
|
fileCount++;
|
|
16341
17352
|
}
|
|
16342
17353
|
for (const vs of health.vital_signs) {
|
|
@@ -16346,7 +17357,7 @@ async function handler11(args, _ctx) {
|
|
|
16346
17357
|
...detail
|
|
16347
17358
|
}));
|
|
16348
17359
|
const filename = EVIDENCE_FILENAMES[vs.vital_sign] ?? `${vs.vital_sign}.csv`;
|
|
16349
|
-
|
|
17360
|
+
writeFileSync12(join16(folder, filename), Papa5.unparse(sanitizeCsvRows(rows)), "utf-8");
|
|
16350
17361
|
fileCount++;
|
|
16351
17362
|
}
|
|
16352
17363
|
console.log(chalk25.green(`
|
|
@@ -16540,8 +17551,8 @@ var init_bundle = __esm({
|
|
|
16540
17551
|
});
|
|
16541
17552
|
|
|
16542
17553
|
// src/repositories/markdown.ts
|
|
16543
|
-
import { mkdirSync as mkdirSync10, writeFileSync as
|
|
16544
|
-
import { basename as basename5, dirname as dirname3, join as
|
|
17554
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync13 } from "fs";
|
|
17555
|
+
import { basename as basename5, dirname as dirname3, join as join17, resolve as resolve6 } from "path";
|
|
16545
17556
|
import { stringify as stringifyYaml } from "yaml";
|
|
16546
17557
|
function renderMarkdownFiles(pkg) {
|
|
16547
17558
|
const bundleJson = JSON.stringify(pkg, null, 2) + "\n";
|
|
@@ -16764,9 +17775,9 @@ var init_markdown3 = __esm({
|
|
|
16764
17775
|
mkdirSync10(root, { recursive: true });
|
|
16765
17776
|
const written = [];
|
|
16766
17777
|
for (const file of files) {
|
|
16767
|
-
const absolutePath =
|
|
17778
|
+
const absolutePath = join17(root, file.relativePath);
|
|
16768
17779
|
mkdirSync10(dirname3(absolutePath), { recursive: true });
|
|
16769
|
-
|
|
17780
|
+
writeFileSync13(absolutePath, file.contents, "utf-8");
|
|
16770
17781
|
written.push(absolutePath);
|
|
16771
17782
|
}
|
|
16772
17783
|
return {
|
|
@@ -17070,8 +18081,8 @@ __export(handoff_exports, {
|
|
|
17070
18081
|
handler: () => handler13
|
|
17071
18082
|
});
|
|
17072
18083
|
import chalk27 from "chalk";
|
|
17073
|
-
import { writeFileSync as
|
|
17074
|
-
import { join as
|
|
18084
|
+
import { writeFileSync as writeFileSync14 } from "fs";
|
|
18085
|
+
import { join as join18 } from "path";
|
|
17075
18086
|
async function handler13(args, ctx) {
|
|
17076
18087
|
const sub = args[0];
|
|
17077
18088
|
if (!sub) {
|
|
@@ -17129,7 +18140,7 @@ async function interactiveMenu(ctx) {
|
|
|
17129
18140
|
}
|
|
17130
18141
|
}
|
|
17131
18142
|
async function runReport(args, ctx) {
|
|
17132
|
-
const out =
|
|
18143
|
+
const out = join18(getExportsDir(), `report-${stamp()}.md`);
|
|
17133
18144
|
const { handler: report } = await Promise.resolve().then(() => (init_report2(), report_exports));
|
|
17134
18145
|
await report(["--format", "md", "--output", out, ...args], ctx);
|
|
17135
18146
|
recordDeliverable(ctx, { kind: "report", at: (/* @__PURE__ */ new Date()).toISOString(), path: out });
|
|
@@ -17177,8 +18188,8 @@ async function runPrompt(target, ctx) {
|
|
|
17177
18188
|
return;
|
|
17178
18189
|
}
|
|
17179
18190
|
const prompt = draft.markdown;
|
|
17180
|
-
const out =
|
|
17181
|
-
|
|
18191
|
+
const out = join18(getExportsDir(), `handoff-${target}-${stamp()}.md`);
|
|
18192
|
+
writeFileSync14(out, prompt, "utf-8");
|
|
17182
18193
|
recordDeliverable(ctx, { kind: `prompt:${target}`, at: (/* @__PURE__ */ new Date()).toISOString(), path: out });
|
|
17183
18194
|
console.log();
|
|
17184
18195
|
console.log(" " + paint("accent", `Agent prompt ready (${target})`));
|
|
@@ -18050,24 +19061,24 @@ JSON SHAPE:
|
|
|
18050
19061
|
|
|
18051
19062
|
// src/strategies/readers.ts
|
|
18052
19063
|
import { createHash } from "crypto";
|
|
18053
|
-
import { existsSync as
|
|
19064
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
|
|
18054
19065
|
import { extname, resolve as resolve7 } from "path";
|
|
18055
19066
|
import { parse as parseYaml } from "yaml";
|
|
18056
19067
|
import { PDFParse } from "pdf-parse";
|
|
18057
19068
|
async function readStrategyFile(pathOrDash) {
|
|
18058
19069
|
if (pathOrDash === "-") {
|
|
18059
|
-
const text2 =
|
|
19070
|
+
const text2 = readFileSync13(0, "utf-8");
|
|
18060
19071
|
return createDocument("stdin", null, text2, {});
|
|
18061
19072
|
}
|
|
18062
19073
|
const sourcePath = resolve7(pathOrDash);
|
|
18063
|
-
if (!
|
|
19074
|
+
if (!existsSync17(sourcePath)) {
|
|
18064
19075
|
throw new NtrpError("strategy_file_not_found", `Strategy file not found: ${pathOrDash}`, 2 /* Usage */);
|
|
18065
19076
|
}
|
|
18066
19077
|
const ext = extname(sourcePath).toLowerCase();
|
|
18067
19078
|
if (ext === ".pdf") {
|
|
18068
19079
|
return readPdf(sourcePath);
|
|
18069
19080
|
}
|
|
18070
|
-
const text =
|
|
19081
|
+
const text = readFileSync13(sourcePath, "utf-8");
|
|
18071
19082
|
if (ext === ".yaml" || ext === ".yml") {
|
|
18072
19083
|
const structured = parseStructuredYaml(text);
|
|
18073
19084
|
return createDocument("yaml", sourcePath, text, structured);
|
|
@@ -18082,7 +19093,7 @@ function readStrategyText(text) {
|
|
|
18082
19093
|
return createDocument("text", null, text, {});
|
|
18083
19094
|
}
|
|
18084
19095
|
async function readPdf(sourcePath) {
|
|
18085
|
-
const data =
|
|
19096
|
+
const data = readFileSync13(sourcePath);
|
|
18086
19097
|
const parser = new PDFParse({ data });
|
|
18087
19098
|
try {
|
|
18088
19099
|
const result = await parser.getText();
|
|
@@ -18129,15 +19140,15 @@ var init_readers = __esm({
|
|
|
18129
19140
|
});
|
|
18130
19141
|
|
|
18131
19142
|
// src/strategies/library.ts
|
|
18132
|
-
import { writeFileSync as
|
|
18133
|
-
import { join as
|
|
19143
|
+
import { writeFileSync as writeFileSync15 } from "fs";
|
|
19144
|
+
import { join as join19 } from "path";
|
|
18134
19145
|
import { stringify as stringifyYaml2 } from "yaml";
|
|
18135
19146
|
function strategyLibraryPath(slug) {
|
|
18136
|
-
return
|
|
19147
|
+
return join19(getStrategiesDir(), `${slug}.md`);
|
|
18137
19148
|
}
|
|
18138
19149
|
function writeStrategyMarkdown(strategy) {
|
|
18139
19150
|
const path = strategyLibraryPath(strategy.slug);
|
|
18140
|
-
|
|
19151
|
+
writeFileSync15(path, renderStrategyMarkdown(strategy), "utf-8");
|
|
18141
19152
|
return path;
|
|
18142
19153
|
}
|
|
18143
19154
|
function renderStrategyMarkdown(strategy) {
|
|
@@ -18211,7 +19222,7 @@ var init_library = __esm({
|
|
|
18211
19222
|
// src/strategies/connectors.ts
|
|
18212
19223
|
import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
18213
19224
|
import { homedir as homedir7 } from "os";
|
|
18214
|
-
import { basename as basename6, extname as extname2, join as
|
|
19225
|
+
import { basename as basename6, extname as extname2, join as join20, relative, resolve as resolve8, sep as sep3 } from "path";
|
|
18215
19226
|
function createLocalFolderConnector(options) {
|
|
18216
19227
|
const rootPath = resolveUserPath2(options.rootPath);
|
|
18217
19228
|
const name = options.name ?? (basename6(rootPath) || "local");
|
|
@@ -18254,7 +19265,7 @@ function createLocalFolderConnector(options) {
|
|
|
18254
19265
|
}
|
|
18255
19266
|
function walkLocalFolder(rootPath, currentPath, refs, opts) {
|
|
18256
19267
|
for (const entry of readdirSync2(currentPath, { withFileTypes: true })) {
|
|
18257
|
-
const absolutePath =
|
|
19268
|
+
const absolutePath = join20(currentPath, entry.name);
|
|
18258
19269
|
const relativePath = normalizePath(relative(rootPath, absolutePath));
|
|
18259
19270
|
if (entry.isDirectory()) {
|
|
18260
19271
|
if (shouldSkipDirectory(entry.name) || matchesAny(relativePath, opts.excludePatterns)) continue;
|
|
@@ -18318,7 +19329,7 @@ function normalizePath(path) {
|
|
|
18318
19329
|
}
|
|
18319
19330
|
function resolveUserPath2(path) {
|
|
18320
19331
|
if (path === "~") return homedir7();
|
|
18321
|
-
if (path.startsWith("~/")) return
|
|
19332
|
+
if (path.startsWith("~/")) return join20(homedir7(), path.slice(2));
|
|
18322
19333
|
return resolve8(path);
|
|
18323
19334
|
}
|
|
18324
19335
|
var DEFAULT_MAX_FILES, DEFAULT_MAX_BYTES, SUPPORTED_EXTENSIONS, DEFAULT_EXCLUDED_DIRS;
|
|
@@ -19442,18 +20453,25 @@ __export(config_exports, {
|
|
|
19442
20453
|
handler: () => handler23
|
|
19443
20454
|
});
|
|
19444
20455
|
import chalk38 from "chalk";
|
|
20456
|
+
import ora10 from "ora";
|
|
20457
|
+
function secretKeys() {
|
|
20458
|
+
const keys = /* @__PURE__ */ new Set(["license-key", "license-instance-id", "voyage-api-key", "tavily-api-key", "brave-api-key"]);
|
|
20459
|
+
for (const spec of listProviderSpecs()) keys.add(spec.key_config_name);
|
|
20460
|
+
return keys;
|
|
20461
|
+
}
|
|
19445
20462
|
function display(key, value) {
|
|
19446
|
-
return
|
|
20463
|
+
return secretKeys().has(key) ? String(value).slice(0, 10) + "..." : String(value);
|
|
19447
20464
|
}
|
|
19448
20465
|
function secretPromptLabel(key) {
|
|
19449
|
-
|
|
19450
|
-
if (
|
|
20466
|
+
const spec = findSpecByConfigKey(key);
|
|
20467
|
+
if (spec) return `${spec.label} API key`;
|
|
19451
20468
|
if (key === "license-key") return "License key";
|
|
19452
20469
|
return key;
|
|
19453
20470
|
}
|
|
19454
20471
|
function usage() {
|
|
19455
20472
|
console.log(chalk38.dim(" Usage: /config <get|set|list|delete> [key] [value]"));
|
|
19456
20473
|
console.log(chalk38.dim(" Tip: ") + paint("accent", "/config set api-key") + chalk38.dim(" opens a hidden prompt (no inline paste)."));
|
|
20474
|
+
console.log(chalk38.dim(" Tip: ") + paint("accent", "/connect") + chalk38.dim(" auto-detects the provider from any pasted key."));
|
|
19457
20475
|
}
|
|
19458
20476
|
function fail(message, ctx) {
|
|
19459
20477
|
console.error(chalk38.red(` ${message}`));
|
|
@@ -19485,7 +20503,7 @@ async function handler23(args, ctx) {
|
|
|
19485
20503
|
return;
|
|
19486
20504
|
}
|
|
19487
20505
|
let value = inlineValue;
|
|
19488
|
-
if (!value &&
|
|
20506
|
+
if (!value && secretKeys().has(key)) {
|
|
19489
20507
|
try {
|
|
19490
20508
|
value = await promptSecretValue(key, ctx);
|
|
19491
20509
|
} catch (err) {
|
|
@@ -19505,8 +20523,24 @@ async function handler23(args, ctx) {
|
|
|
19505
20523
|
}
|
|
19506
20524
|
console.log();
|
|
19507
20525
|
console.log(chalk38.green(` \u2713 ${key} saved`) + chalk38.dim(` (${display(key, value)})`));
|
|
19508
|
-
|
|
19509
|
-
|
|
20526
|
+
const spec = findSpecByConfigKey(key);
|
|
20527
|
+
if (spec) {
|
|
20528
|
+
const spinner = ora10({ text: `Discovering ${spec.label} models\u2026`, discardStdin: false }).start();
|
|
20529
|
+
try {
|
|
20530
|
+
const { refreshProviderModels: refreshProviderModels2 } = await Promise.resolve().then(() => (init_discovery(), discovery_exports));
|
|
20531
|
+
const entry = await refreshProviderModels2(spec.id, { apiKey: value, force: true });
|
|
20532
|
+
if (entry) {
|
|
20533
|
+
spinner.succeed(`${spec.label}: ${entry.models.length} chat models available.`);
|
|
20534
|
+
console.log(
|
|
20535
|
+
" " + chalk38.dim(`high ${entry.tier_stack.high} \xB7 medium ${entry.tier_stack.medium} \xB7 low ${entry.tier_stack.low}`)
|
|
20536
|
+
);
|
|
20537
|
+
} else {
|
|
20538
|
+
spinner.warn(`${spec.label} unreachable \u2014 models will be discovered on first use.`);
|
|
20539
|
+
}
|
|
20540
|
+
} catch {
|
|
20541
|
+
spinner.warn(`${spec.label} unreachable \u2014 models will be discovered on first use.`);
|
|
20542
|
+
}
|
|
20543
|
+
console.log(" " + chalk38.dim("Switch engines with /provider \xB7 browse models with /model list."));
|
|
19510
20544
|
}
|
|
19511
20545
|
console.log();
|
|
19512
20546
|
return;
|
|
@@ -19549,15 +20583,14 @@ async function handler23(args, ctx) {
|
|
|
19549
20583
|
}
|
|
19550
20584
|
}
|
|
19551
20585
|
}
|
|
19552
|
-
var SECRET_KEYS;
|
|
19553
20586
|
var init_config = __esm({
|
|
19554
20587
|
"src/commands/config.ts"() {
|
|
19555
20588
|
"use strict";
|
|
19556
20589
|
init_store();
|
|
20590
|
+
init_providers();
|
|
19557
20591
|
init_argparse();
|
|
19558
20592
|
init_prompts();
|
|
19559
20593
|
init_theme();
|
|
19560
|
-
SECRET_KEYS = /* @__PURE__ */ new Set(["api-key", "openai-api-key", "license-key", "license-instance-id"]);
|
|
19561
20594
|
}
|
|
19562
20595
|
});
|
|
19563
20596
|
|
|
@@ -20246,15 +21279,15 @@ var init_checkout = __esm({
|
|
|
20246
21279
|
});
|
|
20247
21280
|
|
|
20248
21281
|
// src/services/setup.ts
|
|
20249
|
-
import { existsSync as
|
|
20250
|
-
import { join as
|
|
21282
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync16 } from "fs";
|
|
21283
|
+
import { join as join21 } from "path";
|
|
20251
21284
|
function setupCheck() {
|
|
20252
21285
|
const home = ntrpHome();
|
|
20253
21286
|
let writable = false;
|
|
20254
21287
|
try {
|
|
20255
21288
|
mkdirSync11(home, { recursive: true });
|
|
20256
|
-
const probe =
|
|
20257
|
-
|
|
21289
|
+
const probe = join21(home, ".write-check");
|
|
21290
|
+
writeFileSync16(probe, "ok\n");
|
|
20258
21291
|
writable = true;
|
|
20259
21292
|
} catch {
|
|
20260
21293
|
writable = false;
|
|
@@ -20279,7 +21312,8 @@ function setupCheck() {
|
|
|
20279
21312
|
tier: llmCfg.tier,
|
|
20280
21313
|
auto_failover: llmCfg.autoFailover,
|
|
20281
21314
|
anthropic: llmReady.anthropic,
|
|
20282
|
-
openai: llmReady.openai
|
|
21315
|
+
openai: llmReady.openai,
|
|
21316
|
+
providers: llmReady.providers
|
|
20283
21317
|
}
|
|
20284
21318
|
},
|
|
20285
21319
|
license: {
|
|
@@ -20291,7 +21325,7 @@ function setupCheck() {
|
|
|
20291
21325
|
};
|
|
20292
21326
|
}
|
|
20293
21327
|
function readProfileInput(pathOrDash) {
|
|
20294
|
-
const raw = pathOrDash === "-" ?
|
|
21328
|
+
const raw = pathOrDash === "-" ? readFileSync14(0, "utf-8") : readFileSync14(pathOrDash, "utf-8");
|
|
20295
21329
|
return JSON.parse(raw);
|
|
20296
21330
|
}
|
|
20297
21331
|
function writeAgentProfile(input) {
|
|
@@ -20316,11 +21350,15 @@ function writeAgentProfile(input) {
|
|
|
20316
21350
|
saveProfile(profile);
|
|
20317
21351
|
return profile;
|
|
20318
21352
|
}
|
|
20319
|
-
function applyAgentConfig(opts) {
|
|
21353
|
+
async function applyAgentConfig(opts) {
|
|
20320
21354
|
if (opts.defaultFormat) setConfigValue("default-format", opts.defaultFormat);
|
|
20321
21355
|
if (opts.apiKey) setConfigValue("api-key", opts.apiKey);
|
|
20322
21356
|
if (opts.openaiApiKey) setConfigValue("openai-api-key", opts.openaiApiKey);
|
|
20323
|
-
if (opts.
|
|
21357
|
+
if (opts.llmKey) {
|
|
21358
|
+
const { connectWithKey: connectWithKey2 } = await Promise.resolve().then(() => (init_connect(), connect_exports));
|
|
21359
|
+
await connectWithKey2(opts.llmKey, { providerId: opts.llmProvider });
|
|
21360
|
+
}
|
|
21361
|
+
if (opts.llmPrimary && getProviderSpec(opts.llmPrimary)) {
|
|
20324
21362
|
setConfigValue("llm-primary", opts.llmPrimary);
|
|
20325
21363
|
}
|
|
20326
21364
|
if (opts.licenseKey) setConfigValue("license-key", opts.licenseKey);
|
|
@@ -20330,6 +21368,7 @@ var init_setup = __esm({
|
|
|
20330
21368
|
"src/services/setup.ts"() {
|
|
20331
21369
|
"use strict";
|
|
20332
21370
|
init_repl_api();
|
|
21371
|
+
init_providers();
|
|
20333
21372
|
init_llm_config();
|
|
20334
21373
|
init_store();
|
|
20335
21374
|
init_profile();
|
|
@@ -20372,13 +21411,12 @@ async function handler27(args, ctx) {
|
|
|
20372
21411
|
console.log(` Writable: ${result.writable ? "yes" : "no"}`);
|
|
20373
21412
|
console.log(` Profile: ${result.profile.exists ? "ready" : "missing"} (${result.profile.path})`);
|
|
20374
21413
|
const llm = result.config.llm;
|
|
20375
|
-
if (llm) {
|
|
20376
|
-
|
|
20377
|
-
console.log(`
|
|
20378
|
-
console.log(` Anthropic: ${llm.anthropic ? "set" : "missing"} \xB7 OpenAI: ${llm.openai ? "set" : "missing"}`);
|
|
21414
|
+
if (llm && llm.providers.length > 0) {
|
|
21415
|
+
console.log(` Engines: ${llm.providers.length} \xB7 default ${llm.primary} \xB7 tier ${llm.tier}`);
|
|
21416
|
+
console.log(` Connected: ${llm.providers.join(", ")}`);
|
|
20379
21417
|
console.log(` Auto-failover: ${llm.auto_failover ? "on" : "off"}`);
|
|
20380
21418
|
} else {
|
|
20381
|
-
console.log(" Engines: missing");
|
|
21419
|
+
console.log(" Engines: missing \u2014 run /connect with any provider key");
|
|
20382
21420
|
}
|
|
20383
21421
|
console.log(` License: ${formatLicenseSetupLine(result.license)}`);
|
|
20384
21422
|
console.log();
|
|
@@ -20399,10 +21437,12 @@ async function handler27(args, ctx) {
|
|
|
20399
21437
|
sales_motion: getString(flags, "sales-motion")
|
|
20400
21438
|
};
|
|
20401
21439
|
}
|
|
20402
|
-
applyAgentConfig({
|
|
21440
|
+
await applyAgentConfig({
|
|
20403
21441
|
defaultFormat: getString(flags, "default-format"),
|
|
20404
21442
|
apiKey: getString(flags, "api-key"),
|
|
20405
21443
|
openaiApiKey: getString(flags, "openai-api-key"),
|
|
21444
|
+
llmKey: getString(flags, "llm-key"),
|
|
21445
|
+
llmProvider: getString(flags, "llm-provider"),
|
|
20406
21446
|
llmPrimary: getString(flags, "llm-primary"),
|
|
20407
21447
|
licenseKey: getString(flags, "license-key"),
|
|
20408
21448
|
exportDir: getString(flags, "export-dir")
|
|
@@ -20442,13 +21482,13 @@ var init_setup2 = __esm({
|
|
|
20442
21482
|
|
|
20443
21483
|
// src/conversation/orchestrator.ts
|
|
20444
21484
|
import chalk43 from "chalk";
|
|
20445
|
-
import { writeFileSync as
|
|
20446
|
-
import { join as
|
|
21485
|
+
import { writeFileSync as writeFileSync17 } from "fs";
|
|
21486
|
+
import { join as join22 } from "path";
|
|
20447
21487
|
async function handleExploreWithoutKey(ctx) {
|
|
20448
21488
|
console.log();
|
|
20449
21489
|
console.log(" " + chalk43.red("AI interpretation needs an LLM API key saved in config."));
|
|
20450
21490
|
console.log(
|
|
20451
|
-
" " + chalk43.dim("
|
|
21491
|
+
" " + chalk43.dim("Run ") + paint("accent", "/connect") + chalk43.dim(" and paste any provider's key (Anthropic, OpenAI, Groq, Gemini, ...).")
|
|
20452
21492
|
);
|
|
20453
21493
|
console.log(" " + chalk43.dim("Number crunching works without a key \u2014 only Q&A in the REPL needs one."));
|
|
20454
21494
|
if (ctx.gapAudit) {
|
|
@@ -20620,8 +21660,8 @@ async function callProvider(texts) {
|
|
|
20620
21660
|
async function embedText(text) {
|
|
20621
21661
|
const key = text.trim();
|
|
20622
21662
|
if (!key) return null;
|
|
20623
|
-
const
|
|
20624
|
-
if (
|
|
21663
|
+
const cached2 = cache.get(key);
|
|
21664
|
+
if (cached2) return cached2;
|
|
20625
21665
|
const result = await callProvider([key]);
|
|
20626
21666
|
const vec = result?.[0] ?? null;
|
|
20627
21667
|
if (vec) cache.set(key, vec);
|
|
@@ -20631,8 +21671,8 @@ async function embedItems(items) {
|
|
|
20631
21671
|
const needing = [];
|
|
20632
21672
|
const out = items.map((it, index) => {
|
|
20633
21673
|
if (it.embedding && it.embedding.length > 0) return { ...it };
|
|
20634
|
-
const
|
|
20635
|
-
if (
|
|
21674
|
+
const cached2 = cache.get(it.text.trim());
|
|
21675
|
+
if (cached2) return { ...it, embedding: cached2 };
|
|
20636
21676
|
needing.push({ index, text: it.text });
|
|
20637
21677
|
return { ...it };
|
|
20638
21678
|
});
|
|
@@ -20791,17 +21831,17 @@ var init_retrieval = __esm({
|
|
|
20791
21831
|
});
|
|
20792
21832
|
|
|
20793
21833
|
// src/memory/knowledge.ts
|
|
20794
|
-
import { existsSync as
|
|
20795
|
-
import { join as
|
|
21834
|
+
import { existsSync as existsSync19, readFileSync as readFileSync15, appendFileSync as appendFileSync3, readdirSync as readdirSync3 } from "fs";
|
|
21835
|
+
import { join as join23 } from "path";
|
|
20796
21836
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
20797
21837
|
function knowledgePath() {
|
|
20798
|
-
return
|
|
21838
|
+
return join23(getMemoryDir(), KNOWLEDGE_FILE);
|
|
20799
21839
|
}
|
|
20800
21840
|
function loadKnowledgeChunks() {
|
|
20801
21841
|
const path = knowledgePath();
|
|
20802
|
-
if (!
|
|
21842
|
+
if (!existsSync19(path)) return [];
|
|
20803
21843
|
const out = [];
|
|
20804
|
-
for (const line of
|
|
21844
|
+
for (const line of readFileSync15(path, "utf-8").split("\n")) {
|
|
20805
21845
|
const trimmed = line.trim();
|
|
20806
21846
|
if (!trimmed) continue;
|
|
20807
21847
|
try {
|
|
@@ -20902,17 +21942,17 @@ __export(store_exports2, {
|
|
|
20902
21942
|
rewriteJsonl: () => rewriteJsonl,
|
|
20903
21943
|
scrubText: () => scrubText
|
|
20904
21944
|
});
|
|
20905
|
-
import { existsSync as
|
|
20906
|
-
import { join as
|
|
21945
|
+
import { existsSync as existsSync20, readFileSync as readFileSync16, appendFileSync as appendFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync18 } from "fs";
|
|
21946
|
+
import { join as join24 } from "path";
|
|
20907
21947
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
20908
21948
|
function memPath(file) {
|
|
20909
|
-
return
|
|
21949
|
+
return join24(getMemoryDir(), file);
|
|
20910
21950
|
}
|
|
20911
21951
|
function readJsonl(file) {
|
|
20912
21952
|
const path = memPath(file);
|
|
20913
|
-
if (!
|
|
21953
|
+
if (!existsSync20(path)) return [];
|
|
20914
21954
|
const out = [];
|
|
20915
|
-
for (const line of
|
|
21955
|
+
for (const line of readFileSync16(path, "utf-8").split("\n")) {
|
|
20916
21956
|
const trimmed = line.trim();
|
|
20917
21957
|
if (!trimmed) continue;
|
|
20918
21958
|
try {
|
|
@@ -20930,7 +21970,7 @@ function appendJsonl(file, obj) {
|
|
|
20930
21970
|
}
|
|
20931
21971
|
function rewriteJsonl(file, rows) {
|
|
20932
21972
|
try {
|
|
20933
|
-
|
|
21973
|
+
writeFileSync18(memPath(file), rows.map((r) => JSON.stringify(r)).join("\n") + (rows.length ? "\n" : ""));
|
|
20934
21974
|
} catch {
|
|
20935
21975
|
}
|
|
20936
21976
|
}
|
|
@@ -20992,7 +22032,7 @@ function loadWinSnippets() {
|
|
|
20992
22032
|
const out = [];
|
|
20993
22033
|
for (const name of readdirSync4(dir)) {
|
|
20994
22034
|
if (!name.endsWith(".md") || name.toLowerCase() === "readme.md") continue;
|
|
20995
|
-
const raw =
|
|
22035
|
+
const raw = readFileSync16(join24(dir, name), "utf-8");
|
|
20996
22036
|
const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? name.replace(/\.md$/, "");
|
|
20997
22037
|
const body = raw.replace(/^#.*$/m, "").replace(/\s+/g, " ").trim().slice(0, 300);
|
|
20998
22038
|
out.push({ id: `win:${name}`, title, text: `${title}. ${body}` });
|
|
@@ -21081,7 +22121,7 @@ var init_store2 = __esm({
|
|
|
21081
22121
|
});
|
|
21082
22122
|
|
|
21083
22123
|
// src/services/smoke-protocol.ts
|
|
21084
|
-
import { join as
|
|
22124
|
+
import { join as join25 } from "path";
|
|
21085
22125
|
function isSmokeProtocolTrigger(input) {
|
|
21086
22126
|
return normalize(input).includes(SMOKE_TRIGGER_PHRASE);
|
|
21087
22127
|
}
|
|
@@ -21117,7 +22157,7 @@ async function runSmokeProtocol(_input, ctx) {
|
|
|
21117
22157
|
});
|
|
21118
22158
|
const proposalResult = await proposeRepositoryExport({
|
|
21119
22159
|
target: "markdown",
|
|
21120
|
-
directory:
|
|
22160
|
+
directory: join25(getExportsDir(), "repository-smoke"),
|
|
21121
22161
|
source: "smoke_protocol",
|
|
21122
22162
|
modelOrFixture: "smoke-protocol-v1"
|
|
21123
22163
|
});
|
|
@@ -21210,13 +22250,13 @@ var init_smoke_protocol = __esm({
|
|
|
21210
22250
|
});
|
|
21211
22251
|
|
|
21212
22252
|
// src/cli/nl.ts
|
|
21213
|
-
import
|
|
22253
|
+
import ora11 from "ora";
|
|
21214
22254
|
import chalk44 from "chalk";
|
|
21215
22255
|
async function runNaturalLanguage(input, ctx) {
|
|
21216
22256
|
if (isSmokeProtocolTrigger(input)) {
|
|
21217
22257
|
recordMessage(ctx, "user", input);
|
|
21218
22258
|
console.log();
|
|
21219
|
-
const spinner2 =
|
|
22259
|
+
const spinner2 = ora11({ text: "Running smoke protocol\u2026", color: "cyan", discardStdin: false }).start();
|
|
21220
22260
|
try {
|
|
21221
22261
|
const result = await runSmokeProtocol(input, ctx);
|
|
21222
22262
|
spinner2.succeed("Smoke protocol complete");
|
|
@@ -21243,7 +22283,7 @@ async function runNaturalLanguage(input, ctx) {
|
|
|
21243
22283
|
let snapshot = ctx.snapshot.computeResult;
|
|
21244
22284
|
if (!snapshot) {
|
|
21245
22285
|
const metricsFirst = prefersMetricsFirstContext(ctx);
|
|
21246
|
-
const spinner2 =
|
|
22286
|
+
const spinner2 = ora11({
|
|
21247
22287
|
text: metricsFirst ? "Loading session context\u2026" : "Computing vital signs\u2026",
|
|
21248
22288
|
color: "cyan",
|
|
21249
22289
|
discardStdin: false
|
|
@@ -21268,7 +22308,7 @@ async function runNaturalLanguage(input, ctx) {
|
|
|
21268
22308
|
}
|
|
21269
22309
|
console.log();
|
|
21270
22310
|
const memoryBlock = await buildMemoryBlock(input).catch(() => "");
|
|
21271
|
-
const spinner =
|
|
22311
|
+
const spinner = ora11({ text: "Thinking\u2026", color: "cyan", discardStdin: false }).start();
|
|
21272
22312
|
let lastAnswer = "";
|
|
21273
22313
|
let rawHistory = [];
|
|
21274
22314
|
const toolsUsed = [];
|
|
@@ -21559,7 +22599,7 @@ __export(metrics_exports, {
|
|
|
21559
22599
|
handler: () => handler29
|
|
21560
22600
|
});
|
|
21561
22601
|
import chalk46 from "chalk";
|
|
21562
|
-
import
|
|
22602
|
+
import ora12 from "ora";
|
|
21563
22603
|
async function handler29(args, ctx) {
|
|
21564
22604
|
await hydrateAnalysisFromPersistedState(ctx);
|
|
21565
22605
|
const { flags } = parseArgs(args, ["findings"]);
|
|
@@ -21601,7 +22641,7 @@ async function handler29(args, ctx) {
|
|
|
21601
22641
|
await initSchema();
|
|
21602
22642
|
await autoGenerateSegments();
|
|
21603
22643
|
printCompanionBanner("metrics", ctx.analysis.primary);
|
|
21604
|
-
const spinner =
|
|
22644
|
+
const spinner = ora12({
|
|
21605
22645
|
text: "Computing SaaS metrics\u2026",
|
|
21606
22646
|
indent: 2,
|
|
21607
22647
|
discardStdin: false
|
|
@@ -21800,7 +22840,7 @@ __export(feedback_exports, {
|
|
|
21800
22840
|
handler: () => handler30
|
|
21801
22841
|
});
|
|
21802
22842
|
import chalk47 from "chalk";
|
|
21803
|
-
import
|
|
22843
|
+
import ora13 from "ora";
|
|
21804
22844
|
async function handler30(args, ctx) {
|
|
21805
22845
|
const feedbackText = args.join(" ").trim();
|
|
21806
22846
|
if (!feedbackText) {
|
|
@@ -21828,7 +22868,7 @@ async function handler30(args, ctx) {
|
|
|
21828
22868
|
console.log();
|
|
21829
22869
|
return;
|
|
21830
22870
|
}
|
|
21831
|
-
const spinner =
|
|
22871
|
+
const spinner = ora13({ text: "Applying feedback\u2026", discardStdin: false }).start();
|
|
21832
22872
|
try {
|
|
21833
22873
|
const result = await applyFeedback(profile, feedbackText, ctx);
|
|
21834
22874
|
spinner.succeed("Feedback applied");
|
|
@@ -21860,7 +22900,7 @@ var recap_exports = {};
|
|
|
21860
22900
|
__export(recap_exports, {
|
|
21861
22901
|
handler: () => handler31
|
|
21862
22902
|
});
|
|
21863
|
-
import
|
|
22903
|
+
import ora14 from "ora";
|
|
21864
22904
|
import chalk48 from "chalk";
|
|
21865
22905
|
async function handler31(_args, ctx) {
|
|
21866
22906
|
if (ctx.messages.length === 0) {
|
|
@@ -21897,7 +22937,7 @@ ${companyBlock}` : "",
|
|
|
21897
22937
|
const prefix = msg.role === "user" ? "USER" : "ASSISTANT";
|
|
21898
22938
|
conversationLines.push(`[${prefix}]: ${msg.content}`);
|
|
21899
22939
|
}
|
|
21900
|
-
const spinner =
|
|
22940
|
+
const spinner = ora14({ text: "Summarizing session\u2026", color: "cyan", discardStdin: false }).start();
|
|
21901
22941
|
try {
|
|
21902
22942
|
const { text: fullText } = await llmStreamText(
|
|
21903
22943
|
"recap",
|
|
@@ -22025,7 +23065,7 @@ var init_recall = __esm({
|
|
|
22025
23065
|
|
|
22026
23066
|
// src/memory/feedback.ts
|
|
22027
23067
|
import { appendFileSync as appendFileSync5 } from "fs";
|
|
22028
|
-
import { join as
|
|
23068
|
+
import { join as join26 } from "path";
|
|
22029
23069
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
22030
23070
|
function summarize(text) {
|
|
22031
23071
|
return text.replace(/[#*`>_]/g, "").replace(/\s+/g, " ").trim().slice(0, 200);
|
|
@@ -22041,7 +23081,7 @@ function recordFeedback(input) {
|
|
|
22041
23081
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
22042
23082
|
};
|
|
22043
23083
|
try {
|
|
22044
|
-
appendFileSync5(
|
|
23084
|
+
appendFileSync5(join26(getMemoryDir(), "feedback.jsonl"), JSON.stringify(entry) + "\n");
|
|
22045
23085
|
} catch {
|
|
22046
23086
|
}
|
|
22047
23087
|
if (input.rating === "positive") {
|
|
@@ -22132,7 +23172,7 @@ __export(knowledge_exports, {
|
|
|
22132
23172
|
handler: () => handler35
|
|
22133
23173
|
});
|
|
22134
23174
|
import chalk52 from "chalk";
|
|
22135
|
-
import
|
|
23175
|
+
import ora15 from "ora";
|
|
22136
23176
|
async function handler35(args, ctx) {
|
|
22137
23177
|
const sub = (args[0] ?? "list").toLowerCase();
|
|
22138
23178
|
if (sub === "add") {
|
|
@@ -22144,7 +23184,7 @@ async function handler35(args, ctx) {
|
|
|
22144
23184
|
console.log();
|
|
22145
23185
|
return;
|
|
22146
23186
|
}
|
|
22147
|
-
const spin = ctx.execution.progress ?
|
|
23187
|
+
const spin = ctx.execution.progress ? ora15({ text: "Ingesting knowledge\u2026", color: "cyan", discardStdin: false }).start() : null;
|
|
22148
23188
|
try {
|
|
22149
23189
|
const result = await addKnowledgeFile(path);
|
|
22150
23190
|
spin?.succeed(`Indexed "${result.title}"`);
|
|
@@ -22454,8 +23494,8 @@ var switch_exports = {};
|
|
|
22454
23494
|
__export(switch_exports, {
|
|
22455
23495
|
handler: () => handler39
|
|
22456
23496
|
});
|
|
22457
|
-
import { join as
|
|
22458
|
-
import
|
|
23497
|
+
import { join as join27 } from "path";
|
|
23498
|
+
import ora16 from "ora";
|
|
22459
23499
|
import chalk56 from "chalk";
|
|
22460
23500
|
async function handler39(args, ctx) {
|
|
22461
23501
|
if (args.length === 0) {
|
|
@@ -22470,7 +23510,7 @@ async function handler39(args, ctx) {
|
|
|
22470
23510
|
}
|
|
22471
23511
|
const exchangeCount = Math.floor(ctx.messages.length / 2);
|
|
22472
23512
|
if (exchangeCount > 0) {
|
|
22473
|
-
const spinner =
|
|
23513
|
+
const spinner = ora16({ text: "Saving current session\u2026", color: "cyan", discardStdin: false }).start();
|
|
22474
23514
|
await closeSession(ctx);
|
|
22475
23515
|
const fromLabel = ctx.sessionName ? `"${ctx.sessionName}"` : ctx.sessionId.slice(-4);
|
|
22476
23516
|
spinner.succeed(`Saved ${fromLabel}`);
|
|
@@ -22485,7 +23525,7 @@ async function handler39(args, ctx) {
|
|
|
22485
23525
|
}
|
|
22486
23526
|
const context = buildSwitchContext(session);
|
|
22487
23527
|
const newId = makeSessionId();
|
|
22488
|
-
const newFile =
|
|
23528
|
+
const newFile = join27(getSessionsDir(), `${newId}.json`);
|
|
22489
23529
|
resetContextForSwitch(ctx, {
|
|
22490
23530
|
sessionId: newId,
|
|
22491
23531
|
sessionFile: newFile,
|
|
@@ -22511,7 +23551,7 @@ async function handler39(args, ctx) {
|
|
|
22511
23551
|
return `Switched to "${targetName}"`;
|
|
22512
23552
|
} else {
|
|
22513
23553
|
const newId = makeSessionId();
|
|
22514
|
-
const newFile =
|
|
23554
|
+
const newFile = join27(getSessionsDir(), `${newId}.json`);
|
|
22515
23555
|
resetContextForSwitch(ctx, {
|
|
22516
23556
|
sessionId: newId,
|
|
22517
23557
|
sessionFile: newFile,
|
|
@@ -22561,13 +23601,177 @@ var init_switch = __esm({
|
|
|
22561
23601
|
}
|
|
22562
23602
|
});
|
|
22563
23603
|
|
|
22564
|
-
// src/commands/
|
|
22565
|
-
var
|
|
22566
|
-
__export(
|
|
23604
|
+
// src/commands/connect.ts
|
|
23605
|
+
var connect_exports2 = {};
|
|
23606
|
+
__export(connect_exports2, {
|
|
22567
23607
|
handler: () => handler40
|
|
22568
23608
|
});
|
|
22569
23609
|
import chalk57 from "chalk";
|
|
23610
|
+
import ora17 from "ora";
|
|
23611
|
+
function usage2() {
|
|
23612
|
+
console.log(chalk57.dim(" Usage: /connect paste any provider key"));
|
|
23613
|
+
console.log(chalk57.dim(" /connect <provider> key for a specific provider (or: ollama)"));
|
|
23614
|
+
console.log(chalk57.dim(" /connect --key <key> non-interactive (auto-detects provider)"));
|
|
23615
|
+
console.log(chalk57.dim(" /connect --base-url <url> [--id <name>] [--key <key>] custom endpoint"));
|
|
23616
|
+
}
|
|
23617
|
+
function printOutcome(outcome, ctx) {
|
|
23618
|
+
console.log();
|
|
23619
|
+
const [headline, ...rest] = describeConnectOutcome(outcome);
|
|
23620
|
+
console.log(" " + paint("success", "\u2713") + " " + chalk57.bold(headline ?? ""));
|
|
23621
|
+
for (const line of rest) {
|
|
23622
|
+
console.log(" " + chalk57.dim(line));
|
|
23623
|
+
}
|
|
23624
|
+
console.log();
|
|
23625
|
+
console.log(" " + chalk57.dim(`Active stack: ${formatActiveStack(ctx)}`));
|
|
23626
|
+
console.log(" " + chalk57.dim("/provider to switch engines \xB7 /model list to browse models"));
|
|
23627
|
+
console.log();
|
|
23628
|
+
}
|
|
23629
|
+
function printError(err, ctx) {
|
|
23630
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23631
|
+
console.log();
|
|
23632
|
+
console.log(" " + chalk57.red(message));
|
|
23633
|
+
console.log();
|
|
23634
|
+
if (ctx.oneShot) process.exit(1);
|
|
23635
|
+
}
|
|
23636
|
+
async function promptKey(session, label) {
|
|
23637
|
+
console.log();
|
|
23638
|
+
console.log(
|
|
23639
|
+
" " + chalk57.dim("Paste once, press Enter. Stored in ") + paint("accent", "~/.ntrp/config.json") + chalk57.dim(" only.")
|
|
23640
|
+
);
|
|
23641
|
+
return session.askSecret(label, { confirm: false });
|
|
23642
|
+
}
|
|
22570
23643
|
async function handler40(args, ctx) {
|
|
23644
|
+
const { positional, flags } = parseArgs(args);
|
|
23645
|
+
const sub = positional[0]?.toLowerCase();
|
|
23646
|
+
if (sub === "help") {
|
|
23647
|
+
usage2();
|
|
23648
|
+
return;
|
|
23649
|
+
}
|
|
23650
|
+
const inlineKey = getString(flags, "key");
|
|
23651
|
+
const baseUrl = getString(flags, "base-url", "url");
|
|
23652
|
+
const forcedProvider = getString(flags, "provider") ?? (sub && sub !== "help" ? sub : void 0);
|
|
23653
|
+
const customId = getString(flags, "id");
|
|
23654
|
+
const label = getString(flags, "label");
|
|
23655
|
+
const forcedSpec = forcedProvider ? getProviderSpec(forcedProvider) : void 0;
|
|
23656
|
+
if (forcedSpec && !forcedSpec.requires_key && !inlineKey) {
|
|
23657
|
+
const spinner2 = ora17({ text: `Looking for ${forcedSpec.label}\u2026`, discardStdin: false }).start();
|
|
23658
|
+
try {
|
|
23659
|
+
const outcome = await connectKeyless(forcedSpec.id, baseUrl);
|
|
23660
|
+
spinner2.stop();
|
|
23661
|
+
printOutcome(outcome, ctx);
|
|
23662
|
+
} catch (err) {
|
|
23663
|
+
spinner2.stop();
|
|
23664
|
+
printError(err, ctx);
|
|
23665
|
+
}
|
|
23666
|
+
return;
|
|
23667
|
+
}
|
|
23668
|
+
if (baseUrl && !forcedSpec) {
|
|
23669
|
+
const id = customId ?? forcedProvider ?? hostToId(baseUrl);
|
|
23670
|
+
let key2 = inlineKey;
|
|
23671
|
+
if (!key2 && !ctx.oneShot && process.stdin.isTTY) {
|
|
23672
|
+
const session2 = createPromptSession(ctx.rl, ctx);
|
|
23673
|
+
try {
|
|
23674
|
+
const needsKey = await session2.confirm("Does this endpoint need an API key?", false);
|
|
23675
|
+
if (needsKey) key2 = await promptKey(session2, `API key for ${id}`);
|
|
23676
|
+
} finally {
|
|
23677
|
+
session2.close();
|
|
23678
|
+
}
|
|
23679
|
+
}
|
|
23680
|
+
const spinner2 = ora17({ text: `Checking ${baseUrl}\u2026`, discardStdin: false }).start();
|
|
23681
|
+
try {
|
|
23682
|
+
const outcome = await connectCustomEndpoint({ id, baseUrl, key: key2, label });
|
|
23683
|
+
spinner2.stop();
|
|
23684
|
+
printOutcome(outcome, ctx);
|
|
23685
|
+
} catch (err) {
|
|
23686
|
+
spinner2.stop();
|
|
23687
|
+
printError(err, ctx);
|
|
23688
|
+
}
|
|
23689
|
+
return;
|
|
23690
|
+
}
|
|
23691
|
+
if (forcedProvider && !forcedSpec) {
|
|
23692
|
+
console.log();
|
|
23693
|
+
console.log(" " + chalk57.red(`Unknown provider: ${forcedProvider}`));
|
|
23694
|
+
console.log(
|
|
23695
|
+
" " + chalk57.dim("Built-ins: anthropic, openai, google, groq, mistral, deepseek, xai, openrouter, together, fireworks, ollama")
|
|
23696
|
+
);
|
|
23697
|
+
console.log(" " + chalk57.dim(`Custom endpoint: /connect --base-url <url> --id ${forcedProvider}`));
|
|
23698
|
+
console.log();
|
|
23699
|
+
if (ctx.oneShot) process.exit(1);
|
|
23700
|
+
return;
|
|
23701
|
+
}
|
|
23702
|
+
let key = inlineKey;
|
|
23703
|
+
let session;
|
|
23704
|
+
if (!key) {
|
|
23705
|
+
if (ctx.oneShot || !process.stdin.isTTY) {
|
|
23706
|
+
printError(new ConnectError("Non-interactive mode needs --key <key>."), ctx);
|
|
23707
|
+
usage2();
|
|
23708
|
+
return;
|
|
23709
|
+
}
|
|
23710
|
+
session = createPromptSession(ctx.rl, ctx);
|
|
23711
|
+
key = await promptKey(
|
|
23712
|
+
session,
|
|
23713
|
+
forcedSpec ? `${forcedSpec.label} API key` : "LLM API key (any provider)"
|
|
23714
|
+
);
|
|
23715
|
+
}
|
|
23716
|
+
const spinner = ora17({ text: "Identifying provider\u2026", discardStdin: false }).start();
|
|
23717
|
+
try {
|
|
23718
|
+
const outcome = await connectWithKey(key, {
|
|
23719
|
+
providerId: forcedSpec?.id,
|
|
23720
|
+
callbacks: session ? {
|
|
23721
|
+
confirmDetection: async (providerId) => {
|
|
23722
|
+
spinner.stop();
|
|
23723
|
+
return session.confirm(`Detected ${providerLabel(providerId)} \u2014 connect it?`, true);
|
|
23724
|
+
},
|
|
23725
|
+
chooseProvider: async (accepted) => {
|
|
23726
|
+
spinner.stop();
|
|
23727
|
+
return session.choose(
|
|
23728
|
+
"Multiple providers accepted this key \u2014 which is it?",
|
|
23729
|
+
accepted.map((a) => ({ value: a.provider, label: providerLabel(a.provider) }))
|
|
23730
|
+
);
|
|
23731
|
+
}
|
|
23732
|
+
} : void 0
|
|
23733
|
+
});
|
|
23734
|
+
spinner.stop();
|
|
23735
|
+
printOutcome(outcome, ctx);
|
|
23736
|
+
} catch (err) {
|
|
23737
|
+
spinner.stop();
|
|
23738
|
+
if (err instanceof ConnectCancelled) {
|
|
23739
|
+
console.log(" " + chalk57.dim("Cancelled."));
|
|
23740
|
+
console.log();
|
|
23741
|
+
} else {
|
|
23742
|
+
printError(err, ctx);
|
|
23743
|
+
}
|
|
23744
|
+
} finally {
|
|
23745
|
+
session?.close();
|
|
23746
|
+
}
|
|
23747
|
+
}
|
|
23748
|
+
function hostToId(url) {
|
|
23749
|
+
try {
|
|
23750
|
+
const host = new URL(url).hostname;
|
|
23751
|
+
return host.replace(/^www\./, "").split(".")[0] ?? "custom";
|
|
23752
|
+
} catch {
|
|
23753
|
+
return "custom";
|
|
23754
|
+
}
|
|
23755
|
+
}
|
|
23756
|
+
var init_connect2 = __esm({
|
|
23757
|
+
"src/commands/connect.ts"() {
|
|
23758
|
+
"use strict";
|
|
23759
|
+
init_argparse();
|
|
23760
|
+
init_prompts();
|
|
23761
|
+
init_providers();
|
|
23762
|
+
init_session_state();
|
|
23763
|
+
init_connect();
|
|
23764
|
+
init_theme();
|
|
23765
|
+
}
|
|
23766
|
+
});
|
|
23767
|
+
|
|
23768
|
+
// src/commands/provider.ts
|
|
23769
|
+
var provider_exports = {};
|
|
23770
|
+
__export(provider_exports, {
|
|
23771
|
+
handler: () => handler41
|
|
23772
|
+
});
|
|
23773
|
+
import chalk58 from "chalk";
|
|
23774
|
+
async function handler41(args, ctx) {
|
|
22571
23775
|
const { positional, flags } = parseArgs(args, ["default"]);
|
|
22572
23776
|
const sub = positional[0]?.toLowerCase();
|
|
22573
23777
|
if (!sub || sub === "list") {
|
|
@@ -22579,7 +23783,7 @@ async function handler40(args, ctx) {
|
|
|
22579
23783
|
if (!ctx.oneShot) saveSessionState(ctx);
|
|
22580
23784
|
console.log();
|
|
22581
23785
|
console.log(" " + paint("success", "\u2713") + " Session engine reset \u2014 using config defaults.");
|
|
22582
|
-
console.log(" " +
|
|
23786
|
+
console.log(" " + chalk58.dim(`Default: ${loadLlmConfig().primary}`));
|
|
22583
23787
|
console.log();
|
|
22584
23788
|
return;
|
|
22585
23789
|
}
|
|
@@ -22592,7 +23796,7 @@ async function handler40(args, ctx) {
|
|
|
22592
23796
|
setConfigValue("llm-auto-failover", session.autoFailover ? "on" : "off");
|
|
22593
23797
|
}
|
|
22594
23798
|
console.log();
|
|
22595
|
-
console.log(" " + paint("success", "\u2713") + ` Saved ${
|
|
23799
|
+
console.log(" " + paint("success", "\u2713") + ` Saved ${chalk58.bold(active)} as default engine.`);
|
|
22596
23800
|
console.log();
|
|
22597
23801
|
return;
|
|
22598
23802
|
}
|
|
@@ -22611,36 +23815,40 @@ async function handler40(args, ctx) {
|
|
|
22611
23815
|
}
|
|
22612
23816
|
console.log();
|
|
22613
23817
|
console.log(
|
|
22614
|
-
" " + paint("success", "\u2713") + ` Auto-failover ${session.autoFailover ?
|
|
23818
|
+
" " + paint("success", "\u2713") + ` Auto-failover ${session.autoFailover ? chalk58.bold("on") : chalk58.bold("off")} for this session.`
|
|
22615
23819
|
);
|
|
22616
|
-
if (persist) console.log(" " +
|
|
23820
|
+
if (persist) console.log(" " + chalk58.dim("Also saved as config default."));
|
|
22617
23821
|
console.log();
|
|
22618
23822
|
return;
|
|
22619
23823
|
}
|
|
22620
|
-
|
|
23824
|
+
const spec = getProviderSpec(sub);
|
|
23825
|
+
if (!spec || RESERVED.has(sub)) {
|
|
22621
23826
|
console.log();
|
|
22622
|
-
console.log(" " +
|
|
22623
|
-
console.log(" " +
|
|
23827
|
+
console.log(" " + chalk58.red(`Unknown engine: ${sub}`));
|
|
23828
|
+
console.log(" " + chalk58.dim("Usage: /provider [<id>|list|reset|save|failover on|off]"));
|
|
23829
|
+
console.log(" " + chalk58.dim("Connected: ") + (availableEngineLabels().join(", ") || chalk58.dim("none")));
|
|
23830
|
+
console.log(" " + chalk58.dim("Add one with ") + paint("accent", "/connect"));
|
|
22624
23831
|
console.log();
|
|
22625
23832
|
return;
|
|
22626
23833
|
}
|
|
22627
|
-
const provider =
|
|
23834
|
+
const provider = spec.id;
|
|
22628
23835
|
if (!hasProviderKey(provider)) {
|
|
22629
|
-
const keyHint = provider === "anthropic" ? "api-key" : "openai-api-key";
|
|
22630
23836
|
console.log();
|
|
22631
|
-
console.log(" " +
|
|
22632
|
-
console.log(
|
|
23837
|
+
console.log(" " + chalk58.red(`${spec.label} isn't connected.`));
|
|
23838
|
+
console.log(
|
|
23839
|
+
" " + chalk58.dim("Run ") + paint("accent", `/connect ${provider}`) + chalk58.dim(" (or ") + paint("accent", `/config set ${spec.key_config_name}`) + chalk58.dim(").")
|
|
23840
|
+
);
|
|
22633
23841
|
console.log();
|
|
22634
23842
|
return;
|
|
22635
23843
|
}
|
|
22636
23844
|
ensureLlmSession(ctx).provider = provider;
|
|
22637
23845
|
if (!ctx.oneShot) saveSessionState(ctx);
|
|
22638
23846
|
console.log();
|
|
22639
|
-
console.log(" " + paint("success", "\u2713") + ` Active engine: ${
|
|
22640
|
-
console.log(" " +
|
|
23847
|
+
console.log(" " + paint("success", "\u2713") + ` Active engine: ${chalk58.bold(provider)}`);
|
|
23848
|
+
console.log(" " + chalk58.dim(`Stack: ${formatActiveStack(ctx)}`));
|
|
22641
23849
|
const others = availableEngineLabels().filter((p) => p !== provider);
|
|
22642
23850
|
if (others.length > 0) {
|
|
22643
|
-
console.log(" " +
|
|
23851
|
+
console.log(" " + chalk58.dim(`Also available: ${others.join(", ")}`));
|
|
22644
23852
|
}
|
|
22645
23853
|
console.log();
|
|
22646
23854
|
}
|
|
@@ -22651,49 +23859,54 @@ function printStatus(ctx) {
|
|
|
22651
23859
|
const autoFailover = resolveAutoFailoverEnabled(ctx);
|
|
22652
23860
|
const engines = countAvailableEngines();
|
|
22653
23861
|
console.log();
|
|
22654
|
-
console.log(
|
|
22655
|
-
console.log(`
|
|
22656
|
-
|
|
22657
|
-
|
|
22658
|
-
const marker2 =
|
|
22659
|
-
console.log(` ${
|
|
23862
|
+
console.log(chalk58.bold(" LLM engines"));
|
|
23863
|
+
console.log(` Connected: ${engines} engine${engines === 1 ? "" : "s"}`);
|
|
23864
|
+
const configured = listProviderSpecs().filter((s) => hasProviderKey(s.id));
|
|
23865
|
+
for (const s of configured) {
|
|
23866
|
+
const marker2 = s.id === active ? paint("accent", " \u25BA active") : "";
|
|
23867
|
+
console.log(` ${paint("success", "\u2713")} ${s.id}${s.custom ? chalk58.dim(" (custom)") : ""}${marker2}`);
|
|
23868
|
+
}
|
|
23869
|
+
if (configured.length === 0) {
|
|
23870
|
+
console.log(" " + chalk58.dim("none \u2014 run /connect and paste any provider key"));
|
|
22660
23871
|
}
|
|
22661
23872
|
console.log();
|
|
22662
|
-
console.log(
|
|
23873
|
+
console.log(chalk58.bold(" Active stack"));
|
|
22663
23874
|
console.log(` ${formatActiveStack(ctx)}`);
|
|
22664
23875
|
if (sessionOverride) {
|
|
22665
|
-
console.log(
|
|
23876
|
+
console.log(chalk58.dim(" (session override \u2014 /provider reset to use default)"));
|
|
22666
23877
|
} else {
|
|
22667
|
-
console.log(
|
|
23878
|
+
console.log(chalk58.dim(` (config default: ${cfg.primary})`));
|
|
22668
23879
|
}
|
|
22669
23880
|
console.log();
|
|
22670
|
-
console.log(` Auto-failover: ${autoFailover ? paint("success", "on") :
|
|
22671
|
-
console.log(
|
|
22672
|
-
console.log(
|
|
22673
|
-
console.log(
|
|
23881
|
+
console.log(` Auto-failover: ${autoFailover ? paint("success", "on") : chalk58.dim("off")}`);
|
|
23882
|
+
console.log(chalk58.dim(` /provider <id> \u2014 switch engine (${configured.map((s) => s.id).join(", ") || "none connected"})`));
|
|
23883
|
+
console.log(chalk58.dim(" /provider failover on|off \u2014 rate-limit safety net"));
|
|
23884
|
+
console.log(chalk58.dim(" /provider save \u2014 persist active engine to config"));
|
|
23885
|
+
console.log(chalk58.dim(" /connect \u2014 add another provider (any API key)"));
|
|
22674
23886
|
console.log();
|
|
22675
23887
|
}
|
|
22676
|
-
var
|
|
23888
|
+
var RESERVED;
|
|
22677
23889
|
var init_provider = __esm({
|
|
22678
23890
|
"src/commands/provider.ts"() {
|
|
22679
23891
|
"use strict";
|
|
22680
23892
|
init_argparse();
|
|
22681
23893
|
init_session_state();
|
|
23894
|
+
init_providers();
|
|
22682
23895
|
init_llm_config();
|
|
22683
23896
|
init_store();
|
|
22684
23897
|
init_context2();
|
|
22685
23898
|
init_theme();
|
|
22686
|
-
|
|
23899
|
+
RESERVED = /* @__PURE__ */ new Set(["list", "reset", "save", "failover"]);
|
|
22687
23900
|
}
|
|
22688
23901
|
});
|
|
22689
23902
|
|
|
22690
23903
|
// src/commands/tier.ts
|
|
22691
23904
|
var tier_exports = {};
|
|
22692
23905
|
__export(tier_exports, {
|
|
22693
|
-
handler: () =>
|
|
23906
|
+
handler: () => handler42
|
|
22694
23907
|
});
|
|
22695
|
-
import
|
|
22696
|
-
async function
|
|
23908
|
+
import chalk59 from "chalk";
|
|
23909
|
+
async function handler42(args, ctx) {
|
|
22697
23910
|
const { positional, flags } = parseArgs(args, ["default"]);
|
|
22698
23911
|
const sub = positional[0]?.toLowerCase();
|
|
22699
23912
|
if (!sub || sub === "list") {
|
|
@@ -22702,8 +23915,8 @@ async function handler41(args, ctx) {
|
|
|
22702
23915
|
}
|
|
22703
23916
|
if (!TIERS.includes(sub)) {
|
|
22704
23917
|
console.log();
|
|
22705
|
-
console.log(" " +
|
|
22706
|
-
console.log(" " +
|
|
23918
|
+
console.log(" " + chalk59.red(`Unknown tier: ${sub}`));
|
|
23919
|
+
console.log(" " + chalk59.dim("Usage: /tier [high|medium|low|list] [--default]"));
|
|
22707
23920
|
console.log();
|
|
22708
23921
|
return;
|
|
22709
23922
|
}
|
|
@@ -22717,40 +23930,48 @@ async function handler41(args, ctx) {
|
|
|
22717
23930
|
}
|
|
22718
23931
|
console.log();
|
|
22719
23932
|
console.log(
|
|
22720
|
-
" " + paint("success", "\u2713") + ` Inference tier set to ${
|
|
23933
|
+
" " + paint("success", "\u2713") + ` Inference tier set to ${chalk59.bold(tier.toUpperCase())}` + (persist ? chalk59.dim(" (saved as default)") : chalk59.dim(" (this session)"))
|
|
22721
23934
|
);
|
|
22722
|
-
console.log(" " +
|
|
23935
|
+
console.log(" " + chalk59.dim(`Stack: ${formatActiveStack(ctx)}`));
|
|
22723
23936
|
console.log();
|
|
22724
23937
|
}
|
|
22725
23938
|
function printCatalog(ctx) {
|
|
22726
23939
|
const cfg = loadLlmConfig();
|
|
22727
23940
|
const active = resolveModelForActive(ctx, "agentic_investigation");
|
|
22728
23941
|
const sessionTier = ctx.llm?.tier;
|
|
23942
|
+
const providers = getAvailableProviders();
|
|
22729
23943
|
console.log();
|
|
22730
|
-
console.log(
|
|
23944
|
+
console.log(chalk59.bold(" Inference settings"));
|
|
22731
23945
|
console.log(` Active: ${paint("accent", formatActiveStack(ctx))}`);
|
|
22732
23946
|
if (sessionTier) {
|
|
22733
|
-
console.log(
|
|
23947
|
+
console.log(chalk59.dim(" (session tier override)"));
|
|
22734
23948
|
} else {
|
|
22735
|
-
console.log(
|
|
23949
|
+
console.log(chalk59.dim(` Config default tier: ${cfg.tier.toUpperCase()}`));
|
|
22736
23950
|
}
|
|
22737
23951
|
console.log();
|
|
23952
|
+
if (providers.length === 0) {
|
|
23953
|
+
console.log(" " + chalk59.dim("No engines connected \u2014 run /connect and paste any provider key."));
|
|
23954
|
+
console.log();
|
|
23955
|
+
}
|
|
22738
23956
|
for (const tier of TIERS) {
|
|
22739
|
-
console.log(
|
|
22740
|
-
for (const provider of
|
|
22741
|
-
const
|
|
22742
|
-
|
|
22743
|
-
|
|
22744
|
-
|
|
22745
|
-
const status = m.status === "active" ? "" : chalk58.yellow(` [${m.status}]`);
|
|
22746
|
-
console.log(`${marker2}${provider}/${m.id}${status} \u2014 ${m.display_name}`);
|
|
23957
|
+
console.log(chalk59.bold(` ${tier.toUpperCase()}`));
|
|
23958
|
+
for (const provider of providers) {
|
|
23959
|
+
const modelId = resolveModelSafe(provider, tier);
|
|
23960
|
+
if (!modelId) {
|
|
23961
|
+
console.log(` ${provider}/${chalk59.dim("no models \u2014 /model refresh")}`);
|
|
23962
|
+
continue;
|
|
22747
23963
|
}
|
|
23964
|
+
const isActive = provider === active.provider && tier === active.tier && modelId === active.modelId;
|
|
23965
|
+
const marker2 = isActive ? paint("accent", "\u25BA ") : " ";
|
|
23966
|
+
const discovered = !!getProviderModels(provider);
|
|
23967
|
+
const source = discovered ? "" : chalk59.dim(" [bundled fallback]");
|
|
23968
|
+
console.log(`${marker2}${provider}/${modelId}${source}`);
|
|
22748
23969
|
}
|
|
22749
23970
|
console.log();
|
|
22750
23971
|
}
|
|
22751
|
-
console.log(
|
|
22752
|
-
console.log(
|
|
22753
|
-
console.log(
|
|
23972
|
+
console.log(chalk59.dim(" /tier high|medium|low \u2014 set tier for this session"));
|
|
23973
|
+
console.log(chalk59.dim(" /tier high --default \u2014 also save as config default"));
|
|
23974
|
+
console.log(chalk59.dim(" /provider <id> \u2014 switch engine \xB7 /model list \u2014 browse models"));
|
|
22754
23975
|
console.log();
|
|
22755
23976
|
}
|
|
22756
23977
|
var TIERS;
|
|
@@ -22759,6 +23980,7 @@ var init_tier = __esm({
|
|
|
22759
23980
|
"use strict";
|
|
22760
23981
|
init_argparse();
|
|
22761
23982
|
init_catalog();
|
|
23983
|
+
init_models_cache();
|
|
22762
23984
|
init_session_state();
|
|
22763
23985
|
init_llm_config();
|
|
22764
23986
|
init_store();
|
|
@@ -22771,12 +23993,21 @@ var init_tier = __esm({
|
|
|
22771
23993
|
// src/commands/model.ts
|
|
22772
23994
|
var model_exports = {};
|
|
22773
23995
|
__export(model_exports, {
|
|
22774
|
-
handler: () =>
|
|
23996
|
+
handler: () => handler43
|
|
22775
23997
|
});
|
|
22776
|
-
import
|
|
22777
|
-
|
|
22778
|
-
|
|
23998
|
+
import chalk60 from "chalk";
|
|
23999
|
+
import ora18 from "ora";
|
|
24000
|
+
async function handler43(args, ctx) {
|
|
24001
|
+
const { positional, flags } = parseArgs(args, ["default", "all"]);
|
|
22779
24002
|
const sub = positional[0]?.toLowerCase();
|
|
24003
|
+
if (sub === "list") {
|
|
24004
|
+
printModelList(ctx, getBool(flags, "all"));
|
|
24005
|
+
return;
|
|
24006
|
+
}
|
|
24007
|
+
if (sub === "refresh") {
|
|
24008
|
+
await refreshModels(ctx);
|
|
24009
|
+
return;
|
|
24010
|
+
}
|
|
22780
24011
|
if (sub === "clear") {
|
|
22781
24012
|
const persist = getBool(flags, "default");
|
|
22782
24013
|
if (ctx.llm) ctx.llm.modelOverride = void 0;
|
|
@@ -22784,7 +24015,7 @@ async function handler42(args, ctx) {
|
|
|
22784
24015
|
if (!ctx.oneShot) saveSessionState(ctx);
|
|
22785
24016
|
console.log();
|
|
22786
24017
|
console.log(" " + paint("success", "\u2713") + " Model override cleared \u2014 using tier defaults.");
|
|
22787
|
-
console.log(" " +
|
|
24018
|
+
console.log(" " + chalk60.dim(`Stack: ${formatActiveStack(ctx)}`));
|
|
22788
24019
|
console.log();
|
|
22789
24020
|
return;
|
|
22790
24021
|
}
|
|
@@ -22792,22 +24023,28 @@ async function handler42(args, ctx) {
|
|
|
22792
24023
|
const modelId = positional[1];
|
|
22793
24024
|
if (!modelId) {
|
|
22794
24025
|
console.log();
|
|
22795
|
-
console.log(" " +
|
|
24026
|
+
console.log(" " + chalk60.red("Usage: /model set <model-id> [--default]"));
|
|
22796
24027
|
console.log();
|
|
22797
24028
|
return;
|
|
22798
24029
|
}
|
|
22799
24030
|
const active = resolveActiveProvider(ctx);
|
|
22800
24031
|
const providerErr = validateModelForProvider(modelId, active);
|
|
22801
|
-
const entry = getCatalogEntry(modelId);
|
|
22802
24032
|
if (providerErr) {
|
|
22803
24033
|
console.log();
|
|
22804
|
-
console.log(" " +
|
|
24034
|
+
console.log(" " + chalk60.red(providerErr));
|
|
22805
24035
|
console.log();
|
|
22806
24036
|
return;
|
|
22807
24037
|
}
|
|
22808
|
-
|
|
24038
|
+
const cache2 = getProviderModels(active);
|
|
24039
|
+
const known = cache2?.models.some((m) => m.id === modelId);
|
|
24040
|
+
if (cache2 && !known) {
|
|
24041
|
+
console.log();
|
|
24042
|
+
console.log(
|
|
24043
|
+
" " + chalk60.yellow("\u26A0") + ` ${modelId} isn't in ${active}'s discovered list (` + paint("accent", "/model list") + `) \u2014 saving anyway.`
|
|
24044
|
+
);
|
|
24045
|
+
} else if (!cache2) {
|
|
22809
24046
|
console.log();
|
|
22810
|
-
console.log(" " +
|
|
24047
|
+
console.log(" " + chalk60.yellow("\u26A0") + ` No discovered models for ${active} yet (` + paint("accent", "/model refresh") + `) \u2014 saving anyway.`);
|
|
22811
24048
|
}
|
|
22812
24049
|
const persist = getBool(flags, "default");
|
|
22813
24050
|
if (persist) {
|
|
@@ -22818,48 +24055,98 @@ async function handler42(args, ctx) {
|
|
|
22818
24055
|
}
|
|
22819
24056
|
console.log();
|
|
22820
24057
|
console.log(
|
|
22821
|
-
" " + paint("success", "\u2713") + ` Model: ${
|
|
24058
|
+
" " + paint("success", "\u2713") + ` Model: ${chalk60.bold(modelId)}` + (persist ? chalk60.dim(" (saved as default)") : chalk60.dim(" (this session)"))
|
|
22822
24059
|
);
|
|
22823
|
-
console.log(" " +
|
|
24060
|
+
console.log(" " + chalk60.dim(`Stack: ${formatActiveStack(ctx)}`));
|
|
22824
24061
|
console.log();
|
|
22825
24062
|
return;
|
|
22826
24063
|
}
|
|
22827
24064
|
const sessionOverride = ctx.llm?.modelOverride;
|
|
22828
24065
|
const globalOverride = getConfigValue("llm-model-override");
|
|
22829
24066
|
console.log();
|
|
22830
|
-
console.log(
|
|
24067
|
+
console.log(chalk60.bold(" Model"));
|
|
22831
24068
|
if (sessionOverride) {
|
|
22832
24069
|
console.log(` Session override: ${paint("accent", sessionOverride)}`);
|
|
22833
24070
|
} else if (globalOverride) {
|
|
22834
24071
|
console.log(` Config default: ${paint("accent", globalOverride)}`);
|
|
22835
24072
|
} else {
|
|
22836
|
-
console.log(" " +
|
|
24073
|
+
console.log(" " + chalk60.dim("No override \u2014 tier defaults apply."));
|
|
22837
24074
|
}
|
|
22838
24075
|
console.log(` Active stack: ${formatActiveStack(ctx)}`);
|
|
22839
|
-
console.log(
|
|
24076
|
+
console.log(chalk60.dim(" /model list \xB7 /model set <id> \xB7 /model refresh \xB7 /model clear"));
|
|
22840
24077
|
console.log();
|
|
22841
24078
|
}
|
|
24079
|
+
function tierMarkers(cache2, modelId) {
|
|
24080
|
+
const tiers = Object.entries(cache2.tier_stack).filter(([, id]) => id === modelId).map(([tier]) => tier.toUpperCase());
|
|
24081
|
+
return tiers.length > 0 ? paint("accent", ` \u25C2 ${tiers.join("/")}`) : "";
|
|
24082
|
+
}
|
|
24083
|
+
function printModelList(ctx, showAll) {
|
|
24084
|
+
const active = resolveActiveProvider(ctx);
|
|
24085
|
+
const cache2 = getProviderModels(active);
|
|
24086
|
+
console.log();
|
|
24087
|
+
console.log(chalk60.bold(` Models \u2014 ${active}`));
|
|
24088
|
+
if (!cache2) {
|
|
24089
|
+
console.log(" " + chalk60.dim("Nothing discovered yet."));
|
|
24090
|
+
console.log(" " + chalk60.dim("Run ") + paint("accent", "/model refresh") + chalk60.dim(" (or ") + paint("accent", "/connect") + chalk60.dim(" to add the provider)."));
|
|
24091
|
+
console.log();
|
|
24092
|
+
return;
|
|
24093
|
+
}
|
|
24094
|
+
const fetchedAt = cache2.fetched_at.slice(0, 10);
|
|
24095
|
+
console.log(" " + chalk60.dim(`${cache2.models.length} chat models \xB7 discovered ${fetchedAt} \xB7 /model refresh to update`));
|
|
24096
|
+
console.log();
|
|
24097
|
+
const models = showAll ? cache2.models : cache2.models.slice(0, LIST_LIMIT);
|
|
24098
|
+
const noTools = new Set(cache2.quirks?.no_tools ?? []);
|
|
24099
|
+
for (const m of models) {
|
|
24100
|
+
const name = m.display_name && m.display_name !== m.id ? chalk60.dim(` \u2014 ${m.display_name}`) : "";
|
|
24101
|
+
const quirk = noTools.has(m.id) ? chalk60.yellow(" [no tools]") : "";
|
|
24102
|
+
console.log(` ${m.id}${name}${tierMarkers(cache2, m.id)}${quirk}`);
|
|
24103
|
+
}
|
|
24104
|
+
if (!showAll && cache2.models.length > models.length) {
|
|
24105
|
+
console.log(" " + chalk60.dim(`\u2026 and ${cache2.models.length - models.length} more (/model list --all)`));
|
|
24106
|
+
}
|
|
24107
|
+
console.log();
|
|
24108
|
+
console.log(" " + chalk60.dim("/model set <id> \u2014 pin one for this session (--default to persist)"));
|
|
24109
|
+
console.log();
|
|
24110
|
+
}
|
|
24111
|
+
async function refreshModels(ctx) {
|
|
24112
|
+
const active = resolveActiveProvider(ctx);
|
|
24113
|
+
const spinner = ora18({ text: `Discovering ${active} models\u2026`, discardStdin: false }).start();
|
|
24114
|
+
const entry = await refreshProviderModels(active, { force: true });
|
|
24115
|
+
if (!entry) {
|
|
24116
|
+
spinner.fail(`Couldn't reach ${active} to refresh models.`);
|
|
24117
|
+
console.log(" " + chalk60.dim("Check your connection and key, then retry. Cached models remain in use."));
|
|
24118
|
+
console.log();
|
|
24119
|
+
return;
|
|
24120
|
+
}
|
|
24121
|
+
spinner.succeed(`${active}: ${entry.models.length} chat models discovered.`);
|
|
24122
|
+
console.log(" " + chalk60.dim(`high ${entry.tier_stack.high} \xB7 medium ${entry.tier_stack.medium} \xB7 low ${entry.tier_stack.low}`));
|
|
24123
|
+
console.log(" " + chalk60.dim(`Stack: ${formatActiveStack(ctx)}`));
|
|
24124
|
+
console.log();
|
|
24125
|
+
}
|
|
24126
|
+
var LIST_LIMIT;
|
|
22842
24127
|
var init_model = __esm({
|
|
22843
24128
|
"src/commands/model.ts"() {
|
|
22844
24129
|
"use strict";
|
|
22845
24130
|
init_argparse();
|
|
22846
|
-
|
|
24131
|
+
init_discovery();
|
|
24132
|
+
init_models_cache();
|
|
22847
24133
|
init_session_state();
|
|
22848
24134
|
init_store();
|
|
22849
24135
|
init_context2();
|
|
22850
24136
|
init_theme();
|
|
24137
|
+
LIST_LIMIT = 40;
|
|
22851
24138
|
}
|
|
22852
24139
|
});
|
|
22853
24140
|
|
|
22854
24141
|
// src/config/update-check.ts
|
|
22855
|
-
import { existsSync as
|
|
22856
|
-
import { join as
|
|
22857
|
-
function
|
|
22858
|
-
return
|
|
24142
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync17, unlinkSync as unlinkSync5, writeFileSync as writeFileSync19 } from "fs";
|
|
24143
|
+
import { join as join28 } from "path";
|
|
24144
|
+
function cachePath2() {
|
|
24145
|
+
return join28(ntrpHome(), "update-check.json");
|
|
22859
24146
|
}
|
|
22860
24147
|
function invalidateUpdateCheckCache() {
|
|
22861
|
-
const path =
|
|
22862
|
-
if (
|
|
24148
|
+
const path = cachePath2();
|
|
24149
|
+
if (existsSync21(path)) {
|
|
22863
24150
|
unlinkSync5(path);
|
|
22864
24151
|
}
|
|
22865
24152
|
}
|
|
@@ -22871,17 +24158,17 @@ var init_update_check = __esm({
|
|
|
22871
24158
|
});
|
|
22872
24159
|
|
|
22873
24160
|
// src/version.ts
|
|
22874
|
-
import { existsSync as
|
|
22875
|
-
import { dirname as dirname4, join as
|
|
24161
|
+
import { existsSync as existsSync22, readFileSync as readFileSync18 } from "fs";
|
|
24162
|
+
import { dirname as dirname4, join as join29 } from "path";
|
|
22876
24163
|
import { fileURLToPath } from "url";
|
|
22877
24164
|
function getInstalledVersion() {
|
|
22878
24165
|
if (cachedVersion) return cachedVersion;
|
|
22879
24166
|
const start = dirname4(fileURLToPath(import.meta.url));
|
|
22880
24167
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
22881
|
-
const path =
|
|
22882
|
-
if (!
|
|
24168
|
+
const path = join29(start, rel);
|
|
24169
|
+
if (!existsSync22(path)) continue;
|
|
22883
24170
|
try {
|
|
22884
|
-
const pkg = JSON.parse(
|
|
24171
|
+
const pkg = JSON.parse(readFileSync18(path, "utf-8"));
|
|
22885
24172
|
if (typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
22886
24173
|
cachedVersion = pkg.version;
|
|
22887
24174
|
return cachedVersion;
|
|
@@ -22939,10 +24226,10 @@ var init_registry = __esm({
|
|
|
22939
24226
|
// src/commands/update.ts
|
|
22940
24227
|
var update_exports = {};
|
|
22941
24228
|
__export(update_exports, {
|
|
22942
|
-
handler: () =>
|
|
24229
|
+
handler: () => handler44
|
|
22943
24230
|
});
|
|
22944
24231
|
import { spawnSync } from "child_process";
|
|
22945
|
-
import
|
|
24232
|
+
import chalk61 from "chalk";
|
|
22946
24233
|
function tailLines(text, count = 5) {
|
|
22947
24234
|
return text.split("\n").map((line) => line.trimEnd()).filter((line) => line.length > 0).slice(-count).join("\n");
|
|
22948
24235
|
}
|
|
@@ -22958,19 +24245,19 @@ function runGlobalInstall() {
|
|
|
22958
24245
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
22959
24246
|
return { ok: result.status === 0, output };
|
|
22960
24247
|
}
|
|
22961
|
-
async function
|
|
24248
|
+
async function handler44(_args, _ctx) {
|
|
22962
24249
|
const current = getInstalledVersion();
|
|
22963
24250
|
const latest = await fetchLatestVersion(1e4);
|
|
22964
24251
|
if (!latest) {
|
|
22965
24252
|
console.log();
|
|
22966
|
-
console.log(
|
|
22967
|
-
console.log(
|
|
24253
|
+
console.log(chalk61.yellow(" Could not reach the npm registry."));
|
|
24254
|
+
console.log(chalk61.dim(` Try manually: npm install -g ${NPM_PACKAGE}`));
|
|
22968
24255
|
console.log();
|
|
22969
24256
|
return;
|
|
22970
24257
|
}
|
|
22971
24258
|
if (!isNewerVersion(latest, current)) {
|
|
22972
24259
|
console.log();
|
|
22973
|
-
console.log(
|
|
24260
|
+
console.log(chalk61.green(` \u2713 You're on the latest version (v${current})`));
|
|
22974
24261
|
console.log();
|
|
22975
24262
|
return;
|
|
22976
24263
|
}
|
|
@@ -22979,24 +24266,24 @@ async function handler43(_args, _ctx) {
|
|
|
22979
24266
|
const { ok, output } = runGlobalInstall();
|
|
22980
24267
|
if (ok) {
|
|
22981
24268
|
invalidateUpdateCheckCache();
|
|
22982
|
-
console.log(
|
|
24269
|
+
console.log(chalk61.green(` \u2713 Updated! Restart NTRP to use v${latest}`));
|
|
22983
24270
|
console.log();
|
|
22984
24271
|
return;
|
|
22985
24272
|
}
|
|
22986
24273
|
const lower = output.toLowerCase();
|
|
22987
24274
|
if (lower.includes("eacces") || lower.includes("permission denied") || lower.includes("eperm")) {
|
|
22988
|
-
console.log(
|
|
22989
|
-
console.log(
|
|
22990
|
-
console.log(
|
|
24275
|
+
console.log(chalk61.red(` Could not install ${NPM_PACKAGE} (permission denied).`));
|
|
24276
|
+
console.log(chalk61.dim(` Try: sudo npm install -g ${NPM_PACKAGE}`));
|
|
24277
|
+
console.log(chalk61.dim(` Or fix npm global permissions: ${PERMISSIONS_URL}`));
|
|
22991
24278
|
console.log();
|
|
22992
24279
|
return;
|
|
22993
24280
|
}
|
|
22994
24281
|
const detail = tailLines(output);
|
|
22995
|
-
console.log(
|
|
24282
|
+
console.log(chalk61.red(` Could not install ${NPM_PACKAGE}.`));
|
|
22996
24283
|
if (detail) {
|
|
22997
|
-
console.log(
|
|
24284
|
+
console.log(chalk61.dim(` ${detail.split("\n").join("\n ")}`));
|
|
22998
24285
|
}
|
|
22999
|
-
console.log(
|
|
24286
|
+
console.log(chalk61.dim(` Try manually: npm install -g ${NPM_PACKAGE}`));
|
|
23000
24287
|
console.log();
|
|
23001
24288
|
}
|
|
23002
24289
|
var PERMISSIONS_URL;
|
|
@@ -23011,10 +24298,10 @@ var init_update = __esm({
|
|
|
23011
24298
|
});
|
|
23012
24299
|
|
|
23013
24300
|
// src/output/progress-report.ts
|
|
23014
|
-
import
|
|
24301
|
+
import chalk62 from "chalk";
|
|
23015
24302
|
function printCard(title, rows) {
|
|
23016
24303
|
const inner = CARD_W - 4;
|
|
23017
|
-
const border =
|
|
24304
|
+
const border = chalk62.dim;
|
|
23018
24305
|
console.log();
|
|
23019
24306
|
console.log(` ${border(`\u256D${"\u2500".repeat(CARD_W - 2)}\u256E`)}`);
|
|
23020
24307
|
console.log(` ${border("\u2502 ")}${padRight(sectionHeading(title), inner)}${border(" \u2502")}`);
|
|
@@ -23030,7 +24317,7 @@ function formatTokens(n) {
|
|
|
23030
24317
|
return String(n);
|
|
23031
24318
|
}
|
|
23032
24319
|
function sparkline(values) {
|
|
23033
|
-
if (values.length === 0) return
|
|
24320
|
+
if (values.length === 0) return chalk62.dim("(no activity yet)");
|
|
23034
24321
|
const max = Math.max(...values, 1);
|
|
23035
24322
|
const blocks = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
23036
24323
|
return values.map((v) => {
|
|
@@ -23039,7 +24326,7 @@ function sparkline(values) {
|
|
|
23039
24326
|
}).join("");
|
|
23040
24327
|
}
|
|
23041
24328
|
function formatMemberSince(iso) {
|
|
23042
|
-
if (!iso) return
|
|
24329
|
+
if (!iso) return chalk62.dim("\u2014");
|
|
23043
24330
|
const d = new Date(iso);
|
|
23044
24331
|
return d.toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" });
|
|
23045
24332
|
}
|
|
@@ -23056,49 +24343,49 @@ function renderProgressReport() {
|
|
|
23056
24343
|
state.milestones_unlocked.length,
|
|
23057
24344
|
TIME_MILESTONES.length
|
|
23058
24345
|
);
|
|
23059
|
-
const { usage:
|
|
24346
|
+
const { usage: usage3 } = summary;
|
|
23060
24347
|
const nextLabel = bank.next_milestone ? `${formatHoursLabel(bank.total_hours)} \u2192 ${formatHoursLabel(bank.next_milestone.hours)}` : `${formatHoursLabel(bank.total_hours)} saved`;
|
|
23061
24348
|
const bar = inlineBar(bank.progress_pct, 18);
|
|
23062
24349
|
printCard("Progress", [
|
|
23063
|
-
`${
|
|
23064
|
-
`${
|
|
23065
|
-
`${
|
|
23066
|
-
`${
|
|
24350
|
+
`${chalk62.dim("Hours saved")} ${paint("accent", formatHoursLabel(bank.total_hours))} ${bar}`,
|
|
24351
|
+
`${chalk62.dim("Next milestone")} ${bank.next_milestone ? paint("accent", bank.next_milestone.title) : chalk62.dim("top of ladder")}`,
|
|
24352
|
+
`${chalk62.dim("Member since")} ${formatMemberSince(usage3.first_active_at)}`,
|
|
24353
|
+
`${chalk62.dim("Last active")} ${formatMemberSince(usage3.last_active_at)}`
|
|
23067
24354
|
]);
|
|
23068
24355
|
if (bank.perspective_line) {
|
|
23069
|
-
console.log(` ${
|
|
24356
|
+
console.log(` ${chalk62.dim.italic(bank.perspective_line)}`);
|
|
23070
24357
|
}
|
|
23071
24358
|
printCard("Activity", [
|
|
23072
|
-
`${
|
|
23073
|
-
`${
|
|
23074
|
-
`${
|
|
23075
|
-
`${
|
|
23076
|
-
`${
|
|
24359
|
+
`${chalk62.dim("Sessions")} ${chalk62.bold(String(summary.total_sessions_on_disk))} total \xB7 ${summary.sessions_with_work} with work \xB7 ${usage3.sessions_closed} closed`,
|
|
24360
|
+
`${chalk62.dim("Diagnoses")} ${chalk62.bold(String(usage3.diagnoses))}`,
|
|
24361
|
+
`${chalk62.dim("Metrics runs")} ${chalk62.bold(String(usage3.metrics_runs))}`,
|
|
24362
|
+
`${chalk62.dim("Deliverables")} ${chalk62.bold(String(usage3.deliverables))}`,
|
|
24363
|
+
`${chalk62.dim("AI exchanges")} ${chalk62.bold(String(usage3.nl_exchanges))}`
|
|
23077
24364
|
]);
|
|
23078
|
-
const totalTokens =
|
|
24365
|
+
const totalTokens = usage3.input_tokens + usage3.output_tokens;
|
|
23079
24366
|
printCard("AI usage", [
|
|
23080
|
-
`${
|
|
23081
|
-
`${
|
|
24367
|
+
`${chalk62.dim("LLM calls")} ${chalk62.bold(String(usage3.llm_calls))}`,
|
|
24368
|
+
`${chalk62.dim("Tokens")} ${chalk62.bold(formatTokens(totalTokens))} in+out (${formatTokens(usage3.input_tokens)} in \xB7 ${formatTokens(usage3.output_tokens)} out)`
|
|
23082
24369
|
]);
|
|
23083
|
-
const weeks = [...
|
|
24370
|
+
const weeks = [...usage3.weekly].sort((a, b) => a.week.localeCompare(b.week)).slice(-8);
|
|
23084
24371
|
const weekHours = weeks.map((w) => w.minutes_saved / 60);
|
|
23085
24372
|
const weekLabels = weeks.map((w) => w.week.replace(/^\d{4}-/, ""));
|
|
23086
24373
|
console.log();
|
|
23087
24374
|
console.log(` ${sectionHeading("Weekly hours saved")}`);
|
|
23088
24375
|
console.log(` ${sparkline(weekHours)}`);
|
|
23089
24376
|
if (weeks.length > 0) {
|
|
23090
|
-
console.log(` ${
|
|
24377
|
+
console.log(` ${chalk62.dim(weekLabels.join(" "))}`);
|
|
23091
24378
|
}
|
|
23092
24379
|
console.log();
|
|
23093
24380
|
console.log(` ${sectionHeading("Milestone ladder")}`);
|
|
23094
24381
|
for (const m of TIME_MILESTONES) {
|
|
23095
24382
|
const unlocked = state.milestones_unlocked.includes(m.id);
|
|
23096
24383
|
const pct = Math.min(100, bank.total_hours / m.hours * 100);
|
|
23097
|
-
const mark = unlocked ? badge("DONE", "success") : bank.total_hours >= m.hours * 0.85 ? badge("NEAR", "warning") :
|
|
24384
|
+
const mark = unlocked ? badge("DONE", "success") : bank.total_hours >= m.hours * 0.85 ? badge("NEAR", "warning") : chalk62.dim("\u25CB");
|
|
23098
24385
|
const barW = 12;
|
|
23099
|
-
const mBar = unlocked ?
|
|
24386
|
+
const mBar = unlocked ? chalk62.hex("#22c55e")("\u2588".repeat(barW)) : scoreBar(pct, bank.total_hours >= m.hours ? "green" : pct >= 50 ? "yellow" : "red", barW);
|
|
23100
24387
|
const label = `${m.title}`.padEnd(16);
|
|
23101
|
-
console.log(` ${mark} ${
|
|
24388
|
+
console.log(` ${mark} ${chalk62.dim(label)} ${mBar} ${chalk62.dim(`${m.hours}h`)}`);
|
|
23102
24389
|
}
|
|
23103
24390
|
console.log();
|
|
23104
24391
|
}
|
|
@@ -23120,17 +24407,17 @@ var init_progress_report = __esm({
|
|
|
23120
24407
|
// src/commands/progress.ts
|
|
23121
24408
|
var progress_exports = {};
|
|
23122
24409
|
__export(progress_exports, {
|
|
23123
|
-
handler: () =>
|
|
24410
|
+
handler: () => handler45
|
|
23124
24411
|
});
|
|
23125
|
-
import
|
|
24412
|
+
import chalk63 from "chalk";
|
|
23126
24413
|
function printProgressResetPreamble() {
|
|
23127
24414
|
console.log();
|
|
23128
|
-
console.log(" " +
|
|
23129
|
-
console.log(" " +
|
|
23130
|
-
console.log(" " +
|
|
23131
|
-
console.log(" " +
|
|
24415
|
+
console.log(" " + chalk63.yellow.bold("This will permanently remove:"));
|
|
24416
|
+
console.log(" " + chalk63.dim(" \u2022 Hours saved and milestone unlocks"));
|
|
24417
|
+
console.log(" " + chalk63.dim(" \u2022 Usage counters and weekly activity rollups"));
|
|
24418
|
+
console.log(" " + chalk63.dim(" \u2022 Credit history used for dedup"));
|
|
23132
24419
|
console.log();
|
|
23133
|
-
console.log(" " +
|
|
24420
|
+
console.log(" " + chalk63.dim("Preserved: install identity (install.json)"));
|
|
23134
24421
|
console.log();
|
|
23135
24422
|
}
|
|
23136
24423
|
function showProgress() {
|
|
@@ -23148,7 +24435,7 @@ async function handleReset(ctx, confirmedFlag) {
|
|
|
23148
24435
|
const bank = getTimeBankSummary();
|
|
23149
24436
|
if (bank.total_minutes <= 0) {
|
|
23150
24437
|
console.log();
|
|
23151
|
-
console.log(" " +
|
|
24438
|
+
console.log(" " + chalk63.dim("No progress to reset."));
|
|
23152
24439
|
console.log();
|
|
23153
24440
|
return "No progress to reset";
|
|
23154
24441
|
}
|
|
@@ -23165,11 +24452,11 @@ async function handleReset(ctx, confirmedFlag) {
|
|
|
23165
24452
|
}
|
|
23166
24453
|
resetProgress();
|
|
23167
24454
|
console.log();
|
|
23168
|
-
console.log(" " + paint("accent", "\u2713 Progress reset") +
|
|
24455
|
+
console.log(" " + paint("accent", "\u2713 Progress reset") + chalk63.dim(" \u2014 hours and milestones cleared."));
|
|
23169
24456
|
console.log();
|
|
23170
24457
|
return "Progress reset";
|
|
23171
24458
|
}
|
|
23172
|
-
async function
|
|
24459
|
+
async function handler45(args, ctx) {
|
|
23173
24460
|
const { positional, flags } = parseArgs(args, ["confirm"]);
|
|
23174
24461
|
const sub = positional[0]?.toLowerCase();
|
|
23175
24462
|
if (sub === "reset") {
|
|
@@ -23177,7 +24464,7 @@ async function handler44(args, ctx) {
|
|
|
23177
24464
|
}
|
|
23178
24465
|
if (sub && sub !== "reset") {
|
|
23179
24466
|
console.log();
|
|
23180
|
-
console.log(" " +
|
|
24467
|
+
console.log(" " + chalk63.dim("Unknown subcommand. Try ") + paint("accent", "/progress") + chalk63.dim(" or ") + paint("accent", "/progress reset") + chalk63.dim("."));
|
|
23181
24468
|
console.log();
|
|
23182
24469
|
return;
|
|
23183
24470
|
}
|
|
@@ -23204,8 +24491,8 @@ init_time_milestones();
|
|
|
23204
24491
|
init_time_perspectives();
|
|
23205
24492
|
init_time_bank();
|
|
23206
24493
|
init_perspective_rotation();
|
|
23207
|
-
import { existsSync as
|
|
23208
|
-
import { join as
|
|
24494
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync13, mkdtempSync, rmSync as rmSync4, writeFileSync as writeFileSync20 } from "fs";
|
|
24495
|
+
import { join as join30 } from "path";
|
|
23209
24496
|
import { tmpdir } from "os";
|
|
23210
24497
|
|
|
23211
24498
|
// src/workflows/registry.ts
|
|
@@ -23259,10 +24546,10 @@ async function resolveHandler(name) {
|
|
|
23259
24546
|
try {
|
|
23260
24547
|
const mod = await importHandler(runtimePath);
|
|
23261
24548
|
if (!mod) return null;
|
|
23262
|
-
const
|
|
23263
|
-
if (typeof
|
|
23264
|
-
entry.handler =
|
|
23265
|
-
return
|
|
24549
|
+
const handler46 = mod.handler;
|
|
24550
|
+
if (typeof handler46 !== "function") return null;
|
|
24551
|
+
entry.handler = handler46;
|
|
24552
|
+
return handler46;
|
|
23266
24553
|
} catch (err) {
|
|
23267
24554
|
console.error(`Failed to load handler for /${name}:`, err);
|
|
23268
24555
|
return null;
|
|
@@ -23348,6 +24635,8 @@ async function importHandler(runtimePath) {
|
|
|
23348
24635
|
return Promise.resolve().then(() => (init_switch(), switch_exports));
|
|
23349
24636
|
case "../commands/backmeup.js":
|
|
23350
24637
|
return Promise.resolve().then(() => (init_backmeup(), backmeup_exports));
|
|
24638
|
+
case "../commands/connect.js":
|
|
24639
|
+
return Promise.resolve().then(() => (init_connect2(), connect_exports2));
|
|
23351
24640
|
case "../commands/provider.js":
|
|
23352
24641
|
return Promise.resolve().then(() => (init_provider(), provider_exports));
|
|
23353
24642
|
case "../commands/tier.js":
|
|
@@ -23470,7 +24759,9 @@ handler: ../commands/setup.ts
|
|
|
23470
24759
|
|
|
23471
24760
|
Validate local readiness or configure NTRP non-interactively for automation.
|
|
23472
24761
|
\`setup check --json\` reports license, profile, API key, database, and writable
|
|
23473
|
-
directory state. \`setup agent\` accepts a profile JSON file or direct flags
|
|
24762
|
+
directory state. \`setup agent\` accepts a profile JSON file or direct flags \u2014
|
|
24763
|
+
\`--llm-key <key>\` auto-detects the provider from any pasted key
|
|
24764
|
+
(\`--llm-provider <id>\` to force one).`
|
|
23474
24765
|
},
|
|
23475
24766
|
{
|
|
23476
24767
|
name: "update",
|
|
@@ -23906,6 +25197,25 @@ handler: ../commands/profile.ts
|
|
|
23906
25197
|
|
|
23907
25198
|
Choose a sales motion preset (PLG, SMB Velocity, Mid-Market, Enterprise). Each
|
|
23908
25199
|
preset adjusts the vital-sign thresholds to match your deal cycle.`
|
|
25200
|
+
},
|
|
25201
|
+
{
|
|
25202
|
+
name: "connect",
|
|
25203
|
+
raw: `---
|
|
25204
|
+
name: connect
|
|
25205
|
+
description: Connect an AI provider (paste any key)
|
|
25206
|
+
section: Settings
|
|
25207
|
+
args: [provider] [--key <key>] [--base-url <url> --id <name>]
|
|
25208
|
+
handler: ../commands/connect.ts
|
|
25209
|
+
---
|
|
25210
|
+
|
|
25211
|
+
Paste any provider's API key \u2014 NTRP identifies the provider from the key
|
|
25212
|
+
format (probing ambiguous ones), validates it, discovers which models the key
|
|
25213
|
+
can use, and builds the HIGH/MEDIUM/LOW tier stack automatically.
|
|
25214
|
+
|
|
25215
|
+
Works with Anthropic, OpenAI, Google Gemini, Groq, Mistral, DeepSeek, xAI,
|
|
25216
|
+
OpenRouter, Together, and Fireworks out of the box. \`/connect ollama\` wires a
|
|
25217
|
+
local Ollama; \`/connect --base-url <url> --id <name>\` registers any other
|
|
25218
|
+
OpenAI-compatible endpoint.`
|
|
23909
25219
|
},
|
|
23910
25220
|
{
|
|
23911
25221
|
name: "config",
|
|
@@ -23918,10 +25228,12 @@ handler: ../commands/config.ts
|
|
|
23918
25228
|
---
|
|
23919
25229
|
|
|
23920
25230
|
Manage CLI configuration stored at \`~/.ntrp/config.json\`. Useful keys:
|
|
23921
|
-
\`api-key\` (Anthropic), \`openai-api-key\`, \`
|
|
23922
|
-
\`llm-tier\`, \`llm-auto-failover\`,
|
|
25231
|
+
\`api-key\` (Anthropic), \`openai-api-key\` (and \`groq-api-key\`, \`google-api-key\`, ...),
|
|
25232
|
+
\`llm-primary\` (default engine), \`llm-tier\`, \`llm-auto-failover\`,
|
|
25233
|
+
\`default-format\`, \`export-dir\`.
|
|
23923
25234
|
|
|
23924
|
-
|
|
25235
|
+
Setting a provider key opens a hidden prompt and auto-discovers that
|
|
25236
|
+
provider's models. Prefer \`/connect\` \u2014 it detects the provider for you.`
|
|
23925
25237
|
},
|
|
23926
25238
|
{
|
|
23927
25239
|
name: "provider",
|
|
@@ -23929,13 +25241,14 @@ Manage CLI configuration stored at \`~/.ntrp/config.json\`. Useful keys:
|
|
|
23929
25241
|
name: provider
|
|
23930
25242
|
description: Switch active LLM engine
|
|
23931
25243
|
section: Settings
|
|
23932
|
-
args: [
|
|
25244
|
+
args: [<id>|list|reset|save|failover on|off]
|
|
23933
25245
|
handler: ../commands/provider.ts
|
|
23934
25246
|
---
|
|
23935
25247
|
|
|
23936
|
-
Choose which engine answers this session \u2014
|
|
23937
|
-
|
|
23938
|
-
|
|
25248
|
+
Choose which connected engine answers this session \u2014 any provider added via
|
|
25249
|
+
\`/connect\` (anthropic, openai, groq, google, ollama, custom endpoints, ...).
|
|
25250
|
+
Session-scoped by default; \`/provider save\` writes the default to config.
|
|
25251
|
+
\`/provider failover on\` enables rate-limit auto-failover.`
|
|
23939
25252
|
},
|
|
23940
25253
|
{
|
|
23941
25254
|
name: "tier",
|
|
@@ -23957,12 +25270,14 @@ active stack. Add \`--default\` to persist to config.`
|
|
|
23957
25270
|
name: model
|
|
23958
25271
|
description: Override the active LLM model
|
|
23959
25272
|
section: Settings
|
|
23960
|
-
args: [set <id>|clear] [--default]
|
|
25273
|
+
args: [list|set <id>|refresh|clear] [--default]
|
|
23961
25274
|
handler: ../commands/model.ts
|
|
23962
25275
|
---
|
|
23963
25276
|
|
|
23964
|
-
|
|
23965
|
-
|
|
25277
|
+
\`/model list\` shows the models discovered for the active engine with their
|
|
25278
|
+
tier assignments. \`/model refresh\` re-discovers the live list. \`/model set <id>\`
|
|
25279
|
+
pins a model on the **active engine**; cross-provider IDs are rejected \u2014
|
|
25280
|
+
switch with \`/provider\` first.`
|
|
23966
25281
|
},
|
|
23967
25282
|
{
|
|
23968
25283
|
name: "activate",
|
|
@@ -24032,7 +25347,7 @@ function mockCtx(sessionId = "2026-06-21-test") {
|
|
|
24032
25347
|
};
|
|
24033
25348
|
}
|
|
24034
25349
|
function withTempHome(run2) {
|
|
24035
|
-
const dir = mkdtempSync(
|
|
25350
|
+
const dir = mkdtempSync(join30(tmpdir(), "ntrp-time-bank-"));
|
|
24036
25351
|
const prev = process.env.NTRP_HOME;
|
|
24037
25352
|
process.env.NTRP_HOME = dir;
|
|
24038
25353
|
try {
|
|
@@ -24051,7 +25366,7 @@ function withTempHome(run2) {
|
|
|
24051
25366
|
}
|
|
24052
25367
|
}
|
|
24053
25368
|
async function withTempHomeAsync(run2) {
|
|
24054
|
-
const dir = mkdtempSync(
|
|
25369
|
+
const dir = mkdtempSync(join30(tmpdir(), "ntrp-time-bank-"));
|
|
24055
25370
|
const prev = process.env.NTRP_HOME;
|
|
24056
25371
|
process.env.NTRP_HOME = dir;
|
|
24057
25372
|
try {
|
|
@@ -24172,7 +25487,7 @@ function testUsageBackfillFromCredits() {
|
|
|
24172
25487
|
withTempHome(() => {
|
|
24173
25488
|
const at = "2026-06-01T12:00:00.000Z";
|
|
24174
25489
|
const installId = loadProgress().install_id;
|
|
24175
|
-
|
|
25490
|
+
writeFileSync20(join30(ntrpHome(), "progress.json"), JSON.stringify({
|
|
24176
25491
|
schema_version: 2,
|
|
24177
25492
|
install_id: installId,
|
|
24178
25493
|
total_minutes_saved: 30,
|
|
@@ -24187,14 +25502,14 @@ function testUsageBackfillFromCredits() {
|
|
|
24187
25502
|
function testInstallCreatedOnFirstLoad() {
|
|
24188
25503
|
withTempHome(() => {
|
|
24189
25504
|
loadProgress();
|
|
24190
|
-
assert(
|
|
24191
|
-
assert(
|
|
25505
|
+
assert(existsSync23(join30(ntrpHome(), "install.json")), "install.json created");
|
|
25506
|
+
assert(existsSync23(join30(ntrpHome(), "progress.json")), "progress.json created");
|
|
24192
25507
|
});
|
|
24193
25508
|
}
|
|
24194
25509
|
function testLegacyStateMigration() {
|
|
24195
25510
|
withTempHome(() => {
|
|
24196
25511
|
mkdirSync13(ntrpHome(), { recursive: true });
|
|
24197
|
-
|
|
25512
|
+
writeFileSync20(join30(ntrpHome(), "state.json"), JSON.stringify({
|
|
24198
25513
|
schema_version: 1,
|
|
24199
25514
|
total_minutes_saved: 45,
|
|
24200
25515
|
credits: [{ action: "onboard", minutes: 45, at: "2026-06-01T12:00:00.000Z" }],
|
|
@@ -24202,8 +25517,8 @@ function testLegacyStateMigration() {
|
|
|
24202
25517
|
}));
|
|
24203
25518
|
const state = loadProgress();
|
|
24204
25519
|
assert(state.total_minutes_saved === 45, "legacy migration preserves hours");
|
|
24205
|
-
assert(
|
|
24206
|
-
assert(!
|
|
25520
|
+
assert(existsSync23(join30(ntrpHome(), "progress.json")), "progress.json created from legacy");
|
|
25521
|
+
assert(!existsSync23(join30(ntrpHome(), "state.json")), "legacy state.json moved aside");
|
|
24207
25522
|
assert(state.install_id === getInstallId(), "install_id attached on migration");
|
|
24208
25523
|
});
|
|
24209
25524
|
}
|
|
@@ -24214,7 +25529,7 @@ async function testProgressSurvivesScratchWipe() {
|
|
|
24214
25529
|
assert(loadProgress().total_minutes_saved === 180, "pre-scratch credits");
|
|
24215
25530
|
await performScratchWipe();
|
|
24216
25531
|
assert(loadProgress().total_minutes_saved === 180, "post-scratch credits preserved");
|
|
24217
|
-
assert(
|
|
25532
|
+
assert(existsSync23(join30(ntrpHome(), "install.json")), "install survives scratch");
|
|
24218
25533
|
});
|
|
24219
25534
|
}
|
|
24220
25535
|
function testInstallIdStable() {
|
|
@@ -24234,7 +25549,7 @@ function testResetProgressKeepsInstall() {
|
|
|
24234
25549
|
resetProgress();
|
|
24235
25550
|
assert(loadProgress().total_minutes_saved === 0, "reset should clear hours");
|
|
24236
25551
|
assert(getInstallId() === installId, "reset should keep install_id");
|
|
24237
|
-
assert(
|
|
25552
|
+
assert(existsSync23(join30(ntrpHome(), "install.json")), "install.json should remain");
|
|
24238
25553
|
});
|
|
24239
25554
|
}
|
|
24240
25555
|
async function testScratchIncludeProgressWipesHours() {
|
|
@@ -24243,7 +25558,7 @@ async function testScratchIncludeProgressWipesHours() {
|
|
|
24243
25558
|
recordTimeCredit("diagnose", ctx, { silent: true });
|
|
24244
25559
|
const installId = getInstallId();
|
|
24245
25560
|
await performScratchWipe({ includeProgress: true });
|
|
24246
|
-
assert(!
|
|
25561
|
+
assert(!existsSync23(join30(ntrpHome(), "install.json")), "include-progress should remove install.json");
|
|
24247
25562
|
const state = loadProgress();
|
|
24248
25563
|
assert(state.total_minutes_saved === 0, "include-progress should clear hours");
|
|
24249
25564
|
assert(state.install_id !== installId, "include-progress should issue new install_id");
|
|
@@ -24267,8 +25582,8 @@ testInstallIdStable();
|
|
|
24267
25582
|
testResetProgressKeepsInstall();
|
|
24268
25583
|
async function testProgressCommandRegistered() {
|
|
24269
25584
|
assert(hasCommand("progress"), "/progress should be in workflow registry");
|
|
24270
|
-
const
|
|
24271
|
-
assert(typeof
|
|
25585
|
+
const handler46 = await resolveHandler("progress");
|
|
25586
|
+
assert(typeof handler46 === "function", "progress handler should load from importHandler map");
|
|
24272
25587
|
}
|
|
24273
25588
|
await testProgressSurvivesScratchWipe();
|
|
24274
25589
|
await testScratchIncludeProgressWipesHours();
|