privateer-agent 0.12.7 → 0.12.10

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.
@@ -68,12 +68,12 @@ index 4600b23..075ecae 100644
68
68
  export const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;
69
69
  export const ENV_SESSION_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_SESSION_DIR`;
70
70
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js
71
- index e223ce1..cc853e7 100644
71
+ index ce8a9a2..1ae7b25 100644
72
72
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js
73
73
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js
74
- @@ -163,6 +163,14 @@ export class AgentSession {
74
+ @@ -186,6 +186,14 @@ export class AgentSession {
75
75
  }
76
- const isOAuth = this._modelRegistry.isUsingOAuth(model);
76
+ const isOAuth = this._modelRuntime.isUsingOAuth(model.provider);
77
77
  if (isOAuth) {
78
78
  + // Privateer patch: the account channel has no API key that could expire —
79
79
  + // the terminal is simply not signed in yet (a fresh install now boots on
@@ -86,7 +86,7 @@ index e223ce1..cc853e7 100644
86
86
  throw new Error(`Authentication failed for "${model.provider}". ` +
87
87
  `Credentials may have expired or network is unavailable. ` +
88
88
  `Run '/login ${model.provider}' to re-authenticate.`);
89
- @@ -711,6 +719,15 @@ export class AgentSession {
89
+ @@ -772,6 +780,15 @@ export class AgentSession {
90
90
  finalError: msg.errorMessage,
91
91
  });
92
92
  this._retryAttempt = 0;
@@ -102,7 +102,22 @@ index e223ce1..cc853e7 100644
102
102
  }
103
103
  if (await this._checkCompaction(msg)) {
104
104
  return true;
105
- @@ -1996,6 +2013,18 @@ export class AgentSession {
105
+ @@ -852,6 +869,14 @@ export class AgentSession {
106
+ if (!hasConfiguredAuth) {
107
+ const isOAuth = this._modelRuntime.isUsingOAuth(this.model.provider);
108
+ if (isOAuth) {
109
+ + // Privateer patch: the account channel has no API key that could expire —
110
+ + // the terminal is simply not signed in yet (a fresh install now boots on
111
+ + // `privateer/*`, which is the model it will run once logged in). Stock Pi's
112
+ + // wording describes a state that user was never in, and its "/login
113
+ + // privateer" bypasses the branded sign-in. auth-guidance owns the words.
114
+ + if (this.model.provider === "privateer") {
115
+ + throw new Error(formatNoApiKeyFoundMessage(this.model.provider));
116
+ + }
117
+ throw new Error(`Authentication failed for "${this.model.provider}". ` +
118
+ `Credentials may have expired or network is unavailable. ` +
119
+ `Run '/login ${this.model.provider}' to re-authenticate.`);
120
+ @@ -2084,6 +2109,18 @@ export class AgentSession {
106
121
  // Context overflow is handled by compaction, not retry.
107
122
  if (isContextOverflow(message, this.model?.contextWindow ?? 0))
108
123
  return false;
@@ -167,10 +182,10 @@ index 197bccc..bc9ac3f 100644
167
182
  //# sourceMappingURL=auth-guidance.js.map
168
183
 
169
184
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js
170
- index 4b79482..a81b452 100644
185
+ index 324d30a..e613e92 100644
171
186
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js
172
187
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js
173
- @@ -17,7 +17,7 @@ import { createJiti } from "jiti/static";
188
+ @@ -18,7 +18,7 @@ import { createJiti } from "jiti/static";
174
189
  import * as _bundledTypebox from "typebox";
175
190
  import * as _bundledTypeboxCompile from "typebox/compile";
176
191
  import * as _bundledTypeboxValue from "typebox/value";
@@ -179,7 +194,7 @@ index 4b79482..a81b452 100644
179
194
  // NOTE: This import works because loader.ts exports are NOT re-exported from index.ts,
180
195
  // avoiding a circular dependency. Extensions can import from @earendil-works/pi-coding-agent.
181
196
  import * as _bundledPiCodingAgent from "../../index.js";
182
- @@ -508,9 +508,23 @@ export async function discoverAndLoadExtensions(configuredPaths, cwd, agentDir =
197
+ @@ -554,9 +554,23 @@ export async function discoverAndLoadExtensions(configuredPaths, cwd, agentDir =
183
198
  }
184
199
  }
185
200
  };
@@ -207,7 +222,7 @@ index 4b79482..a81b452 100644
207
222
  const globalExtDir = path.join(resolvedAgentDir, "extensions");
208
223
  addPaths(discoverExtensionsInDir(globalExtDir));
209
224
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js
210
- index a125c9a..5ca0ccc 100644
225
+ index e806af3..9b00ebd 100644
211
226
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js
212
227
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js
213
228
  @@ -25,7 +25,7 @@ import { globSync } from "glob";
@@ -219,7 +234,7 @@ index a125c9a..5ca0ccc 100644
219
234
  import { spawnProcess, spawnProcessSync } from "../utils/child-process.js";
220
235
  import { parseGitUrl } from "../utils/git.js";
221
236
  import { canonicalizePath, isLocalPath, markPathIgnoredByCloudSync, resolvePath } from "../utils/paths.js";
222
- @@ -693,23 +693,29 @@ export class DefaultPackageManager {
237
+ @@ -698,23 +698,29 @@ export class DefaultPackageManager {
223
238
  const packageSources = this.dedupePackages(allPackages);
224
239
  await this.resolvePackageSources(packageSources, accumulator, onMissing);
225
240
  const globalBaseDir = this.agentDir;
@@ -256,7 +271,7 @@ index a125c9a..5ca0ccc 100644
256
271
  return this.toResolvedPaths(accumulator);
257
272
  }
258
273
  async resolveExtensionSources(sources, options) {
259
- @@ -1586,13 +1592,30 @@ export class DefaultPackageManager {
274
+ @@ -1668,13 +1674,30 @@ export class DefaultPackageManager {
260
275
  writeFileSync(ignorePath, "*\n!.gitignore\n", "utf-8");
261
276
  }
262
277
  }
@@ -288,7 +303,7 @@ index a125c9a..5ca0ccc 100644
288
303
  }
289
304
  return join(this.agentDir, "npm");
290
305
  }
291
- @@ -1631,7 +1654,9 @@ export class DefaultPackageManager {
306
+ @@ -1713,7 +1736,9 @@ export class DefaultPackageManager {
292
307
  }
293
308
  if (scope === "project") {
294
309
  this.assertProjectTrustedForScope(scope);
@@ -299,7 +314,7 @@ index a125c9a..5ca0ccc 100644
299
314
  }
300
315
  return join(this.agentDir, "npm", "node_modules", source.name);
301
316
  }
302
- @@ -1667,7 +1692,7 @@ export class DefaultPackageManager {
317
+ @@ -1749,7 +1774,7 @@ export class DefaultPackageManager {
303
318
  }
304
319
  if (scope === "project") {
305
320
  this.assertProjectTrustedForScope(scope);
@@ -308,7 +323,7 @@ index a125c9a..5ca0ccc 100644
308
323
  }
309
324
  return join(this.agentDir, "git");
310
325
  }
311
- @@ -1690,7 +1715,7 @@ export class DefaultPackageManager {
326
+ @@ -1772,7 +1797,7 @@ export class DefaultPackageManager {
312
327
  getBaseDirForScope(scope) {
313
328
  if (scope === "project") {
314
329
  this.assertProjectTrustedForScope(scope);
@@ -317,7 +332,7 @@ index a125c9a..5ca0ccc 100644
317
332
  }
318
333
  if (scope === "user") {
319
334
  return this.agentDir;
320
- @@ -1701,7 +1726,30 @@ export class DefaultPackageManager {
335
+ @@ -1783,7 +1808,30 @@ export class DefaultPackageManager {
321
336
  return resolvePath(input, this.cwd, { homeDir: getHomeDir(), trim: true });
322
337
  }
323
338
  resolvePathFromBase(input, baseDir) {
@@ -349,7 +364,7 @@ index a125c9a..5ca0ccc 100644
349
364
  }
350
365
  collectPackageResources(packageRoot, accumulator, filter, metadata) {
351
366
  if (filter) {
352
- @@ -1847,19 +1895,24 @@ export class DefaultPackageManager {
367
+ @@ -1928,19 +1976,24 @@ export class DefaultPackageManager {
353
368
  this.addResource(target, f, metadata, enabledPaths.has(f));
354
369
  }
355
370
  }
@@ -378,7 +393,7 @@ index a125c9a..5ca0ccc 100644
378
393
  const userOverrides = {
379
394
  extensions: (globalSettings.extensions ?? []),
380
395
  skills: (globalSettings.skills ?? []),
381
- @@ -1878,12 +1931,12 @@ export class DefaultPackageManager {
396
+ @@ -1959,12 +2012,12 @@ export class DefaultPackageManager {
382
397
  prompts: join(globalBaseDir, "prompts"),
383
398
  themes: join(globalBaseDir, "themes"),
384
399
  };
@@ -397,7 +412,7 @@ index a125c9a..5ca0ccc 100644
397
412
  const userAgentsSkillsDir = join(getHomeDir(), ".agents", "skills");
398
413
  const projectTrusted = this.settingsManager.isProjectTrusted();
399
414
  const projectAgentsSkillDirs = projectTrusted
400
- @@ -1897,10 +1950,13 @@ export class DefaultPackageManager {
415
+ @@ -1978,10 +2031,13 @@ export class DefaultPackageManager {
401
416
  }
402
417
  };
403
418
  if (projectTrusted) {
@@ -415,7 +430,7 @@ index a125c9a..5ca0ccc 100644
415
430
  }
416
431
  // Project skills from .agents/ (each with its own baseDir)
417
432
  for (const agentsSkillsDir of projectAgentsSkillDirs) {
418
- @@ -1912,8 +1968,11 @@ export class DefaultPackageManager {
433
+ @@ -1993,8 +2049,11 @@ export class DefaultPackageManager {
419
434
  addResources("skills", collectAutoSkillEntries(agentsSkillsDir, "agents"), agentsMetadata, projectOverrides.skills, agentsBaseDir);
420
435
  }
421
436
  if (projectTrusted) {
@@ -445,7 +460,7 @@ index c4f2509..a4402df 100644
445
460
  async function selectProjectTrustOption(cwd, ctx) {
446
461
  const options = getProjectTrustOptions(cwd, { includeSessionOnly: true });
447
462
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.js
448
- index ae4a7d4..a251c37 100644
463
+ index 3700c08..79cb85e 100644
449
464
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.js
450
465
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.js
451
466
  @@ -1,6 +1,6 @@
@@ -488,19 +503,19 @@ index ae4a7d4..a251c37 100644
488
503
  // 3. Load explicit prompt paths
489
504
  for (const rawPath of promptPaths) {
490
505
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js
491
- index 74e36fe..7dfef45 100644
506
+ index 97af6cd..74d5d52 100644
492
507
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js
493
508
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js
494
509
  @@ -1,7 +1,7 @@
495
510
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
496
- import { join, resolve, sep } from "node:path";
511
+ import { basename, dirname, join, resolve, sep } from "node:path";
497
512
  import chalk from "chalk";
498
513
  -import { CONFIG_DIR_NAME } from "../config.js";
499
514
  +import { findProjectConfigPath, projectConfigDirCandidates, projectConfigDirs } from "../config.js";
500
515
  import { loadThemeFromPath } from "../modes/interactive/theme/theme.js";
501
516
  import { canonicalizePath, isLocalPath, resolvePath } from "../utils/paths.js";
502
517
  import { createEventBus } from "./event-bus.js";
503
- @@ -570,12 +570,14 @@ export class DefaultResourceLoader {
518
+ @@ -624,12 +624,14 @@ export class DefaultResourceLoader {
504
519
  join(this.agentDir, "themes"),
505
520
  join(this.agentDir, "extensions"),
506
521
  ];
@@ -521,7 +536,7 @@ index 74e36fe..7dfef45 100644
521
536
  for (const root of agentRoots) {
522
537
  if (this.isUnderPath(normalizedPath, root)) {
523
538
  return { path: filePath, source: "local", scope: "user", origin: "top-level", baseDir: root };
524
- @@ -614,7 +616,11 @@ export class DefaultResourceLoader {
539
+ @@ -668,7 +670,11 @@ export class DefaultResourceLoader {
525
540
  const themes = [];
526
541
  const diagnostics = [];
527
542
  if (includeDefaults) {
@@ -534,7 +549,7 @@ index 74e36fe..7dfef45 100644
534
549
  for (const dir of defaultDirs) {
535
550
  this.loadThemesFromDir(dir, themes, diagnostics);
536
551
  }
537
- @@ -749,8 +755,9 @@ export class DefaultResourceLoader {
552
+ @@ -806,8 +812,9 @@ export class DefaultResourceLoader {
538
553
  return { themes: Array.from(seen.values()), diagnostics };
539
554
  }
540
555
  discoverSystemPromptFile() {
@@ -546,7 +561,7 @@ index 74e36fe..7dfef45 100644
546
561
  return projectPath;
547
562
  }
548
563
  const globalPath = join(this.agentDir, "SYSTEM.md");
549
- @@ -760,8 +767,9 @@ export class DefaultResourceLoader {
564
+ @@ -817,8 +824,9 @@ export class DefaultResourceLoader {
550
565
  return undefined;
551
566
  }
552
567
  discoverAppendSystemPromptFile() {
@@ -559,7 +574,7 @@ index 74e36fe..7dfef45 100644
559
574
  }
560
575
  const globalPath = join(this.agentDir, "APPEND_SYSTEM.md");
561
576
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js b/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js
562
- index 5910aac..f379c0f 100644
577
+ index cb06c10..930a0de 100644
563
578
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js
564
579
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js
565
580
  @@ -2,7 +2,7 @@ import { randomUUID } from "crypto";
@@ -570,8 +585,8 @@ index 5910aac..f379c0f 100644
570
585
  +import { getAgentDir, projectConfigDirCandidates, projectConfigWriteDir } from "../config.js";
571
586
  import { normalizePath, resolvePath } from "../utils/paths.js";
572
587
  import { DEFAULT_HTTP_IDLE_TIMEOUT_MS, parseHttpIdleTimeoutMs } from "./http-dispatcher.js";
573
- /** Deep merge settings: project/overrides take precedence, nested objects merge recursively */
574
- @@ -40,14 +40,112 @@ function parseTimeoutSetting(value, settingName) {
588
+ function isMergeableObject(value) {
589
+ @@ -37,14 +37,112 @@ function parseTimeoutSetting(value, settingName) {
575
590
  }
576
591
  return undefined;
577
592
  }
@@ -685,7 +700,7 @@ index 5910aac..f379c0f 100644
685
700
  }
686
701
  acquireLockSyncWithRetry(path) {
687
702
  const maxAttempts = 10;
688
- @@ -74,7 +172,13 @@ export class FileSettingsStorage {
703
+ @@ -71,7 +169,13 @@ export class FileSettingsStorage {
689
704
  throw lastError ?? new Error("Failed to acquire settings lock");
690
705
  }
691
706
  withLock(scope, fn) {
@@ -770,11 +785,37 @@ index 17af938..6d97caa 100644
770
785
  }
771
786
  while (true) {
772
787
  const agentsSkillsDir = join(currentDir, ".agents", "skills");
788
+ diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/index.d.ts b/node_modules/@earendil-works/pi-coding-agent/dist/index.d.ts
789
+ index 7d3e24c..578ada5 100644
790
+ --- a/node_modules/@earendil-works/pi-coding-agent/dist/index.d.ts
791
+ +++ b/node_modules/@earendil-works/pi-coding-agent/dist/index.d.ts
792
+ @@ -1,7 +1,7 @@
793
+ export { type Args, parseArgs } from "./cli/args.ts";
794
+ export { CONFIG_DIR_NAME, getAgentDir, getDocsPath, getExamplesPath, getPackageDir, getReadmePath, VERSION, } from "./config.ts";
795
+ export { AgentSession, type AgentSessionConfig, type AgentSessionEvent, type AgentSessionEventListener, type ModelCycleResult, type ParsedSkillBlock, type PromptOptions, parseSkillBlock, type SessionStats, } from "./core/agent-session.ts";
796
+ -export { readStoredCredential } from "./core/auth-storage.ts";
797
+ +export { AuthStorage, readStoredCredential } from "./core/auth-storage.ts";
798
+ export { type BranchPreparation, type BranchSummaryResult, type CollectEntriesResult, type CompactionResult, type CutPointResult, calculateContextTokens, collectEntriesForBranchSummary, compact, DEFAULT_COMPACTION_SETTINGS, estimateTokens, type FileOperations, findCutPoint, findTurnStartIndex, type GenerateBranchSummaryOptions, generateBranchSummary, generateSummary, generateSummaryWithUsage, getLastAssistantUsage, prepareBranchEntries, serializeConversation, shouldCompact, } from "./core/compaction/index.ts";
799
+ export { createEventBus, type EventBus, type EventBusController } from "./core/event-bus.ts";
800
+ export type { AgentEndEvent, AgentSettledEvent, AgentStartEvent, AgentToolResult, AgentToolUpdateCallback, AppKeybinding, AutocompleteProviderFactory, BashToolCallEvent, BeforeAgentStartEvent, BeforeAgentStartEventResult, BeforeProviderHeadersEvent, BeforeProviderRequestEvent, BeforeProviderRequestEventResult, BuildSystemPromptOptions, CompactOptions, ContextEvent, ContextUsage, CustomToolCallEvent, EditToolCallEvent, EntryRenderer, EntryRenderOptions, ExecOptions, ExecResult, Extension, ExtensionActions, ExtensionAPI, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, ExtensionContextActions, ExtensionError, ExtensionEvent, ExtensionFactory, ExtensionFlag, ExtensionHandler, ExtensionRuntime, ExtensionShortcut, ExtensionUIContext, ExtensionUIDialogOptions, ExtensionWidgetOptions, FindToolCallEvent, GrepToolCallEvent, InlineExtension, InputEvent, InputEventResult, InputSource, KeybindingsManager, LoadExtensionsResult, LsToolCallEvent, MarkdownTransformContext, MarkdownTransformer, MessageEndEvent, MessageRenderer, MessageRenderOptions, MessageStartEvent, MessageUpdateEvent, ProjectTrustContext, ProjectTrustEvent, ProjectTrustEventDecision, ProjectTrustEventResult, ProjectTrustHandler, ProviderConfig, ProviderModelConfig, ReadToolCallEvent, RegisteredCommand, RegisteredTool, ResolvedCommand, SessionBeforeCompactEvent, SessionBeforeForkEvent, SessionBeforeSwitchEvent, SessionBeforeTreeEvent, SessionCompactEvent, SessionInfoChangedEvent, SessionShutdownEvent, SessionStartEvent, SessionTreeEvent, SlashCommandInfo, SlashCommandSource, SourceInfo, TerminalInputHandler, ToolCallEvent, ToolCallEventResult, ToolDefinition, ToolExecutionEndEvent, ToolExecutionMode, ToolExecutionStartEvent, ToolExecutionUpdateEvent, ToolInfo, ToolRenderResultOptions, ToolResultEvent, TurnEndEvent, TurnStartEvent, UserBashEvent, UserBashEventResult, WidgetPlacement, WorkingIndicatorOptions, WriteToolCallEvent, } from "./core/extensions/index.ts";
801
+ diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/index.js b/node_modules/@earendil-works/pi-coding-agent/dist/index.js
802
+ index 76d728e..2b2c892 100644
803
+ --- a/node_modules/@earendil-works/pi-coding-agent/dist/index.js
804
+ +++ b/node_modules/@earendil-works/pi-coding-agent/dist/index.js
805
+ @@ -3,7 +3,7 @@ export { parseArgs } from "./cli/args.js";
806
+ // Config paths
807
+ export { CONFIG_DIR_NAME, getAgentDir, getDocsPath, getExamplesPath, getPackageDir, getReadmePath, VERSION, } from "./config.js";
808
+ export { AgentSession, parseSkillBlock, } from "./core/agent-session.js";
809
+ -export { readStoredCredential } from "./core/auth-storage.js";
810
+ +export { AuthStorage, readStoredCredential } from "./core/auth-storage.js";
811
+ // Compaction
812
+ export { calculateContextTokens, collectEntriesForBranchSummary, compact, DEFAULT_COMPACTION_SETTINGS, estimateTokens, findCutPoint, findTurnStartIndex, generateBranchSummary, generateSummary, generateSummaryWithUsage, getLastAssistantUsage, prepareBranchEntries, serializeConversation, shouldCompact, } from "./core/compaction/index.js";
813
+ export { createEventBus } from "./core/event-bus.js";
773
814
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/main.js b/node_modules/@earendil-works/pi-coding-agent/dist/main.js
774
- index fc62585..8b08615 100644
815
+ index 5ff9c80..5b91d5f 100644
775
816
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/main.js
776
817
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/main.js
777
- @@ -693,7 +693,58 @@ export async function main(args, options) {
818
+ @@ -786,7 +786,58 @@ export async function main(args, options) {
778
819
  if (exitCode !== 0) {
779
820
  process.exitCode = exitCode;
780
821
  }
@@ -869,7 +910,7 @@ index e3a5ccf..a561a8a 100644
869
910
  return warnings;
870
911
  }
871
912
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js
872
- index 3355a47..8c6bba9 100644
913
+ index 6f7ed6a..b0ed0ae 100644
873
914
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js
874
915
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js
875
916
  @@ -4,7 +4,7 @@
@@ -877,11 +918,11 @@ index 3355a47..8c6bba9 100644
877
918
  import { basename, dirname, join, relative } from "node:path";
878
919
  import { Container, getKeybindings, Input, matchesKey, Spacer, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
879
920
  -import { CONFIG_DIR_NAME } from "../../../config.js";
880
- +import { formatProjectConfigDirNames, projectConfigDirs, projectConfigWriteDir } from "../../../config.js";
921
+ +import { CONFIG_DIR_NAME, PROJECT_CONFIG_DIR_NAMES, formatProjectConfigDirNames, projectConfigDirs, projectConfigWriteDir } from "../../../config.js";
922
+ import { canonicalizePath, isLocalPath, resolvePath } from "../../../utils/paths.js";
881
923
  import { theme } from "../theme/theme.js";
882
924
  import { DynamicBorder } from "./dynamic-border.js";
883
- import { rawKeyHint } from "./keybinding-hints.js";
884
- @@ -41,7 +41,7 @@ function getGroupLabel(metadata, agentDir) {
925
+ @@ -43,7 +43,7 @@ function getGroupLabel(metadata, agentDir) {
885
926
  ? `User (${formatBaseDir(metadata.baseDir)})`
886
927
  : `Project (${formatBaseDir(metadata.baseDir)})`;
887
928
  }
@@ -890,8 +931,17 @@ index 3355a47..8c6bba9 100644
890
931
  }
891
932
  return metadata.scope === "user" ? "User settings" : "Project settings";
892
933
  }
893
- @@ -458,7 +458,12 @@ class ResourceList {
894
- }
934
+ @@ -141,7 +141,7 @@ class ConfigSelectorHeader {
935
+ const hint = switchHint + actionHint + sep + rawKeyHint("esc", "close");
936
+ const spacing = Math.max(1, width - visibleWidth(title) - visibleWidth(hint));
937
+ const scopeHint = this.writeScope === "project"
938
+ - ? theme.fg("muted", `${CONFIG_DIR_NAME}/settings.json · inherited global resources are dimmed`)
939
+ + ? theme.fg("muted", `${PROJECT_CONFIG_DIR_NAMES[0]}/settings.json · inherited global resources are dimmed`)
940
+ : theme.fg("muted", `~/${CONFIG_DIR_NAME}/agent/settings.json`);
941
+ return [
942
+ truncateToWidth(`${title}${" ".repeat(spacing)}${hint}`, width, ""),
943
+ @@ -695,7 +695,12 @@ class ResourceList {
944
+ return item.metadata.scope === "project" ? "project" : "user";
895
945
  }
896
946
  getTopLevelBaseDir(scope) {
897
947
  - return scope === "project" ? join(this.cwd, CONFIG_DIR_NAME) : this.agentDir;
@@ -904,20 +954,88 @@ index 3355a47..8c6bba9 100644
904
954
  }
905
955
  getResourcePattern(item) {
906
956
  const scope = item.metadata.scope;
957
+ diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js
958
+ index 1d9f046..2a1fba8 100644
959
+ --- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js
960
+ +++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js
961
+ @@ -1,5 +1,5 @@
962
+ import { isAbsolute, relative, resolve, sep } from "node:path";
963
+ -import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
964
+ +import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
965
+ import { areExperimentalFeaturesEnabled } from "../../../core/experimental.js";
966
+ import { addUsageToTotals, createUsageTotals } from "../../../core/usage-totals.js";
967
+ import { theme } from "../theme/theme.js";
968
+ @@ -14,6 +14,43 @@ function sanitizeStatusText(text) {
969
+ .replace(/ +/g, " ")
970
+ .trim();
971
+ }
972
+ +/**
973
+ + * Lay extension statuses out over as many lines as the terminal needs, keeping
974
+ + * each status whole. Upstream joined them into ONE line and truncated it to the
975
+ + * width, which silently hid whatever didn't fit — and what didn't fit was often
976
+ + * the loudest indicator on screen (Privateer's "permission gate OFF" flag). A
977
+ + * status too wide to fit on a line of its own is wrapped, never clipped.
978
+ + */
979
+ +function packStatusLines(statuses, width) {
980
+ + if (width <= 0)
981
+ + return [];
982
+ + const lines = [];
983
+ + let current = "";
984
+ + let currentWidth = 0;
985
+ + const flush = () => {
986
+ + if (current)
987
+ + lines.push(current);
988
+ + current = "";
989
+ + currentWidth = 0;
990
+ + };
991
+ + for (const status of statuses) {
992
+ + if (!status)
993
+ + continue;
994
+ + const statusWidth = visibleWidth(status);
995
+ + if (statusWidth > width) {
996
+ + flush();
997
+ + lines.push(...wrapTextWithAnsi(status, width));
998
+ + continue;
999
+ + }
1000
+ + const separator = current ? 1 : 0;
1001
+ + if (currentWidth + separator + statusWidth > width)
1002
+ + flush();
1003
+ + current = current ? `${current} ${status}` : status;
1004
+ + currentWidth += separator + statusWidth;
1005
+ + }
1006
+ + flush();
1007
+ + return lines;
1008
+ +}
1009
+ /**
1010
+ * Format token counts for compact footer display.
1011
+ */
1012
+ @@ -212,9 +249,9 @@ export class FooterComponent {
1013
+ const sortedStatuses = Array.from(extensionStatuses.entries())
1014
+ .sort(([a], [b]) => a.localeCompare(b))
1015
+ .map(([, text]) => sanitizeStatusText(text));
1016
+ - const statusLine = sortedStatuses.join(" ");
1017
+ - // Truncate to terminal width with dim ellipsis for consistency with footer style
1018
+ - lines.push(truncateToWidth(statusLine, width, theme.fg("dim", "...")));
1019
+ + // Wrap onto extra lines instead of truncating: a status is there because
1020
+ + // something wants to be seen, so none of them may be cut off.
1021
+ + lines.push(...packStatusLines(sortedStatuses, width));
1022
+ }
1023
+ return lines;
1024
+ }
907
1025
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js
908
- index 5d65200..f827cd7 100644
1026
+ index 42e655d..d00bec9 100644
909
1027
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js
910
1028
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js
911
- @@ -10,7 +10,7 @@ import { getProviders, } from "@earendil-works/pi-ai/compat";
912
- import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@earendil-works/pi-tui";
1029
+ @@ -10,7 +10,7 @@ import * as TuiLayouts from "@earendil-works/pi-tui";
1030
+ import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TuiAltScreen, TuiMainScreen, visibleWidth, } from "@earendil-works/pi-tui";
913
1031
  import chalk from "chalk";
914
1032
  import { spawn, spawnSync } from "child_process";
915
1033
  -import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
916
1034
  +import { APP_NAME, APP_TITLE, formatProjectConfigDirNames, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
917
1035
  import { parseSkillBlock } from "../../core/agent-session.js";
918
1036
  import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
919
- import { FooterDataProvider } from "../../core/footer-data-provider.js";
920
- @@ -110,7 +110,12 @@ export function formatResumeCommand(sessionManager) {
1037
+ import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
1038
+ @@ -119,7 +119,12 @@ export function formatResumeCommand(sessionManager) {
921
1039
  const sessionFile = sessionManager.getSessionFile();
922
1040
  if (!sessionFile || !fs.existsSync(sessionFile))
923
1041
  return undefined;
@@ -931,7 +1049,7 @@ index 5d65200..f827cd7 100644
931
1049
  if (!sessionManager.usesDefaultSessionDir()) {
932
1050
  args.push("--session-dir", quoteIfNeeded(sessionManager.getSessionDir()));
933
1051
  }
934
- @@ -301,9 +306,15 @@ export class InteractiveMode {
1052
+ @@ -405,9 +410,15 @@ export class InteractiveMode {
935
1053
  }
936
1054
  getBuiltInCommandConflictDiagnostics(extensionRunner) {
937
1055
  const builtinNames = new Set(BUILTIN_SLASH_COMMANDS.map((command) => command.name));
@@ -948,7 +1066,40 @@ index 5d65200..f827cd7 100644
948
1066
  .map((command) => ({
949
1067
  type: "warning",
950
1068
  message: command.invocationName === command.name
951
- @@ -2042,7 +2053,17 @@ export class InteractiveMode {
1069
+ @@ -754,20 +765,18 @@ export class InteractiveMode {
1070
+ this.showNewVersionNotification(newRelease);
1071
+ }
1072
+ });
1073
+ - // Start package update check asynchronously
1074
+ - this.checkForPackageUpdates()
1075
+ - .then((updates) => {
1076
+ - if (updates.length > 0) {
1077
+ - this.showPackageUpdateNotification(updates);
1078
+ - }
1079
+ - })
1080
+ - .finally(() => {
1081
+ - // On Windows, npm can overwrite the shared console title while checking
1082
+ - // extension package versions. Restore Pi's title after the startup check.
1083
+ - if (process.platform === "win32" && this.isInitialized) {
1084
+ - this.updateTerminalTitle();
1085
+ - }
1086
+ - });
1087
+ + // Privateer owns the package ("tool pack") update surface — see
1088
+ + // extensions/privateer-update.ts. Upstream ran its own check here and drew a
1089
+ + // warning-bordered box telling the user to run `pi update --extensions`: a
1090
+ + // binary that is installed nowhere on a Privateer machine, so the only
1091
+ + // instruction on screen could not be followed. We replace it with a single
1092
+ + // line in the startup banner plus /update, which applies packs in place. The
1093
+ + // CHECK is removed too, not just the box, so this costs no registry round
1094
+ + // trip — the extension does exactly one, on the same startup.
1095
+ + //
1096
+ + // 0.84.1 added a .finally() here restoring the Windows console title after
1097
+ + // npm overwrote it. That repair existed only because of this check, so it
1098
+ + // goes with it — nothing is left to scribble on the title.
1099
+ // Check tmux keyboard setup asynchronously
1100
+ this.checkTmuxKeyboardSetup().then((warning) => {
1101
+ if (warning) {
1102
+ @@ -2310,7 +2319,17 @@ export class InteractiveMode {
952
1103
  if (text === "/model" || text.startsWith("/model ")) {
953
1104
  const searchTerm = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
954
1105
  this.editor.setText("");
@@ -967,14 +1118,11 @@ index 5d65200..f827cd7 100644
967
1118
  return;
968
1119
  }
969
1120
  if (text === "/export" || text.startsWith("/export ")) {
970
- @@ -2105,14 +2126,44 @@ export class InteractiveMode {
971
- this.editor.setText("");
972
- return;
973
- }
974
- - if (text === "/login") {
975
- - this.showOAuthSelector("login");
976
- + if (text === "/login" || text.startsWith("/login ")) {
1121
+ @@ -2376,12 +2395,42 @@ export class InteractiveMode {
1122
+ if (text === "/login" || text.startsWith("/login ")) {
1123
+ const providerRef = text.startsWith("/login ") ? text.slice(7).trim() : undefined;
977
1124
  this.editor.setText("");
1125
+ - await this.handleLoginCommand(providerRef);
978
1126
  + // Privateer redirect: a bare /login IS the account sign-in. Pi's built-in
979
1127
  + // opens a two-step menu ("Use a subscription" → a list of 20+ providers)
980
1128
  + // that buries the one option a Privateer user wants, and — because it
@@ -982,14 +1130,14 @@ index 5d65200..f827cd7 100644
982
1130
  + // successful login through it left the terminal on its launch model and
983
1131
  + // the next prompt died on "No API key found". The extension's own flow
984
1132
  + // signs in, arms the account channel, and selects the model.
985
- + // `/login <anything>` (documented as `/login keys`) still opens Pi's own
986
- + // selector, so BYO provider keys stay reachable; and if the extension
987
- + // isn't loaded we fall back to Pi's selector for both.
988
- + if (text === "/login" && this.isExtensionCommand("/privateer")) {
1133
+ + // `/login <anything>` (documented as `/login keys`) still reaches Pi's
1134
+ + // own flow, so BYO provider keys stay reachable; and if the extension
1135
+ + // isn't loaded we fall back to Pi for both.
1136
+ + if (!providerRef && this.isExtensionCommand("/privateer")) {
989
1137
  + await this.session.prompt("/privateer login");
990
1138
  + }
991
1139
  + else {
992
- + this.showOAuthSelector("login");
1140
+ + await this.handleLoginCommand(providerRef);
993
1141
  + }
994
1142
  return;
995
1143
  }
@@ -1015,7 +1163,7 @@ index 5d65200..f827cd7 100644
1015
1163
  return;
1016
1164
  }
1017
1165
  if (text === "/new") {
1018
- @@ -2650,7 +2701,7 @@ export class InteractiveMode {
1166
+ @@ -3024,7 +3073,7 @@ export class InteractiveMode {
1019
1167
  if (this.chatContainer.children.length > 0) {
1020
1168
  this.chatContainer.addChild(new Spacer(1));
1021
1169
  }
@@ -1025,19 +1173,28 @@ index 5d65200..f827cd7 100644
1025
1173
  async getUserInput() {
1026
1174
  const queuedInput = this.pendingUserInputs.shift();
1027
1175
  diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js b/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js
1028
- index f81b4b4..9712a37 100644
1176
+ index 4230b18..f47e80f 100644
1029
1177
  --- a/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js
1030
1178
  +++ b/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js
1031
- @@ -2,7 +2,7 @@ import { Markdown } from "@earendil-works/pi-tui";
1179
+ @@ -3,7 +3,7 @@ import { Markdown } from "@earendil-works/pi-tui";
1032
1180
  import chalk from "chalk";
1033
1181
  import { selectConfig } from "./cli/config-selector.js";
1034
1182
  import { createProjectTrustContext } from "./cli/project-trust.js";
1035
1183
  -import { APP_NAME, CONFIG_DIR_NAME, detectInstallMethod, getAgentDir, getPackageDir, getSelfUpdateCommand, getSelfUpdateUnavailableInstruction, PACKAGE_NAME, VERSION, } from "./config.js";
1036
- +import { APP_NAME, detectInstallMethod, getAgentDir, getPackageDir, getSelfUpdateCommand, getSelfUpdateUnavailableInstruction, PACKAGE_NAME, PROJECT_CONFIG_DIR_NAMES, VERSION, } from "./config.js";
1184
+ +import { APP_NAME, CONFIG_DIR_NAME, detectInstallMethod, getAgentDir, getPackageDir, getSelfUpdateCommand, getSelfUpdateUnavailableInstruction, PACKAGE_NAME, PROJECT_CONFIG_DIR_NAMES, VERSION, } from "./config.js";
1185
+ import { ModelRuntime } from "./core/model-runtime.js";
1037
1186
  import { DefaultPackageManager } from "./core/package-manager.js";
1038
1187
  import { resolveProjectTrusted } from "./core/project-trust.js";
1039
- import { DefaultResourceLoader } from "./core/resource-loader.js";
1040
- @@ -57,7 +57,7 @@ function printPackageCommandHelp(command) {
1188
+ @@ -60,7 +60,7 @@ Without -l, starts in global settings (~/${CONFIG_DIR_NAME}/agent/settings.json)
1189
+ Press Tab in the TUI to switch between global and project-local modes.
1190
+
1191
+ Options:
1192
+ - -l, --local Edit project overrides (${CONFIG_DIR_NAME}/settings.json)
1193
+ + -l, --local Edit project overrides (${PROJECT_CONFIG_DIR_NAMES[0]}/settings.json)
1194
+ -a, --approve Trust project-local files for this command with -l
1195
+ -na, --no-approve Ignore project-local files for this command with -l
1196
+ `);
1197
+ @@ -74,7 +74,7 @@ function printPackageCommandHelp(command) {
1041
1198
  Install a package and add it to settings.
1042
1199
 
1043
1200
  Options:
@@ -1046,7 +1203,7 @@ index f81b4b4..9712a37 100644
1046
1203
  -a, --approve Trust project-local files for this command
1047
1204
  -na, --no-approve Ignore project-local files for this command
1048
1205
 
1049
- @@ -78,7 +78,7 @@ Remove a package and its source from settings.
1206
+ @@ -95,7 +95,7 @@ Remove a package and its source from settings.
1050
1207
  Alias: ${APP_NAME} uninstall <source> [-l]
1051
1208
 
1052
1209
  Options:
@@ -1055,71 +1212,3 @@ index f81b4b4..9712a37 100644
1055
1212
  -a, --approve Trust project-local files for this command
1056
1213
  -na, --no-approve Ignore project-local files for this command
1057
1214
 
1058
- diff --git a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js
1059
- index 68d0308..f7208f1 100644
1060
- --- a/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js
1061
- +++ b/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js
1062
- @@ -1,5 +1,5 @@
1063
- import { isAbsolute, relative, resolve, sep } from "node:path";
1064
- -import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
1065
- +import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
1066
- import { areExperimentalFeaturesEnabled } from "../../../core/experimental.js";
1067
- import { theme } from "../theme/theme.js";
1068
- /**
1069
- @@ -13,6 +13,43 @@ function sanitizeStatusText(text) {
1070
- .replace(/ +/g, " ")
1071
- .trim();
1072
- }
1073
- +/**
1074
- + * Lay extension statuses out over as many lines as the terminal needs, keeping
1075
- + * each status whole. Upstream joined them into ONE line and truncated it to the
1076
- + * width, which silently hid whatever didn't fit — and what didn't fit was often
1077
- + * the loudest indicator on screen (Privateer's "permission gate OFF" flag). A
1078
- + * status too wide to fit on a line of its own is wrapped, never clipped.
1079
- + */
1080
- +function packStatusLines(statuses, width) {
1081
- + if (width <= 0)
1082
- + return [];
1083
- + const lines = [];
1084
- + let current = "";
1085
- + let currentWidth = 0;
1086
- + const flush = () => {
1087
- + if (current)
1088
- + lines.push(current);
1089
- + current = "";
1090
- + currentWidth = 0;
1091
- + };
1092
- + for (const status of statuses) {
1093
- + if (!status)
1094
- + continue;
1095
- + const statusWidth = visibleWidth(status);
1096
- + if (statusWidth > width) {
1097
- + flush();
1098
- + lines.push(...wrapTextWithAnsi(status, width));
1099
- + continue;
1100
- + }
1101
- + const separator = current ? 1 : 0;
1102
- + if (currentWidth + separator + statusWidth > width)
1103
- + flush();
1104
- + current = current ? `${current} ${status}` : status;
1105
- + currentWidth += separator + statusWidth;
1106
- + }
1107
- + flush();
1108
- + return lines;
1109
- +}
1110
- /**
1111
- * Format token counts for compact footer display.
1112
- */
1113
- @@ -211,9 +248,9 @@ export class FooterComponent {
1114
- const sortedStatuses = Array.from(extensionStatuses.entries())
1115
- .sort(([a], [b]) => a.localeCompare(b))
1116
- .map(([, text]) => sanitizeStatusText(text));
1117
- - const statusLine = sortedStatuses.join(" ");
1118
- - // Truncate to terminal width with dim ellipsis for consistency with footer style
1119
- - lines.push(truncateToWidth(statusLine, width, theme.fg("dim", "...")));
1120
- + // Wrap onto extra lines instead of truncating: a status is there because
1121
- + // something wants to be seen, so none of them may be cut off.
1122
- + lines.push(...packStatusLines(sortedStatuses, width));
1123
- }
1124
- return lines;
1125
- }