feat(cli): add persona pack install command - #40

Merged
willwashburn merged 4 commits into
mainfrom
feat/installable-persona-sources
May 7, 2026
Merged

feat(cli): add persona pack install command#40
willwashburn merged 4 commits into
mainfrom
feat/installable-persona-sources

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Closes#36

Summary

  • add agentworkforce install for npm and local persona packs
  • copy persona JSON into .agentworkforce/workforce/personas/ with subset selection, flattened filenames, conflict reporting, and --overwrite
  • allow standalone local persona specs so installed pack personas load through the cwd cascade
  • document package format, sources relationship, and persona pack authoring flow

Tests

  • npm run check

@coderabbitai

coderabbitaiBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Implements a shadcn-style agentworkforce install flow: CLI subcommand to copy persona JSONs from npm packages or local paths into /.agentworkforce/workforce/personas/, with npm pack extraction, persona-id filtering, filename flattening, conflict detection, optional overwrite, temp cleanup, tests, and docs. Adds standalone local persona support via an optional intent field.

Changes

Persona Install + Local Persona Standalone

Layer / File(s)Summary
Local persona override
packages/cli/src/local-personas.ts
Adds optional intent?: PersonaIntent, rejects intent combined with .extends, validates standalone tiers and runtime/harnessSettings, and constructs a full PersonaSpec for standalone overrides.
Install Implementation
packages/cli/src/persona-install.ts
Implements installPersonas() with local vs npm detection, npm pack + tar extraction, package.json.agentworkforce.personas (or personas/) resolution, recursive discovery of *.json, persona id validation, duplicate-id and flattened-filename collision checks, requested-id filtering (fail-fast), copy into .agentworkforce/workforce/personas/, conflict recording, and temp-dir cleanup. Exposes expandHomePath, npmExecutable, projectPersonaInstallDir, and isLocalInstallSource.
CLI Usage, Types & Wiring
README.md, packages/cli/README.md, packages/cli/src/cli.ts
Adds `agentworkforce install [flags] <pkg
Documentation
README.md, packages/cli/README.md
Adds full "Install persona packs" section describing package layout (agentworkforce.personas), flattening, conflict/overwrite rules, persona subset selection with --persona, npm vs local resolution, and an authoring/publishing example. Updates local persona docs for standalone requirements.
Tests
packages/cli/src/persona-install.test.ts, packages/cli/src/cli.test.ts, packages/cli/src/local-personas.test.ts
Adds tests for parseInstallArgs; comprehensive installer tests for npm/local detection, expandHomePath and npmExecutable, metadata fallback, discovery, persona-id filtering and fail-fast behavior, filename flattening, conflict recording, overwrite behavior, collisions across packages, temp cleanup, and standalone override rejection when extends and intent combined.

Sequence Diagram

sequenceDiagram
actor User
participant CLI as "agentworkforce CLI"
participant Parser as "parseInstallArgs"
participant Handler as "runPersonaInstall"
participant Installer as "installPersonas"
participant NPM as "npm (external)"
participant FS as "File System"
participant Loader as "loadLocalPersonas"
User->>CLI: agentworkforce install <pkg|path> --persona id --overwrite
CLI->>Parser: parseInstallArgs(args)
Parser-->>CLI: PersonaInstallArgs{source, personaIds, overwrite}
CLI->>Handler: runPersonaInstall(rest)
Handler->>Installer: installPersonas({source, personaIds, overwrite, cwd})
alt source is npm package
Installer->>NPM: npm pack <pkg> --json (temp dir)
NPM-->>Installer: <pkg>.tgz
Installer->>FS: Extract tarball -> /tmp/extracted/package/
else source is local path
Installer->>FS: Resolve/expand local path -> ./local-personas/
end
Installer->>FS: Read package.json (agentworkforce.personas?) FS-->>Installer: personaDir path
Installer->>FS: Scan personaDir for *.json
FS-->>Installer: List of persona files
Installer->>Installer: Parse each JSON, validate id, dedupe/filter by personaIds
Installer->>FS: Copy selected files to .agentworkforce/workforce/personas/ (flattened)
FS-->>Installer: Result {installed, conflicts}
Installer-->>Handler: PersonaInstallResult
Handler->>FS: Write stdout (installed summary)
Handler->>FS: Write stderr (conflicts)
Handler->>Loader: loadLocalPersonas(cwd)
Loader-->>Handler: Installed personas discoverable
Handler-->>User: Exit status (0 if no conflicts, non-zero if conflicts)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

  • #36 Design and implement shadcn-style persona installs — Implements the full install command, npm pack flow, persona filtering, flattening, conflict rules, and docs described in the issue.
  • Split first-party personas into installable persona packs #38 — The changes implement the persona-pack install flow and behavior sought by this issue.

Possibly related PRs

Poem

🐰
I hopped to fetch persona packs with care,
Unpacked tarballs and flattened paths to spare,
Copied JSON friends into your repo lair,
Conflicts flagged — overwrite if you dare,
Now personas hop, ready to chat and share.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe PR title clearly and concisely identifies the main change: adding a persona pack install command to the CLI.
Description check✅ PassedThe PR description is directly related to the changeset, summarizing the install command implementation, copy behavior, standalone personas, documentation, and tests.
Linked Issues check✅ PassedAll core requirements from issue #36 are met: install command for npm/local sources, subset selection via --persona flags, flattened filenames, conflict reporting with --overwrite, standalone personas, and comprehensive documentation and tests.
Out of Scope Changes check✅ PassedAll code changes directly support the install command feature and documented objectives, with no unrelated modifications detected.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/installable-persona-sources

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:bb8ff10f01

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +69 to +75
export function isLocalInstallSource(source: string): boolean {
return (
source.startsWith('.') ||
source.startsWith('/') ||
source.startsWith('~/') ||
source === '~'
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize Windows absolute paths as local sources

isLocalInstallSource only treats POSIX-style prefixes (., /, ~/, ~) as local paths, so valid Windows local paths like C:\\packs\\personas or ~\\personas are misclassified as npm specs. In that case installPersonas takes the npm branch and calls npm pack on a filesystem path, causing agentworkforce install <absolute-path> to fail on Windows even though absolute local paths are documented as supported.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Addressed in 8acf7e4: isLocalInstallSource() now treats Windows drive-letter paths (C:\\...) and UNC paths (\\\\server\\...) as local sources. The ~\\... home-path case was already fixed in 7223a8d and remains covered by tests.

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/local-personas.ts (1)

282-315: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject intent on overlays instead of silently ignoring it.

If a file sets both extends and intent, the standalone path never runs and the merged spec keeps base.intent, so the authored intent is dropped without any warning. Failing fast here would make that misconfiguration obvious.

Suggested fix
 if (raw.extends !== undefined && (typeof raw.extends !== 'string' || !raw.extends.trim())) {
throw new Error(`${context}.extends must be a non-empty string if provided`);
}
+ if (raw.intent !== undefined && raw.extends !== undefined) {+ throw new Error(+ `${context}.intent cannot be combined with .extends; omit extends for standalone personas`+ );+ }
if (raw.intent !== undefined && !PERSONA_INTENTS.includes(raw.intent as PersonaIntent)) {
throw new Error(`${context}.intent must be one of: ${PERSONA_INTENTS.join(', ')}`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/local-personas.ts` around lines 282 - 315, The validator
currently allows an overlay to specify raw.intent which then gets ignored when
extends is present; add a failing check that throws when both raw.extends and
raw.intent are provided (e.g., before the existing intent validation block),
such as: if (raw.extends !== undefined && raw.intent !== undefined) throw new
Error(`${context}.intent cannot be set on overlays that extend another
persona`); update the validation near the raw.intent checks (referencing
raw.intent, raw.extends, and PERSONA_INTENTS) so overlays must not declare
intent and the error surfaces immediately.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/README.md`:
- Around line 76-78: Update the two fenced code blocks in the README that show
the install usage and the file mapping examples so they use the "text" fence
language instead of unlabeled fences; specifically replace the opening backticks
for the block containing "agentworkforce install <pkg|path> [--persona <id> ...]
[--overwrite]" and the block containing
"package/personas/nested/code-reviewer.json ->
.agentworkforce/workforce/personas/code-reviewer.json" with ```text (leave the
contents unchanged) to satisfy markdownlint.
In `@packages/cli/src/persona-install.ts`:
- Around line 69-86: The local install detection and expansion don't handle
Windows-style home paths (e.g. "~\\personas"); update isLocalInstallSource to
also treat strings starting with "~\\" as local, and update expandHomePath to
handle inputs starting with "~\\" by joining homedir() with input.slice(2) (same
as the "~/” case), keeping the existing "~" and "/"-prefixed logic; ensure
resolveLocalSource continues to call expandHomePath and use
isAbsolute/resolvePath as before.
In `@README.md`:
- Around line 140-146: Update the fenced code block that shows the directory
tree starting with "@acme/personas/" to include a language tag by changing the
opening backticks to "```text" so the block becomes a labeled plaintext block;
locate the block containing the lines "@acme/personas/", "├── package.json", and
"└── personas/" and add the "text" language specifier to the opening fence.
---
Outside diff comments:
In `@packages/cli/src/local-personas.ts`:
- Around line 282-315: The validator currently allows an overlay to specify
raw.intent which then gets ignored when extends is present; add a failing check
that throws when both raw.extends and raw.intent are provided (e.g., before the
existing intent validation block), such as: if (raw.extends !== undefined &&
raw.intent !== undefined) throw new Error(`${context}.intent cannot be set on
overlays that extend another persona`); update the validation near the
raw.intent checks (referencing raw.intent, raw.extends, and PERSONA_INTENTS) so
overlays must not declare intent and the error surfaces immediately.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8de86882-c3c3-4769-8770-d90f90443dfc

📥 Commits

Reviewing files that changed from the base of the PR and between 64592b9 and bb8ff10.

📒 Files selected for processing (7)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/cli.test.ts
  • packages/cli/src/cli.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts

Comment threadpackages/cli/README.md Outdated
Comment threadpackages/cli/src/persona-install.ts
Comment threadREADME.md Outdated

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/cli/src/persona-install.ts (1)

69-77: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat absolute Windows paths as local sources too.

This still routes valid Windows local paths like C:\packs\foo or \\server\share\foo into the npm-package branch, because the detection only checks string prefixes. That makes local installs fail on Windows unless the path happens to start with . or ~.

Suggested fix
 export function isLocalInstallSource(source: string): boolean {
+ const trimmed = source.trim();
return (
- source.startsWith('.') ||- source.startsWith('/') ||- source.startsWith('~/') ||- source.startsWith('~\\') ||- source === '~'+ trimmed.startsWith('.') ||+ trimmed === '~' ||+ trimmed.startsWith('~/') ||+ trimmed.startsWith('~\\') ||+ isAbsolute(trimmed)
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/persona-install.ts` around lines 69 - 77, The
isLocalInstallSource function incorrectly treats Windows absolute paths as
non-local; update isLocalInstallSource to also return true for Windows
drive-letter absolute paths (e.g., matches /^[A-Za-z]:[\\/]/) and UNC paths
(e.g., starts with '\\\\'), in addition to the existing checks for '.', '/', '~'
and '~/'; modify the function to test these regex/prefix conditions
(drive-letter and UNC) and return true when any of them match.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 331-343: The temp directory is created (tempDir =
mkdtempSync(...)) only inside the npm-resolution branch before the try, but
cleanup lives in the finally after try so exceptions from resolver or
assertDirectory leak the temp dir; move the temp-dir lifecycle so it is created
before entering the try/finally that performs cleanup: when
!isLocalInstallSource(source) call mkdtempSync and set tempDir, then call
resolver (options.resolveNpmPackage ?? defaultResolveNpmPackage) and
assertDirectory inside the try block so the finally can always remove tempDir;
reference tempDir, mkdtempSync,
resolver/resolveNpmPackage/defaultResolveNpmPackage, packageRoot and
assertDirectory to locate the changes.
- Around line 285-289: The spawnSync call that sets packed currently invokes
'npm' with shell: false, which fails on Windows because the npm shim is
'npm.cmd'; change the command argument in that spawnSync (the one assigning to
packed) to choose the correct executable like: const npmExe = process.platform
=== 'win32' ? 'npm.cmd' : 'npm'; then call spawnSync(npmExe, ['pack', spec,
'--pack-destination', packDir, '--json'], { encoding: 'utf8', shell: false });
alternatively use cross-spawn to abstract this; update the spawnSync invocation
that creates packed (and any nearby npm spawn calls) to use npmExe or
cross-spawn so Windows uses the npm shim.
---
Duplicate comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 69-77: The isLocalInstallSource function incorrectly treats
Windows absolute paths as non-local; update isLocalInstallSource to also return
true for Windows drive-letter absolute paths (e.g., matches /^[A-Za-z]:[\\/]/)
and UNC paths (e.g., starts with '\\\\'), in addition to the existing checks for
'.', '/', '~' and '~/'; modify the function to test these regex/prefix
conditions (drive-letter and UNC) and return true when any of them match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 54b1d3e1-7ca4-4955-8cb6-f64929447549

📥 Commits

Reviewing files that changed from the base of the PR and between bb8ff10 and 7223a8d.

📒 Files selected for processing (2)
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/cli/src/persona-install.test.ts

Comment threadpackages/cli/src/persona-install.ts
Comment threadpackages/cli/src/persona-install.ts
Comment threadpackages/cli/src/persona-install.ts
@willwashburn

Copy link
Copy Markdown
MemberAuthor

Review follow-up pushed in 8acf7e4.

Fixed still-valid items:

  • README install/tree fences now use text where requested.
  • Windows local-source detection now covers drive-letter paths and UNC paths; ~\\... was already fixed in 7223a8d.
  • npm pack now invokes npm.cmd on Windows when shell: false is used.
  • npm install temp dirs are cleaned if resolver/package-root validation throws.
  • local persona overlays now reject intent when extends is also present instead of silently ignoring it.

Skipped: CodeRabbit docstring coverage warning is advisory and not enforced by repo checks; adding broad docstrings would be unrelated to the review findings.

Validated with corepack pnpm --filter @agentworkforce/cli test and npm run check.

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 292-296: The npm pack invocation in persona-install.ts (the
spawnSync call that sets packed) is vulnerable because git+ specs can run
lifecycle scripts; add the '--ignore-scripts' flag to the array of arguments
passed to npmExecutable() in that spawnSync call so npm pack is executed with
scripts disabled, and keep the change scoped to the same spawnSync invocation
that produces the packed variable (consider documenting the remaining caveat
about old npm versions and .npmrc re-enabling scripts elsewhere if desired).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 506abb40-d5b6-4406-b83b-9fadd5dcce94

📥 Commits

Reviewing files that changed from the base of the PR and between 7223a8d and 8acf7e4.

📒 Files selected for processing (6)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/local-personas.test.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli/README.md
  • packages/cli/src/persona-install.test.ts

Comment threadpackages/cli/src/persona-install.ts

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 69-79: The isLocalInstallSource function currently misses Windows
root-relative paths like "\personas"; update isLocalInstallSource to treat any
single leading backslash as a local path by adding a check for
trimmed.startsWith('\\') (or a regex that matches a single leading backslash) so
those inputs are routed to resolveLocalSource as local installs (you can replace
the existing '\\\\' check with a broader '\\' check to still cover UNC paths).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f491083a-dcdf-411e-be18-f58cb49f0ca1

📥 Commits

Reviewing files that changed from the base of the PR and between 8acf7e4 and eae2954.

📒 Files selected for processing (1)
  • packages/cli/src/persona-install.ts

Comment on lines +69 to +79
export function isLocalInstallSource(source: string): boolean {
const trimmed = source.trim();
return (
trimmed.startsWith('.') ||
trimmed.startsWith('/') ||
trimmed.startsWith('~/') ||
trimmed.startsWith('~\\') ||
trimmed === '~' ||
/^[A-Za-z]:[\\/]/.test(trimmed) ||
trimmed.startsWith('\\\\')
);

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Node.js path handling, is a Windows path like "\personas" considered absolute (e.g., path.win32.isAbsolute("\\personas"))?

💡 Result:

Yes, in Node.js, a Windows path like "\personas" (which is the string "\personas" due to JavaScript escaping) is considered absolute by path.win32.isAbsolute. This returns true because it starts with a path separator (), making it a drive-relative path from the root of the current drive, consistent with Windows PathIsRelative API behavior [1][2][3]. Official Node.js documentation shows path.win32.isAbsolute('\foo') effectively returns true, as single-backslash paths are treated as absolute (equivalent to forward slash '/' examples which also return true) [4][5][6][3]. Note that "\personas" (four characters, two backslashes) would be an incomplete UNC path but still absolute per the logic checking initial separators [4]. This behavior has remained unchanged through Node.js v26 [5].

Citations:


🏁 Script executed:

cat -n packages/cli/src/persona-install.ts | head -150

Repository: AgentWorkforce/workforce

Length of output: 5311


🏁 Script executed:

rg -n "isLocalInstallSource" packages/cli/src/persona-install.ts

Repository: AgentWorkforce/workforce

Length of output: 179


🏁 Script executed:

cat -n packages/cli/src/persona-install.ts | sed -n '330,380p'

Repository: AgentWorkforce/workforce

Length of output: 2105


Handle Windows root-relative paths (\foo) as local install sources.

isLocalInstallSource misses valid Windows root-relative absolute paths. On Windows, \personas should resolve as a local path via resolveLocalSource, but this check returns false and routes it to npm package resolution instead, causing an error.

Suggested fix
 export function isLocalInstallSource(source: string): boolean {
const trimmed = source.trim();
return (
trimmed.startsWith('.') ||
trimmed.startsWith('/') ||
+ trimmed.startsWith('\\') ||
trimmed.startsWith('~/') ||
trimmed.startsWith('~\\') ||
trimmed === '~' ||
/^[A-Za-z]:[\\/]/.test(trimmed) ||
trimmed.startsWith('\\\\')
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/persona-install.ts` around lines 69 - 79, The
isLocalInstallSource function currently misses Windows root-relative paths like
"\personas"; update isLocalInstallSource to treat any single leading backslash
as a local path by adding a check for trimmed.startsWith('\\') (or a regex that
matches a single leading backslash) so those inputs are routed to
resolveLocalSource as local installs (you can replace the existing '\\\\' check
with a broader '\\' check to still cover UNC paths).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Design and implement shadcn-style persona installs

1 participant

@willwashburn
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(cli): add persona pack install command - #40

Merged
willwashburn merged 4 commits into
mainfrom
feat/installable-persona-sources
May 7, 2026
Merged

feat(cli): add persona pack install command#40
willwashburn merged 4 commits into
mainfrom
feat/installable-persona-sources

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Closes#36

Summary

  • add agentworkforce install for npm and local persona packs
  • copy persona JSON into .agentworkforce/workforce/personas/ with subset selection, flattened filenames, conflict reporting, and --overwrite
  • allow standalone local persona specs so installed pack personas load through the cwd cascade
  • document package format, sources relationship, and persona pack authoring flow

Tests

  • npm run check

@coderabbitai

coderabbitaiBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Implements a shadcn-style agentworkforce install flow: CLI subcommand to copy persona JSONs from npm packages or local paths into /.agentworkforce/workforce/personas/, with npm pack extraction, persona-id filtering, filename flattening, conflict detection, optional overwrite, temp cleanup, tests, and docs. Adds standalone local persona support via an optional intent field.

Changes

Persona Install + Local Persona Standalone

Layer / File(s)Summary
Local persona override
packages/cli/src/local-personas.ts
Adds optional intent?: PersonaIntent, rejects intent combined with .extends, validates standalone tiers and runtime/harnessSettings, and constructs a full PersonaSpec for standalone overrides.
Install Implementation
packages/cli/src/persona-install.ts
Implements installPersonas() with local vs npm detection, npm pack + tar extraction, package.json.agentworkforce.personas (or personas/) resolution, recursive discovery of *.json, persona id validation, duplicate-id and flattened-filename collision checks, requested-id filtering (fail-fast), copy into .agentworkforce/workforce/personas/, conflict recording, and temp-dir cleanup. Exposes expandHomePath, npmExecutable, projectPersonaInstallDir, and isLocalInstallSource.
CLI Usage, Types & Wiring
README.md, packages/cli/README.md, packages/cli/src/cli.ts
Adds `agentworkforce install [flags] <pkg
Documentation
README.md, packages/cli/README.md
Adds full "Install persona packs" section describing package layout (agentworkforce.personas), flattening, conflict/overwrite rules, persona subset selection with --persona, npm vs local resolution, and an authoring/publishing example. Updates local persona docs for standalone requirements.
Tests
packages/cli/src/persona-install.test.ts, packages/cli/src/cli.test.ts, packages/cli/src/local-personas.test.ts
Adds tests for parseInstallArgs; comprehensive installer tests for npm/local detection, expandHomePath and npmExecutable, metadata fallback, discovery, persona-id filtering and fail-fast behavior, filename flattening, conflict recording, overwrite behavior, collisions across packages, temp cleanup, and standalone override rejection when extends and intent combined.

Sequence Diagram

sequenceDiagram
actor User
participant CLI as "agentworkforce CLI"
participant Parser as "parseInstallArgs"
participant Handler as "runPersonaInstall"
participant Installer as "installPersonas"
participant NPM as "npm (external)"
participant FS as "File System"
participant Loader as "loadLocalPersonas"
User->>CLI: agentworkforce install <pkg|path> --persona id --overwrite
CLI->>Parser: parseInstallArgs(args)
Parser-->>CLI: PersonaInstallArgs{source, personaIds, overwrite}
CLI->>Handler: runPersonaInstall(rest)
Handler->>Installer: installPersonas({source, personaIds, overwrite, cwd})
alt source is npm package
Installer->>NPM: npm pack <pkg> --json (temp dir)
NPM-->>Installer: <pkg>.tgz
Installer->>FS: Extract tarball -> /tmp/extracted/package/
else source is local path
Installer->>FS: Resolve/expand local path -> ./local-personas/
end
Installer->>FS: Read package.json (agentworkforce.personas?) FS-->>Installer: personaDir path
Installer->>FS: Scan personaDir for *.json
FS-->>Installer: List of persona files
Installer->>Installer: Parse each JSON, validate id, dedupe/filter by personaIds
Installer->>FS: Copy selected files to .agentworkforce/workforce/personas/ (flattened)
FS-->>Installer: Result {installed, conflicts}
Installer-->>Handler: PersonaInstallResult
Handler->>FS: Write stdout (installed summary)
Handler->>FS: Write stderr (conflicts)
Handler->>Loader: loadLocalPersonas(cwd)
Loader-->>Handler: Installed personas discoverable
Handler-->>User: Exit status (0 if no conflicts, non-zero if conflicts)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

  • #36 Design and implement shadcn-style persona installs — Implements the full install command, npm pack flow, persona filtering, flattening, conflict rules, and docs described in the issue.
  • Split first-party personas into installable persona packs #38 — The changes implement the persona-pack install flow and behavior sought by this issue.

Possibly related PRs

Poem

🐰
I hopped to fetch persona packs with care,
Unpacked tarballs and flattened paths to spare,
Copied JSON friends into your repo lair,
Conflicts flagged — overwrite if you dare,
Now personas hop, ready to chat and share.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe PR title clearly and concisely identifies the main change: adding a persona pack install command to the CLI.
Description check✅ PassedThe PR description is directly related to the changeset, summarizing the install command implementation, copy behavior, standalone personas, documentation, and tests.
Linked Issues check✅ PassedAll core requirements from issue #36 are met: install command for npm/local sources, subset selection via --persona flags, flattened filenames, conflict reporting with --overwrite, standalone personas, and comprehensive documentation and tests.
Out of Scope Changes check✅ PassedAll code changes directly support the install command feature and documented objectives, with no unrelated modifications detected.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/installable-persona-sources

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:bb8ff10f01

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +69 to +75
export function isLocalInstallSource(source: string): boolean {
return (
source.startsWith('.') ||
source.startsWith('/') ||
source.startsWith('~/') ||
source === '~'
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize Windows absolute paths as local sources

isLocalInstallSource only treats POSIX-style prefixes (., /, ~/, ~) as local paths, so valid Windows local paths like C:\\packs\\personas or ~\\personas are misclassified as npm specs. In that case installPersonas takes the npm branch and calls npm pack on a filesystem path, causing agentworkforce install <absolute-path> to fail on Windows even though absolute local paths are documented as supported.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Addressed in 8acf7e4: isLocalInstallSource() now treats Windows drive-letter paths (C:\\...) and UNC paths (\\\\server\\...) as local sources. The ~\\... home-path case was already fixed in 7223a8d and remains covered by tests.

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/local-personas.ts (1)

282-315: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject intent on overlays instead of silently ignoring it.

If a file sets both extends and intent, the standalone path never runs and the merged spec keeps base.intent, so the authored intent is dropped without any warning. Failing fast here would make that misconfiguration obvious.

Suggested fix
 if (raw.extends !== undefined && (typeof raw.extends !== 'string' || !raw.extends.trim())) {
throw new Error(`${context}.extends must be a non-empty string if provided`);
}
+ if (raw.intent !== undefined && raw.extends !== undefined) {+ throw new Error(+ `${context}.intent cannot be combined with .extends; omit extends for standalone personas`+ );+ }
if (raw.intent !== undefined && !PERSONA_INTENTS.includes(raw.intent as PersonaIntent)) {
throw new Error(`${context}.intent must be one of: ${PERSONA_INTENTS.join(', ')}`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/local-personas.ts` around lines 282 - 315, The validator
currently allows an overlay to specify raw.intent which then gets ignored when
extends is present; add a failing check that throws when both raw.extends and
raw.intent are provided (e.g., before the existing intent validation block),
such as: if (raw.extends !== undefined && raw.intent !== undefined) throw new
Error(`${context}.intent cannot be set on overlays that extend another
persona`); update the validation near the raw.intent checks (referencing
raw.intent, raw.extends, and PERSONA_INTENTS) so overlays must not declare
intent and the error surfaces immediately.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/README.md`:
- Around line 76-78: Update the two fenced code blocks in the README that show
the install usage and the file mapping examples so they use the "text" fence
language instead of unlabeled fences; specifically replace the opening backticks
for the block containing "agentworkforce install <pkg|path> [--persona <id> ...]
[--overwrite]" and the block containing
"package/personas/nested/code-reviewer.json ->
.agentworkforce/workforce/personas/code-reviewer.json" with ```text (leave the
contents unchanged) to satisfy markdownlint.
In `@packages/cli/src/persona-install.ts`:
- Around line 69-86: The local install detection and expansion don't handle
Windows-style home paths (e.g. "~\\personas"); update isLocalInstallSource to
also treat strings starting with "~\\" as local, and update expandHomePath to
handle inputs starting with "~\\" by joining homedir() with input.slice(2) (same
as the "~/” case), keeping the existing "~" and "/"-prefixed logic; ensure
resolveLocalSource continues to call expandHomePath and use
isAbsolute/resolvePath as before.
In `@README.md`:
- Around line 140-146: Update the fenced code block that shows the directory
tree starting with "@acme/personas/" to include a language tag by changing the
opening backticks to "```text" so the block becomes a labeled plaintext block;
locate the block containing the lines "@acme/personas/", "├── package.json", and
"└── personas/" and add the "text" language specifier to the opening fence.
---
Outside diff comments:
In `@packages/cli/src/local-personas.ts`:
- Around line 282-315: The validator currently allows an overlay to specify
raw.intent which then gets ignored when extends is present; add a failing check
that throws when both raw.extends and raw.intent are provided (e.g., before the
existing intent validation block), such as: if (raw.extends !== undefined &&
raw.intent !== undefined) throw new Error(`${context}.intent cannot be set on
overlays that extend another persona`); update the validation near the
raw.intent checks (referencing raw.intent, raw.extends, and PERSONA_INTENTS) so
overlays must not declare intent and the error surfaces immediately.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8de86882-c3c3-4769-8770-d90f90443dfc

📥 Commits

Reviewing files that changed from the base of the PR and between 64592b9 and bb8ff10.

📒 Files selected for processing (7)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/cli.test.ts
  • packages/cli/src/cli.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts

Comment threadpackages/cli/README.md Outdated
Comment threadpackages/cli/src/persona-install.ts
Comment threadREADME.md Outdated

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/cli/src/persona-install.ts (1)

69-77: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat absolute Windows paths as local sources too.

This still routes valid Windows local paths like C:\packs\foo or \\server\share\foo into the npm-package branch, because the detection only checks string prefixes. That makes local installs fail on Windows unless the path happens to start with . or ~.

Suggested fix
 export function isLocalInstallSource(source: string): boolean {
+ const trimmed = source.trim();
return (
- source.startsWith('.') ||- source.startsWith('/') ||- source.startsWith('~/') ||- source.startsWith('~\\') ||- source === '~'+ trimmed.startsWith('.') ||+ trimmed === '~' ||+ trimmed.startsWith('~/') ||+ trimmed.startsWith('~\\') ||+ isAbsolute(trimmed)
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/persona-install.ts` around lines 69 - 77, The
isLocalInstallSource function incorrectly treats Windows absolute paths as
non-local; update isLocalInstallSource to also return true for Windows
drive-letter absolute paths (e.g., matches /^[A-Za-z]:[\\/]/) and UNC paths
(e.g., starts with '\\\\'), in addition to the existing checks for '.', '/', '~'
and '~/'; modify the function to test these regex/prefix conditions
(drive-letter and UNC) and return true when any of them match.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 331-343: The temp directory is created (tempDir =
mkdtempSync(...)) only inside the npm-resolution branch before the try, but
cleanup lives in the finally after try so exceptions from resolver or
assertDirectory leak the temp dir; move the temp-dir lifecycle so it is created
before entering the try/finally that performs cleanup: when
!isLocalInstallSource(source) call mkdtempSync and set tempDir, then call
resolver (options.resolveNpmPackage ?? defaultResolveNpmPackage) and
assertDirectory inside the try block so the finally can always remove tempDir;
reference tempDir, mkdtempSync,
resolver/resolveNpmPackage/defaultResolveNpmPackage, packageRoot and
assertDirectory to locate the changes.
- Around line 285-289: The spawnSync call that sets packed currently invokes
'npm' with shell: false, which fails on Windows because the npm shim is
'npm.cmd'; change the command argument in that spawnSync (the one assigning to
packed) to choose the correct executable like: const npmExe = process.platform
=== 'win32' ? 'npm.cmd' : 'npm'; then call spawnSync(npmExe, ['pack', spec,
'--pack-destination', packDir, '--json'], { encoding: 'utf8', shell: false });
alternatively use cross-spawn to abstract this; update the spawnSync invocation
that creates packed (and any nearby npm spawn calls) to use npmExe or
cross-spawn so Windows uses the npm shim.
---
Duplicate comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 69-77: The isLocalInstallSource function incorrectly treats
Windows absolute paths as non-local; update isLocalInstallSource to also return
true for Windows drive-letter absolute paths (e.g., matches /^[A-Za-z]:[\\/]/)
and UNC paths (e.g., starts with '\\\\'), in addition to the existing checks for
'.', '/', '~' and '~/'; modify the function to test these regex/prefix
conditions (drive-letter and UNC) and return true when any of them match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 54b1d3e1-7ca4-4955-8cb6-f64929447549

📥 Commits

Reviewing files that changed from the base of the PR and between bb8ff10 and 7223a8d.

📒 Files selected for processing (2)
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/cli/src/persona-install.test.ts

Comment threadpackages/cli/src/persona-install.ts
Comment threadpackages/cli/src/persona-install.ts
Comment threadpackages/cli/src/persona-install.ts
@willwashburn

Copy link
Copy Markdown
MemberAuthor

Review follow-up pushed in 8acf7e4.

Fixed still-valid items:

  • README install/tree fences now use text where requested.
  • Windows local-source detection now covers drive-letter paths and UNC paths; ~\\... was already fixed in 7223a8d.
  • npm pack now invokes npm.cmd on Windows when shell: false is used.
  • npm install temp dirs are cleaned if resolver/package-root validation throws.
  • local persona overlays now reject intent when extends is also present instead of silently ignoring it.

Skipped: CodeRabbit docstring coverage warning is advisory and not enforced by repo checks; adding broad docstrings would be unrelated to the review findings.

Validated with corepack pnpm --filter @agentworkforce/cli test and npm run check.

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 292-296: The npm pack invocation in persona-install.ts (the
spawnSync call that sets packed) is vulnerable because git+ specs can run
lifecycle scripts; add the '--ignore-scripts' flag to the array of arguments
passed to npmExecutable() in that spawnSync call so npm pack is executed with
scripts disabled, and keep the change scoped to the same spawnSync invocation
that produces the packed variable (consider documenting the remaining caveat
about old npm versions and .npmrc re-enabling scripts elsewhere if desired).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 506abb40-d5b6-4406-b83b-9fadd5dcce94

📥 Commits

Reviewing files that changed from the base of the PR and between 7223a8d and 8acf7e4.

📒 Files selected for processing (6)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/local-personas.test.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli/README.md
  • packages/cli/src/persona-install.test.ts

Comment threadpackages/cli/src/persona-install.ts

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 69-79: The isLocalInstallSource function currently misses Windows
root-relative paths like "\personas"; update isLocalInstallSource to treat any
single leading backslash as a local path by adding a check for
trimmed.startsWith('\\') (or a regex that matches a single leading backslash) so
those inputs are routed to resolveLocalSource as local installs (you can replace
the existing '\\\\' check with a broader '\\' check to still cover UNC paths).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f491083a-dcdf-411e-be18-f58cb49f0ca1

📥 Commits

Reviewing files that changed from the base of the PR and between 8acf7e4 and eae2954.

📒 Files selected for processing (1)
  • packages/cli/src/persona-install.ts

Comment on lines +69 to +79
export function isLocalInstallSource(source: string): boolean {
const trimmed = source.trim();
return (
trimmed.startsWith('.') ||
trimmed.startsWith('/') ||
trimmed.startsWith('~/') ||
trimmed.startsWith('~\\') ||
trimmed === '~' ||
/^[A-Za-z]:[\\/]/.test(trimmed) ||
trimmed.startsWith('\\\\')
);

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Node.js path handling, is a Windows path like "\personas" considered absolute (e.g., path.win32.isAbsolute("\\personas"))?

💡 Result:

Yes, in Node.js, a Windows path like "\personas" (which is the string "\personas" due to JavaScript escaping) is considered absolute by path.win32.isAbsolute. This returns true because it starts with a path separator (), making it a drive-relative path from the root of the current drive, consistent with Windows PathIsRelative API behavior [1][2][3]. Official Node.js documentation shows path.win32.isAbsolute('\foo') effectively returns true, as single-backslash paths are treated as absolute (equivalent to forward slash '/' examples which also return true) [4][5][6][3]. Note that "\personas" (four characters, two backslashes) would be an incomplete UNC path but still absolute per the logic checking initial separators [4]. This behavior has remained unchanged through Node.js v26 [5].

Citations:


🏁 Script executed:

cat -n packages/cli/src/persona-install.ts | head -150

Repository: AgentWorkforce/workforce

Length of output: 5311


🏁 Script executed:

rg -n "isLocalInstallSource" packages/cli/src/persona-install.ts

Repository: AgentWorkforce/workforce

Length of output: 179


🏁 Script executed:

cat -n packages/cli/src/persona-install.ts | sed -n '330,380p'

Repository: AgentWorkforce/workforce

Length of output: 2105


Handle Windows root-relative paths (\foo) as local install sources.

isLocalInstallSource misses valid Windows root-relative absolute paths. On Windows, \personas should resolve as a local path via resolveLocalSource, but this check returns false and routes it to npm package resolution instead, causing an error.

Suggested fix
 export function isLocalInstallSource(source: string): boolean {
const trimmed = source.trim();
return (
trimmed.startsWith('.') ||
trimmed.startsWith('/') ||
+ trimmed.startsWith('\\') ||
trimmed.startsWith('~/') ||
trimmed.startsWith('~\\') ||
trimmed === '~' ||
/^[A-Za-z]:[\\/]/.test(trimmed) ||
trimmed.startsWith('\\\\')
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/persona-install.ts` around lines 69 - 79, The
isLocalInstallSource function currently misses Windows root-relative paths like
"\personas"; update isLocalInstallSource to treat any single leading backslash
as a local path by adding a check for trimmed.startsWith('\\') (or a regex that
matches a single leading backslash) so those inputs are routed to
resolveLocalSource as local installs (you can replace the existing '\\\\' check
with a broader '\\' check to still cover UNC paths).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Design and implement shadcn-style persona installs

1 participant

@willwashburn
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(cli): add persona pack install command - #40

Merged
willwashburn merged 4 commits into
mainfrom
feat/installable-persona-sources
May 7, 2026
Merged

feat(cli): add persona pack install command#40
willwashburn merged 4 commits into
mainfrom
feat/installable-persona-sources

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Closes#36

Summary

  • add agentworkforce install for npm and local persona packs
  • copy persona JSON into .agentworkforce/workforce/personas/ with subset selection, flattened filenames, conflict reporting, and --overwrite
  • allow standalone local persona specs so installed pack personas load through the cwd cascade
  • document package format, sources relationship, and persona pack authoring flow

Tests

  • npm run check

@coderabbitai

coderabbitaiBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Implements a shadcn-style agentworkforce install flow: CLI subcommand to copy persona JSONs from npm packages or local paths into /.agentworkforce/workforce/personas/, with npm pack extraction, persona-id filtering, filename flattening, conflict detection, optional overwrite, temp cleanup, tests, and docs. Adds standalone local persona support via an optional intent field.

Changes

Persona Install + Local Persona Standalone

Layer / File(s)Summary
Local persona override
packages/cli/src/local-personas.ts
Adds optional intent?: PersonaIntent, rejects intent combined with .extends, validates standalone tiers and runtime/harnessSettings, and constructs a full PersonaSpec for standalone overrides.
Install Implementation
packages/cli/src/persona-install.ts
Implements installPersonas() with local vs npm detection, npm pack + tar extraction, package.json.agentworkforce.personas (or personas/) resolution, recursive discovery of *.json, persona id validation, duplicate-id and flattened-filename collision checks, requested-id filtering (fail-fast), copy into .agentworkforce/workforce/personas/, conflict recording, and temp-dir cleanup. Exposes expandHomePath, npmExecutable, projectPersonaInstallDir, and isLocalInstallSource.
CLI Usage, Types & Wiring
README.md, packages/cli/README.md, packages/cli/src/cli.ts
Adds `agentworkforce install [flags] <pkg
Documentation
README.md, packages/cli/README.md
Adds full "Install persona packs" section describing package layout (agentworkforce.personas), flattening, conflict/overwrite rules, persona subset selection with --persona, npm vs local resolution, and an authoring/publishing example. Updates local persona docs for standalone requirements.
Tests
packages/cli/src/persona-install.test.ts, packages/cli/src/cli.test.ts, packages/cli/src/local-personas.test.ts
Adds tests for parseInstallArgs; comprehensive installer tests for npm/local detection, expandHomePath and npmExecutable, metadata fallback, discovery, persona-id filtering and fail-fast behavior, filename flattening, conflict recording, overwrite behavior, collisions across packages, temp cleanup, and standalone override rejection when extends and intent combined.

Sequence Diagram

sequenceDiagram
actor User
participant CLI as "agentworkforce CLI"
participant Parser as "parseInstallArgs"
participant Handler as "runPersonaInstall"
participant Installer as "installPersonas"
participant NPM as "npm (external)"
participant FS as "File System"
participant Loader as "loadLocalPersonas"
User->>CLI: agentworkforce install <pkg|path> --persona id --overwrite
CLI->>Parser: parseInstallArgs(args)
Parser-->>CLI: PersonaInstallArgs{source, personaIds, overwrite}
CLI->>Handler: runPersonaInstall(rest)
Handler->>Installer: installPersonas({source, personaIds, overwrite, cwd})
alt source is npm package
Installer->>NPM: npm pack <pkg> --json (temp dir)
NPM-->>Installer: <pkg>.tgz
Installer->>FS: Extract tarball -> /tmp/extracted/package/
else source is local path
Installer->>FS: Resolve/expand local path -> ./local-personas/
end
Installer->>FS: Read package.json (agentworkforce.personas?) FS-->>Installer: personaDir path
Installer->>FS: Scan personaDir for *.json
FS-->>Installer: List of persona files
Installer->>Installer: Parse each JSON, validate id, dedupe/filter by personaIds
Installer->>FS: Copy selected files to .agentworkforce/workforce/personas/ (flattened)
FS-->>Installer: Result {installed, conflicts}
Installer-->>Handler: PersonaInstallResult
Handler->>FS: Write stdout (installed summary)
Handler->>FS: Write stderr (conflicts)
Handler->>Loader: loadLocalPersonas(cwd)
Loader-->>Handler: Installed personas discoverable
Handler-->>User: Exit status (0 if no conflicts, non-zero if conflicts)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

  • #36 Design and implement shadcn-style persona installs — Implements the full install command, npm pack flow, persona filtering, flattening, conflict rules, and docs described in the issue.
  • Split first-party personas into installable persona packs #38 — The changes implement the persona-pack install flow and behavior sought by this issue.

Possibly related PRs

Poem

🐰
I hopped to fetch persona packs with care,
Unpacked tarballs and flattened paths to spare,
Copied JSON friends into your repo lair,
Conflicts flagged — overwrite if you dare,
Now personas hop, ready to chat and share.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe PR title clearly and concisely identifies the main change: adding a persona pack install command to the CLI.
Description check✅ PassedThe PR description is directly related to the changeset, summarizing the install command implementation, copy behavior, standalone personas, documentation, and tests.
Linked Issues check✅ PassedAll core requirements from issue #36 are met: install command for npm/local sources, subset selection via --persona flags, flattened filenames, conflict reporting with --overwrite, standalone personas, and comprehensive documentation and tests.
Out of Scope Changes check✅ PassedAll code changes directly support the install command feature and documented objectives, with no unrelated modifications detected.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/installable-persona-sources

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:bb8ff10f01

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +69 to +75
export function isLocalInstallSource(source: string): boolean {
return (
source.startsWith('.') ||
source.startsWith('/') ||
source.startsWith('~/') ||
source === '~'
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize Windows absolute paths as local sources

isLocalInstallSource only treats POSIX-style prefixes (., /, ~/, ~) as local paths, so valid Windows local paths like C:\\packs\\personas or ~\\personas are misclassified as npm specs. In that case installPersonas takes the npm branch and calls npm pack on a filesystem path, causing agentworkforce install <absolute-path> to fail on Windows even though absolute local paths are documented as supported.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Addressed in 8acf7e4: isLocalInstallSource() now treats Windows drive-letter paths (C:\\...) and UNC paths (\\\\server\\...) as local sources. The ~\\... home-path case was already fixed in 7223a8d and remains covered by tests.

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/local-personas.ts (1)

282-315: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject intent on overlays instead of silently ignoring it.

If a file sets both extends and intent, the standalone path never runs and the merged spec keeps base.intent, so the authored intent is dropped without any warning. Failing fast here would make that misconfiguration obvious.

Suggested fix
 if (raw.extends !== undefined && (typeof raw.extends !== 'string' || !raw.extends.trim())) {
throw new Error(`${context}.extends must be a non-empty string if provided`);
}
+ if (raw.intent !== undefined && raw.extends !== undefined) {+ throw new Error(+ `${context}.intent cannot be combined with .extends; omit extends for standalone personas`+ );+ }
if (raw.intent !== undefined && !PERSONA_INTENTS.includes(raw.intent as PersonaIntent)) {
throw new Error(`${context}.intent must be one of: ${PERSONA_INTENTS.join(', ')}`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/local-personas.ts` around lines 282 - 315, The validator
currently allows an overlay to specify raw.intent which then gets ignored when
extends is present; add a failing check that throws when both raw.extends and
raw.intent are provided (e.g., before the existing intent validation block),
such as: if (raw.extends !== undefined && raw.intent !== undefined) throw new
Error(`${context}.intent cannot be set on overlays that extend another
persona`); update the validation near the raw.intent checks (referencing
raw.intent, raw.extends, and PERSONA_INTENTS) so overlays must not declare
intent and the error surfaces immediately.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/README.md`:
- Around line 76-78: Update the two fenced code blocks in the README that show
the install usage and the file mapping examples so they use the "text" fence
language instead of unlabeled fences; specifically replace the opening backticks
for the block containing "agentworkforce install <pkg|path> [--persona <id> ...]
[--overwrite]" and the block containing
"package/personas/nested/code-reviewer.json ->
.agentworkforce/workforce/personas/code-reviewer.json" with ```text (leave the
contents unchanged) to satisfy markdownlint.
In `@packages/cli/src/persona-install.ts`:
- Around line 69-86: The local install detection and expansion don't handle
Windows-style home paths (e.g. "~\\personas"); update isLocalInstallSource to
also treat strings starting with "~\\" as local, and update expandHomePath to
handle inputs starting with "~\\" by joining homedir() with input.slice(2) (same
as the "~/” case), keeping the existing "~" and "/"-prefixed logic; ensure
resolveLocalSource continues to call expandHomePath and use
isAbsolute/resolvePath as before.
In `@README.md`:
- Around line 140-146: Update the fenced code block that shows the directory
tree starting with "@acme/personas/" to include a language tag by changing the
opening backticks to "```text" so the block becomes a labeled plaintext block;
locate the block containing the lines "@acme/personas/", "├── package.json", and
"└── personas/" and add the "text" language specifier to the opening fence.
---
Outside diff comments:
In `@packages/cli/src/local-personas.ts`:
- Around line 282-315: The validator currently allows an overlay to specify
raw.intent which then gets ignored when extends is present; add a failing check
that throws when both raw.extends and raw.intent are provided (e.g., before the
existing intent validation block), such as: if (raw.extends !== undefined &&
raw.intent !== undefined) throw new Error(`${context}.intent cannot be set on
overlays that extend another persona`); update the validation near the
raw.intent checks (referencing raw.intent, raw.extends, and PERSONA_INTENTS) so
overlays must not declare intent and the error surfaces immediately.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8de86882-c3c3-4769-8770-d90f90443dfc

📥 Commits

Reviewing files that changed from the base of the PR and between 64592b9 and bb8ff10.

📒 Files selected for processing (7)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/cli.test.ts
  • packages/cli/src/cli.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts

Comment threadpackages/cli/README.md Outdated
Comment threadpackages/cli/src/persona-install.ts
Comment threadREADME.md Outdated

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/cli/src/persona-install.ts (1)

69-77: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat absolute Windows paths as local sources too.

This still routes valid Windows local paths like C:\packs\foo or \\server\share\foo into the npm-package branch, because the detection only checks string prefixes. That makes local installs fail on Windows unless the path happens to start with . or ~.

Suggested fix
 export function isLocalInstallSource(source: string): boolean {
+ const trimmed = source.trim();
return (
- source.startsWith('.') ||- source.startsWith('/') ||- source.startsWith('~/') ||- source.startsWith('~\\') ||- source === '~'+ trimmed.startsWith('.') ||+ trimmed === '~' ||+ trimmed.startsWith('~/') ||+ trimmed.startsWith('~\\') ||+ isAbsolute(trimmed)
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/persona-install.ts` around lines 69 - 77, The
isLocalInstallSource function incorrectly treats Windows absolute paths as
non-local; update isLocalInstallSource to also return true for Windows
drive-letter absolute paths (e.g., matches /^[A-Za-z]:[\\/]/) and UNC paths
(e.g., starts with '\\\\'), in addition to the existing checks for '.', '/', '~'
and '~/'; modify the function to test these regex/prefix conditions
(drive-letter and UNC) and return true when any of them match.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 331-343: The temp directory is created (tempDir =
mkdtempSync(...)) only inside the npm-resolution branch before the try, but
cleanup lives in the finally after try so exceptions from resolver or
assertDirectory leak the temp dir; move the temp-dir lifecycle so it is created
before entering the try/finally that performs cleanup: when
!isLocalInstallSource(source) call mkdtempSync and set tempDir, then call
resolver (options.resolveNpmPackage ?? defaultResolveNpmPackage) and
assertDirectory inside the try block so the finally can always remove tempDir;
reference tempDir, mkdtempSync,
resolver/resolveNpmPackage/defaultResolveNpmPackage, packageRoot and
assertDirectory to locate the changes.
- Around line 285-289: The spawnSync call that sets packed currently invokes
'npm' with shell: false, which fails on Windows because the npm shim is
'npm.cmd'; change the command argument in that spawnSync (the one assigning to
packed) to choose the correct executable like: const npmExe = process.platform
=== 'win32' ? 'npm.cmd' : 'npm'; then call spawnSync(npmExe, ['pack', spec,
'--pack-destination', packDir, '--json'], { encoding: 'utf8', shell: false });
alternatively use cross-spawn to abstract this; update the spawnSync invocation
that creates packed (and any nearby npm spawn calls) to use npmExe or
cross-spawn so Windows uses the npm shim.
---
Duplicate comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 69-77: The isLocalInstallSource function incorrectly treats
Windows absolute paths as non-local; update isLocalInstallSource to also return
true for Windows drive-letter absolute paths (e.g., matches /^[A-Za-z]:[\\/]/)
and UNC paths (e.g., starts with '\\\\'), in addition to the existing checks for
'.', '/', '~' and '~/'; modify the function to test these regex/prefix
conditions (drive-letter and UNC) and return true when any of them match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 54b1d3e1-7ca4-4955-8cb6-f64929447549

📥 Commits

Reviewing files that changed from the base of the PR and between bb8ff10 and 7223a8d.

📒 Files selected for processing (2)
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/cli/src/persona-install.test.ts

Comment threadpackages/cli/src/persona-install.ts
Comment threadpackages/cli/src/persona-install.ts
Comment threadpackages/cli/src/persona-install.ts
@willwashburn

Copy link
Copy Markdown
MemberAuthor

Review follow-up pushed in 8acf7e4.

Fixed still-valid items:

  • README install/tree fences now use text where requested.
  • Windows local-source detection now covers drive-letter paths and UNC paths; ~\\... was already fixed in 7223a8d.
  • npm pack now invokes npm.cmd on Windows when shell: false is used.
  • npm install temp dirs are cleaned if resolver/package-root validation throws.
  • local persona overlays now reject intent when extends is also present instead of silently ignoring it.

Skipped: CodeRabbit docstring coverage warning is advisory and not enforced by repo checks; adding broad docstrings would be unrelated to the review findings.

Validated with corepack pnpm --filter @agentworkforce/cli test and npm run check.

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 292-296: The npm pack invocation in persona-install.ts (the
spawnSync call that sets packed) is vulnerable because git+ specs can run
lifecycle scripts; add the '--ignore-scripts' flag to the array of arguments
passed to npmExecutable() in that spawnSync call so npm pack is executed with
scripts disabled, and keep the change scoped to the same spawnSync invocation
that produces the packed variable (consider documenting the remaining caveat
about old npm versions and .npmrc re-enabling scripts elsewhere if desired).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 506abb40-d5b6-4406-b83b-9fadd5dcce94

📥 Commits

Reviewing files that changed from the base of the PR and between 7223a8d and 8acf7e4.

📒 Files selected for processing (6)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/local-personas.test.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli/README.md
  • packages/cli/src/persona-install.test.ts

Comment threadpackages/cli/src/persona-install.ts

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 69-79: The isLocalInstallSource function currently misses Windows
root-relative paths like "\personas"; update isLocalInstallSource to treat any
single leading backslash as a local path by adding a check for
trimmed.startsWith('\\') (or a regex that matches a single leading backslash) so
those inputs are routed to resolveLocalSource as local installs (you can replace
the existing '\\\\' check with a broader '\\' check to still cover UNC paths).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f491083a-dcdf-411e-be18-f58cb49f0ca1

📥 Commits

Reviewing files that changed from the base of the PR and between 8acf7e4 and eae2954.

📒 Files selected for processing (1)
  • packages/cli/src/persona-install.ts

Comment on lines +69 to +79
export function isLocalInstallSource(source: string): boolean {
const trimmed = source.trim();
return (
trimmed.startsWith('.') ||
trimmed.startsWith('/') ||
trimmed.startsWith('~/') ||
trimmed.startsWith('~\\') ||
trimmed === '~' ||
/^[A-Za-z]:[\\/]/.test(trimmed) ||
trimmed.startsWith('\\\\')
);

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Node.js path handling, is a Windows path like "\personas" considered absolute (e.g., path.win32.isAbsolute("\\personas"))?

💡 Result:

Yes, in Node.js, a Windows path like "\personas" (which is the string "\personas" due to JavaScript escaping) is considered absolute by path.win32.isAbsolute. This returns true because it starts with a path separator (), making it a drive-relative path from the root of the current drive, consistent with Windows PathIsRelative API behavior [1][2][3]. Official Node.js documentation shows path.win32.isAbsolute('\foo') effectively returns true, as single-backslash paths are treated as absolute (equivalent to forward slash '/' examples which also return true) [4][5][6][3]. Note that "\personas" (four characters, two backslashes) would be an incomplete UNC path but still absolute per the logic checking initial separators [4]. This behavior has remained unchanged through Node.js v26 [5].

Citations:


🏁 Script executed:

cat -n packages/cli/src/persona-install.ts | head -150

Repository: AgentWorkforce/workforce

Length of output: 5311


🏁 Script executed:

rg -n "isLocalInstallSource" packages/cli/src/persona-install.ts

Repository: AgentWorkforce/workforce

Length of output: 179


🏁 Script executed:

cat -n packages/cli/src/persona-install.ts | sed -n '330,380p'

Repository: AgentWorkforce/workforce

Length of output: 2105


Handle Windows root-relative paths (\foo) as local install sources.

isLocalInstallSource misses valid Windows root-relative absolute paths. On Windows, \personas should resolve as a local path via resolveLocalSource, but this check returns false and routes it to npm package resolution instead, causing an error.

Suggested fix
 export function isLocalInstallSource(source: string): boolean {
const trimmed = source.trim();
return (
trimmed.startsWith('.') ||
trimmed.startsWith('/') ||
+ trimmed.startsWith('\\') ||
trimmed.startsWith('~/') ||
trimmed.startsWith('~\\') ||
trimmed === '~' ||
/^[A-Za-z]:[\\/]/.test(trimmed) ||
trimmed.startsWith('\\\\')
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/persona-install.ts` around lines 69 - 79, The
isLocalInstallSource function currently misses Windows root-relative paths like
"\personas"; update isLocalInstallSource to treat any single leading backslash
as a local path by adding a check for trimmed.startsWith('\\') (or a regex that
matches a single leading backslash) so those inputs are routed to
resolveLocalSource as local installs (you can replace the existing '\\\\' check
with a broader '\\' check to still cover UNC paths).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Design and implement shadcn-style persona installs

1 participant

@willwashburn
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(cli): add persona pack install command - #40

Merged
willwashburn merged 4 commits into
mainfrom
feat/installable-persona-sources
May 7, 2026
Merged

feat(cli): add persona pack install command#40
willwashburn merged 4 commits into
mainfrom
feat/installable-persona-sources

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Closes#36

Summary

  • add agentworkforce install for npm and local persona packs
  • copy persona JSON into .agentworkforce/workforce/personas/ with subset selection, flattened filenames, conflict reporting, and --overwrite
  • allow standalone local persona specs so installed pack personas load through the cwd cascade
  • document package format, sources relationship, and persona pack authoring flow

Tests

  • npm run check

@coderabbitai

coderabbitaiBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Implements a shadcn-style agentworkforce install flow: CLI subcommand to copy persona JSONs from npm packages or local paths into /.agentworkforce/workforce/personas/, with npm pack extraction, persona-id filtering, filename flattening, conflict detection, optional overwrite, temp cleanup, tests, and docs. Adds standalone local persona support via an optional intent field.

Changes

Persona Install + Local Persona Standalone

Layer / File(s)Summary
Local persona override
packages/cli/src/local-personas.ts
Adds optional intent?: PersonaIntent, rejects intent combined with .extends, validates standalone tiers and runtime/harnessSettings, and constructs a full PersonaSpec for standalone overrides.
Install Implementation
packages/cli/src/persona-install.ts
Implements installPersonas() with local vs npm detection, npm pack + tar extraction, package.json.agentworkforce.personas (or personas/) resolution, recursive discovery of *.json, persona id validation, duplicate-id and flattened-filename collision checks, requested-id filtering (fail-fast), copy into .agentworkforce/workforce/personas/, conflict recording, and temp-dir cleanup. Exposes expandHomePath, npmExecutable, projectPersonaInstallDir, and isLocalInstallSource.
CLI Usage, Types & Wiring
README.md, packages/cli/README.md, packages/cli/src/cli.ts
Adds `agentworkforce install [flags] <pkg
Documentation
README.md, packages/cli/README.md
Adds full "Install persona packs" section describing package layout (agentworkforce.personas), flattening, conflict/overwrite rules, persona subset selection with --persona, npm vs local resolution, and an authoring/publishing example. Updates local persona docs for standalone requirements.
Tests
packages/cli/src/persona-install.test.ts, packages/cli/src/cli.test.ts, packages/cli/src/local-personas.test.ts
Adds tests for parseInstallArgs; comprehensive installer tests for npm/local detection, expandHomePath and npmExecutable, metadata fallback, discovery, persona-id filtering and fail-fast behavior, filename flattening, conflict recording, overwrite behavior, collisions across packages, temp cleanup, and standalone override rejection when extends and intent combined.

Sequence Diagram

sequenceDiagram
actor User
participant CLI as "agentworkforce CLI"
participant Parser as "parseInstallArgs"
participant Handler as "runPersonaInstall"
participant Installer as "installPersonas"
participant NPM as "npm (external)"
participant FS as "File System"
participant Loader as "loadLocalPersonas"
User->>CLI: agentworkforce install <pkg|path> --persona id --overwrite
CLI->>Parser: parseInstallArgs(args)
Parser-->>CLI: PersonaInstallArgs{source, personaIds, overwrite}
CLI->>Handler: runPersonaInstall(rest)
Handler->>Installer: installPersonas({source, personaIds, overwrite, cwd})
alt source is npm package
Installer->>NPM: npm pack <pkg> --json (temp dir)
NPM-->>Installer: <pkg>.tgz
Installer->>FS: Extract tarball -> /tmp/extracted/package/
else source is local path
Installer->>FS: Resolve/expand local path -> ./local-personas/
end
Installer->>FS: Read package.json (agentworkforce.personas?) FS-->>Installer: personaDir path
Installer->>FS: Scan personaDir for *.json
FS-->>Installer: List of persona files
Installer->>Installer: Parse each JSON, validate id, dedupe/filter by personaIds
Installer->>FS: Copy selected files to .agentworkforce/workforce/personas/ (flattened)
FS-->>Installer: Result {installed, conflicts}
Installer-->>Handler: PersonaInstallResult
Handler->>FS: Write stdout (installed summary)
Handler->>FS: Write stderr (conflicts)
Handler->>Loader: loadLocalPersonas(cwd)
Loader-->>Handler: Installed personas discoverable
Handler-->>User: Exit status (0 if no conflicts, non-zero if conflicts)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

  • #36 Design and implement shadcn-style persona installs — Implements the full install command, npm pack flow, persona filtering, flattening, conflict rules, and docs described in the issue.
  • Split first-party personas into installable persona packs #38 — The changes implement the persona-pack install flow and behavior sought by this issue.

Possibly related PRs

Poem

🐰
I hopped to fetch persona packs with care,
Unpacked tarballs and flattened paths to spare,
Copied JSON friends into your repo lair,
Conflicts flagged — overwrite if you dare,
Now personas hop, ready to chat and share.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe PR title clearly and concisely identifies the main change: adding a persona pack install command to the CLI.
Description check✅ PassedThe PR description is directly related to the changeset, summarizing the install command implementation, copy behavior, standalone personas, documentation, and tests.
Linked Issues check✅ PassedAll core requirements from issue #36 are met: install command for npm/local sources, subset selection via --persona flags, flattened filenames, conflict reporting with --overwrite, standalone personas, and comprehensive documentation and tests.
Out of Scope Changes check✅ PassedAll code changes directly support the install command feature and documented objectives, with no unrelated modifications detected.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/installable-persona-sources

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:bb8ff10f01

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +69 to +75
export function isLocalInstallSource(source: string): boolean {
return (
source.startsWith('.') ||
source.startsWith('/') ||
source.startsWith('~/') ||
source === '~'
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize Windows absolute paths as local sources

isLocalInstallSource only treats POSIX-style prefixes (., /, ~/, ~) as local paths, so valid Windows local paths like C:\\packs\\personas or ~\\personas are misclassified as npm specs. In that case installPersonas takes the npm branch and calls npm pack on a filesystem path, causing agentworkforce install <absolute-path> to fail on Windows even though absolute local paths are documented as supported.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Addressed in 8acf7e4: isLocalInstallSource() now treats Windows drive-letter paths (C:\\...) and UNC paths (\\\\server\\...) as local sources. The ~\\... home-path case was already fixed in 7223a8d and remains covered by tests.

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/local-personas.ts (1)

282-315: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject intent on overlays instead of silently ignoring it.

If a file sets both extends and intent, the standalone path never runs and the merged spec keeps base.intent, so the authored intent is dropped without any warning. Failing fast here would make that misconfiguration obvious.

Suggested fix
 if (raw.extends !== undefined && (typeof raw.extends !== 'string' || !raw.extends.trim())) {
throw new Error(`${context}.extends must be a non-empty string if provided`);
}
+ if (raw.intent !== undefined && raw.extends !== undefined) {+ throw new Error(+ `${context}.intent cannot be combined with .extends; omit extends for standalone personas`+ );+ }
if (raw.intent !== undefined && !PERSONA_INTENTS.includes(raw.intent as PersonaIntent)) {
throw new Error(`${context}.intent must be one of: ${PERSONA_INTENTS.join(', ')}`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/local-personas.ts` around lines 282 - 315, The validator
currently allows an overlay to specify raw.intent which then gets ignored when
extends is present; add a failing check that throws when both raw.extends and
raw.intent are provided (e.g., before the existing intent validation block),
such as: if (raw.extends !== undefined && raw.intent !== undefined) throw new
Error(`${context}.intent cannot be set on overlays that extend another
persona`); update the validation near the raw.intent checks (referencing
raw.intent, raw.extends, and PERSONA_INTENTS) so overlays must not declare
intent and the error surfaces immediately.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/README.md`:
- Around line 76-78: Update the two fenced code blocks in the README that show
the install usage and the file mapping examples so they use the "text" fence
language instead of unlabeled fences; specifically replace the opening backticks
for the block containing "agentworkforce install <pkg|path> [--persona <id> ...]
[--overwrite]" and the block containing
"package/personas/nested/code-reviewer.json ->
.agentworkforce/workforce/personas/code-reviewer.json" with ```text (leave the
contents unchanged) to satisfy markdownlint.
In `@packages/cli/src/persona-install.ts`:
- Around line 69-86: The local install detection and expansion don't handle
Windows-style home paths (e.g. "~\\personas"); update isLocalInstallSource to
also treat strings starting with "~\\" as local, and update expandHomePath to
handle inputs starting with "~\\" by joining homedir() with input.slice(2) (same
as the "~/” case), keeping the existing "~" and "/"-prefixed logic; ensure
resolveLocalSource continues to call expandHomePath and use
isAbsolute/resolvePath as before.
In `@README.md`:
- Around line 140-146: Update the fenced code block that shows the directory
tree starting with "@acme/personas/" to include a language tag by changing the
opening backticks to "```text" so the block becomes a labeled plaintext block;
locate the block containing the lines "@acme/personas/", "├── package.json", and
"└── personas/" and add the "text" language specifier to the opening fence.
---
Outside diff comments:
In `@packages/cli/src/local-personas.ts`:
- Around line 282-315: The validator currently allows an overlay to specify
raw.intent which then gets ignored when extends is present; add a failing check
that throws when both raw.extends and raw.intent are provided (e.g., before the
existing intent validation block), such as: if (raw.extends !== undefined &&
raw.intent !== undefined) throw new Error(`${context}.intent cannot be set on
overlays that extend another persona`); update the validation near the
raw.intent checks (referencing raw.intent, raw.extends, and PERSONA_INTENTS) so
overlays must not declare intent and the error surfaces immediately.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8de86882-c3c3-4769-8770-d90f90443dfc

📥 Commits

Reviewing files that changed from the base of the PR and between 64592b9 and bb8ff10.

📒 Files selected for processing (7)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/cli.test.ts
  • packages/cli/src/cli.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts

Comment threadpackages/cli/README.md Outdated
Comment threadpackages/cli/src/persona-install.ts
Comment threadREADME.md Outdated

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/cli/src/persona-install.ts (1)

69-77: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat absolute Windows paths as local sources too.

This still routes valid Windows local paths like C:\packs\foo or \\server\share\foo into the npm-package branch, because the detection only checks string prefixes. That makes local installs fail on Windows unless the path happens to start with . or ~.

Suggested fix
 export function isLocalInstallSource(source: string): boolean {
+ const trimmed = source.trim();
return (
- source.startsWith('.') ||- source.startsWith('/') ||- source.startsWith('~/') ||- source.startsWith('~\\') ||- source === '~'+ trimmed.startsWith('.') ||+ trimmed === '~' ||+ trimmed.startsWith('~/') ||+ trimmed.startsWith('~\\') ||+ isAbsolute(trimmed)
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/persona-install.ts` around lines 69 - 77, The
isLocalInstallSource function incorrectly treats Windows absolute paths as
non-local; update isLocalInstallSource to also return true for Windows
drive-letter absolute paths (e.g., matches /^[A-Za-z]:[\\/]/) and UNC paths
(e.g., starts with '\\\\'), in addition to the existing checks for '.', '/', '~'
and '~/'; modify the function to test these regex/prefix conditions
(drive-letter and UNC) and return true when any of them match.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 331-343: The temp directory is created (tempDir =
mkdtempSync(...)) only inside the npm-resolution branch before the try, but
cleanup lives in the finally after try so exceptions from resolver or
assertDirectory leak the temp dir; move the temp-dir lifecycle so it is created
before entering the try/finally that performs cleanup: when
!isLocalInstallSource(source) call mkdtempSync and set tempDir, then call
resolver (options.resolveNpmPackage ?? defaultResolveNpmPackage) and
assertDirectory inside the try block so the finally can always remove tempDir;
reference tempDir, mkdtempSync,
resolver/resolveNpmPackage/defaultResolveNpmPackage, packageRoot and
assertDirectory to locate the changes.
- Around line 285-289: The spawnSync call that sets packed currently invokes
'npm' with shell: false, which fails on Windows because the npm shim is
'npm.cmd'; change the command argument in that spawnSync (the one assigning to
packed) to choose the correct executable like: const npmExe = process.platform
=== 'win32' ? 'npm.cmd' : 'npm'; then call spawnSync(npmExe, ['pack', spec,
'--pack-destination', packDir, '--json'], { encoding: 'utf8', shell: false });
alternatively use cross-spawn to abstract this; update the spawnSync invocation
that creates packed (and any nearby npm spawn calls) to use npmExe or
cross-spawn so Windows uses the npm shim.
---
Duplicate comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 69-77: The isLocalInstallSource function incorrectly treats
Windows absolute paths as non-local; update isLocalInstallSource to also return
true for Windows drive-letter absolute paths (e.g., matches /^[A-Za-z]:[\\/]/)
and UNC paths (e.g., starts with '\\\\'), in addition to the existing checks for
'.', '/', '~' and '~/'; modify the function to test these regex/prefix
conditions (drive-letter and UNC) and return true when any of them match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 54b1d3e1-7ca4-4955-8cb6-f64929447549

📥 Commits

Reviewing files that changed from the base of the PR and between bb8ff10 and 7223a8d.

📒 Files selected for processing (2)
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/cli/src/persona-install.test.ts

Comment threadpackages/cli/src/persona-install.ts
Comment threadpackages/cli/src/persona-install.ts
Comment threadpackages/cli/src/persona-install.ts
@willwashburn

Copy link
Copy Markdown
MemberAuthor

Review follow-up pushed in 8acf7e4.

Fixed still-valid items:

  • README install/tree fences now use text where requested.
  • Windows local-source detection now covers drive-letter paths and UNC paths; ~\\... was already fixed in 7223a8d.
  • npm pack now invokes npm.cmd on Windows when shell: false is used.
  • npm install temp dirs are cleaned if resolver/package-root validation throws.
  • local persona overlays now reject intent when extends is also present instead of silently ignoring it.

Skipped: CodeRabbit docstring coverage warning is advisory and not enforced by repo checks; adding broad docstrings would be unrelated to the review findings.

Validated with corepack pnpm --filter @agentworkforce/cli test and npm run check.

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 292-296: The npm pack invocation in persona-install.ts (the
spawnSync call that sets packed) is vulnerable because git+ specs can run
lifecycle scripts; add the '--ignore-scripts' flag to the array of arguments
passed to npmExecutable() in that spawnSync call so npm pack is executed with
scripts disabled, and keep the change scoped to the same spawnSync invocation
that produces the packed variable (consider documenting the remaining caveat
about old npm versions and .npmrc re-enabling scripts elsewhere if desired).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 506abb40-d5b6-4406-b83b-9fadd5dcce94

📥 Commits

Reviewing files that changed from the base of the PR and between 7223a8d and 8acf7e4.

📒 Files selected for processing (6)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/local-personas.test.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli/README.md
  • packages/cli/src/persona-install.test.ts

Comment threadpackages/cli/src/persona-install.ts

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 69-79: The isLocalInstallSource function currently misses Windows
root-relative paths like "\personas"; update isLocalInstallSource to treat any
single leading backslash as a local path by adding a check for
trimmed.startsWith('\\') (or a regex that matches a single leading backslash) so
those inputs are routed to resolveLocalSource as local installs (you can replace
the existing '\\\\' check with a broader '\\' check to still cover UNC paths).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f491083a-dcdf-411e-be18-f58cb49f0ca1

📥 Commits

Reviewing files that changed from the base of the PR and between 8acf7e4 and eae2954.

📒 Files selected for processing (1)
  • packages/cli/src/persona-install.ts

Comment on lines +69 to +79
export function isLocalInstallSource(source: string): boolean {
const trimmed = source.trim();
return (
trimmed.startsWith('.') ||
trimmed.startsWith('/') ||
trimmed.startsWith('~/') ||
trimmed.startsWith('~\\') ||
trimmed === '~' ||
/^[A-Za-z]:[\\/]/.test(trimmed) ||
trimmed.startsWith('\\\\')
);

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Node.js path handling, is a Windows path like "\personas" considered absolute (e.g., path.win32.isAbsolute("\\personas"))?

💡 Result:

Yes, in Node.js, a Windows path like "\personas" (which is the string "\personas" due to JavaScript escaping) is considered absolute by path.win32.isAbsolute. This returns true because it starts with a path separator (), making it a drive-relative path from the root of the current drive, consistent with Windows PathIsRelative API behavior [1][2][3]. Official Node.js documentation shows path.win32.isAbsolute('\foo') effectively returns true, as single-backslash paths are treated as absolute (equivalent to forward slash '/' examples which also return true) [4][5][6][3]. Note that "\personas" (four characters, two backslashes) would be an incomplete UNC path but still absolute per the logic checking initial separators [4]. This behavior has remained unchanged through Node.js v26 [5].

Citations:


🏁 Script executed:

cat -n packages/cli/src/persona-install.ts | head -150

Repository: AgentWorkforce/workforce

Length of output: 5311


🏁 Script executed:

rg -n "isLocalInstallSource" packages/cli/src/persona-install.ts

Repository: AgentWorkforce/workforce

Length of output: 179


🏁 Script executed:

cat -n packages/cli/src/persona-install.ts | sed -n '330,380p'

Repository: AgentWorkforce/workforce

Length of output: 2105


Handle Windows root-relative paths (\foo) as local install sources.

isLocalInstallSource misses valid Windows root-relative absolute paths. On Windows, \personas should resolve as a local path via resolveLocalSource, but this check returns false and routes it to npm package resolution instead, causing an error.

Suggested fix
 export function isLocalInstallSource(source: string): boolean {
const trimmed = source.trim();
return (
trimmed.startsWith('.') ||
trimmed.startsWith('/') ||
+ trimmed.startsWith('\\') ||
trimmed.startsWith('~/') ||
trimmed.startsWith('~\\') ||
trimmed === '~' ||
/^[A-Za-z]:[\\/]/.test(trimmed) ||
trimmed.startsWith('\\\\')
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/persona-install.ts` around lines 69 - 79, The
isLocalInstallSource function currently misses Windows root-relative paths like
"\personas"; update isLocalInstallSource to treat any single leading backslash
as a local path by adding a check for trimmed.startsWith('\\') (or a regex that
matches a single leading backslash) so those inputs are routed to
resolveLocalSource as local installs (you can replace the existing '\\\\' check
with a broader '\\' check to still cover UNC paths).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Design and implement shadcn-style persona installs

1 participant

@willwashburn
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(cli): add persona pack install command - #40

Merged
willwashburn merged 4 commits into
mainfrom
feat/installable-persona-sources
May 7, 2026
Merged

feat(cli): add persona pack install command#40
willwashburn merged 4 commits into
mainfrom
feat/installable-persona-sources

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Closes#36

Summary

  • add agentworkforce install for npm and local persona packs
  • copy persona JSON into .agentworkforce/workforce/personas/ with subset selection, flattened filenames, conflict reporting, and --overwrite
  • allow standalone local persona specs so installed pack personas load through the cwd cascade
  • document package format, sources relationship, and persona pack authoring flow

Tests

  • npm run check

@coderabbitai

coderabbitaiBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Implements a shadcn-style agentworkforce install flow: CLI subcommand to copy persona JSONs from npm packages or local paths into /.agentworkforce/workforce/personas/, with npm pack extraction, persona-id filtering, filename flattening, conflict detection, optional overwrite, temp cleanup, tests, and docs. Adds standalone local persona support via an optional intent field.

Changes

Persona Install + Local Persona Standalone

Layer / File(s)Summary
Local persona override
packages/cli/src/local-personas.ts
Adds optional intent?: PersonaIntent, rejects intent combined with .extends, validates standalone tiers and runtime/harnessSettings, and constructs a full PersonaSpec for standalone overrides.
Install Implementation
packages/cli/src/persona-install.ts
Implements installPersonas() with local vs npm detection, npm pack + tar extraction, package.json.agentworkforce.personas (or personas/) resolution, recursive discovery of *.json, persona id validation, duplicate-id and flattened-filename collision checks, requested-id filtering (fail-fast), copy into .agentworkforce/workforce/personas/, conflict recording, and temp-dir cleanup. Exposes expandHomePath, npmExecutable, projectPersonaInstallDir, and isLocalInstallSource.
CLI Usage, Types & Wiring
README.md, packages/cli/README.md, packages/cli/src/cli.ts
Adds `agentworkforce install [flags] <pkg
Documentation
README.md, packages/cli/README.md
Adds full "Install persona packs" section describing package layout (agentworkforce.personas), flattening, conflict/overwrite rules, persona subset selection with --persona, npm vs local resolution, and an authoring/publishing example. Updates local persona docs for standalone requirements.
Tests
packages/cli/src/persona-install.test.ts, packages/cli/src/cli.test.ts, packages/cli/src/local-personas.test.ts
Adds tests for parseInstallArgs; comprehensive installer tests for npm/local detection, expandHomePath and npmExecutable, metadata fallback, discovery, persona-id filtering and fail-fast behavior, filename flattening, conflict recording, overwrite behavior, collisions across packages, temp cleanup, and standalone override rejection when extends and intent combined.

Sequence Diagram

sequenceDiagram
actor User
participant CLI as "agentworkforce CLI"
participant Parser as "parseInstallArgs"
participant Handler as "runPersonaInstall"
participant Installer as "installPersonas"
participant NPM as "npm (external)"
participant FS as "File System"
participant Loader as "loadLocalPersonas"
User->>CLI: agentworkforce install <pkg|path> --persona id --overwrite
CLI->>Parser: parseInstallArgs(args)
Parser-->>CLI: PersonaInstallArgs{source, personaIds, overwrite}
CLI->>Handler: runPersonaInstall(rest)
Handler->>Installer: installPersonas({source, personaIds, overwrite, cwd})
alt source is npm package
Installer->>NPM: npm pack <pkg> --json (temp dir)
NPM-->>Installer: <pkg>.tgz
Installer->>FS: Extract tarball -> /tmp/extracted/package/
else source is local path
Installer->>FS: Resolve/expand local path -> ./local-personas/
end
Installer->>FS: Read package.json (agentworkforce.personas?) FS-->>Installer: personaDir path
Installer->>FS: Scan personaDir for *.json
FS-->>Installer: List of persona files
Installer->>Installer: Parse each JSON, validate id, dedupe/filter by personaIds
Installer->>FS: Copy selected files to .agentworkforce/workforce/personas/ (flattened)
FS-->>Installer: Result {installed, conflicts}
Installer-->>Handler: PersonaInstallResult
Handler->>FS: Write stdout (installed summary)
Handler->>FS: Write stderr (conflicts)
Handler->>Loader: loadLocalPersonas(cwd)
Loader-->>Handler: Installed personas discoverable
Handler-->>User: Exit status (0 if no conflicts, non-zero if conflicts)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

  • #36 Design and implement shadcn-style persona installs — Implements the full install command, npm pack flow, persona filtering, flattening, conflict rules, and docs described in the issue.
  • Split first-party personas into installable persona packs #38 — The changes implement the persona-pack install flow and behavior sought by this issue.

Possibly related PRs

Poem

🐰
I hopped to fetch persona packs with care,
Unpacked tarballs and flattened paths to spare,
Copied JSON friends into your repo lair,
Conflicts flagged — overwrite if you dare,
Now personas hop, ready to chat and share.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe PR title clearly and concisely identifies the main change: adding a persona pack install command to the CLI.
Description check✅ PassedThe PR description is directly related to the changeset, summarizing the install command implementation, copy behavior, standalone personas, documentation, and tests.
Linked Issues check✅ PassedAll core requirements from issue #36 are met: install command for npm/local sources, subset selection via --persona flags, flattened filenames, conflict reporting with --overwrite, standalone personas, and comprehensive documentation and tests.
Out of Scope Changes check✅ PassedAll code changes directly support the install command feature and documented objectives, with no unrelated modifications detected.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/installable-persona-sources

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:bb8ff10f01

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +69 to +75
export function isLocalInstallSource(source: string): boolean {
return (
source.startsWith('.') ||
source.startsWith('/') ||
source.startsWith('~/') ||
source === '~'
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize Windows absolute paths as local sources

isLocalInstallSource only treats POSIX-style prefixes (., /, ~/, ~) as local paths, so valid Windows local paths like C:\\packs\\personas or ~\\personas are misclassified as npm specs. In that case installPersonas takes the npm branch and calls npm pack on a filesystem path, causing agentworkforce install <absolute-path> to fail on Windows even though absolute local paths are documented as supported.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Addressed in 8acf7e4: isLocalInstallSource() now treats Windows drive-letter paths (C:\\...) and UNC paths (\\\\server\\...) as local sources. The ~\\... home-path case was already fixed in 7223a8d and remains covered by tests.

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/local-personas.ts (1)

282-315: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject intent on overlays instead of silently ignoring it.

If a file sets both extends and intent, the standalone path never runs and the merged spec keeps base.intent, so the authored intent is dropped without any warning. Failing fast here would make that misconfiguration obvious.

Suggested fix
 if (raw.extends !== undefined && (typeof raw.extends !== 'string' || !raw.extends.trim())) {
throw new Error(`${context}.extends must be a non-empty string if provided`);
}
+ if (raw.intent !== undefined && raw.extends !== undefined) {+ throw new Error(+ `${context}.intent cannot be combined with .extends; omit extends for standalone personas`+ );+ }
if (raw.intent !== undefined && !PERSONA_INTENTS.includes(raw.intent as PersonaIntent)) {
throw new Error(`${context}.intent must be one of: ${PERSONA_INTENTS.join(', ')}`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/local-personas.ts` around lines 282 - 315, The validator
currently allows an overlay to specify raw.intent which then gets ignored when
extends is present; add a failing check that throws when both raw.extends and
raw.intent are provided (e.g., before the existing intent validation block),
such as: if (raw.extends !== undefined && raw.intent !== undefined) throw new
Error(`${context}.intent cannot be set on overlays that extend another
persona`); update the validation near the raw.intent checks (referencing
raw.intent, raw.extends, and PERSONA_INTENTS) so overlays must not declare
intent and the error surfaces immediately.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/README.md`:
- Around line 76-78: Update the two fenced code blocks in the README that show
the install usage and the file mapping examples so they use the "text" fence
language instead of unlabeled fences; specifically replace the opening backticks
for the block containing "agentworkforce install <pkg|path> [--persona <id> ...]
[--overwrite]" and the block containing
"package/personas/nested/code-reviewer.json ->
.agentworkforce/workforce/personas/code-reviewer.json" with ```text (leave the
contents unchanged) to satisfy markdownlint.
In `@packages/cli/src/persona-install.ts`:
- Around line 69-86: The local install detection and expansion don't handle
Windows-style home paths (e.g. "~\\personas"); update isLocalInstallSource to
also treat strings starting with "~\\" as local, and update expandHomePath to
handle inputs starting with "~\\" by joining homedir() with input.slice(2) (same
as the "~/” case), keeping the existing "~" and "/"-prefixed logic; ensure
resolveLocalSource continues to call expandHomePath and use
isAbsolute/resolvePath as before.
In `@README.md`:
- Around line 140-146: Update the fenced code block that shows the directory
tree starting with "@acme/personas/" to include a language tag by changing the
opening backticks to "```text" so the block becomes a labeled plaintext block;
locate the block containing the lines "@acme/personas/", "├── package.json", and
"└── personas/" and add the "text" language specifier to the opening fence.
---
Outside diff comments:
In `@packages/cli/src/local-personas.ts`:
- Around line 282-315: The validator currently allows an overlay to specify
raw.intent which then gets ignored when extends is present; add a failing check
that throws when both raw.extends and raw.intent are provided (e.g., before the
existing intent validation block), such as: if (raw.extends !== undefined &&
raw.intent !== undefined) throw new Error(`${context}.intent cannot be set on
overlays that extend another persona`); update the validation near the
raw.intent checks (referencing raw.intent, raw.extends, and PERSONA_INTENTS) so
overlays must not declare intent and the error surfaces immediately.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8de86882-c3c3-4769-8770-d90f90443dfc

📥 Commits

Reviewing files that changed from the base of the PR and between 64592b9 and bb8ff10.

📒 Files selected for processing (7)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/cli.test.ts
  • packages/cli/src/cli.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts

Comment threadpackages/cli/README.md Outdated
Comment threadpackages/cli/src/persona-install.ts
Comment threadREADME.md Outdated

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/cli/src/persona-install.ts (1)

69-77: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat absolute Windows paths as local sources too.

This still routes valid Windows local paths like C:\packs\foo or \\server\share\foo into the npm-package branch, because the detection only checks string prefixes. That makes local installs fail on Windows unless the path happens to start with . or ~.

Suggested fix
 export function isLocalInstallSource(source: string): boolean {
+ const trimmed = source.trim();
return (
- source.startsWith('.') ||- source.startsWith('/') ||- source.startsWith('~/') ||- source.startsWith('~\\') ||- source === '~'+ trimmed.startsWith('.') ||+ trimmed === '~' ||+ trimmed.startsWith('~/') ||+ trimmed.startsWith('~\\') ||+ isAbsolute(trimmed)
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/persona-install.ts` around lines 69 - 77, The
isLocalInstallSource function incorrectly treats Windows absolute paths as
non-local; update isLocalInstallSource to also return true for Windows
drive-letter absolute paths (e.g., matches /^[A-Za-z]:[\\/]/) and UNC paths
(e.g., starts with '\\\\'), in addition to the existing checks for '.', '/', '~'
and '~/'; modify the function to test these regex/prefix conditions
(drive-letter and UNC) and return true when any of them match.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 331-343: The temp directory is created (tempDir =
mkdtempSync(...)) only inside the npm-resolution branch before the try, but
cleanup lives in the finally after try so exceptions from resolver or
assertDirectory leak the temp dir; move the temp-dir lifecycle so it is created
before entering the try/finally that performs cleanup: when
!isLocalInstallSource(source) call mkdtempSync and set tempDir, then call
resolver (options.resolveNpmPackage ?? defaultResolveNpmPackage) and
assertDirectory inside the try block so the finally can always remove tempDir;
reference tempDir, mkdtempSync,
resolver/resolveNpmPackage/defaultResolveNpmPackage, packageRoot and
assertDirectory to locate the changes.
- Around line 285-289: The spawnSync call that sets packed currently invokes
'npm' with shell: false, which fails on Windows because the npm shim is
'npm.cmd'; change the command argument in that spawnSync (the one assigning to
packed) to choose the correct executable like: const npmExe = process.platform
=== 'win32' ? 'npm.cmd' : 'npm'; then call spawnSync(npmExe, ['pack', spec,
'--pack-destination', packDir, '--json'], { encoding: 'utf8', shell: false });
alternatively use cross-spawn to abstract this; update the spawnSync invocation
that creates packed (and any nearby npm spawn calls) to use npmExe or
cross-spawn so Windows uses the npm shim.
---
Duplicate comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 69-77: The isLocalInstallSource function incorrectly treats
Windows absolute paths as non-local; update isLocalInstallSource to also return
true for Windows drive-letter absolute paths (e.g., matches /^[A-Za-z]:[\\/]/)
and UNC paths (e.g., starts with '\\\\'), in addition to the existing checks for
'.', '/', '~' and '~/'; modify the function to test these regex/prefix
conditions (drive-letter and UNC) and return true when any of them match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 54b1d3e1-7ca4-4955-8cb6-f64929447549

📥 Commits

Reviewing files that changed from the base of the PR and between bb8ff10 and 7223a8d.

📒 Files selected for processing (2)
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/cli/src/persona-install.test.ts

Comment threadpackages/cli/src/persona-install.ts
Comment threadpackages/cli/src/persona-install.ts
Comment threadpackages/cli/src/persona-install.ts
@willwashburn

Copy link
Copy Markdown
MemberAuthor

Review follow-up pushed in 8acf7e4.

Fixed still-valid items:

  • README install/tree fences now use text where requested.
  • Windows local-source detection now covers drive-letter paths and UNC paths; ~\\... was already fixed in 7223a8d.
  • npm pack now invokes npm.cmd on Windows when shell: false is used.
  • npm install temp dirs are cleaned if resolver/package-root validation throws.
  • local persona overlays now reject intent when extends is also present instead of silently ignoring it.

Skipped: CodeRabbit docstring coverage warning is advisory and not enforced by repo checks; adding broad docstrings would be unrelated to the review findings.

Validated with corepack pnpm --filter @agentworkforce/cli test and npm run check.

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 292-296: The npm pack invocation in persona-install.ts (the
spawnSync call that sets packed) is vulnerable because git+ specs can run
lifecycle scripts; add the '--ignore-scripts' flag to the array of arguments
passed to npmExecutable() in that spawnSync call so npm pack is executed with
scripts disabled, and keep the change scoped to the same spawnSync invocation
that produces the packed variable (consider documenting the remaining caveat
about old npm versions and .npmrc re-enabling scripts elsewhere if desired).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 506abb40-d5b6-4406-b83b-9fadd5dcce94

📥 Commits

Reviewing files that changed from the base of the PR and between 7223a8d and 8acf7e4.

📒 Files selected for processing (6)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/local-personas.test.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli/README.md
  • packages/cli/src/persona-install.test.ts

Comment threadpackages/cli/src/persona-install.ts

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 69-79: The isLocalInstallSource function currently misses Windows
root-relative paths like "\personas"; update isLocalInstallSource to treat any
single leading backslash as a local path by adding a check for
trimmed.startsWith('\\') (or a regex that matches a single leading backslash) so
those inputs are routed to resolveLocalSource as local installs (you can replace
the existing '\\\\' check with a broader '\\' check to still cover UNC paths).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f491083a-dcdf-411e-be18-f58cb49f0ca1

📥 Commits

Reviewing files that changed from the base of the PR and between 8acf7e4 and eae2954.

📒 Files selected for processing (1)
  • packages/cli/src/persona-install.ts

Comment on lines +69 to +79
export function isLocalInstallSource(source: string): boolean {
const trimmed = source.trim();
return (
trimmed.startsWith('.') ||
trimmed.startsWith('/') ||
trimmed.startsWith('~/') ||
trimmed.startsWith('~\\') ||
trimmed === '~' ||
/^[A-Za-z]:[\\/]/.test(trimmed) ||
trimmed.startsWith('\\\\')
);

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Node.js path handling, is a Windows path like "\personas" considered absolute (e.g., path.win32.isAbsolute("\\personas"))?

💡 Result:

Yes, in Node.js, a Windows path like "\personas" (which is the string "\personas" due to JavaScript escaping) is considered absolute by path.win32.isAbsolute. This returns true because it starts with a path separator (), making it a drive-relative path from the root of the current drive, consistent with Windows PathIsRelative API behavior [1][2][3]. Official Node.js documentation shows path.win32.isAbsolute('\foo') effectively returns true, as single-backslash paths are treated as absolute (equivalent to forward slash '/' examples which also return true) [4][5][6][3]. Note that "\personas" (four characters, two backslashes) would be an incomplete UNC path but still absolute per the logic checking initial separators [4]. This behavior has remained unchanged through Node.js v26 [5].

Citations:


🏁 Script executed:

cat -n packages/cli/src/persona-install.ts | head -150

Repository: AgentWorkforce/workforce

Length of output: 5311


🏁 Script executed:

rg -n "isLocalInstallSource" packages/cli/src/persona-install.ts

Repository: AgentWorkforce/workforce

Length of output: 179


🏁 Script executed:

cat -n packages/cli/src/persona-install.ts | sed -n '330,380p'

Repository: AgentWorkforce/workforce

Length of output: 2105


Handle Windows root-relative paths (\foo) as local install sources.

isLocalInstallSource misses valid Windows root-relative absolute paths. On Windows, \personas should resolve as a local path via resolveLocalSource, but this check returns false and routes it to npm package resolution instead, causing an error.

Suggested fix
 export function isLocalInstallSource(source: string): boolean {
const trimmed = source.trim();
return (
trimmed.startsWith('.') ||
trimmed.startsWith('/') ||
+ trimmed.startsWith('\\') ||
trimmed.startsWith('~/') ||
trimmed.startsWith('~\\') ||
trimmed === '~' ||
/^[A-Za-z]:[\\/]/.test(trimmed) ||
trimmed.startsWith('\\\\')
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/persona-install.ts` around lines 69 - 79, The
isLocalInstallSource function currently misses Windows root-relative paths like
"\personas"; update isLocalInstallSource to treat any single leading backslash
as a local path by adding a check for trimmed.startsWith('\\') (or a regex that
matches a single leading backslash) so those inputs are routed to
resolveLocalSource as local installs (you can replace the existing '\\\\' check
with a broader '\\' check to still cover UNC paths).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Design and implement shadcn-style persona installs

1 participant

@willwashburn
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(cli): add persona pack install command - #40

Merged
willwashburn merged 4 commits into
mainfrom
feat/installable-persona-sources
May 7, 2026
Merged

feat(cli): add persona pack install command#40
willwashburn merged 4 commits into
mainfrom
feat/installable-persona-sources

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Closes#36

Summary

  • add agentworkforce install for npm and local persona packs
  • copy persona JSON into .agentworkforce/workforce/personas/ with subset selection, flattened filenames, conflict reporting, and --overwrite
  • allow standalone local persona specs so installed pack personas load through the cwd cascade
  • document package format, sources relationship, and persona pack authoring flow

Tests

  • npm run check

@coderabbitai

coderabbitaiBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Implements a shadcn-style agentworkforce install flow: CLI subcommand to copy persona JSONs from npm packages or local paths into /.agentworkforce/workforce/personas/, with npm pack extraction, persona-id filtering, filename flattening, conflict detection, optional overwrite, temp cleanup, tests, and docs. Adds standalone local persona support via an optional intent field.

Changes

Persona Install + Local Persona Standalone

Layer / File(s)Summary
Local persona override
packages/cli/src/local-personas.ts
Adds optional intent?: PersonaIntent, rejects intent combined with .extends, validates standalone tiers and runtime/harnessSettings, and constructs a full PersonaSpec for standalone overrides.
Install Implementation
packages/cli/src/persona-install.ts
Implements installPersonas() with local vs npm detection, npm pack + tar extraction, package.json.agentworkforce.personas (or personas/) resolution, recursive discovery of *.json, persona id validation, duplicate-id and flattened-filename collision checks, requested-id filtering (fail-fast), copy into .agentworkforce/workforce/personas/, conflict recording, and temp-dir cleanup. Exposes expandHomePath, npmExecutable, projectPersonaInstallDir, and isLocalInstallSource.
CLI Usage, Types & Wiring
README.md, packages/cli/README.md, packages/cli/src/cli.ts
Adds `agentworkforce install [flags] <pkg
Documentation
README.md, packages/cli/README.md
Adds full "Install persona packs" section describing package layout (agentworkforce.personas), flattening, conflict/overwrite rules, persona subset selection with --persona, npm vs local resolution, and an authoring/publishing example. Updates local persona docs for standalone requirements.
Tests
packages/cli/src/persona-install.test.ts, packages/cli/src/cli.test.ts, packages/cli/src/local-personas.test.ts
Adds tests for parseInstallArgs; comprehensive installer tests for npm/local detection, expandHomePath and npmExecutable, metadata fallback, discovery, persona-id filtering and fail-fast behavior, filename flattening, conflict recording, overwrite behavior, collisions across packages, temp cleanup, and standalone override rejection when extends and intent combined.

Sequence Diagram

sequenceDiagram
actor User
participant CLI as "agentworkforce CLI"
participant Parser as "parseInstallArgs"
participant Handler as "runPersonaInstall"
participant Installer as "installPersonas"
participant NPM as "npm (external)"
participant FS as "File System"
participant Loader as "loadLocalPersonas"
User->>CLI: agentworkforce install <pkg|path> --persona id --overwrite
CLI->>Parser: parseInstallArgs(args)
Parser-->>CLI: PersonaInstallArgs{source, personaIds, overwrite}
CLI->>Handler: runPersonaInstall(rest)
Handler->>Installer: installPersonas({source, personaIds, overwrite, cwd})
alt source is npm package
Installer->>NPM: npm pack <pkg> --json (temp dir)
NPM-->>Installer: <pkg>.tgz
Installer->>FS: Extract tarball -> /tmp/extracted/package/
else source is local path
Installer->>FS: Resolve/expand local path -> ./local-personas/
end
Installer->>FS: Read package.json (agentworkforce.personas?) FS-->>Installer: personaDir path
Installer->>FS: Scan personaDir for *.json
FS-->>Installer: List of persona files
Installer->>Installer: Parse each JSON, validate id, dedupe/filter by personaIds
Installer->>FS: Copy selected files to .agentworkforce/workforce/personas/ (flattened)
FS-->>Installer: Result {installed, conflicts}
Installer-->>Handler: PersonaInstallResult
Handler->>FS: Write stdout (installed summary)
Handler->>FS: Write stderr (conflicts)
Handler->>Loader: loadLocalPersonas(cwd)
Loader-->>Handler: Installed personas discoverable
Handler-->>User: Exit status (0 if no conflicts, non-zero if conflicts)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

  • #36 Design and implement shadcn-style persona installs — Implements the full install command, npm pack flow, persona filtering, flattening, conflict rules, and docs described in the issue.
  • Split first-party personas into installable persona packs #38 — The changes implement the persona-pack install flow and behavior sought by this issue.

Possibly related PRs

Poem

🐰
I hopped to fetch persona packs with care,
Unpacked tarballs and flattened paths to spare,
Copied JSON friends into your repo lair,
Conflicts flagged — overwrite if you dare,
Now personas hop, ready to chat and share.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe PR title clearly and concisely identifies the main change: adding a persona pack install command to the CLI.
Description check✅ PassedThe PR description is directly related to the changeset, summarizing the install command implementation, copy behavior, standalone personas, documentation, and tests.
Linked Issues check✅ PassedAll core requirements from issue #36 are met: install command for npm/local sources, subset selection via --persona flags, flattened filenames, conflict reporting with --overwrite, standalone personas, and comprehensive documentation and tests.
Out of Scope Changes check✅ PassedAll code changes directly support the install command feature and documented objectives, with no unrelated modifications detected.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/installable-persona-sources

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:bb8ff10f01

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +69 to +75
export function isLocalInstallSource(source: string): boolean {
return (
source.startsWith('.') ||
source.startsWith('/') ||
source.startsWith('~/') ||
source === '~'
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize Windows absolute paths as local sources

isLocalInstallSource only treats POSIX-style prefixes (., /, ~/, ~) as local paths, so valid Windows local paths like C:\\packs\\personas or ~\\personas are misclassified as npm specs. In that case installPersonas takes the npm branch and calls npm pack on a filesystem path, causing agentworkforce install <absolute-path> to fail on Windows even though absolute local paths are documented as supported.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Addressed in 8acf7e4: isLocalInstallSource() now treats Windows drive-letter paths (C:\\...) and UNC paths (\\\\server\\...) as local sources. The ~\\... home-path case was already fixed in 7223a8d and remains covered by tests.

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/local-personas.ts (1)

282-315: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject intent on overlays instead of silently ignoring it.

If a file sets both extends and intent, the standalone path never runs and the merged spec keeps base.intent, so the authored intent is dropped without any warning. Failing fast here would make that misconfiguration obvious.

Suggested fix
 if (raw.extends !== undefined && (typeof raw.extends !== 'string' || !raw.extends.trim())) {
throw new Error(`${context}.extends must be a non-empty string if provided`);
}
+ if (raw.intent !== undefined && raw.extends !== undefined) {+ throw new Error(+ `${context}.intent cannot be combined with .extends; omit extends for standalone personas`+ );+ }
if (raw.intent !== undefined && !PERSONA_INTENTS.includes(raw.intent as PersonaIntent)) {
throw new Error(`${context}.intent must be one of: ${PERSONA_INTENTS.join(', ')}`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/local-personas.ts` around lines 282 - 315, The validator
currently allows an overlay to specify raw.intent which then gets ignored when
extends is present; add a failing check that throws when both raw.extends and
raw.intent are provided (e.g., before the existing intent validation block),
such as: if (raw.extends !== undefined && raw.intent !== undefined) throw new
Error(`${context}.intent cannot be set on overlays that extend another
persona`); update the validation near the raw.intent checks (referencing
raw.intent, raw.extends, and PERSONA_INTENTS) so overlays must not declare
intent and the error surfaces immediately.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/README.md`:
- Around line 76-78: Update the two fenced code blocks in the README that show
the install usage and the file mapping examples so they use the "text" fence
language instead of unlabeled fences; specifically replace the opening backticks
for the block containing "agentworkforce install <pkg|path> [--persona <id> ...]
[--overwrite]" and the block containing
"package/personas/nested/code-reviewer.json ->
.agentworkforce/workforce/personas/code-reviewer.json" with ```text (leave the
contents unchanged) to satisfy markdownlint.
In `@packages/cli/src/persona-install.ts`:
- Around line 69-86: The local install detection and expansion don't handle
Windows-style home paths (e.g. "~\\personas"); update isLocalInstallSource to
also treat strings starting with "~\\" as local, and update expandHomePath to
handle inputs starting with "~\\" by joining homedir() with input.slice(2) (same
as the "~/” case), keeping the existing "~" and "/"-prefixed logic; ensure
resolveLocalSource continues to call expandHomePath and use
isAbsolute/resolvePath as before.
In `@README.md`:
- Around line 140-146: Update the fenced code block that shows the directory
tree starting with "@acme/personas/" to include a language tag by changing the
opening backticks to "```text" so the block becomes a labeled plaintext block;
locate the block containing the lines "@acme/personas/", "├── package.json", and
"└── personas/" and add the "text" language specifier to the opening fence.
---
Outside diff comments:
In `@packages/cli/src/local-personas.ts`:
- Around line 282-315: The validator currently allows an overlay to specify
raw.intent which then gets ignored when extends is present; add a failing check
that throws when both raw.extends and raw.intent are provided (e.g., before the
existing intent validation block), such as: if (raw.extends !== undefined &&
raw.intent !== undefined) throw new Error(`${context}.intent cannot be set on
overlays that extend another persona`); update the validation near the
raw.intent checks (referencing raw.intent, raw.extends, and PERSONA_INTENTS) so
overlays must not declare intent and the error surfaces immediately.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8de86882-c3c3-4769-8770-d90f90443dfc

📥 Commits

Reviewing files that changed from the base of the PR and between 64592b9 and bb8ff10.

📒 Files selected for processing (7)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/cli.test.ts
  • packages/cli/src/cli.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts

Comment threadpackages/cli/README.md Outdated
Comment threadpackages/cli/src/persona-install.ts
Comment threadREADME.md Outdated

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/cli/src/persona-install.ts (1)

69-77: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat absolute Windows paths as local sources too.

This still routes valid Windows local paths like C:\packs\foo or \\server\share\foo into the npm-package branch, because the detection only checks string prefixes. That makes local installs fail on Windows unless the path happens to start with . or ~.

Suggested fix
 export function isLocalInstallSource(source: string): boolean {
+ const trimmed = source.trim();
return (
- source.startsWith('.') ||- source.startsWith('/') ||- source.startsWith('~/') ||- source.startsWith('~\\') ||- source === '~'+ trimmed.startsWith('.') ||+ trimmed === '~' ||+ trimmed.startsWith('~/') ||+ trimmed.startsWith('~\\') ||+ isAbsolute(trimmed)
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/persona-install.ts` around lines 69 - 77, The
isLocalInstallSource function incorrectly treats Windows absolute paths as
non-local; update isLocalInstallSource to also return true for Windows
drive-letter absolute paths (e.g., matches /^[A-Za-z]:[\\/]/) and UNC paths
(e.g., starts with '\\\\'), in addition to the existing checks for '.', '/', '~'
and '~/'; modify the function to test these regex/prefix conditions
(drive-letter and UNC) and return true when any of them match.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 331-343: The temp directory is created (tempDir =
mkdtempSync(...)) only inside the npm-resolution branch before the try, but
cleanup lives in the finally after try so exceptions from resolver or
assertDirectory leak the temp dir; move the temp-dir lifecycle so it is created
before entering the try/finally that performs cleanup: when
!isLocalInstallSource(source) call mkdtempSync and set tempDir, then call
resolver (options.resolveNpmPackage ?? defaultResolveNpmPackage) and
assertDirectory inside the try block so the finally can always remove tempDir;
reference tempDir, mkdtempSync,
resolver/resolveNpmPackage/defaultResolveNpmPackage, packageRoot and
assertDirectory to locate the changes.
- Around line 285-289: The spawnSync call that sets packed currently invokes
'npm' with shell: false, which fails on Windows because the npm shim is
'npm.cmd'; change the command argument in that spawnSync (the one assigning to
packed) to choose the correct executable like: const npmExe = process.platform
=== 'win32' ? 'npm.cmd' : 'npm'; then call spawnSync(npmExe, ['pack', spec,
'--pack-destination', packDir, '--json'], { encoding: 'utf8', shell: false });
alternatively use cross-spawn to abstract this; update the spawnSync invocation
that creates packed (and any nearby npm spawn calls) to use npmExe or
cross-spawn so Windows uses the npm shim.
---
Duplicate comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 69-77: The isLocalInstallSource function incorrectly treats
Windows absolute paths as non-local; update isLocalInstallSource to also return
true for Windows drive-letter absolute paths (e.g., matches /^[A-Za-z]:[\\/]/)
and UNC paths (e.g., starts with '\\\\'), in addition to the existing checks for
'.', '/', '~' and '~/'; modify the function to test these regex/prefix
conditions (drive-letter and UNC) and return true when any of them match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 54b1d3e1-7ca4-4955-8cb6-f64929447549

📥 Commits

Reviewing files that changed from the base of the PR and between bb8ff10 and 7223a8d.

📒 Files selected for processing (2)
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/cli/src/persona-install.test.ts

Comment threadpackages/cli/src/persona-install.ts
Comment threadpackages/cli/src/persona-install.ts
Comment threadpackages/cli/src/persona-install.ts
@willwashburn

Copy link
Copy Markdown
MemberAuthor

Review follow-up pushed in 8acf7e4.

Fixed still-valid items:

  • README install/tree fences now use text where requested.
  • Windows local-source detection now covers drive-letter paths and UNC paths; ~\\... was already fixed in 7223a8d.
  • npm pack now invokes npm.cmd on Windows when shell: false is used.
  • npm install temp dirs are cleaned if resolver/package-root validation throws.
  • local persona overlays now reject intent when extends is also present instead of silently ignoring it.

Skipped: CodeRabbit docstring coverage warning is advisory and not enforced by repo checks; adding broad docstrings would be unrelated to the review findings.

Validated with corepack pnpm --filter @agentworkforce/cli test and npm run check.

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 292-296: The npm pack invocation in persona-install.ts (the
spawnSync call that sets packed) is vulnerable because git+ specs can run
lifecycle scripts; add the '--ignore-scripts' flag to the array of arguments
passed to npmExecutable() in that spawnSync call so npm pack is executed with
scripts disabled, and keep the change scoped to the same spawnSync invocation
that produces the packed variable (consider documenting the remaining caveat
about old npm versions and .npmrc re-enabling scripts elsewhere if desired).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 506abb40-d5b6-4406-b83b-9fadd5dcce94

📥 Commits

Reviewing files that changed from the base of the PR and between 7223a8d and 8acf7e4.

📒 Files selected for processing (6)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/local-personas.test.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli/README.md
  • packages/cli/src/persona-install.test.ts

Comment threadpackages/cli/src/persona-install.ts

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 69-79: The isLocalInstallSource function currently misses Windows
root-relative paths like "\personas"; update isLocalInstallSource to treat any
single leading backslash as a local path by adding a check for
trimmed.startsWith('\\') (or a regex that matches a single leading backslash) so
those inputs are routed to resolveLocalSource as local installs (you can replace
the existing '\\\\' check with a broader '\\' check to still cover UNC paths).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f491083a-dcdf-411e-be18-f58cb49f0ca1

📥 Commits

Reviewing files that changed from the base of the PR and between 8acf7e4 and eae2954.

📒 Files selected for processing (1)
  • packages/cli/src/persona-install.ts

Comment on lines +69 to +79
export function isLocalInstallSource(source: string): boolean {
const trimmed = source.trim();
return (
trimmed.startsWith('.') ||
trimmed.startsWith('/') ||
trimmed.startsWith('~/') ||
trimmed.startsWith('~\\') ||
trimmed === '~' ||
/^[A-Za-z]:[\\/]/.test(trimmed) ||
trimmed.startsWith('\\\\')
);

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Node.js path handling, is a Windows path like "\personas" considered absolute (e.g., path.win32.isAbsolute("\\personas"))?

💡 Result:

Yes, in Node.js, a Windows path like "\personas" (which is the string "\personas" due to JavaScript escaping) is considered absolute by path.win32.isAbsolute. This returns true because it starts with a path separator (), making it a drive-relative path from the root of the current drive, consistent with Windows PathIsRelative API behavior [1][2][3]. Official Node.js documentation shows path.win32.isAbsolute('\foo') effectively returns true, as single-backslash paths are treated as absolute (equivalent to forward slash '/' examples which also return true) [4][5][6][3]. Note that "\personas" (four characters, two backslashes) would be an incomplete UNC path but still absolute per the logic checking initial separators [4]. This behavior has remained unchanged through Node.js v26 [5].

Citations:


🏁 Script executed:

cat -n packages/cli/src/persona-install.ts | head -150

Repository: AgentWorkforce/workforce

Length of output: 5311


🏁 Script executed:

rg -n "isLocalInstallSource" packages/cli/src/persona-install.ts

Repository: AgentWorkforce/workforce

Length of output: 179


🏁 Script executed:

cat -n packages/cli/src/persona-install.ts | sed -n '330,380p'

Repository: AgentWorkforce/workforce

Length of output: 2105


Handle Windows root-relative paths (\foo) as local install sources.

isLocalInstallSource misses valid Windows root-relative absolute paths. On Windows, \personas should resolve as a local path via resolveLocalSource, but this check returns false and routes it to npm package resolution instead, causing an error.

Suggested fix
 export function isLocalInstallSource(source: string): boolean {
const trimmed = source.trim();
return (
trimmed.startsWith('.') ||
trimmed.startsWith('/') ||
+ trimmed.startsWith('\\') ||
trimmed.startsWith('~/') ||
trimmed.startsWith('~\\') ||
trimmed === '~' ||
/^[A-Za-z]:[\\/]/.test(trimmed) ||
trimmed.startsWith('\\\\')
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/persona-install.ts` around lines 69 - 79, The
isLocalInstallSource function currently misses Windows root-relative paths like
"\personas"; update isLocalInstallSource to treat any single leading backslash
as a local path by adding a check for trimmed.startsWith('\\') (or a regex that
matches a single leading backslash) so those inputs are routed to
resolveLocalSource as local installs (you can replace the existing '\\\\' check
with a broader '\\' check to still cover UNC paths).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Design and implement shadcn-style persona installs

1 participant

@willwashburn
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(cli): add persona pack install command - #40

Merged
willwashburn merged 4 commits into
mainfrom
feat/installable-persona-sources
May 7, 2026
Merged

feat(cli): add persona pack install command#40
willwashburn merged 4 commits into
mainfrom
feat/installable-persona-sources

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Closes#36

Summary

  • add agentworkforce install for npm and local persona packs
  • copy persona JSON into .agentworkforce/workforce/personas/ with subset selection, flattened filenames, conflict reporting, and --overwrite
  • allow standalone local persona specs so installed pack personas load through the cwd cascade
  • document package format, sources relationship, and persona pack authoring flow

Tests

  • npm run check

@coderabbitai

coderabbitaiBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Implements a shadcn-style agentworkforce install flow: CLI subcommand to copy persona JSONs from npm packages or local paths into /.agentworkforce/workforce/personas/, with npm pack extraction, persona-id filtering, filename flattening, conflict detection, optional overwrite, temp cleanup, tests, and docs. Adds standalone local persona support via an optional intent field.

Changes

Persona Install + Local Persona Standalone

Layer / File(s)Summary
Local persona override
packages/cli/src/local-personas.ts
Adds optional intent?: PersonaIntent, rejects intent combined with .extends, validates standalone tiers and runtime/harnessSettings, and constructs a full PersonaSpec for standalone overrides.
Install Implementation
packages/cli/src/persona-install.ts
Implements installPersonas() with local vs npm detection, npm pack + tar extraction, package.json.agentworkforce.personas (or personas/) resolution, recursive discovery of *.json, persona id validation, duplicate-id and flattened-filename collision checks, requested-id filtering (fail-fast), copy into .agentworkforce/workforce/personas/, conflict recording, and temp-dir cleanup. Exposes expandHomePath, npmExecutable, projectPersonaInstallDir, and isLocalInstallSource.
CLI Usage, Types & Wiring
README.md, packages/cli/README.md, packages/cli/src/cli.ts
Adds `agentworkforce install [flags] <pkg
Documentation
README.md, packages/cli/README.md
Adds full "Install persona packs" section describing package layout (agentworkforce.personas), flattening, conflict/overwrite rules, persona subset selection with --persona, npm vs local resolution, and an authoring/publishing example. Updates local persona docs for standalone requirements.
Tests
packages/cli/src/persona-install.test.ts, packages/cli/src/cli.test.ts, packages/cli/src/local-personas.test.ts
Adds tests for parseInstallArgs; comprehensive installer tests for npm/local detection, expandHomePath and npmExecutable, metadata fallback, discovery, persona-id filtering and fail-fast behavior, filename flattening, conflict recording, overwrite behavior, collisions across packages, temp cleanup, and standalone override rejection when extends and intent combined.

Sequence Diagram

sequenceDiagram
actor User
participant CLI as "agentworkforce CLI"
participant Parser as "parseInstallArgs"
participant Handler as "runPersonaInstall"
participant Installer as "installPersonas"
participant NPM as "npm (external)"
participant FS as "File System"
participant Loader as "loadLocalPersonas"
User->>CLI: agentworkforce install <pkg|path> --persona id --overwrite
CLI->>Parser: parseInstallArgs(args)
Parser-->>CLI: PersonaInstallArgs{source, personaIds, overwrite}
CLI->>Handler: runPersonaInstall(rest)
Handler->>Installer: installPersonas({source, personaIds, overwrite, cwd})
alt source is npm package
Installer->>NPM: npm pack <pkg> --json (temp dir)
NPM-->>Installer: <pkg>.tgz
Installer->>FS: Extract tarball -> /tmp/extracted/package/
else source is local path
Installer->>FS: Resolve/expand local path -> ./local-personas/
end
Installer->>FS: Read package.json (agentworkforce.personas?) FS-->>Installer: personaDir path
Installer->>FS: Scan personaDir for *.json
FS-->>Installer: List of persona files
Installer->>Installer: Parse each JSON, validate id, dedupe/filter by personaIds
Installer->>FS: Copy selected files to .agentworkforce/workforce/personas/ (flattened)
FS-->>Installer: Result {installed, conflicts}
Installer-->>Handler: PersonaInstallResult
Handler->>FS: Write stdout (installed summary)
Handler->>FS: Write stderr (conflicts)
Handler->>Loader: loadLocalPersonas(cwd)
Loader-->>Handler: Installed personas discoverable
Handler-->>User: Exit status (0 if no conflicts, non-zero if conflicts)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

  • #36 Design and implement shadcn-style persona installs — Implements the full install command, npm pack flow, persona filtering, flattening, conflict rules, and docs described in the issue.
  • Split first-party personas into installable persona packs #38 — The changes implement the persona-pack install flow and behavior sought by this issue.

Possibly related PRs

Poem

🐰
I hopped to fetch persona packs with care,
Unpacked tarballs and flattened paths to spare,
Copied JSON friends into your repo lair,
Conflicts flagged — overwrite if you dare,
Now personas hop, ready to chat and share.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe PR title clearly and concisely identifies the main change: adding a persona pack install command to the CLI.
Description check✅ PassedThe PR description is directly related to the changeset, summarizing the install command implementation, copy behavior, standalone personas, documentation, and tests.
Linked Issues check✅ PassedAll core requirements from issue #36 are met: install command for npm/local sources, subset selection via --persona flags, flattened filenames, conflict reporting with --overwrite, standalone personas, and comprehensive documentation and tests.
Out of Scope Changes check✅ PassedAll code changes directly support the install command feature and documented objectives, with no unrelated modifications detected.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/installable-persona-sources

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:bb8ff10f01

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +69 to +75
export function isLocalInstallSource(source: string): boolean {
return (
source.startsWith('.') ||
source.startsWith('/') ||
source.startsWith('~/') ||
source === '~'
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize Windows absolute paths as local sources

isLocalInstallSource only treats POSIX-style prefixes (., /, ~/, ~) as local paths, so valid Windows local paths like C:\\packs\\personas or ~\\personas are misclassified as npm specs. In that case installPersonas takes the npm branch and calls npm pack on a filesystem path, causing agentworkforce install <absolute-path> to fail on Windows even though absolute local paths are documented as supported.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Addressed in 8acf7e4: isLocalInstallSource() now treats Windows drive-letter paths (C:\\...) and UNC paths (\\\\server\\...) as local sources. The ~\\... home-path case was already fixed in 7223a8d and remains covered by tests.

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/local-personas.ts (1)

282-315: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject intent on overlays instead of silently ignoring it.

If a file sets both extends and intent, the standalone path never runs and the merged spec keeps base.intent, so the authored intent is dropped without any warning. Failing fast here would make that misconfiguration obvious.

Suggested fix
 if (raw.extends !== undefined && (typeof raw.extends !== 'string' || !raw.extends.trim())) {
throw new Error(`${context}.extends must be a non-empty string if provided`);
}
+ if (raw.intent !== undefined && raw.extends !== undefined) {+ throw new Error(+ `${context}.intent cannot be combined with .extends; omit extends for standalone personas`+ );+ }
if (raw.intent !== undefined && !PERSONA_INTENTS.includes(raw.intent as PersonaIntent)) {
throw new Error(`${context}.intent must be one of: ${PERSONA_INTENTS.join(', ')}`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/local-personas.ts` around lines 282 - 315, The validator
currently allows an overlay to specify raw.intent which then gets ignored when
extends is present; add a failing check that throws when both raw.extends and
raw.intent are provided (e.g., before the existing intent validation block),
such as: if (raw.extends !== undefined && raw.intent !== undefined) throw new
Error(`${context}.intent cannot be set on overlays that extend another
persona`); update the validation near the raw.intent checks (referencing
raw.intent, raw.extends, and PERSONA_INTENTS) so overlays must not declare
intent and the error surfaces immediately.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/README.md`:
- Around line 76-78: Update the two fenced code blocks in the README that show
the install usage and the file mapping examples so they use the "text" fence
language instead of unlabeled fences; specifically replace the opening backticks
for the block containing "agentworkforce install <pkg|path> [--persona <id> ...]
[--overwrite]" and the block containing
"package/personas/nested/code-reviewer.json ->
.agentworkforce/workforce/personas/code-reviewer.json" with ```text (leave the
contents unchanged) to satisfy markdownlint.
In `@packages/cli/src/persona-install.ts`:
- Around line 69-86: The local install detection and expansion don't handle
Windows-style home paths (e.g. "~\\personas"); update isLocalInstallSource to
also treat strings starting with "~\\" as local, and update expandHomePath to
handle inputs starting with "~\\" by joining homedir() with input.slice(2) (same
as the "~/” case), keeping the existing "~" and "/"-prefixed logic; ensure
resolveLocalSource continues to call expandHomePath and use
isAbsolute/resolvePath as before.
In `@README.md`:
- Around line 140-146: Update the fenced code block that shows the directory
tree starting with "@acme/personas/" to include a language tag by changing the
opening backticks to "```text" so the block becomes a labeled plaintext block;
locate the block containing the lines "@acme/personas/", "├── package.json", and
"└── personas/" and add the "text" language specifier to the opening fence.
---
Outside diff comments:
In `@packages/cli/src/local-personas.ts`:
- Around line 282-315: The validator currently allows an overlay to specify
raw.intent which then gets ignored when extends is present; add a failing check
that throws when both raw.extends and raw.intent are provided (e.g., before the
existing intent validation block), such as: if (raw.extends !== undefined &&
raw.intent !== undefined) throw new Error(`${context}.intent cannot be set on
overlays that extend another persona`); update the validation near the
raw.intent checks (referencing raw.intent, raw.extends, and PERSONA_INTENTS) so
overlays must not declare intent and the error surfaces immediately.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8de86882-c3c3-4769-8770-d90f90443dfc

📥 Commits

Reviewing files that changed from the base of the PR and between 64592b9 and bb8ff10.

📒 Files selected for processing (7)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/cli.test.ts
  • packages/cli/src/cli.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts

Comment threadpackages/cli/README.md Outdated
Comment threadpackages/cli/src/persona-install.ts
Comment threadREADME.md Outdated

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/cli/src/persona-install.ts (1)

69-77: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat absolute Windows paths as local sources too.

This still routes valid Windows local paths like C:\packs\foo or \\server\share\foo into the npm-package branch, because the detection only checks string prefixes. That makes local installs fail on Windows unless the path happens to start with . or ~.

Suggested fix
 export function isLocalInstallSource(source: string): boolean {
+ const trimmed = source.trim();
return (
- source.startsWith('.') ||- source.startsWith('/') ||- source.startsWith('~/') ||- source.startsWith('~\\') ||- source === '~'+ trimmed.startsWith('.') ||+ trimmed === '~' ||+ trimmed.startsWith('~/') ||+ trimmed.startsWith('~\\') ||+ isAbsolute(trimmed)
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/persona-install.ts` around lines 69 - 77, The
isLocalInstallSource function incorrectly treats Windows absolute paths as
non-local; update isLocalInstallSource to also return true for Windows
drive-letter absolute paths (e.g., matches /^[A-Za-z]:[\\/]/) and UNC paths
(e.g., starts with '\\\\'), in addition to the existing checks for '.', '/', '~'
and '~/'; modify the function to test these regex/prefix conditions
(drive-letter and UNC) and return true when any of them match.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 331-343: The temp directory is created (tempDir =
mkdtempSync(...)) only inside the npm-resolution branch before the try, but
cleanup lives in the finally after try so exceptions from resolver or
assertDirectory leak the temp dir; move the temp-dir lifecycle so it is created
before entering the try/finally that performs cleanup: when
!isLocalInstallSource(source) call mkdtempSync and set tempDir, then call
resolver (options.resolveNpmPackage ?? defaultResolveNpmPackage) and
assertDirectory inside the try block so the finally can always remove tempDir;
reference tempDir, mkdtempSync,
resolver/resolveNpmPackage/defaultResolveNpmPackage, packageRoot and
assertDirectory to locate the changes.
- Around line 285-289: The spawnSync call that sets packed currently invokes
'npm' with shell: false, which fails on Windows because the npm shim is
'npm.cmd'; change the command argument in that spawnSync (the one assigning to
packed) to choose the correct executable like: const npmExe = process.platform
=== 'win32' ? 'npm.cmd' : 'npm'; then call spawnSync(npmExe, ['pack', spec,
'--pack-destination', packDir, '--json'], { encoding: 'utf8', shell: false });
alternatively use cross-spawn to abstract this; update the spawnSync invocation
that creates packed (and any nearby npm spawn calls) to use npmExe or
cross-spawn so Windows uses the npm shim.
---
Duplicate comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 69-77: The isLocalInstallSource function incorrectly treats
Windows absolute paths as non-local; update isLocalInstallSource to also return
true for Windows drive-letter absolute paths (e.g., matches /^[A-Za-z]:[\\/]/)
and UNC paths (e.g., starts with '\\\\'), in addition to the existing checks for
'.', '/', '~' and '~/'; modify the function to test these regex/prefix
conditions (drive-letter and UNC) and return true when any of them match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 54b1d3e1-7ca4-4955-8cb6-f64929447549

📥 Commits

Reviewing files that changed from the base of the PR and between bb8ff10 and 7223a8d.

📒 Files selected for processing (2)
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/cli/src/persona-install.test.ts

Comment threadpackages/cli/src/persona-install.ts
Comment threadpackages/cli/src/persona-install.ts
Comment threadpackages/cli/src/persona-install.ts
@willwashburn

Copy link
Copy Markdown
MemberAuthor

Review follow-up pushed in 8acf7e4.

Fixed still-valid items:

  • README install/tree fences now use text where requested.
  • Windows local-source detection now covers drive-letter paths and UNC paths; ~\\... was already fixed in 7223a8d.
  • npm pack now invokes npm.cmd on Windows when shell: false is used.
  • npm install temp dirs are cleaned if resolver/package-root validation throws.
  • local persona overlays now reject intent when extends is also present instead of silently ignoring it.

Skipped: CodeRabbit docstring coverage warning is advisory and not enforced by repo checks; adding broad docstrings would be unrelated to the review findings.

Validated with corepack pnpm --filter @agentworkforce/cli test and npm run check.

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 292-296: The npm pack invocation in persona-install.ts (the
spawnSync call that sets packed) is vulnerable because git+ specs can run
lifecycle scripts; add the '--ignore-scripts' flag to the array of arguments
passed to npmExecutable() in that spawnSync call so npm pack is executed with
scripts disabled, and keep the change scoped to the same spawnSync invocation
that produces the packed variable (consider documenting the remaining caveat
about old npm versions and .npmrc re-enabling scripts elsewhere if desired).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 506abb40-d5b6-4406-b83b-9fadd5dcce94

📥 Commits

Reviewing files that changed from the base of the PR and between 7223a8d and 8acf7e4.

📒 Files selected for processing (6)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/local-personas.test.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli/README.md
  • packages/cli/src/persona-install.test.ts

Comment threadpackages/cli/src/persona-install.ts

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 69-79: The isLocalInstallSource function currently misses Windows
root-relative paths like "\personas"; update isLocalInstallSource to treat any
single leading backslash as a local path by adding a check for
trimmed.startsWith('\\') (or a regex that matches a single leading backslash) so
those inputs are routed to resolveLocalSource as local installs (you can replace
the existing '\\\\' check with a broader '\\' check to still cover UNC paths).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f491083a-dcdf-411e-be18-f58cb49f0ca1

📥 Commits

Reviewing files that changed from the base of the PR and between 8acf7e4 and eae2954.

📒 Files selected for processing (1)
  • packages/cli/src/persona-install.ts

Comment on lines +69 to +79
export function isLocalInstallSource(source: string): boolean {
const trimmed = source.trim();
return (
trimmed.startsWith('.') ||
trimmed.startsWith('/') ||
trimmed.startsWith('~/') ||
trimmed.startsWith('~\\') ||
trimmed === '~' ||
/^[A-Za-z]:[\\/]/.test(trimmed) ||
trimmed.startsWith('\\\\')
);

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Node.js path handling, is a Windows path like "\personas" considered absolute (e.g., path.win32.isAbsolute("\\personas"))?

💡 Result:

Yes, in Node.js, a Windows path like "\personas" (which is the string "\personas" due to JavaScript escaping) is considered absolute by path.win32.isAbsolute. This returns true because it starts with a path separator (), making it a drive-relative path from the root of the current drive, consistent with Windows PathIsRelative API behavior [1][2][3]. Official Node.js documentation shows path.win32.isAbsolute('\foo') effectively returns true, as single-backslash paths are treated as absolute (equivalent to forward slash '/' examples which also return true) [4][5][6][3]. Note that "\personas" (four characters, two backslashes) would be an incomplete UNC path but still absolute per the logic checking initial separators [4]. This behavior has remained unchanged through Node.js v26 [5].

Citations:


🏁 Script executed:

cat -n packages/cli/src/persona-install.ts | head -150

Repository: AgentWorkforce/workforce

Length of output: 5311


🏁 Script executed:

rg -n "isLocalInstallSource" packages/cli/src/persona-install.ts

Repository: AgentWorkforce/workforce

Length of output: 179


🏁 Script executed:

cat -n packages/cli/src/persona-install.ts | sed -n '330,380p'

Repository: AgentWorkforce/workforce

Length of output: 2105


Handle Windows root-relative paths (\foo) as local install sources.

isLocalInstallSource misses valid Windows root-relative absolute paths. On Windows, \personas should resolve as a local path via resolveLocalSource, but this check returns false and routes it to npm package resolution instead, causing an error.

Suggested fix
 export function isLocalInstallSource(source: string): boolean {
const trimmed = source.trim();
return (
trimmed.startsWith('.') ||
trimmed.startsWith('/') ||
+ trimmed.startsWith('\\') ||
trimmed.startsWith('~/') ||
trimmed.startsWith('~\\') ||
trimmed === '~' ||
/^[A-Za-z]:[\\/]/.test(trimmed) ||
trimmed.startsWith('\\\\')
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/persona-install.ts` around lines 69 - 79, The
isLocalInstallSource function currently misses Windows root-relative paths like
"\personas"; update isLocalInstallSource to treat any single leading backslash
as a local path by adding a check for trimmed.startsWith('\\') (or a regex that
matches a single leading backslash) so those inputs are routed to
resolveLocalSource as local installs (you can replace the existing '\\\\' check
with a broader '\\' check to still cover UNC paths).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Design and implement shadcn-style persona installs

1 participant

@willwashburn
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(cli): add persona pack install command - #40

Merged
willwashburn merged 4 commits into
mainfrom
feat/installable-persona-sources
May 7, 2026
Merged

feat(cli): add persona pack install command#40
willwashburn merged 4 commits into
mainfrom
feat/installable-persona-sources

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Closes#36

Summary

  • add agentworkforce install for npm and local persona packs
  • copy persona JSON into .agentworkforce/workforce/personas/ with subset selection, flattened filenames, conflict reporting, and --overwrite
  • allow standalone local persona specs so installed pack personas load through the cwd cascade
  • document package format, sources relationship, and persona pack authoring flow

Tests

  • npm run check

@coderabbitai

coderabbitaiBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Implements a shadcn-style agentworkforce install flow: CLI subcommand to copy persona JSONs from npm packages or local paths into /.agentworkforce/workforce/personas/, with npm pack extraction, persona-id filtering, filename flattening, conflict detection, optional overwrite, temp cleanup, tests, and docs. Adds standalone local persona support via an optional intent field.

Changes

Persona Install + Local Persona Standalone

Layer / File(s)Summary
Local persona override
packages/cli/src/local-personas.ts
Adds optional intent?: PersonaIntent, rejects intent combined with .extends, validates standalone tiers and runtime/harnessSettings, and constructs a full PersonaSpec for standalone overrides.
Install Implementation
packages/cli/src/persona-install.ts
Implements installPersonas() with local vs npm detection, npm pack + tar extraction, package.json.agentworkforce.personas (or personas/) resolution, recursive discovery of *.json, persona id validation, duplicate-id and flattened-filename collision checks, requested-id filtering (fail-fast), copy into .agentworkforce/workforce/personas/, conflict recording, and temp-dir cleanup. Exposes expandHomePath, npmExecutable, projectPersonaInstallDir, and isLocalInstallSource.
CLI Usage, Types & Wiring
README.md, packages/cli/README.md, packages/cli/src/cli.ts
Adds `agentworkforce install [flags] <pkg
Documentation
README.md, packages/cli/README.md
Adds full "Install persona packs" section describing package layout (agentworkforce.personas), flattening, conflict/overwrite rules, persona subset selection with --persona, npm vs local resolution, and an authoring/publishing example. Updates local persona docs for standalone requirements.
Tests
packages/cli/src/persona-install.test.ts, packages/cli/src/cli.test.ts, packages/cli/src/local-personas.test.ts
Adds tests for parseInstallArgs; comprehensive installer tests for npm/local detection, expandHomePath and npmExecutable, metadata fallback, discovery, persona-id filtering and fail-fast behavior, filename flattening, conflict recording, overwrite behavior, collisions across packages, temp cleanup, and standalone override rejection when extends and intent combined.

Sequence Diagram

sequenceDiagram
actor User
participant CLI as "agentworkforce CLI"
participant Parser as "parseInstallArgs"
participant Handler as "runPersonaInstall"
participant Installer as "installPersonas"
participant NPM as "npm (external)"
participant FS as "File System"
participant Loader as "loadLocalPersonas"
User->>CLI: agentworkforce install <pkg|path> --persona id --overwrite
CLI->>Parser: parseInstallArgs(args)
Parser-->>CLI: PersonaInstallArgs{source, personaIds, overwrite}
CLI->>Handler: runPersonaInstall(rest)
Handler->>Installer: installPersonas({source, personaIds, overwrite, cwd})
alt source is npm package
Installer->>NPM: npm pack <pkg> --json (temp dir)
NPM-->>Installer: <pkg>.tgz
Installer->>FS: Extract tarball -> /tmp/extracted/package/
else source is local path
Installer->>FS: Resolve/expand local path -> ./local-personas/
end
Installer->>FS: Read package.json (agentworkforce.personas?) FS-->>Installer: personaDir path
Installer->>FS: Scan personaDir for *.json
FS-->>Installer: List of persona files
Installer->>Installer: Parse each JSON, validate id, dedupe/filter by personaIds
Installer->>FS: Copy selected files to .agentworkforce/workforce/personas/ (flattened)
FS-->>Installer: Result {installed, conflicts}
Installer-->>Handler: PersonaInstallResult
Handler->>FS: Write stdout (installed summary)
Handler->>FS: Write stderr (conflicts)
Handler->>Loader: loadLocalPersonas(cwd)
Loader-->>Handler: Installed personas discoverable
Handler-->>User: Exit status (0 if no conflicts, non-zero if conflicts)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

  • #36 Design and implement shadcn-style persona installs — Implements the full install command, npm pack flow, persona filtering, flattening, conflict rules, and docs described in the issue.
  • Split first-party personas into installable persona packs #38 — The changes implement the persona-pack install flow and behavior sought by this issue.

Possibly related PRs

Poem

🐰
I hopped to fetch persona packs with care,
Unpacked tarballs and flattened paths to spare,
Copied JSON friends into your repo lair,
Conflicts flagged — overwrite if you dare,
Now personas hop, ready to chat and share.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe PR title clearly and concisely identifies the main change: adding a persona pack install command to the CLI.
Description check✅ PassedThe PR description is directly related to the changeset, summarizing the install command implementation, copy behavior, standalone personas, documentation, and tests.
Linked Issues check✅ PassedAll core requirements from issue #36 are met: install command for npm/local sources, subset selection via --persona flags, flattened filenames, conflict reporting with --overwrite, standalone personas, and comprehensive documentation and tests.
Out of Scope Changes check✅ PassedAll code changes directly support the install command feature and documented objectives, with no unrelated modifications detected.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/installable-persona-sources

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:bb8ff10f01

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +69 to +75
export function isLocalInstallSource(source: string): boolean {
return (
source.startsWith('.') ||
source.startsWith('/') ||
source.startsWith('~/') ||
source === '~'
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize Windows absolute paths as local sources

isLocalInstallSource only treats POSIX-style prefixes (., /, ~/, ~) as local paths, so valid Windows local paths like C:\\packs\\personas or ~\\personas are misclassified as npm specs. In that case installPersonas takes the npm branch and calls npm pack on a filesystem path, causing agentworkforce install <absolute-path> to fail on Windows even though absolute local paths are documented as supported.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Addressed in 8acf7e4: isLocalInstallSource() now treats Windows drive-letter paths (C:\\...) and UNC paths (\\\\server\\...) as local sources. The ~\\... home-path case was already fixed in 7223a8d and remains covered by tests.

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/local-personas.ts (1)

282-315: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject intent on overlays instead of silently ignoring it.

If a file sets both extends and intent, the standalone path never runs and the merged spec keeps base.intent, so the authored intent is dropped without any warning. Failing fast here would make that misconfiguration obvious.

Suggested fix
 if (raw.extends !== undefined && (typeof raw.extends !== 'string' || !raw.extends.trim())) {
throw new Error(`${context}.extends must be a non-empty string if provided`);
}
+ if (raw.intent !== undefined && raw.extends !== undefined) {+ throw new Error(+ `${context}.intent cannot be combined with .extends; omit extends for standalone personas`+ );+ }
if (raw.intent !== undefined && !PERSONA_INTENTS.includes(raw.intent as PersonaIntent)) {
throw new Error(`${context}.intent must be one of: ${PERSONA_INTENTS.join(', ')}`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/local-personas.ts` around lines 282 - 315, The validator
currently allows an overlay to specify raw.intent which then gets ignored when
extends is present; add a failing check that throws when both raw.extends and
raw.intent are provided (e.g., before the existing intent validation block),
such as: if (raw.extends !== undefined && raw.intent !== undefined) throw new
Error(`${context}.intent cannot be set on overlays that extend another
persona`); update the validation near the raw.intent checks (referencing
raw.intent, raw.extends, and PERSONA_INTENTS) so overlays must not declare
intent and the error surfaces immediately.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/README.md`:
- Around line 76-78: Update the two fenced code blocks in the README that show
the install usage and the file mapping examples so they use the "text" fence
language instead of unlabeled fences; specifically replace the opening backticks
for the block containing "agentworkforce install <pkg|path> [--persona <id> ...]
[--overwrite]" and the block containing
"package/personas/nested/code-reviewer.json ->
.agentworkforce/workforce/personas/code-reviewer.json" with ```text (leave the
contents unchanged) to satisfy markdownlint.
In `@packages/cli/src/persona-install.ts`:
- Around line 69-86: The local install detection and expansion don't handle
Windows-style home paths (e.g. "~\\personas"); update isLocalInstallSource to
also treat strings starting with "~\\" as local, and update expandHomePath to
handle inputs starting with "~\\" by joining homedir() with input.slice(2) (same
as the "~/” case), keeping the existing "~" and "/"-prefixed logic; ensure
resolveLocalSource continues to call expandHomePath and use
isAbsolute/resolvePath as before.
In `@README.md`:
- Around line 140-146: Update the fenced code block that shows the directory
tree starting with "@acme/personas/" to include a language tag by changing the
opening backticks to "```text" so the block becomes a labeled plaintext block;
locate the block containing the lines "@acme/personas/", "├── package.json", and
"└── personas/" and add the "text" language specifier to the opening fence.
---
Outside diff comments:
In `@packages/cli/src/local-personas.ts`:
- Around line 282-315: The validator currently allows an overlay to specify
raw.intent which then gets ignored when extends is present; add a failing check
that throws when both raw.extends and raw.intent are provided (e.g., before the
existing intent validation block), such as: if (raw.extends !== undefined &&
raw.intent !== undefined) throw new Error(`${context}.intent cannot be set on
overlays that extend another persona`); update the validation near the
raw.intent checks (referencing raw.intent, raw.extends, and PERSONA_INTENTS) so
overlays must not declare intent and the error surfaces immediately.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8de86882-c3c3-4769-8770-d90f90443dfc

📥 Commits

Reviewing files that changed from the base of the PR and between 64592b9 and bb8ff10.

📒 Files selected for processing (7)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/cli.test.ts
  • packages/cli/src/cli.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts

Comment threadpackages/cli/README.md Outdated
Comment threadpackages/cli/src/persona-install.ts
Comment threadREADME.md Outdated

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/cli/src/persona-install.ts (1)

69-77: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat absolute Windows paths as local sources too.

This still routes valid Windows local paths like C:\packs\foo or \\server\share\foo into the npm-package branch, because the detection only checks string prefixes. That makes local installs fail on Windows unless the path happens to start with . or ~.

Suggested fix
 export function isLocalInstallSource(source: string): boolean {
+ const trimmed = source.trim();
return (
- source.startsWith('.') ||- source.startsWith('/') ||- source.startsWith('~/') ||- source.startsWith('~\\') ||- source === '~'+ trimmed.startsWith('.') ||+ trimmed === '~' ||+ trimmed.startsWith('~/') ||+ trimmed.startsWith('~\\') ||+ isAbsolute(trimmed)
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/persona-install.ts` around lines 69 - 77, The
isLocalInstallSource function incorrectly treats Windows absolute paths as
non-local; update isLocalInstallSource to also return true for Windows
drive-letter absolute paths (e.g., matches /^[A-Za-z]:[\\/]/) and UNC paths
(e.g., starts with '\\\\'), in addition to the existing checks for '.', '/', '~'
and '~/'; modify the function to test these regex/prefix conditions
(drive-letter and UNC) and return true when any of them match.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 331-343: The temp directory is created (tempDir =
mkdtempSync(...)) only inside the npm-resolution branch before the try, but
cleanup lives in the finally after try so exceptions from resolver or
assertDirectory leak the temp dir; move the temp-dir lifecycle so it is created
before entering the try/finally that performs cleanup: when
!isLocalInstallSource(source) call mkdtempSync and set tempDir, then call
resolver (options.resolveNpmPackage ?? defaultResolveNpmPackage) and
assertDirectory inside the try block so the finally can always remove tempDir;
reference tempDir, mkdtempSync,
resolver/resolveNpmPackage/defaultResolveNpmPackage, packageRoot and
assertDirectory to locate the changes.
- Around line 285-289: The spawnSync call that sets packed currently invokes
'npm' with shell: false, which fails on Windows because the npm shim is
'npm.cmd'; change the command argument in that spawnSync (the one assigning to
packed) to choose the correct executable like: const npmExe = process.platform
=== 'win32' ? 'npm.cmd' : 'npm'; then call spawnSync(npmExe, ['pack', spec,
'--pack-destination', packDir, '--json'], { encoding: 'utf8', shell: false });
alternatively use cross-spawn to abstract this; update the spawnSync invocation
that creates packed (and any nearby npm spawn calls) to use npmExe or
cross-spawn so Windows uses the npm shim.
---
Duplicate comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 69-77: The isLocalInstallSource function incorrectly treats
Windows absolute paths as non-local; update isLocalInstallSource to also return
true for Windows drive-letter absolute paths (e.g., matches /^[A-Za-z]:[\\/]/)
and UNC paths (e.g., starts with '\\\\'), in addition to the existing checks for
'.', '/', '~' and '~/'; modify the function to test these regex/prefix
conditions (drive-letter and UNC) and return true when any of them match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 54b1d3e1-7ca4-4955-8cb6-f64929447549

📥 Commits

Reviewing files that changed from the base of the PR and between bb8ff10 and 7223a8d.

📒 Files selected for processing (2)
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/cli/src/persona-install.test.ts

Comment threadpackages/cli/src/persona-install.ts
Comment threadpackages/cli/src/persona-install.ts
Comment threadpackages/cli/src/persona-install.ts
@willwashburn

Copy link
Copy Markdown
MemberAuthor

Review follow-up pushed in 8acf7e4.

Fixed still-valid items:

  • README install/tree fences now use text where requested.
  • Windows local-source detection now covers drive-letter paths and UNC paths; ~\\... was already fixed in 7223a8d.
  • npm pack now invokes npm.cmd on Windows when shell: false is used.
  • npm install temp dirs are cleaned if resolver/package-root validation throws.
  • local persona overlays now reject intent when extends is also present instead of silently ignoring it.

Skipped: CodeRabbit docstring coverage warning is advisory and not enforced by repo checks; adding broad docstrings would be unrelated to the review findings.

Validated with corepack pnpm --filter @agentworkforce/cli test and npm run check.

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 292-296: The npm pack invocation in persona-install.ts (the
spawnSync call that sets packed) is vulnerable because git+ specs can run
lifecycle scripts; add the '--ignore-scripts' flag to the array of arguments
passed to npmExecutable() in that spawnSync call so npm pack is executed with
scripts disabled, and keep the change scoped to the same spawnSync invocation
that produces the packed variable (consider documenting the remaining caveat
about old npm versions and .npmrc re-enabling scripts elsewhere if desired).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 506abb40-d5b6-4406-b83b-9fadd5dcce94

📥 Commits

Reviewing files that changed from the base of the PR and between 7223a8d and 8acf7e4.

📒 Files selected for processing (6)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/local-personas.test.ts
  • packages/cli/src/local-personas.ts
  • packages/cli/src/persona-install.test.ts
  • packages/cli/src/persona-install.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli/README.md
  • packages/cli/src/persona-install.test.ts

Comment threadpackages/cli/src/persona-install.ts

@coderabbitaicoderabbitaiBot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/persona-install.ts`:
- Around line 69-79: The isLocalInstallSource function currently misses Windows
root-relative paths like "\personas"; update isLocalInstallSource to treat any
single leading backslash as a local path by adding a check for
trimmed.startsWith('\\') (or a regex that matches a single leading backslash) so
those inputs are routed to resolveLocalSource as local installs (you can replace
the existing '\\\\' check with a broader '\\' check to still cover UNC paths).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f491083a-dcdf-411e-be18-f58cb49f0ca1

📥 Commits

Reviewing files that changed from the base of the PR and between 8acf7e4 and eae2954.

📒 Files selected for processing (1)
  • packages/cli/src/persona-install.ts

Comment on lines +69 to +79
export function isLocalInstallSource(source: string): boolean {
const trimmed = source.trim();
return (
trimmed.startsWith('.') ||
trimmed.startsWith('/') ||
trimmed.startsWith('~/') ||
trimmed.startsWith('~\\') ||
trimmed === '~' ||
/^[A-Za-z]:[\\/]/.test(trimmed) ||
trimmed.startsWith('\\\\')
);

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Node.js path handling, is a Windows path like "\personas" considered absolute (e.g., path.win32.isAbsolute("\\personas"))?

💡 Result:

Yes, in Node.js, a Windows path like "\personas" (which is the string "\personas" due to JavaScript escaping) is considered absolute by path.win32.isAbsolute. This returns true because it starts with a path separator (), making it a drive-relative path from the root of the current drive, consistent with Windows PathIsRelative API behavior [1][2][3]. Official Node.js documentation shows path.win32.isAbsolute('\foo') effectively returns true, as single-backslash paths are treated as absolute (equivalent to forward slash '/' examples which also return true) [4][5][6][3]. Note that "\personas" (four characters, two backslashes) would be an incomplete UNC path but still absolute per the logic checking initial separators [4]. This behavior has remained unchanged through Node.js v26 [5].

Citations:


🏁 Script executed:

cat -n packages/cli/src/persona-install.ts | head -150

Repository: AgentWorkforce/workforce

Length of output: 5311


🏁 Script executed:

rg -n "isLocalInstallSource" packages/cli/src/persona-install.ts

Repository: AgentWorkforce/workforce

Length of output: 179


🏁 Script executed:

cat -n packages/cli/src/persona-install.ts | sed -n '330,380p'

Repository: AgentWorkforce/workforce

Length of output: 2105


Handle Windows root-relative paths (\foo) as local install sources.

isLocalInstallSource misses valid Windows root-relative absolute paths. On Windows, \personas should resolve as a local path via resolveLocalSource, but this check returns false and routes it to npm package resolution instead, causing an error.

Suggested fix
 export function isLocalInstallSource(source: string): boolean {
const trimmed = source.trim();
return (
trimmed.startsWith('.') ||
trimmed.startsWith('/') ||
+ trimmed.startsWith('\\') ||
trimmed.startsWith('~/') ||
trimmed.startsWith('~\\') ||
trimmed === '~' ||
/^[A-Za-z]:[\\/]/.test(trimmed) ||
trimmed.startsWith('\\\\')
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/persona-install.ts` around lines 69 - 79, The
isLocalInstallSource function currently misses Windows root-relative paths like
"\personas"; update isLocalInstallSource to treat any single leading backslash
as a local path by adding a check for trimmed.startsWith('\\') (or a regex that
matches a single leading backslash) so those inputs are routed to
resolveLocalSource as local installs (you can replace the existing '\\\\' check
with a broader '\\' check to still cover UNC paths).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Design and implement shadcn-style persona installs

1 participant

@willwashburn