jishushell 0.4.30 → 0.5.15

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 (182) hide show
  1. package/apps/anythingllm-container.yaml +287 -0
  2. package/apps/browserless-chromium-container.yaml +18 -6
  3. package/apps/filebrowser-container.yaml +163 -0
  4. package/apps/openclaw-binary.yaml +8 -0
  5. package/apps/openclaw-container.yaml +9 -1
  6. package/apps/openclaw-with-searxng-container.yaml +4 -0
  7. package/apps/searxng-container.yaml +5 -4
  8. package/apps/weknora-container.yaml +471 -0
  9. package/dist/cli/panel.js.map +1 -1
  10. package/dist/config.d.ts +19 -0
  11. package/dist/config.js +99 -1
  12. package/dist/config.js.map +1 -1
  13. package/dist/install.js +3 -3
  14. package/dist/install.js.map +1 -1
  15. package/dist/routes/auth.js +2 -2
  16. package/dist/routes/auth.js.map +1 -1
  17. package/dist/routes/backup.js +64 -11
  18. package/dist/routes/backup.js.map +1 -1
  19. package/dist/routes/external-mounts.d.ts +17 -0
  20. package/dist/routes/external-mounts.js +73 -0
  21. package/dist/routes/external-mounts.js.map +1 -0
  22. package/dist/routes/file-mounts.d.ts +13 -0
  23. package/dist/routes/file-mounts.js +90 -0
  24. package/dist/routes/file-mounts.js.map +1 -0
  25. package/dist/routes/files-organize.d.ts +28 -0
  26. package/dist/routes/files-organize.js +167 -0
  27. package/dist/routes/files-organize.js.map +1 -0
  28. package/dist/routes/files.d.ts +31 -0
  29. package/dist/routes/files.js +321 -0
  30. package/dist/routes/files.js.map +1 -0
  31. package/dist/routes/instances.js +45 -7
  32. package/dist/routes/instances.js.map +1 -1
  33. package/dist/routes/internal.d.ts +2 -0
  34. package/dist/routes/internal.js +59 -0
  35. package/dist/routes/internal.js.map +1 -0
  36. package/dist/routes/setup.js +9 -9
  37. package/dist/routes/setup.js.map +1 -1
  38. package/dist/routes/system.js +1 -1
  39. package/dist/routes/system.js.map +1 -1
  40. package/dist/routes/webdav.d.ts +17 -0
  41. package/dist/routes/webdav.js +114 -0
  42. package/dist/routes/webdav.js.map +1 -0
  43. package/dist/server.js +341 -3
  44. package/dist/server.js.map +1 -1
  45. package/dist/services/app/app-compiler.d.ts +1 -1
  46. package/dist/services/app/app-compiler.js +5 -5
  47. package/dist/services/app/app-compiler.js.map +1 -1
  48. package/dist/services/app/app-manager.d.ts +1 -0
  49. package/dist/services/app/app-manager.js +172 -41
  50. package/dist/services/app/app-manager.js.map +1 -1
  51. package/dist/services/app/custom-manager.js.map +1 -1
  52. package/dist/services/app/hermes-agent-manager.js +1 -0
  53. package/dist/services/app/hermes-agent-manager.js.map +1 -1
  54. package/dist/services/app/ollama-manager.js +1 -1
  55. package/dist/services/app/ollama-manager.js.map +1 -1
  56. package/dist/services/app/openclaw-manager.js +20 -3
  57. package/dist/services/app/openclaw-manager.js.map +1 -1
  58. package/dist/services/app/platform-transform.d.ts +32 -0
  59. package/dist/services/app/platform-transform.js +65 -0
  60. package/dist/services/app/platform-transform.js.map +1 -0
  61. package/dist/services/app-passwords.d.ts +61 -0
  62. package/dist/services/app-passwords.js +173 -0
  63. package/dist/services/app-passwords.js.map +1 -0
  64. package/dist/services/backup-manager.d.ts +11 -0
  65. package/dist/services/backup-manager.js +177 -4
  66. package/dist/services/backup-manager.js.map +1 -1
  67. package/dist/services/connection-apply.d.ts +2 -0
  68. package/dist/services/connection-apply.js +55 -1
  69. package/dist/services/connection-apply.js.map +1 -1
  70. package/dist/services/connection-resolver.js +1 -1
  71. package/dist/services/connection-resolver.js.map +1 -1
  72. package/dist/services/connection-transactor.d.ts +2 -0
  73. package/dist/services/connection-transactor.js +12 -2
  74. package/dist/services/connection-transactor.js.map +1 -1
  75. package/dist/services/external-mounts.d.ts +40 -0
  76. package/dist/services/external-mounts.js +187 -0
  77. package/dist/services/external-mounts.js.map +1 -0
  78. package/dist/services/files-manager.d.ts +252 -0
  79. package/dist/services/files-manager.js +1075 -0
  80. package/dist/services/files-manager.js.map +1 -0
  81. package/dist/services/files-mounts.d.ts +42 -0
  82. package/dist/services/files-mounts.js +207 -0
  83. package/dist/services/files-mounts.js.map +1 -0
  84. package/dist/services/instance-manager.js +1 -23
  85. package/dist/services/instance-manager.js.map +1 -1
  86. package/dist/services/llm-proxy/index.js.map +1 -1
  87. package/dist/services/llm-proxy/ssrf.js +6 -2
  88. package/dist/services/llm-proxy/ssrf.js.map +1 -1
  89. package/dist/services/nomad-manager.d.ts +4 -0
  90. package/dist/services/nomad-manager.js +53 -19
  91. package/dist/services/nomad-manager.js.map +1 -1
  92. package/dist/services/organize/applier.d.ts +46 -0
  93. package/dist/services/organize/applier.js +218 -0
  94. package/dist/services/organize/applier.js.map +1 -0
  95. package/dist/services/organize/rules.d.ts +57 -0
  96. package/dist/services/organize/rules.js +286 -0
  97. package/dist/services/organize/rules.js.map +1 -0
  98. package/dist/services/organize/scanner.d.ts +50 -0
  99. package/dist/services/organize/scanner.js +366 -0
  100. package/dist/services/organize/scanner.js.map +1 -0
  101. package/dist/services/organize/store.d.ts +14 -0
  102. package/dist/services/organize/store.js +82 -0
  103. package/dist/services/organize/store.js.map +1 -0
  104. package/dist/services/panel-manager.js +20 -1
  105. package/dist/services/panel-manager.js.map +1 -1
  106. package/dist/services/process-manager.js +3 -2
  107. package/dist/services/process-manager.js.map +1 -1
  108. package/dist/services/runtime/adapters/hermes.js +1 -1
  109. package/dist/services/runtime/adapters/hermes.js.map +1 -1
  110. package/dist/services/runtime/adapters/openclaw-routes.d.ts +8 -2
  111. package/dist/services/runtime/adapters/openclaw-routes.js +68 -0
  112. package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -1
  113. package/dist/services/runtime/adapters/openclaw.d.ts +90 -0
  114. package/dist/services/runtime/adapters/openclaw.js +957 -45
  115. package/dist/services/runtime/adapters/openclaw.js.map +1 -1
  116. package/dist/services/runtime/instance.d.ts +1 -1
  117. package/dist/services/runtime/instance.js +1 -1
  118. package/dist/services/runtime/instance.js.map +1 -1
  119. package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +46 -0
  120. package/dist/services/runtime/mcp-shims/anythingllm-shim.js +281 -0
  121. package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +1 -0
  122. package/dist/services/runtime/mcp-shims/drive-shim.d.ts +54 -0
  123. package/dist/services/runtime/mcp-shims/drive-shim.js +489 -0
  124. package/dist/services/runtime/mcp-shims/drive-shim.js.map +1 -0
  125. package/dist/services/runtime/types.d.ts +31 -0
  126. package/dist/services/setup-manager.js +93 -18
  127. package/dist/services/setup-manager.js.map +1 -1
  128. package/dist/services/suggestions.js.map +1 -1
  129. package/dist/services/webdav/server.d.ts +24 -0
  130. package/dist/services/webdav/server.js +420 -0
  131. package/dist/services/webdav/server.js.map +1 -0
  132. package/dist/services/webdav/xml-builder.d.ts +73 -0
  133. package/dist/services/webdav/xml-builder.js +156 -0
  134. package/dist/services/webdav/xml-builder.js.map +1 -0
  135. package/dist/services/workspace-builder.d.ts +29 -0
  136. package/dist/services/workspace-builder.js +188 -0
  137. package/dist/services/workspace-builder.js.map +1 -0
  138. package/dist/types.d.ts +60 -0
  139. package/dist/utils/path-locks.d.ts +30 -0
  140. package/dist/utils/path-locks.js +63 -0
  141. package/dist/utils/path-locks.js.map +1 -0
  142. package/dist/utils/path-safety.d.ts +41 -0
  143. package/dist/utils/path-safety.js +119 -0
  144. package/dist/utils/path-safety.js.map +1 -0
  145. package/dist/utils/safe-write.d.ts +24 -0
  146. package/dist/utils/safe-write.js +82 -0
  147. package/dist/utils/safe-write.js.map +1 -0
  148. package/package.json +16 -1
  149. package/public/assets/Dashboard-BdWPtroF.js +1 -0
  150. package/public/assets/{HermesChatPanel-_GHoklgo.js → HermesChatPanel-B_2HlVBQ.js} +1 -1
  151. package/public/assets/{HermesConfigForm-anDnwUp_.js → HermesConfigForm-DVlhg3WV.js} +2 -2
  152. package/public/assets/{InitPassword-ZU9_-hDr.js → InitPassword-D7glTExX.js} +1 -1
  153. package/public/assets/InstanceDetail-CxSy2cpe.js +92 -0
  154. package/public/assets/{Login-BItXqYAJ.js → Login-Cfr5c2sv.js} +1 -1
  155. package/public/assets/NewInstance-BIYDmJis.js +1 -0
  156. package/public/assets/{ProviderRecommendations-DFYj7Fb6.js → ProviderRecommendations-BuRnvRcI.js} +1 -1
  157. package/public/assets/{Settings-Bttc6QmM.js → Settings-Cc-tYBil.js} +1 -1
  158. package/public/assets/{Setup-Bsxx1zgj.js → Setup-lGZEk5jq.js} +1 -1
  159. package/public/assets/{WeixinLoginPanel-DPZpAKgO.js → WeixinLoginPanel-CoGqzxeV.js} +2 -2
  160. package/public/assets/index-87IJXG-w.css +1 -0
  161. package/public/assets/index-BZc5zH7u.js +19 -0
  162. package/public/assets/{registry-5s2UB6is.js → registry-BWnkJgZ1.js} +2 -2
  163. package/public/assets/{usePolling-Do5Erqm_.js → usePolling-CwwT9KrC.js} +1 -1
  164. package/public/assets/{vendor-i18n-ucpM0OR0.js → vendor-i18n-y9V7Sfuu.js} +1 -1
  165. package/public/assets/{vendor-react-Bk1hRGiY.js → vendor-react-BWrEVJVb.js} +6 -6
  166. package/public/index.html +4 -4
  167. package/scripts/check-app-spec.mjs +18 -4
  168. package/scripts/check-new-file-tests.mjs +230 -0
  169. package/scripts/check-quarantine-expiry.mjs +105 -0
  170. package/scripts/perf/README.md +49 -0
  171. package/scripts/perf/auth.js +99 -0
  172. package/scripts/perf/config.js +63 -0
  173. package/scripts/perf/instances.js +143 -0
  174. package/scripts/perf/proxy.js +96 -0
  175. package/scripts/smoke/files-w1.sh +142 -0
  176. package/scripts/smoke-backend.mjs +122 -0
  177. package/scripts/smoke-post-publish.mjs +346 -0
  178. package/public/assets/Dashboard-rkWp-CXd.js +0 -1
  179. package/public/assets/InstanceDetail-CN0FH1aw.js +0 -92
  180. package/public/assets/NewInstance-BousE6kY.js +0 -1
  181. package/public/assets/index-8xZy1z5k.css +0 -1
  182. package/public/assets/index-Dw3HhUYE.js +0 -19
@@ -0,0 +1,46 @@
1
+ import type { OrganizeBatch } from "./scanner.js";
2
+ import type { OrganizeRule } from "./rules.js";
3
+ export declare const UNDO_WINDOW_SEC = 30;
4
+ export interface ApplyReport {
5
+ applied: number;
6
+ skipped: number;
7
+ failed: number;
8
+ details: Array<{
9
+ id: string;
10
+ status: "applied" | "skipped" | "failed";
11
+ reason?: string;
12
+ final_to?: string;
13
+ }>;
14
+ }
15
+ export interface RevertReport {
16
+ reverted: number;
17
+ skipped: number;
18
+ failed: number;
19
+ details: Array<{
20
+ id: string;
21
+ status: "reverted" | "skipped" | "failed";
22
+ reason?: string;
23
+ }>;
24
+ }
25
+ export interface ApplyOptions {
26
+ filesRoot: string;
27
+ /** Map rule_id → on_collision; missing falls back to "keep-both". */
28
+ rulesById: Map<string, OrganizeRule>;
29
+ selectedIds: Set<string>;
30
+ /** Audit log writer; gets one line per mv. */
31
+ audit: (op: "organize.apply" | "organize.revert", relPath: string, meta?: Record<string, unknown>) => void;
32
+ }
33
+ export declare function applyBatch(batch: OrganizeBatch, opts: ApplyOptions): Promise<{
34
+ batch: OrganizeBatch;
35
+ report: ApplyReport;
36
+ }>;
37
+ export interface RevertOptions {
38
+ filesRoot: string;
39
+ audit: (op: "organize.revert", relPath: string, meta?: Record<string, unknown>) => void;
40
+ /** Override `now` for tests. */
41
+ now?: () => number;
42
+ }
43
+ export declare function revertBatch(batch: OrganizeBatch, opts: RevertOptions): Promise<{
44
+ batch: OrganizeBatch;
45
+ report: RevertReport;
46
+ }>;
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Apply / revert organize batches.
3
+ *
4
+ * Apply:
5
+ * - User submits the suggestion ids they accepted (subset of the batch)
6
+ * - For each, lstat-validate source still exists and is the same file
7
+ * we scanned (size + mtime match the original suggestion's snapshot
8
+ * of the file — recorded inline so we don't rescan)
9
+ * - Resolve collision per rule on_collision (keep-both / overwrite /
10
+ * skip)
11
+ * - Hold a per-path lock while moving (matches W1 FilesManager.move)
12
+ * - Audit each mv to files-audit.jsonl
13
+ * - Set batch.applied_at + expires_at = applied_at + UNDO_WINDOW_SEC
14
+ *
15
+ * Revert:
16
+ * - Only valid while expires_at > now
17
+ * - Walk applied suggestions in reverse, mv `to` back to `from`
18
+ * - If `to` no longer exists or was edited (size/mtime changed),
19
+ * skip + audit but do not abort
20
+ * - On completion, mark batch.reverted_at
21
+ *
22
+ * Both operations are best-effort per item: a failure on item N does
23
+ * not abort items N+1..M. The returned report tells the UI which items
24
+ * succeeded/failed/skipped.
25
+ */
26
+ import * as fs from "node:fs";
27
+ import * as path from "node:path";
28
+ import { withPathLock } from "../../utils/path-locks.js";
29
+ import { resolveSafe } from "../../utils/path-safety.js";
30
+ export const UNDO_WINDOW_SEC = 30;
31
+ export async function applyBatch(batch, opts) {
32
+ if (batch.status !== "pending") {
33
+ throw new Error(`batch ${batch.id} is ${batch.status}, not pending`);
34
+ }
35
+ const report = { applied: 0, skipped: 0, failed: 0, details: [] };
36
+ const now = Math.floor(Date.now() / 1000);
37
+ for (const sug of batch.suggestions) {
38
+ if (!opts.selectedIds.has(sug.id))
39
+ continue;
40
+ const rule = opts.rulesById.get(sug.rule_id);
41
+ const collision = rule?.action.on_collision ?? "keep-both";
42
+ try {
43
+ const finalTo = await applyOne(opts.filesRoot, sug, collision);
44
+ if (finalTo === null) {
45
+ sug.applied = false;
46
+ report.skipped++;
47
+ report.details.push({ id: sug.id, status: "skipped", reason: "collision" });
48
+ opts.audit("organize.apply", sug.from, {
49
+ batch: batch.id,
50
+ status: "skipped",
51
+ reason: "collision",
52
+ });
53
+ }
54
+ else {
55
+ sug.applied = true;
56
+ sug.to = finalTo; // may differ from original on keep-both
57
+ report.applied++;
58
+ report.details.push({ id: sug.id, status: "applied", final_to: finalTo });
59
+ opts.audit("organize.apply", sug.from, {
60
+ batch: batch.id,
61
+ to: finalTo,
62
+ });
63
+ }
64
+ }
65
+ catch (e) {
66
+ sug.applied = false;
67
+ report.failed++;
68
+ report.details.push({
69
+ id: sug.id,
70
+ status: "failed",
71
+ reason: e.message ?? String(e),
72
+ });
73
+ opts.audit("organize.apply", sug.from, {
74
+ batch: batch.id,
75
+ status: "failed",
76
+ error: e.message ?? String(e),
77
+ });
78
+ }
79
+ }
80
+ batch.status = "applied";
81
+ batch.applied_at = now;
82
+ batch.expires_at = now + UNDO_WINDOW_SEC;
83
+ return { batch, report };
84
+ }
85
+ async function applyOne(filesRoot, sug, collision) {
86
+ const fromAbs = resolveSafe(filesRoot, sug.from);
87
+ // Source must exist and not be a symlink
88
+ let fromStat;
89
+ try {
90
+ fromStat = fs.lstatSync(fromAbs);
91
+ }
92
+ catch {
93
+ throw new Error("source no longer exists");
94
+ }
95
+ if (fromStat.isSymbolicLink())
96
+ throw new Error("source is a symlink");
97
+ let to = sug.to;
98
+ let toAbs = resolveSafe(filesRoot, to);
99
+ // Collision handling
100
+ if (fs.existsSync(toAbs)) {
101
+ if (collision === "skip")
102
+ return null;
103
+ if (collision === "keep-both") {
104
+ to = pickFreeName(filesRoot, to);
105
+ toAbs = resolveSafe(filesRoot, to);
106
+ }
107
+ // overwrite: leave toAbs as-is, fs.renameSync will replace
108
+ }
109
+ // Lock both paths in deterministic order
110
+ const sortedPair = [fromAbs, toAbs].sort();
111
+ const first = sortedPair[0];
112
+ const second = sortedPair[1];
113
+ await withPathLock(first, () => withPathLock(second, async () => {
114
+ fs.mkdirSync(path.dirname(toAbs), { recursive: true });
115
+ fs.renameSync(fromAbs, toAbs);
116
+ }));
117
+ return to;
118
+ }
119
+ /** "photos/2026-05/IMG.jpg" → "photos/2026-05/IMG (1).jpg" */
120
+ function pickFreeName(filesRoot, target) {
121
+ const ext = path.extname(target);
122
+ const base = target.slice(0, target.length - ext.length);
123
+ for (let i = 1; i < 1000; i++) {
124
+ const candidate = `${base} (${i})${ext}`;
125
+ const abs = resolveSafe(filesRoot, candidate);
126
+ if (!fs.existsSync(abs))
127
+ return candidate;
128
+ }
129
+ // pathological — fallback to unique-ish
130
+ return `${base}.${Date.now()}${ext}`;
131
+ }
132
+ export async function revertBatch(batch, opts) {
133
+ const now = (opts.now ?? (() => Math.floor(Date.now() / 1000)))();
134
+ if (batch.status !== "applied") {
135
+ throw new Error(`batch ${batch.id} is ${batch.status}, not applied`);
136
+ }
137
+ if (!batch.expires_at || now > batch.expires_at) {
138
+ batch.status = "expired";
139
+ throw Object.assign(new Error("undo window has expired"), { code: "expired" });
140
+ }
141
+ const report = {
142
+ reverted: 0,
143
+ skipped: 0,
144
+ failed: 0,
145
+ details: [],
146
+ };
147
+ // Reverse to roll back in opposite order of apply
148
+ const applied = batch.suggestions.filter((s) => s.applied).reverse();
149
+ for (const sug of applied) {
150
+ try {
151
+ const back = await revertOne(opts.filesRoot, sug);
152
+ if (back === "skipped") {
153
+ report.skipped++;
154
+ report.details.push({
155
+ id: sug.id,
156
+ status: "skipped",
157
+ reason: "target missing or modified",
158
+ });
159
+ opts.audit("organize.revert", sug.to, {
160
+ batch: batch.id,
161
+ status: "skipped",
162
+ });
163
+ }
164
+ else {
165
+ sug.reverted = true;
166
+ report.reverted++;
167
+ report.details.push({ id: sug.id, status: "reverted" });
168
+ opts.audit("organize.revert", sug.to, {
169
+ batch: batch.id,
170
+ back_to: sug.from,
171
+ });
172
+ }
173
+ }
174
+ catch (e) {
175
+ report.failed++;
176
+ report.details.push({
177
+ id: sug.id,
178
+ status: "failed",
179
+ reason: e.message ?? String(e),
180
+ });
181
+ opts.audit("organize.revert", sug.to, {
182
+ batch: batch.id,
183
+ status: "failed",
184
+ error: e.message ?? String(e),
185
+ });
186
+ }
187
+ }
188
+ batch.status = "reverted";
189
+ batch.reverted_at = now;
190
+ return { batch, report };
191
+ }
192
+ async function revertOne(filesRoot, sug) {
193
+ const toAbs = resolveSafe(filesRoot, sug.to);
194
+ let toStat;
195
+ try {
196
+ toStat = fs.lstatSync(toAbs);
197
+ }
198
+ catch {
199
+ return "skipped";
200
+ }
201
+ if (toStat.isSymbolicLink())
202
+ return "skipped";
203
+ const fromAbs = resolveSafe(filesRoot, sug.from);
204
+ if (fs.existsSync(fromAbs)) {
205
+ // Original location reoccupied (user dropped a new file there) —
206
+ // do not clobber. Skip and let the user resolve.
207
+ return "skipped";
208
+ }
209
+ const sortedPair = [fromAbs, toAbs].sort();
210
+ const first = sortedPair[0];
211
+ const second = sortedPair[1];
212
+ await withPathLock(first, () => withPathLock(second, async () => {
213
+ fs.mkdirSync(path.dirname(fromAbs), { recursive: true });
214
+ fs.renameSync(toAbs, fromAbs);
215
+ }));
216
+ return "reverted";
217
+ }
218
+ //# sourceMappingURL=applier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"applier.js","sourceRoot":"","sources":["../../../src/services/organize/applier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ export type OnCollision = "keep-both" | "overwrite" | "skip";
2
+ export interface ExtractSpec {
3
+ /** Source: "exif.DateTimeOriginal" / "mtime" / "metadata.epub.author" */
4
+ from: string;
5
+ /** Variable name used in move_to template */
6
+ as: string;
7
+ /** Optional format hint (e.g. "Y-MM-DD"); see formatDate. Default: ISO */
8
+ format?: string;
9
+ }
10
+ export interface MatchSpec {
11
+ /** Glob (minimatch syntax) against `files/`-relative path. */
12
+ glob?: string;
13
+ /** Any-of MIME globs ("image/*", "application/pdf"). */
14
+ mime?: string[];
15
+ }
16
+ export interface ActionSpec {
17
+ /** Template path with {var} interpolation. files/-relative. */
18
+ move_to: string;
19
+ /** Default "keep-both". */
20
+ on_collision?: OnCollision;
21
+ }
22
+ export interface OrganizeRule {
23
+ id: string;
24
+ match: MatchSpec;
25
+ extract?: ExtractSpec[];
26
+ action: ActionSpec;
27
+ enabled?: boolean;
28
+ }
29
+ export interface RulesFile {
30
+ version: 1;
31
+ rules: OrganizeRule[];
32
+ }
33
+ export interface RuleValidationError {
34
+ rule_id?: string;
35
+ rule_index?: number;
36
+ field: string;
37
+ message: string;
38
+ }
39
+ export interface RulesValidationResult {
40
+ ok: boolean;
41
+ errors: RuleValidationError[];
42
+ /** Rules that passed validation, in declaration order. */
43
+ valid_rules: OrganizeRule[];
44
+ }
45
+ export declare const BUILTIN_RULES: OrganizeRule[];
46
+ export declare function validateRules(input: unknown): RulesValidationResult;
47
+ /**
48
+ * Reads `<filesRoot>/.organize-rules.json`. If missing, returns built-in
49
+ * defaults. If malformed, throws — callers should surface the message.
50
+ */
51
+ export declare function readRules(filesRoot: string): Promise<{
52
+ rules: OrganizeRule[];
53
+ hash: string;
54
+ source: "file" | "builtin";
55
+ }>;
56
+ export declare function writeRules(filesRoot: string, rules: OrganizeRule[]): Promise<void>;
57
+ export declare function hashRules(rules: OrganizeRule[]): string;
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Organize rules for the W1.5 file organizer.
3
+ *
4
+ * Rules live in `~/.jishushell/files/.organize-rules.json` so they get
5
+ * backed up alongside user data. If the file is missing we fall back to
6
+ * the built-in defaults below; if it exists but is malformed we throw at
7
+ * scan time so the user sees a clear error instead of silently doing
8
+ * nothing.
9
+ *
10
+ * Design constraints:
11
+ * - No I/O at import time (constructor-time configuration)
12
+ * - validateRules is total: never throws on a *single* bad rule, only
13
+ * on whole-file structural errors. Bad rules are returned as
14
+ * `{ rule, errors }` so the UI can highlight them inline.
15
+ * - move_to template variables are validated lazily (at scan time,
16
+ * once per match) — a rule with `{undefined_var}` only errors on
17
+ * files that match it, not the whole batch.
18
+ */
19
+ import * as fs from "node:fs";
20
+ import * as path from "node:path";
21
+ import { createHash } from "node:crypto";
22
+ import { safeWriteBuffer } from "../../utils/safe-write.js";
23
+ // ── Built-in defaults ────────────────────────────────
24
+ export const BUILTIN_RULES = [
25
+ // 1. Photos by EXIF date — most common case
26
+ {
27
+ id: "builtin:photos-by-exif",
28
+ match: {
29
+ glob: "inbox/**/*.{jpg,jpeg,png,heic}",
30
+ mime: ["image/*"],
31
+ },
32
+ extract: [
33
+ { from: "exif.DateTimeOriginal", as: "shot", format: "Y-MM" },
34
+ ],
35
+ action: {
36
+ move_to: "photos/{shot}/{basename}",
37
+ on_collision: "keep-both",
38
+ },
39
+ enabled: true,
40
+ },
41
+ // 2. Videos by mtime (no EXIF on most videos)
42
+ {
43
+ id: "builtin:videos-by-month",
44
+ match: {
45
+ glob: "inbox/**/*.{mp4,mov,webm,mkv}",
46
+ mime: ["video/*"],
47
+ },
48
+ extract: [{ from: "mtime", as: "t", format: "Y-MM" }],
49
+ action: {
50
+ move_to: "videos/{t}/{basename}",
51
+ on_collision: "keep-both",
52
+ },
53
+ enabled: true,
54
+ },
55
+ // 3. Screenshots by day
56
+ {
57
+ id: "builtin:screenshots",
58
+ match: {
59
+ glob: "**/{Screenshot,屏幕截图,截图}*.{png,jpg}",
60
+ },
61
+ extract: [{ from: "mtime", as: "t", format: "Y-MM-DD" }],
62
+ action: {
63
+ move_to: "screenshots/{t}/{basename}",
64
+ on_collision: "keep-both",
65
+ },
66
+ enabled: true,
67
+ },
68
+ // 4. Invoices (Chinese / English prefix)
69
+ {
70
+ id: "builtin:invoices",
71
+ match: {
72
+ glob: "inbox/**/{发票,invoice,receipt}*.{pdf,jpg,png}",
73
+ },
74
+ extract: [{ from: "mtime", as: "t", format: "Y-MM" }],
75
+ action: {
76
+ move_to: "finance/{t}/{basename}",
77
+ on_collision: "keep-both",
78
+ },
79
+ enabled: true,
80
+ },
81
+ // 5. Downloads by extension bucket
82
+ {
83
+ id: "builtin:downloads-by-ext",
84
+ match: {
85
+ glob: "downloads/*",
86
+ },
87
+ extract: [{ from: "ext", as: "bucket" }],
88
+ action: {
89
+ move_to: "downloads/{bucket}/{basename}",
90
+ on_collision: "skip",
91
+ },
92
+ enabled: false, // off by default — many users prefer flat downloads
93
+ },
94
+ ];
95
+ // ── Validation ───────────────────────────────────────
96
+ const ID_REGEX = /^[a-z0-9][a-z0-9:_-]*$/i;
97
+ export function validateRules(input) {
98
+ const errors = [];
99
+ const valid = [];
100
+ if (!input || typeof input !== "object") {
101
+ errors.push({ field: "<root>", message: "rules file must be a JSON object" });
102
+ return { ok: false, errors, valid_rules: [] };
103
+ }
104
+ const obj = input;
105
+ if (obj.version !== 1) {
106
+ errors.push({ field: "version", message: 'must be the literal number 1' });
107
+ }
108
+ if (!Array.isArray(obj.rules)) {
109
+ errors.push({ field: "rules", message: "must be an array" });
110
+ return { ok: false, errors, valid_rules: [] };
111
+ }
112
+ const seenIds = new Set();
113
+ obj.rules.forEach((raw, idx) => {
114
+ const ruleErrors = validateRule(raw, idx, seenIds);
115
+ if (ruleErrors.length > 0) {
116
+ errors.push(...ruleErrors);
117
+ }
118
+ else {
119
+ valid.push(raw);
120
+ }
121
+ });
122
+ return {
123
+ ok: errors.length === 0,
124
+ errors,
125
+ valid_rules: valid,
126
+ };
127
+ }
128
+ function validateRule(raw, index, seenIds) {
129
+ const errs = [];
130
+ if (!raw || typeof raw !== "object") {
131
+ errs.push({ rule_index: index, field: "<rule>", message: "must be an object" });
132
+ return errs;
133
+ }
134
+ const r = raw;
135
+ // id
136
+ if (typeof r.id !== "string" || !ID_REGEX.test(r.id)) {
137
+ errs.push({
138
+ rule_index: index,
139
+ rule_id: typeof r.id === "string" ? r.id : undefined,
140
+ field: "id",
141
+ message: "must be a slug matching [a-z0-9][a-z0-9:_-]*",
142
+ });
143
+ }
144
+ else if (seenIds.has(r.id)) {
145
+ errs.push({ rule_index: index, rule_id: r.id, field: "id", message: "duplicate id" });
146
+ }
147
+ else {
148
+ seenIds.add(r.id);
149
+ }
150
+ // match
151
+ if (!r.match || typeof r.match !== "object") {
152
+ errs.push({ rule_index: index, field: "match", message: "required object" });
153
+ }
154
+ else {
155
+ const m = r.match;
156
+ const hasGlob = typeof m.glob === "string" && m.glob.length > 0;
157
+ const hasMime = Array.isArray(m.mime) && m.mime.length > 0;
158
+ if (!hasGlob && !hasMime) {
159
+ errs.push({
160
+ rule_index: index,
161
+ field: "match",
162
+ message: "must specify at least one of: glob, mime",
163
+ });
164
+ }
165
+ if (m.glob !== undefined && typeof m.glob !== "string") {
166
+ errs.push({ rule_index: index, field: "match.glob", message: "must be a string" });
167
+ }
168
+ if (m.mime !== undefined && !Array.isArray(m.mime)) {
169
+ errs.push({ rule_index: index, field: "match.mime", message: "must be an array" });
170
+ }
171
+ }
172
+ // extract (optional)
173
+ if (r.extract !== undefined) {
174
+ if (!Array.isArray(r.extract)) {
175
+ errs.push({ rule_index: index, field: "extract", message: "must be an array if present" });
176
+ }
177
+ else {
178
+ r.extract.forEach((e, eidx) => {
179
+ if (!e || typeof e !== "object") {
180
+ errs.push({
181
+ rule_index: index,
182
+ field: `extract[${eidx}]`,
183
+ message: "must be an object",
184
+ });
185
+ return;
186
+ }
187
+ const ex = e;
188
+ if (typeof ex.from !== "string" || !ex.from) {
189
+ errs.push({
190
+ rule_index: index,
191
+ field: `extract[${eidx}].from`,
192
+ message: "required string",
193
+ });
194
+ }
195
+ if (typeof ex.as !== "string" || !/^[a-z_][a-z0-9_]*$/i.test(ex.as)) {
196
+ errs.push({
197
+ rule_index: index,
198
+ field: `extract[${eidx}].as`,
199
+ message: "required identifier ([a-z_][a-z0-9_]*)",
200
+ });
201
+ }
202
+ });
203
+ }
204
+ }
205
+ // action
206
+ if (!r.action || typeof r.action !== "object") {
207
+ errs.push({ rule_index: index, field: "action", message: "required object" });
208
+ }
209
+ else {
210
+ const a = r.action;
211
+ if (typeof a.move_to !== "string" || !a.move_to) {
212
+ errs.push({
213
+ rule_index: index,
214
+ field: "action.move_to",
215
+ message: "required string",
216
+ });
217
+ }
218
+ else if (a.move_to.startsWith("/") || a.move_to.includes("..")) {
219
+ errs.push({
220
+ rule_index: index,
221
+ field: "action.move_to",
222
+ message: "must be a relative path without ..",
223
+ });
224
+ }
225
+ if (a.on_collision !== undefined &&
226
+ a.on_collision !== "keep-both" &&
227
+ a.on_collision !== "overwrite" &&
228
+ a.on_collision !== "skip") {
229
+ errs.push({
230
+ rule_index: index,
231
+ field: "action.on_collision",
232
+ message: "must be one of: keep-both | overwrite | skip",
233
+ });
234
+ }
235
+ }
236
+ return errs;
237
+ }
238
+ // ── Persistence ──────────────────────────────────────
239
+ /**
240
+ * Reads `<filesRoot>/.organize-rules.json`. If missing, returns built-in
241
+ * defaults. If malformed, throws — callers should surface the message.
242
+ */
243
+ export async function readRules(filesRoot) {
244
+ const file = path.join(filesRoot, ".organize-rules.json");
245
+ if (!fs.existsSync(file)) {
246
+ return {
247
+ rules: BUILTIN_RULES.filter((r) => r.enabled !== false),
248
+ hash: hashRules(BUILTIN_RULES),
249
+ source: "builtin",
250
+ };
251
+ }
252
+ const buf = fs.readFileSync(file);
253
+ let parsed;
254
+ try {
255
+ parsed = JSON.parse(buf.toString("utf8"));
256
+ }
257
+ catch (e) {
258
+ throw new Error(`invalid JSON in .organize-rules.json: ${e.message}`);
259
+ }
260
+ const result = validateRules(parsed);
261
+ if (!result.ok) {
262
+ const summary = result.errors
263
+ .slice(0, 5)
264
+ .map((e) => `${e.field}: ${e.message}`)
265
+ .join("; ");
266
+ throw new Error(`invalid rules (${result.errors.length} error(s)): ${summary}`);
267
+ }
268
+ return {
269
+ rules: result.valid_rules.filter((r) => r.enabled !== false),
270
+ hash: hashRules(result.valid_rules),
271
+ source: "file",
272
+ };
273
+ }
274
+ export async function writeRules(filesRoot, rules) {
275
+ const file = path.join(filesRoot, ".organize-rules.json");
276
+ const payload = { version: 1, rules };
277
+ const buf = Buffer.from(JSON.stringify(payload, null, 2) + "\n", "utf8");
278
+ await safeWriteBuffer(file, buf);
279
+ }
280
+ export function hashRules(rules) {
281
+ // Stable hash based on serialized payload — used to detect rule edits
282
+ // between scan and apply.
283
+ const canon = JSON.stringify(rules, Object.keys(rules[0] ?? {}).sort());
284
+ return createHash("sha256").update(canon).digest("hex").slice(0, 16);
285
+ }
286
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../../../src/services/organize/rules.ts"],"names":[],"mappings":"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"}