@papillon6814/copi 0.7.0 → 0.7.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.
@@ -31,4 +31,5 @@ export declare function askPiForSuggestions(candidates: readonly ResourceCandida
31
31
  command: string;
32
32
  timeoutMs?: number;
33
33
  signal?: AbortSignal;
34
+ onProgress?: (done: number, total: number) => void;
34
35
  }): Promise<SuggestionResult>;
@@ -164,6 +164,7 @@ export async function askPiForSuggestions(candidates, options) {
164
164
  if (suggestion)
165
165
  suggestions.set(item.candidate.id, suggestion);
166
166
  }
167
+ options.onProgress?.(offset + chunk.length, candidates.length);
167
168
  }
168
169
  return { ok: true, suggestions };
169
170
  }
@@ -47,6 +47,10 @@ export declare function formatSearchProgress(progress: {
47
47
  total: number;
48
48
  current?: string;
49
49
  }, home: string): string;
50
+ /** Elapsed time as `42s` or `1m05s`. */
51
+ export declare function formatElapsed(ms: number): string;
52
+ /** Bar text while Pi reviews candidates: `40/77 resources · 1m05s`. */
53
+ export declare function formatPiProgress(done: number, total: number, elapsedMs: number): string;
50
54
  /**
51
55
  * SHA-256 over what saving would copy: for a file its mode and bytes, for a directory every regular file's
52
56
  * relative path, mode and bytes (sorted, same exclusions as discovery), each field length-prefixed.
@@ -200,6 +200,15 @@ export function formatSearchProgress(progress, home) {
200
200
  const count = `${progress.done}/${progress.total}`;
201
201
  return progress.current === undefined ? `${count} Searching` : `${count} ${displayPath(progress.current, home)}`;
202
202
  }
203
+ /** Elapsed time as `42s` or `1m05s`. */
204
+ export function formatElapsed(ms) {
205
+ const seconds = Math.max(0, Math.floor(ms / 1000));
206
+ return seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m${String(seconds % 60).padStart(2, "0")}s`;
207
+ }
208
+ /** Bar text while Pi reviews candidates: `40/77 resources · 1m05s`. */
209
+ export function formatPiProgress(done, total, elapsedMs) {
210
+ return `${done}/${total} resources \u00b7 ${formatElapsed(elapsedMs)}`;
211
+ }
203
212
  const fingerprintMaxFiles = 2000;
204
213
  const fingerprintMaxBytes = 50 * 1024 * 1024;
205
214
  /**
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import { assertModernCatalog, importPreparedResource, loadCatalog, prepareResource, readPreparedResource, readSnapshotFiles, saveCatalog, saveNameChanges } from "./catalog.js";
5
5
  import { discoverResources, isWorktreeCopy, piAgentDirectories } from "./discovery.js";
6
6
  import { createGroup, renameGroup, setGroupItems } from "./groups.js";
7
- import { candidateHint, candidateLabels, collapseIdenticalCandidates, contentFingerprint, defaultRenameName, displayPath, findSelectionConflicts, formatSearchProgress, formatOutsideRootSummary, gatherCandidateStats, identicalCopiesSuffix, mapWithLimit, pendingNameError, sortCandidates } from "./setup-display.js";
7
+ import { candidateHint, candidateLabels, collapseIdenticalCandidates, contentFingerprint, defaultRenameName, displayPath, findSelectionConflicts, formatPiProgress, formatSearchProgress, formatOutsideRootSummary, gatherCandidateStats, identicalCopiesSuffix, mapWithLimit, pendingNameError, sortCandidates } from "./setup-display.js";
8
8
  import { inspectPreparedResource, maskSensitiveText } from "./inspect.js";
9
9
  import { assertNameAvailable, assertNoNameConflicts, findNameConflicts, nameKey } from "./names.js";
10
10
  import { executeApply, planApply } from "./apply.js";
@@ -523,9 +523,29 @@ export async function runSetup(libraryDir, roots) {
523
523
  return;
524
524
  }
525
525
  if (ask) {
526
- const asking = clackSpinner(inheritedSignal());
527
- asking.start(`Asking Pi about ${sortedCandidates.length} resource(s)`);
528
- const answer = await askPiForSuggestions(sortedCandidates, { command: piCommand, ...inheritedSignal() });
526
+ // Pi answers a whole chunk at once, so the bar moves per chunk; the elapsed time keeps ticking in between.
527
+ const total = sortedCandidates.length;
528
+ const asking = clackProgress({ ...inheritedSignal(), max: total });
529
+ const startedAt = Date.now();
530
+ let answered = 0;
531
+ const label = () => `Asking Pi ${formatPiProgress(answered, total, Date.now() - startedAt)}`;
532
+ asking.start(label());
533
+ const ticker = setInterval(() => asking.message(label()), 1000);
534
+ let answer;
535
+ try {
536
+ answer = await askPiForSuggestions(sortedCandidates, {
537
+ command: piCommand,
538
+ ...inheritedSignal(),
539
+ onProgress: (done) => {
540
+ const step = done - answered;
541
+ answered = done;
542
+ asking.advance(step, label());
543
+ },
544
+ });
545
+ }
546
+ finally {
547
+ clearInterval(ticker);
548
+ }
529
549
  if (answer.ok) {
530
550
  suggestions = answer.suggestions;
531
551
  const kept = [...suggestions.values()].filter((suggestion) => suggestion.keep).length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papillon6814/copi",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Collect and organize reusable Pi coding-agent resources",
5
5
  "license": "MIT",
6
6
  "type": "module",