Skip to content

feat(install): --env + --skill flags (re-PR after squash collapsed the stack) - #47

Merged
rrader26 merged 2 commits into
mainfrom
feat/install-env-and-skill
May 15, 2026
Merged

feat(install): --env + --skill flags (re-PR after squash collapsed the stack)#47
rrader26 merged 2 commits into
mainfrom
feat/install-env-and-skill

Conversation

@rrader26

Copy link
Copy Markdown
Contributor

Summary

Re-PR of #45 + #46 against main. The originals were stacked PRs whose squash-merge of #44 collapsed the base branches and dropped the child content from main even though GitHub reported them as merged. Verifiable in the commit log: git log origin/main shows only #44.

Bundled as one PR this time so the same merge accident can't happen again.

What's in

From #45--env=KEY=VALUE on install

  • agentmark-mcp install --env=KEY=VAL writes that env block into each AI client's MCP config
  • Validation: key must match [A-Za-z_][A-Za-z0-9_]* (no shell metacharacters), 4 KiB value cap, null bytes rejected, duplicate keys warn-and-overwrite (warning contains the key name but never the value)
  • Refactor: parseFlags / parseEnvFlag / buildEntryFromFlags moved from cli.ts to src/mcp/install/flags.ts so tests can import without spawning the MCP server

From #46--skill=<name> on install

  • agentmark-mcp install --skill=thinkfleet-memory writes the skill content into the right per-tool path (Claude Code / Claude Desktop today; rules-file targets for Cursor/Windsurf/Codex are scaffolded but not in the default target list yet)
  • Canonical thinkfleet-memory skill (~5KB markdown with YAML frontmatter) — teaches the agent to call agentmark_memory_* at session start, save preferences without being asked, search before guessing about the user's environment
  • Skill name validation: [a-z0-9][a-z0-9-]* — rejects path-traversal (../escape) and shell-relevant chars
  • Idempotent: same content → already_present; different → updated
  • Marker-block upsertManagedBlock for future rules-file targets — fully tested

Tests

  • test/mcp/install-flags.test.ts28 cases (env validation + skill flag validation)
  • test/mcp/install-skills.test.ts16 cases (skill installer, marker-block logic, built-in catalog)
  • Total impact: 44 tests added; full suite passes.

Typecheck clean.

Why this is one PR not two

The original PRs were stacked, which made the GitHub UX clean during review but broke catastrophically at merge time — GitHub silently dropped #45 and #46 from main when #44 squash-merged. Consolidating into one PR removes the failure mode.

Once this lands

Cut @thinkfleet/agentmark@0.13.0 — version bump + CHANGELOG + npm publish. I'll prep that release PR the moment this merges.

🤖 Generated with Claude Code

rrader26-sys and others added 2 commits May 15, 2026 07:06
The companion to PR #44 (env-aware memory backend). Now the install
CLI can write the env block into client MCP configs, so
`agentmark-mcp install --env=THINKFLEET_API_KEY=sk-…` makes the
memory plugin in PR #44 actually pick up SaaS creds at startup.

Surface:
  agentmark-mcp install \\
      --client=claude-code \\
      --env=THINKFLEET_BASE_URL=https://app.thinkfleet.ai \\
      --env=THINKFLEET_PROJECT_ID=proj_xxx \\
      --env=THINKFLEET_API_KEY=sk-xxx

The values land in the MCP server entry's `env` field — Claude Code,
Cursor, Windsurf, Codex CLI all already honor that.

Refactor:
  - parseFlags / parseEnvFlag / buildEntryFromFlags moved from
    cli.ts to src/mcp/install/flags.ts. cli.ts imports them. The
    extraction is needed because tests can't import cli.ts directly
    (it has top-level main() side effects that spawn the MCP server).

Validation (all of these MUST throw):
  - Missing or empty key:           --env=NOEQUALS, --env==value
  - Shell-relevant chars in key:    --env=FOO;rm=anything
  - Whitespace in key:              --env=FOO BAR=x
  - Key starting with a digit:      --env=1FOO=bar
  - Null byte in value:             --env=FOO=before\0after
  - Value over 4KB cap:             prevents config-bomb injection

Behavior:
  - Duplicate --env=KEY=… prints a stderr warning and uses last.
    Warning contains only the key name; values never echo.
  - `--env=FLAG=` (empty value) is legal — some env-driven flags
    expect that.
  - Mixed-case keys allowed (NodeEnv-style); strictly alphanumeric +
    underscore otherwise.

Security note (codified in HELP text):
  - --env values are written to the client's MCP config file on disk
    (e.g. ~/.cursor/mcp.json). Prefer rotating from an OS keychain
    rather than passing long-lived keys on a shared machine.
  - ThinkFleet Desktop (PR γ, upcoming) wires this from Electron
    safeStorage so secrets live in the OS keychain at rest and only
    surface as --env at install time.

Tests:
  - test/mcp/install-flags.test.ts — 22 cases covering each
    validation branch + the duplicate-key warning semantics.
  - test/mcp/install.test.ts — 2 new e2e cases proving the env
    block lands in the written JSON and that re-running with new
    values updates idempotently.

564 tests pass. Typecheck clean. No new dependencies.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The third tier of the memory bridge slice. PR #44 made the memory
plugin auto-select the SaaS backend. PR #45 lets the install CLI
write the env block into client configs. This PR adds the skill
file that teaches the agent WHEN and HOW to use the memory tools —
without it, an agent has the tools but rarely calls them at the
right moments.

What's a "skill" here:
  Markdown content (with optional YAML frontmatter) that an AI
  client loads automatically at session start. Each major coding
  assistant has a slot for it:
    - Claude Code:    ~/.claude/skills/<name>/skill.md
    - Claude Desktop: ~/Library/Application Support/Claude/skills/...

  For tools without a native skills concept (Cursor, Windsurf,
  Codex CLI), the same content gets wrapped in marker comments
  and surgically injected into their rules file. That codepath is
  scaffolded here (upsertManagedBlock + non-exclusive targets) but
  no rules-file clients are in the default target list yet — adding
  them needs per-tool research on the rules-file location semantics.

Canonical skill — thinkfleet-memory:
  - Tells the agent to call agentmark_memory_search at session
    start to load project context.
  - Tells it to save user preferences, project facts, decisions
    without being asked.
  - Tells it to search memory before guessing about the user's
    environment.
  - Includes a scope picker, failure modes, and an explicit
    "what NOT to do" section (don't dump every memory at the user,
    don't save secrets, don't overwrite user-scope with
    session-scope writes).
  - Inlined as a TypeScript constant (~5KB) so the skill ships
    with the npm package without separate asset bundling.

CLI surface:
  agentmark-mcp install \\
      --client=claude-code \\
      --env=THINKFLEET_BASE_URL=... \\
      --env=THINKFLEET_PROJECT_ID=... \\
      --env=THINKFLEET_API_KEY=... \\
      --skill=thinkfleet-memory

Validation:
  - Skill name must match [a-z0-9][a-z0-9-]* — rejects path-
    traversal attempts (../escape) and shell-relevant chars.
  - Unknown skill names log to stderr and the install still
    returns a non-zero exit so CI catches typos.
  - Atomic write via temp-file + rename, 0644 perms.
  - Idempotent: same content → "already_present"; different
    content → "updated"; non-existent → "added".

Marker-block logic (pure function, fully tested):
  upsertManagedBlock(text, block) — replaces an existing
  <!-- thinkfleet:skill:start --> ... <!-- thinkfleet:skill:end -->
  block in place, or appends one if absent. Preserves user content
  outside the markers verbatim.

Tests:
  - test/mcp/install-skills.test.ts — 16 cases covering:
      - upsertManagedBlock: append-empty, append-with-trailing-NL,
        replace-existing, idempotent, preserve-user-content.
      - installSkill end-to-end with fake targets: native (whole
        file), rules-file (marker block), already_present
        idempotency, dryRun, unsupported-platform skip.
      - Built-in skill catalog: thinkfleet-memory is registered,
        unknown names return null, every memory tool name appears
        in the skill content.
  - test/mcp/install-flags.test.ts — 6 new cases for --skill
    name validation.

596 tests pass. Typecheck clean. No new dependencies.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rrader26
rrader26 merged commit d7ffefc into main May 15, 2026
4 checks passed
Sign up for free to 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.

2 participants