@tuned-tensor/cli 0.13.0 → 0.13.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -24
- package/dist/index.js +481 -168
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -573,18 +573,157 @@ async function rejectPreparedAction(action, persist) {
|
|
|
573
573
|
}
|
|
574
574
|
|
|
575
575
|
// src/agent-model.ts
|
|
576
|
+
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
577
|
+
import { chmodSync as chmodSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
578
|
+
import { dirname, join as join3 } from "path";
|
|
579
|
+
|
|
580
|
+
// src/config.ts
|
|
576
581
|
import {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
+
chmodSync,
|
|
583
|
+
existsSync,
|
|
584
|
+
mkdirSync,
|
|
585
|
+
readFileSync,
|
|
586
|
+
writeFileSync
|
|
587
|
+
} from "fs";
|
|
588
|
+
import { homedir } from "os";
|
|
589
|
+
import { join as join2 } from "path";
|
|
590
|
+
var AGENT_THINKING_LEVELS = [
|
|
591
|
+
"off",
|
|
592
|
+
"minimal",
|
|
593
|
+
"low",
|
|
594
|
+
"medium",
|
|
595
|
+
"high",
|
|
596
|
+
"xhigh",
|
|
597
|
+
"max"
|
|
598
|
+
];
|
|
599
|
+
var CONFIG_DIR_NAME = "tuned-tensor";
|
|
600
|
+
var CONFIG_FILE_NAME = "config.json";
|
|
601
|
+
function getConfigDir() {
|
|
602
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
603
|
+
const base = xdg || join2(homedir(), ".config");
|
|
604
|
+
return join2(base, CONFIG_DIR_NAME);
|
|
605
|
+
}
|
|
606
|
+
function getAgentConfigDir() {
|
|
607
|
+
return join2(getConfigDir(), "agent");
|
|
608
|
+
}
|
|
609
|
+
function getConfigPath() {
|
|
610
|
+
return join2(getConfigDir(), CONFIG_FILE_NAME);
|
|
611
|
+
}
|
|
612
|
+
function readConfig() {
|
|
613
|
+
const path = getConfigPath();
|
|
614
|
+
if (!existsSync(path)) return {};
|
|
615
|
+
try {
|
|
616
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
617
|
+
} catch {
|
|
618
|
+
return {};
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
var configRevision = 0;
|
|
622
|
+
function getConfigRevision() {
|
|
623
|
+
return configRevision;
|
|
624
|
+
}
|
|
625
|
+
function writeConfig(config) {
|
|
626
|
+
const dir = getConfigDir();
|
|
627
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
|
|
628
|
+
chmodSync(dir, 448);
|
|
629
|
+
const path = getConfigPath();
|
|
630
|
+
writeFileSync(path, JSON.stringify(config, null, 2) + "\n", {
|
|
631
|
+
mode: 384
|
|
632
|
+
});
|
|
633
|
+
chmodSync(path, 384);
|
|
634
|
+
configRevision += 1;
|
|
635
|
+
}
|
|
636
|
+
function updateConfig(partial) {
|
|
637
|
+
const current = readConfig();
|
|
638
|
+
writeConfig({ ...current, ...partial });
|
|
639
|
+
}
|
|
640
|
+
function getApiKey(opts) {
|
|
641
|
+
return opts?.apiKey || process.env.TUNED_TENSOR_API_KEY || readConfig().api_key;
|
|
642
|
+
}
|
|
643
|
+
function getAgentSelection(env = process.env) {
|
|
644
|
+
const stored = readConfig().agent;
|
|
645
|
+
const provider = env.TUNED_TENSOR_AGENT_PROVIDER || stored?.provider;
|
|
646
|
+
const model = env.TUNED_TENSOR_AGENT_MODEL || stored?.model;
|
|
647
|
+
const rawThinking = env.TUNED_TENSOR_AGENT_THINKING || stored?.thinking || "medium";
|
|
648
|
+
if (!AGENT_THINKING_LEVELS.includes(rawThinking)) {
|
|
649
|
+
throw new Error(
|
|
650
|
+
"Agent thinking must be off, minimal, low, medium, high, xhigh, or max."
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
if (!provider && !model) return void 0;
|
|
654
|
+
if (!provider || !model) {
|
|
655
|
+
throw new Error(
|
|
656
|
+
"Both an agent provider and model are required. Run `tt agent configure --provider <provider> --model <model>`."
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
return { provider, model, thinking: rawThinking };
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// src/agent-model.ts
|
|
663
|
+
function missingProviderAuthMessage() {
|
|
664
|
+
return "No API key is saved for the selected model. Run /login, or /model to pick a different one.";
|
|
665
|
+
}
|
|
666
|
+
function unknownAgentProviderMessage(provider) {
|
|
667
|
+
return `Unknown provider "${provider}". Use /login <id> or /model <id>.`;
|
|
668
|
+
}
|
|
669
|
+
var MIN_REDACTED_SECRET_LENGTH = 8;
|
|
670
|
+
function isRecord(value) {
|
|
671
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
672
|
+
}
|
|
673
|
+
function pushSecret(secrets, value) {
|
|
674
|
+
if (typeof value !== "string") return;
|
|
675
|
+
const secret = value.trim();
|
|
676
|
+
if (secret.length < MIN_REDACTED_SECRET_LENGTH) return;
|
|
677
|
+
if (!secrets.includes(secret)) secrets.push(secret);
|
|
678
|
+
}
|
|
679
|
+
function persistProviderApiKey(provider, apiKey, authPath = getAgentAuthPath()) {
|
|
680
|
+
const key = apiKey.trim();
|
|
681
|
+
if (!key) {
|
|
682
|
+
throw new Error("API key cannot be empty.");
|
|
683
|
+
}
|
|
684
|
+
let stored = {};
|
|
685
|
+
if (existsSync2(authPath)) {
|
|
686
|
+
let parsed;
|
|
687
|
+
try {
|
|
688
|
+
parsed = JSON.parse(readFileSync2(authPath, "utf-8"));
|
|
689
|
+
} catch (error) {
|
|
690
|
+
throw new Error(
|
|
691
|
+
`Failed to read ${authPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
if (!isRecord(parsed)) {
|
|
695
|
+
throw new Error(`Invalid auth.json: expected an object at ${authPath}`);
|
|
696
|
+
}
|
|
697
|
+
stored = parsed;
|
|
698
|
+
}
|
|
699
|
+
stored[provider] = { type: "api_key", key };
|
|
700
|
+
writePrivateJson(authPath, stored);
|
|
701
|
+
}
|
|
702
|
+
function readStoredProviderSecrets(authPath = getAgentAuthPath()) {
|
|
703
|
+
if (!existsSync2(authPath)) return [];
|
|
704
|
+
let parsed;
|
|
705
|
+
try {
|
|
706
|
+
parsed = JSON.parse(readFileSync2(authPath, "utf-8"));
|
|
707
|
+
} catch {
|
|
708
|
+
return [];
|
|
709
|
+
}
|
|
710
|
+
if (!isRecord(parsed)) return [];
|
|
711
|
+
const secrets = [];
|
|
712
|
+
for (const credential of Object.values(parsed)) {
|
|
713
|
+
if (!isRecord(credential)) continue;
|
|
714
|
+
if (credential.type === "api_key") {
|
|
715
|
+
pushSecret(secrets, credential.key);
|
|
716
|
+
} else if (credential.type === "oauth") {
|
|
717
|
+
pushSecret(secrets, credential.access);
|
|
718
|
+
pushSecret(secrets, credential.refresh);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return secrets;
|
|
722
|
+
}
|
|
582
723
|
function resolveAgentModel(runtime, selection) {
|
|
583
724
|
const resolved = resolveAgentModelDefinition(runtime, selection);
|
|
584
725
|
if (!runtime.hasConfiguredAuth(selection.provider)) {
|
|
585
|
-
throw new Error(
|
|
586
|
-
`Authenticate provider "${selection.provider}" first, then retry. Provider secrets are never accepted by tt flags.`
|
|
587
|
-
);
|
|
726
|
+
throw new Error(missingProviderAuthMessage());
|
|
588
727
|
}
|
|
589
728
|
return resolved;
|
|
590
729
|
}
|
|
@@ -610,25 +749,46 @@ function resolveAgentModelDefinition(runtime, selection) {
|
|
|
610
749
|
}
|
|
611
750
|
return { model, thinking: selection.thinking };
|
|
612
751
|
}
|
|
752
|
+
function getAgentAuthPath() {
|
|
753
|
+
return join3(getAgentConfigDir(), "auth.json");
|
|
754
|
+
}
|
|
755
|
+
function getAgentModelsPath() {
|
|
756
|
+
return join3(getAgentConfigDir(), "models.json");
|
|
757
|
+
}
|
|
613
758
|
async function createPiModelRuntime() {
|
|
614
|
-
const agentDir =
|
|
615
|
-
|
|
759
|
+
const agentDir = getAgentConfigDir();
|
|
760
|
+
ensurePrivateDir(agentDir);
|
|
761
|
+
const modelsPath = getAgentModelsPath();
|
|
762
|
+
const authPath = getAgentAuthPath();
|
|
616
763
|
ensureOpenRouterAppHeaders(modelsPath);
|
|
617
|
-
|
|
618
|
-
authPath
|
|
764
|
+
const runtime = await ModelRuntime.create({
|
|
765
|
+
authPath,
|
|
619
766
|
modelsPath,
|
|
620
767
|
allowModelNetwork: false
|
|
621
768
|
});
|
|
769
|
+
if (existsSync2(authPath)) chmodSync2(authPath, 384);
|
|
770
|
+
return runtime;
|
|
622
771
|
}
|
|
623
772
|
var OPENROUTER_APP_HEADERS = {
|
|
624
773
|
"HTTP-Referer": "https://tunedtensor.com",
|
|
774
|
+
"X-OpenRouter-Title": "Tuned Tensor",
|
|
775
|
+
"X-OpenRouter-Categories": "cli-agent",
|
|
625
776
|
"X-Title": "Tuned Tensor"
|
|
626
777
|
};
|
|
778
|
+
function ensurePrivateDir(dir) {
|
|
779
|
+
if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
780
|
+
chmodSync2(dir, 448);
|
|
781
|
+
}
|
|
782
|
+
function writePrivateJson(path, value) {
|
|
783
|
+
ensurePrivateDir(dirname(path));
|
|
784
|
+
writeFileSync2(path, JSON.stringify(value, null, 2) + "\n", { mode: 384 });
|
|
785
|
+
chmodSync2(path, 384);
|
|
786
|
+
}
|
|
627
787
|
function ensureOpenRouterAppHeaders(modelsPath) {
|
|
628
788
|
let config = {};
|
|
629
|
-
if (
|
|
789
|
+
if (existsSync2(modelsPath)) {
|
|
630
790
|
try {
|
|
631
|
-
const parsed = JSON.parse(
|
|
791
|
+
const parsed = JSON.parse(readFileSync2(modelsPath, "utf-8"));
|
|
632
792
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
633
793
|
config = parsed;
|
|
634
794
|
}
|
|
@@ -642,10 +802,15 @@ function ensureOpenRouterAppHeaders(modelsPath) {
|
|
|
642
802
|
const providers = asRecord(config.providers) ?? (config.providers = empty());
|
|
643
803
|
const openrouter = asRecord(providers.openrouter) ?? (providers.openrouter = empty());
|
|
644
804
|
const headers = asRecord(openrouter.headers) ?? (openrouter.headers = empty());
|
|
805
|
+
const attributionNames = new Set(
|
|
806
|
+
Object.keys(OPENROUTER_APP_HEADERS).map((name) => name.toLowerCase())
|
|
807
|
+
);
|
|
808
|
+
for (const name of Object.keys(headers)) {
|
|
809
|
+
if (attributionNames.has(name.toLowerCase())) delete headers[name];
|
|
810
|
+
}
|
|
645
811
|
Object.assign(headers, OPENROUTER_APP_HEADERS);
|
|
646
812
|
if (JSON.stringify(config) === before) return;
|
|
647
|
-
|
|
648
|
-
writeFileSync(modelsPath, JSON.stringify(config, null, 2) + "\n");
|
|
813
|
+
writePrivateJson(modelsPath, config);
|
|
649
814
|
}
|
|
650
815
|
|
|
651
816
|
// src/agent-tools.ts
|
|
@@ -1335,7 +1500,7 @@ import {
|
|
|
1335
1500
|
writeFile as writeFile2
|
|
1336
1501
|
} from "fs/promises";
|
|
1337
1502
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
1338
|
-
import { join as
|
|
1503
|
+
import { join as join4 } from "path";
|
|
1339
1504
|
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1340
1505
|
var DEFAULT_MAX_THREAD_BYTES = 1e6;
|
|
1341
1506
|
var DEFAULT_MAX_MESSAGES = 200;
|
|
@@ -1345,7 +1510,7 @@ var DEFAULT_MAX_CLAIMS = 1e3;
|
|
|
1345
1510
|
function assertThreadId(id) {
|
|
1346
1511
|
if (!UUID.test(id)) throw new Error("Invalid local agent thread ID.");
|
|
1347
1512
|
}
|
|
1348
|
-
function
|
|
1513
|
+
function isRecord2(value) {
|
|
1349
1514
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1350
1515
|
}
|
|
1351
1516
|
function scrub(value, secrets) {
|
|
@@ -1356,7 +1521,7 @@ function scrub(value, secrets) {
|
|
|
1356
1521
|
);
|
|
1357
1522
|
}
|
|
1358
1523
|
if (Array.isArray(value)) return value.map((entry) => scrub(entry, secrets));
|
|
1359
|
-
if (
|
|
1524
|
+
if (isRecord2(value)) {
|
|
1360
1525
|
return Object.fromEntries(
|
|
1361
1526
|
Object.entries(value).map(([key, entry]) => [key, scrub(entry, secrets)])
|
|
1362
1527
|
);
|
|
@@ -1366,9 +1531,10 @@ function scrub(value, secrets) {
|
|
|
1366
1531
|
var LocalAgentStore = class {
|
|
1367
1532
|
constructor(rootDir, options = {}) {
|
|
1368
1533
|
this.rootDir = rootDir;
|
|
1369
|
-
this.threadsDir =
|
|
1370
|
-
this.claimsDir =
|
|
1534
|
+
this.threadsDir = join4(rootDir, "threads");
|
|
1535
|
+
this.claimsDir = join4(rootDir, "action-claims");
|
|
1371
1536
|
this.secrets = options.secretValues?.filter(Boolean) ?? [];
|
|
1537
|
+
this.secretValueProvider = options.secretValueProvider;
|
|
1372
1538
|
this.maxThreadBytes = options.maxThreadBytes ?? DEFAULT_MAX_THREAD_BYTES;
|
|
1373
1539
|
this.maxMessages = options.maxMessages ?? DEFAULT_MAX_MESSAGES;
|
|
1374
1540
|
this.maxActions = options.maxActions ?? DEFAULT_MAX_ACTIONS;
|
|
@@ -1378,13 +1544,20 @@ var LocalAgentStore = class {
|
|
|
1378
1544
|
threadsDir;
|
|
1379
1545
|
claimsDir;
|
|
1380
1546
|
secrets;
|
|
1547
|
+
secretValueProvider;
|
|
1381
1548
|
maxThreadBytes;
|
|
1382
1549
|
maxMessages;
|
|
1383
1550
|
maxActions;
|
|
1384
1551
|
maxThreads;
|
|
1385
1552
|
maxClaims;
|
|
1386
1553
|
redact(value) {
|
|
1387
|
-
|
|
1554
|
+
let extra = [];
|
|
1555
|
+
try {
|
|
1556
|
+
extra = this.secretValueProvider?.() ?? [];
|
|
1557
|
+
} catch {
|
|
1558
|
+
extra = [];
|
|
1559
|
+
}
|
|
1560
|
+
return scrub(value, [...this.secrets, ...extra]);
|
|
1388
1561
|
}
|
|
1389
1562
|
async ensureDirectories() {
|
|
1390
1563
|
await mkdir2(this.rootDir, { recursive: true, mode: 448 });
|
|
@@ -1396,7 +1569,7 @@ var LocalAgentStore = class {
|
|
|
1396
1569
|
}
|
|
1397
1570
|
path(id) {
|
|
1398
1571
|
assertThreadId(id);
|
|
1399
|
-
return
|
|
1572
|
+
return join4(this.threadsDir, `${id}.json`);
|
|
1400
1573
|
}
|
|
1401
1574
|
serializeWithinQuota(state) {
|
|
1402
1575
|
const active = state.actions.filter(
|
|
@@ -1421,11 +1594,11 @@ var LocalAgentStore = class {
|
|
|
1421
1594
|
return serialized;
|
|
1422
1595
|
}
|
|
1423
1596
|
async pruneDirectory(directory, suffix, maximum, preserve) {
|
|
1424
|
-
const entries = await Promise.all((await readdir(directory)).filter((name) => suffix.test(name) && name !== preserve).map(async (name) => ({ name, modified: (await stat(
|
|
1597
|
+
const entries = await Promise.all((await readdir(directory)).filter((name) => suffix.test(name) && name !== preserve).map(async (name) => ({ name, modified: (await stat(join4(directory, name))).mtimeMs })));
|
|
1425
1598
|
entries.sort((a, b) => a.modified - b.modified);
|
|
1426
1599
|
const keepOther = Math.max(0, maximum - (preserve ? 1 : 0));
|
|
1427
1600
|
for (const entry of entries.slice(0, Math.max(0, entries.length - keepOther))) {
|
|
1428
|
-
await unlink(
|
|
1601
|
+
await unlink(join4(directory, entry.name));
|
|
1429
1602
|
}
|
|
1430
1603
|
}
|
|
1431
1604
|
async pruneThreads(preserve) {
|
|
@@ -1445,19 +1618,19 @@ var LocalAgentStore = class {
|
|
|
1445
1618
|
}
|
|
1446
1619
|
const candidates = await Promise.all(names.filter((name) => !protectedNames.has(name)).map(async (name) => ({
|
|
1447
1620
|
name,
|
|
1448
|
-
modified: (await stat(
|
|
1621
|
+
modified: (await stat(join4(this.threadsDir, name))).mtimeMs
|
|
1449
1622
|
})));
|
|
1450
1623
|
candidates.sort((a, b) => a.modified - b.modified);
|
|
1451
1624
|
const keep = Math.max(0, this.maxThreads - protectedNames.size);
|
|
1452
1625
|
for (const entry of candidates.slice(0, Math.max(0, candidates.length - keep))) {
|
|
1453
|
-
await unlink(
|
|
1626
|
+
await unlink(join4(this.threadsDir, entry.name));
|
|
1454
1627
|
}
|
|
1455
1628
|
}
|
|
1456
1629
|
async save(state) {
|
|
1457
1630
|
assertThreadId(state.thread.id);
|
|
1458
1631
|
await this.ensureDirectories();
|
|
1459
1632
|
const destination = this.path(state.thread.id);
|
|
1460
|
-
const temporary =
|
|
1633
|
+
const temporary = join4(
|
|
1461
1634
|
this.threadsDir,
|
|
1462
1635
|
`.${state.thread.id}.${randomUUID3()}.tmp`
|
|
1463
1636
|
);
|
|
@@ -1477,7 +1650,7 @@ var LocalAgentStore = class {
|
|
|
1477
1650
|
async claimAction(actionId) {
|
|
1478
1651
|
assertThreadId(actionId);
|
|
1479
1652
|
await this.ensureDirectories();
|
|
1480
|
-
const path =
|
|
1653
|
+
const path = join4(this.claimsDir, `${actionId}.claim`);
|
|
1481
1654
|
try {
|
|
1482
1655
|
await writeFile2(path, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1483
1656
|
`, {
|
|
@@ -1505,7 +1678,7 @@ var LocalAgentStore = class {
|
|
|
1505
1678
|
throw new Error("Invalid local agent thread file.");
|
|
1506
1679
|
}
|
|
1507
1680
|
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
1508
|
-
if (!
|
|
1681
|
+
if (!isRecord2(parsed) || !isRecord2(parsed.thread) || parsed.thread.id !== id || !Array.isArray(parsed.messages) || !Array.isArray(parsed.actions)) {
|
|
1509
1682
|
throw new Error("Local agent thread file is malformed.");
|
|
1510
1683
|
}
|
|
1511
1684
|
return parsed;
|
|
@@ -1528,88 +1701,6 @@ var LocalAgentStore = class {
|
|
|
1528
1701
|
}
|
|
1529
1702
|
};
|
|
1530
1703
|
|
|
1531
|
-
// src/config.ts
|
|
1532
|
-
import {
|
|
1533
|
-
chmodSync,
|
|
1534
|
-
existsSync as existsSync2,
|
|
1535
|
-
mkdirSync as mkdirSync2,
|
|
1536
|
-
readFileSync as readFileSync2,
|
|
1537
|
-
writeFileSync as writeFileSync2
|
|
1538
|
-
} from "fs";
|
|
1539
|
-
import { homedir } from "os";
|
|
1540
|
-
import { join as join4 } from "path";
|
|
1541
|
-
var AGENT_THINKING_LEVELS = [
|
|
1542
|
-
"off",
|
|
1543
|
-
"minimal",
|
|
1544
|
-
"low",
|
|
1545
|
-
"medium",
|
|
1546
|
-
"high",
|
|
1547
|
-
"xhigh",
|
|
1548
|
-
"max"
|
|
1549
|
-
];
|
|
1550
|
-
var CONFIG_DIR_NAME = "tuned-tensor";
|
|
1551
|
-
var CONFIG_FILE_NAME = "config.json";
|
|
1552
|
-
function getConfigDir() {
|
|
1553
|
-
const xdg = process.env.XDG_CONFIG_HOME;
|
|
1554
|
-
const base = xdg || join4(homedir(), ".config");
|
|
1555
|
-
return join4(base, CONFIG_DIR_NAME);
|
|
1556
|
-
}
|
|
1557
|
-
function getConfigPath() {
|
|
1558
|
-
return join4(getConfigDir(), CONFIG_FILE_NAME);
|
|
1559
|
-
}
|
|
1560
|
-
function readConfig() {
|
|
1561
|
-
const path = getConfigPath();
|
|
1562
|
-
if (!existsSync2(path)) return {};
|
|
1563
|
-
try {
|
|
1564
|
-
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
1565
|
-
} catch {
|
|
1566
|
-
return {};
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
var configRevision = 0;
|
|
1570
|
-
function getConfigRevision() {
|
|
1571
|
-
return configRevision;
|
|
1572
|
-
}
|
|
1573
|
-
function writeConfig(config) {
|
|
1574
|
-
const dir = getConfigDir();
|
|
1575
|
-
if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
1576
|
-
chmodSync(dir, 448);
|
|
1577
|
-
const path = getConfigPath();
|
|
1578
|
-
writeFileSync2(path, JSON.stringify(config, null, 2) + "\n", {
|
|
1579
|
-
mode: 384
|
|
1580
|
-
});
|
|
1581
|
-
chmodSync(path, 384);
|
|
1582
|
-
configRevision += 1;
|
|
1583
|
-
}
|
|
1584
|
-
function updateConfig(partial) {
|
|
1585
|
-
const current = readConfig();
|
|
1586
|
-
writeConfig({ ...current, ...partial });
|
|
1587
|
-
}
|
|
1588
|
-
function getApiKey(opts) {
|
|
1589
|
-
return opts?.apiKey || process.env.TUNED_TENSOR_API_KEY || readConfig().api_key;
|
|
1590
|
-
}
|
|
1591
|
-
function getAgentSelection(env = process.env) {
|
|
1592
|
-
const stored = readConfig().agent;
|
|
1593
|
-
const provider = env.TUNED_TENSOR_AGENT_PROVIDER || stored?.provider;
|
|
1594
|
-
const model = env.TUNED_TENSOR_AGENT_MODEL || stored?.model;
|
|
1595
|
-
const rawThinking = env.TUNED_TENSOR_AGENT_THINKING || stored?.thinking || "medium";
|
|
1596
|
-
if (!AGENT_THINKING_LEVELS.includes(rawThinking)) {
|
|
1597
|
-
throw new Error(
|
|
1598
|
-
"Agent thinking must be off, minimal, low, medium, high, xhigh, or max."
|
|
1599
|
-
);
|
|
1600
|
-
}
|
|
1601
|
-
if (!provider && !model) return void 0;
|
|
1602
|
-
if (!provider || !model) {
|
|
1603
|
-
throw new Error(
|
|
1604
|
-
"Both an agent provider and model are required. Run `tt agent configure --provider <provider> --model <model>`."
|
|
1605
|
-
);
|
|
1606
|
-
}
|
|
1607
|
-
return { provider, model, thinking: rawThinking };
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
// src/cli.ts
|
|
1611
|
-
import { join as join7 } from "path";
|
|
1612
|
-
|
|
1613
1704
|
// src/agent.ts
|
|
1614
1705
|
import chalk3 from "chalk";
|
|
1615
1706
|
import { stripVTControlCharacters as stripVTControlCharacters3 } from "util";
|
|
@@ -1619,7 +1710,7 @@ import chalk from "chalk";
|
|
|
1619
1710
|
import { stat as stat3 } from "fs/promises";
|
|
1620
1711
|
import { homedir as homedir3 } from "os";
|
|
1621
1712
|
import { isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
1622
|
-
import { createInterface } from "readline";
|
|
1713
|
+
import { createInterface as createInterface2 } from "readline";
|
|
1623
1714
|
import { stripVTControlCharacters } from "util";
|
|
1624
1715
|
|
|
1625
1716
|
// src/command-catalog.ts
|
|
@@ -1659,7 +1750,8 @@ var SLASH_COMMANDS = [
|
|
|
1659
1750
|
{ path: "/help", description: "Show commands; add a word to filter." },
|
|
1660
1751
|
{ path: "/status", description: "Show lightweight workflow status." },
|
|
1661
1752
|
{ path: "/context", description: "Show the current project context." },
|
|
1662
|
-
{ path: "/model", description: "Show or change the TT agent model." },
|
|
1753
|
+
{ path: "/model", description: "Show or change the TT agent provider and model." },
|
|
1754
|
+
{ path: "/login", description: "Save a provider API key; asks which provider if omitted." },
|
|
1663
1755
|
{ path: "/cd", description: "Change the shell's working directory." },
|
|
1664
1756
|
{ path: "/clear", description: "Clear the terminal." },
|
|
1665
1757
|
{ path: "/exit", description: "Exit the TT shell." }
|
|
@@ -1901,6 +1993,23 @@ function formatShellStatus(context) {
|
|
|
1901
1993
|
}
|
|
1902
1994
|
|
|
1903
1995
|
// src/agent-control.ts
|
|
1996
|
+
var FEATURED_AGENT_PROVIDERS = [
|
|
1997
|
+
"openai",
|
|
1998
|
+
"openrouter"
|
|
1999
|
+
];
|
|
2000
|
+
var RECOMMENDED_AGENT_MODELS = [
|
|
2001
|
+
{ provider: "openai", ids: ["gpt-5.6-sol"] },
|
|
2002
|
+
{
|
|
2003
|
+
provider: "openrouter",
|
|
2004
|
+
ids: ["deepseek/deepseek-v4-flash-0731", "deepseek/deepseek-v4-flash"]
|
|
2005
|
+
}
|
|
2006
|
+
];
|
|
2007
|
+
var FEATURED_PROVIDER_INDEX = new Map(
|
|
2008
|
+
FEATURED_AGENT_PROVIDERS.map((id, index) => [id, index])
|
|
2009
|
+
);
|
|
2010
|
+
function isFeaturedProvider(id) {
|
|
2011
|
+
return FEATURED_PROVIDER_INDEX.has(id);
|
|
2012
|
+
}
|
|
1904
2013
|
function modelKey(model) {
|
|
1905
2014
|
return `${model.provider}/${model.id}`;
|
|
1906
2015
|
}
|
|
@@ -1917,9 +2026,28 @@ function modelRelevance(model, query) {
|
|
|
1917
2026
|
if (name.includes(q)) return 4;
|
|
1918
2027
|
return void 0;
|
|
1919
2028
|
}
|
|
2029
|
+
function listAgentProviders(runtime, options = {}) {
|
|
2030
|
+
return runtime.getProviders().filter((provider) => !options.featuredOnly || isFeaturedProvider(provider.id)).map((provider) => ({
|
|
2031
|
+
id: provider.id,
|
|
2032
|
+
name: provider.name ?? provider.id,
|
|
2033
|
+
authenticated: runtime.hasConfiguredAuth(provider.id)
|
|
2034
|
+
})).sort((left, right) => {
|
|
2035
|
+
const leftRank = FEATURED_PROVIDER_INDEX.get(left.id);
|
|
2036
|
+
const rightRank = FEATURED_PROVIDER_INDEX.get(right.id);
|
|
2037
|
+
if (leftRank !== void 0 || rightRank !== void 0) {
|
|
2038
|
+
return (leftRank ?? Number.MAX_SAFE_INTEGER) - (rightRank ?? Number.MAX_SAFE_INTEGER) || left.id.localeCompare(right.id);
|
|
2039
|
+
}
|
|
2040
|
+
return left.id.localeCompare(right.id);
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
function findAgentProvider(runtime, id) {
|
|
2044
|
+
const needle = id.trim().toLowerCase();
|
|
2045
|
+
if (!needle) return void 0;
|
|
2046
|
+
return listAgentProviders(runtime).find((provider) => provider.id.toLowerCase() === needle);
|
|
2047
|
+
}
|
|
1920
2048
|
function listAgentModels(runtime, options = {}) {
|
|
1921
2049
|
let models = runtime.getModels(options.provider).filter(
|
|
1922
|
-
(model) => options.includeUnauthenticated || runtime.hasConfiguredAuth(model.provider)
|
|
2050
|
+
(model) => (options.includeUnauthenticated || runtime.hasConfiguredAuth(model.provider)) && (!options.featuredOnly || isFeaturedProvider(model.provider))
|
|
1923
2051
|
).map((model) => ({
|
|
1924
2052
|
provider: model.provider,
|
|
1925
2053
|
id: model.id,
|
|
@@ -1946,6 +2074,19 @@ function listAgentModels(runtime, options = {}) {
|
|
|
1946
2074
|
}
|
|
1947
2075
|
return models;
|
|
1948
2076
|
}
|
|
2077
|
+
function recommendAgentModels(runtime, options = {}) {
|
|
2078
|
+
const models = listAgentModels(runtime, {
|
|
2079
|
+
includeUnauthenticated: options.includeUnauthenticated ?? true
|
|
2080
|
+
});
|
|
2081
|
+
const picks = [];
|
|
2082
|
+
for (const recommendation of RECOMMENDED_AGENT_MODELS) {
|
|
2083
|
+
const match = recommendation.ids.map((id) => models.find(
|
|
2084
|
+
(model) => model.provider === recommendation.provider && model.id === id
|
|
2085
|
+
)).find((model) => model !== void 0);
|
|
2086
|
+
if (match) picks.push(match);
|
|
2087
|
+
}
|
|
2088
|
+
return picks;
|
|
2089
|
+
}
|
|
1949
2090
|
function describeAgentModel(runtime, env) {
|
|
1950
2091
|
const selection = getAgentSelection(env);
|
|
1951
2092
|
if (!selection) return void 0;
|
|
@@ -1961,6 +2102,21 @@ function describeAgentModel(runtime, env) {
|
|
|
1961
2102
|
supportsThinking: resolved.model.reasoning !== false
|
|
1962
2103
|
};
|
|
1963
2104
|
}
|
|
2105
|
+
async function loginAgentProvider(runtime, provider, apiKey) {
|
|
2106
|
+
const choice = findAgentProvider(runtime, provider);
|
|
2107
|
+
if (!choice) {
|
|
2108
|
+
throw new Error(unknownAgentProviderMessage(provider));
|
|
2109
|
+
}
|
|
2110
|
+
const key = apiKey.trim();
|
|
2111
|
+
if (!key) {
|
|
2112
|
+
throw new Error("API key cannot be empty.");
|
|
2113
|
+
}
|
|
2114
|
+
persistProviderApiKey(choice.id, key);
|
|
2115
|
+
if (typeof runtime.setRuntimeApiKey === "function") {
|
|
2116
|
+
await runtime.setRuntimeApiKey(choice.id, key);
|
|
2117
|
+
}
|
|
2118
|
+
return { provider: choice.id };
|
|
2119
|
+
}
|
|
1964
2120
|
function setAgentModel(runtime, env, provider, model, options = {}) {
|
|
1965
2121
|
const current = getAgentSelection(env);
|
|
1966
2122
|
const requestedThinking = options.thinking ?? current?.thinking ?? "medium";
|
|
@@ -1997,6 +2153,65 @@ function setAgentModel(runtime, env, provider, model, options = {}) {
|
|
|
1997
2153
|
return { selection, adjustedThinking: effectiveThinking !== requestedThinking };
|
|
1998
2154
|
}
|
|
1999
2155
|
|
|
2156
|
+
// src/secret-prompt.ts
|
|
2157
|
+
import { createInterface } from "readline/promises";
|
|
2158
|
+
import { stdin, stdout } from "process";
|
|
2159
|
+
import { Writable } from "stream";
|
|
2160
|
+
async function promptVisibleInput(message, input = stdin, output = stdout) {
|
|
2161
|
+
return await promptInput(message, false, input, output);
|
|
2162
|
+
}
|
|
2163
|
+
async function promptHiddenInput(message, input = stdin, output = stdout) {
|
|
2164
|
+
return await promptInput(message, true, input, output);
|
|
2165
|
+
}
|
|
2166
|
+
async function withDetachedKeypress(input, run) {
|
|
2167
|
+
const listeners = [...input.listeners("keypress")];
|
|
2168
|
+
input.removeAllListeners("keypress");
|
|
2169
|
+
try {
|
|
2170
|
+
return await run();
|
|
2171
|
+
} finally {
|
|
2172
|
+
input.removeAllListeners("keypress");
|
|
2173
|
+
for (const listener of listeners) {
|
|
2174
|
+
input.on("keypress", listener);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
async function promptInput(message, hidden, input, output) {
|
|
2179
|
+
if (input.isTTY !== true || output.isTTY !== true) {
|
|
2180
|
+
throw new Error("Provider login needs an interactive tt session.");
|
|
2181
|
+
}
|
|
2182
|
+
return await withDetachedKeypress(input, async () => {
|
|
2183
|
+
if (!hidden) {
|
|
2184
|
+
const rl2 = createInterface({ input, output, terminal: true });
|
|
2185
|
+
try {
|
|
2186
|
+
return await rl2.question(message);
|
|
2187
|
+
} finally {
|
|
2188
|
+
rl2.close();
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
let muted = false;
|
|
2192
|
+
const maskedOutput = new Writable({
|
|
2193
|
+
write(chunk, _encoding, callback) {
|
|
2194
|
+
if (!muted) output.write(chunk);
|
|
2195
|
+
callback();
|
|
2196
|
+
}
|
|
2197
|
+
});
|
|
2198
|
+
const rl = createInterface({
|
|
2199
|
+
input,
|
|
2200
|
+
output: maskedOutput,
|
|
2201
|
+
terminal: true
|
|
2202
|
+
});
|
|
2203
|
+
try {
|
|
2204
|
+
const pending = rl.question(message);
|
|
2205
|
+
muted = true;
|
|
2206
|
+
return await pending;
|
|
2207
|
+
} finally {
|
|
2208
|
+
muted = false;
|
|
2209
|
+
output.write("\n");
|
|
2210
|
+
rl.close();
|
|
2211
|
+
}
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2000
2215
|
// src/shell.ts
|
|
2001
2216
|
var AGENT_SLASH_COMMANDS = /* @__PURE__ */ new Set([
|
|
2002
2217
|
"new",
|
|
@@ -2155,6 +2370,7 @@ var SLASH_NAMES = /* @__PURE__ */ new Set([
|
|
|
2155
2370
|
"status",
|
|
2156
2371
|
"context",
|
|
2157
2372
|
"model",
|
|
2373
|
+
"login",
|
|
2158
2374
|
"clear",
|
|
2159
2375
|
"cd",
|
|
2160
2376
|
"exit"
|
|
@@ -2324,6 +2540,9 @@ function agentModelLabel(context) {
|
|
|
2324
2540
|
}
|
|
2325
2541
|
function renderShellBanner(snapshot) {
|
|
2326
2542
|
const heading = snapshot.version ? `${accent.bold("tt")} ${chalk.dim(`v${snapshot.version}`)}` : accent.bold("tt");
|
|
2543
|
+
const configured = Boolean(
|
|
2544
|
+
snapshot.context.agent?.provider && snapshot.context.agent?.model
|
|
2545
|
+
);
|
|
2327
2546
|
const lines = [
|
|
2328
2547
|
heading,
|
|
2329
2548
|
chalk.dim(
|
|
@@ -2331,7 +2550,9 @@ function renderShellBanner(snapshot) {
|
|
|
2331
2550
|
),
|
|
2332
2551
|
chalk.dim("ctrl+c stop/clear \xB7 ctrl+d exit \xB7 /help commands \xB7 tab complete"),
|
|
2333
2552
|
"",
|
|
2334
|
-
chalk.dim(
|
|
2553
|
+
chalk.dim(
|
|
2554
|
+
configured ? "Ask TT anything. Known commands run directly." : "Use /model to choose a provider and model. Workflow commands work now."
|
|
2555
|
+
)
|
|
2335
2556
|
];
|
|
2336
2557
|
return `${lines.join("\n")}
|
|
2337
2558
|
|
|
@@ -2440,45 +2661,74 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
2440
2661
|
const lines = styleDetailLines([
|
|
2441
2662
|
detailLine("Agent model", label),
|
|
2442
2663
|
detailLine("Change", "/model <provider>/<model>"),
|
|
2443
|
-
detailLine("
|
|
2664
|
+
detailLine("List", "/model <provider>"),
|
|
2665
|
+
detailLine("Search", "/model <query>"),
|
|
2666
|
+
detailLine("Login", "/login <provider>")
|
|
2444
2667
|
]);
|
|
2445
|
-
const
|
|
2446
|
-
|
|
2447
|
-
|
|
2668
|
+
const catalog = { includeUnauthenticated: true };
|
|
2669
|
+
const featuredCatalog = { ...catalog, featuredOnly: true };
|
|
2670
|
+
const provider = query ? findAgentProvider(runtime, query) : void 0;
|
|
2671
|
+
if (provider) {
|
|
2672
|
+
const matches = listAgentModels(runtime, { ...catalog, provider: provider.id });
|
|
2673
|
+
lines.push("", chalk.bold(`Models from ${provider.id}`));
|
|
2674
|
+
this.appendAgentModelChoices(lines, matches, summary, 20, "/model <query> to search");
|
|
2675
|
+
if (matches.length === 0) {
|
|
2676
|
+
lines.push(chalk.dim(" no models"));
|
|
2677
|
+
}
|
|
2448
2678
|
return lines;
|
|
2449
2679
|
}
|
|
2450
2680
|
if (query) {
|
|
2451
|
-
const matches = listAgentModels(runtime, { query });
|
|
2681
|
+
const matches = listAgentModels(runtime, { ...catalog, query });
|
|
2452
2682
|
lines.push("", chalk.bold(`Models matching ${JSON.stringify(query)}`));
|
|
2683
|
+
this.appendAgentModelChoices(lines, matches, summary, 10, "refine the query");
|
|
2453
2684
|
if (matches.length === 0) {
|
|
2454
2685
|
lines.push(chalk.dim(" no matches"));
|
|
2455
|
-
} else {
|
|
2456
|
-
const shown2 = matches.slice(0, 10);
|
|
2457
|
-
for (const model of shown2) {
|
|
2458
|
-
lines.push(this.formatAgentModelChoice(model, summary));
|
|
2459
|
-
}
|
|
2460
|
-
if (matches.length > shown2.length) {
|
|
2461
|
-
lines.push(chalk.dim(` \u2026 ${matches.length - shown2.length} more \u2014 refine the query`));
|
|
2462
|
-
}
|
|
2463
2686
|
}
|
|
2464
2687
|
return lines;
|
|
2465
2688
|
}
|
|
2466
|
-
const
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2689
|
+
const providers = listAgentProviders(runtime, { featuredOnly: true });
|
|
2690
|
+
lines.push("", chalk.bold("Providers"));
|
|
2691
|
+
if (providers.length === 0) {
|
|
2692
|
+
lines.push(chalk.dim(" no providers in the catalog"));
|
|
2693
|
+
} else {
|
|
2694
|
+
for (const choice of providers) {
|
|
2695
|
+
lines.push(this.formatAgentProviderChoice(choice));
|
|
2696
|
+
}
|
|
2697
|
+
lines.push(chalk.dim(" Other providers: /login <id> or /model <id>"));
|
|
2698
|
+
if (providers.some((choice) => !choice.authenticated)) {
|
|
2699
|
+
lines.push(chalk.dim(" Use /login <provider> to save a key."));
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
const suggestions = recommendAgentModels(runtime, featuredCatalog);
|
|
2703
|
+
lines.push("", chalk.bold("Suggestions"));
|
|
2704
|
+
this.appendAgentModelChoices(lines, suggestions, summary);
|
|
2705
|
+
if (suggestions.length === 0) {
|
|
2706
|
+
lines.push(chalk.dim(" no models in the catalog"));
|
|
2707
|
+
} else {
|
|
2708
|
+
lines.push(chalk.dim(" Use /model <provider> to list models, /model <query> to search"));
|
|
2709
|
+
}
|
|
2710
|
+
return lines;
|
|
2711
|
+
}
|
|
2712
|
+
appendAgentModelChoices(lines, models, summary, limit, moreHint) {
|
|
2713
|
+
const shown = limit ? models.slice(0, limit) : models;
|
|
2470
2714
|
for (const model of shown) {
|
|
2471
2715
|
lines.push(this.formatAgentModelChoice(model, summary));
|
|
2472
2716
|
}
|
|
2473
|
-
if (
|
|
2474
|
-
|
|
2717
|
+
if (limit && models.length > shown.length) {
|
|
2718
|
+
const hint = moreHint ? ` \u2014 ${moreHint}` : "";
|
|
2719
|
+
lines.push(chalk.dim(` \u2026 ${models.length - shown.length} more${hint}`));
|
|
2475
2720
|
}
|
|
2476
|
-
|
|
2721
|
+
}
|
|
2722
|
+
formatAgentProviderChoice(provider) {
|
|
2723
|
+
const displayName = provider.name !== provider.id ? ` ${provider.name}` : "";
|
|
2724
|
+
const auth = provider.authenticated ? "" : chalk.dim(" auth required");
|
|
2725
|
+
return ` ${accent(provider.id)}${displayName}${auth}`;
|
|
2477
2726
|
}
|
|
2478
2727
|
formatAgentModelChoice(model, summary) {
|
|
2479
2728
|
const active = summary?.provider === model.provider && summary?.model === model.id;
|
|
2480
2729
|
const displayName = model.name !== model.id ? ` ${model.name}` : "";
|
|
2481
|
-
|
|
2730
|
+
const auth = model.authenticated ? "" : chalk.dim(" auth required");
|
|
2731
|
+
return ` ${accent(`${model.provider}/${model.id}`)}${displayName}${active ? chalk.dim(" (active)") : ""}${auth}`;
|
|
2482
2732
|
}
|
|
2483
2733
|
async handleSlash(command) {
|
|
2484
2734
|
switch (command.name) {
|
|
@@ -2506,7 +2756,7 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
2506
2756
|
return "continue";
|
|
2507
2757
|
}
|
|
2508
2758
|
if (command.args.length !== 1) {
|
|
2509
|
-
throw new ShellParseError("Usage: /model [<query> | <provider>/<model>]");
|
|
2759
|
+
throw new ShellParseError("Usage: /model [<query> | <provider> | <provider>/<model>]");
|
|
2510
2760
|
}
|
|
2511
2761
|
const target = command.args[0];
|
|
2512
2762
|
const slash = target.indexOf("/");
|
|
@@ -2527,6 +2777,48 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
2527
2777
|
const note = result2.adjustedThinking ? " \u2014 thinking set to off for this model" : "";
|
|
2528
2778
|
this.io.write(
|
|
2529
2779
|
`${successMark()} Agent model: ${result2.selection.provider}/${result2.selection.model} (thinking ${result2.selection.thinking})${note}.
|
|
2780
|
+
`
|
|
2781
|
+
);
|
|
2782
|
+
return "continue";
|
|
2783
|
+
}
|
|
2784
|
+
case "login": {
|
|
2785
|
+
const runtime = await this.requireAgentModelRuntime();
|
|
2786
|
+
if (command.args.length > 1) {
|
|
2787
|
+
throw new ShellParseError("Usage: /login [<provider>]");
|
|
2788
|
+
}
|
|
2789
|
+
let providerId = command.args[0]?.trim();
|
|
2790
|
+
if (!providerId) {
|
|
2791
|
+
const providers = listAgentProviders(runtime, { featuredOnly: true });
|
|
2792
|
+
if (providers.length === 0) {
|
|
2793
|
+
throw new ShellParseError("No providers in the catalog.");
|
|
2794
|
+
}
|
|
2795
|
+
this.writeLines([
|
|
2796
|
+
"",
|
|
2797
|
+
chalk.bold("Providers"),
|
|
2798
|
+
...providers.map((choice) => this.formatAgentProviderChoice(choice)),
|
|
2799
|
+
chalk.dim(" Other providers: /login <id>")
|
|
2800
|
+
]);
|
|
2801
|
+
if (!this.io.promptLine) {
|
|
2802
|
+
throw new ShellParseError("Usage: /login <provider>");
|
|
2803
|
+
}
|
|
2804
|
+
providerId = (await this.io.promptLine("Provider: ")).trim();
|
|
2805
|
+
if (!providerId) {
|
|
2806
|
+
throw new ShellParseError("Usage: /login <provider>");
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
const provider = findAgentProvider(runtime, providerId);
|
|
2810
|
+
if (!provider) {
|
|
2811
|
+
throw new ShellParseError(unknownAgentProviderMessage(providerId));
|
|
2812
|
+
}
|
|
2813
|
+
if (!this.io.promptSecret) {
|
|
2814
|
+
throw new ShellParseError("Provider login needs an interactive tt session.");
|
|
2815
|
+
}
|
|
2816
|
+
const label = provider.name !== provider.id ? provider.name : provider.id;
|
|
2817
|
+
const apiKey = await this.io.promptSecret(`${label} API key: `);
|
|
2818
|
+
const result2 = await loginAgentProvider(runtime, provider.id, apiKey);
|
|
2819
|
+
await this.refreshContext();
|
|
2820
|
+
this.io.write(
|
|
2821
|
+
`${successMark()} Saved ${result2.provider} credentials. Chat and /model can use this provider now.
|
|
2530
2822
|
`
|
|
2531
2823
|
);
|
|
2532
2824
|
return "continue";
|
|
@@ -2653,6 +2945,26 @@ async function startInteractiveShell(options) {
|
|
|
2653
2945
|
}
|
|
2654
2946
|
let readline;
|
|
2655
2947
|
const terminalInput = input;
|
|
2948
|
+
const withPrompt = async (message, prompt) => {
|
|
2949
|
+
readline?.pause();
|
|
2950
|
+
const restoreRawMode = terminalInput.isRaw === true && typeof terminalInput.setRawMode === "function";
|
|
2951
|
+
if (restoreRawMode) terminalInput.setRawMode(false);
|
|
2952
|
+
try {
|
|
2953
|
+
return await prompt(message, input, output);
|
|
2954
|
+
} finally {
|
|
2955
|
+
if (restoreRawMode) terminalInput.setRawMode(true);
|
|
2956
|
+
readline?.resume();
|
|
2957
|
+
}
|
|
2958
|
+
};
|
|
2959
|
+
const io = {
|
|
2960
|
+
...streamIO(output, error),
|
|
2961
|
+
async promptLine(message) {
|
|
2962
|
+
return await withPrompt(message, promptVisibleInput);
|
|
2963
|
+
},
|
|
2964
|
+
async promptSecret(message) {
|
|
2965
|
+
return await withPrompt(message, promptHiddenInput);
|
|
2966
|
+
}
|
|
2967
|
+
};
|
|
2656
2968
|
const foregroundRunner = (request) => withForegroundSignalHandoff(
|
|
2657
2969
|
{
|
|
2658
2970
|
input: terminalInput,
|
|
@@ -2663,7 +2975,7 @@ async function startInteractiveShell(options) {
|
|
|
2663
2975
|
);
|
|
2664
2976
|
const session = await createShellSession({
|
|
2665
2977
|
runner: foregroundRunner,
|
|
2666
|
-
io
|
|
2978
|
+
io,
|
|
2667
2979
|
agent: options.agent,
|
|
2668
2980
|
cwd: options.cwd,
|
|
2669
2981
|
env: options.env,
|
|
@@ -2671,7 +2983,7 @@ async function startInteractiveShell(options) {
|
|
|
2671
2983
|
agentModelRuntime: options.agentModelRuntime
|
|
2672
2984
|
});
|
|
2673
2985
|
const completer = createCommandCompleter(() => session.snapshot().mode);
|
|
2674
|
-
readline =
|
|
2986
|
+
readline = createInterface2({
|
|
2675
2987
|
input,
|
|
2676
2988
|
output,
|
|
2677
2989
|
terminal: true,
|
|
@@ -2844,7 +3156,7 @@ var StreamingTerminalMarkdown = class {
|
|
|
2844
3156
|
var accent3 = chalk3.hex("#8B5CF6");
|
|
2845
3157
|
var successMark2 = () => chalk3.green("\u2713");
|
|
2846
3158
|
var errorMark2 = () => chalk3.red("\u2717");
|
|
2847
|
-
function
|
|
3159
|
+
function isRecord3(value) {
|
|
2848
3160
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2849
3161
|
}
|
|
2850
3162
|
function stringValue(value) {
|
|
@@ -2885,7 +3197,7 @@ function formatThread(thread, activeId) {
|
|
|
2885
3197
|
return `${active} ${chalk3.dim(thread.id.slice(0, 8))} ${sanitizeTerminalText2(thread.title)} ${chalk3.dim(dateLabel)}`;
|
|
2886
3198
|
}
|
|
2887
3199
|
function actionFromPayload(payload) {
|
|
2888
|
-
const value =
|
|
3200
|
+
const value = isRecord3(payload.action) ? payload.action : isRecord3(payload.approval) ? payload.approval : payload;
|
|
2889
3201
|
const id = stringValue(value.id);
|
|
2890
3202
|
const title = stringValue(value.title);
|
|
2891
3203
|
if (!id || !title) return null;
|
|
@@ -3018,7 +3330,7 @@ ${chalk3.yellow.bold("Approval required")} ${chalk3.dim(action.id.slice(0, 8))}
|
|
|
3018
3330
|
if (event.type === "tool_call") {
|
|
3019
3331
|
this.flushResponse();
|
|
3020
3332
|
this.endOpenLine();
|
|
3021
|
-
const name = stringValue(payload.name) ?? stringValue(
|
|
3333
|
+
const name = stringValue(payload.name) ?? stringValue(isRecord3(payload.tool_call) ? payload.tool_call.name : null) ?? "tool";
|
|
3022
3334
|
this.options.io.write(chalk3.dim(` \u25CB ${name}
|
|
3023
3335
|
`));
|
|
3024
3336
|
return;
|
|
@@ -3454,7 +3766,7 @@ function shortId(id) {
|
|
|
3454
3766
|
}
|
|
3455
3767
|
|
|
3456
3768
|
// src/local-output.ts
|
|
3457
|
-
function
|
|
3769
|
+
function isRecord4(value) {
|
|
3458
3770
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3459
3771
|
}
|
|
3460
3772
|
function text(value) {
|
|
@@ -3468,10 +3780,10 @@ function number(value) {
|
|
|
3468
3780
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3469
3781
|
}
|
|
3470
3782
|
function record3(value) {
|
|
3471
|
-
return
|
|
3783
|
+
return isRecord4(value) ? value : void 0;
|
|
3472
3784
|
}
|
|
3473
3785
|
function rows(value) {
|
|
3474
|
-
return Array.isArray(value) ? value.filter(
|
|
3786
|
+
return Array.isArray(value) ? value.filter(isRecord4) : [];
|
|
3475
3787
|
}
|
|
3476
3788
|
function displayId(value) {
|
|
3477
3789
|
const id = text(value);
|
|
@@ -3761,19 +4073,19 @@ function renderServePlan(value) {
|
|
|
3761
4073
|
function renderKnownJson(args2, value) {
|
|
3762
4074
|
const command = args2[0];
|
|
3763
4075
|
const subcommand = args2[1];
|
|
3764
|
-
if (command === "doctor" &&
|
|
4076
|
+
if (command === "doctor" && isRecord4(value)) {
|
|
3765
4077
|
renderDoctor(value);
|
|
3766
4078
|
return true;
|
|
3767
4079
|
}
|
|
3768
|
-
if (command === "init" &&
|
|
4080
|
+
if (command === "init" && isRecord4(value)) {
|
|
3769
4081
|
renderInit(value);
|
|
3770
4082
|
return true;
|
|
3771
4083
|
}
|
|
3772
|
-
if (command === "validate" &&
|
|
4084
|
+
if (command === "validate" && isRecord4(value)) {
|
|
3773
4085
|
renderValidate(value);
|
|
3774
4086
|
return true;
|
|
3775
4087
|
}
|
|
3776
|
-
if (command === "run" &&
|
|
4088
|
+
if (command === "run" && isRecord4(value)) {
|
|
3777
4089
|
renderRun(value);
|
|
3778
4090
|
return true;
|
|
3779
4091
|
}
|
|
@@ -3786,15 +4098,15 @@ function renderKnownJson(args2, value) {
|
|
|
3786
4098
|
renderRunEvents(value);
|
|
3787
4099
|
return true;
|
|
3788
4100
|
}
|
|
3789
|
-
if (subcommand === "get" &&
|
|
4101
|
+
if (subcommand === "get" && isRecord4(value)) {
|
|
3790
4102
|
renderRunRecord(value);
|
|
3791
4103
|
return true;
|
|
3792
4104
|
}
|
|
3793
|
-
if (subcommand === "report" &&
|
|
4105
|
+
if (subcommand === "report" && isRecord4(value)) {
|
|
3794
4106
|
renderRunReport(value);
|
|
3795
4107
|
return true;
|
|
3796
4108
|
}
|
|
3797
|
-
if (subcommand === "compare" &&
|
|
4109
|
+
if (subcommand === "compare" && isRecord4(value)) {
|
|
3798
4110
|
renderRunComparison(value);
|
|
3799
4111
|
return true;
|
|
3800
4112
|
}
|
|
@@ -3804,25 +4116,25 @@ function renderKnownJson(args2, value) {
|
|
|
3804
4116
|
renderModelsList(value);
|
|
3805
4117
|
return true;
|
|
3806
4118
|
}
|
|
3807
|
-
if ((subcommand === "get" || subcommand === "verify") &&
|
|
4119
|
+
if ((subcommand === "get" || subcommand === "verify") && isRecord4(value)) {
|
|
3808
4120
|
if (subcommand === "verify") printSuccess("Local model artifact is valid");
|
|
3809
4121
|
renderModelRecord(value);
|
|
3810
4122
|
return true;
|
|
3811
4123
|
}
|
|
3812
|
-
if ((subcommand === "prefetch" || subcommand === "verify-base") &&
|
|
4124
|
+
if ((subcommand === "prefetch" || subcommand === "verify-base") && isRecord4(value)) {
|
|
3813
4125
|
renderModelPrefetch(value);
|
|
3814
4126
|
return true;
|
|
3815
4127
|
}
|
|
3816
|
-
if (["active", "activate", "rollback"].includes(subcommand ?? "") &&
|
|
4128
|
+
if (["active", "activate", "rollback"].includes(subcommand ?? "") && isRecord4(value)) {
|
|
3817
4129
|
renderActiveModel(value);
|
|
3818
4130
|
return true;
|
|
3819
4131
|
}
|
|
3820
|
-
if (subcommand === "serve" &&
|
|
4132
|
+
if (subcommand === "serve" && isRecord4(value)) {
|
|
3821
4133
|
renderServePlan(value);
|
|
3822
4134
|
return true;
|
|
3823
4135
|
}
|
|
3824
4136
|
}
|
|
3825
|
-
if (command === "serve" &&
|
|
4137
|
+
if (command === "serve" && isRecord4(value)) {
|
|
3826
4138
|
renderServePlan(value);
|
|
3827
4139
|
return true;
|
|
3828
4140
|
}
|
|
@@ -4107,10 +4419,10 @@ function localCommandStreamsStdout(args2) {
|
|
|
4107
4419
|
const serves = args2[0] === "serve" || args2[0] === "models" && args2[1] === "serve";
|
|
4108
4420
|
return serves && !args2.includes("--print-command") && !args2.includes("--help") && !args2.includes("-h");
|
|
4109
4421
|
}
|
|
4110
|
-
function parseJsonOutput(
|
|
4111
|
-
if (!
|
|
4422
|
+
function parseJsonOutput(stdout2) {
|
|
4423
|
+
if (!stdout2.trim()) return { hasJson: false, json: void 0 };
|
|
4112
4424
|
try {
|
|
4113
|
-
return { hasJson: true, json: JSON.parse(
|
|
4425
|
+
return { hasJson: true, json: JSON.parse(stdout2) };
|
|
4114
4426
|
} catch {
|
|
4115
4427
|
return { hasJson: false, json: void 0 };
|
|
4116
4428
|
}
|
|
@@ -4156,7 +4468,7 @@ async function spawnLocalCli(args2) {
|
|
|
4156
4468
|
}
|
|
4157
4469
|
);
|
|
4158
4470
|
if (pipeInput && args2.stdin && child.stdin) args2.stdin.pipe(child.stdin);
|
|
4159
|
-
let
|
|
4471
|
+
let stdout2 = "";
|
|
4160
4472
|
let stderr = "";
|
|
4161
4473
|
child.stdout?.setEncoding("utf8");
|
|
4162
4474
|
child.stderr?.setEncoding("utf8");
|
|
@@ -4166,7 +4478,7 @@ async function spawnLocalCli(args2) {
|
|
|
4166
4478
|
args2.pathReplacements
|
|
4167
4479
|
);
|
|
4168
4480
|
if (args2.streamingStdout) writeStream(output, normalized);
|
|
4169
|
-
else
|
|
4481
|
+
else stdout2 += normalized;
|
|
4170
4482
|
});
|
|
4171
4483
|
child.stderr?.on("data", (chunk) => {
|
|
4172
4484
|
const normalized = normalizeProjectedPaths(
|
|
@@ -4211,7 +4523,7 @@ async function spawnLocalCli(args2) {
|
|
|
4211
4523
|
resolveChild({
|
|
4212
4524
|
exitCode: spawnError ? 1 : code2 ?? signalExitCode(signal),
|
|
4213
4525
|
signal,
|
|
4214
|
-
stdout,
|
|
4526
|
+
stdout: stdout2,
|
|
4215
4527
|
stderr,
|
|
4216
4528
|
...spawnError ? { errorMessage: spawnError.message } : {}
|
|
4217
4529
|
});
|
|
@@ -4611,7 +4923,7 @@ async function createDefaultAgentClient(runtime, env, workspaceRoot, getModelRun
|
|
|
4611
4923
|
const selection = getAgentSelection(env);
|
|
4612
4924
|
if (!selection) {
|
|
4613
4925
|
throw new Error(
|
|
4614
|
-
"The laptop-local agent is not configured.
|
|
4926
|
+
"The laptop-local agent is not configured. Use /model to choose a provider and model, then try again."
|
|
4615
4927
|
);
|
|
4616
4928
|
}
|
|
4617
4929
|
const modelRuntime = await getModelRuntime();
|
|
@@ -4620,8 +4932,9 @@ async function createDefaultAgentClient(runtime, env, workspaceRoot, getModelRun
|
|
|
4620
4932
|
const secret = getApiKey({ apiKey: env.TUNED_TENSOR_API_KEY });
|
|
4621
4933
|
const providerSecrets = Object.entries(env).filter(([name, value]) => value && /(?:API_KEY|TOKEN|SECRET)$/i.test(name)).map(([, value]) => value).filter((value) => value.length >= 8 && value !== secret);
|
|
4622
4934
|
return createLocalAgentClient({
|
|
4623
|
-
store: runtime.agentStore ?? new LocalAgentStore(
|
|
4624
|
-
secretValues: [...secret ? [secret] : [], ...providerSecrets]
|
|
4935
|
+
store: runtime.agentStore ?? new LocalAgentStore(getAgentConfigDir(), {
|
|
4936
|
+
secretValues: [...secret ? [secret] : [], ...providerSecrets],
|
|
4937
|
+
secretValueProvider: readStoredProviderSecrets
|
|
4625
4938
|
}),
|
|
4626
4939
|
workspaceRoot,
|
|
4627
4940
|
selection,
|
|
@@ -4893,7 +5206,7 @@ async function runCli(version, runtime = {}) {
|
|
|
4893
5206
|
}
|
|
4894
5207
|
|
|
4895
5208
|
// src/index.ts
|
|
4896
|
-
runCli("0.13.
|
|
5209
|
+
runCli("0.13.1-beta.1").catch((err) => {
|
|
4897
5210
|
reportError(err);
|
|
4898
5211
|
process.exit(1);
|
|
4899
5212
|
});
|