Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
5c9d18d
Rebase onto upstream/master (2026-08-15): 323 commits (d5b9f6c8..484b…
claude Aug 15, 2026
6f38b86
fix(ci): remove pnpm-lock.yaml from rebase commit
claude Aug 15, 2026
6cc8bef
fix(ui): re-apply fork-specific patches to SidebarAccountMenu, Summar…
claude Aug 15, 2026
405573e
fix(shared): restore fork-specific enableCloudSync to InstanceExperim…
claude Aug 15, 2026
1ed954d
fix(adapter-utils): restore brace balance in acpx-engine execute.ts
claude Aug 15, 2026
e7d896b
fix(codex-local): remove duplicate firstMeaningfulStderrLine export
claude Aug 15, 2026
a16a546
fix: resolve remaining TypeScript errors from upstream rebase
claude Aug 15, 2026
8289794
fix: restore fork-specific cloud-upstream files and enableCloudSync d…
claude Aug 15, 2026
6616f85
fix(plugin-sdk): add bypassSession to PluginEnvironmentExecuteParams
claude Aug 15, 2026
c158eae
fix(test): remove duplicate vi.doMock for adapter-plugin-store
claude Aug 15, 2026
57ba17c
fix(test): convert adapter-plugin-store mock to top-level vi.mock
claude Aug 15, 2026
097ddab
fix(test): restore health.test.ts createApp signature from merge arti…
claude Aug 15, 2026
f75f148
fix(ui): restore userSecretDefinitions query body in AgentConfigForm
claude Aug 15, 2026
db20f77
fix(ui): remove stray duplicate query body in AgentConfigForm
claude Aug 15, 2026
48189de
fix(test): replace health.test.ts with upstream version
claude Aug 15, 2026
19e1e76
fix(ui): resolve remaining TypeScript errors from upstream rebase
claude Aug 15, 2026
8b3decd
fix(test): allowlist upstream files in features-migration-guard
claude Aug 15, 2026
34b2c0b
fix(db): update migration numbers in DB tests (+3 offset)
claude Aug 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ is missing, the cloned app does not have the expected companies/issues/agents,
or the user explicitly asks for the normal isolated-workspace database.

```sh
pnpm paperclipai worktree reseed --from-instance default --seed-mode full --yes
npx paperclipai worktree reseed --from-instance default --seed-mode full --yes
```

After reseed, restart through the managed runtime path. A reseed can copy
Expand Down
44 changes: 33 additions & 11 deletions .agents/skills/paperclip-page/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,24 +73,44 @@ Required for live publishes:
export AWS_REGION=us-east-1
export PAPERCLIP_PAGE_BUCKET=paperclip-pages-prod
export PAPERCLIP_PAGE_BASE_URL=https://pages.paperclip.ing
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID=...
export PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY=...
```

Optional:

```bash
export PAPERCLIP_PAGE_DEFAULT_PREFIX=""
export PAPERCLIP_PAGE_AWS_PROFILE=paperclip-page-uploader
export PAPERCLIP_PAGE_AWS_SESSION_TOKEN=... # only with the namespaced key pair
```

Credential resolution order inside `publish.sh`:

1. `PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID` + `PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY`
(scoped to the helper's `aws` calls; the surrounding process identity is
untouched)
2. `PAPERCLIP_PAGE_AWS_PROFILE`, passed to `aws` as `--profile` (ambient
`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` /
`AWS_PROFILE` are stripped from the helper's `aws` calls so the named
profile always wins)
3. The ambient AWS credential chain

Setting both the namespaced key pair and `PAPERCLIP_PAGE_AWS_PROFILE` is an
error.

Recommended Paperclip secret names:

- `paperclip-page-aws-access-key-id`
- `paperclip-page-aws-secret-access-key`

Bind those secrets into publisher agents as `AWS_ACCESS_KEY_ID` and
`AWS_SECRET_ACCESS_KEY`. Do not reuse Paperclip's internal S3 attachment/object
Bind those secrets into publisher agents as `PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID`
and `PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY`. Never bind them as the global
`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` names: static env keys override
`AWS_PROFILE` in the AWS CLI and every SDK, so global names silently switch the
whole agent run — and every subprocess — to the page-uploader identity and
break access to anything the uploader cannot reach (Secrets Manager, STS role
use, other buckets). Do not reuse Paperclip's internal S3 attachment/object
storage credentials.

## AWS Setup
Expand Down Expand Up @@ -473,27 +493,29 @@ Create secrets from environment variables so values do not land in shell history
export PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID="$(jq -r '.AccessKey.AccessKeyId' /tmp/paperclip-page-uploader-key.json)"
export PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY="$(jq -r '.AccessKey.SecretAccessKey' /tmp/paperclip-page-uploader-key.json)"

pnpm paperclipai secrets create \
npx paperclipai secrets create \
--company-id <company-id> \
--name paperclip-page-aws-access-key-id \
--value-env PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID

pnpm paperclipai secrets create \
npx paperclipai secrets create \
--company-id <company-id> \
--name paperclip-page-aws-secret-access-key \
--value-env PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY
```

Bind runtime env to publishing agents:
Bind runtime env to publishing agents. Use the namespaced names — never the
global `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`, which would shadow the
host `AWS_PROFILE` identity for the entire agent run:

```json
{
"AWS_ACCESS_KEY_ID": {
"PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID": {
"type": "secret_ref",
"secretId": "<access-key-secret-id>",
"version": "latest"
},
"AWS_SECRET_ACCESS_KEY": {
"PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY": {
"type": "secret_ref",
"secretId": "<secret-key-secret-id>",
"version": "latest"
Expand All @@ -510,7 +532,7 @@ Bind runtime env to publishing agents:
Create or update the company skill from this package:

```bash
pnpm paperclipai skills create \
npx paperclipai skills create \
--company-id <company-id> \
--name "Paperclip Page" \
--slug paperclip-page \
Expand All @@ -521,7 +543,7 @@ pnpm paperclipai skills create \
Attach it to an agent:

```bash
pnpm paperclipai skills agent sync <agent-id-or-shortname> \
npx paperclipai skills agent sync <agent-id-or-shortname> \
--company-id <company-id> \
--skill paperclip-page
```
Expand Down
14 changes: 12 additions & 2 deletions .agents/skills/paperclip-page/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,20 @@ host, for example `https://pages.paperclip.ing/<slug>/`.
- `PAPERCLIP_PAGE_BUCKET`
- `PAPERCLIP_PAGE_BASE_URL`
- `AWS_REGION`
- AWS credentials via Paperclip Secrets or an approved AWS vault
- `PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID` and `PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY`
with the page-uploader credentials from Paperclip Secrets
- Optional environment variables:
- `PAPERCLIP_PAGE_DEFAULT_PREFIX`
- `PAPERCLIP_PAGE_AWS_PROFILE`
- `PAPERCLIP_PAGE_AWS_PROFILE` (alternative to the namespaced key pair)
- `PAPERCLIP_PAGE_AWS_SESSION_TOKEN` (only together with the namespaced key
pair)

Do not bind the page-uploader credentials as global `AWS_ACCESS_KEY_ID` /
`AWS_SECRET_ACCESS_KEY`: static env keys take precedence over `AWS_PROFILE` in
every AWS SDK, so global names silently replace the host identity for every
process in the agent run. The namespaced variables scope the uploader identity
to this helper only. The ambient credential chain still works as a fallback
when none of the `PAPERCLIP_PAGE_AWS_*` credential variables are set.

## Workflow

Expand Down
52 changes: 50 additions & 2 deletions .agents/skills/paperclip-page/scripts/publish.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ Required environment for live publish:

Optional environment:
PAPERCLIP_PAGE_DEFAULT_PREFIX, PAPERCLIP_PAGE_AWS_PROFILE
PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID, PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY,
PAPERCLIP_PAGE_AWS_SESSION_TOKEN

Credential resolution for aws calls made by this helper:
1. PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID + PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY
(used only by this helper; ambient AWS_PROFILE/AWS_* identity is untouched)
2. PAPERCLIP_PAGE_AWS_PROFILE (passed as --profile; ambient AWS_* identity
variables are stripped from the helper's aws calls)
3. Ambient AWS credential chain (env keys, profile, instance role)

Options:
--slug SLUG Lowercase URL slug. Allowed: a-z, 0-9, hyphen.
Expand Down Expand Up @@ -125,9 +134,26 @@ join_prefix() {
}

aws_base_args=()
aws_env_unset=()
aws_env_overrides=()

aws_cli() {
aws "${aws_base_args[@]}" "$@"
local name pair
if [[ ${#aws_env_unset[@]} -gt 0 || ${#aws_env_overrides[@]} -gt 0 ]]; then
# Scope the page-uploader identity to this helper's aws calls only, and
# drop the ambient identity variables that would otherwise mix with or
# shadow the configured credential source. Apply the overrides with shell
# builtins in a subshell — passing them to an external `env` command would
# expose the credential values in its argv (world-readable via
# /proc/<pid>/cmdline) while it runs.
(
for name in "${aws_env_unset[@]}"; do unset "$name"; done
for pair in "${aws_env_overrides[@]}"; do export "$pair"; done
exec aws "${aws_base_args[@]}" "$@"
)
else
aws "${aws_base_args[@]}" "$@"
fi
}

object_exists() {
Expand Down Expand Up @@ -297,6 +323,18 @@ default_prefix="$(normalize_default_prefix "${PAPERCLIP_PAGE_DEFAULT_PREFIX:-}")
[[ -n "$base_url" ]] || die "PAPERCLIP_PAGE_BASE_URL is required"
base_url="$(normalize_base_url "$base_url")"

page_access_key_id="${PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID:-}"
page_secret_access_key="${PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY:-}"
if [[ -n "$page_access_key_id" || -n "$page_secret_access_key" ]]; then
[[ -n "$page_access_key_id" && -n "$page_secret_access_key" ]] ||
die "PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID and PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY must be set together"
[[ -z "${PAPERCLIP_PAGE_AWS_PROFILE:-}" ]] ||
die "set PAPERCLIP_PAGE_AWS_PROFILE or the PAPERCLIP_PAGE_AWS_* key pair, not both"
fi
if [[ -n "${PAPERCLIP_PAGE_AWS_SESSION_TOKEN:-}" && -z "$page_access_key_id" ]]; then
die "PAPERCLIP_PAGE_AWS_SESSION_TOKEN requires the PAPERCLIP_PAGE_AWS_* key pair"
fi

explicit_slug=0
if [[ -n "$slug_arg" ]]; then
explicit_slug=1
Expand All @@ -310,8 +348,18 @@ if [[ "$dry_run" == "0" ]]; then
require_command curl
[[ -n "$region" ]] || die "AWS_REGION is required for live publish"
aws_base_args=(--region "$region")
if [[ -n "${PAPERCLIP_PAGE_AWS_PROFILE:-}" ]]; then
if [[ -n "$page_access_key_id" ]]; then
aws_env_unset=(AWS_PROFILE AWS_SESSION_TOKEN)
aws_env_overrides=(
AWS_ACCESS_KEY_ID="$page_access_key_id"
AWS_SECRET_ACCESS_KEY="$page_secret_access_key"
)
if [[ -n "${PAPERCLIP_PAGE_AWS_SESSION_TOKEN:-}" ]]; then
aws_env_overrides+=(AWS_SESSION_TOKEN="$PAPERCLIP_PAGE_AWS_SESSION_TOKEN")
fi
elif [[ -n "${PAPERCLIP_PAGE_AWS_PROFILE:-}" ]]; then
aws_base_args+=(--profile "$PAPERCLIP_PAGE_AWS_PROFILE")
aws_env_unset=(AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_PROFILE)
fi
fi

Expand Down
Loading
Loading