@7ots/cli 0.1.5 → 0.1.7

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.
Files changed (51) hide show
  1. package/README.md +15 -21
  2. package/cli/7ots.mjs +55 -5
  3. package/cli/lib/account.mjs +89 -0
  4. package/cli/lib/assistant.mjs +126 -16
  5. package/cli/lib/brain.mjs +41 -11
  6. package/cli/lib/card-shot.mjs +84 -0
  7. package/cli/lib/config.mjs +29 -5
  8. package/cli/lib/i18n.mjs +57 -0
  9. package/cli/lib/inferences.mjs +76 -0
  10. package/cli/lib/integrations.mjs +414 -0
  11. package/cli/lib/orquesta.mjs +53 -0
  12. package/cli/lib/pet-core.mjs +7 -10
  13. package/cli/lib/pet-server.mjs +373 -22
  14. package/cli/lib/prompts.mjs +31 -17
  15. package/cli/lib/prowl.mjs +110 -0
  16. package/cli/lib/screen.mjs +125 -0
  17. package/cli/lib/stt-local.py +14 -0
  18. package/cli/lib/stt.mjs +68 -0
  19. package/cli/lib/tts-local.mjs +110 -0
  20. package/cli/lib/tts-local.py +18 -0
  21. package/cli/lib/vm.mjs +309 -0
  22. package/cli/lib/watchers.mjs +151 -0
  23. package/cli/pet/card.html +294 -0
  24. package/cli/pet/electron/director.cjs +175 -0
  25. package/cli/pet/electron/main.cjs +354 -49
  26. package/cli/pet/pet.html +328 -15
  27. package/cli/pet/settings.html +995 -58
  28. package/dist/7ots.esm.js +56 -56
  29. package/dist/7ots.esm.js.map +3 -3
  30. package/dist/7ots.iife.js +56 -56
  31. package/dist/7ots.iife.js.map +3 -3
  32. package/llms.txt +31 -17
  33. package/package.json +17 -14
  34. package/server/llm.mjs +2 -0
  35. package/server/platform/connect.mjs +3 -1
  36. package/server/platform/db.mjs +82 -0
  37. package/server/platform/device.mjs +125 -0
  38. package/server/platform/orquesta.mjs +5 -0
  39. package/server/platform/routes.mjs +101 -1
  40. package/server/platform/runtime.mjs +2 -2
  41. package/server/platform/sites.mjs +146 -0
  42. package/server/platform/store.mjs +3 -1
  43. package/server/platform/vm.mjs +778 -0
  44. package/server/tts.mjs +17 -4
  45. package/src/character/Character.js +1 -1
  46. package/src/character/parts.js +10 -0
  47. package/src/character3d/Character3D.js +105 -2
  48. package/src/character3d/index.js +2 -1
  49. package/src/character3d/stage.js +15 -4
  50. package/src/i18n/messages/character.js +6 -3
  51. package/src/i18n/messages/platform.js +60 -3
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Sites, APIs and MCP servers an account enrolls so ots know how to use them.
3
+ *
4
+ * The directory is Prowl.world: 7ots registers the entry there (source '7ots') as one Prowl
5
+ * vendor, starts the domain claim and hands the owner Prowl's three proofs (DNS TXT,
6
+ * /.well-known/prowl-verify.txt or a <meta>). Once verified, Prowl serves the owner's usage
7
+ * guide at GET /v1/context/{domain}, which is what desktop ots read.
8
+ *
9
+ * From the dashboard (session cookie, X-7ots-Admin: 1, same origin; see routes.mjs):
10
+ * GET /api/platform/sites → { sites, enabled }
11
+ * POST /api/platform/sites { kind, url, name, guide } → { site }
12
+ * POST /api/platform/sites/:id/verify → { site } (422 while the proof isn't there)
13
+ * DELETE /api/platform/sites/:id
14
+ *
15
+ * Env: PROWL_API (default https://prowl.world), PROWL_EMAIL + PROWL_PASSWORD (the 7ots vendor).
16
+ */
17
+
18
+ import { getDb } from './db.mjs';
19
+ import { newId } from './crypto.mjs';
20
+
21
+ const KINDS = ['site', 'api', 'mcp'];
22
+ const base = () => (process.env.PROWL_API || 'https://prowl.world').replace(/\/+$/, '');
23
+ export const sitesEnabled = () => !!(process.env.PROWL_EMAIL && process.env.PROWL_PASSWORD);
24
+
25
+ let access = null;
26
+ async function prowl(path, { method = 'GET', body, retry = true } = {}) {
27
+ if (!access) {
28
+ const r = await fetch(`${base()}/v1/auth/login`, {
29
+ method: 'POST',
30
+ headers: { 'content-type': 'application/json' },
31
+ body: JSON.stringify({ email: process.env.PROWL_EMAIL, password: process.env.PROWL_PASSWORD }),
32
+ signal: AbortSignal.timeout(15_000),
33
+ });
34
+ if (!r.ok) throw Object.assign(new Error(`prowl login ${r.status}`), { status: 502 });
35
+ access = (await r.json()).access_token;
36
+ }
37
+ const r = await fetch(`${base()}${path}`, {
38
+ method,
39
+ headers: { authorization: `Bearer ${access}`, ...(body ? { 'content-type': 'application/json' } : {}) },
40
+ body: body ? JSON.stringify(body) : undefined,
41
+ signal: AbortSignal.timeout(30_000),
42
+ });
43
+ if (r.status === 401 && retry) {
44
+ access = null;
45
+ return prowl(path, { method, body, retry: false });
46
+ }
47
+ const data = await r.json().catch(() => ({}));
48
+ return { status: r.status, data };
49
+ }
50
+
51
+ const row = (r) =>
52
+ r && {
53
+ id: r.id,
54
+ kind: r.kind,
55
+ url: r.url,
56
+ domain: r.domain,
57
+ name: r.name,
58
+ guide: r.guide,
59
+ status: r.status,
60
+ proof: JSON.parse(r.proof || 'null'),
61
+ createdAt: r.created_at,
62
+ verifiedAt: r.verified_at,
63
+ };
64
+
65
+ export function listSites(accountId) {
66
+ return getDb().prepare('SELECT * FROM sites WHERE account_id = ? ORDER BY created_at DESC').all(accountId).map(row);
67
+ }
68
+
69
+ function cleanUrl(raw) {
70
+ let u;
71
+ try {
72
+ u = new URL(/^https?:\/\//i.test(raw) ? raw : `https://${raw}`);
73
+ } catch {
74
+ return null;
75
+ }
76
+ if (!/^https?:$/.test(u.protocol) || !u.hostname.includes('.')) return null;
77
+ return u;
78
+ }
79
+
80
+ export function createSites({ send, readJson }) {
81
+ const fail = (res, e) => send(res, e.status || 502, { error: e.message || 'Prowl error' });
82
+
83
+ return async function sites(req, res, account, path) {
84
+ if (!path.startsWith('/sites')) return false;
85
+ const d = getDb();
86
+ if (path === '/sites' && req.method === 'GET') return send(res, 200, { sites: listSites(account.id), enabled: sitesEnabled() }), true;
87
+ if (!sitesEnabled()) return send(res, 503, { error: 'Site enrollment is not configured on this server (PROWL_EMAIL/PROWL_PASSWORD).' }), true;
88
+
89
+ if (path === '/sites' && req.method === 'POST') {
90
+ const { kind, url, name, guide } = await readJson(req, 32 * 1024);
91
+ const u = cleanUrl(String(url || '').trim());
92
+ if (!u) return send(res, 400, { error: 'Invalid URL' }), true;
93
+ const k = KINDS.includes(kind) ? kind : 'site';
94
+ const domain = u.hostname.replace(/^www\./, '').toLowerCase();
95
+ if (d.prepare('SELECT 1 FROM sites WHERE account_id = ? AND domain = ?').get(account.id, domain)) return send(res, 409, { error: `${domain} is already registered` }), true;
96
+ const title = String(name || '').trim().slice(0, 120) || domain;
97
+ const text = String(guide || '').trim().slice(0, 8000);
98
+ try {
99
+ const reg = await prowl('/v1/register', {
100
+ method: 'POST',
101
+ body: {
102
+ name: title,
103
+ website_url: `https://${domain}`,
104
+ category: [k === 'mcp' ? 'mcp' : k === 'api' ? 'api' : 'website'],
105
+ kind: k,
106
+ usage_guide: text || undefined,
107
+ source: '7ots',
108
+ ...(k === 'mcp' ? { mcp_manifest_url: u.href } : k === 'api' && u.pathname !== '/' ? { api_docs_url: u.href } : {}),
109
+ },
110
+ });
111
+ const serviceId = reg.status === 201 ? reg.data.id : reg.status === 409 ? reg.data.detail?.service_id : null;
112
+ if (!serviceId) throw Object.assign(new Error(reg.data.detail?.message || reg.data.detail || `Prowl register ${reg.status}`), { status: 502 });
113
+ const claim = await prowl('/v1/claim', { method: 'POST', body: { service_id: serviceId } });
114
+ if (claim.status !== 200) throw Object.assign(new Error(claim.data.detail || `Prowl claim ${claim.status}`), { status: claim.status === 409 ? 409 : 502 });
115
+ const c = claim.data;
116
+ const proof = { dnsRecord: c.dns_record, dnsValue: c.dns_value, wellKnownUrl: c.well_known_url, wellKnownContent: c.well_known_content, metaTag: c.meta_tag };
117
+ const id = newId('site');
118
+ d.prepare('INSERT INTO sites (id, account_id, kind, url, domain, name, guide, service_id, status, proof, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)')
119
+ .run(id, account.id, k, u.href, domain, title, text, serviceId, 'pending', JSON.stringify(proof), Date.now());
120
+ return send(res, 201, { site: row(d.prepare('SELECT * FROM sites WHERE id = ?').get(id)) }), true;
121
+ } catch (e) {
122
+ return fail(res, e), true;
123
+ }
124
+ }
125
+
126
+ const m = path.match(/^\/sites\/(site_[A-Za-z0-9_-]{6,60})(\/verify)?$/);
127
+ const s = m && d.prepare('SELECT * FROM sites WHERE id = ? AND account_id = ?').get(m[1], account.id);
128
+ if (!s) return send(res, 404, { error: 'Not found' }), true;
129
+ if (m[2] && req.method === 'POST') {
130
+ try {
131
+ const v = await prowl('/v1/claim/verify', { method: 'POST', body: { service_id: s.service_id } });
132
+ if (v.status === 422) return send(res, 422, { error: 'proof_missing', site: row(s) }), true;
133
+ if (v.status !== 200) throw Object.assign(new Error(v.data.detail || `Prowl verify ${v.status}`), { status: 502 });
134
+ d.prepare("UPDATE sites SET status = 'verified', verified_at = ? WHERE id = ?").run(Date.now(), s.id);
135
+ return send(res, 200, { site: row(d.prepare('SELECT * FROM sites WHERE id = ?').get(s.id)) }), true;
136
+ } catch (e) {
137
+ return fail(res, e), true;
138
+ }
139
+ }
140
+ if (!m[2] && req.method === 'DELETE') {
141
+ d.prepare('DELETE FROM sites WHERE id = ?').run(s.id);
142
+ return send(res, 200, { ok: true }), true;
143
+ }
144
+ return send(res, 405, { error: 'Method not allowed' }), true;
145
+ };
146
+ }
@@ -54,6 +54,8 @@ export const OTS_KEYS = {
54
54
  // Read on every message, so changing them never restarts the channels.
55
55
  ORQUESTA_PROJECT: { managed: true }, ORQUESTA_PROJECT_NAME: { managed: true }, ORQUESTA_TASKS: { managed: true },
56
56
  ORQUESTA_ALLOW: { managed: true }, ORQUESTA_DAILY: { managed: true, max: 200 },
57
+ // Cloud VM (vm.mjs): which job kinds the desktop may send to this ot's worker (csv; default research,browse,long).
58
+ VM_JOB_KINDS: {},
57
59
  };
58
60
 
59
61
  // ───────────────────────────── cuentas ─────────────────────────────
@@ -86,7 +88,7 @@ export function upsertAccount({ email = null, notloginSub = null, name = '' }) {
86
88
  }
87
89
 
88
90
  export function publicAccount(acc) {
89
- return { id: acc.id, email: acc.email, name: acc.name, plan: acc.plan, notlogin: !!acc.notlogin_sub, createdAt: acc.created_at };
91
+ return { id: acc.id, email: acc.email, name: acc.name, plan: acc.plan, notlogin: !!acc.notlogin_sub, vmEnabled: acc.vm_enabled === 1, createdAt: acc.created_at };
90
92
  }
91
93
 
92
94
  // ───────────────────────────── sesiones ─────────────────────────────