samagotchi 0.6.0 → 0.7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -1
- data/docs/configuration.md +20 -0
- data/docs/desktop.md +49 -0
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/config.rb +5 -0
- data/lib/samagotchi/desktop/macos/App.swift +5 -1
- data/lib/samagotchi/desktop/macos/ChiRunner.swift +7 -63
- data/lib/samagotchi/desktop/macos/Images.swift +38 -3
- data/lib/samagotchi/desktop/macos/Kitty.swift +346 -0
- data/lib/samagotchi/desktop/macos/Panel.swift +233 -55
- data/lib/samagotchi/desktop/macos/ProcessRunner.swift +74 -0
- data/lib/samagotchi/desktop/macos.rb +23 -8
- data/lib/samagotchi/desktop_command.rb +1 -1
- data/lib/samagotchi/update_command.rb +1 -1
- data/lib/samagotchi/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ca1d3be1642f5b3a7eb95aad166a1d613e125d5f82d6c79e27f54860c676650f
|
|
4
|
+
data.tar.gz: 839db8c02d67e9dab21143e00d19aa04ff09eddf974598f24a5811719001c70d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 06d07b5787fa0f5fdcc71c43ad19bbc22f36670030907d841c78ea5ad103569b514c356e493a55b8d669925a35b4ce63ccb6217dcb1209f3e54530477cc1de45
|
|
7
|
+
data.tar.gz: 36edb5314aba266d1724b270ccc6f1645372431b1db3a173519f71ca641b31636531a63fecad7f5ff3af6218fffbca9268a46d0d1d1eb4e030851e519c992ae9
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,18 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.7.0] - 2026-09-30
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- The desktop helper also sends to agent CLIs (claude, codex, …) in kitty windows: with `kitty.listen_on` copied
|
|
16
|
+
from kitty.conf (and `chi desktop upgrade`), the panel lists them in a "kitty" group next to the sessions; ⏎
|
|
17
|
+
pastes the quote, message and image paths and presses Enter, the new ⌥⏎ only pastes (so several screenshots
|
|
18
|
+
can pile up), and one send can go to chi sessions and kitty windows together. ⌥⏎ no longer inserts a newline
|
|
19
|
+
in the panel (⇧⏎ does). See docs/desktop.md "Agents in kitty".
|
|
20
|
+
|
|
21
|
+
Update with `chi update`. No bundle versions changed.
|
|
22
|
+
|
|
11
23
|
## [0.6.0] - 2026-09-30
|
|
12
24
|
|
|
13
25
|
### Added
|
|
@@ -393,7 +405,8 @@ and long-lived sessions.
|
|
|
393
405
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
394
406
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
395
407
|
|
|
396
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
408
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.7.0...HEAD
|
|
409
|
+
[0.7.0]: https://github.com/dm1try/samagotchi/compare/v0.6.0...v0.7.0
|
|
397
410
|
[0.6.0]: https://github.com/dm1try/samagotchi/compare/v0.5.1...v0.6.0
|
|
398
411
|
[0.5.1]: https://github.com/dm1try/samagotchi/compare/v0.5.0...v0.5.1
|
|
399
412
|
[0.5.0]: https://github.com/dm1try/samagotchi/compare/v0.4.0...v0.5.0
|
data/docs/configuration.md
CHANGED
|
@@ -687,6 +687,23 @@ hosts:
|
|
|
687
687
|
`models:` wins over `hosts:`. On a native host, `vision: true` skips only the
|
|
688
688
|
modalities check: without a media marker the prompt can't carry an image.
|
|
689
689
|
|
|
690
|
+
## Desktop helper: agents in kitty
|
|
691
|
+
|
|
692
|
+
The desktop helper (`chi desktop`) can also paste into agent CLIs (claude,
|
|
693
|
+
codex, …) running in [kitty](https://sw.kovidgoyal.net/kitty/) windows. It
|
|
694
|
+
lists them only with `kitty.listen_on` set: copy the value of `listen_on` from
|
|
695
|
+
your `kitty.conf` as it is (kitty must also have `allow_remote_control` on).
|
|
696
|
+
|
|
697
|
+
```yaml
|
|
698
|
+
kitty:
|
|
699
|
+
listen_on: unix:/tmp/kitty.${KITTY_PID} # as in kitty.conf; unset = no kitty targets
|
|
700
|
+
binary: /Applications/kitty.app/Contents/MacOS/kitty # the default
|
|
701
|
+
agents: [claude, codex] # the programs listed; "*" = every window
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
The helper reads these from its launch file: after editing them run
|
|
705
|
+
`chi desktop upgrade` (or `chi update`). See [Desktop helper](desktop.md#agents-in-kitty).
|
|
706
|
+
|
|
690
707
|
## Project specific description
|
|
691
708
|
|
|
692
709
|
If an AGENT.md file is present in the project root, samagotchi injects its
|
|
@@ -756,6 +773,9 @@ described in their own sections.
|
|
|
756
773
|
| `update.gem` | `true` | | `false`: `chi update` never installs a newer gem (`--no-gem` for one run). See [CLI: Updating](cli.md#updating). |
|
|
757
774
|
| `update.bundles` | `true` | | `false`: `chi update` leaves the shipped bundles to `chi bundle upgrade` (`--no-bundles`). |
|
|
758
775
|
| `update.desktop` | `true` | | `false`: `chi update` leaves the desktop helper alone (`--no-desktop`). |
|
|
776
|
+
| `kitty.listen_on` | none | | The desktop helper's kitty socket, copied from `kitty.conf`; unset = no kitty targets. See "Desktop helper: agents in kitty". |
|
|
777
|
+
| `kitty.binary` | `/Applications/kitty.app/Contents/MacOS/kitty` | | The kitty the helper runs `kitty @` with. |
|
|
778
|
+
| `kitty.agents` | `claude\|codex\|gemini\|aider\|opencode\|cursor-agent\|amp\|goose` | | Foreground programs whose kitty windows the helper lists, `\|`-separated (a YAML list works too); `*` lists every window. |
|
|
759
779
|
| `read.truncate_at_bytes` | `65536` | yes | A `read` result larger than this is cut to a preview. |
|
|
760
780
|
| `read.preview_bytes` | `12288` | yes | Size of that preview. |
|
|
761
781
|
| `read.hard_max_bytes` | `2097152` | yes | Largest file `read` opens. |
|
data/docs/desktop.md
CHANGED
|
@@ -20,6 +20,8 @@ sessions. Keys:
|
|
|
20
20
|
- **⏎** sends a message: `chi send -m <the line>` with the text as quoted context above it. With the line empty,
|
|
21
21
|
the text is the message.
|
|
22
22
|
- **⌘⏎** sends a note: `chi note`, the line (if any) then the text.
|
|
23
|
+
- **⌥⏎** pastes into an agent in a kitty window without pressing Enter (see [Agents in kitty](#agents-in-kitty));
|
|
24
|
+
it is not a newline.
|
|
23
25
|
- **⇧⏎** is a newline, in either field. Esc closes.
|
|
24
26
|
|
|
25
27
|
The list shows the sessions a worker runs now, then, under a "recent" divider, up to 3 stopped ones (dimmed, with
|
|
@@ -64,6 +66,53 @@ thumbnails sit under the message line, 48 px high, the file name as a tooltip; h
|
|
|
64
66
|
the panel closes; files from Finder are sent as they are, never touched. A send with images may take up to 30 s
|
|
65
67
|
(converting, a worker starting) before the panel gives up.
|
|
66
68
|
|
|
69
|
+
## Agents in kitty
|
|
70
|
+
|
|
71
|
+
The panel can also send to agent CLIs (claude, codex, gemini, aider, …) running in
|
|
72
|
+
[kitty](https://sw.kovidgoyal.net/kitty/) windows, next to chi sessions. kitty only, through its remote control;
|
|
73
|
+
off until you set it up:
|
|
74
|
+
|
|
75
|
+
1. In `kitty.conf`, remote control on and a socket: `allow_remote_control yes` and a `listen_on unix:…` line.
|
|
76
|
+
2. Copy that `listen_on` value as it is into chi's config.yml:
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
kitty:
|
|
80
|
+
listen_on: unix:/tmp/kitty.${KITTY_PID}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`kitty.agents` picks the programs listed (default `claude|codex|gemini|aider|opencode|cursor-agent|amp|goose`, a
|
|
84
|
+
YAML list works too; `"*"` lists every window, shells too), `kitty.binary` the kitty to run (default
|
|
85
|
+
`/Applications/kitty.app/Contents/MacOS/kitty`). See [Configuration](configuration.md#desktop-helper-agents-in-kitty).
|
|
86
|
+
3. Run `chi desktop upgrade` (or `chi update`): the helper reads these from its launch file, so **after every edit of
|
|
87
|
+
`kitty:`** run it again. No rebuild when only the settings changed.
|
|
88
|
+
|
|
89
|
+
The helper finds the sockets itself: kitty adds `-<pid>` to a `listen_on` path from kitty.conf (and fills in
|
|
90
|
+
`{kitty_pid}`), `~` and variables the helper knows are expanded, others (`${KITTY_PID}`) stay as written, as kitty
|
|
91
|
+
keeps them. Several kitty instances all count. Only `unix:` sockets work.
|
|
92
|
+
|
|
93
|
+
The panel lists the windows whose foreground program is one of the agents under a **kitty** divider, between live
|
|
94
|
+
and recent sessions: "claude · <its title>", the folder below, a terminal icon, and a ⌘ number like the sessions.
|
|
95
|
+
With the settings on but no agent running, one dim line says "no agents in kitty". A window counts when a foreground
|
|
96
|
+
process's program is an agent, also as the script of `node …/codex` (that match is untested for codex and gemini).
|
|
97
|
+
|
|
98
|
+
- **⏎** pastes into every chosen window and presses Enter: the text as a `> ` quote, a blank line, your message, then
|
|
99
|
+
one image path per line. Chosen together with chi sessions, each gets the message and the panel shows one line
|
|
100
|
+
("Sent. · sent to claude · samagotchi").
|
|
101
|
+
- **⌥⏎** only pastes (no Enter), and only when every chosen target is a kitty window: build up several screenshots,
|
|
102
|
+
each with its own line, then press Enter in kitty. The footer shows **Paste ⌥⏎** instead of Note then.
|
|
103
|
+
- **⌘⏎** with a kitty window chosen beeps: "Notes are for chi sessions". ⌥⏎ with a chi session: "Paste only is
|
|
104
|
+
for kitty windows".
|
|
105
|
+
- **Images:** a screenshot or other image data is copied to `$TMPDIR/chi-helper-sent/<uuid>.png` (or `.tiff`) and
|
|
106
|
+
its path pasted; the agent reads it from there (claude attaches it as `[Image #1]`). These files are deleted a week
|
|
107
|
+
later, at the helper's next start. Finder files are pasted with their own path, `'…'`-quoted when they hold spaces.
|
|
108
|
+
- The paste arrives as one bracketed paste, so the agent takes newlines as text (claude shows long ones as
|
|
109
|
+
`[Pasted text]`). With `agents: "*"` beware of a program that doesn't turn bracketed paste on: the newlines would
|
|
110
|
+
act as Enter.
|
|
111
|
+
- No delivery confirmation: before pasting, the helper checks the window is still there ("window closed" if not);
|
|
112
|
+
it can't know whether the agent read it. The 16 KB cap is chi's; a kitty window takes more.
|
|
113
|
+
- Focus goes back to the app you came from, as for sessions. The last choice (kitty windows too) is preselected
|
|
114
|
+
while listed; ⌘ numbers shift as windows come and go.
|
|
115
|
+
|
|
67
116
|
## Install
|
|
68
117
|
|
|
69
118
|
```sh
|
data/lib/samagotchi/config.rb
CHANGED
|
@@ -135,6 +135,11 @@ module Samagotchi
|
|
|
135
135
|
Entry.new(key: "update.gem", yaml_path: %w[update gem], type: :bool, default: true, expose: %i[env config]),
|
|
136
136
|
Entry.new(key: "update.bundles", yaml_path: %w[update bundles], type: :bool, default: true, expose: %i[env config]),
|
|
137
137
|
Entry.new(key: "update.desktop", yaml_path: %w[update desktop], type: :bool, default: true, expose: %i[env config]),
|
|
138
|
+
# The desktop helper's kitty targets (Desktop::MacOS.kitty_settings): listen_on copied from kitty.conf (unset = none),
|
|
139
|
+
# the kitty binary, and the foreground programs listed, "|"-separated (a YAML list works too; "*" = every window).
|
|
140
|
+
Entry.new(key: "kitty.listen_on", yaml_path: %w[kitty listen_on], type: :string, default: nil, expose: %i[env config]),
|
|
141
|
+
Entry.new(key: "kitty.binary", yaml_path: %w[kitty binary], type: :string, default: "/Applications/kitty.app/Contents/MacOS/kitty", expose: %i[env config]),
|
|
142
|
+
Entry.new(key: "kitty.agents", yaml_path: %w[kitty agents], type: :string, default: "claude|codex|gemini|aider|opencode|cursor-agent|amp|goose", expose: %i[env config]),
|
|
138
143
|
|
|
139
144
|
Entry.new(key: "read.truncate_at_bytes", yaml_path: %w[read truncate_at_bytes], type: :integer, default: 65_536, expose: %i[env config cli]),
|
|
140
145
|
Entry.new(key: "read.preview_bytes", yaml_path: %w[read preview_bytes], type: :integer, default: 12_288, expose: %i[env config cli]),
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// (text, or images: files in Finder, a picture in Preview), the hotkey with
|
|
3
3
|
// the clipboard (a screenshot too). No Dock icon (LSUIElement); it stays
|
|
4
4
|
// running after the first launch. `ChiHelper --login on|off|status` (run
|
|
5
|
-
// directly by chi desktop) manages the login item and exits
|
|
5
|
+
// directly by chi desktop) manages the login item and exits;
|
|
6
|
+
// `ChiHelper --kitty list|send` is the kitty targets' seam (Kitty.swift).
|
|
6
7
|
import AppKit
|
|
7
8
|
import ServiceManagement
|
|
8
9
|
|
|
@@ -97,6 +98,9 @@ struct ChiHelperMain {
|
|
|
97
98
|
if args.count >= 2, args[1] == "--login" {
|
|
98
99
|
exit(loginCommand(args.count >= 3 ? args[2] : ""))
|
|
99
100
|
}
|
|
101
|
+
if args.count >= 2, args[1] == "--kitty" {
|
|
102
|
+
exit(kittyCommand(Array(args.dropFirst(2))))
|
|
103
|
+
}
|
|
100
104
|
let app = NSApplication.shared
|
|
101
105
|
let delegate = AppDelegate()
|
|
102
106
|
app.delegate = delegate
|
|
@@ -7,13 +7,8 @@ struct LaunchConfig: Decodable {
|
|
|
7
7
|
let version: String?
|
|
8
8
|
let argv: [String]
|
|
9
9
|
let env: [String: String]?
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
struct ChiResult {
|
|
13
|
-
let status: Int32
|
|
14
|
-
let stdout: String
|
|
15
|
-
let stderr: String
|
|
16
|
-
let timedOut: Bool
|
|
10
|
+
/// Agents in kitty windows (Kitty.swift); nil = none.
|
|
11
|
+
let kitty: KittySettings?
|
|
17
12
|
}
|
|
18
13
|
|
|
19
14
|
enum ChiError: Error, CustomStringConvertible {
|
|
@@ -106,9 +101,8 @@ final class ChiRunner {
|
|
|
106
101
|
return config
|
|
107
102
|
}
|
|
108
103
|
|
|
109
|
-
/// Runs `chi <args
|
|
110
|
-
///
|
|
111
|
-
/// note cap). Calls back on the main queue.
|
|
104
|
+
/// Runs `chi <args>` through ProcessRunner (no pipe deadlock near the
|
|
105
|
+
/// 16 KiB note cap). Calls back on the main queue.
|
|
112
106
|
/// @param timeout this call's limit instead of the runner's
|
|
113
107
|
func run(_ args: [String], stdin: Data? = nil, timeout: TimeInterval? = nil,
|
|
114
108
|
completion: @escaping (Result<ChiResult, ChiError>) -> Void) {
|
|
@@ -119,59 +113,9 @@ final class ChiRunner {
|
|
|
119
113
|
completion(.failure(.failed("\(error)"))); return
|
|
120
114
|
}
|
|
121
115
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
process.environment = ProcessInfo.processInfo.environment.merging(config.env ?? [:]) { _, baked in baked }
|
|
126
|
-
process.currentDirectoryURL = URL(fileURLWithPath: NSHomeDirectory())
|
|
127
|
-
let out = Pipe(), err = Pipe(), input = Pipe()
|
|
128
|
-
process.standardOutput = out
|
|
129
|
-
process.standardError = err
|
|
130
|
-
process.standardInput = stdin == nil ? FileHandle.nullDevice : input
|
|
131
|
-
|
|
132
|
-
let lock = NSLock()
|
|
133
|
-
var outData = Data(), errData = Data()
|
|
134
|
-
out.fileHandleForReading.readabilityHandler = { handle in
|
|
135
|
-
let chunk = handle.availableData
|
|
136
|
-
lock.lock(); outData.append(chunk); lock.unlock()
|
|
137
|
-
}
|
|
138
|
-
err.fileHandleForReading.readabilityHandler = { handle in
|
|
139
|
-
let chunk = handle.availableData
|
|
140
|
-
lock.lock(); errData.append(chunk); lock.unlock()
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
var timedOut = false
|
|
144
|
-
process.terminationHandler = { proc in
|
|
145
|
-
out.fileHandleForReading.readabilityHandler = nil
|
|
146
|
-
err.fileHandleForReading.readabilityHandler = nil
|
|
147
|
-
let restOut = out.fileHandleForReading.readDataToEndOfFile()
|
|
148
|
-
let restErr = err.fileHandleForReading.readDataToEndOfFile()
|
|
149
|
-
lock.lock()
|
|
150
|
-
outData.append(restOut); errData.append(restErr)
|
|
151
|
-
let result = ChiResult(status: proc.terminationStatus,
|
|
152
|
-
stdout: String(decoding: outData, as: UTF8.self),
|
|
153
|
-
stderr: String(decoding: errData, as: UTF8.self),
|
|
154
|
-
timedOut: timedOut)
|
|
155
|
-
lock.unlock()
|
|
156
|
-
DispatchQueue.main.async { completion(.success(result)) }
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
do { try process.run() } catch {
|
|
160
|
-
completion(.failure(.failed("Could not start \(config.argv[0]): \(error.localizedDescription)")))
|
|
161
|
-
return
|
|
162
|
-
}
|
|
163
|
-
if let stdin {
|
|
164
|
-
DispatchQueue.global().async {
|
|
165
|
-
try? input.fileHandleForWriting.write(contentsOf: stdin)
|
|
166
|
-
try? input.fileHandleForWriting.close()
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
DispatchQueue.global().asyncAfter(deadline: .now() + (timeout ?? self.timeout)) {
|
|
170
|
-
if process.isRunning {
|
|
171
|
-
lock.lock(); timedOut = true; lock.unlock()
|
|
172
|
-
process.terminate()
|
|
173
|
-
}
|
|
174
|
-
}
|
|
116
|
+
ProcessRunner.run(executable: config.argv[0], args: Array(config.argv.dropFirst()) + args,
|
|
117
|
+
env: ProcessInfo.processInfo.environment.merging(config.env ?? [:]) { _, baked in baked },
|
|
118
|
+
stdin: stdin, timeout: timeout ?? self.timeout, completion: completion)
|
|
175
119
|
}
|
|
176
120
|
|
|
177
121
|
/// The live sessions, then up to +recentCount+ stopped ones, newest
|
|
@@ -27,7 +27,11 @@ struct PanelImage: Identifiable, Equatable {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
enum ImageIntake {
|
|
30
|
-
|
|
30
|
+
/// $TMPDIR when set (launchd sets it; a spec points it elsewhere), else
|
|
31
|
+
/// the user's temp folder.
|
|
32
|
+
static let tempRoot = URL(fileURLWithPath: ProcessInfo.processInfo.environment["TMPDIR"].flatMap { $0.isEmpty ? nil : $0 }
|
|
33
|
+
?? NSTemporaryDirectory(), isDirectory: true)
|
|
34
|
+
static let tempDir = tempRoot.appendingPathComponent("chi-helper", isDirectory: true)
|
|
31
35
|
|
|
32
36
|
/// A pasteboard's content for the panel, in this order: image files
|
|
33
37
|
/// (Finder ⌘C, a Service on files) with no text; else its text, as before
|
|
@@ -78,9 +82,40 @@ enum ImageIntake {
|
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
84
|
|
|
81
|
-
///
|
|
82
|
-
|
|
85
|
+
/// Images pasted into kitty windows, kept for the agent to read after
|
|
86
|
+
/// the send: never deleted by it, swept at launch after a week.
|
|
87
|
+
static let sentDir = tempRoot.appendingPathComponent("chi-helper-sent", isDirectory: true)
|
|
88
|
+
static let sentMaxAge: TimeInterval = 7 * 86_400
|
|
89
|
+
|
|
90
|
+
/// The paths a kitty paste names: an image the helper wrote is copied to
|
|
91
|
+
/// sentDir first (the send deletes the original), with no spaces in its
|
|
92
|
+
/// name; a file of the user's is named as it is. Call before any send
|
|
93
|
+
/// job starts. An image that can't be copied is left out.
|
|
94
|
+
static func keepForPaste(_ images: [PanelImage]) -> [String] {
|
|
95
|
+
images.compactMap { image in
|
|
96
|
+
guard image.temp else { return image.url.path }
|
|
97
|
+
let ext = image.url.pathExtension.isEmpty ? "png" : image.url.pathExtension
|
|
98
|
+
let copy = sentDir.appendingPathComponent("\(UUID().uuidString).\(ext)")
|
|
99
|
+
do {
|
|
100
|
+
try FileManager.default.createDirectory(at: sentDir, withIntermediateDirectories: true)
|
|
101
|
+
try FileManager.default.copyItem(at: image.url, to: copy)
|
|
102
|
+
return copy.path
|
|
103
|
+
} catch {
|
|
104
|
+
return nil
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// At launch: what a helper that quit mid-send left, and pasted images
|
|
110
|
+
/// older than a week.
|
|
111
|
+
static func sweep(now: Date = Date()) {
|
|
83
112
|
try? FileManager.default.removeItem(at: tempDir)
|
|
113
|
+
let fm = FileManager.default
|
|
114
|
+
for name in (try? fm.contentsOfDirectory(atPath: sentDir.path)) ?? [] {
|
|
115
|
+
let url = sentDir.appendingPathComponent(name)
|
|
116
|
+
let modified = (try? fm.attributesOfItem(atPath: url.path)[.modificationDate] as? Date) ?? now
|
|
117
|
+
if now.timeIntervalSince(modified) > sentMaxAge { try? fm.removeItem(at: url) }
|
|
118
|
+
}
|
|
84
119
|
}
|
|
85
120
|
}
|
|
86
121
|
|