@inixiative/archive 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -76,7 +76,9 @@ First, pair this Archive with Kingdom:
76
76
  bun run archive pair --kingdom https://api.your-kingdom.example --name "Work laptop"
77
77
  ```
78
78
 
79
- `pair` creates a device key in `<home>/kingdom/` and asks Kingdom to pair this Archive, identified by its `sourceId`. It prints a review code and a link. Open the link, choose the owner and the hosted Archives this machine may write to, and approve. The command waits for the approval, then saves the delivered Signet credential (0600) and lists the libraries it may write to.
79
+ `pair` registers this Archive with Kingdom as an **Installation**, named by a key kept in `<home>/kingdom/<kingdom host>/`. The same key is reused for every owner it registers with. It then asks to be registered as an integration and prints a review code with a link.
80
+
81
+ Open the link, choose the owner and the hosted Archives this machine may write to, and approve. The command waits for approval, then shows which owner it was registered with and asks you to accept. Pass `--yes` to accept without the prompt; without a terminal, `--yes` is required. Only then does it collect the Signet (0600) and list the libraries it may write to. Confirming the owner protects you if someone else claimed your code into their own owner.
80
82
 
81
83
  Next, route a project to one of those libraries:
82
84
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inixiative/archive",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts",
@@ -35,7 +35,7 @@
35
35
  "prepare": "if git rev-parse --git-dir >/dev/null 2>&1; then lefthook install; fi && prisma generate"
36
36
  },
37
37
  "dependencies": {
38
- "@inixiative/signet": "^0.1.0",
38
+ "@inixiative/signet": "^0.3.0",
39
39
  "@prisma/client": "^6.6.0",
40
40
  "js-tiktoken": "1.0.21",
41
41
  "prisma": "^6.6.0",
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "@biomejs/biome": "2.5.2",
46
- "@inixiative/config": "^0.5.53",
46
+ "@inixiative/config": "^0.5.62",
47
47
  "@types/bun": "1.4.2",
48
48
  "lefthook": "2.1.9",
49
49
  "typescript": "6.0.3"
package/src/cli.ts CHANGED
@@ -71,6 +71,7 @@ export async function runCli(args = Bun.argv.slice(2)) {
71
71
  watch: { type: 'boolean', default: false },
72
72
  sync: { type: 'boolean', default: false },
73
73
  remote: { type: 'boolean', default: false },
74
+ yes: { type: 'boolean', default: false },
74
75
  help: { type: 'boolean' },
75
76
  },
76
77
  });
@@ -87,7 +88,7 @@ export async function runCli(args = Bun.argv.slice(2)) {
87
88
  'import --file PATH --source codex|claude-code|chatgpt --project-id ID [--tag TAG]\n' +
88
89
  'collect --directory HISTORY --source codex|claude-code --project-root EXACT_CWD [--project-root ...] [--worktrees] [--atlas] --project-id ID [--watch]\n' +
89
90
  'list | export --id ID | tag --id ID [--tag TAG ...] [--untag TAG ...] | search --query TEXT [--remote]\n' +
90
- 'pair --kingdom KINGDOM_API_URL [--name NAME] (pair this Archive with Kingdom; approve the review code there)\n' +
91
+ 'pair --kingdom KINGDOM_API_URL [--name NAME] [--yes] (register this Archive with Kingdom; claim the review code there, then confirm the owner)\n' +
91
92
  'connect --url HTTPS_URL --project-id ID (--token-env ENV | --token-file PATH) (a remote Archive, for serve --sync)\n' +
92
93
  'connect --kind kingdom --url KINGDOM_API_URL --credential-file PATH --integration-id UUID --resource-id UUID --project-id ID (a hosted Archive through Kingdom)\n' +
93
94
  'sync | routes (DATABASE_URL: publish to, or preview, destinations once)\n' +
@@ -191,6 +192,15 @@ export async function runCli(args = Bun.argv.slice(2)) {
191
192
  sourceId: await local.sourceId(),
192
193
  directory: join(home, 'kingdom'),
193
194
  onReview: (review) => console.error(JSON.stringify(review)),
195
+ onError: (error) =>
196
+ console.error(`Still waiting: ${error instanceof Error ? error.message : String(error)}`),
197
+ confirmOwner: async ({ ownerName }) => {
198
+ if (v.yes) return true;
199
+ if (!process.stdin.isTTY)
200
+ throw new Error(`Registered with ${ownerName}; rerun with --yes to accept it`);
201
+ const answer = prompt(`Kingdom registered this Archive with ${ownerName}. Accept? [y/N]`);
202
+ return /^y(es)?$/i.test(answer?.trim() ?? '');
203
+ },
194
204
  }),
195
205
  );
196
206
  return;
package/src/kingdom.ts CHANGED
@@ -1,18 +1,6 @@
1
- import { rm } from 'node:fs/promises';
2
- import { join } from 'node:path';
3
- import {
4
- collectPairing,
5
- generateSignetKey,
6
- kingdomUrl,
7
- requestPairing,
8
- SignetClient,
9
- saveCollectedSignet,
10
- writePrivateJson,
11
- } from '@inixiative/signet';
1
+ import { pairInstallation, SignetClient } from '@inixiative/signet';
12
2
  import type { ArchiveDestination } from './config';
13
3
 
14
- const pollMs = 3000;
15
-
16
4
  /** Libraries this Archive's Signet may write to, as Kingdom describes them. */
17
5
  export async function writableLibraries(credentialFile: string) {
18
6
  const description = await (await SignetClient.fromFile(credentialFile)).describe();
@@ -27,65 +15,49 @@ export async function writableLibraries(credentialFile: string) {
27
15
  );
28
16
  }
29
17
 
30
- /**
31
- * Pairs this Archive with Kingdom as a local Archive integration bound to its sourceId. The owner
32
- * approves the review code in Kingdom and chooses which hosted Archives it may write to.
33
- */
34
- export async function pairWithKingdom(input: {
18
+ type Pairing = {
35
19
  kingdom: string;
36
20
  name: string;
37
21
  sourceId: string;
38
22
  directory: string;
39
23
  onReview: (review: { reviewCode: string; review: string; expiresAt: string }) => void;
40
- sleep?: (ms: number) => Promise<unknown>;
41
- }) {
42
- const kingdom = kingdomUrl(input.kingdom);
43
- const keyFile = join(input.directory, `key-${crypto.randomUUID()}.json`);
44
- await writePrivateJson(keyFile, generateSignetKey());
45
- try {
46
- return await awaitPairing(kingdom, keyFile, input);
47
- } catch (error) {
48
- await rm(keyFile, { force: true });
49
- throw error;
50
- }
51
- }
24
+ confirmOwner: (owner: { ownerName: string | null; owner: unknown }) => Promise<boolean>;
25
+ onError?: (error: unknown) => void;
26
+ socketOptions?: { pollMs?: number; retryBaseMs?: number; authTimeoutMs?: number };
27
+ };
52
28
 
53
- async function awaitPairing(
54
- kingdom: string,
55
- keyFile: string,
56
- input: Parameters<typeof pairWithKingdom>[0],
57
- ) {
58
- const pending = await requestPairing(kingdom, keyFile, {
59
- provider: 'archive',
60
- deviceId: input.sourceId,
29
+ /**
30
+ * Pairs this Archive with Kingdom as an Installation: a person claims the review code, the Archive
31
+ * confirms the owner locally, then collects the Signet for its local Archive integration.
32
+ */
33
+ export async function pairWithKingdom(input: Pairing) {
34
+ const paired = await pairInstallation({
35
+ url: input.kingdom,
36
+ root: input.directory,
37
+ kind: 'archive',
61
38
  name: input.name,
62
- resources: [],
63
- expiresAt: null,
64
- maxRequests: null,
65
- maxConcurrent: 4,
66
- });
67
- input.onReview({
68
- reviewCode: pending.reviewCode,
69
- review: `${kingdom}/dashboard?reviewSignet=${pending.reviewCode}`,
70
- expiresAt: pending.expiresAt,
39
+ sourceId: input.sourceId,
40
+ terms: {
41
+ name: input.name,
42
+ lifecycle: 'ongoing',
43
+ resources: [],
44
+ expiresAt: null,
45
+ maxRequests: null,
46
+ maxConcurrent: 4,
47
+ },
48
+ onReview: input.onReview,
49
+ confirmOwner: input.confirmOwner,
50
+ onError: input.onError,
51
+ socketOptions: input.socketOptions,
71
52
  });
72
- const sleep = input.sleep ?? Bun.sleep;
73
- while (Date.parse(pending.expiresAt) > Date.now()) {
74
- const collected = await collectPairing(kingdom, keyFile, pending.deviceCode);
75
- if (collected) {
76
- const credentialFile = join(input.directory, `signet-${collected.signetId}.json`);
77
- await saveCollectedSignet(credentialFile, kingdom, keyFile, collected);
78
- return {
79
- paired: true,
80
- integrationId: collected.integrationId,
81
- credentialFile,
82
- libraries: await writableLibraries(credentialFile),
83
- next: 'archive connect --kind kingdom --url KINGDOM --credential-file FILE --integration-id ID --resource-id ID --project-id PROJECT',
84
- };
85
- }
86
- await sleep(pollMs);
87
- }
88
- throw new Error('The review code expired before it was approved; run archive pair again');
53
+ return {
54
+ paired: true,
55
+ owner: paired.ownerName,
56
+ integrationId: paired.integrationId,
57
+ credentialFile: paired.credentialFile,
58
+ libraries: await writableLibraries(paired.credentialFile),
59
+ next: 'archive connect --kind kingdom --url KINGDOM --credential-file FILE --integration-id ID --resource-id ID --project-id PROJECT',
60
+ };
89
61
  }
90
62
 
91
63
  export async function verifyKingdomDestination(