patchwork-os 0.2.0-alpha.0 → 0.2.0-alpha.3

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 (75) hide show
  1. package/README.md +41 -46
  2. package/dist/claudeDriver.d.ts +3 -1
  3. package/dist/claudeDriver.js +48 -0
  4. package/dist/claudeDriver.js.map +1 -1
  5. package/dist/commands/dashboard.d.ts +47 -0
  6. package/dist/commands/dashboard.js +319 -0
  7. package/dist/commands/dashboard.js.map +1 -0
  8. package/dist/config.d.ts +2 -2
  9. package/dist/config.js +5 -2
  10. package/dist/config.js.map +1 -1
  11. package/dist/connectors/github.d.ts +44 -0
  12. package/dist/connectors/github.js +113 -0
  13. package/dist/connectors/github.js.map +1 -0
  14. package/dist/connectors/gmail.d.ts +40 -0
  15. package/dist/connectors/gmail.js +289 -0
  16. package/dist/connectors/gmail.js.map +1 -0
  17. package/dist/connectors/linear.d.ts +67 -0
  18. package/dist/connectors/linear.js +198 -0
  19. package/dist/connectors/linear.js.map +1 -0
  20. package/dist/connectors/sentry.d.ts +47 -0
  21. package/dist/connectors/sentry.js +204 -0
  22. package/dist/connectors/sentry.js.map +1 -0
  23. package/dist/drivers/claude/api.d.ts +11 -0
  24. package/dist/drivers/claude/api.js +54 -0
  25. package/dist/drivers/claude/api.js.map +1 -0
  26. package/dist/drivers/claude/envSanitizer.d.ts +7 -0
  27. package/dist/drivers/claude/envSanitizer.js +18 -0
  28. package/dist/drivers/claude/envSanitizer.js.map +1 -0
  29. package/dist/drivers/claude/streamParser.d.ts +38 -0
  30. package/dist/drivers/claude/streamParser.js +34 -0
  31. package/dist/drivers/claude/streamParser.js.map +1 -0
  32. package/dist/drivers/claude/subprocess.d.ts +19 -0
  33. package/dist/drivers/claude/subprocess.js +216 -0
  34. package/dist/drivers/claude/subprocess.js.map +1 -0
  35. package/dist/drivers/claude/subprocessSettings.d.ts +9 -0
  36. package/dist/drivers/claude/subprocessSettings.js +55 -0
  37. package/dist/drivers/claude/subprocessSettings.js.map +1 -0
  38. package/dist/drivers/gemini/index.d.ts +14 -0
  39. package/dist/drivers/gemini/index.js +176 -0
  40. package/dist/drivers/gemini/index.js.map +1 -0
  41. package/dist/drivers/grok/index.d.ts +11 -0
  42. package/dist/drivers/grok/index.js +22 -0
  43. package/dist/drivers/grok/index.js.map +1 -0
  44. package/dist/drivers/index.d.ts +18 -0
  45. package/dist/drivers/index.js +31 -0
  46. package/dist/drivers/index.js.map +1 -0
  47. package/dist/drivers/openai/index.d.ts +24 -0
  48. package/dist/drivers/openai/index.js +110 -0
  49. package/dist/drivers/openai/index.js.map +1 -0
  50. package/dist/drivers/types.d.ts +72 -0
  51. package/dist/drivers/types.js +30 -0
  52. package/dist/drivers/types.js.map +1 -0
  53. package/dist/index.js +116 -22
  54. package/dist/index.js.map +1 -1
  55. package/dist/recipes/yamlRunner.d.ts +95 -0
  56. package/dist/recipes/yamlRunner.js +598 -0
  57. package/dist/recipes/yamlRunner.js.map +1 -0
  58. package/dist/server.js +272 -1
  59. package/dist/server.js.map +1 -1
  60. package/dist/tools/ctxGetTaskContext.d.ts +4 -1
  61. package/dist/tools/ctxGetTaskContext.js +45 -2
  62. package/dist/tools/ctxGetTaskContext.js.map +1 -1
  63. package/dist/tools/fetchLinearIssue.d.ts +112 -0
  64. package/dist/tools/fetchLinearIssue.js +129 -0
  65. package/dist/tools/fetchLinearIssue.js.map +1 -0
  66. package/dist/tools/fetchSentryIssue.d.ts +143 -0
  67. package/dist/tools/fetchSentryIssue.js +150 -0
  68. package/dist/tools/fetchSentryIssue.js.map +1 -0
  69. package/dist/tools/index.js +4 -0
  70. package/dist/tools/index.js.map +1 -1
  71. package/package.json +1 -1
  72. package/templates/recipes/gmail-health-check.yaml +19 -0
  73. package/templates/recipes/inbox-triage.yaml +15 -0
  74. package/templates/recipes/morning-brief.yaml +64 -0
  75. package/templates/scheduled-tasks/morning-brief/SKILL.md +37 -0
@@ -0,0 +1,18 @@
1
+ import type { ProviderDriver } from "./types.js";
2
+ export type DriverMode = "subprocess" | "api" | "openai" | "grok" | "gemini" | "none";
3
+ export interface DriverFactoryOpts {
4
+ binary: string;
5
+ antBinary: string;
6
+ }
7
+ /**
8
+ * Create the appropriate driver from a mode string.
9
+ * Returns null for "none" (orchestration disabled).
10
+ */
11
+ export declare function createDriver(mode: DriverMode, opts: DriverFactoryOpts, log: (msg: string) => void): ProviderDriver | null;
12
+ export { ApiDriver } from "./claude/api.js";
13
+ export { SubprocessDriver } from "./claude/subprocess.js";
14
+ export { GeminiSubprocessDriver } from "./gemini/index.js";
15
+ export { GrokApiDriver } from "./grok/index.js";
16
+ export { OpenAIApiDriver } from "./openai/index.js";
17
+ export type { ProviderDriver, ProviderTaskInput, ProviderTaskOutcome, ProviderTaskResult, } from "./types.js";
18
+ export { toProviderTaskOutcome } from "./types.js";
@@ -0,0 +1,31 @@
1
+ import { ApiDriver } from "./claude/api.js";
2
+ import { SubprocessDriver } from "./claude/subprocess.js";
3
+ import { GeminiSubprocessDriver } from "./gemini/index.js";
4
+ import { GrokApiDriver } from "./grok/index.js";
5
+ import { OpenAIApiDriver } from "./openai/index.js";
6
+ /**
7
+ * Create the appropriate driver from a mode string.
8
+ * Returns null for "none" (orchestration disabled).
9
+ */
10
+ export function createDriver(mode, opts, log) {
11
+ if (mode === "none")
12
+ return null;
13
+ if (mode === "subprocess")
14
+ return new SubprocessDriver(opts.binary, opts.antBinary, log);
15
+ if (mode === "api")
16
+ return new ApiDriver(log);
17
+ if (mode === "openai")
18
+ return new OpenAIApiDriver(log);
19
+ if (mode === "grok")
20
+ return new GrokApiDriver(log);
21
+ if (mode === "gemini")
22
+ return new GeminiSubprocessDriver(opts.binary === "claude" ? "gemini" : opts.binary, log);
23
+ throw new Error(`Unknown driver mode: ${mode}`);
24
+ }
25
+ export { ApiDriver } from "./claude/api.js";
26
+ export { SubprocessDriver } from "./claude/subprocess.js";
27
+ export { GeminiSubprocessDriver } from "./gemini/index.js";
28
+ export { GrokApiDriver } from "./grok/index.js";
29
+ export { OpenAIApiDriver } from "./openai/index.js";
30
+ export { toProviderTaskOutcome } from "./types.js";
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/drivers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAgBpD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAgB,EAChB,IAAuB,EACvB,GAA0B;IAE1B,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACjC,IAAI,IAAI,KAAK,YAAY;QACvB,OAAO,IAAI,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAChE,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC;IAC9C,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,eAAe,CAAC,GAAG,CAAC,CAAC;IACvD,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,IAAI,aAAa,CAAC,GAAG,CAAC,CAAC;IACnD,IAAI,IAAI,KAAK,QAAQ;QACnB,OAAO,IAAI,sBAAsB,CAC/B,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,EACjD,GAAG,CACJ,CAAC;IACJ,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,EAAE,CAAC,CAAC;AAClD,CAAC;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAOpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { ProviderDriver, ProviderTaskInput, ProviderTaskResult } from "../types.js";
2
+ export interface OpenAIDriverOpts {
3
+ /** Override API base URL — used by Grok and other OpenAI-compatible endpoints. */
4
+ baseURL?: string;
5
+ /** Default model when input.model is not set. */
6
+ defaultModel?: string;
7
+ }
8
+ /**
9
+ * OpenAI API driver — streams via chat.completions.create.
10
+ * Dynamic import: openai package is an optional peer dep (not in package.json).
11
+ * Install: npm install openai
12
+ *
13
+ * Limitation: single-turn only — no agentic tool-use loop.
14
+ * providerOptions: { maxTokens?: number, temperature?: number }
15
+ */
16
+ export declare class OpenAIApiDriver implements ProviderDriver {
17
+ private readonly log;
18
+ private readonly opts;
19
+ readonly name: string;
20
+ constructor(log: (msg: string) => void, opts?: OpenAIDriverOpts);
21
+ run(input: ProviderTaskInput): Promise<ProviderTaskResult>;
22
+ /** Override in subclasses to use a different env var. */
23
+ protected apiKey(): string | undefined;
24
+ }
@@ -0,0 +1,110 @@
1
+ const OUTPUT_CAP = 50 * 1024;
2
+ /**
3
+ * OpenAI API driver — streams via chat.completions.create.
4
+ * Dynamic import: openai package is an optional peer dep (not in package.json).
5
+ * Install: npm install openai
6
+ *
7
+ * Limitation: single-turn only — no agentic tool-use loop.
8
+ * providerOptions: { maxTokens?: number, temperature?: number }
9
+ */
10
+ export class OpenAIApiDriver {
11
+ log;
12
+ opts;
13
+ name = "openai";
14
+ constructor(log, opts = {}) {
15
+ this.log = log;
16
+ this.opts = opts;
17
+ if (!process.env.OPENAI_API_KEY && !opts.baseURL) {
18
+ throw new Error("OpenAIApiDriver requires OPENAI_API_KEY environment variable");
19
+ }
20
+ }
21
+ async run(input) {
22
+ // biome-ignore lint/suspicious/noExplicitAny: dynamic import of optional peer dep
23
+ let OpenAICtor;
24
+ try {
25
+ // biome-ignore lint/suspicious/noExplicitAny: dynamic import
26
+ const mod = await import("openai");
27
+ // biome-ignore lint/suspicious/noExplicitAny: dynamic import
28
+ OpenAICtor = mod.default ?? mod.OpenAI ?? mod;
29
+ }
30
+ catch {
31
+ throw new Error("OpenAIApiDriver requires openai — install it with: npm install openai");
32
+ }
33
+ const opts = input.providerOptions ?? {};
34
+ const maxTokens = typeof opts.maxTokens === "number" ? opts.maxTokens : 4096;
35
+ const temperature = typeof opts.temperature === "number" ? opts.temperature : undefined;
36
+ const contextNote = input.contextFiles && input.contextFiles.length > 0
37
+ ? `\n\n--- BEGIN CONTEXT FILE LIST ---\n${input.contextFiles
38
+ .map((f) => f.slice(0, 500).replace(/[\x00-\x1f\x7f]/g, ""))
39
+ .join("\n")}\n--- END CONTEXT FILE LIST ---`
40
+ : "";
41
+ const messages = [];
42
+ if (input.systemPrompt) {
43
+ messages.push({ role: "system", content: input.systemPrompt });
44
+ }
45
+ messages.push({ role: "user", content: input.prompt + contextNote });
46
+ const clientOpts = {};
47
+ if (this.opts.baseURL)
48
+ clientOpts.baseURL = this.opts.baseURL;
49
+ // Use provider-specific API key env var if set, fall back to OPENAI_API_KEY
50
+ const apiKey = this.apiKey();
51
+ if (apiKey)
52
+ clientOpts.apiKey = apiKey;
53
+ const client = new OpenAICtor(clientOpts);
54
+ const start = Date.now();
55
+ const model = input.model ?? this.opts.defaultModel ?? "gpt-4o";
56
+ this.log(`[${this.name}] streaming: model=${model} workspace=${input.workspace}`);
57
+ let text = "";
58
+ let firstChunkAt;
59
+ try {
60
+ // biome-ignore lint/suspicious/noExplicitAny: dynamic import shape
61
+ const stream = await client.chat.completions.create({
62
+ model,
63
+ max_tokens: maxTokens,
64
+ ...(temperature !== undefined ? { temperature } : {}),
65
+ messages,
66
+ stream: true,
67
+ }, { signal: input.signal });
68
+ // biome-ignore lint/suspicious/noExplicitAny: stream shape from dynamic import
69
+ for await (const chunk of stream) {
70
+ const delta = chunk.choices?.[0]?.delta?.content ?? "";
71
+ if (delta) {
72
+ if (firstChunkAt === undefined)
73
+ firstChunkAt = Date.now();
74
+ text += delta;
75
+ if (text.length <= OUTPUT_CAP) {
76
+ input.onChunk?.(delta);
77
+ }
78
+ }
79
+ }
80
+ }
81
+ catch (err) {
82
+ const isAbort = (err instanceof Error && err.name === "AbortError") ||
83
+ input.signal.aborted;
84
+ if (isAbort) {
85
+ return {
86
+ text: text.slice(0, OUTPUT_CAP),
87
+ durationMs: Date.now() - start,
88
+ wasAborted: true,
89
+ startupMs: firstChunkAt !== undefined ? firstChunkAt - start : undefined,
90
+ };
91
+ }
92
+ return {
93
+ text: text.slice(0, OUTPUT_CAP),
94
+ durationMs: Date.now() - start,
95
+ errorMessage: err instanceof Error ? err.message : String(err),
96
+ };
97
+ }
98
+ return {
99
+ text: text.slice(0, OUTPUT_CAP),
100
+ durationMs: Date.now() - start,
101
+ startupMs: firstChunkAt !== undefined ? firstChunkAt - start : undefined,
102
+ providerMeta: { model },
103
+ };
104
+ }
105
+ /** Override in subclasses to use a different env var. */
106
+ apiKey() {
107
+ return process.env.OPENAI_API_KEY;
108
+ }
109
+ }
110
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/drivers/openai/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Provider-neutral driver types.
3
+ * All drivers implement ProviderDriver; Claude-specific fields go in providerOptions.
4
+ */
5
+ export interface ProviderTaskInput {
6
+ prompt: string;
7
+ /** Working directory / context hint passed as cwd to the subprocess or API call. */
8
+ workspace: string;
9
+ timeoutMs: number;
10
+ signal: AbortSignal;
11
+ onChunk?: (chunk: string) => void;
12
+ /** Informational list of paths; driver decides how to surface them. */
13
+ contextFiles?: string[];
14
+ /** Provider-specific model ID (e.g. "claude-sonnet-4-6", "gemini-2.5-pro"). */
15
+ model?: string;
16
+ systemPrompt?: string;
17
+ /** Startup timeout: abort if no output arrives within this many ms of spawn. */
18
+ startupTimeoutMs?: number;
19
+ /**
20
+ * Provider-specific overrides — driver may ignore unknown keys.
21
+ * Claude subprocess: { effort, fallbackModel, maxBudgetUsd, useAnt }
22
+ * Gemini subprocess: { binary }
23
+ * OpenAI API: { maxTokens, temperature }
24
+ */
25
+ providerOptions?: Record<string, unknown>;
26
+ }
27
+ export interface ProviderTaskResult {
28
+ text: string;
29
+ durationMs: number;
30
+ startupMs?: number;
31
+ wasAborted?: boolean;
32
+ /** Set when the provider signals an error (replaces exitCode for API drivers). */
33
+ errorMessage?: string;
34
+ /** Tokens used, resolved model, etc. Driver-specific. */
35
+ providerMeta?: Record<string, unknown>;
36
+ exitCode?: number;
37
+ stderrTail?: string;
38
+ startupTimedOut?: boolean;
39
+ }
40
+ export type ProviderTaskOutcome = {
41
+ outcome: "done";
42
+ text: string;
43
+ durationMs: number;
44
+ startupMs?: number;
45
+ providerMeta?: Record<string, unknown>;
46
+ } | {
47
+ outcome: "error";
48
+ errorMessage: string;
49
+ durationMs: number;
50
+ } | {
51
+ outcome: "aborted";
52
+ cancelKind: "startup_timeout" | "timeout" | "user";
53
+ durationMs: number;
54
+ };
55
+ export interface ProviderDriver {
56
+ readonly name: string;
57
+ /** Primary entry point. Must resolve; never reject (swallow errors into result). */
58
+ run(input: ProviderTaskInput): Promise<ProviderTaskResult>;
59
+ /** Optional: discriminated-union variant. Default impl wraps run(). */
60
+ runOutcome?(input: ProviderTaskInput): Promise<ProviderTaskOutcome>;
61
+ /** Optional: long-lived session lifecycle (server-mode drivers). */
62
+ spawnForSession?(sessionId: string): Promise<void>;
63
+ killForSession?(sessionId: string): void;
64
+ /** Called once on bridge shutdown. Clean up connections, temp files. */
65
+ destroy?(): Promise<void>;
66
+ }
67
+ export declare function toProviderTaskOutcome(result: ProviderTaskResult, cancelReason?: "timeout" | "startup_timeout" | "user" | "shutdown"): ProviderTaskOutcome;
68
+ /**
69
+ * @deprecated Use ProviderDriver. IClaudeDriver is a backward-compat alias.
70
+ * Will be removed in a future minor version.
71
+ */
72
+ export type IClaudeDriver = ProviderDriver;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Provider-neutral driver types.
3
+ * All drivers implement ProviderDriver; Claude-specific fields go in providerOptions.
4
+ */
5
+ export function toProviderTaskOutcome(result, cancelReason) {
6
+ if (result.wasAborted) {
7
+ const cancelKind = result.startupTimedOut || cancelReason === "startup_timeout"
8
+ ? "startup_timeout"
9
+ : cancelReason === "timeout"
10
+ ? "timeout"
11
+ : "user";
12
+ return { outcome: "aborted", cancelKind, durationMs: result.durationMs };
13
+ }
14
+ if (result.errorMessage ||
15
+ (result.exitCode !== undefined && result.exitCode !== 0)) {
16
+ return {
17
+ outcome: "error",
18
+ errorMessage: result.errorMessage ?? `exit code ${result.exitCode}`,
19
+ durationMs: result.durationMs,
20
+ };
21
+ }
22
+ return {
23
+ outcome: "done",
24
+ text: result.text,
25
+ durationMs: result.durationMs,
26
+ startupMs: result.startupMs,
27
+ providerMeta: result.providerMeta,
28
+ };
29
+ }
30
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/drivers/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAoEH,MAAM,UAAU,qBAAqB,CACnC,MAA0B,EAC1B,YAAkE;IAElE,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACtB,MAAM,UAAU,GACd,MAAM,CAAC,eAAe,IAAI,YAAY,KAAK,iBAAiB;YAC1D,CAAC,CAAC,iBAAiB;YACnB,CAAC,CAAC,YAAY,KAAK,SAAS;gBAC1B,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,MAAM,CAAC;QACf,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;IAC3E,CAAC;IACD,IACE,MAAM,CAAC,YAAY;QACnB,CAAC,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,CAAC,EACxD,CAAC;QACD,OAAO;YACL,OAAO,EAAE,OAAO;YAChB,YAAY,EAAE,MAAM,CAAC,YAAY,IAAI,aAAa,MAAM,CAAC,QAAQ,EAAE;YACnE,UAAU,EAAE,MAAM,CAAC,UAAU;SAC9B,CAAC;IACJ,CAAC;IACD,OAAO;QACL,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,YAAY,EAAE,MAAM,CAAC,YAAY;KAClC,CAAC;AACJ,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,24 @@
1
1
  #!/usr/bin/env node
2
+ // Load .env from repo root if present (connector credentials, etc.).
3
+ // Uses Node 20.6+ native dotenv loader; falls back to manual parse for older Node.
4
+ {
5
+ const { fileURLToPath: _fileURLToPath } = await import("node:url");
6
+ const envPath = _fileURLToPath(new URL("../../.env", import.meta.url));
7
+ try {
8
+ const { readFileSync, existsSync } = await import("node:fs");
9
+ if (existsSync(envPath)) {
10
+ for (const line of readFileSync(envPath, "utf-8").split("\n")) {
11
+ const m = /^([A-Z_][A-Z0-9_]*)=(.*)$/.exec(line.trim());
12
+ if (m?.[1] && !process.env[m[1]]) {
13
+ process.env[m[1]] = m[2]?.replace(/^["']|["']$/g, "");
14
+ }
15
+ }
16
+ }
17
+ }
18
+ catch {
19
+ /* non-fatal */
20
+ }
21
+ }
2
22
  // Enable V8 compile cache for faster cold-start on repeated restarts (Node 22.8+).
3
23
  import nodeModule from "node:module";
4
24
  if (typeof nodeModule.enableCompileCache === "function") {
@@ -547,39 +567,99 @@ export function register(ctx) {
547
567
  process.stderr.write(` 3. Or add to your config: { "plugins": ["${outDir}"] }\n`);
548
568
  process.exit(0);
549
569
  }
550
- // Patchwork: `patchwork recipe run <name>` POSTs to a running bridge's
551
- // /recipes/run endpoint to enqueue the recipe via the Claude orchestrator.
570
+ // Patchwork: `patchwork recipe list`enumerate installed recipes.
571
+ if (process.argv[2] === "recipe" && process.argv[3] === "list") {
572
+ (async () => {
573
+ const { listYamlRecipes } = await import("./recipes/yamlRunner.js");
574
+ const recipesDir = path.join(os.homedir(), ".patchwork", "recipes");
575
+ const recipes = listYamlRecipes(recipesDir);
576
+ if (recipes.length === 0) {
577
+ process.stdout.write("No recipes installed. Run `patchwork-os patchwork-init` to install the starter set.\n");
578
+ }
579
+ else {
580
+ process.stdout.write(`Installed recipes (${recipes.length}):\n\n`);
581
+ for (const r of recipes) {
582
+ const desc = r.description ? ` ${r.description}` : "";
583
+ process.stdout.write(` ${r.name.padEnd(28)} [${r.trigger}]${desc}\n`);
584
+ }
585
+ process.stdout.write(`\nRun a recipe: patchwork-os recipe run <name>\n`);
586
+ }
587
+ process.exit(0);
588
+ })();
589
+ }
590
+ // Patchwork: `patchwork recipe run <name>` — runs a recipe locally or via
591
+ // a running bridge's /recipes/run endpoint if one is available.
552
592
  if (process.argv[2] === "recipe" && process.argv[3] === "run") {
553
- const name = process.argv[4];
593
+ const args = process.argv.slice(4);
594
+ const localFlag = args.includes("--local");
595
+ const name = args.find((a) => !a.startsWith("--"));
554
596
  if (!name) {
555
- process.stderr.write("Usage: patchwork recipe run <name>\n");
597
+ process.stderr.write("Usage: patchwork recipe run <name> [--local]\n");
556
598
  process.exit(1);
557
599
  }
558
600
  (async () => {
559
601
  try {
602
+ // Try bridge first (requires --claude-driver subprocess).
560
603
  const { findBridgeLock } = await import("./bridgeLockDiscovery.js");
561
- const lock = findBridgeLock();
562
- if (!lock) {
563
- process.stderr.write("Error: no running bridge found under ~/.claude/ide/. Start the bridge with --claude-driver subprocess first.\n");
564
- process.exit(1);
565
- return;
604
+ const lock = localFlag ? null : findBridgeLock();
605
+ if (lock) {
606
+ const res = await fetch(`http://127.0.0.1:${lock.port}/recipes/run`, {
607
+ method: "POST",
608
+ headers: {
609
+ Authorization: `Bearer ${lock.authToken}`,
610
+ "Content-Type": "application/json",
611
+ },
612
+ body: JSON.stringify({ name }),
613
+ });
614
+ const body = (await res.json());
615
+ if (!body.ok) {
616
+ // Fall through to local YAML runner if bridge doesn't know the recipe.
617
+ if (!(body.error ?? "").includes("not found")) {
618
+ process.stderr.write(`Error: ${body.error ?? "unknown"}\n`);
619
+ process.exit(1);
620
+ return;
621
+ }
622
+ // else: fall through to local runner below
623
+ }
624
+ else {
625
+ process.stdout.write(` ✓ enqueued recipe "${name}" as task ${(body.taskId ?? "").slice(0, 8)}\n` +
626
+ " Watch progress on the dashboard Tasks page or via listClaudeTasks.\n");
627
+ process.exit(0);
628
+ return;
629
+ }
566
630
  }
567
- const res = await fetch(`http://127.0.0.1:${lock.port}/recipes/run`, {
568
- method: "POST",
569
- headers: {
570
- Authorization: `Bearer ${lock.authToken}`,
571
- "Content-Type": "application/json",
572
- },
573
- body: JSON.stringify({ name }),
574
- });
575
- const body = (await res.json());
576
- if (!body.ok) {
577
- process.stderr.write(`Error: ${body.error ?? "unknown"}\n`);
631
+ // No bridge run locally using the YAML runner.
632
+ const { loadYamlRecipe, runYamlRecipe } = await import("./recipes/yamlRunner.js");
633
+ const recipesDir = path.join(os.homedir(), ".patchwork", "recipes");
634
+ const bundledDir = fileURLToPath(new URL("../templates/recipes", import.meta.url));
635
+ const candidates = [
636
+ path.join(recipesDir, `${name}.yaml`),
637
+ path.join(recipesDir, `${name}.yml`),
638
+ path.join(recipesDir, `${name}.json`),
639
+ path.join(bundledDir, `${name}.yaml`),
640
+ path.join(bundledDir, `${name}.yml`),
641
+ ];
642
+ let recipePath;
643
+ for (const c of candidates) {
644
+ if (existsSync(c)) {
645
+ recipePath = c;
646
+ break;
647
+ }
648
+ }
649
+ if (!recipePath) {
650
+ process.stderr.write(`Error: recipe "${name}" not found in ${recipesDir}\n` +
651
+ " Run `patchwork-os recipe list` to see available recipes.\n");
578
652
  process.exit(1);
579
653
  return;
580
654
  }
581
- process.stdout.write(` enqueued recipe "${name}" as task ${(body.taskId ?? "").slice(0, 8)}\n` +
582
- " Watch progress on the dashboard Tasks page or via listClaudeTasks.\n");
655
+ process.stdout.write(` Running recipe "${name}" locally…\n`);
656
+ const recipe = loadYamlRecipe(recipePath);
657
+ const workdir = lock?.workspace || process.cwd();
658
+ const result = await runYamlRecipe(recipe, { workdir });
659
+ process.stdout.write(` ✓ ${result.stepsRun} step(s) completed\n`);
660
+ if (result.outputs.length > 0) {
661
+ process.stdout.write(` Output written to:\n${result.outputs.map((o) => ` ${o}`).join("\n")}\n`);
662
+ }
583
663
  process.exit(0);
584
664
  }
585
665
  catch (err) {
@@ -1304,6 +1384,19 @@ Options:
1304
1384
  process.exit(0);
1305
1385
  }
1306
1386
  // F6: "Did you mean?" for unknown CLI subcommands
1387
+ // Patchwork: no-args → terminal dashboard (when invoked as patchwork-os or patchwork).
1388
+ {
1389
+ const binName = path.basename(process.argv[1] ?? "");
1390
+ const isPatchworkBin = binName === "patchwork-os" ||
1391
+ binName === "patchwork" ||
1392
+ binName === "patchwork.js";
1393
+ if (isPatchworkBin && !process.argv[2]) {
1394
+ (async () => {
1395
+ const { runDashboard } = await import("./commands/dashboard.js");
1396
+ await runDashboard();
1397
+ })();
1398
+ }
1399
+ }
1307
1400
  {
1308
1401
  const KNOWN_COMMANDS = [
1309
1402
  "init",
@@ -1319,6 +1412,7 @@ Options:
1319
1412
  "status",
1320
1413
  "shim",
1321
1414
  "recipe",
1415
+ "dashboard",
1322
1416
  ];
1323
1417
  const unknownSub = process.argv[2];
1324
1418
  if (unknownSub &&