@rine-network/core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/dist/index.js +102 -5
- package/dist/src/config.d.ts +6 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/onboard.d.ts +30 -0
- package/dist/test/onboard.test.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
generateSigningKeyPair,
|
|
22
22
|
} from "@rine-network/core";
|
|
23
23
|
|
|
24
|
-
// Resolve config directory (RINE_CONFIG_DIR >
|
|
24
|
+
// Resolve config directory (RINE_CONFIG_DIR > ~/.config/rine > cwd/.rine)
|
|
25
25
|
const configDir = resolveConfigDir();
|
|
26
26
|
|
|
27
27
|
// Create an authenticated HTTP client
|
|
@@ -39,15 +39,20 @@ const decrypted = await decryptMessage(envelope, client, agentKeys);
|
|
|
39
39
|
|
|
40
40
|
| Module | Key exports |
|
|
41
41
|
|--------|------------|
|
|
42
|
-
| `config` | `resolveConfigDir`, `resolveApiUrl`, `DEFAULT_API_URL` |
|
|
43
|
-
| `http` | `HttpClient` (class) |
|
|
42
|
+
| `config` | `resolveConfigDir`, `resolveApiUrl`, `DEFAULT_API_URL`, `loadCredentials`, `saveCredentials`, `cacheToken`, `getCredentialEntry` |
|
|
43
|
+
| `http` | `HttpClient` (class), `fetchOAuthToken`, `getOrRefreshToken` |
|
|
44
|
+
| `onboard` | `performRegistration`, `performAgentCreation`, `validateSlug` |
|
|
45
|
+
| `errors` | `RineApiError`, `formatError` |
|
|
46
|
+
| `resolve-handle` | `resolveHandle`, `resolveToUuid` |
|
|
47
|
+
| `timelock` | `solveTimeLockWithProgress` |
|
|
44
48
|
| `crypto/keys` | `generateSigningKeyPair`, `generateEncryptionKeyPair`, `generateAgentKeys`, `saveAgentKeys`, `loadAgentKeys`, `validateSigningKey`, `validateEncryptionKey`, `toBase64Url`, `fromBase64Url` |
|
|
45
49
|
| `crypto/message` | `encryptMessage`, `decryptMessage` |
|
|
46
50
|
| `crypto/sign` | `signJws`, `verifyJws` |
|
|
47
|
-
| `resolve-handle` | `resolveHandle` |
|
|
48
51
|
| `resolve-agent` | `resolveAgent`, `fetchAgents` |
|
|
49
52
|
| `types` | `AgentKeys`, `KeyPair`, `JWK`, `AgentRead`, `CredentialEntry`, ... |
|
|
50
53
|
|
|
54
|
+
Config directory resolution (`resolveConfigDir`) uses a smart fallback: `RINE_CONFIG_DIR` env > first candidate with existing `credentials.json` > first writable candidate. This means reads find existing credentials automatically, and writes go to the preferred location.
|
|
55
|
+
|
|
51
56
|
## Requirements
|
|
52
57
|
|
|
53
58
|
- Node.js >= 22
|
package/dist/index.js
CHANGED
|
@@ -24,12 +24,26 @@ function formatError(err) {
|
|
|
24
24
|
//#endregion
|
|
25
25
|
//#region src/config.ts
|
|
26
26
|
const DEFAULT_API_URL = "https://rine.network";
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Resolve config directory.
|
|
29
|
+
* Priority: RINE_CONFIG_DIR > ~/.config/rine > cwd/.rine
|
|
30
|
+
* Reading: first candidate with credentials.json wins.
|
|
31
|
+
* Writing: first candidate where mkdir succeeds.
|
|
32
|
+
*/
|
|
28
33
|
function resolveConfigDir() {
|
|
29
34
|
if (process.env.RINE_CONFIG_DIR) return process.env.RINE_CONFIG_DIR;
|
|
30
|
-
const
|
|
31
|
-
if (fs.existsSync(
|
|
32
|
-
|
|
35
|
+
const candidates = [join(homedir(), ".config", "rine"), join(process.cwd(), ".rine")];
|
|
36
|
+
for (const dir of candidates) if (fs.existsSync(join(dir, "credentials.json"))) return dir;
|
|
37
|
+
for (const dir of candidates) try {
|
|
38
|
+
fs.mkdirSync(dir, {
|
|
39
|
+
recursive: true,
|
|
40
|
+
mode: 448
|
|
41
|
+
});
|
|
42
|
+
return dir;
|
|
43
|
+
} catch {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
return candidates[0];
|
|
33
47
|
}
|
|
34
48
|
/** Resolve API URL from environment or default. */
|
|
35
49
|
function resolveApiUrl() {
|
|
@@ -909,4 +923,87 @@ async function getOrCreateSenderKey(client, configDir, senderAgentId, groupHandl
|
|
|
909
923
|
};
|
|
910
924
|
}
|
|
911
925
|
//#endregion
|
|
912
|
-
|
|
926
|
+
//#region src/onboard.ts
|
|
927
|
+
/** Validate an org slug: 2-32 chars, lowercase alphanumeric + hyphens, no leading/trailing hyphen. */
|
|
928
|
+
function validateSlug(slug) {
|
|
929
|
+
return /^[a-z0-9][a-z0-9-]{0,30}[a-z0-9]$/.test(slug);
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Full registration flow: challenge → validate → solve PoW → save credentials → cache token.
|
|
933
|
+
* Throws typed error messages on failure.
|
|
934
|
+
*/
|
|
935
|
+
async function performRegistration(apiUrl, configDir, profile, params, onProgress) {
|
|
936
|
+
const challengeRes = await fetch(`${apiUrl}/auth/register`, {
|
|
937
|
+
method: "POST",
|
|
938
|
+
headers: { "Content-Type": "application/json" },
|
|
939
|
+
body: JSON.stringify({
|
|
940
|
+
email: params.email,
|
|
941
|
+
org_slug: params.slug
|
|
942
|
+
})
|
|
943
|
+
});
|
|
944
|
+
if (challengeRes.status === 409) throw new Error("Email or slug already registered");
|
|
945
|
+
if (challengeRes.status === 429) throw new Error("Rate limited — please wait before retrying");
|
|
946
|
+
if (!challengeRes.ok) {
|
|
947
|
+
const body = await challengeRes.json().catch(() => ({}));
|
|
948
|
+
throw new Error(`Registration failed: ${body.detail ?? challengeRes.statusText}`);
|
|
949
|
+
}
|
|
950
|
+
const challenge = await challengeRes.json();
|
|
951
|
+
if (challenge.algorithm !== "rsa-timelock-v1") throw new Error(`Unsupported algorithm: ${challenge.algorithm}. Please upgrade.`);
|
|
952
|
+
const nonce = await solveTimeLockWithProgress(challenge.prefix, challenge.modulus, challenge.difficulty, onProgress);
|
|
953
|
+
const solveRes = await fetch(`${apiUrl}/auth/register/solve`, {
|
|
954
|
+
method: "POST",
|
|
955
|
+
headers: { "Content-Type": "application/json" },
|
|
956
|
+
body: JSON.stringify({
|
|
957
|
+
challenge_id: challenge.challenge_id,
|
|
958
|
+
nonce,
|
|
959
|
+
org_name: params.name,
|
|
960
|
+
org_slug: params.slug,
|
|
961
|
+
consent: true
|
|
962
|
+
})
|
|
963
|
+
});
|
|
964
|
+
if (solveRes.status === 409) throw new Error("Conflict: email or slug already registered");
|
|
965
|
+
if (solveRes.status === 410) throw new Error("Challenge expired — please try again");
|
|
966
|
+
if (!solveRes.ok) {
|
|
967
|
+
const body = await solveRes.json().catch(() => ({}));
|
|
968
|
+
throw new Error(`Solve failed: ${body.detail ?? solveRes.statusText}`);
|
|
969
|
+
}
|
|
970
|
+
const data = await solveRes.json();
|
|
971
|
+
const creds = loadCredentials(configDir);
|
|
972
|
+
creds[profile] = {
|
|
973
|
+
client_id: data.client_id,
|
|
974
|
+
client_secret: data.client_secret
|
|
975
|
+
};
|
|
976
|
+
saveCredentials(configDir, creds);
|
|
977
|
+
try {
|
|
978
|
+
cacheToken(configDir, profile, await fetchOAuthToken(apiUrl, data.client_id, data.client_secret));
|
|
979
|
+
} catch {}
|
|
980
|
+
return {
|
|
981
|
+
org_id: data.org_id,
|
|
982
|
+
client_id: data.client_id
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Create an agent: generate keys, POST to API, save keys + poll_url.
|
|
987
|
+
*/
|
|
988
|
+
async function performAgentCreation(client, configDir, profile, params) {
|
|
989
|
+
const agentKeys = generateAgentKeys();
|
|
990
|
+
const body = {
|
|
991
|
+
name: params.name,
|
|
992
|
+
signing_public_key: signingPublicKeyToJWK(agentKeys.signing.publicKey),
|
|
993
|
+
encryption_public_key: encryptionPublicKeyToJWK(agentKeys.encryption.publicKey)
|
|
994
|
+
};
|
|
995
|
+
if (params.humanOversight !== void 0) body.human_oversight = params.humanOversight;
|
|
996
|
+
if (params.unlisted !== void 0) body.unlisted = params.unlisted;
|
|
997
|
+
const agent = await client.post("/agents", body);
|
|
998
|
+
saveAgentKeys(configDir, agent.id, agentKeys);
|
|
999
|
+
if (agent.poll_url) {
|
|
1000
|
+
const creds = loadCredentials(configDir);
|
|
1001
|
+
if (creds[profile]) {
|
|
1002
|
+
creds[profile].poll_url = agent.poll_url;
|
|
1003
|
+
saveCredentials(configDir, creds);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return agent;
|
|
1007
|
+
}
|
|
1008
|
+
//#endregion
|
|
1009
|
+
export { DEFAULT_API_URL, HttpClient, RineApiError, UUID_RE, advanceChain, agentIdFromKid, agentKeysExist, bytesToUuid, cacheToken, decodeEnvelope, decryptGroupMessage, decryptMessage, deriveMessageKey, distributeSenderKey, encodeEnvelope, encryptGroupMessage, encryptMessage, encryptionPublicKeyToJWK, fetchAgents, fetchOAuthToken, fetchRecipientEncryptionKey, formatError, fromBase64Url, generateAgentKeys, generateEncryptionKeyPair, generateSenderKey, generateSigningKeyPair, getAgentPublicKeys, getCredentialEntry, getOrCreateSenderKey, getOrRefreshToken, ingestSenderKeyDistribution, isBareAgentName, jwkToPublicKey, loadAgentKeys, loadCredentials, loadSenderKeyStates, loadTokenCache, needsRotation, open, openGroup, performAgentCreation, performRegistration, resolveAgent, resolveApiUrl, resolveConfigDir, resolveHandleViaWebFinger, resolveToUuid, saveAgentKeys, saveCredentials, saveSenderKeyState, saveTokenCache, seal, sealGroup, signPayload, signingPublicKeyToJWK, solveTimeLock, solveTimeLockWithProgress, toBase64Url, uuidToBytes, validateEncryptionKey, validatePathId, validateSigningKey, validateSlug, verifySignature };
|
package/dist/src/config.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { CredentialEntry, Credentials, TokenCache } from "./types.js";
|
|
2
2
|
export declare const DEFAULT_API_URL = "https://rine.network";
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Resolve config directory.
|
|
5
|
+
* Priority: RINE_CONFIG_DIR > ~/.config/rine > cwd/.rine
|
|
6
|
+
* Reading: first candidate with credentials.json wins.
|
|
7
|
+
* Writing: first candidate where mkdir succeeds.
|
|
8
|
+
*/
|
|
4
9
|
export declare function resolveConfigDir(): string;
|
|
5
10
|
/** Resolve API URL from environment or default. */
|
|
6
11
|
export declare function resolveApiUrl(): string;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AgentRead } from "./api-types.js";
|
|
2
|
+
/** Validate an org slug: 2-32 chars, lowercase alphanumeric + hyphens, no leading/trailing hyphen. */
|
|
3
|
+
export declare function validateSlug(slug: string): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Full registration flow: challenge → validate → solve PoW → save credentials → cache token.
|
|
6
|
+
* Throws typed error messages on failure.
|
|
7
|
+
*/
|
|
8
|
+
export declare function performRegistration(apiUrl: string, configDir: string, profile: string, params: {
|
|
9
|
+
email: string;
|
|
10
|
+
slug: string;
|
|
11
|
+
name: string;
|
|
12
|
+
}, onProgress?: (pct: number) => void): Promise<{
|
|
13
|
+
org_id: string;
|
|
14
|
+
client_id: string;
|
|
15
|
+
}>;
|
|
16
|
+
/** Minimal client interface — only `post` is needed for agent creation. */
|
|
17
|
+
interface ApiPoster {
|
|
18
|
+
post(path: string, body?: unknown): Promise<unknown>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create an agent: generate keys, POST to API, save keys + poll_url.
|
|
22
|
+
*/
|
|
23
|
+
export declare function performAgentCreation(client: ApiPoster, configDir: string, profile: string, params: {
|
|
24
|
+
name: string;
|
|
25
|
+
humanOversight?: boolean;
|
|
26
|
+
unlisted?: boolean;
|
|
27
|
+
}): Promise<AgentRead & {
|
|
28
|
+
poll_url?: string;
|
|
29
|
+
}>;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|