Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
artifact_name: ${{ steps.context.outputs.artifact_name }}
source_ref: ${{ steps.context.outputs.source_ref }}
injector_ref: ${{ steps.context.outputs.injector_ref }}
mcp_ref: ${{ steps.context.outputs.mcp_ref }}

steps:
- name: Validate Trigger Actor
Expand All @@ -40,6 +41,7 @@ jobs:
BUILD_NUMBER="${{ github.event.client_payload.build_number }}"
SOURCE_REF="${{ github.event.client_payload.source_ref }}"
INJECTOR_REF="${{ github.event.client_payload.injector_ref }}"
MCP_REF="${{ github.event.client_payload.mcp_ref }}"
RELEASE_REF="${{ github.event.client_payload.release_ref }}"
PARENT_REPOSITORY="${{ github.event.client_payload.parent_repository }}"
PARENT_REF="${{ github.event.client_payload.parent_ref }}"
Expand All @@ -62,6 +64,7 @@ jobs:
[[ "$PARENT_SHA" =~ ^[0-9a-f]{40}$ ]] || { echo "Invalid parent SHA." >&2; exit 1; }
[[ "$SOURCE_REF" =~ ^[0-9a-f]{40}$ ]] || { echo "Invalid Host source SHA." >&2; exit 1; }
[[ "$INJECTOR_REF" =~ ^[0-9a-f]{40}$ ]] || { echo "Invalid Injector source SHA." >&2; exit 1; }
[[ "$MCP_REF" =~ ^[0-9a-f]{40}$ ]] || { echo "Invalid MCP source SHA." >&2; exit 1; }
[[ "$RELEASE_REF" =~ ^[0-9a-f]{40}$ ]] || { echo "Invalid Release source SHA." >&2; exit 1; }
[[ "$RELEASE_ID" == "release/app/$VERSION@$PARENT_SHA/host" ]] || {
echo "Release id does not identify the parent tag and commit." >&2
Expand All @@ -75,6 +78,7 @@ jobs:
echo "artifact_name=LookInside-${VERSION}-release"
echo "source_ref=${SOURCE_REF}"
echo "injector_ref=${INJECTOR_REF}"
echo "mcp_ref=${MCP_REF}"
} >>"${GITHUB_OUTPUT}"

build_package:
Expand Down Expand Up @@ -130,6 +134,21 @@ jobs:
git -C ../LookInside-Injector checkout --detach "${{ needs.prepare.outputs.injector_ref }}"
[[ "$(git -C ../LookInside-Injector rev-parse HEAD)" == "${{ needs.prepare.outputs.injector_ref }}" ]]

- name: Checkout MCP Server
env:
LOOKINSIDE_MCP_READ_TOKEN: ${{ secrets.LOOKINSIDE_PRIVATE_READ_TOKEN || secrets.LOOKINSIDE_WEB_RELEASE_TOKEN || secrets.LOOKINSIDE_INJECTOR_READ_TOKEN }}
run: |
set -euo pipefail
if [[ -z "${LOOKINSIDE_MCP_READ_TOKEN}" ]]; then
echo "LOOKINSIDE_PRIVATE_READ_TOKEN, LOOKINSIDE_WEB_RELEASE_TOKEN, or LOOKINSIDE_INJECTOR_READ_TOKEN is required to checkout LookInside-MCP." >&2
exit 1
fi
git clone --no-checkout \
"https://x-access-token:${LOOKINSIDE_MCP_READ_TOKEN}@github.com/LookInsideApp/LookInside-MCP.git" \
../LookInside-MCP
git -C ../LookInside-MCP checkout --detach "${{ needs.prepare.outputs.mcp_ref }}"
[[ "$(git -C ../LookInside-MCP rev-parse HEAD)" == "${{ needs.prepare.outputs.mcp_ref }}" ]]

- name: Configure Private Package Access
env:
LOOKINSIDE_PRIVATE_READ_TOKEN: ${{ secrets.LOOKINSIDE_PRIVATE_READ_TOKEN || secrets.LOOKINSIDE_WEB_RELEASE_TOKEN || secrets.LOOKINSIDE_INJECTOR_READ_TOKEN }}
Expand Down
49 changes: 49 additions & 0 deletions LookInside.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,26 @@
Launch/LKLaunchWindowController.swift,
Localizable.xcstrings,
LookinArchiveIcon.icns,
MCPBridge/LKMCPBridgeAttributeEncoder.swift,
MCPBridge/LKMCPBridgeAttributeValueDecoder.swift,
MCPBridge/LKMCPBridgeConnection.swift,
MCPBridge/LKMCPBridgeDetailsService.swift,
MCPBridge/LKMCPBridgeEventPublisher.swift,
MCPBridge/LKMCPBridgeFrame.swift,
MCPBridge/LKMCPBridgeInspectionModels.swift,
MCPBridge/LKMCPBridgeInspectionService.swift,
MCPBridge/LKMCPBridgeInvocationService.swift,
MCPBridge/LKMCPBridgeListenSocket.swift,
MCPBridge/LKMCPBridgeLiveDocumentLookup.swift,
MCPBridge/LKMCPBridgeModificationService.swift,
MCPBridge/LKMCPBridgeObjectIdentifierList.swift,
MCPBridge/LKMCPBridgeRefreshService.swift,
MCPBridge/LKMCPBridgeScreenshotService.swift,
MCPBridge/LKMCPBridgeSearchQuery.swift,
MCPBridge/LKMCPBridgeSearchService.swift,
MCPBridge/LKMCPBridgeSecureContentDetector.swift,
MCPBridge/LKMCPBridgeSelectorService.swift,
MCPBridge/LKMCPBridgeServer.swift,
Manager/LKAppMenuManager.swift,
Manager/LKHelper.swift,
Manager/LKNavigationManager.swift,
Expand Down Expand Up @@ -328,6 +348,7 @@
AA48F29821CC0CA50032EC2C /* Frameworks */,
AA48F29921CC0CA50032EC2C /* Resources */,
5005F8FE2FA505DF00C0BB07 /* Embed Injector Daemon */,
5005F8FF2FA505DF00C0BB07 /* Embed MCP CLI */,
);
buildRules = (
);
Expand Down Expand Up @@ -444,6 +465,34 @@
"",
);
};
5005F8FF2FA505DF00C0BB07 /* Embed MCP CLI */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
name = "Embed MCP CLI";
shellPath = /bin/sh;
shellScript = (
"if [ ! -d \"$SRCROOT/../LookInside-MCP\" ]; then",
" if [ \"$CONFIGURATION\" = \"Release\" ]; then",
" echo \"error: LookInside-MCP not present; Release builds must embed the MCP server binary\"",
" exit 1",
" fi",
" echo \"Skipping Embed MCP CLI: LookInside-MCP not present (not in monorepo checkout)\"",
" exit 0",
"fi",
"PREPARE_SCRIPT=\"$SRCROOT/Scripts/prepare-mcp-cli.sh\"",
"# Run in a fresh login zsh so xcodebuild env from the host build does not leak",
"# into the nested SwiftPM build (would crash XCBBuildService).",
"# Re-export the host's BUILT_PRODUCTS_DIR / CONTENTS_FOLDER_PATH / CONFIGURATION",
"# so the nested zsh can locate the LookInside.app bundle to embed into.",
"env -i HOME=\"$HOME\" USER=\"$USER\" TERM=\"${TERM:-dumb}\" \\",
" GITHUB_TOKEN=\"${GITHUB_TOKEN:-}\" \\",
" CONFIGURATION=\"$CONFIGURATION\" \\",
" BUILT_PRODUCTS_DIR=\"$BUILT_PRODUCTS_DIR\" \\",
" CONTENTS_FOLDER_PATH=\"$CONTENTS_FOLDER_PATH\" \\",
" /bin/zsh -lc \"$PREPARE_SCRIPT\"",
"",
);
};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
Expand Down
2 changes: 2 additions & 0 deletions LookInside/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

func applicationDidFinishLaunching(_: Notification) {
_ = LKConnectionManager.shared
LKMCPBridgeServer.sharedInstance.start()
// Documents opened during launch (Finder double-click, Open With…)
// are registered with NSDocumentController by now, so "no documents
// ⇒ show Launch" covers both cold-start cases.
Expand Down Expand Up @@ -176,6 +177,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

func applicationShouldTerminate(_: NSApplication) -> NSApplication.TerminateReply {
LKSwiftUISupportGatekeeper.sharedInstance().shutdownRuntime()
LKMCPBridgeServer.sharedInstance.stop()

// 清理打开 UIImageView 的图片时创建的临时文件
for path in LKHelper.sharedInstance().tempImageFiles {
Expand Down
15 changes: 14 additions & 1 deletion LookInside/Connection/LookinLiveDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ import AppKit

@objc(LookinLiveDocument)
final class LookinLiveDocument: NSDocument {
/// Posted (object: the document) once `LookinLiveDocumentController`
/// added the document and made its window controllers, so observers can
/// read `inspectableApp` and `hierarchyDataSource` at once.
/// `NSDocumentController` has no such notification of its own.
static let didOpenNotification = Notification.Name("LookinLiveDocumentDidOpenNotification")

/// Posted (object: the document) at the start of `close()`, while the
/// document is still whole.
static let willCloseNotification = Notification.Name("LookinLiveDocumentWillCloseNotification")

/// The app this document inspects. Replaced by a new `LKInspectableApp`
/// (new channel and app info) when the app reconnects. Observable with
/// KVO.
Expand Down Expand Up @@ -51,7 +61,7 @@ final class LookinLiveDocument: NSDocument {
}

/// Nil before `makeWindowControllers()` ran.
private var staticWindowController: LKStaticWindowController? {
var staticWindowController: LKStaticWindowController? {
windowControllers.first as? LKStaticWindowController
}

Expand Down Expand Up @@ -127,6 +137,9 @@ final class LookinLiveDocument: NSDocument {
}

override func close() {
// Before any teardown, so observers can still read the app info and
// the data source.
NotificationCenter.default.post(name: Self.willCloseNotification, object: self)
channelEndTask?.cancel()
channelEndTask = nil
reconnectTask?.cancel()
Expand Down
3 changes: 3 additions & 0 deletions LookInside/Connection/LookinLiveDocumentController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ final class LookinLiveDocumentController: NSObject {
NSDocumentController.shared.addDocument(document)
document.makeWindowControllers()
document.showWindows()
// After makeWindowControllers(), so observers can reach the data
// source, which lives on the window controller.
NotificationCenter.default.post(name: LookinLiveDocument.didOpenNotification, object: document)
return (document, false)
}

Expand Down
Loading
Loading