@guidanti/duet 0.0.0-stage → 0.0.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/LICENSE.md +51 -0
- package/NOTICE.md +35 -0
- package/README.md +38 -2
- package/artifact/duet-0.0.1-py3-none-any.whl +0 -0
- package/artifact/duet-0.0.1.artifact.json +15 -0
- package/bin/duet +64 -0
- package/duet-npm.json +33 -0
- package/lib/setup.py +70 -0
- package/lib/setup.sh +116 -0
- package/package.json +25 -4
package/LICENSE.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Licence
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 Guidanti Inc. All rights reserved.
|
|
4
|
+
|
|
5
|
+
**duet is not open-source software, and no licence to use it is granted here.** These terms reserve every right in duet that applicable law and the terms of the services hosting it leave to reserve. Permission to use duet is granted to a named person or organisation in writing, individually, and in no other way -- see [how permission is granted](#3-how-permission-is-granted). Being able to download duet is not permission to use it.
|
|
6
|
+
|
|
7
|
+
Nothing in this file is an SPDX open-source licence, a permissive licence, a non-commercial licence or a "source-available" licence. The npm package declares `UNLICENSED`, which is npm's own spelling for "no licence is granted here", and that declaration is accurate rather than provisional.
|
|
8
|
+
|
|
9
|
+
## 1. What is covered
|
|
10
|
+
|
|
11
|
+
"duet" means the software in this repository and every artifact built from it: the Python package, the deterministic wheel and its record, the generated `@guidanti/duet` npm package, the prompt templates, the rule files and fragments, the provider schemas, the documentation and the tests. It does not mean your own projects, your own task documents, or the reports and evidence duet writes into your repository when you run it; those are yours.
|
|
12
|
+
|
|
13
|
+
## 2. What publication does and does not grant
|
|
14
|
+
|
|
15
|
+
Publishing duet where anyone can fetch it is a distribution decision. It is not a licence, and the two are separate deliberately.
|
|
16
|
+
|
|
17
|
+
- **On GitHub, nothing is published at all.** The repository `guidanti/duet` is private and stays private, so the viewing-and-forking licence [GitHub's Terms of Service](https://docs.github.com/en/site-policy/github-terms/github-terms-of-service#5-license-grant-to-other-users) grant to other users -- which is a condition of setting a repository to be viewed publicly -- never arises here. What the private repository keeps is the repository: its history, its branches, its tests, its tooling, its documentation, and the release pages, notes and assets a publication creates there, none of which can be read or downloaded without access to it.
|
|
18
|
+
- **duet's own Python code is distributed, in the public package.** The npm package below carries the built wheel, and a wheel of a pure-Python package is readable source: anybody who installs `@guidanti/duet` has the `.py` modules duet runs, together with these terms and [NOTICE.md](NOTICE.md), and needs no GitHub account to get them. Being able to read duet is not permission to use it, and it is not permission to copy, adapt or redistribute it either -- what reserves the rights in that code is this file, not the difficulty of obtaining it.
|
|
19
|
+
- **On npm.** Publishing `@guidanti/duet` licenses npm to provide the package to users of npm's services and to copy, publish and analyse it, under [npm's Open Source Terms](https://docs.npmjs.com/policies/open-source-terms/#your-content). Those terms leave it to the publisher to decide whether and how to license the content to anyone else. This file is that decision: nothing is licensed to anyone else. Installing the package, or having a copy of it on your machine, is not permission to use it.
|
|
20
|
+
- **Everywhere else.** No other distribution, redistribution, mirroring, republication, sublicensing or bundling of duet is permitted without a written grant.
|
|
21
|
+
|
|
22
|
+
## 3. How permission is granted
|
|
23
|
+
|
|
24
|
+
Permission is an individual, written grant from Guidanti Inc. naming the person or organisation it is for, the version or versions it covers, and what it permits. Ask at **min@guidanti.com**; the template is in [permission to use duet](docs/licensing.md), and it is governed by the laws of Ontario, Canada. A grant is not implied by a download, an installation, an invoice, a conversation, a fork, a GitHub star, an issue, a pull request or the absence of an objection. Guidanti Inc. may decline a request without reason.
|
|
25
|
+
|
|
26
|
+
Contributions are internal to Guidanti Inc.: there is no inbound contributor licence or assignment, and a contribution from anyone else is not accepted.
|
|
27
|
+
|
|
28
|
+
## 4. What these terms do not claim
|
|
29
|
+
|
|
30
|
+
Stating a reservation of rights is not a claim to control everything adjacent to duet, and this file claims none of the following:
|
|
31
|
+
|
|
32
|
+
- **Independent reimplementation is unaffected.** Copyright protects this expression of duet, not the idea of a two-agent review loop, not the design it embodies, not a workflow, not a command vocabulary and not an interface as such. Anyone may build their own software that does what duet does, including after reading the documentation, and these terms neither prohibit it nor are intended to deter it.
|
|
33
|
+
- **Rights the law does not let a licence remove stay where they are.** Fair use and fair dealing, the interoperability and error-correction rights mandatory law reserves to a lawful user, and every other non-waivable statutory exception apply whatever this file says. Where applicable law is narrower than a clause here, that clause is limited to what the law permits and the rest stands.
|
|
34
|
+
- **No patent, trademark or service-mark licence is granted, and none is asserted against you** by this file.
|
|
35
|
+
- **Nothing here restricts discussing, reviewing, criticising or writing about duet.**
|
|
36
|
+
|
|
37
|
+
## 5. External software
|
|
38
|
+
|
|
39
|
+
duet bundles no third-party code. It needs a Python interpreter, Git and the vendor CLIs it drives, and it installs, bundles and redistributes none of them -- see [NOTICE.md](NOTICE.md) for what that means for each of them and whose terms govern them.
|
|
40
|
+
|
|
41
|
+
## 6. No warranty, and no liability
|
|
42
|
+
|
|
43
|
+
duet is provided "as is", without warranty of any kind, express or implied, including the implied warranties of merchantability, fitness for a particular purpose and non-infringement. duet drives external agent CLIs that run commands and change files with your permissions, and it spends money at your providers; you are responsible for what it is pointed at. To the fullest extent applicable law permits, Guidanti Inc. is not liable for any claim, damage, data loss, provider charge or other liability arising from duet or its use. A written grant may state terms of its own, and where it does, those terms govern between its parties.
|
|
44
|
+
|
|
45
|
+
## 7. Contact
|
|
46
|
+
|
|
47
|
+
Licensing enquiries: **min@guidanti.com**. [Permission to use duet](docs/licensing.md) is the longer account of the same route.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
**Review status: approved.** This wording was **revised on 2026-10-08** to carry the owner's decisions -- the repository stays private, Guidanti Inc. owns the existing code, Ontario and Canada govern the grant, contributions are internal only, and min@guidanti.com is the published contact -- and **corrected the same day**, because clause 2 described the private repository as meaning duet's source is not distributed and the public package's wheel is readable Python source. The owner was shown those corrected terms and **approved them on 2026-10-08**, naming the exact bytes: sha256 `41d8a0763b603eafe24d5d9605167299bfc03d52cd01c9add56c8b5a843ad9e7` for this file and `23f7ea74c9f7afc9c96baaf268136b73200fc9f8d0937ba92f5991cee1726d10` for [NOTICE.md](NOTICE.md). Clauses 1 to 7 above are unchanged by this note and are the approved text; what changed after the approval is this paragraph, which is why the file no longer hashes to the digest the approval names. A change to any clause would be a new approval and a different build identity. No publication has happened.
|
package/NOTICE.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Notices
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 Guidanti Inc. All rights reserved. The terms are in [LICENSE.md](LICENSE.md); this file is the third-party notice that travels beside them.
|
|
4
|
+
|
|
5
|
+
## duet bundles no third-party code
|
|
6
|
+
|
|
7
|
+
Every byte duet distributes is its own. The wheel holds the `duet` package and its own metadata and nothing else: `pyproject.toml` declares an empty dependency list, the test suite re-derives that list from the package's own imports so adding a dependency fails there rather than quietly becoming part of the installation, and `duet install --artifact` refuses an artifact that declares any `Requires-Dist` at all. The generated `@guidanti/duet` npm package is assembled from an explicit allowlist -- a launcher, an install script, a bootstrap, that same wheel, its record, a generated manifest and README, and these notices -- with no dependencies and nothing taken from a checkout. Nothing is downloaded by an installation of duet, at any point.
|
|
8
|
+
|
|
9
|
+
So there is no vendored library here, no statically linked component, and no accompanying licence text that a redistribution would have to carry. If that ever changes, the component and its terms belong in this file and in the packaging allowlists that carry it.
|
|
10
|
+
|
|
11
|
+
## What duet needs, and does not distribute
|
|
12
|
+
|
|
13
|
+
duet runs external programs. It installs none of them, bundles none of them, redistributes none of them and modifies none of them; each is obtained by you, from its own publisher, under that publisher's own terms, and nothing in [LICENSE.md](LICENSE.md) says anything about them.
|
|
14
|
+
|
|
15
|
+
| Program | What duet does with it | Whose terms govern |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| CPython (3.9 or newer) | a managed runtime is a virtual environment built from an interpreter already on your machine, which stays where it is and is recorded as a dependency | the Python Software Foundation's, or whoever packaged the interpreter you installed |
|
|
18
|
+
| Git | duet asks it where the project is, creates the implementation worktree with it and integrates through it | the Git project's |
|
|
19
|
+
| Codex CLI (`codex`) | launched as a subprocess, per turn | OpenAI's |
|
|
20
|
+
| Claude Code (`claude`) | launched as a subprocess, per turn | Anthropic's |
|
|
21
|
+
| Node and npm | install and update duet, and nothing afterwards; no duet command invokes either | the Node.js project's and npm's |
|
|
22
|
+
|
|
23
|
+
Your use of those programs, and what your provider accounts permit, is between you and them. duet starts no sign-in, opens no credential file and makes no claim about which account a CLI would run as.
|
|
24
|
+
|
|
25
|
+
## Contributor tooling, which is not distributed
|
|
26
|
+
|
|
27
|
+
The root `package.json` is private contributor metadata named `duet`, is marked `"private": true` and is never published. Its one development dependency, `@changesets/cli` 3.0.3, is recorded in `package-lock.json` as MIT-licensed, is installed from the registry by a contributor who needs it, and is not part of any artifact duet distributes: it is absent from the wheel, absent from the generated npm package's allowlist, and never read at runtime.
|
|
28
|
+
|
|
29
|
+
## The agents' own output
|
|
30
|
+
|
|
31
|
+
duet drives model providers, and what a provider's model writes into a report, a prompt or your repository arrives under that provider's terms. Guidanti Inc. makes no claim to it and gives no assurance about it.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
**Review status: approved.** Revised with [LICENSE.md](LICENSE.md) on 2026-10-08 under the owner's decisions and **approved the same day**, by sha256 `23f7ea74c9f7afc9c96baaf268136b73200fc9f8d0937ba92f5991cee1726d10`, which is this file as it stood when the approval was recorded. Everything above this note is the approved text and is unchanged by it; this paragraph is what moved the digest afterwards. Nothing else in this file has ever changed: duet still bundles no third-party code, so the decisions about distribution, jurisdiction, contributions and contact touch the terms rather than the notices. The account of the approval is in [permission to use duet](docs/licensing.md#what-the-owner-decided-and-what-that-settled).
|
package/README.md
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @guidanti/duet
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An automated Codex <-> Claude research, planning and implementation loop.
|
|
4
|
+
|
|
5
|
+
npm installs and updates duet. It does not run it: the `duet` on your PATH is a shell script that execs the managed dispatcher, and every invocation after that is Python. Nothing duet runs invokes Node, npm or a source checkout.
|
|
6
|
+
|
|
7
|
+
## Prerequisite: a Python you already have
|
|
8
|
+
|
|
9
|
+
duet needs CPython >=3.9 on macOS (arm64) and does not install one. Installation finds an interpreter already on the machine, checks its version, architecture and ability to create a virtual environment, and fails with instructions when nothing usable is there. The official installers are at https://www.python.org/downloads/macos/.
|
|
10
|
+
|
|
11
|
+
## Install, update, retry
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install --global @guidanti/duet
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Setup runs in this package's install script: it verifies the bundled artifact, installs it as an immutable managed runtime under `~/.duet/installations/`, self-checks that runtime, writes the dispatcher and selects the new build when the default is this distribution's own or there is none. Running the same command again is the retry and the upgrade; it installs beside what is there and removes nothing.
|
|
18
|
+
|
|
19
|
+
`DUET_SETUP_PYTHON=/path/to/python3` names the interpreter yourself. `--foreground-scripts` shows the setup output. If npm is configured with `ignore-scripts`, no setup runs and the launcher says so.
|
|
20
|
+
|
|
21
|
+
## Uninstalling is not removing your runtimes
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm uninstall --global @guidanti/duet
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
That removes this package and the `duet` npm linked onto your PATH. It removes nothing under `~/.duet`: every managed runtime, pin, run and piece of evidence stays, reachable by each runtime's own launcher path and by the dispatcher at `~/.duet/installations/.dispatch/duet`. Removing a runtime is `duet remove <name> --yes`, one build at a time, and it refuses while anything still needs it. duet never purges `~/.duet`.
|
|
28
|
+
|
|
29
|
+
## This build
|
|
30
|
+
|
|
31
|
+
- version `0.0.1`, build `c921cf37ea5b416b`
|
|
32
|
+
- artifact `duet-0.0.1-py3-none-any.whl`
|
|
33
|
+
- `sha256=560d88ba5bca6f3c7d17573a17659cef2763208acad085ebb43d26306c713f64`
|
|
34
|
+
|
|
35
|
+
## Permission to use duet
|
|
36
|
+
|
|
37
|
+
duet is not open-source software and installing it is not permission to use it: the copyright is Guidanti Inc.'s, all rights are reserved, and permission is granted individually and in writing. `license` is `UNLICENSED`, which is npm's own spelling for "no license is granted here" and is accurate rather than provisional. The terms are `LICENSE.md` and `NOTICE.md` in this package; the source repository is private, so ask for permission at min@guidanti.com rather than looking for an issue tracker.
|
|
38
|
+
|
|
39
|
+
`docs/packaging.md` and `docs/launching.md` are the full documentation, in that private repository; this README and the files beside it are what travels with the package.
|
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"build_id": "c921cf37ea5b416b",
|
|
3
|
+
"name": "duet",
|
|
4
|
+
"schema": 1,
|
|
5
|
+
"source": {
|
|
6
|
+
"clean": true,
|
|
7
|
+
"commit": "807ed6c5ca2f004f25160b072f82f59e2046466b"
|
|
8
|
+
},
|
|
9
|
+
"verification": {
|
|
10
|
+
"contract": 1
|
|
11
|
+
},
|
|
12
|
+
"version": "0.0.1",
|
|
13
|
+
"wheel": "duet-0.0.1-py3-none-any.whl",
|
|
14
|
+
"wheel_sha256": "560d88ba5bca6f3c7d17573a17659cef2763208acad085ebb43d26306c713f64"
|
|
15
|
+
}
|
package/bin/duet
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Generated by duet.npm for @guidanti/duet 0.0.1.
|
|
3
|
+
#
|
|
4
|
+
# This is the whole of what npm puts on your PATH. It invokes no
|
|
5
|
+
# Node, no npm and no source checkout: it finds the managed
|
|
6
|
+
# dispatcher under the installation root and becomes it, and from
|
|
7
|
+
# there duet is Python. The dispatcher reads which build a run is
|
|
8
|
+
# pinned to -- so an existing run reaches the build it was started
|
|
9
|
+
# on, whatever this package was upgraded to since.
|
|
10
|
+
set -u
|
|
11
|
+
launcher=$0
|
|
12
|
+
while [ -L "$launcher" ]; do
|
|
13
|
+
directory=$(CDPATH= cd -P "$(dirname "$launcher")" && pwd)
|
|
14
|
+
target=$(readlink "$launcher")
|
|
15
|
+
case "$target" in
|
|
16
|
+
/*) launcher=$target ;;
|
|
17
|
+
*) launcher=$directory/$target ;;
|
|
18
|
+
esac
|
|
19
|
+
done
|
|
20
|
+
here=$(CDPATH= cd -P "$(dirname "$launcher")/.." && pwd)
|
|
21
|
+
root=${DUET_INSTALL_ROOT:-$HOME/.duet/installations}
|
|
22
|
+
dispatcher=$root/.dispatch/duet
|
|
23
|
+
if [ ! -x "$dispatcher" ]; then
|
|
24
|
+
cat >&2 <<SETUP
|
|
25
|
+
duet: this installation of @guidanti/duet has no managed runtime yet.
|
|
26
|
+
There is no dispatcher at $dispatcher.
|
|
27
|
+
Setup runs in this package's install script, so if npm was run
|
|
28
|
+
with --ignore-scripts, or that script failed, nothing was
|
|
29
|
+
installed and nothing was selected.
|
|
30
|
+
Install or retry: npm install --global @guidanti/duet
|
|
31
|
+
See why it failed: npm install --global @guidanti/duet --foreground-scripts
|
|
32
|
+
Run setup by hand: /bin/sh $here/lib/setup.sh
|
|
33
|
+
duet installs no software by itself: nothing has been downloaded,
|
|
34
|
+
installed or selected here.
|
|
35
|
+
SETUP
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
# Whether a finished setup under this root installed the artifact
|
|
39
|
+
# *this* package carries. One digest, matched in the record setup
|
|
40
|
+
# writes last, with shell builtins and no interpreter: the launcher
|
|
41
|
+
# is on the path of every duet invocation.
|
|
42
|
+
set_up_here() {
|
|
43
|
+
[ -f "$1" ] || return 1
|
|
44
|
+
while IFS= read -r line; do
|
|
45
|
+
case "$line" in
|
|
46
|
+
*'"wheel_sha256": "560d88ba5bca6f3c7d17573a17659cef2763208acad085ebb43d26306c713f64"'*) return 0 ;;
|
|
47
|
+
esac
|
|
48
|
+
done < "$1"
|
|
49
|
+
return 1
|
|
50
|
+
}
|
|
51
|
+
if ! set_up_here "$root/npm.json"; then
|
|
52
|
+
cat >&2 <<INCOMPLETE
|
|
53
|
+
duet: @guidanti/duet 0.0.1 is installed and its setup did not finish.
|
|
54
|
+
The runtime this package carries is not under $root, so this
|
|
55
|
+
command runs the managed runtime that is already there. Every run
|
|
56
|
+
still reaches the build it was started on, and nothing has been
|
|
57
|
+
downloaded, installed or selected -- including by this message.
|
|
58
|
+
Retry the setup: npm install --global @guidanti/duet
|
|
59
|
+
See why it failed: npm install --global @guidanti/duet --foreground-scripts
|
|
60
|
+
If scripts are off: npm config get ignore-scripts
|
|
61
|
+
Run setup by hand: /bin/sh $here/lib/setup.sh
|
|
62
|
+
INCOMPLETE
|
|
63
|
+
fi
|
|
64
|
+
exec "$dispatcher" "$@"
|
package/duet-npm.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"artifact": {
|
|
3
|
+
"build_id": "c921cf37ea5b416b",
|
|
4
|
+
"sidecar": "duet-0.0.1.artifact.json",
|
|
5
|
+
"verification": {
|
|
6
|
+
"contract": 1
|
|
7
|
+
},
|
|
8
|
+
"wheel": "duet-0.0.1-py3-none-any.whl",
|
|
9
|
+
"wheel_sha256": "560d88ba5bca6f3c7d17573a17659cef2763208acad085ebb43d26306c713f64"
|
|
10
|
+
},
|
|
11
|
+
"contents": {
|
|
12
|
+
"LICENSE.md": "b4d64972a2579e709434fab1e11cd0c7cf936d8ced6240af164a8283b3c1f2d7",
|
|
13
|
+
"NOTICE.md": "95e6b3212b7a907e59befdca37999fd683cf02c94cc2d487df62b77c7c3ef2ba",
|
|
14
|
+
"README.md": "d58cc9bde1fb0583d1c51f33a59affb6f0359ab0f0c0196aeef3be0bddb40b00",
|
|
15
|
+
"artifact/duet-0.0.1-py3-none-any.whl": "560d88ba5bca6f3c7d17573a17659cef2763208acad085ebb43d26306c713f64",
|
|
16
|
+
"artifact/duet-0.0.1.artifact.json": "a4a69d90f3aa1fc7cae414136822aa48a170b5acd97a30ea14ed181d93a24cdc",
|
|
17
|
+
"bin/duet": "521c73f89306e0060376f3afd4ea2de3c5eba9a299d9b8a3bacaba40a4078f6b",
|
|
18
|
+
"lib/setup.py": "af8b06ef8c81a9c41e75a9b1d2f34da9633773199201776811d95e073e6917ab",
|
|
19
|
+
"lib/setup.sh": "98e180d851b626898d1a71ad0794b17b8c2496e6907368ab0ac40180832f7494",
|
|
20
|
+
"package.json": "6a00de1294ab0aec1a03cdc715e13f988d48d7509e7bd49261669338fe34928d"
|
|
21
|
+
},
|
|
22
|
+
"notices": [
|
|
23
|
+
"LICENSE.md",
|
|
24
|
+
"NOTICE.md"
|
|
25
|
+
],
|
|
26
|
+
"package": "@guidanti/duet",
|
|
27
|
+
"schema": 1,
|
|
28
|
+
"source": {
|
|
29
|
+
"clean": true,
|
|
30
|
+
"commit": "807ed6c5ca2f004f25160b072f82f59e2046466b"
|
|
31
|
+
},
|
|
32
|
+
"version": "0.0.1"
|
|
33
|
+
}
|
package/lib/setup.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Generated by duet.npm for @guidanti/duet 0.0.1.
|
|
2
|
+
|
|
3
|
+
The install script's Python half: check the bundled artifact against
|
|
4
|
+
the digest this file was generated with, import the installer out of
|
|
5
|
+
those same bytes, and run it. Nothing here verifies an artifact
|
|
6
|
+
itself, creates an environment, or writes outside the installation
|
|
7
|
+
root the installer resolves."""
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
PACKAGE = '@guidanti/duet'
|
|
14
|
+
PACKAGE_VERSION = '0.0.1'
|
|
15
|
+
HERE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
16
|
+
WHEEL = os.path.join(HERE, 'artifact', 'duet-0.0.1-py3-none-any.whl')
|
|
17
|
+
SIDECAR = os.path.join(HERE, 'artifact', 'duet-0.0.1.artifact.json')
|
|
18
|
+
WHEEL_SHA256 = '560d88ba5bca6f3c7d17573a17659cef2763208acad085ebb43d26306c713f64'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def refuse(message, *lines):
|
|
22
|
+
sys.stderr.write('duet: ' + message + '\n')
|
|
23
|
+
for line in lines:
|
|
24
|
+
sys.stderr.write((' ' + line if line else '') + '\n')
|
|
25
|
+
return 1
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main():
|
|
29
|
+
try:
|
|
30
|
+
with open(WHEEL, 'rb') as stream:
|
|
31
|
+
data = stream.read()
|
|
32
|
+
except OSError as exc:
|
|
33
|
+
return refuse(
|
|
34
|
+
'the artifact this package carries could not be read ('
|
|
35
|
+
+ str(exc) + ')',
|
|
36
|
+
'Nothing was installed and nothing was selected.',
|
|
37
|
+
'Install the package again: npm install --global @guidanti/duet')
|
|
38
|
+
found = hashlib.sha256(data).hexdigest()
|
|
39
|
+
if found != WHEEL_SHA256:
|
|
40
|
+
return refuse(
|
|
41
|
+
'the artifact this package carries is not the one it was'
|
|
42
|
+
' published with',
|
|
43
|
+
'expected sha256=' + WHEEL_SHA256,
|
|
44
|
+
'found sha256=' + found,
|
|
45
|
+
'',
|
|
46
|
+
'duet will not install bytes that are not the bytes this'
|
|
47
|
+
' package records.',
|
|
48
|
+
'Nothing was installed and nothing was selected.',
|
|
49
|
+
'Install the package again: npm install --global @guidanti/duet')
|
|
50
|
+
# Those bytes, and nothing else, are what the installer is read
|
|
51
|
+
# out of. A wheel is a zip and an interpreter imports from one
|
|
52
|
+
# directly, so there is no temporary environment to clean up.
|
|
53
|
+
sys.path.insert(0, WHEEL)
|
|
54
|
+
try:
|
|
55
|
+
from duet import npm
|
|
56
|
+
except Exception as exc:
|
|
57
|
+
return refuse(
|
|
58
|
+
'the artifact this package carries could not be read as'
|
|
59
|
+
' a duet (' + type(exc).__name__ + ': ' + str(exc)
|
|
60
|
+
+ ')',
|
|
61
|
+
'Nothing was installed and nothing was selected.',
|
|
62
|
+
'Install the package again: npm install --global @guidanti/duet')
|
|
63
|
+
return npm.setup_main(wheel=WHEEL, sidecar=SIDECAR, here=HERE,
|
|
64
|
+
package=PACKAGE,
|
|
65
|
+
package_version=PACKAGE_VERSION,
|
|
66
|
+
argv=sys.argv[1:])
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
sys.exit(main())
|
package/lib/setup.sh
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Generated by duet.npm for @guidanti/duet 0.0.1.
|
|
3
|
+
#
|
|
4
|
+
# npm runs this once, after it has unpacked the package. It finds an
|
|
5
|
+
# interpreter that can run the installer and hands over to it; the
|
|
6
|
+
# installer is the bundled artifact's own code, and it is what checks
|
|
7
|
+
# the version, the architecture and the capabilities of whichever
|
|
8
|
+
# interpreter a runtime will actually be built from.
|
|
9
|
+
set -u
|
|
10
|
+
here=$(CDPATH= cd -P "$(dirname "$0")/.." && pwd)
|
|
11
|
+
|
|
12
|
+
resolved() {
|
|
13
|
+
# The pathname a candidate really is, every link followed. A case
|
|
14
|
+
# check on the name alone is not enough: an ordinary alias in
|
|
15
|
+
# somebody's ~/bin pointing at the macOS stub *is* the stub, and
|
|
16
|
+
# duet must not execute it to find that out.
|
|
17
|
+
target=$1
|
|
18
|
+
hops=0
|
|
19
|
+
while [ -L "$target" ] && [ "$hops" -lt 40 ]; do
|
|
20
|
+
directory=$(CDPATH= cd -P "$(dirname "$target")" 2>/dev/null && pwd) || return 1
|
|
21
|
+
link=$(readlink "$target") || return 1
|
|
22
|
+
case "$link" in
|
|
23
|
+
/*) target=$link ;;
|
|
24
|
+
*) target=$directory/$link ;;
|
|
25
|
+
esac
|
|
26
|
+
hops=$((hops + 1))
|
|
27
|
+
done
|
|
28
|
+
directory=$(CDPATH= cd -P "$(dirname "$target")" 2>/dev/null && pwd) || return 1
|
|
29
|
+
case "$directory" in
|
|
30
|
+
/) printf '/%s\n' "$(basename "$target")" ;;
|
|
31
|
+
*) printf '%s/%s\n' "$directory" "$(basename "$target")" ;;
|
|
32
|
+
esac
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
runnable() {
|
|
36
|
+
# Never these: on macOS /usr/bin/python3 is a stub that execs
|
|
37
|
+
# xcrun, and running it with the command line tools absent opens
|
|
38
|
+
# the operating system's own installation dialog. duet starts no
|
|
39
|
+
# installer, so it does not ask. Asked of the name *and* of what
|
|
40
|
+
# the name resolves to, before anything is executed.
|
|
41
|
+
case "$1" in
|
|
42
|
+
/usr/bin/python3|/usr/bin/python) return 1 ;;
|
|
43
|
+
esac
|
|
44
|
+
real=$(resolved "$1") || return 1
|
|
45
|
+
case "$real" in
|
|
46
|
+
/usr/bin/python3|/usr/bin/python) return 1 ;;
|
|
47
|
+
esac
|
|
48
|
+
[ -x "$1" ] || return 1
|
|
49
|
+
# It has to run, and it has to be new enough to read the installer:
|
|
50
|
+
# an interpreter below duet's floor cannot import the bundled
|
|
51
|
+
# artifact at all, and 'that is not a duet' is the wrong diagnosis
|
|
52
|
+
# for 'that Python is too old'.
|
|
53
|
+
"$1" -I -c 'import sys; sys.exit(0 if sys.version_info >= (3, 9) else 1)' >/dev/null 2>&1 || return 1
|
|
54
|
+
return 0
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
found=
|
|
58
|
+
if [ -n "${DUET_SETUP_PYTHON:-}" ]; then
|
|
59
|
+
if runnable "$DUET_SETUP_PYTHON"; then
|
|
60
|
+
found=$DUET_SETUP_PYTHON
|
|
61
|
+
else
|
|
62
|
+
cat >&2 <<OVERRIDE
|
|
63
|
+
duet: DUET_SETUP_PYTHON names $DUET_SETUP_PYTHON, which duet will not run as an interpreter.
|
|
64
|
+
It is not there, it is not executable, it did not run, it is older than CPython >=3.9, or it is a stub duet does not ask.
|
|
65
|
+
duet needs CPython >=3.9 on macOS (arm64), able to create a virtual environment.
|
|
66
|
+
Install Python from https://www.python.org/downloads/macos/
|
|
67
|
+
An explicit interpreter is an instruction, so duet does not look elsewhere when it is unusable.
|
|
68
|
+
Nothing was installed and nothing was selected. Unset it or name another, then run:
|
|
69
|
+
npm install --global @guidanti/duet
|
|
70
|
+
OVERRIDE
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
fi
|
|
74
|
+
if [ -z "$found" ]; then
|
|
75
|
+
for candidate in \
|
|
76
|
+
python3.14 \
|
|
77
|
+
python3.13 \
|
|
78
|
+
python3.12 \
|
|
79
|
+
python3.11 \
|
|
80
|
+
python3.10 \
|
|
81
|
+
python3.9 \
|
|
82
|
+
python3 \
|
|
83
|
+
/opt/homebrew/opt/python@3.14/bin/python3.14 \
|
|
84
|
+
/opt/homebrew/opt/python@3.13/bin/python3.13 \
|
|
85
|
+
/opt/homebrew/opt/python@3.12/bin/python3.12 \
|
|
86
|
+
/opt/homebrew/opt/python@3.11/bin/python3.11 \
|
|
87
|
+
/opt/homebrew/opt/python@3.10/bin/python3.10 \
|
|
88
|
+
/opt/homebrew/opt/python@3.9/bin/python3.9 \
|
|
89
|
+
/Library/Frameworks/Python.framework/Versions/3.14/bin/python3.14 \
|
|
90
|
+
/Library/Frameworks/Python.framework/Versions/3.13/bin/python3.13 \
|
|
91
|
+
/Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12 \
|
|
92
|
+
/Library/Frameworks/Python.framework/Versions/3.11/bin/python3.11 \
|
|
93
|
+
/Library/Frameworks/Python.framework/Versions/3.10/bin/python3.10 \
|
|
94
|
+
/Library/Frameworks/Python.framework/Versions/3.9/bin/python3.9 \
|
|
95
|
+
/Library/Developer/CommandLineTools/usr/bin/python3 \
|
|
96
|
+
; do
|
|
97
|
+
case "$candidate" in
|
|
98
|
+
/*) path=$candidate ;;
|
|
99
|
+
*) path=$(command -v "$candidate" 2>/dev/null) || path= ;;
|
|
100
|
+
esac
|
|
101
|
+
[ -n "$path" ] || continue
|
|
102
|
+
if runnable "$path"; then found=$path; break; fi
|
|
103
|
+
done
|
|
104
|
+
fi
|
|
105
|
+
if [ -z "$found" ]; then
|
|
106
|
+
cat >&2 <<'NOPYTHON'
|
|
107
|
+
duet: no usable Python was found, and duet does not install one.
|
|
108
|
+
duet needs CPython >=3.9 on macOS (arm64), able to create a virtual environment.
|
|
109
|
+
Install Python from https://www.python.org/downloads/macos/
|
|
110
|
+
Then run: npm install --global @guidanti/duet
|
|
111
|
+
Or name one yourself: DUET_SETUP_PYTHON=/path/to/python3 npm install --global @guidanti/duet
|
|
112
|
+
Nothing was installed, nothing was selected, and no existing duet installation was changed.
|
|
113
|
+
NOPYTHON
|
|
114
|
+
exit 1
|
|
115
|
+
fi
|
|
116
|
+
exec "$found" -I "$here/lib/setup.py" "$@"
|
package/package.json
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
{
|
|
2
|
+
"bin": {
|
|
3
|
+
"duet": "bin/duet"
|
|
4
|
+
},
|
|
5
|
+
"cpu": [
|
|
6
|
+
"arm64"
|
|
7
|
+
],
|
|
8
|
+
"description": "An automated Codex <-> Claude research, planning and implementation loop.",
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"lib/",
|
|
12
|
+
"artifact/",
|
|
13
|
+
"duet-npm.json",
|
|
14
|
+
"LICENSE.md",
|
|
15
|
+
"NOTICE.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"license": "UNLICENSED",
|
|
2
19
|
"name": "@guidanti/duet",
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
20
|
+
"os": [
|
|
21
|
+
"darwin"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"postinstall": "/bin/sh lib/setup.sh"
|
|
25
|
+
},
|
|
26
|
+
"version": "0.0.1"
|
|
27
|
+
}
|