do-again 0.1.0__tar.gz → 0.2.0__tar.gz

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.
Files changed (39) hide show
  1. do_again-0.2.0/.github/dependabot.yml +11 -0
  2. do_again-0.2.0/.github/workflows/ci.yml +60 -0
  3. do_again-0.2.0/.github/workflows/npm-release.yml +50 -0
  4. do_again-0.2.0/.github/workflows/release.yml +55 -0
  5. do_again-0.2.0/.npmignore +6 -0
  6. {do_again-0.1.0 → do_again-0.2.0}/PKG-INFO +69 -14
  7. do_again-0.2.0/README.md +131 -0
  8. do_again-0.2.0/bin/do-again.js +94 -0
  9. do_again-0.2.0/npm-tests/launcher.test.js +30 -0
  10. do_again-0.2.0/package-lock.json +19 -0
  11. do_again-0.2.0/package.json +44 -0
  12. {do_again-0.1.0 → do_again-0.2.0}/pyproject.toml +1 -1
  13. do_again-0.2.0/src/do_again/cli.py +137 -0
  14. do_again-0.2.0/src/do_again/service/__init__.py +3 -0
  15. do_again-0.2.0/src/do_again/service/runtime.py +372 -0
  16. do_again-0.2.0/templates/policy.json +137 -0
  17. do_again-0.2.0/tests/test_service.py +62 -0
  18. do_again-0.2.0/tools/check_release_version.py +34 -0
  19. do_again-0.1.0/.github/workflows/ci.yml +0 -23
  20. do_again-0.1.0/.github/workflows/release.yml +0 -40
  21. do_again-0.1.0/README.md +0 -76
  22. do_again-0.1.0/src/do_again/cli.py +0 -63
  23. do_again-0.1.0/src/do_again/service/__init__.py +0 -0
  24. {do_again-0.1.0 → do_again-0.2.0}/.gitignore +0 -0
  25. {do_again-0.1.0 → do_again-0.2.0}/LICENSE +0 -0
  26. {do_again-0.1.0 → do_again-0.2.0}/src/do_again/__init__.py +0 -0
  27. {do_again-0.1.0 → do_again-0.2.0}/src/do_again/core/__init__.py +0 -0
  28. {do_again-0.1.0 → do_again-0.2.0}/src/do_again/core/agent.py +0 -0
  29. {do_again-0.1.0 → do_again-0.2.0}/src/do_again/core/executor.py +0 -0
  30. {do_again-0.1.0 → do_again-0.2.0}/src/do_again/core/schema.py +0 -0
  31. /do_again-0.1.0/templates/policy.json → /do_again-0.2.0/src/do_again/default_policy.json +0 -0
  32. {do_again-0.1.0 → do_again-0.2.0}/src/do_again/platforms/__init__.py +0 -0
  33. {do_again-0.1.0 → do_again-0.2.0}/src/do_again/platforms/base.py +0 -0
  34. {do_again-0.1.0 → do_again-0.2.0}/src/do_again/platforms/detect.py +0 -0
  35. {do_again-0.1.0 → do_again-0.2.0}/src/do_again/platforms/linux.py +0 -0
  36. {do_again-0.1.0 → do_again-0.2.0}/src/do_again/platforms/macos.py +0 -0
  37. {do_again-0.1.0 → do_again-0.2.0}/src/do_again/platforms/windows.py +0 -0
  38. {do_again-0.1.0 → do_again-0.2.0}/tests/test_executor.py +0 -0
  39. {do_again-0.1.0 → do_again-0.2.0}/tests/test_platforms.py +0 -0
@@ -0,0 +1,11 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+
8
+ - package-ecosystem: "pip"
9
+ directory: "/"
10
+ schedule:
11
+ interval: "weekly"
@@ -0,0 +1,60 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ci-${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ version-sync:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
21
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
22
+ with:
23
+ python-version: "3.13"
24
+ - run: python tools/check_release_version.py
25
+
26
+ test:
27
+ strategy:
28
+ fail-fast: false
29
+ matrix:
30
+ os: [ubuntu-latest, macos-latest, windows-latest]
31
+ python-version: ["3.11", "3.12", "3.13"]
32
+ runs-on: ${{ matrix.os }}
33
+ steps:
34
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
35
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
36
+ with:
37
+ python-version: ${{ matrix.python-version }}
38
+ - run: python -m pip install .
39
+ - run: python -m pip check
40
+ - run: python -m unittest discover -s tests -v
41
+ - run: do-again doctor
42
+
43
+ npm-package:
44
+ strategy:
45
+ fail-fast: false
46
+ matrix:
47
+ os: [ubuntu-latest, macos-latest, windows-latest]
48
+ runs-on: ${{ matrix.os }}
49
+ steps:
50
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
51
+ - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
52
+ with:
53
+ node-version: "24"
54
+ package-manager-cache: false
55
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
56
+ with:
57
+ python-version: "3.13"
58
+ - run: npm ci
59
+ - run: npm test
60
+ - run: npm pack --dry-run --ignore-scripts
@@ -0,0 +1,50 @@
1
+ name: NPM Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ publish:
13
+ runs-on: ubuntu-latest
14
+ environment:
15
+ name: npm
16
+ url: https://www.npmjs.com/package/do-again
17
+ permissions:
18
+ contents: read
19
+ id-token: write
20
+ steps:
21
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
22
+ - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
23
+ with:
24
+ node-version: "24"
25
+ registry-url: "https://registry.npmjs.org"
26
+ package-manager-cache: false
27
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
28
+ with:
29
+ python-version: "3.13"
30
+ - name: Verify release versions
31
+ run: python tools/check_release_version.py --tag "$GITHUB_REF_NAME"
32
+ - run: npm ci
33
+ - run: npm test
34
+ - run: npm publish
35
+ - name: Wait for npm registry availability
36
+ shell: bash
37
+ run: |
38
+ version=$(node -p "require('./package.json').version")
39
+ for attempt in $(seq 1 120); do
40
+ published=$(npm view "do-again@$version" version 2>/dev/null || true)
41
+ latest=$(npm view do-again dist-tags.latest 2>/dev/null || true)
42
+ if [ "$published" = "$version" ] && [ "$latest" = "$version" ]; then
43
+ echo "do-again@$version is available from the public npm registry"
44
+ exit 0
45
+ fi
46
+ echo "Waiting for npm registry propagation ($attempt/120)..."
47
+ sleep 15
48
+ done
49
+ echo "Timed out waiting for do-again@$version to become publicly available" >&2
50
+ exit 1
@@ -0,0 +1,55 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
16
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
17
+ with:
18
+ python-version: "3.13"
19
+ - name: Verify release versions
20
+ run: python tools/check_release_version.py --tag "$GITHUB_REF_NAME"
21
+ - run: python -m pip install --upgrade build twine
22
+ - run: python -m build
23
+ - run: python -m twine check dist/*
24
+ - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
25
+ with:
26
+ name: python-package-distributions
27
+ path: dist/
28
+
29
+ publish:
30
+ needs: build
31
+ runs-on: ubuntu-latest
32
+ environment:
33
+ name: pypi
34
+ url: https://pypi.org/p/do-again
35
+ permissions:
36
+ id-token: write
37
+ steps:
38
+ - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
39
+ with:
40
+ name: python-package-distributions
41
+ path: dist/
42
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
43
+
44
+ github-release:
45
+ needs: publish
46
+ runs-on: ubuntu-latest
47
+ permissions:
48
+ contents: write
49
+ steps:
50
+ - name: Create GitHub release
51
+ env:
52
+ GH_TOKEN: ${{ github.token }}
53
+ run: |
54
+ gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 ||
55
+ gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --verify-tag --generate-notes --title "Do Again $GITHUB_REF_NAME"
@@ -0,0 +1,6 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .DS_Store
4
+ .venv/
5
+ dist/
6
+ build/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: do-again
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Policy-controlled local execution for agent-driven development workflows
5
5
  Project-URL: Homepage, https://github.com/Tran-Steven/do-again
6
6
  Project-URL: Repository, https://github.com/Tran-Steven/do-again
@@ -35,69 +35,124 @@ Description-Content-Type: text/markdown
35
35
 
36
36
  # Do Again
37
37
 
38
- [![CI](https://github.com/Tran-Steven/do-again/actions/workflows/ci.yml/badge.svg)](https://github.com/Tran-Steven/do-again/actions/workflows/ci.yml)
39
- [![PyPI](https://img.shields.io/pypi/v/do-again.svg)](https://pypi.org/project/do-again/)
40
- [![Python](https://img.shields.io/pypi/pyversions/do-again.svg)](https://pypi.org/project/do-again/)
41
- [![License](https://img.shields.io/github/license/Tran-Steven/do-again.svg)](LICENSE)
38
+ [![CI](https://github.com/Tran-Steven/do-again/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Tran-Steven/do-again/actions/workflows/ci.yml)
39
+ [![PyPI](https://img.shields.io/pypi/v/do-again.svg?cacheSeconds=300)](https://pypi.org/project/do-again/)
40
+ [![npm](https://img.shields.io/npm/v/do-again.svg?cacheSeconds=300)](https://www.npmjs.com/package/do-again)
41
+ [![Python](https://img.shields.io/pypi/pyversions/do-again.svg?cacheSeconds=300)](https://pypi.org/project/do-again/)
42
+ [![License](https://img.shields.io/github/license/Tran-Steven/do-again.svg?cacheSeconds=300)](LICENSE)
43
+
44
+ Do Again gives AI coding agents a policy-controlled way to execute work on a local development machine, return auditable receipts and artifacts, and keep iterating when another pass is needed.
45
+
46
+ ## How it works
47
+
48
+ ```text
49
+ agent
50
+ |
51
+ | request
52
+ v
53
+ operator-control branch
54
+ |
55
+ v
56
+ Do Again -> policy validation -> local command / file / test / git action
57
+ ^ |
58
+ | |
59
+ +----------- receipt / artifact -----------+
60
+ ```
42
61
 
43
- Do Again is a policy-controlled local execution layer for agent-driven development workflows. It lets an agent submit scoped work to a developer machine, receive auditable receipts and artifacts, inspect the result, and continue working when another pass is needed.
62
+ The agent can inspect the result, decide what still needs work, and submit another scoped action instead of stopping at the first attempt.
44
63
 
45
64
  ## Install
46
65
 
66
+ ### Python / PyPI
67
+
47
68
  ```bash
48
69
  pip install do-again
49
70
  ```
50
71
 
51
- The installed command is:
72
+ ### Node / npm
73
+
74
+ ```bash
75
+ npm install -g do-again
76
+ ```
77
+
78
+ or run it without a global install:
79
+
80
+ ```bash
81
+ npx do-again doctor
82
+ ```
83
+
84
+ Both distributions expose the same command:
52
85
 
53
86
  ```bash
54
87
  do-again
55
88
  ```
56
89
 
90
+ The npm package ships the same Python runtime from this repository behind a small Node launcher. It requires Node.js 18+ and Python 3.11+.
91
+
57
92
  ## Quick start
58
93
 
59
94
  ```bash
60
95
  do-again doctor
61
96
  do-again init
97
+ do-again install
62
98
  do-again status
63
99
  ```
64
100
 
101
+ ## Service lifecycle
102
+
103
+ On macOS, Do Again can install a per-repository launchd agent:
104
+
105
+ do-again install
106
+ do-again status
107
+ do-again restart
108
+ do-again stop
109
+ do-again uninstall
110
+
111
+ For attended or cross-platform use, run the agent in the foreground:
112
+
113
+ do-again run
114
+ do-again run --once
115
+
116
+ Each repository gets an isolated runtime, control worktree, state directory, policy copy, and service label under ~/.do_again. The installed service runs from a copied runtime so installs made through either PyPI or npm remain stable after the invoking shell exits.
117
+
65
118
  ## Why Do Again
66
119
 
67
120
  - Explicit policy-based command permissions
68
121
  - GitHub-backed request and receipt transport
69
- - Persistent local background execution architecture
70
122
  - Project-scoped filesystem and command access
71
123
  - Deterministic audit trail and recovery
72
124
  - No agent API keys embedded in the project
73
125
  - A shared platform interface for macOS, Linux, and Windows
126
+ - CI-tested on Python 3.11, 3.12, and 3.13 across all three platforms
74
127
 
75
128
  ## Platform status
76
129
 
77
130
  | Platform | Status | Service backend |
78
131
  | --- | --- | --- |
79
- | macOS | Core working | launchd |
80
- | Linux | Adapter scaffolded | systemd user service |
81
- | Windows | Adapter scaffolded | Windows service |
132
+ | macOS | Service lifecycle working | launchd |
133
+ | Linux | Foreground runner working; service adapter scaffolded | systemd user service |
134
+ | Windows | Foreground runner working; service adapter scaffolded | Windows service |
82
135
 
83
- Do Again is currently alpha software. macOS is the first active platform while Linux and Windows service integration are being completed behind the same interface.
136
+ Do Again is currently alpha software. The foreground runner works across the supported Python platforms; macOS is the first platform with an installable background service while Linux and Windows service integration are being completed behind the same interface.
84
137
 
85
138
  ## Development
86
139
 
87
140
  ```bash
88
141
  git clone https://github.com/Tran-Steven/do-again.git
89
142
  cd do-again
90
- python -m pip install -e .
143
+ python -m pip install .
91
144
  python -m unittest discover -s tests -v
92
145
  do-again doctor
93
146
  ```
94
147
 
95
148
  ## Release model
96
149
 
97
- Releases use semantic versioning. Pushing a version tag such as `v0.1.0` builds a wheel and source distribution, validates both artifacts, and publishes them to PyPI through Trusted Publishing.
150
+ Releases use semantic versioning. Pushing a version tag such as `v0.2.0` validates the shared release version, publishes the Python distribution to PyPI and the Node launcher to npm through Trusted Publishing, and creates the matching GitHub Release.
98
151
 
99
152
  PyPI distribution name: `do-again`
100
153
 
154
+ npm package name: `do-again`
155
+
101
156
  CLI command: `do-again`
102
157
 
103
158
  Python package: `do_again`
@@ -0,0 +1,131 @@
1
+ # Do Again
2
+
3
+ [![CI](https://github.com/Tran-Steven/do-again/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Tran-Steven/do-again/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/do-again.svg?cacheSeconds=300)](https://pypi.org/project/do-again/)
5
+ [![npm](https://img.shields.io/npm/v/do-again.svg?cacheSeconds=300)](https://www.npmjs.com/package/do-again)
6
+ [![Python](https://img.shields.io/pypi/pyversions/do-again.svg?cacheSeconds=300)](https://pypi.org/project/do-again/)
7
+ [![License](https://img.shields.io/github/license/Tran-Steven/do-again.svg?cacheSeconds=300)](LICENSE)
8
+
9
+ Do Again gives AI coding agents a policy-controlled way to execute work on a local development machine, return auditable receipts and artifacts, and keep iterating when another pass is needed.
10
+
11
+ ## How it works
12
+
13
+ ```text
14
+ agent
15
+ |
16
+ | request
17
+ v
18
+ operator-control branch
19
+ |
20
+ v
21
+ Do Again -> policy validation -> local command / file / test / git action
22
+ ^ |
23
+ | |
24
+ +----------- receipt / artifact -----------+
25
+ ```
26
+
27
+ The agent can inspect the result, decide what still needs work, and submit another scoped action instead of stopping at the first attempt.
28
+
29
+ ## Install
30
+
31
+ ### Python / PyPI
32
+
33
+ ```bash
34
+ pip install do-again
35
+ ```
36
+
37
+ ### Node / npm
38
+
39
+ ```bash
40
+ npm install -g do-again
41
+ ```
42
+
43
+ or run it without a global install:
44
+
45
+ ```bash
46
+ npx do-again doctor
47
+ ```
48
+
49
+ Both distributions expose the same command:
50
+
51
+ ```bash
52
+ do-again
53
+ ```
54
+
55
+ The npm package ships the same Python runtime from this repository behind a small Node launcher. It requires Node.js 18+ and Python 3.11+.
56
+
57
+ ## Quick start
58
+
59
+ ```bash
60
+ do-again doctor
61
+ do-again init
62
+ do-again install
63
+ do-again status
64
+ ```
65
+
66
+ ## Service lifecycle
67
+
68
+ On macOS, Do Again can install a per-repository launchd agent:
69
+
70
+ do-again install
71
+ do-again status
72
+ do-again restart
73
+ do-again stop
74
+ do-again uninstall
75
+
76
+ For attended or cross-platform use, run the agent in the foreground:
77
+
78
+ do-again run
79
+ do-again run --once
80
+
81
+ Each repository gets an isolated runtime, control worktree, state directory, policy copy, and service label under ~/.do_again. The installed service runs from a copied runtime so installs made through either PyPI or npm remain stable after the invoking shell exits.
82
+
83
+ ## Why Do Again
84
+
85
+ - Explicit policy-based command permissions
86
+ - GitHub-backed request and receipt transport
87
+ - Project-scoped filesystem and command access
88
+ - Deterministic audit trail and recovery
89
+ - No agent API keys embedded in the project
90
+ - A shared platform interface for macOS, Linux, and Windows
91
+ - CI-tested on Python 3.11, 3.12, and 3.13 across all three platforms
92
+
93
+ ## Platform status
94
+
95
+ | Platform | Status | Service backend |
96
+ | --- | --- | --- |
97
+ | macOS | Service lifecycle working | launchd |
98
+ | Linux | Foreground runner working; service adapter scaffolded | systemd user service |
99
+ | Windows | Foreground runner working; service adapter scaffolded | Windows service |
100
+
101
+ Do Again is currently alpha software. The foreground runner works across the supported Python platforms; macOS is the first platform with an installable background service while Linux and Windows service integration are being completed behind the same interface.
102
+
103
+ ## Development
104
+
105
+ ```bash
106
+ git clone https://github.com/Tran-Steven/do-again.git
107
+ cd do-again
108
+ python -m pip install .
109
+ python -m unittest discover -s tests -v
110
+ do-again doctor
111
+ ```
112
+
113
+ ## Release model
114
+
115
+ Releases use semantic versioning. Pushing a version tag such as `v0.2.0` validates the shared release version, publishes the Python distribution to PyPI and the Node launcher to npm through Trusted Publishing, and creates the matching GitHub Release.
116
+
117
+ PyPI distribution name: `do-again`
118
+
119
+ npm package name: `do-again`
120
+
121
+ CLI command: `do-again`
122
+
123
+ Python package: `do_again`
124
+
125
+ ## Security
126
+
127
+ Do Again is designed around explicit allowlists, scoped filesystem roots, bounded execution, and auditable request/receipt records. It should not be configured as unrestricted shell access.
128
+
129
+ ## License
130
+
131
+ MIT
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("node:fs");
5
+ const os = require("node:os");
6
+ const path = require("node:path");
7
+ const { spawnSync } = require("node:child_process");
8
+
9
+ const MIN_PYTHON = [3, 11];
10
+
11
+ function parsePythonVersion(output) {
12
+ const match = String(output || "").match(/Python\s+(\d+)\.(\d+)(?:\.(\d+))?/i);
13
+ if (!match) return null;
14
+ return [Number(match[1]), Number(match[2]), Number(match[3] || 0)];
15
+ }
16
+
17
+ function versionAtLeast(version, minimum = MIN_PYTHON) {
18
+ if (!version) return false;
19
+ if (version[0] !== minimum[0]) return version[0] > minimum[0];
20
+ return version[1] >= minimum[1];
21
+ }
22
+
23
+ function candidates() {
24
+ const configured = process.env.DO_AGAIN_PYTHON;
25
+ if (configured) return [{ command: configured, prefix: [] }];
26
+
27
+ if (process.platform === "win32") {
28
+ return [
29
+ { command: "py", prefix: ["-3"] },
30
+ { command: "python", prefix: [] },
31
+ { command: "python3", prefix: [] }
32
+ ];
33
+ }
34
+ return [
35
+ { command: "python3", prefix: [] },
36
+ { command: "python", prefix: [] }
37
+ ];
38
+ }
39
+
40
+ function findPython() {
41
+ for (const candidate of candidates()) {
42
+ const probe = spawnSync(candidate.command, [...candidate.prefix, "--version"], {
43
+ encoding: "utf8",
44
+ windowsHide: true
45
+ });
46
+ if (probe.error || probe.status !== 0) continue;
47
+ const version = parsePythonVersion(`${probe.stdout || ""}\n${probe.stderr || ""}`);
48
+ if (versionAtLeast(version)) return candidate;
49
+ }
50
+ return null;
51
+ }
52
+
53
+ function main() {
54
+ const python = findPython();
55
+ if (!python) {
56
+ console.error("Do Again requires Python 3.11 or newer.");
57
+ console.error("Install Python, or set DO_AGAIN_PYTHON to a Python 3.11+ executable.");
58
+ return 1;
59
+ }
60
+
61
+ const packageRoot = path.resolve(__dirname, "..");
62
+ const sourceRoot = path.join(packageRoot, "src");
63
+ const packageDir = path.join(sourceRoot, "do_again");
64
+ if (!fs.existsSync(packageDir)) {
65
+ console.error(`Do Again runtime is missing from the npm package: ${packageDir}`);
66
+ return 1;
67
+ }
68
+
69
+ const env = { ...process.env };
70
+ env.PYTHONDONTWRITEBYTECODE = env.PYTHONDONTWRITEBYTECODE || "1";
71
+ env.PYTHONPATH = env.PYTHONPATH
72
+ ? `${sourceRoot}${path.delimiter}${env.PYTHONPATH}`
73
+ : sourceRoot;
74
+
75
+ const child = spawnSync(
76
+ python.command,
77
+ [...python.prefix, "-m", "do_again.cli", ...process.argv.slice(2)],
78
+ {
79
+ cwd: process.cwd(),
80
+ env,
81
+ stdio: "inherit",
82
+ windowsHide: true
83
+ }
84
+ );
85
+
86
+ if (child.error) {
87
+ console.error(`Failed to launch Do Again: ${child.error.message}`);
88
+ return 1;
89
+ }
90
+ if (child.signal) return 1;
91
+ return child.status ?? 1;
92
+ }
93
+
94
+ process.exitCode = main();
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+
3
+ const assert = require("node:assert/strict");
4
+ const path = require("node:path");
5
+ const { spawnSync } = require("node:child_process");
6
+ const test = require("node:test");
7
+
8
+ const root = path.resolve(__dirname, "..");
9
+ const launcher = path.join(root, "bin", "do-again.js");
10
+
11
+ function run(args, extraEnv = {}) {
12
+ return spawnSync(process.execPath, [launcher, ...args], {
13
+ cwd: root,
14
+ encoding: "utf8",
15
+ env: { ...process.env, ...extraEnv }
16
+ });
17
+ }
18
+
19
+ test("npm launcher runs the bundled Python CLI", () => {
20
+ const result = run(["status"]);
21
+ assert.equal(result.status, 0, result.stderr);
22
+ assert.match(result.stdout, /platform=/);
23
+ assert.match(result.stdout, /service_manager=/);
24
+ });
25
+
26
+ test("npm launcher reports a missing configured Python cleanly", () => {
27
+ const result = run(["status"], { DO_AGAIN_PYTHON: "definitely-not-a-python-executable" });
28
+ assert.equal(result.status, 1);
29
+ assert.match(result.stderr, /requires Python 3\.11 or newer/);
30
+ });
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "do-again",
3
+ "version": "0.2.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "do-again",
9
+ "version": "0.2.0",
10
+ "license": "MIT",
11
+ "bin": {
12
+ "do-again": "bin/do-again.js"
13
+ },
14
+ "engines": {
15
+ "node": ">=18"
16
+ }
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "do-again",
3
+ "version": "0.2.0",
4
+ "description": "Policy-controlled local execution for AI coding agents",
5
+ "license": "MIT",
6
+ "author": "Steven Tran",
7
+ "type": "commonjs",
8
+ "bin": {
9
+ "do-again": "bin/do-again.js"
10
+ },
11
+ "files": [
12
+ "bin/",
13
+ "src/do_again/**/*.py",
14
+ "README.md",
15
+ "LICENSE",
16
+ "src/do_again/default_policy.json"
17
+ ],
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/Tran-Steven/do-again.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/Tran-Steven/do-again/issues"
27
+ },
28
+ "homepage": "https://github.com/Tran-Steven/do-again#readme",
29
+ "keywords": [
30
+ "ai-agents",
31
+ "coding-agents",
32
+ "automation",
33
+ "developer-tools",
34
+ "python",
35
+ "local-execution"
36
+ ],
37
+ "scripts": {
38
+ "test": "node --test npm-tests/*.test.js",
39
+ "prepack": "npm test"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ }
44
+ }
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "do-again"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Policy-controlled local execution for agent-driven development workflows"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"