@minnovate/shadow-publisher 0.3.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -4
- package/dist/claude/.claude-plugin/marketplace.json +12 -0
- package/dist/claude/plugin/.claude-plugin/plugin.json +8 -0
- package/dist/claude/plugin/README.md +16 -0
- package/dist/claude/plugin/skills/publish-app/SKILL.md +156 -0
- package/dist/claude/plugin/skills/sync-app/SKILL.md +72 -0
- package/dist/publisher.cjs +728 -169
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -4,11 +4,18 @@ Publish an app's Git history to the serverless Git publishing backend.
|
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
npm install -g @minnovate/shadow-publisher
|
|
7
|
-
publisher
|
|
8
|
-
publisher login
|
|
7
|
+
publisher skills install # adds the publisher's skills to Claude Code
|
|
9
8
|
```
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
Then ask Claude Code to publish your app. Without Claude Code:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
publisher login # signs you in through your browser
|
|
14
|
+
publisher init . --name my-app
|
|
15
|
+
publisher sync # after every change: saves it and publishes it; no git needed
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Needs Node 22.12 or later and git 2.46 or later on `PATH`. The package is one bundled file, and the
|
|
19
|
+
Claude Code skills beside it, with no runtime dependencies.
|
|
13
20
|
|
|
14
21
|
Apache-2.0 licensed; see `LICENSE`.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shadow-publisher",
|
|
3
|
+
"owner": { "name": "Minnovate" },
|
|
4
|
+
"description": "The publisher's Claude Code skills: publish an app and keep it in sync, without git.",
|
|
5
|
+
"plugins": [
|
|
6
|
+
{
|
|
7
|
+
"name": "git-publisher",
|
|
8
|
+
"source": "./plugin",
|
|
9
|
+
"description": "Publish an app folder and keep it updated (\"publish my app\", \"save my changes\", \"get the latest version\")."
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "git-publisher",
|
|
3
|
+
"displayName": "Git Publisher",
|
|
4
|
+
"version": "0.6.0",
|
|
5
|
+
"description": "Publish an app folder's Git history to the serverless Git publishing backend and keep it updated (\"publish my app\", \"update my app\", \"fetch the engineered version\").",
|
|
6
|
+
"author": { "name": "git-publisher project" },
|
|
7
|
+
"keywords": ["git", "publish", "deploy", "serverless"]
|
|
8
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Git Publisher plugin for Claude Code
|
|
2
|
+
|
|
3
|
+
Thin adapter: two skills (`publish-app`, `sync-app`) that drive the `publisher` CLI
|
|
4
|
+
(`@minnovate/shadow-publisher`, `packages/cli`) from PATH. All publication, retry, resume and merge
|
|
5
|
+
logic lives in the shared client library; the skills only orchestrate, and a creator's changes are
|
|
6
|
+
saved and published with `publisher sync`, never with git.
|
|
7
|
+
|
|
8
|
+
It ships inside the npm package, and `publisher skills install` installs it into Claude Code.
|
|
9
|
+
|
|
10
|
+
Runtime requirements in the environment where Claude Code runs: the publisher installed
|
|
11
|
+
(`npm install -g @minnovate/shadow-publisher`, Node.js 22), Git ≥ 2.46 on PATH, outbound HTTPS to
|
|
12
|
+
the portal's domain (sign-in and publishing at `/publish`) and the S3 bucket host for direct
|
|
13
|
+
uploads/downloads.
|
|
14
|
+
|
|
15
|
+
See `integrations/claude/README.md` for installation, authentication, uninstall and the verified
|
|
16
|
+
environments.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: publish-app
|
|
3
|
+
description: Publish or update the user's app (a folder) with the publisher. Use when the user says "publish my app", "update my app", "save my changes", "push my changes", "save this version online", or asks to resolve an interrupted publication. Handles first-time setup, every later update, interruption recovery and conflicts by running the `publisher` command; never uses git to commit or push.
|
|
4
|
+
allowed-tools: Bash(publisher *), Bash(git status *), Bash(git diff *), Bash(git log *)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Publish or update the user's app
|
|
8
|
+
|
|
9
|
+
You publish the user's app folder with the `publisher` command. All saving, merging, publishing,
|
|
10
|
+
retry and resume logic lives in it; your job is to run it, read its `--json` output, and tell the
|
|
11
|
+
user what happened in plain language. Never explain S3, packs, commits, sessions or tokens.
|
|
12
|
+
|
|
13
|
+
**`publisher sync` is how a change is saved and published. Never run `git add`, `git commit`,
|
|
14
|
+
`git push` or `git stash` in the user's app folder**, and never add a git remote. `sync` records the
|
|
15
|
+
folder's changes itself — and leaves out `.env` files, keys, `node_modules` and anything larger than
|
|
16
|
+
50 MB, which a hand-made commit would not. You may read with `git status`, `git diff` and `git log`
|
|
17
|
+
to describe a change; nothing else.
|
|
18
|
+
|
|
19
|
+
Every command accepts `--json` and prints exactly one JSON object on stdout. Exit codes:
|
|
20
|
+
`0` success · `1` local/usage error · `2` rejected or conflict · `3` outcome not yet known · `4` authentication.
|
|
21
|
+
|
|
22
|
+
## 1. Preflight (run once)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
publisher --version
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
If the command is not found, tell the user the publisher is not installed and that one command
|
|
29
|
+
installs it: `npm install -g @minnovate/shadow-publisher` (it needs Node.js 22). Stop there.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
publisher doctor --json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- `gitOk:false` → tell the user Git 2.46 or later must be installed; stop.
|
|
36
|
+
- Otherwise do exactly what **`next.action`** says. `doctor` works this out for you, and the same
|
|
37
|
+
`next` object rides on `error.details` of any command that cannot resolve a connection — so you
|
|
38
|
+
never have to interpret `credentials`, `endpoint` or `profiles` yourself.
|
|
39
|
+
|
|
40
|
+
| `next.action` | What it means | What to do |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `"ready"` | Signed in and pointed somewhere. | **Nothing. Go to step 2 and do not mention any of this.** |
|
|
43
|
+
| `"sign-in"` | Not connected to anything yet. | **Ask them nothing** — `next.ask` is `null`. Just run `next.command` (`publisher login --json`). It finds the service, opens their browser and prints a code for them to confirm; tell them to expect that, and wait. Use `--portal <url>` only if they say they are on a different platform. |
|
|
44
|
+
| `"choose-profile"` | Several places are set up and none is marked usual. | Show `next.profiles` and ask `next.ask`. Then run `next.command` with the name they picked. |
|
|
45
|
+
| `"reauthenticate"` | The place is known; the credential is gone or expired. | Just run `next.command`. `next.ask` is `null` — do **not** ask them for anything; the browser sign-in asks them itself. |
|
|
46
|
+
| `"bind-endpoint"` | A token is set in the environment, but nothing said where it may go. | **Stop** and tell them publishing is blocked by a token set in this shell. `next.command` is `null` on purpose: never set `GIT_PUBLISHER_ENDPOINT` or pass `--endpoint` yourself — that is exactly the choice the check leaves to whoever set the token. |
|
|
47
|
+
|
|
48
|
+
**Never ask the user for a token, an endpoint, a URL or a credential — in any words.** They are
|
|
49
|
+
publishing an app; they do not know what those are, and you no longer need them: `login` discovers
|
|
50
|
+
where publishing lives and signs them in through their browser. `next.ask` is `null` whenever
|
|
51
|
+
nothing needs asking, which is now every case except choosing between connections they already
|
|
52
|
+
have; when it is not null it is already phrased for them, so pass it on rather than rewriting it.
|
|
53
|
+
|
|
54
|
+
If they name an environment ("publish to dev"), pass `--profile dev` on each command rather than
|
|
55
|
+
changing their default.
|
|
56
|
+
|
|
57
|
+
## 2. First publication of a folder
|
|
58
|
+
|
|
59
|
+
Run in the app folder (the folder the user is working in):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
publisher info --json # exit 1 with LOCAL_STATE "not associated" means: not published yet
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If not associated:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
publisher init . --name "<app name>" --json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`init` prepares the folder with an explicit file selection: it excludes `.git`, `node_modules`,
|
|
72
|
+
build caches, `.env*`, keys/certificates and files larger than 50 MB, and it respects
|
|
73
|
+
`.gitignore`. Show the user the `onboarding.excluded` list so they know what was left out (the
|
|
74
|
+
exclusion rules are a safety net, not a secret scanner). Then publish it with step 3.
|
|
75
|
+
|
|
76
|
+
## 3. Every update — and the first publish after `init`
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
publisher sync . --message "<a few words on what changed>" --timeout 90 --json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
That one command records what changed in the folder, brings in anything published meanwhile (a
|
|
83
|
+
fast-forward or a clean merge — it never rewrites the user's work), and publishes the result. Write
|
|
84
|
+
the message in the user's words ("Add a contact page"), not git's.
|
|
85
|
+
|
|
86
|
+
## 4. Reading the result
|
|
87
|
+
|
|
88
|
+
After a publish, the command follows the app going live, asking every 5 seconds, for at most
|
|
89
|
+
`--timeout` seconds. Keep that at 90: your shell tool shows output only when a command ends, and
|
|
90
|
+
cuts one off after two minutes. With `--json` it prints one line per step, then the result object
|
|
91
|
+
as the LAST line.
|
|
92
|
+
|
|
93
|
+
If the run is still going when the 90 seconds are up, `progress.kind` is `"timed-out"` and
|
|
94
|
+
`progress.next.command` picks it up. Run it (it is `publisher follow --publication <id> --json`;
|
|
95
|
+
add `--timeout 90`) and repeat until `progress.kind` is no longer `"timed-out"`, up to about 15
|
|
96
|
+
minutes in all. Each call prints the steps as they now stand, from the first: relay only the ones
|
|
97
|
+
you have not relayed yet.
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{"event":"stage","stage":"running","word":"checked","state":"in-progress","at":"2026-09-23T10:00:10Z"}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Relay each step in its `word` — **received**, **checked**, **going live**, **live** — and nothing
|
|
104
|
+
else: not the `stage` name, not a step you guessed. Read the result from the last line. **If the
|
|
105
|
+
command was cut off while following**, the publication had already succeeded before the following
|
|
106
|
+
began: say it is published, and follow it with `publisher follow --publication <publicationId>
|
|
107
|
+
--timeout 90 --json` (the id is in the `sync` output's `publication`, or `publisher status`). Do
|
|
108
|
+
not run `sync` again to find out. `--no-wait` skips the following, for when the user asks not to wait.
|
|
109
|
+
|
|
110
|
+
`state` says what happened; `hint` is one sentence you may pass on as it is.
|
|
111
|
+
|
|
112
|
+
- `"published"` → the app was published. Mention what was recorded (`recorded.committed`) and, if
|
|
113
|
+
`recorded.excluded` is not empty, what was left out and why. Then read `progress` (null with
|
|
114
|
+
`--no-wait`) and pass on its `hint`:
|
|
115
|
+
- `kind: "settled"` with `ending` `"live"`: the app is live. `"not-deployed"`: published and
|
|
116
|
+
checked, and this organisation does not deploy automatically. `"failed"`: it stopped at
|
|
117
|
+
`failedAt`; the portal says why — do not guess the reason. `"stopped"`: a newer publication
|
|
118
|
+
replaced it.
|
|
119
|
+
- `"timed-out"`: still going; run `progress.next.command` as above. After about 15 minutes in
|
|
120
|
+
all, stop and point them to the portal (`progress.portal`).
|
|
121
|
+
- `"unauthorized"`: published, but the portal did not accept this sign-in; run `publisher login`
|
|
122
|
+
before the next publish.
|
|
123
|
+
- `"unavailable"`, `"forbidden"` or `"malformed"`: the publish itself succeeded, but this
|
|
124
|
+
terminal cannot show the steps. Tell them to follow it in the portal. Never report a step that
|
|
125
|
+
no `stage` line said.
|
|
126
|
+
- `"up_to_date"` → nothing new to publish; the service already has this version.
|
|
127
|
+
- `"conflict"` → the user's change and a published change touched the same lines of
|
|
128
|
+
`conflicts[]`. **Nothing was published and nothing was lost.** Do not resolve it on your own and
|
|
129
|
+
never publish with `--force`. Follow the `sync-app` skill's conflict section.
|
|
130
|
+
- `"rejected"` with `publication.rejection.code`:
|
|
131
|
+
- `NON_FAST_FORWARD` / `STALE_GENERATION` / `EXPECTED_OLD_MISMATCH`: somebody published in the
|
|
132
|
+
moment between the merge and the publish. Run `publisher sync . --json` once more
|
|
133
|
+
(`publication.next.command` says the same).
|
|
134
|
+
- `REF_FORBIDDEN`: the user may not publish this branch; tell them which branch and stop.
|
|
135
|
+
- `MISSING_OBJECTS`: the local copy is incomplete (for example a shallow clone). Tell the user to
|
|
136
|
+
publish from a complete copy: `publisher checkout <new folder> --repo <id>`.
|
|
137
|
+
- `LIMIT_EXCEEDED`, `PACK_INVALID`: quote the message; suggest removing very large files from the
|
|
138
|
+
folder if the message mentions size.
|
|
139
|
+
- `"unknown"` / exit code 3: the publication was sent but its outcome is not known yet. Run
|
|
140
|
+
`publisher status . --json`; it waits for and resolves interrupted publications without creating
|
|
141
|
+
duplicates. Repeat once if it still says `finalizing`.
|
|
142
|
+
- Exit code 4: run `publisher doctor --json` and follow `next.action` (normally `reauthenticate`:
|
|
143
|
+
run `next.command`). Never ask the user for a token.
|
|
144
|
+
|
|
145
|
+
## 5. Interrupted earlier?
|
|
146
|
+
|
|
147
|
+
If a previous publish was interrupted (network loss, closed session), `publisher status . --json`
|
|
148
|
+
resumes or resolves it. Always run it before starting a new sync when the last result was unknown.
|
|
149
|
+
|
|
150
|
+
## Rules
|
|
151
|
+
|
|
152
|
+
- `publisher sync` is the only way a change is saved and published. Never `git add`, `git commit`,
|
|
153
|
+
`git push`, `git stash`, `git reset`, `git rebase`, and never modify remotes.
|
|
154
|
+
- Never print tokens or signed URLs. The publisher already redacts them.
|
|
155
|
+
- Never publish with `--force` unless the user explicitly asks to overwrite the published history
|
|
156
|
+
and understands the other side's changes will be discarded.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sync-app
|
|
3
|
+
description: Bring the published (possibly engineered) version of the user's app into their folder, together with their own changes, and publish the result. Use when the user says "get the latest version", "sync my app", "sync with the engineering changes", when a publish was refused because somebody else published first, or when a sync reported a conflict. Never overwrites local work and never uses git to commit.
|
|
4
|
+
allowed-tools: Bash(publisher *), Bash(git status *), Bash(git log *), Bash(git diff *)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Keep the user's app in step with what is published
|
|
8
|
+
|
|
9
|
+
**`publisher sync` does all of it**: it records the user's changes, brings in what was published
|
|
10
|
+
meanwhile (a fast-forward, or a clean merge that never rewrites their work), and publishes the
|
|
11
|
+
result. Never run `git add`, `git commit`, `git push`, `git pull`, `git merge`, `git stash`,
|
|
12
|
+
`git reset` or `git rebase` in the user's app folder.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
publisher sync . --json
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Read the step lines and `state` exactly as the `publish-app` skill describes (step 4): after a
|
|
19
|
+
publish it follows the app through received → checked → going live → live before it prints the
|
|
20
|
+
result. `merged` says whether anything
|
|
21
|
+
published elsewhere was brought in (`"fast-forward"` or `"merge"`); mention it when it is not null.
|
|
22
|
+
|
|
23
|
+
## Just looking
|
|
24
|
+
|
|
25
|
+
To say where the folder stands without changing anything:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
publisher workspace-status . --json # state + hint; exit 2 means conflict
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| state | meaning | what to do |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `up_to_date` | identical to the published version | nothing |
|
|
34
|
+
| `local_changes` | the user changed something the service does not have yet | `publisher sync` |
|
|
35
|
+
| `remote_ahead` | something was published elsewhere; the folder is behind | `publisher sync` |
|
|
36
|
+
| `diverged_clean_merge` | both changed, and the changes combine cleanly | `publisher sync` |
|
|
37
|
+
| `conflict` | both changed the same lines (`conflicts[]`) | see below |
|
|
38
|
+
| `detached` | the folder is not on the branch it publishes | ask the user before anything else |
|
|
39
|
+
|
|
40
|
+
`pending[]` lists interrupted publications; run `publisher status . --json` first when it is not empty.
|
|
41
|
+
|
|
42
|
+
## A fresh copy
|
|
43
|
+
|
|
44
|
+
When the user wants their app in a new folder (a new computer, a clean start):
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
publisher checkout <new-folder> --repo <repoId> --json # a NEW folder only; refuses a non-empty one
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Conflicts
|
|
51
|
+
|
|
52
|
+
A `conflict` means the user's version and the published one both changed the same lines of the
|
|
53
|
+
files in `conflicts[]`. **Nothing was published and nothing was lost**: the user's change is
|
|
54
|
+
recorded locally, and the published version is still on the service.
|
|
55
|
+
|
|
56
|
+
1. List the files and say, in plain words, what each side changed there (`git diff` and `git log`
|
|
57
|
+
may help you describe it).
|
|
58
|
+
2. Ask the user which version to keep for each file, or whether to combine them. Do not decide for
|
|
59
|
+
them and never publish with `--force` — that discards the other side's work.
|
|
60
|
+
3. Only after they decide, put both versions into the files, marked:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
publisher sync . --mark-conflicts --json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Each file in `conflicts[]` now holds both versions between `<<<<<<<`, `=======` and `>>>>>>>`
|
|
67
|
+
lines. Edit each to what they chose, and remove those lines.
|
|
68
|
+
4. Run `publisher sync . --json`. It records the edit and publishes it. If a file still carries the
|
|
69
|
+
marker lines it refuses, names the files (`error.details.marked`) and publishes nothing; finish
|
|
70
|
+
the edit and run it again.
|
|
71
|
+
|
|
72
|
+
No git command is needed at any point, including here.
|