@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
package/dist/index.js
CHANGED
|
@@ -1623,6 +1623,9 @@ function ensureInstall() {
|
|
|
1623
1623
|
function getInstallId() {
|
|
1624
1624
|
return ensureInstall().install_id;
|
|
1625
1625
|
}
|
|
1626
|
+
function clearInstallCache() {
|
|
1627
|
+
cachedInstall = null;
|
|
1628
|
+
}
|
|
1626
1629
|
var cachedInstall;
|
|
1627
1630
|
var init_install = __esm({
|
|
1628
1631
|
"src/config/install.ts"() {
|
|
@@ -1752,7 +1755,7 @@ function migrateUsageIfNeeded(state) {
|
|
|
1752
1755
|
if (state.usage?.first_active_at) return { state, changed: false };
|
|
1753
1756
|
const fromCredits = rebuildFromCredits(state.credits);
|
|
1754
1757
|
const prior = state.usage;
|
|
1755
|
-
const
|
|
1758
|
+
const usage3 = {
|
|
1756
1759
|
sessions_closed: prior?.sessions_closed ?? 0,
|
|
1757
1760
|
llm_calls: prior?.llm_calls ?? 0,
|
|
1758
1761
|
input_tokens: prior?.input_tokens ?? 0,
|
|
@@ -1760,7 +1763,7 @@ function migrateUsageIfNeeded(state) {
|
|
|
1760
1763
|
...fromCredits,
|
|
1761
1764
|
weekly: mergeWeekly(prior?.weekly ?? [], fromCredits.weekly)
|
|
1762
1765
|
};
|
|
1763
|
-
return { state: { ...state, usage:
|
|
1766
|
+
return { state: { ...state, usage: usage3 }, changed: true };
|
|
1764
1767
|
}
|
|
1765
1768
|
var init_usage_backfill = __esm({
|
|
1766
1769
|
"src/whimsy/usage-backfill.ts"() {
|
|
@@ -1774,6 +1777,12 @@ import { join as join5 } from "path";
|
|
|
1774
1777
|
function progressPath2() {
|
|
1775
1778
|
return join5(ntrpHome(), "progress.json");
|
|
1776
1779
|
}
|
|
1780
|
+
function legacyStatePath2() {
|
|
1781
|
+
return join5(ntrpHome(), "state.json");
|
|
1782
|
+
}
|
|
1783
|
+
function legacyStateBackupPath2() {
|
|
1784
|
+
return join5(ntrpHome(), "state.json.bak");
|
|
1785
|
+
}
|
|
1777
1786
|
function ensureDir4() {
|
|
1778
1787
|
const dir = ntrpHome();
|
|
1779
1788
|
if (!existsSync5(dir)) {
|
|
@@ -1852,6 +1861,18 @@ function saveProgress(state) {
|
|
|
1852
1861
|
};
|
|
1853
1862
|
writeFileSync4(progressPath2(), JSON.stringify(next, null, 2) + "\n");
|
|
1854
1863
|
}
|
|
1864
|
+
function wipeProgressFiles() {
|
|
1865
|
+
installMismatchWarned = false;
|
|
1866
|
+
for (const path of [progressPath2(), legacyStatePath2(), legacyStateBackupPath2()]) {
|
|
1867
|
+
if (existsSync5(path)) {
|
|
1868
|
+
unlinkSync2(path);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
function resetProgress() {
|
|
1873
|
+
ensureInstall();
|
|
1874
|
+
wipeProgressFiles();
|
|
1875
|
+
}
|
|
1855
1876
|
function appendCredit(state, credit) {
|
|
1856
1877
|
const credits = [...state.credits, credit];
|
|
1857
1878
|
if (credits.length > CREDIT_HISTORY_CAP) {
|
|
@@ -2266,48 +2287,48 @@ function bumpWeekly2(weekly, patch) {
|
|
|
2266
2287
|
}
|
|
2267
2288
|
function touchUsage(state, patch) {
|
|
2268
2289
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
2269
|
-
const
|
|
2290
|
+
const usage3 = ensureUsage(state);
|
|
2270
2291
|
return {
|
|
2271
2292
|
...state,
|
|
2272
2293
|
usage: {
|
|
2273
|
-
...
|
|
2294
|
+
...usage3,
|
|
2274
2295
|
...patch,
|
|
2275
|
-
first_active_at:
|
|
2296
|
+
first_active_at: usage3.first_active_at ?? now2,
|
|
2276
2297
|
last_active_at: now2,
|
|
2277
|
-
weekly: patch.weekly ??
|
|
2298
|
+
weekly: patch.weekly ?? usage3.weekly
|
|
2278
2299
|
}
|
|
2279
2300
|
};
|
|
2280
2301
|
}
|
|
2281
2302
|
function recordUsageFromCredit(action, minutes) {
|
|
2282
2303
|
if (minutes <= 0) return;
|
|
2283
2304
|
let state = loadProgress();
|
|
2284
|
-
const
|
|
2285
|
-
const weekly = bumpWeekly2(
|
|
2305
|
+
const usage3 = ensureUsage(state);
|
|
2306
|
+
const weekly = bumpWeekly2(usage3.weekly, { minutes_saved: minutes, actions: 1 });
|
|
2286
2307
|
const counters = { weekly };
|
|
2287
|
-
if (action === "diagnose" || action === "diagnose_findings") counters.diagnoses =
|
|
2288
|
-
if (action === "metrics" || action === "metrics_findings") counters.metrics_runs =
|
|
2289
|
-
if (action === "deliverable" || action === "deliverable_deck") counters.deliverables =
|
|
2290
|
-
if (action === "nl_answer") counters.nl_exchanges =
|
|
2308
|
+
if (action === "diagnose" || action === "diagnose_findings") counters.diagnoses = usage3.diagnoses + 1;
|
|
2309
|
+
if (action === "metrics" || action === "metrics_findings") counters.metrics_runs = usage3.metrics_runs + 1;
|
|
2310
|
+
if (action === "deliverable" || action === "deliverable_deck") counters.deliverables = usage3.deliverables + 1;
|
|
2311
|
+
if (action === "nl_answer") counters.nl_exchanges = usage3.nl_exchanges + 1;
|
|
2291
2312
|
state = touchUsage(state, counters);
|
|
2292
2313
|
saveProgress(state);
|
|
2293
2314
|
}
|
|
2294
2315
|
function recordSessionClosed() {
|
|
2295
2316
|
let state = loadProgress();
|
|
2296
|
-
const
|
|
2317
|
+
const usage3 = ensureUsage(state);
|
|
2297
2318
|
state = touchUsage(state, {
|
|
2298
|
-
sessions_closed:
|
|
2299
|
-
weekly: bumpWeekly2(
|
|
2319
|
+
sessions_closed: usage3.sessions_closed + 1,
|
|
2320
|
+
weekly: bumpWeekly2(usage3.weekly, { actions: 1 })
|
|
2300
2321
|
});
|
|
2301
2322
|
saveProgress(state);
|
|
2302
2323
|
}
|
|
2303
2324
|
function recordLlmUsage(tokenUsage) {
|
|
2304
2325
|
let state = loadProgress();
|
|
2305
|
-
const
|
|
2306
|
-
const weekly = bumpWeekly2(
|
|
2326
|
+
const usage3 = ensureUsage(state);
|
|
2327
|
+
const weekly = bumpWeekly2(usage3.weekly, { llm_calls: 1 });
|
|
2307
2328
|
state = touchUsage(state, {
|
|
2308
|
-
llm_calls:
|
|
2309
|
-
input_tokens:
|
|
2310
|
-
output_tokens:
|
|
2329
|
+
llm_calls: usage3.llm_calls + 1,
|
|
2330
|
+
input_tokens: usage3.input_tokens + (tokenUsage?.input_tokens ?? 0),
|
|
2331
|
+
output_tokens: usage3.output_tokens + (tokenUsage?.output_tokens ?? 0),
|
|
2311
2332
|
weekly
|
|
2312
2333
|
});
|
|
2313
2334
|
saveProgress(state);
|
|
@@ -3088,10 +3109,232 @@ var init_context2 = __esm({
|
|
|
3088
3109
|
}
|
|
3089
3110
|
});
|
|
3090
3111
|
|
|
3112
|
+
// src/ai/llm/providers.ts
|
|
3113
|
+
var providers_exports = {};
|
|
3114
|
+
__export(providers_exports, {
|
|
3115
|
+
findSpecByConfigKey: () => findSpecByConfigKey,
|
|
3116
|
+
getProviderSpec: () => getProviderSpec,
|
|
3117
|
+
isEndpointEnabled: () => isEndpointEnabled,
|
|
3118
|
+
keyConfigNameFor: () => keyConfigNameFor,
|
|
3119
|
+
listProviderSpecs: () => listProviderSpecs,
|
|
3120
|
+
loadCustomProviders: () => loadCustomProviders,
|
|
3121
|
+
modelsUrl: () => modelsUrl,
|
|
3122
|
+
providerLabel: () => providerLabel,
|
|
3123
|
+
removeCustomProvider: () => removeCustomProvider,
|
|
3124
|
+
resetProvidersCache: () => resetProvidersCache,
|
|
3125
|
+
saveCustomProvider: () => saveCustomProvider
|
|
3126
|
+
});
|
|
3127
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
3128
|
+
import { join as join7 } from "path";
|
|
3129
|
+
function providersPath() {
|
|
3130
|
+
return join7(ntrpHome(), "providers.json");
|
|
3131
|
+
}
|
|
3132
|
+
function loadCustomProviders() {
|
|
3133
|
+
if (cachedEntries) return cachedEntries;
|
|
3134
|
+
const path = providersPath();
|
|
3135
|
+
if (!existsSync7(path)) {
|
|
3136
|
+
cachedEntries = [];
|
|
3137
|
+
return cachedEntries;
|
|
3138
|
+
}
|
|
3139
|
+
try {
|
|
3140
|
+
const parsed = JSON.parse(readFileSync6(path, "utf-8"));
|
|
3141
|
+
cachedEntries = Array.isArray(parsed.providers) ? parsed.providers : [];
|
|
3142
|
+
} catch {
|
|
3143
|
+
cachedEntries = [];
|
|
3144
|
+
}
|
|
3145
|
+
return cachedEntries;
|
|
3146
|
+
}
|
|
3147
|
+
function saveCustomProvider(entry) {
|
|
3148
|
+
const entries = loadCustomProviders().filter((e) => e.id !== entry.id);
|
|
3149
|
+
entries.push(entry);
|
|
3150
|
+
writeFileSync6(providersPath(), JSON.stringify({ version: 1, providers: entries }, null, 2) + "\n");
|
|
3151
|
+
cachedEntries = entries;
|
|
3152
|
+
}
|
|
3153
|
+
function removeCustomProvider(id) {
|
|
3154
|
+
const entries = loadCustomProviders().filter((e) => e.id !== id);
|
|
3155
|
+
writeFileSync6(providersPath(), JSON.stringify({ version: 1, providers: entries }, null, 2) + "\n");
|
|
3156
|
+
cachedEntries = entries;
|
|
3157
|
+
}
|
|
3158
|
+
function resetProvidersCache() {
|
|
3159
|
+
cachedEntries = null;
|
|
3160
|
+
}
|
|
3161
|
+
function customEntryToSpec(entry) {
|
|
3162
|
+
return {
|
|
3163
|
+
id: entry.id,
|
|
3164
|
+
label: entry.label ?? entry.id,
|
|
3165
|
+
api: "openai-compat",
|
|
3166
|
+
base_url: entry.base_url.replace(/\/+$/, ""),
|
|
3167
|
+
key_prefixes: [],
|
|
3168
|
+
shared_prefixes: [],
|
|
3169
|
+
key_config_name: keyConfigNameFor(entry.id),
|
|
3170
|
+
requires_key: entry.requires_key ?? false,
|
|
3171
|
+
custom: true
|
|
3172
|
+
};
|
|
3173
|
+
}
|
|
3174
|
+
function keyConfigNameFor(providerId) {
|
|
3175
|
+
return providerId === "anthropic" ? "api-key" : `${providerId}-api-key`;
|
|
3176
|
+
}
|
|
3177
|
+
function listProviderSpecs() {
|
|
3178
|
+
const customs = loadCustomProviders();
|
|
3179
|
+
const customById = new Map(customs.map((e) => [e.id, e]));
|
|
3180
|
+
const specs = BUILTIN_SPECS.map((spec) => {
|
|
3181
|
+
const override = customById.get(spec.id);
|
|
3182
|
+
if (override?.base_url) {
|
|
3183
|
+
return { ...spec, base_url: override.base_url.replace(/\/+$/, "") };
|
|
3184
|
+
}
|
|
3185
|
+
return spec;
|
|
3186
|
+
});
|
|
3187
|
+
for (const entry of customs) {
|
|
3188
|
+
if (!BUILTIN_SPECS.some((s) => s.id === entry.id)) {
|
|
3189
|
+
specs.push(customEntryToSpec(entry));
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
return specs;
|
|
3193
|
+
}
|
|
3194
|
+
function getProviderSpec(id) {
|
|
3195
|
+
return listProviderSpecs().find((s) => s.id === id);
|
|
3196
|
+
}
|
|
3197
|
+
function findSpecByConfigKey(configKey) {
|
|
3198
|
+
return listProviderSpecs().find((s) => s.key_config_name === configKey);
|
|
3199
|
+
}
|
|
3200
|
+
function isEndpointEnabled(id) {
|
|
3201
|
+
const entry = loadCustomProviders().find((e) => e.id === id);
|
|
3202
|
+
return !!entry && entry.enabled !== false;
|
|
3203
|
+
}
|
|
3204
|
+
function modelsUrl(spec) {
|
|
3205
|
+
if (spec.api === "anthropic") return `${spec.base_url}/v1/models?limit=100`;
|
|
3206
|
+
return `${spec.base_url}/models`;
|
|
3207
|
+
}
|
|
3208
|
+
function providerLabel(id) {
|
|
3209
|
+
return getProviderSpec(id)?.label ?? id;
|
|
3210
|
+
}
|
|
3211
|
+
var BUILTIN_SPECS, cachedEntries;
|
|
3212
|
+
var init_providers = __esm({
|
|
3213
|
+
"src/ai/llm/providers.ts"() {
|
|
3214
|
+
"use strict";
|
|
3215
|
+
init_store();
|
|
3216
|
+
BUILTIN_SPECS = [
|
|
3217
|
+
{
|
|
3218
|
+
id: "anthropic",
|
|
3219
|
+
label: "Anthropic",
|
|
3220
|
+
api: "anthropic",
|
|
3221
|
+
base_url: "https://api.anthropic.com",
|
|
3222
|
+
key_prefixes: ["sk-ant-"],
|
|
3223
|
+
shared_prefixes: [],
|
|
3224
|
+
key_config_name: "api-key",
|
|
3225
|
+
requires_key: true
|
|
3226
|
+
},
|
|
3227
|
+
{
|
|
3228
|
+
id: "openai",
|
|
3229
|
+
label: "OpenAI",
|
|
3230
|
+
api: "openai-compat",
|
|
3231
|
+
base_url: "https://api.openai.com/v1",
|
|
3232
|
+
key_prefixes: ["sk-proj-", "sk-svcacct-", "sk-admin-"],
|
|
3233
|
+
shared_prefixes: ["sk-"],
|
|
3234
|
+
key_config_name: "openai-api-key",
|
|
3235
|
+
env_var: "OPENAI_API_KEY",
|
|
3236
|
+
requires_key: true
|
|
3237
|
+
},
|
|
3238
|
+
{
|
|
3239
|
+
id: "google",
|
|
3240
|
+
label: "Google Gemini",
|
|
3241
|
+
api: "openai-compat",
|
|
3242
|
+
base_url: "https://generativelanguage.googleapis.com/v1beta/openai",
|
|
3243
|
+
key_prefixes: ["AIza"],
|
|
3244
|
+
shared_prefixes: [],
|
|
3245
|
+
key_config_name: "google-api-key",
|
|
3246
|
+
requires_key: true
|
|
3247
|
+
},
|
|
3248
|
+
{
|
|
3249
|
+
id: "groq",
|
|
3250
|
+
label: "Groq",
|
|
3251
|
+
api: "openai-compat",
|
|
3252
|
+
base_url: "https://api.groq.com/openai/v1",
|
|
3253
|
+
key_prefixes: ["gsk_"],
|
|
3254
|
+
shared_prefixes: [],
|
|
3255
|
+
key_config_name: "groq-api-key",
|
|
3256
|
+
requires_key: true
|
|
3257
|
+
},
|
|
3258
|
+
{
|
|
3259
|
+
id: "mistral",
|
|
3260
|
+
label: "Mistral",
|
|
3261
|
+
api: "openai-compat",
|
|
3262
|
+
base_url: "https://api.mistral.ai/v1",
|
|
3263
|
+
key_prefixes: [],
|
|
3264
|
+
shared_prefixes: [],
|
|
3265
|
+
key_config_name: "mistral-api-key",
|
|
3266
|
+
requires_key: true
|
|
3267
|
+
},
|
|
3268
|
+
{
|
|
3269
|
+
id: "deepseek",
|
|
3270
|
+
label: "DeepSeek",
|
|
3271
|
+
api: "openai-compat",
|
|
3272
|
+
base_url: "https://api.deepseek.com/v1",
|
|
3273
|
+
key_prefixes: [],
|
|
3274
|
+
shared_prefixes: ["sk-"],
|
|
3275
|
+
key_config_name: "deepseek-api-key",
|
|
3276
|
+
requires_key: true
|
|
3277
|
+
},
|
|
3278
|
+
{
|
|
3279
|
+
id: "xai",
|
|
3280
|
+
label: "xAI",
|
|
3281
|
+
api: "openai-compat",
|
|
3282
|
+
base_url: "https://api.x.ai/v1",
|
|
3283
|
+
key_prefixes: ["xai-"],
|
|
3284
|
+
shared_prefixes: [],
|
|
3285
|
+
key_config_name: "xai-api-key",
|
|
3286
|
+
requires_key: true
|
|
3287
|
+
},
|
|
3288
|
+
{
|
|
3289
|
+
id: "openrouter",
|
|
3290
|
+
label: "OpenRouter",
|
|
3291
|
+
api: "openai-compat",
|
|
3292
|
+
base_url: "https://openrouter.ai/api/v1",
|
|
3293
|
+
key_prefixes: ["sk-or-"],
|
|
3294
|
+
shared_prefixes: [],
|
|
3295
|
+
key_config_name: "openrouter-api-key",
|
|
3296
|
+
requires_key: true
|
|
3297
|
+
},
|
|
3298
|
+
{
|
|
3299
|
+
id: "together",
|
|
3300
|
+
label: "Together AI",
|
|
3301
|
+
api: "openai-compat",
|
|
3302
|
+
base_url: "https://api.together.xyz/v1",
|
|
3303
|
+
key_prefixes: [],
|
|
3304
|
+
shared_prefixes: [],
|
|
3305
|
+
key_config_name: "together-api-key",
|
|
3306
|
+
requires_key: true
|
|
3307
|
+
},
|
|
3308
|
+
{
|
|
3309
|
+
id: "fireworks",
|
|
3310
|
+
label: "Fireworks AI",
|
|
3311
|
+
api: "openai-compat",
|
|
3312
|
+
base_url: "https://api.fireworks.ai/inference/v1",
|
|
3313
|
+
key_prefixes: ["fw_"],
|
|
3314
|
+
shared_prefixes: [],
|
|
3315
|
+
key_config_name: "fireworks-api-key",
|
|
3316
|
+
requires_key: true
|
|
3317
|
+
},
|
|
3318
|
+
{
|
|
3319
|
+
id: "ollama",
|
|
3320
|
+
label: "Ollama (local)",
|
|
3321
|
+
api: "openai-compat",
|
|
3322
|
+
base_url: "http://localhost:11434/v1",
|
|
3323
|
+
key_prefixes: [],
|
|
3324
|
+
shared_prefixes: [],
|
|
3325
|
+
key_config_name: "ollama-api-key",
|
|
3326
|
+
requires_key: false
|
|
3327
|
+
}
|
|
3328
|
+
];
|
|
3329
|
+
cachedEntries = null;
|
|
3330
|
+
}
|
|
3331
|
+
});
|
|
3332
|
+
|
|
3091
3333
|
// src/config/llm-config.ts
|
|
3092
3334
|
function parseProvider(raw) {
|
|
3093
|
-
if (raw
|
|
3094
|
-
|
|
3335
|
+
if (!raw?.trim()) return void 0;
|
|
3336
|
+
const id = raw.trim();
|
|
3337
|
+
return getProviderSpec(id) ? id : void 0;
|
|
3095
3338
|
}
|
|
3096
3339
|
function parseTier(raw) {
|
|
3097
3340
|
if (raw === "high" || raw === "medium" || raw === "low") return raw;
|
|
@@ -3099,7 +3342,7 @@ function parseTier(raw) {
|
|
|
3099
3342
|
}
|
|
3100
3343
|
function parseFailoverOrder(raw) {
|
|
3101
3344
|
if (!raw?.trim()) return ["openai"];
|
|
3102
|
-
return raw.split(",").map((s) => s.trim()).filter((s) => s
|
|
3345
|
+
return raw.split(",").map((s) => s.trim()).filter((s) => !!s && !!getProviderSpec(s));
|
|
3103
3346
|
}
|
|
3104
3347
|
function parseAutoFailover(raw) {
|
|
3105
3348
|
if (!raw) return false;
|
|
@@ -3114,30 +3357,42 @@ function getOpenAiApiKey() {
|
|
|
3114
3357
|
if (fromConfig) return fromConfig;
|
|
3115
3358
|
return process.env.OPENAI_API_KEY?.trim() || void 0;
|
|
3116
3359
|
}
|
|
3360
|
+
function getProviderApiKey(provider) {
|
|
3361
|
+
const spec = getProviderSpec(provider);
|
|
3362
|
+
if (!spec) return void 0;
|
|
3363
|
+
const record = loadConfig();
|
|
3364
|
+
const fromConfig = record[spec.key_config_name]?.trim();
|
|
3365
|
+
if (fromConfig) return fromConfig;
|
|
3366
|
+
if (spec.env_var) {
|
|
3367
|
+
const fromEnv = process.env[spec.env_var]?.trim();
|
|
3368
|
+
if (fromEnv) return fromEnv;
|
|
3369
|
+
}
|
|
3370
|
+
return void 0;
|
|
3371
|
+
}
|
|
3117
3372
|
function hasProviderKey(provider) {
|
|
3118
|
-
|
|
3119
|
-
|
|
3373
|
+
const spec = getProviderSpec(provider);
|
|
3374
|
+
if (!spec) return false;
|
|
3375
|
+
if (!spec.requires_key) return isEndpointEnabled(spec.id) || !!getProviderApiKey(provider);
|
|
3376
|
+
return !!getProviderApiKey(provider);
|
|
3120
3377
|
}
|
|
3121
3378
|
function getAvailableProviders() {
|
|
3122
|
-
|
|
3123
|
-
if (hasProviderKey("anthropic")) out.push("anthropic");
|
|
3124
|
-
if (hasProviderKey("openai")) out.push("openai");
|
|
3125
|
-
return out;
|
|
3379
|
+
return listProviderSpecs().filter((s) => hasProviderKey(s.id)).map((s) => s.id);
|
|
3126
3380
|
}
|
|
3127
3381
|
function hasAnyLlmProvider() {
|
|
3128
3382
|
return getAvailableProviders().length > 0;
|
|
3129
3383
|
}
|
|
3384
|
+
function hasKeylessConfiguredProvider() {
|
|
3385
|
+
return listProviderSpecs().some((s) => !s.requires_key && hasProviderKey(s.id));
|
|
3386
|
+
}
|
|
3130
3387
|
function applyLazyMigration(config) {
|
|
3131
3388
|
if (migrated) return;
|
|
3132
3389
|
migrated = true;
|
|
3133
3390
|
let changed = false;
|
|
3134
3391
|
const record = config;
|
|
3135
3392
|
if (!record["llm-primary"]) {
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
} else if (record["openai-api-key"] || process.env.OPENAI_API_KEY) {
|
|
3140
|
-
record["llm-primary"] = "openai";
|
|
3393
|
+
const available = getAvailableProviders();
|
|
3394
|
+
if (available.length > 0) {
|
|
3395
|
+
record["llm-primary"] = available[0];
|
|
3141
3396
|
changed = true;
|
|
3142
3397
|
}
|
|
3143
3398
|
}
|
|
@@ -3180,20 +3435,20 @@ function loadLlmConfig() {
|
|
|
3180
3435
|
openaiKey: getOpenAiApiKey()
|
|
3181
3436
|
};
|
|
3182
3437
|
}
|
|
3183
|
-
function getProviderApiKey(provider) {
|
|
3184
|
-
if (provider === "anthropic") return getAnthropicApiKey();
|
|
3185
|
-
return getOpenAiApiKey();
|
|
3186
|
-
}
|
|
3187
3438
|
function getInvestigationApiKey(provider) {
|
|
3188
3439
|
if (provider === "anthropic") {
|
|
3189
3440
|
return process.env.NTRP_INVESTIGATION_API_KEY?.trim() || getAnthropicApiKey();
|
|
3190
3441
|
}
|
|
3191
|
-
|
|
3442
|
+
if (provider === "openai") {
|
|
3443
|
+
return process.env.NTRP_INVESTIGATION_OPENAI_KEY?.trim() || getOpenAiApiKey();
|
|
3444
|
+
}
|
|
3445
|
+
return getProviderApiKey(provider);
|
|
3192
3446
|
}
|
|
3193
3447
|
var migrated;
|
|
3194
3448
|
var init_llm_config = __esm({
|
|
3195
3449
|
"src/config/llm-config.ts"() {
|
|
3196
3450
|
"use strict";
|
|
3451
|
+
init_providers();
|
|
3197
3452
|
init_store();
|
|
3198
3453
|
migrated = false;
|
|
3199
3454
|
}
|
|
@@ -3623,9 +3878,9 @@ var init_admin_confirm = __esm({
|
|
|
3623
3878
|
});
|
|
3624
3879
|
|
|
3625
3880
|
// src/services/scratch-wipe.ts
|
|
3626
|
-
import { existsSync as
|
|
3627
|
-
import { join as
|
|
3628
|
-
async function performScratchWipe() {
|
|
3881
|
+
import { existsSync as existsSync8, rmSync as rmSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
3882
|
+
import { join as join8 } from "path";
|
|
3883
|
+
async function performScratchWipe(opts = {}) {
|
|
3629
3884
|
const home = ntrpHome();
|
|
3630
3885
|
const removed = [];
|
|
3631
3886
|
try {
|
|
@@ -3634,18 +3889,25 @@ async function performScratchWipe() {
|
|
|
3634
3889
|
} catch {
|
|
3635
3890
|
}
|
|
3636
3891
|
const targets = [
|
|
3637
|
-
{ path:
|
|
3638
|
-
{ path:
|
|
3639
|
-
{ path:
|
|
3640
|
-
{ path:
|
|
3641
|
-
{ path:
|
|
3642
|
-
{ path:
|
|
3643
|
-
{ path:
|
|
3644
|
-
{ path:
|
|
3645
|
-
{ path:
|
|
3892
|
+
{ path: join8(home, "config.json"), kind: "file" },
|
|
3893
|
+
{ path: join8(home, "profile.json"), kind: "file" },
|
|
3894
|
+
{ path: join8(home, "demo-taxonomy.json"), kind: "file" },
|
|
3895
|
+
{ path: join8(home, "update-check.json"), kind: "file" },
|
|
3896
|
+
{ path: join8(home, "state.json.bak"), kind: "file" },
|
|
3897
|
+
{ path: join8(home, "ntrp.duckdb"), kind: "file" },
|
|
3898
|
+
{ path: join8(home, "ntrp.duckdb.wal"), kind: "file" },
|
|
3899
|
+
{ path: join8(home, "sessions"), kind: "dir" },
|
|
3900
|
+
{ path: join8(home, "datasets"), kind: "dir" }
|
|
3646
3901
|
];
|
|
3902
|
+
if (opts.includeProgress) {
|
|
3903
|
+
targets.push(
|
|
3904
|
+
{ path: join8(home, "install.json"), kind: "file" },
|
|
3905
|
+
{ path: join8(home, "progress.json"), kind: "file" },
|
|
3906
|
+
{ path: join8(home, "state.json"), kind: "file" }
|
|
3907
|
+
);
|
|
3908
|
+
}
|
|
3647
3909
|
for (const { path, kind } of targets) {
|
|
3648
|
-
if (!
|
|
3910
|
+
if (!existsSync8(path)) continue;
|
|
3649
3911
|
try {
|
|
3650
3912
|
if (kind === "dir") {
|
|
3651
3913
|
rmSync3(path, { recursive: true, force: true });
|
|
@@ -3656,21 +3918,27 @@ async function performScratchWipe() {
|
|
|
3656
3918
|
} catch {
|
|
3657
3919
|
}
|
|
3658
3920
|
}
|
|
3921
|
+
if (opts.includeProgress) {
|
|
3922
|
+
wipeProgressFiles();
|
|
3923
|
+
clearInstallCache();
|
|
3924
|
+
}
|
|
3659
3925
|
resetConfigCache();
|
|
3660
3926
|
return { removed };
|
|
3661
3927
|
}
|
|
3662
3928
|
var init_scratch_wipe = __esm({
|
|
3663
3929
|
"src/services/scratch-wipe.ts"() {
|
|
3664
3930
|
"use strict";
|
|
3931
|
+
init_install();
|
|
3932
|
+
init_progress();
|
|
3665
3933
|
init_store();
|
|
3666
3934
|
}
|
|
3667
3935
|
});
|
|
3668
3936
|
|
|
3669
3937
|
// src/config/profile.ts
|
|
3670
|
-
import { readFileSync as
|
|
3671
|
-
import { join as
|
|
3938
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
|
|
3939
|
+
import { join as join9 } from "path";
|
|
3672
3940
|
function ensureDir5() {
|
|
3673
|
-
if (!
|
|
3941
|
+
if (!existsSync9(NTRP_DIR3)) {
|
|
3674
3942
|
mkdirSync6(NTRP_DIR3, { recursive: true });
|
|
3675
3943
|
}
|
|
3676
3944
|
}
|
|
@@ -3678,16 +3946,16 @@ function profilePath() {
|
|
|
3678
3946
|
return PROFILE_PATH;
|
|
3679
3947
|
}
|
|
3680
3948
|
function profileExists() {
|
|
3681
|
-
return
|
|
3949
|
+
return existsSync9(PROFILE_PATH);
|
|
3682
3950
|
}
|
|
3683
3951
|
function isProfileConfigured(profile = loadProfile()) {
|
|
3684
3952
|
if (!profile) return false;
|
|
3685
3953
|
return profile.company_name.trim().length > 0;
|
|
3686
3954
|
}
|
|
3687
3955
|
function loadProfile() {
|
|
3688
|
-
if (!
|
|
3956
|
+
if (!existsSync9(PROFILE_PATH)) return null;
|
|
3689
3957
|
try {
|
|
3690
|
-
const parsed = JSON.parse(
|
|
3958
|
+
const parsed = JSON.parse(readFileSync7(PROFILE_PATH, "utf-8"));
|
|
3691
3959
|
if (!parsed || typeof parsed !== "object") return null;
|
|
3692
3960
|
return parsed;
|
|
3693
3961
|
} catch {
|
|
@@ -3703,7 +3971,7 @@ function saveProfile(profile) {
|
|
|
3703
3971
|
created_at: profile.created_at || now2,
|
|
3704
3972
|
updated_at: now2
|
|
3705
3973
|
};
|
|
3706
|
-
|
|
3974
|
+
writeFileSync7(PROFILE_PATH, JSON.stringify(toWrite, null, 2) + "\n");
|
|
3707
3975
|
}
|
|
3708
3976
|
function updateProfile(patch) {
|
|
3709
3977
|
const existing = loadProfile();
|
|
@@ -3729,7 +3997,7 @@ var init_profile = __esm({
|
|
|
3729
3997
|
"use strict";
|
|
3730
3998
|
init_store();
|
|
3731
3999
|
NTRP_DIR3 = ntrpHome();
|
|
3732
|
-
PROFILE_PATH =
|
|
4000
|
+
PROFILE_PATH = join9(NTRP_DIR3, "profile.json");
|
|
3733
4001
|
}
|
|
3734
4002
|
});
|
|
3735
4003
|
|
|
@@ -4081,7 +4349,13 @@ function resolvePrimaryApiKey(ctx) {
|
|
|
4081
4349
|
if (isInvestigationMode(ctx)) {
|
|
4082
4350
|
return getInvestigationApiKey(primary) ?? getInvestigationApiKey("anthropic") ?? getInvestigationApiKey("openai");
|
|
4083
4351
|
}
|
|
4084
|
-
|
|
4352
|
+
const primaryKey = getProviderApiKey(primary);
|
|
4353
|
+
if (primaryKey) return primaryKey;
|
|
4354
|
+
for (const provider of getAvailableProviders()) {
|
|
4355
|
+
const key = getProviderApiKey(provider);
|
|
4356
|
+
if (key) return key;
|
|
4357
|
+
}
|
|
4358
|
+
return void 0;
|
|
4085
4359
|
}
|
|
4086
4360
|
function canUseReplAi(ctx) {
|
|
4087
4361
|
if (!ctx) return false;
|
|
@@ -4092,25 +4366,21 @@ function canUseReplAi(ctx) {
|
|
|
4092
4366
|
}
|
|
4093
4367
|
function assertReplAi(ctx) {
|
|
4094
4368
|
if (!ctx) {
|
|
4095
|
-
throw new Error(
|
|
4096
|
-
"AI features require stored API keys. Run `ntrp`, then /config set api-key or /config set openai-api-key."
|
|
4097
|
-
);
|
|
4369
|
+
throw new Error(`AI features require stored API keys. Run \`ntrp\`, then /connect.`);
|
|
4098
4370
|
}
|
|
4099
4371
|
if (!canUseReplAi(ctx)) {
|
|
4100
4372
|
if (!hasAnyLlmProvider()) {
|
|
4101
|
-
throw new Error(
|
|
4102
|
-
"No LLM API key configured. Run: /config set api-key (Anthropic) and/or /config set openai-api-key"
|
|
4103
|
-
);
|
|
4373
|
+
throw new Error(NO_KEY_MESSAGE);
|
|
4104
4374
|
}
|
|
4105
4375
|
throw new Error(
|
|
4106
4376
|
"AI features run only in the interactive REPL or headless mode with stored keys."
|
|
4107
4377
|
);
|
|
4108
4378
|
}
|
|
4109
4379
|
const key = resolvePrimaryApiKey(ctx);
|
|
4110
|
-
if (!key) {
|
|
4111
|
-
throw new Error(
|
|
4380
|
+
if (!key && !hasKeylessConfiguredProvider()) {
|
|
4381
|
+
throw new Error(NO_KEY_MESSAGE);
|
|
4112
4382
|
}
|
|
4113
|
-
return key;
|
|
4383
|
+
return key ?? "";
|
|
4114
4384
|
}
|
|
4115
4385
|
function hasEnvApiKeyHint() {
|
|
4116
4386
|
return !!(process.env.ANTHROPIC_API_KEY ?? process.env.NTRP_API_KEY ?? process.env.OPENAI_API_KEY);
|
|
@@ -4123,10 +4393,12 @@ function describeLlmReadiness() {
|
|
|
4123
4393
|
openai: providers.includes("openai")
|
|
4124
4394
|
};
|
|
4125
4395
|
}
|
|
4396
|
+
var NO_KEY_MESSAGE;
|
|
4126
4397
|
var init_gate = __esm({
|
|
4127
4398
|
"src/ai/llm/gate.ts"() {
|
|
4128
4399
|
"use strict";
|
|
4129
4400
|
init_llm_config();
|
|
4401
|
+
NO_KEY_MESSAGE = "No LLM API key configured. Run /connect and paste any provider's key (Anthropic, OpenAI, Groq, Gemini, ...).";
|
|
4130
4402
|
}
|
|
4131
4403
|
});
|
|
4132
4404
|
|
|
@@ -4151,18 +4423,18 @@ var init_repl_api = __esm({
|
|
|
4151
4423
|
});
|
|
4152
4424
|
|
|
4153
4425
|
// src/demo/taxonomy-cache.ts
|
|
4154
|
-
import { readFileSync as
|
|
4426
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, existsSync as existsSync10, mkdirSync as mkdirSync7, unlinkSync as unlinkSync4 } from "fs";
|
|
4155
4427
|
import { homedir as homedir3 } from "os";
|
|
4156
|
-
import { join as
|
|
4428
|
+
import { join as join10 } from "path";
|
|
4157
4429
|
function ensureDir6() {
|
|
4158
|
-
if (!
|
|
4430
|
+
if (!existsSync10(NTRP_DIR4)) {
|
|
4159
4431
|
mkdirSync7(NTRP_DIR4, { recursive: true });
|
|
4160
4432
|
}
|
|
4161
4433
|
}
|
|
4162
4434
|
function loadCachedTaxonomy(profile) {
|
|
4163
|
-
if (!
|
|
4435
|
+
if (!existsSync10(TAXONOMY_PATH)) return null;
|
|
4164
4436
|
try {
|
|
4165
|
-
const parsed = JSON.parse(
|
|
4437
|
+
const parsed = JSON.parse(readFileSync8(TAXONOMY_PATH, "utf-8"));
|
|
4166
4438
|
if (!parsed || typeof parsed !== "object") return null;
|
|
4167
4439
|
if (parsed.profile_updated_at !== profile.updated_at) return null;
|
|
4168
4440
|
return parsed;
|
|
@@ -4172,10 +4444,10 @@ function loadCachedTaxonomy(profile) {
|
|
|
4172
4444
|
}
|
|
4173
4445
|
function saveCachedTaxonomy(taxonomy) {
|
|
4174
4446
|
ensureDir6();
|
|
4175
|
-
|
|
4447
|
+
writeFileSync8(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
|
|
4176
4448
|
}
|
|
4177
4449
|
function invalidateTaxonomy() {
|
|
4178
|
-
if (
|
|
4450
|
+
if (existsSync10(TAXONOMY_PATH)) {
|
|
4179
4451
|
try {
|
|
4180
4452
|
unlinkSync4(TAXONOMY_PATH);
|
|
4181
4453
|
} catch {
|
|
@@ -4186,8 +4458,8 @@ var NTRP_DIR4, TAXONOMY_PATH;
|
|
|
4186
4458
|
var init_taxonomy_cache = __esm({
|
|
4187
4459
|
"src/demo/taxonomy-cache.ts"() {
|
|
4188
4460
|
"use strict";
|
|
4189
|
-
NTRP_DIR4 =
|
|
4190
|
-
TAXONOMY_PATH =
|
|
4461
|
+
NTRP_DIR4 = join10(homedir3(), ".ntrp");
|
|
4462
|
+
TAXONOMY_PATH = join10(NTRP_DIR4, "demo-taxonomy.json");
|
|
4191
4463
|
}
|
|
4192
4464
|
});
|
|
4193
4465
|
|
|
@@ -4212,6 +4484,12 @@ var init_types = __esm({
|
|
|
4212
4484
|
});
|
|
4213
4485
|
|
|
4214
4486
|
// src/ai/llm/errors.ts
|
|
4487
|
+
function isToolsUnsupportedMessage(message) {
|
|
4488
|
+
const msg = message.toLowerCase();
|
|
4489
|
+
const mentionsTools = msg.includes("tool") || msg.includes("function");
|
|
4490
|
+
const mentionsUnsupported = msg.includes("not support") || msg.includes("unsupported") || msg.includes("no support") || msg.includes("not available") || msg.includes("not enabled");
|
|
4491
|
+
return mentionsTools && mentionsUnsupported;
|
|
4492
|
+
}
|
|
4215
4493
|
function mapAnthropicError(err, provider) {
|
|
4216
4494
|
const e = err;
|
|
4217
4495
|
const status = e.status;
|
|
@@ -4229,6 +4507,9 @@ function mapAnthropicError(err, provider) {
|
|
|
4229
4507
|
if (status === 503) {
|
|
4230
4508
|
return new LlmError("OVERLOADED", message, provider, status);
|
|
4231
4509
|
}
|
|
4510
|
+
if (isToolsUnsupportedMessage(message)) {
|
|
4511
|
+
return new LlmError("TOOLS_UNSUPPORTED", message, provider, status);
|
|
4512
|
+
}
|
|
4232
4513
|
if (status === 404 || message.toLowerCase().includes("model")) {
|
|
4233
4514
|
return new LlmError("MODEL_NOT_FOUND", message, provider, status);
|
|
4234
4515
|
}
|
|
@@ -4237,6 +4518,11 @@ function mapAnthropicError(err, provider) {
|
|
|
4237
4518
|
}
|
|
4238
4519
|
return new LlmError("UNKNOWN", message, provider, status);
|
|
4239
4520
|
}
|
|
4521
|
+
function isModelNotFoundMessage(message) {
|
|
4522
|
+
const msg = message.toLowerCase();
|
|
4523
|
+
if (!msg.includes("model")) return false;
|
|
4524
|
+
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");
|
|
4525
|
+
}
|
|
4240
4526
|
function mapOpenAiError(err, provider) {
|
|
4241
4527
|
const e = err;
|
|
4242
4528
|
const status = e.status;
|
|
@@ -4251,7 +4537,10 @@ function mapOpenAiError(err, provider) {
|
|
|
4251
4537
|
if (status === 503 || code === "server_error") {
|
|
4252
4538
|
return new LlmError("OVERLOADED", message, provider, status);
|
|
4253
4539
|
}
|
|
4254
|
-
if (
|
|
4540
|
+
if (isToolsUnsupportedMessage(message)) {
|
|
4541
|
+
return new LlmError("TOOLS_UNSUPPORTED", message, provider, status);
|
|
4542
|
+
}
|
|
4543
|
+
if (status === 404 || code === "model_not_found" || code === "model_decommissioned" || isModelNotFoundMessage(message)) {
|
|
4255
4544
|
return new LlmError("MODEL_NOT_FOUND", message, provider, status);
|
|
4256
4545
|
}
|
|
4257
4546
|
if (code === "context_length_exceeded") {
|
|
@@ -4387,8 +4676,15 @@ var init_anthropic = __esm({
|
|
|
4387
4676
|
}
|
|
4388
4677
|
});
|
|
4389
4678
|
|
|
4390
|
-
// src/ai/llm/adapters/openai.ts
|
|
4679
|
+
// src/ai/llm/adapters/openai-compat.ts
|
|
4391
4680
|
import OpenAI from "openai";
|
|
4681
|
+
function makeClient(apiKey, baseUrl) {
|
|
4682
|
+
return new OpenAI({
|
|
4683
|
+
// Keyless endpoints (Ollama) still need a non-empty string for the SDK.
|
|
4684
|
+
apiKey: apiKey || "local",
|
|
4685
|
+
...baseUrl ? { baseURL: baseUrl } : {}
|
|
4686
|
+
});
|
|
4687
|
+
}
|
|
4392
4688
|
function toOpenAiTools(tools) {
|
|
4393
4689
|
return tools.map((t) => ({
|
|
4394
4690
|
type: "function",
|
|
@@ -4457,9 +4753,8 @@ function parseResponse2(message) {
|
|
|
4457
4753
|
assistant_message: { role: "assistant", content: text, tool_calls }
|
|
4458
4754
|
};
|
|
4459
4755
|
}
|
|
4460
|
-
async function
|
|
4461
|
-
const
|
|
4462
|
-
const client = new OpenAI({ apiKey });
|
|
4756
|
+
async function openaiCompatComplete(provider, baseUrl, apiKey, model, req) {
|
|
4757
|
+
const client = makeClient(apiKey, baseUrl);
|
|
4463
4758
|
try {
|
|
4464
4759
|
const response = await client.chat.completions.create({
|
|
4465
4760
|
model,
|
|
@@ -4469,7 +4764,7 @@ async function openaiComplete(apiKey, model, req) {
|
|
|
4469
4764
|
});
|
|
4470
4765
|
const choice = response.choices[0];
|
|
4471
4766
|
if (!choice?.message) {
|
|
4472
|
-
throw new Error(
|
|
4767
|
+
throw new Error(`${provider} returned no message`);
|
|
4473
4768
|
}
|
|
4474
4769
|
const parsed = parseResponse2(choice.message);
|
|
4475
4770
|
if (response.usage) {
|
|
@@ -4480,15 +4775,11 @@ async function openaiComplete(apiKey, model, req) {
|
|
|
4480
4775
|
}
|
|
4481
4776
|
return parsed;
|
|
4482
4777
|
} catch (err) {
|
|
4483
|
-
if (err instanceof OpenAI.APIError) {
|
|
4484
|
-
throw mapOpenAiError(err, provider);
|
|
4485
|
-
}
|
|
4486
4778
|
throw mapOpenAiError(err, provider);
|
|
4487
4779
|
}
|
|
4488
4780
|
}
|
|
4489
|
-
async function*
|
|
4490
|
-
const
|
|
4491
|
-
const client = new OpenAI({ apiKey });
|
|
4781
|
+
async function* openaiCompatStream(provider, baseUrl, apiKey, model, req) {
|
|
4782
|
+
const client = makeClient(apiKey, baseUrl);
|
|
4492
4783
|
try {
|
|
4493
4784
|
const stream = await client.chat.completions.create({
|
|
4494
4785
|
model,
|
|
@@ -4501,68 +4792,118 @@ async function* openaiStream(apiKey, model, req) {
|
|
|
4501
4792
|
if (delta) yield { type: "text_delta", text: delta };
|
|
4502
4793
|
}
|
|
4503
4794
|
} catch (err) {
|
|
4504
|
-
if (err instanceof OpenAI.APIError) {
|
|
4505
|
-
throw mapOpenAiError(err, provider);
|
|
4506
|
-
}
|
|
4507
4795
|
throw mapOpenAiError(err, provider);
|
|
4508
4796
|
}
|
|
4509
4797
|
}
|
|
4510
|
-
var
|
|
4511
|
-
"src/ai/llm/adapters/openai.ts"() {
|
|
4798
|
+
var init_openai_compat = __esm({
|
|
4799
|
+
"src/ai/llm/adapters/openai-compat.ts"() {
|
|
4512
4800
|
"use strict";
|
|
4513
4801
|
init_errors();
|
|
4514
4802
|
}
|
|
4515
4803
|
});
|
|
4516
4804
|
|
|
4517
|
-
// src/ai/llm/
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
if (seen.has(current)) break;
|
|
4530
|
-
seen.add(current);
|
|
4531
|
-
const entry = byId.get(current);
|
|
4532
|
-
if (!entry) return current;
|
|
4533
|
-
if (entry.status === "active") return entry.id;
|
|
4534
|
-
if (!entry.successor_id) {
|
|
4535
|
-
const fallback = cheapestActiveInTier(entry.provider, entry.tier);
|
|
4536
|
-
return fallback?.id ?? current;
|
|
4537
|
-
}
|
|
4538
|
-
current = entry.successor_id;
|
|
4805
|
+
// src/ai/llm/models-cache.ts
|
|
4806
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
4807
|
+
import { join as join11 } from "path";
|
|
4808
|
+
function cachePath() {
|
|
4809
|
+
return join11(ntrpHome(), "models.json");
|
|
4810
|
+
}
|
|
4811
|
+
function loadFile() {
|
|
4812
|
+
if (cached) return cached;
|
|
4813
|
+
const path = cachePath();
|
|
4814
|
+
if (!existsSync11(path)) {
|
|
4815
|
+
cached = { version: 1, providers: {} };
|
|
4816
|
+
return cached;
|
|
4539
4817
|
}
|
|
4540
|
-
|
|
4818
|
+
try {
|
|
4819
|
+
const parsed = JSON.parse(readFileSync9(path, "utf-8"));
|
|
4820
|
+
cached = { version: 1, providers: parsed.providers ?? {} };
|
|
4821
|
+
} catch {
|
|
4822
|
+
cached = { version: 1, providers: {} };
|
|
4823
|
+
}
|
|
4824
|
+
return cached;
|
|
4541
4825
|
}
|
|
4542
|
-
function
|
|
4826
|
+
function saveFile(file) {
|
|
4827
|
+
writeFileSync9(cachePath(), JSON.stringify(file, null, 2) + "\n");
|
|
4828
|
+
cached = file;
|
|
4829
|
+
}
|
|
4830
|
+
function getProviderModels(provider) {
|
|
4831
|
+
return loadFile().providers[provider];
|
|
4832
|
+
}
|
|
4833
|
+
function setProviderModels(provider, entry) {
|
|
4834
|
+
const file = loadFile();
|
|
4835
|
+
file.providers[provider] = entry;
|
|
4836
|
+
saveFile(file);
|
|
4837
|
+
}
|
|
4838
|
+
function getCachedTierModel(provider, tier) {
|
|
4839
|
+
return getProviderModels(provider)?.tier_stack?.[tier];
|
|
4840
|
+
}
|
|
4841
|
+
function findCachedModel(provider, modelId) {
|
|
4842
|
+
return getProviderModels(provider)?.models.find((m) => m.id === modelId);
|
|
4843
|
+
}
|
|
4844
|
+
function cachedModelProvider(modelId) {
|
|
4845
|
+
const file = loadFile();
|
|
4846
|
+
for (const [provider, entry] of Object.entries(file.providers)) {
|
|
4847
|
+
if (entry.models.some((m) => m.id === modelId)) return provider;
|
|
4848
|
+
}
|
|
4849
|
+
return void 0;
|
|
4850
|
+
}
|
|
4851
|
+
function markModelNoTools(provider, modelId) {
|
|
4852
|
+
const file = loadFile();
|
|
4853
|
+
const entry = file.providers[provider];
|
|
4854
|
+
if (!entry) return;
|
|
4855
|
+
const noTools = new Set(entry.quirks?.no_tools ?? []);
|
|
4856
|
+
if (noTools.has(modelId)) return;
|
|
4857
|
+
noTools.add(modelId);
|
|
4858
|
+
entry.quirks = { ...entry.quirks, no_tools: [...noTools] };
|
|
4859
|
+
saveFile(file);
|
|
4860
|
+
}
|
|
4861
|
+
function modelHasNoToolsQuirk(provider, modelId) {
|
|
4862
|
+
return !!getProviderModels(provider)?.quirks?.no_tools?.includes(modelId);
|
|
4863
|
+
}
|
|
4864
|
+
function isProviderCacheStale(provider, ttlMs = CACHE_TTL_MS) {
|
|
4865
|
+
const entry = getProviderModels(provider);
|
|
4866
|
+
if (!entry) return true;
|
|
4867
|
+
const fetched = Date.parse(entry.fetched_at);
|
|
4868
|
+
if (Number.isNaN(fetched)) return true;
|
|
4869
|
+
return Date.now() - fetched > ttlMs;
|
|
4870
|
+
}
|
|
4871
|
+
var CACHE_TTL_MS, cached;
|
|
4872
|
+
var init_models_cache = __esm({
|
|
4873
|
+
"src/ai/llm/models-cache.ts"() {
|
|
4874
|
+
"use strict";
|
|
4875
|
+
init_store();
|
|
4876
|
+
CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
4877
|
+
cached = null;
|
|
4878
|
+
}
|
|
4879
|
+
});
|
|
4880
|
+
|
|
4881
|
+
// src/ai/llm/catalog.ts
|
|
4882
|
+
function catalogTierDefault(provider, tier) {
|
|
4543
4883
|
const candidates = ENTRIES.filter(
|
|
4544
4884
|
(e) => e.provider === provider && e.tier === tier && e.status === "active"
|
|
4545
4885
|
);
|
|
4546
4886
|
if (candidates.length === 0) return void 0;
|
|
4547
4887
|
return candidates.sort((a, b) => a.relative_cost - b.relative_cost)[0];
|
|
4548
4888
|
}
|
|
4549
|
-
function
|
|
4550
|
-
|
|
4551
|
-
if (!entry) {
|
|
4552
|
-
throw new Error(`No active ${tier}-tier model for provider ${provider} in catalog`);
|
|
4553
|
-
}
|
|
4554
|
-
return entry;
|
|
4889
|
+
function modelProviderHint(modelId) {
|
|
4890
|
+
return cachedModelProvider(modelId) ?? byId.get(modelId)?.provider;
|
|
4555
4891
|
}
|
|
4556
|
-
function
|
|
4557
|
-
if (override)
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4892
|
+
function overrideForProvider(override, provider, activeProvider) {
|
|
4893
|
+
if (!override) return void 0;
|
|
4894
|
+
const hint = modelProviderHint(override);
|
|
4895
|
+
if (hint) return hint === provider ? override : void 0;
|
|
4896
|
+
return provider === activeProvider ? override : void 0;
|
|
4897
|
+
}
|
|
4898
|
+
function resolveModelSafe(provider, tier, override) {
|
|
4899
|
+
if (override) return override;
|
|
4900
|
+
const discovered = getCachedTierModel(provider, tier);
|
|
4901
|
+
if (discovered) return discovered;
|
|
4902
|
+
return catalogTierDefault(provider, tier)?.id;
|
|
4564
4903
|
}
|
|
4565
4904
|
function formatModelLabel(provider, modelId) {
|
|
4905
|
+
const cachedName = findCachedModel(provider, modelId)?.display_name;
|
|
4906
|
+
if (cachedName) return `${provider}/${cachedName}`;
|
|
4566
4907
|
const entry = byId.get(modelId);
|
|
4567
4908
|
return entry ? `${provider}/${entry.display_name}` : `${provider}/${modelId}`;
|
|
4568
4909
|
}
|
|
@@ -4570,6 +4911,7 @@ var ENTRIES, byId;
|
|
|
4570
4911
|
var init_catalog = __esm({
|
|
4571
4912
|
"src/ai/llm/catalog.ts"() {
|
|
4572
4913
|
"use strict";
|
|
4914
|
+
init_models_cache();
|
|
4573
4915
|
ENTRIES = [
|
|
4574
4916
|
{
|
|
4575
4917
|
id: "claude-opus-4-6",
|
|
@@ -4642,6 +4984,306 @@ var init_catalog = __esm({
|
|
|
4642
4984
|
}
|
|
4643
4985
|
});
|
|
4644
4986
|
|
|
4987
|
+
// src/ai/llm/http.ts
|
|
4988
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
4989
|
+
function fixtureResponse(url, headers) {
|
|
4990
|
+
try {
|
|
4991
|
+
const raw = readFileSync10(process.env.NTRP_LLM_HTTP_FIXTURE, "utf-8");
|
|
4992
|
+
const entries = JSON.parse(raw);
|
|
4993
|
+
const headerValues = Object.values(headers).join(" ");
|
|
4994
|
+
for (const entry of entries) {
|
|
4995
|
+
if (!url.includes(entry.url_includes)) continue;
|
|
4996
|
+
if (entry.auth_includes && !headerValues.includes(entry.auth_includes)) continue;
|
|
4997
|
+
return { status: entry.status, ok: entry.status >= 200 && entry.status < 300, body: entry.body };
|
|
4998
|
+
}
|
|
4999
|
+
} catch {
|
|
5000
|
+
}
|
|
5001
|
+
return { status: 0, ok: false, body: void 0 };
|
|
5002
|
+
}
|
|
5003
|
+
async function llmHttpGetJson(url, headers, timeoutMs = 6e3) {
|
|
5004
|
+
if (process.env.NTRP_LLM_HTTP_FIXTURE) {
|
|
5005
|
+
return fixtureResponse(url, headers);
|
|
5006
|
+
}
|
|
5007
|
+
const controller = new AbortController();
|
|
5008
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
5009
|
+
try {
|
|
5010
|
+
const res = await fetch(url, { method: "GET", headers, signal: controller.signal });
|
|
5011
|
+
let body;
|
|
5012
|
+
try {
|
|
5013
|
+
body = await res.json();
|
|
5014
|
+
} catch {
|
|
5015
|
+
body = void 0;
|
|
5016
|
+
}
|
|
5017
|
+
return { status: res.status, ok: res.ok, body };
|
|
5018
|
+
} catch {
|
|
5019
|
+
return { status: 0, ok: false, body: void 0 };
|
|
5020
|
+
} finally {
|
|
5021
|
+
clearTimeout(timer);
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
var init_http = __esm({
|
|
5025
|
+
"src/ai/llm/http.ts"() {
|
|
5026
|
+
"use strict";
|
|
5027
|
+
}
|
|
5028
|
+
});
|
|
5029
|
+
|
|
5030
|
+
// src/ai/llm/ranking.ts
|
|
5031
|
+
function compareModels(a, b) {
|
|
5032
|
+
const createdA = a.created ?? 0;
|
|
5033
|
+
const createdB = b.created ?? 0;
|
|
5034
|
+
if (createdA !== createdB) return createdB - createdA;
|
|
5035
|
+
const versionA = extractVersion(a.id);
|
|
5036
|
+
const versionB = extractVersion(b.id);
|
|
5037
|
+
if (versionA !== versionB) return versionB - versionA;
|
|
5038
|
+
if (a.id.length !== b.id.length) return a.id.length - b.id.length;
|
|
5039
|
+
return a.id.localeCompare(b.id);
|
|
5040
|
+
}
|
|
5041
|
+
function extractVersion(id) {
|
|
5042
|
+
const match = id.match(/(\d+(?:\.\d+)?)/);
|
|
5043
|
+
return match ? Number(match[1]) : 0;
|
|
5044
|
+
}
|
|
5045
|
+
function pickByPatterns(models, patterns) {
|
|
5046
|
+
for (const pattern of patterns) {
|
|
5047
|
+
const matches = models.filter((m) => pattern.test(m.id));
|
|
5048
|
+
if (matches.length > 0) return [...matches].sort(compareModels)[0];
|
|
5049
|
+
}
|
|
5050
|
+
return void 0;
|
|
5051
|
+
}
|
|
5052
|
+
function genericBucket(model) {
|
|
5053
|
+
if (GENERIC_HIGH.test(model.id)) return "high";
|
|
5054
|
+
if (GENERIC_LOW.test(model.id)) return "low";
|
|
5055
|
+
return "medium";
|
|
5056
|
+
}
|
|
5057
|
+
function genericPick(models, tier) {
|
|
5058
|
+
const bucket = models.filter((m) => genericBucket(m) === tier);
|
|
5059
|
+
if (bucket.length > 0) return [...bucket].sort(compareModels)[0];
|
|
5060
|
+
return void 0;
|
|
5061
|
+
}
|
|
5062
|
+
function rankModels(providerId, models) {
|
|
5063
|
+
if (models.length === 0) return null;
|
|
5064
|
+
const preferences = PROVIDER_PREFERENCES[providerId];
|
|
5065
|
+
const picks = {};
|
|
5066
|
+
for (const tier of ["high", "medium", "low"]) {
|
|
5067
|
+
const preferred = preferences ? pickByPatterns(models, preferences[tier]) : void 0;
|
|
5068
|
+
const generic = preferred ?? genericPick(models, tier);
|
|
5069
|
+
if (generic) picks[tier] = generic.id;
|
|
5070
|
+
}
|
|
5071
|
+
const anyModel = [...models].sort(compareModels)[0].id;
|
|
5072
|
+
const high = picks.high ?? picks.medium ?? picks.low ?? anyModel;
|
|
5073
|
+
const medium = picks.medium ?? picks.high ?? picks.low ?? anyModel;
|
|
5074
|
+
const low = picks.low ?? picks.medium ?? picks.high ?? anyModel;
|
|
5075
|
+
return { high, medium, low };
|
|
5076
|
+
}
|
|
5077
|
+
var PROVIDER_PREFERENCES, GENERIC_LOW, GENERIC_HIGH;
|
|
5078
|
+
var init_ranking = __esm({
|
|
5079
|
+
"src/ai/llm/ranking.ts"() {
|
|
5080
|
+
"use strict";
|
|
5081
|
+
PROVIDER_PREFERENCES = {
|
|
5082
|
+
anthropic: {
|
|
5083
|
+
high: [/^claude-opus/i, /^claude-sonnet/i],
|
|
5084
|
+
medium: [/^claude-sonnet/i, /^claude-haiku/i],
|
|
5085
|
+
low: [/^claude-haiku/i, /^claude-sonnet/i]
|
|
5086
|
+
},
|
|
5087
|
+
openai: {
|
|
5088
|
+
high: [/^gpt-5(?!.*(mini|nano|chat))/i, /^gpt-4\.1(?!.*(mini|nano))/i, /^gpt-4o(?!.*mini)/i, /^o3(?!.*mini)/i],
|
|
5089
|
+
medium: [/^gpt-5.*mini/i, /^gpt-4\.1-mini/i, /^gpt-4o-mini/i, /^o4-mini/i],
|
|
5090
|
+
low: [/^gpt-5.*nano/i, /^gpt-4\.1-nano/i, /^gpt-4o-mini/i]
|
|
5091
|
+
},
|
|
5092
|
+
google: {
|
|
5093
|
+
high: [/^gemini-[\d.]+-pro/i, /^gemini-[\d.]+-flash(?!-lite)/i],
|
|
5094
|
+
medium: [/^gemini-[\d.]+-flash(?!-lite|-8b)/i, /^gemini-[\d.]+-pro/i],
|
|
5095
|
+
low: [/^gemini-[\d.]+-flash-lite/i, /flash-8b/i, /^gemini-[\d.]+-flash(?!-lite)/i]
|
|
5096
|
+
},
|
|
5097
|
+
groq: {
|
|
5098
|
+
high: [/llama-3\.3-70b/i, /gpt-oss-120b/i, /70b/i, /deepseek-r1/i],
|
|
5099
|
+
medium: [/llama-3\.1-8b-instant/i, /gpt-oss-20b/i, /llama.*8b/i],
|
|
5100
|
+
low: [/8b-instant/i, /llama.*8b/i, /gemma/i]
|
|
5101
|
+
},
|
|
5102
|
+
deepseek: {
|
|
5103
|
+
high: [/reasoner/i, /chat/i],
|
|
5104
|
+
medium: [/chat/i],
|
|
5105
|
+
low: [/chat/i]
|
|
5106
|
+
},
|
|
5107
|
+
mistral: {
|
|
5108
|
+
high: [/large/i, /medium/i],
|
|
5109
|
+
medium: [/medium/i, /^mistral-small/i],
|
|
5110
|
+
low: [/ministral/i, /small/i, /tiny/i]
|
|
5111
|
+
},
|
|
5112
|
+
xai: {
|
|
5113
|
+
high: [/^grok-\d+(?!.*(mini|fast))/i, /^grok(?!.*(mini|fast))/i],
|
|
5114
|
+
medium: [/^grok.*mini(?!.*fast)/i, /^grok.*fast/i],
|
|
5115
|
+
low: [/^grok.*mini.*fast/i, /^grok.*mini/i]
|
|
5116
|
+
},
|
|
5117
|
+
openrouter: {
|
|
5118
|
+
high: [/^openrouter\/auto$/i, /claude.*opus/i, /^openai\/gpt-5(?!.*(mini|nano))/i, /gemini.*pro/i],
|
|
5119
|
+
medium: [/claude.*sonnet/i, /gpt-5.*mini/i, /gpt-4\.1-mini/i, /gemini.*flash(?!-lite)/i],
|
|
5120
|
+
low: [/claude.*haiku/i, /nano/i, /flash-lite/i, /mini/i]
|
|
5121
|
+
}
|
|
5122
|
+
};
|
|
5123
|
+
GENERIC_LOW = /(mini|nano|lite|tiny|micro|small|haiku|instant|flash|turbo|\b0?\.?5b\b|\b[1-8]b\b)/i;
|
|
5124
|
+
GENERIC_HIGH = /(opus|ultra|large|max\b|\bpro\b|405b|253b|235b|120b|72b|70b|reason|-r1\b|think|deep)/i;
|
|
5125
|
+
}
|
|
5126
|
+
});
|
|
5127
|
+
|
|
5128
|
+
// src/ai/llm/discovery.ts
|
|
5129
|
+
var discovery_exports = {};
|
|
5130
|
+
__export(discovery_exports, {
|
|
5131
|
+
fetchProviderModels: () => fetchProviderModels,
|
|
5132
|
+
filterChatModels: () => filterChatModels,
|
|
5133
|
+
refreshProviderModels: () => refreshProviderModels,
|
|
5134
|
+
refreshStaleProviderCaches: () => refreshStaleProviderCaches,
|
|
5135
|
+
rerankExcluding: () => rerankExcluding,
|
|
5136
|
+
storeDiscoveredModels: () => storeDiscoveredModels
|
|
5137
|
+
});
|
|
5138
|
+
function authHeaders(spec, apiKey) {
|
|
5139
|
+
if (spec.api === "anthropic") {
|
|
5140
|
+
return { "x-api-key": apiKey ?? "", "anthropic-version": "2023-06-01" };
|
|
5141
|
+
}
|
|
5142
|
+
return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
|
|
5143
|
+
}
|
|
5144
|
+
function normalizeItem(spec, item) {
|
|
5145
|
+
let id = item.id ?? "";
|
|
5146
|
+
if (!id) return null;
|
|
5147
|
+
if (id.startsWith("models/")) id = id.slice("models/".length);
|
|
5148
|
+
const model = { id };
|
|
5149
|
+
const display2 = item.display_name ?? item.name;
|
|
5150
|
+
if (display2 && display2 !== id) model.display_name = display2;
|
|
5151
|
+
if (typeof item.created === "number") model.created = item.created;
|
|
5152
|
+
else if (item.created_at) {
|
|
5153
|
+
const parsed = Date.parse(item.created_at);
|
|
5154
|
+
if (!Number.isNaN(parsed)) model.created = Math.floor(parsed / 1e3);
|
|
5155
|
+
}
|
|
5156
|
+
if (typeof item.context_length === "number") model.context_length = item.context_length;
|
|
5157
|
+
if (Array.isArray(item.supported_parameters)) {
|
|
5158
|
+
model.supports_tools = item.supported_parameters.includes("tools");
|
|
5159
|
+
}
|
|
5160
|
+
return model;
|
|
5161
|
+
}
|
|
5162
|
+
async function fetchProviderModels(spec, apiKey, timeoutMs = 6e3) {
|
|
5163
|
+
const headers = authHeaders(spec, apiKey);
|
|
5164
|
+
if (spec.api === "anthropic") {
|
|
5165
|
+
const models2 = [];
|
|
5166
|
+
let url = modelsUrl(spec);
|
|
5167
|
+
for (let page = 0; page < 5 && url; page++) {
|
|
5168
|
+
const res2 = await llmHttpGetJson(url, headers, timeoutMs);
|
|
5169
|
+
if (!res2.ok) return models2.length > 0 ? { ok: true, models: models2 } : { ok: false, status: res2.status };
|
|
5170
|
+
const body2 = res2.body;
|
|
5171
|
+
for (const item of body2?.data ?? []) {
|
|
5172
|
+
const model = normalizeItem(spec, item);
|
|
5173
|
+
if (model) models2.push(model);
|
|
5174
|
+
}
|
|
5175
|
+
url = body2?.has_more && body2.last_id ? `${spec.base_url}/v1/models?limit=100&after_id=${encodeURIComponent(body2.last_id)}` : null;
|
|
5176
|
+
}
|
|
5177
|
+
return { ok: true, models: models2 };
|
|
5178
|
+
}
|
|
5179
|
+
const res = await llmHttpGetJson(modelsUrl(spec), headers, timeoutMs);
|
|
5180
|
+
if (!res.ok) return { ok: false, status: res.status };
|
|
5181
|
+
const body = res.body;
|
|
5182
|
+
const list = Array.isArray(body) ? body : body?.data ?? [];
|
|
5183
|
+
const models = [];
|
|
5184
|
+
for (const item of list) {
|
|
5185
|
+
const model = normalizeItem(spec, item);
|
|
5186
|
+
if (model) models.push(model);
|
|
5187
|
+
}
|
|
5188
|
+
return { ok: true, models };
|
|
5189
|
+
}
|
|
5190
|
+
function filterChatModels(spec, models) {
|
|
5191
|
+
const extra = PROVIDER_EXCLUDE[spec.id];
|
|
5192
|
+
return models.filter((m) => !NON_CHAT.test(m.id) && !(extra && extra.test(m.id)));
|
|
5193
|
+
}
|
|
5194
|
+
function storeDiscoveredModels(providerId, rawModels) {
|
|
5195
|
+
const spec = getProviderSpec(providerId);
|
|
5196
|
+
if (!spec || rawModels.length === 0) return null;
|
|
5197
|
+
const chat = filterChatModels(spec, rawModels);
|
|
5198
|
+
const usable = chat.length > 0 ? chat : rawModels;
|
|
5199
|
+
const stack = rankModels(providerId, usable);
|
|
5200
|
+
if (!stack) return null;
|
|
5201
|
+
const prior = getProviderModels(providerId);
|
|
5202
|
+
const entry = {
|
|
5203
|
+
fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5204
|
+
models: usable,
|
|
5205
|
+
tier_stack: stack,
|
|
5206
|
+
...prior?.quirks ? { quirks: prior.quirks } : {}
|
|
5207
|
+
};
|
|
5208
|
+
setProviderModels(providerId, entry);
|
|
5209
|
+
return entry;
|
|
5210
|
+
}
|
|
5211
|
+
async function refreshProviderModels(providerId, opts = {}) {
|
|
5212
|
+
const spec = getProviderSpec(providerId);
|
|
5213
|
+
if (!spec) return null;
|
|
5214
|
+
if (!opts.force && !isProviderCacheStale(providerId)) {
|
|
5215
|
+
return getProviderModels(providerId) ?? null;
|
|
5216
|
+
}
|
|
5217
|
+
const apiKey = opts.apiKey ?? getProviderApiKey(providerId);
|
|
5218
|
+
if (spec.requires_key && !apiKey) return null;
|
|
5219
|
+
const result = await fetchProviderModels(spec, apiKey);
|
|
5220
|
+
if (!result.ok) return null;
|
|
5221
|
+
return storeDiscoveredModels(providerId, result.models);
|
|
5222
|
+
}
|
|
5223
|
+
function rerankExcluding(providerId, deadModelId) {
|
|
5224
|
+
const prior = getProviderModels(providerId);
|
|
5225
|
+
if (!prior) return null;
|
|
5226
|
+
const survivors = prior.models.filter((m) => m.id !== deadModelId);
|
|
5227
|
+
const stack = rankModels(providerId, survivors);
|
|
5228
|
+
if (!stack) return null;
|
|
5229
|
+
const entry = { ...prior, models: survivors, tier_stack: stack };
|
|
5230
|
+
setProviderModels(providerId, entry);
|
|
5231
|
+
return entry;
|
|
5232
|
+
}
|
|
5233
|
+
async function refreshStaleProviderCaches() {
|
|
5234
|
+
await Promise.allSettled(
|
|
5235
|
+
getAvailableProviders().filter((p) => isProviderCacheStale(p)).map((p) => refreshProviderModels(p))
|
|
5236
|
+
);
|
|
5237
|
+
}
|
|
5238
|
+
var NON_CHAT, PROVIDER_EXCLUDE;
|
|
5239
|
+
var init_discovery = __esm({
|
|
5240
|
+
"src/ai/llm/discovery.ts"() {
|
|
5241
|
+
"use strict";
|
|
5242
|
+
init_llm_config();
|
|
5243
|
+
init_http();
|
|
5244
|
+
init_models_cache();
|
|
5245
|
+
init_providers();
|
|
5246
|
+
init_ranking();
|
|
5247
|
+
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;
|
|
5248
|
+
PROVIDER_EXCLUDE = {
|
|
5249
|
+
openai: /(chatgpt|-search|deep-research|-pro\b|computer-use|codex-mini|-instruct\b)/i
|
|
5250
|
+
};
|
|
5251
|
+
}
|
|
5252
|
+
});
|
|
5253
|
+
|
|
5254
|
+
// src/ai/llm/heal.ts
|
|
5255
|
+
async function healModelNotFound(opts) {
|
|
5256
|
+
const { provider, tier, deadModel } = opts;
|
|
5257
|
+
const refreshed = await refreshProviderModels(provider, { apiKey: opts.apiKey, force: true });
|
|
5258
|
+
let candidate = refreshed?.tier_stack?.[tier] ?? getCachedTierModel(provider, tier);
|
|
5259
|
+
if (!candidate || candidate === deadModel) {
|
|
5260
|
+
const reranked = rerankExcluding(provider, deadModel);
|
|
5261
|
+
candidate = reranked?.tier_stack?.[tier];
|
|
5262
|
+
}
|
|
5263
|
+
if (!candidate || candidate === deadModel) return null;
|
|
5264
|
+
clearDeadOverride(deadModel, opts.ctx);
|
|
5265
|
+
return {
|
|
5266
|
+
model: candidate,
|
|
5267
|
+
notice: `model ${deadModel} is no longer available \u2014 switched to ${candidate}`
|
|
5268
|
+
};
|
|
5269
|
+
}
|
|
5270
|
+
function clearDeadOverride(deadModel, ctx) {
|
|
5271
|
+
if (getConfigValue("llm-model-override")?.trim() === deadModel) {
|
|
5272
|
+
deleteConfigValue("llm-model-override");
|
|
5273
|
+
}
|
|
5274
|
+
if (ctx?.llm?.modelOverride === deadModel) {
|
|
5275
|
+
ctx.llm.modelOverride = void 0;
|
|
5276
|
+
}
|
|
5277
|
+
}
|
|
5278
|
+
var init_heal = __esm({
|
|
5279
|
+
"src/ai/llm/heal.ts"() {
|
|
5280
|
+
"use strict";
|
|
5281
|
+
init_store();
|
|
5282
|
+
init_discovery();
|
|
5283
|
+
init_models_cache();
|
|
5284
|
+
}
|
|
5285
|
+
});
|
|
5286
|
+
|
|
4645
5287
|
// src/ai/llm/surfaces.ts
|
|
4646
5288
|
function tierForSurface(surface, userTier) {
|
|
4647
5289
|
const spec = SURFACE_SPECS[surface];
|
|
@@ -4713,7 +5355,7 @@ function resolveActiveProvider(ctx) {
|
|
|
4713
5355
|
if (session && hasProviderKey(session)) return session;
|
|
4714
5356
|
const cfg = loadLlmConfig();
|
|
4715
5357
|
if (hasProviderKey(cfg.primary)) return cfg.primary;
|
|
4716
|
-
const available =
|
|
5358
|
+
const available = getAvailableProviders();
|
|
4717
5359
|
if (available.length > 0) return available[0];
|
|
4718
5360
|
return cfg.primary;
|
|
4719
5361
|
}
|
|
@@ -4735,8 +5377,8 @@ function resolveModelForActive(ctx, surface) {
|
|
|
4735
5377
|
const provider = resolveActiveProvider(ctx);
|
|
4736
5378
|
const tier = resolveEffectiveTier(ctx, surface);
|
|
4737
5379
|
const override = resolveEffectiveModelOverride(ctx);
|
|
4738
|
-
const providerOverride = override
|
|
4739
|
-
const modelId =
|
|
5380
|
+
const providerOverride = overrideForProvider(override, provider, provider);
|
|
5381
|
+
const modelId = resolveModelSafe(provider, tier, providerOverride);
|
|
4740
5382
|
return { provider, tier, modelId };
|
|
4741
5383
|
}
|
|
4742
5384
|
function resolveProviderOrder(ctx) {
|
|
@@ -4747,30 +5389,30 @@ function resolveProviderOrder(ctx) {
|
|
|
4747
5389
|
for (const p of cfg.failoverOrder) {
|
|
4748
5390
|
if (p !== active && hasProviderKey(p) && !order.includes(p)) order.push(p);
|
|
4749
5391
|
}
|
|
4750
|
-
for (const p of
|
|
4751
|
-
if (p !== active &&
|
|
5392
|
+
for (const p of getAvailableProviders()) {
|
|
5393
|
+
if (p !== active && !order.includes(p)) order.push(p);
|
|
4752
5394
|
}
|
|
4753
5395
|
return order;
|
|
4754
5396
|
}
|
|
4755
5397
|
function formatActiveStack(ctx, surface = "agentic_investigation") {
|
|
4756
5398
|
const { provider, tier, modelId } = resolveModelForActive(ctx, surface);
|
|
4757
|
-
return `${provider} \xB7 ${tier} \xB7 ${modelId}`;
|
|
5399
|
+
return `${provider} \xB7 ${tier} \xB7 ${modelId ?? "no models yet (run /connect)"}`;
|
|
4758
5400
|
}
|
|
4759
5401
|
function formatActiveStackShort(ctx, surface = "agentic_investigation") {
|
|
4760
5402
|
const { provider, tier } = resolveModelForActive(ctx, surface);
|
|
4761
5403
|
return `${provider} \xB7 ${tier}`;
|
|
4762
5404
|
}
|
|
4763
5405
|
function countAvailableEngines() {
|
|
4764
|
-
return
|
|
5406
|
+
return getAvailableProviders().length;
|
|
4765
5407
|
}
|
|
4766
5408
|
function availableEngineLabels() {
|
|
4767
|
-
return
|
|
5409
|
+
return getAvailableProviders();
|
|
4768
5410
|
}
|
|
4769
5411
|
function validateModelForProvider(modelId, provider) {
|
|
4770
|
-
const
|
|
4771
|
-
if (!
|
|
4772
|
-
if (
|
|
4773
|
-
return `Model ${modelId} belongs to ${
|
|
5412
|
+
const hint = modelProviderHint(modelId);
|
|
5413
|
+
if (!hint) return null;
|
|
5414
|
+
if (hint !== provider) {
|
|
5415
|
+
return `Model ${modelId} belongs to ${hint}. Run /provider ${hint} first.`;
|
|
4774
5416
|
}
|
|
4775
5417
|
return null;
|
|
4776
5418
|
}
|
|
@@ -4784,23 +5426,16 @@ var init_session_state = __esm({
|
|
|
4784
5426
|
});
|
|
4785
5427
|
|
|
4786
5428
|
// src/ai/llm/resolver.ts
|
|
4787
|
-
function getProviderOrder(config, ctx) {
|
|
4788
|
-
void config;
|
|
4789
|
-
return resolveProviderOrder(ctx);
|
|
4790
|
-
}
|
|
4791
5429
|
function resolveCompletionContext(surface, opts = {}) {
|
|
4792
5430
|
const activeProvider = resolveActiveProvider(opts.ctx);
|
|
4793
5431
|
const tier = opts.tier ?? resolveEffectiveTier(opts.ctx, surface);
|
|
4794
5432
|
const override = opts.modelOverride ?? resolveEffectiveModelOverride(opts.ctx);
|
|
4795
5433
|
const providerOrder = resolveProviderOrder(opts.ctx);
|
|
4796
5434
|
const modelByProvider = {};
|
|
4797
|
-
for (const provider of providerOrder) {
|
|
4798
|
-
const providerOverride =
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
if (!modelByProvider[activeProvider]) {
|
|
4802
|
-
const activeOverride = override && getCatalogEntry(override)?.provider === activeProvider ? override : void 0;
|
|
4803
|
-
modelByProvider[activeProvider] = resolveModel(activeProvider, tier, activeOverride);
|
|
5435
|
+
for (const provider of /* @__PURE__ */ new Set([...providerOrder, activeProvider])) {
|
|
5436
|
+
const providerOverride = overrideForProvider(override, provider, activeProvider);
|
|
5437
|
+
const model = resolveModelSafe(provider, tier, providerOverride);
|
|
5438
|
+
if (model) modelByProvider[provider] = model;
|
|
4804
5439
|
}
|
|
4805
5440
|
return {
|
|
4806
5441
|
providerOrder,
|
|
@@ -4826,70 +5461,142 @@ var init_resolver = __esm({
|
|
|
4826
5461
|
|
|
4827
5462
|
// src/ai/llm/failover.ts
|
|
4828
5463
|
async function completeOnProvider(provider, model, apiKey, req) {
|
|
4829
|
-
|
|
4830
|
-
|
|
5464
|
+
const spec = getProviderSpec(provider);
|
|
5465
|
+
if (!spec) {
|
|
5466
|
+
throw new LlmError("UNKNOWN", `Unknown provider "${provider}" \u2014 run /connect to register it.`, provider);
|
|
5467
|
+
}
|
|
5468
|
+
if (spec.api === "anthropic") {
|
|
5469
|
+
return anthropicComplete(apiKey ?? "", model, req);
|
|
5470
|
+
}
|
|
5471
|
+
return openaiCompatComplete(provider, spec.base_url, apiKey, model, req);
|
|
5472
|
+
}
|
|
5473
|
+
function usableKey(provider, ctx) {
|
|
5474
|
+
const spec = getProviderSpec(provider);
|
|
5475
|
+
if (!spec) return { ok: false };
|
|
5476
|
+
const apiKey = getApiKeyForProvider(provider, ctx);
|
|
5477
|
+
if (spec.requires_key && !apiKey) return { ok: false };
|
|
5478
|
+
return { ok: true, apiKey };
|
|
5479
|
+
}
|
|
5480
|
+
async function resolveModelWithDiscovery(provider, cfg, opts) {
|
|
5481
|
+
const known = cfg.modelByProvider[provider];
|
|
5482
|
+
if (known) return known;
|
|
5483
|
+
const override = overrideForProvider(opts.modelOverride, provider, cfg.activeProvider);
|
|
5484
|
+
const direct = resolveModelSafe(provider, cfg.tier, override);
|
|
5485
|
+
if (direct) return direct;
|
|
5486
|
+
await refreshProviderModels(provider, { apiKey: opts.apiKey, force: true }).catch(() => null);
|
|
5487
|
+
return resolveModelSafe(provider, cfg.tier, override);
|
|
5488
|
+
}
|
|
5489
|
+
function stripTools(req) {
|
|
5490
|
+
const { tools: _tools, ...rest } = req;
|
|
5491
|
+
return rest;
|
|
4831
5492
|
}
|
|
4832
5493
|
async function completeWithFailover(req, opts = {}) {
|
|
4833
|
-
const
|
|
5494
|
+
const cfg = resolveCompletionContext(req.surface, {
|
|
4834
5495
|
max_tokens: req.max_tokens,
|
|
4835
5496
|
tier: opts.tier,
|
|
4836
5497
|
modelOverride: opts.modelOverride,
|
|
4837
5498
|
ctx: opts.ctx
|
|
4838
5499
|
});
|
|
4839
|
-
const providers =
|
|
5500
|
+
const providers = cfg.providerOrder;
|
|
4840
5501
|
if (providers.length === 0) {
|
|
4841
|
-
throw new Error(
|
|
5502
|
+
throw new Error(NO_PROVIDER_MESSAGE);
|
|
4842
5503
|
}
|
|
5504
|
+
const notices = [];
|
|
4843
5505
|
let lastError;
|
|
4844
5506
|
let failoverFrom;
|
|
5507
|
+
const buildMeta = (provider, model, response) => ({
|
|
5508
|
+
provider_used: provider,
|
|
5509
|
+
model_used: model,
|
|
5510
|
+
...response.token_usage ?? {},
|
|
5511
|
+
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {},
|
|
5512
|
+
...notices.length > 0 ? { notices: [...notices] } : {}
|
|
5513
|
+
});
|
|
4845
5514
|
for (let i = 0; i < providers.length; i++) {
|
|
4846
5515
|
const provider = providers[i];
|
|
4847
|
-
const
|
|
4848
|
-
if (!
|
|
4849
|
-
let model =
|
|
5516
|
+
const key = usableKey(provider, opts.ctx);
|
|
5517
|
+
if (!key.ok) continue;
|
|
5518
|
+
let model = await resolveModelWithDiscovery(provider, cfg, {
|
|
5519
|
+
modelOverride: opts.modelOverride,
|
|
5520
|
+
apiKey: key.apiKey
|
|
5521
|
+
});
|
|
5522
|
+
if (!model) {
|
|
5523
|
+
lastError = new LlmError(
|
|
5524
|
+
"MODEL_NOT_FOUND",
|
|
5525
|
+
`No models known for provider "${provider}". Run /connect or /model refresh.`,
|
|
5526
|
+
provider
|
|
5527
|
+
);
|
|
5528
|
+
continue;
|
|
5529
|
+
}
|
|
5530
|
+
let effectiveReq = req;
|
|
5531
|
+
if (req.tools?.length && modelHasNoToolsQuirk(provider, model)) {
|
|
5532
|
+
effectiveReq = stripTools(req);
|
|
5533
|
+
notices.push(`${model} doesn't support tool calling \u2014 answering without live data tools`);
|
|
5534
|
+
}
|
|
4850
5535
|
try {
|
|
4851
|
-
const response = await completeOnProvider(provider, model, apiKey,
|
|
4852
|
-
const meta =
|
|
4853
|
-
provider_used: provider,
|
|
4854
|
-
model_used: model,
|
|
4855
|
-
...response.token_usage ?? {},
|
|
4856
|
-
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
|
|
4857
|
-
};
|
|
5536
|
+
const response = await completeOnProvider(provider, model, key.apiKey, effectiveReq);
|
|
5537
|
+
const meta = buildMeta(provider, model, response);
|
|
4858
5538
|
recordLlmUsage(response.token_usage);
|
|
4859
5539
|
return { response, meta };
|
|
4860
5540
|
} catch (err) {
|
|
4861
|
-
|
|
5541
|
+
let llmErr = err;
|
|
4862
5542
|
if (llmErr.name !== "LlmError") throw err;
|
|
4863
5543
|
lastError = llmErr;
|
|
4864
|
-
if (llmErr.code === "
|
|
4865
|
-
|
|
5544
|
+
if (llmErr.code === "TOOLS_UNSUPPORTED" && effectiveReq.tools?.length) {
|
|
5545
|
+
markModelNoTools(provider, model);
|
|
5546
|
+
notices.push(`${model} doesn't support tool calling \u2014 retrying without live data tools`);
|
|
4866
5547
|
try {
|
|
4867
|
-
const response = await completeOnProvider(provider, model, apiKey,
|
|
4868
|
-
const meta =
|
|
4869
|
-
provider_used: provider,
|
|
4870
|
-
model_used: model,
|
|
4871
|
-
...response.token_usage ?? {},
|
|
4872
|
-
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
|
|
4873
|
-
};
|
|
5548
|
+
const response = await completeOnProvider(provider, model, key.apiKey, stripTools(effectiveReq));
|
|
5549
|
+
const meta = buildMeta(provider, model, response);
|
|
4874
5550
|
recordLlmUsage(response.token_usage);
|
|
4875
5551
|
return { response, meta };
|
|
4876
5552
|
} catch (retryErr) {
|
|
4877
5553
|
const retryLlm = retryErr;
|
|
4878
|
-
if (retryLlm.name
|
|
4879
|
-
|
|
5554
|
+
if (retryLlm.name !== "LlmError") throw retryErr;
|
|
5555
|
+
lastError = retryLlm;
|
|
5556
|
+
llmErr = retryLlm;
|
|
5557
|
+
}
|
|
5558
|
+
}
|
|
5559
|
+
if (llmErr.code === "MODEL_NOT_FOUND") {
|
|
5560
|
+
const healed = await healModelNotFound({
|
|
5561
|
+
provider,
|
|
5562
|
+
tier: cfg.tier,
|
|
5563
|
+
deadModel: model,
|
|
5564
|
+
apiKey: key.apiKey,
|
|
5565
|
+
ctx: opts.ctx
|
|
5566
|
+
}).catch(() => null);
|
|
5567
|
+
if (healed) {
|
|
5568
|
+
notices.push(healed.notice);
|
|
5569
|
+
model = healed.model;
|
|
5570
|
+
let retryReq = req;
|
|
5571
|
+
if (req.tools?.length && modelHasNoToolsQuirk(provider, model)) {
|
|
5572
|
+
retryReq = stripTools(req);
|
|
5573
|
+
notices.push(`${model} doesn't support tool calling \u2014 answering without live data tools`);
|
|
5574
|
+
}
|
|
5575
|
+
try {
|
|
5576
|
+
const response = await completeOnProvider(provider, model, key.apiKey, retryReq);
|
|
5577
|
+
const meta = buildMeta(provider, model, response);
|
|
5578
|
+
recordLlmUsage(response.token_usage);
|
|
5579
|
+
return { response, meta };
|
|
5580
|
+
} catch (retryErr) {
|
|
5581
|
+
const retryLlm = retryErr;
|
|
5582
|
+
if (retryLlm.name !== "LlmError") throw retryErr;
|
|
5583
|
+
lastError = retryLlm;
|
|
5584
|
+
llmErr = retryLlm;
|
|
5585
|
+
}
|
|
4880
5586
|
}
|
|
4881
5587
|
}
|
|
4882
5588
|
if (!isFailoverEligible(llmErr.code)) throw llmErr;
|
|
4883
5589
|
const next = providers[i + 1];
|
|
4884
5590
|
if (next) {
|
|
4885
5591
|
failoverFrom = failoverFrom ?? provider;
|
|
5592
|
+
notices.push(`${provider} unavailable (${llmErr.code.toLowerCase()}) \u2014 trying ${next}`);
|
|
4886
5593
|
opts.onFailover?.(provider, next, llmErr.code);
|
|
4887
5594
|
continue;
|
|
4888
5595
|
}
|
|
4889
5596
|
throw llmErr;
|
|
4890
5597
|
}
|
|
4891
5598
|
}
|
|
4892
|
-
throw lastError ?? new Error(
|
|
5599
|
+
throw lastError ?? new Error(NO_PROVIDER_MESSAGE);
|
|
4893
5600
|
}
|
|
4894
5601
|
async function* streamWithFailover(req, opts = {}) {
|
|
4895
5602
|
const cfg = resolveCompletionContext(req.surface, {
|
|
@@ -4898,23 +5605,47 @@ async function* streamWithFailover(req, opts = {}) {
|
|
|
4898
5605
|
modelOverride: opts.modelOverride,
|
|
4899
5606
|
ctx: opts.ctx
|
|
4900
5607
|
});
|
|
4901
|
-
const providers =
|
|
5608
|
+
const providers = cfg.providerOrder;
|
|
4902
5609
|
if (providers.length === 0) {
|
|
4903
|
-
throw new Error(
|
|
5610
|
+
throw new Error(NO_PROVIDER_MESSAGE);
|
|
4904
5611
|
}
|
|
5612
|
+
const notices = [];
|
|
4905
5613
|
let lastError;
|
|
4906
5614
|
let failoverFrom;
|
|
5615
|
+
async function* streamOnProvider(provider, model, apiKey) {
|
|
5616
|
+
const spec = getProviderSpec(provider);
|
|
5617
|
+
if (!spec) {
|
|
5618
|
+
throw new LlmError("UNKNOWN", `Unknown provider "${provider}" \u2014 run /connect to register it.`, provider);
|
|
5619
|
+
}
|
|
5620
|
+
if (spec.api === "anthropic") {
|
|
5621
|
+
yield* anthropicStream(apiKey ?? "", model, req);
|
|
5622
|
+
return;
|
|
5623
|
+
}
|
|
5624
|
+
yield* openaiCompatStream(provider, spec.base_url, apiKey, model, req);
|
|
5625
|
+
}
|
|
4907
5626
|
for (let i = 0; i < providers.length; i++) {
|
|
4908
5627
|
const provider = providers[i];
|
|
4909
|
-
const
|
|
4910
|
-
if (!
|
|
4911
|
-
|
|
4912
|
-
|
|
5628
|
+
const key = usableKey(provider, opts.ctx);
|
|
5629
|
+
if (!key.ok) continue;
|
|
5630
|
+
let model = await resolveModelWithDiscovery(provider, cfg, {
|
|
5631
|
+
modelOverride: opts.modelOverride,
|
|
5632
|
+
apiKey: key.apiKey
|
|
5633
|
+
});
|
|
5634
|
+
if (!model) {
|
|
5635
|
+
lastError = new LlmError(
|
|
5636
|
+
"MODEL_NOT_FOUND",
|
|
5637
|
+
`No models known for provider "${provider}". Run /connect or /model refresh.`,
|
|
5638
|
+
provider
|
|
5639
|
+
);
|
|
5640
|
+
continue;
|
|
5641
|
+
}
|
|
5642
|
+
let yieldedAny = false;
|
|
5643
|
+
const attempt = async function* (attemptModel) {
|
|
4913
5644
|
let fullText = "";
|
|
4914
|
-
const
|
|
4915
|
-
for await (const event of streamFn(apiKey, model, req)) {
|
|
5645
|
+
for await (const event of streamOnProvider(provider, attemptModel, key.apiKey)) {
|
|
4916
5646
|
if (event.type === "text_delta") {
|
|
4917
5647
|
fullText += event.text;
|
|
5648
|
+
yieldedAny = true;
|
|
4918
5649
|
yield event;
|
|
4919
5650
|
}
|
|
4920
5651
|
}
|
|
@@ -4922,10 +5653,11 @@ async function* streamWithFailover(req, opts = {}) {
|
|
|
4922
5653
|
recordLlmUsage({ input_tokens: 0, output_tokens: estimatedOut });
|
|
4923
5654
|
const meta = {
|
|
4924
5655
|
provider_used: provider,
|
|
4925
|
-
model_used:
|
|
5656
|
+
model_used: attemptModel,
|
|
4926
5657
|
input_tokens: 0,
|
|
4927
5658
|
output_tokens: estimatedOut,
|
|
4928
|
-
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
|
|
5659
|
+
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {},
|
|
5660
|
+
...notices.length > 0 ? { notices: [...notices] } : {}
|
|
4929
5661
|
};
|
|
4930
5662
|
yield {
|
|
4931
5663
|
type: "done",
|
|
@@ -4937,32 +5669,66 @@ async function* streamWithFailover(req, opts = {}) {
|
|
|
4937
5669
|
},
|
|
4938
5670
|
meta
|
|
4939
5671
|
};
|
|
5672
|
+
};
|
|
5673
|
+
try {
|
|
5674
|
+
yield* attempt(model);
|
|
4940
5675
|
return;
|
|
4941
5676
|
} catch (err) {
|
|
4942
5677
|
const llmErr = err;
|
|
4943
5678
|
if (llmErr.name !== "LlmError") throw err;
|
|
4944
5679
|
lastError = llmErr;
|
|
4945
|
-
if (
|
|
5680
|
+
if (yieldedAny) throw llmErr;
|
|
5681
|
+
if (llmErr.code === "MODEL_NOT_FOUND") {
|
|
5682
|
+
const healed = await healModelNotFound({
|
|
5683
|
+
provider,
|
|
5684
|
+
tier: cfg.tier,
|
|
5685
|
+
deadModel: model,
|
|
5686
|
+
apiKey: key.apiKey,
|
|
5687
|
+
ctx: opts.ctx
|
|
5688
|
+
}).catch(() => null);
|
|
5689
|
+
if (healed) {
|
|
5690
|
+
notices.push(healed.notice);
|
|
5691
|
+
model = healed.model;
|
|
5692
|
+
try {
|
|
5693
|
+
yield* attempt(model);
|
|
5694
|
+
return;
|
|
5695
|
+
} catch (retryErr) {
|
|
5696
|
+
const retryLlm = retryErr;
|
|
5697
|
+
if (retryLlm.name !== "LlmError") throw retryErr;
|
|
5698
|
+
lastError = retryLlm;
|
|
5699
|
+
if (yieldedAny) throw retryLlm;
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5702
|
+
}
|
|
5703
|
+
if (!isFailoverEligible(lastError.code)) throw lastError;
|
|
4946
5704
|
const next = providers[i + 1];
|
|
4947
5705
|
if (next) {
|
|
4948
5706
|
failoverFrom = failoverFrom ?? provider;
|
|
4949
|
-
|
|
5707
|
+
notices.push(`${provider} unavailable (${lastError.code.toLowerCase()}) \u2014 trying ${next}`);
|
|
5708
|
+
opts.onFailover?.(provider, next, lastError.code);
|
|
4950
5709
|
continue;
|
|
4951
5710
|
}
|
|
4952
|
-
throw
|
|
5711
|
+
throw lastError;
|
|
4953
5712
|
}
|
|
4954
5713
|
}
|
|
4955
|
-
throw lastError ?? new Error(
|
|
5714
|
+
throw lastError ?? new Error(NO_PROVIDER_MESSAGE);
|
|
4956
5715
|
}
|
|
5716
|
+
var NO_PROVIDER_MESSAGE;
|
|
4957
5717
|
var init_failover = __esm({
|
|
4958
5718
|
"src/ai/llm/failover.ts"() {
|
|
4959
5719
|
"use strict";
|
|
4960
5720
|
init_usage_stats();
|
|
4961
5721
|
init_anthropic();
|
|
4962
|
-
|
|
5722
|
+
init_openai_compat();
|
|
4963
5723
|
init_catalog();
|
|
5724
|
+
init_discovery();
|
|
4964
5725
|
init_errors();
|
|
5726
|
+
init_heal();
|
|
5727
|
+
init_models_cache();
|
|
5728
|
+
init_providers();
|
|
5729
|
+
init_types();
|
|
4965
5730
|
init_resolver();
|
|
5731
|
+
NO_PROVIDER_MESSAGE = "No LLM provider configured. Run /connect and paste any API key (Anthropic, OpenAI, Groq, Gemini, ...).";
|
|
4966
5732
|
}
|
|
4967
5733
|
});
|
|
4968
5734
|
|
|
@@ -5623,6 +6389,235 @@ var init_profile2 = __esm({
|
|
|
5623
6389
|
}
|
|
5624
6390
|
});
|
|
5625
6391
|
|
|
6392
|
+
// src/ai/llm/detect.ts
|
|
6393
|
+
function detectProviderByKey(key) {
|
|
6394
|
+
const k = key.trim();
|
|
6395
|
+
const specs = listProviderSpecs();
|
|
6396
|
+
let best;
|
|
6397
|
+
for (const spec of specs) {
|
|
6398
|
+
for (const prefix of spec.key_prefixes) {
|
|
6399
|
+
if (k.startsWith(prefix) && (!best || prefix.length > best.length)) {
|
|
6400
|
+
best = { id: spec.id, length: prefix.length };
|
|
6401
|
+
}
|
|
6402
|
+
}
|
|
6403
|
+
}
|
|
6404
|
+
if (best) return { certain: best.id, candidates: [best.id] };
|
|
6405
|
+
const shared = specs.filter((s) => s.shared_prefixes.some((p) => k.startsWith(p)));
|
|
6406
|
+
if (shared.length > 0) return { candidates: shared.map((s) => s.id) };
|
|
6407
|
+
const noPrefix = specs.filter(
|
|
6408
|
+
(s) => s.requires_key && !s.custom && s.key_prefixes.length === 0 && s.shared_prefixes.length === 0
|
|
6409
|
+
);
|
|
6410
|
+
return { candidates: noPrefix.map((s) => s.id) };
|
|
6411
|
+
}
|
|
6412
|
+
async function probeProviders(key, candidateIds, timeoutMs = 5e3) {
|
|
6413
|
+
const results = await Promise.all(
|
|
6414
|
+
candidateIds.map(async (id) => {
|
|
6415
|
+
const spec = getProviderSpec(id);
|
|
6416
|
+
if (!spec) return { id, ok: false, status: 0 };
|
|
6417
|
+
const result = await fetchProviderModels(spec, key, timeoutMs);
|
|
6418
|
+
if (result.ok) return { id, ok: true, models: result.models };
|
|
6419
|
+
return { id, ok: false, status: result.status };
|
|
6420
|
+
})
|
|
6421
|
+
);
|
|
6422
|
+
const accepted = [];
|
|
6423
|
+
let sawNetworkFailure = false;
|
|
6424
|
+
for (const r of results) {
|
|
6425
|
+
if (r.ok && r.models.length > 0) accepted.push({ provider: r.id, models: r.models });
|
|
6426
|
+
else if (!r.ok && r.status === 0) sawNetworkFailure = true;
|
|
6427
|
+
}
|
|
6428
|
+
return { accepted, sawNetworkFailure };
|
|
6429
|
+
}
|
|
6430
|
+
var init_detect = __esm({
|
|
6431
|
+
"src/ai/llm/detect.ts"() {
|
|
6432
|
+
"use strict";
|
|
6433
|
+
init_discovery();
|
|
6434
|
+
init_providers();
|
|
6435
|
+
}
|
|
6436
|
+
});
|
|
6437
|
+
|
|
6438
|
+
// src/services/connect.ts
|
|
6439
|
+
var connect_exports = {};
|
|
6440
|
+
__export(connect_exports, {
|
|
6441
|
+
ConnectCancelled: () => ConnectCancelled,
|
|
6442
|
+
ConnectError: () => ConnectError,
|
|
6443
|
+
connectCustomEndpoint: () => connectCustomEndpoint,
|
|
6444
|
+
connectKeyless: () => connectKeyless,
|
|
6445
|
+
connectWithKey: () => connectWithKey,
|
|
6446
|
+
describeConnectOutcome: () => describeConnectOutcome
|
|
6447
|
+
});
|
|
6448
|
+
function finishConnect(spec, opts) {
|
|
6449
|
+
const before = getAvailableProviders();
|
|
6450
|
+
if (opts.key) {
|
|
6451
|
+
setConfigValue(spec.key_config_name, opts.key);
|
|
6452
|
+
}
|
|
6453
|
+
const entry = opts.models && opts.models.length > 0 ? storeDiscoveredModels(spec.id, opts.models) : null;
|
|
6454
|
+
const cfg = loadLlmConfig();
|
|
6455
|
+
let becamePrimary = false;
|
|
6456
|
+
if (cfg.primary !== spec.id && (before.length === 0 || !hasProviderKey(cfg.primary))) {
|
|
6457
|
+
setConfigValue("llm-primary", spec.id);
|
|
6458
|
+
becamePrimary = true;
|
|
6459
|
+
}
|
|
6460
|
+
return {
|
|
6461
|
+
provider: spec.id,
|
|
6462
|
+
label: spec.label,
|
|
6463
|
+
modelCount: entry?.models.length ?? 0,
|
|
6464
|
+
...entry ? { stack: entry.tier_stack } : {},
|
|
6465
|
+
becamePrimary,
|
|
6466
|
+
offline: !!opts.offline
|
|
6467
|
+
};
|
|
6468
|
+
}
|
|
6469
|
+
async function connectWithKey(rawKey, opts = {}) {
|
|
6470
|
+
const key = rawKey.trim();
|
|
6471
|
+
if (!key) throw new ConnectError("Empty key.");
|
|
6472
|
+
if (opts.providerId) {
|
|
6473
|
+
const spec = getProviderSpec(opts.providerId);
|
|
6474
|
+
if (!spec) {
|
|
6475
|
+
throw new ConnectError(
|
|
6476
|
+
`Unknown provider "${opts.providerId}". Use a built-in id or /connect --base-url <url> --id ${opts.providerId} for a custom endpoint.`
|
|
6477
|
+
);
|
|
6478
|
+
}
|
|
6479
|
+
const result = await fetchProviderModels(spec, key);
|
|
6480
|
+
if (result.ok) return finishConnect(spec, { key, models: result.models });
|
|
6481
|
+
if (result.status === 0) {
|
|
6482
|
+
return finishConnect(spec, { key, offline: true });
|
|
6483
|
+
}
|
|
6484
|
+
throw new ConnectError(`${spec.label} rejected this key (HTTP ${result.status}) \u2014 double-check it and try again.`);
|
|
6485
|
+
}
|
|
6486
|
+
const detection = detectProviderByKey(key);
|
|
6487
|
+
if (detection.certain) {
|
|
6488
|
+
const spec = getProviderSpec(detection.certain);
|
|
6489
|
+
const result = await fetchProviderModels(spec, key);
|
|
6490
|
+
if (result.ok) return finishConnect(spec, { key, models: result.models });
|
|
6491
|
+
if (result.status === 0) return finishConnect(spec, { key, offline: true });
|
|
6492
|
+
throw new ConnectError(`${spec.label} rejected this key (HTTP ${result.status}) \u2014 double-check it and try again.`);
|
|
6493
|
+
}
|
|
6494
|
+
const report = await probeProviders(key, detection.candidates);
|
|
6495
|
+
if (report.accepted.length === 1) {
|
|
6496
|
+
const match = report.accepted[0];
|
|
6497
|
+
const spec = getProviderSpec(match.provider);
|
|
6498
|
+
if (opts.callbacks?.confirmDetection) {
|
|
6499
|
+
const ok = await opts.callbacks.confirmDetection(match.provider);
|
|
6500
|
+
if (!ok) throw new ConnectCancelled();
|
|
6501
|
+
}
|
|
6502
|
+
return finishConnect(spec, { key, models: match.models });
|
|
6503
|
+
}
|
|
6504
|
+
if (report.accepted.length > 1) {
|
|
6505
|
+
if (opts.callbacks?.chooseProvider) {
|
|
6506
|
+
const chosen = await opts.callbacks.chooseProvider(report.accepted);
|
|
6507
|
+
if (!chosen) throw new ConnectCancelled();
|
|
6508
|
+
const match = report.accepted.find((a) => a.provider === chosen);
|
|
6509
|
+
return finishConnect(getProviderSpec(chosen), { key, models: match.models });
|
|
6510
|
+
}
|
|
6511
|
+
throw new ConnectError(
|
|
6512
|
+
`Multiple providers accepted this key (${report.accepted.map((a) => a.provider).join(", ")}). Re-run with --provider <id>.`
|
|
6513
|
+
);
|
|
6514
|
+
}
|
|
6515
|
+
if (report.sawNetworkFailure) {
|
|
6516
|
+
throw new ConnectError(
|
|
6517
|
+
`Couldn't reach ${detection.candidates.map(providerLabel).join(" / ")} to identify this key. Check your connection, or force a provider with --provider <id>.`
|
|
6518
|
+
);
|
|
6519
|
+
}
|
|
6520
|
+
throw new ConnectError(
|
|
6521
|
+
`No provider accepted this key (tried ${detection.candidates.map(providerLabel).join(", ")}). If it belongs to an OpenAI-compatible endpoint, run /connect --base-url <url>.`
|
|
6522
|
+
);
|
|
6523
|
+
}
|
|
6524
|
+
async function connectCustomEndpoint(opts) {
|
|
6525
|
+
const id = opts.id.trim().toLowerCase();
|
|
6526
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(id)) {
|
|
6527
|
+
throw new ConnectError(`Invalid provider id "${opts.id}" \u2014 use letters, digits, dashes.`);
|
|
6528
|
+
}
|
|
6529
|
+
const baseUrl = opts.baseUrl.trim().replace(/\/+$/, "");
|
|
6530
|
+
if (!/^https?:\/\//.test(baseUrl)) {
|
|
6531
|
+
throw new ConnectError(`Base URL must start with http:// or https:// (got "${opts.baseUrl}").`);
|
|
6532
|
+
}
|
|
6533
|
+
const builtin = getProviderSpec(id);
|
|
6534
|
+
const spec = builtin ? { ...builtin, base_url: baseUrl } : {
|
|
6535
|
+
id,
|
|
6536
|
+
label: opts.label ?? id,
|
|
6537
|
+
api: "openai-compat",
|
|
6538
|
+
base_url: baseUrl,
|
|
6539
|
+
key_prefixes: [],
|
|
6540
|
+
shared_prefixes: [],
|
|
6541
|
+
key_config_name: `${id}-api-key`,
|
|
6542
|
+
requires_key: !!opts.key,
|
|
6543
|
+
custom: true
|
|
6544
|
+
};
|
|
6545
|
+
const result = await fetchProviderModels(spec, opts.key);
|
|
6546
|
+
if (!result.ok) {
|
|
6547
|
+
if (result.status === 0) {
|
|
6548
|
+
throw new ConnectError(`Couldn't reach ${baseUrl} \u2014 check the URL (expects an OpenAI-compatible /models endpoint).`);
|
|
6549
|
+
}
|
|
6550
|
+
if (result.status === 401 || result.status === 403) {
|
|
6551
|
+
throw new ConnectError(
|
|
6552
|
+
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.`
|
|
6553
|
+
);
|
|
6554
|
+
}
|
|
6555
|
+
throw new ConnectError(`${baseUrl} answered HTTP ${result.status} \u2014 is this an OpenAI-compatible endpoint?`);
|
|
6556
|
+
}
|
|
6557
|
+
if (result.models.length === 0) {
|
|
6558
|
+
throw new ConnectError(`${baseUrl} lists no models \u2014 nothing to connect.`);
|
|
6559
|
+
}
|
|
6560
|
+
saveCustomProvider({
|
|
6561
|
+
id,
|
|
6562
|
+
...opts.label ? { label: opts.label } : {},
|
|
6563
|
+
base_url: baseUrl,
|
|
6564
|
+
requires_key: !!opts.key,
|
|
6565
|
+
enabled: true
|
|
6566
|
+
});
|
|
6567
|
+
return finishConnect(getProviderSpec(id), { key: opts.key, models: result.models });
|
|
6568
|
+
}
|
|
6569
|
+
async function connectKeyless(providerId, baseUrl) {
|
|
6570
|
+
const builtin = getProviderSpec(providerId);
|
|
6571
|
+
if (!builtin) throw new ConnectError(`Unknown provider "${providerId}".`);
|
|
6572
|
+
const spec = baseUrl ? { ...builtin, base_url: baseUrl.replace(/\/+$/, "") } : builtin;
|
|
6573
|
+
const result = await fetchProviderModels(spec, void 0);
|
|
6574
|
+
if (!result.ok) {
|
|
6575
|
+
throw new ConnectError(
|
|
6576
|
+
`${spec.label} not reachable at ${spec.base_url}. Is it running? (ollama serve, then retry)`
|
|
6577
|
+
);
|
|
6578
|
+
}
|
|
6579
|
+
if (result.models.length === 0) {
|
|
6580
|
+
throw new ConnectError(`${spec.label} is running but has no models \u2014 pull one first (e.g. \`ollama pull llama3.2\`).`);
|
|
6581
|
+
}
|
|
6582
|
+
saveCustomProvider({ id: spec.id, base_url: spec.base_url, enabled: true });
|
|
6583
|
+
return finishConnect(getProviderSpec(spec.id), { models: result.models });
|
|
6584
|
+
}
|
|
6585
|
+
function describeConnectOutcome(outcome) {
|
|
6586
|
+
const lines = [];
|
|
6587
|
+
if (outcome.offline) {
|
|
6588
|
+
lines.push(`${outcome.label} key saved \u2014 provider unreachable right now, models will be discovered on first use.`);
|
|
6589
|
+
} else {
|
|
6590
|
+
lines.push(`Connected ${outcome.label} \u2014 ${outcome.modelCount} chat model${outcome.modelCount === 1 ? "" : "s"} available.`);
|
|
6591
|
+
}
|
|
6592
|
+
if (outcome.stack) {
|
|
6593
|
+
lines.push(`high ${outcome.stack.high}`);
|
|
6594
|
+
lines.push(`medium ${outcome.stack.medium}`);
|
|
6595
|
+
lines.push(`low ${outcome.stack.low}`);
|
|
6596
|
+
}
|
|
6597
|
+
if (outcome.becamePrimary) {
|
|
6598
|
+
lines.push(`Primary engine: ${outcome.provider}`);
|
|
6599
|
+
}
|
|
6600
|
+
return lines;
|
|
6601
|
+
}
|
|
6602
|
+
var ConnectError, ConnectCancelled;
|
|
6603
|
+
var init_connect = __esm({
|
|
6604
|
+
"src/services/connect.ts"() {
|
|
6605
|
+
"use strict";
|
|
6606
|
+
init_detect();
|
|
6607
|
+
init_discovery();
|
|
6608
|
+
init_providers();
|
|
6609
|
+
init_llm_config();
|
|
6610
|
+
init_store();
|
|
6611
|
+
ConnectError = class extends Error {
|
|
6612
|
+
};
|
|
6613
|
+
ConnectCancelled = class extends ConnectError {
|
|
6614
|
+
constructor() {
|
|
6615
|
+
super("Connect cancelled.");
|
|
6616
|
+
}
|
|
6617
|
+
};
|
|
6618
|
+
}
|
|
6619
|
+
});
|
|
6620
|
+
|
|
5626
6621
|
// src/commands/onboard.ts
|
|
5627
6622
|
var onboard_exports = {};
|
|
5628
6623
|
__export(onboard_exports, {
|
|
@@ -5950,42 +6945,58 @@ function printIntro() {
|
|
|
5950
6945
|
}
|
|
5951
6946
|
async function ensureLlmKeys(session) {
|
|
5952
6947
|
if (hasAnyLlmProvider()) return;
|
|
6948
|
+
const { connectWithKey: connectWithKey2, describeConnectOutcome: describeConnectOutcome2, ConnectCancelled: ConnectCancelled2 } = await Promise.resolve().then(() => (init_connect(), connect_exports));
|
|
6949
|
+
const { providerLabel: providerLabel2 } = await Promise.resolve().then(() => (init_providers(), providers_exports));
|
|
6950
|
+
const { countAvailableEngines: countAvailableEngines2 } = await Promise.resolve().then(() => (init_session_state(), session_state_exports));
|
|
5953
6951
|
console.log();
|
|
5954
6952
|
console.log(" " + chalk8.dim("Onboarding uses AI to draft your profile."));
|
|
5955
6953
|
console.log(
|
|
5956
|
-
" " + chalk8.dim("
|
|
6954
|
+
" " + chalk8.dim("Paste any provider's API key \u2014 Anthropic, OpenAI, Groq, Gemini, Mistral, ...")
|
|
5957
6955
|
);
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
{ value: "openai", label: "OpenAI (GPT)", description: "Full parity on all surfaces" }
|
|
5961
|
-
]);
|
|
5962
|
-
const firstLabel = first === "anthropic" ? "Anthropic API key" : "OpenAI API key";
|
|
5963
|
-
const firstKey = await session.askSecret(firstLabel, { confirm: true });
|
|
5964
|
-
if (first === "anthropic") setConfigValue("api-key", firstKey);
|
|
5965
|
-
else setConfigValue("openai-api-key", firstKey);
|
|
5966
|
-
setConfigValue("llm-primary", first);
|
|
5967
|
-
setConfigValue("llm-tier", "high");
|
|
5968
|
-
const second = first === "anthropic" ? "openai" : "anthropic";
|
|
5969
|
-
setConfigValue("llm-failover-order", second);
|
|
5970
|
-
setConfigValue("llm-auto-failover", "off");
|
|
5971
|
-
const addSecond = await session.confirm(
|
|
5972
|
-
`Add a second engine (${second}) for switching in the REPL?`,
|
|
5973
|
-
false
|
|
6956
|
+
console.log(
|
|
6957
|
+
" " + chalk8.dim("NTRP detects the provider and discovers its models. Or run ") + paint("accent", "/connect") + chalk8.dim(" anytime.")
|
|
5974
6958
|
);
|
|
5975
|
-
|
|
5976
|
-
const
|
|
5977
|
-
const
|
|
5978
|
-
|
|
5979
|
-
|
|
6959
|
+
for (; ; ) {
|
|
6960
|
+
const key = await session.askSecret("LLM API key (any provider)", { confirm: false });
|
|
6961
|
+
const spinner = ora({ text: "Identifying provider\u2026", discardStdin: false }).start();
|
|
6962
|
+
try {
|
|
6963
|
+
const outcome = await connectWithKey2(key, {
|
|
6964
|
+
callbacks: {
|
|
6965
|
+
confirmDetection: async (providerId) => {
|
|
6966
|
+
spinner.stop();
|
|
6967
|
+
return session.confirm(`Detected ${providerLabel2(providerId)} \u2014 connect it?`, true);
|
|
6968
|
+
},
|
|
6969
|
+
chooseProvider: async (accepted) => {
|
|
6970
|
+
spinner.stop();
|
|
6971
|
+
return session.choose(
|
|
6972
|
+
"Multiple providers accepted this key \u2014 which is it?",
|
|
6973
|
+
accepted.map((a) => ({ value: a.provider, label: providerLabel2(a.provider) }))
|
|
6974
|
+
);
|
|
6975
|
+
}
|
|
6976
|
+
}
|
|
6977
|
+
});
|
|
6978
|
+
spinner.stop();
|
|
6979
|
+
const [headline, ...rest] = describeConnectOutcome2(outcome);
|
|
6980
|
+
console.log(" " + paint("success", "\u2713") + " " + (headline ?? ""));
|
|
6981
|
+
for (const line of rest) console.log(" " + chalk8.dim(line));
|
|
6982
|
+
} catch (err) {
|
|
6983
|
+
spinner.stop();
|
|
6984
|
+
if (!(err instanceof ConnectCancelled2)) {
|
|
6985
|
+
console.log(" " + chalk8.red(String(err.message ?? err)));
|
|
6986
|
+
}
|
|
6987
|
+
const retry = await session.confirm("Try another key?", true);
|
|
6988
|
+
if (retry) continue;
|
|
6989
|
+
if (!hasAnyLlmProvider()) return;
|
|
6990
|
+
}
|
|
6991
|
+
const addAnother = await session.confirm("Add another engine? (switch anytime with /provider)", false);
|
|
6992
|
+
if (!addAnother) break;
|
|
6993
|
+
}
|
|
6994
|
+
if (countAvailableEngines2() >= 2) {
|
|
5980
6995
|
const enableFailover = await session.confirm(
|
|
5981
6996
|
"Enable auto-failover on rate limits? (off = you choose engine with /provider)",
|
|
5982
6997
|
false
|
|
5983
6998
|
);
|
|
5984
|
-
|
|
5985
|
-
} else {
|
|
5986
|
-
console.log(
|
|
5987
|
-
" " + chalk8.dim(`Single engine \u2014 add ${second} later via /config set ${second === "anthropic" ? "api-key" : "openai-api-key"}.`)
|
|
5988
|
-
);
|
|
6999
|
+
setConfigValue("llm-auto-failover", enableFailover ? "on" : "off");
|
|
5989
7000
|
}
|
|
5990
7001
|
console.log(" " + paint("success", "\u2713") + " " + chalk8.dim("LLM engines configured. Use /provider to switch."));
|
|
5991
7002
|
}
|
|
@@ -6055,15 +7066,22 @@ __export(scratch_exports, {
|
|
|
6055
7066
|
handler: () => handler3
|
|
6056
7067
|
});
|
|
6057
7068
|
import chalk9 from "chalk";
|
|
6058
|
-
function printScratchPreamble() {
|
|
7069
|
+
function printScratchPreamble(includeProgress) {
|
|
6059
7070
|
console.log();
|
|
6060
7071
|
console.log(" " + chalk9.yellow.bold("This will permanently remove:"));
|
|
6061
7072
|
console.log(" " + chalk9.dim(" \u2022 API key and all config.json settings"));
|
|
6062
7073
|
console.log(" " + chalk9.dim(" \u2022 Company profile (you'll re-onboard on next use)"));
|
|
6063
7074
|
console.log(" " + chalk9.dim(" \u2022 All sessions and datasets"));
|
|
6064
7075
|
console.log(" " + chalk9.dim(" \u2022 Demo taxonomy cache"));
|
|
7076
|
+
if (includeProgress) {
|
|
7077
|
+
console.log(" " + chalk9.dim(" \u2022 Progress (hours saved) and install identity"));
|
|
7078
|
+
}
|
|
6065
7079
|
console.log();
|
|
6066
|
-
|
|
7080
|
+
if (includeProgress) {
|
|
7081
|
+
console.log(" " + chalk9.dim("Preserved: memory, strategies, wins, knowledge, exports, audit"));
|
|
7082
|
+
} else {
|
|
7083
|
+
console.log(" " + chalk9.dim("Preserved: progress (hours saved), memory, strategies, wins, knowledge, exports, audit"));
|
|
7084
|
+
}
|
|
6067
7085
|
console.log();
|
|
6068
7086
|
}
|
|
6069
7087
|
function resetContextAfterScratch(ctx) {
|
|
@@ -6085,24 +7103,26 @@ function resetContextAfterScratch(ctx) {
|
|
|
6085
7103
|
ctx.lastExchange = void 0;
|
|
6086
7104
|
}
|
|
6087
7105
|
async function handler3(args, ctx) {
|
|
6088
|
-
const { flags } = parseArgs2(args, ["confirm"]);
|
|
7106
|
+
const { flags } = parseArgs2(args, ["confirm", "include-progress"]);
|
|
6089
7107
|
const confirmedFlag = getBool(flags, "confirm");
|
|
7108
|
+
const includeProgress = getBool(flags, "include-progress");
|
|
6090
7109
|
const ok = await requireTypedWord(ctx, {
|
|
6091
|
-
title: "Factory reset",
|
|
7110
|
+
title: includeProgress ? "Factory reset (including progress)" : "Factory reset",
|
|
6092
7111
|
word: "scratch",
|
|
6093
7112
|
confirmedFlag,
|
|
6094
|
-
preamble: printScratchPreamble
|
|
7113
|
+
preamble: () => printScratchPreamble(includeProgress)
|
|
6095
7114
|
});
|
|
6096
7115
|
if (!ok) {
|
|
6097
7116
|
printAdminCancelled("Scratch", 'Type "scratch" exactly to confirm.');
|
|
6098
7117
|
return "Scratch cancelled";
|
|
6099
7118
|
}
|
|
6100
|
-
await performScratchWipe();
|
|
7119
|
+
await performScratchWipe({ includeProgress });
|
|
6101
7120
|
resetContextAfterScratch(ctx);
|
|
6102
7121
|
await rotateToFreshSession(ctx);
|
|
6103
7122
|
await initSchema();
|
|
6104
7123
|
console.log();
|
|
6105
|
-
|
|
7124
|
+
const detail = includeProgress ? " \u2014 local config, data, and progress wiped." : " \u2014 local config and data wiped.";
|
|
7125
|
+
console.log(" " + paint("accent", "\u2713 Scratch complete") + chalk9.dim(detail));
|
|
6106
7126
|
console.log();
|
|
6107
7127
|
if (ctx.oneShot) {
|
|
6108
7128
|
console.log(
|
|
@@ -6275,16 +7295,16 @@ async function runGlobalAdminCommand(command, line, ctx) {
|
|
|
6275
7295
|
const args = tokens.slice(1);
|
|
6276
7296
|
switch (command) {
|
|
6277
7297
|
case "scratch": {
|
|
6278
|
-
const { handler:
|
|
6279
|
-
return
|
|
7298
|
+
const { handler: handler46 } = await Promise.resolve().then(() => (init_scratch(), scratch_exports));
|
|
7299
|
+
return handler46(args, ctx);
|
|
6280
7300
|
}
|
|
6281
7301
|
case "cleanup": {
|
|
6282
|
-
const { handler:
|
|
6283
|
-
return
|
|
7302
|
+
const { handler: handler46 } = await Promise.resolve().then(() => (init_cleanup(), cleanup_exports));
|
|
7303
|
+
return handler46(args, ctx);
|
|
6284
7304
|
}
|
|
6285
7305
|
case "deactivate-demo": {
|
|
6286
|
-
const { handler:
|
|
6287
|
-
return
|
|
7306
|
+
const { handler: handler46 } = await Promise.resolve().then(() => (init_deactivate_demo(), deactivate_demo_exports));
|
|
7307
|
+
return handler46(args, ctx);
|
|
6288
7308
|
}
|
|
6289
7309
|
default:
|
|
6290
7310
|
return void 0;
|
|
@@ -9175,6 +10195,9 @@ function formatLlmAttribution(meta) {
|
|
|
9175
10195
|
return line;
|
|
9176
10196
|
}
|
|
9177
10197
|
function printLlmAttribution(meta) {
|
|
10198
|
+
for (const notice of meta.notices ?? []) {
|
|
10199
|
+
console.log(chalk13.dim(` ${notice}`));
|
|
10200
|
+
}
|
|
9178
10201
|
const line = formatLlmAttribution(meta);
|
|
9179
10202
|
if (line) console.log(chalk13.dim(` ${line}`));
|
|
9180
10203
|
}
|
|
@@ -9492,6 +10515,7 @@ async function renderDiagnoseStream(options) {
|
|
|
9492
10515
|
let modelUsed = "";
|
|
9493
10516
|
let providerUsed;
|
|
9494
10517
|
let failover;
|
|
10518
|
+
let notices;
|
|
9495
10519
|
let rawPrompt = "";
|
|
9496
10520
|
try {
|
|
9497
10521
|
for await (const event of runFindings(fullResult)) {
|
|
@@ -9507,6 +10531,7 @@ async function renderDiagnoseStream(options) {
|
|
|
9507
10531
|
modelUsed = event.model_used;
|
|
9508
10532
|
providerUsed = event.provider_used;
|
|
9509
10533
|
failover = event.failover;
|
|
10534
|
+
notices = event.usage?.notices;
|
|
9510
10535
|
rawPrompt = event.raw_prompt;
|
|
9511
10536
|
}
|
|
9512
10537
|
}
|
|
@@ -9530,7 +10555,8 @@ async function renderDiagnoseStream(options) {
|
|
|
9530
10555
|
printLlmAttribution({
|
|
9531
10556
|
model_used: modelUsed,
|
|
9532
10557
|
provider_used: providerUsed,
|
|
9533
|
-
failover
|
|
10558
|
+
failover,
|
|
10559
|
+
notices
|
|
9534
10560
|
});
|
|
9535
10561
|
} catch (err) {
|
|
9536
10562
|
findingsSpinner.fail(deep ? "Agentic investigation failed" : "AI findings failed");
|
|
@@ -9910,8 +10936,8 @@ function markFailure(ctx) {
|
|
|
9910
10936
|
}
|
|
9911
10937
|
async function loadOrBuildTaxonomy(profile, forceRegen, ctx) {
|
|
9912
10938
|
if (!forceRegen) {
|
|
9913
|
-
const
|
|
9914
|
-
if (
|
|
10939
|
+
const cached2 = loadCachedTaxonomy(profile);
|
|
10940
|
+
if (cached2) return cached2;
|
|
9915
10941
|
}
|
|
9916
10942
|
const spinnerText = forceRegen ? "Rebuilding market taxonomy\u2026" : "Researching your market taxonomy\u2026";
|
|
9917
10943
|
const spinner = ora3({ text: spinnerText, discardStdin: false }).start();
|
|
@@ -10069,7 +11095,7 @@ __export(ingest_exports, {
|
|
|
10069
11095
|
});
|
|
10070
11096
|
import chalk16 from "chalk";
|
|
10071
11097
|
import ora4 from "ora";
|
|
10072
|
-
import { readFileSync as
|
|
11098
|
+
import { readFileSync as readFileSync11, existsSync as existsSync12 } from "fs";
|
|
10073
11099
|
import { basename as basename3 } from "path";
|
|
10074
11100
|
async function handler7(args, ctx) {
|
|
10075
11101
|
const { positional, flags } = parseArgs2(args, [
|
|
@@ -10093,7 +11119,7 @@ async function handler7(args, ctx) {
|
|
|
10093
11119
|
console.error(chalk16.dim(" /ingest --demo [--scenario <name>]"));
|
|
10094
11120
|
process.exit(1);
|
|
10095
11121
|
}
|
|
10096
|
-
if (!
|
|
11122
|
+
if (!existsSync12(file)) {
|
|
10097
11123
|
console.error(chalk16.red(` File not found: ${file}`));
|
|
10098
11124
|
process.exit(1);
|
|
10099
11125
|
}
|
|
@@ -10111,7 +11137,7 @@ async function handler7(args, ctx) {
|
|
|
10111
11137
|
try {
|
|
10112
11138
|
await initSchema();
|
|
10113
11139
|
spinner.text = "Parsing CSV...";
|
|
10114
|
-
const content =
|
|
11140
|
+
const content = readFileSync11(file, "utf-8");
|
|
10115
11141
|
const { rows, headers } = parseCSV(content);
|
|
10116
11142
|
if (rows.length === 0) {
|
|
10117
11143
|
spinner.fail("CSV is empty");
|
|
@@ -12274,16 +13300,16 @@ var init_compute = __esm({
|
|
|
12274
13300
|
});
|
|
12275
13301
|
|
|
12276
13302
|
// src/data/playbook.ts
|
|
12277
|
-
import { existsSync as
|
|
12278
|
-
import { join as
|
|
13303
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12, appendFileSync } from "fs";
|
|
13304
|
+
import { join as join12 } from "path";
|
|
12279
13305
|
function playsPath() {
|
|
12280
|
-
return
|
|
13306
|
+
return join12(getMemoryDir(), PLAYS_FILE);
|
|
12281
13307
|
}
|
|
12282
13308
|
function getCustomPlays() {
|
|
12283
13309
|
const path = playsPath();
|
|
12284
|
-
if (!
|
|
13310
|
+
if (!existsSync13(path)) return [];
|
|
12285
13311
|
const out = [];
|
|
12286
|
-
for (const line of
|
|
13312
|
+
for (const line of readFileSync12(path, "utf-8").split("\n")) {
|
|
12287
13313
|
const trimmed = line.trim();
|
|
12288
13314
|
if (!trimmed) continue;
|
|
12289
13315
|
try {
|
|
@@ -12875,7 +13901,7 @@ async function runMetricsAnalysis(options = {}) {
|
|
|
12875
13901
|
if (options.findings) {
|
|
12876
13902
|
if (!canUseReplAi(options.ctx)) {
|
|
12877
13903
|
throw new Error(
|
|
12878
|
-
"AI metrics findings require stored API keys. Run `ntrp`,
|
|
13904
|
+
"AI metrics findings require stored API keys. Run `ntrp`, then /connect (any provider key), then /metrics --findings."
|
|
12879
13905
|
);
|
|
12880
13906
|
}
|
|
12881
13907
|
options.onProgress?.("findings");
|
|
@@ -13284,7 +14310,8 @@ async function* streamFindings(input, ctx) {
|
|
|
13284
14310
|
findings,
|
|
13285
14311
|
model_used: meta.model_used,
|
|
13286
14312
|
provider_used: meta.provider_used,
|
|
13287
|
-
raw_prompt: userMessage
|
|
14313
|
+
raw_prompt: userMessage,
|
|
14314
|
+
usage: meta
|
|
13288
14315
|
};
|
|
13289
14316
|
}
|
|
13290
14317
|
}
|
|
@@ -13585,9 +14612,9 @@ var init_tool_schemas = __esm({
|
|
|
13585
14612
|
});
|
|
13586
14613
|
|
|
13587
14614
|
// src/ai/privacy.ts
|
|
13588
|
-
import { existsSync as
|
|
14615
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2 } from "fs";
|
|
13589
14616
|
import { homedir as homedir4 } from "os";
|
|
13590
|
-
import { join as
|
|
14617
|
+
import { join as join13 } from "path";
|
|
13591
14618
|
function stripPII(obj) {
|
|
13592
14619
|
if (obj === null || obj === void 0) return obj;
|
|
13593
14620
|
if (typeof obj !== "object") return obj;
|
|
@@ -13602,14 +14629,14 @@ function stripPII(obj) {
|
|
|
13602
14629
|
return out;
|
|
13603
14630
|
}
|
|
13604
14631
|
function ensureAuditDir() {
|
|
13605
|
-
if (!
|
|
14632
|
+
if (!existsSync14(AUDIT_DIR)) {
|
|
13606
14633
|
mkdirSync8(AUDIT_DIR, { recursive: true });
|
|
13607
14634
|
}
|
|
13608
14635
|
}
|
|
13609
14636
|
function logToolCall(entry) {
|
|
13610
14637
|
ensureAuditDir();
|
|
13611
14638
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
13612
|
-
const path =
|
|
14639
|
+
const path = join13(AUDIT_DIR, `agentic-${date}.jsonl`);
|
|
13613
14640
|
appendFileSync2(path, JSON.stringify(entry) + "\n");
|
|
13614
14641
|
}
|
|
13615
14642
|
var PII_FIELDS, AUDIT_DIR;
|
|
@@ -13633,7 +14660,7 @@ var init_privacy = __esm({
|
|
|
13633
14660
|
"raw_data",
|
|
13634
14661
|
"metadata"
|
|
13635
14662
|
]);
|
|
13636
|
-
AUDIT_DIR =
|
|
14663
|
+
AUDIT_DIR = join13(homedir4(), ".ntrp", "audit");
|
|
13637
14664
|
}
|
|
13638
14665
|
});
|
|
13639
14666
|
|
|
@@ -14139,7 +15166,7 @@ __export(ingest_chat_exports, {
|
|
|
14139
15166
|
loadDemoFromChat: () => loadDemoFromChat,
|
|
14140
15167
|
looksLikeFilePath: () => looksLikeFilePath
|
|
14141
15168
|
});
|
|
14142
|
-
import { existsSync as
|
|
15169
|
+
import { existsSync as existsSync15 } from "fs";
|
|
14143
15170
|
import { basename as basename4, resolve as resolve4 } from "path";
|
|
14144
15171
|
import { homedir as homedir5 } from "os";
|
|
14145
15172
|
import chalk22 from "chalk";
|
|
@@ -14159,11 +15186,11 @@ function extractFilePath(input) {
|
|
|
14159
15186
|
const m = trimmed.match(re);
|
|
14160
15187
|
if (m?.[1]) {
|
|
14161
15188
|
const p = expandPath(m[1]);
|
|
14162
|
-
if (
|
|
15189
|
+
if (existsSync15(p)) return p;
|
|
14163
15190
|
}
|
|
14164
15191
|
if (!m?.[1] && re.test(trimmed) && trimmed.toLowerCase().endsWith(".csv")) {
|
|
14165
15192
|
const p = expandPath(trimmed.replace(/^["']|["']$/g, ""));
|
|
14166
|
-
if (
|
|
15193
|
+
if (existsSync15(p)) return p;
|
|
14167
15194
|
}
|
|
14168
15195
|
}
|
|
14169
15196
|
return null;
|
|
@@ -14193,12 +15220,12 @@ async function ingestFromChat(ctx, filePath) {
|
|
|
14193
15220
|
}
|
|
14194
15221
|
const { handler: ingest } = await Promise.resolve().then(() => (init_ingest(), ingest_exports));
|
|
14195
15222
|
const { detectEntityType: detectEntityType2 } = await Promise.resolve().then(() => (init_csv_detect(), csv_detect_exports));
|
|
14196
|
-
const { readFileSync:
|
|
15223
|
+
const { readFileSync: readFileSync19 } = await import("fs");
|
|
14197
15224
|
const { parseCSV: parseCSV2 } = await Promise.resolve().then(() => (init_csv_parse(), csv_parse_exports));
|
|
14198
15225
|
const { getStoredApiKey } = await Promise.resolve().then(() => (init_repl_api(), repl_api_exports));
|
|
14199
15226
|
let headerCheckFailed = false;
|
|
14200
15227
|
try {
|
|
14201
|
-
const raw =
|
|
15228
|
+
const raw = readFileSync19(filePath, "utf-8");
|
|
14202
15229
|
const { headers } = parseCSV2(raw);
|
|
14203
15230
|
const detected = detectEntityType2(headers, "unknown");
|
|
14204
15231
|
if (!detected) headerCheckFailed = true;
|
|
@@ -14946,12 +15973,12 @@ async function handleDraftHandoff(input) {
|
|
|
14946
15973
|
};
|
|
14947
15974
|
}
|
|
14948
15975
|
async function executeToolCall(name, input, ctx) {
|
|
14949
|
-
const
|
|
14950
|
-
if (!
|
|
15976
|
+
const handler46 = HANDLERS[name];
|
|
15977
|
+
if (!handler46) {
|
|
14951
15978
|
return JSON.stringify({ error: `Unknown tool '${name}'` });
|
|
14952
15979
|
}
|
|
14953
15980
|
const start = Date.now();
|
|
14954
|
-
const rawResult = await
|
|
15981
|
+
const rawResult = await handler46(input, ctx);
|
|
14955
15982
|
const safeResult = stripPII(rawResult);
|
|
14956
15983
|
const resultJson = JSON.stringify(safeResult);
|
|
14957
15984
|
const duration = Date.now() - start;
|
|
@@ -15344,7 +16371,7 @@ async function runDiagnosis(options = {}) {
|
|
|
15344
16371
|
if (options.findings) {
|
|
15345
16372
|
if (!canUseReplAi(options.ctx)) {
|
|
15346
16373
|
throw new Error(
|
|
15347
|
-
"AI findings require stored API keys. Run `ntrp`, then /
|
|
16374
|
+
"AI findings require stored API keys. Run `ntrp`, then /connect (any provider key), and use /diagnose --findings."
|
|
15348
16375
|
);
|
|
15349
16376
|
}
|
|
15350
16377
|
if (options.deep) {
|
|
@@ -15487,7 +16514,7 @@ async function handler8(args, ctx) {
|
|
|
15487
16514
|
console.log();
|
|
15488
16515
|
console.log(" " + chalk23.red("AI findings run only in the interactive REPL."));
|
|
15489
16516
|
console.log(" " + chalk23.dim("Vital signs compute without a key \u2014 omit --findings for numbers only."));
|
|
15490
|
-
console.log(" " + chalk23.dim("Start with ") + paint("accent", "ntrp") + chalk23.dim(",
|
|
16517
|
+
console.log(" " + chalk23.dim("Start with ") + paint("accent", "ntrp") + chalk23.dim(", run ") + paint("accent", "/connect") + chalk23.dim(" (any provider key), then /diagnose --findings."));
|
|
15491
16518
|
console.log();
|
|
15492
16519
|
return;
|
|
15493
16520
|
}
|
|
@@ -15667,7 +16694,7 @@ __export(new_exports, {
|
|
|
15667
16694
|
handler: () => handler9
|
|
15668
16695
|
});
|
|
15669
16696
|
import chalk24 from "chalk";
|
|
15670
|
-
import { existsSync as
|
|
16697
|
+
import { existsSync as existsSync16 } from "fs";
|
|
15671
16698
|
import { basename as basename5 } from "path";
|
|
15672
16699
|
async function handler9(args, ctx) {
|
|
15673
16700
|
const { positional, flags } = parseArgs2(args, ["demo", "empty", "list-scenarios", "regen-taxonomy"]);
|
|
@@ -15689,7 +16716,7 @@ async function handler9(args, ctx) {
|
|
|
15689
16716
|
console.error(chalk24.red(" Usage: /new <file.csv> | --demo [--scenario <name>] | --empty [--lens health|metrics]"));
|
|
15690
16717
|
return;
|
|
15691
16718
|
}
|
|
15692
|
-
if (source.kind === "file" && !
|
|
16719
|
+
if (source.kind === "file" && !existsSync16(source.path)) {
|
|
15693
16720
|
console.error(chalk24.red(` File not found: ${source.path}`));
|
|
15694
16721
|
return;
|
|
15695
16722
|
}
|
|
@@ -15756,11 +16783,11 @@ async function handler9(args, ctx) {
|
|
|
15756
16783
|
return "New empty session";
|
|
15757
16784
|
}
|
|
15758
16785
|
if (lens === "revenue_metrics") {
|
|
15759
|
-
const
|
|
16786
|
+
const ora20 = (await import("ora")).default;
|
|
15760
16787
|
const { runMetricsAnalysis: runMetricsAnalysis2 } = await Promise.resolve().then(() => (init_metrics_analysis(), metrics_analysis_exports));
|
|
15761
16788
|
const { renderMetricsReport: renderMetricsReport2 } = await Promise.resolve().then(() => (init_metrics_report(), metrics_report_exports));
|
|
15762
16789
|
const structured = isStructuredOutput(ctx.execution);
|
|
15763
|
-
const spinner = structured ? null :
|
|
16790
|
+
const spinner = structured ? null : ora20({ text: "Computing SaaS metrics\u2026", indent: 2, discardStdin: false }).start();
|
|
15764
16791
|
let result;
|
|
15765
16792
|
try {
|
|
15766
16793
|
result = await runMetricsAnalysis2({
|
|
@@ -15966,7 +16993,7 @@ __export(session_exports, {
|
|
|
15966
16993
|
handler: () => handler11
|
|
15967
16994
|
});
|
|
15968
16995
|
import chalk26 from "chalk";
|
|
15969
|
-
import { join as
|
|
16996
|
+
import { join as join14 } from "path";
|
|
15970
16997
|
import ora7 from "ora";
|
|
15971
16998
|
async function handler11(args, ctx) {
|
|
15972
16999
|
const sub = args[0];
|
|
@@ -16055,7 +17082,7 @@ async function pickUp(idArg, ctx) {
|
|
|
16055
17082
|
}
|
|
16056
17083
|
resetContextForSwitch(ctx, {
|
|
16057
17084
|
sessionId: target.id,
|
|
16058
|
-
sessionFile:
|
|
17085
|
+
sessionFile: join14(getSessionsDir(), `${target.id}.json`),
|
|
16059
17086
|
sessionName: session.name,
|
|
16060
17087
|
messages: [...session.messages],
|
|
16061
17088
|
conversation: session.thread ? [...session.thread] : [],
|
|
@@ -16367,7 +17394,7 @@ __export(report_exports, {
|
|
|
16367
17394
|
handler: () => handler12
|
|
16368
17395
|
});
|
|
16369
17396
|
import chalk27 from "chalk";
|
|
16370
|
-
import { writeFileSync as
|
|
17397
|
+
import { writeFileSync as writeFileSync10 } from "fs";
|
|
16371
17398
|
import { dirname as dirname2 } from "path";
|
|
16372
17399
|
async function handler12(args, ctx) {
|
|
16373
17400
|
const { flags } = parseArgs2(args);
|
|
@@ -16463,7 +17490,7 @@ async function handler12(args, ctx) {
|
|
|
16463
17490
|
if (!isInsideNtrp(resolvedOutput)) {
|
|
16464
17491
|
console.warn(chalk27.yellow(` Warning: writing report outside ~/.ntrp (${dirname2(resolvedOutput)})`));
|
|
16465
17492
|
}
|
|
16466
|
-
|
|
17493
|
+
writeFileSync10(resolvedOutput, rendered);
|
|
16467
17494
|
console.log(chalk27.green(` Report written to ${resolvedOutput}`));
|
|
16468
17495
|
} else if (rendered) {
|
|
16469
17496
|
console.log(rendered);
|
|
@@ -16494,8 +17521,8 @@ var init_report2 = __esm({
|
|
|
16494
17521
|
});
|
|
16495
17522
|
|
|
16496
17523
|
// src/output/notes-export.ts
|
|
16497
|
-
import { writeFileSync as
|
|
16498
|
-
import { join as
|
|
17524
|
+
import { writeFileSync as writeFileSync11 } from "fs";
|
|
17525
|
+
import { join as join15 } from "path";
|
|
16499
17526
|
function exportToNotes(data) {
|
|
16500
17527
|
const { computeResult, divergences, findings, exchanges } = data;
|
|
16501
17528
|
const { aggregate, segments } = computeResult;
|
|
@@ -16504,7 +17531,7 @@ function exportToNotes(data) {
|
|
|
16504
17531
|
const timeStr = formatTime(now2);
|
|
16505
17532
|
const filename = `${dateStr}-${timeStr}-gtm-health.md`;
|
|
16506
17533
|
const dir = getExportsDir();
|
|
16507
|
-
const filepath =
|
|
17534
|
+
const filepath = join15(dir, filename);
|
|
16508
17535
|
const severityTags = /* @__PURE__ */ new Set();
|
|
16509
17536
|
for (const f of findings) severityTags.add(f.severity);
|
|
16510
17537
|
const tags = ["ntrp", "gtm-health", ...severityTags];
|
|
@@ -16593,7 +17620,7 @@ function exportToNotes(data) {
|
|
|
16593
17620
|
}
|
|
16594
17621
|
}
|
|
16595
17622
|
const content = frontmatter.join("\n") + "\n\n" + body.join("\n") + "\n";
|
|
16596
|
-
|
|
17623
|
+
writeFileSync11(filepath, content);
|
|
16597
17624
|
return filepath;
|
|
16598
17625
|
}
|
|
16599
17626
|
function formatDate(d) {
|
|
@@ -16733,8 +17760,8 @@ __export(backmeup_exports, {
|
|
|
16733
17760
|
});
|
|
16734
17761
|
import chalk29 from "chalk";
|
|
16735
17762
|
import Papa5 from "papaparse";
|
|
16736
|
-
import { mkdirSync as mkdirSync9, writeFileSync as
|
|
16737
|
-
import { join as
|
|
17763
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync12 } from "fs";
|
|
17764
|
+
import { join as join16 } from "path";
|
|
16738
17765
|
function sanitizeCsvValue(value) {
|
|
16739
17766
|
if (typeof value !== "string") return value;
|
|
16740
17767
|
return CSV_FORMULA_RE.test(value) ? `'${value}` : value;
|
|
@@ -16770,7 +17797,7 @@ async function handler14(args, _ctx) {
|
|
|
16770
17797
|
if (!isInsideNtrp(baseDir)) {
|
|
16771
17798
|
console.warn(chalk29.yellow(` Warning: writing backup outside ~/.ntrp (${baseDir})`));
|
|
16772
17799
|
}
|
|
16773
|
-
const folder =
|
|
17800
|
+
const folder = join16(baseDir, folderName);
|
|
16774
17801
|
mkdirSync9(folder, { recursive: true });
|
|
16775
17802
|
const generatedAt = now2.toISOString();
|
|
16776
17803
|
let fileCount = 0;
|
|
@@ -16785,7 +17812,7 @@ async function handler14(args, _ctx) {
|
|
|
16785
17812
|
"Total At Risk": health.total_value_at_risk != null ? formatCurrency(health.total_value_at_risk) : "N/A",
|
|
16786
17813
|
"Generated At": generatedAt
|
|
16787
17814
|
}));
|
|
16788
|
-
|
|
17815
|
+
writeFileSync12(join16(folder, "cover-sheet.csv"), Papa5.unparse(sanitizeCsvRows(coverRows)), "utf-8");
|
|
16789
17816
|
fileCount++;
|
|
16790
17817
|
if (findings.length > 0) {
|
|
16791
17818
|
const findingsRows = findings.map((f) => ({
|
|
@@ -16795,7 +17822,7 @@ async function handler14(args, _ctx) {
|
|
|
16795
17822
|
Finding: f.finding,
|
|
16796
17823
|
"Recommended Plays": f.recommended_plays ? f.recommended_plays.map((p) => p.play_name).join("; ") : ""
|
|
16797
17824
|
}));
|
|
16798
|
-
|
|
17825
|
+
writeFileSync12(join16(folder, "findings.csv"), Papa5.unparse(sanitizeCsvRows(findingsRows)), "utf-8");
|
|
16799
17826
|
fileCount++;
|
|
16800
17827
|
}
|
|
16801
17828
|
for (const vs of health.vital_signs) {
|
|
@@ -16805,7 +17832,7 @@ async function handler14(args, _ctx) {
|
|
|
16805
17832
|
...detail
|
|
16806
17833
|
}));
|
|
16807
17834
|
const filename = EVIDENCE_FILENAMES[vs.vital_sign] ?? `${vs.vital_sign}.csv`;
|
|
16808
|
-
|
|
17835
|
+
writeFileSync12(join16(folder, filename), Papa5.unparse(sanitizeCsvRows(rows)), "utf-8");
|
|
16809
17836
|
fileCount++;
|
|
16810
17837
|
}
|
|
16811
17838
|
console.log(chalk29.green(`
|
|
@@ -16999,8 +18026,8 @@ var init_bundle = __esm({
|
|
|
16999
18026
|
});
|
|
17000
18027
|
|
|
17001
18028
|
// src/repositories/markdown.ts
|
|
17002
|
-
import { mkdirSync as mkdirSync10, writeFileSync as
|
|
17003
|
-
import { basename as basename6, dirname as dirname3, join as
|
|
18029
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync13 } from "fs";
|
|
18030
|
+
import { basename as basename6, dirname as dirname3, join as join17, resolve as resolve6 } from "path";
|
|
17004
18031
|
import { stringify as stringifyYaml } from "yaml";
|
|
17005
18032
|
function renderMarkdownFiles(pkg) {
|
|
17006
18033
|
const bundleJson = JSON.stringify(pkg, null, 2) + "\n";
|
|
@@ -17223,9 +18250,9 @@ var init_markdown3 = __esm({
|
|
|
17223
18250
|
mkdirSync10(root, { recursive: true });
|
|
17224
18251
|
const written = [];
|
|
17225
18252
|
for (const file of files) {
|
|
17226
|
-
const absolutePath =
|
|
18253
|
+
const absolutePath = join17(root, file.relativePath);
|
|
17227
18254
|
mkdirSync10(dirname3(absolutePath), { recursive: true });
|
|
17228
|
-
|
|
18255
|
+
writeFileSync13(absolutePath, file.contents, "utf-8");
|
|
17229
18256
|
written.push(absolutePath);
|
|
17230
18257
|
}
|
|
17231
18258
|
return {
|
|
@@ -17529,8 +18556,8 @@ __export(handoff_exports, {
|
|
|
17529
18556
|
handler: () => handler16
|
|
17530
18557
|
});
|
|
17531
18558
|
import chalk31 from "chalk";
|
|
17532
|
-
import { writeFileSync as
|
|
17533
|
-
import { join as
|
|
18559
|
+
import { writeFileSync as writeFileSync14 } from "fs";
|
|
18560
|
+
import { join as join18 } from "path";
|
|
17534
18561
|
async function handler16(args, ctx) {
|
|
17535
18562
|
const sub = args[0];
|
|
17536
18563
|
if (!sub) {
|
|
@@ -17588,7 +18615,7 @@ async function interactiveMenu(ctx) {
|
|
|
17588
18615
|
}
|
|
17589
18616
|
}
|
|
17590
18617
|
async function runReport(args, ctx) {
|
|
17591
|
-
const out =
|
|
18618
|
+
const out = join18(getExportsDir(), `report-${stamp()}.md`);
|
|
17592
18619
|
const { handler: report } = await Promise.resolve().then(() => (init_report2(), report_exports));
|
|
17593
18620
|
await report(["--format", "md", "--output", out, ...args], ctx);
|
|
17594
18621
|
recordDeliverable(ctx, { kind: "report", at: (/* @__PURE__ */ new Date()).toISOString(), path: out });
|
|
@@ -17636,8 +18663,8 @@ async function runPrompt(target, ctx) {
|
|
|
17636
18663
|
return;
|
|
17637
18664
|
}
|
|
17638
18665
|
const prompt = draft.markdown;
|
|
17639
|
-
const out =
|
|
17640
|
-
|
|
18666
|
+
const out = join18(getExportsDir(), `handoff-${target}-${stamp()}.md`);
|
|
18667
|
+
writeFileSync14(out, prompt, "utf-8");
|
|
17641
18668
|
recordDeliverable(ctx, { kind: `prompt:${target}`, at: (/* @__PURE__ */ new Date()).toISOString(), path: out });
|
|
17642
18669
|
console.log();
|
|
17643
18670
|
console.log(" " + paint("accent", `Agent prompt ready (${target})`));
|
|
@@ -18509,24 +19536,24 @@ JSON SHAPE:
|
|
|
18509
19536
|
|
|
18510
19537
|
// src/strategies/readers.ts
|
|
18511
19538
|
import { createHash } from "crypto";
|
|
18512
|
-
import { existsSync as
|
|
19539
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
|
|
18513
19540
|
import { extname, resolve as resolve7 } from "path";
|
|
18514
19541
|
import { parse as parseYaml } from "yaml";
|
|
18515
19542
|
import { PDFParse } from "pdf-parse";
|
|
18516
19543
|
async function readStrategyFile(pathOrDash) {
|
|
18517
19544
|
if (pathOrDash === "-") {
|
|
18518
|
-
const text2 =
|
|
19545
|
+
const text2 = readFileSync13(0, "utf-8");
|
|
18519
19546
|
return createDocument("stdin", null, text2, {});
|
|
18520
19547
|
}
|
|
18521
19548
|
const sourcePath = resolve7(pathOrDash);
|
|
18522
|
-
if (!
|
|
19549
|
+
if (!existsSync17(sourcePath)) {
|
|
18523
19550
|
throw new NtrpError("strategy_file_not_found", `Strategy file not found: ${pathOrDash}`, 2 /* Usage */);
|
|
18524
19551
|
}
|
|
18525
19552
|
const ext = extname(sourcePath).toLowerCase();
|
|
18526
19553
|
if (ext === ".pdf") {
|
|
18527
19554
|
return readPdf(sourcePath);
|
|
18528
19555
|
}
|
|
18529
|
-
const text =
|
|
19556
|
+
const text = readFileSync13(sourcePath, "utf-8");
|
|
18530
19557
|
if (ext === ".yaml" || ext === ".yml") {
|
|
18531
19558
|
const structured = parseStructuredYaml(text);
|
|
18532
19559
|
return createDocument("yaml", sourcePath, text, structured);
|
|
@@ -18541,7 +19568,7 @@ function readStrategyText(text) {
|
|
|
18541
19568
|
return createDocument("text", null, text, {});
|
|
18542
19569
|
}
|
|
18543
19570
|
async function readPdf(sourcePath) {
|
|
18544
|
-
const data =
|
|
19571
|
+
const data = readFileSync13(sourcePath);
|
|
18545
19572
|
const parser = new PDFParse({ data });
|
|
18546
19573
|
try {
|
|
18547
19574
|
const result = await parser.getText();
|
|
@@ -18588,15 +19615,15 @@ var init_readers = __esm({
|
|
|
18588
19615
|
});
|
|
18589
19616
|
|
|
18590
19617
|
// src/strategies/library.ts
|
|
18591
|
-
import { writeFileSync as
|
|
18592
|
-
import { join as
|
|
19618
|
+
import { writeFileSync as writeFileSync15 } from "fs";
|
|
19619
|
+
import { join as join19 } from "path";
|
|
18593
19620
|
import { stringify as stringifyYaml2 } from "yaml";
|
|
18594
19621
|
function strategyLibraryPath(slug) {
|
|
18595
|
-
return
|
|
19622
|
+
return join19(getStrategiesDir(), `${slug}.md`);
|
|
18596
19623
|
}
|
|
18597
19624
|
function writeStrategyMarkdown(strategy) {
|
|
18598
19625
|
const path = strategyLibraryPath(strategy.slug);
|
|
18599
|
-
|
|
19626
|
+
writeFileSync15(path, renderStrategyMarkdown(strategy), "utf-8");
|
|
18600
19627
|
return path;
|
|
18601
19628
|
}
|
|
18602
19629
|
function renderStrategyMarkdown(strategy) {
|
|
@@ -18670,7 +19697,7 @@ var init_library = __esm({
|
|
|
18670
19697
|
// src/strategies/connectors.ts
|
|
18671
19698
|
import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
18672
19699
|
import { homedir as homedir7 } from "os";
|
|
18673
|
-
import { basename as basename7, extname as extname2, join as
|
|
19700
|
+
import { basename as basename7, extname as extname2, join as join20, relative, resolve as resolve8, sep as sep3 } from "path";
|
|
18674
19701
|
function createLocalFolderConnector(options) {
|
|
18675
19702
|
const rootPath = resolveUserPath2(options.rootPath);
|
|
18676
19703
|
const name = options.name ?? (basename7(rootPath) || "local");
|
|
@@ -18713,7 +19740,7 @@ function createLocalFolderConnector(options) {
|
|
|
18713
19740
|
}
|
|
18714
19741
|
function walkLocalFolder(rootPath, currentPath, refs, opts) {
|
|
18715
19742
|
for (const entry of readdirSync2(currentPath, { withFileTypes: true })) {
|
|
18716
|
-
const absolutePath =
|
|
19743
|
+
const absolutePath = join20(currentPath, entry.name);
|
|
18717
19744
|
const relativePath = normalizePath(relative(rootPath, absolutePath));
|
|
18718
19745
|
if (entry.isDirectory()) {
|
|
18719
19746
|
if (shouldSkipDirectory(entry.name) || matchesAny(relativePath, opts.excludePatterns)) continue;
|
|
@@ -18777,7 +19804,7 @@ function normalizePath(path) {
|
|
|
18777
19804
|
}
|
|
18778
19805
|
function resolveUserPath2(path) {
|
|
18779
19806
|
if (path === "~") return homedir7();
|
|
18780
|
-
if (path.startsWith("~/")) return
|
|
19807
|
+
if (path.startsWith("~/")) return join20(homedir7(), path.slice(2));
|
|
18781
19808
|
return resolve8(path);
|
|
18782
19809
|
}
|
|
18783
19810
|
var DEFAULT_MAX_FILES, DEFAULT_MAX_BYTES, SUPPORTED_EXTENSIONS, DEFAULT_EXCLUDED_DIRS;
|
|
@@ -19631,18 +20658,25 @@ __export(config_exports, {
|
|
|
19631
20658
|
handler: () => handler23
|
|
19632
20659
|
});
|
|
19633
20660
|
import chalk38 from "chalk";
|
|
20661
|
+
import ora10 from "ora";
|
|
20662
|
+
function secretKeys() {
|
|
20663
|
+
const keys = /* @__PURE__ */ new Set(["license-key", "license-instance-id", "voyage-api-key", "tavily-api-key", "brave-api-key"]);
|
|
20664
|
+
for (const spec of listProviderSpecs()) keys.add(spec.key_config_name);
|
|
20665
|
+
return keys;
|
|
20666
|
+
}
|
|
19634
20667
|
function display(key, value) {
|
|
19635
|
-
return
|
|
20668
|
+
return secretKeys().has(key) ? String(value).slice(0, 10) + "..." : String(value);
|
|
19636
20669
|
}
|
|
19637
20670
|
function secretPromptLabel(key) {
|
|
19638
|
-
|
|
19639
|
-
if (
|
|
20671
|
+
const spec = findSpecByConfigKey(key);
|
|
20672
|
+
if (spec) return `${spec.label} API key`;
|
|
19640
20673
|
if (key === "license-key") return "License key";
|
|
19641
20674
|
return key;
|
|
19642
20675
|
}
|
|
19643
20676
|
function usage() {
|
|
19644
20677
|
console.log(chalk38.dim(" Usage: /config <get|set|list|delete> [key] [value]"));
|
|
19645
20678
|
console.log(chalk38.dim(" Tip: ") + paint("accent", "/config set api-key") + chalk38.dim(" opens a hidden prompt (no inline paste)."));
|
|
20679
|
+
console.log(chalk38.dim(" Tip: ") + paint("accent", "/connect") + chalk38.dim(" auto-detects the provider from any pasted key."));
|
|
19646
20680
|
}
|
|
19647
20681
|
function fail(message, ctx) {
|
|
19648
20682
|
console.error(chalk38.red(` ${message}`));
|
|
@@ -19674,7 +20708,7 @@ async function handler23(args, ctx) {
|
|
|
19674
20708
|
return;
|
|
19675
20709
|
}
|
|
19676
20710
|
let value = inlineValue;
|
|
19677
|
-
if (!value &&
|
|
20711
|
+
if (!value && secretKeys().has(key)) {
|
|
19678
20712
|
try {
|
|
19679
20713
|
value = await promptSecretValue(key, ctx);
|
|
19680
20714
|
} catch (err) {
|
|
@@ -19694,8 +20728,24 @@ async function handler23(args, ctx) {
|
|
|
19694
20728
|
}
|
|
19695
20729
|
console.log();
|
|
19696
20730
|
console.log(chalk38.green(` \u2713 ${key} saved`) + chalk38.dim(` (${display(key, value)})`));
|
|
19697
|
-
|
|
19698
|
-
|
|
20731
|
+
const spec = findSpecByConfigKey(key);
|
|
20732
|
+
if (spec) {
|
|
20733
|
+
const spinner = ora10({ text: `Discovering ${spec.label} models\u2026`, discardStdin: false }).start();
|
|
20734
|
+
try {
|
|
20735
|
+
const { refreshProviderModels: refreshProviderModels2 } = await Promise.resolve().then(() => (init_discovery(), discovery_exports));
|
|
20736
|
+
const entry = await refreshProviderModels2(spec.id, { apiKey: value, force: true });
|
|
20737
|
+
if (entry) {
|
|
20738
|
+
spinner.succeed(`${spec.label}: ${entry.models.length} chat models available.`);
|
|
20739
|
+
console.log(
|
|
20740
|
+
" " + chalk38.dim(`high ${entry.tier_stack.high} \xB7 medium ${entry.tier_stack.medium} \xB7 low ${entry.tier_stack.low}`)
|
|
20741
|
+
);
|
|
20742
|
+
} else {
|
|
20743
|
+
spinner.warn(`${spec.label} unreachable \u2014 models will be discovered on first use.`);
|
|
20744
|
+
}
|
|
20745
|
+
} catch {
|
|
20746
|
+
spinner.warn(`${spec.label} unreachable \u2014 models will be discovered on first use.`);
|
|
20747
|
+
}
|
|
20748
|
+
console.log(" " + chalk38.dim("Switch engines with /provider \xB7 browse models with /model list."));
|
|
19699
20749
|
}
|
|
19700
20750
|
console.log();
|
|
19701
20751
|
return;
|
|
@@ -19738,15 +20788,14 @@ async function handler23(args, ctx) {
|
|
|
19738
20788
|
}
|
|
19739
20789
|
}
|
|
19740
20790
|
}
|
|
19741
|
-
var SECRET_KEYS;
|
|
19742
20791
|
var init_config = __esm({
|
|
19743
20792
|
"src/commands/config.ts"() {
|
|
19744
20793
|
"use strict";
|
|
19745
20794
|
init_store();
|
|
20795
|
+
init_providers();
|
|
19746
20796
|
init_argparse();
|
|
19747
20797
|
init_prompts();
|
|
19748
20798
|
init_theme();
|
|
19749
|
-
SECRET_KEYS = /* @__PURE__ */ new Set(["api-key", "openai-api-key", "license-key", "license-instance-id"]);
|
|
19750
20799
|
}
|
|
19751
20800
|
});
|
|
19752
20801
|
|
|
@@ -20528,15 +21577,15 @@ var init_checkout = __esm({
|
|
|
20528
21577
|
});
|
|
20529
21578
|
|
|
20530
21579
|
// src/services/setup.ts
|
|
20531
|
-
import { existsSync as
|
|
20532
|
-
import { join as
|
|
21580
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync16 } from "fs";
|
|
21581
|
+
import { join as join21 } from "path";
|
|
20533
21582
|
function setupCheck() {
|
|
20534
21583
|
const home = ntrpHome();
|
|
20535
21584
|
let writable = false;
|
|
20536
21585
|
try {
|
|
20537
21586
|
mkdirSync11(home, { recursive: true });
|
|
20538
|
-
const probe =
|
|
20539
|
-
|
|
21587
|
+
const probe = join21(home, ".write-check");
|
|
21588
|
+
writeFileSync16(probe, "ok\n");
|
|
20540
21589
|
writable = true;
|
|
20541
21590
|
} catch {
|
|
20542
21591
|
writable = false;
|
|
@@ -20561,7 +21610,8 @@ function setupCheck() {
|
|
|
20561
21610
|
tier: llmCfg.tier,
|
|
20562
21611
|
auto_failover: llmCfg.autoFailover,
|
|
20563
21612
|
anthropic: llmReady.anthropic,
|
|
20564
|
-
openai: llmReady.openai
|
|
21613
|
+
openai: llmReady.openai,
|
|
21614
|
+
providers: llmReady.providers
|
|
20565
21615
|
}
|
|
20566
21616
|
},
|
|
20567
21617
|
license: {
|
|
@@ -20573,7 +21623,7 @@ function setupCheck() {
|
|
|
20573
21623
|
};
|
|
20574
21624
|
}
|
|
20575
21625
|
function readProfileInput(pathOrDash) {
|
|
20576
|
-
const raw = pathOrDash === "-" ?
|
|
21626
|
+
const raw = pathOrDash === "-" ? readFileSync14(0, "utf-8") : readFileSync14(pathOrDash, "utf-8");
|
|
20577
21627
|
return JSON.parse(raw);
|
|
20578
21628
|
}
|
|
20579
21629
|
function writeAgentProfile(input) {
|
|
@@ -20598,11 +21648,15 @@ function writeAgentProfile(input) {
|
|
|
20598
21648
|
saveProfile(profile);
|
|
20599
21649
|
return profile;
|
|
20600
21650
|
}
|
|
20601
|
-
function applyAgentConfig(opts) {
|
|
21651
|
+
async function applyAgentConfig(opts) {
|
|
20602
21652
|
if (opts.defaultFormat) setConfigValue("default-format", opts.defaultFormat);
|
|
20603
21653
|
if (opts.apiKey) setConfigValue("api-key", opts.apiKey);
|
|
20604
21654
|
if (opts.openaiApiKey) setConfigValue("openai-api-key", opts.openaiApiKey);
|
|
20605
|
-
if (opts.
|
|
21655
|
+
if (opts.llmKey) {
|
|
21656
|
+
const { connectWithKey: connectWithKey2 } = await Promise.resolve().then(() => (init_connect(), connect_exports));
|
|
21657
|
+
await connectWithKey2(opts.llmKey, { providerId: opts.llmProvider });
|
|
21658
|
+
}
|
|
21659
|
+
if (opts.llmPrimary && getProviderSpec(opts.llmPrimary)) {
|
|
20606
21660
|
setConfigValue("llm-primary", opts.llmPrimary);
|
|
20607
21661
|
}
|
|
20608
21662
|
if (opts.licenseKey) setConfigValue("license-key", opts.licenseKey);
|
|
@@ -20612,6 +21666,7 @@ var init_setup = __esm({
|
|
|
20612
21666
|
"src/services/setup.ts"() {
|
|
20613
21667
|
"use strict";
|
|
20614
21668
|
init_repl_api();
|
|
21669
|
+
init_providers();
|
|
20615
21670
|
init_llm_config();
|
|
20616
21671
|
init_store();
|
|
20617
21672
|
init_profile();
|
|
@@ -20654,13 +21709,12 @@ async function handler27(args, ctx) {
|
|
|
20654
21709
|
console.log(` Writable: ${result.writable ? "yes" : "no"}`);
|
|
20655
21710
|
console.log(` Profile: ${result.profile.exists ? "ready" : "missing"} (${result.profile.path})`);
|
|
20656
21711
|
const llm = result.config.llm;
|
|
20657
|
-
if (llm) {
|
|
20658
|
-
|
|
20659
|
-
console.log(`
|
|
20660
|
-
console.log(` Anthropic: ${llm.anthropic ? "set" : "missing"} \xB7 OpenAI: ${llm.openai ? "set" : "missing"}`);
|
|
21712
|
+
if (llm && llm.providers.length > 0) {
|
|
21713
|
+
console.log(` Engines: ${llm.providers.length} \xB7 default ${llm.primary} \xB7 tier ${llm.tier}`);
|
|
21714
|
+
console.log(` Connected: ${llm.providers.join(", ")}`);
|
|
20661
21715
|
console.log(` Auto-failover: ${llm.auto_failover ? "on" : "off"}`);
|
|
20662
21716
|
} else {
|
|
20663
|
-
console.log(" Engines: missing");
|
|
21717
|
+
console.log(" Engines: missing \u2014 run /connect with any provider key");
|
|
20664
21718
|
}
|
|
20665
21719
|
console.log(` License: ${formatLicenseSetupLine(result.license)}`);
|
|
20666
21720
|
console.log();
|
|
@@ -20681,10 +21735,12 @@ async function handler27(args, ctx) {
|
|
|
20681
21735
|
sales_motion: getString(flags, "sales-motion")
|
|
20682
21736
|
};
|
|
20683
21737
|
}
|
|
20684
|
-
applyAgentConfig({
|
|
21738
|
+
await applyAgentConfig({
|
|
20685
21739
|
defaultFormat: getString(flags, "default-format"),
|
|
20686
21740
|
apiKey: getString(flags, "api-key"),
|
|
20687
21741
|
openaiApiKey: getString(flags, "openai-api-key"),
|
|
21742
|
+
llmKey: getString(flags, "llm-key"),
|
|
21743
|
+
llmProvider: getString(flags, "llm-provider"),
|
|
20688
21744
|
llmPrimary: getString(flags, "llm-primary"),
|
|
20689
21745
|
licenseKey: getString(flags, "license-key"),
|
|
20690
21746
|
exportDir: getString(flags, "export-dir")
|
|
@@ -20724,8 +21780,8 @@ var init_setup2 = __esm({
|
|
|
20724
21780
|
|
|
20725
21781
|
// src/conversation/orchestrator.ts
|
|
20726
21782
|
import chalk43 from "chalk";
|
|
20727
|
-
import { writeFileSync as
|
|
20728
|
-
import { join as
|
|
21783
|
+
import { writeFileSync as writeFileSync17 } from "fs";
|
|
21784
|
+
import { join as join22 } from "path";
|
|
20729
21785
|
function printScopeProposal(ctx) {
|
|
20730
21786
|
if (!ctx.scope) return;
|
|
20731
21787
|
const lens = ctx.scope.primary_lens === "revenue_metrics" ? "SaaS metrics" : "pipeline health";
|
|
@@ -20883,8 +21939,8 @@ async function handleDeliverFlow(input, ctx) {
|
|
|
20883
21939
|
prompts.close();
|
|
20884
21940
|
}
|
|
20885
21941
|
const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/T/, "-").replace(/:/g, "").slice(0, 15);
|
|
20886
|
-
const out =
|
|
20887
|
-
|
|
21942
|
+
const out = join22(getExportsDir(), `handoff-${target}-${stamp2}.md`);
|
|
21943
|
+
writeFileSync17(out, draft.markdown, "utf-8");
|
|
20888
21944
|
const d = {
|
|
20889
21945
|
kind: `prompt:${target}`,
|
|
20890
21946
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -20908,7 +21964,7 @@ async function handleExploreWithoutKey(ctx) {
|
|
|
20908
21964
|
console.log();
|
|
20909
21965
|
console.log(" " + chalk43.red("AI interpretation needs an LLM API key saved in config."));
|
|
20910
21966
|
console.log(
|
|
20911
|
-
" " + chalk43.dim("
|
|
21967
|
+
" " + chalk43.dim("Run ") + paint("accent", "/connect") + chalk43.dim(" and paste any provider's key (Anthropic, OpenAI, Groq, Gemini, ...).")
|
|
20912
21968
|
);
|
|
20913
21969
|
console.log(" " + chalk43.dim("Number crunching works without a key \u2014 only Q&A in the REPL needs one."));
|
|
20914
21970
|
if (ctx.gapAudit) {
|
|
@@ -21083,8 +22139,8 @@ async function callProvider(texts) {
|
|
|
21083
22139
|
async function embedText(text) {
|
|
21084
22140
|
const key = text.trim();
|
|
21085
22141
|
if (!key) return null;
|
|
21086
|
-
const
|
|
21087
|
-
if (
|
|
22142
|
+
const cached2 = cache.get(key);
|
|
22143
|
+
if (cached2) return cached2;
|
|
21088
22144
|
const result = await callProvider([key]);
|
|
21089
22145
|
const vec = result?.[0] ?? null;
|
|
21090
22146
|
if (vec) cache.set(key, vec);
|
|
@@ -21094,8 +22150,8 @@ async function embedItems(items) {
|
|
|
21094
22150
|
const needing = [];
|
|
21095
22151
|
const out = items.map((it, index) => {
|
|
21096
22152
|
if (it.embedding && it.embedding.length > 0) return { ...it };
|
|
21097
|
-
const
|
|
21098
|
-
if (
|
|
22153
|
+
const cached2 = cache.get(it.text.trim());
|
|
22154
|
+
if (cached2) return { ...it, embedding: cached2 };
|
|
21099
22155
|
needing.push({ index, text: it.text });
|
|
21100
22156
|
return { ...it };
|
|
21101
22157
|
});
|
|
@@ -21254,17 +22310,17 @@ var init_retrieval = __esm({
|
|
|
21254
22310
|
});
|
|
21255
22311
|
|
|
21256
22312
|
// src/memory/knowledge.ts
|
|
21257
|
-
import { existsSync as
|
|
21258
|
-
import { join as
|
|
22313
|
+
import { existsSync as existsSync19, readFileSync as readFileSync15, appendFileSync as appendFileSync3, readdirSync as readdirSync3 } from "fs";
|
|
22314
|
+
import { join as join23 } from "path";
|
|
21259
22315
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
21260
22316
|
function knowledgePath() {
|
|
21261
|
-
return
|
|
22317
|
+
return join23(getMemoryDir(), KNOWLEDGE_FILE);
|
|
21262
22318
|
}
|
|
21263
22319
|
function loadKnowledgeChunks() {
|
|
21264
22320
|
const path = knowledgePath();
|
|
21265
|
-
if (!
|
|
22321
|
+
if (!existsSync19(path)) return [];
|
|
21266
22322
|
const out = [];
|
|
21267
|
-
for (const line of
|
|
22323
|
+
for (const line of readFileSync15(path, "utf-8").split("\n")) {
|
|
21268
22324
|
const trimmed = line.trim();
|
|
21269
22325
|
if (!trimmed) continue;
|
|
21270
22326
|
try {
|
|
@@ -21365,17 +22421,17 @@ __export(store_exports2, {
|
|
|
21365
22421
|
rewriteJsonl: () => rewriteJsonl,
|
|
21366
22422
|
scrubText: () => scrubText
|
|
21367
22423
|
});
|
|
21368
|
-
import { existsSync as
|
|
21369
|
-
import { join as
|
|
22424
|
+
import { existsSync as existsSync20, readFileSync as readFileSync16, appendFileSync as appendFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync18 } from "fs";
|
|
22425
|
+
import { join as join24 } from "path";
|
|
21370
22426
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
21371
22427
|
function memPath(file) {
|
|
21372
|
-
return
|
|
22428
|
+
return join24(getMemoryDir(), file);
|
|
21373
22429
|
}
|
|
21374
22430
|
function readJsonl(file) {
|
|
21375
22431
|
const path = memPath(file);
|
|
21376
|
-
if (!
|
|
22432
|
+
if (!existsSync20(path)) return [];
|
|
21377
22433
|
const out = [];
|
|
21378
|
-
for (const line of
|
|
22434
|
+
for (const line of readFileSync16(path, "utf-8").split("\n")) {
|
|
21379
22435
|
const trimmed = line.trim();
|
|
21380
22436
|
if (!trimmed) continue;
|
|
21381
22437
|
try {
|
|
@@ -21393,7 +22449,7 @@ function appendJsonl(file, obj) {
|
|
|
21393
22449
|
}
|
|
21394
22450
|
function rewriteJsonl(file, rows) {
|
|
21395
22451
|
try {
|
|
21396
|
-
|
|
22452
|
+
writeFileSync18(memPath(file), rows.map((r) => JSON.stringify(r)).join("\n") + (rows.length ? "\n" : ""));
|
|
21397
22453
|
} catch {
|
|
21398
22454
|
}
|
|
21399
22455
|
}
|
|
@@ -21455,7 +22511,7 @@ function loadWinSnippets() {
|
|
|
21455
22511
|
const out = [];
|
|
21456
22512
|
for (const name of readdirSync4(dir)) {
|
|
21457
22513
|
if (!name.endsWith(".md") || name.toLowerCase() === "readme.md") continue;
|
|
21458
|
-
const raw =
|
|
22514
|
+
const raw = readFileSync16(join24(dir, name), "utf-8");
|
|
21459
22515
|
const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? name.replace(/\.md$/, "");
|
|
21460
22516
|
const body = raw.replace(/^#.*$/m, "").replace(/\s+/g, " ").trim().slice(0, 300);
|
|
21461
22517
|
out.push({ id: `win:${name}`, title, text: `${title}. ${body}` });
|
|
@@ -21544,7 +22600,7 @@ var init_store2 = __esm({
|
|
|
21544
22600
|
});
|
|
21545
22601
|
|
|
21546
22602
|
// src/services/smoke-protocol.ts
|
|
21547
|
-
import { join as
|
|
22603
|
+
import { join as join25 } from "path";
|
|
21548
22604
|
function isSmokeProtocolTrigger(input) {
|
|
21549
22605
|
return normalize(input).includes(SMOKE_TRIGGER_PHRASE);
|
|
21550
22606
|
}
|
|
@@ -21580,7 +22636,7 @@ async function runSmokeProtocol(_input, ctx) {
|
|
|
21580
22636
|
});
|
|
21581
22637
|
const proposalResult = await proposeRepositoryExport({
|
|
21582
22638
|
target: "markdown",
|
|
21583
|
-
directory:
|
|
22639
|
+
directory: join25(getExportsDir(), "repository-smoke"),
|
|
21584
22640
|
source: "smoke_protocol",
|
|
21585
22641
|
modelOrFixture: "smoke-protocol-v1"
|
|
21586
22642
|
});
|
|
@@ -21677,13 +22733,13 @@ var nl_exports = {};
|
|
|
21677
22733
|
__export(nl_exports, {
|
|
21678
22734
|
runNaturalLanguage: () => runNaturalLanguage
|
|
21679
22735
|
});
|
|
21680
|
-
import
|
|
22736
|
+
import ora11 from "ora";
|
|
21681
22737
|
import chalk44 from "chalk";
|
|
21682
22738
|
async function runNaturalLanguage(input, ctx) {
|
|
21683
22739
|
if (isSmokeProtocolTrigger(input)) {
|
|
21684
22740
|
recordMessage(ctx, "user", input);
|
|
21685
22741
|
console.log();
|
|
21686
|
-
const spinner2 =
|
|
22742
|
+
const spinner2 = ora11({ text: "Running smoke protocol\u2026", color: "cyan", discardStdin: false }).start();
|
|
21687
22743
|
try {
|
|
21688
22744
|
const result = await runSmokeProtocol(input, ctx);
|
|
21689
22745
|
spinner2.succeed("Smoke protocol complete");
|
|
@@ -21710,7 +22766,7 @@ async function runNaturalLanguage(input, ctx) {
|
|
|
21710
22766
|
let snapshot = ctx.snapshot.computeResult;
|
|
21711
22767
|
if (!snapshot) {
|
|
21712
22768
|
const metricsFirst = prefersMetricsFirstContext(ctx);
|
|
21713
|
-
const spinner2 =
|
|
22769
|
+
const spinner2 = ora11({
|
|
21714
22770
|
text: metricsFirst ? "Loading session context\u2026" : "Computing vital signs\u2026",
|
|
21715
22771
|
color: "cyan",
|
|
21716
22772
|
discardStdin: false
|
|
@@ -21735,7 +22791,7 @@ async function runNaturalLanguage(input, ctx) {
|
|
|
21735
22791
|
}
|
|
21736
22792
|
console.log();
|
|
21737
22793
|
const memoryBlock = await buildMemoryBlock(input).catch(() => "");
|
|
21738
|
-
const spinner =
|
|
22794
|
+
const spinner = ora11({ text: "Thinking\u2026", color: "cyan", discardStdin: false }).start();
|
|
21739
22795
|
let lastAnswer = "";
|
|
21740
22796
|
let rawHistory = [];
|
|
21741
22797
|
const toolsUsed = [];
|
|
@@ -22026,7 +23082,7 @@ __export(metrics_exports, {
|
|
|
22026
23082
|
handler: () => handler29
|
|
22027
23083
|
});
|
|
22028
23084
|
import chalk46 from "chalk";
|
|
22029
|
-
import
|
|
23085
|
+
import ora12 from "ora";
|
|
22030
23086
|
async function handler29(args, ctx) {
|
|
22031
23087
|
await hydrateAnalysisFromPersistedState(ctx);
|
|
22032
23088
|
const { flags } = parseArgs2(args, ["findings"]);
|
|
@@ -22068,7 +23124,7 @@ async function handler29(args, ctx) {
|
|
|
22068
23124
|
await initSchema();
|
|
22069
23125
|
await autoGenerateSegments();
|
|
22070
23126
|
printCompanionBanner("metrics", ctx.analysis.primary);
|
|
22071
|
-
const spinner =
|
|
23127
|
+
const spinner = ora12({
|
|
22072
23128
|
text: "Computing SaaS metrics\u2026",
|
|
22073
23129
|
indent: 2,
|
|
22074
23130
|
discardStdin: false
|
|
@@ -22267,7 +23323,7 @@ __export(feedback_exports, {
|
|
|
22267
23323
|
handler: () => handler30
|
|
22268
23324
|
});
|
|
22269
23325
|
import chalk47 from "chalk";
|
|
22270
|
-
import
|
|
23326
|
+
import ora13 from "ora";
|
|
22271
23327
|
async function handler30(args, ctx) {
|
|
22272
23328
|
const feedbackText = args.join(" ").trim();
|
|
22273
23329
|
if (!feedbackText) {
|
|
@@ -22295,7 +23351,7 @@ async function handler30(args, ctx) {
|
|
|
22295
23351
|
console.log();
|
|
22296
23352
|
return;
|
|
22297
23353
|
}
|
|
22298
|
-
const spinner =
|
|
23354
|
+
const spinner = ora13({ text: "Applying feedback\u2026", discardStdin: false }).start();
|
|
22299
23355
|
try {
|
|
22300
23356
|
const result = await applyFeedback(profile, feedbackText, ctx);
|
|
22301
23357
|
spinner.succeed("Feedback applied");
|
|
@@ -22327,7 +23383,7 @@ var recap_exports = {};
|
|
|
22327
23383
|
__export(recap_exports, {
|
|
22328
23384
|
handler: () => handler31
|
|
22329
23385
|
});
|
|
22330
|
-
import
|
|
23386
|
+
import ora14 from "ora";
|
|
22331
23387
|
import chalk48 from "chalk";
|
|
22332
23388
|
async function handler31(_args, ctx) {
|
|
22333
23389
|
if (ctx.messages.length === 0) {
|
|
@@ -22364,7 +23420,7 @@ ${companyBlock}` : "",
|
|
|
22364
23420
|
const prefix = msg.role === "user" ? "USER" : "ASSISTANT";
|
|
22365
23421
|
conversationLines.push(`[${prefix}]: ${msg.content}`);
|
|
22366
23422
|
}
|
|
22367
|
-
const spinner =
|
|
23423
|
+
const spinner = ora14({ text: "Summarizing session\u2026", color: "cyan", discardStdin: false }).start();
|
|
22368
23424
|
try {
|
|
22369
23425
|
const { text: fullText } = await llmStreamText(
|
|
22370
23426
|
"recap",
|
|
@@ -22492,7 +23548,7 @@ var init_recall = __esm({
|
|
|
22492
23548
|
|
|
22493
23549
|
// src/memory/feedback.ts
|
|
22494
23550
|
import { appendFileSync as appendFileSync5 } from "fs";
|
|
22495
|
-
import { join as
|
|
23551
|
+
import { join as join26 } from "path";
|
|
22496
23552
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
22497
23553
|
function summarize(text) {
|
|
22498
23554
|
return text.replace(/[#*`>_]/g, "").replace(/\s+/g, " ").trim().slice(0, 200);
|
|
@@ -22508,7 +23564,7 @@ function recordFeedback(input) {
|
|
|
22508
23564
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
22509
23565
|
};
|
|
22510
23566
|
try {
|
|
22511
|
-
appendFileSync5(
|
|
23567
|
+
appendFileSync5(join26(getMemoryDir(), "feedback.jsonl"), JSON.stringify(entry) + "\n");
|
|
22512
23568
|
} catch {
|
|
22513
23569
|
}
|
|
22514
23570
|
if (input.rating === "positive") {
|
|
@@ -22599,7 +23655,7 @@ __export(knowledge_exports, {
|
|
|
22599
23655
|
handler: () => handler35
|
|
22600
23656
|
});
|
|
22601
23657
|
import chalk52 from "chalk";
|
|
22602
|
-
import
|
|
23658
|
+
import ora15 from "ora";
|
|
22603
23659
|
async function handler35(args, ctx) {
|
|
22604
23660
|
const sub = (args[0] ?? "list").toLowerCase();
|
|
22605
23661
|
if (sub === "add") {
|
|
@@ -22611,7 +23667,7 @@ async function handler35(args, ctx) {
|
|
|
22611
23667
|
console.log();
|
|
22612
23668
|
return;
|
|
22613
23669
|
}
|
|
22614
|
-
const spin = ctx.execution.progress ?
|
|
23670
|
+
const spin = ctx.execution.progress ? ora15({ text: "Ingesting knowledge\u2026", color: "cyan", discardStdin: false }).start() : null;
|
|
22615
23671
|
try {
|
|
22616
23672
|
const result = await addKnowledgeFile(path);
|
|
22617
23673
|
spin?.succeed(`Indexed "${result.title}"`);
|
|
@@ -22921,8 +23977,8 @@ var switch_exports = {};
|
|
|
22921
23977
|
__export(switch_exports, {
|
|
22922
23978
|
handler: () => handler39
|
|
22923
23979
|
});
|
|
22924
|
-
import { join as
|
|
22925
|
-
import
|
|
23980
|
+
import { join as join27 } from "path";
|
|
23981
|
+
import ora16 from "ora";
|
|
22926
23982
|
import chalk56 from "chalk";
|
|
22927
23983
|
async function handler39(args, ctx) {
|
|
22928
23984
|
if (args.length === 0) {
|
|
@@ -22937,7 +23993,7 @@ async function handler39(args, ctx) {
|
|
|
22937
23993
|
}
|
|
22938
23994
|
const exchangeCount = Math.floor(ctx.messages.length / 2);
|
|
22939
23995
|
if (exchangeCount > 0) {
|
|
22940
|
-
const spinner =
|
|
23996
|
+
const spinner = ora16({ text: "Saving current session\u2026", color: "cyan", discardStdin: false }).start();
|
|
22941
23997
|
await closeSession(ctx);
|
|
22942
23998
|
const fromLabel = ctx.sessionName ? `"${ctx.sessionName}"` : ctx.sessionId.slice(-4);
|
|
22943
23999
|
spinner.succeed(`Saved ${fromLabel}`);
|
|
@@ -22952,7 +24008,7 @@ async function handler39(args, ctx) {
|
|
|
22952
24008
|
}
|
|
22953
24009
|
const context = buildSwitchContext(session);
|
|
22954
24010
|
const newId = makeSessionId();
|
|
22955
|
-
const newFile =
|
|
24011
|
+
const newFile = join27(getSessionsDir(), `${newId}.json`);
|
|
22956
24012
|
resetContextForSwitch(ctx, {
|
|
22957
24013
|
sessionId: newId,
|
|
22958
24014
|
sessionFile: newFile,
|
|
@@ -22978,7 +24034,7 @@ async function handler39(args, ctx) {
|
|
|
22978
24034
|
return `Switched to "${targetName}"`;
|
|
22979
24035
|
} else {
|
|
22980
24036
|
const newId = makeSessionId();
|
|
22981
|
-
const newFile =
|
|
24037
|
+
const newFile = join27(getSessionsDir(), `${newId}.json`);
|
|
22982
24038
|
resetContextForSwitch(ctx, {
|
|
22983
24039
|
sessionId: newId,
|
|
22984
24040
|
sessionFile: newFile,
|
|
@@ -23028,13 +24084,177 @@ var init_switch = __esm({
|
|
|
23028
24084
|
}
|
|
23029
24085
|
});
|
|
23030
24086
|
|
|
23031
|
-
// src/commands/
|
|
23032
|
-
var
|
|
23033
|
-
__export(
|
|
24087
|
+
// src/commands/connect.ts
|
|
24088
|
+
var connect_exports2 = {};
|
|
24089
|
+
__export(connect_exports2, {
|
|
23034
24090
|
handler: () => handler40
|
|
23035
24091
|
});
|
|
23036
24092
|
import chalk57 from "chalk";
|
|
24093
|
+
import ora17 from "ora";
|
|
24094
|
+
function usage2() {
|
|
24095
|
+
console.log(chalk57.dim(" Usage: /connect paste any provider key"));
|
|
24096
|
+
console.log(chalk57.dim(" /connect <provider> key for a specific provider (or: ollama)"));
|
|
24097
|
+
console.log(chalk57.dim(" /connect --key <key> non-interactive (auto-detects provider)"));
|
|
24098
|
+
console.log(chalk57.dim(" /connect --base-url <url> [--id <name>] [--key <key>] custom endpoint"));
|
|
24099
|
+
}
|
|
24100
|
+
function printOutcome(outcome, ctx) {
|
|
24101
|
+
console.log();
|
|
24102
|
+
const [headline, ...rest] = describeConnectOutcome(outcome);
|
|
24103
|
+
console.log(" " + paint("success", "\u2713") + " " + chalk57.bold(headline ?? ""));
|
|
24104
|
+
for (const line of rest) {
|
|
24105
|
+
console.log(" " + chalk57.dim(line));
|
|
24106
|
+
}
|
|
24107
|
+
console.log();
|
|
24108
|
+
console.log(" " + chalk57.dim(`Active stack: ${formatActiveStack(ctx)}`));
|
|
24109
|
+
console.log(" " + chalk57.dim("/provider to switch engines \xB7 /model list to browse models"));
|
|
24110
|
+
console.log();
|
|
24111
|
+
}
|
|
24112
|
+
function printError(err, ctx) {
|
|
24113
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
24114
|
+
console.log();
|
|
24115
|
+
console.log(" " + chalk57.red(message));
|
|
24116
|
+
console.log();
|
|
24117
|
+
if (ctx.oneShot) process.exit(1);
|
|
24118
|
+
}
|
|
24119
|
+
async function promptKey(session, label) {
|
|
24120
|
+
console.log();
|
|
24121
|
+
console.log(
|
|
24122
|
+
" " + chalk57.dim("Paste once, press Enter. Stored in ") + paint("accent", "~/.ntrp/config.json") + chalk57.dim(" only.")
|
|
24123
|
+
);
|
|
24124
|
+
return session.askSecret(label, { confirm: false });
|
|
24125
|
+
}
|
|
23037
24126
|
async function handler40(args, ctx) {
|
|
24127
|
+
const { positional, flags } = parseArgs2(args);
|
|
24128
|
+
const sub = positional[0]?.toLowerCase();
|
|
24129
|
+
if (sub === "help") {
|
|
24130
|
+
usage2();
|
|
24131
|
+
return;
|
|
24132
|
+
}
|
|
24133
|
+
const inlineKey = getString(flags, "key");
|
|
24134
|
+
const baseUrl = getString(flags, "base-url", "url");
|
|
24135
|
+
const forcedProvider = getString(flags, "provider") ?? (sub && sub !== "help" ? sub : void 0);
|
|
24136
|
+
const customId = getString(flags, "id");
|
|
24137
|
+
const label = getString(flags, "label");
|
|
24138
|
+
const forcedSpec = forcedProvider ? getProviderSpec(forcedProvider) : void 0;
|
|
24139
|
+
if (forcedSpec && !forcedSpec.requires_key && !inlineKey) {
|
|
24140
|
+
const spinner2 = ora17({ text: `Looking for ${forcedSpec.label}\u2026`, discardStdin: false }).start();
|
|
24141
|
+
try {
|
|
24142
|
+
const outcome = await connectKeyless(forcedSpec.id, baseUrl);
|
|
24143
|
+
spinner2.stop();
|
|
24144
|
+
printOutcome(outcome, ctx);
|
|
24145
|
+
} catch (err) {
|
|
24146
|
+
spinner2.stop();
|
|
24147
|
+
printError(err, ctx);
|
|
24148
|
+
}
|
|
24149
|
+
return;
|
|
24150
|
+
}
|
|
24151
|
+
if (baseUrl && !forcedSpec) {
|
|
24152
|
+
const id = customId ?? forcedProvider ?? hostToId(baseUrl);
|
|
24153
|
+
let key2 = inlineKey;
|
|
24154
|
+
if (!key2 && !ctx.oneShot && process.stdin.isTTY) {
|
|
24155
|
+
const session2 = createPromptSession(ctx.rl, ctx);
|
|
24156
|
+
try {
|
|
24157
|
+
const needsKey = await session2.confirm("Does this endpoint need an API key?", false);
|
|
24158
|
+
if (needsKey) key2 = await promptKey(session2, `API key for ${id}`);
|
|
24159
|
+
} finally {
|
|
24160
|
+
session2.close();
|
|
24161
|
+
}
|
|
24162
|
+
}
|
|
24163
|
+
const spinner2 = ora17({ text: `Checking ${baseUrl}\u2026`, discardStdin: false }).start();
|
|
24164
|
+
try {
|
|
24165
|
+
const outcome = await connectCustomEndpoint({ id, baseUrl, key: key2, label });
|
|
24166
|
+
spinner2.stop();
|
|
24167
|
+
printOutcome(outcome, ctx);
|
|
24168
|
+
} catch (err) {
|
|
24169
|
+
spinner2.stop();
|
|
24170
|
+
printError(err, ctx);
|
|
24171
|
+
}
|
|
24172
|
+
return;
|
|
24173
|
+
}
|
|
24174
|
+
if (forcedProvider && !forcedSpec) {
|
|
24175
|
+
console.log();
|
|
24176
|
+
console.log(" " + chalk57.red(`Unknown provider: ${forcedProvider}`));
|
|
24177
|
+
console.log(
|
|
24178
|
+
" " + chalk57.dim("Built-ins: anthropic, openai, google, groq, mistral, deepseek, xai, openrouter, together, fireworks, ollama")
|
|
24179
|
+
);
|
|
24180
|
+
console.log(" " + chalk57.dim(`Custom endpoint: /connect --base-url <url> --id ${forcedProvider}`));
|
|
24181
|
+
console.log();
|
|
24182
|
+
if (ctx.oneShot) process.exit(1);
|
|
24183
|
+
return;
|
|
24184
|
+
}
|
|
24185
|
+
let key = inlineKey;
|
|
24186
|
+
let session;
|
|
24187
|
+
if (!key) {
|
|
24188
|
+
if (ctx.oneShot || !process.stdin.isTTY) {
|
|
24189
|
+
printError(new ConnectError("Non-interactive mode needs --key <key>."), ctx);
|
|
24190
|
+
usage2();
|
|
24191
|
+
return;
|
|
24192
|
+
}
|
|
24193
|
+
session = createPromptSession(ctx.rl, ctx);
|
|
24194
|
+
key = await promptKey(
|
|
24195
|
+
session,
|
|
24196
|
+
forcedSpec ? `${forcedSpec.label} API key` : "LLM API key (any provider)"
|
|
24197
|
+
);
|
|
24198
|
+
}
|
|
24199
|
+
const spinner = ora17({ text: "Identifying provider\u2026", discardStdin: false }).start();
|
|
24200
|
+
try {
|
|
24201
|
+
const outcome = await connectWithKey(key, {
|
|
24202
|
+
providerId: forcedSpec?.id,
|
|
24203
|
+
callbacks: session ? {
|
|
24204
|
+
confirmDetection: async (providerId) => {
|
|
24205
|
+
spinner.stop();
|
|
24206
|
+
return session.confirm(`Detected ${providerLabel(providerId)} \u2014 connect it?`, true);
|
|
24207
|
+
},
|
|
24208
|
+
chooseProvider: async (accepted) => {
|
|
24209
|
+
spinner.stop();
|
|
24210
|
+
return session.choose(
|
|
24211
|
+
"Multiple providers accepted this key \u2014 which is it?",
|
|
24212
|
+
accepted.map((a) => ({ value: a.provider, label: providerLabel(a.provider) }))
|
|
24213
|
+
);
|
|
24214
|
+
}
|
|
24215
|
+
} : void 0
|
|
24216
|
+
});
|
|
24217
|
+
spinner.stop();
|
|
24218
|
+
printOutcome(outcome, ctx);
|
|
24219
|
+
} catch (err) {
|
|
24220
|
+
spinner.stop();
|
|
24221
|
+
if (err instanceof ConnectCancelled) {
|
|
24222
|
+
console.log(" " + chalk57.dim("Cancelled."));
|
|
24223
|
+
console.log();
|
|
24224
|
+
} else {
|
|
24225
|
+
printError(err, ctx);
|
|
24226
|
+
}
|
|
24227
|
+
} finally {
|
|
24228
|
+
session?.close();
|
|
24229
|
+
}
|
|
24230
|
+
}
|
|
24231
|
+
function hostToId(url) {
|
|
24232
|
+
try {
|
|
24233
|
+
const host = new URL(url).hostname;
|
|
24234
|
+
return host.replace(/^www\./, "").split(".")[0] ?? "custom";
|
|
24235
|
+
} catch {
|
|
24236
|
+
return "custom";
|
|
24237
|
+
}
|
|
24238
|
+
}
|
|
24239
|
+
var init_connect2 = __esm({
|
|
24240
|
+
"src/commands/connect.ts"() {
|
|
24241
|
+
"use strict";
|
|
24242
|
+
init_argparse();
|
|
24243
|
+
init_prompts();
|
|
24244
|
+
init_providers();
|
|
24245
|
+
init_session_state();
|
|
24246
|
+
init_connect();
|
|
24247
|
+
init_theme();
|
|
24248
|
+
}
|
|
24249
|
+
});
|
|
24250
|
+
|
|
24251
|
+
// src/commands/provider.ts
|
|
24252
|
+
var provider_exports = {};
|
|
24253
|
+
__export(provider_exports, {
|
|
24254
|
+
handler: () => handler41
|
|
24255
|
+
});
|
|
24256
|
+
import chalk58 from "chalk";
|
|
24257
|
+
async function handler41(args, ctx) {
|
|
23038
24258
|
const { positional, flags } = parseArgs2(args, ["default"]);
|
|
23039
24259
|
const sub = positional[0]?.toLowerCase();
|
|
23040
24260
|
if (!sub || sub === "list") {
|
|
@@ -23046,7 +24266,7 @@ async function handler40(args, ctx) {
|
|
|
23046
24266
|
if (!ctx.oneShot) saveSessionState(ctx);
|
|
23047
24267
|
console.log();
|
|
23048
24268
|
console.log(" " + paint("success", "\u2713") + " Session engine reset \u2014 using config defaults.");
|
|
23049
|
-
console.log(" " +
|
|
24269
|
+
console.log(" " + chalk58.dim(`Default: ${loadLlmConfig().primary}`));
|
|
23050
24270
|
console.log();
|
|
23051
24271
|
return;
|
|
23052
24272
|
}
|
|
@@ -23059,7 +24279,7 @@ async function handler40(args, ctx) {
|
|
|
23059
24279
|
setConfigValue("llm-auto-failover", session.autoFailover ? "on" : "off");
|
|
23060
24280
|
}
|
|
23061
24281
|
console.log();
|
|
23062
|
-
console.log(" " + paint("success", "\u2713") + ` Saved ${
|
|
24282
|
+
console.log(" " + paint("success", "\u2713") + ` Saved ${chalk58.bold(active)} as default engine.`);
|
|
23063
24283
|
console.log();
|
|
23064
24284
|
return;
|
|
23065
24285
|
}
|
|
@@ -23078,36 +24298,40 @@ async function handler40(args, ctx) {
|
|
|
23078
24298
|
}
|
|
23079
24299
|
console.log();
|
|
23080
24300
|
console.log(
|
|
23081
|
-
" " + paint("success", "\u2713") + ` Auto-failover ${session.autoFailover ?
|
|
24301
|
+
" " + paint("success", "\u2713") + ` Auto-failover ${session.autoFailover ? chalk58.bold("on") : chalk58.bold("off")} for this session.`
|
|
23082
24302
|
);
|
|
23083
|
-
if (persist) console.log(" " +
|
|
24303
|
+
if (persist) console.log(" " + chalk58.dim("Also saved as config default."));
|
|
23084
24304
|
console.log();
|
|
23085
24305
|
return;
|
|
23086
24306
|
}
|
|
23087
|
-
|
|
24307
|
+
const spec = getProviderSpec(sub);
|
|
24308
|
+
if (!spec || RESERVED.has(sub)) {
|
|
23088
24309
|
console.log();
|
|
23089
|
-
console.log(" " +
|
|
23090
|
-
console.log(" " +
|
|
24310
|
+
console.log(" " + chalk58.red(`Unknown engine: ${sub}`));
|
|
24311
|
+
console.log(" " + chalk58.dim("Usage: /provider [<id>|list|reset|save|failover on|off]"));
|
|
24312
|
+
console.log(" " + chalk58.dim("Connected: ") + (availableEngineLabels().join(", ") || chalk58.dim("none")));
|
|
24313
|
+
console.log(" " + chalk58.dim("Add one with ") + paint("accent", "/connect"));
|
|
23091
24314
|
console.log();
|
|
23092
24315
|
return;
|
|
23093
24316
|
}
|
|
23094
|
-
const provider =
|
|
24317
|
+
const provider = spec.id;
|
|
23095
24318
|
if (!hasProviderKey(provider)) {
|
|
23096
|
-
const keyHint = provider === "anthropic" ? "api-key" : "openai-api-key";
|
|
23097
24319
|
console.log();
|
|
23098
|
-
console.log(" " +
|
|
23099
|
-
console.log(
|
|
24320
|
+
console.log(" " + chalk58.red(`${spec.label} isn't connected.`));
|
|
24321
|
+
console.log(
|
|
24322
|
+
" " + chalk58.dim("Run ") + paint("accent", `/connect ${provider}`) + chalk58.dim(" (or ") + paint("accent", `/config set ${spec.key_config_name}`) + chalk58.dim(").")
|
|
24323
|
+
);
|
|
23100
24324
|
console.log();
|
|
23101
24325
|
return;
|
|
23102
24326
|
}
|
|
23103
24327
|
ensureLlmSession(ctx).provider = provider;
|
|
23104
24328
|
if (!ctx.oneShot) saveSessionState(ctx);
|
|
23105
24329
|
console.log();
|
|
23106
|
-
console.log(" " + paint("success", "\u2713") + ` Active engine: ${
|
|
23107
|
-
console.log(" " +
|
|
24330
|
+
console.log(" " + paint("success", "\u2713") + ` Active engine: ${chalk58.bold(provider)}`);
|
|
24331
|
+
console.log(" " + chalk58.dim(`Stack: ${formatActiveStack(ctx)}`));
|
|
23108
24332
|
const others = availableEngineLabels().filter((p) => p !== provider);
|
|
23109
24333
|
if (others.length > 0) {
|
|
23110
|
-
console.log(" " +
|
|
24334
|
+
console.log(" " + chalk58.dim(`Also available: ${others.join(", ")}`));
|
|
23111
24335
|
}
|
|
23112
24336
|
console.log();
|
|
23113
24337
|
}
|
|
@@ -23118,49 +24342,54 @@ function printStatus(ctx) {
|
|
|
23118
24342
|
const autoFailover = resolveAutoFailoverEnabled(ctx);
|
|
23119
24343
|
const engines = countAvailableEngines();
|
|
23120
24344
|
console.log();
|
|
23121
|
-
console.log(
|
|
23122
|
-
console.log(`
|
|
23123
|
-
|
|
23124
|
-
|
|
23125
|
-
const marker2 =
|
|
23126
|
-
console.log(` ${
|
|
24345
|
+
console.log(chalk58.bold(" LLM engines"));
|
|
24346
|
+
console.log(` Connected: ${engines} engine${engines === 1 ? "" : "s"}`);
|
|
24347
|
+
const configured = listProviderSpecs().filter((s) => hasProviderKey(s.id));
|
|
24348
|
+
for (const s of configured) {
|
|
24349
|
+
const marker2 = s.id === active ? paint("accent", " \u25BA active") : "";
|
|
24350
|
+
console.log(` ${paint("success", "\u2713")} ${s.id}${s.custom ? chalk58.dim(" (custom)") : ""}${marker2}`);
|
|
24351
|
+
}
|
|
24352
|
+
if (configured.length === 0) {
|
|
24353
|
+
console.log(" " + chalk58.dim("none \u2014 run /connect and paste any provider key"));
|
|
23127
24354
|
}
|
|
23128
24355
|
console.log();
|
|
23129
|
-
console.log(
|
|
24356
|
+
console.log(chalk58.bold(" Active stack"));
|
|
23130
24357
|
console.log(` ${formatActiveStack(ctx)}`);
|
|
23131
24358
|
if (sessionOverride) {
|
|
23132
|
-
console.log(
|
|
24359
|
+
console.log(chalk58.dim(" (session override \u2014 /provider reset to use default)"));
|
|
23133
24360
|
} else {
|
|
23134
|
-
console.log(
|
|
24361
|
+
console.log(chalk58.dim(` (config default: ${cfg.primary})`));
|
|
23135
24362
|
}
|
|
23136
24363
|
console.log();
|
|
23137
|
-
console.log(` Auto-failover: ${autoFailover ? paint("success", "on") :
|
|
23138
|
-
console.log(
|
|
23139
|
-
console.log(
|
|
23140
|
-
console.log(
|
|
24364
|
+
console.log(` Auto-failover: ${autoFailover ? paint("success", "on") : chalk58.dim("off")}`);
|
|
24365
|
+
console.log(chalk58.dim(` /provider <id> \u2014 switch engine (${configured.map((s) => s.id).join(", ") || "none connected"})`));
|
|
24366
|
+
console.log(chalk58.dim(" /provider failover on|off \u2014 rate-limit safety net"));
|
|
24367
|
+
console.log(chalk58.dim(" /provider save \u2014 persist active engine to config"));
|
|
24368
|
+
console.log(chalk58.dim(" /connect \u2014 add another provider (any API key)"));
|
|
23141
24369
|
console.log();
|
|
23142
24370
|
}
|
|
23143
|
-
var
|
|
24371
|
+
var RESERVED;
|
|
23144
24372
|
var init_provider = __esm({
|
|
23145
24373
|
"src/commands/provider.ts"() {
|
|
23146
24374
|
"use strict";
|
|
23147
24375
|
init_argparse();
|
|
23148
24376
|
init_session_state();
|
|
24377
|
+
init_providers();
|
|
23149
24378
|
init_llm_config();
|
|
23150
24379
|
init_store();
|
|
23151
24380
|
init_context2();
|
|
23152
24381
|
init_theme();
|
|
23153
|
-
|
|
24382
|
+
RESERVED = /* @__PURE__ */ new Set(["list", "reset", "save", "failover"]);
|
|
23154
24383
|
}
|
|
23155
24384
|
});
|
|
23156
24385
|
|
|
23157
24386
|
// src/commands/tier.ts
|
|
23158
24387
|
var tier_exports = {};
|
|
23159
24388
|
__export(tier_exports, {
|
|
23160
|
-
handler: () =>
|
|
24389
|
+
handler: () => handler42
|
|
23161
24390
|
});
|
|
23162
|
-
import
|
|
23163
|
-
async function
|
|
24391
|
+
import chalk59 from "chalk";
|
|
24392
|
+
async function handler42(args, ctx) {
|
|
23164
24393
|
const { positional, flags } = parseArgs2(args, ["default"]);
|
|
23165
24394
|
const sub = positional[0]?.toLowerCase();
|
|
23166
24395
|
if (!sub || sub === "list") {
|
|
@@ -23169,8 +24398,8 @@ async function handler41(args, ctx) {
|
|
|
23169
24398
|
}
|
|
23170
24399
|
if (!TIERS.includes(sub)) {
|
|
23171
24400
|
console.log();
|
|
23172
|
-
console.log(" " +
|
|
23173
|
-
console.log(" " +
|
|
24401
|
+
console.log(" " + chalk59.red(`Unknown tier: ${sub}`));
|
|
24402
|
+
console.log(" " + chalk59.dim("Usage: /tier [high|medium|low|list] [--default]"));
|
|
23174
24403
|
console.log();
|
|
23175
24404
|
return;
|
|
23176
24405
|
}
|
|
@@ -23184,40 +24413,48 @@ async function handler41(args, ctx) {
|
|
|
23184
24413
|
}
|
|
23185
24414
|
console.log();
|
|
23186
24415
|
console.log(
|
|
23187
|
-
" " + paint("success", "\u2713") + ` Inference tier set to ${
|
|
24416
|
+
" " + paint("success", "\u2713") + ` Inference tier set to ${chalk59.bold(tier.toUpperCase())}` + (persist ? chalk59.dim(" (saved as default)") : chalk59.dim(" (this session)"))
|
|
23188
24417
|
);
|
|
23189
|
-
console.log(" " +
|
|
24418
|
+
console.log(" " + chalk59.dim(`Stack: ${formatActiveStack(ctx)}`));
|
|
23190
24419
|
console.log();
|
|
23191
24420
|
}
|
|
23192
24421
|
function printCatalog(ctx) {
|
|
23193
24422
|
const cfg = loadLlmConfig();
|
|
23194
24423
|
const active = resolveModelForActive(ctx, "agentic_investigation");
|
|
23195
24424
|
const sessionTier = ctx.llm?.tier;
|
|
24425
|
+
const providers = getAvailableProviders();
|
|
23196
24426
|
console.log();
|
|
23197
|
-
console.log(
|
|
24427
|
+
console.log(chalk59.bold(" Inference settings"));
|
|
23198
24428
|
console.log(` Active: ${paint("accent", formatActiveStack(ctx))}`);
|
|
23199
24429
|
if (sessionTier) {
|
|
23200
|
-
console.log(
|
|
24430
|
+
console.log(chalk59.dim(" (session tier override)"));
|
|
23201
24431
|
} else {
|
|
23202
|
-
console.log(
|
|
24432
|
+
console.log(chalk59.dim(` Config default tier: ${cfg.tier.toUpperCase()}`));
|
|
23203
24433
|
}
|
|
23204
24434
|
console.log();
|
|
24435
|
+
if (providers.length === 0) {
|
|
24436
|
+
console.log(" " + chalk59.dim("No engines connected \u2014 run /connect and paste any provider key."));
|
|
24437
|
+
console.log();
|
|
24438
|
+
}
|
|
23205
24439
|
for (const tier of TIERS) {
|
|
23206
|
-
console.log(
|
|
23207
|
-
for (const provider of
|
|
23208
|
-
const
|
|
23209
|
-
|
|
23210
|
-
|
|
23211
|
-
|
|
23212
|
-
const status = m.status === "active" ? "" : chalk58.yellow(` [${m.status}]`);
|
|
23213
|
-
console.log(`${marker2}${provider}/${m.id}${status} \u2014 ${m.display_name}`);
|
|
24440
|
+
console.log(chalk59.bold(` ${tier.toUpperCase()}`));
|
|
24441
|
+
for (const provider of providers) {
|
|
24442
|
+
const modelId = resolveModelSafe(provider, tier);
|
|
24443
|
+
if (!modelId) {
|
|
24444
|
+
console.log(` ${provider}/${chalk59.dim("no models \u2014 /model refresh")}`);
|
|
24445
|
+
continue;
|
|
23214
24446
|
}
|
|
24447
|
+
const isActive = provider === active.provider && tier === active.tier && modelId === active.modelId;
|
|
24448
|
+
const marker2 = isActive ? paint("accent", "\u25BA ") : " ";
|
|
24449
|
+
const discovered = !!getProviderModels(provider);
|
|
24450
|
+
const source = discovered ? "" : chalk59.dim(" [bundled fallback]");
|
|
24451
|
+
console.log(`${marker2}${provider}/${modelId}${source}`);
|
|
23215
24452
|
}
|
|
23216
24453
|
console.log();
|
|
23217
24454
|
}
|
|
23218
|
-
console.log(
|
|
23219
|
-
console.log(
|
|
23220
|
-
console.log(
|
|
24455
|
+
console.log(chalk59.dim(" /tier high|medium|low \u2014 set tier for this session"));
|
|
24456
|
+
console.log(chalk59.dim(" /tier high --default \u2014 also save as config default"));
|
|
24457
|
+
console.log(chalk59.dim(" /provider <id> \u2014 switch engine \xB7 /model list \u2014 browse models"));
|
|
23221
24458
|
console.log();
|
|
23222
24459
|
}
|
|
23223
24460
|
var TIERS;
|
|
@@ -23226,6 +24463,7 @@ var init_tier = __esm({
|
|
|
23226
24463
|
"use strict";
|
|
23227
24464
|
init_argparse();
|
|
23228
24465
|
init_catalog();
|
|
24466
|
+
init_models_cache();
|
|
23229
24467
|
init_session_state();
|
|
23230
24468
|
init_llm_config();
|
|
23231
24469
|
init_store();
|
|
@@ -23238,12 +24476,21 @@ var init_tier = __esm({
|
|
|
23238
24476
|
// src/commands/model.ts
|
|
23239
24477
|
var model_exports = {};
|
|
23240
24478
|
__export(model_exports, {
|
|
23241
|
-
handler: () =>
|
|
24479
|
+
handler: () => handler43
|
|
23242
24480
|
});
|
|
23243
|
-
import
|
|
23244
|
-
|
|
23245
|
-
|
|
24481
|
+
import chalk60 from "chalk";
|
|
24482
|
+
import ora18 from "ora";
|
|
24483
|
+
async function handler43(args, ctx) {
|
|
24484
|
+
const { positional, flags } = parseArgs2(args, ["default", "all"]);
|
|
23246
24485
|
const sub = positional[0]?.toLowerCase();
|
|
24486
|
+
if (sub === "list") {
|
|
24487
|
+
printModelList(ctx, getBool(flags, "all"));
|
|
24488
|
+
return;
|
|
24489
|
+
}
|
|
24490
|
+
if (sub === "refresh") {
|
|
24491
|
+
await refreshModels(ctx);
|
|
24492
|
+
return;
|
|
24493
|
+
}
|
|
23247
24494
|
if (sub === "clear") {
|
|
23248
24495
|
const persist = getBool(flags, "default");
|
|
23249
24496
|
if (ctx.llm) ctx.llm.modelOverride = void 0;
|
|
@@ -23251,7 +24498,7 @@ async function handler42(args, ctx) {
|
|
|
23251
24498
|
if (!ctx.oneShot) saveSessionState(ctx);
|
|
23252
24499
|
console.log();
|
|
23253
24500
|
console.log(" " + paint("success", "\u2713") + " Model override cleared \u2014 using tier defaults.");
|
|
23254
|
-
console.log(" " +
|
|
24501
|
+
console.log(" " + chalk60.dim(`Stack: ${formatActiveStack(ctx)}`));
|
|
23255
24502
|
console.log();
|
|
23256
24503
|
return;
|
|
23257
24504
|
}
|
|
@@ -23259,22 +24506,28 @@ async function handler42(args, ctx) {
|
|
|
23259
24506
|
const modelId = positional[1];
|
|
23260
24507
|
if (!modelId) {
|
|
23261
24508
|
console.log();
|
|
23262
|
-
console.log(" " +
|
|
24509
|
+
console.log(" " + chalk60.red("Usage: /model set <model-id> [--default]"));
|
|
23263
24510
|
console.log();
|
|
23264
24511
|
return;
|
|
23265
24512
|
}
|
|
23266
24513
|
const active = resolveActiveProvider(ctx);
|
|
23267
24514
|
const providerErr = validateModelForProvider(modelId, active);
|
|
23268
|
-
const entry = getCatalogEntry(modelId);
|
|
23269
24515
|
if (providerErr) {
|
|
23270
24516
|
console.log();
|
|
23271
|
-
console.log(" " +
|
|
24517
|
+
console.log(" " + chalk60.red(providerErr));
|
|
23272
24518
|
console.log();
|
|
23273
24519
|
return;
|
|
23274
24520
|
}
|
|
23275
|
-
|
|
24521
|
+
const cache2 = getProviderModels(active);
|
|
24522
|
+
const known = cache2?.models.some((m) => m.id === modelId);
|
|
24523
|
+
if (cache2 && !known) {
|
|
24524
|
+
console.log();
|
|
24525
|
+
console.log(
|
|
24526
|
+
" " + chalk60.yellow("\u26A0") + ` ${modelId} isn't in ${active}'s discovered list (` + paint("accent", "/model list") + `) \u2014 saving anyway.`
|
|
24527
|
+
);
|
|
24528
|
+
} else if (!cache2) {
|
|
23276
24529
|
console.log();
|
|
23277
|
-
console.log(" " +
|
|
24530
|
+
console.log(" " + chalk60.yellow("\u26A0") + ` No discovered models for ${active} yet (` + paint("accent", "/model refresh") + `) \u2014 saving anyway.`);
|
|
23278
24531
|
}
|
|
23279
24532
|
const persist = getBool(flags, "default");
|
|
23280
24533
|
if (persist) {
|
|
@@ -23285,36 +24538,86 @@ async function handler42(args, ctx) {
|
|
|
23285
24538
|
}
|
|
23286
24539
|
console.log();
|
|
23287
24540
|
console.log(
|
|
23288
|
-
" " + paint("success", "\u2713") + ` Model: ${
|
|
24541
|
+
" " + paint("success", "\u2713") + ` Model: ${chalk60.bold(modelId)}` + (persist ? chalk60.dim(" (saved as default)") : chalk60.dim(" (this session)"))
|
|
23289
24542
|
);
|
|
23290
|
-
console.log(" " +
|
|
24543
|
+
console.log(" " + chalk60.dim(`Stack: ${formatActiveStack(ctx)}`));
|
|
23291
24544
|
console.log();
|
|
23292
24545
|
return;
|
|
23293
24546
|
}
|
|
23294
24547
|
const sessionOverride = ctx.llm?.modelOverride;
|
|
23295
24548
|
const globalOverride = getConfigValue("llm-model-override");
|
|
23296
24549
|
console.log();
|
|
23297
|
-
console.log(
|
|
24550
|
+
console.log(chalk60.bold(" Model"));
|
|
23298
24551
|
if (sessionOverride) {
|
|
23299
24552
|
console.log(` Session override: ${paint("accent", sessionOverride)}`);
|
|
23300
24553
|
} else if (globalOverride) {
|
|
23301
24554
|
console.log(` Config default: ${paint("accent", globalOverride)}`);
|
|
23302
24555
|
} else {
|
|
23303
|
-
console.log(" " +
|
|
24556
|
+
console.log(" " + chalk60.dim("No override \u2014 tier defaults apply."));
|
|
23304
24557
|
}
|
|
23305
24558
|
console.log(` Active stack: ${formatActiveStack(ctx)}`);
|
|
23306
|
-
console.log(
|
|
24559
|
+
console.log(chalk60.dim(" /model list \xB7 /model set <id> \xB7 /model refresh \xB7 /model clear"));
|
|
24560
|
+
console.log();
|
|
24561
|
+
}
|
|
24562
|
+
function tierMarkers(cache2, modelId) {
|
|
24563
|
+
const tiers = Object.entries(cache2.tier_stack).filter(([, id]) => id === modelId).map(([tier]) => tier.toUpperCase());
|
|
24564
|
+
return tiers.length > 0 ? paint("accent", ` \u25C2 ${tiers.join("/")}`) : "";
|
|
24565
|
+
}
|
|
24566
|
+
function printModelList(ctx, showAll) {
|
|
24567
|
+
const active = resolveActiveProvider(ctx);
|
|
24568
|
+
const cache2 = getProviderModels(active);
|
|
24569
|
+
console.log();
|
|
24570
|
+
console.log(chalk60.bold(` Models \u2014 ${active}`));
|
|
24571
|
+
if (!cache2) {
|
|
24572
|
+
console.log(" " + chalk60.dim("Nothing discovered yet."));
|
|
24573
|
+
console.log(" " + chalk60.dim("Run ") + paint("accent", "/model refresh") + chalk60.dim(" (or ") + paint("accent", "/connect") + chalk60.dim(" to add the provider)."));
|
|
24574
|
+
console.log();
|
|
24575
|
+
return;
|
|
24576
|
+
}
|
|
24577
|
+
const fetchedAt = cache2.fetched_at.slice(0, 10);
|
|
24578
|
+
console.log(" " + chalk60.dim(`${cache2.models.length} chat models \xB7 discovered ${fetchedAt} \xB7 /model refresh to update`));
|
|
24579
|
+
console.log();
|
|
24580
|
+
const models = showAll ? cache2.models : cache2.models.slice(0, LIST_LIMIT);
|
|
24581
|
+
const noTools = new Set(cache2.quirks?.no_tools ?? []);
|
|
24582
|
+
for (const m of models) {
|
|
24583
|
+
const name = m.display_name && m.display_name !== m.id ? chalk60.dim(` \u2014 ${m.display_name}`) : "";
|
|
24584
|
+
const quirk = noTools.has(m.id) ? chalk60.yellow(" [no tools]") : "";
|
|
24585
|
+
console.log(` ${m.id}${name}${tierMarkers(cache2, m.id)}${quirk}`);
|
|
24586
|
+
}
|
|
24587
|
+
if (!showAll && cache2.models.length > models.length) {
|
|
24588
|
+
console.log(" " + chalk60.dim(`\u2026 and ${cache2.models.length - models.length} more (/model list --all)`));
|
|
24589
|
+
}
|
|
24590
|
+
console.log();
|
|
24591
|
+
console.log(" " + chalk60.dim("/model set <id> \u2014 pin one for this session (--default to persist)"));
|
|
24592
|
+
console.log();
|
|
24593
|
+
}
|
|
24594
|
+
async function refreshModels(ctx) {
|
|
24595
|
+
const active = resolveActiveProvider(ctx);
|
|
24596
|
+
const spinner = ora18({ text: `Discovering ${active} models\u2026`, discardStdin: false }).start();
|
|
24597
|
+
const entry = await refreshProviderModels(active, { force: true });
|
|
24598
|
+
if (!entry) {
|
|
24599
|
+
spinner.fail(`Couldn't reach ${active} to refresh models.`);
|
|
24600
|
+
console.log(" " + chalk60.dim("Check your connection and key, then retry. Cached models remain in use."));
|
|
24601
|
+
console.log();
|
|
24602
|
+
return;
|
|
24603
|
+
}
|
|
24604
|
+
spinner.succeed(`${active}: ${entry.models.length} chat models discovered.`);
|
|
24605
|
+
console.log(" " + chalk60.dim(`high ${entry.tier_stack.high} \xB7 medium ${entry.tier_stack.medium} \xB7 low ${entry.tier_stack.low}`));
|
|
24606
|
+
console.log(" " + chalk60.dim(`Stack: ${formatActiveStack(ctx)}`));
|
|
23307
24607
|
console.log();
|
|
23308
24608
|
}
|
|
24609
|
+
var LIST_LIMIT;
|
|
23309
24610
|
var init_model = __esm({
|
|
23310
24611
|
"src/commands/model.ts"() {
|
|
23311
24612
|
"use strict";
|
|
23312
24613
|
init_argparse();
|
|
23313
|
-
|
|
24614
|
+
init_discovery();
|
|
24615
|
+
init_models_cache();
|
|
23314
24616
|
init_session_state();
|
|
23315
24617
|
init_store();
|
|
23316
24618
|
init_context2();
|
|
23317
24619
|
init_theme();
|
|
24620
|
+
LIST_LIMIT = 40;
|
|
23318
24621
|
}
|
|
23319
24622
|
});
|
|
23320
24623
|
|
|
@@ -23326,22 +24629,22 @@ __export(update_check_exports, {
|
|
|
23326
24629
|
loadUpdateCheckCache: () => loadUpdateCheckCache,
|
|
23327
24630
|
saveUpdateCheckCache: () => saveUpdateCheckCache
|
|
23328
24631
|
});
|
|
23329
|
-
import { existsSync as
|
|
23330
|
-
import { join as
|
|
23331
|
-
function
|
|
23332
|
-
return
|
|
24632
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync17, unlinkSync as unlinkSync5, writeFileSync as writeFileSync19 } from "fs";
|
|
24633
|
+
import { join as join28 } from "path";
|
|
24634
|
+
function cachePath2() {
|
|
24635
|
+
return join28(ntrpHome(), "update-check.json");
|
|
23333
24636
|
}
|
|
23334
24637
|
function ensureDir7() {
|
|
23335
24638
|
const dir = ntrpHome();
|
|
23336
|
-
if (!
|
|
24639
|
+
if (!existsSync21(dir)) {
|
|
23337
24640
|
mkdirSync12(dir, { recursive: true });
|
|
23338
24641
|
}
|
|
23339
24642
|
}
|
|
23340
24643
|
function loadUpdateCheckCache() {
|
|
23341
|
-
const path =
|
|
23342
|
-
if (!
|
|
24644
|
+
const path = cachePath2();
|
|
24645
|
+
if (!existsSync21(path)) return null;
|
|
23343
24646
|
try {
|
|
23344
|
-
const parsed = JSON.parse(
|
|
24647
|
+
const parsed = JSON.parse(readFileSync17(path, "utf-8"));
|
|
23345
24648
|
if (!parsed || typeof parsed !== "object" || typeof parsed.lastCheck !== "number" || typeof parsed.latestVersion !== "string") {
|
|
23346
24649
|
return null;
|
|
23347
24650
|
}
|
|
@@ -23352,39 +24655,39 @@ function loadUpdateCheckCache() {
|
|
|
23352
24655
|
}
|
|
23353
24656
|
function saveUpdateCheckCache(cache2) {
|
|
23354
24657
|
ensureDir7();
|
|
23355
|
-
|
|
24658
|
+
writeFileSync19(cachePath2(), JSON.stringify(cache2, null, 2) + "\n");
|
|
23356
24659
|
}
|
|
23357
|
-
function isCacheFresh(cache2, ttlMs =
|
|
24660
|
+
function isCacheFresh(cache2, ttlMs = CACHE_TTL_MS2) {
|
|
23358
24661
|
if (!cache2) return false;
|
|
23359
24662
|
return Date.now() - cache2.lastCheck < ttlMs;
|
|
23360
24663
|
}
|
|
23361
24664
|
function invalidateUpdateCheckCache() {
|
|
23362
|
-
const path =
|
|
23363
|
-
if (
|
|
24665
|
+
const path = cachePath2();
|
|
24666
|
+
if (existsSync21(path)) {
|
|
23364
24667
|
unlinkSync5(path);
|
|
23365
24668
|
}
|
|
23366
24669
|
}
|
|
23367
|
-
var
|
|
24670
|
+
var CACHE_TTL_MS2;
|
|
23368
24671
|
var init_update_check = __esm({
|
|
23369
24672
|
"src/config/update-check.ts"() {
|
|
23370
24673
|
"use strict";
|
|
23371
24674
|
init_store();
|
|
23372
|
-
|
|
24675
|
+
CACHE_TTL_MS2 = 864e5;
|
|
23373
24676
|
}
|
|
23374
24677
|
});
|
|
23375
24678
|
|
|
23376
24679
|
// src/version.ts
|
|
23377
|
-
import { existsSync as
|
|
23378
|
-
import { dirname as dirname4, join as
|
|
24680
|
+
import { existsSync as existsSync22, readFileSync as readFileSync18 } from "fs";
|
|
24681
|
+
import { dirname as dirname4, join as join29 } from "path";
|
|
23379
24682
|
import { fileURLToPath } from "url";
|
|
23380
24683
|
function getInstalledVersion() {
|
|
23381
24684
|
if (cachedVersion) return cachedVersion;
|
|
23382
24685
|
const start = dirname4(fileURLToPath(import.meta.url));
|
|
23383
24686
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
23384
|
-
const path =
|
|
23385
|
-
if (!
|
|
24687
|
+
const path = join29(start, rel);
|
|
24688
|
+
if (!existsSync22(path)) continue;
|
|
23386
24689
|
try {
|
|
23387
|
-
const pkg = JSON.parse(
|
|
24690
|
+
const pkg = JSON.parse(readFileSync18(path, "utf-8"));
|
|
23388
24691
|
if (typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
23389
24692
|
cachedVersion = pkg.version;
|
|
23390
24693
|
return cachedVersion;
|
|
@@ -23450,14 +24753,14 @@ function buildResult(current, latest) {
|
|
|
23450
24753
|
async function checkForUpdate(options) {
|
|
23451
24754
|
const current = getInstalledVersion();
|
|
23452
24755
|
const timeoutMs = options?.timeoutMs ?? 5e3;
|
|
23453
|
-
const
|
|
23454
|
-
if (!options?.force && isCacheFresh(
|
|
23455
|
-
return buildResult(current,
|
|
24756
|
+
const cached2 = loadUpdateCheckCache();
|
|
24757
|
+
if (!options?.force && isCacheFresh(cached2)) {
|
|
24758
|
+
return buildResult(current, cached2.latestVersion);
|
|
23456
24759
|
}
|
|
23457
24760
|
const latest = await fetchLatestVersion(timeoutMs);
|
|
23458
24761
|
if (!latest) {
|
|
23459
|
-
if (
|
|
23460
|
-
return buildResult(current,
|
|
24762
|
+
if (cached2?.latestVersion) {
|
|
24763
|
+
return buildResult(current, cached2.latestVersion);
|
|
23461
24764
|
}
|
|
23462
24765
|
return null;
|
|
23463
24766
|
}
|
|
@@ -23478,10 +24781,10 @@ var init_registry = __esm({
|
|
|
23478
24781
|
// src/commands/update.ts
|
|
23479
24782
|
var update_exports = {};
|
|
23480
24783
|
__export(update_exports, {
|
|
23481
|
-
handler: () =>
|
|
24784
|
+
handler: () => handler44
|
|
23482
24785
|
});
|
|
23483
24786
|
import { spawnSync } from "child_process";
|
|
23484
|
-
import
|
|
24787
|
+
import chalk61 from "chalk";
|
|
23485
24788
|
function tailLines(text, count = 5) {
|
|
23486
24789
|
return text.split("\n").map((line) => line.trimEnd()).filter((line) => line.length > 0).slice(-count).join("\n");
|
|
23487
24790
|
}
|
|
@@ -23497,19 +24800,19 @@ function runGlobalInstall() {
|
|
|
23497
24800
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
23498
24801
|
return { ok: result.status === 0, output };
|
|
23499
24802
|
}
|
|
23500
|
-
async function
|
|
24803
|
+
async function handler44(_args, _ctx) {
|
|
23501
24804
|
const current = getInstalledVersion();
|
|
23502
24805
|
const latest = await fetchLatestVersion(1e4);
|
|
23503
24806
|
if (!latest) {
|
|
23504
24807
|
console.log();
|
|
23505
|
-
console.log(
|
|
23506
|
-
console.log(
|
|
24808
|
+
console.log(chalk61.yellow(" Could not reach the npm registry."));
|
|
24809
|
+
console.log(chalk61.dim(` Try manually: npm install -g ${NPM_PACKAGE}`));
|
|
23507
24810
|
console.log();
|
|
23508
24811
|
return;
|
|
23509
24812
|
}
|
|
23510
24813
|
if (!isNewerVersion(latest, current)) {
|
|
23511
24814
|
console.log();
|
|
23512
|
-
console.log(
|
|
24815
|
+
console.log(chalk61.green(` \u2713 You're on the latest version (v${current})`));
|
|
23513
24816
|
console.log();
|
|
23514
24817
|
return;
|
|
23515
24818
|
}
|
|
@@ -23518,24 +24821,24 @@ async function handler43(_args, _ctx) {
|
|
|
23518
24821
|
const { ok, output } = runGlobalInstall();
|
|
23519
24822
|
if (ok) {
|
|
23520
24823
|
invalidateUpdateCheckCache();
|
|
23521
|
-
console.log(
|
|
24824
|
+
console.log(chalk61.green(` \u2713 Updated! Restart NTRP to use v${latest}`));
|
|
23522
24825
|
console.log();
|
|
23523
24826
|
return;
|
|
23524
24827
|
}
|
|
23525
24828
|
const lower = output.toLowerCase();
|
|
23526
24829
|
if (lower.includes("eacces") || lower.includes("permission denied") || lower.includes("eperm")) {
|
|
23527
|
-
console.log(
|
|
23528
|
-
console.log(
|
|
23529
|
-
console.log(
|
|
24830
|
+
console.log(chalk61.red(` Could not install ${NPM_PACKAGE} (permission denied).`));
|
|
24831
|
+
console.log(chalk61.dim(` Try: sudo npm install -g ${NPM_PACKAGE}`));
|
|
24832
|
+
console.log(chalk61.dim(` Or fix npm global permissions: ${PERMISSIONS_URL}`));
|
|
23530
24833
|
console.log();
|
|
23531
24834
|
return;
|
|
23532
24835
|
}
|
|
23533
24836
|
const detail = tailLines(output);
|
|
23534
|
-
console.log(
|
|
24837
|
+
console.log(chalk61.red(` Could not install ${NPM_PACKAGE}.`));
|
|
23535
24838
|
if (detail) {
|
|
23536
|
-
console.log(
|
|
24839
|
+
console.log(chalk61.dim(` ${detail.split("\n").join("\n ")}`));
|
|
23537
24840
|
}
|
|
23538
|
-
console.log(
|
|
24841
|
+
console.log(chalk61.dim(` Try manually: npm install -g ${NPM_PACKAGE}`));
|
|
23539
24842
|
console.log();
|
|
23540
24843
|
}
|
|
23541
24844
|
var PERMISSIONS_URL;
|
|
@@ -23550,10 +24853,10 @@ var init_update = __esm({
|
|
|
23550
24853
|
});
|
|
23551
24854
|
|
|
23552
24855
|
// src/output/progress-report.ts
|
|
23553
|
-
import
|
|
24856
|
+
import chalk62 from "chalk";
|
|
23554
24857
|
function printCard(title, rows) {
|
|
23555
24858
|
const inner = CARD_W - 4;
|
|
23556
|
-
const border =
|
|
24859
|
+
const border = chalk62.dim;
|
|
23557
24860
|
console.log();
|
|
23558
24861
|
console.log(` ${border(`\u256D${"\u2500".repeat(CARD_W - 2)}\u256E`)}`);
|
|
23559
24862
|
console.log(` ${border("\u2502 ")}${padRight(sectionHeading(title), inner)}${border(" \u2502")}`);
|
|
@@ -23569,7 +24872,7 @@ function formatTokens(n) {
|
|
|
23569
24872
|
return String(n);
|
|
23570
24873
|
}
|
|
23571
24874
|
function sparkline(values) {
|
|
23572
|
-
if (values.length === 0) return
|
|
24875
|
+
if (values.length === 0) return chalk62.dim("(no activity yet)");
|
|
23573
24876
|
const max = Math.max(...values, 1);
|
|
23574
24877
|
const blocks = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
23575
24878
|
return values.map((v) => {
|
|
@@ -23578,7 +24881,7 @@ function sparkline(values) {
|
|
|
23578
24881
|
}).join("");
|
|
23579
24882
|
}
|
|
23580
24883
|
function formatMemberSince(iso) {
|
|
23581
|
-
if (!iso) return
|
|
24884
|
+
if (!iso) return chalk62.dim("\u2014");
|
|
23582
24885
|
const d = new Date(iso);
|
|
23583
24886
|
return d.toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" });
|
|
23584
24887
|
}
|
|
@@ -23595,49 +24898,49 @@ function renderProgressReport() {
|
|
|
23595
24898
|
state.milestones_unlocked.length,
|
|
23596
24899
|
TIME_MILESTONES.length
|
|
23597
24900
|
);
|
|
23598
|
-
const { usage:
|
|
24901
|
+
const { usage: usage3 } = summary;
|
|
23599
24902
|
const nextLabel = bank.next_milestone ? `${formatHoursLabel(bank.total_hours)} \u2192 ${formatHoursLabel(bank.next_milestone.hours)}` : `${formatHoursLabel(bank.total_hours)} saved`;
|
|
23600
24903
|
const bar = inlineBar(bank.progress_pct, 18);
|
|
23601
24904
|
printCard("Progress", [
|
|
23602
|
-
`${
|
|
23603
|
-
`${
|
|
23604
|
-
`${
|
|
23605
|
-
`${
|
|
24905
|
+
`${chalk62.dim("Hours saved")} ${paint("accent", formatHoursLabel(bank.total_hours))} ${bar}`,
|
|
24906
|
+
`${chalk62.dim("Next milestone")} ${bank.next_milestone ? paint("accent", bank.next_milestone.title) : chalk62.dim("top of ladder")}`,
|
|
24907
|
+
`${chalk62.dim("Member since")} ${formatMemberSince(usage3.first_active_at)}`,
|
|
24908
|
+
`${chalk62.dim("Last active")} ${formatMemberSince(usage3.last_active_at)}`
|
|
23606
24909
|
]);
|
|
23607
24910
|
if (bank.perspective_line) {
|
|
23608
|
-
console.log(` ${
|
|
24911
|
+
console.log(` ${chalk62.dim.italic(bank.perspective_line)}`);
|
|
23609
24912
|
}
|
|
23610
24913
|
printCard("Activity", [
|
|
23611
|
-
`${
|
|
23612
|
-
`${
|
|
23613
|
-
`${
|
|
23614
|
-
`${
|
|
23615
|
-
`${
|
|
24914
|
+
`${chalk62.dim("Sessions")} ${chalk62.bold(String(summary.total_sessions_on_disk))} total \xB7 ${summary.sessions_with_work} with work \xB7 ${usage3.sessions_closed} closed`,
|
|
24915
|
+
`${chalk62.dim("Diagnoses")} ${chalk62.bold(String(usage3.diagnoses))}`,
|
|
24916
|
+
`${chalk62.dim("Metrics runs")} ${chalk62.bold(String(usage3.metrics_runs))}`,
|
|
24917
|
+
`${chalk62.dim("Deliverables")} ${chalk62.bold(String(usage3.deliverables))}`,
|
|
24918
|
+
`${chalk62.dim("AI exchanges")} ${chalk62.bold(String(usage3.nl_exchanges))}`
|
|
23616
24919
|
]);
|
|
23617
|
-
const totalTokens =
|
|
24920
|
+
const totalTokens = usage3.input_tokens + usage3.output_tokens;
|
|
23618
24921
|
printCard("AI usage", [
|
|
23619
|
-
`${
|
|
23620
|
-
`${
|
|
24922
|
+
`${chalk62.dim("LLM calls")} ${chalk62.bold(String(usage3.llm_calls))}`,
|
|
24923
|
+
`${chalk62.dim("Tokens")} ${chalk62.bold(formatTokens(totalTokens))} in+out (${formatTokens(usage3.input_tokens)} in \xB7 ${formatTokens(usage3.output_tokens)} out)`
|
|
23621
24924
|
]);
|
|
23622
|
-
const weeks = [...
|
|
24925
|
+
const weeks = [...usage3.weekly].sort((a, b) => a.week.localeCompare(b.week)).slice(-8);
|
|
23623
24926
|
const weekHours = weeks.map((w) => w.minutes_saved / 60);
|
|
23624
24927
|
const weekLabels = weeks.map((w) => w.week.replace(/^\d{4}-/, ""));
|
|
23625
24928
|
console.log();
|
|
23626
24929
|
console.log(` ${sectionHeading("Weekly hours saved")}`);
|
|
23627
24930
|
console.log(` ${sparkline(weekHours)}`);
|
|
23628
24931
|
if (weeks.length > 0) {
|
|
23629
|
-
console.log(` ${
|
|
24932
|
+
console.log(` ${chalk62.dim(weekLabels.join(" "))}`);
|
|
23630
24933
|
}
|
|
23631
24934
|
console.log();
|
|
23632
24935
|
console.log(` ${sectionHeading("Milestone ladder")}`);
|
|
23633
24936
|
for (const m of TIME_MILESTONES) {
|
|
23634
24937
|
const unlocked = state.milestones_unlocked.includes(m.id);
|
|
23635
24938
|
const pct = Math.min(100, bank.total_hours / m.hours * 100);
|
|
23636
|
-
const mark = unlocked ? badge("DONE", "success") : bank.total_hours >= m.hours * 0.85 ? badge("NEAR", "warning") :
|
|
24939
|
+
const mark = unlocked ? badge("DONE", "success") : bank.total_hours >= m.hours * 0.85 ? badge("NEAR", "warning") : chalk62.dim("\u25CB");
|
|
23637
24940
|
const barW = 12;
|
|
23638
|
-
const mBar = unlocked ?
|
|
24941
|
+
const mBar = unlocked ? chalk62.hex("#22c55e")("\u2588".repeat(barW)) : scoreBar(pct, bank.total_hours >= m.hours ? "green" : pct >= 50 ? "yellow" : "red", barW);
|
|
23639
24942
|
const label = `${m.title}`.padEnd(16);
|
|
23640
|
-
console.log(` ${mark} ${
|
|
24943
|
+
console.log(` ${mark} ${chalk62.dim(label)} ${mBar} ${chalk62.dim(`${m.hours}h`)}`);
|
|
23641
24944
|
}
|
|
23642
24945
|
console.log();
|
|
23643
24946
|
}
|
|
@@ -23659,9 +24962,20 @@ var init_progress_report = __esm({
|
|
|
23659
24962
|
// src/commands/progress.ts
|
|
23660
24963
|
var progress_exports = {};
|
|
23661
24964
|
__export(progress_exports, {
|
|
23662
|
-
handler: () =>
|
|
24965
|
+
handler: () => handler45
|
|
23663
24966
|
});
|
|
23664
|
-
|
|
24967
|
+
import chalk63 from "chalk";
|
|
24968
|
+
function printProgressResetPreamble() {
|
|
24969
|
+
console.log();
|
|
24970
|
+
console.log(" " + chalk63.yellow.bold("This will permanently remove:"));
|
|
24971
|
+
console.log(" " + chalk63.dim(" \u2022 Hours saved and milestone unlocks"));
|
|
24972
|
+
console.log(" " + chalk63.dim(" \u2022 Usage counters and weekly activity rollups"));
|
|
24973
|
+
console.log(" " + chalk63.dim(" \u2022 Credit history used for dedup"));
|
|
24974
|
+
console.log();
|
|
24975
|
+
console.log(" " + chalk63.dim("Preserved: install identity (install.json)"));
|
|
24976
|
+
console.log();
|
|
24977
|
+
}
|
|
24978
|
+
function showProgress() {
|
|
23665
24979
|
const bank = getTimeBankSummary();
|
|
23666
24980
|
if (bank.total_minutes <= 0) {
|
|
23667
24981
|
console.log();
|
|
@@ -23672,10 +24986,53 @@ async function handler44(_args, _ctx) {
|
|
|
23672
24986
|
renderProgressReport();
|
|
23673
24987
|
return `Progress: ${bank.total_hours.toFixed(1)}h saved`;
|
|
23674
24988
|
}
|
|
24989
|
+
async function handleReset(ctx, confirmedFlag) {
|
|
24990
|
+
const bank = getTimeBankSummary();
|
|
24991
|
+
if (bank.total_minutes <= 0) {
|
|
24992
|
+
console.log();
|
|
24993
|
+
console.log(" " + chalk63.dim("No progress to reset."));
|
|
24994
|
+
console.log();
|
|
24995
|
+
return "No progress to reset";
|
|
24996
|
+
}
|
|
24997
|
+
const ok = await requireTypedWord(ctx, {
|
|
24998
|
+
title: "Reset progress",
|
|
24999
|
+
word: "reset",
|
|
25000
|
+
confirmedFlag,
|
|
25001
|
+
preamble: printProgressResetPreamble,
|
|
25002
|
+
oneShotHint: "Re-run with: ntrp progress reset --confirm"
|
|
25003
|
+
});
|
|
25004
|
+
if (!ok) {
|
|
25005
|
+
printAdminCancelled("Progress reset", 'Type "reset" exactly to confirm.');
|
|
25006
|
+
return "Progress reset cancelled";
|
|
25007
|
+
}
|
|
25008
|
+
resetProgress();
|
|
25009
|
+
console.log();
|
|
25010
|
+
console.log(" " + paint("accent", "\u2713 Progress reset") + chalk63.dim(" \u2014 hours and milestones cleared."));
|
|
25011
|
+
console.log();
|
|
25012
|
+
return "Progress reset";
|
|
25013
|
+
}
|
|
25014
|
+
async function handler45(args, ctx) {
|
|
25015
|
+
const { positional, flags } = parseArgs2(args, ["confirm"]);
|
|
25016
|
+
const sub = positional[0]?.toLowerCase();
|
|
25017
|
+
if (sub === "reset") {
|
|
25018
|
+
return handleReset(ctx, getBool(flags, "confirm"));
|
|
25019
|
+
}
|
|
25020
|
+
if (sub && sub !== "reset") {
|
|
25021
|
+
console.log();
|
|
25022
|
+
console.log(" " + chalk63.dim("Unknown subcommand. Try ") + paint("accent", "/progress") + chalk63.dim(" or ") + paint("accent", "/progress reset") + chalk63.dim("."));
|
|
25023
|
+
console.log();
|
|
25024
|
+
return;
|
|
25025
|
+
}
|
|
25026
|
+
return showProgress();
|
|
25027
|
+
}
|
|
23675
25028
|
var init_progress2 = __esm({
|
|
23676
25029
|
"src/commands/progress.ts"() {
|
|
23677
25030
|
"use strict";
|
|
25031
|
+
init_argparse();
|
|
25032
|
+
init_admin_confirm();
|
|
25033
|
+
init_progress();
|
|
23678
25034
|
init_progress_report();
|
|
25035
|
+
init_theme();
|
|
23679
25036
|
init_time_bank();
|
|
23680
25037
|
}
|
|
23681
25038
|
});
|
|
@@ -23769,10 +25126,10 @@ async function resolveHandler(name) {
|
|
|
23769
25126
|
try {
|
|
23770
25127
|
const mod = await importHandler(runtimePath);
|
|
23771
25128
|
if (!mod) return null;
|
|
23772
|
-
const
|
|
23773
|
-
if (typeof
|
|
23774
|
-
entry.handler =
|
|
23775
|
-
return
|
|
25129
|
+
const handler46 = mod.handler;
|
|
25130
|
+
if (typeof handler46 !== "function") return null;
|
|
25131
|
+
entry.handler = handler46;
|
|
25132
|
+
return handler46;
|
|
23776
25133
|
} catch (err) {
|
|
23777
25134
|
console.error(`Failed to load handler for /${name}:`, err);
|
|
23778
25135
|
return null;
|
|
@@ -23858,6 +25215,8 @@ async function importHandler(runtimePath) {
|
|
|
23858
25215
|
return Promise.resolve().then(() => (init_switch(), switch_exports));
|
|
23859
25216
|
case "../commands/backmeup.js":
|
|
23860
25217
|
return Promise.resolve().then(() => (init_backmeup(), backmeup_exports));
|
|
25218
|
+
case "../commands/connect.js":
|
|
25219
|
+
return Promise.resolve().then(() => (init_connect2(), connect_exports2));
|
|
23861
25220
|
case "../commands/provider.js":
|
|
23862
25221
|
return Promise.resolve().then(() => (init_provider(), provider_exports));
|
|
23863
25222
|
case "../commands/tier.js":
|
|
@@ -23985,7 +25344,9 @@ handler: ../commands/setup.ts
|
|
|
23985
25344
|
|
|
23986
25345
|
Validate local readiness or configure NTRP non-interactively for automation.
|
|
23987
25346
|
\`setup check --json\` reports license, profile, API key, database, and writable
|
|
23988
|
-
directory state. \`setup agent\` accepts a profile JSON file or direct flags
|
|
25347
|
+
directory state. \`setup agent\` accepts a profile JSON file or direct flags \u2014
|
|
25348
|
+
\`--llm-key <key>\` auto-detects the provider from any pasted key
|
|
25349
|
+
(\`--llm-provider <id>\` to force one).`
|
|
23989
25350
|
},
|
|
23990
25351
|
{
|
|
23991
25352
|
name: "update",
|
|
@@ -24269,11 +25630,13 @@ Export the most recent diagnosis as terminal output, markdown, or JSON. Use
|
|
|
24269
25630
|
name: progress
|
|
24270
25631
|
description: Usage stats and milestone ladder
|
|
24271
25632
|
section: Navigation
|
|
25633
|
+
args: [reset] [--confirm]
|
|
24272
25634
|
handler: ../commands/progress.ts
|
|
24273
25635
|
---
|
|
24274
25636
|
|
|
24275
25637
|
Hours saved, weekly activity trend, session counts, AI token usage, and the
|
|
24276
|
-
full milestone ladder with progress bars
|
|
25638
|
+
full milestone ladder with progress bars. Use reset (type "reset" to confirm)
|
|
25639
|
+
to clear hours and milestones while keeping this install's identity.`
|
|
24277
25640
|
},
|
|
24278
25641
|
{
|
|
24279
25642
|
name: "status",
|
|
@@ -24293,15 +25656,17 @@ diagnosis, if any.`
|
|
|
24293
25656
|
name: scratch
|
|
24294
25657
|
description: Wipe config, profile, and all datasets
|
|
24295
25658
|
section: Admin
|
|
24296
|
-
args: [--confirm]
|
|
25659
|
+
args: [--confirm] [--include-progress]
|
|
24297
25660
|
handler: ../commands/scratch.ts
|
|
24298
25661
|
hidden: true
|
|
24299
25662
|
---
|
|
24300
25663
|
|
|
24301
25664
|
Minimal factory reset: removes API key, config, company profile, all sessions,
|
|
24302
|
-
per-session datasets, and demo taxonomy cache. Preserves progress (hours saved)
|
|
24303
|
-
|
|
24304
|
-
|
|
25665
|
+
per-session datasets, and demo taxonomy cache. Preserves progress (hours saved)
|
|
25666
|
+
by default. Pass \`--include-progress\` to also wipe install identity and hours.
|
|
25667
|
+
Also preserves memory, strategies, wins, knowledge, exports, and audit. Requires
|
|
25668
|
+
typing \`scratch\` in the REPL or passing \`--confirm\` one-shot. Triggers
|
|
25669
|
+
onboarding on next interactive use.`
|
|
24305
25670
|
},
|
|
24306
25671
|
{
|
|
24307
25672
|
name: "cleanup",
|
|
@@ -24417,6 +25782,25 @@ handler: ../commands/profile.ts
|
|
|
24417
25782
|
|
|
24418
25783
|
Choose a sales motion preset (PLG, SMB Velocity, Mid-Market, Enterprise). Each
|
|
24419
25784
|
preset adjusts the vital-sign thresholds to match your deal cycle.`
|
|
25785
|
+
},
|
|
25786
|
+
{
|
|
25787
|
+
name: "connect",
|
|
25788
|
+
raw: `---
|
|
25789
|
+
name: connect
|
|
25790
|
+
description: Connect an AI provider (paste any key)
|
|
25791
|
+
section: Settings
|
|
25792
|
+
args: [provider] [--key <key>] [--base-url <url> --id <name>]
|
|
25793
|
+
handler: ../commands/connect.ts
|
|
25794
|
+
---
|
|
25795
|
+
|
|
25796
|
+
Paste any provider's API key \u2014 NTRP identifies the provider from the key
|
|
25797
|
+
format (probing ambiguous ones), validates it, discovers which models the key
|
|
25798
|
+
can use, and builds the HIGH/MEDIUM/LOW tier stack automatically.
|
|
25799
|
+
|
|
25800
|
+
Works with Anthropic, OpenAI, Google Gemini, Groq, Mistral, DeepSeek, xAI,
|
|
25801
|
+
OpenRouter, Together, and Fireworks out of the box. \`/connect ollama\` wires a
|
|
25802
|
+
local Ollama; \`/connect --base-url <url> --id <name>\` registers any other
|
|
25803
|
+
OpenAI-compatible endpoint.`
|
|
24420
25804
|
},
|
|
24421
25805
|
{
|
|
24422
25806
|
name: "config",
|
|
@@ -24429,10 +25813,12 @@ handler: ../commands/config.ts
|
|
|
24429
25813
|
---
|
|
24430
25814
|
|
|
24431
25815
|
Manage CLI configuration stored at \`~/.ntrp/config.json\`. Useful keys:
|
|
24432
|
-
\`api-key\` (Anthropic), \`openai-api-key\`, \`
|
|
24433
|
-
\`llm-tier\`, \`llm-auto-failover\`,
|
|
25816
|
+
\`api-key\` (Anthropic), \`openai-api-key\` (and \`groq-api-key\`, \`google-api-key\`, ...),
|
|
25817
|
+
\`llm-primary\` (default engine), \`llm-tier\`, \`llm-auto-failover\`,
|
|
25818
|
+
\`default-format\`, \`export-dir\`.
|
|
24434
25819
|
|
|
24435
|
-
|
|
25820
|
+
Setting a provider key opens a hidden prompt and auto-discovers that
|
|
25821
|
+
provider's models. Prefer \`/connect\` \u2014 it detects the provider for you.`
|
|
24436
25822
|
},
|
|
24437
25823
|
{
|
|
24438
25824
|
name: "provider",
|
|
@@ -24440,13 +25826,14 @@ Manage CLI configuration stored at \`~/.ntrp/config.json\`. Useful keys:
|
|
|
24440
25826
|
name: provider
|
|
24441
25827
|
description: Switch active LLM engine
|
|
24442
25828
|
section: Settings
|
|
24443
|
-
args: [
|
|
25829
|
+
args: [<id>|list|reset|save|failover on|off]
|
|
24444
25830
|
handler: ../commands/provider.ts
|
|
24445
25831
|
---
|
|
24446
25832
|
|
|
24447
|
-
Choose which engine answers this session \u2014
|
|
24448
|
-
|
|
24449
|
-
|
|
25833
|
+
Choose which connected engine answers this session \u2014 any provider added via
|
|
25834
|
+
\`/connect\` (anthropic, openai, groq, google, ollama, custom endpoints, ...).
|
|
25835
|
+
Session-scoped by default; \`/provider save\` writes the default to config.
|
|
25836
|
+
\`/provider failover on\` enables rate-limit auto-failover.`
|
|
24450
25837
|
},
|
|
24451
25838
|
{
|
|
24452
25839
|
name: "tier",
|
|
@@ -24468,12 +25855,14 @@ active stack. Add \`--default\` to persist to config.`
|
|
|
24468
25855
|
name: model
|
|
24469
25856
|
description: Override the active LLM model
|
|
24470
25857
|
section: Settings
|
|
24471
|
-
args: [set <id>|clear] [--default]
|
|
25858
|
+
args: [list|set <id>|refresh|clear] [--default]
|
|
24472
25859
|
handler: ../commands/model.ts
|
|
24473
25860
|
---
|
|
24474
25861
|
|
|
24475
|
-
|
|
24476
|
-
|
|
25862
|
+
\`/model list\` shows the models discovered for the active engine with their
|
|
25863
|
+
tier assignments. \`/model refresh\` re-discovers the live list. \`/model set <id>\`
|
|
25864
|
+
pins a model on the **active engine**; cross-provider IDs are rejected \u2014
|
|
25865
|
+
switch with \`/provider\` first.`
|
|
24477
25866
|
},
|
|
24478
25867
|
{
|
|
24479
25868
|
name: "activate",
|
|
@@ -24532,7 +25921,7 @@ paragraph that flows into all AI surfaces.`
|
|
|
24532
25921
|
});
|
|
24533
25922
|
|
|
24534
25923
|
// src/license/activation.ts
|
|
24535
|
-
import
|
|
25924
|
+
import chalk64 from "chalk";
|
|
24536
25925
|
function hasValidLicense() {
|
|
24537
25926
|
return checkLicense().valid;
|
|
24538
25927
|
}
|
|
@@ -24540,10 +25929,10 @@ async function ensureLicenseActivated(ctx) {
|
|
|
24540
25929
|
if (hasValidLicense()) return false;
|
|
24541
25930
|
if (!process.stdin.isTTY) {
|
|
24542
25931
|
console.error();
|
|
24543
|
-
console.error(
|
|
24544
|
-
console.error(
|
|
24545
|
-
console.error(
|
|
24546
|
-
console.error(
|
|
25932
|
+
console.error(chalk64.red(" A license key is required."));
|
|
25933
|
+
console.error(chalk64.dim(` Sign up: ${getCheckoutUrl()}`));
|
|
25934
|
+
console.error(chalk64.dim(" Then run: ntrp activate <key>"));
|
|
25935
|
+
console.error(chalk64.dim(" Or set NTRP_LICENSE_KEY for headless use."));
|
|
24547
25936
|
console.error();
|
|
24548
25937
|
process.exit(1);
|
|
24549
25938
|
}
|
|
@@ -24553,7 +25942,7 @@ async function ensureLicenseActivated(ctx) {
|
|
|
24553
25942
|
}
|
|
24554
25943
|
printCenteredLogo();
|
|
24555
25944
|
console.log(" " + bold("Activate your license"));
|
|
24556
|
-
console.log(" " +
|
|
25945
|
+
console.log(" " + chalk64.dim("Don't have a key yet? Sign up (free trial or Pro), then paste it below."));
|
|
24557
25946
|
console.log();
|
|
24558
25947
|
await promptOpenCheckout(ctx);
|
|
24559
25948
|
return promptForLicenseKey(ctx);
|
|
@@ -24580,6 +25969,7 @@ var init_gate2 = __esm({
|
|
|
24580
25969
|
UNGATED_COMMANDS = /* @__PURE__ */ new Set([
|
|
24581
25970
|
"activate",
|
|
24582
25971
|
"config",
|
|
25972
|
+
"connect",
|
|
24583
25973
|
"profile",
|
|
24584
25974
|
"onboard",
|
|
24585
25975
|
"setup",
|
|
@@ -24593,7 +25983,8 @@ var init_gate2 = __esm({
|
|
|
24593
25983
|
"deactivate-demo",
|
|
24594
25984
|
"update",
|
|
24595
25985
|
"upgrade",
|
|
24596
|
-
"checkout"
|
|
25986
|
+
"checkout",
|
|
25987
|
+
"progress"
|
|
24597
25988
|
]);
|
|
24598
25989
|
}
|
|
24599
25990
|
});
|
|
@@ -24603,7 +25994,7 @@ var router_exports = {};
|
|
|
24603
25994
|
__export(router_exports, {
|
|
24604
25995
|
conversationRouter: () => conversationRouter
|
|
24605
25996
|
});
|
|
24606
|
-
import
|
|
25997
|
+
import chalk65 from "chalk";
|
|
24607
25998
|
async function conversationRouter(input, ctx) {
|
|
24608
25999
|
if (ctx.oneShot || (ctx.wizardDepth ?? 0) > 0) {
|
|
24609
26000
|
return { handled: false };
|
|
@@ -24613,7 +26004,7 @@ async function conversationRouter(input, ctx) {
|
|
|
24613
26004
|
if (FRESH_START_RE.test(line)) {
|
|
24614
26005
|
console.log();
|
|
24615
26006
|
console.log(
|
|
24616
|
-
" " +
|
|
26007
|
+
" " + chalk65.dim("Start a fresh analysis? This keeps prior sessions \u2014 say ") + chalk65.cyan("yes") + chalk65.dim(" to confirm or ") + chalk65.cyan("/home") + chalk65.dim(" for the dashboard.")
|
|
24617
26008
|
);
|
|
24618
26009
|
console.log();
|
|
24619
26010
|
return { handled: true };
|
|
@@ -24647,7 +26038,7 @@ async function conversationRouter(input, ctx) {
|
|
|
24647
26038
|
}
|
|
24648
26039
|
if (phase === "compute") {
|
|
24649
26040
|
console.log();
|
|
24650
|
-
console.log(" " +
|
|
26041
|
+
console.log(" " + chalk65.dim("Analysis running \u2014 wait for it to finish before typing another question."));
|
|
24651
26042
|
console.log();
|
|
24652
26043
|
return { handled: true };
|
|
24653
26044
|
}
|
|
@@ -24687,7 +26078,7 @@ var init_router = __esm({
|
|
|
24687
26078
|
});
|
|
24688
26079
|
|
|
24689
26080
|
// src/cli/dispatch.ts
|
|
24690
|
-
import
|
|
26081
|
+
import chalk66 from "chalk";
|
|
24691
26082
|
function printLicenseRequired(command) {
|
|
24692
26083
|
printLicenseBlocked(command);
|
|
24693
26084
|
}
|
|
@@ -24753,7 +26144,7 @@ async function dispatch(input, ctx) {
|
|
|
24753
26144
|
if (tokens.length === 1) {
|
|
24754
26145
|
if (/^\d$/.test(first)) {
|
|
24755
26146
|
console.log(
|
|
24756
|
-
" " +
|
|
26147
|
+
" " + chalk66.dim("Looks like a menu pick \u2014 run ") + paint("accent", "/new") + chalk66.dim(" to start (pick Demo, then choose your analysis type).")
|
|
24757
26148
|
);
|
|
24758
26149
|
return { kind: "handled" };
|
|
24759
26150
|
}
|
|
@@ -24776,22 +26167,22 @@ async function dispatch(input, ctx) {
|
|
|
24776
26167
|
return { kind: "handled", summary };
|
|
24777
26168
|
}
|
|
24778
26169
|
console.log(
|
|
24779
|
-
" " +
|
|
26170
|
+
" " + chalk66.dim("Not in Q&A yet \u2014 confirm scope, load data, and run analysis first. Type ") + paint("accent", "/home") + chalk66.dim(" for status.")
|
|
24780
26171
|
);
|
|
24781
26172
|
return { kind: "handled" };
|
|
24782
26173
|
}
|
|
24783
26174
|
console.log(
|
|
24784
|
-
" " +
|
|
26175
|
+
" " + chalk66.dim("Natural-language questions run in the interactive REPL. Start with ") + paint("accent", "ntrp") + chalk66.dim(" and ask after analysis.")
|
|
24785
26176
|
);
|
|
24786
26177
|
return { kind: "handled" };
|
|
24787
26178
|
}
|
|
24788
26179
|
async function runSlashCommand(name, args, ctx) {
|
|
24789
|
-
const
|
|
24790
|
-
if (!
|
|
24791
|
-
console.error(
|
|
26180
|
+
const handler46 = await resolveHandler(name);
|
|
26181
|
+
if (!handler46) {
|
|
26182
|
+
console.error(chalk66.red(` Unknown command: /${name}`));
|
|
24792
26183
|
return void 0;
|
|
24793
26184
|
}
|
|
24794
|
-
const result = await
|
|
26185
|
+
const result = await handler46(args, ctx);
|
|
24795
26186
|
return result ?? void 0;
|
|
24796
26187
|
}
|
|
24797
26188
|
async function runNaturalLanguage2(input, ctx) {
|
|
@@ -24820,7 +26211,7 @@ __export(welcome_exports, {
|
|
|
24820
26211
|
GRADIENT: () => GRADIENT,
|
|
24821
26212
|
printWelcome: () => printWelcome
|
|
24822
26213
|
});
|
|
24823
|
-
import
|
|
26214
|
+
import chalk67 from "chalk";
|
|
24824
26215
|
function resolveSessionSummary(input) {
|
|
24825
26216
|
if (input.scope?.intent_summary?.trim()) return input.scope.intent_summary.trim();
|
|
24826
26217
|
if (input.summary?.trim()) return input.summary.trim();
|
|
@@ -24854,19 +26245,19 @@ function sessionSummaryText(s) {
|
|
|
24854
26245
|
summary: s.summary,
|
|
24855
26246
|
dataset: s.dataset
|
|
24856
26247
|
});
|
|
24857
|
-
return summary === NO_SUMMARY ?
|
|
26248
|
+
return summary === NO_SUMMARY ? chalk67.dim(summary) : summary;
|
|
24858
26249
|
}
|
|
24859
26250
|
function formatLastSessionLine(s, colW, ctx, opts) {
|
|
24860
26251
|
const phase = sessionPhaseLabel(s, ctx);
|
|
24861
|
-
const current = opts?.markCurrent && s.id === ctx?.sessionId ?
|
|
24862
|
-
const meta = `${formatSessionId(s.id, s.name)} ${
|
|
26252
|
+
const current = opts?.markCurrent && s.id === ctx?.sessionId ? chalk67.dim(" \xB7 current") : "";
|
|
26253
|
+
const meta = `${formatSessionId(s.id, s.name)} ${chalk67.dim("\xB7")} ${chalk67.dim(lensBadgeLabel(s.analysis))} ${chalk67.dim("\xB7")} ${paint("accent", phase)} ${chalk67.dim("\xB7")} ${sessionSummaryText(s)}${current}`;
|
|
24863
26254
|
return truncateVisible(` ${meta}`, colW);
|
|
24864
26255
|
}
|
|
24865
26256
|
function formatActiveSessionLine(s, colW, ctx, opts) {
|
|
24866
26257
|
const indent = " ";
|
|
24867
26258
|
const idPart = formatSessionId(s.id, s.name);
|
|
24868
|
-
const status =
|
|
24869
|
-
const current = opts?.markCurrent && s.id === ctx?.sessionId ?
|
|
26259
|
+
const status = chalk67.dim(` \xB7 ${sessionStatusSuffix(s)}`);
|
|
26260
|
+
const current = opts?.markCurrent && s.id === ctx?.sessionId ? chalk67.dim(" \xB7 current") : "";
|
|
24870
26261
|
const suffix = `${status}${current}`;
|
|
24871
26262
|
const summaryBudget = Math.max(8, colW - visibleWidth(indent) - visibleWidth(idPart) - visibleWidth(suffix) - 2);
|
|
24872
26263
|
const summaryPart = truncateVisible(sessionSummaryText(s), summaryBudget);
|
|
@@ -24903,13 +26294,13 @@ function buildSystemLines(colW, statusRows, recent) {
|
|
|
24903
26294
|
const lines = [""];
|
|
24904
26295
|
lines.push(sectionHeading("System"));
|
|
24905
26296
|
for (const item of statusRows) {
|
|
24906
|
-
const label =
|
|
26297
|
+
const label = chalk67.dim(padRight(item.label, 8));
|
|
24907
26298
|
const state = padRight(item.state, 10);
|
|
24908
26299
|
const detailW = Math.max(1, colW - 21);
|
|
24909
|
-
lines.push(`${label} ${state} ${
|
|
26300
|
+
lines.push(`${label} ${state} ${chalk67.dim(truncateVisible(item.detail, detailW))}`);
|
|
24910
26301
|
}
|
|
24911
26302
|
if (recent) {
|
|
24912
|
-
lines.push(`${
|
|
26303
|
+
lines.push(`${chalk67.dim(padRight("last used", 8))} ${chalk67.dim(recent)}`);
|
|
24913
26304
|
}
|
|
24914
26305
|
return lines;
|
|
24915
26306
|
}
|
|
@@ -24919,7 +26310,7 @@ function buildHelpLines(colW, unfinishedCount) {
|
|
|
24919
26310
|
lines.push(sectionHeading(section.heading));
|
|
24920
26311
|
for (const entry of section.entries) {
|
|
24921
26312
|
const desc = entry.dynamicDescription ? entry.dynamicDescription(unfinishedCount) : entry.description;
|
|
24922
|
-
const text = ` ${paint("accent", entry.command)} ${
|
|
26313
|
+
const text = ` ${paint("accent", entry.command)} ${chalk67.dim(desc)}`;
|
|
24923
26314
|
lines.push(truncateVisible(text, colW));
|
|
24924
26315
|
}
|
|
24925
26316
|
}
|
|
@@ -24929,10 +26320,10 @@ function buildLastSessionLines(colW, ctx, lastSession, nextAction, emptyDataHint
|
|
|
24929
26320
|
const lines = [""];
|
|
24930
26321
|
lines.push(sectionHeading("Last Session"));
|
|
24931
26322
|
if (!lastSession) {
|
|
24932
|
-
lines.push(` ${
|
|
26323
|
+
lines.push(` ${chalk67.dim("(none yet)")}`);
|
|
24933
26324
|
lines.push(
|
|
24934
26325
|
truncateVisible(
|
|
24935
|
-
` ${nextAction.command ? actionHint(nextAction.label, nextAction.command, nextAction.detail) : `${
|
|
26326
|
+
` ${nextAction.command ? actionHint(nextAction.label, nextAction.command, nextAction.detail) : `${chalk67.dim(nextAction.label)} ${chalk67.dim(nextAction.detail)}`}`,
|
|
24936
26327
|
colW
|
|
24937
26328
|
)
|
|
24938
26329
|
);
|
|
@@ -24943,7 +26334,7 @@ function buildLastSessionLines(colW, ctx, lastSession, nextAction, emptyDataHint
|
|
|
24943
26334
|
if (!isCurrent) {
|
|
24944
26335
|
lines.push(
|
|
24945
26336
|
truncateVisible(
|
|
24946
|
-
` ${
|
|
26337
|
+
` ${chalk67.dim("Resume:")} ${paint("accent", `/session ${lastSession.id.slice(-4)}`)}`,
|
|
24947
26338
|
colW
|
|
24948
26339
|
)
|
|
24949
26340
|
);
|
|
@@ -24952,7 +26343,7 @@ function buildLastSessionLines(colW, ctx, lastSession, nextAction, emptyDataHint
|
|
|
24952
26343
|
truncateVisible(` ${actionHint(nextAction.label, nextAction.command, nextAction.detail)}`, colW)
|
|
24953
26344
|
);
|
|
24954
26345
|
} else {
|
|
24955
|
-
lines.push(truncateVisible(` ${
|
|
26346
|
+
lines.push(truncateVisible(` ${chalk67.dim(nextAction.label)} ${chalk67.dim(nextAction.detail)}`, colW));
|
|
24956
26347
|
}
|
|
24957
26348
|
if (isCurrent && emptyDataHint) {
|
|
24958
26349
|
lines.push(truncateVisible(` ${emptyDataHint}`, colW));
|
|
@@ -24963,14 +26354,14 @@ function buildActiveSessionsLines(colW, ctx, activeSessions) {
|
|
|
24963
26354
|
const lines = [""];
|
|
24964
26355
|
lines.push(sectionHeading("Active Sessions"));
|
|
24965
26356
|
if (activeSessions.length === 0) {
|
|
24966
|
-
lines.push(` ${
|
|
26357
|
+
lines.push(` ${chalk67.dim("(none in progress)")}`);
|
|
24967
26358
|
return lines;
|
|
24968
26359
|
}
|
|
24969
26360
|
for (const s of activeSessions.slice(0, 5)) {
|
|
24970
26361
|
lines.push(formatActiveSessionLine(s, colW, ctx, { markCurrent: true }));
|
|
24971
26362
|
}
|
|
24972
26363
|
if (activeSessions.length > 5) {
|
|
24973
|
-
lines.push(` ${
|
|
26364
|
+
lines.push(` ${chalk67.dim(`+${activeSessions.length - 5} more \xB7 `)}${paint("accent", "/session")}`);
|
|
24974
26365
|
}
|
|
24975
26366
|
return lines;
|
|
24976
26367
|
}
|
|
@@ -24978,7 +26369,7 @@ function buildProgressLines(colW) {
|
|
|
24978
26369
|
const summary = getTimeBankSummary();
|
|
24979
26370
|
const lines = [""];
|
|
24980
26371
|
const heading = sectionHeading("Progress");
|
|
24981
|
-
const hint = `${paint("accent", "/progress")}${
|
|
26372
|
+
const hint = `${paint("accent", "/progress")}${chalk67.dim(" for usage metrics")}`;
|
|
24982
26373
|
const gap = colW - visibleWidth(heading) - visibleWidth(hint);
|
|
24983
26374
|
if (gap > 2) {
|
|
24984
26375
|
lines.push(truncateVisible(`${heading}${" ".repeat(gap)}${hint}`, colW));
|
|
@@ -24988,7 +26379,7 @@ function buildProgressLines(colW) {
|
|
|
24988
26379
|
if (summary.total_minutes <= 0) {
|
|
24989
26380
|
lines.push(
|
|
24990
26381
|
truncateVisible(
|
|
24991
|
-
` ${
|
|
26382
|
+
` ${chalk67.dim("Run /diagnose or ask a question to start banking hours.")}`,
|
|
24992
26383
|
colW
|
|
24993
26384
|
)
|
|
24994
26385
|
);
|
|
@@ -24999,7 +26390,7 @@ function buildProgressLines(colW) {
|
|
|
24999
26390
|
const bar = inlineBar(summary.progress_pct, 16);
|
|
25000
26391
|
lines.push(truncateVisible(` ${bar} ${nextLabel}`, colW));
|
|
25001
26392
|
if (summary.perspective_line) {
|
|
25002
|
-
lines.push(truncateVisible(` ${
|
|
26393
|
+
lines.push(truncateVisible(` ${chalk67.dim.italic(summary.perspective_line)}`, colW));
|
|
25003
26394
|
}
|
|
25004
26395
|
return lines;
|
|
25005
26396
|
}
|
|
@@ -25009,7 +26400,7 @@ async function printWelcome(ctx, version) {
|
|
|
25009
26400
|
const innerW = cardW - 2;
|
|
25010
26401
|
const contentW = innerW - 2;
|
|
25011
26402
|
const outerPad = " ".repeat(Math.max(0, Math.floor((width - cardW) / 2)));
|
|
25012
|
-
const border = (ch) =>
|
|
26403
|
+
const border = (ch) => chalk67.dim(ch);
|
|
25013
26404
|
const push = (line) => console.log(outerPad + line);
|
|
25014
26405
|
const fitCell = (content, width2) => {
|
|
25015
26406
|
if (visibleWidth(content) > width2) return truncateVisible(content, width2);
|
|
@@ -25047,11 +26438,9 @@ async function printWelcome(ctx, version) {
|
|
|
25047
26438
|
(s) => s.id !== ctx.sessionId && (s.exchange_count > 0 || s.stage === "analyzed" || s.stage === "delivered" || !!s.dataset?.label)
|
|
25048
26439
|
);
|
|
25049
26440
|
const datasetDetail = hasData ? ctx.dataset?.label ?? countStr : savedSessions.length > 0 ? `none loaded \xB7 ${savedSessions.length} saved` : "none loaded";
|
|
25050
|
-
const { describeLlmReadiness: describeLlmReadiness2 } = await Promise.resolve().then(() => (init_repl_api(), repl_api_exports));
|
|
25051
26441
|
const { countAvailableEngines: countAvailableEngines2, formatActiveStack: formatActiveStack2, availableEngineLabels: availableEngineLabels2 } = await Promise.resolve().then(() => (init_session_state(), session_state_exports));
|
|
25052
|
-
const llmReady = describeLlmReadiness2();
|
|
25053
26442
|
const engineCount = countAvailableEngines2();
|
|
25054
|
-
const llmDetail = engineCount === 0 ? "
|
|
26443
|
+
const llmDetail = engineCount === 0 ? "run /connect (any key)" : engineCount === 1 ? `1 engine \xB7 ${availableEngineLabels2()[0]}` : `${engineCount} engines \xB7 ${availableEngineLabels2().slice(0, 3).join(", ")}${engineCount > 3 ? ", \u2026" : ""}`;
|
|
25055
26444
|
const llmState = engineCount >= 2 ? badge("READY", "success") : engineCount === 1 ? badge("READY", "success") : badge("MISSING", "warning");
|
|
25056
26445
|
const inferenceDetail = engineCount > 0 ? `active: ${formatActiveStack2(ctx)}` : "not configured";
|
|
25057
26446
|
const license = checkLicense();
|
|
@@ -25107,7 +26496,7 @@ async function printWelcome(ctx, version) {
|
|
|
25107
26496
|
ctx,
|
|
25108
26497
|
unfinishedCount: unfinishedSessions.length
|
|
25109
26498
|
});
|
|
25110
|
-
const emptyDataHint = !hasData ? savedSessions.length > 0 ?
|
|
26499
|
+
const emptyDataHint = !hasData ? savedSessions.length > 0 ? chalk67.dim("Run ") + paint("accent", "/session") + chalk67.dim(" to resume a saved analysis, or ") + paint("accent", "/new") + chalk67.dim(" for a fresh start") : chalk67.dim("Run ") + paint("accent", "/new") + chalk67.dim(" \u2192 pick Demo to explore sample data") : null;
|
|
25111
26500
|
const colW = useWideLayout ? leftW : contentW;
|
|
25112
26501
|
const rightColW = useWideLayout ? rightW : contentW;
|
|
25113
26502
|
const systemLines = buildSystemLines(colW, statusRows, recent);
|
|
@@ -25123,14 +26512,14 @@ async function printWelcome(ctx, version) {
|
|
|
25123
26512
|
const logoOffset = " ".repeat(Math.max(0, Math.floor((cardW - maxLogoW) / 2)));
|
|
25124
26513
|
for (const line of logo) push(logoOffset + line);
|
|
25125
26514
|
const taglineOffset = " ".repeat(Math.max(0, Math.floor((cardW - visibleWidth(TAGLINE)) / 2)));
|
|
25126
|
-
push(taglineOffset +
|
|
26515
|
+
push(taglineOffset + chalk67.dim(TAGLINE));
|
|
25127
26516
|
push("");
|
|
25128
26517
|
}
|
|
25129
26518
|
const versionTag = ` v${version} `;
|
|
25130
26519
|
const gap = Math.max(0, innerW - versionTag.length);
|
|
25131
26520
|
const gapL = Math.floor(gap / 2);
|
|
25132
26521
|
push(
|
|
25133
|
-
border(`\u256D${"\u2500".repeat(gapL)}`) +
|
|
26522
|
+
border(`\u256D${"\u2500".repeat(gapL)}`) + chalk67.dim(versionTag) + border(`${"\u2500".repeat(gap - gapL)}\u256E`)
|
|
25134
26523
|
);
|
|
25135
26524
|
if (useWideLayout) {
|
|
25136
26525
|
const leftLines = [...systemLines, ...helpLines];
|
|
@@ -25230,8 +26619,8 @@ __export(repl_exports, {
|
|
|
25230
26619
|
});
|
|
25231
26620
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
25232
26621
|
import { clearLine as clearLine2, cursorTo as cursorTo2 } from "readline";
|
|
25233
|
-
import
|
|
25234
|
-
import
|
|
26622
|
+
import ora19 from "ora";
|
|
26623
|
+
import chalk68 from "chalk";
|
|
25235
26624
|
function buildPrompt(ctx) {
|
|
25236
26625
|
return buildConversationPrompt(ctx);
|
|
25237
26626
|
}
|
|
@@ -25268,12 +26657,12 @@ function renderInlineSuggestion(rl, prompt) {
|
|
|
25268
26657
|
const suffix = cursor === line.length ? inlineCommandSuggestion(line) : null;
|
|
25269
26658
|
clearLine2(process.stdout, 0);
|
|
25270
26659
|
cursorTo2(process.stdout, 0);
|
|
25271
|
-
process.stdout.write(prompt + line + (suffix ?
|
|
26660
|
+
process.stdout.write(prompt + line + (suffix ? chalk68.dim(suffix) : ""));
|
|
25272
26661
|
cursorTo2(process.stdout, visibleLength(prompt) + cursor);
|
|
25273
26662
|
}
|
|
25274
26663
|
function appendTurnLine(current, promptLabel, currentSummary) {
|
|
25275
|
-
const currentLine = currentSummary ? `${promptLabel} ${current} ${
|
|
25276
|
-
console.log(" " +
|
|
26664
|
+
const currentLine = currentSummary ? `${promptLabel} ${current} ${chalk68.white("\u2192")} ${currentSummary}` : `${promptLabel} ${current}`;
|
|
26665
|
+
console.log(" " + chalk68.dim(currentLine));
|
|
25277
26666
|
console.log();
|
|
25278
26667
|
}
|
|
25279
26668
|
async function goHome(ctx, version, history, opts) {
|
|
@@ -25283,7 +26672,7 @@ async function goHome(ctx, version, history, opts) {
|
|
|
25283
26672
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
25284
26673
|
if (opts?.banner) {
|
|
25285
26674
|
console.log();
|
|
25286
|
-
console.log(" " + paint("accent", "\u2713") + " " +
|
|
26675
|
+
console.log(" " + paint("accent", "\u2713") + " " + chalk68.dim(opts.banner));
|
|
25287
26676
|
}
|
|
25288
26677
|
await printWelcome(ctx, version);
|
|
25289
26678
|
}
|
|
@@ -25306,11 +26695,11 @@ async function handleDispatchResult(result, ctx, version, history) {
|
|
|
25306
26695
|
case "unknown":
|
|
25307
26696
|
if (result.suggestion) {
|
|
25308
26697
|
console.log(
|
|
25309
|
-
" " +
|
|
26698
|
+
" " + chalk68.red(`Unknown command: ${result.token}.`) + chalk68.dim(" Did you mean ") + paint("accent", result.suggestion) + chalk68.dim("?")
|
|
25310
26699
|
);
|
|
25311
26700
|
} else {
|
|
25312
26701
|
console.log(
|
|
25313
|
-
" " +
|
|
26702
|
+
" " + chalk68.red(`Unknown command: ${result.token}`) + chalk68.dim(" Type ") + paint("accent", "/help") + chalk68.dim(" to see available commands.")
|
|
25314
26703
|
);
|
|
25315
26704
|
}
|
|
25316
26705
|
break;
|
|
@@ -25334,7 +26723,7 @@ async function runRepl(ctx, version) {
|
|
|
25334
26723
|
ctx.rl = rl;
|
|
25335
26724
|
console.log();
|
|
25336
26725
|
console.log(
|
|
25337
|
-
" " +
|
|
26726
|
+
" " + chalk68.dim("What do you want to look at? ") + chalk68.dim('(e.g. "pipeline health", "is NRR real?", "board deck on Q3")')
|
|
25338
26727
|
);
|
|
25339
26728
|
console.log();
|
|
25340
26729
|
if (ctx.pendingUpdateCheck) {
|
|
@@ -25364,7 +26753,7 @@ async function runRepl(ctx, version) {
|
|
|
25364
26753
|
return;
|
|
25365
26754
|
}
|
|
25366
26755
|
sigintPrimed = true;
|
|
25367
|
-
console.log("\n " +
|
|
26756
|
+
console.log("\n " + chalk68.dim("Type /exit to quit, or press Ctrl+C again."));
|
|
25368
26757
|
};
|
|
25369
26758
|
rl.on("SIGINT", sigintHandler);
|
|
25370
26759
|
function shutdownRepl() {
|
|
@@ -25435,7 +26824,7 @@ async function runRepl(ctx, version) {
|
|
|
25435
26824
|
}
|
|
25436
26825
|
}
|
|
25437
26826
|
} else {
|
|
25438
|
-
console.error(" " +
|
|
26827
|
+
console.error(" " + chalk68.red("Error: " + String(err.message ?? err)));
|
|
25439
26828
|
}
|
|
25440
26829
|
}
|
|
25441
26830
|
history.push({ input: line, summary });
|
|
@@ -25443,7 +26832,7 @@ async function runRepl(ctx, version) {
|
|
|
25443
26832
|
shutdownRepl();
|
|
25444
26833
|
const exchangeCount = Math.floor(ctx.messages.length / 2);
|
|
25445
26834
|
if (exchangeCount > 0) {
|
|
25446
|
-
const spinner =
|
|
26835
|
+
const spinner = ora19({ text: "Saving session\u2026", color: "cyan", discardStdin: false }).start();
|
|
25447
26836
|
const summary = await closeSession(ctx);
|
|
25448
26837
|
if (summary) {
|
|
25449
26838
|
spinner.succeed(`Session saved (${summary})`);
|
|
@@ -25453,7 +26842,7 @@ async function runRepl(ctx, version) {
|
|
|
25453
26842
|
} else {
|
|
25454
26843
|
await closeSession(ctx);
|
|
25455
26844
|
}
|
|
25456
|
-
console.log(" " +
|
|
26845
|
+
console.log(" " + chalk68.dim(randomGoodbye()));
|
|
25457
26846
|
}
|
|
25458
26847
|
function printHelpOneShot() {
|
|
25459
26848
|
printHelp();
|
|
@@ -25461,10 +26850,10 @@ function printHelpOneShot() {
|
|
|
25461
26850
|
function printHelp() {
|
|
25462
26851
|
console.log();
|
|
25463
26852
|
console.log(" " + sectionHeading("Conversation"));
|
|
25464
|
-
console.log(" " +
|
|
25465
|
-
console.log(" " +
|
|
25466
|
-
console.log(" " +
|
|
25467
|
-
console.log(" " +
|
|
26853
|
+
console.log(" " + chalk68.dim("Type what you want to investigate \u2014 no slash needed."));
|
|
26854
|
+
console.log(" " + chalk68.dim("Paste a CSV path or say ") + paint("accent", '"use demo data"') + chalk68.dim(" to load data."));
|
|
26855
|
+
console.log(" " + chalk68.dim("After analysis, ask questions in plain English."));
|
|
26856
|
+
console.log(" " + chalk68.dim("Say ") + paint("accent", '"ship a board deck"') + chalk68.dim(" to draft a handoff prompt."));
|
|
25468
26857
|
console.log();
|
|
25469
26858
|
console.log(" " + sectionHeading("Shortcuts"));
|
|
25470
26859
|
const shortcuts = [
|
|
@@ -25479,21 +26868,21 @@ function printHelp() {
|
|
|
25479
26868
|
];
|
|
25480
26869
|
const maxW = Math.max(...shortcuts.map(([c]) => c.length)) + 2;
|
|
25481
26870
|
for (const [cmd, desc] of shortcuts) {
|
|
25482
|
-
console.log(` ${paint("accent", padRight(cmd, maxW))} ${
|
|
26871
|
+
console.log(` ${paint("accent", padRight(cmd, maxW))} ${chalk68.dim(desc)}`);
|
|
25483
26872
|
}
|
|
25484
26873
|
console.log();
|
|
25485
26874
|
console.log(" " + sectionHeading("Admin"));
|
|
25486
26875
|
const admin = [
|
|
25487
|
-
["/scratch", "Wipe config, profile, and datasets"],
|
|
26876
|
+
["/scratch", "Wipe config, profile, and datasets (--include-progress to wipe hours)"],
|
|
25488
26877
|
["/cleanup", "Close all active sessions"],
|
|
25489
26878
|
["/deactivate-demo", "Disable demo data generators"]
|
|
25490
26879
|
];
|
|
25491
26880
|
const adminMaxW = Math.max(...admin.map(([c]) => c.length)) + 2;
|
|
25492
26881
|
for (const [cmd, desc] of admin) {
|
|
25493
|
-
console.log(` ${paint("accent", padRight(cmd, adminMaxW))} ${
|
|
26882
|
+
console.log(` ${paint("accent", padRight(cmd, adminMaxW))} ${chalk68.dim(desc)}`);
|
|
25494
26883
|
}
|
|
25495
26884
|
console.log();
|
|
25496
|
-
console.log(" " +
|
|
26885
|
+
console.log(" " + chalk68.dim("Power-user commands (") + paint("accent", "/new") + chalk68.dim(", ") + paint("accent", "/diagnose") + chalk68.dim(", ") + paint("accent", "/metrics") + chalk68.dim(") remain available."));
|
|
25497
26886
|
console.log();
|
|
25498
26887
|
}
|
|
25499
26888
|
var REPL_BUILTINS, ANSI_PATTERN, GOODBYES;
|
|
@@ -25576,7 +26965,7 @@ init_emit();
|
|
|
25576
26965
|
init_errors2();
|
|
25577
26966
|
init_types2();
|
|
25578
26967
|
init_version();
|
|
25579
|
-
import
|
|
26968
|
+
import chalk69 from "chalk";
|
|
25580
26969
|
var VERSION = getInstalledVersion();
|
|
25581
26970
|
var UNGATED = UNGATED_COMMANDS;
|
|
25582
26971
|
var DB_COMMANDS = /* @__PURE__ */ new Set(["actions", "ask", "backmeup", "demo", "diagnose", "export", "handoff", "ingest", "metrics", "new", "playbook", "publish", "report", "reset", "segment", "session", "status", "strategy"]);
|
|
@@ -25603,7 +26992,7 @@ async function main() {
|
|
|
25603
26992
|
quiet: args.globals.quiet
|
|
25604
26993
|
});
|
|
25605
26994
|
if (!ctx.execution.color) {
|
|
25606
|
-
|
|
26995
|
+
chalk69.level = 0;
|
|
25607
26996
|
}
|
|
25608
26997
|
if (args.globals.stdin) {
|
|
25609
26998
|
args.input = (await readStdin()).trim();
|
|
@@ -25616,16 +27005,16 @@ async function main() {
|
|
|
25616
27005
|
if (isStructuredOutput(ctx.execution)) {
|
|
25617
27006
|
emitError(cmd || "ntrp", new NtrpError("license_invalid", lic2.message, 3 /* Auth */));
|
|
25618
27007
|
}
|
|
25619
|
-
console.error(
|
|
27008
|
+
console.error(chalk69.red(`
|
|
25620
27009
|
${lic2.message}`));
|
|
25621
|
-
console.error(
|
|
27010
|
+
console.error(chalk69.dim(" Trial's over \u2014 /upgrade and paste your key.\n"));
|
|
25622
27011
|
process.exit(1);
|
|
25623
27012
|
}
|
|
25624
27013
|
}
|
|
25625
|
-
const PROFILE_HINT_SKIP = /* @__PURE__ */ new Set(["onboard", "setup", "config", "activate", "help", "home", "exit", "quit", "clear", "profile"]);
|
|
27014
|
+
const PROFILE_HINT_SKIP = /* @__PURE__ */ new Set(["onboard", "setup", "config", "connect", "activate", "help", "home", "exit", "quit", "clear", "profile", "progress"]);
|
|
25626
27015
|
if (!isProfileConfigured() && !PROFILE_HINT_SKIP.has(cmd) && !ctx.execution.quiet) {
|
|
25627
27016
|
console.error(
|
|
25628
|
-
" " +
|
|
27017
|
+
" " + chalk69.dim("Tip: run ") + paint("accent", "ntrp") + chalk69.dim(" interactively to set up your company profile for richer answers.")
|
|
25629
27018
|
);
|
|
25630
27019
|
}
|
|
25631
27020
|
const result = await dispatch(args.input, ctx);
|
|
@@ -25637,12 +27026,12 @@ async function main() {
|
|
|
25637
27026
|
}
|
|
25638
27027
|
if (result.suggestion) {
|
|
25639
27028
|
console.error(
|
|
25640
|
-
|
|
27029
|
+
chalk69.red(` Unknown command: ${result.token}.`) + chalk69.dim(" Did you mean ") + paint("accent", result.suggestion) + chalk69.dim("?")
|
|
25641
27030
|
);
|
|
25642
27031
|
} else {
|
|
25643
|
-
console.error(
|
|
27032
|
+
console.error(chalk69.red(` Unknown command: ${result.token}`));
|
|
25644
27033
|
}
|
|
25645
|
-
console.error(
|
|
27034
|
+
console.error(chalk69.dim(" Run 'ntrp' for the interactive prompt."));
|
|
25646
27035
|
process.exit(1);
|
|
25647
27036
|
break;
|
|
25648
27037
|
case "help":
|
|
@@ -25669,6 +27058,7 @@ async function main() {
|
|
|
25669
27058
|
const { printTrialNudge: printTrialNudge2 } = await Promise.resolve().then(() => (init_upgrade(), upgrade_exports));
|
|
25670
27059
|
printTrialNudge2(lic);
|
|
25671
27060
|
}
|
|
27061
|
+
void Promise.resolve().then(() => (init_discovery(), discovery_exports)).then((m) => m.refreshStaleProviderCaches()).catch(() => void 0);
|
|
25672
27062
|
if (!isProfileConfigured()) {
|
|
25673
27063
|
try {
|
|
25674
27064
|
const { handler: onboard } = await Promise.resolve().then(() => (init_onboard(), onboard_exports));
|