drafted 1.11.7 → 1.11.8
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/install-mcp.sh +158 -0
- package/package.json +1 -1
package/install-mcp.sh
CHANGED
|
@@ -625,6 +625,8 @@ import AppKit
|
|
|
625
625
|
|
|
626
626
|
@main
|
|
627
627
|
struct DraftedUpdaterApp: App {
|
|
628
|
+
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
|
629
|
+
|
|
628
630
|
init() {
|
|
629
631
|
Telemetry.report(event: "drafted_update_helper_started", updateHelperStatus: "running")
|
|
630
632
|
Telemetry.report(event: "drafted_heartbeat", updateHelperStatus: "running")
|
|
@@ -632,6 +634,7 @@ struct DraftedUpdaterApp: App {
|
|
|
632
634
|
|
|
633
635
|
var body: some Scene {
|
|
634
636
|
MenuBarExtra {
|
|
637
|
+
Button("Check for Updates…") { Actions.checkForUpdate(userInitiated: true) }
|
|
635
638
|
Button("Update Drafted") { Actions.updateDrafted() }
|
|
636
639
|
Button("View Logs") { Actions.viewLogs() }
|
|
637
640
|
Button("Open Drafted") { Actions.openDrafted() }
|
|
@@ -651,6 +654,24 @@ struct DraftedUpdaterApp: App {
|
|
|
651
654
|
}
|
|
652
655
|
}
|
|
653
656
|
|
|
657
|
+
// Drives the genuine auto-update: a background check shortly after login and
|
|
658
|
+
// every 6 hours while the menu-bar helper is resident. When a newer published
|
|
659
|
+
// version exists (and the user hasn't skipped it), it prompts Update / Skip.
|
|
660
|
+
final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
661
|
+
private var timer: Timer?
|
|
662
|
+
|
|
663
|
+
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
664
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 25) {
|
|
665
|
+
Actions.checkForUpdate(userInitiated: false)
|
|
666
|
+
}
|
|
667
|
+
let t = Timer(timeInterval: 6 * 3600, repeats: true) { _ in
|
|
668
|
+
Actions.checkForUpdate(userInitiated: false)
|
|
669
|
+
}
|
|
670
|
+
RunLoop.main.add(t, forMode: .common)
|
|
671
|
+
timer = t
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
654
675
|
|
|
655
676
|
final class UpdateStatusWindow: NSWindowController {
|
|
656
677
|
private let stack = NSStackView()
|
|
@@ -734,6 +755,8 @@ final class UpdateStatusWindow: NSWindowController {
|
|
|
734
755
|
}
|
|
735
756
|
|
|
736
757
|
struct Actions {
|
|
758
|
+
static let home = FileManager.default.homeDirectoryForCurrentUser
|
|
759
|
+
|
|
737
760
|
static func logoImage() -> NSImage? {
|
|
738
761
|
guard
|
|
739
762
|
let url = Bundle.main.url(forResource: "logo", withExtension: "svg"),
|
|
@@ -747,6 +770,141 @@ struct Actions {
|
|
|
747
770
|
private static var isUpdating = false
|
|
748
771
|
private static var updateWindow: UpdateStatusWindow?
|
|
749
772
|
|
|
773
|
+
// MARK: - Auto-update check
|
|
774
|
+
|
|
775
|
+
// Compare the installed CLI version to npm's published `latest`. Prompt only
|
|
776
|
+
// when newer and not skipped (background), or always report (user-initiated).
|
|
777
|
+
static func checkForUpdate(userInitiated: Bool) {
|
|
778
|
+
let installed = installedVersion()
|
|
779
|
+
latestVersion { latest in
|
|
780
|
+
DispatchQueue.main.async {
|
|
781
|
+
guard let latest = latest else {
|
|
782
|
+
if userInitiated {
|
|
783
|
+
showInfo(title: "Couldn’t check for updates", body: "Please check your connection and try again.")
|
|
784
|
+
}
|
|
785
|
+
return
|
|
786
|
+
}
|
|
787
|
+
guard let installed = installed, isNewer(latest, than: installed) else {
|
|
788
|
+
if userInitiated {
|
|
789
|
+
showInfo(title: "Drafted is up to date", body: "You’re on \(installed ?? "the latest") version.")
|
|
790
|
+
}
|
|
791
|
+
return
|
|
792
|
+
}
|
|
793
|
+
if !userInitiated && skippedVersion() == latest { return }
|
|
794
|
+
presentUpdateAlert(latest: latest, installed: installed)
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
static func presentUpdateAlert(latest: String, installed: String) {
|
|
800
|
+
Telemetry.report(event: "drafted_update_available", updateHelperStatus: "running")
|
|
801
|
+
let alert = NSAlert()
|
|
802
|
+
alert.messageText = "Drafted \(latest) is available"
|
|
803
|
+
alert.informativeText = "You have \(installed). Update now? Your editor needs to be restarted afterward to load the new MCP tools."
|
|
804
|
+
alert.addButton(withTitle: "Update")
|
|
805
|
+
alert.addButton(withTitle: "Skip This Version")
|
|
806
|
+
alert.addButton(withTitle: "Later")
|
|
807
|
+
if let logo = logoImage() { alert.icon = logo }
|
|
808
|
+
NSApp.activate(ignoringOtherApps: true)
|
|
809
|
+
switch alert.runModal() {
|
|
810
|
+
case .alertFirstButtonReturn:
|
|
811
|
+
updateDrafted()
|
|
812
|
+
case .alertSecondButtonReturn:
|
|
813
|
+
setSkippedVersion(latest)
|
|
814
|
+
Telemetry.report(event: "drafted_update_skipped", updateHelperStatus: "running")
|
|
815
|
+
default:
|
|
816
|
+
break
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
static func showInfo(title: String, body: String) {
|
|
821
|
+
let alert = NSAlert()
|
|
822
|
+
alert.messageText = title
|
|
823
|
+
alert.informativeText = body
|
|
824
|
+
alert.addButton(withTitle: "OK")
|
|
825
|
+
if let logo = logoImage() { alert.icon = logo }
|
|
826
|
+
NSApp.activate(ignoringOtherApps: true)
|
|
827
|
+
alert.runModal()
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Installed version via the CLI, run through a login shell so the npm-global
|
|
831
|
+
// bin is on PATH (the same shell the manual update uses).
|
|
832
|
+
static func installedVersion() -> String? {
|
|
833
|
+
let process = Process()
|
|
834
|
+
process.executableURL = URL(fileURLWithPath: "/bin/bash")
|
|
835
|
+
process.arguments = ["-lc", "drafted --version 2>/dev/null"]
|
|
836
|
+
let pipe = Pipe()
|
|
837
|
+
process.standardOutput = pipe
|
|
838
|
+
process.standardError = FileHandle.nullDevice
|
|
839
|
+
do { try process.run(); process.waitUntilExit() } catch { return nil }
|
|
840
|
+
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
|
841
|
+
guard let out = String(data: data, encoding: .utf8) else { return nil }
|
|
842
|
+
return firstSemver(in: out)
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
static func latestVersion(_ completion: @escaping (String?) -> Void) {
|
|
846
|
+
guard let url = URL(string: "https://registry.npmjs.org/drafted/latest") else { completion(nil); return }
|
|
847
|
+
var request = URLRequest(url: url)
|
|
848
|
+
request.timeoutInterval = 15
|
|
849
|
+
URLSession.shared.dataTask(with: request) { data, _, _ in
|
|
850
|
+
guard
|
|
851
|
+
let data = data,
|
|
852
|
+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
853
|
+
let version = json["version"] as? String
|
|
854
|
+
else { completion(nil); return }
|
|
855
|
+
completion(version)
|
|
856
|
+
}.resume()
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
static func skippedVersion() -> String? {
|
|
860
|
+
let path = home.appendingPathComponent(".drafted/install.json")
|
|
861
|
+
guard
|
|
862
|
+
let data = try? Data(contentsOf: path),
|
|
863
|
+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
864
|
+
else { return nil }
|
|
865
|
+
return json["skippedVersion"] as? String
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
static func setSkippedVersion(_ version: String) {
|
|
869
|
+
let path = home.appendingPathComponent(".drafted/install.json")
|
|
870
|
+
var json = ((try? Data(contentsOf: path)).flatMap { try? JSONSerialization.jsonObject(with: $0) } as? [String: Any]) ?? [:]
|
|
871
|
+
json["skippedVersion"] = version
|
|
872
|
+
if let out = try? JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted, .sortedKeys]) {
|
|
873
|
+
try? out.write(to: path)
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// First dotted-numeric token in the text, e.g. "1.11.7" from "drafted 1.11.7".
|
|
878
|
+
static func firstSemver(in text: String) -> String? {
|
|
879
|
+
var current = ""
|
|
880
|
+
for ch in text {
|
|
881
|
+
if ch.isNumber || ch == "." {
|
|
882
|
+
current.append(ch)
|
|
883
|
+
} else {
|
|
884
|
+
if isSemver(current) { return current }
|
|
885
|
+
current = ""
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
return isSemver(current) ? current : nil
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
static func isSemver(_ s: String) -> Bool {
|
|
892
|
+
let parts = s.split(separator: ".")
|
|
893
|
+
return parts.count >= 2 && parts.allSatisfy { !$0.isEmpty && $0.allSatisfy { $0.isNumber } }
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Numeric, dot-separated comparison (our versions have no prerelease tags).
|
|
897
|
+
static func isNewer(_ a: String, than b: String) -> Bool {
|
|
898
|
+
let pa = a.split(separator: ".").map { Int($0) ?? 0 }
|
|
899
|
+
let pb = b.split(separator: ".").map { Int($0) ?? 0 }
|
|
900
|
+
for i in 0..<max(pa.count, pb.count) {
|
|
901
|
+
let x = i < pa.count ? pa[i] : 0
|
|
902
|
+
let y = i < pb.count ? pb[i] : 0
|
|
903
|
+
if x != y { return x > y }
|
|
904
|
+
}
|
|
905
|
+
return false
|
|
906
|
+
}
|
|
907
|
+
|
|
750
908
|
static func updateDrafted() {
|
|
751
909
|
if isUpdating {
|
|
752
910
|
updateWindow?.showUpdating()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.8",
|
|
4
4
|
"description": "Drafted — visual thinking surface for humans and AI agents. Renders HTML, markdown, images, and code as frames on a zoomable canvas, with MCP tools for AI agents and real-time sync for humans.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|