@robbiesrobotics/alice-agents 1.5.3 → 1.5.4

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.
@@ -311,12 +311,30 @@ async function watch(args) {
311
311
  await startHeartbeatLoop(config);
312
312
  }
313
313
 
314
+ // ── Programmatic API exports ───────────────────────────────────────────────────
315
+ // Allow ESM callers to import these via createRequire or spawn as child process.
316
+ module.exports = {
317
+ login,
318
+ register,
319
+ status,
320
+ unregister,
321
+ watch,
322
+ loadConfig,
323
+ saveConfig,
324
+ detectGatewayUrl,
325
+ readGatewayToken,
326
+ API_BASE,
327
+ CONFIG_FILE,
328
+ };
329
+
314
330
  // ── CLI dispatcher ─────────────────────────────────────────────────────────────
315
- const commands = { login, register, status, unregister, watch };
316
- const cmd = process.argv[2];
331
+ // Only run as CLI when this file is the main module (not when required/imported)
332
+ if (require.main === module) {
333
+ const commands = { login, register, status, unregister, watch };
334
+ const cmd = process.argv[2];
317
335
 
318
- if (!cmd) {
319
- console.log(`alice-cloud v1.0.1 — A.L.I.C.E. | Control Cloud CLI
336
+ if (!cmd) {
337
+ console.log(`alice-cloud v1.0.1 — A.L.I.C.E. | Control Cloud CLI
320
338
 
321
339
  Usage: alice-cloud <command> [options]
322
340
 
@@ -335,16 +353,17 @@ Environment:
335
353
  ALICE_SUPABASE_URL Supabase project URL (default: xxx project)
336
354
 
337
355
  Run 'alice-cloud <command> --help' for more options.`);
338
- process.exit(0);
339
- }
356
+ process.exit(0);
357
+ }
340
358
 
341
- const handler = commands[cmd];
342
- if (!handler) {
343
- console.error(`Unknown command: ${cmd}`);
344
- process.exit(1);
345
- }
359
+ const handler = commands[cmd];
360
+ if (!handler) {
361
+ console.error(`Unknown command: ${cmd}`);
362
+ process.exit(1);
363
+ }
346
364
 
347
- handler(process.argv.slice(3)).catch((err) => {
348
- console.error('Error:', err.message);
349
- process.exit(1);
350
- });
365
+ handler(process.argv.slice(3)).catch((err) => {
366
+ console.error('Error:', err.message);
367
+ process.exit(1);
368
+ });
369
+ }
package/lib/installer.mjs CHANGED
@@ -6,7 +6,14 @@ import { homedir } from 'node:os';
6
6
  import { configExists, mergeConfig, removeAliceAgents, detectAvailableModels } from './config-merger.mjs';
7
7
  import { scaffoldAll } from './workspace-scaffolder.mjs';
8
8
  import { readManifest, writeManifest } from './manifest.mjs';
9
- import { configureMissionControlCloud, getDefaultMissionControlSettings } from './mission-control.mjs';
9
+ import {
10
+ configureMissionControlCloud,
11
+ getDefaultMissionControlSettings,
12
+ getCloudStatus,
13
+ isCloudAuthenticated,
14
+ isCloudRegistered,
15
+ configureCloudFromSupabase,
16
+ } from './mission-control.mjs';
10
17
  import {
11
18
  promptInstallMode,
12
19
  promptUserInfo,
@@ -15,8 +22,10 @@ import {
15
22
  promptTier,
16
23
  promptLicenseKey,
17
24
  promptCloudAddon,
18
- promptMissionControlToken,
19
- promptMissionControlWorkerToken,
25
+ promptCloudSetup,
26
+ promptCloudReauth,
27
+ promptCloudHeartbeat,
28
+ promptCloudToken,
20
29
  confirm,
21
30
  choose,
22
31
  input,
@@ -470,11 +479,10 @@ function printSummaryWithOptions(mode, tier, agents, preset, userInfo, detectedM
470
479
  `${dim('Model:')} ${green(modelLabel)}`,
471
480
  `${dim('User:')} ${green(userInfo.name)}`,
472
481
  `${dim('Timezone:')} ${green(userInfo.timezone)}`,
473
- `${dim('Mission Control:')} ${green(missionControl?.enabled ? 'cloud enabled' : 'local only')}`,
482
+ `${dim('Cloud:')} ${green(missionControl?.enabled ? 'enabled' : 'local only')}`,
474
483
  ...(missionControl?.enabled
475
484
  ? [
476
485
  `${dim('Dashboard:')} ${green(missionControl.dashboardUrl)}`,
477
- `${dim('Ingest:')} ${green(missionControl.ingestUrl)}`,
478
486
  ]
479
487
  : []),
480
488
  '',
@@ -486,6 +494,75 @@ function printSummaryWithOptions(mode, tier, agents, preset, userInfo, detectedM
486
494
  console.log('');
487
495
  }
488
496
 
497
+ /**
498
+ * Run inline cloud onboarding — opens browser, validates token, registers gateway.
499
+ * Uses alice-cloud CLI spawned as a child process (CJS ↔ ESM boundary).
500
+ */
501
+ async function _runCloudOnboarding(auto, options, existingMissionControl) {
502
+ const { execSync: execSyncLocal, spawn: spawnLocal } = await import('node:child_process');
503
+ const aliceCloudBin = join(__dirname, '..', 'bin', 'alice-cloud.cjs');
504
+ const defaults = getDefaultMissionControlSettings();
505
+
506
+ try {
507
+ // Step 1: Login — open browser for Supabase OAuth
508
+ console.log('');
509
+ console.log(` ${icons.pkg} ${bold('Setting up A.L.I.C.E. Cloud...')}`);
510
+ console.log(` Opening your browser to sign in with GitHub...`);
511
+ console.log('');
512
+
513
+ // Spawn alice-cloud login as a child process
514
+ // In non-interactive mode, use env var for token
515
+ if (auto && options.cloudSupabaseToken) {
516
+ execSyncLocal(
517
+ `node ${JSON.stringify(aliceCloudBin)} login --non-interactive`,
518
+ {
519
+ stdio: 'inherit',
520
+ env: { ...process.env, ALICE_SUPABASE_TOKEN: options.cloudSupabaseToken, ALICE_NON_INTERACTIVE: '1' },
521
+ }
522
+ );
523
+ } else if (!auto) {
524
+ // Interactive: spawn login which opens browser and waits for token paste
525
+ execSyncLocal(`node ${JSON.stringify(aliceCloudBin)} login`, { stdio: 'inherit' });
526
+ }
527
+
528
+ // Step 2: Register gateway
529
+ if (isCloudAuthenticated()) {
530
+ console.log('');
531
+ console.log(` ${icons.pkg} ${bold('Registering gateway with A.L.I.C.E. Cloud...')}`);
532
+ execSyncLocal(`node ${JSON.stringify(aliceCloudBin)} register`, { stdio: 'inherit' });
533
+ printStepDone('Cloud login and registration complete');
534
+
535
+ // Step 3: Ask about heartbeat daemon
536
+ const startHeartbeat = auto ? false : await promptCloudHeartbeat();
537
+ if (startHeartbeat) {
538
+ console.log(` ${dim('Starting heartbeat daemon...')}`);
539
+ execSyncLocal(`node ${JSON.stringify(aliceCloudBin)} watch --daemon`, { stdio: 'inherit' });
540
+ printStepDone('Heartbeat daemon started');
541
+ }
542
+ } else {
543
+ printStepSkip('Cloud registration', 'login was skipped or failed — run `alice-cloud login` later');
544
+ }
545
+ } catch (err) {
546
+ console.log(` ${icons.warn} ${yellow('Cloud setup encountered an issue: ' + (err.message || 'unknown error'))}`);
547
+ console.log(` ${dim('You can complete setup later with:')} ${cyan('alice-cloud login && alice-cloud register')}`);
548
+ }
549
+
550
+ // Build missionControl object from whatever state we achieved
551
+ const cloudStatus = getCloudStatus();
552
+ return {
553
+ enabled: true,
554
+ provider: 'cloud',
555
+ dashboardUrl: existingMissionControl?.dashboardUrl || defaults.dashboardUrl,
556
+ ingestUrl: existingMissionControl?.ingestUrl || `${defaults.dashboardUrl}/api/v1/ingest`,
557
+ sourceNode: existingMissionControl?.sourceNode || defaults.sourceNode,
558
+ teamId: String(options.cloudTeamId || existingMissionControl?.teamId || '').trim(),
559
+ teamSlug: String(options.cloudTeamSlug || existingMissionControl?.teamSlug || '').trim(),
560
+ teamName: String(options.cloudTeamName || existingMissionControl?.teamName || '').trim(),
561
+ teamPlan: String(options.cloudTeamPlan || existingMissionControl?.teamPlan || '').trim(),
562
+ ...(cloudStatus.authenticated ? { hasIngestToken: true } : {}),
563
+ };
564
+ }
565
+
489
566
  export async function runInstall(options = {}) {
490
567
  const auto = options.yes || false;
491
568
  const manifest = readManifest();
@@ -718,36 +795,61 @@ export async function runInstall(options = {}) {
718
795
  : cloudFlag ?? await promptCloudAddon();
719
796
 
720
797
  if (enableCloud) {
721
- const defaults = getDefaultMissionControlSettings();
722
- const dashboardUrl =
723
- String(options.cloudDashboardUrl || existingMissionControl?.dashboardUrl || defaults.dashboardUrl).trim();
724
- const ingestUrl =
725
- String(options.cloudIngestUrl || existingMissionControl?.ingestUrl || `${dashboardUrl}/api/v1/ingest`).trim();
726
- const ingestToken =
727
- auto
728
- ? String(options.cloudToken || '').trim()
729
- : String(options.cloudToken || await promptMissionControlToken()).trim();
730
- const workerToken =
731
- auto
732
- ? String(options.cloudWorkerToken || '').trim()
733
- : String(options.cloudWorkerToken || await promptMissionControlWorkerToken(ingestToken)).trim();
734
- const sourceNode =
735
- String(options.cloudSourceNode || existingMissionControl?.sourceNode || defaults.sourceNode).trim();
736
-
737
- missionControl = {
738
- enabled: true,
739
- provider: 'cloud',
740
- dashboardUrl,
741
- ingestUrl,
742
- sourceNode,
743
- teamId: String(options.cloudTeamId || existingMissionControl?.teamId || '').trim(),
744
- teamSlug: String(options.cloudTeamSlug || existingMissionControl?.teamSlug || '').trim(),
745
- teamName: String(options.cloudTeamName || existingMissionControl?.teamName || '').trim(),
746
- teamPlan: String(options.cloudTeamPlan || existingMissionControl?.teamPlan || '').trim(),
747
- hasIngestToken: !!ingestToken,
748
- ingestToken,
749
- workerToken,
750
- };
798
+ const cloudStatus = getCloudStatus();
799
+
800
+ // Upgrade mode: show existing cloud status
801
+ if (mode === 'upgrade' && (cloudStatus.authenticated || existingMissionControl?.enabled)) {
802
+ console.log('');
803
+ console.log(` ${icons.ok} ${green('A.L.I.C.E. Cloud is configured')}`);
804
+ if (cloudStatus.user) {
805
+ console.log(` ${dim('User:')} ${cloudStatus.user}`);
806
+ }
807
+ if (cloudStatus.gatewayUrl) {
808
+ console.log(` ${dim('Gateway:')} ${cloudStatus.gatewayUrl}`);
809
+ }
810
+ if (cloudStatus.registeredAt) {
811
+ console.log(` ${dim('Registered:')} ${cloudStatus.registeredAt}`);
812
+ }
813
+ console.log('');
814
+
815
+ const reauth = auto ? false : await promptCloudReauth();
816
+ if (!reauth) {
817
+ // Keep existing config — use Supabase config if available, fall back to legacy
818
+ if (cloudStatus.authenticated) {
819
+ missionControl = {
820
+ enabled: true,
821
+ provider: 'cloud',
822
+ dashboardUrl: existingMissionControl?.dashboardUrl || 'https://alice.av3.ai',
823
+ ingestUrl: existingMissionControl?.ingestUrl || 'https://alice.av3.ai/api/v1/ingest',
824
+ sourceNode: existingMissionControl?.sourceNode || getDefaultMissionControlSettings().sourceNode,
825
+ };
826
+ } else {
827
+ missionControl = { ...existingMissionControl, enabled: true };
828
+ }
829
+ } else {
830
+ // Re-authenticate — run alice-cloud login inline
831
+ missionControl = await _runCloudOnboarding(auto, options, existingMissionControl);
832
+ }
833
+ } else {
834
+ // Fresh install — run cloud onboarding
835
+ const wantsSetup = auto ? true : await promptCloudSetup();
836
+
837
+ if (wantsSetup) {
838
+ missionControl = await _runCloudOnboarding(auto, options, existingMissionControl);
839
+ } else {
840
+ console.log('');
841
+ console.log(` ${dim('You can set up cloud later with:')} ${cyan('alice-cloud login && alice-cloud register')}`);
842
+ console.log('');
843
+ // Cloud addon enabled but setup skipped — bridge installed, no auth yet
844
+ missionControl = {
845
+ enabled: true,
846
+ provider: 'cloud',
847
+ dashboardUrl: 'https://alice.av3.ai',
848
+ ingestUrl: 'https://alice.av3.ai/api/v1/ingest',
849
+ sourceNode: getDefaultMissionControlSettings().sourceNode,
850
+ };
851
+ }
852
+ }
751
853
  }
752
854
  }
753
855
 
@@ -790,12 +892,12 @@ export async function runInstall(options = {}) {
790
892
 
791
893
  if (missionControl?.enabled) {
792
894
  const missionControlResult = configureMissionControlCloud(missionControl);
793
- printStepDone('Mission Control cloud', missionControlResult.summary.dashboardUrl);
895
+ printStepDone('A.L.I.C.E. Cloud', missionControlResult.summary.dashboardUrl);
794
896
  if (!missionControlResult.summary.hasIngestToken) {
795
- printStepSkip('Cloud access token not set', 'bridge installed; add token later to enable authenticated ingest');
897
+ printStepSkip('Cloud authentication pending', 'run `alice-cloud login && alice-cloud register` to complete setup');
796
898
  }
797
899
  } else {
798
- printStepSkip('Mission Control cloud', 'not enabled for this install');
900
+ printStepSkip('A.L.I.C.E. Cloud', 'not enabled for this install');
799
901
  }
800
902
 
801
903
  // Scaffold workspaces
@@ -179,3 +179,64 @@ export function getDefaultMissionControlSettings() {
179
179
  export function hasMissionControlBridgeInstalled() {
180
180
  return existsSync(join(OPENCLAW_HOME, 'extensions', BRIDGE_ID));
181
181
  }
182
+
183
+ // ── Supabase-based cloud onboarding helpers ────────────────────────────────────
184
+
185
+ const CLOUD_CONFIG_PATH = join(OPENCLAW_HOME, 'alice-cloud.json');
186
+
187
+ export function readCloudConfig() {
188
+ return readJsonFile(CLOUD_CONFIG_PATH);
189
+ }
190
+
191
+ export function isCloudAuthenticated() {
192
+ const config = readCloudConfig();
193
+ return !!(config && config.supabaseToken);
194
+ }
195
+
196
+ export function isCloudRegistered() {
197
+ const config = readCloudConfig();
198
+ return !!(config && config.registration);
199
+ }
200
+
201
+ export function getCloudStatus() {
202
+ const config = readCloudConfig();
203
+ if (!config) return { authenticated: false, registered: false };
204
+ return {
205
+ authenticated: !!config.supabaseToken,
206
+ registered: !!config.registration,
207
+ user: config.user?.email || config.user?.user_metadata?.user_name || null,
208
+ gatewayUrl: config.registration?.gatewayUrl || null,
209
+ hostname: config.registration?.hostname || null,
210
+ registeredAt: config.registration?.registeredAt || null,
211
+ };
212
+ }
213
+
214
+ /**
215
+ * Configure cloud using Supabase auth (new flow).
216
+ * This preserves the existing bridge installation and MC config writing,
217
+ * and reads auth state from alice-cloud.json.
218
+ */
219
+ export function configureCloudFromSupabase(input = {}) {
220
+ // Read alice-cloud.json for auth info
221
+ const cloudConfig = readCloudConfig() || {};
222
+
223
+ // Build settings — we still use the same bridge + MC config infrastructure,
224
+ // but tokens now come from Supabase auth rather than manual ingest tokens
225
+ const settingsInput = {
226
+ dashboardUrl: input.dashboardUrl || DEFAULT_DASHBOARD_URL,
227
+ sourceNode: input.sourceNode || hostname() || 'openclaw-local',
228
+ teamId: input.teamId || '',
229
+ teamSlug: input.teamSlug || '',
230
+ teamName: input.teamName || '',
231
+ teamPlan: input.teamPlan || '',
232
+ enabled: true,
233
+ };
234
+
235
+ // If we have a supabase token, use it as the ingest/worker token for bridge compatibility
236
+ if (cloudConfig.supabaseToken) {
237
+ settingsInput.ingestToken = cloudConfig.supabaseToken;
238
+ settingsInput.workerToken = cloudConfig.supabaseToken;
239
+ }
240
+
241
+ return configureMissionControlCloud(settingsInput);
242
+ }
package/lib/prompter.mjs CHANGED
@@ -128,27 +128,33 @@ export async function promptLicenseKey() {
128
128
 
129
129
  export async function promptCloudAddon() {
130
130
  console.log('');
131
- console.log(' Mission Control Cloud add-on');
132
- console.log(' Hosted dashboard, telemetry sync, and cloud memory for Pro users.');
131
+ console.log(' A.L.I.C.E. Cloud add-on');
132
+ console.log(' Remote monitoring, fleet management, and cloud sync for Pro users.');
133
133
  console.log('');
134
- return confirm(' Enable the Mission Control Cloud add-on on this machine?', true);
134
+ return confirm(' Enable A.L.I.C.E. Cloud on this machine?', true);
135
135
  }
136
136
 
137
- export async function promptMissionControlToken() {
137
+ export async function promptCloudSetup() {
138
138
  console.log('');
139
- console.log(' Enter your Mission Control Ingest Token from your purchase email.');
140
- console.log(' It looks like: mc_ingest_[64 hex chars]');
141
- console.log(' You can find it in the "A.L.I.C.E. Pro License Delivered" email.');
142
- console.log(' Press Enter to skip — you can add it later.');
139
+ console.log(' A.L.I.C.E. Cloud connects your local gateway to alice.av3.ai');
140
+ console.log(' for remote monitoring, fleet management, and cloud sync.');
143
141
  console.log('');
144
- return input(' Mission Control Ingest Token', '');
142
+ return confirm(' Would you like to set up A.L.I.C.E. Cloud now?', true);
145
143
  }
146
144
 
147
- export async function promptMissionControlWorkerToken(ingestToken = '') {
148
- if (!ingestToken) return '';
145
+ export async function promptCloudReauth() {
149
146
  console.log('');
150
- console.log(' Enter your Mission Control Worker Token from the same purchase email.');
151
- console.log(' It looks like: mc_worker_[64 hex chars]');
147
+ return confirm(' Re-authenticate with A.L.I.C.E. Cloud?', false);
148
+ }
149
+
150
+ export async function promptCloudHeartbeat() {
151
+ return confirm(' Start the heartbeat daemon (keeps cloud connection alive)?', true);
152
+ }
153
+
154
+ export async function promptCloudToken() {
155
+ console.log('');
156
+ console.log(' After logging in via your browser, paste the access token here.');
157
+ console.log(' Press Enter to skip — you can run `alice-cloud login` later.');
152
158
  console.log('');
153
- return input(' Mission Control Worker Token', '');
159
+ return input(' Access token', '');
154
160
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robbiesrobotics/alice-agents",
3
- "version": "1.5.3",
3
+ "version": "1.5.4",
4
4
  "description": "A.L.I.C.E. \u2014 31 AI agents for OpenClaw. One conversation, one team.",
5
5
  "bin": {
6
6
  "alice-agents": "bin/alice-install.mjs",