@mariozechner/pi-coding-agent 0.8.5 → 0.9.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/CHANGELOG.md +17 -1
- package/README.md +50 -11
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +131 -68
- package/dist/main.js.map +1 -1
- package/dist/session-manager.d.ts +2 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +6 -0
- package/dist/session-manager.js.map +1 -1
- package/dist/settings-manager.d.ts +3 -0
- package/dist/settings-manager.d.ts.map +1 -1
- package/dist/settings-manager.js +7 -0
- package/dist/settings-manager.js.map +1 -1
- package/dist/tui/tui-renderer.d.ts +9 -5
- package/dist/tui/tui-renderer.d.ts.map +1 -1
- package/dist/tui/tui-renderer.js +135 -39
- package/dist/tui/tui-renderer.js.map +1 -1
- package/package.json +4 -4
package/dist/main.js
CHANGED
|
@@ -18,16 +18,6 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
18
18
|
const __dirname = dirname(__filename);
|
|
19
19
|
const packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
|
20
20
|
const VERSION = packageJson.version;
|
|
21
|
-
const envApiKeyMap = {
|
|
22
|
-
google: ["GEMINI_API_KEY"],
|
|
23
|
-
openai: ["OPENAI_API_KEY"],
|
|
24
|
-
anthropic: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
|
|
25
|
-
xai: ["XAI_API_KEY"],
|
|
26
|
-
groq: ["GROQ_API_KEY"],
|
|
27
|
-
cerebras: ["CEREBRAS_API_KEY"],
|
|
28
|
-
openrouter: ["OPENROUTER_API_KEY"],
|
|
29
|
-
zai: ["ZAI_API_KEY"],
|
|
30
|
-
};
|
|
31
21
|
const defaultModelPerProvider = {
|
|
32
22
|
anthropic: "claude-sonnet-4-5",
|
|
33
23
|
openai: "gpt-5.1-codex",
|
|
@@ -80,6 +70,18 @@ function parseArgs(args) {
|
|
|
80
70
|
else if (arg === "--models" && i + 1 < args.length) {
|
|
81
71
|
result.models = args[++i].split(",").map((s) => s.trim());
|
|
82
72
|
}
|
|
73
|
+
else if (arg === "--thinking" && i + 1 < args.length) {
|
|
74
|
+
const level = args[++i];
|
|
75
|
+
if (level === "off" || level === "minimal" || level === "low" || level === "medium" || level === "high") {
|
|
76
|
+
result.thinking = level;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.error(chalk.yellow(`Warning: Invalid thinking level "${level}". Valid values: off, minimal, low, medium, high`));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else if (arg === "--print" || arg === "-p") {
|
|
83
|
+
result.print = true;
|
|
84
|
+
}
|
|
83
85
|
else if (!arg.startsWith("-")) {
|
|
84
86
|
result.messages.push(arg);
|
|
85
87
|
}
|
|
@@ -98,21 +100,26 @@ ${chalk.bold("Options:")}
|
|
|
98
100
|
--api-key <key> API key (defaults to env vars)
|
|
99
101
|
--system-prompt <text> System prompt (default: coding assistant prompt)
|
|
100
102
|
--mode <mode> Output mode: text (default), json, or rpc
|
|
103
|
+
--print, -p Non-interactive mode: process prompt and exit
|
|
101
104
|
--continue, -c Continue previous session
|
|
102
105
|
--resume, -r Select a session to resume
|
|
103
106
|
--session <path> Use specific session file
|
|
104
107
|
--no-session Don't save session (ephemeral)
|
|
105
108
|
--models <patterns> Comma-separated model patterns for quick cycling with Ctrl+P
|
|
109
|
+
--thinking <level> Set thinking level: off, minimal, low, medium, high
|
|
106
110
|
--help, -h Show this help
|
|
107
111
|
|
|
108
112
|
${chalk.bold("Examples:")}
|
|
109
|
-
# Interactive mode
|
|
113
|
+
# Interactive mode
|
|
110
114
|
pi
|
|
111
115
|
|
|
112
|
-
#
|
|
116
|
+
# Interactive mode with initial prompt
|
|
113
117
|
pi "List all .ts files in src/"
|
|
114
118
|
|
|
115
|
-
#
|
|
119
|
+
# Non-interactive mode (process and exit)
|
|
120
|
+
pi -p "List all .ts files in src/"
|
|
121
|
+
|
|
122
|
+
# Multiple messages (interactive)
|
|
116
123
|
pi "Read package.json" "What dependencies do we have?"
|
|
117
124
|
|
|
118
125
|
# Continue previous session
|
|
@@ -124,6 +131,12 @@ ${chalk.bold("Examples:")}
|
|
|
124
131
|
# Limit model cycling to specific models
|
|
125
132
|
pi --models claude-sonnet,claude-haiku,gpt-4o
|
|
126
133
|
|
|
134
|
+
# Cycle models with fixed thinking levels
|
|
135
|
+
pi --models sonnet:high,haiku:low
|
|
136
|
+
|
|
137
|
+
# Start with a specific thinking level
|
|
138
|
+
pi --thinking high "Solve this complex problem"
|
|
139
|
+
|
|
127
140
|
${chalk.bold("Environment Variables:")}
|
|
128
141
|
ANTHROPIC_API_KEY - Anthropic Claude API key
|
|
129
142
|
ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth token (alternative to API key)
|
|
@@ -307,7 +320,8 @@ async function checkForNewVersion(currentVersion) {
|
|
|
307
320
|
}
|
|
308
321
|
}
|
|
309
322
|
/**
|
|
310
|
-
* Resolve model patterns to actual Model objects
|
|
323
|
+
* Resolve model patterns to actual Model objects with optional thinking levels
|
|
324
|
+
* Format: "pattern:level" where :level is optional
|
|
311
325
|
* For each pattern, finds all matching models and picks the best version:
|
|
312
326
|
* 1. Prefer alias (e.g., claude-sonnet-4-5) over dated versions (claude-sonnet-4-5-20250929)
|
|
313
327
|
* 2. If no alias, pick the latest dated version
|
|
@@ -320,10 +334,47 @@ async function resolveModelScope(patterns) {
|
|
|
320
334
|
}
|
|
321
335
|
const scopedModels = [];
|
|
322
336
|
for (const pattern of patterns) {
|
|
323
|
-
//
|
|
324
|
-
const
|
|
337
|
+
// Parse pattern:level format
|
|
338
|
+
const parts = pattern.split(":");
|
|
339
|
+
const modelPattern = parts[0];
|
|
340
|
+
let thinkingLevel = "off";
|
|
341
|
+
if (parts.length > 1) {
|
|
342
|
+
const level = parts[1];
|
|
343
|
+
if (level === "off" || level === "minimal" || level === "low" || level === "medium" || level === "high") {
|
|
344
|
+
thinkingLevel = level;
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
console.warn(chalk.yellow(`Warning: Invalid thinking level "${level}" in pattern "${pattern}". Using "off" instead.`));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Check for provider/modelId format (provider is everything before the first /)
|
|
351
|
+
const slashIndex = modelPattern.indexOf("/");
|
|
352
|
+
if (slashIndex !== -1) {
|
|
353
|
+
const provider = modelPattern.substring(0, slashIndex);
|
|
354
|
+
const modelId = modelPattern.substring(slashIndex + 1);
|
|
355
|
+
const providerMatch = availableModels.find((m) => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase());
|
|
356
|
+
if (providerMatch) {
|
|
357
|
+
if (!scopedModels.find((sm) => sm.model.id === providerMatch.id && sm.model.provider === providerMatch.provider)) {
|
|
358
|
+
scopedModels.push({ model: providerMatch, thinkingLevel });
|
|
359
|
+
}
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
// No exact provider/model match - fall through to other matching
|
|
363
|
+
}
|
|
364
|
+
// Check for exact ID match (case-insensitive)
|
|
365
|
+
const exactMatch = availableModels.find((m) => m.id.toLowerCase() === modelPattern.toLowerCase());
|
|
366
|
+
if (exactMatch) {
|
|
367
|
+
// Exact match found - use it directly
|
|
368
|
+
if (!scopedModels.find((sm) => sm.model.id === exactMatch.id && sm.model.provider === exactMatch.provider)) {
|
|
369
|
+
scopedModels.push({ model: exactMatch, thinkingLevel });
|
|
370
|
+
}
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
// No exact match - fall back to partial matching
|
|
374
|
+
const matches = availableModels.filter((m) => m.id.toLowerCase().includes(modelPattern.toLowerCase()) ||
|
|
375
|
+
m.name?.toLowerCase().includes(modelPattern.toLowerCase()));
|
|
325
376
|
if (matches.length === 0) {
|
|
326
|
-
console.warn(chalk.yellow(`Warning: No models match pattern "${
|
|
377
|
+
console.warn(chalk.yellow(`Warning: No models match pattern "${modelPattern}"`));
|
|
327
378
|
continue;
|
|
328
379
|
}
|
|
329
380
|
// Helper to check if a model ID looks like an alias (no date suffix)
|
|
@@ -351,8 +402,8 @@ async function resolveModelScope(patterns) {
|
|
|
351
402
|
bestMatch = datedVersions[0];
|
|
352
403
|
}
|
|
353
404
|
// Avoid duplicates
|
|
354
|
-
if (!scopedModels.find((
|
|
355
|
-
scopedModels.push(bestMatch);
|
|
405
|
+
if (!scopedModels.find((sm) => sm.model.id === bestMatch.id && sm.model.provider === bestMatch.provider)) {
|
|
406
|
+
scopedModels.push({ model: bestMatch, thinkingLevel });
|
|
356
407
|
}
|
|
357
408
|
}
|
|
358
409
|
return scopedModels;
|
|
@@ -379,25 +430,26 @@ async function selectSession(sessionManager) {
|
|
|
379
430
|
ui.start();
|
|
380
431
|
});
|
|
381
432
|
}
|
|
382
|
-
async function runInteractiveMode(agent, sessionManager, settingsManager, version, changelogMarkdown = null, modelFallbackMessage = null, newVersion = null, scopedModels = []) {
|
|
433
|
+
async function runInteractiveMode(agent, sessionManager, settingsManager, version, changelogMarkdown = null, modelFallbackMessage = null, newVersion = null, scopedModels = [], initialMessages = []) {
|
|
383
434
|
const renderer = new TuiRenderer(agent, sessionManager, settingsManager, version, changelogMarkdown, newVersion, scopedModels);
|
|
384
|
-
// Initialize TUI
|
|
435
|
+
// Initialize TUI (subscribes to agent events internally)
|
|
385
436
|
await renderer.init();
|
|
386
|
-
// Set interrupt callback
|
|
387
|
-
renderer.setInterruptCallback(() => {
|
|
388
|
-
agent.abort();
|
|
389
|
-
});
|
|
390
437
|
// Render any existing messages (from --continue mode)
|
|
391
438
|
renderer.renderInitialMessages(agent.state);
|
|
392
439
|
// Show model fallback warning at the end of the chat if applicable
|
|
393
440
|
if (modelFallbackMessage) {
|
|
394
441
|
renderer.showWarning(modelFallbackMessage);
|
|
395
442
|
}
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
443
|
+
// Process initial messages if provided (from CLI args)
|
|
444
|
+
for (const message of initialMessages) {
|
|
445
|
+
try {
|
|
446
|
+
await agent.prompt(message);
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
450
|
+
renderer.showError(errorMessage);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
401
453
|
// Interactive loop
|
|
402
454
|
while (true) {
|
|
403
455
|
const userInput = await renderer.getUserInput();
|
|
@@ -407,7 +459,8 @@ async function runInteractiveMode(agent, sessionManager, settingsManager, versio
|
|
|
407
459
|
}
|
|
408
460
|
catch (error) {
|
|
409
461
|
// Display error in the TUI by adding an error message to the chat
|
|
410
|
-
|
|
462
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
463
|
+
renderer.showError(errorMessage);
|
|
411
464
|
}
|
|
412
465
|
}
|
|
413
466
|
}
|
|
@@ -491,13 +544,20 @@ export async function main(args) {
|
|
|
491
544
|
// Set the selected session as the active session
|
|
492
545
|
sessionManager.setSessionFile(selectedSession);
|
|
493
546
|
}
|
|
547
|
+
// Resolve model scope early if provided (needed for initial model selection)
|
|
548
|
+
let scopedModels = [];
|
|
549
|
+
if (parsed.models && parsed.models.length > 0) {
|
|
550
|
+
scopedModels = await resolveModelScope(parsed.models);
|
|
551
|
+
}
|
|
494
552
|
// Determine initial model using priority system:
|
|
495
553
|
// 1. CLI args (--provider and --model)
|
|
496
|
-
// 2.
|
|
497
|
-
// 3.
|
|
498
|
-
// 4.
|
|
499
|
-
// 5.
|
|
554
|
+
// 2. First model from --models scope
|
|
555
|
+
// 3. Restored from session (if --continue or --resume)
|
|
556
|
+
// 4. Saved default from settings.json
|
|
557
|
+
// 5. First available model with valid API key
|
|
558
|
+
// 6. null (allowed in interactive mode)
|
|
500
559
|
let initialModel = null;
|
|
560
|
+
let initialThinking = "off";
|
|
501
561
|
if (parsed.provider && parsed.model) {
|
|
502
562
|
// 1. CLI args take priority
|
|
503
563
|
const { model, error } = findModel(parsed.provider, parsed.model);
|
|
@@ -511,8 +571,13 @@ export async function main(args) {
|
|
|
511
571
|
}
|
|
512
572
|
initialModel = model;
|
|
513
573
|
}
|
|
574
|
+
else if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {
|
|
575
|
+
// 2. Use first model from --models scope (skip if continuing/resuming session)
|
|
576
|
+
initialModel = scopedModels[0].model;
|
|
577
|
+
initialThinking = scopedModels[0].thinkingLevel;
|
|
578
|
+
}
|
|
514
579
|
else if (parsed.continue || parsed.resume) {
|
|
515
|
-
//
|
|
580
|
+
// 3. Restore from session (will be handled below after loading session)
|
|
516
581
|
// Leave initialModel as null for now
|
|
517
582
|
}
|
|
518
583
|
if (!initialModel) {
|
|
@@ -526,6 +591,11 @@ export async function main(args) {
|
|
|
526
591
|
process.exit(1);
|
|
527
592
|
}
|
|
528
593
|
initialModel = model;
|
|
594
|
+
// Also load saved thinking level if we're using saved model
|
|
595
|
+
const savedThinking = settingsManager.getDefaultThinkingLevel();
|
|
596
|
+
if (savedThinking) {
|
|
597
|
+
initialThinking = savedThinking;
|
|
598
|
+
}
|
|
529
599
|
}
|
|
530
600
|
}
|
|
531
601
|
if (!initialModel) {
|
|
@@ -553,7 +623,9 @@ export async function main(args) {
|
|
|
553
623
|
}
|
|
554
624
|
}
|
|
555
625
|
// Determine mode early to know if we should print messages and fail early
|
|
556
|
-
|
|
626
|
+
// Interactive mode: no --print flag and no --mode flag
|
|
627
|
+
// Having initial messages doesn't make it non-interactive anymore
|
|
628
|
+
const isInteractive = !parsed.print && parsed.mode === undefined;
|
|
557
629
|
const mode = parsed.mode || "text";
|
|
558
630
|
const shouldPrintMessages = isInteractive || mode === "text";
|
|
559
631
|
// Non-interactive mode: fail early if no model available
|
|
@@ -576,10 +648,6 @@ export async function main(args) {
|
|
|
576
648
|
// Load previous messages if continuing or resuming
|
|
577
649
|
// This may update initialModel if restoring from session
|
|
578
650
|
if (parsed.continue || parsed.resume) {
|
|
579
|
-
const messages = sessionManager.loadMessages();
|
|
580
|
-
if (messages.length > 0 && shouldPrintMessages) {
|
|
581
|
-
console.log(chalk.dim(`Loaded ${messages.length} messages from previous session`));
|
|
582
|
-
}
|
|
583
651
|
// Load and restore model (overrides initialModel if found and has API key)
|
|
584
652
|
const savedModel = sessionManager.loadModel();
|
|
585
653
|
if (savedModel) {
|
|
@@ -644,12 +712,16 @@ export async function main(args) {
|
|
|
644
712
|
}
|
|
645
713
|
}
|
|
646
714
|
}
|
|
715
|
+
// CLI --thinking flag takes highest priority
|
|
716
|
+
if (parsed.thinking) {
|
|
717
|
+
initialThinking = parsed.thinking;
|
|
718
|
+
}
|
|
647
719
|
// Create agent (initialModel can be null in interactive mode)
|
|
648
720
|
const agent = new Agent({
|
|
649
721
|
initialState: {
|
|
650
722
|
systemPrompt,
|
|
651
723
|
model: initialModel, // Can be null
|
|
652
|
-
thinkingLevel:
|
|
724
|
+
thinkingLevel: initialThinking,
|
|
653
725
|
tools: codingTools,
|
|
654
726
|
},
|
|
655
727
|
queueMode: settingsManager.getQueueMode(),
|
|
@@ -673,6 +745,10 @@ export async function main(args) {
|
|
|
673
745
|
},
|
|
674
746
|
}),
|
|
675
747
|
});
|
|
748
|
+
// If initial thinking was requested but model doesn't support it, silently reset to off
|
|
749
|
+
if (initialThinking !== "off" && initialModel && !initialModel.reasoning) {
|
|
750
|
+
agent.setThinkingLevel("off");
|
|
751
|
+
}
|
|
676
752
|
// Track if we had to fall back from saved model (to show in chat later)
|
|
677
753
|
let modelFallbackMessage = null;
|
|
678
754
|
// Load previous messages if continuing or resuming
|
|
@@ -706,8 +782,6 @@ export async function main(args) {
|
|
|
706
782
|
}
|
|
707
783
|
}
|
|
708
784
|
}
|
|
709
|
-
// Note: Session will be started lazily after first user+assistant message exchange
|
|
710
|
-
// (unless continuing/resuming, in which case it's already initialized)
|
|
711
785
|
// Log loaded context files (they're already in the system prompt)
|
|
712
786
|
if (shouldPrintMessages && !parsed.continue && !parsed.resume) {
|
|
713
787
|
const contextFiles = loadProjectContextFiles();
|
|
@@ -718,17 +792,6 @@ export async function main(args) {
|
|
|
718
792
|
}
|
|
719
793
|
}
|
|
720
794
|
}
|
|
721
|
-
// Subscribe to agent events to save messages
|
|
722
|
-
agent.subscribe((event) => {
|
|
723
|
-
// Save messages on completion
|
|
724
|
-
if (event.type === "message_end") {
|
|
725
|
-
sessionManager.saveMessage(event.message);
|
|
726
|
-
// Check if we should initialize session now (after first user+assistant exchange)
|
|
727
|
-
if (sessionManager.shouldInitializeSession(agent.state.messages)) {
|
|
728
|
-
sessionManager.startSession(agent.state);
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
});
|
|
732
795
|
// Route to appropriate mode
|
|
733
796
|
if (mode === "rpc") {
|
|
734
797
|
// RPC mode - headless operation
|
|
@@ -762,8 +825,6 @@ export async function main(args) {
|
|
|
762
825
|
}
|
|
763
826
|
else {
|
|
764
827
|
// Parse current and last versions
|
|
765
|
-
const currentParts = VERSION.split(".").map(Number);
|
|
766
|
-
const current = { major: currentParts[0] || 0, minor: currentParts[1] || 0, patch: currentParts[2] || 0 };
|
|
767
828
|
const changelogPath = getChangelogPath();
|
|
768
829
|
const entries = parseChangelog(changelogPath);
|
|
769
830
|
const newEntries = getNewEntries(entries, lastVersion);
|
|
@@ -773,19 +834,21 @@ export async function main(args) {
|
|
|
773
834
|
}
|
|
774
835
|
}
|
|
775
836
|
}
|
|
776
|
-
//
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
837
|
+
// Show model scope if provided
|
|
838
|
+
if (scopedModels.length > 0) {
|
|
839
|
+
const modelList = scopedModels
|
|
840
|
+
.map((sm) => {
|
|
841
|
+
const thinkingStr = sm.thinkingLevel !== "off" ? `:${sm.thinkingLevel}` : "";
|
|
842
|
+
return `${sm.model.id}${thinkingStr}`;
|
|
843
|
+
})
|
|
844
|
+
.join(", ");
|
|
845
|
+
console.log(chalk.dim(`Model scope: ${modelList} ${chalk.gray("(Ctrl+P to cycle)")}`));
|
|
846
|
+
}
|
|
847
|
+
// Interactive mode - use TUI (may have initial messages from CLI args)
|
|
848
|
+
await runInteractiveMode(agent, sessionManager, settingsManager, VERSION, changelogMarkdown, modelFallbackMessage, newVersion, scopedModels, parsed.messages);
|
|
786
849
|
}
|
|
787
850
|
else {
|
|
788
|
-
//
|
|
851
|
+
// Non-interactive mode (--print flag or --mode flag)
|
|
789
852
|
await runSingleShotMode(agent, sessionManager, parsed.messages, mode);
|
|
790
853
|
}
|
|
791
854
|
}
|