pi-code 1.0.59 → 1.0.60

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.
@@ -36,6 +36,7 @@ import { claudeEffortLevel } from './internal/effort.js'
36
36
  import { readManagedSettings } from './internal/managed-settings.js'
37
37
  import { isPlanModeState, PLAN_MODE_CHANNEL } from './internal/plan-mode-state.js'
38
38
  import { isProjectApprovedSilently } from './internal/project-approval.js'
39
+ import { repoRoot } from './internal/project-root.js'
39
40
  import { readSettingsChain } from './internal/settings-chain.js'
40
41
  import { watchSettingsFiles } from './internal/settings-watch.js'
41
42
  import { readActiveStyleName, settingsFiles } from './output-styles.js'
@@ -43,6 +44,18 @@ import { readActiveStyleName, settingsFiles } from './output-styles.js'
43
44
  const COMMAND_TIMEOUT_MS = 5_000
44
45
  const DEBOUNCE_MS = 300
45
46
 
47
+ /** Whether cwd sits in a git worktree: `.git` is a file pointing at the main
48
+ * checkout there, and a directory in an ordinary clone. */
49
+ function isGitWorktree(cwd: string): boolean {
50
+ const root = repoRoot(cwd)
51
+ if (root === undefined) return false
52
+ try {
53
+ return fs.statSync(path.join(root, '.git')).isFile()
54
+ } catch {
55
+ return false
56
+ }
57
+ }
58
+
46
59
  /** Claude sends its CLI version; pi-code's own version is the honest analogue. */
47
60
  const PACKAGE_VERSION = (() => {
48
61
  try {
@@ -235,6 +248,16 @@ export default function statusLine(pi: ExtensionAPI) {
235
248
  ctx.ui.setStatus('pi-code-status', commandLine ?? builtIn)
236
249
  }
237
250
 
251
+ /** The --add-dir directories, the flag pi-code registers for Claude's additional
252
+ * working directories. Empty when none were given, which is the documented shape. */
253
+ function addedDirs(): string[] {
254
+ const raw = String(pi.getFlag?.('add-dir') ?? '')
255
+ return raw
256
+ .split(',')
257
+ .map((dir) => dir.trim())
258
+ .filter(Boolean)
259
+ }
260
+
238
261
  /** The stdin payload per Claude's documented statusline contract. */
239
262
  function buildPayload(ctx: ExtensionContext): Record<string, unknown> {
240
263
  const usage = ctx.getContextUsage() ?? { tokens: null, contextWindow: 0, percent: null }
@@ -249,9 +272,16 @@ export default function statusLine(pi: ExtensionAPI) {
249
272
  session_id: ctx.sessionManager.getSessionId(),
250
273
  cwd: ctx.cwd,
251
274
  version: PACKAGE_VERSION,
252
- // added_dirs is always empty: pi has no /add-dir; the field stays present
253
- // because Claude documents "Empty array if none have been added".
254
- workspace: { current_dir: ctx.cwd, project_dir: ctx.cwd, added_dirs: [] },
275
+ // project_dir is the repository, not the directory the session started in: a
276
+ // script labelling the project showed whichever subdirectory it was launched
277
+ // from. git_worktree reports the .git file a worktree carries in place of a
278
+ // directory. added_dirs comes from the --add-dir flag pi-code registers.
279
+ workspace: {
280
+ current_dir: ctx.cwd,
281
+ project_dir: repoRoot(ctx.cwd) ?? ctx.cwd,
282
+ git_worktree: isGitWorktree(ctx.cwd),
283
+ added_dirs: addedDirs(),
284
+ },
255
285
  // Both fields, per Claude's documented contract: published statusline scripts
256
286
  // read .model.display_name and render the literal "null" when it is missing.
257
287
  model: { id: model?.id ?? '', display_name: model?.name ?? model?.id ?? '' },
@@ -269,17 +299,17 @@ export default function statusLine(pi: ExtensionAPI) {
269
299
  total_input_tokens: usage.tokens,
270
300
  // The per-component breakdown from the last message's usage, which
271
301
  // ctx.getContextUsage() (input-side estimate only) cannot provide.
272
- ...(lastUsage
302
+ // Present before the first response too, as Claude sends them: a script
303
+ // reading current_usage gets null rather than undefined.
304
+ total_output_tokens: lastUsage?.output ?? 0,
305
+ current_usage: lastUsage
273
306
  ? {
274
- total_output_tokens: lastUsage.output,
275
- current_usage: {
276
- input_tokens: lastUsage.input,
277
- output_tokens: lastUsage.output,
278
- cache_read_input_tokens: lastUsage.cacheRead,
279
- cache_creation_input_tokens: lastUsage.cacheWrite,
280
- },
307
+ input_tokens: lastUsage.input,
308
+ output_tokens: lastUsage.output,
309
+ cache_read_input_tokens: lastUsage.cacheRead,
310
+ cache_creation_input_tokens: lastUsage.cacheWrite,
281
311
  }
282
- : {}),
312
+ : null,
283
313
  },
284
314
  // The true combined total when a message usage is known, else the input-side estimate.
285
315
  exceeds_200k_tokens: (lastUsage?.totalTokens ?? usage.tokens ?? 0) > 200_000,
@@ -323,9 +353,17 @@ export default function statusLine(pi: ExtensionAPI) {
323
353
  const result = await runHookCommand(config.command, buildPayload(ctx), COMMAND_TIMEOUT_MS, undefined, undefined, (kill) => {
324
354
  killInflight = kill
325
355
  })
326
- const first = result.stdout.split('\n')[0].trimEnd()
356
+ // Claude: "Your script can output multiple lines to create a richer display."
357
+ // pi has one row for every extension status and replaces newlines with spaces
358
+ // before rendering it (footer.js sanitizeStatusText), so the rows are joined
359
+ // here rather than dropped; taking only the first silently lost the rest.
360
+ const rows = result.stdout
361
+ .split('\n')
362
+ .map((line) => line.trim())
363
+ .filter(Boolean)
364
+ .join(' ')
327
365
  const pad = ' '.repeat(config.padding)
328
- commandLine = first ? `${pad}${first}${pad}` : undefined
366
+ commandLine = rows ? `${pad}${rows}${pad}` : undefined
329
367
  show(ctx, segmentText(ctx, ctx.ui.theme.fg('dim', '○')))
330
368
  } catch {
331
369
  // A replaced or reloaded session invalidates ctx while the command is in
@@ -463,9 +501,13 @@ export default function statusLine(pi: ExtensionAPI) {
463
501
  // command change re-resolves and re-runs.
464
502
  disposeSettingsWatch()
465
503
  disposeSettingsWatch = watchSettingsFiles(files, () => {
504
+ const previousCommand = config?.command
466
505
  config = readDisableAllHooks(files) ? undefined : readStatusLineConfig(files)
467
506
  armRefresh()
468
- scheduleRefresh()
507
+ // The debounce batches rapid triggers, but a command the user just edited has
508
+ // nothing to batch with: run it now so the result of the edit is immediate.
509
+ if (config?.command !== previousCommand) void runCommand(ctx)
510
+ else scheduleRefresh()
469
511
  })
470
512
  show(ctx, segmentText(ctx, ctx.ui.theme.fg('dim', '○')))
471
513
  scheduleRefresh()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-code",
3
- "version": "1.0.59",
3
+ "version": "1.0.60",
4
4
  "description": "Claude Code experience for the pi coding agent: reads your .claude config (rules, commands, skills, hooks, output styles, MCP servers, agents) and adds todo, checkpoints, memory, web, subagents, and goals",
5
5
  "keywords": [
6
6
  "pi",