Skip to content
This repository was archived by the owner on Mar 30, 2026. It is now read-only.

feat: add onboard hook type to plugin manifest schema (AGE-208)#152

Merged
stepandel merged 10 commits intomainfrom
feat/AGE-208-onboard-hook
Mar 1, 2026
Merged

feat: add onboard hook type to plugin manifest schema (AGE-208)#152
stepandel merged 10 commits intomainfrom
feat/AGE-208-onboard-hook

Conversation

@stepandel
Copy link
Copy Markdown
Owner

@stepandel stepandel commented Feb 27, 2026

Summary

Adds an onboard hook type to the plugin manifest schema for first-time interactive plugin setup flows (e.g., creating a Slack app).

Cherry-picked from the merged feat/manifest-hooks-schema branch onto a fresh branch rebased against latest main.

Changes

  • OnboardHookSchema with description, inputs (envVar/prompt/instructions/validator), script, runOnce
  • runOnboardHook() in manifest-hooks.ts — captures stdout for instructions, streams stderr, timeout protection
  • Wired into setup.ts between secret validation and Pulumi config
  • runOnce support: skips if all required secrets already present
  • Interactive input prompting with env var override and prefix validation
  • --skip-onboard CLI flag
  • 10 new tests (4 schema + 5 execution + 1 edge case)

Bug fix included

Fixed the runOnce secret check that Scout flagged — was comparing raw plugin keys against agent-scoped resolved secrets. Now uses envVar-based env dict lookup which correctly detects already-configured secrets.

Tests: 206 passing | Build: ✅ green

Closes AGE-208

Summary by CodeRabbit

  • New Features

    • New standalone "onboard" CLI command, a setup --onboard option and --env-file support to run plugin onboarding.
    • Plugins may declare interactive onboard hooks with env/prompts, run-once semantics, and follow-up instructions.
    • Public API expanded to expose onboard hook schemas and an onboarding hook execution surface.
    • Added secret-redaction for onboarding output.
  • Tests

    • Added unit and comprehensive end-to-end tests validating onboard hook execution, prompts, run-once behavior, and error cases.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 27, 2026

📝 Walkthrough

Walkthrough

Adds an onboarding feature: schema and manifest support for onboard hooks, runtime execution (runOnboardHook), CLI commands and setup wiring to run hooks, an orchestrator to run per-agent/plugin onboard flows, a redaction utility, and unit + E2E tests and fixtures.

Changes

Cohort / File(s) Summary
Schemas & Manifest
packages/core/src/schemas/plugin-manifest.ts, packages/core/src/schemas/index.ts, packages/core/src/index.ts
Add OnboardHookInputSchema and OnboardHookSchema, export them from barrels, and include optional onboard in PluginHooksSchema.
Hook runtime & API
packages/core/src/manifest-hooks.ts, packages/core/src/__tests__/manifest-hooks.test.ts
Add runOnboardHook API and tests: spawn script, capture stdout as instructions, stream stderr, support timeout and non-zero-exit handling.
CLI command & setup integration
packages/cli/commands/onboard.ts, packages/cli/commands/setup.ts, packages/cli/bin.ts
Add onboardCommand and top-level onboard CLI command; add --onboard flag to setup; wire manifest load, identity resolution, env/.env loading, secret merging, and call into onboarding orchestrator.
Onboard orchestration & utils
packages/cli/lib/onboard-hooks.ts, packages/cli/lib/redact.ts
Add runOnboardHooks to orchestrate per-agent/plugin onboarding (prompts, runOnce checks, env assembly, error handling) and redactSecretsFromString to sanitize displayed instructions.
E2E tests & fixtures
packages/cli/__e2e__/onboard-hooks.e2e.test.ts, packages/cli/__e2e__/onboard-command.e2e.test.ts, packages/cli/__e2e__/helpers/fixtures/plugin-identity/plugins/test-linear.yaml, packages/cli/__e2e__/plugins.e2e.test.ts
Add E2E suites and a fixture for onboard hooks; cover env-driven, interactive, skip/runOnce, and error scenarios; add test env var for token.
Plugin manifest unit tests
packages/core/src/__tests__/plugin-manifest.test.ts
Add tests validating onboard hook schema: presence, non-empty script requirement, inputs handling, and default runOnce behavior.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as CLI (onboard/setup)
    participant PluginMgr as Plugin Manager
    participant Onboard as Onboard Orchestrator
    participant HookExec as runOnboardHook (Shell)
    participant Env as Env/.env & Secrets

    User->>CLI: run setup or onboard (flags)
    alt skipOnboard true
        CLI->>User: Log "Onboarding skipped"
    else
        CLI->>PluginMgr: load manifest & identities
        PluginMgr->>PluginMgr: build agent→plugins map
        CLI->>Env: load .env and resolved secrets
        PluginMgr->>Onboard: for each plugin with onboard hook
        Onboard->>Env: check env vars / autoResolvedSecrets
        alt runOnce already satisfied
            Onboard->>User: Log "skipped (already configured)"
        else
            alt inputs from env
                Env->>Onboard: provide input values
            else prompt needed
                Onboard->>User: Prompt for input
                User->>Onboard: Provide input / cancel
            end
            Onboard->>HookExec: spawn script with assembled env
            HookExec->>Onboard: return success {instructions} or error
            alt success
                Onboard->>CLI: redact & print instructions
            else failure
                Onboard->>CLI: exitWithError (message)
            end
        end
    end
    CLI->>User: Setup complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through schemas, scripts, and prompts,
Gathered envs and guarded secrets' fonts,
Spawned a hook that whispered next-step signs,
Redacted tokens, printed friendly lines,
Hooray — onboarding blooms with carrot shines! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding an onboard hook type to the plugin manifest schema, supporting first-time interactive plugin setup flows.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/AGE-208-onboard-hook

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@stepandel
Copy link
Copy Markdown
Owner Author

🔍 QA Approved

Build: ✅ Clean
Unit tests: ✅ 206/206 (10 new — 4 schema, 5 execution, 1 resolve edge case)

Verified against AGE-208 Definition of Done:

  • OnboardHookInputSchema: envVar, prompt, instructions, validator fields
  • OnboardHookSchema: description, inputs, script, runOnce fields
  • PluginHooksSchema includes optional onboard field
  • runOnboardHook(): executes script with input env vars, captures stdout for instructions, streams stderr, 120s default timeout
  • setup.ts: checks onboard hooks after secret validation, skips with runOnce when secrets present, prompts interactively or reads from env, displays follow-up instructions
  • --skip-onboard flag bypasses onboard hooks
  • ✅ Input validator works (prefix check)
  • ✅ Schema validation: valid hook ✅, missing script ❌, empty script ❌, empty inputs ✅
  • ✅ Execution tests: stdout capture, env var passing, empty stdout, timeout, non-zero exit
  • ✅ All existing hook tests still pass

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/cli/commands/setup.ts (1)

382-479: Extract onboard orchestration into a dedicated module/helper.

This block is substantial and pushes an already large file further past the size guideline, making maintenance harder.

As per coding guidelines: "Keep files under 300 lines; split large files into smaller modules".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/commands/setup.ts` around lines 382 - 479, Extract the large
onboard orchestration block into a new helper function (e.g., runOnboardHooks or
onboardOrchestrator) and move it to its own module; the function should accept
the required inputs (fetchedIdentities, agentPlugins, resolvePlugin,
autoResolvedSecrets, envDict, resolvedSecrets, p, runOnboardHook, exitWithError,
and opts.skipOnboard) and return/throw errors as currently handled. Replace the
in-file loop with a single awaited call to runOnboardHooks(...) and keep all
original behavior (runOnce logic, env merging, interactive prompts, logging via
p.log, and handling of runOnboardHook results) so callers and tests need only
wiring changes. Ensure you export the new function and import it where used, and
add small unit tests around runOnce skipping, env-derived secret population
(envDerivedKey logic), and error path to preserve coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/__e2e__/onboard-hooks.e2e.test.ts`:
- Around line 310-314: The test mutates a shared fixture by writing into
PLUGIN_IDENTITY_DIR/plugins/test-linear.yaml (variables manifestPath and
originalManifest); instead, create a per-test copy of the identity fixture
(e.g., copy PLUGIN_IDENTITY_DIR to a temp directory or copy the single plugin
file to a tmp folder), update manifestPath to point to that per-test copy, then
modify the copied file (replace "runOnce: false" -> "runOnce: true") so the
original fixture is never mutated; apply the same change for the similar patch
at the other occurrence (lines referencing the same manifest modification).

In `@packages/cli/commands/setup.ts`:
- Around line 463-467: The code prints raw hook output via result.instructions
(in the setup command handler in packages/cli/commands/setup.ts) which may
contain secrets; update the handler so that before calling p.log.info and
console.log you sanitize/redact sensitive values from result.instructions (e.g.,
redact values that match known env var names like API_KEY, TOKEN, SECRET, or any
patterns for keys/credentials) by applying a helper such as
redactSecretsFromString(result.instructions) and then log/display the redacted
string; ensure you reuse the same redaction helper for any other hook outputs
and keep the original secret-containing string out of logs or console output.
- Around line 396-410: The current runOnce check treats an empty set of required
secrets as "all present" because .every returns true for empty arrays; change
the logic around onboard.runOnce so you first collect required secrets from
pluginManifest.secrets (e.g., const requiredSecrets =
Object.entries(pluginManifest.secrets).filter(([, s]) => s.required)), then
compute allSecretsPresent only if requiredSecrets.length > 0 (e.g.,
requiredSecrets.length > 0 && requiredSecrets.every(...)); use the same keys
(key, secret) and existing checks against
autoResolvedSecrets[fi.agent.role]?.[key] and
envDict[`${roleUpper}_${secret.envVar}`] so onboarding is not skipped when there
are no required secrets.

---

Nitpick comments:
In `@packages/cli/commands/setup.ts`:
- Around line 382-479: Extract the large onboard orchestration block into a new
helper function (e.g., runOnboardHooks or onboardOrchestrator) and move it to
its own module; the function should accept the required inputs
(fetchedIdentities, agentPlugins, resolvePlugin, autoResolvedSecrets, envDict,
resolvedSecrets, p, runOnboardHook, exitWithError, and opts.skipOnboard) and
return/throw errors as currently handled. Replace the in-file loop with a single
awaited call to runOnboardHooks(...) and keep all original behavior (runOnce
logic, env merging, interactive prompts, logging via p.log, and handling of
runOnboardHook results) so callers and tests need only wiring changes. Ensure
you export the new function and import it where used, and add small unit tests
around runOnce skipping, env-derived secret population (envDerivedKey logic),
and error path to preserve coverage.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 130c4ce and 9a43ca0.

📒 Files selected for processing (11)
  • packages/cli/__e2e__/helpers/fixtures/plugin-identity/plugins/test-linear.yaml
  • packages/cli/__e2e__/onboard-hooks.e2e.test.ts
  • packages/cli/__e2e__/plugins.e2e.test.ts
  • packages/cli/bin.ts
  • packages/cli/commands/setup.ts
  • packages/core/src/__tests__/manifest-hooks.test.ts
  • packages/core/src/__tests__/plugin-manifest.test.ts
  • packages/core/src/index.ts
  • packages/core/src/manifest-hooks.ts
  • packages/core/src/schemas/index.ts
  • packages/core/src/schemas/plugin-manifest.ts

@stepandel
Copy link
Copy Markdown
Owner Author

🔍 Re-test after 9a43ca0 (merged #111 + new E2E tests)

Unit tests: ✅ 242/242 (10 more from merged #111 provider tests)
E2E tests: 4 new onboard E2E tests fail — same root cause as other E2E failures (Pulumi binary not available in test env, setupCommand exits before reaching onboard hooks). Not a regression.

Unit test coverage for onboard hooks remains solid (5 execution + 4 schema tests). QA approval stands.

@stepandel
Copy link
Copy Markdown
Owner Author

QA update (head 9a43ca0):

Local verification:

  • pnpm install
  • pnpm build ✅
  • pnpm test ✅ (242/242)

E2E: failing due to Pulumi binary not available in test env / suite assumptions (known issue; not a regression from this PR).

@stepandel
Copy link
Copy Markdown
Owner Author

Pushed fixup commit c5c97b3 addressing CodeRabbit items:

  • Fix onboard runOnce skip logic: only skip when there is at least 1 required secret AND all required secrets are present.
  • Redact likely secrets from onboard hook instructions before printing/logging.

Verification:

  • pnpm build ✅
  • pnpm test ✅ (242/242)

Diff: adds packages/cli/lib/redact.ts and wires into setup.ts.

@stepandel
Copy link
Copy Markdown
Owner Author

Pushed refactor commit c878878:

  • Extracted onboard hook orchestration from packages/cli/commands/setup.ts into packages/cli/lib/onboard-hooks.ts (runOnboardHooks)
  • setup.ts now delegates via await runOnboardHooks(...)

Verification:

  • pnpm build ✅
  • pnpm test ✅ (242/242)

Behavior preserved (runOnce logic, env merging, prompting, redaction, error handling).

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
packages/cli/__e2e__/onboard-hooks.e2e.test.ts (1)

309-313: ⚠️ Potential issue | 🟠 Major

Avoid mutating the shared plugin fixture in-place.

At Line 310, this test edits packages/cli/__e2e__/helpers/fixtures/plugin-identity/plugins/test-linear.yaml directly. That can leak state across tests/workers.

🔧 Proposed fix
-    const manifestPath = path.join(PLUGIN_IDENTITY_DIR, "plugins", "test-linear.yaml");
-    const originalManifest = fs.readFileSync(manifestPath, "utf-8");
-    fs.writeFileSync(manifestPath, originalManifest.replace("runOnce: false", "runOnce: true"), "utf-8");
+    const identityDirForRunOnce = path.join(tempDir, `plugin-identity-runonce-${Date.now()}`);
+    fs.cpSync(PLUGIN_IDENTITY_DIR, identityDirForRunOnce, { recursive: true });
+    const manifestPath = path.join(identityDirForRunOnce, "plugins", "test-linear.yaml");
+    const originalManifest = fs.readFileSync(manifestPath, "utf-8");
+    fs.writeFileSync(manifestPath, originalManifest.replace("runOnce: false", "runOnce: true"), "utf-8");
@@
-        identityDir: PLUGIN_IDENTITY_DIR,
+        identityDir: identityDirForRunOnce,
@@
-    } finally {
-      // Restore original manifest
-      fs.writeFileSync(manifestPath, originalManifest, "utf-8");
-    }
+    } finally {
+      fs.rmSync(identityDirForRunOnce, { recursive: true, force: true });
+    }

Also applies to: 340-343

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/__e2e__/onboard-hooks.e2e.test.ts` around lines 309 - 313, The
test currently mutates the shared fixture file by writing to manifestPath
(constructed from PLUGIN_IDENTITY_DIR + "plugins/test-linear.yaml"); instead,
avoid in-place edits by creating a temporary copy of the fixture, modify the
copy to set runOnce: true, and point the code under test at that temp manifest
(or restore the original in a finally/after hook). Update the sections around
manifestPath/originalManifest so they copy the file to a temp directory (or use
a tmp file), perform the replace on the copy, and ensure cleanup to prevent
state leaking across tests/workers.
🧹 Nitpick comments (3)
packages/cli/lib/onboard-hooks.ts (1)

8-20: Use interfaces for object-shape definitions in this module.

At Line 8 and Line 30, object-shape type aliases are used where repo standards prefer interface.

♻️ Proposed refactor
-type PromptLike = {
+interface PromptLike {
   log: {
     info: (msg: string) => void;
     warn: (msg: string) => void;
     error: (msg: string) => void;
   };
   spinner: () => { start: (msg: string) => void; stop: (msg?: string) => void };
   text: (opts: {
     message: string;
     validate?: (val: string) => string | undefined;
   }) => Promise<string | symbol>;
   isCancel: (val: unknown) => boolean;
-};
+}
+
+interface RunOnboardHooksArgs {
+  fetchedIdentities: Array<{
+    agent: { name: string; role: string; displayName: string };
+    identityResult: IdentityResult;
+  }>;
+  agentPlugins: Map<string, Set<string>>;
+  resolvePlugin: (pluginName: string, identityResult: IdentityResult) => PluginManifest;
+  autoResolvedSecrets: Record<string, Record<string, string>>;
+  envDict: Record<string, string>;
+  resolvedSecrets: { perAgent: Record<string, Record<string, string>> };
+  p: PromptLike;
+  runOnboardHook: (opts: { script: string; env: Record<string, string> }) => Promise<
+    | { ok: true; instructions?: string }
+    | { ok: false; error: string }
+  >;
+  exitWithError: (message: string) => never;
+  skipOnboard: boolean;
+}
 
-export async function runOnboardHooks(args: {
-  ...
-}): Promise<void> {
+export async function runOnboardHooks(args: RunOnboardHooksArgs): Promise<void> {

As per coding guidelines: "Prefer interfaces over type aliases for object shapes in TypeScript".

Also applies to: 30-47

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/lib/onboard-hooks.ts` around lines 8 - 20, Replace the
object-shape type aliases with interfaces: change the PromptLike type alias to
an interface named PromptLike (keeping members log, spinner, text, isCancel and
their signatures intact) and likewise convert the other object-shaped type alias
at the later block (lines 30-47) to an interface with the same property names
and method signatures; ensure exported names and usages remain unchanged so code
referencing PromptLike, log.info/warn/error, spinner.start/stop, text(...), and
isCancel(...) continue to type-check.
packages/cli/__e2e__/onboard-hooks.e2e.test.ts (1)

1-345: Consider splitting this E2E file into smaller scenario-focused suites.

This new file is over 300 lines; splitting by flow (env input, interactive input, runOnce, skip) will make maintenance easier.

As per coding guidelines: "packages/**/*.{ts,tsx}: Keep files under 300 lines; split large files into smaller, focused modules".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/__e2e__/onboard-hooks.e2e.test.ts` around lines 1 - 345, The
test file is too large and should be split into smaller scenario-focused suites;
create separate test files (or describe blocks in their own files) for each flow
referenced by the current describe: "setup runs onboard hook and captures
instructions from stdout", "setup prompts for onboard input when not in env",
"setup skips onboard hooks by default", and "runOnce skips onboard when all
required secrets are present"; move related setup/teardown, mocks (the
`@clack/prompts`, ../lib/project, ../lib/workspace mocks and shared helpers like
createTestProject/forceCleanup), and constants (PLUGIN_IDENTITY_DIR,
E2E_ENV_KEYS) into a shared test helper module so each new file imports common
fixtures and only contains the specific it(...) test and any small per-test
setup (stackName/tempDir creation and unique extraEnvLines), leaving
beforeAll/afterAll minimal in each file and ensuring the ProcessExitError spy
and tempDir cleanup are handled via the shared helper.
packages/cli/commands/onboard.ts (1)

45-47: Use the CLI config module for manifest YAML handling in command code.

At Line 45, onboard.ts manually reads/parses YAML. This command should delegate manifest I/O to packages/cli/lib/config.ts for consistency with command-layer conventions.

As per coding guidelines: "packages/cli/commands/**/*.ts: Load and save YAML manifests using the config module in packages/cli/lib/config.ts".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/commands/onboard.ts` around lines 45 - 47, Replace the manual
fs/YAML parsing in the onboard command (the raw/parsed variables and
fs.readFileSync + YAML.parse usage around manifestPath) with the manifest load
helper from packages/cli/lib/config.ts (use the config module's exported
manifest loader function instead of reading/parsing directly); call that loader
with manifestPath to obtain the parsed manifest object, then continue to
validate with ClawupManifestSchema.safeParse on the returned object and handle
errors consistently with other CLI commands.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/__e2e__/onboard-hooks.e2e.test.ts`:
- Around line 202-205: The execSync call assigning result for "pulumi stack ls
--json" is unguarded and should be wrapped in a try-catch to match the existing
pattern used for other Pulumi shell-outs (the other calls that wrap in try-catch
at the same test file); update the block around the execSync invocation that
produces result so any thrown error is caught and ignored (or handled the same
way as the other three shell-outs) to prevent the test from hard-failing when
Pulumi is unavailable.

In `@packages/cli/commands/onboard.ts`:
- Around line 122-137: In the agents mapping passed to buildManifestSecrets, the
requiredSecrets field can be undefined due to optional chaining on
fetchedIdentities, violating the SecretsBuilderOpts type; update the mapping
(inside the agents.map callback that builds objects for buildManifestSecrets) to
coerce requiredSecrets to an array (e.g., use the nullish coalescing operator to
set requiredSecrets to [] when fi?.identityResult.manifest.requiredSecrets is
undefined) so buildManifestSecrets always receives a string[].

In `@packages/cli/lib/redact.ts`:
- Around line 11-12: The regex keyValue used to redact secrets only matches
uppercase names so lowercase/mixed keys like "token=" or "api_key=" slip
through; update the keyValue pattern in redact.ts (the const keyValue and the
subsequent input.replace call) to be case-insensitive (e.g., allow a-z or add
the /i flag) so it matches keys regardless of case, ensuring the replace still
returns `${key}=[REDACTED]` for the captured key and that out =
input.replace(keyValue, ...) continues to redact all token/secret-like keys.

---

Duplicate comments:
In `@packages/cli/__e2e__/onboard-hooks.e2e.test.ts`:
- Around line 309-313: The test currently mutates the shared fixture file by
writing to manifestPath (constructed from PLUGIN_IDENTITY_DIR +
"plugins/test-linear.yaml"); instead, avoid in-place edits by creating a
temporary copy of the fixture, modify the copy to set runOnce: true, and point
the code under test at that temp manifest (or restore the original in a
finally/after hook). Update the sections around manifestPath/originalManifest so
they copy the file to a temp directory (or use a tmp file), perform the replace
on the copy, and ensure cleanup to prevent state leaking across tests/workers.

---

Nitpick comments:
In `@packages/cli/__e2e__/onboard-hooks.e2e.test.ts`:
- Around line 1-345: The test file is too large and should be split into smaller
scenario-focused suites; create separate test files (or describe blocks in their
own files) for each flow referenced by the current describe: "setup runs onboard
hook and captures instructions from stdout", "setup prompts for onboard input
when not in env", "setup skips onboard hooks by default", and "runOnce skips
onboard when all required secrets are present"; move related setup/teardown,
mocks (the `@clack/prompts`, ../lib/project, ../lib/workspace mocks and shared
helpers like createTestProject/forceCleanup), and constants
(PLUGIN_IDENTITY_DIR, E2E_ENV_KEYS) into a shared test helper module so each new
file imports common fixtures and only contains the specific it(...) test and any
small per-test setup (stackName/tempDir creation and unique extraEnvLines),
leaving beforeAll/afterAll minimal in each file and ensuring the
ProcessExitError spy and tempDir cleanup are handled via the shared helper.

In `@packages/cli/commands/onboard.ts`:
- Around line 45-47: Replace the manual fs/YAML parsing in the onboard command
(the raw/parsed variables and fs.readFileSync + YAML.parse usage around
manifestPath) with the manifest load helper from packages/cli/lib/config.ts (use
the config module's exported manifest loader function instead of reading/parsing
directly); call that loader with manifestPath to obtain the parsed manifest
object, then continue to validate with ClawupManifestSchema.safeParse on the
returned object and handle errors consistently with other CLI commands.

In `@packages/cli/lib/onboard-hooks.ts`:
- Around line 8-20: Replace the object-shape type aliases with interfaces:
change the PromptLike type alias to an interface named PromptLike (keeping
members log, spinner, text, isCancel and their signatures intact) and likewise
convert the other object-shaped type alias at the later block (lines 30-47) to
an interface with the same property names and method signatures; ensure exported
names and usages remain unchanged so code referencing PromptLike,
log.info/warn/error, spinner.start/stop, text(...), and isCancel(...) continue
to type-check.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a43ca0 and 13edf2b.

📒 Files selected for processing (6)
  • packages/cli/__e2e__/onboard-hooks.e2e.test.ts
  • packages/cli/bin.ts
  • packages/cli/commands/onboard.ts
  • packages/cli/commands/setup.ts
  • packages/cli/lib/onboard-hooks.ts
  • packages/cli/lib/redact.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/__e2e__/onboard-command.e2e.test.ts`:
- Around line 186-219: The test mutates the shared fixture file via
manifestPath/originalManifest; instead, create and modify an isolated copy of
the fixture and point createTestProject at that copy so other tests aren't
affected. Concretely: copy the plugin identity fixture (or at minimum the
test-linear.yaml file) into a temporary directory, update the copied file to set
"runOnce: true" (keeping originalManifest for reference if needed), and pass the
temp identityDir into createTestProject; keep the finally block to clean up the
temp copy rather than rewriting the shared fixture. This change touches the
manifestPath/originalManifest handling and the createTestProject(identityDir)
call used before invoking onboardCommand.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13edf2b and 0d089a2.

📒 Files selected for processing (1)
  • packages/cli/__e2e__/onboard-command.e2e.test.ts

@stepandel
Copy link
Copy Markdown
Owner Author

Review comments addressed (074f479)

All actionable review comments have been addressed:

Comment Status Fix
Fixture mutation (Major) — runOnce tests mutate shared test-linear.yaml ✅ Fixed Both test files now fs.cpSync the fixture to an isolated temp dir and clean up in finally
runOnce empty array (Major) — .every() returns true for empty arrays ✅ Previously fixed Addressed in c5c97b3 with requiredSecrets.length > 0 && guard
Raw hook stdout (Major) — may expose secrets ✅ Previously fixed Addressed in c5c97b3 via redactSecretsFromString()
Unguarded execSync (Minor) — pulumi stack ls not in try-catch ✅ Fixed Wrapped in try-catch to match other Pulumi shell-outs
requiredSecrets nullability (Critical) — undefined passed where string[] expected ✅ Fixed Added ?? [] fallback
Redact regex case-insensitive (Major) — lowercase keys not caught ✅ Fixed Added /gi flag + quoted value support
typeinterface (Nitpick) — coding conventions ✅ Fixed Converted PromptLike and extracted RunOnboardHooksArgs

Not addressed (intentionally):

  • "Use config module for manifest loading" — onboardCommand needs Zod validation with detailed error messages that the config module's loadManifest/requireManifest don't provide
  • "Split test file >300 lines" — onboard-hooks.e2e.test.ts is 348 lines with heavy setup/teardown that would be duplicated across files; splitting would increase complexity without benefit

CI is green ✅

Titus and others added 10 commits March 1, 2026 01:39
- Add OnboardHookSchema with description, inputs, script, runOnce fields
- Add OnboardHookInputSchema with envVar, prompt, instructions, validator
- Add runOnboardHook() to manifest-hooks.ts (captures stdout for instructions)
- Wire onboard hooks into setup.ts between secret validation and Pulumi config
- Add --skip-onboard CLI flag
- runOnce support: skip if all required secrets are already present
- Interactive input prompting with env var override and prefix validation
- 10 new tests (4 schema + 5 execution + 1 resolve edge case)
- All 206 tests passing, build green
The runOnce check was comparing raw plugin secret keys against
resolvedSecrets.perAgent which uses agent-scoped keys. Now checks
the env dict using the plugin secret's envVar (prefixed with role),
which correctly detects already-configured secrets.

Fixes bug reported by Scout on AGE-208.
4 new E2E tests in onboard-hooks.e2e.test.ts:
1. Onboard hook runs and captures stdout instructions (input from env)
2. Onboard hook prompts interactively when input not in env (p.text mock)
3. --skip-onboard flag bypasses onboard hooks entirely
4. runOnce: true skips onboard when all required secrets are present

Also:
- Added echo-based onboard hook to test-linear fixture manifest
- Added TEST_LINEAR_CONFIG_TOKEN to plugin test env lines
- Merged main (PR #111 multi-provider changes)

All 242 unit tests + 28 E2E tests passing
…ommand

Flip the default so setup skips onboard hooks unless --onboard is passed,
and add `clawup onboard` as a standalone command for first-time interactive
plugin setup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cover the onboardCommand bootstrapping and hook execution without
Pulumi: env-var resolution, interactive prompting, runOnce skip,
missing .env error, and missing manifest error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The SecretsBuilderOpts interface on main requires allModels for
multi-provider API key resolution. Without it the merge build fails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…safety

- Use isolated copies of identity fixture in runOnce tests instead of
  mutating shared fixture in-place (both test files)
- Add case-insensitive flag to redact regex and handle quoted values
- Default requiredSecrets to [] when undefined in onboard command
- Wrap unguarded pulumi execSync in try-catch
- Convert type aliases to interfaces in onboard-hooks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After rebase onto main, agent schema fields (name, role, displayName)
became optional. Use resolveAgentSync to get ResolvedAgent with
required fields, matching setup.ts pattern.
@stepandel stepandel force-pushed the feat/AGE-208-onboard-hook branch from 074f479 to 2b23dd2 Compare March 1, 2026 01:43
@stepandel
Copy link
Copy Markdown
Owner Author

🔍 QA Re-verified (2b23dd2)

Build: ✅ Clean
Unit tests: ✅ 304/304 passed

Re-tested after new commits (refactoring, fixture isolation, type fixes). All AGE-208 acceptance criteria remain satisfied.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/cli/__e2e__/onboard-command.e2e.test.ts (1)

189-190: ⚠️ Potential issue | 🟡 Minor

Assert the runOnce fixture patch was applied.

This replace can silently no-op if the fixture changes shape, weakening the test intent.

Proposed reliability fix
-    const originalManifest = fs.readFileSync(manifestPath, "utf-8");
-    fs.writeFileSync(manifestPath, originalManifest.replace("runOnce: false", "runOnce: true"), "utf-8");
+    const originalManifest = fs.readFileSync(manifestPath, "utf-8");
+    const patchedManifest = originalManifest.replace("runOnce: false", "runOnce: true");
+    if (patchedManifest === originalManifest) {
+      throw new Error("Expected runOnce flag in test-linear fixture");
+    }
+    fs.writeFileSync(manifestPath, patchedManifest, "utf-8");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/__e2e__/onboard-command.e2e.test.ts` around lines 189 - 190, The
test currently overwrites the manifest using originalManifest.replace(...) which
may silently no-op; after writing, read the file back (or check the replaced
string) and assert that the fixture now contains "runOnce: true" (e.g., verify
fs.readFileSync(manifestPath, "utf-8") includes "runOnce: true" or assert that
originalManifest !== patched and that patched.includes("runOnce: true")), and
fail the test if the replacement did not occur so the test cannot silently
proceed when the fixture shape changes; reference the variables manifestPath and
originalManifest (and the write operation) to locate where to add this
assertion.
🧹 Nitpick comments (5)
packages/core/src/manifest-hooks.ts (1)

270-274: Consider redacting stderr output for onboard hooks.

Stderr is streamed directly to process.stderr without redaction. If an onboard script inadvertently echoes sensitive values to stderr (e.g., debug output), they would be visible in the terminal. The stdout instructions are redacted by the caller, but stderr is not.

This mirrors the existing runLifecycleHook behavior, so it may be intentional for real-time progress visibility. However, given the onboarding context where users may input tokens, consider whether stderr should pass through redactSecretsFromString.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/manifest-hooks.ts` around lines 270 - 274, The stderr
handler in child.stderr.on is writing raw stderr to process.stderr (accumulating
into the stderr variable) without redaction; update the handler in
manifest-hooks.ts to pass the chunk string through redactSecretsFromString
before appending to stderr and writing to process.stderr (mirroring how stdout
is handled), i.e., use redactSecretsFromString(msg) when updating the stderr
accumulator and when calling process.stderr.write; reference the child.stderr.on
listener, the stderr accumulator, and redactSecretsFromString to locate and
patch the logic (keeping behavior consistent with runLifecycleHook).
packages/cli/commands/setup.ts (2)

32-32: Unused import: redactSecretsFromString.

The redactSecretsFromString import is not used in setup.ts - it's only used in onboard-hooks.ts. This import can be removed.

Proposed fix
 import { runOnboardHooks } from "../lib/onboard-hooks";
-import { redactSecretsFromString } from "../lib/redact";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/commands/setup.ts` at line 32, Remove the unused import
`redactSecretsFromString` from the top of the file (the import statement in
setup.ts: `import { redactSecretsFromString } from "../lib/redact";`) since it
is not referenced in this file (it’s only used in onboard-hooks.ts); update the
imports accordingly and run the linter/TypeScript build to ensure no other
unused imports remain.

1-599: File exceeds 300-line guideline.

At 599 lines, this file significantly exceeds the 300-line guideline. The extraction of onboard hook orchestration to onboard-hooks.ts was a good step. Consider further refactoring in a follow-up PR to split remaining concerns (e.g., Pulumi configuration, secret resolution) into separate modules.

As per coding guidelines: "Keep files under 300 lines; split large files into smaller, focused modules."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/commands/setup.ts` around lines 1 - 599, This file is over the
300-line guideline; split it into focused modules by extracting major
responsibilities: (1) identity fetching & plugin/manifest resolution — move the
loop that builds fetchedIdentities, agentPlugins/agentDeps, allModels and
related helpers into a new module (e.g., functions like
resolveAgentSync/fetchIdentity orchestration exported as fetchAllIdentities or
buildIdentityContext), (2) secret resolution and validation — extract the
expectedSecrets construction, loadEnvSecrets usage, validators building,
requiredMissing logic and helper functions like resolveIsSecret/getValidatorHint
into a secrets module (e.g., secrets/resolve.ts) that exports resolveSecrets and
validateSecrets, and (3) Pulumi configuration — move configuration logic that
calls ensureWorkspace, selectOrCreateStack, setConfig and the
per-agent/autoresolved config loops into a pulumi/configure.ts module that
exports configurePulumi; update setupCommand to import and call these new
functions and keep only high-level orchestration, preserving existing function
names (resolveIsSecret, getValidatorHint) while making them internal to the new
modules or re-exporting as needed so tests/consumers keep working.
packages/cli/lib/onboard-hooks.ts (1)

41-44: Consider using the full OnboardHookResult type from core.

The inline type definition for runOnboardHook's return type differs slightly from the actual OnboardHookResult type in @clawup/core/manifest-hooks (which uses instructions: string rather than instructions?: string). While functionally compatible, importing and using the actual type would ensure type safety and consistency.

Proposed change
+import type { OnboardHookResult } from "@clawup/core/manifest-hooks";
+
 interface RunOnboardHooksArgs {
   // ...
-  runOnboardHook: (opts: { script: string; env: Record<string, string> }) => Promise<
-    | { ok: true; instructions?: string }
-    | { ok: false; error: string }
-  >;
+  runOnboardHook: (opts: { script: string; env: Record<string, string> }) => Promise<OnboardHookResult>;
   // ...
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/lib/onboard-hooks.ts` around lines 41 - 44, Replace the inline
return union for runOnboardHook with the canonical OnboardHookResult from
`@clawup/core/manifest-hooks`: import OnboardHookResult and change the method
signature runOnboardHook: (opts: { script: string; env: Record<string, string>
}) => Promise<OnboardHookResult>. Ensure the imported type is used so the
instructions field matches the core definition (instructions: string) and update
any callers or tests that relied on the previous optionality if needed.
packages/cli/__e2e__/onboard-hooks.e2e.test.ts (1)

1-348: Consider splitting this E2E suite into smaller focused files.

This file is above the project’s size guideline; extracting shared setup/helpers and splitting scenarios would improve maintainability.

As per coding guidelines: packages/**/*.{ts,tsx}: Keep files under 300 lines; split large files into smaller, focused modules.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/__e2e__/onboard-hooks.e2e.test.ts` around lines 1 - 348, This
test file is too large and should be split into smaller focused test files;
extract the shared setup/teardown and fixtures (tempDir creation, E2E_ENV_KEYS
handling, beforeAll/afterAll logic, PLUGIN_IDENTITY_DIR, and helper calls to
createTestProject/forceCleanup) into a reusable helper module (e.g., export
functions like createTempWorkspace, prepareEnv, cleanupWorkspace) and then move
each scenario (the three it blocks: "setup runs onboard hook...", "setup
prompts...", "setup skips...", "runOnce skips...") into its own test file that
imports the shared helpers and calls setupCommand and assertions; ensure each
new file remains under the 300-line guideline and keep references to the mocked
modules (vi.mock of `@clack/prompts`, ../lib/project, ../lib/workspace) in a
common setup helper or a per-file top-level mock so behavior is preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/__e2e__/onboard-hooks.e2e.test.ts`:
- Around line 315-317: The test currently does a blind string replace on
originalManifest (using originalManifest.replace("runOnce: false", "runOnce:
true")) which can silently no-op if formatting changes; modify the patch step in
the test (around variables manifestPath and originalManifest) to perform a
replacement that asserts it succeeded—e.g., use a regex or search to find the
runOnce key, produce the modified content, and throw or assert/fail the test if
no substitution occurred before calling fs.writeFileSync; ensure the assertion
references manifestPath/originalManifest so failures point to the fixture.

In `@packages/cli/commands/onboard.ts`:
- Around line 43-49: The onboard command currently reads and parses clawup.yaml
directly (manifestPath, fs.readFileSync, YAML.parse and
ClawupManifestSchema.safeParse); replace that direct I/O with the centralized
manifest helpers exported from packages/cli/lib/config.ts (use the module's
load/read manifest function and its validated return instead of manual fs/YAML
parsing), then feed the returned object into ClawupManifestSchema.safeParse or
use the module's already-validated result; ensure any thrown errors from the
config module are handled the same way the current try/catch does so behavior
remains consistent.

---

Duplicate comments:
In `@packages/cli/__e2e__/onboard-command.e2e.test.ts`:
- Around line 189-190: The test currently overwrites the manifest using
originalManifest.replace(...) which may silently no-op; after writing, read the
file back (or check the replaced string) and assert that the fixture now
contains "runOnce: true" (e.g., verify fs.readFileSync(manifestPath, "utf-8")
includes "runOnce: true" or assert that originalManifest !== patched and that
patched.includes("runOnce: true")), and fail the test if the replacement did not
occur so the test cannot silently proceed when the fixture shape changes;
reference the variables manifestPath and originalManifest (and the write
operation) to locate where to add this assertion.

---

Nitpick comments:
In `@packages/cli/__e2e__/onboard-hooks.e2e.test.ts`:
- Around line 1-348: This test file is too large and should be split into
smaller focused test files; extract the shared setup/teardown and fixtures
(tempDir creation, E2E_ENV_KEYS handling, beforeAll/afterAll logic,
PLUGIN_IDENTITY_DIR, and helper calls to createTestProject/forceCleanup) into a
reusable helper module (e.g., export functions like createTempWorkspace,
prepareEnv, cleanupWorkspace) and then move each scenario (the three it blocks:
"setup runs onboard hook...", "setup prompts...", "setup skips...", "runOnce
skips...") into its own test file that imports the shared helpers and calls
setupCommand and assertions; ensure each new file remains under the 300-line
guideline and keep references to the mocked modules (vi.mock of `@clack/prompts`,
../lib/project, ../lib/workspace) in a common setup helper or a per-file
top-level mock so behavior is preserved.

In `@packages/cli/commands/setup.ts`:
- Line 32: Remove the unused import `redactSecretsFromString` from the top of
the file (the import statement in setup.ts: `import { redactSecretsFromString }
from "../lib/redact";`) since it is not referenced in this file (it’s only used
in onboard-hooks.ts); update the imports accordingly and run the
linter/TypeScript build to ensure no other unused imports remain.
- Around line 1-599: This file is over the 300-line guideline; split it into
focused modules by extracting major responsibilities: (1) identity fetching &
plugin/manifest resolution — move the loop that builds fetchedIdentities,
agentPlugins/agentDeps, allModels and related helpers into a new module (e.g.,
functions like resolveAgentSync/fetchIdentity orchestration exported as
fetchAllIdentities or buildIdentityContext), (2) secret resolution and
validation — extract the expectedSecrets construction, loadEnvSecrets usage,
validators building, requiredMissing logic and helper functions like
resolveIsSecret/getValidatorHint into a secrets module (e.g.,
secrets/resolve.ts) that exports resolveSecrets and validateSecrets, and (3)
Pulumi configuration — move configuration logic that calls ensureWorkspace,
selectOrCreateStack, setConfig and the per-agent/autoresolved config loops into
a pulumi/configure.ts module that exports configurePulumi; update setupCommand
to import and call these new functions and keep only high-level orchestration,
preserving existing function names (resolveIsSecret, getValidatorHint) while
making them internal to the new modules or re-exporting as needed so
tests/consumers keep working.

In `@packages/cli/lib/onboard-hooks.ts`:
- Around line 41-44: Replace the inline return union for runOnboardHook with the
canonical OnboardHookResult from `@clawup/core/manifest-hooks`: import
OnboardHookResult and change the method signature runOnboardHook: (opts: {
script: string; env: Record<string, string> }) => Promise<OnboardHookResult>.
Ensure the imported type is used so the instructions field matches the core
definition (instructions: string) and update any callers or tests that relied on
the previous optionality if needed.

In `@packages/core/src/manifest-hooks.ts`:
- Around line 270-274: The stderr handler in child.stderr.on is writing raw
stderr to process.stderr (accumulating into the stderr variable) without
redaction; update the handler in manifest-hooks.ts to pass the chunk string
through redactSecretsFromString before appending to stderr and writing to
process.stderr (mirroring how stdout is handled), i.e., use
redactSecretsFromString(msg) when updating the stderr accumulator and when
calling process.stderr.write; reference the child.stderr.on listener, the stderr
accumulator, and redactSecretsFromString to locate and patch the logic (keeping
behavior consistent with runLifecycleHook).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d089a2 and 2b23dd2.

📒 Files selected for processing (15)
  • packages/cli/__e2e__/helpers/fixtures/plugin-identity/plugins/test-linear.yaml
  • packages/cli/__e2e__/onboard-command.e2e.test.ts
  • packages/cli/__e2e__/onboard-hooks.e2e.test.ts
  • packages/cli/__e2e__/plugins.e2e.test.ts
  • packages/cli/bin.ts
  • packages/cli/commands/onboard.ts
  • packages/cli/commands/setup.ts
  • packages/cli/lib/onboard-hooks.ts
  • packages/cli/lib/redact.ts
  • packages/core/src/__tests__/manifest-hooks.test.ts
  • packages/core/src/__tests__/plugin-manifest.test.ts
  • packages/core/src/index.ts
  • packages/core/src/manifest-hooks.ts
  • packages/core/src/schemas/index.ts
  • packages/core/src/schemas/plugin-manifest.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/core/src/index.ts
  • packages/core/src/schemas/plugin-manifest.ts
  • packages/cli/bin.ts

Comment on lines +315 to +317
const originalManifest = fs.readFileSync(manifestPath, "utf-8");
fs.writeFileSync(manifestPath, originalManifest.replace("runOnce: false", "runOnce: true"), "utf-8");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against silent no-op when patching runOnce in the fixture.

If fixture formatting changes, replace("runOnce: false", "runOnce: true") can do nothing and still let the test proceed.

Proposed reliability fix
-    const originalManifest = fs.readFileSync(manifestPath, "utf-8");
-    fs.writeFileSync(manifestPath, originalManifest.replace("runOnce: false", "runOnce: true"), "utf-8");
+    const originalManifest = fs.readFileSync(manifestPath, "utf-8");
+    const patchedManifest = originalManifest.replace("runOnce: false", "runOnce: true");
+    if (patchedManifest === originalManifest) {
+      throw new Error("Expected runOnce flag in test-linear fixture");
+    }
+    fs.writeFileSync(manifestPath, patchedManifest, "utf-8");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/__e2e__/onboard-hooks.e2e.test.ts` around lines 315 - 317, The
test currently does a blind string replace on originalManifest (using
originalManifest.replace("runOnce: false", "runOnce: true")) which can silently
no-op if formatting changes; modify the patch step in the test (around variables
manifestPath and originalManifest) to perform a replacement that asserts it
succeeded—e.g., use a regex or search to find the runOnce key, produce the
modified content, and throw or assert/fail the test if no substitution occurred
before calling fs.writeFileSync; ensure the assertion references
manifestPath/originalManifest so failures point to the fixture.

Comment on lines +43 to +49
const manifestPath = path.join(projectRoot, MANIFEST_FILE);
let validation: ReturnType<typeof ClawupManifestSchema.safeParse>;
try {
const raw = fs.readFileSync(manifestPath, "utf-8");
const parsed = YAML.parse(raw);
validation = ClawupManifestSchema.safeParse(parsed);
} catch (err) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use the CLI config module for manifest loading here.

This command reads/parses clawup.yaml directly via fs + yaml; please route manifest I/O through packages/cli/lib/config.ts to keep command behavior consistent and centralized.

As per coding guidelines: packages/cli/commands/**/*.ts: Load and save YAML manifests using the config module in packages/cli/lib/config.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/commands/onboard.ts` around lines 43 - 49, The onboard command
currently reads and parses clawup.yaml directly (manifestPath, fs.readFileSync,
YAML.parse and ClawupManifestSchema.safeParse); replace that direct I/O with the
centralized manifest helpers exported from packages/cli/lib/config.ts (use the
module's load/read manifest function and its validated return instead of manual
fs/YAML parsing), then feed the returned object into
ClawupManifestSchema.safeParse or use the module's already-validated result;
ensure any thrown errors from the config module are handled the same way the
current try/catch does so behavior remains consistent.

@stepandel stepandel merged commit d1ed687 into main Mar 1, 2026
4 of 5 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant