@alwaysai/device-agent 0.0.2

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/application-control.d.ts +41 -0
  2. package/lib/application-control/application-control.d.ts.map +1 -0
  3. package/lib/application-control/application-control.js +197 -0
  4. package/lib/application-control/application-control.js.map +1 -0
  5. package/lib/application-control/models.d.ts +12 -0
  6. package/lib/application-control/models.d.ts.map +1 -0
  7. package/lib/application-control/models.js +44 -0
  8. package/lib/application-control/models.js.map +1 -0
  9. package/lib/application-control/utils.d.ts +16 -0
  10. package/lib/application-control/utils.d.ts.map +1 -0
  11. package/lib/application-control/utils.js +62 -0
  12. package/lib/application-control/utils.js.map +1 -0
  13. package/lib/constants.d.ts +15 -0
  14. package/lib/constants.d.ts.map +1 -0
  15. package/lib/constants.js +22 -0
  16. package/lib/constants.js.map +1 -0
  17. package/lib/device-control/device-control.d.ts +4 -0
  18. package/lib/device-control/device-control.d.ts.map +1 -0
  19. package/lib/device-control/device-control.js +20 -0
  20. package/lib/device-control/device-control.js.map +1 -0
  21. package/lib/docker/docker-cmd.d.ts +4 -0
  22. package/lib/docker/docker-cmd.d.ts.map +1 -0
  23. package/lib/docker/docker-cmd.js +16 -0
  24. package/lib/docker/docker-cmd.js.map +1 -0
  25. package/lib/docker/docker-compose-cmd.d.ts +5 -0
  26. package/lib/docker/docker-compose-cmd.d.ts.map +1 -0
  27. package/lib/docker/docker-compose-cmd.js +16 -0
  28. package/lib/docker/docker-compose-cmd.js.map +1 -0
  29. package/lib/environment.d.ts +4 -0
  30. package/lib/environment.d.ts.map +1 -0
  31. package/lib/environment.js +21 -0
  32. package/lib/environment.js.map +1 -0
  33. package/lib/index.d.ts +3 -0
  34. package/lib/index.d.ts.map +1 -0
  35. package/lib/index.js +19 -0
  36. package/lib/index.js.map +1 -0
  37. package/lib/root.d.ts +2 -0
  38. package/lib/root.d.ts.map +1 -0
  39. package/lib/root.js +18 -0
  40. package/lib/root.js.map +1 -0
  41. package/lib/subcommands/app/app.d.ts +36 -0
  42. package/lib/subcommands/app/app.d.ts.map +1 -0
  43. package/lib/subcommands/app/app.js +218 -0
  44. package/lib/subcommands/app/app.js.map +1 -0
  45. package/lib/subcommands/app/index.d.ts +2 -0
  46. package/lib/subcommands/app/index.d.ts.map +1 -0
  47. package/lib/subcommands/app/index.js +23 -0
  48. package/lib/subcommands/app/index.js.map +1 -0
  49. package/lib/subcommands/device-control.d.ts +2 -0
  50. package/lib/subcommands/device-control.d.ts.map +1 -0
  51. package/lib/subcommands/device-control.js +19 -0
  52. package/lib/subcommands/device-control.js.map +1 -0
  53. package/lib/subcommands/index.d.ts +2 -0
  54. package/lib/subcommands/index.d.ts.map +1 -0
  55. package/lib/subcommands/index.js +14 -0
  56. package/lib/subcommands/index.js.map +1 -0
  57. package/lib/subcommands/login.d.ts +5 -0
  58. package/lib/subcommands/login.d.ts.map +1 -0
  59. package/lib/subcommands/login.js +27 -0
  60. package/lib/subcommands/login.js.map +1 -0
  61. package/lib/subcommands/test-app.d.ts +5 -0
  62. package/lib/subcommands/test-app.d.ts.map +1 -0
  63. package/lib/subcommands/test-app.js +33 -0
  64. package/lib/subcommands/test-app.js.map +1 -0
  65. package/lib/util/copy-dir.d.ts +5 -0
  66. package/lib/util/copy-dir.d.ts.map +1 -0
  67. package/lib/util/copy-dir.js +14 -0
  68. package/lib/util/copy-dir.js.map +1 -0
  69. package/lib/util/run-cli-cmd.d.ts +5 -0
  70. package/lib/util/run-cli-cmd.d.ts.map +1 -0
  71. package/lib/util/run-cli-cmd.js +24 -0
  72. package/lib/util/run-cli-cmd.js.map +1 -0
  73. package/lib/util/spawner/gnu-spawner.d.ts +9 -0
  74. package/lib/util/spawner/gnu-spawner.d.ts.map +1 -0
  75. package/lib/util/spawner/gnu-spawner.js +102 -0
  76. package/lib/util/spawner/gnu-spawner.js.map +1 -0
  77. package/lib/util/spawner/js-spawner.d.ts +5 -0
  78. package/lib/util/spawner/js-spawner.d.ts.map +1 -0
  79. package/lib/util/spawner/js-spawner.js +89 -0
  80. package/lib/util/spawner/js-spawner.js.map +1 -0
  81. package/lib/util/spawner/types.d.ts +28 -0
  82. package/lib/util/spawner/types.d.ts.map +1 -0
  83. package/lib/util/spawner/types.js +3 -0
  84. package/lib/util/spawner/types.js.map +1 -0
  85. package/lib/util/spawner-base/index.d.ts +17 -0
  86. package/lib/util/spawner-base/index.d.ts.map +1 -0
  87. package/lib/util/spawner-base/index.js +30 -0
  88. package/lib/util/spawner-base/index.js.map +1 -0
  89. package/lib/util/spawner-base/run-foreground-sync.d.ts +3 -0
  90. package/lib/util/spawner-base/run-foreground-sync.d.ts.map +1 -0
  91. package/lib/util/spawner-base/run-foreground-sync.js +18 -0
  92. package/lib/util/spawner-base/run-foreground-sync.js.map +1 -0
  93. package/lib/util/spawner-base/run-foreground.d.ts +3 -0
  94. package/lib/util/spawner-base/run-foreground.d.ts.map +1 -0
  95. package/lib/util/spawner-base/run-foreground.js +49 -0
  96. package/lib/util/spawner-base/run-foreground.js.map +1 -0
  97. package/lib/util/spawner-base/run-streaming.d.ts +4 -0
  98. package/lib/util/spawner-base/run-streaming.d.ts.map +1 -0
  99. package/lib/util/spawner-base/run-streaming.js +35 -0
  100. package/lib/util/spawner-base/run-streaming.js.map +1 -0
  101. package/lib/util/spawner-base/run.d.ts +4 -0
  102. package/lib/util/spawner-base/run.d.ts.map +1 -0
  103. package/lib/util/spawner-base/run.js +56 -0
  104. package/lib/util/spawner-base/run.js.map +1 -0
  105. package/lib/web/index.html +229 -0
  106. package/lib/web/static/Karla.css +18 -0
  107. package/lib/web/static/bootstrap-4.3.1.min.css +7 -0
  108. package/lib/web/static/bootstrap-4.3.1.min.js +7 -0
  109. package/lib/web/static/favicon.ico +0 -0
  110. package/lib/web/static/jquery-3.3.1.slim.min.js +2 -0
  111. package/lib/web/static/popper-1.14.7.min.js +5 -0
  112. package/lib/web/web-interface.d.ts +2 -0
  113. package/lib/web/web-interface.d.ts.map +1 -0
  114. package/lib/web/web-interface.js +74 -0
  115. package/lib/web/web-interface.js.map +1 -0
  116. package/package.json +89 -0
  117. package/readme.md +104 -0
  118. package/src/application-control/application-control.ts +233 -0
  119. package/src/application-control/models.ts +40 -0
  120. package/src/application-control/utils.ts +64 -0
  121. package/src/constants.ts +29 -0
  122. package/src/device-control/device-control.ts +16 -0
  123. package/src/docker/docker-cmd.ts +12 -0
  124. package/src/docker/docker-compose-cmd.ts +12 -0
  125. package/src/environment.ts +20 -0
  126. package/src/index.ts +18 -0
  127. package/src/root.ts +18 -0
  128. package/src/subcommands/app/app.ts +216 -0
  129. package/src/subcommands/app/index.ts +32 -0
  130. package/src/subcommands/device-control.ts +16 -0
  131. package/src/subcommands/index.ts +11 -0
  132. package/src/subcommands/login.ts +24 -0
  133. package/src/subcommands/test-app.ts +30 -0
  134. package/src/util/copy-dir.ts +10 -0
  135. package/src/util/run-cli-cmd.ts +18 -0
  136. package/src/util/spawner/gnu-spawner.ts +114 -0
  137. package/src/util/spawner/js-spawner.ts +110 -0
  138. package/src/util/spawner/types.ts +28 -0
  139. package/src/util/spawner-base/index.ts +28 -0
  140. package/src/util/spawner-base/run-foreground-sync.ts +16 -0
  141. package/src/util/spawner-base/run-foreground.ts +49 -0
  142. package/src/util/spawner-base/run-streaming.ts +40 -0
  143. package/src/util/spawner-base/run.ts +60 -0
  144. package/src/web/index.html +229 -0
  145. package/src/web/static/Karla.css +18 -0
  146. package/src/web/static/bootstrap-4.3.1.min.css +7 -0
  147. package/src/web/static/bootstrap-4.3.1.min.js +7 -0
  148. package/src/web/static/favicon.ico +0 -0
  149. package/src/web/static/jquery-3.3.1.slim.min.js +2 -0
  150. package/src/web/static/popper-1.14.7.min.js +5 -0
  151. package/src/web/web-interface.ts +89 -0
@@ -0,0 +1,233 @@
1
+ import compose from 'docker-compose';
2
+ import * as rimraf from 'rimraf';
3
+ import * as fs from 'fs';
4
+
5
+ import { runDockerLogin } from '../docker/docker-cmd';
6
+ import { JsSpawner } from '../util/spawner/js-spawner';
7
+ import { copyDir } from '../util/copy-dir';
8
+ import {
9
+ APP_ROOT,
10
+ buildApp,
11
+ getAppDir,
12
+ getAppVersion,
13
+ isAppInstalled,
14
+ setAppVersion,
15
+ } from './utils';
16
+
17
+ const BACKUP_EXT = '.bak';
18
+
19
+ export type AppDetails = { projectId: string; version: string };
20
+
21
+ export async function getInstalledApps(): Promise<AppDetails[]> {
22
+ const projectIds = fs.readdirSync(APP_ROOT).filter(file => {
23
+ const isDir = fs.statSync(`${APP_ROOT}/${file}`).isDirectory();
24
+ const isBak = `${APP_ROOT}/${file}`.includes(BACKUP_EXT);
25
+ return isDir && !isBak;
26
+ });
27
+
28
+ const appDetails: AppDetails[] = [];
29
+ for (const projectId of projectIds) {
30
+ const appDir = getAppDir(projectId);
31
+ const version = await getAppVersion({ appDir });
32
+ appDetails.push({ projectId, version });
33
+ }
34
+ return appDetails;
35
+ }
36
+
37
+ async function createAppBackup(props: { projectId: string }) {
38
+ const { projectId } = props;
39
+ const appDir = getAppDir(projectId);
40
+ const backupAppDir = `${appDir}${BACKUP_EXT}`;
41
+ await copyDir({ srcPath: appDir, destPath: backupAppDir });
42
+ console.log(`Backed up app ${projectId} to ${backupAppDir}`);
43
+ }
44
+
45
+ export async function rollbackApp(props: { projectId: string }) {
46
+ const { projectId } = props;
47
+ const appDir = getAppDir(projectId);
48
+ const backupAppDir = `${appDir}${BACKUP_EXT}`;
49
+ if (!fs.existsSync(backupAppDir)) {
50
+ throw new Error(`Backup doesn't exist for ${projectId}`);
51
+ }
52
+ rimraf.sync(appDir);
53
+ await copyDir({ srcPath: `${appDir}${BACKUP_EXT}`, destPath: appDir });
54
+ await buildApp({ appDir });
55
+ console.log(`Rolled back app ${projectId} to previous version`);
56
+ }
57
+
58
+ export async function installApp(props: {
59
+ projectId: string;
60
+ appPkg: NodeJS.ReadableStream;
61
+ version: string;
62
+ }): Promise<void> {
63
+ const { projectId, appPkg, version } = props;
64
+ console.log(`Installing ${projectId}`);
65
+ const appDir = getAppDir(projectId);
66
+ const spawner = JsSpawner();
67
+ // TODO Check valid projectId
68
+ // Check if app is installed
69
+ if (await isAppInstalled({ appDir })) {
70
+ console.log('Application is already installed, updating');
71
+ await createAppBackup({ projectId });
72
+ await spawner.rimraf(appDir);
73
+ }
74
+ // Create app directory
75
+ await spawner.mkdirp(appDir);
76
+
77
+ // Unpack app package and move to install dir
78
+ await spawner.untar(appPkg, appDir);
79
+ console.log(`Unpacked application to ${appDir}`);
80
+
81
+ await setAppVersion({ appDir, version });
82
+
83
+ await buildApp({ appDir });
84
+
85
+ console.log(`Installed ${projectId} to ${appDir}`);
86
+ }
87
+
88
+ export type ServiceState = 'Stopped' | 'Up' | 'Restarting';
89
+ export type ServiceStatus = { name: string; state: ServiceState };
90
+ export type AppStatus = { appDetails: AppDetails; services: ServiceStatus[] };
91
+
92
+ export async function getAppStatus(props: { projectId: string }): Promise<AppStatus> {
93
+ const { projectId } = props;
94
+ const appDir = getAppDir(projectId);
95
+
96
+ if (!(await isAppInstalled({ appDir }))) {
97
+ throw new Error('Application is not installed');
98
+ }
99
+
100
+ // Check if app is running
101
+ const output = await compose.ps({ cwd: appDir });
102
+ if (output.exitCode !== 0) {
103
+ throw new Error(
104
+ `Failed to get application status! stdout=${output.out} stderr=${output.err}`,
105
+ );
106
+ }
107
+
108
+ const appDetails = { projectId, version: await getAppVersion({ appDir }) };
109
+ const services: ServiceStatus[] = [];
110
+ if (output.data.services.length === 0) {
111
+ // If the app has never been started (or has an empty docker-compose.yaml)
112
+ // the list will be empty.
113
+ services.push({ name: '', state: 'Stopped' });
114
+ } else {
115
+ for (const service of output.data.services) {
116
+ // Container name has the format:
117
+ // <projectId>_name_<number>
118
+ const matchProjectId = new RegExp(`^${projectId}`);
119
+ const name = service.name
120
+ .replace(matchProjectId, '')
121
+ .replace(/\d+$/, '')
122
+ .replace(/_/g, '');
123
+
124
+ let state: ServiceState;
125
+ if (service.state.includes('Up')) {
126
+ state = 'Up';
127
+ } else if (service.state.includes('Stopped') || service.state.includes('Exit')) {
128
+ state = 'Stopped';
129
+ } else if (service.state.includes('Restarting')) {
130
+ state = 'Restarting';
131
+ } else {
132
+ throw new Error(`Unknown state for service! (${service.state})`);
133
+ }
134
+ services.push({ name, state });
135
+ }
136
+ }
137
+ return { appDetails, services };
138
+ }
139
+
140
+ export async function getAppLogs(props: {
141
+ projectId: string;
142
+ services?: string[];
143
+ }): Promise<NodeJS.ReadableStream> {
144
+ const { projectId, services } = props;
145
+ const appDir = getAppDir(projectId);
146
+
147
+ if (!(await isAppInstalled({ appDir }))) {
148
+ throw new Error('Application is not installed');
149
+ }
150
+
151
+ const serviceList = services
152
+ ? services
153
+ : await (async function() {
154
+ const appStatus = await getAppStatus({ projectId });
155
+ const services: string[] = [];
156
+ for (const service of appStatus.services) {
157
+ services.push(service.name);
158
+ }
159
+ return services;
160
+ })();
161
+
162
+ // Use direct command with spawner in order to get a readable stream
163
+ return await JsSpawner().runStreaming({
164
+ exe: 'docker-compose',
165
+ args: ['logs', '-f', ...serviceList],
166
+ cwd: appDir,
167
+ });
168
+ }
169
+
170
+ export async function startApp(props: {
171
+ projectId: string;
172
+ dockerLoginToken?: string;
173
+ }): Promise<void> {
174
+ const { projectId, dockerLoginToken } = props;
175
+ const appDir = getAppDir(projectId);
176
+ // Check if app is installed
177
+ if (!(await isAppInstalled({ appDir }))) {
178
+ throw new Error('Application is not installed');
179
+ }
180
+ if (dockerLoginToken !== undefined) {
181
+ const result = await runDockerLogin({ token: dockerLoginToken });
182
+ console.log(result);
183
+ }
184
+
185
+ // TODO: Check if app is running
186
+ // Start app
187
+ const upOut = await compose.upAll({ cwd: appDir });
188
+ console.log(upOut);
189
+ if (upOut.exitCode !== 0) {
190
+ throw new Error(
191
+ `Failed to start application! stdout=${upOut.out} stderr=${upOut.err}`,
192
+ );
193
+ }
194
+ console.log('Started', projectId);
195
+ }
196
+
197
+ export async function stopApp(props: { projectId: string }): Promise<void> {
198
+ const { projectId } = props;
199
+ const appDir = getAppDir(projectId);
200
+ // Check if app is installed
201
+ if (!(await isAppInstalled({ appDir }))) {
202
+ console.log('Application is not installed');
203
+ return;
204
+ }
205
+ // TODO: Check if app is running
206
+ // Stop app
207
+ const output = await compose.down({ cwd: appDir });
208
+ console.log(output);
209
+ if (output.exitCode !== 0) {
210
+ throw new Error(
211
+ `Failed to stop application! stdout=${output.out} stderr=${output.err}`,
212
+ );
213
+ }
214
+ console.log('Stopped', projectId);
215
+ }
216
+
217
+ export async function uninstallApp(props: { projectId: string }): Promise<void> {
218
+ const { projectId } = props;
219
+ const appDir = getAppDir(projectId);
220
+ if (!(await isAppInstalled({ appDir }))) {
221
+ console.log(`Application ${projectId} not installed`);
222
+ return;
223
+ }
224
+ try {
225
+ await stopApp({ projectId });
226
+ } catch {
227
+ console.log('Failed to stop app, may be left running...');
228
+ }
229
+ // Delete application directory and backup
230
+ rimraf.sync(appDir);
231
+ rimraf.sync(`${appDir}${BACKUP_EXT}`);
232
+ console.log('Uninstalled', projectId);
233
+ }
@@ -0,0 +1,40 @@
1
+ import { runCliCmd } from '../util/run-cli-cmd';
2
+ import { buildApp, getAppDir, isAppInstalled } from './utils';
3
+
4
+ export async function addModel(props: { projectId: string; modelId: string }) {
5
+ const { projectId, modelId } = props;
6
+ const appDir = getAppDir(projectId);
7
+ if (!(await isAppInstalled({ appDir }))) {
8
+ throw new Error('Application is not installed');
9
+ }
10
+ await runCliCmd({
11
+ cmd: ['app', 'models', 'add', modelId],
12
+ cwd: appDir,
13
+ });
14
+ await buildApp({ appDir });
15
+ }
16
+
17
+ export async function removeModel(props: { projectId: string; modelId: string }) {
18
+ const { projectId, modelId } = props;
19
+ const appDir = getAppDir(projectId);
20
+ if (!(await isAppInstalled({ appDir }))) {
21
+ throw new Error('Application is not installed');
22
+ }
23
+ await runCliCmd({
24
+ cmd: ['app', 'models', 'remove', modelId],
25
+ cwd: appDir,
26
+ });
27
+ }
28
+
29
+ export async function updateModels(props: { projectId: string }) {
30
+ const { projectId } = props;
31
+ const appDir = getAppDir(projectId);
32
+ if (!(await isAppInstalled({ appDir }))) {
33
+ throw new Error('Application is not installed');
34
+ }
35
+ await runCliCmd({
36
+ cmd: ['app', 'models', 'update'],
37
+ cwd: appDir,
38
+ });
39
+ await buildApp({ appDir });
40
+ }
@@ -0,0 +1,64 @@
1
+ import compose from 'docker-compose';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs';
4
+
5
+ import { JsSpawner } from '../util/spawner/js-spawner';
6
+ import { DOT_ALWAYSAI_DIR } from '../constants';
7
+ import { runCliCmd } from '../util/run-cli-cmd';
8
+
9
+ export const APP_ROOT = path.join(DOT_ALWAYSAI_DIR, 'applications');
10
+ const RELEASE_FILENAME = 'alwaysai.release.json';
11
+ const DOCKER_COMPOSE_FILENAME = 'docker-compose.yaml';
12
+
13
+ export function getAppDir(projectId: string): string {
14
+ return path.join(APP_ROOT, projectId);
15
+ }
16
+
17
+ export async function isAppInstalled(props: { appDir: string }): Promise<boolean> {
18
+ const { appDir } = props;
19
+ // TODO: Could do more sophisticated check
20
+ if (fs.existsSync(appDir)) {
21
+ return true;
22
+ }
23
+ return false;
24
+ }
25
+
26
+ export async function setAppVersion(props: { appDir: string; version: string }) {
27
+ const { appDir, version } = props;
28
+ // TODO: Switch to node-config
29
+ const versionPath = path.join(appDir, RELEASE_FILENAME);
30
+ const versionData = { version };
31
+ await JsSpawner().writeFile(versionPath, JSON.stringify(versionData, null, 2));
32
+ }
33
+
34
+ export async function getAppVersion(props: { appDir: string }) {
35
+ const { appDir } = props;
36
+ const versionPath = path.join(appDir, RELEASE_FILENAME);
37
+ const releaseData = JSON.parse(await JsSpawner().readFile(versionPath));
38
+ return releaseData.version;
39
+ }
40
+
41
+ export async function buildApp(props: { appDir: string }) {
42
+ const { appDir } = props;
43
+ await runCliCmd({
44
+ cmd: ['app', 'configure', '--yes', '--protocol', 'docker:'],
45
+ cwd: appDir,
46
+ });
47
+ await runCliCmd({ cmd: ['app', 'install', '--yes', '--clean', '--pull'], cwd: appDir });
48
+
49
+ // Build standalone image and docker-compose
50
+ if (!fs.existsSync(path.join(appDir, DOCKER_COMPOSE_FILENAME))) {
51
+ await runCliCmd({
52
+ cmd: ['app', 'deploy', '--yes', '--generateDockerCompose'],
53
+ cwd: appDir,
54
+ });
55
+ }
56
+
57
+ const buildOut = await compose.buildAll({ cwd: appDir });
58
+ console.log(buildOut);
59
+ if (buildOut.exitCode !== 0) {
60
+ throw new Error(
61
+ `Failed to build application! stdout=${buildOut.out} stderr=${buildOut.err}`,
62
+ );
63
+ }
64
+ }
@@ -0,0 +1,29 @@
1
+ import { resolve, join, posix } from 'path';
2
+ import { homedir } from 'os';
3
+ const os = require('os');
4
+ const path = require('path');
5
+
6
+ export const ALWAYSAI_CLI_EXECUTABLE_NAME = 'aai-agent';
7
+ export const DOT_ALWAYSAI_DIR = join(homedir(), '.alwaysai');
8
+ export const AAI_DIR = path.join(os.homedir(), '.alwaysai');
9
+
10
+ export const DEVICE_TOKEN_FILE_NAME = 'alwaysai.credentials.json';
11
+ export const DEVICE_CONFIG_FILE_NAME = 'alwaysai.config.json';
12
+
13
+ export const DEVICE_TOKEN_DIR_LINUX = posix.join('~', '.config', 'alwaysai');
14
+ export const DEVICE_CERT_AND_KEY_DIR_LINUX = posix.join(
15
+ DEVICE_TOKEN_DIR_LINUX,
16
+ 'certificates',
17
+ );
18
+
19
+ export const DEVICE_CERTIFICATE_FILE_NAME = 'alwaysai.certificate.pem.crt';
20
+ export const DEVICE_PRIVATE_KEY_FILE_NAME = 'alwaysai.private.pem.key';
21
+ export const DEVICE_PUBLIC_KEY_FILE_NAME = 'alwaysai.public.pem.key';
22
+ export const DEVICE_ROOT_CERT_FILE_NAME = 'alwaysai.root.pem';
23
+
24
+ export const SUPPORTED_JETPACK_VERSION = 4.4;
25
+ export const JETPACK_IMAGE_LINK =
26
+ 'https://developer.nvidia.com/jetson-nano-sd-card-image-44';
27
+
28
+ export const PLEASE_REPORT_THIS_ERROR_MESSAGE =
29
+ 'Please report this error message to support@alwaysai.co';
@@ -0,0 +1,16 @@
1
+ const osu = require('node-os-utils');
2
+
3
+ export async function getCpuUtil(): Promise<number> {
4
+ const cpuUsage = await osu.cpu.usage();
5
+ return cpuUsage;
6
+ }
7
+
8
+ export async function getDiskUtil(): Promise<number> {
9
+ const driveInfo = await osu.drive.info();
10
+ return parseFloat(driveInfo.usedPercentage);
11
+ }
12
+
13
+ export async function getMemUtil(): Promise<number> {
14
+ const memInfo = await osu.mem.info();
15
+ return 100.0 - memInfo.freeMemPercentage;
16
+ }
@@ -0,0 +1,12 @@
1
+ import { JsSpawner } from '../util/spawner/js-spawner';
2
+
3
+ export async function runDockerLogin(props: { token: string }) {
4
+ const { token } = props;
5
+ const spawner = JsSpawner();
6
+ const server = '994534263224.dkr.ecr.us-west-2.amazonaws.com';
7
+ const output = await spawner.run({
8
+ exe: 'docker',
9
+ args: ['login', '--username', 'AWS', '--password', token, server],
10
+ });
11
+ return output;
12
+ }
@@ -0,0 +1,12 @@
1
+ import { JsSpawner } from '../util/spawner/js-spawner';
2
+
3
+ export async function runDockerComposeCmd(props: { args: string[]; dir: string }) {
4
+ const { args, dir } = props;
5
+ const spawner = JsSpawner();
6
+ const output = await spawner.run({
7
+ exe: 'docker-compose',
8
+ args,
9
+ cwd: dir,
10
+ });
11
+ return output;
12
+ }
@@ -0,0 +1,20 @@
1
+ import { platform } from 'os';
2
+
3
+ export const ALWAYSAI_OS_PLATFORM = parseOsPlatform(process.env.ALWAYSAI_OS_PLATFORM);
4
+ export const ALWAYSAI_SHOW_HIDDEN = parseBoolean(process.env.ALWAYSAI_SHOW_HIDDEN);
5
+
6
+ function parseOsPlatform(str: string | undefined): NodeJS.Platform {
7
+ switch (str) {
8
+ case 'win32':
9
+ case 'darwin':
10
+ case 'linux': {
11
+ return str;
12
+ }
13
+ default:
14
+ return platform();
15
+ }
16
+ }
17
+
18
+ function parseBoolean(str: string | undefined) {
19
+ return str === '1';
20
+ }
package/src/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+
3
+ // This file is the entry point for when this package is run as a CLI. Though
4
+ // this file is not executable, the above "shebang" line is necessary as an
5
+ // indicator to npm that this file is a Node.js script, not a shell script e.g.
6
+
7
+ import { runCliAndExit } from '@alwaysai/alwayscli';
8
+ import { root } from './root';
9
+ import { runWebInterface } from './web/web-interface';
10
+
11
+ if (module === require.main) {
12
+ console.log('Starting alwaysAI Device Agent');
13
+ if (process.env.ALWAYSAI_DEVICE_AGENT_MODE === 'cli') {
14
+ runCliAndExit(root, {});
15
+ } else {
16
+ runWebInterface();
17
+ }
18
+ }
package/src/root.ts ADDED
@@ -0,0 +1,18 @@
1
+ const winston = require('winston');
2
+
3
+ import { CliBranch } from '@alwaysai/alwayscli';
4
+
5
+ import { subcommands } from './subcommands';
6
+ import { ALWAYSAI_CLI_EXECUTABLE_NAME } from './constants';
7
+
8
+ winston.configure({
9
+ level: process.env.AAI_LOG_LEVEL || 'info',
10
+ transports: [new winston.transports.Console()],
11
+ format: winston.format.simple(),
12
+ });
13
+
14
+ export const root = CliBranch({
15
+ name: ALWAYSAI_CLI_EXECUTABLE_NAME,
16
+ description: 'Manage your alwaysAI production device',
17
+ subcommands,
18
+ });
@@ -0,0 +1,216 @@
1
+ import { CliLeaf, CliStringInput, CliTerseError } from '@alwaysai/alwayscli';
2
+ import {
3
+ getAppLogs,
4
+ getAppStatus,
5
+ getInstalledApps,
6
+ installApp,
7
+ rollbackApp,
8
+ startApp,
9
+ stopApp,
10
+ uninstallApp,
11
+ } from '../../application-control/application-control';
12
+ import { addModel, removeModel, updateModels } from '../../application-control/models';
13
+
14
+ export const listAppsCliLeaf = CliLeaf({
15
+ name: 'list',
16
+ description: 'List all installed apps',
17
+ namedInputs: {},
18
+ async action(_, opts) {
19
+ const apps = await getInstalledApps();
20
+ console.table(apps);
21
+ },
22
+ });
23
+
24
+ export const installAppCliLeaf = CliLeaf({
25
+ name: 'install',
26
+ description: 'Install an alwaysAI app from a project',
27
+ namedInputs: {
28
+ project: CliStringInput({
29
+ description: 'Project Id',
30
+ }),
31
+ dockerComposeFile: CliStringInput({
32
+ description: 'Contents of the docker compose file',
33
+ }),
34
+ },
35
+ async action(_, opts) {
36
+ const { project } = opts;
37
+ if (project === undefined) {
38
+ throw new CliTerseError('--project is required');
39
+ }
40
+ // await installApp({ projectId: project, appPkg });
41
+ throw new CliTerseError('Install not yet implemented!');
42
+ },
43
+ });
44
+
45
+ export const getAppStatusCliLeaf = CliLeaf({
46
+ name: 'status',
47
+ description: 'Get the status of an installed alwaysAI app',
48
+ namedInputs: {
49
+ project: CliStringInput({
50
+ description: 'Project Id',
51
+ }),
52
+ },
53
+ async action(_, opts) {
54
+ const { project } = opts;
55
+ if (project === undefined) {
56
+ throw new CliTerseError('--project is required');
57
+ }
58
+ const appStatus = await getAppStatus({ projectId: project });
59
+ console.log(appStatus);
60
+ },
61
+ });
62
+
63
+ export const startAppCliLeaf = CliLeaf({
64
+ name: 'start',
65
+ description: 'Start an installed alwaysAI app',
66
+ namedInputs: {
67
+ project: CliStringInput({
68
+ description: 'Project Id',
69
+ }),
70
+ dockerLoginToken: CliStringInput({
71
+ description: 'Docker login token',
72
+ }),
73
+ },
74
+ async action(_, opts) {
75
+ const { project, dockerLoginToken } = opts;
76
+ if (project === undefined) {
77
+ throw new CliTerseError('--project is required');
78
+ }
79
+ await startApp({ projectId: project, dockerLoginToken });
80
+ },
81
+ });
82
+
83
+ export const getAppLogsCliLeaf = CliLeaf({
84
+ name: 'logs',
85
+ description: 'Get logs for an application',
86
+ namedInputs: {
87
+ project: CliStringInput({
88
+ description: 'Project Id',
89
+ }),
90
+ },
91
+ async action(_, opts) {
92
+ const { project } = opts;
93
+ if (project === undefined) {
94
+ throw new CliTerseError('--project is required');
95
+ }
96
+ const readable = await getAppLogs({ projectId: project });
97
+ readable.setEncoding('utf8');
98
+ for await (const chunk of readable) {
99
+ console.log(chunk);
100
+ }
101
+ },
102
+ });
103
+
104
+ export const stopAppCliLeaf = CliLeaf({
105
+ name: 'stop',
106
+ description: 'Stop a running alwaysAI app',
107
+ namedInputs: {
108
+ project: CliStringInput({
109
+ description: 'Project Id',
110
+ }),
111
+ },
112
+ async action(_, opts) {
113
+ const { project } = opts;
114
+ if (project === undefined) {
115
+ throw new CliTerseError('Project ID is required');
116
+ }
117
+ await stopApp({ projectId: project });
118
+ },
119
+ });
120
+
121
+ export const uninstallAppCliLeaf = CliLeaf({
122
+ name: 'uninstall',
123
+ description: 'Remove an alwaysAI app',
124
+ namedInputs: {
125
+ project: CliStringInput({
126
+ description: 'Project Id',
127
+ }),
128
+ },
129
+ async action(_, opts) {
130
+ const { project } = opts;
131
+ if (project === undefined) {
132
+ throw new CliTerseError('Project ID is required');
133
+ }
134
+ await uninstallApp({ projectId: project });
135
+ },
136
+ });
137
+
138
+ export const rollbackAppCliLeaf = CliLeaf({
139
+ name: 'rollback',
140
+ description: 'Rollback an alwaysAI app to the previous version',
141
+ namedInputs: {
142
+ project: CliStringInput({
143
+ description: 'Project Id',
144
+ }),
145
+ },
146
+ async action(_, opts) {
147
+ const { project } = opts;
148
+ if (project === undefined) {
149
+ throw new CliTerseError('Project ID is required');
150
+ }
151
+ await rollbackApp({ projectId: project });
152
+ },
153
+ });
154
+
155
+ export const addModelCliLeaf = CliLeaf({
156
+ name: 'add-model',
157
+ description: 'Add a model to an alwaysAI app',
158
+ namedInputs: {
159
+ project: CliStringInput({
160
+ description: 'Project Id',
161
+ }),
162
+ model: CliStringInput({
163
+ description: 'Model Id',
164
+ }),
165
+ },
166
+ async action(_, opts) {
167
+ const { project, model } = opts;
168
+ if (project === undefined) {
169
+ throw new CliTerseError('--project is required');
170
+ }
171
+ if (model === undefined) {
172
+ throw new CliTerseError('--model is required');
173
+ }
174
+ await addModel({ projectId: project, modelId: model });
175
+ },
176
+ });
177
+
178
+ export const removeModelCliLeaf = CliLeaf({
179
+ name: 'remove-model',
180
+ description: 'Remove a model from an alwaysAI app',
181
+ namedInputs: {
182
+ project: CliStringInput({
183
+ description: 'Project Id',
184
+ }),
185
+ model: CliStringInput({
186
+ description: 'Model Id',
187
+ }),
188
+ },
189
+ async action(_, opts) {
190
+ const { project, model } = opts;
191
+ if (project === undefined) {
192
+ throw new CliTerseError('--project is required');
193
+ }
194
+ if (model === undefined) {
195
+ throw new CliTerseError('--model is required');
196
+ }
197
+ await removeModel({ projectId: project, modelId: model });
198
+ },
199
+ });
200
+
201
+ export const updateModelsCliLeaf = CliLeaf({
202
+ name: 'update-models',
203
+ description: 'Update all models for an alwaysAI app',
204
+ namedInputs: {
205
+ project: CliStringInput({
206
+ description: 'Project Id',
207
+ }),
208
+ },
209
+ async action(_, opts) {
210
+ const { project } = opts;
211
+ if (project === undefined) {
212
+ throw new CliTerseError('--project is required');
213
+ }
214
+ await updateModels({ projectId: project });
215
+ },
216
+ });