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
11 changes: 10 additions & 1 deletion docs/pm/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,22 @@ sidebarLabel: "Usage"
description: "Interactive drafting, source inputs, and posting flows for @devintern/pm."
section: "PM"
order: 3
dateModified: 2026-08-12
dateModified: 2026-08-23
---

# @devintern/pm Usage Guide

[DevIntern PM](https://devintern.com/pm-desktop/) is the primary way to create and review tickets. This guide covers the CLI for terminal workflows and automation.

## Quick Capture (Desktop)

DevIntern PM Desktop can register an OS-level global shortcut so you can turn fleeting context into ticket drafts without switching apps:

- Enable it in **Settings → Quick Capture**. The default binding is `Cmd+Alt+Q` on macOS and `Ctrl+Alt+Q` on Windows/Linux, and you can record any combination.
- Invoking the shortcut focuses (or launches) the app and opens a **new** ticket workspace as the active tab — open tickets and running agents keep working in the background.
- If the clipboard holds useful text, it is prefilled and the source tab is inferred automatically (Figma URL → **Figma**, stack-trace-like text → **Error log**, anything else → **Prompt**). An empty clipboard opens an empty Prompt field with the cursor ready.
- If no project is configured yet, capturing just brings the app forward so you can finish setup first. If the shortcut is already taken by another app, Settings shows how to change the binding.

## Interactive Mode (Recommended)

The interactive mode provides a step-by-step terminal UI for creating tasks:
Expand Down
4 changes: 4 additions & 0 deletions packages/pm-desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ The app is not a self-contained agent runtime: **Git** and **at least one suppor

If a required tool is missing, a blocking screen names it and how to install it. **Check again** (or switching back to the app after installing) re-runs the probe — there is no “everything is fine” step when the check passes. Optional extras such as sandbox CLIs are not required for core ticket workflows.

## Quick Capture

Settings → **Quick Capture** registers an OS-level global shortcut (off by default; default binding `Cmd+Alt+Q` on macOS / `Ctrl+Alt+Q` on Windows and Linux, customizable via an in-app recorder). Invoking it from any app focuses or launches the window and opens a fresh ticket workspace as the active tab. Useful clipboard text is prefilled with the source tab inferred (Figma URL → Figma, stack-trace-like text → Error log, otherwise Prompt); empty clipboards land on an empty Prompt field ready to type. Existing tickets and in-flight agent runs are never disturbed. If the binding is taken by another app (or invalid), Settings shows the conflict and how to change it; disabled state never holds a global hotkey. Clipboard contents are read only at invocation time in the main process and are never logged or persisted. Implementation: `src/main/quick-capture.ts` (registration + delivery), `src/shared/quick-capture.ts` (classification/accelerator helpers).

## App icon

Installer + dock/taskbar icon: `build/icon.png` (1024×1024 RGBA, with transparent rounded corners
Expand Down
2 changes: 1 addition & 1 deletion packages/pm-desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@devintern/pm-desktop",
"productName": "DevIntern PM",
"version": "0.9.12",
"version": "0.9.13",
"private": true,
"description": "Desktop app for @devintern/pm — multi-ticket AI task creation for your tracker.",
"author": "DevIntern <hello@getdevintern.com>",
Expand Down
1 change: 1 addition & 0 deletions packages/pm-desktop/src/main/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type AnalyticsEvent =
| "story_decomposed"
| "task_created"
| "analytics_opt_out"
| "quick_capture_invoked"
| "update_available"
| "update_downloaded"
| "update_applied"
Expand Down
7 changes: 6 additions & 1 deletion packages/pm-desktop/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import { registerIpcHandlers } from "./ipc.ts";
import { installAppMenu } from "./menu.ts";
import { augmentPath } from "./path-fix.ts";
import { disposeQuickCapture, syncQuickCaptureRegistration } from "./quick-capture.ts";
import { createWindow } from "./window.ts";

augmentPath();
Expand Down Expand Up @@ -54,7 +55,10 @@ if (!hasLock) {
void app.whenReady().then(async () => {
// Re-apply identity now that `app.getVersion()` is fully resolved.
installAppMenu({ createWindow });
registerIpcHandlers();
registerIpcHandlers({ createWindow });
// Register the Quick Capture global shortcut per persisted settings.
// Registration failures surface through Settings; never block startup.
void syncQuickCaptureRegistration();

if (app.isPackaged) {
// Dynamic import: unpackaged/dev never loads electron-updater.
Expand Down Expand Up @@ -85,6 +89,7 @@ if (!hasLock) {

app.on("before-quit", () => {
stopAutoUpdateChecks();
disposeQuickCapture();
void shutdownAnalytics();
void shutdownErrorTracking();
});
Expand Down
37 changes: 35 additions & 2 deletions packages/pm-desktop/src/main/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { randomUUID } from "node:crypto";
import { mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { basename, join, resolve } from "node:path";
import { BrowserWindow, app, clipboard, dialog, ipcMain, shell } from "electron";
import { BrowserWindow, app, clipboard, dialog, globalShortcut, ipcMain, shell } from "electron";
import { MAX_ATTACHMENTS, attachmentExtensionError } from "@getdevintern/pm/attachments";
import { EngineError } from "@getdevintern/pm/engine";
import type { EngineCallEvents } from "@getdevintern/pm/engine";
Expand All @@ -26,6 +26,7 @@ import type {
GenerateStoryRequest,
InitializeProjectRequest,
IpcResult,
QuickCaptureConfig,
SubtaskDraft,
SubtaskOutcome,
UpdateProjectTrackerRequest,
Expand Down Expand Up @@ -53,6 +54,11 @@ import { connectManagedGitHubRepo } from "./managed-clone.ts";
import { listProjectBindings } from "./project-bindings.ts";
import { persistTrackerCredentials, readProjectEnv } from "./project-env.ts";
import { removeConnectedProject } from "./remove-connected-project.ts";
import {
getQuickCaptureStatus,
initQuickCapture,
setQuickCaptureSettings,
} from "./quick-capture.ts";
import {
beginAgentRequest,
detectGitRepository,
Expand Down Expand Up @@ -141,7 +147,20 @@ async function withAgentRequest<T>(requestId: string, run: () => Promise<T>): Pr
}
}

export function registerIpcHandlers(): void {
export function registerIpcHandlers(options?: {
/** Window factory for Quick Capture when no live window exists yet. */
createWindow?: () => BrowserWindow;
}): void {
// Quick Capture ports: the global shortcut may fire while no window exists
// (macOS background, closed window), so main owns creation + focus.
initQuickCapture({
globalShortcut,
readClipboardText: () => clipboard.readText(),
getWindow: () => BrowserWindow.getAllWindows().find((window) => !window.isDestroyed()),
createWindow: options?.createWindow,
activateApp: () => app.focus({ steal: true }),
});

handle(IPC_CHANNELS.chooseProjectDir, async (event) => {
const window = BrowserWindow.fromWebContents(event.sender);
// Electron 43+ opens Downloads when defaultPath is omitted; seed from
Expand Down Expand Up @@ -659,6 +678,20 @@ export function registerIpcHandlers(): void {

handle(IPC_CHANNELS.dismissUpdateError, async () => dismissUpdateError());

handle(IPC_CHANNELS.getQuickCaptureStatus, async () => {
return getQuickCaptureStatus();
});

handle(IPC_CHANNELS.setQuickCaptureSettings, async (_event, config: QuickCaptureConfig) => {
if (!config || typeof config !== "object") {
throw Object.assign(new Error("Invalid Quick Capture settings."), { code: "invalid_input" });
}
if (typeof config.enabled !== "boolean") {
throw Object.assign(new Error("Invalid Quick Capture settings."), { code: "invalid_input" });
}
return setQuickCaptureSettings(config);
});

// Push status changes to all renderer windows (progress, available, errors).
subscribeUpdateStatus((status) => {
for (const window of BrowserWindow.getAllWindows()) {
Expand Down
Loading
Loading