@lampdotdev/lamp 0.36.2-nightly.19 → 0.36.2-nightly.21

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
@@ -33,7 +33,7 @@ Mac app, `projectOf`), or any folder with an `index.html`; without one, the fold
33
33
  | `lamp unshare` | Takes the link down. |
34
34
  | `lamp account [username] [--email address]` | The maker's account on lamp.dev, which their links go under (`lamp.dev/<username>/<app>`): with a username and an email it asks the site for one (`askAccount`), whose key lamp keeps with its state (`account.json`, by site) and sends with every share (`X-Lamp-Account`); the link in the email the site sends confirms it. With only an email that has an account, it signs in to it, with a key of its own. On its own, it says which it's signed in as. A share before the email's link is opened goes out as links always have (`/w/<id>-<name>`), and moves under the username at its next share. |
35
35
  | `lamp play <link> [--to folder] [--newest]` | Plays an app shared on lamp.dev on this computer: the link's file (`latestOf`, its `.lampapp`), unpacked as `lamp unpack` does into a folder of its own in `~/Lamp Games` (or `--to`), with Lamp's own files beside its page, then opened as `lamp open` opens any folder. Its AI is answered by `lamp dev` (`ai.mjs`) in any browser, Safari included, where a shared page can't reach the computer. lamp remembers which folder each link went into (a file for each link under `played/` in its state), so playing it again goes on where it was; `--newest` takes the link's latest version into a new folder. |
36
- | `lamp ai [--forget]` | Answers the AI of pages shared on lamp.dev, played in a browser on this computer, with the person's own Claude Code, Codex or OpenRouter key (`cli/local-ai.mjs`, the same answers as `ai.mjs` gives `lamp dev`), until it's stopped. It listens on this computer alone at a port lamp.js knows (`LOCAL_AI_PORT`, `NEARBY` in lamp.js), and answers only a link's own page (`<id in hex>.play.lamp.dev`, `playOrigin`): the first time a page uses the AI, the person is asked in the terminal, and the page keeps the token it's given. Whether there's an AI is answered without asking. The browser asks the person too, once, before a page may reach this computer, so the link's frame on lamp.dev is given that permission. Safari never lets the page reach it (Safari 27, checked against a stand-in on the port), so lamp.js tells someone playing in Safari to use Chrome, Lamp or `lamp play` instead. `--forget` takes back every page allowed. Lamp's app runs the same server while it's open (`sharedPages` in `calls.mjs`, `SharedPagesAI`): it asks the person in a dialog and answers with the AI they set up in Lamp, and the pages allowed are the same list; whichever of the two starts first answers. |
36
+ | `lamp ai [--forget]` | Answers the AI of pages shared on lamp.dev, played in a browser on this computer, with the person's own Claude Code, Codex or OpenRouter key (`cli/local-ai.mjs`, the same answers as `ai.mjs` gives `lamp dev`), until it's stopped. It listens on this computer alone at a port lamp.js knows (`LOCAL_AI_PORT`, `NEARBY` in lamp.js), and answers only a link's own page (`<id in hex>.play.lamp.dev`, `playOrigin`): the first time a page uses the AI, the person is asked in the terminal, and the page keeps the token it's given. Whether there's an AI is answered without asking. The browser asks the person too, once, before a page may reach this computer, so the link's frame on lamp.dev is given that permission; lamp.js reaches the port only once that's granted, the page has a token, or the person asks (`LampAI.connect()` from a button, or a real AI call), so the browser doesn't ask as the page loads. Safari never lets the page reach it (Safari 27, checked against a stand-in on the port), so lamp.js tells someone playing in Safari to use Chrome, Lamp or `lamp play` instead. `--forget` takes back every page allowed. Lamp's app runs the same server while it's open (`sharedPages` in `calls.mjs`, `SharedPagesAI`): it asks the person in a dialog and answers with the AI they set up in Lamp, and the pages allowed are the same list; whichever of the two starts first answers. |
37
37
  | `lamp devices [add \| forget <name>]` | The person's devices lamp puts a page on: an Xbox in Developer Mode (those set up and those found on the network), and Android (lamp's emulators, and the phones, tablets and TVs plugged in with USB debugging on). `add` sets one up, a step at a time; `add android` downloads Android's tools. |
38
38
  | `lamp open --on xbox` | Packages the page, installs it on the device and opens it there. `lamp look --on xbox` is the device's screen and the app's log there, `lamp stop --on xbox` ends it, and `lamp remove --on xbox` takes it off. |
39
39
  | `lamp open --on android [--hidden]` | Builds Lamp's Android app around the page and opens it in lamp's Android emulator (`--on android-tv`: its TV emulator; a device of the person's by its name), with `look`, `stop` and `remove` as on any device, and `lamp use --on android` to tap, type and press there. `--hidden` starts the emulator with no window. |
@@ -20,7 +20,10 @@ opening comment for the whole list. What it gives:
20
20
  on it, check `LampAI.status('text')` and, when it can't answer, show its `reason` as it's given: lamp.js words it
21
21
  for where the page is (away from the Mac's Wi‑Fi, Lamp closed, a link on lamp.dev), so don't write your own. Where it
22
22
  gives `help` (a page away from Lamp), add a link to it beside the reason, "How to turn on AI", so someone who has never
23
- heard of Lamp finds out how.
23
+ heard of Lamp finds out how. Where it answers `connect: true` (a page on lamp.dev that hasn't reached this computer
24
+ yet), also show a small "Connect AI" button that calls `LampAI.connect(capability)` with the same kind of AI you asked
25
+ `status` about (`LampAI.connect('pictures')` for pictures) and use the answer it gives as `status`'s;
26
+ never call `connect()` as the page loads, since it makes the browser ask the person.
24
27
  - **LampAI.act**: the person asks the app to do something in their own words, and the app's own reads and writes do
25
28
  it; "Doing things for the person" below, with **LampVoice** and **LampAI.hear** for saying it.
26
29
  - **LampStore**: what the app saves, as JSON on the device. Once something is saved, only ever add to it: a new field
@@ -254,6 +254,26 @@
254
254
  var ua = (window.navigator && navigator.userAgent) || '';
255
255
  return /Safari\//.test(ua) && !/Chrome|Chromium|CriOS|Edg|OPR|Firefox|FxiOS/.test(ua);
256
256
  }
257
+ var SAFARI_AI = 'AI here works in Chrome or in Lamp, or with Lamp\'s command line: lamp play and this page\'s link.';
258
+
259
+ // Reaching this computer makes the browser ask the person about it, so a shared page doesn't reach out just to
260
+ // see whether there's an AI: only once the browser already lets it, the page has paired before, or the person
261
+ // has asked for it (LampAI.connect, from a button, or a real AI call).
262
+ var nearbyAsked = false;
263
+ function nearbyAllowed() {
264
+ // Connected before in this browser (kept with the page's saved things): the browser has had its say already.
265
+ if (nearbyAsked || read('nearby.token', null) || read('nearby.connected', false)) return Promise.resolve(true);
266
+ var permissions = window.navigator && navigator.permissions;
267
+ if (!permissions || typeof permissions.query !== 'function') return Promise.resolve(false);
268
+ // Chrome has named this permission more than one way; any that's granted will do, and one it doesn't know is unknown.
269
+ var names = ['local-network-access', 'loopback-network', 'local-network'];
270
+ return Promise.all(names.map(function (name) {
271
+ try {
272
+ return Promise.resolve(permissions.query({ name: name })).then(function (s) { return !!s && s.state === 'granted'; }, function () { return false; });
273
+ } catch (e) { return false; }
274
+ })).then(function (states) { return states.indexOf(true) !== -1; });
275
+ }
276
+
257
277
  function nearby(path, body, timeout, deadline) {
258
278
  var text = JSON.stringify(body);
259
279
  function go(t) { return exchange(NEARBY, path, t, text, deadline ? Math.max(1000, deadline - Date.now()) : timeout); }
@@ -276,7 +296,7 @@
276
296
  return done.then(null, function (e) {
277
297
  if (e instanceof Failure) throw e;
278
298
  // Safari never lets a page on the web reach this computer this way, so Lamp or lamp ai open here wouldn't help.
279
- if (safari()) throw new Failure('AI here works in Chrome or in Lamp, or with Lamp\'s command line: lamp play and this page\'s link.', null, AI_HELP);
299
+ if (safari()) throw new Failure(SAFARI_AI, null, AI_HELP);
280
300
  throw new Failure('AI isn\'t available here. Open Lamp on this computer, or with Lamp\'s command line, run lamp ai.', null, AI_HELP);
281
301
  });
282
302
  }
@@ -738,31 +758,58 @@
738
758
  * a feature depends on it, and show the feature with its `reason` as it's given rather than words of the app's
739
759
  * own, or hiding it or failing at it, and where it gives `help` (a page away from Lamp), a link to it beside the
740
760
  * reason: "How to turn on AI". A call that fails for that reason has `needsSetup`, or `help`, set on its error.
761
+ * On a page shared on lamp.dev that hasn't reached this computer yet, `status` asks nothing and answers
762
+ * `connect: true`: show a small "Connect AI" button beside its reason that calls `LampAI.connect(capability)` with
763
+ * the same kind of AI (`connect('pictures')` for pictures), and use what that gives as `status`'s answer. Never
764
+ * call `connect()` as the page loads.
741
765
  */
742
766
  var LampAI = {
743
767
  Failure: Failure,
744
768
 
745
769
  /** Whether this kind of AI ('text', 'decisions', 'pictures', 'act' or 'hear') can answer now: { available, reason,
746
- * help }, where `reason` is the short line to show when it can't, written for where the page is, and `help`, when
747
- * it's there, the page that says how to turn AI on, to link to beside it. */
770
+ * help, connect }, where `reason` is the short line to show when it can't, written for where the page is, `help`,
771
+ * when it's there, the page that says how to turn AI on, to link to beside it, and `connect`, when true, that
772
+ * a button calling `LampAI.connect()` would find out (a page on lamp.dev, before it has reached this computer). */
748
773
  status: function (capability) {
749
774
  var no = function (reason, help) {
750
775
  var said = { available: false, reason: reason || 'AI isn\'t set up for this here.' };
751
776
  if (help) said.help = help;
752
777
  return said;
753
778
  };
754
- // 'act' is asked of /ai/act itself: available only when this lamp.js has it, the app around the page lets the
755
- // route through (an older one answers Not found) and the AI that answers the app can take it. 'hear' the same.
756
- if (capability === 'act' || capability === 'hear') {
757
- return post('/ai/' + capability, { probe: true }, 10000).then(function (reply) {
758
- return reply[capability] === true ? { available: true, reason: '' } : no(reply.reason || reply.why);
779
+ if (!bridge && shared()) {
780
+ return nearbyAllowed().then(function (allowed) {
781
+ if (allowed) return ask(capability);
782
+ if (safari()) return no(SAFARI_AI, AI_HELP);
783
+ return { available: false, connect: true, reason: 'Connect to the AI on this computer to use this.', help: AI_HELP };
784
+ });
785
+ }
786
+ return ask(capability);
787
+ function ask(capability) {
788
+ // 'act' is asked of /ai/act itself: available only when this lamp.js has it, the app around the page lets the
789
+ // route through (an older one answers Not found) and the AI that answers the app can take it. 'hear' the same.
790
+ if (capability === 'act' || capability === 'hear') {
791
+ return post('/ai/' + capability, { probe: true }, 10000).then(function (reply) {
792
+ return reply[capability] === true ? { available: true, reason: '' } : no(reply.reason || reply.why);
793
+ }, function (e) { return no(e && e.message, e && e.help); });
794
+ }
795
+ return post('/ai/status', {}, 10000).then(function (reply) {
796
+ if (reply[capability] === true) return { available: true, reason: '' };
797
+ var detail = reply.details && reply.details[capability];
798
+ return no(detail && detail.reason);
759
799
  }, function (e) { return no(e && e.message, e && e.help); });
760
800
  }
761
- return post('/ai/status', {}, 10000).then(function (reply) {
762
- if (reply[capability] === true) return { available: true, reason: '' };
763
- var detail = reply.details && reply.details[capability];
764
- return no(detail && detail.reason);
765
- }, function (e) { return no(e && e.message, e && e.help); });
801
+ },
802
+
803
+ /** On a page shared on lamp.dev, reaches the AI on this computer, so the browser asks the person then: call it
804
+ * from a button ("Connect AI") shown when `status` answers `connect: true`, never as the page loads. Gives what
805
+ * `status` gives for that kind of AI ('text' unless named). Anywhere else it's just `status`. */
806
+ connect: function (capability) {
807
+ nearbyAsked = true;
808
+ return LampAI.status(capability || 'text').then(function (said) {
809
+ // Reached it (whatever it said of this kind of AI): later visits look without asking first.
810
+ if (said.available || !said.help) write('nearby.connected', true);
811
+ return said;
812
+ });
766
813
  },
767
814
 
768
815
  /** Whether this kind of AI can answer now, as `status` says. */
package/cli/lamps/lamp.js CHANGED
@@ -254,6 +254,26 @@
254
254
  var ua = (window.navigator && navigator.userAgent) || '';
255
255
  return /Safari\//.test(ua) && !/Chrome|Chromium|CriOS|Edg|OPR|Firefox|FxiOS/.test(ua);
256
256
  }
257
+ var SAFARI_AI = 'AI here works in Chrome or in Lamp, or with Lamp\'s command line: lamp play and this page\'s link.';
258
+
259
+ // Reaching this computer makes the browser ask the person about it, so a shared page doesn't reach out just to
260
+ // see whether there's an AI: only once the browser already lets it, the page has paired before, or the person
261
+ // has asked for it (LampAI.connect, from a button, or a real AI call).
262
+ var nearbyAsked = false;
263
+ function nearbyAllowed() {
264
+ // Connected before in this browser (kept with the page's saved things): the browser has had its say already.
265
+ if (nearbyAsked || read('nearby.token', null) || read('nearby.connected', false)) return Promise.resolve(true);
266
+ var permissions = window.navigator && navigator.permissions;
267
+ if (!permissions || typeof permissions.query !== 'function') return Promise.resolve(false);
268
+ // Chrome has named this permission more than one way; any that's granted will do, and one it doesn't know is unknown.
269
+ var names = ['local-network-access', 'loopback-network', 'local-network'];
270
+ return Promise.all(names.map(function (name) {
271
+ try {
272
+ return Promise.resolve(permissions.query({ name: name })).then(function (s) { return !!s && s.state === 'granted'; }, function () { return false; });
273
+ } catch (e) { return false; }
274
+ })).then(function (states) { return states.indexOf(true) !== -1; });
275
+ }
276
+
257
277
  function nearby(path, body, timeout, deadline) {
258
278
  var text = JSON.stringify(body);
259
279
  function go(t) { return exchange(NEARBY, path, t, text, deadline ? Math.max(1000, deadline - Date.now()) : timeout); }
@@ -276,7 +296,7 @@
276
296
  return done.then(null, function (e) {
277
297
  if (e instanceof Failure) throw e;
278
298
  // Safari never lets a page on the web reach this computer this way, so Lamp or lamp ai open here wouldn't help.
279
- if (safari()) throw new Failure('AI here works in Chrome or in Lamp, or with Lamp\'s command line: lamp play and this page\'s link.', null, AI_HELP);
299
+ if (safari()) throw new Failure(SAFARI_AI, null, AI_HELP);
280
300
  throw new Failure('AI isn\'t available here. Open Lamp on this computer, or with Lamp\'s command line, run lamp ai.', null, AI_HELP);
281
301
  });
282
302
  }
@@ -738,31 +758,58 @@
738
758
  * a feature depends on it, and show the feature with its `reason` as it's given rather than words of the app's
739
759
  * own, or hiding it or failing at it, and where it gives `help` (a page away from Lamp), a link to it beside the
740
760
  * reason: "How to turn on AI". A call that fails for that reason has `needsSetup`, or `help`, set on its error.
761
+ * On a page shared on lamp.dev that hasn't reached this computer yet, `status` asks nothing and answers
762
+ * `connect: true`: show a small "Connect AI" button beside its reason that calls `LampAI.connect(capability)` with
763
+ * the same kind of AI (`connect('pictures')` for pictures), and use what that gives as `status`'s answer. Never
764
+ * call `connect()` as the page loads.
741
765
  */
742
766
  var LampAI = {
743
767
  Failure: Failure,
744
768
 
745
769
  /** Whether this kind of AI ('text', 'decisions', 'pictures', 'act' or 'hear') can answer now: { available, reason,
746
- * help }, where `reason` is the short line to show when it can't, written for where the page is, and `help`, when
747
- * it's there, the page that says how to turn AI on, to link to beside it. */
770
+ * help, connect }, where `reason` is the short line to show when it can't, written for where the page is, `help`,
771
+ * when it's there, the page that says how to turn AI on, to link to beside it, and `connect`, when true, that
772
+ * a button calling `LampAI.connect()` would find out (a page on lamp.dev, before it has reached this computer). */
748
773
  status: function (capability) {
749
774
  var no = function (reason, help) {
750
775
  var said = { available: false, reason: reason || 'AI isn\'t set up for this here.' };
751
776
  if (help) said.help = help;
752
777
  return said;
753
778
  };
754
- // 'act' is asked of /ai/act itself: available only when this lamp.js has it, the app around the page lets the
755
- // route through (an older one answers Not found) and the AI that answers the app can take it. 'hear' the same.
756
- if (capability === 'act' || capability === 'hear') {
757
- return post('/ai/' + capability, { probe: true }, 10000).then(function (reply) {
758
- return reply[capability] === true ? { available: true, reason: '' } : no(reply.reason || reply.why);
779
+ if (!bridge && shared()) {
780
+ return nearbyAllowed().then(function (allowed) {
781
+ if (allowed) return ask(capability);
782
+ if (safari()) return no(SAFARI_AI, AI_HELP);
783
+ return { available: false, connect: true, reason: 'Connect to the AI on this computer to use this.', help: AI_HELP };
784
+ });
785
+ }
786
+ return ask(capability);
787
+ function ask(capability) {
788
+ // 'act' is asked of /ai/act itself: available only when this lamp.js has it, the app around the page lets the
789
+ // route through (an older one answers Not found) and the AI that answers the app can take it. 'hear' the same.
790
+ if (capability === 'act' || capability === 'hear') {
791
+ return post('/ai/' + capability, { probe: true }, 10000).then(function (reply) {
792
+ return reply[capability] === true ? { available: true, reason: '' } : no(reply.reason || reply.why);
793
+ }, function (e) { return no(e && e.message, e && e.help); });
794
+ }
795
+ return post('/ai/status', {}, 10000).then(function (reply) {
796
+ if (reply[capability] === true) return { available: true, reason: '' };
797
+ var detail = reply.details && reply.details[capability];
798
+ return no(detail && detail.reason);
759
799
  }, function (e) { return no(e && e.message, e && e.help); });
760
800
  }
761
- return post('/ai/status', {}, 10000).then(function (reply) {
762
- if (reply[capability] === true) return { available: true, reason: '' };
763
- var detail = reply.details && reply.details[capability];
764
- return no(detail && detail.reason);
765
- }, function (e) { return no(e && e.message, e && e.help); });
801
+ },
802
+
803
+ /** On a page shared on lamp.dev, reaches the AI on this computer, so the browser asks the person then: call it
804
+ * from a button ("Connect AI") shown when `status` answers `connect: true`, never as the page loads. Gives what
805
+ * `status` gives for that kind of AI ('text' unless named). Anywhere else it's just `status`. */
806
+ connect: function (capability) {
807
+ nearbyAsked = true;
808
+ return LampAI.status(capability || 'text').then(function (said) {
809
+ // Reached it (whatever it said of this kind of AI): later visits look without asking first.
810
+ if (said.available || !said.help) write('nearby.connected', true);
811
+ return said;
812
+ });
766
813
  },
767
814
 
768
815
  /** Whether this kind of AI can answer now, as `status` says. */
@@ -1 +1 @@
1
- {"platform":"mac","instance":"12A4CA51-92CD-4391-8AD0-548165ABFCAE","exe":"ZqxAppName","name":"Zqx App Name"}
1
+ {"exe":"ZqxAppName","name":"Zqx App Name","instance":"66001C8E-04AF-4B31-9585-64F312C02625","platform":"mac"}
@@ -254,6 +254,26 @@
254
254
  var ua = (window.navigator && navigator.userAgent) || '';
255
255
  return /Safari\//.test(ua) && !/Chrome|Chromium|CriOS|Edg|OPR|Firefox|FxiOS/.test(ua);
256
256
  }
257
+ var SAFARI_AI = 'AI here works in Chrome or in Lamp, or with Lamp\'s command line: lamp play and this page\'s link.';
258
+
259
+ // Reaching this computer makes the browser ask the person about it, so a shared page doesn't reach out just to
260
+ // see whether there's an AI: only once the browser already lets it, the page has paired before, or the person
261
+ // has asked for it (LampAI.connect, from a button, or a real AI call).
262
+ var nearbyAsked = false;
263
+ function nearbyAllowed() {
264
+ // Connected before in this browser (kept with the page's saved things): the browser has had its say already.
265
+ if (nearbyAsked || read('nearby.token', null) || read('nearby.connected', false)) return Promise.resolve(true);
266
+ var permissions = window.navigator && navigator.permissions;
267
+ if (!permissions || typeof permissions.query !== 'function') return Promise.resolve(false);
268
+ // Chrome has named this permission more than one way; any that's granted will do, and one it doesn't know is unknown.
269
+ var names = ['local-network-access', 'loopback-network', 'local-network'];
270
+ return Promise.all(names.map(function (name) {
271
+ try {
272
+ return Promise.resolve(permissions.query({ name: name })).then(function (s) { return !!s && s.state === 'granted'; }, function () { return false; });
273
+ } catch (e) { return false; }
274
+ })).then(function (states) { return states.indexOf(true) !== -1; });
275
+ }
276
+
257
277
  function nearby(path, body, timeout, deadline) {
258
278
  var text = JSON.stringify(body);
259
279
  function go(t) { return exchange(NEARBY, path, t, text, deadline ? Math.max(1000, deadline - Date.now()) : timeout); }
@@ -276,7 +296,7 @@
276
296
  return done.then(null, function (e) {
277
297
  if (e instanceof Failure) throw e;
278
298
  // Safari never lets a page on the web reach this computer this way, so Lamp or lamp ai open here wouldn't help.
279
- if (safari()) throw new Failure('AI here works in Chrome or in Lamp, or with Lamp\'s command line: lamp play and this page\'s link.', null, AI_HELP);
299
+ if (safari()) throw new Failure(SAFARI_AI, null, AI_HELP);
280
300
  throw new Failure('AI isn\'t available here. Open Lamp on this computer, or with Lamp\'s command line, run lamp ai.', null, AI_HELP);
281
301
  });
282
302
  }
@@ -738,31 +758,58 @@
738
758
  * a feature depends on it, and show the feature with its `reason` as it's given rather than words of the app's
739
759
  * own, or hiding it or failing at it, and where it gives `help` (a page away from Lamp), a link to it beside the
740
760
  * reason: "How to turn on AI". A call that fails for that reason has `needsSetup`, or `help`, set on its error.
761
+ * On a page shared on lamp.dev that hasn't reached this computer yet, `status` asks nothing and answers
762
+ * `connect: true`: show a small "Connect AI" button beside its reason that calls `LampAI.connect(capability)` with
763
+ * the same kind of AI (`connect('pictures')` for pictures), and use what that gives as `status`'s answer. Never
764
+ * call `connect()` as the page loads.
741
765
  */
742
766
  var LampAI = {
743
767
  Failure: Failure,
744
768
 
745
769
  /** Whether this kind of AI ('text', 'decisions', 'pictures', 'act' or 'hear') can answer now: { available, reason,
746
- * help }, where `reason` is the short line to show when it can't, written for where the page is, and `help`, when
747
- * it's there, the page that says how to turn AI on, to link to beside it. */
770
+ * help, connect }, where `reason` is the short line to show when it can't, written for where the page is, `help`,
771
+ * when it's there, the page that says how to turn AI on, to link to beside it, and `connect`, when true, that
772
+ * a button calling `LampAI.connect()` would find out (a page on lamp.dev, before it has reached this computer). */
748
773
  status: function (capability) {
749
774
  var no = function (reason, help) {
750
775
  var said = { available: false, reason: reason || 'AI isn\'t set up for this here.' };
751
776
  if (help) said.help = help;
752
777
  return said;
753
778
  };
754
- // 'act' is asked of /ai/act itself: available only when this lamp.js has it, the app around the page lets the
755
- // route through (an older one answers Not found) and the AI that answers the app can take it. 'hear' the same.
756
- if (capability === 'act' || capability === 'hear') {
757
- return post('/ai/' + capability, { probe: true }, 10000).then(function (reply) {
758
- return reply[capability] === true ? { available: true, reason: '' } : no(reply.reason || reply.why);
779
+ if (!bridge && shared()) {
780
+ return nearbyAllowed().then(function (allowed) {
781
+ if (allowed) return ask(capability);
782
+ if (safari()) return no(SAFARI_AI, AI_HELP);
783
+ return { available: false, connect: true, reason: 'Connect to the AI on this computer to use this.', help: AI_HELP };
784
+ });
785
+ }
786
+ return ask(capability);
787
+ function ask(capability) {
788
+ // 'act' is asked of /ai/act itself: available only when this lamp.js has it, the app around the page lets the
789
+ // route through (an older one answers Not found) and the AI that answers the app can take it. 'hear' the same.
790
+ if (capability === 'act' || capability === 'hear') {
791
+ return post('/ai/' + capability, { probe: true }, 10000).then(function (reply) {
792
+ return reply[capability] === true ? { available: true, reason: '' } : no(reply.reason || reply.why);
793
+ }, function (e) { return no(e && e.message, e && e.help); });
794
+ }
795
+ return post('/ai/status', {}, 10000).then(function (reply) {
796
+ if (reply[capability] === true) return { available: true, reason: '' };
797
+ var detail = reply.details && reply.details[capability];
798
+ return no(detail && detail.reason);
759
799
  }, function (e) { return no(e && e.message, e && e.help); });
760
800
  }
761
- return post('/ai/status', {}, 10000).then(function (reply) {
762
- if (reply[capability] === true) return { available: true, reason: '' };
763
- var detail = reply.details && reply.details[capability];
764
- return no(detail && detail.reason);
765
- }, function (e) { return no(e && e.message, e && e.help); });
801
+ },
802
+
803
+ /** On a page shared on lamp.dev, reaches the AI on this computer, so the browser asks the person then: call it
804
+ * from a button ("Connect AI") shown when `status` answers `connect: true`, never as the page loads. Gives what
805
+ * `status` gives for that kind of AI ('text' unless named). Anywhere else it's just `status`. */
806
+ connect: function (capability) {
807
+ nearbyAsked = true;
808
+ return LampAI.status(capability || 'text').then(function (said) {
809
+ // Reached it (whatever it said of this kind of AI): later visits look without asking first.
810
+ if (said.available || !said.help) write('nearby.connected', true);
811
+ return said;
812
+ });
766
813
  },
767
814
 
768
815
  /** Whether this kind of AI can answer now, as `status` says. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lampdotdev/lamp",
3
- "version": "0.36.2-nightly.19",
3
+ "version": "0.36.2-nightly.21",
4
4
  "description": "Runs a web app or game from its folder and puts each saved change into the open page.",
5
5
  "type": "module",
6
6
  "bin": {