procoder-cli 0.1.1 → 0.1.3

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/CONTRIBUTORS.md CHANGED
@@ -11,6 +11,24 @@ Maintainer notes for this template repository.
11
11
 
12
12
  ## Local development
13
13
 
14
+ Common local commands:
15
+
16
+ ```bash
17
+ make fmt
18
+ make test
19
+ make vet
20
+ make lint
21
+ make check
22
+ make build
23
+ make build-helper
24
+ make build-all
25
+ make install-local
26
+ ```
27
+
28
+ `make build-all` produces the release binaries for the host CLI targets plus the packaged helper asset `procoder-return_linux_amd64`.
29
+
30
+ Quick local smoke path:
31
+
14
32
  ```bash
15
33
  make check
16
34
  make build
package/Makefile CHANGED
@@ -12,7 +12,8 @@ HELPER_CMD_PATH ?= ./cmd/$(HELPER_NAME)
12
12
  DIST_DIR ?= dist
13
13
  BIN_PATH ?= $(DIST_DIR)/$(BIN_NAME)
14
14
  HELPER_PATH ?= $(DIST_DIR)/$(HELPER_ASSET)
15
- LDFLAGS ?= -s -w
15
+ VERSION ?= $(shell node -p "require('./package.json').version" 2>/dev/null)
16
+ LDFLAGS ?= -s -w -X github.com/amxv/procoder/internal/buildinfo.Version=$(if $(VERSION),$(VERSION),dev)
16
17
 
17
18
  .PHONY: help fmt test vet lint check build build-helper build-all install-local clean release-tag
18
19
 
package/README.md CHANGED
@@ -1,125 +1,145 @@
1
1
  # procoder
2
2
 
3
- `procoder` is a Go CLI for an offline Git exchange workflow between:
3
+ `procoder` is a CLI for getting real Git commits back from ChatGPT's coding sandbox.
4
4
 
5
- - a local developer repository
6
- - a locked-down ChatGPT coding container
5
+ ChatGPT 5.4 Pro is the best coding model, but it is not available in Codex. `procoder` solves that specific problem: your repository stays local, ChatGPT works inside its locked-down sandbox, and you still get real commits back through a clean upload/download workflow powered by `git bundle`.
7
6
 
8
- The default round trip is:
7
+ The workflow is simple:
9
8
 
10
- 1. Run `procoder prepare` in a clean local repository.
11
- 2. Upload `./procoder-task-<exchange-id>.zip` to ChatGPT.
12
- 3. Work inside the exported repo, commit on the prepared task branch, and run `./procoder-return`.
13
- 4. Download `./procoder-return-<exchange-id>.zip`.
14
- 5. Run `procoder apply <return-package.zip>` in the source repository.
9
+ 1. You run `procoder prepare` and upload the generated zip.
10
+ 2. ChatGPT works inside that package and gives you an optimized incremental Git bundle as a zip file.
11
+ 3. You run `procoder apply`, which applies the commit ChatGPT made.
12
+
13
+ It is Git-native, not a patch-copying workaround.
15
14
 
16
15
  ## Install
17
16
 
18
- Global install via npm:
17
+ Install globally with npm:
19
18
 
20
19
  ```bash
21
20
  npm i -g procoder-cli
22
21
  procoder --help
23
22
  ```
24
23
 
25
- The installer fetches two release assets when available:
24
+ ## Quick Start
25
+
26
+ ```bash
27
+ procoder prepare
28
+ ```
26
29
 
27
- - the host `procoder` binary
28
- - the packaged `linux/amd64` `procoder-return` helper used inside prepared task packages
30
+ Upload the generated task package to ChatGPT and give it a prompt like:
29
31
 
30
- If those release assets are unavailable, `scripts/postinstall.js` falls back to local Go builds for both binaries.
32
+ ```text
33
+ Make the requested changes in this repository.
31
34
 
32
- ## Workflow
35
+ Commit them on the prepared branch.
33
36
 
34
- ### `procoder prepare`
37
+ Then run ./procoder-return and give me the sandbox path to the generated zip.
38
+ ```
35
39
 
36
- Run `procoder prepare` from the root of a clean Git worktree:
40
+ After downloading the return package locally:
37
41
 
38
42
  ```bash
39
- procoder prepare
43
+ procoder apply procoder-return-<exchange-id>.zip
40
44
  ```
41
45
 
42
- `prepare` does the following:
46
+ If you want to inspect the import first:
43
47
 
44
- - validates that the repo is clean and has no untracked files, submodules, or Git LFS usage
45
- - creates a local task branch at `refs/heads/procoder/<exchange-id>/task`
46
- - writes the local exchange record to `.git/procoder/exchanges/<exchange-id>/exchange.json`
47
- - builds a sanitized export repo that includes local heads and tags for read-only context
48
- - injects the `procoder-return` helper at the exported repo root
49
- - writes `./procoder-task-<exchange-id>.zip` in the source repo root
48
+ ```bash
49
+ procoder apply procoder-return-<exchange-id>.zip --dry-run
50
+ ```
50
51
 
51
- The current checkout is left unchanged. On success the command prints the task branch ref and the absolute task package path.
52
+ ## Why `git bundle` is the right primitive
52
53
 
53
- ### `./procoder-return`
54
+ `git bundle` is what makes this workflow feel native instead of fragile.
54
55
 
55
- After ChatGPT has made one or more commits inside the exported repo, run the helper from that prepared task package:
56
+ It lets `procoder` move Git commits, trees, blobs, and refs as a portable file, without requiring a network connection or a Git server. That matters because the ChatGPT sandbox can work with Git locally, but it cannot push to your remote.
56
57
 
57
- ```bash
58
- ./procoder-return
58
+ Using an incremental bundle also keeps the return package small. The sandbox sends back only the objects created after `prepare`, not the whole repository again.
59
+
60
+ ## Workflow Diagram
61
+
62
+ ```mermaid
63
+ flowchart TD
64
+ A[Local Git repo] -->|procoder prepare| B[Task zip]
65
+ B -->|Upload to ChatGPT| C[ChatGPT codes, commits on the prepared branch,<br/>and runs the included ./procoder-return binary]
66
+ C -->|Download return zip and run procoder apply| D[Local task branch updated]
59
67
  ```
60
68
 
61
- The helper:
69
+ ## The Three Tools
70
+
71
+ ### `procoder prepare`
62
72
 
63
- - verifies that it is running inside a prepared task package
64
- - rejects dirty worktrees, tag changes, and branch changes outside `refs/heads/procoder/<exchange-id>/*`
65
- - bundles only task-family refs that descend from the prepared base commit
66
- - writes `./procoder-return-<exchange-id>.zip` at the repo root
73
+ Runs locally in your clean repository.
67
74
 
68
- On success it prints the absolute zip path and a `sandbox:` hint that can be pasted back to the local user.
75
+ It creates a dedicated task branch, builds a sanitized export of your repo, injects the `procoder-return` helper, and writes a task zip you can upload to ChatGPT.
76
+
77
+ ### `./procoder-return`
78
+
79
+ Runs inside the exported repository in the ChatGPT sandbox.
80
+
81
+ It is a simple agent-friendly binary with no arguments or subcommands. After making one or more commits, the agent runs `./procoder-return`, and then sends back the path to the generated zip file.
69
82
 
70
83
  ### `procoder apply`
71
84
 
72
- Apply the returned work from the original source repo:
85
+ Runs locally in your original repository.
73
86
 
74
- ```bash
75
- procoder apply procoder-return-<exchange-id>.zip
76
- ```
87
+ It verifies the returned bundle, checks whether the target refs are still safe to update, and then imports the returned commits into your repo.
77
88
 
78
- Supported flags:
89
+ ## How it works under the hood
79
90
 
80
- - `--dry-run`: verify the return package and print the ref update plan without mutating refs
81
- - `--namespace <prefix>`: import returned refs under `refs/heads/<prefix>/<exchange-id>/...`
82
- - `--checkout`: check out the updated default task branch after a successful apply
91
+ At a high level, `procoder` works because it ships Git history into the sandbox and ships only new Git history back out.
83
92
 
84
- Examples:
93
+ ### 1. `prepare` makes an offline-capable repo
85
94
 
86
- ```bash
87
- procoder apply procoder-return-<exchange-id>.zip
88
- procoder apply procoder-return-<exchange-id>.zip --dry-run
89
- procoder apply procoder-return-<exchange-id>.zip --namespace procoder-import
90
- procoder apply procoder-return-<exchange-id>.zip --checkout
91
- ```
95
+ The task package is a sanitized Git repository, not just a folder of files.
92
96
 
93
- Default apply behavior is "update when safe, otherwise fail." If the prepared task branch moved locally, `procoder apply` fails with a `BRANCH_MOVED` error and suggests retrying with `--namespace`.
97
+ That export:
94
98
 
95
- ## Command Summary
99
+ - includes your tracked files
100
+ - includes local branches and tags for read-only context
101
+ - creates a dedicated task branch for the exchange
102
+ - strips remotes, credentials, hooks, reflogs, and other local-only Git state
103
+ - includes a Linux `procoder-return` helper binary at the repo root
96
104
 
97
- ```bash
98
- procoder --help
99
- procoder prepare
100
- procoder apply <return-package.zip>
101
- procoder apply <return-package.zip> --dry-run
102
- procoder apply <return-package.zip> --namespace <prefix>
103
- procoder apply <return-package.zip> --checkout
104
- procoder version
105
- ```
105
+ So when ChatGPT opens the zip, it already has a working repo with real history and a branch ready to commit on. No internet connection is required for that part because the relevant Git state is already inside the upload.
106
106
 
107
- ## Development
107
+ ### 2. ChatGPT returns only the incremental update
108
108
 
109
- Common local commands:
109
+ After ChatGPT commits its changes, `./procoder-return` does not re-export the entire repository.
110
110
 
111
- ```bash
112
- make fmt
113
- make test
114
- make vet
115
- make lint
116
- make check
117
- make build
118
- make build-helper
119
- make build-all
120
- make install-local
121
- ```
111
+ Instead, it creates:
112
+
113
+ - `procoder-return.json`
114
+ a small manifest describing the returned refs and commit IDs
115
+ - `procoder-return.bundle`
116
+ an incremental Git bundle containing only the new objects needed to move those refs forward
117
+
118
+ That is the key idea. The return package is a portable Git layer that can be applied on top of the base commit prepared earlier.
119
+
120
+ ### 3. `apply` verifies before it updates anything
121
+
122
+ When you run `procoder apply`, the CLI verifies the bundle, imports it into a temporary namespace, checks that the imported refs match the manifest, and only then updates your local task branch when it is safe.
123
+
124
+ If the branch moved locally, or the return package tries to update something outside the allowed exchange branch family, `procoder` fails clearly instead of guessing.
125
+
126
+ That gives you the practical feeling of "ChatGPT committed to my repo", while still keeping the operation local and controlled.
127
+
128
+ ## Current V1 Constraints
129
+
130
+ The happy path is intentionally narrow:
131
+
132
+ - run `procoder prepare` from a clean repo
133
+ - no Git LFS support
134
+ - no submodule support
135
+ - returned work must stay inside the prepared exchange branch family
136
+ - tag changes are not part of the V1 return flow
137
+
138
+ Those constraints keep the round trip predictable and let `apply` behave like "update when safe, otherwise fail."
122
139
 
123
- `make build-all` produces the release binaries for the host CLI targets plus the packaged helper asset `procoder-return_linux_amd64`.
140
+ ## Docs
124
141
 
125
- See [AGENTS.md](AGENTS.md) and [CONTRIBUTORS.md](CONTRIBUTORS.md) for implementation and release details.
142
+ - [How It Works](docs/how-it-works.md)
143
+ - [Command Reference](docs/commands.md)
144
+ - [Agent Guidance](AGENTS.md)
145
+ - [Maintainer Notes](CONTRIBUTORS.md)
@@ -5,12 +5,13 @@ import (
5
5
  "io"
6
6
  "os"
7
7
 
8
+ "github.com/amxv/procoder/internal/buildinfo"
8
9
  "github.com/amxv/procoder/internal/errs"
9
10
  "github.com/amxv/procoder/internal/output"
10
11
  "github.com/amxv/procoder/internal/returnpkg"
11
12
  )
12
13
 
13
- var version = "dev"
14
+ var version = buildinfo.CurrentVersion()
14
15
  var runReturn = returnpkg.Run
15
16
 
16
17
  func main() {
@@ -6,13 +6,14 @@ import (
6
6
  "strings"
7
7
 
8
8
  "github.com/amxv/procoder/internal/apply"
9
+ "github.com/amxv/procoder/internal/buildinfo"
9
10
  "github.com/amxv/procoder/internal/errs"
10
11
  "github.com/amxv/procoder/internal/prepare"
11
12
  )
12
13
 
13
14
  const commandName = "procoder"
14
15
 
15
- var version = "dev"
16
+ var version = buildinfo.CurrentVersion()
16
17
  var runPrepare = prepare.Run
17
18
  var runApplyDryRun = apply.RunDryRun
18
19
  var runApply = apply.Run
@@ -24,11 +25,12 @@ func Run(args []string, stdout, stderr io.Writer) error {
24
25
  printRootHelp(stdout)
25
26
  return nil
26
27
  }
27
-
28
- switch args[0] {
29
- case "version":
28
+ if len(args) == 1 && isVersionArg(args[0]) {
30
29
  _, _ = fmt.Fprintf(stdout, "%s %s\n", commandName, version)
31
30
  return nil
31
+ }
32
+
33
+ switch args[0] {
32
34
  case "prepare":
33
35
  if len(args) > 1 && isHelpArg(args[1]) {
34
36
  printPrepareHelp(stdout)
@@ -172,22 +174,26 @@ func isHelpArg(v string) bool {
172
174
  }
173
175
  }
174
176
 
177
+ func isVersionArg(v string) bool {
178
+ return v == "--version"
179
+ }
180
+
175
181
  func printRootHelp(w io.Writer) {
176
182
  writeLines(w,
177
183
  "procoder",
178
184
  "",
179
185
  "Usage:",
186
+ " procoder [--version]",
180
187
  " procoder <command> [arguments]",
181
188
  "",
182
189
  "Commands:",
183
190
  " prepare create a task package",
184
191
  " apply <return-package.zip> apply a return package",
185
- " version print CLI version",
186
192
  "",
187
193
  "Examples:",
194
+ " procoder --version",
188
195
  " procoder prepare",
189
196
  " procoder apply procoder-return-<exchange-id>.zip --dry-run",
190
- " procoder version",
191
197
  )
192
198
  }
193
199
 
@@ -31,7 +31,7 @@ func TestRunVersion(t *testing.T) {
31
31
  var out bytes.Buffer
32
32
  var errBuf bytes.Buffer
33
33
 
34
- err := Run([]string{"version"}, &out, &errBuf)
34
+ err := Run([]string{"--version"}, &out, &errBuf)
35
35
  if err != nil {
36
36
  t.Fatalf("Run returned error: %v", err)
37
37
  }
@@ -41,6 +41,27 @@ func TestRunVersion(t *testing.T) {
41
41
  }
42
42
  }
43
43
 
44
+ func TestRunVersionSubcommandUnknown(t *testing.T) {
45
+ var out bytes.Buffer
46
+ var errBuf bytes.Buffer
47
+
48
+ err := Run([]string{"version"}, &out, &errBuf)
49
+ if err == nil {
50
+ t.Fatal("expected error for removed version subcommand")
51
+ }
52
+
53
+ typed, ok := errs.As(err)
54
+ if !ok {
55
+ t.Fatalf("expected typed error, got %T", err)
56
+ }
57
+ if typed.Code != errs.CodeUnknownCommand {
58
+ t.Fatalf("unexpected error code: %s", typed.Code)
59
+ }
60
+ if !strings.Contains(typed.Message, "unknown command") {
61
+ t.Fatalf("expected unknown command message, got %q", typed.Message)
62
+ }
63
+ }
64
+
44
65
  func TestRunApplyHelpTerminology(t *testing.T) {
45
66
  var out bytes.Buffer
46
67
  var errBuf bytes.Buffer
@@ -0,0 +1,16 @@
1
+ package buildinfo
2
+
3
+ import "strings"
4
+
5
+ const defaultVersion = "dev"
6
+
7
+ // Version is overridden at build time via linker flags.
8
+ var Version = defaultVersion
9
+
10
+ func CurrentVersion() string {
11
+ trimmed := strings.TrimSpace(Version)
12
+ if trimmed == "" {
13
+ return defaultVersion
14
+ }
15
+ return trimmed
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "procoder-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Offline Git exchange CLI for local repositories and locked-down ChatGPT coding containers",
5
5
  "license": "MIT",
6
6
  "author": "amxv",
@@ -60,6 +60,11 @@ function buildReleaseAssetURL({ repoOwner, repoName, version, assetName }) {
60
60
  return `https://github.com/${repoOwner}/${repoName}/releases/download/v${version}/${assetName}`;
61
61
  }
62
62
 
63
+ function buildLdflags(version) {
64
+ const resolvedVersion = `${version || ""}`.trim() || "dev";
65
+ return `-s -w -X github.com/amxv/procoder/internal/buildinfo.Version=${resolvedVersion}`;
66
+ }
67
+
63
68
  function buildInstallPlan({
64
69
  pkg,
65
70
  cliName,
@@ -167,6 +172,7 @@ function fallbackBuildOrExit(plan) {
167
172
  }
168
173
 
169
174
  function buildFallbackBuilds(plan) {
175
+ const ldflags = buildLdflags(plan.version);
170
176
  return [
171
177
  {
172
178
  destination: plan.cli.destination,
@@ -174,7 +180,7 @@ function buildFallbackBuilds(plan) {
174
180
  args: [
175
181
  "build",
176
182
  "-trimpath",
177
- '-ldflags=-s -w',
183
+ `-ldflags=${ldflags}`,
178
184
  "-o",
179
185
  plan.cli.destination,
180
186
  `./cmd/${plan.cli.name}`
@@ -190,7 +196,7 @@ function buildFallbackBuilds(plan) {
190
196
  args: [
191
197
  "build",
192
198
  "-trimpath",
193
- '-ldflags=-s -w',
199
+ `-ldflags=${ldflags}`,
194
200
  "-o",
195
201
  plan.helper.destination,
196
202
  `./cmd/${plan.helper.name}`
@@ -245,6 +251,7 @@ function downloadToFile(url, destinationPath) {
245
251
 
246
252
  module.exports = {
247
253
  buildFallbackBuilds,
254
+ buildLdflags,
248
255
  buildInstallPlan,
249
256
  buildReleaseAssetName,
250
257
  buildReleaseAssetURL,