@alwaysai/device-agent 0.0.13 → 0.0.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.
Files changed (151) hide show
  1. package/lib/application-control/backup.js +3 -3
  2. package/lib/application-control/backup.js.map +1 -1
  3. package/lib/application-control/index.d.ts +4 -4
  4. package/lib/application-control/index.d.ts.map +1 -1
  5. package/lib/application-control/index.js +1 -4
  6. package/lib/application-control/index.js.map +1 -1
  7. package/lib/application-control/install.d.ts +1 -1
  8. package/lib/application-control/install.d.ts.map +1 -1
  9. package/lib/application-control/install.js +41 -54
  10. package/lib/application-control/install.js.map +1 -1
  11. package/lib/application-control/models.d.ts +0 -4
  12. package/lib/application-control/models.d.ts.map +1 -1
  13. package/lib/application-control/models.js +13 -22
  14. package/lib/application-control/models.js.map +1 -1
  15. package/lib/application-control/status.d.ts +0 -6
  16. package/lib/application-control/status.d.ts.map +1 -1
  17. package/lib/application-control/status.js +3 -19
  18. package/lib/application-control/status.js.map +1 -1
  19. package/lib/application-control/utils.d.ts +3 -0
  20. package/lib/application-control/utils.d.ts.map +1 -1
  21. package/lib/application-control/utils.js +50 -21
  22. package/lib/application-control/utils.js.map +1 -1
  23. package/lib/cloud-connection/bootstrap-provision.d.ts +1 -1
  24. package/lib/cloud-connection/bootstrap-provision.d.ts.map +1 -1
  25. package/lib/cloud-connection/bootstrap-provision.js +9 -9
  26. package/lib/cloud-connection/bootstrap-provision.js.map +1 -1
  27. package/lib/cloud-connection/cmd-status.d.ts +8 -0
  28. package/lib/cloud-connection/cmd-status.d.ts.map +1 -0
  29. package/lib/cloud-connection/cmd-status.js +62 -0
  30. package/lib/cloud-connection/cmd-status.js.map +1 -0
  31. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +10 -2
  32. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  33. package/lib/cloud-connection/device-agent-cloud-connection.js +156 -66
  34. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  35. package/lib/cloud-connection/device-agent.d.ts.map +1 -1
  36. package/lib/cloud-connection/device-agent.js +4 -3
  37. package/lib/cloud-connection/device-agent.js.map +1 -1
  38. package/lib/cloud-connection/live-updates-handler.d.ts +10 -18
  39. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
  40. package/lib/cloud-connection/live-updates-handler.js +50 -50
  41. package/lib/cloud-connection/live-updates-handler.js.map +1 -1
  42. package/lib/cloud-connection/messages.d.ts +3 -1
  43. package/lib/cloud-connection/messages.d.ts.map +1 -1
  44. package/lib/cloud-connection/messages.js +13 -1
  45. package/lib/cloud-connection/messages.js.map +1 -1
  46. package/lib/cloud-connection/passthrough-handler.d.ts +11 -0
  47. package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -0
  48. package/lib/cloud-connection/passthrough-handler.js +59 -0
  49. package/lib/cloud-connection/passthrough-handler.js.map +1 -0
  50. package/lib/cloud-connection/publisher.d.ts +1 -0
  51. package/lib/cloud-connection/publisher.d.ts.map +1 -1
  52. package/lib/cloud-connection/publisher.js +14 -0
  53. package/lib/cloud-connection/publisher.js.map +1 -1
  54. package/lib/cloud-connection/shadow-handler.d.ts +2 -3
  55. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
  56. package/lib/cloud-connection/shadow-handler.js +18 -4
  57. package/lib/cloud-connection/shadow-handler.js.map +1 -1
  58. package/lib/cloud-connection/shadow-handler.test.d.ts +2 -0
  59. package/lib/cloud-connection/shadow-handler.test.d.ts.map +1 -0
  60. package/lib/cloud-connection/shadow-handler.test.js +321 -0
  61. package/lib/cloud-connection/shadow-handler.test.js.map +1 -0
  62. package/lib/environment.d.ts +1 -0
  63. package/lib/environment.d.ts.map +1 -1
  64. package/lib/environment.js +3 -2
  65. package/lib/environment.js.map +1 -1
  66. package/lib/index.js +2 -2
  67. package/lib/index.js.map +1 -1
  68. package/lib/infrastructure/agent-config.d.ts +15 -48
  69. package/lib/infrastructure/agent-config.d.ts.map +1 -1
  70. package/lib/infrastructure/agent-config.js.map +1 -1
  71. package/lib/infrastructure/agent-config.test.js +0 -6
  72. package/lib/infrastructure/agent-config.test.js.map +1 -1
  73. package/lib/infrastructure/system-id.js +2 -2
  74. package/lib/infrastructure/system-id.js.map +1 -1
  75. package/lib/infrastructure/tokens-and-device-cfg.d.ts.map +1 -1
  76. package/lib/infrastructure/tokens-and-device-cfg.js +5 -9
  77. package/lib/infrastructure/tokens-and-device-cfg.js.map +1 -1
  78. package/lib/local-connection/rabbitmq-connection.d.ts +4 -0
  79. package/lib/local-connection/rabbitmq-connection.d.ts.map +1 -0
  80. package/lib/local-connection/rabbitmq-connection.js +58 -0
  81. package/lib/local-connection/rabbitmq-connection.js.map +1 -0
  82. package/lib/subcommands/app/app.d.ts +4 -3
  83. package/lib/subcommands/app/app.d.ts.map +1 -1
  84. package/lib/subcommands/app/app.js +78 -27
  85. package/lib/subcommands/app/app.js.map +1 -1
  86. package/lib/subcommands/app/index.js +2 -2
  87. package/lib/subcommands/device/clean.js +4 -4
  88. package/lib/subcommands/device/clean.js.map +1 -1
  89. package/lib/subcommands/device/device.d.ts +1 -1
  90. package/lib/subcommands/device/device.d.ts.map +1 -1
  91. package/lib/subcommands/device/device.js +9 -10
  92. package/lib/subcommands/device/device.js.map +1 -1
  93. package/lib/subcommands/index.d.ts +0 -1
  94. package/lib/subcommands/index.d.ts.map +1 -1
  95. package/lib/subcommands/login.d.ts +0 -1
  96. package/lib/subcommands/login.d.ts.map +1 -1
  97. package/lib/subcommands/login.js +1 -9
  98. package/lib/subcommands/login.js.map +1 -1
  99. package/lib/util/directories.d.ts +11 -12
  100. package/lib/util/directories.d.ts.map +1 -1
  101. package/lib/util/directories.js +24 -29
  102. package/lib/util/directories.js.map +1 -1
  103. package/lib/util/fetch-with-timeout.d.ts +4 -0
  104. package/lib/util/fetch-with-timeout.d.ts.map +1 -0
  105. package/lib/util/fetch-with-timeout.js +15 -0
  106. package/lib/util/fetch-with-timeout.js.map +1 -0
  107. package/lib/util/logger.js +1 -0
  108. package/lib/util/logger.js.map +1 -1
  109. package/lib/util/require-logged-in-and-paid-plan.d.ts +2 -0
  110. package/lib/util/require-logged-in-and-paid-plan.d.ts.map +1 -0
  111. package/lib/util/require-logged-in-and-paid-plan.js +18 -0
  112. package/lib/util/require-logged-in-and-paid-plan.js.map +1 -0
  113. package/package.json +20 -32
  114. package/readme.md +100 -89
  115. package/src/application-control/backup.ts +3 -3
  116. package/src/application-control/index.ts +0 -6
  117. package/src/application-control/install.ts +53 -73
  118. package/src/application-control/models.ts +7 -19
  119. package/src/application-control/status.ts +3 -19
  120. package/src/application-control/utils.ts +61 -22
  121. package/src/cloud-connection/bootstrap-provision.ts +13 -10
  122. package/src/cloud-connection/cmd-status.ts +71 -0
  123. package/src/cloud-connection/device-agent-cloud-connection.ts +211 -102
  124. package/src/cloud-connection/device-agent.ts +7 -4
  125. package/src/cloud-connection/live-updates-handler.ts +79 -86
  126. package/src/cloud-connection/messages.ts +22 -1
  127. package/src/cloud-connection/passthrough-handler.ts +67 -0
  128. package/src/cloud-connection/publisher.ts +21 -0
  129. package/src/cloud-connection/shadow-handler.test.ts +361 -0
  130. package/src/cloud-connection/shadow-handler.ts +28 -7
  131. package/src/environment.ts +4 -1
  132. package/src/index.ts +2 -2
  133. package/src/infrastructure/agent-config.test.ts +0 -7
  134. package/src/infrastructure/agent-config.ts +24 -2
  135. package/src/infrastructure/system-id.ts +1 -1
  136. package/src/infrastructure/tokens-and-device-cfg.ts +8 -13
  137. package/src/local-connection/rabbitmq-connection.ts +53 -0
  138. package/src/subcommands/app/app.ts +82 -31
  139. package/src/subcommands/app/index.ts +4 -4
  140. package/src/subcommands/device/clean.ts +4 -4
  141. package/src/subcommands/device/device.ts +13 -13
  142. package/src/subcommands/login.ts +1 -9
  143. package/src/util/directories.ts +31 -29
  144. package/src/util/fetch-with-timeout.ts +18 -0
  145. package/src/util/logger.ts +2 -0
  146. package/src/util/require-logged-in-and-paid-plan.ts +16 -0
  147. package/lib/cloud-connection/app-install-status.d.ts +0 -16
  148. package/lib/cloud-connection/app-install-status.d.ts.map +0 -1
  149. package/lib/cloud-connection/app-install-status.js +0 -53
  150. package/lib/cloud-connection/app-install-status.js.map +0 -1
  151. package/src/cloud-connection/app-install-status.ts +0 -62
@@ -7,7 +7,7 @@ import { logger } from '../util/logger';
7
7
  import { Publisher } from './publisher';
8
8
  import { AppConfigModels, getAppCfgModelsDiff } from './shadow';
9
9
 
10
- interface ShadowTopics {
10
+ export interface ShadowTopics {
11
11
  projects: {
12
12
  update: string;
13
13
  get: string;
@@ -59,8 +59,9 @@ export class ShadowHandler {
59
59
  if (projectShadow.appConfig) {
60
60
  const newAppCfg = JSON.parse(projectShadow.appConfig);
61
61
  if (!validateAppConfig(newAppCfg)) {
62
+ // FIXME: Raise an exception to be handled at higher layer
62
63
  logger.error(
63
- `Received invalid app config for ${projectId}!/n${JSON.stringify(
64
+ `Received invalid app config for ${projectId}!\n${JSON.stringify(
64
65
  validateAppConfig.errors,
65
66
  null,
66
67
  2
@@ -78,6 +79,10 @@ export class ShadowHandler {
78
79
  } else {
79
80
  appConfigUpdates.push({ projectId, newAppCfg });
80
81
  }
82
+ } else {
83
+ logger.warn(
84
+ `Ignoring shadow update for ${projectId} due to no app config`
85
+ );
81
86
  }
82
87
  }
83
88
  return appConfigUpdates;
@@ -96,6 +101,16 @@ export class ShadowHandler {
96
101
  const shadowName = topic.split('/')[5];
97
102
  switch (topic) {
98
103
  case this.shadowTopics.projects.updateDelta:
104
+ if (payload.clientToken === this.clientId) {
105
+ logger.debug(
106
+ `Ignoring message sent from self: ${JSON.stringify(
107
+ { topic, payload },
108
+ null,
109
+ 2
110
+ )}`
111
+ );
112
+ break;
113
+ }
99
114
  return await this.handleNamedShadowUpdate({ delta: payload });
100
115
  case this.shadowTopics.projects.getAccepted:
101
116
  if (payload['delta']) {
@@ -143,12 +158,18 @@ export class ShadowHandler {
143
158
  );
144
159
  }
145
160
 
146
- public deleteProjectShadow() {
161
+ public deleteProjectShadow(projectId: string) {
162
+ const packet = {
163
+ state: {
164
+ reported: {
165
+ [projectId]: null
166
+ }
167
+ },
168
+ clientToken: this.clientId
169
+ };
147
170
  this.publisher.publish(
148
- `${this.shadowTopics.projects.delete}`,
149
- JSON.stringify({
150
- clientToken: this.clientId
151
- })
171
+ this.shadowTopics.projects.update,
172
+ JSON.stringify(packet)
152
173
  );
153
174
  }
154
175
  }
@@ -8,8 +8,11 @@ export const ALWAYSAI_SHOW_HIDDEN = parseBoolean(
8
8
  );
9
9
  export const ALWAYSAI_DEVICE_AGENT_MODE =
10
10
  process.env.ALWAYSAI_DEVICE_AGENT_MODE;
11
- export const ALWAYSAI_LOG_LEVEL = process.env.AAI_LOG_LEVEL;
11
+ export const ALWAYSAI_LOG_LEVEL = process.env.ALWAYSAI_LOG_LEVEL;
12
12
  export const ALWAYSAI_LOG_TO_CONSOLE = process.env.ALWAYSAI_LOG_TO_CONSOLE;
13
+ export const ALWAYSAI_ANALYTICS_PASSTHROUGH = parseBoolean(
14
+ process.env.ALWAYSAI_ANALYTICS_PASSTHROUGH
15
+ );
13
16
 
14
17
  function parseOsPlatform(str: string | undefined): NodeJS.Platform {
15
18
  switch (str) {
package/src/index.ts CHANGED
@@ -19,8 +19,8 @@ if (module === require.main) {
19
19
  }
20
20
 
21
21
  if (ALWAYSAI_DEVICE_AGENT_MODE === 'cloud') {
22
- runDeviceAgentCloudInterface();
22
+ void runDeviceAgentCloudInterface();
23
23
  } else {
24
- runCliAndExit(root, {});
24
+ void runCliAndExit(root, {});
25
25
  }
26
26
  }
@@ -1,13 +1,6 @@
1
1
  import * as tempy from 'tempy';
2
2
  import { AgentConfigFile } from './agent-config';
3
3
 
4
- const mockAgentModeGetter = jest.fn().mockReturnValue(undefined);
5
- jest.mock('../environment', () => ({
6
- get ALWAYSAI_DEVICE_AGENT_MODE() {
7
- return mockAgentModeGetter();
8
- }
9
- }));
10
-
11
4
  const configFile = AgentConfigFile(tempy.directory());
12
5
 
13
6
  describe('Test Agent Config', () => {
@@ -1,4 +1,7 @@
1
- import { ConfigFileSchema } from '@alwaysai/config-nodejs';
1
+ import {
2
+ ConfigFileSchema,
3
+ ConfigFileSchemaReturnType
4
+ } from '@alwaysai/config-nodejs';
2
5
  import Ajv, { JSONSchemaType } from 'ajv';
3
6
  import { homedir } from 'os';
4
7
  import { join } from 'path';
@@ -55,7 +58,26 @@ const ALWAYSAI_CONFIG_DIR = join(homedir(), '.config', 'alwaysai');
55
58
 
56
59
  const AGENT_CONFIG_FILE_NAME = 'alwaysai.agent.json';
57
60
 
58
- export function AgentConfigFile(dir = ALWAYSAI_CONFIG_DIR) {
61
+ export interface AgentJsonFileReturnType
62
+ extends ConfigFileSchemaReturnType<AgentConfig> {
63
+ name: string;
64
+ getApps: () => Promise<InstalledAppConfig[]>;
65
+ getReadyApps;
66
+ getApp;
67
+ isAppPresent;
68
+ isAppReady;
69
+ removeApp;
70
+ setAppInstalling;
71
+ setAppInstalled;
72
+ setAppUninstalled;
73
+ getAppVersion;
74
+ setAppBackup;
75
+ getAppBackup;
76
+ }
77
+
78
+ export function AgentConfigFile(
79
+ dir = ALWAYSAI_CONFIG_DIR
80
+ ): AgentJsonFileReturnType {
59
81
  const path = join(dir, AGENT_CONFIG_FILE_NAME);
60
82
  const initialValue: AgentConfig = {
61
83
  applications: []
@@ -1,6 +1,6 @@
1
+ import { SystemId } from 'alwaysai/lib/constants';
1
2
  import { DeviceConfigFile } from 'alwaysai/lib/core/device';
2
3
  import { ALWAYSAI_SYSTEM_ID } from 'alwaysai/lib/environment';
3
- import { SystemId } from 'alwaysai/lib/infrastructure';
4
4
 
5
5
  export function getSystemId() {
6
6
  if (ALWAYSAI_SYSTEM_ID) {
@@ -1,23 +1,18 @@
1
- import {
2
- DeviceTokens,
3
- writeOrValidateDeviceCfgFile
4
- } from 'alwaysai/lib/components/device';
5
- import { checkUserIsLoggedInComponent } from 'alwaysai/lib/components/user';
6
1
  import { LOCAL_AAI_CFG_DIR } from 'alwaysai/lib/constants';
7
- import { checkPaidPlan } from 'alwaysai/lib/core/project';
8
- import { JsSpawner, writeTokens } from 'alwaysai/lib/util';
9
2
  import { logger } from '../util/logger';
10
3
  import { microServiceHttpClient } from '../util/http-client';
4
+ import { requireLoggedInAndPaidPlan } from '../util/require-logged-in-and-paid-plan';
5
+ import {
6
+ DeviceTokens,
7
+ writeOrValidateDeviceCfgFile,
8
+ writeTokens
9
+ } from 'alwaysai/lib/core/device';
10
+ import { JsSpawner } from 'alwaysai/lib/util';
11
11
 
12
12
  // NOTE: This closely follows the flow of deviceCheckAndUpdateComponent in the CLI
13
13
  export async function writeTokenAndDeviceCfg(props: { deviceUuid: string }) {
14
14
  const { deviceUuid } = props;
15
- await checkUserIsLoggedInComponent({ yes: true });
16
- if (!(await checkPaidPlan())) {
17
- throw new Error(
18
- `This action only supported for Enterprise alwaysAI accounts!`
19
- );
20
- }
15
+ await requireLoggedInAndPaidPlan();
21
16
  const { accessToken, refreshToken, idToken } = await microServiceHttpClient(
22
17
  'token-service',
23
18
  'create-device-tokens',
@@ -0,0 +1,53 @@
1
+ import { JsSpawner } from 'alwaysai/lib/util';
2
+ import { logger } from '../util/logger';
3
+ import sleep from '../util/sleep';
4
+
5
+ export async function checkRabbitMQContainerRunning() {
6
+ const spawner = JsSpawner();
7
+ return await spawner.run({
8
+ exe: 'docker',
9
+ args: ['ps', '-q', '--filter', 'ancestor=rabbitmq']
10
+ });
11
+ }
12
+
13
+ export async function setupRabbitMQContainer() {
14
+ const spawner = JsSpawner();
15
+ const rabbitMqContainerRunning = await checkRabbitMQContainerRunning();
16
+ logger.debug('Checking for running RabbitMQ container');
17
+ if (!rabbitMqContainerRunning) {
18
+ logger.debug('Setting up RabbitMQ container');
19
+ await spawner.run({
20
+ exe: 'docker',
21
+ args: [
22
+ 'run',
23
+ '--rm',
24
+ '-p',
25
+ '5672:5672',
26
+ '-d',
27
+ '--hostname',
28
+ 'my-rabbit',
29
+ 'rabbitmq'
30
+ ]
31
+ });
32
+ await sleep(8000);
33
+ while (!(await checkRabbitMQContainerRunning())) {
34
+ await sleep(5000);
35
+ }
36
+ }
37
+ }
38
+
39
+ export async function stopRabbitMQContainer() {
40
+ const spawner = JsSpawner();
41
+ const rabbitMqContainer = await spawner.run({
42
+ exe: 'docker',
43
+ args: ['ps', '-q', '--filter', 'ancestor=rabbitmq']
44
+ });
45
+ if (!rabbitMqContainer) {
46
+ logger.debug('No RabbitMQ container running');
47
+ } else {
48
+ await spawner.run({
49
+ exe: 'docker',
50
+ args: ['stop', `${rabbitMqContainer}`]
51
+ });
52
+ }
53
+ }
@@ -4,15 +4,13 @@ import {
4
4
  CliStringInput,
5
5
  CliTerseError
6
6
  } from '@alwaysai/alwayscli';
7
+ import { ClientMessage, keyMirrors } from '@alwaysai/device-agent-schemas';
7
8
  import {
8
- addModel,
9
9
  getAllEnvs,
10
10
  getAppLogs,
11
11
  getAppModels,
12
12
  getAppStatus,
13
- installApp,
14
- listAppLatestRelease,
15
- listAppReleases,
13
+ readAppCfgFile,
16
14
  removeModel,
17
15
  replaceModels,
18
16
  restartApp,
@@ -20,11 +18,12 @@ import {
20
18
  setEnv,
21
19
  startApp,
22
20
  stopApp,
23
- uninstallApp,
24
21
  updateModels
25
22
  } from '../../application-control';
23
+ import { DeviceAgentCloudConnection } from '../../cloud-connection/device-agent-cloud-connection';
26
24
  import { AgentConfigFile } from '../../infrastructure/agent-config';
27
25
  import { logger } from '../../util/logger';
26
+ import sleep from '../../util/sleep';
28
27
 
29
28
  export const listAppsCliLeaf = CliLeaf({
30
29
  name: 'list',
@@ -45,10 +44,9 @@ export const listAppReleasesCliLeaf = CliLeaf({
45
44
  required: true
46
45
  })
47
46
  },
47
+ hidden: true,
48
48
  async action(_, opts) {
49
- const { project } = opts;
50
- const releaseHistory = await listAppReleases({ projectId: project });
51
- logger.info(releaseHistory);
49
+ throw new CliTerseError('This command is yet currently implemented!');
52
50
  }
53
51
  });
54
52
 
@@ -61,15 +59,9 @@ export const listAppLatestReleaseCliLeaf = CliLeaf({
61
59
  required: true
62
60
  })
63
61
  },
62
+ hidden: true,
64
63
  async action(_, opts) {
65
- const { project } = opts;
66
- const latestReleaseHash = await listAppLatestRelease({
67
- projectId: project
68
- });
69
- if (latestReleaseHash === undefined) {
70
- throw new CliTerseError('This application has not been published yet');
71
- }
72
- logger.info(latestReleaseHash);
64
+ throw new CliTerseError('This command is yet currently implemented!');
73
65
  }
74
66
  });
75
67
 
@@ -82,19 +74,32 @@ export const installAppCliLeaf = CliLeaf({
82
74
  required: true
83
75
  }),
84
76
  releaseHash: CliStringInput({
85
- description: 'Release Hash'
77
+ description: 'Release Hash',
78
+ required: true
86
79
  })
87
80
  },
88
81
  async action(_, opts) {
89
- const project = opts.project;
90
- let releaseHash = opts.releaseHash;
91
- if (releaseHash === undefined) {
92
- releaseHash = await listAppLatestRelease({ projectId: project });
82
+ const { project, releaseHash } = opts;
83
+ const deviceAgent = new DeviceAgentCloudConnection();
84
+ await deviceAgent.setupHandlers();
85
+ const topic = deviceAgent.getToDeviceTopic();
86
+ const message: ClientMessage = {
87
+ timestamp: '',
88
+ topic,
89
+ payload: {
90
+ messageType: keyMirrors.clientMessageType.app_version_control,
91
+ appVersionControl: {
92
+ baseCommand: keyMirrors.appVersionControl.install,
93
+ projectId: project,
94
+ appReleaseHash: releaseHash
95
+ }
96
+ }
97
+ };
98
+ await deviceAgent.handleMessage(topic, message);
99
+ while (deviceAgent.isCmdInProgress(project)) {
100
+ await sleep(1000);
93
101
  }
94
- if (releaseHash === undefined) {
95
- throw new CliTerseError('This application has not been published yet');
96
- }
97
- await installApp({ projectId: project, appReleaseHash: releaseHash });
102
+ deviceAgent.stop();
98
103
  }
99
104
  });
100
105
 
@@ -110,7 +115,7 @@ export const getAppStatusCliLeaf = CliLeaf({
110
115
  async action(_, opts) {
111
116
  const { project } = opts;
112
117
  const appStatus = await getAppStatus({ projectId: project });
113
- logger.info(appStatus);
118
+ logger.info(JSON.stringify(appStatus, null, 2));
114
119
  }
115
120
  });
116
121
 
@@ -192,7 +197,25 @@ export const uninstallAppCliLeaf = CliLeaf({
192
197
  },
193
198
  async action(_, opts) {
194
199
  const { project } = opts;
195
- await uninstallApp({ projectId: project });
200
+ const deviceAgent = new DeviceAgentCloudConnection();
201
+ await deviceAgent.setupHandlers();
202
+ const topic = deviceAgent.getToDeviceTopic();
203
+ const message: ClientMessage = {
204
+ timestamp: '',
205
+ topic,
206
+ payload: {
207
+ messageType: keyMirrors.clientMessageType.app_version_control,
208
+ appVersionControl: {
209
+ baseCommand: keyMirrors.appVersionControl.uninstall,
210
+ projectId: project
211
+ }
212
+ }
213
+ };
214
+ await deviceAgent.handleMessage(topic, message);
215
+ while (deviceAgent.isCmdInProgress(project)) {
216
+ await sleep(1000);
217
+ }
218
+ deviceAgent.stop();
196
219
  }
197
220
  });
198
221
 
@@ -205,6 +228,7 @@ export const rollbackAppCliLeaf = CliLeaf({
205
228
  required: true
206
229
  })
207
230
  },
231
+ hidden: true,
208
232
  async action(_, opts) {
209
233
  const { project } = opts;
210
234
  await rollbackApp({ projectId: project });
@@ -238,11 +262,38 @@ export const addModelCliLeaf = CliLeaf({
238
262
  model: CliStringInput({
239
263
  description: 'Model ID',
240
264
  required: true
265
+ }),
266
+ version: CliStringInput({
267
+ description: 'Model version',
268
+ required: true
241
269
  })
242
270
  },
243
271
  async action(_, opts) {
244
- const { project, model } = opts;
245
- await addModel({ projectId: project, modelId: model });
272
+ const { project, model, version } = opts;
273
+ const deviceAgent = new DeviceAgentCloudConnection();
274
+ await deviceAgent.setupHandlers();
275
+
276
+ const topic = deviceAgent.getShadowTopics().projects.updateDelta;
277
+
278
+ const newAppCfg = await readAppCfgFile({ projectId: project });
279
+ newAppCfg.models[model] = Number(version);
280
+
281
+ const message = {
282
+ version: 3,
283
+ timestamp: 0,
284
+ state: {
285
+ [project]: {
286
+ appConfig: JSON.stringify(newAppCfg)
287
+ }
288
+ },
289
+ clientToken: 'not-self'
290
+ };
291
+
292
+ await deviceAgent.handleMessage(topic, message);
293
+ while (deviceAgent.isCmdInProgress(project)) {
294
+ await sleep(1000);
295
+ }
296
+ deviceAgent.stop();
246
297
  }
247
298
  });
248
299
 
@@ -299,7 +350,7 @@ export const updateModelsCliLeaf = CliLeaf({
299
350
  }
300
351
  });
301
352
 
302
- export const getAllEnvsCLiLeaf = CliLeaf({
353
+ export const getAllEnvsCliLeaf = CliLeaf({
303
354
  name: 'get-all-envs',
304
355
  description: 'Get environment variables for an application',
305
356
  namedInputs: {
@@ -314,7 +365,7 @@ export const getAllEnvsCLiLeaf = CliLeaf({
314
365
  }
315
366
  });
316
367
 
317
- export const setEnvCLiLeaf = CliLeaf({
368
+ export const setEnvCliLeaf = CliLeaf({
318
369
  name: 'set-env',
319
370
  description: 'Set environment variables for a service',
320
371
  positionalInput: CliStringArrayInput({
@@ -16,8 +16,8 @@ import {
16
16
  restartAppCliLeaf,
17
17
  replaceModelsCliLeaf,
18
18
  showAppModelsCliLeaf,
19
- getAllEnvsCLiLeaf,
20
- setEnvCLiLeaf
19
+ getAllEnvsCliLeaf,
20
+ setEnvCliLeaf
21
21
  } from './app';
22
22
 
23
23
  export const appCliBranch = CliBranch({
@@ -40,7 +40,7 @@ export const appCliBranch = CliBranch({
40
40
  removeModelCliLeaf,
41
41
  replaceModelsCliLeaf,
42
42
  updateModelsCliLeaf,
43
- getAllEnvsCLiLeaf,
44
- setEnvCLiLeaf
43
+ getAllEnvsCliLeaf,
44
+ setEnvCliLeaf
45
45
  ]
46
46
  });
@@ -1,5 +1,5 @@
1
1
  import { CliLeaf } from '@alwaysai/alwayscli';
2
- import * as rimraf from 'rimraf';
2
+ import { rimraf } from 'rimraf';
3
3
  import { LOCAL_CERT_AND_KEY_DIR } from 'alwaysai/lib/constants';
4
4
  import { logger } from '../../util/logger';
5
5
  import { AgentConfigFile } from '../../infrastructure/agent-config';
@@ -12,15 +12,15 @@ export const cleanCliLeaf = CliLeaf({
12
12
  async action(_, opts) {
13
13
  logger.info('Cleaning device configuration');
14
14
  logger.debug(`Removing ${LOCAL_CERT_AND_KEY_DIR}`);
15
- rimraf.sync(LOCAL_CERT_AND_KEY_DIR);
15
+ await rimraf(LOCAL_CERT_AND_KEY_DIR);
16
16
  logger.debug(`Removing ${AgentConfigFile().path}`);
17
17
  AgentConfigFile().remove();
18
18
  logger.debug(`Removing ${DeviceConfigFile().path}`);
19
19
  DeviceConfigFile().remove();
20
20
  logger.debug(`Removing ${CREDENTIALS_FILE_PATH}`);
21
- rimraf.sync(CREDENTIALS_FILE_PATH);
21
+ await rimraf(CREDENTIALS_FILE_PATH);
22
22
  logger.debug(`Removing ${APP_ROOT}`);
23
- rimraf.sync(APP_ROOT);
23
+ await rimraf(APP_ROOT);
24
24
  logger.info('Device configuration cleaned');
25
25
  }
26
26
  });
@@ -1,7 +1,5 @@
1
1
  import { CliLeaf, CliStringInput } from '@alwaysai/alwayscli';
2
- import { checkUserIsLoggedInComponent } from 'alwaysai/lib/components/user';
3
2
  import { getTargetHardwareUuid } from 'alwaysai/lib/core/app';
4
- import { checkPaidPlan } from 'alwaysai/lib/core/project';
5
3
  import { v4 as uuidv4 } from 'uuid';
6
4
  import { CliAuthenticationClient } from 'alwaysai/lib/infrastructure';
7
5
  import { existsSync } from 'fs';
@@ -9,7 +7,7 @@ import { httpClient, microServiceHttpClient } from '../../util/http-client';
9
7
  import {
10
8
  AWS_ROOT_CERTIFICATE_FILE_NAME,
11
9
  BOOTSTRAP_CERTIFICATES_DIR_PATH,
12
- getPrivateKeyFilePath
10
+ DEVICE_PRIVATE_KEY_FILE_PATH
13
11
  } from '../../util/directories';
14
12
 
15
13
  import { LOCAL_CERT_AND_KEY_DIR } from 'alwaysai/lib/constants';
@@ -22,6 +20,7 @@ import {
22
20
  import { writeTokenAndDeviceCfg } from '../../infrastructure/tokens-and-device-cfg';
23
21
  import { logger } from '../../util/logger';
24
22
  import { DeviceConfigFile } from 'alwaysai/lib/core/device';
23
+ import { requireLoggedInAndPaidPlan } from '../../util/require-logged-in-and-paid-plan';
25
24
 
26
25
  export const initCliLeaf = CliLeaf({
27
26
  name: 'init',
@@ -29,7 +28,7 @@ export const initCliLeaf = CliLeaf({
29
28
  namedInputs: {
30
29
  name: CliStringInput({
31
30
  description: 'Device name',
32
- required: true
31
+ required: false
33
32
  }),
34
33
  description: CliStringInput({
35
34
  description: 'Device description',
@@ -37,19 +36,19 @@ export const initCliLeaf = CliLeaf({
37
36
  })
38
37
  },
39
38
  async action(_, opts) {
40
- const { name, description } = opts;
41
- if (DeviceConfigFile().exists() || existsSync(getPrivateKeyFilePath())) {
39
+ let { name } = opts;
40
+ name = name || uuidv4();
41
+
42
+ if (
43
+ DeviceConfigFile().exists() ||
44
+ existsSync(DEVICE_PRIVATE_KEY_FILE_PATH)
45
+ ) {
42
46
  throw new Error(
43
47
  "Device has been previously provisioned. Run 'aai-agent device clean' to re-provision"
44
48
  );
45
49
  }
46
50
  logger.info(`Initializing device ${name}`);
47
- await checkUserIsLoggedInComponent({ yes: true });
48
- if (!(await checkPaidPlan())) {
49
- throw new Error(
50
- `This action only supported for Enterprise alwaysAI accounts!`
51
- );
52
- }
51
+ await requireLoggedInAndPaidPlan();
53
52
  const { username } = await CliAuthenticationClient().getInfo();
54
53
  const hardwareId = await getTargetHardwareUuid(JsSpawner());
55
54
  const device = {
@@ -60,6 +59,7 @@ export const initCliLeaf = CliLeaf({
60
59
  friendlyName: name
61
60
  };
62
61
 
62
+ // FIXME: Use JSON schema to validate input
63
63
  const response: { deviceUuid: string; claimCertificate: string[] } =
64
64
  await microServiceHttpClient(
65
65
  'fleet-provision',
@@ -69,7 +69,7 @@ export const initCliLeaf = CliLeaf({
69
69
  );
70
70
  logger.debug(`addDevice Response: ${JSON.stringify(response, null, 2)}`);
71
71
 
72
- if (!Object.hasOwn(response, 'claimCertificate')) {
72
+ if (!('claimCertificate' in response)) {
73
73
  throw new Error(
74
74
  "Device cannot be provisioned. Run 'aai-agent device clean' and retry."
75
75
  );
@@ -1,6 +1,5 @@
1
1
  import { CliLeaf, CliStringInput } from '@alwaysai/alwayscli';
2
2
  import { alwaysaiUserLoginYesComponent } from 'alwaysai/lib/components/user';
3
- import { writeTokenAndDeviceCfg } from '../infrastructure/tokens-and-device-cfg';
4
3
 
5
4
  export const loginCliLeaf = CliLeaf({
6
5
  name: 'login',
@@ -13,21 +12,14 @@ export const loginCliLeaf = CliLeaf({
13
12
  password: CliStringInput({
14
13
  description: 'Account password',
15
14
  required: true
16
- }),
17
- device: CliStringInput({
18
- description: 'The device UUID',
19
- required: false
20
15
  })
21
16
  },
22
17
  async action(_, opts) {
23
- const { email, password, device } = opts;
18
+ const { email, password } = opts;
24
19
 
25
20
  await alwaysaiUserLoginYesComponent({
26
21
  alwaysaiUserEmail: email,
27
22
  alwaysaiUserPassword: password
28
23
  });
29
- if (device) {
30
- await writeTokenAndDeviceCfg({ deviceUuid: device });
31
- }
32
24
  }
33
25
  });
@@ -33,52 +33,54 @@ export const shortenSystemId = () => {
33
33
  }
34
34
  };
35
35
 
36
+ /*===================================================================
37
+ Bootstrap Certificates
38
+ ===================================================================*/
39
+
36
40
  export const BOOTSTRAP_DIR_NAME = 'bootstrap-certificates';
37
- export const BOOTSTRAP_DEVICE_PRIVATE_KEY_FILE_NAME = () =>
41
+ export const BOOTSTRAP_PRIVATE_KEY_FILE_NAME = () =>
38
42
  `aai-claim-private-key_${shortenSystemId()}.pem.key`;
39
- export const BOOTSTRAP_DEVICE_CERTIFICATE_FILE_NAME = () =>
43
+ export const BOOTSTRAP_CERTIFICATE_FILE_NAME = () =>
40
44
  `aai-claim-cert_${shortenSystemId()}.pem.crt`;
41
- export const BOOTSTRAP_CLAIM_ID_FILE_NAME = () =>
45
+ export const BOOTSTRAP_ID_FILE_NAME = () =>
42
46
  `aai-claim-cert-id_${shortenSystemId()}.txt`;
43
47
  export const CERTIFICATE_OWNERSHIP_TOKEN_FILE_NAME =
44
48
  'certificate-ownership-token.txt';
45
- export const DEVICE_PRIVATE_KEY_FILE_NAME = () =>
46
- `aai-claim-private-key_${shortenSystemId()}.pem.key`;
47
- export const DEVICE_CERTIFICATE_FILE_NAME = () =>
48
- `aai-claim-cert_${shortenSystemId()}.pem.crt`;
49
- export const DEVICE_CLAIM_ID_FILE_NAME = () =>
50
- `aai-claim-cert-id_${shortenSystemId()}.txt`;
51
-
52
- export function getPrivateKeyFilePath() {
53
- return join(LOCAL_CERT_AND_KEY_DIR, DEVICE_PRIVATE_KEY_FILE_NAME());
54
- }
55
-
56
- export function getCertificateFilePath() {
57
- return join(LOCAL_CERT_AND_KEY_DIR, DEVICE_CERTIFICATE_FILE_NAME());
58
- }
59
49
 
60
50
  export const BOOTSTRAP_CERTIFICATES_DIR_PATH = () =>
61
51
  join(LOCAL_CERT_AND_KEY_DIR, BOOTSTRAP_DIR_NAME);
62
- export const DEVICE_CLAIM_ID_FILE_PATH = () =>
63
- join(LOCAL_CERT_AND_KEY_DIR, DEVICE_TOKEN_FILE_NAME);
64
52
 
65
- export const BOOTSTRAP_DEVICE_PRIVATE_KEY_FILE_PATH = () =>
53
+ export const BOOTSTRAP_PRIVATE_KEY_FILE_PATH = () =>
66
54
  join(
67
55
  LOCAL_CERT_AND_KEY_DIR,
68
56
  BOOTSTRAP_DIR_NAME,
69
- BOOTSTRAP_DEVICE_PRIVATE_KEY_FILE_NAME()
57
+ BOOTSTRAP_PRIVATE_KEY_FILE_NAME()
70
58
  );
71
59
 
72
- export const BOOTSTRAP_DEVICE_CERTIFICATE_FILE_PATH = () =>
60
+ export const BOOTSTRAP_CERTIFICATE_FILE_PATH = () =>
73
61
  join(
74
62
  LOCAL_CERT_AND_KEY_DIR,
75
63
  BOOTSTRAP_DIR_NAME,
76
- BOOTSTRAP_DEVICE_CERTIFICATE_FILE_NAME()
64
+ BOOTSTRAP_CERTIFICATE_FILE_NAME()
77
65
  );
78
66
 
79
- export const BOOTSTRAP_CLAIM_ID_FILE_PATH = () =>
80
- join(
81
- LOCAL_CERT_AND_KEY_DIR,
82
- BOOTSTRAP_DIR_NAME,
83
- BOOTSTRAP_CLAIM_ID_FILE_NAME()
84
- );
67
+ export const BOOTSTRAP_ID_FILE_PATH = () =>
68
+ join(LOCAL_CERT_AND_KEY_DIR, BOOTSTRAP_DIR_NAME, BOOTSTRAP_ID_FILE_NAME());
69
+
70
+ /*===================================================================
71
+ Device Certificates
72
+ ===================================================================*/
73
+
74
+ export const DEVICE_PRIVATE_KEY_FILE_NAME = 'aai-device-private-key.pem.key';
75
+ export const DEVICE_CERTIFICATE_FILE_NAME = 'aai-device-cert.pem.crt';
76
+ export const DEVICE_ID_FILE_NAME = 'aai-device-cert-id.txt';
77
+
78
+ export const DEVICE_PRIVATE_KEY_FILE_PATH = join(
79
+ LOCAL_CERT_AND_KEY_DIR,
80
+ DEVICE_PRIVATE_KEY_FILE_NAME
81
+ );
82
+
83
+ export const DEVICE_CERTIFICATE_FILE_PATH = join(
84
+ LOCAL_CERT_AND_KEY_DIR,
85
+ DEVICE_CERTIFICATE_FILE_NAME
86
+ );