@kendoo.agentdesk/agentdesk 0.27.0 → 0.28.1
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/CHANGELOG.md +33 -0
- package/bin/agentdesk.mjs +35 -45
- package/cli/agents.mjs +4 -256
- package/cli/bootstrap.mjs +40 -59
- package/cli/config.mjs +29 -4
- package/cli/daemon.mjs +72 -44
- package/cli/dotenv.mjs +96 -13
- package/cli/engine/agents/index.mjs +152 -0
- package/cli/engine/claude-auth.mjs +72 -0
- package/cli/engine/env.mjs +56 -0
- package/cli/engine/events.mjs +214 -0
- package/cli/engine/hooks.mjs +119 -0
- package/cli/engine/phases/EXECUTION.md +45 -0
- package/cli/engine/phases/INTAKE.md +34 -0
- package/cli/engine/phases/PLAN.md +26 -0
- package/cli/engine/phases/REVIEW.md +21 -0
- package/cli/engine/phases/SOLO.md +115 -0
- package/cli/engine/phases/SUMMARY.md +23 -0
- package/cli/engine/prompts.mjs +181 -0
- package/cli/engine/query.mjs +63 -0
- package/cli/engine/schemas.mjs +180 -0
- package/cli/engine/session.mjs +285 -0
- package/cli/engine/spawn.mjs +83 -0
- package/cli/engine/tracker/github.md +19 -0
- package/cli/engine/tracker/jira.md +23 -0
- package/cli/engine/tracker/linear.md +24 -0
- package/cli/engine/verdict.mjs +83 -0
- package/cli/init.mjs +290 -147
- package/cli/login.mjs +11 -3
- package/cli/phase-loop.mjs +78 -0
- package/cli/proc.mjs +131 -0
- package/cli/project-key.mjs +56 -0
- package/cli/prompt.mjs +9 -503
- package/cli/prompts.mjs +20 -1
- package/cli/security-check.mjs +1 -1
- package/cli/session-isolation.mjs +65 -9
- package/cli/session-sandbox.mjs +13 -1
- package/cli/setup-helpers.mjs +83 -36
- package/cli/team.mjs +41 -34
- package/cli/tracker-check.mjs +12 -2
- package/cli/tracker-project.mjs +93 -0
- package/cli/update-check.mjs +62 -0
- package/package.json +12 -3
- package/cli/orchestrator.mjs +0 -461
- package/cli/stream-parser.mjs +0 -216
- package/prompts/phased.md +0 -549
- package/prompts/team.md +0 -505
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,39 @@ All user-facing changes to AgentDesk. Each entry is tagged:
|
|
|
8
8
|
|
|
9
9
|
Internal refactors, infrastructure changes, and architectural notes are not listed here.
|
|
10
10
|
|
|
11
|
+
## [0.28.1] — 2026-09-17
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- `[CLI]` Jane's delegation to the team ran the delegate in the background by default, leaving its own tool calls with no live turn to get approved — every one was rejected, so a delegated agent could do nothing. Delegation now always runs synchronously.
|
|
15
|
+
|
|
16
|
+
## [0.28.0] — 2026-09-17
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- `[CLI]` Team sessions now run on a new engine built on the Claude Agent SDK. Each phase (INTAKE → PLAN → EXECUTION → REVIEW → SUMMARY) is its own Claude run; Jane leads with a single tool — delegation — and the engineering roles are real subagents with role-scoped tools. Jane can no longer touch code because the tools do not exist in her session. The dashboard's per-agent feed is now driven by real attribution instead of name-matching prose.
|
|
20
|
+
- `[CLI]` REVIEW fans out three independent reviewers (Sam, Bart, Vera) who read the diff themselves rather than the implementer's summary. The verdict is structured and validated; a review that ends without an explicit verdict is treated as *not approved*, never as approval.
|
|
21
|
+
- `[CLI]` Publishing (pushing or opening a PR) is blocked during EXECUTION until Sam's audit has run in that phase. This was previously a prose rule that did not hold.
|
|
22
|
+
- `[CLI]` Session memory (`.agentdesk/session-memory.md`) is now written by the engine from each phase's structured summary, so later phases never start with a missing or malformed handoff. Stale memory from an earlier run is archived instead of being fed into the next session.
|
|
23
|
+
- `[CLI]` Per-phase model overrides continue to apply; REVIEW and SUMMARY still default to Haiku. New project settings: `phaseMaxTurns` (per-phase turn cap) and `maxBudgetUsd` (hard spend cap for a session).
|
|
24
|
+
- `[CLI]` The CLI update check no longer blocks on minor releases, never blocks the daemon, and no longer runs for `--version`/`help`. `AGENTDESK_SKIP_UPDATE_CHECK=1` disables it.
|
|
25
|
+
|
|
26
|
+
### Removed
|
|
27
|
+
- `[CLI]` `--single-process` / `--no-phased`. Every session runs the phased engine; the flags are accepted and ignored with a note.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- `[CLI]` `agentdesk init` run from the directory that contains the clone (the parent of `./<repo>`) now uses that clone instead of stopping with "already exists and is not empty". When the directory holds something else, the message now says what it found: a checkout of another repo, or not a git checkout at all.
|
|
31
|
+
- `[CLI]` `agentdesk init` no longer leaves the Jira project / Linear team empty. In existing mode it copied the server's value without asking, and in new mode it only asked when the credential check succeeded, so "Save anyway" skipped the question. With no project bound, create-from-description sessions filed issues in whatever project the token could reach, and the final hint printed the agentdesk project id as if it were the tracker prefix. Init now always resolves the team/project (listing the tracker's projects when credentials verify, asking by hand otherwise), fills the one missing field in on agentdesk.live for existing projects, and the tracker check — in init and before every `agentdesk team` — fails with "Missing Jira project key — run 'agentdesk init'" instead of passing.
|
|
32
|
+
- `[CLI]` Settings pushes to agentdesk.live (init's registration push and the background heal-sync) were silently rejected with "Unknown fields: projectKey", so the server never learned the tracker binding the wizard had picked. The push payload is now reduced to the fields the settings API accepts.
|
|
33
|
+
- `[CLI]` Cancelling a session from the dashboard now actually stops it. Previously the daemon reported "stopped" while Claude kept editing and pushing, and a new session could start on the same working tree.
|
|
34
|
+
- `[CLI]` Sessions that crashed mid-way are no longer reported as complete; they end as a handoff with a resume file. Crash detection had been dead code since the first phase always wrote session memory.
|
|
35
|
+
- `[CLI]` Team sessions no longer die instantly on macOS. The strict sandbox profile was missing read access to the root directory and to the project's ancestor directories, which killed every Claude child process at startup (sessions ended after 0.1s with a misleading rate-limit handoff). The profile now grants those directory nodes plus Claude's own state files, while keeping ~/.ssh, ~/.aws, ~/Documents, and other projects' files blocked.
|
|
36
|
+
- `[CLI]` On macOS, sessions started by a user logged in through the browser reported "Not logged in" inside the sandbox: the strict profile blocked reads of the Keychain where Claude Code keeps its login. The profile now allows that, and also exposes an account profile directory when one is selected.
|
|
37
|
+
- `[CLI]` Running `agentdesk team` from inside an interactive Claude Code session no longer makes the child believe it is nested in that session (which also surfaced as "Not logged in").
|
|
38
|
+
- `[CLI]` `agentdesk init` no longer leaves the GitHub token in the clone's git remote URL, writes `.env` with owner-only permissions, adds `.env` to `.gitignore`, masks secret prompts, verifies a new token before saving it, and recognises repository names containing dots. Existing clones are repaired on the next `init`.
|
|
39
|
+
- `[CLI]` `agentdesk init` now reports a failed project registration instead of printing success, defaults the final confirmation to "no" when an access check failed, and offers to re-enter tracker credentials in existing-project mode.
|
|
40
|
+
- `[CLI]` Two projects mapped to the same tracker team no longer overwrite each other's settings on the server; the second gets a repo-qualified project id.
|
|
41
|
+
- `[CLI]` `agentdesk login` opens the browser on Windows.
|
|
42
|
+
- `[UI]` Saved tracker credentials could not be read back by sessions since late May; they now decrypt again and no data was lost.
|
|
43
|
+
|
|
11
44
|
## [0.27.0] — 2026-07-06
|
|
12
45
|
|
|
13
46
|
### Added
|
package/bin/agentdesk.mjs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { readFileSync } from "fs";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { dirname, join } from "path";
|
|
8
|
+
import { classifyUpdate, shouldSkipCheck, updateAction } from "../cli/update-check.mjs";
|
|
8
9
|
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = dirname(__filename);
|
|
@@ -13,37 +14,6 @@ const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8")
|
|
|
13
14
|
const args = process.argv.slice(2);
|
|
14
15
|
const command = args[0];
|
|
15
16
|
|
|
16
|
-
// Blocking update check — require update for major/minor bumps, warn for patches
|
|
17
|
-
if (command !== "update") {
|
|
18
|
-
try {
|
|
19
|
-
const res = await fetch(`https://registry.npmjs.org/${pkg.name}/latest`, { signal: AbortSignal.timeout(3000) });
|
|
20
|
-
if (res.ok) {
|
|
21
|
-
const { version } = await res.json();
|
|
22
|
-
if (version !== pkg.version) {
|
|
23
|
-
const [curMajor, curMinor] = pkg.version.split(".").map(Number);
|
|
24
|
-
const [latMajor, latMinor] = version.split(".").map(Number);
|
|
25
|
-
const breaking = latMajor > curMajor || latMinor > curMinor;
|
|
26
|
-
|
|
27
|
-
const dim = "\x1b[2m";
|
|
28
|
-
const red = "\x1b[31m";
|
|
29
|
-
const yellow = "\x1b[33m";
|
|
30
|
-
const green = "\x1b[32m";
|
|
31
|
-
const cyan = "\x1b[36m";
|
|
32
|
-
const reset = "\x1b[0m";
|
|
33
|
-
|
|
34
|
-
if (breaking) {
|
|
35
|
-
console.log(`\n ${red}Update required:${reset} ${dim}${pkg.version}${reset} → ${green}${version}${reset}`);
|
|
36
|
-
console.log(` Run: ${cyan}agentdesk update${reset}\n`);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
} else {
|
|
39
|
-
console.log(`\n ${yellow}Update available:${reset} ${dim}${pkg.version}${reset} → ${green}${version}${reset}`);
|
|
40
|
-
console.log(` Run: ${cyan}agentdesk update${reset}\n`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
} catch {}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
17
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
48
18
|
console.log(pkg.version);
|
|
49
19
|
process.exit(0);
|
|
@@ -80,11 +50,13 @@ if (!command || command === "help" || command === "--help") {
|
|
|
80
50
|
Options:
|
|
81
51
|
--description, -d Task description or requirements
|
|
82
52
|
--cwd Working directory (defaults to current)
|
|
83
|
-
--single-process Run the whole team session in one Claude process on the EXECUTION model.
|
|
84
|
-
Default is phased mode (INTAKE → PLAN → EXECUTION → REVIEW → SUMMARY),
|
|
85
|
-
which honors per-phase model overrides from project settings.
|
|
86
53
|
--child-strategy How to handle child tasks: "inline" (default) or "branch"
|
|
87
54
|
|
|
55
|
+
Sessions run five phases — INTAKE → PLAN → EXECUTION → REVIEW → SUMMARY — each as
|
|
56
|
+
its own Claude query. Jane leads with a single tool (Agent) and delegates to
|
|
57
|
+
role-scoped subagents; REVIEW fans out independent reviewers who read the diff cold.
|
|
58
|
+
Per-phase model overrides come from project settings (phaseModels).
|
|
59
|
+
|
|
88
60
|
|
|
89
61
|
How it works:
|
|
90
62
|
With a tracker (Linear, Jira, GitHub Issues):
|
|
@@ -96,7 +68,6 @@ if (!command || command === "help" || command === "--help") {
|
|
|
96
68
|
|
|
97
69
|
Examples:
|
|
98
70
|
agentdesk team KEN-517
|
|
99
|
-
agentdesk team KEN-517 --single-process
|
|
100
71
|
agentdesk team -d "Fix the checkout total calculation"
|
|
101
72
|
agentdesk team -d "Add Google OAuth to the login page"
|
|
102
73
|
|
|
@@ -109,6 +80,30 @@ if (!command || command === "help" || command === "--help") {
|
|
|
109
80
|
process.exit(0);
|
|
110
81
|
}
|
|
111
82
|
|
|
83
|
+
// Update check. Runs after version/help so those never need the network, and
|
|
84
|
+
// only a MAJOR release blocks — and never for `daemon`, which runs unattended
|
|
85
|
+
// and must not refuse to start because a new version was published overnight.
|
|
86
|
+
// Policy lives in cli/update-check.mjs; AGENTDESK_SKIP_UPDATE_CHECK=1 disables.
|
|
87
|
+
if (!shouldSkipCheck(command)) {
|
|
88
|
+
try {
|
|
89
|
+
const res = await fetch(`https://registry.npmjs.org/${pkg.name}/latest`, { signal: AbortSignal.timeout(3000) });
|
|
90
|
+
if (res.ok) {
|
|
91
|
+
const { version } = await res.json();
|
|
92
|
+
const { action } = updateAction(command, classifyUpdate(pkg.version, version));
|
|
93
|
+
if (action !== "none") {
|
|
94
|
+
const dim = "\x1b[2m", red = "\x1b[31m", yellow = "\x1b[33m", green = "\x1b[32m", cyan = "\x1b[36m", reset = "\x1b[0m";
|
|
95
|
+
if (action === "block") {
|
|
96
|
+
console.log(`\n ${red}Update required:${reset} ${dim}${pkg.version}${reset} → ${green}${version}${reset} (major release)`);
|
|
97
|
+
console.log(` Run: ${cyan}agentdesk update${reset}\n`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
console.log(`\n ${yellow}Update available:${reset} ${dim}${pkg.version}${reset} → ${green}${version}${reset}`);
|
|
101
|
+
console.log(` Run: ${cyan}agentdesk update${reset}\n`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch {}
|
|
105
|
+
}
|
|
106
|
+
|
|
112
107
|
// Single-agent mode — agentdesk jane -d "...", agentdesk dennis TASK-123, etc.
|
|
113
108
|
const AGENT_NAMES = ["jane", "dennis", "sam", "bart", "vera", "luna", "mark", "nora"];
|
|
114
109
|
if (AGENT_NAMES.includes(command?.toLowerCase())) {
|
|
@@ -165,15 +160,12 @@ else if (command === "bootstrap") {
|
|
|
165
160
|
|
|
166
161
|
else if (command === "team") {
|
|
167
162
|
// Parse options first to find -d, --cwd, --child-strategy.
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
// --single-process (alias --no-phased) to fall back to the legacy
|
|
171
|
-
// single-process mode that runs everything on the EXECUTION model.
|
|
163
|
+
// Every team session runs the phased engine (INTAKE → PLAN → EXECUTION →
|
|
164
|
+
// REVIEW → SUMMARY) with real subagents; the old single-process mode is gone.
|
|
172
165
|
let description = "";
|
|
173
166
|
let cwd = process.cwd();
|
|
174
167
|
let taskId = null;
|
|
175
168
|
let childStrategy = null;
|
|
176
|
-
let phased = true;
|
|
177
169
|
const remaining = args.slice(1);
|
|
178
170
|
|
|
179
171
|
for (let i = 0; i < remaining.length; i++) {
|
|
@@ -183,10 +175,8 @@ else if (command === "team") {
|
|
|
183
175
|
cwd = remaining[++i];
|
|
184
176
|
} else if (remaining[i] === "--child-strategy" && remaining[i + 1]) {
|
|
185
177
|
childStrategy = remaining[++i];
|
|
186
|
-
} else if (remaining[i] === "--phased") {
|
|
187
|
-
phased
|
|
188
|
-
} else if (remaining[i] === "--single-process" || remaining[i] === "--no-phased") {
|
|
189
|
-
phased = false;
|
|
178
|
+
} else if (remaining[i] === "--phased" || remaining[i] === "--single-process" || remaining[i] === "--no-phased") {
|
|
179
|
+
console.error(` Note: ${remaining[i]} is no longer needed — sessions always run the phased engine.`);
|
|
190
180
|
} else if (!taskId && !remaining[i].startsWith("-")) {
|
|
191
181
|
taskId = remaining[i];
|
|
192
182
|
}
|
|
@@ -199,7 +189,7 @@ else if (command === "team") {
|
|
|
199
189
|
}
|
|
200
190
|
|
|
201
191
|
const { runTeam } = await import("../cli/team.mjs");
|
|
202
|
-
const code = await runTeam(taskId, { description, cwd, childStrategy
|
|
192
|
+
const code = await runTeam(taskId, { description, cwd, childStrategy });
|
|
203
193
|
process.exit(code);
|
|
204
194
|
}
|
|
205
195
|
|
package/cli/agents.mjs
CHANGED
|
@@ -5,7 +5,7 @@ export const BUILT_IN_AGENTS = {
|
|
|
5
5
|
badge: "●● JANE ●●",
|
|
6
6
|
role: "Product Analyst / Team Lead",
|
|
7
7
|
description: "leads the session, clarifies requirements, coordinates the team, dictates tracker comments (Dennis executes), decomposes large tasks into subtasks",
|
|
8
|
-
groundRules: "Jane is strictly non-technical and NEVER calls tools. She MUST NEVER: call Read, Write, Edit, Bash, Grep, Glob, or any other tool; read or reference code; mention file names, paths, function names, variable names, class names, imports, code snippets, terminal commands, error messages, or stack traces; write or modify code; execute tracker API calls herself. Her language is product-only: user stories, acceptance criteria, scope, priorities, stakeholder impact. When she needs technical information she asks Dennis or Sam (\"Dennis, can you check...\"). When tracker comments or status transitions are needed, Jane DICTATES the exact text and Dennis runs the command. If you are about to call a tool while the current speaker is Jane, STOP and switch to Dennis instead.",
|
|
8
|
+
groundRules: "Jane is strictly non-technical and NEVER calls tools. She MUST NEVER: call Read, Write, Edit, Bash, Grep, Glob, or any other tool; read or reference code; mention file names, paths, function names, variable names, class names, imports, code snippets, terminal commands, error messages, or stack traces; write or modify code; execute tracker API calls herself. Her language is product-only: user stories, acceptance criteria, scope, priorities, stakeholder impact. During INTAKE she evaluates task complexity and decomposes large features into subtasks (basic vs deferred). When she needs technical information she asks Dennis or Sam (\"Dennis, can you check...\"). When tracker comments or status transitions are needed, Jane DICTATES the exact text and Dennis runs the command. If you are about to call a tool while the current speaker is Jane, STOP and switch to Dennis instead.",
|
|
9
9
|
planning: "Requirements: what we're building, acceptance criteria, scope. Flags UI tasks for Luna. (No code, no files, no tool calls.)",
|
|
10
10
|
execution: {
|
|
11
11
|
step: "Jane wraps up (dictates, Dennis executes)",
|
|
@@ -184,6 +184,7 @@ export function resolveTeam(config) {
|
|
|
184
184
|
badge: `** ${custom.name.toUpperCase()} **`,
|
|
185
185
|
role: custom.role || "Team Member",
|
|
186
186
|
description: custom.role || "Custom team member",
|
|
187
|
+
tools: Array.isArray(custom.tools) ? custom.tools : undefined,
|
|
187
188
|
groundRules: custom.when ? `${custom.name} is invoked: ${custom.when}. How to use: ${custom.how || "as needed"}.` : "",
|
|
188
189
|
planning: `${custom.role || custom.name} Review`,
|
|
189
190
|
execution: custom.when ? {
|
|
@@ -212,6 +213,8 @@ export function resolveTeam(config) {
|
|
|
212
213
|
badge: entry.badge || `** ${entry.name.toUpperCase()} **`,
|
|
213
214
|
role: entry.role || "Team Member",
|
|
214
215
|
description: entry.description || entry.role || "Custom team member",
|
|
216
|
+
// Engine: explicit tool list for this agent's subagent definition.
|
|
217
|
+
tools: Array.isArray(entry.tools) ? entry.tools : undefined,
|
|
215
218
|
groundRules: entry.groundRules || "",
|
|
216
219
|
planning: entry.planning || `${entry.role || entry.name} Review`,
|
|
217
220
|
execution: entry.execution || null,
|
|
@@ -235,258 +238,3 @@ export function resolveTeam(config) {
|
|
|
235
238
|
|
|
236
239
|
return team;
|
|
237
240
|
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Return tracker-specific command snippets for execution steps.
|
|
241
|
-
*/
|
|
242
|
-
function trackerCommands(tracker, config) {
|
|
243
|
-
if (!tracker) return null;
|
|
244
|
-
|
|
245
|
-
if (tracker === "linear") {
|
|
246
|
-
return {
|
|
247
|
-
postPrLink: [
|
|
248
|
-
'Post PR link on Linear using attachmentCreate:',
|
|
249
|
-
'```',
|
|
250
|
-
'curl -s -X POST https://api.linear.app/graphql \\',
|
|
251
|
-
' -H "Authorization: $LINEAR_API_KEY" -H "Content-Type: application/json" \\',
|
|
252
|
-
' -d \'{"query":"mutation { attachmentCreate(input: { issueId: \\"$ISSUE_ID\\", title: \\"Pull Request\\", url: \\"$PR_URL\\" }) { success } }"}\'',
|
|
253
|
-
'```',
|
|
254
|
-
].join('\n'),
|
|
255
|
-
attachScreenshots: [
|
|
256
|
-
'Upload each screenshot via Linear fileUpload mutation, then post a comment with the image URLs:',
|
|
257
|
-
'```bash',
|
|
258
|
-
'for f in screenshots/*.png; do',
|
|
259
|
-
' UPLOAD=$(curl -s -X POST https://api.linear.app/graphql \\',
|
|
260
|
-
' -H "Authorization: $LINEAR_API_KEY" -H "Content-Type: application/json" \\',
|
|
261
|
-
' -d \'{"query":"mutation { fileUpload(contentType: \\"image/png\\", filename: \\"\'$(basename "$f")\'\\", size: \'$(stat -f%z "$f")\') { uploadFile { uploadUrl assetUrl headers { key value } } } }"}\')',
|
|
262
|
-
' # Extract uploadUrl, assetUrl, headers from $UPLOAD — PUT the file to uploadUrl, then collect assetUrl',
|
|
263
|
-
'done',
|
|
264
|
-
'# Post a comment with all image URLs as markdown images',
|
|
265
|
-
'```',
|
|
266
|
-
].join('\n'),
|
|
267
|
-
agentComment: [
|
|
268
|
-
'Post a comment on Linear with key findings:',
|
|
269
|
-
'```',
|
|
270
|
-
'curl -s -X POST https://api.linear.app/graphql \\',
|
|
271
|
-
' -H "Authorization: $LINEAR_API_KEY" -H "Content-Type: application/json" \\',
|
|
272
|
-
' -d \'{"query":"mutation { commentCreate(input: { issueId: \\"$ISSUE_ID\\", body: \\"<COMMENT>\\" }) { success } }"}\'',
|
|
273
|
-
'```',
|
|
274
|
-
].join('\n'),
|
|
275
|
-
transitionToReview: [
|
|
276
|
-
'Transition to "In Review" on Linear:',
|
|
277
|
-
'```',
|
|
278
|
-
'curl -s -X POST https://api.linear.app/graphql \\',
|
|
279
|
-
' -H "Authorization: $LINEAR_API_KEY" -H "Content-Type: application/json" \\',
|
|
280
|
-
' -d \'{"query":"mutation { issueUpdate(id: \\"$ISSUE_ID\\", input: { stateId: \\"$IN_REVIEW_STATE_ID\\" }) { success } }"}\'',
|
|
281
|
-
'```',
|
|
282
|
-
'Find the "In Review" state ID from workflowStates first.',
|
|
283
|
-
].join('\n'),
|
|
284
|
-
postSummary: [
|
|
285
|
-
'Post final summary comment on Linear (use commentCreate mutation above).',
|
|
286
|
-
].join('\n'),
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const baseUrl = config?.jira?.baseUrl || '$JIRA_BASE_URL';
|
|
291
|
-
if (tracker === "jira") {
|
|
292
|
-
return {
|
|
293
|
-
postPrLink: [
|
|
294
|
-
'Post PR link on Jira as a remote link:',
|
|
295
|
-
'```bash',
|
|
296
|
-
`curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \\`,
|
|
297
|
-
` "${baseUrl}/rest/api/3/issue/$TASK_ID/remotelink" \\`,
|
|
298
|
-
` -d '{"object":{"url":"$PR_URL","title":"Pull Request"}}'`,
|
|
299
|
-
'```',
|
|
300
|
-
].join('\n'),
|
|
301
|
-
attachScreenshots: [
|
|
302
|
-
'Upload each screenshot as a Jira attachment:',
|
|
303
|
-
'```bash',
|
|
304
|
-
`for f in screenshots/*.png; do`,
|
|
305
|
-
` curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \\`,
|
|
306
|
-
` -H "X-Atlassian-Token: no-check" \\`,
|
|
307
|
-
` -F "file=@$f" \\`,
|
|
308
|
-
` "${baseUrl}/rest/api/3/issue/$TASK_ID/attachments"`,
|
|
309
|
-
`done`,
|
|
310
|
-
'```',
|
|
311
|
-
].join('\n'),
|
|
312
|
-
agentComment: [
|
|
313
|
-
'Post a comment on Jira with key findings. IMPORTANT: Any URLs in the comment MUST use ADF inlineCard nodes so they render as clickable links:',
|
|
314
|
-
'```bash',
|
|
315
|
-
`curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \\`,
|
|
316
|
-
` "${baseUrl}/rest/api/3/issue/$TASK_ID/comment" \\`,
|
|
317
|
-
` -d '<ADF_BODY>'`,
|
|
318
|
-
'```',
|
|
319
|
-
'For plain text: `{"type":"text","text":"..."}`',
|
|
320
|
-
'For clickable URLs: `{"type":"inlineCard","attrs":{"url":"https://..."}}`',
|
|
321
|
-
'Example body with a link:',
|
|
322
|
-
'```json',
|
|
323
|
-
'{"body":{"type":"doc","version":1,"content":[{"type":"paragraph","content":[{"type":"text","text":"Session: "},{"type":"inlineCard","attrs":{"url":"https://agentdesk.live/sessions/..."}}]}]}}',
|
|
324
|
-
'```',
|
|
325
|
-
].join('\n'),
|
|
326
|
-
transitionToReview: [
|
|
327
|
-
'Transition task to "In Review" on Jira:',
|
|
328
|
-
'```bash',
|
|
329
|
-
`# First get available transitions:`,
|
|
330
|
-
`curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" "${baseUrl}/rest/api/3/issue/$TASK_ID/transitions"`,
|
|
331
|
-
`# Then transition (find the "In Review" transition ID):`,
|
|
332
|
-
`curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \\`,
|
|
333
|
-
` "${baseUrl}/rest/api/3/issue/$TASK_ID/transitions" \\`,
|
|
334
|
-
` -d '{"transition":{"id":"<TRANSITION_ID>"}}'`,
|
|
335
|
-
'```',
|
|
336
|
-
].join('\n'),
|
|
337
|
-
postSummary: [
|
|
338
|
-
`Post final summary comment on Jira (use comment endpoint above).`,
|
|
339
|
-
].join('\n'),
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (tracker === "github") {
|
|
344
|
-
return {
|
|
345
|
-
postPrLink: [
|
|
346
|
-
'Reference the issue in the PR body ("Closes #$TASK_ID") and post a comment:',
|
|
347
|
-
'```bash',
|
|
348
|
-
'gh issue comment $TASK_ID --body "PR created: $PR_URL"',
|
|
349
|
-
'```',
|
|
350
|
-
].join('\n'),
|
|
351
|
-
attachScreenshots: [
|
|
352
|
-
'Upload screenshots and post them in a GitHub issue comment:',
|
|
353
|
-
'```bash',
|
|
354
|
-
'BODY=""',
|
|
355
|
-
'for f in screenshots/*.png; do',
|
|
356
|
-
' BASE64=$(base64 < "$f")',
|
|
357
|
-
' BODY="$BODY\\n"',
|
|
358
|
-
'done',
|
|
359
|
-
'gh issue comment $TASK_ID --body "$BODY"',
|
|
360
|
-
'```',
|
|
361
|
-
].join('\n'),
|
|
362
|
-
agentComment: [
|
|
363
|
-
'Post a comment on GitHub with key findings:',
|
|
364
|
-
'```bash',
|
|
365
|
-
'gh issue comment $TASK_ID --body "<COMMENT>"',
|
|
366
|
-
'```',
|
|
367
|
-
].join('\n'),
|
|
368
|
-
transitionToReview: [
|
|
369
|
-
'Add "in review" label on GitHub:',
|
|
370
|
-
'```bash',
|
|
371
|
-
'gh issue edit $TASK_ID --remove-label "in progress" --add-label "in review" 2>/dev/null || true',
|
|
372
|
-
'```',
|
|
373
|
-
].join('\n'),
|
|
374
|
-
postSummary: [
|
|
375
|
-
'Post final summary comment on GitHub:',
|
|
376
|
-
'```bash',
|
|
377
|
-
'gh issue comment $TASK_ID --body "<SUMMARY>"',
|
|
378
|
-
'```',
|
|
379
|
-
].join('\n'),
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
return null;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Generate the dynamic parts of the prompt from the team.
|
|
388
|
-
* @param {Array} team - resolved team array
|
|
389
|
-
* @param {Object} opts - optional { tracker, config } for tracker-specific execution steps
|
|
390
|
-
*/
|
|
391
|
-
export function generateTeamPrompt(team, opts = {}) {
|
|
392
|
-
const count = team.length;
|
|
393
|
-
const agentWord = count === 1 ? "one AI agent" : `${count} AI agents`;
|
|
394
|
-
|
|
395
|
-
const sections = {};
|
|
396
|
-
|
|
397
|
-
// Agent list
|
|
398
|
-
sections.agentList = team.map(a =>
|
|
399
|
-
`- ${a.name} (${a.role}) — ${a.description}`
|
|
400
|
-
).join("\n");
|
|
401
|
-
|
|
402
|
-
// Speaking order
|
|
403
|
-
sections.speakingOrder = team.map((a, i) =>
|
|
404
|
-
`${i + 1}. ${a.badge}`
|
|
405
|
-
).join("\n");
|
|
406
|
-
|
|
407
|
-
// Ground rules
|
|
408
|
-
const rules = [];
|
|
409
|
-
rules.push(`1. Each agent speaks prefixed with their badge (e.g., "${team[0].badge} ...").`);
|
|
410
|
-
rules.push(`2. ALL text output MUST be prefixed with the acting agent's badge.`);
|
|
411
|
-
rules.push(`3. Raise concerns when you see problems — don't rubber-stamp.`);
|
|
412
|
-
rules.push(`4. **No announcement without observation.** Before any agent claims "done", "deployed", "fixed", "passes", "works", "ready", "now shows X", or "should work" — run the check that would falsify it: execute the command and read the output, fetch the live endpoint and grep the response, load the page and inspect the DOM, verify the deployed artifact contains the new code. "The code looks right" / "the CI is green" / "the DB has the row" are intermediate signals, NOT proof of user-visible outcome. If the observation is out of reach (auth, browser state, external system), say so plainly and ask the user to confirm — don't paper over with confident language. If a fix didn't land on the first try, stop and re-diagnose from scratch: repeated near-misses mean the hypothesis is wrong, not that one more patch will do it.`);
|
|
413
|
-
let ruleNum = 5;
|
|
414
|
-
for (const a of team) {
|
|
415
|
-
if (a.groundRules) {
|
|
416
|
-
rules.push(`${ruleNum}. ${a.groundRules}`);
|
|
417
|
-
ruleNum++;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
sections.groundRules = rules.join("\n");
|
|
421
|
-
|
|
422
|
-
// Code principles
|
|
423
|
-
const principles = team
|
|
424
|
-
.filter(a => a.codePrinciple)
|
|
425
|
-
.map(a => `- ${a.name}: ${a.codePrinciple}`);
|
|
426
|
-
sections.codePrinciples = principles.length > 0 ? principles.join("\n") : "";
|
|
427
|
-
|
|
428
|
-
// Planning presentations
|
|
429
|
-
sections.planningOrder = team.map(a =>
|
|
430
|
-
`${a.badge} ${a.planning}`
|
|
431
|
-
).join("\n");
|
|
432
|
-
|
|
433
|
-
// Execution steps — inject tracker-specific commands
|
|
434
|
-
const cmds = trackerCommands(opts.tracker, opts.config);
|
|
435
|
-
const execAgents = team
|
|
436
|
-
.filter(a => a.execution)
|
|
437
|
-
.sort((a, b) => a.execution.order - b.execution.order);
|
|
438
|
-
let stepNum = 1;
|
|
439
|
-
const execSteps = [];
|
|
440
|
-
for (const a of execAgents) {
|
|
441
|
-
let tasks = [...a.execution.tasks];
|
|
442
|
-
|
|
443
|
-
if (cmds) {
|
|
444
|
-
// Bart: expand PR link + tracker comment tasks with actual commands
|
|
445
|
-
if (a.name === "Bart") {
|
|
446
|
-
tasks = tasks.map(t => {
|
|
447
|
-
if (/PR link/i.test(t) || /Post PR.*tracker/i.test(t)) {
|
|
448
|
-
return `Push and create PR.\n ${cmds.postPrLink}`;
|
|
449
|
-
}
|
|
450
|
-
if (/screenshot.*tracker/i.test(t)) {
|
|
451
|
-
return `Upload screenshots to tracker, then post a comment.\n ${cmds.attachScreenshots}\n ${cmds.agentComment.replace('<COMMENT>', 'Screenshots attached — see attachments above.')}`;
|
|
452
|
-
}
|
|
453
|
-
return t;
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// Jane: expand transition + summary tasks
|
|
458
|
-
if (a.name === "Jane") {
|
|
459
|
-
tasks = tasks.map(t => {
|
|
460
|
-
if (/transition.*in review/i.test(t) || /move.*in review/i.test(t)) {
|
|
461
|
-
return `Transition task to "In Review".\n ${cmds.transitionToReview}`;
|
|
462
|
-
}
|
|
463
|
-
if (/summary.*tracker/i.test(t) || /final summary/i.test(t)) {
|
|
464
|
-
return `Post final summary comment on tracker.\n ${cmds.postSummary}`;
|
|
465
|
-
}
|
|
466
|
-
return t;
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// Dennis & Sam: expand tracker comment
|
|
471
|
-
if (a.name === "Dennis" || a.name === "Sam") {
|
|
472
|
-
const hasTrackerTask = tasks.some(t => /tracker/i.test(t) || /comment/i.test(t));
|
|
473
|
-
if (!hasTrackerTask) {
|
|
474
|
-
const findingsLabel = a.name === "Dennis" ? "Files changed, technical decisions" : "Architecture concerns or clean audit";
|
|
475
|
-
tasks.push(`Post comment on tracker with key findings (${findingsLabel}).\n ${cmds.agentComment}`);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
const taskList = tasks.map((t, i) => `${i + 1}. ${t}`).join("\n");
|
|
481
|
-
execSteps.push(`### Step ${stepNum} — ${a.name} ${a.execution.step.replace(/^[A-Za-z]+ /, "")}:\n${taskList}`);
|
|
482
|
-
stepNum++;
|
|
483
|
-
}
|
|
484
|
-
sections.executionSteps = execSteps.join("\n\n");
|
|
485
|
-
|
|
486
|
-
// Count and phrasing
|
|
487
|
-
sections.count = count;
|
|
488
|
-
sections.agentWord = agentWord;
|
|
489
|
-
sections.names = team.map(a => a.name);
|
|
490
|
-
|
|
491
|
-
return sections;
|
|
492
|
-
}
|
package/cli/bootstrap.mjs
CHANGED
|
@@ -10,24 +10,30 @@
|
|
|
10
10
|
// This module also exports reusable building blocks so `agentdesk init` can
|
|
11
11
|
// short-circuit to the bootstrap flow when it detects a fresh clone.
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { writeFileSync } from "fs";
|
|
14
14
|
import { join } from "path";
|
|
15
|
-
import { execSync } from "child_process";
|
|
16
15
|
import { loadConfig } from "./config.mjs";
|
|
17
16
|
import { getStoredApiKey } from "./login.mjs";
|
|
18
17
|
import { checkTrackerPermissions, resolveCredentialsFromEnv } from "./tracker-check.mjs";
|
|
19
|
-
import { assertPushable, PreflightError } from "./session-preflight.mjs";
|
|
20
|
-
import { select, promptRequired } from "./prompts.mjs";
|
|
18
|
+
import { assertPushable, PreflightError, getGitRemoteUrl } from "./session-preflight.mjs";
|
|
19
|
+
import { select, promptRequired, promptSecret } from "./prompts.mjs";
|
|
20
|
+
import { loadDotEnv, saveEnvVar } from "./dotenv.mjs";
|
|
21
|
+
import {
|
|
22
|
+
fetchGitHubLogin,
|
|
23
|
+
parseGitHubRef,
|
|
24
|
+
printAccessChecks,
|
|
25
|
+
readLocalConfig,
|
|
26
|
+
runAccessChecks,
|
|
27
|
+
} from "./setup-helpers.mjs";
|
|
21
28
|
|
|
22
29
|
const SERVER = process.env.AGENTDESK_SERVER || "https://agentdesk.live";
|
|
23
30
|
|
|
31
|
+
// Shares parseGitHubRef with init so both entry points agree on what counts
|
|
32
|
+
// as a GitHub remote (the private regex here rejected repo names with dots).
|
|
24
33
|
function detectGitRemote(dir) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (m) return `${m[1]}/${m[2]}`;
|
|
29
|
-
} catch {}
|
|
30
|
-
return null;
|
|
34
|
+
const url = getGitRemoteUrl(dir);
|
|
35
|
+
const parsed = url ? parseGitHubRef(url) : null;
|
|
36
|
+
return parsed ? `${parsed.owner}/${parsed.repo}` : null;
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
async function fetchProjects(apiKey) {
|
|
@@ -162,37 +168,6 @@ export function writeProjectConfig(cwd, projectKey) {
|
|
|
162
168
|
writeFileSync(join(cwd, ".agentdesk.json"), JSON.stringify({ projectKey }, null, 2) + "\n");
|
|
163
169
|
}
|
|
164
170
|
|
|
165
|
-
function saveEnvVar(dir, key, value) {
|
|
166
|
-
const envPath = join(dir, ".env");
|
|
167
|
-
let content = existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
|
|
168
|
-
const re = new RegExp(`^${key}=.*$`, "m");
|
|
169
|
-
if (re.test(content)) {
|
|
170
|
-
content = content.replace(re, `${key}=${value}`);
|
|
171
|
-
} else {
|
|
172
|
-
content += `${content && !content.endsWith("\n") ? "\n" : ""}${key}=${value}\n`;
|
|
173
|
-
}
|
|
174
|
-
writeFileSync(envPath, content);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function loadDotEnvLocal(dir) {
|
|
178
|
-
const envPath = join(dir, ".env");
|
|
179
|
-
const out = {};
|
|
180
|
-
if (!existsSync(envPath)) return out;
|
|
181
|
-
for (const line of readFileSync(envPath, "utf-8").split("\n")) {
|
|
182
|
-
const trimmed = line.trim();
|
|
183
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
184
|
-
const eq = trimmed.indexOf("=");
|
|
185
|
-
if (eq !== -1) out[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
|
|
186
|
-
}
|
|
187
|
-
return out;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function readLocalConfig(cwd) {
|
|
191
|
-
const path = join(cwd, ".agentdesk.json");
|
|
192
|
-
if (!existsSync(path)) return null;
|
|
193
|
-
try { return JSON.parse(readFileSync(path, "utf-8")); } catch { return null; }
|
|
194
|
-
}
|
|
195
|
-
|
|
196
171
|
// Compact step list — the init wizard carries the full narrative; bootstrap
|
|
197
172
|
// assumes the user has already seen it on their first machine.
|
|
198
173
|
function printTrackerHint(tracker) {
|
|
@@ -211,7 +186,7 @@ async function promptTrackerCreds(cwd, tracker, existing) {
|
|
|
211
186
|
if (!creds.LINEAR_API_KEY) {
|
|
212
187
|
console.log("");
|
|
213
188
|
printTrackerHint("linear");
|
|
214
|
-
const v = await
|
|
189
|
+
const v = await promptSecret("Linear API key");
|
|
215
190
|
saveEnvVar(cwd, "LINEAR_API_KEY", v);
|
|
216
191
|
creds.LINEAR_API_KEY = v;
|
|
217
192
|
console.log(" ✓ Saved LINEAR_API_KEY to .env");
|
|
@@ -227,7 +202,7 @@ async function promptTrackerCreds(cwd, tracker, existing) {
|
|
|
227
202
|
}
|
|
228
203
|
if (!creds.JIRA_API_TOKEN) {
|
|
229
204
|
if (creds.JIRA_EMAIL) console.log("");
|
|
230
|
-
const v = await
|
|
205
|
+
const v = await promptSecret("Jira API token");
|
|
231
206
|
saveEnvVar(cwd, "JIRA_API_TOKEN", v);
|
|
232
207
|
creds.JIRA_API_TOKEN = v;
|
|
233
208
|
console.log(" ✓ Saved JIRA_API_TOKEN to .env");
|
|
@@ -239,7 +214,7 @@ async function promptTrackerCreds(cwd, tracker, existing) {
|
|
|
239
214
|
async function promptGitHubToken(cwd) {
|
|
240
215
|
console.log("");
|
|
241
216
|
printTrackerHint("github");
|
|
242
|
-
const v = await
|
|
217
|
+
const v = await promptSecret("GitHub token");
|
|
243
218
|
saveEnvVar(cwd, "GITHUB_TOKEN", v);
|
|
244
219
|
console.log(" ✓ Saved GITHUB_TOKEN to .env");
|
|
245
220
|
return v;
|
|
@@ -250,7 +225,9 @@ async function promptGitHubToken(cwd) {
|
|
|
250
225
|
// are missing or broken, prompt for them, validate. Writes to `.env` only.
|
|
251
226
|
// Returns { trackerOk, githubOk } for the caller to surface.
|
|
252
227
|
export async function gapScan({ cwd, apiKey, projectKey }) {
|
|
253
|
-
|
|
228
|
+
// readOnly: bootstrap's contract is "writes to .env only". loadConfig's
|
|
229
|
+
// default behaviour rewrote .agentdesk.json and pushed to the server.
|
|
230
|
+
const config = await loadConfig(cwd, { apiKey, serverUrl: SERVER, projectName: projectKey, silent: true, readOnly: true });
|
|
254
231
|
const tracker = config.tracker || null;
|
|
255
232
|
const repo = config.github?.repo || null;
|
|
256
233
|
const login = config.github?.login || null;
|
|
@@ -262,7 +239,7 @@ export async function gapScan({ cwd, apiKey, projectKey }) {
|
|
|
262
239
|
if (repo) console.log(` Repo: ${repo}${login ? ` (@${login})` : ""}`);
|
|
263
240
|
console.log("");
|
|
264
241
|
|
|
265
|
-
let creds = resolveCredentialsFromEnv(
|
|
242
|
+
let creds = resolveCredentialsFromEnv(loadDotEnv(cwd));
|
|
266
243
|
let trackerOk = !tracker;
|
|
267
244
|
let githubOk = false;
|
|
268
245
|
let attempts = 0;
|
|
@@ -309,16 +286,22 @@ export async function gapScan({ cwd, apiKey, projectKey }) {
|
|
|
309
286
|
if (!creds.GITHUB_TOKEN) creds.GITHUB_TOKEN = await promptGitHubToken(cwd);
|
|
310
287
|
try {
|
|
311
288
|
assertPushable({ cwd, creds: { GITHUB_TOKEN: creds.GITHUB_TOKEN }, projectName: projectKey });
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
289
|
+
// REST call, same as init. This used to shell out to `gh`, so a
|
|
290
|
+
// machine without the gh CLI got no identity check at all, and an
|
|
291
|
+
// invalid token was printed as "✓ present" and accepted. An
|
|
292
|
+
// unverifiable token now goes through the same retry/skip prompt as a
|
|
293
|
+
// missing one.
|
|
294
|
+
const who = await fetchGitHubLogin(creds.GITHUB_TOKEN);
|
|
295
|
+
if (!who) {
|
|
296
|
+
throw new PreflightError(
|
|
297
|
+
"GITHUB_TOKEN did not authenticate with GitHub (invalid token, or offline)",
|
|
298
|
+
{ code: "INVALID_GITHUB_TOKEN" }
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
if (login && who.toLowerCase() !== login.toLowerCase()) {
|
|
302
|
+
console.log(` ⚠ GitHub token authenticates as @${who}, not @${login} (from .agentdesk.json). Agents will post as @${who}.`);
|
|
303
|
+
} else {
|
|
304
|
+
console.log(` ✓ GitHub verified as @${who}`);
|
|
322
305
|
}
|
|
323
306
|
githubOk = true;
|
|
324
307
|
} catch (err) {
|
|
@@ -363,7 +346,7 @@ export async function runBootstrap(cwd = process.cwd()) {
|
|
|
363
346
|
process.exit(1);
|
|
364
347
|
}
|
|
365
348
|
|
|
366
|
-
|
|
349
|
+
const localConfig = readLocalConfig(cwd);
|
|
367
350
|
let projectKey = localConfig?.projectKey || null;
|
|
368
351
|
|
|
369
352
|
if (!projectKey) {
|
|
@@ -383,8 +366,6 @@ export async function runBootstrap(cwd = process.cwd()) {
|
|
|
383
366
|
// Final access-checks summary — same checklist the new-project wizard
|
|
384
367
|
// prints at the end of its own flow, so both entry points finish
|
|
385
368
|
// consistently.
|
|
386
|
-
const { loadDotEnv } = await import("./setup-helpers.mjs");
|
|
387
|
-
const { runAccessChecks, printAccessChecks } = await import("./setup-helpers.mjs");
|
|
388
369
|
const creds = loadDotEnv(cwd);
|
|
389
370
|
const results = await runAccessChecks({ apiKey, serverUrl: SERVER, cwd, config, creds });
|
|
390
371
|
printAccessChecks(results, { tracker: config?.tracker });
|