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
12 changes: 10 additions & 2 deletions docs/components/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ reviewer reports findings with a verdict, and the two loop until the verdict is
`clean` or the round cap is hit.

A `.pwlworkflow` directory contains `workflow.yaml` with `schema: prowl.workflow/v1`,
and optional local script actions and assets. Workflow definitions are loaded from bundle directories. Roles have a `source`:
and optional local script actions and assets. Workflow definitions are loaded from bundle directories.

Prowl registers `.pwlworkflow` as a macOS document package (`com.onevcat.prowl.workflow`).
Finder shows a bundle as one item with a Prowl workflow icon; use **Show Package
Contents** to browse its files. A bundle is still an ordinary directory for the CLI,
editors, and Git. Prowl does not open a bundle from Finder: a double-click reports
that no application is set to open it. Open or run workflows from Prowl.

Roles have a `source`:

| `source` | Meaning | How it is chosen |
|---|---|---|
Expand Down Expand Up @@ -325,7 +333,7 @@ The detail page owns these controls and explanations:
| **Roles** | every `current`, `pick`, and `launch` role with a plain-language behavior summary. Only a `launch` role has a **Preferred Agent Profile** menu; **Choose Automatically** forgets the preference and lets Prowl resolve a qualifying profile at start. Unqualified profiles remain visible with the reason but cannot be selected. **Manage Agent Profiles…** appears once per page. |
| **Run Setup** | **Follow Workflow**, **Always Review Before Running**, or **Run Directly When Possible**. The last choice starts immediately only when profiles, required role choices, inputs, and validation are already resolved; otherwise the review sheet still opens. |
| **Validation** | every diagnostic as message, source location, and stable code. Saving the YAML revalidates automatically; there is no separate Validate button. |
| **Source File** | **Open Workflow** uses the default YAML app; the folder button reveals it in Finder. **Delete Workflow…** asks for confirmation, then moves a personal or repository workflow to Trash and returns to the list. Failed deletions keep the detail open with an error. Built-ins are read-only and cannot be deleted. YAML remains the source of truth—Settings does not embed an editor. |
| **Source File** | **Open Workflow** opens `workflow.yaml` in the default YAML app; the folder button selects the bundle in Finder, where **Show Package Contents** exposes its files. **Delete Workflow…** asks for confirmation, then moves a personal or repository workflow to Trash and returns to the list. Failed deletions keep the detail open with an error. Built-ins are read-only and cannot be deleted. YAML remains the source of truth—Settings does not embed an editor. |

Starting from Settings keeps the review panel in the Settings window. Cancelling
returns to the same detail without bringing the main window or terminal forward.
Expand Down
41 changes: 41 additions & 0 deletions supacode/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,49 @@
<string>$(PROWL_POSTHOG_API_KEY)</string>
<key>ProwlPostHogHost</key>
<string>$(PROWL_POSTHOG_HOST)</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Prowl Workflow</string>
<key>CFBundleTypeRole</key>
<string>None</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<true/>
<key>CFBundleTypeIconSystemGenerated</key>
<true/>
<key>LSItemContentTypes</key>
<array>
<string>com.onevcat.prowl.workflow</string>
</array>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>com.onevcat.prowl.workflow</string>
<key>UTTypeDescription</key>
<string>Prowl Workflow</string>
<key>UTTypeConformsTo</key>
<array>
<string>com.apple.package</string>
</array>
<key>UTTypeIcons</key>
<dict>
<key>UTTypeIconText</key>
<string>Workflow</string>
</dict>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>pwlworkflow</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>com.onevcat.prowl.ghosttySurfaceId</string>
Expand Down
46 changes: 46 additions & 0 deletions supacodeTests/WorkflowDocumentTypeTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Foundation
import Testing

@testable import supacode

/// The test host is the app, so `Bundle.main` carries the Info.plist that Launch Services reads.
struct WorkflowDocumentTypeTests {
private static let identifier = "com.onevcat.prowl.workflow"

@Test func exportedTypeIsAPackageForTheWorkflowExtension() throws {
let exported = try #require(
Bundle.main.object(forInfoDictionaryKey: "UTExportedTypeDeclarations") as? [[String: Any]])
let type = try #require(exported.first { $0["UTTypeIdentifier"] as? String == Self.identifier })
#expect(type["UTTypeConformsTo"] as? [String] == ["com.apple.package"])

let tags = try #require(type["UTTypeTagSpecification"] as? [String: Any])
let request = WorkflowStarterTemplate.Request(name: "Demo", id: "demo", icon: nil, kind: .singleAgent)
let created = URL(filePath: request.fileName).pathExtension
#expect(tags["public.filename-extension"] as? [String] == [created])
}

/// Prowl has no handler for an opened workflow. With any role other than `None`, a double-click
/// in Finder brings Prowl forward and does nothing.
@Test func documentTypeDoesNotClaimToOpenWorkflows() throws {
let documents = try #require(
Bundle.main.object(forInfoDictionaryKey: "CFBundleDocumentTypes") as? [[String: Any]])
let document = try #require(
documents.first { ($0["LSItemContentTypes"] as? [String])?.contains(Self.identifier) == true })
#expect(document["CFBundleTypeRole"] as? String == "None")
}

/// No icon file is bundled: Finder composes the icon from the app icon and this label.
@Test func iconIsSystemGeneratedWithAWorkflowLabel() throws {
let documents = try #require(
Bundle.main.object(forInfoDictionaryKey: "CFBundleDocumentTypes") as? [[String: Any]])
let document = try #require(
documents.first { ($0["LSItemContentTypes"] as? [String])?.contains(Self.identifier) == true })
#expect(document["CFBundleTypeIconSystemGenerated"] as? Bool == true)

let exported = try #require(
Bundle.main.object(forInfoDictionaryKey: "UTExportedTypeDeclarations") as? [[String: Any]])
let type = try #require(exported.first { $0["UTTypeIdentifier"] as? String == Self.identifier })
let icons = try #require(type["UTTypeIcons"] as? [String: Any])
#expect(icons["UTTypeIconText"] as? String == "Workflow")
}
}
Loading