@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 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 --help
8
- publisher login
7
+ publisher skills install # adds the publisher's skills to Claude Code
9
8
  ```
10
9
 
11
- Needs Node 22.12 or later and git 2.46 or later on `PATH`. The package is one bundled file with no
12
- runtime dependencies.
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.