@sonnechasser/ntrp 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/dist/index.js +1986 -596
- package/dist/index.js.map +1 -1
- package/dist/investigation/verbosity-cli.js +897 -202
- package/dist/investigation/verbosity-cli.js.map +1 -1
- package/dist/mcp/server.js +922 -222
- package/dist/mcp/server.js.map +1 -1
- package/dist/whimsy/time-bank-smoke.js +1929 -501
- package/dist/whimsy/time-bank-smoke.js.map +1 -1
- package/package.json +2 -1
|
@@ -221,12 +221,15 @@ function getInstallId() {
|
|
|
221
221
|
return ensureInstall().install_id;
|
|
222
222
|
}
|
|
223
223
|
function invalidateInstall() {
|
|
224
|
-
|
|
224
|
+
clearInstallCache();
|
|
225
225
|
const path = installPath();
|
|
226
226
|
if (existsSync2(path)) {
|
|
227
227
|
unlinkSync(path);
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
|
+
function clearInstallCache() {
|
|
231
|
+
cachedInstall = null;
|
|
232
|
+
}
|
|
230
233
|
var cachedInstall;
|
|
231
234
|
var init_install = __esm({
|
|
232
235
|
"src/config/install.ts"() {
|
|
@@ -356,7 +359,7 @@ function migrateUsageIfNeeded(state) {
|
|
|
356
359
|
if (state.usage?.first_active_at) return { state, changed: false };
|
|
357
360
|
const fromCredits = rebuildFromCredits(state.credits);
|
|
358
361
|
const prior = state.usage;
|
|
359
|
-
const
|
|
362
|
+
const usage3 = {
|
|
360
363
|
sessions_closed: prior?.sessions_closed ?? 0,
|
|
361
364
|
llm_calls: prior?.llm_calls ?? 0,
|
|
362
365
|
input_tokens: prior?.input_tokens ?? 0,
|
|
@@ -364,7 +367,7 @@ function migrateUsageIfNeeded(state) {
|
|
|
364
367
|
...fromCredits,
|
|
365
368
|
weekly: mergeWeekly(prior?.weekly ?? [], fromCredits.weekly)
|
|
366
369
|
};
|
|
367
|
-
return { state: { ...state, usage:
|
|
370
|
+
return { state: { ...state, usage: usage3 }, changed: true };
|
|
368
371
|
}
|
|
369
372
|
var init_usage_backfill = __esm({
|
|
370
373
|
"src/whimsy/usage-backfill.ts"() {
|
|
@@ -465,12 +468,20 @@ function saveProgress(state) {
|
|
|
465
468
|
function invalidateProgress() {
|
|
466
469
|
installMismatchWarned = false;
|
|
467
470
|
invalidateInstall();
|
|
471
|
+
wipeProgressFiles();
|
|
472
|
+
}
|
|
473
|
+
function wipeProgressFiles() {
|
|
474
|
+
installMismatchWarned = false;
|
|
468
475
|
for (const path of [progressPath2(), legacyStatePath2(), legacyStateBackupPath2()]) {
|
|
469
476
|
if (existsSync4(path)) {
|
|
470
477
|
unlinkSync2(path);
|
|
471
478
|
}
|
|
472
479
|
}
|
|
473
480
|
}
|
|
481
|
+
function resetProgress() {
|
|
482
|
+
ensureInstall();
|
|
483
|
+
wipeProgressFiles();
|
|
484
|
+
}
|
|
474
485
|
function appendCredit(state, credit) {
|
|
475
486
|
const credits = [...state.credits, credit];
|
|
476
487
|
if (credits.length > CREDIT_HISTORY_CAP) {
|
|
@@ -678,7 +689,7 @@ var init_connection = __esm({
|
|
|
678
689
|
// src/services/scratch-wipe.ts
|
|
679
690
|
import { existsSync as existsSync6, rmSync as rmSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
680
691
|
import { join as join6 } from "path";
|
|
681
|
-
async function performScratchWipe() {
|
|
692
|
+
async function performScratchWipe(opts = {}) {
|
|
682
693
|
const home = ntrpHome();
|
|
683
694
|
const removed = [];
|
|
684
695
|
try {
|
|
@@ -697,6 +708,13 @@ async function performScratchWipe() {
|
|
|
697
708
|
{ path: join6(home, "sessions"), kind: "dir" },
|
|
698
709
|
{ path: join6(home, "datasets"), kind: "dir" }
|
|
699
710
|
];
|
|
711
|
+
if (opts.includeProgress) {
|
|
712
|
+
targets.push(
|
|
713
|
+
{ path: join6(home, "install.json"), kind: "file" },
|
|
714
|
+
{ path: join6(home, "progress.json"), kind: "file" },
|
|
715
|
+
{ path: join6(home, "state.json"), kind: "file" }
|
|
716
|
+
);
|
|
717
|
+
}
|
|
700
718
|
for (const { path, kind } of targets) {
|
|
701
719
|
if (!existsSync6(path)) continue;
|
|
702
720
|
try {
|
|
@@ -709,12 +727,18 @@ async function performScratchWipe() {
|
|
|
709
727
|
} catch {
|
|
710
728
|
}
|
|
711
729
|
}
|
|
730
|
+
if (opts.includeProgress) {
|
|
731
|
+
wipeProgressFiles();
|
|
732
|
+
clearInstallCache();
|
|
733
|
+
}
|
|
712
734
|
resetConfigCache();
|
|
713
735
|
return { removed };
|
|
714
736
|
}
|
|
715
737
|
var init_scratch_wipe = __esm({
|
|
716
738
|
"src/services/scratch-wipe.ts"() {
|
|
717
739
|
"use strict";
|
|
740
|
+
init_install();
|
|
741
|
+
init_progress();
|
|
718
742
|
init_store();
|
|
719
743
|
}
|
|
720
744
|
});
|
|
@@ -1188,48 +1212,48 @@ function bumpWeekly2(weekly, patch) {
|
|
|
1188
1212
|
}
|
|
1189
1213
|
function touchUsage(state, patch) {
|
|
1190
1214
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
1191
|
-
const
|
|
1215
|
+
const usage3 = ensureUsage(state);
|
|
1192
1216
|
return {
|
|
1193
1217
|
...state,
|
|
1194
1218
|
usage: {
|
|
1195
|
-
...
|
|
1219
|
+
...usage3,
|
|
1196
1220
|
...patch,
|
|
1197
|
-
first_active_at:
|
|
1221
|
+
first_active_at: usage3.first_active_at ?? now2,
|
|
1198
1222
|
last_active_at: now2,
|
|
1199
|
-
weekly: patch.weekly ??
|
|
1223
|
+
weekly: patch.weekly ?? usage3.weekly
|
|
1200
1224
|
}
|
|
1201
1225
|
};
|
|
1202
1226
|
}
|
|
1203
1227
|
function recordUsageFromCredit(action, minutes) {
|
|
1204
1228
|
if (minutes <= 0) return;
|
|
1205
1229
|
let state = loadProgress();
|
|
1206
|
-
const
|
|
1207
|
-
const weekly = bumpWeekly2(
|
|
1230
|
+
const usage3 = ensureUsage(state);
|
|
1231
|
+
const weekly = bumpWeekly2(usage3.weekly, { minutes_saved: minutes, actions: 1 });
|
|
1208
1232
|
const counters = { weekly };
|
|
1209
|
-
if (action === "diagnose" || action === "diagnose_findings") counters.diagnoses =
|
|
1210
|
-
if (action === "metrics" || action === "metrics_findings") counters.metrics_runs =
|
|
1211
|
-
if (action === "deliverable" || action === "deliverable_deck") counters.deliverables =
|
|
1212
|
-
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;
|
|
1213
1237
|
state = touchUsage(state, counters);
|
|
1214
1238
|
saveProgress(state);
|
|
1215
1239
|
}
|
|
1216
1240
|
function recordSessionClosed() {
|
|
1217
1241
|
let state = loadProgress();
|
|
1218
|
-
const
|
|
1242
|
+
const usage3 = ensureUsage(state);
|
|
1219
1243
|
state = touchUsage(state, {
|
|
1220
|
-
sessions_closed:
|
|
1221
|
-
weekly: bumpWeekly2(
|
|
1244
|
+
sessions_closed: usage3.sessions_closed + 1,
|
|
1245
|
+
weekly: bumpWeekly2(usage3.weekly, { actions: 1 })
|
|
1222
1246
|
});
|
|
1223
1247
|
saveProgress(state);
|
|
1224
1248
|
}
|
|
1225
1249
|
function recordLlmUsage(tokenUsage) {
|
|
1226
1250
|
let state = loadProgress();
|
|
1227
|
-
const
|
|
1228
|
-
const weekly = bumpWeekly2(
|
|
1251
|
+
const usage3 = ensureUsage(state);
|
|
1252
|
+
const weekly = bumpWeekly2(usage3.weekly, { llm_calls: 1 });
|
|
1229
1253
|
state = touchUsage(state, {
|
|
1230
|
-
llm_calls:
|
|
1231
|
-
input_tokens:
|
|
1232
|
-
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),
|
|
1233
1257
|
weekly
|
|
1234
1258
|
});
|
|
1235
1259
|
saveProgress(state);
|
|
@@ -6615,55 +6639,108 @@ var init_markdown = __esm({
|
|
|
6615
6639
|
}
|
|
6616
6640
|
});
|
|
6617
6641
|
|
|
6618
|
-
// src/ai/llm/
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
if (seen.has(current)) break;
|
|
6631
|
-
seen.add(current);
|
|
6632
|
-
const entry = byId.get(current);
|
|
6633
|
-
if (!entry) return current;
|
|
6634
|
-
if (entry.status === "active") return entry.id;
|
|
6635
|
-
if (!entry.successor_id) {
|
|
6636
|
-
const fallback = cheapestActiveInTier(entry.provider, entry.tier);
|
|
6637
|
-
return fallback?.id ?? current;
|
|
6638
|
-
}
|
|
6639
|
-
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;
|
|
6640
6654
|
}
|
|
6641
|
-
|
|
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];
|
|
6642
6669
|
}
|
|
6643
|
-
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) {
|
|
6644
6720
|
const candidates = ENTRIES.filter(
|
|
6645
6721
|
(e) => e.provider === provider && e.tier === tier && e.status === "active"
|
|
6646
6722
|
);
|
|
6647
6723
|
if (candidates.length === 0) return void 0;
|
|
6648
6724
|
return candidates.sort((a, b) => a.relative_cost - b.relative_cost)[0];
|
|
6649
6725
|
}
|
|
6650
|
-
function
|
|
6651
|
-
|
|
6652
|
-
if (!entry) {
|
|
6653
|
-
throw new Error(`No active ${tier}-tier model for provider ${provider} in catalog`);
|
|
6654
|
-
}
|
|
6655
|
-
return entry;
|
|
6726
|
+
function modelProviderHint(modelId) {
|
|
6727
|
+
return cachedModelProvider(modelId) ?? byId.get(modelId)?.provider;
|
|
6656
6728
|
}
|
|
6657
|
-
function
|
|
6658
|
-
if (override)
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
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;
|
|
6665
6740
|
}
|
|
6666
6741
|
function formatModelLabel(provider, modelId) {
|
|
6742
|
+
const cachedName = findCachedModel(provider, modelId)?.display_name;
|
|
6743
|
+
if (cachedName) return `${provider}/${cachedName}`;
|
|
6667
6744
|
const entry = byId.get(modelId);
|
|
6668
6745
|
return entry ? `${provider}/${entry.display_name}` : `${provider}/${modelId}`;
|
|
6669
6746
|
}
|
|
@@ -6671,6 +6748,7 @@ var ENTRIES, byId;
|
|
|
6671
6748
|
var init_catalog = __esm({
|
|
6672
6749
|
"src/ai/llm/catalog.ts"() {
|
|
6673
6750
|
"use strict";
|
|
6751
|
+
init_models_cache();
|
|
6674
6752
|
ENTRIES = [
|
|
6675
6753
|
{
|
|
6676
6754
|
id: "claude-opus-4-6",
|
|
@@ -6755,6 +6833,9 @@ function formatLlmAttribution(meta) {
|
|
|
6755
6833
|
return line;
|
|
6756
6834
|
}
|
|
6757
6835
|
function printLlmAttribution(meta) {
|
|
6836
|
+
for (const notice of meta.notices ?? []) {
|
|
6837
|
+
console.log(chalk6.dim(` ${notice}`));
|
|
6838
|
+
}
|
|
6758
6839
|
const line = formatLlmAttribution(meta);
|
|
6759
6840
|
if (line) console.log(chalk6.dim(` ${line}`));
|
|
6760
6841
|
}
|
|
@@ -7072,6 +7153,7 @@ async function renderDiagnoseStream(options) {
|
|
|
7072
7153
|
let modelUsed = "";
|
|
7073
7154
|
let providerUsed;
|
|
7074
7155
|
let failover;
|
|
7156
|
+
let notices;
|
|
7075
7157
|
let rawPrompt = "";
|
|
7076
7158
|
try {
|
|
7077
7159
|
for await (const event of runFindings(fullResult)) {
|
|
@@ -7087,6 +7169,7 @@ async function renderDiagnoseStream(options) {
|
|
|
7087
7169
|
modelUsed = event.model_used;
|
|
7088
7170
|
providerUsed = event.provider_used;
|
|
7089
7171
|
failover = event.failover;
|
|
7172
|
+
notices = event.usage?.notices;
|
|
7090
7173
|
rawPrompt = event.raw_prompt;
|
|
7091
7174
|
}
|
|
7092
7175
|
}
|
|
@@ -7110,7 +7193,8 @@ async function renderDiagnoseStream(options) {
|
|
|
7110
7193
|
printLlmAttribution({
|
|
7111
7194
|
model_used: modelUsed,
|
|
7112
7195
|
provider_used: providerUsed,
|
|
7113
|
-
failover
|
|
7196
|
+
failover,
|
|
7197
|
+
notices
|
|
7114
7198
|
});
|
|
7115
7199
|
} catch (err) {
|
|
7116
7200
|
findingsSpinner.fail(deep ? "Agentic investigation failed" : "AI findings failed");
|
|
@@ -7165,10 +7249,232 @@ var init_terminal = __esm({
|
|
|
7165
7249
|
}
|
|
7166
7250
|
});
|
|
7167
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
|
+
|
|
7168
7473
|
// src/config/llm-config.ts
|
|
7169
7474
|
function parseProvider(raw) {
|
|
7170
|
-
if (raw
|
|
7171
|
-
|
|
7475
|
+
if (!raw?.trim()) return void 0;
|
|
7476
|
+
const id = raw.trim();
|
|
7477
|
+
return getProviderSpec(id) ? id : void 0;
|
|
7172
7478
|
}
|
|
7173
7479
|
function parseTier(raw) {
|
|
7174
7480
|
if (raw === "high" || raw === "medium" || raw === "low") return raw;
|
|
@@ -7176,7 +7482,7 @@ function parseTier(raw) {
|
|
|
7176
7482
|
}
|
|
7177
7483
|
function parseFailoverOrder(raw) {
|
|
7178
7484
|
if (!raw?.trim()) return ["openai"];
|
|
7179
|
-
return raw.split(",").map((s) => s.trim()).filter((s) => s
|
|
7485
|
+
return raw.split(",").map((s) => s.trim()).filter((s) => !!s && !!getProviderSpec(s));
|
|
7180
7486
|
}
|
|
7181
7487
|
function parseAutoFailover(raw) {
|
|
7182
7488
|
if (!raw) return false;
|
|
@@ -7191,30 +7497,42 @@ function getOpenAiApiKey() {
|
|
|
7191
7497
|
if (fromConfig) return fromConfig;
|
|
7192
7498
|
return process.env.OPENAI_API_KEY?.trim() || void 0;
|
|
7193
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
|
+
}
|
|
7194
7512
|
function hasProviderKey(provider) {
|
|
7195
|
-
|
|
7196
|
-
|
|
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);
|
|
7197
7517
|
}
|
|
7198
7518
|
function getAvailableProviders() {
|
|
7199
|
-
|
|
7200
|
-
if (hasProviderKey("anthropic")) out.push("anthropic");
|
|
7201
|
-
if (hasProviderKey("openai")) out.push("openai");
|
|
7202
|
-
return out;
|
|
7519
|
+
return listProviderSpecs().filter((s) => hasProviderKey(s.id)).map((s) => s.id);
|
|
7203
7520
|
}
|
|
7204
7521
|
function hasAnyLlmProvider() {
|
|
7205
7522
|
return getAvailableProviders().length > 0;
|
|
7206
7523
|
}
|
|
7524
|
+
function hasKeylessConfiguredProvider() {
|
|
7525
|
+
return listProviderSpecs().some((s) => !s.requires_key && hasProviderKey(s.id));
|
|
7526
|
+
}
|
|
7207
7527
|
function applyLazyMigration(config) {
|
|
7208
7528
|
if (migrated) return;
|
|
7209
7529
|
migrated = true;
|
|
7210
7530
|
let changed = false;
|
|
7211
7531
|
const record = config;
|
|
7212
7532
|
if (!record["llm-primary"]) {
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
} else if (record["openai-api-key"] || process.env.OPENAI_API_KEY) {
|
|
7217
|
-
record["llm-primary"] = "openai";
|
|
7533
|
+
const available = getAvailableProviders();
|
|
7534
|
+
if (available.length > 0) {
|
|
7535
|
+
record["llm-primary"] = available[0];
|
|
7218
7536
|
changed = true;
|
|
7219
7537
|
}
|
|
7220
7538
|
}
|
|
@@ -7254,20 +7572,20 @@ function loadLlmConfig() {
|
|
|
7254
7572
|
openaiKey: getOpenAiApiKey()
|
|
7255
7573
|
};
|
|
7256
7574
|
}
|
|
7257
|
-
function getProviderApiKey(provider) {
|
|
7258
|
-
if (provider === "anthropic") return getAnthropicApiKey();
|
|
7259
|
-
return getOpenAiApiKey();
|
|
7260
|
-
}
|
|
7261
7575
|
function getInvestigationApiKey(provider) {
|
|
7262
7576
|
if (provider === "anthropic") {
|
|
7263
7577
|
return process.env.NTRP_INVESTIGATION_API_KEY?.trim() || getAnthropicApiKey();
|
|
7264
7578
|
}
|
|
7265
|
-
|
|
7579
|
+
if (provider === "openai") {
|
|
7580
|
+
return process.env.NTRP_INVESTIGATION_OPENAI_KEY?.trim() || getOpenAiApiKey();
|
|
7581
|
+
}
|
|
7582
|
+
return getProviderApiKey(provider);
|
|
7266
7583
|
}
|
|
7267
7584
|
var migrated;
|
|
7268
7585
|
var init_llm_config = __esm({
|
|
7269
7586
|
"src/config/llm-config.ts"() {
|
|
7270
7587
|
"use strict";
|
|
7588
|
+
init_providers();
|
|
7271
7589
|
init_store();
|
|
7272
7590
|
migrated = false;
|
|
7273
7591
|
}
|
|
@@ -7285,7 +7603,13 @@ function resolvePrimaryApiKey(ctx) {
|
|
|
7285
7603
|
if (isInvestigationMode(ctx)) {
|
|
7286
7604
|
return getInvestigationApiKey(primary) ?? getInvestigationApiKey("anthropic") ?? getInvestigationApiKey("openai");
|
|
7287
7605
|
}
|
|
7288
|
-
|
|
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;
|
|
7289
7613
|
}
|
|
7290
7614
|
function canUseReplAi(ctx) {
|
|
7291
7615
|
if (!ctx) return false;
|
|
@@ -7296,25 +7620,21 @@ function canUseReplAi(ctx) {
|
|
|
7296
7620
|
}
|
|
7297
7621
|
function assertReplAi(ctx) {
|
|
7298
7622
|
if (!ctx) {
|
|
7299
|
-
throw new Error(
|
|
7300
|
-
"AI features require stored API keys. Run `ntrp`, then /config set api-key or /config set openai-api-key."
|
|
7301
|
-
);
|
|
7623
|
+
throw new Error(`AI features require stored API keys. Run \`ntrp\`, then /connect.`);
|
|
7302
7624
|
}
|
|
7303
7625
|
if (!canUseReplAi(ctx)) {
|
|
7304
7626
|
if (!hasAnyLlmProvider()) {
|
|
7305
|
-
throw new Error(
|
|
7306
|
-
"No LLM API key configured. Run: /config set api-key (Anthropic) and/or /config set openai-api-key"
|
|
7307
|
-
);
|
|
7627
|
+
throw new Error(NO_KEY_MESSAGE);
|
|
7308
7628
|
}
|
|
7309
7629
|
throw new Error(
|
|
7310
7630
|
"AI features run only in the interactive REPL or headless mode with stored keys."
|
|
7311
7631
|
);
|
|
7312
7632
|
}
|
|
7313
7633
|
const key = resolvePrimaryApiKey(ctx);
|
|
7314
|
-
if (!key) {
|
|
7315
|
-
throw new Error(
|
|
7634
|
+
if (!key && !hasKeylessConfiguredProvider()) {
|
|
7635
|
+
throw new Error(NO_KEY_MESSAGE);
|
|
7316
7636
|
}
|
|
7317
|
-
return key;
|
|
7637
|
+
return key ?? "";
|
|
7318
7638
|
}
|
|
7319
7639
|
function hasEnvApiKeyHint() {
|
|
7320
7640
|
return !!(process.env.ANTHROPIC_API_KEY ?? process.env.NTRP_API_KEY ?? process.env.OPENAI_API_KEY);
|
|
@@ -7327,10 +7647,12 @@ function describeLlmReadiness() {
|
|
|
7327
7647
|
openai: providers.includes("openai")
|
|
7328
7648
|
};
|
|
7329
7649
|
}
|
|
7650
|
+
var NO_KEY_MESSAGE;
|
|
7330
7651
|
var init_gate = __esm({
|
|
7331
7652
|
"src/ai/llm/gate.ts"() {
|
|
7332
7653
|
"use strict";
|
|
7333
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, ...).";
|
|
7334
7656
|
}
|
|
7335
7657
|
});
|
|
7336
7658
|
|
|
@@ -7355,18 +7677,18 @@ var init_repl_api = __esm({
|
|
|
7355
7677
|
});
|
|
7356
7678
|
|
|
7357
7679
|
// src/demo/taxonomy-cache.ts
|
|
7358
|
-
import { readFileSync as
|
|
7680
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, existsSync as existsSync11, mkdirSync as mkdirSync7, unlinkSync as unlinkSync4 } from "fs";
|
|
7359
7681
|
import { homedir as homedir3 } from "os";
|
|
7360
|
-
import { join as
|
|
7682
|
+
import { join as join11 } from "path";
|
|
7361
7683
|
function ensureDir6() {
|
|
7362
|
-
if (!
|
|
7684
|
+
if (!existsSync11(NTRP_DIR4)) {
|
|
7363
7685
|
mkdirSync7(NTRP_DIR4, { recursive: true });
|
|
7364
7686
|
}
|
|
7365
7687
|
}
|
|
7366
7688
|
function loadCachedTaxonomy(profile) {
|
|
7367
|
-
if (!
|
|
7689
|
+
if (!existsSync11(TAXONOMY_PATH)) return null;
|
|
7368
7690
|
try {
|
|
7369
|
-
const parsed = JSON.parse(
|
|
7691
|
+
const parsed = JSON.parse(readFileSync9(TAXONOMY_PATH, "utf-8"));
|
|
7370
7692
|
if (!parsed || typeof parsed !== "object") return null;
|
|
7371
7693
|
if (parsed.profile_updated_at !== profile.updated_at) return null;
|
|
7372
7694
|
return parsed;
|
|
@@ -7376,10 +7698,10 @@ function loadCachedTaxonomy(profile) {
|
|
|
7376
7698
|
}
|
|
7377
7699
|
function saveCachedTaxonomy(taxonomy) {
|
|
7378
7700
|
ensureDir6();
|
|
7379
|
-
|
|
7701
|
+
writeFileSync9(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
|
|
7380
7702
|
}
|
|
7381
7703
|
function invalidateTaxonomy() {
|
|
7382
|
-
if (
|
|
7704
|
+
if (existsSync11(TAXONOMY_PATH)) {
|
|
7383
7705
|
try {
|
|
7384
7706
|
unlinkSync4(TAXONOMY_PATH);
|
|
7385
7707
|
} catch {
|
|
@@ -7390,8 +7712,8 @@ var NTRP_DIR4, TAXONOMY_PATH;
|
|
|
7390
7712
|
var init_taxonomy_cache = __esm({
|
|
7391
7713
|
"src/demo/taxonomy-cache.ts"() {
|
|
7392
7714
|
"use strict";
|
|
7393
|
-
NTRP_DIR4 =
|
|
7394
|
-
TAXONOMY_PATH =
|
|
7715
|
+
NTRP_DIR4 = join11(homedir3(), ".ntrp");
|
|
7716
|
+
TAXONOMY_PATH = join11(NTRP_DIR4, "demo-taxonomy.json");
|
|
7395
7717
|
}
|
|
7396
7718
|
});
|
|
7397
7719
|
|
|
@@ -7416,6 +7738,12 @@ var init_types2 = __esm({
|
|
|
7416
7738
|
});
|
|
7417
7739
|
|
|
7418
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
|
+
}
|
|
7419
7747
|
function mapAnthropicError(err, provider) {
|
|
7420
7748
|
const e = err;
|
|
7421
7749
|
const status = e.status;
|
|
@@ -7433,6 +7761,9 @@ function mapAnthropicError(err, provider) {
|
|
|
7433
7761
|
if (status === 503) {
|
|
7434
7762
|
return new LlmError("OVERLOADED", message, provider, status);
|
|
7435
7763
|
}
|
|
7764
|
+
if (isToolsUnsupportedMessage(message)) {
|
|
7765
|
+
return new LlmError("TOOLS_UNSUPPORTED", message, provider, status);
|
|
7766
|
+
}
|
|
7436
7767
|
if (status === 404 || message.toLowerCase().includes("model")) {
|
|
7437
7768
|
return new LlmError("MODEL_NOT_FOUND", message, provider, status);
|
|
7438
7769
|
}
|
|
@@ -7441,6 +7772,11 @@ function mapAnthropicError(err, provider) {
|
|
|
7441
7772
|
}
|
|
7442
7773
|
return new LlmError("UNKNOWN", message, provider, status);
|
|
7443
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
|
+
}
|
|
7444
7780
|
function mapOpenAiError(err, provider) {
|
|
7445
7781
|
const e = err;
|
|
7446
7782
|
const status = e.status;
|
|
@@ -7455,7 +7791,10 @@ function mapOpenAiError(err, provider) {
|
|
|
7455
7791
|
if (status === 503 || code === "server_error") {
|
|
7456
7792
|
return new LlmError("OVERLOADED", message, provider, status);
|
|
7457
7793
|
}
|
|
7458
|
-
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)) {
|
|
7459
7798
|
return new LlmError("MODEL_NOT_FOUND", message, provider, status);
|
|
7460
7799
|
}
|
|
7461
7800
|
if (code === "context_length_exceeded") {
|
|
@@ -7591,8 +7930,15 @@ var init_anthropic = __esm({
|
|
|
7591
7930
|
}
|
|
7592
7931
|
});
|
|
7593
7932
|
|
|
7594
|
-
// src/ai/llm/adapters/openai.ts
|
|
7933
|
+
// src/ai/llm/adapters/openai-compat.ts
|
|
7595
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
|
+
}
|
|
7596
7942
|
function toOpenAiTools(tools) {
|
|
7597
7943
|
return tools.map((t) => ({
|
|
7598
7944
|
type: "function",
|
|
@@ -7661,9 +8007,8 @@ function parseResponse2(message) {
|
|
|
7661
8007
|
assistant_message: { role: "assistant", content: text, tool_calls }
|
|
7662
8008
|
};
|
|
7663
8009
|
}
|
|
7664
|
-
async function
|
|
7665
|
-
const
|
|
7666
|
-
const client = new OpenAI({ apiKey });
|
|
8010
|
+
async function openaiCompatComplete(provider, baseUrl, apiKey, model, req) {
|
|
8011
|
+
const client = makeClient(apiKey, baseUrl);
|
|
7667
8012
|
try {
|
|
7668
8013
|
const response = await client.chat.completions.create({
|
|
7669
8014
|
model,
|
|
@@ -7673,7 +8018,7 @@ async function openaiComplete(apiKey, model, req) {
|
|
|
7673
8018
|
});
|
|
7674
8019
|
const choice = response.choices[0];
|
|
7675
8020
|
if (!choice?.message) {
|
|
7676
|
-
throw new Error(
|
|
8021
|
+
throw new Error(`${provider} returned no message`);
|
|
7677
8022
|
}
|
|
7678
8023
|
const parsed = parseResponse2(choice.message);
|
|
7679
8024
|
if (response.usage) {
|
|
@@ -7684,15 +8029,11 @@ async function openaiComplete(apiKey, model, req) {
|
|
|
7684
8029
|
}
|
|
7685
8030
|
return parsed;
|
|
7686
8031
|
} catch (err) {
|
|
7687
|
-
if (err instanceof OpenAI.APIError) {
|
|
7688
|
-
throw mapOpenAiError(err, provider);
|
|
7689
|
-
}
|
|
7690
8032
|
throw mapOpenAiError(err, provider);
|
|
7691
8033
|
}
|
|
7692
8034
|
}
|
|
7693
|
-
async function*
|
|
7694
|
-
const
|
|
7695
|
-
const client = new OpenAI({ apiKey });
|
|
8035
|
+
async function* openaiCompatStream(provider, baseUrl, apiKey, model, req) {
|
|
8036
|
+
const client = makeClient(apiKey, baseUrl);
|
|
7696
8037
|
try {
|
|
7697
8038
|
const stream = await client.chat.completions.create({
|
|
7698
8039
|
model,
|
|
@@ -7705,19 +8046,316 @@ async function* openaiStream(apiKey, model, req) {
|
|
|
7705
8046
|
if (delta) yield { type: "text_delta", text: delta };
|
|
7706
8047
|
}
|
|
7707
8048
|
} catch (err) {
|
|
7708
|
-
if (err instanceof OpenAI.APIError) {
|
|
7709
|
-
throw mapOpenAiError(err, provider);
|
|
7710
|
-
}
|
|
7711
8049
|
throw mapOpenAiError(err, provider);
|
|
7712
8050
|
}
|
|
7713
8051
|
}
|
|
7714
|
-
var
|
|
7715
|
-
"src/ai/llm/adapters/openai.ts"() {
|
|
8052
|
+
var init_openai_compat = __esm({
|
|
8053
|
+
"src/ai/llm/adapters/openai-compat.ts"() {
|
|
7716
8054
|
"use strict";
|
|
7717
8055
|
init_errors2();
|
|
7718
8056
|
}
|
|
7719
8057
|
});
|
|
7720
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
|
+
|
|
7721
8359
|
// src/ai/llm/surfaces.ts
|
|
7722
8360
|
function tierForSurface(surface, userTier) {
|
|
7723
8361
|
const spec = SURFACE_SPECS[surface];
|
|
@@ -7745,6 +8383,26 @@ var init_surfaces = __esm({
|
|
|
7745
8383
|
});
|
|
7746
8384
|
|
|
7747
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
|
+
});
|
|
7748
8406
|
function ensureLlmSession(ctx) {
|
|
7749
8407
|
if (!ctx.llm) ctx.llm = {};
|
|
7750
8408
|
return ctx.llm;
|
|
@@ -7769,7 +8427,7 @@ function resolveActiveProvider(ctx) {
|
|
|
7769
8427
|
if (session && hasProviderKey(session)) return session;
|
|
7770
8428
|
const cfg = loadLlmConfig();
|
|
7771
8429
|
if (hasProviderKey(cfg.primary)) return cfg.primary;
|
|
7772
|
-
const available =
|
|
8430
|
+
const available = getAvailableProviders();
|
|
7773
8431
|
if (available.length > 0) return available[0];
|
|
7774
8432
|
return cfg.primary;
|
|
7775
8433
|
}
|
|
@@ -7791,8 +8449,8 @@ function resolveModelForActive(ctx, surface) {
|
|
|
7791
8449
|
const provider = resolveActiveProvider(ctx);
|
|
7792
8450
|
const tier = resolveEffectiveTier(ctx, surface);
|
|
7793
8451
|
const override = resolveEffectiveModelOverride(ctx);
|
|
7794
|
-
const providerOverride = override
|
|
7795
|
-
const modelId =
|
|
8452
|
+
const providerOverride = overrideForProvider(override, provider, provider);
|
|
8453
|
+
const modelId = resolveModelSafe(provider, tier, providerOverride);
|
|
7796
8454
|
return { provider, tier, modelId };
|
|
7797
8455
|
}
|
|
7798
8456
|
function resolveProviderOrder(ctx) {
|
|
@@ -7803,26 +8461,30 @@ function resolveProviderOrder(ctx) {
|
|
|
7803
8461
|
for (const p of cfg.failoverOrder) {
|
|
7804
8462
|
if (p !== active && hasProviderKey(p) && !order.includes(p)) order.push(p);
|
|
7805
8463
|
}
|
|
7806
|
-
for (const p of
|
|
7807
|
-
if (p !== active &&
|
|
8464
|
+
for (const p of getAvailableProviders()) {
|
|
8465
|
+
if (p !== active && !order.includes(p)) order.push(p);
|
|
7808
8466
|
}
|
|
7809
8467
|
return order;
|
|
7810
8468
|
}
|
|
7811
8469
|
function formatActiveStack(ctx, surface = "agentic_investigation") {
|
|
7812
8470
|
const { provider, tier, modelId } = resolveModelForActive(ctx, surface);
|
|
7813
|
-
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}`;
|
|
7814
8476
|
}
|
|
7815
8477
|
function countAvailableEngines() {
|
|
7816
|
-
return
|
|
8478
|
+
return getAvailableProviders().length;
|
|
7817
8479
|
}
|
|
7818
8480
|
function availableEngineLabels() {
|
|
7819
|
-
return
|
|
8481
|
+
return getAvailableProviders();
|
|
7820
8482
|
}
|
|
7821
8483
|
function validateModelForProvider(modelId, provider) {
|
|
7822
|
-
const
|
|
7823
|
-
if (!
|
|
7824
|
-
if (
|
|
7825
|
-
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.`;
|
|
7826
8488
|
}
|
|
7827
8489
|
return null;
|
|
7828
8490
|
}
|
|
@@ -7836,23 +8498,16 @@ var init_session_state = __esm({
|
|
|
7836
8498
|
});
|
|
7837
8499
|
|
|
7838
8500
|
// src/ai/llm/resolver.ts
|
|
7839
|
-
function getProviderOrder(config, ctx) {
|
|
7840
|
-
void config;
|
|
7841
|
-
return resolveProviderOrder(ctx);
|
|
7842
|
-
}
|
|
7843
8501
|
function resolveCompletionContext(surface, opts = {}) {
|
|
7844
8502
|
const activeProvider = resolveActiveProvider(opts.ctx);
|
|
7845
8503
|
const tier = opts.tier ?? resolveEffectiveTier(opts.ctx, surface);
|
|
7846
8504
|
const override = opts.modelOverride ?? resolveEffectiveModelOverride(opts.ctx);
|
|
7847
8505
|
const providerOrder = resolveProviderOrder(opts.ctx);
|
|
7848
8506
|
const modelByProvider = {};
|
|
7849
|
-
for (const provider of providerOrder) {
|
|
7850
|
-
const providerOverride =
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
if (!modelByProvider[activeProvider]) {
|
|
7854
|
-
const activeOverride = override && getCatalogEntry(override)?.provider === activeProvider ? override : void 0;
|
|
7855
|
-
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;
|
|
7856
8511
|
}
|
|
7857
8512
|
return {
|
|
7858
8513
|
providerOrder,
|
|
@@ -7878,70 +8533,142 @@ var init_resolver2 = __esm({
|
|
|
7878
8533
|
|
|
7879
8534
|
// src/ai/llm/failover.ts
|
|
7880
8535
|
async function completeOnProvider(provider, model, apiKey, req) {
|
|
7881
|
-
|
|
7882
|
-
|
|
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;
|
|
7883
8564
|
}
|
|
7884
8565
|
async function completeWithFailover(req, opts = {}) {
|
|
7885
|
-
const
|
|
8566
|
+
const cfg = resolveCompletionContext(req.surface, {
|
|
7886
8567
|
max_tokens: req.max_tokens,
|
|
7887
8568
|
tier: opts.tier,
|
|
7888
8569
|
modelOverride: opts.modelOverride,
|
|
7889
8570
|
ctx: opts.ctx
|
|
7890
8571
|
});
|
|
7891
|
-
const providers =
|
|
8572
|
+
const providers = cfg.providerOrder;
|
|
7892
8573
|
if (providers.length === 0) {
|
|
7893
|
-
throw new Error(
|
|
8574
|
+
throw new Error(NO_PROVIDER_MESSAGE);
|
|
7894
8575
|
}
|
|
8576
|
+
const notices = [];
|
|
7895
8577
|
let lastError;
|
|
7896
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
|
+
});
|
|
7897
8586
|
for (let i = 0; i < providers.length; i++) {
|
|
7898
8587
|
const provider = providers[i];
|
|
7899
|
-
const
|
|
7900
|
-
if (!
|
|
7901
|
-
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
|
+
}
|
|
7902
8607
|
try {
|
|
7903
|
-
const response = await completeOnProvider(provider, model, apiKey,
|
|
7904
|
-
const meta =
|
|
7905
|
-
provider_used: provider,
|
|
7906
|
-
model_used: model,
|
|
7907
|
-
...response.token_usage ?? {},
|
|
7908
|
-
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
|
|
7909
|
-
};
|
|
8608
|
+
const response = await completeOnProvider(provider, model, key.apiKey, effectiveReq);
|
|
8609
|
+
const meta = buildMeta(provider, model, response);
|
|
7910
8610
|
recordLlmUsage(response.token_usage);
|
|
7911
8611
|
return { response, meta };
|
|
7912
8612
|
} catch (err) {
|
|
7913
|
-
|
|
8613
|
+
let llmErr = err;
|
|
7914
8614
|
if (llmErr.name !== "LlmError") throw err;
|
|
7915
8615
|
lastError = llmErr;
|
|
7916
|
-
if (llmErr.code === "
|
|
7917
|
-
|
|
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`);
|
|
7918
8619
|
try {
|
|
7919
|
-
const response = await completeOnProvider(provider, model, apiKey,
|
|
7920
|
-
const meta =
|
|
7921
|
-
provider_used: provider,
|
|
7922
|
-
model_used: model,
|
|
7923
|
-
...response.token_usage ?? {},
|
|
7924
|
-
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
|
|
7925
|
-
};
|
|
8620
|
+
const response = await completeOnProvider(provider, model, key.apiKey, stripTools(effectiveReq));
|
|
8621
|
+
const meta = buildMeta(provider, model, response);
|
|
7926
8622
|
recordLlmUsage(response.token_usage);
|
|
7927
8623
|
return { response, meta };
|
|
7928
8624
|
} catch (retryErr) {
|
|
7929
8625
|
const retryLlm = retryErr;
|
|
7930
|
-
if (retryLlm.name
|
|
7931
|
-
|
|
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
|
+
}
|
|
7932
8658
|
}
|
|
7933
8659
|
}
|
|
7934
8660
|
if (!isFailoverEligible(llmErr.code)) throw llmErr;
|
|
7935
8661
|
const next = providers[i + 1];
|
|
7936
8662
|
if (next) {
|
|
7937
8663
|
failoverFrom = failoverFrom ?? provider;
|
|
8664
|
+
notices.push(`${provider} unavailable (${llmErr.code.toLowerCase()}) \u2014 trying ${next}`);
|
|
7938
8665
|
opts.onFailover?.(provider, next, llmErr.code);
|
|
7939
8666
|
continue;
|
|
7940
8667
|
}
|
|
7941
8668
|
throw llmErr;
|
|
7942
8669
|
}
|
|
7943
8670
|
}
|
|
7944
|
-
throw lastError ?? new Error(
|
|
8671
|
+
throw lastError ?? new Error(NO_PROVIDER_MESSAGE);
|
|
7945
8672
|
}
|
|
7946
8673
|
async function* streamWithFailover(req, opts = {}) {
|
|
7947
8674
|
const cfg = resolveCompletionContext(req.surface, {
|
|
@@ -7950,23 +8677,47 @@ async function* streamWithFailover(req, opts = {}) {
|
|
|
7950
8677
|
modelOverride: opts.modelOverride,
|
|
7951
8678
|
ctx: opts.ctx
|
|
7952
8679
|
});
|
|
7953
|
-
const providers =
|
|
8680
|
+
const providers = cfg.providerOrder;
|
|
7954
8681
|
if (providers.length === 0) {
|
|
7955
|
-
throw new Error(
|
|
8682
|
+
throw new Error(NO_PROVIDER_MESSAGE);
|
|
7956
8683
|
}
|
|
8684
|
+
const notices = [];
|
|
7957
8685
|
let lastError;
|
|
7958
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
|
+
}
|
|
7959
8698
|
for (let i = 0; i < providers.length; i++) {
|
|
7960
8699
|
const provider = providers[i];
|
|
7961
|
-
const
|
|
7962
|
-
if (!
|
|
7963
|
-
|
|
7964
|
-
|
|
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) {
|
|
7965
8716
|
let fullText = "";
|
|
7966
|
-
const
|
|
7967
|
-
for await (const event of streamFn(apiKey, model, req)) {
|
|
8717
|
+
for await (const event of streamOnProvider(provider, attemptModel, key.apiKey)) {
|
|
7968
8718
|
if (event.type === "text_delta") {
|
|
7969
8719
|
fullText += event.text;
|
|
8720
|
+
yieldedAny = true;
|
|
7970
8721
|
yield event;
|
|
7971
8722
|
}
|
|
7972
8723
|
}
|
|
@@ -7974,10 +8725,11 @@ async function* streamWithFailover(req, opts = {}) {
|
|
|
7974
8725
|
recordLlmUsage({ input_tokens: 0, output_tokens: estimatedOut });
|
|
7975
8726
|
const meta = {
|
|
7976
8727
|
provider_used: provider,
|
|
7977
|
-
model_used:
|
|
8728
|
+
model_used: attemptModel,
|
|
7978
8729
|
input_tokens: 0,
|
|
7979
8730
|
output_tokens: estimatedOut,
|
|
7980
|
-
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
|
|
8731
|
+
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {},
|
|
8732
|
+
...notices.length > 0 ? { notices: [...notices] } : {}
|
|
7981
8733
|
};
|
|
7982
8734
|
yield {
|
|
7983
8735
|
type: "done",
|
|
@@ -7989,32 +8741,66 @@ async function* streamWithFailover(req, opts = {}) {
|
|
|
7989
8741
|
},
|
|
7990
8742
|
meta
|
|
7991
8743
|
};
|
|
8744
|
+
};
|
|
8745
|
+
try {
|
|
8746
|
+
yield* attempt(model);
|
|
7992
8747
|
return;
|
|
7993
8748
|
} catch (err) {
|
|
7994
8749
|
const llmErr = err;
|
|
7995
8750
|
if (llmErr.name !== "LlmError") throw err;
|
|
7996
8751
|
lastError = llmErr;
|
|
7997
|
-
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;
|
|
7998
8776
|
const next = providers[i + 1];
|
|
7999
8777
|
if (next) {
|
|
8000
8778
|
failoverFrom = failoverFrom ?? provider;
|
|
8001
|
-
|
|
8779
|
+
notices.push(`${provider} unavailable (${lastError.code.toLowerCase()}) \u2014 trying ${next}`);
|
|
8780
|
+
opts.onFailover?.(provider, next, lastError.code);
|
|
8002
8781
|
continue;
|
|
8003
8782
|
}
|
|
8004
|
-
throw
|
|
8783
|
+
throw lastError;
|
|
8005
8784
|
}
|
|
8006
8785
|
}
|
|
8007
|
-
throw lastError ?? new Error(
|
|
8786
|
+
throw lastError ?? new Error(NO_PROVIDER_MESSAGE);
|
|
8008
8787
|
}
|
|
8788
|
+
var NO_PROVIDER_MESSAGE;
|
|
8009
8789
|
var init_failover = __esm({
|
|
8010
8790
|
"src/ai/llm/failover.ts"() {
|
|
8011
8791
|
"use strict";
|
|
8012
8792
|
init_usage_stats();
|
|
8013
8793
|
init_anthropic();
|
|
8014
|
-
|
|
8794
|
+
init_openai_compat();
|
|
8015
8795
|
init_catalog();
|
|
8796
|
+
init_discovery();
|
|
8016
8797
|
init_errors2();
|
|
8798
|
+
init_heal();
|
|
8799
|
+
init_models_cache();
|
|
8800
|
+
init_providers();
|
|
8801
|
+
init_types2();
|
|
8017
8802
|
init_resolver2();
|
|
8803
|
+
NO_PROVIDER_MESSAGE = "No LLM provider configured. Run /connect and paste any API key (Anthropic, OpenAI, Groq, Gemini, ...).";
|
|
8018
8804
|
}
|
|
8019
8805
|
});
|
|
8020
8806
|
|
|
@@ -8383,8 +9169,8 @@ function markFailure(ctx) {
|
|
|
8383
9169
|
}
|
|
8384
9170
|
async function loadOrBuildTaxonomy(profile, forceRegen, ctx) {
|
|
8385
9171
|
if (!forceRegen) {
|
|
8386
|
-
const
|
|
8387
|
-
if (
|
|
9172
|
+
const cached2 = loadCachedTaxonomy(profile);
|
|
9173
|
+
if (cached2) return cached2;
|
|
8388
9174
|
}
|
|
8389
9175
|
const spinnerText = forceRegen ? "Rebuilding market taxonomy\u2026" : "Researching your market taxonomy\u2026";
|
|
8390
9176
|
const spinner = ora2({ text: spinnerText, discardStdin: false }).start();
|
|
@@ -8542,7 +9328,7 @@ __export(ingest_exports, {
|
|
|
8542
9328
|
});
|
|
8543
9329
|
import chalk9 from "chalk";
|
|
8544
9330
|
import ora3 from "ora";
|
|
8545
|
-
import { readFileSync as
|
|
9331
|
+
import { readFileSync as readFileSync11, existsSync as existsSync12 } from "fs";
|
|
8546
9332
|
import { basename as basename2 } from "path";
|
|
8547
9333
|
async function handler2(args, ctx) {
|
|
8548
9334
|
const { positional, flags } = parseArgs(args, [
|
|
@@ -8566,7 +9352,7 @@ async function handler2(args, ctx) {
|
|
|
8566
9352
|
console.error(chalk9.dim(" /ingest --demo [--scenario <name>]"));
|
|
8567
9353
|
process.exit(1);
|
|
8568
9354
|
}
|
|
8569
|
-
if (!
|
|
9355
|
+
if (!existsSync12(file)) {
|
|
8570
9356
|
console.error(chalk9.red(` File not found: ${file}`));
|
|
8571
9357
|
process.exit(1);
|
|
8572
9358
|
}
|
|
@@ -8584,7 +9370,7 @@ async function handler2(args, ctx) {
|
|
|
8584
9370
|
try {
|
|
8585
9371
|
await initSchema();
|
|
8586
9372
|
spinner.text = "Parsing CSV...";
|
|
8587
|
-
const content =
|
|
9373
|
+
const content = readFileSync11(file, "utf-8");
|
|
8588
9374
|
const { rows, headers } = parseCSV(content);
|
|
8589
9375
|
if (rows.length === 0) {
|
|
8590
9376
|
spinner.fail("CSV is empty");
|
|
@@ -10909,16 +11695,16 @@ var init_compute = __esm({
|
|
|
10909
11695
|
});
|
|
10910
11696
|
|
|
10911
11697
|
// src/data/playbook.ts
|
|
10912
|
-
import { existsSync as
|
|
10913
|
-
import { join as
|
|
11698
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12, appendFileSync } from "fs";
|
|
11699
|
+
import { join as join12 } from "path";
|
|
10914
11700
|
function playsPath() {
|
|
10915
|
-
return
|
|
11701
|
+
return join12(getMemoryDir(), PLAYS_FILE);
|
|
10916
11702
|
}
|
|
10917
11703
|
function getCustomPlays() {
|
|
10918
11704
|
const path = playsPath();
|
|
10919
|
-
if (!
|
|
11705
|
+
if (!existsSync13(path)) return [];
|
|
10920
11706
|
const out = [];
|
|
10921
|
-
for (const line of
|
|
11707
|
+
for (const line of readFileSync12(path, "utf-8").split("\n")) {
|
|
10922
11708
|
const trimmed = line.trim();
|
|
10923
11709
|
if (!trimmed) continue;
|
|
10924
11710
|
try {
|
|
@@ -11510,7 +12296,7 @@ async function runMetricsAnalysis(options = {}) {
|
|
|
11510
12296
|
if (options.findings) {
|
|
11511
12297
|
if (!canUseReplAi(options.ctx)) {
|
|
11512
12298
|
throw new Error(
|
|
11513
|
-
"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."
|
|
11514
12300
|
);
|
|
11515
12301
|
}
|
|
11516
12302
|
options.onProgress?.("findings");
|
|
@@ -11919,7 +12705,8 @@ async function* streamFindings(input, ctx) {
|
|
|
11919
12705
|
findings,
|
|
11920
12706
|
model_used: meta.model_used,
|
|
11921
12707
|
provider_used: meta.provider_used,
|
|
11922
|
-
raw_prompt: userMessage
|
|
12708
|
+
raw_prompt: userMessage,
|
|
12709
|
+
usage: meta
|
|
11923
12710
|
};
|
|
11924
12711
|
}
|
|
11925
12712
|
}
|
|
@@ -12220,9 +13007,9 @@ var init_tool_schemas = __esm({
|
|
|
12220
13007
|
});
|
|
12221
13008
|
|
|
12222
13009
|
// src/ai/privacy.ts
|
|
12223
|
-
import { existsSync as
|
|
13010
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2 } from "fs";
|
|
12224
13011
|
import { homedir as homedir4 } from "os";
|
|
12225
|
-
import { join as
|
|
13012
|
+
import { join as join13 } from "path";
|
|
12226
13013
|
function stripPII(obj) {
|
|
12227
13014
|
if (obj === null || obj === void 0) return obj;
|
|
12228
13015
|
if (typeof obj !== "object") return obj;
|
|
@@ -12237,14 +13024,14 @@ function stripPII(obj) {
|
|
|
12237
13024
|
return out;
|
|
12238
13025
|
}
|
|
12239
13026
|
function ensureAuditDir() {
|
|
12240
|
-
if (!
|
|
13027
|
+
if (!existsSync14(AUDIT_DIR)) {
|
|
12241
13028
|
mkdirSync8(AUDIT_DIR, { recursive: true });
|
|
12242
13029
|
}
|
|
12243
13030
|
}
|
|
12244
13031
|
function logToolCall(entry) {
|
|
12245
13032
|
ensureAuditDir();
|
|
12246
13033
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
12247
|
-
const path =
|
|
13034
|
+
const path = join13(AUDIT_DIR, `agentic-${date}.jsonl`);
|
|
12248
13035
|
appendFileSync2(path, JSON.stringify(entry) + "\n");
|
|
12249
13036
|
}
|
|
12250
13037
|
var PII_FIELDS, AUDIT_DIR;
|
|
@@ -12268,7 +13055,7 @@ var init_privacy = __esm({
|
|
|
12268
13055
|
"raw_data",
|
|
12269
13056
|
"metadata"
|
|
12270
13057
|
]);
|
|
12271
|
-
AUDIT_DIR =
|
|
13058
|
+
AUDIT_DIR = join13(homedir4(), ".ntrp", "audit");
|
|
12272
13059
|
}
|
|
12273
13060
|
});
|
|
12274
13061
|
|
|
@@ -12744,7 +13531,7 @@ __export(ingest_chat_exports, {
|
|
|
12744
13531
|
loadDemoFromChat: () => loadDemoFromChat,
|
|
12745
13532
|
looksLikeFilePath: () => looksLikeFilePath
|
|
12746
13533
|
});
|
|
12747
|
-
import { existsSync as
|
|
13534
|
+
import { existsSync as existsSync15 } from "fs";
|
|
12748
13535
|
import { basename as basename3, resolve as resolve4 } from "path";
|
|
12749
13536
|
import { homedir as homedir5 } from "os";
|
|
12750
13537
|
import chalk15 from "chalk";
|
|
@@ -12764,11 +13551,11 @@ function extractFilePath(input) {
|
|
|
12764
13551
|
const m = trimmed.match(re);
|
|
12765
13552
|
if (m?.[1]) {
|
|
12766
13553
|
const p = expandPath(m[1]);
|
|
12767
|
-
if (
|
|
13554
|
+
if (existsSync15(p)) return p;
|
|
12768
13555
|
}
|
|
12769
13556
|
if (!m?.[1] && re.test(trimmed) && trimmed.toLowerCase().endsWith(".csv")) {
|
|
12770
13557
|
const p = expandPath(trimmed.replace(/^["']|["']$/g, ""));
|
|
12771
|
-
if (
|
|
13558
|
+
if (existsSync15(p)) return p;
|
|
12772
13559
|
}
|
|
12773
13560
|
}
|
|
12774
13561
|
return null;
|
|
@@ -12798,12 +13585,12 @@ async function ingestFromChat(ctx, filePath) {
|
|
|
12798
13585
|
}
|
|
12799
13586
|
const { handler: ingest } = await Promise.resolve().then(() => (init_ingest(), ingest_exports));
|
|
12800
13587
|
const { detectEntityType: detectEntityType2 } = await Promise.resolve().then(() => (init_csv_detect(), csv_detect_exports));
|
|
12801
|
-
const { readFileSync:
|
|
13588
|
+
const { readFileSync: readFileSync19 } = await import("fs");
|
|
12802
13589
|
const { parseCSV: parseCSV2 } = await Promise.resolve().then(() => (init_csv_parse(), csv_parse_exports));
|
|
12803
13590
|
const { getStoredApiKey } = await Promise.resolve().then(() => (init_repl_api(), repl_api_exports));
|
|
12804
13591
|
let headerCheckFailed = false;
|
|
12805
13592
|
try {
|
|
12806
|
-
const raw =
|
|
13593
|
+
const raw = readFileSync19(filePath, "utf-8");
|
|
12807
13594
|
const { headers } = parseCSV2(raw);
|
|
12808
13595
|
const detected = detectEntityType2(headers, "unknown");
|
|
12809
13596
|
if (!detected) headerCheckFailed = true;
|
|
@@ -13551,12 +14338,12 @@ async function handleDraftHandoff(input) {
|
|
|
13551
14338
|
};
|
|
13552
14339
|
}
|
|
13553
14340
|
async function executeToolCall(name, input, ctx) {
|
|
13554
|
-
const
|
|
13555
|
-
if (!
|
|
14341
|
+
const handler46 = HANDLERS[name];
|
|
14342
|
+
if (!handler46) {
|
|
13556
14343
|
return JSON.stringify({ error: `Unknown tool '${name}'` });
|
|
13557
14344
|
}
|
|
13558
14345
|
const start = Date.now();
|
|
13559
|
-
const rawResult = await
|
|
14346
|
+
const rawResult = await handler46(input, ctx);
|
|
13560
14347
|
const safeResult = stripPII(rawResult);
|
|
13561
14348
|
const resultJson = JSON.stringify(safeResult);
|
|
13562
14349
|
const duration = Date.now() - start;
|
|
@@ -13949,7 +14736,7 @@ async function runDiagnosis(options = {}) {
|
|
|
13949
14736
|
if (options.findings) {
|
|
13950
14737
|
if (!canUseReplAi(options.ctx)) {
|
|
13951
14738
|
throw new Error(
|
|
13952
|
-
"AI findings require stored API keys. Run `ntrp`, then /
|
|
14739
|
+
"AI findings require stored API keys. Run `ntrp`, then /connect (any provider key), and use /diagnose --findings."
|
|
13953
14740
|
);
|
|
13954
14741
|
}
|
|
13955
14742
|
if (options.deep) {
|
|
@@ -14092,7 +14879,7 @@ async function handler3(args, ctx) {
|
|
|
14092
14879
|
console.log();
|
|
14093
14880
|
console.log(" " + chalk16.red("AI findings run only in the interactive REPL."));
|
|
14094
14881
|
console.log(" " + chalk16.dim("Vital signs compute without a key \u2014 omit --findings for numbers only."));
|
|
14095
|
-
console.log(" " + chalk16.dim("Start with ") + paint("accent", "ntrp") + chalk16.dim(",
|
|
14882
|
+
console.log(" " + chalk16.dim("Start with ") + paint("accent", "ntrp") + chalk16.dim(", run ") + paint("accent", "/connect") + chalk16.dim(" (any provider key), then /diagnose --findings."));
|
|
14096
14883
|
console.log();
|
|
14097
14884
|
return;
|
|
14098
14885
|
}
|
|
@@ -14752,13 +15539,242 @@ var init_profile2 = __esm({
|
|
|
14752
15539
|
}
|
|
14753
15540
|
});
|
|
14754
15541
|
|
|
14755
|
-
// src/
|
|
14756
|
-
|
|
14757
|
-
|
|
14758
|
-
|
|
14759
|
-
|
|
15542
|
+
// src/ai/llm/detect.ts
|
|
15543
|
+
function detectProviderByKey(key) {
|
|
15544
|
+
const k = key.trim();
|
|
15545
|
+
const specs = listProviderSpecs();
|
|
15546
|
+
let best;
|
|
15547
|
+
for (const spec of specs) {
|
|
15548
|
+
for (const prefix of spec.key_prefixes) {
|
|
15549
|
+
if (k.startsWith(prefix) && (!best || prefix.length > best.length)) {
|
|
15550
|
+
best = { id: spec.id, length: prefix.length };
|
|
15551
|
+
}
|
|
15552
|
+
}
|
|
15553
|
+
}
|
|
15554
|
+
if (best) return { certain: best.id, candidates: [best.id] };
|
|
15555
|
+
const shared = specs.filter((s) => s.shared_prefixes.some((p) => k.startsWith(p)));
|
|
15556
|
+
if (shared.length > 0) return { candidates: shared.map((s) => s.id) };
|
|
15557
|
+
const noPrefix = specs.filter(
|
|
15558
|
+
(s) => s.requires_key && !s.custom && s.key_prefixes.length === 0 && s.shared_prefixes.length === 0
|
|
15559
|
+
);
|
|
15560
|
+
return { candidates: noPrefix.map((s) => s.id) };
|
|
15561
|
+
}
|
|
15562
|
+
async function probeProviders(key, candidateIds, timeoutMs = 5e3) {
|
|
15563
|
+
const results = await Promise.all(
|
|
15564
|
+
candidateIds.map(async (id) => {
|
|
15565
|
+
const spec = getProviderSpec(id);
|
|
15566
|
+
if (!spec) return { id, ok: false, status: 0 };
|
|
15567
|
+
const result = await fetchProviderModels(spec, key, timeoutMs);
|
|
15568
|
+
if (result.ok) return { id, ok: true, models: result.models };
|
|
15569
|
+
return { id, ok: false, status: result.status };
|
|
15570
|
+
})
|
|
15571
|
+
);
|
|
15572
|
+
const accepted = [];
|
|
15573
|
+
let sawNetworkFailure = false;
|
|
15574
|
+
for (const r of results) {
|
|
15575
|
+
if (r.ok && r.models.length > 0) accepted.push({ provider: r.id, models: r.models });
|
|
15576
|
+
else if (!r.ok && r.status === 0) sawNetworkFailure = true;
|
|
15577
|
+
}
|
|
15578
|
+
return { accepted, sawNetworkFailure };
|
|
15579
|
+
}
|
|
15580
|
+
var init_detect = __esm({
|
|
15581
|
+
"src/ai/llm/detect.ts"() {
|
|
15582
|
+
"use strict";
|
|
15583
|
+
init_discovery();
|
|
15584
|
+
init_providers();
|
|
15585
|
+
}
|
|
14760
15586
|
});
|
|
14761
|
-
|
|
15587
|
+
|
|
15588
|
+
// src/services/connect.ts
|
|
15589
|
+
var connect_exports = {};
|
|
15590
|
+
__export(connect_exports, {
|
|
15591
|
+
ConnectCancelled: () => ConnectCancelled,
|
|
15592
|
+
ConnectError: () => ConnectError,
|
|
15593
|
+
connectCustomEndpoint: () => connectCustomEndpoint,
|
|
15594
|
+
connectKeyless: () => connectKeyless,
|
|
15595
|
+
connectWithKey: () => connectWithKey,
|
|
15596
|
+
describeConnectOutcome: () => describeConnectOutcome
|
|
15597
|
+
});
|
|
15598
|
+
function finishConnect(spec, opts) {
|
|
15599
|
+
const before = getAvailableProviders();
|
|
15600
|
+
if (opts.key) {
|
|
15601
|
+
setConfigValue(spec.key_config_name, opts.key);
|
|
15602
|
+
}
|
|
15603
|
+
const entry = opts.models && opts.models.length > 0 ? storeDiscoveredModels(spec.id, opts.models) : null;
|
|
15604
|
+
const cfg = loadLlmConfig();
|
|
15605
|
+
let becamePrimary = false;
|
|
15606
|
+
if (cfg.primary !== spec.id && (before.length === 0 || !hasProviderKey(cfg.primary))) {
|
|
15607
|
+
setConfigValue("llm-primary", spec.id);
|
|
15608
|
+
becamePrimary = true;
|
|
15609
|
+
}
|
|
15610
|
+
return {
|
|
15611
|
+
provider: spec.id,
|
|
15612
|
+
label: spec.label,
|
|
15613
|
+
modelCount: entry?.models.length ?? 0,
|
|
15614
|
+
...entry ? { stack: entry.tier_stack } : {},
|
|
15615
|
+
becamePrimary,
|
|
15616
|
+
offline: !!opts.offline
|
|
15617
|
+
};
|
|
15618
|
+
}
|
|
15619
|
+
async function connectWithKey(rawKey, opts = {}) {
|
|
15620
|
+
const key = rawKey.trim();
|
|
15621
|
+
if (!key) throw new ConnectError("Empty key.");
|
|
15622
|
+
if (opts.providerId) {
|
|
15623
|
+
const spec = getProviderSpec(opts.providerId);
|
|
15624
|
+
if (!spec) {
|
|
15625
|
+
throw new ConnectError(
|
|
15626
|
+
`Unknown provider "${opts.providerId}". Use a built-in id or /connect --base-url <url> --id ${opts.providerId} for a custom endpoint.`
|
|
15627
|
+
);
|
|
15628
|
+
}
|
|
15629
|
+
const result = await fetchProviderModels(spec, key);
|
|
15630
|
+
if (result.ok) return finishConnect(spec, { key, models: result.models });
|
|
15631
|
+
if (result.status === 0) {
|
|
15632
|
+
return finishConnect(spec, { key, offline: true });
|
|
15633
|
+
}
|
|
15634
|
+
throw new ConnectError(`${spec.label} rejected this key (HTTP ${result.status}) \u2014 double-check it and try again.`);
|
|
15635
|
+
}
|
|
15636
|
+
const detection = detectProviderByKey(key);
|
|
15637
|
+
if (detection.certain) {
|
|
15638
|
+
const spec = getProviderSpec(detection.certain);
|
|
15639
|
+
const result = await fetchProviderModels(spec, key);
|
|
15640
|
+
if (result.ok) return finishConnect(spec, { key, models: result.models });
|
|
15641
|
+
if (result.status === 0) return finishConnect(spec, { key, offline: true });
|
|
15642
|
+
throw new ConnectError(`${spec.label} rejected this key (HTTP ${result.status}) \u2014 double-check it and try again.`);
|
|
15643
|
+
}
|
|
15644
|
+
const report = await probeProviders(key, detection.candidates);
|
|
15645
|
+
if (report.accepted.length === 1) {
|
|
15646
|
+
const match = report.accepted[0];
|
|
15647
|
+
const spec = getProviderSpec(match.provider);
|
|
15648
|
+
if (opts.callbacks?.confirmDetection) {
|
|
15649
|
+
const ok = await opts.callbacks.confirmDetection(match.provider);
|
|
15650
|
+
if (!ok) throw new ConnectCancelled();
|
|
15651
|
+
}
|
|
15652
|
+
return finishConnect(spec, { key, models: match.models });
|
|
15653
|
+
}
|
|
15654
|
+
if (report.accepted.length > 1) {
|
|
15655
|
+
if (opts.callbacks?.chooseProvider) {
|
|
15656
|
+
const chosen = await opts.callbacks.chooseProvider(report.accepted);
|
|
15657
|
+
if (!chosen) throw new ConnectCancelled();
|
|
15658
|
+
const match = report.accepted.find((a) => a.provider === chosen);
|
|
15659
|
+
return finishConnect(getProviderSpec(chosen), { key, models: match.models });
|
|
15660
|
+
}
|
|
15661
|
+
throw new ConnectError(
|
|
15662
|
+
`Multiple providers accepted this key (${report.accepted.map((a) => a.provider).join(", ")}). Re-run with --provider <id>.`
|
|
15663
|
+
);
|
|
15664
|
+
}
|
|
15665
|
+
if (report.sawNetworkFailure) {
|
|
15666
|
+
throw new ConnectError(
|
|
15667
|
+
`Couldn't reach ${detection.candidates.map(providerLabel).join(" / ")} to identify this key. Check your connection, or force a provider with --provider <id>.`
|
|
15668
|
+
);
|
|
15669
|
+
}
|
|
15670
|
+
throw new ConnectError(
|
|
15671
|
+
`No provider accepted this key (tried ${detection.candidates.map(providerLabel).join(", ")}). If it belongs to an OpenAI-compatible endpoint, run /connect --base-url <url>.`
|
|
15672
|
+
);
|
|
15673
|
+
}
|
|
15674
|
+
async function connectCustomEndpoint(opts) {
|
|
15675
|
+
const id = opts.id.trim().toLowerCase();
|
|
15676
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(id)) {
|
|
15677
|
+
throw new ConnectError(`Invalid provider id "${opts.id}" \u2014 use letters, digits, dashes.`);
|
|
15678
|
+
}
|
|
15679
|
+
const baseUrl = opts.baseUrl.trim().replace(/\/+$/, "");
|
|
15680
|
+
if (!/^https?:\/\//.test(baseUrl)) {
|
|
15681
|
+
throw new ConnectError(`Base URL must start with http:// or https:// (got "${opts.baseUrl}").`);
|
|
15682
|
+
}
|
|
15683
|
+
const builtin = getProviderSpec(id);
|
|
15684
|
+
const spec = builtin ? { ...builtin, base_url: baseUrl } : {
|
|
15685
|
+
id,
|
|
15686
|
+
label: opts.label ?? id,
|
|
15687
|
+
api: "openai-compat",
|
|
15688
|
+
base_url: baseUrl,
|
|
15689
|
+
key_prefixes: [],
|
|
15690
|
+
shared_prefixes: [],
|
|
15691
|
+
key_config_name: `${id}-api-key`,
|
|
15692
|
+
requires_key: !!opts.key,
|
|
15693
|
+
custom: true
|
|
15694
|
+
};
|
|
15695
|
+
const result = await fetchProviderModels(spec, opts.key);
|
|
15696
|
+
if (!result.ok) {
|
|
15697
|
+
if (result.status === 0) {
|
|
15698
|
+
throw new ConnectError(`Couldn't reach ${baseUrl} \u2014 check the URL (expects an OpenAI-compatible /models endpoint).`);
|
|
15699
|
+
}
|
|
15700
|
+
if (result.status === 401 || result.status === 403) {
|
|
15701
|
+
throw new ConnectError(
|
|
15702
|
+
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.`
|
|
15703
|
+
);
|
|
15704
|
+
}
|
|
15705
|
+
throw new ConnectError(`${baseUrl} answered HTTP ${result.status} \u2014 is this an OpenAI-compatible endpoint?`);
|
|
15706
|
+
}
|
|
15707
|
+
if (result.models.length === 0) {
|
|
15708
|
+
throw new ConnectError(`${baseUrl} lists no models \u2014 nothing to connect.`);
|
|
15709
|
+
}
|
|
15710
|
+
saveCustomProvider({
|
|
15711
|
+
id,
|
|
15712
|
+
...opts.label ? { label: opts.label } : {},
|
|
15713
|
+
base_url: baseUrl,
|
|
15714
|
+
requires_key: !!opts.key,
|
|
15715
|
+
enabled: true
|
|
15716
|
+
});
|
|
15717
|
+
return finishConnect(getProviderSpec(id), { key: opts.key, models: result.models });
|
|
15718
|
+
}
|
|
15719
|
+
async function connectKeyless(providerId, baseUrl) {
|
|
15720
|
+
const builtin = getProviderSpec(providerId);
|
|
15721
|
+
if (!builtin) throw new ConnectError(`Unknown provider "${providerId}".`);
|
|
15722
|
+
const spec = baseUrl ? { ...builtin, base_url: baseUrl.replace(/\/+$/, "") } : builtin;
|
|
15723
|
+
const result = await fetchProviderModels(spec, void 0);
|
|
15724
|
+
if (!result.ok) {
|
|
15725
|
+
throw new ConnectError(
|
|
15726
|
+
`${spec.label} not reachable at ${spec.base_url}. Is it running? (ollama serve, then retry)`
|
|
15727
|
+
);
|
|
15728
|
+
}
|
|
15729
|
+
if (result.models.length === 0) {
|
|
15730
|
+
throw new ConnectError(`${spec.label} is running but has no models \u2014 pull one first (e.g. \`ollama pull llama3.2\`).`);
|
|
15731
|
+
}
|
|
15732
|
+
saveCustomProvider({ id: spec.id, base_url: spec.base_url, enabled: true });
|
|
15733
|
+
return finishConnect(getProviderSpec(spec.id), { models: result.models });
|
|
15734
|
+
}
|
|
15735
|
+
function describeConnectOutcome(outcome) {
|
|
15736
|
+
const lines = [];
|
|
15737
|
+
if (outcome.offline) {
|
|
15738
|
+
lines.push(`${outcome.label} key saved \u2014 provider unreachable right now, models will be discovered on first use.`);
|
|
15739
|
+
} else {
|
|
15740
|
+
lines.push(`Connected ${outcome.label} \u2014 ${outcome.modelCount} chat model${outcome.modelCount === 1 ? "" : "s"} available.`);
|
|
15741
|
+
}
|
|
15742
|
+
if (outcome.stack) {
|
|
15743
|
+
lines.push(`high ${outcome.stack.high}`);
|
|
15744
|
+
lines.push(`medium ${outcome.stack.medium}`);
|
|
15745
|
+
lines.push(`low ${outcome.stack.low}`);
|
|
15746
|
+
}
|
|
15747
|
+
if (outcome.becamePrimary) {
|
|
15748
|
+
lines.push(`Primary engine: ${outcome.provider}`);
|
|
15749
|
+
}
|
|
15750
|
+
return lines;
|
|
15751
|
+
}
|
|
15752
|
+
var ConnectError, ConnectCancelled;
|
|
15753
|
+
var init_connect = __esm({
|
|
15754
|
+
"src/services/connect.ts"() {
|
|
15755
|
+
"use strict";
|
|
15756
|
+
init_detect();
|
|
15757
|
+
init_discovery();
|
|
15758
|
+
init_providers();
|
|
15759
|
+
init_llm_config();
|
|
15760
|
+
init_store();
|
|
15761
|
+
ConnectError = class extends Error {
|
|
15762
|
+
};
|
|
15763
|
+
ConnectCancelled = class extends ConnectError {
|
|
15764
|
+
constructor() {
|
|
15765
|
+
super("Connect cancelled.");
|
|
15766
|
+
}
|
|
15767
|
+
};
|
|
15768
|
+
}
|
|
15769
|
+
});
|
|
15770
|
+
|
|
15771
|
+
// src/commands/onboard.ts
|
|
15772
|
+
var onboard_exports = {};
|
|
15773
|
+
__export(onboard_exports, {
|
|
15774
|
+
handler: () => handler5,
|
|
15775
|
+
profileExists: () => profileExists
|
|
15776
|
+
});
|
|
15777
|
+
import chalk19 from "chalk";
|
|
14762
15778
|
import ora6 from "ora";
|
|
14763
15779
|
async function handler5(args, ctx) {
|
|
14764
15780
|
const { flags } = parseArgs(args, ["force", "skip-brand"]);
|
|
@@ -15079,42 +16095,58 @@ function printIntro() {
|
|
|
15079
16095
|
}
|
|
15080
16096
|
async function ensureLlmKeys(session) {
|
|
15081
16097
|
if (hasAnyLlmProvider()) return;
|
|
16098
|
+
const { connectWithKey: connectWithKey2, describeConnectOutcome: describeConnectOutcome2, ConnectCancelled: ConnectCancelled2 } = await Promise.resolve().then(() => (init_connect(), connect_exports));
|
|
16099
|
+
const { providerLabel: providerLabel2 } = await Promise.resolve().then(() => (init_providers(), providers_exports));
|
|
16100
|
+
const { countAvailableEngines: countAvailableEngines2 } = await Promise.resolve().then(() => (init_session_state(), session_state_exports));
|
|
15082
16101
|
console.log();
|
|
15083
16102
|
console.log(" " + chalk19.dim("Onboarding uses AI to draft your profile."));
|
|
15084
16103
|
console.log(
|
|
15085
|
-
" " + chalk19.dim("
|
|
16104
|
+
" " + chalk19.dim("Paste any provider's API key \u2014 Anthropic, OpenAI, Groq, Gemini, Mistral, ...")
|
|
15086
16105
|
);
|
|
15087
|
-
|
|
15088
|
-
|
|
15089
|
-
{ value: "openai", label: "OpenAI (GPT)", description: "Full parity on all surfaces" }
|
|
15090
|
-
]);
|
|
15091
|
-
const firstLabel = first === "anthropic" ? "Anthropic API key" : "OpenAI API key";
|
|
15092
|
-
const firstKey = await session.askSecret(firstLabel, { confirm: true });
|
|
15093
|
-
if (first === "anthropic") setConfigValue("api-key", firstKey);
|
|
15094
|
-
else setConfigValue("openai-api-key", firstKey);
|
|
15095
|
-
setConfigValue("llm-primary", first);
|
|
15096
|
-
setConfigValue("llm-tier", "high");
|
|
15097
|
-
const second = first === "anthropic" ? "openai" : "anthropic";
|
|
15098
|
-
setConfigValue("llm-failover-order", second);
|
|
15099
|
-
setConfigValue("llm-auto-failover", "off");
|
|
15100
|
-
const addSecond = await session.confirm(
|
|
15101
|
-
`Add a second engine (${second}) for switching in the REPL?`,
|
|
15102
|
-
false
|
|
16106
|
+
console.log(
|
|
16107
|
+
" " + chalk19.dim("NTRP detects the provider and discovers its models. Or run ") + paint("accent", "/connect") + chalk19.dim(" anytime.")
|
|
15103
16108
|
);
|
|
15104
|
-
|
|
15105
|
-
const
|
|
15106
|
-
const
|
|
15107
|
-
|
|
15108
|
-
|
|
16109
|
+
for (; ; ) {
|
|
16110
|
+
const key = await session.askSecret("LLM API key (any provider)", { confirm: false });
|
|
16111
|
+
const spinner = ora6({ text: "Identifying provider\u2026", discardStdin: false }).start();
|
|
16112
|
+
try {
|
|
16113
|
+
const outcome = await connectWithKey2(key, {
|
|
16114
|
+
callbacks: {
|
|
16115
|
+
confirmDetection: async (providerId) => {
|
|
16116
|
+
spinner.stop();
|
|
16117
|
+
return session.confirm(`Detected ${providerLabel2(providerId)} \u2014 connect it?`, true);
|
|
16118
|
+
},
|
|
16119
|
+
chooseProvider: async (accepted) => {
|
|
16120
|
+
spinner.stop();
|
|
16121
|
+
return session.choose(
|
|
16122
|
+
"Multiple providers accepted this key \u2014 which is it?",
|
|
16123
|
+
accepted.map((a) => ({ value: a.provider, label: providerLabel2(a.provider) }))
|
|
16124
|
+
);
|
|
16125
|
+
}
|
|
16126
|
+
}
|
|
16127
|
+
});
|
|
16128
|
+
spinner.stop();
|
|
16129
|
+
const [headline, ...rest] = describeConnectOutcome2(outcome);
|
|
16130
|
+
console.log(" " + paint("success", "\u2713") + " " + (headline ?? ""));
|
|
16131
|
+
for (const line of rest) console.log(" " + chalk19.dim(line));
|
|
16132
|
+
} catch (err) {
|
|
16133
|
+
spinner.stop();
|
|
16134
|
+
if (!(err instanceof ConnectCancelled2)) {
|
|
16135
|
+
console.log(" " + chalk19.red(String(err.message ?? err)));
|
|
16136
|
+
}
|
|
16137
|
+
const retry = await session.confirm("Try another key?", true);
|
|
16138
|
+
if (retry) continue;
|
|
16139
|
+
if (!hasAnyLlmProvider()) return;
|
|
16140
|
+
}
|
|
16141
|
+
const addAnother = await session.confirm("Add another engine? (switch anytime with /provider)", false);
|
|
16142
|
+
if (!addAnother) break;
|
|
16143
|
+
}
|
|
16144
|
+
if (countAvailableEngines2() >= 2) {
|
|
15109
16145
|
const enableFailover = await session.confirm(
|
|
15110
16146
|
"Enable auto-failover on rate limits? (off = you choose engine with /provider)",
|
|
15111
16147
|
false
|
|
15112
16148
|
);
|
|
15113
|
-
|
|
15114
|
-
} else {
|
|
15115
|
-
console.log(
|
|
15116
|
-
" " + chalk19.dim(`Single engine \u2014 add ${second} later via /config set ${second === "anthropic" ? "api-key" : "openai-api-key"}.`)
|
|
15117
|
-
);
|
|
16149
|
+
setConfigValue("llm-auto-failover", enableFailover ? "on" : "off");
|
|
15118
16150
|
}
|
|
15119
16151
|
console.log(" " + paint("success", "\u2713") + " " + chalk19.dim("LLM engines configured. Use /provider to switch."));
|
|
15120
16152
|
}
|
|
@@ -15184,7 +16216,7 @@ __export(new_exports, {
|
|
|
15184
16216
|
handler: () => handler6
|
|
15185
16217
|
});
|
|
15186
16218
|
import chalk20 from "chalk";
|
|
15187
|
-
import { existsSync as
|
|
16219
|
+
import { existsSync as existsSync16 } from "fs";
|
|
15188
16220
|
import { basename as basename4 } from "path";
|
|
15189
16221
|
async function handler6(args, ctx) {
|
|
15190
16222
|
const { positional, flags } = parseArgs(args, ["demo", "empty", "list-scenarios", "regen-taxonomy"]);
|
|
@@ -15206,7 +16238,7 @@ async function handler6(args, ctx) {
|
|
|
15206
16238
|
console.error(chalk20.red(" Usage: /new <file.csv> | --demo [--scenario <name>] | --empty [--lens health|metrics]"));
|
|
15207
16239
|
return;
|
|
15208
16240
|
}
|
|
15209
|
-
if (source.kind === "file" && !
|
|
16241
|
+
if (source.kind === "file" && !existsSync16(source.path)) {
|
|
15210
16242
|
console.error(chalk20.red(` File not found: ${source.path}`));
|
|
15211
16243
|
return;
|
|
15212
16244
|
}
|
|
@@ -15273,11 +16305,11 @@ async function handler6(args, ctx) {
|
|
|
15273
16305
|
return "New empty session";
|
|
15274
16306
|
}
|
|
15275
16307
|
if (lens === "revenue_metrics") {
|
|
15276
|
-
const
|
|
16308
|
+
const ora19 = (await import("ora")).default;
|
|
15277
16309
|
const { runMetricsAnalysis: runMetricsAnalysis2 } = await Promise.resolve().then(() => (init_metrics_analysis(), metrics_analysis_exports));
|
|
15278
16310
|
const { renderMetricsReport: renderMetricsReport2 } = await Promise.resolve().then(() => (init_metrics_report(), metrics_report_exports));
|
|
15279
16311
|
const structured = isStructuredOutput(ctx.execution);
|
|
15280
|
-
const spinner = structured ? null :
|
|
16312
|
+
const spinner = structured ? null : ora19({ text: "Computing SaaS metrics\u2026", indent: 2, discardStdin: false }).start();
|
|
15281
16313
|
let result;
|
|
15282
16314
|
try {
|
|
15283
16315
|
result = await runMetricsAnalysis2({
|
|
@@ -15483,7 +16515,7 @@ __export(session_exports, {
|
|
|
15483
16515
|
handler: () => handler8
|
|
15484
16516
|
});
|
|
15485
16517
|
import chalk22 from "chalk";
|
|
15486
|
-
import { join as
|
|
16518
|
+
import { join as join14 } from "path";
|
|
15487
16519
|
import ora7 from "ora";
|
|
15488
16520
|
async function handler8(args, ctx) {
|
|
15489
16521
|
const sub = args[0];
|
|
@@ -15572,7 +16604,7 @@ async function pickUp(idArg, ctx) {
|
|
|
15572
16604
|
}
|
|
15573
16605
|
resetContextForSwitch(ctx, {
|
|
15574
16606
|
sessionId: target.id,
|
|
15575
|
-
sessionFile:
|
|
16607
|
+
sessionFile: join14(getSessionsDir(), `${target.id}.json`),
|
|
15576
16608
|
sessionName: session.name,
|
|
15577
16609
|
messages: [...session.messages],
|
|
15578
16610
|
conversation: session.thread ? [...session.thread] : [],
|
|
@@ -15884,7 +16916,7 @@ __export(report_exports, {
|
|
|
15884
16916
|
handler: () => handler9
|
|
15885
16917
|
});
|
|
15886
16918
|
import chalk23 from "chalk";
|
|
15887
|
-
import { writeFileSync as
|
|
16919
|
+
import { writeFileSync as writeFileSync10 } from "fs";
|
|
15888
16920
|
import { dirname as dirname2 } from "path";
|
|
15889
16921
|
async function handler9(args, ctx) {
|
|
15890
16922
|
const { flags } = parseArgs(args);
|
|
@@ -15980,7 +17012,7 @@ async function handler9(args, ctx) {
|
|
|
15980
17012
|
if (!isInsideNtrp(resolvedOutput)) {
|
|
15981
17013
|
console.warn(chalk23.yellow(` Warning: writing report outside ~/.ntrp (${dirname2(resolvedOutput)})`));
|
|
15982
17014
|
}
|
|
15983
|
-
|
|
17015
|
+
writeFileSync10(resolvedOutput, rendered);
|
|
15984
17016
|
console.log(chalk23.green(` Report written to ${resolvedOutput}`));
|
|
15985
17017
|
} else if (rendered) {
|
|
15986
17018
|
console.log(rendered);
|
|
@@ -16011,8 +17043,8 @@ var init_report2 = __esm({
|
|
|
16011
17043
|
});
|
|
16012
17044
|
|
|
16013
17045
|
// src/output/notes-export.ts
|
|
16014
|
-
import { writeFileSync as
|
|
16015
|
-
import { join as
|
|
17046
|
+
import { writeFileSync as writeFileSync11 } from "fs";
|
|
17047
|
+
import { join as join15 } from "path";
|
|
16016
17048
|
function exportToNotes(data) {
|
|
16017
17049
|
const { computeResult, divergences, findings, exchanges } = data;
|
|
16018
17050
|
const { aggregate, segments } = computeResult;
|
|
@@ -16021,7 +17053,7 @@ function exportToNotes(data) {
|
|
|
16021
17053
|
const timeStr = formatTime(now2);
|
|
16022
17054
|
const filename = `${dateStr}-${timeStr}-gtm-health.md`;
|
|
16023
17055
|
const dir = getExportsDir();
|
|
16024
|
-
const filepath =
|
|
17056
|
+
const filepath = join15(dir, filename);
|
|
16025
17057
|
const severityTags = /* @__PURE__ */ new Set();
|
|
16026
17058
|
for (const f of findings) severityTags.add(f.severity);
|
|
16027
17059
|
const tags = ["ntrp", "gtm-health", ...severityTags];
|
|
@@ -16110,7 +17142,7 @@ function exportToNotes(data) {
|
|
|
16110
17142
|
}
|
|
16111
17143
|
}
|
|
16112
17144
|
const content = frontmatter.join("\n") + "\n\n" + body.join("\n") + "\n";
|
|
16113
|
-
|
|
17145
|
+
writeFileSync11(filepath, content);
|
|
16114
17146
|
return filepath;
|
|
16115
17147
|
}
|
|
16116
17148
|
function formatDate(d) {
|
|
@@ -16250,8 +17282,8 @@ __export(backmeup_exports, {
|
|
|
16250
17282
|
});
|
|
16251
17283
|
import chalk25 from "chalk";
|
|
16252
17284
|
import Papa5 from "papaparse";
|
|
16253
|
-
import { mkdirSync as mkdirSync9, writeFileSync as
|
|
16254
|
-
import { join as
|
|
17285
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync12 } from "fs";
|
|
17286
|
+
import { join as join16 } from "path";
|
|
16255
17287
|
function sanitizeCsvValue(value) {
|
|
16256
17288
|
if (typeof value !== "string") return value;
|
|
16257
17289
|
return CSV_FORMULA_RE.test(value) ? `'${value}` : value;
|
|
@@ -16287,7 +17319,7 @@ async function handler11(args, _ctx) {
|
|
|
16287
17319
|
if (!isInsideNtrp(baseDir)) {
|
|
16288
17320
|
console.warn(chalk25.yellow(` Warning: writing backup outside ~/.ntrp (${baseDir})`));
|
|
16289
17321
|
}
|
|
16290
|
-
const folder =
|
|
17322
|
+
const folder = join16(baseDir, folderName);
|
|
16291
17323
|
mkdirSync9(folder, { recursive: true });
|
|
16292
17324
|
const generatedAt = now2.toISOString();
|
|
16293
17325
|
let fileCount = 0;
|
|
@@ -16302,7 +17334,7 @@ async function handler11(args, _ctx) {
|
|
|
16302
17334
|
"Total At Risk": health.total_value_at_risk != null ? formatCurrency(health.total_value_at_risk) : "N/A",
|
|
16303
17335
|
"Generated At": generatedAt
|
|
16304
17336
|
}));
|
|
16305
|
-
|
|
17337
|
+
writeFileSync12(join16(folder, "cover-sheet.csv"), Papa5.unparse(sanitizeCsvRows(coverRows)), "utf-8");
|
|
16306
17338
|
fileCount++;
|
|
16307
17339
|
if (findings.length > 0) {
|
|
16308
17340
|
const findingsRows = findings.map((f) => ({
|
|
@@ -16312,7 +17344,7 @@ async function handler11(args, _ctx) {
|
|
|
16312
17344
|
Finding: f.finding,
|
|
16313
17345
|
"Recommended Plays": f.recommended_plays ? f.recommended_plays.map((p) => p.play_name).join("; ") : ""
|
|
16314
17346
|
}));
|
|
16315
|
-
|
|
17347
|
+
writeFileSync12(join16(folder, "findings.csv"), Papa5.unparse(sanitizeCsvRows(findingsRows)), "utf-8");
|
|
16316
17348
|
fileCount++;
|
|
16317
17349
|
}
|
|
16318
17350
|
for (const vs of health.vital_signs) {
|
|
@@ -16322,7 +17354,7 @@ async function handler11(args, _ctx) {
|
|
|
16322
17354
|
...detail
|
|
16323
17355
|
}));
|
|
16324
17356
|
const filename = EVIDENCE_FILENAMES[vs.vital_sign] ?? `${vs.vital_sign}.csv`;
|
|
16325
|
-
|
|
17357
|
+
writeFileSync12(join16(folder, filename), Papa5.unparse(sanitizeCsvRows(rows)), "utf-8");
|
|
16326
17358
|
fileCount++;
|
|
16327
17359
|
}
|
|
16328
17360
|
console.log(chalk25.green(`
|
|
@@ -16516,8 +17548,8 @@ var init_bundle = __esm({
|
|
|
16516
17548
|
});
|
|
16517
17549
|
|
|
16518
17550
|
// src/repositories/markdown.ts
|
|
16519
|
-
import { mkdirSync as mkdirSync10, writeFileSync as
|
|
16520
|
-
import { basename as basename5, dirname as dirname3, join as
|
|
17551
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync13 } from "fs";
|
|
17552
|
+
import { basename as basename5, dirname as dirname3, join as join17, resolve as resolve6 } from "path";
|
|
16521
17553
|
import { stringify as stringifyYaml } from "yaml";
|
|
16522
17554
|
function renderMarkdownFiles(pkg) {
|
|
16523
17555
|
const bundleJson = JSON.stringify(pkg, null, 2) + "\n";
|
|
@@ -16740,9 +17772,9 @@ var init_markdown3 = __esm({
|
|
|
16740
17772
|
mkdirSync10(root, { recursive: true });
|
|
16741
17773
|
const written = [];
|
|
16742
17774
|
for (const file of files) {
|
|
16743
|
-
const absolutePath =
|
|
17775
|
+
const absolutePath = join17(root, file.relativePath);
|
|
16744
17776
|
mkdirSync10(dirname3(absolutePath), { recursive: true });
|
|
16745
|
-
|
|
17777
|
+
writeFileSync13(absolutePath, file.contents, "utf-8");
|
|
16746
17778
|
written.push(absolutePath);
|
|
16747
17779
|
}
|
|
16748
17780
|
return {
|
|
@@ -17046,8 +18078,8 @@ __export(handoff_exports, {
|
|
|
17046
18078
|
handler: () => handler13
|
|
17047
18079
|
});
|
|
17048
18080
|
import chalk27 from "chalk";
|
|
17049
|
-
import { writeFileSync as
|
|
17050
|
-
import { join as
|
|
18081
|
+
import { writeFileSync as writeFileSync14 } from "fs";
|
|
18082
|
+
import { join as join18 } from "path";
|
|
17051
18083
|
async function handler13(args, ctx) {
|
|
17052
18084
|
const sub = args[0];
|
|
17053
18085
|
if (!sub) {
|
|
@@ -17105,7 +18137,7 @@ async function interactiveMenu(ctx) {
|
|
|
17105
18137
|
}
|
|
17106
18138
|
}
|
|
17107
18139
|
async function runReport(args, ctx) {
|
|
17108
|
-
const out =
|
|
18140
|
+
const out = join18(getExportsDir(), `report-${stamp()}.md`);
|
|
17109
18141
|
const { handler: report } = await Promise.resolve().then(() => (init_report2(), report_exports));
|
|
17110
18142
|
await report(["--format", "md", "--output", out, ...args], ctx);
|
|
17111
18143
|
recordDeliverable(ctx, { kind: "report", at: (/* @__PURE__ */ new Date()).toISOString(), path: out });
|
|
@@ -17153,8 +18185,8 @@ async function runPrompt(target, ctx) {
|
|
|
17153
18185
|
return;
|
|
17154
18186
|
}
|
|
17155
18187
|
const prompt = draft.markdown;
|
|
17156
|
-
const out =
|
|
17157
|
-
|
|
18188
|
+
const out = join18(getExportsDir(), `handoff-${target}-${stamp()}.md`);
|
|
18189
|
+
writeFileSync14(out, prompt, "utf-8");
|
|
17158
18190
|
recordDeliverable(ctx, { kind: `prompt:${target}`, at: (/* @__PURE__ */ new Date()).toISOString(), path: out });
|
|
17159
18191
|
console.log();
|
|
17160
18192
|
console.log(" " + paint("accent", `Agent prompt ready (${target})`));
|
|
@@ -18026,24 +19058,24 @@ JSON SHAPE:
|
|
|
18026
19058
|
|
|
18027
19059
|
// src/strategies/readers.ts
|
|
18028
19060
|
import { createHash } from "crypto";
|
|
18029
|
-
import { existsSync as
|
|
19061
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
|
|
18030
19062
|
import { extname, resolve as resolve7 } from "path";
|
|
18031
19063
|
import { parse as parseYaml } from "yaml";
|
|
18032
19064
|
import { PDFParse } from "pdf-parse";
|
|
18033
19065
|
async function readStrategyFile(pathOrDash) {
|
|
18034
19066
|
if (pathOrDash === "-") {
|
|
18035
|
-
const text2 =
|
|
19067
|
+
const text2 = readFileSync13(0, "utf-8");
|
|
18036
19068
|
return createDocument("stdin", null, text2, {});
|
|
18037
19069
|
}
|
|
18038
19070
|
const sourcePath = resolve7(pathOrDash);
|
|
18039
|
-
if (!
|
|
19071
|
+
if (!existsSync17(sourcePath)) {
|
|
18040
19072
|
throw new NtrpError("strategy_file_not_found", `Strategy file not found: ${pathOrDash}`, 2 /* Usage */);
|
|
18041
19073
|
}
|
|
18042
19074
|
const ext = extname(sourcePath).toLowerCase();
|
|
18043
19075
|
if (ext === ".pdf") {
|
|
18044
19076
|
return readPdf(sourcePath);
|
|
18045
19077
|
}
|
|
18046
|
-
const text =
|
|
19078
|
+
const text = readFileSync13(sourcePath, "utf-8");
|
|
18047
19079
|
if (ext === ".yaml" || ext === ".yml") {
|
|
18048
19080
|
const structured = parseStructuredYaml(text);
|
|
18049
19081
|
return createDocument("yaml", sourcePath, text, structured);
|
|
@@ -18058,7 +19090,7 @@ function readStrategyText(text) {
|
|
|
18058
19090
|
return createDocument("text", null, text, {});
|
|
18059
19091
|
}
|
|
18060
19092
|
async function readPdf(sourcePath) {
|
|
18061
|
-
const data =
|
|
19093
|
+
const data = readFileSync13(sourcePath);
|
|
18062
19094
|
const parser = new PDFParse({ data });
|
|
18063
19095
|
try {
|
|
18064
19096
|
const result = await parser.getText();
|
|
@@ -18105,15 +19137,15 @@ var init_readers = __esm({
|
|
|
18105
19137
|
});
|
|
18106
19138
|
|
|
18107
19139
|
// src/strategies/library.ts
|
|
18108
|
-
import { writeFileSync as
|
|
18109
|
-
import { join as
|
|
19140
|
+
import { writeFileSync as writeFileSync15 } from "fs";
|
|
19141
|
+
import { join as join19 } from "path";
|
|
18110
19142
|
import { stringify as stringifyYaml2 } from "yaml";
|
|
18111
19143
|
function strategyLibraryPath(slug) {
|
|
18112
|
-
return
|
|
19144
|
+
return join19(getStrategiesDir(), `${slug}.md`);
|
|
18113
19145
|
}
|
|
18114
19146
|
function writeStrategyMarkdown(strategy) {
|
|
18115
19147
|
const path = strategyLibraryPath(strategy.slug);
|
|
18116
|
-
|
|
19148
|
+
writeFileSync15(path, renderStrategyMarkdown(strategy), "utf-8");
|
|
18117
19149
|
return path;
|
|
18118
19150
|
}
|
|
18119
19151
|
function renderStrategyMarkdown(strategy) {
|
|
@@ -18187,7 +19219,7 @@ var init_library = __esm({
|
|
|
18187
19219
|
// src/strategies/connectors.ts
|
|
18188
19220
|
import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
18189
19221
|
import { homedir as homedir7 } from "os";
|
|
18190
|
-
import { basename as basename6, extname as extname2, join as
|
|
19222
|
+
import { basename as basename6, extname as extname2, join as join20, relative, resolve as resolve8, sep as sep3 } from "path";
|
|
18191
19223
|
function createLocalFolderConnector(options) {
|
|
18192
19224
|
const rootPath = resolveUserPath2(options.rootPath);
|
|
18193
19225
|
const name = options.name ?? (basename6(rootPath) || "local");
|
|
@@ -18230,7 +19262,7 @@ function createLocalFolderConnector(options) {
|
|
|
18230
19262
|
}
|
|
18231
19263
|
function walkLocalFolder(rootPath, currentPath, refs, opts) {
|
|
18232
19264
|
for (const entry of readdirSync2(currentPath, { withFileTypes: true })) {
|
|
18233
|
-
const absolutePath =
|
|
19265
|
+
const absolutePath = join20(currentPath, entry.name);
|
|
18234
19266
|
const relativePath = normalizePath(relative(rootPath, absolutePath));
|
|
18235
19267
|
if (entry.isDirectory()) {
|
|
18236
19268
|
if (shouldSkipDirectory(entry.name) || matchesAny(relativePath, opts.excludePatterns)) continue;
|
|
@@ -18294,7 +19326,7 @@ function normalizePath(path) {
|
|
|
18294
19326
|
}
|
|
18295
19327
|
function resolveUserPath2(path) {
|
|
18296
19328
|
if (path === "~") return homedir7();
|
|
18297
|
-
if (path.startsWith("~/")) return
|
|
19329
|
+
if (path.startsWith("~/")) return join20(homedir7(), path.slice(2));
|
|
18298
19330
|
return resolve8(path);
|
|
18299
19331
|
}
|
|
18300
19332
|
var DEFAULT_MAX_FILES, DEFAULT_MAX_BYTES, SUPPORTED_EXTENSIONS, DEFAULT_EXCLUDED_DIRS;
|
|
@@ -18939,15 +19971,22 @@ __export(scratch_exports, {
|
|
|
18939
19971
|
handler: () => handler18
|
|
18940
19972
|
});
|
|
18941
19973
|
import chalk33 from "chalk";
|
|
18942
|
-
function printScratchPreamble() {
|
|
19974
|
+
function printScratchPreamble(includeProgress) {
|
|
18943
19975
|
console.log();
|
|
18944
19976
|
console.log(" " + chalk33.yellow.bold("This will permanently remove:"));
|
|
18945
19977
|
console.log(" " + chalk33.dim(" \u2022 API key and all config.json settings"));
|
|
18946
19978
|
console.log(" " + chalk33.dim(" \u2022 Company profile (you'll re-onboard on next use)"));
|
|
18947
19979
|
console.log(" " + chalk33.dim(" \u2022 All sessions and datasets"));
|
|
18948
19980
|
console.log(" " + chalk33.dim(" \u2022 Demo taxonomy cache"));
|
|
19981
|
+
if (includeProgress) {
|
|
19982
|
+
console.log(" " + chalk33.dim(" \u2022 Progress (hours saved) and install identity"));
|
|
19983
|
+
}
|
|
18949
19984
|
console.log();
|
|
18950
|
-
|
|
19985
|
+
if (includeProgress) {
|
|
19986
|
+
console.log(" " + chalk33.dim("Preserved: memory, strategies, wins, knowledge, exports, audit"));
|
|
19987
|
+
} else {
|
|
19988
|
+
console.log(" " + chalk33.dim("Preserved: progress (hours saved), memory, strategies, wins, knowledge, exports, audit"));
|
|
19989
|
+
}
|
|
18951
19990
|
console.log();
|
|
18952
19991
|
}
|
|
18953
19992
|
function resetContextAfterScratch(ctx) {
|
|
@@ -18969,24 +20008,26 @@ function resetContextAfterScratch(ctx) {
|
|
|
18969
20008
|
ctx.lastExchange = void 0;
|
|
18970
20009
|
}
|
|
18971
20010
|
async function handler18(args, ctx) {
|
|
18972
|
-
const { flags } = parseArgs(args, ["confirm"]);
|
|
20011
|
+
const { flags } = parseArgs(args, ["confirm", "include-progress"]);
|
|
18973
20012
|
const confirmedFlag = getBool(flags, "confirm");
|
|
20013
|
+
const includeProgress = getBool(flags, "include-progress");
|
|
18974
20014
|
const ok = await requireTypedWord(ctx, {
|
|
18975
|
-
title: "Factory reset",
|
|
20015
|
+
title: includeProgress ? "Factory reset (including progress)" : "Factory reset",
|
|
18976
20016
|
word: "scratch",
|
|
18977
20017
|
confirmedFlag,
|
|
18978
|
-
preamble: printScratchPreamble
|
|
20018
|
+
preamble: () => printScratchPreamble(includeProgress)
|
|
18979
20019
|
});
|
|
18980
20020
|
if (!ok) {
|
|
18981
20021
|
printAdminCancelled("Scratch", 'Type "scratch" exactly to confirm.');
|
|
18982
20022
|
return "Scratch cancelled";
|
|
18983
20023
|
}
|
|
18984
|
-
await performScratchWipe();
|
|
20024
|
+
await performScratchWipe({ includeProgress });
|
|
18985
20025
|
resetContextAfterScratch(ctx);
|
|
18986
20026
|
await rotateToFreshSession(ctx);
|
|
18987
20027
|
await initSchema();
|
|
18988
20028
|
console.log();
|
|
18989
|
-
|
|
20029
|
+
const detail = includeProgress ? " \u2014 local config, data, and progress wiped." : " \u2014 local config and data wiped.";
|
|
20030
|
+
console.log(" " + paint("accent", "\u2713 Scratch complete") + chalk33.dim(detail));
|
|
18990
20031
|
console.log();
|
|
18991
20032
|
if (ctx.oneShot) {
|
|
18992
20033
|
console.log(
|
|
@@ -19409,18 +20450,25 @@ __export(config_exports, {
|
|
|
19409
20450
|
handler: () => handler23
|
|
19410
20451
|
});
|
|
19411
20452
|
import chalk38 from "chalk";
|
|
20453
|
+
import ora10 from "ora";
|
|
20454
|
+
function secretKeys() {
|
|
20455
|
+
const keys = /* @__PURE__ */ new Set(["license-key", "license-instance-id", "voyage-api-key", "tavily-api-key", "brave-api-key"]);
|
|
20456
|
+
for (const spec of listProviderSpecs()) keys.add(spec.key_config_name);
|
|
20457
|
+
return keys;
|
|
20458
|
+
}
|
|
19412
20459
|
function display(key, value) {
|
|
19413
|
-
return
|
|
20460
|
+
return secretKeys().has(key) ? String(value).slice(0, 10) + "..." : String(value);
|
|
19414
20461
|
}
|
|
19415
20462
|
function secretPromptLabel(key) {
|
|
19416
|
-
|
|
19417
|
-
if (
|
|
20463
|
+
const spec = findSpecByConfigKey(key);
|
|
20464
|
+
if (spec) return `${spec.label} API key`;
|
|
19418
20465
|
if (key === "license-key") return "License key";
|
|
19419
20466
|
return key;
|
|
19420
20467
|
}
|
|
19421
20468
|
function usage() {
|
|
19422
20469
|
console.log(chalk38.dim(" Usage: /config <get|set|list|delete> [key] [value]"));
|
|
19423
20470
|
console.log(chalk38.dim(" Tip: ") + paint("accent", "/config set api-key") + chalk38.dim(" opens a hidden prompt (no inline paste)."));
|
|
20471
|
+
console.log(chalk38.dim(" Tip: ") + paint("accent", "/connect") + chalk38.dim(" auto-detects the provider from any pasted key."));
|
|
19424
20472
|
}
|
|
19425
20473
|
function fail(message, ctx) {
|
|
19426
20474
|
console.error(chalk38.red(` ${message}`));
|
|
@@ -19452,7 +20500,7 @@ async function handler23(args, ctx) {
|
|
|
19452
20500
|
return;
|
|
19453
20501
|
}
|
|
19454
20502
|
let value = inlineValue;
|
|
19455
|
-
if (!value &&
|
|
20503
|
+
if (!value && secretKeys().has(key)) {
|
|
19456
20504
|
try {
|
|
19457
20505
|
value = await promptSecretValue(key, ctx);
|
|
19458
20506
|
} catch (err) {
|
|
@@ -19472,8 +20520,24 @@ async function handler23(args, ctx) {
|
|
|
19472
20520
|
}
|
|
19473
20521
|
console.log();
|
|
19474
20522
|
console.log(chalk38.green(` \u2713 ${key} saved`) + chalk38.dim(` (${display(key, value)})`));
|
|
19475
|
-
|
|
19476
|
-
|
|
20523
|
+
const spec = findSpecByConfigKey(key);
|
|
20524
|
+
if (spec) {
|
|
20525
|
+
const spinner = ora10({ text: `Discovering ${spec.label} models\u2026`, discardStdin: false }).start();
|
|
20526
|
+
try {
|
|
20527
|
+
const { refreshProviderModels: refreshProviderModels2 } = await Promise.resolve().then(() => (init_discovery(), discovery_exports));
|
|
20528
|
+
const entry = await refreshProviderModels2(spec.id, { apiKey: value, force: true });
|
|
20529
|
+
if (entry) {
|
|
20530
|
+
spinner.succeed(`${spec.label}: ${entry.models.length} chat models available.`);
|
|
20531
|
+
console.log(
|
|
20532
|
+
" " + chalk38.dim(`high ${entry.tier_stack.high} \xB7 medium ${entry.tier_stack.medium} \xB7 low ${entry.tier_stack.low}`)
|
|
20533
|
+
);
|
|
20534
|
+
} else {
|
|
20535
|
+
spinner.warn(`${spec.label} unreachable \u2014 models will be discovered on first use.`);
|
|
20536
|
+
}
|
|
20537
|
+
} catch {
|
|
20538
|
+
spinner.warn(`${spec.label} unreachable \u2014 models will be discovered on first use.`);
|
|
20539
|
+
}
|
|
20540
|
+
console.log(" " + chalk38.dim("Switch engines with /provider \xB7 browse models with /model list."));
|
|
19477
20541
|
}
|
|
19478
20542
|
console.log();
|
|
19479
20543
|
return;
|
|
@@ -19516,15 +20580,14 @@ async function handler23(args, ctx) {
|
|
|
19516
20580
|
}
|
|
19517
20581
|
}
|
|
19518
20582
|
}
|
|
19519
|
-
var SECRET_KEYS;
|
|
19520
20583
|
var init_config = __esm({
|
|
19521
20584
|
"src/commands/config.ts"() {
|
|
19522
20585
|
"use strict";
|
|
19523
20586
|
init_store();
|
|
20587
|
+
init_providers();
|
|
19524
20588
|
init_argparse();
|
|
19525
20589
|
init_prompts();
|
|
19526
20590
|
init_theme();
|
|
19527
|
-
SECRET_KEYS = /* @__PURE__ */ new Set(["api-key", "openai-api-key", "license-key", "license-instance-id"]);
|
|
19528
20591
|
}
|
|
19529
20592
|
});
|
|
19530
20593
|
|
|
@@ -20213,15 +21276,15 @@ var init_checkout = __esm({
|
|
|
20213
21276
|
});
|
|
20214
21277
|
|
|
20215
21278
|
// src/services/setup.ts
|
|
20216
|
-
import { existsSync as
|
|
20217
|
-
import { join as
|
|
21279
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync16 } from "fs";
|
|
21280
|
+
import { join as join21 } from "path";
|
|
20218
21281
|
function setupCheck() {
|
|
20219
21282
|
const home = ntrpHome();
|
|
20220
21283
|
let writable = false;
|
|
20221
21284
|
try {
|
|
20222
21285
|
mkdirSync11(home, { recursive: true });
|
|
20223
|
-
const probe =
|
|
20224
|
-
|
|
21286
|
+
const probe = join21(home, ".write-check");
|
|
21287
|
+
writeFileSync16(probe, "ok\n");
|
|
20225
21288
|
writable = true;
|
|
20226
21289
|
} catch {
|
|
20227
21290
|
writable = false;
|
|
@@ -20246,7 +21309,8 @@ function setupCheck() {
|
|
|
20246
21309
|
tier: llmCfg.tier,
|
|
20247
21310
|
auto_failover: llmCfg.autoFailover,
|
|
20248
21311
|
anthropic: llmReady.anthropic,
|
|
20249
|
-
openai: llmReady.openai
|
|
21312
|
+
openai: llmReady.openai,
|
|
21313
|
+
providers: llmReady.providers
|
|
20250
21314
|
}
|
|
20251
21315
|
},
|
|
20252
21316
|
license: {
|
|
@@ -20258,7 +21322,7 @@ function setupCheck() {
|
|
|
20258
21322
|
};
|
|
20259
21323
|
}
|
|
20260
21324
|
function readProfileInput(pathOrDash) {
|
|
20261
|
-
const raw = pathOrDash === "-" ?
|
|
21325
|
+
const raw = pathOrDash === "-" ? readFileSync14(0, "utf-8") : readFileSync14(pathOrDash, "utf-8");
|
|
20262
21326
|
return JSON.parse(raw);
|
|
20263
21327
|
}
|
|
20264
21328
|
function writeAgentProfile(input) {
|
|
@@ -20283,11 +21347,15 @@ function writeAgentProfile(input) {
|
|
|
20283
21347
|
saveProfile(profile);
|
|
20284
21348
|
return profile;
|
|
20285
21349
|
}
|
|
20286
|
-
function applyAgentConfig(opts) {
|
|
21350
|
+
async function applyAgentConfig(opts) {
|
|
20287
21351
|
if (opts.defaultFormat) setConfigValue("default-format", opts.defaultFormat);
|
|
20288
21352
|
if (opts.apiKey) setConfigValue("api-key", opts.apiKey);
|
|
20289
21353
|
if (opts.openaiApiKey) setConfigValue("openai-api-key", opts.openaiApiKey);
|
|
20290
|
-
if (opts.
|
|
21354
|
+
if (opts.llmKey) {
|
|
21355
|
+
const { connectWithKey: connectWithKey2 } = await Promise.resolve().then(() => (init_connect(), connect_exports));
|
|
21356
|
+
await connectWithKey2(opts.llmKey, { providerId: opts.llmProvider });
|
|
21357
|
+
}
|
|
21358
|
+
if (opts.llmPrimary && getProviderSpec(opts.llmPrimary)) {
|
|
20291
21359
|
setConfigValue("llm-primary", opts.llmPrimary);
|
|
20292
21360
|
}
|
|
20293
21361
|
if (opts.licenseKey) setConfigValue("license-key", opts.licenseKey);
|
|
@@ -20297,6 +21365,7 @@ var init_setup = __esm({
|
|
|
20297
21365
|
"src/services/setup.ts"() {
|
|
20298
21366
|
"use strict";
|
|
20299
21367
|
init_repl_api();
|
|
21368
|
+
init_providers();
|
|
20300
21369
|
init_llm_config();
|
|
20301
21370
|
init_store();
|
|
20302
21371
|
init_profile();
|
|
@@ -20339,13 +21408,12 @@ async function handler27(args, ctx) {
|
|
|
20339
21408
|
console.log(` Writable: ${result.writable ? "yes" : "no"}`);
|
|
20340
21409
|
console.log(` Profile: ${result.profile.exists ? "ready" : "missing"} (${result.profile.path})`);
|
|
20341
21410
|
const llm = result.config.llm;
|
|
20342
|
-
if (llm) {
|
|
20343
|
-
|
|
20344
|
-
console.log(`
|
|
20345
|
-
console.log(` Anthropic: ${llm.anthropic ? "set" : "missing"} \xB7 OpenAI: ${llm.openai ? "set" : "missing"}`);
|
|
21411
|
+
if (llm && llm.providers.length > 0) {
|
|
21412
|
+
console.log(` Engines: ${llm.providers.length} \xB7 default ${llm.primary} \xB7 tier ${llm.tier}`);
|
|
21413
|
+
console.log(` Connected: ${llm.providers.join(", ")}`);
|
|
20346
21414
|
console.log(` Auto-failover: ${llm.auto_failover ? "on" : "off"}`);
|
|
20347
21415
|
} else {
|
|
20348
|
-
console.log(" Engines: missing");
|
|
21416
|
+
console.log(" Engines: missing \u2014 run /connect with any provider key");
|
|
20349
21417
|
}
|
|
20350
21418
|
console.log(` License: ${formatLicenseSetupLine(result.license)}`);
|
|
20351
21419
|
console.log();
|
|
@@ -20366,10 +21434,12 @@ async function handler27(args, ctx) {
|
|
|
20366
21434
|
sales_motion: getString(flags, "sales-motion")
|
|
20367
21435
|
};
|
|
20368
21436
|
}
|
|
20369
|
-
applyAgentConfig({
|
|
21437
|
+
await applyAgentConfig({
|
|
20370
21438
|
defaultFormat: getString(flags, "default-format"),
|
|
20371
21439
|
apiKey: getString(flags, "api-key"),
|
|
20372
21440
|
openaiApiKey: getString(flags, "openai-api-key"),
|
|
21441
|
+
llmKey: getString(flags, "llm-key"),
|
|
21442
|
+
llmProvider: getString(flags, "llm-provider"),
|
|
20373
21443
|
llmPrimary: getString(flags, "llm-primary"),
|
|
20374
21444
|
licenseKey: getString(flags, "license-key"),
|
|
20375
21445
|
exportDir: getString(flags, "export-dir")
|
|
@@ -20409,13 +21479,13 @@ var init_setup2 = __esm({
|
|
|
20409
21479
|
|
|
20410
21480
|
// src/conversation/orchestrator.ts
|
|
20411
21481
|
import chalk43 from "chalk";
|
|
20412
|
-
import { writeFileSync as
|
|
20413
|
-
import { join as
|
|
21482
|
+
import { writeFileSync as writeFileSync17 } from "fs";
|
|
21483
|
+
import { join as join22 } from "path";
|
|
20414
21484
|
async function handleExploreWithoutKey(ctx) {
|
|
20415
21485
|
console.log();
|
|
20416
21486
|
console.log(" " + chalk43.red("AI interpretation needs an LLM API key saved in config."));
|
|
20417
21487
|
console.log(
|
|
20418
|
-
" " + chalk43.dim("
|
|
21488
|
+
" " + chalk43.dim("Run ") + paint("accent", "/connect") + chalk43.dim(" and paste any provider's key (Anthropic, OpenAI, Groq, Gemini, ...).")
|
|
20419
21489
|
);
|
|
20420
21490
|
console.log(" " + chalk43.dim("Number crunching works without a key \u2014 only Q&A in the REPL needs one."));
|
|
20421
21491
|
if (ctx.gapAudit) {
|
|
@@ -20587,8 +21657,8 @@ async function callProvider(texts) {
|
|
|
20587
21657
|
async function embedText(text) {
|
|
20588
21658
|
const key = text.trim();
|
|
20589
21659
|
if (!key) return null;
|
|
20590
|
-
const
|
|
20591
|
-
if (
|
|
21660
|
+
const cached2 = cache.get(key);
|
|
21661
|
+
if (cached2) return cached2;
|
|
20592
21662
|
const result = await callProvider([key]);
|
|
20593
21663
|
const vec = result?.[0] ?? null;
|
|
20594
21664
|
if (vec) cache.set(key, vec);
|
|
@@ -20598,8 +21668,8 @@ async function embedItems(items) {
|
|
|
20598
21668
|
const needing = [];
|
|
20599
21669
|
const out = items.map((it, index) => {
|
|
20600
21670
|
if (it.embedding && it.embedding.length > 0) return { ...it };
|
|
20601
|
-
const
|
|
20602
|
-
if (
|
|
21671
|
+
const cached2 = cache.get(it.text.trim());
|
|
21672
|
+
if (cached2) return { ...it, embedding: cached2 };
|
|
20603
21673
|
needing.push({ index, text: it.text });
|
|
20604
21674
|
return { ...it };
|
|
20605
21675
|
});
|
|
@@ -20758,17 +21828,17 @@ var init_retrieval = __esm({
|
|
|
20758
21828
|
});
|
|
20759
21829
|
|
|
20760
21830
|
// src/memory/knowledge.ts
|
|
20761
|
-
import { existsSync as
|
|
20762
|
-
import { join as
|
|
21831
|
+
import { existsSync as existsSync19, readFileSync as readFileSync15, appendFileSync as appendFileSync3, readdirSync as readdirSync3 } from "fs";
|
|
21832
|
+
import { join as join23 } from "path";
|
|
20763
21833
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
20764
21834
|
function knowledgePath() {
|
|
20765
|
-
return
|
|
21835
|
+
return join23(getMemoryDir(), KNOWLEDGE_FILE);
|
|
20766
21836
|
}
|
|
20767
21837
|
function loadKnowledgeChunks() {
|
|
20768
21838
|
const path = knowledgePath();
|
|
20769
|
-
if (!
|
|
21839
|
+
if (!existsSync19(path)) return [];
|
|
20770
21840
|
const out = [];
|
|
20771
|
-
for (const line of
|
|
21841
|
+
for (const line of readFileSync15(path, "utf-8").split("\n")) {
|
|
20772
21842
|
const trimmed = line.trim();
|
|
20773
21843
|
if (!trimmed) continue;
|
|
20774
21844
|
try {
|
|
@@ -20869,17 +21939,17 @@ __export(store_exports2, {
|
|
|
20869
21939
|
rewriteJsonl: () => rewriteJsonl,
|
|
20870
21940
|
scrubText: () => scrubText
|
|
20871
21941
|
});
|
|
20872
|
-
import { existsSync as
|
|
20873
|
-
import { join as
|
|
21942
|
+
import { existsSync as existsSync20, readFileSync as readFileSync16, appendFileSync as appendFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync18 } from "fs";
|
|
21943
|
+
import { join as join24 } from "path";
|
|
20874
21944
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
20875
21945
|
function memPath(file) {
|
|
20876
|
-
return
|
|
21946
|
+
return join24(getMemoryDir(), file);
|
|
20877
21947
|
}
|
|
20878
21948
|
function readJsonl(file) {
|
|
20879
21949
|
const path = memPath(file);
|
|
20880
|
-
if (!
|
|
21950
|
+
if (!existsSync20(path)) return [];
|
|
20881
21951
|
const out = [];
|
|
20882
|
-
for (const line of
|
|
21952
|
+
for (const line of readFileSync16(path, "utf-8").split("\n")) {
|
|
20883
21953
|
const trimmed = line.trim();
|
|
20884
21954
|
if (!trimmed) continue;
|
|
20885
21955
|
try {
|
|
@@ -20897,7 +21967,7 @@ function appendJsonl(file, obj) {
|
|
|
20897
21967
|
}
|
|
20898
21968
|
function rewriteJsonl(file, rows) {
|
|
20899
21969
|
try {
|
|
20900
|
-
|
|
21970
|
+
writeFileSync18(memPath(file), rows.map((r) => JSON.stringify(r)).join("\n") + (rows.length ? "\n" : ""));
|
|
20901
21971
|
} catch {
|
|
20902
21972
|
}
|
|
20903
21973
|
}
|
|
@@ -20959,7 +22029,7 @@ function loadWinSnippets() {
|
|
|
20959
22029
|
const out = [];
|
|
20960
22030
|
for (const name of readdirSync4(dir)) {
|
|
20961
22031
|
if (!name.endsWith(".md") || name.toLowerCase() === "readme.md") continue;
|
|
20962
|
-
const raw =
|
|
22032
|
+
const raw = readFileSync16(join24(dir, name), "utf-8");
|
|
20963
22033
|
const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? name.replace(/\.md$/, "");
|
|
20964
22034
|
const body = raw.replace(/^#.*$/m, "").replace(/\s+/g, " ").trim().slice(0, 300);
|
|
20965
22035
|
out.push({ id: `win:${name}`, title, text: `${title}. ${body}` });
|
|
@@ -21048,7 +22118,7 @@ var init_store2 = __esm({
|
|
|
21048
22118
|
});
|
|
21049
22119
|
|
|
21050
22120
|
// src/services/smoke-protocol.ts
|
|
21051
|
-
import { join as
|
|
22121
|
+
import { join as join25 } from "path";
|
|
21052
22122
|
function isSmokeProtocolTrigger(input) {
|
|
21053
22123
|
return normalize(input).includes(SMOKE_TRIGGER_PHRASE);
|
|
21054
22124
|
}
|
|
@@ -21084,7 +22154,7 @@ async function runSmokeProtocol(_input, ctx) {
|
|
|
21084
22154
|
});
|
|
21085
22155
|
const proposalResult = await proposeRepositoryExport({
|
|
21086
22156
|
target: "markdown",
|
|
21087
|
-
directory:
|
|
22157
|
+
directory: join25(getExportsDir(), "repository-smoke"),
|
|
21088
22158
|
source: "smoke_protocol",
|
|
21089
22159
|
modelOrFixture: "smoke-protocol-v1"
|
|
21090
22160
|
});
|
|
@@ -21177,13 +22247,13 @@ var init_smoke_protocol = __esm({
|
|
|
21177
22247
|
});
|
|
21178
22248
|
|
|
21179
22249
|
// src/cli/nl.ts
|
|
21180
|
-
import
|
|
22250
|
+
import ora11 from "ora";
|
|
21181
22251
|
import chalk44 from "chalk";
|
|
21182
22252
|
async function runNaturalLanguage(input, ctx) {
|
|
21183
22253
|
if (isSmokeProtocolTrigger(input)) {
|
|
21184
22254
|
recordMessage(ctx, "user", input);
|
|
21185
22255
|
console.log();
|
|
21186
|
-
const spinner2 =
|
|
22256
|
+
const spinner2 = ora11({ text: "Running smoke protocol\u2026", color: "cyan", discardStdin: false }).start();
|
|
21187
22257
|
try {
|
|
21188
22258
|
const result = await runSmokeProtocol(input, ctx);
|
|
21189
22259
|
spinner2.succeed("Smoke protocol complete");
|
|
@@ -21210,7 +22280,7 @@ async function runNaturalLanguage(input, ctx) {
|
|
|
21210
22280
|
let snapshot = ctx.snapshot.computeResult;
|
|
21211
22281
|
if (!snapshot) {
|
|
21212
22282
|
const metricsFirst = prefersMetricsFirstContext(ctx);
|
|
21213
|
-
const spinner2 =
|
|
22283
|
+
const spinner2 = ora11({
|
|
21214
22284
|
text: metricsFirst ? "Loading session context\u2026" : "Computing vital signs\u2026",
|
|
21215
22285
|
color: "cyan",
|
|
21216
22286
|
discardStdin: false
|
|
@@ -21235,7 +22305,7 @@ async function runNaturalLanguage(input, ctx) {
|
|
|
21235
22305
|
}
|
|
21236
22306
|
console.log();
|
|
21237
22307
|
const memoryBlock = await buildMemoryBlock(input).catch(() => "");
|
|
21238
|
-
const spinner =
|
|
22308
|
+
const spinner = ora11({ text: "Thinking\u2026", color: "cyan", discardStdin: false }).start();
|
|
21239
22309
|
let lastAnswer = "";
|
|
21240
22310
|
let rawHistory = [];
|
|
21241
22311
|
const toolsUsed = [];
|
|
@@ -21526,7 +22596,7 @@ __export(metrics_exports, {
|
|
|
21526
22596
|
handler: () => handler29
|
|
21527
22597
|
});
|
|
21528
22598
|
import chalk46 from "chalk";
|
|
21529
|
-
import
|
|
22599
|
+
import ora12 from "ora";
|
|
21530
22600
|
async function handler29(args, ctx) {
|
|
21531
22601
|
await hydrateAnalysisFromPersistedState(ctx);
|
|
21532
22602
|
const { flags } = parseArgs(args, ["findings"]);
|
|
@@ -21568,7 +22638,7 @@ async function handler29(args, ctx) {
|
|
|
21568
22638
|
await initSchema();
|
|
21569
22639
|
await autoGenerateSegments();
|
|
21570
22640
|
printCompanionBanner("metrics", ctx.analysis.primary);
|
|
21571
|
-
const spinner =
|
|
22641
|
+
const spinner = ora12({
|
|
21572
22642
|
text: "Computing SaaS metrics\u2026",
|
|
21573
22643
|
indent: 2,
|
|
21574
22644
|
discardStdin: false
|
|
@@ -21767,7 +22837,7 @@ __export(feedback_exports, {
|
|
|
21767
22837
|
handler: () => handler30
|
|
21768
22838
|
});
|
|
21769
22839
|
import chalk47 from "chalk";
|
|
21770
|
-
import
|
|
22840
|
+
import ora13 from "ora";
|
|
21771
22841
|
async function handler30(args, ctx) {
|
|
21772
22842
|
const feedbackText = args.join(" ").trim();
|
|
21773
22843
|
if (!feedbackText) {
|
|
@@ -21795,7 +22865,7 @@ async function handler30(args, ctx) {
|
|
|
21795
22865
|
console.log();
|
|
21796
22866
|
return;
|
|
21797
22867
|
}
|
|
21798
|
-
const spinner =
|
|
22868
|
+
const spinner = ora13({ text: "Applying feedback\u2026", discardStdin: false }).start();
|
|
21799
22869
|
try {
|
|
21800
22870
|
const result = await applyFeedback(profile, feedbackText, ctx);
|
|
21801
22871
|
spinner.succeed("Feedback applied");
|
|
@@ -21827,7 +22897,7 @@ var recap_exports = {};
|
|
|
21827
22897
|
__export(recap_exports, {
|
|
21828
22898
|
handler: () => handler31
|
|
21829
22899
|
});
|
|
21830
|
-
import
|
|
22900
|
+
import ora14 from "ora";
|
|
21831
22901
|
import chalk48 from "chalk";
|
|
21832
22902
|
async function handler31(_args, ctx) {
|
|
21833
22903
|
if (ctx.messages.length === 0) {
|
|
@@ -21864,7 +22934,7 @@ ${companyBlock}` : "",
|
|
|
21864
22934
|
const prefix = msg.role === "user" ? "USER" : "ASSISTANT";
|
|
21865
22935
|
conversationLines.push(`[${prefix}]: ${msg.content}`);
|
|
21866
22936
|
}
|
|
21867
|
-
const spinner =
|
|
22937
|
+
const spinner = ora14({ text: "Summarizing session\u2026", color: "cyan", discardStdin: false }).start();
|
|
21868
22938
|
try {
|
|
21869
22939
|
const { text: fullText } = await llmStreamText(
|
|
21870
22940
|
"recap",
|
|
@@ -21992,7 +23062,7 @@ var init_recall = __esm({
|
|
|
21992
23062
|
|
|
21993
23063
|
// src/memory/feedback.ts
|
|
21994
23064
|
import { appendFileSync as appendFileSync5 } from "fs";
|
|
21995
|
-
import { join as
|
|
23065
|
+
import { join as join26 } from "path";
|
|
21996
23066
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
21997
23067
|
function summarize(text) {
|
|
21998
23068
|
return text.replace(/[#*`>_]/g, "").replace(/\s+/g, " ").trim().slice(0, 200);
|
|
@@ -22008,7 +23078,7 @@ function recordFeedback(input) {
|
|
|
22008
23078
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
22009
23079
|
};
|
|
22010
23080
|
try {
|
|
22011
|
-
appendFileSync5(
|
|
23081
|
+
appendFileSync5(join26(getMemoryDir(), "feedback.jsonl"), JSON.stringify(entry) + "\n");
|
|
22012
23082
|
} catch {
|
|
22013
23083
|
}
|
|
22014
23084
|
if (input.rating === "positive") {
|
|
@@ -22099,7 +23169,7 @@ __export(knowledge_exports, {
|
|
|
22099
23169
|
handler: () => handler35
|
|
22100
23170
|
});
|
|
22101
23171
|
import chalk52 from "chalk";
|
|
22102
|
-
import
|
|
23172
|
+
import ora15 from "ora";
|
|
22103
23173
|
async function handler35(args, ctx) {
|
|
22104
23174
|
const sub = (args[0] ?? "list").toLowerCase();
|
|
22105
23175
|
if (sub === "add") {
|
|
@@ -22111,7 +23181,7 @@ async function handler35(args, ctx) {
|
|
|
22111
23181
|
console.log();
|
|
22112
23182
|
return;
|
|
22113
23183
|
}
|
|
22114
|
-
const spin = ctx.execution.progress ?
|
|
23184
|
+
const spin = ctx.execution.progress ? ora15({ text: "Ingesting knowledge\u2026", color: "cyan", discardStdin: false }).start() : null;
|
|
22115
23185
|
try {
|
|
22116
23186
|
const result = await addKnowledgeFile(path);
|
|
22117
23187
|
spin?.succeed(`Indexed "${result.title}"`);
|
|
@@ -22421,8 +23491,8 @@ var switch_exports = {};
|
|
|
22421
23491
|
__export(switch_exports, {
|
|
22422
23492
|
handler: () => handler39
|
|
22423
23493
|
});
|
|
22424
|
-
import { join as
|
|
22425
|
-
import
|
|
23494
|
+
import { join as join27 } from "path";
|
|
23495
|
+
import ora16 from "ora";
|
|
22426
23496
|
import chalk56 from "chalk";
|
|
22427
23497
|
async function handler39(args, ctx) {
|
|
22428
23498
|
if (args.length === 0) {
|
|
@@ -22437,7 +23507,7 @@ async function handler39(args, ctx) {
|
|
|
22437
23507
|
}
|
|
22438
23508
|
const exchangeCount = Math.floor(ctx.messages.length / 2);
|
|
22439
23509
|
if (exchangeCount > 0) {
|
|
22440
|
-
const spinner =
|
|
23510
|
+
const spinner = ora16({ text: "Saving current session\u2026", color: "cyan", discardStdin: false }).start();
|
|
22441
23511
|
await closeSession(ctx);
|
|
22442
23512
|
const fromLabel = ctx.sessionName ? `"${ctx.sessionName}"` : ctx.sessionId.slice(-4);
|
|
22443
23513
|
spinner.succeed(`Saved ${fromLabel}`);
|
|
@@ -22452,7 +23522,7 @@ async function handler39(args, ctx) {
|
|
|
22452
23522
|
}
|
|
22453
23523
|
const context = buildSwitchContext(session);
|
|
22454
23524
|
const newId = makeSessionId();
|
|
22455
|
-
const newFile =
|
|
23525
|
+
const newFile = join27(getSessionsDir(), `${newId}.json`);
|
|
22456
23526
|
resetContextForSwitch(ctx, {
|
|
22457
23527
|
sessionId: newId,
|
|
22458
23528
|
sessionFile: newFile,
|
|
@@ -22478,7 +23548,7 @@ async function handler39(args, ctx) {
|
|
|
22478
23548
|
return `Switched to "${targetName}"`;
|
|
22479
23549
|
} else {
|
|
22480
23550
|
const newId = makeSessionId();
|
|
22481
|
-
const newFile =
|
|
23551
|
+
const newFile = join27(getSessionsDir(), `${newId}.json`);
|
|
22482
23552
|
resetContextForSwitch(ctx, {
|
|
22483
23553
|
sessionId: newId,
|
|
22484
23554
|
sessionFile: newFile,
|
|
@@ -22528,13 +23598,177 @@ var init_switch = __esm({
|
|
|
22528
23598
|
}
|
|
22529
23599
|
});
|
|
22530
23600
|
|
|
22531
|
-
// src/commands/
|
|
22532
|
-
var
|
|
22533
|
-
__export(
|
|
23601
|
+
// src/commands/connect.ts
|
|
23602
|
+
var connect_exports2 = {};
|
|
23603
|
+
__export(connect_exports2, {
|
|
22534
23604
|
handler: () => handler40
|
|
22535
23605
|
});
|
|
22536
23606
|
import chalk57 from "chalk";
|
|
23607
|
+
import ora17 from "ora";
|
|
23608
|
+
function usage2() {
|
|
23609
|
+
console.log(chalk57.dim(" Usage: /connect paste any provider key"));
|
|
23610
|
+
console.log(chalk57.dim(" /connect <provider> key for a specific provider (or: ollama)"));
|
|
23611
|
+
console.log(chalk57.dim(" /connect --key <key> non-interactive (auto-detects provider)"));
|
|
23612
|
+
console.log(chalk57.dim(" /connect --base-url <url> [--id <name>] [--key <key>] custom endpoint"));
|
|
23613
|
+
}
|
|
23614
|
+
function printOutcome(outcome, ctx) {
|
|
23615
|
+
console.log();
|
|
23616
|
+
const [headline, ...rest] = describeConnectOutcome(outcome);
|
|
23617
|
+
console.log(" " + paint("success", "\u2713") + " " + chalk57.bold(headline ?? ""));
|
|
23618
|
+
for (const line of rest) {
|
|
23619
|
+
console.log(" " + chalk57.dim(line));
|
|
23620
|
+
}
|
|
23621
|
+
console.log();
|
|
23622
|
+
console.log(" " + chalk57.dim(`Active stack: ${formatActiveStack(ctx)}`));
|
|
23623
|
+
console.log(" " + chalk57.dim("/provider to switch engines \xB7 /model list to browse models"));
|
|
23624
|
+
console.log();
|
|
23625
|
+
}
|
|
23626
|
+
function printError(err, ctx) {
|
|
23627
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23628
|
+
console.log();
|
|
23629
|
+
console.log(" " + chalk57.red(message));
|
|
23630
|
+
console.log();
|
|
23631
|
+
if (ctx.oneShot) process.exit(1);
|
|
23632
|
+
}
|
|
23633
|
+
async function promptKey(session, label) {
|
|
23634
|
+
console.log();
|
|
23635
|
+
console.log(
|
|
23636
|
+
" " + chalk57.dim("Paste once, press Enter. Stored in ") + paint("accent", "~/.ntrp/config.json") + chalk57.dim(" only.")
|
|
23637
|
+
);
|
|
23638
|
+
return session.askSecret(label, { confirm: false });
|
|
23639
|
+
}
|
|
22537
23640
|
async function handler40(args, ctx) {
|
|
23641
|
+
const { positional, flags } = parseArgs(args);
|
|
23642
|
+
const sub = positional[0]?.toLowerCase();
|
|
23643
|
+
if (sub === "help") {
|
|
23644
|
+
usage2();
|
|
23645
|
+
return;
|
|
23646
|
+
}
|
|
23647
|
+
const inlineKey = getString(flags, "key");
|
|
23648
|
+
const baseUrl = getString(flags, "base-url", "url");
|
|
23649
|
+
const forcedProvider = getString(flags, "provider") ?? (sub && sub !== "help" ? sub : void 0);
|
|
23650
|
+
const customId = getString(flags, "id");
|
|
23651
|
+
const label = getString(flags, "label");
|
|
23652
|
+
const forcedSpec = forcedProvider ? getProviderSpec(forcedProvider) : void 0;
|
|
23653
|
+
if (forcedSpec && !forcedSpec.requires_key && !inlineKey) {
|
|
23654
|
+
const spinner2 = ora17({ text: `Looking for ${forcedSpec.label}\u2026`, discardStdin: false }).start();
|
|
23655
|
+
try {
|
|
23656
|
+
const outcome = await connectKeyless(forcedSpec.id, baseUrl);
|
|
23657
|
+
spinner2.stop();
|
|
23658
|
+
printOutcome(outcome, ctx);
|
|
23659
|
+
} catch (err) {
|
|
23660
|
+
spinner2.stop();
|
|
23661
|
+
printError(err, ctx);
|
|
23662
|
+
}
|
|
23663
|
+
return;
|
|
23664
|
+
}
|
|
23665
|
+
if (baseUrl && !forcedSpec) {
|
|
23666
|
+
const id = customId ?? forcedProvider ?? hostToId(baseUrl);
|
|
23667
|
+
let key2 = inlineKey;
|
|
23668
|
+
if (!key2 && !ctx.oneShot && process.stdin.isTTY) {
|
|
23669
|
+
const session2 = createPromptSession(ctx.rl, ctx);
|
|
23670
|
+
try {
|
|
23671
|
+
const needsKey = await session2.confirm("Does this endpoint need an API key?", false);
|
|
23672
|
+
if (needsKey) key2 = await promptKey(session2, `API key for ${id}`);
|
|
23673
|
+
} finally {
|
|
23674
|
+
session2.close();
|
|
23675
|
+
}
|
|
23676
|
+
}
|
|
23677
|
+
const spinner2 = ora17({ text: `Checking ${baseUrl}\u2026`, discardStdin: false }).start();
|
|
23678
|
+
try {
|
|
23679
|
+
const outcome = await connectCustomEndpoint({ id, baseUrl, key: key2, label });
|
|
23680
|
+
spinner2.stop();
|
|
23681
|
+
printOutcome(outcome, ctx);
|
|
23682
|
+
} catch (err) {
|
|
23683
|
+
spinner2.stop();
|
|
23684
|
+
printError(err, ctx);
|
|
23685
|
+
}
|
|
23686
|
+
return;
|
|
23687
|
+
}
|
|
23688
|
+
if (forcedProvider && !forcedSpec) {
|
|
23689
|
+
console.log();
|
|
23690
|
+
console.log(" " + chalk57.red(`Unknown provider: ${forcedProvider}`));
|
|
23691
|
+
console.log(
|
|
23692
|
+
" " + chalk57.dim("Built-ins: anthropic, openai, google, groq, mistral, deepseek, xai, openrouter, together, fireworks, ollama")
|
|
23693
|
+
);
|
|
23694
|
+
console.log(" " + chalk57.dim(`Custom endpoint: /connect --base-url <url> --id ${forcedProvider}`));
|
|
23695
|
+
console.log();
|
|
23696
|
+
if (ctx.oneShot) process.exit(1);
|
|
23697
|
+
return;
|
|
23698
|
+
}
|
|
23699
|
+
let key = inlineKey;
|
|
23700
|
+
let session;
|
|
23701
|
+
if (!key) {
|
|
23702
|
+
if (ctx.oneShot || !process.stdin.isTTY) {
|
|
23703
|
+
printError(new ConnectError("Non-interactive mode needs --key <key>."), ctx);
|
|
23704
|
+
usage2();
|
|
23705
|
+
return;
|
|
23706
|
+
}
|
|
23707
|
+
session = createPromptSession(ctx.rl, ctx);
|
|
23708
|
+
key = await promptKey(
|
|
23709
|
+
session,
|
|
23710
|
+
forcedSpec ? `${forcedSpec.label} API key` : "LLM API key (any provider)"
|
|
23711
|
+
);
|
|
23712
|
+
}
|
|
23713
|
+
const spinner = ora17({ text: "Identifying provider\u2026", discardStdin: false }).start();
|
|
23714
|
+
try {
|
|
23715
|
+
const outcome = await connectWithKey(key, {
|
|
23716
|
+
providerId: forcedSpec?.id,
|
|
23717
|
+
callbacks: session ? {
|
|
23718
|
+
confirmDetection: async (providerId) => {
|
|
23719
|
+
spinner.stop();
|
|
23720
|
+
return session.confirm(`Detected ${providerLabel(providerId)} \u2014 connect it?`, true);
|
|
23721
|
+
},
|
|
23722
|
+
chooseProvider: async (accepted) => {
|
|
23723
|
+
spinner.stop();
|
|
23724
|
+
return session.choose(
|
|
23725
|
+
"Multiple providers accepted this key \u2014 which is it?",
|
|
23726
|
+
accepted.map((a) => ({ value: a.provider, label: providerLabel(a.provider) }))
|
|
23727
|
+
);
|
|
23728
|
+
}
|
|
23729
|
+
} : void 0
|
|
23730
|
+
});
|
|
23731
|
+
spinner.stop();
|
|
23732
|
+
printOutcome(outcome, ctx);
|
|
23733
|
+
} catch (err) {
|
|
23734
|
+
spinner.stop();
|
|
23735
|
+
if (err instanceof ConnectCancelled) {
|
|
23736
|
+
console.log(" " + chalk57.dim("Cancelled."));
|
|
23737
|
+
console.log();
|
|
23738
|
+
} else {
|
|
23739
|
+
printError(err, ctx);
|
|
23740
|
+
}
|
|
23741
|
+
} finally {
|
|
23742
|
+
session?.close();
|
|
23743
|
+
}
|
|
23744
|
+
}
|
|
23745
|
+
function hostToId(url) {
|
|
23746
|
+
try {
|
|
23747
|
+
const host = new URL(url).hostname;
|
|
23748
|
+
return host.replace(/^www\./, "").split(".")[0] ?? "custom";
|
|
23749
|
+
} catch {
|
|
23750
|
+
return "custom";
|
|
23751
|
+
}
|
|
23752
|
+
}
|
|
23753
|
+
var init_connect2 = __esm({
|
|
23754
|
+
"src/commands/connect.ts"() {
|
|
23755
|
+
"use strict";
|
|
23756
|
+
init_argparse();
|
|
23757
|
+
init_prompts();
|
|
23758
|
+
init_providers();
|
|
23759
|
+
init_session_state();
|
|
23760
|
+
init_connect();
|
|
23761
|
+
init_theme();
|
|
23762
|
+
}
|
|
23763
|
+
});
|
|
23764
|
+
|
|
23765
|
+
// src/commands/provider.ts
|
|
23766
|
+
var provider_exports = {};
|
|
23767
|
+
__export(provider_exports, {
|
|
23768
|
+
handler: () => handler41
|
|
23769
|
+
});
|
|
23770
|
+
import chalk58 from "chalk";
|
|
23771
|
+
async function handler41(args, ctx) {
|
|
22538
23772
|
const { positional, flags } = parseArgs(args, ["default"]);
|
|
22539
23773
|
const sub = positional[0]?.toLowerCase();
|
|
22540
23774
|
if (!sub || sub === "list") {
|
|
@@ -22546,7 +23780,7 @@ async function handler40(args, ctx) {
|
|
|
22546
23780
|
if (!ctx.oneShot) saveSessionState(ctx);
|
|
22547
23781
|
console.log();
|
|
22548
23782
|
console.log(" " + paint("success", "\u2713") + " Session engine reset \u2014 using config defaults.");
|
|
22549
|
-
console.log(" " +
|
|
23783
|
+
console.log(" " + chalk58.dim(`Default: ${loadLlmConfig().primary}`));
|
|
22550
23784
|
console.log();
|
|
22551
23785
|
return;
|
|
22552
23786
|
}
|
|
@@ -22559,7 +23793,7 @@ async function handler40(args, ctx) {
|
|
|
22559
23793
|
setConfigValue("llm-auto-failover", session.autoFailover ? "on" : "off");
|
|
22560
23794
|
}
|
|
22561
23795
|
console.log();
|
|
22562
|
-
console.log(" " + paint("success", "\u2713") + ` Saved ${
|
|
23796
|
+
console.log(" " + paint("success", "\u2713") + ` Saved ${chalk58.bold(active)} as default engine.`);
|
|
22563
23797
|
console.log();
|
|
22564
23798
|
return;
|
|
22565
23799
|
}
|
|
@@ -22578,36 +23812,40 @@ async function handler40(args, ctx) {
|
|
|
22578
23812
|
}
|
|
22579
23813
|
console.log();
|
|
22580
23814
|
console.log(
|
|
22581
|
-
" " + paint("success", "\u2713") + ` Auto-failover ${session.autoFailover ?
|
|
23815
|
+
" " + paint("success", "\u2713") + ` Auto-failover ${session.autoFailover ? chalk58.bold("on") : chalk58.bold("off")} for this session.`
|
|
22582
23816
|
);
|
|
22583
|
-
if (persist) console.log(" " +
|
|
23817
|
+
if (persist) console.log(" " + chalk58.dim("Also saved as config default."));
|
|
22584
23818
|
console.log();
|
|
22585
23819
|
return;
|
|
22586
23820
|
}
|
|
22587
|
-
|
|
23821
|
+
const spec = getProviderSpec(sub);
|
|
23822
|
+
if (!spec || RESERVED.has(sub)) {
|
|
22588
23823
|
console.log();
|
|
22589
|
-
console.log(" " +
|
|
22590
|
-
console.log(" " +
|
|
23824
|
+
console.log(" " + chalk58.red(`Unknown engine: ${sub}`));
|
|
23825
|
+
console.log(" " + chalk58.dim("Usage: /provider [<id>|list|reset|save|failover on|off]"));
|
|
23826
|
+
console.log(" " + chalk58.dim("Connected: ") + (availableEngineLabels().join(", ") || chalk58.dim("none")));
|
|
23827
|
+
console.log(" " + chalk58.dim("Add one with ") + paint("accent", "/connect"));
|
|
22591
23828
|
console.log();
|
|
22592
23829
|
return;
|
|
22593
23830
|
}
|
|
22594
|
-
const provider =
|
|
23831
|
+
const provider = spec.id;
|
|
22595
23832
|
if (!hasProviderKey(provider)) {
|
|
22596
|
-
const keyHint = provider === "anthropic" ? "api-key" : "openai-api-key";
|
|
22597
23833
|
console.log();
|
|
22598
|
-
console.log(" " +
|
|
22599
|
-
console.log(
|
|
23834
|
+
console.log(" " + chalk58.red(`${spec.label} isn't connected.`));
|
|
23835
|
+
console.log(
|
|
23836
|
+
" " + chalk58.dim("Run ") + paint("accent", `/connect ${provider}`) + chalk58.dim(" (or ") + paint("accent", `/config set ${spec.key_config_name}`) + chalk58.dim(").")
|
|
23837
|
+
);
|
|
22600
23838
|
console.log();
|
|
22601
23839
|
return;
|
|
22602
23840
|
}
|
|
22603
23841
|
ensureLlmSession(ctx).provider = provider;
|
|
22604
23842
|
if (!ctx.oneShot) saveSessionState(ctx);
|
|
22605
23843
|
console.log();
|
|
22606
|
-
console.log(" " + paint("success", "\u2713") + ` Active engine: ${
|
|
22607
|
-
console.log(" " +
|
|
23844
|
+
console.log(" " + paint("success", "\u2713") + ` Active engine: ${chalk58.bold(provider)}`);
|
|
23845
|
+
console.log(" " + chalk58.dim(`Stack: ${formatActiveStack(ctx)}`));
|
|
22608
23846
|
const others = availableEngineLabels().filter((p) => p !== provider);
|
|
22609
23847
|
if (others.length > 0) {
|
|
22610
|
-
console.log(" " +
|
|
23848
|
+
console.log(" " + chalk58.dim(`Also available: ${others.join(", ")}`));
|
|
22611
23849
|
}
|
|
22612
23850
|
console.log();
|
|
22613
23851
|
}
|
|
@@ -22618,49 +23856,54 @@ function printStatus(ctx) {
|
|
|
22618
23856
|
const autoFailover = resolveAutoFailoverEnabled(ctx);
|
|
22619
23857
|
const engines = countAvailableEngines();
|
|
22620
23858
|
console.log();
|
|
22621
|
-
console.log(
|
|
22622
|
-
console.log(`
|
|
22623
|
-
|
|
22624
|
-
|
|
22625
|
-
const marker2 =
|
|
22626
|
-
console.log(` ${
|
|
23859
|
+
console.log(chalk58.bold(" LLM engines"));
|
|
23860
|
+
console.log(` Connected: ${engines} engine${engines === 1 ? "" : "s"}`);
|
|
23861
|
+
const configured = listProviderSpecs().filter((s) => hasProviderKey(s.id));
|
|
23862
|
+
for (const s of configured) {
|
|
23863
|
+
const marker2 = s.id === active ? paint("accent", " \u25BA active") : "";
|
|
23864
|
+
console.log(` ${paint("success", "\u2713")} ${s.id}${s.custom ? chalk58.dim(" (custom)") : ""}${marker2}`);
|
|
23865
|
+
}
|
|
23866
|
+
if (configured.length === 0) {
|
|
23867
|
+
console.log(" " + chalk58.dim("none \u2014 run /connect and paste any provider key"));
|
|
22627
23868
|
}
|
|
22628
23869
|
console.log();
|
|
22629
|
-
console.log(
|
|
23870
|
+
console.log(chalk58.bold(" Active stack"));
|
|
22630
23871
|
console.log(` ${formatActiveStack(ctx)}`);
|
|
22631
23872
|
if (sessionOverride) {
|
|
22632
|
-
console.log(
|
|
23873
|
+
console.log(chalk58.dim(" (session override \u2014 /provider reset to use default)"));
|
|
22633
23874
|
} else {
|
|
22634
|
-
console.log(
|
|
23875
|
+
console.log(chalk58.dim(` (config default: ${cfg.primary})`));
|
|
22635
23876
|
}
|
|
22636
23877
|
console.log();
|
|
22637
|
-
console.log(` Auto-failover: ${autoFailover ? paint("success", "on") :
|
|
22638
|
-
console.log(
|
|
22639
|
-
console.log(
|
|
22640
|
-
console.log(
|
|
23878
|
+
console.log(` Auto-failover: ${autoFailover ? paint("success", "on") : chalk58.dim("off")}`);
|
|
23879
|
+
console.log(chalk58.dim(` /provider <id> \u2014 switch engine (${configured.map((s) => s.id).join(", ") || "none connected"})`));
|
|
23880
|
+
console.log(chalk58.dim(" /provider failover on|off \u2014 rate-limit safety net"));
|
|
23881
|
+
console.log(chalk58.dim(" /provider save \u2014 persist active engine to config"));
|
|
23882
|
+
console.log(chalk58.dim(" /connect \u2014 add another provider (any API key)"));
|
|
22641
23883
|
console.log();
|
|
22642
23884
|
}
|
|
22643
|
-
var
|
|
23885
|
+
var RESERVED;
|
|
22644
23886
|
var init_provider = __esm({
|
|
22645
23887
|
"src/commands/provider.ts"() {
|
|
22646
23888
|
"use strict";
|
|
22647
23889
|
init_argparse();
|
|
22648
23890
|
init_session_state();
|
|
23891
|
+
init_providers();
|
|
22649
23892
|
init_llm_config();
|
|
22650
23893
|
init_store();
|
|
22651
23894
|
init_context2();
|
|
22652
23895
|
init_theme();
|
|
22653
|
-
|
|
23896
|
+
RESERVED = /* @__PURE__ */ new Set(["list", "reset", "save", "failover"]);
|
|
22654
23897
|
}
|
|
22655
23898
|
});
|
|
22656
23899
|
|
|
22657
23900
|
// src/commands/tier.ts
|
|
22658
23901
|
var tier_exports = {};
|
|
22659
23902
|
__export(tier_exports, {
|
|
22660
|
-
handler: () =>
|
|
23903
|
+
handler: () => handler42
|
|
22661
23904
|
});
|
|
22662
|
-
import
|
|
22663
|
-
async function
|
|
23905
|
+
import chalk59 from "chalk";
|
|
23906
|
+
async function handler42(args, ctx) {
|
|
22664
23907
|
const { positional, flags } = parseArgs(args, ["default"]);
|
|
22665
23908
|
const sub = positional[0]?.toLowerCase();
|
|
22666
23909
|
if (!sub || sub === "list") {
|
|
@@ -22669,8 +23912,8 @@ async function handler41(args, ctx) {
|
|
|
22669
23912
|
}
|
|
22670
23913
|
if (!TIERS.includes(sub)) {
|
|
22671
23914
|
console.log();
|
|
22672
|
-
console.log(" " +
|
|
22673
|
-
console.log(" " +
|
|
23915
|
+
console.log(" " + chalk59.red(`Unknown tier: ${sub}`));
|
|
23916
|
+
console.log(" " + chalk59.dim("Usage: /tier [high|medium|low|list] [--default]"));
|
|
22674
23917
|
console.log();
|
|
22675
23918
|
return;
|
|
22676
23919
|
}
|
|
@@ -22684,40 +23927,48 @@ async function handler41(args, ctx) {
|
|
|
22684
23927
|
}
|
|
22685
23928
|
console.log();
|
|
22686
23929
|
console.log(
|
|
22687
|
-
" " + paint("success", "\u2713") + ` Inference tier set to ${
|
|
23930
|
+
" " + paint("success", "\u2713") + ` Inference tier set to ${chalk59.bold(tier.toUpperCase())}` + (persist ? chalk59.dim(" (saved as default)") : chalk59.dim(" (this session)"))
|
|
22688
23931
|
);
|
|
22689
|
-
console.log(" " +
|
|
23932
|
+
console.log(" " + chalk59.dim(`Stack: ${formatActiveStack(ctx)}`));
|
|
22690
23933
|
console.log();
|
|
22691
23934
|
}
|
|
22692
23935
|
function printCatalog(ctx) {
|
|
22693
23936
|
const cfg = loadLlmConfig();
|
|
22694
23937
|
const active = resolveModelForActive(ctx, "agentic_investigation");
|
|
22695
23938
|
const sessionTier = ctx.llm?.tier;
|
|
23939
|
+
const providers = getAvailableProviders();
|
|
22696
23940
|
console.log();
|
|
22697
|
-
console.log(
|
|
23941
|
+
console.log(chalk59.bold(" Inference settings"));
|
|
22698
23942
|
console.log(` Active: ${paint("accent", formatActiveStack(ctx))}`);
|
|
22699
23943
|
if (sessionTier) {
|
|
22700
|
-
console.log(
|
|
23944
|
+
console.log(chalk59.dim(" (session tier override)"));
|
|
22701
23945
|
} else {
|
|
22702
|
-
console.log(
|
|
23946
|
+
console.log(chalk59.dim(` Config default tier: ${cfg.tier.toUpperCase()}`));
|
|
22703
23947
|
}
|
|
22704
23948
|
console.log();
|
|
23949
|
+
if (providers.length === 0) {
|
|
23950
|
+
console.log(" " + chalk59.dim("No engines connected \u2014 run /connect and paste any provider key."));
|
|
23951
|
+
console.log();
|
|
23952
|
+
}
|
|
22705
23953
|
for (const tier of TIERS) {
|
|
22706
|
-
console.log(
|
|
22707
|
-
for (const provider of
|
|
22708
|
-
const
|
|
22709
|
-
|
|
22710
|
-
|
|
22711
|
-
|
|
22712
|
-
const status = m.status === "active" ? "" : chalk58.yellow(` [${m.status}]`);
|
|
22713
|
-
console.log(`${marker2}${provider}/${m.id}${status} \u2014 ${m.display_name}`);
|
|
23954
|
+
console.log(chalk59.bold(` ${tier.toUpperCase()}`));
|
|
23955
|
+
for (const provider of providers) {
|
|
23956
|
+
const modelId = resolveModelSafe(provider, tier);
|
|
23957
|
+
if (!modelId) {
|
|
23958
|
+
console.log(` ${provider}/${chalk59.dim("no models \u2014 /model refresh")}`);
|
|
23959
|
+
continue;
|
|
22714
23960
|
}
|
|
23961
|
+
const isActive = provider === active.provider && tier === active.tier && modelId === active.modelId;
|
|
23962
|
+
const marker2 = isActive ? paint("accent", "\u25BA ") : " ";
|
|
23963
|
+
const discovered = !!getProviderModels(provider);
|
|
23964
|
+
const source = discovered ? "" : chalk59.dim(" [bundled fallback]");
|
|
23965
|
+
console.log(`${marker2}${provider}/${modelId}${source}`);
|
|
22715
23966
|
}
|
|
22716
23967
|
console.log();
|
|
22717
23968
|
}
|
|
22718
|
-
console.log(
|
|
22719
|
-
console.log(
|
|
22720
|
-
console.log(
|
|
23969
|
+
console.log(chalk59.dim(" /tier high|medium|low \u2014 set tier for this session"));
|
|
23970
|
+
console.log(chalk59.dim(" /tier high --default \u2014 also save as config default"));
|
|
23971
|
+
console.log(chalk59.dim(" /provider <id> \u2014 switch engine \xB7 /model list \u2014 browse models"));
|
|
22721
23972
|
console.log();
|
|
22722
23973
|
}
|
|
22723
23974
|
var TIERS;
|
|
@@ -22726,6 +23977,7 @@ var init_tier = __esm({
|
|
|
22726
23977
|
"use strict";
|
|
22727
23978
|
init_argparse();
|
|
22728
23979
|
init_catalog();
|
|
23980
|
+
init_models_cache();
|
|
22729
23981
|
init_session_state();
|
|
22730
23982
|
init_llm_config();
|
|
22731
23983
|
init_store();
|
|
@@ -22738,12 +23990,21 @@ var init_tier = __esm({
|
|
|
22738
23990
|
// src/commands/model.ts
|
|
22739
23991
|
var model_exports = {};
|
|
22740
23992
|
__export(model_exports, {
|
|
22741
|
-
handler: () =>
|
|
23993
|
+
handler: () => handler43
|
|
22742
23994
|
});
|
|
22743
|
-
import
|
|
22744
|
-
|
|
22745
|
-
|
|
23995
|
+
import chalk60 from "chalk";
|
|
23996
|
+
import ora18 from "ora";
|
|
23997
|
+
async function handler43(args, ctx) {
|
|
23998
|
+
const { positional, flags } = parseArgs(args, ["default", "all"]);
|
|
22746
23999
|
const sub = positional[0]?.toLowerCase();
|
|
24000
|
+
if (sub === "list") {
|
|
24001
|
+
printModelList(ctx, getBool(flags, "all"));
|
|
24002
|
+
return;
|
|
24003
|
+
}
|
|
24004
|
+
if (sub === "refresh") {
|
|
24005
|
+
await refreshModels(ctx);
|
|
24006
|
+
return;
|
|
24007
|
+
}
|
|
22747
24008
|
if (sub === "clear") {
|
|
22748
24009
|
const persist = getBool(flags, "default");
|
|
22749
24010
|
if (ctx.llm) ctx.llm.modelOverride = void 0;
|
|
@@ -22751,7 +24012,7 @@ async function handler42(args, ctx) {
|
|
|
22751
24012
|
if (!ctx.oneShot) saveSessionState(ctx);
|
|
22752
24013
|
console.log();
|
|
22753
24014
|
console.log(" " + paint("success", "\u2713") + " Model override cleared \u2014 using tier defaults.");
|
|
22754
|
-
console.log(" " +
|
|
24015
|
+
console.log(" " + chalk60.dim(`Stack: ${formatActiveStack(ctx)}`));
|
|
22755
24016
|
console.log();
|
|
22756
24017
|
return;
|
|
22757
24018
|
}
|
|
@@ -22759,22 +24020,28 @@ async function handler42(args, ctx) {
|
|
|
22759
24020
|
const modelId = positional[1];
|
|
22760
24021
|
if (!modelId) {
|
|
22761
24022
|
console.log();
|
|
22762
|
-
console.log(" " +
|
|
24023
|
+
console.log(" " + chalk60.red("Usage: /model set <model-id> [--default]"));
|
|
22763
24024
|
console.log();
|
|
22764
24025
|
return;
|
|
22765
24026
|
}
|
|
22766
24027
|
const active = resolveActiveProvider(ctx);
|
|
22767
24028
|
const providerErr = validateModelForProvider(modelId, active);
|
|
22768
|
-
const entry = getCatalogEntry(modelId);
|
|
22769
24029
|
if (providerErr) {
|
|
22770
24030
|
console.log();
|
|
22771
|
-
console.log(" " +
|
|
24031
|
+
console.log(" " + chalk60.red(providerErr));
|
|
22772
24032
|
console.log();
|
|
22773
24033
|
return;
|
|
22774
24034
|
}
|
|
22775
|
-
|
|
24035
|
+
const cache2 = getProviderModels(active);
|
|
24036
|
+
const known = cache2?.models.some((m) => m.id === modelId);
|
|
24037
|
+
if (cache2 && !known) {
|
|
22776
24038
|
console.log();
|
|
22777
|
-
console.log(
|
|
24039
|
+
console.log(
|
|
24040
|
+
" " + chalk60.yellow("\u26A0") + ` ${modelId} isn't in ${active}'s discovered list (` + paint("accent", "/model list") + `) \u2014 saving anyway.`
|
|
24041
|
+
);
|
|
24042
|
+
} else if (!cache2) {
|
|
24043
|
+
console.log();
|
|
24044
|
+
console.log(" " + chalk60.yellow("\u26A0") + ` No discovered models for ${active} yet (` + paint("accent", "/model refresh") + `) \u2014 saving anyway.`);
|
|
22778
24045
|
}
|
|
22779
24046
|
const persist = getBool(flags, "default");
|
|
22780
24047
|
if (persist) {
|
|
@@ -22785,48 +24052,98 @@ async function handler42(args, ctx) {
|
|
|
22785
24052
|
}
|
|
22786
24053
|
console.log();
|
|
22787
24054
|
console.log(
|
|
22788
|
-
" " + paint("success", "\u2713") + ` Model: ${
|
|
24055
|
+
" " + paint("success", "\u2713") + ` Model: ${chalk60.bold(modelId)}` + (persist ? chalk60.dim(" (saved as default)") : chalk60.dim(" (this session)"))
|
|
22789
24056
|
);
|
|
22790
|
-
console.log(" " +
|
|
24057
|
+
console.log(" " + chalk60.dim(`Stack: ${formatActiveStack(ctx)}`));
|
|
22791
24058
|
console.log();
|
|
22792
24059
|
return;
|
|
22793
24060
|
}
|
|
22794
24061
|
const sessionOverride = ctx.llm?.modelOverride;
|
|
22795
24062
|
const globalOverride = getConfigValue("llm-model-override");
|
|
22796
24063
|
console.log();
|
|
22797
|
-
console.log(
|
|
24064
|
+
console.log(chalk60.bold(" Model"));
|
|
22798
24065
|
if (sessionOverride) {
|
|
22799
24066
|
console.log(` Session override: ${paint("accent", sessionOverride)}`);
|
|
22800
24067
|
} else if (globalOverride) {
|
|
22801
24068
|
console.log(` Config default: ${paint("accent", globalOverride)}`);
|
|
22802
24069
|
} else {
|
|
22803
|
-
console.log(" " +
|
|
24070
|
+
console.log(" " + chalk60.dim("No override \u2014 tier defaults apply."));
|
|
22804
24071
|
}
|
|
22805
24072
|
console.log(` Active stack: ${formatActiveStack(ctx)}`);
|
|
22806
|
-
console.log(
|
|
24073
|
+
console.log(chalk60.dim(" /model list \xB7 /model set <id> \xB7 /model refresh \xB7 /model clear"));
|
|
24074
|
+
console.log();
|
|
24075
|
+
}
|
|
24076
|
+
function tierMarkers(cache2, modelId) {
|
|
24077
|
+
const tiers = Object.entries(cache2.tier_stack).filter(([, id]) => id === modelId).map(([tier]) => tier.toUpperCase());
|
|
24078
|
+
return tiers.length > 0 ? paint("accent", ` \u25C2 ${tiers.join("/")}`) : "";
|
|
24079
|
+
}
|
|
24080
|
+
function printModelList(ctx, showAll) {
|
|
24081
|
+
const active = resolveActiveProvider(ctx);
|
|
24082
|
+
const cache2 = getProviderModels(active);
|
|
24083
|
+
console.log();
|
|
24084
|
+
console.log(chalk60.bold(` Models \u2014 ${active}`));
|
|
24085
|
+
if (!cache2) {
|
|
24086
|
+
console.log(" " + chalk60.dim("Nothing discovered yet."));
|
|
24087
|
+
console.log(" " + chalk60.dim("Run ") + paint("accent", "/model refresh") + chalk60.dim(" (or ") + paint("accent", "/connect") + chalk60.dim(" to add the provider)."));
|
|
24088
|
+
console.log();
|
|
24089
|
+
return;
|
|
24090
|
+
}
|
|
24091
|
+
const fetchedAt = cache2.fetched_at.slice(0, 10);
|
|
24092
|
+
console.log(" " + chalk60.dim(`${cache2.models.length} chat models \xB7 discovered ${fetchedAt} \xB7 /model refresh to update`));
|
|
24093
|
+
console.log();
|
|
24094
|
+
const models = showAll ? cache2.models : cache2.models.slice(0, LIST_LIMIT);
|
|
24095
|
+
const noTools = new Set(cache2.quirks?.no_tools ?? []);
|
|
24096
|
+
for (const m of models) {
|
|
24097
|
+
const name = m.display_name && m.display_name !== m.id ? chalk60.dim(` \u2014 ${m.display_name}`) : "";
|
|
24098
|
+
const quirk = noTools.has(m.id) ? chalk60.yellow(" [no tools]") : "";
|
|
24099
|
+
console.log(` ${m.id}${name}${tierMarkers(cache2, m.id)}${quirk}`);
|
|
24100
|
+
}
|
|
24101
|
+
if (!showAll && cache2.models.length > models.length) {
|
|
24102
|
+
console.log(" " + chalk60.dim(`\u2026 and ${cache2.models.length - models.length} more (/model list --all)`));
|
|
24103
|
+
}
|
|
24104
|
+
console.log();
|
|
24105
|
+
console.log(" " + chalk60.dim("/model set <id> \u2014 pin one for this session (--default to persist)"));
|
|
22807
24106
|
console.log();
|
|
22808
24107
|
}
|
|
24108
|
+
async function refreshModels(ctx) {
|
|
24109
|
+
const active = resolveActiveProvider(ctx);
|
|
24110
|
+
const spinner = ora18({ text: `Discovering ${active} models\u2026`, discardStdin: false }).start();
|
|
24111
|
+
const entry = await refreshProviderModels(active, { force: true });
|
|
24112
|
+
if (!entry) {
|
|
24113
|
+
spinner.fail(`Couldn't reach ${active} to refresh models.`);
|
|
24114
|
+
console.log(" " + chalk60.dim("Check your connection and key, then retry. Cached models remain in use."));
|
|
24115
|
+
console.log();
|
|
24116
|
+
return;
|
|
24117
|
+
}
|
|
24118
|
+
spinner.succeed(`${active}: ${entry.models.length} chat models discovered.`);
|
|
24119
|
+
console.log(" " + chalk60.dim(`high ${entry.tier_stack.high} \xB7 medium ${entry.tier_stack.medium} \xB7 low ${entry.tier_stack.low}`));
|
|
24120
|
+
console.log(" " + chalk60.dim(`Stack: ${formatActiveStack(ctx)}`));
|
|
24121
|
+
console.log();
|
|
24122
|
+
}
|
|
24123
|
+
var LIST_LIMIT;
|
|
22809
24124
|
var init_model = __esm({
|
|
22810
24125
|
"src/commands/model.ts"() {
|
|
22811
24126
|
"use strict";
|
|
22812
24127
|
init_argparse();
|
|
22813
|
-
|
|
24128
|
+
init_discovery();
|
|
24129
|
+
init_models_cache();
|
|
22814
24130
|
init_session_state();
|
|
22815
24131
|
init_store();
|
|
22816
24132
|
init_context2();
|
|
22817
24133
|
init_theme();
|
|
24134
|
+
LIST_LIMIT = 40;
|
|
22818
24135
|
}
|
|
22819
24136
|
});
|
|
22820
24137
|
|
|
22821
24138
|
// src/config/update-check.ts
|
|
22822
|
-
import { existsSync as
|
|
22823
|
-
import { join as
|
|
22824
|
-
function
|
|
22825
|
-
return
|
|
24139
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync17, unlinkSync as unlinkSync5, writeFileSync as writeFileSync19 } from "fs";
|
|
24140
|
+
import { join as join28 } from "path";
|
|
24141
|
+
function cachePath2() {
|
|
24142
|
+
return join28(ntrpHome(), "update-check.json");
|
|
22826
24143
|
}
|
|
22827
24144
|
function invalidateUpdateCheckCache() {
|
|
22828
|
-
const path =
|
|
22829
|
-
if (
|
|
24145
|
+
const path = cachePath2();
|
|
24146
|
+
if (existsSync21(path)) {
|
|
22830
24147
|
unlinkSync5(path);
|
|
22831
24148
|
}
|
|
22832
24149
|
}
|
|
@@ -22838,17 +24155,17 @@ var init_update_check = __esm({
|
|
|
22838
24155
|
});
|
|
22839
24156
|
|
|
22840
24157
|
// src/version.ts
|
|
22841
|
-
import { existsSync as
|
|
22842
|
-
import { dirname as dirname4, join as
|
|
24158
|
+
import { existsSync as existsSync22, readFileSync as readFileSync18 } from "fs";
|
|
24159
|
+
import { dirname as dirname4, join as join29 } from "path";
|
|
22843
24160
|
import { fileURLToPath } from "url";
|
|
22844
24161
|
function getInstalledVersion() {
|
|
22845
24162
|
if (cachedVersion) return cachedVersion;
|
|
22846
24163
|
const start = dirname4(fileURLToPath(import.meta.url));
|
|
22847
24164
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
22848
|
-
const path =
|
|
22849
|
-
if (!
|
|
24165
|
+
const path = join29(start, rel);
|
|
24166
|
+
if (!existsSync22(path)) continue;
|
|
22850
24167
|
try {
|
|
22851
|
-
const pkg = JSON.parse(
|
|
24168
|
+
const pkg = JSON.parse(readFileSync18(path, "utf-8"));
|
|
22852
24169
|
if (typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
22853
24170
|
cachedVersion = pkg.version;
|
|
22854
24171
|
return cachedVersion;
|
|
@@ -22906,10 +24223,10 @@ var init_registry = __esm({
|
|
|
22906
24223
|
// src/commands/update.ts
|
|
22907
24224
|
var update_exports = {};
|
|
22908
24225
|
__export(update_exports, {
|
|
22909
|
-
handler: () =>
|
|
24226
|
+
handler: () => handler44
|
|
22910
24227
|
});
|
|
22911
24228
|
import { spawnSync } from "child_process";
|
|
22912
|
-
import
|
|
24229
|
+
import chalk61 from "chalk";
|
|
22913
24230
|
function tailLines(text, count = 5) {
|
|
22914
24231
|
return text.split("\n").map((line) => line.trimEnd()).filter((line) => line.length > 0).slice(-count).join("\n");
|
|
22915
24232
|
}
|
|
@@ -22925,19 +24242,19 @@ function runGlobalInstall() {
|
|
|
22925
24242
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
22926
24243
|
return { ok: result.status === 0, output };
|
|
22927
24244
|
}
|
|
22928
|
-
async function
|
|
24245
|
+
async function handler44(_args, _ctx) {
|
|
22929
24246
|
const current = getInstalledVersion();
|
|
22930
24247
|
const latest = await fetchLatestVersion(1e4);
|
|
22931
24248
|
if (!latest) {
|
|
22932
24249
|
console.log();
|
|
22933
|
-
console.log(
|
|
22934
|
-
console.log(
|
|
24250
|
+
console.log(chalk61.yellow(" Could not reach the npm registry."));
|
|
24251
|
+
console.log(chalk61.dim(` Try manually: npm install -g ${NPM_PACKAGE}`));
|
|
22935
24252
|
console.log();
|
|
22936
24253
|
return;
|
|
22937
24254
|
}
|
|
22938
24255
|
if (!isNewerVersion(latest, current)) {
|
|
22939
24256
|
console.log();
|
|
22940
|
-
console.log(
|
|
24257
|
+
console.log(chalk61.green(` \u2713 You're on the latest version (v${current})`));
|
|
22941
24258
|
console.log();
|
|
22942
24259
|
return;
|
|
22943
24260
|
}
|
|
@@ -22946,24 +24263,24 @@ async function handler43(_args, _ctx) {
|
|
|
22946
24263
|
const { ok, output } = runGlobalInstall();
|
|
22947
24264
|
if (ok) {
|
|
22948
24265
|
invalidateUpdateCheckCache();
|
|
22949
|
-
console.log(
|
|
24266
|
+
console.log(chalk61.green(` \u2713 Updated! Restart NTRP to use v${latest}`));
|
|
22950
24267
|
console.log();
|
|
22951
24268
|
return;
|
|
22952
24269
|
}
|
|
22953
24270
|
const lower = output.toLowerCase();
|
|
22954
24271
|
if (lower.includes("eacces") || lower.includes("permission denied") || lower.includes("eperm")) {
|
|
22955
|
-
console.log(
|
|
22956
|
-
console.log(
|
|
22957
|
-
console.log(
|
|
24272
|
+
console.log(chalk61.red(` Could not install ${NPM_PACKAGE} (permission denied).`));
|
|
24273
|
+
console.log(chalk61.dim(` Try: sudo npm install -g ${NPM_PACKAGE}`));
|
|
24274
|
+
console.log(chalk61.dim(` Or fix npm global permissions: ${PERMISSIONS_URL}`));
|
|
22958
24275
|
console.log();
|
|
22959
24276
|
return;
|
|
22960
24277
|
}
|
|
22961
24278
|
const detail = tailLines(output);
|
|
22962
|
-
console.log(
|
|
24279
|
+
console.log(chalk61.red(` Could not install ${NPM_PACKAGE}.`));
|
|
22963
24280
|
if (detail) {
|
|
22964
|
-
console.log(
|
|
24281
|
+
console.log(chalk61.dim(` ${detail.split("\n").join("\n ")}`));
|
|
22965
24282
|
}
|
|
22966
|
-
console.log(
|
|
24283
|
+
console.log(chalk61.dim(` Try manually: npm install -g ${NPM_PACKAGE}`));
|
|
22967
24284
|
console.log();
|
|
22968
24285
|
}
|
|
22969
24286
|
var PERMISSIONS_URL;
|
|
@@ -22978,10 +24295,10 @@ var init_update = __esm({
|
|
|
22978
24295
|
});
|
|
22979
24296
|
|
|
22980
24297
|
// src/output/progress-report.ts
|
|
22981
|
-
import
|
|
24298
|
+
import chalk62 from "chalk";
|
|
22982
24299
|
function printCard(title, rows) {
|
|
22983
24300
|
const inner = CARD_W - 4;
|
|
22984
|
-
const border =
|
|
24301
|
+
const border = chalk62.dim;
|
|
22985
24302
|
console.log();
|
|
22986
24303
|
console.log(` ${border(`\u256D${"\u2500".repeat(CARD_W - 2)}\u256E`)}`);
|
|
22987
24304
|
console.log(` ${border("\u2502 ")}${padRight(sectionHeading(title), inner)}${border(" \u2502")}`);
|
|
@@ -22997,7 +24314,7 @@ function formatTokens(n) {
|
|
|
22997
24314
|
return String(n);
|
|
22998
24315
|
}
|
|
22999
24316
|
function sparkline(values) {
|
|
23000
|
-
if (values.length === 0) return
|
|
24317
|
+
if (values.length === 0) return chalk62.dim("(no activity yet)");
|
|
23001
24318
|
const max = Math.max(...values, 1);
|
|
23002
24319
|
const blocks = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
23003
24320
|
return values.map((v) => {
|
|
@@ -23006,7 +24323,7 @@ function sparkline(values) {
|
|
|
23006
24323
|
}).join("");
|
|
23007
24324
|
}
|
|
23008
24325
|
function formatMemberSince(iso) {
|
|
23009
|
-
if (!iso) return
|
|
24326
|
+
if (!iso) return chalk62.dim("\u2014");
|
|
23010
24327
|
const d = new Date(iso);
|
|
23011
24328
|
return d.toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" });
|
|
23012
24329
|
}
|
|
@@ -23023,49 +24340,49 @@ function renderProgressReport() {
|
|
|
23023
24340
|
state.milestones_unlocked.length,
|
|
23024
24341
|
TIME_MILESTONES.length
|
|
23025
24342
|
);
|
|
23026
|
-
const { usage:
|
|
24343
|
+
const { usage: usage3 } = summary;
|
|
23027
24344
|
const nextLabel = bank.next_milestone ? `${formatHoursLabel(bank.total_hours)} \u2192 ${formatHoursLabel(bank.next_milestone.hours)}` : `${formatHoursLabel(bank.total_hours)} saved`;
|
|
23028
24345
|
const bar = inlineBar(bank.progress_pct, 18);
|
|
23029
24346
|
printCard("Progress", [
|
|
23030
|
-
`${
|
|
23031
|
-
`${
|
|
23032
|
-
`${
|
|
23033
|
-
`${
|
|
24347
|
+
`${chalk62.dim("Hours saved")} ${paint("accent", formatHoursLabel(bank.total_hours))} ${bar}`,
|
|
24348
|
+
`${chalk62.dim("Next milestone")} ${bank.next_milestone ? paint("accent", bank.next_milestone.title) : chalk62.dim("top of ladder")}`,
|
|
24349
|
+
`${chalk62.dim("Member since")} ${formatMemberSince(usage3.first_active_at)}`,
|
|
24350
|
+
`${chalk62.dim("Last active")} ${formatMemberSince(usage3.last_active_at)}`
|
|
23034
24351
|
]);
|
|
23035
24352
|
if (bank.perspective_line) {
|
|
23036
|
-
console.log(` ${
|
|
24353
|
+
console.log(` ${chalk62.dim.italic(bank.perspective_line)}`);
|
|
23037
24354
|
}
|
|
23038
24355
|
printCard("Activity", [
|
|
23039
|
-
`${
|
|
23040
|
-
`${
|
|
23041
|
-
`${
|
|
23042
|
-
`${
|
|
23043
|
-
`${
|
|
24356
|
+
`${chalk62.dim("Sessions")} ${chalk62.bold(String(summary.total_sessions_on_disk))} total \xB7 ${summary.sessions_with_work} with work \xB7 ${usage3.sessions_closed} closed`,
|
|
24357
|
+
`${chalk62.dim("Diagnoses")} ${chalk62.bold(String(usage3.diagnoses))}`,
|
|
24358
|
+
`${chalk62.dim("Metrics runs")} ${chalk62.bold(String(usage3.metrics_runs))}`,
|
|
24359
|
+
`${chalk62.dim("Deliverables")} ${chalk62.bold(String(usage3.deliverables))}`,
|
|
24360
|
+
`${chalk62.dim("AI exchanges")} ${chalk62.bold(String(usage3.nl_exchanges))}`
|
|
23044
24361
|
]);
|
|
23045
|
-
const totalTokens =
|
|
24362
|
+
const totalTokens = usage3.input_tokens + usage3.output_tokens;
|
|
23046
24363
|
printCard("AI usage", [
|
|
23047
|
-
`${
|
|
23048
|
-
`${
|
|
24364
|
+
`${chalk62.dim("LLM calls")} ${chalk62.bold(String(usage3.llm_calls))}`,
|
|
24365
|
+
`${chalk62.dim("Tokens")} ${chalk62.bold(formatTokens(totalTokens))} in+out (${formatTokens(usage3.input_tokens)} in \xB7 ${formatTokens(usage3.output_tokens)} out)`
|
|
23049
24366
|
]);
|
|
23050
|
-
const weeks = [...
|
|
24367
|
+
const weeks = [...usage3.weekly].sort((a, b) => a.week.localeCompare(b.week)).slice(-8);
|
|
23051
24368
|
const weekHours = weeks.map((w) => w.minutes_saved / 60);
|
|
23052
24369
|
const weekLabels = weeks.map((w) => w.week.replace(/^\d{4}-/, ""));
|
|
23053
24370
|
console.log();
|
|
23054
24371
|
console.log(` ${sectionHeading("Weekly hours saved")}`);
|
|
23055
24372
|
console.log(` ${sparkline(weekHours)}`);
|
|
23056
24373
|
if (weeks.length > 0) {
|
|
23057
|
-
console.log(` ${
|
|
24374
|
+
console.log(` ${chalk62.dim(weekLabels.join(" "))}`);
|
|
23058
24375
|
}
|
|
23059
24376
|
console.log();
|
|
23060
24377
|
console.log(` ${sectionHeading("Milestone ladder")}`);
|
|
23061
24378
|
for (const m of TIME_MILESTONES) {
|
|
23062
24379
|
const unlocked = state.milestones_unlocked.includes(m.id);
|
|
23063
24380
|
const pct = Math.min(100, bank.total_hours / m.hours * 100);
|
|
23064
|
-
const mark = unlocked ? badge("DONE", "success") : bank.total_hours >= m.hours * 0.85 ? badge("NEAR", "warning") :
|
|
24381
|
+
const mark = unlocked ? badge("DONE", "success") : bank.total_hours >= m.hours * 0.85 ? badge("NEAR", "warning") : chalk62.dim("\u25CB");
|
|
23065
24382
|
const barW = 12;
|
|
23066
|
-
const mBar = unlocked ?
|
|
24383
|
+
const mBar = unlocked ? chalk62.hex("#22c55e")("\u2588".repeat(barW)) : scoreBar(pct, bank.total_hours >= m.hours ? "green" : pct >= 50 ? "yellow" : "red", barW);
|
|
23067
24384
|
const label = `${m.title}`.padEnd(16);
|
|
23068
|
-
console.log(` ${mark} ${
|
|
24385
|
+
console.log(` ${mark} ${chalk62.dim(label)} ${mBar} ${chalk62.dim(`${m.hours}h`)}`);
|
|
23069
24386
|
}
|
|
23070
24387
|
console.log();
|
|
23071
24388
|
}
|
|
@@ -23087,9 +24404,20 @@ var init_progress_report = __esm({
|
|
|
23087
24404
|
// src/commands/progress.ts
|
|
23088
24405
|
var progress_exports = {};
|
|
23089
24406
|
__export(progress_exports, {
|
|
23090
|
-
handler: () =>
|
|
24407
|
+
handler: () => handler45
|
|
23091
24408
|
});
|
|
23092
|
-
|
|
24409
|
+
import chalk63 from "chalk";
|
|
24410
|
+
function printProgressResetPreamble() {
|
|
24411
|
+
console.log();
|
|
24412
|
+
console.log(" " + chalk63.yellow.bold("This will permanently remove:"));
|
|
24413
|
+
console.log(" " + chalk63.dim(" \u2022 Hours saved and milestone unlocks"));
|
|
24414
|
+
console.log(" " + chalk63.dim(" \u2022 Usage counters and weekly activity rollups"));
|
|
24415
|
+
console.log(" " + chalk63.dim(" \u2022 Credit history used for dedup"));
|
|
24416
|
+
console.log();
|
|
24417
|
+
console.log(" " + chalk63.dim("Preserved: install identity (install.json)"));
|
|
24418
|
+
console.log();
|
|
24419
|
+
}
|
|
24420
|
+
function showProgress() {
|
|
23093
24421
|
const bank = getTimeBankSummary();
|
|
23094
24422
|
if (bank.total_minutes <= 0) {
|
|
23095
24423
|
console.log();
|
|
@@ -23100,10 +24428,53 @@ async function handler44(_args, _ctx) {
|
|
|
23100
24428
|
renderProgressReport();
|
|
23101
24429
|
return `Progress: ${bank.total_hours.toFixed(1)}h saved`;
|
|
23102
24430
|
}
|
|
24431
|
+
async function handleReset(ctx, confirmedFlag) {
|
|
24432
|
+
const bank = getTimeBankSummary();
|
|
24433
|
+
if (bank.total_minutes <= 0) {
|
|
24434
|
+
console.log();
|
|
24435
|
+
console.log(" " + chalk63.dim("No progress to reset."));
|
|
24436
|
+
console.log();
|
|
24437
|
+
return "No progress to reset";
|
|
24438
|
+
}
|
|
24439
|
+
const ok = await requireTypedWord(ctx, {
|
|
24440
|
+
title: "Reset progress",
|
|
24441
|
+
word: "reset",
|
|
24442
|
+
confirmedFlag,
|
|
24443
|
+
preamble: printProgressResetPreamble,
|
|
24444
|
+
oneShotHint: "Re-run with: ntrp progress reset --confirm"
|
|
24445
|
+
});
|
|
24446
|
+
if (!ok) {
|
|
24447
|
+
printAdminCancelled("Progress reset", 'Type "reset" exactly to confirm.');
|
|
24448
|
+
return "Progress reset cancelled";
|
|
24449
|
+
}
|
|
24450
|
+
resetProgress();
|
|
24451
|
+
console.log();
|
|
24452
|
+
console.log(" " + paint("accent", "\u2713 Progress reset") + chalk63.dim(" \u2014 hours and milestones cleared."));
|
|
24453
|
+
console.log();
|
|
24454
|
+
return "Progress reset";
|
|
24455
|
+
}
|
|
24456
|
+
async function handler45(args, ctx) {
|
|
24457
|
+
const { positional, flags } = parseArgs(args, ["confirm"]);
|
|
24458
|
+
const sub = positional[0]?.toLowerCase();
|
|
24459
|
+
if (sub === "reset") {
|
|
24460
|
+
return handleReset(ctx, getBool(flags, "confirm"));
|
|
24461
|
+
}
|
|
24462
|
+
if (sub && sub !== "reset") {
|
|
24463
|
+
console.log();
|
|
24464
|
+
console.log(" " + chalk63.dim("Unknown subcommand. Try ") + paint("accent", "/progress") + chalk63.dim(" or ") + paint("accent", "/progress reset") + chalk63.dim("."));
|
|
24465
|
+
console.log();
|
|
24466
|
+
return;
|
|
24467
|
+
}
|
|
24468
|
+
return showProgress();
|
|
24469
|
+
}
|
|
23103
24470
|
var init_progress2 = __esm({
|
|
23104
24471
|
"src/commands/progress.ts"() {
|
|
23105
24472
|
"use strict";
|
|
24473
|
+
init_argparse();
|
|
24474
|
+
init_admin_confirm();
|
|
24475
|
+
init_progress();
|
|
23106
24476
|
init_progress_report();
|
|
24477
|
+
init_theme();
|
|
23107
24478
|
init_time_bank();
|
|
23108
24479
|
}
|
|
23109
24480
|
});
|
|
@@ -23117,8 +24488,8 @@ init_time_milestones();
|
|
|
23117
24488
|
init_time_perspectives();
|
|
23118
24489
|
init_time_bank();
|
|
23119
24490
|
init_perspective_rotation();
|
|
23120
|
-
import { existsSync as
|
|
23121
|
-
import { join as
|
|
24491
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync13, mkdtempSync, rmSync as rmSync4, writeFileSync as writeFileSync20 } from "fs";
|
|
24492
|
+
import { join as join30 } from "path";
|
|
23122
24493
|
import { tmpdir } from "os";
|
|
23123
24494
|
|
|
23124
24495
|
// src/workflows/registry.ts
|
|
@@ -23172,10 +24543,10 @@ async function resolveHandler(name) {
|
|
|
23172
24543
|
try {
|
|
23173
24544
|
const mod = await importHandler(runtimePath);
|
|
23174
24545
|
if (!mod) return null;
|
|
23175
|
-
const
|
|
23176
|
-
if (typeof
|
|
23177
|
-
entry.handler =
|
|
23178
|
-
return
|
|
24546
|
+
const handler46 = mod.handler;
|
|
24547
|
+
if (typeof handler46 !== "function") return null;
|
|
24548
|
+
entry.handler = handler46;
|
|
24549
|
+
return handler46;
|
|
23179
24550
|
} catch (err) {
|
|
23180
24551
|
console.error(`Failed to load handler for /${name}:`, err);
|
|
23181
24552
|
return null;
|
|
@@ -23261,6 +24632,8 @@ async function importHandler(runtimePath) {
|
|
|
23261
24632
|
return Promise.resolve().then(() => (init_switch(), switch_exports));
|
|
23262
24633
|
case "../commands/backmeup.js":
|
|
23263
24634
|
return Promise.resolve().then(() => (init_backmeup(), backmeup_exports));
|
|
24635
|
+
case "../commands/connect.js":
|
|
24636
|
+
return Promise.resolve().then(() => (init_connect2(), connect_exports2));
|
|
23264
24637
|
case "../commands/provider.js":
|
|
23265
24638
|
return Promise.resolve().then(() => (init_provider(), provider_exports));
|
|
23266
24639
|
case "../commands/tier.js":
|
|
@@ -23383,7 +24756,9 @@ handler: ../commands/setup.ts
|
|
|
23383
24756
|
|
|
23384
24757
|
Validate local readiness or configure NTRP non-interactively for automation.
|
|
23385
24758
|
\`setup check --json\` reports license, profile, API key, database, and writable
|
|
23386
|
-
directory state. \`setup agent\` accepts a profile JSON file or direct flags
|
|
24759
|
+
directory state. \`setup agent\` accepts a profile JSON file or direct flags \u2014
|
|
24760
|
+
\`--llm-key <key>\` auto-detects the provider from any pasted key
|
|
24761
|
+
(\`--llm-provider <id>\` to force one).`
|
|
23387
24762
|
},
|
|
23388
24763
|
{
|
|
23389
24764
|
name: "update",
|
|
@@ -23667,11 +25042,13 @@ Export the most recent diagnosis as terminal output, markdown, or JSON. Use
|
|
|
23667
25042
|
name: progress
|
|
23668
25043
|
description: Usage stats and milestone ladder
|
|
23669
25044
|
section: Navigation
|
|
25045
|
+
args: [reset] [--confirm]
|
|
23670
25046
|
handler: ../commands/progress.ts
|
|
23671
25047
|
---
|
|
23672
25048
|
|
|
23673
25049
|
Hours saved, weekly activity trend, session counts, AI token usage, and the
|
|
23674
|
-
full milestone ladder with progress bars
|
|
25050
|
+
full milestone ladder with progress bars. Use reset (type "reset" to confirm)
|
|
25051
|
+
to clear hours and milestones while keeping this install's identity.`
|
|
23675
25052
|
},
|
|
23676
25053
|
{
|
|
23677
25054
|
name: "status",
|
|
@@ -23691,15 +25068,17 @@ diagnosis, if any.`
|
|
|
23691
25068
|
name: scratch
|
|
23692
25069
|
description: Wipe config, profile, and all datasets
|
|
23693
25070
|
section: Admin
|
|
23694
|
-
args: [--confirm]
|
|
25071
|
+
args: [--confirm] [--include-progress]
|
|
23695
25072
|
handler: ../commands/scratch.ts
|
|
23696
25073
|
hidden: true
|
|
23697
25074
|
---
|
|
23698
25075
|
|
|
23699
25076
|
Minimal factory reset: removes API key, config, company profile, all sessions,
|
|
23700
|
-
per-session datasets, and demo taxonomy cache. Preserves progress (hours saved)
|
|
23701
|
-
|
|
23702
|
-
|
|
25077
|
+
per-session datasets, and demo taxonomy cache. Preserves progress (hours saved)
|
|
25078
|
+
by default. Pass \`--include-progress\` to also wipe install identity and hours.
|
|
25079
|
+
Also preserves memory, strategies, wins, knowledge, exports, and audit. Requires
|
|
25080
|
+
typing \`scratch\` in the REPL or passing \`--confirm\` one-shot. Triggers
|
|
25081
|
+
onboarding on next interactive use.`
|
|
23703
25082
|
},
|
|
23704
25083
|
{
|
|
23705
25084
|
name: "cleanup",
|
|
@@ -23815,6 +25194,25 @@ handler: ../commands/profile.ts
|
|
|
23815
25194
|
|
|
23816
25195
|
Choose a sales motion preset (PLG, SMB Velocity, Mid-Market, Enterprise). Each
|
|
23817
25196
|
preset adjusts the vital-sign thresholds to match your deal cycle.`
|
|
25197
|
+
},
|
|
25198
|
+
{
|
|
25199
|
+
name: "connect",
|
|
25200
|
+
raw: `---
|
|
25201
|
+
name: connect
|
|
25202
|
+
description: Connect an AI provider (paste any key)
|
|
25203
|
+
section: Settings
|
|
25204
|
+
args: [provider] [--key <key>] [--base-url <url> --id <name>]
|
|
25205
|
+
handler: ../commands/connect.ts
|
|
25206
|
+
---
|
|
25207
|
+
|
|
25208
|
+
Paste any provider's API key \u2014 NTRP identifies the provider from the key
|
|
25209
|
+
format (probing ambiguous ones), validates it, discovers which models the key
|
|
25210
|
+
can use, and builds the HIGH/MEDIUM/LOW tier stack automatically.
|
|
25211
|
+
|
|
25212
|
+
Works with Anthropic, OpenAI, Google Gemini, Groq, Mistral, DeepSeek, xAI,
|
|
25213
|
+
OpenRouter, Together, and Fireworks out of the box. \`/connect ollama\` wires a
|
|
25214
|
+
local Ollama; \`/connect --base-url <url> --id <name>\` registers any other
|
|
25215
|
+
OpenAI-compatible endpoint.`
|
|
23818
25216
|
},
|
|
23819
25217
|
{
|
|
23820
25218
|
name: "config",
|
|
@@ -23827,10 +25225,12 @@ handler: ../commands/config.ts
|
|
|
23827
25225
|
---
|
|
23828
25226
|
|
|
23829
25227
|
Manage CLI configuration stored at \`~/.ntrp/config.json\`. Useful keys:
|
|
23830
|
-
\`api-key\` (Anthropic), \`openai-api-key\`, \`
|
|
23831
|
-
\`llm-tier\`, \`llm-auto-failover\`,
|
|
25228
|
+
\`api-key\` (Anthropic), \`openai-api-key\` (and \`groq-api-key\`, \`google-api-key\`, ...),
|
|
25229
|
+
\`llm-primary\` (default engine), \`llm-tier\`, \`llm-auto-failover\`,
|
|
25230
|
+
\`default-format\`, \`export-dir\`.
|
|
23832
25231
|
|
|
23833
|
-
|
|
25232
|
+
Setting a provider key opens a hidden prompt and auto-discovers that
|
|
25233
|
+
provider's models. Prefer \`/connect\` \u2014 it detects the provider for you.`
|
|
23834
25234
|
},
|
|
23835
25235
|
{
|
|
23836
25236
|
name: "provider",
|
|
@@ -23838,13 +25238,14 @@ Manage CLI configuration stored at \`~/.ntrp/config.json\`. Useful keys:
|
|
|
23838
25238
|
name: provider
|
|
23839
25239
|
description: Switch active LLM engine
|
|
23840
25240
|
section: Settings
|
|
23841
|
-
args: [
|
|
25241
|
+
args: [<id>|list|reset|save|failover on|off]
|
|
23842
25242
|
handler: ../commands/provider.ts
|
|
23843
25243
|
---
|
|
23844
25244
|
|
|
23845
|
-
Choose which engine answers this session \u2014
|
|
23846
|
-
|
|
23847
|
-
|
|
25245
|
+
Choose which connected engine answers this session \u2014 any provider added via
|
|
25246
|
+
\`/connect\` (anthropic, openai, groq, google, ollama, custom endpoints, ...).
|
|
25247
|
+
Session-scoped by default; \`/provider save\` writes the default to config.
|
|
25248
|
+
\`/provider failover on\` enables rate-limit auto-failover.`
|
|
23848
25249
|
},
|
|
23849
25250
|
{
|
|
23850
25251
|
name: "tier",
|
|
@@ -23866,12 +25267,14 @@ active stack. Add \`--default\` to persist to config.`
|
|
|
23866
25267
|
name: model
|
|
23867
25268
|
description: Override the active LLM model
|
|
23868
25269
|
section: Settings
|
|
23869
|
-
args: [set <id>|clear] [--default]
|
|
25270
|
+
args: [list|set <id>|refresh|clear] [--default]
|
|
23870
25271
|
handler: ../commands/model.ts
|
|
23871
25272
|
---
|
|
23872
25273
|
|
|
23873
|
-
|
|
23874
|
-
|
|
25274
|
+
\`/model list\` shows the models discovered for the active engine with their
|
|
25275
|
+
tier assignments. \`/model refresh\` re-discovers the live list. \`/model set <id>\`
|
|
25276
|
+
pins a model on the **active engine**; cross-provider IDs are rejected \u2014
|
|
25277
|
+
switch with \`/provider\` first.`
|
|
23875
25278
|
},
|
|
23876
25279
|
{
|
|
23877
25280
|
name: "activate",
|
|
@@ -23941,7 +25344,7 @@ function mockCtx(sessionId = "2026-06-21-test") {
|
|
|
23941
25344
|
};
|
|
23942
25345
|
}
|
|
23943
25346
|
function withTempHome(run2) {
|
|
23944
|
-
const dir = mkdtempSync(
|
|
25347
|
+
const dir = mkdtempSync(join30(tmpdir(), "ntrp-time-bank-"));
|
|
23945
25348
|
const prev = process.env.NTRP_HOME;
|
|
23946
25349
|
process.env.NTRP_HOME = dir;
|
|
23947
25350
|
try {
|
|
@@ -23960,7 +25363,7 @@ function withTempHome(run2) {
|
|
|
23960
25363
|
}
|
|
23961
25364
|
}
|
|
23962
25365
|
async function withTempHomeAsync(run2) {
|
|
23963
|
-
const dir = mkdtempSync(
|
|
25366
|
+
const dir = mkdtempSync(join30(tmpdir(), "ntrp-time-bank-"));
|
|
23964
25367
|
const prev = process.env.NTRP_HOME;
|
|
23965
25368
|
process.env.NTRP_HOME = dir;
|
|
23966
25369
|
try {
|
|
@@ -24081,7 +25484,7 @@ function testUsageBackfillFromCredits() {
|
|
|
24081
25484
|
withTempHome(() => {
|
|
24082
25485
|
const at = "2026-06-01T12:00:00.000Z";
|
|
24083
25486
|
const installId = loadProgress().install_id;
|
|
24084
|
-
|
|
25487
|
+
writeFileSync20(join30(ntrpHome(), "progress.json"), JSON.stringify({
|
|
24085
25488
|
schema_version: 2,
|
|
24086
25489
|
install_id: installId,
|
|
24087
25490
|
total_minutes_saved: 30,
|
|
@@ -24096,14 +25499,14 @@ function testUsageBackfillFromCredits() {
|
|
|
24096
25499
|
function testInstallCreatedOnFirstLoad() {
|
|
24097
25500
|
withTempHome(() => {
|
|
24098
25501
|
loadProgress();
|
|
24099
|
-
assert(
|
|
24100
|
-
assert(
|
|
25502
|
+
assert(existsSync23(join30(ntrpHome(), "install.json")), "install.json created");
|
|
25503
|
+
assert(existsSync23(join30(ntrpHome(), "progress.json")), "progress.json created");
|
|
24101
25504
|
});
|
|
24102
25505
|
}
|
|
24103
25506
|
function testLegacyStateMigration() {
|
|
24104
25507
|
withTempHome(() => {
|
|
24105
25508
|
mkdirSync13(ntrpHome(), { recursive: true });
|
|
24106
|
-
|
|
25509
|
+
writeFileSync20(join30(ntrpHome(), "state.json"), JSON.stringify({
|
|
24107
25510
|
schema_version: 1,
|
|
24108
25511
|
total_minutes_saved: 45,
|
|
24109
25512
|
credits: [{ action: "onboard", minutes: 45, at: "2026-06-01T12:00:00.000Z" }],
|
|
@@ -24111,8 +25514,8 @@ function testLegacyStateMigration() {
|
|
|
24111
25514
|
}));
|
|
24112
25515
|
const state = loadProgress();
|
|
24113
25516
|
assert(state.total_minutes_saved === 45, "legacy migration preserves hours");
|
|
24114
|
-
assert(
|
|
24115
|
-
assert(!
|
|
25517
|
+
assert(existsSync23(join30(ntrpHome(), "progress.json")), "progress.json created from legacy");
|
|
25518
|
+
assert(!existsSync23(join30(ntrpHome(), "state.json")), "legacy state.json moved aside");
|
|
24116
25519
|
assert(state.install_id === getInstallId(), "install_id attached on migration");
|
|
24117
25520
|
});
|
|
24118
25521
|
}
|
|
@@ -24123,7 +25526,7 @@ async function testProgressSurvivesScratchWipe() {
|
|
|
24123
25526
|
assert(loadProgress().total_minutes_saved === 180, "pre-scratch credits");
|
|
24124
25527
|
await performScratchWipe();
|
|
24125
25528
|
assert(loadProgress().total_minutes_saved === 180, "post-scratch credits preserved");
|
|
24126
|
-
assert(
|
|
25529
|
+
assert(existsSync23(join30(ntrpHome(), "install.json")), "install survives scratch");
|
|
24127
25530
|
});
|
|
24128
25531
|
}
|
|
24129
25532
|
function testInstallIdStable() {
|
|
@@ -24135,6 +25538,29 @@ function testInstallIdStable() {
|
|
|
24135
25538
|
assert(id1 === id2, "install_id stable across loads");
|
|
24136
25539
|
});
|
|
24137
25540
|
}
|
|
25541
|
+
function testResetProgressKeepsInstall() {
|
|
25542
|
+
withTempHome(() => {
|
|
25543
|
+
const ctx = mockCtx();
|
|
25544
|
+
recordTimeCredit("diagnose", ctx, { silent: true });
|
|
25545
|
+
const installId = getInstallId();
|
|
25546
|
+
resetProgress();
|
|
25547
|
+
assert(loadProgress().total_minutes_saved === 0, "reset should clear hours");
|
|
25548
|
+
assert(getInstallId() === installId, "reset should keep install_id");
|
|
25549
|
+
assert(existsSync23(join30(ntrpHome(), "install.json")), "install.json should remain");
|
|
25550
|
+
});
|
|
25551
|
+
}
|
|
25552
|
+
async function testScratchIncludeProgressWipesHours() {
|
|
25553
|
+
await withTempHomeAsync(async () => {
|
|
25554
|
+
const ctx = mockCtx();
|
|
25555
|
+
recordTimeCredit("diagnose", ctx, { silent: true });
|
|
25556
|
+
const installId = getInstallId();
|
|
25557
|
+
await performScratchWipe({ includeProgress: true });
|
|
25558
|
+
assert(!existsSync23(join30(ntrpHome(), "install.json")), "include-progress should remove install.json");
|
|
25559
|
+
const state = loadProgress();
|
|
25560
|
+
assert(state.total_minutes_saved === 0, "include-progress should clear hours");
|
|
25561
|
+
assert(state.install_id !== installId, "include-progress should issue new install_id");
|
|
25562
|
+
});
|
|
25563
|
+
}
|
|
24138
25564
|
testCreditDedup();
|
|
24139
25565
|
testMilestoneBoundary();
|
|
24140
25566
|
testNextMilestone();
|
|
@@ -24150,12 +25576,14 @@ testUsageBackfillFromCredits();
|
|
|
24150
25576
|
testInstallCreatedOnFirstLoad();
|
|
24151
25577
|
testLegacyStateMigration();
|
|
24152
25578
|
testInstallIdStable();
|
|
25579
|
+
testResetProgressKeepsInstall();
|
|
24153
25580
|
async function testProgressCommandRegistered() {
|
|
24154
25581
|
assert(hasCommand("progress"), "/progress should be in workflow registry");
|
|
24155
|
-
const
|
|
24156
|
-
assert(typeof
|
|
25582
|
+
const handler46 = await resolveHandler("progress");
|
|
25583
|
+
assert(typeof handler46 === "function", "progress handler should load from importHandler map");
|
|
24157
25584
|
}
|
|
24158
25585
|
await testProgressSurvivesScratchWipe();
|
|
25586
|
+
await testScratchIncludeProgressWipesHours();
|
|
24159
25587
|
await testProgressCommandRegistered();
|
|
24160
25588
|
console.log("time bank smoke passed");
|
|
24161
25589
|
//# sourceMappingURL=time-bank-smoke.js.map
|