@abraca/cli 1.9.1 → 2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abraca/cli",
3
- "version": "1.9.1",
3
+ "version": "2.3.0",
4
4
  "description": "CLI for Abracadabra — interact with CRDT document workspaces from the terminal",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -28,12 +28,17 @@
28
28
  "dist"
29
29
  ],
30
30
  "dependencies": {
31
- "@noble/ed25519": "^2.3.0",
32
- "@noble/hashes": "^1.8.0"
31
+ "@noble/ed25519": "^3.1.0",
32
+ "@noble/hashes": "^2.2.0",
33
+ "wtf-plugin-api": "^2.0.1",
34
+ "wtf_wikipedia": "^10.4.1"
33
35
  },
34
36
  "peerDependencies": {
35
- "@abraca/dabra": ">=1.0.0",
37
+ "@abraca/dabra": ">=2.0.0",
36
38
  "y-protocols": "^1.0.6",
37
39
  "yjs": "^13.6.8"
40
+ },
41
+ "devDependencies": {
42
+ "@abraca/dabra": "2.3.0"
38
43
  }
39
- }
44
+ }
@@ -96,27 +96,30 @@ registerCommand({
96
96
  registerCommand({
97
97
  name: 'chat',
98
98
  aliases: ['send'],
99
- description: 'Send a chat message to a channel.',
100
- usage: 'chat channel=<group:docId> text=<message>',
99
+ description: 'Send a chat message to a channel doc.',
100
+ usage: 'chat channel_doc_id=<docId> text=<message>',
101
101
  async run(conn: CLIConnection | null, args: ParsedArgs): Promise<string> {
102
102
  if (!conn) return 'Not connected'
103
103
 
104
104
  const rootProvider = conn.rootProvider
105
105
  if (!rootProvider) return 'Not connected'
106
106
 
107
- const channel = args.params['channel']
108
- if (!channel) return 'Missing required parameter: channel=<group:docId>'
107
+ // Accept either `channel_doc_id` or legacy `channel` (with optional
108
+ // `group:` prefix the new server doesn't use strip it).
109
+ const raw = args.params['channel_doc_id'] || args.params['channel']
110
+ if (!raw) return 'Missing required parameter: channel_doc_id=<docId>'
111
+ const channel_doc_id = raw.startsWith('group:') ? raw.slice(6) : raw
109
112
 
110
113
  const text = args.params['text'] || args.params['content'] || args.positional[0]
111
114
  if (!text) return 'Missing required parameter: text=<message>'
112
115
 
113
116
  rootProvider.sendStateless(JSON.stringify({
114
- type: 'chat:send',
115
- channel,
117
+ type: 'messages:send',
118
+ channel_doc_id,
116
119
  content: text,
117
- sender_name: conn.displayName,
120
+ mentions: [],
118
121
  }))
119
122
 
120
- return `Sent to ${channel}`
123
+ return `Sent to ${channel_doc_id}`
121
124
  },
122
125
  })
@@ -4,8 +4,8 @@
4
4
  import { registerCommand } from '../command.ts'
5
5
  import type { CLIConnection } from '../connection.ts'
6
6
  import type { ParsedArgs } from '../parser.ts'
7
- import { printJson, printTable, getFormat, pad } from '../output.ts'
8
- import { readEntries, childrenOf } from '../resolve.ts'
7
+ import { printJson, printTable, getFormat } from '../output.ts'
8
+ import { readEntries } from '../resolve.ts'
9
9
 
10
10
  registerCommand({
11
11
  name: 'info',
@@ -28,7 +28,7 @@ registerCommand({
28
28
  `URL: ${conn.config.url}`,
29
29
  `Version: ${si.version ?? '—'}`,
30
30
  `Protocol: ${si.protocol_version ?? '—'}`,
31
- `Hub Doc: ${conn.rootDocId ?? '—'}`,
31
+ `Active Doc: ${conn.rootDocId ?? '—'}`,
32
32
  `Auth: ${si.auth_methods?.join(', ') ?? '—'}`,
33
33
  `Registration:${si.registration_allowed ? ' open' : ' closed'}${si.invite_only ? ' (invite only)' : ''}`,
34
34
  `Documents: ${docCount}`,
@@ -40,7 +40,7 @@ registerCommand({
40
40
 
41
41
  registerCommand({
42
42
  name: 'spaces',
43
- description: 'List available spaces/root documents.',
43
+ description: 'List available spaces (top-level documents).',
44
44
  usage: 'spaces [--format=json|tsv]',
45
45
  async run(conn: CLIConnection | null, args: ParsedArgs): Promise<string> {
46
46
  if (!conn) return 'Not connected'
@@ -51,18 +51,20 @@ registerCommand({
51
51
  const active = conn.rootDocId
52
52
 
53
53
  if (format === 'json') {
54
- return printJson(spaces.map(s => ({ ...s, active: s.doc_id === active })))
54
+ return printJson(spaces.map(s => ({ ...s, active: s.id === active })))
55
55
  }
56
56
 
57
- const rows = spaces.map(s => [
58
- s.doc_id === active ? '' : ' ',
59
- s.doc_id.slice(0, 8) + '…',
60
- s.name,
61
- s.is_hub ? 'hub' : '',
62
- s.visibility,
63
- ])
57
+ const rows = spaces.map(s => {
58
+ const visibility = s.public_access === 'observer' ? 'public' : 'private'
59
+ return [
60
+ s.id === active ? '▸' : ' ',
61
+ s.id.slice(0, 8) + '',
62
+ s.label ?? s.id,
63
+ visibility,
64
+ ]
65
+ })
64
66
 
65
- return printTable(rows, ['', 'ID', 'NAME', 'HUB', 'VISIBILITY'])
67
+ return printTable(rows, ['', 'ID', 'NAME', 'VISIBILITY'])
66
68
  },
67
69
  })
68
70
 
@@ -84,9 +86,9 @@ registerCommand({
84
86
  } else if (targetName) {
85
87
  const lower = targetName.toLowerCase()
86
88
  const space = conn.spaces.find(s =>
87
- s.name.toLowerCase() === lower || s.doc_id === targetName
89
+ (s.label ?? s.id).toLowerCase() === lower || s.id === targetName
88
90
  )
89
- if (space) docId = space.doc_id
91
+ if (space) docId = space.id
90
92
  }
91
93
 
92
94
  if (!docId) {
@@ -94,7 +96,7 @@ registerCommand({
94
96
  }
95
97
 
96
98
  await conn.switchSpace(docId)
97
- const space = conn.spaces.find(s => s.doc_id === docId)
98
- return `Switched to space "${space?.name ?? docId}"`
99
+ const space = conn.spaces.find(s => s.id === docId)
100
+ return `Switched to space "${space?.label ?? docId}"`
99
101
  },
100
102
  })
@@ -51,9 +51,9 @@ registerCommand({
51
51
  return '(empty tree)'
52
52
  }
53
53
 
54
- // Find root label for header
55
- const hubSpace = conn.spaces.find(s => s.doc_id === conn.rootDocId)
56
- const rootLabel = hubSpace?.name ?? conn.rootDocId ?? 'Workspace'
54
+ // Find root label for header — the active Space, falling back to its id.
55
+ const activeSpace = conn.spaces.find(s => s.id === conn.rootDocId)
56
+ const rootLabel = activeSpace?.label ?? conn.rootDocId ?? 'Workspace'
57
57
 
58
58
  return rootLabel + '\n' + printTree(toTreeNodes(tree))
59
59
  },
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Open a DocumentManager session for the wiki command, mirroring the
3
+ * auth/register flow that CLIConnection uses but using the modern public API.
4
+ *
5
+ * Reuses the CLI's Ed25519 keypair handling (loadOrCreateKeypair, signChallenge)
6
+ * so the wiki command authenticates with the same identity as every other
7
+ * subcommand.
8
+ */
9
+ import { DocumentManager } from '@abraca/dabra'
10
+ import { loadOrCreateKeypair, signChallenge } from '../../crypto.ts'
11
+
12
+ export interface OpenSessionConfig {
13
+ url: string
14
+ name?: string
15
+ color?: string
16
+ inviteCode?: string
17
+ keyFile?: string
18
+ /** Suppress informational stderr logging. */
19
+ quiet?: boolean
20
+ }
21
+
22
+ export interface OpenSessionResult {
23
+ dm: DocumentManager
24
+ /** Active root doc id (the entry-point space). */
25
+ rootDocId: string
26
+ }
27
+
28
+ export async function openSession(config: OpenSessionConfig): Promise<OpenSessionResult> {
29
+ const keypair = await loadOrCreateKeypair(config.keyFile)
30
+ const sign = (challenge: string) => Promise.resolve(signChallenge(challenge, keypair.privateKey))
31
+
32
+ const dm = new DocumentManager({
33
+ url: config.url,
34
+ name: config.name ?? 'Wiki Extractor',
35
+ color: config.color,
36
+ quiet: config.quiet,
37
+ })
38
+
39
+ // Authenticate first; register on first run.
40
+ try {
41
+ await dm.client.loginWithKey(keypair.publicKeyB64, sign)
42
+ } catch (err: any) {
43
+ const status = err?.status ?? err?.response?.status
44
+ if (status === 404 || status === 422) {
45
+ if (!config.quiet) {
46
+ console.error('[abracadabra] Key not registered, creating new account...')
47
+ }
48
+ await dm.client.registerWithKey({
49
+ publicKey: keypair.publicKeyB64,
50
+ username: (config.name ?? 'wiki-extractor').replace(/\s+/g, '-').toLowerCase(),
51
+ displayName: config.name ?? 'Wiki Extractor',
52
+ deviceName: 'CLI Wiki',
53
+ inviteCode: config.inviteCode,
54
+ })
55
+ await dm.client.loginWithKey(keypair.publicKeyB64, sign)
56
+ } else {
57
+ throw err
58
+ }
59
+ }
60
+
61
+ await dm.connect()
62
+
63
+ const rootDocId = dm.rootDocId
64
+ if (!rootDocId) {
65
+ throw new Error('Connected but no rootDocId — server has no spaces.')
66
+ }
67
+
68
+ return { dm, rootDocId }
69
+ }