straylight-ai 1.0.0 → 1.0.1
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 +2 -1
- package/bin/cli.js +2 -1
- package/dist/commands/setup.d.ts +5 -3
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +24 -9
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/upgrade.d.ts +7 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +45 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/docker.d.ts +14 -2
- package/dist/docker.d.ts.map +1 -1
- package/dist/docker.js +52 -1
- package/dist/docker.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/commands.test.ts +16 -0
- package/src/commands/setup.ts +28 -8
- package/src/commands/upgrade.ts +59 -0
- package/src/docker.ts +55 -2
package/README.md
CHANGED
|
@@ -53,7 +53,8 @@ Any MCP-compatible AI coding assistant works. The MCP server speaks the standard
|
|
|
53
53
|
|
|
54
54
|
| Command | Description |
|
|
55
55
|
|---------|-------------|
|
|
56
|
-
| `npx straylight-ai` | Full setup (pull, start, register) |
|
|
56
|
+
| `npx straylight-ai` | Full setup (pull latest, start, register) |
|
|
57
|
+
| `npx straylight-ai upgrade` | Pull latest image and replace container (data preserved) |
|
|
57
58
|
| `npx straylight-ai start` | Start the container |
|
|
58
59
|
| `npx straylight-ai stop` | Stop the container |
|
|
59
60
|
| `npx straylight-ai status` | Check health and service status |
|
package/bin/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ const path = require("path");
|
|
|
5
5
|
|
|
6
6
|
const command = process.argv[2] || "setup";
|
|
7
7
|
|
|
8
|
-
const validCommands = ["setup", "start", "stop", "status", "mcp"];
|
|
8
|
+
const validCommands = ["setup", "start", "stop", "status", "upgrade", "mcp"];
|
|
9
9
|
|
|
10
10
|
if (!validCommands.includes(command)) {
|
|
11
11
|
console.error(`Unknown command: ${command}`);
|
|
@@ -27,6 +27,7 @@ if (command === "setup") runner = commandModule.runSetup;
|
|
|
27
27
|
else if (command === "start") runner = commandModule.runStart;
|
|
28
28
|
else if (command === "stop") runner = commandModule.runStop;
|
|
29
29
|
else if (command === "status") runner = commandModule.runStatus;
|
|
30
|
+
else if (command === "upgrade") runner = commandModule.runUpgrade;
|
|
30
31
|
|
|
31
32
|
if (typeof runner !== "function") {
|
|
32
33
|
console.error(`Internal error: could not find runner for command "${command}"`);
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Full bootstrap: pull image
|
|
3
|
-
* health check, register MCP server, and open
|
|
2
|
+
* Full bootstrap: pull latest image, create/start container (upgrading if
|
|
3
|
+
* the image changed), wait for health check, register MCP server, and open
|
|
4
|
+
* the browser.
|
|
4
5
|
*
|
|
5
6
|
* This operation is idempotent: calling it when the container is already
|
|
6
|
-
* running will skip the create/start steps and go
|
|
7
|
+
* running on the latest image will skip the create/start steps and go
|
|
8
|
+
* straight to health + open.
|
|
7
9
|
*/
|
|
8
10
|
export declare function runSetup(): Promise<void>;
|
|
9
11
|
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAmBA;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CA+D9C"}
|
package/dist/commands/setup.js
CHANGED
|
@@ -10,11 +10,13 @@ const HEALTH_URL = "http://localhost:9470/api/v1/health";
|
|
|
10
10
|
const HEALTH_TIMEOUT_MS = 30_000;
|
|
11
11
|
const UI_URL = "http://localhost:9470";
|
|
12
12
|
/**
|
|
13
|
-
* Full bootstrap: pull image
|
|
14
|
-
* health check, register MCP server, and open
|
|
13
|
+
* Full bootstrap: pull latest image, create/start container (upgrading if
|
|
14
|
+
* the image changed), wait for health check, register MCP server, and open
|
|
15
|
+
* the browser.
|
|
15
16
|
*
|
|
16
17
|
* This operation is idempotent: calling it when the container is already
|
|
17
|
-
* running will skip the create/start steps and go
|
|
18
|
+
* running on the latest image will skip the create/start steps and go
|
|
19
|
+
* straight to health + open.
|
|
18
20
|
*/
|
|
19
21
|
async function runSetup() {
|
|
20
22
|
const runtime = (0, docker_js_1.detectRuntime)();
|
|
@@ -24,17 +26,30 @@ async function runSetup() {
|
|
|
24
26
|
"or Podman: https://podman.io/getting-started/installation");
|
|
25
27
|
}
|
|
26
28
|
console.log(`Using container runtime: ${runtime}`);
|
|
29
|
+
// Always pull the latest image first.
|
|
30
|
+
console.log("Pulling latest Straylight-AI image...");
|
|
31
|
+
(0, docker_js_1.pullImage)(runtime);
|
|
27
32
|
const status = await (0, docker_js_1.getContainerStatus)(runtime);
|
|
28
33
|
if (status === "not_found") {
|
|
29
34
|
console.log("Creating and starting Straylight-AI container...");
|
|
30
35
|
(0, child_process_1.execSync)((0, docker_js_1.buildRunCommand)(runtime), { stdio: "inherit" });
|
|
31
36
|
}
|
|
32
|
-
else if (status === "stopped") {
|
|
33
|
-
console.log("Starting existing Straylight-AI container...");
|
|
34
|
-
(0, child_process_1.execSync)((0, docker_js_1.buildStartCommand)(runtime), { stdio: "inherit" });
|
|
35
|
-
}
|
|
36
37
|
else {
|
|
37
|
-
|
|
38
|
+
// Container exists — check if it needs upgrading.
|
|
39
|
+
const containerImage = (0, docker_js_1.getContainerImageId)(runtime);
|
|
40
|
+
const latestImage = (0, docker_js_1.getImageId)(runtime);
|
|
41
|
+
if (containerImage && latestImage && containerImage !== latestImage) {
|
|
42
|
+
console.log("New image available — upgrading container...");
|
|
43
|
+
(0, docker_js_1.removeContainer)(runtime);
|
|
44
|
+
(0, child_process_1.execSync)((0, docker_js_1.buildRunCommand)(runtime), { stdio: "inherit" });
|
|
45
|
+
}
|
|
46
|
+
else if (status === "stopped") {
|
|
47
|
+
console.log("Starting existing Straylight-AI container...");
|
|
48
|
+
(0, child_process_1.execSync)((0, docker_js_1.buildStartCommand)(runtime), { stdio: "inherit" });
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.log("Straylight-AI container is already running (up to date).");
|
|
52
|
+
}
|
|
38
53
|
}
|
|
39
54
|
console.log("Waiting for Straylight-AI to be ready...");
|
|
40
55
|
await (0, health_js_1.waitForHealth)(HEALTH_URL, HEALTH_TIMEOUT_MS);
|
|
@@ -53,7 +68,7 @@ async function runSetup() {
|
|
|
53
68
|
"",
|
|
54
69
|
"Next steps:",
|
|
55
70
|
" 1. Open http://localhost:9470 in your browser",
|
|
56
|
-
" 2. Add your service credentials via the
|
|
71
|
+
" 2. Add your service credentials via the Services page",
|
|
57
72
|
" 3. Use Claude Code with the straylight-ai MCP server",
|
|
58
73
|
"",
|
|
59
74
|
].join("\n"));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":";;AA4BA,4BA+DC;AA3FD,iDAAyC;AACzC,4CASsB;AACtB,4CAA6C;AAC7C,wDAAiF;AACjF,wCAAyC;AAEzC,MAAM,UAAU,GAAG,qCAAqC,CAAC;AACzD,MAAM,iBAAiB,GAAG,MAAM,CAAC;AACjC,MAAM,MAAM,GAAG,uBAAuB,CAAC;AAEvC;;;;;;;;GAQG;AACI,KAAK,UAAU,QAAQ;IAC5B,MAAM,OAAO,GAAG,IAAA,yBAAa,GAAE,CAAC;IAChC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,qDAAqD;YACnD,+DAA+D;YAC/D,2DAA2D,CAC9D,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,4BAA4B,OAAO,EAAE,CAAC,CAAC;IAEnD,sCAAsC;IACtC,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;IACrD,IAAA,qBAAS,EAAC,OAAO,CAAC,CAAC;IAEnB,MAAM,MAAM,GAAG,MAAM,IAAA,8BAAkB,EAAC,OAAO,CAAC,CAAC;IAEjD,IAAI,MAAM,KAAK,WAAW,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC;QAChE,IAAA,wBAAQ,EAAC,IAAA,2BAAe,EAAC,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAC3D,CAAC;SAAM,CAAC;QACN,kDAAkD;QAClD,MAAM,cAAc,GAAG,IAAA,+BAAmB,EAAC,OAAO,CAAC,CAAC;QACpD,MAAM,WAAW,GAAG,IAAA,sBAAU,EAAC,OAAO,CAAC,CAAC;QAExC,IAAI,cAAc,IAAI,WAAW,IAAI,cAAc,KAAK,WAAW,EAAE,CAAC;YACpE,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;YAC5D,IAAA,2BAAe,EAAC,OAAO,CAAC,CAAC;YACzB,IAAA,wBAAQ,EAAC,IAAA,2BAAe,EAAC,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAC3D,CAAC;aAAM,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAChC,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;YAC5D,IAAA,wBAAQ,EAAC,IAAA,6BAAiB,EAAC,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAC7D,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,0DAA0D,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;IACxD,MAAM,IAAA,yBAAa,EAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IAEvC,MAAM,UAAU,GAAG,MAAM,IAAA,6BAAW,GAAE,CAAC;IACvC,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;IACzD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,IAAA,gDAA8B,GAAE,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,IAAA,qBAAW,EAAC,MAAM,CAAC,CAAC;IAE1B,OAAO,CAAC,GAAG,CACT;QACE,EAAE;QACF,mDAAmD;QACnD,EAAE;QACF,aAAa;QACb,iDAAiD;QACjD,yDAAyD;QACzD,wDAAwD;QACxD,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upgrade Straylight-AI to the latest image.
|
|
3
|
+
* Pulls the latest image, stops and removes the old container (preserving
|
|
4
|
+
* the data volume), and starts a new container from the updated image.
|
|
5
|
+
*/
|
|
6
|
+
export declare function runUpgrade(): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=upgrade.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../src/commands/upgrade.ts"],"names":[],"mappings":"AAeA;;;;GAIG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAsChD"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runUpgrade = runUpgrade;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const docker_js_1 = require("../docker.js");
|
|
6
|
+
const health_js_1 = require("../health.js");
|
|
7
|
+
const HEALTH_URL = "http://localhost:9470/api/v1/health";
|
|
8
|
+
const HEALTH_TIMEOUT_MS = 30_000;
|
|
9
|
+
/**
|
|
10
|
+
* Upgrade Straylight-AI to the latest image.
|
|
11
|
+
* Pulls the latest image, stops and removes the old container (preserving
|
|
12
|
+
* the data volume), and starts a new container from the updated image.
|
|
13
|
+
*/
|
|
14
|
+
async function runUpgrade() {
|
|
15
|
+
const runtime = (0, docker_js_1.detectRuntime)();
|
|
16
|
+
if (!runtime) {
|
|
17
|
+
throw new Error("Neither Docker nor Podman was found on your PATH.\n" +
|
|
18
|
+
"Install Docker Desktop: https://docs.docker.com/get-docker/\n" +
|
|
19
|
+
"or Podman: https://podman.io/getting-started/installation");
|
|
20
|
+
}
|
|
21
|
+
console.log(`Using container runtime: ${runtime}`);
|
|
22
|
+
// Pull the latest image.
|
|
23
|
+
console.log("Pulling latest Straylight-AI image...");
|
|
24
|
+
const changed = (0, docker_js_1.pullImage)(runtime);
|
|
25
|
+
const status = await (0, docker_js_1.getContainerStatus)(runtime);
|
|
26
|
+
if (status === "not_found") {
|
|
27
|
+
console.log("No existing container found. Creating...");
|
|
28
|
+
(0, child_process_1.execSync)((0, docker_js_1.buildRunCommand)(runtime), { stdio: "inherit" });
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const containerImage = (0, docker_js_1.getContainerImageId)(runtime);
|
|
32
|
+
const latestImage = (0, docker_js_1.getImageId)(runtime);
|
|
33
|
+
if (!changed && containerImage === latestImage) {
|
|
34
|
+
console.log("Already running the latest image. Nothing to upgrade.");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
console.log("Stopping and replacing container (data volume preserved)...");
|
|
38
|
+
(0, docker_js_1.removeContainer)(runtime);
|
|
39
|
+
(0, child_process_1.execSync)((0, docker_js_1.buildRunCommand)(runtime), { stdio: "inherit" });
|
|
40
|
+
}
|
|
41
|
+
console.log("Waiting for Straylight-AI to be ready...");
|
|
42
|
+
await (0, health_js_1.waitForHealth)(HEALTH_URL, HEALTH_TIMEOUT_MS);
|
|
43
|
+
console.log("Straylight-AI upgraded and running at http://localhost:9470");
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=upgrade.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../src/commands/upgrade.ts"],"names":[],"mappings":";;AAoBA,gCAsCC;AA1DD,iDAAyC;AACzC,4CAQsB;AACtB,4CAA6C;AAE7C,MAAM,UAAU,GAAG,qCAAqC,CAAC;AACzD,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAEjC;;;;GAIG;AACI,KAAK,UAAU,UAAU;IAC9B,MAAM,OAAO,GAAG,IAAA,yBAAa,GAAE,CAAC;IAChC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,qDAAqD;YACnD,+DAA+D;YAC/D,2DAA2D,CAC9D,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,4BAA4B,OAAO,EAAE,CAAC,CAAC;IAEnD,yBAAyB;IACzB,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,IAAA,qBAAS,EAAC,OAAO,CAAC,CAAC;IAEnC,MAAM,MAAM,GAAG,MAAM,IAAA,8BAAkB,EAAC,OAAO,CAAC,CAAC;IAEjD,IAAI,MAAM,KAAK,WAAW,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;QACxD,IAAA,wBAAQ,EAAC,IAAA,2BAAe,EAAC,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAC3D,CAAC;SAAM,CAAC;QACN,MAAM,cAAc,GAAG,IAAA,+BAAmB,EAAC,OAAO,CAAC,CAAC;QACpD,MAAM,WAAW,GAAG,IAAA,sBAAU,EAAC,OAAO,CAAC,CAAC;QAExC,IAAI,CAAC,OAAO,IAAI,cAAc,KAAK,WAAW,EAAE,CAAC;YAC/C,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;YACrE,OAAO;QACT,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,6DAA6D,CAAC,CAAC;QAC3E,IAAA,2BAAe,EAAC,OAAO,CAAC,CAAC;QACzB,IAAA,wBAAQ,EAAC,IAAA,2BAAe,EAAC,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;IACxD,MAAM,IAAA,yBAAa,EAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,6DAA6D,CAAC,CAAC;AAC7E,CAAC"}
|
package/dist/docker.d.ts
CHANGED
|
@@ -36,7 +36,19 @@ export declare function buildStartCommand(runtime: string): string;
|
|
|
36
36
|
*/
|
|
37
37
|
export declare function buildStopCommand(runtime: string): string;
|
|
38
38
|
/**
|
|
39
|
-
* Pull the container image.
|
|
39
|
+
* Pull the container image. Returns true if a newer image was downloaded.
|
|
40
40
|
*/
|
|
41
|
-
export declare function pullImage(runtime: string):
|
|
41
|
+
export declare function pullImage(runtime: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Returns the image ID for the container image, or null if not present.
|
|
44
|
+
*/
|
|
45
|
+
export declare function getImageId(runtime: string): string | null;
|
|
46
|
+
/**
|
|
47
|
+
* Returns the image ID that a running/stopped container was created from.
|
|
48
|
+
*/
|
|
49
|
+
export declare function getContainerImageId(runtime: string): string | null;
|
|
50
|
+
/**
|
|
51
|
+
* Stop and remove the container (preserves the named volume).
|
|
52
|
+
*/
|
|
53
|
+
export declare function removeContainer(runtime: string): void;
|
|
42
54
|
//# sourceMappingURL=docker.d.ts.map
|
package/dist/docker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../src/docker.ts"],"names":[],"mappings":"AAEA,oCAAoC;AACpC,eAAO,MAAM,cAAc,kBAAkB,CAAC;AAE9C,mCAAmC;AACnC,eAAO,MAAM,eAAe,2CAA2C,CAAC;AAExE,8CAA8C;AAC9C,eAAO,MAAM,cAAc,OAAO,CAAC;AAEnC,gEAAgE;AAChE,eAAO,MAAM,WAAW,uBAAuB,CAAC;AAEhD,8BAA8B;AAC9B,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;AAElE,mCAAmC;AACnC,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE1C;;;GAGG;AACH,wBAAgB,aAAa,IAAI,OAAO,GAAG,IAAI,CAU9C;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,eAAe,CAAC,CAe1B;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAE1E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAUvD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../src/docker.ts"],"names":[],"mappings":"AAEA,oCAAoC;AACpC,eAAO,MAAM,cAAc,kBAAkB,CAAC;AAE9C,mCAAmC;AACnC,eAAO,MAAM,eAAe,2CAA2C,CAAC;AAExE,8CAA8C;AAC9C,eAAO,MAAM,cAAc,OAAO,CAAC;AAEnC,gEAAgE;AAChE,eAAO,MAAM,WAAW,uBAAuB,CAAC;AAEhD,8BAA8B;AAC9B,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;AAElE,mCAAmC;AACnC,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE1C;;;GAGG;AACH,wBAAgB,aAAa,IAAI,OAAO,GAAG,IAAI,CAU9C;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,eAAe,CAAC,CAe1B;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAE1E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAUvD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAKlD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYzD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYlE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAWrD"}
|
package/dist/docker.js
CHANGED
|
@@ -8,6 +8,9 @@ exports.buildRunCommand = buildRunCommand;
|
|
|
8
8
|
exports.buildStartCommand = buildStartCommand;
|
|
9
9
|
exports.buildStopCommand = buildStopCommand;
|
|
10
10
|
exports.pullImage = pullImage;
|
|
11
|
+
exports.getImageId = getImageId;
|
|
12
|
+
exports.getContainerImageId = getContainerImageId;
|
|
13
|
+
exports.removeContainer = removeContainer;
|
|
11
14
|
const child_process_1 = require("child_process");
|
|
12
15
|
/** Name of the managed container */
|
|
13
16
|
exports.CONTAINER_NAME = "straylight-ai";
|
|
@@ -83,9 +86,57 @@ function buildStopCommand(runtime) {
|
|
|
83
86
|
return `${runtime} stop ${exports.CONTAINER_NAME}`;
|
|
84
87
|
}
|
|
85
88
|
/**
|
|
86
|
-
* Pull the container image.
|
|
89
|
+
* Pull the container image. Returns true if a newer image was downloaded.
|
|
87
90
|
*/
|
|
88
91
|
function pullImage(runtime) {
|
|
92
|
+
const before = getImageId(runtime);
|
|
89
93
|
(0, child_process_1.execSync)(`${runtime} pull ${exports.CONTAINER_IMAGE}`, { stdio: "inherit" });
|
|
94
|
+
const after = getImageId(runtime);
|
|
95
|
+
return before !== after;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Returns the image ID for the container image, or null if not present.
|
|
99
|
+
*/
|
|
100
|
+
function getImageId(runtime) {
|
|
101
|
+
try {
|
|
102
|
+
return (0, child_process_1.execSync)(`${runtime} image inspect --format "{{.Id}}" ${exports.CONTAINER_IMAGE}`, { stdio: "pipe" })
|
|
103
|
+
.toString()
|
|
104
|
+
.trim()
|
|
105
|
+
.replace(/^"|"$/g, "");
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Returns the image ID that a running/stopped container was created from.
|
|
113
|
+
*/
|
|
114
|
+
function getContainerImageId(runtime) {
|
|
115
|
+
try {
|
|
116
|
+
return (0, child_process_1.execSync)(`${runtime} inspect --format "{{.Image}}" ${exports.CONTAINER_NAME}`, { stdio: "pipe" })
|
|
117
|
+
.toString()
|
|
118
|
+
.trim()
|
|
119
|
+
.replace(/^"|"$/g, "");
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Stop and remove the container (preserves the named volume).
|
|
127
|
+
*/
|
|
128
|
+
function removeContainer(runtime) {
|
|
129
|
+
try {
|
|
130
|
+
(0, child_process_1.execSync)(`${runtime} stop ${exports.CONTAINER_NAME}`, { stdio: "pipe" });
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// already stopped
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
(0, child_process_1.execSync)(`${runtime} rm ${exports.CONTAINER_NAME}`, { stdio: "pipe" });
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// already removed
|
|
140
|
+
}
|
|
90
141
|
}
|
|
91
142
|
//# sourceMappingURL=docker.js.map
|
package/dist/docker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docker.js","sourceRoot":"","sources":["../src/docker.ts"],"names":[],"mappings":";;;AAwBA,sCAUC;AAKD,gDAiBC;AAKD,gDAEC;AAKD,0CAUC;AAKD,8CAEC;AAKD,4CAEC;AAKD,
|
|
1
|
+
{"version":3,"file":"docker.js","sourceRoot":"","sources":["../src/docker.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -14,6 +14,10 @@ vi.mock("../docker.js", () => ({
|
|
|
14
14
|
buildRunCommand: vi.fn(),
|
|
15
15
|
buildStartCommand: vi.fn(),
|
|
16
16
|
buildStopCommand: vi.fn(),
|
|
17
|
+
pullImage: vi.fn(),
|
|
18
|
+
getImageId: vi.fn(),
|
|
19
|
+
getContainerImageId: vi.fn(),
|
|
20
|
+
removeContainer: vi.fn(),
|
|
17
21
|
}));
|
|
18
22
|
|
|
19
23
|
vi.mock("../health.js", () => ({
|
|
@@ -40,6 +44,10 @@ import {
|
|
|
40
44
|
buildRunCommand,
|
|
41
45
|
buildStartCommand,
|
|
42
46
|
buildStopCommand,
|
|
47
|
+
pullImage,
|
|
48
|
+
getImageId,
|
|
49
|
+
getContainerImageId,
|
|
50
|
+
removeContainer,
|
|
43
51
|
} from "../docker.js";
|
|
44
52
|
import { waitForHealth, checkHealth } from "../health.js";
|
|
45
53
|
import { registerMCP, isClaudeAvailable } from "../mcp-register.js";
|
|
@@ -56,6 +64,10 @@ const mockIsContainerRunning = vi.mocked(isContainerRunning);
|
|
|
56
64
|
const mockBuildRunCommand = vi.mocked(buildRunCommand);
|
|
57
65
|
const mockBuildStartCommand = vi.mocked(buildStartCommand);
|
|
58
66
|
const mockBuildStopCommand = vi.mocked(buildStopCommand);
|
|
67
|
+
const mockPullImage = vi.mocked(pullImage);
|
|
68
|
+
const mockGetImageId = vi.mocked(getImageId);
|
|
69
|
+
const mockGetContainerImageId = vi.mocked(getContainerImageId);
|
|
70
|
+
const mockRemoveContainer = vi.mocked(removeContainer);
|
|
59
71
|
const mockWaitForHealth = vi.mocked(waitForHealth);
|
|
60
72
|
const mockCheckHealth = vi.mocked(checkHealth);
|
|
61
73
|
const mockRegisterMCP = vi.mocked(registerMCP);
|
|
@@ -81,6 +93,10 @@ beforeEach(() => {
|
|
|
81
93
|
mockIsClaudeAvailable.mockReturnValue(false);
|
|
82
94
|
mockOpenBrowser.mockResolvedValue(undefined);
|
|
83
95
|
mockExecSync.mockReturnValue(Buffer.from(""));
|
|
96
|
+
mockPullImage.mockReturnValue(false);
|
|
97
|
+
mockGetImageId.mockReturnValue("sha256:abc123");
|
|
98
|
+
mockGetContainerImageId.mockReturnValue("sha256:abc123");
|
|
99
|
+
mockRemoveContainer.mockReturnValue(undefined);
|
|
84
100
|
});
|
|
85
101
|
|
|
86
102
|
afterEach(() => {
|
package/src/commands/setup.ts
CHANGED
|
@@ -2,6 +2,10 @@ import { execSync } from "child_process";
|
|
|
2
2
|
import {
|
|
3
3
|
detectRuntime,
|
|
4
4
|
getContainerStatus,
|
|
5
|
+
getContainerImageId,
|
|
6
|
+
getImageId,
|
|
7
|
+
pullImage,
|
|
8
|
+
removeContainer,
|
|
5
9
|
buildRunCommand,
|
|
6
10
|
buildStartCommand,
|
|
7
11
|
} from "../docker.js";
|
|
@@ -14,11 +18,13 @@ const HEALTH_TIMEOUT_MS = 30_000;
|
|
|
14
18
|
const UI_URL = "http://localhost:9470";
|
|
15
19
|
|
|
16
20
|
/**
|
|
17
|
-
* Full bootstrap: pull image
|
|
18
|
-
* health check, register MCP server, and open
|
|
21
|
+
* Full bootstrap: pull latest image, create/start container (upgrading if
|
|
22
|
+
* the image changed), wait for health check, register MCP server, and open
|
|
23
|
+
* the browser.
|
|
19
24
|
*
|
|
20
25
|
* This operation is idempotent: calling it when the container is already
|
|
21
|
-
* running will skip the create/start steps and go
|
|
26
|
+
* running on the latest image will skip the create/start steps and go
|
|
27
|
+
* straight to health + open.
|
|
22
28
|
*/
|
|
23
29
|
export async function runSetup(): Promise<void> {
|
|
24
30
|
const runtime = detectRuntime();
|
|
@@ -32,16 +38,30 @@ export async function runSetup(): Promise<void> {
|
|
|
32
38
|
|
|
33
39
|
console.log(`Using container runtime: ${runtime}`);
|
|
34
40
|
|
|
41
|
+
// Always pull the latest image first.
|
|
42
|
+
console.log("Pulling latest Straylight-AI image...");
|
|
43
|
+
pullImage(runtime);
|
|
44
|
+
|
|
35
45
|
const status = await getContainerStatus(runtime);
|
|
36
46
|
|
|
37
47
|
if (status === "not_found") {
|
|
38
48
|
console.log("Creating and starting Straylight-AI container...");
|
|
39
49
|
execSync(buildRunCommand(runtime), { stdio: "inherit" });
|
|
40
|
-
} else if (status === "stopped") {
|
|
41
|
-
console.log("Starting existing Straylight-AI container...");
|
|
42
|
-
execSync(buildStartCommand(runtime), { stdio: "inherit" });
|
|
43
50
|
} else {
|
|
44
|
-
|
|
51
|
+
// Container exists — check if it needs upgrading.
|
|
52
|
+
const containerImage = getContainerImageId(runtime);
|
|
53
|
+
const latestImage = getImageId(runtime);
|
|
54
|
+
|
|
55
|
+
if (containerImage && latestImage && containerImage !== latestImage) {
|
|
56
|
+
console.log("New image available — upgrading container...");
|
|
57
|
+
removeContainer(runtime);
|
|
58
|
+
execSync(buildRunCommand(runtime), { stdio: "inherit" });
|
|
59
|
+
} else if (status === "stopped") {
|
|
60
|
+
console.log("Starting existing Straylight-AI container...");
|
|
61
|
+
execSync(buildStartCommand(runtime), { stdio: "inherit" });
|
|
62
|
+
} else {
|
|
63
|
+
console.log("Straylight-AI container is already running (up to date).");
|
|
64
|
+
}
|
|
45
65
|
}
|
|
46
66
|
|
|
47
67
|
console.log("Waiting for Straylight-AI to be ready...");
|
|
@@ -64,7 +84,7 @@ export async function runSetup(): Promise<void> {
|
|
|
64
84
|
"",
|
|
65
85
|
"Next steps:",
|
|
66
86
|
" 1. Open http://localhost:9470 in your browser",
|
|
67
|
-
" 2. Add your service credentials via the
|
|
87
|
+
" 2. Add your service credentials via the Services page",
|
|
68
88
|
" 3. Use Claude Code with the straylight-ai MCP server",
|
|
69
89
|
"",
|
|
70
90
|
].join("\n")
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import {
|
|
3
|
+
detectRuntime,
|
|
4
|
+
getContainerStatus,
|
|
5
|
+
getContainerImageId,
|
|
6
|
+
getImageId,
|
|
7
|
+
pullImage,
|
|
8
|
+
removeContainer,
|
|
9
|
+
buildRunCommand,
|
|
10
|
+
} from "../docker.js";
|
|
11
|
+
import { waitForHealth } from "../health.js";
|
|
12
|
+
|
|
13
|
+
const HEALTH_URL = "http://localhost:9470/api/v1/health";
|
|
14
|
+
const HEALTH_TIMEOUT_MS = 30_000;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Upgrade Straylight-AI to the latest image.
|
|
18
|
+
* Pulls the latest image, stops and removes the old container (preserving
|
|
19
|
+
* the data volume), and starts a new container from the updated image.
|
|
20
|
+
*/
|
|
21
|
+
export async function runUpgrade(): Promise<void> {
|
|
22
|
+
const runtime = detectRuntime();
|
|
23
|
+
if (!runtime) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
"Neither Docker nor Podman was found on your PATH.\n" +
|
|
26
|
+
"Install Docker Desktop: https://docs.docker.com/get-docker/\n" +
|
|
27
|
+
"or Podman: https://podman.io/getting-started/installation"
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log(`Using container runtime: ${runtime}`);
|
|
32
|
+
|
|
33
|
+
// Pull the latest image.
|
|
34
|
+
console.log("Pulling latest Straylight-AI image...");
|
|
35
|
+
const changed = pullImage(runtime);
|
|
36
|
+
|
|
37
|
+
const status = await getContainerStatus(runtime);
|
|
38
|
+
|
|
39
|
+
if (status === "not_found") {
|
|
40
|
+
console.log("No existing container found. Creating...");
|
|
41
|
+
execSync(buildRunCommand(runtime), { stdio: "inherit" });
|
|
42
|
+
} else {
|
|
43
|
+
const containerImage = getContainerImageId(runtime);
|
|
44
|
+
const latestImage = getImageId(runtime);
|
|
45
|
+
|
|
46
|
+
if (!changed && containerImage === latestImage) {
|
|
47
|
+
console.log("Already running the latest image. Nothing to upgrade.");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log("Stopping and replacing container (data volume preserved)...");
|
|
52
|
+
removeContainer(runtime);
|
|
53
|
+
execSync(buildRunCommand(runtime), { stdio: "inherit" });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log("Waiting for Straylight-AI to be ready...");
|
|
57
|
+
await waitForHealth(HEALTH_URL, HEALTH_TIMEOUT_MS);
|
|
58
|
+
console.log("Straylight-AI upgraded and running at http://localhost:9470");
|
|
59
|
+
}
|
package/src/docker.ts
CHANGED
|
@@ -93,8 +93,61 @@ export function buildStopCommand(runtime: string): string {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
|
-
* Pull the container image.
|
|
96
|
+
* Pull the container image. Returns true if a newer image was downloaded.
|
|
97
97
|
*/
|
|
98
|
-
export function pullImage(runtime: string):
|
|
98
|
+
export function pullImage(runtime: string): boolean {
|
|
99
|
+
const before = getImageId(runtime);
|
|
99
100
|
execSync(`${runtime} pull ${CONTAINER_IMAGE}`, { stdio: "inherit" });
|
|
101
|
+
const after = getImageId(runtime);
|
|
102
|
+
return before !== after;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Returns the image ID for the container image, or null if not present.
|
|
107
|
+
*/
|
|
108
|
+
export function getImageId(runtime: string): string | null {
|
|
109
|
+
try {
|
|
110
|
+
return execSync(
|
|
111
|
+
`${runtime} image inspect --format "{{.Id}}" ${CONTAINER_IMAGE}`,
|
|
112
|
+
{ stdio: "pipe" }
|
|
113
|
+
)
|
|
114
|
+
.toString()
|
|
115
|
+
.trim()
|
|
116
|
+
.replace(/^"|"$/g, "");
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Returns the image ID that a running/stopped container was created from.
|
|
124
|
+
*/
|
|
125
|
+
export function getContainerImageId(runtime: string): string | null {
|
|
126
|
+
try {
|
|
127
|
+
return execSync(
|
|
128
|
+
`${runtime} inspect --format "{{.Image}}" ${CONTAINER_NAME}`,
|
|
129
|
+
{ stdio: "pipe" }
|
|
130
|
+
)
|
|
131
|
+
.toString()
|
|
132
|
+
.trim()
|
|
133
|
+
.replace(/^"|"$/g, "");
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Stop and remove the container (preserves the named volume).
|
|
141
|
+
*/
|
|
142
|
+
export function removeContainer(runtime: string): void {
|
|
143
|
+
try {
|
|
144
|
+
execSync(`${runtime} stop ${CONTAINER_NAME}`, { stdio: "pipe" });
|
|
145
|
+
} catch {
|
|
146
|
+
// already stopped
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
execSync(`${runtime} rm ${CONTAINER_NAME}`, { stdio: "pipe" });
|
|
150
|
+
} catch {
|
|
151
|
+
// already removed
|
|
152
|
+
}
|
|
100
153
|
}
|