aicodeman 1.0.0 → 1.1.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.
Files changed (136) hide show
  1. package/dist/document-conversion-limiter.d.ts +27 -0
  2. package/dist/document-conversion-limiter.d.ts.map +1 -0
  3. package/dist/document-conversion-limiter.js +64 -0
  4. package/dist/document-conversion-limiter.js.map +1 -0
  5. package/dist/document-preview-cache.d.ts +15 -0
  6. package/dist/document-preview-cache.d.ts.map +1 -0
  7. package/dist/document-preview-cache.js +261 -0
  8. package/dist/document-preview-cache.js.map +1 -0
  9. package/dist/document-thumbnailer.d.ts +9 -0
  10. package/dist/document-thumbnailer.d.ts.map +1 -0
  11. package/dist/document-thumbnailer.js +70 -0
  12. package/dist/document-thumbnailer.js.map +1 -0
  13. package/dist/hooks-config.d.ts +17 -0
  14. package/dist/hooks-config.d.ts.map +1 -1
  15. package/dist/hooks-config.js +164 -73
  16. package/dist/hooks-config.js.map +1 -1
  17. package/dist/image-watcher.d.ts.map +1 -1
  18. package/dist/image-watcher.js +2 -6
  19. package/dist/image-watcher.js.map +1 -1
  20. package/dist/session-attachment-history.d.ts +19 -0
  21. package/dist/session-attachment-history.d.ts.map +1 -0
  22. package/dist/session-attachment-history.js +63 -0
  23. package/dist/session-attachment-history.js.map +1 -0
  24. package/dist/session.d.ts +10 -0
  25. package/dist/session.d.ts.map +1 -1
  26. package/dist/session.js +27 -0
  27. package/dist/session.js.map +1 -1
  28. package/dist/types/session.d.ts +35 -0
  29. package/dist/types/session.d.ts.map +1 -1
  30. package/dist/types/session.js.map +1 -1
  31. package/dist/usage-telemetry.d.ts +102 -0
  32. package/dist/usage-telemetry.d.ts.map +1 -0
  33. package/dist/usage-telemetry.js +130 -0
  34. package/dist/usage-telemetry.js.map +1 -0
  35. package/dist/web/middleware/auth.d.ts.map +1 -1
  36. package/dist/web/middleware/auth.js +4 -3
  37. package/dist/web/middleware/auth.js.map +1 -1
  38. package/dist/web/plan-usage-latest.d.ts +15 -0
  39. package/dist/web/plan-usage-latest.d.ts.map +1 -0
  40. package/dist/web/plan-usage-latest.js +20 -0
  41. package/dist/web/plan-usage-latest.js.map +1 -0
  42. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  43. package/dist/web/public/app.9daf49ad.js +37 -0
  44. package/dist/web/public/app.9daf49ad.js.br +0 -0
  45. package/dist/web/public/app.9daf49ad.js.gz +0 -0
  46. package/dist/web/public/{constants.aa510a4d.js → constants.00fa5405.js} +1 -0
  47. package/dist/web/public/constants.00fa5405.js.br +0 -0
  48. package/dist/web/public/{constants.aa510a4d.js.gz → constants.00fa5405.js.gz} +0 -0
  49. package/dist/web/public/image-input.0ea86695.js.gz +0 -0
  50. package/dist/web/public/index.html +31 -24
  51. package/dist/web/public/index.html.br +0 -0
  52. package/dist/web/public/index.html.gz +0 -0
  53. package/dist/web/public/input-cjk.b8686b5e.js.gz +0 -0
  54. package/dist/web/public/keyboard-accessory.bc753cc7.js.gz +0 -0
  55. package/dist/web/public/mobile-handlers.763a7439.js.gz +0 -0
  56. package/dist/web/public/mobile.06b38d3a.css +1 -0
  57. package/dist/web/public/mobile.06b38d3a.css.br +0 -0
  58. package/dist/web/public/mobile.06b38d3a.css.gz +0 -0
  59. package/dist/web/public/notification-manager.9c984ac2.js.gz +0 -0
  60. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  61. package/dist/web/public/{panels-ui.ba0b0f1a.js → panels-ui.2f467969.js} +141 -87
  62. package/dist/web/public/panels-ui.2f467969.js.br +0 -0
  63. package/dist/web/public/panels-ui.2f467969.js.gz +0 -0
  64. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  65. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  66. package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
  67. package/dist/web/public/session-ui.1463b824.js +36 -0
  68. package/dist/web/public/session-ui.1463b824.js.br +0 -0
  69. package/dist/web/public/session-ui.1463b824.js.gz +0 -0
  70. package/dist/web/public/settings-ui.44b99ce0.js +55 -0
  71. package/dist/web/public/settings-ui.44b99ce0.js.br +0 -0
  72. package/dist/web/public/settings-ui.44b99ce0.js.gz +0 -0
  73. package/dist/web/public/styles.c13845d5.css +1 -0
  74. package/dist/web/public/styles.c13845d5.css.br +0 -0
  75. package/dist/web/public/styles.c13845d5.css.gz +0 -0
  76. package/dist/web/public/subagent-windows.a366a4ad.js.gz +0 -0
  77. package/dist/web/public/sw.js.gz +0 -0
  78. package/dist/web/public/terminal-ui.5bf97f7e.js.gz +0 -0
  79. package/dist/web/public/upload.html.gz +0 -0
  80. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  81. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  82. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  83. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  84. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  85. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  86. package/dist/web/public/vendor/xterm.css.gz +0 -0
  87. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  88. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  89. package/dist/web/routes/file-routes.d.ts +2 -2
  90. package/dist/web/routes/file-routes.d.ts.map +1 -1
  91. package/dist/web/routes/file-routes.js +298 -1
  92. package/dist/web/routes/file-routes.js.map +1 -1
  93. package/dist/web/routes/index.d.ts +1 -0
  94. package/dist/web/routes/index.d.ts.map +1 -1
  95. package/dist/web/routes/index.js +1 -0
  96. package/dist/web/routes/index.js.map +1 -1
  97. package/dist/web/routes/session-routes.d.ts.map +1 -1
  98. package/dist/web/routes/session-routes.js +15 -1
  99. package/dist/web/routes/session-routes.js.map +1 -1
  100. package/dist/web/routes/status-telemetry-routes.d.ts +17 -0
  101. package/dist/web/routes/status-telemetry-routes.d.ts.map +1 -0
  102. package/dist/web/routes/status-telemetry-routes.js +57 -0
  103. package/dist/web/routes/status-telemetry-routes.js.map +1 -0
  104. package/dist/web/routes/system-routes.d.ts.map +1 -1
  105. package/dist/web/routes/system-routes.js +21 -1
  106. package/dist/web/routes/system-routes.js.map +1 -1
  107. package/dist/web/schemas.d.ts +30 -1
  108. package/dist/web/schemas.d.ts.map +1 -1
  109. package/dist/web/schemas.js +52 -0
  110. package/dist/web/schemas.js.map +1 -1
  111. package/dist/web/server.d.ts.map +1 -1
  112. package/dist/web/server.js +50 -4
  113. package/dist/web/server.js.map +1 -1
  114. package/dist/web/sse-events.d.ts +3 -0
  115. package/dist/web/sse-events.d.ts.map +1 -1
  116. package/dist/web/sse-events.js +3 -0
  117. package/dist/web/sse-events.js.map +1 -1
  118. package/package.json +1 -1
  119. package/dist/web/public/app.95e6e231.js +0 -35
  120. package/dist/web/public/app.95e6e231.js.br +0 -0
  121. package/dist/web/public/app.95e6e231.js.gz +0 -0
  122. package/dist/web/public/constants.aa510a4d.js.br +0 -0
  123. package/dist/web/public/mobile.c7513aed.css +0 -1
  124. package/dist/web/public/mobile.c7513aed.css.br +0 -0
  125. package/dist/web/public/mobile.c7513aed.css.gz +0 -0
  126. package/dist/web/public/panels-ui.ba0b0f1a.js.br +0 -0
  127. package/dist/web/public/panels-ui.ba0b0f1a.js.gz +0 -0
  128. package/dist/web/public/session-ui.34f25fdf.js +0 -36
  129. package/dist/web/public/session-ui.34f25fdf.js.br +0 -0
  130. package/dist/web/public/session-ui.34f25fdf.js.gz +0 -0
  131. package/dist/web/public/settings-ui.bf79c4c0.js +0 -55
  132. package/dist/web/public/settings-ui.bf79c4c0.js.br +0 -0
  133. package/dist/web/public/settings-ui.bf79c4c0.js.gz +0 -0
  134. package/dist/web/public/styles.f3cc9833.css +0 -1
  135. package/dist/web/public/styles.f3cc9833.css.br +0 -0
  136. package/dist/web/public/styles.f3cc9833.css.gz +0 -0
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @fileoverview Global concurrency limiter for spawning external document
3
+ * converters (pdftoppm / LibreOffice `soffice` / Word-COM `powershell.exe`).
4
+ *
5
+ * Without a cap, N simultaneous thumbnail/preview requests for *distinct*
6
+ * documents fork N converter processes at once — each held open for up to the
7
+ * multi-minute conversion timeout. That is a localhost resource-exhaustion
8
+ * (fork-bomb-shaped) vector: a handful of large PDFs detected at once can pin
9
+ * CPU and RAM. This module serializes converter spawns down to a small fixed
10
+ * pool; excess spawns queue (FIFO) until a slot frees. The in-flight cache in
11
+ * `document-preview-cache.ts` already de-dups *identical* inputs; this bounds
12
+ * the *distinct* case the cache can't.
13
+ *
14
+ * Permit accounting transfers the slot directly to the next waiter on release
15
+ * (rather than decrement-then-reacquire) so the active count can never exceed
16
+ * the cap even under interleaved async resumption.
17
+ *
18
+ * NOT re-entrant: never call `runWithConversionLimit` from inside a task that is
19
+ * already holding a slot — a nested acquire under a full pool would deadlock.
20
+ * The converter call sites only ever acquire once per request (the office path
21
+ * acquires for `soffice` and `pdftoppm` sequentially, not nested).
22
+ */
23
+ /** Test/diagnostic hook: converters currently holding a slot. */
24
+ export declare function getActiveConversionCount(): number;
25
+ /** Run `task` once a converter slot is free, releasing the slot afterward. */
26
+ export declare function runWithConversionLimit<T>(task: () => Promise<T>): Promise<T>;
27
+ //# sourceMappingURL=document-conversion-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-conversion-limiter.d.ts","sourceRoot":"","sources":["../src/document-conversion-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAcH,iEAAiE;AACjE,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAoBD,8EAA8E;AAC9E,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOlF"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @fileoverview Global concurrency limiter for spawning external document
3
+ * converters (pdftoppm / LibreOffice `soffice` / Word-COM `powershell.exe`).
4
+ *
5
+ * Without a cap, N simultaneous thumbnail/preview requests for *distinct*
6
+ * documents fork N converter processes at once — each held open for up to the
7
+ * multi-minute conversion timeout. That is a localhost resource-exhaustion
8
+ * (fork-bomb-shaped) vector: a handful of large PDFs detected at once can pin
9
+ * CPU and RAM. This module serializes converter spawns down to a small fixed
10
+ * pool; excess spawns queue (FIFO) until a slot frees. The in-flight cache in
11
+ * `document-preview-cache.ts` already de-dups *identical* inputs; this bounds
12
+ * the *distinct* case the cache can't.
13
+ *
14
+ * Permit accounting transfers the slot directly to the next waiter on release
15
+ * (rather than decrement-then-reacquire) so the active count can never exceed
16
+ * the cap even under interleaved async resumption.
17
+ *
18
+ * NOT re-entrant: never call `runWithConversionLimit` from inside a task that is
19
+ * already holding a slot — a nested acquire under a full pool would deadlock.
20
+ * The converter call sites only ever acquire once per request (the office path
21
+ * acquires for `soffice` and `pdftoppm` sequentially, not nested).
22
+ */
23
+ /**
24
+ * Max converter processes allowed to run concurrently across the whole process.
25
+ * Override with CODEMAN_MAX_DOCUMENT_CONVERSIONS (clamped to >= 1).
26
+ */
27
+ const MAX_CONCURRENT_DOCUMENT_CONVERSIONS = (() => {
28
+ const raw = Number(process.env.CODEMAN_MAX_DOCUMENT_CONVERSIONS);
29
+ return Number.isFinite(raw) && raw >= 1 ? Math.floor(raw) : 3;
30
+ })();
31
+ let active = 0;
32
+ const waiters = [];
33
+ /** Test/diagnostic hook: converters currently holding a slot. */
34
+ export function getActiveConversionCount() {
35
+ return active;
36
+ }
37
+ function acquire() {
38
+ if (active < MAX_CONCURRENT_DOCUMENT_CONVERSIONS) {
39
+ active++;
40
+ return Promise.resolve();
41
+ }
42
+ return new Promise((resolve) => waiters.push(resolve));
43
+ }
44
+ function release() {
45
+ const next = waiters.shift();
46
+ if (next) {
47
+ // Hand the slot straight to the next waiter — `active` stays at the cap.
48
+ next();
49
+ }
50
+ else {
51
+ active--;
52
+ }
53
+ }
54
+ /** Run `task` once a converter slot is free, releasing the slot afterward. */
55
+ export async function runWithConversionLimit(task) {
56
+ await acquire();
57
+ try {
58
+ return await task();
59
+ }
60
+ finally {
61
+ release();
62
+ }
63
+ }
64
+ //# sourceMappingURL=document-conversion-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-conversion-limiter.js","sourceRoot":"","sources":["../src/document-conversion-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH;;;GAGG;AACH,MAAM,mCAAmC,GAAG,CAAC,GAAG,EAAE;IAChD,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;IACjE,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChE,CAAC,CAAC,EAAE,CAAC;AAEL,IAAI,MAAM,GAAG,CAAC,CAAC;AACf,MAAM,OAAO,GAAsB,EAAE,CAAC;AAEtC,iEAAiE;AACjE,MAAM,UAAU,wBAAwB;IACtC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,OAAO;IACd,IAAI,MAAM,GAAG,mCAAmC,EAAE,CAAC;QACjD,MAAM,EAAE,CAAC;QACT,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IACD,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,SAAS,OAAO;IACd,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAC7B,IAAI,IAAI,EAAE,CAAC;QACT,yEAAyE;QACzE,IAAI,EAAE,CAAC;IACT,CAAC;SAAM,CAAC;QACN,MAAM,EAAE,CAAC;IACX,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAI,IAAsB;IACpE,MAAM,OAAO,EAAE,CAAC;IAChB,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,EAAE,CAAC;IACtB,CAAC;YAAS,CAAC;QACT,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @fileoverview Shared disk cache for expensive Office document previews.
3
+ */
4
+ export declare function clearDocumentPreviewCache(): void;
5
+ /**
6
+ * Best-effort LRU-ish eviction for the persistent converted-PDF cache: keeps at
7
+ * most MAX_PREVIEW_CACHE_FILES `*.pdf` files in `cacheDir`, deleting the oldest
8
+ * by mtime once over the cap. Never throws — a pruning failure must not fail the
9
+ * conversion that triggered it. Only `*.pdf` files are considered, so the
10
+ * transient `work-*` mkdtemp dirs are ignored.
11
+ */
12
+ export declare function pruneDocumentPreviewCache(cacheDir: string): Promise<void>;
13
+ export declare function getOfficePreviewPdfPath(filePath: string, extension: string): Promise<string | null>;
14
+ export declare function getPreviewPdfDownloadName(fileName: string, extension: string): string;
15
+ //# sourceMappingURL=document-preview-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-preview-cache.d.ts","sourceRoot":"","sources":["../src/document-preview-cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAyDH,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB/E;AAED,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyCzG;AAmKD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAErF"}
@@ -0,0 +1,261 @@
1
+ /**
2
+ * @fileoverview Shared disk cache for expensive Office document previews.
3
+ */
4
+ import { createHash } from 'node:crypto';
5
+ import { execFile } from 'node:child_process';
6
+ import fs from 'node:fs/promises';
7
+ import { tmpdir } from 'node:os';
8
+ import { basename, dirname, extname, join } from 'node:path';
9
+ import { pathToFileURL } from 'node:url';
10
+ import { promisify } from 'node:util';
11
+ import { runWithConversionLimit } from './document-conversion-limiter.js';
12
+ const execFileAsync = promisify(execFile);
13
+ const OFFICE_CONVERSION_TIMEOUT_MS = 5 * 60_000;
14
+ const DOCUMENT_PREVIEW_CACHE_DIR = join(tmpdir(), 'codeman-document-preview-cache');
15
+ /**
16
+ * Cap on persistent converted-PDF files kept in DOCUMENT_PREVIEW_CACHE_DIR.
17
+ * The cache key embeds the source mtime, so every edit to a doc orphans its
18
+ * prior PDF; without a cap the dir grows unbounded across long-running sessions.
19
+ * Override with CODEMAN_MAX_PREVIEW_CACHE_FILES (clamped to >= 1).
20
+ */
21
+ const MAX_PREVIEW_CACHE_FILES = (() => {
22
+ const raw = Number(process.env.CODEMAN_MAX_PREVIEW_CACHE_FILES);
23
+ return Number.isFinite(raw) && raw >= 1 ? Math.floor(raw) : 100;
24
+ })();
25
+ function buildWordExportPdfScript(sourcePath, outputPath) {
26
+ return `
27
+ $ErrorActionPreference = "Stop"
28
+ $source = ${toPowerShellSingleQuotedString(sourcePath)}
29
+ $output = ${toPowerShellSingleQuotedString(outputPath)}
30
+ $word = $null
31
+ $doc = $null
32
+ try {
33
+ $word = New-Object -ComObject Word.Application
34
+ $word.Visible = $false
35
+ $word.DisplayAlerts = 0
36
+ $doc = $word.Documents.Open($source)
37
+ $doc.ExportAsFixedFormat($output, 17)
38
+ } finally {
39
+ if ($null -ne $doc) {
40
+ $doc.Close($false) | Out-Null
41
+ [System.Runtime.InteropServices.Marshal]::ReleaseComObject($doc) | Out-Null
42
+ }
43
+ if ($null -ne $word) {
44
+ $word.Quit() | Out-Null
45
+ [System.Runtime.InteropServices.Marshal]::ReleaseComObject($word) | Out-Null
46
+ }
47
+ [System.GC]::Collect()
48
+ [System.GC]::WaitForPendingFinalizers()
49
+ }
50
+ `.trim();
51
+ }
52
+ const inFlightOfficeConversions = new Map();
53
+ export function clearDocumentPreviewCache() {
54
+ inFlightOfficeConversions.clear();
55
+ }
56
+ /**
57
+ * Best-effort LRU-ish eviction for the persistent converted-PDF cache: keeps at
58
+ * most MAX_PREVIEW_CACHE_FILES `*.pdf` files in `cacheDir`, deleting the oldest
59
+ * by mtime once over the cap. Never throws — a pruning failure must not fail the
60
+ * conversion that triggered it. Only `*.pdf` files are considered, so the
61
+ * transient `work-*` mkdtemp dirs are ignored.
62
+ */
63
+ export async function pruneDocumentPreviewCache(cacheDir) {
64
+ try {
65
+ const entries = await fs.readdir(cacheDir);
66
+ const pdfs = entries.filter((name) => name.toLowerCase().endsWith('.pdf'));
67
+ if (pdfs.length <= MAX_PREVIEW_CACHE_FILES)
68
+ return;
69
+ const stats = await Promise.all(pdfs.map(async (name) => {
70
+ const fullPath = join(cacheDir, name);
71
+ try {
72
+ const stat = await fs.stat(fullPath);
73
+ return { fullPath, mtimeMs: stat.mtimeMs ?? 0 };
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ }));
79
+ const sorted = stats.filter((s) => s !== null);
80
+ sorted.sort((a, b) => a.mtimeMs - b.mtimeMs); // oldest first
81
+ const toRemove = sorted.slice(0, Math.max(0, sorted.length - MAX_PREVIEW_CACHE_FILES));
82
+ await Promise.all(toRemove.map((entry) => fs.rm(entry.fullPath, { force: true }).catch(() => { })));
83
+ }
84
+ catch {
85
+ // Best-effort: pruning must never break a conversion.
86
+ }
87
+ }
88
+ export async function getOfficePreviewPdfPath(filePath, extension) {
89
+ const ext = extension.toLowerCase().replace(/^\./, '');
90
+ if (ext !== 'docx' && ext !== 'pptx')
91
+ return null;
92
+ let sourceStat;
93
+ try {
94
+ sourceStat = await fs.stat(filePath);
95
+ }
96
+ catch {
97
+ return null;
98
+ }
99
+ for (const converter of getOfficePreviewConverters(filePath, ext)) {
100
+ const cacheKey = createDocumentPreviewCacheKey(filePath, ext, sourceStat.size, sourceStat.mtimeMs ?? 0, converter);
101
+ const cachePath = getOfficePreviewCachePath(filePath, cacheKey, converter);
102
+ if (await fileExists(cachePath)) {
103
+ return cachePath;
104
+ }
105
+ const inFlightKey = `${converter}:${cacheKey}`;
106
+ const inFlight = inFlightOfficeConversions.get(inFlightKey);
107
+ if (inFlight) {
108
+ const converted = await inFlight;
109
+ if (converted)
110
+ return converted;
111
+ continue;
112
+ }
113
+ const conversion = converter === 'msword'
114
+ ? convertWordDocumentToCachedPdf(filePath, cachePath)
115
+ : convertLibreOfficeDocumentToCachedPdf(filePath, cachePath);
116
+ inFlightOfficeConversions.set(inFlightKey, conversion);
117
+ try {
118
+ const converted = await conversion;
119
+ if (converted)
120
+ return converted;
121
+ }
122
+ finally {
123
+ inFlightOfficeConversions.delete(inFlightKey);
124
+ }
125
+ }
126
+ return null;
127
+ }
128
+ function getOfficePreviewConverters(filePath, extension) {
129
+ if (extension === 'docx' && wslMountPathToWindowsPath(filePath)) {
130
+ return ['msword', 'libreoffice'];
131
+ }
132
+ return ['libreoffice'];
133
+ }
134
+ function createDocumentPreviewCacheKey(filePath, extension, size, mtimeMs, converter) {
135
+ return createHash('sha256')
136
+ .update(JSON.stringify({ cacheVersion: 2, converter, filePath, extension, size, mtimeMs }))
137
+ .digest('hex')
138
+ .slice(0, 32);
139
+ }
140
+ function getOfficePreviewCachePath(filePath, cacheKey, converter) {
141
+ if (converter === 'msword') {
142
+ const windowsCacheDir = getWindowsUserTempCacheDir(filePath);
143
+ if (windowsCacheDir) {
144
+ return join(windowsCacheDir, `${cacheKey}.pdf`);
145
+ }
146
+ }
147
+ return join(DOCUMENT_PREVIEW_CACHE_DIR, `${cacheKey}.pdf`);
148
+ }
149
+ async function fileExists(filePath) {
150
+ try {
151
+ const stat = await fs.stat(filePath);
152
+ return typeof stat.isFile !== 'function' || stat.isFile();
153
+ }
154
+ catch {
155
+ return false;
156
+ }
157
+ }
158
+ async function convertWordDocumentToCachedPdf(filePath, cachePath) {
159
+ const outputPath = wslMountPathToWindowsPath(cachePath);
160
+ if (!outputPath)
161
+ return null;
162
+ let sourceCopyPath;
163
+ try {
164
+ await fs.mkdir(dirname(cachePath), { recursive: true });
165
+ sourceCopyPath = join(dirname(cachePath), `${basename(cachePath, '.pdf')}.docx`);
166
+ await fs.copyFile(filePath, sourceCopyPath);
167
+ const sourcePath = wslMountPathToWindowsPath(sourceCopyPath);
168
+ if (!sourcePath)
169
+ return null;
170
+ await runWithConversionLimit(() => execFileAsync('powershell.exe', [
171
+ '-NoProfile',
172
+ '-NonInteractive',
173
+ '-ExecutionPolicy',
174
+ 'Bypass',
175
+ '-EncodedCommand',
176
+ encodePowerShellCommand(buildWordExportPdfScript(sourcePath, outputPath)),
177
+ ], {
178
+ timeout: OFFICE_CONVERSION_TIMEOUT_MS,
179
+ maxBuffer: 1024 * 1024,
180
+ }));
181
+ if (await fileExists(cachePath)) {
182
+ await pruneDocumentPreviewCache(dirname(cachePath));
183
+ return cachePath;
184
+ }
185
+ console.warn(`[DocumentPreviewCache] Microsoft Word did not produce PDF output for ${filePath}`);
186
+ return null;
187
+ }
188
+ catch (err) {
189
+ console.warn(`[DocumentPreviewCache] Failed to convert DOCX with Microsoft Word (${filePath}):`, getCacheErrorMessage(err));
190
+ return null;
191
+ }
192
+ finally {
193
+ if (sourceCopyPath) {
194
+ await fs.rm(sourceCopyPath, { force: true }).catch(() => { });
195
+ }
196
+ }
197
+ }
198
+ async function convertLibreOfficeDocumentToCachedPdf(filePath, cachePath) {
199
+ let workDir;
200
+ try {
201
+ await fs.mkdir(DOCUMENT_PREVIEW_CACHE_DIR, { recursive: true });
202
+ const outDir = await fs.mkdtemp(join(DOCUMENT_PREVIEW_CACHE_DIR, 'work-'));
203
+ workDir = outDir;
204
+ const profileDir = join(outDir, 'profile');
205
+ await fs.mkdir(profileDir, { recursive: true });
206
+ await runWithConversionLimit(() => execFileAsync('soffice', [
207
+ '--headless',
208
+ '--nologo',
209
+ '--nofirststartwizard',
210
+ `-env:UserInstallation=${pathToFileURL(profileDir).href}`,
211
+ '--convert-to',
212
+ 'pdf',
213
+ '--outdir',
214
+ outDir,
215
+ filePath,
216
+ ], {
217
+ timeout: OFFICE_CONVERSION_TIMEOUT_MS,
218
+ maxBuffer: 1024 * 1024,
219
+ }));
220
+ const converted = (await fs.readdir(outDir)).find((name) => name.toLowerCase().endsWith('.pdf'));
221
+ if (!converted)
222
+ return null;
223
+ await fs.rename(join(workDir, converted), cachePath);
224
+ await pruneDocumentPreviewCache(DOCUMENT_PREVIEW_CACHE_DIR);
225
+ return cachePath;
226
+ }
227
+ catch (err) {
228
+ console.warn(`[DocumentPreviewCache] Failed to convert Office file to PDF (${filePath}):`, getCacheErrorMessage(err));
229
+ return null;
230
+ }
231
+ finally {
232
+ if (workDir) {
233
+ await fs.rm(workDir, { recursive: true, force: true }).catch(() => { });
234
+ }
235
+ }
236
+ }
237
+ function wslMountPathToWindowsPath(filePath) {
238
+ const match = filePath.match(/^\/mnt\/([a-zA-Z])\/(.+)$/);
239
+ if (!match)
240
+ return null;
241
+ return `${match[1].toUpperCase()}:\\${match[2].replace(/\//g, '\\')}`;
242
+ }
243
+ function getWindowsUserTempCacheDir(filePath) {
244
+ const match = filePath.match(/^\/mnt\/([a-zA-Z])\/Users\/([^/]+)\//);
245
+ if (!match)
246
+ return null;
247
+ return `/mnt/${match[1].toLowerCase()}/Users/${match[2]}/AppData/Local/Temp/codeman-document-preview-cache`;
248
+ }
249
+ function toPowerShellSingleQuotedString(value) {
250
+ return `'${value.replace(/'/g, "''")}'`;
251
+ }
252
+ function encodePowerShellCommand(script) {
253
+ return Buffer.from(script, 'utf16le').toString('base64');
254
+ }
255
+ export function getPreviewPdfDownloadName(fileName, extension) {
256
+ return `${basename(fileName, extname(fileName) || `.${extension}`)}.pdf`;
257
+ }
258
+ function getCacheErrorMessage(err) {
259
+ return err instanceof Error ? err.message : String(err);
260
+ }
261
+ //# sourceMappingURL=document-preview-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-preview-cache.js","sourceRoot":"","sources":["../src/document-preview-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @fileoverview Best-effort first-page thumbnails for attachment cards.
3
+ */
4
+ export interface ThumbnailResult {
5
+ content: Buffer;
6
+ contentType: 'image/png';
7
+ }
8
+ export declare function generateFirstPageThumbnail(filePath: string, extension: string): Promise<ThumbnailResult | null>;
9
+ //# sourceMappingURL=document-thumbnailer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-thumbnailer.d.ts","sourceRoot":"","sources":["../src/document-thumbnailer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAaH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,wBAAsB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAuBrH"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @fileoverview Best-effort first-page thumbnails for attachment cards.
3
+ */
4
+ import { execFile } from 'node:child_process';
5
+ import fs from 'node:fs/promises';
6
+ import { tmpdir } from 'node:os';
7
+ import { basename, extname, join } from 'node:path';
8
+ import { promisify } from 'node:util';
9
+ import { getOfficePreviewPdfPath } from './document-preview-cache.js';
10
+ import { runWithConversionLimit } from './document-conversion-limiter.js';
11
+ const execFileAsync = promisify(execFile);
12
+ const THUMBNAIL_CONVERSION_TIMEOUT_MS = 5 * 60_000;
13
+ export async function generateFirstPageThumbnail(filePath, extension) {
14
+ const ext = extension.toLowerCase().replace(/^\./, '');
15
+ try {
16
+ await fs.stat(filePath);
17
+ if (ext === 'png') {
18
+ return { content: await fs.readFile(filePath), contentType: 'image/png' };
19
+ }
20
+ if (ext === 'pdf') {
21
+ return renderPdfFirstPage(filePath);
22
+ }
23
+ if (ext === 'docx' || ext === 'pptx') {
24
+ return renderOfficeFirstPage(filePath);
25
+ }
26
+ }
27
+ catch (err) {
28
+ console.warn(`[Thumbnailer] Failed to generate ${ext} thumbnail for ${filePath}:`, getThumbnailErrorMessage(err));
29
+ return null;
30
+ }
31
+ return null;
32
+ }
33
+ async function renderOfficeFirstPage(filePath) {
34
+ try {
35
+ const previewPdfPath = await getOfficePreviewPdfPath(filePath, extname(filePath).toLowerCase().replace(/^\./, ''));
36
+ if (!previewPdfPath)
37
+ return null;
38
+ return await renderPdfFirstPage(previewPdfPath);
39
+ }
40
+ catch (err) {
41
+ console.warn(`[Thumbnailer] Failed to convert Office file to PDF for thumbnail (${filePath}):`, getThumbnailErrorMessage(err));
42
+ return null;
43
+ }
44
+ }
45
+ async function renderPdfFirstPage(filePath) {
46
+ let previewDir;
47
+ try {
48
+ previewDir = await fs.mkdtemp(join(tmpdir(), 'codeman-thumb-pdf-'));
49
+ const prefix = join(previewDir, basename(filePath, extname(filePath)));
50
+ await runWithConversionLimit(() => execFileAsync('pdftoppm', ['-png', '-singlefile', '-f', '1', '-l', '1', '-scale-to', '520', filePath, prefix], {
51
+ timeout: THUMBNAIL_CONVERSION_TIMEOUT_MS,
52
+ maxBuffer: 1024 * 1024,
53
+ }));
54
+ const content = await fs.readFile(`${prefix}.png`);
55
+ return { content, contentType: 'image/png' };
56
+ }
57
+ catch (err) {
58
+ console.warn(`[Thumbnailer] Failed to render PDF first page for thumbnail (${filePath}):`, getThumbnailErrorMessage(err));
59
+ return null;
60
+ }
61
+ finally {
62
+ if (previewDir) {
63
+ await fs.rm(previewDir, { recursive: true, force: true }).catch(() => { });
64
+ }
65
+ }
66
+ }
67
+ function getThumbnailErrorMessage(err) {
68
+ return err instanceof Error ? err.message : String(err);
69
+ }
70
+ //# sourceMappingURL=document-thumbnailer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-thumbnailer.js","sourceRoot":"","sources":["../src/document-thumbnailer.ts"],"names":[],"mappings":"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"}
@@ -57,4 +57,21 @@ export declare function updateCaseModel(casePath: string, model: string | null):
57
57
  * Merges with existing file content, only touching the `hooks` key.
58
58
  */
59
59
  export declare function writeHooksConfig(casePath: string): Promise<void>;
60
+ /**
61
+ * The plan-usage statusLine exporter command. Mirrors the hook `curlCmd` pattern:
62
+ * reads Claude Code's statusline stdin JSON, POSTs `{sessionId,data}` to Codeman,
63
+ * and prints the response body (a compact "⟳ 5h 15% · 7d 34%" footer) back to
64
+ * stdout so the in-terminal statusline stays useful. Env vars resolve at runtime
65
+ * (present in every managed session via tmux setenv), so the config is static.
66
+ */
67
+ export declare function generateStatusLineCommand(): string;
68
+ /**
69
+ * Add or remove Codeman's plan-usage statusLine exporter in
70
+ * `.claude/settings.local.json`. Only ever touches a statusLine that is OURS
71
+ * (command targets `/api/status-telemetry`), so a user's hand-authored
72
+ * statusLine is never removed OR overwritten — on both the enable and disable
73
+ * paths we bail out when an existing statusLine isn't ours. Callers gate on
74
+ * Claude mode. Merges, preserving all other keys (hooks, env, model).
75
+ */
76
+ export declare function applyStatusLineConfig(casePath: string, enabled: boolean): Promise<void>;
60
77
  //# sourceMappingURL=hooks-config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hooks-config.d.ts","sourceRoot":"","sources":["../src/hooks-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AASH;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,IAAI;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;CAAE,CAmD1E;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BvG;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BxG;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAsB3F;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBtE"}
1
+ {"version":3,"file":"hooks-config.d.ts","sourceRoot":"","sources":["../src/hooks-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAiCH;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,IAAI;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;CAAE,CAmD1E;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BvG;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BxG;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB3F;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBtE;AAKD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAalD;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA8B7F"}