zas-agent 0.1.0 → 0.1.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/CHANGELOG.md +13 -1
- package/README.md +23 -9
- package/dist/cli.js +51 -51
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.1] - 2026-09-02
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Every line the agent prints and every tool result is in English. The pairing
|
|
15
|
+
prompt, the status report and the error sentences were in Spanish.
|
|
16
|
+
- The README opens with what Zas is. The README, the package keywords and the
|
|
17
|
+
registry entry no longer call the design end-to-end encryption: the
|
|
18
|
+
product's own privacy page does not, because a separate service derives
|
|
19
|
+
account keys.
|
|
20
|
+
|
|
10
21
|
## [0.1.0] - 2026-09-02
|
|
11
22
|
|
|
12
23
|
### Added
|
|
@@ -25,5 +36,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
25
36
|
two separate identities that cannot read each other's keys.
|
|
26
37
|
- Install snippets for Claude Code and Codex, printed by `zas-agent pair`.
|
|
27
38
|
|
|
28
|
-
[Unreleased]: https://github.com/soke1556/zas-agent/compare/v0.1.
|
|
39
|
+
[Unreleased]: https://github.com/soke1556/zas-agent/compare/v0.1.1...HEAD
|
|
40
|
+
[0.1.1]: https://github.com/soke1556/zas-agent/compare/v0.1.0...v0.1.1
|
|
29
41
|
[0.1.0]: https://github.com/soke1556/zas-agent/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -10,6 +10,19 @@ read items back out of them.
|
|
|
10
10
|
[](https://scorecard.dev/viewer/?uri=github.com/soke1556/zas-agent)
|
|
11
11
|
[](LICENSE)
|
|
12
12
|
|
|
13
|
+
## What is Zas
|
|
14
|
+
|
|
15
|
+
[Zas](https://zas.red) moves things between your devices and the people you
|
|
16
|
+
choose: files, photos, text, code. You put something into a channel on one
|
|
17
|
+
device and use it on another. Whatever you do not pin leaves on its own after a
|
|
18
|
+
few days, so there is nothing to tidy up later. Everything is encrypted on the
|
|
19
|
+
device before it is uploaded: the database and the object storage receive
|
|
20
|
+
encrypted bytes, not filenames, content or previews.
|
|
21
|
+
|
|
22
|
+
This package is the piece that lets a coding agent use your channels the way
|
|
23
|
+
your other devices do, under an identity of its own that you approve, that you
|
|
24
|
+
scope to the channels you choose, and that you can revoke.
|
|
25
|
+
|
|
13
26
|
## What it does
|
|
14
27
|
|
|
15
28
|
Your coding agent gets seven tools. It can send a file or a note into a channel
|
|
@@ -56,11 +69,11 @@ args = ["-y", "zas-agent", "--profile", "codex"]
|
|
|
56
69
|
terminal shows four things:
|
|
57
70
|
|
|
58
71
|
```
|
|
59
|
-
|
|
72
|
+
Open this page signed in to your Zas account:
|
|
60
73
|
https://zas.red/agents/pair?p=...
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
74
|
+
Code: ABCD-EFGH
|
|
75
|
+
Fingerprint: 1a2b 3c4d 5e6f 7a8b
|
|
76
|
+
Waiting for approval… (expires in 10 minutes)
|
|
64
77
|
```
|
|
65
78
|
|
|
66
79
|
Open the link signed in to your Zas account. Check that the fingerprint on the
|
|
@@ -98,11 +111,12 @@ source, the sentence says so.
|
|
|
98
111
|
- **Everything it sends is visible as its work.** Every item carries the `>_`
|
|
99
112
|
agent mark and the agent's name, in the channel, on every device you read Zas
|
|
100
113
|
from.
|
|
101
|
-
- **You can revoke it at any time.** Settings →
|
|
114
|
+
- **You can revoke it at any time.** Settings → Agents → the agent → Revoke.
|
|
102
115
|
The session stops, its refresh tokens are revoked *(server-side)*, and the
|
|
103
116
|
next tool call answers "the owner revoked this agent". What it already sent
|
|
104
117
|
stays where it is. You can also drop a single channel and keep the rest.
|
|
105
|
-
- **Content is
|
|
118
|
+
- **Content is encrypted on your machine before it leaves, the same way the
|
|
119
|
+
app does it.** The
|
|
106
120
|
chunking, the manifest and the envelope formats under `src/shared/` are the
|
|
107
121
|
same modules the Zas web app ships. The server stores ciphertext and never
|
|
108
122
|
sees a channel key, a channel name or item plaintext *(server-side)*.
|
|
@@ -131,7 +145,7 @@ ones marked *(server-side)* are enforced by the Zas server.
|
|
|
131
145
|
- **Rate limited by the server**, on its own buckets, with the key-derivation
|
|
132
146
|
budget charged to your account so ten agents are not ten times your own
|
|
133
147
|
allowance *(server-side)*.
|
|
134
|
-
- **
|
|
148
|
+
- **As many agents as your plan or your organization allows** *(server-side)*.
|
|
135
149
|
- **Files up to 5 GiB**, and in practice less: the agent reads a file into
|
|
136
150
|
memory to hash it, so the machine's memory is the real ceiling.
|
|
137
151
|
|
|
@@ -171,7 +185,7 @@ string ever reaches a terminal.
|
|
|
171
185
|
| `missing_token` | The session token is missing; pair the agent again. |
|
|
172
186
|
| `internal` | Something failed inside the agent. |
|
|
173
187
|
|
|
174
|
-
Every code comes back as one sentence in
|
|
188
|
+
Every code comes back as one sentence in English, never as a
|
|
175
189
|
stack trace.
|
|
176
190
|
|
|
177
191
|
## Tools
|
|
@@ -233,7 +247,7 @@ profile they live in, and the package does not try to set any others.
|
|
|
233
247
|
|
|
234
248
|
Deleting the directory makes this machine forget the agent. It does not revoke
|
|
235
249
|
anything: the account side is closed from the web app, under
|
|
236
|
-
Settings →
|
|
250
|
+
Settings → Agents → Revoke.
|
|
237
251
|
|
|
238
252
|
## Configuration
|
|
239
253
|
|
package/dist/cli.js
CHANGED
|
@@ -136,38 +136,38 @@ function errorFromResponse(status, body) {
|
|
|
136
136
|
return new ZasError(code, status, raw, retryAfterMs, raw);
|
|
137
137
|
}
|
|
138
138
|
var SENTENCES = {
|
|
139
|
-
identity_corrupt:
|
|
140
|
-
not_paired:
|
|
141
|
-
internal:
|
|
142
|
-
agent_forbidden:
|
|
143
|
-
agent_revoked:
|
|
144
|
-
grant_missing:
|
|
145
|
-
not_found:
|
|
146
|
-
invalid_cap:
|
|
147
|
-
write_failed:
|
|
148
|
-
send_forbidden:
|
|
149
|
-
read_forbidden:
|
|
150
|
-
direct_mode:
|
|
151
|
-
not_direct_mode:
|
|
152
|
-
key_stale:
|
|
153
|
-
quota_exceeded:
|
|
154
|
-
rate_limited:
|
|
155
|
-
file_too_big:
|
|
156
|
-
duplicate:
|
|
157
|
-
pairing_expired:
|
|
158
|
-
pairing_cancelled:
|
|
159
|
-
feature_disabled:
|
|
160
|
-
upload_failed:
|
|
161
|
-
oprf_failed:
|
|
162
|
-
network:
|
|
163
|
-
sign_in_failed:
|
|
164
|
-
bad_signature:
|
|
165
|
-
missing_token:
|
|
139
|
+
identity_corrupt: "The agent identity file is damaged: {path}. Restore it, or delete it and pair again.",
|
|
140
|
+
not_paired: "This agent is not paired yet. Run \u201Cnpx -y zas-agent pair\u201D in the terminal.",
|
|
141
|
+
internal: "Something failed inside the agent. Try again.",
|
|
142
|
+
agent_forbidden: "This agent cannot do that; only the account owner can.",
|
|
143
|
+
agent_revoked: "The owner revoked this agent. Pair again with \u201Czas-agent pair\u201D.",
|
|
144
|
+
grant_missing: "This agent has no access to that channel. The owner adds it from Settings \u2192 Agents.",
|
|
145
|
+
not_found: "That item is not in the channel.",
|
|
146
|
+
invalid_cap: "That file is no longer available.",
|
|
147
|
+
write_failed: "The file could not be saved to the destination.",
|
|
148
|
+
send_forbidden: "This agent cannot send to that channel.",
|
|
149
|
+
read_forbidden: "This agent cannot read that channel.",
|
|
150
|
+
direct_mode: "That channel is in Directo mode. Use zas_send_direct.",
|
|
151
|
+
not_direct_mode: "That channel is not in Directo mode. Use zas_send_file.",
|
|
152
|
+
key_stale: "The channel key changed. The owner refreshes it by opening Zas.",
|
|
153
|
+
quota_exceeded: "The account reached its storage limit.",
|
|
154
|
+
rate_limited: "Too many sends in a row. Wait a moment.",
|
|
155
|
+
file_too_big: "The file is over the plan limit.",
|
|
156
|
+
duplicate: "That item is already in the channel.",
|
|
157
|
+
pairing_expired: "The pairing expired. Run \u201Czas-agent pair\u201D again.",
|
|
158
|
+
pairing_cancelled: "The owner cancelled the pairing.",
|
|
159
|
+
feature_disabled: "Agents are not enabled for this account yet.",
|
|
160
|
+
upload_failed: "The upload failed. Try again.",
|
|
161
|
+
oprf_failed: "Zas did not answer correctly while preparing the file. Try again.",
|
|
162
|
+
network: "Zas cannot be reached. Try again in a moment.",
|
|
163
|
+
sign_in_failed: "Zas did not accept this agent session. Try again, or pair again.",
|
|
164
|
+
bad_signature: "Zas rejected this agent's signature. Pair it again.",
|
|
165
|
+
missing_token: "The session token is missing. Pair the agent again."
|
|
166
166
|
};
|
|
167
|
-
function humanSentence(error
|
|
168
|
-
const sentence = SENTENCES[error.code]
|
|
167
|
+
function humanSentence(error) {
|
|
168
|
+
const sentence = SENTENCES[error.code];
|
|
169
169
|
if (sentence) return sentence.replace("{path}", () => error.message);
|
|
170
|
-
return
|
|
170
|
+
return `Zas answered ${error.code} (${error.status}).`;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
// src/identity.ts
|
|
@@ -185,7 +185,7 @@ function agentHome() {
|
|
|
185
185
|
}
|
|
186
186
|
var PROFILE_RE = /^(?!\.)[A-Za-z0-9._-]{1,64}$/;
|
|
187
187
|
function profileDir(profile) {
|
|
188
|
-
if (!PROFILE_RE.test(profile)) throw new ZasError("internal", 0, `
|
|
188
|
+
if (!PROFILE_RE.test(profile)) throw new ZasError("internal", 0, `Invalid profile: ${profile}`);
|
|
189
189
|
return join(agentHome(), profile);
|
|
190
190
|
}
|
|
191
191
|
function ensureDir(dir) {
|
|
@@ -453,7 +453,7 @@ async function runPair(opts) {
|
|
|
453
453
|
const existing = loadIdentity(opts.profile);
|
|
454
454
|
if (existing) {
|
|
455
455
|
opts.log(
|
|
456
|
-
`
|
|
456
|
+
`This profile is already paired as \u201C${existing.name}\u201D. A new agent will be created; revoke the old one from Settings \u2192 Agents.`
|
|
457
457
|
);
|
|
458
458
|
}
|
|
459
459
|
const host = (opts.host ?? hostname()).slice(0, AGENT_HOST_MAX);
|
|
@@ -485,11 +485,11 @@ async function runPair(opts) {
|
|
|
485
485
|
const minutesLeft = Math.max(0, Math.round((created.expires_at - now()) / 6e4));
|
|
486
486
|
opts.log(
|
|
487
487
|
[
|
|
488
|
-
"
|
|
488
|
+
"Open this page signed in to your Zas account:",
|
|
489
489
|
` ${pairUrl(opts.webBase, created.pairing_id)}`,
|
|
490
|
-
`
|
|
491
|
-
`
|
|
492
|
-
`
|
|
490
|
+
`Code: ${formatCode(created.code)}`,
|
|
491
|
+
`Fingerprint: ${agentFingerprintShort(created.fingerprint)}`,
|
|
492
|
+
`Waiting for approval\u2026 (expires in ${minutesLeft} minutes)`
|
|
493
493
|
].join("\n")
|
|
494
494
|
);
|
|
495
495
|
let approved = null;
|
|
@@ -538,10 +538,10 @@ async function runPair(opts) {
|
|
|
538
538
|
clearPending(opts.profile);
|
|
539
539
|
const claudeLines = (lead) => [lead, ` ${claudeSnippet(opts.profile)}`];
|
|
540
540
|
const codexLines = (lead) => [lead, ` ${codexSnippet(opts.profile)}`];
|
|
541
|
-
const install = opts.kind === "claude_code" ? claudeLines("
|
|
541
|
+
const install = opts.kind === "claude_code" ? claudeLines("Add it to Claude Code:") : opts.kind === "codex" ? codexLines("Add it to Codex:") : [...claudeLines("Add it to Claude Code:"), ...codexLines("Or to Codex:")];
|
|
542
542
|
opts.log(
|
|
543
543
|
[
|
|
544
|
-
`
|
|
544
|
+
`Done: the agent \u201C${identity.name}\u201D is paired with your account.`,
|
|
545
545
|
...install
|
|
546
546
|
].join("\n")
|
|
547
547
|
);
|
|
@@ -1631,7 +1631,7 @@ async function sendNote(ctx, input) {
|
|
|
1631
1631
|
|
|
1632
1632
|
// src/server.ts
|
|
1633
1633
|
function agentVersion() {
|
|
1634
|
-
return true ? "0.1.
|
|
1634
|
+
return true ? "0.1.1" : "0.0.0-dev";
|
|
1635
1635
|
}
|
|
1636
1636
|
var AGENT_CUE = " The owner sees every item this agent sends with the >_ agent mark and this agent's name, on every device.";
|
|
1637
1637
|
var PAIR_ANNOUNCE_MS = 15e3;
|
|
@@ -1641,9 +1641,9 @@ var delay = (ms) => new Promise((resolve2) => {
|
|
|
1641
1641
|
});
|
|
1642
1642
|
function rightsOf(grant) {
|
|
1643
1643
|
const rights = [];
|
|
1644
|
-
if (grant.send && grant.mode !== "view" && !grant.direct_mode) rights.push("
|
|
1645
|
-
if (grant.read) rights.push("
|
|
1646
|
-
return rights.length > 0 ? rights.join(" \xB7 ") : "
|
|
1644
|
+
if (grant.send && grant.mode !== "view" && !grant.direct_mode) rights.push("send");
|
|
1645
|
+
if (grant.read) rights.push("read");
|
|
1646
|
+
return rights.length > 0 ? rights.join(" \xB7 ") : "no access";
|
|
1647
1647
|
}
|
|
1648
1648
|
function buildServer(profile, deps = {}) {
|
|
1649
1649
|
const server = new McpServer({ name: "zas", version: agentVersion() });
|
|
@@ -1664,7 +1664,7 @@ function buildServer(profile, deps = {}) {
|
|
|
1664
1664
|
});
|
|
1665
1665
|
const failed = (e) => {
|
|
1666
1666
|
const err = e instanceof ZasError ? e : new ZasError("internal", 0, String(e));
|
|
1667
|
-
return { isError: true, ...text2(`${err.code}: ${humanSentence(err
|
|
1667
|
+
return { isError: true, ...text2(`${err.code}: ${humanSentence(err)}`) };
|
|
1668
1668
|
};
|
|
1669
1669
|
const settled = (job) => {
|
|
1670
1670
|
if (job.status === "done") return text2(job.result);
|
|
@@ -1679,7 +1679,7 @@ function buildServer(profile, deps = {}) {
|
|
|
1679
1679
|
}, async () => {
|
|
1680
1680
|
try {
|
|
1681
1681
|
if (!(deps.identity ?? loadIdentity(profile))) {
|
|
1682
|
-
return text2(humanSentence(new ZasError("not_paired", 0)
|
|
1682
|
+
return text2(humanSentence(new ZasError("not_paired", 0)));
|
|
1683
1683
|
}
|
|
1684
1684
|
const c = ctx();
|
|
1685
1685
|
const grants = await grantsFor(c.client, profile);
|
|
@@ -1692,10 +1692,10 @@ function buildServer(profile, deps = {}) {
|
|
|
1692
1692
|
return ` ${name} \xB7 ${rightsOf(grant)}`;
|
|
1693
1693
|
});
|
|
1694
1694
|
return text2([
|
|
1695
|
-
`
|
|
1696
|
-
grants.length > 0 ? "
|
|
1695
|
+
`Paired as \u201C${c.identity.name}\u201D (${c.identity.kind}) with account ${c.identity.owner_uid}.`,
|
|
1696
|
+
grants.length > 0 ? "Channels:" : "No channels: the owner has not granted access to any yet.",
|
|
1697
1697
|
...lines,
|
|
1698
|
-
`${packageName()} ${agentVersion()} \xB7
|
|
1698
|
+
`${packageName()} ${agentVersion()} \xB7 profile ${profile}`
|
|
1699
1699
|
].join("\n"));
|
|
1700
1700
|
} catch (e) {
|
|
1701
1701
|
return failed(e);
|
|
@@ -1736,7 +1736,7 @@ function buildServer(profile, deps = {}) {
|
|
|
1736
1736
|
pairing = null;
|
|
1737
1737
|
return failed(state.error);
|
|
1738
1738
|
}
|
|
1739
|
-
return text2(state.logs.join("\n") || "
|
|
1739
|
+
return text2(state.logs.join("\n") || "Opening the pairing\u2026");
|
|
1740
1740
|
}
|
|
1741
1741
|
if (pairing.status === "paired") return text2(`paired as ${pairing.identity?.name ?? ""}`.trim());
|
|
1742
1742
|
if (pairing.status === "failed") {
|
|
@@ -1840,7 +1840,7 @@ function parseArgs(argv) {
|
|
|
1840
1840
|
else if (name === "--profile") {
|
|
1841
1841
|
parsed.profile = take() || DEFAULT_PROFILE;
|
|
1842
1842
|
if (!PROFILE_RE.test(parsed.profile)) {
|
|
1843
|
-
return { ...parsed, command: "invalid", message: `
|
|
1843
|
+
return { ...parsed, command: "invalid", message: `Invalid profile: ${parsed.profile}` };
|
|
1844
1844
|
}
|
|
1845
1845
|
} else if (name === "--kind") {
|
|
1846
1846
|
const kind = take();
|
|
@@ -1870,7 +1870,7 @@ async function main(argv, log = (l) => console.error(l)) {
|
|
|
1870
1870
|
return 2;
|
|
1871
1871
|
}
|
|
1872
1872
|
if (args.command === "help") {
|
|
1873
|
-
if (args.unknown) log(`
|
|
1873
|
+
if (args.unknown) log(`Unknown argument \u201C${args.unknown}\u201D.`);
|
|
1874
1874
|
log(USAGE);
|
|
1875
1875
|
return args.unknown ? 2 : 0;
|
|
1876
1876
|
}
|
|
@@ -1886,7 +1886,7 @@ async function main(argv, log = (l) => console.error(l)) {
|
|
|
1886
1886
|
});
|
|
1887
1887
|
} catch (e) {
|
|
1888
1888
|
const err = e instanceof ZasError ? e : new ZasError("internal", 0, String(e));
|
|
1889
|
-
log(humanSentence(err
|
|
1889
|
+
log(humanSentence(err));
|
|
1890
1890
|
return 1;
|
|
1891
1891
|
}
|
|
1892
1892
|
return 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zas-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
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",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"claude-code",
|
|
19
19
|
"codex",
|
|
20
20
|
"zas",
|
|
21
|
-
"
|
|
21
|
+
"encryption"
|
|
22
22
|
],
|
|
23
23
|
"mcpName": "io.github.soke1556/zas-agent",
|
|
24
24
|
"bin": {
|