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.
Files changed (2) hide show
  1. package/install-mcp.sh +158 -0
  2. 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.7",
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": [