@semanticist14/clco 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 semanticist21
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,279 @@
1
+ # clco
2
+
3
+ Run Claude Code on your GitHub Copilot subscription. It starts a local adapter that translates Anthropic Messages ↔ GitHub Copilot, and runs claude against a config directory of its own — so **a `/model` pick inside clco never changes the model your plain `claude` uses**.
4
+
5
+ ```
6
+ $ clco
7
+ │
8
+ ◇ Checking GitHub token ✓ 0.0s
9
+ │
10
+ ◇ Fetching Copilot token and model list ✓ 1.8s
11
+ │
12
+ ◆ Pick a model - type to search
13
+ │ Search models...
14
+ │ ● Claude Sonnet 5 claude-sonnet-5 · native · 200k
15
+ │ ○ GPT-5.6 Luna gpt-5.6-luna · responses · 200k
16
+ │ ○ Kimi K3 kimi-k3 · chat · 918k
17
+ └
18
+ + adapter: http://127.0.0.1:56844
19
+ + model: claude-sonnet-5 (sonnet=claude-sonnet-5 opus=claude-opus-5 haiku=claude-haiku-4.5)
20
+ + browser: @playwright/mcp@latest
21
+ ```
22
+
23
+ ## Install
24
+
25
+ From npm (Bun is still required at runtime):
26
+
27
+ ```sh
28
+ npm install --global @semanticist14/clco
29
+ ```
30
+
31
+ Update npm installs with `npm install --global @semanticist14/clco@latest`.
32
+
33
+ Or use the GitHub installer:
34
+
35
+ ```sh
36
+ curl -fsSL https://raw.githubusercontent.com/semanticist21/clco/main/install.sh | bash
37
+ ```
38
+
39
+ Installs to `~/.local/share/clco`, launcher at `~/.local/bin/clco`.
40
+ For custom paths: `curl -fsSL ... | CLCO_DIR=~/somewhere BIN_DIR=~/bin bash`.
41
+ The same `CLCO_DIR` and `BIN_DIR` values can be passed to `uninstall.sh`.
42
+
43
+ Needs the [claude CLI](https://claude.com/claude-code), a GitHub account with
44
+ Copilot, and [Bun](https://bun.sh). npm installs the package, but clco still
45
+ runs on Bun because the adapter uses Bun's server and process APIs; Node is not
46
+ a runtime fallback yet.
47
+
48
+ If you need to install Bun yourself:
49
+
50
+ ```sh
51
+ curl -fsSL https://bun.sh/install | bash
52
+ ```
53
+
54
+ Reopen your terminal afterwards so `bun` is on your `PATH`.
55
+
56
+ ## First run
57
+
58
+ ```sh
59
+ clco
60
+ ```
61
+
62
+ 1. **GitHub login**, once — a device-flow code to paste in your browser. The
63
+ token is stored at `~/.config/clco/auth.json` (mode 600).
64
+ 2. **Three questions**, once. Re-run them any time with `clco setup`:
65
+
66
+ | Question | Default | What it does |
67
+ |---|---|---|
68
+ | Run without permission prompts? | **Yes** | Passes `--dangerously-skip-permissions`, so claude edits files and runs commands without asking. `clco --no-bypass` for one session. |
69
+ | Enable Playwright MCP for browser control? | **Yes** | Registers the Playwright MCP server for clco sessions only. **Also needs a Chrome extension you install yourself** — see [Browser control](#browser-control). `clco --no-browser` for one session. |
70
+ | Pick a model each time clco starts? | **Yes** | Shows the model prompt at launch. Answering No reuses your last pick. `clco --no-select` for one session. |
71
+
72
+ 3. **Pick a model**, then claude starts. Switch mid-session with `/model`.
73
+
74
+ Answering yes to the first two together is worth understanding: claude runs
75
+ without permission prompts, and each session resolves the current
76
+ `@playwright/mcp` release from npm (`bunx -y @playwright/mcp@latest`) and runs
77
+ it with access to the browser tab you share. The version is re-resolved every
78
+ session, so a release published today runs on your machine today; nothing in
79
+ this path checks an integrity hash. Either answer is reasonable alone; both at
80
+ once is a lot of trust in one command. `clco setup` changes them,
81
+ `--no-bypass` / `--no-browser` skip them for a single run, and
82
+ `CLCO_MCP_PACKAGE` pins or redirects the package — see
83
+ [Browser control](#browser-control).
84
+
85
+ ## Usage
86
+
87
+ ```sh
88
+ clco # a session
89
+ clco -p "question" # anything clco doesn't own goes straight to claude
90
+ clco status # account, plan, and every model's route/policy/context
91
+ clco setup # change the three answers above
92
+ clco help # all commands and environment variables
93
+ ```
94
+
95
+ ## Models
96
+
97
+ `/model` lists every conversational model your Copilot account offers, not just
98
+ the Claude ones. Each row shows the upstream id, its route, its real context
99
+ window, and whether it supports `/effort`.
100
+
101
+ What your plan actually allows is a separate matter, and Copilot's metadata does
102
+ not predict it — a model can be marked `enabled` and still refuse. Picking one
103
+ your plan does not cover answers `400 The requested model is not supported`.
104
+
105
+ Pin a model instead of choosing: `clco --model kimi-k3`, or per slot with
106
+ `CLCO_OPUS` / `CLCO_SONNET` / `CLCO_HAIKU` / `CLCO_FABLE` — the four model
107
+ slots Claude Code asks for by name.
108
+
109
+ ## Browser control
110
+
111
+ Claude's own Chrome extension cannot work here: Claude Code gates it on the
112
+ session's OAuth scope, and clco authenticates with `ANTHROPIC_AUTH_TOKEN`, which
113
+ is always `user:inference`. Passing `--chrome` registers nothing.
114
+
115
+ Playwright MCP has no such gate, and in `--extension` mode it drives the tab you
116
+ share from your own browser — logins and cookies intact — rather than a fresh
117
+ profile.
118
+
119
+ **It needs a Chrome extension, and clco cannot install it for you.** Without it
120
+ there is no browser control at all — clco registers no server and no tools
121
+ appear. Install [Playwright
122
+ Extension](https://chromewebstore.google.com/detail/mmlmfjhmonkocbjadbfplnigmagldckm)
123
+ (listed under that name, not "Playwright MCP"), then answer yes at `clco setup`.
124
+ Tools arrive as `mcp__playwright__*` once you click the extension to share a tab.
125
+
126
+ The extension shows a `PLAYWRIGHT_MCP_EXTENSION_TOKEN`; storing it skips the
127
+ connect dialog every session:
128
+
129
+ ```sh
130
+ pbpaste | clco token # clco token --clear to remove it
131
+ ```
132
+
133
+ Started with `bunx` (or `npx`) and fetched from npm each session. If that fails,
134
+ clco says which way it failed at startup rather than leaving you with tools that
135
+ silently never appear — unless `CLCO_MCP_PACKAGE` names a different package, in
136
+ which case clco cannot say whose registry serves it and reports that instead:
137
+
138
+ ```
139
+ ! browser: cannot reach registry.npmjs.org - browser tools will not appear
140
+ ! browser: registry.npmjs.org is slow to answer - browser tools may be slow to appear
141
+ ! browser: TLS rejected by registry.npmjs.org - browser tools will not appear. Set CLCO_CA_BUNDLE to your company CA.
142
+ ! browser: registry.npmjs.org presented an expired certificate - browser tools will not appear. ...
143
+ + browser: @corp/playwright-mcp@1.2.3 (not the default package, so the registry check was skipped)
144
+ ```
145
+
146
+ That check only knows about `registry.npmjs.org`, so it cannot speak for an
147
+ internal mirror. `CLCO_MCP_PACKAGE` overrides the package spec for one — or to
148
+ pin a version, or roll back past a bad release:
149
+
150
+ ```sh
151
+ CLCO_MCP_PACKAGE=@playwright/mcp@0.0.80 clco
152
+ ```
153
+
154
+ Pinning is not an offline mode: browser control needs npm either way.
155
+
156
+ ## What clco reads
157
+
158
+ Nothing clco reads from your disk leaves the machine. Its outbound requests are
159
+ GitHub login, the Copilot token exchange, the model list, your chat itself,
160
+ plus — with browser control on — resolving `@playwright/mcp` against npm each
161
+ session, and checking GitHub for a staged update when you run `clco update`.
162
+
163
+ `clco update` installs and smoke-tests the new revision in a temporary checkout,
164
+ then swaps it in only after those checks pass. Local changes or a non-fast-forward
165
+ branch stop the update before the live install is touched.
166
+
167
+ It first tries `bun install --frozen-lockfile`. If the installed Bun cannot read
168
+ this lockfile format, it falls back to `bun install --no-save`, which installs
169
+ dependencies without rewriting `bun.lock`; using a current Bun release is still
170
+ recommended.
171
+
172
+ If you installed with custom `CLCO_DIR` or `BIN_DIR` values before launcher v4,
173
+ re-run the installer once with those same values so the launcher can migrate.
174
+
175
+ - **Chrome, Chromium and Edge profile directories** — directory *names* only, to
176
+ see whether the Playwright extension is installed.
177
+ - **The claude binary** — scanned once per claude version for the model ids it
178
+ knows, cached in `~/.config/clco/catalog.json`. Without it clco cannot tell
179
+ which ids `/model` will accept.
180
+ - **`~/.claude`** — every entry is symlinked into `~/.config/clco/claude-home`
181
+ except `settings.json`, `settings.local.json` and `backups`, which are
182
+ private copies. So plugins, skills, agents, projects, history and shell
183
+ snapshots stay shared and are written into `~/.claude` exactly as a plain
184
+ `claude` run would; only the settings file, where a `/model` pick would land,
185
+ is clco's own.
186
+ - **`~/.claude.json`** — copied into clco's config dir, since it holds trust
187
+ decisions and MCP servers, and it carries account identifiers and any secrets
188
+ your MCP servers declare. Your MCP servers and per-project trust are re-read
189
+ from the real file on every launch, so deleting a server or withdrawing trust
190
+ there applies to clco too; everything else in the copy is clco's own.
191
+ `uninstall.sh --full` removes it; a plain uninstall leaves it.
192
+
193
+ ## Corporate networks
194
+
195
+ Behind a TLS-inspecting proxy, export your company CA and point clco at it:
196
+
197
+ ```sh
198
+ security find-certificate -a -p -c "<CA name>" > ~/ca.pem # macOS
199
+ CLCO_CA_BUNDLE=~/ca.pem clco
200
+ ```
201
+
202
+ clco **adds** it to the OS trust store rather than replacing it, so everything
203
+ that worked before still does. Several paths can be joined with `:`.
204
+
205
+ Prefer this to `NODE_EXTRA_CA_CERTS`, which has been reported to supplant the
206
+ system store on macOS and break trust that already worked. `NODE_USE_SYSTEM_CA`
207
+ does nothing on Bun — its default set already includes the system roots.
208
+
209
+ ## Uninstall
210
+
211
+ ```sh
212
+ # app + launcher (keeps your GitHub token)
213
+ curl -fsSL https://raw.githubusercontent.com/semanticist21/clco/main/uninstall.sh | bash
214
+
215
+ # also wipe ~/.config/clco, token included
216
+ curl -fsSL https://raw.githubusercontent.com/semanticist21/clco/main/uninstall.sh | bash -s -- --full
217
+
218
+ # custom installation paths
219
+ curl -fsSL https://raw.githubusercontent.com/semanticist21/clco/main/uninstall.sh \
220
+ | CLCO_DIR="$HOME/somewhere" BIN_DIR="$HOME/bin" bash
221
+ ```
222
+
223
+ ## How it works
224
+
225
+ 1. One-time GitHub OAuth device flow → a long-lived token stored locally.
226
+ 2. That token mints short-lived Copilot tokens while a local adapter
227
+ (127.0.0.1, ephemeral port) runs.
228
+ 3. claude is launched against that adapter with `--settings`, and against a
229
+ private `CLAUDE_CONFIG_DIR` at `~/.config/clco/claude-home`. Your plugins,
230
+ skills, agents and commands are symlinked in, so they stay shared and live;
231
+ the files claude writes back — including the model a `/model` pick saves as
232
+ your default — stay inside clco's directory and never reach `~/.claude`.
233
+ 4. The adapter picks a route per model from Copilot's own `/models`:
234
+ - **native** — Copilot serves Claude models on `/v1/messages`, the real
235
+ Anthropic endpoint, so those requests pass through untranslated: thinking,
236
+ `cache_control`, `/effort` and tool blocks stay intact.
237
+ - **translated** — everything else becomes `chat/completions`, or the
238
+ Responses API where a model requires it (the GPT-5.x family). SSE
239
+ streaming, tool calling, images and parallel tool calls work on both.
240
+
241
+ A rejected native attempt falls back to the translated path. Only an
242
+ unsupported model/endpoint rejection (including HTTP 404) is remembered;
243
+ other request errors leave native routing available on the next turn.
244
+ Route changes are recorded in the adapter log (`CLCO_DEBUG=1` in run mode).
245
+ `CLCO_NO_PASSTHROUGH=1` forces translation everywhere.
246
+
247
+ Input token counts are approximate. Near the discovered limit, the adapter
248
+ logs a warning and forwards the request; the upstream decides whether it
249
+ fits. Catalog-known Claude rows use Claude Code's own per-model context
250
+ handling after `/model` switches. If the lineup contains a non-catalog row,
251
+ clco applies a conservative session ceiling based on the exact selectable
252
+ rows.
253
+
254
+ ## Development
255
+
256
+ ```sh
257
+ bun install
258
+ bun test # unit + mock-upstream integration tests
259
+ bun run check # tsc --noEmit
260
+ CLCO_UPSTREAM=http://127.0.0.1:9099 bun run scripts/mock-upstream.ts
261
+ ```
262
+
263
+ ## Caveats
264
+
265
+ - Using Copilot outside official clients is a gray area of GitHub's terms. clco
266
+ authenticates with the VS Code Copilot OAuth client id and sends the same
267
+ editor headers, so its requests are indistinguishable from the official
268
+ client — that is what makes it work, and what makes it a gray area. Heavy use
269
+ may flag your account, and Claude models consume premium quota. Intended for
270
+ personal use.
271
+ - Extended thinking works on the native route only; it stays disabled on
272
+ translated routes. `stop_sequences` are not enforced on Responses-API models.
273
+ - `/effort` is forwarded when the selected model declares that level, and
274
+ dropped otherwise — rows in `/model` say which do not support it.
275
+ - Catalog-known Claude rows recalculate their context handling when `/model`
276
+ switches. Non-catalog rows use a conservative session ceiling because
277
+ Claude Code cannot inspect their upstream context limit; this can compact
278
+ earlier than a larger model requires.
279
+ - License: [MIT](LICENSE)
package/bin/clco ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ CLCO_LAUNCHER="${BASH_SOURCE[0]}"
5
+ while [ -L "$CLCO_LAUNCHER" ]; do
6
+ CLCO_LAUNCHER_DIR="$(cd -P -- "$(dirname -- "$CLCO_LAUNCHER")" && pwd)"
7
+ CLCO_LAUNCHER="$(readlink -- "$CLCO_LAUNCHER")"
8
+ case "$CLCO_LAUNCHER" in
9
+ /*) ;;
10
+ *) CLCO_LAUNCHER="$CLCO_LAUNCHER_DIR/$CLCO_LAUNCHER" ;;
11
+ esac
12
+ done
13
+ CLCO_PACKAGE_DIR="$(cd -P -- "$(dirname -- "$CLCO_LAUNCHER")/.." && pwd)"
14
+ export CLCO_NPM_INSTALL=1
15
+ CLCO_BUN_BIN="${CLCO_BUN_BIN:-$(command -v bun || true)}"
16
+ if [ -z "$CLCO_BUN_BIN" ]; then
17
+ printf '%s\n' "Bun is required to run clco. Install it with: curl -fsSL https://bun.sh/install | bash" >&2
18
+ printf '%s\n' "Node is not a runtime fallback yet; see https://bun.sh/docs/installation" >&2
19
+ exit 1
20
+ fi
21
+
22
+ if [ "${1:-}" = "--" ]; then
23
+ shift
24
+ set -- "__clco_passthrough__" "$@"
25
+ fi
26
+
27
+ exec "$CLCO_BUN_BIN" run --no-install "$CLCO_PACKAGE_DIR/src/cli.ts" "$@"
package/bun.lock ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "lockfileVersion": 2,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "clcopilot",
7
+ "dependencies": {
8
+ "@clack/prompts": "^1.8.0",
9
+ },
10
+ "devDependencies": {
11
+ "@types/bun": "latest",
12
+ "typescript": "^5",
13
+ },
14
+ },
15
+ },
16
+ "packages": {
17
+ "@clack/core": ["@clack/core@1.5.0", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-zNikCcd8BbcEvzzG1sbXFrRHFk5kHPrpwZwksPvf9qyQO1Teb7JaXaOAxXZei9nZLDW0gaZawiuTCji88bTBhw=="],
18
+
19
+ "@clack/prompts": ["@clack/prompts@1.8.0", "", { "dependencies": { "@clack/core": "1.5.0", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-PXzLZ8N34rxmuo4dJg3xtOXhcBse94qGjDqsteoEYrFrrZ5FSjIGwMAuOcv64ln8rHVBBD06XeVGr+/JX+plcA=="],
20
+
21
+ "@types/bun": ["@types/bun@1.4.2", "", { "dependencies": { "bun-types": "1.4.2" } }, "sha512-GimotNn7+ZV0uVArItBbriZsR1oNf0+WTzPkdcFrzShI7k2norL0uzEaJT8T33dWr7O/c9ZDuAFQrctKCi72oQ=="],
22
+
23
+ "@types/node": ["@types/node@22.20.2", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-xlvWf4Vs9n1PEVYwP1n4vvG07M6y8WgvJ2t0vbrWTmijsIHp1cS+uJ2kMIRdY3nHZK0nCYKrPeD171+SzF4/zw=="],
24
+
25
+ "bun-types": ["bun-types@1.4.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w=="],
26
+
27
+ "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="],
28
+
29
+ "fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="],
30
+
31
+ "fast-wrap-ansi": ["fast-wrap-ansi@0.2.2", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q=="],
32
+
33
+ "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
34
+
35
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
36
+
37
+ "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
38
+ }
39
+ }
package/install.sh ADDED
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env bash
2
+ # clco installer — GitHub Copilot-backed Claude Code wrapper.
3
+ # curl -fsSL https://raw.githubusercontent.com/semanticist21/clco/main/install.sh | bash
4
+ set -euo pipefail
5
+
6
+ REPO="https://github.com/semanticist21/clco.git"
7
+ CLCO_DIR="${CLCO_DIR:-$HOME/.local/share/clco}"
8
+ BIN_DIR="${BIN_DIR:-$HOME/.local/bin}"
9
+
10
+ log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; }
11
+ fail() { printf '\033[1;31mError:\033[0m %s\n' "$*" >&2; exit 1; }
12
+
13
+ command -v git >/dev/null 2>&1 || fail "git is required - please install it first"
14
+
15
+ install_dependencies() {
16
+ local target="$1"
17
+ local frozen_output
18
+ log "Installing dependencies (bun install)"
19
+ if frozen_output="$(cd "$target" && bun install --frozen-lockfile 2>&1)"; then
20
+ return 0
21
+ fi
22
+ if [[ "$frozen_output" != *"Unknown lockfile version"* &&
23
+ "$frozen_output" != *"UnknownLockfileVersion"* &&
24
+ "$frozen_output" != *"failed to parse lockfile"* ]]; then
25
+ printf '%s\n' "$frozen_output" >&2
26
+ return 1
27
+ fi
28
+ if (cd "$target" && bun install --no-save >/dev/null 2>&1); then
29
+ return 0
30
+ fi
31
+ log "bun install failed - full output:"
32
+ printf '%s\n' "$frozen_output" >&2
33
+ (cd "$target" && bun install --no-save) || return 1
34
+ }
35
+
36
+ # --- Bun --------------------------------------------------------------------
37
+ if ! command -v bun >/dev/null 2>&1; then
38
+ fail "Bun is not installed. Please install using: curl -fsSL https://bun.sh/install | bash. Or visit https://bun.sh/docs/installation to install it."
39
+ fi
40
+
41
+ # --- Source ------------------------------------------------------------------
42
+ if [ ! -e "$CLCO_DIR" ] && [ -d "$CLCO_DIR.previous/.git" ]; then
43
+ log "Recovering the previous install after an interrupted update"
44
+ mv "$CLCO_DIR.previous" "$CLCO_DIR" \
45
+ || fail "Could not recover the previous clco install"
46
+ fi
47
+ if [ -d "$CLCO_DIR/.git" ]; then
48
+ log "Updating the existing install: $CLCO_DIR"
49
+ grep -Eq '^[[:space:]]*"name"[[:space:]]*:[[:space:]]*"(@semanticist14/)?clco"[[:space:]]*,?[[:space:]]*$' \
50
+ "$CLCO_DIR/package.json" \
51
+ || fail "$CLCO_DIR is a git repo, but not a clco install"
52
+ CURRENT_URL="$(git -C "$CLCO_DIR" remote get-url origin 2>/dev/null || true)"
53
+ case "$CURRENT_URL" in
54
+ "$REPO"|git@github.com:semanticist21/clco.git) ;;
55
+ *) fail "$CLCO_DIR does not point to the canonical clco repository" ;;
56
+ esac
57
+ [ -z "$(git -C "$CLCO_DIR" status --porcelain)" ] \
58
+ || fail "Update aborted - local changes exist in $CLCO_DIR; commit or stash them first"
59
+ CURRENT_HEAD="$(git -C "$CLCO_DIR" rev-parse HEAD)" \
60
+ || fail "Could not read the current revision in $CLCO_DIR"
61
+ BRANCH="$(git -C "$CLCO_DIR" rev-parse --abbrev-ref HEAD)" \
62
+ || fail "Could not read the current branch in $CLCO_DIR"
63
+ [ "$BRANCH" != HEAD ] || fail "Update requires a checked-out branch in $CLCO_DIR"
64
+
65
+ UPDATE_LOCK="$CLCO_DIR.update.lock"
66
+ mkdir "$UPDATE_LOCK" \
67
+ || fail "Another clco update is already running (or left an update lock; remove it after verifying)"
68
+ STAGE_ROOT="$(mktemp -d "$(dirname "$CLCO_DIR")/.clco-install.XXXXXX")" \
69
+ || fail "Could not create a temporary update directory"
70
+ STAGE="$STAGE_ROOT/app"
71
+ trap 'rm -rf "$STAGE_ROOT" "$UPDATE_LOCK"' EXIT
72
+ git clone --local "$CLCO_DIR" "$STAGE" >/dev/null \
73
+ || fail "Could not create a staged update checkout"
74
+ git -C "$STAGE" remote set-url origin "$REPO" 2>/dev/null \
75
+ || git -C "$STAGE" remote add origin "$REPO" \
76
+ || fail "Could not set the canonical clco origin"
77
+ git -C "$STAGE" fetch --quiet origin "$BRANCH" \
78
+ || fail "Could not fetch the latest clco revision"
79
+ git -C "$STAGE" merge-base --is-ancestor "$CURRENT_HEAD" "origin/$BRANCH" \
80
+ || fail "Update is not a fast-forward - resolve the branch manually first"
81
+ git -C "$STAGE" checkout --quiet -B "$BRANCH" "origin/$BRANCH" \
82
+ || fail "Could not check out the staged clco revision"
83
+ install_dependencies "$STAGE" \
84
+ || fail "bun install failed - the previous revision is still active"
85
+ (cd "$STAGE" && bun run --no-install src/cli.ts version >/dev/null) \
86
+ || fail "Updated checkout failed its smoke test - the previous revision is still active"
87
+
88
+ LIVE_HEAD="$(git -C "$CLCO_DIR" rev-parse HEAD)" \
89
+ || fail "Could not re-check the live revision before activation"
90
+ LIVE_STATUS="$(git -C "$CLCO_DIR" status --porcelain)" \
91
+ || fail "Could not re-check the live checkout before activation"
92
+ [ "$LIVE_HEAD" = "$CURRENT_HEAD" ] && [ -z "$LIVE_STATUS" ] \
93
+ || fail "The live checkout changed while it was being updated"
94
+ git -C "$CLCO_DIR" config --replace-all remote.origin.url "$REPO" \
95
+ || fail "Could not sanitize the clco origin before activation"
96
+ git -C "$CLCO_DIR" config --unset-all remote.origin.pushurl 2>/dev/null || true
97
+ [ -z "$(git -C "$CLCO_DIR" config --get-all remote.origin.pushurl 2>/dev/null || true)" ] \
98
+ || fail "Could not remove credentials from the clco origin before activation"
99
+ if [ -e "$CLCO_DIR.previous" ]; then
100
+ [ -d "$CLCO_DIR.previous/.git" ] \
101
+ || fail "$CLCO_DIR.previous is not a clco rollback directory - refusing to replace it"
102
+ rm -rf "$CLCO_DIR.previous"
103
+ fi
104
+ mv "$CLCO_DIR" "$CLCO_DIR.previous" \
105
+ || fail "Could not prepare the existing install for activation"
106
+ if ! mv "$STAGE" "$CLCO_DIR"; then
107
+ mv "$CLCO_DIR.previous" "$CLCO_DIR" || true
108
+ fail "Could not activate the staged install"
109
+ fi
110
+ trap - EXIT
111
+ rm -rf "$STAGE_ROOT"
112
+ rmdir "$UPDATE_LOCK" 2>/dev/null || true
113
+ NEEDS_INSTALL=0
114
+ else
115
+ [ -e "$CLCO_DIR" ] && fail "$CLCO_DIR already exists and is not a git repo - remove it and retry"
116
+ log "Cloning into $CLCO_DIR"
117
+ git clone --depth 1 "$REPO" "$CLCO_DIR"
118
+ NEEDS_INSTALL=1
119
+ fi
120
+
121
+ if [ "$NEEDS_INSTALL" = 1 ]; then
122
+ install_dependencies "$CLCO_DIR" || fail "bun install failed"
123
+ fi
124
+
125
+ # --- claude CLI (required by clco; offer to install) -------------------------
126
+ CLAUDE_WARN="Install it later with: curl -fsSL https://claude.ai/install.sh | bash"
127
+ if command -v claude >/dev/null 2>&1; then
128
+ log "Found the claude CLI"
129
+ else
130
+ log "The claude CLI is missing (clco needs it)"
131
+ INSTALL_CLAUDE=n
132
+ if [ -e /dev/tty ]; then
133
+ printf 'Install it now? [y/N] '
134
+ answer=n
135
+ read -r answer < /dev/tty || answer=n
136
+ case "$answer" in y|Y|yes|Yes) INSTALL_CLAUDE=y ;; esac
137
+ fi
138
+ if [ "$INSTALL_CLAUDE" = y ]; then
139
+ log "Installing the claude CLI (official script)"
140
+ curl -fsSL https://claude.ai/install.sh | bash
141
+ export PATH="$HOME/.local/bin:$PATH"
142
+ command -v claude >/dev/null 2>&1 \
143
+ || log "! Could not verify the claude install - reopen your terminal and check. $CLAUDE_WARN"
144
+ else
145
+ log "! Skipped - clco needs the claude CLI before it can run. $CLAUDE_WARN"
146
+ fi
147
+ fi
148
+
149
+ # --- Launcher ----------------------------------------------------------------
150
+ # Generated by a script the repo owns, so `clco update` refreshes it too.
151
+ bash "$CLCO_DIR/scripts/write-launcher.sh" "$CLCO_DIR" "$BIN_DIR"
152
+
153
+
154
+
155
+ case ":$PATH:" in
156
+ *":$BIN_DIR:"*) ;;
157
+ *) printf '\033[1;33mNote:\033[0m %s is not on your PATH. Add it to ~/.zshrc or similar:\n export PATH="%s:$PATH"\n' "$BIN_DIR" "$BIN_DIR" ;;
158
+ esac
159
+
160
+ log "Installed: $BIN_DIR/clco"
161
+ printf '\nGetting started:\n clco first run: GitHub device login, pick a model, launch claude\n clco setup set your startup defaults\n clco --help full usage\n\nUninstall:\n curl -fsSL https://raw.githubusercontent.com/semanticist21/clco/main/uninstall.sh | bash\n'
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@semanticist14/clco",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Run Claude Code on a GitHub Copilot subscription",
7
+ "bin": {
8
+ "clco": "bin/clco"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "files": [
14
+ "bin",
15
+ "src",
16
+ "scripts",
17
+ "install.sh",
18
+ "uninstall.sh",
19
+ "README.md",
20
+ "LICENSE",
21
+ "bun.lock"
22
+ ],
23
+ "scripts": {
24
+ "start": "bun run src/cli.ts",
25
+ "test": "bun test",
26
+ "check": "tsc --noEmit"
27
+ },
28
+ "devDependencies": {
29
+ "@types/bun": "latest",
30
+ "typescript": "^5"
31
+ },
32
+ "dependencies": {
33
+ "@clack/prompts": "^1.8.0"
34
+ }
35
+ }