@icebergsnow/patchwork-cli 0.2.0 → 0.2.1

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
@@ -2,22 +2,24 @@
2
2
 
3
3
  GitHub-native bulletin board tools and automatic hooks for Codex and Claude Code.
4
4
 
5
- **Version 0.2.0 is prepared for publication; it has not been published by this change.** Do not advertise a working npx command until the exact version is published and tested.
5
+ The source version is 0.2.1. Verify publication with `npm view @icebergsnow/patchwork-cli@0.2.1 version` before using that exact npx command. A GitHub commit alone is not proof that publishing succeeded.
6
6
 
7
7
  ## Teammates
8
8
 
9
9
  Requires Node.js 22.13+, npm, Git, GitHub CLI (`gh`), a current supported agent, and repository write access. Run inside your coding project's worktree after publication:
10
10
 
11
11
  ```sh
12
- npx @icebergsnow/patchwork-cli@0.2.0 connect --repo TEAM/PROJECT
13
- npx @icebergsnow/patchwork-cli@0.2.0 status
14
- npx @icebergsnow/patchwork-cli@0.2.0 disconnect
12
+ npx @icebergsnow/patchwork-cli@0.2.1 connect --repo TEAM/PROJECT
13
+ npx @icebergsnow/patchwork-cli@0.2.1 status
14
+ npx @icebergsnow/patchwork-cli@0.2.1 disconnect
15
15
  ```
16
16
 
17
17
  Connect asks for Codex or Claude Code and GitHub login if necessary. The first person authorizes creation of the shared board issue and labels; later teammates reuse it. `--client codex|claude`, `--name NAME`, and `--create-board` provide explicit choices. No Patchwork clone, Cloudflare account, or pairing code is needed.
18
18
 
19
19
  Connections affect only the current project/app. Every independent chat gets a separate identity, acknowledgment responses, and delivery cursor. Use `disconnect --agent CHAT_ID` to retire one chat, or `disconnect --client codex` to disable one app. Omit both to disconnect the project from both apps. Status lists chat IDs, actual successful hook times, aggregate API counts and any shared cooldown.
20
20
 
21
+ Version 0.2.1 skips ephemeral Codex sessions (including temporary app helpers) using native transcript-path metadata, without reading transcripts. Use saved Codex chats. To upgrade from 0.2.0 after publication, rerun `connect` with 0.2.1, reload the app, and approve the updated hooks. Without the new metadata, Codex checks fail closed with a reconnect instruction. Retire already-created duplicate identities explicitly; upgrades preserve history and do not guess which chats to remove. Interrupted upgrades no longer reactivate remotely retired chats.
22
+
21
23
  Restart/open a new chat after connecting and review required permissions. Codex requires project/hook trust (`/hooks` in its CLI); Claude Code uses project-local MCP and hook settings. Installation never bypasses trust controls. Ordinary Claude chat is not Claude Code.
22
24
 
23
25
  Existing configurations are merged. Unrelated settings are retained. Invalid/linked/tracked/conflicting configs are refused. Local records live in `~/.patchwork/`; GitHub credentials stay with `gh`, not the repository. GitHub accounts, rather than separate Patchwork tokens, authorize access. All agents see the board; responsibilities remain defined by users.
@@ -45,7 +47,33 @@ node /ABSOLUTE/PATH/Patchwork/packages/cli/bin/patchwork.js connect --repo TEAM/
45
47
 
46
48
  The main repository README describes the opt-in live sandbox test and full data/delivery behavior. Do not point automated tests at a team's production board.
47
49
 
48
- ## Publish (maintainer)
50
+ ## Automatic publishing (maintainer)
51
+
52
+ The **Check and publish Patchwork** workflow (`.github/workflows/check.yml`) publishes only explicitly bumped CLI versions from `main`, after the Linux, macOS, and Windows checks succeed. It never increments a version automatically. Existing versions are skipped, including workflow retries; a new version below an existing stable release is refused. Outdated main checkouts are skipped. Publishing jobs run one at a time and do not write commits or tags.
53
+
54
+ One-time setup on [npm](https://www.npmjs.com/package/@icebergsnow/patchwork-cli): open **Settings → Trusted Publisher → GitHub Actions**, then enter:
55
+
56
+ | Field | Value |
57
+ | --- | --- |
58
+ | Organization or user | `IcebergSnow` |
59
+ | Repository | `Patchwork` |
60
+ | Workflow filename | `check.yml` (filename only) |
61
+ | Environment name | Leave empty |
62
+ | Allowed actions | Enable direct **npm publish** |
63
+
64
+ Save, then open [GitHub Actions](https://github.com/IcebergSnow/Patchwork/actions/workflows/check.yml) and select **Run workflow → main**. This reruns the checks and publishes the pending version. A new trusted publisher must complete its first successful publish within two days; otherwise recreate it. No npm token secret is needed. Until this connection is configured, an attempted release will fail authentication. See [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/).
65
+
66
+ For later releases, explicitly bump the version in a development branch:
67
+
68
+ ```sh
69
+ npm version patch --workspace packages/cli --no-git-tag-version --ignore-scripts
70
+ ```
71
+
72
+ Commit the changed `packages/cli/package.json` and `package-lock.json` with the release changes, then merge to `main`. Use `minor` or `major` instead of `patch` when appropriate. The MCP server reads its version from the package manifest. Ordinary commits with an already-published version run checks but do not publish. Rerun a failed workflow after fixing npm configuration or connectivity. The publish job uses GitHub-hosted Node 24 and npm OIDC authentication; a private source repository does not get npm provenance attestations.
73
+
74
+ Teammates' existing connections are not automatically upgraded. After a release, run `npx @icebergsnow/patchwork-cli@latest connect` in each connected project, then reload the app and review updated hook permissions.
75
+
76
+ ## Manual publishing (fallback)
49
77
 
50
78
  Confirm ownership of the npm scope `@icebergsnow`. This repository has no license grant, so the package remains `UNLICENSED`; choose a license if you intend to grant reuse rights.
51
79
 
@@ -55,10 +83,10 @@ After checks pass on Windows and macOS:
55
83
  npm pack --workspace packages/cli --dry-run
56
84
  npm login
57
85
  npm publish --workspace packages/cli --access public
58
- npm view @icebergsnow/patchwork-cli@0.2.0 version
59
- npx @icebergsnow/patchwork-cli@0.2.0 --help
86
+ npm view @icebergsnow/patchwork-cli@0.2.1 version
87
+ npx @icebergsnow/patchwork-cli@0.2.1 --help
60
88
  ```
61
89
 
62
- The package contains its executable, runtime modules, README and manifest, with no installation scripts. Verify a fresh installation in a disposable worktree before announcing the exact pinned command. Publishing is a separate maintainer action; committing or pushing this repository does not publish it.
90
+ The package contains its executable, runtime modules, README and manifest, with no installation scripts. Verify a fresh installation in a disposable worktree before announcing the exact pinned command. The automated workflow skips versions already published manually.
63
91
 
64
92
  Routine tool hooks check at eligible checkpoints, at most once every 30 seconds. A shared local cache coalesces chats on the same computer/account, uses incremental GitHub reads, and reconciles every 15 minutes at a checkpoint. Each chat still has its own cursor and identity. Cached board text stays under `~/.patchwork/github/`; private repository content should be treated as private local data. See the root README for the API benchmark and recovery limits.
package/bin/patchwork.js CHANGED
@@ -20,7 +20,7 @@ Disconnect without --agent disables the selected app (both apps if omitted).
20
20
  Use status --repo and disconnect --repo --agent to retire a lost chat remotely.
21
21
  Use connect --client APP --agent CHAT_ID to explicitly resume a local chat.
22
22
  GitHub credentials remain with gh. No Cloudflare account or pairing code.
23
- The npm package must be published before the documented npx command works.
23
+ To update, rerun connect with the chosen CLI version, then reload your app.
24
24
  `;
25
25
  async function ask(question) {
26
26
  if (!process.stdin.isTTY)
@@ -122,12 +122,14 @@ try {
122
122
  if (raw.length > 1_000_000) throw new Error("Hook input too large.");
123
123
  }
124
124
  const input = JSON.parse(raw);
125
- // Never persist or transmit prompts, transcripts, tool arguments or output.
125
+ // The transcript path only distinguishes saved Codex chats from ephemeral
126
+ // helpers. Never read/persist/upload transcripts, prompts or tool output.
126
127
  console.log(
127
128
  JSON.stringify(
128
129
  await new Runtime(connection).hook({
129
130
  session_id: input.session_id,
130
131
  hook_event_name: input.hook_event_name,
132
+ transcript_path: input.transcript_path,
131
133
  }),
132
134
  ),
133
135
  );
package/lib/connect.js CHANGED
@@ -103,8 +103,12 @@ export async function connect({
103
103
  const file = connectionFile(root, client);
104
104
  return locked(file, async () => {
105
105
  let existing = await readJSON(file);
106
+ // The disable marker also protects an in-progress installation. Retrying
107
+ // that installation must not reactivate remotely retired chats. Preserve
108
+ // explicit project-reconnect intent across an interrupted install instead.
106
109
  const resuming =
107
- !!existing && (!existing.enabled || !!(await read(disabledFile(file))));
110
+ !!existing &&
111
+ (!existing.enabled || (existing.installing && existing.resumeChats));
108
112
  if (
109
113
  agent &&
110
114
  !Object.values((await readJSON(stateFile(file)))?.chats || {}).some(
@@ -183,6 +187,7 @@ export async function connect({
183
187
  version: 1,
184
188
  enabled: true,
185
189
  installing: true,
190
+ resumeChats: !!resuming,
186
191
  previousServer,
187
192
  previousHooks,
188
193
  root,
@@ -217,10 +222,6 @@ export async function connect({
217
222
  await checkUntracked(root, files.hooks);
218
223
  if (nextConfig !== before.config) await write(files.config, nextConfig);
219
224
  if (nextHooks !== before.hooks) await write(files.hooks, nextHooks);
220
- delete c.previousServer;
221
- delete c.previousHooks;
222
- c.installing = false;
223
- await writeJSON(file, c);
224
225
  if (resuming || agent)
225
226
  await locked(stateFile(file), async () => {
226
227
  const sessions = await readJSON(stateFile(file));
@@ -246,6 +247,11 @@ export async function connect({
246
247
  if (e.code !== "ENOENT") throw e;
247
248
  });
248
249
  });
250
+ delete c.previousServer;
251
+ delete c.previousHooks;
252
+ delete c.resumeChats;
253
+ c.installing = false;
254
+ await writeJSON(file, c);
249
255
  await unlink(disabledFile(file)).catch((e) => {
250
256
  if (e.code !== "ENOENT") throw e;
251
257
  });
@@ -39,6 +39,7 @@ export function hookConfiguration(entry, file) {
39
39
  input: {
40
40
  session_id: "${session_id}",
41
41
  hook_event_name: "${hook_event_name}",
42
+ transcript_path: "${transcript_path}",
42
43
  },
43
44
  },
44
45
  ],
package/lib/mcp.js CHANGED
@@ -2,9 +2,12 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { z } from "zod";
4
4
  import { Runtime } from "./runtime.js";
5
+ import { readFileSync } from "node:fs";
6
+
7
+ const { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
5
8
 
6
9
  export function server(runtime) {
7
- const mcp = new McpServer({ name: "patchwork", version: "0.2.0" });
10
+ const mcp = new McpServer({ name: "patchwork", version });
8
11
  const key = z
9
12
  .string()
10
13
  .uuid()
@@ -48,6 +51,7 @@ export function server(runtime) {
48
51
  "UserPromptSubmit",
49
52
  "PostToolUse",
50
53
  ]),
54
+ transcript_path: z.string().max(32768).nullable().optional(),
51
55
  })
52
56
  .strict(),
53
57
  );
package/lib/runtime.js CHANGED
@@ -130,6 +130,24 @@ export class Runtime {
130
130
  )
131
131
  )
132
132
  throw new Error("Invalid hook session metadata.");
133
+ if ((await guard(this.file)).client === "codex") {
134
+ // Codex also runs hooks in ephemeral app helpers (e.g. composer
135
+ // suggestions). Only saved conversations belong on the board. Check
136
+ // native metadata before creating an identity, a lock, or any API calls;
137
+ // never read, store, or upload the transcript itself or its path.
138
+ if (input.transcript_path === null || input.transcript_path === "")
139
+ return {};
140
+ if (
141
+ typeof input.transcript_path !== "string" ||
142
+ !input.transcript_path.trim() ||
143
+ input.transcript_path.length > 32768 ||
144
+ input.transcript_path.includes("${") ||
145
+ input.transcript_path.includes("\0")
146
+ )
147
+ throw new Error(
148
+ "Codex transcript metadata is unavailable. Rerun connect with the current CLI, then restart/reload Codex and approve the updated hooks.",
149
+ );
150
+ }
133
151
  return locked(stateFile(this.file), async () => {
134
152
  const c = await guard(this.file),
135
153
  now = this.clock();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icebergsnow/patchwork-cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A GitHub bulletin board with automatic hooks for Codex and Claude Code",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",