@mariozechner/pi-coding-agent 0.25.4 → 0.26.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.
Files changed (118) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +47 -5
  3. package/dist/cli/file-processor.d.ts.map +1 -1
  4. package/dist/cli/file-processor.js +1 -1
  5. package/dist/cli/file-processor.js.map +1 -1
  6. package/dist/cli/session-picker.d.ts +2 -2
  7. package/dist/cli/session-picker.d.ts.map +1 -1
  8. package/dist/cli/session-picker.js +2 -2
  9. package/dist/cli/session-picker.js.map +1 -1
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +1 -13
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/custom-tools/loader.d.ts +3 -2
  14. package/dist/core/custom-tools/loader.d.ts.map +1 -1
  15. package/dist/core/custom-tools/loader.js +5 -4
  16. package/dist/core/custom-tools/loader.js.map +1 -1
  17. package/dist/core/hooks/loader.d.ts +2 -5
  18. package/dist/core/hooks/loader.d.ts.map +1 -1
  19. package/dist/core/hooks/loader.js +4 -7
  20. package/dist/core/hooks/loader.js.map +1 -1
  21. package/dist/core/model-config.d.ts +5 -4
  22. package/dist/core/model-config.d.ts.map +1 -1
  23. package/dist/core/model-config.js +12 -19
  24. package/dist/core/model-config.js.map +1 -1
  25. package/dist/core/sdk.d.ts +211 -0
  26. package/dist/core/sdk.d.ts.map +1 -0
  27. package/dist/core/sdk.js +466 -0
  28. package/dist/core/sdk.js.map +1 -0
  29. package/dist/core/session-manager.d.ts +31 -91
  30. package/dist/core/session-manager.d.ts.map +1 -1
  31. package/dist/core/session-manager.js +187 -352
  32. package/dist/core/session-manager.js.map +1 -1
  33. package/dist/core/settings-manager.d.ts +12 -2
  34. package/dist/core/settings-manager.d.ts.map +1 -1
  35. package/dist/core/settings-manager.js +101 -37
  36. package/dist/core/settings-manager.js.map +1 -1
  37. package/dist/core/skills.d.ts +7 -1
  38. package/dist/core/skills.d.ts.map +1 -1
  39. package/dist/core/skills.js +7 -5
  40. package/dist/core/skills.js.map +1 -1
  41. package/dist/core/slash-commands.d.ts +9 -3
  42. package/dist/core/slash-commands.d.ts.map +1 -1
  43. package/dist/core/slash-commands.js +10 -7
  44. package/dist/core/slash-commands.js.map +1 -1
  45. package/dist/core/system-prompt.d.ts +24 -2
  46. package/dist/core/system-prompt.d.ts.map +1 -1
  47. package/dist/core/system-prompt.js +18 -16
  48. package/dist/core/system-prompt.js.map +1 -1
  49. package/dist/core/tools/bash.d.ts +6 -1
  50. package/dist/core/tools/bash.d.ts.map +1 -1
  51. package/dist/core/tools/bash.js +149 -144
  52. package/dist/core/tools/bash.js.map +1 -1
  53. package/dist/core/tools/edit.d.ts +7 -1
  54. package/dist/core/tools/edit.d.ts.map +1 -1
  55. package/dist/core/tools/edit.js +105 -102
  56. package/dist/core/tools/edit.js.map +1 -1
  57. package/dist/core/tools/find.d.ts +7 -1
  58. package/dist/core/tools/find.d.ts.map +1 -1
  59. package/dist/core/tools/find.js +128 -124
  60. package/dist/core/tools/find.js.map +1 -1
  61. package/dist/core/tools/grep.d.ts +11 -1
  62. package/dist/core/tools/grep.d.ts.map +1 -1
  63. package/dist/core/tools/grep.js +198 -194
  64. package/dist/core/tools/grep.js.map +1 -1
  65. package/dist/core/tools/index.d.ts +31 -29
  66. package/dist/core/tools/index.d.ts.map +1 -1
  67. package/dist/core/tools/index.js +44 -16
  68. package/dist/core/tools/index.js.map +1 -1
  69. package/dist/core/tools/ls.d.ts +6 -1
  70. package/dist/core/tools/ls.d.ts.map +1 -1
  71. package/dist/core/tools/ls.js +90 -86
  72. package/dist/core/tools/ls.js.map +1 -1
  73. package/dist/core/tools/path-utils.d.ts +6 -1
  74. package/dist/core/tools/path-utils.d.ts.map +1 -1
  75. package/dist/core/tools/path-utils.js +17 -5
  76. package/dist/core/tools/path-utils.js.map +1 -1
  77. package/dist/core/tools/read.d.ts +7 -1
  78. package/dist/core/tools/read.d.ts.map +1 -1
  79. package/dist/core/tools/read.js +118 -115
  80. package/dist/core/tools/read.js.map +1 -1
  81. package/dist/core/tools/write.d.ts +6 -1
  82. package/dist/core/tools/write.d.ts.map +1 -1
  83. package/dist/core/tools/write.js +63 -59
  84. package/dist/core/tools/write.js.map +1 -1
  85. package/dist/index.d.ts +2 -1
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +14 -0
  88. package/dist/index.js.map +1 -1
  89. package/dist/main.d.ts +4 -1
  90. package/dist/main.d.ts.map +1 -1
  91. package/dist/main.js +142 -312
  92. package/dist/main.js.map +1 -1
  93. package/dist/modes/interactive/components/session-selector.d.ts +3 -12
  94. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  95. package/dist/modes/interactive/components/session-selector.js +1 -3
  96. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  97. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  98. package/dist/modes/interactive/interactive-mode.js +3 -2
  99. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  100. package/dist/utils/shell.d.ts.map +1 -1
  101. package/dist/utils/shell.js +1 -1
  102. package/dist/utils/shell.js.map +1 -1
  103. package/docs/sdk.md +864 -0
  104. package/examples/README.md +29 -0
  105. package/examples/sdk/01-minimal.ts +22 -0
  106. package/examples/sdk/02-custom-model.ts +36 -0
  107. package/examples/sdk/03-custom-prompt.ts +44 -0
  108. package/examples/sdk/04-skills.ts +44 -0
  109. package/examples/sdk/05-tools.ts +93 -0
  110. package/examples/sdk/06-hooks.ts +61 -0
  111. package/examples/sdk/07-context-files.ts +36 -0
  112. package/examples/sdk/08-slash-commands.ts +37 -0
  113. package/examples/sdk/09-api-keys-and-oauth.ts +45 -0
  114. package/examples/sdk/10-settings.ts +38 -0
  115. package/examples/sdk/11-sessions.ts +46 -0
  116. package/examples/sdk/12-full-control.ts +99 -0
  117. package/examples/sdk/README.md +138 -0
  118. package/package.json +4 -4
package/dist/main.js CHANGED
@@ -1,58 +1,27 @@
1
1
  /**
2
- * Main entry point for the coding agent
2
+ * Main entry point for the coding agent CLI.
3
+ *
4
+ * This file handles CLI argument parsing and translates them into
5
+ * createAgentSession() options. The SDK does the heavy lifting.
3
6
  */
4
- import { Agent, ProviderTransport } from "@mariozechner/pi-agent-core";
5
- import { setOAuthStorage, supportsXhigh } from "@mariozechner/pi-ai";
7
+ import { supportsXhigh } from "@mariozechner/pi-ai";
6
8
  import chalk from "chalk";
7
- import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
8
- import { dirname } from "path";
9
9
  import { parseArgs, printHelp } from "./cli/args.js";
10
10
  import { processFileArguments } from "./cli/file-processor.js";
11
11
  import { listModels } from "./cli/list-models.js";
12
12
  import { selectSession } from "./cli/session-picker.js";
13
- import { getModelsPath, getOAuthPath, VERSION } from "./config.js";
14
- import { AgentSession } from "./core/agent-session.js";
15
- import { discoverAndLoadCustomTools } from "./core/custom-tools/index.js";
13
+ import { getModelsPath, VERSION } from "./config.js";
16
14
  import { exportFromFile } from "./core/export-html.js";
17
- import { discoverAndLoadHooks, HookRunner, wrapToolsWithHooks } from "./core/hooks/index.js";
18
- import { messageTransformer } from "./core/messages.js";
19
- import { findModel, getApiKeyForModel, getAvailableModels } from "./core/model-config.js";
20
- import { resolveModelScope, restoreModelFromSession } from "./core/model-resolver.js";
15
+ import { findModel } from "./core/model-config.js";
16
+ import { resolveModelScope } from "./core/model-resolver.js";
17
+ import { configureOAuthStorage, createAgentSession } from "./core/sdk.js";
21
18
  import { SessionManager } from "./core/session-manager.js";
22
19
  import { SettingsManager } from "./core/settings-manager.js";
23
- import { loadSlashCommands } from "./core/slash-commands.js";
24
- import { buildSystemPrompt } from "./core/system-prompt.js";
25
- import { allTools, codingTools } from "./core/tools/index.js";
20
+ import { allTools } from "./core/tools/index.js";
26
21
  import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.js";
27
22
  import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js";
28
23
  import { getChangelogPath, getNewEntries, parseChangelog } from "./utils/changelog.js";
29
24
  import { ensureTool } from "./utils/tools-manager.js";
30
- /** Configure OAuth storage to use the coding-agent's configurable path */
31
- function configureOAuthStorage() {
32
- const oauthPath = getOAuthPath();
33
- setOAuthStorage({
34
- load: () => {
35
- if (!existsSync(oauthPath)) {
36
- return {};
37
- }
38
- try {
39
- return JSON.parse(readFileSync(oauthPath, "utf-8"));
40
- }
41
- catch {
42
- return {};
43
- }
44
- },
45
- save: (storage) => {
46
- const dir = dirname(oauthPath);
47
- if (!existsSync(dir)) {
48
- mkdirSync(dir, { recursive: true, mode: 0o700 });
49
- }
50
- writeFileSync(oauthPath, JSON.stringify(storage, null, 2), "utf-8");
51
- chmodSync(oauthPath, 0o600);
52
- },
53
- });
54
- }
55
- /** Check npm registry for new version (non-blocking) */
56
25
  async function checkForNewVersion(currentVersion) {
57
26
  try {
58
27
  const response = await fetch("https://registry.npmjs.org/@mariozechner/pi-coding-agent/latest");
@@ -66,28 +35,21 @@ async function checkForNewVersion(currentVersion) {
66
35
  return null;
67
36
  }
68
37
  catch {
69
- // Silently fail - don't disrupt the user experience
70
38
  return null;
71
39
  }
72
40
  }
73
- /** Run interactive mode with TUI */
74
41
  async function runInteractiveMode(session, version, changelogMarkdown, modelFallbackMessage, versionCheckPromise, initialMessages, customTools, setToolUIContext, initialMessage, initialAttachments, fdPath = null) {
75
42
  const mode = new InteractiveMode(session, version, changelogMarkdown, customTools, setToolUIContext, fdPath);
76
- // Initialize TUI (subscribes to agent events internally)
77
43
  await mode.init();
78
- // Handle version check result when it completes (don't block)
79
44
  versionCheckPromise.then((newVersion) => {
80
45
  if (newVersion) {
81
46
  mode.showNewVersionNotification(newVersion);
82
47
  }
83
48
  });
84
- // Render any existing messages (from --continue mode)
85
49
  mode.renderInitialMessages(session.state);
86
- // Show model fallback warning at the end of the chat if applicable
87
50
  if (modelFallbackMessage) {
88
51
  mode.showWarning(modelFallbackMessage);
89
52
  }
90
- // Process initial message with attachments if provided (from @file args)
91
53
  if (initialMessage) {
92
54
  try {
93
55
  await session.prompt(initialMessage, { attachments: initialAttachments });
@@ -97,7 +59,6 @@ async function runInteractiveMode(session, version, changelogMarkdown, modelFall
97
59
  mode.showError(errorMessage);
98
60
  }
99
61
  }
100
- // Process remaining initial messages if provided (from CLI args)
101
62
  for (const message of initialMessages) {
102
63
  try {
103
64
  await session.prompt(message);
@@ -107,10 +68,8 @@ async function runInteractiveMode(session, version, changelogMarkdown, modelFall
107
68
  mode.showError(errorMessage);
108
69
  }
109
70
  }
110
- // Interactive loop
111
71
  while (true) {
112
72
  const userInput = await mode.getUserInput();
113
- // Process the message
114
73
  try {
115
74
  await session.prompt(userInput);
116
75
  }
@@ -120,17 +79,15 @@ async function runInteractiveMode(session, version, changelogMarkdown, modelFall
120
79
  }
121
80
  }
122
81
  }
123
- /** Prepare initial message from @file arguments */
124
82
  async function prepareInitialMessage(parsed) {
125
83
  if (parsed.fileArgs.length === 0) {
126
84
  return {};
127
85
  }
128
86
  const { textContent, imageAttachments } = await processFileArguments(parsed.fileArgs);
129
- // Combine file content with first plain text message (if any)
130
87
  let initialMessage;
131
88
  if (parsed.messages.length > 0) {
132
89
  initialMessage = textContent + parsed.messages[0];
133
- parsed.messages.shift(); // Remove first message as it's been combined
90
+ parsed.messages.shift();
134
91
  }
135
92
  else {
136
93
  initialMessage = textContent;
@@ -140,9 +97,107 @@ async function prepareInitialMessage(parsed) {
140
97
  initialAttachments: imageAttachments.length > 0 ? imageAttachments : undefined,
141
98
  };
142
99
  }
100
+ function getChangelogForDisplay(parsed, settingsManager) {
101
+ if (parsed.continue || parsed.resume) {
102
+ return null;
103
+ }
104
+ const lastVersion = settingsManager.getLastChangelogVersion();
105
+ const changelogPath = getChangelogPath();
106
+ const entries = parseChangelog(changelogPath);
107
+ if (!lastVersion) {
108
+ if (entries.length > 0) {
109
+ settingsManager.setLastChangelogVersion(VERSION);
110
+ return entries.map((e) => e.content).join("\n\n");
111
+ }
112
+ }
113
+ else {
114
+ const newEntries = getNewEntries(entries, lastVersion);
115
+ if (newEntries.length > 0) {
116
+ settingsManager.setLastChangelogVersion(VERSION);
117
+ return newEntries.map((e) => e.content).join("\n\n");
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+ function createSessionManager(parsed, cwd) {
123
+ if (parsed.noSession) {
124
+ return SessionManager.inMemory();
125
+ }
126
+ if (parsed.session) {
127
+ return SessionManager.open(parsed.session);
128
+ }
129
+ if (parsed.continue) {
130
+ return SessionManager.continueRecent(cwd);
131
+ }
132
+ // --resume is handled separately (needs picker UI)
133
+ // Default case (new session) returns null, SDK will create one
134
+ return null;
135
+ }
136
+ function buildSessionOptions(parsed, scopedModels, sessionManager) {
137
+ const options = {};
138
+ if (sessionManager) {
139
+ options.sessionManager = sessionManager;
140
+ }
141
+ // Model from CLI
142
+ if (parsed.provider && parsed.model) {
143
+ const { model, error } = findModel(parsed.provider, parsed.model);
144
+ if (error) {
145
+ console.error(chalk.red(error));
146
+ process.exit(1);
147
+ }
148
+ if (!model) {
149
+ console.error(chalk.red(`Model ${parsed.provider}/${parsed.model} not found`));
150
+ process.exit(1);
151
+ }
152
+ options.model = model;
153
+ }
154
+ else if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {
155
+ options.model = scopedModels[0].model;
156
+ }
157
+ // Thinking level
158
+ if (parsed.thinking) {
159
+ options.thinkingLevel = parsed.thinking;
160
+ }
161
+ else if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {
162
+ options.thinkingLevel = scopedModels[0].thinkingLevel;
163
+ }
164
+ // Scoped models for Ctrl+P cycling
165
+ if (scopedModels.length > 0) {
166
+ options.scopedModels = scopedModels;
167
+ }
168
+ // API key from CLI
169
+ if (parsed.apiKey) {
170
+ options.getApiKey = async () => parsed.apiKey;
171
+ }
172
+ // System prompt
173
+ if (parsed.systemPrompt && parsed.appendSystemPrompt) {
174
+ options.systemPrompt = `${parsed.systemPrompt}\n\n${parsed.appendSystemPrompt}`;
175
+ }
176
+ else if (parsed.systemPrompt) {
177
+ options.systemPrompt = parsed.systemPrompt;
178
+ }
179
+ else if (parsed.appendSystemPrompt) {
180
+ options.systemPrompt = (defaultPrompt) => `${defaultPrompt}\n\n${parsed.appendSystemPrompt}`;
181
+ }
182
+ // Tools
183
+ if (parsed.tools) {
184
+ options.tools = parsed.tools.map((name) => allTools[name]);
185
+ }
186
+ // Skills
187
+ if (parsed.noSkills) {
188
+ options.skills = [];
189
+ }
190
+ // Additional hook paths from CLI
191
+ if (parsed.hooks && parsed.hooks.length > 0) {
192
+ options.additionalHookPaths = parsed.hooks;
193
+ }
194
+ // Additional custom tool paths from CLI
195
+ if (parsed.customTools && parsed.customTools.length > 0) {
196
+ options.additionalCustomToolPaths = parsed.customTools;
197
+ }
198
+ return options;
199
+ }
143
200
  export async function main(args) {
144
- // Configure OAuth storage to use the coding-agent's configurable path
145
- // This must happen before any OAuth operations
146
201
  configureOAuthStorage();
147
202
  const parsed = parseArgs(args);
148
203
  if (parsed.version) {
@@ -153,13 +208,11 @@ export async function main(args) {
153
208
  printHelp();
154
209
  return;
155
210
  }
156
- // Handle --list-models flag: list available models and exit
157
211
  if (parsed.listModels !== undefined) {
158
212
  const searchPattern = typeof parsed.listModels === "string" ? parsed.listModels : undefined;
159
213
  await listModels(searchPattern);
160
214
  return;
161
215
  }
162
- // Handle --export flag: convert session file to HTML and exit
163
216
  if (parsed.export) {
164
217
  try {
165
218
  const outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;
@@ -173,215 +226,64 @@ export async function main(args) {
173
226
  process.exit(1);
174
227
  }
175
228
  }
176
- // Validate: RPC mode doesn't support @file arguments
177
229
  if (parsed.mode === "rpc" && parsed.fileArgs.length > 0) {
178
230
  console.error(chalk.red("Error: @file arguments are not supported in RPC mode"));
179
231
  process.exit(1);
180
232
  }
181
- // Process @file arguments
233
+ const cwd = process.cwd();
182
234
  const { initialMessage, initialAttachments } = await prepareInitialMessage(parsed);
183
- // Determine if we're in interactive mode (needed for theme watcher)
184
235
  const isInteractive = !parsed.print && parsed.mode === undefined;
185
- // Initialize theme (before any TUI rendering)
186
- const settingsManager = new SettingsManager();
187
- const themeName = settingsManager.getTheme();
188
- initTheme(themeName, isInteractive);
189
- // Setup session manager
190
- const sessionManager = new SessionManager(parsed.continue && !parsed.resume, parsed.session);
191
- if (parsed.noSession) {
192
- sessionManager.disable();
193
- }
194
- // Handle --resume flag: show session selector
195
- if (parsed.resume) {
196
- const selectedSession = await selectSession(sessionManager);
197
- if (!selectedSession) {
198
- console.log(chalk.dim("No session selected"));
199
- return;
200
- }
201
- sessionManager.setSessionFile(selectedSession);
202
- }
203
- // Resolve model scope early if provided
236
+ const mode = parsed.mode || "text";
237
+ const settingsManager = SettingsManager.create(cwd);
238
+ initTheme(settingsManager.getTheme(), isInteractive);
204
239
  let scopedModels = [];
205
240
  if (parsed.models && parsed.models.length > 0) {
206
241
  scopedModels = await resolveModelScope(parsed.models);
207
242
  }
208
- // Determine mode and output behavior
209
- const mode = parsed.mode || "text";
210
- const shouldPrintMessages = isInteractive;
211
- // Find initial model
212
- let initialModel = await findInitialModelForSession(parsed, scopedModels, settingsManager);
213
- let initialThinking = "off";
214
- // Get thinking level from scoped models if applicable
215
- if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {
216
- initialThinking = scopedModels[0].thinkingLevel;
217
- }
218
- else {
219
- // Try saved thinking level
220
- const savedThinking = settingsManager.getDefaultThinkingLevel();
221
- if (savedThinking) {
222
- initialThinking = savedThinking;
243
+ // Create session manager based on CLI flags
244
+ let sessionManager = createSessionManager(parsed, cwd);
245
+ // Handle --resume: show session picker
246
+ if (parsed.resume) {
247
+ const sessions = SessionManager.list(cwd);
248
+ if (sessions.length === 0) {
249
+ console.log(chalk.dim("No sessions found"));
250
+ return;
223
251
  }
252
+ const selectedPath = await selectSession(sessions);
253
+ if (!selectedPath) {
254
+ console.log(chalk.dim("No session selected"));
255
+ return;
256
+ }
257
+ sessionManager = SessionManager.open(selectedPath);
224
258
  }
225
- // Non-interactive mode: fail early if no model available
226
- if (!isInteractive && !initialModel) {
259
+ const sessionOptions = buildSessionOptions(parsed, scopedModels, sessionManager);
260
+ const { session, customToolsResult, modelFallbackMessage } = await createAgentSession(sessionOptions);
261
+ if (!isInteractive && !session.model) {
227
262
  console.error(chalk.red("No models available."));
228
263
  console.error(chalk.yellow("\nSet an API key environment variable:"));
229
264
  console.error(" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.");
230
265
  console.error(chalk.yellow(`\nOr create ${getModelsPath()}`));
231
266
  process.exit(1);
232
267
  }
233
- // Non-interactive mode: validate API key exists
234
- if (!isInteractive && initialModel) {
235
- const apiKey = parsed.apiKey || (await getApiKeyForModel(initialModel));
236
- if (!apiKey) {
237
- console.error(chalk.red(`No API key found for ${initialModel.provider}`));
238
- process.exit(1);
239
- }
240
- }
241
- // Build system prompt
242
- const skillsSettings = settingsManager.getSkillsSettings();
243
- if (parsed.noSkills) {
244
- skillsSettings.enabled = false;
245
- }
246
- if (parsed.skills && parsed.skills.length > 0) {
247
- skillsSettings.includeSkills = parsed.skills;
248
- }
249
- const systemPrompt = buildSystemPrompt({
250
- customPrompt: parsed.systemPrompt,
251
- selectedTools: parsed.tools,
252
- appendSystemPrompt: parsed.appendSystemPrompt,
253
- skillsSettings,
254
- });
255
- // Handle session restoration
256
- let modelFallbackMessage = null;
257
- if (parsed.continue || parsed.resume || parsed.session) {
258
- const savedModel = sessionManager.loadModel();
259
- if (savedModel) {
260
- const result = await restoreModelFromSession(savedModel.provider, savedModel.modelId, initialModel, shouldPrintMessages);
261
- if (result.model) {
262
- initialModel = result.model;
263
- }
264
- modelFallbackMessage = result.fallbackMessage;
265
- }
266
- // Load and restore thinking level
267
- const thinkingLevel = sessionManager.loadThinkingLevel();
268
- if (thinkingLevel) {
269
- initialThinking = thinkingLevel;
270
- if (shouldPrintMessages) {
271
- console.log(chalk.dim(`Restored thinking level: ${thinkingLevel}`));
272
- }
273
- }
274
- }
275
- // CLI --thinking flag takes highest priority
276
- if (parsed.thinking) {
277
- initialThinking = parsed.thinking;
278
- }
279
- // Clamp thinking level to model capabilities
280
- if (initialModel) {
281
- if (!initialModel.reasoning) {
282
- initialThinking = "off";
268
+ // Clamp thinking level to model capabilities (for CLI override case)
269
+ if (session.model && parsed.thinking) {
270
+ let effectiveThinking = parsed.thinking;
271
+ if (!session.model.reasoning) {
272
+ effectiveThinking = "off";
283
273
  }
284
- else if (initialThinking === "xhigh" && !supportsXhigh(initialModel)) {
285
- initialThinking = "high";
274
+ else if (effectiveThinking === "xhigh" && !supportsXhigh(session.model)) {
275
+ effectiveThinking = "high";
286
276
  }
287
- }
288
- // Determine which tools to use
289
- let selectedTools = parsed.tools ? parsed.tools.map((name) => allTools[name]) : codingTools;
290
- // Discover and load hooks from:
291
- // 1. ~/.pi/agent/hooks/*.ts (global)
292
- // 2. cwd/.pi/hooks/*.ts (project-local)
293
- // 3. Explicit paths in settings.json
294
- // 4. CLI --hook flags
295
- let hookRunner = null;
296
- const cwd = process.cwd();
297
- const configuredHookPaths = [...settingsManager.getHookPaths(), ...(parsed.hooks ?? [])];
298
- const { hooks, errors } = await discoverAndLoadHooks(configuredHookPaths, cwd);
299
- // Report hook loading errors
300
- for (const { path, error } of errors) {
301
- console.error(chalk.red(`Failed to load hook "${path}": ${error}`));
302
- }
303
- if (hooks.length > 0) {
304
- const timeout = settingsManager.getHookTimeout();
305
- hookRunner = new HookRunner(hooks, cwd, timeout);
306
- }
307
- // Discover and load custom tools from:
308
- // 1. ~/.pi/agent/tools/*.ts (global)
309
- // 2. cwd/.pi/tools/*.ts (project-local)
310
- // 3. Explicit paths in settings.json
311
- // 4. CLI --tool flags
312
- const configuredToolPaths = [...settingsManager.getCustomToolPaths(), ...(parsed.customTools ?? [])];
313
- const builtInToolNames = Object.keys(allTools);
314
- const { tools: loadedCustomTools, errors: toolErrors, setUIContext: setToolUIContext, } = await discoverAndLoadCustomTools(configuredToolPaths, cwd, builtInToolNames);
315
- // Report custom tool loading errors
316
- for (const { path, error } of toolErrors) {
317
- console.error(chalk.red(`Failed to load custom tool "${path}": ${error}`));
318
- }
319
- // Add custom tools to selected tools
320
- if (loadedCustomTools.length > 0) {
321
- const customToolInstances = loadedCustomTools.map((lt) => lt.tool);
322
- selectedTools = [...selectedTools, ...customToolInstances];
323
- }
324
- // Wrap tools with hook callbacks (built-in and custom)
325
- if (hookRunner) {
326
- selectedTools = wrapToolsWithHooks(selectedTools, hookRunner);
327
- }
328
- // Create agent
329
- const agent = new Agent({
330
- initialState: {
331
- systemPrompt,
332
- model: initialModel, // Can be null in interactive mode
333
- thinkingLevel: initialThinking,
334
- tools: selectedTools,
335
- },
336
- messageTransformer,
337
- queueMode: settingsManager.getQueueMode(),
338
- transport: new ProviderTransport({
339
- getApiKey: async () => {
340
- const currentModel = agent.state.model;
341
- if (!currentModel) {
342
- throw new Error("No model selected");
343
- }
344
- if (parsed.apiKey) {
345
- return parsed.apiKey;
346
- }
347
- const key = await getApiKeyForModel(currentModel);
348
- if (!key) {
349
- throw new Error(`No API key found for provider "${currentModel.provider}". Please set the appropriate environment variable or update ${getModelsPath()}`);
350
- }
351
- return key;
352
- },
353
- }),
354
- });
355
- // Load previous messages if continuing, resuming, or using --session
356
- if (parsed.continue || parsed.resume || parsed.session) {
357
- const messages = sessionManager.loadMessages();
358
- if (messages.length > 0) {
359
- agent.replaceMessages(messages);
277
+ if (effectiveThinking !== session.thinkingLevel) {
278
+ session.setThinkingLevel(effectiveThinking);
360
279
  }
361
280
  }
362
- // Load file commands for slash command expansion
363
- const fileCommands = loadSlashCommands();
364
- // Create session
365
- const session = new AgentSession({
366
- agent,
367
- sessionManager,
368
- settingsManager,
369
- scopedModels,
370
- fileCommands,
371
- hookRunner,
372
- customTools: loadedCustomTools,
373
- skillsSettings,
374
- });
375
- // Route to appropriate mode
376
281
  if (mode === "rpc") {
377
282
  await runRpcMode(session);
378
283
  }
379
284
  else if (isInteractive) {
380
- // Check for new version in the background
381
285
  const versionCheckPromise = checkForNewVersion(VERSION).catch(() => null);
382
- // Check if we should show changelog
383
286
  const changelogMarkdown = getChangelogForDisplay(parsed, settingsManager);
384
- // Show model scope if provided
385
287
  if (scopedModels.length > 0) {
386
288
  const modelList = scopedModels
387
289
  .map((sm) => {
@@ -391,88 +293,16 @@ export async function main(args) {
391
293
  .join(", ");
392
294
  console.log(chalk.dim(`Model scope: ${modelList} ${chalk.gray("(Ctrl+P to cycle)")}`));
393
295
  }
394
- // Ensure fd tool is available for file autocomplete
395
296
  const fdPath = await ensureTool("fd");
396
- await runInteractiveMode(session, VERSION, changelogMarkdown, modelFallbackMessage, versionCheckPromise, parsed.messages, loadedCustomTools, setToolUIContext, initialMessage, initialAttachments, fdPath);
297
+ await runInteractiveMode(session, VERSION, changelogMarkdown, modelFallbackMessage, versionCheckPromise, parsed.messages, customToolsResult.tools, customToolsResult.setUIContext, initialMessage, initialAttachments, fdPath);
397
298
  }
398
299
  else {
399
- // Non-interactive mode (--print flag or --mode flag)
400
300
  await runPrintMode(session, mode, parsed.messages, initialMessage, initialAttachments);
401
- // Clean up and exit (file watchers keep process alive)
402
301
  stopThemeWatcher();
403
- // Wait for stdout to fully flush before exiting
404
302
  if (process.stdout.writableLength > 0) {
405
303
  await new Promise((resolve) => process.stdout.once("drain", resolve));
406
304
  }
407
305
  process.exit(0);
408
306
  }
409
307
  }
410
- /** Find initial model based on CLI args, scoped models, settings, or available models */
411
- async function findInitialModelForSession(parsed, scopedModels, settingsManager) {
412
- // 1. CLI args take priority
413
- if (parsed.provider && parsed.model) {
414
- const { model, error } = findModel(parsed.provider, parsed.model);
415
- if (error) {
416
- console.error(chalk.red(error));
417
- process.exit(1);
418
- }
419
- if (!model) {
420
- console.error(chalk.red(`Model ${parsed.provider}/${parsed.model} not found`));
421
- process.exit(1);
422
- }
423
- return model;
424
- }
425
- // 2. Use first model from scoped models (skip if continuing/resuming)
426
- if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {
427
- return scopedModels[0].model;
428
- }
429
- // 3. Try saved default from settings
430
- const defaultProvider = settingsManager.getDefaultProvider();
431
- const defaultModelId = settingsManager.getDefaultModel();
432
- if (defaultProvider && defaultModelId) {
433
- const { model, error } = findModel(defaultProvider, defaultModelId);
434
- if (error) {
435
- console.error(chalk.red(error));
436
- process.exit(1);
437
- }
438
- if (model) {
439
- return model;
440
- }
441
- }
442
- // 4. Try first available model with valid API key
443
- const { models: availableModels, error } = await getAvailableModels();
444
- if (error) {
445
- console.error(chalk.red(error));
446
- process.exit(1);
447
- }
448
- if (availableModels.length > 0) {
449
- return availableModels[0];
450
- }
451
- return null;
452
- }
453
- /** Get changelog markdown to display (only for new sessions with updates) */
454
- function getChangelogForDisplay(parsed, settingsManager) {
455
- if (parsed.continue || parsed.resume) {
456
- return null;
457
- }
458
- const lastVersion = settingsManager.getLastChangelogVersion();
459
- const changelogPath = getChangelogPath();
460
- const entries = parseChangelog(changelogPath);
461
- if (!lastVersion) {
462
- // First run - show all entries
463
- if (entries.length > 0) {
464
- settingsManager.setLastChangelogVersion(VERSION);
465
- return entries.map((e) => e.content).join("\n\n");
466
- }
467
- }
468
- else {
469
- // Check for new entries since last version
470
- const newEntries = getNewEntries(entries, lastVersion);
471
- if (newEntries.length > 0) {
472
- settingsManager.setLastChangelogVersion(VERSION);
473
- return newEntries.map((e) => e.content).join("\n\n");
474
- }
475
- }
476
- return null;
477
- }
478
308
  //# sourceMappingURL=main.js.map