@kitnai/cli 0.1.34 → 0.1.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +360 -140
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -282,8 +282,8 @@ var init_config = __esm({
|
|
|
282
282
|
// src/installers/tsconfig-patcher.ts
|
|
283
283
|
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
284
284
|
import { join as join4 } from "path";
|
|
285
|
-
function stripJsonc(
|
|
286
|
-
return
|
|
285
|
+
function stripJsonc(text4) {
|
|
286
|
+
return text4.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/,\s*([}\]])/g, "$1");
|
|
287
287
|
}
|
|
288
288
|
function patchTsconfig(tsconfigContent, paths, removePrefixes) {
|
|
289
289
|
const config2 = JSON.parse(stripJsonc(tsconfigContent));
|
|
@@ -423,7 +423,7 @@ async function resolveDependencies(names, fetchItem) {
|
|
|
423
423
|
const visited = /* @__PURE__ */ new Set();
|
|
424
424
|
const items = /* @__PURE__ */ new Map();
|
|
425
425
|
const edges = [];
|
|
426
|
-
async function
|
|
426
|
+
async function resolve2(name) {
|
|
427
427
|
if (visited.has(name)) return;
|
|
428
428
|
visited.add(name);
|
|
429
429
|
const item = await fetchItem(name);
|
|
@@ -431,11 +431,11 @@ async function resolveDependencies(names, fetchItem) {
|
|
|
431
431
|
const deps = item.registryDependencies ?? [];
|
|
432
432
|
for (const dep of deps) {
|
|
433
433
|
edges.push([dep, name]);
|
|
434
|
-
await
|
|
434
|
+
await resolve2(dep);
|
|
435
435
|
}
|
|
436
436
|
}
|
|
437
437
|
for (const name of names) {
|
|
438
|
-
await
|
|
438
|
+
await resolve2(name);
|
|
439
439
|
}
|
|
440
440
|
return topologicalSort(items, edges);
|
|
441
441
|
}
|
|
@@ -3448,14 +3448,61 @@ var init_registry = __esm({
|
|
|
3448
3448
|
// src/commands/chat.ts
|
|
3449
3449
|
var chat_exports = {};
|
|
3450
3450
|
__export(chat_exports, {
|
|
3451
|
-
|
|
3451
|
+
applyCompaction: () => applyCompaction,
|
|
3452
|
+
buildServicePayload: () => buildServicePayload,
|
|
3452
3453
|
chatCommand: () => chatCommand,
|
|
3453
3454
|
fetchGlobalRegistries: () => fetchGlobalRegistries,
|
|
3455
|
+
formatElapsed: () => formatElapsed,
|
|
3454
3456
|
formatPlan: () => formatPlan,
|
|
3455
|
-
|
|
3457
|
+
formatSessionStats: () => formatSessionStats,
|
|
3458
|
+
formatStepLabel: () => formatStepLabel,
|
|
3459
|
+
formatTokens: () => formatTokens,
|
|
3460
|
+
handleAskUser: () => handleAskUser,
|
|
3461
|
+
handleCreatePlan: () => handleCreatePlan,
|
|
3462
|
+
handleListFiles: () => handleListFiles,
|
|
3463
|
+
handleReadFile: () => handleReadFile,
|
|
3464
|
+
handleUpdateEnv: () => handleUpdateEnv,
|
|
3465
|
+
handleUpdateEnvDirect: () => handleUpdateEnvDirect,
|
|
3466
|
+
handleWriteFile: () => handleWriteFile,
|
|
3467
|
+
hasToolCalls: () => hasToolCalls,
|
|
3468
|
+
resolveServiceUrl: () => resolveServiceUrl,
|
|
3469
|
+
shouldCompact: () => shouldCompact
|
|
3456
3470
|
});
|
|
3457
3471
|
import * as p16 from "@clack/prompts";
|
|
3458
3472
|
import pc15 from "picocolors";
|
|
3473
|
+
import { readFile as fsReadFile, writeFile as fsWriteFile, mkdir as mkdir8, readdir as readdir2 } from "fs/promises";
|
|
3474
|
+
import { join as join15, dirname as dirname7, resolve } from "path";
|
|
3475
|
+
function buildServicePayload(messages, metadata) {
|
|
3476
|
+
return { messages, metadata };
|
|
3477
|
+
}
|
|
3478
|
+
function hasToolCalls(response) {
|
|
3479
|
+
return !!(response.message.toolCalls && response.message.toolCalls.length > 0);
|
|
3480
|
+
}
|
|
3481
|
+
function formatTokens(count) {
|
|
3482
|
+
if (count >= 1e3) return `${(count / 1e3).toFixed(1)}k`;
|
|
3483
|
+
return `${count}`;
|
|
3484
|
+
}
|
|
3485
|
+
function formatElapsed(ms) {
|
|
3486
|
+
const seconds = Math.floor(ms / 1e3);
|
|
3487
|
+
if (seconds < 60) return `${seconds}s`;
|
|
3488
|
+
const minutes = Math.floor(seconds / 60);
|
|
3489
|
+
const remaining = seconds % 60;
|
|
3490
|
+
return `${minutes}m ${remaining}s`;
|
|
3491
|
+
}
|
|
3492
|
+
function formatSessionStats(elapsedMs, totalTokens) {
|
|
3493
|
+
return `Session: ${formatElapsed(elapsedMs)} | ${formatTokens(totalTokens)} tokens`;
|
|
3494
|
+
}
|
|
3495
|
+
function shouldCompact(lastPromptTokens, threshold) {
|
|
3496
|
+
return lastPromptTokens >= threshold;
|
|
3497
|
+
}
|
|
3498
|
+
function applyCompaction(messages, summary, keepRecent = 2) {
|
|
3499
|
+
const recent = messages.slice(-keepRecent);
|
|
3500
|
+
return [
|
|
3501
|
+
{ role: "user", content: `[Context from earlier in conversation]
|
|
3502
|
+
${summary}` },
|
|
3503
|
+
...recent
|
|
3504
|
+
];
|
|
3505
|
+
}
|
|
3459
3506
|
async function fetchGlobalRegistries(configuredNamespaces) {
|
|
3460
3507
|
let directory;
|
|
3461
3508
|
try {
|
|
@@ -3496,9 +3543,6 @@ async function resolveServiceUrl(urlOverride, chatServiceConfig) {
|
|
|
3496
3543
|
if (chatServiceConfig?.url) return chatServiceConfig.url;
|
|
3497
3544
|
return DEFAULT_SERVICE_URL;
|
|
3498
3545
|
}
|
|
3499
|
-
function buildRequestPayload(message, metadata) {
|
|
3500
|
-
return { message, metadata };
|
|
3501
|
-
}
|
|
3502
3546
|
function formatPlan(plan) {
|
|
3503
3547
|
const lines = [plan.summary, ""];
|
|
3504
3548
|
for (let i = 0; i < plan.steps.length; i++) {
|
|
@@ -3516,13 +3560,253 @@ function formatStepLabel(step) {
|
|
|
3516
3560
|
case "remove":
|
|
3517
3561
|
return `Remove ${pc15.red(step.component)}`;
|
|
3518
3562
|
case "create":
|
|
3519
|
-
return `Create ${pc15.green(
|
|
3563
|
+
return `Create ${pc15.green(step.name)} ${pc15.dim(`(${step.type})`)}`;
|
|
3520
3564
|
case "link":
|
|
3521
3565
|
return `Link ${pc15.cyan(step.toolName)} \u2192 ${pc15.cyan(step.agentName)}`;
|
|
3522
3566
|
case "unlink":
|
|
3523
3567
|
return `Unlink ${pc15.red(step.toolName)} from ${pc15.cyan(step.agentName)}`;
|
|
3524
3568
|
case "registry-add":
|
|
3525
3569
|
return `Add registry ${pc15.magenta(step.namespace)}`;
|
|
3570
|
+
case "update":
|
|
3571
|
+
return `Update ${pc15.yellow(step.component)}`;
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
async function executeStep(step) {
|
|
3575
|
+
switch (step.action) {
|
|
3576
|
+
case "registry-add": {
|
|
3577
|
+
const { registryAddCommand: registryAddCommand2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
|
|
3578
|
+
await registryAddCommand2(step.namespace, step.url, { overwrite: true });
|
|
3579
|
+
break;
|
|
3580
|
+
}
|
|
3581
|
+
case "add": {
|
|
3582
|
+
const { addCommand: addCommand2 } = await Promise.resolve().then(() => (init_add(), add_exports));
|
|
3583
|
+
await addCommand2([step.component], { yes: true });
|
|
3584
|
+
break;
|
|
3585
|
+
}
|
|
3586
|
+
case "create": {
|
|
3587
|
+
const { createCommand: createCommand2 } = await Promise.resolve().then(() => (init_create(), create_exports));
|
|
3588
|
+
await createCommand2(step.type, step.name);
|
|
3589
|
+
break;
|
|
3590
|
+
}
|
|
3591
|
+
case "link": {
|
|
3592
|
+
const { linkCommand: linkCommand2 } = await Promise.resolve().then(() => (init_link(), link_exports));
|
|
3593
|
+
await linkCommand2("tool", step.toolName, { to: step.agentName });
|
|
3594
|
+
break;
|
|
3595
|
+
}
|
|
3596
|
+
case "remove": {
|
|
3597
|
+
const { removeCommand: removeCommand2 } = await Promise.resolve().then(() => (init_remove(), remove_exports));
|
|
3598
|
+
await removeCommand2(step.component);
|
|
3599
|
+
break;
|
|
3600
|
+
}
|
|
3601
|
+
case "unlink": {
|
|
3602
|
+
const { unlinkCommand: unlinkCommand2 } = await Promise.resolve().then(() => (init_unlink(), unlink_exports));
|
|
3603
|
+
await unlinkCommand2("tool", step.toolName, { from: step.agentName });
|
|
3604
|
+
break;
|
|
3605
|
+
}
|
|
3606
|
+
case "update": {
|
|
3607
|
+
const { addCommand: addCommand2 } = await Promise.resolve().then(() => (init_add(), add_exports));
|
|
3608
|
+
await addCommand2([step.component], { overwrite: true, yes: true });
|
|
3609
|
+
break;
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
async function handleAskUser(input) {
|
|
3614
|
+
const responses = [];
|
|
3615
|
+
for (const item of input.items) {
|
|
3616
|
+
switch (item.type) {
|
|
3617
|
+
case "info":
|
|
3618
|
+
p16.log.info(item.text);
|
|
3619
|
+
break;
|
|
3620
|
+
case "warning":
|
|
3621
|
+
p16.log.warn(item.text);
|
|
3622
|
+
break;
|
|
3623
|
+
case "confirmation": {
|
|
3624
|
+
const confirmed = await p16.confirm({ message: item.text });
|
|
3625
|
+
if (p16.isCancel(confirmed)) return "User cancelled.";
|
|
3626
|
+
responses.push(confirmed ? "Yes" : "No");
|
|
3627
|
+
break;
|
|
3628
|
+
}
|
|
3629
|
+
case "option": {
|
|
3630
|
+
if (!item.choices?.length) {
|
|
3631
|
+
responses.push("No choices provided.");
|
|
3632
|
+
break;
|
|
3633
|
+
}
|
|
3634
|
+
const selected = await p16.select({
|
|
3635
|
+
message: item.text,
|
|
3636
|
+
options: item.choices.map((c) => ({ value: c, label: c }))
|
|
3637
|
+
});
|
|
3638
|
+
if (p16.isCancel(selected)) return "User cancelled.";
|
|
3639
|
+
responses.push(`User selected: ${selected}`);
|
|
3640
|
+
break;
|
|
3641
|
+
}
|
|
3642
|
+
case "question": {
|
|
3643
|
+
const answer = await p16.text({ message: item.text, placeholder: item.context });
|
|
3644
|
+
if (p16.isCancel(answer)) return "User cancelled.";
|
|
3645
|
+
responses.push(`User answered: ${answer}`);
|
|
3646
|
+
break;
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
return responses.join("\n");
|
|
3651
|
+
}
|
|
3652
|
+
async function handleCreatePlan(plan, cwd) {
|
|
3653
|
+
p16.log.message(formatPlan(plan));
|
|
3654
|
+
const steps = plan.steps;
|
|
3655
|
+
let selectedSteps;
|
|
3656
|
+
if (steps.length === 1) {
|
|
3657
|
+
const confirm7 = await p16.confirm({ message: `Run: ${formatStepLabel(steps[0])}?` });
|
|
3658
|
+
if (p16.isCancel(confirm7) || !confirm7) return "User cancelled the plan.";
|
|
3659
|
+
selectedSteps = steps;
|
|
3660
|
+
} else {
|
|
3661
|
+
const action = await p16.select({
|
|
3662
|
+
message: "How would you like to proceed?",
|
|
3663
|
+
options: [
|
|
3664
|
+
{ value: "all", label: "Yes, run all steps" },
|
|
3665
|
+
{ value: "select", label: "Select which steps to run" },
|
|
3666
|
+
{ value: "cancel", label: "Cancel" }
|
|
3667
|
+
]
|
|
3668
|
+
});
|
|
3669
|
+
if (p16.isCancel(action) || action === "cancel") return "User cancelled the plan.";
|
|
3670
|
+
if (action === "select") {
|
|
3671
|
+
const choices = await p16.multiselect({
|
|
3672
|
+
message: "Select steps to run:",
|
|
3673
|
+
options: steps.map((step, i) => ({ value: i, label: `${formatStepLabel(step)} - ${step.reason}` }))
|
|
3674
|
+
});
|
|
3675
|
+
if (p16.isCancel(choices)) return "User cancelled the plan.";
|
|
3676
|
+
selectedSteps = choices.map((i) => steps[i]);
|
|
3677
|
+
} else {
|
|
3678
|
+
selectedSteps = steps;
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
const results = [];
|
|
3682
|
+
const s = p16.spinner();
|
|
3683
|
+
for (const step of selectedSteps) {
|
|
3684
|
+
s.start(`Running: ${formatStepLabel(step)}...`);
|
|
3685
|
+
try {
|
|
3686
|
+
await executeStep(step);
|
|
3687
|
+
s.stop(pc15.green(`Done: ${formatStepLabel(step)}`));
|
|
3688
|
+
results.push(`Completed: ${step.action} ${step.component ?? step.name ?? ""}`);
|
|
3689
|
+
} catch (err) {
|
|
3690
|
+
s.stop(pc15.red(`Failed: ${formatStepLabel(step)}`));
|
|
3691
|
+
results.push(`Failed: ${step.action} ${step.component ?? step.name ?? ""} \u2014 ${err.message}`);
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
return results.join("\n");
|
|
3695
|
+
}
|
|
3696
|
+
async function handleWriteFile(input, cwd) {
|
|
3697
|
+
const fullPath = resolve(cwd, input.path);
|
|
3698
|
+
if (!fullPath.startsWith(resolve(cwd))) {
|
|
3699
|
+
return `Rejected: path '${input.path}' would escape project directory`;
|
|
3700
|
+
}
|
|
3701
|
+
await mkdir8(dirname7(fullPath), { recursive: true });
|
|
3702
|
+
await fsWriteFile(fullPath, input.content, "utf-8");
|
|
3703
|
+
p16.log.success(`Wrote ${pc15.cyan(input.path)}${input.description ? ` \u2014 ${input.description}` : ""}`);
|
|
3704
|
+
return `Wrote ${input.path}`;
|
|
3705
|
+
}
|
|
3706
|
+
async function handleReadFile(input, cwd) {
|
|
3707
|
+
const fullPath = resolve(cwd, input.path);
|
|
3708
|
+
if (!fullPath.startsWith(resolve(cwd))) {
|
|
3709
|
+
return `Rejected: path '${input.path}' would escape project directory`;
|
|
3710
|
+
}
|
|
3711
|
+
try {
|
|
3712
|
+
return await fsReadFile(fullPath, "utf-8");
|
|
3713
|
+
} catch {
|
|
3714
|
+
return `File not found: ${input.path}`;
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
async function handleListFiles(input, cwd) {
|
|
3718
|
+
const searchDir = input.directory ? join15(cwd, input.directory) : cwd;
|
|
3719
|
+
try {
|
|
3720
|
+
const entries = await readdir2(searchDir, { recursive: true });
|
|
3721
|
+
const filtered = entries.filter((e) => {
|
|
3722
|
+
if (input.pattern.startsWith("*.")) {
|
|
3723
|
+
const ext = input.pattern.slice(1);
|
|
3724
|
+
return String(e).endsWith(ext);
|
|
3725
|
+
}
|
|
3726
|
+
return true;
|
|
3727
|
+
});
|
|
3728
|
+
return filtered.length > 0 ? `Files found:
|
|
3729
|
+
${filtered.join("\n")}` : "No files found matching the pattern.";
|
|
3730
|
+
} catch {
|
|
3731
|
+
return `Directory not found: ${input.directory ?? "."}`;
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
async function handleUpdateEnv(input, cwd) {
|
|
3735
|
+
const value = await p16.password({ message: `Enter ${input.key} (${input.description}):` });
|
|
3736
|
+
if (p16.isCancel(value) || !value) return "User cancelled.";
|
|
3737
|
+
return handleUpdateEnvDirect(input, cwd, value);
|
|
3738
|
+
}
|
|
3739
|
+
async function handleUpdateEnvDirect(input, cwd, value) {
|
|
3740
|
+
const envPath = join15(cwd, ".env");
|
|
3741
|
+
let existing = "";
|
|
3742
|
+
try {
|
|
3743
|
+
existing = await fsReadFile(envPath, "utf-8");
|
|
3744
|
+
} catch {
|
|
3745
|
+
}
|
|
3746
|
+
const lines = existing.split("\n");
|
|
3747
|
+
const keyIndex = lines.findIndex((l) => l.startsWith(`${input.key}=`));
|
|
3748
|
+
let newContent;
|
|
3749
|
+
if (keyIndex >= 0) {
|
|
3750
|
+
lines[keyIndex] = `${input.key}=${value}`;
|
|
3751
|
+
newContent = lines.join("\n");
|
|
3752
|
+
} else {
|
|
3753
|
+
newContent = existing + (existing && !existing.endsWith("\n") ? "\n" : "") + `${input.key}=${value}
|
|
3754
|
+
`;
|
|
3755
|
+
}
|
|
3756
|
+
await fsWriteFile(envPath, newContent, "utf-8");
|
|
3757
|
+
p16.log.success(`Set ${pc15.cyan(input.key)} in .env`);
|
|
3758
|
+
return `Successfully set ${input.key} in .env`;
|
|
3759
|
+
}
|
|
3760
|
+
async function handleToolCalls(toolCalls, cwd) {
|
|
3761
|
+
const results = [];
|
|
3762
|
+
for (const call of toolCalls) {
|
|
3763
|
+
let result;
|
|
3764
|
+
try {
|
|
3765
|
+
switch (call.name) {
|
|
3766
|
+
case "askUser":
|
|
3767
|
+
result = await handleAskUser(call.input);
|
|
3768
|
+
break;
|
|
3769
|
+
case "createPlan":
|
|
3770
|
+
result = await handleCreatePlan(call.input, cwd);
|
|
3771
|
+
break;
|
|
3772
|
+
case "writeFile":
|
|
3773
|
+
result = await handleWriteFile(call.input, cwd);
|
|
3774
|
+
break;
|
|
3775
|
+
case "readFile":
|
|
3776
|
+
result = await handleReadFile(call.input, cwd);
|
|
3777
|
+
break;
|
|
3778
|
+
case "listFiles":
|
|
3779
|
+
result = await handleListFiles(call.input, cwd);
|
|
3780
|
+
break;
|
|
3781
|
+
case "updateEnv":
|
|
3782
|
+
result = await handleUpdateEnv(call.input, cwd);
|
|
3783
|
+
break;
|
|
3784
|
+
default:
|
|
3785
|
+
result = `Unknown tool: ${call.name}`;
|
|
3786
|
+
}
|
|
3787
|
+
} catch (err) {
|
|
3788
|
+
result = `Error executing ${call.name}: ${err.message ?? "Unknown error"}`;
|
|
3789
|
+
}
|
|
3790
|
+
results.push({ toolCallId: call.id, result });
|
|
3791
|
+
}
|
|
3792
|
+
return results;
|
|
3793
|
+
}
|
|
3794
|
+
async function compactConversation(messages, serviceUrl) {
|
|
3795
|
+
try {
|
|
3796
|
+
const headers = { "Content-Type": "application/json" };
|
|
3797
|
+
if (process.env.KITN_API_KEY) headers["Authorization"] = `Bearer ${process.env.KITN_API_KEY}`;
|
|
3798
|
+
const res = await fetch(`${serviceUrl}/api/chat/compact`, {
|
|
3799
|
+
method: "POST",
|
|
3800
|
+
headers,
|
|
3801
|
+
body: JSON.stringify({ messages })
|
|
3802
|
+
});
|
|
3803
|
+
if (res.ok) {
|
|
3804
|
+
const data = await res.json();
|
|
3805
|
+
const compacted = applyCompaction(messages, data.summary);
|
|
3806
|
+
messages.length = 0;
|
|
3807
|
+
messages.push(...compacted);
|
|
3808
|
+
}
|
|
3809
|
+
} catch {
|
|
3526
3810
|
}
|
|
3527
3811
|
}
|
|
3528
3812
|
async function chatCommand(message, opts) {
|
|
@@ -3532,11 +3816,18 @@ async function chatCommand(message, opts) {
|
|
|
3532
3816
|
p16.log.error("No kitn.json found. Run `kitn init` first.");
|
|
3533
3817
|
process.exit(1);
|
|
3534
3818
|
}
|
|
3819
|
+
p16.intro(pc15.bold("kitn assistant"));
|
|
3535
3820
|
if (!message) {
|
|
3536
|
-
|
|
3537
|
-
|
|
3821
|
+
const input = await p16.text({
|
|
3822
|
+
message: "What would you like to do?",
|
|
3823
|
+
placeholder: "e.g., I want to build a weather agent"
|
|
3824
|
+
});
|
|
3825
|
+
if (p16.isCancel(input) || !input) {
|
|
3826
|
+
p16.cancel("Cancelled.");
|
|
3827
|
+
return;
|
|
3828
|
+
}
|
|
3829
|
+
message = input;
|
|
3538
3830
|
}
|
|
3539
|
-
p16.intro(pc15.bold("kitn assistant"));
|
|
3540
3831
|
const s = p16.spinner();
|
|
3541
3832
|
s.start("Gathering project context...");
|
|
3542
3833
|
let registryIndex;
|
|
@@ -3568,141 +3859,70 @@ async function chatCommand(message, opts) {
|
|
|
3568
3859
|
globalRegistryIndex = globalEntries.length > 0 ? globalEntries : void 0;
|
|
3569
3860
|
} catch {
|
|
3570
3861
|
s.stop(pc15.red("Failed to gather context"));
|
|
3571
|
-
p16.log.error("Could not read project context.
|
|
3862
|
+
p16.log.error("Could not read project context.");
|
|
3572
3863
|
process.exit(1);
|
|
3573
3864
|
}
|
|
3574
3865
|
s.stop("Context gathered");
|
|
3575
|
-
|
|
3866
|
+
const messages = [{ role: "user", content: message }];
|
|
3576
3867
|
const serviceUrl = await resolveServiceUrl(opts?.url, config2.chatService);
|
|
3577
3868
|
const metadata = { registryIndex, installed };
|
|
3578
3869
|
if (globalRegistryIndex) metadata.globalRegistryIndex = globalRegistryIndex;
|
|
3579
|
-
|
|
3580
|
-
let
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
if (
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
});
|
|
3593
|
-
} catch (err) {
|
|
3594
|
-
s.stop(pc15.red("Connection failed"));
|
|
3595
|
-
p16.log.error(`Could not reach chat service at ${serviceUrl}. ${err.message ?? ""}`);
|
|
3596
|
-
process.exit(1);
|
|
3597
|
-
}
|
|
3598
|
-
if (!response.ok) {
|
|
3599
|
-
s.stop(pc15.red("Request failed"));
|
|
3600
|
-
p16.log.error(`Chat service returned ${response.status}: ${response.statusText}`);
|
|
3601
|
-
process.exit(1);
|
|
3602
|
-
}
|
|
3603
|
-
let data;
|
|
3604
|
-
try {
|
|
3605
|
-
data = await response.json();
|
|
3606
|
-
} catch {
|
|
3607
|
-
s.stop(pc15.red("Invalid response"));
|
|
3608
|
-
p16.log.error("Chat service returned an invalid response.");
|
|
3609
|
-
process.exit(1);
|
|
3610
|
-
}
|
|
3611
|
-
s.stop("Done");
|
|
3612
|
-
if (data.rejected) {
|
|
3613
|
-
p16.log.warn(data.text ?? "Request was rejected by the assistant.");
|
|
3614
|
-
p16.outro("Try rephrasing your request.");
|
|
3615
|
-
return;
|
|
3616
|
-
}
|
|
3617
|
-
if (!data.plan) {
|
|
3618
|
-
p16.log.info(data.text ?? "No actionable plan returned.");
|
|
3619
|
-
p16.outro("Nothing to do.");
|
|
3620
|
-
return;
|
|
3621
|
-
}
|
|
3622
|
-
p16.log.message(formatPlan(data.plan));
|
|
3623
|
-
const steps = data.plan.steps;
|
|
3624
|
-
let selectedSteps;
|
|
3625
|
-
if (steps.length === 1) {
|
|
3626
|
-
const confirm7 = await p16.confirm({
|
|
3627
|
-
message: `Run: ${formatStepLabel(steps[0])}?`
|
|
3628
|
-
});
|
|
3629
|
-
if (p16.isCancel(confirm7) || !confirm7) {
|
|
3630
|
-
p16.cancel("Cancelled.");
|
|
3631
|
-
return;
|
|
3632
|
-
}
|
|
3633
|
-
selectedSteps = steps;
|
|
3634
|
-
} else {
|
|
3635
|
-
const action = await p16.select({
|
|
3636
|
-
message: "How would you like to proceed?",
|
|
3637
|
-
options: [
|
|
3638
|
-
{ value: "all", label: "Yes, run all steps" },
|
|
3639
|
-
{ value: "select", label: "Select which steps to run" },
|
|
3640
|
-
{ value: "cancel", label: "Cancel" }
|
|
3641
|
-
]
|
|
3642
|
-
});
|
|
3643
|
-
if (p16.isCancel(action) || action === "cancel") {
|
|
3644
|
-
p16.cancel("Cancelled.");
|
|
3645
|
-
return;
|
|
3646
|
-
}
|
|
3647
|
-
if (action === "select") {
|
|
3648
|
-
const choices = await p16.multiselect({
|
|
3649
|
-
message: "Select steps to run:",
|
|
3650
|
-
options: steps.map((step, i) => ({
|
|
3651
|
-
value: i,
|
|
3652
|
-
label: `${formatStepLabel(step)} - ${step.reason}`
|
|
3653
|
-
}))
|
|
3654
|
-
});
|
|
3655
|
-
if (p16.isCancel(choices)) {
|
|
3656
|
-
p16.cancel("Cancelled.");
|
|
3657
|
-
return;
|
|
3658
|
-
}
|
|
3659
|
-
selectedSteps = choices.map((i) => steps[i]);
|
|
3660
|
-
} else {
|
|
3661
|
-
selectedSteps = steps;
|
|
3662
|
-
}
|
|
3663
|
-
}
|
|
3664
|
-
for (const step of selectedSteps) {
|
|
3665
|
-
s.start(`Running: ${formatStepLabel(step)}...`);
|
|
3870
|
+
let totalTokens = 0;
|
|
3871
|
+
let lastInputTokens = 0;
|
|
3872
|
+
const sessionStart = Date.now();
|
|
3873
|
+
const MAX_PROMPT_TOKENS = 1e5;
|
|
3874
|
+
while (true) {
|
|
3875
|
+
const turnStart = Date.now();
|
|
3876
|
+
if (shouldCompact(lastInputTokens, MAX_PROMPT_TOKENS)) {
|
|
3877
|
+
s.start("Compacting conversation...");
|
|
3878
|
+
await compactConversation(messages, serviceUrl);
|
|
3879
|
+
s.stop(pc15.dim("Conversation compacted."));
|
|
3880
|
+
}
|
|
3881
|
+
s.start("Thinking...");
|
|
3882
|
+
let response;
|
|
3666
3883
|
try {
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
case "create": {
|
|
3679
|
-
const { createCommand: createCommand2 } = await Promise.resolve().then(() => (init_create(), create_exports));
|
|
3680
|
-
await createCommand2(step.type, step.name);
|
|
3681
|
-
break;
|
|
3682
|
-
}
|
|
3683
|
-
case "link": {
|
|
3684
|
-
const { linkCommand: linkCommand2 } = await Promise.resolve().then(() => (init_link(), link_exports));
|
|
3685
|
-
await linkCommand2("tool", step.toolName, { to: step.agentName });
|
|
3686
|
-
break;
|
|
3687
|
-
}
|
|
3688
|
-
case "remove": {
|
|
3689
|
-
const { removeCommand: removeCommand2 } = await Promise.resolve().then(() => (init_remove(), remove_exports));
|
|
3690
|
-
await removeCommand2(step.component);
|
|
3691
|
-
break;
|
|
3692
|
-
}
|
|
3693
|
-
case "unlink": {
|
|
3694
|
-
const { unlinkCommand: unlinkCommand2 } = await Promise.resolve().then(() => (init_unlink(), unlink_exports));
|
|
3695
|
-
await unlinkCommand2("tool", step.toolName, { from: step.agentName });
|
|
3696
|
-
break;
|
|
3697
|
-
}
|
|
3884
|
+
const headers = { "Content-Type": "application/json" };
|
|
3885
|
+
if (process.env.KITN_API_KEY) headers["Authorization"] = `Bearer ${process.env.KITN_API_KEY}`;
|
|
3886
|
+
const res = await fetch(`${serviceUrl}/api/chat`, {
|
|
3887
|
+
method: "POST",
|
|
3888
|
+
headers,
|
|
3889
|
+
body: JSON.stringify(buildServicePayload(messages, metadata))
|
|
3890
|
+
});
|
|
3891
|
+
if (!res.ok) {
|
|
3892
|
+
s.stop(pc15.red("Request failed"));
|
|
3893
|
+
p16.log.error(`Chat service returned ${res.status}: ${res.statusText}`);
|
|
3894
|
+
break;
|
|
3698
3895
|
}
|
|
3699
|
-
|
|
3896
|
+
response = await res.json();
|
|
3700
3897
|
} catch (err) {
|
|
3701
|
-
s.stop(pc15.red(
|
|
3702
|
-
p16.log.error(err.message ?? "
|
|
3898
|
+
s.stop(pc15.red("Connection failed"));
|
|
3899
|
+
p16.log.error(`Could not reach chat service at ${serviceUrl}. ${err.message ?? ""}`);
|
|
3900
|
+
break;
|
|
3703
3901
|
}
|
|
3902
|
+
const elapsed = Date.now() - turnStart;
|
|
3903
|
+
totalTokens += response.usage.outputTokens;
|
|
3904
|
+
lastInputTokens = response.usage.inputTokens;
|
|
3905
|
+
if (response.rejected) {
|
|
3906
|
+
s.stop("Done");
|
|
3907
|
+
p16.log.warn(response.text ?? "Request was rejected.");
|
|
3908
|
+
break;
|
|
3909
|
+
}
|
|
3910
|
+
if (!hasToolCalls(response)) {
|
|
3911
|
+
s.stop(`Done ${pc15.dim(`(${formatElapsed(elapsed)} | ${formatTokens(response.usage.outputTokens)} tokens)`)}`);
|
|
3912
|
+
if (response.message.content) p16.log.message(response.message.content);
|
|
3913
|
+
break;
|
|
3914
|
+
}
|
|
3915
|
+
s.stop(`${pc15.dim(`(${formatElapsed(elapsed)} | ${formatTokens(response.usage.outputTokens)} tokens)`)}`);
|
|
3916
|
+
messages.push({
|
|
3917
|
+
role: "assistant",
|
|
3918
|
+
content: response.message.content,
|
|
3919
|
+
toolCalls: response.message.toolCalls
|
|
3920
|
+
});
|
|
3921
|
+
const toolResults = await handleToolCalls(response.message.toolCalls, cwd);
|
|
3922
|
+
messages.push({ role: "tool", toolResults });
|
|
3704
3923
|
}
|
|
3705
|
-
|
|
3924
|
+
const totalElapsed = Date.now() - sessionStart;
|
|
3925
|
+
p16.outro(pc15.green("Done! ") + pc15.dim(formatSessionStats(totalElapsed, totalTokens)));
|
|
3706
3926
|
}
|
|
3707
3927
|
var DEFAULT_SERVICE_URL, GLOBAL_REGISTRY_URL;
|
|
3708
3928
|
var init_chat = __esm({
|
|
@@ -3719,7 +3939,7 @@ var init_chat = __esm({
|
|
|
3719
3939
|
// src/index.ts
|
|
3720
3940
|
init_update_check();
|
|
3721
3941
|
import { Command } from "commander";
|
|
3722
|
-
var VERSION = true ? "0.1.
|
|
3942
|
+
var VERSION = true ? "0.1.36" : "0.0.0-dev";
|
|
3723
3943
|
var printUpdateNotice = startUpdateCheck(VERSION);
|
|
3724
3944
|
var program = new Command().name("kitn").description("Install AI agent components from the kitn registry").version(VERSION);
|
|
3725
3945
|
program.command("init").description("Initialize kitn in your project").option("-r, --runtime <runtime>", "runtime to use (bun, node, deno)").option("-f, --framework <framework>", "HTTP framework (hono, hono-openapi, elysia)").option("-b, --base <path>", "base directory for components (default: src/ai)").option("-y, --yes", "accept all defaults without prompting").action(async (opts) => {
|
|
@@ -3770,7 +3990,7 @@ program.command("rules").description("Regenerate AI coding tool rules files").ac
|
|
|
3770
3990
|
const { rulesCommand: rulesCommand2 } = await Promise.resolve().then(() => (init_rules(), rules_exports));
|
|
3771
3991
|
await rulesCommand2();
|
|
3772
3992
|
});
|
|
3773
|
-
program.command("chat").description("AI-powered scaffolding assistant
|
|
3993
|
+
program.command("chat").description("AI-powered scaffolding assistant").argument("[message]", "What you want to do").option("-u, --url <url>", "Chat service URL").action(async (message, opts) => {
|
|
3774
3994
|
const { chatCommand: chatCommand2 } = await Promise.resolve().then(() => (init_chat(), chat_exports));
|
|
3775
3995
|
await chatCommand2(message, opts);
|
|
3776
3996
|
});
|