@backburner/cli 0.1.1 → 0.1.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.
- package/README.md +21 -19
- package/dist/src/cli/commands/broker-smoke.js +2 -3
- package/dist/src/cli/commands/broker.js +2 -3
- package/dist/src/cli/commands/journal.js +2 -3
- package/dist/src/cli/commands/tui.js +8 -9
- package/dist/src/cli/init.js +9 -7
- package/dist/src/cli/runtime/run-context.js +6 -6
- package/dist/src/onboarding/config-writer.js +10 -9
- package/dist/src/onboarding/services/label-provisioner.js +39 -0
- package/dist/src/onboarding/steps/configure-paths.js +5 -5
- package/dist/src/onboarding/steps/generate-config.js +3 -1
- package/dist/src/onboarding/steps/provision-labels.js +104 -0
- package/dist/src/onboarding/steps/select-repos.js +39 -21
- package/dist/src/onboarding/steps/show-summary.js +54 -4
- package/dist/src/onboarding/steps/welcome.js +2 -1
- package/dist/src/utils/paths.js +13 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Backburner is a local GitHub-driven agent orchestration CLI. It uses GitHub labels, issues, and pull requests as the control plane, keeps configuration and state as readable JSON on your machine, and dispatches eligible work to provider CLIs that you already have installed locally.
|
|
4
4
|
|
|
5
|
-
Backburner
|
|
5
|
+
Backburner supports:
|
|
6
6
|
|
|
7
7
|
- `backburner init` for interactive or non-interactive local setup
|
|
8
8
|
- `backburner run` for one orchestration cycle
|
|
@@ -75,7 +75,7 @@ npm run install:local -- --prefix "$env:TEMP\backburner-local"
|
|
|
75
75
|
& "$env:TEMP\backburner-local\backburner.cmd" journal --json
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
Install from npm:
|
|
79
79
|
|
|
80
80
|
```sh
|
|
81
81
|
npm install -g @backburner/cli
|
|
@@ -117,14 +117,16 @@ Useful `init` flags:
|
|
|
117
117
|
--yes, -y Accept defaults without prompts
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
`backburner init` checks local requirements, confirms `gh auth status`, discovers repositories, detects provider CLIs, attempts model-list discovery when supported by those CLIs,
|
|
120
|
+
`backburner init` checks local requirements, confirms `gh auth status`, discovers repositories, detects provider CLIs, attempts model-list discovery when supported by those CLIs, writes configuration, and creates the configured Backburner labels on each selected repository.
|
|
121
121
|
|
|
122
122
|
- `repos.json`
|
|
123
123
|
- `agents.json`
|
|
124
124
|
|
|
125
|
-
If either file already exists, onboarding asks before overwriting it. With `--yes`, defaults are accepted non-interactively, including selecting existing discovered repositories, enabling every discovered provider,
|
|
125
|
+
If either file already exists, onboarding asks before overwriting it. With `--yes`, defaults are accepted non-interactively, including selecting existing discovered repositories, enabling every discovered provider, creating strong and cheap agents for each one, and creating or updating labels on selected repositories. `--yes` does not create a private demo repository.
|
|
126
126
|
|
|
127
|
-
Repository setup first asks whether to
|
|
127
|
+
Repository setup first asks whether to create a private Backburner demo repository, select existing repositories, or skip repository setup for now. When GitHub authentication is available, the private demo repository is the interactive default. Before choosing existing repositories, onboarding tells you that Backburner will create or update its labels on those repositories. In an interactive terminal, repository and provider selection use a checkbox picker: arrow keys move, space toggles, and enter confirms. Non-TTY sessions fall back to text input and accept comma-separated numbers. If you are authenticated with GitHub but do not want to onboard an existing local clone yet, onboarding can explicitly create a private `backburner-demo` repository and clone it under the configured code root.
|
|
128
|
+
|
|
129
|
+
Label creation is best-effort. If GitHub rejects a label write because of permissions or repository policy, onboarding continues and prints the repository labels URL plus the exact label names to create manually. The final summary prints the next `backburner run` command, a prefilled GitHub issue URL for the first configured repository, and a terminal QR code for opening that repository's issues page from a phone.
|
|
128
130
|
|
|
129
131
|
## Default Directories
|
|
130
132
|
|
|
@@ -160,13 +162,13 @@ Minimal `repos.json`:
|
|
|
160
162
|
"localPath": "your-github-user/example-repo",
|
|
161
163
|
"enabled": true,
|
|
162
164
|
"labels": {
|
|
163
|
-
"agentInProgress": "
|
|
164
|
-
"agentProductApproved": "
|
|
165
|
-
"agentPlanApproved": "
|
|
166
|
-
"agentReview": "
|
|
167
|
-
"planBreakdownNeeded": "plan-breakdown-needed",
|
|
168
|
-
"agentSyncParentBranch": "
|
|
169
|
-
"prepareForMerge": "prepare-for-merge"
|
|
165
|
+
"agentInProgress": "backburner:in-progress",
|
|
166
|
+
"agentProductApproved": "backburner:product-approved",
|
|
167
|
+
"agentPlanApproved": "backburner:plan-approved",
|
|
168
|
+
"agentReview": "backburner:review",
|
|
169
|
+
"planBreakdownNeeded": "backburner:plan-breakdown-needed",
|
|
170
|
+
"agentSyncParentBranch": "backburner:sync-parent-branch",
|
|
171
|
+
"prepareForMerge": "backburner:prepare-for-merge"
|
|
170
172
|
}
|
|
171
173
|
}
|
|
172
174
|
]
|
|
@@ -215,14 +217,14 @@ Agent selection filters enabled agents by required role and `modelClass`, then b
|
|
|
215
217
|
|
|
216
218
|
## Label Control Plane
|
|
217
219
|
|
|
218
|
-
Configure labels per repository in `repos.json`. The
|
|
220
|
+
Configure labels per repository in `repos.json`. The workflow labels are:
|
|
219
221
|
|
|
220
|
-
- `
|
|
221
|
-
- `
|
|
222
|
-
- `
|
|
223
|
-
- `
|
|
222
|
+
- `agentProductApproved`: approves product discovery and allows Backburner to create the implementation PR/worktree flow for an issue
|
|
223
|
+
- `agentPlanApproved`: approves an implementation plan and allows implementation tasks to run
|
|
224
|
+
- `agentReview`: requests an explicit code review; review tasks are dispatched when the PR has this label and its head SHA changed since the last review
|
|
225
|
+
- `agentSyncParentBranch`: authorizes Backburner to sync a blocked parent implementation branch from the repository default branch
|
|
224
226
|
|
|
225
|
-
Backburner also uses `
|
|
227
|
+
Backburner also uses `agentInProgress`, `planBreakdownNeeded`, and `prepareForMerge` when those labels are configured. Onboarding writes `repos.json` with these label values by default: `backburner:product-approved`, `backburner:plan-approved`, `backburner:review`, `backburner:in-progress`, `backburner:plan-breakdown-needed`, `backburner:sync-parent-branch`, and `backburner:prepare-for-merge`. It creates or updates the configured labels through `gh label create --force`. Custom label names, including older unprefixed names, are supported when configured in `repos.json`.
|
|
226
228
|
|
|
227
229
|
## Commands
|
|
228
230
|
|
|
@@ -315,7 +317,7 @@ Generated files are readable JSON. Control-plane state is written under `<output
|
|
|
315
317
|
|
|
316
318
|
## Platform Support
|
|
317
319
|
|
|
318
|
-
Backburner is intended to run on macOS, Linux, and Windows with Node.js 20 or newer. The
|
|
320
|
+
Backburner is intended to run on macOS, Linux, and Windows with Node.js 20 or newer. The CI workflow runs on Ubuntu, macOS, and Windows with Node 20 and 22, and includes build, typecheck, tests, package dry-run, and local package install smoke checks.
|
|
319
321
|
|
|
320
322
|
## Verification
|
|
321
323
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
1
|
import { ExecFileCommandRunner } from "../../utils/command-runner.js";
|
|
3
2
|
import { loadConfig } from "../../config/loader.js";
|
|
4
3
|
import { GhCliGitHubGateway } from "../../github/gateway.js";
|
|
@@ -9,11 +8,11 @@ import { PlanBreakdownSessionStore } from "../../workstreams/plan-breakdown-gene
|
|
|
9
8
|
import { PlanBreakdownToolService } from "../../workstreams/plan-breakdown-generator/tools.js";
|
|
10
9
|
import { WaveAssessmentSessionStore } from "../../workstreams/wave-assessment/store.js";
|
|
11
10
|
import { WaveAssessmentToolService } from "../../workstreams/wave-assessment/tools.js";
|
|
12
|
-
import { defaultBackburnerManagementRoot } from "../../utils/paths.js";
|
|
11
|
+
import { defaultBackburnerManagementRoot, resolveUserPath } from "../../utils/paths.js";
|
|
13
12
|
import { defaultStderrLogger } from "../../utils/logger.js";
|
|
14
13
|
import { DEFAULT_GITHUB_BROKER_PORT, readOptionalNumberFlag, readOptionalStringFlag, readStringFlag } from "../options.js";
|
|
15
14
|
export async function runBrokerSmokeCli(argv, logger = defaultStderrLogger) {
|
|
16
|
-
const managementDir =
|
|
15
|
+
const managementDir = resolveUserPath(readStringFlag(argv, "--management-dir", defaultBackburnerManagementRoot()));
|
|
17
16
|
const host = "127.0.0.1";
|
|
18
17
|
const config = await loadConfig(managementDir);
|
|
19
18
|
const commandRunner = new ExecFileCommandRunner();
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
1
|
import { ExecFileCommandRunner } from "../../utils/command-runner.js";
|
|
3
2
|
import { loadConfig } from "../../config/loader.js";
|
|
4
3
|
import { GhCliGitHubGateway } from "../../github/gateway.js";
|
|
@@ -9,11 +8,11 @@ import { PlanBreakdownSessionStore } from "../../workstreams/plan-breakdown-gene
|
|
|
9
8
|
import { PlanBreakdownToolService } from "../../workstreams/plan-breakdown-generator/tools.js";
|
|
10
9
|
import { WaveAssessmentSessionStore } from "../../workstreams/wave-assessment/store.js";
|
|
11
10
|
import { WaveAssessmentToolService } from "../../workstreams/wave-assessment/tools.js";
|
|
12
|
-
import { defaultBackburnerManagementRoot } from "../../utils/paths.js";
|
|
11
|
+
import { defaultBackburnerManagementRoot, resolveUserPath } from "../../utils/paths.js";
|
|
13
12
|
import { defaultStderrLogger } from "../../utils/logger.js";
|
|
14
13
|
import { DEFAULT_GITHUB_BROKER_PORT, readNumberFlag, readStringFlag } from "../options.js";
|
|
15
14
|
export async function runBrokerCli(argv, logger = defaultStderrLogger) {
|
|
16
|
-
const managementDir =
|
|
15
|
+
const managementDir = resolveUserPath(readStringFlag(argv, "--management-dir", defaultBackburnerManagementRoot()));
|
|
17
16
|
const port = readNumberFlag(argv, "--port", DEFAULT_GITHUB_BROKER_PORT);
|
|
18
17
|
const host = readStringFlag(argv, "--host", "127.0.0.1");
|
|
19
18
|
const config = await loadConfig(managementDir);
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
1
|
import { JournalReader } from "../../journal/index.js";
|
|
3
2
|
import { renderJournalEntries } from "../../journal/renderer.js";
|
|
4
|
-
import { defaultBackburnerOutputsRoot, resolveOutputsJournalRoot } from "../../utils/paths.js";
|
|
3
|
+
import { defaultBackburnerOutputsRoot, resolveOutputsJournalRoot, resolveUserPath } from "../../utils/paths.js";
|
|
5
4
|
import { CliUsageError } from "../errors.js";
|
|
6
5
|
import { readOptionalStringFlag, readStringFlag } from "../options.js";
|
|
7
6
|
export async function runJournalCli(argv, injectedJournal) {
|
|
8
|
-
const outputsDir =
|
|
7
|
+
const outputsDir = resolveUserPath(readStringFlag(argv, "--outputs-dir", defaultBackburnerOutputsRoot()));
|
|
9
8
|
const journalDir = resolveOutputsJournalRoot(outputsDir);
|
|
10
9
|
const reader = injectedJournal ? injectedJournal.reader : new JournalReader(journalDir);
|
|
11
10
|
const repoId = readOptionalStringFlag(argv, "--repo");
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
1
|
import { loadConfig } from "../../config/loader.js";
|
|
3
2
|
import { StateStore } from "../../state/loader.js";
|
|
4
|
-
import { defaultBackburnerManagementRoot, defaultBackburnerOutputsRoot, resolveOutputsLogsRoot, resolveOutputsStateRoot } from "../../utils/paths.js";
|
|
3
|
+
import { defaultBackburnerManagementRoot, defaultBackburnerOutputsRoot, resolveOutputsLogsRoot, resolveOutputsStateRoot, resolveUserPath } from "../../utils/paths.js";
|
|
5
4
|
import { RuntimeStatusTracker, TuiScreen } from "../tui/index.js";
|
|
6
5
|
import { readIntervalMs, readOptionalStringFlag, readStringFlag } from "../options.js";
|
|
7
6
|
import { runCli } from "./run.js";
|
|
@@ -73,7 +72,7 @@ export class SerializedCycleRunner {
|
|
|
73
72
|
}
|
|
74
73
|
export async function runInteractiveTui(argv) {
|
|
75
74
|
const rest = argv.filter((arg) => arg !== "run");
|
|
76
|
-
const managementDir =
|
|
75
|
+
const managementDir = resolveUserPath(readStringFlag(rest, "--management-dir", defaultBackburnerManagementRoot()));
|
|
77
76
|
let repoCount = 0;
|
|
78
77
|
let initialConfig = null;
|
|
79
78
|
try {
|
|
@@ -91,9 +90,9 @@ export async function runInteractiveTui(argv) {
|
|
|
91
90
|
tracker.setIntervalMs(intervalMs);
|
|
92
91
|
// Load initial state for the explorer immediately on start
|
|
93
92
|
try {
|
|
94
|
-
const outputsDir =
|
|
95
|
-
const stateDir =
|
|
96
|
-
const logDir =
|
|
93
|
+
const outputsDir = resolveUserPath(readOptionalStringFlag(rest, "--outputs-dir") ?? defaultBackburnerOutputsRoot());
|
|
94
|
+
const stateDir = resolveUserPath(resolveOutputsStateRoot(outputsDir));
|
|
95
|
+
const logDir = resolveUserPath(resolveOutputsLogsRoot(outputsDir));
|
|
97
96
|
const stateStore = new StateStore({ stateDir, executionDir: logDir });
|
|
98
97
|
const state = await stateStore.load();
|
|
99
98
|
tracker.setState(state);
|
|
@@ -114,9 +113,9 @@ export async function runInteractiveTui(argv) {
|
|
|
114
113
|
logger: silentLogger,
|
|
115
114
|
cycleNo: currentCycleNo
|
|
116
115
|
});
|
|
117
|
-
const outputsDir =
|
|
118
|
-
const stateDir =
|
|
119
|
-
const logDir =
|
|
116
|
+
const outputsDir = resolveUserPath(readOptionalStringFlag(rest, "--outputs-dir") ?? defaultBackburnerOutputsRoot());
|
|
117
|
+
const stateDir = resolveUserPath(resolveOutputsStateRoot(outputsDir));
|
|
118
|
+
const logDir = resolveUserPath(resolveOutputsLogsRoot(outputsDir));
|
|
120
119
|
const stateStore = new StateStore({ stateDir, executionDir: logDir });
|
|
121
120
|
const state = await stateStore.load();
|
|
122
121
|
try {
|
package/dist/src/cli/init.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { ExecFileCommandRunner } from "../utils/command-runner.js";
|
|
3
|
-
import { defaultBackburnerCodeRoot, defaultBackburnerManagementRoot, defaultBackburnerOutputsRoot, defaultWorkspacesRootForCodeRoot } from "../utils/paths.js";
|
|
3
|
+
import { defaultBackburnerCodeRoot, defaultBackburnerManagementRoot, defaultBackburnerOutputsRoot, defaultWorkspacesRootForCodeRoot, resolveUserPath } from "../utils/paths.js";
|
|
4
4
|
import { OnboardingEngine } from "../onboarding/engine.js";
|
|
5
5
|
import { NodeReadlinePrompter, NonInteractivePrompter } from "../onboarding/prompt.js";
|
|
6
6
|
import { WelcomeStep } from "../onboarding/steps/welcome.js";
|
|
@@ -12,6 +12,7 @@ import { ConfigurePathsStep } from "../onboarding/steps/configure-paths.js";
|
|
|
12
12
|
import { DiscoverProvidersStep } from "../onboarding/steps/discover-providers.js";
|
|
13
13
|
import { ConfigureModelsStep } from "../onboarding/steps/configure-models.js";
|
|
14
14
|
import { GenerateConfigStep } from "../onboarding/steps/generate-config.js";
|
|
15
|
+
import { ProvisionLabelsStep } from "../onboarding/steps/provision-labels.js";
|
|
15
16
|
import { ReadinessValidationStep } from "../onboarding/steps/readiness-validation.js";
|
|
16
17
|
import { ShowSummaryStep } from "../onboarding/steps/show-summary.js";
|
|
17
18
|
import { DemoRepoCreator } from "../onboarding/services/demo-repo.js";
|
|
@@ -38,14 +39,14 @@ export async function runInitCli(argv) {
|
|
|
38
39
|
export async function runInit(options) {
|
|
39
40
|
const commandRunner = new ExecFileCommandRunner();
|
|
40
41
|
const { codeRoot, nonInteractive = false } = options;
|
|
41
|
-
const resolvedCodeRoot =
|
|
42
|
-
const resolvedOutputsDir =
|
|
42
|
+
const resolvedCodeRoot = resolveUserPath(codeRoot);
|
|
43
|
+
const resolvedOutputsDir = resolveUserPath(options.outputsDir ?? defaultBackburnerOutputsRoot());
|
|
43
44
|
const ctx = {
|
|
44
45
|
codeRoot: resolvedCodeRoot,
|
|
45
|
-
managementDir:
|
|
46
|
+
managementDir: resolveUserPath(options.managementDir ?? defaultBackburnerManagementRoot()),
|
|
46
47
|
outputsDir: resolvedOutputsDir,
|
|
47
|
-
logDir:
|
|
48
|
-
workspacesDir:
|
|
48
|
+
logDir: resolveUserPath(options.logDir ?? path.join(resolvedOutputsDir, "logs")),
|
|
49
|
+
workspacesDir: resolveUserPath(options.workspacesDir ?? defaultWorkspacesRootForCodeRoot(resolvedCodeRoot)),
|
|
49
50
|
discoveredRepos: [],
|
|
50
51
|
selectedRepos: [],
|
|
51
52
|
discoveredProviders: [],
|
|
@@ -57,11 +58,12 @@ export async function runInit(options) {
|
|
|
57
58
|
new CheckRequirementsStep(commandRunner),
|
|
58
59
|
new CheckGitHubAuthStep(commandRunner),
|
|
59
60
|
new DiscoverReposStep(commandRunner, ctx.codeRoot),
|
|
60
|
-
new SelectReposStep(new DemoRepoCreator(commandRunner)),
|
|
61
|
+
new SelectReposStep(new DemoRepoCreator(commandRunner), nonInteractive ? "existing" : "demo"),
|
|
61
62
|
new ConfigurePathsStep(),
|
|
62
63
|
new DiscoverProvidersStep(commandRunner),
|
|
63
64
|
new ConfigureModelsStep(),
|
|
64
65
|
new GenerateConfigStep(),
|
|
66
|
+
new ProvisionLabelsStep(commandRunner),
|
|
65
67
|
new ReadinessValidationStep(commandRunner),
|
|
66
68
|
new ShowSummaryStep()
|
|
67
69
|
];
|
|
@@ -15,19 +15,19 @@ import { createSmitheryMcpResolverFromEnv, reconcileMcpStatus } from "../../mcps
|
|
|
15
15
|
import { StateStore } from "../../state/loader.js";
|
|
16
16
|
import { ExecFileCommandRunner } from "../../utils/command-runner.js";
|
|
17
17
|
import { defaultStderrLogger } from "../../utils/logger.js";
|
|
18
|
-
import { defaultBackburnerCodeRoot, defaultBackburnerManagementRoot, defaultBackburnerOutputsRoot, resolveOutputsStateRoot, resolveOutputsLogsRoot, resolveOutputsJournalRoot } from "../../utils/paths.js";
|
|
18
|
+
import { defaultBackburnerCodeRoot, defaultBackburnerManagementRoot, defaultBackburnerOutputsRoot, resolveOutputsStateRoot, resolveOutputsLogsRoot, resolveOutputsJournalRoot, resolveUserPath } from "../../utils/paths.js";
|
|
19
19
|
import { TriageFollowUpService } from "../../workflows/triage.js";
|
|
20
20
|
import { ImplementationWorkflowService } from "../../worktrees/service.js";
|
|
21
21
|
import { WorkspaceRecoveryService } from "../../worktrees/workspace-recovery.js";
|
|
22
22
|
import { DEFAULT_GITHUB_BROKER_PORT as CLI_DEFAULT_GITHUB_BROKER_PORT, readNumberFlag, readOptionalStringFlag, readStringFlag } from "../options.js";
|
|
23
23
|
export async function createRunContext(input) {
|
|
24
24
|
const { rest, options } = input;
|
|
25
|
-
const managementDir =
|
|
25
|
+
const managementDir = resolveUserPath(options.managementDir ?? readStringFlag(rest, "--management-dir", defaultBackburnerManagementRoot()));
|
|
26
26
|
const configuredOutputsDir = options.outputsDir ?? readOptionalStringFlag(rest, "--outputs-dir");
|
|
27
|
-
const outputsDir =
|
|
27
|
+
const outputsDir = resolveUserPath(configuredOutputsDir ?? defaultBackburnerOutputsRoot());
|
|
28
28
|
const useLegacyStateDirWithoutOutputs = options.stateDir !== undefined && configuredOutputsDir === undefined;
|
|
29
29
|
const logger = options.logger ?? defaultStderrLogger;
|
|
30
|
-
const codeRoot =
|
|
30
|
+
const codeRoot = resolveUserPath(readOptionalStringFlag(rest, "--code-root") ?? defaultBackburnerCodeRoot());
|
|
31
31
|
const brokerPort = readNumberFlag(rest, "--broker-port", CLI_DEFAULT_GITHUB_BROKER_PORT);
|
|
32
32
|
const commandRunner = options.commandRunner ?? new ExecFileCommandRunner();
|
|
33
33
|
const gitGateway = options.gitGateway ?? new GitCliGateway(commandRunner);
|
|
@@ -48,9 +48,9 @@ export async function createRunContext(input) {
|
|
|
48
48
|
logger.info(`Preparing outputs root ${outputsDir}`);
|
|
49
49
|
outputsGitOpsPrepared = await gitOpsLifecycle.prepareOutputsRoot(outputsDir);
|
|
50
50
|
}
|
|
51
|
-
const stateDir =
|
|
51
|
+
const stateDir = resolveUserPath(options.stateDir ?? resolveOutputsStateRoot(outputsDir));
|
|
52
52
|
const defaultLogDir = options.stateDir !== undefined ? stateDir : resolveOutputsLogsRoot(outputsDir);
|
|
53
|
-
const logDir =
|
|
53
|
+
const logDir = resolveUserPath(options.logDir ?? readStringFlag(rest, "--log-dir", defaultLogDir));
|
|
54
54
|
let managementRepo;
|
|
55
55
|
try {
|
|
56
56
|
const remoteResult = await commandRunner.run("git", ["remote", "get-url", "origin"], { cwd: managementDir });
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { writeJsonFileAtomic } from "../utils/json.js";
|
|
4
|
+
export const DEFAULT_BACKBURNER_LABELS = {
|
|
5
|
+
agentInProgress: "backburner:in-progress",
|
|
6
|
+
agentPlanApproved: "backburner:plan-approved",
|
|
7
|
+
agentProductApproved: "backburner:product-approved",
|
|
8
|
+
agentReview: "backburner:review",
|
|
9
|
+
planBreakdownNeeded: "backburner:plan-breakdown-needed",
|
|
10
|
+
agentSyncParentBranch: "backburner:sync-parent-branch",
|
|
11
|
+
prepareForMerge: "backburner:prepare-for-merge"
|
|
12
|
+
};
|
|
4
13
|
export class ConfigWriter {
|
|
5
14
|
async detectExisting(managementDir) {
|
|
6
15
|
const reposFile = path.join(managementDir, "repos.json");
|
|
@@ -55,15 +64,7 @@ function buildRepoEntry(repo) {
|
|
|
55
64
|
defaultBranch: repo.defaultBranch,
|
|
56
65
|
localPath: repo.localPath,
|
|
57
66
|
enabled: repo.enabled,
|
|
58
|
-
labels:
|
|
59
|
-
agentInProgress: "agent-in-progress",
|
|
60
|
-
agentPlanApproved: "agent-plan-approved",
|
|
61
|
-
agentProductApproved: "agent-product-approved",
|
|
62
|
-
agentReview: "agent-review",
|
|
63
|
-
planBreakdownNeeded: "plan-breakdown-needed",
|
|
64
|
-
agentSyncParentBranch: "agent-sync-parent-branch",
|
|
65
|
-
prepareForMerge: "prepare-for-merge"
|
|
66
|
-
}
|
|
67
|
+
labels: repo.labels
|
|
67
68
|
};
|
|
68
69
|
}
|
|
69
70
|
function buildAgentsJson(draft) {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const LABEL_TIMEOUT_MS = 30_000;
|
|
2
|
+
export class GitHubLabelProvisioner {
|
|
3
|
+
commandRunner;
|
|
4
|
+
constructor(commandRunner) {
|
|
5
|
+
this.commandRunner = commandRunner;
|
|
6
|
+
}
|
|
7
|
+
async provisionLabels(targets) {
|
|
8
|
+
const results = [];
|
|
9
|
+
for (const target of targets) {
|
|
10
|
+
const repoSlug = `${target.owner}/${target.name}`;
|
|
11
|
+
for (const label of target.labels) {
|
|
12
|
+
try {
|
|
13
|
+
await this.commandRunner.run("gh", [
|
|
14
|
+
"label",
|
|
15
|
+
"create",
|
|
16
|
+
label.name,
|
|
17
|
+
"--repo",
|
|
18
|
+
repoSlug,
|
|
19
|
+
"--description",
|
|
20
|
+
label.description,
|
|
21
|
+
"--color",
|
|
22
|
+
label.color,
|
|
23
|
+
"--force"
|
|
24
|
+
], { timeoutMs: LABEL_TIMEOUT_MS });
|
|
25
|
+
results.push({ repoSlug, labelName: label.name, status: "created_or_updated" });
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
results.push({
|
|
29
|
+
repoSlug,
|
|
30
|
+
labelName: label.name,
|
|
31
|
+
status: "failed",
|
|
32
|
+
message: err instanceof Error ? err.message : String(err)
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return results;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import { defaultBackburnerManagementRoot, defaultBackburnerOutputsRoot, defaultWorkspacesRootForCodeRoot } from "../../utils/paths.js";
|
|
2
|
+
import { defaultBackburnerManagementRoot, defaultBackburnerOutputsRoot, defaultWorkspacesRootForCodeRoot, resolveUserPath } from "../../utils/paths.js";
|
|
3
3
|
export class ConfigurePathsStep {
|
|
4
4
|
name = "configure-paths";
|
|
5
5
|
description = "Configure directories";
|
|
@@ -15,10 +15,10 @@ export class ConfigurePathsStep {
|
|
|
15
15
|
const outputsDir = await prompter.ask("Outputs directory (state, logs):", defaultOutputs);
|
|
16
16
|
const logDir = await prompter.ask("Log directory:", ctx.logDir || path.join(outputsDir || defaultOutputs, "logs"));
|
|
17
17
|
const workspacesDir = await prompter.ask("Local workspaces directory (where Backburner creates git worktrees):", defaultWorkspaces);
|
|
18
|
-
ctx.managementDir = managementDir || defaultManagement;
|
|
19
|
-
ctx.outputsDir = outputsDir || defaultOutputs;
|
|
20
|
-
ctx.logDir = logDir || path.join(ctx.outputsDir, "logs");
|
|
21
|
-
ctx.workspacesDir = workspacesDir || defaultWorkspaces;
|
|
18
|
+
ctx.managementDir = resolveUserPath(managementDir || defaultManagement);
|
|
19
|
+
ctx.outputsDir = resolveUserPath(outputsDir || defaultOutputs);
|
|
20
|
+
ctx.logDir = resolveUserPath(logDir || path.join(ctx.outputsDir, "logs"));
|
|
21
|
+
ctx.workspacesDir = resolveUserPath(workspacesDir || defaultWorkspaces);
|
|
22
22
|
return {
|
|
23
23
|
status: "passed",
|
|
24
24
|
message: "Directories configured",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { ConfigWriter } from "../config-writer.js";
|
|
3
|
+
import { DEFAULT_BACKBURNER_LABELS } from "../config-writer.js";
|
|
3
4
|
export class GenerateConfigStep {
|
|
4
5
|
name = "generate-config";
|
|
5
6
|
description = "Generating configuration";
|
|
@@ -101,6 +102,7 @@ function buildRepoDraft(repo, codeRoot) {
|
|
|
101
102
|
name: repo.name,
|
|
102
103
|
localPath,
|
|
103
104
|
defaultBranch: repo.defaultBranch || "main",
|
|
104
|
-
enabled: true
|
|
105
|
+
enabled: true,
|
|
106
|
+
labels: { ...DEFAULT_BACKBURNER_LABELS }
|
|
105
107
|
};
|
|
106
108
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { GitHubLabelProvisioner } from "../services/label-provisioner.js";
|
|
2
|
+
const LABEL_DEFINITIONS = {
|
|
3
|
+
agentInProgress: {
|
|
4
|
+
description: "Backburner is actively working on this item.",
|
|
5
|
+
color: "6F42C1"
|
|
6
|
+
},
|
|
7
|
+
agentProductApproved: {
|
|
8
|
+
description: "Approve Backburner to start product discovery from this issue.",
|
|
9
|
+
color: "2DA44E"
|
|
10
|
+
},
|
|
11
|
+
agentPlanApproved: {
|
|
12
|
+
description: "Approve Backburner to implement an accepted plan.",
|
|
13
|
+
color: "0969DA"
|
|
14
|
+
},
|
|
15
|
+
agentReview: {
|
|
16
|
+
description: "Ask Backburner to review this pull request.",
|
|
17
|
+
color: "BF8700"
|
|
18
|
+
},
|
|
19
|
+
planBreakdownNeeded: {
|
|
20
|
+
description: "Ask Backburner to break a plan into implementation packets.",
|
|
21
|
+
color: "8250DF"
|
|
22
|
+
},
|
|
23
|
+
agentSyncParentBranch: {
|
|
24
|
+
description: "Allow Backburner to sync the parent branch from the default branch.",
|
|
25
|
+
color: "1F883D"
|
|
26
|
+
},
|
|
27
|
+
prepareForMerge: {
|
|
28
|
+
description: "Ask Backburner to prepare this pull request for merge.",
|
|
29
|
+
color: "CF222E"
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
export class ProvisionLabelsStep {
|
|
33
|
+
commandRunner;
|
|
34
|
+
name = "provision-labels";
|
|
35
|
+
description = "Creating GitHub labels";
|
|
36
|
+
constructor(commandRunner) {
|
|
37
|
+
this.commandRunner = commandRunner;
|
|
38
|
+
}
|
|
39
|
+
async run(ctx, _prompter) {
|
|
40
|
+
const draft = ctx.configDraft;
|
|
41
|
+
if (draft === undefined || draft.repos.length === 0) {
|
|
42
|
+
return {
|
|
43
|
+
status: "skipped",
|
|
44
|
+
message: "Skipped: no repositories selected for label creation"
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const targets = buildLabelTargets(draft);
|
|
48
|
+
const provisioner = new GitHubLabelProvisioner(this.commandRunner);
|
|
49
|
+
const results = await provisioner.provisionLabels(targets);
|
|
50
|
+
const failures = results.filter((result) => result.status === "failed");
|
|
51
|
+
if (failures.length > 0) {
|
|
52
|
+
ctx.warnings.push("Some GitHub labels could not be created. You can retry onboarding or create them manually from repos.json.");
|
|
53
|
+
return {
|
|
54
|
+
status: "warning",
|
|
55
|
+
message: `Created labels with ${failures.length} failure${failures.length === 1 ? "" : "s"}`,
|
|
56
|
+
details: [
|
|
57
|
+
...failures.map((failure) => {
|
|
58
|
+
const suffix = failure.message !== undefined ? `: ${failure.message}` : "";
|
|
59
|
+
return `${failure.repoSlug}: ${failure.labelName}${suffix}`;
|
|
60
|
+
}),
|
|
61
|
+
...buildManualLabelDetails(targets)
|
|
62
|
+
]
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
status: "passed",
|
|
67
|
+
message: `Created or updated ${results.length} GitHub label${results.length === 1 ? "" : "s"}`,
|
|
68
|
+
details: targets.map((target) => `${target.owner}/${target.name}: ${target.labels.length} labels`)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function buildManualLabelDetails(targets) {
|
|
73
|
+
return targets.flatMap((target) => {
|
|
74
|
+
const repoSlug = `${target.owner}/${target.name}`;
|
|
75
|
+
return [
|
|
76
|
+
`Manual labels page for ${repoSlug}: https://github.com/${repoSlug}/labels`,
|
|
77
|
+
`Required labels: ${target.labels.map((label) => label.name).join(", ")}`
|
|
78
|
+
];
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function buildLabelTargets(draft) {
|
|
82
|
+
return draft.repos.map((repo) => ({
|
|
83
|
+
owner: repo.owner,
|
|
84
|
+
name: repo.name,
|
|
85
|
+
labels: buildRepoLabels(repo)
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
function buildRepoLabels(repo) {
|
|
89
|
+
return [
|
|
90
|
+
buildLabel("agentInProgress", repo.labels.agentInProgress),
|
|
91
|
+
buildLabel("agentProductApproved", repo.labels.agentProductApproved),
|
|
92
|
+
buildLabel("agentPlanApproved", repo.labels.agentPlanApproved),
|
|
93
|
+
buildLabel("agentReview", repo.labels.agentReview),
|
|
94
|
+
buildLabel("planBreakdownNeeded", repo.labels.planBreakdownNeeded),
|
|
95
|
+
buildLabel("agentSyncParentBranch", repo.labels.agentSyncParentBranch),
|
|
96
|
+
buildLabel("prepareForMerge", repo.labels.prepareForMerge)
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
function buildLabel(key, name) {
|
|
100
|
+
return {
|
|
101
|
+
name,
|
|
102
|
+
...LABEL_DEFINITIONS[key]
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { defaultDemoRepoName } from "../services/demo-repo.js";
|
|
2
2
|
export class SelectReposStep {
|
|
3
3
|
demoRepoCreator;
|
|
4
|
+
defaultSetupChoice;
|
|
4
5
|
name = "select-repos";
|
|
5
6
|
description = "Select repositories to manage";
|
|
6
|
-
constructor(demoRepoCreator) {
|
|
7
|
+
constructor(demoRepoCreator, defaultSetupChoice = "demo") {
|
|
7
8
|
this.demoRepoCreator = demoRepoCreator;
|
|
9
|
+
this.defaultSetupChoice = defaultSetupChoice;
|
|
8
10
|
}
|
|
9
11
|
async run(ctx, prompter) {
|
|
10
12
|
const { discoveredRepos } = ctx;
|
|
@@ -55,36 +57,52 @@ export class SelectReposStep {
|
|
|
55
57
|
}
|
|
56
58
|
async promptSetupChoice(ctx, prompter) {
|
|
57
59
|
const hasDiscoveredRepos = ctx.discoveredRepos.length > 0;
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"Skip repository setup for now"
|
|
64
|
-
|
|
65
|
-
const
|
|
60
|
+
const existingLabel = hasDiscoveredRepos
|
|
61
|
+
? "Select existing repositories (Backburner will create/update its labels on them)"
|
|
62
|
+
: "Enter an existing repository manually";
|
|
63
|
+
const hasDemoOption = ctx.githubUser !== undefined;
|
|
64
|
+
const options = hasDemoOption
|
|
65
|
+
? ["Create a private Backburner demo repo", existingLabel, "Skip repository setup for now"]
|
|
66
|
+
: [existingLabel, "Skip repository setup for now"];
|
|
67
|
+
const defaultIndex = hasDemoOption && this.defaultSetupChoice === "existing" ? 1 : 0;
|
|
68
|
+
const choice = await prompter.choose("How do you want to set up repositories?", options, defaultIndex);
|
|
69
|
+
if (hasDemoOption) {
|
|
70
|
+
if (choice === 0) {
|
|
71
|
+
return "demo";
|
|
72
|
+
}
|
|
73
|
+
if (choice === 1) {
|
|
74
|
+
return "existing";
|
|
75
|
+
}
|
|
76
|
+
return "skip";
|
|
77
|
+
}
|
|
66
78
|
if (choice === 0) {
|
|
67
79
|
return "existing";
|
|
68
80
|
}
|
|
69
|
-
if (ctx.githubUser !== undefined && choice === 1) {
|
|
70
|
-
return "demo";
|
|
71
|
-
}
|
|
72
81
|
return "skip";
|
|
73
82
|
}
|
|
74
83
|
async promptNoSelection(ctx, prompter) {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
const hasDemoOption = ctx.githubUser !== undefined;
|
|
85
|
+
const options = hasDemoOption
|
|
86
|
+
? [
|
|
87
|
+
"Create a private Backburner demo repo",
|
|
88
|
+
"Add an existing repository manually",
|
|
89
|
+
"Skip repository setup for now"
|
|
90
|
+
]
|
|
91
|
+
: ["Add an existing repository manually", "Skip repository setup for now"];
|
|
80
92
|
const defaultIndex = 0;
|
|
81
93
|
const choice = await prompter.choose("No repositories selected. How do you want to continue?", options, defaultIndex);
|
|
94
|
+
if (hasDemoOption) {
|
|
95
|
+
if (choice === 0) {
|
|
96
|
+
return await this.promptCreateDemoRepo(ctx, prompter);
|
|
97
|
+
}
|
|
98
|
+
if (choice === 1) {
|
|
99
|
+
return await this.promptManualEntry(ctx, prompter);
|
|
100
|
+
}
|
|
101
|
+
return this.noRepositoryWarning();
|
|
102
|
+
}
|
|
82
103
|
if (choice === 0) {
|
|
83
104
|
return await this.promptManualEntry(ctx, prompter);
|
|
84
105
|
}
|
|
85
|
-
if (ctx.githubUser !== undefined && choice === 1) {
|
|
86
|
-
return await this.promptCreateDemoRepo(ctx, prompter);
|
|
87
|
-
}
|
|
88
106
|
return this.noRepositoryWarning();
|
|
89
107
|
}
|
|
90
108
|
async promptCreateDemoRepo(ctx, prompter) {
|
|
@@ -106,7 +124,7 @@ export class SelectReposStep {
|
|
|
106
124
|
details: ["Repository names can contain letters, numbers, dots, underscores, and hyphens."]
|
|
107
125
|
};
|
|
108
126
|
}
|
|
109
|
-
const confirmed = await prompter.confirm(`Create private GitHub repository ${ctx.githubUser}/${repoName} and clone it under ${ctx.codeRoot}?`,
|
|
127
|
+
const confirmed = await prompter.confirm(`Create private GitHub repository ${ctx.githubUser}/${repoName} and clone it under ${ctx.codeRoot}?`, true);
|
|
110
128
|
if (!confirmed) {
|
|
111
129
|
return this.noRepositoryWarning();
|
|
112
130
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { deriveOverallStatus } from "../engine.js";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
3
4
|
import { defaultBackburnerCodeRoot, defaultBackburnerManagementRoot, defaultBackburnerOutputsRoot } from "../../utils/paths.js";
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
const terminalQr = require("qrcode-terminal");
|
|
4
7
|
export class ShowSummaryStep {
|
|
5
8
|
name = "show-summary";
|
|
6
9
|
description = "Summary";
|
|
@@ -62,13 +65,23 @@ export class ShowSummaryStep {
|
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
67
|
if (succeeded) {
|
|
68
|
+
const issueTarget = buildFirstIssueTarget(ctx);
|
|
65
69
|
lines.push("");
|
|
66
70
|
lines.push("Next steps:");
|
|
67
|
-
lines.push(" 1.
|
|
68
|
-
lines.push(" 2. Start the orchestrator:");
|
|
71
|
+
lines.push(" 1. Start the orchestrator:");
|
|
69
72
|
lines.push(` ${buildRunCommand(ctx)}`);
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
if (issueTarget !== undefined) {
|
|
74
|
+
lines.push(" 2. Open a Backburner issue:");
|
|
75
|
+
lines.push(` ${issueTarget.prefilledIssueUrl}`);
|
|
76
|
+
lines.push(" 3. Watch Backburner pick it up on the next orchestration cycle");
|
|
77
|
+
lines.push("");
|
|
78
|
+
lines.push("Repository issues QR:");
|
|
79
|
+
lines.push(renderQr(issueTarget.repositoryIssuesUrl));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
lines.push(" 2. Add a repository to repos.json");
|
|
83
|
+
lines.push(" 3. Open an issue on a managed repo with the configured product-approved label");
|
|
84
|
+
}
|
|
72
85
|
}
|
|
73
86
|
else {
|
|
74
87
|
lines.push("");
|
|
@@ -79,6 +92,43 @@ export class ShowSummaryStep {
|
|
|
79
92
|
return { status: "passed", message: "Summary displayed" };
|
|
80
93
|
}
|
|
81
94
|
}
|
|
95
|
+
function buildFirstIssueTarget(ctx) {
|
|
96
|
+
const repo = ctx.configDraft?.repos[0];
|
|
97
|
+
if (repo === undefined) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
const url = new URL(`https://github.com/${repo.owner}/${repo.name}/issues/new`);
|
|
101
|
+
url.searchParams.set("title", "Initialize a TypeScript React Native kitchen measurement converter");
|
|
102
|
+
url.searchParams.set("body", [
|
|
103
|
+
"Create a small TypeScript React Native application for converting common kitchen measurements.",
|
|
104
|
+
"",
|
|
105
|
+
"Goal:",
|
|
106
|
+
"- Let a cook enter a quantity, choose a source unit, choose a target unit, and see the converted value.",
|
|
107
|
+
"",
|
|
108
|
+
"Requirements:",
|
|
109
|
+
"- Use TypeScript.",
|
|
110
|
+
"- Support cups, tablespoons, teaspoons, milliliters, liters, fluid ounces, ounces, grams, and kilograms.",
|
|
111
|
+
"- Include a simple conversion screen with clear input validation and useful empty/error states.",
|
|
112
|
+
"- Keep the implementation small and easy to review.",
|
|
113
|
+
"",
|
|
114
|
+
"Acceptance criteria:",
|
|
115
|
+
"- The app can convert at least 1 cup to tablespoons, milliliters, and fluid ounces.",
|
|
116
|
+
"- Invalid or empty input does not crash the app.",
|
|
117
|
+
"- Add or update tests for the conversion logic where the project test setup supports it."
|
|
118
|
+
].join("\n"));
|
|
119
|
+
url.searchParams.set("labels", repo.labels.agentProductApproved);
|
|
120
|
+
return {
|
|
121
|
+
prefilledIssueUrl: url.toString(),
|
|
122
|
+
repositoryIssuesUrl: `https://github.com/${repo.owner}/${repo.name}/issues`
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function renderQr(input) {
|
|
126
|
+
let rendered = "";
|
|
127
|
+
terminalQr.generate(input, { small: true }, (qr) => {
|
|
128
|
+
rendered = qr.trimEnd();
|
|
129
|
+
});
|
|
130
|
+
return rendered;
|
|
131
|
+
}
|
|
82
132
|
function buildRunCommand(ctx) {
|
|
83
133
|
const args = ["backburner", "run"];
|
|
84
134
|
if (ctx.codeRoot !== defaultBackburnerCodeRoot()) {
|
|
@@ -18,7 +18,8 @@ export class WelcomeStep {
|
|
|
18
18
|
" 5. Detecting available AI providers",
|
|
19
19
|
" 6. Assigning models to agent roles",
|
|
20
20
|
" 7. Generating configuration files",
|
|
21
|
-
" 8.
|
|
21
|
+
" 8. Creating configured GitHub labels",
|
|
22
|
+
" 9. Running a final readiness check",
|
|
22
23
|
""
|
|
23
24
|
];
|
|
24
25
|
process.stdout.write(lines.join("\n"));
|
package/dist/src/utils/paths.js
CHANGED
|
@@ -18,9 +18,21 @@ export const outputsJournalRoot = path.join(outputsRoot, "journal");
|
|
|
18
18
|
export function resolveOutputsJournalRoot(outputsDir) {
|
|
19
19
|
return path.join(outputsDir, "journal");
|
|
20
20
|
}
|
|
21
|
+
export function expandHomePath(input) {
|
|
22
|
+
if (input === "~") {
|
|
23
|
+
return os.homedir();
|
|
24
|
+
}
|
|
25
|
+
if (input.startsWith("~/") || input.startsWith("~\\")) {
|
|
26
|
+
return path.join(os.homedir(), input.slice(2));
|
|
27
|
+
}
|
|
28
|
+
return input;
|
|
29
|
+
}
|
|
30
|
+
export function resolveUserPath(input) {
|
|
31
|
+
return path.resolve(expandHomePath(input));
|
|
32
|
+
}
|
|
21
33
|
export function defaultBackburnerRoot() {
|
|
22
34
|
if (process.env.BACKBURNER_ROOT !== undefined && process.env.BACKBURNER_ROOT.trim() !== "") {
|
|
23
|
-
return process.env.BACKBURNER_ROOT;
|
|
35
|
+
return resolveUserPath(process.env.BACKBURNER_ROOT);
|
|
24
36
|
}
|
|
25
37
|
return path.join(os.homedir(), ".backburner");
|
|
26
38
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backburner/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local GitHub-based agent orchestration CLI",
|
|
6
6
|
"license": "MIT",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@smithery/api": "^0.67.0",
|
|
57
57
|
"cross-spawn": "^7.0.6",
|
|
58
|
+
"qrcode-terminal": "^0.12.0",
|
|
58
59
|
"xstate": "^5.31.0",
|
|
59
60
|
"zod": "^4.4.3"
|
|
60
61
|
}
|