identityapp 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/README.md +128 -0
- package/bin/cli.mjs +13 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +907 -0
- package/dist/cli.mjs.map +1 -0
- package/package.json +41 -0
- package/skills/agent-identity/SKILL.md +160 -0
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# identityapp CLI
|
|
2
|
+
|
|
3
|
+
`identityapp` is a TypeScript CLI for interacting with identity.app as an agent or integrator.
|
|
4
|
+
|
|
5
|
+
## Install and run
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# one-off
|
|
9
|
+
npx identityapp --help
|
|
10
|
+
|
|
11
|
+
# or global install
|
|
12
|
+
npm i -g identityapp
|
|
13
|
+
identityapp --help
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Commands
|
|
17
|
+
|
|
18
|
+
### Agent commands
|
|
19
|
+
|
|
20
|
+
- `identityapp register`
|
|
21
|
+
- `identityapp sign`
|
|
22
|
+
- `identityapp verify`
|
|
23
|
+
- `identityapp certify`
|
|
24
|
+
- `identityapp report`
|
|
25
|
+
- `identityapp identity list|show|use|remove`
|
|
26
|
+
- `identityapp auth link set|show|clear`
|
|
27
|
+
|
|
28
|
+
### Integrator commands
|
|
29
|
+
|
|
30
|
+
- `identityapp integrator consent`
|
|
31
|
+
- `identityapp integrator ingest`
|
|
32
|
+
- `identityapp integrator verify`
|
|
33
|
+
- `identityapp integrator certify`
|
|
34
|
+
|
|
35
|
+
## Examples
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Register identity alias and persist credentials under ~/.identity
|
|
39
|
+
npx identityapp register --as writer --label "writer"
|
|
40
|
+
|
|
41
|
+
# Sign and verify
|
|
42
|
+
npx identityapp sign --as writer "Hello world"
|
|
43
|
+
npx identityapp verify <signatureHash> "Hello world"
|
|
44
|
+
|
|
45
|
+
# Certify content
|
|
46
|
+
npx identityapp certify <signatureHash> "Hello world"
|
|
47
|
+
|
|
48
|
+
# Report a bad actor
|
|
49
|
+
npx identityapp report did:identity:badagent malicious --details "Scam attempts"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Set a default linking key for future register calls
|
|
54
|
+
npx identityapp auth link set lk_abc123
|
|
55
|
+
|
|
56
|
+
# Register using default linking key
|
|
57
|
+
npx identityapp register --as support-bot
|
|
58
|
+
|
|
59
|
+
# Skip default linking key once
|
|
60
|
+
npx identityapp register --as experiment --no-link
|
|
61
|
+
|
|
62
|
+
# Set default alias so --as is optional later
|
|
63
|
+
npx identityapp identity use writer
|
|
64
|
+
npx identityapp sign "Message from default alias"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Register is intentionally non-destructive: if an alias already exists, registration fails.
|
|
68
|
+
Use a new alias, or explicitly remove the old one first:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx identityapp identity remove --as writer --yes
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Human owner linking guidance
|
|
75
|
+
|
|
76
|
+
- If your human owner already has a linking key, set it once:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx identityapp auth link set <linking_key>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- If they do not have one yet:
|
|
83
|
+
- ask them to create/log into an account on `identity.app`
|
|
84
|
+
- ask them to generate a linking key from the dashboard
|
|
85
|
+
- then set it locally with the command above
|
|
86
|
+
|
|
87
|
+
- If linking is not ready yet:
|
|
88
|
+
- register with `--no-link`
|
|
89
|
+
- share the resulting claim token with the human owner for manual claiming later
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Integrator verify with consent context
|
|
93
|
+
npx identityapp integrator verify <signatureHash> --api-key <integratorApiKey>
|
|
94
|
+
|
|
95
|
+
# Ingest signals (bearer auth)
|
|
96
|
+
npx identityapp integrator ingest \
|
|
97
|
+
--api-key <integratorApiKey> \
|
|
98
|
+
--ingest-url https://integrator.identity.app/ingest \
|
|
99
|
+
--body-file ./event.json
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Local development
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npm install
|
|
106
|
+
npm run build
|
|
107
|
+
node bin/cli.mjs --help
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Publish
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npm run build
|
|
114
|
+
npm publish --access public
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The package uses a `bin` launcher (`bin/cli.mjs`) that executes the compiled output in `dist/`.
|
|
118
|
+
|
|
119
|
+
## Identity storage
|
|
120
|
+
|
|
121
|
+
- Default home directory: `~/.identity`
|
|
122
|
+
- Override with:
|
|
123
|
+
- env var: `IDENTITY_HOME`
|
|
124
|
+
- command flag: `--home <dir>`
|
|
125
|
+
- Layout:
|
|
126
|
+
- `config.json` (default alias + default linking key)
|
|
127
|
+
- `identities/<alias>.json` (private key + DID + metadata)
|
|
128
|
+
- Integrator ingest default endpoint: `https://integrator.identity.app/ingest` (override with `--ingest-url`)
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import module from "node:module";
|
|
4
|
+
|
|
5
|
+
if (module.enableCompileCache && !process.env.NODE_DISABLE_COMPILE_CACHE) {
|
|
6
|
+
try {
|
|
7
|
+
module.enableCompileCache();
|
|
8
|
+
} catch {
|
|
9
|
+
// Ignore compile cache errors.
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
await import("../dist/cli.mjs");
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,907 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/constants.ts
|
|
4
|
+
import os from "os";
|
|
5
|
+
import path from "path";
|
|
6
|
+
var DEFAULT_BASE_URL = "https://identity.app";
|
|
7
|
+
var DEFAULT_EVENTS_URL = "https://integrator.identity.app/ingest";
|
|
8
|
+
var DEFAULT_IDENTITY_HOME = path.join(os.homedir(), ".identity");
|
|
9
|
+
var IDENTITY_HOME_ENV = "IDENTITY_HOME";
|
|
10
|
+
var CURRENT_VERSION = "0.1.0";
|
|
11
|
+
|
|
12
|
+
// src/lib/errors.ts
|
|
13
|
+
function fail(message) {
|
|
14
|
+
console.error(message);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/lib/storage.ts
|
|
19
|
+
import fs2 from "fs";
|
|
20
|
+
import path3 from "path";
|
|
21
|
+
|
|
22
|
+
// src/lib/fs.ts
|
|
23
|
+
import fs from "fs";
|
|
24
|
+
import path2 from "path";
|
|
25
|
+
function ensureDir(dirPath) {
|
|
26
|
+
fs.mkdirSync(dirPath, { recursive: true, mode: 448 });
|
|
27
|
+
}
|
|
28
|
+
function ensureDirForFile(filePath) {
|
|
29
|
+
ensureDir(path2.dirname(filePath));
|
|
30
|
+
}
|
|
31
|
+
function readJsonFile(filePath) {
|
|
32
|
+
try {
|
|
33
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
34
|
+
return JSON.parse(raw);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (typeof error === "object" && error && "code" in error && error.code === "ENOENT") {
|
|
37
|
+
fail(`File not found: ${filePath}`);
|
|
38
|
+
}
|
|
39
|
+
fail(`Invalid JSON file: ${filePath}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function writeSecureJsonFile(filePath, value) {
|
|
43
|
+
ensureDirForFile(filePath);
|
|
44
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
|
|
45
|
+
`, "utf-8");
|
|
46
|
+
try {
|
|
47
|
+
fs.chmodSync(filePath, 384);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/lib/storage.ts
|
|
53
|
+
function resolveHome(rawArgs) {
|
|
54
|
+
const remaining = [];
|
|
55
|
+
let homeFromArg;
|
|
56
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
57
|
+
const token = rawArgs[i];
|
|
58
|
+
if (token === "--home") {
|
|
59
|
+
homeFromArg = rawArgs[i + 1];
|
|
60
|
+
i += 1;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (token.startsWith("--home=")) {
|
|
64
|
+
homeFromArg = token.slice("--home=".length);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
remaining.push(token);
|
|
68
|
+
}
|
|
69
|
+
const home = homeFromArg || process.env[IDENTITY_HOME_ENV] || DEFAULT_IDENTITY_HOME;
|
|
70
|
+
const absHome = path3.resolve(home);
|
|
71
|
+
return { home: absHome, args: remaining };
|
|
72
|
+
}
|
|
73
|
+
function createContext(home) {
|
|
74
|
+
const identitiesDir = path3.join(home, "identities");
|
|
75
|
+
return {
|
|
76
|
+
home,
|
|
77
|
+
configPath: path3.join(home, "config.json"),
|
|
78
|
+
identitiesDir
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function defaultConfig() {
|
|
82
|
+
return {
|
|
83
|
+
version: 1,
|
|
84
|
+
defaultAlias: null,
|
|
85
|
+
defaultLinkingKey: null
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function readConfig(ctx) {
|
|
89
|
+
if (!fs2.existsSync(ctx.configPath)) {
|
|
90
|
+
return defaultConfig();
|
|
91
|
+
}
|
|
92
|
+
const parsed = readJsonFile(ctx.configPath);
|
|
93
|
+
return {
|
|
94
|
+
version: typeof parsed.version === "number" ? parsed.version : 1,
|
|
95
|
+
defaultAlias: typeof parsed.defaultAlias === "string" ? parsed.defaultAlias : null,
|
|
96
|
+
defaultLinkingKey: typeof parsed.defaultLinkingKey === "string" ? parsed.defaultLinkingKey : null
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function writeConfig(ctx, config) {
|
|
100
|
+
writeSecureJsonFile(ctx.configPath, config);
|
|
101
|
+
}
|
|
102
|
+
function normalizeAlias(alias) {
|
|
103
|
+
const normalized = alias.trim().toLowerCase();
|
|
104
|
+
if (!/^[a-z][a-z0-9_-]{0,62}$/.test(normalized)) {
|
|
105
|
+
fail(
|
|
106
|
+
`Invalid alias "${alias}". Alias must start with a letter and contain only lowercase letters, digits, "_" or "-".`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return normalized;
|
|
110
|
+
}
|
|
111
|
+
function identityPathForAlias(ctx, alias) {
|
|
112
|
+
return path3.join(ctx.identitiesDir, `${normalizeAlias(alias)}.json`);
|
|
113
|
+
}
|
|
114
|
+
function resolveAlias(requestedAlias, config) {
|
|
115
|
+
if (requestedAlias) return normalizeAlias(requestedAlias);
|
|
116
|
+
if (config.defaultAlias) return normalizeAlias(config.defaultAlias);
|
|
117
|
+
fail(
|
|
118
|
+
'No alias selected. Pass --as <alias> or set a default with "identityapp identity use <alias>".'
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
function loadCredentials(filePath) {
|
|
122
|
+
if (!filePath) fail("Internal error: credentials file path is required");
|
|
123
|
+
const json = readJsonFile(filePath);
|
|
124
|
+
if (!json.did || !json.privateKey) {
|
|
125
|
+
fail(`Invalid credentials file: ${filePath} (missing did or privateKey)`);
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
did: json.did,
|
|
129
|
+
publicKey: json.publicKey,
|
|
130
|
+
privateKey: json.privateKey,
|
|
131
|
+
linked: json.linked,
|
|
132
|
+
claimToken: json.claimToken
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function saveCredentials(filePath, creds) {
|
|
136
|
+
writeSecureJsonFile(filePath, creds);
|
|
137
|
+
}
|
|
138
|
+
function loadIdentityFromAlias(ctx, config, requestedAlias) {
|
|
139
|
+
const alias = resolveAlias(requestedAlias, config);
|
|
140
|
+
const filePath = identityPathForAlias(ctx, alias);
|
|
141
|
+
if (!fs2.existsSync(filePath)) {
|
|
142
|
+
fail(
|
|
143
|
+
`Identity alias "${alias}" not found at ${filePath}. Register first with "identityapp register --as ${alias}".`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
const credentials = loadCredentials(filePath);
|
|
147
|
+
return { ...credentials, alias };
|
|
148
|
+
}
|
|
149
|
+
function maybeSetDefaultAlias(ctx, config, alias) {
|
|
150
|
+
if (!config.defaultAlias) {
|
|
151
|
+
writeConfig(ctx, { ...config, defaultAlias: alias });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function redactPrivateKey(identity) {
|
|
155
|
+
return {
|
|
156
|
+
...identity,
|
|
157
|
+
privateKey: "[redacted]"
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/commands/auth.ts
|
|
162
|
+
async function handleAuth(args, ctx) {
|
|
163
|
+
const subcommand = args[0];
|
|
164
|
+
const rest = args.slice(1);
|
|
165
|
+
if (subcommand !== "link") {
|
|
166
|
+
fail("Usage: identityapp auth link <set|show|clear> ...");
|
|
167
|
+
}
|
|
168
|
+
const action = rest[0];
|
|
169
|
+
const actionArgs = rest.slice(1);
|
|
170
|
+
const config = readConfig(ctx);
|
|
171
|
+
if (action === "set") {
|
|
172
|
+
const key = actionArgs[0];
|
|
173
|
+
if (!key) fail("Usage: identityapp auth link set <linking_key>");
|
|
174
|
+
writeConfig(ctx, { ...config, defaultLinkingKey: key });
|
|
175
|
+
console.log(JSON.stringify({ ok: true, defaultLinkingKey: "[set]" }, null, 2));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (action === "show") {
|
|
179
|
+
console.log(
|
|
180
|
+
JSON.stringify(
|
|
181
|
+
{
|
|
182
|
+
defaultLinkingKey: config.defaultLinkingKey ? "[set]" : null
|
|
183
|
+
},
|
|
184
|
+
null,
|
|
185
|
+
2
|
|
186
|
+
)
|
|
187
|
+
);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (action === "clear") {
|
|
191
|
+
writeConfig(ctx, { ...config, defaultLinkingKey: null });
|
|
192
|
+
console.log(JSON.stringify({ ok: true, defaultLinkingKey: null }, null, 2));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
fail("Usage: identityapp auth link <set|show|clear> ...");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/commands/agent.ts
|
|
199
|
+
import fs3 from "fs";
|
|
200
|
+
import path4 from "path";
|
|
201
|
+
import { parseArgs } from "util";
|
|
202
|
+
|
|
203
|
+
// src/lib/crypto.ts
|
|
204
|
+
import crypto from "crypto";
|
|
205
|
+
function sha256Hex(input) {
|
|
206
|
+
return crypto.createHash("sha256").update(input).digest("hex");
|
|
207
|
+
}
|
|
208
|
+
function buildPrivateKeyFromRawBase64(privateKeyBase64) {
|
|
209
|
+
const raw = Buffer.from(privateKeyBase64, "base64");
|
|
210
|
+
const pkcs8Prefix = Buffer.from("302e020100300506032b657004220420", "hex");
|
|
211
|
+
const der = Buffer.concat([pkcs8Prefix, raw]);
|
|
212
|
+
return crypto.createPrivateKey({
|
|
213
|
+
key: der,
|
|
214
|
+
format: "der",
|
|
215
|
+
type: "pkcs8"
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
function signMessage(privateKeyBase64, message) {
|
|
219
|
+
const privateKey = buildPrivateKeyFromRawBase64(privateKeyBase64);
|
|
220
|
+
return crypto.sign(null, Buffer.from(message), privateKey).toString("base64");
|
|
221
|
+
}
|
|
222
|
+
function generateEd25519KeyPair() {
|
|
223
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
|
|
224
|
+
const publicKeyB64 = publicKey.export({ type: "spki", format: "der" }).subarray(-32).toString("base64");
|
|
225
|
+
const privateKeyB64 = privateKey.export({ type: "pkcs8", format: "der" }).subarray(-32).toString("base64");
|
|
226
|
+
return { publicKey: publicKeyB64, privateKey: privateKeyB64 };
|
|
227
|
+
}
|
|
228
|
+
function minePowNonce(publicKey, difficulty = 4) {
|
|
229
|
+
let nonce = 0;
|
|
230
|
+
const prefix = "0".repeat(difficulty);
|
|
231
|
+
while (true) {
|
|
232
|
+
const hash = sha256Hex(`${publicKey}${String(nonce)}`);
|
|
233
|
+
if (hash.startsWith(prefix)) return String(nonce);
|
|
234
|
+
nonce += 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/lib/http.ts
|
|
239
|
+
function withBearer(apiKey) {
|
|
240
|
+
return {
|
|
241
|
+
Authorization: `Bearer ${apiKey}`
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
async function readResponseJson(response) {
|
|
245
|
+
return response.json().catch(() => ({}));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/lib/utils.ts
|
|
249
|
+
import { canonicalize } from "json-canonicalize";
|
|
250
|
+
function stripTrailingSlash(url) {
|
|
251
|
+
return url.replace(/\/+$/, "");
|
|
252
|
+
}
|
|
253
|
+
function parseJsonText(text) {
|
|
254
|
+
try {
|
|
255
|
+
return JSON.parse(text);
|
|
256
|
+
} catch {
|
|
257
|
+
fail("Body is not valid JSON");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/commands/agent.ts
|
|
262
|
+
async function handleRegister(args, ctx) {
|
|
263
|
+
const { values } = parseArgs({
|
|
264
|
+
args,
|
|
265
|
+
options: {
|
|
266
|
+
as: { type: "string" },
|
|
267
|
+
label: { type: "string" },
|
|
268
|
+
"linking-key": { type: "string" },
|
|
269
|
+
"no-link": { type: "boolean" },
|
|
270
|
+
"key-file": { type: "string" },
|
|
271
|
+
"public-key": { type: "string" },
|
|
272
|
+
"private-key": { type: "string" },
|
|
273
|
+
save: { type: "string" },
|
|
274
|
+
url: { type: "string", default: DEFAULT_BASE_URL }
|
|
275
|
+
},
|
|
276
|
+
strict: true,
|
|
277
|
+
allowPositionals: false
|
|
278
|
+
});
|
|
279
|
+
const config = readConfig(ctx);
|
|
280
|
+
const alias = resolveAlias(values.as, config);
|
|
281
|
+
const canonicalPath = identityPathForAlias(ctx, alias);
|
|
282
|
+
if (fs3.existsSync(canonicalPath)) {
|
|
283
|
+
fail(
|
|
284
|
+
`Identity alias "${alias}" already exists at ${canonicalPath}. Register with a new alias or remove the existing one via "identityapp identity remove --as ${alias} --yes".`
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
let keyPair;
|
|
288
|
+
if (values["key-file"]) {
|
|
289
|
+
const json = readJsonFile(values["key-file"]);
|
|
290
|
+
if (!json.publicKey || !json.privateKey) {
|
|
291
|
+
fail('Key file must include "publicKey" and "privateKey"');
|
|
292
|
+
}
|
|
293
|
+
keyPair = { publicKey: json.publicKey, privateKey: json.privateKey };
|
|
294
|
+
} else if (values["public-key"] && values["private-key"]) {
|
|
295
|
+
console.error(
|
|
296
|
+
"Warning: passing --private-key on the CLI exposes it in shell history. Prefer --key-file."
|
|
297
|
+
);
|
|
298
|
+
keyPair = {
|
|
299
|
+
publicKey: values["public-key"],
|
|
300
|
+
privateKey: values["private-key"]
|
|
301
|
+
};
|
|
302
|
+
} else if (values["public-key"] || values["private-key"]) {
|
|
303
|
+
fail("Both --public-key and --private-key must be set together");
|
|
304
|
+
} else {
|
|
305
|
+
keyPair = generateEd25519KeyPair();
|
|
306
|
+
}
|
|
307
|
+
console.error("Mining proof-of-work nonce...");
|
|
308
|
+
const powNonce = minePowNonce(keyPair.publicKey);
|
|
309
|
+
const payload = {
|
|
310
|
+
publicKey: keyPair.publicKey,
|
|
311
|
+
powNonce
|
|
312
|
+
};
|
|
313
|
+
const label = values.label ?? alias;
|
|
314
|
+
payload.label = label;
|
|
315
|
+
const linkingKey = values["no-link"] === true ? void 0 : values["linking-key"] ?? config.defaultLinkingKey ?? void 0;
|
|
316
|
+
if (linkingKey) payload.linkingKey = linkingKey;
|
|
317
|
+
const baseUrl = stripTrailingSlash(values.url);
|
|
318
|
+
const response = await fetch(`${baseUrl}/api/v1/agents/register`, {
|
|
319
|
+
method: "POST",
|
|
320
|
+
headers: { "Content-Type": "application/json" },
|
|
321
|
+
body: JSON.stringify(payload)
|
|
322
|
+
});
|
|
323
|
+
if (!response.ok) {
|
|
324
|
+
const error = await readResponseJson(response);
|
|
325
|
+
fail(
|
|
326
|
+
`Registration failed (${response.status}): ${error.error ?? "Unknown error"}`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
const body = await response.json();
|
|
330
|
+
const credentials = {
|
|
331
|
+
alias,
|
|
332
|
+
did: body.did,
|
|
333
|
+
publicKey: keyPair.publicKey,
|
|
334
|
+
privateKey: keyPair.privateKey,
|
|
335
|
+
linked: body.linked,
|
|
336
|
+
claimToken: body.claimToken,
|
|
337
|
+
label,
|
|
338
|
+
createdAt: Date.now(),
|
|
339
|
+
updatedAt: Date.now()
|
|
340
|
+
};
|
|
341
|
+
saveCredentials(canonicalPath, credentials);
|
|
342
|
+
maybeSetDefaultAlias(ctx, config, alias);
|
|
343
|
+
console.error(`Identity saved to ${canonicalPath}`);
|
|
344
|
+
if (values.save) {
|
|
345
|
+
saveCredentials(path4.resolve(values.save), credentials);
|
|
346
|
+
console.error(`Additional copy saved to ${path4.resolve(values.save)}`);
|
|
347
|
+
}
|
|
348
|
+
const registerOutput = {
|
|
349
|
+
alias: credentials.alias,
|
|
350
|
+
did: credentials.did,
|
|
351
|
+
publicKey: credentials.publicKey,
|
|
352
|
+
linked: credentials.linked,
|
|
353
|
+
claimToken: credentials.claimToken,
|
|
354
|
+
label: credentials.label,
|
|
355
|
+
createdAt: credentials.createdAt,
|
|
356
|
+
updatedAt: credentials.updatedAt,
|
|
357
|
+
identityPath: canonicalPath,
|
|
358
|
+
privateKey: "[stored locally only; never printed]"
|
|
359
|
+
};
|
|
360
|
+
console.log(JSON.stringify(registerOutput, null, 2));
|
|
361
|
+
}
|
|
362
|
+
async function handleSign(args, ctx) {
|
|
363
|
+
const { values, positionals } = parseArgs({
|
|
364
|
+
args,
|
|
365
|
+
options: {
|
|
366
|
+
as: { type: "string" },
|
|
367
|
+
file: { type: "string" },
|
|
368
|
+
note: { type: "string" },
|
|
369
|
+
credentials: { type: "string" },
|
|
370
|
+
url: { type: "string", default: DEFAULT_BASE_URL }
|
|
371
|
+
},
|
|
372
|
+
strict: true,
|
|
373
|
+
allowPositionals: true
|
|
374
|
+
});
|
|
375
|
+
const payload = positionals[0];
|
|
376
|
+
if (!payload && !values.file) {
|
|
377
|
+
fail(
|
|
378
|
+
"Usage: identityapp sign <payload> [--note <text>] [--credentials <path>] [--url <base_url>] OR identityapp sign --file <path> ..."
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
const config = readConfig(ctx);
|
|
382
|
+
const creds = values.credentials ? loadCredentials(path4.resolve(values.credentials)) : loadIdentityFromAlias(ctx, config, values.as);
|
|
383
|
+
const content = values.file !== void 0 ? fs3.existsSync(values.file) ? fs3.readFileSync(values.file) : fail(`File not found: ${values.file}`) : payload;
|
|
384
|
+
const payloadHash = sha256Hex(content);
|
|
385
|
+
const signedAt = Date.now();
|
|
386
|
+
const signature = signMessage(creds.privateKey, `${payloadHash}:${signedAt}`);
|
|
387
|
+
const response = await fetch(
|
|
388
|
+
`${stripTrailingSlash(values.url)}/api/v1/signatures/sign`,
|
|
389
|
+
{
|
|
390
|
+
method: "POST",
|
|
391
|
+
headers: { "Content-Type": "application/json" },
|
|
392
|
+
body: JSON.stringify({
|
|
393
|
+
did: creds.did,
|
|
394
|
+
payloadHash,
|
|
395
|
+
signature,
|
|
396
|
+
signedAt,
|
|
397
|
+
publicNote: values.note
|
|
398
|
+
})
|
|
399
|
+
}
|
|
400
|
+
);
|
|
401
|
+
if (!response.ok) {
|
|
402
|
+
const error = await readResponseJson(response);
|
|
403
|
+
fail(
|
|
404
|
+
`Signing failed (${response.status}): ${error.error ?? "Unknown error"}`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
const result = await response.json();
|
|
408
|
+
const baseUrl = stripTrailingSlash(values.url);
|
|
409
|
+
console.log(
|
|
410
|
+
JSON.stringify(
|
|
411
|
+
{
|
|
412
|
+
...result,
|
|
413
|
+
verifyUrl: `${baseUrl}/verify/${result.signatureHash}`,
|
|
414
|
+
verifyApiUrl: `${baseUrl}/api/v1/signatures/verify?hash=${result.signatureHash}`
|
|
415
|
+
},
|
|
416
|
+
null,
|
|
417
|
+
2
|
|
418
|
+
)
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
async function handleVerify(args, options) {
|
|
422
|
+
const { values, positionals } = parseArgs({
|
|
423
|
+
args,
|
|
424
|
+
options: {
|
|
425
|
+
url: { type: "string", default: DEFAULT_BASE_URL }
|
|
426
|
+
},
|
|
427
|
+
strict: true,
|
|
428
|
+
allowPositionals: true
|
|
429
|
+
});
|
|
430
|
+
const signatureHash = positionals[0];
|
|
431
|
+
const contentToCheck = positionals[1];
|
|
432
|
+
if (!signatureHash) {
|
|
433
|
+
fail(
|
|
434
|
+
"Usage: identityapp verify <signature_hash> [content_to_check] [--url <base_url>]"
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
const headers = {};
|
|
438
|
+
if (options?.apiKey) Object.assign(headers, withBearer(options.apiKey));
|
|
439
|
+
const response = await fetch(
|
|
440
|
+
`${stripTrailingSlash(values.url)}/api/v1/signatures/verify?hash=${encodeURIComponent(signatureHash)}`,
|
|
441
|
+
{ headers }
|
|
442
|
+
);
|
|
443
|
+
if (!response.ok) {
|
|
444
|
+
const error = await readResponseJson(response);
|
|
445
|
+
fail(
|
|
446
|
+
`Verification failed (${response.status}): ${error.error ?? "Unknown error"}`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
const result = await response.json();
|
|
450
|
+
const output = contentToCheck !== void 0 ? {
|
|
451
|
+
...result,
|
|
452
|
+
contentMatch: sha256Hex(contentToCheck) === result.payloadHash
|
|
453
|
+
} : result;
|
|
454
|
+
console.log(JSON.stringify(output, null, 2));
|
|
455
|
+
}
|
|
456
|
+
async function handleCertify(args, options) {
|
|
457
|
+
const { values, positionals } = parseArgs({
|
|
458
|
+
args,
|
|
459
|
+
options: {
|
|
460
|
+
file: { type: "string" },
|
|
461
|
+
url: { type: "string", default: DEFAULT_BASE_URL }
|
|
462
|
+
},
|
|
463
|
+
strict: true,
|
|
464
|
+
allowPositionals: true
|
|
465
|
+
});
|
|
466
|
+
const signatureHash = positionals[0];
|
|
467
|
+
const contentArg = positionals[1];
|
|
468
|
+
if (!signatureHash || !contentArg && !values.file) {
|
|
469
|
+
fail(
|
|
470
|
+
"Usage: identityapp certify <signature_hash> <content> [--url <base_url>] OR identityapp certify <signature_hash> --file <path> [--url <base_url>]"
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
const content = values.file !== void 0 ? fs3.existsSync(values.file) ? fs3.readFileSync(values.file) : fail(`File not found: ${values.file}`) : contentArg;
|
|
474
|
+
const contentHash = sha256Hex(content);
|
|
475
|
+
const headers = { "Content-Type": "application/json" };
|
|
476
|
+
if (options?.apiKey) Object.assign(headers, withBearer(options.apiKey));
|
|
477
|
+
const response = await fetch(
|
|
478
|
+
`${stripTrailingSlash(values.url)}/api/v1/signatures/certify`,
|
|
479
|
+
{
|
|
480
|
+
method: "POST",
|
|
481
|
+
headers,
|
|
482
|
+
body: JSON.stringify({ signatureHash, contentHash })
|
|
483
|
+
}
|
|
484
|
+
);
|
|
485
|
+
if (!response.ok) {
|
|
486
|
+
const error = await readResponseJson(response);
|
|
487
|
+
fail(
|
|
488
|
+
`Certification failed (${response.status}): ${error.error ?? "Unknown error"}`
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
console.log(JSON.stringify(await response.json(), null, 2));
|
|
492
|
+
}
|
|
493
|
+
async function handleReport(args, ctx) {
|
|
494
|
+
const { values, positionals } = parseArgs({
|
|
495
|
+
args,
|
|
496
|
+
options: {
|
|
497
|
+
as: { type: "string" },
|
|
498
|
+
signature: { type: "string" },
|
|
499
|
+
details: { type: "string" },
|
|
500
|
+
credentials: { type: "string" },
|
|
501
|
+
url: { type: "string", default: DEFAULT_BASE_URL }
|
|
502
|
+
},
|
|
503
|
+
strict: true,
|
|
504
|
+
allowPositionals: true
|
|
505
|
+
});
|
|
506
|
+
const targetDid = positionals[0];
|
|
507
|
+
const reason = positionals[1];
|
|
508
|
+
if (!targetDid || !reason) {
|
|
509
|
+
fail(
|
|
510
|
+
"Usage: identityapp report <target_did> <reason> [--signature <hash>] [--details <text>] [--credentials <path>] [--url <base_url>]"
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
const validReasons = ["spam", "impersonation", "malicious", "other"];
|
|
514
|
+
if (!validReasons.includes(reason)) {
|
|
515
|
+
fail(
|
|
516
|
+
`Invalid reason "${reason}". Must be one of: ${validReasons.join(", ")}`
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
const config = readConfig(ctx);
|
|
520
|
+
const creds = values.credentials ? loadCredentials(path4.resolve(values.credentials)) : loadIdentityFromAlias(ctx, config, values.as);
|
|
521
|
+
const signedAt = Date.now();
|
|
522
|
+
const signature = signMessage(
|
|
523
|
+
creds.privateKey,
|
|
524
|
+
`report:${targetDid}:${reason}:${signedAt}`
|
|
525
|
+
);
|
|
526
|
+
const response = await fetch(
|
|
527
|
+
`${stripTrailingSlash(values.url)}/api/v1/agents/report`,
|
|
528
|
+
{
|
|
529
|
+
method: "POST",
|
|
530
|
+
headers: { "Content-Type": "application/json" },
|
|
531
|
+
body: JSON.stringify({
|
|
532
|
+
did: targetDid,
|
|
533
|
+
reason,
|
|
534
|
+
reporterDid: creds.did,
|
|
535
|
+
signature,
|
|
536
|
+
signedAt,
|
|
537
|
+
signatureHash: values.signature,
|
|
538
|
+
details: values.details
|
|
539
|
+
})
|
|
540
|
+
}
|
|
541
|
+
);
|
|
542
|
+
if (!response.ok) {
|
|
543
|
+
const error = await readResponseJson(response);
|
|
544
|
+
fail(
|
|
545
|
+
`Report failed (${response.status}): ${error.error ?? "Unknown error"}`
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
console.log(JSON.stringify(await response.json(), null, 2));
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// src/commands/identity.ts
|
|
552
|
+
import fs4 from "fs";
|
|
553
|
+
import path5 from "path";
|
|
554
|
+
import { parseArgs as parseArgs2 } from "util";
|
|
555
|
+
async function handleIdentity(args, ctx) {
|
|
556
|
+
const subcommand = args[0];
|
|
557
|
+
const rest = args.slice(1);
|
|
558
|
+
if (subcommand === "list") {
|
|
559
|
+
const config = readConfig(ctx);
|
|
560
|
+
if (!fs4.existsSync(ctx.identitiesDir)) {
|
|
561
|
+
console.log(
|
|
562
|
+
JSON.stringify({ defaultAlias: config.defaultAlias, identities: [] }, null, 2)
|
|
563
|
+
);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
const files = fs4.readdirSync(ctx.identitiesDir).filter((name) => name.endsWith(".json")).sort();
|
|
567
|
+
const identities = files.map((fileName) => {
|
|
568
|
+
const filePath = path5.join(ctx.identitiesDir, fileName);
|
|
569
|
+
const identity = readJsonFile(filePath);
|
|
570
|
+
const alias = fileName.slice(0, -5);
|
|
571
|
+
return {
|
|
572
|
+
alias,
|
|
573
|
+
did: identity.did ?? null,
|
|
574
|
+
label: identity.label ?? null,
|
|
575
|
+
updatedAt: identity.updatedAt ?? null,
|
|
576
|
+
isDefault: config.defaultAlias === alias
|
|
577
|
+
};
|
|
578
|
+
});
|
|
579
|
+
console.log(
|
|
580
|
+
JSON.stringify(
|
|
581
|
+
{
|
|
582
|
+
home: ctx.home,
|
|
583
|
+
defaultAlias: config.defaultAlias,
|
|
584
|
+
identities
|
|
585
|
+
},
|
|
586
|
+
null,
|
|
587
|
+
2
|
|
588
|
+
)
|
|
589
|
+
);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (subcommand === "show") {
|
|
593
|
+
const { values } = parseArgs2({
|
|
594
|
+
args: rest,
|
|
595
|
+
options: {
|
|
596
|
+
as: { type: "string" }
|
|
597
|
+
},
|
|
598
|
+
strict: true,
|
|
599
|
+
allowPositionals: false
|
|
600
|
+
});
|
|
601
|
+
const config = readConfig(ctx);
|
|
602
|
+
const identity = loadIdentityFromAlias(ctx, config, values.as);
|
|
603
|
+
console.log(
|
|
604
|
+
JSON.stringify(
|
|
605
|
+
{
|
|
606
|
+
home: ctx.home,
|
|
607
|
+
...redactPrivateKey(identity)
|
|
608
|
+
},
|
|
609
|
+
null,
|
|
610
|
+
2
|
|
611
|
+
)
|
|
612
|
+
);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
if (subcommand === "use") {
|
|
616
|
+
const alias = rest[0];
|
|
617
|
+
if (!alias) fail("Usage: identityapp identity use <alias>");
|
|
618
|
+
const normalized = normalizeAlias(alias);
|
|
619
|
+
const identityPath = identityPathForAlias(ctx, normalized);
|
|
620
|
+
if (!fs4.existsSync(identityPath)) {
|
|
621
|
+
fail(`Identity alias "${normalized}" not found.`);
|
|
622
|
+
}
|
|
623
|
+
const config = readConfig(ctx);
|
|
624
|
+
writeConfig(ctx, { ...config, defaultAlias: normalized });
|
|
625
|
+
console.log(JSON.stringify({ ok: true, defaultAlias: normalized }, null, 2));
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
if (subcommand === "remove") {
|
|
629
|
+
const { values } = parseArgs2({
|
|
630
|
+
args: rest,
|
|
631
|
+
options: {
|
|
632
|
+
as: { type: "string" },
|
|
633
|
+
yes: { type: "boolean" }
|
|
634
|
+
},
|
|
635
|
+
strict: true,
|
|
636
|
+
allowPositionals: false
|
|
637
|
+
});
|
|
638
|
+
if (!values.as) fail("Usage: identityapp identity remove --as <alias> --yes");
|
|
639
|
+
if (values.yes !== true) {
|
|
640
|
+
fail("Refusing to remove identity without --yes");
|
|
641
|
+
}
|
|
642
|
+
const alias = normalizeAlias(values.as);
|
|
643
|
+
const target = identityPathForAlias(ctx, alias);
|
|
644
|
+
if (!fs4.existsSync(target)) {
|
|
645
|
+
fail(`Identity alias "${alias}" not found.`);
|
|
646
|
+
}
|
|
647
|
+
fs4.rmSync(target);
|
|
648
|
+
const config = readConfig(ctx);
|
|
649
|
+
const nextConfig = config.defaultAlias === alias ? { ...config, defaultAlias: null } : config;
|
|
650
|
+
writeConfig(ctx, nextConfig);
|
|
651
|
+
console.log(JSON.stringify({ ok: true, removed: alias }, null, 2));
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
fail("Usage: identityapp identity <list|show|use|remove> ...");
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// src/commands/integrator.ts
|
|
658
|
+
import fs5 from "fs";
|
|
659
|
+
import path6 from "path";
|
|
660
|
+
import { parseArgs as parseArgs3 } from "util";
|
|
661
|
+
async function handleIntegratorConsent(args, ctx) {
|
|
662
|
+
const { values, positionals } = parseArgs3({
|
|
663
|
+
args,
|
|
664
|
+
options: {
|
|
665
|
+
as: { type: "string" },
|
|
666
|
+
credentials: { type: "string" },
|
|
667
|
+
integrator: { type: "string" },
|
|
668
|
+
url: { type: "string", default: DEFAULT_BASE_URL }
|
|
669
|
+
},
|
|
670
|
+
strict: true,
|
|
671
|
+
allowPositionals: true
|
|
672
|
+
});
|
|
673
|
+
const consentAction = positionals[0];
|
|
674
|
+
if (consentAction !== "allow" && consentAction !== "revoke") {
|
|
675
|
+
fail(
|
|
676
|
+
"Usage: identityapp integrator consent <allow|revoke> --as <alias> --integrator <slug>"
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
if (!values.integrator) {
|
|
680
|
+
fail("Missing required flag: --integrator");
|
|
681
|
+
}
|
|
682
|
+
const config = readConfig(ctx);
|
|
683
|
+
const creds = values.credentials ? loadCredentials(path6.resolve(values.credentials)) : loadIdentityFromAlias(ctx, config, values.as);
|
|
684
|
+
const baseUrl = stripTrailingSlash(values.url);
|
|
685
|
+
const signedAt = Date.now();
|
|
686
|
+
const consentPayload = canonicalize({
|
|
687
|
+
type: "integrator_consent_v1",
|
|
688
|
+
did: creds.did,
|
|
689
|
+
integratorSlug: values.integrator,
|
|
690
|
+
action: consentAction,
|
|
691
|
+
signedAt
|
|
692
|
+
});
|
|
693
|
+
const payloadHash = sha256Hex(consentPayload);
|
|
694
|
+
const signature = signMessage(creds.privateKey, `${payloadHash}:${signedAt}`);
|
|
695
|
+
console.error("Signing consent payload...");
|
|
696
|
+
const signResponse = await fetch(`${baseUrl}/api/v1/signatures/sign`, {
|
|
697
|
+
method: "POST",
|
|
698
|
+
headers: { "Content-Type": "application/json" },
|
|
699
|
+
body: JSON.stringify({
|
|
700
|
+
did: creds.did,
|
|
701
|
+
payloadHash,
|
|
702
|
+
signature,
|
|
703
|
+
signedAt,
|
|
704
|
+
publicNote: `integrator consent: ${consentAction} ${values.integrator}`
|
|
705
|
+
})
|
|
706
|
+
});
|
|
707
|
+
if (!signResponse.ok) {
|
|
708
|
+
const error = await readResponseJson(signResponse);
|
|
709
|
+
fail(
|
|
710
|
+
`Signing consent failed (${signResponse.status}): ${error.error ?? "Unknown error"}`
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
const { signatureHash } = await signResponse.json();
|
|
714
|
+
console.error("Submitting consent...");
|
|
715
|
+
const consentResponse = await fetch(`${baseUrl}/api/v1/integrators/consent`, {
|
|
716
|
+
method: "POST",
|
|
717
|
+
headers: { "Content-Type": "application/json" },
|
|
718
|
+
body: JSON.stringify({
|
|
719
|
+
integratorSlug: values.integrator,
|
|
720
|
+
did: creds.did,
|
|
721
|
+
action: consentAction,
|
|
722
|
+
signatureHash,
|
|
723
|
+
signedAt
|
|
724
|
+
})
|
|
725
|
+
});
|
|
726
|
+
if (!consentResponse.ok) {
|
|
727
|
+
const error = await readResponseJson(consentResponse);
|
|
728
|
+
fail(
|
|
729
|
+
`Consent update failed (${consentResponse.status}): ${error.error ?? "Unknown error"}`
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
console.log(JSON.stringify(await consentResponse.json(), null, 2));
|
|
733
|
+
}
|
|
734
|
+
async function handleIntegratorIngest(args) {
|
|
735
|
+
const { values } = parseArgs3({
|
|
736
|
+
args,
|
|
737
|
+
options: {
|
|
738
|
+
"api-key": { type: "string" },
|
|
739
|
+
"ingest-url": { type: "string" },
|
|
740
|
+
body: { type: "string" },
|
|
741
|
+
"body-file": { type: "string" }
|
|
742
|
+
},
|
|
743
|
+
strict: true,
|
|
744
|
+
allowPositionals: false
|
|
745
|
+
});
|
|
746
|
+
if (!values["api-key"]) {
|
|
747
|
+
fail("Missing required flag: --api-key");
|
|
748
|
+
}
|
|
749
|
+
if (!values.body && !values["body-file"]) {
|
|
750
|
+
fail("Provide one of --body or --body-file");
|
|
751
|
+
}
|
|
752
|
+
const jsonText = values["body-file"] ? fs5.existsSync(values["body-file"]) ? fs5.readFileSync(values["body-file"], "utf-8") : fail(`File not found: ${values["body-file"]}`) : values.body;
|
|
753
|
+
parseJsonText(jsonText);
|
|
754
|
+
const ingestUrl = values["ingest-url"] ?? DEFAULT_EVENTS_URL;
|
|
755
|
+
const response = await fetch(ingestUrl, {
|
|
756
|
+
method: "POST",
|
|
757
|
+
headers: {
|
|
758
|
+
"Content-Type": "application/json",
|
|
759
|
+
...withBearer(values["api-key"])
|
|
760
|
+
},
|
|
761
|
+
body: jsonText
|
|
762
|
+
});
|
|
763
|
+
const body = await readResponseJson(response);
|
|
764
|
+
if (!response.ok) {
|
|
765
|
+
fail(
|
|
766
|
+
`Ingest failed (${response.status}): ${body.error ?? "Unknown error"}`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
console.log(JSON.stringify(body, null, 2));
|
|
770
|
+
}
|
|
771
|
+
function extractApiKey(tokens) {
|
|
772
|
+
const out = [];
|
|
773
|
+
let apiKey;
|
|
774
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
775
|
+
const token = tokens[i];
|
|
776
|
+
if (token === "--api-key") {
|
|
777
|
+
apiKey = tokens[i + 1];
|
|
778
|
+
i += 1;
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
out.push(token);
|
|
782
|
+
}
|
|
783
|
+
return { apiKey, remaining: out };
|
|
784
|
+
}
|
|
785
|
+
async function handleIntegrator(args, ctx) {
|
|
786
|
+
const subcommand = args[0];
|
|
787
|
+
const rest = args.slice(1);
|
|
788
|
+
if (subcommand === "consent") {
|
|
789
|
+
await handleIntegratorConsent(rest, ctx);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
if (subcommand === "ingest") {
|
|
793
|
+
await handleIntegratorIngest(rest);
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
if (subcommand === "verify") {
|
|
797
|
+
const parsed = extractApiKey(rest);
|
|
798
|
+
if (!parsed.apiKey) fail("Missing required flag: --api-key");
|
|
799
|
+
await handleVerify(parsed.remaining, { apiKey: parsed.apiKey });
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
if (subcommand === "certify") {
|
|
803
|
+
const parsed = extractApiKey(rest);
|
|
804
|
+
if (!parsed.apiKey) fail("Missing required flag: --api-key");
|
|
805
|
+
await handleCertify(parsed.remaining, { apiKey: parsed.apiKey });
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
fail("Usage: identityapp integrator <consent|ingest|verify|certify> ...");
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// src/usage.ts
|
|
812
|
+
function usage() {
|
|
813
|
+
return `
|
|
814
|
+
identityapp CLI
|
|
815
|
+
|
|
816
|
+
Usage:
|
|
817
|
+
identityapp register [--as <alias>] [--label <name>] [--linking-key <key>] [--no-link] [--url <base_url>]
|
|
818
|
+
[--key-file <path>] [--public-key <b64> --private-key <b64>]
|
|
819
|
+
[--save <path>] [--home <dir>]
|
|
820
|
+
identityapp sign <payload> [--as <alias>] [--note <text>] [--credentials <path>] [--url <base_url>] [--home <dir>]
|
|
821
|
+
identityapp sign --file <path> [--as <alias>] [--note <text>] [--credentials <path>] [--url <base_url>] [--home <dir>]
|
|
822
|
+
identityapp verify <signature_hash> [content_to_check] [--url <base_url>]
|
|
823
|
+
identityapp certify <signature_hash> <content> [--url <base_url>]
|
|
824
|
+
identityapp certify <signature_hash> --file <path> [--url <base_url>]
|
|
825
|
+
identityapp report <target_did> <reason> [--as <alias>] [--signature <hash>] [--details <text>]
|
|
826
|
+
[--credentials <path>] [--url <base_url>] [--home <dir>]
|
|
827
|
+
|
|
828
|
+
identityapp identity list [--home <dir>]
|
|
829
|
+
identityapp identity show [--as <alias>] [--home <dir>]
|
|
830
|
+
identityapp identity use <alias> [--home <dir>]
|
|
831
|
+
identityapp identity remove --as <alias> --yes [--home <dir>]
|
|
832
|
+
|
|
833
|
+
identityapp auth link set <linking_key> [--home <dir>]
|
|
834
|
+
identityapp auth link show [--home <dir>]
|
|
835
|
+
identityapp auth link clear [--home <dir>]
|
|
836
|
+
|
|
837
|
+
identityapp integrator consent <allow|revoke> --as <alias> --integrator <slug>
|
|
838
|
+
[--credentials <path>] [--url <base_url>] [--home <dir>]
|
|
839
|
+
identityapp integrator ingest --api-key <key> [--ingest-url <full_url>] [--home <dir>]
|
|
840
|
+
(--body <json> | --body-file <path>)
|
|
841
|
+
identityapp integrator verify <signature_hash> [content_to_check] --api-key <key> [--url <base_url>]
|
|
842
|
+
identityapp integrator certify <signature_hash> <content> --api-key <key> [--url <base_url>]
|
|
843
|
+
identityapp integrator certify <signature_hash> --file <path> --api-key <key> [--url <base_url>]
|
|
844
|
+
|
|
845
|
+
Examples:
|
|
846
|
+
npx identityapp register --label "my-agent"
|
|
847
|
+
npx identityapp sign "Hello world"
|
|
848
|
+
npx identityapp verify <signatureHash> "Hello world"
|
|
849
|
+
npx identityapp integrator ingest --api-key <key> --body-file ./event.json
|
|
850
|
+
`.trim();
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// src/cli.ts
|
|
854
|
+
async function main() {
|
|
855
|
+
const parsed = resolveHome(process.argv.slice(2));
|
|
856
|
+
const args = parsed.args;
|
|
857
|
+
const ctx = createContext(parsed.home);
|
|
858
|
+
const command = args[0];
|
|
859
|
+
const rest = args.slice(1);
|
|
860
|
+
if (!command || command === "--help" || command === "-h") {
|
|
861
|
+
console.log(usage());
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
if (command === "--version" || command === "-v") {
|
|
865
|
+
console.log(CURRENT_VERSION);
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
if (command === "register") {
|
|
869
|
+
await handleRegister(rest, ctx);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
if (command === "sign") {
|
|
873
|
+
await handleSign(rest, ctx);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
if (command === "verify") {
|
|
877
|
+
await handleVerify(rest);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (command === "certify") {
|
|
881
|
+
await handleCertify(rest);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (command === "report") {
|
|
885
|
+
await handleReport(rest, ctx);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
if (command === "identity") {
|
|
889
|
+
await handleIdentity(rest, ctx);
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (command === "auth") {
|
|
893
|
+
await handleAuth(rest, ctx);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
if (command === "integrator") {
|
|
897
|
+
await handleIntegrator(rest, ctx);
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
fail(`Unknown command: ${command}
|
|
901
|
+
|
|
902
|
+
${usage()}`);
|
|
903
|
+
}
|
|
904
|
+
main().catch((error) => {
|
|
905
|
+
fail(error instanceof Error ? error.message : "Unknown error");
|
|
906
|
+
});
|
|
907
|
+
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/lib/errors.ts","../src/lib/storage.ts","../src/lib/fs.ts","../src/commands/auth.ts","../src/commands/agent.ts","../src/lib/crypto.ts","../src/lib/http.ts","../src/lib/utils.ts","../src/commands/identity.ts","../src/commands/integrator.ts","../src/usage.ts","../src/cli.ts"],"sourcesContent":["import os from \"node:os\";\nimport path from \"node:path\";\n\nexport const DEFAULT_BASE_URL = \"https://identity.app\";\nexport const DEFAULT_EVENTS_URL = \"https://integrator.identity.app/ingest\";\nexport const DEFAULT_IDENTITY_HOME = path.join(os.homedir(), \".identity\");\nexport const IDENTITY_HOME_ENV = \"IDENTITY_HOME\";\nexport const CURRENT_VERSION = \"0.1.0\";\n","export function fail(message: string): never {\n console.error(message);\n process.exit(1);\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { DEFAULT_IDENTITY_HOME, IDENTITY_HOME_ENV } from \"../constants\";\nimport type { Credentials, IdentityConfig, RuntimeContext } from \"../types\";\nimport { fail } from \"./errors\";\nimport { readJsonFile, writeSecureJsonFile } from \"./fs\";\n\nexport function resolveHome(rawArgs: string[]) {\n const remaining: string[] = [];\n let homeFromArg: string | undefined;\n\n for (let i = 0; i < rawArgs.length; i += 1) {\n const token = rawArgs[i];\n if (token === \"--home\") {\n homeFromArg = rawArgs[i + 1];\n i += 1;\n continue;\n }\n if (token.startsWith(\"--home=\")) {\n homeFromArg = token.slice(\"--home=\".length);\n continue;\n }\n remaining.push(token);\n }\n\n const home =\n homeFromArg || process.env[IDENTITY_HOME_ENV] || DEFAULT_IDENTITY_HOME;\n const absHome = path.resolve(home);\n return { home: absHome, args: remaining };\n}\n\nexport function createContext(home: string): RuntimeContext {\n const identitiesDir = path.join(home, \"identities\");\n return {\n home,\n configPath: path.join(home, \"config.json\"),\n identitiesDir,\n };\n}\n\nfunction defaultConfig(): IdentityConfig {\n return {\n version: 1,\n defaultAlias: null,\n defaultLinkingKey: null,\n };\n}\n\nexport function readConfig(ctx: RuntimeContext): IdentityConfig {\n if (!fs.existsSync(ctx.configPath)) {\n return defaultConfig();\n }\n const parsed = readJsonFile(ctx.configPath) as Partial<IdentityConfig>;\n return {\n version: typeof parsed.version === \"number\" ? parsed.version : 1,\n defaultAlias:\n typeof parsed.defaultAlias === \"string\" ? parsed.defaultAlias : null,\n defaultLinkingKey:\n typeof parsed.defaultLinkingKey === \"string\"\n ? parsed.defaultLinkingKey\n : null,\n };\n}\n\nexport function writeConfig(ctx: RuntimeContext, config: IdentityConfig) {\n writeSecureJsonFile(ctx.configPath, config);\n}\n\nexport function normalizeAlias(alias: string): string {\n const normalized = alias.trim().toLowerCase();\n if (!/^[a-z][a-z0-9_-]{0,62}$/.test(normalized)) {\n fail(\n `Invalid alias \"${alias}\". Alias must start with a letter and contain only lowercase letters, digits, \"_\" or \"-\".`,\n );\n }\n return normalized;\n}\n\nexport function identityPathForAlias(ctx: RuntimeContext, alias: string): string {\n return path.join(ctx.identitiesDir, `${normalizeAlias(alias)}.json`);\n}\n\nexport function resolveAlias(\n requestedAlias: string | undefined,\n config: IdentityConfig,\n): string {\n if (requestedAlias) return normalizeAlias(requestedAlias);\n if (config.defaultAlias) return normalizeAlias(config.defaultAlias);\n fail(\n 'No alias selected. Pass --as <alias> or set a default with \"identityapp identity use <alias>\".',\n );\n}\n\nexport function loadCredentials(filePath?: string): Credentials {\n if (!filePath) fail(\"Internal error: credentials file path is required\");\n const json = readJsonFile(filePath) as Partial<Credentials>;\n if (!json.did || !json.privateKey) {\n fail(`Invalid credentials file: ${filePath} (missing did or privateKey)`);\n }\n return {\n did: json.did,\n publicKey: json.publicKey,\n privateKey: json.privateKey,\n linked: json.linked,\n claimToken: json.claimToken,\n };\n}\n\nexport function saveCredentials(filePath: string, creds: Credentials) {\n writeSecureJsonFile(filePath, creds);\n}\n\nexport function loadIdentityFromAlias(\n ctx: RuntimeContext,\n config: IdentityConfig,\n requestedAlias?: string,\n): Credentials {\n const alias = resolveAlias(requestedAlias, config);\n const filePath = identityPathForAlias(ctx, alias);\n if (!fs.existsSync(filePath)) {\n fail(\n `Identity alias \"${alias}\" not found at ${filePath}. Register first with \"identityapp register --as ${alias}\".`,\n );\n }\n const credentials = loadCredentials(filePath);\n return { ...credentials, alias };\n}\n\nexport function maybeSetDefaultAlias(\n ctx: RuntimeContext,\n config: IdentityConfig,\n alias: string,\n) {\n if (!config.defaultAlias) {\n writeConfig(ctx, { ...config, defaultAlias: alias });\n }\n}\n\nexport function redactPrivateKey(identity: Credentials) {\n return {\n ...identity,\n privateKey: \"[redacted]\",\n };\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fail } from \"./errors\";\n\nexport function ensureDir(dirPath: string) {\n fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });\n}\n\nexport function ensureDirForFile(filePath: string) {\n ensureDir(path.dirname(filePath));\n}\n\nexport function readJsonFile(filePath: string): unknown {\n try {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n return JSON.parse(raw);\n } catch (error) {\n if (\n typeof error === \"object\" &&\n error &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n ) {\n fail(`File not found: ${filePath}`);\n }\n fail(`Invalid JSON file: ${filePath}`);\n }\n}\n\nexport function writeSecureJsonFile(filePath: string, value: unknown) {\n ensureDirForFile(filePath);\n fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\\n`, \"utf-8\");\n try {\n fs.chmodSync(filePath, 0o600);\n } catch {\n // Ignore chmod issues on non-POSIX filesystems.\n }\n}\n","import type { RuntimeContext } from \"../types\";\nimport { fail } from \"../lib/errors\";\nimport { readConfig, writeConfig } from \"../lib/storage\";\n\nexport async function handleAuth(args: string[], ctx: RuntimeContext) {\n const subcommand = args[0];\n const rest = args.slice(1);\n if (subcommand !== \"link\") {\n fail(\"Usage: identityapp auth link <set|show|clear> ...\");\n }\n\n const action = rest[0];\n const actionArgs = rest.slice(1);\n const config = readConfig(ctx);\n\n if (action === \"set\") {\n const key = actionArgs[0];\n if (!key) fail(\"Usage: identityapp auth link set <linking_key>\");\n writeConfig(ctx, { ...config, defaultLinkingKey: key });\n console.log(JSON.stringify({ ok: true, defaultLinkingKey: \"[set]\" }, null, 2));\n return;\n }\n if (action === \"show\") {\n console.log(\n JSON.stringify(\n {\n defaultLinkingKey: config.defaultLinkingKey ? \"[set]\" : null,\n },\n null,\n 2,\n ),\n );\n return;\n }\n if (action === \"clear\") {\n writeConfig(ctx, { ...config, defaultLinkingKey: null });\n console.log(JSON.stringify({ ok: true, defaultLinkingKey: null }, null, 2));\n return;\n }\n\n fail(\"Usage: identityapp auth link <set|show|clear> ...\");\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { DEFAULT_BASE_URL } from \"../constants\";\nimport type { Credentials, RuntimeContext } from \"../types\";\nimport { generateEd25519KeyPair, minePowNonce, sha256Hex, signMessage } from \"../lib/crypto\";\nimport { fail } from \"../lib/errors\";\nimport { readJsonFile } from \"../lib/fs\";\nimport { readResponseJson, withBearer } from \"../lib/http\";\nimport {\n identityPathForAlias,\n loadCredentials,\n loadIdentityFromAlias,\n maybeSetDefaultAlias,\n readConfig,\n resolveAlias,\n saveCredentials,\n} from \"../lib/storage\";\nimport { stripTrailingSlash } from \"../lib/utils\";\n\nexport async function handleRegister(args: string[], ctx: RuntimeContext) {\n const { values } = parseArgs({\n args,\n options: {\n as: { type: \"string\" },\n label: { type: \"string\" },\n \"linking-key\": { type: \"string\" },\n \"no-link\": { type: \"boolean\" },\n \"key-file\": { type: \"string\" },\n \"public-key\": { type: \"string\" },\n \"private-key\": { type: \"string\" },\n save: { type: \"string\" },\n url: { type: \"string\", default: DEFAULT_BASE_URL },\n },\n strict: true,\n allowPositionals: false,\n });\n const config = readConfig(ctx);\n const alias = resolveAlias(values.as, config);\n const canonicalPath = identityPathForAlias(ctx, alias);\n if (fs.existsSync(canonicalPath)) {\n fail(\n `Identity alias \"${alias}\" already exists at ${canonicalPath}. Register with a new alias or remove the existing one via \"identityapp identity remove --as ${alias} --yes\".`,\n );\n }\n\n let keyPair: { publicKey: string; privateKey: string };\n if (values[\"key-file\"]) {\n const json = readJsonFile(values[\"key-file\"]) as Partial<{\n publicKey: string;\n privateKey: string;\n }>;\n if (!json.publicKey || !json.privateKey) {\n fail('Key file must include \"publicKey\" and \"privateKey\"');\n }\n keyPair = { publicKey: json.publicKey, privateKey: json.privateKey };\n } else if (values[\"public-key\"] && values[\"private-key\"]) {\n console.error(\n \"Warning: passing --private-key on the CLI exposes it in shell history. Prefer --key-file.\",\n );\n keyPair = {\n publicKey: values[\"public-key\"],\n privateKey: values[\"private-key\"],\n };\n } else if (values[\"public-key\"] || values[\"private-key\"]) {\n fail(\"Both --public-key and --private-key must be set together\");\n } else {\n keyPair = generateEd25519KeyPair();\n }\n\n console.error(\"Mining proof-of-work nonce...\");\n const powNonce = minePowNonce(keyPair.publicKey);\n\n const payload: Record<string, unknown> = {\n publicKey: keyPair.publicKey,\n powNonce,\n };\n const label = values.label ?? alias;\n payload.label = label;\n\n const linkingKey =\n values[\"no-link\"] === true\n ? undefined\n : values[\"linking-key\"] ?? config.defaultLinkingKey ?? undefined;\n if (linkingKey) payload.linkingKey = linkingKey;\n\n const baseUrl = stripTrailingSlash(values.url);\n const response = await fetch(`${baseUrl}/api/v1/agents/register`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n });\n if (!response.ok) {\n const error = await readResponseJson(response);\n fail(\n `Registration failed (${response.status}): ${(error as { error?: string }).error ?? \"Unknown error\"}`,\n );\n }\n\n const body = (await response.json()) as {\n did: string;\n linked: boolean;\n claimToken?: string;\n };\n const credentials: Credentials = {\n alias,\n did: body.did,\n publicKey: keyPair.publicKey,\n privateKey: keyPair.privateKey,\n linked: body.linked,\n claimToken: body.claimToken,\n label,\n createdAt: Date.now(),\n updatedAt: Date.now(),\n };\n\n saveCredentials(canonicalPath, credentials);\n maybeSetDefaultAlias(ctx, config, alias);\n console.error(`Identity saved to ${canonicalPath}`);\n\n if (values.save) {\n saveCredentials(path.resolve(values.save), credentials);\n console.error(`Additional copy saved to ${path.resolve(values.save)}`);\n }\n\n const registerOutput = {\n alias: credentials.alias,\n did: credentials.did,\n publicKey: credentials.publicKey,\n linked: credentials.linked,\n claimToken: credentials.claimToken,\n label: credentials.label,\n createdAt: credentials.createdAt,\n updatedAt: credentials.updatedAt,\n identityPath: canonicalPath,\n privateKey: \"[stored locally only; never printed]\",\n };\n console.log(JSON.stringify(registerOutput, null, 2));\n}\n\nexport async function handleSign(args: string[], ctx: RuntimeContext) {\n const { values, positionals } = parseArgs({\n args,\n options: {\n as: { type: \"string\" },\n file: { type: \"string\" },\n note: { type: \"string\" },\n credentials: { type: \"string\" },\n url: { type: \"string\", default: DEFAULT_BASE_URL },\n },\n strict: true,\n allowPositionals: true,\n });\n\n const payload = positionals[0];\n if (!payload && !values.file) {\n fail(\n \"Usage: identityapp sign <payload> [--note <text>] [--credentials <path>] [--url <base_url>] OR identityapp sign --file <path> ...\",\n );\n }\n\n const config = readConfig(ctx);\n const creds = values.credentials\n ? loadCredentials(path.resolve(values.credentials))\n : loadIdentityFromAlias(ctx, config, values.as);\n const content =\n values.file !== undefined\n ? fs.existsSync(values.file)\n ? fs.readFileSync(values.file)\n : fail(`File not found: ${values.file}`)\n : payload;\n const payloadHash = sha256Hex(content);\n const signedAt = Date.now();\n const signature = signMessage(creds.privateKey, `${payloadHash}:${signedAt}`);\n\n const response = await fetch(\n `${stripTrailingSlash(values.url)}/api/v1/signatures/sign`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n did: creds.did,\n payloadHash,\n signature,\n signedAt,\n publicNote: values.note,\n }),\n },\n );\n if (!response.ok) {\n const error = await readResponseJson(response);\n fail(\n `Signing failed (${response.status}): ${(error as { error?: string }).error ?? \"Unknown error\"}`,\n );\n }\n const result = (await response.json()) as { signatureHash: string };\n const baseUrl = stripTrailingSlash(values.url);\n console.log(\n JSON.stringify(\n {\n ...result,\n verifyUrl: `${baseUrl}/verify/${result.signatureHash}`,\n verifyApiUrl: `${baseUrl}/api/v1/signatures/verify?hash=${result.signatureHash}`,\n },\n null,\n 2,\n ),\n );\n}\n\nexport async function handleVerify(\n args: string[],\n options?: {\n apiKey?: string;\n },\n) {\n const { values, positionals } = parseArgs({\n args,\n options: {\n url: { type: \"string\", default: DEFAULT_BASE_URL },\n },\n strict: true,\n allowPositionals: true,\n });\n const signatureHash = positionals[0];\n const contentToCheck = positionals[1];\n if (!signatureHash) {\n fail(\n \"Usage: identityapp verify <signature_hash> [content_to_check] [--url <base_url>]\",\n );\n }\n\n const headers: HeadersInit = {};\n if (options?.apiKey) Object.assign(headers, withBearer(options.apiKey));\n\n const response = await fetch(\n `${stripTrailingSlash(values.url)}/api/v1/signatures/verify?hash=${encodeURIComponent(signatureHash)}`,\n { headers },\n );\n if (!response.ok) {\n const error = await readResponseJson(response);\n fail(\n `Verification failed (${response.status}): ${(error as { error?: string }).error ?? \"Unknown error\"}`,\n );\n }\n const result = (await response.json()) as { payloadHash: string };\n const output =\n contentToCheck !== undefined\n ? {\n ...result,\n contentMatch: sha256Hex(contentToCheck) === result.payloadHash,\n }\n : result;\n console.log(JSON.stringify(output, null, 2));\n}\n\nexport async function handleCertify(\n args: string[],\n options?: {\n apiKey?: string;\n },\n) {\n const { values, positionals } = parseArgs({\n args,\n options: {\n file: { type: \"string\" },\n url: { type: \"string\", default: DEFAULT_BASE_URL },\n },\n strict: true,\n allowPositionals: true,\n });\n const signatureHash = positionals[0];\n const contentArg = positionals[1];\n if (!signatureHash || (!contentArg && !values.file)) {\n fail(\n \"Usage: identityapp certify <signature_hash> <content> [--url <base_url>] OR identityapp certify <signature_hash> --file <path> [--url <base_url>]\",\n );\n }\n\n const content =\n values.file !== undefined\n ? fs.existsSync(values.file)\n ? fs.readFileSync(values.file)\n : fail(`File not found: ${values.file}`)\n : contentArg;\n const contentHash = sha256Hex(content);\n\n const headers: HeadersInit = { \"Content-Type\": \"application/json\" };\n if (options?.apiKey) Object.assign(headers, withBearer(options.apiKey));\n\n const response = await fetch(\n `${stripTrailingSlash(values.url)}/api/v1/signatures/certify`,\n {\n method: \"POST\",\n headers,\n body: JSON.stringify({ signatureHash, contentHash }),\n },\n );\n if (!response.ok) {\n const error = await readResponseJson(response);\n fail(\n `Certification failed (${response.status}): ${(error as { error?: string }).error ?? \"Unknown error\"}`,\n );\n }\n console.log(JSON.stringify(await response.json(), null, 2));\n}\n\nexport async function handleReport(args: string[], ctx: RuntimeContext) {\n const { values, positionals } = parseArgs({\n args,\n options: {\n as: { type: \"string\" },\n signature: { type: \"string\" },\n details: { type: \"string\" },\n credentials: { type: \"string\" },\n url: { type: \"string\", default: DEFAULT_BASE_URL },\n },\n strict: true,\n allowPositionals: true,\n });\n const targetDid = positionals[0];\n const reason = positionals[1];\n if (!targetDid || !reason) {\n fail(\n \"Usage: identityapp report <target_did> <reason> [--signature <hash>] [--details <text>] [--credentials <path>] [--url <base_url>]\",\n );\n }\n\n const validReasons = [\"spam\", \"impersonation\", \"malicious\", \"other\"];\n if (!validReasons.includes(reason)) {\n fail(\n `Invalid reason \"${reason}\". Must be one of: ${validReasons.join(\", \")}`,\n );\n }\n\n const config = readConfig(ctx);\n const creds = values.credentials\n ? loadCredentials(path.resolve(values.credentials))\n : loadIdentityFromAlias(ctx, config, values.as);\n const signedAt = Date.now();\n const signature = signMessage(\n creds.privateKey,\n `report:${targetDid}:${reason}:${signedAt}`,\n );\n\n const response = await fetch(\n `${stripTrailingSlash(values.url)}/api/v1/agents/report`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n did: targetDid,\n reason,\n reporterDid: creds.did,\n signature,\n signedAt,\n signatureHash: values.signature,\n details: values.details,\n }),\n },\n );\n if (!response.ok) {\n const error = await readResponseJson(response);\n fail(\n `Report failed (${response.status}): ${(error as { error?: string }).error ?? \"Unknown error\"}`,\n );\n }\n console.log(JSON.stringify(await response.json(), null, 2));\n}\n","import crypto from \"node:crypto\";\n\nexport function sha256Hex(input: string | Buffer): string {\n return crypto.createHash(\"sha256\").update(input).digest(\"hex\");\n}\n\nexport function buildPrivateKeyFromRawBase64(privateKeyBase64: string) {\n const raw = Buffer.from(privateKeyBase64, \"base64\");\n const pkcs8Prefix = Buffer.from(\"302e020100300506032b657004220420\", \"hex\");\n const der = Buffer.concat([pkcs8Prefix, raw]);\n return crypto.createPrivateKey({\n key: der,\n format: \"der\",\n type: \"pkcs8\",\n });\n}\n\nexport function signMessage(privateKeyBase64: string, message: string): string {\n const privateKey = buildPrivateKeyFromRawBase64(privateKeyBase64);\n return crypto.sign(null, Buffer.from(message), privateKey).toString(\"base64\");\n}\n\nexport function generateEd25519KeyPair() {\n const { publicKey, privateKey } = crypto.generateKeyPairSync(\"ed25519\");\n const publicKeyB64 = publicKey\n .export({ type: \"spki\", format: \"der\" })\n .subarray(-32)\n .toString(\"base64\");\n const privateKeyB64 = privateKey\n .export({ type: \"pkcs8\", format: \"der\" })\n .subarray(-32)\n .toString(\"base64\");\n return { publicKey: publicKeyB64, privateKey: privateKeyB64 };\n}\n\nexport function minePowNonce(publicKey: string, difficulty = 4): string {\n let nonce = 0;\n const prefix = \"0\".repeat(difficulty);\n while (true) {\n const hash = sha256Hex(`${publicKey}${String(nonce)}`);\n if (hash.startsWith(prefix)) return String(nonce);\n nonce += 1;\n }\n}\n","export function withBearer(apiKey: string): HeadersInit {\n return {\n Authorization: `Bearer ${apiKey}`,\n };\n}\n\nexport async function readResponseJson(response: Response) {\n return response.json().catch(() => ({}));\n}\n","import { canonicalize } from \"json-canonicalize\";\nimport { fail } from \"./errors\";\n\nexport { canonicalize };\n\nexport function stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\nexport function parseJsonText(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n fail(\"Body is not valid JSON\");\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport type { Credentials, RuntimeContext } from \"../types\";\nimport { fail } from \"../lib/errors\";\nimport { readJsonFile } from \"../lib/fs\";\nimport {\n identityPathForAlias,\n loadIdentityFromAlias,\n normalizeAlias,\n readConfig,\n redactPrivateKey,\n writeConfig,\n} from \"../lib/storage\";\n\nexport async function handleIdentity(args: string[], ctx: RuntimeContext) {\n const subcommand = args[0];\n const rest = args.slice(1);\n\n if (subcommand === \"list\") {\n const config = readConfig(ctx);\n if (!fs.existsSync(ctx.identitiesDir)) {\n console.log(\n JSON.stringify({ defaultAlias: config.defaultAlias, identities: [] }, null, 2),\n );\n return;\n }\n const files = fs\n .readdirSync(ctx.identitiesDir)\n .filter((name) => name.endsWith(\".json\"))\n .sort();\n const identities = files.map((fileName) => {\n const filePath = path.join(ctx.identitiesDir, fileName);\n const identity = readJsonFile(filePath) as Partial<Credentials>;\n const alias = fileName.slice(0, -5);\n return {\n alias,\n did: identity.did ?? null,\n label: identity.label ?? null,\n updatedAt: identity.updatedAt ?? null,\n isDefault: config.defaultAlias === alias,\n };\n });\n console.log(\n JSON.stringify(\n {\n home: ctx.home,\n defaultAlias: config.defaultAlias,\n identities,\n },\n null,\n 2,\n ),\n );\n return;\n }\n\n if (subcommand === \"show\") {\n const { values } = parseArgs({\n args: rest,\n options: {\n as: { type: \"string\" },\n },\n strict: true,\n allowPositionals: false,\n });\n const config = readConfig(ctx);\n const identity = loadIdentityFromAlias(ctx, config, values.as);\n console.log(\n JSON.stringify(\n {\n home: ctx.home,\n ...redactPrivateKey(identity),\n },\n null,\n 2,\n ),\n );\n return;\n }\n\n if (subcommand === \"use\") {\n const alias = rest[0];\n if (!alias) fail(\"Usage: identityapp identity use <alias>\");\n const normalized = normalizeAlias(alias);\n const identityPath = identityPathForAlias(ctx, normalized);\n if (!fs.existsSync(identityPath)) {\n fail(`Identity alias \"${normalized}\" not found.`);\n }\n const config = readConfig(ctx);\n writeConfig(ctx, { ...config, defaultAlias: normalized });\n console.log(JSON.stringify({ ok: true, defaultAlias: normalized }, null, 2));\n return;\n }\n\n if (subcommand === \"remove\") {\n const { values } = parseArgs({\n args: rest,\n options: {\n as: { type: \"string\" },\n yes: { type: \"boolean\" },\n },\n strict: true,\n allowPositionals: false,\n });\n if (!values.as) fail(\"Usage: identityapp identity remove --as <alias> --yes\");\n if (values.yes !== true) {\n fail(\"Refusing to remove identity without --yes\");\n }\n const alias = normalizeAlias(values.as);\n const target = identityPathForAlias(ctx, alias);\n if (!fs.existsSync(target)) {\n fail(`Identity alias \"${alias}\" not found.`);\n }\n fs.rmSync(target);\n const config = readConfig(ctx);\n const nextConfig =\n config.defaultAlias === alias ? { ...config, defaultAlias: null } : config;\n writeConfig(ctx, nextConfig);\n console.log(JSON.stringify({ ok: true, removed: alias }, null, 2));\n return;\n }\n\n fail(\"Usage: identityapp identity <list|show|use|remove> ...\");\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { DEFAULT_BASE_URL, DEFAULT_EVENTS_URL } from \"../constants\";\nimport type { RuntimeContext } from \"../types\";\nimport { sha256Hex, signMessage } from \"../lib/crypto\";\nimport { handleCertify, handleVerify } from \"./agent\";\nimport { fail } from \"../lib/errors\";\nimport { readResponseJson, withBearer } from \"../lib/http\";\nimport { loadCredentials, loadIdentityFromAlias, readConfig } from \"../lib/storage\";\nimport { canonicalize, parseJsonText, stripTrailingSlash } from \"../lib/utils\";\n\nasync function handleIntegratorConsent(args: string[], ctx: RuntimeContext) {\n const { values, positionals } = parseArgs({\n args,\n options: {\n as: { type: \"string\" },\n credentials: { type: \"string\" },\n integrator: { type: \"string\" },\n url: { type: \"string\", default: DEFAULT_BASE_URL },\n },\n strict: true,\n allowPositionals: true,\n });\n\n const consentAction = positionals[0];\n if (consentAction !== \"allow\" && consentAction !== \"revoke\") {\n fail(\n \"Usage: identityapp integrator consent <allow|revoke> --as <alias> --integrator <slug>\",\n );\n }\n if (!values.integrator) {\n fail(\"Missing required flag: --integrator\");\n }\n\n const config = readConfig(ctx);\n const creds = values.credentials\n ? loadCredentials(path.resolve(values.credentials))\n : loadIdentityFromAlias(ctx, config, values.as);\n\n const baseUrl = stripTrailingSlash(values.url);\n const signedAt = Date.now();\n\n const consentPayload = canonicalize({\n type: \"integrator_consent_v1\",\n did: creds.did,\n integratorSlug: values.integrator,\n action: consentAction,\n signedAt,\n });\n const payloadHash = sha256Hex(consentPayload);\n const signature = signMessage(creds.privateKey, `${payloadHash}:${signedAt}`);\n\n console.error(\"Signing consent payload...\");\n const signResponse = await fetch(`${baseUrl}/api/v1/signatures/sign`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n did: creds.did,\n payloadHash,\n signature,\n signedAt,\n publicNote: `integrator consent: ${consentAction} ${values.integrator}`,\n }),\n });\n if (!signResponse.ok) {\n const error = await readResponseJson(signResponse);\n fail(\n `Signing consent failed (${signResponse.status}): ${(error as { error?: string }).error ?? \"Unknown error\"}`,\n );\n }\n const { signatureHash } = (await signResponse.json()) as { signatureHash: string };\n\n console.error(\"Submitting consent...\");\n const consentResponse = await fetch(`${baseUrl}/api/v1/integrators/consent`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n integratorSlug: values.integrator,\n did: creds.did,\n action: consentAction,\n signatureHash,\n signedAt,\n }),\n });\n if (!consentResponse.ok) {\n const error = await readResponseJson(consentResponse);\n fail(\n `Consent update failed (${consentResponse.status}): ${(error as { error?: string }).error ?? \"Unknown error\"}`,\n );\n }\n console.log(JSON.stringify(await consentResponse.json(), null, 2));\n}\n\nasync function handleIntegratorIngest(args: string[]) {\n const { values } = parseArgs({\n args,\n options: {\n \"api-key\": { type: \"string\" },\n \"ingest-url\": { type: \"string\" },\n body: { type: \"string\" },\n \"body-file\": { type: \"string\" },\n },\n strict: true,\n allowPositionals: false,\n });\n if (!values[\"api-key\"]) {\n fail(\"Missing required flag: --api-key\");\n }\n if (!values.body && !values[\"body-file\"]) {\n fail(\"Provide one of --body or --body-file\");\n }\n\n const jsonText = values[\"body-file\"]\n ? fs.existsSync(values[\"body-file\"])\n ? fs.readFileSync(values[\"body-file\"], \"utf-8\")\n : fail(`File not found: ${values[\"body-file\"]}`)\n : values.body!;\n\n parseJsonText(jsonText);\n\n const ingestUrl = values[\"ingest-url\"] ?? DEFAULT_EVENTS_URL;\n const response = await fetch(ingestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...withBearer(values[\"api-key\"]),\n },\n body: jsonText,\n });\n\n const body = await readResponseJson(response);\n if (!response.ok) {\n fail(\n `Ingest failed (${response.status}): ${(body as { error?: string }).error ?? \"Unknown error\"}`,\n );\n }\n console.log(JSON.stringify(body, null, 2));\n}\n\nfunction extractApiKey(tokens: string[]) {\n const out: string[] = [];\n let apiKey: string | undefined;\n for (let i = 0; i < tokens.length; i += 1) {\n const token = tokens[i];\n if (token === \"--api-key\") {\n apiKey = tokens[i + 1];\n i += 1;\n continue;\n }\n out.push(token);\n }\n return { apiKey, remaining: out };\n}\n\nexport async function handleIntegrator(args: string[], ctx: RuntimeContext) {\n const subcommand = args[0];\n const rest = args.slice(1);\n\n if (subcommand === \"consent\") {\n await handleIntegratorConsent(rest, ctx);\n return;\n }\n if (subcommand === \"ingest\") {\n await handleIntegratorIngest(rest);\n return;\n }\n if (subcommand === \"verify\") {\n const parsed = extractApiKey(rest);\n if (!parsed.apiKey) fail(\"Missing required flag: --api-key\");\n await handleVerify(parsed.remaining, { apiKey: parsed.apiKey });\n return;\n }\n if (subcommand === \"certify\") {\n const parsed = extractApiKey(rest);\n if (!parsed.apiKey) fail(\"Missing required flag: --api-key\");\n await handleCertify(parsed.remaining, { apiKey: parsed.apiKey });\n return;\n }\n\n fail(\"Usage: identityapp integrator <consent|ingest|verify|certify> ...\");\n}\n","export function usage() {\n return `\nidentityapp CLI\n\nUsage:\n identityapp register [--as <alias>] [--label <name>] [--linking-key <key>] [--no-link] [--url <base_url>]\n [--key-file <path>] [--public-key <b64> --private-key <b64>]\n [--save <path>] [--home <dir>]\n identityapp sign <payload> [--as <alias>] [--note <text>] [--credentials <path>] [--url <base_url>] [--home <dir>]\n identityapp sign --file <path> [--as <alias>] [--note <text>] [--credentials <path>] [--url <base_url>] [--home <dir>]\n identityapp verify <signature_hash> [content_to_check] [--url <base_url>]\n identityapp certify <signature_hash> <content> [--url <base_url>]\n identityapp certify <signature_hash> --file <path> [--url <base_url>]\n identityapp report <target_did> <reason> [--as <alias>] [--signature <hash>] [--details <text>]\n [--credentials <path>] [--url <base_url>] [--home <dir>]\n\n identityapp identity list [--home <dir>]\n identityapp identity show [--as <alias>] [--home <dir>]\n identityapp identity use <alias> [--home <dir>]\n identityapp identity remove --as <alias> --yes [--home <dir>]\n\n identityapp auth link set <linking_key> [--home <dir>]\n identityapp auth link show [--home <dir>]\n identityapp auth link clear [--home <dir>]\n\n identityapp integrator consent <allow|revoke> --as <alias> --integrator <slug>\n [--credentials <path>] [--url <base_url>] [--home <dir>]\n identityapp integrator ingest --api-key <key> [--ingest-url <full_url>] [--home <dir>]\n (--body <json> | --body-file <path>)\n identityapp integrator verify <signature_hash> [content_to_check] --api-key <key> [--url <base_url>]\n identityapp integrator certify <signature_hash> <content> --api-key <key> [--url <base_url>]\n identityapp integrator certify <signature_hash> --file <path> --api-key <key> [--url <base_url>]\n\nExamples:\n npx identityapp register --label \"my-agent\"\n npx identityapp sign \"Hello world\"\n npx identityapp verify <signatureHash> \"Hello world\"\n npx identityapp integrator ingest --api-key <key> --body-file ./event.json\n`.trim();\n}\n","#!/usr/bin/env node\n\nimport { CURRENT_VERSION } from \"./constants\";\nimport { handleAuth } from \"./commands/auth\";\nimport {\n handleCertify,\n handleRegister,\n handleReport,\n handleSign,\n handleVerify,\n} from \"./commands/agent\";\nimport { handleIdentity } from \"./commands/identity\";\nimport { handleIntegrator } from \"./commands/integrator\";\nimport { fail } from \"./lib/errors\";\nimport { createContext, resolveHome } from \"./lib/storage\";\nimport { usage } from \"./usage\";\n\nasync function main() {\n const parsed = resolveHome(process.argv.slice(2));\n const args = parsed.args;\n const ctx = createContext(parsed.home);\n const command = args[0];\n const rest = args.slice(1);\n\n if (!command || command === \"--help\" || command === \"-h\") {\n console.log(usage());\n return;\n }\n if (command === \"--version\" || command === \"-v\") {\n console.log(CURRENT_VERSION);\n return;\n }\n\n if (command === \"register\") {\n await handleRegister(rest, ctx);\n return;\n }\n if (command === \"sign\") {\n await handleSign(rest, ctx);\n return;\n }\n if (command === \"verify\") {\n await handleVerify(rest);\n return;\n }\n if (command === \"certify\") {\n await handleCertify(rest);\n return;\n }\n if (command === \"report\") {\n await handleReport(rest, ctx);\n return;\n }\n if (command === \"identity\") {\n await handleIdentity(rest, ctx);\n return;\n }\n if (command === \"auth\") {\n await handleAuth(rest, ctx);\n return;\n }\n if (command === \"integrator\") {\n await handleIntegrator(rest, ctx);\n return;\n }\n\n fail(`Unknown command: ${command}\\n\\n${usage()}`);\n}\n\nmain().catch((error) => {\n fail(error instanceof Error ? error.message : \"Unknown error\");\n});\n"],"mappings":"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","names":["fs","path","path","path","path","fs","fs","path","fs","path","fs","path","parseArgs","fs","path","parseArgs","fs","path","parseArgs","parseArgs","path","fs"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "identityapp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for identity.app agent identity, signatures, trust, and integrator flows.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"identityapp": "./bin/cli.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"bin",
|
|
12
|
+
"skills",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup",
|
|
17
|
+
"dev": "node src/cli.ts",
|
|
18
|
+
"typecheck": "tsc --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"identity",
|
|
22
|
+
"agent",
|
|
23
|
+
"signature",
|
|
24
|
+
"verify",
|
|
25
|
+
"reputation",
|
|
26
|
+
"integrator",
|
|
27
|
+
"cli"
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^24.5.2",
|
|
35
|
+
"tsup": "^8.5.0",
|
|
36
|
+
"typescript": "^5.9.2"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"json-canonicalize": "^2.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-identity
|
|
3
|
+
description: Use identityapp CLI to register agents, sign and verify content, certify authenticity, report bad actors, and run integrator consent/ingest flows.
|
|
4
|
+
metadata:
|
|
5
|
+
author: identityapp
|
|
6
|
+
version: "0.1"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Agent Identity
|
|
10
|
+
|
|
11
|
+
Use the `identityapp` npm CLI as the execution layer. This skill contains instructions only; it does not bundle scripts.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx identityapp --help
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
If you prefer a global install:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm i -g identityapp
|
|
23
|
+
identityapp --help
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Default behavior
|
|
27
|
+
|
|
28
|
+
- Default API base URL: `https://identity.app`
|
|
29
|
+
- Default identity home: `~/.identity`
|
|
30
|
+
- Credentials are stored per alias in `~/.identity/identities/<alias>.json`
|
|
31
|
+
- Most commands support `--url <base_url>` for non-production/dev usage
|
|
32
|
+
- Override identity home with `--home <dir>` or `IDENTITY_HOME=<dir>`
|
|
33
|
+
|
|
34
|
+
## Multi-identity model
|
|
35
|
+
|
|
36
|
+
- Use `--as <alias>` to choose which local identity performs an action.
|
|
37
|
+
- Set default alias:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx identityapp identity use <alias>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- List/show stored identities:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx identityapp identity list
|
|
47
|
+
npx identityapp identity show --as <alias>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Agent workflow
|
|
51
|
+
|
|
52
|
+
### 1) Register
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx identityapp register --as writer --label "writer"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Register stores credentials in `~/.identity/identities/writer.json`.
|
|
59
|
+
If `writer` already exists, registration fails to protect the existing private key.
|
|
60
|
+
|
|
61
|
+
### 2) Sign
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx identityapp sign --as writer "Hello world" --note "demo"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Or sign a file:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx identityapp sign --file ./message.txt
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 3) Verify
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx identityapp verify <signatureHash>
|
|
77
|
+
npx identityapp verify <signatureHash> "Hello world"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 4) Certify
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx identityapp certify <signatureHash> "Hello world"
|
|
84
|
+
npx identityapp certify <signatureHash> --file ./message.txt
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 5) Report
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npx identityapp report --as writer did:identity:badagent malicious --details "Scam attempts"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Default linking key management
|
|
94
|
+
|
|
95
|
+
Set a linking key once and use it by default for future registrations:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npx identityapp auth link set <linking_key>
|
|
99
|
+
npx identityapp auth link show
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`register` uses this key unless you pass `--no-link`:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npx identityapp register --as test-bot --no-link
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Human owner linking flow
|
|
109
|
+
|
|
110
|
+
If your human owner already has a linking key:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npx identityapp auth link set <linking_key>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
If your human owner does not have a linking key yet:
|
|
117
|
+
|
|
118
|
+
1. Ask them to create/log into an account on `identity.app`.
|
|
119
|
+
2. Ask them to generate a linking key from their dashboard.
|
|
120
|
+
3. Once they share it, set it locally with:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npx identityapp auth link set <linking_key>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Fallback if linking is not ready yet:
|
|
127
|
+
|
|
128
|
+
- Register with `--no-link`.
|
|
129
|
+
- Then share the returned claim token with the human owner so they can claim the agent manually later.
|
|
130
|
+
|
|
131
|
+
## Integrator workflow
|
|
132
|
+
|
|
133
|
+
### 1) Set consent
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npx identityapp integrator consent allow --as <alias> --integrator survaivor
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Use `revoke` instead of `allow` to revoke. The command signs the consent payload and submits it in a single step.
|
|
140
|
+
|
|
141
|
+
### 2) Verify/certify with integrator context
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npx identityapp integrator verify <signatureHash> --api-key <integratorApiKey>
|
|
145
|
+
npx identityapp integrator certify <signatureHash> "content" --api-key <integratorApiKey>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 3) Ingest events
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
npx identityapp integrator ingest \
|
|
152
|
+
--api-key <integratorApiKey> \
|
|
153
|
+
--ingest-url https://integrator.identity.app/ingest \
|
|
154
|
+
--body-file ./event.json
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Notes:
|
|
158
|
+
- Ingest requests use `Authorization: Bearer <integratorApiKey>`.
|
|
159
|
+
- Default ingest endpoint is `https://integrator.identity.app/ingest` (override with `--ingest-url`).
|
|
160
|
+
- For `subjectType: "agent"`, ingest is deny-by-default unless consent is `allowed`.
|