Skip to content

Commit 582fed6

Browse files
committed
Merge branch 'release/0.35.0'
2 parents 35a955f + 1f68309 commit 582fed6

File tree

141 files changed

+3966
-2523
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

141 files changed

+3966
-2523
lines changed

ChatPlugins/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1600"
4+
version = "1.7">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES"
8+
buildArchitectures = "Automatic">
9+
<BuildActionEntries>
10+
<BuildActionEntry
11+
buildForTesting = "YES"
12+
buildForRunning = "YES"
13+
buildForProfiling = "YES"
14+
buildForArchiving = "YES"
15+
buildForAnalyzing = "YES">
16+
<BuildableReference
17+
BuildableIdentifier = "primary"
18+
BlueprintIdentifier = "ChatPlugins"
19+
BuildableName = "ChatPlugins"
20+
BlueprintName = "ChatPlugins"
21+
ReferencedContainer = "container:">
22+
</BuildableReference>
23+
</BuildActionEntry>
24+
</BuildActionEntries>
25+
</BuildAction>
26+
<TestAction
27+
buildConfiguration = "Debug"
28+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
29+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
30+
shouldUseLaunchSchemeArgsEnv = "YES"
31+
shouldAutocreateTestPlan = "YES">
32+
</TestAction>
33+
<LaunchAction
34+
buildConfiguration = "Debug"
35+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
36+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
37+
launchStyle = "0"
38+
useCustomWorkingDirectory = "NO"
39+
ignoresPersistentStateOnLaunch = "NO"
40+
debugDocumentVersioning = "YES"
41+
debugServiceExtension = "internal"
42+
allowLocationSimulation = "YES">
43+
</LaunchAction>
44+
<ProfileAction
45+
buildConfiguration = "Release"
46+
shouldUseLaunchSchemeArgsEnv = "YES"
47+
savedToolIdentifier = ""
48+
useCustomWorkingDirectory = "NO"
49+
debugDocumentVersioning = "YES">
50+
<MacroExpansion>
51+
<BuildableReference
52+
BuildableIdentifier = "primary"
53+
BlueprintIdentifier = "ChatPlugins"
54+
BuildableName = "ChatPlugins"
55+
BlueprintName = "ChatPlugins"
56+
ReferencedContainer = "container:">
57+
</BuildableReference>
58+
</MacroExpansion>
59+
</ProfileAction>
60+
<AnalyzeAction
61+
buildConfiguration = "Debug">
62+
</AnalyzeAction>
63+
<ArchiveAction
64+
buildConfiguration = "Release"
65+
revealArchiveInOrganizer = "YES">
66+
</ArchiveAction>
67+
</Scheme>

ChatPlugins/Package.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// swift-tools-version: 5.8
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "ChatPlugins",
8+
platforms: [.macOS(.v12)],
9+
products: [
10+
.library(
11+
name: "ChatPlugins",
12+
targets: ["TerminalChatPlugin", "ShortcutChatPlugin"]
13+
),
14+
],
15+
dependencies: [
16+
.package(path: "../Tool"),
17+
],
18+
targets: [
19+
.target(
20+
name: "TerminalChatPlugin",
21+
dependencies: [
22+
.product(name: "Chat", package: "Tool"),
23+
.product(name: "Terminal", package: "Tool"),
24+
.product(name: "AppMonitoring", package: "Tool"),
25+
]
26+
),
27+
.target(
28+
name: "ShortcutChatPlugin",
29+
dependencies: [
30+
.product(name: "Chat", package: "Tool"),
31+
.product(name: "Terminal", package: "Tool"),
32+
.product(name: "AppMonitoring", package: "Tool"),
33+
]
34+
),
35+
]
36+
)
37+
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import ChatBasic
2+
import Foundation
3+
import Terminal
4+
5+
public final class ShortcutChatPlugin: ChatPlugin {
6+
public static var id: String { "com.intii.shortcut" }
7+
public static var command: String { "shortcut" }
8+
public static var name: String { "Shortcut" }
9+
public static var description: String { """
10+
Run a shortcut and use message content as input. You need to provide the shortcut name as an argument, for example, `/shortcut(Shortcut Name)`.
11+
""" }
12+
13+
let terminal: TerminalType
14+
15+
init(terminal: TerminalType) {
16+
self.terminal = terminal
17+
}
18+
19+
public init() {
20+
terminal = Terminal()
21+
}
22+
23+
public func send(_ request: Request) async -> AsyncThrowingStream<Response, any Error> {
24+
return .init { continuation in
25+
let task = Task {
26+
let id = "\(Self.command)-\(UUID().uuidString)"
27+
28+
guard let shortcutName = request.arguments.first, !shortcutName.isEmpty else {
29+
continuation.yield(.content(.text(
30+
"Please provide the shortcut name in format: `/\(Self.command)(shortcut name)`"
31+
)))
32+
return
33+
}
34+
35+
var input = String(request.text).trimmingCharacters(in: .whitespacesAndNewlines)
36+
if input.isEmpty {
37+
// if no input detected, use the previous message as input
38+
input = request.history.last?.content ?? ""
39+
}
40+
41+
do {
42+
continuation.yield(.startAction(
43+
id: "run",
44+
task: "Run shortcut `\(shortcutName)`"
45+
))
46+
47+
let env = ProcessInfo.processInfo.environment
48+
let shell = env["SHELL"] ?? "/bin/bash"
49+
let temporaryURL = FileManager.default.temporaryDirectory
50+
let temporaryInputFileURL = temporaryURL
51+
.appendingPathComponent("\(id)-input.txt")
52+
let temporaryOutputFileURL = temporaryURL
53+
.appendingPathComponent("\(id)-output")
54+
55+
try input.write(to: temporaryInputFileURL, atomically: true, encoding: .utf8)
56+
57+
let command = """
58+
shortcuts run "\(shortcutName)" \
59+
-i "\(temporaryInputFileURL.path)" \
60+
-o "\(temporaryOutputFileURL.path)"
61+
"""
62+
63+
continuation.yield(.startAction(
64+
id: "run",
65+
task: "Run shortcut \(shortcutName)"
66+
))
67+
68+
do {
69+
let result = try await terminal.runCommand(
70+
shell,
71+
arguments: ["-i", "-l", "-c", command],
72+
currentDirectoryURL: nil,
73+
environment: [:]
74+
)
75+
continuation.yield(.finishAction(id: "run", result: .success(result)))
76+
} catch {
77+
continuation.yield(.finishAction(
78+
id: "run",
79+
result: .failure(error.localizedDescription)
80+
))
81+
throw error
82+
}
83+
84+
await Task.yield()
85+
try Task.checkCancellation()
86+
87+
if FileManager.default.fileExists(atPath: temporaryOutputFileURL.path) {
88+
let data = try Data(contentsOf: temporaryOutputFileURL)
89+
if let text = String(data: data, encoding: .utf8) {
90+
var response = text
91+
if response.isEmpty {
92+
response = "Finished"
93+
}
94+
continuation.yield(.content(.text(response)))
95+
} else {
96+
let content = """
97+
[View File](\(temporaryOutputFileURL))
98+
"""
99+
continuation.yield(.content(.text(content)))
100+
}
101+
} else {
102+
continuation.yield(.content(.text("Finished")))
103+
}
104+
105+
} catch {
106+
continuation.yield(.content(.text(error.localizedDescription)))
107+
}
108+
109+
continuation.finish()
110+
}
111+
112+
continuation.onTermination = { _ in
113+
task.cancel()
114+
}
115+
}
116+
}
117+
}
118+
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import ChatBasic
2+
import Foundation
3+
import Terminal
4+
import XcodeInspector
5+
6+
public final class TerminalChatPlugin: ChatPlugin {
7+
public static var id: String { "com.intii.terminal" }
8+
public static var command: String { "run" }
9+
public static var name: String { "Terminal" }
10+
public static var description: String { """
11+
Run the command in the message from terminal.
12+
13+
You can use environment variable `$FILE_PATH` and `$PROJECT_ROOT` to access the current file path and project root.
14+
""" }
15+
16+
let terminal: TerminalType
17+
18+
init(terminal: TerminalType) {
19+
self.terminal = terminal
20+
}
21+
22+
public init() {
23+
terminal = Terminal()
24+
}
25+
26+
public func formatContent(_ content: Response.Content) -> Response.Content {
27+
switch content {
28+
case let .text(content):
29+
return .text("""
30+
```sh
31+
\(content)
32+
```
33+
""")
34+
}
35+
}
36+
37+
public func send(_ request: Request) async -> AsyncThrowingStream<Response, any Error> {
38+
return .init { continuation in
39+
let task = Task {
40+
var updateTime = Date()
41+
42+
func streamOutput(_ content: String) {
43+
defer { updateTime = Date() }
44+
if Date().timeIntervalSince(updateTime) > 60 * 2 {
45+
continuation.yield(.startNewMessage)
46+
continuation.yield(.startAction(
47+
id: "run",
48+
task: "Continue `\(request.text)`"
49+
))
50+
continuation.yield(.finishAction(
51+
id: "run",
52+
result: .success("Executed.")
53+
))
54+
continuation.yield(.content(.text("[continue]\n")))
55+
continuation.yield(.content(.text(content)))
56+
} else {
57+
continuation.yield(.content(.text(content)))
58+
}
59+
}
60+
61+
do {
62+
let fileURL = await XcodeInspector.shared.safe.realtimeActiveDocumentURL
63+
let projectURL = await XcodeInspector.shared.safe.realtimeActiveProjectURL
64+
65+
var environment = [String: String]()
66+
if let fileURL {
67+
environment["FILE_PATH"] = fileURL.path
68+
}
69+
if let projectURL {
70+
environment["PROJECT_ROOT"] = projectURL.path
71+
}
72+
73+
try Task.checkCancellation()
74+
75+
let env = ProcessInfo.processInfo.environment
76+
let shell = env["SHELL"] ?? "/bin/bash"
77+
78+
continuation.yield(.startAction(id: "run", task: "Run `\(request.text)`"))
79+
80+
let output = terminal.streamCommand(
81+
shell,
82+
arguments: ["-i", "-l", "-c", request.text],
83+
currentDirectoryURL: projectURL,
84+
environment: environment
85+
)
86+
87+
continuation.yield(.finishAction(
88+
id: "run",
89+
result: .success("Executed.")
90+
))
91+
92+
for try await content in output {
93+
try Task.checkCancellation()
94+
streamOutput(content)
95+
}
96+
} catch let error as Terminal.TerminationError {
97+
continuation.yield(.content(.text("""
98+
99+
[error: \(error.reason)]
100+
""")))
101+
} catch {
102+
continuation.yield(.content(.text("""
103+
104+
[error: \(error.localizedDescription)]
105+
""")))
106+
}
107+
108+
continuation.finish()
109+
}
110+
111+
continuation.onTermination = { _ in
112+
task.cancel()
113+
Task {
114+
await self.terminal.terminate()
115+
}
116+
}
117+
}
118+
}
119+
}
120+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Testing
2+
@testable import ChatPlugins
3+
4+
@Test func example() async throws {
5+
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
6+
}

Copilot for Xcode.xcodeproj/project.pbxproj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@
205205
C8216B772980370100AD38C7 /* ReloadLaunchAgent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReloadLaunchAgent.swift; sourceTree = "<group>"; };
206206
C828B27D2B1F241500E7612A /* ExtensionPoint.appextensionpoint */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = ExtensionPoint.appextensionpoint; sourceTree = "<group>"; };
207207
C82E38492A1F025F00D4EADF /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
208+
C84FD9D72CC671C600BE5093 /* ChatPlugins */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = ChatPlugins; sourceTree = "<group>"; };
208209
C8520300293C4D9000460097 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
209210
C8520308293D805800460097 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
210211
C861A6A229E5503F005C41A3 /* PromptToCodeCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromptToCodeCommand.swift; sourceTree = "<group>"; };
@@ -341,6 +342,7 @@
341342
C81458AE293A009800135263 /* Config.debug.xcconfig */,
342343
C8CD828229B88006008D044D /* TestPlan.xctestplan */,
343344
C828B27D2B1F241500E7612A /* ExtensionPoint.appextensionpoint */,
345+
C84FD9D72CC671C600BE5093 /* ChatPlugins */,
344346
C81D181E2A1B509B006C1B70 /* Tool */,
345347
C8189B282938979000C9DCDA /* Core */,
346348
C8189B182938972F00C9DCDA /* Copilot for Xcode */,
@@ -775,7 +777,7 @@
775777
DEVELOPMENT_TEAM = 5YKZ4Y3DAW;
776778
ENABLE_HARDENED_RUNTIME = YES;
777779
INFOPLIST_FILE = EditorExtension/Info.plist;
778-
INFOPLIST_KEY_CFBundleDisplayName = "$(EXTESNION_BUNDLE_NAME)";
780+
INFOPLIST_KEY_CFBundleDisplayName = "$(EXTENSION_BUNDLE_NAME)";
779781
INFOPLIST_KEY_NSHumanReadableCopyright = "";
780782
LD_RUNPATH_SEARCH_PATHS = (
781783
"$(inherited)",
@@ -803,7 +805,7 @@
803805
DEVELOPMENT_TEAM = 5YKZ4Y3DAW;
804806
ENABLE_HARDENED_RUNTIME = YES;
805807
INFOPLIST_FILE = EditorExtension/Info.plist;
806-
INFOPLIST_KEY_CFBundleDisplayName = "$(EXTESNION_BUNDLE_NAME)";
808+
INFOPLIST_KEY_CFBundleDisplayName = "$(EXTENSION_BUNDLE_NAME)";
807809
INFOPLIST_KEY_NSHumanReadableCopyright = "";
808810
LD_RUNPATH_SEARCH_PATHS = (
809811
"$(inherited)",

0 commit comments

Comments
 (0)