@amdahl/cli 0.1.3 → 0.2.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/README.md CHANGED
@@ -2,11 +2,46 @@
2
2
 
3
3
  The `amdahl` command line. It signs you in with your console account, checks
4
4
  that a workspace can optimize, rewrites messages, manages API keys (each one
5
- approved in the console), and points MCP clients at Amdahl.
5
+ approved in the console), points MCP clients at Amdahl, and installs skills
6
+ from the library at https://amdahl.ai/skills.
7
+
8
+ ## Using the Claude or ChatGPT app? You do not need this CLI
9
+
10
+ The Claude and ChatGPT apps connect to the Amdahl Optimizer as a custom
11
+ connector, with no key and no terminal. The MCP server URL is
12
+ `https://app.amdahl.ai/mcp`.
13
+
14
+ In Claude (web or desktop):
15
+
16
+ 1. Open Settings, then Customize, then Connectors
17
+ (https://claude.ai/customize/connectors).
18
+ 2. Click **+ Add**, then **Add custom connector**.
19
+ 3. Name it **Amdahl** and set the URL to `https://app.amdahl.ai/mcp`.
20
+ 4. Click **Connect**, sign in to Amdahl with your work email, and choose your
21
+ workspace.
22
+ 5. Open a new chat and turn on Amdahl in the **+** menu under Connectors. If
23
+ Amdahl is not listed, reload the app (Cmd+R, or Ctrl+R on Windows).
24
+ 6. Optional: in Amdahl's connector settings, set its tool access to
25
+ **Always available**, so Claude does not have to search for its tools.
26
+ Under **Tool permissions**, you can also set **Read-only tools** to
27
+ **Always allow**. Writing a message is a write call, so Claude asks before
28
+ each one unless you also allow the message tool, `optimize_message`
29
+ (**Custom**, or **Write/delete tools** set to **Always allow**).
30
+ 7. Name the server in your ask: "Use the Amdahl MCP server to optimize this
31
+ draft: ...". Claude does not call Amdahl for a plain "make this better".
32
+
33
+ In the ChatGPT desktop app: open Settings, then Plugins, then **Add**, then
34
+ **Add MCP server**. Choose **Connect to a custom MCP**, name it Amdahl, set the
35
+ type to Streamable HTTP and the URL to `https://app.amdahl.ai/mcp`, save, then
36
+ click **Authenticate** and sign in. In Work mode, ask "Use the Amdahl MCP server
37
+ to optimize this draft: ...".
38
+
39
+ Full steps: https://amdahl.ai/connect
6
40
 
7
41
  ## Install
8
42
 
9
- Needs Node.js 20 or later.
43
+ Needs Node.js 20 or later. On an older Node, `amdahl` stops before it does
44
+ anything, names the version it found, and links the Node.js download page.
10
45
 
11
46
  ```sh
12
47
  # Run once, with no install
@@ -17,6 +52,10 @@ npm i -g @amdahl/cli
17
52
  amdahl login
18
53
  ```
19
54
 
55
+ `amdahl` is also on npm by its short name, so `npx amdahl ...` is the same as
56
+ `npx @amdahl/cli ...`. That package ([packages/amdahl](packages/amdahl)) has no
57
+ code of its own: it depends on `@amdahl/cli` at the same version and runs it.
58
+
20
59
  To connect an AI client, run `amdahl install claude-code` (or `codex`,
21
60
  `cursor`). It adds the Amdahl MCP server and also installs the Amdahl skill,
22
61
  a map of everything Amdahl can do, so your agent knows which tool fits each
@@ -28,9 +67,11 @@ job. The skill goes to the client's user skills folder:
28
67
  | Codex | `~/.agents/skills/amdahl/SKILL.md` |
29
68
  | Cursor | `~/.cursor/skills/amdahl/SKILL.md` |
30
69
 
31
- If that file already exists and you changed it, `install` keeps your copy and
32
- says so; add `--force` to replace it. `--print` shows what it would run and the
33
- path it would write, without changing anything. The same map is published at
70
+ If that file is a copy an earlier `amdahl install` wrote, `install` replaces it
71
+ with the current skill. If it differs from every skill the CLI has shipped (you
72
+ changed it), `install` keeps your copy and says so; add `--force` to replace
73
+ it. `--print` shows what it would run and the path it would write, without
74
+ changing anything. The same map is published at
34
75
  https://docs.amdahl.ai/skills/amdahl/SKILL.md.
35
76
 
36
77
  ## Quick start
@@ -41,6 +82,7 @@ amdahl whoami # the account, workspace and role you are signed in
41
82
  amdahl status # can this workspace optimize, and what blocks it
42
83
  amdahl optimize draft.md # rewrite one message (or pipe it in with -)
43
84
  amdahl install claude-code # connect an AI client and add the Amdahl skill (also codex, cursor)
85
+ amdahl skills add <publisher>/<skill> # add a skill from amdahl.ai/skills to this project
44
86
  ```
45
87
 
46
88
  No workspace yet? `amdahl login` prints the console link where you can
@@ -63,11 +105,129 @@ amdahl keys list
63
105
  amdahl keys revoke <id|prefix> [--yes] [--no-browser]
64
106
  amdahl workspace list | amdahl workspace use <profile>
65
107
  amdahl install claude-code|codex|cursor [--print] [--force]
108
+ amdahl skills add <publisher>/<skill> [-g] [--agents] [--force] [--site <origin>]
66
109
  ```
67
110
 
68
111
  Global flags: `--profile`, `--api-url`, `--api-key`, `--json`, `--no-color`,
69
112
  `-h`, `-v`. With `--json`, data goes to stdout and messages to stderr.
70
113
 
114
+ ## Skills
115
+
116
+ `skills add` installs one skill from the library at https://amdahl.ai/skills.
117
+ It needs no sign-in.
118
+
119
+ ```sh
120
+ npx amdahl skills add <publisher>/<skill>
121
+ # the skill's link works too, with or without .md:
122
+ npx amdahl skills add https://amdahl.ai/skills/<publisher>/<skill>
123
+ ```
124
+
125
+ In a terminal, it asks one question. It prints `Checking <publisher>/<skill>
126
+ on amdahl.ai... OK` while it downloads and checks the skill. Then it names
127
+ the skill and the agents it sees in use, and asks where to install it, in a
128
+ menu:
129
+
130
+ ```
131
+ System diagram, by Amdahl (17 files), for Claude Code and Cursor.
132
+ Install it in:
133
+ > This project (~/proj)
134
+ Every project on this computer
135
+ (Use the arrow keys, then Enter)
136
+ ```
137
+
138
+ Up and Down (or `k` and `j`) move the marker; typing `1` or `2` jumps to
139
+ that row. Enter picks, and the menu shrinks to one line, such as
140
+ `Install it in: This project (~/proj)`. The marked row is in the brand
141
+ colour, or bold when colour is off (`--no-color`, `NO_COLOR`, `TERM=dumb`).
142
+ The pick is the go-ahead: it installs, prints the folders it wrote, and
143
+ says to open a new chat. In your home folder it does not ask, and installs
144
+ for every project. Ctrl+C stops it with nothing written (exit 130).
145
+
146
+ It never asks about agents. A `.claude` folder here or in your home folder,
147
+ or `CLAUDECODE` set, means Claude Code (`.claude/skills`). A `.codex` folder
148
+ means Codex, a `.cursor` folder or `CURSOR_AGENT` means Cursor, and an
149
+ `.agents` folder means other agents (all `.agents/skills`). When it sees
150
+ both kinds, or neither, it writes both folders. To choose yourself, use the
151
+ flags below.
152
+
153
+ If a different copy of the skill is already there, it asks once more, in
154
+ the same kind of menu: "Keep your copy" (where the marker starts) or
155
+ "Replace it". With `--force` it replaces it without asking.
156
+
157
+ It never asks when stdin or stdout is not a terminal (an agent, CI, a pipe),
158
+ or with `--json`, `-y`, `-g` or `--agents`, or with `AMDAHL_NO_PROMPT=1`.
159
+ Then the flags say where. Without `--agents`, it uses the folder of the agent
160
+ that ran it, when that agent says so: Claude Code sets `CLAUDECODE`
161
+ (`.claude/skills`), Cursor sets `CURSOR_AGENT` (`.agents/skills`). Codex
162
+ documents no such variable, so a run from Codex gets the default, Claude
163
+ Code. Without flags it says on stderr where the skill went. `-y` (`--yes`)
164
+ adds it to this project without asking. Neither replaces a different copy
165
+ without `--force`.
166
+
167
+ It reads the skill's index,
168
+ `https://amdahl.ai/skills/<publisher>/<skill>/.well-known/agent-skills/index.json`
169
+ ([Agent Skills Discovery](https://schemas.agentskills.io/discovery/0.2.0/schema.json)
170
+ v0.2.0). The index lists one of two kinds of skill:
171
+
172
+ - `type: "skill-md"`: one SKILL.md. `skills add` downloads it.
173
+ - `type: "archive"`: a folder (the SKILL.md plus scripts, references and
174
+ other files), as one `.tar.gz`. `skills add` downloads it and unpacks it
175
+ with its own reader. It never runs `tar`.
176
+
177
+ It refuses the download, and writes nothing, unless all of these are true:
178
+
179
+ - The index points at a file on the same site.
180
+ - The sha256 of the downloaded bytes equals the index's `digest`. This is
181
+ checked before anything in an archive is read. The index and the file come
182
+ from the same site, so this only catches a corrupted or cut-off download.
183
+ It is not a signature and does not show the file is unchanged: that rests
184
+ on HTTPS and on who can publish to amdahl.ai/skills.
185
+ - The SKILL.md front matter parses as YAML, `name` is a string, and
186
+ `description` is a string that is not empty.
187
+ - `name` equals the skill's name in the index.
188
+
189
+ An archive must also be a plain folder. `skills add` refuses it if:
190
+
191
+ - the gzip CRC-32 or length does not match, or bytes follow the gzip data;
192
+ - a tar header has a wrong checksum, is not ustar, or the tar has no end;
193
+ - a path is absolute, holds `..`, `.` or an empty segment, a backslash or a
194
+ control character, or two entries share a path (also when only the
195
+ case differs);
196
+ - an entry is not a file or a folder (a link, a device, a pax header);
197
+ - a file mode is not 0644 or 0755;
198
+ - it has more than 200 files, or more than 4 MB of file contents;
199
+ - SKILL.md is not at its root.
200
+
201
+ Without the questions, the flags say where it writes, and it prints each
202
+ path it wrote:
203
+
204
+ | flag | where |
205
+ |---|---|
206
+ | (none) or `-y` | `./.claude/skills/<name>/`, for Claude Code in this project |
207
+ | `-g`, `--global` | `~/.claude/skills/<name>/`, for every project |
208
+ | `--agents` | also `.agents/skills/<name>/` (in this project, or in `~` with `-g`), which Codex, Cursor and most other agents read |
209
+
210
+ A `skill-md` skill writes `SKILL.md` in that folder, exactly as downloaded,
211
+ through a temp file renamed into place. It refuses, even with `--force`, when
212
+ the skill folder, the `skills` or `.claude`/`.agents` folder above it, or
213
+ `SKILL.md` itself is a symbolic link, so it never writes outside that folder.
214
+ An `archive` skill unpacks into a hidden temp folder beside each target,
215
+ then renames it into place only when every target is complete. If any step
216
+ fails, it removes what it unpacked and puts back any folder it replaced.
217
+
218
+ If a different file or folder with that name is already there, `skills add`
219
+ stops before it writes anything. Add `--force` to replace it. For a folder
220
+ skill, `--force` replaces the whole folder, with any files you added to it.
221
+ A skill that is already identical is left as it is.
222
+
223
+ It sends two requests, both to amdahl.ai: the index, then the file. It
224
+ follows no redirect and sends no telemetry. It waits at most 15 seconds for
225
+ each request. It refuses an index over 256 KB, a SKILL.md over 64 KB (the
226
+ most amdahl.ai accepts) and an archive over 4 MB. Each refusal writes
227
+ nothing. `--site <origin>` sends both requests to another origin, for
228
+ example a local copy of the site (`--site http://localhost:6782`). The
229
+ origin must be `https`; `http` is allowed only for localhost.
230
+
71
231
  ## Targeting staging
72
232
 
73
233
  ```sh
@@ -106,7 +266,7 @@ code shown in the terminal; then the key is printed once and stored nowhere.
106
266
  | code | meaning |
107
267
  |---|---|
108
268
  | 0 | ok |
109
- | 1 | general (`login_timeout`, `state_mismatch`, optimize `ok: false`) |
269
+ | 1 | general (`login_timeout`, `state_mismatch`, optimize `ok: false`, a `skills add` refusal) |
110
270
  | 2 | usage |
111
271
  | 3 | not signed in (401, `invalid_grant`, no credential) |
112
272
  | 4 | forbidden (403, `oauth_required`, `cli_client_required`, `not_admin`) |
@@ -114,6 +274,7 @@ code shown in the terminal; then the key is printed once and stored nowhere.
114
274
  | 6 | rate limited or quota (429) |
115
275
  | 7 | approval denied, expired or locked out |
116
276
  | 8 | network or 5xx |
277
+ | 130 | stopped with Ctrl+C at a question (nothing was written) |
117
278
 
118
279
  ## Development
119
280
 
@@ -125,8 +286,8 @@ pnpm install
125
286
  pnpm build # dist/amdahl.js, one file, no runtime deps
126
287
  pnpm test # vitest, against a local mock server
127
288
  pnpm type-check
128
- pnpm smoke:pack # pack, install offline, run --version
129
- pnpm sync:skill # refresh skill/SKILL.md from docs.amdahl.ai, then commit it
289
+ pnpm smoke:pack # pack both packages, install offline, run --help and --version
290
+ pnpm sync:skill # refresh skill/SKILL.md from docs.amdahl.ai (and skill/shipped.json), then commit both
130
291
  ```
131
292
 
132
293
  See [CONTRIBUTING.md](CONTRIBUTING.md). Report security issues as described in
@@ -136,9 +297,10 @@ See [CONTRIBUTING.md](CONTRIBUTING.md). Report security issues as described in
136
297
 
137
298
  `.github/workflows/publish.yml` publishes to npm. To release:
138
299
 
139
- 1. In a PR, bump the version in `package.json` and `src/version.ts` together.
140
- The workflow refuses a version that does not match both, or one that is
141
- already on npm.
300
+ 1. In a PR, bump the version in `package.json` and `src/version.ts` together,
301
+ and in `packages/amdahl/package.json` both its `version` and its
302
+ `@amdahl/cli` dependency. The workflow refuses a version that does not
303
+ match the first two, or one that is already on npm; a test checks the third.
142
304
  2. After it merges, push a tag `cli-v<version>` (for example `cli-v0.2.0`). A
143
305
  version with a `-` (for example `0.2.0-rc.1`) goes to the `next` dist-tag;
144
306
  anything else goes to `latest`.
@@ -154,3 +316,26 @@ the `npm` environment, so no long-lived npm token is needed. Every release
154
316
  carries a signed [provenance attestation](https://docs.npmjs.com/generating-provenance-statements)
155
317
  that links the package on npm to the commit and workflow run that built it.
156
318
  Check it with `npm audit signatures` or on the package page on npmjs.com.
319
+
320
+ The workflow publishes `amdahl` (the short name) right after `@amdahl/cli`,
321
+ from the same run, only when the repository variable `NPM_PUBLISH_AMDAHL` is
322
+ `true`. Amdahl already holds the `amdahl` name on npm (a 0.0.1 placeholder
323
+ with no bin), so trusted publishing can be set up for it now. Before you push
324
+ the tag:
325
+
326
+ 1. On npmjs.com, `amdahl`, Settings, add a trusted publisher: GitHub Actions,
327
+ `amdahlco/amdahl-cli`, workflow `publish.yml`, environment `npm`.
328
+ 2. Set the repository variable `NPM_PUBLISH_AMDAHL` to `true`.
329
+
330
+ Then each `cli-v<version>` tag publishes both packages from CI, both with
331
+ provenance. Each publish step skips a version already on npm, so if the
332
+ `amdahl` step fails, use **Re-run failed jobs** on that run. (Re-running every
333
+ job stops at the check that the version is not on npm yet.)
334
+
335
+ Only if CI cannot publish `amdahl`, publish it by hand as a fallback: download
336
+ the `tarball` artifact from the run (it holds `amdahl-<version>.tgz`, already
337
+ checked), then, as an owner of the `amdahl` npm org with 2FA, run
338
+ `npm publish amdahl-<version>.tgz --access public --provenance=false`.
339
+ `publishConfig` asks for provenance, which a laptop cannot give, so without
340
+ `--provenance=false` the publish is expected to fail. That version has no
341
+ provenance.
package/bin/amdahl.js ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ // bin/amdahl.js
3
+ //
4
+ // The `amdahl` command. It checks the Node.js version before it loads the
5
+ // CLI: dist/amdahl.js is built for Node 20, and an older Node fails to parse
6
+ // it or crashes in it, while npx only warns about `engines`. So this file
7
+ // uses no syntax newer than Node 14 (no `??=`, no top-level await, no class
8
+ // fields), and an old Node can run it far enough to say what to do.
9
+
10
+ var version = process.versions.node
11
+ var major = parseInt(version.split('.')[0], 10)
12
+
13
+ if (!(major >= 20)) {
14
+ process.stderr.write(
15
+ 'amdahl needs Node.js 20 or later. This computer has Node.js ' +
16
+ version +
17
+ '.\nInstall a newer one from https://nodejs.org/en/download, then run the same command again.\n'
18
+ )
19
+ process.exitCode = 1
20
+ } else {
21
+ import('../dist/amdahl.js')
22
+ }