zas-agent 0.2.1 → 0.3.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/CHANGELOG.md CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-09-03
11
+
12
+ ### Added
13
+
14
+ - Pairing again in a profile that is already paired replaces the agent
15
+ instead of adding a second one. The terminal opens the pairing as the old
16
+ agent, the approval page says which agent it replaces and fills in its
17
+ name and channels, and the old agent is revoked in the same step that
18
+ creates the new one. If the server no longer accepts the old agent, `pair`
19
+ says so and creates a new agent.
20
+
21
+ ### Changed
22
+
23
+ - The help text, the tool descriptions and the README say "this agent" where
24
+ they said "this machine": a pairing is one profile's key pair, and one
25
+ machine can hold several.
26
+
10
27
  ## [0.2.1] - 2026-09-03
11
28
 
12
29
  ### Fixed
@@ -58,7 +75,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
58
75
  two separate identities that cannot read each other's keys.
59
76
  - Install snippets for Claude Code and Codex, printed by `zas-agent pair`.
60
77
 
61
- [Unreleased]: https://github.com/soke1556/zas-agent/compare/v0.2.1...HEAD
78
+ [Unreleased]: https://github.com/soke1556/zas-agent/compare/v0.3.0...HEAD
79
+ [0.3.0]: https://github.com/soke1556/zas-agent/compare/v0.2.1...v0.3.0
62
80
  [0.2.1]: https://github.com/soke1556/zas-agent/compare/v0.2.0...v0.2.1
63
81
  [0.2.0]: https://github.com/soke1556/zas-agent/compare/v0.1.1...v0.2.0
64
82
  [0.1.1]: https://github.com/soke1556/zas-agent/compare/v0.1.0...v0.1.1
package/README.md CHANGED
@@ -35,7 +35,7 @@ get a short-lived session.
35
35
 
36
36
  ## Install
37
37
 
38
- Pair the machine first, then hand the command to your harness.
38
+ Pair the agent first, then hand the command to your harness.
39
39
 
40
40
  **Pair**
41
41
 
@@ -168,7 +168,7 @@ string ever reaches a terminal.
168
168
 
169
169
  | Code | What it means |
170
170
  | --- | --- |
171
- | `not_paired` | This machine is not paired yet. |
171
+ | `not_paired` | This agent is not paired yet. |
172
172
  | `identity_corrupt` | The identity file on disk is damaged. |
173
173
  | `agent_revoked` | The owner revoked this agent. |
174
174
  | `agent_forbidden` | Only the account owner can do that. |
@@ -203,8 +203,8 @@ stack trace.
203
203
 
204
204
  | Tool | What it does |
205
205
  | --- | --- |
206
- | `zas_status` | Says whether this machine is paired with a Zas account, and lists the owner's channels this agent may send to or read from. |
207
- | `zas_pair` | Pairs this machine with a Zas account. The first call returns a URL for the owner to open; a later call says whether they approved. If the page shows a code, a call with `code` claims with it. |
206
+ | `zas_status` | Says whether this agent is paired with a Zas account, and lists the owner's channels it may send to or read from. |
207
+ | `zas_pair` | Pairs this agent with a Zas account. The first call returns a URL for the owner to open; a later call says whether they approved. If the page shows a code, a call with `code` claims with it. In a profile that is already paired, approval replaces the old agent. |
208
208
  | `zas_send_file` | Sends a file from this machine into one of the owner's channels. Returns the item id, or a job id when the upload takes longer than a minute. |
209
209
  | `zas_send_note` | Sends a note — plain text, or a code snippet with its language — into one of the owner's channels. |
210
210
  | `zas_list_items` | Lists the most recent items in one of the owner's channels. Needs a grant that includes reading. |
@@ -260,6 +260,22 @@ Deleting the directory makes this machine forget the agent. It does not revoke
260
260
  anything: the account side is closed from the web app, under
261
261
  Settings → Agents → Revoke.
262
262
 
263
+ ## Pairing again
264
+
265
+ A pairing is one profile's key pair, not the machine and not the program. A
266
+ profile holds one agent. Running `zas-agent pair` in a profile that is already
267
+ paired opens a replacement: the terminal signs the request with the old
268
+ identity, the approval page says which agent it replaces and fills in its name
269
+ and channels, and the old agent is revoked in the same step that creates the
270
+ new one. Its row stays under Settings → Agents as revoked until you remove it,
271
+ and it keeps costing a slot until then. If the old agent was already revoked
272
+ or removed, `pair` says so and creates a new agent.
273
+
274
+ One machine that runs Claude Code and Codex has two profiles (`claude-code`
275
+ and `codex`), two agents, two approvals and two rows. Revoking one does not
276
+ touch the other. Two programs pointed at the same `--profile` act as one
277
+ agent: same keys, same channels, one row.
278
+
263
279
  ## Configuration
264
280
 
265
281
  | Setting | Default | What it changes |
package/dist/cli.js CHANGED
@@ -604,22 +604,28 @@ async function runPair(opts) {
604
604
  const sleep2 = opts.sleep ?? defaultSleep;
605
605
  const now = opts.now ?? (() => Date.now());
606
606
  const existing = loadIdentity(opts.profile);
607
- if (existing) {
608
- opts.log(
609
- `This profile is already paired as \u201C${existing.name}\u201D. A new agent will be created; revoke the old one from Settings \u2192 Agents.`
610
- );
611
- }
612
607
  const host = (opts.host ?? hostname()).slice(0, AGENT_HOST_MAX);
613
608
  const keys = newKeyMaterial();
614
609
  const endpoints = { ...defaultEndpoints(), api_base: opts.apiBase };
615
- const created = await apiPublic(
616
- opts.apiBase,
617
- "POST",
618
- "/v1/agents/pairings",
619
- { kind: opts.kind, host, x25519_public: keys.x25519_public, p256_public: keys.p256_public, claim: true },
620
- {},
621
- opts.fetch
622
- );
610
+ const request = { kind: opts.kind, host, x25519_public: keys.x25519_public, p256_public: keys.p256_public, claim: true };
611
+ let created = null;
612
+ let replacing = false;
613
+ if (existing) {
614
+ try {
615
+ const old = new ZasClient({ ...existing, api_base: opts.apiBase }, { fetch: opts.fetch, now });
616
+ created = await old.api("POST", "/agents/me/pairings", request);
617
+ replacing = true;
618
+ opts.log(`This profile is paired as \u201C${existing.name}\u201D. Approving this pairing replaces that agent.`);
619
+ } catch (err) {
620
+ const code = err instanceof ZasError ? err.code : "network";
621
+ opts.log(
622
+ `This profile was paired as \u201C${existing.name}\u201D, but the server no longer accepts that agent (${code}). A new agent will be created; remove the old one from Settings \u2192 Agents.`
623
+ );
624
+ }
625
+ }
626
+ if (!created) {
627
+ created = await apiPublic(opts.apiBase, "POST", "/v1/agents/pairings", request, {}, opts.fetch);
628
+ }
623
629
  let claimed = null;
624
630
  let inFlight = null;
625
631
  const wake = deferred();
@@ -780,6 +786,7 @@ async function runPair(opts) {
780
786
  opts.log(
781
787
  [
782
788
  `Done: the agent \u201C${identity.name}\u201D is paired with your account.`,
789
+ ...replacing ? ["It replaces the previous agent of this profile, which is now revoked."] : [],
783
790
  ...install
784
791
  ].join("\n")
785
792
  );
@@ -1869,7 +1876,7 @@ async function sendNote(ctx, input) {
1869
1876
 
1870
1877
  // src/server.ts
1871
1878
  function agentVersion() {
1872
- return true ? "0.2.1" : "0.0.0-dev";
1879
+ return true ? "0.3.0" : "0.0.0-dev";
1873
1880
  }
1874
1881
  var AGENT_CUE = " The owner sees every item this agent sends with the >_ agent mark and this agent's name, on every device.";
1875
1882
  var PAIR_ANNOUNCE_MS = 15e3;
@@ -1913,7 +1920,7 @@ function buildServer(profile, deps = {}) {
1913
1920
  return text2({ job_id: job.id, status: "running", phase: job.phase });
1914
1921
  };
1915
1922
  server.registerTool("zas_status", {
1916
- description: "Say whether this machine is paired with a Zas account, and list the owner's channels this agent may send to or read from." + AGENT_CUE
1923
+ description: "Say whether this agent is paired with a Zas account, and list the owner's channels it may send to or read from." + AGENT_CUE
1917
1924
  }, async () => {
1918
1925
  try {
1919
1926
  if (!(deps.identity ?? loadIdentity(profile))) {
@@ -1941,7 +1948,7 @@ function buildServer(profile, deps = {}) {
1941
1948
  });
1942
1949
  let pairing = null;
1943
1950
  server.registerTool("zas_pair", {
1944
- description: "Pair this machine with a Zas account. The first call returns a URL for the owner to open; a later call says whether they approved. If the page shows a code, call again with `code`." + AGENT_CUE,
1951
+ description: "Pair this agent with a Zas account. The first call returns a URL for the owner to open; a later call says whether they approved. If the page shows a code, call again with `code`. In a profile that is already paired, approval replaces the old agent." + AGENT_CUE,
1945
1952
  inputSchema: {
1946
1953
  code: z.string().optional().describe("The code the pairing page shows when the browser could not reach this machine.")
1947
1954
  }
@@ -2123,7 +2130,7 @@ var USAGE = [
2123
2130
  "zas-agent \u2014 send files and notes from a coding agent into your Zas channels.",
2124
2131
  "",
2125
2132
  " zas-agent [--profile <name>] serve the MCP tools over stdio",
2126
- " zas-agent pair [--profile <name>] pair this machine with a Zas account",
2133
+ " zas-agent pair [--profile <name>] pair this agent with a Zas account",
2127
2134
  " [--kind claude_code|codex|other] [--host <name>] [--no-open]",
2128
2135
  " zas-agent --version"
2129
2136
  ].join("\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zas-agent",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server that lets a coding agent send files and notes into your Zas channels.",
5
5
  "type": "module",
6
6
  "license": "MIT",