pim-agent 0.0.1 → 0.10.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/LICENSE +21 -0
- package/README.md +293 -7
- package/bin/pim.ts +151 -0
- package/package.json +82 -4
- package/packages/core/src/attachments/AttachmentStore.ts +99 -0
- package/packages/core/src/attachments/Attachments.ts +69 -0
- package/packages/core/src/extensions/CoreExtensions.ts +54 -0
- package/packages/core/src/extensions/apply-patch/coordinator.ts +53 -0
- package/packages/core/src/extensions/apply-patch/executor.ts +550 -0
- package/packages/core/src/extensions/apply-patch/index.ts +69 -0
- package/packages/core/src/extensions/apply-patch/matcher.ts +47 -0
- package/packages/core/src/extensions/apply-patch/model.ts +39 -0
- package/packages/core/src/extensions/apply-patch/parser.ts +379 -0
- package/packages/core/src/extensions/apply-patch/render.ts +177 -0
- package/packages/core/src/extensions/apply-patch/schema.ts +37 -0
- package/packages/core/src/extensions/apply-patch/types.ts +30 -0
- package/packages/core/src/extensions/bash/capture.ts +86 -0
- package/packages/core/src/extensions/bash/format.ts +110 -0
- package/packages/core/src/extensions/bash/image.ts +31 -0
- package/packages/core/src/extensions/bash/index.ts +88 -0
- package/packages/core/src/extensions/bash/render.ts +31 -0
- package/packages/core/src/extensions/bash/run.ts +221 -0
- package/packages/core/src/extensions/bash/schema.ts +64 -0
- package/packages/core/src/extensions/edit/edit.ts +366 -0
- package/packages/core/src/extensions/edit/index.ts +36 -0
- package/packages/core/src/extensions/edit/render.ts +16 -0
- package/packages/core/src/extensions/edit/schema.ts +37 -0
- package/packages/core/src/extensions/glob/glob.ts +34 -0
- package/packages/core/src/extensions/glob/index.ts +83 -0
- package/packages/core/src/extensions/glob/render.ts +72 -0
- package/packages/core/src/extensions/glob/schema.ts +66 -0
- package/packages/core/src/extensions/grep/grep.ts +256 -0
- package/packages/core/src/extensions/grep/index.ts +114 -0
- package/packages/core/src/extensions/grep/render.ts +271 -0
- package/packages/core/src/extensions/grep/schema.ts +92 -0
- package/packages/core/src/extensions/read/ImageMemory.ts +42 -0
- package/packages/core/src/extensions/read/image.ts +57 -0
- package/packages/core/src/extensions/read/index.ts +88 -0
- package/packages/core/src/extensions/read/read.ts +191 -0
- package/packages/core/src/extensions/read/render.ts +58 -0
- package/packages/core/src/extensions/read/schema.ts +49 -0
- package/packages/core/src/extensions/subagent/index.ts +33 -0
- package/packages/core/src/extensions/subagent/render.ts +127 -0
- package/packages/core/src/extensions/subagent/schema.ts +9 -0
- package/packages/core/src/extensions/subagent/subagent.ts +419 -0
- package/packages/core/src/extensions/system-prompt/index.ts +36 -0
- package/packages/core/src/extensions/system-prompt/prompt.ts +232 -0
- package/packages/core/src/extensions/todo/index.ts +123 -0
- package/packages/core/src/extensions/todo/render.ts +156 -0
- package/packages/core/src/extensions/todo/schema.ts +24 -0
- package/packages/core/src/extensions/todo/todo.ts +185 -0
- package/packages/core/src/extensions/web-fetch/JinaReaderClient.ts +187 -0
- package/packages/core/src/extensions/web-fetch/WebViewFetchClient.ts +183 -0
- package/packages/core/src/extensions/web-fetch/WebViewMarkdownSnapshot.ts +512 -0
- package/packages/core/src/extensions/web-fetch/fetch.ts +265 -0
- package/packages/core/src/extensions/web-fetch/image.ts +165 -0
- package/packages/core/src/extensions/web-fetch/index.ts +83 -0
- package/packages/core/src/extensions/web-fetch/render.ts +100 -0
- package/packages/core/src/extensions/web-fetch/schema.ts +46 -0
- package/packages/core/src/extensions/web-search/ExaMcpClient.ts +251 -0
- package/packages/core/src/extensions/web-search/SearchBreaker.ts +147 -0
- package/packages/core/src/extensions/web-search/SearchChain.ts +77 -0
- package/packages/core/src/extensions/web-search/index.ts +90 -0
- package/packages/core/src/extensions/web-search/providers/DuckDuckGoProvider.ts +195 -0
- package/packages/core/src/extensions/web-search/providers/ExaProvider.ts +54 -0
- package/packages/core/src/extensions/web-search/providers/FirecrawlProvider.ts +144 -0
- package/packages/core/src/extensions/web-search/providers/SearchProvider.ts +118 -0
- package/packages/core/src/extensions/web-search/render.ts +49 -0
- package/packages/core/src/extensions/web-search/schema.ts +29 -0
- package/packages/core/src/extensions/web-search/search.ts +23 -0
- package/packages/core/src/extensions/write/index.ts +58 -0
- package/packages/core/src/extensions/write/render.ts +16 -0
- package/packages/core/src/extensions/write/schema.ts +14 -0
- package/packages/core/src/extensions/write/write.ts +87 -0
- package/packages/core/src/picker/FilePickerSuggestionEngine.ts +14 -0
- package/packages/core/src/picker/InProcessFilePickerSuggestionEngine.ts +52 -0
- package/packages/core/src/picker/PickerItem.ts +6 -0
- package/packages/core/src/picker/PickerService.ts +132 -0
- package/packages/core/src/picker/RemoteFilePickerSuggestionEngine.ts +56 -0
- package/packages/core/src/picker/WorkerFilePickerSuggestionEngine.ts +268 -0
- package/packages/core/src/picker/catalog.ts +220 -0
- package/packages/core/src/picker/commandRanker.ts +17 -0
- package/packages/core/src/picker/filePickerWorker.ts +66 -0
- package/packages/core/src/picker/filePickerWorkerMessages.ts +39 -0
- package/packages/core/src/picker/ranker.ts +245 -0
- package/packages/core/src/picker/token.ts +77 -0
- package/packages/core/src/session/AgentRuntime.ts +85 -0
- package/packages/core/src/session/EventLog.ts +156 -0
- package/packages/core/src/session/MessageText.ts +21 -0
- package/packages/core/src/session/ReadCursors.ts +94 -0
- package/packages/core/src/session/SearchIndex.ts +752 -0
- package/packages/core/src/session/SearchTokens.ts +97 -0
- package/packages/core/src/session/SessionCache.ts +68 -0
- package/packages/core/src/session/SessionDigest.ts +161 -0
- package/packages/core/src/session/SessionHost.ts +892 -0
- package/packages/core/src/session/SessionLease.ts +279 -0
- package/packages/core/src/session/SessionMeta.ts +267 -0
- package/packages/core/src/session/SessionName.ts +18 -0
- package/packages/core/src/session/SessionRegistry.ts +244 -0
- package/packages/core/src/session/WriteMark.ts +51 -0
- package/packages/core/src/shared/Cli.ts +25 -0
- package/packages/core/src/shared/DaemonUnit.ts +13 -0
- package/packages/core/src/shared/DiffLines.ts +304 -0
- package/packages/core/src/shared/DiffPatch.ts +70 -0
- package/packages/core/src/shared/DiffRenderer.ts +283 -0
- package/packages/core/src/shared/DiffView.ts +29 -0
- package/packages/core/src/shared/Directories.ts +61 -0
- package/packages/core/src/shared/EditMatcher.ts +767 -0
- package/packages/core/src/shared/Errors.ts +9 -0
- package/packages/core/src/shared/ExtensionToggles.ts +130 -0
- package/packages/core/src/shared/FileEnumerator.ts +361 -0
- package/packages/core/src/shared/FileScanner.ts +57 -0
- package/packages/core/src/shared/FileWatch.ts +121 -0
- package/packages/core/src/shared/Format.ts +74 -0
- package/packages/core/src/shared/Fs.ts +91 -0
- package/packages/core/src/shared/FsErrors.ts +67 -0
- package/packages/core/src/shared/FuzzyMatcher.ts +71 -0
- package/packages/core/src/shared/Git.ts +530 -0
- package/packages/core/src/shared/GitMonitor.ts +221 -0
- package/packages/core/src/shared/GlobExclusions.ts +13 -0
- package/packages/core/src/shared/Http.ts +14 -0
- package/packages/core/src/shared/ImageMime.ts +36 -0
- package/packages/core/src/shared/Images.ts +357 -0
- package/packages/core/src/shared/Json.ts +19 -0
- package/packages/core/src/shared/Languages.ts +95 -0
- package/packages/core/src/shared/Levenshtein.ts +83 -0
- package/packages/core/src/shared/Lines.ts +79 -0
- package/packages/core/src/shared/McpClient.ts +444 -0
- package/packages/core/src/shared/OutputBudget.ts +84 -0
- package/packages/core/src/shared/PatchSummary.ts +40 -0
- package/packages/core/src/shared/Paths.ts +80 -0
- package/packages/core/src/shared/PimSettings.ts +128 -0
- package/packages/core/src/shared/PimVersion.ts +93 -0
- package/packages/core/src/shared/Pool.ts +26 -0
- package/packages/core/src/shared/Proc.ts +50 -0
- package/packages/core/src/shared/RateLimiter.ts +50 -0
- package/packages/core/src/shared/Renderer.ts +371 -0
- package/packages/core/src/shared/RepoDiff.ts +741 -0
- package/packages/core/src/shared/SafePath.ts +17 -0
- package/packages/core/src/shared/SearchRender.ts +82 -0
- package/packages/core/src/shared/SendFile.ts +23 -0
- package/packages/core/src/shared/SpillCache.ts +78 -0
- package/packages/core/src/shared/SubagentLogs.ts +102 -0
- package/packages/core/src/shared/Supervisor.ts +460 -0
- package/packages/core/src/shared/Surface.ts +2 -0
- package/packages/core/src/shared/Sweeper.ts +32 -0
- package/packages/core/src/shared/Tools.ts +801 -0
- package/packages/core/src/shared/Updater.ts +209 -0
- package/packages/core/src/view/AnsiPainter.ts +227 -0
- package/packages/core/src/view/BodyRenderer.ts +102 -0
- package/packages/core/src/view/DiffBlocks.ts +52 -0
- package/packages/core/src/view/DiffExpand.ts +214 -0
- package/packages/core/src/view/DiffLayout.ts +69 -0
- package/packages/core/src/view/DiffPairs.ts +47 -0
- package/packages/core/src/view/MarkdownPainter.ts +314 -0
- package/packages/core/src/view/MovePath.ts +70 -0
- package/packages/core/src/view/Painting.ts +182 -0
- package/packages/core/src/view/ViewBlock.ts +124 -0
- package/packages/daemon/src/Daemon.ts +63 -0
- package/packages/daemon/src/DaemonInstall.ts +115 -0
- package/packages/daemon/src/Surfaces.ts +66 -0
- package/packages/daemon/src/TelegramSurface.ts +30 -0
- package/packages/daemon/src/WebSurface.ts +56 -0
- package/packages/daemon/src/index.ts +37 -0
- package/packages/protocol/src/Command.ts +219 -0
- package/packages/protocol/src/Diff.ts +15 -0
- package/packages/protocol/src/ImageRoute.ts +23 -0
- package/packages/protocol/src/ServerEvent.ts +406 -0
- package/packages/server/src/AttachmentEndpoint.ts +184 -0
- package/packages/server/src/ClientConnection.ts +277 -0
- package/packages/server/src/ImageEndpoint.ts +36 -0
- package/packages/server/src/ProbeClient.ts +374 -0
- package/packages/server/src/Reloader.ts +93 -0
- package/packages/server/src/SendFileTool.ts +88 -0
- package/packages/server/src/SessionCatalogue.ts +456 -0
- package/packages/server/src/SessionProjection.ts +194 -0
- package/packages/server/src/SessionStream.ts +627 -0
- package/packages/server/src/StaticClient.ts +178 -0
- package/packages/server/src/WebOptions.ts +72 -0
- package/packages/server/src/WsGateway.ts +798 -0
- package/packages/server/src/probe.ts +200 -0
- package/packages/telegram/src/Bot.ts +229 -0
- package/packages/telegram/src/Commands.ts +768 -0
- package/packages/telegram/src/Config.ts +148 -0
- package/packages/telegram/src/Markdown.ts +235 -0
- package/packages/telegram/src/Message.ts +152 -0
- package/packages/telegram/src/Renderer.ts +456 -0
- package/packages/telegram/src/SendFileSchema.ts +18 -0
- package/packages/telegram/src/SendFileTool.ts +65 -0
- package/packages/telegram/src/Session.ts +253 -0
- package/packages/telegram/src/SessionRegistry.ts +126 -0
- package/packages/telegram/src/TaskScheduler.ts +296 -0
- package/packages/telegram/src/TaskSchema.ts +88 -0
- package/packages/telegram/src/TaskStore.ts +67 -0
- package/packages/telegram/src/TaskTool.ts +208 -0
- package/packages/telegram/src/TelegramHtml.ts +288 -0
- package/packages/telegram/src/TypingIndicator.ts +43 -0
- package/packages/telegram/src/UpdateConfirm.ts +61 -0
- package/packages/tui/src/extensions/_init/index.ts +144 -0
- package/packages/tui/src/extensions/command-picker/index.ts +75 -0
- package/packages/tui/src/extensions/file-picker/index.ts +156 -0
- package/packages/tui/src/extensions/footer/index.ts +93 -0
- package/packages/tui/src/extensions/footer/powerline.ts +87 -0
- package/packages/tui/src/extensions/footer/segments.ts +202 -0
- package/packages/tui/src/extensions/pim/index.ts +89 -0
- package/packages/tui/src/extensions/session-lease/index.ts +253 -0
- package/packages/tui/src/extensions/tps/index.ts +118 -0
- package/packages/tui/src/extensions/working-indicator/index.ts +75 -0
- package/packages/tui/src/extensions/wrapProvider.ts +18 -0
- package/packages/tui/src/themes/pim-dark.json +84 -0
- package/packages/tui/src/themes/pim-light.json +84 -0
- package/packages/tui/src/themes/themeCliArgs.ts +20 -0
- package/packages/web/dist/client/apple-touch-icon.png +0 -0
- package/packages/web/dist/client/assets/bash-LmRjUKlR.js +1 -0
- package/packages/web/dist/client/assets/c-DBVbJFpC.js +1 -0
- package/packages/web/dist/client/assets/clojure-CR7dvLJ4.js +1 -0
- package/packages/web/dist/client/assets/cmake-CgP6gB4W.js +1 -0
- package/packages/web/dist/client/assets/commit-mono-latin-300-italic-B48RJMeK.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-300-italic-DBq5bJCE.woff2 +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-300-normal-B-iV2FbL.woff2 +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-300-normal-kM0OTZCv.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-400-italic-BXinMwCi.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-400-italic-DjSHLl2N.woff2 +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-400-normal-s0S3qwFW.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-400-normal-wzhe4RuD.woff2 +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-600-normal-BuQDXT0M.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-600-normal-SwgSWHaV.woff2 +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-700-normal-1-GU0IUE.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-700-normal-DU8mrtj2.woff2 +0 -0
- package/packages/web/dist/client/assets/core-CA0aSzPu.js +3 -0
- package/packages/web/dist/client/assets/cpp-N2d8M7Nc.js +1 -0
- package/packages/web/dist/client/assets/csharp-yYt0Ikks.js +1 -0
- package/packages/web/dist/client/assets/css-D8M8GYaw.js +1 -0
- package/packages/web/dist/client/assets/diff-BhdZizse.js +1 -0
- package/packages/web/dist/client/assets/dockerfile-C0zufGsU.js +1 -0
- package/packages/web/dist/client/assets/elixir-DXIHlGsR.js +1 -0
- package/packages/web/dist/client/assets/erlang-BjYI8dhz.js +1 -0
- package/packages/web/dist/client/assets/go-CYstVVJV.js +1 -0
- package/packages/web/dist/client/assets/graphql-BHHhacaR.js +1 -0
- package/packages/web/dist/client/assets/haskell-DUkhXIda.js +1 -0
- package/packages/web/dist/client/assets/index-B9be-i9d.css +1 -0
- package/packages/web/dist/client/assets/index-CpinM5NK.js +45 -0
- package/packages/web/dist/client/assets/ini-B1CroMsf.js +1 -0
- package/packages/web/dist/client/assets/java-Dde-Wnb4.js +1 -0
- package/packages/web/dist/client/assets/javascript-8AmHuH5O.js +1 -0
- package/packages/web/dist/client/assets/json-PmyKWHmm.js +1 -0
- package/packages/web/dist/client/assets/kotlin-DgSK8bu-.js +2 -0
- package/packages/web/dist/client/assets/less-Bp0mgmpG.js +1 -0
- package/packages/web/dist/client/assets/lua-1dXYaL-H.js +1 -0
- package/packages/web/dist/client/assets/makefile-BX-o3EIz.js +1 -0
- package/packages/web/dist/client/assets/markdown-D6FrnlqJ.js +1 -0
- package/packages/web/dist/client/assets/ocaml-B-P1Uyq8.js +1 -0
- package/packages/web/dist/client/assets/perl-CvqMZYuf.js +1 -0
- package/packages/web/dist/client/assets/php-DuZl5e-L.js +2 -0
- package/packages/web/dist/client/assets/powershell-BUe16zE4.js +1 -0
- package/packages/web/dist/client/assets/protobuf-DGOW1qT8.js +1 -0
- package/packages/web/dist/client/assets/python-gHmu6VPG.js +1 -0
- package/packages/web/dist/client/assets/r-DNWPCnjT.js +1 -0
- package/packages/web/dist/client/assets/ruby-BGpZ3p4_.js +1 -0
- package/packages/web/dist/client/assets/rust-BIKPVoi6.js +1 -0
- package/packages/web/dist/client/assets/scala-C9lP4FWY.js +1 -0
- package/packages/web/dist/client/assets/scss-gOyOKiXd.js +1 -0
- package/packages/web/dist/client/assets/sql-DWRKyvJ-.js +1 -0
- package/packages/web/dist/client/assets/swift-BTn9__hL.js +1 -0
- package/packages/web/dist/client/assets/typescript-CxkPLmSc.js +1 -0
- package/packages/web/dist/client/assets/vim-Dhhcn_CA.js +1 -0
- package/packages/web/dist/client/assets/xml-COl5yFSC.js +1 -0
- package/packages/web/dist/client/assets/yaml-DjOhhVLG.js +1 -0
- package/packages/web/dist/client/favicon.svg +1 -0
- package/packages/web/dist/client/icon-192.png +0 -0
- package/packages/web/dist/client/icon-512.png +0 -0
- package/packages/web/dist/client/icon-maskable.png +0 -0
- package/packages/web/dist/client/index.html +24 -0
- package/packages/web/dist/client/manifest.webmanifest +21 -0
- package/packages/web/dist/client/wordmark.svg +1 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const maxBytes = 32 * 1024;
|
|
2
|
+
const maxLineLength = 2000;
|
|
3
|
+
|
|
4
|
+
function truncateLine(line: string): string {
|
|
5
|
+
if (line.length <= maxLineLength) {
|
|
6
|
+
return line;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return `${line.slice(0, maxLineLength)}... (line truncated to ${maxLineLength} chars)`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function truncateUtf8(
|
|
13
|
+
content: string,
|
|
14
|
+
limit: number = maxBytes
|
|
15
|
+
): {
|
|
16
|
+
readonly body: string;
|
|
17
|
+
readonly returnedBytes: number;
|
|
18
|
+
readonly totalBytes: number;
|
|
19
|
+
readonly truncated: boolean;
|
|
20
|
+
} {
|
|
21
|
+
const totalBytes = Buffer.byteLength(content, "utf8");
|
|
22
|
+
|
|
23
|
+
if (totalBytes <= limit) {
|
|
24
|
+
return {
|
|
25
|
+
body: content,
|
|
26
|
+
returnedBytes: totalBytes,
|
|
27
|
+
totalBytes,
|
|
28
|
+
truncated: false,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const encoded = new TextEncoder().encode(content);
|
|
33
|
+
let cut = limit;
|
|
34
|
+
while (cut > 0 && ((encoded[cut] ?? 0) & 0xc0) === 0x80) {
|
|
35
|
+
cut -= 1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const body = new TextDecoder("utf-8").decode(encoded.subarray(0, cut));
|
|
39
|
+
|
|
40
|
+
return { body, returnedBytes: cut, totalBytes, truncated: true };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function applyByteCap(
|
|
44
|
+
items: readonly string[],
|
|
45
|
+
options: {
|
|
46
|
+
readonly maxBytes?: number;
|
|
47
|
+
readonly separator?: string;
|
|
48
|
+
} = {}
|
|
49
|
+
): {
|
|
50
|
+
readonly visible: readonly string[];
|
|
51
|
+
readonly droppedItems: number;
|
|
52
|
+
} {
|
|
53
|
+
const limit = options.maxBytes ?? maxBytes;
|
|
54
|
+
const separator = options.separator ?? "\n";
|
|
55
|
+
const separatorBytes = Buffer.byteLength(separator, "utf8");
|
|
56
|
+
const visible: string[] = [];
|
|
57
|
+
let bytes = 0;
|
|
58
|
+
|
|
59
|
+
for (const item of items) {
|
|
60
|
+
const itemBytes = Buffer.byteLength(item, "utf8");
|
|
61
|
+
const cost = visible.length === 0 ? itemBytes : separatorBytes + itemBytes;
|
|
62
|
+
|
|
63
|
+
if (visible.length > 0 && bytes + cost > limit) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
visible.push(item);
|
|
68
|
+
bytes += cost;
|
|
69
|
+
|
|
70
|
+
if (bytes >= limit) {
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { visible, droppedItems: items.length - visible.length };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const OutputBudget = {
|
|
79
|
+
maxBytes,
|
|
80
|
+
maxLineLength,
|
|
81
|
+
truncateLine,
|
|
82
|
+
truncateUtf8,
|
|
83
|
+
applyByteCap,
|
|
84
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const ADD_FILE_MARKER = "*** Add File: ";
|
|
2
|
+
export const DELETE_FILE_MARKER = "*** Delete File: ";
|
|
3
|
+
export const UPDATE_FILE_MARKER = "*** Update File: ";
|
|
4
|
+
export const MOVE_TO_MARKER = "*** Move to:";
|
|
5
|
+
|
|
6
|
+
const FILE_MARKERS = [
|
|
7
|
+
ADD_FILE_MARKER,
|
|
8
|
+
DELETE_FILE_MARKER,
|
|
9
|
+
UPDATE_FILE_MARKER,
|
|
10
|
+
] as const;
|
|
11
|
+
|
|
12
|
+
function firstPath(input: string): string | undefined {
|
|
13
|
+
for (const raw of input.split("\n")) {
|
|
14
|
+
const line = raw.trim();
|
|
15
|
+
for (const marker of FILE_MARKERS) {
|
|
16
|
+
if (line.startsWith(marker)) {
|
|
17
|
+
return cleanPath(line.slice(marker.length));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function cleanPath(raw: string): string {
|
|
25
|
+
let path = raw.trim();
|
|
26
|
+
if (path.startsWith("@")) {
|
|
27
|
+
path = path.slice(1).trim();
|
|
28
|
+
}
|
|
29
|
+
if (path.length >= 2) {
|
|
30
|
+
const first = path[0]!;
|
|
31
|
+
const last = path.at(-1)!;
|
|
32
|
+
if ((first === '"' || first === "'" || first === "`") && first === last) {
|
|
33
|
+
path = path.slice(1, -1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return path;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Fault-tolerant scan of V4A patch text for renderers; unrecognized lines are ignored. */
|
|
40
|
+
export const PatchSummary = { firstPath };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { chmod, mkdir } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { isAbsolute, relative, resolve as pathResolve, sep } from "node:path";
|
|
4
|
+
|
|
5
|
+
function pimHomeDir(): string {
|
|
6
|
+
return expandHome(process.env.PIM_HOME_DIR ?? "~/.pim");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function ensurePimHome(): Promise<string> {
|
|
10
|
+
const dir = pimHomeDir();
|
|
11
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
12
|
+
await chmod(dir, 0o700);
|
|
13
|
+
return dir;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function resolve(value: string, baseDir: string): string {
|
|
17
|
+
const expanded = expandHome(value);
|
|
18
|
+
return isAbsolute(expanded) ? expanded : pathResolve(baseDir, expanded);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function toForwardSlashes(path: string): string {
|
|
22
|
+
return sep === "/" ? path : path.split(sep).join("/");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function expandHome(value: string): string {
|
|
26
|
+
if (value === "~") {
|
|
27
|
+
return homedir();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (value.startsWith("~/")) {
|
|
31
|
+
return pathResolve(homedir(), value.slice(2));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function abbreviateHome(path: string): string {
|
|
38
|
+
const home = homedir();
|
|
39
|
+
return home && path.startsWith(home) ? `~${path.slice(home.length)}` : path;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function displayRelative(path: string, cwd: string): string {
|
|
43
|
+
const rel = relative(cwd, path);
|
|
44
|
+
|
|
45
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
|
|
46
|
+
return path;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return rel;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function compare(left: string, right: string): number {
|
|
53
|
+
if (left < right) {
|
|
54
|
+
return -1;
|
|
55
|
+
}
|
|
56
|
+
if (left > right) {
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function titleOr(
|
|
63
|
+
path: string | undefined,
|
|
64
|
+
cwd: string,
|
|
65
|
+
placeholder = "..."
|
|
66
|
+
): string {
|
|
67
|
+
return path ? displayRelative(path, cwd) : placeholder;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const Paths = {
|
|
71
|
+
pimHomeDir,
|
|
72
|
+
ensurePimHome,
|
|
73
|
+
resolve,
|
|
74
|
+
toForwardSlashes,
|
|
75
|
+
expandHome,
|
|
76
|
+
abbreviateHome,
|
|
77
|
+
displayRelative,
|
|
78
|
+
titleOr,
|
|
79
|
+
compare,
|
|
80
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { type Static, Type } from "typebox";
|
|
3
|
+
import { Value } from "typebox/value";
|
|
4
|
+
|
|
5
|
+
import { Fs } from "./Fs";
|
|
6
|
+
import { Paths } from "./Paths";
|
|
7
|
+
|
|
8
|
+
const Schema = Type.Object({
|
|
9
|
+
extensions: Type.Object(
|
|
10
|
+
{
|
|
11
|
+
toggles: Type.Record(Type.String(), Type.Boolean(), { default: {} }),
|
|
12
|
+
},
|
|
13
|
+
{ default: { toggles: {} } }
|
|
14
|
+
),
|
|
15
|
+
exa: Type.Object(
|
|
16
|
+
{
|
|
17
|
+
apiKey: Type.Optional(Type.String()),
|
|
18
|
+
},
|
|
19
|
+
{ default: {} }
|
|
20
|
+
),
|
|
21
|
+
jina: Type.Object(
|
|
22
|
+
{
|
|
23
|
+
apiKey: Type.Optional(Type.String()),
|
|
24
|
+
},
|
|
25
|
+
{ default: {} }
|
|
26
|
+
),
|
|
27
|
+
firecrawl: Type.Object(
|
|
28
|
+
{
|
|
29
|
+
apiKey: Type.Optional(Type.String()),
|
|
30
|
+
},
|
|
31
|
+
{ default: {} }
|
|
32
|
+
),
|
|
33
|
+
read: Type.Object(
|
|
34
|
+
{
|
|
35
|
+
dedupImages: Type.Boolean({ default: true }),
|
|
36
|
+
},
|
|
37
|
+
{ default: { dedupImages: true } }
|
|
38
|
+
),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
type Settings = Static<typeof Schema>;
|
|
42
|
+
|
|
43
|
+
let loaded: Promise<Settings> | undefined;
|
|
44
|
+
let loadedPath: string | undefined;
|
|
45
|
+
const writes = Fs.serialised();
|
|
46
|
+
|
|
47
|
+
function path(): string {
|
|
48
|
+
return join(Paths.pimHomeDir(), "settings.json");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function load(): Promise<Settings> {
|
|
52
|
+
const settingsPath = path();
|
|
53
|
+
if (loadedPath !== settingsPath) {
|
|
54
|
+
loaded = undefined;
|
|
55
|
+
loadedPath = settingsPath;
|
|
56
|
+
}
|
|
57
|
+
loaded ??= (async () => {
|
|
58
|
+
const raw = await Fs.readJsonOr<unknown>(settingsPath, {});
|
|
59
|
+
const filled = Value.Default(Schema, raw);
|
|
60
|
+
return Value.Check(Schema, filled) ? filled : Value.Create(Schema);
|
|
61
|
+
})();
|
|
62
|
+
return loaded;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function getExaApiKey(): Promise<string | undefined> {
|
|
66
|
+
return (
|
|
67
|
+
normalize(process.env["EXA_API_KEY"]) ??
|
|
68
|
+
normalize((await get("exa")).apiKey)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function getJinaApiKey(): Promise<string | undefined> {
|
|
73
|
+
return (
|
|
74
|
+
normalize(process.env["JINA_API_KEY"]) ??
|
|
75
|
+
normalize((await get("jina")).apiKey)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function getFirecrawlApiKey(): Promise<string | undefined> {
|
|
80
|
+
return (
|
|
81
|
+
normalize(process.env["FIRECRAWL_API_KEY"]) ??
|
|
82
|
+
normalize((await get("firecrawl")).apiKey)
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalize(value: string | undefined): string | undefined {
|
|
87
|
+
const trimmed = value?.trim();
|
|
88
|
+
return trimmed === undefined || trimmed.length === 0 ? undefined : trimmed;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function get<K extends keyof Settings>(key: K): Promise<Settings[K]> {
|
|
92
|
+
return (await load())[key];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function set<K extends keyof Settings>(
|
|
96
|
+
key: K,
|
|
97
|
+
value: Settings[K]
|
|
98
|
+
): Promise<void> {
|
|
99
|
+
return update(key, () => value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function update<K extends keyof Settings>(
|
|
103
|
+
key: K,
|
|
104
|
+
mutate: (current: Settings[K]) => Settings[K]
|
|
105
|
+
): Promise<void> {
|
|
106
|
+
return writes.run(async () => {
|
|
107
|
+
const current = await load();
|
|
108
|
+
const next: Settings = { ...current, [key]: mutate(current[key]) };
|
|
109
|
+
if (!Value.Check(Schema, next)) {
|
|
110
|
+
throw new Error(`Invalid value for pim setting "${String(key)}"`);
|
|
111
|
+
}
|
|
112
|
+
const settingsPath = path();
|
|
113
|
+
loaded = Promise.resolve(next);
|
|
114
|
+
loadedPath = settingsPath;
|
|
115
|
+
await Paths.ensurePimHome();
|
|
116
|
+
await Fs.writeJson(settingsPath, next);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const PimSettings = {
|
|
121
|
+
path,
|
|
122
|
+
getExaApiKey,
|
|
123
|
+
getJinaApiKey,
|
|
124
|
+
getFirecrawlApiKey,
|
|
125
|
+
get,
|
|
126
|
+
set,
|
|
127
|
+
update,
|
|
128
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Fs } from "./Fs";
|
|
2
|
+
import { createKy } from "./Http";
|
|
3
|
+
|
|
4
|
+
const REGISTRY = "https://registry.npmjs.org";
|
|
5
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
6
|
+
|
|
7
|
+
type Manifest = {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly version: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type LatestOptions = {
|
|
13
|
+
readonly fetch?: typeof fetch;
|
|
14
|
+
readonly timeoutMs?: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
async function read(url: URL): Promise<Manifest> {
|
|
18
|
+
const pkg = await Fs.readJsonOr<Partial<Manifest> | null>(url, null);
|
|
19
|
+
return {
|
|
20
|
+
name: typeof pkg?.name === "string" ? pkg.name : "?",
|
|
21
|
+
version: typeof pkg?.version === "string" ? pkg.version : "?",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function self(): Promise<Manifest> {
|
|
26
|
+
return read(new URL("../../../../package.json", import.meta.url));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function current(): Promise<string> {
|
|
30
|
+
return (await self()).version;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The npm package this pim was published as. */
|
|
34
|
+
async function name(): Promise<string> {
|
|
35
|
+
return (await self()).name;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The pi that ships inside pim's install tree. */
|
|
39
|
+
async function pi(): Promise<string> {
|
|
40
|
+
return (
|
|
41
|
+
await read(
|
|
42
|
+
new URL(
|
|
43
|
+
import.meta.resolve("@earendil-works/pi-coding-agent/package.json")
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
).version;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function latest(
|
|
50
|
+
options: LatestOptions = {}
|
|
51
|
+
): Promise<string | undefined> {
|
|
52
|
+
const pkg = await self();
|
|
53
|
+
const client = createKy(options.fetch);
|
|
54
|
+
try {
|
|
55
|
+
const release = await client(
|
|
56
|
+
`${REGISTRY}/${pkg.name.replace("/", "%2f")}/latest`,
|
|
57
|
+
{
|
|
58
|
+
timeout: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
59
|
+
headers: { accept: "application/json" },
|
|
60
|
+
}
|
|
61
|
+
).json<{ readonly version?: unknown }>();
|
|
62
|
+
return typeof release.version === "string" && release.version.trim()
|
|
63
|
+
? release.version.trim()
|
|
64
|
+
: undefined;
|
|
65
|
+
} catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function core(version: string): readonly number[] | undefined {
|
|
71
|
+
const parts = version.trim().split(/[-+]/)[0]!.split(".");
|
|
72
|
+
const numbers = parts.map(Number);
|
|
73
|
+
return parts.length === 3 &&
|
|
74
|
+
numbers.every((part) => Number.isInteger(part) && part >= 0)
|
|
75
|
+
? numbers
|
|
76
|
+
: undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isNewer(candidate: string, installed: string): boolean {
|
|
80
|
+
const left = core(candidate);
|
|
81
|
+
const right = core(installed);
|
|
82
|
+
if (!left || !right) {
|
|
83
|
+
return candidate.trim() !== installed.trim();
|
|
84
|
+
}
|
|
85
|
+
for (const [index, part] of left.entries()) {
|
|
86
|
+
if (part !== right[index]) {
|
|
87
|
+
return part > right[index]!;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return installed.includes("-") && !candidate.includes("-");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const PimVersion = { current, name, pi, latest, isNewer };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
async function mapPooled<T, R>(
|
|
2
|
+
items: readonly T[],
|
|
3
|
+
limit: number,
|
|
4
|
+
fn: (item: T, index: number) => Promise<R>
|
|
5
|
+
): Promise<R[]> {
|
|
6
|
+
const results: R[] = Array.from({ length: items.length });
|
|
7
|
+
let next = 0;
|
|
8
|
+
|
|
9
|
+
const worker = async (): Promise<void> => {
|
|
10
|
+
while (next < items.length) {
|
|
11
|
+
const index = next;
|
|
12
|
+
next += 1;
|
|
13
|
+
results[index] = await fn(items[index] as T, index);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
await Promise.all(
|
|
18
|
+
Array.from({ length: Math.min(Math.max(limit, 1), items.length) }, worker)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return results;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const Pool = {
|
|
25
|
+
mapPooled,
|
|
26
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type ProcOptions = {
|
|
2
|
+
readonly cwd?: string | undefined;
|
|
3
|
+
readonly stdout?: "pipe" | "inherit";
|
|
4
|
+
/** Merged over the process environment; a key set to `undefined` is removed from it. */
|
|
5
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
6
|
+
/** Kills the child once it elapses, answering `timedOut`. */
|
|
7
|
+
readonly timeoutMs?: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type ProcResult = {
|
|
11
|
+
readonly code: number;
|
|
12
|
+
readonly stdout: string;
|
|
13
|
+
readonly stderr: string;
|
|
14
|
+
readonly timedOut: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
async function run(
|
|
18
|
+
cmd: ReadonlyArray<string>,
|
|
19
|
+
options: ProcOptions = {}
|
|
20
|
+
): Promise<ProcResult> {
|
|
21
|
+
const proc = Bun.spawn([...cmd], {
|
|
22
|
+
cwd: options.cwd,
|
|
23
|
+
stdout: options.stdout ?? "pipe",
|
|
24
|
+
stderr: "pipe",
|
|
25
|
+
// `undefined` is Bun's own "inherit this process's environment".
|
|
26
|
+
env:
|
|
27
|
+
options.env === undefined
|
|
28
|
+
? undefined
|
|
29
|
+
: { ...process.env, ...options.env },
|
|
30
|
+
});
|
|
31
|
+
let timedOut = false;
|
|
32
|
+
const timer =
|
|
33
|
+
options.timeoutMs === undefined
|
|
34
|
+
? undefined
|
|
35
|
+
: setTimeout(() => {
|
|
36
|
+
timedOut = true;
|
|
37
|
+
proc.kill();
|
|
38
|
+
}, options.timeoutMs);
|
|
39
|
+
const [stdout, stderr] = await Promise.all([
|
|
40
|
+
proc.stdout instanceof ReadableStream
|
|
41
|
+
? new Response(proc.stdout).text()
|
|
42
|
+
: "",
|
|
43
|
+
new Response(proc.stderr).text(),
|
|
44
|
+
]);
|
|
45
|
+
const code = await proc.exited;
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
return { code, stdout, stderr, timedOut };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const Proc = { run };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type RateLimiterOptions = {
|
|
2
|
+
readonly maxRequests: number;
|
|
3
|
+
readonly windowMs: number;
|
|
4
|
+
readonly now?: () => number;
|
|
5
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export class RateLimiter {
|
|
9
|
+
private readonly maxRequests: number;
|
|
10
|
+
private readonly windowMs: number;
|
|
11
|
+
private readonly now: () => number;
|
|
12
|
+
private readonly sleep: (ms: number) => Promise<void>;
|
|
13
|
+
private readonly timestamps: number[] = [];
|
|
14
|
+
private tail: Promise<void> = Promise.resolve();
|
|
15
|
+
|
|
16
|
+
public constructor(options: RateLimiterOptions) {
|
|
17
|
+
this.maxRequests = Math.max(1, options.maxRequests);
|
|
18
|
+
this.windowMs = options.windowMs;
|
|
19
|
+
this.now = options.now ?? Date.now;
|
|
20
|
+
this.sleep = options.sleep ?? ((ms) => Bun.sleep(ms));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public acquire(): Promise<void> {
|
|
24
|
+
const reserved = this.tail.then(() => this.reserve());
|
|
25
|
+
this.tail = reserved.catch(() => {});
|
|
26
|
+
|
|
27
|
+
return reserved;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private async reserve(): Promise<void> {
|
|
31
|
+
while (true) {
|
|
32
|
+
const now = this.now();
|
|
33
|
+
const threshold = now - this.windowMs;
|
|
34
|
+
|
|
35
|
+
while (this.timestamps.length > 0 && this.timestamps[0]! <= threshold) {
|
|
36
|
+
this.timestamps.shift();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (this.timestamps.length < this.maxRequests) {
|
|
40
|
+
this.timestamps.push(now);
|
|
41
|
+
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const waitMs = this.timestamps[0]! + this.windowMs - now;
|
|
46
|
+
|
|
47
|
+
await this.sleep(Math.max(0, waitMs));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|