patchwork-os 0.2.0-alpha.0 → 0.2.0-alpha.11
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.
- package/README.md +41 -46
- package/dist/bridge.js +23 -10
- package/dist/bridge.js.map +1 -1
- package/dist/claudeDriver.d.ts +3 -1
- package/dist/claudeDriver.js +48 -0
- package/dist/claudeDriver.js.map +1 -1
- package/dist/commands/dashboard.d.ts +47 -0
- package/dist/commands/dashboard.js +319 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.js +5 -2
- package/dist/config.js.map +1 -1
- package/dist/connectors/github.d.ts +94 -0
- package/dist/connectors/github.js +350 -0
- package/dist/connectors/github.js.map +1 -0
- package/dist/connectors/gmail.d.ts +40 -0
- package/dist/connectors/gmail.js +304 -0
- package/dist/connectors/gmail.js.map +1 -0
- package/dist/connectors/googleCalendar.d.ts +57 -0
- package/dist/connectors/googleCalendar.js +308 -0
- package/dist/connectors/googleCalendar.js.map +1 -0
- package/dist/connectors/linear.d.ts +117 -0
- package/dist/connectors/linear.js +248 -0
- package/dist/connectors/linear.js.map +1 -0
- package/dist/connectors/mcpClient.d.ts +56 -0
- package/dist/connectors/mcpClient.js +189 -0
- package/dist/connectors/mcpClient.js.map +1 -0
- package/dist/connectors/mcpOAuth.d.ts +83 -0
- package/dist/connectors/mcpOAuth.js +363 -0
- package/dist/connectors/mcpOAuth.js.map +1 -0
- package/dist/connectors/sentry.d.ts +43 -0
- package/dist/connectors/sentry.js +197 -0
- package/dist/connectors/sentry.js.map +1 -0
- package/dist/connectors/slack.d.ts +50 -0
- package/dist/connectors/slack.js +254 -0
- package/dist/connectors/slack.js.map +1 -0
- package/dist/drivers/claude/api.d.ts +11 -0
- package/dist/drivers/claude/api.js +54 -0
- package/dist/drivers/claude/api.js.map +1 -0
- package/dist/drivers/claude/envSanitizer.d.ts +7 -0
- package/dist/drivers/claude/envSanitizer.js +18 -0
- package/dist/drivers/claude/envSanitizer.js.map +1 -0
- package/dist/drivers/claude/streamParser.d.ts +38 -0
- package/dist/drivers/claude/streamParser.js +34 -0
- package/dist/drivers/claude/streamParser.js.map +1 -0
- package/dist/drivers/claude/subprocess.d.ts +19 -0
- package/dist/drivers/claude/subprocess.js +216 -0
- package/dist/drivers/claude/subprocess.js.map +1 -0
- package/dist/drivers/claude/subprocessSettings.d.ts +9 -0
- package/dist/drivers/claude/subprocessSettings.js +55 -0
- package/dist/drivers/claude/subprocessSettings.js.map +1 -0
- package/dist/drivers/gemini/index.d.ts +14 -0
- package/dist/drivers/gemini/index.js +176 -0
- package/dist/drivers/gemini/index.js.map +1 -0
- package/dist/drivers/grok/index.d.ts +11 -0
- package/dist/drivers/grok/index.js +22 -0
- package/dist/drivers/grok/index.js.map +1 -0
- package/dist/drivers/index.d.ts +18 -0
- package/dist/drivers/index.js +31 -0
- package/dist/drivers/index.js.map +1 -0
- package/dist/drivers/openai/index.d.ts +24 -0
- package/dist/drivers/openai/index.js +110 -0
- package/dist/drivers/openai/index.js.map +1 -0
- package/dist/drivers/types.d.ts +72 -0
- package/dist/drivers/types.js +30 -0
- package/dist/drivers/types.js.map +1 -0
- package/dist/index.js +116 -22
- package/dist/index.js.map +1 -1
- package/dist/recipes/yamlRunner.d.ts +104 -0
- package/dist/recipes/yamlRunner.js +683 -0
- package/dist/recipes/yamlRunner.js.map +1 -0
- package/dist/recipesHttp.d.ts +13 -1
- package/dist/recipesHttp.js +9 -1
- package/dist/recipesHttp.js.map +1 -1
- package/dist/runLog.d.ts +5 -0
- package/dist/runLog.js +44 -0
- package/dist/runLog.js.map +1 -1
- package/dist/server.d.ts +3 -1
- package/dist/server.js +490 -2
- package/dist/server.js.map +1 -1
- package/dist/tools/addLinearComment.d.ts +55 -0
- package/dist/tools/addLinearComment.js +70 -0
- package/dist/tools/addLinearComment.js.map +1 -0
- package/dist/tools/createLinearIssue.d.ts +84 -0
- package/dist/tools/createLinearIssue.js +146 -0
- package/dist/tools/createLinearIssue.js.map +1 -0
- package/dist/tools/ctxGetTaskContext.d.ts +4 -1
- package/dist/tools/ctxGetTaskContext.js +45 -2
- package/dist/tools/ctxGetTaskContext.js.map +1 -1
- package/dist/tools/fetchCalendarEvents.d.ts +94 -0
- package/dist/tools/fetchCalendarEvents.js +97 -0
- package/dist/tools/fetchCalendarEvents.js.map +1 -0
- package/dist/tools/fetchGithubIssue.d.ts +80 -0
- package/dist/tools/fetchGithubIssue.js +84 -0
- package/dist/tools/fetchGithubIssue.js.map +1 -0
- package/dist/tools/fetchGithubPR.d.ts +89 -0
- package/dist/tools/fetchGithubPR.js +96 -0
- package/dist/tools/fetchGithubPR.js.map +1 -0
- package/dist/tools/fetchLinearIssue.d.ts +112 -0
- package/dist/tools/fetchLinearIssue.js +129 -0
- package/dist/tools/fetchLinearIssue.js.map +1 -0
- package/dist/tools/fetchSentryIssue.d.ts +143 -0
- package/dist/tools/fetchSentryIssue.js +150 -0
- package/dist/tools/fetchSentryIssue.js.map +1 -0
- package/dist/tools/fetchSlackProfile.d.ts +43 -0
- package/dist/tools/fetchSlackProfile.js +43 -0
- package/dist/tools/fetchSlackProfile.js.map +1 -0
- package/dist/tools/getConnectorStatus.d.ts +58 -0
- package/dist/tools/getConnectorStatus.js +56 -0
- package/dist/tools/getConnectorStatus.js.map +1 -0
- package/dist/tools/github/index.d.ts +1 -1
- package/dist/tools/github/index.js +1 -1
- package/dist/tools/github/index.js.map +1 -1
- package/dist/tools/github/pr.d.ts +122 -0
- package/dist/tools/github/pr.js +152 -0
- package/dist/tools/github/pr.js.map +1 -1
- package/dist/tools/index.js +27 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/slackListChannels.d.ts +65 -0
- package/dist/tools/slackListChannels.js +70 -0
- package/dist/tools/slackListChannels.js.map +1 -0
- package/dist/tools/slackPostMessage.d.ts +57 -0
- package/dist/tools/slackPostMessage.js +72 -0
- package/dist/tools/slackPostMessage.js.map +1 -0
- package/dist/tools/updateLinearIssue.d.ts +89 -0
- package/dist/tools/updateLinearIssue.js +103 -0
- package/dist/tools/updateLinearIssue.js.map +1 -0
- package/package.json +1 -1
- package/scripts/start-all.sh +56 -19
- package/templates/recipes/ctx-loop-test.yaml +75 -0
- package/templates/recipes/gmail-health-check.yaml +19 -0
- package/templates/recipes/inbox-triage.yaml +15 -0
- package/templates/recipes/morning-brief-slack.yaml +54 -0
- package/templates/recipes/morning-brief.yaml +72 -0
- package/templates/recipes/sentry-to-linear.yaml +77 -0
- 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":"AAMA,MAAM,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC;AAS7B;;;;;;;GAOG;AACH,MAAM,OAAO,eAAe;IAIP;IACA;IAJV,IAAI,GAAW,QAAQ,CAAC;IAEjC,YACmB,GAA0B,EAC1B,OAAyB,EAAE;QAD3B,QAAG,GAAH,GAAG,CAAuB;QAC1B,SAAI,GAAJ,IAAI,CAAuB;QAE5C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YACjD,MAAM,IAAI,KAAK,CACb,8DAA8D,CAC/D,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,KAAwB;QAChC,kFAAkF;QAClF,IAAI,UAAkC,CAAC;QACvC,IAAI,CAAC;YACH,6DAA6D;YAC7D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,QAAe,CAAC,CAAC;YAC1C,6DAA6D;YAC7D,UAAU,GAAI,GAAW,CAAC,OAAO,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CACb,uEAAuE,CACxE,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,KAAK,CAAC,eAAe,IAAI,EAAE,CAAC;QACzC,MAAM,SAAS,GACb,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;QAC7D,MAAM,WAAW,GACf,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;QAEtE,MAAM,WAAW,GACf,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC;YACjD,CAAC,CAAC,wCAAwC,KAAK,CAAC,YAAY;iBACvD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;iBAC3D,IAAI,CAAC,IAAI,CAAC,iCAAiC;YAChD,CAAC,CAAC,EAAE,CAAC;QAET,MAAM,QAAQ,GAA6C,EAAE,CAAC;QAC9D,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;QACjE,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC,CAAC;QAErE,MAAM,UAAU,GAA4B,EAAE,CAAC;QAC/C,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;QAC9D,4EAA4E;QAC5E,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC7B,IAAI,MAAM;YAAE,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC;QAEvC,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,QAAQ,CAAC;QAEhE,IAAI,CAAC,GAAG,CACN,IAAI,IAAI,CAAC,IAAI,sBAAsB,KAAK,cAAc,KAAK,CAAC,SAAS,EAAE,CACxE,CAAC;QAEF,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,YAAgC,CAAC;QAErC,IAAI,CAAC;YACH,mEAAmE;YACnE,MAAM,MAAM,GAAG,MAAO,MAAM,CAAC,IAAI,CAAC,WAAmB,CAAC,MAAM,CAC1D;gBACE,KAAK;gBACL,UAAU,EAAE,SAAS;gBACrB,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrD,QAAQ;gBACR,MAAM,EAAE,IAAI;aACb,EACD,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CACzB,CAAC;YAEF,+EAA+E;YAC/E,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAA4B,EAAE,CAAC;gBACvD,MAAM,KAAK,GAAW,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,IAAI,EAAE,CAAC;gBAC/D,IAAI,KAAK,EAAE,CAAC;oBACV,IAAI,YAAY,KAAK,SAAS;wBAAE,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;oBAC1D,IAAI,IAAI,KAAK,CAAC;oBACd,IAAI,IAAI,CAAC,MAAM,IAAI,UAAU,EAAE,CAAC;wBAC9B,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;oBACzB,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GACX,CAAC,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,CAAC;gBACnD,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC;YACvB,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO;oBACL,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC;oBAC/B,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;oBAC9B,UAAU,EAAE,IAAI;oBAChB,SAAS,EACP,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS;iBAChE,CAAC;YACJ,CAAC;YACD,OAAO;gBACL,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC;gBAC/B,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;gBAC9B,YAAY,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aAC/D,CAAC;QACJ,CAAC;QAED,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC;YAC/B,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;YAC9B,SAAS,EAAE,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS;YACxE,YAAY,EAAE,EAAE,KAAK,EAAE;SACxB,CAAC;IACJ,CAAC;IAED,yDAAyD;IAC/C,MAAM;QACd,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IACpC,CAAC;CACF"}
|
|
@@ -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
|
|
551
|
-
|
|
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
|
|
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
|
|
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 (
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
},
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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(`
|
|
582
|
-
|
|
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 &&
|