clavarium-tether 0.1.0-next.13 → 0.1.0-next.15
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/host/connectors/contacts.mjs +10 -2
- package/host/oauth.mjs +16 -10
- package/package.json +1 -1
- package/server/name.mjs +1 -1
|
@@ -65,10 +65,18 @@ export function contacts({ fetch: fetchImpl = globalThis.fetch } = {}) {
|
|
|
65
65
|
|
|
66
66
|
async function googleList(target, ctx) {
|
|
67
67
|
const base = CARDDAV(userOf(target));
|
|
68
|
+
const basePath = (() => { try { return new URL(base).pathname.replace(/\/+$/, ''); } catch { return ''; } })();
|
|
68
69
|
const listed = await req(target, ctx, 'PROPFIND', base, { headers: { Depth: '1', 'Content-Type': 'application/xml; charset=utf-8' }, body: PROPFIND });
|
|
69
70
|
if (!listed.ok) no(`${target.title}: the address book could not be read (${listed.status})`);
|
|
70
|
-
/* The collection lists itself and its contacts; keep the
|
|
71
|
-
|
|
71
|
+
/* The collection lists itself and its contacts; keep the entries, whatever
|
|
72
|
+
they are named (Google uses an id, not a .vcf suffix). A contact is any
|
|
73
|
+
href under the collection that is not the collection itself. */
|
|
74
|
+
const hrefs = hrefsIn(await listed.text()).filter((h) => {
|
|
75
|
+
const clean = h.replace(/[?#].*$/, '');
|
|
76
|
+
if (!clean || clean.endsWith('/')) return false;
|
|
77
|
+
let p; try { p = new URL(h, base).pathname.replace(/\/+$/, ''); } catch { p = clean.replace(/\/+$/, ''); }
|
|
78
|
+
return p !== basePath;
|
|
79
|
+
}).slice(0, LIST_MAX);
|
|
72
80
|
if (!hrefs.length) return [];
|
|
73
81
|
const body = `<?xml version="1.0" encoding="utf-8"?><C:addressbook-multiget xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav"><D:prop><D:getetag/><C:address-data/></D:prop>${hrefs.map((h) => `<D:href>${h.replace(/&/g, '&').replace(/</g, '<')}</D:href>`).join('')}</C:addressbook-multiget>`;
|
|
74
82
|
const res = await req(target, ctx, 'REPORT', base, { headers: { Depth: '1', 'Content-Type': 'application/xml; charset=utf-8' }, body });
|
package/host/oauth.mjs
CHANGED
|
@@ -35,11 +35,13 @@ const b64url = (buf) => Buffer.from(buf).toString('base64url');
|
|
|
35
35
|
field, kept by that name for its callers). WHICH client matters: a client is
|
|
36
36
|
only granted the scopes its app was verified for. For GOOGLE we borrow GNOME
|
|
37
37
|
Online Accounts' client, verified for mail AND calendar AND contacts, so one
|
|
38
|
-
consent gives all three. For MICROSOFT we use Thunderbird's client
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
consent gives all three. For MICROSOFT we use Thunderbird's own client; both
|
|
39
|
+
are multitenant public clients that take any delegated scope the person
|
|
40
|
+
consents to, and both registered the loopback 127.0.0.1 redirect (any port), so
|
|
41
|
+
mail, calendar and contacts go over Graph on the same copy-pastable address.
|
|
42
|
+
`consentExtra` is what a provider needs on the consent URL to return a refresh
|
|
43
|
+
token; `offlineScope` is the same thing when it is a scope rather than a
|
|
44
|
+
parameter (Microsoft). */
|
|
43
45
|
export const PROVIDERS = {
|
|
44
46
|
google: {
|
|
45
47
|
label: 'Google',
|
|
@@ -68,17 +70,21 @@ export const PROVIDERS = {
|
|
|
68
70
|
pkce: true,
|
|
69
71
|
consentExtra: {},
|
|
70
72
|
offlineScope: 'offline_access',
|
|
71
|
-
/* Microsoft over Graph:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
/* Microsoft over Graph: mail, calendar and contacts all go through
|
|
74
|
+
graph.microsoft.com (the mail connector uses its Graph path for
|
|
75
|
+
Microsoft). mail.send is needed to send. Thunderbird's public client
|
|
76
|
+
takes these delegated scopes by dynamic consent. */
|
|
75
77
|
scopes: {
|
|
76
78
|
mail: 'mail.readwrite mail.send',
|
|
77
79
|
calendar: 'calendars.readwrite',
|
|
78
80
|
contacts: 'contacts.readwrite',
|
|
79
81
|
},
|
|
82
|
+
/* Thunderbird's built-in Microsoft client (open source, public, no secret;
|
|
83
|
+
comm-central OAuth2Providers.sys.mjs). It registered the loopback
|
|
84
|
+
127.0.0.1 redirect on ANY port, so we reuse the same http://127.0.0.1
|
|
85
|
+
address as Google. */
|
|
80
86
|
thunderbird: {
|
|
81
|
-
clientId: '
|
|
87
|
+
clientId: '9e5f94bc-e8a4-4e73-b8be-63364c29d753',
|
|
82
88
|
clientSecret: null,
|
|
83
89
|
},
|
|
84
90
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clavarium-tether",
|
|
3
|
-
"version": "0.1.0-next.
|
|
3
|
+
"version": "0.1.0-next.15",
|
|
4
4
|
"description": "Clavarium Tether: an AI agent acts outside its own machine on a tether: levels per target, a person's approval, a journal and a stop (a command and an MCP server for agents, a keeper and a screen for the application). One dependency: ssh2, for the SSH connector.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/clavarium/clavarium-tether#readme",
|
package/server/name.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
export const NAME = 'tether';
|
|
7
7
|
export const PRODUCT = 'Clavarium Tether';
|
|
8
8
|
export const PACKAGE = `clavarium-${NAME}`;
|
|
9
|
-
export const VERSION = '0.1.0-next.
|
|
9
|
+
export const VERSION = '0.1.0-next.15';
|
|
10
10
|
/* The agent's folder: where its asks and the keeper's answers are written. */
|
|
11
11
|
export const FOLDER = `.${NAME}`;
|
|
12
12
|
/* $TETHER_DIR: the agent's folder; $TETHER_STORE: the keeper's store (serve). */
|